[turbofan] Stabilize escape analysis (without deopt)

Bugfixes and improvements in escape analysis include:

* Handling of ObjectIsSmi (non-escaping)
* Handling of nested phi replacements
* Handling of phis with arity > 2
* Resilience against effectful nodes dangling from start
* Allocations escape now, if non-const load/store is performed
* Fixed a bug where non-allocated objects where tracked
* Allow fixed double arrays to be tracked

R=mstarzinger@chromium.org
BUG=v8:4586
LOG=n

Review URL: https://codereview.chromium.org/1510973006

Cr-Commit-Position: refs/heads/master@{#32833}
This commit is contained in:
sigurds 2015-12-14 05:13:26 -08:00 committed by Commit bot
parent 4460b85a3e
commit 3161c17126
7 changed files with 437 additions and 149 deletions

View File

@ -34,6 +34,8 @@ Reduction EscapeAnalysisReducer::Reduce(Node* node) {
return ReduceFinishRegion(node);
case IrOpcode::kReferenceEqual:
return ReduceReferenceEqual(node);
case IrOpcode::kObjectIsSmi:
return ReduceObjectIsSmi(node);
case IrOpcode::kStateValues:
case IrOpcode::kFrameState:
return ReplaceWithDeoptDummy(node);
@ -146,6 +148,20 @@ Reduction EscapeAnalysisReducer::ReduceReferenceEqual(Node* node) {
}
Reduction EscapeAnalysisReducer::ReduceObjectIsSmi(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kObjectIsSmi);
Node* input = NodeProperties::GetValueInput(node, 0);
if (escape_analysis()->IsVirtual(input)) {
ReplaceWithValue(node, jsgraph()->FalseConstant());
if (FLAG_trace_turbo_escape) {
PrintF("Replaced ObjectIsSmi #%d with false\n", node->id());
}
return Replace(node);
}
return NoChange();
}
// TODO(sigurds): This is a temporary solution until escape analysis
// supports deoptimization.
Reduction EscapeAnalysisReducer::ReplaceWithDeoptDummy(Node* node) {

View File

@ -29,6 +29,7 @@ class EscapeAnalysisReducer final : public AdvancedReducer {
Reduction ReduceAllocate(Node* node);
Reduction ReduceFinishRegion(Node* node);
Reduction ReduceReferenceEqual(Node* node);
Reduction ReduceObjectIsSmi(Node* node);
Reduction ReplaceWithDeoptDummy(Node* node);

View File

@ -29,17 +29,27 @@ class VirtualObject : public ZoneObject {
public:
enum Status { kUntracked = 0, kTracked = 1 };
VirtualObject(NodeId id, Zone* zone)
: id_(id), status_(kUntracked), fields_(zone), replacement_(nullptr) {}
: id_(id),
status_(kUntracked),
fields_(zone),
phi_(zone),
replacement_(nullptr) {}
VirtualObject(const VirtualObject& other)
: id_(other.id_),
status_(other.status_),
fields_(other.fields_),
phi_(other.phi_),
replacement_(other.replacement_) {}
VirtualObject(NodeId id, Zone* zone, size_t field_number)
: id_(id), status_(kTracked), fields_(zone), replacement_(nullptr) {
: id_(id),
status_(kTracked),
fields_(zone),
phi_(zone),
replacement_(nullptr) {
fields_.resize(field_number);
phi_.resize(field_number, false);
}
Node* GetField(size_t offset) {
@ -49,9 +59,21 @@ class VirtualObject : public ZoneObject {
return nullptr;
}
bool SetField(size_t offset, Node* node) {
bool changed = fields_[offset] != node;
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; }
@ -68,6 +90,7 @@ class VirtualObject : public ZoneObject {
bool ResizeFields(size_t field_count) {
if (field_count != fields_.size()) {
fields_.resize(field_count);
phi_.resize(field_count);
return true;
}
return false;
@ -79,6 +102,7 @@ class VirtualObject : public ZoneObject {
fields_[i] = nullptr;
changed = true;
}
phi_[i] = false;
}
return changed;
}
@ -91,6 +115,7 @@ class VirtualObject : public ZoneObject {
NodeId id_;
Status status_;
ZoneVector<Node*> fields_;
ZoneVector<bool> phi_;
Node* replacement_;
};
@ -133,8 +158,8 @@ class VirtualState : public ZoneObject {
Node* ResolveReplacement(Node* node);
bool UpdateReplacement(Node* node, Node* rep, Zone* zone);
bool UpdateFrom(VirtualState* state, Zone* zone);
bool MergeFrom(VirtualState* left, VirtualState* right, Zone* zone,
Graph* graph, CommonOperatorBuilder* common, Node* control);
bool MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
CommonOperatorBuilder* common, Node* control);
size_t size() { return info_.size(); }
@ -230,7 +255,6 @@ bool VirtualState::UpdateFrom(NodeId id, VirtualObject* fromObj, Zone* zone) {
bool VirtualState::UpdateFrom(VirtualState* from, Zone* zone) {
DCHECK_EQ(size(), from->size());
bool changed = false;
for (NodeId id = 0; id < size(); ++id) {
VirtualObject* ls = GetVirtualObject(id);
@ -257,59 +281,187 @@ bool VirtualState::UpdateFrom(VirtualState* from, Zone* zone) {
}
bool VirtualState::MergeFrom(VirtualState* left, VirtualState* right,
Zone* zone, Graph* graph,
CommonOperatorBuilder* common, Node* control) {
bool changed = false;
for (NodeId id = 0; id < std::min(left->size(), right->size()); ++id) {
VirtualObject* ls = left->GetVirtualObject(id);
VirtualObject* rs = right->GetVirtualObject(id);
namespace {
if (ls != nullptr && rs != nullptr) {
size_t min_size(ZoneVector<VirtualState*>& states) {
size_t min = SIZE_MAX;
for (VirtualState* state : states) {
min = std::min(state->size(), min);
}
return min;
}
size_t min_field_count(ZoneVector<VirtualObject*>& objs) {
size_t min = SIZE_MAX;
for (VirtualObject* obj : objs) {
min = std::min(obj->field_count(), min);
}
return min;
}
void GetVirtualObjects(ZoneVector<VirtualState*> states,
ZoneVector<VirtualObject*>& objs, NodeId id) {
objs.clear();
for (VirtualState* state : states) {
if (VirtualObject* obj = state->GetVirtualObject(id)) {
objs.push_back(obj);
}
}
}
void GetVirtualObjects(VirtualState* state, ZoneVector<Node*> nodes,
ZoneVector<VirtualObject*>& objs) {
objs.clear();
for (Node* node : nodes) {
if (VirtualObject* obj = state->GetVirtualObject(node)) {
objs.push_back(obj);
}
}
}
Node* GetFieldIfSame(size_t pos, ZoneVector<VirtualObject*>& objs) {
Node* rep = objs.front()->GetField(pos);
for (VirtualObject* obj : objs) {
if (obj->GetField(pos) != rep) {
return nullptr;
}
}
return rep;
}
Node* GetReplacementIfSame(ZoneVector<VirtualObject*>& objs) {
Node* rep = objs.front()->GetReplacement();
for (VirtualObject* obj : objs) {
if (obj->GetReplacement() != rep) {
return nullptr;
}
}
return rep;
}
void GetFields(ZoneVector<VirtualObject*>& objs, ZoneVector<Node*>& fields,
size_t pos) {
fields.clear();
for (VirtualObject* obj : objs) {
if (Node* field = obj->GetField(pos)) {
fields.push_back(field);
}
}
}
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
bool VirtualState::MergeFrom(MergeCache* cache, Zone* zone, Graph* graph,
CommonOperatorBuilder* common, Node* control) {
DCHECK_GT(cache->states().size(), 0u);
bool changed = false;
for (NodeId id = 0; id < min_size(cache->states()); ++id) {
GetVirtualObjects(cache->states(), cache->objects(), id);
if (cache->objects().size() == cache->states().size()) {
// Don't process linked objects.
if (cache->objects()[0]->id() != id) continue;
if (FLAG_trace_turbo_escape) {
PrintF(" Merging fields of #%d\n", id);
PrintF(" Merging virtual objects of #%d\n", id);
}
VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone);
size_t fields = std::max(ls->field_count(), rs->field_count());
mergeObject->SetReplacement(GetReplacementIfSame(cache->objects()));
size_t fields = min_field_count(cache->objects());
changed = mergeObject->ResizeFields(fields) || changed;
for (size_t i = 0; i < fields; ++i) {
if (ls->GetField(i) == rs->GetField(i)) {
changed = mergeObject->SetField(i, ls->GetField(i)) || changed;
if (FLAG_trace_turbo_escape && ls->GetField(i)) {
PrintF(" Field %zu agree on rep #%d\n", i,
ls->GetField(i)->id());
}
} else if (ls->GetField(i) != nullptr && rs->GetField(i) != nullptr) {
Node* rep = mergeObject->GetField(i);
if (!rep || rep->opcode() != IrOpcode::kPhi ||
NodeProperties::GetValueInput(rep, 0) != ls->GetField(i) ||
NodeProperties::GetValueInput(rep, 1) != rs->GetField(i)) {
Node* phi =
graph->NewNode(common->Phi(MachineRepresentation::kTagged, 2),
ls->GetField(i), rs->GetField(i), control);
if (mergeObject->SetField(i, phi)) {
if (FLAG_trace_turbo_escape) {
PrintF(" Creating Phi #%d as merge of #%d and #%d\n",
phi->id(), ls->GetField(i)->id(), rs->GetField(i)->id());
}
changed = true;
}
} else {
if (FLAG_trace_turbo_escape) {
PrintF(" Retaining Phi #%d as merge of #%d and #%d\n",
rep->id(), ls->GetField(i)->id(), rs->GetField(i)->id());
}
if (Node* field = GetFieldIfSame(i, cache->objects())) {
changed = mergeObject->SetField(i, field) || changed;
if (FLAG_trace_turbo_escape) {
PrintF(" Field %zu agree on rep #%d\n", i, field->id());
}
} else {
changed = mergeObject->SetField(i, nullptr) || changed;
GetFields(cache->objects(), cache->fields(), i);
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, 2),
static_cast<int>(cache->fields().size()),
&cache->fields().front());
if (mergeObject->SetField(i, phi, true)) {
if (FLAG_trace_turbo_escape) {
PrintF(" Creating Phi #%d as merge of", phi->id());
for (size_t i = 0; i + 1 < cache->fields().size(); i++) {
PrintF(" #%d (%s)", cache->fields()[i]->id(),
cache->fields()[i]->op()->mnemonic());
}
PrintF("\n");
}
changed = true;
}
} else {
DCHECK(rep->opcode() == IrOpcode::kPhi);
for (size_t n = 0; n < cache->fields().size(); ++n) {
Node* old =
NodeProperties::GetValueInput(rep, static_cast<int>(n));
if (old != cache->fields()[n]) {
changed = true;
NodeProperties::ReplaceValueInput(rep, cache->fields()[n],
static_cast<int>(n));
}
}
}
} else {
changed = mergeObject->SetField(i, nullptr) || changed;
}
}
}
} else if (ls) {
VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone);
changed = mergeObject->UpdateFrom(*ls) || changed;
} else if (rs) {
VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone);
changed = mergeObject->UpdateFrom(*rs) || changed;
} else {
SetVirtualObject(id, nullptr);
}
}
// Update linked objects.
for (NodeId id = 0; id < min_size(cache->states()); ++id) {
GetVirtualObjects(cache->states(), cache->objects(), id);
if (cache->objects().size() == cache->states().size()) {
if (cache->objects()[0]->id() != id) {
SetVirtualObject(id, GetVirtualObject(cache->objects()[0]->id()));
}
}
}
return changed;
@ -329,13 +481,21 @@ Node* VirtualState::ResolveReplacement(Node* node) {
bool VirtualState::UpdateReplacement(Node* node, Node* rep, Zone* zone) {
if (!GetVirtualObject(node)) {
SetVirtualObject(node->id(), new (zone) VirtualObject(node->id(), zone));
if (rep) {
SetVirtualObject(node->id(), new (zone) VirtualObject(node->id(), zone));
} else {
return false;
}
}
if (GetVirtualObject(node)->SetReplacement(rep)) {
LastChangedAt(node);
if (FLAG_trace_turbo_escape) {
PrintF("Representation of #%d is #%d (%s)\n", node->id(), rep->id(),
rep->op()->mnemonic());
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;
}
@ -352,7 +512,9 @@ EscapeStatusAnalysis::EscapeStatusAnalysis(EscapeAnalysis* object_analysis,
graph_(graph),
zone_(zone),
info_(zone),
queue_(zone) {}
queue_(zone) {
info_.resize(graph->NodeCount());
}
EscapeStatusAnalysis::~EscapeStatusAnalysis() {}
@ -376,6 +538,12 @@ bool EscapeStatusAnalysis::IsEscaped(Node* node) {
}
bool EscapeStatusAnalysis::IsAllocation(Node* node) {
return node->opcode() == IrOpcode::kAllocate ||
node->opcode() == IrOpcode::kFinishRegion;
}
bool EscapeStatusAnalysis::SetEscaped(Node* node) {
bool changed = info_[node->id()] != kEscaped;
info_[node->id()] = kEscaped;
@ -436,12 +604,9 @@ void EscapeStatusAnalysis::Process(Node* node) {
case IrOpcode::kLoadField:
case IrOpcode::kLoadElement: {
if (Node* rep = object_analysis_->GetReplacement(node, node->id())) {
if (rep->opcode() == IrOpcode::kAllocate ||
rep->opcode() == IrOpcode::kFinishRegion) {
if (CheckUsesForEscape(node, rep)) {
RevisitInputs(rep);
RevisitUses(rep);
}
if (IsAllocation(rep) && CheckUsesForEscape(node, rep)) {
RevisitInputs(rep);
RevisitUses(rep);
}
}
break;
@ -461,8 +626,9 @@ 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) && SetEscaped(val)) {
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());
@ -475,8 +641,9 @@ 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) && SetEscaped(val)) {
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());
@ -549,7 +716,21 @@ bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep,
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",
@ -558,15 +739,6 @@ bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep,
}
return true;
}
if (use->op()->EffectInputCount() == 0 &&
uses->op()->EffectInputCount() > 0 &&
uses->opcode() != IrOpcode::kLoadField) {
if (FLAG_trace_turbo_escape) {
PrintF("Encountered unaccounted use by #%d (%s)\n", use->id(),
use->op()->mnemonic());
}
UNREACHABLE();
}
}
}
return false;
@ -604,7 +776,8 @@ EscapeAnalysis::EscapeAnalysis(Graph* graph, CommonOperatorBuilder* common,
common_(common),
zone_(zone),
virtual_states_(zone),
escape_status_(this, graph, zone) {}
escape_status_(this, graph, zone),
cache_(zone) {}
EscapeAnalysis::~EscapeAnalysis() {}
@ -658,6 +831,13 @@ void EscapeAnalysis::RunObjectAnalysis() {
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;
@ -740,9 +920,7 @@ void EscapeAnalysis::ProcessAllocationUsers(Node* node) {
bool EscapeAnalysis::IsEffectBranchPoint(Node* node) {
int count = 0;
for (Edge edge : node->use_edges()) {
Node* use = edge.from();
if (NodeProperties::IsEffectEdge(edge) &&
use->opcode() != IrOpcode::kLoadField) {
if (NodeProperties::IsEffectEdge(edge)) {
if (++count > 1) {
return true;
}
@ -771,6 +949,11 @@ void EscapeAnalysis::ForwardVirtualState(Node* node) {
}
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()]);
@ -778,11 +961,6 @@ void EscapeAnalysis::ForwardVirtualState(Node* node) {
virtual_states_[node->id()]->UpdateFrom(virtual_states_[effect->id()],
zone());
}
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());
}
} else {
virtual_states_[node->id()] = virtual_states_[effect->id()];
if (FLAG_trace_turbo_escape) {
@ -803,10 +981,6 @@ void EscapeAnalysis::ProcessStart(Node* node) {
bool EscapeAnalysis::ProcessEffectPhi(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
// For now only support binary phis.
CHECK_EQ(node->op()->EffectInputCount(), 2);
Node* left = NodeProperties::GetEffectInput(node, 0);
Node* right = NodeProperties::GetEffectInput(node, 1);
bool changed = false;
VirtualState* mergeState = virtual_states_[node->id()];
@ -822,33 +996,40 @@ bool EscapeAnalysis::ProcessEffectPhi(Node* node) {
changed = true;
}
VirtualState* l = virtual_states_[left->id()];
VirtualState* r = virtual_states_[right->id()];
cache_.Clear();
if (l == nullptr && r == nullptr) {
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;
}
if (FLAG_trace_turbo_escape) {
PrintF(
"At Effect Phi #%d, merging states %p (from #%d) and %p (from #%d) "
"into %p\n",
node->id(), static_cast<void*>(l), left->id(), static_cast<void*>(r),
right->id(), static_cast<void*>(mergeState));
}
changed = mergeState->MergeFrom(&cache_, zone(), graph(), common(),
NodeProperties::GetControlInput(node)) ||
changed;
if (r && l == nullptr) {
changed = mergeState->UpdateFrom(r, zone()) || changed;
} else if (l && r == nullptr) {
changed = mergeState->UpdateFrom(l, zone()) || changed;
} else {
changed = mergeState->MergeFrom(l, r, 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);
}
@ -915,6 +1096,11 @@ bool EscapeAnalysis::IsEscaped(Node* 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->GetVirtualObject(id);
@ -931,45 +1117,43 @@ int EscapeAnalysis::OffsetFromAccess(Node* node) {
void EscapeAnalysis::ProcessLoadFromPhi(int offset, Node* from, Node* node,
VirtualState* state) {
// Only binary phis are supported for now.
CHECK_EQ(from->op()->ValueInputCount(), 2);
if (FLAG_trace_turbo_escape) {
PrintF("Load #%d from phi #%d", node->id(), from->id());
}
Node* left = NodeProperties::GetValueInput(from, 0);
Node* right = NodeProperties::GetValueInput(from, 1);
VirtualObject* l = state->GetVirtualObject(left);
VirtualObject* r = state->GetVirtualObject(right);
if (l && r) {
Node* lv = l->GetField(offset);
Node* rv = r->GetField(offset);
if (lv && rv) {
ZoneVector<Node*> inputs(zone());
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
Node* input = NodeProperties::GetValueInput(node, i);
inputs.push_back(input);
}
GetVirtualObjects(state, inputs, cache_.objects());
if (cache_.objects().size() == inputs.size()) {
GetFields(cache_.objects(), cache_.fields(), offset);
if (cache_.fields().size() == cache_.objects().size()) {
if (!state->GetVirtualObject(node)) {
state->SetVirtualObject(node->id(),
new (zone()) VirtualObject(node->id(), zone()));
}
Node* rep = state->GetVirtualObject(node)->GetReplacement();
if (!rep || rep->opcode() != IrOpcode::kPhi ||
NodeProperties::GetValueInput(rep, 0) != lv ||
NodeProperties::GetValueInput(rep, 1) != rv) {
Node* phi =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
lv, rv, NodeProperties::GetControlInput(from));
if (!rep || !IsEquivalentPhi(rep, cache_.fields())) {
cache_.fields().push_back(NodeProperties::GetControlInput(from));
Node* phi = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2),
static_cast<int>(cache_.fields().size()), &cache_.fields().front());
state->GetVirtualObject(node)->SetReplacement(phi);
state->LastChangedAt(node);
if (FLAG_trace_turbo_escape) {
PrintF(" got phi of #%d is #%d created.\n", lv->id(), rv->id());
PrintF(" got phi created.\n");
}
} else if (FLAG_trace_turbo_escape) {
PrintF(" has already the right phi representation.\n");
PrintF(" has already phi #%d.\n", rep->id());
}
} else if (FLAG_trace_turbo_escape) {
PrintF(" has incomplete field info: %p %p\n", static_cast<void*>(lv),
static_cast<void*>(rv));
PrintF(" has incomplete field info.\n");
}
} else if (FLAG_trace_turbo_escape) {
PrintF(" has incomplete virtual object info: %p %p\n",
static_cast<void*>(l), static_cast<void*>(r));
PrintF(" has incomplete virtual object info.\n");
}
}
@ -985,11 +1169,9 @@ void EscapeAnalysis::ProcessLoadField(Node* node) {
Node* value = object->GetField(offset);
if (value) {
value = state->ResolveReplacement(value);
// Record that the load has this alias.
state->UpdateReplacement(node, value, zone());
} else if (FLAG_trace_turbo_escape) {
PrintF("No field %d on record for #%d\n", offset, from->id());
}
// Record that the load has this alias.
state->UpdateReplacement(node, value, zone());
} else {
if (from->opcode() == IrOpcode::kPhi) {
int offset = OffsetFromAccess(node);
@ -1005,31 +1187,38 @@ void EscapeAnalysis::ProcessLoadElement(Node* node) {
ForwardVirtualState(node);
Node* from = NodeProperties::GetValueInput(node, 0);
VirtualState* state = virtual_states_[node->id()];
if (VirtualObject* object = state->ResolveVirtualObject(from)) {
NumberMatcher index(node->InputAt(1));
ElementAccess access = OpParameter<ElementAccess>(node);
if (index.HasValue()) {
CHECK_EQ(ElementSizeLog2Of(access.machine_type.representation()),
Node* index_node = node->InputAt(1);
NumberMatcher index(index_node);
ElementAccess access = OpParameter<ElementAccess>(node);
if (index.HasValue()) {
int offset = index.Value() + access.header_size / kPointerSize;
if (VirtualObject* object = state->ResolveVirtualObject(from)) {
CHECK_GE(ElementSizeLog2Of(access.machine_type.representation()),
kPointerSizeLog2);
CHECK_EQ(access.header_size % kPointerSize, 0);
int offset = index.Value() + access.header_size / kPointerSize;
if (!object->IsTracked()) return;
Node* value = object->GetField(offset);
if (value) {
value = state->ResolveReplacement(value);
// Record that the load has this alias.
state->UpdateReplacement(node, value, zone());
} else if (FLAG_trace_turbo_escape) {
PrintF("No field %d on record for #%d\n", offset, from->id());
}
}
} else {
if (from->opcode() == IrOpcode::kPhi) {
NumberMatcher index(node->InputAt(1));
// Record that the load has this alias.
state->UpdateReplacement(node, value, zone());
} else if (from->opcode() == IrOpcode::kPhi) {
ElementAccess access = OpParameter<ElementAccess>(node);
int offset = index.Value() + access.header_size / kPointerSize;
if (index.HasValue()) {
ProcessLoadFromPhi(offset, from, node, state);
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());
}
}
}
@ -1056,21 +1245,34 @@ void EscapeAnalysis::ProcessStoreElement(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kStoreElement);
ForwardVirtualState(node);
Node* to = NodeProperties::GetValueInput(node, 0);
NumberMatcher index(node->InputAt(1));
Node* index_node = node->InputAt(1);
NumberMatcher index(index_node);
ElementAccess access = OpParameter<ElementAccess>(node);
Node* val = NodeProperties::GetValueInput(node, 2);
if (index.HasValue()) {
CHECK_EQ(ElementSizeLog2Of(access.machine_type.representation()),
kPointerSizeLog2);
CHECK_EQ(access.header_size % kPointerSize, 0);
int offset = index.Value() + access.header_size / kPointerSize;
VirtualState* states = virtual_states_[node->id()];
if (VirtualObject* obj = states->ResolveVirtualObject(to)) {
if (!obj->IsTracked()) return;
CHECK_GE(ElementSizeLog2Of(access.machine_type.representation()),
kPointerSizeLog2);
CHECK_EQ(access.header_size % kPointerSize, 0);
if (obj->SetField(offset, states->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());
}
}
}
}

View File

@ -35,6 +35,7 @@ class EscapeStatusAnalysis {
bool IsVirtual(Node* node);
bool IsEscaped(Node* node);
bool IsAllocation(Node* node);
void DebugPrint();
@ -73,6 +74,30 @@ class EscapeStatusAnalysis {
DEFINE_OPERATORS_FOR_FLAGS(EscapeStatusAnalysis::EscapeStatusFlags)
class MergeCache {
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();
}
private:
ZoneVector<VirtualState*> states_;
ZoneVector<VirtualObject*> objects_;
ZoneVector<Node*> fields_;
};
// EscapeObjectAnalysis simulates stores to determine values of loads if
// an object is virtual and eliminated.
class EscapeAnalysis {
@ -110,6 +135,8 @@ class EscapeAnalysis {
VirtualObject* GetVirtualObject(Node* at, NodeId id);
bool SetEscaped(Node* node);
void DebugPrint();
void DebugPrintState(VirtualState* state);
void DebugPrintObject(VirtualObject* state, NodeId id);
@ -123,6 +150,7 @@ class EscapeAnalysis {
Zone* const zone_;
ZoneVector<VirtualState*> virtual_states_;
EscapeStatusAnalysis escape_status_;
MergeCache cache_;
DISALLOW_COPY_AND_ASSIGN(EscapeAnalysis);
};

View File

@ -1159,6 +1159,9 @@ Handle<Code> Pipeline::GenerateCode() {
}
if (FLAG_turbo_escape) {
// TODO(sigurds): EscapeAnalysis needs a trimmed graph at the moment,
// because it does a forwards traversal of the effect edges.
Run<EarlyGraphTrimmingPhase>();
Run<EscapeAnalysisPhase>();
RunPrintAndVerify("Escape Analysed");
}

View File

@ -0,0 +1,37 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --allow-natives-syntax --turbo-escape
(function() {
"use strict";
function f() {
for (let i = 0; i < 5; ++i) {
function g() { return i }
}
}
f();
})();

View File

@ -164,6 +164,7 @@
'compiler/escape-analysis-6': [PASS, NO_VARIANTS],
'compiler/escape-analysis-7': [PASS, NO_VARIANTS],
'compiler/escape-analysis-9': [PASS, NO_VARIANTS],
'compiler/escape-analysis-10': [PASS, NO_VARIANTS],
# TODO(sigurds): Tests that fail because of incomplete use handling (i.e. select).
'compiler/escape-analysis-8': [PASS, NO_VARIANTS],