comptime VM: extern slice/string args (-> NUL-term char*) + float guards (Phase 4D.2)

Extract marshalExternArg: a scalar/pointer word passes verbatim (a cstring arg
already works as a pointer word via 4D.1); a string/slice {ptr,len} fat pointer is
copied into a NUL-terminated arena buffer and its char* passed -- mirrors the legacy
marshalExternArg, and is what the bundler's popen(cmd: [:0]u8, ...) needs.

Add float guards on args AND returns: floats are kindOf == .word but the host_ffi
trampolines have no float variant, so bail loudly rather than miscall through an
integer register (the legacy interp doesn't support float FFI either -> parity).

New example 0637-comptime-extern-slice-arg (#run strlen("hello, world") with a
[:0]u8 param -> 12) runs HANDLED on the VM, byte-matching legacy. 699/0 both gates.
The FFI escape now covers scalar/pointer/cstring/slice args + scalar/pointer returns.
This commit is contained in:
agra
2026-06-18 18:09:46 +03:00
parent e7a8708287
commit 6a7f6902b8
6 changed files with 60 additions and 4 deletions

View File

@@ -352,6 +352,18 @@ when reached (sentinels or accessor fns; see the design doc Risks).
`List` growth; orthogonal, see `current/CHECKPOINT-METATYPE.md`.)
## Log
- **Phase 4D.2 (VM plan) — extern SLICE/string args (→ NUL-terminated `char*`) + float guards (2026-06-18).**
Extracted `marshalExternArg`: a scalar/pointer WORD passes verbatim (a `cstring` arg already works
as a pointer word via 4D.1); a `string`/slice `{ptr,len}` fat pointer is copied into a
NUL-terminated arena buffer and its `char*` passed (mirrors legacy `marshalExternArg` — what the
bundler's `popen(cmd: [:0]u8, …)` needs). Added FLOAT guards on args AND returns: floats are
`kindOf == .word` but the host_ffi trampolines have no float variant, so they bail loudly rather
than miscall through an integer register (the legacy interp doesn't support float FFI either, so
parity holds — no corpus float-FFI example exists). New example `0637-comptime-extern-slice-arg`
(`#run strlen("hello, world")` with a `[:0]u8` param → 12) runs **HANDLED on the VM**, byte-matching
legacy. **699/0 BOTH gates.** On `reify`. The FFI escape is now complete for scalar/pointer/cstring/
slice args + scalar/pointer returns — enough for the bundler's libc surface. **Next (4D.3):**
`compiler_call` (#compiler hooks — 0602/0603), the last legacy-only role besides #insert/bundler.
- **Phase 4D.1 (VM plan) — general host-FFI escape: the VM calls any extern libc fn via dlsym + host_ffi (2026-06-18).**
Replaced the "extern not ported → bail" stub in `Vm.invoke` with `callHostExtern`: resolve the
symbol via `host_ffi.lookupSymbol` (dlsym RTLD_DEFAULT) and dispatch through the `host_ffi`