• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/compiler/escape-analysis.h"
6 
7 #include "src/codegen/tick-counter.h"
8 #include "src/compiler/frame-states.h"
9 #include "src/compiler/linkage.h"
10 #include "src/compiler/node-matchers.h"
11 #include "src/compiler/operator-properties.h"
12 #include "src/compiler/simplified-operator.h"
13 #include "src/compiler/state-values-utils.h"
14 #include "src/handles/handles-inl.h"
15 #include "src/init/bootstrapper.h"
16 #include "src/objects/map-inl.h"
17 
18 #ifdef DEBUG
19 #define TRACE(...)                                    \
20   do {                                                \
21     if (FLAG_trace_turbo_escape) PrintF(__VA_ARGS__); \
22   } while (false)
23 #else
24 #define TRACE(...)
25 #endif
26 
27 namespace v8 {
28 namespace internal {
29 namespace compiler {
30 
31 template <class T>
32 class Sidetable {
33  public:
Sidetable(Zone * zone)34   explicit Sidetable(Zone* zone) : map_(zone) {}
operator [](const Node * node)35   T& operator[](const Node* node) {
36     NodeId id = node->id();
37     if (id >= map_.size()) {
38       map_.resize(id + 1);
39     }
40     return map_[id];
41   }
42 
43  private:
44   ZoneVector<T> map_;
45 };
46 
47 template <class T>
48 class SparseSidetable {
49  public:
SparseSidetable(Zone * zone,T def_value=T ())50   explicit SparseSidetable(Zone* zone, T def_value = T())
51       : def_value_(std::move(def_value)), map_(zone) {}
Set(const Node * node,T value)52   void Set(const Node* node, T value) {
53     auto iter = map_.find(node->id());
54     if (iter != map_.end()) {
55       iter->second = std::move(value);
56     } else if (value != def_value_) {
57       map_.insert(iter, std::make_pair(node->id(), std::move(value)));
58     }
59   }
Get(const Node * node) const60   const T& Get(const Node* node) const {
61     auto iter = map_.find(node->id());
62     return iter != map_.end() ? iter->second : def_value_;
63   }
64 
65  private:
66   T def_value_;
67   ZoneUnorderedMap<NodeId, T> map_;
68 };
69 
70 // Keeps track of the changes to the current node during reduction.
71 // Encapsulates the current state of the IR graph and the reducer state like
72 // side-tables. All access to the IR and the reducer state should happen through
73 // a ReduceScope to ensure that changes and dependencies are tracked and all
74 // necessary node revisitations happen.
75 class ReduceScope {
76  public:
77   using Reduction = EffectGraphReducer::Reduction;
ReduceScope(Node * node,Reduction * reduction)78   explicit ReduceScope(Node* node, Reduction* reduction)
79       : current_node_(node), reduction_(reduction) {}
80 
SetValueChanged()81   void SetValueChanged() { reduction()->set_value_changed(); }
82 
83  protected:
current_node() const84   Node* current_node() const { return current_node_; }
reduction()85   Reduction* reduction() { return reduction_; }
86 
87  private:
88   Node* current_node_;
89   Reduction* reduction_;
90 };
91 
92 // A VariableTracker object keeps track of the values of variables at all points
93 // of the effect chain and introduces new phi nodes when necessary.
94 // Initially and by default, variables are mapped to nullptr, which means that
95 // the variable allocation point does not dominate the current point on the
96 // effect chain. We map variables that represent uninitialized memory to the
97 // Dead node to ensure it is not read.
98 // Unmapped values are impossible by construction, it is indistinguishable if a
99 // PersistentMap does not contain an element or maps it to the default element.
100 class VariableTracker {
101  private:
102   // The state of all variables at one point in the effect chain.
103   class State {
104    public:
105     using Map = PersistentMap<Variable, Node*>;
106 
State(Zone * zone)107     explicit State(Zone* zone) : map_(zone) {}
Get(Variable var) const108     Node* Get(Variable var) const {
109       CHECK(var != Variable::Invalid());
110       return map_.Get(var);
111     }
Set(Variable var,Node * node)112     void Set(Variable var, Node* node) {
113       CHECK(var != Variable::Invalid());
114       return map_.Set(var, node);
115     }
begin() const116     Map::iterator begin() const { return map_.begin(); }
end() const117     Map::iterator end() const { return map_.end(); }
operator !=(const State & other) const118     bool operator!=(const State& other) const { return map_ != other.map_; }
119 
120    private:
121     Map map_;
122   };
123 
124  public:
125   VariableTracker(JSGraph* graph, EffectGraphReducer* reducer, Zone* zone);
126   VariableTracker(const VariableTracker&) = delete;
127   VariableTracker& operator=(const VariableTracker&) = delete;
128 
NewVariable()129   Variable NewVariable() { return Variable(next_variable_++); }
Get(Variable var,Node * effect)130   Node* Get(Variable var, Node* effect) { return table_.Get(effect).Get(var); }
zone()131   Zone* zone() { return zone_; }
132 
133   class V8_NODISCARD Scope : public ReduceScope {
134    public:
135     Scope(VariableTracker* tracker, Node* node, Reduction* reduction);
136     ~Scope();
Get(Variable var)137     Maybe<Node*> Get(Variable var) {
138       Node* node = current_state_.Get(var);
139       if (node && node->opcode() == IrOpcode::kDead) {
140         // TODO(turbofan): We use {Dead} as a sentinel for uninitialized memory.
141         // Reading uninitialized memory can only happen in unreachable code. In
142         // this case, we have to mark the object as escaping to avoid dead nodes
143         // in the graph. This is a workaround that should be removed once we can
144         // handle dead nodes everywhere.
145         return Nothing<Node*>();
146       }
147       return Just(node);
148     }
Set(Variable var,Node * node)149     void Set(Variable var, Node* node) { current_state_.Set(var, node); }
150 
151    private:
152     VariableTracker* states_;
153     State current_state_;
154   };
155 
156  private:
157   State MergeInputs(Node* effect_phi);
158   Zone* zone_;
159   JSGraph* graph_;
160   SparseSidetable<State> table_;
161   ZoneVector<Node*> buffer_;
162   EffectGraphReducer* reducer_;
163   int next_variable_ = 0;
164   TickCounter* const tick_counter_;
165 };
166 
167 // Encapsulates the current state of the escape analysis reducer to preserve
168 // invariants regarding changes and re-visitation.
169 class EscapeAnalysisTracker : public ZoneObject {
170  public:
EscapeAnalysisTracker(JSGraph * jsgraph,EffectGraphReducer * reducer,Zone * zone)171   EscapeAnalysisTracker(JSGraph* jsgraph, EffectGraphReducer* reducer,
172                         Zone* zone)
173       : virtual_objects_(zone),
174         replacements_(zone),
175         variable_states_(jsgraph, reducer, zone),
176         jsgraph_(jsgraph),
177         zone_(zone) {}
178   EscapeAnalysisTracker(const EscapeAnalysisTracker&) = delete;
179   EscapeAnalysisTracker& operator=(const EscapeAnalysisTracker&) = delete;
180 
181   class V8_NODISCARD Scope : public VariableTracker::Scope {
182    public:
Scope(EffectGraphReducer * reducer,EscapeAnalysisTracker * tracker,Node * node,Reduction * reduction)183     Scope(EffectGraphReducer* reducer, EscapeAnalysisTracker* tracker,
184           Node* node, Reduction* reduction)
185         : VariableTracker::Scope(&tracker->variable_states_, node, reduction),
186           tracker_(tracker),
187           reducer_(reducer) {}
GetVirtualObject(Node * node)188     const VirtualObject* GetVirtualObject(Node* node) {
189       VirtualObject* vobject = tracker_->virtual_objects_.Get(node);
190       if (vobject) vobject->AddDependency(current_node());
191       return vobject;
192     }
193     // Create or retrieve a virtual object for the current node.
InitVirtualObject(int size)194     const VirtualObject* InitVirtualObject(int size) {
195       DCHECK_EQ(IrOpcode::kAllocate, current_node()->opcode());
196       VirtualObject* vobject = tracker_->virtual_objects_.Get(current_node());
197       if (vobject) {
198         CHECK(vobject->size() == size);
199       } else {
200         vobject = tracker_->NewVirtualObject(size);
201       }
202       if (vobject) vobject->AddDependency(current_node());
203       vobject_ = vobject;
204       return vobject;
205     }
206 
SetVirtualObject(Node * object)207     void SetVirtualObject(Node* object) {
208       vobject_ = tracker_->virtual_objects_.Get(object);
209     }
210 
SetEscaped(Node * node)211     void SetEscaped(Node* node) {
212       if (VirtualObject* object = tracker_->virtual_objects_.Get(node)) {
213         if (object->HasEscaped()) return;
214         TRACE("Setting %s#%d to escaped because of use by %s#%d\n",
215               node->op()->mnemonic(), node->id(),
216               current_node()->op()->mnemonic(), current_node()->id());
217         object->SetEscaped();
218         object->RevisitDependants(reducer_);
219       }
220     }
221     // The inputs of the current node have to be accessed through the scope to
222     // ensure that they respect the node replacements.
ValueInput(int i)223     Node* ValueInput(int i) {
224       return tracker_->ResolveReplacement(
225           NodeProperties::GetValueInput(current_node(), i));
226     }
ContextInput()227     Node* ContextInput() {
228       return tracker_->ResolveReplacement(
229           NodeProperties::GetContextInput(current_node()));
230     }
231     // Accessing the current node is fine for `FrameState nodes.
CurrentNode()232     Node* CurrentNode() {
233       DCHECK_EQ(current_node()->opcode(), IrOpcode::kFrameState);
234       return current_node();
235     }
236 
SetReplacement(Node * replacement)237     void SetReplacement(Node* replacement) {
238       replacement_ = replacement;
239       vobject_ =
240           replacement ? tracker_->virtual_objects_.Get(replacement) : nullptr;
241       if (replacement) {
242         TRACE("Set %s#%d as replacement.\n", replacement->op()->mnemonic(),
243               replacement->id());
244       } else {
245         TRACE("Set nullptr as replacement.\n");
246       }
247     }
248 
MarkForDeletion()249     void MarkForDeletion() { SetReplacement(tracker_->jsgraph_->Dead()); }
250 
~Scope()251     ~Scope() {
252       if (replacement_ != tracker_->replacements_[current_node()] ||
253           vobject_ != tracker_->virtual_objects_.Get(current_node())) {
254         reduction()->set_value_changed();
255       }
256       tracker_->replacements_[current_node()] = replacement_;
257       tracker_->virtual_objects_.Set(current_node(), vobject_);
258     }
259 
260    private:
261     EscapeAnalysisTracker* tracker_;
262     EffectGraphReducer* reducer_;
263     VirtualObject* vobject_ = nullptr;
264     Node* replacement_ = nullptr;
265   };
266 
GetReplacementOf(Node * node)267   Node* GetReplacementOf(Node* node) { return replacements_[node]; }
ResolveReplacement(Node * node)268   Node* ResolveReplacement(Node* node) {
269     if (Node* replacement = GetReplacementOf(node)) {
270       return replacement;
271     }
272     return node;
273   }
274 
275  private:
276   friend class EscapeAnalysisResult;
277   static const size_t kMaxTrackedObjects = 100;
278 
NewVirtualObject(int size)279   VirtualObject* NewVirtualObject(int size) {
280     if (next_object_id_ >= kMaxTrackedObjects) return nullptr;
281     return zone_->New<VirtualObject>(&variable_states_, next_object_id_++,
282                                      size);
283   }
284 
285   SparseSidetable<VirtualObject*> virtual_objects_;
286   Sidetable<Node*> replacements_;
287   VariableTracker variable_states_;
288   VirtualObject::Id next_object_id_ = 0;
289   JSGraph* const jsgraph_;
290   Zone* const zone_;
291 };
292 
EffectGraphReducer(Graph * graph,std::function<void (Node *,Reduction *)> reduce,TickCounter * tick_counter,Zone * zone)293 EffectGraphReducer::EffectGraphReducer(
294     Graph* graph, std::function<void(Node*, Reduction*)> reduce,
295     TickCounter* tick_counter, Zone* zone)
296     : graph_(graph),
297       state_(graph, kNumStates),
298       revisit_(zone),
299       stack_(zone),
300       reduce_(std::move(reduce)),
301       tick_counter_(tick_counter) {}
302 
ReduceFrom(Node * node)303 void EffectGraphReducer::ReduceFrom(Node* node) {
304   // Perform DFS and eagerly trigger revisitation as soon as possible.
305   // A stack element {node, i} indicates that input i of node should be visited
306   // next.
307   DCHECK(stack_.empty());
308   stack_.push({node, 0});
309   while (!stack_.empty()) {
310     tick_counter_->TickAndMaybeEnterSafepoint();
311     Node* current = stack_.top().node;
312     int& input_index = stack_.top().input_index;
313     if (input_index < current->InputCount()) {
314       Node* input = current->InputAt(input_index);
315       input_index++;
316       switch (state_.Get(input)) {
317         case State::kVisited:
318           // The input is already reduced.
319           break;
320         case State::kOnStack:
321           // The input is on the DFS stack right now, so it will be revisited
322           // later anyway.
323           break;
324         case State::kUnvisited:
325         case State::kRevisit: {
326           state_.Set(input, State::kOnStack);
327           stack_.push({input, 0});
328           break;
329         }
330       }
331     } else {
332       stack_.pop();
333       Reduction reduction;
334       reduce_(current, &reduction);
335       for (Edge edge : current->use_edges()) {
336         // Mark uses for revisitation.
337         Node* use = edge.from();
338         if (NodeProperties::IsEffectEdge(edge)) {
339           if (reduction.effect_changed()) Revisit(use);
340         } else {
341           if (reduction.value_changed()) Revisit(use);
342         }
343       }
344       state_.Set(current, State::kVisited);
345       // Process the revisitation buffer immediately. This improves performance
346       // of escape analysis. Using a stack for {revisit_} reverses the order in
347       // which the revisitation happens. This also seems to improve performance.
348       while (!revisit_.empty()) {
349         Node* revisit = revisit_.top();
350         if (state_.Get(revisit) == State::kRevisit) {
351           state_.Set(revisit, State::kOnStack);
352           stack_.push({revisit, 0});
353         }
354         revisit_.pop();
355       }
356     }
357   }
358 }
359 
Revisit(Node * node)360 void EffectGraphReducer::Revisit(Node* node) {
361   if (state_.Get(node) == State::kVisited) {
362     TRACE("  Queueing for revisit: %s#%d\n", node->op()->mnemonic(),
363           node->id());
364     state_.Set(node, State::kRevisit);
365     revisit_.push(node);
366   }
367 }
368 
VariableTracker(JSGraph * graph,EffectGraphReducer * reducer,Zone * zone)369 VariableTracker::VariableTracker(JSGraph* graph, EffectGraphReducer* reducer,
370                                  Zone* zone)
371     : zone_(zone),
372       graph_(graph),
373       table_(zone, State(zone)),
374       buffer_(zone),
375       reducer_(reducer),
376       tick_counter_(reducer->tick_counter()) {}
377 
Scope(VariableTracker * states,Node * node,Reduction * reduction)378 VariableTracker::Scope::Scope(VariableTracker* states, Node* node,
379                               Reduction* reduction)
380     : ReduceScope(node, reduction),
381       states_(states),
382       current_state_(states->zone_) {
383   switch (node->opcode()) {
384     case IrOpcode::kEffectPhi:
385       current_state_ = states_->MergeInputs(node);
386       break;
387     default:
388       int effect_inputs = node->op()->EffectInputCount();
389       if (effect_inputs == 1) {
390         current_state_ =
391             states_->table_.Get(NodeProperties::GetEffectInput(node, 0));
392       } else {
393         DCHECK_EQ(0, effect_inputs);
394       }
395   }
396 }
397 
~Scope()398 VariableTracker::Scope::~Scope() {
399   if (!reduction()->effect_changed() &&
400       states_->table_.Get(current_node()) != current_state_) {
401     reduction()->set_effect_changed();
402   }
403   states_->table_.Set(current_node(), current_state_);
404 }
405 
MergeInputs(Node * effect_phi)406 VariableTracker::State VariableTracker::MergeInputs(Node* effect_phi) {
407   // A variable that is mapped to [nullptr] was not assigned a value on every
408   // execution path to the current effect phi. Relying on the invariant that
409   // every variable is initialized (at least with a sentinel like the Dead
410   // node), this means that the variable initialization does not dominate the
411   // current point. So for loop effect phis, we can keep nullptr for a variable
412   // as long as the first input of the loop has nullptr for this variable. For
413   // non-loop effect phis, we can even keep it nullptr as long as any input has
414   // nullptr.
415   DCHECK_EQ(IrOpcode::kEffectPhi, effect_phi->opcode());
416   int arity = effect_phi->op()->EffectInputCount();
417   Node* control = NodeProperties::GetControlInput(effect_phi, 0);
418   TRACE("control: %s#%d\n", control->op()->mnemonic(), control->id());
419   bool is_loop = control->opcode() == IrOpcode::kLoop;
420   buffer_.reserve(arity + 1);
421 
422   State first_input = table_.Get(NodeProperties::GetEffectInput(effect_phi, 0));
423   State result = first_input;
424   for (std::pair<Variable, Node*> var_value : first_input) {
425     tick_counter_->TickAndMaybeEnterSafepoint();
426     if (Node* value = var_value.second) {
427       Variable var = var_value.first;
428       TRACE("var %i:\n", var.id_);
429       buffer_.clear();
430       buffer_.push_back(value);
431       bool identical_inputs = true;
432       int num_defined_inputs = 1;
433       TRACE("  input 0: %s#%d\n", value->op()->mnemonic(), value->id());
434       for (int i = 1; i < arity; ++i) {
435         Node* next_value =
436             table_.Get(NodeProperties::GetEffectInput(effect_phi, i)).Get(var);
437         if (next_value != value) identical_inputs = false;
438         if (next_value != nullptr) {
439           num_defined_inputs++;
440           TRACE("  input %i: %s#%d\n", i, next_value->op()->mnemonic(),
441                 next_value->id());
442         } else {
443           TRACE("  input %i: nullptr\n", i);
444         }
445         buffer_.push_back(next_value);
446       }
447 
448       Node* old_value = table_.Get(effect_phi).Get(var);
449       if (old_value) {
450         TRACE("  old: %s#%d\n", old_value->op()->mnemonic(), old_value->id());
451       } else {
452         TRACE("  old: nullptr\n");
453       }
454       // Reuse a previously created phi node if possible.
455       if (old_value && old_value->opcode() == IrOpcode::kPhi &&
456           NodeProperties::GetControlInput(old_value, 0) == control) {
457         // Since a phi node can never dominate its control node,
458         // [old_value] cannot originate from the inputs. Thus [old_value]
459         // must have been created by a previous reduction of this [effect_phi].
460         for (int i = 0; i < arity; ++i) {
461           Node* old_input = NodeProperties::GetValueInput(old_value, i);
462           Node* new_input = buffer_[i] ? buffer_[i] : graph_->Dead();
463           if (old_input != new_input) {
464             NodeProperties::ReplaceValueInput(old_value, new_input, i);
465             reducer_->Revisit(old_value);
466           }
467         }
468         result.Set(var, old_value);
469       } else {
470         if (num_defined_inputs == 1 && is_loop) {
471           // For loop effect phis, the variable initialization dominates iff it
472           // dominates the first input.
473           DCHECK_EQ(2, arity);
474           DCHECK_EQ(value, buffer_[0]);
475           result.Set(var, value);
476         } else if (num_defined_inputs < arity) {
477           // If the variable is undefined on some input of this non-loop effect
478           // phi, then its initialization does not dominate this point.
479           result.Set(var, nullptr);
480         } else {
481           DCHECK_EQ(num_defined_inputs, arity);
482           // We only create a phi if the values are different.
483           if (identical_inputs) {
484             result.Set(var, value);
485           } else {
486             TRACE("Creating new phi\n");
487             buffer_.push_back(control);
488             Node* phi = graph_->graph()->NewNode(
489                 graph_->common()->Phi(MachineRepresentation::kTagged, arity),
490                 arity + 1, &buffer_.front());
491             // TODO(turbofan): Computing precise types here is tricky, because
492             // of the necessary revisitations. If we really need this, we should
493             // probably do it afterwards.
494             NodeProperties::SetType(phi, Type::Any());
495             reducer_->AddRoot(phi);
496             result.Set(var, phi);
497           }
498         }
499       }
500 #ifdef DEBUG
501       if (Node* result_node = result.Get(var)) {
502         TRACE("  result: %s#%d\n", result_node->op()->mnemonic(),
503               result_node->id());
504       } else {
505         TRACE("  result: nullptr\n");
506       }
507 #endif
508     }
509   }
510   return result;
511 }
512 
513 namespace {
514 
OffsetOfFieldAccess(const Operator * op)515 int OffsetOfFieldAccess(const Operator* op) {
516   DCHECK(op->opcode() == IrOpcode::kLoadField ||
517          op->opcode() == IrOpcode::kStoreField);
518   FieldAccess access = FieldAccessOf(op);
519   return access.offset;
520 }
521 
OffsetOfElementAt(ElementAccess const & access,int index)522 Maybe<int> OffsetOfElementAt(ElementAccess const& access, int index) {
523   MachineRepresentation representation = access.machine_type.representation();
524   // Double elements accesses are not yet supported. See chromium:1237821.
525   if (representation == MachineRepresentation::kFloat64) return Nothing<int>();
526 
527   DCHECK_GE(index, 0);
528   DCHECK_GE(ElementSizeLog2Of(representation), kTaggedSizeLog2);
529   return Just(access.header_size +
530               (index << ElementSizeLog2Of(representation)));
531 }
532 
OffsetOfElementsAccess(const Operator * op,Node * index_node)533 Maybe<int> OffsetOfElementsAccess(const Operator* op, Node* index_node) {
534   DCHECK(op->opcode() == IrOpcode::kLoadElement ||
535          op->opcode() == IrOpcode::kStoreElement);
536   Type index_type = NodeProperties::GetType(index_node);
537   if (!index_type.Is(Type::OrderedNumber())) return Nothing<int>();
538   double max = index_type.Max();
539   double min = index_type.Min();
540   int index = static_cast<int>(min);
541   if (index < 0 || index != min || index != max) return Nothing<int>();
542   return OffsetOfElementAt(ElementAccessOf(op), index);
543 }
544 
LowerCompareMapsWithoutLoad(Node * checked_map,ZoneHandleSet<Map> const & checked_against,JSGraph * jsgraph)545 Node* LowerCompareMapsWithoutLoad(Node* checked_map,
546                                   ZoneHandleSet<Map> const& checked_against,
547                                   JSGraph* jsgraph) {
548   Node* true_node = jsgraph->TrueConstant();
549   Node* false_node = jsgraph->FalseConstant();
550   Node* replacement = false_node;
551   for (Handle<Map> map : checked_against) {
552     Node* map_node = jsgraph->HeapConstant(map);
553     // We cannot create a HeapConstant type here as we are off-thread.
554     NodeProperties::SetType(map_node, Type::Internal());
555     Node* comparison = jsgraph->graph()->NewNode(
556         jsgraph->simplified()->ReferenceEqual(), checked_map, map_node);
557     NodeProperties::SetType(comparison, Type::Boolean());
558     if (replacement == false_node) {
559       replacement = comparison;
560     } else {
561       replacement = jsgraph->graph()->NewNode(
562           jsgraph->common()->Select(MachineRepresentation::kTaggedPointer),
563           comparison, true_node, replacement);
564       NodeProperties::SetType(replacement, Type::Boolean());
565     }
566   }
567   return replacement;
568 }
569 
ReduceNode(const Operator * op,EscapeAnalysisTracker::Scope * current,JSGraph * jsgraph)570 void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
571                 JSGraph* jsgraph) {
572   switch (op->opcode()) {
573     case IrOpcode::kAllocate: {
574       NumberMatcher size(current->ValueInput(0));
575       if (!size.HasResolvedValue()) break;
576       int size_int = static_cast<int>(size.ResolvedValue());
577       if (size_int != size.ResolvedValue()) break;
578       if (const VirtualObject* vobject = current->InitVirtualObject(size_int)) {
579         // Initialize with dead nodes as a sentinel for uninitialized memory.
580         for (Variable field : *vobject) {
581           current->Set(field, jsgraph->Dead());
582         }
583       }
584       break;
585     }
586     case IrOpcode::kFinishRegion:
587       current->SetVirtualObject(current->ValueInput(0));
588       break;
589     case IrOpcode::kStoreField: {
590       Node* object = current->ValueInput(0);
591       Node* value = current->ValueInput(1);
592       const VirtualObject* vobject = current->GetVirtualObject(object);
593       Variable var;
594       if (vobject && !vobject->HasEscaped() &&
595           vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
596         current->Set(var, value);
597         current->MarkForDeletion();
598       } else {
599         current->SetEscaped(object);
600         current->SetEscaped(value);
601       }
602       break;
603     }
604     case IrOpcode::kStoreElement: {
605       Node* object = current->ValueInput(0);
606       Node* index = current->ValueInput(1);
607       Node* value = current->ValueInput(2);
608       const VirtualObject* vobject = current->GetVirtualObject(object);
609       int offset;
610       Variable var;
611       if (vobject && !vobject->HasEscaped() &&
612           OffsetOfElementsAccess(op, index).To(&offset) &&
613           vobject->FieldAt(offset).To(&var)) {
614         current->Set(var, value);
615         current->MarkForDeletion();
616       } else {
617         current->SetEscaped(value);
618         current->SetEscaped(object);
619       }
620       break;
621     }
622     case IrOpcode::kLoadField: {
623       Node* object = current->ValueInput(0);
624       const VirtualObject* vobject = current->GetVirtualObject(object);
625       Variable var;
626       Node* value;
627       if (vobject && !vobject->HasEscaped() &&
628           vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
629           current->Get(var).To(&value)) {
630         current->SetReplacement(value);
631       } else {
632         current->SetEscaped(object);
633       }
634       break;
635     }
636     case IrOpcode::kLoadElement: {
637       Node* object = current->ValueInput(0);
638       Node* index = current->ValueInput(1);
639       const VirtualObject* vobject = current->GetVirtualObject(object);
640       int offset;
641       Variable var;
642       Node* value;
643       if (vobject && !vobject->HasEscaped() &&
644           OffsetOfElementsAccess(op, index).To(&offset) &&
645           vobject->FieldAt(offset).To(&var) && current->Get(var).To(&value)) {
646         current->SetReplacement(value);
647         break;
648       } else if (vobject && !vobject->HasEscaped()) {
649         // Compute the known length (aka the number of elements) of {object}
650         // based on the virtual object information.
651         ElementAccess const& access = ElementAccessOf(op);
652         int const length =
653             (vobject->size() - access.header_size) >>
654             ElementSizeLog2Of(access.machine_type.representation());
655         Variable var0, var1;
656         Node* value0;
657         Node* value1;
658         if (length == 1 &&
659             vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var) &&
660             current->Get(var).To(&value) &&
661             (value == nullptr ||
662              NodeProperties::GetType(value).Is(access.type))) {
663           // The {object} has no elements, and we know that the LoadElement
664           // {index} must be within bounds, thus it must always yield this
665           // one element of {object}.
666           current->SetReplacement(value);
667           break;
668         } else if (length == 2 &&
669                    vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var0) &&
670                    current->Get(var0).To(&value0) &&
671                    (value0 == nullptr ||
672                     NodeProperties::GetType(value0).Is(access.type)) &&
673                    vobject->FieldAt(OffsetOfElementAt(access, 1)).To(&var1) &&
674                    current->Get(var1).To(&value1) &&
675                    (value1 == nullptr ||
676                     NodeProperties::GetType(value1).Is(access.type))) {
677           if (value0 && value1) {
678             // The {object} has exactly two elements, so the LoadElement
679             // must return one of them (i.e. either the element at index
680             // 0 or the one at index 1). So we can turn the LoadElement
681             // into a Select operation instead (still allowing the {object}
682             // to be scalar replaced). We must however mark the elements
683             // of the {object} itself as escaping.
684             Node* check =
685                 jsgraph->graph()->NewNode(jsgraph->simplified()->NumberEqual(),
686                                           index, jsgraph->ZeroConstant());
687             NodeProperties::SetType(check, Type::Boolean());
688             Node* select = jsgraph->graph()->NewNode(
689                 jsgraph->common()->Select(access.machine_type.representation()),
690                 check, value0, value1);
691             NodeProperties::SetType(select, access.type);
692             current->SetReplacement(select);
693             current->SetEscaped(value0);
694             current->SetEscaped(value1);
695             break;
696           } else {
697             // If the variables have no values, we have
698             // not reached the fixed-point yet.
699             break;
700           }
701         }
702       }
703       current->SetEscaped(object);
704       break;
705     }
706     case IrOpcode::kTypeGuard: {
707       current->SetVirtualObject(current->ValueInput(0));
708       break;
709     }
710     case IrOpcode::kReferenceEqual: {
711       Node* left = current->ValueInput(0);
712       Node* right = current->ValueInput(1);
713       const VirtualObject* left_object = current->GetVirtualObject(left);
714       const VirtualObject* right_object = current->GetVirtualObject(right);
715       Node* replacement = nullptr;
716       if (left_object && !left_object->HasEscaped()) {
717         if (right_object && !right_object->HasEscaped() &&
718             left_object->id() == right_object->id()) {
719           replacement = jsgraph->TrueConstant();
720         } else {
721           replacement = jsgraph->FalseConstant();
722         }
723       } else if (right_object && !right_object->HasEscaped()) {
724         replacement = jsgraph->FalseConstant();
725       }
726       // TODO(turbofan) This is a workaround for uninhabited types. If we
727       // replaced a value of uninhabited type with a constant, we would
728       // widen the type of the node. This could produce inconsistent
729       // types (which might confuse representation selection). We get
730       // around this by refusing to constant-fold and escape-analyze
731       // if the type is not inhabited.
732       if (replacement && !NodeProperties::GetType(left).IsNone() &&
733           !NodeProperties::GetType(right).IsNone()) {
734         current->SetReplacement(replacement);
735         break;
736       }
737       current->SetEscaped(left);
738       current->SetEscaped(right);
739       break;
740     }
741     case IrOpcode::kCheckMaps: {
742       CheckMapsParameters params = CheckMapsParametersOf(op);
743       Node* checked = current->ValueInput(0);
744       const VirtualObject* vobject = current->GetVirtualObject(checked);
745       Variable map_field;
746       Node* map;
747       if (vobject && !vobject->HasEscaped() &&
748           vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
749           current->Get(map_field).To(&map)) {
750         if (map) {
751           Type const map_type = NodeProperties::GetType(map);
752           if (map_type.IsHeapConstant() &&
753               params.maps().contains(
754                   map_type.AsHeapConstant()->Ref().AsMap().object())) {
755             current->MarkForDeletion();
756             break;
757           }
758         } else {
759           // If the variable has no value, we have not reached the fixed-point
760           // yet.
761           break;
762         }
763       }
764       current->SetEscaped(checked);
765       break;
766     }
767     case IrOpcode::kCompareMaps: {
768       Node* object = current->ValueInput(0);
769       const VirtualObject* vobject = current->GetVirtualObject(object);
770       Variable map_field;
771       Node* object_map;
772       if (vobject && !vobject->HasEscaped() &&
773           vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
774           current->Get(map_field).To(&object_map)) {
775         if (object_map) {
776           current->SetReplacement(LowerCompareMapsWithoutLoad(
777               object_map, CompareMapsParametersOf(op), jsgraph));
778           break;
779         } else {
780           // If the variable has no value, we have not reached the fixed-point
781           // yet.
782           break;
783         }
784       }
785       current->SetEscaped(object);
786       break;
787     }
788     case IrOpcode::kCheckHeapObject: {
789       Node* checked = current->ValueInput(0);
790       switch (checked->opcode()) {
791         case IrOpcode::kAllocate:
792         case IrOpcode::kFinishRegion:
793         case IrOpcode::kHeapConstant:
794           current->SetReplacement(checked);
795           break;
796         default:
797           current->SetEscaped(checked);
798           break;
799       }
800       break;
801     }
802     case IrOpcode::kMapGuard: {
803       Node* object = current->ValueInput(0);
804       const VirtualObject* vobject = current->GetVirtualObject(object);
805       if (vobject && !vobject->HasEscaped()) {
806         current->MarkForDeletion();
807       }
808       break;
809     }
810     case IrOpcode::kStateValues:
811       // We visit StateValue nodes through their correpsonding FrameState node,
812       // so we need to make sure we revisit the FrameState.
813       current->SetValueChanged();
814       break;
815     case IrOpcode::kFrameState: {
816       // We mark the receiver as escaping due to the non-standard `.getThis`
817       // API.
818       FrameState frame_state{current->CurrentNode()};
819       if (frame_state.frame_state_info().type() !=
820           FrameStateType::kUnoptimizedFunction)
821         break;
822       StateValuesAccess::iterator it =
823           StateValuesAccess(frame_state.parameters()).begin();
824       if (!it.done()) {
825         if (Node* receiver = it.node()) {
826           current->SetEscaped(receiver);
827         }
828         current->SetEscaped(frame_state.function());
829       }
830       break;
831     }
832     default: {
833       // For unknown nodes, treat all value inputs as escaping.
834       int value_input_count = op->ValueInputCount();
835       for (int i = 0; i < value_input_count; ++i) {
836         Node* input = current->ValueInput(i);
837         current->SetEscaped(input);
838       }
839       if (OperatorProperties::HasContextInput(op)) {
840         current->SetEscaped(current->ContextInput());
841       }
842       break;
843     }
844   }
845 }
846 
847 }  // namespace
848 
Reduce(Node * node,Reduction * reduction)849 void EscapeAnalysis::Reduce(Node* node, Reduction* reduction) {
850   const Operator* op = node->op();
851   TRACE("Reducing %s#%d\n", op->mnemonic(), node->id());
852 
853   EscapeAnalysisTracker::Scope current(this, tracker_, node, reduction);
854   ReduceNode(op, &current, jsgraph());
855 }
856 
EscapeAnalysis(JSGraph * jsgraph,TickCounter * tick_counter,Zone * zone)857 EscapeAnalysis::EscapeAnalysis(JSGraph* jsgraph, TickCounter* tick_counter,
858                                Zone* zone)
859     : EffectGraphReducer(
860           jsgraph->graph(),
861           [this](Node* node, Reduction* reduction) { Reduce(node, reduction); },
862           tick_counter, zone),
863       tracker_(zone->New<EscapeAnalysisTracker>(jsgraph, this, zone)),
864       jsgraph_(jsgraph) {}
865 
GetReplacementOf(Node * node)866 Node* EscapeAnalysisResult::GetReplacementOf(Node* node) {
867   Node* replacement = tracker_->GetReplacementOf(node);
868   // Replacements cannot have replacements. This is important to ensure
869   // re-visitation: If a replacement is replaced, then all nodes accessing
870   // the replacement have to be updated.
871   if (replacement) DCHECK_NULL(tracker_->GetReplacementOf(replacement));
872   return replacement;
873 }
874 
GetVirtualObjectField(const VirtualObject * vobject,int field,Node * effect)875 Node* EscapeAnalysisResult::GetVirtualObjectField(const VirtualObject* vobject,
876                                                   int field, Node* effect) {
877   return tracker_->variable_states_.Get(vobject->FieldAt(field).FromJust(),
878                                         effect);
879 }
880 
GetVirtualObject(Node * node)881 const VirtualObject* EscapeAnalysisResult::GetVirtualObject(Node* node) {
882   return tracker_->virtual_objects_.Get(node);
883 }
884 
VirtualObject(VariableTracker * var_states,VirtualObject::Id id,int size)885 VirtualObject::VirtualObject(VariableTracker* var_states, VirtualObject::Id id,
886                              int size)
887     : Dependable(var_states->zone()), id_(id), fields_(var_states->zone()) {
888   DCHECK(IsAligned(size, kTaggedSize));
889   TRACE("Creating VirtualObject id:%d size:%d\n", id, size);
890   int num_fields = size / kTaggedSize;
891   fields_.reserve(num_fields);
892   for (int i = 0; i < num_fields; ++i) {
893     fields_.push_back(var_states->NewVariable());
894   }
895 }
896 
897 #undef TRACE
898 
899 }  // namespace compiler
900 }  // namespace internal
901 }  // namespace v8
902