|  | // Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | #include "vm/compiler/backend/linearscan.h" | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "vm/compiler/backend/block_builder.h" | 
|  | #include "vm/compiler/backend/il_printer.h" | 
|  | #include "vm/compiler/backend/il_test_helper.h" | 
|  | #include "vm/unit_test.h" | 
|  | #include "vm/zone_text_buffer.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | class DummyDef : public Definition { | 
|  | public: | 
|  | explicit DummyDef( | 
|  | Zone* zone, | 
|  | std::initializer_list<std::pair<Definition*, Location>> inputs, | 
|  | Location output, | 
|  | LocationSummary::ContainsCall contains_call = LocationSummary::kNoCall) | 
|  | : inputs_(inputs.size()), | 
|  | summary_(new LocationSummary(zone, | 
|  | inputs.size(), | 
|  | /*temp_count=*/0, | 
|  | contains_call)) { | 
|  | intptr_t index = 0; | 
|  | for (auto [defn, loc] : inputs) { | 
|  | auto v = new Value(defn); | 
|  | summary_->set_in(index, loc); | 
|  | v->set_use_index(index); | 
|  | v->set_instruction(this); | 
|  | inputs_.Add(v); | 
|  | index++; | 
|  | } | 
|  | summary_->set_out(0, output); | 
|  | } | 
|  |  | 
|  | LocationSummary* MakeLocationSummary(Zone* zone, bool opt) const { | 
|  | return summary_; | 
|  | } | 
|  |  | 
|  | virtual void Accept(InstructionVisitor* visitor) { UNREACHABLE(); } | 
|  |  | 
|  | virtual Tag tag() const { return Instruction::kRedefinition; } | 
|  |  | 
|  | virtual const char* DebugName() const { return "DummyDef"; } | 
|  |  | 
|  | virtual intptr_t InputCount() const { return inputs_.length(); } | 
|  | virtual Value* InputAt(intptr_t i) const { return inputs_[i]; } | 
|  |  | 
|  | virtual bool MayThrow() const { return false; } | 
|  | virtual bool ComputeCanDeoptimize() const { return false; } | 
|  | virtual bool HasUnknownSideEffects() const { return false; } | 
|  |  | 
|  | private: | 
|  | virtual void RawSetInputAt(intptr_t i, Value* value) { inputs_[i] = value; } | 
|  |  | 
|  | GrowableArray<Value*> inputs_; | 
|  | LocationSummary* const summary_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(DummyDef); | 
|  | }; | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(LinearScan_TestSameAsFirstOrSecondFlip) { | 
|  | using compiler::BlockBuilder; | 
|  | CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true); | 
|  | FlowGraphBuilderHelper H; | 
|  |  | 
|  | auto zone = H.flow_graph()->zone(); | 
|  |  | 
|  | auto b1 = H.flow_graph()->graph_entry()->normal_entry(); | 
|  |  | 
|  | DummyDef* lhs; | 
|  | DummyDef* rhs; | 
|  | DummyDef* binop; | 
|  |  | 
|  | { | 
|  | BlockBuilder builder(H.flow_graph(), b1); | 
|  |  | 
|  | lhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | rhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | binop = builder.AddDefinition( | 
|  | new DummyDef(zone, | 
|  | {{lhs, Location::RequiresRegister()}, | 
|  | {rhs, Location::RequiresRegister()}}, | 
|  | Location::SameAsFirstOrSecondInput())); | 
|  | // Left hand side of the binary operation is still needed after it. | 
|  | builder.AddInstruction( | 
|  | new DummyDef(zone, {{lhs, Location::RequiresRegister()}}, Location())); | 
|  | builder.AddInstruction(new DartReturnInstr( | 
|  | InstructionSource(), new Value(binop), S.GetNextDeoptId())); | 
|  | } | 
|  | H.FinishGraph(); | 
|  |  | 
|  | FlowGraphPrinter::PrintGraph("before regalloc", H.flow_graph()); | 
|  |  | 
|  | H.flow_graph()->InsertMoveArguments(); | 
|  | // Ensure loop hierarchy has been computed. | 
|  | H.flow_graph()->GetLoopHierarchy(); | 
|  | // Perform register allocation on the SSA graph. | 
|  | FlowGraphAllocator allocator(*H.flow_graph()); | 
|  | allocator.AllocateRegisters(); | 
|  |  | 
|  | // There should be no parallel move between binop and rhs and inputs | 
|  | // to binop should be flipped. | 
|  | EXPECT_PROPERTY(binop->previous(), | 
|  | &it == rhs || (it.IsParallelMove() && | 
|  | it.AsParallelMove()->IsRedundant() && | 
|  | it.previous() == rhs)); | 
|  | EXPECT_PROPERTY(binop->InputAt(0)->definition(), &it == rhs); | 
|  | EXPECT_PROPERTY(binop->InputAt(1)->definition(), &it == lhs); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(LinearScan_TestSameAsFirstOrSecondNoFlip) { | 
|  | using compiler::BlockBuilder; | 
|  | CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true); | 
|  | FlowGraphBuilderHelper H; | 
|  |  | 
|  | auto zone = H.flow_graph()->zone(); | 
|  |  | 
|  | auto b1 = H.flow_graph()->graph_entry()->normal_entry(); | 
|  |  | 
|  | DummyDef* lhs; | 
|  | DummyDef* rhs; | 
|  | DummyDef* binop; | 
|  |  | 
|  | { | 
|  | BlockBuilder builder(H.flow_graph(), b1); | 
|  |  | 
|  | lhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | rhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | binop = builder.AddDefinition( | 
|  | new DummyDef(zone, | 
|  | {{lhs, Location::RequiresRegister()}, | 
|  | {rhs, Location::RequiresRegister()}}, | 
|  | Location::SameAsFirstOrSecondInput())); | 
|  | // Right hand side of the binary operation is still needed after it. | 
|  | builder.AddInstruction( | 
|  | new DummyDef(zone, {{rhs, Location::RequiresRegister()}}, Location())); | 
|  | builder.AddInstruction(new DartReturnInstr( | 
|  | InstructionSource(), new Value(binop), S.GetNextDeoptId())); | 
|  | } | 
|  | H.FinishGraph(); | 
|  |  | 
|  | H.flow_graph()->InsertMoveArguments(); | 
|  | // Ensure loop hierarchy has been computed. | 
|  | H.flow_graph()->GetLoopHierarchy(); | 
|  | // Perform register allocation on the SSA graph. | 
|  | FlowGraphAllocator allocator(*H.flow_graph()); | 
|  | allocator.AllocateRegisters(); | 
|  |  | 
|  | // There should be no parallel move between binop and rhs and inputs | 
|  | // to binop should *not* be flipped. | 
|  | EXPECT_PROPERTY(binop->previous(), | 
|  | &it == rhs || (it.IsParallelMove() && | 
|  | it.AsParallelMove()->IsRedundant() && | 
|  | it.previous() == rhs)); | 
|  | EXPECT_PROPERTY(binop->InputAt(0)->definition(), &it == lhs); | 
|  | EXPECT_PROPERTY(binop->InputAt(1)->definition(), &it == rhs); | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(LinearScan_TestSameAsFirstOrSecondNoFlip2) { | 
|  | using compiler::BlockBuilder; | 
|  | CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true); | 
|  | FlowGraphBuilderHelper H; | 
|  |  | 
|  | auto zone = H.flow_graph()->zone(); | 
|  |  | 
|  | auto b1 = H.flow_graph()->graph_entry()->normal_entry(); | 
|  |  | 
|  | DummyDef* lhs; | 
|  | DummyDef* rhs; | 
|  | DummyDef* binop; | 
|  |  | 
|  | { | 
|  | BlockBuilder builder(H.flow_graph(), b1); | 
|  |  | 
|  | lhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | rhs = builder.AddDefinition( | 
|  | new DummyDef(zone, {}, Location::RequiresRegister())); | 
|  | binop = builder.AddDefinition( | 
|  | new DummyDef(zone, | 
|  | {{lhs, Location::RequiresRegister()}, | 
|  | {rhs, Location::RequiresRegister()}}, | 
|  | Location::SameAsFirstOrSecondInput())); | 
|  | // Both right and left hand sides of the binary operation are still needed | 
|  | // after it. | 
|  | builder.AddInstruction( | 
|  | new DummyDef(zone, {{rhs, Location::RequiresRegister()}}, Location())); | 
|  | builder.AddInstruction( | 
|  | new DummyDef(zone, {{lhs, Location::RequiresRegister()}}, Location())); | 
|  | builder.AddInstruction(new DartReturnInstr( | 
|  | InstructionSource(), new Value(binop), S.GetNextDeoptId())); | 
|  | } | 
|  | H.FinishGraph(); | 
|  |  | 
|  | H.flow_graph()->InsertMoveArguments(); | 
|  | // Ensure loop hierarchy has been computed. | 
|  | H.flow_graph()->GetLoopHierarchy(); | 
|  | // Perform register allocation on the SSA graph. | 
|  | FlowGraphAllocator allocator(*H.flow_graph()); | 
|  | allocator.AllocateRegisters(); | 
|  |  | 
|  | // There should be a parallel move between binop and rhs and inputs | 
|  | // to binop should *not* be flipped. | 
|  | EXPECT_PROPERTY(binop->previous(), it.IsParallelMove()); | 
|  | EXPECT_PROPERTY(binop->InputAt(0)->definition(), &it == lhs); | 
|  | EXPECT_PROPERTY(binop->InputAt(1)->definition(), &it == rhs); | 
|  | } | 
|  |  | 
|  | }  // namespace dart |