#1149809 spirv-tools: validator regressions break spirv-llvm-translator test suites

#1149809#5
Date:
2026-10-03 10:40:06 UTC
From:
To:
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;
   }