{"record":{"id":"9858aca9edfeb8d0","repo":"stride3d/stride","slug":"l-value-int-or-uint-expected-but-got-desttype","errorCode":null,"errorMessage":"l-value int or uint expected but got {destType}","messagePattern":"l-value int or uint expected but got (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs","lineNumber":633,"sourceCode":"    public static SpirvValue CompileGLSLFloatBinaryCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, SymbolType resultType, Specification.GLSLOp op, SpirvValue x, SpirvValue y)\n    {\n        var instruction = builder.Insert(new GLSLPow(context.GetOrRegister(resultType), context.Bound++, context.GetGLSL(), x.Id, y.Id));\n        // Adjust OpCode only since Pow/Atan2/etc. share the same operands\n        instruction.InstructionMemory.Span[4] = (int)op;\n        return new(instruction.ResultId, instruction.ResultType);\n    }\n\n    public static SpirvValue CompileBitcastCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, SymbolType resultType, SpirvValue x)\n    {\n        var instruction = builder.Insert(new OpBitcast(context.GetOrRegister(resultType), context.Bound++, x.Id));\n        return new(instruction.ResultId, instruction.ResultType);\n    }\n\n    public static SpirvValue CompileInterlockedCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, InterlockedOp op, SpirvValue dest, SpirvValue value, SpirvValue? originalLocation = null, SpirvValue? compare = null)\n    {\n        var destType = context.ReverseTypes[dest.TypeId];\n        if (destType is not PointerType pointerType || pointerType.BaseType is not ScalarType { Type: Scalar.UInt or Scalar.Int } s)\n            throw new InvalidOperationException($\"l-value int or uint expected but got {destType}\");\n\n        var resultType = s;\n\n        // If there is an out parameter to save original value\n        SpirvValue originalValue;\n        if (op == InterlockedOp.CompareStore || op == InterlockedOp.CompareExchange)\n        {\n            var instruction = builder.Insert(new OpAtomicCompareExchange(context.GetOrRegister(resultType), context.Bound++, dest.Id,\n                context.CompileConstant((int)Specification.Scope.Device).Id,\n                context.CompileConstant((int)Specification.MemorySemanticsMask.Relaxed).Id,\n                context.CompileConstant((int)Specification.MemorySemanticsMask.Relaxed).Id,\n                compare!.Value.Id,\n                value.Id));\n            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,","sourceCodeStart":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs#L615-L651","documentation":"CompileInterlockedCall implements InterlockedAdd/Exchange/CompareExchange and friends as SPIR-V atomic opcodes. SPIR-V atomics require the destination to be a pointer to an int or uint scalar; if the dest argument is not a PointerType or points to a non-integer scalar (e.g. float), InvalidOperationException is thrown.","triggerScenarios":"Passing a regular (non-l-value) value, a float-typed variable, or a struct member of unsupported type as the dest of an InterlockedAdd/InterlockedExchange/InterlockedCompareExchange call.","commonSituations":"Doing atomic adds on float buffers (not supported by this path); passing a local variable instead of a RWByteAddressBuffer/RWStructuredBuffer element or groupshared variable; HLSL code ported from float atomics on other APIs (e.g. CUDA) that Vulkan doesn't expose here.","solutions":["Make the dest an int/uint storage location: use RWStructuredBuffer<int/uint> or groupshared int/uint and pass the element reference.","For float atomics, emulate with InterlockedCompareExchange on uint bits or use asuint-based CAS loops.","Ensure you pass the l-value itself (e.g. buffer[index]) not a previously copied value.","Verify the pointed-to scalar type resolves to int or uint; cast the buffer element type if needed."],"exampleFix":"// before (SDSL/HLSL)\nfloat counter;\nInterlockedAdd(counter, 1); // throws\n// after\nuint counter;\nInterlockedAdd(counter, 1u); // dest must be l-value int/uint","handlingStrategy":"validation","validationCode":"bool IsInterlockedDest(SpirvContext ctx, SpirvValue dest) =>\n    ctx.ReverseTypes[dest.TypeId] is PointerType { BaseType: ScalarType { Type: Scalar.UInt or Scalar.Int } };","typeGuard":"bool IsIntPointer(TypeBase t) => t is PointerType { BaseType: ScalarType { Type: Scalar.Int or Scalar.UInt } };","tryCatchPattern":"try { result = CompileInterlockedCall(table, ctx, builder, op, dest, value); }\ncatch (InvalidOperationException ex) { /* report dest must be l-value int/uint */ }","preventionTips":["Only perform atomics on int/uint storage (buffers, groupshared)","Emulate float atomics with asuint CAS loops","Pass the l-value itself, not a copied value"],"tags":["shader","spirv","sdsl","atomics"],"backgroundTag":"invalid-argument-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"}