stride3d/stride · error · InvalidOperationException

OpCode not supported when compiling constant

Error message

OpCode {i.Op} not supported when compiling constant {expression}

What it means

CompileConstantValue builds a SPIR-V constant for a parsed shader expression. It consumes instructions from a scratch buffer and expects each to be a constant-defining opcode; if the instruction has any other opcode, the compiler cannot represent the expression as a constant and throws InvalidOperationException.

Solutions

  1. Check the expression used in the constant context is genuinely compile-time constant (literals, constant composites)
  2. If the value depends on runtime input, move it to a variable instead of a constant context
  3. Use supported constant expressions only (e.g. simple arithmetic on constants) or extend CompileConstantValue to handle the missing opcode
  4. Inspect the failing opcode name in the message to identify which SPIR-V instruction is unsupported

Example fix

// before
const int N = ComputeSize(); // function call in constant context
// after
const int N = 16; // literal constant
Defensive patterns

Strategy: validation

Validate before calling

bool IsCompileTimeConstant(SpirvInstruction i) => i.Op is OpCode.OpConstant or OpCode.OpConstantTrue or OpCode.OpConstantFalse or OpCode.OpConstantNull or OpCode.OpConstantComposite or OpCode.OpUndef;
if (!IsCompileTimeConstant(instruction)) throw new NotSupportedException($"Non-constant opcode {instruction.Op} in constant context");

Type guard

static bool IsConstantOp(OpData i) => i.Op is OpCode.OpConstant or OpCode.OpConstantComposite;

Prevention

When it happens

Trigger: Calling CompileConstantValue on an expression whose compiled instruction stream contains a non-constant opcode (e.g. an instruction producing a result id that is not OpConstant/OpConstantComposite-family), typically after an expression that requires runtime evaluation was fed into a constant context.

Common situations: Using non-constant expressions (function calls, runtime math, stream reads) where SDSL expects a compile-time constant: array sizes, layout offsets, or constant initializers.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/147cb778231740c4. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/ExpressionExtensions.cs:149

                var resultId = i.Data.Memory.Span[2];

                // Try to fold: if all operands are known constants, compute the result at compile time.
                if (TryFoldConstantOp(context, i, out var foldedInstruction))
                {
                    context.Add(foldedInstruction);
                    result = new(resultId, resultType);
                }
                else
                {
                    Span<int> instruction = [(int)Op.OpSpecConstantOp, resultType, resultId, (int)i.Op, .. i.Data.Memory.Span[3..]];
                    instruction[0] |= instruction.Length << 16;
                    context.Add(new OpData(instruction));
                    result = new(resultId, resultType);
                }
            }
            else
            {
                throw new InvalidOperationException($"OpCode {i.Op} not supported when compiling constant {expression}");
            }
        }

        buffer.Dispose();

        return result;
    }

    /// <summary>
    /// Returns true if the given ID refers to an OpConstant, OpConstantTrue, OpConstantFalse,
    /// or OpConstantComposite (i.e. not a spec constant).
    /// </summary>
    private static bool IsPlainConstant(SpirvContext context, int id)
    {
        if (!context.GetBuffer().TryGetInstructionById(id, out var inst))
            return false;
        return inst.Op is Op.OpConstant or Op.OpConstantTrue or Op.OpConstantFalse or Op.OpConstantComposite or Op.OpConstantNull;
    }

View on GitHub (pinned to 96fad776d2)