spirv-val, core: add support for OpExtInstWithForwardRefs (#5698)

* val, core: add support for OpExtInstWithForwardRefs

This commit adds validation and support for
OpExtInstWithForwardRefs. This new instruction will be used
for non-semantic debug info, when forward references are
required.

For now, this commit only fixes the code to handle this new instruction,
and adds validation rules. But it does not add the pass to generate/fix
the OpExtInst instruction when forward references are in use.
Such pass would be useful for DXC or other tools, but I wanted to land
validation rules first.

This commit also bumps SPIRV-Headers to get this new opcode.

---------

Signed-off-by: Nathan Gauër <brioche@google.com>
This commit is contained in:
Nathan Gauër 2024-06-04 16:18:06 +02:00 committed by GitHub
parent 4a2e0c9b36
commit 6a2bdeee75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 293 additions and 28 deletions

2
DEPS
View File

@ -14,7 +14,7 @@ vars = {
're2_revision': '917047f3606d3ba9e2de0d383c3cd80c94ed732c',
'spirv_headers_revision': 'ea77f2a826bc820cb8f57f9b2a7c7eccb681c731',
'spirv_headers_revision': 'ff2afc3afc48dff4eec2a10f0212402a80708e38',
}
deps = {

View File

@ -473,7 +473,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
if (!word) return diagnostic(SPV_ERROR_INVALID_ID) << "Id is 0";
parsed_operand.type = SPV_OPERAND_TYPE_ID;
if (opcode == spv::Op::OpExtInst && parsed_operand.offset == 3) {
if (spvIsExtendedInstruction(opcode) && parsed_operand.offset == 3) {
// The current word is the extended instruction set Id.
// Set the extended instruction set type for the current instruction.
auto ext_inst_type_iter = _.import_id_to_ext_inst_type.find(word);
@ -494,7 +494,7 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
assert(spv::Op::OpExtInst == opcode);
assert(spvIsExtendedInstruction(opcode));
assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE);
spv_ext_inst_desc ext_inst;
if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst) ==

View File

@ -720,6 +720,16 @@ bool spvOpcodeIsImageSample(const spv::Op opcode) {
}
}
bool spvIsExtendedInstruction(const spv::Op opcode) {
switch (opcode) {
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
return true;
default:
return false;
}
}
std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(spv::Op opcode) {
switch (opcode) {
case spv::Op::OpMemoryBarrier:

View File

@ -146,6 +146,9 @@ bool spvOpcodeIsLinearAlgebra(spv::Op opcode);
// Returns true for opcodes that represent image sample instructions.
bool spvOpcodeIsImageSample(spv::Op opcode);
// Returns true if the opcode is either OpExtInst or OpExtInstWithForwardRefs
bool spvIsExtendedInstruction(spv::Op opcode);
// Returns a vector containing the indices of the memory semantics <id>
// operands for |opcode|.
std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(spv::Op opcode);

View File

@ -582,11 +582,13 @@ std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
}
std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
spv_ext_inst_type_t ext_type, uint32_t key) {
spv::Op opcode, spv_ext_inst_type_t ext_type, uint32_t key) {
// The Vulkan debug info extended instruction set is non-semantic so allows no
// forward references ever
// forward references except if used through OpExtInstWithForwardRefs.
if (ext_type == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) {
return [](unsigned) { return false; };
return [opcode](unsigned) {
return opcode == spv::Op::OpExtInstWithForwardRefs;
};
}
// TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/532): Forward

View File

@ -140,6 +140,6 @@ std::function<bool(unsigned)> spvOperandCanBeForwardDeclaredFunction(
// of the operand can be forward declared. This function will
// used in the SSA validation stage of the pipeline
std::function<bool(unsigned)> spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
spv_ext_inst_type_t ext_type, uint32_t key);
spv::Op opcode, spv_ext_inst_type_t ext_type, uint32_t key);
#endif // SOURCE_OPERAND_H_

View File

@ -202,8 +202,8 @@ class IRContext {
inline IteratorRange<Module::const_inst_iterator> debugs3() const;
// Iterators for debug info instructions (excluding OpLine & OpNoLine)
// contained in this module. These are OpExtInst for DebugInfo extension
// placed between section 9 and 10.
// contained in this module. These are OpExtInst & OpExtInstWithForwardRefs
// for DebugInfo extension placed between section 9 and 10.
inline Module::inst_iterator ext_inst_debuginfo_begin();
inline Module::inst_iterator ext_inst_debuginfo_end();
inline IteratorRange<Module::inst_iterator> ext_inst_debuginfo();

View File

@ -42,7 +42,7 @@ IrLoader::IrLoader(const MessageConsumer& consumer, Module* m)
bool IsLineInst(const spv_parsed_instruction_t* inst) {
const auto opcode = static_cast<spv::Op>(inst->opcode);
if (IsOpLineInst(opcode)) return true;
if (opcode != spv::Op::OpExtInst) return false;
if (!spvIsExtendedInstruction(opcode)) return false;
if (inst->ext_inst_type != SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100)
return false;
const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
@ -65,7 +65,7 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
// create a new instruction, but simply keep the information in
// struct DebugScope.
const auto opcode = static_cast<spv::Op>(inst->opcode);
if (opcode == spv::Op::OpExtInst &&
if (spvIsExtendedInstruction(opcode) &&
spvExtInstIsDebugInfo(inst->ext_inst_type)) {
const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
@ -209,10 +209,10 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
} else if (IsConstantInst(opcode) || opcode == spv::Op::OpVariable ||
opcode == spv::Op::OpUndef) {
module_->AddGlobalValue(std::move(spv_inst));
} else if (opcode == spv::Op::OpExtInst &&
} else if (spvIsExtendedInstruction(opcode) &&
spvExtInstIsDebugInfo(inst->ext_inst_type)) {
module_->AddExtInstDebugInfo(std::move(spv_inst));
} else if (opcode == spv::Op::OpExtInst &&
} else if (spvIsExtendedInstruction(opcode) &&
spvExtInstIsNonSemantic(inst->ext_inst_type)) {
// If there are no functions, add the non-semantic instructions to the
// global values. Otherwise append it to the list of the last function.
@ -235,7 +235,7 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
last_dbg_scope_ = DebugScope(kNoDebugScope, kNoInlinedAt);
if (last_dbg_scope_.GetLexicalScope() != kNoDebugScope)
spv_inst->SetDebugScope(last_dbg_scope_);
if (opcode == spv::Op::OpExtInst &&
if (spvIsExtendedInstruction(opcode) &&
spvExtInstIsDebugInfo(inst->ext_inst_type)) {
const uint32_t ext_inst_index = inst->words[kExtInstSetIndex];
if (inst->ext_inst_type == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {

View File

@ -43,7 +43,7 @@ Pass::Status StripDebugInfoPass::Process() {
// see if this string is used anywhere by a non-semantic instruction
bool no_nonsemantic_use =
def_use->WhileEachUser(&inst, [def_use](Instruction* use) {
if (use->opcode() == spv::Op::OpExtInst) {
if (spvIsExtendedInstruction(use->opcode())) {
auto ext_inst_set =
def_use->GetDef(use->GetSingleWordInOperand(0u));
const std::string extension_name =

View File

@ -96,7 +96,7 @@ Pass::Status StripNonSemanticInfoPass::Process() {
if (!non_semantic_sets.empty()) {
context()->module()->ForEachInst(
[&non_semantic_sets, &to_remove](Instruction* inst) {
if (inst->opcode() == spv::Op::OpExtInst) {
if (spvIsExtendedInstruction(inst->opcode())) {
if (non_semantic_sets.find(inst->GetSingleWordInOperand(0)) !=
non_semantic_sets.end()) {
to_remove.push_back(inst);

View File

@ -227,8 +227,7 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar,
// Set the extended instruction type.
// The import set id is the 3rd operand of OpExtInst.
if (spv::Op(pInst->opcode) == spv::Op::OpExtInst &&
pInst->words.size() == 4) {
if (spvIsExtendedInstruction(pInst->opcode) && pInst->words.size() == 4) {
auto ext_inst_type = context->getExtInstTypeForId(pInst->words[3]);
if (ext_inst_type == SPV_EXT_INST_TYPE_NONE) {
return context->diagnostic()

View File

@ -22,6 +22,7 @@
#include <vector>
#include "source/ext_inst.h"
#include "source/opcode.h"
#include "source/table.h"
#include "spirv-tools/libspirv.h"
@ -87,13 +88,13 @@ class Instruction {
}
bool IsNonSemantic() const {
return opcode() == spv::Op::OpExtInst &&
return spvIsExtendedInstruction(opcode()) &&
spvExtInstIsNonSemantic(inst_.ext_inst_type);
}
/// True if this is an OpExtInst for debug info extension.
bool IsDebugInfo() const {
return opcode() == spv::Op::OpExtInst &&
return spvIsExtendedInstruction(opcode()) &&
spvExtInstIsDebugInfo(inst_.ext_inst_type);
}

View File

@ -52,6 +52,7 @@ spv_result_t ValidateAdjacency(ValidationState_t& _) {
adjacency_status == IN_NEW_FUNCTION ? IN_ENTRY_BLOCK : PHI_VALID;
break;
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
// If it is a debug info instruction, we do not change the status to
// allow debug info instructions before OpVariable in a function.
// TODO(https://gitlab.khronos.org/spirv/SPIR-V/issues/533): We need

View File

@ -1684,6 +1684,7 @@ spv_result_t CheckIntegerWrapDecoration(ValidationState_t& vstate,
case spv::Op::OpSNegate:
return SPV_SUCCESS;
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
// TODO(dneto): Only certain extended instructions allow these
// decorations. For now allow anything.
return SPV_SUCCESS;

View File

@ -147,7 +147,7 @@ bool DoesDebugInfoOperandMatchExpectation(
const Instruction* inst, uint32_t word_index) {
if (inst->words().size() <= word_index) return false;
auto* debug_inst = _.FindDef(inst->word(word_index));
if (debug_inst->opcode() != spv::Op::OpExtInst ||
if (!spvIsExtendedInstruction(debug_inst->opcode()) ||
(debug_inst->ext_inst_type() != SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 &&
debug_inst->ext_inst_type() !=
SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) ||
@ -165,7 +165,7 @@ bool DoesDebugInfoOperandMatchExpectation(
const Instruction* inst, uint32_t word_index) {
if (inst->words().size() <= word_index) return false;
auto* debug_inst = _.FindDef(inst->word(word_index));
if (debug_inst->opcode() != spv::Op::OpExtInst ||
if (!spvIsExtendedInstruction(debug_inst->opcode()) ||
(debug_inst->ext_inst_type() !=
SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) ||
!expectation(
@ -409,7 +409,7 @@ spv_result_t ValidateClspvReflectionArgumentInfo(ValidationState_t& _,
spv_result_t ValidateKernelDecl(ValidationState_t& _, const Instruction* inst) {
const auto decl_id = inst->GetOperandAs<uint32_t>(4);
const auto decl = _.FindDef(decl_id);
if (!decl || decl->opcode() != spv::Op::OpExtInst) {
if (!decl || !spvIsExtendedInstruction(decl->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Kernel must be a Kernel extended instruction";
}
@ -432,7 +432,7 @@ spv_result_t ValidateKernelDecl(ValidationState_t& _, const Instruction* inst) {
spv_result_t ValidateArgInfo(ValidationState_t& _, const Instruction* inst,
uint32_t info_index) {
auto info = _.FindDef(inst->GetOperandAs<uint32_t>(info_index));
if (!info || info->opcode() != spv::Op::OpExtInst) {
if (!info || !spvIsExtendedInstruction(info->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ArgInfo must be an ArgumentInfo extended instruction";
}
@ -3706,7 +3706,7 @@ spv_result_t ExtensionPass(ValidationState_t& _, const Instruction* inst) {
const spv::Op opcode = inst->opcode();
if (opcode == spv::Op::OpExtension) return ValidateExtension(_, inst);
if (opcode == spv::Op::OpExtInstImport) return ValidateExtInstImport(_, inst);
if (opcode == spv::Op::OpExtInst) return ValidateExtInst(_, inst);
if (spvIsExtendedInstruction(opcode)) return ValidateExtInst(_, inst);
return SPV_SUCCESS;
}

View File

@ -120,15 +120,16 @@ spv_result_t CheckIdDefinitionDominateUse(ValidationState_t& _) {
// instruction operand's ID can be forward referenced.
spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
auto can_have_forward_declared_ids =
inst->opcode() == spv::Op::OpExtInst &&
spvIsExtendedInstruction(inst->opcode()) &&
spvExtInstIsDebugInfo(inst->ext_inst_type())
? spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
inst->ext_inst_type(), inst->word(4))
inst->opcode(), inst->ext_inst_type(), inst->word(4))
: spvOperandCanBeForwardDeclaredFunction(inst->opcode());
// Keep track of a result id defined by this instruction. 0 means it
// does not define an id.
uint32_t result_id = 0;
bool has_forward_declared_ids = false;
for (unsigned i = 0; i < inst->operands().size(); i++) {
const spv_parsed_operand_t& operand = inst->operand(i);
@ -177,6 +178,7 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
!inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
!spvOpcodeIsBranch(opcode) && opcode != spv::Op::OpPhi &&
opcode != spv::Op::OpExtInst &&
opcode != spv::Op::OpExtInstWithForwardRefs &&
opcode != spv::Op::OpExtInstImport &&
opcode != spv::Op::OpSelectionMerge &&
opcode != spv::Op::OpLoopMerge &&
@ -200,6 +202,7 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
ret = SPV_SUCCESS;
}
} else if (can_have_forward_declared_ids(i)) {
has_forward_declared_ids = true;
if (spvOpcodeGeneratesType(inst->opcode()) &&
!_.IsForwardPointer(operand_word)) {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
@ -229,12 +232,34 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
<< " has not been defined";
}
break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
// Ideally, this check would live in validate_extensions.cpp. But since
// forward references are only allowed on non-semantic instructions, and
// ID validation is done first, we would fail with a "ID had not been
// defined" error before we could give a more helpful message. For this
// reason, this test is done here, so we can be more helpful to the
// user.
if (inst->opcode() == spv::Op::OpExtInstWithForwardRefs &&
!inst->IsNonSemantic())
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "OpExtInstWithForwardRefs is only allowed with "
"non-semantic instructions.";
ret = SPV_SUCCESS;
break;
default:
ret = SPV_SUCCESS;
break;
}
if (SPV_SUCCESS != ret) return ret;
}
const bool must_have_forward_declared_ids =
inst->opcode() == spv::Op::OpExtInstWithForwardRefs;
if (must_have_forward_declared_ids && !has_forward_declared_ids) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Opcode OpExtInstWithForwardRefs must have at least one forward "
"declared ID.";
}
if (result_id) _.RemoveIfForwardDeclared(result_id);
return SPV_SUCCESS;

View File

@ -35,6 +35,7 @@ spv_result_t ModuleScopedInstructions(ValidationState_t& _,
const Instruction* inst, spv::Op opcode) {
switch (opcode) {
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
const uint32_t ext_inst_index = inst->word(4);
bool local_debug_info = false;
@ -243,6 +244,7 @@ spv_result_t FunctionScopedInstructions(ValidationState_t& _,
break;
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
const uint32_t ext_inst_index = inst->word(4);
bool local_debug_info = false;

View File

@ -76,6 +76,7 @@ ModuleLayoutSection InstructionLayoutSection(
if (current_section == kLayoutTypes) return kLayoutTypes;
return kLayoutFunctionDefinitions;
case spv::Op::OpExtInst:
case spv::Op::OpExtInstWithForwardRefs:
// spv::Op::OpExtInst is only allowed in types section for certain
// extended instruction sets. This will be checked separately.
if (current_section == kLayoutTypes) return kLayoutTypes;

View File

@ -7472,6 +7472,81 @@ OpFunctionEnd
}
}
TEST_F(ValidateExtInst, OpExtInstWithForwardNotAllowedSemantic) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
OpExtension "SPV_KHR_relaxed_extended_instruction"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
%extinst = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpExecutionMode %2 LocalSize 1 1 1
%3 = OpString "sample"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%f32 = OpTypeFloat 32
%uint_0 = OpConstant %uint 0
%f32_0 = OpConstant %f32 0
%f32_1 = OpConstant %f32 1
%7 = OpTypeFunction %void
%8 = OpExtInst %void %1 DebugSource %3 %3
%9 = OpExtInst %void %1 DebugCompilationUnit %uint_0 %uint_0 %8 %uint_0
%10 = OpExtInstWithForwardRefs %void %1 DebugTypeFunction %uint_0 %11
%12 = OpExtInstWithForwardRefs %void %1 DebugFunction %3 %10 %8 %uint_0 %uint_0 %11 %3 %uint_0 %uint_0
%11 = OpExtInst %void %1 DebugTypeComposite %3 %uint_0 %8 %uint_0 %uint_0 %9 %3 %uint_0 %uint_0 %12
%2 = OpFunction %void None %7
%13 = OpLabel
%18 = OpExtInstWithForwardRefs %f32 %extinst FMin %f32_0 %19
%19 = OpExtInst %f32 %extinst FMin %f32_0 %f32_1
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"OpExtInstWithForwardRefs is only allowed with non-semantic "
"instructions.\n"
" %18 = OpExtInstWithForwardRefs %float %2 FMin %float_0 %19\n"));
}
TEST_F(ValidateExtInst, OpExtInstRequiresNonSemanticBefore16) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
%extinst = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpExecutionMode %2 LocalSize 1 1 1
%3 = OpString "sample"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%7 = OpTypeFunction %void
%8 = OpExtInst %void %1 DebugSource %3 %3
%9 = OpExtInst %void %1 DebugCompilationUnit %uint_0 %uint_0 %8 %uint_0
%10 = OpExtInstWithForwardRefs %void %1 DebugTypeFunction %uint_0 %11
%12 = OpExtInstWithForwardRefs %void %1 DebugFunction %3 %10 %8 %uint_0 %uint_0 %11 %3 %uint_0 %uint_0
%11 = OpExtInst %void %1 DebugTypeComposite %3 %uint_0 %8 %uint_0 %uint_0 %9 %3 %uint_0 %uint_0 %12
%2 = OpFunction %void None %7
%13 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
ASSERT_EQ(SPV_ERROR_MISSING_EXTENSION, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("ExtInstWithForwardRefs requires one of the following "
"extensions: SPV_KHR_relaxed_extended_instruction \n"
" %11 = OpExtInstWithForwardRefs %void %1 "
"DebugTypeFunction %uint_0 %12\n"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -61,7 +61,8 @@ INSTANTIATE_TEST_SUITE_P(
"SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_fragment_mask",
"SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1",
"SPV_NV_shader_subgroup_partitioned", "SPV_EXT_descriptor_indexing",
"SPV_KHR_terminate_invocation"));
"SPV_KHR_terminate_invocation",
"SPV_KHR_relaxed_extended_instruction"));
INSTANTIATE_TEST_SUITE_P(FailSilently, ValidateUnknownExtensions,
Values("ERROR_unknown_extension", "SPV_KHR_",
@ -550,6 +551,43 @@ INSTANTIATE_TEST_SUITE_P(
}));
// clang-format on
using ValidateRelaxedExtendedInstructionExt = spvtest::ValidateBase<bool>;
TEST_F(ValidateRelaxedExtendedInstructionExt, RequiresExtension) {
const std::string str = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpExecutionMode %2 LocalSize 1 1 1
%3 = OpString "sample"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%7 = OpTypeFunction %void
%8 = OpExtInst %void %1 DebugSource %3 %3
%9 = OpExtInst %void %1 DebugCompilationUnit %uint_0 %uint_0 %8 %uint_0
%10 = OpExtInstWithForwardRefs %void %1 DebugTypeFunction %uint_0 %11
%12 = OpExtInstWithForwardRefs %void %1 DebugFunction %3 %10 %8 %uint_0 %uint_0 %11 %3 %uint_0 %uint_0
%11 = OpExtInst %void %1 DebugTypeComposite %3 %uint_0 %8 %uint_0 %uint_0 %9 %3 %uint_0 %uint_0 %12
%2 = OpFunction %void None %7
%13 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(str.c_str());
EXPECT_NE(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"ExtInstWithForwardRefs requires one of the following extensions:"
" SPV_KHR_relaxed_extended_instruction \n"
" %10 = OpExtInstWithForwardRefs %void %1 DebugTypeFunction %uint_0 "
"%11\n"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -6907,6 +6907,113 @@ TEST_P(ValidateIdWithMessage, NVBindlessSamplerInStruct) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
TEST_P(ValidateIdWithMessage, OpExtInstWithForwardRefsDisallowedNoForwardRef) {
std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
OpExtension "SPV_KHR_relaxed_extended_instruction"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%main_type = OpTypeFunction %void
%4 = OpExtInstWithForwardRefs %void %1 DebugInfoNone
%main = OpFunction %void None %main_type
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_6);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(make_message("Opcode OpExtInstWithForwardRefs must have at "
"least one forward declared ID.")));
}
TEST_P(ValidateIdWithMessage, OpExtInstNoForwardRef) {
std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
OpExtension "SPV_KHR_relaxed_extended_instruction"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%main_type = OpTypeFunction %void
%4 = OpExtInst %void %1 DebugInfoNone
%main = OpFunction %void None %main_type
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_6);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
}
TEST_P(ValidateIdWithMessage,
OpExtInstWithForwardRefsAllowedForwardReferenceInNonSemantic) {
std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
OpExtension "SPV_KHR_relaxed_extended_instruction"
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpExecutionMode %2 LocalSize 1 1 1
%3 = OpString "sample"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%7 = OpTypeFunction %void
%8 = OpExtInst %void %1 DebugSource %3 %3
%9 = OpExtInst %void %1 DebugCompilationUnit %uint_0 %uint_0 %8 %uint_0
%10 = OpExtInstWithForwardRefs %void %1 DebugTypeFunction %uint_0 %11
%12 = OpExtInstWithForwardRefs %void %1 DebugFunction %3 %10 %8 %uint_0 %uint_0 %11 %3 %uint_0 %uint_0
%11 = OpExtInst %void %1 DebugTypeComposite %3 %uint_0 %8 %uint_0 %uint_0 %9 %3 %uint_0 %uint_0 %12
%2 = OpFunction %void None %7
%13 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_6);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
}
TEST_P(ValidateIdWithMessage, OpExtInstNoForwardDeclAllowed) {
std::string spirv = R"(
OpCapability Shader
%1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpExecutionMode %2 LocalSize 1 1 1
%3 = OpString "sample"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%7 = OpTypeFunction %void
%8 = OpExtInst %void %1 DebugSource %3 %3
%9 = OpExtInst %void %1 DebugCompilationUnit %uint_0 %uint_0 %8 %uint_0
%10 = OpExtInst %void %1 DebugTypeFunction %uint_0 %11
%12 = OpExtInst %void %1 DebugFunction %3 %10 %8 %uint_0 %uint_0 %11 %3 %uint_0 %uint_0
%11 = OpExtInst %void %1 DebugTypeComposite %3 %uint_0 %8 %uint_0 %uint_0 %9 %3 %uint_0 %uint_0 %12
%2 = OpFunction %void None %7
%13 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_6);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_6));
EXPECT_THAT(getDiagnosticString(),
HasSubstr(make_message("ID '11[%11]' has not been defined")));
}
INSTANTIATE_TEST_SUITE_P(, ValidateIdWithMessage, ::testing::Bool());
} // namespace