stride3d/stride · error · InvalidOperationException
out parameter is not a l-value, got
Error message
out parameter is not a l-value, got {originalLocationType} instead What it means
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.
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.
Example fix
// before (SDSL/HLSL) InterlockedAdd(counter, 1, out (counter + 0)); // throws // after uint original; InterlockedAdd(counter, 1, out original);
Defensive patterns
Strategy: validation
Validate before calling
bool IsWritablePointer(SpirvContext ctx, SpirvValue loc) =>
ctx.ReverseTypes[loc.TypeId] is PointerType; Type guard
bool IsPointer(TypeBase t) => t is PointerType;
Try / catch
try { result = CompileInterlockedCall(table, ctx, builder, op, dest, value, originalLocation); }
catch (InvalidOperationException ex) { /* report out param must be an l-value */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- l-value int or uint expected but got
- Unsupported interlocked operation
- Unsupported element type for clamp
- Unsupported mul operand types
- Unsupported element type for saturate
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/3992a3c8a64ac963.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Parsing/SDSL/AST/IntrinsicImplementations.cs:675
{
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();
}
public static SpirvValue CompileMemoryBarrierCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, Specification.MemorySemanticsMask memorySemanticsMask)
{
builder.Insert(new OpMemoryBarrier(context.CompileConstant((int)Specification.Scope.Device).Id, context.CompileConstant((int)memorySemanticsMask).Id));
return new();
}
public static SpirvValue CompileControlBarrierCall(SymbolTable table, SpirvContext context, SpirvBuilder builder, Specification.MemorySemanticsMask memorySemanticsMask)
{
builder.Insert(new OpControlBarrier(context.CompileConstant((int)Specification.Scope.Workgroup).Id, context.CompileConstant((int)Specification.Scope.Device).Id, context.CompileConstant((int)memorySemanticsMask).Id));
return new();
}View on GitHub (pinned to 96fad776d2)