Continuation

← Reference
Io

C implementation of first-class continuations. A continuation owns a deep copy of the IoEvalFrame chain that was current at `callcc` time, plus the locals of the capturing block. Invocation is one-shot and destructive: the stored chain is installed as `state->currentFrame`, the continuation's pointer is nulled, and `state->continuationInvoked` tells the eval loop to restart from the new top. `copy` makes a fresh deep copy so the same point can be re-entered more than once. Deep copy is required because popped frames are zeroed by the pool in IoState_iterative_fast.c — a grab-pointer capture would see empty frames by the time `callcc` returns. Frames stay GC-reachable through IoEvalFrame's own mark walk of the parent chain; `capturedLocals` is marked here. `asMap` / `fromMap` serialize a continuation to a plain Map (state enum name, message source text, primitive fields, per-state controlFlow payload) so continuations can cross process or network boundaries and be rebuilt from text — the design target that motivated the stackless rewrite.

IoContinuation_captureFrameStack_(self, frame, locals)

Deep-copies the current frame chain into the continuation and stores the capturing block's locals. Deep copy is mandatory: IoState pops frames back into a pool that zeroes their data, so a raw pointer would see empty frames once callcc returns. Called from Object_callcc while the frame is still in FRAME_STATE_CALLCC_EVAL_BLOCK, so the restored chain resumes exactly at the callcc site.

IoContinuation_free(self)

Frees the IoContinuationData payload. Captured frames are GC-managed IoEvalFrames; we only clear the pointer and let the collector reclaim them via the parent chain if nothing else references them.

IoContinuation_mark(self)

GC mark callback. Marks capturedLocals and the top captured frame; the parent chain is walked by IoEvalFrame_mark, so no loop is needed here. This is what keeps a frame chain alive between capture and invoke even though no Io variable references the intermediate frames.

IoContinuation_new(state)

Convenience constructor: looks up the registered proto and clones it. Used by callcc and by the `copy` method to mint fresh Continuations from C without going through Io's message machinery.

IoContinuation_newTag(state)

Builds the Continuation tag with clone/free/mark function pointers. No compare or write methods — continuations are opaque and not meant to be ordered, hashed, or serialized through the generic path.

IoContinuation_proto(state)

Creates the Continuation proto, allocates an empty IoContinuationData (no captured frame yet), and installs the Io-visible method table. Called once at VM init; later Continuations are clones of this proto.

IoContinuation_rawClone(proto)

Clones the proto with a fresh, empty IoContinuationData. The captured frame and locals are always reset; the actual capture happens later in IoContinuation_captureFrameStack_ when callcc fires.

copyFrameChain_(state, src)

Iteratively clones a linked chain of IoEvalFrames, memcpy'ing the payload and reallocating each frame's argValues array so the copy owns its heap memory. Parent pointers are rewritten as the walk goes, so the returned top frame has a fully independent chain. Used by capture (on callcc) and by the `copy` method (multi-shot support). Recursion-free by design — a deeply nested capture must not overflow the C stack since that is exactly what the stackless rewrite removed.

deserializeControlFlow_(state, frame, map)

Inverse of serializeControlFlow_: dispatches on the already-restored frame state and reads the matching controlFlow arm's fields back out of the map. Missing or ill-typed fields fall back to sensible defaults via mapNumberAt_ / mapStringAt_, so partial maps yield a usable (if approximate) resumption rather than a crash.

deserializeFrame_(state, frameMap)

Allocates a fresh IoEvalFrame and populates it from one map emitted by serializeFrame_: state enum, current message, arg array, primitive flags, result/slotValue, block locals, and the control-flow payload. target and locals are deliberately restored to `state->lobby` — the map stores only their tag names, so the original binding environment is not recoverable across serialization; resumption runs in lobby scope. fromMap stitches the returned frame into a chain.

deserializeMessage_(state, msgMap)

Rebuilds a message tree by reparsing "chainCode" (or "code" as a fallback) through IoMessage_newFromText_label_. Going through the parser — rather than reconstructing chain pointers by hand — keeps the deserialization tolerant of message-internal layout changes and means the result carries proper line/label metadata from the parser.

mapNumberAt_(state, map, key, defaultVal)

Reads a numeric field from a deserialized Map, returning defaultVal for missing keys, nil, or non-Number values. Lets deserializeFrame_ and deserializeControlFlow_ treat absent fields as "use the default" rather than error out — important because serialization drops empty values to keep the output small.

mapStringAt_(state, map, key)

Reads a Sequence field from a deserialized Map, returning NULL for missing, nil, or non-Sequence values. Symbol/Sequence equivalence is handled by ISSEQ.

serializeControlFlow_(state, frame, map)

Dispatches on fd->state and writes the live arm of the controlFlow discriminated union (if / while / loop / for / foreach / callcc) into the frame's output map. States that share a controlFlow payload (e.g. the four FRAME_STATE_WHILE_* states) fall through to one case. Any state without runtime-dependent data — including leaf states like FRAME_STATE_START — writes nothing.

serializeFrame_(state, frame)

Writes one frame's full state to a Map: state enum name, current message, passStops / isNestedEvalRoot flags, argument bookkeeping, already-evaluated arg values, target and locals tag names, result and slotValue slots, blockLocals contents, and call/savedCall stop statuses. Control-flow payload is then appended by serializeControlFlow_. Parent linkage is expressed implicitly by this frame's position in the frames list emitted by asMap.

serializeMessage_(state, msg)

Serializes a message into a Map carrying its name, its local source ("code"), and the full chain source ("chainCode"), plus line number, label, cached result, and arg count. deserializeMessage_ reparses from chainCode, so the chain is rebuilt by going through the parser again rather than by walking tree links — simpler and more robust to message-internals changes.

serializeObject_(state, obj)

Lossy best-effort conversion of an arbitrary IoObject to something that round-trips through asMap / fromMap. Numbers, Sequences, and the singletons nil/true/false pass through unchanged; any other object becomes a Map carrying just its tag name and slot-name list. Cached target pointers and fast-path caches are intentionally dropped — the goal is portable text, not bit-exact reconstruction.