{"record":{"id":"3992a3c8a64ac963","repo":"stride3d/stride","slug":"out-parameter-is-not-a-l-value-got-originallocationtype","errorCode":null,"errorMessage":"out parameter is not a l-value, got {originalLocationType} instead","messagePattern":"out parameter is not a l-value, got (.+?) instead","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs","lineNumber":675,"sourceCode":"            {\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\n    public static SpirvValue CompileMemoryBarrierCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, Specification.MemorySemanticsMask memorySemanticsMask)\n    {\n        builder.Insert(new OpMemoryBarrier(context.CompileConstant((int)Specification.Scope.Device).Id, context.CompileConstant((int)memorySemanticsMask).Id));\n        return new();\n    }\n    public static SpirvValue CompileControlBarrierCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, Specification.MemorySemanticsMask memorySemanticsMask)\n    {\n        builder.Insert(new OpControlBarrier(context.CompileConstant((int)Specification.Scope.Workgroup).Id, context.CompileConstant((int)Specification.Scope.Device).Id, context.CompileConstant((int)memorySemanticsMask).Id));\n        return new();\n    }","sourceCodeStart":657,"sourceCodeEnd":693,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs#L657-L693","documentation":"When an interlocked intrinsic has an 'out original' parameter, CompileInterlockedCall stores the pre-op atomic value through the provided location. That location must be a PointerType (an l-value such as a variable or buffer element); if it resolves to any other type (a plain value), InvalidOperationException is thrown because there is no memory address to write to.","triggerScenarios":"Calling e.g. InterlockedAdd(dest, value, out original) where the out argument is not an l-value — a temporary, a function return value, or a constant expression instead of a variable/buffer element.","commonSituations":"Passing a computed expression into the out parameter; assigning to a swizzle or a property that doesn't resolve to storage; shader code where the out variable was optimized or declared in a way the type resolver treats as a value.","solutions":["Declare a local int/uint variable and pass it as the out parameter so it resolves to a pointer.","Pass a buffer element l-value (e.g. buffer[index]) rather than an expression.","Avoid passing swizzled or compound expressions to the out parameter; split into an explicit variable.","If the resolver incorrectly classifies a valid l-value, inspect how the argument's TypeId is assigned upstream."],"exampleFix":"// before (SDSL/HLSL)\nInterlockedAdd(counter, 1, out (counter + 0)); // throws\n// after\nuint original;\nInterlockedAdd(counter, 1, out original);","handlingStrategy":"validation","validationCode":"bool IsWritablePointer(SpirvContext ctx, SpirvValue loc) =>\n    ctx.ReverseTypes[loc.TypeId] is PointerType;","typeGuard":"bool IsPointer(TypeBase t) => t is PointerType;","tryCatchPattern":"try { result = CompileInterlockedCall(table, ctx, builder, op, dest, value, originalLocation); }\ncatch (InvalidOperationException ex) { /* report out param must be an l-value */ }","preventionTips":["Always pass a declared local or buffer element as the out parameter","Avoid expressions, swizzles, or temporaries in out arguments","Keep out variables plain int/uint scalars"],"tags":["shader","spirv","sdsl","atomics","lvalue"],"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"}