// 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 "src/base/vector.h" #include "src/objects/objects-inl.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/turboshaft-codegen-tester.h" #include "test/common/value-helper.h" namespace v8::internal::compiler::turboshaft { namespace { constexpr TurboshaftBinop kLogicOpcodes[] = {TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr}; constexpr std::array kInt32CmpOpcodes = { #ifdef V8_COMPRESS_POINTERS TurboshaftComparison::kTaggedEqual, #endif // V8_COMPRESS_POINTERS TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan, TurboshaftComparison::kInt32LessThanOrEqual, TurboshaftComparison::kUint32LessThan, TurboshaftComparison::kUint32LessThanOrEqual}; #ifdef V8_COMPRESS_POINTERS constexpr size_t kNumInt32Cmps = 6; #else constexpr size_t kNumInt32Cmps = 5; #endif // V8_COMPRESS_POINTERS #if V8_TARGET_ARCH_64_BIT constexpr std::array kInt64CmpOpcodes = { #ifndef V8_COMPRESS_POINTERS TurboshaftComparison::kTaggedEqual, #endif // V8_COMPRESS_POINTERS TurboshaftComparison::kWord64Equal, TurboshaftComparison::kInt64LessThan, TurboshaftComparison::kInt64LessThanOrEqual, TurboshaftComparison::kUint64LessThan, TurboshaftComparison::kUint64LessThanOrEqual}; #ifdef V8_COMPRESS_POINTERS constexpr size_t kNumInt64Cmps = 5; #else constexpr size_t kNumInt64Cmps = 6; #endif // V8_COMPRESS_POINTERS #endif // V8_TARGET_ARCH_64_BIT enum GraphShape { kBalanced, kUnbalanced }; enum InvertPattern { kNoInvert, kInvertCompare, kInvertLogic, kInvertCompareAndLogic, kInvertCompareDouble, kInvertLogicDouble }; enum BranchPattern { kNone, kDirect, kEqualZero, kNotEqualZero }; constexpr GraphShape kGraphShapes[] = {kBalanced, kUnbalanced}; constexpr InvertPattern kInvertPatterns[] = {kNoInvert, kInvertCompare, kInvertLogic, kInvertCompareAndLogic, kInvertCompareDouble, kInvertLogicDouble}; constexpr BranchPattern kBranchPatterns[] = {kNone, kDirect, kEqualZero, kNotEqualZero}; // These are shorter versions of ValueHelper::uint32_vector() and // ValueHelper::uint64_vector() (which are used by FOR_UINT32_INPUTS and // FOR_UINT64_INPUTS). static constexpr uint32_t uint32_test_array[] = { 0x00000000, 0x00000001, 0xFFFFFFFF, 0x1B09788B, 0x00000005, 0x00000008, 0x273A798E, 0x56123761, 0xFFFFFFFD, 0x001FFFFF, 0x0007FFFF, 0x7FC00000, 0x7F876543}; static constexpr auto uint32_test_vector = base::VectorOf(uint32_test_array); #ifdef V8_TARGET_ARCH_64_BIT static constexpr uint64_t uint64_test_array[] = { 0x00000000, 0x00000001, 0xFFFFFFFF, 0x1B09788B, 0x00000008, 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFE, 0x0000000100000000, 0x1B09788B00000000, 0x273A798E187937A3, 0xECE3AF835495A16B, 0x80000000EEEEEEEE, 0x007FFFFFDDDDDDDD, 0x8000000000000000, 0x7FF8000000000000, 0x7FF7654321FEDCBA}; static constexpr auto uint64_test_vector = base::VectorOf(uint64_test_array); #endif // Given kGraphShapes and kInvertPatterns, defined above, the graphs produced by // the test framework, with four compares, are illustrated below. In the cases // where we insert a branch, this takes the final logic node as the input. // kBalanced - kNoInvert // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- --> logic <-- // | | // ---------> logic <-------- // // kBalanced - kInvertCompare // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // --> logic <-- --> logic <-- // | | // | | // ---------> logic <-------- // kBalanced - kInvertCompareDouble // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // not | not | // | | | | // --> logic <-- --> logic <-- // | | // | | // ---------> logic <-------- // kBalanced - kInvertLogic // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- --> logic <-- // | | // not | // ---------> logic <-------- // kBalanced - kInvertLogicDouble // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- --> logic <-- // | | // not | // | | // not | // ---------> logic <-------- // kBalanced - kInvertCompareAndLogic // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // --> logic <-- --> logic <-- // | | // not | // ---------> logic <-------- // kUnbalanced - kNoInvert // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- | | // | | | // --------> logic <-- | // | | // -----> logic <----- // kUnbalanced - kInvertCompare // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // --> logic <-- | | // | | | // --------> logic <-- | // | | // -----> logic <----- // kUnbalanced - kInvertCompareDouble // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // not | not | // | | | | // --> logic <-- | | // | | | // --------> logic <-- | // | | // -----> logic <----- // kUnbalanced - kInvertLogic // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- | | // | | | // not | | // --------> logic <-- | // | | // not | // | | // -----> logic <----- // kUnbalanced - kInvertLogicDouble // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // --> logic <-- | | // | | | // not | | // | | | // not | | // --------> logic <-- | // | | // not | // | | // not | // | | // -----> logic <----- // kUnbalanced - kInvertCompareAndLogic // a b c d a b c d // | | | | | | | | // | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | // not | not | // | | | | // --> logic <-- | | // | | | // not | | // --------> logic <-- | // | | // not | // | | // -----> logic <----- template class CombineCompares { static constexpr uint32_t NumInputs = 4; static constexpr uint32_t NumCompares = NumLogic + 1; static_assert(NumLogic > 0); // a b c d a b NumInputs = 4 // | | | | | | // | | | | | | // -> cmp <- -> cmp <- -> cmp <- NumCompares = 3 // | | | // --> logic <-- | --------- // | | NumLogic = 2 // ------> logic <----- --------- public: CombineCompares(RawMachineAssemblerTester& m, GraphShape shape, InvertPattern invert_pattern, BranchPattern branch_pattern, std::array logic_ops, std::array compare_ops) : m_(m), graph_shape_(shape), invert_pattern_(invert_pattern), branch_pattern_(branch_pattern), logic_ops_(logic_ops), compare_ops_(compare_ops) {} void GenerateReturn(V combine) { if (branch_pattern() == kNone) { m().Return(combine); } else { blocka_ = m().NewBlock(); blockb_ = m().NewBlock(); if (branch_pattern() == kDirect) { m().Branch(static_cast>(combine), blocka(), blockb()); } else if (branch_pattern() == kEqualZero) { m().Branch(m().Word32Equal(combine, m().Word32Constant(0)), blocka(), blockb()); } else { auto cond = static_cast>( MakeNot(m().Word32Equal(combine, m().Word32Constant(0)))); m().Branch(cond, blocka(), blockb()); } m().Bind(blocka()); m().Return(m().Word32Constant(1)); m().Bind(blockb()); m().Return(m().Word32Constant(0)); } } V MakeBinop(TurboshaftBinop op, V lhs, V rhs) { switch (op) { case TurboshaftBinop::kWord32BitwiseAnd: return m().Word32BitwiseAnd(lhs, rhs); case TurboshaftBinop::kWord32BitwiseOr: return m().Word32BitwiseOr(lhs, rhs); default: UNREACHABLE(); } } V MakeCompare(TurboshaftComparison op, OpIndex lhs, OpIndex rhs) { switch (op) { default: UNREACHABLE(); case TurboshaftComparison::kTaggedEqual: #ifdef V8_COMPRESS_POINTERS return m().Word32Equal(lhs, rhs); #else return m().Word64Equal(lhs, rhs); #endif // V8_COMPRESS_POINTERS case TurboshaftComparison::kWord32Equal: return m().Word32Equal(lhs, rhs); case TurboshaftComparison::kInt32LessThan: return m().Int32LessThan(lhs, rhs); case TurboshaftComparison::kInt32LessThanOrEqual: return m().Int32LessThanOrEqual(lhs, rhs); case TurboshaftComparison::kUint32LessThan: return m().Uint32LessThan(lhs, rhs); case TurboshaftComparison::kUint32LessThanOrEqual: return m().Uint32LessThanOrEqual(lhs, rhs); case TurboshaftComparison::kWord64Equal: return m().Word64Equal(lhs, rhs); case TurboshaftComparison::kInt64LessThan: return m().Int64LessThan(lhs, rhs); case TurboshaftComparison::kInt64LessThanOrEqual: return m().Int64LessThanOrEqual(lhs, rhs); case TurboshaftComparison::kUint64LessThan: return m().Uint64LessThan(lhs, rhs); case TurboshaftComparison::kUint64LessThanOrEqual: return m().Uint64LessThanOrEqual(lhs, rhs); } } V MakeNot(V node) { return m().Word32Equal(node, m().Word32Constant(0)); } V MakeNotCompare(V node) { V inverted = MakeNot(node); if (invert_pattern() == kInvertCompareDouble) { return MakeNot(inverted); } else { return inverted; } } V MakeNotLogic(V node) { V inverted = MakeNot(node); if (invert_pattern() == kInvertLogicDouble) { return MakeNot(inverted); } else { return inverted; } } bool ShouldDoubleInvert() const { return invert_pattern() == kInvertLogicDouble || invert_pattern() == kInvertCompareDouble; } bool ShouldInvertLogic() const { return invert_pattern() == kInvertLogic || invert_pattern() == kInvertCompareAndLogic || invert_pattern() == kInvertLogicDouble; } bool ShouldInvertCompare() const { return invert_pattern() == kInvertCompare || invert_pattern() == kInvertCompareAndLogic || invert_pattern() == kInvertCompareDouble; } void BuildGraph(std::array& inputs) { std::array, NumCompares> compares; for (unsigned i = 0; i < NumCompares; ++i) { OpIndex a = inputs.at((2 * i) % NumInputs); OpIndex b = inputs.at((2 * i + 1) % NumInputs); V cmp = MakeCompare(CompareOpcode(i), a, b); // Invert every other compare, starting with the first. if (ShouldInvertCompare() && (i % 1)) { compares[i] = MakeNotCompare(cmp); } else { compares[i] = cmp; } } V first_combine = MakeBinop(LogicOpcode(0), compares[0], compares[1]); if (NumLogic == 1) { if (ShouldInvertLogic()) { return GenerateReturn(MakeNotLogic(first_combine)); } return GenerateReturn(first_combine); } if (graph_shape() == kUnbalanced) { V combine = first_combine; for (unsigned i = 1; i < NumLogic; ++i) { // Invert every other logic operation, beginning with the first. if (ShouldInvertLogic() && (i % 1)) { combine = MakeNotLogic(combine); } combine = MakeBinop(LogicOpcode(i), compares.at(i + 1), combine); } return GenerateReturn(combine); } else { constexpr uint32_t NumFirstLayerLogic = NumCompares / 2; std::array, NumFirstLayerLogic> first_layer_logic{ first_combine}; for (unsigned i = 1; i < NumFirstLayerLogic; ++i) { first_layer_logic[i] = MakeBinop(LogicOpcode(i), compares.at(2 * i), compares.at(2 * i + 1)); } V combine = first_combine; // Invert every other first layer logic operation, beginning with the // first. if (ShouldInvertLogic()) { combine = MakeNotLogic(combine); } for (unsigned i = 1; i < NumFirstLayerLogic; ++i) { V logic_node = first_layer_logic.at(i); if (ShouldInvertLogic() && !(i % 2)) { logic_node = MakeNotLogic(logic_node); } uint32_t logic_idx = NumFirstLayerLogic + i - 1; combine = MakeBinop(LogicOpcode(logic_idx), logic_node, combine); } GenerateReturn(combine); } } uint32_t ExpectedReturn(uint32_t combine) const { if (branch_pattern() == kNone) { return combine; } else if (branch_pattern() == kDirect) { return combine == 0 ? 0 : 1; } else if (branch_pattern() == kEqualZero) { return combine == 0 ? 1 : 0; } else { return combine != 0 ? 1 : 0; } } uint32_t InvertCompare(uint32_t v) const { return invert_pattern() == kInvertCompareDouble ? v : !v; } uint32_t InvertLogic(uint32_t v) const { return invert_pattern() == kInvertLogicDouble ? v : !v; } uint32_t Expected(std::array& inputs) { std::array compare_results; for (unsigned i = 0; i < NumCompares; ++i) { CompareType cmp_lhs = inputs.at((2 * i) % NumInputs); CompareType cmp_rhs = inputs.at((2 * i + 1) % NumInputs); CompareWrapper cmpw = CompareWrapper(CompareOpcode(i)); uint32_t cmp_res = EvalCompare(cmpw, cmp_lhs, cmp_rhs); // Invert every other compare, starting with the first. if (ShouldInvertCompare() && (i % 1)) { compare_results[i] = InvertCompare(cmp_res); } else { compare_results[i] = cmp_res; } } auto logicw = IntBinopWrapper(LogicOpcode(0)); uint32_t first_combine = logicw.eval(compare_results[0], compare_results[1]); if (NumLogic == 1) { if (ShouldInvertLogic()) { first_combine = InvertLogic(first_combine); } return ExpectedReturn(first_combine); } if (graph_shape() == kUnbalanced) { uint32_t combine = first_combine; for (unsigned i = 1; i < NumLogic; ++i) { // Invert every other logic operation, beginning with the first. if (ShouldInvertLogic() && (i % 1)) { combine = InvertLogic(combine); } logicw = IntBinopWrapper(LogicOpcode(i)); combine = logicw.eval(compare_results.at(i + 1), combine); } return ExpectedReturn(combine); } else { constexpr uint32_t NumFirstLayerLogic = NumCompares / 2; std::array first_layer_logic{first_combine}; for (unsigned i = 1; i < NumFirstLayerLogic; ++i) { logicw = IntBinopWrapper(LogicOpcode(i)); first_layer_logic[i] = logicw.eval(compare_results.at(2 * i), compare_results.at(2 * i + 1)); } uint32_t combine = first_combine; // Invert every other first layer logic operation, beginning with the // first. if (ShouldInvertLogic()) { combine = InvertLogic(combine); } for (unsigned i = 1; i < NumFirstLayerLogic; ++i) { uint32_t logic_res = first_layer_logic.at(i); if (ShouldInvertLogic() && !(i % 2)) { logic_res = InvertLogic(logic_res); } uint32_t logic_idx = NumFirstLayerLogic + i - 1; logicw = IntBinopWrapper(LogicOpcode(logic_idx)); combine = logicw.eval(logic_res, combine); } return ExpectedReturn(combine); } } virtual uint32_t EvalCompare(CompareWrapper& cmpw, CompareType lhs, CompareType rhs) const = 0; virtual OpIndex Zero() const = 0; virtual OpIndex One() const = 0; virtual OpIndex ThirtyTwo() const = 0; RawMachineAssemblerTester& m() const { return m_; } GraphShape graph_shape() const { return graph_shape_; } InvertPattern invert_pattern() const { return invert_pattern_; } BranchPattern branch_pattern() const { return branch_pattern_; } TurboshaftBinop LogicOpcode(uint32_t i) const { return logic_ops_.at(i); } TurboshaftComparison CompareOpcode(uint32_t i) const { return compare_ops_.at(i); } Block* blocka() { return blocka_; } Block* blockb() { return blockb_; } private: RawMachineAssemblerTester& m_; GraphShape graph_shape_; InvertPattern invert_pattern_; BranchPattern branch_pattern_; Block* blocka_; Block* blockb_; std::array logic_ops_; std::array compare_ops_; }; template class CombineCompareWord32 : public CombineCompares { public: using CombineCompares::CombineCompares; uint32_t EvalCompare(CompareWrapper& cmpw, uint32_t lhs, uint32_t rhs) const override { return cmpw.Int32Compare(lhs, rhs); } OpIndex Zero() const override { return this->m().Word32Constant(0); } OpIndex One() const override { return this->m().Word32Constant(1); } OpIndex ThirtyTwo() const override { return this->m().Word32Constant(32); } }; template class CombineCompareWord64 : public CombineCompares { public: using CombineCompares::CombineCompares; uint32_t EvalCompare(CompareWrapper& cmpw, uint64_t lhs, uint64_t rhs) const override { return cmpw.Int64Compare(lhs, rhs); } OpIndex Zero() const override { return this->m().Word64Constant(static_cast(0)); } OpIndex One() const override { return this->m().Word64Constant(static_cast(1)); } OpIndex ThirtyTwo() const override { return this->m().Word64Constant(static_cast(32)); } }; template void CombineCompareLogic1( const std::array& cmp_opcodes, MachineType (*input_type)(void), const base::Vector& input_vector) { constexpr GraphShape shape = kBalanced; for (auto cmp0 : cmp_opcodes) { for (auto cmp1 : cmp_opcodes) { for (auto logic : kLogicOpcodes) { for (auto invert_pattern : kInvertPatterns) { for (auto branch_pattern : kBranchPatterns) { RawMachineAssemblerTester m(input_type(), input_type(), input_type(), input_type()); std::array logic_ops = {logic}; std::array compare_ops = {cmp0, cmp1}; Combiner gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); for (auto a : input_vector) { for (auto b : input_vector) { std::array call_inputs{a, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } } } } TEST(CombineCompareWord32Logic1) { CombineCompareLogic1, uint32_t, kNumInt32Cmps>( kInt32CmpOpcodes, MachineType::Uint32, uint32_test_vector); } #if V8_TARGET_ARCH_64_BIT TEST(CombineCompareWord64Logic1) { CombineCompareLogic1, uint64_t, kNumInt64Cmps>( kInt64CmpOpcodes, MachineType::Uint64, uint64_test_vector); } #endif template void CombineCompareLogic2( const std::array& cmp_opcodes, MachineType (*input_type)(void), const base::Vector& input_vector) { constexpr GraphShape shape = kUnbalanced; constexpr BranchPattern branch_pattern = kNone; auto cmp0 = cmp_opcodes[3]; auto cmp1 = cmp_opcodes[2]; auto cmp2 = cmp_opcodes[1]; std::array compare_ops = {cmp0, cmp1, cmp2}; for (auto logic0 : kLogicOpcodes) { for (auto logic1 : kLogicOpcodes) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m(input_type(), input_type(), input_type(), input_type()); std::array logic_ops = {logic0, logic1}; Combiner gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); for (auto a : input_vector) { for (auto b : input_vector) { std::array call_inputs{a, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } } TEST(CombineCompareWord32Logic2) { CombineCompareLogic2, uint32_t, kNumInt32Cmps>( kInt32CmpOpcodes, MachineType::Uint32, uint32_test_vector); } #if V8_TARGET_ARCH_64_BIT TEST(CombineCompareWord64Logic2) { CombineCompareLogic2, uint64_t, kNumInt64Cmps>( kInt64CmpOpcodes, MachineType::Uint64, uint64_test_vector); } #endif template void CombineCompareLogic3Zero( const std::array& cmp_opcodes, MachineType (*input_type)(void), const base::Vector& input_vector) { constexpr BranchPattern branch_pattern = kNone; auto cmp0 = cmp_opcodes[0]; auto cmp1 = cmp_opcodes[1]; auto cmp2 = cmp_opcodes[2]; auto cmp3 = cmp_opcodes[3]; std::array compare_ops = {cmp0, cmp1, cmp2, cmp3}; for (auto logic0 : kLogicOpcodes) { for (auto logic1 : kLogicOpcodes) { for (auto logic2 : kLogicOpcodes) { for (auto shape : kGraphShapes) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m(input_type(), input_type(), input_type(), input_type()); std::array logic_ops = {logic0, logic1, logic2}; Combiner gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), gen.Zero(), m.Parameter(3), }; gen.BuildGraph(inputs); for (auto a : input_vector) { for (auto b : input_vector) { std::array call_inputs{a, b, 0, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } } } } TEST(CombineCompareWord32Logic3Zero) { CombineCompareLogic3Zero, uint32_t, kNumInt32Cmps>( kInt32CmpOpcodes, MachineType::Uint32, uint32_test_vector); } #if V8_TARGET_ARCH_64_BIT TEST(CombineCompareWord64Logic3Zero) { CombineCompareLogic3Zero, uint64_t, kNumInt64Cmps>( kInt64CmpOpcodes, MachineType::Uint64, uint64_test_vector); } #endif template void CombineCompareLogic3One( const std::array& cmp_opcodes, MachineType (*input_type)(void), const base::Vector& input_vector) { constexpr BranchPattern branch_pattern = kNone; auto cmp0 = cmp_opcodes[4]; auto cmp1 = cmp_opcodes[1]; auto cmp2 = cmp_opcodes[2]; auto cmp3 = cmp_opcodes[0]; std::array compare_ops = {cmp0, cmp1, cmp2, cmp3}; for (auto logic0 : kLogicOpcodes) { for (auto logic1 : kLogicOpcodes) { for (auto logic2 : kLogicOpcodes) { for (auto shape : kGraphShapes) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m(input_type(), input_type(), input_type(), input_type()); std::array logic_ops = {logic0, logic1, logic2}; Combiner gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { gen.One(), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); for (auto a : input_vector) { for (auto b : input_vector) { std::array call_inputs{1, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } } } } TEST(CombineCompareWord32Logic3One) { CombineCompareLogic3One, uint32_t, kNumInt32Cmps>( kInt32CmpOpcodes, MachineType::Uint32, uint32_test_vector); } #if V8_TARGET_ARCH_64_BIT TEST(CombineCompareWord64Logic3One) { CombineCompareLogic3One, uint64_t, kNumInt64Cmps>( kInt64CmpOpcodes, MachineType::Uint64, uint64_test_vector); } #endif template void CombineCompareLogic3ThirtyTwo( const std::array& cmp_opcodes, MachineType (*input_type)(void), const base::Vector& input_vector) { constexpr BranchPattern branch_pattern = kNone; auto cmp0 = cmp_opcodes[0]; auto cmp1 = cmp_opcodes[3]; auto cmp2 = cmp_opcodes[2]; auto cmp3 = cmp_opcodes[4]; std::array compare_ops = {cmp0, cmp1, cmp2, cmp3}; for (auto logic0 : kLogicOpcodes) { for (auto logic1 : kLogicOpcodes) { for (auto logic2 : kLogicOpcodes) { for (auto shape : kGraphShapes) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m(input_type(), input_type(), input_type(), input_type()); std::array logic_ops = {logic0, logic1, logic2}; Combiner gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), gen.ThirtyTwo(), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); for (auto a : input_vector) { for (auto b : input_vector) { std::array call_inputs{a, 32, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } } } } TEST(CombineCompareWord32Logic3ThirtyTwo) { CombineCompareLogic3ThirtyTwo, uint32_t, kNumInt32Cmps>( kInt32CmpOpcodes, MachineType::Uint32, uint32_test_vector); } #if V8_TARGET_ARCH_64_BIT TEST(CombineCompareWord64Logic3ThirtyTwo) { CombineCompareLogic3ThirtyTwo, uint64_t, kNumInt64Cmps>( kInt64CmpOpcodes, MachineType::Uint64, uint64_test_vector); } #endif constexpr uint32_t kMaxDepth = 4; // a b b a a b b a a b // | | | | | | | | | | // | | | | | | | | | | // -> cmp <- -> cmp <- -> cmp <- -> cmp <- -> cmp <- // | | | | | // ---> and <--- | | | // | | | | // ---------> or <---- | | // | | | // ------> and <------ | // | | // --------> or <-------- TEST(CombineCompareMaxDepth) { constexpr GraphShape shape = kUnbalanced; constexpr BranchPattern branch_pattern = kNone; std::array logic_ops = { TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr}; std::array compare_ops = {TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan, TurboshaftComparison::kInt32LessThanOrEqual, TurboshaftComparison::kUint32LessThan, TurboshaftComparison::kUint32LessThanOrEqual}; for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32()); CombineCompareWord32 gen(m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { std::array call_inputs{a, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } TEST(CombineCompareBranchesMaxDepth) { constexpr GraphShape shape = kUnbalanced; std::array logic_ops = { TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr}; std::array compare_ops = {TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan, TurboshaftComparison::kInt32LessThanOrEqual, TurboshaftComparison::kUint32LessThan, TurboshaftComparison::kUint32LessThanOrEqual}; for (auto branch_pattern : kBranchPatterns) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32()); CombineCompareWord32 gen( m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { std::array call_inputs{a, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } TEST(CombineCompareMaxDepthPlusOne) { std::array logic_ops = { TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseAnd}; std::array compare_ops = { TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan, TurboshaftComparison::kInt32LessThanOrEqual, TurboshaftComparison::kUint32LessThan, TurboshaftComparison::kUint32LessThanOrEqual, TurboshaftComparison::kWord32Equal, }; constexpr BranchPattern branch_pattern = kNone; for (auto shape : kGraphShapes) { for (auto invert_pattern : kInvertPatterns) { RawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32()); CombineCompareWord32 gen( m, shape, invert_pattern, branch_pattern, logic_ops, compare_ops); std::array inputs = { m.Parameter(0), m.Parameter(1), m.Parameter(2), m.Parameter(3), }; gen.BuildGraph(inputs); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { std::array call_inputs{a, b, b, a}; uint32_t expected = gen.Expected(call_inputs); uint32_t actual = m.Call(a, b, b, a); CHECK_EQ(expected, actual); } } } } } TEST(CombineCompareTwoLogicInputs) { // cmp cmp cmp cmp cmp cmp // | | | | | | // logic logic logic // | | | // - cmp - | // | | // -- logic -- auto run = [](uint32_t a, uint32_t b, uint32_t c, uint32_t d) { bool cmp1 = static_cast(a) < static_cast(b); bool cmp2 = static_cast(a) <= 1024; bool cmp3 = static_cast(c) < static_cast(d); bool cmp4 = static_cast(c) < 4096; bool cmp5 = a < d; bool cmp6 = b <= c; bool logic1 = cmp1 && cmp2; bool logic2 = cmp3 || cmp4; bool logic3 = cmp5 && cmp6; bool cmp7 = logic1 == logic2; return static_cast(cmp7 || logic3); }; RawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint32()); V cmp1 = m.Int32LessThan(m.Parameter(0), m.Parameter(1)); V cmp2 = m.Int32LessThanOrEqual(m.Parameter(0), m.Word32Constant(1024)); V cmp3 = m.Int32LessThan(m.Parameter(2), m.Parameter(3)); V cmp4 = m.Int32LessThanOrEqual(m.Parameter(2), m.Word32Constant(4096)); V cmp5 = m.Uint32LessThan(m.Parameter(0), m.Parameter(3)); V cmp6 = m.Uint32LessThanOrEqual(m.Parameter(1), m.Parameter(2)); V logic1 = m.Word32BitwiseAnd(cmp1, cmp2); V logic2 = m.Word32BitwiseOr(cmp3, cmp4); V logic3 = m.Word32BitwiseAnd(cmp5, cmp6); V cmp7 = m.Word32Equal(logic1, logic2); m.Return(m.Word32BitwiseOr(cmp7, logic3)); for (uint32_t a : uint32_test_vector) { for (uint32_t b : uint32_test_vector) { for (uint32_t c : uint32_test_vector) { for (uint32_t d : uint32_test_vector) { uint32_t result = m.Call(a, b, c, d); uint32_t expected = run(a, b, c, d); CHECK_EQ(result, expected); } } } } } } // end namespace } // namespace v8::internal::compiler::turboshaft