stride3d/stride · error · InvalidOperationException

No function termination, but a return value is expected

Error message

No function termination, but a return value is expected

What it means

EndFunction auto-inserts a Return when the last block isn't terminated. If the function's return type is non-void, no value-less return can be synthesized, so it throws InvalidOperationException: the function body ended without returning the required value.

Solutions

  1. Ensure every execution path in the function ends with Return(value) for non-void functions.
  2. Add a final Return(defaultValue) after branch merges as a fallback.
  3. Make helper-generated control flow always terminate each branch with a return.
  4. Verify with SPIR-V validation (spirv-val) during development to catch missing terminators early.

Example fix

// before
if (cond) { Return(a); } else { Branch(elseBlock); } // falls off the end
// after
if (cond) { Return(a); } else { Return(b); } // or: merge block ends with Return(result)
Defensive patterns

Strategy: validation

Validate before calling

if (functionType.ReturnType != ScalarType.Void) ensureAllPathsReturn(bodyBlocks);

Type guard

bool needsExplicitReturn = fn.FunctionType.ReturnType != ScalarType.Void;

Try / catch

try { builder.EndFunction(); } catch (InvalidOperationException ex) when (ex.Message.Contains("return value")) { /* append a fallback Return(value) before retrying */ }

Prevention

When it happens

Trigger: Emitting a non-void function whose control flow reaches OpFunctionEnd without an OpReturnValue/OpReturn — e.g. all branches returned but a trailing path (or a missed branch) falls through, or Return(value) was never emitted on some path.

Common situations: Shader functions with if/else where one branch omits the return, early code-generated functions with dynamic branches (discard/clip) that skip the final return, or translation bugs from HLSL where all paths were assumed to return.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/35185d8c50d30de9. Report an issue: GitHub.

Appendix: source

Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Functions.cs:39

    public void BeginFunction(SpirvContext context, SpirvFunction function, FunctionControlMask mask = FunctionControlMask.None)
    {
        Buffer.Add(new OpFunction(context.GetOrRegister(function.FunctionType.ReturnType), function.Id, mask, context.GetOrRegister(function.FunctionType)));
        Position = Buffer.Count;
        CurrentFunction = function;
    }

    public void EndFunction()
    {
        if (CurrentFunction is null)
            throw new InvalidOperationException("Trying to end a function which was not started");

        // If there was no explicit return, add one
        var lastInstruction = Buffer[Position - 1];
        if (!IsBlockTermination(lastInstruction.Op))
        {
            if (CurrentFunction.Value.FunctionType.ReturnType != ScalarType.Void)
                throw new InvalidOperationException("No function termination, but a return value is expected");

            Return(null);
        }

        Buffer.Insert(Position++, new OpFunctionEnd());
    }

    public SpirvValue EmitFunctionParameter(SpirvContext context, string name, SymbolType type)
    {
        var p = Buffer.Insert(Position++, new OpFunctionParameter(context.GetOrRegister(type), context.Bound++));
        context.AddName(p, name);
        var value = new SpirvValue(p.ResultId, p.ResultType, name);
        CurrentFunction!.Value.Parameters.Add(name, value);
        return value;
    }

    public static OpFunctionParameter GetFunctionParameter(SpirvBuffer buffer, Symbol method, int functionParameterIndex)
    {

View on GitHub (pinned to 96fad776d2)