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>
diff --git a/DEPS b/DEPS
index af8ad4b..4f0ff6e 100644
--- a/DEPS
+++ b/DEPS
@@ -14,7 +14,7 @@
're2_revision': '917047f3606d3ba9e2de0d383c3cd80c94ed732c',
- 'spirv_headers_revision': 'ea77f2a826bc820cb8f57f9b2a7c7eccb681c731',
+ 'spirv_headers_revision': 'ff2afc3afc48dff4eec2a10f0212402a80708e38',
}
deps = {
diff --git a/source/binary.cpp b/source/binary.cpp
index cf1f0b7..a39bcf0 100644
--- a/source/binary.cpp
+++ b/source/binary.cpp
@@ -473,7 +473,7 @@
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 @@
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) ==
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 726a4e0..c28f26b 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -720,6 +720,16 @@
}
}
+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:
diff --git a/source/opcode.h b/source/opcode.h
index 217aeb2..6578e80 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -146,6 +146,9 @@
// 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);
diff --git a/source/operand.cpp b/source/operand.cpp
index 7848846..dc80588 100644
--- a/source/operand.cpp
+++ b/source/operand.cpp
@@ -582,11 +582,13 @@
}
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
diff --git a/source/operand.h b/source/operand.h
index f74c933..3d42a05 100644
--- a/source/operand.h
+++ b/source/operand.h
@@ -140,6 +140,6 @@
// 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_
diff --git a/source/opt/ir_context.h b/source/opt/ir_context.h
index ef7c458..6e1713c 100644
--- a/source/opt/ir_context.h
+++ b/source/opt/ir_context.h
@@ -202,8 +202,8 @@
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();
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index e9b7bbf..a785048 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -42,7 +42,7 @@
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 @@
// 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 @@
} 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 @@
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) {
diff --git a/source/opt/strip_debug_info_pass.cpp b/source/opt/strip_debug_info_pass.cpp
index f81bced..118d846 100644
--- a/source/opt/strip_debug_info_pass.cpp
+++ b/source/opt/strip_debug_info_pass.cpp
@@ -43,7 +43,7 @@
// 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 =
diff --git a/source/opt/strip_nonsemantic_info_pass.cpp b/source/opt/strip_nonsemantic_info_pass.cpp
index 3886835..659849e 100644
--- a/source/opt/strip_nonsemantic_info_pass.cpp
+++ b/source/opt/strip_nonsemantic_info_pass.cpp
@@ -96,7 +96,7 @@
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);
diff --git a/source/text.cpp b/source/text.cpp
index 263bacd..fda46ec 100644
--- a/source/text.cpp
+++ b/source/text.cpp
@@ -227,8 +227,7 @@
// 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()
diff --git a/source/val/instruction.h b/source/val/instruction.h
index c524bd3..59e8af1 100644
--- a/source/val/instruction.h
+++ b/source/val/instruction.h
@@ -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 @@
}
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);
}
diff --git a/source/val/validate_adjacency.cpp b/source/val/validate_adjacency.cpp
index 7e371c2..013c167 100644
--- a/source/val/validate_adjacency.cpp
+++ b/source/val/validate_adjacency.cpp
@@ -52,6 +52,7 @@
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
diff --git a/source/val/validate_decorations.cpp b/source/val/validate_decorations.cpp
index ebc153c..13b2aa2 100644
--- a/source/val/validate_decorations.cpp
+++ b/source/val/validate_decorations.cpp
@@ -1684,6 +1684,7 @@
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;
diff --git a/source/val/validate_extensions.cpp b/source/val/validate_extensions.cpp
index 7b73c9c..05f8ca8 100644
--- a/source/val/validate_extensions.cpp
+++ b/source/val/validate_extensions.cpp
@@ -147,7 +147,7 @@
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 @@
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 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 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 @@
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;
}
diff --git a/source/val/validate_id.cpp b/source/val/validate_id.cpp
index bcfeb59..d7d3e8f 100644
--- a/source/val/validate_id.cpp
+++ b/source/val/validate_id.cpp
@@ -120,15 +120,16 @@
// 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 @@
!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 @@
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 @@
<< " 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;
diff --git a/source/val/validate_layout.cpp b/source/val/validate_layout.cpp
index dbc1f1e..3efeea6 100644
--- a/source/val/validate_layout.cpp
+++ b/source/val/validate_layout.cpp
@@ -35,6 +35,7 @@
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 @@
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;
diff --git a/source/val/validation_state.cpp b/source/val/validation_state.cpp
index 316972e..e4c7014 100644
--- a/source/val/validation_state.cpp
+++ b/source/val/validation_state.cpp
@@ -76,6 +76,7 @@
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;
diff --git a/test/val/val_ext_inst_test.cpp b/test/val/val_ext_inst_test.cpp
index 8f0bcce..30f25a5 100644
--- a/test/val/val_ext_inst_test.cpp
+++ b/test/val/val_ext_inst_test.cpp
@@ -7472,6 +7472,81 @@
}
}
+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
diff --git a/test/val/val_extensions_test.cpp b/test/val/val_extensions_test.cpp
index 932bbee..bdeb329 100644
--- a/test/val/val_extensions_test.cpp
+++ b/test/val/val_extensions_test.cpp
@@ -61,7 +61,8 @@
"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 @@
}));
// 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
diff --git a/test/val/val_id_test.cpp b/test/val/val_id_test.cpp
index cc29736..c4e2562 100644
--- a/test/val/val_id_test.cpp
+++ b/test/val/val_id_test.cpp
@@ -6907,6 +6907,113 @@
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