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
- Pre-compute the negation and emit the already-negated constant.
- Extend the unary switch: OpNot => (object)~(int)unaryOperand, OpLogicalNot => (object)!(((int)unaryOperand) != 0).
- 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
- Pre-compute bitwise/boolean negation of constants before codegen
- Restrict constant unary expressions to numeric negation
- Add folding support (or tests) for OpNot/OpLogicalNot if your front end emits them
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
- OpBitcast target scalar
- Unsupported binary op
- Unsupported conversion op
- OpBitcast operand type
- OpBitcast result type
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)