nx_surface_compatibility_expanded_r2_t207.nx source
↩ module page · 5411 lines · 262144 B
1// One shader-capture owner for normal and explicitly selected staged builds.
2// Digest checks bracket each execution; they detect changed files, not hostile swap-and-restore races.
3// Native packaging by artifact reference; no publication or engine-acceptance claim.
4// nx_fsops_lib.nx -- CONSOLIDATED filesystem tool (MCP name: nx_fs, tool #4 of the 15), LIBRARY half.
5// (Source is named nx_fsops because nx_fs.nx is the safety-enveloped file-I/O STDLIB -- a different thing.)
6// READ-ONLY first increment: `read` (bounded file read) + `ls` (typed dir listing). Retires ssh-cat for
7// remote reads per rule 27 (api-first, no shell plumbing).
8//
9// BOUNDARY DEFENSE (rule 12 -- MCP callers are EXTERNAL input): `read` REFUSES any path that matches the
10// secret DENY-LIST: compiled-in default needles (secret/key/token/passw/.pem, matched case-insensitively
11// against the WHOLE path) plus data-driven extras from fs_read_deny.conf (one lowercase needle per line,
12// CWD-relative -- rule 11: policy in data, not code). The tools-api runs where key material lives; an
13// arbitrary-read tool that could return opaque_keys.bin or tools_cap_secret.key would convert a read-cap
14// into a key-theft primitive. Over-blocking is the SAFE failure direction for v1.
15// WRITE/EDIT increment (2026-07-16): fsx_write (ATOMIC tmp+fsync+rename) + fsx_edit (exact-string replace
16// with the Claude-Edit UNIQUENESS contract). Exposed as the SEPARATE tools-api name `nx_fs_write` (its own
17// cap class per knowledge/mcp/exposure_policy.txt: read=broad, write=cap) -- the `nx_fs` name stays read-only.
18// The write DENY is a superset of the read deny (never clobber key material) PLUS the OS device/kernel/
19// firmware namespace via the nx_os_fs seam (rule 26 never-brick BY CONSTRUCTION -- not config-disableable)
20// PLUS the tool-registry escalation surface ("allowlist") PLUS data-driven extras (fs_write_deny.conf).
21// license_tier: ORIGINAL
22// syscalls.nx -- thin __syscall wrappers used across modules.
23//
24// Sovereign path: no libc. Every memory allocation, file op, and
25// clock read in the rest of the runtime routes through one of these
26// helpers. Numbers match Linux RV64; NishiOS uses the same set.
27//
28// Extracted from runtime.nx and ir.nx's copy-pasted helpers so the
29// module-import build doesn't produce duplicate symbols.
30
31// Tier aliases (nx_size / nx_idx / nx_fd / ...) ride along with the
32// syscall shelf: 141 runtime files use `as nx_size` etc. and only
33// compiled historically because the old parser silently void-cast
34// unknown type names (T#nx-int-alias-size-0 closed that hole LOUDLY,
35// which exposed the missing import). nx_tier.nx is pure type
36// aliases (0 funcs); prepass_register_aliases skips duplicates, so
37// modules that also import it directly stay fine.
38// nx_tier.nx -- substrate-wide tier configuration.
39//
40// Single point of edit for scale-agnostic substrate. Per user
41// directive 2026-05-13: "with the i64 it looks hardcoded everywhere
42// if we really want this dynamic dont we want that to be a changeable
43// value everywhere so it can switch to i128 and i256 etc."
44//
45// Per cardinals:
46// - feedback-numeric-tier-ladder.md (N0..N9 swap)
47// - feedback-scale-agnostic-substrate.md (MCU..HPC swap)
48// - feedback-substrate-additive-not-restrictive.md (declare cost)
49//
50// SEMANTIC ALIASES (not all should swap simultaneously):
51//
52// nx_int -- DEFAULT ARITHMETIC integer. Swappable across the
53// numeric tier ladder. Swap this to i128 to make the
54// entire substrate compute in 128-bit integers.
55//
56// nx_size -- MEMORY-SIZE integer. Always platform-pointer-width.
57// Used for buffer sizes, mmap byte counts, struct
58// sizes. Does NOT swap with nx_int -- changing this
59// would break pointer arithmetic. Stays i64 on RV64.
60//
61// nx_idx -- ARRAY-INDEX integer. Same width as nx_size on
62// flat-memory targets. Distinct alias so future
63// GPU/distributed targets can change indexing without
64// touching arithmetic.
65//
66// nx_byte -- The byte type. Stays u8. Distinct alias so MCU
67// targets that emulate u16-byte memory could rebind.
68//
69// HARDWARE-TIER BUFFER SIZES (declare cost, don't restrict):
70//
71// NX_BUF_TINY -- 64 B (MCU-friendly; stack-safe)
72// NX_BUF_SMALL -- 256 B (MCU heap-friendly)
73// NX_BUF_MEDIUM -- 4096 B (page-size; workstation default)
74// NX_BUF_LARGE -- 64 KiB (server-friendly)
75// NX_BUF_HUGE -- 1 MiB (HPC; assumes virtual memory)
76//
77// Use these instead of `sys_mmap(4096)` etc. so the substrate
78// announces its memory footprint and tier-incompatible code can
79// be flagged by audit.
80//
81// HARDWARE TIER (informational; downstream code may branch):
82//
83// NX_TIER_MCU = 0 -- microcontroller, kilobytes RAM
84// NX_TIER_SOVEREIGN_CHIP = 1 -- custom silicon, ~MB RAM
85// NX_TIER_FAMILY_DEVICE = 2 -- phone/router, ~GB RAM
86// NX_TIER_WORKSTATION = 3 -- laptop/desktop, ~10-100 GB RAM
87// NX_TIER_SERVER = 4 -- server-class, ~TB RAM
88// NX_TIER_HPC = 5 -- cluster, distributed
89//
90// COMPILE-TIME SWAP for nx_int (uncomment exactly one line):
91
92// THIS FILE IS THE SINGLE DEFINITION SITE for substrate-wide types.
93// Per user directive 2026-05-13: only this file (and platform-ABI
94// definition files like nx_syscalls.nx) should declare bare i64.
95// Every other substrate module uses the aliases below.
96
97// ===== arithmetic-tier aliases (swappable per nx_int tier ladder) =====
98
99type nx_int = i64 // N1 -- default; 9 quintillion, fits all physical scales
100// type nx_int = i32 // N0 -- MCU / embedded
101// type nx_int = i128 // N2 -- queued; needs nx_i128 backend ops
102// type nx_int = i256 // N3 -- shipped (nx_i256.nx); cosmology / crypto
103
104// ===== platform-width aliases (stay at pointer width) =================
105
106type nx_size = i64 // memory-size / byte-count
107type nx_idx = i64 // array-index
108type nx_byte = u8 // single-byte unit
109
110// ===== POSIX/Linux platform-ABI aliases (mandated 64-bit on RV64) ====
111//
112// Each is a 64-bit integer by Linux RV64 ABI. Renamed here so substrate
113// code never writes bare `i64` for these semantic types.
114
115type nx_fd = i64 // file descriptor (kernel-mandated width)
116type nx_exit = i64 // exit / status code (main() return)
117type nx_pid = i64 // process id
118type nx_uid = i64 // user id
119type nx_gid = i64 // group id
120type nx_syscall_num = i64 // Linux syscall number
121type nx_off = i64 // file offset (off_t)
122type nx_errno = i64 // errno (negative on syscall failure)
123
124// ===== SEMANTIC TYPE GENEALOGY (added 2026-05-20) ======================
125//
126// Per cardinal [[feedback-type-genealogy-math-cardinal-not-script]]
127// AND its immediate refinement (same session): every alias collapsing
128// to i64 is "y2k incestuous" -- relabeling, not genealogy. Real
129// semantic types pick the APPROPRIATE underlying width based on
130// the physics of the values they represent:
131//
132// - Small sealed enums (15 outcomes, 18 probe kinds) -> u8
133// - Display pixel coords (~32M max realistic) -> i32
134// - Q10 / Q14 fixed-point (values * 1024 / 16384) -> i32
135// - 32-bit color packs (RGBA8888) -> u32
136// - Q20 fixed-point (values * 1048576) -> i64
137// - Wide color packs (RGBA16161616, PRESERVE_ALL) -> u64
138// - Timestamps (ns / us / ms / cycles) -> i64 (2038 Y2K38)
139// - 64-bit hash digests -> u64
140// - Cryptographic hashes (SHA-256, SHA-512) -> STRUCT (multi-word; queued)
141// - Virtual addresses on 64-bit ISA -> u64
142//
143// Each type is a child of its PHYSICALLY-APPROPRIATE parent
144// (i8/u8/i32/u32/i64/u64), not blanket-i64. This breaks the
145// y2k-incestuous trap where renaming i64 N ways pretends to be
146// type discipline while every value silently shares one width.
147
148// ----- TIME family (all i64; ns/us/ms/cycles legitimately need it) -----
149// 2038 Y2K38 lurks for 32-bit time_t; i64 is the substrate-honest
150// choice. ms/us/ns + cycles all i64. s_q14 needs only i32 range
151// (val*16384 fits comfortably in i32 for typical second scales) but
152// we stay at i64 to compose cleanly with the i64 time arithmetic
153// across the substrate.
154type nx_ns = i64 // nanoseconds (since boot, monotonic)
155type nx_us = i64 // microseconds (since boot, monotonic)
156type nx_ms = i64 // milliseconds (since epoch, wall)
157type nx_s_q14 = i64 // seconds in Q14 fixed-point
158type nx_cycles = i64 // CPU cycle count
159
160// ----- HASH family (non-cryptographic 64-bit; crypto = STRUCT) -----
161// FNV-1a / xxhash digest is u64 by spec. SHA-256 / SHA-512 / BLAKE
162// hashes are MULTI-WORD; they're declared as structs in
163// nx_sha256.nx / nx_sha512.nx / nx_blake2b.nx (each carries its own
164// fixed-size byte array; NOT i64).
165type nx_hash64 = u64 // FNV-1a / xxhash / truncated SHA -- 64-bit digest
166
167// ----- ETG family (sealed enums; small value space -> u8) -----
168// nx_outcome_id sealed enum has 11 values; u8 fits 256
169// nx_probe_kind sealed enum has 18 values; u8 fits 256
170// nx_claim_source sealed enum has 13 values; u8 fits 256
171// nx_silicon_serial is a content-addressed identity HASH; u64.
172type nx_outcome_id = u8 // NX_ETG_OUTCOME_* (11 values; u8 fits)
173type nx_probe_kind = u8 // NX_ETG_PROBE_* (18 values; u8 fits)
174type nx_claim_source = u8 // NX_ETG_CLAIM_* (13 values; u8 fits)
175type nx_silicon_serial = u64 // per-die identity hash (cryptographic-strength width)
176
177// ----- PERF family (sealed enums) -----
178type nx_pathology_id = u8 // NX_PERF_PATH_* (15 values; u8 fits)
179type nx_flow_state_id = u8 // NX_FLOW_STATE_* (6 values; u8 fits)
180
181// ----- FIXED-POINT family (width chosen by precision*range) -----
182// Q10: value * 1024. Typical seed values are 0..255 so q10 max is
183// ~261K; i32 holds up to ~2.1B -> plenty of headroom.
184// Q14: value * 16384. Typical max around 16K of seed -> q14 ~ 2.6e8;
185// i32 holds up to 2.1e9 -> headroom for a few decimal seconds.
186// Q20: value * 1048576. Wider precision; needs i64 to avoid wrap.
187type nx_q10 = i32 // val * 1024; ~0.001 precision
188type nx_q14 = i32 // val * 16384; ~6e-5 precision
189type nx_q20 = i64 // val * 1048576; ~1e-6 precision
190
191// ----- GRAPHICS family (display coords + color packs at real widths) -----
192// Modern displays are well within 32-bit pixel addressing.
193// 8K display = 7680x4320 pixels. i32 holds 2.1B -> plenty.
194// nx_color_rgba8 = 32-bit packed RGBA (the common case)
195// nx_color_rgba16 = 64-bit packed RGBA16161616 (HDR / wide gamut)
196type nx_pixel_x = i32 // screen X in pixels
197type nx_pixel_y = i32 // screen Y in pixels
198type nx_color_rgba8 = u32 // RGBA8888 packed
199type nx_color_rgba16 = u64 // RGBA16161616 packed (HDR / preserve-all)
200
201// ----- PERCEPTUAL family (sealed enum; small value space) -----
202// nx_perceptual_profile has ~40 declared values up through
203// NX_PERCEPT_PRESERVE_ALL = 9999. Sentinel value 9999 needs i16,
204// not u8. i16 fits -32768..32767 with room for sentinels.
205type nx_perceptual_profile = i16 // NX_PERCEPT_* (~40 values + 9999 sentinel)
206
207// ----- ADDRESS family (virtual addresses on 64-bit ISA) -----
208// Pointer-width is u64 on all our supported 64-bit targets
209// (RV64 / x86_64 / AArch64 / ppc64le / loongarch64 / mips64 /
210// s390x / RV32 uses u32 -- TODO: tier-conditional).
211type nx_addr = u64 // raw virtual address (caller casts to *u8)
212
213// nx_capability_manifest:
214// variant_class: tier_config
215// variant_id: tier_config_v1_global
216// requires_isa: [rv32i, rv32imac, rv64imac, rv64imacv, x86_64, aarch64, armv7a, cortex_m, avr, xtensa, wasm32]
217// requires_syscalls: []
218// requires_ram_min_b: 0 // pure-const + typedef module, no runtime cost
219// tier_floor: NX_TIER_MCU
220// tier_ceiling: NX_TIER_HPC
221// cost_model:
222// flops_per_n: 0.0
223// bytes_per_n: 0.0
224// syscalls_per_n: 0.0
225// adversary_class: THREAT_OPPORTUNISTIC
226//
227// Note: This file is the substrate's TIER ENUM SOURCE OF TRUTH. It
228// has no variants by design (it IS the variant_class taxonomy that
229// other primitives' tier_floor / tier_ceiling reference). Manifest
230// declared for hygiene completeness; selector will skip it.
231
232// ---- buffer-size constants (use instead of bare numbers) -------
233
234const NX_BUF_TINY: nx_size = 64
235const NX_BUF_SMALL: nx_size = 256
236const NX_BUF_MEDIUM: nx_size = 4096
237const NX_BUF_LARGE: nx_size = 65536
238const NX_BUF_HUGE: nx_size = 1048576
239
240// ---- hardware tier sentinels -----------------------------------
241
242const NX_TIER_MCU: nx_int = 0
243const NX_TIER_SOVEREIGN_CHIP: nx_int = 1
244const NX_TIER_FAMILY_DEVICE: nx_int = 2
245const NX_TIER_WORKSTATION: nx_int = 3
246const NX_TIER_SERVER: nx_int = 4
247const NX_TIER_HPC: nx_int = 5
248
249// ---- numeric tier sentinels (informational) --------------------
250
251const NX_NUM_N0_I32: nx_int = 0
252const NX_NUM_N1_I64: nx_int = 1
253const NX_NUM_N2_I128: nx_int = 2
254const NX_NUM_N3_I256: nx_int = 3
255const NX_NUM_N4_I512: nx_int = 4
256const NX_NUM_N5_BIGINT: nx_int = 5
257
258// ---- byte-width of substrate types (replace bare `8` / `4`) ----
259//
260// Use these wherever you need the byte count of a substrate type --
261// e.g., sys_mmap(N * NX_SIZEOF_NX_SIZE) to allocate N nx_size slots.
262// Swap nx_int's underlying type and ONLY this constant changes.
263
264const NX_SIZEOF_NX_INT: nx_size = 8 // nx_int currently i64 -> 8 bytes
265const NX_SIZEOF_NX_SIZE: nx_size = 8 // nx_size always pointer-width
266const NX_SIZEOF_NX_IDX: nx_size = 8 // nx_idx alias of nx_size
267
268// ---- POSIX stdio file descriptors (replace bare 0/1/2) ---------
269
270const NX_FD_STDIN: nx_fd = 0
271const NX_FD_STDOUT: nx_fd = 1
272const NX_FD_STDERR: nx_fd = 2
273
274const SYS_MAGIC_1024: i64 = 1024
275const SYS_MAGIC_1000000: i64 = 1000000
276const SYS_MAGIC_4294967296: i64 = 4294967296
277// first read window for a size-UNKNOWABLE file (lseek END <= 0); doubles while it fills -- see sys_read_file
278const SYS_READ_GROW_INIT: i64 = 65536
279const SYS_MAGIC_100000: i64 = 100000
280
281// ---- syscall numbers (per-target) ----
282//
283// Cross-target via the macro processor (cardinal landed 2026-05-20:
284// feedback-hardware-agnostic-is-robustness -- the substrate must
285// compile + run on every silicon we point it at). Default path
286// (TARGET_X86_64 not defined) carries Linux RV64 numbers used by
287// qemu-RV64 + NishiOS. When nxc2 is invoked with --target x86_64
288// main.c pre-defines @macro TARGET_X86_64 1 so this file resolves
289// to x86_64 Linux ABI numbers.
290//
291// nx_syscalls_x86_64.nx remains the dedicated x86_64-only mirror
292// for files that want explicit single-target imports (e.g., bench
293// smokes built only for x86_64). This block makes nx_syscalls.nx
294// itself dual-target so substrate primitives compile portably.
295
296@ifdef TARGET_X86_64
297const SYS_READ: i64 = 0
298const SYS_WRITE: i64 = 1
299const SYS_CLOSE: i64 = 3
300const SYS_LSEEK: i64 = 8
301const SYS_OPENAT: i64 = 257
302const SYS_EXIT: i64 = 60
303const SYS_MMAP: i64 = 9
304const SYS_CLOCK_GETTIME: i64 = 228
305const SYS_IOCTL: i64 = 16
306const SYS_CLOCK_NANOSLEEP: i64 = 230
307// Namespace/container family, x86 branch (debt 1785528831). Moved here from
308// nx_syscalls_x86_64.nx so ONE module owns the wrapper set -- a TU reaching both
309// modules used to hold every wrapper TWICE, resolved silently by definition ORDER.
310const SYS_CHROOT: i64 = 161
311const SYS_MOUNT: i64 = 165
312const SYS_UNSHARE: i64 = 272
313const SYS_GETUID: i64 = 102
314const SYS_GETGID: i64 = 104
315const SYS_POLL: i64 = 7
316@endif
317
318@ifndef TARGET_X86_64
319const SYS_READ: i64 = 63
320const SYS_WRITE: i64 = 64
321const SYS_CLOSE: i64 = 57
322const SYS_LSEEK: i64 = 62
323const SYS_OPENAT: i64 = 56
324const SYS_EXIT: i64 = 93
325const SYS_MMAP: i64 = 222
326const SYS_CLOCK_GETTIME: i64 = 113
327const SYS_IOCTL: i64 = 29
328const SYS_CLOCK_NANOSLEEP: i64 = 115
329// Namespace/container family, RV64 branch (debt 1785528831). This is the branch actually
330// KEPT (TARGET_X86_64 is hard-pinned undefined), so these are the numbers the x86 backend
331// translates at emit: 51->161 chroot, 40->165 mount, 97->272 unshare, 174->102 getuid,
332// 176->104 getgid. The 40 and 51 rows were added to x86ctx_rv64_to_x86_64_syscall and
333// shipped FIRST -- without them both would pass through to the WRONG x86 syscall
334// (sendfile / getsockname), silently, because that translator's default is `return num`.
335const SYS_CHROOT: i64 = 51
336const SYS_MOUNT: i64 = 40
337const SYS_UNSHARE: i64 = 97
338const SYS_GETUID: i64 = 174
339const SYS_GETGID: i64 = 176
340const SYS_POLL: i64 = 73
341@endif
342
343func sys_ioctl(fd: i64, request: i64, arg: i64) -> i64 {
344 return __syscall(SYS_IOCTL, fd, request, arg, 0, 0, 0)
345}
346
347// poll(2): wait for events on fds. fds points to an array of `nfds`
348// struct pollfd { i32 fd; i16 events; i16 revents } (8 bytes each).
349// timeout_ms < 0 = block forever, 0 = return immediately. Returns the
350// count of ready fds (>0), 0 on timeout, or -errno. Used by the
351// substrate's own network diagnostics (bounded non-blocking connect)
352// instead of reaching for external tools. (rv64 const = ppoll; this
353// wrapper only runs on the x86_64 target.)
354func sys_poll(fds: *u8, nfds: i64, timeout_ms: i64) -> i64 {
355 return __syscall(SYS_POLL, fds, nfds, timeout_ms, 0, 0, 0)
356}
357
358// ---- core wrappers ----
359
360func sys_write(fd: i64, buf: *u8, count: i64) -> i64 {
361 return __syscall(SYS_WRITE, fd, buf, count, 0, 0, 0)
362}
363
364func sys_read(fd: i64, buf: *u8, count: i64) -> i64 {
365 return __syscall(SYS_READ, fd, buf, count, 0, 0, 0)
366}
367
368func sys_close(fd: i64) -> i64 {
369 return __syscall(SYS_CLOSE, fd, 0, 0, 0, 0, 0)
370}
371
372// chdir. The compiler only rv64->x86 translates CONSTANT syscall numbers (x86ctx_emit_syscall:
373// VK_CONST_INT); chdir is absent from that table, so a constant 49 falls through to x86_64 bind and a
374// constant 80 is mapped to fstat -- BOTH gave EBADF (PROBE-PROVEN by test_chdir). The documented escape
375// (nx_x86_64_ctx.nx:1004 "Runtime-computed syscall number -- load as-is") is to make op0 RUNTIME: a memory
376// load can't be folded to VK_CONST_INT, so the raw x86_64 number 80 passes through untranslated = real
377// chdir. Used by the supervisor to set a spawned daemon's CWD before execve. 0 on success, -errno on fail.
378func sys_chdir(path: *u8) -> i64 {
379 let nbox: *i64 = sys_mmap(16) as *i64
380 nbox[0] = 80 // x86_64 chdir, forced runtime so the rv64->x86 xlate is skipped
381 return __syscall(nbox[0], path as i64, 0, 0, 0, 0, 0)
382}
383
384// getcwd -- SAME runtime-number escape as sys_chdir directly above, for the same documented reason: the
385// rv64->x86 translator only rewrites CONSTANT syscall numbers, and getcwd is absent from that table, so a
386// constant would be mangled exactly as chdir's was. A memory load cannot be folded to VK_CONST_INT, so the
387// raw x86_64 number passes through untranslated.
388// WHY THIS EXISTS (2026-08-14): the shim had sys_chdir but NOTHING to ask where we are. Every organ that
389// resolves a path against the CWD could therefore only print a RELATIVE path -- a claim whose truth depends
390// on invisible state. Three separate working-directory faults in one session stayed invisible until they
391// bit, and in each the reader could not tell "the file is missing" from "I am standing somewhere else".
392// ★★★AN ORGAN THAT CANNOT REPORT WHERE IT IS CANNOT WRITE AN HONEST PATH.
393// Returns the byte length written INCLUDING the terminator, or -errno (notably -ERANGE if cap is short).
394// SYS_PATH_MAX is exported so a caller never hand-writes the size: the FIRST consumer of sys_getcwd (this
395// author, minutes after adding it) wrote `sys_mmap(4096)` and `sys_getcwd(buf, 4096)` on consecutive
396// lines -- a bare literal AND a duplicate-authored pair, the exact shape being removed elsewhere the same
397// day. ★★A NEW PRIMITIVE THAT DOES NOT EXPORT ITS OWN SIZE INVITES EVERY CALLER TO INVENT ONE.
398const SYS_PATH_MAX: i64 = 4096 // Linux PATH_MAX; getcwd returns -ERANGE below it
399// The DIRECTORY sibling of MODE_0644, added on the same evidence: `0x1ed` appears at 569 sites in
400// buildroot/runtime (nx_shelltool, corpus_complete=1), i.e. the estate scatters TWO file-mode constants,
401// not one. Named here so the pair lives together and a reader meets both at the same place.
402const MODE_0755: i64 = 0x1ed // rwxr-xr-x : default mode for a created directory
403func sys_getcwd(buf: *u8, cap: i64) -> i64 {
404 let nbox: *i64 = sys_mmap(16) as *i64
405 nbox[0] = 79 // x86_64 getcwd, forced runtime so the rv64->x86 xlate is skipped
406 return __syscall(nbox[0], buf as i64, cap, 0, 0, 0, 0)
407}
408
409// ⚠AT_FDCWD MOVED UP 2026-07-20 -- IT WAS A LIVE MISCOMPILE. This const was declared ~60 lines BELOW
410// (in the openat block) while sys_unlinkat and sys_fchmodat immediately below REFERENCE it. A module
411// const referenced ABOVE its declaration does not resolve, and nx_cc silently substituted CONSTANT 0
412// -- so both wrappers passed dirfd=0 (stdin) instead of -100. Absolute paths survive that (openat
413// ignores dirfd when the path is absolute), RELATIVE paths do not, which is exactly why unlinkat was
414// long recorded as flaky and "passing only by luck". Surfaced by the new unknown-identifier
415// diagnostic, which turned a silent 0 into a compile error. LAW (already banked, now enforced):
416// module-wide consts/statics go ABOVE every possible reader.
417const AT_FDCWD: i64 = -100
418
419// unlinkat(AT_FDCWD, path, 0) -- delete a file. x86_64 263 is a PROVEN pass-through (not an rv64 key),
420// but this is THE canonical home: 5+ organs hand-rolled `__syscall(263,...)` before this landed (DRY,
421// 2026-07-20). 0 on success, -errno on fail.
422func sys_unlinkat(path: *u8) -> i64 {
423 return __syscall(263, AT_FDCWD, path as i64, 0, 0, 0, 0)
424}
425
426// fchmodat(AT_FDCWD, path, mode) -- chmod by path. ⚠a CONSTANT 268 gets rv64->x86 TRANSLATED to the
427// wrong syscall (silent no-op chmod -- cost a vacuous-permission-test debug cycle, 2026-07-20), so the
428// number is forced RUNTIME via the sys_chdir nbox pattern. 0 on success, -errno on fail.
429func sys_fchmodat(path: *u8, mode: i64) -> i64 {
430 let nbox: *i64 = sys_mmap(16) as *i64
431 nbox[0] = 268 // x86_64 fchmodat, forced runtime so the xlate is skipped
432 return __syscall(nbox[0], AT_FDCWD, path as i64, mode, 0, 0, 0)
433}
434
435// exit_group(2) -- terminate ALL tasks in the thread group. Raw x86_64 231
436// (231 is NOT an rv64 key in the compiler's swap table, so it passes through
437// untranslated -- the munmap-11 precedent). THE explicit program-exit call
438// once a process holds live nx_thread_pool workers: CLONE_VM tasks are
439// separate PIDs, so plain sys_exit (93 -> x86 60, single task) leaves them
440// running, holding stdout open and wedging any pipeline that waits for EOF
441// (found 2026-07-07: the shared-pool matmul dispatcher hung the build lane
442// this way). Return-from-main already exit_groups via the _start trampoline;
443// use THIS for explicit early program exit. Per-THREAD exit stays sys_exit
444// (see nx_thread_exit).
445func sys_exit_group(code: i64) -> i64 {
446 return __syscall(231, code, 0, 0, 0, 0, 0)
447}
448
449// setpriority(PRIO_PROCESS=0, who=0 -> SELF, prio) -- x86_64 syscall 141.
450// Lower priority = larger nice value; 19 is the maximum yield.
451// WHY A WRAPPER AND NOT AN OPERATOR STEP (measured 2026-07-30): a bulk media
452// migration walk saturated the NAS; every forked organ queued behind its I/O so
453// EVERY agent MCP call 503'd for minutes -- the control plane went blind while a
454// background job did exactly what it was told. `renice 19` on the running pid
455// restored interactive service at once.
456// LAW: a long-running BULK job must yield to the interactive control plane BY
457// CONSTRUCTION at its own launch, not when an operator notices. Bind it to the
458// one act every bulk job performs (its startup) and nothing has to remember it.
459// WARN: `ionice` does NOT exist on the Synology busybox, so the I/O-class lever
460// is unavailable; CPU nice sufficed because the walk is SHA-256-bound over
461// cached reads (state R, not D, once niced).
462func sys_setpriority(prio: i64) -> i64 {
463 return __syscall(141, 0, 0, prio, 0, 0, 0)
464}
465
466// ADDITIVE TWIN 2026-08-04 (nx_resgov): re-nice ANOTHER process by pid. The incumbent above pins
467// who=0 = "me", so it cannot deprioritise a runaway -- and a governor that can only slow ITSELF has
468// no graceful rung between "observe" and "kill". PRIO_PROCESS=0, who=pid. Existing callers untouched
469// (rule 19: add the new entry point, never re-shape the one in service).
470func sys_setpriority_of(pid: i64, prio: i64) -> i64 {
471 return __syscall(141, 0, pid, prio, 0, 0, 0)
472}
473
474// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's
475// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat
476// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each;
477// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT
478// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive.
479// ===== SMALL-ALLOCATION BUMP ARENA (2026-08-06, debt 1785516350 / 1786055008) =====================
480// MEASURED FIRST, THEN BUILT. nx_arena_probe: 20,000 x sys_mmap(32) -> VmSize 80,172 kB,
481// VmRSS 80,024 kB. 640 KB of requested data cost 78 MB of RESIDENT memory -- 4096 bytes per 32-byte
482// request, exactly one page and one kernel VMA each. Across the corpus nx_mmapbal deep counts 17,157
483// functions / 43,498 sites that allocate and never return, so this multiplier is the actual shape of
484// the leak: the call sites are not individually wrong so much as individually EXPENSIVE.
485//
486// One VMA per call is also a HARD CORRECTNESS CEILING, not just a memory cost: vm.max_map_count
487// defaults to 65530, after which mmap returns -ENOMEM and callers write through the failed pointer.
488// That is precisely the dmesg-proven nx_hostctl SEGFAULT at 0xfffffffffffffff4 described below.
489//
490// SO: requests <= NXA_SMALL_MAX are bump-allocated out of a 256 KiB chunk (one VMA per ~5,400 small
491// allocations instead of one per allocation). Larger requests take the ORIGINAL path untouched --
492// they are the ones plausibly relying on page alignment, and they are not where the leak lives.
493//
494// THE ZEROING CONTRACT IS LOAD-BEARING AND IS PRESERVED BY NEVER RECYCLING. Callers rely on mmap
495// returning zeroed memory (nx_mmapbal: "mmap zeroes, so an untouched slot reads empty with no init
496// loop"). Bytes handed out here come from a freshly mmapped chunk and are NEVER handed out twice, so
497// every region is zero-filled exactly as before. LIFO give-back on munmap was deliberately REJECTED:
498// it would recover memory but hand back dirty bytes, silently breaking every caller that trusts the
499// zero -- a correctness regression traded for a memory win, which is the wrong trade.
500//
501// KNOWN TRADE-OFF, stated rather than hidden: small allocations are now ADJACENT within a chunk
502// instead of isolated in their own pages. An overrun that today walks off the end of a page and
503// SIGSEGVs loudly may instead corrupt a neighbouring allocation quietly. NXA_GAP puts slack between
504// allocations and NXA_SMALL_MAX is kept deliberately low to bound the exposure, but the risk is real
505// and is the reason this starts at 256 rather than a page.
506// ---- MEMORY ORDERING, THE ONE DEFINITION -------------------------------------------------------
507// Moved here from nx_atom.nx on 2026-08-25 and DELETED from its two other copies
508// (nx_atomic_intrinsic_test, nx_simd_i32x8_test). Measured before the move, corpus_complete=1:
509// THREE files each declared NX_MO_SEQ_CST = 5 independently. A constant written in three places is
510// three rulers that agree until one of them does not.
511//
512// They live at THIS layer because the arena allocator below needs an ordering value for its own
513// lock, and this file cannot import nx_atom.nx -- nx_atom imports THIS file, so that direction is a
514// cycle. Everything that had these constants still has them: nx_atom.nx imports this file, and so
515// does every consumer of nx_atom.
516//
517// The __atomic_* forms these feed are COMPILER INTRINSICS, not library calls, so this file can use
518// them with no import at all. Verified in nx_x86_64_ctx rather than assumed: __atomic_cas_i64 emits
519// `lock cmpxchgq`, __atomic_faa_i64 emits `lock xaddq`, __atomic_fence emits `mfence`. On x86-64 the
520// ordering operand is not consulted by the emitter because those instructions are full barriers
521// regardless; it is carried for the RV64A backend, where it selects the aq/rl bits.
522const NX_MO_RELAXED: i64 = 0
523const NX_MO_CONSUME: i64 = 1
524const NX_MO_ACQUIRE: i64 = 2
525const NX_MO_RELEASE: i64 = 3
526const NX_MO_ACQ_REL: i64 = 4
527const NX_MO_SEQ_CST: i64 = 5
528
529const NXA_SMALL_MAX: i64 = 256
530const NXA_CHUNK: i64 = 262144
531const NXA_ALIGN: i64 = 16
532const NXA_GAP: i64 = 16
533const NXA_STATE: i64 = 4096
534// RING CANARY (temporary diagnostic): the single-slot canary checked only the immediately
535// previous allocation and reported ZERO overruns -- but the bisection proved the write is
536// DELAYED, landing after later allocations have been served. Track the last NXA_RING
537// allocations and re-verify every one of them on each call. Lives at i64 slot NXA_RBASE in
538// the state page; the reporter borrows bytes 64/128, so 512 is clear of it.
539const NXA_RING: i64 = 128
540const NXA_RBASE: i64 = 64
541// ---- ARENA MARK/RESET (2026-08-12, additive; the durable fix for bump-without-reset). The arena
542// abandons a full chunk on rollover, so a long-running accept loop accumulates chunks into one giant
543// coalesced VMA (hub_gw MEASURED 3.4GB over 64k requests). A daemon marks the arena AFTER startup and
544// resets at its accept-loop's quiescent point; reset munmaps every chunk allocated since the mark and
545// zeroes the marked chunk's reclaimed tail, so per-request small allocations reuse a bounded slab.
546// State slots (state page is 512 i64): [3]=chunk_count [4]=mark_valid [5]=mark_bump [6]=mark_chunk_end
547// [7]=mark_chunk_count; the chunk-base list lives at slots NXA_CHUNKBASE..+NXA_CHUNKMAX (clear of the
548// ring at 64..320 and the reporter scratch below 64). CONTRACT: the caller guarantees NO arena
549// allocation made after the mark is still referenced at reset (the accept-loop top, where the previous
550// request's frames have all returned -- the same quiescent point ss_cache_reap already uses). LARGE
551// (>NXA_SMALL_MAX) allocations take their own VMA and are NOT tracked here; a per-request large mmap
552// still needs its own munmap. Untracked-overflow (>NXA_CHUNKMAX chunks between resets) degrades to the
553// old leak for the excess, never corrupts.
554// ---- ARENA MUTUAL EXCLUSION (2026-08-25) -------------------------------------------------------
555// THE DEFECT: the bump-pointer advance below was a plain read-modify-write --
556// let p: i64 = nxa_st[0]
557// nxa_st[0] = p + need
558// -- so two threads that read nxa_st[0] before either wrote it BOTH RECEIVE THE SAME POINTER and
559// then write over each other. The chunk refill, the ring-canary scan and the nxa_st[2] counter have
560// the same shape. MEASURED while shipping structured concurrency: eight pool workers calling a
561// helper that allocates a 16-byte timespec raced this cursor and produced ARENA-OVERRUN
562// prev_alloc_size=16 followed by SIGSEGV. It generalises to EVERY small allocation from more than
563// one thread, which is why the scoped-spawn child body was written to allocate nothing at all.
564//
565// WHY A LOCK AND NOT A LOCK-FREE BUMP. A fetch-and-add on the cursor fixes only the fast path; two
566// threads can still both observe the chunk exhausted and both refill, and the canary ring and the
567// counter would still race. One lock over the whole mutable region is correct by inspection, which
568// on the allocator that every organ in the estate calls is worth more than a clever fast path.
569// THE COST IS NOT THE DOMINANT COST HERE: this function ALREADY walks all NXA_RING canary slots on
570// every allocation, so one uncontended `lock cmpxchgq` is far below the noise of work already done.
571//
572// SLOT 4 IS FREE BY THE LAYOUT ABOVE: [0] cursor, [1] limit, [2] ring counter, [3] chunk count, and
573// the ring starts at NXA_RBASE=64. It is also clear of the byte-64 and byte-128 scratch that
574// nxa_report_overrun formats digits into (slots 8 and 16), which slot 4 (bytes 32-39) does not touch.
575const NXA_LOCK: i64 = 4
576// A BOUND ON AN UNKNOWABLE WAIT, DERIVED RATHER THAN PICKED, AND ITS EXHAUSTION ANNOUNCES. The
577// longest thing the critical section can do is the NXA_RING canary scan plus one mmap, so a spin far
578// beyond that is not contention -- it is a holder that is never coming back. Eight times the ring
579// gives an order of magnitude of headroom over the longest legitimate hold; on reaching it the
580// allocator SAYS SO on stderr once and keeps waiting, because hanging visibly is recoverable and
581// corrupting silently is not, and dying inside the allocator would take down a process that may be
582// merely slow.
583const NXA_LOCK_WARN: i64 = NXA_RING * 8
584// Slot 5: "the contention hint has already been printed by this process". Also free by the layout
585// above and clear of every scratch region. It is a FLAG, not a counter, and it is set through a CAS
586// so the once-ness is itself race-free rather than depending on the lock it reports about.
587const NXA_LOCK_WARNED: i64 = 5
588
589const NXA_CHUNKBASE: i64 = 320
590const NXA_CHUNKMAX: i64 = 192
591
592// [0] = next free byte, [1] = one past the end of the current chunk. A static POINTER to a real
593// mmapped page rather than scalar statics, matching the idiom the corpus already proves; the state
594// page is taken through __syscall directly so this can never recurse into itself.
595static nxa_st: *i64
596
597// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's
598// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat
599// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each;
600// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT
601// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive.
602//
603// A small len means the region came from the bump arena above, because sys_mmap routes by the SAME
604// threshold. Unmapping an interior pointer would tear a hole in a chunk still holding other callers'
605// live allocations, so it is a no-op here. Balanced small callers therefore no longer return memory --
606// but they now cost ~48 bytes instead of 4096, so the arena wins by two orders of magnitude even
607// against code that was already correct.
608// Matching release for sys_mmap_try and other whole kernel mappings.
609// Never pass an arena allocation from sys_mmap: its small pointers may be interior.
610// Preserve the requested mapping length; the kernel applies its page rounding.
611const NXA_MAP_INVALID:i64=0-22 // Linux EINVAL, a protocol value rather than a resource budget.
612func sys_munmap_direct(addr:*u8,len:i64)->i64{
613 if (addr as i64)<=0||len<=0{return NXA_MAP_INVALID}
614 return __syscall(11,addr as i64,len,0,0,0,0)
615}
616
617func sys_munmap(addr: *u8, len: i64) -> i64 {
618 if len <= NXA_SMALL_MAX { return 0 }
619 return __syscall(11, addr as i64, len, 0, 0, 0, 0)
620}
621
622// Seek within a file. whence: 0=SEEK_SET, 1=SEEK_CUR, 2=SEEK_END.
623// Returns new file offset on success, -errno on failure.
624func sys_lseek(fd: i64, offset: i64, whence: i64) -> i64 {
625 return __syscall(SYS_LSEEK, fd, offset, whence, 0, 0, 0)
626}
627
628// ---- FILESYSTEM SPACE: THE AXIS THE ESTATE DID NOT HAVE (2026-08-28) -----------------------------
629// WHY THIS IS HERE AND NOT LEFT WHERE IT WAS. On 2026-08-28 a 100%-FULL DISK truncated a sibling seat's
630// MEMORY.md to 0 bytes -- open(path,"w") truncates before it writes, so a full volume does not refuse a
631// write, it DESTROYS the file. Nothing in the estate saw it coming: nx_resmon is "the resource axis
632// nx_health lacks" for MEMORY and SWAP, and a search for the disk primitive returned matches=0 for BOTH
633// sys_statfs and statvfs with corpus_complete=1. nx_res_census records the same absence in its own header.
634// The capability was not missing, it was DARK: nx_system_triage.tr_free_gb has read filesystem space since
635// 2026-06-10, in an _hdl_build organ that is NOT REGISTERED (nx_job_run refuses it as "not an unpinned
636// GREEN tool"), so the one instrument that could have warned was unreachable by any caller.
637// A CAPABILITY THAT EXISTS IN ONE UNREACHABLE ORGAN IS INDISTINGUISHABLE FROM ONE NOBODY BUILT.
638//
639// WHY THE RAW 137 AND NOT A SYS_ CONST. This file's dual-arch blocks are gated on TARGET_X86_64, which is
640// HARD-PINNED UNDEFINED, so the RV64 branch is what compiles and the x86 backend translates each number at
641// emit through x86ctx_rv64_to_x86_64_syscall -- whose default is `return num`. There is NO row for RV64 43
642// (statfs), so a SYS_STATFS=43 const would pass through unmapped to x86_64 43 = ACCEPT: a different
643// syscall, silently, on a path pointer. That is not a hypothesis -- nx_system_triage PROBE-PROVED it on
644// 2026-06-10: "rv64 43 returns -9 through the translation table; 137 raw matches df exactly." So 137 is
645// the MEASURED-CORRECT number for the target we actually emit, and it is named here ONCE instead of
646// sitting as a bare literal at each call site.
647// ⚠NAMED FOLLOW-UP, conflict-checked and deliberately NOT taken here: adding `if num == 43 { return 137 }`
648// to x86ctx_rv64_to_x86_64_syscall would make the arch-correct const work too. Nothing passes 43 as an x86
649// number (43 appears only as a translation TARGET, from RV64 202 accept), so the row is safe -- but it is a
650// COMPILER change that activates only on the next nx_cc self-host rebuild, and the working path needs none.
651//
652// struct statfs (x86_64) as i64 slots: 0 f_type, 1 f_bsize, 2 f_blocks, 3 f_bfree, 4 f_bavail, 5 f_files.
653// f_bavail (not f_bfree) is the honest number for "will my write succeed": it excludes the root reserve, so
654// it reports FULLER than root would see. Wrong in the safe direction, and said out loud rather than implied.
655// ⚠THE IMPRECISION, MEASURED AND NAMED SO NOBODY LATER "FIXES" IT INTO AGREEING WITH df: this permil is
656// NOT df's Use%. df computes Used/(Used+Available), which EXCLUDES the root-reserved blocks from its
657// denominator; this computes (blocks-bavail)/blocks, which counts the reserve as used. VERIFIED against df
658// on 2026-08-28: avail_bytes came back 958449582080, which is EXACTLY df's Available of 935985920 KiB, while
659// the same volume read 113 permil here and 7% there -- both correct, measuring different things. Both reach
660// their maximum at the SAME event (bavail = 0), so a threshold calibrated against THIS metric alarms at the
661// same moment a writer actually hits the wall; it simply sits higher below that. Calibrate thresholds to
662// this definition, and do not import a df-derived number as if it were the same quantity.
663const SYS_STATFS_X86_MEASURED: i64 = 137
664const STATFS_BUF_BYTES: i64 = 144
665const STATFS_I_BSIZE: i64 = 1
666const STATFS_I_BLOCKS: i64 = 2
667const STATFS_I_BAVAIL: i64 = 4
668const STATFS_PERMIL: i64 = 1000
669const STATFS_ERR: i64 = 0 - 1
670
671// raw statfs into a caller-supplied 144-byte buffer. 0 = ok, non-zero = the kernel's negative errno.
672func sys_statfs(path: *u8, buf: *i64) -> i64 {
673 return __syscall(SYS_STATFS_X86_MEASURED, path, buf, 0, 0, 0, 0)
674}
675
676// bytes available to a non-root writer on the filesystem holding `path`; STATFS_ERR if statfs failed.
677func sys_fs_avail_bytes(path: *u8) -> i64 {
678 let buf: *i64 = sys_mmap(STATFS_BUF_BYTES) as *i64
679 if sys_statfs(path, buf) != 0 { return STATFS_ERR }
680 return buf[STATFS_I_BSIZE] * buf[STATFS_I_BAVAIL]
681}
682
683// USED per-mille of the filesystem holding `path`, counted against what a non-root writer can reach:
684// (blocks - bavail) * 1000 / blocks. STATFS_ERR if statfs failed or the volume reports zero blocks --
685// an UNMEASURABLE volume must never read as 0 permil used, which is the most flattering possible lie.
686func sys_fs_used_permil(path: *u8) -> i64 {
687 let buf: *i64 = sys_mmap(STATFS_BUF_BYTES) as *i64
688 if sys_statfs(path, buf) != 0 { return STATFS_ERR }
689 let blocks: i64 = buf[STATFS_I_BLOCKS]
690 if blocks <= 0 { return STATFS_ERR }
691 let avail: i64 = buf[STATFS_I_BAVAIL]
692 return ((blocks - avail) * STATFS_PERMIL) / blocks
693}
694
695func sys_exit(code: i64) -> i64 {
696 return __syscall(SYS_EXIT, code, 0, 0, 0, 0, 0)
697}
698
699// mmap anonymous R/W memory; returns raw bytes. Fixed flags:
700// PROT_READ|PROT_WRITE = 3, MAP_PRIVATE|MAP_ANONYMOUS = 0x22, fd=-1.
701// FAIL-CLOSED ON A REFUSED MAPPING (2026-08-07). MEASURED: the corpus has 90,817 sys_mmap call sites
702// and SIX of them check the result -- all six in test probes whose response is sys_exit anyway. So
703// 90,811 sites take whatever this returns and write through it. When the kernel refuses, that value is
704// -errno, and the write lands at 0xfffffffffffffff4 (-12, ENOMEM). That is not a hypothetical: dmesg
705// on this host recorded it hourly in nx_web_shard_compact, and 18 times in nx_web_crawl_step.
706// Returning a poisoned pointer to 90,811 unguarded callers is the defect. Dying here is strictly safer
707// than dying there: the process ends either way, but this way there is no memory corruption first and
708// the failure is NAMED instead of arriving as a bare segfault address an operator has to decode.
709// This is the never-brick shape -- fail-safe BY CONSTRUCTION, not by every caller remembering.
710// KNOWN COST, stated: nx_mmap_probe / test_munmap deliberately provoke a refusal to observe it. They
711// now exit here with code 12 rather than printing their own verdict. Six probes lose a diagnostic;
712// 90,811 sites stop corrupting memory.
713// ===== TEMPORARY DIAGNOSTIC -- ARENA OVERRUN CANARY (2026-08-07) =====================================
714// ⛔DO NOT BLESS A COMPILER BUILT WITH THIS. The canary writes 0xC7 into the NXA_GAP slack that a
715// caller could otherwise legitimately read as zeros, so it changes observable behaviour for any code
716// that reads past its declared size -- which is precisely the code being hunted.
717// PURPOSE: at NXA_SMALL_MAX=256 the compiler produces 14 SPURIOUS type diagnostics (it reports
718// `arg 2 is an INTEGER but the parameter is a POINTER` against a parameter DECLARED `j: *u8`), i.e.
719// something writes past its allocation and corrupts the parser's type table. At threshold 64 the same
720// requests each get a 4096-byte page whose slack absorbs it. Reading the source found nothing: the
721// two obvious suspects (nx_ir.nx:70 sys_mmap(104), nx_parse.nx:868 sys_mmap(256)) are both correctly
722// sized and bounded. So stop reading and MEASURE: stamp each small allocation's gap, verify the
723// PREVIOUS one on the next call, and print the size of whichever allocation was overrun.
724// Writes to fd 2 without allocating -- it borrows scratch inside the arena state page, because a
725// reporter that called sys_mmap would recurse into the thing it is instrumenting.
726// Dump n bytes at src to fd 2, unprintables as '.', using scratch at state+256 (the ring starts at
727// state+512 and the decimal scratch sits at +64/+128, so this cannot collide with either). n is
728// capped by callers at 48 so the buffer stays clear of the ring.
729func nxa_dump_printable(src: i64, n: i64) -> i64 {
730 let o: *u8 = ((nxa_st as i64) + 256) as *u8
731 var i: i64 = 0
732 while i < n {
733 let sp: *u8 = (src + i) as *u8
734 var c: i64 = sp[0] as i64
735 if c < 32 { c = 46 }
736 if c > 126 { c = 46 }
737 o[i] = c as u8
738 i = i + 1
739 }
740 o[n] = 10 as u8
741 sys_write(2, o, n + 1)
742 return 0
743}
744
745// FINGERPRINT (2026-08-12): the size alone + all-zeros byte dump never named the site. The ring already
746// records each allocation's REQUESTED size in counter order, so the recent size SEQUENCE fingerprints the
747// code path that was running when the overrun landed (a distinctive run of sizes is near-unique to a
748// function). Writes to fd 2 borrowing state-page scratch at bytes 320/340 (clear of the ring at byte 512,
749// the reporter decimals at 64/128, and the byte-dump at 256). No allocation -- must not recurse into sys_mmap.
750func nxa_dump_sizes() -> i64 {
751 sys_write(2, " ring_sizes(old->recent): " as *u8, 27)
752 let scr: *u8 = ((nxa_st as i64) + 320) as *u8
753 let out2: *u8 = ((nxa_st as i64) + 340) as *u8
754 let cnt: i64 = nxa_st[2]
755 var start: i64 = cnt - 32
756 if start < 0 { start = 0 }
757 var idx: i64 = start
758 while idx < cnt {
759 let slot: i64 = idx % NXA_RING
760 let szv: i64 = nxa_st[NXA_RBASE + slot * 2 + 1]
761 var m: i64 = szv
762 var k: i64 = 0
763 if m == 0 { scr[0] = 48 as u8; k = 1 }
764 while m > 0 { scr[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
765 var j: i64 = 0
766 while j < k { out2[j] = scr[k - 1 - j]; j = j + 1 }
767 out2[k] = 44 as u8
768 sys_write(2, out2, k + 1)
769 idx = idx + 1
770 }
771 sys_write(2, "\n" as *u8, 1)
772 return 0
773}
774
775func nxa_report_overrun(sz: i64, gs: i64) -> i64 {
776 let msg: *u8 = "ARENA-OVERRUN prev_alloc_size=" as *u8
777 var n: i64 = 0
778 while msg[n] != (0 as u8) { n = n + 1 }
779 sys_write(2, msg, n)
780 let b: *u8 = ((nxa_st as i64) + 64) as *u8
781 let o: *u8 = ((nxa_st as i64) + 128) as *u8
782 var m: i64 = sz
783 var k: i64 = 0
784 if m == 0 { b[0] = 48 as u8; k = 1 }
785 while m > 0 { b[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
786 var i: i64 = 0
787 while i < k { o[i] = b[k - 1 - i]; i = i + 1 }
788 o[k] = 10 as u8
789 sys_write(2, o, k + 1)
790 // The SIZE alone did not name the site (four 80-byte victims, and the two unbounded 80-byte
791 // buffers in nx_parse.nx were sized from their inputs with no effect). So show the DATA: the
792 // victim's own bytes identify the buffer, and the bytes written past its end identify the WRITER.
793 let algn: i64 = (sz + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN
794 let base: i64 = gs - algn
795 var dn: i64 = sz
796 if dn > 48 { dn = 48 }
797 sys_write(2, " own : " as *u8, 8)
798 nxa_dump_printable(base, dn)
799 sys_write(2, " over: " as *u8, 8)
800 nxa_dump_printable(gs, 16)
801 nxa_dump_sizes()
802 return 0
803}
804
805func nxa_die(msg: *u8) -> i64 {
806 var n: i64 = 0
807 while msg[n] != (0 as u8) { n = n + 1 }
808 sys_write(2, msg, n)
809 sys_exit(12)
810 return 0
811}
812
813// Address of the arena lock word. Valid only once nxa_st exists; every caller below has already
814// ensured that, and the state-page creation itself is discussed at the take site.
815func nxa_lock_addr() -> *i64 {
816 return ((nxa_st as i64) + NXA_LOCK * 8) as *i64
817}
818
819// __atomic_cas_i64 returns 1 when it wrote and 0 when it did not, so the spin condition is == 0.
820// It is a COMPILER INTRINSIC, not a call into nx_atom -- that module imports THIS file, so importing
821// it back would be a cycle. Verified in nx_x86_64_ctx rather than assumed: it lowers to a genuine
822// `lock cmpxchgq` followed by sete, which is a full barrier on x86-64 whatever ordering is passed.
823func nxa_lock_take() -> i64 {
824 var spins: i64 = 0
825 while __atomic_cas_i64(nxa_lock_addr(), 0, 1, NX_MO_ACQUIRE) == 0 {
826 spins = spins + 1
827 // Fires EXACTLY ONCE, on equality rather than on exceeding, so a genuinely long wait reports
828 // itself without turning the allocator into a log generator.
829 if spins == NXA_LOCK_WARN {
830 // ONCE PER PROCESS, not once per acquisition. MEASURED 2026-08-25 and this is a
831 // correction to the first cut of this very function: it fired on equality per CALL, and
832 // eight workers contending LEGITIMATELY produced hundreds of identical lines in a single
833 // gate run. A DIAGNOSTIC THAT FIRES CONSTANTLY IS ONE EVERY READER LEARNS TO IGNORE, and
834 // this one writes to the stderr of every organ in the estate.
835 // The threshold was derived from the longest the critical section can run, which bounds
836 // ONE hold and says nothing about QUEUE DEPTH: with N threads waiting, a legitimate wait
837 // is N holds and can exceed any per-section derivation. So this is a NOISE FLOOR for a
838 // hint, never a correctness bound -- it never fails, never delays, and never repeats.
839 // The flag is set through a CAS so the once-ness cannot itself race.
840 let wflag: *i64 = ((nxa_st as i64) + NXA_LOCK_WARNED * 8) as *i64
841 if __atomic_cas_i64(wflag, 0, 1, NX_MO_ACQ_REL) == 1 {
842 let m: *u8 = "ARENA-LOCK: sustained allocator contention seen (reported once per process; a hint, not an error -- allocation proceeds normally).\n" as *u8
843 var mn: i64 = 0
844 while m[mn] != (0 as u8) { mn = mn + 1 }
845 sys_write(2, m, mn)
846 }
847 }
848 }
849 return 0
850}
851
852func nxa_lock_give() -> i64 {
853 // nx_cc refuses a bare intrinsic statement ("computes a value and never uses it") and an atomic
854 // store has no result worth using, so it is bound and discarded -- the same shape nx_atom uses
855 // for exactly this reason. The contract is unchanged: this returns 0 either way.
856 let discarded: i64 = __atomic_store_i64(nxa_lock_addr(), 0, NX_MO_RELEASE)
857 if discarded != 0 { return 0 }
858 return 0
859}
860
861// Optional mapping for request boundaries that must report allocation refusal.
862// Unlike sys_mmap, this never aborts the process and never consumes arena storage.
863// Release successful mappings with sys_munmap_direct, not the arena-aware sys_munmap.
864// A successful reservation can still fail on later physical-memory pressure; callers
865// must not describe virtual address admission as guaranteed resident RAM.
866func sys_mmap_try(size:i64)->*u8 {
867 if size<=0 { return 0 as *u8 }
868 let mapped:i64=__syscall(SYS_MMAP,0,size,3,0x22,-1,0)
869 if mapped<=0 { return 0 as *u8 }
870 return mapped as *u8
871}
872
873func sys_mmap(size: i64) -> *u8 {
874 // Large requests keep the EXACT original behaviour, byte for byte: page-aligned, own VMA. Any
875 // caller that depends on page alignment is allocating at least a page, so the arena cannot reach
876 // it. Every failure path below also falls back to this same call, so an exhausted arena degrades
877 // to the old allocator rather than returning a bad pointer.
878 if size > NXA_SMALL_MAX {
879 let big: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
880 if big <= 0 { nxa_die("FATAL sys_mmap: kernel refused a large mapping (ENOMEM). Refusing to return a poisoned pointer -- a write through it would corrupt memory.\n" as *u8) }
881 return big as *u8
882 }
883 if (nxa_st as i64) == 0 {
884 let s: i64 = __syscall(SYS_MMAP, 0, NXA_STATE, 3, 0x22, -1, 0)
885 if s <= 0 {
886 // arena state page refused -- degrade to the plain allocator, and only die if THAT fails too
887 let f1: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
888 if f1 <= 0 { nxa_die("FATAL sys_mmap: kernel refused the arena state page AND the fallback mapping (ENOMEM).\n" as *u8) }
889 return f1 as *u8
890 }
891 nxa_st = s as *i64
892 }
893 // EVERYTHING FROM HERE TO THE RETURN TOUCHES SHARED STATE: the cursor, the limit, the chunk
894 // table, the canary ring and the ring counter. It is ONE critical section because the refill
895 // decision and the bump that depends on it cannot be separated without reintroducing the race.
896 // The state page itself is created ABOVE this point, unlocked: two threads arriving there
897 // together would each map a page and one would win the static, leaking the other's 4 KiB but
898 // corrupting nothing, and in practice the arena is warm long before any thread is spawned
899 // because spawning one allocates. That residual is NAMED here rather than papered over.
900 nxa_lock_take()
901 var need: i64 = size
902 if need <= 0 { need = 1 }
903 need = (need + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN + NXA_GAP
904 if nxa_st[0] + need > nxa_st[1] {
905 let c: i64 = __syscall(SYS_MMAP, 0, NXA_CHUNK, 3, 0x22, -1, 0)
906 if c <= 0 {
907 // chunk refused -- degrade to the plain allocator, and only die if THAT fails too.
908 // RELEASE FIRST: this is the one path that leaves the critical section early, and a lock
909 // held across a degraded return would wedge every other allocator in the process.
910 nxa_lock_give()
911 let f2: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
912 if f2 <= 0 { nxa_die("FATAL sys_mmap: kernel refused an arena chunk AND the fallback mapping (ENOMEM).\n" as *u8) }
913 return f2 as *u8
914 }
915 nxa_st[0] = c
916 nxa_st[1] = c + NXA_CHUNK
917 // track the chunk base so arena_reset can munmap post-mark chunks (additive; guarded at cap).
918 if nxa_st[3] < NXA_CHUNKMAX { nxa_st[NXA_CHUNKBASE + nxa_st[3]] = c; nxa_st[3] = nxa_st[3] + 1 }
919 }
920 // ---- RING CANARY (temporary diagnostic) ----
921 var rk: i64 = 0
922 while rk < NXA_RING {
923 let gs0: i64 = nxa_st[NXA_RBASE + rk * 2]
924 if gs0 != 0 {
925 var bi: i64 = 0
926 var bad: i64 = 0
927 while bi < 8 {
928 let bp: *u8 = (gs0 + bi) as *u8
929 if bp[0] != (199 as u8) { bad = 1; bi = 8 } else { bi = bi + 1 }
930 }
931 if bad == 1 {
932 nxa_report_overrun(nxa_st[NXA_RBASE + rk * 2 + 1], gs0)
933 nxa_st[NXA_RBASE + rk * 2] = 0
934 }
935 }
936 rk = rk + 1
937 }
938 let p: i64 = nxa_st[0]
939 nxa_st[0] = p + need
940 let gs: i64 = p + need - NXA_GAP
941 var gj: i64 = 0
942 while gj < NXA_GAP { let q: *u8 = (gs + gj) as *u8; q[0] = 199 as u8; gj = gj + 1 }
943 let slot: i64 = nxa_st[2] % NXA_RING
944 nxa_st[NXA_RBASE + slot * 2] = gs
945 nxa_st[NXA_RBASE + slot * 2 + 1] = size
946 nxa_st[2] = nxa_st[2] + 1
947 // The ONLY other exit from the critical section is the degraded chunk-refill path above, which
948 // releases before it returns. Every shared write is now behind this pair.
949 nxa_lock_give()
950 return p as *u8
951}
952
953// arena_mark: force the arena warm (so a first chunk + state page exist), then record the current
954// position as the reset barrier. Returns 1. A daemon calls this ONCE after startup, before its loop.
955func sys_arena_mark() -> i64 {
956 let warm: *u8 = sys_mmap(1) // ensures nxa_st + chunk[0] exist; the 1 byte is itself arena scratch
957 if (warm as i64) == 0 { return 0 }
958 nxa_st[4] = 1
959 nxa_st[5] = nxa_st[0]
960 nxa_st[6] = nxa_st[1]
961 nxa_st[7] = nxa_st[3]
962 return 1
963}
964
965// arena_reset: reclaim everything allocated since the mark. munmap post-mark chunks, restore the bump
966// to the mark, ZERO the marked chunk's reclaimed tail (preserves the mmap-returns-zeroed contract for
967// recycled bytes), and CLEAR the ring canary (its stamps may point into a just-munmap'd chunk, and a
968// stale deref on the next alloc would SEGV). Returns 1 on reset, 0 if no mark was set.
969func sys_arena_reset() -> i64 {
970 if (nxa_st as i64) == 0 { return 0 }
971 if nxa_st[4] != 1 { return 0 }
972 var i: i64 = nxa_st[7]
973 while i < nxa_st[3] {
974 let cb: i64 = nxa_st[NXA_CHUNKBASE + i]
975 if cb != 0 { __syscall(11, cb, NXA_CHUNK, 0, 0, 0, 0); nxa_st[NXA_CHUNKBASE + i] = 0 }
976 i = i + 1
977 }
978 nxa_st[3] = nxa_st[7]
979 nxa_st[0] = nxa_st[5]
980 nxa_st[1] = nxa_st[6]
981 var z: i64 = nxa_st[0]
982 while z < nxa_st[1] { let q: *u8 = z as *u8; q[0] = 0 as u8; z = z + 1 }
983 var r: i64 = 0
984 while r < NXA_RING * 2 { nxa_st[NXA_RBASE + r] = 0; r = r + 1 }
985 nxa_st[2] = 0
986 return 1
987}
988
989// mmap anonymous SHARED R/W memory -- ONE region that survives fork() so all
990// children see each other's writes (MAP_SHARED|MAP_ANONYMOUS = 0x21). Allocate
991// in the PARENT before fork. Foundation for the fork-per-connection video relay
992// (peers in separate children share the per-room frame table).
993func sys_mmap_shared(size: i64) -> *u8 {
994 let r: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x21, -1, 0)
995 return r as *u8
996}
997
998// madvise(2) -- prefetch/advice hints for mapped ranges. MADV_WILLNEED=3 batches page-ins so a
999// serial fault loop over a cold file-backed mmap becomes parallel disk readahead (the dp-web-pub
1000// stage-2 p95 fix, 2026-08-12). RAW x86_64 NUMBER 28 ON PURPOSE (sys_exit_group's raw-231 pattern):
1001// the portable rv64/asm-generic number is 233 and x86ctx_rv64_to_x86_64_syscall has no 233 row in
1002// the DEPLOYED compiler, so a portable const would emit x86_64 233 = epoll_ctl (the wrong-syscall-
1003// not-an-error class; see the setpgid/flock rows). The 233->28 row is staged in nx_x86_64_ctx.nx and
1004// activates on the next nx_cc self-host rebuild; flip this to the portable const AFTER that lands.
1005// Signature bite-proven by nx_madvise_probe (0 / -12 ENOMEM / -22 EINVAL). Advisory contract: callers
1006// may ignore the return value -- a failed hint costs nothing but the cold-read behaviour it hints away.
1007func sys_madvise(addr: *u8, len: i64, advice: i64) -> i64 {
1008 return __syscall(28, addr, len, advice, 0, 0, 0)
1009}
1010
1011// openat flavors used by the compiler driver. AT_FDCWD = -100 (declared ABOVE, next to its first
1012// reader -- see the miscompile note there; do NOT move it back down).
1013// O_RDONLY = 0; O_CREAT|O_WRONLY|O_TRUNC = 0x241 on Linux RV64.
1014const O_RDONLY: i64 = 0
1015const O_WRONLY_CT: i64 = 0x241 // O_CREAT | O_WRONLY | O_TRUNC
1016const O_WRONLY_CA: i64 = 0x441 // O_CREAT | O_WRONLY | O_APPEND
1017
1018func sys_openat_rd(path: *u8) -> i64 {
1019 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY, 0, 0, 0)
1020}
1021
1022// O_RDWR|O_CREAT (NO truncate) -- for offset-addressed persistent files like the metrics ring TSDB
1023// (create if missing, then lseek+read/write records in place, never truncating existing history).
1024const O_RDWR_CREATE: i64 = 0x42
1025func sys_openat_rdwr(path: *u8, mode: i64) -> i64 {
1026 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDWR_CREATE, mode, 0, 0)
1027}
1028
1029// ★★★THE FILE MODE IS THE HALF OF THIS INTERFACE THAT WAS NEVER NAMED. The O_ flags above are named
1030// consts in hex WITH a decoding comment; the mode passed beside them is a bare literal at every call
1031// site. MEASURED 2026-08-14 (coverage_complete=1 corpus_complete=1 over 23,053 files):
1032// - 29 organs passed the mode as a bare DECIMAL literal, which no reader decodes as rw-r--r--
1033// without stopping to convert it. ⚠THE FIRST COUNT PUBLISHED HERE WAS 26: the scan was scoped to
1034// runtime/_hdl_build/ and the SUBDIRECTORY's count was published as the estate figure -- three
1035// more (nx_forge_rag, nx_gpu_export, nx_bvhfk) sat one level up in runtime/.
1036// ★A COUNT INHERITS THE SCOPE OF ITS SCAN, AND THE SCOPE IS THE PART NOBODY PRINTS BESIDE IT.
1037// ⚠The offending call is deliberately NOT spelled out literally in this comment: prose is source
1038// bytes, so writing the pattern here would make every future grep for it match this note;
1039// - 10 MORE each define their OWN private 0644 const (IP_ VR_ VP_ LIVE_ FD_ FP_ WL_ PUB_ REG_ HFF_),
1040// nine written 0x1a4 and one written 420 -- THE SAME CONSTANT IN TWO DIFFERENT BASES.
1041// Ten seats each solved this privately and none put the answer where the next one would look. That is
1042// the duplicate-ruler defect precisely: changing the estate's default artifact mode today means finding
1043// 39 sites in two notations and hoping none was missed. One name, in the shim every organ already
1044// imports, is the entire fix -- and it belongs HERE, beside the flags, not in a 40th private copy.
1045const MODE_0644: i64 = 0x1a4 // rw-r--r-- : default mode for a generated artifact
1046// rwxr-xr-x : default mode for a created DIRECTORY. A directory without the execute bit cannot be
1047// traversed, so MODE_0644 is not merely stricter here -- it is wrong, and the failure surfaces later
1048// as an unopenable path rather than as a refused mkdir. Named beside its sibling so the choice is a
1049// lookup rather than a recollection; the estate otherwise spells this as a raw 0x1ed at every site.
1050const MODE_0755: i64 = 0x1ed
1051// Seconds of ZERO PROGRESS on one socket operation before an accepted connection is abandoned.
1052// A single-threaded accept-loop daemon that loop-reads to Content-Length can be starved FOREVER by one
1053// peer that declares a body it never finishes sending -- a one-request DoS, hostile OR merely buggy.
1054// nx_dos_timeout_scan supervises the class and named 16 daemons carrying no timeout at all; the cure is
1055// sys_set_socket_timeout(cfd, ACCEPT_TMO_S) folded in right after accept.
1056// WHY 30 AND NOT THE 5 THE LOGIN DAEMONS USE: this bound must be wrong in the direction of SERVING, not
1057// of dropping. The attack is an UNBOUNDED wait, so ANY finite bound closes it; a short one additionally
1058// risks aborting a legitimate slow client. 30s of zero progress on a single recv/send is pathological
1059// for every daemon in the class -- including the streaming ones, where data is flowing and the timer
1060// never approaches its bound -- while still converting an infinite starvation into a bounded one.
1061// It is the calibration nx_galx_bridge already uses for an accepted cfd; named here rather than copied
1062// into a 16th private literal, exactly as MODE_0644 above.
1063const ACCEPT_TMO_S: i64 = 30
1064func sys_openat_wr(path: *u8, mode: i64) -> i64 {
1065 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CT, mode, 0, 0)
1066}
1067
1068// Linux O_WRONLY | O_CREAT | O_EXCL. An existing final component, including
1069// a symlink, is a conflict; callers acquire ownership only on success.
1070const O_WRONLY_CREATE_EXCLUSIVE: i64 = 0x1 | 0x40 | 0x80
1071func sys_openat_exclusive(path: *u8, mode: i64) -> i64 {
1072 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CREATE_EXCLUSIVE, mode, 0, 0)
1073}
1074
1075// Linux O_DIRECTORY: require a directory, rather than merely an openable node.
1076const O_DIRECTORY: i64 = 0x10000
1077func sys_openat_directory(path: *u8) -> i64 {
1078 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0, 0, 0)
1079}
1080
1081// Open path for append (create if missing). Used by append-only
1082// journals such as .race_telemetry.tsv. RV64 syscall numbers; the
1083// x86_64 mirror lives in nx_syscalls_x86_64.nx.
1084func sys_openat_append(path: *u8, mode: i64) -> i64 {
1085 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA, mode, 0, 0)
1086}
1087
1088// Linux open ABI flags: acquire close-on-exec atomically and refuse a final
1089// symlink. Nonblocking also prevents an unexpected FIFO from stalling admission.
1090const O_CLOEXEC: i64 = 0x80000
1091const O_NOFOLLOW: i64 = 0x20000
1092const O_NONBLOCK: i64 = 0x800
1093const MODE_0600: i64 = 0x180
1094func sys_openat_lock(path: *u8) -> i64 {
1095 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK, MODE_0600, 0, 0)
1096}
1097
1098// symlinkat(target, AT_FDCWD, linkpath) -- raw x86_64 266 forced RUNTIME (the chdir escape, same as
1099// readlinkat below). THE atomic-repoint primitive for release management: create releases/current.new ->
1100// sys_renameat over releases/current = an atomic symlink swap (golive/rollback are instant + crash-safe).
1101// 0 on success, -errno (notably -EEXIST=-17 if linkpath exists -- create the .new name, then rename).
1102func sys_symlinkat(target: *u8, linkpath: *u8) -> i64 {
1103 let nbox: *i64 = sys_mmap(16) as *i64
1104 nbox[0] = 266
1105 let r: i64 = __syscall(nbox[0], target as i64, AT_FDCWD, linkpath as i64, 0, 0, 0)
1106 sys_munmap(nbox as *u8, 16)
1107 return r
1108}
1109
1110// readlinkat(AT_FDCWD, path, buf, cap) -- raw x86_64 267 forced RUNTIME (the chdir escape: keep the
1111// number out of the rv64->x86 constant-translate path). Returns link length (NO NUL appended), -errno
1112// on fail. nbox is munmap'd before return: the daemon supervisor calls this hundreds of times PER CYCLE
1113// (exe-identity sweeps), and a leaked page per call is exactly the VSZ-balloon class that broke fork.
1114func sys_readlinkat(path: *u8, buf: *u8, cap: i64) -> i64 {
1115 let nbox: *i64 = sys_mmap(16) as *i64
1116 nbox[0] = 267
1117 let r: i64 = __syscall(nbox[0], AT_FDCWD, path as i64, buf as i64, cap, 0, 0)
1118 sys_munmap(nbox as *u8, 16)
1119 return r
1120}
1121
1122// Atomically replace newpath with oldpath (rename(2) on one filesystem: a concurrent reader sees the
1123// whole old file or the whole new file, never a torn read). The S-class content-publish primitive:
1124// write the new page to a temp file, then sys_renameat(tmp, live) -> hot-swap, NO rm+ln race.
1125// renameat2: rv64=276, x86_64=316, flags=0. The known-good compiler translates most rv64 syscall
1126// numbers to the x86_64 target but its table MISSES 276 -- verified 2026-06-14 via nx_rename_probe:
1127// raw 276 -> -EINVAL (lands on x86_64 `tee`), raw 316 -> renames OK. That silently broke every
1128// cst_write_atomic publish (page.html.new written, never swapped in). Try the x86_64 number first
1129// (works on every x86_64 build incl. known-good); fall back to the rv64 number for native-rv64 or
1130// translating compilers that do map it. flags=0 so renameat2 == renameat semantics.
1131func sys_renameat(oldpath: *u8, newpath: *u8) -> i64 {
1132 let r: i64 = __syscall(316, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
1133 if r == 0 { return 0 }
1134 return __syscall(276, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
1135}
1136
1137// fsync(2): flush file (or directory) data+metadata to stable storage.
1138// PROBE-PROVEN 2026-06-10 (_fsync_probe): rv64 82 is NOT in the compiler's
1139// translation table (lands on x86 rename -> -EFAULT both ways); direct
1140// x86_64 74 passes through raw (the unlinkat-263 precedent) and behaves as
1141// fsync (0 on a valid fd, -9 EBADF on a bad one). Storage commit points
1142// fsync the data files AND their directory around rename(2) so a committed
1143// segment survives power loss, not just process death.
1144func sys_fsync(fd: i64) -> i64 {
1145 return __syscall(74, fd, 0, 0, 0, 0, 0)
1146}
1147
1148// flock(2): BSD-style whole-file ADVISORY lock. rv64 32 -> x86_64 73 via the compiler's
1149// x86ctx_rv64_to_x86_64_syscall table (nx_x86_64_ctx.nx:961, PROVEN LIVE in flock_deploy.log).
1150// op: SYS_LOCK_SH=1 / SYS_LOCK_EX=2 / SYS_LOCK_NB=4 (OR) / SYS_LOCK_UN=8. Returns 0 on success,
1151// -errno on failure. Used by the framed-append durability floor to serialize the write-until-
1152// complete loop so a partial/short write under contention can NEVER misalign a concurrent appender
1153// (O_APPEND single-write atomicity is necessary but not sufficient on every fs -- the lock makes
1154// the whole framed record write atomic against other lockers). Additive: no existing caller in
1155// this file changes. NOTE: nx_flock.nx is a separate organ importing the LEGACY "syscalls.nx"
1156// name; this wrapper lives HERE so organs already on nx_syscalls.nx (e.g. nx_framed_append) get
1157// flock without a second import (double-import rc=6 trap).
1158const SYS_LOCK_SH: i64 = 1
1159const SYS_LOCK_EX: i64 = 2
1160const SYS_LOCK_NB: i64 = 4
1161const SYS_LOCK_UN: i64 = 8
1162func sys_flock(fd: i64, op: i64) -> i64 {
1163 return __syscall(32, fd, op, 0, 0, 0, 0)
1164}
1165
1166// newfstatat(2): stat `path` into a 144-byte x86-64 struct stat at `statbuf`. x86_64 nr 262 is passed
1167// DIRECTLY (the unlinkat-263 / fsync-74 precedent: stat-family rv64 numbers aren't in the compiler's
1168// translation table, so a raw x86_64 number passes through untranslated). Returns 0 on success, <0
1169// (e.g. -2 ENOENT) on error. st_mtim.tv_sec @ offset 88, st_mtim.tv_nsec @ 96 (the freshness channel).
1170func sys_fstatat(path: *u8, statbuf: *u8) -> i64 {
1171 return __syscall(262, AT_FDCWD, path, statbuf, 0, 0, 0)
1172}
1173
1174// utimensat(2): set `path` atime+mtime from `times` (a struct timespec[2] = [atime.sec,atime.nsec,
1175// mtime.sec,mtime.nsec]). x86_64 nr 280 passed DIRECTLY. A sovereign `touch`; also makes freshness
1176// tests deterministic. Returns 0 on success, <0 on error.
1177func sys_utimensat(path: *u8, times: *i64) -> i64 {
1178 return __syscall(280, AT_FDCWD, path, times as i64, 0, 0, 0)
1179}
1180
1181// ---- sovereign host control-plane syscalls (x86_64; single unconditional consts,
1182// per the known-good-compiler @ifdef finding). The Nishi supervisor uses these to
1183// manage the daemon lifecycle WITHOUT any shell (no pkill / mkdir / chmod glue). ----
1184
1185// COMPILER NOTE: the known-good compiler BAKES whole function bodies by NAME for some syscalls
1186// (proven via emitted .s: a function literally named sys_kill emits number 8, sys_chmod emits 155
1187// -- both wrong, regardless of the const referenced). So these wrappers use NON-baked names
1188// (nx_kill / nx_chmod). sys_mkdir / sys_renameat are not baked, so those keep the sys_ name.
1189
1190// DESIGN: __syscall takes the RV64/generic number; the compiler's x86ctx_rv64_to_x86_64_syscall table
1191// (nx_x86_64_ctx.nx) translates it to the build target. So pass the RV64 number. These four were added
1192// to that sovereign table 2026-06-06 (kill 129->62, mkdirat 34->258, fchmodat 53->268, renameat2
1193// 276->316); x86 kill(62) had collided with rv64 lseek(62), x86 fchmodat(268) with rv64 pivot_root(268).
1194
1195// kill(pid, sig) -- rv64 129 -> x86_64 62. SIGTERM=15 / SIGKILL=9. Host control plane.
1196func nx_kill(pid: i64, sig: i64) -> i64 { return __syscall(129, pid, sig, 0, 0, 0, 0) }
1197
1198// setpgid(pid, pgid) -- put a process in its own PROCESS GROUP so a killer can reach its whole
1199// subtree. nx_kill(0 - pgid, sig) signals every member, not just the one process you forked.
1200// A BOUND THAT ONLY REACHES THE PROCESS YOU FORKED IS NOT A BOUND ON THE WORK IT STARTED.
1201// Per-target const, NOT a bare generic number: x86ctx_rv64_to_x86_64_syscall translates only the
1202// numbers it knows and FALLS THROUGH for the rest. MEASURED on the laptop lane 2026-08-10: a bare
1203// generic 154 reached x86_64 as 154 and returned -38 (ENOSYS), silently -- and a fix built on it
1204// reproduced the original bug exactly. Callers must treat setpgid as BEST-EFFORT.
1205@ifdef TARGET_X86_64
1206const SYS_SETPGID: i64 = 109
1207@endif
1208@ifndef TARGET_X86_64
1209const SYS_SETPGID: i64 = 154
1210@endif
1211func sys_setpgid(pid: i64, pgid: i64) -> i64 { return __syscall(SYS_SETPGID, pid, pgid, 0, 0, 0, 0) }
1212
1213// prlimit64(pid, resource, new_limit, old_limit) -- the Linux RESOURCE-LIMIT primitive =
1214// the Job-Object ActiveProcessLimit / memory-limit analog for the sovereign supervisor (M5).
1215// x86_64 prlimit64 = 302 (PASSED DIRECTLY, the unlinkat-263 / fsync-74 / fstatat-262
1216// precedent: a raw x86_64 number not in the compiler's rv64->x86 swap table passes through
1217// untranslated). NOTE: rv64 prlimit64 IS 261 but x86_64 261 = futimesat -- so the naive
1218// "261 is the same on both" is WRONG (PROBE-PROVEN: 261 returned EFAULT/EINVAL because it
1219// hit futimesat); the build target here is x86_64, so we emit 302 directly. pid=0 => the
1220// calling process (a forked child caps ITSELF before running its payload). new_limit /
1221// old_limit each point at a struct rlimit64 { rlim_cur: i64, rlim_max: i64 } (16 bytes);
1222// pass 0 for old_limit to skip read-back. Returns 0 on success, -errno (e.g. -1 EPERM if
1223// raising a hard limit unprivileged) on failure. NON-baked name (the compiler bakes some
1224// sys_* bodies by name; the nx_ prefix avoids that trap).
1225func nx_prlimit(pid: i64, resource: i64, new_limit: *u8, old_limit: *u8) -> i64 {
1226 return __syscall(302, pid, resource, new_limit as i64, old_limit as i64, 0, 0)
1227}
1228
1229// RLIMIT resource ids (Linux generic; identical rv64/x86_64). RLIMIT_AS = address-space
1230// (virtual memory) cap -- the cleanest userspace-settable "memory budget" for a supervised
1231// job. RLIMIT_CPU = CPU-seconds cap. WNOHANG=1 = wait4 non-blocking liveness poll option.
1232const RLIMIT_CPU: i64 = 0
1233const RLIMIT_AS: i64 = 9
1234const WNOHANG: i64 = 1
1235
1236// mkdirat -- rv64 34 -> x86_64 258. Create a doc-root directory. mode e.g. 0x1ed (0755).
1237func sys_mkdir(path: *u8, mode: i64) -> i64 { return __syscall(34, AT_FDCWD, path, mode, 0, 0, 0) }
1238
1239// fchmodat -- rv64 53 -> x86_64 268. +x a freshly-deployed daemon binary (mode 0x1ed). flags=0.
1240func nx_chmod(path: *u8, mode: i64) -> i64 { return __syscall(53, AT_FDCWD, path, mode, 0, 0, 0) }
1241
1242// setsid -- x86_64 = 112 (not in the rv64->x86 table, so the literal passes through). Detach a forked
1243// process into a NEW session so it survives the SSH/parent close -- sovereign daemonization (no shell setsid).
1244func nx_setsid() -> i64 { return __syscall(112, 0, 0, 0, 0, 0, 0) }
1245
1246// CLOCK_MONOTONIC = 1. ts is 16 bytes {sec: i64, nsec: i64}.
1247// Returns 0 / -errno.
1248func sys_clock_gettime_mono(ts: *i64) -> i64 {
1249 return __syscall(SYS_CLOCK_GETTIME, 1, ts, 0, 0, 0, 0)
1250}
1251
1252// CLOCK_REALTIME = 0 -- wall-clock seconds since the Unix epoch. Use
1253// this (NOT monotonic) for anything that must match calendar time:
1254// X.509 notBefore/notAfter, logs, TLS timestamps. Monotonic returns
1255// time-since-boot, which encodes as ~1970 when (mis)used as an epoch.
1256func sys_clock_gettime_real(ts: *i64) -> i64 {
1257 return __syscall(SYS_CLOCK_GETTIME, 0, ts, 0, 0, 0, 0)
1258}
1259
1260// Wall-clock seconds since the Unix epoch.
1261func sys_now_realtime_sec() -> i64 {
1262 let ts: *i64 = sys_mmap(16) as *i64
1263 sys_clock_gettime_real(ts)
1264 return ts[0]
1265}
1266
1267// Wall-clock milliseconds since the Unix epoch.
1268func sys_now_realtime_ms() -> i64 {
1269 let ts: *i64 = sys_mmap(16) as *i64
1270 sys_clock_gettime_real(ts)
1271 return ts[0] * 1000 + ts[1] / SYS_MAGIC_1000000
1272}
1273
1274// Wall-clock MICROSECONDS since the Unix epoch -- the CROSS-MACHINE stamp.
1275// ★ Use this, never sys_now_us(), for any value one machine writes and ANOTHER machine judges
1276// (fleet beats, lease expiry, telemetry rows). Monotonic counts from each machine's OWN boot, so
1277// subtracting one node's monotonic stamp from another's monotonic now yields the difference of two
1278// unrelated boot epochs -- the remote row then reads as ancient (or future-forged) and a freshness
1279// guard rejects every honest remote node while looking like it is working.
1280func sys_now_realtime_us() -> i64 {
1281 let ts: *i64 = sys_mmap(16) as *i64
1282 sys_clock_gettime_real(ts)
1283 return ts[0] * SYS_MAGIC_1000000 + ts[1] / 1000
1284}
1285
1286// Convenience: monotonic time in milliseconds. Caller does not own
1287// the timespec buffer -- it is mmap'd once per call (cheap; the
1288// underlying syscall already costs more than the page fault).
1289func sys_now_ms() -> i64 {
1290 let ts: *i64 = sys_mmap(16) as *i64
1291 sys_clock_gettime_mono(ts)
1292 let sec_part: i64 = ts[0] * 1000
1293 let nsec_part: i64 = ts[1] / SYS_MAGIC_1000000
1294 return sec_part + nsec_part
1295}
1296
1297// Convenience: monotonic time in microseconds. Used by per-request
1298// elapsed-time tracking in search engines + benches where ms is too
1299// coarse. Same caller-ownership rules as sys_now_ms.
1300func sys_now_us() -> i64 {
1301 let ts: *i64 = sys_mmap(16) as *i64
1302 sys_clock_gettime_mono(ts)
1303 let sec_part: i64 = ts[0] * SYS_MAGIC_1000000
1304 let nsec_part: i64 = ts[1] / 1000
1305 return sec_part + nsec_part
1306}
1307
1308// Alias used by nx_search_onsite_engine etc. Matches `_us` naming
1309// convention. Substrate-canonical name is sys_now_us; this alias
1310// preserves existing call sites without churn.
1311func sys_clock_now_us() -> i64 {
1312 return sys_now_us()
1313}
1314
1315// Read the entire file at `path` into a fresh mmap'd buffer. Returns
1316// a null-terminated *u8 plus writes the byte count to *out_len. On
1317// error (open failure, oversize) returns null and leaves out_len = 0.
1318// Uses a fixed 1 MiB buffer for the first pass; larger sources need a
1319// growth loop.
1320// ---- process control (Linux RV64) ----------------------------
1321//
1322// Lets NishiLang programs spawn other processes -- prerequisite
1323// for replacing shell scripts (f6_gate.sh) with .nx equivalents.
1324// NishiOS will expose a different process model (capability-based);
1325// these wrappers are the Linux-host compatibility layer.
1326
1327@ifdef TARGET_X86_64
1328const SYS_CLONE: i64 = 56
1329const SYS_EXECVE: i64 = 59
1330const SYS_WAIT4: i64 = 61
1331const SYS_PIPE2: i64 = 293
1332const SYS_DUP3: i64 = 292
1333@endif
1334
1335@ifndef TARGET_X86_64
1336const SYS_CLONE: i64 = 220
1337const SYS_EXECVE: i64 = 221
1338const SYS_WAIT4: i64 = 260
1339const SYS_PIPE2: i64 = 59
1340const SYS_DUP3: i64 = 24
1341@endif
1342
1343// Clone flags (subset). CLONE_VFORK blocks parent until child
1344// exec's or exits, matching fork() semantics closely enough for
1345// our spawn-then-wait patterns.
1346const CLONE_VM: i64 = 0x00000100
1347const CLONE_VFORK: i64 = 0x00004000
1348const SIGCHLD: i64 = 17
1349
1350// Create a child process via Linux clone(). Returns:
1351// > 0 in the parent: child PID
1352// == 0 in the child: child should exec or exit
1353// < 0 on error: -errno
1354// Uses SIGCHLD as the signal that parent receives on child exit
1355// (the libc fork() default); no shared memory or thread flags.
1356// ---- namespace / container family (debt 1785528831) ----------------
1357// Moved here from nx_syscalls_x86_64.nx so ONE module owns the wrapper set. Their
1358// absence here is why nx_container.nx had to import that module as a SECOND syscall
1359// layer, which put every wrapper in the TU twice and let definition ORDER pick the
1360// winner, silently, until the duplicate-definition guard made it fail closed.
1361func sys_unshare(flags: i64) -> i64 {
1362 return __syscall(SYS_UNSHARE, flags, 0, 0, 0, 0, 0)
1363}
1364func sys_mount(source: *u8, target: *u8, fs_type: *u8, mountflags: i64, data: *u8) -> i64 {
1365 return __syscall(SYS_MOUNT, source, target, fs_type, mountflags, data, 0)
1366}
1367func sys_chroot(path: *u8) -> i64 {
1368 return __syscall(SYS_CHROOT, path, 0, 0, 0, 0, 0)
1369}
1370func sys_getuid() -> i64 {
1371 return __syscall(SYS_GETUID, 0, 0, 0, 0, 0, 0)
1372}
1373func sys_getgid() -> i64 {
1374 return __syscall(SYS_GETGID, 0, 0, 0, 0, 0, 0)
1375}
1376
1377func sys_fork() -> i64 {
1378 return __syscall(SYS_CLONE, SIGCHLD, 0, 0, 0, 0, 0)
1379}
1380
1381// Replace the current process image. `path` is the executable
1382// (absolute or in $PATH if the child first does a fresh clone).
1383// `argv` is a null-terminated array of *u8 (already-marshalled).
1384// `envp` same shape, or null for "inherit parent's env".
1385// Only returns on failure (-errno).
1386// EXEC WITH A CLEAN FD TABLE (seq1785451144). A child inherits every fd its parent held, INCLUDING
1387// listen sockets, across fork AND execve. That is how nx_opaque_login came to hold mgmt s :18098
1388// alongside mgmt itself -- two listeners on one port, connections split between them, a VALID route
1389// answering 404 on some requests. There is no error anywhere in that state, which is why it was
1390// filed as a transport flake for months.
1391// ADDITIVE ON PURPOSE: sys_execve is left byte-identical (910 call sites across 719 files -- a
1392// global change there is unverifiable in one session). Spawners opt in by calling THIS instead.
1393// AUDIT THAT MAKES IT SAFE: zero call sites in the tree dup3 to a target fd above 2, so no exec d
1394// child is deliberately handed a high fd; 0/1/2 are preserved untouched.
1395// Linux child lifetime binding: call in the freshly forked child, before exec.
1396// The expected parent PID is captured before fork, closing the pre-arm death race.
1397// Kernel semantics bind to the creating thread; privileged exec can clear this.
1398const NX_SYS_PRCTL: i64 = 167
1399const NX_PR_SET_PDEATHSIG: i64 = 1
1400const NX_PR_SET_CHILD_SUBREAPER: i64 = 36
1401func sys_prctl(option: i64, arg: i64) -> i64 {
1402 return __syscall(NX_SYS_PRCTL,option,arg,0,0,0,0)
1403}
1404func sys_bind_parent_lifetime(expected_parent: i64, signal: i64) -> i64 {
1405 if expected_parent <= 0 || signal <= 0 { return 0-22 }
1406 let armed: i64=sys_prctl(NX_PR_SET_PDEATHSIG,signal)
1407 if armed < 0 { return armed }
1408 let parent: i64=__syscall(173,0,0,0,0,0,0)
1409 if parent != expected_parent { return 0-10 }
1410 return 0
1411}
1412
1413// Linux waitid observes termination without releasing the child's PID when WNOWAIT is set.
1414// Portable syscall 95 requires the matching x86 backend translation to 247.
1415const SYS_WAITID_PORTABLE: i64 = 95
1416const NX_WAIT_P_PID: i64 = 1
1417const NX_WAIT_EXITED: i64 = 4
1418const NX_WAIT_NOWAIT: i64 = 0x01000000
1419const NX_WAIT_SIGINFO_BYTES: i64 = 128
1420func sys_waitid(idtype: i64, id: i64, info: *u8, options: i64) -> i64 {
1421 return __syscall(SYS_WAITID_PORTABLE,idtype,id,info as i64,options,0,0)
1422}
1423
1424// Post-fork only: the child owns its descriptor table. The buffer bounds a
1425// getdents batch, never the descriptor numbers or number of open handles.
1426const NX_FD_DENT_BUFFER: i64 = 4096
1427const NX_SYS_CLOSE_RANGE: i64 = 436 // Linux x86_64 and asm-generic ABI
1428const NX_FD_UINT_MAX: i64 = 4294967295
1429func sys_close_inherited_proc(first: i64) -> i64 {
1430 let directory: i64=sys_openat_rd("/proc/self/fd")
1431 if directory < 0 { return directory }
1432 let buf: *u8=sys_mmap(NX_FD_DENT_BUFFER)
1433 var result: i64=0
1434 var running: i64=1
1435 while running == 1 {
1436 let n: i64=sys_getdents64(directory,buf,NX_FD_DENT_BUFFER)
1437 if n == (0-4) { continue }
1438 if n <= 0 { result=n; break }
1439 var off: i64=0
1440 while off < n {
1441 if n-off < 20 { result=0-5; running=0; break }
1442 let rec: *u8=buf+off
1443 let size: i64=dirent_reclen(rec)
1444 if size < 20 || size > n-off { result=0-5; running=0; break }
1445 var i: i64=19
1446 var fd: i64=0
1447 var valid: i64=1
1448 while i < size {
1449 let c: i64=rec[i] as i64
1450 if c == 0 { break }
1451 if c < 48 || c > 57 { valid=0; break }
1452 if fd > (2147483647-(c-48))/10 { valid=0; break }
1453 fd=fd*10+c-48; i=i+1
1454 }
1455 if i == 19 || i == size { valid=0 }
1456 if valid == 1 && fd >= first && fd != directory {
1457 // Linux releases the descriptor even when close reports a late
1458 // I/O error; never retry close and risk a reused descriptor.
1459 let closed: i64=sys_close(fd)
1460 if closed < 0 && closed != (0-9) { result=closed; running=0; break }
1461 }
1462 off=off+size
1463 }
1464 }
1465 let closedir: i64=sys_close(directory)
1466 sys_munmap(buf,NX_FD_DENT_BUFFER)
1467 if result == 0 && closedir < 0 { result=closedir }
1468 return result
1469}
1470func sys_close_inherited(first: i64) -> i64 {
1471 if first < 0 { return 0-22 }
1472 let rc: i64=__syscall(NX_SYS_CLOSE_RANGE,first,NX_FD_UINT_MAX,0,0,0,0)
1473 if rc == (0-38) { return sys_close_inherited_proc(first) }
1474 return rc
1475}
1476func sys_execve_clean(path: *u8, argv: *i64, envp: *i64) -> i64 {
1477 let rc: i64=sys_close_inherited(3)
1478 if rc < 0 { return rc }
1479 return sys_execve(path,argv,envp)
1480}
1481
1482func sys_execve(path: *u8, argv: *i64, envp: *i64) -> i64 {
1483 return __syscall(SYS_EXECVE, path, argv, envp, 0, 0, 0)
1484}
1485
1486// Wait for a child to exit. `pid` = -1 waits for ANY child,
1487// otherwise waits for that specific PID. `status` is a caller-
1488// mmapped i64 slot: on exit the low 16 bits carry Linux's w* status
1489// flags (WIFEXITED / WEXITSTATUS). Returns the reaped child's PID
1490// or -errno.
1491func sys_wait4(pid: i64, status: *i64, options: i64) -> i64 {
1492 return __syscall(SYS_WAIT4, pid, status, options, 0, 0, 0)
1493}
1494
1495// Extract exit code from a wait4 status word. Matches the glibc
1496// WEXITSTATUS macro: bits 8-15 of the low 16.
1497func wait_exit_code(status: i64) -> i64 {
1498 return (status >> 8) & 0xFF
1499}
1500
1501// Terminating signal from a wait4 status (0 when the child exited normally). Sibling of
1502// wait_exit_code; RESTORED 2026-07-30 after a stale whole-tree push erased both it and
1503// sys_ignore_sigpipe below, while three files still CALLED them (nx_http_server, nx_sigpipe_gate,
1504// nx_tools_api_serve) -- so the tree could not build until they came back.
1505func wait_term_signal(status: i64) -> i64 {
1506 return status & 0x7f
1507}
1508
1509// THE ONE RULER for "what result code did this process actually produce". Use this, not
1510// wait_exit_code, anywhere the answer becomes a VERDICT.
1511//
1512// WHY IT EXISTS, MEASURED 2026-08-25. wait_exit_code is WEXITSTATUS and is correctly named:
1513// bits 8-15 of the status word. But a child KILLED BY A SIGNAL has no exit status at all, and
1514// those bits are ZERO -- so a SEGFAULTING process is indistinguishable from a clean exit 0 to
1515// every caller that reads only wait_exit_code. Measured live: a gate that SIGSEGV'd mid-run was
1516// served by /api/gate_run as exit_code 0, verdict GREEN. A CRASHED GATE WORE A PASS.
1517//
1518// This is not a new discovery in this estate -- and that is the point. nx_gatekit_lib's
1519// gk_wait_code already carried exactly this rule, with its own measurement recorded (two gates
1520// the 60 s watchdog KILLED journaled `GREEN exit=0 ms=60443`). It was fixed THERE in August and
1521// left unfixed in nx_tool_run, which is the shared exec primitive sitting behind /api/gate_run,
1522// /api/build and 51 other consumers. A LAW APPLIED IN ONE ORGAN AND NOT ITS SIBLING IS HALF A
1523// LAW, AND THE HALF LEFT UNDONE IS THE ONE ON THE PRODUCTION PATH. So the rule now lives HERE,
1524// beside the two accessors it is composed of, and gk_wait_code delegates to it: one ruler.
1525//
1526// Shell convention 128+signal (137 SIGKILL, 139 SIGSEGV) is deliberate: it makes the death both
1527// VISIBLE and NON-ZERO, so every existing caller that branches on rc != 0 sees it with no change.
1528// wait_exit_code is left EXACTLY as it was -- 85 call sites across the corpus (corpus_complete=1)
1529// read it, and silently redefining WEXITSTATUS under them would be the cure being worse.
1530func wait_status_rc(status: i64) -> i64 {
1531 let sig: i64 = wait_term_signal(status)
1532 if sig != 0 { return 128 + sig }
1533 return wait_exit_code(status)
1534}
1535
1536// Ignore SIGPIPE process-wide, so writing to a socket the peer already closed returns -EPIPE
1537// instead of KILLING the process. SIGPIPE default action is TERMINATE, which for a daemon means
1538// every client that walks away mid-response is an outage -- this one call at the listen primitive
1539// is inherited by all 52 consumers of nx_http_server_listen.
1540// rt_sigaction(SIGPIPE, {handler=SIG_IGN}, NULL, 8): syscall 13 on x86-64, which happens to equal
1541// the signal number. SA_RESTORER is deliberately NOT set -- the kernel consults it only when it
1542// DELIVERS a handler frame, and SIG_IGN never delivers one.
1543// PROVEN, not asserted: nx_sigpipe_gate forks a child that writes to a closed pipe and demands
1544// death-by-signal-13 WITHOUT this call and a clean -EPIPE WITH it.
1545// Restore a signal to its DEFAULT disposition. THE INVERSE OF sys_ignore_sigpipe, and it exists
1546// because SIG_IGN is inherited across BOTH fork and execve: a daemon that ignores SIGPIPE hands
1547// that ignore to every child it spawns, FOREVER. That silently corrupted verification -- the
1548// sigpipe gate reported 4/5 RED under /api/gate_run and 5/5 GREEN under a shell, same binary,
1549// same minute, because its DISEASE control (writing to a closed peer must KILL) could not be
1550// observed inside an environment where the kill was already disabled (seq1463). A harness must
1551// not change the state it is verifying; where it must, it has to hand back a clean slate.
1552// ⚠the same inheritance can also produce a FALSE GREEN, which is the far more dangerous half.
1553func sys_default_signal(sig: i64) -> i64 {
1554 let act: *i64 = sys_mmap(64) as *i64
1555 act[0] = 0
1556 act[1] = 0
1557 act[2] = 0
1558 act[3] = 0
1559 return __syscall(13, sig, act as i64, 0, 8, 0, 0)
1560}
1561
1562func sys_ignore_sigpipe() -> i64 {
1563 let act: *i64 = sys_mmap(64) as *i64
1564 act[0] = 1
1565 act[1] = 0
1566 act[2] = 0
1567 act[3] = 0
1568 return __syscall(13, 13, act as i64, 0, 8, 0, 0)
1569}
1570
1571// Create a pipe. `fds` must point at 8+ writable bytes; the kernel
1572// packs BOTH int32 fds into fds[0]: read end = low 32 bits, write end
1573// = HIGH 32 bits (fds[1] is never written -- the old comment claiming
1574// fds[1]=write-end caused a false-pass KAT + a hung gate, 2026-07-16).
1575// Extract: rfd = fds[0] & 0xffffffff; wfd = (fds[0] / 4294967296) &
1576// 0xffffffff. Returns 0 on success, -errno on failure.
1577func sys_pipe2(fds: *i64, flags: i64) -> i64 {
1578 return __syscall(SYS_PIPE2, fds, flags, 0, 0, 0, 0)
1579}
1580
1581// Duplicate `oldfd` onto `newfd`, closing `newfd` first if open.
1582// Used to wire child stdout to a pipe: dup3(pipe_write_end, 1).
1583func sys_dup3(oldfd: i64, newfd: i64, flags: i64) -> i64 {
1584 return __syscall(SYS_DUP3, oldfd, newfd, flags, 0, 0, 0)
1585}
1586
1587// ---- directory listing (Linux RV64 getdents64) ---------------
1588//
1589// Foundation for ls / glob / dir-walk helpers. Linux returns
1590// linux_dirent64 records:
1591// u64 d_ino (inode, ignored here)
1592// s64 d_off (next-record offset)
1593// u16 d_reclen (this record's byte length)
1594// u8 d_type (file type; DT_DIR=4, DT_REG=8, DT_LNK=10)
1595// char d_name[] (null-terminated name, padded so d_reclen
1596// carries us to the next record boundary)
1597// Total struct header: 19 bytes, then name up to d_reclen - 19.
1598
1599@ifdef TARGET_X86_64
1600const SYS_GETDENTS64: i64 = 217
1601@endif
1602@ifndef TARGET_X86_64
1603const SYS_GETDENTS64: i64 = 61
1604@endif
1605
1606const DT_UNKNOWN: i64 = 0
1607const DT_FIFO: i64 = 1
1608const DT_CHR: i64 = 2
1609const DT_DIR: i64 = 4
1610const DT_BLK: i64 = 6
1611const DT_REG: i64 = 8
1612const DT_LNK: i64 = 10
1613const DT_SOCK: i64 = 12
1614
1615// Raw syscall. Returns bytes written on success (0 = end-of-dir),
1616// or -errno on failure.
1617func sys_getdents64(fd: i64, buf: *u8, buf_len: i64) -> i64 {
1618 return __syscall(SYS_GETDENTS64, fd, buf, buf_len, 0, 0, 0)
1619}
1620
1621// Extract fields from a linux_dirent64 record. `rec` points at
1622// the start of the record; fields are at fixed offsets.
1623func dirent_reclen(rec: *u8) -> i64 {
1624 // d_reclen is u16 at offset 16. Read as two bytes little-endian.
1625 let lo: i64 = rec[16]
1626 let hi: i64 = rec[17]
1627 return lo | (hi << 8)
1628}
1629
1630func dirent_type(rec: *u8) -> i64 {
1631 return rec[18]
1632}
1633
1634// Pointer to the null-terminated name inside the record.
1635func dirent_name(rec: *u8) -> *u8 {
1636 let base: i64 = rec as i64
1637 return (base + 19) as *u8
1638}
1639
1640// ---- content-addressed file reader ---------------------------
1641
1642func sys_read_file(path: *u8, out_len: *i64) -> *u8 {
1643 let fd: i64 = sys_openat_rd(path)
1644 if fd < 0 {
1645 *out_len = 0
1646 return 0 as *u8
1647 }
1648 // DEBT-EATEN 2026-07-15: the old fixed 4 GiB cap SILENTLY TRUNCATED bigger files (a 9 GB gguf would
1649 // short-read into plausible-garbage tensors -- the worst failure class). Now the buffer is sized from
1650 // the file itself (lseek END), so ANY size reads fully. Physical pages still allocate on-demand. For
1651 // zero-copy any-size READ-ONLY access prefer sys_map_file (below).
1652 // DEBT-EATEN 2026-08-19 (1787076780): when the size is UNKNOWABLE (lseek END <= 0: /proc files, pipes
1653 // -- AND every empty regular file, which reports 0 just the same) this used to reserve
1654 // SYS_MAGIC_4294967296 of address space per call. Untouched pages were never resident, but the
1655 // mapping WAS: a daemon that read an empty registry every sweep ballooned its VmSize by 4 GiB per
1656 // read (measured: smoke instances at a 4.2 GB base), the leak screens flagged it, and sys_free_file
1657 // could only release what was read. The size-unknowable path now GROWS: start at SYS_READ_GROW_INIT,
1658 // double while the window fills, and hand back an EXACT mapping (total + 16) so sys_free_file
1659 // releases all of it. An empty file costs one small read and a 16-byte arena cell; /proc/stat fits
1660 // the first window; a pipe of any length still reads whole. The known-size path is unchanged.
1661 let fsz: i64 = sys_lseek(fd, 0, 2)
1662 sys_lseek(fd, 0, 0)
1663 var cap: i64 = SYS_READ_GROW_INIT
1664 var grow: i64 = 1
1665 if fsz > 0 { cap = fsz; grow = 0 }
1666 var buf: *u8 = sys_mmap(cap + 16)
1667 var total: i64 = 0
1668 var go: i64 = 1
1669 while go == 1 {
1670 let base: i64 = buf as i64
1671 let tail: *u8 = (base + total) as *u8
1672 let n: i64 = sys_read(fd, tail, cap - total)
1673 if n <= 0 { go = 0 }
1674 if n > 0 { total = total + n }
1675 if total >= cap {
1676 if grow == 0 { go = 0 } else {
1677 // the window filled and the size is unknown: double it, copy, release the old mapping
1678 let ncap: i64 = cap * 2
1679 let nb: *u8 = sys_mmap(ncap + 16)
1680 var ci: i64 = 0
1681 let obase: i64 = buf as i64
1682 let nbase: i64 = nb as i64
1683 while ci < total { let src: *u8 = (obase + ci) as *u8; let dst: *u8 = (nbase + ci) as *u8; dst[0] = src[0]; ci = ci + 1 }
1684 sys_munmap(buf, cap + 16)
1685 buf = nb
1686 cap = ncap
1687 }
1688 }
1689 }
1690 sys_close(fd)
1691 if grow == 1 {
1692 // hand back an EXACT mapping so the paired free releases everything (the doubled window would
1693 // otherwise leave its slack mapped forever -- the address-space leak this change exists to end)
1694 let xb: *u8 = sys_mmap(total + 16)
1695 var xi: i64 = 0
1696 let gbase: i64 = buf as i64
1697 let xbase: i64 = xb as i64
1698 while xi < total { let gsrc: *u8 = (gbase + xi) as *u8; let xdst: *u8 = (xbase + xi) as *u8; xdst[0] = gsrc[0]; xi = xi + 1 }
1699 sys_munmap(buf, cap + 16)
1700 buf = xb
1701 }
1702 // Null-terminate for the lexer.
1703 let bbase: i64 = buf as i64
1704 let term: *u8 = (bbase + total) as *u8
1705 term[0] = 0
1706 *out_len = total
1707 return buf
1708}
1709
1710// PAIRED FREE FOR sys_read_file (2026-08-17). sys_read_file mmaps `cap + 16` where cap is the FILE SIZE
1711// and returns only the pointer -- so any caller that frees it must know the padding, and a caller that
1712// unmaps `len` alone leaks the tail page whenever the file size sits just under a page boundary.
1713// ★A CALLER FORCED TO KNOW ITS ALLOCATOR'S PADDING IS A COUPLING THAT WILL DRIFT -- so the +16 lives
1714// HERE, beside the +16 it mirrors, instead of being retyped at every call site.
1715// Pass the length sys_read_file reported through out_len; this re-derives the mapping from it.
1716// Null-safe by construction: sys_read_file returns 0 on failure, so callers need no extra guard --
1717// ★A FREE THAT REFUSES NULL IS A FREE NOBODY HAS TO WRAP IN AN IF.
1718// EXACT for every path since 2026-08-19: the size-unknowable fallback (lseek <= 0: /proc, pipes, empty
1719// regular files) now returns a mapping of exactly total + 16, so this releases ALL of it. (It used to
1720// map SYS_MAGIC_4294967296 of address space and release only what was read -- stated then, ended now.)
1721// WHY IT EXISTS: nx_sites_daemon serves /wiki/roadmap by calling sys_read_file PER REQUEST inside a loop
1722// that runs up to NX_SD_MAX_REQ_PER_CONN (64) times per connection and never released it -- an 8,408 B
1723// file became 3 fresh pages and a fresh kernel VMA on every hit, held until the child exited.
1724func sys_free_file(buf: *u8, len: i64) -> i64 {
1725 if (buf as i64) == 0 { return 0 }
1726 if len < 0 { return 0 }
1727 return sys_munmap(buf, len + 16)
1728}
1729
1730// Read-only FILE-BACKED map of the whole file (PROT_READ=1, MAP_PRIVATE=2): any size, zero-copy -- only
1731// touched pages become resident (the lazy-MoE shape: a 9 GB model serves in ~active-set RSS, and load
1732// time is ~0 because nothing is copied). NO NUL pad (a file mapping cannot be extended) -- BINARY
1733// consumers only; text/lexer callers keep sys_read_file. Returns 0 on failure; *out_len = file size.
1734// Read-only by construction (PROT_READ; writes fault -- Rule 26-friendly).
1735func sys_map_file(path: *u8, out_len: *i64) -> *u8 {
1736 *out_len = 0
1737 let fd: i64 = sys_openat_rd(path)
1738 if fd < 0 { return 0 as *u8 }
1739 let fsz: i64 = sys_lseek(fd, 0, 2)
1740 if fsz <= 0 { sys_close(fd); return 0 as *u8 }
1741 let r: i64 = __syscall(SYS_MMAP, 0, fsz, 1, 2, fd, 0)
1742 sys_close(fd)
1743 if r <= 0 { return 0 as *u8 }
1744 *out_len = fsz
1745 return r as *u8
1746}
1747
1748// Sleep for `ms` milliseconds against CLOCK_MONOTONIC (relative).
1749// Returns 0 on success, negative errno on failure. Caller-supplied
1750// budget: ms <= 0 is a no-op; very large values are accepted as-is
1751// (the kernel will saturate to its own clamp). Defined at the bottom
1752// of this file so sys_mmap is in scope (single-pass parser).
1753func sys_sleep_ms(ms: i64) -> i64 {
1754 if ms <= 0 { return 0 }
1755 // struct timespec { sec: i64, nsec: i64 } -- 16 bytes RV64.
1756 let req: *u8 = sys_mmap(16)
1757 let rem: *u8 = sys_mmap(16)
1758 let secs: i64 = ms / 1000
1759 let nsec: i64 = (ms - secs * 1000) * SYS_MAGIC_1000000 // remainder ms -> ns
1760 let req_sec: *i64 = req as *i64
1761 let req_nsec: *i64 = ((req as i64) + 8) as *i64
1762 req_sec[0] = secs
1763 req_nsec[0] = nsec
1764 // clock_nanosleep(CLOCK_MONOTONIC=1, flags=0, req, rem). On EINTR (-4) a signal (e.g. SIGCHLD from a
1765 // reaped child) cut the sleep short and wrote the leftover into rem -- RESUME it, otherwise a caller
1766 // that uses the sleep as a timer (the torrent pool's 2s tick) gets spun into a busy loop by child
1767 // deaths and any tick-based budget collapses to milliseconds. A sleep must sleep its full duration.
1768 var r: i64 = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
1769 var guard: i64 = 0
1770 while r == (0 - 4) {
1771 if guard > SYS_MAGIC_100000 { r = 0 } else {
1772 let rs: *i64 = rem as *i64
1773 let rn: *i64 = ((rem as i64) + 8) as *i64
1774 req_sec[0] = rs[0]
1775 req_nsec[0] = rn[0]
1776 r = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
1777 guard = guard + 1
1778 }
1779 }
1780 sys_munmap(req, 16); sys_munmap(rem, 16) // FREE the timespec pages -- every call mmap'd 2 pages; in a
1781 // long-running poll loop (the supervisor's 15s tick) that leaked ~8KB/iter until mmap -> -12 -> SEGFAULT.
1782 return r
1783}
1784
1785// ---- sockets (RV64 generic syscall numbers) ----------------------
1786//
1787// Source uses RV64 numbers; the x86_64 backend's
1788// x86ctx_rv64_to_x86_64_syscall table translates at codegen time.
1789// Numbers from arch/arm64/include/asm/unistd.h (RV64 inherits the
1790// generic ABI).
1791
1792// Socket-family syscall numbers via @ifdef macro -- mirrors the
1793// pattern already used for SYS_READ/WRITE/MMAP/etc. above. Without
1794// this gate, --target x86_64 compiled the RV64 numbers as literals
1795// into the `syscall` instruction (e.g. 198 = sched_setaffinity on
1796// x86_64, not socket) and any daemon using sys_socket() died with
1797// ENOSYS before printing its banner -- caught by the nx_signaling
1798// stone S2 deploy on 2026-05-20 (see [[project-cross-isa-syscall-
1799// unification-gap-2026-05-20]]).
1800@ifdef TARGET_X86_64
1801const SYS_SOCKET: i64 = 41
1802const SYS_BIND: i64 = 49
1803const SYS_LISTEN: i64 = 50
1804const SYS_ACCEPT: i64 = 43
1805const SYS_CONNECT: i64 = 42
1806const SYS_SETSOCKOPT: i64 = 54
1807const SYS_SENDTO: i64 = 44
1808const SYS_RECVFROM: i64 = 45
1809const SYS_SHUTDOWN: i64 = 48
1810@endif
1811
1812@ifndef TARGET_X86_64
1813const SYS_SOCKET: i64 = 198
1814const SYS_BIND: i64 = 200
1815const SYS_LISTEN: i64 = 201
1816const SYS_ACCEPT: i64 = 202
1817const SYS_CONNECT: i64 = 203
1818const SYS_SETSOCKOPT: i64 = 208
1819const SYS_SENDTO: i64 = 206
1820const SYS_RECVFROM: i64 = 207
1821const SYS_SHUTDOWN: i64 = 210
1822@endif
1823
1824// Socket-option constants used by nx_http_server / nx_https_server.
1825const SOL_SOCKET: i64 = 1
1826const SO_REUSEADDR: i64 = 2
1827// Receive/send timeouts (Linux x86_64). optval is a struct timeval
1828// {tv_sec: i64, tv_usec: i64} (16 bytes). Essential on PUBLIC sockets:
1829// without them, a single silent/slow client hangs a blocking read
1830// forever -> trivial DoS on a single-threaded accept loop.
1831const SO_SNDTIMEO: i64 = 21
1832const SO_RCVTIMEO: i64 = 20
1833
1834// setsockopt(2) -- set a socket option. Defined BEFORE its first caller
1835// (sys_set_socket_timeout, below): NishiLang forbids forward references,
1836// so the definition must precede every use.
1837func sys_setsockopt(fd: i64, level: i64, optname: i64,
1838 optval: *u8, optlen: i64) -> i64 {
1839 return __syscall(SYS_SETSOCKOPT, fd, level, optname, optval, optlen, 0)
1840}
1841
1842// Set a receive+send timeout (in whole seconds) on a socket fd.
1843// tv is munmap'd before return (LEAK FIXED 2026-07-16): this is called once per PROBE by the daemon
1844// supervisor (35/cycle forever -> ~800MB VSZ/day) and once per CONNECTION by fork-per-connection daemons.
1845// The unfreed page-per-call ballooned VSZ until heuristic overcommit made fork() return -ENOMEM (the
1846// proven pid=-12 failure class) -- likely the historical VSZ pressure behind the vsz_watchdog.
1847func sys_set_socket_timeout(fd: i64, secs: i64) -> i64 {
1848 let tv: *i64 = (sys_mmap(16)) as *i64
1849 tv[0] = secs // tv_sec
1850 tv[1] = 0 // tv_usec
1851 sys_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv as *u8, 16)
1852 sys_setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv as *u8, 16)
1853 sys_munmap(tv as *u8, 16)
1854 return 0
1855}
1856
1857// alarm(2): deliver SIGALRM after `secs` seconds (0 cancels a pending alarm). No SIGALRM handler is installed, so
1858// the default action TERMINATES the process. Used as a per-request watchdog inside a forked request-child: a
1859// pathologically-slow page can then never hang the child forever (which would leak its buffers + pile up procs).
1860const SYS_ALARM: i64 = 37
1861func sys_alarm(secs: i64) -> i64 { return __syscall(SYS_ALARM, secs, 0, 0, 0, 0, 0) }
1862
1863const AF_INET: i64 = 2
1864const SOCK_STREAM: i64 = 1
1865const SOCK_DGRAM: i64 = 2
1866
1867func sys_socket(domain: i64, sock_type: i64, protocol: i64) -> i64 {
1868 return __syscall(SYS_SOCKET, domain, sock_type, protocol, 0, 0, 0)
1869}
1870// Pack an AF_INET any-address sockaddr_in (16 bytes) for `port` at `addr`.
1871// RESTORED INTO THE OWNER 2026-08-19: this lived in the old full nx_syscalls_x86_64.nx and was the
1872// one wrapper WITH LIVE CALLERS (nx_nishipages_serve, nx_udp) that the 2026-07-31 alias-stub
1873// consolidation dropped -- both lanes sat NAS-unbuildable ("I do not know the name") until the
1874// rebuild-drain surfaced them. Body verbatim from the old file, including its documented
1875// workaround: NO `as u8` casts on the byte stores -- the array-element-store already truncates
1876// when the lvalue is *u8, and casts on this path once tripped a codegen defect.
1877// (The old file's other two uncalled orphans, sys_pivot_root/sys_umount2, were left dead on a
1878// zero-caller full-tree grep -- restoring an uncalled wrapper is inventory, not capability.)
1879func sockaddr_in_init(addr: *u8, port: i64) -> i64 {
1880 addr[0] = 2 // AF_INET low byte
1881 addr[1] = 0
1882 // Port in network byte order (big-endian).
1883 let hi: i64 = (port >> 8) & 0xFF
1884 let lo: i64 = port & 0xFF
1885 addr[2] = hi
1886 addr[3] = lo
1887 addr[4] = 0
1888 addr[5] = 0
1889 addr[6] = 0
1890 addr[7] = 0
1891 addr[8] = 0
1892 addr[9] = 0
1893 addr[10] = 0
1894 addr[11] = 0
1895 addr[12] = 0
1896 addr[13] = 0
1897 addr[14] = 0
1898 addr[15] = 0
1899 return 0
1900}
1901
1902func sys_bind(fd: i64, addr: *u8, addr_len: i64) -> i64 {
1903 return __syscall(SYS_BIND, fd, addr, addr_len, 0, 0, 0)
1904}
1905func sys_listen(fd: i64, backlog: i64) -> i64 {
1906 return __syscall(SYS_LISTEN, fd, backlog, 0, 0, 0, 0)
1907}
1908// accept(2) -- accept the next pending connection on a listening socket.
1909// Single-arg form (kernel ignores NULL addr/addr_len writes). Existing
1910// nx_http_server callers use this signature; the 3-arg form is provided
1911// as sys_accept_with_addr for outliers needing peer address.
1912func sys_accept(fd: i64) -> i64 {
1913 return __syscall(SYS_ACCEPT, fd, 0, 0, 0, 0, 0)
1914}
1915func sys_accept_with_addr(fd: i64, addr: *u8, addr_len: *i64) -> i64 {
1916 return __syscall(SYS_ACCEPT, fd, addr, addr_len, 0, 0, 0)
1917}
1918// shutdown(2) -- half-close a socket. how: 0=RD, 1=WR, 2=RDWR.
1919func sys_shutdown(fd: i64, how: i64) -> i64 {
1920 return __syscall(SYS_SHUTDOWN, fd, how, 0, 0, 0, 0)
1921}
1922func sys_connect(fd: i64, addr: *u8, addr_len: i64) -> i64 {
1923 return __syscall(SYS_CONNECT, fd, addr, addr_len, 0, 0, 0)
1924}
1925func sys_sendto(fd: i64, buf: *u8, n: i64, flags: i64,
1926 dest_addr: *u8, addr_len: i64) -> i64 {
1927 return __syscall(SYS_SENDTO, fd, buf, n, flags, dest_addr, addr_len)
1928}
1929func sys_recvfrom(fd: i64, buf: *u8, n: i64, flags: i64,
1930 src_addr: *u8, addr_len: *i64) -> i64 {
1931 return __syscall(SYS_RECVFROM, fd, buf, n, flags, src_addr, addr_len)
1932}
1933
1934// ---- SCM_RIGHTS DESCRIPTOR PASSING (sendmsg/recvmsg over AF_UNIX) -----------------------------
1935// ADDED 2026-08-21 for /compare/trafficsafety TS1. Until now sys_sendmsg was ABSENT-PROVEN from the
1936// whole tree (corpus_complete=1), so the mechanism nginx, HAProxy and Envoy all use for hitless
1937// replacement -- MOVING the listening descriptor rather than re-binding it -- could not be written
1938// at all. SO_REUSEPORT co-binding is an ACCEPT-DISTRIBUTION primitive, NOT a handoff primitive:
1939// LWN documents that changing the set of listening sockets on a port drops connections during the
1940// three-way handshake, so co-binding proves two binders and can never prove zero drops.
1941//
1942// EVERY OFFSET BELOW IS MEASURED, NOT RECALLED. They were read out of the platform's own headers
1943// with offsetof/sizeof/CMSG_LEN compiled for x86_64:
1944// msghdr 56 = name 0 | namelen 8 (u32) | iov 16 | iovlen 24 | control 32 | controllen 40 | flags 48 (u32)
1945// iovec 16 = base 0 | len 8
1946// cmsghdr 16 = len 0 (u64) | level 8 (u32) | type 12 (u32), data at 16
1947// CMSG_LEN(4)=20 CMSG_SPACE(4)=24 sendmsg=46 recvmsg=47 socketpair=53
1948// AF_UNIX=1 SOL_SOCKET=1 SCM_RIGHTS=1 MSG_CMSG_CLOEXEC=1073741824
1949// A WRONG LAYOUT HERE DOES NOT FAIL LOUD. The syscall still returns a positive byte count and
1950// simply transfers no descriptor, which is why the gate for this proves the property by passing a
1951// REAL descriptor between two REAL processes and then USING it, never by reading a return code.
1952// x86_64 Linux numbers, DELIBERATELY UNGUARDED, and the reason is a measurement rather than a
1953// preference. The first draft of this block wrapped these three in the same
1954// @ifdef TARGET_X86_64 / @ifndef pair every other syscall number in this file uses. On an x86 build
1955// that made every call ENOSYS, and the probe that caught it printed why:
1956// CONSTS SYS_SENDMSG=211 SYS_RECVMSG=212 SYS_SOCKETPAIR=199 SYS_WRITE=64
1957// N sendmsg PLAIN via the CONST rc=-38 (211 is unassigned on x86_64)
1958// N2 sendmsg PLAIN via the LITERAL rc=1
1959// SYS_WRITE reading 64 is the tell and it is NOT MINE: the file's own original guarded block
1960// resolves to its RV64 branch when the constant is referenced, on a build whose sys_write plainly
1961// works. So a constant inside these guards is not reliably the value the guard appears to select.
1962// !! A GUARD THAT SILENTLY SELECTS THE OTHER TARGET'S NUMBER IS WORSE THAN NO GUARD: the call still
1963// compiles, still returns, and dispatches a DIFFERENT SYSCALL. Syscall 199 on x86_64 is
1964// fremovexattr, which is why socketpair appeared to answer EFAULT for every input including a NULL
1965// vector and an unsupported domain -- varying the ARGUMENTS can never reveal that the NUMBER is
1966// wrong, because every variant was equally wrong.
1967// => RV64 support for these three is an OPEN, NAMED requirement, blocked on that toolchain
1968// behaviour. It is left undone and stated rather than papered over with a guard measured not to
1969// work. The estate already keeps nx_syscalls_x86_64.nx as the explicit single-target mirror for
1970// exactly this class of problem.
1971const SYS_SENDMSG: i64 = 46
1972const SYS_RECVMSG: i64 = 47
1973const SYS_SOCKETPAIR: i64 = 53
1974const SCM_AF_UNIX: i64 = 1
1975const SCM_SOL_SOCKET: i64 = 1
1976const SCM_RIGHTS_TYPE: i64 = 1
1977const SCM_MSG_CMSG_CLOEXEC: i64 = 1073741824
1978const SCM_MSGHDR_BYTES: i64 = 56
1979const SCM_MSGHDR_OFF_IOV: i64 = 16
1980const SCM_MSGHDR_OFF_IOVLEN: i64 = 24
1981const SCM_MSGHDR_OFF_CTRL: i64 = 32
1982const SCM_MSGHDR_OFF_CTRLLEN: i64 = 40
1983const SCM_IOVEC_BYTES: i64 = 16
1984const SCM_IOVEC_OFF_BASE: i64 = 0
1985const SCM_IOVEC_OFF_LEN: i64 = 8
1986const SCM_CMSG_OFF_LEN: i64 = 0
1987const SCM_CMSG_OFF_LEVEL: i64 = 8
1988const SCM_CMSG_OFF_TYPE: i64 = 12
1989const SCM_CMSG_OFF_DATA: i64 = 16
1990const SCM_CMSG_LEN_1FD: i64 = 20
1991const SCM_CMSG_SPACE_1FD: i64 = 24
1992const SCM_IOV_COUNT_ONE: i64 = 1
1993const SCM_U32_BYTES: i64 = 4
1994const SCM_BYTE_RADIX: i64 = 256
1995const SCM_FDPAIR_BYTES: i64 = 8
1996// One real data byte travels with the ancillary data ON PURPOSE: a sendmsg carrying SCM_RIGHTS and
1997// NO ordinary payload is the classic silent no-transfer, and it returns 0 rather than an error.
1998const SCM_PAYLOAD_BYTES: i64 = 1
1999const SCM_PAYLOAD_BYTE: i64 = 70
2000// Distinguishable refusals, each naming WHICH conjunct failed -- a compound assertion that will not
2001// name its failing conjunct is a false-alarm generator. All are negative and all sit far outside the
2002// errno range, so no caller can confuse one with a kernel error or with a valid descriptor.
2003const SCM_ERR_NO_CMSG: i64 = 0 - 901
2004const SCM_ERR_CMSG_LEN: i64 = 0 - 902
2005const SCM_ERR_CMSG_LEVEL: i64 = 0 - 903
2006const SCM_ERR_CMSG_TYPE: i64 = 0 - 904
2007
2008func scm_zero(base: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { base[i] = 0; i = i + 1 } return 0 }
2009func scm_put_i64(base: *u8, off: i64, v: i64) -> i64 {
2010 let p: *i64 = ((base as i64) + off) as *i64
2011 p[0] = v
2012 return 0
2013}
2014func scm_get_i64(base: *u8, off: i64) -> i64 {
2015 let p: *i64 = ((base as i64) + off) as *i64
2016 return p[0]
2017}
2018// The two cmsg header fields and the descriptor slot itself are 4-byte ints, so they are packed and
2019// unpacked byte by byte in little-endian order. Radix arithmetic rather than bit shifts, matching
2020// sockaddr_in_init's documented style on this exact path.
2021func scm_put_u32(base: *u8, off: i64, v: i64) -> i64 {
2022 var i: i64 = 0
2023 var m: i64 = v
2024 while i < SCM_U32_BYTES {
2025 base[off + i] = m % SCM_BYTE_RADIX
2026 m = m / SCM_BYTE_RADIX
2027 i = i + 1
2028 }
2029 return 0
2030}
2031func scm_get_u32(base: *u8, off: i64) -> i64 {
2032 var v: i64 = 0
2033 var mult: i64 = 1
2034 var i: i64 = 0
2035 while i < SCM_U32_BYTES {
2036 v = v + (base[off + i] as i64) * mult
2037 mult = mult * SCM_BYTE_RADIX
2038 i = i + 1
2039 }
2040 return v
2041}
2042
2043func sys_sendmsg(fd: i64, msg: *u8, flags: i64) -> i64 {
2044 return __syscall(SYS_SENDMSG, fd, msg, flags, 0, 0, 0)
2045}
2046func sys_recvmsg(fd: i64, msg: *u8, flags: i64) -> i64 {
2047 return __syscall(SYS_RECVMSG, fd, msg, flags, 0, 0, 0)
2048}
2049// socketpair(2). sv receives TWO 4-byte descriptors, so it is a *u8 read with scm_get_u32 -- a
2050// single *i64 read would splice both descriptors into one number and the second would vanish.
2051// !! THIS NUMBER IS NOT REACHING socketpair, AND THE FIRST DIAGNOSIS OF THAT WAS WRONG.
2052// Measured 2026-08-21: every call returns -14 (EFAULT) -- with a valid pointer, with a NULL vector,
2053// and with an UNSUPPORTED DOMAIN alike. The first reading of that evidence was "the host refuses
2054// this call for every input", and it was REFUTED by measuring the emitted constants instead of the
2055// arguments. TARGET_X86_64 is hard-pinned UNDEFINED in this toolchain (see nx_syscalls_x86_64.nx
2056// and nx_tokenizer.nx), so the @ifndef branch is what compiles and the x86 backend TRANSLATES RV64
2057// syscall numbers at emit time. Under that translation 53 is RV64 fchmodat, whose SECOND argument
2058// is a path pointer -- and SOCK_STREAM==1 as a path pointer is exactly EFAULT, every time,
2059// regardless of the other arguments.
2060// * VARYING THE ARGUMENTS CAN NEVER REVEAL THAT THE SYSCALL NUMBER IS WRONG: every variant is
2061// equally wrong, so a set of controls that all agree reads as a confident finding about the host.
2062// The control that actually discriminated was PRINTING THE CONSTANT the binary emits.
2063// => The likely correct value here is the RV64 number 199, exactly as sendmsg/recvmsg above needed
2064// their own numbers rather than the guarded pair. That is NOT asserted: it is UNTESTED, and this
2065// comment says so rather than shipping a plausible number with a confident sentence.
2066// => NOTHING DEPENDS ON IT. The descriptor-passing lane uses a NAMED AF_UNIX rendezvous
2067// (sys_unix_listen + sys_unix_connect_fd below), which is proven end to end by nx_scm_rights_gate
2068// and is also what nginx, HAProxy and systemd actually use to move a listener between processes.
2069// socketpair was only ever the convenience.
2070func sys_socketpair(domain: i64, sock_type: i64, protocol: i64, sv: *u8) -> i64 {
2071 return __syscall(SYS_SOCKETPAIR, domain, sock_type, protocol, sv, 0, 0)
2072}
2073
2074// Bind+listen a NAMED AF_UNIX stream socket -- the accepting half of the rendezvous whose
2075// connecting half is nx_unix_connect. Returns the listening fd, or a negative errno.
2076// The caller owns the path: unlink it first (a stale node makes bind return EADDRINUSE) and unlink
2077// it after, because an AF_UNIX bind leaves a filesystem entry that outlives the process.
2078const SCM_SUN_PATH_OFF: i64 = 2 // sockaddr_un = [sa_family: u16][sun_path: 108]
2079const SCM_SUN_BYTES: i64 = 110
2080const SCM_SUN_PATH_MAX: i64 = 107
2081func sys_unix_listen(path: *u8, backlog: i64) -> i64 {
2082 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0)
2083 if fd < 0 { return fd }
2084 let sa: *u8 = sys_mmap(SCM_SUN_BYTES)
2085 var i: i64 = 0
2086 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 }
2087 sa[0] = SCM_AF_UNIX
2088 sa[1] = 0
2089 var p: i64 = 0
2090 while path[p] != (0 as u8) {
2091 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 }
2092 sa[SCM_SUN_PATH_OFF + p] = path[p]
2093 p = p + 1
2094 }
2095 let br: i64 = sys_bind(fd, sa, SCM_SUN_PATH_OFF + p + 1)
2096 if br < 0 { sys_close(fd); return br }
2097 let lr: i64 = sys_listen(fd, backlog)
2098 if lr < 0 { sys_close(fd); return lr }
2099 return fd
2100}
2101
2102// The CONNECTING half of the same rendezvous. Returns the connected fd or a negative errno.
2103// RESIDUAL NAMED RATHER THAN LEFT SILENT: nx_unix_socket.nx already carries an nx_unix_connect with
2104// this exact body. It is not composed here because that file also defines a main(), so importing it
2105// would inject a second main into every one of the 52 daemons that reach nx_http_server -- a
2106// resolution-by-definition-order hazard this tree has already been bitten by. The primitive belongs
2107// in the shim; the older standalone file should be reduced to a caller of this one, and that is a
2108// separate change to a file with its own consumers rather than something to fold in silently here.
2109func sys_unix_connect_fd(path: *u8) -> i64 {
2110 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0)
2111 if fd < 0 { return fd }
2112 let sa: *u8 = sys_mmap(SCM_SUN_BYTES)
2113 var i: i64 = 0
2114 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 }
2115 sa[0] = SCM_AF_UNIX
2116 sa[1] = 0
2117 var p: i64 = 0
2118 while path[p] != (0 as u8) {
2119 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 }
2120 sa[SCM_SUN_PATH_OFF + p] = path[p]
2121 p = p + 1
2122 }
2123 let cr: i64 = sys_connect(fd, sa, SCM_SUN_PATH_OFF + p + 1)
2124 if cr < 0 { sys_close(fd); return cr }
2125 return fd
2126}
2127
2128// Send ONE open descriptor over a connected AF_UNIX socket. Returns the sendmsg result: the number
2129// of ordinary data bytes sent (SCM_PAYLOAD_BYTES on success) or a negative errno. The descriptor
2130// itself is NOT closed here -- both ends legitimately hold it until the sender chooses to let go,
2131// and that overlap is the entire point: there must be no instant at which zero processes hold the
2132// listening socket.
2133func sys_send_fd(sock: i64, fd: i64) -> i64 {
2134 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES)
2135 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES)
2136 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD)
2137 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES)
2138 scm_zero(msg, SCM_MSGHDR_BYTES)
2139 scm_zero(cbuf, SCM_CMSG_SPACE_1FD)
2140 data[0] = SCM_PAYLOAD_BYTE
2141 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64)
2142 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES)
2143 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64)
2144 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE)
2145 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64)
2146 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD)
2147 scm_put_i64(cbuf, SCM_CMSG_OFF_LEN, SCM_CMSG_LEN_1FD)
2148 scm_put_u32(cbuf, SCM_CMSG_OFF_LEVEL, SCM_SOL_SOCKET)
2149 scm_put_u32(cbuf, SCM_CMSG_OFF_TYPE, SCM_RIGHTS_TYPE)
2150 scm_put_u32(cbuf, SCM_CMSG_OFF_DATA, fd)
2151 let r: i64 = sys_sendmsg(sock, msg, 0)
2152 sys_munmap(msg, SCM_MSGHDR_BYTES)
2153 sys_munmap(iov, SCM_IOVEC_BYTES)
2154 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD)
2155 sys_munmap(data, SCM_PAYLOAD_BYTES)
2156 return r
2157}
2158
2159// Receive ONE descriptor from a connected AF_UNIX socket. Returns the NEW descriptor number in this
2160// process (>= 0), a negative errno from recvmsg, or one of the SCM_ERR_* codes above.
2161// flags: 0, or SCM_MSG_CMSG_CLOEXEC so the arriving descriptor is not leaked into grandchildren --
2162// the estate has already lost a port for six days to exactly that inheritance (nx_cloexec_gate).
2163// THE VALIDATION IS THE WHOLE POINT. recvmsg happily returns a positive byte count having delivered
2164// no ancillary data at all, so the kernel's REWRITTEN msg_controllen is read back rather than the
2165// value we asked for, and each of the three cmsg header fields is checked separately so a failure
2166// says which one.
2167func sys_recv_fd(sock: i64, flags: i64) -> i64 {
2168 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES)
2169 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES)
2170 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD)
2171 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES)
2172 scm_zero(msg, SCM_MSGHDR_BYTES)
2173 scm_zero(cbuf, SCM_CMSG_SPACE_1FD)
2174 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64)
2175 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES)
2176 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64)
2177 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE)
2178 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64)
2179 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD)
2180 let r: i64 = sys_recvmsg(sock, msg, flags)
2181 var out: i64 = r
2182 if r >= 0 {
2183 out = SCM_ERR_NO_CMSG
2184 if scm_get_i64(msg, SCM_MSGHDR_OFF_CTRLLEN) >= SCM_CMSG_LEN_1FD {
2185 out = SCM_ERR_CMSG_LEN
2186 if scm_get_i64(cbuf, SCM_CMSG_OFF_LEN) == SCM_CMSG_LEN_1FD {
2187 out = SCM_ERR_CMSG_LEVEL
2188 if scm_get_u32(cbuf, SCM_CMSG_OFF_LEVEL) == SCM_SOL_SOCKET {
2189 out = SCM_ERR_CMSG_TYPE
2190 if scm_get_u32(cbuf, SCM_CMSG_OFF_TYPE) == SCM_RIGHTS_TYPE {
2191 out = scm_get_u32(cbuf, SCM_CMSG_OFF_DATA)
2192 }
2193 }
2194 }
2195 }
2196 }
2197 sys_munmap(msg, SCM_MSGHDR_BYTES)
2198 sys_munmap(iov, SCM_IOVEC_BYTES)
2199 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD)
2200 sys_munmap(data, SCM_PAYLOAD_BYTES)
2201 return out
2202}
2203
2204// nx_fio.nx -- canonical sovereign file operations: unlink (delete) + existence check. Importable (no main).
2205// Retires Remove-Item / rm. rename is already canonical (sys_renameat in nx_syscalls). unlinkat x86_64=263 is passed
2206// DIRECTLY (the fsync-74 / fstatat-262 / unlinkat-263 precedent: a raw x86_64 number not in the rv64->x86 swap table
2207// passes through untranslated). AT_FDCWD=-100, flags=0. Returns 0 on success, -errno on failure. license_tier: ORIGINAL
2208
2209// sha256.nx -- SHA-256 in pure NishiLang (Phase G9, FIPS 180-4).
2210//
2211// Canonical: this is the substrate-wide canonical SHA-256
2212// implementation per [[feedback-no-tool-proliferation-bit-level]].
2213// HMAC-SHA256 / HKDF-SHA256 / DRBG-SHA256 etc. compose THIS file's
2214// sha256 primitive; they're distinct primitives (different specs:
2215// FIPS 198-1 HMAC, RFC 5869 HKDF, NIST SP 800-90A DRBG) but all
2216// share THIS sha256 as their SHA-256 backbone. Re-implementing
2217// the SHA-256 K-table or round function inline is refused.
2218//
2219// license_tier: INDEPENDENT_REDERIVE
2220// genealogy_id: international-research-sources/nist/fips_180_4
2221//
2222// Used for: content-addressed build artifacts (F6), session tokens
2223// (rand.nx + sha256 = HMAC), TLS 1.3 handshake (G15), Git-style
2224// object addressing, reproducible-build attestation.
2225//
2226// Implementation follows FIPS 180-4 section 6.2 exactly -- no
2227// precomputed tables beyond the standard K[0..63] round constants.
2228// Pure i64 arithmetic; all 32-bit ops masked with 0xFFFFFFFF.
2229//
2230// API:
2231// sha256_init(*ctx) — reset a fresh Sha256 context
2232// sha256_update(*ctx, *u8 bytes, len) — feed input chunks
2233// sha256_final(*ctx, *u8 out32) — write 32-byte digest
2234// sha256_digest(*u8 bytes, len, *u8 out32) — one-shot convenience
2235//
2236// The context is ~128 bytes: 8 words of hash state + 64-byte partial
2237// block buffer + 8-byte length counter + an index. Caller allocates
2238// (stack or heap) and passes pointer.
2239//
2240// nx_safety_envelope: (schema: nishi-library/seeds/safety-critical-standards.toml)
2241// intended_use: "SHA-256 cryptographic hash -- HMAC + HKDF
2242// + content-addressed storage + digital
2243// signatures + Wheeler-DDC integrity chain"
2244// sil_target: SIL3 (integrity primitive; collision or
2245// preimage attack = signature forgery)
2246// asil_target: QM
2247// dal_target: DAL B
2248// iec_62304_class: B
2249// evidence: [no_floating_point, no_table_lookup,
2250// bit_equal_reproducible,
2251// FIPS_180-4_Sec_5_3_3_init_vector,
2252// NIST_CAVP_test_vectors_VERIFIED,
2253// constant_time_by_construction,
2254// license_tier_INDEPENDENT_REDERIVE]
2255// hazard_register: [bug-tape-length-extension-attack,
2256// bug-tape-implementation-skipping-final-block,
2257// bug-tape-state-not-cleared-after-use]
2258// residual_risk: "Length-extension attack applies to raw
2259// SHA-256. Callers MUST use HMAC-SHA-256
2260// (nx_hmac) for keyed scenarios; never raw
2261// SHA-256(key || msg). Substrate cannot
2262// enforce this from the hash primitive's
2263// boundary; it's a composition responsibility."
2264// verdict: NOT_YET_EVALUATED
2265
2266
2267// nx_bits.nx -- bit-manipulation primitives, dispatching to hardware
2268// intrinsics on supported backends with portable software fallbacks.
2269//
2270// Inspired by Hacker's Delight (Henry S. Warren Jr.) -- the canonical
2271// reference for bit-twiddling. Every soft path is BRANCHLESS or
2272// minimally-branched, FIXED-CYCLE, and CROSS-ARCH PORTABLE.
2273//
2274// Dispatch model:
2275// nx_bits_popcount64 / nx_bits_clz32 / nx_bits_ctz32 -> backend
2276// intrinsic on x86_64 (popcntq/bsrl+xor/bsfl) and rv64 with Zbb
2277// (cpop/clzw/ctzw). One machine instruction. Used by hot paths
2278// (sketches, hashing, bitmap iteration).
2279//
2280// nx_bits_popcount64_soft / nx_bits_clz32_soft / nx_bits_ctz32_soft
2281// -- pure-NishiLang SWAR + binary-search variants. Cross-arch
2282// portable to backends without bit-count opcodes. Used by paired
2283// correctness oracles and any caller targeting an exotic ISA.
2284//
2285// Substrate "get off C" trajectory: this module is pure NishiLang.
2286
2287// syscalls.nx -- alias stub.
2288//
2289// nx_syscalls.nx is the canonical syscall surface: same 15 funcs
2290// this file used to define (sys_write/read/mmap/openat/close/exit/
2291// brk/lseek/getpid/kill/fork/execve/wait4/clone/ioctl) plus 9
2292// additions for sockets and time (sys_socket/bind/listen/accept/
2293// connect/clock_gettime_mono/now_ms/sleep_ms/openat_append).
2294//
2295// Consumers using `import "syscalls.nx"` resolve via NishiLang's
2296// textual import splicing with path-dedup (runtime/import.nx I1):
2297// this stub splices nx_syscalls.nx once, and any other file that
2298// also imports "nx_syscalls.nx" directly gets deduped to the same
2299// canonical splice. No duplicate symbols. This fixed the
2300// nxasm_main.nx duplicate-symbol error on first link 2026-05-19.
2301//
2302// Deprecation: when every "syscalls.nx" consumer migrates to the
2303// nx_-prefixed name, delete this stub. bench/nx_import_closure.sh
2304// will catch any straggler before the build ships.
2305
2306
2307
2308
2309// === popcount FAST: dispatches to backend intrinsic ==============
2310
2311func nx_bits_popcount64(x: i64) -> i64 {
2312 return __popcnt64(x)
2313}
2314
2315func nx_bits_popcount32(x: i64) -> i64 {
2316 return __popcnt64(x & 0xFFFFFFFF)
2317}
2318
2319// === clz32 / ctz32 FAST: backend intrinsic =======================
2320// __clz32(0) and __ctz32(0) both return 32 on both backends (x86
2321// uses a tested fallback to set the result; rv64 Zbb returns 32 by
2322// spec) so the wrapper is a thin pass-through.
2323
2324func nx_bits_clz32(x: i64) -> i64 {
2325 return __clz32(x)
2326}
2327
2328func nx_bits_ctz32(x: i64) -> i64 {
2329 return __ctz32(x)
2330}
2331
2332// 64-bit clz / ctz: composed from two 32-bit intrinsics. Until the
2333// backend grows OP_CLZ64 / OP_CTZ64 these are still ~3-instruction
2334// hot paths vs the legacy 64-iteration loops, so they replace those
2335// substrate-wide. clz(0) = 64; ctz(0) = 64.
2336
2337func nx_bits_clz64(x: i64) -> i64 {
2338 let hi: i64 = (x >> 32) & 0xFFFFFFFF
2339 if hi != 0 { return __clz32(hi) }
2340 return 32 + __clz32(x & 0xFFFFFFFF)
2341}
2342
2343func nx_bits_ctz64(x: i64) -> i64 {
2344 let lo: i64 = x & 0xFFFFFFFF
2345 if lo != 0 { return __ctz32(lo) }
2346 if x == 0 { return 64 }
2347 return 32 + __ctz32((x >> 32) & 0xFFFFFFFF)
2348}
2349
2350// === rotate left / right FAST: backend intrinsic ================
2351// Hardware native via rolq/rorq (x86_64, 1985) and rol/ror (rv64
2352// Zbb). Mask the count to 0..63 so the substrate exposes
2353// "rotate-mod-64" semantics on both ISAs (x86_64 already masks; rv64
2354// behaviour is identical with the explicit mask).
2355
2356func nx_bits_rotl64(x: i64, n: i64) -> i64 {
2357 return __rotl64(x, n & 63)
2358}
2359
2360func nx_bits_rotr64(x: i64, n: i64) -> i64 {
2361 return __rotr64(x, n & 63)
2362}
2363
2364// 32-bit rotate (no native intrinsic emitted; we pre-mask the value
2365// to its low 32 bits so the i64 arithmetic shift right doesn't
2366// contaminate with sign bits, then mask the result back to 32 bits).
2367// ~3 ops vs 5-7 in inline rotr32/rotl32 callsites scattered across
2368// crypto modules (SHA-256, ChaCha20, MurmurHash, etc.).
2369
2370func nx_bits_rotl32(x: i64, n: i64) -> i64 {
2371 let v: i64 = x & 0xFFFFFFFF
2372 let nn: i64 = n & 31
2373 if nn == 0 { return v }
2374 return ((v << nn) | (v >> (32 - nn))) & 0xFFFFFFFF
2375}
2376
2377func nx_bits_rotr32(x: i64, n: i64) -> i64 {
2378 let v: i64 = x & 0xFFFFFFFF
2379 let nn: i64 = n & 31
2380 if nn == 0 { return v }
2381 return ((v >> nn) | (v << (32 - nn))) & 0xFFFFFFFF
2382}
2383
2384// === byte-reverse FAST: backend intrinsic =========================
2385// bswapq (x86_64, i486 1989+, universal) and rev8 (rv64 Zbb). 1
2386// cycle vs the 13-op SWAR phrasing. Used by every endian flip,
2387// every network/header parse, SHA-256 big-endian word loads.
2388
2389func nx_bits_bswap64(x: i64) -> i64 {
2390 return __bswap64(x)
2391}
2392
2393// 32-bit byte-reverse: mask to low 32 (zero-extends the i64), bswap
2394// the whole register -- the four low bytes get reversed into the top
2395// half -- then shift down to recover them. Mask after shift to
2396// discard the sign extension on inputs where bit 31 of the bswapped
2397// low half is set (which becomes bit 63 of the 64-bit register).
2398func nx_bits_bswap32(x: i64) -> i64 {
2399 return (__bswap64(x & 0xFFFFFFFF) >> 32) & 0xFFFFFFFF
2400}
2401
2402// === SOFT fallbacks: pure NishiLang, cross-arch portable =========
2403
2404func nx_bits_popcount64_soft(x: i64) -> i64 {
2405 var v: i64 = x
2406 v = v - ((v >> 1) & 0x5555555555555555)
2407 v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
2408 v = (v + (v >> 4)) & 0x0F0F0F0F0F0F0F0F
2409 return ((v * 0x0101010101010101) >> 56) & 0xFF
2410}
2411
2412func nx_bits_popcount32_soft(x: i64) -> i64 {
2413 var v: i64 = x & 0xFFFFFFFF
2414 v = v - ((v >> 1) & 0x55555555)
2415 v = (v & 0x33333333) + ((v >> 2) & 0x33333333)
2416 v = (v + (v >> 4)) & 0x0F0F0F0F
2417 return ((v * 0x01010101) >> 24) & 0xFF
2418}
2419
2420func nx_bits_clz32_soft(x: i64) -> i64 {
2421 let lo: i64 = x & 0xFFFFFFFF
2422 if lo == 0 { return 32 }
2423 var t: i64 = lo
2424 var n: i64 = 0
2425 if (t & 0xFFFF0000) == 0 { n = n + 16; t = t << 16; t = t & 0xFFFFFFFF }
2426 if (t & 0xFF000000) == 0 { n = n + 8; t = t << 8; t = t & 0xFFFFFFFF }
2427 if (t & 0xF0000000) == 0 { n = n + 4; t = t << 4; t = t & 0xFFFFFFFF }
2428 if (t & 0xC0000000) == 0 { n = n + 2; t = t << 2; t = t & 0xFFFFFFFF }
2429 if (t & 0x80000000) == 0 { n = n + 1 }
2430 return n
2431}
2432
2433// 64-bit rotate soft fallback (pure NishiLang -- shift+or, ~5 ops).
2434// Used by paired correctness oracle and by backends without rotate
2435// opcodes. Note: shifting by 0 is the identity; explicit branch
2436// avoids the undefined-behaviour case of `x >> 64` on some ISAs.
2437
2438// The signed >> arithmetic-shifts sign bits in for negative x, so the
2439// shifted-right half must be masked to the actual m / (64-m) low bits
2440// to discard the sign extension.
2441
2442func nx_bits_rotl64_soft(x: i64, n: i64) -> i64 {
2443 let m: i64 = n & 63
2444 if m == 0 { return x }
2445 let top: i64 = (x >> (64 - m)) & ((1 << m) - 1)
2446 return (x << m) | top
2447}
2448
2449func nx_bits_rotr64_soft(x: i64, n: i64) -> i64 {
2450 let m: i64 = n & 63
2451 if m == 0 { return x }
2452 let low: i64 = (x >> m) & ((1 << (64 - m)) - 1)
2453 return low | (x << (64 - m))
2454}
2455
2456// bswap SOFT (Hacker's Delight 7-1, 13-op SWAR). Used by paired
2457// oracle and exotic backends.
2458
2459func nx_bits_bswap64_soft(x: i64) -> i64 {
2460 var v: i64 = x
2461 v = ((v & 0x00FF00FF00FF00FF) << 8) | ((v >> 8) & 0x00FF00FF00FF00FF)
2462 v = ((v & 0x0000FFFF0000FFFF) << 16) | ((v >> 16) & 0x0000FFFF0000FFFF)
2463 v = ((v & 0x00000000FFFFFFFF) << 32) | ((v >> 32) & 0x00000000FFFFFFFF)
2464 return v
2465}
2466
2467func nx_bits_bswap32_soft(x: i64) -> i64 {
2468 let v: i64 = x & 0xFFFFFFFF
2469 let b0: i64 = (v >> 24) & 0xFF
2470 let b1: i64 = (v >> 16) & 0xFF
2471 let b2: i64 = (v >> 8) & 0xFF
2472 let b3: i64 = (v ) & 0xFF
2473 return (b3 << 24) | (b2 << 16) | (b1 << 8) | b0
2474}
2475
2476// 32-bit rotate SOFT (identical body to FAST; no separate intrinsic
2477// path) -- kept as the named-soft for the consolidation paired-oracle
2478// convention.
2479func nx_bits_rotl32_soft(x: i64, n: i64) -> i64 {
2480 return nx_bits_rotl32(x, n)
2481}
2482func nx_bits_rotr32_soft(x: i64, n: i64) -> i64 {
2483 return nx_bits_rotr32(x, n)
2484}
2485
2486// 64-bit soft fallbacks (Knuth TAOCP 4A linear-scan). O(64) iterations
2487// in the worst case; used by the paired oracle and by exotic backends.
2488
2489func nx_bits_clz64_soft(x: i64) -> i64 {
2490 if x == 0 { return 64 }
2491 var v: i64 = x
2492 var n: i64 = 0
2493 var mask: i64 = 0x8000000000000000
2494 var done: i64 = 0
2495 while done == 0 {
2496 if (v & mask) != 0 { done = 1 }
2497 if done == 0 {
2498 n = n + 1
2499 mask = mask >> 1
2500 if mask == 0 { done = 1 }
2501 }
2502 }
2503 return n
2504}
2505
2506func nx_bits_ctz64_soft(x: i64) -> i64 {
2507 if x == 0 { return 64 }
2508 var v: i64 = x
2509 var n: i64 = 0
2510 var done: i64 = 0
2511 while done == 0 {
2512 if (v & 1) != 0 { done = 1 }
2513 if done == 0 {
2514 n = n + 1
2515 v = v >> 1
2516 if n >= 64 { done = 1 }
2517 }
2518 }
2519 return n
2520}
2521
2522func nx_bits_ctz32_soft(x: i64) -> i64 {
2523 let lo: i64 = x & 0xFFFFFFFF
2524 if lo == 0 { return 32 }
2525 var t: i64 = lo
2526 var n: i64 = 0
2527 if (t & 0x0000FFFF) == 0 { n = n + 16; t = t >> 16 }
2528 if (t & 0x000000FF) == 0 { n = n + 8; t = t >> 8 }
2529 if (t & 0x0000000F) == 0 { n = n + 4; t = t >> 4 }
2530 if (t & 0x00000003) == 0 { n = n + 2; t = t >> 2 }
2531 if (t & 0x00000001) == 0 { n = n + 1 }
2532 return n
2533}
2534
2535// === isolate lowest set bit (Hacker's Delight 2-1) ================
2536//
2537// x & -x selects only the lowest 1-bit of x. Useful for iterating
2538// set bits in a bitmap (faster than testing each bit).
2539// for bitmap != 0:
2540// bit = nx_bits_lowest(bitmap)
2541// // process bit
2542// bitmap = bitmap ^ bit // clear it
2543
2544func nx_bits_lowest(x: i64) -> i64 {
2545 return x & (0 - x)
2546}
2547
2548// === reset lowest set bit (Hacker's Delight 2-1) ==================
2549//
2550// x & (x-1) clears the lowest 1-bit. When combined with popcount,
2551// gives O(popcount) bit-traversal loops -- faster than O(width)
2552// when the bitmap is sparse.
2553
2554func nx_bits_clear_lowest(x: i64) -> i64 {
2555 return x & (x - 1)
2556}
2557
2558// === is power of 2 (Hacker's Delight 2-1) =========================
2559//
2560// x > 0 AND (x & (x-1)) == 0. One subtract + one and + one compare.
2561
2562func nx_bits_is_pow2(x: i64) -> i64 {
2563 if x <= 0 { return 0 }
2564 if (x & (x - 1)) == 0 { return 1 }
2565 return 0
2566}
2567
2568// === next power of 2 (Hacker's Delight 3-2) =======================
2569//
2570// Round up to next power of 2. For x already pow2, returns x.
2571// For x = 0, returns 1. Standard "smear high bit" pattern.
2572
2573func nx_bits_next_pow2_32(x: i64) -> i64 {
2574 if x <= 1 { return 1 }
2575 var v: i64 = (x - 1) & 0xFFFFFFFF
2576 v = v | (v >> 1)
2577 v = v | (v >> 2)
2578 v = v | (v >> 4)
2579 v = v | (v >> 8)
2580 v = v | (v >> 16)
2581 return (v + 1) & 0xFFFFFFFF
2582}
2583
2584// === parity (Hacker's Delight 5-1) ================================
2585//
2586// Returns 1 if odd number of set bits, 0 if even. Two-and-XOR
2587// reduction, branchless.
2588
2589func nx_bits_parity64(x: i64) -> i64 {
2590 var v: i64 = x
2591 v = v ^ (v >> 32)
2592 v = v ^ (v >> 16)
2593 v = v ^ (v >> 8)
2594 v = v ^ (v >> 4)
2595 return (0x6996 >> (v & 15)) & 1
2596}
2597
2598// === floor(log2(x)) ===============================================
2599//
2600// Equivalent to (31 - clz(x)) for x > 0. Returns -1 for x <= 0.
2601
2602func nx_bits_floor_log2(x: i64) -> i64 {
2603 if x <= 0 { return -1 }
2604 if x <= 0xFFFFFFFF {
2605 return 31 - nx_bits_clz32(x)
2606 }
2607 // High 32 bits set: 32 + log2(x >> 32)
2608 return 63 - nx_bits_clz32(x >> 32)
2609}
2610
2611// === bit-field extract (BMI BEXTR semantics) ======================
2612//
2613// Extract `len` bits starting at `start` from x.
2614// Equivalent to (x >> start) & ((1 << len) - 1).
2615
2616func nx_bits_bextr(x: i64, start: i64, len: i64) -> i64 {
2617 if len <= 0 { return 0 }
2618 if len >= 64 { return x >> start }
2619 let mask: i64 = (1 << len) - 1
2620 return (x >> start) & mask
2621}
2622
2623const K_MAGIC_536870912: i64 = 536870912
2624
2625struct Sha256 {
2626 // Hash state H[0..7] as i64 (low 32 bits used).
2627 h0: i64, h1: i64, h2: i64, h3: i64,
2628 h4: i64, h5: i64, h6: i64, h7: i64,
2629
2630 // Legacy byte-packed block fields (retained for struct-size
2631 // compatibility; no longer the active buffer -- see bufptr below).
2632 b0: i64, b1: i64, b2: i64, b3: i64,
2633 b4: i64, b5: i64, b6: i64, b7: i64,
2634
2635 // Byte index into the 64-byte block (0..63) and total bits
2636 // processed (for final padding).
2637 idx: i64,
2638 bit_len: i64,
2639
2640 // Scratch buffers allocated ONCE per context in sha256_init (the perf rewrite,
2641 // 2026-06-10: kills the per-block mmap syscall + the per-round K if-chain +
2642 // the linear-scan byte access). ALL three are indexed only by PUBLIC counters
2643 // (byte position / round number 0..63 / schedule index) -- never by secret data --
2644 // so constant_time_by_construction is preserved (no secret-indexed table access).
2645 bufptr: i64, // -> 64-byte contiguous block buffer (O(1) byte access)
2646 kptr: i64, // -> 64 round constants, materialized once from sha256_k()
2647 wptr: i64, // -> 64-word message schedule, reused every block
2648
2649 // Hardware SHA-NI scratch (the perf path, 2026-07-02). Allocated once per ctx.
2650 // k32ptr -> 64 CONTIGUOUS i32 round constants (the SHA-NI intrinsic wants packed 32-bit
2651 // K, whereas kptr above is 64 i64 for the software loop). st8ptr -> 8 CONTIGUOUS i32
2652 // working state a..h, marshalled from h0..h7 around each __sha256_ni_block call. ni_ok
2653 // caches the CPUID SHA-feature probe (1=use hardware, 0=software fallback) so cpuid runs
2654 // once per hash, not once per block. All three are indexed only by PUBLIC counters ->
2655 // constant_time_by_construction is preserved.
2656 k32ptr: i64, // -> 64 i32 round constants (packed), for the SHA-NI intrinsic
2657 st8ptr: i64, // -> 8 i32 working state a..h, marshalled around __sha256_ni_block
2658 ni_ok: i64, // 1 = CPU has SHA-NI (cpuid(7,0):EBX bit-29) -> hardware compress; 0 = software
2659}
2660
2661// Mask utilities.
2662const M32: i64 = 0xFFFFFFFF
2663
2664// Delegated to nx_bits_rotr32. SHA-256 round does 6 rotates per
2665// word * 64 rounds = 384 rotates per block.
2666func rotr32(x: i64, n: i64) -> i64 {
2667 return nx_bits_rotr32(x, n)
2668}
2669
2670func shr32(x: i64, n: i64) -> i64 {
2671 return (x >> n) & M32
2672}
2673
2674// SHA-256 round constants K[0..63]. Standard cube-roots-of-primes.
2675// We encode as a simple index -> constant lookup; each returns the
2676// i64 with the 32-bit constant in the low bits.
2677func sha256_k(i: i64) -> i64 {
2678 if i == 0 { return 0x428a2f98 }
2679 if i == 1 { return 0x71374491 }
2680 if i == 2 { return 0xb5c0fbcf }
2681 if i == 3 { return 0xe9b5dba5 }
2682 if i == 4 { return 0x3956c25b }
2683 if i == 5 { return 0x59f111f1 }
2684 if i == 6 { return 0x923f82a4 }
2685 if i == 7 { return 0xab1c5ed5 }
2686 if i == 8 { return 0xd807aa98 }
2687 if i == 9 { return 0x12835b01 }
2688 if i == 10 { return 0x243185be }
2689 if i == 11 { return 0x550c7dc3 }
2690 if i == 12 { return 0x72be5d74 }
2691 if i == 13 { return 0x80deb1fe }
2692 if i == 14 { return 0x9bdc06a7 }
2693 if i == 15 { return 0xc19bf174 }
2694 if i == 16 { return 0xe49b69c1 }
2695 if i == 17 { return 0xefbe4786 }
2696 if i == 18 { return 0x0fc19dc6 }
2697 if i == 19 { return 0x240ca1cc }
2698 if i == 20 { return 0x2de92c6f }
2699 if i == 21 { return 0x4a7484aa }
2700 if i == 22 { return 0x5cb0a9dc }
2701 if i == 23 { return 0x76f988da }
2702 if i == 24 { return 0x983e5152 }
2703 if i == 25 { return 0xa831c66d }
2704 if i == 26 { return 0xb00327c8 }
2705 if i == 27 { return 0xbf597fc7 }
2706 if i == 28 { return 0xc6e00bf3 }
2707 if i == 29 { return 0xd5a79147 }
2708 if i == 30 { return 0x06ca6351 }
2709 if i == 31 { return 0x14292967 }
2710 if i == 32 { return 0x27b70a85 }
2711 if i == 33 { return 0x2e1b2138 }
2712 if i == 34 { return 0x4d2c6dfc }
2713 if i == 35 { return 0x53380d13 }
2714 if i == 36 { return 0x650a7354 }
2715 if i == 37 { return 0x766a0abb }
2716 if i == 38 { return 0x81c2c92e }
2717 if i == 39 { return 0x92722c85 }
2718 if i == 40 { return 0xa2bfe8a1 }
2719 if i == 41 { return 0xa81a664b }
2720 if i == 42 { return 0xc24b8b70 }
2721 if i == 43 { return 0xc76c51a3 }
2722 if i == 44 { return 0xd192e819 }
2723 if i == 45 { return 0xd6990624 }
2724 if i == 46 { return 0xf40e3585 }
2725 if i == 47 { return 0x106aa070 }
2726 if i == 48 { return 0x19a4c116 }
2727 if i == 49 { return 0x1e376c08 }
2728 if i == 50 { return 0x2748774c }
2729 if i == 51 { return 0x34b0bcb5 }
2730 if i == 52 { return 0x391c0cb3 }
2731 if i == 53 { return 0x4ed8aa4a }
2732 if i == 54 { return 0x5b9cca4f }
2733 if i == 55 { return 0x682e6ff3 }
2734 if i == 56 { return 0x748f82ee }
2735 if i == 57 { return 0x78a5636f }
2736 if i == 58 { return 0x84c87814 }
2737 if i == 59 { return 0x8cc70208 }
2738 if i == 60 { return 0x90befffa }
2739 if i == 61 { return 0xa4506ceb }
2740 if i == 62 { return 0xbef9a3f7 }
2741 if i == 63 { return 0xc67178f2 }
2742 return 0
2743}
2744
2745// Access byte n (0..63) of the current block buffer. O(1) -- the buffer is contiguous
2746// (was a linear scan over 8 byte-packed fields per access; n is a public position).
2747func blk_byte(c: *Sha256, n: i64) -> i64 {
2748 let p: *u8 = c.bufptr as *u8
2749 return p[n] as i64
2750}
2751
2752// Set byte n (0..63) of the current block buffer. O(1) contiguous store.
2753func blk_set_byte(c: *Sha256, n: i64, v: i64) -> i64 {
2754 let p: *u8 = c.bufptr as *u8
2755 p[n] = (v & 0xFF) as u8
2756 return 0
2757}
2758
2759// Pack bytes [4*i .. 4*i+4) of the current block into a 32-bit
2760// big-endian word (SHA-256 spec is big-endian).
2761func blk_word(c: *Sha256, i: i64) -> i64 {
2762 let off: i64 = i * 4
2763 let b0: i64 = blk_byte(c, off + 0)
2764 let b1: i64 = blk_byte(c, off + 1)
2765 let b2: i64 = blk_byte(c, off + 2)
2766 let b3: i64 = blk_byte(c, off + 3)
2767 return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) & M32
2768}
2769
2770// Hardware SHA-NI block compression: marshal the working state h0..h7 into the 8-word i32
2771// buffer, run one full SHA-256 block via the fused __sha256_ni_block intrinsic (which reads
2772// the 64 raw big-endian bytes at bufptr and the packed i32 K table), marshal the updated
2773// state back. Bit-identical to sha256_compress_sw (validated by nx_shani_block_probe against
2774// the software oracle for many blocks + the NIST KAT). ~hardware speed vs the ~40 MB/s soft path.
2775func sha256_compress_ni(c: *Sha256) -> i64 {
2776 let st: *i32 = c.st8ptr as *i32
2777 st[0] = (c.h0 & M32) as i32; st[1] = (c.h1 & M32) as i32
2778 st[2] = (c.h2 & M32) as i32; st[3] = (c.h3 & M32) as i32
2779 st[4] = (c.h4 & M32) as i32; st[5] = (c.h5 & M32) as i32
2780 st[6] = (c.h6 & M32) as i32; st[7] = (c.h7 & M32) as i32
2781 let _r: i64 = __sha256_ni_block(c.st8ptr as *u8, c.bufptr as *u8, c.k32ptr as *u8)
2782 c.h0 = (st[0] as i64) & M32; c.h1 = (st[1] as i64) & M32
2783 c.h2 = (st[2] as i64) & M32; c.h3 = (st[3] as i64) & M32
2784 c.h4 = (st[4] as i64) & M32; c.h5 = (st[5] as i64) & M32
2785 c.h6 = (st[6] as i64) & M32; c.h7 = (st[7] as i64) & M32
2786 return 0
2787}
2788
2789// MULTI-BLOCK SHA-NI (2026-07-02, organ-level, NO new intrinsic): marshal state -> i32 buffer ONCE,
2790// run __sha256_ni_block over `nblk` consecutive 64-byte blocks read DIRECTLY from `blocks` (state
2791// stays resident in st8ptr between calls), marshal back ONCE. Eliminates the per-block marshal AND
2792// the per-byte blk_set_byte buffering of the byte-at-a-time path -> the SHA-NI GB/s lever. Each
2793// __sha256_ni_block is the identical proven compression -> bit-identical to N separate compresses.
2794func sha256_compress_ni_blocks(c: *Sha256, blocks: i64, nblk: i64) -> i64 {
2795 let st: *i32 = c.st8ptr as *i32
2796 st[0] = (c.h0 & M32) as i32; st[1] = (c.h1 & M32) as i32
2797 st[2] = (c.h2 & M32) as i32; st[3] = (c.h3 & M32) as i32
2798 st[4] = (c.h4 & M32) as i32; st[5] = (c.h5 & M32) as i32
2799 st[6] = (c.h6 & M32) as i32; st[7] = (c.h7 & M32) as i32
2800 var b: i64 = 0
2801 while b < nblk {
2802 let blkp: i64 = blocks + b * 64
2803 let _r: i64 = __sha256_ni_block(c.st8ptr as *u8, blkp as *u8, c.k32ptr as *u8)
2804 b = b + 1
2805 }
2806 c.h0 = (st[0] as i64) & M32; c.h1 = (st[1] as i64) & M32
2807 c.h2 = (st[2] as i64) & M32; c.h3 = (st[3] as i64) & M32
2808 c.h4 = (st[4] as i64) & M32; c.h5 = (st[5] as i64) & M32
2809 c.h6 = (st[6] as i64) & M32; c.h7 = (st[7] as i64) & M32
2810 return 0
2811}
2812
2813// One compression function call: process the 64 bytes currently in the block buffer.
2814// Mutates c.h0..c.h7. Routes to hardware SHA-NI when the CPU supports it (probed once in
2815// sha256_init -> c.ni_ok); the pure-integer software path below stays the ORACLE/fallback.
2816func sha256_compress(c: *Sha256) -> i64 {
2817 if c.ni_ok == 1 { return sha256_compress_ni(c) }
2818 // Message schedule W[0..63] + round constants K[0..63] -- both per-ctx scratch
2819 // (allocated once in sha256_init), so no per-block mmap syscall and no K if-chain.
2820 let w: *i64 = c.wptr as *i64
2821 let k: *i64 = c.kptr as *i64
2822 var i: i64 = 0
2823 while i < 16 {
2824 w[i] = blk_word(c, i)
2825 i = i + 1
2826 }
2827 i = 16
2828 while i < 64 {
2829 let x15: i64 = w[i - 15]
2830 let x2: i64 = w[i - 2]
2831 // sigma0/sigma1 with the rotates inlined (was 4 rotr32 calls/iter -> pure arithmetic)
2832 let s0: i64 = (((x15 >> 7) | (x15 << 25)) ^ ((x15 >> 18) | (x15 << 14)) ^ (x15 >> 3)) & M32
2833 let s1: i64 = (((x2 >> 17) | (x2 << 15)) ^ ((x2 >> 19) | (x2 << 13)) ^ (x2 >> 10)) & M32
2834 w[i] = (w[i - 16] + s0 + w[i - 7] + s1) & M32
2835 i = i + 1
2836 }
2837 var a: i64 = c.h0
2838 var b: i64 = c.h1
2839 var cc: i64 = c.h2
2840 var d: i64 = c.h3
2841 var e: i64 = c.h4
2842 var ff: i64 = c.h5
2843 var g: i64 = c.h6
2844 var h: i64 = c.h7
2845 i = 0
2846 while i < 64 {
2847 // Sigma1(e), Sigma0(a) with rotates inlined (was 6 rotr32 calls/round)
2848 let S1: i64 = (((e >> 6) | (e << 26)) ^ ((e >> 11) | (e << 21)) ^ ((e >> 25) | (e << 7))) & M32
2849 let ch: i64 = ((e & ff) ^ ((e ^ M32) & g)) & M32
2850 let t1: i64 = (h + S1 + ch + k[i] + w[i]) & M32
2851 let S0: i64 = (((a >> 2) | (a << 30)) ^ ((a >> 13) | (a << 19)) ^ ((a >> 22) | (a << 10))) & M32
2852 let mj: i64 = ((a & b) ^ (a & cc) ^ (b & cc)) & M32
2853 let t2: i64 = (S0 + mj) & M32
2854 h = g
2855 g = ff
2856 ff = e
2857 e = (d + t1) & M32
2858 d = cc
2859 cc = b
2860 b = a
2861 a = (t1 + t2) & M32
2862 i = i + 1
2863 }
2864 c.h0 = (c.h0 + a) & M32
2865 c.h1 = (c.h1 + b) & M32
2866 c.h2 = (c.h2 + cc) & M32
2867 c.h3 = (c.h3 + d) & M32
2868 c.h4 = (c.h4 + e) & M32
2869 c.h5 = (c.h5 + ff) & M32
2870 c.h6 = (c.h6 + g) & M32
2871 c.h7 = (c.h7 + h) & M32
2872 return 0
2873}
2874
2875// Initialise state. H[0..7] values from FIPS 180-4 section 5.3.3
2876// (first 32 bits of fractional parts of square roots of first 8
2877// primes).
2878// Shared allocation-free initializer: all five scratch pointers are supplied by the owning path.
2879func sha256_seed_allocated(c: *Sha256) -> i64 {
2880 c.h0 = 0x6a09e667; c.h1 = 0xbb67ae85; c.h2 = 0x3c6ef372; c.h3 = 0xa54ff53a
2881 c.h4 = 0x510e527f; c.h5 = 0x9b05688c; c.h6 = 0x1f83d9ab; c.h7 = 0x5be0cd19
2882 // Per-ctx scratch, allocated once (amortized over every block of this hash):
2883 // Materialize the canonical K table once (sha256_k stays the single source of the
2884 // constants -- DRY; the if-chain now runs 64x per HASH, not 64x per BLOCK).
2885 let kp: *i64 = c.kptr as *i64
2886 var i: i64 = 0
2887 while i < 64 { kp[i] = sha256_k(i); i = i + 1 }
2888 let bp: *u8 = c.bufptr as *u8
2889 i = 0
2890 while i < 64 { bp[i] = 0 as u8; i = i + 1 }
2891 c.idx = 0
2892 c.bit_len = 0
2893
2894 // ---- Hardware SHA-NI setup (additive; software path is the oracle/fallback) ----
2895 // Packed i32 K table for the intrinsic + an 8-word i32 state marshalling buffer.
2896 let k32: *i32 = c.k32ptr as *i32
2897 i = 0
2898 while i < 64 { k32[i] = (sha256_k(i) & M32) as i32; i = i + 1 }
2899 // Probe CPU SHA support ONCE per context: cpuid(leaf=7, subleaf=0):EBX bit-29 = SHA.
2900 // 1<<29 = 0x20000000 = 536870912. Gate the compress path on this; a CPU without SHA-NI
2901 // transparently uses the software compression (byte-identical result, just slower).
2902 if (__cpuid_ebx(7, 0) & K_MAGIC_536870912) != 0 { c.ni_ok = 1 } else { c.ni_ok = 0 }
2903 return 0
2904}
2905
2906func sha256_init(c: *Sha256) -> i64 {
2907 c.bufptr = sys_mmap(64) as i64
2908 c.kptr = sys_mmap(64 * 8) as i64
2909 c.wptr = sys_mmap(64 * 8) as i64
2910 c.k32ptr = sys_mmap(64 * 4) as i64
2911 c.st8ptr = sys_mmap(8 * 4) as i64
2912 return sha256_seed_allocated(c)
2913}
2914
2915// Feed `n` bytes. Buffers partial blocks; compresses full blocks
2916// as soon as they fill.
2917func sha256_update(c: *Sha256, bytes: *u8, n: i64) -> i64 {
2918 var i: i64 = 0
2919 // BULK FAST PATH: when block-aligned (idx==0) and SHA-NI is available, process all full 64-byte
2920 // blocks straight from the input via the resident-state multi-block compress -- skipping both the
2921 // byte-at-a-time blk_set_byte buffering and the per-block state marshalling. Bit-identical.
2922 if c.idx == 0 {
2923 if c.ni_ok == 1 {
2924 let nblk: i64 = n / 64
2925 if nblk > 0 {
2926 sha256_compress_ni_blocks(c, (bytes as i64) + i, nblk)
2927 c.bit_len = c.bit_len + nblk * 512
2928 i = i + nblk * 64
2929 }
2930 }
2931 }
2932 while i < n {
2933 blk_set_byte(c, c.idx, bytes[i])
2934 c.idx = c.idx + 1
2935 c.bit_len = c.bit_len + 8
2936 if c.idx == 64 {
2937 sha256_compress(c)
2938 c.idx = 0
2939 }
2940 i = i + 1
2941 }
2942 return 0
2943}
2944
2945// Finalise: append 0x80, pad with zeros, append 8-byte bit length,
2946// then do one or two final compressions. Writes 32 bytes to `out`.
2947func sha256_final(c: *Sha256, out: *u8) -> i64 {
2948 // Remember total bit length before padding.
2949 let total_bits: i64 = c.bit_len
2950 // Append 0x80.
2951 blk_set_byte(c, c.idx, 0x80)
2952 c.idx = c.idx + 1
2953 // If not enough room for 8-byte length in this block, pad rest
2954 // with zeros + compress.
2955 if c.idx > 56 {
2956 while c.idx < 64 {
2957 blk_set_byte(c, c.idx, 0)
2958 c.idx = c.idx + 1
2959 }
2960 sha256_compress(c)
2961 c.idx = 0
2962 }
2963 // Pad zeros up to byte 56.
2964 while c.idx < 56 {
2965 blk_set_byte(c, c.idx, 0)
2966 c.idx = c.idx + 1
2967 }
2968 // Write 64-bit big-endian length in bytes 56..63.
2969 blk_set_byte(c, 56, (total_bits >> 56) & 0xFF)
2970 blk_set_byte(c, 57, (total_bits >> 48) & 0xFF)
2971 blk_set_byte(c, 58, (total_bits >> 40) & 0xFF)
2972 blk_set_byte(c, 59, (total_bits >> 32) & 0xFF)
2973 blk_set_byte(c, 60, (total_bits >> 24) & 0xFF)
2974 blk_set_byte(c, 61, (total_bits >> 16) & 0xFF)
2975 blk_set_byte(c, 62, (total_bits >> 8) & 0xFF)
2976 blk_set_byte(c, 63, total_bits & 0xFF)
2977 sha256_compress(c)
2978 // Emit H[0..7] as big-endian 4-byte words.
2979 out[0] = (c.h0 >> 24) & 0xFF
2980 out[1] = (c.h0 >> 16) & 0xFF
2981 out[2] = (c.h0 >> 8) & 0xFF
2982 out[3] = c.h0 & 0xFF
2983 out[4] = (c.h1 >> 24) & 0xFF
2984 out[5] = (c.h1 >> 16) & 0xFF
2985 out[6] = (c.h1 >> 8) & 0xFF
2986 out[7] = c.h1 & 0xFF
2987 out[8] = (c.h2 >> 24) & 0xFF
2988 out[9] = (c.h2 >> 16) & 0xFF
2989 out[10] = (c.h2 >> 8) & 0xFF
2990 out[11] = c.h2 & 0xFF
2991 out[12] = (c.h3 >> 24) & 0xFF
2992 out[13] = (c.h3 >> 16) & 0xFF
2993 out[14] = (c.h3 >> 8) & 0xFF
2994 out[15] = c.h3 & 0xFF
2995 out[16] = (c.h4 >> 24) & 0xFF
2996 out[17] = (c.h4 >> 16) & 0xFF
2997 out[18] = (c.h4 >> 8) & 0xFF
2998 out[19] = c.h4 & 0xFF
2999 out[20] = (c.h5 >> 24) & 0xFF
3000 out[21] = (c.h5 >> 16) & 0xFF
3001 out[22] = (c.h5 >> 8) & 0xFF
3002 out[23] = c.h5 & 0xFF
3003 out[24] = (c.h6 >> 24) & 0xFF
3004 out[25] = (c.h6 >> 16) & 0xFF
3005 out[26] = (c.h6 >> 8) & 0xFF
3006 out[27] = c.h6 & 0xFF
3007 out[28] = (c.h7 >> 24) & 0xFF
3008 out[29] = (c.h7 >> 16) & 0xFF
3009 out[30] = (c.h7 >> 8) & 0xFF
3010 out[31] = c.h7 & 0xFF
3011 return 0
3012}
3013
3014// One-shot: hash `n` bytes, write 32-byte digest to `out`.
3015// Release only scratch owned by this initialized context; the caller owns c.
3016// Reset pointers so explicit cleanup is safe to repeat after completion/failure.
3017func sha256_destroy(c: *Sha256) -> i64 {
3018 if c.bufptr!=0 { sys_munmap(c.bufptr as *u8,64);c.bufptr=0 }
3019 if c.kptr!=0 { sys_munmap(c.kptr as *u8,64*8);c.kptr=0 }
3020 if c.wptr!=0 { sys_munmap(c.wptr as *u8,64*8);c.wptr=0 }
3021 if c.k32ptr!=0 { sys_munmap(c.k32ptr as *u8,64*4);c.k32ptr=0 }
3022 if c.st8ptr!=0 { sys_munmap(c.st8ptr as *u8,8*4);c.st8ptr=0 }
3023 return 0
3024}
3025
3026func sha256_digest(bytes: *u8, n: i64, out: *u8) -> i64 {
3027 let ctx_raw: *u8 = sys_mmap(__size_of(Sha256))
3028 let ctx: *Sha256 = ctx_raw as *Sha256
3029 sha256_init(ctx)
3030 sha256_update(ctx, bytes, n)
3031 sha256_final(ctx, out)
3032 sha256_destroy(ctx)
3033 sys_munmap(ctx_raw,__size_of(Sha256))
3034 return 0
3035}
3036
3037// Native Linux x86-64 checked observation path using the existing shared allocator owner.
3038// No cross-backend portability claim: sys_munmap currently uses the native x86-64 release ABI.
3039// Synchronous caller-owned scratch: do not publish it or pass it to forked children.
3040const SHA256_WORD_ALIGN: i64 = 8
3041const SHA256_BLOCK_BYTES: i64 = 64
3042const SHA256_ROUND_WORDS: i64 = 64
3043const SHA256_WIDE_WORD: i64 = 8
3044const SHA256_PACKED_WORD: i64 = 4
3045const SHA256_STATE_WORDS: i64 = 8
3046const SHA256_DIGEST_BYTES: i64 = 32
3047const SHA256_SIGNED_MAX: i64 = 9223372036854775807
3048const SHA256_BITS_PER_BYTE: i64 = 8
3049const SHA256_E_INPUT: i64 = 0-1
3050const SHA256_E_WORKSPACE: i64 = 0-2
3051const SHA256_E_MAPPING: i64 = 0-3
3052const SHA256_E_RELEASE: i64 = 0-4
3053
3054func sha256_context_aligned_bytes() -> i64 {
3055 return ((__size_of(Sha256)+SHA256_WORD_ALIGN-1)/SHA256_WORD_ALIGN)*SHA256_WORD_ALIGN
3056}
3057func sha256_workspace_bytes() -> i64 {
3058 return sha256_context_aligned_bytes()+SHA256_BLOCK_BYTES+2*SHA256_ROUND_WORDS*SHA256_WIDE_WORD+SHA256_ROUND_WORDS*SHA256_PACKED_WORD+SHA256_STATE_WORDS*SHA256_PACKED_WORD
3059}
3060func sha256_checked_input(bytes: *u8, n: i64, out: *u8) -> i64 {
3061 if n < 0 || n > SHA256_SIGNED_MAX/SHA256_BITS_PER_BYTE { return 0 }
3062 let source: i64=bytes as i64; let target: i64=out as i64
3063 if source < 0 || (n > 0 && source == 0) || source > SHA256_SIGNED_MAX-n { return 0 }
3064 if target <= 0 || target > SHA256_SIGNED_MAX-SHA256_DIGEST_BYTES { return 0 }
3065 return 1
3066}
3067func sha256_ranges_overlap(a: i64, an: i64, b: i64, bn: i64) -> i64 {
3068 if an == 0 || bn == 0 { return 0 }; return a < b+bn && b < a+an
3069}
3070// Borrowed scratch. Never call sha256_destroy: the buffers share one allocation.
3071// No allocation/release occurs here. Refused boundary inputs leave output unchanged.
3072// Initialize caller-owned scratch for incremental update/final; never call destroy on it.
3073func sha256_init_workspace(workspace: *u8, capacity: i64) -> i64 {
3074 let base: i64=workspace as i64; let needed: i64=sha256_workspace_bytes()
3075 if base <= 0 || capacity < needed || base > SHA256_SIGNED_MAX-needed || base%SHA256_WORD_ALIGN != 0 { return SHA256_E_WORKSPACE }
3076 let ctx: *Sha256=workspace as *Sha256; var p: i64=base+sha256_context_aligned_bytes()
3077 ctx.bufptr=p; p=p+SHA256_BLOCK_BYTES
3078 ctx.kptr=p; p=p+SHA256_ROUND_WORDS*SHA256_WIDE_WORD
3079 ctx.wptr=p; p=p+SHA256_ROUND_WORDS*SHA256_WIDE_WORD
3080 ctx.k32ptr=p; p=p+SHA256_ROUND_WORDS*SHA256_PACKED_WORD
3081 ctx.st8ptr=p
3082 return sha256_seed_allocated(ctx)
3083}
3084func sha256_digest_workspace(bytes: *u8, n: i64, out: *u8, workspace: *u8, capacity: i64) -> i64 {
3085 if sha256_checked_input(bytes,n,out) != 1 { return SHA256_E_INPUT }
3086 let base: i64=workspace as i64; let needed: i64=sha256_workspace_bytes()
3087 if base <= 0 || capacity < needed || base > SHA256_SIGNED_MAX-needed || base%SHA256_WORD_ALIGN != 0 { return SHA256_E_WORKSPACE }
3088 if sha256_ranges_overlap(base,needed,bytes as i64,n) == 1 || sha256_ranges_overlap(base,needed,out as i64,SHA256_DIGEST_BYTES) == 1 { return SHA256_E_WORKSPACE }
3089 let initialized:i64=sha256_init_workspace(workspace,capacity)
3090 if initialized != 0 { return initialized }
3091 let ctx:*Sha256=workspace as *Sha256
3092 sha256_update(ctx,bytes,n); sha256_final(ctx,out)
3093 return 0
3094}
3095// Takes ownership of an actual whole sys_mmap_shared(workspace_bytes()) result.
3096// A failed mapping leaves output unchanged. A release failure may follow computed output;
3097// callers must accept output only on0. Never supply an arena pointer or undersized mapping.
3098func sha256_digest_mapping_native(bytes: *u8, n: i64, out: *u8, mapping: i64) -> i64 {
3099 if mapping <= 0 { return SHA256_E_MAPPING }
3100 let size: i64=sha256_workspace_bytes()
3101 let result: i64=sha256_digest_workspace(bytes,n,out,mapping as *u8,size)
3102 let released: i64=sys_munmap(mapping as *u8,size)
3103 if result != 0 { return result }
3104 if released != 0 { return SHA256_E_RELEASE }
3105 return 0
3106}
3107func sha256_digest_checked_native(bytes: *u8, n: i64, out: *u8) -> i64 {
3108 if sha256_checked_input(bytes,n,out) != 1 { return SHA256_E_INPUT }
3109 // The existing shared wrapper returns errno; sys_mmap's failure policy is fatal.
3110 let mapping: i64=sys_mmap_shared(sha256_workspace_bytes()) as i64
3111 return sha256_digest_mapping_native(bytes,n,out,mapping)
3112}
3113
3114
3115// Linux syscall ABI results, not admission or retry policy.
3116const FIO_EINTR: i64 = 0 - 4
3117const FIO_EIO: i64 = 0 - 5
3118const FIO_EINVAL: i64 = 0 - 22
3119const FIO_EEXIST: i64 = 0 - 17
3120const FIO_EBADMSG: i64 = 0 - 74 // Linux ABI: artifact digest mismatch.
3121
3122struct NxFileWriteResult {
3123 stage: *u8,
3124 code: i64,
3125 written: i64,
3126 close_code: i64
3127}
3128
3129// Owns fd until close. Preserve the first failure and the independent close
3130// result; never retry close because Linux may already have released the fd.
3131func fio_write_sync_fd(fd: i64, body: *u8, n: i64, result: *NxFileWriteResult) -> i64 {
3132 result.stage = "write" as *u8
3133 result.code = 0
3134 result.written = 0
3135 result.close_code = 0
3136 if n < 0 || ((body as i64) == 0 && n > 0) {
3137 result.stage = "input" as *u8
3138 result.code = FIO_EINVAL
3139 }
3140 while result.code == 0 && result.written < n {
3141 let w: i64 = sys_write(fd, body + result.written, n - result.written)
3142 if w == FIO_EINTR { continue }
3143 if w < 0 { result.code = w; break }
3144 if w == 0 { result.code = FIO_EIO; break }
3145 result.written = result.written + w
3146 }
3147 if result.code == 0 {
3148 result.stage = "fsync" as *u8
3149 var synced: i64 = sys_fsync(fd)
3150 while synced == FIO_EINTR { synced = sys_fsync(fd) }
3151 result.code = synced
3152 }
3153 result.close_code = sys_close(fd)
3154 if result.code == 0 {
3155 result.stage = "close" as *u8
3156 result.code = result.close_code
3157 }
3158 if result.code == 0 { result.stage = "complete" as *u8 }
3159 return result.code
3160}
3161
3162// Persist the directory entry after rename. A failure here occurs after the
3163// visible update: the caller must retain that publication state in its receipt.
3164func fio_sync_parent(path: *u8, result: *NxFileWriteResult) -> i64 {
3165 var length: i64=0
3166 var slash: i64=0-1
3167 while path[length]!=(0 as u8) { if path[length]==(47 as u8) { slash=length }; length=length+1 }
3168 let parent: *u8=sys_mmap(length+2)
3169 if slash<0 { parent[0]=46 as u8; parent[1]=0 as u8 }
3170 else {
3171 var end: i64=slash
3172 if end==0 { end=1 }
3173 var i: i64=0
3174 while i<end { parent[i]=path[i]; i=i+1 }
3175 parent[end]=0 as u8
3176 }
3177 result.stage="directory-open" as *u8
3178 result.close_code=0
3179 let fd: i64=sys_openat_directory(parent)
3180 sys_munmap(parent,length+2)
3181 if fd<0 { result.code=fd; return fd }
3182 result.stage="directory-fsync" as *u8
3183 var synced: i64=sys_fsync(fd)
3184 while synced==FIO_EINTR { synced=sys_fsync(fd) }
3185 result.close_code=sys_close(fd)
3186 result.code=synced
3187 if synced==0 { result.stage="directory-close" as *u8; result.code=result.close_code }
3188 if result.code==0 { result.stage="complete" as *u8 }
3189 return result.code
3190}
3191
3192// delete a file (unlinkat). Returns 0 on success.
3193func fio_unlink(path: *u8) -> i64 { return __syscall(263, 0 - 100, path as i64, 0, 0, 0, 0) }
3194
3195// 1 if `path` exists (fstatat succeeds), else 0.
3196func fio_exists(path: *u8) -> i64 {
3197 let st: *u8 = sys_mmap(160)
3198 if sys_fstatat(path, st) == 0 { return 1 }
3199 return 0
3200}
3201
3202// A caller-owned, single-use read session. Initialize once before open; never
3203// reinitialize an open session. Atomic pathname replacement does not change its fd.
3204struct NxFileReadRegion {
3205 fd: i64,
3206 total: i64,
3207 start: i64,
3208 length: i64,
3209 read_bytes: i64,
3210 last_read: i64,
3211 stage: *u8,
3212 code: i64,
3213 close_code: i64,
3214}
3215func fio_region_init(r: *NxFileReadRegion) -> i64 {
3216 r.fd=0-1;r.total=0;r.start=0;r.length=0;r.read_bytes=0;r.last_read=0
3217 r.stage="initialized";r.code=0;r.close_code=0
3218 return 0
3219}
3220// Linux close consumes ownership even when it reports an error; do not retry it.
3221func fio_region_close(r: *NxFileReadRegion) -> i64 {
3222 if r.fd>=0 {
3223 let fd: i64=r.fd;r.fd=0-1
3224 r.close_code=sys_close(fd)
3225 if r.code==0 && r.close_code<0 { r.code=r.close_code;r.stage="close" }
3226 }
3227 return r.code
3228}
3229func fio_region_fail(r: *NxFileReadRegion,stage: *u8,code: i64) -> i64 {
3230 r.stage=stage;r.code=code
3231 fio_region_close(r)
3232 return code
3233}
3234func fio_region_open(path: *u8,r: *NxFileReadRegion) -> i64 {
3235 if r.fd>=0 { return FIO_EEXIST }
3236 fio_region_init(r)
3237 if (path as i64)==0 { return fio_region_fail(r,"path",FIO_EINVAL) }
3238 if path[0]==(0 as u8) { return fio_region_fail(r,"path",FIO_EINVAL) }
3239 r.stage="open";r.fd=sys_openat_rd(path)
3240 if r.fd<0 { r.code=r.fd;return r.code }
3241 // SEEK_END/SEEK_SET are platform ABI selectors, not transfer-size policy.
3242 let size: i64=sys_lseek(r.fd,0,2)
3243 if size<0 { return fio_region_fail(r,"size-seek",size) }
3244 r.total=size;r.length=size
3245 let back: i64=sys_lseek(r.fd,0,0)
3246 if back!=0 { if back<0 { return fio_region_fail(r,"initial-seek",back) };return fio_region_fail(r,"initial-seek",FIO_EIO) }
3247 r.stage="ready";return 0
3248}
3249// Bounds use subtraction, so start+length can never wrap before validation.
3250func fio_region_select(r: *NxFileReadRegion,start: i64,length: i64) -> i64 {
3251 if r.fd<0 || r.code!=0 || r.read_bytes!=0 { return FIO_EINVAL }
3252 if start<0 || length<0 || start>r.total { return FIO_EINVAL }
3253 if length>r.total-start { return FIO_EINVAL }
3254 let at: i64=sys_lseek(r.fd,start,0)
3255 if at!=start { if at<0 { return fio_region_fail(r,"region-seek",at) };return fio_region_fail(r,"region-seek",FIO_EIO) }
3256 r.start=start;r.length=length;r.stage="ready";return 0
3257}
3258// The caller supplies its reusable transport buffer. No allocation depends on
3259// file size; each read is at most min(buffer capacity, remaining region bytes).
3260func fio_region_next(r: *NxFileReadRegion,out: *u8,cap: i64) -> i64 {
3261 r.last_read=0
3262 if r.code!=0 { return r.code }
3263 if r.fd<0 { if r.read_bytes==r.length { return 0 };return FIO_EINVAL }
3264 if cap<=0 || (out as i64)==0 { return FIO_EINVAL }
3265 let remaining: i64=r.length-r.read_bytes
3266 if remaining==0 { r.stage="complete";return fio_region_close(r) }
3267 var want: i64=remaining;if want>cap { want=cap }
3268 r.stage="read"
3269 while r.last_read<want {
3270 let got: i64=sys_read(r.fd,out+r.last_read,want-r.last_read)
3271 if got==FIO_EINTR { continue }
3272 if got<0 { return fio_region_fail(r,"read",got) }
3273 if got==0 { return fio_region_fail(r,"read-premature-eof",FIO_EIO) }
3274 r.last_read=r.last_read+got;r.read_bytes=r.read_bytes+got
3275 }
3276 if r.read_bytes==r.length {
3277 r.stage="complete"
3278 if fio_region_close(r)<0 { return r.code }
3279 } else { r.stage="ready" }
3280 return r.last_read
3281}
3282
3283
3284// Preparation never replaces a pathname. The caller owns an exclusive candidate
3285// path and keeps it for diagnosis on failure; publication is a separate operation.
3286struct NxFilePrepareResult {
3287 stage: *u8,
3288 code: i64,
3289 copied: i64,
3290 created: i64,
3291 source_close: i64,
3292 destination_close: i64,
3293 durable: i64,
3294}
3295func fio_prepare_copy(source: *u8,candidate: *u8,mode: i64,buffer: *u8,capacity: i64,out: *NxFilePrepareResult) -> i64 {
3296 out.stage="input";out.code=FIO_EINVAL;out.copied=0;out.created=0
3297 out.source_close=0;out.destination_close=0;out.durable=0
3298 if (source as i64)==0 || (candidate as i64)==0 || (buffer as i64)==0 || capacity<=0 { return out.code }
3299 if source[0]==(0 as u8) || candidate[0]==(0 as u8) || mode<0 || mode>0x1ff { return out.code }
3300 let input: *NxFileReadRegion=sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
3301 fio_region_init(input)
3302 out.code=fio_region_open(source,input);out.stage="source-open"
3303 var fd: i64=0-1
3304 if out.code==0 {
3305 out.stage="candidate-create"
3306 fd=sys_openat_exclusive(candidate,mode)
3307 if fd<0 { out.code=fd } else { out.created=1 }
3308 }
3309 while out.code==0 && input.read_bytes<input.length {
3310 let n: i64=fio_region_next(input,buffer,capacity)
3311 if n<0 { out.stage="source-read";out.code=n;break }
3312 var sent: i64=0
3313 out.stage="candidate-write"
3314 while sent<n {
3315 let w: i64=sys_write(fd,buffer+sent,n-sent)
3316 if w==FIO_EINTR { continue }
3317 if w<0 { out.code=w;break }
3318 if w==0 { out.code=FIO_EIO;break }
3319 sent=sent+w;out.copied=out.copied+w
3320 }
3321 }
3322 fio_region_close(input);out.source_close=input.close_code
3323 if out.code==0 && input.code!=0 { out.code=input.code;out.stage="source-close" }
3324 sys_munmap(input as *u8,__size_of(NxFileReadRegion))
3325 if out.code==0 {
3326 out.stage="candidate-mode"
3327 out.code=nx_chmod(candidate,mode)
3328 }
3329 if out.code==0 {
3330 out.stage="candidate-fsync";out.code=sys_fsync(fd)
3331 while out.code==FIO_EINTR { out.code=sys_fsync(fd) }
3332 }
3333 if fd>=0 {
3334 out.destination_close=sys_close(fd)
3335 if out.code==0 && out.destination_close!=0 { out.code=out.destination_close;out.stage="candidate-close" }
3336 }
3337 if out.code==0 {
3338 let sync: *NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
3339 out.code=fio_sync_parent(candidate,sync)
3340 if out.code!=0 { out.stage=sync.stage }
3341 sys_munmap(sync as *u8,__size_of(NxFileWriteResult))
3342 }
3343 if out.code==0 { out.stage="prepared";out.durable=1 }
3344 return out.code
3345}
3346
3347
3348struct NxFilePublishResult {
3349 stage: *u8,
3350 code: i64,
3351 visible: i64,
3352 durable: i64,
3353}
3354// Caller owns the prepared candidate and target's mutation lock. Rename failure
3355// leaves live intact; sync failure AFTER rename must retain visible=1.
3356func fio_publish_candidate(candidate: *u8,live: *u8,out: *NxFilePublishResult) -> i64 {
3357 out.stage="publish-input";out.code=FIO_EINVAL;out.visible=0;out.durable=0
3358 if (candidate as i64)==0 || (live as i64)==0 { return out.code }
3359 if candidate[0]==(0 as u8) || live[0]==(0 as u8) { return out.code }
3360 out.stage="publish-rename";out.code=sys_renameat(candidate,live)
3361 if out.code!=0 { return out.code }
3362 out.visible=1
3363 let sync: *NxFileWriteResult=sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
3364 out.stage="live-directory-sync";out.code=fio_sync_parent(live,sync)
3365 // Both directory entries change if preparation used another directory.
3366 if out.code==0 { out.stage="candidate-directory-sync";out.code=fio_sync_parent(candidate,sync) }
3367 sys_munmap(sync as *u8,__size_of(NxFileWriteResult))
3368 if out.code==0 { out.stage="published";out.durable=1 }
3369 return out.code
3370}
3371
3372
3373// Stable lock inode: never unlink the lockfile. All cooperating callers must use
3374// the same canonical live pathname in an estate-owned directory. This excludes
3375// arbitrary writers and pathname aliases from the guarantee.
3376struct NxFileTargetLock {
3377 fd: i64,
3378 stage: *u8,
3379 code: i64,
3380 unlock_code: i64,
3381 close_code: i64,
3382}
3383func fio_target_lock_init(lock: *NxFileTargetLock) -> i64 {
3384 lock.fd=0-1;lock.stage="not-started";lock.code=0;lock.unlock_code=0;lock.close_code=0
3385 return 0
3386}
3387func fio_target_lock_release(lock: *NxFileTargetLock) -> i64 {
3388 if lock.fd>=0 {
3389 let fd: i64=lock.fd;lock.fd=0-1
3390 lock.unlock_code=sys_flock(fd,SYS_LOCK_UN)
3391 lock.close_code=sys_close(fd)
3392 if lock.code==0 && lock.unlock_code!=0 { lock.code=lock.unlock_code;lock.stage="target-unlock" }
3393 if lock.code==0 && lock.close_code!=0 { lock.code=lock.close_code;lock.stage="target-lock-close" }
3394 if lock.code==0 { lock.stage="released" }
3395 }
3396 return lock.code
3397}
3398func fio_target_lock_acquire(live: *u8,lock: *NxFileTargetLock) -> i64 {
3399 if lock.fd>=0 { return FIO_EEXIST }
3400 fio_target_lock_init(lock)
3401 lock.stage="target-lock-input";lock.code=FIO_EINVAL
3402 if (live as i64)==0 { return lock.code }
3403 var n: i64=0;while live[n]!=(0 as u8) { n=n+1 }
3404 if n==0 { return lock.code }
3405 let suffix: *u8=".install.lock"
3406 var extra: i64=0;while suffix[extra]!=(0 as u8) { extra=extra+1 }
3407 let bytes: i64=n+extra+1
3408 if bytes<=n { return lock.code }
3409 let path: *u8=sys_mmap(bytes)
3410 if (path as i64)<0 { lock.stage="target-lock-allocation";lock.code=path as i64;return lock.code }
3411 var i: i64=0;while i<n { path[i]=live[i];i=i+1 }
3412 i=0;while i<extra { path[n+i]=suffix[i];i=i+1 };path[n+extra]=0 as u8
3413 lock.stage="target-lock-open";lock.fd=sys_openat_lock(path)
3414 sys_munmap(path,bytes)
3415 if lock.fd<0 { lock.code=lock.fd;return lock.code }
3416 lock.stage="target-lock-acquire";lock.code=sys_flock(lock.fd,SYS_LOCK_EX | SYS_LOCK_NB)
3417 if lock.code!=0 {
3418 let fd: i64=lock.fd;lock.fd=0-1;lock.close_code=sys_close(fd)
3419 return lock.code
3420 }
3421 lock.stage="held";return 0
3422}
3423struct NxFileReplaceResult {
3424 stage: *u8,
3425 code: i64,
3426 candidate: NxFilePrepareResult,
3427 backup: NxFilePrepareResult,
3428 publication: NxFilePublishResult,
3429 lock: NxFileTargetLock,
3430}
3431// Internal locked body. Source remains an immutable artifact; history paths
3432// are exclusive. The public replacement entry points acquire the target lock.
3433func fio_replace_owned(source: *u8,live: *u8,candidate: *u8,backup: *u8,mode: i64,buffer: *u8,capacity: i64,expected_candidate: *u8,expected_live: *u8,out: *NxFileReplaceResult) -> i64 {
3434 out.stage="prepare-candidate"
3435 out.backup.created=0;out.backup.durable=0;out.backup.copied=0;out.backup.code=0
3436 out.backup.stage="not-started";out.backup.source_close=0;out.backup.destination_close=0
3437 out.publication.stage="not-started";out.publication.code=0;out.publication.visible=0;out.publication.durable=0
3438 out.code=fio_prepare_copy(source,candidate,mode,buffer,capacity,&out.candidate)
3439 if out.code!=0 { return out.code }
3440 if (expected_candidate as i64)!=0 {
3441 out.stage="candidate-identity"
3442 out.code=fio_verify_sha256(candidate,expected_candidate,buffer,capacity)
3443 if out.code!=0 { return out.code }
3444 }
3445 out.stage="prepare-backup"
3446 out.code=fio_prepare_copy(live,backup,mode,buffer,capacity,&out.backup)
3447 if out.code!=0 { return out.code }
3448 if (expected_live as i64)!=0 {
3449 out.stage="live-identity"
3450 out.code=fio_verify_sha256(backup,expected_live,buffer,capacity)
3451 if out.code!=0 { return out.code }
3452 }
3453 out.stage="publish"
3454 out.code=fio_publish_candidate(candidate,live,&out.publication)
3455 if out.code==0 { out.stage="complete" }
3456 return out.code
3457}
3458
3459func fio_replace_init(out: *NxFileReplaceResult) -> i64 {
3460 let raw: *u8=out as *u8;var i: i64=0
3461 while i<__size_of(NxFileReplaceResult) { raw[i]=0 as u8;i=i+1 }
3462 out.stage="not-started";out.candidate.stage="not-started"
3463 out.backup.stage="not-started";out.publication.stage="not-started"
3464 fio_target_lock_init(&out.lock);return 0
3465}
3466func fio_replace_core(source: *u8,live: *u8,candidate: *u8,backup: *u8,mode: i64,buffer: *u8,capacity: i64,expected_candidate: *u8,expected_live: *u8,out: *NxFileReplaceResult) -> i64 {
3467 fio_replace_init(out)
3468 out.code=fio_target_lock_acquire(live,&out.lock)
3469 if out.code!=0 { out.stage=out.lock.stage;return out.code }
3470 fio_replace_owned(source,live,candidate,backup,mode,buffer,capacity,expected_candidate,expected_live,out)
3471 let released: i64=fio_target_lock_release(&out.lock)
3472 if out.code==0 && released!=0 { out.code=released;out.stage=out.lock.stage }
3473 return out.code
3474}
3475func fio_replace_with_backup(source: *u8,live: *u8,candidate: *u8,backup: *u8,mode: i64,buffer: *u8,capacity: i64,out: *NxFileReplaceResult) -> i64 {
3476 return fio_replace_core(source,live,candidate,backup,mode,buffer,capacity,0 as *u8,0 as *u8,out)
3477}
3478
3479func fio_replace_verified(source: *u8,live: *u8,candidate: *u8,backup: *u8,mode: i64,buffer: *u8,capacity: i64,expected_candidate: *u8,expected_live: *u8,out: *NxFileReplaceResult) -> i64 {
3480 if (expected_candidate as i64)==0 || (expected_live as i64)==0 {
3481 fio_replace_init(out)
3482 out.stage="identity-input";out.code=FIO_EINVAL
3483 out.candidate.stage="not-started";out.backup.stage="not-started";out.publication.stage="not-started"
3484 return out.code
3485 }
3486 return fio_replace_core(source,live,candidate,backup,mode,buffer,capacity,expected_candidate,expected_live,out)
3487}
3488
3489
3490// Digest comparison is over the prepared bytes, before any live replacement.
3491// Expected points to a SHA-256 digest (32 bytes), not a filename or size.
3492func fio_verify_sha256(path: *u8,expected: *u8,buffer: *u8,capacity: i64) -> i64 {
3493 if (expected as i64)==0 || (buffer as i64)==0 || capacity<=0 { return FIO_EINVAL }
3494 let input: *NxFileReadRegion=sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
3495 fio_region_init(input)
3496 var rc: i64=fio_region_open(path,input)
3497 let ctx: *Sha256=sys_mmap(__size_of(Sha256)) as *Sha256
3498 sha256_init(ctx)
3499 while rc==0 && input.read_bytes<input.length {
3500 let n: i64=fio_region_next(input,buffer,capacity)
3501 if n<0 { rc=n;break }
3502 sha256_update(ctx,buffer,n)
3503 }
3504 fio_region_close(input)
3505 if rc==0 { rc=input.code }
3506 if rc==0 {
3507 let actual: *u8=sys_mmap(32)
3508 sha256_final(ctx,actual)
3509 var i: i64=0;var differs: i64=0
3510 while i<32 { differs=differs | ((actual[i] as i64) ^ (expected[i] as i64));i=i+1 }
3511 if differs!=0 { rc=FIO_EBADMSG }
3512 sys_munmap(actual,32)
3513 }
3514 sha256_destroy(ctx);sys_munmap(ctx as *u8,__size_of(Sha256))
3515 sys_munmap(input as *u8,__size_of(NxFileReadRegion))
3516 return rc
3517}
3518
3519// nx_itoa_lib.nx -- THE shared integer->decimal emitter. ONE copy, so the corpus stops retyping it.
3520//
3521// LIFTED, NEVER COPIED (2026-07-31, debt 1785557603). ccz_cat_num was already correct, already
3522// MSB-first, already zero-allocation, and already had 10+ callers -- it was simply IMPRISONED inside
3523// nx_crashresume_census_core.nx, a crash-resume census organ. Seven files imported an entire census
3524// just to print an integer. That import cost, NOT ignorance of the primitive, is why ~87 sites
3525// hand-rolled their own. LAW: WHEN A CORRECT PRIMITIVE IS RETYPED, MEASURE ITS IMPORT COST BEFORE
3526// BLAMING DISCOVERABILITY -- people do not retype what is CHEAP to reach.
3527//
3528// THE LEAK WAS NEVER IN THE PRIMITIVE, IT WAS IN THE MISSING WRAPPER. ccz_cat_num allocates nothing.
3529// What every clone hand-rolled was the fd shim around it, e.g. nx_lock_reap_gate.g_putn:
3530// let b: *u8 = sys_mmap(32); let e: i64 = ccz_cat_num(b, 0, v); sys_write(1, b, e); return 0
3531// -- one mmap per call, never freed. nxi_fd below is that shim, written ONCE and always freeing.
3532//
3533// The census now imports THIS file; NishiLang import is transitive (verified: nx_lock_reap_gate
3534// imports only nx_syscalls + nx_lock_reap_core, and resolves ccz_cat_num through the core), so all
3535// existing callers keep resolving with no edit.
3536//
3537// LAYERING: lives in runtime/ so BOTH runtime/ and _hdl_build/ can import it.
3538// license_tier: ORIGINAL No hw writes (Rule 26).
3539
3540// MSB-FIRST (2026-07-31, debt 1785516350): the previous body built digits LEAST-significant first,
3541// which comes out BACKWARDS and therefore needed a sys_mmap(32) scratch buffer to reverse through --
3542// and never freed it, leaking a page per call across 12+ importers. Emitting MOST-significant first
3543// needs no buffer at all, so this now ALLOCATES NOTHING. Output bytes and the NUL-terminate contract
3544// are unchanged; this is a rewrite of the algorithm, not a sprinkled munmap (rule 3).
3545const CCZ_ASCII_0: i64 = 48
3546const CCZ_MINUS: i64 = 45
3547const CCZ_DEC: i64 = 10
3548func ccz_cat_num(buf: *u8, off: i64, v: i64) -> i64 {
3549 var o: i64 = off
3550 var m: i64 = v
3551 if m == 0 { buf[o] = CCZ_ASCII_0 as u8; o = o + 1; buf[o] = 0 as u8; return o }
3552 if m < 0 { buf[o] = CCZ_MINUS as u8; o = o + 1; m = 0 - m }
3553 // i64 MIN negates to itself and stays negative; clamp rather than loop forever on the digit walk.
3554 if m < 0 { m = 0 }
3555 var pw: i64 = 1
3556 while m / pw >= CCZ_DEC { pw = pw * CCZ_DEC }
3557 while pw > 0 {
3558 buf[o] = (CCZ_ASCII_0 + ((m / pw) % CCZ_DEC)) as u8
3559 o = o + 1
3560 pw = pw / CCZ_DEC
3561 }
3562 buf[o] = 0 as u8
3563 return o
3564}
3565
3566// max i64 is 19 digits + sign + the NUL ccz_cat_num writes; 24 leaves slack, well under one page.
3567const NXI_BUF: i64 = 24
3568const NXI_STDOUT: i64 = 1
3569const NXI_STDERR: i64 = 2
3570
3571// Write v as decimal to fd. ONE buffer, ALWAYS freed -- the balanced shape nx_mmapbal certifies.
3572// This is the drop-in for every hand-rolled putn/gn/wn/pn clone. Returns bytes written.
3573func nxi_fd(fd: i64, v: i64) -> i64 {
3574 let b: *u8 = sys_mmap(NXI_BUF)
3575 let n: i64 = ccz_cat_num(b, 0, v)
3576 sys_write(fd, b, n)
3577 sys_munmap(b, NXI_BUF)
3578 return n
3579}
3580
3581func nxi_out(v: i64) -> i64 { return nxi_fd(NXI_STDOUT, v) }
3582func nxi_err(v: i64) -> i64 { return nxi_fd(NXI_STDERR, v) }
3583
3584// NUL-FREE buffer form (2026-07-31). ccz_cat_num NUL-terminates -- it writes dst[ret]=0 -- which is
3585// right for its own callers but WRONG as a drop-in for the large clone family whose contract is
3586// "append digits, touch nothing else, return the new offset". Pointing those at ccz_cat_num would
3587// write one byte past the returned offset, and a clone that patches a number into the MIDDLE of an
3588// already-built buffer would have the next byte clobbered. nx_office_serve.of_catn alone has 60
3589// call sites, none of them audited for that.
3590// So the lib carries BOTH contracts explicitly rather than making every migrator guess:
3591// ccz_cat_num -> digits + NUL, returns the offset BEFORE the NUL
3592// nxi_buf -> digits only, returns the offset AFTER them, ZERO bytes touched beyond
3593// Both are MSB-first and allocate NOTHING. Constants are the CCZ_ ones lifted with ccz_cat_num.
3594func nxi_buf(dst: *u8, off: i64, v: i64) -> i64 {
3595 var p: i64 = off
3596 var m: i64 = v
3597 if m < 0 {
3598 dst[p] = CCZ_MINUS as u8
3599 p = p + 1
3600 m = 0 - m
3601 }
3602 // i64 MIN negates to ITSELF and stays negative. Clamp to 0 rather than looping forever or
3603 // emitting garbage -- a documented bound, never a silent wrong number.
3604 if m < 0 { m = 0 }
3605 var pw: i64 = 1
3606 while m / pw >= CCZ_DEC { pw = pw * CCZ_DEC }
3607 while pw > 0 {
3608 dst[p] = (CCZ_ASCII_0 + ((m / pw) % CCZ_DEC)) as u8
3609 p = p + 1
3610 pw = pw / CCZ_DEC
3611 }
3612 return p
3613}
3614
3615// nx_vsz_watchdog_core.nx -- importable CORE of the VSZ watchdog (the permanent fix for outage MODE 2:
3616// mmap-per-request daemons never munmap -> VSZ balloons (mgmt hit ~160GB) -> fork() fails -> child-exec
3617// SILENTLY EMPTY while /api/health stays 200; see reference-mgmt-api-outage-tmp-log-rootcause-2026-07-12).
3618// The watchdog DECIDES DEATH ONLY: it kills a conf-listed daemon whose VSZ crossed its threshold; RESPAWN
3619// stays 100% the nx_hostctl guard's job (single responsibility, no dueling supervisors). FAIL-SAFE BY
3620// CONSTRUCTION: no conf file -> INERT; unreadable /proc -> skip; cooldown suppresses kill-storms; pid<=300
3621// and self are never killed. Pure decision funcs here (gate-locked); the /proc walk + kill live in the CLI.
3622// license_tier: ORIGINAL
3623
3624
3625const VW_PROC_PATH_CAP: i64 = 256 // /proc/<pid>/status path buffer
3626const VW_STATUS_BUF: i64 = 8192 // /proc status read buffer
3627const VW_PTR_CELL: i64 = 16 // 2-i64 scratch cell (vw_num_at end-pointer out-param)
3628
3629func vw_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
3630
3631// substring containment (hostctl's hc_contains idiom; needle has no NUL so cmdline NUL separators are safe).
3632func vw_contains(hay: *u8, hn: i64, needle: *u8, nn: i64) -> i64 {
3633 if nn == 0 { return 0 }
3634 var i: i64 = 0
3635 while i + nn <= hn {
3636 var k: i64 = 0
3637 var ok: i64 = 1
3638 while k < nn { if hay[i+k] != needle[k] { ok = 0; k = nn } k = k + 1 }
3639 if ok == 1 { return 1 }
3640 i = i + 1
3641 }
3642 return 0
3643}
3644
3645// parse leading unsigned decimal from s[off..n): value, or -1 if no digit at off. end offset in endp[0].
3646func vw_num_at(s: *u8, n: i64, off: i64, endp: *i64) -> i64 {
3647 var v: i64 = 0
3648 var any: i64 = 0
3649 var i: i64 = off
3650 var go: i64 = 1
3651 while go == 1 {
3652 go = 0
3653 if i < n { let c: i64 = s[i] as i64; if c >= 48 { if c <= 57 { v = v*10 + (c-48); any = 1; i = i + 1; go = 1 } } }
3654 }
3655 endp[0] = i
3656 if any == 0 { return 0 - 1 }
3657 return v
3658}
3659
3660// parse one conf line buf[ls..le): "<needle> <max_gb>". Writes NUL after the needle IN PLACE, returns gb
3661// (>=1) with needle start in outp[0], or -1 for comment/blank/malformed (row dropped, fail-safe).
3662func vw_parse_row(buf: *u8, ls: i64, le: i64, outp: *i64) -> i64 {
3663 var i: i64 = ls
3664 var go: i64 = 1
3665 while go == 1 { go = 0; if i < le { let c: i64 = buf[i] as i64; if c == 32 { i = i + 1; go = 1 } else { if c == 9 { i = i + 1; go = 1 } } } }
3666 if i >= le { return 0 - 1 }
3667 if buf[i] == (35 as u8) { return 0 - 1 } // '#' comment
3668 let nstart: i64 = i
3669 go = 1
3670 while go == 1 { go = 0; if i < le { let c: i64 = buf[i] as i64; if c != 32 { if c != 9 { i = i + 1; go = 1 } } } }
3671 if i >= le { return 0 - 1 } // no separator -> malformed
3672 let nend: i64 = i
3673 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64
3674 var j: i64 = i
3675 go = 1
3676 while go == 1 { go = 0; if j < le { let c: i64 = buf[j] as i64; if c == 32 { j = j + 1; go = 1 } else { if c == 9 { j = j + 1; go = 1 } } } }
3677 let gb: i64 = vw_num_at(buf, le, j, ep)
3678 sys_munmap(ep as *u8, VW_PTR_CELL) // leak-free: ep (the end-ptr out-param) was leaked per row (the ep-out-param class my leak-checker flagged)
3679 if gb < 1 { return 0 - 1 } // gb<1 -> inert row (never a 0-threshold kill-everything)
3680 buf[nend] = 0 as u8 // NUL-terminate the needle in place
3681 outp[0] = nstart
3682 return gb
3683}
3684
3685// parse the kB value of an arbitrary "<Label>:" row out of a /proc status text. -1 absent/unreadable.
3686// Generalized so VmSize (address space) and VmRSS (resident -- the heap-leak meter VSZ can hide) share ONE
3687// parser (DRY; NEVER kill on parse failure).
3688func vw_status_kb(buf: *u8, n: i64, pat: *u8) -> i64 {
3689 let pl: i64 = vw_slen(pat)
3690 var i: i64 = 0
3691 while i + pl <= n {
3692 var k: i64 = 0
3693 var ok: i64 = 1
3694 while k < pl { if buf[i+k] != pat[k] { ok = 0; k = pl } k = k + 1 }
3695 if ok == 1 {
3696 var j: i64 = i + pl
3697 var go: i64 = 1
3698 while go == 1 { go = 0; if j < n { let c: i64 = buf[j] as i64; if c == 32 { j = j + 1; go = 1 } else { if c == 9 { j = j + 1; go = 1 } } } }
3699 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64
3700 let r: i64 = vw_num_at(buf, n, j, ep)
3701 sys_munmap(ep as *u8, VW_PTR_CELL) // leak-free: ep was mmap'd-and-leaked per call (the ep-out-param leak class)
3702 return r
3703 }
3704 i = i + 1
3705 }
3706 return 0 - 1
3707}
3708
3709// THE kill decision. 1 only when: threshold sane (gb>=1) AND vsz known (kb>0) AND over threshold AND the
3710// per-row cooldown expired. Everything else -> 0 (fail-safe).
3711func vw_should_kill(vsz_kb: i64, max_gb: i64, last_kill_s: i64, now_s: i64, cooldown_s: i64) -> i64 {
3712 if max_gb < 1 { return 0 }
3713 if vsz_kb <= 0 { return 0 }
3714 if vsz_kb <= max_gb * 1048576 { return 0 }
3715 if now_s - last_kill_s < cooldown_s { return 0 }
3716 return 1
3717}
3718
3719// bounded whole-file read. -1 absent.
3720func vw_read(path: *u8, buf: *u8, cap: i64) -> i64 {
3721 let fd: i64 = sys_openat_rd(path)
3722 if fd < 0 { return 0 - 1 }
3723 var tot: i64 = 0
3724 var n: i64 = sys_read(fd, buf, cap)
3725 while n > 0 { tot = tot + n; if tot >= cap { n = 0 } else { n = sys_read(fd, (buf as i64 + tot) as *u8, cap - tot) } }
3726 sys_close(fd)
3727 return tot
3728}
3729
3730// monotonic seconds (persists across one-shot runs within a boot -- exactly the cooldown scope we want).
3731func vw_now_s() -> i64 { let ts: *i64 = sys_mmap(16) as *i64; sys_clock_gettime_mono(ts); return ts[0] }
3732
3733// self pid via /proc/self/stat leading digits (no getpid syscall-number risk).
3734func vw_selfpid() -> i64 {
3735 let b: *u8 = sys_mmap(VW_PROC_PATH_CAP)
3736 let n: i64 = vw_read("/proc/self/stat" as *u8, b, VW_PROC_PATH_CAP - 1)
3737 if n <= 0 { sys_munmap(b, VW_PROC_PATH_CAP); return 0 - 1 }
3738 let ep: *i64 = sys_mmap(VW_PTR_CELL) as *i64
3739 let r: i64 = vw_num_at(b, n, 0, ep)
3740 sys_munmap(b, VW_PROC_PATH_CAP); sys_munmap(ep, VW_PTR_CELL) // leak-free (b + ep were leaked per call)
3741 return r
3742}
3743
3744// back-compat: the VmSize row (a sibling gate + vw_status_kb_of call this by name)
3745func vw_vmsize_parse(buf: *u8, n: i64) -> i64 { return vw_status_kb(buf, n, "VmSize:" as *u8) }
3746// kB of an arbitrary "<label>:" status row for a /proc entry named by DIRECTORY STRING (pid or "self").
3747// -1 unreadable. LEAK-FREE (munmaps path+b on every return). Generalized so VmSize (address space) and
3748// VmRSS (resident heap -- the leak class an arena hides from VSZ) share ONE reader.
3749func vw_status_kb_of(dirname: *u8, label: *u8) -> i64 {
3750 let path: *u8 = sys_mmap(VW_PROC_PATH_CAP)
3751 var o: i64 = 0
3752 let pre: *u8 = "/proc/" as *u8
3753 var a: i64 = 0
3754 while pre[a] != (0 as u8) { path[o] = pre[a]; o = o + 1; a = a + 1 }
3755 a = 0
3756 while dirname[a] != (0 as u8) { path[o] = dirname[a]; o = o + 1; a = a + 1 }
3757 let suf: *u8 = "/status" as *u8
3758 a = 0
3759 while suf[a] != (0 as u8) { path[o] = suf[a]; o = o + 1; a = a + 1 }
3760 path[o] = 0 as u8
3761 let b: *u8 = sys_mmap(VW_STATUS_BUF)
3762 let n: i64 = vw_read(path, b, VW_STATUS_BUF - 1)
3763 if n <= 0 { sys_munmap(path, VW_PROC_PATH_CAP); sys_munmap(b, VW_STATUS_BUF); return 0 - 1 }
3764 let r: i64 = vw_status_kb(b, n, label)
3765 sys_munmap(path, VW_PROC_PATH_CAP)
3766 sys_munmap(b, VW_STATUS_BUF)
3767 return r
3768}
3769// VmSize kB (back-compat; leak_check's memory meter). -1 unreadable.
3770func vw_vmsize_kb_of(dirname: *u8) -> i64 { return vw_status_kb_of(dirname, "VmSize:" as *u8) }
3771// VmRSS kB (resident set -- the heap-leak meter VmSize can hide in an arena).
3772func vw_rss_kb_of(dirname: *u8) -> i64 { return vw_status_kb_of(dirname, "VmRSS:" as *u8) }
3773
3774// nx_os_fs.nx -- OS FILESYSTEM-NAMESPACE SEAM (the write-safety half; sibling of nx_os_proc.nx).
3775// Answers ONE question for the IO layer: is this path in the OS's device/kernel/firmware namespace,
3776// where a file write could touch hardware or kernel state? Rule 26 (never-brick) demands the answer
3777// be BY CONSTRUCTION -- compiled in, not config-disableable -- so the deny lives here, in code, and
3778// callers cannot toggle it off with a conf line.
3779//
3780// LINUX BACKEND (current): the kernel exposes devices/firmware knobs as FILES under /dev, /sys, /proc
3781// (e.g. /sys/firmware/efi/efivars -- an errant write there can brick a board; /dev/sda -- raw disk).
3782// A path is write-forbidden iff it IS or is UNDER one of those roots.
3783//
3784// NISHIOS-NATIVE (target): NishiOS has no ambient device files -- device access is capability-routed
3785// through typed channels, so the ambient-namespace hazard class does not exist; the native backend
3786// returns forbid only for its reserved kernel-object namespace. This file is the SOURCE-SWAP seam
3787// (same contract, swapped backend), exactly like nx_os_proc.nx. license_tier: ORIGINAL
3788
3789
3790const OSF_SLASH: i64 = 47 // '/' -- path separator (namespace-boundary test)
3791
3792// is path EXACTLY root or UNDER root/ ? (blocks "/dev" and "/dev/null", not "/devdata")
3793func osf_under(path: *u8, root: *u8) -> i64 {
3794 var i: i64 = 0
3795 while root[i] != (0 as u8) {
3796 if path[i] != root[i] { return 0 }
3797 i = i + 1
3798 }
3799 if path[i] == (0 as u8) { return 1 } // exactly the root
3800 if path[i] == (OSF_SLASH as u8) { return 1 } // inside the root
3801 return 0
3802}
3803// WRITE-FORBIDDEN check: 1 = the OS device/kernel/firmware namespace, never writable through the IO layer.
3804func osf_write_forbidden(path: *u8) -> i64 {
3805 if osf_under(path, "/dev" as *u8) == 1 { return 1 }
3806 if osf_under(path, "/sys" as *u8) == 1 { return 1 }
3807 if osf_under(path, "/proc" as *u8) == 1 { return 1 }
3808 return 0
3809}
3810
3811// nx_os_proc.nx -- OS PROCESS-INTROSPECTION abstraction (the PORTABILITY SEAM). THE ONE place OS-specific
3812// process access lives, so every tool above it (nx_heal, ...) stays OS-AGNOSTIC + portable. Interop by
3813// construction: ONE source compiles to BOTH backends via the @ifdef target guard --
3814// LINUX backend = procfs (/proc/<pid>/{stat,cmdline}, getdents on /proc) [current NAS deploy]
3815// NISHIOS backend = native process table (@ifdef TARGET_NISHI seam) -- superior: a direct kernel
3816// query, NO text-parsing of /proc, NO USER_HZ guesswork. FAILS LOUD until wired,
3817// so a NishiOS build never silently inherits Linux assumptions.
3818// LAW (portability): NEVER scatter raw /proc, /sys, /dev, or hardcoded syscall numbers through the LOGIC
3819// layer -- put OS-specifics behind an nx_os_* seam like this one. The sovereign core (seg_store, tool
3820// logic) already only touches nx_syscalls (ABI-abstracted); this extends the same discipline to OS features.
3821// license_tier: ORIGINAL
3822
3823
3824const OSP_HZ_LINUX: i64 = 100 // Linux USER_HZ: /proc/<pid>/stat starttime ticks/sec
3825const OSP_PATH_CAP: i64 = 256
3826const OSP_RD_CAP: i64 = 4096
3827const OSP_STAT_CAP: i64 = 262144 // /proc/stat whole-file read cap (btime scan)
3828const OSP_DENT_BUF: i64 = 65536 // getdents64 batch buffer (proven sizing)
3829const OSP_F_PPID: i64 = 2 // /proc/<pid>/stat field after ')': state=1 ppid=2 ... utime=12 stime=13 ... starttime=20
3830const OSP_F_START: i64 = 20
3831const OSP_F_UTIME: i64 = 12 // user-mode CPU ticks (cumulative)
3832const OSP_F_STIME: i64 = 13 // kernel-mode CPU ticks (cumulative)
3833const OSP_ASCII_0: i64 = 48
3834const OSP_ASCII_9: i64 = 57
3835const OSP_SP: i64 = 32
3836const OSP_NL: i64 = 10
3837const OSP_RP: i64 = 41 // ')'
3838const OSP_SLASH: i64 = 47
3839const OSP_NUL: i64 = 0
3840const OSP_SENTINEL: i64 = 0 - 1 // "not available on this OS backend"
3841
3842func osp_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (OSP_NUL as u8) { n = n + 1 } return n }
3843// pid integer -> decimal string in out; return len
3844func osp_itoa(v: i64, out: *u8) -> i64 {
3845 if v == 0 { out[0] = OSP_ASCII_0 as u8; out[1] = 0 as u8; return 1 }
3846 let t: *u8 = sys_mmap(24); var m: i64 = v; var k: i64 = 0
3847 while m > 0 { t[k] = (OSP_ASCII_0 + (m % 10)) as u8; m = m / 10; k = k + 1 }
3848 var i: i64 = 0
3849 while i < k { out[i] = t[k-1-i]; i = i + 1 }
3850 out[k] = 0 as u8
3851 return k
3852}
3853// parse leading integer at buf[off..]; endp[0] = position of the FIRST non-digit (NOT n) so a caller
3854// walking fields can resume there. BUG-FIX 2026-07-16: the old `i = n` break jumped to end-of-buffer,
3855// so osp_stat_field skipped every field after the first non-numeric one (the state char) and never
3856// reached starttime (field 20) -- diagnose then dropped every real process.
3857func osp_num(buf: *u8, n: i64, off: i64, endp: *i64) -> i64 {
3858 var v: i64 = 0; var i: i64 = off; var any: i64 = 0; var go: i64 = 1
3859 while go == 1 {
3860 go = 0
3861 if i < n { let c: i64 = buf[i] as i64; if c >= OSP_ASCII_0 { if c <= OSP_ASCII_9 { v = v*(10 as i64)+(c-OSP_ASCII_0); any = 1; i = i + 1; go = 1 } } }
3862 }
3863 endp[0] = i
3864 if any == 0 { return OSP_SENTINEL }
3865 return v
3866}
3867// basename (after last '/') of NUL-terminated s
3868func osp_basename(s: *u8) -> *u8 {
3869 var i: i64 = 0; var last: i64 = 0
3870 while s[i] != (OSP_NUL as u8) { if s[i] == (OSP_SLASH as u8) { last = i + 1 } i = i + 1 }
3871 return (s as i64 + last) as *u8
3872}
3873
3874// ============================ LINUX BACKEND (procfs) ============================
3875// NOTE: the OS-target guard is currently a SOURCE-SWAP seam, not a compile-time @ifdef -- nx_cc's
3876// preprocessor is arch-guard-only today (TARGET_X86_64); a custom TARGET_NISHI compiled BOTH branches
3877// and the stub won (proven 2026-07-16). When nx_cc gains OS-target guards, wrap this in @ifdef
3878// TARGET_LINUX and the NishiOS backend (spec at bottom) in @ifdef TARGET_NISHI. The portability WIN
3879// stands regardless: every raw /proc lives HERE and nowhere else, so swapping the backend is a
3880// single-file change with the whole LOGIC layer (nx_heal) untouched.
3881// bounded read of a whole (small) proc/file into buf; returns len (0 on empty/absent)
3882func osp_bread(path: *u8, buf: *u8, cap: i64) -> i64 {
3883 let fd: i64 = sys_openat_rd(path)
3884 if fd < 0 { return 0 }
3885 var got: i64 = 0; var go: i64 = 1
3886 while go == 1 { let r: i64 = sys_read(fd, (buf as i64 + got) as *u8, cap - got); if r > 0 { got = got + r; if got >= cap { go = 0 } } else { go = 0 } }
3887 sys_close(fd)
3888 return got
3889}
3890// build "/proc/<pid><leaf>" (leaf NUL-terminated, e.g. "/stat") into out
3891func osp_ppath(out: *u8, pid: i64, leaf: *u8) -> i64 {
3892 var o: i64 = 0
3893 let pre: *u8 = "/proc/" as *u8
3894 var i: i64 = 0
3895 while pre[i] != (OSP_NUL as u8) { out[o] = pre[i]; o = o + 1; i = i + 1 }
3896 o = o + osp_itoa(pid, (out as i64 + o) as *u8)
3897 i = 0
3898 while leaf[i] != (OSP_NUL as u8) { out[o] = leaf[i]; o = o + 1; i = i + 1 }
3899 out[o] = 0 as u8
3900 return o
3901}
3902// enumerate live pids into pids[0..cap); returns count
3903func osp_list_pids(pids: *i64, cap: i64) -> i64 {
3904 let fd: i64 = sys_openat_rd("/proc" as *u8)
3905 if fd < 0 { return 0 }
3906 let dbuf: *u8 = sys_mmap(OSP_DENT_BUF)
3907 let ep: *i64 = sys_mmap(16) as *i64
3908 var cnt: i64 = 0; var run: i64 = 1
3909 while run == 1 {
3910 let n: i64 = sys_getdents64(fd, dbuf, OSP_DENT_BUF)
3911 if n <= 0 { run = 0 } else {
3912 var off: i64 = 0
3913 while off < n {
3914 let rec: *u8 = ((dbuf as i64 + off) as *u8)
3915 let reclen: i64 = dirent_reclen(rec)
3916 if reclen <= 0 { off = n } else {
3917 let name: *u8 = dirent_name(rec)
3918 if name[0] >= (OSP_ASCII_0 as u8) { if name[0] <= (OSP_ASCII_9 as u8) {
3919 if cnt < cap { let pid: i64 = osp_num(name, osp_slen(name), 0, ep); if pid > 0 { pids[cnt] = pid; cnt = cnt + 1 } }
3920 } }
3921 off = off + reclen
3922 }
3923 }
3924 }
3925 }
3926 sys_close(fd)
3927 return cnt
3928}
3929// count OPEN FILE DESCRIPTORS of pid = numeric entries in /proc/<pid>/fd. -1 if unreadable (gone/no perm).
3930// The fd METER for anomaly detection: a socket/file-descriptor leak trends up HERE while VmSize can stay
3931// flat (the fd table is not the address space) -- a leak shows in whatever resource disappears. Leak-free.
3932func osp_fd_count_from_fd_result(fd: i64, cause: *i64) -> i64 {
3933 if (cause as i64) != 0 { cause[0]=0 }
3934 let dbuf: *u8=sys_mmap(OSP_DENT_BUF)
3935 var count: i64=0
3936 var running: i64=1
3937 var failure: i64=0
3938 while running == 1 {
3939 let n: i64=sys_getdents64(fd,dbuf,OSP_DENT_BUF)
3940 if n < 0 { failure=n; running=0 } else {
3941 if n == 0 { running=0 } else {
3942 var off: i64=0
3943 while off < n {
3944 let rec: *u8=((dbuf as i64)+off) as *u8
3945 let reclen: i64=dirent_reclen(rec)
3946 if reclen <= 0 { failure=OSP_SENTINEL; running=0; break }
3947 let name: *u8=dirent_name(rec)
3948 if name[0] >= OSP_ASCII_0 as u8 && name[0] <= OSP_ASCII_9 as u8 { count=count+1 }
3949 off=off+reclen
3950 }
3951 }
3952 }
3953 }
3954 sys_munmap(dbuf,OSP_DENT_BUF)
3955 if failure < 0 { if (cause as i64) != 0 { cause[0]=failure }; return OSP_SENTINEL }
3956 return count
3957}
3958func osp_fd_count_from_fd(fd: i64) -> i64 {
3959 return osp_fd_count_from_fd_result(fd,0 as *i64)
3960}
3961func osp_fd_count(pid: i64) -> i64 {
3962 let path: *u8=sys_mmap(OSP_PATH_CAP)
3963 osp_ppath(path,pid,"/fd")
3964 let fd: i64=sys_openat_rd(path)
3965 sys_munmap(path,OSP_PATH_CAP)
3966 if fd < 0 { return OSP_SENTINEL }
3967 let count: i64=osp_fd_count_from_fd(fd)
3968 sys_close(fd)
3969 return count
3970}
3971
3972// field after the last ')' in /proc/<pid>/stat: OSP_F_PPID or OSP_F_START; SENTINEL on fail
3973func osp_stat_field(pid: i64, fidx: i64) -> i64 {
3974 let p: *u8 = sys_mmap(OSP_PATH_CAP)
3975 osp_ppath(p, pid, "/stat" as *u8)
3976 let b: *u8 = sys_mmap(OSP_RD_CAP)
3977 let n: i64 = osp_bread(p, b, OSP_RD_CAP - 1)
3978 if n <= 0 { return OSP_SENTINEL }
3979 var rp: i64 = 0 - 1; var i: i64 = 0
3980 while i < n { if b[i] == (OSP_RP as u8) { rp = i } i = i + 1 }
3981 if rp < 0 { return OSP_SENTINEL }
3982 var f: i64 = 0; i = rp + 1
3983 let ep: *i64 = sys_mmap(16) as *i64
3984 while i < n {
3985 if b[i] == (OSP_SP as u8) { i = i + 1 } else {
3986 f = f + 1
3987 let v: i64 = osp_num(b, n, i, ep)
3988 if f == fidx { return v }
3989 i = ep[0]
3990 var go: i64 = 1
3991 while go == 1 { go = 0; if i < n { if b[i] != (OSP_SP as u8) { i = i + 1; go = 1 } } }
3992 }
3993 }
3994 return OSP_SENTINEL
3995}
3996func osp_ppid(pid: i64) -> i64 { return osp_stat_field(pid, OSP_F_PPID) }
3997func osp_starttime_ticks(pid: i64) -> i64 { return osp_stat_field(pid, OSP_F_START) }
3998// cumulative CPU ticks consumed by pid = utime + stime. The CPU METER for anomaly detection: sampled over
3999// time, its RATE (Theil-Sen slope of the per-interval deltas) = the burn = "power disappearing" (a busy-loop
4000// pegs a core). SENTINEL if unreadable. HZ ticks/sec via osp_hz().
4001func osp_cpu_ticks(pid: i64) -> i64 {
4002 let u: i64 = osp_stat_field(pid, OSP_F_UTIME)
4003 let s: i64 = osp_stat_field(pid, OSP_F_STIME)
4004 if u == OSP_SENTINEL { return OSP_SENTINEL }
4005 if s == OSP_SENTINEL { return OSP_SENTINEL }
4006 return u + s
4007}
4008// argv0 basename of /proc/<pid>/cmdline into out; return len (0 if none)
4009func osp_cmd_argv0(pid: i64, out: *u8, cap: i64) -> i64 {
4010 let p: *u8 = sys_mmap(OSP_PATH_CAP)
4011 osp_ppath(p, pid, "/cmdline" as *u8)
4012 let cl: *u8 = sys_mmap(cap + 1)
4013 let n: i64 = osp_bread(p, cl, cap)
4014 if n <= 0 { out[0] = 0 as u8; return 0 }
4015 cl[n] = 0 as u8 // argv0 = bytes up to the first NUL (already there)
4016 let bn: *u8 = osp_basename(cl)
4017 var o: i64 = 0
4018 while bn[o] != (OSP_NUL as u8) { out[o] = bn[o]; o = o + 1 }
4019 out[o] = 0 as u8
4020 return o
4021}
4022func osp_hz() -> i64 { return OSP_HZ_LINUX }
4023// where the HOST SUPERVISOR writes its log -- a deployment/OS question, so it lives in the seam.
4024// Linux/NAS deploy: /tmp/supervisor.log (nx_hostctl supervise). NishiOS: its native supervisor journal.
4025func osp_supervisor_log() -> *u8 { return "/tmp/supervisor.log" as *u8 }
4026func osp_uptime_s() -> i64 { let ts: *i64 = sys_mmap(16) as *i64; sys_clock_gettime_mono(ts); return ts[0] }
4027func osp_selfpid() -> i64 {
4028 let b: *u8 = sys_mmap(OSP_RD_CAP)
4029 let n: i64 = osp_bread("/proc/self/stat" as *u8, b, OSP_RD_CAP - 1)
4030 if n <= 0 { return OSP_SENTINEL }
4031 let ep: *i64 = sys_mmap(16) as *i64
4032 return osp_num(b, n, 0, ep)
4033}
4034// wallclock epoch = /proc/stat btime + monotonic-since-boot
4035func osp_boot_epoch() -> i64 {
4036 let b: *u8 = sys_mmap(OSP_STAT_CAP)
4037 let n: i64 = osp_bread("/proc/stat" as *u8, b, OSP_STAT_CAP - 1)
4038 if n <= 0 { return 0 }
4039 let needle: *u8 = "btime " as *u8
4040 var i: i64 = 0
4041 let ep: *i64 = sys_mmap(16) as *i64
4042 while i < n {
4043 var m: i64 = 1; var k: i64 = 0
4044 while needle[k] != (OSP_NUL as u8) { if i+k >= n { m = 0 } else { if b[i+k] != needle[k] { m = 0 } } k = k + 1 }
4045 if m == 1 { return osp_num(b, n, i + k, ep) }
4046 i = i + 1
4047 }
4048 return 0
4049}
4050
4051// ======================= NISHIOS BACKEND SPEC (the swap-in seam) =======================
4052// When NishiOS's native process API lands, REPLACE the Linux backend above (or @ifdef-branch it once
4053// nx_cc has OS-target guards) with these ~8 functions over NishiOS's DIRECT kernel process table --
4054// superior to procfs: no /proc text-parsing, no USER_HZ, a real syscall query. The interface the LOGIC
4055// layer depends on (and ALL it depends on) is exactly:
4056// osp_list_pids(pids,cap)->count nishi_proc_enumerate (live pids)
4057// osp_ppid(pid)->ppid nishi_proc_parent
4058// osp_starttime_ticks(pid)->ticks nishi_proc_starttime (osp_hz() ticks/sec)
4059// osp_cmd_argv0(pid,out,cap)->len nishi_proc_argv0 (basename of argv0)
4060// osp_hz()->ticks_per_sec native rate (not the Linux-100 assumption)
4061// osp_uptime_s()->secs sys_clock_gettime_mono (already OS-neutral)
4062// osp_selfpid()->pid nishi_getpid
4063// osp_boot_epoch()->epoch nishi_boot_epoch
4064// Interop: NishiOS ships the superior backend; Linux stays supported for the current NAS deploy; the
4065// LOGIC (nx_heal) compiles unchanged on both. THAT is the point of this file.
4066
4067// Direct-child enumeration does not depend on CONFIG_CHECKPOINT_RESTORE's
4068// optional /proc/<pid>/task/<pid>/children file. The caller owns the output
4069// capacity; overflow is an error, never a plausible partial child set.
4070const OSP_DIRENT_NAME_OFFSET: i64 = 19 // Linux linux_dirent64 ABI
4071func osp_stat_parent(buf: *u8, n: i64) -> i64 {
4072 var last: i64=0-1;var i: i64=0
4073 while i < n { if buf[i] == OSP_RP as u8 { last=i };i=i+1 }
4074 if last < 0 { return 0-5 }
4075 i=last+1
4076 while i < n && buf[i] == OSP_SP as u8 { i=i+1 }
4077 while i < n && buf[i] != OSP_SP as u8 { i=i+1 }
4078 while i < n && buf[i] == OSP_SP as u8 { i=i+1 }
4079 let first: i64=i;var parent: i64=0
4080 while i < n && buf[i] >= OSP_ASCII_0 as u8 && buf[i] <= OSP_ASCII_9 as u8 {
4081 parent=parent*10+(buf[i] as i64)-OSP_ASCII_0;i=i+1
4082 }
4083 if i == first || i == n || buf[i] != OSP_SP as u8 { return 0-5 }
4084 return parent
4085}
4086func osp_children(parent: i64, children: *i64, capacity: i64) -> i64 {
4087 if parent <= 0 || capacity < 0 { return 0-22 }
4088 let directory: i64=sys_openat_directory("/proc")
4089 if directory < 0 { return directory }
4090 let batch: *u8=sys_mmap(OSP_DENT_BUF)
4091 let path: *u8=sys_mmap(OSP_PATH_CAP)
4092 let stat: *u8=sys_mmap(OSP_RD_CAP)
4093 var count: i64=0;var failure: i64=0;var running: i64=1
4094 while running == 1 {
4095 let n: i64=sys_getdents64(directory,batch,OSP_DENT_BUF)
4096 if n == (0-4) { continue }
4097 if n <= 0 { failure=n;break }
4098 var off: i64=0
4099 while off < n {
4100 if n-off <= OSP_DIRENT_NAME_OFFSET { failure=0-5;running=0;break }
4101 let rec: *u8=batch+off
4102 let size: i64=dirent_reclen(rec)
4103 if size <= OSP_DIRENT_NAME_OFFSET || size > n-off { failure=0-5;running=0;break }
4104 var i: i64=OSP_DIRENT_NAME_OFFSET;var pid: i64=0
4105 while i < size && rec[i] >= OSP_ASCII_0 as u8 && rec[i] <= OSP_ASCII_9 as u8 {
4106 pid=pid*10+(rec[i] as i64)-OSP_ASCII_0;i=i+1
4107 }
4108 if pid > 0 && i < size && rec[i] == OSP_NUL as u8 {
4109 osp_ppath(path,pid,"/stat")
4110 let fd: i64=sys_openat_rd(path)
4111 // A process may disappear during enumeration; other failures
4112 // make completeness unproven and must propagate.
4113 if fd < 0 && fd != (0-2) { failure=fd;running=0;break }
4114 if fd >= 0 {
4115 var used: i64=0;var readrc: i64=1
4116 while readrc > 0 && used < OSP_RD_CAP {
4117 readrc=sys_read(fd,stat+used,OSP_RD_CAP-used)
4118 if readrc == (0-4) { readrc=1;continue }
4119 if readrc > 0 { used=used+readrc }
4120 }
4121 let closed: i64=sys_close(fd)
4122 if readrc < 0 && readrc != (0-3) { failure=readrc;running=0;break }
4123 if closed < 0 { failure=closed;running=0;break }
4124 if used == OSP_RD_CAP { failure=0-75;running=0;break }
4125 if used > 0 {
4126 let observed: i64=osp_stat_parent(stat,used)
4127 if observed < 0 { failure=observed;running=0;break }
4128 if observed == parent {
4129 if count >= capacity { failure=0-28;running=0;break }
4130 children[count]=pid;count=count+1
4131 }
4132 }
4133 }
4134 }
4135 off=off+size
4136 }
4137 }
4138 let closed: i64=sys_close(directory)
4139 sys_munmap(batch,OSP_DENT_BUF);sys_munmap(path,OSP_PATH_CAP);sys_munmap(stat,OSP_RD_CAP)
4140 if failure < 0 { return failure }
4141 if closed < 0 { return closed }
4142 return count
4143}
4144
4145const FSX_MAGIC_4095: i64 = 4095
4146
4147const FSX_READ_CAP: i64 = 1048576 // max bytes returned by `read` (truncation is MARKED, never silent)
4148const FSX_DENY_CAP: i64 = 8192 // fs_read_deny.conf read cap
4149const FSX_PATH_CAP: i64 = 1024 // lowercased path work buffer
4150const FSX_DENT_BUF: i64 = 65536 // getdents64 batch buffer (matches the proven vsz/heal sizing)
4151const FSX_LS_CAP: i64 = 200 // scale-law: max ls entries EMITTED; true total ALWAYS declared (65KB-dump fix)
4152const FSX_RC_ABSENT: i64 = 3 // exit: path absent/unreadable (mirrors nx_fileop's exists convention)
4153const FSX_RC_DENIED: i64 = 5 // exit: deny-list refused the read
4154const FSX_UPPER_A: i64 = 65 // 'A' (ASCII lowercasing)
4155const FSX_UPPER_Z: i64 = 90 // 'Z'
4156const FSX_CASE_OFF: i64 = 32 // 'a' - 'A'
4157const FSX_ASCII_0: i64 = 48 // '0' (decimal print)
4158
4159func fsx_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
4160// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer
4161// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the
4162// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls).
4163// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign.
4164func fsx_putn(v: i64) -> i64 { nxi_out(v); return 0 }
4165// lowercase copy of s into out (bounded), returns length
4166func fsx_lower(s: *u8, out: *u8, cap: i64) -> i64 {
4167 var i: i64 = 0
4168 while s[i] != (0 as u8) {
4169 if i >= cap - 1 { out[i] = 0 as u8; return i }
4170 var c: i64 = s[i] as i64
4171 if c >= FSX_UPPER_A { if c <= FSX_UPPER_Z { c = c + FSX_CASE_OFF } }
4172 out[i] = c as u8
4173 i = i + 1
4174 }
4175 out[i] = 0 as u8
4176 return i
4177}
4178// exact NUL-terminated string equality
4179func fsx_seq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 }
4180// is `needle` (NUL-terminated, lowercase) contained in lowercase path lp[0..ln)?
4181// ---------- compare-and-swap decision (seq1422/seq1456) ----------
4182//
4183// PURE, and in the LIB on purpose: the decision used to live inside the CLI's
4184// main(), where a gate cannot reach it -- which is exactly how it shipped
4185// refusing every correct expectation (seq1422). A rule nothing can drive is a
4186// rule nothing can prove.
4187//
4188// tok is the raw argv token (`expect=<n>` / `expect=any` / a bare number);
4189// cur is the file's real size. Returns 1 = ALLOW, 0 = REFUSE.
4190func fsx_cas_val(tok: *u8) -> *u8 {
4191 var i: i64 = 0
4192 while tok[i] != (0 as u8) {
4193 if tok[i] == (61 as u8) { return ((tok as i64) + i + 1) as *u8 }
4194 i = i + 1
4195 }
4196 return tok
4197}
4198func fsx_cas_ok(cur: i64, tok: *u8) -> i64 {
4199 let v: *u8 = fsx_cas_val(tok)
4200 if fsx_seq(v, "any" as *u8) == 1 { return 1 }
4201 var n: i64 = 0
4202 var i: i64 = 0
4203 var got: i64 = 0
4204 while v[i] != (0 as u8) {
4205 let c: i64 = v[i] as i64
4206 if c >= 48 { if c <= 57 { n = n * 10 + (c - 48); got = 1 } }
4207 i = i + 1
4208 }
4209 if got == 0 { return 0 }
4210 if n == cur { return 1 }
4211 return 0
4212}
4213
4214func fsx_deny_hit(lp: *u8, ln: i64, needle: *u8) -> i64 {
4215 let nl: i64 = vw_slen(needle)
4216 if nl == 0 { return 0 }
4217 return vw_contains(lp, ln, needle, nl)
4218}
4219// data-driven deny extras: one lowercase needle per line in `conf`; 1 = some line matches the path.
4220// Factored out so the read deny (fs_read_deny.conf) and write deny (fs_write_deny.conf) share ONE scanner.
4221func fsx_conf_deny(lp: *u8, ln: i64, conf: *u8) -> i64 {
4222 let cb: *u8 = sys_mmap(FSX_DENY_CAP)
4223 let cn: i64 = vw_read(conf, cb, FSX_DENY_CAP - 1)
4224 if cn > 0 {
4225 var ls: i64 = 0
4226 var i: i64 = 0
4227 while i <= cn {
4228 var eol: i64 = 0
4229 if i == cn { eol = 1 } else { if cb[i] == (10 as u8) { eol = 1 } }
4230 if eol == 1 {
4231 if i > ls {
4232 cb[i] = 0 as u8 // terminate the line in place
4233 if fsx_deny_hit(lp, ln, (cb as i64 + ls) as *u8) == 1 { return 1 }
4234 }
4235 ls = i + 1
4236 }
4237 i = i + 1
4238 }
4239 }
4240 return 0
4241}
4242const FSX_SNIFF_CAP: i64 = 4096
4243
4244func fsx_isalnum(c: i64) -> i64 {
4245 if c >= 48 { if c <= 57 { return 1 } }
4246 if c >= 97 { if c <= 122 { return 1 } }
4247 if c >= 65 { if c <= 90 { return 1 } }
4248 return 0
4249}
4250
4251func fsx_ends_with(lp: *u8, ln: i64, suf: *u8) -> i64 {
4252 let sl: i64 = vw_slen(suf)
4253 if sl == 0 { return 0 }
4254 if sl > ln { return 0 }
4255 var i: i64 = 0
4256 while i < sl {
4257 if lp[ln - sl + i] != suf[i] { return 0 }
4258 i = i + 1
4259 }
4260 return 1
4261}
4262
4263func fsx_basename_is(lp: *u8, ln: i64, name: *u8) -> i64 {
4264 let nl: i64 = vw_slen(name)
4265 if nl == 0 { return 0 }
4266 if nl > ln { return 0 }
4267 if fsx_ends_with(lp, ln, name) == 0 { return 0 }
4268 if nl == ln { return 1 }
4269 let c: i64 = lp[ln - nl - 1] as i64
4270 if c == 47 { return 1 }
4271 if c == 92 { return 1 }
4272 return 0
4273}
4274
4275// Whole-word containment: bounded by non-alphanumeric on BOTH sides, so `api_secret.txt` is denied and
4276// `secretary_notes.md` is not.
4277func fsx_word_has(lp: *u8, ln: i64, w: *u8) -> i64 {
4278 let wl: i64 = vw_slen(w)
4279 if wl == 0 { return 0 }
4280 if wl > ln { return 0 }
4281 var i: i64 = 0
4282 while i + wl <= ln {
4283 var eq: i64 = 1
4284 var k: i64 = 0
4285 while k < wl { if lp[i + k] != w[k] { eq = 0; k = wl } else { k = k + 1 } }
4286 if eq == 1 {
4287 var lb: i64 = 1
4288 if i > 0 { if fsx_isalnum(lp[i - 1] as i64) == 1 { lb = 0 } }
4289 var rb: i64 = 1
4290 if i + wl < ln { if fsx_isalnum(lp[i + wl] as i64) == 1 { rb = 0 } }
4291 if lb == 1 { if rb == 1 { return 1 } }
4292 }
4293 i = i + 1
4294 }
4295 return 0
4296}
4297
4298// CONTENT LEG: sniff the leading bytes for what a secret actually IS. This is the half a name-only list
4299// can never do -- it denies a private key no matter what it is called, including `notes.txt`.
4300// A CERTIFICATE is deliberately NOT denied: certs are public by definition, and denying them is the same
4301// category error as denying the tokenizer.
4302func fsx_content_secret(path: *u8) -> i64 {
4303 let fd: i64 = sys_openat_rd(path)
4304 if fd < 0 { return 0 }
4305 let b: *u8 = sys_mmap(FSX_SNIFF_CAP)
4306 let n: i64 = sys_read(fd, b, FSX_SNIFF_CAP - 1)
4307 sys_close(fd)
4308 if n <= 0 { return 0 }
4309 if vw_contains(b, n, "PRIVATE KEY-----" as *u8, 16) == 1 { return 1 }
4310 if vw_contains(b, n, "OPENSSH PRIVATE KEY" as *u8, 19) == 1 { return 1 }
4311 if vw_contains(b, n, "PGP PRIVATE KEY BLOCK" as *u8, 21) == 1 { return 1 }
4312 if vw_contains(b, n, "PuTTY-User-Key-File" as *u8, 19) == 1 { return 1 }
4313 return 0
4314}
4315
4316// DENY check: 1 = refuse this path. SOTA-2026 REWRITE (2026-07-31).
4317//
4318// THE OLD RULE WAS WRONG IN BOTH DIRECTIONS, measured on real paths:
4319// OVER-BLOCKED substring "token" denied runtime/nx_tokenizer.nx -- the compiler's own tokenizer, which
4320// contains no secret -- and blocked BOTH nx_fs read AND nx_fs_write on it, while
4321// nx_shelltool grep returned the same bytes freely. It cost real work and bought nothing.
4322// substring "key" likewise denies monkey / keyword / keyboard.
4323// UNDER-BLOCKED `id_rsa`, the canonical SSH private key filename, contains NONE of
4324// secret/key/token/passw/.pem and sailed straight through.
4325// A denylist that blocks source and passes private keys is not a security control -- it is a rename away
4326// from useless in one direction and a permanent nuisance in the other.
4327//
4328// REPLACEMENT -- two INDEPENDENT legs, either one denies:
4329// (1) PATH leg: real secret-bearing EXTENSIONS and exact BASENAMES, matched at a true suffix/segment
4330// boundary, plus whole-word `secret`/`password`. No substring-anywhere matching survives.
4331// (2) CONTENT leg: PEM/OpenSSH/PGP/PuTTY private-key armour, which catches a secret regardless of name.
4332// Net effect: strictly MORE secrets denied (id_rsa, a renamed key, a key with no extension) and strictly
4333// FEWER ordinary sources blocked.
4334func fsx_denied(path: *u8) -> i64 {
4335 let lp: *u8 = sys_mmap(FSX_PATH_CAP)
4336 let ln: i64 = fsx_lower(path, lp, FSX_PATH_CAP)
4337
4338 if fsx_ends_with(lp, ln, ".pem" as *u8) == 1 { return 1 }
4339 if fsx_ends_with(lp, ln, ".key" as *u8) == 1 { return 1 }
4340 if fsx_ends_with(lp, ln, ".cap" as *u8) == 1 { return 1 }
4341 if fsx_ends_with(lp, ln, ".p12" as *u8) == 1 { return 1 }
4342 if fsx_ends_with(lp, ln, ".pfx" as *u8) == 1 { return 1 }
4343 if fsx_ends_with(lp, ln, ".jks" as *u8) == 1 { return 1 }
4344 if fsx_ends_with(lp, ln, ".ppk" as *u8) == 1 { return 1 }
4345 if fsx_ends_with(lp, ln, "_rsa" as *u8) == 1 { return 1 }
4346 if fsx_ends_with(lp, ln, "_dsa" as *u8) == 1 { return 1 }
4347 if fsx_ends_with(lp, ln, "_ecdsa" as *u8) == 1 { return 1 }
4348 if fsx_ends_with(lp, ln, "_ed25519" as *u8) == 1 { return 1 }
4349
4350 if fsx_basename_is(lp, ln, ".env" as *u8) == 1 { return 1 }
4351 if fsx_basename_is(lp, ln, "credentials" as *u8) == 1 { return 1 }
4352 if fsx_basename_is(lp, ln, "shadow" as *u8) == 1 { return 1 }
4353 if fsx_basename_is(lp, ln, "opaque_keys.bin" as *u8) == 1 { return 1 }
4354
4355 // CALIBRATED BY WORD FREQUENCY, not by one uniform rule -- the gate proved a uniform rule wrong in
4356 // BOTH directions within minutes. `secret` and `passw` are high-signal and essentially absent from
4357 // ordinary source, so SUBSTRING matching is correct for them and catches mysecret_key.bin. `key` and
4358 // `token` are common English fragments (tokenizer, monkey, keyword, keyboard) and must NEVER be
4359 // substring-matched -- that is what denied the compiler's own tokenizer. They are covered instead by
4360 // the extension/suffix rules above and by the content leg below.
4361 if fsx_deny_hit(lp, ln, "secret" as *u8) == 1 { return 1 }
4362 if fsx_deny_hit(lp, ln, "passw" as *u8) == 1 { return 1 }
4363 if fsx_deny_hit(lp, ln, "credential" as *u8) == 1 { return 1 }
4364
4365 if fsx_content_secret(path) == 1 { return 1 }
4366
4367 return fsx_conf_deny(lp, ln, "fs_read_deny.conf" as *u8)
4368}
4369// read: emit up to `cap` bytes of path to stdout. Returns bytes emitted; -1 absent; -2 DENIED.
4370// deniedp/absent are ALSO visible in the CLI exit code. Truncation is marked with a trailing banner.
4371// Failure reporter that KEEPS THE ERRNO. sys_openat_rd returns -errno, and the old message printed
4372// "ABSENT" for every negative -- so EACCES (-13, EXISTS but unopenable) read as "missing", which are
4373// OPPOSITE remedies. Cost a real hour on 2026-08-01: knowledge/foundation existed with mode 0100 and
4374// every instrument in the stack called it absent (the mkdirp read-back that printed the errno cracked
4375// the case in one call). rc>=0 means a probe re-open SUCCEEDED: the earlier read failed for a
4376// non-open reason (an empty file), so say THAT. Always returns -1 (callers' contract unchanged;
4377// the -2 DENIED sentinel stays distinct).
4378func fsx_fail(path: *u8, rc: i64) -> i64 {
4379 if rc >= 0 { sys_close(rc); fsx_puts("NX-FS EMPTY: 0 bytes: " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
4380 if rc == 0 - 13 {
4381 fsx_puts("NX-FS PERMISSION (EACCES): exists but this process may not open it: " as *u8)
4382 fsx_puts(path); fsx_puts("\n" as *u8)
4383 return 0 - 1
4384 }
4385 if rc == 0 - 2 { fsx_puts("NX-FS ABSENT: " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
4386 fsx_puts("NX-FS ERROR rc=" as *u8); fsx_putn(rc)
4387 fsx_puts(": " as *u8); fsx_puts(path); fsx_puts("\n" as *u8)
4388 return 0 - 1
4389}
4390
4391const FSX_SEEK_END: i64 = 2 // lseek whence: EOF offset = size, WITHOUT reading a single byte
4392
4393// TRUE SIZE -- the one thing no other read verb in this lib can give you (2026-08-07, debt 1786054029).
4394// read/lines/outline all report BYTES THEY READ against FSX_READ_CAP/FSX_LINES_SCAN, and they DO honestly
4395// declare the cap -- but an honest floor is still not a measurement: "bytes=1048576 (covers first 1048576
4396// bytes only)" is the IDENTICAL answer for a 1.05MB file and a 30MB one.
4397// MEASURED COST OF NOT HAVING IT: bounding ONE 1.38MB journal took TWELVE probe reads at hand-chosen
4398// offsets, because the only way to learn a big file size was to binary-search EOF by hand.
4399// lseek(SEEK_END) reads ZERO bytes, so the answer is exact at ANY size for one syscall.
4400// Deny-list still applies: consistency with every other verb beats a special case for a metadata read.
4401// CONTRACT DIFFERS FROM fsx_read ON PURPOSE: an EMPTY file returns 0, never -1. Size is the one caller for
4402// which "absent" and "zero bytes" are DIFFERENT FACTS, so fsx_fail -- which folds both to -1 -- is not used
4403// here. (Same distinction lt_read_tail needed: -1 ABSENT vs 0 EMPTY. A reader that conflates them cannot
4404// tell a lane that never wrote from a lane whose file vanished.)
4405// A DECLARED FLOOR IS HONEST BUT IT IS NOT A MEASUREMENT -- IF THE NUMBER IS CHEAP, EMIT THE NUMBER.
4406func fsx_size(path: *u8) -> i64 {
4407 if fsx_denied(path) == 1 {
4408 fsx_puts("NX-FS-SIZE DENIED: path matches the secret deny-list. WHY: this tool never returns key material.\n" as *u8)
4409 return 0 - (2 as i64)
4410 }
4411 let fd: i64 = sys_openat_rd(path)
4412 if fd < 0 {
4413 fsx_puts("NX-FS-SIZE ABSENT: cannot open " as *u8); fsx_puts(path)
4414 fsx_puts(" . FIX: confirm the path with `nx_fs ls <dir>`.\n" as *u8)
4415 return 0 - 1
4416 }
4417 let sz: i64 = sys_lseek(fd, 0, FSX_SEEK_END)
4418 sys_close(fd)
4419 if sz < 0 {
4420 fsx_puts("NX-FS-SIZE UNSEEKABLE: " as *u8); fsx_puts(path)
4421 fsx_puts(" (a pipe/char device has no size; this is NOT a zero-byte file)\n" as *u8)
4422 return 0 - 1
4423 }
4424 fsx_puts("NX-FS-SIZE " as *u8); fsx_puts(path)
4425 fsx_puts(" bytes=" as *u8); fsx_putn(sz)
4426 fsx_puts(" exact=1 read_bytes=0\n" as *u8)
4427 return sz
4428}
4429
4430func fsx_read(path: *u8, cap: i64) -> i64 {
4431 if fsx_denied(path) == 1 {
4432 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
4433 return 0 - (2 as i64) // DENIED sentinel (distinct from -1 absent)
4434 }
4435 var want: i64 = cap
4436 if want <= 0 { want = FSX_READ_CAP }
4437 if want > FSX_READ_CAP { want = FSX_READ_CAP }
4438 let buf: *u8 = sys_mmap(want + 1)
4439 let n: i64 = vw_read(path, buf, want)
4440 // vw_read flattens the errno (-1 for every failure); re-probe the open ONLY on the failure path
4441 // so the message can distinguish absent / permission / empty. Zero cost on success.
4442 if n <= 0 { return fsx_fail(path, sys_openat_rd(path)) }
4443 sys_write(1, buf, n)
4444 if n == want {
4445 fsx_puts("\n[NX-FS TRUNCATED at " as *u8); fsx_putn(n); fsx_puts(" bytes]\n" as *u8)
4446 }
4447 return n
4448}
4449// WINDOWED read (eats debt seq222: the tools-call transport caps ~64KB, so files past the cap were
4450// unreadable over MCP): emit up to `cap` bytes starting at byte `off`. Same deny-list as fsx_read.
4451// A separate function (NOT an fsx_read arity change) so every existing caller keeps its exact contract.
4452func fsx_read_at(path: *u8, cap: i64, off: i64) -> i64 {
4453 if fsx_denied(path) == 1 {
4454 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
4455 return 0 - (2 as i64)
4456 }
4457 var want: i64 = cap
4458 if want <= 0 { want = FSX_READ_CAP }
4459 if want > FSX_READ_CAP { want = FSX_READ_CAP }
4460 let fd: i64 = sys_openat_rd(path)
4461 if fd < 0 { return fsx_fail(path, fd) }
4462 if off > 0 { if sys_lseek(fd, off, 0) < 0 { sys_close(fd); fsx_puts("NX-FS ABSENT: seek failed " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 } }
4463 let buf: *u8 = sys_mmap(want + 1)
4464 var got: i64 = 0
4465 var sc: i64 = 1
4466 while sc == 1 {
4467 let r: i64 = sys_read(fd, ((buf as i64 + got) as *u8), want - got)
4468 if r <= 0 { sc = 0 } else { got = got + r; if got >= want { sc = 0 } }
4469 }
4470 sys_close(fd)
4471 if got <= 0 { fsx_puts("NX-FS EOF: no bytes at offset " as *u8); fsx_putn(off); fsx_puts(" in " as *u8); fsx_puts(path); fsx_puts("\n" as *u8); return 0 - 1 }
4472 sys_write(1, buf, got)
4473 if got == want {
4474 fsx_puts("\n[NX-FS WINDOW off=" as *u8); fsx_putn(off); fsx_puts(" n=" as *u8); fsx_putn(got); fsx_puts(" -- more remains]\n" as *u8)
4475 }
4476 return got
4477}
4478const FSX_LINES_SCAN: i64 = 1048576 // line-addressing scan window (matches the proven read cap)
4479const FSX_LINES_MAXOUT: i64 = 262144 // max bytes emitted by one `lines` call (transport-friendly)
4480const FSX_LINES_DEFN: i64 = 40 // default line count when the caller omits it
4481const FSX_LINES_MAXN: i64 = 400 // max lines per call
4482
4483// LINE-ADDRESSED read -- THE MISSING PRIMITIVE (measured 2026-07-20): `grep` reports file:LINE but `read`
4484// takes BYTES, so the two did NOT compose -- locating one function in a remote file meant hand
4485// binary-searching byte offsets (cost one subagent 70K tokens + 22 calls for a single extraction).
4486// Emits lines [start, start+count) 1-based, then a DECLARED envelope banner (scale-law: a caller can
4487// NEVER be silently windowed -- scanned bytes, scan cap, over-window and clip flags are all stated).
4488// Same deny-list as fsx_read. Returns bytes emitted; -1 absent; -2 DENIED.
4489func fsx_read_lines(path: *u8, start: i64, count: i64) -> i64 {
4490 if fsx_denied(path) == 1 {
4491 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
4492 return 0 - (2 as i64)
4493 }
4494 var s: i64 = start
4495 if s < 1 { s = 1 }
4496 var c: i64 = count
4497 if c <= 0 { c = FSX_LINES_DEFN }
4498 if c > FSX_LINES_MAXN { c = FSX_LINES_MAXN }
4499 let buf: *u8 = sys_mmap(FSX_LINES_SCAN + 1)
4500 let n: i64 = vw_read(path, buf, FSX_LINES_SCAN)
4501 if n <= 0 { return fsx_fail(path, sys_openat_rd(path)) }
4502 // walk to the first byte of line `s`; cur > s afterwards means we ran off the end (fail-loud, not empty)
4503 var i: i64 = 0
4504 var cur: i64 = 1
4505 while cur < s {
4506 if i >= n { cur = s + 1 } else {
4507 if buf[i] == (10 as u8) { cur = cur + 1 }
4508 i = i + 1
4509 }
4510 }
4511 if cur > s {
4512 fsx_puts("NX-FS LINES: start line " as *u8); fsx_putn(s)
4513 fsx_puts(" is beyond EOF (scanned " as *u8); fsx_putn(n); fsx_puts(" bytes)\n" as *u8)
4514 return 0
4515 }
4516 let from: i64 = i
4517 var lines_out: i64 = 0
4518 var j: i64 = i
4519 var go: i64 = 1
4520 while go == 1 {
4521 if j >= n { go = 0 } else {
4522 if buf[j] == (10 as u8) {
4523 lines_out = lines_out + 1
4524 j = j + 1
4525 if lines_out >= c { go = 0 }
4526 } else { j = j + 1 }
4527 }
4528 }
4529 var outn: i64 = j - from
4530 var clipped: i64 = 0
4531 if outn > FSX_LINES_MAXOUT { outn = FSX_LINES_MAXOUT; clipped = 1 }
4532 if outn > 0 { sys_write(1, ((buf as i64 + from) as *u8), outn) }
4533 fsx_puts("\n[NX-FS LINES start=" as *u8); fsx_putn(s)
4534 fsx_puts(" lines=" as *u8); fsx_putn(lines_out)
4535 fsx_puts(" next=" as *u8); fsx_putn(s + lines_out)
4536 fsx_puts(" bytes=" as *u8); fsx_putn(outn)
4537 fsx_puts(" scanned=" as *u8); fsx_putn(n)
4538 fsx_puts(" scan_cap=" as *u8); fsx_putn(FSX_LINES_SCAN)
4539 if n >= FSX_LINES_SCAN { fsx_puts(" FILE-EXCEEDS-SCAN-WINDOW" as *u8) }
4540 if clipped == 1 { fsx_puts(" BYTE-CLIPPED" as *u8) }
4541 fsx_puts("]\n" as *u8)
4542 return outn
4543}
4544// ==== WRITE/EDIT half (cap class: write; tools-api name nx_fs_write) =========================
4545// ★ONE DEFINITION, TWO NAMES: this const KEEPS its name so no caller changes, but its VALUE now comes
4546// from the shim's MODE_0644 instead of a second literal. This line already called itself "the ecosystem's
4547// file-create mode idiom" -- and it was right, which is why adding MODE_0644 to nx_syscalls without
4548// finding it created a 64th copy rather than a single ruler.
4549// ★★★SEARCHING BY NAME FINDS ONLY WHAT SHARES YOUR NAMING CONVENTION. TO FIND A DUPLICATE CONSTANT YOU
4550// MUST SEARCH BY VALUE: a grep for `_MODE_0644` returned 10, a grep for `= 0x1a4` returned 66.
4551const FSX_MODE_RW: i64 = MODE_0644 // 0644 -- the ecosystem's file-create mode idiom
4552const FSX_DEC: i64 = 10 // decimal base (pid rendering in the tmp suffix)
4553const FSX_EDIT_OUT: i64 = 2097152 // edit output buffer (2x read cap: bounded replacement growth)
4554const FSX_TMP_ROOM: i64 = 32 // reserved room for ".nxw" + pid digits + NUL in the tmp name
4555const FSX_RC_IO: i64 = 4 // exit: io failure (open/short-write/rename)
4556const FSX_RC_NOMATCH: i64 = 6 // exit: edit found 0 occurrences (file UNCHANGED)
4557const FSX_RC_AMBIG: i64 = 7 // exit: edit found >1 occurrences without `all` (file UNCHANGED)
4558
4559// write-DENY: read deny (never clobber key material) + OS device/firmware namespace (rule 26, seam,
4560// BY CONSTRUCTION) + registry-escalation needle + fs_write_deny.conf extras (data-driven).
4561// TAIL -- the "WHERE DOES THIS FILE END" primitive, answered from the file's own end in ONE call.
4562// The documented recipe was `size`, then `read <path> <n> <size-n>`: two calls and an offset the caller
4563// carries by hand. What actually happened (measured 2026-09-03): a caller chose a `lines` start from an
4564// EARLIER run's size, read a window that landed mid-file, and published the window's last line as the
4565// file's last line -- while the envelope on that very read said next=212. Two false mechanisms and a
4566// false scope claim followed. ★A WINDOW READ IS NOT A TAIL READ. This verb cannot be pointed at the
4567// middle: it seeks to the end, walks BACKWARD for the last `count` line starts, and declares its window.
4568// Same deny-list as every read verb. Returns bytes emitted; 0 for an empty file (banner, never silence);
4569// -1 absent/unseekable; -2 DENIED.
4570func fsx_tail(path: *u8, count: i64) -> i64 {
4571 if fsx_denied(path) == 1 {
4572 fsx_puts("NX-FS DENIED: path matches the secret deny-list (defaults + fs_read_deny.conf)\n" as *u8)
4573 return 0 - (2 as i64)
4574 }
4575 var c: i64 = count
4576 if c <= 0 { c = FSX_LINES_DEFN }
4577 if c > FSX_LINES_MAXN { c = FSX_LINES_MAXN }
4578 let fd: i64 = sys_openat_rd(path)
4579 if fd < 0 { return fsx_fail(path, fd) }
4580 let sz: i64 = sys_lseek(fd, 0, FSX_SEEK_END)
4581 if sz < 0 {
4582 sys_close(fd)
4583 fsx_puts("NX-FS TAIL UNSEEKABLE: " as *u8); fsx_puts(path)
4584 fsx_puts(" (a pipe/char device has no end to seek to)\n" as *u8)
4585 return 0 - 1
4586 }
4587 if sz == 0 {
4588 sys_close(fd)
4589 fsx_puts("[NX-FS TAIL lines=0 total_bytes=0 window_off=0 scanned=0 EMPTY-FILE]\n" as *u8)
4590 return 0
4591 }
4592 // read the LAST scan-window of the file, never the first: a log past the window still yields its end
4593 var off: i64 = 0
4594 if sz > FSX_LINES_SCAN { off = sz - FSX_LINES_SCAN }
4595 if sys_lseek(fd, off, 0) < 0 {
4596 sys_close(fd)
4597 fsx_puts("NX-FS ABSENT: seek failed " as *u8); fsx_puts(path); fsx_puts("\n" as *u8)
4598 return 0 - 1
4599 }
4600 let buf: *u8 = sys_mmap(FSX_LINES_SCAN + 1)
4601 var n: i64 = 0
4602 var sc: i64 = 1
4603 while sc == 1 {
4604 let r: i64 = sys_read(fd, ((buf as i64 + n) as *u8), FSX_LINES_SCAN - n)
4605 if r <= 0 { sc = 0 } else { n = n + r; if n >= FSX_LINES_SCAN { sc = 0 } }
4606 }
4607 sys_close(fd)
4608 if n <= 0 { return fsx_fail(path, 0 - 1) }
4609 // a single trailing newline terminates the last line; it is not an empty extra line
4610 var lim: i64 = n
4611 var terminated: i64 = 0
4612 if buf[n - 1] == (10 as u8) { lim = n - 1; terminated = 1 }
4613 // walk backward for `c` line starts
4614 var p: i64 = lim
4615 var seen: i64 = 0
4616 var start: i64 = 0
4617 var go: i64 = 1
4618 while go == 1 {
4619 if p <= 0 { start = 0; go = 0 } else {
4620 p = p - 1
4621 if buf[p] == (10 as u8) {
4622 seen = seen + 1
4623 if seen >= c { start = p + 1; go = 0 }
4624 }
4625 }
4626 }
4627 var lines_out: i64 = seen + 1
4628 if seen >= c { lines_out = c }
4629 // count the window's lines once so a caller can address the whole file with `lines` afterwards
4630 var wl: i64 = 0
4631 var q: i64 = 0
4632 while q < lim { if buf[q] == (10 as u8) { wl = wl + 1 } q = q + 1 }
4633 wl = wl + 1
4634 let outn: i64 = n - start
4635 if outn > 0 { sys_write(1, ((buf as i64 + start) as *u8), outn) }
4636 if terminated == 0 { fsx_puts("\n" as *u8) }
4637 fsx_puts("[NX-FS TAIL lines=" as *u8); fsx_putn(lines_out)
4638 fsx_puts(" bytes=" as *u8); fsx_putn(outn)
4639 fsx_puts(" total_bytes=" as *u8); fsx_putn(sz)
4640 fsx_puts(" window_off=" as *u8); fsx_putn(off)
4641 fsx_puts(" scanned=" as *u8); fsx_putn(n)
4642 fsx_puts(" window_lines=" as *u8); fsx_putn(wl)
4643 fsx_puts(" last_line_terminated=" as *u8); fsx_putn(terminated)
4644 if off > 0 { fsx_puts(" WINDOW-IS-TAIL-OF-FILE" as *u8) }
4645 if off > 0 { if start == 0 { fsx_puts(" FIRST-LINE-MAY-BE-PARTIAL" as *u8) } }
4646 fsx_puts("]\n" as *u8)
4647 return outn
4648}
4649
4650func fsx_write_denied(path: *u8) -> i64 {
4651 if fsx_denied(path) == 1 { return 1 }
4652 if osf_write_forbidden(path) == 1 { return 1 }
4653 let lp: *u8 = sys_mmap(FSX_PATH_CAP)
4654 let ln: i64 = fsx_lower(path, lp, FSX_PATH_CAP)
4655 if fsx_deny_hit(lp, ln, "allowlist" as *u8) == 1 { return 1 }
4656 return fsx_conf_deny(lp, ln, "fs_write_deny.conf" as *u8)
4657}
4658// ---------- APPEND-ONLY write for journals and boards (2026-09-02) ----------
4659// ONE O_APPEND write under an exclusive flock: the row lands whole and AFTER every row already there, and
4660// there is no read-modify-write window for a sibling seat to lose it in. MEASURED the same day: a `log|`
4661// row appended to lang.plan by anchored CAS edit (receipt OK bytes=51180) was gone minutes later -- a
4662// sibling's whole-file write had rebuilt the file from its own stale read. A BOARD IS A JOURNAL; JOURNALS
4663// ARE APPENDED, NEVER REWRITTEN. The write deny-list applies unchanged (a new write path must never become
4664// a way into the secret or device namespace).
4665// CONTRACT: body must end in '\n' (a row that does not terminate glues itself to the next seat's row ->
4666// FSX_APP_NONL, file unchanged); an empty body is refused (FSX_APP_EMPTY); when the file's LAST byte is not
4667// a newline (a rewrite left an unterminated tail) one newline is prepended INSIDE the same locked write, so
4668// the caller sees bytes-written == blen + 1 and can announce the heal. Returns bytes written; -2 DENIED;
4669// -3 io (open/lock/short write).
4670const FSX_NL: i64 = 10 // '\n' -- the row terminator this verb requires and heals
4671const FSX_APP_EMPTY: i64 = 0 - 4 // append refused: nothing to append
4672const FSX_APP_NONL: i64 = 0 - 5 // append refused: body does not end in a newline
4673const FSX_SEEK_SET: i64 = 0 // lseek whence: absolute offset (the tail probe)
4674// 1 = the file exists, is non-empty and its last byte is NOT a newline (an unterminated tail); else 0.
4675func fsx_tail_unterminated(path: *u8) -> i64 {
4676 let fd: i64 = sys_openat_rd(path)
4677 if fd < 0 { return 0 }
4678 let sz: i64 = sys_lseek(fd, 0, FSX_SEEK_END)
4679 var unterminated: i64 = 0
4680 if sz > 0 {
4681 if sys_lseek(fd, sz - 1, FSX_SEEK_SET) == sz - 1 {
4682 let lb: *u8 = sys_mmap(16)
4683 if sys_read(fd, lb, 1) == 1 { if lb[0] != (FSX_NL as u8) { unterminated = 1 } }
4684 }
4685 }
4686 sys_close(fd)
4687 return unterminated
4688}
4689func fsx_append(path: *u8, body: *u8, blen: i64) -> i64 {
4690 if fsx_write_denied(path) == 1 {
4691 fsx_puts("NX-FS DENIED: append refused (secret/device-namespace/allowlist deny)\n" as *u8)
4692 return 0 - (2 as i64)
4693 }
4694 if blen <= 0 { return FSX_APP_EMPTY }
4695 if body[blen - 1] != (FSX_NL as u8) { return FSX_APP_NONL }
4696 let heal: i64 = fsx_tail_unterminated(path)
4697 let fd: i64 = sys_openat_append(path, FSX_MODE_RW)
4698 if fd < 0 { return 0 - (3 as i64) }
4699 sys_flock(fd, SYS_LOCK_EX)
4700 let total: i64 = blen + heal
4701 let buf: *u8 = sys_mmap(total + 1)
4702 var i: i64 = 0
4703 if heal == 1 { buf[0] = FSX_NL as u8; i = 1 }
4704 var j: i64 = 0
4705 while j < blen { buf[i] = body[j]; i = i + 1; j = j + 1 }
4706 var off: i64 = 0
4707 while off < total {
4708 let w: i64 = sys_write(fd, ((buf as i64 + off) as *u8), total - off)
4709 if w <= 0 { sys_flock(fd, SYS_LOCK_UN); sys_close(fd); return 0 - (3 as i64) }
4710 off = off + w
4711 }
4712 sys_fsync(fd)
4713 sys_flock(fd, SYS_LOCK_UN)
4714 sys_close(fd)
4715 return total
4716}
4717// ATOMIC full-file write: content lands via <path>.nxw<pid> + fsync + rename, so a reader NEVER sees a
4718// torn file and concurrent writers each land whole (last rename wins; pid suffix = no shared tmp).
4719// Returns bytes written; -2 DENIED; -3 io error (path too long / open / short write / rename).
4720func fsx_write(path: *u8, body: *u8, blen: i64) -> i64 {
4721 if fsx_write_denied(path) == 1 {
4722 fsx_puts("NX-FS DENIED: write refused (secret/device-namespace/allowlist deny)\n" as *u8)
4723 return 0 - (2 as i64)
4724 }
4725 let plen: i64 = vw_slen(path)
4726 if plen + FSX_TMP_ROOM >= FSX_PATH_CAP { return 0 - (3 as i64) }
4727 let tmp: *u8 = sys_mmap(FSX_PATH_CAP)
4728 var i: i64 = 0
4729 while i < plen { tmp[i] = path[i]; i = i + 1 }
4730 let suf: *u8 = ".nxw" as *u8
4731 var s: i64 = 0
4732 while suf[s] != (0 as u8) { tmp[i] = suf[s]; i = i + 1; s = s + 1 }
4733 var pid: i64 = osp_selfpid()
4734 if pid < 0 { pid = 0 }
4735 if pid == 0 { tmp[i] = FSX_ASCII_0 as u8; i = i + 1 } else {
4736 let ds: *u8 = sys_mmap(FSX_TMP_ROOM)
4737 var k: i64 = 0
4738 while pid > 0 { ds[k] = (FSX_ASCII_0 + (pid % FSX_DEC)) as u8; pid = pid / FSX_DEC; k = k + 1 }
4739 while k > 0 { tmp[i] = ds[k-1]; i = i + 1; k = k - 1 }
4740 }
4741 tmp[i] = 0 as u8
4742 let fd: i64 = sys_openat_wr(tmp, FSX_MODE_RW)
4743 if fd < 0 { return 0 - (3 as i64) }
4744 let saved: *NxFileWriteResult = sys_mmap(__size_of(NxFileWriteResult)) as *NxFileWriteResult
4745 if (saved as i64) < 0 { sys_close(fd); sys_unlinkat(tmp); return 0 - (3 as i64) }
4746 let write_rc: i64 = fio_write_sync_fd(fd, body, blen, saved)
4747 if write_rc < 0 {
4748 fsx_puts("NX-FS WRITE-FAILED stage="); fsx_puts(saved.stage)
4749 fsx_puts(" code="); fsx_putn(saved.code)
4750 fsx_puts(" written="); fsx_putn(saved.written)
4751 fsx_puts(" close_code="); fsx_putn(saved.close_code)
4752 fsx_puts(" path="); fsx_puts(path)
4753 fsx_puts(" publication=not-attempted\n")
4754 sys_munmap(saved as *u8, __size_of(NxFileWriteResult))
4755 sys_unlinkat(tmp)
4756 return 0 - (3 as i64)
4757 }
4758 sys_munmap(saved as *u8, __size_of(NxFileWriteResult))
4759 // PRESERVE the original file's mode across tmp+rename (debt eaten 2026-07-18: an edit of an
4760 // executable script used to land 0644 -- the exec bit vanished and the cron runner broke with
4761 // rc=126). st_mode = u32 at stat offset 24; keep the permission bits (low 12) only.
4762 let sb: *u8 = sys_mmap(160)
4763 if sys_fstatat(path, sb) == 0 {
4764 let m0: i64 = sb[24] as i64
4765 let m1: i64 = sb[25] as i64
4766 let om: i64 = (m0 + (m1 * 256)) & FSX_MAGIC_4095
4767 if om != FSX_MODE_RW { nx_chmod(tmp, om) }
4768 }
4769 // The same law at the last possible failure: if the rename cannot complete, the tmp is not a
4770 // partial result anyone wants -- it is litter wearing the shape of a real file. Take it with us.
4771 if sys_renameat(tmp, path) < 0 { sys_unlinkat(tmp); return 0 - (3 as i64) }
4772 return blen
4773}
4774// count non-overlapping occurrences of nee[0..nl) in hay[0..hn)
4775func fsx_count_occ(hay: *u8, hn: i64, nee: *u8, nl: i64) -> i64 {
4776 if nl <= 0 { return 0 }
4777 var c: i64 = 0
4778 var i: i64 = 0
4779 while i + nl <= hn {
4780 var m: i64 = 1
4781 var j: i64 = 0
4782 while j < nl { if hay[i+j] != nee[j] { m = 0; j = nl } else { j = j + 1 } }
4783 if m == 1 { c = c + 1; i = i + nl } else { i = i + 1 }
4784 }
4785 return c
4786}
4787// replace occurrences of nee with rep into out (allf=0: first only; 1: all). Returns new length; -1 overflow.
4788func fsx_replace(hay: *u8, hn: i64, nee: *u8, nl: i64, rep: *u8, rl: i64, out: *u8, ocap: i64, allf: i64) -> i64 {
4789 var o: i64 = 0
4790 var i: i64 = 0
4791 var used: i64 = 0
4792 while i < hn {
4793 var m: i64 = 0
4794 if i + nl <= hn { if nl > 0 {
4795 var ok: i64 = 1
4796 if allf == 0 { if used == 1 { ok = 0 } }
4797 if ok == 1 {
4798 m = 1
4799 var j: i64 = 0
4800 while j < nl { if hay[i+j] != nee[j] { m = 0; j = nl } else { j = j + 1 } }
4801 }
4802 } }
4803 if m == 1 {
4804 if o + rl > ocap { return 0 - 1 }
4805 var k: i64 = 0
4806 while k < rl { out[o] = rep[k]; o = o + 1; k = k + 1 }
4807 i = i + nl
4808 used = 1
4809 } else {
4810 if o + 1 > ocap { return 0 - 1 }
4811 out[o] = hay[i]
4812 o = o + 1
4813 i = i + 1
4814 }
4815 }
4816 return o
4817}
4818// EDIT: exact-string replace with the UNIQUENESS contract (the Claude-Edit SOTA semantic):
4819// 0 matches -> -6 NOMATCH (file untouched); >1 without allf -> -7 AMBIGUOUS (file untouched);
4820// otherwise replace (allf=1: every occurrence) and land ATOMICALLY via fsx_write.
4821// Returns new byte length; -1 absent; -2 DENIED; -3 io/overflow; -6 nomatch; -7 ambiguous.
4822func fsx_edit(path: *u8, olds: *u8, news: *u8, allf: i64) -> i64 {
4823 if fsx_write_denied(path) == 1 {
4824 fsx_puts("NX-FS DENIED: edit refused (secret/device-namespace/allowlist deny)\n" as *u8)
4825 return 0 - 2
4826 }
4827 let region: *NxFileReadRegion = sys_mmap(__size_of(NxFileReadRegion)) as *NxFileReadRegion
4828 if (region as i64) <= 0 { return 0 - 3 }
4829 fio_region_init(region)
4830 if fio_region_open(path, region) != 0 {
4831 sys_munmap(region as *u8, __size_of(NxFileReadRegion))
4832 return 0 - 1
4833 }
4834 let n: i64 = region.total
4835 if n <= 0 {
4836 fio_region_close(region)
4837 sys_munmap(region as *u8, __size_of(NxFileReadRegion))
4838 return 0 - 1
4839 }
4840 let buf: *u8 = sys_mmap(n)
4841 if (buf as i64) <= 0 {
4842 fio_region_close(region)
4843 sys_munmap(region as *u8, __size_of(NxFileReadRegion))
4844 return 0 - 3
4845 }
4846 let readn: i64 = fio_region_next(region, buf, n)
4847 sys_munmap(region as *u8, __size_of(NxFileReadRegion))
4848 if readn != n { sys_munmap(buf,n); return 0 - 3 }
4849 let ol: i64 = vw_slen(olds)
4850 let rl: i64 = vw_slen(news)
4851 let cnt: i64 = fsx_count_occ(buf,n,olds,ol)
4852 if cnt == 0 { sys_munmap(buf,n); return 0 - FSX_RC_NOMATCH }
4853 if cnt > 1 && allf == 0 { sys_munmap(buf,n); return 0 - FSX_RC_AMBIG }
4854 // Derive exact output extent from measured input and replacement count.
4855 // The numeric bound is the signed length representation, not a file-size policy.
4856 let delta: i64 = rl - ol
4857 if delta > 0 {
4858 if cnt > (9223372036854775807 - n) / delta {
4859 sys_munmap(buf,n); return 0 - 3
4860 }
4861 }
4862 let expected: i64 = n + cnt * delta
4863 var capacity: i64 = expected
4864 if capacity == 0 { capacity = 1 }
4865 let out: *u8 = sys_mmap(capacity)
4866 if (out as i64) <= 0 { sys_munmap(buf,n); return 0 - 3 }
4867 let nn: i64 = fsx_replace(buf,n,olds,ol,news,rl,out,capacity,allf)
4868 var result: i64 = 0 - 3
4869 if nn == expected { result = fsx_write(path,out,nn) }
4870 sys_munmap(out,capacity)
4871 sys_munmap(buf,n)
4872 return result
4873}
4874
4875// SELF-ANCHORED EDIT PREDICATE (pure, no I/O). Does the replacement CONTAIN its own anchor?
4876// UNIQUENESS IS TESTED AGAINST THE PRE-IMAGE; THE RETRY GUARANTEE IS A CLAIM ABOUT THE POST-IMAGE.
4877// They coincide ONLY when the replacement destroys its anchor. When `news` contains `olds` the anchor
4878// SURVIVES the apply and is STILL UNIQUE, so a retry returns OK whether or not the first call landed --
4879// the three-state table (OK=had-not-landed / NOMATCH=had-landed) collapses to ONE state and OK carries
4880// ZERO discriminating information.
4881// MEASURED 2026-09-04 over 12,806 edit calls in this laptop's transcripts: 8,308 true replaces, 4,343
4882// self-anchored (339 permil), 155 identity, sum reconciles. Seats re-issue the unsafe shape at 95 permil
4883// against a 99 permil control on the safe shape -- i.e. the retry doctrine is applied UNIFORMLY AND
4884// BLINDLY because nothing in the tool discriminated by shape. Three double-applies are confirmed in the
4885// record, plus the 2026-09-04 incident that produced two definitions of ba_confirmed and broke the gate
4886// that admits every build on this estate.
4887// The law was already banked on 2026-08-20 in nx_atomic_publish as CALLER advice keyed on a SELF-DECLARED
4888// kind=. A caller can get that declaration wrong, and one did. The primitive holds BOTH strings, so the
4889// kind is DERIVABLE rather than declarable -- and deriving it here is what makes writer and reader unable
4890// to disagree, instead of asking them to agree by discipline.
4891// Returns 1 self-anchored (retry UNSAFE) | 0 true replace (retry exact-safe).
4892// An empty anchor is NOT self-anchored: fsx_edit never reaches the apply with ol==0.
4893func fsx_edit_self_anchored(olds: *u8, news: *u8) -> i64 {
4894 let ol: i64 = vw_slen(olds)
4895 if ol == 0 { return 0 }
4896 if fsx_count_occ(news, vw_slen(news), olds, ol) > 0 { return 1 }
4897 return 0
4898}
4899
4900// ls (declared below the self-anchored-edit predicate): one entry per line "<t> <name>" (t: d=dir f=file l=link o=other; . and .. skipped).
4901// Returns entry count; -1 if the dir cannot be opened.
4902// PAGING (2026-08-05). The cap was always honest -- it declared total= and truncated=1 -- but an
4903// honest refusal is not access: knowledge/status/ holds 1027 entries, so 827 of them were simply
4904// UNREACHABLE through this tool, and a worker that listed it reported "queue empty" over a job that
4905// was sitting right there. u2605u2605u2605u2605u2605DECLARING A TRUNCATION IS NOT THE SAME AS OFFERING A WAY PAST IT --
4906// a loud cap with no next page is still a wall. `skip` is that way past.
4907// Contract preserved exactly (rule 19): fsx_ls(dir) keeps its old signature and behaviour.
4908func fsx_ls(dir: *u8) -> i64 { return fsx_ls_from(dir, 0) }
4909
4910func fsx_ls_from(dir: *u8, skip: i64) -> i64 {
4911 let fd: i64 = sys_openat_rd(dir)
4912 if fd < 0 { return fsx_fail(dir, fd) }
4913 let dbuf: *u8 = sys_mmap(FSX_DENT_BUF)
4914 var cnt: i64 = 0
4915 var shown: i64 = 0
4916 var run: i64 = 1
4917 while run == 1 {
4918 let n: i64 = sys_getdents64(fd, dbuf, FSX_DENT_BUF)
4919 if n <= 0 { run = 0 } else {
4920 var off: i64 = 0
4921 while off < n {
4922 let rec: *u8 = ((dbuf as i64 + off) as *u8)
4923 let reclen: i64 = dirent_reclen(rec)
4924 if reclen <= 0 { off = n } else {
4925 let name: *u8 = dirent_name(rec)
4926 // skip "." and ".."
4927 var isdot: i64 = 0
4928 if fsx_seq(name, "." as *u8) == 1 { isdot = 1 }
4929 if fsx_seq(name, ".." as *u8) == 1 { isdot = 1 }
4930 if isdot == 0 {
4931 if cnt >= skip { if shown < FSX_LS_CAP {
4932 let t: i64 = dirent_type(rec)
4933 if t == DT_DIR { fsx_puts("d " as *u8) } else {
4934 if t == DT_REG { fsx_puts("f " as *u8) } else {
4935 if t == DT_LNK { fsx_puts("l " as *u8) } else { fsx_puts("o " as *u8) } } }
4936 fsx_puts(name)
4937 fsx_puts("\n" as *u8)
4938 shown = shown + 1
4939 } }
4940 cnt = cnt + 1
4941 }
4942 off = off + reclen
4943 }
4944 }
4945 }
4946 }
4947 sys_close(fd)
4948 // SCALE-LAW: cap the emitted list but ALWAYS declare the true total; truncation is LOUD not silent
4949 fsx_puts("NX-FS-LS skip=" as *u8)
4950 fsx_putn(skip)
4951 fsx_puts(" shown=" as *u8)
4952 fsx_putn(shown)
4953 fsx_puts(" total=" as *u8)
4954 fsx_putn(cnt)
4955 // u26a0THE OLD PREDICATE (cnt > shown) BECOMES A LIE THE MOMENT skip EXISTS: the LAST page would
4956 // still report truncated=1 forever, so a caller paging until truncated=0 would never stop.
4957 // What actually remains is everything past the window just emitted.
4958 if cnt > skip + shown { fsx_puts(" truncated=1 (more remain -- next page: ls <dir> " as *u8); fsx_putn(skip + shown); fsx_puts(")\n" as *u8) } else { fsx_puts(" truncated=0\n" as *u8) }
4959 return cnt
4960}
4961
4962// ==== THE CLAIM-OR-OUT VERB (ES26, 2026-09-06) ====
4963// The most common ritual in the estate's action journal is two reads of the job lane -- the terminal marker
4964// (.claim) and then the output (.out): 120,106 adjacent pairs and 87,785 triples measured by nx_actlog steps.
4965// ONE call answers both. The marker decides the state and only a DONE marker earns the read of the output, so a
4966// running job never reads as dead, a never-claimed id never reads as running, and an empty output never reads as
4967// still working. States are NAMED, never guessed: a marker with no state token this reader knows is UNPARSED and
4968// printed verbatim as data. The id is digits only, so the verb cannot be aimed outside the directory it is given.
4969// fsx_job_at takes the directory so the gate drives it on a /tmp fixture; fsx_job is the production binding.
4970const FSX_JOB_DIR: *u8 = "_jobs/"
4971const FSX_JOB_PFX: *u8 = "job_"
4972const FSX_JOB_CLAIM: *u8 = ".claim"
4973const FSX_JOB_OUT: *u8 = ".out"
4974const FSX_JOB_IDMAX: i64 = 24 // a job id is an epoch-shaped integer; longer than this is not an id
4975const FSX_JOB_NOSUCH: i64 = 1 // no marker: the id was never claimed (unknown id, or the lane has not claimed it yet)
4976const FSX_JOB_RUNNING: i64 = 2 // marker reads state=CLAIMED
4977const FSX_JOB_DONE: i64 = 3 // marker reads state=DONE with bytes>0: the output was printed
4978const FSX_JOB_DONE_EMPTY: i64 = 4 // marker reads state=DONE with bytes=0: the tool produced NOTHING
4979const FSX_JOB_UNPARSED: i64 = 5 // marker present, no state token this reader knows: printed verbatim
4980const FSX_JOB_REFUSED: i64 = 6 // id is not digits-only
4981const FSX_JOB_OUT_ABSENT: i64 = 7 // marker says DONE with bytes>0 but the output file is unreadable
4982const FSX_RC_JOB_RUNNING: i64 = 8 // CLI exit for RUNNING, distinct from every other fs exit code
4983const FSX_ASCII_9: i64 = 57 // '9' (decimal parse upper bound)
4984// first offset of needle in buf[0..n), -1 when absent (flag-terminated compare, the cursor is never the sentinel)
4985func fsx_find(buf: *u8, n: i64, needle: *u8) -> i64 {
4986 var m: i64 = 0
4987 while needle[m] != (0 as u8) { m = m + 1 }
4988 if m == 0 { return 0 - 1 }
4989 var i: i64 = 0
4990 while i + m <= n {
4991 var j: i64 = 0
4992 var same: i64 = 1
4993 while j < m { if buf[i + j] != needle[j] { same = 0 } j = j + 1 }
4994 if same == 1 { return i }
4995 i = i + 1
4996 }
4997 return 0 - 1
4998}
4999// the integer right after `key` in buf[0..n); -1 when the key is absent or carries no digits
5000func fsx_kv_int(buf: *u8, n: i64, key: *u8) -> i64 {
5001 let at: i64 = fsx_find(buf, n, key)
5002 if at < 0 { return 0 - 1 }
5003 var kl: i64 = 0
5004 while key[kl] != (0 as u8) { kl = kl + 1 }
5005 var f: i64 = at + kl
5006 var v: i64 = 0
5007 var nd: i64 = 0
5008 var scan: i64 = 1
5009 while scan == 1 {
5010 if f >= n { scan = 0 } else {
5011 let c: i64 = buf[f] as i64
5012 if c < FSX_ASCII_0 { scan = 0 } else { if c > FSX_ASCII_9 { scan = 0 } else { v = v * (10 as i64) + (c - FSX_ASCII_0); nd = nd + 1; f = f + 1 } }
5013 }
5014 }
5015 if nd == 0 { return 0 - 1 }
5016 return v
5017}
5018func fsx_job_id_ok(id: *u8) -> i64 {
5019 var i: i64 = 0
5020 while id[i] != (0 as u8) {
5021 let c: i64 = id[i] as i64
5022 if c < FSX_ASCII_0 { return 0 }
5023 if c > FSX_ASCII_9 { return 0 }
5024 i = i + 1
5025 }
5026 if i == 0 { return 0 }
5027 if i > FSX_JOB_IDMAX { return 0 }
5028 return 1
5029}
5030// <dir><pfx><id><sfx> into out; returns the length
5031func fsx_job_path(dir: *u8, id: *u8, sfx: *u8, out: *u8) -> i64 {
5032 let pfx: *u8 = FSX_JOB_PFX
5033 var o: i64 = 0
5034 var i: i64 = 0
5035 while dir[i] != (0 as u8) { out[o] = dir[i]; o = o + 1; i = i + 1 }
5036 i = 0
5037 while pfx[i] != (0 as u8) { out[o] = pfx[i]; o = o + 1; i = i + 1 }
5038 i = 0
5039 while id[i] != (0 as u8) { out[o] = id[i]; o = o + 1; i = i + 1 }
5040 i = 0
5041 while sfx[i] != (0 as u8) { out[o] = sfx[i]; o = o + 1; i = i + 1 }
5042 out[o] = 0 as u8
5043 return o
5044}
5045// returns the FSX_JOB_* state; prints the marker verbatim and, on DONE with bytes>0, the output through fsx_read
5046// (truncation marked, deny-list inherited)
5047func fsx_job_at(dir: *u8, id: *u8) -> i64 {
5048 if fsx_job_id_ok(id) == 0 {
5049 fsx_puts("NX-FS-JOB REFUSED: the id must be digits only (a job number), got: " as *u8); fsx_puts(id); fsx_puts("\n" as *u8)
5050 return FSX_JOB_REFUSED
5051 }
5052 let cp: *u8 = sys_mmap(FSX_PATH_CAP)
5053 let op: *u8 = sys_mmap(FSX_PATH_CAP)
5054 fsx_job_path(dir, id, FSX_JOB_CLAIM, cp)
5055 fsx_job_path(dir, id, FSX_JOB_OUT, op)
5056 let cb: *u8 = sys_mmap(FSX_MAGIC_4095 + 1)
5057 let cn: i64 = vw_read(cp, cb, FSX_MAGIC_4095)
5058 fsx_puts("NX-FS-JOB id=" as *u8); fsx_puts(id)
5059 if cn <= 0 {
5060 fsx_puts(" NOSUCH: no marker at " as *u8); fsx_puts(cp)
5061 fsx_puts(" -- the id was never claimed by the lane (unknown id, or not claimed yet); a claimed job carries state=CLAIMED\n" as *u8)
5062 return FSX_JOB_NOSUCH
5063 }
5064 fsx_puts(" marker=" as *u8)
5065 var cl: i64 = cn
5066 var strip: i64 = 1
5067 while strip == 1 { if cl <= 0 { strip = 0 } else { if cb[cl - 1] == (FSX_NL as u8) { cl = cl - 1 } else { strip = 0 } } }
5068 sys_write(1, cb, cl)
5069 if fsx_find(cb, cn, "state=DONE" as *u8) >= 0 {
5070 let b: i64 = fsx_kv_int(cb, cn, "bytes=" as *u8)
5071 if b == 0 {
5072 fsx_puts(" DONE-EMPTY: the tool produced NOTHING (bytes=0); it is not still working\n" as *u8)
5073 return FSX_JOB_DONE_EMPTY
5074 }
5075 fsx_puts(" DONE: output follows\n" as *u8)
5076 let r: i64 = fsx_read(op, 0)
5077 if r > 0 { return FSX_JOB_DONE }
5078 return FSX_JOB_OUT_ABSENT
5079 }
5080 if fsx_find(cb, cn, "state=CLAIMED" as *u8) >= 0 {
5081 fsx_puts(" RUNNING: claimed, no terminal state yet -- the lane rewrites this marker atomically when the job ends\n" as *u8)
5082 return FSX_JOB_RUNNING
5083 }
5084 fsx_puts(" UNPARSED: no state token this reader knows -- the marker above is data, decide from it\n" as *u8)
5085 return FSX_JOB_UNPARSED
5086}
5087func fsx_job(id: *u8) -> i64 { return fsx_job_at(FSX_JOB_DIR, id) }
5088
5089// Exclusive artifact creation for the current Linux x86-64 host backend.
5090// Complete bytes are fsynced before RENAME_NOREPLACE. Unsupported filesystems fail closed.
5091// This is not a portable-ISA claim; the rename ABI matches the existing host syscall seam.
5092
5093const FXC_OPEN_EXCLUSIVE: i64 = 0xc1 // O_WRONLY | O_CREAT | O_EXCL
5094const FXC_RENAMEAT2_X86: i64 = 316
5095const FXC_RENAME_NOREPLACE: i64 = 1
5096const FXC_EXISTS: i64 = 0 - 17
5097const FXC_IO: i64 = 0 - 3
5098func fxc_create(path: *u8, body: *u8, n: i64) -> i64 {
5099 if fsx_write_denied(path) == 1 { return 0 - 2 }
5100 let plen: i64 = vw_slen(path)
5101 if plen + FSX_TMP_ROOM >= FSX_PATH_CAP { return FXC_IO }
5102 let tmp: *u8 = sys_mmap(FSX_PATH_CAP)
5103 var i: i64 = 0
5104 while i < plen { tmp[i] = path[i]; i = i + 1 }
5105 let suffix: *u8 = ".nxc" as *u8
5106 var j: i64 = 0
5107 while j < 4 { tmp[i] = suffix[j]; i = i+1; j = j+1 }
5108 i = nxi_buf(tmp,i,osp_selfpid()); tmp[i] = 0 as u8
5109 let fd: i64 = __syscall(SYS_OPENAT,AT_FDCWD,tmp,FXC_OPEN_EXCLUSIVE,MODE_0644,0,0)
5110 if fd < 0 { return FXC_IO }
5111 var off: i64 = 0
5112 while off < n {
5113 let w: i64 = sys_write(fd,((body as i64)+off) as *u8,n-off)
5114 if w <= 0 { sys_close(fd);sys_unlinkat(tmp);return FXC_IO }
5115 off = off+w
5116 }
5117 let sync: i64 = sys_fsync(fd)
5118 sys_close(fd)
5119 if sync < 0 { sys_unlinkat(tmp);return FXC_IO }
5120 let installed: i64 = __syscall(FXC_RENAMEAT2_X86,AT_FDCWD,tmp,AT_FDCWD,path,FXC_RENAME_NOREPLACE,0)
5121 if installed != 0 {
5122 // This process created tmp with O_EXCL; only its own uncommitted scratch is removed.
5123 sys_unlinkat(tmp)
5124 return installed
5125 }
5126 return n
5127}
5128
5129// base64.nx -- RFC 4648 base64 encoder + decoder.
5130//
5131// Canonical: this is the substrate-wide canonical Base64 (RFC 4648
5132// §4 standard alphabet) per [[feedback-no-tool-proliferation-bit-
5133// level]]. Variants that need URL-safe alphabet (RFC 4648 §5) are
5134// candidates for ONE distinct sibling primitive nx_base64_url.nx
5135// (queued) that imports THIS file's encode/decode skeleton; all
5136// other consumers compose THIS file's encode/decode primitives.
5137// Re-implementing the Base64 alphabet or quantum-loop inline is
5138// refused.
5139//
5140// Used for:
5141// - PEM decoding of X.509 certs (thin ASCII wrapper around DER)
5142// - TLS 1.3 pre-shared key encoding
5143// - HTTP Basic auth, OAuth tokens, JWT
5144// - Web content (data: URIs, JSON-embedded bytes)
5145//
5146// Standard alphabet (RFC 4648 §4):
5147// 0-25 : A-Z
5148// 26-51 : a-z
5149// 52-61 : 0-9
5150// 62 : +
5151// 63 : /
5152// pad : =
5153//
5154// URL-safe alphabet variant (§5) swaps +/ for -_; provided as
5155// b64url_encode / b64url_decode.
5156//
5157// Invariants:
5158// B1 Input/output lengths are predictable:
5159// encode(n bytes) -> 4 * ceil(n / 3) chars
5160// decode(n chars) -> 3 * (n / 4) - padding bytes
5161// B2 Decoder rejects invalid input (non-alphabet chars) by
5162// returning a negative length. No silent skip.
5163// B3 Decoder is tolerant of missing padding (RFC 4648 §3.2
5164// permits this as "unpadded" variant).
5165// B4 Encoder is deterministic; same input -> same output. No
5166// trailing whitespace, no line breaks inserted. Callers
5167// that want MIME-style 76-char wrap do it outside.
5168//
5169// license_tier: INDEPENDENT_REDERIVE
5170// genealogy_id: international-research-sources/ietf/rfc_8446
5171//
5172
5173// nx_safety_envelope:
5174// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
5175// sil_target: SIL1
5176// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
5177// verdict: NOT_YET_EVALUATED
5178//
5179// F-meta-4 refactor 2026-05-17: removed `import "nx_syscalls.nx"`.
5180// This file is pure -- all functions operate on caller-provided
5181// buffers + stack-local counters. Composing with any syscall layer
5182// (nx_syscalls.nx OR nx_syscalls_x86_64.nx) no longer hits the
5183// duplicate-symbol bug class. Unblocks crypto chain (nx_jwt ->
5184// nx_jose / jwk / acme / csr) for x86_64 native runtime testing.
5185
5186const B64_PAD: i64 = 0x3D // '='
5187
5188// Encode one 6-bit index to an ASCII char. Standard alphabet.
5189func b64_enc_char(n: i64) -> i64 {
5190 let v: i64 = n & 0x3F
5191 if v < 26 { return 0x41 + v } // 'A'..'Z'
5192 if v < 52 { return 0x61 + (v - 26) } // 'a'..'z'
5193 if v < 62 { return 0x30 + (v - 52) } // '0'..'9'
5194 if v == 62 { return 0x2B } // '+'
5195 return 0x2F // '/'
5196}
5197
5198// URL-safe variant: replace + / with - _
5199func b64url_enc_char(n: i64) -> i64 {
5200 let v: i64 = n & 0x3F
5201 if v < 26 { return 0x41 + v }
5202 if v < 52 { return 0x61 + (v - 26) }
5203 if v < 62 { return 0x30 + (v - 52) }
5204 if v == 62 { return 0x2D } // '-'
5205 return 0x5F // '_'
5206}
5207
5208// Decode one ASCII char to 6-bit value; returns -1 if invalid.
5209// Accepts either standard (+,/) or URL-safe (-,_) variants.
5210func b64_dec_char(c: i64) -> i64 {
5211 if c >= 0x41 { if c <= 0x5A { return c - 0x41 } } // A-Z
5212 if c >= 0x61 { if c <= 0x7A { return c - 0x61 + 26 } } // a-z
5213 if c >= 0x30 { if c <= 0x39 { return c - 0x30 + 52 } } // 0-9
5214 if c == 0x2B { return 62 } // +
5215 if c == 0x2F { return 63 } // /
5216 if c == 0x2D { return 62 } // - (URL-safe)
5217 if c == 0x5F { return 63 } // _ (URL-safe)
5218 return -1
5219}
5220
5221// Encode `n` bytes from `in_bytes` to `out`; returns written length.
5222// Output size: 4 * ceil(n / 3) chars. Pads with '=' to full groups.
5223func b64_encode(in_bytes: *u8, n: i64, out: *u8) -> i64 {
5224 var pos: i64 = 0
5225 var out_pos: i64 = 0
5226 while pos + 3 <= n {
5227 let b0: i64 = in_bytes[pos]
5228 let b1: i64 = in_bytes[pos + 1]
5229 let b2: i64 = in_bytes[pos + 2]
5230 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F)
5231 out[out_pos + 1] = b64_enc_char(((b0 << 4) | (b1 >> 4)) & 0x3F)
5232 out[out_pos + 2] = b64_enc_char(((b1 << 2) | (b2 >> 6)) & 0x3F)
5233 out[out_pos + 3] = b64_enc_char(b2 & 0x3F)
5234 pos = pos + 3
5235 out_pos = out_pos + 4
5236 }
5237 let remain: i64 = n - pos
5238 if remain == 1 {
5239 let b0: i64 = in_bytes[pos]
5240 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F)
5241 out[out_pos + 1] = b64_enc_char((b0 << 4) & 0x3F)
5242 out[out_pos + 2] = B64_PAD
5243 out[out_pos + 3] = B64_PAD
5244 out_pos = out_pos + 4
5245 }
5246 if remain == 2 {
5247 let b0: i64 = in_bytes[pos]
5248 let b1: i64 = in_bytes[pos + 1]
5249 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F)
5250 out[out_pos + 1] = b64_enc_char(((b0 << 4) | (b1 >> 4)) & 0x3F)
5251 out[out_pos + 2] = b64_enc_char((b1 << 2) & 0x3F)
5252 out[out_pos + 3] = B64_PAD
5253 out_pos = out_pos + 4
5254 }
5255 return out_pos
5256}
5257
5258// Grab one sextet: return 0..63 on valid, -1 on '=' or end-of-input,
5259// -2 on any other invalid char. Advances *pos on success.
5260func b64_grab(in_chars: *u8, n: i64, pos: *i64) -> i64 {
5261 let p: i64 = *pos
5262 if p >= n { return -1 }
5263 let c: i64 = in_chars[p]
5264 if c == B64_PAD {
5265 *pos = n + 1
5266 return -1
5267 }
5268 let v: i64 = b64_dec_char(c)
5269 if v < 0 { return -2 }
5270 *pos = p + 1
5271 return v
5272}
5273
5274// Decode `n` base64 chars into raw bytes. Returns bytes written on
5275// success or -1 on invalid input. Tolerant of missing padding (B3).
5276// Whitespace is NOT skipped.
5277//
5278// F-meta-4 refactor: position counter is a stack-local; no sys_mmap.
5279func b64_decode(in_chars: *u8, n: i64, out: *u8) -> i64 {
5280 var pos: i64 = 0
5281 var out_pos: i64 = 0
5282 while pos < n {
5283 let s0: i64 = b64_grab(in_chars, n, &pos)
5284 if s0 == -2 { return -1 }
5285 if s0 < 0 { return out_pos }
5286 let s1: i64 = b64_grab(in_chars, n, &pos)
5287 if s1 == -2 { return -1 }
5288 if s1 < 0 { return -1 } // single lonely char invalid
5289 out[out_pos] = ((s0 << 2) | (s1 >> 4)) & 0xFF
5290 out_pos = out_pos + 1
5291 let s2: i64 = b64_grab(in_chars, n, &pos)
5292 if s2 == -2 { return -1 }
5293 if s2 < 0 { return out_pos }
5294 out[out_pos] = ((s1 << 4) | (s2 >> 2)) & 0xFF
5295 out_pos = out_pos + 1
5296 let s3: i64 = b64_grab(in_chars, n, &pos)
5297 if s3 == -2 { return -1 }
5298 if s3 < 0 { return out_pos }
5299 out[out_pos] = ((s2 << 6) | s3) & 0xFF
5300 out_pos = out_pos + 1
5301 }
5302 return out_pos
5303}
5304
5305// Self-test main() removed by F-meta-4 refactor (used sys_mmap; this
5306// file is now syscall-free). Round-trip smoke lives in a separate
5307// nx_base64_test.nx that imports a caller-chosen syscall layer.
5308
5309
5310
5311const GPA_HEADER_BYTES: i64 = 8
5312
5313struct GpaWasmCursor {
5314 data: *u8,
5315 size: i64,
5316 pos: i64,
5317}
5318struct GpaMemory {
5319 minimum: i64,
5320 maximum: i64,
5321 count: i64,
5322}
5323// WebAssembly u32 LEB128: at most five bytes, with only four payload bits in the last.
5324func gpa_u32(c: *GpaWasmCursor) -> i64 {
5325 var value: i64 = 0
5326 var shift: i64 = 0
5327 while shift < 35 {
5328 if c.pos >= c.size { return -1 }
5329 let b: i64 = c.data[c.pos] as i64
5330 c.pos = c.pos+1
5331 if shift == 28 { if b > 15 { return -1 } }
5332 value = value | ((b & 127) << shift)
5333 if (b & 128) == 0 { return value }
5334 shift = shift+7
5335 }
5336 return -1
5337}
5338func gpa_wasm_header(data: *u8,n: i64) -> i64 {
5339 if n < GPA_HEADER_BYTES { return 0 }
5340 let header: *u8 = "\x00asm\x01\x00\x00\x00" as *u8
5341 return gpa_same(data,GPA_HEADER_BYTES,header,GPA_HEADER_BYTES)
5342}
5343func gpa_wasm_name(c: *GpaWasmCursor,name: *u8) -> i64 {
5344 let n: i64 = gpa_u32(c)
5345 if n < 0 || n > c.size-c.pos { return 0 }
5346 let ok: i64 = gpa_same((c.data as i64+c.pos) as *u8,n,name,vw_slen(name))
5347 c.pos = c.pos+n
5348 return ok
5349}
5350// This packager supports the compiler's one-memory, no-other-import twin contract.
5351// Other valid Wasm layouts require a separate supported contract, never silent guessing.
5352func gpa_memory_section(data: *u8,n: i64,shared: i64,m: *GpaMemory) -> i64 {
5353 let c: *GpaWasmCursor = sys_mmap(__size_of(GpaWasmCursor)) as *GpaWasmCursor
5354 c.data=data; c.size=n; c.pos=0
5355 var ok: i64 = 1
5356 if gpa_u32(c) != 1 { ok=0 }
5357 if shared == 1 {
5358 if gpa_wasm_name(c,"env") != 1 { ok=0 }
5359 if gpa_wasm_name(c,"memory") != 1 { ok=0 }
5360 if gpa_u32(c) != 2 { ok=0 }
5361 }
5362 let flags: i64 = gpa_u32(c)
5363 if shared == 1 { if flags != 3 { ok=0 } } else { if flags != 0 && flags != 1 { ok=0 } }
5364 let minimum: i64 = gpa_u32(c)
5365 var maximum: i64 = minimum
5366 if (flags & 1) == 1 { maximum=gpa_u32(c) }
5367 // 2^16 pages is the wasm32 address-space ceiling, not a workload budget.
5368 if minimum <= 0 || maximum < minimum || maximum > 65536 { ok=0 }
5369 if c.pos != n { ok=0 }
5370 if m.count != 0 { ok=0 }
5371 if ok == 1 { m.minimum=minimum; m.maximum=maximum; m.count=1 }
5372 sys_munmap(c as *u8,__size_of(GpaWasmCursor))
5373 return ok
5374}
5375func gpa_body_section(c: *GpaWasmCursor,shared: i64,m: *GpaMemory,span: *GpaWasmCursor) -> i64 {
5376 while c.pos < c.size {
5377 let start: i64 = c.pos
5378 let id: i64 = c.data[c.pos] as i64
5379 c.pos=c.pos+1
5380 let n: i64 = gpa_u32(c)
5381 if n < 0 || n > c.size-c.pos { return -1 }
5382 let payload: *u8 = (c.data as i64+c.pos) as *u8
5383 c.pos=c.pos+n
5384 if id == 2 || id == 5 {
5385 if (shared == 1 && id != 2) || (shared == 0 && id != 5) { return -1 }
5386 if gpa_memory_section(payload,n,shared,m) != 1 { return -1 }
5387 } else {
5388 span.data=(c.data as i64+start) as *u8; span.size=c.pos-start
5389 return 1
5390 }
5391 }
5392 return 0
5393}
5394// Equal non-memory sections bind code, exports, types and data to the same engine.
5395// This is pair identity checking, not complete Wasm validation or runtime qualification.
5396func gpa_pair(plain: *u8,pn: i64,twin: *u8,tn: i64,memory: *GpaMemory) -> i64 {
5397 if gpa_wasm_header(plain,pn) != 1 || gpa_wasm_header(twin,tn) != 1 { return 0 }
5398 let a: *GpaWasmCursor=sys_mmap(__size_of(GpaWasmCursor)) as *GpaWasmCursor
5399 let b: *GpaWasmCursor=sys_mmap(__size_of(GpaWasmCursor)) as *GpaWasmCursor
5400 let sa: *GpaWasmCursor=sys_mmap(__size_of(GpaWasmCursor)) as *GpaWasmCursor
5401 let sb: *GpaWasmCursor=sys_mmap(__size_of(GpaWasmCursor)) as *GpaWasmCursor
5402 let ma: *GpaMemory=sys_mmap(__size_of(GpaMemory)) as *GpaMemory
5403 let mb: *GpaMemory=sys_mmap(__size_of(GpaMemory)) as *GpaMemory
5404 a.data=plain; a.size=pn; a.pos=GPA_HEADER_BYTES
5405 b.data=twin; b.size=tn; b.pos=GPA_HEADER_BYTES
5406 var ok: i64=1
5407 while ok == 1 {
5408 let ra: i64=gpa_body_section(a,0,ma,sa)
5409 let rb: i64=gpa_body_section(b,1,mb,sb)
5410 if ra < 0 || rb < 0 || ra != rb { ok=0; break }
5411 if ra == 0 { break }
5412 if gpa_same(sa.data,sa.size