diff --git a/BUILD.gn b/BUILD.gn index 96a17e5866..f24571b065 100644 --- a/BUILD.gn +++ b/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", diff --git a/src/compiler/csa-load-elimination.cc b/src/compiler/csa-load-elimination.cc new file mode 100644 index 0000000000..7c34f74db7 --- /dev/null +++ b/src/compiler/csa-load-elimination.cc @@ -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 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 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 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 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 queue(zone()); + ZoneSet 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 diff --git a/src/compiler/csa-load-elimination.h b/src/compiler/csa-load-elimination.h new file mode 100644 index 0000000000..3e839824c3 --- /dev/null +++ b/src/compiler/csa-load-elimination.h @@ -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; + using FieldInfos = PersistentMap; + 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 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_ diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc index e771cef123..fec0687810 100644 --- a/src/compiler/pipeline.cc +++ b/src/compiler/pipeline.cc @@ -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 Pipeline::GenerateCodeForCodeStub( pipeline.Run("V8.TFMachineCode"); } + pipeline.Run(); + pipeline.RunPrintAndVerify(CsaEarlyOptimizationPhase::phase_name(), true); + // Optimize memory access and allocation operations. pipeline.Run(); pipeline.RunPrintAndVerify(MemoryOptimizationPhase::phase_name(), true); diff --git a/test/cctest/torque/test-torque.cc b/test/cctest/torque/test-torque.cc index ed22a79beb..5499c51f35 100644 --- a/test/cctest/torque/test-torque.cc +++ b/test/cctest/torque/test-torque.cc @@ -498,6 +498,22 @@ TEST(TestStaticAssert) { ft.Call(); } +TEST(TestLoadEliminationNoWrite) { + CcTest::InitializeVM(); + Isolate* isolate(CcTest::i_isolate()); + i::HandleScope scope(isolate); + Handle context = + Utils::OpenHandle(*v8::Isolate::GetCurrent()->GetCurrentContext()); + CodeAssemblerTester asm_tester(isolate); + TestTorqueAssembler m(asm_tester.state()); + { + m.TestLoadEliminationNoWrite( + m.UncheckedCast(m.HeapConstant(context))); + m.Return(m.UndefinedConstant()); + } + asm_tester.GenerateCode(); +} + } // namespace compiler } // namespace internal } // namespace v8 diff --git a/test/torque/test-torque.tq b/test/torque/test-torque.tq index 8f6635a459..8e1e691c88 100644 --- a/test/torque/test-torque.tq +++ b/test/torque/test-torque.tq @@ -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)); + } + }