| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <array> |
| #include <iostream> |
| |
| #include "gmock/gmock.h" |
| #include "source/spirv_target_env.h" |
| #include "source/table2.h" |
| #include "test/unit_spirv.h" |
| |
| using ::testing::ContainerEq; |
| using ::testing::ValuesIn; |
| |
| namespace spvtools { |
| namespace { |
| |
| struct OpcodeLookupCase { |
| std::string name; |
| uint32_t opcode; |
| bool expect_pass = true; |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, const OpcodeLookupCase& olc) { |
| os << "OLC('" << olc.name << "', " << olc.opcode << ", expect pass? " |
| << olc.expect_pass << ")"; |
| return os; |
| } |
| |
| using OpcodeLookupTest = ::testing::TestWithParam<OpcodeLookupCase>; |
| |
| TEST_P(OpcodeLookupTest, OpcodeLookup_ByName) { |
| const InstructionDesc* desc = nullptr; |
| auto status = LookupOpcode(GetParam().name.data(), &desc); |
| if (GetParam().expect_pass) { |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| EXPECT_EQ(static_cast<uint32_t>(desc->opcode), GetParam().opcode); |
| } else { |
| EXPECT_NE(status, SPV_SUCCESS); |
| EXPECT_EQ(desc, nullptr); |
| } |
| } |
| |
| TEST_P(OpcodeLookupTest, OpcodeLookup_ByOpcode_Success) { |
| const InstructionDesc* desc = nullptr; |
| if (GetParam().expect_pass) { |
| spv::Op opcode = static_cast<spv::Op>(GetParam().opcode); |
| auto status = LookupOpcode(opcode, &desc); |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| EXPECT_EQ(desc->opcode, opcode); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Samples, OpcodeLookupTest, |
| ValuesIn(std::vector<OpcodeLookupCase>{ |
| {"Nop", 0}, |
| {"WritePipe", 275}, |
| {"TypeAccelerationStructureKHR", 5341}, |
| {"TypeAccelerationStructureNV", 5341}, |
| {"does not exist", 0, false}, |
| {"CopyLogical", 400}, |
| {"FPGARegINTEL", 5949}, |
| {"SubgroupMatrixMultiplyAccumulateINTEL", 6237}, |
| })); |
| |
| TEST(OpcodeLookupSingleTest, OpcodeLookup_ByOpcode_Fails) { |
| // This list may need adjusting over time. |
| std::array<uint32_t, 3> bad_opcodes = {{99999, 37737, 110101}}; |
| for (auto bad_opcode : bad_opcodes) { |
| const InstructionDesc* desc = nullptr; |
| spv::Op opcode = static_cast<spv::Op>(bad_opcode); |
| auto status = LookupOpcode(opcode, &desc); |
| EXPECT_NE(status, SPV_SUCCESS); |
| ASSERT_EQ(desc, nullptr); |
| } |
| } |
| |
| struct OpcodeLookupEnvCase { |
| std::string name; |
| uint32_t opcode; |
| spv_target_env env = SPV_ENV_UNIVERSAL_1_0; |
| bool expect_pass = true; |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, const OpcodeLookupEnvCase& olec) { |
| os << "OLC('" << olec.name << "', " << olec.opcode << ", env " |
| << spvTargetEnvDescription(olec.env) << ", expect pass? " |
| << olec.expect_pass << ")"; |
| return os; |
| } |
| |
| using OpcodeLookupEnvTest = ::testing::TestWithParam<OpcodeLookupEnvCase>; |
| |
| TEST_P(OpcodeLookupEnvTest, OpcodeLookupForEnv_ByName) { |
| const InstructionDesc* desc = nullptr; |
| auto status = |
| LookupOpcodeForEnv(GetParam().env, GetParam().name.data(), &desc); |
| if (GetParam().expect_pass) { |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| EXPECT_EQ(static_cast<uint32_t>(desc->opcode), GetParam().opcode); |
| } else { |
| EXPECT_NE(status, SPV_SUCCESS); |
| EXPECT_EQ(desc, nullptr); |
| } |
| } |
| |
| TEST_P(OpcodeLookupEnvTest, OpcodeLookupForEnv_ByOpcode) { |
| const InstructionDesc* desc = nullptr; |
| spv::Op opcode = static_cast<spv::Op>(GetParam().opcode); |
| auto status = LookupOpcodeForEnv(GetParam().env, opcode, &desc); |
| if (GetParam().expect_pass) { |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| EXPECT_EQ(desc->opcode, opcode); |
| } else { |
| // Skip nonsense cases created for the lookup-by-name case. |
| if (GetParam().name != "does not exist") { |
| EXPECT_NE(status, SPV_SUCCESS); |
| EXPECT_EQ(desc, nullptr); |
| } |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Samples, OpcodeLookupEnvTest, |
| ValuesIn(std::vector<OpcodeLookupEnvCase>{ |
| {"Nop", 0}, |
| {"WritePipe", 275}, |
| {"TypeAccelerationStructureKHR", 5341}, |
| {"TypeAccelerationStructureNV", 5341}, |
| {"does not exist", 0, SPV_ENV_UNIVERSAL_1_0, |
| false}, |
| {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_0, false}, |
| {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_3, false}, |
| {"CopyLogical", 400, SPV_ENV_UNIVERSAL_1_4, true}, |
| {"FPGARegINTEL", 5949}, |
| {"SubgroupMatrixMultiplyAccumulateINTEL", 6237}, |
| })); |
| |
| TEST(OpcodeLookupExtInstTest, Operands) { |
| // The SPIR-V spec grammar has a single rule for OpExtInst, where the last |
| // item is "sequence of Ids". SPIRV-Tools handles it differently. It drops |
| // that last item, and instead specifies those operands as operands of the |
| // extended instruction enum, such as 'cos'. |
| // See https://github.com/KhronosGroup/SPIRV-Tools/issues/233 |
| // Test the exact sequence of operand types extracted for OpExtInst. |
| const InstructionDesc* desc = nullptr; |
| auto status = LookupOpcode("ExtInst", &desc); |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| |
| EXPECT_EQ(desc->operands_range.count(), 4u); |
| |
| auto operands = desc->operands(); |
| using vtype = std::vector<spv_operand_type_t>; |
| |
| EXPECT_THAT( |
| vtype(operands.begin(), operands.end()), |
| ContainerEq(vtype{SPV_OPERAND_TYPE_TYPE_ID, SPV_OPERAND_TYPE_RESULT_ID, |
| SPV_OPERAND_TYPE_ID, |
| SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER})); |
| } |
| |
| // Test printingClass |
| |
| struct OpcodePrintingClassCase { |
| std::string name; |
| PrintingClass expected; |
| }; |
| |
| std::ostream& operator<<(std::ostream& os, |
| const OpcodePrintingClassCase& opcc) { |
| os << "OPCC('" << opcc.name << "', " << static_cast<int>(opcc.expected) |
| << ")"; |
| return os; |
| } |
| |
| using OpcodePrintingClassTest = |
| ::testing::TestWithParam<OpcodePrintingClassCase>; |
| |
| TEST_P(OpcodePrintingClassTest, OpcodeLookup_ByName) { |
| const InstructionDesc* desc = nullptr; |
| auto status = LookupOpcode(GetParam().name.data(), &desc); |
| EXPECT_EQ(status, SPV_SUCCESS); |
| ASSERT_NE(desc, nullptr); |
| EXPECT_EQ(desc->printingClass, GetParam().expected); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Samples, OpcodePrintingClassTest, |
| ValuesIn(std::vector<OpcodePrintingClassCase>{ |
| {"ConstantFunctionPointerINTEL", PrintingClass::k_exclude}, |
| {"Nop", PrintingClass::kMiscellaneous}, |
| {"SourceContinued", PrintingClass::kDebug}, |
| {"Decorate", PrintingClass::kAnnotation}, |
| {"Extension", PrintingClass::kExtension}, |
| {"MemoryModel", PrintingClass::kMode_Setting}, |
| {"Variable", PrintingClass::kMemory}, |
| {"CooperativeMatrixPerElementOpNV", PrintingClass::kFunction}, |
| {"SampledImage", PrintingClass::kImage}, |
| {"ConvertFToU", PrintingClass::kConversion}, |
| {"VectorExtractDynamic", PrintingClass::kComposite}, |
| {"IAdd", PrintingClass::kArithmetic}, |
| {"ShiftRightLogical", PrintingClass::kBit}, |
| {"Any", PrintingClass::kRelational_and_Logical}, |
| {"DPdx", PrintingClass::kDerivative}, |
| {"Branch", PrintingClass::kControl_Flow}, |
| {"AtomicLoad", PrintingClass::kAtomic}, |
| {"ControlBarrier", PrintingClass::kBarrier}, |
| {"GroupAll", PrintingClass::kGroup}, |
| {"EnqueueMarker", PrintingClass::kDevice_Side_Enqueue}, |
| {"ReadPipe", PrintingClass::kPipe}, |
| {"GroupNonUniformElect", PrintingClass::kNon_Uniform}, |
| // Skipping "Reserved" because it's probably an |
| // unstable class. |
| })); |
| |
| } // namespace |
| } // namespace spvtools |