AxlRuntime – CRT0-owned runtime glue
Runtime Glue – CRT0-owned init, signals, exit, atexit
The pieces an AXL app interacts with around its own lifecycle and
interruptibility. CRT0 (src/crt0/axl-crt0-native.c) bridges the
UEFI entry point to int main(int argc, char **argv) by calling
_axl_init before main and _axl_cleanup after. This module
owns those two bookends plus everything they wire up: the default
event loop, the cooperative yield, the interrupt handler registry,
the tier-1 resource-leak sweep, and the LIFO atexit callback
registry.
Four sub-modules, each a single concern:
axl-runtime.c–_axl_init/_axl_cleanup, the singletonaxl_loop_default(), andaxl_yield.axl-registry.c– tier-1 firmware-resource registry. Internal-only API (_axl_registry_*) called by the_new_impl/_freepaths of AxlEvent, AxlLoop, AxlCancellable, and AxlArena. Sweeps leaked resources during_axl_cleanup.axl-atexit.c– POSIX-flavored cleanup registry (axl_atexit/axl_atexit_remove). LIFO drain during_axl_cleanup, before the tier-1 sweep.axl-signal.c–axl_signal_install/axl_interrupted/axl_exit. Hooked into loop break detection; invokes the user handler once per Ctrl-C and sets the interrupted flag.
Headers:
<axl/axl-runtime.h>– default loop, yield, registry count<axl/axl-signal.h>– interrupt API + blessed exit<axl/axl-atexit.h>– LIFO cleanup callbacks<axl/axl-loop.h>– includesaxl_loop_iterate_until(nested-wait primitive), the loop-module partner for callers inside a callback that need to wait without freezing outer sources
When to Use What
I need to… |
Use |
|---|---|
Handle Ctrl-C with a custom callback |
|
Ask “did Ctrl-C happen yet?” from a CPU loop |
|
Exit with cleanup guaranteed (vs raw |
|
Keep a tight CPU loop responsive to Ctrl-C |
|
Free a long-lived resource on any exit path |
|
Share a loop across modules |
|
Wait inside a callback without starving the outer loop |
|
Interrupt lifecycle
user presses Ctrl-C
|
v
shell signals its ExecutionBreak event
|
+--- loop observes via axl_backend_shell_break_flag /
| axl_backend_shell_break_event (in axl_loop_next_event)
|
+--- OR axl_yield observes via a non-blocking loop dispatch
| (or a direct poll when no default loop exists)
|
v
_axl_signal_on_break() fires once:
- sets g_axl_interrupted = true
- if axl_signal_install(fn) was called, invokes fn()
- idempotent (subsequent observations no-op)
|
v
yield / loop_run / wait_* return with status indicating interrupt
|
+--- if user handler installed: caller unwinds main, CRT0
| runs _axl_cleanup on main's return
|
+--- if no handler: next axl_yield() auto-calls axl_exit(1),
which takes the same _axl_cleanup path + gBS->Exit
The two exit paths (return from main, axl_exit(rc)) both
converge on _axl_cleanup, so cleanup output is byte-identical
between them. _axl_cleanup has a reentrancy guard: if
axl_exit fires mid-main and the firmware somehow returns from
gBS->Exit (paranoia), CRT0’s post-main cleanup is a no-op.
Cleanup order
_axl_cleanup runs these in order:
atexit callbacks (LIFO) –
_axl_atexit_run_all. User callbacks may free resources that would otherwise show up in the sweep.argv strings –
_axl_args_freeinsrc/posix/axl-app.c.Default loop – explicit
axl_loop_free(mDefaultLoop)so its registry entry comes off cleanly (otherwise sweep would flag it as a leak on every exit).Tier-1 registry sweep –
_axl_registry_sweep. LIFO walk of live entries, each logged with userfile:lineand closed via the appropriate_free.Heap leak report (AXL_MEM_DEBUG only) –
axl_mem_dump_leaks. Release-mode auto-free of heap is deferred (seedocs/AXL-Runtime.md§10.1).
Caller attribution
Every tier-1 resource allocation records the caller’s file/line at macro-expansion time:
AxlEvent *e = axl_event_new(); // expands to:
// axl_event_new_impl(__FILE__, __LINE__)
When the sweep fires on a leak, the warning names that file/line
– the app developer’s call site, not the library’s internal
wrapper. Library-internal callers (e.g., axl_cancellable_new
internally calling axl_event_new) record the library’s own file/
line by design; library code that correctly frees never reaches
the sweep, so the only way those appear is if the library itself
leaks – in which case the library source is the correct
attribution.
See also
docs/AXL-Runtime.md– the design doc, now describing what landed.docs/AXL-Concurrency.md– primitive taxonomy; the runtime sits under these primitives.sdk/examples/runtime-demo.c– eight subcommand scenarios exercising every facet.src/loop/README.md–AxlLoop,AxlDefer,AxlPubsub, and the nested-wait primitive.
API Reference
AxlSignal (interrupts + blessed exit)
Typedefs
-
typedef void (*AxlSignalHandler)(void)
AxlSignalHandler:
Interrupt-time callback. Runs at raised TPL when the shell ExecutionBreak event fires. Do not allocate, do not block, do not call Boot Services that mutate state. Set a flag, log, return.
Functions
-
void axl_signal_install(AxlSignalHandler on_interrupt)
Install a handler fired on Ctrl-C. Overrides the default exit-on-interrupt behavior.
Passing NULL is equivalent to axl_signal_default(): no user handler, the runtime will exit cleanly at the next yield point.
-
void axl_signal_default(void)
Restore the default handler (auto-exit on next yield).
Equivalent to axl_signal_install(NULL) — named for readability.
-
bool axl_interrupted(void)
Poll the interrupted flag.
True between the break event firing and _axl_cleanup clearing it (which only happens as part of axl_exit). App code reads this to decide whether to keep working or unwind.
-
void axl_exit(int rc)
Terminate the app with guaranteed cleanup. Does not return.
Runs atexit callbacks (LIFO), sweeps the tier-1 resource registry, then calls gBS->Exit via the backend. This is the ONE blessed exit path — returning from main takes the same route via CRT0. App code that calls gBS->Exit directly, or aborts through some other path, bypasses cleanup and leaks firmware resources; don’t.
Convention: rc == 0 -> EFI_SUCCESS, any other value -> EFI_ABORTED.
AxlAtexit (LIFO cleanup callbacks)
Typedefs
-
typedef void (*AxlAtexitFn)(void *data)
AxlAtexitFn:
Cleanup callback. data is the opaque pointer supplied at registration time. Runs on the main thread during _axl_cleanup.
Functions
-
uint32_t axl_atexit(AxlAtexitFn fn, void *data)
Register a callback to run during _axl_cleanup.
Callbacks fire in LIFO order on every exit path (return from main, axl_exit, or Ctrl-C through the default signal handler).
- Parameters:
fn – cleanup function
data – opaque user data passed to fn
- Returns:
non-zero handle on success, 0 on failure (fn is NULL or registration-time allocation failed).
-
void axl_atexit_remove(uint32_t handle)
Remove a previously-registered atexit callback.
Safe to call with handle==0 or with an already-removed handle (no-op in both cases). Useful when a resource is freed explicitly before exit and the atexit entry would otherwise run with a dangling pointer.
- Parameters:
handle – handle returned by axl_atexit
AxlRuntime (default loop, yield, registry inspection)
Typedefs
-
typedef struct AxlLoop AxlLoop
Functions
-
AxlLoop *axl_loop_default(void)
Return the runtime’s default loop, lazy-creating on first call.
The default loop is owned by the runtime and freed during _axl_cleanup. Apps can run it directly (axl_loop_run(axl_loop_default())), add their own sources to it, or ignore it entirely and create private loops. axl_yield() dispatches immediately-ready work on this loop opportunistically.
- Returns:
the default loop, or NULL if allocation failed.
-
void axl_yield(void)
Cooperative yield point.
Call inside CPU-bound loops to keep the app interruptible. Cost is ~nanoseconds when the default loop is idle. Safe from any context except a raised-TPL notify handler.
Behavior:
If any immediately-ready work is pending on the default loop (timers, deferred callbacks), dispatch it — bounded to one iteration.
If Ctrl-C was observed during that dispatch, sets the interrupted flag so axl_interrupted() returns true.
Otherwise returns immediately.
-
size_t axl_registry_count(void)
Return the number of tier-1 resources currently registered.
Purely informational — mostly useful in tests to verify resource-balancing. Returns 0 if the registry has not been initialized yet.