diff options
Diffstat (limited to 'deps/v8/src/compiler/escape-analysis.cc')
-rw-r--r-- | deps/v8/src/compiler/escape-analysis.cc | 1471 |
1 files changed, 1471 insertions, 0 deletions
diff --git a/deps/v8/src/compiler/escape-analysis.cc b/deps/v8/src/compiler/escape-analysis.cc new file mode 100644 index 0000000000..af0ba6a639 --- /dev/null +++ b/deps/v8/src/compiler/escape-analysis.cc @@ -0,0 +1,1471 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "src/compiler/escape-analysis.h" + +#include <limits> + +#include "src/base/flags.h" +#include "src/bootstrapper.h" +#include "src/compilation-dependencies.h" +#include "src/compiler/common-operator.h" +#include "src/compiler/graph-reducer.h" +#include "src/compiler/js-operator.h" +#include "src/compiler/node.h" +#include "src/compiler/node-matchers.h" +#include "src/compiler/node-properties.h" +#include "src/compiler/operator-properties.h" +#include "src/compiler/simplified-operator.h" +#include "src/objects-inl.h" +#include "src/type-cache.h" + +namespace v8 { +namespace internal { +namespace compiler { + +const EscapeAnalysis::Alias EscapeAnalysis::kNotReachable = + std::numeric_limits<Alias>::max(); +const EscapeAnalysis::Alias EscapeAnalysis::kUntrackable = + std::numeric_limits<Alias>::max() - 1; + + +class VirtualObject : public ZoneObject { + public: + enum Status { kUntracked = 0, kTracked = 1 }; + VirtualObject(NodeId id, Zone* zone) + : id_(id), + status_(kUntracked), + fields_(zone), + phi_(zone), + object_state_(nullptr) {} + + VirtualObject(const VirtualObject& other) + : id_(other.id_), + status_(other.status_), + fields_(other.fields_), + phi_(other.phi_), + object_state_(other.object_state_) {} + + VirtualObject(NodeId id, Zone* zone, size_t field_number) + : id_(id), + status_(kTracked), + fields_(zone), + phi_(zone), + object_state_(nullptr) { + fields_.resize(field_number); + phi_.resize(field_number, false); + } + + Node* GetField(size_t offset) { + if (offset < fields_.size()) { + return fields_[offset]; + } + return nullptr; + } + + bool IsCreatedPhi(size_t offset) { + if (offset < phi_.size()) { + return phi_[offset]; + } + return false; + } + + bool SetField(size_t offset, Node* node, bool created_phi = false) { + bool changed = fields_[offset] != node || phi_[offset] != created_phi; + fields_[offset] = node; + phi_[offset] = created_phi; + if (changed && FLAG_trace_turbo_escape && node) { + PrintF("Setting field %zu of #%d to #%d (%s)\n", offset, id(), node->id(), + node->op()->mnemonic()); + } + return changed; + } + bool IsVirtual() const { return status_ == kTracked; } + bool IsTracked() const { return status_ != kUntracked; } + + Node** fields_array() { return &fields_.front(); } + size_t field_count() { return fields_.size(); } + bool ResizeFields(size_t field_count) { + if (field_count != fields_.size()) { + fields_.resize(field_count); + phi_.resize(field_count); + return true; + } + return false; + } + bool ClearAllFields() { + bool changed = false; + for (size_t i = 0; i < fields_.size(); ++i) { + if (fields_[i] != nullptr) { + fields_[i] = nullptr; + changed = true; + } + phi_[i] = false; + } + return changed; + } + bool UpdateFrom(const VirtualObject& other); + void SetObjectState(Node* node) { object_state_ = node; } + Node* GetObjectState() const { return object_state_; } + + NodeId id() const { return id_; } + void id(NodeId id) { id_ = id; } + + private: + NodeId id_; + Status status_; + ZoneVector<Node*> fields_; + ZoneVector<bool> phi_; + Node* object_state_; +}; + + +bool VirtualObject::UpdateFrom(const VirtualObject& other) { + bool changed = status_ != other.status_; + status_ = other.status_; + if (fields_.size() != other.fields_.size()) { + fields_ = other.fields_; + return true; + } + for (size_t i = 0; i < fields_.size(); ++i) { + if (fields_[i] != other.fields_[i]) { + changed = true; + fields_[i] = other.fields_[i]; + } + } + return changed; +} + + +class VirtualState : public ZoneObject { + public: + VirtualState(Zone* zone, size_t size); + VirtualState(const VirtualState& states); + + VirtualObject* VirtualObjectFromAlias(size_t alias); + VirtualObject* GetOrCreateTrackedVirtualObject(EscapeAnalysis::Alias alias, + NodeId id, Zone* zone); + void SetVirtualObject(EscapeAnalysis::Alias alias, VirtualObject* state); + void LastChangedAt(Node* node) { last_changed_ = node; } + Node* GetLastChanged() { return last_changed_; } + bool UpdateFrom(VirtualState* state, Zone* zone); + bool MergeFrom(MergeCache* cache, Zone* zone, Graph* graph, + CommonOperatorBuilder* common, Node* control); + size_t size() const { return info_.size(); } + + private: + ZoneVector<VirtualObject*> info_; + Node* last_changed_; +}; + + +class MergeCache : public ZoneObject { + public: + explicit MergeCache(Zone* zone) + : states_(zone), objects_(zone), fields_(zone) { + states_.reserve(4); + objects_.reserve(4); + fields_.reserve(4); + } + ZoneVector<VirtualState*>& states() { return states_; } + ZoneVector<VirtualObject*>& objects() { return objects_; } + ZoneVector<Node*>& fields() { return fields_; } + void Clear() { + states_.clear(); + objects_.clear(); + fields_.clear(); + } + size_t LoadVirtualObjectsFromStatesFor(EscapeAnalysis::Alias alias); + void LoadVirtualObjectsForFieldsFrom( + VirtualState* state, const ZoneVector<EscapeAnalysis::Alias>& aliases); + Node* GetFields(size_t pos); + + private: + ZoneVector<VirtualState*> states_; + ZoneVector<VirtualObject*> objects_; + ZoneVector<Node*> fields_; +}; + + +size_t MergeCache::LoadVirtualObjectsFromStatesFor( + EscapeAnalysis::Alias alias) { + objects_.clear(); + DCHECK_GT(states_.size(), 0u); + size_t min = std::numeric_limits<size_t>::max(); + for (VirtualState* state : states_) { + if (VirtualObject* obj = state->VirtualObjectFromAlias(alias)) { + objects_.push_back(obj); + min = std::min(obj->field_count(), min); + } + } + return min; +} + + +void MergeCache::LoadVirtualObjectsForFieldsFrom( + VirtualState* state, const ZoneVector<EscapeAnalysis::Alias>& aliases) { + objects_.clear(); + size_t max_alias = state->size(); + for (Node* field : fields_) { + EscapeAnalysis::Alias alias = aliases[field->id()]; + if (alias >= max_alias) continue; + if (VirtualObject* obj = state->VirtualObjectFromAlias(alias)) { + objects_.push_back(obj); + } + } +} + + +Node* MergeCache::GetFields(size_t pos) { + fields_.clear(); + Node* rep = objects_.front()->GetField(pos); + for (VirtualObject* obj : objects_) { + Node* field = obj->GetField(pos); + if (field) { + fields_.push_back(field); + } + if (field != rep) { + rep = nullptr; + } + } + return rep; +} + + +VirtualState::VirtualState(Zone* zone, size_t size) + : info_(size, nullptr, zone), last_changed_(nullptr) {} + + +VirtualState::VirtualState(const VirtualState& state) + : info_(state.info_.size(), nullptr, state.info_.get_allocator().zone()), + last_changed_(state.last_changed_) { + for (size_t i = 0; i < state.info_.size(); ++i) { + if (state.info_[i]) { + info_[i] = + new (info_.get_allocator().zone()) VirtualObject(*state.info_[i]); + } + } +} + + +VirtualObject* VirtualState::VirtualObjectFromAlias(size_t alias) { + return info_[alias]; +} + + +VirtualObject* VirtualState::GetOrCreateTrackedVirtualObject( + EscapeAnalysis::Alias alias, NodeId id, Zone* zone) { + if (VirtualObject* obj = VirtualObjectFromAlias(alias)) { + return obj; + } + VirtualObject* obj = new (zone) VirtualObject(id, zone, 0); + SetVirtualObject(alias, obj); + return obj; +} + + +void VirtualState::SetVirtualObject(EscapeAnalysis::Alias alias, + VirtualObject* obj) { + info_[alias] = obj; +} + + +bool VirtualState::UpdateFrom(VirtualState* from, Zone* zone) { + bool changed = false; + for (EscapeAnalysis::Alias alias = 0; alias < size(); ++alias) { + VirtualObject* ls = VirtualObjectFromAlias(alias); + VirtualObject* rs = from->VirtualObjectFromAlias(alias); + + if (rs == nullptr) { + continue; + } + + if (ls == nullptr) { + ls = new (zone) VirtualObject(*rs); + SetVirtualObject(alias, ls); + changed = true; + continue; + } + + if (FLAG_trace_turbo_escape) { + PrintF(" Updating fields of @%d\n", alias); + } + + changed = ls->UpdateFrom(*rs) || changed; + } + return false; +} + + +namespace { + +bool IsEquivalentPhi(Node* node1, Node* node2) { + if (node1 == node2) return true; + if (node1->opcode() != IrOpcode::kPhi || node2->opcode() != IrOpcode::kPhi || + node1->op()->ValueInputCount() != node2->op()->ValueInputCount()) { + return false; + } + for (int i = 0; i < node1->op()->ValueInputCount(); ++i) { + Node* input1 = NodeProperties::GetValueInput(node1, i); + Node* input2 = NodeProperties::GetValueInput(node2, i); + if (!IsEquivalentPhi(input1, input2)) { + return false; + } + } + return true; +} + + +bool IsEquivalentPhi(Node* phi, ZoneVector<Node*>& inputs) { + if (phi->opcode() != IrOpcode::kPhi) return false; + if (phi->op()->ValueInputCount() != inputs.size()) { + return false; + } + for (size_t i = 0; i < inputs.size(); ++i) { + Node* input = NodeProperties::GetValueInput(phi, static_cast<int>(i)); + if (!IsEquivalentPhi(input, inputs[i])) { + return false; + } + } + return true; +} + +} // namespace + + +Node* EscapeAnalysis::GetReplacementIfSame(ZoneVector<VirtualObject*>& objs) { + Node* rep = GetReplacement(objs.front()->id()); + for (VirtualObject* obj : objs) { + if (GetReplacement(obj->id()) != rep) { + return nullptr; + } + } + return rep; +} + + +bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph, + CommonOperatorBuilder* common, Node* control) { + DCHECK_GT(cache->states().size(), 0u); + bool changed = false; + for (EscapeAnalysis::Alias alias = 0; alias < size(); ++alias) { + size_t fields = cache->LoadVirtualObjectsFromStatesFor(alias); + if (cache->objects().size() == cache->states().size()) { + if (FLAG_trace_turbo_escape) { + PrintF(" Merging virtual objects of @%d\n", alias); + } + VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject( + alias, cache->objects().front()->id(), zone); + changed = mergeObject->ResizeFields(fields) || changed; + for (size_t i = 0; i < fields; ++i) { + if (Node* field = cache->GetFields(i)) { + changed = mergeObject->SetField(i, field) || changed; + if (FLAG_trace_turbo_escape) { + PrintF(" Field %zu agree on rep #%d\n", i, field->id()); + } + } else { + int value_input_count = static_cast<int>(cache->fields().size()); + if (cache->fields().size() == cache->objects().size()) { + Node* rep = mergeObject->GetField(i); + if (!rep || !mergeObject->IsCreatedPhi(i)) { + cache->fields().push_back(control); + Node* phi = graph->NewNode( + common->Phi(MachineRepresentation::kTagged, + value_input_count), + value_input_count + 1, &cache->fields().front()); + mergeObject->SetField(i, phi, true); + if (FLAG_trace_turbo_escape) { + PrintF(" Creating Phi #%d as merge of", phi->id()); + for (int i = 0; i < value_input_count; i++) { + PrintF(" #%d (%s)", cache->fields()[i]->id(), + cache->fields()[i]->op()->mnemonic()); + } + PrintF("\n"); + } + changed = true; + } else { + DCHECK(rep->opcode() == IrOpcode::kPhi); + for (int n = 0; n < value_input_count; ++n) { + if (n < rep->op()->ValueInputCount()) { + Node* old = NodeProperties::GetValueInput(rep, n); + if (old != cache->fields()[n]) { + changed = true; + NodeProperties::ReplaceValueInput(rep, cache->fields()[n], + n); + } + } else { + changed = true; + rep->InsertInput(graph->zone(), n, cache->fields()[n]); + } + } + if (rep->op()->ValueInputCount() != value_input_count) { + if (FLAG_trace_turbo_escape) { + PrintF(" Widening Phi #%d of arity %d to %d", rep->id(), + rep->op()->ValueInputCount(), value_input_count); + } + NodeProperties::ChangeOp( + rep, common->Phi(MachineRepresentation::kTagged, + value_input_count)); + } + } + } else { + changed = mergeObject->SetField(i, nullptr) || changed; + } + } + } + } else { + SetVirtualObject(alias, nullptr); + } + } + return changed; +} + + +EscapeStatusAnalysis::EscapeStatusAnalysis(EscapeAnalysis* object_analysis, + Graph* graph, Zone* zone) + : object_analysis_(object_analysis), + graph_(graph), + zone_(zone), + status_(graph->NodeCount(), kUnknown, zone), + queue_(zone) {} + + +EscapeStatusAnalysis::~EscapeStatusAnalysis() {} + + +bool EscapeStatusAnalysis::HasEntry(Node* node) { + return status_[node->id()] & (kTracked | kEscaped); +} + + +bool EscapeStatusAnalysis::IsVirtual(Node* node) { + return (status_[node->id()] & kTracked) && !(status_[node->id()] & kEscaped); +} + + +bool EscapeStatusAnalysis::IsEscaped(Node* node) { + return status_[node->id()] & kEscaped; +} + + +bool EscapeStatusAnalysis::IsAllocation(Node* node) { + return node->opcode() == IrOpcode::kAllocate || + node->opcode() == IrOpcode::kFinishRegion; +} + + +bool EscapeStatusAnalysis::SetEscaped(Node* node) { + bool changed = !(status_[node->id()] & kEscaped); + status_[node->id()] |= kEscaped | kTracked; + return changed; +} + + +void EscapeStatusAnalysis::Resize() { + status_.resize(graph()->NodeCount(), kUnknown); +} + + +size_t EscapeStatusAnalysis::size() { return status_.size(); } + + +void EscapeStatusAnalysis::Run() { + Resize(); + queue_.push_back(graph()->end()); + status_[graph()->end()->id()] |= kOnStack; + while (!queue_.empty()) { + Node* node = queue_.front(); + queue_.pop_front(); + status_[node->id()] &= ~kOnStack; + Process(node); + status_[node->id()] |= kVisited; + for (Edge edge : node->input_edges()) { + Node* input = edge.to(); + if (!(status_[input->id()] & (kVisited | kOnStack))) { + queue_.push_back(input); + status_[input->id()] |= kOnStack; + } + } + } +} + + +void EscapeStatusAnalysis::RevisitInputs(Node* node) { + for (Edge edge : node->input_edges()) { + Node* input = edge.to(); + if (!(status_[input->id()] & kOnStack)) { + queue_.push_back(input); + status_[input->id()] |= kOnStack; + } + } +} + + +void EscapeStatusAnalysis::RevisitUses(Node* node) { + for (Edge edge : node->use_edges()) { + Node* use = edge.from(); + if (!(status_[use->id()] & kOnStack)) { + queue_.push_back(use); + status_[use->id()] |= kOnStack; + } + } +} + + +void EscapeStatusAnalysis::Process(Node* node) { + switch (node->opcode()) { + case IrOpcode::kAllocate: + ProcessAllocate(node); + break; + case IrOpcode::kFinishRegion: + ProcessFinishRegion(node); + break; + case IrOpcode::kStoreField: + ProcessStoreField(node); + break; + case IrOpcode::kStoreElement: + ProcessStoreElement(node); + break; + case IrOpcode::kLoadField: + case IrOpcode::kLoadElement: { + if (Node* rep = object_analysis_->GetReplacement(node)) { + if (IsAllocation(rep) && CheckUsesForEscape(node, rep)) { + RevisitInputs(rep); + RevisitUses(rep); + } + } + break; + } + case IrOpcode::kPhi: + if (!HasEntry(node)) { + status_[node->id()] |= kTracked; + if (!IsAllocationPhi(node)) { + SetEscaped(node); + RevisitUses(node); + } + } + CheckUsesForEscape(node); + default: + break; + } +} + + +bool EscapeStatusAnalysis::IsAllocationPhi(Node* node) { + for (Edge edge : node->input_edges()) { + Node* input = edge.to(); + if (input->opcode() == IrOpcode::kPhi && !IsEscaped(input)) continue; + if (IsAllocation(input)) continue; + return false; + } + return true; +} + + +void EscapeStatusAnalysis::ProcessStoreField(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kStoreField); + Node* to = NodeProperties::GetValueInput(node, 0); + Node* val = NodeProperties::GetValueInput(node, 1); + if ((IsEscaped(to) || !IsAllocation(to)) && SetEscaped(val)) { + RevisitUses(val); + RevisitInputs(val); + if (FLAG_trace_turbo_escape) { + PrintF("Setting #%d (%s) to escaped because of store to field of #%d\n", + val->id(), val->op()->mnemonic(), to->id()); + } + } +} + + +void EscapeStatusAnalysis::ProcessStoreElement(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kStoreElement); + Node* to = NodeProperties::GetValueInput(node, 0); + Node* val = NodeProperties::GetValueInput(node, 2); + if ((IsEscaped(to) || !IsAllocation(to)) && SetEscaped(val)) { + RevisitUses(val); + RevisitInputs(val); + if (FLAG_trace_turbo_escape) { + PrintF("Setting #%d (%s) to escaped because of store to field of #%d\n", + val->id(), val->op()->mnemonic(), to->id()); + } + } +} + + +void EscapeStatusAnalysis::ProcessAllocate(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kAllocate); + if (!HasEntry(node)) { + status_[node->id()] |= kTracked; + if (FLAG_trace_turbo_escape) { + PrintF("Created status entry for node #%d (%s)\n", node->id(), + node->op()->mnemonic()); + } + NumberMatcher size(node->InputAt(0)); + DCHECK(node->InputAt(0)->opcode() != IrOpcode::kInt32Constant && + node->InputAt(0)->opcode() != IrOpcode::kInt64Constant && + node->InputAt(0)->opcode() != IrOpcode::kFloat32Constant && + node->InputAt(0)->opcode() != IrOpcode::kFloat64Constant); + if (!size.HasValue() && SetEscaped(node)) { + RevisitUses(node); + if (FLAG_trace_turbo_escape) { + PrintF("Setting #%d to escaped because of non-const alloc\n", + node->id()); + } + // This node is known to escape, uses do not have to be checked. + return; + } + } + if (CheckUsesForEscape(node, true)) { + RevisitUses(node); + } +} + + +bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep, + bool phi_escaping) { + for (Edge edge : uses->use_edges()) { + Node* use = edge.from(); + if (edge.index() >= use->op()->ValueInputCount() + + OperatorProperties::GetContextInputCount(use->op())) + continue; + switch (use->opcode()) { + case IrOpcode::kPhi: + if (phi_escaping && SetEscaped(rep)) { + if (FLAG_trace_turbo_escape) { + PrintF( + "Setting #%d (%s) to escaped because of use by phi node " + "#%d (%s)\n", + rep->id(), rep->op()->mnemonic(), use->id(), + use->op()->mnemonic()); + } + return true; + } + // Fallthrough. + case IrOpcode::kStoreField: + case IrOpcode::kLoadField: + case IrOpcode::kStoreElement: + case IrOpcode::kLoadElement: + case IrOpcode::kFrameState: + case IrOpcode::kStateValues: + case IrOpcode::kReferenceEqual: + case IrOpcode::kFinishRegion: + if (IsEscaped(use) && SetEscaped(rep)) { + if (FLAG_trace_turbo_escape) { + PrintF( + "Setting #%d (%s) to escaped because of use by escaping node " + "#%d (%s)\n", + rep->id(), rep->op()->mnemonic(), use->id(), + use->op()->mnemonic()); + } + return true; + } + break; + case IrOpcode::kObjectIsSmi: + if (!IsAllocation(rep) && SetEscaped(rep)) { + PrintF("Setting #%d (%s) to escaped because of use by #%d (%s)\n", + rep->id(), rep->op()->mnemonic(), use->id(), + use->op()->mnemonic()); + return true; + } + break; + default: + if (use->op()->EffectInputCount() == 0 && + uses->op()->EffectInputCount() > 0) { + PrintF("Encountered unaccounted use by #%d (%s)\n", use->id(), + use->op()->mnemonic()); + UNREACHABLE(); + } + if (SetEscaped(rep)) { + if (FLAG_trace_turbo_escape) { + PrintF("Setting #%d (%s) to escaped because of use by #%d (%s)\n", + rep->id(), rep->op()->mnemonic(), use->id(), + use->op()->mnemonic()); + } + return true; + } + } + } + return false; +} + + +void EscapeStatusAnalysis::ProcessFinishRegion(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kFinishRegion); + if (!HasEntry(node)) { + status_[node->id()] |= kTracked; + RevisitUses(node); + } + if (CheckUsesForEscape(node, true)) { + RevisitInputs(node); + } +} + + +void EscapeStatusAnalysis::DebugPrint() { + for (NodeId id = 0; id < status_.size(); id++) { + if (status_[id] & kTracked) { + PrintF("Node #%d is %s\n", id, + (status_[id] & kEscaped) ? "escaping" : "virtual"); + } + } +} + + +EscapeAnalysis::EscapeAnalysis(Graph* graph, CommonOperatorBuilder* common, + Zone* zone) + : graph_(graph), + common_(common), + zone_(zone), + virtual_states_(zone), + replacements_(zone), + escape_status_(this, graph, zone), + cache_(new (zone) MergeCache(zone)), + aliases_(zone), + next_free_alias_(0) {} + + +EscapeAnalysis::~EscapeAnalysis() {} + + +void EscapeAnalysis::Run() { + replacements_.resize(graph()->NodeCount()); + AssignAliases(); + RunObjectAnalysis(); + escape_status_.Run(); +} + + +void EscapeAnalysis::AssignAliases() { + ZoneVector<Node*> stack(zone()); + stack.push_back(graph()->end()); + CHECK_LT(graph()->NodeCount(), kUntrackable); + aliases_.resize(graph()->NodeCount(), kNotReachable); + aliases_[graph()->end()->id()] = kUntrackable; + while (!stack.empty()) { + Node* node = stack.back(); + stack.pop_back(); + switch (node->opcode()) { + case IrOpcode::kAllocate: + if (aliases_[node->id()] >= kUntrackable) { + aliases_[node->id()] = NextAlias(); + } + break; + case IrOpcode::kFinishRegion: { + Node* allocate = NodeProperties::GetValueInput(node, 0); + if (allocate->opcode() == IrOpcode::kAllocate) { + if (aliases_[allocate->id()] >= kUntrackable) { + if (aliases_[allocate->id()] == kNotReachable) { + stack.push_back(allocate); + } + aliases_[allocate->id()] = NextAlias(); + } + aliases_[node->id()] = aliases_[allocate->id()]; + } else { + aliases_[node->id()] = NextAlias(); + } + break; + } + default: + DCHECK_EQ(aliases_[node->id()], kUntrackable); + break; + } + for (Edge edge : node->input_edges()) { + Node* input = edge.to(); + if (aliases_[input->id()] == kNotReachable) { + stack.push_back(input); + aliases_[input->id()] = kUntrackable; + } + } + } + + if (FLAG_trace_turbo_escape) { + PrintF("Discovered trackable nodes"); + for (EscapeAnalysis::Alias id = 0; id < graph()->NodeCount(); ++id) { + if (aliases_[id] < kUntrackable) { + if (FLAG_trace_turbo_escape) { + PrintF(" #%u", id); + } + } + } + PrintF("\n"); + } +} + + +void EscapeAnalysis::RunObjectAnalysis() { + virtual_states_.resize(graph()->NodeCount()); + ZoneVector<Node*> stack(zone()); + stack.push_back(graph()->start()); + while (!stack.empty()) { + Node* node = stack.back(); + stack.pop_back(); + if (aliases_[node->id()] != kNotReachable && Process(node)) { + for (Edge edge : node->use_edges()) { + if (NodeProperties::IsEffectEdge(edge)) { + Node* use = edge.from(); + if ((use->opcode() != IrOpcode::kLoadField && + use->opcode() != IrOpcode::kLoadElement) || + !IsDanglingEffectNode(use)) { + stack.push_back(use); + } + } + } + // First process loads: dangling loads are a problem otherwise. + for (Edge edge : node->use_edges()) { + if (NodeProperties::IsEffectEdge(edge)) { + Node* use = edge.from(); + if ((use->opcode() == IrOpcode::kLoadField || + use->opcode() == IrOpcode::kLoadElement) && + IsDanglingEffectNode(use)) { + stack.push_back(use); + } + } + } + } + } + if (FLAG_trace_turbo_escape) { + DebugPrint(); + } +} + + +bool EscapeAnalysis::IsDanglingEffectNode(Node* node) { + if (node->op()->EffectInputCount() == 0) return false; + if (node->op()->EffectOutputCount() == 0) return false; + if (node->op()->EffectInputCount() == 1 && + NodeProperties::GetEffectInput(node)->opcode() == IrOpcode::kStart) { + // The start node is used as sentinel for nodes that are in general + // effectful, but of which an analysis has determined that they do not + // produce effects in this instance. We don't consider these nodes dangling. + return false; + } + for (Edge edge : node->use_edges()) { + if (NodeProperties::IsEffectEdge(edge)) { + return false; + } + } + return true; +} + + +bool EscapeAnalysis::Process(Node* node) { + switch (node->opcode()) { + case IrOpcode::kAllocate: + ProcessAllocation(node); + break; + case IrOpcode::kBeginRegion: + ForwardVirtualState(node); + break; + case IrOpcode::kFinishRegion: + ProcessFinishRegion(node); + break; + case IrOpcode::kStoreField: + ProcessStoreField(node); + break; + case IrOpcode::kLoadField: + ProcessLoadField(node); + break; + case IrOpcode::kStoreElement: + ProcessStoreElement(node); + break; + case IrOpcode::kLoadElement: + ProcessLoadElement(node); + break; + case IrOpcode::kStart: + ProcessStart(node); + break; + case IrOpcode::kEffectPhi: + return ProcessEffectPhi(node); + break; + default: + if (node->op()->EffectInputCount() > 0) { + ForwardVirtualState(node); + } + ProcessAllocationUsers(node); + break; + } + return true; +} + + +void EscapeAnalysis::ProcessAllocationUsers(Node* node) { + for (Edge edge : node->input_edges()) { + Node* input = edge.to(); + if (!NodeProperties::IsValueEdge(edge) && + !NodeProperties::IsContextEdge(edge)) + continue; + switch (node->opcode()) { + case IrOpcode::kStoreField: + case IrOpcode::kLoadField: + case IrOpcode::kStoreElement: + case IrOpcode::kLoadElement: + case IrOpcode::kFrameState: + case IrOpcode::kStateValues: + case IrOpcode::kReferenceEqual: + case IrOpcode::kFinishRegion: + case IrOpcode::kPhi: + break; + default: + VirtualState* state = virtual_states_[node->id()]; + if (VirtualObject* obj = ResolveVirtualObject(state, input)) { + if (obj->ClearAllFields()) { + state->LastChangedAt(node); + } + } + break; + } + } +} + + +bool EscapeAnalysis::IsEffectBranchPoint(Node* node) { + int count = 0; + for (Edge edge : node->use_edges()) { + if (NodeProperties::IsEffectEdge(edge)) { + if (++count > 1) { + return true; + } + } + } + return false; +} + + +void EscapeAnalysis::ForwardVirtualState(Node* node) { + DCHECK_EQ(node->op()->EffectInputCount(), 1); + if (node->opcode() != IrOpcode::kLoadField && + node->opcode() != IrOpcode::kLoadElement && + node->opcode() != IrOpcode::kLoad && IsDanglingEffectNode(node)) { + PrintF("Dangeling effect node: #%d (%s)\n", node->id(), + node->op()->mnemonic()); + UNREACHABLE(); + } + Node* effect = NodeProperties::GetEffectInput(node); + // Break the cycle for effect phis. + if (effect->opcode() == IrOpcode::kEffectPhi) { + if (virtual_states_[effect->id()] == nullptr) { + virtual_states_[effect->id()] = + new (zone()) VirtualState(zone(), AliasCount()); + } + } + DCHECK_NOT_NULL(virtual_states_[effect->id()]); + if (IsEffectBranchPoint(effect)) { + if (FLAG_trace_turbo_escape) { + PrintF("Copying object state %p from #%d (%s) to #%d (%s)\n", + static_cast<void*>(virtual_states_[effect->id()]), effect->id(), + effect->op()->mnemonic(), node->id(), node->op()->mnemonic()); + } + if (!virtual_states_[node->id()]) { + virtual_states_[node->id()] = + new (zone()) VirtualState(*virtual_states_[effect->id()]); + } else { + virtual_states_[node->id()]->UpdateFrom(virtual_states_[effect->id()], + zone()); + } + } else { + virtual_states_[node->id()] = virtual_states_[effect->id()]; + if (FLAG_trace_turbo_escape) { + PrintF("Forwarding object state %p from #%d (%s) to #%d (%s)\n", + static_cast<void*>(virtual_states_[effect->id()]), effect->id(), + effect->op()->mnemonic(), node->id(), node->op()->mnemonic()); + } + } +} + + +void EscapeAnalysis::ProcessStart(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kStart); + virtual_states_[node->id()] = new (zone()) VirtualState(zone(), AliasCount()); +} + + +bool EscapeAnalysis::ProcessEffectPhi(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi); + bool changed = false; + + VirtualState* mergeState = virtual_states_[node->id()]; + if (!mergeState) { + mergeState = new (zone()) VirtualState(zone(), AliasCount()); + virtual_states_[node->id()] = mergeState; + changed = true; + if (FLAG_trace_turbo_escape) { + PrintF("Effect Phi #%d got new states map %p.\n", node->id(), + static_cast<void*>(mergeState)); + } + } else if (mergeState->GetLastChanged() != node) { + changed = true; + } + + cache_->Clear(); + + if (FLAG_trace_turbo_escape) { + PrintF("At Effect Phi #%d, merging states into %p:", node->id(), + static_cast<void*>(mergeState)); + } + + for (int i = 0; i < node->op()->EffectInputCount(); ++i) { + Node* input = NodeProperties::GetEffectInput(node, i); + VirtualState* state = virtual_states_[input->id()]; + if (state) { + cache_->states().push_back(state); + } + if (FLAG_trace_turbo_escape) { + PrintF(" %p (from %d %s)", static_cast<void*>(state), input->id(), + input->op()->mnemonic()); + } + } + if (FLAG_trace_turbo_escape) { + PrintF("\n"); + } + + if (cache_->states().size() == 0) { + return changed; + } + + changed = mergeState->MergeFrom(cache_, zone(), graph(), common(), + NodeProperties::GetControlInput(node)) || + changed; + + if (FLAG_trace_turbo_escape) { + PrintF("Merge %s the node.\n", changed ? "changed" : "did not change"); + } + + if (changed) { + mergeState->LastChangedAt(node); + escape_status_.Resize(); + } + return changed; +} + + +void EscapeAnalysis::ProcessAllocation(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kAllocate); + ForwardVirtualState(node); + + // Check if we have already processed this node. + if (virtual_states_[node->id()]->VirtualObjectFromAlias( + aliases_[node->id()])) { + return; + } + + NumberMatcher size(node->InputAt(0)); + DCHECK(node->InputAt(0)->opcode() != IrOpcode::kInt32Constant && + node->InputAt(0)->opcode() != IrOpcode::kInt64Constant && + node->InputAt(0)->opcode() != IrOpcode::kFloat32Constant && + node->InputAt(0)->opcode() != IrOpcode::kFloat64Constant); + if (size.HasValue()) { + virtual_states_[node->id()]->SetVirtualObject( + aliases_[node->id()], + new (zone()) + VirtualObject(node->id(), zone(), size.Value() / kPointerSize)); + } else { + virtual_states_[node->id()]->SetVirtualObject( + aliases_[node->id()], new (zone()) VirtualObject(node->id(), zone())); + } + virtual_states_[node->id()]->LastChangedAt(node); +} + + +void EscapeAnalysis::ProcessFinishRegion(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kFinishRegion); + ForwardVirtualState(node); + Node* allocation = NodeProperties::GetValueInput(node, 0); + if (allocation->opcode() == IrOpcode::kAllocate) { + VirtualState* state = virtual_states_[node->id()]; + if (!state->VirtualObjectFromAlias(aliases_[node->id()])) { + VirtualObject* vobj_alloc = + state->VirtualObjectFromAlias(aliases_[allocation->id()]); + DCHECK_NOT_NULL(vobj_alloc); + state->SetVirtualObject(aliases_[node->id()], vobj_alloc); + if (FLAG_trace_turbo_escape) { + PrintF("Linked finish region node #%d to node #%d\n", node->id(), + allocation->id()); + } + state->LastChangedAt(node); + } + } +} + + +Node* EscapeAnalysis::replacement(NodeId id) { + if (id >= replacements_.size()) return nullptr; + return replacements_[id]; +} + + +Node* EscapeAnalysis::replacement(Node* node) { + return replacement(node->id()); +} + + +bool EscapeAnalysis::SetReplacement(Node* node, Node* rep) { + bool changed = replacements_[node->id()] != rep; + replacements_[node->id()] = rep; + return changed; +} + + +bool EscapeAnalysis::UpdateReplacement(VirtualState* state, Node* node, + Node* rep) { + if (SetReplacement(node, rep)) { + state->LastChangedAt(node); + if (FLAG_trace_turbo_escape) { + if (rep) { + PrintF("Replacement of #%d is #%d (%s)\n", node->id(), rep->id(), + rep->op()->mnemonic()); + } else { + PrintF("Replacement of #%d cleared\n", node->id()); + } + } + return true; + } + return false; +} + + +Node* EscapeAnalysis::ResolveReplacement(Node* node) { + while (replacement(node)) { + node = replacement(node); + } + return node; +} + + +Node* EscapeAnalysis::GetReplacement(Node* node) { + return GetReplacement(node->id()); +} + + +Node* EscapeAnalysis::GetReplacement(NodeId id) { + Node* node = nullptr; + while (replacement(id)) { + node = replacement(id); + id = node->id(); + } + return node; +} + + +bool EscapeAnalysis::IsVirtual(Node* node) { + if (node->id() >= escape_status_.size()) { + return false; + } + return escape_status_.IsVirtual(node); +} + + +bool EscapeAnalysis::IsEscaped(Node* node) { + if (node->id() >= escape_status_.size()) { + return false; + } + return escape_status_.IsEscaped(node); +} + + +bool EscapeAnalysis::SetEscaped(Node* node) { + return escape_status_.SetEscaped(node); +} + + +VirtualObject* EscapeAnalysis::GetVirtualObject(Node* at, NodeId id) { + if (VirtualState* states = virtual_states_[at->id()]) { + return states->VirtualObjectFromAlias(aliases_[id]); + } + return nullptr; +} + + +VirtualObject* EscapeAnalysis::ResolveVirtualObject(VirtualState* state, + Node* node) { + VirtualObject* obj = GetVirtualObject(state, ResolveReplacement(node)); + while (obj && replacement(obj->id())) { + if (VirtualObject* next = GetVirtualObject(state, replacement(obj->id()))) { + obj = next; + } else { + break; + } + } + return obj; +} + + +bool EscapeAnalysis::CompareVirtualObjects(Node* left, Node* right) { + DCHECK(IsVirtual(left) && IsVirtual(right)); + left = ResolveReplacement(left); + right = ResolveReplacement(right); + if (IsEquivalentPhi(left, right)) { + return true; + } + return false; +} + + +int EscapeAnalysis::OffsetFromAccess(Node* node) { + DCHECK(OpParameter<FieldAccess>(node).offset % kPointerSize == 0); + return OpParameter<FieldAccess>(node).offset / kPointerSize; +} + + +void EscapeAnalysis::ProcessLoadFromPhi(int offset, Node* from, Node* node, + VirtualState* state) { + if (FLAG_trace_turbo_escape) { + PrintF("Load #%d from phi #%d", node->id(), from->id()); + } + + cache_->fields().clear(); + for (int i = 0; i < node->op()->ValueInputCount(); ++i) { + Node* input = NodeProperties::GetValueInput(node, i); + cache_->fields().push_back(input); + } + + cache_->LoadVirtualObjectsForFieldsFrom(state, aliases_); + if (cache_->objects().size() == cache_->fields().size()) { + cache_->GetFields(offset); + if (cache_->fields().size() == cache_->objects().size()) { + Node* rep = replacement(node); + if (!rep || !IsEquivalentPhi(rep, cache_->fields())) { + int value_input_count = static_cast<int>(cache_->fields().size()); + cache_->fields().push_back(NodeProperties::GetControlInput(from)); + Node* phi = graph()->NewNode( + common()->Phi(MachineRepresentation::kTagged, value_input_count), + value_input_count + 1, &cache_->fields().front()); + escape_status_.Resize(); + SetReplacement(node, phi); + state->LastChangedAt(node); + if (FLAG_trace_turbo_escape) { + PrintF(" got phi created.\n"); + } + } else if (FLAG_trace_turbo_escape) { + PrintF(" has already phi #%d.\n", rep->id()); + } + } else if (FLAG_trace_turbo_escape) { + PrintF(" has incomplete field info.\n"); + } + } else if (FLAG_trace_turbo_escape) { + PrintF(" has incomplete virtual object info.\n"); + } +} + + +void EscapeAnalysis::ProcessLoadField(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kLoadField); + ForwardVirtualState(node); + Node* from = NodeProperties::GetValueInput(node, 0); + VirtualState* state = virtual_states_[node->id()]; + if (VirtualObject* object = ResolveVirtualObject(state, from)) { + int offset = OffsetFromAccess(node); + if (!object->IsTracked()) return; + Node* value = object->GetField(offset); + if (value) { + value = ResolveReplacement(value); + } + // Record that the load has this alias. + UpdateReplacement(state, node, value); + } else { + if (from->opcode() == IrOpcode::kPhi && + OpParameter<FieldAccess>(node).offset % kPointerSize == 0) { + int offset = OffsetFromAccess(node); + // Only binary phis are supported for now. + ProcessLoadFromPhi(offset, from, node, state); + } + } +} + + +void EscapeAnalysis::ProcessLoadElement(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kLoadElement); + ForwardVirtualState(node); + Node* from = NodeProperties::GetValueInput(node, 0); + VirtualState* state = virtual_states_[node->id()]; + Node* index_node = node->InputAt(1); + NumberMatcher index(index_node); + DCHECK(index_node->opcode() != IrOpcode::kInt32Constant && + index_node->opcode() != IrOpcode::kInt64Constant && + index_node->opcode() != IrOpcode::kFloat32Constant && + index_node->opcode() != IrOpcode::kFloat64Constant); + ElementAccess access = OpParameter<ElementAccess>(node); + if (index.HasValue()) { + int offset = index.Value() + access.header_size / kPointerSize; + if (VirtualObject* object = ResolveVirtualObject(state, from)) { + CHECK_GE(ElementSizeLog2Of(access.machine_type.representation()), + kPointerSizeLog2); + CHECK_EQ(access.header_size % kPointerSize, 0); + + if (!object->IsTracked()) return; + Node* value = object->GetField(offset); + if (value) { + value = ResolveReplacement(value); + } + // Record that the load has this alias. + UpdateReplacement(state, node, value); + } else if (from->opcode() == IrOpcode::kPhi) { + ElementAccess access = OpParameter<ElementAccess>(node); + int offset = index.Value() + access.header_size / kPointerSize; + ProcessLoadFromPhi(offset, from, node, state); + } + } else { + // We have a load from a non-const index, cannot eliminate object. + if (SetEscaped(from)) { + if (FLAG_trace_turbo_escape) { + PrintF( + "Setting #%d (%s) to escaped because store element #%d to " + "non-const " + "index #%d (%s)\n", + from->id(), from->op()->mnemonic(), node->id(), index_node->id(), + index_node->op()->mnemonic()); + } + } + } +} + + +void EscapeAnalysis::ProcessStoreField(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kStoreField); + ForwardVirtualState(node); + Node* to = NodeProperties::GetValueInput(node, 0); + Node* val = NodeProperties::GetValueInput(node, 1); + VirtualState* state = virtual_states_[node->id()]; + if (VirtualObject* obj = ResolveVirtualObject(state, to)) { + if (!obj->IsTracked()) return; + int offset = OffsetFromAccess(node); + if (obj->SetField(offset, ResolveReplacement(val))) { + state->LastChangedAt(node); + } + } +} + + +void EscapeAnalysis::ProcessStoreElement(Node* node) { + DCHECK_EQ(node->opcode(), IrOpcode::kStoreElement); + ForwardVirtualState(node); + Node* to = NodeProperties::GetValueInput(node, 0); + Node* index_node = node->InputAt(1); + NumberMatcher index(index_node); + DCHECK(index_node->opcode() != IrOpcode::kInt32Constant && + index_node->opcode() != IrOpcode::kInt64Constant && + index_node->opcode() != IrOpcode::kFloat32Constant && + index_node->opcode() != IrOpcode::kFloat64Constant); + ElementAccess access = OpParameter<ElementAccess>(node); + Node* val = NodeProperties::GetValueInput(node, 2); + if (index.HasValue()) { + int offset = index.Value() + access.header_size / kPointerSize; + VirtualState* states = virtual_states_[node->id()]; + if (VirtualObject* obj = ResolveVirtualObject(states, to)) { + if (!obj->IsTracked()) return; + CHECK_GE(ElementSizeLog2Of(access.machine_type.representation()), + kPointerSizeLog2); + CHECK_EQ(access.header_size % kPointerSize, 0); + if (obj->SetField(offset, ResolveReplacement(val))) { + states->LastChangedAt(node); + } + } + } else { + // We have a store to a non-const index, cannot eliminate object. + if (SetEscaped(to)) { + if (FLAG_trace_turbo_escape) { + PrintF( + "Setting #%d (%s) to escaped because store element #%d to " + "non-const " + "index #%d (%s)\n", + to->id(), to->op()->mnemonic(), node->id(), index_node->id(), + index_node->op()->mnemonic()); + } + } + } +} + + +Node* EscapeAnalysis::GetOrCreateObjectState(Node* effect, Node* node) { + if ((node->opcode() == IrOpcode::kFinishRegion || + node->opcode() == IrOpcode::kAllocate) && + IsVirtual(node)) { + if (VirtualObject* vobj = + ResolveVirtualObject(virtual_states_[effect->id()], node)) { + if (Node* object_state = vobj->GetObjectState()) { + return object_state; + } else { + cache_->fields().clear(); + for (size_t i = 0; i < vobj->field_count(); ++i) { + if (Node* field = vobj->GetField(i)) { + cache_->fields().push_back(field); + } + } + int input_count = static_cast<int>(cache_->fields().size()); + Node* new_object_state = + graph()->NewNode(common()->ObjectState(input_count, vobj->id()), + input_count, &cache_->fields().front()); + vobj->SetObjectState(new_object_state); + if (FLAG_trace_turbo_escape) { + PrintF( + "Creating object state #%d for vobj %p (from node #%d) at effect " + "#%d\n", + new_object_state->id(), static_cast<void*>(vobj), node->id(), + effect->id()); + } + // Now fix uses of other objects. + for (size_t i = 0; i < vobj->field_count(); ++i) { + if (Node* field = vobj->GetField(i)) { + if (Node* field_object_state = + GetOrCreateObjectState(effect, field)) { + NodeProperties::ReplaceValueInput( + new_object_state, field_object_state, static_cast<int>(i)); + } + } + } + return new_object_state; + } + } + } + return nullptr; +} + + +void EscapeAnalysis::DebugPrintObject(VirtualObject* object, Alias alias) { + PrintF(" Alias @%d: Object #%d with %zu fields\n", alias, object->id(), + object->field_count()); + for (size_t i = 0; i < object->field_count(); ++i) { + if (Node* f = object->GetField(i)) { + PrintF(" Field %zu = #%d (%s)\n", i, f->id(), f->op()->mnemonic()); + } + } +} + + +void EscapeAnalysis::DebugPrintState(VirtualState* state) { + PrintF("Dumping object state %p\n", static_cast<void*>(state)); + for (Alias alias = 0; alias < AliasCount(); ++alias) { + if (VirtualObject* object = state->VirtualObjectFromAlias(alias)) { + DebugPrintObject(object, alias); + } + } +} + + +void EscapeAnalysis::DebugPrint() { + ZoneVector<VirtualState*> object_states(zone()); + for (NodeId id = 0; id < virtual_states_.size(); id++) { + if (VirtualState* states = virtual_states_[id]) { + if (std::find(object_states.begin(), object_states.end(), states) == + object_states.end()) { + object_states.push_back(states); + } + } + } + for (size_t n = 0; n < object_states.size(); n++) { + DebugPrintState(object_states[n]); + } +} + + +VirtualObject* EscapeAnalysis::GetVirtualObject(VirtualState* state, + Node* node) { + if (node->id() >= aliases_.size()) return nullptr; + Alias alias = aliases_[node->id()]; + if (alias >= state->size()) return nullptr; + return state->VirtualObjectFromAlias(alias); +} + +} // namespace compiler +} // namespace internal +} // namespace v8 |