Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,443 @@
// Copyright 2015 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/backend/register-allocator.h"
#include "test/unittests/test-utils.h"
// TODO(mtrofin): would we want to centralize this definition?
#ifdef DEBUG
#define V8_ASSERT_DEBUG_DEATH(statement, regex) \
ASSERT_DEATH_IF_SUPPORTED(statement, regex)
#define DISABLE_IN_RELEASE(Name) Name
#else
#define V8_ASSERT_DEBUG_DEATH(statement, regex) statement
#define DISABLE_IN_RELEASE(Name) DISABLED_##Name
#endif // DEBUG
namespace v8 {
namespace internal {
namespace compiler {
// Utility offering shorthand syntax for building up a range by providing its ID
// and pairs (start, end) specifying intervals. Circumvents current incomplete
// support for C++ features such as instantiation lists, on OS X and Android.
class TestRangeBuilder {
public:
explicit TestRangeBuilder(Zone* zone)
: id_(-1), pairs_(), uses_(), zone_(zone) {}
TestRangeBuilder& Id(int id) {
id_ = id;
return *this;
}
TestRangeBuilder& Add(int start, int end) {
pairs_.push_back({start, end});
return *this;
}
TestRangeBuilder& AddUse(int pos) {
uses_.insert(pos);
return *this;
}
TopLevelLiveRange* Build(int start, int end) {
return Add(start, end).Build();
}
TopLevelLiveRange* Build() {
TopLevelLiveRange* range = zone_->New<TopLevelLiveRange>(
id_, MachineRepresentation::kTagged, zone_);
// Traverse the provided interval specifications backwards, because that is
// what LiveRange expects.
for (int i = static_cast<int>(pairs_.size()) - 1; i >= 0; --i) {
Interval pair = pairs_[i];
LifetimePosition start = LifetimePosition::FromInt(pair.first);
LifetimePosition end = LifetimePosition::FromInt(pair.second);
CHECK(start < end);
range->AddUseInterval(start, end, zone_);
}
for (int pos : uses_) {
UsePosition* use_position =
zone_->New<UsePosition>(LifetimePosition::FromInt(pos), nullptr,
nullptr, UsePositionHintType::kNone);
range->AddUsePosition(use_position, zone_);
}
pairs_.clear();
return range;
}
private:
using Interval = std::pair<int, int>;
using IntervalList = std::vector<Interval>;
int id_;
IntervalList pairs_;
std::set<int> uses_;
Zone* zone_;
};
class LiveRangeUnitTest : public TestWithZone {
public:
// Split helper, to avoid int->LifetimePosition conversion nuisance.
LiveRange* Split(LiveRange* range, int pos) {
return range->SplitAt(LifetimePosition::FromInt(pos), zone());
}
// Ranges first and second match structurally.
bool RangesMatch(const LiveRange* first, const LiveRange* second) {
if (first->Start() != second->Start() || first->End() != second->End()) {
return false;
}
auto i1 = first->intervals().begin();
auto i2 = second->intervals().begin();
while (i1 != first->intervals().end() && i2 != second->intervals().end()) {
if (*i1 != *i2) return false;
++i1;
++i2;
}
if (i1 != first->intervals().end() || i2 != second->intervals().end()) {
return false;
}
UsePosition* const* p1 = first->positions().begin();
UsePosition* const* p2 = second->positions().begin();
while (p1 != first->positions().end() && p2 != second->positions().end()) {
if ((*p1)->pos() != (*p2)->pos()) return false;
++p1;
++p2;
}
if (p1 != first->positions().end() || p2 != second->positions().end()) {
return false;
}
return true;
}
};
TEST_F(LiveRangeUnitTest, InvalidConstruction) {
// Build a range manually, because the builder guards against empty cases.
TopLevelLiveRange* range =
zone()->New<TopLevelLiveRange>(1, MachineRepresentation::kTagged, zone());
V8_ASSERT_DEBUG_DEATH(
range->AddUseInterval(LifetimePosition::FromInt(0),
LifetimePosition::FromInt(0), zone()),
".*");
}
TEST_F(LiveRangeUnitTest, SplitInvalidStart) {
TopLevelLiveRange* range = TestRangeBuilder(zone()).Build(0, 1);
V8_ASSERT_DEBUG_DEATH(Split(range, 0), ".*");
}
TEST_F(LiveRangeUnitTest, DISABLE_IN_RELEASE(InvalidSplitEnd)) {
TopLevelLiveRange* range = TestRangeBuilder(zone()).Build(0, 1);
ASSERT_DEATH_IF_SUPPORTED(Split(range, 1), ".*");
}
TEST_F(LiveRangeUnitTest, DISABLE_IN_RELEASE(SplitInvalidPreStart)) {
TopLevelLiveRange* range = TestRangeBuilder(zone()).Build(1, 2);
ASSERT_DEATH_IF_SUPPORTED(Split(range, 0), ".*");
}
TEST_F(LiveRangeUnitTest, DISABLE_IN_RELEASE(SplitInvalidPostEnd)) {
TopLevelLiveRange* range = TestRangeBuilder(zone()).Build(0, 1);
ASSERT_DEATH_IF_SUPPORTED(Split(range, 2), ".*");
}
TEST_F(LiveRangeUnitTest, SplitSingleIntervalNoUsePositions) {
TopLevelLiveRange* range = TestRangeBuilder(zone()).Build(0, 2);
LiveRange* child = Split(range, 1);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top = TestRangeBuilder(zone()).Build(0, 1);
LiveRange* expected_bottom = TestRangeBuilder(zone()).Build(1, 2);
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalNoUsePositionsBetween) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).Build();
LiveRange* child = Split(range, 3);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top = TestRangeBuilder(zone()).Build(0, 2);
LiveRange* expected_bottom = TestRangeBuilder(zone()).Build(4, 6);
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalNoUsePositionsFront) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).Build();
LiveRange* child = Split(range, 1);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top = TestRangeBuilder(zone()).Build(0, 1);
LiveRange* expected_bottom =
TestRangeBuilder(zone()).Add(1, 2).Add(4, 6).Build();
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalNoUsePositionsAfter) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).Build();
LiveRange* child = Split(range, 5);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 5).Build();
LiveRange* expected_bottom = TestRangeBuilder(zone()).Build(5, 6);
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitSingleIntervalUsePositions) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 3).AddUse(0).AddUse(2).Build();
LiveRange* child = Split(range, 1);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 1).AddUse(0).Build();
LiveRange* expected_bottom =
TestRangeBuilder(zone()).Add(1, 3).AddUse(2).Build();
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitSingleIntervalUsePositionsAtPos) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 3).AddUse(0).AddUse(2).Build();
LiveRange* child = Split(range, 2);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 2).AddUse(0).AddUse(2).Build();
LiveRange* expected_bottom = TestRangeBuilder(zone()).Build(2, 3);
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalUsePositionsBetween) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).AddUse(1).AddUse(5).Build();
LiveRange* child = Split(range, 3);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 2).AddUse(1).Build();
LiveRange* expected_bottom =
TestRangeBuilder(zone()).Add(4, 6).AddUse(5).Build();
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalUsePositionsAtInterval) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).AddUse(1).AddUse(4).Build();
LiveRange* child = Split(range, 4);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 2).AddUse(1).Build();
LiveRange* expected_bottom =
TestRangeBuilder(zone()).Add(4, 6).AddUse(4).Build();
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalUsePositionsFront) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).AddUse(1).AddUse(5).Build();
LiveRange* child = Split(range, 1);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 1).AddUse(1).Build();
LiveRange* expected_bottom =
TestRangeBuilder(zone()).Add(1, 2).Add(4, 6).AddUse(5).Build();
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
TEST_F(LiveRangeUnitTest, SplitManyIntervalUsePositionsAfter) {
TopLevelLiveRange* range =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 6).AddUse(1).AddUse(5).Build();
LiveRange* child = Split(range, 5);
EXPECT_NE(nullptr, range->next());
EXPECT_EQ(child, range->next());
LiveRange* expected_top =
TestRangeBuilder(zone()).Add(0, 2).Add(4, 5).AddUse(1).AddUse(5).Build();
LiveRange* expected_bottom = TestRangeBuilder(zone()).Build(5, 6);
EXPECT_TRUE(RangesMatch(expected_top, range));
EXPECT_TRUE(RangesMatch(expected_bottom, child));
}
class DoubleEndedSplitVectorTest : public TestWithZone {};
TEST_F(DoubleEndedSplitVectorTest, PushFront) {
DoubleEndedSplitVector<int> vec;
vec.push_front(zone(), 0);
vec.push_front(zone(), 1);
EXPECT_EQ(vec.front(), 1);
EXPECT_EQ(vec.back(), 0);
// Subsequent `push_front` should grow the backing allocation super-linearly.
vec.push_front(zone(), 2);
CHECK_EQ(vec.capacity(), 4);
// As long as there is remaining capacity, `push_front` should not copy or
// reallocate.
int* address_of_0 = &vec.back();
CHECK_EQ(*address_of_0, 0);
vec.push_front(zone(), 3);
EXPECT_EQ(address_of_0, &vec.back());
}
TEST_F(DoubleEndedSplitVectorTest, PopFront) {
DoubleEndedSplitVector<int> vec;
vec.push_front(zone(), 0);
vec.push_front(zone(), 1);
vec.pop_front();
EXPECT_EQ(vec.size(), 1u);
EXPECT_EQ(vec.front(), 0);
}
TEST_F(DoubleEndedSplitVectorTest, Insert) {
DoubleEndedSplitVector<int> vec;
// Inserts with `direction = kFrontOrBack` should not reallocate when
// there is space at either the front or back.
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
vec.insert(zone(), vec.end(), 2);
CHECK_EQ(vec.capacity(), 4);
size_t memory_before = zone()->allocation_size();
vec.insert(zone(), vec.end(), 3);
size_t used_memory = zone()->allocation_size() - memory_before;
EXPECT_EQ(used_memory, 0u);
}
TEST_F(DoubleEndedSplitVectorTest, InsertFront) {
DoubleEndedSplitVector<int> vec;
// Inserts with `direction = kFront` should only copy elements to the left
// of the insert position, if there is space at the front.
vec.insert<kFront>(zone(), vec.begin(), 0);
vec.insert<kFront>(zone(), vec.begin(), 1);
vec.insert<kFront>(zone(), vec.begin(), 2);
int* address_of_0 = &vec.back();
CHECK_EQ(*address_of_0, 0);
vec.insert<kFront>(zone(), vec.begin(), 3);
EXPECT_EQ(address_of_0, &vec.back());
}
TEST_F(DoubleEndedSplitVectorTest, SplitAtBegin) {
DoubleEndedSplitVector<int> vec;
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
vec.insert(zone(), vec.end(), 2);
DoubleEndedSplitVector<int> all_split_begin = vec.SplitAt(vec.begin());
EXPECT_EQ(all_split_begin.size(), 3u);
EXPECT_EQ(vec.size(), 0u);
}
TEST_F(DoubleEndedSplitVectorTest, SplitAtEnd) {
DoubleEndedSplitVector<int> vec;
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
vec.insert(zone(), vec.end(), 2);
DoubleEndedSplitVector<int> empty_split_end = vec.SplitAt(vec.end());
EXPECT_EQ(empty_split_end.size(), 0u);
EXPECT_EQ(vec.size(), 3u);
}
TEST_F(DoubleEndedSplitVectorTest, SplitAtMiddle) {
DoubleEndedSplitVector<int> vec;
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
vec.insert(zone(), vec.end(), 2);
DoubleEndedSplitVector<int> split_off = vec.SplitAt(vec.begin() + 1);
EXPECT_EQ(split_off.size(), 2u);
EXPECT_EQ(split_off[0], 1);
EXPECT_EQ(split_off[1], 2);
EXPECT_EQ(vec.size(), 1u);
EXPECT_EQ(vec[0], 0);
}
TEST_F(DoubleEndedSplitVectorTest, AppendCheap) {
DoubleEndedSplitVector<int> vec;
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
vec.insert(zone(), vec.end(), 2);
DoubleEndedSplitVector<int> split_off = vec.SplitAt(vec.begin() + 1);
// `Append`s of just split vectors should not allocate.
size_t memory_before = zone()->allocation_size();
vec.Append(zone(), split_off);
size_t used_memory = zone()->allocation_size() - memory_before;
EXPECT_EQ(used_memory, 0u);
EXPECT_EQ(vec[0], 0);
EXPECT_EQ(vec[1], 1);
EXPECT_EQ(vec[2], 2);
}
TEST_F(DoubleEndedSplitVectorTest, AppendGeneralCase) {
DoubleEndedSplitVector<int> vec;
vec.insert(zone(), vec.end(), 0);
vec.insert(zone(), vec.end(), 1);
DoubleEndedSplitVector<int> other;
other.insert(zone(), other.end(), 2);
// May allocate.
vec.Append(zone(), other);
EXPECT_EQ(vec[0], 0);
EXPECT_EQ(vec[1], 1);
EXPECT_EQ(vec[2], 2);
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,384 @@
// Copyright 2014 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/backend/move-optimizer.h"
#include "src/utils/ostreams.h"
#include "test/unittests/compiler/backend/instruction-sequence-unittest.h"
namespace v8 {
namespace internal {
namespace compiler {
class MoveOptimizerTest : public InstructionSequenceTest {
public:
// FP register indices which don't interfere under simple or complex aliasing.
static const int kF64_1 = 0;
static const int kF64_2 = 1;
static const int kF32_1 = 4;
static const int kF32_2 = 5;
static const int kS128_1 = 2;
static const int kS128_2 = 3;
Instruction* LastInstruction() { return sequence()->instructions().back(); }
void AddMove(Instruction* instr, TestOperand from, TestOperand to,
Instruction::GapPosition pos = Instruction::START) {
auto parallel_move = instr->GetOrCreateParallelMove(pos, zone());
parallel_move->AddMove(ConvertMoveArg(from), ConvertMoveArg(to));
}
int NonRedundantSize(ParallelMove* moves) {
int i = 0;
for (auto move : *moves) {
if (move->IsRedundant()) continue;
i++;
}
return i;
}
bool Contains(ParallelMove* moves, TestOperand from_op, TestOperand to_op) {
auto from = ConvertMoveArg(from_op);
auto to = ConvertMoveArg(to_op);
for (auto move : *moves) {
if (move->IsRedundant()) continue;
if (move->source().Equals(from) && move->destination().Equals(to)) {
return true;
}
}
return false;
}
// TODO(dcarney): add a verifier.
void Optimize() {
WireBlocks();
if (v8_flags.trace_turbo) {
StdoutStream{}
<< "----- Instruction sequence before move optimization -----\n"
<< *sequence();
}
MoveOptimizer move_optimizer(zone(), sequence());
move_optimizer.Run();
if (v8_flags.trace_turbo) {
StdoutStream{}
<< "----- Instruction sequence after move optimization -----\n"
<< *sequence();
}
}
private:
bool DoesRegisterAllocation() const override { return false; }
InstructionOperand ConvertMoveArg(TestOperand op) {
CHECK_EQ(kNoValue, op.vreg_.value_);
CHECK_NE(kNoValue, op.value_);
switch (op.type_) {
case kConstant:
return ConstantOperand(op.value_);
case kFixedSlot:
return AllocatedOperand(LocationOperand::STACK_SLOT,
MachineRepresentation::kWord32, op.value_);
case kFixedRegister: {
MachineRepresentation rep = GetCanonicalRep(op);
CHECK(0 <= op.value_ && op.value_ < GetNumRegs(rep));
return AllocatedOperand(LocationOperand::REGISTER, rep, op.value_);
}
default:
break;
}
UNREACHABLE();
}
};
TEST_F(MoveOptimizerTest, RemovesRedundant) {
StartBlock();
auto first_instr = EmitNop();
auto last_instr = EmitNop();
AddMove(first_instr, Reg(0), Reg(1));
AddMove(last_instr, Reg(1), Reg(0));
AddMove(first_instr, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128));
AddMove(last_instr, FPReg(kS128_2, kSimd128), FPReg(kS128_1, kSimd128));
AddMove(first_instr, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64));
AddMove(last_instr, FPReg(kF64_2, kFloat64), FPReg(kF64_1, kFloat64));
AddMove(first_instr, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32));
AddMove(last_instr, FPReg(kF32_2, kFloat32), FPReg(kF32_1, kFloat32));
EndBlock(Last());
Optimize();
CHECK_EQ(0, NonRedundantSize(first_instr->parallel_moves()[0]));
auto move = last_instr->parallel_moves()[0];
CHECK_EQ(4, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Reg(1)));
CHECK(Contains(move, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128)));
CHECK(Contains(move, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64)));
CHECK(Contains(move, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32)));
}
TEST_F(MoveOptimizerTest, SplitsConstants) {
StartBlock();
EndBlock(Last());
auto gap = LastInstruction();
AddMove(gap, Const(1), Slot(0));
AddMove(gap, Const(1), Slot(1));
AddMove(gap, Const(1), Reg(0));
AddMove(gap, Const(1), Slot(2));
Optimize();
auto move = gap->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(move));
CHECK(Contains(move, Const(1), Reg(0)));
move = gap->parallel_moves()[1];
CHECK_EQ(3, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Slot(0)));
CHECK(Contains(move, Reg(0), Slot(1)));
CHECK(Contains(move, Reg(0), Slot(2)));
}
TEST_F(MoveOptimizerTest, SimpleMerge) {
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
EndBlock(Jump(2));
AddMove(LastInstruction(), Reg(0), Reg(1));
AddMove(LastInstruction(), FPReg(kS128_1, kSimd128),
FPReg(kS128_2, kSimd128));
AddMove(LastInstruction(), FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64));
AddMove(LastInstruction(), FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32));
StartBlock();
EndBlock(Jump(1));
AddMove(LastInstruction(), Reg(0), Reg(1));
AddMove(LastInstruction(), FPReg(kS128_1, kSimd128),
FPReg(kS128_2, kSimd128));
AddMove(LastInstruction(), FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64));
AddMove(LastInstruction(), FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32));
StartBlock();
EndBlock(Last());
auto last = LastInstruction();
Optimize();
auto move = last->parallel_moves()[0];
CHECK_EQ(4, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Reg(1)));
CHECK(Contains(move, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128)));
CHECK(Contains(move, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64)));
CHECK(Contains(move, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32)));
}
TEST_F(MoveOptimizerTest, SimpleMergeCycle) {
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
EndBlock(Jump(2));
auto gap_0 = LastInstruction();
AddMove(gap_0, Reg(0), Reg(1));
AddMove(LastInstruction(), Reg(1), Reg(0));
AddMove(gap_0, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128));
AddMove(LastInstruction(), FPReg(kS128_2, kSimd128),
FPReg(kS128_1, kSimd128));
AddMove(gap_0, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64));
AddMove(LastInstruction(), FPReg(kF64_2, kFloat64), FPReg(kF64_1, kFloat64));
AddMove(gap_0, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32));
AddMove(LastInstruction(), FPReg(kF32_2, kFloat32), FPReg(kF32_1, kFloat32));
StartBlock();
EndBlock(Jump(1));
auto gap_1 = LastInstruction();
AddMove(gap_1, Reg(0), Reg(1));
AddMove(gap_1, Reg(1), Reg(0));
AddMove(gap_1, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128));
AddMove(gap_1, FPReg(kS128_2, kSimd128), FPReg(kS128_1, kSimd128));
AddMove(gap_1, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64));
AddMove(gap_1, FPReg(kF64_2, kFloat64), FPReg(kF64_1, kFloat64));
AddMove(gap_1, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32));
AddMove(gap_1, FPReg(kF32_2, kFloat32), FPReg(kF32_1, kFloat32));
StartBlock();
EndBlock(Last());
auto last = LastInstruction();
Optimize();
CHECK(gap_0->AreMovesRedundant());
CHECK(gap_1->AreMovesRedundant());
auto move = last->parallel_moves()[0];
CHECK_EQ(8, NonRedundantSize(move));
CHECK(Contains(move, Reg(0), Reg(1)));
CHECK(Contains(move, Reg(1), Reg(0)));
CHECK(Contains(move, FPReg(kS128_1, kSimd128), FPReg(kS128_2, kSimd128)));
CHECK(Contains(move, FPReg(kS128_2, kSimd128), FPReg(kS128_1, kSimd128)));
CHECK(Contains(move, FPReg(kF64_1, kFloat64), FPReg(kF64_2, kFloat64)));
CHECK(Contains(move, FPReg(kF64_2, kFloat64), FPReg(kF64_1, kFloat64)));
CHECK(Contains(move, FPReg(kF32_1, kFloat32), FPReg(kF32_2, kFloat32)));
CHECK(Contains(move, FPReg(kF32_2, kFloat32), FPReg(kF32_1, kFloat32)));
}
TEST_F(MoveOptimizerTest, GapsCanMoveOverInstruction) {
StartBlock();
int const_index = 1;
DefineConstant(const_index);
Instruction* ctant_def = LastInstruction();
AddMove(ctant_def, Reg(1), Reg(0));
Instruction* last = EmitNop();
AddMove(last, Const(const_index), Reg(0));
AddMove(last, Reg(0), Reg(1));
EndBlock(Last());
Optimize();
ParallelMove* inst1_start =
ctant_def->GetParallelMove(Instruction::GapPosition::START);
ParallelMove* inst1_end =
ctant_def->GetParallelMove(Instruction::GapPosition::END);
ParallelMove* last_start =
last->GetParallelMove(Instruction::GapPosition::START);
CHECK(inst1_start == nullptr || NonRedundantSize(inst1_start) == 0);
CHECK(inst1_end == nullptr || NonRedundantSize(inst1_end) == 0);
CHECK_EQ(2, last_start->size());
int redundants = 0;
int assignment = 0;
for (MoveOperands* move : *last_start) {
if (move->IsRedundant()) {
++redundants;
} else {
++assignment;
CHECK(move->destination().IsRegister());
CHECK(move->source().IsConstant());
}
}
CHECK_EQ(1, redundants);
CHECK_EQ(1, assignment);
}
TEST_F(MoveOptimizerTest, SubsetMovesMerge) {
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
EndBlock(Jump(2));
Instruction* last_move_b1 = LastInstruction();
AddMove(last_move_b1, Reg(0), Reg(1));
AddMove(last_move_b1, Reg(2), Reg(3));
StartBlock();
EndBlock(Jump(1));
Instruction* last_move_b2 = LastInstruction();
AddMove(last_move_b2, Reg(0), Reg(1));
AddMove(last_move_b2, Reg(4), Reg(5));
StartBlock();
EndBlock(Last());
Instruction* last = LastInstruction();
Optimize();
ParallelMove* last_move = last->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(last_move));
CHECK(Contains(last_move, Reg(0), Reg(1)));
ParallelMove* b1_move = last_move_b1->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(b1_move));
CHECK(Contains(b1_move, Reg(2), Reg(3)));
ParallelMove* b2_move = last_move_b2->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(b2_move));
CHECK(Contains(b2_move, Reg(4), Reg(5)));
}
TEST_F(MoveOptimizerTest, GapConflictSubsetMovesDoNotMerge) {
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
EndBlock(Jump(2));
Instruction* last_move_b1 = LastInstruction();
AddMove(last_move_b1, Reg(0), Reg(1));
AddMove(last_move_b1, Reg(2), Reg(0));
AddMove(last_move_b1, Reg(4), Reg(5));
StartBlock();
EndBlock(Jump(1));
Instruction* last_move_b2 = LastInstruction();
AddMove(last_move_b2, Reg(0), Reg(1));
AddMove(last_move_b2, Reg(4), Reg(5));
StartBlock();
EndBlock(Last());
Instruction* last = LastInstruction();
Optimize();
ParallelMove* last_move = last->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(last_move));
CHECK(Contains(last_move, Reg(4), Reg(5)));
ParallelMove* b1_move = last_move_b1->parallel_moves()[0];
CHECK_EQ(2, NonRedundantSize(b1_move));
CHECK(Contains(b1_move, Reg(0), Reg(1)));
CHECK(Contains(b1_move, Reg(2), Reg(0)));
ParallelMove* b2_move = last_move_b2->parallel_moves()[0];
CHECK_EQ(1, NonRedundantSize(b2_move));
CHECK(Contains(b1_move, Reg(0), Reg(1)));
}
TEST_F(MoveOptimizerTest, ClobberedDestinationsAreEliminated) {
StartBlock();
EmitNop();
Instruction* first_instr = LastInstruction();
AddMove(first_instr, Reg(0), Reg(1));
EmitOI(Reg(1), 0, nullptr);
Instruction* last_instr = LastInstruction();
EndBlock();
Optimize();
ParallelMove* first_move = first_instr->parallel_moves()[0];
CHECK_EQ(0, NonRedundantSize(first_move));
ParallelMove* last_move = last_instr->parallel_moves()[0];
CHECK_EQ(0, NonRedundantSize(last_move));
}
TEST_F(MoveOptimizerTest, ClobberedFPDestinationsAreEliminated) {
StartBlock();
EmitNop();
Instruction* first_instr = LastInstruction();
AddMove(first_instr, FPReg(4, kFloat64), FPReg(1, kFloat64));
if (kFPAliasing == AliasingKind::kCombine) {
// We clobber q0 below. This is aliased by d0, d1, s0, s1, s2, and s3.
// Add moves to registers s2 and s3.
AddMove(first_instr, FPReg(10, kFloat32), FPReg(0, kFloat32));
AddMove(first_instr, FPReg(11, kFloat32), FPReg(1, kFloat32));
}
// Clobbers output register 0.
EmitOI(FPReg(0, kSimd128), 0, nullptr);
Instruction* last_instr = LastInstruction();
EndBlock();
Optimize();
ParallelMove* first_move = first_instr->parallel_moves()[0];
CHECK_EQ(0, NonRedundantSize(first_move));
ParallelMove* last_move = last_instr->parallel_moves()[0];
CHECK_EQ(0, NonRedundantSize(last_move));
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,794 @@
// Copyright 2014 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/codegen/assembler-inl.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/turboshaft/pipelines.h"
#include "test/unittests/compiler/backend/instruction-sequence-unittest.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
// We can't just use the size of the moves collection, because of
// redundant moves which need to be discounted.
int GetMoveCount(const ParallelMove& moves) {
int move_count = 0;
for (auto move : moves) {
if (move->IsEliminated() || move->IsRedundant()) continue;
++move_count;
}
return move_count;
}
bool AreOperandsOfSameType(
const AllocatedOperand& op,
const InstructionSequenceTest::TestOperand& test_op) {
bool test_op_is_reg =
(test_op.type_ ==
InstructionSequenceTest::TestOperandType::kFixedRegister ||
test_op.type_ == InstructionSequenceTest::TestOperandType::kRegister);
return (op.IsRegister() && test_op_is_reg) ||
(op.IsStackSlot() && !test_op_is_reg);
}
bool AllocatedOperandMatches(
const AllocatedOperand& op,
const InstructionSequenceTest::TestOperand& test_op) {
return AreOperandsOfSameType(op, test_op) &&
((op.IsRegister() ? op.GetRegister().code() : op.index()) ==
test_op.value_ ||
test_op.value_ == InstructionSequenceTest::kNoValue);
}
int GetParallelMoveCount(int instr_index, Instruction::GapPosition gap_pos,
const InstructionSequence* sequence) {
const ParallelMove* moves =
sequence->InstructionAt(instr_index)->GetParallelMove(gap_pos);
if (moves == nullptr) return 0;
return GetMoveCount(*moves);
}
bool IsParallelMovePresent(int instr_index, Instruction::GapPosition gap_pos,
const InstructionSequence* sequence,
const InstructionSequenceTest::TestOperand& src,
const InstructionSequenceTest::TestOperand& dest) {
const ParallelMove* moves =
sequence->InstructionAt(instr_index)->GetParallelMove(gap_pos);
EXPECT_NE(nullptr, moves);
bool found_match = false;
for (auto move : *moves) {
if (move->IsEliminated() || move->IsRedundant()) continue;
if (AllocatedOperandMatches(AllocatedOperand::cast(move->source()), src) &&
AllocatedOperandMatches(AllocatedOperand::cast(move->destination()),
dest)) {
found_match = true;
break;
}
}
return found_match;
}
class RegisterAllocatorTest : public InstructionSequenceTest {
public:
void Allocate() {
WireBlocks();
InstructionSequence* sequence = this->sequence();
OptimizedCompilationInfo info(base::ArrayVector("testing"),
sequence->zone(), CodeKind::FOR_TESTING);
ZoneStats zone_stats(sequence->isolate()->allocator());
turboshaft::PipelineData data(
&zone_stats, turboshaft::TurboshaftPipelineKind::kCSA, nullptr, &info,
AssemblerOptions(sequence->isolate()));
data.InitializeCodegenComponent(nullptr);
data.InitializeFrameData(nullptr);
data.InitializeInstructionComponentWithSequence(sequence);
if (info.trace_turbo_json()) {
TurboJsonFile json_of(&info, std::ios_base::trunc);
json_of << "{\"function\":\"" << info.GetDebugName().get()
<< "\", \"source\":\"\",\n\"phases\":[";
}
turboshaft::Pipeline pipeline(&data);
pipeline.AllocateRegisters(config(), nullptr, true);
}
};
TEST_F(RegisterAllocatorTest, CanAllocateThreeRegisters) {
// return p0 + p1;
StartBlock();
auto a_reg = Parameter();
auto b_reg = Parameter();
auto c_reg = EmitOI(Reg(1), Reg(a_reg, 1), Reg(b_reg, 0));
Return(c_reg);
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, CanAllocateFPRegisters) {
StartBlock();
TestOperand inputs[] = {
Reg(FPParameter(kFloat64)), Reg(FPParameter(kFloat64)),
Reg(FPParameter(kFloat32)), Reg(FPParameter(kFloat32)),
Reg(FPParameter(kSimd128)), Reg(FPParameter(kSimd128))};
VReg out1 = EmitOI(FPReg(1, kFloat64), arraysize(inputs), inputs);
Return(out1);
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, SimpleLoop) {
// i = K;
// while(true) { i++ }
StartBlock();
auto i_reg = DefineConstant();
// Add a branch around the loop to ensure the end-block
// is connected.
EndBlock(Branch(Reg(DefineConstant()), 3, 1));
StartBlock();
EndBlock();
{
StartLoop(1);
StartBlock();
auto phi = Phi(i_reg, 2);
auto ipp = EmitOI(Same(), Reg(phi), Use(DefineConstant()));
SetInput(phi, 1, ipp);
EndBlock(Jump(0));
EndLoop();
}
StartBlock();
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, SimpleBranch) {
// return i ? K1 : K2
StartBlock();
auto i = DefineConstant();
EndBlock(Branch(Reg(i), 1, 2));
StartBlock();
Return(DefineConstant());
EndBlock(Last());
StartBlock();
Return(DefineConstant());
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, SimpleDiamond) {
// return p0 ? p0 : p0
StartBlock();
auto param = Parameter();
EndBlock(Branch(Reg(param), 1, 2));
StartBlock();
EndBlock(Jump(2));
StartBlock();
EndBlock(Jump(1));
StartBlock();
Return(param);
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, SimpleDiamondPhi) {
// return i ? K1 : K2
StartBlock();
EndBlock(Branch(Reg(DefineConstant()), 1, 2));
StartBlock();
auto t_val = DefineConstant();
EndBlock(Jump(2));
StartBlock();
auto f_val = DefineConstant();
EndBlock(Jump(1));
StartBlock();
Return(Reg(Phi(t_val, f_val)));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, DiamondManyPhis) {
constexpr int kPhis = Register::kNumRegisters * 2;
StartBlock();
EndBlock(Branch(Reg(DefineConstant()), 1, 2));
StartBlock();
VReg t_vals[kPhis];
for (int i = 0; i < kPhis; ++i) {
t_vals[i] = DefineConstant();
}
EndBlock(Jump(2));
StartBlock();
VReg f_vals[kPhis];
for (int i = 0; i < kPhis; ++i) {
f_vals[i] = DefineConstant();
}
EndBlock(Jump(1));
StartBlock();
TestOperand merged[kPhis];
for (int i = 0; i < kPhis; ++i) {
merged[i] = Use(Phi(t_vals[i], f_vals[i]));
}
Return(EmitCall(Slot(-1), kPhis, merged));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, DoubleDiamondManyRedundantPhis) {
constexpr int kPhis = Register::kNumRegisters * 2;
// First diamond.
StartBlock();
VReg vals[kPhis];
for (int i = 0; i < kPhis; ++i) {
vals[i] = Parameter(Slot(-1 - i));
}
EndBlock(Branch(Reg(DefineConstant()), 1, 2));
StartBlock();
EndBlock(Jump(2));
StartBlock();
EndBlock(Jump(1));
// Second diamond.
StartBlock();
EndBlock(Branch(Reg(DefineConstant()), 1, 2));
StartBlock();
EndBlock(Jump(2));
StartBlock();
EndBlock(Jump(1));
StartBlock();
TestOperand merged[kPhis];
for (int i = 0; i < kPhis; ++i) {
merged[i] = Use(Phi(vals[i], vals[i]));
}
Return(EmitCall(Reg(0), kPhis, merged));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, RegressionPhisNeedTooManyRegisters) {
const size_t kNumRegs = 3;
const size_t kParams = kNumRegs + 1;
// Override number of registers.
SetNumRegs(kNumRegs, kNumRegs);
StartBlock();
auto constant = DefineConstant();
VReg parameters[kParams];
for (size_t i = 0; i < arraysize(parameters); ++i) {
parameters[i] = DefineConstant();
}
EndBlock();
PhiInstruction* phis[kParams];
{
StartLoop(2);
// Loop header.
StartBlock();
for (size_t i = 0; i < arraysize(parameters); ++i) {
phis[i] = Phi(parameters[i], 2);
}
// Perform some computations.
// something like phi[i] += const
for (size_t i = 0; i < arraysize(parameters); ++i) {
auto result = EmitOI(Same(), Reg(phis[i]), Use(constant));
SetInput(phis[i], 1, result);
}
EndBlock(Branch(Reg(DefineConstant()), 1, 2));
// Jump back to loop header.
StartBlock();
EndBlock(Jump(-1));
EndLoop();
}
StartBlock();
Return(DefineConstant());
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, SpillPhi) {
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
auto left = Define(Reg(0));
EndBlock(Jump(2));
StartBlock();
auto right = Define(Reg(0));
EndBlock();
StartBlock();
auto phi = Phi(left, right);
EmitCall(Slot(-1));
Return(Reg(phi));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, MoveLotsOfConstants) {
StartBlock();
VReg constants[Register::kNumRegisters];
for (size_t i = 0; i < arraysize(constants); ++i) {
constants[i] = DefineConstant();
}
TestOperand call_ops[Register::kNumRegisters * 2];
for (int i = 0; i < Register::kNumRegisters; ++i) {
call_ops[i] = Reg(constants[i], i);
}
for (int i = 0; i < Register::kNumRegisters; ++i) {
call_ops[i + Register::kNumRegisters] = Slot(constants[i], i);
}
EmitCall(Slot(-1), arraysize(call_ops), call_ops);
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, SplitBeforeInstruction) {
const int kNumRegs = 6;
SetNumRegs(kNumRegs, kNumRegs);
StartBlock();
// Stack parameters/spilled values.
auto p_0 = Define(Slot(-1));
auto p_1 = Define(Slot(-2));
// Fill registers.
VReg values[kNumRegs];
for (size_t i = 0; i < arraysize(values); ++i) {
values[i] = Define(Reg(static_cast<int>(i)));
}
// values[0] will be split in the second half of this instruction.
// Models Intel mod instructions.
EmitOI(Reg(0), Reg(p_0, 1), UniqueReg(p_1));
EmitI(Reg(values[0], 0));
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, SplitBeforeInstruction2) {
const int kNumRegs = 6;
SetNumRegs(kNumRegs, kNumRegs);
StartBlock();
// Stack parameters/spilled values.
auto p_0 = Define(Slot(-1));
auto p_1 = Define(Slot(-2));
// Fill registers.
VReg values[kNumRegs];
for (size_t i = 0; i < arraysize(values); ++i) {
values[i] = Define(Reg(static_cast<int>(i)));
}
// values[0] and [1] will be split in the second half of this instruction.
EmitOOI(Reg(0), Reg(1), Reg(p_0, 0), Reg(p_1, 1));
EmitI(Reg(values[0]), Reg(values[1]));
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, NestedDiamondPhiMerge) {
// Outer diamond.
StartBlock();
EndBlock(Branch(Imm(), 1, 5));
// Diamond 1
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
auto ll = Define(Reg());
EndBlock(Jump(2));
StartBlock();
auto lr = Define(Reg());
EndBlock();
StartBlock();
auto l_phi = Phi(ll, lr);
EndBlock(Jump(5));
// Diamond 2
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
auto rl = Define(Reg());
EndBlock(Jump(2));
StartBlock();
auto rr = Define(Reg());
EndBlock();
StartBlock();
auto r_phi = Phi(rl, rr);
EndBlock();
// Outer diamond merge.
StartBlock();
auto phi = Phi(l_phi, r_phi);
Return(Reg(phi));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, NestedDiamondPhiMergeDifferent) {
// Outer diamond.
StartBlock();
EndBlock(Branch(Imm(), 1, 5));
// Diamond 1
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
auto ll = Define(Reg(0));
EndBlock(Jump(2));
StartBlock();
auto lr = Define(Reg(1));
EndBlock();
StartBlock();
auto l_phi = Phi(ll, lr);
EndBlock(Jump(5));
// Diamond 2
StartBlock();
EndBlock(Branch(Imm(), 1, 2));
StartBlock();
auto rl = Define(Reg(2));
EndBlock(Jump(2));
StartBlock();
auto rr = Define(Reg(3));
EndBlock();
StartBlock();
auto r_phi = Phi(rl, rr);
EndBlock();
// Outer diamond merge.
StartBlock();
auto phi = Phi(l_phi, r_phi);
Return(Reg(phi));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, RegressionSplitBeforeAndMove) {
StartBlock();
// Fill registers.
VReg values[Register::kNumRegisters];
for (size_t i = 0; i < arraysize(values); ++i) {
if (i == 0 || i == 1) continue; // Leave a hole for c_1 to take.
values[i] = Define(Reg(static_cast<int>(i)));
}
auto c_0 = DefineConstant();
auto c_1 = DefineConstant();
EmitOI(Reg(1), Reg(c_0, 0), UniqueReg(c_1));
// Use previous values to force c_1 to split before the previous instruction.
for (size_t i = 0; i < arraysize(values); ++i) {
if (i == 0 || i == 1) continue;
EmitI(Reg(values[i], static_cast<int>(i)));
}
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, RegressionSpillTwice) {
StartBlock();
auto p_0 = Parameter(Reg(1));
EmitCall(Slot(-2), Unique(p_0), Reg(p_0, 1));
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, RegressionLoadConstantBeforeSpill) {
StartBlock();
// Fill registers.
VReg values[Register::kNumRegisters];
for (size_t i = arraysize(values); i > 0; --i) {
values[i - 1] = Define(Reg(static_cast<int>(i - 1)));
}
auto c = DefineConstant();
auto to_spill = Define(Reg());
EndBlock(Jump(1));
{
StartLoop(1);
StartBlock();
// Create a use for c in second half of prev block's last gap
Phi(c);
for (size_t i = arraysize(values); i > 0; --i) {
Phi(values[i - 1]);
}
EndBlock(Jump(1));
EndLoop();
}
StartBlock();
// Force c to split within to_spill's definition.
EmitI(Reg(c));
EmitI(Reg(to_spill));
EndBlock(Last());
Allocate();
}
TEST_F(RegisterAllocatorTest, DiamondWithCallFirstBlock) {
StartBlock();
auto x = EmitOI(Reg(0));
EndBlock(Branch(Reg(x), 1, 2));
StartBlock();
EmitCall(Slot(-1));
auto occupy = EmitOI(Reg(0));
EndBlock(Jump(2));
StartBlock();
EndBlock(FallThrough());
StartBlock();
Use(occupy);
Return(Reg(x));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, DiamondWithCallSecondBlock) {
StartBlock();
auto x = EmitOI(Reg(0));
EndBlock(Branch(Reg(x), 1, 2));
StartBlock();
EndBlock(Jump(2));
StartBlock();
EmitCall(Slot(-1));
auto occupy = EmitOI(Reg(0));
EndBlock(FallThrough());
StartBlock();
Use(occupy);
Return(Reg(x));
EndBlock();
Allocate();
}
TEST_F(RegisterAllocatorTest, SingleDeferredBlockSpill) {
StartBlock(); // B0
auto var = EmitOI(Reg(0));
EndBlock(Branch(Reg(var), 1, 2));
StartBlock(); // B1
EndBlock(Jump(2));
StartBlock(true); // B2
EmitCall(Slot(-1), Slot(var));
EndBlock();
StartBlock(); // B3
EmitNop();
EndBlock();
StartBlock(); // B4
Return(Reg(var, 0));
EndBlock();
Allocate();
const int var_def_index = 1;
const int call_index = 3;
// We should have no parallel moves at the "var_def_index" position.
EXPECT_EQ(
0, GetParallelMoveCount(var_def_index, Instruction::START, sequence()));
// The spill should be performed at the position "call_index".
EXPECT_TRUE(IsParallelMovePresent(call_index, Instruction::START, sequence(),
Reg(0), Slot(0)));
}
TEST_F(RegisterAllocatorTest, ValidMultipleDeferredBlockSpills) {
StartBlock(); // B0
auto var1 = EmitOI(Reg(0));
auto var2 = EmitOI(Reg(1));
auto var3 = EmitOI(Reg(2));
EndBlock(Branch(Reg(var1, 0), 1, 2));
StartBlock(true); // B1
EmitCall(Slot(-2), Slot(var1));
EndBlock(Jump(5));
StartBlock(); // B2
EmitNop();
EndBlock();
StartBlock(); // B3
EmitNop();
EndBlock(Branch(Reg(var2, 0), 1, 2));
StartBlock(true); // B4
EmitCall(Slot(-1), Slot(var2));
EndBlock(Jump(2));
StartBlock(); // B5
EmitNop();
EndBlock();
StartBlock(); // B6
Return(Reg(var3, 2));
EndBlock();
const int def_of_v2 = 2;
const int call_in_b1 = 4;
const int call_in_b4 = 10;
const int end_of_b1 = 5;
const int end_of_b4 = 11;
const int start_of_b6 = 14;
Allocate();
const int var3_reg = 2;
const int var3_slot = 2;
EXPECT_FALSE(IsParallelMovePresent(def_of_v2, Instruction::START, sequence(),
Reg(var3_reg), Slot()));
EXPECT_TRUE(IsParallelMovePresent(call_in_b1, Instruction::START, sequence(),
Reg(var3_reg), Slot(var3_slot)));
EXPECT_TRUE(IsParallelMovePresent(end_of_b1, Instruction::START, sequence(),
Slot(var3_slot), Reg()));
EXPECT_TRUE(IsParallelMovePresent(call_in_b4, Instruction::START, sequence(),
Reg(var3_reg), Slot(var3_slot)));
EXPECT_TRUE(IsParallelMovePresent(end_of_b4, Instruction::START, sequence(),
Slot(var3_slot), Reg()));
EXPECT_EQ(0,
GetParallelMoveCount(start_of_b6, Instruction::START, sequence()));
}
namespace {
enum class ParameterType { kFixedSlot, kSlot, kRegister, kFixedRegister };
const ParameterType kParameterTypes[] = {
ParameterType::kFixedSlot, ParameterType::kSlot, ParameterType::kRegister,
ParameterType::kFixedRegister};
class SlotConstraintTest : public RegisterAllocatorTest,
public ::testing::WithParamInterface<
::testing::tuple<ParameterType, int>> {
public:
static const int kMaxVariant = 5;
protected:
ParameterType parameter_type() const {
return ::testing::get<0>(B::GetParam());
}
int variant() const { return ::testing::get<1>(B::GetParam()); }
private:
using B = ::testing::WithParamInterface<::testing::tuple<ParameterType, int>>;
};
} // namespace
TEST_P(SlotConstraintTest, SlotConstraint) {
StartBlock();
VReg p_0;
switch (parameter_type()) {
case ParameterType::kFixedSlot:
p_0 = Parameter(Slot(-1));
break;
case ParameterType::kSlot:
p_0 = Parameter(Slot(-1));
break;
case ParameterType::kRegister:
p_0 = Parameter(Reg());
break;
case ParameterType::kFixedRegister:
p_0 = Parameter(Reg(1));
break;
}
switch (variant()) {
case 0:
EmitI(Slot(p_0), Reg(p_0));
break;
case 1:
EmitI(Slot(p_0));
break;
case 2:
EmitI(Reg(p_0));
EmitI(Slot(p_0));
break;
case 3:
EmitI(Slot(p_0));
EmitI(Reg(p_0));
break;
case 4:
EmitI(Slot(p_0, -1), Slot(p_0), Reg(p_0), Reg(p_0, 1));
break;
default:
UNREACHABLE();
}
EndBlock(Last());
Allocate();
}
INSTANTIATE_TEST_SUITE_P(
RegisterAllocatorTest, SlotConstraintTest,
::testing::Combine(::testing::ValuesIn(kParameterTypes),
::testing::Range(0, SlotConstraintTest::kMaxVariant)));
} // namespace
} // namespace compiler
} // namespace internal
} // namespace v8