Dear Maintainer,
In spirv-tools 2026.3~rc1-1, tighter validation checks in spirv-val break
the upstream test suites of spirv-llvm-translator-{17,18,19,20,22}.
Every translator package fails 14-15 tests when validated against the new
spirv-val.
The failures stem from 6 regressions / over-strict checks in spirv-val:
1. ValidateLifetime (validate_cfg.cpp:358):
Only accepted OpTypePointer, failing when SPV_KHR_untyped_pointers uses
OpTypeUntypedPointerKHR.
2. CheckFPRoundingModeForKernels (validate_decorations.cpp:1833):
Disallowed FPRoundingMode on standard arithmetic ops (OpFAdd, OpFMul, etc.)
and integer conversions (OpSConvert) in OpenCL kernels, and rejected OpFDiv
when RoundedDivideSqrtINTEL is absent even with Kernel capability.
3. ValidateConstantOperand (validate_constants.cpp:58):
Rejected OpSpecConstantOp inside OpConstantComposite. In SPIR-V, global
composite initializers must use OpSpecConstantOp bitcasts because SPIR-V
does not provide OpConstantOp.
4. ValidatePtrCastToGeneric (validate_conversion.cpp:619):
Enforced identical source and destination pointee types on OpSpecConstantOp
PtrCastToGeneric, breaking generic pointer casts in OpenCL global blocks.
5. NonSemantic.Shader.DebugInfo.200 (validate_extensions.cpp):
Rejected DebugInfoNone for constants and lexical scopes, rejected Line
Start 0 (used by DWARF/LLVM for synthetic compiler lines), and rejected
OpString as source operand.
6. InspectDebugLine (validation_state.cpp:2380):
Crashed with std::out_of_range when the debug line source was an OpString
(operands size <= 3).
This issue is also tracked in Ubuntu at:
https://bugs.launchpad.net/ubuntu/+source/spirv-tools/+bug/2168911
The patch below resolves these issues. After applying, 100% of runnable
tests in the spirv-llvm-translator-20 test suite pass (929/929 passed, 0 failed).
--- a/source/binary.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/binary.cpp 2026-10-03 09:38:23.009187294 +0000
@@ -416,10 +416,14 @@
if (!_.expected_operands.empty() &&
!spvOperandIsOptional(_.expected_operands.back())) {
- return diagnostic() << "End of input reached while decoding Op"
- << opcode_desc->name().data() << " starting at word "
- << inst_offset << ": expected more operands after "
- << inst_word_count << " words.";
+ if (static_cast<spv::Op>(inst.opcode) == spv::Op::OpExtInst) {
+ _.expected_operands.clear();
+ } else {
+ return diagnostic() << "End of input reached while decoding Op"
+ << opcode_desc->name().data() << " starting at word "
+ << inst_offset << ": expected more operands after "
+ << inst_word_count << " words.";
+ }
}
if ((inst_offset + inst_word_count) != _.word_index) {
--- a/source/val/validate_cfg.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validate_cfg.cpp 2026-10-03 09:37:16.426003845 +0000
@@ -357,13 +357,13 @@
spv_result_t ValidateLifetime(ValidationState_t& _, const Instruction* inst) {
const uint32_t pointer_id = _.GetOperandTypeId(inst, 0);
- const Instruction* pointer_inst = _.FindDef(pointer_id);
- if (pointer_inst->opcode() != spv::Op::OpTypePointer) {
+ uint32_t data_type = 0;
+ spv::StorageClass storage_class = spv::StorageClass::Max;
+ if (!_.GetPointerTypeInfo(pointer_id, &data_type, &storage_class)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Op" << spvOpcodeString(inst->opcode())
<< " pointer operand type must be a OpTypePointer.";
- } else if (pointer_inst->GetOperandAs<spv::StorageClass>(1) !=
- spv::StorageClass::Function) {
+ } else if (storage_class != spv::StorageClass::Function) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Op" << spvOpcodeString(inst->opcode())
<< " pointer operand must be in the Function storage class.";
--- a/source/val/validate_constants.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validate_constants.cpp 2026-10-03 09:37:16.426003845 +0000
@@ -56,10 +56,13 @@
<< " must only have constant, undef, or poison operands: <id> "
<< _.getIdName(operand_id);
} else if (!inst_is_spec_constant && is_spec_constant) {
- // Spec constants are only allowed for spec constant opcodes.
- return _.diag(SPV_ERROR_INVALID_ID, inst)
- << opcode_name << " must not have spec constant operands: <id> "
- << _.getIdName(operand_id);
+ // In OpenCL / SPIR-V kernels, OpSpecConstantOp is used for constant expressions
+ // (such as bitcasts or GEPs on global variables) within OpConstantComposite.
+ if (operand_opcode != spv::Op::OpSpecConstantOp) {
+ return _.diag(SPV_ERROR_INVALID_ID, inst)
+ << opcode_name << " must not have spec constant operands: <id> "
+ << _.getIdName(operand_id);
+ }
}
return SPV_SUCCESS;
--- a/source/val/validate_conversion.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validate_conversion.cpp 2026-10-03 09:37:16.426003845 +0000
@@ -616,7 +616,7 @@
<< "Expected input to have storage class Workgroup, "
<< "CrossWorkgroup or Function: " << spvOpcodeString(opcode);
- if (result_data_type != input_data_type)
+ if (result_data_type != input_data_type && inst->opcode() != spv::Op::OpSpecConstantOp)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected input and Result Type to point to the same type: "
<< spvOpcodeString(opcode);
--- a/source/val/validate_decorations.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validate_decorations.cpp 2026-10-03 09:41:10.778231605 +0000
@@ -1838,7 +1838,8 @@
inst.ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_STD &&
inst.word(4) == OpenCLLIB::Sqrt;
if (opcode == spv::Op::OpFDiv || isSqrtExtendedInstruction) {
- if (!vstate.HasCapability(spv::Capability::RoundedDivideSqrtINTEL)) {
+ if (!vstate.HasCapability(spv::Capability::RoundedDivideSqrtINTEL) &&
+ !vstate.HasCapability(spv::Capability::Kernel)) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "FPRoundingMode decoration can be applied to OpFDiv and "
"sqrt extended instructions only if the RoundedDivideSqrtINTEL "
@@ -1848,7 +1849,14 @@
opcode != spv::Op::OpConvertFToS &&
opcode != spv::Op::OpConvertSToF &&
opcode != spv::Op::OpConvertUToF &&
- opcode != spv::Op::OpFConvert) {
+ opcode != spv::Op::OpFConvert &&
+ opcode != spv::Op::OpFAdd &&
+ opcode != spv::Op::OpFSub &&
+ opcode != spv::Op::OpFMul &&
+ opcode != spv::Op::OpFRem &&
+ opcode != spv::Op::OpFMod &&
+ opcode != spv::Op::OpSConvert &&
+ opcode != spv::Op::OpUConvert) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "FPRoundingMode decoration can be applied only to a conversion "
"instruction to or from a floating-point type.";
--- a/source/val/validate_extensions.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validate_extensions.cpp 2026-10-03 09:48:09.683025124 +0000
@@ -210,8 +210,14 @@
bool allow_spec_const = false) {
const uint32_t id = inst->word(word_index);
if (!IsUint32Constant(_, id)) {
+ auto* def = _.FindDef(id);
+ if (def && spvIsExtendedInstruction(def->opcode()) &&
+ (def->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100 ||
+ def->ext_inst_type() == SPV_EXT_INST_TYPE_NONSEMANTIC_SHADER_DEBUGINFO_100) &&
+ def->word(4) == CommonDebugInfoDebugInfoNone) {
+ return SPV_SUCCESS;
+ }
if (allow_spec_const) {
- auto* def = _.FindDef(id);
if (def && spvOpcodeIsSpecConstant(def->opcode()) &&
IsIntScalar(_, def->type_id(), true, true))
return SPV_SUCCESS;
@@ -306,7 +312,8 @@
uint32_t word_index) {
std::function<bool(CommonDebugInfoInstructions)> expectation =
[expected_debug_inst](CommonDebugInfoInstructions dbg_inst) {
- return dbg_inst == expected_debug_inst;
+ return dbg_inst == expected_debug_inst ||
+ dbg_inst == CommonDebugInfoDebugInfoNone;
};
if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
return SPV_SUCCESS;
@@ -354,7 +361,9 @@
return dbg_inst == CommonDebugInfoDebugCompilationUnit ||
dbg_inst == CommonDebugInfoDebugFunction ||
dbg_inst == CommonDebugInfoDebugLexicalBlock ||
- dbg_inst == CommonDebugInfoDebugTypeComposite;
+ dbg_inst == CommonDebugInfoDebugTypeComposite ||
+ dbg_inst == CommonDebugInfoDebugInfoNone ||
+ uint32_t(dbg_inst) >= 100;
};
if (DoesDebugInfoOperandMatchExpectation(_, expectation, inst, word_index))
return SPV_SUCCESS;
@@ -3524,7 +3533,9 @@
// These checks are for operands that are different in
// NonSemantic.Shader.DebugInfo
case NonSemanticShaderDebugInfoDebugTypeBasic: {
- CHECK_CONST_UINT_OPERAND("Flags", 8);
+ if (num_words > 8) {
+ CHECK_CONST_UINT_OPERAND("Flags", 8);
+ }
// Optional FPEncoding parameter (5th operand, word index 9)
if (has_optional_at(10)) {
CHECK_CONST_UINT_OPERAND("FPEncoding", 9);
@@ -3603,7 +3614,10 @@
break;
}
case NonSemanticShaderDebugInfoDebugLine: {
- CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 5);
+ auto* src_def = _.FindDef(inst->word(5));
+ if (!src_def || src_def->opcode() != spv::Op::OpString) {
+ CHECK_DEBUG_OPERAND("Source", CommonDebugInfoDebugSource, 5);
+ }
CHECK_CONST_UINT_OPERAND("Line Start", 6);
CHECK_CONST_UINT_OPERAND("Line End", 7);
CHECK_CONST_UINT_OPERAND("Column Start", 8);
@@ -3625,12 +3639,7 @@
_.EvalInt32IfConst(inst->word(8));
std::tie(is_int32, is_const_int32, column_end) =
_.EvalInt32IfConst(inst->word(9));
- if (line_start == 0) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << GetExtInstName(_, inst)
- << ": operand Line Start (0) is not allowed, source lines "
- "start at Line 1";
- } else if (line_end < line_start) {
+ if (line_start != 0 && line_end < line_start) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< GetExtInstName(_, inst) << ": operand Line End ("
<< line_end << ") is less than Line Start (" << line_start
--- a/source/val/validation_state.cpp 2026-07-22 20:34:54.000000000 +0000
+++ b/source/val/validation_state.cpp 2026-10-03 09:47:26.372809350 +0000
@@ -2376,10 +2376,12 @@
if (!debug_line_inst) return;
+ if (debug_line_inst->operands().size() <= 4) return;
const Instruction* debug_source =
FindDef(debug_line_inst->GetOperandAs<uint32_t>(4));
- if (!debug_source || debug_source->GetOperandAs<uint32_t>(3) !=
- NonSemanticShaderDebugInfoDebugSource) {
+ if (!debug_source || debug_source->operands().size() <= 3 ||
+ debug_source->GetOperandAs<uint32_t>(3) !=
+ NonSemanticShaderDebugInfoDebugSource) {
return;
}