[turbofan] Improve escape analysis
This patch improves escape analysis and fixes bugs triggered by clusterfuzz. Impovements include: * Handling of LoadElement/StoreElement if index is a constant * Handling of JSStoreProperty: invalidate all information, as the store could have altered any field. * Treat phis that use an allocation as escaping * Improve resolution of replacements R=mstarzinger@chromium.org BUG=v8:4586 LOG=n Review URL: https://codereview.chromium.org/1499143002 Cr-Commit-Position: refs/heads/master@{#32656}
This commit is contained in:
parent
063920e2e4
commit
5b5821142b
@ -23,9 +23,11 @@ EscapeAnalysisReducer::EscapeAnalysisReducer(Editor* editor, JSGraph* jsgraph,
|
||||
Reduction EscapeAnalysisReducer::Reduce(Node* node) {
|
||||
switch (node->opcode()) {
|
||||
case IrOpcode::kLoadField:
|
||||
return ReduceLoadField(node);
|
||||
case IrOpcode::kLoadElement:
|
||||
return ReduceLoad(node);
|
||||
case IrOpcode::kStoreField:
|
||||
return ReduceStoreField(node);
|
||||
case IrOpcode::kStoreElement:
|
||||
return ReduceStore(node);
|
||||
case IrOpcode::kAllocate:
|
||||
return ReduceAllocate(node);
|
||||
case IrOpcode::kFinishRegion:
|
||||
@ -42,11 +44,13 @@ Reduction EscapeAnalysisReducer::Reduce(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
Reduction EscapeAnalysisReducer::ReduceLoadField(Node* node) {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kLoadField);
|
||||
Reduction EscapeAnalysisReducer::ReduceLoad(Node* node) {
|
||||
DCHECK(node->opcode() == IrOpcode::kLoadField ||
|
||||
node->opcode() == IrOpcode::kLoadElement);
|
||||
if (Node* rep = escape_analysis()->GetReplacement(node, node->id())) {
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF("Replaced #%d with #%d\n", node->id(), rep->id());
|
||||
PrintF("Replaced #%d (%s) with #%d (%s)\n", node->id(),
|
||||
node->op()->mnemonic(), rep->id(), rep->op()->mnemonic());
|
||||
}
|
||||
ReplaceWithValue(node, rep);
|
||||
return Changed(rep);
|
||||
@ -55,11 +59,13 @@ Reduction EscapeAnalysisReducer::ReduceLoadField(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
Reduction EscapeAnalysisReducer::ReduceStoreField(Node* node) {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kStoreField);
|
||||
Reduction EscapeAnalysisReducer::ReduceStore(Node* node) {
|
||||
DCHECK(node->opcode() == IrOpcode::kStoreField ||
|
||||
node->opcode() == IrOpcode::kStoreElement);
|
||||
if (escape_analysis()->IsVirtual(NodeProperties::GetValueInput(node, 0))) {
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF("Removed store field #%d from effect chain\n", node->id());
|
||||
PrintF("Removed #%d (%s) from effect chain\n", node->id(),
|
||||
node->op()->mnemonic());
|
||||
}
|
||||
RelaxEffectsAndControls(node);
|
||||
return Changed(node);
|
||||
@ -90,7 +96,7 @@ Reduction EscapeAnalysisReducer::ReduceFinishRegion(Node* node) {
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF("Removed region #%d / #%d from effect chain,", effect->id(),
|
||||
node->id());
|
||||
PrintF("%d user(s) of #%d remain(s):", node->UseCount(), node->id());
|
||||
PrintF(" %d user(s) of #%d remain(s):", node->UseCount(), node->id());
|
||||
for (Edge edge : node->use_edges()) {
|
||||
PrintF(" #%d", edge.from()->id());
|
||||
}
|
||||
|
@ -24,8 +24,8 @@ class EscapeAnalysisReducer final : public AdvancedReducer {
|
||||
Reduction Reduce(Node* node) final;
|
||||
|
||||
private:
|
||||
Reduction ReduceLoadField(Node* node);
|
||||
Reduction ReduceStoreField(Node* node);
|
||||
Reduction ReduceLoad(Node* node);
|
||||
Reduction ReduceStore(Node* node);
|
||||
Reduction ReduceAllocate(Node* node);
|
||||
Reduction ReduceFinishRegion(Node* node);
|
||||
Reduction ReduceReferenceEqual(Node* node);
|
||||
|
@ -63,15 +63,25 @@ class VirtualObject : public ZoneObject {
|
||||
return changed;
|
||||
}
|
||||
|
||||
size_t fields() { return fields_.size(); }
|
||||
bool ResizeFields(size_t field_number) {
|
||||
if (field_number != fields_.size()) {
|
||||
fields_.resize(field_number);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
bool UpdateFrom(const VirtualObject& other);
|
||||
|
||||
NodeId id() { return id_; }
|
||||
@ -112,6 +122,7 @@ class VirtualState : public ZoneObject {
|
||||
VirtualState(Zone* zone, size_t size);
|
||||
VirtualState(const VirtualState& states);
|
||||
|
||||
VirtualObject* ResolveVirtualObject(Node* node);
|
||||
VirtualObject* GetVirtualObject(Node* node);
|
||||
VirtualObject* GetVirtualObject(size_t id);
|
||||
VirtualObject* GetOrCreateTrackedVirtualObject(NodeId id, Zone* zone);
|
||||
@ -157,7 +168,10 @@ VirtualState::VirtualState(const VirtualState& state)
|
||||
}
|
||||
|
||||
|
||||
VirtualObject* VirtualState::GetVirtualObject(size_t id) { return info_[id]; }
|
||||
VirtualObject* VirtualState::GetVirtualObject(size_t id) {
|
||||
if (id >= info_.size()) return nullptr;
|
||||
return info_[id];
|
||||
}
|
||||
|
||||
|
||||
VirtualObject* VirtualState::GetVirtualObject(Node* node) {
|
||||
@ -165,6 +179,16 @@ VirtualObject* VirtualState::GetVirtualObject(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
VirtualObject* VirtualState::ResolveVirtualObject(Node* node) {
|
||||
VirtualObject* obj = GetVirtualObject(node->id());
|
||||
while (obj && !obj->IsTracked() && obj->GetReplacement() &&
|
||||
GetVirtualObject(obj->GetReplacement())) {
|
||||
obj = GetVirtualObject(obj->GetReplacement());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
||||
VirtualObject* VirtualState::GetOrCreateTrackedVirtualObject(NodeId id,
|
||||
Zone* zone) {
|
||||
if (VirtualObject* obj = GetVirtualObject(id)) {
|
||||
@ -246,7 +270,7 @@ bool VirtualState::MergeFrom(VirtualState* left, VirtualState* right,
|
||||
PrintF(" Merging fields of #%d\n", id);
|
||||
}
|
||||
VirtualObject* mergeObject = GetOrCreateTrackedVirtualObject(id, zone);
|
||||
size_t fields = std::max(ls->fields(), rs->fields());
|
||||
size_t fields = std::max(ls->field_count(), rs->field_count());
|
||||
changed = mergeObject->ResizeFields(fields) || changed;
|
||||
for (size_t i = 0; i < fields; ++i) {
|
||||
if (ls->GetField(i) == rs->GetField(i)) {
|
||||
@ -256,19 +280,36 @@ bool VirtualState::MergeFrom(VirtualState* left, VirtualState* right,
|
||||
ls->GetField(i)->id());
|
||||
}
|
||||
} else if (ls->GetField(i) != nullptr && rs->GetField(i) != nullptr) {
|
||||
Node* phi = graph->NewNode(common->Phi(kMachAnyTagged, 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());
|
||||
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(kMachAnyTagged, 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());
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
@ -276,12 +317,13 @@ bool VirtualState::MergeFrom(VirtualState* left, VirtualState* right,
|
||||
|
||||
|
||||
Node* VirtualState::ResolveReplacement(Node* node) {
|
||||
if (VirtualObject* obj = GetVirtualObject(node)) {
|
||||
if (Node* rep = obj->GetReplacement()) {
|
||||
return rep;
|
||||
}
|
||||
Node* replacement = node;
|
||||
VirtualObject* obj = GetVirtualObject(node);
|
||||
while (obj != nullptr && obj->GetReplacement()) {
|
||||
replacement = obj->GetReplacement();
|
||||
obj = GetVirtualObject(replacement);
|
||||
}
|
||||
return node;
|
||||
return replacement;
|
||||
}
|
||||
|
||||
|
||||
@ -388,7 +430,11 @@ void EscapeStatusAnalysis::Process(Node* node) {
|
||||
case IrOpcode::kStoreField:
|
||||
ProcessStoreField(node);
|
||||
break;
|
||||
case IrOpcode::kLoadField: {
|
||||
case IrOpcode::kStoreElement:
|
||||
ProcessStoreElement(node);
|
||||
break;
|
||||
case IrOpcode::kLoadField:
|
||||
case IrOpcode::kLoadElement: {
|
||||
if (Node* rep = object_analysis_->GetReplacement(node, node->id())) {
|
||||
if (rep->opcode() == IrOpcode::kAllocate ||
|
||||
rep->opcode() == IrOpcode::kFinishRegion) {
|
||||
@ -425,6 +471,20 @@ void EscapeStatusAnalysis::ProcessStoreField(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
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)) {
|
||||
RevisitUses(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)) {
|
||||
@ -444,19 +504,24 @@ void EscapeStatusAnalysis::ProcessAllocate(Node* node) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (CheckUsesForEscape(node)) {
|
||||
if (CheckUsesForEscape(node, true)) {
|
||||
RevisitUses(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep) {
|
||||
bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep,
|
||||
bool phi_escaping) {
|
||||
for (Edge edge : uses->use_edges()) {
|
||||
Node* use = edge.from();
|
||||
if (!NodeProperties::IsValueEdge(edge)) continue;
|
||||
if (!NodeProperties::IsValueEdge(edge) &&
|
||||
!NodeProperties::IsContextEdge(edge))
|
||||
continue;
|
||||
switch (use->opcode()) {
|
||||
case IrOpcode::kStoreField:
|
||||
case IrOpcode::kLoadField:
|
||||
case IrOpcode::kStoreElement:
|
||||
case IrOpcode::kLoadElement:
|
||||
case IrOpcode::kFrameState:
|
||||
case IrOpcode::kStateValues:
|
||||
case IrOpcode::kReferenceEqual:
|
||||
@ -472,6 +537,17 @@ bool EscapeStatusAnalysis::CheckUsesForEscape(Node* uses, Node* rep) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (phi_escaping && use->opcode() == IrOpcode::kPhi &&
|
||||
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;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (SetEscaped(rep)) {
|
||||
@ -503,7 +579,7 @@ void EscapeStatusAnalysis::ProcessFinishRegion(Node* node) {
|
||||
info_[node->id()] = kVirtual;
|
||||
RevisitUses(node);
|
||||
}
|
||||
if (CheckUsesForEscape(node)) {
|
||||
if (CheckUsesForEscape(node, true)) {
|
||||
RevisitInputs(node);
|
||||
}
|
||||
}
|
||||
@ -542,18 +618,19 @@ void EscapeAnalysis::Run() {
|
||||
|
||||
void EscapeAnalysis::RunObjectAnalysis() {
|
||||
virtual_states_.resize(graph()->NodeCount());
|
||||
ZoneVector<Node*> queue(zone());
|
||||
queue.push_back(graph()->start());
|
||||
while (!queue.empty()) {
|
||||
Node* node = queue.back();
|
||||
queue.pop_back();
|
||||
ZoneVector<Node*> stack(zone());
|
||||
stack.push_back(graph()->start());
|
||||
while (!stack.empty()) {
|
||||
Node* node = stack.back();
|
||||
stack.pop_back();
|
||||
if (Process(node)) {
|
||||
for (Edge edge : node->use_edges()) {
|
||||
if (NodeProperties::IsEffectEdge(edge)) {
|
||||
Node* use = edge.from();
|
||||
if (use->opcode() != IrOpcode::kLoadField ||
|
||||
if ((use->opcode() != IrOpcode::kLoadField &&
|
||||
use->opcode() != IrOpcode::kLoadElement) ||
|
||||
!IsDanglingEffectNode(use)) {
|
||||
queue.push_back(use);
|
||||
stack.push_back(use);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -561,9 +638,12 @@ void EscapeAnalysis::RunObjectAnalysis() {
|
||||
for (Edge edge : node->use_edges()) {
|
||||
if (NodeProperties::IsEffectEdge(edge)) {
|
||||
Node* use = edge.from();
|
||||
if (use->opcode() == IrOpcode::kLoadField &&
|
||||
if ((use->opcode() == IrOpcode::kLoadField ||
|
||||
use->opcode() == IrOpcode::kLoadElement) &&
|
||||
|
||||
|
||||
IsDanglingEffectNode(use)) {
|
||||
queue.push_back(use);
|
||||
stack.push_back(use);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -604,6 +684,12 @@ bool EscapeAnalysis::Process(Node* node) {
|
||||
case IrOpcode::kLoadField:
|
||||
ProcessLoadField(node);
|
||||
break;
|
||||
case IrOpcode::kStoreElement:
|
||||
ProcessStoreElement(node);
|
||||
break;
|
||||
case IrOpcode::kLoadElement:
|
||||
ProcessLoadElement(node);
|
||||
break;
|
||||
case IrOpcode::kStart:
|
||||
ProcessStart(node);
|
||||
break;
|
||||
@ -614,12 +700,43 @@ bool EscapeAnalysis::Process(Node* node) {
|
||||
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* states = virtual_states_[node->id()];
|
||||
if (VirtualObject* obj = states->ResolveVirtualObject(input)) {
|
||||
if (obj->ClearAllFields()) {
|
||||
states->LastChangedAt(node);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool EscapeAnalysis::IsEffectBranchPoint(Node* node) {
|
||||
int count = 0;
|
||||
for (Edge edge : node->use_edges()) {
|
||||
@ -637,10 +754,12 @@ bool EscapeAnalysis::IsEffectBranchPoint(Node* node) {
|
||||
|
||||
void EscapeAnalysis::ForwardVirtualState(Node* node) {
|
||||
DCHECK_EQ(node->op()->EffectInputCount(), 1);
|
||||
if (node->opcode() != IrOpcode::kLoadField && IsDanglingEffectNode(node)) {
|
||||
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());
|
||||
DCHECK(false);
|
||||
UNREACHABLE();
|
||||
}
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
// Break the cycle for effect phis.
|
||||
@ -652,9 +771,13 @@ void EscapeAnalysis::ForwardVirtualState(Node* node) {
|
||||
}
|
||||
DCHECK_NOT_NULL(virtual_states_[effect->id()]);
|
||||
if (IsEffectBranchPoint(effect)) {
|
||||
if (virtual_states_[node->id()]) return;
|
||||
virtual_states_[node->id()] =
|
||||
new (zone()) VirtualState(*virtual_states_[effect->id()]);
|
||||
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());
|
||||
}
|
||||
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(),
|
||||
@ -681,7 +804,7 @@ void EscapeAnalysis::ProcessStart(Node* node) {
|
||||
bool EscapeAnalysis::ProcessEffectPhi(Node* node) {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
|
||||
// For now only support binary phis.
|
||||
DCHECK_EQ(node->op()->EffectInputCount(), 2);
|
||||
CHECK_EQ(node->op()->EffectInputCount(), 2);
|
||||
Node* left = NodeProperties::GetEffectInput(node, 0);
|
||||
Node* right = NodeProperties::GetEffectInput(node, 1);
|
||||
bool changed = false;
|
||||
@ -692,7 +815,7 @@ bool EscapeAnalysis::ProcessEffectPhi(Node* node) {
|
||||
virtual_states_[node->id()] = mergeState;
|
||||
changed = true;
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF("Phi #%d got new states map %p.\n", node->id(),
|
||||
PrintF("Effect Phi #%d got new states map %p.\n", node->id(),
|
||||
static_cast<void*>(mergeState));
|
||||
}
|
||||
} else if (mergeState->GetLastChanged() != node) {
|
||||
@ -707,9 +830,11 @@ bool EscapeAnalysis::ProcessEffectPhi(Node* node) {
|
||||
}
|
||||
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF("At Phi #%d, merging states %p (from #%d) and %p (from #%d)\n",
|
||||
node->id(), static_cast<void*>(l), left->id(), static_cast<void*>(r),
|
||||
right->id());
|
||||
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));
|
||||
}
|
||||
|
||||
if (r && l == nullptr) {
|
||||
@ -790,68 +915,119 @@ bool EscapeAnalysis::IsEscaped(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
VirtualObject* EscapeAnalysis::GetVirtualObject(Node* at, NodeId id) {
|
||||
if (VirtualState* states = virtual_states_[at->id()]) {
|
||||
return states->GetVirtualObject(id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
// 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) {
|
||||
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(kMachAnyTagged, 2), lv, rv,
|
||||
NodeProperties::GetControlInput(from));
|
||||
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());
|
||||
}
|
||||
} else if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" has already the right phi representation.\n");
|
||||
}
|
||||
} else if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" has incomplete field info: %p %p\n", static_cast<void*>(lv),
|
||||
static_cast<void*>(rv));
|
||||
}
|
||||
} else if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" has incomplete virtual object info: %p %p\n",
|
||||
static_cast<void*>(l), static_cast<void*>(r));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EscapeAnalysis::ProcessLoadField(Node* node) {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kLoadField);
|
||||
ForwardVirtualState(node);
|
||||
Node* from = NodeProperties::GetValueInput(node, 0);
|
||||
int offset = OffsetFromAccess(node);
|
||||
VirtualState* states = virtual_states_[node->id()];
|
||||
if (VirtualObject* state = states->GetVirtualObject(from)) {
|
||||
if (!state->IsTracked()) return;
|
||||
Node* value = state->GetField(offset);
|
||||
VirtualState* state = virtual_states_[node->id()];
|
||||
if (VirtualObject* object = state->ResolveVirtualObject(from)) {
|
||||
int offset = OffsetFromAccess(node);
|
||||
if (!object->IsTracked()) return;
|
||||
Node* value = object->GetField(offset);
|
||||
if (value) {
|
||||
value = state->ResolveReplacement(value);
|
||||
// Record that the load has this alias.
|
||||
states->UpdateReplacement(node, value, zone());
|
||||
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) {
|
||||
int offset = OffsetFromAccess(node);
|
||||
// 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 = states->GetVirtualObject(left);
|
||||
VirtualObject* r = states->GetVirtualObject(right);
|
||||
if (l && r) {
|
||||
Node* lv = l->GetField(offset);
|
||||
Node* rv = r->GetField(offset);
|
||||
if (lv && rv) {
|
||||
if (!states->GetVirtualObject(node)) {
|
||||
states->SetVirtualObject(
|
||||
node->id(), new (zone()) VirtualObject(node->id(), zone()));
|
||||
}
|
||||
Node* rep = states->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(kMachAnyTagged, 2), lv, rv,
|
||||
NodeProperties::GetControlInput(from));
|
||||
states->GetVirtualObject(node)->SetReplacement(phi);
|
||||
states->LastChangedAt(node);
|
||||
if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" got phi of #%d is #%d created.\n", lv->id(), rv->id());
|
||||
}
|
||||
} else if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" has already the right phi representation.\n");
|
||||
}
|
||||
} else if (FLAG_trace_turbo_escape) {
|
||||
PrintF(" has incomplete field info: %p %p\n", static_cast<void*>(lv),
|
||||
static_cast<void*>(rv));
|
||||
}
|
||||
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()];
|
||||
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), 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(" has incomplete virtual object info: %p %p\n",
|
||||
static_cast<void*>(l), static_cast<void*>(r));
|
||||
PrintF("No field %d on record for #%d\n", offset, from->id());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (from->opcode() == IrOpcode::kPhi) {
|
||||
NumberMatcher index(node->InputAt(1));
|
||||
ElementAccess access = OpParameter<ElementAccess>(node);
|
||||
int offset = index.Value() + access.header_size / kPointerSize;
|
||||
if (index.HasValue()) {
|
||||
ProcessLoadFromPhi(offset, from, node, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -865,7 +1041,7 @@ void EscapeAnalysis::ProcessStoreField(Node* node) {
|
||||
Node* val = NodeProperties::GetValueInput(node, 1);
|
||||
int offset = OffsetFromAccess(node);
|
||||
VirtualState* states = virtual_states_[node->id()];
|
||||
if (VirtualObject* obj = states->GetVirtualObject(to)) {
|
||||
if (VirtualObject* obj = states->ResolveVirtualObject(to)) {
|
||||
if (!obj->IsTracked()) return;
|
||||
if (obj->SetField(offset, states->ResolveReplacement(val))) {
|
||||
states->LastChangedAt(node);
|
||||
@ -874,6 +1050,56 @@ void EscapeAnalysis::ProcessStoreField(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
void EscapeAnalysis::ProcessStoreElement(Node* node) {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kStoreElement);
|
||||
ForwardVirtualState(node);
|
||||
Node* to = NodeProperties::GetValueInput(node, 0);
|
||||
NumberMatcher index(node->InputAt(1));
|
||||
ElementAccess access = OpParameter<ElementAccess>(node);
|
||||
Node* val = NodeProperties::GetValueInput(node, 2);
|
||||
if (index.HasValue()) {
|
||||
CHECK_EQ(ElementSizeLog2Of(access.machine_type), 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;
|
||||
if (obj->SetField(offset, states->ResolveReplacement(val))) {
|
||||
states->LastChangedAt(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EscapeAnalysis::DebugPrintObject(VirtualObject* object, NodeId id) {
|
||||
PrintF(" Object #%d with %zu fields", id, object->field_count());
|
||||
if (Node* rep = object->GetReplacement()) {
|
||||
PrintF(", rep = #%d (%s)", rep->id(), rep->op()->mnemonic());
|
||||
}
|
||||
PrintF("\n");
|
||||
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 (size_t id = 0; id < state->size(); id++) {
|
||||
if (VirtualObject* object = state->GetVirtualObject(id)) {
|
||||
if (object->id() == id) {
|
||||
DebugPrintObject(object, static_cast<int>(id));
|
||||
} else {
|
||||
PrintF(" Object #%zu links to object #%d\n", id, object->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EscapeAnalysis::DebugPrint() {
|
||||
ZoneVector<VirtualState*> object_states(zone());
|
||||
for (NodeId id = 0; id < virtual_states_.size(); id++) {
|
||||
@ -885,26 +1111,7 @@ void EscapeAnalysis::DebugPrint() {
|
||||
}
|
||||
}
|
||||
for (size_t n = 0; n < object_states.size(); n++) {
|
||||
PrintF("Dumping object state %p\n", static_cast<void*>(object_states[n]));
|
||||
for (size_t id = 0; id < object_states[n]->size(); id++) {
|
||||
if (VirtualObject* obj = object_states[n]->GetVirtualObject(id)) {
|
||||
if (obj->id() == id) {
|
||||
PrintF(" Object #%zu with %zu fields", id, obj->fields());
|
||||
if (Node* rep = obj->GetReplacement()) {
|
||||
PrintF(", rep = #%d (%s)", rep->id(), rep->op()->mnemonic());
|
||||
}
|
||||
PrintF("\n");
|
||||
for (size_t i = 0; i < obj->fields(); ++i) {
|
||||
if (Node* f = obj->GetField(i)) {
|
||||
PrintF(" Field %zu = #%d (%s)\n", i, f->id(),
|
||||
f->op()->mnemonic());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PrintF(" Object #%zu links to object #%d\n", id, obj->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
DebugPrintState(object_states[n]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ namespace compiler {
|
||||
class CommonOperatorBuilder;
|
||||
class EscapeAnalysis;
|
||||
class VirtualState;
|
||||
class VirtualObject;
|
||||
|
||||
|
||||
// EscapeStatusAnalysis determines for each allocation whether it escapes.
|
||||
@ -46,8 +47,11 @@ class EscapeStatusAnalysis {
|
||||
void ProcessAllocate(Node* node);
|
||||
void ProcessFinishRegion(Node* node);
|
||||
void ProcessStoreField(Node* node);
|
||||
bool CheckUsesForEscape(Node* node) { return CheckUsesForEscape(node, node); }
|
||||
bool CheckUsesForEscape(Node* node, Node* rep);
|
||||
void ProcessStoreElement(Node* node);
|
||||
bool CheckUsesForEscape(Node* node, bool phi_escaping = false) {
|
||||
return CheckUsesForEscape(node, node, phi_escaping);
|
||||
}
|
||||
bool CheckUsesForEscape(Node* node, Node* rep, bool phi_escaping = false);
|
||||
void RevisitUses(Node* node);
|
||||
void RevisitInputs(Node* node);
|
||||
bool SetEscaped(Node* node);
|
||||
@ -87,17 +91,28 @@ class EscapeAnalysis {
|
||||
bool Process(Node* node);
|
||||
void ProcessLoadField(Node* node);
|
||||
void ProcessStoreField(Node* node);
|
||||
void ProcessLoadElement(Node* node);
|
||||
void ProcessStoreElement(Node* node);
|
||||
void ProcessAllocationUsers(Node* node);
|
||||
void ProcessAllocation(Node* node);
|
||||
void ProcessFinishRegion(Node* node);
|
||||
void ProcessCall(Node* node);
|
||||
void ProcessStart(Node* node);
|
||||
bool ProcessEffectPhi(Node* node);
|
||||
void ProcessLoadFromPhi(int offset, Node* from, Node* node,
|
||||
VirtualState* states);
|
||||
|
||||
void ForwardVirtualState(Node* node);
|
||||
|
||||
bool IsEffectBranchPoint(Node* node);
|
||||
bool IsDanglingEffectNode(Node* node);
|
||||
int OffsetFromAccess(Node* node);
|
||||
|
||||
VirtualObject* GetVirtualObject(Node* at, NodeId id);
|
||||
|
||||
void DebugPrint();
|
||||
void DebugPrintState(VirtualState* state);
|
||||
void DebugPrintObject(VirtualObject* state, NodeId id);
|
||||
|
||||
Graph* graph() const { return graph_; }
|
||||
CommonOperatorBuilder* common() const { return common_; }
|
||||
|
@ -28,24 +28,25 @@
|
||||
// Flags: --allow-natives-syntax --turbo-escape
|
||||
//
|
||||
|
||||
function f(a) {
|
||||
"use strict";
|
||||
return arguments;
|
||||
function f() {
|
||||
this.x=0;
|
||||
}
|
||||
|
||||
function g(a) {
|
||||
"use strict";
|
||||
var x = f(1,2,3);
|
||||
var o = new f();
|
||||
if (a) {
|
||||
x[1] = 5;
|
||||
o.x = 5;
|
||||
} else {
|
||||
x[1] = 7;
|
||||
o.x = 7;
|
||||
}
|
||||
|
||||
return x[1];
|
||||
return o.x;
|
||||
}
|
||||
|
||||
assertEquals(7, g());
|
||||
assertEquals(7, g());
|
||||
assertEquals(5, g(true));
|
||||
assertEquals(7, g(false));
|
||||
%OptimizeFunctionOnNextCall(g);
|
||||
assertEquals(5, g(true));
|
||||
assertEquals(7, g(false));
|
||||
assertEquals(7, g());
|
||||
|
52
test/mjsunit/compiler/escape-analysis-9.js
Normal file
52
test/mjsunit/compiler/escape-analysis-9.js
Normal file
@ -0,0 +1,52 @@
|
||||
// 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 f() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
function g(a) {
|
||||
"use strict";
|
||||
var o = f(1,2);
|
||||
if (a) {
|
||||
o[0] = 5;
|
||||
} else {
|
||||
o[0] = 7;
|
||||
}
|
||||
|
||||
return o[0];
|
||||
}
|
||||
|
||||
assertEquals(7, g());
|
||||
assertEquals(7, g());
|
||||
%OptimizeFunctionOnNextCall(g);
|
||||
assertEquals(5, g(true));
|
||||
assertEquals(7, g(false));
|
||||
assertEquals(7, g());
|
@ -172,9 +172,9 @@
|
||||
'compiler/escape-analysis-3': [PASS, NO_VARIANTS],
|
||||
'compiler/escape-analysis-4': [PASS, NO_VARIANTS],
|
||||
'compiler/escape-analysis-5': [PASS, NO_VARIANTS],
|
||||
'compiler/escape-analysis-7': [PASS, NO_VARIANTS],
|
||||
# TODO(sigurds): Tests that fail because of incomplete phi handling.
|
||||
'compiler/escape-analysis-6': [PASS, NO_VARIANTS],
|
||||
'compiler/escape-analysis-7': [PASS, NO_VARIANTS],
|
||||
'compiler/escape-analysis-9': [PASS, NO_VARIANTS],
|
||||
# TODO(sigurds): Tests that fail because of incomplete use handling (i.e. select).
|
||||
'compiler/escape-analysis-8': [PASS, NO_VARIANTS],
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user