← board

A routine with a managed result pays an exception frame even when its body cannot raise

Measured (2026-10-02, frankuser, x86-64, default -O)

def f(n): for i in range(n): x = list(range(32)), 2000 passes, run under callgrind. Symbols come from the program's .map, which lists about 480 of its 2300 procs, so unlisted addresses resolve to the nearest listed one.

Where the decision is made

pasparser_proc.inc, about line 3735:

needsProcCleanupFrame := TargetHasProcCleanupFrame and ExceptionUsed and
                         (not isAsmFunc) and ProcHasManagedLocalCleanup(procIdx, -1);

The decision is made at the PROLOGUE, before the body is parsed, so the compiler cannot yet know whether the body can raise. The late-arm path (ProcCleanupFrameWanted / ProcCleanupFrameLateArmed, about line 3970) shows a frame can be decided after the body has been compiled. This ticket wants the reverse: request a frame at the prologue, then cancel it.

Shape of a fix

The frame exists to release managed locals when an exception unwinds through the routine. A body that makes no call, has no raise, and has no checked operation that traps into the exception runtime cannot be unwound through, so it does not need the frame. Two options:

  1. Patch the frame-enter sequence to a jump over itself once the body is compiled and shown to be leaf and non-raising, on each backend that has EmitProcCleanupFrameEnterForTarget.
  2. Defer prologue emission until after ParseBlockAST. This is cleaner but touches every backend's prologue order.

pynone is the tiny case. The bigger win is RTL routines that call only other non-raising RTL routines. Covering those needs a per-proc "cannot raise" bit propagated bottom-up, which is a larger job.

pyiter_has minted about 46 Variant temps across all its arms and cleared them all on every call (2.97M clears in this profile). It now has a fast path with no managed temps for list, range and generator cursors. The rest go to pyiter_has_slow. Same program: 236M → 157M instructions, and the per-call times in the format/set leak fixture halved for the range rows.