5.1. Verification path: YieldElim
YieldElim (enabled by verifyCoroutine := true) verifies each coroutine as a
straight-line procedure, so the verifier never reasons about a dispatch loop. It
rewrites every yield in the body into an inline block
assert ⋀guarantees; snapshot $old_heap; // = $heap, pre-havoc (for the rely's old) havocHeap(); // the environment acts (a havoc) assume ⋀relies; snapshot $old_heap // = $heap, post-havoc (start of the next step)
and clears the coroutine's relies/guarantees clauses, turning it into a regular
procedure. The assert discharges the coroutine's own step guarantee; the
havocHeap() call (a bodiless opaque modifies * preamble procedure) models the
environment acting while suspended; the assume grants the rely about that
environment step. havocHeap's monotonic-counter postcondition records that the
environment step allocates but never deallocates, so a post-yield allocation cannot
alias a pre-yield reference.
Both clause families are two-state, and each relates a prior heap to the current
$heap. A single reassigned snapshot $old_heap serves both sides, since a coroutine
step is linear:
-
A guarantee's
old(...)— spelledoldGuarantee(...)in body asserts and loop invariants — reads$old_heapwhile it holds the start of the current step (procedure entry, or the post-havoc heap after the previous yield). -
A rely's
old(...)— spelledoldRelies(...)— reads$old_heapafter the pre-havoc reassignment, so an assumed rely isR($old_heap, $heap)= "what the environment did across this step".
Both are emitted as a labeled Old (some $old_heap), which heap parameterization
evaluates against $old_heap; heap-param also declares the $old_heap local (seeded to
the entry heap) and lowers each Snapshot to $old_heap := $heap. The per-yield asserts
cover each resume → yield step; a separate pass step (addExitGuarantees) asserts
the guarantee on every path out of the body (before each return and at the body end),
covering the final resume → halt segment that the caller also observes.
YieldElim runs before HeapParameterization: it never touches $heap directly,
emitting Snapshot/labeled-Old/havocHeap() for heap-param to lower. Resolution
is disabled across that gap, so those transient artifacts reach heap-param untouched.
YieldElim also handles the caller-side dual (the coroutineRelyHeap step): it threads a
per-instance $h1_co snapshot through resume callers and adds the $h_rely_old
parameter to the generated resume procedures. Unlike the body rewrite, this caller-side
step runs regardless of verifyCoroutine, since the default elaboration path also emits
the oldRelies/oldGuarantees markers it lowers.