stride3d/stride · error · InvalidOperationException
Trying to end a function which was not started
Error message
Trying to end a function which was not started
What it means
EndFunction finalizes the SPIR-V function currently being built. It throws InvalidOperationException when CurrentFunction is null, i.e. EndFunction was called without a matching StartFunction — the builder's function stack is empty.
Solutions
- Ensure every EndFunction call is paired with exactly one earlier StartFunction.
- Guard the EndFunction call: only call it if CurrentFunction is not null.
- In error/finally paths, check builder state before finalizing.
- Log function start/end pairs during development to detect imbalance.
Example fix
// before
builder.EndFunction();
// after
if (builder.CurrentFunction is not null)
builder.EndFunction(); Defensive patterns
Strategy: validation
Validate before calling
if (builder.CurrentFunction is null) return; // nothing to end
Type guard
bool canEndFunction = builder.CurrentFunction is not null;
Try / catch
try { builder.EndFunction(); } catch (InvalidOperationException ex) { /* unbalanced Start/EndFunction — audit emit pipeline */ throw; } Prevention
- Strictly pair StartFunction/EndFunction calls
- Guard cleanup/finally paths with a null check
- Avoid calling EndFunction twice for the same function
- Track function scope in the generator state machine
When it happens
Trigger: Calling EndFunction() when no function was started, calling it twice for the same function, or an exception earlier in emission skipped StartFunction while cleanup still calls EndFunction.
Common situations: Unbalanced Start/End pairs in shader code generators, exception paths that partially emit a function, or refactored emit pipelines where a helper now starts the function conditionally.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Binary operator not supported for element type
- Can't cast between array of different sizes
- Can't cast between array of different types
- Can't cast between structures
- Can't cast from to (more components)
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/9235ae49d332db10.
Report an issue: GitHub.
Appendix: source
Thrown at sources/shaders/Stride.Shaders.Parsers/Spirv/Building/Builder.Functions.cs:32
var func = context.Bound++;
foreach (var t in ftype.ParameterTypes)
context.GetOrRegister(t.Type);
context.AddName(func, name);
var result = new SpirvFunction(func, name, ftype) { IsStage = isStage };
return result;
}
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);View on GitHub (pinned to 96fad776d2)