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,622 @@
// 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 "test/unittests/compiler/backend/instruction-selector-unittest.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/schedule.h"
#include "src/compiler/turbofan-graph.h"
#include "src/flags/flags.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/compiler/compiler-test-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
// TODO(391750831): This needs to be ported to Turboshaft.
#if 0
InstructionSelectorTest::InstructionSelectorTest()
: TestWithNativeContextAndZone(kCompressGraphZone),
rng_(v8_flags.random_seed) {}
InstructionSelectorTest::~InstructionSelectorTest() = default;
InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build(
InstructionSelector::Features features,
InstructionSelectorTest::StreamBuilderMode mode,
InstructionSelector::SourcePositionMode source_position_mode) {
Schedule* schedule = ExportForTest();
if (v8_flags.trace_turbo) {
StdoutStream{} << "=== Schedule before instruction selection ==="
<< std::endl
<< *schedule;
}
size_t const node_count = graph()->NodeCount();
EXPECT_NE(0u, node_count);
Linkage linkage(call_descriptor());
InstructionBlocks* instruction_blocks =
InstructionSequence::InstructionBlocksFor(test_->zone(), schedule);
InstructionSequence sequence(test_->isolate(), test_->zone(),
instruction_blocks);
SourcePositionTable source_position_table(graph());
TickCounter tick_counter;
size_t max_unoptimized_frame_height = 0;
size_t max_pushed_argument_count = 0;
InstructionSelector selector = InstructionSelector::ForTurbofan(
test_->zone(), node_count, &linkage, &sequence, schedule,
&source_position_table, nullptr,
InstructionSelector::kEnableSwitchJumpTable, &tick_counter, nullptr,
&max_unoptimized_frame_height, &max_pushed_argument_count,
source_position_mode, features, InstructionSelector::kDisableScheduling,
InstructionSelector::kEnableRootsRelativeAddressing);
selector.SelectInstructions();
if (v8_flags.trace_turbo) {
StdoutStream{} << "=== Code sequence after instruction selection ==="
<< std::endl
<< sequence;
}
Stream s;
s.virtual_registers_ = selector.GetVirtualRegistersForTesting();
// Map virtual registers.
for (Instruction* const instr : sequence) {
if (instr->opcode() < 0) continue;
if (mode == kTargetInstructions) {
switch (instr->arch_opcode()) {
#define CASE(Name) \
case k##Name: \
break;
TARGET_ARCH_OPCODE_LIST(CASE)
#undef CASE
default:
continue;
}
}
if (mode == kAllExceptNopInstructions && instr->arch_opcode() == kArchNop) {
continue;
}
for (size_t i = 0; i < instr->OutputCount(); ++i) {
InstructionOperand* output = instr->OutputAt(i);
EXPECT_NE(InstructionOperand::IMMEDIATE, output->kind());
if (output->IsConstant()) {
int vreg = ConstantOperand::cast(output)->virtual_register();
s.constants_.insert(std::make_pair(vreg, sequence.GetConstant(vreg)));
}
}
for (size_t i = 0; i < instr->InputCount(); ++i) {
InstructionOperand* input = instr->InputAt(i);
EXPECT_NE(InstructionOperand::CONSTANT, input->kind());
if (input->IsImmediate()) {
auto imm = ImmediateOperand::cast(input);
if (imm->type() == ImmediateOperand::INDEXED_IMM) {
int index = imm->indexed_value();
s.immediates_.insert(
std::make_pair(index, sequence.GetImmediate(imm)));
}
}
}
s.instructions_.push_back(instr);
}
for (auto i : s.virtual_registers_) {
int const virtual_register = i.second;
if (sequence.IsFP(virtual_register)) {
EXPECT_FALSE(sequence.IsReference(virtual_register));
s.doubles_.insert(virtual_register);
}
if (sequence.IsReference(virtual_register)) {
EXPECT_FALSE(sequence.IsFP(virtual_register));
s.references_.insert(virtual_register);
}
}
for (int i = 0; i < sequence.GetDeoptimizationEntryCount(); i++) {
s.deoptimization_entries_.push_back(
sequence.GetDeoptimizationEntry(i).descriptor());
}
return s;
}
int InstructionSelectorTest::Stream::ToVreg(const Node* node) const {
VirtualRegisters::const_iterator i = virtual_registers_.find(node->id());
CHECK(i != virtual_registers_.end());
return i->second;
}
bool InstructionSelectorTest::Stream::IsFixed(const InstructionOperand* operand,
Register reg) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
if (!unallocated->HasFixedRegisterPolicy()) return false;
return unallocated->fixed_register_index() == reg.code();
}
bool InstructionSelectorTest::Stream::IsSameAsFirst(
const InstructionOperand* operand) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->HasSameAsInputPolicy();
}
bool InstructionSelectorTest::Stream::IsSameAsInput(
const InstructionOperand* operand, int input_index) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->HasSameAsInputPolicy() &&
unallocated->input_index() == input_index;
}
bool InstructionSelectorTest::Stream::IsUsedAtStart(
const InstructionOperand* operand) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->IsUsedAtStart();
}
const FrameStateFunctionInfo*
InstructionSelectorTest::StreamBuilder::GetFrameStateFunctionInfo(
uint16_t parameter_count, int local_count) {
const uint16_t max_arguments = 0;
return common()->CreateFrameStateFunctionInfo(
FrameStateType::kUnoptimizedFunction, parameter_count, max_arguments,
local_count, {}, {});
}
// -----------------------------------------------------------------------------
// Return.
TARGET_TEST_F(InstructionSelectorTest, ReturnFloat32Constant) {
const float kValue = 4.2f;
StreamBuilder m(this, MachineType::Float32());
m.Return(m.Float32Constant(kValue));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(3U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(InstructionOperand::CONSTANT, s[0]->OutputAt(0)->kind());
EXPECT_FLOAT_EQ(kValue, s.ToFloat32(s[0]->OutputAt(0)));
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
TARGET_TEST_F(InstructionSelectorTest, ReturnParameter) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32());
m.Return(m.Parameter(0));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(3U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
TARGET_TEST_F(InstructionSelectorTest, ReturnZero) {
StreamBuilder m(this, MachineType::Int32());
m.Return(m.Int32Constant(0));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(3U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(InstructionOperand::CONSTANT, s[0]->OutputAt(0)->kind());
EXPECT_EQ(0, s.ToInt32(s[0]->OutputAt(0)));
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
// -----------------------------------------------------------------------------
// Conversions.
TARGET_TEST_F(InstructionSelectorTest, TruncateFloat64ToWord32WithParameter) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Float64());
m.Return(m.TruncateFloat64ToWord32(m.Parameter(0)));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(4U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
EXPECT_EQ(kArchTruncateDoubleToI, s[1]->arch_opcode());
EXPECT_EQ(1U, s[1]->InputCount());
EXPECT_EQ(1U, s[1]->OutputCount());
EXPECT_EQ(kArchRet, s[2]->arch_opcode());
}
// -----------------------------------------------------------------------------
// Parameters.
TARGET_TEST_F(InstructionSelectorTest, DoubleParameter) {
StreamBuilder m(this, MachineType::Float64(), MachineType::Float64());
Node* param = m.Parameter(0);
m.Return(param);
Stream s = m.Build(kAllInstructions);
EXPECT_TRUE(s.IsDouble(param));
}
TARGET_TEST_F(InstructionSelectorTest, ReferenceParameter) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged());
Node* param = m.Parameter(0);
m.Return(param);
Stream s = m.Build(kAllInstructions);
EXPECT_TRUE(s.IsReference(param));
}
// -----------------------------------------------------------------------------
// FinishRegion.
TARGET_TEST_F(InstructionSelectorTest, FinishRegion) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged());
Node* param = m.Parameter(0);
Node* finish =
m.AddNode(m.common()->FinishRegion(), param, m.graph()->start());
m.Return(finish);
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(3U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(1U, s[0]->OutputCount());
ASSERT_TRUE(s[0]->Output()->IsUnallocated());
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(s.ToVreg(param), s.ToVreg(s[0]->Output()));
EXPECT_EQ(s.ToVreg(param), s.ToVreg(s[1]->InputAt(1)));
EXPECT_TRUE(s.IsReference(finish));
}
// -----------------------------------------------------------------------------
// Phi.
using InstructionSelectorPhiTest =
InstructionSelectorTestWithParam<MachineType>;
TARGET_TEST_P(InstructionSelectorPhiTest, Doubleness) {
const MachineType type = GetParam();
StreamBuilder m(this, type, type, type);
Node* param0 = m.Parameter(0);
Node* param1 = m.Parameter(1);
RawMachineLabel a, b, c;
m.Branch(m.Int32Constant(0), &a, &b);
m.Bind(&a);
m.Goto(&c);
m.Bind(&b);
m.Goto(&c);
m.Bind(&c);
Node* phi = m.Phi(type.representation(), param0, param1);
m.Return(phi);
Stream s = m.Build(kAllInstructions);
EXPECT_EQ(s.IsDouble(phi), s.IsDouble(param0));
EXPECT_EQ(s.IsDouble(phi), s.IsDouble(param1));
}
TARGET_TEST_P(InstructionSelectorPhiTest, Referenceness) {
const MachineType type = GetParam();
StreamBuilder m(this, type, type, type);
Node* param0 = m.Parameter(0);
Node* param1 = m.Parameter(1);
RawMachineLabel a, b, c;
m.Branch(m.Int32Constant(1), &a, &b);
m.Bind(&a);
m.Goto(&c);
m.Bind(&b);
m.Goto(&c);
m.Bind(&c);
Node* phi = m.Phi(type.representation(), param0, param1);
m.Return(phi);
Stream s = m.Build(kAllInstructions);
EXPECT_EQ(s.IsReference(phi), s.IsReference(param0));
EXPECT_EQ(s.IsReference(phi), s.IsReference(param1));
}
INSTANTIATE_TEST_SUITE_P(
InstructionSelectorTest, InstructionSelectorPhiTest,
::testing::Values(MachineType::Float64(), MachineType::Int8(),
MachineType::Uint8(), MachineType::Int16(),
MachineType::Uint16(), MachineType::Int32(),
MachineType::Uint32(), MachineType::Int64(),
MachineType::Uint64(), MachineType::Pointer(),
MachineType::AnyTagged()));
// -----------------------------------------------------------------------------
// ValueEffect.
TARGET_TEST_F(InstructionSelectorTest, ValueEffect) {
StreamBuilder m1(this, MachineType::Int32(), MachineType::Pointer());
Node* p1 = m1.Parameter(0);
m1.Return(m1.Load(MachineType::Int32(), p1, m1.Int32Constant(0)));
Stream s1 = m1.Build(kAllInstructions);
StreamBuilder m2(this, MachineType::Int32(), MachineType::Pointer());
Node* p2 = m2.Parameter(0);
m2.Return(m2.AddNode(
m2.machine()->Load(MachineType::Int32()), p2, m2.Int32Constant(0),
m2.AddNode(m2.common()->BeginRegion(RegionObservability::kObservable),
m2.graph()->start())));
Stream s2 = m2.Build(kAllInstructions);
EXPECT_LE(3U, s1.size());
ASSERT_EQ(s1.size(), s2.size());
TRACED_FORRANGE(size_t, i, 0, s1.size() - 1) {
const Instruction* i1 = s1[i];
const Instruction* i2 = s2[i];
EXPECT_EQ(i1->arch_opcode(), i2->arch_opcode());
EXPECT_EQ(i1->InputCount(), i2->InputCount());
EXPECT_EQ(i1->OutputCount(), i2->OutputCount());
}
}
// -----------------------------------------------------------------------------
// Calls with deoptimization.
TARGET_TEST_F(InstructionSelectorTest, CallJSFunctionWithDeopt) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id(42);
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Parameter(2);
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> tagged_type(1, MachineType::AnyTagged(), zone());
ZoneVector<MachineType> empty_type(zone());
auto call_descriptor = Linkage::GetJSCallDescriptor(
zone(), false, 1,
CallDescriptor::kNeedsFrameState | CallDescriptor::kCanUseRoots);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(1));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&empty_type, SparseInputMask::Dense()));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&tagged_type, SparseInputMask::Dense()),
m.UndefinedConstant());
Node* context_sentinel = m.Int32Constant(0);
Node* state_node = m.AddNode(
m.common()->FrameState(bailout_id, OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 0)),
parameters, locals, stack, context_sentinel, function_node,
m.graph()->start());
// Build the call.
Node* argc = m.Int32Constant(1);
#ifdef V8_JS_LINKAGE_INCLUDES_DISPATCH_HANDLE
Node* dispatch_handle = m.Int32Constant(-1);
Node* nodes[] = {function_node, receiver, m.UndefinedConstant(),
argc, dispatch_handle, context,
state_node};
#else
Node* nodes[] = {function_node, receiver, m.UndefinedConstant(),
argc, context, state_node};
#endif
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallJSFunction.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallJSFunction;
index++) {
}
// Now we should have two instructions: call and return.
ASSERT_EQ(index + 2, s.size());
EXPECT_EQ(kArchCallJSFunction, s[index++]->arch_opcode());
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
// TODO(jarin) Check deoptimization table.
}
TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeopt) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id_before(42);
// Some arguments for the call node.
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Int32Constant(1); // Context is ignored.
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> float64_type(1, MachineType::Float64(), zone());
ZoneVector<MachineType> tagged_type(1, MachineType::AnyTagged(), zone());
Callable callable = Builtins::CallableFor(isolate(), Builtin::kToObject);
auto call_descriptor = Linkage::GetStubCallDescriptor(
zone(), callable.descriptor(), 1, CallDescriptor::kNeedsFrameState,
Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.5));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&tagged_type, SparseInputMask::Dense()),
m.UndefinedConstant());
Node* context_sentinel = m.Int32Constant(0);
Node* state_node =
m.AddNode(m.common()->FrameState(bailout_id_before,
OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 1)),
parameters, locals, stack, context_sentinel, function_node,
m.graph()->start());
// Build the call.
Node* stub_code = m.HeapConstant(callable.code());
Node* nodes[] = {stub_code, function_node, receiver, context, state_node};
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallCodeObject.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject;
index++) {
}
// Now we should have two instructions: call, return.
ASSERT_EQ(index + 2, s.size());
// Check the call instruction
const Instruction* call_instr = s[index++];
EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode());
size_t num_operands =
1 + // Code object.
6 + // Frame state deopt id + one input for each value in frame state.
1 + // Function.
1 + // Context.
1; // Entrypoint tag.
ASSERT_EQ(num_operands, call_instr->InputCount());
// Code object.
EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate());
// Deoptimization id.
int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1));
FrameStateDescriptor* desc_before =
s.GetFrameStateDescriptor(deopt_id_before);
EXPECT_EQ(bailout_id_before, desc_before->bailout_id());
EXPECT_EQ(1u, desc_before->parameters_count());
EXPECT_EQ(1u, desc_before->locals_count());
EXPECT_EQ(1u, desc_before->stack_count());
EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(3)));
EXPECT_EQ(0, s.ToInt32(call_instr->InputAt(4))); // This should be a context.
// We inserted 0 here.
EXPECT_EQ(0.5, s.ToFloat64(call_instr->InputAt(5)));
EXPECT_TRUE(IsUndefined(*s.ToHeapObject(call_instr->InputAt(6)), isolate()));
// Function.
EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(7)));
// Context.
EXPECT_EQ(s.ToVreg(context), s.ToVreg(call_instr->InputAt(8)));
// Entrypoint tag.
EXPECT_TRUE(call_instr->InputAt(9)->IsImmediate());
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
EXPECT_EQ(index, s.size());
}
TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeoptRecursiveFrameState) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id_before(42);
BytecodeOffset bailout_id_parent(62);
// Some arguments for the call node.
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Int32Constant(66);
Node* context2 = m.Int32Constant(46);
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> float64_type(1, MachineType::Float64(), zone());
Callable callable = Builtins::CallableFor(isolate(), Builtin::kToObject);
auto call_descriptor = Linkage::GetStubCallDescriptor(
zone(), callable.descriptor(), 1, CallDescriptor::kNeedsFrameState,
Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(63));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(64));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(65));
Node* frame_state_parent = m.AddNode(
m.common()->FrameState(bailout_id_parent,
OutputFrameStateCombine::Ignore(),
m.GetFrameStateFunctionInfo(1, 1)),
parameters, locals, stack, context, function_node, m.graph()->start());
Node* parameters2 = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals2 = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.25));
Node* stack2 = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(44));
Node* state_node =
m.AddNode(m.common()->FrameState(bailout_id_before,
OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 1)),
parameters2, locals2, stack2, context2, function_node,
frame_state_parent);
// Build the call.
Node* stub_code = m.HeapConstant(callable.code());
Node* nodes[] = {stub_code, function_node, receiver, context2, state_node};
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallCodeObject.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject;
index++) {
}
// Now we should have three instructions: call, return.
EXPECT_EQ(index + 2, s.size());
// Check the call instruction
const Instruction* call_instr = s[index++];
EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode());
size_t num_operands =
1 + // Code object.
1 + // Frame state deopt id
5 + // One input for each value in frame state + context.
5 + // One input for each value in the parent frame state + context.
1 + // Function.
1 + // Context.
1; // Entrypoint tag.
EXPECT_EQ(num_operands, call_instr->InputCount());
// Code object.
EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate());
// Deoptimization id.
int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1));
FrameStateDescriptor* desc_before =
s.GetFrameStateDescriptor(deopt_id_before);
FrameStateDescriptor* desc_before_outer = desc_before->outer_state();
EXPECT_EQ(bailout_id_before, desc_before->bailout_id());
EXPECT_EQ(1u, desc_before_outer->parameters_count());
EXPECT_EQ(1u, desc_before_outer->locals_count());
EXPECT_EQ(1u, desc_before_outer->stack_count());
// Values from parent environment.
EXPECT_EQ(63, s.ToInt32(call_instr->InputAt(3)));
// Context:
EXPECT_EQ(66, s.ToInt32(call_instr->InputAt(4)));
EXPECT_EQ(64, s.ToInt32(call_instr->InputAt(5)));
EXPECT_EQ(65, s.ToInt32(call_instr->InputAt(6)));
// Values from the nested frame.
EXPECT_EQ(1u, desc_before->parameters_count());
EXPECT_EQ(1u, desc_before->locals_count());
EXPECT_EQ(1u, desc_before->stack_count());
EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(8)));
EXPECT_EQ(46, s.ToInt32(call_instr->InputAt(9)));
EXPECT_EQ(0.25, s.ToFloat64(call_instr->InputAt(10)));
EXPECT_EQ(44, s.ToInt32(call_instr->InputAt(11)));
// Function.
EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(12)));
// Context.
EXPECT_EQ(s.ToVreg(context2), s.ToVreg(call_instr->InputAt(13)));
// Entrypoint tag.
EXPECT_TRUE(call_instr->InputAt(14)->IsImmediate());
// Continuation.
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
EXPECT_EQ(index, s.size());
}
#endif
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,312 @@
// 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.
#ifndef V8_UNITTESTS_COMPILER_INSTRUCTION_SELECTOR_UNITTEST_H_
#define V8_UNITTESTS_COMPILER_INSTRUCTION_SELECTOR_UNITTEST_H_
#include <deque>
#include <set>
#include "src/base/utils/random-number-generator.h"
#include "src/codegen/macro-assembler.h"
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/raw-machine-assembler.h"
#include "test/unittests/test-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
class InstructionSelectorTest : public TestWithNativeContextAndZone {
public:
InstructionSelectorTest();
~InstructionSelectorTest() override;
base::RandomNumberGenerator* rng() { return &rng_; }
class Stream;
enum StreamBuilderMode {
kAllInstructions,
kTargetInstructions,
kAllExceptNopInstructions
};
class StreamBuilder final : public RawMachineAssembler {
public:
StreamBuilder(InstructionSelectorTest* test, MachineType return_type)
: RawMachineAssembler(test->isolate(),
test->zone()->New<TFGraph>(test->zone()),
MakeCallDescriptor(test->zone(), return_type),
MachineType::PointerRepresentation(),
MachineOperatorBuilder::kAllOptionalOps),
test_(test) {}
StreamBuilder(InstructionSelectorTest* test, MachineType return_type,
MachineType parameter0_type)
: RawMachineAssembler(
test->isolate(), test->zone()->New<TFGraph>(test->zone()),
MakeCallDescriptor(test->zone(), return_type, parameter0_type),
MachineType::PointerRepresentation(),
MachineOperatorBuilder::kAllOptionalOps,
InstructionSelector::AlignmentRequirements()),
test_(test) {}
StreamBuilder(InstructionSelectorTest* test, MachineType return_type,
MachineType parameter0_type, MachineType parameter1_type)
: RawMachineAssembler(
test->isolate(), test->zone()->New<TFGraph>(test->zone()),
MakeCallDescriptor(test->zone(), return_type, parameter0_type,
parameter1_type),
MachineType::PointerRepresentation(),
MachineOperatorBuilder::kAllOptionalOps),
test_(test) {}
StreamBuilder(InstructionSelectorTest* test, MachineType return_type,
MachineType parameter0_type, MachineType parameter1_type,
MachineType parameter2_type)
: RawMachineAssembler(
test->isolate(), test->zone()->New<TFGraph>(test->zone()),
MakeCallDescriptor(test->zone(), return_type, parameter0_type,
parameter1_type, parameter2_type),
MachineType::PointerRepresentation(),
MachineOperatorBuilder::kAllOptionalOps),
test_(test) {}
Stream Build(CpuFeature feature) {
return Build(InstructionSelector::Features(feature));
}
Stream Build(CpuFeature feature1, CpuFeature feature2) {
return Build(InstructionSelector::Features(feature1, feature2));
}
Stream Build(StreamBuilderMode mode = kTargetInstructions) {
return Build(InstructionSelector::Features(), mode);
}
Stream Build(InstructionSelector::Features features,
StreamBuilderMode mode = kTargetInstructions,
InstructionSelector::SourcePositionMode source_position_mode =
InstructionSelector::kAllSourcePositions);
const FrameStateFunctionInfo* GetFrameStateFunctionInfo(
uint16_t parameter_count, int local_count);
// Create a simple call descriptor for testing.
static CallDescriptor* MakeSimpleCallDescriptor(Zone* zone,
MachineSignature* msig) {
LocationSignature::Builder locations(zone, msig->return_count(),
msig->parameter_count());
// Add return location(s).
const int return_count = static_cast<int>(msig->return_count());
for (int i = 0; i < return_count; i++) {
locations.AddReturn(
LinkageLocation::ForCallerFrameSlot(-1 - i, msig->GetReturn(i)));
}
// Just put all parameters on the stack.
const int parameter_count = static_cast<int>(msig->parameter_count());
unsigned slot_index = -1;
for (int i = 0; i < parameter_count; i++) {
locations.AddParam(
LinkageLocation::ForCallerFrameSlot(slot_index, msig->GetParam(i)));
// Slots are kSystemPointerSize sized. This reserves enough for space
// for types that might be bigger, eg. Simd128.
slot_index -=
std::max(1, ElementSizeInBytes(msig->GetParam(i).representation()) /
kSystemPointerSize);
}
const RegList kCalleeSaveRegisters;
const DoubleRegList kCalleeSaveFPRegisters;
MachineType target_type = MachineType::Pointer();
LinkageLocation target_loc = LinkageLocation::ForAnyRegister();
return zone->New<CallDescriptor>( // --
CallDescriptor::kCallAddress, // kind
kDefaultCodeEntrypointTag, // tag
target_type, // target MachineType
target_loc, // target location
locations.Get(), // location_sig
0, // stack_parameter_count
Operator::kNoProperties, // properties
kCalleeSaveRegisters, // callee-saved registers
kCalleeSaveFPRegisters, // callee-saved fp regs
CallDescriptor::kCanUseRoots, // flags
"iselect-test-call");
}
private:
CallDescriptor* MakeCallDescriptor(Zone* zone, MachineType return_type) {
MachineSignature::Builder builder(zone, 1, 0);
builder.AddReturn(return_type);
return MakeSimpleCallDescriptor(zone, builder.Get());
}
CallDescriptor* MakeCallDescriptor(Zone* zone, MachineType return_type,
MachineType parameter0_type) {
MachineSignature::Builder builder(zone, 1, 1);
builder.AddReturn(return_type);
builder.AddParam(parameter0_type);
return MakeSimpleCallDescriptor(zone, builder.Get());
}
CallDescriptor* MakeCallDescriptor(Zone* zone, MachineType return_type,
MachineType parameter0_type,
MachineType parameter1_type) {
MachineSignature::Builder builder(zone, 1, 2);
builder.AddReturn(return_type);
builder.AddParam(parameter0_type);
builder.AddParam(parameter1_type);
return MakeSimpleCallDescriptor(zone, builder.Get());
}
CallDescriptor* MakeCallDescriptor(Zone* zone, MachineType return_type,
MachineType parameter0_type,
MachineType parameter1_type,
MachineType parameter2_type) {
MachineSignature::Builder builder(zone, 1, 3);
builder.AddReturn(return_type);
builder.AddParam(parameter0_type);
builder.AddParam(parameter1_type);
builder.AddParam(parameter2_type);
return MakeSimpleCallDescriptor(zone, builder.Get());
}
InstructionSelectorTest* test_;
};
class Stream final {
public:
size_t size() const { return instructions_.size(); }
const Instruction* operator[](size_t index) const {
EXPECT_LT(index, size());
return instructions_[index];
}
bool IsDouble(const InstructionOperand* operand) const {
return IsDouble(ToVreg(operand));
}
bool IsDouble(const Node* node) const { return IsDouble(ToVreg(node)); }
bool IsInteger(const InstructionOperand* operand) const {
return IsInteger(ToVreg(operand));
}
bool IsInteger(const Node* node) const { return IsInteger(ToVreg(node)); }
bool IsReference(const InstructionOperand* operand) const {
return IsReference(ToVreg(operand));
}
bool IsReference(const Node* node) const {
return IsReference(ToVreg(node));
}
float ToFloat32(const InstructionOperand* operand) const {
return ToConstant(operand).ToFloat32();
}
double ToFloat64(const InstructionOperand* operand) const {
return ToConstant(operand).ToFloat64().value();
}
int32_t ToInt32(const InstructionOperand* operand) const {
return ToConstant(operand).ToInt32();
}
int64_t ToInt64(const InstructionOperand* operand) const {
return ToConstant(operand).ToInt64();
}
DirectHandle<HeapObject> ToHeapObject(
const InstructionOperand* operand) const {
return ToConstant(operand).ToHeapObject();
}
int ToVreg(const InstructionOperand* operand) const {
if (operand->IsConstant()) {
return ConstantOperand::cast(operand)->virtual_register();
}
EXPECT_EQ(InstructionOperand::UNALLOCATED, operand->kind());
return UnallocatedOperand::cast(operand)->virtual_register();
}
int ToVreg(const Node* node) const;
bool IsFixed(const InstructionOperand* operand, Register reg) const;
bool IsSameAsFirst(const InstructionOperand* operand) const;
bool IsSameAsInput(const InstructionOperand* operand,
int input_index) const;
bool IsUsedAtStart(const InstructionOperand* operand) const;
FrameStateDescriptor* GetFrameStateDescriptor(int deoptimization_id) {
EXPECT_LT(deoptimization_id, GetFrameStateDescriptorCount());
return deoptimization_entries_[deoptimization_id];
}
int GetFrameStateDescriptorCount() {
return static_cast<int>(deoptimization_entries_.size());
}
private:
bool IsDouble(int virtual_register) const {
return doubles_.find(virtual_register) != doubles_.end();
}
bool IsInteger(int virtual_register) const {
return !IsDouble(virtual_register) && !IsReference(virtual_register);
}
bool IsReference(int virtual_register) const {
return references_.find(virtual_register) != references_.end();
}
Constant ToConstant(const InstructionOperand* operand) const {
ConstantMap::const_iterator i;
if (operand->IsConstant()) {
i = constants_.find(ConstantOperand::cast(operand)->virtual_register());
EXPECT_EQ(ConstantOperand::cast(operand)->virtual_register(), i->first);
EXPECT_FALSE(constants_.end() == i);
} else {
EXPECT_EQ(InstructionOperand::IMMEDIATE, operand->kind());
auto imm = ImmediateOperand::cast(operand);
if (imm->type() == ImmediateOperand::INLINE_INT32) {
return Constant(imm->inline_int32_value());
} else if (imm->type() == ImmediateOperand::INLINE_INT64) {
return Constant(imm->inline_int64_value());
}
i = immediates_.find(imm->indexed_value());
EXPECT_EQ(imm->indexed_value(), i->first);
EXPECT_FALSE(immediates_.end() == i);
}
return i->second;
}
friend class StreamBuilder;
using ConstantMap = std::map<int, Constant>;
using VirtualRegisters = std::map<NodeId, int>;
ConstantMap constants_;
ConstantMap immediates_;
std::deque<Instruction*> instructions_;
std::set<int> doubles_;
std::set<int> references_;
VirtualRegisters virtual_registers_;
std::deque<FrameStateDescriptor*> deoptimization_entries_;
};
base::RandomNumberGenerator rng_;
};
template <typename T>
class InstructionSelectorTestWithParam
: public InstructionSelectorTest,
public ::testing::WithParamInterface<T> {};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_UNITTESTS_COMPILER_INSTRUCTION_SELECTOR_UNITTEST_H_

View File

@ -0,0 +1,561 @@
// 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 "test/unittests/compiler/backend/instruction-sequence-unittest.h"
#include "src/base/utils/random-number-generator.h"
#include "src/compiler/pipeline.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
constexpr int kMaxNumAllocatable =
std::max(Register::kNumRegisters, DoubleRegister::kNumRegisters);
static std::array<int, kMaxNumAllocatable> kAllocatableCodes =
base::make_array<kMaxNumAllocatable>(
[](size_t i) { return static_cast<int>(i); });
}
InstructionSequenceTest::InstructionSequenceTest()
: sequence_(nullptr),
num_general_registers_(Register::kNumRegisters),
num_double_registers_(DoubleRegister::kNumRegisters),
num_simd128_registers_(Simd128Register::kNumRegisters),
#if V8_TARGET_ARCH_X64
num_simd256_registers_(Simd256Register::kNumRegisters),
#else
num_simd256_registers_(0),
#endif // V8_TARGET_ARCH_X64
instruction_blocks_(zone()),
current_block_(nullptr),
block_returns_(false) {
}
void InstructionSequenceTest::SetNumRegs(int num_general_registers,
int num_double_registers) {
CHECK(!config_);
CHECK(instructions_.empty());
CHECK(instruction_blocks_.empty());
CHECK_GE(Register::kNumRegisters, num_general_registers);
CHECK_GE(DoubleRegister::kNumRegisters, num_double_registers);
num_general_registers_ = num_general_registers;
num_double_registers_ = num_double_registers;
}
int InstructionSequenceTest::GetNumRegs(MachineRepresentation rep) {
switch (rep) {
case MachineRepresentation::kFloat32:
return config()->num_float_registers();
case MachineRepresentation::kFloat64:
return config()->num_double_registers();
case MachineRepresentation::kSimd128:
return config()->num_simd128_registers();
case MachineRepresentation::kSimd256:
return config()->num_simd256_registers();
default:
return config()->num_general_registers();
}
}
int InstructionSequenceTest::GetAllocatableCode(int index,
MachineRepresentation rep) {
switch (rep) {
case MachineRepresentation::kFloat32:
return config()->GetAllocatableFloatCode(index);
case MachineRepresentation::kFloat64:
return config()->GetAllocatableDoubleCode(index);
case MachineRepresentation::kSimd128:
return config()->GetAllocatableSimd128Code(index);
case MachineRepresentation::kSimd256:
return config()->GetAllocatableSimd256Code(index);
default:
return config()->GetAllocatableGeneralCode(index);
}
}
const RegisterConfiguration* InstructionSequenceTest::config() {
if (!config_) {
config_.reset(new RegisterConfiguration(
kFPAliasing, num_general_registers_, num_double_registers_,
num_simd128_registers_, num_simd256_registers_, num_general_registers_,
num_double_registers_, num_simd128_registers_, num_simd256_registers_,
kAllocatableCodes.data(), kAllocatableCodes.data(),
kAllocatableCodes.data()));
}
return config_.get();
}
InstructionSequence* InstructionSequenceTest::sequence() {
if (sequence_ == nullptr) {
sequence_ = zone()->New<InstructionSequence>(isolate(), zone(),
&instruction_blocks_);
sequence_->SetRegisterConfigurationForTesting(
InstructionSequenceTest::config());
}
return sequence_;
}
void InstructionSequenceTest::StartLoop(int loop_blocks) {
CHECK_NULL(current_block_);
if (!loop_blocks_.empty()) {
CHECK(!loop_blocks_.back().loop_header_.IsValid());
}
LoopData loop_data = {Rpo::Invalid(), loop_blocks};
loop_blocks_.push_back(loop_data);
}
void InstructionSequenceTest::EndLoop() {
CHECK_NULL(current_block_);
CHECK(!loop_blocks_.empty());
CHECK_EQ(0, loop_blocks_.back().expected_blocks_);
loop_blocks_.pop_back();
}
void InstructionSequenceTest::StartBlock(bool deferred) {
block_returns_ = false;
NewBlock(deferred);
}
Instruction* InstructionSequenceTest::EndBlock(BlockCompletion completion) {
Instruction* result = nullptr;
if (block_returns_) {
CHECK(completion.type_ == kBlockEnd || completion.type_ == kFallThrough);
completion.type_ = kBlockEnd;
}
switch (completion.type_) {
case kBlockEnd:
break;
case kFallThrough:
result = EmitJump(completion.op_);
break;
case kJump:
CHECK(!block_returns_);
result = EmitJump(completion.op_);
break;
case kBranch:
CHECK(!block_returns_);
result = EmitBranch(completion.op_);
break;
}
completions_.push_back(completion);
CHECK_NOT_NULL(current_block_);
int end = static_cast<int>(sequence()->instructions().size());
if (current_block_->code_start() == end) { // Empty block. Insert a nop.
sequence()->AddInstruction(Instruction::New(zone(), kArchNop));
}
sequence()->EndBlock(current_block_->rpo_number());
current_block_ = nullptr;
return result;
}
InstructionSequenceTest::TestOperand InstructionSequenceTest::Imm(int32_t imm) {
return TestOperand(kImmediate, imm);
}
InstructionSequenceTest::VReg InstructionSequenceTest::Define(
TestOperand output_op) {
VReg vreg = NewReg(output_op);
InstructionOperand outputs[1]{ConvertOutputOp(vreg, output_op)};
Emit(kArchNop, 1, outputs);
return vreg;
}
Instruction* InstructionSequenceTest::Return(TestOperand input_op_0) {
block_returns_ = true;
InstructionOperand inputs[1]{ConvertInputOp(input_op_0)};
return Emit(kArchRet, 0, nullptr, 1, inputs);
}
PhiInstruction* InstructionSequenceTest::Phi(VReg incoming_vreg_0,
VReg incoming_vreg_1,
VReg incoming_vreg_2,
VReg incoming_vreg_3) {
VReg inputs[] = {incoming_vreg_0, incoming_vreg_1, incoming_vreg_2,
incoming_vreg_3};
size_t input_count = 0;
for (; input_count < arraysize(inputs); ++input_count) {
if (inputs[input_count].value_ == kNoValue) break;
}
CHECK_LT(0, input_count);
auto phi = zone()->New<PhiInstruction>(zone(), NewReg().value_, input_count);
for (size_t i = 0; i < input_count; ++i) {
SetInput(phi, i, inputs[i]);
}
current_block_->AddPhi(phi);
return phi;
}
PhiInstruction* InstructionSequenceTest::Phi(VReg incoming_vreg_0,
size_t input_count) {
auto phi = zone()->New<PhiInstruction>(zone(), NewReg().value_, input_count);
SetInput(phi, 0, incoming_vreg_0);
current_block_->AddPhi(phi);
return phi;
}
void InstructionSequenceTest::SetInput(PhiInstruction* phi, size_t input,
VReg vreg) {
CHECK_NE(kNoValue, vreg.value_);
phi->SetInput(input, vreg.value_);
}
InstructionSequenceTest::VReg InstructionSequenceTest::DefineConstant(
int32_t imm) {
VReg vreg = NewReg();
sequence()->AddConstant(vreg.value_, Constant(imm));
InstructionOperand outputs[1]{ConstantOperand(vreg.value_)};
Emit(kArchNop, 1, outputs);
return vreg;
}
Instruction* InstructionSequenceTest::EmitNop() { return Emit(kArchNop); }
static size_t CountInputs(size_t size,
InstructionSequenceTest::TestOperand* inputs) {
size_t i = 0;
for (; i < size; ++i) {
if (inputs[i].type_ == InstructionSequenceTest::kInvalid) break;
}
return i;
}
Instruction* InstructionSequenceTest::EmitI(size_t input_size,
TestOperand* inputs) {
InstructionOperand* mapped_inputs = ConvertInputs(input_size, inputs);
return Emit(kArchNop, 0, nullptr, input_size, mapped_inputs);
}
Instruction* InstructionSequenceTest::EmitI(TestOperand input_op_0,
TestOperand input_op_1,
TestOperand input_op_2,
TestOperand input_op_3) {
TestOperand inputs[] = {input_op_0, input_op_1, input_op_2, input_op_3};
return EmitI(CountInputs(arraysize(inputs), inputs), inputs);
}
InstructionSequenceTest::VReg InstructionSequenceTest::EmitOI(
TestOperand output_op, size_t input_size, TestOperand* inputs) {
VReg output_vreg = NewReg(output_op);
InstructionOperand outputs[1]{ConvertOutputOp(output_vreg, output_op)};
InstructionOperand* mapped_inputs = ConvertInputs(input_size, inputs);
Emit(kArchNop, 1, outputs, input_size, mapped_inputs);
return output_vreg;
}
InstructionSequenceTest::VReg InstructionSequenceTest::EmitOI(
TestOperand output_op, TestOperand input_op_0, TestOperand input_op_1,
TestOperand input_op_2, TestOperand input_op_3) {
TestOperand inputs[] = {input_op_0, input_op_1, input_op_2, input_op_3};
return EmitOI(output_op, CountInputs(arraysize(inputs), inputs), inputs);
}
InstructionSequenceTest::VRegPair InstructionSequenceTest::EmitOOI(
TestOperand output_op_0, TestOperand output_op_1, size_t input_size,
TestOperand* inputs) {
VRegPair output_vregs =
std::make_pair(NewReg(output_op_0), NewReg(output_op_1));
InstructionOperand outputs[2]{
ConvertOutputOp(output_vregs.first, output_op_0),
ConvertOutputOp(output_vregs.second, output_op_1)};
InstructionOperand* mapped_inputs = ConvertInputs(input_size, inputs);
Emit(kArchNop, 2, outputs, input_size, mapped_inputs);
return output_vregs;
}
InstructionSequenceTest::VRegPair InstructionSequenceTest::EmitOOI(
TestOperand output_op_0, TestOperand output_op_1, TestOperand input_op_0,
TestOperand input_op_1, TestOperand input_op_2, TestOperand input_op_3) {
TestOperand inputs[] = {input_op_0, input_op_1, input_op_2, input_op_3};
return EmitOOI(output_op_0, output_op_1,
CountInputs(arraysize(inputs), inputs), inputs);
}
InstructionSequenceTest::VReg InstructionSequenceTest::EmitCall(
TestOperand output_op, size_t input_size, TestOperand* inputs) {
VReg output_vreg = NewReg(output_op);
InstructionOperand outputs[1]{ConvertOutputOp(output_vreg, output_op)};
CHECK(UnallocatedOperand::cast(outputs[0]).HasFixedPolicy());
InstructionOperand* mapped_inputs = ConvertInputs(input_size, inputs);
Emit(kArchCallCodeObject, 1, outputs, input_size, mapped_inputs, 0, nullptr,
true);
return output_vreg;
}
InstructionSequenceTest::VReg InstructionSequenceTest::EmitCall(
TestOperand output_op, TestOperand input_op_0, TestOperand input_op_1,
TestOperand input_op_2, TestOperand input_op_3) {
TestOperand inputs[] = {input_op_0, input_op_1, input_op_2, input_op_3};
return EmitCall(output_op, CountInputs(arraysize(inputs), inputs), inputs);
}
Instruction* InstructionSequenceTest::EmitBranch(TestOperand input_op) {
InstructionOperand inputs[4]{ConvertInputOp(input_op), ConvertInputOp(Imm()),
ConvertInputOp(Imm()), ConvertInputOp(Imm())};
InstructionCode opcode = kArchJmp | FlagsModeField::encode(kFlags_branch) |
FlagsConditionField::encode(kEqual);
auto instruction = NewInstruction(opcode, 0, nullptr, 4, inputs);
return AddInstruction(instruction);
}
Instruction* InstructionSequenceTest::EmitFallThrough() {
auto instruction = NewInstruction(kArchNop, 0, nullptr);
return AddInstruction(instruction);
}
Instruction* InstructionSequenceTest::EmitJump(TestOperand input_op) {
InstructionOperand inputs[1]{ConvertInputOp(input_op)};
auto instruction = NewInstruction(kArchJmp, 0, nullptr, 1, inputs);
return AddInstruction(instruction);
}
Instruction* InstructionSequenceTest::NewInstruction(
InstructionCode code, size_t outputs_size, InstructionOperand* outputs,
size_t inputs_size, InstructionOperand* inputs, size_t temps_size,
InstructionOperand* temps) {
CHECK(current_block_);
return Instruction::New(zone(), code, outputs_size, outputs, inputs_size,
inputs, temps_size, temps);
}
InstructionOperand InstructionSequenceTest::Unallocated(
TestOperand op, UnallocatedOperand::ExtendedPolicy policy) {
return UnallocatedOperand(policy, op.vreg_.value_);
}
InstructionOperand InstructionSequenceTest::Unallocated(
TestOperand op, UnallocatedOperand::ExtendedPolicy policy,
UnallocatedOperand::Lifetime lifetime) {
return UnallocatedOperand(policy, lifetime, op.vreg_.value_);
}
InstructionOperand InstructionSequenceTest::Unallocated(
TestOperand op, UnallocatedOperand::ExtendedPolicy policy, int index) {
return UnallocatedOperand(policy, index, op.vreg_.value_);
}
InstructionOperand InstructionSequenceTest::Unallocated(
TestOperand op, UnallocatedOperand::BasicPolicy policy, int index) {
return UnallocatedOperand(policy, index, op.vreg_.value_);
}
InstructionOperand* InstructionSequenceTest::ConvertInputs(
size_t input_size, TestOperand* inputs) {
InstructionOperand* mapped_inputs =
zone()->AllocateArray<InstructionOperand>(static_cast<int>(input_size));
for (size_t i = 0; i < input_size; ++i) {
mapped_inputs[i] = ConvertInputOp(inputs[i]);
}
return mapped_inputs;
}
InstructionOperand InstructionSequenceTest::ConvertInputOp(TestOperand op) {
if (op.type_ == kImmediate) {
CHECK_EQ(op.vreg_.value_, kNoValue);
return ImmediateOperand(ImmediateOperand::INLINE_INT32, op.value_);
}
CHECK_NE(op.vreg_.value_, kNoValue);
switch (op.type_) {
case kNone:
return Unallocated(op, UnallocatedOperand::NONE,
UnallocatedOperand::USED_AT_START);
case kUnique:
return Unallocated(op, UnallocatedOperand::NONE);
case kUniqueRegister:
return Unallocated(op, UnallocatedOperand::MUST_HAVE_REGISTER);
case kRegister:
return Unallocated(op, UnallocatedOperand::MUST_HAVE_REGISTER,
UnallocatedOperand::USED_AT_START);
case kSlot:
return Unallocated(op, UnallocatedOperand::MUST_HAVE_SLOT,
UnallocatedOperand::USED_AT_START);
case kDeoptArg:
return Unallocated(op, UnallocatedOperand::REGISTER_OR_SLOT,
UnallocatedOperand::USED_AT_END);
case kFixedRegister: {
MachineRepresentation rep = GetCanonicalRep(op);
CHECK(0 <= op.value_ && op.value_ < GetNumRegs(rep));
if (DoesRegisterAllocation()) {
auto extended_policy = IsFloatingPoint(rep)
? UnallocatedOperand::FIXED_FP_REGISTER
: UnallocatedOperand::FIXED_REGISTER;
return Unallocated(op, extended_policy, op.value_);
} else {
return AllocatedOperand(LocationOperand::REGISTER, rep, op.value_);
}
}
case kFixedSlot:
if (DoesRegisterAllocation()) {
return Unallocated(op, UnallocatedOperand::FIXED_SLOT, op.value_);
} else {
return AllocatedOperand(LocationOperand::STACK_SLOT,
GetCanonicalRep(op), op.value_);
}
default:
break;
}
UNREACHABLE();
}
InstructionOperand InstructionSequenceTest::ConvertOutputOp(VReg vreg,
TestOperand op) {
CHECK_EQ(op.vreg_.value_, kNoValue);
op.vreg_ = vreg;
switch (op.type_) {
case kSameAsInput:
return Unallocated(op, UnallocatedOperand::SAME_AS_INPUT);
case kRegister:
return Unallocated(op, UnallocatedOperand::MUST_HAVE_REGISTER);
case kFixedSlot:
if (DoesRegisterAllocation()) {
return Unallocated(op, UnallocatedOperand::FIXED_SLOT, op.value_);
} else {
return AllocatedOperand(LocationOperand::STACK_SLOT,
GetCanonicalRep(op), op.value_);
}
case kFixedRegister: {
MachineRepresentation rep = GetCanonicalRep(op);
CHECK(0 <= op.value_ && op.value_ < GetNumRegs(rep));
if (DoesRegisterAllocation()) {
auto extended_policy = IsFloatingPoint(rep)
? UnallocatedOperand::FIXED_FP_REGISTER
: UnallocatedOperand::FIXED_REGISTER;
return Unallocated(op, extended_policy, op.value_);
} else {
return AllocatedOperand(LocationOperand::REGISTER, rep, op.value_);
}
}
default:
break;
}
UNREACHABLE();
}
InstructionBlock* InstructionSequenceTest::NewBlock(bool deferred) {
CHECK_NULL(current_block_);
Rpo rpo = Rpo::FromInt(static_cast<int>(instruction_blocks_.size()));
Rpo loop_header = Rpo::Invalid();
Rpo loop_end = Rpo::Invalid();
if (!loop_blocks_.empty()) {
auto& loop_data = loop_blocks_.back();
// This is a loop header.
if (!loop_data.loop_header_.IsValid()) {
loop_end = Rpo::FromInt(rpo.ToInt() + loop_data.expected_blocks_);
loop_data.expected_blocks_--;
loop_data.loop_header_ = rpo;
} else {
// This is a loop body.
CHECK_NE(0, loop_data.expected_blocks_);
// TODO(dcarney): handle nested loops.
loop_data.expected_blocks_--;
loop_header = loop_data.loop_header_;
}
}
// Construct instruction block.
auto instruction_block = zone()->New<InstructionBlock>(
zone(), rpo, loop_header, loop_end, Rpo::Invalid(), deferred, false);
instruction_blocks_.push_back(instruction_block);
current_block_ = instruction_block;
sequence()->StartBlock(rpo);
return instruction_block;
}
void InstructionSequenceTest::WireBlocks() {
CHECK(!current_block());
CHECK(instruction_blocks_.size() == completions_.size());
CHECK(loop_blocks_.empty());
// Wire in end block to look like a scheduler produced cfg.
auto end_block = NewBlock();
Emit(kArchNop);
current_block_ = nullptr;
sequence()->EndBlock(end_block->rpo_number());
size_t offset = 0;
for (const auto& completion : completions_) {
switch (completion.type_) {
case kBlockEnd: {
auto block = instruction_blocks_[offset];
block->successors().push_back(end_block->rpo_number());
end_block->predecessors().push_back(block->rpo_number());
break;
}
case kFallThrough: // Fallthrough.
case kJump:
WireBlock(offset, completion.offset_0_);
break;
case kBranch:
WireBlock(offset, completion.offset_0_);
WireBlock(offset, completion.offset_1_);
break;
}
++offset;
}
CalculateDominators();
}
void InstructionSequenceTest::WireBlock(size_t block_offset, int jump_offset) {
size_t target_block_offset = block_offset + static_cast<size_t>(jump_offset);
CHECK(block_offset < instruction_blocks_.size());
CHECK(target_block_offset < instruction_blocks_.size());
auto block = instruction_blocks_[block_offset];
auto target = instruction_blocks_[target_block_offset];
block->successors().push_back(target->rpo_number());
target->predecessors().push_back(block->rpo_number());
}
void InstructionSequenceTest::CalculateDominators() {
CHECK_GT(instruction_blocks_.size(), 0);
ZoneVector<int> dominator_depth(instruction_blocks_.size(), -1, zone());
CHECK_EQ(instruction_blocks_[0]->rpo_number(), RpoNumber::FromInt(0));
dominator_depth[0] = 0;
instruction_blocks_[0]->set_dominator(RpoNumber::FromInt(0));
for (size_t i = 1; i < instruction_blocks_.size(); i++) {
InstructionBlock* block = instruction_blocks_[i];
auto pred = block->predecessors().begin();
auto end = block->predecessors().end();
DCHECK(pred != end); // All blocks except start have predecessors.
RpoNumber dominator = *pred;
// For multiple predecessors, walk up the dominator tree until a common
// dominator is found. Visitation order guarantees that all predecessors
// except for backwards edges have been visited.
for (++pred; pred != end; ++pred) {
// Don't examine backwards edges.
if (dominator_depth[pred->ToInt()] < 0) continue;
RpoNumber other = *pred;
while (dominator != other) {
if (dominator_depth[dominator.ToInt()] <
dominator_depth[other.ToInt()]) {
other = instruction_blocks_[other.ToInt()]->dominator();
} else {
dominator = instruction_blocks_[dominator.ToInt()]->dominator();
}
}
}
block->set_dominator(dominator);
dominator_depth[i] = dominator_depth[dominator.ToInt()] + 1;
}
}
Instruction* InstructionSequenceTest::Emit(
InstructionCode code, size_t outputs_size, InstructionOperand* outputs,
size_t inputs_size, InstructionOperand* inputs, size_t temps_size,
InstructionOperand* temps, bool is_call) {
auto instruction = NewInstruction(code, outputs_size, outputs, inputs_size,
inputs, temps_size, temps);
if (is_call) instruction->MarkAsCall();
return AddInstruction(instruction);
}
Instruction* InstructionSequenceTest::AddInstruction(Instruction* instruction) {
sequence()->AddInstruction(instruction);
return instruction;
}
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,298 @@
// 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.
#ifndef V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_
#define V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_
#include <memory>
#include "src/codegen/register-configuration.h"
#include "src/compiler/backend/instruction.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace v8 {
namespace internal {
namespace compiler {
class InstructionSequenceTest : public TestWithIsolateAndZone {
public:
static constexpr int kNoValue = kMinInt;
static constexpr MachineRepresentation kNoRep = MachineRepresentation::kNone;
static constexpr MachineRepresentation kFloat32 =
MachineRepresentation::kFloat32;
static constexpr MachineRepresentation kFloat64 =
MachineRepresentation::kFloat64;
static constexpr MachineRepresentation kSimd128 =
MachineRepresentation::kSimd128;
using Rpo = RpoNumber;
struct VReg {
VReg() : value_(kNoValue) {}
VReg(PhiInstruction* phi) : value_(phi->virtual_register()) {} // NOLINT
explicit VReg(int value, MachineRepresentation rep = kNoRep)
: value_(value), rep_(rep) {}
int value_;
MachineRepresentation rep_ = kNoRep;
};
using VRegPair = std::pair<VReg, VReg>;
enum TestOperandType {
kInvalid,
kSameAsInput,
kRegister,
kFixedRegister,
kSlot,
kFixedSlot,
kImmediate,
kNone,
kConstant,
kUnique,
kUniqueRegister,
kDeoptArg
};
struct TestOperand {
TestOperand() : type_(kInvalid), vreg_(), value_(kNoValue), rep_(kNoRep) {}
explicit TestOperand(TestOperandType type)
: type_(type), vreg_(), value_(kNoValue), rep_(kNoRep) {}
// For tests that do register allocation.
TestOperand(TestOperandType type, VReg vreg, int value = kNoValue)
: type_(type), vreg_(vreg), value_(value), rep_(vreg.rep_) {}
// For immediates, constants, and tests that don't do register allocation.
TestOperand(TestOperandType type, int value,
MachineRepresentation rep = kNoRep)
: type_(type), vreg_(), value_(value), rep_(rep) {}
TestOperandType type_;
VReg vreg_;
int value_;
MachineRepresentation rep_;
};
static TestOperand Same() { return TestOperand(kSameAsInput); }
static TestOperand Reg(VReg vreg, int index = kNoValue) {
TestOperandType type = (index == kNoValue) ? kRegister : kFixedRegister;
return TestOperand(type, vreg, index);
}
static TestOperand Reg(int index = kNoValue,
MachineRepresentation rep = kNoRep) {
return Reg(VReg(kNoValue, rep), index);
}
static TestOperand FPReg(int index = kNoValue,
MachineRepresentation rep = kFloat64) {
return Reg(index, rep);
}
static TestOperand Slot(VReg vreg, int index = kNoValue) {
TestOperandType type = (index == kNoValue) ? kSlot : kFixedSlot;
return TestOperand(type, vreg, index);
}
static TestOperand Slot(int index = kNoValue,
MachineRepresentation rep = kNoRep) {
return Slot(VReg(kNoValue, rep), index);
}
static TestOperand Const(int index) {
CHECK_NE(kNoValue, index);
return TestOperand(kConstant, index);
}
static TestOperand DeoptArg(VReg vreg) {
return TestOperand(kDeoptArg, vreg);
}
static TestOperand Use(VReg vreg) { return TestOperand(kNone, vreg); }
static TestOperand Use() { return Use(VReg()); }
static TestOperand Unique(VReg vreg) { return TestOperand(kUnique, vreg); }
static TestOperand UniqueReg(VReg vreg) {
return TestOperand(kUniqueRegister, vreg);
}
enum BlockCompletionType { kBlockEnd, kFallThrough, kBranch, kJump };
struct BlockCompletion {
BlockCompletionType type_;
TestOperand op_;
int offset_0_;
int offset_1_;
};
static BlockCompletion FallThrough() {
BlockCompletion completion = {kFallThrough, TestOperand(kImmediate, 0), 1,
kNoValue};
return completion;
}
static BlockCompletion Jump(int offset,
TestOperand operand = TestOperand(kImmediate,
0)) {
BlockCompletion completion = {kJump, operand, offset, kNoValue};
return completion;
}
static BlockCompletion Branch(TestOperand op, int left_offset,
int right_offset) {
BlockCompletion completion = {kBranch, op, left_offset, right_offset};
return completion;
}
static BlockCompletion Last() {
BlockCompletion completion = {kBlockEnd, TestOperand(), kNoValue, kNoValue};
return completion;
}
InstructionSequenceTest();
InstructionSequenceTest(const InstructionSequenceTest&) = delete;
InstructionSequenceTest& operator=(const InstructionSequenceTest&) = delete;
void SetNumRegs(int num_general_registers, int num_double_registers);
int GetNumRegs(MachineRepresentation rep);
int GetAllocatableCode(int index, MachineRepresentation rep = kNoRep);
const RegisterConfiguration* config();
InstructionSequence* sequence();
void StartLoop(int loop_blocks);
void EndLoop();
void StartBlock(bool deferred = false);
Instruction* EndBlock(BlockCompletion completion = FallThrough());
TestOperand Imm(int32_t imm = 0);
VReg Define(TestOperand output_op);
VReg Parameter(TestOperand output_op = Reg()) { return Define(output_op); }
VReg FPParameter(MachineRepresentation rep = kFloat64) {
return Parameter(FPReg(kNoValue, rep));
}
MachineRepresentation GetCanonicalRep(TestOperand op) {
return IsFloatingPoint(op.rep_) ? op.rep_
: sequence()->DefaultRepresentation();
}
Instruction* Return(TestOperand input_op_0);
Instruction* Return(VReg vreg) { return Return(Reg(vreg, 0)); }
PhiInstruction* Phi(VReg incoming_vreg_0 = VReg(),
VReg incoming_vreg_1 = VReg(),
VReg incoming_vreg_2 = VReg(),
VReg incoming_vreg_3 = VReg());
PhiInstruction* Phi(VReg incoming_vreg_0, size_t input_count);
void SetInput(PhiInstruction* phi, size_t input, VReg vreg);
VReg DefineConstant(int32_t imm = 0);
Instruction* EmitNop();
Instruction* EmitI(size_t input_size, TestOperand* inputs);
Instruction* EmitI(TestOperand input_op_0 = TestOperand(),
TestOperand input_op_1 = TestOperand(),
TestOperand input_op_2 = TestOperand(),
TestOperand input_op_3 = TestOperand());
VReg EmitOI(TestOperand output_op, size_t input_size, TestOperand* inputs);
VReg EmitOI(TestOperand output_op, TestOperand input_op_0 = TestOperand(),
TestOperand input_op_1 = TestOperand(),
TestOperand input_op_2 = TestOperand(),
TestOperand input_op_3 = TestOperand());
VRegPair EmitOOI(TestOperand output_op_0, TestOperand output_op_1,
size_t input_size, TestOperand* inputs);
VRegPair EmitOOI(TestOperand output_op_0, TestOperand output_op_1,
TestOperand input_op_0 = TestOperand(),
TestOperand input_op_1 = TestOperand(),
TestOperand input_op_2 = TestOperand(),
TestOperand input_op_3 = TestOperand());
VReg EmitCall(TestOperand output_op, size_t input_size, TestOperand* inputs);
VReg EmitCall(TestOperand output_op, TestOperand input_op_0 = TestOperand(),
TestOperand input_op_1 = TestOperand(),
TestOperand input_op_2 = TestOperand(),
TestOperand input_op_3 = TestOperand());
InstructionBlock* current_block() const { return current_block_; }
// Called after all instructions have been inserted.
void WireBlocks();
private:
virtual bool DoesRegisterAllocation() const { return true; }
VReg NewReg(TestOperand op = TestOperand()) {
int vreg = sequence()->NextVirtualRegister();
if (IsFloatingPoint(op.rep_))
sequence()->MarkAsRepresentation(op.rep_, vreg);
return VReg(vreg, op.rep_);
}
static TestOperand Invalid() { return TestOperand(kInvalid); }
Instruction* EmitBranch(TestOperand input_op);
Instruction* EmitFallThrough();
Instruction* EmitJump(TestOperand input_op);
Instruction* NewInstruction(InstructionCode code, size_t outputs_size,
InstructionOperand* outputs,
size_t inputs_size = 0,
InstructionOperand* inputs = nullptr,
size_t temps_size = 0,
InstructionOperand* temps = nullptr);
InstructionOperand Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy);
InstructionOperand Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy,
UnallocatedOperand::Lifetime lifetime);
InstructionOperand Unallocated(TestOperand op,
UnallocatedOperand::ExtendedPolicy policy,
int index);
InstructionOperand Unallocated(TestOperand op,
UnallocatedOperand::BasicPolicy policy,
int index);
InstructionOperand* ConvertInputs(size_t input_size, TestOperand* inputs);
InstructionOperand ConvertInputOp(TestOperand op);
InstructionOperand ConvertOutputOp(VReg vreg, TestOperand op);
InstructionBlock* NewBlock(bool deferred = false);
void WireBlock(size_t block_offset, int jump_offset);
void CalculateDominators();
Instruction* Emit(InstructionCode code, size_t outputs_size = 0,
InstructionOperand* outputs = nullptr,
size_t inputs_size = 0,
InstructionOperand* inputs = nullptr, size_t temps_size = 0,
InstructionOperand* temps = nullptr, bool is_call = false);
Instruction* AddInstruction(Instruction* instruction);
struct LoopData {
Rpo loop_header_;
int expected_blocks_;
};
using LoopBlocks = std::vector<LoopData>;
using Instructions = std::map<int, const Instruction*>;
using Completions = std::vector<BlockCompletion>;
std::unique_ptr<RegisterConfiguration> config_;
InstructionSequence* sequence_;
int num_general_registers_;
int num_double_registers_;
int num_simd128_registers_;
int num_simd256_registers_;
// Block building state.
InstructionBlocks instruction_blocks_;
Instructions instructions_;
Completions completions_;
LoopBlocks loop_blocks_;
InstructionBlock* current_block_;
bool block_returns_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_UNITTESTS_COMPILER_INSTRUCTION_SEQUENCE_UNITTEST_H_

View File

@ -0,0 +1,193 @@
// Copyright 2016 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/instruction.h"
#include "src/codegen/register-configuration.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest-support.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace instruction_unittest {
namespace {
const MachineRepresentation kWord = MachineRepresentation::kWord32;
const MachineRepresentation kFloat = MachineRepresentation::kFloat32;
const MachineRepresentation kDouble = MachineRepresentation::kFloat64;
bool Interfere(LocationOperand::LocationKind kind, MachineRepresentation rep1,
int index1, MachineRepresentation rep2, int index2) {
return AllocatedOperand(kind, rep1, index1)
.InterferesWith(AllocatedOperand(kind, rep2, index2));
}
bool Contains(const ZoneVector<MoveOperands*>* moves,
const InstructionOperand& to, const InstructionOperand& from) {
for (auto move : *moves) {
if (move->destination().Equals(to) && move->source().Equals(from)) {
return true;
}
}
return false;
}
} // namespace
class InstructionTest : public TestWithZone {
public:
InstructionTest() = default;
~InstructionTest() override = default;
ParallelMove* CreateParallelMove(
const std::vector<InstructionOperand>& operand_pairs) {
ParallelMove* parallel_move = zone()->New<ParallelMove>(zone());
for (size_t i = 0; i < operand_pairs.size(); i += 2)
parallel_move->AddMove(operand_pairs[i + 1], operand_pairs[i]);
return parallel_move;
}
};
TEST_F(InstructionTest, OperandInterference) {
// All general registers and slots interfere only with themselves.
for (int i = 0; i < RegisterConfiguration::kMaxGeneralRegisters; ++i) {
EXPECT_TRUE(Interfere(LocationOperand::REGISTER, kWord, i, kWord, i));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT, kWord, i, kWord, i));
for (int j = i + 1; j < RegisterConfiguration::kMaxGeneralRegisters; ++j) {
EXPECT_FALSE(Interfere(LocationOperand::REGISTER, kWord, i, kWord, j));
EXPECT_FALSE(Interfere(LocationOperand::STACK_SLOT, kWord, i, kWord, j));
}
}
// 128 bit slots can interfere with other slots at a different index.
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 128 / kBitsPerByte / kSystemPointerSize; ++j) {
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, i, kWord, i - j));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, i, kFloat, i - j));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, i, kDouble,
i - j));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT,
MachineRepresentation::kSimd128, i,
MachineRepresentation::kSimd128, i - j));
}
}
// All FP registers interfere with themselves.
for (int i = 0; i < RegisterConfiguration::kMaxFPRegisters; ++i) {
EXPECT_TRUE(Interfere(LocationOperand::REGISTER, kFloat, i, kFloat, i));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT, kFloat, i, kFloat, i));
EXPECT_TRUE(Interfere(LocationOperand::REGISTER, kDouble, i, kDouble, i));
EXPECT_TRUE(Interfere(LocationOperand::STACK_SLOT, kDouble, i, kDouble, i));
}
if (kFPAliasing != AliasingKind::kCombine) {
// Simple FP aliasing: interfering registers of different reps have the same
// index.
for (int i = 0; i < RegisterConfiguration::kMaxFPRegisters; ++i) {
EXPECT_TRUE(Interfere(LocationOperand::REGISTER, kFloat, i, kDouble, i));
EXPECT_TRUE(Interfere(LocationOperand::REGISTER, kDouble, i, kFloat, i));
for (int j = i + 1; j < RegisterConfiguration::kMaxFPRegisters; ++j) {
EXPECT_FALSE(Interfere(LocationOperand::REGISTER, kWord, i, kWord, j));
EXPECT_FALSE(
Interfere(LocationOperand::STACK_SLOT, kWord, i, kWord, j));
}
}
} else {
// Complex FP aliasing: sub-registers intefere with containing registers.
// Test sub-register indices which may not exist on the platform. This is
// necessary since the GapResolver may split large moves into smaller ones.
for (int i = 0; i < RegisterConfiguration::kMaxFPRegisters; ++i) {
EXPECT_TRUE(
Interfere(LocationOperand::REGISTER, kFloat, i * 2, kDouble, i));
EXPECT_TRUE(
Interfere(LocationOperand::REGISTER, kFloat, i * 2 + 1, kDouble, i));
EXPECT_TRUE(
Interfere(LocationOperand::REGISTER, kDouble, i, kFloat, i * 2));
EXPECT_TRUE(
Interfere(LocationOperand::REGISTER, kDouble, i, kFloat, i * 2 + 1));
for (int j = i + 1; j < RegisterConfiguration::kMaxFPRegisters; ++j) {
EXPECT_FALSE(
Interfere(LocationOperand::REGISTER, kFloat, i * 2, kDouble, j));
EXPECT_FALSE(Interfere(LocationOperand::REGISTER, kFloat, i * 2 + 1,
kDouble, j));
EXPECT_FALSE(
Interfere(LocationOperand::REGISTER, kDouble, i, kFloat, j * 2));
EXPECT_FALSE(Interfere(LocationOperand::REGISTER, kDouble, i, kFloat,
j * 2 + 1));
}
}
}
}
TEST_F(InstructionTest, PrepareInsertAfter) {
InstructionOperand r0 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kWord32, 0);
InstructionOperand r1 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kWord32, 1);
InstructionOperand r2 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kWord32, 2);
InstructionOperand d0 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 0);
InstructionOperand d1 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 1);
InstructionOperand d2 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat64, 2);
{
// Moves inserted after should pick up assignments to their sources.
// Moves inserted after should cause interfering moves to be eliminated.
ZoneVector<MoveOperands*> to_eliminate(zone());
std::vector<InstructionOperand> moves = {
r1, r0, // r1 <- r0
r2, r0, // r2 <- r0
d1, d0, // d1 <- d0
d2, d0 // d2 <- d0
};
ParallelMove* pm = CreateParallelMove(moves);
MoveOperands m1(r1, r2); // r2 <- r1
pm->PrepareInsertAfter(&m1, &to_eliminate);
CHECK(m1.source().Equals(r0));
CHECK(Contains(&to_eliminate, r2, r0));
MoveOperands m2(d1, d2); // d2 <- d1
pm->PrepareInsertAfter(&m2, &to_eliminate);
CHECK(m2.source().Equals(d0));
CHECK(Contains(&to_eliminate, d2, d0));
}
if (kFPAliasing == AliasingKind::kCombine) {
// Moves inserted after should cause all interfering moves to be eliminated.
auto s0 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 0);
auto s1 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 1);
auto s2 = AllocatedOperand(LocationOperand::REGISTER,
MachineRepresentation::kFloat32, 2);
{
ZoneVector<MoveOperands*> to_eliminate(zone());
std::vector<InstructionOperand> moves = {
s0, s2, // s0 <- s2
s1, s2 // s1 <- s2
};
ParallelMove* pm = CreateParallelMove(moves);
MoveOperands m1(d1, d0); // d0 <- d1
pm->PrepareInsertAfter(&m1, &to_eliminate);
CHECK(Contains(&to_eliminate, s0, s2));
CHECK(Contains(&to_eliminate, s1, s2));
}
}
}
} // namespace instruction_unittest
} // namespace compiler
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,584 @@
// Copyright 2024 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 "test/unittests/compiler/backend/turboshaft-instruction-selector-unittest.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/compiler-source-position-table.h"
#include "src/compiler/schedule.h"
#include "src/compiler/turbofan-graph.h"
#include "src/compiler/turboshaft/instruction-selection-phase.h"
#include "src/compiler/turboshaft/phase.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/flags/flags.h"
#include "src/objects/objects-inl.h"
#include "test/unittests/compiler/compiler-test-utils.h"
namespace v8::internal::compiler::turboshaft {
TurboshaftInstructionSelectorTest::TurboshaftInstructionSelectorTest()
: TestWithNativeContextAndZone(kCompressGraphZone),
rng_(v8_flags.random_seed) {}
TurboshaftInstructionSelectorTest::~TurboshaftInstructionSelectorTest() =
default;
TurboshaftInstructionSelectorTest::Stream
TurboshaftInstructionSelectorTest::StreamBuilder::Build(
InstructionSelector::Features features,
TurboshaftInstructionSelectorTest::StreamBuilderMode mode,
InstructionSelector::SourcePositionMode source_position_mode) {
if (v8_flags.trace_turbo) {
StdoutStream{} << "=== Graph before instruction selection ===" << std::endl
<< output_graph();
}
size_t const node_count = output_graph().NumberOfOperationsForDebugging();
EXPECT_NE(0u, node_count);
Linkage linkage(call_descriptor());
Graph& graph = output_graph();
// Compute special RPO order....
TurboshaftSpecialRPONumberer numberer(graph, test_->zone());
auto schedule = numberer.ComputeSpecialRPO();
graph.ReorderBlocks(base::VectorOf(schedule));
// Determine deferred blocks.
PropagateDeferred(graph);
// Initialize an instruction sequence.
InstructionBlocks* instruction_blocks =
InstructionSequence::InstructionBlocksFor(test_->zone(), graph);
InstructionSequence sequence(test_->isolate(), test_->zone(),
instruction_blocks);
TickCounter tick_counter;
size_t max_unoptimized_frame_height = 0;
size_t max_pushed_argument_count = 0;
InstructionSelector selector = InstructionSelector::ForTurboshaft(
test_->zone(), graph.op_id_count(), &linkage, &sequence, &graph, nullptr,
InstructionSelector::kEnableSwitchJumpTable, &tick_counter, nullptr,
&max_unoptimized_frame_height, &max_pushed_argument_count,
source_position_mode, features, InstructionSelector::kDisableScheduling,
InstructionSelector::kEnableRootsRelativeAddressing);
selector.SelectInstructions();
if (v8_flags.trace_turbo) {
StdoutStream{} << "=== Code sequence after instruction selection ==="
<< std::endl
<< sequence;
}
Stream s;
s.virtual_registers_ = selector.GetVirtualRegistersForTesting();
// Map virtual registers.
for (Instruction* const instr : sequence) {
if (instr->opcode() < 0) continue;
if (mode == kTargetInstructions) {
switch (instr->arch_opcode()) {
#define CASE(Name) \
case k##Name: \
break;
TARGET_ARCH_OPCODE_LIST(CASE)
#undef CASE
default:
continue;
}
}
if (mode == kAllExceptNopInstructions && instr->arch_opcode() == kArchNop) {
continue;
}
for (size_t i = 0; i < instr->OutputCount(); ++i) {
InstructionOperand* output = instr->OutputAt(i);
EXPECT_NE(InstructionOperand::IMMEDIATE, output->kind());
if (output->IsConstant()) {
int vreg = ConstantOperand::cast(output)->virtual_register();
s.constants_.insert(std::make_pair(vreg, sequence.GetConstant(vreg)));
}
}
for (size_t i = 0; i < instr->InputCount(); ++i) {
InstructionOperand* input = instr->InputAt(i);
EXPECT_NE(InstructionOperand::CONSTANT, input->kind());
if (input->IsImmediate()) {
auto imm = ImmediateOperand::cast(input);
if (imm->type() == ImmediateOperand::INDEXED_IMM) {
int index = imm->indexed_value();
s.immediates_.insert(
std::make_pair(index, sequence.GetImmediate(imm)));
}
}
}
s.instructions_.push_back(instr);
}
for (auto i : s.virtual_registers_) {
int const virtual_register = i.second;
if (sequence.IsFP(virtual_register)) {
EXPECT_FALSE(sequence.IsReference(virtual_register));
s.doubles_.insert(virtual_register);
}
if (sequence.IsReference(virtual_register)) {
EXPECT_FALSE(sequence.IsFP(virtual_register));
s.references_.insert(virtual_register);
}
}
for (int i = 0; i < sequence.GetDeoptimizationEntryCount(); i++) {
s.deoptimization_entries_.push_back(
sequence.GetDeoptimizationEntry(i).descriptor());
}
return s;
}
int TurboshaftInstructionSelectorTest::Stream::ToVreg(OpIndex index) const {
VirtualRegisters::const_iterator i = virtual_registers_.find(index.id());
CHECK(i != virtual_registers_.end());
return i->second;
}
bool TurboshaftInstructionSelectorTest::Stream::IsFixed(
const InstructionOperand* operand, Register reg) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
if (!unallocated->HasFixedRegisterPolicy()) return false;
return unallocated->fixed_register_index() == reg.code();
}
bool TurboshaftInstructionSelectorTest::Stream::IsSameAsFirst(
const InstructionOperand* operand) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->HasSameAsInputPolicy();
}
bool TurboshaftInstructionSelectorTest::Stream::IsSameAsInput(
const InstructionOperand* operand, int input_index) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->HasSameAsInputPolicy() &&
unallocated->input_index() == input_index;
}
bool TurboshaftInstructionSelectorTest::Stream::IsUsedAtStart(
const InstructionOperand* operand) const {
if (!operand->IsUnallocated()) return false;
const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
return unallocated->IsUsedAtStart();
}
const FrameStateFunctionInfo*
TurboshaftInstructionSelectorTest::StreamBuilder::GetFrameStateFunctionInfo(
uint16_t parameter_count, int local_count) {
const uint16_t max_arguments = 0;
return test_->zone()->New<FrameStateFunctionInfo>(
FrameStateType::kUnoptimizedFunction, parameter_count, max_arguments,
local_count, Handle<SharedFunctionInfo>(), Handle<BytecodeArray>());
}
// -----------------------------------------------------------------------------
// Return.
TARGET_TEST_F(TurboshaftInstructionSelectorTest, ReturnFloat32Constant) {
const float kValue = 4.2f;
StreamBuilder m(this, MachineType::Float32());
m.Return(m.Float32Constant(kValue));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(2U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(InstructionOperand::CONSTANT, s[0]->OutputAt(0)->kind());
EXPECT_FLOAT_EQ(kValue, s.ToFloat32(s[0]->OutputAt(0)));
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
TARGET_TEST_F(TurboshaftInstructionSelectorTest, ReturnParameter) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Int32());
m.Return(m.Parameter(0));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(2U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
TARGET_TEST_F(TurboshaftInstructionSelectorTest, ReturnZero) {
StreamBuilder m(this, MachineType::Int32());
m.Return(m.Int32Constant(0));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(2U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
ASSERT_EQ(1U, s[0]->OutputCount());
EXPECT_EQ(InstructionOperand::CONSTANT, s[0]->OutputAt(0)->kind());
EXPECT_EQ(0, s.ToInt32(s[0]->OutputAt(0)));
EXPECT_EQ(kArchRet, s[1]->arch_opcode());
EXPECT_EQ(2U, s[1]->InputCount());
}
// -----------------------------------------------------------------------------
// Conversions.
TARGET_TEST_F(TurboshaftInstructionSelectorTest,
TruncateFloat64ToWord32WithParameter) {
StreamBuilder m(this, MachineType::Int32(), MachineType::Float64());
m.Return(m.JSTruncateFloat64ToWord32(m.Parameter(0)));
Stream s = m.Build(kAllInstructions);
ASSERT_EQ(3U, s.size());
EXPECT_EQ(kArchNop, s[0]->arch_opcode());
EXPECT_EQ(kArchTruncateDoubleToI, s[1]->arch_opcode());
EXPECT_EQ(1U, s[1]->InputCount());
EXPECT_EQ(1U, s[1]->OutputCount());
EXPECT_EQ(kArchRet, s[2]->arch_opcode());
}
// -----------------------------------------------------------------------------
// Parameters.
TARGET_TEST_F(TurboshaftInstructionSelectorTest, DoubleParameter) {
StreamBuilder m(this, MachineType::Float64(), MachineType::Float64());
OpIndex param = m.Parameter(0);
m.Return(param);
Stream s = m.Build(kAllInstructions);
EXPECT_TRUE(s.IsDouble(param));
}
TARGET_TEST_F(TurboshaftInstructionSelectorTest, ReferenceParameter) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged());
OpIndex param = m.Parameter(0);
m.Return(param);
Stream s = m.Build(kAllInstructions);
EXPECT_TRUE(s.IsReference(param));
}
// -----------------------------------------------------------------------------
// Phi.
using TurboshaftInstructionSelectorPhiTest =
TurboshaftInstructionSelectorTestWithParam<MachineType>;
TARGET_TEST_P(TurboshaftInstructionSelectorPhiTest, Doubleness) {
const MachineType type = GetParam();
StreamBuilder m(this, type, type, type);
OpIndex param0 = m.Parameter(0);
OpIndex param1 = m.Parameter(1);
Block *a = m.NewBlock(), *b = m.NewBlock(), *c = m.NewBlock();
m.Branch(m.Int32Constant(0), a, b);
m.Bind(a);
m.Goto(c);
m.Bind(b);
m.Goto(c);
m.Bind(c);
OpIndex phi = m.Phi(type.representation(), param0, param1);
m.Return(phi);
Stream s = m.Build(kAllInstructions);
EXPECT_EQ(s.IsDouble(phi), s.IsDouble(param0));
EXPECT_EQ(s.IsDouble(phi), s.IsDouble(param1));
}
TARGET_TEST_P(TurboshaftInstructionSelectorPhiTest, Referenceness) {
const MachineType type = GetParam();
StreamBuilder m(this, type, type, type);
OpIndex param0 = m.Parameter(0);
OpIndex param1 = m.Parameter(1);
Block *a = m.NewBlock(), *b = m.NewBlock(), *c = m.NewBlock();
m.Branch(m.Int32Constant(1), a, b);
m.Bind(a);
m.Goto(c);
m.Bind(b);
m.Goto(c);
m.Bind(c);
OpIndex phi = m.Phi(type.representation(), param0, param1);
m.Return(phi);
Stream s = m.Build(kAllInstructions);
EXPECT_EQ(s.IsReference(phi), s.IsReference(param0));
EXPECT_EQ(s.IsReference(phi), s.IsReference(param1));
}
INSTANTIATE_TEST_SUITE_P(
TurboshaftInstructionSelectorTest, TurboshaftInstructionSelectorPhiTest,
::testing::Values(MachineType::Float64(), MachineType::Int8(),
MachineType::Uint8(), MachineType::Int16(),
MachineType::Uint16(), MachineType::Int32(),
MachineType::Uint32(), MachineType::Int64(),
MachineType::Uint64(), MachineType::Pointer(),
MachineType::AnyTagged()));
// TODO(dmercadier): port following tests to Turboshaft.
#if 0
// -----------------------------------------------------------------------------
// Calls with deoptimization.
TARGET_TEST_F(TurboshaftInstructionSelectorTest, CallJSFunctionWithDeopt) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id(42);
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Parameter(2);
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> tagged_type(1, MachineType::AnyTagged(), zone());
ZoneVector<MachineType> empty_type(zone());
auto call_descriptor = Linkage::GetJSCallDescriptor(
zone(), false, 1,
CallDescriptor::kNeedsFrameState | CallDescriptor::kCanUseRoots);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(1));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&empty_type, SparseInputMask::Dense()));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&tagged_type, SparseInputMask::Dense()),
m.UndefinedConstant());
Node* context_sentinel = m.Int32Constant(0);
Node* state_node = m.AddNode(
m.common()->FrameState(bailout_id, OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 0)),
parameters, locals, stack, context_sentinel, function_node,
m.graph()->start());
// Build the call.
Node* nodes[] = {function_node, receiver, m.UndefinedConstant(),
m.Int32Constant(1), context, state_node};
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallJSFunction.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallJSFunction;
index++) {
}
// Now we should have two instructions: call and return.
ASSERT_EQ(index + 2, s.size());
EXPECT_EQ(kArchCallJSFunction, s[index++]->arch_opcode());
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
// TODO(jarin) Check deoptimization table.
}
TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeopt) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id_before(42);
// Some arguments for the call node.
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Int32Constant(1); // Context is ignored.
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> float64_type(1, MachineType::Float64(), zone());
ZoneVector<MachineType> tagged_type(1, MachineType::AnyTagged(), zone());
Callable callable = Builtins::CallableFor(isolate(), Builtin::kToObject);
auto call_descriptor = Linkage::GetStubCallDescriptor(
zone(), callable.descriptor(), 1, CallDescriptor::kNeedsFrameState,
Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.5));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&tagged_type, SparseInputMask::Dense()),
m.UndefinedConstant());
Node* context_sentinel = m.Int32Constant(0);
Node* state_node =
m.AddNode(m.common()->FrameState(bailout_id_before,
OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 1)),
parameters, locals, stack, context_sentinel, function_node,
m.graph()->start());
// Build the call.
Node* stub_code = m.HeapConstant(callable.code());
Node* nodes[] = {stub_code, function_node, receiver, context, state_node};
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallCodeObject.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject;
index++) {
}
// Now we should have two instructions: call, return.
ASSERT_EQ(index + 2, s.size());
// Check the call instruction
const Instruction* call_instr = s[index++];
EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode());
size_t num_operands =
1 + // Code object.
6 + // Frame state deopt id + one input for each value in frame state.
1 + // Function.
1 + // Context.
1; // Entrypoint tag.
ASSERT_EQ(num_operands, call_instr->InputCount());
// Code object.
EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate());
// Deoptimization id.
int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1));
FrameStateDescriptor* desc_before =
s.GetFrameStateDescriptor(deopt_id_before);
EXPECT_EQ(bailout_id_before, desc_before->bailout_id());
EXPECT_EQ(1u, desc_before->parameters_count());
EXPECT_EQ(1u, desc_before->locals_count());
EXPECT_EQ(1u, desc_before->stack_count());
EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(3)));
EXPECT_EQ(0, s.ToInt32(call_instr->InputAt(4))); // This should be a context.
// We inserted 0 here.
EXPECT_EQ(0.5, s.ToFloat64(call_instr->InputAt(5)));
EXPECT_TRUE(IsUndefined(*s.ToHeapObject(call_instr->InputAt(6)), isolate()));
// Function.
EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(7)));
// Context.
EXPECT_EQ(s.ToVreg(context), s.ToVreg(call_instr->InputAt(8)));
// Entrypoint tag.
EXPECT_TRUE(call_instr->InputAt(9)->IsImmediate());
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
EXPECT_EQ(index, s.size());
}
TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeoptRecursiveFrameState) {
StreamBuilder m(this, MachineType::AnyTagged(), MachineType::AnyTagged(),
MachineType::AnyTagged(), MachineType::AnyTagged());
BytecodeOffset bailout_id_before(42);
BytecodeOffset bailout_id_parent(62);
// Some arguments for the call node.
Node* function_node = m.Parameter(0);
Node* receiver = m.Parameter(1);
Node* context = m.Int32Constant(66);
Node* context2 = m.Int32Constant(46);
ZoneVector<MachineType> int32_type(1, MachineType::Int32(), zone());
ZoneVector<MachineType> float64_type(1, MachineType::Float64(), zone());
Callable callable = Builtins::CallableFor(isolate(), Builtin::kToObject);
auto call_descriptor = Linkage::GetStubCallDescriptor(
zone(), callable.descriptor(), 1, CallDescriptor::kNeedsFrameState,
Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(63));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(64));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(65));
Node* frame_state_parent = m.AddNode(
m.common()->FrameState(bailout_id_parent,
OutputFrameStateCombine::Ignore(),
m.GetFrameStateFunctionInfo(1, 1)),
parameters, locals, stack, context, function_node, m.graph()->start());
Node* parameters2 = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals2 = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.25));
Node* stack2 = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(44));
Node* state_node =
m.AddNode(m.common()->FrameState(bailout_id_before,
OutputFrameStateCombine::PokeAt(0),
m.GetFrameStateFunctionInfo(1, 1)),
parameters2, locals2, stack2, context2, function_node,
frame_state_parent);
// Build the call.
Node* stub_code = m.HeapConstant(callable.code());
Node* nodes[] = {stub_code, function_node, receiver, context2, state_node};
Node* call = m.CallNWithFrameState(call_descriptor, arraysize(nodes), nodes);
m.Return(call);
Stream s = m.Build(kAllExceptNopInstructions);
// Skip until kArchCallCodeObject.
size_t index = 0;
for (; index < s.size() && s[index]->arch_opcode() != kArchCallCodeObject;
index++) {
}
// Now we should have three instructions: call, return.
EXPECT_EQ(index + 2, s.size());
// Check the call instruction
const Instruction* call_instr = s[index++];
EXPECT_EQ(kArchCallCodeObject, call_instr->arch_opcode());
size_t num_operands =
1 + // Code object.
1 + // Frame state deopt id
5 + // One input for each value in frame state + context.
5 + // One input for each value in the parent frame state + context.
1 + // Function.
1 + // Context.
1; // Entrypoint tag.
EXPECT_EQ(num_operands, call_instr->InputCount());
// Code object.
EXPECT_TRUE(call_instr->InputAt(0)->IsImmediate());
// Deoptimization id.
int32_t deopt_id_before = s.ToInt32(call_instr->InputAt(1));
FrameStateDescriptor* desc_before =
s.GetFrameStateDescriptor(deopt_id_before);
FrameStateDescriptor* desc_before_outer = desc_before->outer_state();
EXPECT_EQ(bailout_id_before, desc_before->bailout_id());
EXPECT_EQ(1u, desc_before_outer->parameters_count());
EXPECT_EQ(1u, desc_before_outer->locals_count());
EXPECT_EQ(1u, desc_before_outer->stack_count());
// Values from parent environment.
EXPECT_EQ(63, s.ToInt32(call_instr->InputAt(3)));
// Context:
EXPECT_EQ(66, s.ToInt32(call_instr->InputAt(4)));
EXPECT_EQ(64, s.ToInt32(call_instr->InputAt(5)));
EXPECT_EQ(65, s.ToInt32(call_instr->InputAt(6)));
// Values from the nested frame.
EXPECT_EQ(1u, desc_before->parameters_count());
EXPECT_EQ(1u, desc_before->locals_count());
EXPECT_EQ(1u, desc_before->stack_count());
EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(8)));
EXPECT_EQ(46, s.ToInt32(call_instr->InputAt(9)));
EXPECT_EQ(0.25, s.ToFloat64(call_instr->InputAt(10)));
EXPECT_EQ(44, s.ToInt32(call_instr->InputAt(11)));
// Function.
EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(12)));
// Context.
EXPECT_EQ(s.ToVreg(context2), s.ToVreg(call_instr->InputAt(13)));
// Entrypoint tag.
EXPECT_TRUE(call_instr->InputAt(14)->IsImmediate());
// Continuation.
EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
EXPECT_EQ(index, s.size());
}
#endif
} // namespace v8::internal::compiler::turboshaft

View File

@ -0,0 +1,621 @@
// Copyright 2024 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_UNITTESTS_C_BACKEND_TURBOSHAFT_INSTRUCTION_SELECTOR_UNITTEST_H_
#define V8_UNITTESTS_C_BACKEND_TURBOSHAFT_INSTRUCTION_SELECTOR_UNITTEST_H_
#include <deque>
#include <set>
#include <type_traits>
#include "src/base/utils/random-number-generator.h"
#include "src/common/globals.h"
#include "src/compiler/backend/instruction-selector.h"
#include "src/compiler/globals.h"
#include "src/compiler/turboshaft/assembler.h"
#include "src/compiler/turboshaft/index.h"
#include "src/compiler/turboshaft/instruction-selection-normalization-reducer.h"
#include "src/compiler/turboshaft/load-store-simplification-reducer.h"
#include "src/compiler/turboshaft/operations.h"
#include "src/compiler/turboshaft/phase.h"
#include "src/compiler/turboshaft/representations.h"
#include "test/unittests/test-utils.h"
namespace v8::internal::compiler::turboshaft {
#if V8_ENABLE_WEBASSEMBLY
#define SIMD_BINOP_LIST(V) \
FOREACH_SIMD_128_BINARY_OPCODE(V) \
FOREACH_SIMD_128_SHIFT_OPCODE(V)
#else
#define SIMD_BINOP_LIST(V)
#endif // V8_ENABLE_WEBASSEMBLY
#define BINOP_LIST(V) \
SIMD_BINOP_LIST(V) \
V(Word32BitwiseAnd) \
V(Word64BitwiseAnd) \
V(Word32BitwiseOr) \
V(Word64BitwiseOr) \
V(Word32BitwiseXor) \
V(Word64BitwiseXor) \
V(Word32Add) \
V(Word64Add) \
V(Word32Sub) \
V(Word64Sub) \
V(Word32Mul) \
V(Word64Mul) \
V(Int32MulOverflownBits) \
V(Int64MulOverflownBits) \
V(Int32Div) \
V(Int64Div) \
V(Int32Mod) \
V(Int64Mod) \
V(Uint32MulOverflownBits) \
V(Uint64MulOverflownBits) \
V(Uint32Div) \
V(Uint64Div) \
V(Uint32Mod) \
V(Uint64Mod) \
V(Word32ShiftLeft) \
V(Word64ShiftLeft) \
V(Word32ShiftRightLogical) \
V(Word64ShiftRightLogical) \
V(Word32ShiftRightArithmetic) \
V(Word64ShiftRightArithmetic) \
V(Word32RotateRight) \
V(Word64RotateRight) \
V(Int32AddCheckOverflow) \
V(Int64AddCheckOverflow) \
V(Int32SubCheckOverflow) \
V(Int64SubCheckOverflow) \
V(Int32MulCheckOverflow) \
V(Int64MulCheckOverflow) \
V(Word32Equal) \
V(Word64Equal) \
V(Word32NotEqual) \
V(Word64NotEqual) \
V(Int32LessThan) \
V(Int32LessThanOrEqual) \
V(Uint32LessThan) \
V(Uint32LessThanOrEqual) \
V(Int32GreaterThanOrEqual) \
V(Int32GreaterThan) \
V(Uint32GreaterThanOrEqual) \
V(Uint32GreaterThan) \
V(Int64LessThan) \
V(Int64LessThanOrEqual) \
V(Uint64LessThan) \
V(Uint64LessThanOrEqual) \
V(Int64GreaterThanOrEqual) \
V(Int64GreaterThan) \
V(Uint64GreaterThanOrEqual) \
V(Uint64GreaterThan) \
V(Float64Add) \
V(Float32Add) \
V(Float64Sub) \
V(Float32Sub) \
V(Float64Mul) \
V(Float32Mul) \
V(Float64Div) \
V(Float32Div) \
V(Float64Equal) \
V(Float64LessThan) \
V(Float64LessThanOrEqual) \
V(Float32Equal) \
V(Float32LessThan) \
V(Float32LessThanOrEqual)
#define UNOP_LIST(V) \
V(ChangeFloat32ToFloat64) \
V(TruncateFloat64ToFloat32) \
V(ChangeInt32ToInt64) \
V(ChangeUint32ToUint64) \
V(TruncateWord64ToWord32) \
V(ChangeInt32ToFloat64) \
V(ChangeUint32ToFloat64) \
V(ReversibleFloat64ToInt32) \
V(ReversibleFloat64ToUint32)
#define DECL(Op) k##Op,
enum class TSBinop { BINOP_LIST(DECL) };
enum class TSUnop { UNOP_LIST(DECL) };
#undef DECL
class TurboshaftInstructionSelectorTest : public TestWithNativeContextAndZone {
public:
using BaseAssembler = TSAssembler<LoadStoreSimplificationReducer,
InstructionSelectionNormalizationReducer>;
TurboshaftInstructionSelectorTest();
~TurboshaftInstructionSelectorTest() override;
ZoneStats zone_stats_{this->zone()->allocator()};
void SetUp() override {
pipeline_data_ = std::make_unique<PipelineData>(
&zone_stats_, TurboshaftPipelineKind::kJS, isolate_, nullptr,
AssemblerOptions::Default(isolate_));
pipeline_data_->InitializeGraphComponent(nullptr);
}
void TearDown() override { pipeline_data_.reset(); }
PipelineData* data() { return pipeline_data_.get(); }
base::RandomNumberGenerator* rng() { return &rng_; }
class Stream;
enum StreamBuilderMode {
kAllInstructions,
kTargetInstructions,
kAllExceptNopInstructions
};
class StreamBuilder final : public BaseAssembler {
public:
StreamBuilder(TurboshaftInstructionSelectorTest* test,
MachineType return_type)
: BaseAssembler(test->data(), test->graph(), test->graph(),
test->zone()),
test_(test),
call_descriptor_(MakeCallDescriptor(test->zone(), return_type)) {
Init();
}
StreamBuilder(TurboshaftInstructionSelectorTest* test,
MachineType return_type, MachineType parameter0_type)
: BaseAssembler(test->data(), test->graph(), test->graph(),
test->zone()),
test_(test),
call_descriptor_(
MakeCallDescriptor(test->zone(), return_type, parameter0_type)) {
Init();
}
StreamBuilder(TurboshaftInstructionSelectorTest* test,
MachineType return_type, MachineType parameter0_type,
MachineType parameter1_type)
: BaseAssembler(test->data(), test->graph(), test->graph(),
test->zone()),
test_(test),
call_descriptor_(MakeCallDescriptor(
test->zone(), return_type, parameter0_type, parameter1_type)) {
Init();
}
StreamBuilder(TurboshaftInstructionSelectorTest* test,
MachineType return_type, MachineType parameter0_type,
MachineType parameter1_type, MachineType parameter2_type)
: BaseAssembler(test->data(), test->graph(), test->graph(),
test->zone()),
test_(test),
call_descriptor_(MakeCallDescriptor(test->zone(), return_type,
parameter0_type, parameter1_type,
parameter2_type)) {
Init();
}
Stream Build(CpuFeature feature) {
return Build(InstructionSelector::Features(feature));
}
Stream Build(CpuFeature feature1, CpuFeature feature2) {
return Build(InstructionSelector::Features(feature1, feature2));
}
Stream Build(StreamBuilderMode mode = kTargetInstructions) {
return Build(InstructionSelector::Features(), mode);
}
Stream Build(InstructionSelector::Features features,
StreamBuilderMode mode = kTargetInstructions,
InstructionSelector::SourcePositionMode source_position_mode =
InstructionSelector::kAllSourcePositions);
const FrameStateFunctionInfo* GetFrameStateFunctionInfo(
uint16_t parameter_count, int local_count);
// Create a simple call descriptor for testing.
static CallDescriptor* MakeSimpleCallDescriptor(Zone* zone,
MachineSignature* msig) {
LocationSignature::Builder locations(zone, msig->return_count(),
msig->parameter_count());
// Add return location(s).
const int return_count = static_cast<int>(msig->return_count());
for (int i = 0; i < return_count; i++) {
locations.AddReturn(
LinkageLocation::ForCallerFrameSlot(-1 - i, msig->GetReturn(i)));
}
// Just put all parameters on the stack.
const int parameter_count = static_cast<int>(msig->parameter_count());
unsigned slot_index = -1;
for (int i = 0; i < parameter_count; i++) {
locations.AddParam(
LinkageLocation::ForCallerFrameSlot(slot_index, msig->GetParam(i)));
// Slots are kSystemPointerSize sized. This reserves enough for space
// for types that might be bigger, eg. Simd128.
slot_index -=
std::max(1, ElementSizeInBytes(msig->GetParam(i).representation()) /
kSystemPointerSize);
}
const RegList kCalleeSaveRegisters;
const DoubleRegList kCalleeSaveFPRegisters;
MachineType target_type = MachineType::Pointer();
LinkageLocation target_loc = LinkageLocation::ForAnyRegister();
return zone->New<CallDescriptor>( // --
CallDescriptor::kCallAddress, // kind
kDefaultCodeEntrypointTag, // tag
target_type, // target MachineType
target_loc, // target location
locations.Get(), // location_sig
0, // stack_parameter_count
Operator::kNoProperties, // properties
kCalleeSaveRegisters, // callee-saved registers
kCalleeSaveFPRegisters, // callee-saved fp regs
CallDescriptor::kCanUseRoots, // flags
"iselect-test-call");
}
static const TSCallDescriptor* MakeSimpleTSCallDescriptor(
Zone* zone, MachineSignature* msig) {
return TSCallDescriptor::Create(MakeSimpleCallDescriptor(zone, msig),
CanThrow::kYes, LazyDeoptOnThrow::kNo,
zone);
}
CallDescriptor* call_descriptor() { return call_descriptor_; }
OpIndex Emit(TSUnop op, OpIndex input) {
switch (op) {
#define CASE(Op) \
case TSUnop::k##Op: \
return Op(input);
UNOP_LIST(CASE)
#undef CASE
}
}
OpIndex Emit(TSBinop op, OpIndex left, OpIndex right) {
switch (op) {
#define CASE(Op) \
case TSBinop::k##Op: \
return Op(left, right);
BINOP_LIST(CASE)
#undef CASE
}
}
template <typename T>
V<T> Emit(TSBinop op, OpIndex left, OpIndex right) {
OpIndex result = Emit(op, left, right);
DCHECK_EQ(Get(result).outputs_rep().size(), 1);
DCHECK_EQ(Get(result).outputs_rep()[0], v_traits<T>::rep);
return V<T>::Cast(result);
}
// Some helpers to have the same interface as the Turbofan instruction
// selector test had.
V<Word32> Int32Constant(int32_t c) { return Word32Constant(c); }
V<Word64> Int64Constant(int64_t c) { return Word64Constant(c); }
V<Word32> Word32BinaryNot(V<Word32> a) { return Word32Equal(a, 0); }
V<Word32> Word32BitwiseNot(V<Word32> a) { return Word32BitwiseXor(a, -1); }
V<Word64> Word64BitwiseNot(V<Word64> a) { return Word64BitwiseXor(a, -1); }
V<Word32> Word32NotEqual(V<Word32> a, V<Word32> b) {
return Word32BinaryNot(Word32Equal(a, b));
}
V<Word32> Word64NotEqual(V<Word64> a, V<Word64> b) {
return Word32BinaryNot(Word64Equal(a, b));
}
V<Word32> Int32GreaterThanOrEqual(V<Word32> a, V<Word32> b) {
return Int32LessThanOrEqual(b, a);
}
V<Word32> Uint32GreaterThanOrEqual(V<Word32> a, V<Word32> b) {
return Uint32LessThanOrEqual(b, a);
}
V<Word32> Int32GreaterThan(V<Word32> a, V<Word32> b) {
return Int32LessThan(b, a);
}
V<Word32> Uint32GreaterThan(V<Word32> a, V<Word32> b) {
return Uint32LessThan(b, a);
}
V<Word32> Int64GreaterThanOrEqual(V<Word64> a, V<Word64> b) {
return Int64LessThanOrEqual(b, a);
}
V<Word32> Uint64GreaterThanOrEqual(V<Word64> a, V<Word64> b) {
return Uint64LessThanOrEqual(b, a);
}
V<Word32> Int64GreaterThan(V<Word64> a, V<Word64> b) {
return Int64LessThan(b, a);
}
V<Word32> Uint64GreaterThan(V<Word64> a, V<Word64> b) {
return Uint64LessThan(b, a);
}
OpIndex Parameter(int index) {
return Assembler::Parameter(
index, RegisterRepresentation::FromMachineType(
call_descriptor()->GetParameterType(index)));
}
OpIndex Parameter(int index, RegisterRepresentation rep) {
return Assembler::Parameter(index, rep);
}
template <typename T>
V<T> Parameter(int index) {
RegisterRepresentation rep = RegisterRepresentation::FromMachineType(
call_descriptor()->GetParameterType(index));
DCHECK_EQ(rep, v_traits<T>::rep);
return Assembler::Parameter(index, rep);
}
using Assembler::Phi;
template <typename... Args,
typename = std::enable_if_t<
(true && ... && std::is_convertible_v<Args, OpIndex>)>>
OpIndex Phi(MachineRepresentation rep, Args... inputs) {
return Phi({inputs...},
RegisterRepresentation::FromMachineRepresentation(rep));
}
using Assembler::Load;
OpIndex Load(MachineType type, OpIndex base, OpIndex index) {
MemoryRepresentation mem_rep =
MemoryRepresentation::FromMachineType(type);
return Load(base, index, LoadOp::Kind::RawAligned(), mem_rep,
mem_rep.ToRegisterRepresentation());
}
OpIndex Load(MachineType type, OpIndex base) {
MemoryRepresentation mem_rep =
MemoryRepresentation::FromMachineType(type);
return Load(base, LoadOp::Kind::RawAligned(), mem_rep);
}
OpIndex LoadImmutable(MachineType type, OpIndex base, OpIndex index) {
MemoryRepresentation mem_rep =
MemoryRepresentation::FromMachineType(type);
return Load(base, index, LoadOp::Kind::RawAligned().Immutable(), mem_rep);
}
using Assembler::Store;
void Store(MachineRepresentation rep, OpIndex base, OpIndex index,
OpIndex value, WriteBarrierKind write_barrier) {
MemoryRepresentation mem_rep =
MemoryRepresentation::FromMachineRepresentation(rep);
Store(base, index, value, StoreOp::Kind::RawAligned(), mem_rep,
write_barrier);
}
using Assembler::Projection;
OpIndex Projection(OpIndex input, int index) {
const Operation& input_op = output_graph().Get(input);
if (const TupleOp* tuple = input_op.TryCast<TupleOp>()) {
DCHECK_LT(index, tuple->input_count);
return tuple->input(index);
}
DCHECK_LT(index, input_op.outputs_rep().size());
return Projection(input, index, input_op.outputs_rep()[index]);
}
V<Undefined> UndefinedConstant() {
return HeapConstant(test_->isolate_->factory()->undefined_value());
}
#ifdef V8_ENABLE_WEBASSEMBLY
#define DECL_SPLAT(Name) \
V<Simd128> Name##Splat(OpIndex input) { \
return Simd128Splat(input, Simd128SplatOp::Kind::k##Name); \
}
FOREACH_SIMD_128_SPLAT_OPCODE(DECL_SPLAT)
#undef DECL_SPLAT
#define DECL_SIMD128_BINOP(Name) \
V<Simd128> Name(V<Simd128> left, V<Simd128> right) { \
return Simd128Binop(left, right, Simd128BinopOp::Kind::k##Name); \
}
FOREACH_SIMD_128_BINARY_OPCODE(DECL_SIMD128_BINOP)
#undef DECL_SIMD128_BINOP
#define DECL_SIMD128_UNOP(Name) \
V<Simd128> Name(V<Simd128> input) { \
return Simd128Unary(input, Simd128UnaryOp::Kind::k##Name); \
}
FOREACH_SIMD_128_UNARY_OPCODE(DECL_SIMD128_UNOP)
#undef DECL_SIMD128_UNOP
#define DECL_SIMD128_EXTRACT_LANE(Name, Suffix, Type) \
V<Type> Name##Suffix##ExtractLane(V<Simd128> input, uint8_t lane) { \
return V<Type>::Cast(Simd128ExtractLane( \
input, Simd128ExtractLaneOp::Kind::k##Name##Suffix, lane)); \
}
DECL_SIMD128_EXTRACT_LANE(I8x16, S, Word32)
DECL_SIMD128_EXTRACT_LANE(I8x16, U, Word32)
DECL_SIMD128_EXTRACT_LANE(I16x8, S, Word32)
DECL_SIMD128_EXTRACT_LANE(I16x8, U, Word32)
DECL_SIMD128_EXTRACT_LANE(I32x4, , Word32)
DECL_SIMD128_EXTRACT_LANE(I64x2, , Word64)
DECL_SIMD128_EXTRACT_LANE(F32x4, , Float32)
DECL_SIMD128_EXTRACT_LANE(F64x2, , Float64)
#undef DECL_SIMD128_EXTRACT_LANE
#define DECL_SIMD128_REDUCE(Name) \
V<Simd128> Name##AddReduce(V<Simd128> input) { \
return Simd128Reduce(input, Simd128ReduceOp::Kind::k##Name##AddReduce); \
}
DECL_SIMD128_REDUCE(I8x16)
DECL_SIMD128_REDUCE(I16x8)
DECL_SIMD128_REDUCE(I32x4)
DECL_SIMD128_REDUCE(I64x2)
DECL_SIMD128_REDUCE(F32x4)
DECL_SIMD128_REDUCE(F64x2)
#undef DECL_SIMD128_REDUCE
#define DECL_SIMD128_SHIFT(Name) \
V<Simd128> Name(V<Simd128> input, V<Word32> shift) { \
return Simd128Shift(input, shift, Simd128ShiftOp::Kind::k##Name); \
}
FOREACH_SIMD_128_SHIFT_OPCODE(DECL_SIMD128_SHIFT)
#undef DECL_SIMD128_SHIFT
#endif // V8_ENABLE_WEBASSEMBLY
private:
template <typename... ParamT>
CallDescriptor* MakeCallDescriptor(Zone* zone, MachineType return_type,
ParamT... parameter_type) {
MachineSignature::Builder builder(zone, 1, sizeof...(ParamT));
builder.AddReturn(return_type);
(builder.AddParam(parameter_type), ...);
return MakeSimpleCallDescriptor(zone, builder.Get());
}
void Init() {
// We reset the graph since the StreamBuilder is meant to create a new
// fresh graph.
test_->graph().Reset();
// We bind a block right at the start so that test can start emitting
// operations without always needing to bind a block first.
Block* start_block = NewBlock();
Bind(start_block);
}
TurboshaftInstructionSelectorTest* test_;
CallDescriptor* call_descriptor_;
};
class Stream final {
public:
size_t size() const { return instructions_.size(); }
const Instruction* operator[](size_t index) const {
EXPECT_LT(index, size());
return instructions_[index];
}
bool IsDouble(const InstructionOperand* operand) const {
return IsDouble(ToVreg(operand));
}
bool IsDouble(OpIndex index) const { return IsDouble(ToVreg(index)); }
bool IsInteger(const InstructionOperand* operand) const {
return IsInteger(ToVreg(operand));
}
bool IsInteger(OpIndex index) const { return IsInteger(ToVreg(index)); }
bool IsReference(const InstructionOperand* operand) const {
return IsReference(ToVreg(operand));
}
bool IsReference(OpIndex index) const { return IsReference(ToVreg(index)); }
float ToFloat32(const InstructionOperand* operand) const {
return ToConstant(operand).ToFloat32();
}
double ToFloat64(const InstructionOperand* operand) const {
return ToConstant(operand).ToFloat64().value();
}
int32_t ToInt32(const InstructionOperand* operand) const {
return ToConstant(operand).ToInt32();
}
int64_t ToInt64(const InstructionOperand* operand) const {
return ToConstant(operand).ToInt64();
}
DirectHandle<HeapObject> ToHeapObject(
const InstructionOperand* operand) const {
return ToConstant(operand).ToHeapObject();
}
int ToVreg(const InstructionOperand* operand) const {
if (operand->IsConstant()) {
return ConstantOperand::cast(operand)->virtual_register();
}
EXPECT_EQ(InstructionOperand::UNALLOCATED, operand->kind());
return UnallocatedOperand::cast(operand)->virtual_register();
}
int ToVreg(OpIndex index) const;
bool IsFixed(const InstructionOperand* operand, Register reg) const;
bool IsSameAsFirst(const InstructionOperand* operand) const;
bool IsSameAsInput(const InstructionOperand* operand,
int input_index) const;
bool IsUsedAtStart(const InstructionOperand* operand) const;
FrameStateDescriptor* GetFrameStateDescriptor(int deoptimization_id) {
EXPECT_LT(deoptimization_id, GetFrameStateDescriptorCount());
return deoptimization_entries_[deoptimization_id];
}
int GetFrameStateDescriptorCount() {
return static_cast<int>(deoptimization_entries_.size());
}
private:
bool IsDouble(int virtual_register) const {
return doubles_.find(virtual_register) != doubles_.end();
}
bool IsInteger(int virtual_register) const {
return !IsDouble(virtual_register) && !IsReference(virtual_register);
}
bool IsReference(int virtual_register) const {
return references_.find(virtual_register) != references_.end();
}
Constant ToConstant(const InstructionOperand* operand) const {
ConstantMap::const_iterator i;
if (operand->IsConstant()) {
i = constants_.find(ConstantOperand::cast(operand)->virtual_register());
EXPECT_EQ(ConstantOperand::cast(operand)->virtual_register(), i->first);
EXPECT_FALSE(constants_.end() == i);
} else {
EXPECT_EQ(InstructionOperand::IMMEDIATE, operand->kind());
auto imm = ImmediateOperand::cast(operand);
if (imm->type() == ImmediateOperand::INLINE_INT32) {
return Constant(imm->inline_int32_value());
} else if (imm->type() == ImmediateOperand::INLINE_INT64) {
return Constant(imm->inline_int64_value());
}
i = immediates_.find(imm->indexed_value());
EXPECT_EQ(imm->indexed_value(), i->first);
EXPECT_FALSE(immediates_.end() == i);
}
return i->second;
}
friend class StreamBuilder;
using ConstantMap = std::map<int, Constant>;
using VirtualRegisters = std::map<uint32_t, int>;
ConstantMap constants_;
ConstantMap immediates_;
std::deque<Instruction*> instructions_;
std::set<int> doubles_;
std::set<int> references_;
VirtualRegisters virtual_registers_;
std::deque<FrameStateDescriptor*> deoptimization_entries_;
};
base::RandomNumberGenerator rng_;
Graph& graph() { return pipeline_data_->graph(); }
Isolate* isolate_ = this->isolate();
std::unique_ptr<turboshaft::PipelineData> pipeline_data_;
};
template <typename T>
class TurboshaftInstructionSelectorTestWithParam
: public TurboshaftInstructionSelectorTest,
public ::testing::WithParamInterface<T> {};
} // namespace v8::internal::compiler::turboshaft
#endif // V8_UNITTESTS_C_BACKEND_TURBOSHAFT_INSTRUCTION_SELECTOR_UNITTEST_H_