{"record":{"id":"db9959562448ea38","repo":"stride3d/stride","slug":"unsupported-interlocked-operation-op","errorCode":null,"errorMessage":"Unsupported interlocked operation: {op}","messagePattern":"Unsupported interlocked operation: (.+?)","errorType":"exception","errorClass":"NotSupportedException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs","lineNumber":665,"sourceCode":"            originalValue = new SpirvValue(instruction.ResultId, instruction.ResultType);\n        }\n        else\n        {\n            var instruction = builder.Insert(new OpAtomicIAdd(context.GetOrRegister(resultType), context.Bound++, dest.Id,\n                context.CompileConstant((int)Specification.Scope.Device).Id,\n                context.CompileConstant((int)Specification.MemorySemanticsMask.Relaxed).Id,\n                value.Id));\n            // Update instruction type (they all share same memory layout)\n            instruction.InstructionMemory.Span[0] = (int)(instruction.InstructionMemory.Span[0] & 0xFFFF0000) | (int)(op switch\n            {\n                InterlockedOp.Add => Specification.Op.OpAtomicIAdd,\n                InterlockedOp.And => Specification.Op.OpAtomicAnd,\n                InterlockedOp.Or => Specification.Op.OpAtomicOr,\n                InterlockedOp.Xor => Specification.Op.OpAtomicXor,\n                InterlockedOp.Max => s.IsSigned() ? Specification.Op.OpAtomicSMax : Specification.Op.OpAtomicUMax,\n                InterlockedOp.Min => s.IsSigned() ? Specification.Op.OpAtomicSMin : Specification.Op.OpAtomicUMin,\n                InterlockedOp.Exchange => Specification.Op.OpAtomicExchange,\n                _ => throw new NotSupportedException($\"Unsupported interlocked operation: {op}\"),\n            });\n            originalValue = new SpirvValue(instruction.ResultId, instruction.ResultType);\n        }\n\n        // Out parameter?\n        if (originalLocation is { } originalLocationValue)\n        {\n            var originalLocationType = context.ReverseTypes[originalLocationValue.TypeId];\n            if (originalLocationType is not PointerType originalLocationPointerType)\n                throw new InvalidOperationException($\"out parameter is not a l-value, got {originalLocationType} instead\");\n\n            originalValue = builder.Convert(context, originalValue, originalLocationPointerType.BaseType);\n            builder.Insert(new OpStore(originalLocationValue.Id, originalValue.Id, null, []));\n        }\n\n        return new();\n    }\n","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs#L647-L683","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (C#)\nInterlockedOp.Xchg => /* missing */\n_ => throw new NotSupportedException($\"Unsupported interlocked operation: {op}\"),\n// after\nInterlockedOp.Exchange => Specification.Op.OpAtomicExchange,\nInterlockedOp.Xchg => Specification.Op.OpAtomicExchange, // add the missing mapping","handlingStrategy":"type-guard","validationCode":"static readonly HashSet<InterlockedOp> Supported = new() {\n    InterlockedOp.Add, InterlockedOp.And, InterlockedOp.Or, InterlockedOp.Xor,\n    InterlockedOp.Min, InterlockedOp.Max, InterlockedOp.Exchange,\n    InterlockedOp.CompareExchange, InterlockedOp.CompareStore };\nbool IsSupportedInterlockedOp(InterlockedOp op) => Supported.Contains(op);","typeGuard":null,"tryCatchPattern":"try { result = CompileInterlockedCall(table, ctx, builder, op, dest, value); }\ncatch (NotSupportedException ex) { /* map op to SPIR-V opcode or reject at compile time */ }","preventionTips":["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"],"tags":["shader","spirv","sdsl","atomics","not-implemented"],"backgroundTag":"invalid-enum-value","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}