[turbofan] Add Late Escape Analysis to JS pipeline

Add a new late escape analysis pass to JS late optimizations.
The new pass simply removes allocations that are not used (besides
initializing stores to the object).

Bug: v8:12200
Change-Id: I01fc6233cca2f369c77ff2116ed7c4da1a232d95
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3677298
Commit-Queue: Patrick Thier <pthier@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80862}
This commit is contained in:
Patrick Thier 2022-05-31 12:55:11 +00:00 committed by V8 LUCI CQ
parent b77ff66b4c
commit f22c7eb65c
5 changed files with 199 additions and 0 deletions

View File

@ -2738,6 +2738,8 @@ filegroup(
"src/compiler/js-type-hint-lowering.h",
"src/compiler/js-typed-lowering.cc",
"src/compiler/js-typed-lowering.h",
"src/compiler/late-escape-analysis.cc",
"src/compiler/late-escape-analysis.h",
"src/compiler/linkage.cc",
"src/compiler/linkage.h",
"src/compiler/load-elimination.cc",

View File

@ -2861,6 +2861,7 @@ v8_header_set("v8_internal_headers") {
"src/compiler/js-operator.h",
"src/compiler/js-type-hint-lowering.h",
"src/compiler/js-typed-lowering.h",
"src/compiler/late-escape-analysis.h",
"src/compiler/linkage.h",
"src/compiler/load-elimination.h",
"src/compiler/loop-analysis.h",
@ -3994,6 +3995,7 @@ v8_compiler_sources = [
"src/compiler/js-operator.cc",
"src/compiler/js-type-hint-lowering.cc",
"src/compiler/js-typed-lowering.cc",
"src/compiler/late-escape-analysis.cc",
"src/compiler/linkage.cc",
"src/compiler/load-elimination.cc",
"src/compiler/loop-analysis.cc",

View File

@ -0,0 +1,145 @@
// Copyright 2022 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/late-escape-analysis.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
namespace v8 {
namespace internal {
namespace compiler {
LateEscapeAnalysis::LateEscapeAnalysis(Editor* editor, Graph* graph,
CommonOperatorBuilder* common,
Zone* zone)
: AdvancedReducer(editor),
dead_(graph->NewNode(common->Dead())),
all_allocations_(zone),
escaping_allocations_(zone),
revisit_(zone) {}
namespace {
bool IsStore(Edge edge) {
DCHECK_EQ(edge.to()->opcode(), IrOpcode::kAllocateRaw);
DCHECK(NodeProperties::IsValueEdge(edge));
switch (edge.from()->opcode()) {
case IrOpcode::kInitializeImmutableInObject:
case IrOpcode::kStore:
case IrOpcode::kStoreElement:
case IrOpcode::kStoreField:
case IrOpcode::kStoreToObject:
return edge.index() == 0;
default:
return false;
}
}
bool IsEscapingAllocationWitness(Edge edge) {
if (edge.to()->opcode() != IrOpcode::kAllocateRaw) return false;
if (!NodeProperties::IsValueEdge(edge)) return false;
return !IsStore(edge);
}
} // namespace
Reduction LateEscapeAnalysis::Reduce(Node* node) {
if (node->opcode() == IrOpcode::kAllocateRaw) {
all_allocations_.insert(node);
return NoChange();
}
for (Edge edge : node->input_edges()) {
if (IsEscapingAllocationWitness(edge)) {
RecordEscapingAllocation(edge.to());
}
}
return NoChange();
}
void LateEscapeAnalysis::Finalize() {
for (Node* alloc : all_allocations_) {
if (!IsEscaping(alloc)) {
RemoveAllocation(alloc);
}
}
while (!revisit_.empty()) {
Node* alloc = revisit_.front();
revisit_.pop_front();
if (!IsEscaping(alloc) && !alloc->IsDead()) {
RemoveAllocation(alloc);
}
}
}
namespace {
base::Optional<Node*> TryGetStoredValue(Node* node) {
int value_index;
switch (node->opcode()) {
case IrOpcode::kInitializeImmutableInObject:
case IrOpcode::kStore:
case IrOpcode::kStoreElement:
case IrOpcode::kStoreToObject:
value_index = 2;
break;
case IrOpcode::kStoreField:
value_index = 1;
break;
default:
return {};
}
return NodeProperties::GetValueInput(node, value_index);
}
} // namespace
bool LateEscapeAnalysis::IsEscaping(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kAllocateRaw);
auto escaping = escaping_allocations_.find(node);
if (escaping == escaping_allocations_.end()) return false;
return escaping->second != 0;
}
void LateEscapeAnalysis::RemoveAllocation(Node* node) {
DCHECK_EQ(node->opcode(), IrOpcode::kAllocateRaw);
for (Edge edge : node->use_edges()) {
if (!NodeProperties::IsValueEdge(edge)) continue;
Node* use = edge.from();
// The value stored by this Store node might be another allocation which has
// no more uses. Affected allocations are revisited.
if (base::Optional<Node*> stored_value = TryGetStoredValue(use);
stored_value.has_value() &&
stored_value.value()->opcode() == IrOpcode::kAllocateRaw &&
stored_value.value() != node) {
RemoveWitness(stored_value.value());
revisit_.push_back(stored_value.value());
}
ReplaceWithValue(use, dead());
use->Kill();
}
// Remove the allocation from the effect and control chains.
ReplaceWithValue(node, dead());
node->Kill();
}
void LateEscapeAnalysis::RecordEscapingAllocation(Node* allocation) {
DCHECK_EQ(allocation->opcode(), IrOpcode::kAllocateRaw);
escaping_allocations_[allocation]++;
}
void LateEscapeAnalysis::RemoveWitness(Node* allocation) {
DCHECK_EQ(allocation->opcode(), IrOpcode::kAllocateRaw);
DCHECK_GT(escaping_allocations_[allocation], 0);
escaping_allocations_[allocation]--;
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,46 @@
// Copyright 2022 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.
#ifndef V8_COMPILER_LATE_ESCAPE_ANALYSIS_H_
#define V8_COMPILER_LATE_ESCAPE_ANALYSIS_H_
#include "src/compiler/graph-reducer.h"
namespace v8 {
namespace internal {
namespace compiler {
class CommonOperatorBuilder;
// Eliminate allocated objects that have no uses besides the stores initializing
// the object.
class LateEscapeAnalysis final : public AdvancedReducer {
public:
LateEscapeAnalysis(Editor* editor, Graph* graph,
CommonOperatorBuilder* common, Zone* zone);
const char* reducer_name() const override { return "LateEscapeAnalysis"; }
Reduction Reduce(Node* node) final;
void Finalize() override;
private:
bool IsEscaping(Node* node);
void RemoveAllocation(Node* node);
void RecordEscapingAllocation(Node* allocation);
void RemoveWitness(Node* allocation);
Node* dead() const { return dead_; }
Node* dead_;
ZoneUnorderedSet<Node*> all_allocations_;
// Key: Allocation; Value: Number of witnesses for the allocation escaping.
ZoneUnorderedMap<Node*, int> escaping_allocations_;
NodeDeque revisit_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_LATE_ESCAPE_ANALYSIS_H_

View File

@ -55,6 +55,7 @@
#include "src/compiler/js-intrinsic-lowering.h"
#include "src/compiler/js-native-context-specialization.h"
#include "src/compiler/js-typed-lowering.h"
#include "src/compiler/late-escape-analysis.h"
#include "src/compiler/load-elimination.h"
#include "src/compiler/loop-analysis.h"
#include "src/compiler/loop-peeling.h"
@ -1950,6 +1951,8 @@ struct LateOptimizationPhase {
GraphReducer graph_reducer(
temp_zone, data->graph(), &data->info()->tick_counter(), data->broker(),
data->jsgraph()->Dead(), data->observe_node_manager());
LateEscapeAnalysis escape_analysis(&graph_reducer, data->graph(),
data->common(), temp_zone);
BranchElimination branch_condition_elimination(
&graph_reducer, data->jsgraph(), temp_zone, data->source_positions());
DeadCodeElimination dead_code_elimination(&graph_reducer, data->graph(),
@ -1961,6 +1964,7 @@ struct LateOptimizationPhase {
data->machine(), temp_zone, BranchSemantics::kMachine);
JSGraphAssembler graph_assembler(data->jsgraph(), temp_zone);
SelectLowering select_lowering(&graph_assembler, data->graph());
AddReducer(data, &graph_reducer, &escape_analysis);
AddReducer(data, &graph_reducer, &branch_condition_elimination);
AddReducer(data, &graph_reducer, &dead_code_elimination);
AddReducer(data, &graph_reducer, &machine_reducer);