5 #include "src/compiler/escape-analysis.h" 7 #include "src/bootstrapper.h" 8 #include "src/compiler/linkage.h" 9 #include "src/compiler/node-matchers.h" 10 #include "src/compiler/operator-properties.h" 11 #include "src/compiler/simplified-operator.h" 12 #include "src/handles-inl.h" 13 #include "src/objects/map-inl.h" 18 if (FLAG_trace_turbo_escape) PrintF(__VA_ARGS__); \ 32 T& operator[](
const Node* node) {
34 if (
id >= map_.size()) {
48 : def_value_(std::move(def_value)), map_(zone) {}
49 void Set(
const Node* node, T value) {
50 auto iter = map_.find(node->id());
51 if (iter != map_.end()) {
52 iter->second = std::move(value);
53 }
else if (value != def_value_) {
54 map_.insert(iter, std::make_pair(node->id(), std::move(value)));
57 const T& Get(
const Node* node)
const {
58 auto iter = map_.find(node->id());
59 return iter != map_.end() ? iter->second : def_value_;
76 : current_node_(node), reduction_(reduction) {}
79 Node* current_node()
const {
return current_node_; }
80 Reduction* reduction() {
return reduction_; }
102 explicit State(
Zone* zone) : map_(zone) {}
104 CHECK(var != Variable::Invalid());
105 return map_.Get(var);
108 CHECK(var != Variable::Invalid());
109 return map_.Set(var, node);
111 Map::iterator begin()
const {
return map_.begin(); }
112 Map::iterator end()
const {
return map_.end(); }
113 bool operator!=(
const State& other)
const {
return map_ != other.map_; }
122 Node* Get(
Variable var,
Node* effect) {
return table_.Get(effect).Get(var); }
123 Zone* zone() {
return zone_; }
130 Node* node = current_state_.Get(var);
131 if (node && node->opcode() == IrOpcode::kDead) {
137 return Nothing<Node*>();
145 State current_state_;
149 State MergeInputs(
Node* effect_phi);
155 int next_variable_ = 0;
166 : virtual_objects_(zone),
168 variable_states_(jsgraph, reducer, zone),
180 VirtualObject* vobject = tracker_->virtual_objects_.Get(node);
181 if (vobject) vobject->AddDependency(current_node());
186 DCHECK_EQ(IrOpcode::kAllocate, current_node()->opcode());
187 VirtualObject* vobject = tracker_->virtual_objects_.Get(current_node());
189 CHECK(vobject->size() == size);
191 vobject = tracker_->NewVirtualObject(size);
193 if (vobject) vobject->AddDependency(current_node());
198 void SetVirtualObject(
Node*
object) {
199 vobject_ = tracker_->virtual_objects_.Get(
object);
202 void SetEscaped(
Node* node) {
203 if (
VirtualObject*
object = tracker_->virtual_objects_.Get(node)) {
204 if (object->HasEscaped())
return;
205 TRACE(
"Setting %s#%d to escaped because of use by %s#%d\n",
206 node->op()->mnemonic(), node->id(),
207 current_node()->op()->mnemonic(), current_node()->id());
208 object->SetEscaped();
209 object->RevisitDependants(reducer_);
214 Node* ValueInput(
int i) {
215 return tracker_->ResolveReplacement(
216 NodeProperties::GetValueInput(current_node(),
i));
218 Node* ContextInput() {
219 return tracker_->ResolveReplacement(
220 NodeProperties::GetContextInput(current_node()));
223 void SetReplacement(
Node* replacement) {
224 replacement_ = replacement;
226 replacement ? tracker_->virtual_objects_.Get(replacement) :
nullptr;
228 TRACE(
"Set %s#%d as replacement.\n", replacement->op()->mnemonic(),
231 TRACE(
"Set nullptr as replacement.\n");
235 void MarkForDeletion() { SetReplacement(tracker_->jsgraph_->Dead()); }
238 if (replacement_ != tracker_->replacements_[current_node()] ||
239 vobject_ != tracker_->virtual_objects_.Get(current_node())) {
240 reduction()->set_value_changed();
242 tracker_->replacements_[current_node()] = replacement_;
243 tracker_->virtual_objects_.Set(current_node(), vobject_);
250 Node* replacement_ =
nullptr;
253 Node* GetReplacementOf(
Node* node) {
return replacements_[node]; }
254 Node* ResolveReplacement(
Node* node) {
255 if (
Node* replacement = GetReplacementOf(node)) {
262 friend class EscapeAnalysisResult;
263 static const size_t kMaxTrackedObjects = 100;
265 VirtualObject* NewVirtualObject(
int size) {
266 if (next_object_id_ >= kMaxTrackedObjects)
return nullptr;
268 VirtualObject(&variable_states_, next_object_id_++, size);
271 SparseSidetable<VirtualObject*> virtual_objects_;
272 Sidetable<Node*> replacements_;
273 VariableTracker variable_states_;
274 VirtualObject::Id next_object_id_ = 0;
275 JSGraph*
const jsgraph_;
278 DISALLOW_COPY_AND_ASSIGN(EscapeAnalysisTracker);
281 EffectGraphReducer::EffectGraphReducer(
282 Graph* graph, std::function<
void(Node*, Reduction*)> reduce, Zone* zone)
284 state_(graph, kNumStates),
287 reduce_(
std::move(reduce)) {}
289 void EffectGraphReducer::ReduceFrom(Node* node) {
293 DCHECK(stack_.empty());
294 stack_.push({node, 0});
295 while (!stack_.empty()) {
296 Node* current = stack_.top().node;
297 int& input_index = stack_.top().input_index;
298 if (input_index < current->InputCount()) {
299 Node* input = current->InputAt(input_index);
301 switch (state_.Get(input)) {
302 case State::kVisited:
305 case State::kOnStack:
309 case State::kUnvisited:
310 case State::kRevisit: {
311 state_.Set(input, State::kOnStack);
312 stack_.push({input, 0});
319 reduce_(current, &reduction);
320 for (Edge edge : current->use_edges()) {
322 Node* use = edge.from();
323 if (NodeProperties::IsEffectEdge(edge)) {
324 if (reduction.effect_changed()) Revisit(use);
326 if (reduction.value_changed()) Revisit(use);
329 state_.Set(current, State::kVisited);
333 while (!revisit_.empty()) {
334 Node* revisit = revisit_.top();
335 if (state_.Get(revisit) == State::kRevisit) {
336 state_.Set(revisit, State::kOnStack);
337 stack_.push({revisit, 0});
345 void EffectGraphReducer::Revisit(Node* node) {
346 if (state_.Get(node) == State::kVisited) {
347 TRACE(
" Queueing for revisit: %s#%d\n", node->op()->mnemonic(),
349 state_.Set(node, State::kRevisit);
354 VariableTracker::VariableTracker(JSGraph* graph, EffectGraphReducer* reducer,
358 table_(zone, State(zone)),
362 VariableTracker::Scope::Scope(VariableTracker* states, Node* node,
363 Reduction* reduction)
364 : ReduceScope(node, reduction),
366 current_state_(states->zone_) {
367 switch (node->opcode()) {
368 case IrOpcode::kEffectPhi:
369 current_state_ = states_->MergeInputs(node);
372 int effect_inputs = node->op()->EffectInputCount();
373 if (effect_inputs == 1) {
375 states_->table_.Get(NodeProperties::GetEffectInput(node, 0));
377 DCHECK_EQ(0, effect_inputs);
382 VariableTracker::Scope::~Scope() {
383 if (!reduction()->effect_changed() &&
384 states_->table_.Get(current_node()) != current_state_) {
385 reduction()->set_effect_changed();
387 states_->table_.Set(current_node(), current_state_);
390 VariableTracker::State VariableTracker::MergeInputs(Node* effect_phi) {
399 DCHECK_EQ(IrOpcode::kEffectPhi, effect_phi->opcode());
400 int arity = effect_phi->op()->EffectInputCount();
401 Node* control = NodeProperties::GetControlInput(effect_phi, 0);
402 TRACE(
"control: %s#%d\n", control->op()->mnemonic(), control->id());
403 bool is_loop = control->opcode() == IrOpcode::kLoop;
404 buffer_.reserve(arity + 1);
406 State first_input = table_.Get(NodeProperties::GetEffectInput(effect_phi, 0));
407 State result = first_input;
408 for (std::pair<Variable, Node*> var_value : first_input) {
409 if (Node* value = var_value.second) {
410 Variable var = var_value.first;
411 TRACE(
"var %i:\n", var.id_);
413 buffer_.push_back(value);
414 bool identical_inputs =
true;
415 int num_defined_inputs = 1;
416 TRACE(
" input 0: %s#%d\n", value->op()->mnemonic(), value->id());
417 for (
int i = 1;
i < arity; ++
i) {
419 table_.Get(NodeProperties::GetEffectInput(effect_phi,
i)).Get(var);
420 if (next_value != value) identical_inputs =
false;
421 if (next_value !=
nullptr) {
422 num_defined_inputs++;
423 TRACE(
" input %i: %s#%d\n",
i, next_value->op()->mnemonic(),
426 TRACE(
" input %i: nullptr\n",
i);
428 buffer_.push_back(next_value);
431 Node* old_value = table_.Get(effect_phi).Get(var);
433 TRACE(
" old: %s#%d\n", old_value->op()->mnemonic(), old_value->id());
435 TRACE(
" old: nullptr\n");
438 if (old_value && old_value->opcode() == IrOpcode::kPhi &&
439 NodeProperties::GetControlInput(old_value, 0) == control) {
443 for (
int i = 0;
i < arity; ++
i) {
444 NodeProperties::ReplaceValueInput(
445 old_value, buffer_[
i] ? buffer_[
i] : graph_->Dead(),
i);
449 result.Set(var, old_value);
451 if (num_defined_inputs == 1 && is_loop) {
455 DCHECK_EQ(value, buffer_[0]);
456 result.Set(var, value);
457 }
else if (num_defined_inputs < arity) {
460 result.Set(var,
nullptr);
462 DCHECK_EQ(num_defined_inputs, arity);
464 if (identical_inputs) {
465 result.Set(var, value);
467 TRACE(
"Creating new phi\n");
468 buffer_.push_back(control);
469 Node* phi = graph_->graph()->NewNode(
470 graph_->common()->Phi(MachineRepresentation::kTagged, arity),
471 arity + 1, &buffer_.front());
475 NodeProperties::SetType(phi, Type::Any());
476 reducer_->AddRoot(phi);
477 result.Set(var, phi);
482 if (Node* result_node = result.Get(var)) {
483 TRACE(
" result: %s#%d\n", result_node->op()->mnemonic(),
486 TRACE(
" result: nullptr\n");
496 int OffsetOfFieldAccess(
const Operator* op) {
497 DCHECK(op->opcode() == IrOpcode::kLoadField ||
498 op->opcode() == IrOpcode::kStoreField);
499 FieldAccess access = FieldAccessOf(op);
500 return access.offset;
503 int OffsetOfElementAt(ElementAccess
const& access,
int index) {
505 DCHECK_GE(ElementSizeLog2Of(access.machine_type.representation()),
507 return access.header_size +
508 (index << ElementSizeLog2Of(access.machine_type.representation()));
511 Maybe<int> OffsetOfElementsAccess(
const Operator* op, Node* index_node) {
512 DCHECK(op->opcode() == IrOpcode::kLoadElement ||
513 op->opcode() == IrOpcode::kStoreElement);
514 Type index_type = NodeProperties::GetType(index_node);
515 if (!index_type.Is(Type::OrderedNumber()))
return Nothing<int>();
516 double max = index_type.Max();
517 double min = index_type.Min();
518 int index =
static_cast<int>(min);
519 if (index < 0 || index != min || index != max)
return Nothing<int>();
520 return Just(OffsetOfElementAt(ElementAccessOf(op), index));
523 Node* LowerCompareMapsWithoutLoad(Node* checked_map,
524 ZoneHandleSet<Map>
const& checked_against,
526 Node* true_node = jsgraph->TrueConstant();
527 Node* false_node = jsgraph->FalseConstant();
528 Node* replacement = false_node;
529 for (Handle<Map> map : checked_against) {
530 Node* map_node = jsgraph->HeapConstant(map);
532 NodeProperties::SetType(map_node, Type::Internal());
533 Node* comparison = jsgraph->graph()->NewNode(
534 jsgraph->simplified()->ReferenceEqual(), checked_map, map_node);
535 NodeProperties::SetType(comparison, Type::Boolean());
536 if (replacement == false_node) {
537 replacement = comparison;
539 replacement = jsgraph->graph()->NewNode(
540 jsgraph->common()->Select(MachineRepresentation::kTaggedPointer),
541 comparison, true_node, replacement);
542 NodeProperties::SetType(replacement, Type::Boolean());
548 void ReduceNode(
const Operator* op, EscapeAnalysisTracker::Scope* current,
550 switch (op->opcode()) {
551 case IrOpcode::kAllocate: {
552 NumberMatcher size(current->ValueInput(0));
553 if (!size.HasValue())
break;
554 int size_int =
static_cast<int>(size.Value());
555 if (size_int != size.Value())
break;
556 if (
const VirtualObject* vobject = current->InitVirtualObject(size_int)) {
558 for (Variable field : *vobject) {
559 current->Set(field, jsgraph->Dead());
564 case IrOpcode::kFinishRegion:
565 current->SetVirtualObject(current->ValueInput(0));
567 case IrOpcode::kStoreField: {
568 Node*
object = current->ValueInput(0);
569 Node* value = current->ValueInput(1);
570 const VirtualObject* vobject = current->GetVirtualObject(
object);
572 if (vobject && !vobject->HasEscaped() &&
573 vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
574 current->Set(var, value);
575 current->MarkForDeletion();
577 current->SetEscaped(
object);
578 current->SetEscaped(value);
582 case IrOpcode::kStoreElement: {
583 Node*
object = current->ValueInput(0);
584 Node* index = current->ValueInput(1);
585 Node* value = current->ValueInput(2);
586 const VirtualObject* vobject = current->GetVirtualObject(
object);
589 if (vobject && !vobject->HasEscaped() &&
590 OffsetOfElementsAccess(op, index).To(&offset) &&
591 vobject->FieldAt(offset).To(&var)) {
592 current->Set(var, value);
593 current->MarkForDeletion();
595 current->SetEscaped(value);
596 current->SetEscaped(
object);
600 case IrOpcode::kLoadField: {
601 Node*
object = current->ValueInput(0);
602 const VirtualObject* vobject = current->GetVirtualObject(
object);
605 if (vobject && !vobject->HasEscaped() &&
606 vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
607 current->Get(var).To(&value)) {
608 current->SetReplacement(value);
610 current->SetEscaped(
object);
614 case IrOpcode::kLoadElement: {
615 Node*
object = current->ValueInput(0);
616 Node* index = current->ValueInput(1);
617 const VirtualObject* vobject = current->GetVirtualObject(
object);
621 if (vobject && !vobject->HasEscaped() &&
622 OffsetOfElementsAccess(op, index).To(&offset) &&
623 vobject->FieldAt(offset).To(&var) && current->Get(var).To(&value)) {
624 current->SetReplacement(value);
625 }
else if (vobject && !vobject->HasEscaped()) {
628 ElementAccess
const& access = ElementAccessOf(op);
630 (vobject->size() - access.header_size) >>
631 ElementSizeLog2Of(access.machine_type.representation());
636 vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var) &&
637 current->Get(var).To(&value) &&
639 NodeProperties::GetType(value).Is(access.type))) {
643 current->SetReplacement(value);
645 }
else if (length == 2 &&
646 vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var0) &&
647 current->Get(var0).To(&value0) &&
648 (value0 ==
nullptr ||
649 NodeProperties::GetType(value0).Is(access.type)) &&
650 vobject->FieldAt(OffsetOfElementAt(access, 1)).To(&var1) &&
651 current->Get(var1).To(&value1) &&
652 (value1 ==
nullptr ||
653 NodeProperties::GetType(value1).Is(access.type))) {
654 if (value0 && value1) {
662 jsgraph->graph()->NewNode(jsgraph->simplified()->NumberEqual(),
663 index, jsgraph->ZeroConstant());
664 NodeProperties::SetType(check, Type::Boolean());
665 Node* select = jsgraph->graph()->NewNode(
666 jsgraph->common()->Select(access.machine_type.representation()),
667 check, value0, value1);
668 NodeProperties::SetType(select, access.type);
669 current->SetReplacement(select);
670 current->SetEscaped(value0);
671 current->SetEscaped(value1);
680 current->SetEscaped(
object);
683 case IrOpcode::kTypeGuard: {
684 current->SetVirtualObject(current->ValueInput(0));
687 case IrOpcode::kReferenceEqual: {
688 Node* left = current->ValueInput(0);
689 Node* right = current->ValueInput(1);
690 const VirtualObject* left_object = current->GetVirtualObject(left);
691 const VirtualObject* right_object = current->GetVirtualObject(right);
692 Node* replacement =
nullptr;
693 if (left_object && !left_object->HasEscaped()) {
694 if (right_object && !right_object->HasEscaped() &&
695 left_object->id() == right_object->id()) {
696 replacement = jsgraph->TrueConstant();
698 replacement = jsgraph->FalseConstant();
700 }
else if (right_object && !right_object->HasEscaped()) {
701 replacement = jsgraph->FalseConstant();
710 if (!NodeProperties::GetType(left).IsNone() &&
711 !NodeProperties::GetType(right).IsNone()) {
712 current->SetReplacement(replacement);
714 current->SetEscaped(left);
715 current->SetEscaped(right);
720 case IrOpcode::kCheckMaps: {
721 CheckMapsParameters params = CheckMapsParametersOf(op);
722 Node* checked = current->ValueInput(0);
723 const VirtualObject* vobject = current->GetVirtualObject(checked);
726 if (vobject && !vobject->HasEscaped() &&
727 vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
728 current->Get(map_field).To(&map)) {
730 Type const map_type = NodeProperties::GetType(map);
731 AllowHandleDereference handle_dereference;
732 if (map_type.IsHeapConstant() &&
733 params.maps().contains(
734 Handle<Map>::cast(map_type.AsHeapConstant()->Value()))) {
735 current->MarkForDeletion();
744 current->SetEscaped(checked);
747 case IrOpcode::kCompareMaps: {
748 Node*
object = current->ValueInput(0);
749 const VirtualObject* vobject = current->GetVirtualObject(
object);
752 if (vobject && !vobject->HasEscaped() &&
753 vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
754 current->Get(map_field).To(&object_map)) {
756 current->SetReplacement(LowerCompareMapsWithoutLoad(
757 object_map, CompareMapsParametersOf(op).maps(), jsgraph));
765 current->SetEscaped(
object);
768 case IrOpcode::kCheckHeapObject: {
769 Node* checked = current->ValueInput(0);
770 switch (checked->opcode()) {
771 case IrOpcode::kAllocate:
772 case IrOpcode::kFinishRegion:
773 case IrOpcode::kHeapConstant:
774 current->SetReplacement(checked);
777 current->SetEscaped(checked);
782 case IrOpcode::kMapGuard: {
783 Node*
object = current->ValueInput(0);
784 const VirtualObject* vobject = current->GetVirtualObject(
object);
785 if (vobject && !vobject->HasEscaped()) {
786 current->MarkForDeletion();
790 case IrOpcode::kStateValues:
791 case IrOpcode::kFrameState:
796 int value_input_count = op->ValueInputCount();
797 for (
int i = 0;
i < value_input_count; ++
i) {
798 Node* input = current->ValueInput(
i);
799 current->SetEscaped(input);
801 if (OperatorProperties::HasContextInput(op)) {
802 current->SetEscaped(current->ContextInput());
811 void EscapeAnalysis::Reduce(Node* node, Reduction* reduction) {
812 const Operator* op = node->op();
813 TRACE(
"Reducing %s#%d\n", op->mnemonic(), node->id());
815 EscapeAnalysisTracker::Scope current(
this, tracker_, node, reduction);
816 ReduceNode(op, ¤t, jsgraph());
819 EscapeAnalysis::EscapeAnalysis(JSGraph* jsgraph, Zone* zone)
820 : EffectGraphReducer(
822 [this](Node* node, Reduction* reduction) { Reduce(node, reduction); },
824 tracker_(
new (zone) EscapeAnalysisTracker(jsgraph,
this, zone)),
827 Node* EscapeAnalysisResult::GetReplacementOf(Node* node) {
828 Node* replacement = tracker_->GetReplacementOf(node);
832 if (replacement) DCHECK_NULL(tracker_->GetReplacementOf(replacement));
836 Node* EscapeAnalysisResult::GetVirtualObjectField(
const VirtualObject* vobject,
837 int field, Node* effect) {
838 return tracker_->variable_states_.Get(vobject->FieldAt(field).FromJust(),
842 const VirtualObject* EscapeAnalysisResult::GetVirtualObject(Node* node) {
843 return tracker_->virtual_objects_.Get(node);
846 VirtualObject::VirtualObject(VariableTracker* var_states, VirtualObject::Id
id,
848 : Dependable(var_states->zone()), id_(id), fields_(var_states->zone()) {
849 DCHECK_EQ(0, size % kPointerSize);
850 TRACE(
"Creating VirtualObject id:%d size:%d\n",
id, size);
851 int num_fields = size / kPointerSize;
852 fields_.reserve(num_fields);
853 for (
int i = 0;
i < num_fields; ++
i) {
854 fields_.push_back(var_states->NewVariable());