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

  1. Declare a local int/uint variable and pass it as the out parameter so it resolves to a pointer.
  2. Pass a buffer element l-value (e.g. buffer[index]) rather than an expression.
  3. Avoid passing swizzled or compound expressions to the out parameter; split into an explicit variable.
  4. 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

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


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)