blob: c3d2dd1b47592a7a92cd09a81cc49fceb0923fc0 [file] [log] [blame]
// 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