stride3d/stride · error · NotSupportedException

Unsupported mul operand types

Error message

Unsupported mul operand types: {context.ReverseTypes[a.TypeId]} and {context.ReverseTypes[b.TypeId]}

What it means

CompileMul lowers the SDSL mul() intrinsic (HLSL-style matrix multiply) to SPIR-V OpMatrixTimesMatrix/OpVectorTimesMatrix etc. It pattern-matches on the operand type pair; if the combination (e.g. matrix-matrix with mismatched inner dimensions, or types that are neither vectors nor matrices) has no matching case, it throws NotSupportedException listing both resolved operand types.

Solutions

  1. Verify the operand shapes: for mul(M1, M2) the inner dimensions must match (columns of M1 vs rows of M2 in HLSL terms).
  2. Use explicit casts so both operands are the same base type (e.g. float matrices).
  3. Multiply by a scalar with the * operator, not mul().
  4. If a valid SPIR-V mapping exists that is missing, add a case to the switch in CompileMul (e.g. OpVectorTimesScalar).

Example fix

// before (SDSL)
float2x4 a; float4x3 b; float3 v;
float3 r = mul(mul(a, b), v); // ok only if dims align
float1x1 s = mul(a, 2); // throws: unsupported operand types
// after
float2x3 r = mul(a, b);
float2x4 s2 = a * 2;
Defensive patterns

Strategy: validation

Validate before calling

bool IsValidMul(TypeBase a, TypeBase b) =>
    (a, b) switch {
        (MatrixType m1, MatrixType m2) => m1.Rows == m2.Columns,
        (VectorType, MatrixType) => true,
        (MatrixType, VectorType) => true,
        _ => false };

Type guard

bool IsMatrixOrVector(TypeBase t) => t is MatrixType or VectorType;

Try / catch

try { result = intrinsics.CompileMul(table, ctx, builder, fnType, a, b); }
catch (NotSupportedException ex) { /* report operand types to shader diagnostics */ }

Prevention

When it happens

Trigger: Calling mul(a, b) where a and b are not a valid (matrix,matrix), (vector,matrix), (matrix,vector) pair, or where the inner dimensions do not match (type1.Rows != type2.Columns for matrix-matrix).

Common situations: Transposed dimension assumptions from HLSL row/column conventions; multiplying a matrix by a scalar with mul instead of *; dimension typo in a floatNxM declaration; mixing float and int matrices.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs:203

        // Version on https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-mul
        // Note: SPIR-V and HLSL have opposite meaning for Rows/Columns and multiplication order need to be swapped
        var result = (context.ReverseTypes[a.TypeId], context.ReverseTypes[b.TypeId]) switch
        {
            (ScalarType type1, ScalarType type2) => builder.InsertData(new OpFMul(a.TypeId, context.Bound++, a.Id, b.Id)),
            (ScalarType type1, VectorType type2) => builder.InsertData(new OpVectorTimesScalar(b.TypeId, context.Bound++, b.Id, a.Id)),
            (ScalarType type1, MatrixType type2) => builder.InsertData(new OpMatrixTimesScalar(b.TypeId, context.Bound++, b.Id, a.Id)),
            (VectorType type1, ScalarType type2) => builder.InsertData(new OpVectorTimesScalar(a.TypeId, context.Bound++, a.Id, b.Id)),
            (VectorType type1, VectorType type2) when type1.Size == type2.Size => builder.InsertData(new OpDot(a.TypeId, context.Bound++, a.Id, b.Id)),
            (VectorType type1, MatrixType type2) when type1.Size == type2.Columns => builder.InsertData(new OpMatrixTimesVector(context.GetOrRegister(new VectorType(type1.BaseType, type2.Rows)), context.Bound++, b.Id, a.Id)),
            (MatrixType type1, ScalarType type2) => builder.InsertData(new OpMatrixTimesScalar(a.TypeId, context.Bound++, a.Id, b.Id)),
            (MatrixType type1, VectorType type2) when type1.Rows == type2.Size => builder.InsertData(new OpVectorTimesMatrix(context.GetOrRegister(new VectorType(type1.BaseType, type1.Columns)), context.Bound++, b.Id, a.Id)),
            // This is HLSL-style so Rows/Columns meaning is swapped
            //float2x4 = OpTypeMatrix vec4 x2 = MatrixType(Rows: 4, Columns: 2)
            //float4x3 = OpTypeMatrix vec3 x4 = MatrixType(Rows: 3, Columns: 4)
            //float2x3 = OpTypeMatrix vec3 x2 = MatrixType(Rows: 3, Columns: 2)
            // mul(float2x4,float4x3) => float2x3
            (MatrixType type1, MatrixType type2) when type1.Rows == type2.Columns => builder.InsertData(new OpMatrixTimesMatrix(context.GetOrRegister(new MatrixType(type1.BaseType, type2.Rows, type1.Columns)), context.Bound++, b.Id, a.Id)),
            _ => throw new NotSupportedException($"Unsupported mul operand types: {context.ReverseTypes[a.TypeId]} and {context.ReverseTypes[b.TypeId]}"),
        };

        return new SpirvValue(result);
    }

    public override SpirvValue CompileReflect(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue i, SpirvValue n, TextLocation location = default)
    {
        var instruction = builder.Insert(new GLSLReflect(i.TypeId, context.Bound++, context.GetGLSL(), i.Id, n.Id));
        return new(instruction.ResultId, instruction.ResultType);
    }
    public override SpirvValue CompileRefract(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue i, SpirvValue n, SpirvValue ri, TextLocation location = default)
    {
        var instruction = builder.Insert(new GLSLRefract(i.TypeId, context.Bound++, context.GetGLSL(), i.Id, n.Id, ri.Id));
        return new(instruction.ResultId, instruction.ResultType);
    }

    public override SpirvValue CompileFaceforward(SymbolTable table, SpirvContext context, SpirvBuilder builder, FunctionType functionType, SpirvValue N, SpirvValue I, SpirvValue Ng, TextLocation location = default)
    {

View on GitHub (pinned to 96fad776d2)