x-engine-c v0.2.13
The C engine for x-lang
Loading...
Searching...
No Matches
callcc.c File Reference

First-class continuation primitives (call/cc via stack copying). More...

#include "x-prim.h"
#include "x-eval.h"
#include "x-env.h"
#include "x-heap.h"
#include <setjmp.h>
#include <string.h>
#include "x-type/list.h"
#include "x-type/prim.h"
#include "x-type/ptr.h"
#include "x-type/procedure.h"
#include "x-type/symbol.h"

Data Structures

struct  x_callcc_cont_t
 

Macros

#define X_CALLCC_NO_ASAN
 

Functions

static X_CALLCC_NO_ASAN void x_callcc_copy (char *dst, const char *src, size_t n)
 
X_CALLCC_NO_ASAN void x_callcc_init (void)
 
static X_CALLCC_NO_ASAN void x_callcc_sp (void **pp_sp)
 
static void x_callcc_restore (x_callcc_cont_t *cont)
 
static x_obj_tx_prim_cc_invoke (x_obj_t *p_base, x_obj_t *p_args)
 
static X_CALLCC_NO_ASAN x_obj_tx_prim_callcc (x_obj_t *p_base, x_obj_t *p_args)
 
x_obj_tx_prim_callcc_register (x_obj_t *p_base, x_obj_t *p_args)
 

Variables

static void(*volatile x_callcc_sp_fn )(void **) = x_callcc_sp
 
static void(*volatile x_callcc_restore_fn )(x_callcc_cont_t *)
 
Continuation Data

Stack-copying continuations: capture the C call stack segment from a known base (set in main) to the current frame. Invocation restores the stack and longjmps back to the capture point.

Interpreter state (env, save_stack, error_handler) is saved as a GC-visible x-lang list in the continuation procedure's closure env.

static void * g_stack_base = NULL
 

Detailed Description

First-class continuation primitives (call/cc via stack copying).

Author
Jon Ruttan (jonru.nosp@m.ttan.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)

Macro Definition Documentation

◆ X_CALLCC_NO_ASAN

#define X_CALLCC_NO_ASAN

Function Documentation

◆ x_callcc_copy()

static X_CALLCC_NO_ASAN void x_callcc_copy ( char *  dst,
const char *  src,
size_t  n 
)
static

Copy a captured stack segment. Under ASan this must be a plain byte loop in an exempt function – see the truce note above.

◆ x_callcc_init()

X_CALLCC_NO_ASAN void x_callcc_init ( void  )

Establish the stack base address for continuation capture.

Must be called once at interpreter startup, from a frame close to the bottom of the C call stack (typically main). The address of a local variable is recorded as the upper bound of all future stack captures.

Note
Uses a volatile local to prevent the compiler from eliminating the anchor variable.

◆ x_callcc_restore()

static X_CALLCC_NO_ASAN void x_callcc_restore ( x_callcc_cont_t cont)
static

Restore a captured continuation by replacing the current stack.

Recursively grows the C stack (via a 2 KiB pad per frame) until the stack pointer is below the lower bound of the captured segment, then copies the saved bytes back and longjmps to the capture point.

Parameters
contContinuation whose stack segment and jmp_buf to restore.
Note
This function never returns.

◆ x_callcc_sp()

static X_CALLCC_NO_ASAN void x_callcc_sp ( void **  pp_sp)
static

Store an address strictly below the caller's entire stack frame.

The capture in x_prim_callcc() must include EVERY slot of its own frame: locals the compiler spills below the address taken (gcc puts p_base/cont there) would otherwise be restored as garbage after longjmp – the crash class that only clang's register allocation hid. A callee's local is below the caller's whole frame by construction, so its address is a safe lower bound.

Note
Out-parameter, not a return value: gcc's -Wreturn-local-addr pass REPLACES a returned local address with NULL at -O; a store through a pointer (same idiom as x_callcc_init) is only warned about, never rewritten.

◆ x_prim_callcc()

static X_CALLCC_NO_ASAN x_obj_t * x_prim_callcc ( x_obj_t p_base,
x_obj_t p_args 
)
static

Capture the current continuation and pass it to a procedure. x-lang: (call/cc proc)

Allocates a continuation struct with a copy of the C stack segment from the current frame up to g_stack_base, saves interpreter state in a GC-visible list, and constructs a variadic closure k that invokes cc-invoke when called. Then applies proc to k.

When k is later invoked, the saved stack is restored via x_callcc_restore() and control returns here through longjmp, yielding the value passed to k.

Parameters
p_baseInterpreter base context.
p_argsUnevaluated argument list: (call/cc proc).
Returns
The result of proc, or the value passed to the continuation.
See also
x_prim_cc_invoke, x_callcc_restore

◆ x_prim_callcc_register()

x_obj_t * x_prim_callcc_register ( x_obj_t p_base,
x_obj_t p_args 
)

Register continuation primitives.

Binds: cc-invoke, call/cc.

Parameters
p_baseInterpreter base context.
p_argsUnused.
Returns
p_base.

◆ x_prim_cc_invoke()

static x_obj_t * x_prim_cc_invoke ( x_obj_t p_base,
x_obj_t p_args 
)
static

Invoke a captured continuation, restoring its stack and interpreter state. x-lang: (cc-invoke ptr state args-list)

This is the internal entry point called by the closure k that call/cc hands to its procedure. args-list is the variadic argument collector: () for zero arguments, (val) for one.

Parameters
p_baseInterpreter base context.
p_argsUnevaluated argument list: (cc-invoke ptr state args-list).
Returns
Does not return; transfers control via longjmp.
See also
x_prim_callcc, x_callcc_restore

Variable Documentation

◆ g_stack_base

void* g_stack_base = NULL
static

Stack base pointer, set once at startup via x_callcc_init().

◆ x_callcc_restore_fn

void(*volatile x_callcc_restore_fn) (x_callcc_cont_t *) ( x_callcc_cont_t )
static
Initial value:
=
static void x_callcc_restore(x_callcc_cont_t *cont)
Definition callcc.c:154

Volatile call path for the recursion: sibling-call optimization would reuse the frame, so the pad would never advance and the descent would spin forever. A call through a volatile pointer cannot be folded.

◆ x_callcc_sp_fn

void(*volatile x_callcc_sp_fn) (void **) ( void **  ) = x_callcc_sp
static

Volatile call path to x_callcc_sp(): inlining would hoist the anchor back into the caller's frame and re-create the under-capture; a call through a volatile pointer cannot be inlined or sibling-call folded.