fix: aarch64-linux port of the M:1 fiber runtime (sched.sx)
Port library/modules/std/sched.sx to run on aarch64-linux alongside aarch64-macOS, validated byte-identical on both via Apple `container`. Per-OS bits are comptime-branched: - MAP_AP (mmap MAP_ANON flag): linux 0x22 / macOS 0x1002. - fd-readiness backend: epoll on linux, kqueue on darwin (epoll import scoped to the linux branch). block_on_fd, the run-loop Mode-2 drain, and cancel_io_waiter_for each branch; the epoll paths EPOLL_CTL_DEL on fire and on early-wake (EPOLLONESHOT only disables a registration; kqueue EV_ONESHOT auto-removes it). - first-entry trampoline: a per-OS hand-written global-asm symbol becomes a naked sx fn fib_tramp (mov x0,x19; br x20) + register-indirect dispatch (spawn presets regs[1] == x20 == &fib_dispatch), dropping the per-OS .global symbol entirely. Fixes issue 0193 Bug A: the trampoline redesign bus-errored on the go/wait/sleep capstone (1817) until `export "fib_dispatch"` was restored. Without the export, fib_dispatch reverts to sx's internal ABI (x0 = implicit context, first arg self shifted to x1) while the trampoline hands self over in x0 (C-ABI); on first entry the body runs (x1 happens to alias self) but the closure then loads regs[1] == &fib_dispatch as its first capture and re-invokes fib_dispatch forever -> stack overflow -> bus error. The export pins fib_dispatch to the C-ABI (self in x0), matching the trampoline. Root cause found via lldb on an AOT build; confirmed against the compiler source. Bug B (a top-level asm block wrapped in inline-if is dropped during the comptime-conditional flatten) is carved out to issue 0194 (OPEN) -- no live trigger remains, since the naked-fn trampoline sidesteps it. 1811/1814/1816/1817 run byte-identical on the aarch64-macOS host and in an aarch64-linux container; full suite green (817/0). Documents the fiber runtime in readme.md.
This commit is contained in:
@@ -1,8 +1,35 @@
|
||||
# Issue 0193 — linux fiber-runtime port (sched.sx) + a wrapped top-level `asm` drop
|
||||
|
||||
Status: **OPEN.** Two intertwined items uncovered while porting `library/modules/std/sched.sx`
|
||||
(the M:1 fiber runtime) to aarch64-linux. The WIP sched.sx port is preserved in
|
||||
`git stash` (`stash@{0}`, "WIP on fix/0192-qualified-import-const-comptime") — pop it to resume.
|
||||
> **RESOLVED — port landed on aarch64-linux.**
|
||||
>
|
||||
> **Bug A (register-indirect trampoline bus-errors on 1817): FIXED.** Root cause found via lldb on an
|
||||
> AOT macOS build (the bug reproduced on macOS too, so no container needed): the WIP port had dropped
|
||||
> `export "fib_dispatch"` from `fib_dispatch`. Without the export the fn reverts to sx's INTERNAL
|
||||
> calling convention, which reserves x0 for the implicit `context` pointer and shifts the first real
|
||||
> arg `self` to x1 — but the trampoline (`mov x0, x19; br x20`) hands the fiber over in x0, C-ABI
|
||||
> style. On first entry x1 coincidentally aliases `&fiber.ctx == self` (left there by the scheduler's
|
||||
> prior `swap_context(from, to)`, x1 = to), so the body runs once; but inside it the closure loads
|
||||
> `[Fiber+8] == ctx.regs[1] == &fib_dispatch` as its "first capture" and re-invokes `fib_dispatch`
|
||||
> forever → stack overflow → bus error. **Fix:** restore `export "fib_dispatch"` so the fn keeps the
|
||||
> C-ABI (`self` in x0), matching what the trampoline supplies — a one-line library change, no compiler
|
||||
> change. The register-indirect naked-fn trampoline design is kept (it sidesteps Bug B's hand-written
|
||||
> per-OS global-asm symbol). Adversarially reviewed against the compiler source (`src/ir/lower/decl.zig`
|
||||
> `funcWantsImplicitCtx`/`wants_ctx`/`CallingConvention.c`); root cause + fix confirmed CORRECT.
|
||||
>
|
||||
> **Validation:** 1811 / 1814 / 1816 / 1817 (the go/wait/sleep capstone) all run **byte-identical** on
|
||||
> the aarch64-macOS host AND in an aarch64-linux Apple `container` (`sum: 123`, completion order
|
||||
> `2@10 3@20 1@30`, etc.). Full `zig build test` macOS suite GREEN (817/0).
|
||||
>
|
||||
> **Bug B (wrapped top-level `asm` dropped): carved out to `issues/0194-wrapped-toplevel-asm-dropped.md`
|
||||
> as an OPEN compiler bug.** It is no longer triggered anywhere in the tree (the port no longer uses a
|
||||
> wrapped global-asm block), so it does not block anything — but it is a real defect and stays filed.
|
||||
>
|
||||
> Original writeup below for history.
|
||||
|
||||
---
|
||||
|
||||
Status: **(historical — see RESOLVED banner above).** Two intertwined items uncovered while porting
|
||||
`library/modules/std/sched.sx` (the M:1 fiber runtime) to aarch64-linux.
|
||||
|
||||
The epoll *bindings* + `std.event.Loop` epoll backend are already committed (`cc137002`) and
|
||||
**runtime-validated on real Linux** via Apple `container` (see the event.sx VALIDATION note / the
|
||||
|
||||
99
issues/0194-wrapped-toplevel-asm-dropped.md
Normal file
99
issues/0194-wrapped-toplevel-asm-dropped.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# Issue 0194 — a top-level global `asm` block wrapped in `inline if` / `case` is DROPPED
|
||||
|
||||
Status: **OPEN.** Carved out of issue 0193 (the linux fiber-runtime port). The port itself is
|
||||
RESOLVED — it sidesteps this bug entirely by using a naked-sx-fn trampoline (`fib_tramp`) plus a
|
||||
register-indirect `br x20` instead of a hand-written global-asm symbol, so there is **no live trigger
|
||||
for this bug in the tree today.** It is filed standalone so the compiler defect is not lost.
|
||||
|
||||
## Symptom
|
||||
|
||||
A top-level global `asm { … }` block that defines a symbol (e.g. `.global _foo` / `_foo: …`) is
|
||||
**not emitted** when it is wrapped in a comptime `inline if OS == { case … }` (or
|
||||
`inline if OS == .linux { asm } else { asm }`). `nm main.o` shows the symbol as `U` (undefined) and
|
||||
the link fails on both platforms. A PLAIN, unwrapped top-level `asm { … }` emits fine.
|
||||
|
||||
- **Observed:** symbol undefined, link error.
|
||||
- **Expected:** the `asm` block in the taken comptime arm emits its template into the module's global
|
||||
asm exactly as an unwrapped block would (the comptime-conditional pre-pass already surfaces the
|
||||
taken arm's *other* top-level decls — fns, consts, imports — correctly; only the `asm_global` node
|
||||
is lost).
|
||||
|
||||
## Reproduction
|
||||
|
||||
**Not yet reproducible in isolation.** During the 0193 port, minimal/medium repros ALL emitted +
|
||||
linked correctly: a top-level `asm` in a single `case`; two `case` blocks; a `case` asm in an
|
||||
imported module; a naked fn + `case` asm with `bl` to an exported fn; a one-sided
|
||||
`inline if .linux { #import }` before the asm. **Only the full `library/modules/std/sched.sx`
|
||||
dropped it** — so the trigger is an interaction with something else in that module, not the wrapped
|
||||
`asm` alone.
|
||||
|
||||
The exact form that triggered it (now replaced on the branch, recoverable from history): the original
|
||||
global trampoline
|
||||
|
||||
```sx
|
||||
asm {
|
||||
#string T
|
||||
.global _fib_tramp
|
||||
_fib_tramp:
|
||||
mov x0, x19
|
||||
bl _fib_dispatch
|
||||
brk #0
|
||||
T,
|
||||
};
|
||||
fib_tramp :: () extern;
|
||||
```
|
||||
|
||||
wrapped as
|
||||
|
||||
```sx
|
||||
inline if OS == {
|
||||
case .linux: asm { #string T
|
||||
fib_tramp:
|
||||
mov x0, x19
|
||||
bl fib_dispatch
|
||||
br x30
|
||||
T, };
|
||||
case .macos: asm { #string T
|
||||
.global _fib_tramp
|
||||
_fib_tramp:
|
||||
mov x0, x19
|
||||
bl _fib_dispatch
|
||||
brk #0
|
||||
T, };
|
||||
}
|
||||
```
|
||||
|
||||
dropped the asm in BOTH arms (whichever was taken). See `issues/0193-linux-fiber-port.patch` for the
|
||||
full module context that triggers it, and the 0193 writeup for the larger investigation history.
|
||||
|
||||
## Investigation prompt (ready to paste)
|
||||
|
||||
> A top-level global `asm` block defining a symbol is dropped when wrapped in a comptime
|
||||
> `inline if OS == { case … }` — but only inside the full `library/modules/std/sched.sx`; it can't be
|
||||
> reproduced in isolation. Find where the surfaced `asm_global` node is lost between the
|
||||
> comptime-conditional flatten and IR lowering.
|
||||
>
|
||||
> Key files:
|
||||
> - `src/imports.zig` — `flattenComptimeConditionals` (line ~38) + `appendBranchDecls` (line ~72): the
|
||||
> pre-pass that surfaces a taken comptime arm's top-level decls. It *appears* correct — it appends
|
||||
> every node of the taken branch's block, `asm_global` included — so confirm the flattened slice
|
||||
> actually carries the `asm_global` node (dump `flat_decls` at `src/imports.zig:932`).
|
||||
> - `src/ir/lower/decl.zig` — `lowerMainAndComptime` (line ~1494), whose `.asm_global` arm (line ~1503)
|
||||
> appends the verbatim template to `self.module.global_asm`. **Prime suspect:** does the lowering
|
||||
> entry point feed `lowerMainAndComptime` the *flattened* decl list, or a pre-flatten `root.decls`
|
||||
> that never contains the surfaced (formerly-nested) `asm_global`? If the asm-emission pass walks a
|
||||
> different decl list than the one flattening wrote to, a surfaced `asm_global` is silently skipped.
|
||||
> - `src/ir/emit_llvm.zig:384` — where `module.global_asm` is concatenated into the LLVM module. If the
|
||||
> node never reached `global_asm`, it never emits.
|
||||
>
|
||||
> Steps: (1) build sched.sx's wrapped-asm variant (recover from `issues/0193-linux-fiber-port.patch`
|
||||
> or git history of branch `fix/0192-qualified-import-const-comptime`), (2) instrument
|
||||
> `flattenComptimeConditionals` to log whether the `asm_global` node survives into `flat_decls`,
|
||||
> (3) instrument `lowerMainAndComptime` to log whether it ever *sees* an `asm_global`, (4) bisect what
|
||||
> else in sched.sx must be present for the drop to occur (the isolation repros lacked it).
|
||||
> Verification: `nm` the object shows the wrapped-asm symbol DEFINED (not `U`); the wrapped form links
|
||||
> and runs identically to a plain unwrapped `asm`.
|
||||
>
|
||||
> **Verify it isn't a syntax issue first:** it reproduces with both the `case` and `if/else` forms,
|
||||
> and plain unwrapped asm emits fine — so the wrapping, not the asm itself, is the trigger. That points
|
||||
> to the flatten/lowering interaction, not user error.
|
||||
Reference in New Issue
Block a user