stride3d/stride · error · InvalidOperationException

Invalid SPIR-V instruction

Error message

Invalid SPIR-V instruction

What it means

Thrown during SpirvProbe's instruction walk when an instruction header declares a word count of zero. SPIR-V requires every instruction's high 16 bits (wordCount) to be at least 1 (the opcode word itself); a zero means the module is corrupt or the parse position has desynchronized.

Solutions

  1. Regenerate or re-download the SPIR-V module — the file is truncated/corrupted; compare checksums against the build output.
  2. Validate the module with spirv-val before probing to pinpoint where the layout breaks.
  3. Re-run the shader compiler without lossy post-processing steps that rewrite instruction sizes.

Example fix

// before
var probe = new SpirvProbe(truncatedBytes);
// after
if (!SpirvValidator.IsValid(truncatedBytes)) // e.g. spirv-val
    throw new InvalidOperationException("SPIR-V module is corrupt; recompile shader");
var probe = new SpirvProbe(truncatedBytes);
Defensive patterns

Strategy: validation

Validate before calling

static bool HasValidLayout(ReadOnlySpan<byte> spirv)
{
    var words = MemoryMarshal.Cast<byte, uint>(spirv);
    if (words.Length < 5) return false;
    int i = 5;
    while (i < words.Length)
    {
        int wc = (int)(words[i] >> 16);
        if (wc == 0) return false;
        i += wc;
    }
    return i == words.Length;
}

Try / catch

try { var probe = new SpirvProbe(bytes); }
catch (InvalidOperationException ex) when (ex.Message == "Invalid SPIR-V instruction")
{
    // module is corrupt/truncated — recompile or re-fetch the artifact
}

Prevention

When it happens

Trigger: Parsing a truncated or corrupted .spv file where the offset lands on a word whose upper 16 bits are 0; manually patched/relocated SPIR-V that broke instruction boundaries; a header-valid module whose body was cut short so trailing garbage is read as a header.

Common situations: Incomplete file downloads or partial writes producing truncated SPIR-V; byte-level post-processing (e.g. stripping or obfuscation tools) corrupting instruction lengths; hand-crafted fuzz inputs in tests.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Tests/SpirvProbe.cs:52

    public List<EntryPointInfo> EntryPoints { get; } = [];
    public Dictionary<uint, uint> Locations { get; } = [];
    public Dictionary<uint, string> UserSemantics { get; } = [];
    public HashSet<uint> BuiltIns { get; } = [];
    public Dictionary<uint, uint> VariableStorageClasses { get; } = [];

    public SpirvProbe(ReadOnlySpan<byte> spirv)
    {
        var words = MemoryMarshal.Cast<byte, uint>(spirv);
        if (words.Length < 5 || words[0] != 0x07230203)
            throw new InvalidOperationException("Not a SPIR-V module");

        for (int i = 5; i < words.Length;)
        {
            var wordCount = (int)(words[i] >> 16);
            var opcode = words[i] & 0xFFFF;
            if (wordCount == 0)
                throw new InvalidOperationException("Invalid SPIR-V instruction");
            var operands = words.Slice(i + 1, wordCount - 1);

            switch (opcode)
            {
                case OpEntryPoint:
                {
                    var name = ReadString(operands.Slice(2), out var nameWords);
                    EntryPoints.Add(new EntryPointInfo(operands[0], name, operands.Slice(2 + nameWords).ToArray()));
                    break;
                }
                case OpVariable:
                    VariableStorageClasses[operands[1]] = operands[2];
                    break;
                case OpDecorate:
                    switch (operands[1])
                    {
                        case DecorationLocation:
                            Locations[operands[0]] = operands[2];

View on GitHub (pinned to 96fad776d2)