Hot reload
low dev app.low or low dev app/ builds a dev binary, runs it, and watches every source file of every loaded module, every embedded file, and the file lists of their directories. On save:
A file that fails to compile is reported and the running program is left alone.
The program restarts when any of these changes:
- the set of linked libraries;
- a struct or enum layout;
- an existing function's signature;
- a closure's captured variables;
main.
A running frame of old code may still call any function, so it must keep the old calling convention and data layout.
mainnever returns to pick up new code.Otherwise every function whose generated assembly differs from the running version is recompiled, including callers whose code changed only because a callee did, every instance of an edited generic function, and new functions. Heap, stack and all live values are kept.
Mechanism:
- Dev builds call every function through
fn_table, a writable table of 4096 code addresses, withcall [rip+fn_table+8*i]. Release builds call directly. - In dev builds a function value's code word is the address of its
fn_tableentry and calls through it usecall [r10], so a stored function value or closure runs the newest code. Callback thunks call through the table too. - At start the dev runtime installs a
SIGUSR1handler and maps a 64 MB read-execute region at0x40000000, close enough to the binary for 32-bit relative references. - A patch is assembled with only the changed functions and their strings, then linked at the next free address in that region against the running binary's symbols (
ld -R). Patched functions get a fresh symbol suffix so they never collide with the originals. low devwrites the machine code into the region through/proc/<pid>/mem, writes(index, address)pairs and a count into a mailbox in the program, and sendsSIGUSR1. The handler stores each address intofn_tablewith an aligned 8-byte write and clears the count, whichlow devwaits for.- Frames already running keep executing their old code; the next call through the table enters the new code. Old code is never unmapped.