kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9182c95cbe31e38578e3f0fcdc239722ce7eb349
parent 633d6444262a670bdf4f7517be3d0bb70cfbfcbf
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 14 May 2026 19:03:07 -0700

docs: design incremental jit linking

Diffstat:
Adoc/HOT_RELOAD.md | 443+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adoc/INCREMENTAL_LINK.md | 416+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdoc/JIT.md | 4++++
3 files changed, 863 insertions(+), 0 deletions(-)

diff --git a/doc/HOT_RELOAD.md b/doc/HOT_RELOAD.md @@ -0,0 +1,443 @@ +# Function-only hot reload + +This document extends `doc/INCREMENTAL_LINK.md`. It assumes cfree already +has append-only incremental JIT linking: new code can be compiled and +placed in a live image without moving old code. + +Hot reload adds replacement. In v1, only functions can be replaced. Data +symbols, TLS, type layouts, initializers, destructors, and object lifetime +changes are out of scope. + +## 1. Goals + +- Replace the implementation of an existing function in a live JIT image. +- Keep the public address of the function stable when possible. +- Avoid patching every caller for the baseline implementation. +- Allow new code to call old code and replaced code through the same symbol + name. +- Keep old function bodies alive while any stack frame may still return + into them. +- Integrate with `dbg` so a stopped debuggee can reload a function and + continue execution. + +## 2. Non-goals + +- Data replacement or migration. +- Changing function ABI: parameter types, return type, calling convention, + variadic-ness, or visibility. +- Replacing inline copies already compiled into other functions. +- Fully concurrent multi-threaded reload. +- Unloading old generations immediately. +- Replacing functions in external DSOs. +- A production dynamic linker ABI. + +## 3. Core idea + +Append-only linking gives us a way to add a new function body. Hot reload +adds a stable function entry that indirects to the current body. + +For a reloadable function `foo`, callers see: + +```text +foo entry/trampoline -> foo.slot -> current foo body +``` + +Reloading `foo` compiles and appends a new body, relocates it, then updates +`foo.slot` to point at the new body. Existing pointers to `foo` remain +valid because they point at the stable entry/trampoline, not a specific +body generation. + +## 4. Reloadable function representation + +Add a per-function record in the link session: + +```c +typedef struct LinkReloadFunc { + Sym name; + LinkSymId public_sym; + LinkSymId current_body_sym; + uint64_t entry_vaddr; + uint64_t slot_vaddr; + uint64_t current_body_vaddr; + uint32_t generation; + uint8_t abi_hash[16]; +} LinkReloadFunc; +``` + +The exact hash shape can be internal. It must identify the C ABI contract: + +- return type ABI class and size +- parameter ABI classes and sizes +- variadic flag +- target ABI +- calling convention once cfree has more than one + +The hash is not a C type-system identity. It is a runtime-callability +identity. + +## 5. Entry/trampoline shape + +Each supported architecture needs one stable entry sequence. + +AArch64 example: + +```asm +foo: + adrp x16, foo.slot + ldr x16, [x16, #:lo12:foo.slot] + br x16 +foo.slot: + .quad foo.body.0 +``` + +x86-64 example: + +```asm +foo: + jmp *foo.slot(%rip) +foo.slot: + .quad foo.body.0 +``` + +RISC-V example: + +```asm +foo: + auipc t0, %pcrel_hi(foo.slot) + ld t0, %pcrel_lo(foo)(t0) + jr t0 +foo.slot: + .quad foo.body.0 +``` + +The entry lives in RX memory. The slot lives in writable data or in a +JIT-managed patchable cell with the same W^X discipline used elsewhere. + +Slot updates must be pointer-width atomic when the target ABI can observe +the function concurrently. `dbg` v1 can require the worker to be stopped, +but the representation should not rule out atomic publication later. + +## 6. Symbol semantics + +The public symbol name resolves to the stable entry: + +```text +cfree_jit_lookup("foo") == runtime address of foo entry +``` + +The body symbol is internal and generationed: + +```text +foo$body$0 +foo$body$1 +foo$body$2 +``` + +Debug and inspector surfaces should present the public function as `foo`, +with the active body generation as implementation detail. Low-level +symbol iteration may expose generationed body names only under an +internal/debug flag. + +Relocations against `foo` use the public entry by default. Direct body +references are allowed only for linker-synthesized records. + +## 7. Baseline call policy + +The baseline policy is simple: + +- Calls to reloadable functions target the stable entry/trampoline. +- Address-taking of reloadable functions returns the stable entry. +- The slot points at the active body. + +This means reloading a function usually patches one pointer-sized cell, +not every call site. + +Later optimization can patch selected call sites directly to the current +body. That requires a patch-site index and invalidation. It should be a +separate performance phase, not the correctness baseline. + +## 8. Selecting reloadable functions + +Do not make every function reloadable by default in all JIT modes. The +trampoline cost is real and unnecessary for normal `cfree run`. + +Enable it through a JIT/debug option: + +- `dbg` hot-reload mode: externally visible functions are reloadable. +- Optional attribute later: only marked functions are reloadable. +- Internal static functions are not reloadable in v1 unless the reload + command names a containing translation unit and the compiler preserves a + stable synthetic identity for them. + +For v1, restrict reload to global functions with C linkage names visible +to `cfree_jit_lookup`. + +## 9. Reload flow + +Debugger-side: + +```text +reload foo from replacement source + -> compile replacement source to ObjBuilder + -> identify exactly one replacement body for foo + -> verify ABI compatibility + -> append new body and dependencies + -> update foo.slot + -> refresh JIT view / DWARF +``` + +Link-side: + +```text +link_session_reload_function(session, "foo", new_obj) + -> resolve new object against current globals + -> reject data definitions and unsupported side effects + -> place new function sections append-only + -> assign vaddrs + -> apply relocations + -> publish body symbol as generation N+1 + -> atomically store body runtime address into foo.slot +``` + +The public symbol table entry for `foo` does not move. + +## 10. Input restrictions for v1 + +The replacement object may contain: + +- the replacement function body +- private helper functions used only by the replacement +- constants and read-only literals needed by the replacement +- debug sections +- undefined references to already-linked symbols or external resolver + symbols + +The replacement object may not contain: + +- new writable global data +- TLS +- constructors or destructors +- public definitions other than the function being replaced +- strong definitions that collide with existing non-target symbols +- COMDAT/group semantics that require replacing prior selected members + +This keeps reload function-only in practice, not just in name. + +## 11. ABI compatibility + +Before publishing a replacement, verify that the replacement can be called +through the old entry. + +For C frontend replacements, record a compact ABI signature at compile time +for each function definition. The linker should not need to understand full +C types. + +Suggested record: + +```c +typedef struct CfreeFuncAbiSig { + uint8_t target_arch; + uint8_t target_os; + uint8_t call_conv; + uint8_t variadic; + uint8_t ret_class; + uint8_t ret_size_log2; + uint8_t nargs; + uint8_t arg_class[CFREE_ABI_MAX_ARGS]; + uint8_t arg_size_log2[CFREE_ABI_MAX_ARGS]; +} CfreeFuncAbiSig; +``` + +No VLA. If the signature exceeds a fixed bound, mark the function +non-reloadable until a heap-backed encoding is added. + +For non-C objects or missing signatures, v1 should reject reload unless +the user explicitly opts into unchecked replacement. + +## 12. Old generation lifetime + +After a slot update, old bodies remain mapped. + +In `dbg` v1, reload occurs only while the worker is stopped. Even then, the +current stack may contain frames inside the old function. Continuing after +reload must be valid: + +- Existing frames finish in the old body. +- New calls enter the new body. +- Breakpoints in old body addresses remain attached to old code unless + the driver chooses to move source breakpoints. + +Retirement policy: + +- v1: never reclaim old generations until `cfree_jit_free`. +- later: retire when the debugger can prove no stopped/running frame has a + PC or return address inside the old generation. + +Never unmap old code immediately after publishing a replacement. + +## 13. Debugger behavior + +The debugger must distinguish symbol breakpoints from address breakpoints. + +Address breakpoint: + +```text +b *0x1234 +``` + +Stays at that exact address, even if it belongs to an old generation. + +Symbol/source breakpoint: + +```text +b foo +b file.c:42 +``` + +Should be rebound after reload if the source location exists in the new +generation. The old breakpoint should be cleared or marked stale depending +on user policy. + +For v1, a pragmatic rule: + +- Breakpoints set by exact address stay exact. +- Breakpoints set by symbol are moved to the active generation. +- Breakpoints set by file/line are re-resolved after DWARF refresh. +- If re-resolution fails, keep the old breakpoint but mark it stale in + `info breakpoints`. + +The session should be stopped while reload changes breakpoint bindings. + +## 14. DWARF and JIT view + +Every reload increments the JIT generation, same as append-only extension. +`cfree_jit_view` rebuilds on generation mismatch. + +DWARF consumers need enough information to answer two questions: + +- What is the active source location for `foo`? +- If the PC is in an old generation, can we still render its source line? + +v1 can keep old debug info in the rebuilt view. That lets backtraces from +old frames still resolve. Active symbol lookup should prefer the latest +generation for name-to-address queries. + +## 15. Patch-site index + +The baseline does not need caller patching, but a patch-site index is still +useful for future fast mode and for non-function slot fixups later. + +Build the index from durable relocation records: + +```c +target LinkSymId -> LinkRelocApply ids +owner input id -> LinkRelocApply ids +write section id -> LinkRelocApply ids +``` + +Do not scan every relocation on reload. When direct-call optimization lands, +the linker can patch only relocation sites that target the reloaded symbol. + +For v1, it is acceptable to create the data structures but use them only in +assertions/tests. + +## 16. Concurrency and publication + +`dbg` v1 reload is single-threaded: + +1. Worker is stopped. +2. REPL compiles and links replacement. +3. Slot is updated. +4. Breakpoints and DWARF are refreshed. +5. Worker resumes. + +The slot update still should be implemented as an atomic aligned pointer +store. That makes the representation compatible with future multi-threaded +sessions. + +If compilation or relocation fails, the slot is not updated and the old +generation remains active. + +## 17. API sketch + +Debugger-facing experimental surface: + +```c +typedef struct CfreeJitReloadOptions { + const char* symbol; + CfreeObjBuilder* obj; + uint32_t flags; +} CfreeJitReloadOptions; + +int cfree_jit_reload_function(CfreeJit*, const CfreeJitReloadOptions*); +``` + +Internal link session surface: + +```c +int link_session_mark_reloadable(LinkSession*, Sym name); +int link_session_reload_function(LinkSession*, LinkImage*, Sym name, + ObjBuilder* replacement); +``` + +Initial JIT link needs an option to create reloadable entries: + +```c +typedef enum CfreeJitIndirectionMode { + CFREE_JIT_INDIRECT_NONE, + CFREE_JIT_INDIRECT_EXPORTED_FUNCS, +} CfreeJitIndirectionMode; +``` + +This should not affect AOT executable links. + +## 18. Failure behavior + +Reload must be transactional: + +- ABI mismatch: reject, old body remains active. +- Replacement contains data/TLS/init arrays: reject, old body remains + active. +- Unresolved symbol: reject, old body remains active. +- Out of append capacity: reject, old body remains active. +- Relocation failure: reject, old body remains active. + +If new pages were committed before failure, they may remain reserved as +dead space, but no public symbol or slot may point at them. + +## 19. Test plan + +Targeted tests: + +- JIT unit: `cfree_jit_lookup("foo")` returns the same address before and + after reload. +- JIT unit: calling `foo` before reload returns old result; after reload + returns new result. +- JIT unit: a saved function pointer from before reload calls the new body. +- JIT unit: old body remains mapped and `addr_to_sym` can describe an old + PC. +- Negative: ABI mismatch rejects. +- Negative: replacement defining writable global data rejects. +- Negative: duplicate public non-target definition rejects. +- Debug smoke: stop in `foo`, reload `foo`, finish old frame, call `foo` + again and observe new behavior. +- Debug smoke: symbol breakpoint on `foo` moves to the active generation. + +Run these on one JIT target first. Cross-arch trampoline encoding gets its +own arch-specific tests. + +## 20. Implementation sequence + +1. Land append-only incremental link and debugger snippet append. +2. Add reloadable function entries and slots for selected exported + functions. +3. Make `cfree_jit_lookup` return stable entries for reloadable functions. +4. Add function ABI signature emission from the C frontend. +5. Implement replacement object validation. +6. Append replacement body and publish through slot update. +7. Refresh JIT view/DWARF and rebind symbol breakpoints. +8. Add optional direct-call patching only after the baseline is correct. + +The first usable milestone is: in `dbg`, reload a global function while the +worker is stopped; existing function pointers keep working; new calls hit +the new body; old frames can return safely. diff --git a/doc/INCREMENTAL_LINK.md b/doc/INCREMENTAL_LINK.md @@ -0,0 +1,416 @@ +# Append-only incremental link + +This document describes the first incremental-linking step for cfree: +append-only growth of a live JIT image. It is sequenced before +`doc/HOT_RELOAD.md`. + +The first concrete use case is `cfree dbg`: while stopped at the debugger +REPL, a user should be able to enter C code, JIT it into the existing +debuggee image, and call or inspect the new symbols as if they had been +present in the original link. + +`cfree emu` also wants append-only linking, but it is a separate +workstream. This document intentionally keeps the motivating path in +`dbg`. + +## 1. Goals + +- Grow one live `CfreeJit` image with additional object inputs. +- Keep all previously published runtime addresses stable. +- Let new code reference old symbols from the original debuggee. +- Let old debugger surfaces see new symbols: `cfree_jit_lookup`, + `cfree_jit_addr_to_sym`, symbol iteration, breakpoints, PC translation, + and the JIT debug view. +- Preserve relocation records as durable data so later work can index and + selectively reapply them. +- Keep the public surface small until the implementation has one real + consumer. The first API can be private to `src/link/`, `src/dbg/`, and + `driver/dbg.c`. + +## 2. Non-goals + +- Replacing or removing existing code. That is hot reload and is covered + by `doc/HOT_RELOAD.md`. +- Reclaiming appended code. Debug sessions are short-lived; v1 may leak + appended code until `cfree_jit_free`. +- Data-symbol migration. New code may define new data, but existing data + addresses do not move and are not replaced. +- Cross-thread debuggee mutation. `dbg` remains a single-worker session. +- A general dynamic loader ABI. This is an in-process cfree JIT facility, + not `dlopen`. +- Expression parsing as a linker feature. C expression evaluation is a + frontend/driver problem that can be implemented by wrapping an + expression in a generated function. + +## 3. User model in `dbg` + +The REPL grows a new command family around JIT extension. Exact spelling is +driver policy; the link-side contract should support these shapes: + +```text +(cfree) jit { +int twice(int x) { return x * 2; } +} +(cfree) p twice +(cfree) call twice(21) +``` + +and: + +```text +(cfree) jit { +extern int existing_func(int); +int probe(int x) { return existing_func(x) + 1; } +} +(cfree) b probe +(cfree) call probe(41) +``` + +The minimal v1 can require full C declarations and function definitions in +the snippet. A later REPL expression command can synthesize: + +```c +static <T> __cfree_dbg_expr_N(void) { return <user-expression>; } +``` + +The important linker property is that the synthesized object is just +another input appended to the same live JIT. + +## 4. Current shape + +Today the JIT path is single-shot: + +```text +objects / archives + -> Linker + -> link_resolve() + -> LinkImage + -> cfree_jit_from_image() + -> CfreeJit +``` + +`CfreeJit` owns the mapped pages and the resolved `LinkImage`. The debugger +owns a `CfreeJitSession` over that `CfreeJit`. + +The linker already has the right discipline for incremental work: + +- `LinkInputId` values are stable for the lifetime of a `Linker`. +- `ObjBuilder` inputs are not consumed by `link_resolve`. +- `LinkRelocApply` records survive as data. +- Resolution is structured as a function from linker inputs to an image. + +The missing piece is a live link session that survives after initial JIT +mapping and can append newly compiled objects. + +## 5. Proposed internal model + +Add an internal session owned by `CfreeJit`: + +```c +typedef struct LinkSession LinkSession; + +struct CfreeJit { + Compiler* c; + LinkSession* link; + LinkImage* image; + CfreeExecMemRegion master; + ... +}; +``` + +`LinkSession` owns state that must outlive one `link_resolve` call: + +- the `Linker`-style input list +- a watermark for inputs already placed into the live image +- the global symbol hash +- per-input `InputMap` entries +- append cursors for each segment class +- executable-memory capacity and committed ranges +- durable relocation records +- optional relocation indexes, introduced in later phases + +`LinkImage` remains the read-side view consumed by JIT inspection, +debugging, and DWARF. In v1 it can still hold the arrays directly, but they +must become growable or session-backed: + +- symbols: appendable +- sections: appendable +- segments: fixed count where possible +- relocations: appendable +- debug input list: appendable + +The simplest v1 should keep the same segment classes as normal JIT layout: + +- RX +- R +- RW +- TLS + +Instead of creating new segments for every append, the JIT reserves one +larger contiguous master region up front, then commits pages as appended +sections land. + +## 6. Address stability + +Append-only incremental link has one hard invariant: + +> Once a runtime address is observable, it never changes. + +Observable addresses include: + +- `cfree_jit_lookup` results +- breakpoint locations +- return addresses on the worker stack +- addresses shown by `info functions` +- addresses captured by host code that called into the JIT +- DWARF PC ranges already handed to debugger consumers + +Therefore `link_extend` never compacts, reorders, or lays out old +sections. It only appends new sections at segment append cursors. + +## 7. Reservation and commit + +The current JIT maps a contiguous reservation sized to the initial image. +Append-only linking needs slack. + +Add a JIT option internally: + +```c +typedef struct LinkJitReserveOptions { + uint64_t reserve_rx; + uint64_t reserve_r; + uint64_t reserve_rw; + uint64_t reserve_tls; +} LinkJitReserveOptions; +``` + +For `dbg`, default to a conservative fixed budget, for example 64 MiB RX +and smaller R/RW budgets. The exact number should be target/host tunable, +but the first implementation can choose a simple constant. + +The reservation model: + +1. Reserve one contiguous master VA range large enough for the initial + image plus append budgets. +2. Lay out initial segments at the front of each class range. +3. Copy and relocate initial bytes. +4. Protect committed pages. +5. Keep uncommitted slack inaccessible. +6. On append, commit the pages covering newly used ranges, write bytes, + apply relocations, flush icache, then protect RX pages. + +This keeps AArch64 branch and ADRP proximity behavior predictable because +old and new code live in one planned range. + +## 8. Append flow + +Debugger-side flow: + +```text +REPL snippet + -> compile as C input through the existing frontend + -> ObjBuilder + -> cfree_jit_append_obj(jit, obj) + -> update dbg's DWARF/JIT view bindings +``` + +Link-side flow: + +```text +link_session_add_obj(session, obj) + -> read new input summaries + -> resolve new definitions and undefs + -> place new sections at append cursors + -> assign vaddrs for new symbols + -> synthesize any needed GOT/stub/helper sections + -> emit relocation-apply records for new sections + -> write new section bytes into the live mapping + -> apply new relocations + -> publish new symbols +``` + +Only new sections and new relocation records are processed during append. +Old relocation records remain valid but are not revisited. + +## 9. Symbol resolution + +New inputs resolve against: + +1. Existing global definitions in the live image. +2. New definitions from this append batch. +3. The registered external resolver. +4. Archive members, if archive support is enabled for incremental sessions. + +v1 should probably skip archive reselection for REPL snippets. The initial +debuggee link can include archives as normal, but appending a snippet should +resolve against the already-linked result plus the external resolver. That +keeps the first cut smaller. + +Duplicate definitions: + +- A new strong definition of an existing strong global is an error. +- A new weak definition of an existing global is ignored for global + resolution but remains present as an object-local symbol. +- A new strong definition can satisfy prior unresolved weak references only + if those references belong to new code. Old code is not repatched in + append-only mode. + +The last point is deliberate. If old code needs to start calling a new +definition, that is replacement/patching territory and belongs in hot +reload. + +## 10. Relocations + +The append pass emits `LinkRelocApply` records for new sections only. +Each record must include enough information to reapply the relocation later: + +- write location as image vaddr +- write width +- relocation kind +- target `LinkSymId` +- addend +- owning input and section + +For the live JIT mapping, relocation application translates image vaddr to +the write alias, computes target runtime address or image address according +to the relocation kind, writes the bytes, and flushes icache for executable +patches. + +Old relocations are not re-run. New code can refer to old code. Old code +does not learn about new code unless it already had an indirect call through +some user-controlled data slot. + +## 11. Debug info and JIT view + +`cfree_jit_view` currently builds a borrowed object view lazily from debug +inputs. Append-only linking needs invalidation: + +- Every append increments `jit->generation`. +- The cached view records the generation it was built for. +- `cfree_jit_view` rebuilds when generations differ. +- `CfreeDebugInfo` attached to a `CfreeJitSession` must be refreshed after + append. + +For `dbg`, the REPL can handle this directly: + +1. Append object. +2. Drop the old `CfreeDebugInfo`. +3. Call `cfree_jit_view`. +4. Open a new `CfreeDebugInfo`. +5. Attach it to the existing session. + +The worker should be stopped while this happens. That keeps debugger state +single-threaded and avoids racing line-table replacement with a running +thread. + +## 12. Breakpoints + +Existing breakpoints remain valid because old addresses remain valid. + +New breakpoints can be set against appended symbols after the append +publishes the symbol table. Breakpoint specs by name should resolve through +the normal `cfree_jit_lookup` path. + +If a source-level breakpoint was pending by file/line and the file was not +covered before the append, `dbg` can either: + +- leave it unresolved until the user retries, or +- maintain pending source breakpoint specs and arm them after each append. + +The second behavior is nicer but not required for the first linker cut. + +## 13. API sketch + +Keep the first surface private or experimental: + +```c +int cfree_jit_append_obj(CfreeJit*, CfreeObjBuilder*); +uint64_t cfree_jit_generation(CfreeJit*); +``` + +Internally this maps to: + +```c +LinkSession* link_session_from_initial(Linker*, LinkImage*); +void link_session_set_reserve(LinkSession*, const LinkJitReserveOptions*); +void link_session_add_obj(LinkSession*, ObjBuilder*); +void link_session_extend(LinkSession*, LinkImage*); +``` + +Once the REPL path is proven, the public API can be made more general: + +```c +typedef struct CfreeJitAppendOptions { + CfreeObjBuilder* const* objs; + uint32_t nobjs; +} CfreeJitAppendOptions; + +int cfree_jit_append(CfreeJit*, const CfreeJitAppendOptions*); +``` + +## 14. Failure behavior + +Append should be transactional from the user's point of view: + +- If compile fails, the JIT is unchanged. +- If symbol resolution fails, the JIT is unchanged. +- If reservation capacity is exhausted, the JIT is unchanged. +- If relocation application fails, the new allocation is not published. + +Implementation detail: pages may have been committed before a late failure. +That memory can stay reserved and unused, but symbols must not become +visible and append cursors must roll back. + +Use a small append transaction: + +```c +typedef struct LinkAppendTxn { + uint32_t old_nsyms; + uint32_t old_nsections; + uint32_t old_nrelocs; + uint64_t old_rx_cursor; + uint64_t old_r_cursor; + uint64_t old_rw_cursor; + uint64_t old_tls_cursor; +} LinkAppendTxn; +``` + +No VLA. The transaction hangs off the link session or the stack as a fixed +struct. + +## 15. Test plan + +Targeted tests: + +- Link unit: initial object plus appended object where appended code calls + an initial function. +- Link unit: appended duplicate strong definition fails without changing + existing lookup results. +- Link unit: appended object with unresolved symbol fails transactionally. +- JIT unit: `cfree_jit_lookup` sees appended function and old function + addresses are unchanged. +- JIT unit: `cfree_jit_addr_to_sym` maps PCs in both initial and appended + code. +- Debug smoke: scripted `cfree dbg`, append `twice`, set breakpoint on it, + call it, observe stop. +- Debug smoke: append code with `-g`, refresh DWARF, `b file:line` inside + appended code. + +Prefer narrow tests by target/arch. AArch64 JIT on the host is enough for +the first debugger path; ELF/Mach-O file emission should not be in scope. + +## 16. Implementation sequence + +1. Convert `CfreeJit` to retain a link session or enough linker state to + append. +2. Reserve JIT slack and track append cursors. +3. Implement append placement for RX/R/RW sections without archives. +4. Apply new relocations into live mappings. +5. Grow JIT symbol lookup, addr-to-symbol, and iteration. +6. Invalidate/rebuild `cfree_jit_view` by generation. +7. Add `dbg` REPL command for full C snippets. +8. Add a small `call` command or equivalent helper for invoking appended + functions. + +The first usable milestone is: append a function in `dbg`, call it, and set +a breakpoint in it without changing any original address. diff --git a/doc/JIT.md b/doc/JIT.md @@ -10,6 +10,10 @@ Companion docs: - `doc/DESIGN.md` §5.5 — `LinkImage` / `CfreeJit` ownership and lifetime. - `doc/MACHO.md` §3 — Mach-O Path-J reloc-apply gaps (the longest list). - `doc/DBG.md` §12 — JIT debugger checklist (session, view, REPL). +- `doc/INCREMENTAL_LINK.md` — append-only incremental JIT linking, first + for `dbg` REPL snippets. +- `doc/HOT_RELOAD.md` — function-only hot reload built on append-only + incremental linking. - `doc/EMU.md` §6 — per-block JIT on a growing `LinkImage` (separate scheme). ## Driver — `cfree run`