stride3d/stride · error · NotSupportedException

Unsupported binary op

Error message

Unsupported binary op: {op}

What it means

Binary constant folding implements only OpIAdd, OpISub, OpIMul, OpFAdd, OpFSub, OpFMul, and OpFDiv (all as int or float); any other binary opcode reaching the default arm throws NotSupportedException 'Unsupported binary op'. Notably absent are OpUDiv, OpSRem/OpSMod, OpShift*/OpBitwise*, and OpFRem/OpFMod.

Solutions

  1. Pre-compute the binary result and emit the folded constant directly.
  2. Extend the switch with the needed arms (e.g. OpUDiv => (int)left / (int)right guarded for zero, OpBitwiseAnd => (int)left & (int)right, OpShiftLeftLogical => (int)left << (int)right).
  3. Emit the operation as a runtime instruction instead of relying on fold-time evaluation.

Example fix

// before
Specification.Op.OpFDiv => (float)left / (float)right,
_ => throw new NotSupportedException(...)
// after
Specification.Op.OpFDiv => (float)left / (float)right,
Specification.Op.OpSRem => (int)left % (int)right,
Specification.Op.OpBitwiseAnd => (int)left & (int)right,
_ => throw new NotSupportedException(...)
Defensive patterns

Strategy: validation

Validate before calling

static readonly Specification.Op[] FoldableBinary = { Specification.Op.OpIAdd, Specification.Op.OpISub, Specification.Op.OpIMul, Specification.Op.OpFAdd, Specification.Op.OpFSub, Specification.Op.OpFMul, Specification.Op.OpFDiv };
if (!FoldableBinary.Contains(op)) EmitRuntimeInstruction(op);

Type guard

bool IsFoldableBinary(Specification.Op op) => op is Specification.Op.OpIAdd or Specification.Op.OpISub or Specification.Op.OpIMul or Specification.Op.OpFAdd or Specification.Op.OpFSub or Specification.Op.OpFMul or Specification.Op.OpFDiv;

Try / catch

try { folded = context.GetConstantValue(binaryId); } catch (NotSupportedException ex) when (ex.Message.StartsWith("Unsupported binary op")) { EmitRuntimeInstruction(op); }

Prevention

When it happens

Trigger: Constant-folding OpUDiv, OpSRem, OpSMod, OpShiftLeftLogical, OpBitwiseAnd/Or/Xor, OpFRem/OpFMod, or any other binary opcode with constant operands.

Common situations: Constant expressions with integer division/remainder (e.g. array index math like i % 4), bitmask composition of constant flags, or shift-based packing in generated shaders.

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/d14d3254fc97e100. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Context.Constants.cs:168

                case Specification.Op.OpFMul:
                case Specification.Op.OpFDiv:
                    if (!TryGetConstantValue(i.Data.Memory.Span[4], out var left, out var leftTypeId))
                        return false;
                    if (!TryGetConstantValue(i.Data.Memory.Span[5], out var right, out var rightTypeId))
                        return false;
                    if (leftTypeId != resultType || rightTypeId != resultType)
                        return false;
                    value = op switch
                    {
                        // Note: first cast to object is important, otherwise int/float will be cast as float
                        Specification.Op.OpIAdd => (object)((int)left + (int)right),
                        Specification.Op.OpISub => (int)left - (int)right,
                        Specification.Op.OpIMul => (int)left * (int)right,
                        Specification.Op.OpFAdd => (float)left + (float)right,
                        Specification.Op.OpFSub => (float)left - (float)right,
                        Specification.Op.OpFMul => (float)left * (float)right,
                        Specification.Op.OpFDiv => (float)left / (float)right,
                        _ => throw new NotSupportedException($"Unsupported binary op: {op}"),
                    };
                    break;
                default:
                    throw new NotImplementedException();
            }

            return true;
        }

        if ((i.Op == Specification.Op.OpConstantComposite || i.Op == Specification.Op.OpSpecConstantComposite) &&
            (OpConstantComposite)i is { } constantComposite)
        {
            var values = constantComposite.Constituents;
            var constants = new object[values.WordCount];
            for (int j = 0; j < values.WordCount; ++j)
            {
                if (!TryGetConstantValue(values.Elements.Span[j], out constants[j]!, out _))
                    return false;

View on GitHub (pinned to 96fad776d2)