stride3d/stride · error · NotSupportedException

Unsupported unary op

Error message

Unsupported unary op: {op}

What it means

Unary constant folding implements only OpSNegate (integer negate) and OpFNegate (float negate); any other opcode routed into the unary case throws NotSupportedException 'Unsupported unary op'. Missing ops include OpNot (bitwise not) and OpLogicalNot.

Solutions

  1. Pre-compute the negation and emit the already-negated constant.
  2. Extend the unary switch: OpNot => (object)~(int)unaryOperand, OpLogicalNot => (object)!(((int)unaryOperand) != 0).
  3. Emit the unary op as a runtime instruction instead of folding.

Example fix

// before
Specification.Op.OpFNegate => -(float)unaryOperand,
_ => throw new NotSupportedException(...)
// after
Specification.Op.OpFNegate => -(float)unaryOperand,
Specification.Op.OpNot => (object)~(int)unaryOperand,
Specification.Op.OpLogicalNot => (object)!(((int)unaryOperand) != 0),
_ => throw new NotSupportedException(...)
Defensive patterns

Strategy: validation

Validate before calling

if (op is not (Specification.Op.OpSNegate or Specification.Op.OpFNegate)) EmitRuntimeInstruction(op); // only negate ops fold

Type guard

bool IsFoldableUnary(Specification.Op op) => op is Specification.Op.OpSNegate or Specification.Op.OpFNegate;

Try / catch

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

Prevention

When it happens

Trigger: Constant-folding OpNot (~x), OpLogicalNot (!x), or any other unary opcode whose operand is a constant, reaching the default arm of the unary switch.

Common situations: Bitmask inversion or boolean negation of constant expressions in generated shader code (e.g. ~0u flags, !true).

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

Appendix: source

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

                        Scalar.Int => (object)(int)bitcastBits,
                        Scalar.UInt => bitcastBits,
                        Scalar.Float => BitConverter.UInt32BitsToSingle(bitcastBits),
                        _ => throw new NotSupportedException($"OpBitcast target scalar {bitcastTargetScalar} is not supported"),
                    };
                    break;
                // Unary operations
                case Specification.Op.OpSNegate:
                case Specification.Op.OpFNegate:
                    if (!TryGetConstantValue(i.Data.Memory.Span[4], out var unaryOperand, out var unaryOperandTypeId))
                        return false;
                    if (unaryOperandTypeId != resultType)
                        return false;
                    value = op switch
                    {
                        // Note: first cast to object is important, otherwise int/float will be cast as float
                        Specification.Op.OpSNegate => (object)(-(int)unaryOperand),
                        Specification.Op.OpFNegate => -(float)unaryOperand,
                        _ => throw new NotSupportedException($"Unsupported unary op: {op}"),
                    };
                    break;
                // Binary operations
                case Specification.Op.OpIAdd:
                case Specification.Op.OpISub:
                case Specification.Op.OpIMul:
                case Specification.Op.OpFAdd:
                case Specification.Op.OpFSub:
                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
                    {

View on GitHub (pinned to 96fad776d2)