Files
sdk/runtime/vm/compiler/backend/linearscan_test.cc
Vyacheslav Egorov 34fa3c1fb5 [vm] Improve same-as-first constraint handling
All other moves generated by the register allocator to satisfy
constraints were using `PrefersRegister` for the source, but
`SameAsFirstInput` was using `Any`. This lead to situations
where hot code inside a loop would repeatedly reload a constant.
To avoid these situations switch `SameAsFirstInput` to use
`PrefersRegister` when the use occurs inside a loop.

Additionally introduce an extension of `SameAsFirstInput`:
`SameAsFirstOrSecondInput`. This new constraint allows register
allocator to reorder first and second inputs if the second one
is no longer alive after the instruction. This allows register
allocator to avoid unnecessary move.

`SameAsFirstOrSecondInput` can be used as an output constraint
for commutative binary operations on X64

Additionally this CL adds a nascent infrastructure for writing
unit tests against register allocator. See `linearscan_test.cc`.

This CL improves code quality for tight loops with binary
double operations written with constants on the left, for example:


    loop {
      doubleA = C * doubleB
    }

Before this CL `C` would be reloaded into a register immediately
before multiplication, but after this CL it will be kept in
register (if register pressure allows).

Issue https://github.com/dart-lang/sdk/issues/56705

TEST=LinearScan_TestSameAsFirstOrSecond*

Change-Id: Id8e8242a8d1c1d1b8958076f10257e21e4a00aae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/385001
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
2024-09-20 14:47:49 +00:00

220 lines
7.6 KiB
C++

// 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