fibers B1.3b: mmap guard-page fiber stacks (x86_64 switch sibling deferred)
Fiber stacks are now mmap'd with a PROT_NONE guard page at the low end: mmap a [guard | usable] region and mprotect the low 16KB page PROT_NONE, so a stack overflow faults at the guard boundary instead of silently corrupting a neighbor (design 8.1.1 — fixed stacks without a guard corrupt silently on overflow). Locked by examples/1809-concurrency-fiber-guard-stack.sx (aarch64-macos-pinned): guard armed: 1 (mprotect -> 0) + sum: 20100 (a fiber runs real recursion on the guarded stack and yields). The guard FIRING is validated manually (a fiber recursing past its 128KB stack faults with Bus error at region+GUARD, exit 134 via the sx crash handler) — not corpus-pinned, since a deliberate-overflow crash is host-fragile and a 'child faulted' fork test would not prove the boundary catch specifically. The x86_64 swap_context sibling is DEFERRED: sx build --target x86_64-macos mislinks on this arm64 host (object x86_64, link step arm64) and x86_64-linux can't run here, so it could only ship IR-only / unrun. For the highest- corruption-risk asm, shipping un-run / un-negative-controlled code violates the design 10.7 'correctness not existence' rule. SysV target notes (rbx/rbp/r12-r15 /rsp, no callee-saved XMM, rsp-carried return address) recorded for a future x86_64 host. Suite green 735/0.
This commit is contained in:
@@ -4,7 +4,25 @@ Companion to [PLAN-FIBERS.md](PLAN-FIBERS.md). Update after every step (one step
|
||||
per the cadence rule). New corpus category: `18xx` concurrency.
|
||||
|
||||
## Last completed step
|
||||
**B1.3a-2 — the context-switch STRESS GATE (design §10.7) — DONE + adversarially reviewed.**
|
||||
**B1.3b (mmap guard-page stacks) — DONE. x86_64 switch sibling DEFERRED (not runnable on this
|
||||
host).** Fiber stacks are now `mmap`'d with a `PROT_NONE` GUARD PAGE at the low end (§8.1.1: a
|
||||
fixed stack without a guard silently corrupts neighbors on overflow). `mmap` the `[guard |
|
||||
usable]` region, `mprotect` the low 16KB page `PROT_NONE`; SP descends into the guard and faults
|
||||
loudly at the boundary instead of corrupting a neighbor. Locked by
|
||||
`examples/1809-concurrency-fiber-guard-stack.sx` (aarch64-macos-pinned): `guard armed: 1`
|
||||
(`mprotect`→0) + `sum: 20100` (a fiber runs real recursion on the guarded stack + yields).
|
||||
- **Guard FIRING validated** (manually, not corpus-pinned — a deliberate overflow crash is
|
||||
host-fragile): a fiber recursing past its 128KB stack faults with `Bus error` at the guard page
|
||||
(`region+GUARD`); the sx crash handler turns it into exit 134. Documented in the example header.
|
||||
- **x86_64 `swap_context` sibling DEFERRED:** `sx build --target x86_64-macos` mislinks on this
|
||||
arm64 host (object is x86_64 but the link step targets arm64), and `--target x86_64-linux` can't
|
||||
run here either — so the x86_64 switch could only ship IR-only, UNRUN. For the single
|
||||
highest-corruption-risk asm, shipping un-run/un-negative-controlled asm violates the §10.7
|
||||
"correctness not existence" rule. Deferred until an x86_64 host (or working cross-run) is
|
||||
available. The aarch64 switch + its §10.7 gate are complete and reviewed; portability is the
|
||||
only gap. SysV target notes recorded in Next step.
|
||||
|
||||
### Earlier — B1.3a-2 — the context-switch STRESS GATE (design §10.7) — DONE + adversarially reviewed
|
||||
The explicit every-callee-saved-register scribble that B1.3a-1 owed. `swap_context` now saves the
|
||||
COMPLETE AAPCS64 callee-saved set — integer x19-x28 + fp/lr + sp AND FP **d8-d15** (per §6.1.2
|
||||
only the low 64 bits of v8-v15 are callee-saved, so `d8-d15` is exactly sufficient; x18 is Apple's
|
||||
@@ -221,33 +239,29 @@ fibers/Io/scheduler code yet. Grounded floor facts:
|
||||
boundary; a sharper sx diagnostic for it is a candidate polish, not a blocker.
|
||||
|
||||
## Next step
|
||||
**B1.3b — the x86_64 switch sibling + `mmap` guard-page stacks.** The aarch64 switch + the §10.7
|
||||
gate (B1.3a) are done + reviewed. Sequence:
|
||||
1. **B1.3b-1 (x86_64 `swap_context`):** the per-arch sibling — System V callee-saved is
|
||||
rbx/rbp/r12/r13/r14/r15 + rsp (6 GP + sp; NO callee-saved XMM on the SysV C ABI, unlike Win64),
|
||||
so a different slot count + a different `scribble_verify` reg set. Arch-gate it like the asm
|
||||
corpus (`x86_64-linux` run on a matching CI host, ir-only on this aarch64 mac — `.ir` required).
|
||||
Carry the same 2-fiber mutual-scribble gate + the negative-control discipline. (1802 is the
|
||||
x86_64 naked-asm template.)
|
||||
2. **B1.3b-2 (`mmap` guard-page stacks):** replace the `alloc_bytes` stack with `mmap` +
|
||||
`mprotect` the low page `PROT_NONE` (mandatory — a fixed stack without a guard silently
|
||||
corrupts neighbors on overflow, §8.1.1). Add an overflow-hits-guard test (deep recursion past
|
||||
the stack → SIGSEGV/SIGBUS at the guard, NOT silent corruption). `mmap`/`mprotect` via
|
||||
`extern "c"`.
|
||||
3. Then B1.3 (fiber runtime substrate) is done → **B1.4** (`Io` impls: blocking ✅ →
|
||||
deterministic-sim KEYSTONE → event-loop) and **B1.5** (M:1 scheduler) build the real scheduler
|
||||
on top, replacing the hand-bootstrapped ping-pong with `spawn`/`yield`/`resume`. The §10.7 gate
|
||||
(1808) must keep passing as the switch grows.
|
||||
Two open threads — pick by host availability:
|
||||
|
||||
**(A) x86_64 `swap_context` sibling — needs an x86_64 host (or a working cross-run).** The per-arch
|
||||
switch. SysV-AMD64 callee-saved = rbx, rbp, r12, r13, r14, r15 + rsp (6 GP + sp; **no** callee-saved
|
||||
XMM on SysV, unlike Win64) — so a 7-slot ctx and a different `scribble_verify` reg set. No link
|
||||
register: the return address rides each fiber's stack, so the switch is `mov [from],regs… ;
|
||||
mov rsp,[to+48] ; ret` (the final `ret` pops `to`'s saved return addr). Bootstrap: push
|
||||
`&_fib_tramp` onto the new stack and set saved rsp to it (16-align: at the trampoline's `call`,
|
||||
rsp must be ≡0 mod 16). Args rdi/rsi/rdx; result rax. Carry the SAME 2-fiber mutual-scribble gate
|
||||
+ negative-control discipline + adversarial review. **Must be RUN + negative-controlled on a
|
||||
matching host** — do NOT ship it ir-only/unrun (§10.7). (1802 is the x86_64 naked-asm template.)
|
||||
|
||||
**(B) B1.4 — `Io` impls (blocking ✅ → deterministic-sim KEYSTONE → event-loop).** The aarch64
|
||||
substrate (switch + §10.7 gate + guarded stacks) is enough to build the scheduler on. B1.4 builds
|
||||
the deterministic-sim `Io` (calibrated against blocking `Io` before trusting it — §8.1.3), then
|
||||
**B1.5** (M:1 scheduler) replaces the hand-bootstrapped ping-pong with real `spawn`/`yield`/
|
||||
`resume` over the switch. The §10.7 gate (1808) + the guarded-stack path (1809) must keep passing
|
||||
as the switch is wrapped into the scheduler.
|
||||
|
||||
**Deferred (do NOT block on these):** issue **0150** (`void` struct field SIGTRAP) — only
|
||||
`Future(void)`/`timeout`, which are B1.4. The **`::` callable-parameter feature** (named-fn
|
||||
async workers `async(read_a, conn)`) — WIP at `.sx-tmp/wip-callable-params/patch.diff` (parser
|
||||
done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idiom meanwhile.
|
||||
|
||||
**Deferred (do NOT block on these):** issue **0150** (`void` struct field SIGTRAP) — only
|
||||
`Future(void)`/`timeout`, which are B1.4. The **`::` callable-parameter feature** (named-fn
|
||||
async workers `async(read_a, conn)`) — WIP at `.sx-tmp/wip-callable-params/patch.diff` (parser
|
||||
done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idiom meanwhile.
|
||||
`Future(void)`/`timeout` (B1.4). The **`::` callable-parameter feature** (named-fn async workers
|
||||
`async(read_a, conn)`) — WIP at `.sx-tmp/wip-callable-params/patch.diff` (parser done, inference
|
||||
incomplete); a dedicated effort; lambda workers are the idiom meanwhile.
|
||||
|
||||
`Context` layout settled: `{ allocator; data; io; }` (allocator index 0 fixed by
|
||||
`call.zig:1229`, io last). Io protocol + materializers + push-inherit are LANDED + reviewed.
|
||||
@@ -425,3 +439,16 @@ done, inference incomplete); a dedicated effort; lambda workers are the B1.2 idi
|
||||
(spec-correct for a call-boundary swap; in the example header): FPCR/FPSR/NZCV + TPIDR/TLS not
|
||||
swapped, fp=0 blocks unwind — relevant at N×M:1 / signals, not here. Suite green 734/0.
|
||||
Next: B1.3b (x86_64 sibling + mmap guard-page stacks).
|
||||
- **B1.3b — mmap guard-page stacks (x86_64 sibling deferred).** Fiber stacks now `mmap` a
|
||||
`[guard | usable]` region and `mprotect` the low 16KB page `PROT_NONE`, so a stack overflow
|
||||
faults at the guard boundary instead of silently corrupting a neighbor (§8.1.1). Locked by
|
||||
`examples/1809-concurrency-fiber-guard-stack.sx` (aarch64-macos-pinned): `guard armed: 1`
|
||||
(`mprotect`→0) + `sum: 20100` (a fiber runs real recursion on the guarded stack + yields).
|
||||
Guard FIRING validated manually (overflow → `Bus error` at `region+GUARD`, exit 134 via the sx
|
||||
crash handler) — not corpus-pinned because a deliberate-overflow crash is host-fragile (and a
|
||||
mere "child faulted" fork test wouldn't prove the BOUNDARY catch). The x86_64 `swap_context`
|
||||
sibling was DEFERRED: `--target x86_64-macos` mislinks on this arm64 host and `x86_64-linux`
|
||||
can't run here, so it could only ship un-run/un-negative-controlled — which §10.7 forbids for the
|
||||
highest-risk asm. SysV target notes (rbx/rbp/r12-r15/rsp, no callee-saved XMM, rsp-carried return
|
||||
addr) recorded in Next step. Suite green **735/0**. Next: x86_64 sibling (needs an x86_64 host)
|
||||
OR B1.4 (`Io` impls / scheduler) on the proven aarch64 substrate.
|
||||
|
||||
104
examples/1809-concurrency-fiber-guard-stack.sx
Normal file
104
examples/1809-concurrency-fiber-guard-stack.sx
Normal file
@@ -0,0 +1,104 @@
|
||||
// Stream B1 (fibers) B1.3b — fiber stacks are `mmap`'d with a PROT_NONE GUARD
|
||||
// PAGE at their low end (§8.1.1: a fixed stack without a guard silently
|
||||
// corrupts neighbors on overflow — the guard turns overflow into an immediate,
|
||||
// loud fault instead). The stack grows DOWN, so the guard sits at the lowest
|
||||
// address; when a fiber's SP descends past the usable region it hits the
|
||||
// unwritable guard and faults at the boundary.
|
||||
//
|
||||
// This is the positive integration: a fiber bootstrapped on a guarded `mmap`
|
||||
// stack runs real recursion + yields correctly, and the guard syscall is
|
||||
// asserted to have succeeded (`mprotect` → 0). The guard FIRING on overflow is
|
||||
// validated separately (a fiber recursing past its 128KB stack faults with
|
||||
// "Bus error" at the guard page, address = region+GUARD) — a deterministic
|
||||
// corpus assertion for it is omitted because a deliberate stack-overflow crash
|
||||
// is host/runtime-fragile (the sx crash handler turns the fault into SIGABRT,
|
||||
// and the fault address varies run-to-run).
|
||||
//
|
||||
// aarch64-macos-pinned: the `mmap` flag constants (MAP_ANON = 0x1000) and the
|
||||
// 16 KB page size are Apple-specific; the asm switch is per-arch. Runs
|
||||
// end-to-end here, ir-only on a mismatch.
|
||||
#import "modules/std.sx";
|
||||
|
||||
mmap :: (addr: *void, len: i64, prot: i32, flags: i32, fd: i32, off: i64) -> *void extern libc "mmap";
|
||||
mprotect :: (addr: *void, len: i64, prot: i32) -> i32 extern libc "mprotect";
|
||||
|
||||
PROT_NONE :: 0;
|
||||
PROT_RW :: 3; // PROT_READ | PROT_WRITE
|
||||
MAP_AP :: 0x1002; // macOS MAP_PRIVATE (0x2) | MAP_ANON (0x1000)
|
||||
GUARD :: 16384; // one 16 KB page (aarch64-macOS)
|
||||
STACK :: 131072; // 128 KB usable
|
||||
|
||||
FiberCtx :: struct { regs: [13]u64; }
|
||||
Fiber :: struct { ctx: FiberCtx; finish: *FiberCtx; out: i64; guard_ok: i64; }
|
||||
|
||||
swap_context :: (from: *FiberCtx, to: *FiberCtx) abi(.naked) {
|
||||
asm volatile {
|
||||
#string ASM
|
||||
stp x19, x20, [x0, #0]
|
||||
stp x21, x22, [x0, #16]
|
||||
stp x23, x24, [x0, #32]
|
||||
stp x25, x26, [x0, #48]
|
||||
stp x27, x28, [x0, #64]
|
||||
stp x29, x30, [x0, #80]
|
||||
mov x9, sp
|
||||
str x9, [x0, #96]
|
||||
ldp x19, x20, [x1, #0]
|
||||
ldp x21, x22, [x1, #16]
|
||||
ldp x23, x24, [x1, #32]
|
||||
ldp x25, x26, [x1, #48]
|
||||
ldp x27, x28, [x1, #64]
|
||||
ldp x29, x30, [x1, #80]
|
||||
ldr x9, [x1, #96]
|
||||
mov sp, x9
|
||||
ret
|
||||
ASM
|
||||
};
|
||||
}
|
||||
|
||||
asm {
|
||||
#string T
|
||||
.global _fib_tramp
|
||||
_fib_tramp:
|
||||
mov x0, x19
|
||||
bl _fib_body
|
||||
brk #0
|
||||
T,
|
||||
};
|
||||
fib_tramp :: () extern;
|
||||
|
||||
// Real recursion → genuine stack usage on the guarded stack (well within 128KB).
|
||||
sum_to :: (n: i64) -> i64 {
|
||||
if n == 0 { return 0; }
|
||||
return n + sum_to(n - 1);
|
||||
}
|
||||
|
||||
fib_body :: (self: *Fiber) export "fib_body" {
|
||||
self.out = sum_to(200); // 200*201/2 = 20100
|
||||
swap_context(@self.ctx, self.finish);
|
||||
}
|
||||
|
||||
// mmap a [guard | usable-stack] region and mprotect the low guard page
|
||||
// PROT_NONE. Returns the 16-aligned stack top; reports guard-syscall success.
|
||||
guarded_stack :: (f: *Fiber, size: i64) -> u64 {
|
||||
region : *void = mmap(null, GUARD + size, PROT_RW, MAP_AP, -1, 0);
|
||||
if (xx region) == (xx (0 - 1)) { f.guard_ok = 0; return 0; }
|
||||
f.guard_ok = 0;
|
||||
if mprotect(region, GUARD, PROT_NONE) == 0 { f.guard_ok = 1; }
|
||||
usable : u64 = (xx region) + GUARD;
|
||||
top : u64 = usable + size;
|
||||
return top - (top % 16);
|
||||
}
|
||||
|
||||
main :: () -> i64 {
|
||||
main_ctx : FiberCtx = ---;
|
||||
f : Fiber = ---; f.finish = @main_ctx; f.out = -1; f.guard_ok = 0;
|
||||
top := guarded_stack(@f, STACK);
|
||||
f.ctx.regs[0] = xx @f;
|
||||
f.ctx.regs[10] = 0;
|
||||
f.ctx.regs[11] = xx fib_tramp;
|
||||
f.ctx.regs[12] = top;
|
||||
swap_context(@main_ctx, @f.ctx);
|
||||
print("guard armed: {}\n", f.guard_ok); // 1 — mprotect(PROT_NONE) succeeded
|
||||
print("sum: {}\n", f.out); // 20100 — fiber ran on the guarded stack
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "target": "macos" }
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
16632
examples/expected/1809-concurrency-fiber-guard-stack.ir
Normal file
16632
examples/expected/1809-concurrency-fiber-guard-stack.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
guard armed: 1
|
||||
sum: 20100
|
||||
Reference in New Issue
Block a user