stride3d/stride · error · NotImplementedException

Couldn't handle operand

Error message

Couldn't handle operand {logOp}

What it means

OpDataEnumerator.MoveNext advances word/operand/pattern indices per the SPIR-V grammar's operand quantifiers (One, ZeroOrOne, ZeroOrMore). The switch covers all quantifier kinds it knows; an operand shape not in the table hits the throw. This indicates either an unhandled combination of Quantifier and operand-position state or corrupted/unknown operand metadata.

Solutions

  1. Log logOp (name, quantifier, kind) at the throw site to identify the unhandled combination and add a matching switch arm.
  2. Update the library's operand grammar tables to match the SPIR-V version of the module being parsed.
  3. Validate the input SPIR-V with spirv-val to rule out a malformed module driving the enumerator off-spec.
  4. As a workaround, parse the failing opcode manually instead of via OpDataEnumerator.

Example fix

// before
_ => throw new NotImplementedException($"Couldn't handle operand {logOp}")
// after
{ Quantifier: OperandQuantifier.ZeroOrMore, Kind: OperandKind.LiteralString } => /* handle strings */,
_ => throw new NotImplementedException($"Couldn't handle operand {logOp}")
Defensive patterns

Strategy: try-catch

Validate before calling

// validate the module first with spirv-val (external) and check opcode support:
if (!opData.KnownOpcodes.Contains((Op)words[0] & 0xFFFF)) throw new NotSupportedException("Unknown opcode before enumeration");

Type guard

null

Try / catch

try { while (enum.MoveNext()) ProcessCurrent(); }
catch (NotImplementedException ex) when (ex.Message.StartsWith("Couldn't handle operand")) { /* fall back to manual parsing of this opcode; log the operand descriptor */ }

Prevention

When it happens

Trigger: Iterating operands of an instruction whose operand descriptor has a Quantifier/Kind combination not covered by the MoveNext switch — e.g. ZeroOrMore appearing not-last, or unexpected logOp state — while consuming Op instruction words.

Common situations: Parsing SPIR-V from a newer grammar version (new operand kinds in spirv.core.grammar.json) than the library's embedded OpData tables; malformed/custom instruction streams; regenerated operand tables missing a case.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Spirv/Parsing/OpDataEnumerator.cs:118

                    operands[pid] switch
                    {
                        { Kind: OperandKind.PairIdRefIdRef or OperandKind.PairIdRefLiteralInteger or OperandKind.PairLiteralIntegerIdRef } => (wid + 2, oid, pid + 1, startOperand),
                        { Kind: OperandKind.LiteralString } => (wid + Operands[wid..].LengthOfString(), oid, pid + 1, startOperand),
                        { Kind: _ } => (wid + 1, oid, pid + 1, startOperand)
                    },
                { Quantifier: OperandQuantifier.One, Kind: OperandKind.PairIdRefIdRef or OperandKind.PairIdRefLiteralInteger or OperandKind.PairLiteralIntegerIdRef }
                    => (wid + 2, oid + 1, -1, -1),
                { Quantifier: OperandQuantifier.One, Kind: OperandKind.LiteralString } => (wid + Operands[wid..].LengthOfString(), oid + 1, -1, -1),
                { Quantifier: OperandQuantifier.One, Kind: _ } => (wid + 1, oid + 1, -1, -1),
                { Quantifier: OperandQuantifier.ZeroOrOne, Kind: OperandKind.PairIdRefIdRef or OperandKind.PairIdRefLiteralInteger or OperandKind.PairLiteralIntegerIdRef }
                    => (wid + (wid < Operands.Length - 2 ? 2 : 0), oid + (wid < Operands.Length - 2 ? 1 : 0), -1, -1),
                { Quantifier: OperandQuantifier.ZeroOrOne, Kind: OperandKind.LiteralString }
                    => (wid + (wid < Operands.Length - 1 ? Operands[wid..].LengthOfString() : 0), oid + (wid < Operands.Length ? 1 : 0), -1, -1),
                { Quantifier: OperandQuantifier.ZeroOrOne, Kind: _ }
                    => (wid + (wid < Operands.Length ? 1 : 0), oid + (wid < Operands.Length ? 1 : 0), -1, -1),
                { Quantifier: OperandQuantifier.ZeroOrMore }
                    => (wid < Operands.Length - 1 ? Operands.Length : wid, oid + (wid < Operands.Length - 1 ? 0 : 1), -1, -1),
                _ => throw new NotImplementedException($"Couldn't handle operand {logOp}")
            };
            wid = newWid;
            oid = newOid;
            pid = newPid;
            // Reasons to return false : 
            // - no operands left
            // - current operand has no kind (i.e. None)
            return !(wid >= Operands.Length || oid >= logicalOperands.Count);

        }
    }

    public SpvOperand ParseCurrent()
    {
        var logOp = logicalOperands[oid];

        return logOp switch
        {

View on GitHub (pinned to 96fad776d2)