summaryrefslogtreecommitdiff
path: root/deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc')
-rw-r--r--deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc839
1 files changed, 839 insertions, 0 deletions
diff --git a/deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc b/deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc
new file mode 100644
index 0000000000..59d5dccd06
--- /dev/null
+++ b/deps/v8/test/unittests/compiler/backend/instruction-selector-unittest.cc
@@ -0,0 +1,839 @@
+// 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/code-factory.h"
+#include "src/compiler/compiler-source-position-table.h"
+#include "src/compiler/graph.h"
+#include "src/compiler/schedule.h"
+#include "src/flags.h"
+#include "src/objects-inl.h"
+#include "test/unittests/compiler/compiler-test-utils.h"
+
+namespace v8 {
+namespace internal {
+namespace compiler {
+
+InstructionSelectorTest::InstructionSelectorTest() : rng_(FLAG_random_seed) {}
+
+InstructionSelectorTest::~InstructionSelectorTest() = default;
+
+InstructionSelectorTest::Stream InstructionSelectorTest::StreamBuilder::Build(
+ InstructionSelector::Features features,
+ InstructionSelectorTest::StreamBuilderMode mode,
+ InstructionSelector::SourcePositionMode source_position_mode) {
+ Schedule* schedule = Export();
+ if (FLAG_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());
+ InstructionSelector selector(
+ test_->zone(), node_count, &linkage, &sequence, schedule,
+ &source_position_table, nullptr,
+ InstructionSelector::kEnableSwitchJumpTable, source_position_mode,
+ features, InstructionSelector::kDisableScheduling,
+ InstructionSelector::kEnableRootsRelativeAddressing,
+ PoisoningMitigationLevel::kPoisonAll);
+ selector.SelectInstructions();
+ if (FLAG_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) {
+ 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::IsUsedAtStart(
+ const InstructionOperand* operand) const {
+ if (!operand->IsUnallocated()) return false;
+ const UnallocatedOperand* unallocated = UnallocatedOperand::cast(operand);
+ return unallocated->IsUsedAtStart();
+}
+
+const FrameStateFunctionInfo*
+InstructionSelectorTest::StreamBuilder::GetFrameStateFunctionInfo(
+ int parameter_count, int local_count) {
+ return common()->CreateFrameStateFunctionInfo(
+ FrameStateType::kInterpretedFunction, parameter_count, local_count,
+ Handle<SharedFunctionInfo>());
+}
+
+// -----------------------------------------------------------------------------
+// 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.
+
+typedef InstructionSelectorTestWithParam<MachineType>
+ InstructionSelectorPhiTest;
+
+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_CASE_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());
+
+ BailoutId 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> empty_types(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_types, SparseInputMask::Dense()));
+ Node* stack = m.AddNode(
+ m.common()->TypedStateValues(&empty_types, SparseInputMask::Dense()));
+ 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.UndefinedConstant());
+
+ // 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());
+
+ BailoutId 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(), Builtins::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.UndefinedConstant());
+
+ // 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 kArchCallJSFunction.
+ 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.
+ 1 + // Poison index
+ 6 + // Frame state deopt id + one input for each value in frame state.
+ 1 + // Function.
+ 1; // Context.
+ 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(2));
+ 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(4)));
+ EXPECT_EQ(0, s.ToInt32(call_instr->InputAt(5))); // This should be a context.
+ // We inserted 0 here.
+ EXPECT_EQ(0.5, s.ToFloat64(call_instr->InputAt(6)));
+ EXPECT_TRUE(s.ToHeapObject(call_instr->InputAt(7))->IsUndefined(isolate()));
+
+ // Function.
+ EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(8)));
+ // Context.
+ EXPECT_EQ(s.ToVreg(context), s.ToVreg(call_instr->InputAt(9)));
+
+ 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());
+
+ BailoutId bailout_id_before(42);
+ BailoutId 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> int32x2_type(2, MachineType::Int32(), zone());
+ ZoneVector<MachineType> float64_type(1, MachineType::Float64(), zone());
+
+ Callable callable = Builtins::CallableFor(isolate(), Builtins::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.UndefinedConstant());
+
+ 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(&int32x2_type, SparseInputMask::Dense()),
+ m.Int32Constant(44), m.Int32Constant(45));
+ 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 kArchCallJSFunction.
+ 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 + // Poison index.
+ 1 + // Frame state deopt id
+ 6 + // 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.
+ 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(2));
+ 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(4)));
+ // Context:
+ EXPECT_EQ(66, s.ToInt32(call_instr->InputAt(5)));
+ EXPECT_EQ(64, s.ToInt32(call_instr->InputAt(6)));
+ EXPECT_EQ(65, s.ToInt32(call_instr->InputAt(7)));
+ // Values from the nested frame.
+ EXPECT_EQ(1u, desc_before->parameters_count());
+ EXPECT_EQ(1u, desc_before->locals_count());
+ EXPECT_EQ(2u, desc_before->stack_count());
+ EXPECT_EQ(43, s.ToInt32(call_instr->InputAt(9)));
+ EXPECT_EQ(46, s.ToInt32(call_instr->InputAt(10)));
+ EXPECT_EQ(0.25, s.ToFloat64(call_instr->InputAt(11)));
+ EXPECT_EQ(44, s.ToInt32(call_instr->InputAt(12)));
+ EXPECT_EQ(45, s.ToInt32(call_instr->InputAt(13)));
+
+ // Function.
+ EXPECT_EQ(s.ToVreg(function_node), s.ToVreg(call_instr->InputAt(14)));
+ // Context.
+ EXPECT_EQ(s.ToVreg(context2), s.ToVreg(call_instr->InputAt(15)));
+ // Continuation.
+
+ EXPECT_EQ(kArchRet, s[index++]->arch_opcode());
+ EXPECT_EQ(index, s.size());
+}
+
+// Helper to make calls to private InstructionSelector shuffle functions.
+class InstructionSelectorShuffleTest : public ::testing::Test {
+ public:
+ using Shuffle = std::array<uint8_t, kSimd128Size>;
+
+ struct TestShuffle {
+ Shuffle non_canonical;
+ Shuffle canonical;
+ bool needs_swap;
+ bool is_swizzle;
+ };
+
+ // Call testing members in InstructionSelector.
+ static void CanonicalizeShuffle(bool inputs_equal, Shuffle* shuffle,
+ bool* needs_swap, bool* is_swizzle) {
+ InstructionSelector::CanonicalizeShuffleForTesting(
+ inputs_equal, &(*shuffle)[0], needs_swap, is_swizzle);
+ }
+
+ static bool TryMatchIdentity(const Shuffle& shuffle) {
+ return InstructionSelector::TryMatchIdentityForTesting(&shuffle[0]);
+ }
+ template <int LANES>
+ static bool TryMatchDup(const Shuffle& shuffle, int* index) {
+ return InstructionSelector::TryMatchDupForTesting<LANES>(&shuffle[0],
+ index);
+ }
+ static bool TryMatch32x4Shuffle(const Shuffle& shuffle,
+ uint8_t* shuffle32x4) {
+ return InstructionSelector::TryMatch32x4ShuffleForTesting(&shuffle[0],
+ shuffle32x4);
+ }
+ static bool TryMatch16x8Shuffle(const Shuffle& shuffle,
+ uint8_t* shuffle16x8) {
+ return InstructionSelector::TryMatch16x8ShuffleForTesting(&shuffle[0],
+ shuffle16x8);
+ }
+ static bool TryMatchConcat(const Shuffle& shuffle, uint8_t* offset) {
+ return InstructionSelector::TryMatchConcatForTesting(&shuffle[0], offset);
+ }
+ static bool TryMatchBlend(const Shuffle& shuffle) {
+ return InstructionSelector::TryMatchBlendForTesting(&shuffle[0]);
+ }
+};
+
+bool operator==(const InstructionSelectorShuffleTest::Shuffle& a,
+ const InstructionSelectorShuffleTest::Shuffle& b) {
+ for (int i = 0; i < kSimd128Size; ++i) {
+ if (a[i] != b[i]) return false;
+ }
+ return true;
+}
+
+TEST_F(InstructionSelectorShuffleTest, CanonicalizeShuffle) {
+ const bool kInputsEqual = true;
+ const bool kNeedsSwap = true;
+ const bool kIsSwizzle = true;
+
+ bool needs_swap;
+ bool is_swizzle;
+
+ // Test canonicalization driven by input shuffle.
+ TestShuffle test_shuffles[] = {
+ // Identity is canonical.
+ {{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ !kNeedsSwap,
+ kIsSwizzle},
+ // Non-canonical identity requires a swap.
+ {{{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}},
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ kNeedsSwap,
+ kIsSwizzle},
+ // General shuffle, canonical is unchanged.
+ {{{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23}},
+ {{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23}},
+ !kNeedsSwap,
+ !kIsSwizzle},
+ // Non-canonical shuffle requires a swap.
+ {{{16, 0, 17, 1, 18, 2, 19, 3, 20, 4, 21, 5, 22, 6, 23, 7}},
+ {{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23}},
+ kNeedsSwap,
+ !kIsSwizzle},
+ };
+ for (size_t i = 0; i < arraysize(test_shuffles); ++i) {
+ Shuffle shuffle = test_shuffles[i].non_canonical;
+ CanonicalizeShuffle(!kInputsEqual, &shuffle, &needs_swap, &is_swizzle);
+ EXPECT_EQ(shuffle, test_shuffles[i].canonical);
+ EXPECT_EQ(needs_swap, test_shuffles[i].needs_swap);
+ EXPECT_EQ(is_swizzle, test_shuffles[i].is_swizzle);
+ }
+
+ // Test canonicalization when inputs are equal (explicit swizzle).
+ TestShuffle test_swizzles[] = {
+ // Identity is canonical.
+ {{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ !kNeedsSwap,
+ kIsSwizzle},
+ // Non-canonical identity requires a swap.
+ {{{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}},
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}},
+ !kNeedsSwap,
+ kIsSwizzle},
+ // Canonicalized to swizzle.
+ {{{0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23}},
+ {{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7}},
+ !kNeedsSwap,
+ kIsSwizzle},
+ // Canonicalized to swizzle.
+ {{{16, 0, 17, 1, 18, 2, 19, 3, 20, 4, 21, 5, 22, 6, 23, 7}},
+ {{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7}},
+ !kNeedsSwap,
+ kIsSwizzle},
+ };
+ for (size_t i = 0; i < arraysize(test_swizzles); ++i) {
+ Shuffle shuffle = test_swizzles[i].non_canonical;
+ CanonicalizeShuffle(kInputsEqual, &shuffle, &needs_swap, &is_swizzle);
+ EXPECT_EQ(shuffle, test_swizzles[i].canonical);
+ EXPECT_EQ(needs_swap, test_swizzles[i].needs_swap);
+ EXPECT_EQ(is_swizzle, test_swizzles[i].is_swizzle);
+ }
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatchIdentity) {
+ // Match shuffle that returns first source operand.
+ EXPECT_TRUE(TryMatchIdentity(
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}));
+ // The non-canonicalized identity shuffle doesn't match.
+ EXPECT_FALSE(TryMatchIdentity(
+ {{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}}));
+ // Even one lane out of place is not an identity shuffle.
+ EXPECT_FALSE(TryMatchIdentity(
+ {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31}}));
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatchDup) {
+ int index;
+ // All lanes from the same 32 bit source lane.
+ EXPECT_TRUE(TryMatchDup<4>({{4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}},
+ &index));
+ EXPECT_EQ(1, index);
+ // It shouldn't match for other vector shapes.
+ EXPECT_FALSE(TryMatchDup<8>(
+ {{4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}}, &index));
+ EXPECT_FALSE(TryMatchDup<16>(
+ {{4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7, 4, 5, 6, 7}}, &index));
+ // All lanes from the same 16 bit source lane.
+ EXPECT_TRUE(TryMatchDup<8>(
+ {{16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17}},
+ &index));
+ EXPECT_EQ(8, index);
+ // It shouldn't match for other vector shapes.
+ EXPECT_FALSE(TryMatchDup<4>(
+ {{16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17}},
+ &index));
+ EXPECT_FALSE(TryMatchDup<16>(
+ {{16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17, 16, 17}},
+ &index));
+ // All lanes from the same 8 bit source lane.
+ EXPECT_TRUE(TryMatchDup<16>(
+ {{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}}, &index));
+ EXPECT_EQ(7, index);
+ // It shouldn't match for other vector shapes.
+ EXPECT_FALSE(TryMatchDup<4>(
+ {{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}}, &index));
+ EXPECT_FALSE(TryMatchDup<8>(
+ {{7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}}, &index));
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatchConcat) {
+ uint8_t offset;
+ // Ascending indices, jump at end to same input (concatenating swizzle).
+ EXPECT_TRUE(TryMatchConcat(
+ {{3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2}}, &offset));
+ EXPECT_EQ(3, offset);
+ // Ascending indices, jump at end to other input (concatenating shuffle).
+ EXPECT_TRUE(TryMatchConcat(
+ {{4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}}, &offset));
+ EXPECT_EQ(4, offset);
+
+ // Shuffles that should not match:
+ // Ascending indices, but jump isn't at end/beginning.
+ EXPECT_FALSE(TryMatchConcat(
+ {{3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6}}, &offset));
+ // Ascending indices, but multiple jumps.
+ EXPECT_FALSE(TryMatchConcat(
+ {{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3}}, &offset));
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatch32x4Shuffle) {
+ uint8_t shuffle32x4[4];
+ // Match if each group of 4 bytes is from the same 32 bit lane.
+ EXPECT_TRUE(TryMatch32x4Shuffle(
+ {{12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 16, 17, 18, 19}},
+ shuffle32x4));
+ EXPECT_EQ(3, shuffle32x4[0]);
+ EXPECT_EQ(2, shuffle32x4[1]);
+ EXPECT_EQ(1, shuffle32x4[2]);
+ EXPECT_EQ(4, shuffle32x4[3]);
+ // Bytes must be in order in the 32 bit lane.
+ EXPECT_FALSE(TryMatch32x4Shuffle(
+ {{12, 13, 14, 14, 8, 9, 10, 11, 4, 5, 6, 7, 16, 17, 18, 19}},
+ shuffle32x4));
+ // Each group must start with the first byte in the 32 bit lane.
+ EXPECT_FALSE(TryMatch32x4Shuffle(
+ {{13, 14, 15, 12, 8, 9, 10, 11, 4, 5, 6, 7, 16, 17, 18, 19}},
+ shuffle32x4));
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatch16x8Shuffle) {
+ uint8_t shuffle16x8[8];
+ // Match if each group of 2 bytes is from the same 16 bit lane.
+ EXPECT_TRUE(TryMatch16x8Shuffle(
+ {{12, 13, 30, 31, 8, 9, 26, 27, 4, 5, 22, 23, 16, 17, 2, 3}},
+ shuffle16x8));
+ EXPECT_EQ(6, shuffle16x8[0]);
+ EXPECT_EQ(15, shuffle16x8[1]);
+ EXPECT_EQ(4, shuffle16x8[2]);
+ EXPECT_EQ(13, shuffle16x8[3]);
+ EXPECT_EQ(2, shuffle16x8[4]);
+ EXPECT_EQ(11, shuffle16x8[5]);
+ EXPECT_EQ(8, shuffle16x8[6]);
+ EXPECT_EQ(1, shuffle16x8[7]);
+ // Bytes must be in order in the 16 bit lane.
+ EXPECT_FALSE(TryMatch16x8Shuffle(
+ {{12, 13, 30, 30, 8, 9, 26, 27, 4, 5, 22, 23, 16, 17, 2, 3}},
+ shuffle16x8));
+ // Each group must start with the first byte in the 16 bit lane.
+ EXPECT_FALSE(TryMatch16x8Shuffle(
+ {{12, 13, 31, 30, 8, 9, 26, 27, 4, 5, 22, 23, 16, 17, 2, 3}},
+ shuffle16x8));
+}
+
+TEST_F(InstructionSelectorShuffleTest, TryMatchBlend) {
+ // Match if each byte remains in place.
+ EXPECT_TRUE(TryMatchBlend(
+ {{0, 17, 2, 19, 4, 21, 6, 23, 8, 25, 10, 27, 12, 29, 14, 31}}));
+ // Identity is a blend.
+ EXPECT_TRUE(
+ TryMatchBlend({{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}}));
+ // Even one lane out of place is not a blend.
+ EXPECT_FALSE(TryMatchBlend(
+ {{1, 17, 2, 19, 4, 21, 6, 23, 8, 25, 10, 27, 12, 29, 14, 31}}));
+}
+
+} // namespace compiler
+} // namespace internal
+} // namespace v8