Skip to content

Commit d708300

Browse files
olwangclaude
andcommitted
feat(vm-jit): native-call ABI slice 1 — depth-carrying call ABI
First slice of native recursion (the call-ABI keystone). Threads a native call-chain depth through the compiled-function ABI so a later slice can guard against host-stack overflow before enabling recursive native->native calls. - CompiledAbi gains a trailing `depth: usize` parameter. - compile_inner appends it to the function signature; build_function binds it at the entry block. - Each `CallNative` forwards `caller_depth + 1` to its callee; the top-level `call()` starts the chain at depth 0. Carried only — no entry guard yet, so behavior is unchanged. The public `NativeModule::call` signature is unchanged, so reg_vm callers are unaffected. Validated: vm-jit 81/0 (incl. native-call-chain + child-deopt), differential 33/0 (the corpus heavily exercises CallNative + force-deopt), rsscript lib native-call/deopt 15/0, jit_acceptance 83/0, default workspace build clean. Next: slice 2 — CallSelf lowering + entry depth guard (declare-before-define). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 39ed894 commit d708300

2 files changed

Lines changed: 36 additions & 4 deletions

File tree

crates/vm-jit/src/lib.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,6 +1237,7 @@ type CompiledAbi = unsafe extern "C" fn(
12371237
*const u8,
12381238
*mut i64,
12391239
*mut i64,
1240+
usize,
12401241
) -> u8;
12411242

12421243
#[derive(Debug)]
@@ -1833,6 +1834,11 @@ impl NativeModule {
18331834
self.ctx.func.signature.params.push(AbiParam::new(ptr_ty)); // bail flag ptr
18341835
self.ctx.func.signature.params.push(AbiParam::new(ptr_ty)); // safepoint id out ptr
18351836
self.ctx.func.signature.params.push(AbiParam::new(ptr_ty)); // deopt payload out ptr
1837+
// Native call depth (native-call-ABI slice 1): the dynamic depth of the
1838+
// native->native call chain. Threaded so a recursive native frame can bail to
1839+
// the interpreter before overflowing the host C stack (the guard lands in a
1840+
// later slice; for now it is carried only). `usize`-width == pointer-width.
1841+
self.ctx.func.signature.params.push(AbiParam::new(ptr_ty)); // native call depth
18361842
self.ctx
18371843
.func
18381844
.signature
@@ -2169,6 +2175,9 @@ impl NativeModule {
21692175
bail_ptr,
21702176
safepoint_ptr,
21712177
payload_ptr,
2178+
// Top-level native entry: the native call chain starts at
2179+
// depth 0 (native-call-ABI slice 1).
2180+
0,
21722181
)
21732182
};
21742183
if completed != 0 && bail.get() == 0 {
@@ -3435,6 +3444,10 @@ fn build_function(
34353444
let bail_ptr = params[5];
34363445
let safepoint_ptr = params[6];
34373446
let payload_ptr = params[7];
3447+
// Native call depth (native-call-ABI slice 1): the chain depth passed by the
3448+
// caller. Forwarded as `depth + 1` to native callees so a future entry guard can
3449+
// bail before host-stack overflow; not yet checked.
3450+
let native_call_depth = params[8];
34383451
// Running per-site bail-id counter. Starts at 1 (0 is reserved = no bail);
34393452
// `bail_if` post-increments it so every guard/bail site gets a stable id.
34403453
let mut next_id: i64 = 1;
@@ -3931,6 +3944,10 @@ fn build_function(
39313944
let safepoint_ptr_v = bcx.ins().stack_addr(ptr_ty, safepoint_slot, 0);
39323945
let payload_ptr_v = bcx.ins().stack_addr(ptr_ty, payload_slot, 0);
39333946
let nargs_v = bcx.ins().iconst(ptr_ty, meta.n_params as i64);
3947+
// Forward the chain depth as `caller_depth + 1` (native-call-ABI
3948+
// slice 1): a native callee's depth is one deeper than its caller's.
3949+
let one_depth = bcx.ins().iconst(ptr_ty, 1);
3950+
let child_depth = bcx.ins().iadd(native_call_depth, one_depth);
39343951
let call = bcx.ins().call(
39353952
native_ref(*callee),
39363953
&[
@@ -3942,6 +3959,7 @@ fn build_function(
39423959
bail_ptr_v,
39433960
safepoint_ptr_v,
39443961
payload_ptr_v,
3962+
child_depth,
39453963
],
39463964
);
39473965
let completed = bcx.inst_results(call)[0];

docs/planning/vm-optimizing-jit-plan.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,24 @@ machinery **engage real program shapes and prove it**, not new subsystems. In pr
214214
investigated), `json` (1.01, parse), `string-map` (0.98, Map-key hashing). These need
215215
`List.len(split(...))` count-law / `starts_with` byte-compare folds, or the general allocation
216216
path (a) for programs that actually *use* the constructed strings; each is its own slice.
217-
2. **Native-call ABI — the large architectural keystone (later).** Unlocks `recursion-tree`/
218-
`fib` (~192 ms), bounded recursion generally, and non-inlinable cross-function native calls.
219-
Much larger, and it will interact with the full inlined frame-chain deopt / state maps
220-
(Pending #3, J0.1) and a host-stack depth guard — scope it deliberately, not opportunistically.
217+
2. **Native-call ABI — the large architectural keystone (IN PROGRESS, sliced).** Unlocks
218+
`recursion-tree`/`fib` (~192 ms today via the tier-0 scalar executor, not Cranelift),
219+
bounded recursion generally, and non-inlinable cross-function native calls. Non-recursive
220+
native call chains already ship (`CallNative` + child-frame deopt); the remaining work is
221+
**native recursion**, blocked by (i) self-reference (a `CallNative` resolves its callee via
222+
`self.funcs`, but a self-call's id isn't minted until compile returns → needs a self-call IR
223+
form + declare-before-define), and (ii) a **host-stack depth guard** (native→native calls the
224+
callee on the host C stack at lib.rs CallNative codegen, so unbounded recursion overflows it
225+
→ a crash, not a clean bail; safety-critical). Sliced:
226+
- **Slice 1 — depth-carrying ABI: DONE.** `CompiledAbi` gained a trailing `depth: usize`
227+
param; the top-level `call()` passes 0, each `CallNative` passes `caller_depth + 1`. Carried
228+
only (no entry guard yet); behavior unchanged, validated by vm-jit 81/0 + differential 33/0.
229+
- **Slice 2 — self-call lowering + entry depth guard** (next): a `CallSelf` IR form,
230+
declare-before-define in `compile_inner`, and an entry check that bails (deopts to the
231+
interpreter) when `depth >= cap` so the host stack can't overflow.
232+
- **Slice 3 — enable + verify** (`fact`/`fib` go native, byte-identical to interp, deep
233+
recursion bails cleanly at the cap), then **slice 4 — mutual recursion** (declare a cycle
234+
before defining). Interacts with the frame-chain deopt / state maps (Pending #3, J0.1).
221235
*Net (FRONTIER REACHED, 2026-06-23): the cleanly-tractable incremental wins are DONE* — folds
222236
(string/Bytes length, ~13×/~20×), scalar replacement (Option/variant/struct/nested/loop-carried),
223237
closure-sink (~53×), TCO (~70×), float (~42×), deopt-before-heap (~10×), plus the registry +

0 commit comments

Comments
 (0)