Files
sx/examples/concurrency/1809-concurrency-fiber-guard-stack.sx
agra 66bdc70bf1 test: group examples into per-category folders
Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
2026-06-21 14:41:34 +03:00

105 lines
3.8 KiB
Plaintext

// 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;
}