stride3d/stride · error · NotSupportedException
Unsupported interlocked operation
Error message
Unsupported interlocked operation: {op} What it means
CompileInterlockedCall maps an InterlockedOp enum value to its SPIR-V atomic opcode (OpAtomicIAdd, OpAtomicAnd, OpAtomicUMax, etc.). The switch covers Add, And, Or, Xor, Max, Min, Exchange, CompareExchange, CompareStore; any other op value falls to the default and throws NotSupportedException.
Solutions
- Add a case in the switch in CompileInterlockedCall mapping the new InterlockedOp to its SPIR-V atomic opcode.
- Check which Interlocked intrinsic your shader code calls and confirm it is in the supported set (Add, And, Or, Xor, Min, Max, Exchange, CompareExchange, CompareStore).
- If the op is unsupported on SPIR-V, rewrite the shader using an emulated CAS (compare-exchange) loop.
- Report/patch upstream in Stride's IntrinsicImplementations if a standard op is missing.
Example fix
// before (C#)
InterlockedOp.Xchg => /* missing */
_ => throw new NotSupportedException($"Unsupported interlocked operation: {op}"),
// after
InterlockedOp.Exchange => Specification.Op.OpAtomicExchange,
InterlockedOp.Xchg => Specification.Op.OpAtomicExchange, // add the missing mapping Defensive patterns
Strategy: type-guard
Validate before calling
static readonly HashSet<InterlockedOp> Supported = new() {
InterlockedOp.Add, InterlockedOp.And, InterlockedOp.Or, InterlockedOp.Xor,
InterlockedOp.Min, InterlockedOp.Max, InterlockedOp.Exchange,
InterlockedOp.CompareExchange, InterlockedOp.CompareStore };
bool IsSupportedInterlockedOp(InterlockedOp op) => Supported.Contains(op); Try / catch
try { result = CompileInterlockedCall(table, ctx, builder, op, dest, value); }
catch (NotSupportedException ex) { /* map op to SPIR-V opcode or reject at compile time */ } Prevention
- Add the opcode mapping whenever adding a new InterlockedOp enum member
- Enumerate the InterlockedOp enum in tests against the switch
- Reject unsupported ops at the SDSL frontend before codegen
When it happens
Trigger: An InterlockedOp enum value outside the implemented set reaching CompileInterlockedCall — typically from a new intrinsic wired to the dispatcher without adding its opcode mapping, or internal dispatch confusion.
Common situations: Adding a new interlocked intrinsic in SDSL but forgetting to extend the opcode switch; a frontend feature (e.g. InterlockedAdd on floats via a special op flag) not yet mapped.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- l-value int or uint expected but got
- out parameter is not a l-value, got
- Assign back to matrix swizzle is not implemented yet
- unknown accessor on type in expression
- Unsupported element type for clamp
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/db9959562448ea38.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs:665
originalValue = new SpirvValue(instruction.ResultId, instruction.ResultType);
}
else
{
var instruction = builder.Insert(new OpAtomicIAdd(context.GetOrRegister(resultType), context.Bound++, dest.Id,
context.CompileConstant((int)Specification.Scope.Device).Id,
context.CompileConstant((int)Specification.MemorySemanticsMask.Relaxed).Id,
value.Id));
// Update instruction type (they all share same memory layout)
instruction.InstructionMemory.Span[0] = (int)(instruction.InstructionMemory.Span[0] & 0xFFFF0000) | (int)(op switch
{
InterlockedOp.Add => Specification.Op.OpAtomicIAdd,
InterlockedOp.And => Specification.Op.OpAtomicAnd,
InterlockedOp.Or => Specification.Op.OpAtomicOr,
InterlockedOp.Xor => Specification.Op.OpAtomicXor,
InterlockedOp.Max => s.IsSigned() ? Specification.Op.OpAtomicSMax : Specification.Op.OpAtomicUMax,
InterlockedOp.Min => s.IsSigned() ? Specification.Op.OpAtomicSMin : Specification.Op.OpAtomicUMin,
InterlockedOp.Exchange => Specification.Op.OpAtomicExchange,
_ => throw new NotSupportedException($"Unsupported interlocked operation: {op}"),
});
originalValue = new SpirvValue(instruction.ResultId, instruction.ResultType);
}
// Out parameter?
if (originalLocation is { } originalLocationValue)
{
var originalLocationType = context.ReverseTypes[originalLocationValue.TypeId];
if (originalLocationType is not PointerType originalLocationPointerType)
throw new InvalidOperationException($"out parameter is not a l-value, got {originalLocationType} instead");
originalValue = builder.Convert(context, originalValue, originalLocationPointerType.BaseType);
builder.Insert(new OpStore(originalLocationValue.Id, originalValue.Id, null, []));
}
return new();
}
View on GitHub (pinned to 96fad776d2)