Add very basic CsaLoadElimination phase
R=jarin@chromium.org, tebbi@chromium.org TBR: machenbach@chromium.org Change-Id: I82dd17b14eb086928f602395d80f0f2cf09770eb Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1635449 Reviewed-by: Michael Achenbach <machenbach@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org> Commit-Queue: Georg Schmid <gsps@google.com> Cr-Commit-Position: refs/heads/master@{#62020}
This commit is contained in:
parent
cdcdcb5e76
commit
0686e1ef70
2
BUILD.gn
2
BUILD.gn
@ -1730,6 +1730,8 @@ v8_compiler_sources = [
|
||||
"src/compiler/control-equivalence.h",
|
||||
"src/compiler/control-flow-optimizer.cc",
|
||||
"src/compiler/control-flow-optimizer.h",
|
||||
"src/compiler/csa-load-elimination.cc",
|
||||
"src/compiler/csa-load-elimination.h",
|
||||
"src/compiler/dead-code-elimination.cc",
|
||||
"src/compiler/dead-code-elimination.h",
|
||||
"src/compiler/decompression-elimination.cc",
|
||||
|
279
src/compiler/csa-load-elimination.cc
Normal file
279
src/compiler/csa-load-elimination.cc
Normal file
@ -0,0 +1,279 @@
|
||||
// Copyright 2019 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/csa-load-elimination.h"
|
||||
|
||||
#include "src/compiler/common-operator.h"
|
||||
#include "src/compiler/node-matchers.h"
|
||||
#include "src/compiler/node-properties.h"
|
||||
#include "src/compiler/simplified-operator.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
namespace compiler {
|
||||
|
||||
namespace {
|
||||
|
||||
base::Optional<size_t> ConstantOffsetOf(Node* node) {
|
||||
DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||
|
||||
node->opcode() == IrOpcode::kStoreToObject);
|
||||
IntPtrMatcher m(NodeProperties::GetValueInput(node, 1));
|
||||
if (m.HasValue()) {
|
||||
return m.Value();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reduction CsaLoadElimination::Reduce(Node* node) {
|
||||
if (FLAG_trace_turbo_load_elimination) {
|
||||
if (node->op()->EffectInputCount() > 0) {
|
||||
PrintF(" visit #%d:%s", node->id(), node->op()->mnemonic());
|
||||
if (node->op()->ValueInputCount() > 0) {
|
||||
PrintF("(");
|
||||
for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
|
||||
if (i > 0) PrintF(", ");
|
||||
Node* const value = NodeProperties::GetValueInput(node, i);
|
||||
PrintF("#%d:%s", value->id(), value->op()->mnemonic());
|
||||
}
|
||||
PrintF(")");
|
||||
}
|
||||
PrintF("\n");
|
||||
for (int i = 0; i < node->op()->EffectInputCount(); ++i) {
|
||||
Node* const effect = NodeProperties::GetEffectInput(node, i);
|
||||
if (AbstractState const* const state = node_states_.Get(effect)) {
|
||||
PrintF(" state[%i]: #%d:%s\n", i, effect->id(),
|
||||
effect->op()->mnemonic());
|
||||
state->Print();
|
||||
} else {
|
||||
PrintF(" no state[%i]: #%d:%s\n", i, effect->id(),
|
||||
effect->op()->mnemonic());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (node->opcode()) {
|
||||
case IrOpcode::kLoadFromObject:
|
||||
return ReduceLoadFromObject(node, ObjectAccessOf(node->op()));
|
||||
case IrOpcode::kDebugBreak:
|
||||
case IrOpcode::kDebugAbort:
|
||||
// Avoid changing optimizations in the presence of debug instructions
|
||||
return PropagateInputState(node);
|
||||
case IrOpcode::kCall:
|
||||
return ReduceCall(node);
|
||||
case IrOpcode::kEffectPhi:
|
||||
return ReduceEffectPhi(node);
|
||||
case IrOpcode::kDead:
|
||||
break;
|
||||
case IrOpcode::kStart:
|
||||
return ReduceStart(node);
|
||||
default:
|
||||
return ReduceOtherNode(node);
|
||||
}
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool CsaLoadEliminationIsCompatible(MachineRepresentation r1,
|
||||
MachineRepresentation r2) {
|
||||
if (r1 == r2) return true;
|
||||
return IsAnyCompressedTagged(r1) && IsAnyCompressedTagged(r2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CsaLoadElimination::AbstractState::Merge(AbstractState const* that,
|
||||
Zone* zone) {
|
||||
FieldInfo empty_info;
|
||||
for (std::pair<Field, FieldInfo> entry : field_infos_) {
|
||||
if (that->field_infos_.Get(entry.first) != entry.second) {
|
||||
field_infos_.Set(entry.first, empty_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CsaLoadElimination::AbstractState const*
|
||||
CsaLoadElimination::AbstractState::AddField(Node* object, size_t offset,
|
||||
CsaLoadElimination::FieldInfo info,
|
||||
Zone* zone) const {
|
||||
AbstractState* that = new (zone) AbstractState(*this);
|
||||
that->field_infos_.Set({offset, object}, info);
|
||||
return that;
|
||||
}
|
||||
|
||||
CsaLoadElimination::FieldInfo CsaLoadElimination::AbstractState::Lookup(
|
||||
Node* object, size_t offset) const {
|
||||
if (object->IsDead()) {
|
||||
return {};
|
||||
}
|
||||
return field_infos_.Get({offset, object});
|
||||
}
|
||||
|
||||
void CsaLoadElimination::AbstractState::Print() const {
|
||||
for (std::pair<Field, FieldInfo> entry : field_infos_) {
|
||||
Field field = entry.first;
|
||||
size_t offset = field.first;
|
||||
Node* node = field.second;
|
||||
FieldInfo info = entry.second;
|
||||
PrintF(" #%d+%zu:%s -> #%d:%s [repr=%s]\n", node->id(), offset,
|
||||
node->op()->mnemonic(), info.value->id(),
|
||||
info.value->op()->mnemonic(),
|
||||
MachineReprToString(info.representation));
|
||||
}
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::ReduceLoadFromObject(Node* node,
|
||||
ObjectAccess const& access) {
|
||||
Node* object = NodeProperties::GetValueInput(node, 0);
|
||||
Node* effect = NodeProperties::GetEffectInput(node);
|
||||
AbstractState const* state = node_states_.Get(effect);
|
||||
if (state == nullptr) return NoChange();
|
||||
|
||||
if (base::Optional<size_t> offset = ConstantOffsetOf(node)) {
|
||||
MachineRepresentation representation = access.machine_type.representation();
|
||||
FieldInfo lookup_result = state->Lookup(object, offset.value());
|
||||
if (!lookup_result.IsEmpty()) {
|
||||
// Make sure we don't reuse values that were recorded with a different
|
||||
// representation or resurrect dead {replacement} nodes.
|
||||
Node* replacement = lookup_result.value;
|
||||
if (CsaLoadEliminationIsCompatible(representation,
|
||||
lookup_result.representation) &&
|
||||
!replacement->IsDead()) {
|
||||
ReplaceWithValue(node, replacement, effect);
|
||||
return Replace(replacement);
|
||||
}
|
||||
}
|
||||
FieldInfo info(node, representation);
|
||||
state = state->AddField(object, offset.value(), info, zone());
|
||||
}
|
||||
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::ReduceEffectPhi(Node* node) {
|
||||
Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
|
||||
Node* const control = NodeProperties::GetControlInput(node);
|
||||
AbstractState const* state0 = node_states_.Get(effect0);
|
||||
if (state0 == nullptr) return NoChange();
|
||||
if (control->opcode() == IrOpcode::kLoop) {
|
||||
// Here we rely on having only reducible loops:
|
||||
// The loop entry edge always dominates the header, so we can just take
|
||||
// the state from the first input, and compute the loop state based on it.
|
||||
AbstractState const* state = ComputeLoopState(node, state0);
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
DCHECK_EQ(IrOpcode::kMerge, control->opcode());
|
||||
|
||||
// Shortcut for the case when we do not know anything about some input.
|
||||
int const input_count = node->op()->EffectInputCount();
|
||||
for (int i = 1; i < input_count; ++i) {
|
||||
Node* const effect = NodeProperties::GetEffectInput(node, i);
|
||||
if (node_states_.Get(effect) == nullptr) return NoChange();
|
||||
}
|
||||
|
||||
// Make a copy of the first input's state and merge with the state
|
||||
// from other inputs.
|
||||
AbstractState* state = new (zone()) AbstractState(*state0);
|
||||
for (int i = 1; i < input_count; ++i) {
|
||||
Node* const input = NodeProperties::GetEffectInput(node, i);
|
||||
state->Merge(node_states_.Get(input), zone());
|
||||
}
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::ReduceStart(Node* node) {
|
||||
return UpdateState(node, empty_state());
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::ReduceCall(Node* node) {
|
||||
Node* value = NodeProperties::GetValueInput(node, 0);
|
||||
ExternalReferenceMatcher m(value);
|
||||
if (m.Is(ExternalReference::check_object_type())) {
|
||||
return PropagateInputState(node);
|
||||
}
|
||||
return ReduceOtherNode(node);
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::ReduceOtherNode(Node* node) {
|
||||
if (node->op()->EffectInputCount() == 1) {
|
||||
if (node->op()->EffectOutputCount() == 1) {
|
||||
Node* const effect = NodeProperties::GetEffectInput(node);
|
||||
AbstractState const* state = node_states_.Get(effect);
|
||||
// If we do not know anything about the predecessor, do not propagate
|
||||
// just yet because we will have to recompute anyway once we compute
|
||||
// the predecessor.
|
||||
if (state == nullptr) return NoChange();
|
||||
// Check if this {node} has some uncontrolled side effects.
|
||||
if (!node->op()->HasProperty(Operator::kNoWrite)) {
|
||||
state = empty_state();
|
||||
}
|
||||
return UpdateState(node, state);
|
||||
} else {
|
||||
return NoChange();
|
||||
}
|
||||
}
|
||||
DCHECK_EQ(0, node->op()->EffectInputCount());
|
||||
DCHECK_EQ(0, node->op()->EffectOutputCount());
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::UpdateState(Node* node,
|
||||
AbstractState const* state) {
|
||||
AbstractState const* original = node_states_.Get(node);
|
||||
// Only signal that the {node} has Changed, if the information about {state}
|
||||
// has changed wrt. the {original}.
|
||||
if (state != original) {
|
||||
if (original == nullptr || !state->Equals(original)) {
|
||||
node_states_.Set(node, state);
|
||||
return Changed(node);
|
||||
}
|
||||
}
|
||||
return NoChange();
|
||||
}
|
||||
|
||||
Reduction CsaLoadElimination::PropagateInputState(Node* node) {
|
||||
Node* const effect = NodeProperties::GetEffectInput(node);
|
||||
AbstractState const* state = node_states_.Get(effect);
|
||||
if (state == nullptr) return NoChange();
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
|
||||
CsaLoadElimination::AbstractState const* CsaLoadElimination::ComputeLoopState(
|
||||
Node* node, AbstractState const* state) const {
|
||||
DCHECK_EQ(node->opcode(), IrOpcode::kEffectPhi);
|
||||
Node* const control = NodeProperties::GetControlInput(node);
|
||||
ZoneQueue<Node*> queue(zone());
|
||||
ZoneSet<Node*> visited(zone());
|
||||
visited.insert(node);
|
||||
for (int i = 1; i < control->InputCount(); ++i) {
|
||||
queue.push(node->InputAt(i));
|
||||
}
|
||||
while (!queue.empty()) {
|
||||
Node* const current = queue.front();
|
||||
queue.pop();
|
||||
if (visited.insert(current).second) {
|
||||
if (!current->op()->HasProperty(Operator::kNoWrite)) {
|
||||
return empty_state();
|
||||
}
|
||||
for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
|
||||
queue.push(NodeProperties::GetEffectInput(current, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
CommonOperatorBuilder* CsaLoadElimination::common() const {
|
||||
return jsgraph()->common();
|
||||
}
|
||||
|
||||
Graph* CsaLoadElimination::graph() const { return jsgraph()->graph(); }
|
||||
|
||||
Isolate* CsaLoadElimination::isolate() const { return jsgraph()->isolate(); }
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
115
src/compiler/csa-load-elimination.h
Normal file
115
src/compiler/csa-load-elimination.h
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2019 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_CSA_LOAD_ELIMINATION_H_
|
||||
#define V8_COMPILER_CSA_LOAD_ELIMINATION_H_
|
||||
|
||||
#include "src/base/compiler-specific.h"
|
||||
#include "src/codegen/machine-type.h"
|
||||
#include "src/common/globals.h"
|
||||
#include "src/compiler/graph-reducer.h"
|
||||
#include "src/compiler/js-graph.h"
|
||||
#include "src/compiler/node-aux-data.h"
|
||||
#include "src/compiler/persistent-map.h"
|
||||
#include "src/handles/maybe-handles.h"
|
||||
#include "src/zone/zone-handle-set.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// Forward declarations.
|
||||
class CommonOperatorBuilder;
|
||||
struct ObjectAccess;
|
||||
class Graph;
|
||||
class JSGraph;
|
||||
|
||||
class V8_EXPORT_PRIVATE CsaLoadElimination final
|
||||
: public NON_EXPORTED_BASE(AdvancedReducer) {
|
||||
public:
|
||||
CsaLoadElimination(Editor* editor, JSGraph* jsgraph, Zone* zone)
|
||||
: AdvancedReducer(editor),
|
||||
empty_state_(zone),
|
||||
node_states_(jsgraph->graph()->NodeCount(), zone),
|
||||
jsgraph_(jsgraph),
|
||||
zone_(zone) {}
|
||||
~CsaLoadElimination() final = default;
|
||||
|
||||
const char* reducer_name() const override { return "CsaLoadElimination"; }
|
||||
|
||||
Reduction Reduce(Node* node) final;
|
||||
|
||||
private:
|
||||
struct FieldInfo {
|
||||
FieldInfo() = default;
|
||||
FieldInfo(Node* value, MachineRepresentation representation)
|
||||
: value(value), representation(representation) {}
|
||||
|
||||
bool operator==(const FieldInfo& other) const {
|
||||
return value == other.value && representation == other.representation;
|
||||
}
|
||||
|
||||
bool operator!=(const FieldInfo& other) const { return !(*this == other); }
|
||||
|
||||
bool IsEmpty() const { return value == nullptr; }
|
||||
|
||||
Node* value = nullptr;
|
||||
MachineRepresentation representation = MachineRepresentation::kNone;
|
||||
};
|
||||
|
||||
class AbstractState final : public ZoneObject {
|
||||
public:
|
||||
explicit AbstractState(Zone* zone) : field_infos_(zone) {}
|
||||
|
||||
bool Equals(AbstractState const* that) const {
|
||||
return field_infos_ == that->field_infos_;
|
||||
}
|
||||
void Merge(AbstractState const* that, Zone* zone);
|
||||
|
||||
AbstractState const* AddField(Node* object, size_t offset, FieldInfo info,
|
||||
Zone* zone) const;
|
||||
AbstractState const* KillAll(Zone* zone) const;
|
||||
FieldInfo Lookup(Node* object, size_t offset) const;
|
||||
|
||||
void Print() const;
|
||||
|
||||
private:
|
||||
using Field = std::pair<size_t, Node*>;
|
||||
using FieldInfos = PersistentMap<Field, FieldInfo>;
|
||||
FieldInfos field_infos_;
|
||||
};
|
||||
|
||||
Reduction ReduceLoadFromObject(Node* node, ObjectAccess const& access);
|
||||
Reduction ReduceEffectPhi(Node* node);
|
||||
Reduction ReduceStart(Node* node);
|
||||
Reduction ReduceCall(Node* node);
|
||||
Reduction ReduceOtherNode(Node* node);
|
||||
|
||||
Reduction UpdateState(Node* node, AbstractState const* state);
|
||||
Reduction PropagateInputState(Node* node);
|
||||
|
||||
AbstractState const* ComputeLoopState(Node* node,
|
||||
AbstractState const* state) const;
|
||||
|
||||
CommonOperatorBuilder* common() const;
|
||||
Isolate* isolate() const;
|
||||
Graph* graph() const;
|
||||
JSGraph* jsgraph() const { return jsgraph_; }
|
||||
Zone* zone() const { return zone_; }
|
||||
AbstractState const* empty_state() const { return &empty_state_; }
|
||||
|
||||
AbstractState const empty_state_;
|
||||
NodeAuxData<AbstractState const*> node_states_;
|
||||
JSGraph* const jsgraph_;
|
||||
Zone* zone_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CsaLoadElimination);
|
||||
};
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_COMPILER_CSA_LOAD_ELIMINATION_H_
|
@ -34,6 +34,7 @@
|
||||
#include "src/compiler/compiler-source-position-table.h"
|
||||
#include "src/compiler/constant-folding-reducer.h"
|
||||
#include "src/compiler/control-flow-optimizer.h"
|
||||
#include "src/compiler/csa-load-elimination.h"
|
||||
#include "src/compiler/dead-code-elimination.h"
|
||||
#include "src/compiler/decompression-elimination.h"
|
||||
#include "src/compiler/effect-control-linearizer.h"
|
||||
@ -1565,6 +1566,19 @@ struct MachineOperatorOptimizationPhase {
|
||||
}
|
||||
};
|
||||
|
||||
struct CsaEarlyOptimizationPhase {
|
||||
static const char* phase_name() { return "V8.CSAEarlyOptimization"; }
|
||||
|
||||
void Run(PipelineData* data, Zone* temp_zone) {
|
||||
GraphReducer graph_reducer(temp_zone, data->graph(),
|
||||
data->jsgraph()->Dead());
|
||||
CsaLoadElimination load_elimination(&graph_reducer, data->jsgraph(),
|
||||
temp_zone);
|
||||
AddReducer(data, &graph_reducer, &load_elimination);
|
||||
graph_reducer.ReduceGraph();
|
||||
}
|
||||
};
|
||||
|
||||
struct CsaOptimizationPhase {
|
||||
static const char* phase_name() { return "V8.CSAOptimization"; }
|
||||
|
||||
@ -2201,6 +2215,9 @@ MaybeHandle<Code> Pipeline::GenerateCodeForCodeStub(
|
||||
pipeline.Run<PrintGraphPhase>("V8.TFMachineCode");
|
||||
}
|
||||
|
||||
pipeline.Run<CsaEarlyOptimizationPhase>();
|
||||
pipeline.RunPrintAndVerify(CsaEarlyOptimizationPhase::phase_name(), true);
|
||||
|
||||
// Optimize memory access and allocation operations.
|
||||
pipeline.Run<MemoryOptimizationPhase>();
|
||||
pipeline.RunPrintAndVerify(MemoryOptimizationPhase::phase_name(), true);
|
||||
|
@ -498,6 +498,22 @@ TEST(TestStaticAssert) {
|
||||
ft.Call();
|
||||
}
|
||||
|
||||
TEST(TestLoadEliminationNoWrite) {
|
||||
CcTest::InitializeVM();
|
||||
Isolate* isolate(CcTest::i_isolate());
|
||||
i::HandleScope scope(isolate);
|
||||
Handle<Context> context =
|
||||
Utils::OpenHandle(*v8::Isolate::GetCurrent()->GetCurrentContext());
|
||||
CodeAssemblerTester asm_tester(isolate);
|
||||
TestTorqueAssembler m(asm_tester.state());
|
||||
{
|
||||
m.TestLoadEliminationNoWrite(
|
||||
m.UncheckedCast<Context>(m.HeapConstant(context)));
|
||||
m.Return(m.UndefinedConstant());
|
||||
}
|
||||
asm_tester.GenerateCode();
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -907,4 +907,21 @@ namespace test {
|
||||
StaticAssert(1 + 2 == 3);
|
||||
}
|
||||
|
||||
class SmiBox {
|
||||
value: Smi;
|
||||
unrelated: Smi;
|
||||
}
|
||||
|
||||
builtin NewSmiBox(implicit c: Context)(value: Smi): SmiBox {
|
||||
return new SmiBox{value, unrelated: 0};
|
||||
}
|
||||
|
||||
@export
|
||||
macro TestLoadEliminationNoWrite(implicit c: Context)() {
|
||||
const box = NewSmiBox(123);
|
||||
const v1 = box.value;
|
||||
const v2 = (box.unrelated == 0) ? box.value : box.value;
|
||||
StaticAssert(WordEqual(v1, v2));
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user