// 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/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 { // Generates a binop arithmetic instruction, followed by an integer compare zero // and select. This is to test a possible merge of the arithmetic op and the // compare for use by the select. We test a matrix of configurations: // - floating-point and integer select. // - add, sub, mul, and, or and xor. // - int32, uint32t, int64_t, uint64_t, float and double. // - one or multiple users of the binary operation. // - two different graph layouts (single block vs three blocks). namespace { enum GraphConfig { kOneUse, kTwoUsesOneBlock, kTwoUsesTwoBlocks }; constexpr GraphConfig graph_configs[] = {GraphConfig::kOneUse, GraphConfig::kTwoUsesOneBlock, GraphConfig::kTwoUsesTwoBlocks}; #define SELECT_OP_LIST(V) \ V(Word32Select) \ V(Word64Select) \ V(Float32Select) \ V(Float64Select) enum class SelectOperator { #define DEF(kind) k##kind, SELECT_OP_LIST(DEF) #undef DEF }; bool SelectIsSupported(SelectOperator op) { // SupportedOperations::Initialize is usually called by the Turboshaft // Assembler, but some tests use this function before having created an // Assembler, so we manually call it here to make sure that the // SupportedOperations list is indeed initialized. SupportedOperations::Initialize(); switch (op) { case SelectOperator::kWord32Select: return SupportedOperations::word32_select(); case SelectOperator::kWord64Select: return SupportedOperations::word64_select(); case SelectOperator::kFloat32Select: return SupportedOperations::float32_select(); case SelectOperator::kFloat64Select: return SupportedOperations::float64_select(); } } // kOneUse: // (bin_res = binop lhs, rhs) // (return (select (compare bin_res, zero, cond), tval, fval)) // // kTwoUsesOneBlock: // (bin_res = binop lhs, rhs) // (return (add (select (compare bin_res, zero, cond), tval, fval), bin_res)) // // kTwoUsesTwoBlocks: // Same as above, but the final addition is conditionally executed in a // different block. // (bin_res = binop lhs, rhs) // (select_res = (select (compare bin_res, zero, cond), tval, fval)) // (select_res >= tval) // ? (return select_res) // : (return (add select_res, bin_res)) template class ConditionalSelectGen { public: ConditionalSelectGen(BufferedRawMachineAssemblerTester& m, GraphConfig c, TurboshaftComparison icmp_op, TurboshaftBinop bin_op) : m_(m), config_(c), cmpw_(icmp_op), binw_(bin_op), blocka_(m.NewBlock()), blockb_(m.NewBlock()) {} void BuildGraph(SelectOperator select_op, OpIndex lhs, OpIndex rhs, OpIndex tval, OpIndex fval) { CompareAndSelect(select_op, lhs, rhs, tval, fval); switch (config()) { case GraphConfig::kOneUse: m().Return(select()); break; case GraphConfig::kTwoUsesOneBlock: m().Return(AddBinopUse()); break; case GraphConfig::kTwoUsesTwoBlocks: m().Return(AddBranchAndUse()); break; default: UNREACHABLE(); } } void CompareAndSelect(SelectOperator selectop, OpIndex lhs, OpIndex rhs, OpIndex tval, OpIndex fval) { OpIndex zero = Is32() ? OpIndex{m().Word32Constant(0)} : OpIndex{m().Word64Constant(static_cast(0))}; bin_node_ = binw().MakeNode(m(), lhs, rhs); OpIndex cond = cmpw().MakeNode(m(), bin_node(), zero); select_ = MakeSelect(selectop, cond, tval, fval); select_op_ = selectop; } OpIndex AddBranchAndUse() { OpIndex cond_second_input = m().Get(select()).template Cast().vtrue(); V cond; switch (select_op()) { case SelectOperator::kFloat32Select: cond = m().Float32LessThan(select(), cond_second_input); break; case SelectOperator::kFloat64Select: cond = m().Float64LessThan(select(), cond_second_input); break; case SelectOperator::kWord32Select: cond = m().Int32LessThan(select(), cond_second_input); break; case SelectOperator::kWord64Select: cond = m().Int64LessThan(select(), cond_second_input); break; } m().Branch(cond, blocka(), blockb()); m().Bind(blocka()); OpIndex res = AddBinopUse(); m().Return(res); m().Bind(blockb()); return select(); } ResultType expected(CondType lhs, CondType rhs, ResultType tval, ResultType fval) { CondType bin_node_res = binw().eval(lhs, rhs); ResultType res = Is32() ? cmpw().Int32Compare(static_cast(bin_node_res), 0) ? tval : fval : cmpw().Int64Compare(static_cast(bin_node_res), 0) ? tval : fval; if (config() == GraphConfig::kTwoUsesTwoBlocks && res >= tval) { return res; } if (config() != GraphConfig::kOneUse) { res += static_cast(bin_node_res); } return res; } BufferedRawMachineAssemblerTester& m() { return m_; } GraphConfig config() const { return config_; } IntBinopWrapper& binw() { return binw_; } CompareWrapper& cmpw() { return cmpw_; } OpIndex select() const { return select_; } SelectOperator select_op() const { return select_op_; } OpIndex bin_node() const { return bin_node_; } Block* blocka() { return blocka_; } Block* blockb() { return blockb_; } virtual OpIndex AddBinopUse() = 0; virtual bool Is32() const = 0; private: OpIndex MakeSelect(SelectOperator op, OpIndex cond, OpIndex vtrue, OpIndex vfalse) { switch (op) { #define CASE(kind) \ case SelectOperator::k##kind: \ return m().kind(cond, vtrue, vfalse); SELECT_OP_LIST(CASE) #undef CASE } } BufferedRawMachineAssemblerTester& m_; GraphConfig config_; CompareWrapper cmpw_; IntBinopWrapper binw_; OpIndex bin_node_; OpIndex select_; SelectOperator select_op_; Block *blocka_, *blockb_; }; template class UInt32ConditionalSelectGen : public ConditionalSelectGen { public: using ConditionalSelectGen::ConditionalSelectGen; OpIndex AddBinopUse() override { BufferedRawMachineAssemblerTester& m = this->m(); switch (this->select_op()) { case SelectOperator::kFloat32Select: return m.Float32Add(this->select(), m.ChangeUint32ToFloat32(this->bin_node())); case SelectOperator::kFloat64Select: return m.Float64Add(this->select(), m.ChangeUint32ToFloat64(this->bin_node())); case SelectOperator::kWord32Select: return m.Word32Add(this->select(), this->bin_node()); case SelectOperator::kWord64Select: return m.Word64Add(this->select(), m.ChangeUint32ToUint64(this->bin_node())); } } bool Is32() const override { return true; } }; template class UInt64ConditionalSelectGen : public ConditionalSelectGen { public: using ConditionalSelectGen::ConditionalSelectGen; OpIndex AddBinopUse() override { BufferedRawMachineAssemblerTester& m = this->m(); switch (this->select_op()) { case SelectOperator::kFloat32Select: return m.Float32Add(this->select(), m.ChangeUint64ToFloat32(this->bin_node())); case SelectOperator::kFloat64Select: return m.Float64Add(this->select(), m.ChangeUint64ToFloat64(this->bin_node())); case SelectOperator::kWord32Select: return m.Word32Add(this->select(), m.TruncateWord64ToWord32(this->bin_node())); case SelectOperator::kWord64Select: return m.Word64Add(this->select(), this->bin_node()); } } bool Is32() const override { return false; } }; constexpr TurboshaftComparison int32_cmp_opcodes[] = { TurboshaftComparison::kWord32Equal, TurboshaftComparison::kInt32LessThan, TurboshaftComparison::kInt32LessThanOrEqual, TurboshaftComparison::kUint32LessThan, TurboshaftComparison::kUint32LessThanOrEqual}; constexpr TurboshaftBinop int32_bin_opcodes[] = { TurboshaftBinop::kWord32Add, TurboshaftBinop::kWord32Sub, TurboshaftBinop::kWord32Mul, TurboshaftBinop::kWord32BitwiseAnd, TurboshaftBinop::kWord32BitwiseOr, TurboshaftBinop::kWord32BitwiseXor, }; TEST(Word32SelectCombineInt32CompareZero) { if (!SelectIsSupported(SelectOperator::kWord32Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int32_cmp_opcodes) { for (auto bin : int32_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Int32(), MachineType::Int32()); UInt32ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kWord32Select, lhs, rhs, tval, fval); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { uint32_t expected = gen.expected(a, b, 2, 1); uint32_t actual = m.Call(a, b, 2, 1); CHECK_EQ(expected, actual); } } } } } } TEST(Word64SelectCombineInt32CompareZero) { if (!SelectIsSupported(SelectOperator::kWord64Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int32_cmp_opcodes) { for (auto bin : int32_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Uint64(), MachineType::Uint64()); UInt32ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kWord64Select, lhs, rhs, tval, fval); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { uint64_t c = 2; uint64_t d = 1; uint64_t expected = gen.expected(a, b, c, d); uint64_t actual = m.Call(a, b, c, d); CHECK_EQ(expected, actual); } } } } } } TEST(Float32SelectCombineInt32CompareZero) { if (!SelectIsSupported(SelectOperator::kFloat32Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int32_cmp_opcodes) { for (auto bin : int32_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Float32(), MachineType::Float32()); UInt32ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kFloat32Select, lhs, rhs, tval, fval); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { float expected = gen.expected(a, b, 2.0f, 1.0f); float actual = m.Call(a, b, 2.0f, 1.0f); CHECK_FLOAT_EQ(expected, actual); } } } } } } TEST(Float64SelectCombineInt32CompareZero) { if (!SelectIsSupported(SelectOperator::kFloat64Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int32_cmp_opcodes) { for (auto bin : int32_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint32(), MachineType::Uint32(), MachineType::Float64(), MachineType::Float64()); UInt32ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kFloat64Select, lhs, rhs, tval, fval); FOR_UINT32_INPUTS(a) { FOR_UINT32_INPUTS(b) { double expected = gen.expected(a, b, 2.0, 1.0); double actual = m.Call(a, b, 2.0, 1.0); CHECK_DOUBLE_EQ(expected, actual); } } } } } } constexpr TurboshaftBinop int64_bin_opcodes[] = { TurboshaftBinop::kWord64Add, TurboshaftBinop::kWord64Sub, TurboshaftBinop::kWord64Mul, TurboshaftBinop::kWord64BitwiseAnd, TurboshaftBinop::kWord64BitwiseOr, TurboshaftBinop::kWord64BitwiseXor, }; constexpr TurboshaftComparison int64_cmp_opcodes[] = { TurboshaftComparison::kWord64Equal, TurboshaftComparison::kInt64LessThan, TurboshaftComparison::kInt64LessThanOrEqual, TurboshaftComparison::kUint64LessThan, TurboshaftComparison::kUint64LessThanOrEqual}; TEST(Word32SelectCombineInt64CompareZero) { RawMachineAssemblerTester features(MachineType::Int32()); if (!SelectIsSupported(SelectOperator::kWord32Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int64_cmp_opcodes) { for (auto bin : int64_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint64(), MachineType::Uint64(), MachineType::Int32(), MachineType::Int32()); UInt64ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kWord32Select, lhs, rhs, tval, fval); FOR_UINT64_INPUTS(a) { FOR_UINT64_INPUTS(b) { uint32_t expected = gen.expected(a, b, 2, 1); uint32_t actual = m.Call(a, b, 2, 1); CHECK_EQ(expected, actual); } } } } } } TEST(Word64SelectCombineInt64CompareZero) { RawMachineAssemblerTester features(MachineType::Uint32()); if (!SelectIsSupported(SelectOperator::kWord64Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int64_cmp_opcodes) { for (auto bin : int64_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint64(), MachineType::Uint64(), MachineType::Uint64(), MachineType::Uint64()); UInt64ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kWord64Select, lhs, rhs, tval, fval); FOR_UINT64_INPUTS(a) { FOR_UINT64_INPUTS(b) { uint64_t c = 2; uint64_t d = 1; uint64_t expected = gen.expected(a, b, c, d); uint64_t actual = m.Call(a, b, c, d); CHECK_EQ(expected, actual); } } } } } } TEST(Float32SelectCombineInt64CompareZero) { RawMachineAssemblerTester features(MachineType::Uint32()); if (!SelectIsSupported(SelectOperator::kFloat32Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int64_cmp_opcodes) { for (auto bin : int64_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint64(), MachineType::Uint64(), MachineType::Float32(), MachineType::Float32()); UInt64ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kFloat32Select, lhs, rhs, tval, fval); FOR_UINT64_INPUTS(a) { FOR_UINT64_INPUTS(b) { float expected = gen.expected(a, b, 2.0f, 1.0f); float actual = m.Call(a, b, 2.0f, 1.0f); CHECK_FLOAT_EQ(expected, actual); } } } } } } TEST(Float64SelectCombineInt64CompareZero) { RawMachineAssemblerTester features(MachineType::Uint32()); if (!SelectIsSupported(SelectOperator::kFloat64Select)) { return; } for (auto config : graph_configs) { for (auto cmp : int64_cmp_opcodes) { for (auto bin : int64_bin_opcodes) { BufferedRawMachineAssemblerTester m( MachineType::Uint64(), MachineType::Uint64(), MachineType::Float64(), MachineType::Float64()); UInt64ConditionalSelectGen gen(m, config, cmp, bin); OpIndex lhs = m.Parameter(0); OpIndex rhs = m.Parameter(1); OpIndex tval = m.Parameter(2); OpIndex fval = m.Parameter(3); gen.BuildGraph(SelectOperator::kFloat64Select, lhs, rhs, tval, fval); FOR_UINT64_INPUTS(a) { FOR_UINT64_INPUTS(b) { double expected = gen.expected(a, b, 2.0, 1.0); double actual = m.Call(a, b, 2.0, 1.0); CHECK_DOUBLE_EQ(expected, actual); } } } } } } } // end namespace } // namespace v8::internal::compiler::turboshaft