Skip to content

refactor(delib-1b): wasi path-based ops use Linux direct syscalls#48

Merged
chaploud merged 1 commit into
mainfrom
develop/delib-phase1b
Apr 24, 2026
Merged

refactor(delib-1b): wasi path-based ops use Linux direct syscalls#48
chaploud merged 1 commit into
mainfrom
develop/delib-phase1b

Conversation

@chaploud
Copy link
Copy Markdown
Contributor

Summary

Second step of the W46 un-link-libc migration, building on #47.

  • Add pfdMkdirAt, pfdUnlinkAt, pfdRenameAt, pfdReadlinkAt, pfdDup to src/platform.zig. Each uses std.os.linux.* direct syscalls on Linux, std.c.* on Mac/BSD, returns -1 on Windows (never reached — callers already branch to std.Io.Dir first).
  • Update src/wasi.zig path-based ops: path_create_directory, path_remove_directory, path_unlink_file, path_rename, path_readlink, fd_renumber.
  • Inline switch (comptime builtin.os.tag) for fd_filestat_set_times — Linux uses utimensat(fd, null, &times, 0) which is equivalent to futimens(fd, &times).

Behavior unchanged: existing cErrnoToWasi callers still work because the Linux branch mirrors the syscall errno into std.c._errno().* before returning.

Still not ready to flip link_libc = false

Remaining std.c.* references that will block un-linking:

  • src/platform.zig:331,352std.c.getenv(...) (app-cache dir / env lookup)
  • src/trace.zig:71std.c.write(stderr_fd, ...) (should be platform.pfdWrite)
  • src/wasi.zig:525std.c._errno().* in cErrnoToWasi (need an alt errno source)
  • src/wasi.zig path_filestat_set_times already uses std.c.utimensat on non-Linux (OK for Mac)

Those are W46 Phase 1c-1e, intentionally out of scope here.

Test plan

  • zig build test (Mac aarch64) — all pass.
  • zig build -Dtarget=x86_64-linux-gnu — clean.
  • zig build -Dtarget=x86_64-windows-gnu — clean.
  • bash test/realworld/run_compat.sh (Mac ReleaseSafe) — PASS: 50 / FAIL: 0.
  • Full CI — Mac + Ubuntu + Windows green (46/46 real-world compat on Windows).

Tracks: W46 in .dev/checklist.md.

Second step of the W46 un-link-libc migration. Add `pfdMkdirAt`,
`pfdUnlinkAt`, `pfdRenameAt`, `pfdReadlinkAt`, `pfdDup` to
`src/platform.zig`, each using `std.os.linux.*` direct syscalls on
Linux and `std.c.*` on Mac/BSD. `.windows` arms return -1 so the
helpers compile on Windows; they are never reached at runtime
(every caller already branches to `std.Io.Dir.{createDir,
deleteFile, deleteDir, rename, readLink}` before this point).

Update `src/wasi.zig` callers in:

- path_create_directory (mkdirat)
- path_remove_directory (unlinkat + AT_REMOVEDIR)
- path_unlink_file (unlinkat + 0)
- path_rename (renameat)
- path_readlink (readlinkat)
- fd_renumber (dup)

And inline a `switch (comptime builtin.os.tag)` for
`fd_filestat_set_times`, calling `linux.utimensat(fd, null, &times, 0)`
(equivalent to `futimens(fd, &times)`) on Linux and `std.c.futimens`
elsewhere.

Behavior unchanged: existing `cErrnoToWasi` callers keep working
because the Linux branch mirrors the syscall errno into
`std.c._errno().*` before returning.

Verified:
- `zig build test` (Mac aarch64) — all pass.
- `zig build -Dtarget=x86_64-linux-gnu` — clean.
- `zig build -Dtarget=x86_64-windows-gnu` — clean.
- `bash test/realworld/run_compat.sh` (Mac) — PASS: 50 / FAIL: 0.
@chaploud chaploud merged commit fbaf926 into main Apr 24, 2026
5 checks passed
@chaploud chaploud deleted the develop/delib-phase1b branch April 30, 2026 15:15
chaploud added a commit that referenced this pull request May 9, 2026
Lands `qLoadSpilled` / `qDefSpilled` / `qStoreSpilled` in
`src/engine/codegen/arm64/gpr.zig`. Q-form (16-byte) analog of
the existing D-form (8-byte) `fp*Spilled` trio.

Key properties:
- Uses the same `abi.fp_spill_stage_vregs` (V29/V30) staging
  registers as the D-form fp* helpers — Q view of the same
  V registers, so no new ABI reservation needed.
- Encoders: `inst_neon.encLdrQImm` / `encStrQImm` with SP=31
  base register. SP-relative addressing works identically to
  the D-form path; the only difference is 16-byte stride
  (Q-form imm12 × 16) vs 8-byte (D-form imm12 × 8).
- Alignment check: rejects abs_off > 65520 (= 4095 × 16, the
  imm12 cap) and abs_off & 0xF != 0 (16-byte alignment).
- Graceful spill-overflow surface — diagnostic stderr line
  matching the existing fp* helpers.

Caller-responsibility note: `Allocation.slot(vreg, .fpr)` uses
an 8-byte spill-stride formula `(slot_id - max_reg_slots_gpr) *
8`. For 16-byte-aligned v128 spills the caller must lay slots
at even-pair indices — slots 14, 16, 18, 20 produce offsets
48, 64, 80, 96 (all 16-byte aligned). Odd slot_ids fail the
helper's alignment check. The 9.5-c-ii caller migration
(lifting op_simd's SPILL-EXEMPT degradation) handles even-pair
discipline at v128 spill-slot allocation.

Per ADR-0041 §"Negative" / "Conservative spill-frame size":
the conservative per-vreg-pays-its-stride packing means
mixed-shape spill frames may have idle bytes; tighter packing
defers to Phase 15 alongside the class-aware allocator
(ADR-0038).

6 unit tests:
- qLoadSpilled in-V-reg (no bytes emitted)
- qLoadSpilled spilled (slot 14 → off 48 → LDR Q29, [SP, #48])
- qLoadSpilled rejects 8-byte-aligned-only offset
- qDefSpilled in-V-reg returns the V-reg
- qDefSpilled spilled returns stage V29/V30
- qStoreSpilled in-V-reg no-op
- qStoreSpilled spilled (slot 14, base 16 → STR Q29, [SP, #64])

Mac gates: zone ✓, file_size ✓, spill ✓, lint ✓, test 1205/0/12
(was 1199; +6 q* helper tests).

Refs: ADR-0041 §"Decision" / 2 (FP-class register pool reuse
with shape-tag axis), ADR-0027 (callee-saved pool) + D-037
(V29/V30 fp_spill_stage_vregs source), `single_slot_dual_
meaning.md` (§14 enforcement preserved — slot id remains the
single semantic axis; shape lives on a separate axis).
chaploud added a commit that referenced this pull request May 26, 2026
…line

Phase 10.E IT-6 impl cycle 3b. Replaces IT-3's direct
"B/JMP-to-trap-stub" with the address-load + CALL sequence
targeting the per-arch trampoline shipped at cycle 3a (commit
14b32f7 + 0d099a4 fix-forward).

Per-arch shape:

- arm64 (24 bytes per throw site):
    MOVZ X16, #(addr[0..16])
    MOVK X16, #(addr[16..32]), LSL #16
    MOVK X16, #(addr[32..48]), LSL #32
    MOVK X16, #(addr[48..64]), LSL #48
    BLR  X16
    B    <trap_stub>         ; bounds_fixups fallback

- x86_64 (17 bytes per throw site):
    MOVABS R10, imm64
    CALL   R10
    JMP    <trap_stub>       ; unreach_fixups fallback

The trampoline address comes from
`@intFromPtr(&trampoline_mod.zwasmThrowTrampoline)` — known at
Zig compile time, stable for the process lifetime. X16 / R10 are
caller-saved scratch registers (AAPCS64 IP0 / SysV-Win64 R10);
the trampoline's body clobbers R10 explicitly in its clobber set.

`throw_ref` shares the emit path via
`throw.emitTrampolineCallAndTrap` (the exnref-pop divergence
lands at cycle 3c alongside the full dispatchThrow integration).

The trampoline (currently trap-only per cycle 3a) sets
`trap_flag=1` then RETs back to the throw site; control resumes
at the post-CALL B/JMP and lands at the function trap stub which
finishes the standard epilogue + RET to the entry shim. Net
observable behavior is identical to IT-3 (throw traps) BUT the
JIT byte stream now routes through the per-Instance trampoline
symbol — the integration surface for cycle 3c is in place.

Per ADR-0076 D3 cross-compile sanity (Mac aarch64 + Linux
x86_64 cross-build) verified locally before push.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant