stride3d/stride · error · InvalidOperationException

Not a SPIR-V module

Error message

Not a SPIR-V module

What it means

Thrown by the SpirvProbe constructor when the input byte span is too small (<5 words) or its first word is not the SPIR-V magic number 0x07230203. SpirvProbe only parses genuine SPIR-V modules, so malformed input is rejected immediately before any instruction decoding.

Solutions

  1. Ensure the shader is compiled to SPIR-V (dxc -spirv or glslang) before handing the bytes to SpirvProbe.
  2. Check the byte length: a valid module has at least a 20-byte header; log the actual size and first bytes when diagnosing.
  3. Confirm the loaded file is the SPIR-V artifact, not the DXIL/ISKBlob or source text.
  4. If bytes may come from an external source, verify magic 0x03 0x02 0x23 0x07 (little-endian) before constructing the probe.

Example fix

// before
var probe = new SpirvProbe(File.ReadAllBytes("shader.dxil"));
// after
var bytes = File.ReadAllBytes("shader.spv");
if (bytes.Length < 20 || BitConverter.ToUInt32(bytes, 0) != 0x07230203)
    throw new InvalidOperationException("shader.spv is not SPIR-V");
var probe = new SpirvProbe(bytes);
Defensive patterns

Strategy: validation

Validate before calling

static bool IsSpirv(byte[] bytes)
    => bytes.Length >= 20 && BitConverter.ToUInt32(bytes, 0) == 0x07230203;

Type guard

bool TryAsSpirv(ReadOnlySpan<byte> bytes, out SpirvProbe? probe)
{
    probe = null;
    if (bytes.Length < 20 || BitConverter.ToUInt32(bytes.ToArray(), 0) != 0x07230203)
        return false;
    probe = new SpirvProbe(bytes);
    return true;
}

Try / catch

try { var probe = new SpirvProbe(shaderBytes); }
catch (InvalidOperationException ex) when (ex.Message == "Not a SPIR-V module")
{
    // compile the shader to SPIR-V first; log first bytes for diagnosis
}

Prevention

When it happens

Trigger: new SpirvProbe(bytes) is given bytecode that is actually DXIL, GLSL text, a truncated buffer, or byte-swapped/wrong-endian data, so words[0] != 0x07230203.

Common situations: Passing dxbc/dxil output to a SPIR-V pipeline; loading a shader file that failed to compile and contains an error string; reading a file with wrong length (zero bytes); missing the SPIR-V codegen step (e.g. no dxc/spirv backend) so raw HLSL bytes flow through.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

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

    private const uint DecorationBuiltIn = 11;
    private const uint DecorationLocation = 30;
    private const uint DecorationUserSemantic = 5635;

    private const uint StorageClassInput = 1;

    public record EntryPointInfo(uint ExecutionModel, string Name, uint[] Interface);

    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:

View on GitHub (pinned to 96fad776d2)