nx_surface_boundary_expanded_t207.nx source
↩ module page · 3045 lines · 170610 B
1// nx_quicksort.nx -- Hoare quicksort (in-place, last-element pivot).
2//
3// Canonical: this is the substrate-wide canonical quicksort per
4// [[feedback-no-tool-proliferation-bit-level]]. Other primitives
5// needing quicksort MUST `import "nx_quicksort.nx"` and compose;
6// re-implementing inline is refused per the cardinal. nx_qsort.nx +
7// nx_quickselect.nx may carry distinct semantics (selection vs sort)
8// but MUST declare "Distinct from [[nx_quicksort.nx]] because: ..."
9// in their headers.
10//
11// genealogy_id: hoare_1961_quicksort
12// lineage_id: in_place_comparison_sort
13// references: Hoare 'Quicksort' CACM 4(7):321, 1961.
14// Sedgewick 1978 implementation analysis.
15// license: public_domain
16// complexity: avg O(n log n), worst O(n^2).
17//
18// Tier discipline (no bare i64 in API surface):
19// - elements: *nx_int (swappable via nx_tier.nx)
20// - indices: nx_idx (platform-pointer-width)
21// - status: nx_int (small value; not platform-mandated i64)
22// The only place `i64` appears in NishiLang source is the entrypoint
23// main() return, where the kernel exit syscall ABI mandates 64-bit.
24
25// nx_safety_envelope:
26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
27// sil_target: SIL1
28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
29// verdict: NOT_YET_EVALUATED
30
31// syscalls.nx -- thin __syscall wrappers used across modules.
32//
33// Sovereign path: no libc. Every memory allocation, file op, and
34// clock read in the rest of the runtime routes through one of these
35// helpers. Numbers match Linux RV64; NishiOS uses the same set.
36//
37// Extracted from runtime.nx and ir.nx's copy-pasted helpers so the
38// module-import build doesn't produce duplicate symbols.
39
40// Tier aliases (nx_size / nx_idx / nx_fd / ...) ride along with the
41// syscall shelf: 141 runtime files use `as nx_size` etc. and only
42// compiled historically because the old parser silently void-cast
43// unknown type names (T#nx-int-alias-size-0 closed that hole LOUDLY,
44// which exposed the missing import). nx_tier.nx is pure type
45// aliases (0 funcs); prepass_register_aliases skips duplicates, so
46// modules that also import it directly stay fine.
47// nx_tier.nx -- substrate-wide tier configuration.
48//
49// Single point of edit for scale-agnostic substrate. Per user
50// directive 2026-05-13: "with the i64 it looks hardcoded everywhere
51// if we really want this dynamic dont we want that to be a changeable
52// value everywhere so it can switch to i128 and i256 etc."
53//
54// Per cardinals:
55// - feedback-numeric-tier-ladder.md (N0..N9 swap)
56// - feedback-scale-agnostic-substrate.md (MCU..HPC swap)
57// - feedback-substrate-additive-not-restrictive.md (declare cost)
58//
59// SEMANTIC ALIASES (not all should swap simultaneously):
60//
61// nx_int -- DEFAULT ARITHMETIC integer. Swappable across the
62// numeric tier ladder. Swap this to i128 to make the
63// entire substrate compute in 128-bit integers.
64//
65// nx_size -- MEMORY-SIZE integer. Always platform-pointer-width.
66// Used for buffer sizes, mmap byte counts, struct
67// sizes. Does NOT swap with nx_int -- changing this
68// would break pointer arithmetic. Stays i64 on RV64.
69//
70// nx_idx -- ARRAY-INDEX integer. Same width as nx_size on
71// flat-memory targets. Distinct alias so future
72// GPU/distributed targets can change indexing without
73// touching arithmetic.
74//
75// nx_byte -- The byte type. Stays u8. Distinct alias so MCU
76// targets that emulate u16-byte memory could rebind.
77//
78// HARDWARE-TIER BUFFER SIZES (declare cost, don't restrict):
79//
80// NX_BUF_TINY -- 64 B (MCU-friendly; stack-safe)
81// NX_BUF_SMALL -- 256 B (MCU heap-friendly)
82// NX_BUF_MEDIUM -- 4096 B (page-size; workstation default)
83// NX_BUF_LARGE -- 64 KiB (server-friendly)
84// NX_BUF_HUGE -- 1 MiB (HPC; assumes virtual memory)
85//
86// Use these instead of `sys_mmap(4096)` etc. so the substrate
87// announces its memory footprint and tier-incompatible code can
88// be flagged by audit.
89//
90// HARDWARE TIER (informational; downstream code may branch):
91//
92// NX_TIER_MCU = 0 -- microcontroller, kilobytes RAM
93// NX_TIER_SOVEREIGN_CHIP = 1 -- custom silicon, ~MB RAM
94// NX_TIER_FAMILY_DEVICE = 2 -- phone/router, ~GB RAM
95// NX_TIER_WORKSTATION = 3 -- laptop/desktop, ~10-100 GB RAM
96// NX_TIER_SERVER = 4 -- server-class, ~TB RAM
97// NX_TIER_HPC = 5 -- cluster, distributed
98//
99// COMPILE-TIME SWAP for nx_int (uncomment exactly one line):
100
101// THIS FILE IS THE SINGLE DEFINITION SITE for substrate-wide types.
102// Per user directive 2026-05-13: only this file (and platform-ABI
103// definition files like nx_syscalls.nx) should declare bare i64.
104// Every other substrate module uses the aliases below.
105
106// ===== arithmetic-tier aliases (swappable per nx_int tier ladder) =====
107
108type nx_int = i64 // N1 -- default; 9 quintillion, fits all physical scales
109// type nx_int = i32 // N0 -- MCU / embedded
110// type nx_int = i128 // N2 -- queued; needs nx_i128 backend ops
111// type nx_int = i256 // N3 -- shipped (nx_i256.nx); cosmology / crypto
112
113// ===== platform-width aliases (stay at pointer width) =================
114
115type nx_size = i64 // memory-size / byte-count
116type nx_idx = i64 // array-index
117type nx_byte = u8 // single-byte unit
118
119// ===== POSIX/Linux platform-ABI aliases (mandated 64-bit on RV64) ====
120//
121// Each is a 64-bit integer by Linux RV64 ABI. Renamed here so substrate
122// code never writes bare `i64` for these semantic types.
123
124type nx_fd = i64 // file descriptor (kernel-mandated width)
125type nx_exit = i64 // exit / status code (main() return)
126type nx_pid = i64 // process id
127type nx_uid = i64 // user id
128type nx_gid = i64 // group id
129type nx_syscall_num = i64 // Linux syscall number
130type nx_off = i64 // file offset (off_t)
131type nx_errno = i64 // errno (negative on syscall failure)
132
133// ===== SEMANTIC TYPE GENEALOGY (added 2026-05-20) ======================
134//
135// Per cardinal [[feedback-type-genealogy-math-cardinal-not-script]]
136// AND its immediate refinement (same session): every alias collapsing
137// to i64 is "y2k incestuous" -- relabeling, not genealogy. Real
138// semantic types pick the APPROPRIATE underlying width based on
139// the physics of the values they represent:
140//
141// - Small sealed enums (15 outcomes, 18 probe kinds) -> u8
142// - Display pixel coords (~32M max realistic) -> i32
143// - Q10 / Q14 fixed-point (values * 1024 / 16384) -> i32
144// - 32-bit color packs (RGBA8888) -> u32
145// - Q20 fixed-point (values * 1048576) -> i64
146// - Wide color packs (RGBA16161616, PRESERVE_ALL) -> u64
147// - Timestamps (ns / us / ms / cycles) -> i64 (2038 Y2K38)
148// - 64-bit hash digests -> u64
149// - Cryptographic hashes (SHA-256, SHA-512) -> STRUCT (multi-word; queued)
150// - Virtual addresses on 64-bit ISA -> u64
151//
152// Each type is a child of its PHYSICALLY-APPROPRIATE parent
153// (i8/u8/i32/u32/i64/u64), not blanket-i64. This breaks the
154// y2k-incestuous trap where renaming i64 N ways pretends to be
155// type discipline while every value silently shares one width.
156
157// ----- TIME family (all i64; ns/us/ms/cycles legitimately need it) -----
158// 2038 Y2K38 lurks for 32-bit time_t; i64 is the substrate-honest
159// choice. ms/us/ns + cycles all i64. s_q14 needs only i32 range
160// (val*16384 fits comfortably in i32 for typical second scales) but
161// we stay at i64 to compose cleanly with the i64 time arithmetic
162// across the substrate.
163type nx_ns = i64 // nanoseconds (since boot, monotonic)
164type nx_us = i64 // microseconds (since boot, monotonic)
165type nx_ms = i64 // milliseconds (since epoch, wall)
166type nx_s_q14 = i64 // seconds in Q14 fixed-point
167type nx_cycles = i64 // CPU cycle count
168
169// ----- HASH family (non-cryptographic 64-bit; crypto = STRUCT) -----
170// FNV-1a / xxhash digest is u64 by spec. SHA-256 / SHA-512 / BLAKE
171// hashes are MULTI-WORD; they're declared as structs in
172// nx_sha256.nx / nx_sha512.nx / nx_blake2b.nx (each carries its own
173// fixed-size byte array; NOT i64).
174type nx_hash64 = u64 // FNV-1a / xxhash / truncated SHA -- 64-bit digest
175
176// ----- ETG family (sealed enums; small value space -> u8) -----
177// nx_outcome_id sealed enum has 11 values; u8 fits 256
178// nx_probe_kind sealed enum has 18 values; u8 fits 256
179// nx_claim_source sealed enum has 13 values; u8 fits 256
180// nx_silicon_serial is a content-addressed identity HASH; u64.
181type nx_outcome_id = u8 // NX_ETG_OUTCOME_* (11 values; u8 fits)
182type nx_probe_kind = u8 // NX_ETG_PROBE_* (18 values; u8 fits)
183type nx_claim_source = u8 // NX_ETG_CLAIM_* (13 values; u8 fits)
184type nx_silicon_serial = u64 // per-die identity hash (cryptographic-strength width)
185
186// ----- PERF family (sealed enums) -----
187type nx_pathology_id = u8 // NX_PERF_PATH_* (15 values; u8 fits)
188type nx_flow_state_id = u8 // NX_FLOW_STATE_* (6 values; u8 fits)
189
190// ----- FIXED-POINT family (width chosen by precision*range) -----
191// Q10: value * 1024. Typical seed values are 0..255 so q10 max is
192// ~261K; i32 holds up to ~2.1B -> plenty of headroom.
193// Q14: value * 16384. Typical max around 16K of seed -> q14 ~ 2.6e8;
194// i32 holds up to 2.1e9 -> headroom for a few decimal seconds.
195// Q20: value * 1048576. Wider precision; needs i64 to avoid wrap.
196type nx_q10 = i32 // val * 1024; ~0.001 precision
197type nx_q14 = i32 // val * 16384; ~6e-5 precision
198type nx_q20 = i64 // val * 1048576; ~1e-6 precision
199
200// ----- GRAPHICS family (display coords + color packs at real widths) -----
201// Modern displays are well within 32-bit pixel addressing.
202// 8K display = 7680x4320 pixels. i32 holds 2.1B -> plenty.
203// nx_color_rgba8 = 32-bit packed RGBA (the common case)
204// nx_color_rgba16 = 64-bit packed RGBA16161616 (HDR / wide gamut)
205type nx_pixel_x = i32 // screen X in pixels
206type nx_pixel_y = i32 // screen Y in pixels
207type nx_color_rgba8 = u32 // RGBA8888 packed
208type nx_color_rgba16 = u64 // RGBA16161616 packed (HDR / preserve-all)
209
210// ----- PERCEPTUAL family (sealed enum; small value space) -----
211// nx_perceptual_profile has ~40 declared values up through
212// NX_PERCEPT_PRESERVE_ALL = 9999. Sentinel value 9999 needs i16,
213// not u8. i16 fits -32768..32767 with room for sentinels.
214type nx_perceptual_profile = i16 // NX_PERCEPT_* (~40 values + 9999 sentinel)
215
216// ----- ADDRESS family (virtual addresses on 64-bit ISA) -----
217// Pointer-width is u64 on all our supported 64-bit targets
218// (RV64 / x86_64 / AArch64 / ppc64le / loongarch64 / mips64 /
219// s390x / RV32 uses u32 -- TODO: tier-conditional).
220type nx_addr = u64 // raw virtual address (caller casts to *u8)
221
222// nx_capability_manifest:
223// variant_class: tier_config
224// variant_id: tier_config_v1_global
225// requires_isa: [rv32i, rv32imac, rv64imac, rv64imacv, x86_64, aarch64, armv7a, cortex_m, avr, xtensa, wasm32]
226// requires_syscalls: []
227// requires_ram_min_b: 0 // pure-const + typedef module, no runtime cost
228// tier_floor: NX_TIER_MCU
229// tier_ceiling: NX_TIER_HPC
230// cost_model:
231// flops_per_n: 0.0
232// bytes_per_n: 0.0
233// syscalls_per_n: 0.0
234// adversary_class: THREAT_OPPORTUNISTIC
235//
236// Note: This file is the substrate's TIER ENUM SOURCE OF TRUTH. It
237// has no variants by design (it IS the variant_class taxonomy that
238// other primitives' tier_floor / tier_ceiling reference). Manifest
239// declared for hygiene completeness; selector will skip it.
240
241// ---- buffer-size constants (use instead of bare numbers) -------
242
243const NX_BUF_TINY: nx_size = 64
244const NX_BUF_SMALL: nx_size = 256
245const NX_BUF_MEDIUM: nx_size = 4096
246const NX_BUF_LARGE: nx_size = 65536
247const NX_BUF_HUGE: nx_size = 1048576
248
249// ---- hardware tier sentinels -----------------------------------
250
251const NX_TIER_MCU: nx_int = 0
252const NX_TIER_SOVEREIGN_CHIP: nx_int = 1
253const NX_TIER_FAMILY_DEVICE: nx_int = 2
254const NX_TIER_WORKSTATION: nx_int = 3
255const NX_TIER_SERVER: nx_int = 4
256const NX_TIER_HPC: nx_int = 5
257
258// ---- numeric tier sentinels (informational) --------------------
259
260const NX_NUM_N0_I32: nx_int = 0
261const NX_NUM_N1_I64: nx_int = 1
262const NX_NUM_N2_I128: nx_int = 2
263const NX_NUM_N3_I256: nx_int = 3
264const NX_NUM_N4_I512: nx_int = 4
265const NX_NUM_N5_BIGINT: nx_int = 5
266
267// ---- byte-width of substrate types (replace bare `8` / `4`) ----
268//
269// Use these wherever you need the byte count of a substrate type --
270// e.g., sys_mmap(N * NX_SIZEOF_NX_SIZE) to allocate N nx_size slots.
271// Swap nx_int's underlying type and ONLY this constant changes.
272
273const NX_SIZEOF_NX_INT: nx_size = 8 // nx_int currently i64 -> 8 bytes
274const NX_SIZEOF_NX_SIZE: nx_size = 8 // nx_size always pointer-width
275const NX_SIZEOF_NX_IDX: nx_size = 8 // nx_idx alias of nx_size
276
277// ---- POSIX stdio file descriptors (replace bare 0/1/2) ---------
278
279const NX_FD_STDIN: nx_fd = 0
280const NX_FD_STDOUT: nx_fd = 1
281const NX_FD_STDERR: nx_fd = 2
282
283const SYS_MAGIC_1024: i64 = 1024
284const SYS_MAGIC_1000000: i64 = 1000000
285const SYS_MAGIC_4294967296: i64 = 4294967296
286// first read window for a size-UNKNOWABLE file (lseek END <= 0); doubles while it fills -- see sys_read_file
287const SYS_READ_GROW_INIT: i64 = 65536
288const SYS_MAGIC_100000: i64 = 100000
289
290// ---- syscall numbers (per-target) ----
291//
292// Cross-target via the macro processor (cardinal landed 2026-05-20:
293// feedback-hardware-agnostic-is-robustness -- the substrate must
294// compile + run on every silicon we point it at). Default path
295// (TARGET_X86_64 not defined) carries Linux RV64 numbers used by
296// qemu-RV64 + NishiOS. When nxc2 is invoked with --target x86_64
297// main.c pre-defines @macro TARGET_X86_64 1 so this file resolves
298// to x86_64 Linux ABI numbers.
299//
300// nx_syscalls_x86_64.nx remains the dedicated x86_64-only mirror
301// for files that want explicit single-target imports (e.g., bench
302// smokes built only for x86_64). This block makes nx_syscalls.nx
303// itself dual-target so substrate primitives compile portably.
304
305@ifdef TARGET_X86_64
306const SYS_READ: i64 = 0
307const SYS_WRITE: i64 = 1
308const SYS_CLOSE: i64 = 3
309const SYS_LSEEK: i64 = 8
310const SYS_OPENAT: i64 = 257
311const SYS_EXIT: i64 = 60
312const SYS_MMAP: i64 = 9
313const SYS_CLOCK_GETTIME: i64 = 228
314const SYS_IOCTL: i64 = 16
315const SYS_CLOCK_NANOSLEEP: i64 = 230
316// Namespace/container family, x86 branch (debt 1785528831). Moved here from
317// nx_syscalls_x86_64.nx so ONE module owns the wrapper set -- a TU reaching both
318// modules used to hold every wrapper TWICE, resolved silently by definition ORDER.
319const SYS_CHROOT: i64 = 161
320const SYS_MOUNT: i64 = 165
321const SYS_UNSHARE: i64 = 272
322const SYS_GETUID: i64 = 102
323const SYS_GETGID: i64 = 104
324const SYS_POLL: i64 = 7
325@endif
326
327@ifndef TARGET_X86_64
328const SYS_READ: i64 = 63
329const SYS_WRITE: i64 = 64
330const SYS_CLOSE: i64 = 57
331const SYS_LSEEK: i64 = 62
332const SYS_OPENAT: i64 = 56
333const SYS_EXIT: i64 = 93
334const SYS_MMAP: i64 = 222
335const SYS_CLOCK_GETTIME: i64 = 113
336const SYS_IOCTL: i64 = 29
337const SYS_CLOCK_NANOSLEEP: i64 = 115
338// Namespace/container family, RV64 branch (debt 1785528831). This is the branch actually
339// KEPT (TARGET_X86_64 is hard-pinned undefined), so these are the numbers the x86 backend
340// translates at emit: 51->161 chroot, 40->165 mount, 97->272 unshare, 174->102 getuid,
341// 176->104 getgid. The 40 and 51 rows were added to x86ctx_rv64_to_x86_64_syscall and
342// shipped FIRST -- without them both would pass through to the WRONG x86 syscall
343// (sendfile / getsockname), silently, because that translator's default is `return num`.
344const SYS_CHROOT: i64 = 51
345const SYS_MOUNT: i64 = 40
346const SYS_UNSHARE: i64 = 97
347const SYS_GETUID: i64 = 174
348const SYS_GETGID: i64 = 176
349const SYS_POLL: i64 = 73
350@endif
351
352func sys_ioctl(fd: i64, request: i64, arg: i64) -> i64 {
353 return __syscall(SYS_IOCTL, fd, request, arg, 0, 0, 0)
354}
355
356// poll(2): wait for events on fds. fds points to an array of `nfds`
357// struct pollfd { i32 fd; i16 events; i16 revents } (8 bytes each).
358// timeout_ms < 0 = block forever, 0 = return immediately. Returns the
359// count of ready fds (>0), 0 on timeout, or -errno. Used by the
360// substrate's own network diagnostics (bounded non-blocking connect)
361// instead of reaching for external tools. (rv64 const = ppoll; this
362// wrapper only runs on the x86_64 target.)
363func sys_poll(fds: *u8, nfds: i64, timeout_ms: i64) -> i64 {
364 return __syscall(SYS_POLL, fds, nfds, timeout_ms, 0, 0, 0)
365}
366
367// ---- core wrappers ----
368
369func sys_write(fd: i64, buf: *u8, count: i64) -> i64 {
370 return __syscall(SYS_WRITE, fd, buf, count, 0, 0, 0)
371}
372
373func sys_read(fd: i64, buf: *u8, count: i64) -> i64 {
374 return __syscall(SYS_READ, fd, buf, count, 0, 0, 0)
375}
376
377func sys_close(fd: i64) -> i64 {
378 return __syscall(SYS_CLOSE, fd, 0, 0, 0, 0, 0)
379}
380
381// chdir. The compiler only rv64->x86 translates CONSTANT syscall numbers (x86ctx_emit_syscall:
382// VK_CONST_INT); chdir is absent from that table, so a constant 49 falls through to x86_64 bind and a
383// constant 80 is mapped to fstat -- BOTH gave EBADF (PROBE-PROVEN by test_chdir). The documented escape
384// (nx_x86_64_ctx.nx:1004 "Runtime-computed syscall number -- load as-is") is to make op0 RUNTIME: a memory
385// load can't be folded to VK_CONST_INT, so the raw x86_64 number 80 passes through untranslated = real
386// chdir. Used by the supervisor to set a spawned daemon's CWD before execve. 0 on success, -errno on fail.
387func sys_chdir(path: *u8) -> i64 {
388 let nbox: *i64 = sys_mmap(16) as *i64
389 nbox[0] = 80 // x86_64 chdir, forced runtime so the rv64->x86 xlate is skipped
390 return __syscall(nbox[0], path as i64, 0, 0, 0, 0, 0)
391}
392
393// getcwd -- SAME runtime-number escape as sys_chdir directly above, for the same documented reason: the
394// rv64->x86 translator only rewrites CONSTANT syscall numbers, and getcwd is absent from that table, so a
395// constant would be mangled exactly as chdir's was. A memory load cannot be folded to VK_CONST_INT, so the
396// raw x86_64 number passes through untranslated.
397// WHY THIS EXISTS (2026-08-14): the shim had sys_chdir but NOTHING to ask where we are. Every organ that
398// resolves a path against the CWD could therefore only print a RELATIVE path -- a claim whose truth depends
399// on invisible state. Three separate working-directory faults in one session stayed invisible until they
400// bit, and in each the reader could not tell "the file is missing" from "I am standing somewhere else".
401// ★★★AN ORGAN THAT CANNOT REPORT WHERE IT IS CANNOT WRITE AN HONEST PATH.
402// Returns the byte length written INCLUDING the terminator, or -errno (notably -ERANGE if cap is short).
403// SYS_PATH_MAX is exported so a caller never hand-writes the size: the FIRST consumer of sys_getcwd (this
404// author, minutes after adding it) wrote `sys_mmap(4096)` and `sys_getcwd(buf, 4096)` on consecutive
405// lines -- a bare literal AND a duplicate-authored pair, the exact shape being removed elsewhere the same
406// day. ★★A NEW PRIMITIVE THAT DOES NOT EXPORT ITS OWN SIZE INVITES EVERY CALLER TO INVENT ONE.
407const SYS_PATH_MAX: i64 = 4096 // Linux PATH_MAX; getcwd returns -ERANGE below it
408// The DIRECTORY sibling of MODE_0644, added on the same evidence: `0x1ed` appears at 569 sites in
409// buildroot/runtime (nx_shelltool, corpus_complete=1), i.e. the estate scatters TWO file-mode constants,
410// not one. Named here so the pair lives together and a reader meets both at the same place.
411const MODE_0755: i64 = 0x1ed // rwxr-xr-x : default mode for a created directory
412func sys_getcwd(buf: *u8, cap: i64) -> i64 {
413 let nbox: *i64 = sys_mmap(16) as *i64
414 nbox[0] = 79 // x86_64 getcwd, forced runtime so the rv64->x86 xlate is skipped
415 return __syscall(nbox[0], buf as i64, cap, 0, 0, 0, 0)
416}
417
418// ⚠AT_FDCWD MOVED UP 2026-07-20 -- IT WAS A LIVE MISCOMPILE. This const was declared ~60 lines BELOW
419// (in the openat block) while sys_unlinkat and sys_fchmodat immediately below REFERENCE it. A module
420// const referenced ABOVE its declaration does not resolve, and nx_cc silently substituted CONSTANT 0
421// -- so both wrappers passed dirfd=0 (stdin) instead of -100. Absolute paths survive that (openat
422// ignores dirfd when the path is absolute), RELATIVE paths do not, which is exactly why unlinkat was
423// long recorded as flaky and "passing only by luck". Surfaced by the new unknown-identifier
424// diagnostic, which turned a silent 0 into a compile error. LAW (already banked, now enforced):
425// module-wide consts/statics go ABOVE every possible reader.
426const AT_FDCWD: i64 = -100
427
428// unlinkat(AT_FDCWD, path, 0) -- delete a file. x86_64 263 is a PROVEN pass-through (not an rv64 key),
429// but this is THE canonical home: 5+ organs hand-rolled `__syscall(263,...)` before this landed (DRY,
430// 2026-07-20). 0 on success, -errno on fail.
431func sys_unlinkat(path: *u8) -> i64 {
432 return __syscall(263, AT_FDCWD, path as i64, 0, 0, 0, 0)
433}
434
435// fchmodat(AT_FDCWD, path, mode) -- chmod by path. ⚠a CONSTANT 268 gets rv64->x86 TRANSLATED to the
436// wrong syscall (silent no-op chmod -- cost a vacuous-permission-test debug cycle, 2026-07-20), so the
437// number is forced RUNTIME via the sys_chdir nbox pattern. 0 on success, -errno on fail.
438func sys_fchmodat(path: *u8, mode: i64) -> i64 {
439 let nbox: *i64 = sys_mmap(16) as *i64
440 nbox[0] = 268 // x86_64 fchmodat, forced runtime so the xlate is skipped
441 return __syscall(nbox[0], AT_FDCWD, path as i64, mode, 0, 0, 0)
442}
443
444// exit_group(2) -- terminate ALL tasks in the thread group. Raw x86_64 231
445// (231 is NOT an rv64 key in the compiler's swap table, so it passes through
446// untranslated -- the munmap-11 precedent). THE explicit program-exit call
447// once a process holds live nx_thread_pool workers: CLONE_VM tasks are
448// separate PIDs, so plain sys_exit (93 -> x86 60, single task) leaves them
449// running, holding stdout open and wedging any pipeline that waits for EOF
450// (found 2026-07-07: the shared-pool matmul dispatcher hung the build lane
451// this way). Return-from-main already exit_groups via the _start trampoline;
452// use THIS for explicit early program exit. Per-THREAD exit stays sys_exit
453// (see nx_thread_exit).
454func sys_exit_group(code: i64) -> i64 {
455 return __syscall(231, code, 0, 0, 0, 0, 0)
456}
457
458// setpriority(PRIO_PROCESS=0, who=0 -> SELF, prio) -- x86_64 syscall 141.
459// Lower priority = larger nice value; 19 is the maximum yield.
460// WHY A WRAPPER AND NOT AN OPERATOR STEP (measured 2026-07-30): a bulk media
461// migration walk saturated the NAS; every forked organ queued behind its I/O so
462// EVERY agent MCP call 503'd for minutes -- the control plane went blind while a
463// background job did exactly what it was told. `renice 19` on the running pid
464// restored interactive service at once.
465// LAW: a long-running BULK job must yield to the interactive control plane BY
466// CONSTRUCTION at its own launch, not when an operator notices. Bind it to the
467// one act every bulk job performs (its startup) and nothing has to remember it.
468// WARN: `ionice` does NOT exist on the Synology busybox, so the I/O-class lever
469// is unavailable; CPU nice sufficed because the walk is SHA-256-bound over
470// cached reads (state R, not D, once niced).
471func sys_setpriority(prio: i64) -> i64 {
472 return __syscall(141, 0, 0, prio, 0, 0, 0)
473}
474
475// ADDITIVE TWIN 2026-08-04 (nx_resgov): re-nice ANOTHER process by pid. The incumbent above pins
476// who=0 = "me", so it cannot deprioritise a runaway -- and a governor that can only slow ITSELF has
477// no graceful rung between "observe" and "kill". PRIO_PROCESS=0, who=pid. Existing callers untouched
478// (rule 19: add the new entry point, never re-shape the one in service).
479func sys_setpriority_of(pid: i64, prio: i64) -> i64 {
480 return __syscall(141, 0, pid, prio, 0, 0, 0)
481}
482
483// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's
484// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat
485// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each;
486// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT
487// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive.
488// ===== SMALL-ALLOCATION BUMP ARENA (2026-08-06, debt 1785516350 / 1786055008) =====================
489// MEASURED FIRST, THEN BUILT. nx_arena_probe: 20,000 x sys_mmap(32) -> VmSize 80,172 kB,
490// VmRSS 80,024 kB. 640 KB of requested data cost 78 MB of RESIDENT memory -- 4096 bytes per 32-byte
491// request, exactly one page and one kernel VMA each. Across the corpus nx_mmapbal deep counts 17,157
492// functions / 43,498 sites that allocate and never return, so this multiplier is the actual shape of
493// the leak: the call sites are not individually wrong so much as individually EXPENSIVE.
494//
495// One VMA per call is also a HARD CORRECTNESS CEILING, not just a memory cost: vm.max_map_count
496// defaults to 65530, after which mmap returns -ENOMEM and callers write through the failed pointer.
497// That is precisely the dmesg-proven nx_hostctl SEGFAULT at 0xfffffffffffffff4 described below.
498//
499// SO: requests <= NXA_SMALL_MAX are bump-allocated out of a 256 KiB chunk (one VMA per ~5,400 small
500// allocations instead of one per allocation). Larger requests take the ORIGINAL path untouched --
501// they are the ones plausibly relying on page alignment, and they are not where the leak lives.
502//
503// THE ZEROING CONTRACT IS LOAD-BEARING AND IS PRESERVED BY NEVER RECYCLING. Callers rely on mmap
504// returning zeroed memory (nx_mmapbal: "mmap zeroes, so an untouched slot reads empty with no init
505// loop"). Bytes handed out here come from a freshly mmapped chunk and are NEVER handed out twice, so
506// every region is zero-filled exactly as before. LIFO give-back on munmap was deliberately REJECTED:
507// it would recover memory but hand back dirty bytes, silently breaking every caller that trusts the
508// zero -- a correctness regression traded for a memory win, which is the wrong trade.
509//
510// KNOWN TRADE-OFF, stated rather than hidden: small allocations are now ADJACENT within a chunk
511// instead of isolated in their own pages. An overrun that today walks off the end of a page and
512// SIGSEGVs loudly may instead corrupt a neighbouring allocation quietly. NXA_GAP puts slack between
513// allocations and NXA_SMALL_MAX is kept deliberately low to bound the exposure, but the risk is real
514// and is the reason this starts at 256 rather than a page.
515// ---- MEMORY ORDERING, THE ONE DEFINITION -------------------------------------------------------
516// Moved here from nx_atom.nx on 2026-08-25 and DELETED from its two other copies
517// (nx_atomic_intrinsic_test, nx_simd_i32x8_test). Measured before the move, corpus_complete=1:
518// THREE files each declared NX_MO_SEQ_CST = 5 independently. A constant written in three places is
519// three rulers that agree until one of them does not.
520//
521// They live at THIS layer because the arena allocator below needs an ordering value for its own
522// lock, and this file cannot import nx_atom.nx -- nx_atom imports THIS file, so that direction is a
523// cycle. Everything that had these constants still has them: nx_atom.nx imports this file, and so
524// does every consumer of nx_atom.
525//
526// The __atomic_* forms these feed are COMPILER INTRINSICS, not library calls, so this file can use
527// them with no import at all. Verified in nx_x86_64_ctx rather than assumed: __atomic_cas_i64 emits
528// `lock cmpxchgq`, __atomic_faa_i64 emits `lock xaddq`, __atomic_fence emits `mfence`. On x86-64 the
529// ordering operand is not consulted by the emitter because those instructions are full barriers
530// regardless; it is carried for the RV64A backend, where it selects the aq/rl bits.
531const NX_MO_RELAXED: i64 = 0
532const NX_MO_CONSUME: i64 = 1
533const NX_MO_ACQUIRE: i64 = 2
534const NX_MO_RELEASE: i64 = 3
535const NX_MO_ACQ_REL: i64 = 4
536const NX_MO_SEQ_CST: i64 = 5
537
538const NXA_SMALL_MAX: i64 = 256
539const NXA_CHUNK: i64 = 262144
540const NXA_ALIGN: i64 = 16
541const NXA_GAP: i64 = 16
542const NXA_STATE: i64 = 4096
543// RING CANARY (temporary diagnostic): the single-slot canary checked only the immediately
544// previous allocation and reported ZERO overruns -- but the bisection proved the write is
545// DELAYED, landing after later allocations have been served. Track the last NXA_RING
546// allocations and re-verify every one of them on each call. Lives at i64 slot NXA_RBASE in
547// the state page; the reporter borrows bytes 64/128, so 512 is clear of it.
548const NXA_RING: i64 = 128
549const NXA_RBASE: i64 = 64
550// ---- ARENA MARK/RESET (2026-08-12, additive; the durable fix for bump-without-reset). The arena
551// abandons a full chunk on rollover, so a long-running accept loop accumulates chunks into one giant
552// coalesced VMA (hub_gw MEASURED 3.4GB over 64k requests). A daemon marks the arena AFTER startup and
553// resets at its accept-loop's quiescent point; reset munmaps every chunk allocated since the mark and
554// zeroes the marked chunk's reclaimed tail, so per-request small allocations reuse a bounded slab.
555// State slots (state page is 512 i64): [3]=chunk_count [4]=mark_valid [5]=mark_bump [6]=mark_chunk_end
556// [7]=mark_chunk_count; the chunk-base list lives at slots NXA_CHUNKBASE..+NXA_CHUNKMAX (clear of the
557// ring at 64..320 and the reporter scratch below 64). CONTRACT: the caller guarantees NO arena
558// allocation made after the mark is still referenced at reset (the accept-loop top, where the previous
559// request's frames have all returned -- the same quiescent point ss_cache_reap already uses). LARGE
560// (>NXA_SMALL_MAX) allocations take their own VMA and are NOT tracked here; a per-request large mmap
561// still needs its own munmap. Untracked-overflow (>NXA_CHUNKMAX chunks between resets) degrades to the
562// old leak for the excess, never corrupts.
563// ---- ARENA MUTUAL EXCLUSION (2026-08-25) -------------------------------------------------------
564// THE DEFECT: the bump-pointer advance below was a plain read-modify-write --
565// let p: i64 = nxa_st[0]
566// nxa_st[0] = p + need
567// -- so two threads that read nxa_st[0] before either wrote it BOTH RECEIVE THE SAME POINTER and
568// then write over each other. The chunk refill, the ring-canary scan and the nxa_st[2] counter have
569// the same shape. MEASURED while shipping structured concurrency: eight pool workers calling a
570// helper that allocates a 16-byte timespec raced this cursor and produced ARENA-OVERRUN
571// prev_alloc_size=16 followed by SIGSEGV. It generalises to EVERY small allocation from more than
572// one thread, which is why the scoped-spawn child body was written to allocate nothing at all.
573//
574// WHY A LOCK AND NOT A LOCK-FREE BUMP. A fetch-and-add on the cursor fixes only the fast path; two
575// threads can still both observe the chunk exhausted and both refill, and the canary ring and the
576// counter would still race. One lock over the whole mutable region is correct by inspection, which
577// on the allocator that every organ in the estate calls is worth more than a clever fast path.
578// THE COST IS NOT THE DOMINANT COST HERE: this function ALREADY walks all NXA_RING canary slots on
579// every allocation, so one uncontended `lock cmpxchgq` is far below the noise of work already done.
580//
581// SLOT 4 IS FREE BY THE LAYOUT ABOVE: [0] cursor, [1] limit, [2] ring counter, [3] chunk count, and
582// the ring starts at NXA_RBASE=64. It is also clear of the byte-64 and byte-128 scratch that
583// nxa_report_overrun formats digits into (slots 8 and 16), which slot 4 (bytes 32-39) does not touch.
584const NXA_LOCK: i64 = 4
585// A BOUND ON AN UNKNOWABLE WAIT, DERIVED RATHER THAN PICKED, AND ITS EXHAUSTION ANNOUNCES. The
586// longest thing the critical section can do is the NXA_RING canary scan plus one mmap, so a spin far
587// beyond that is not contention -- it is a holder that is never coming back. Eight times the ring
588// gives an order of magnitude of headroom over the longest legitimate hold; on reaching it the
589// allocator SAYS SO on stderr once and keeps waiting, because hanging visibly is recoverable and
590// corrupting silently is not, and dying inside the allocator would take down a process that may be
591// merely slow.
592const NXA_LOCK_WARN: i64 = NXA_RING * 8
593// Slot 5: "the contention hint has already been printed by this process". Also free by the layout
594// above and clear of every scratch region. It is a FLAG, not a counter, and it is set through a CAS
595// so the once-ness is itself race-free rather than depending on the lock it reports about.
596const NXA_LOCK_WARNED: i64 = 5
597
598const NXA_CHUNKBASE: i64 = 320
599const NXA_CHUNKMAX: i64 = 192
600
601// [0] = next free byte, [1] = one past the end of the current chunk. A static POINTER to a real
602// mmapped page rather than scalar statics, matching the idiom the corpus already proves; the state
603// page is taken through __syscall directly so this can never recurse into itself.
604static nxa_st: *i64
605
606// munmap -- free a region from sys_mmap. x86_64 munmap = 11; 11 is NOT an rv64 number in the compiler's
607// swap table, so the literal passes through untranslated = real munmap (unlike chdir, where rv64 80=fstat
608// intercepted it). CRITICAL for long-running loops: the supervisor's per-poll proc_* scans mmap 64KB+ each;
609// unfreed, the leak hits DSM's RLIMIT_AS -> mmap returns -12 -> the code writes through it -> SEGFAULT
610// (dmesg-proven: nx_hostctl segfault at 0xfffffffffffffff4). Free scan buffers to keep the supervisor alive.
611//
612// A small len means the region came from the bump arena above, because sys_mmap routes by the SAME
613// threshold. Unmapping an interior pointer would tear a hole in a chunk still holding other callers'
614// live allocations, so it is a no-op here. Balanced small callers therefore no longer return memory --
615// but they now cost ~48 bytes instead of 4096, so the arena wins by two orders of magnitude even
616// against code that was already correct.
617// Matching release for sys_mmap_try and other whole kernel mappings.
618// Never pass an arena allocation from sys_mmap: its small pointers may be interior.
619// Preserve the requested mapping length; the kernel applies its page rounding.
620const NXA_MAP_INVALID:i64=0-22 // Linux EINVAL, a protocol value rather than a resource budget.
621func sys_munmap_direct(addr:*u8,len:i64)->i64{
622 if (addr as i64)<=0||len<=0{return NXA_MAP_INVALID}
623 return __syscall(11,addr as i64,len,0,0,0,0)
624}
625
626func sys_munmap(addr: *u8, len: i64) -> i64 {
627 if len <= NXA_SMALL_MAX { return 0 }
628 return __syscall(11, addr as i64, len, 0, 0, 0, 0)
629}
630
631// Seek within a file. whence: 0=SEEK_SET, 1=SEEK_CUR, 2=SEEK_END.
632// Returns new file offset on success, -errno on failure.
633func sys_lseek(fd: i64, offset: i64, whence: i64) -> i64 {
634 return __syscall(SYS_LSEEK, fd, offset, whence, 0, 0, 0)
635}
636
637// ---- FILESYSTEM SPACE: THE AXIS THE ESTATE DID NOT HAVE (2026-08-28) -----------------------------
638// WHY THIS IS HERE AND NOT LEFT WHERE IT WAS. On 2026-08-28 a 100%-FULL DISK truncated a sibling seat's
639// MEMORY.md to 0 bytes -- open(path,"w") truncates before it writes, so a full volume does not refuse a
640// write, it DESTROYS the file. Nothing in the estate saw it coming: nx_resmon is "the resource axis
641// nx_health lacks" for MEMORY and SWAP, and a search for the disk primitive returned matches=0 for BOTH
642// sys_statfs and statvfs with corpus_complete=1. nx_res_census records the same absence in its own header.
643// The capability was not missing, it was DARK: nx_system_triage.tr_free_gb has read filesystem space since
644// 2026-06-10, in an _hdl_build organ that is NOT REGISTERED (nx_job_run refuses it as "not an unpinned
645// GREEN tool"), so the one instrument that could have warned was unreachable by any caller.
646// A CAPABILITY THAT EXISTS IN ONE UNREACHABLE ORGAN IS INDISTINGUISHABLE FROM ONE NOBODY BUILT.
647//
648// WHY THE RAW 137 AND NOT A SYS_ CONST. This file's dual-arch blocks are gated on TARGET_X86_64, which is
649// HARD-PINNED UNDEFINED, so the RV64 branch is what compiles and the x86 backend translates each number at
650// emit through x86ctx_rv64_to_x86_64_syscall -- whose default is `return num`. There is NO row for RV64 43
651// (statfs), so a SYS_STATFS=43 const would pass through unmapped to x86_64 43 = ACCEPT: a different
652// syscall, silently, on a path pointer. That is not a hypothesis -- nx_system_triage PROBE-PROVED it on
653// 2026-06-10: "rv64 43 returns -9 through the translation table; 137 raw matches df exactly." So 137 is
654// the MEASURED-CORRECT number for the target we actually emit, and it is named here ONCE instead of
655// sitting as a bare literal at each call site.
656// ⚠NAMED FOLLOW-UP, conflict-checked and deliberately NOT taken here: adding `if num == 43 { return 137 }`
657// to x86ctx_rv64_to_x86_64_syscall would make the arch-correct const work too. Nothing passes 43 as an x86
658// number (43 appears only as a translation TARGET, from RV64 202 accept), so the row is safe -- but it is a
659// COMPILER change that activates only on the next nx_cc self-host rebuild, and the working path needs none.
660//
661// 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.
662// f_bavail (not f_bfree) is the honest number for "will my write succeed": it excludes the root reserve, so
663// it reports FULLER than root would see. Wrong in the safe direction, and said out loud rather than implied.
664// ⚠THE IMPRECISION, MEASURED AND NAMED SO NOBODY LATER "FIXES" IT INTO AGREEING WITH df: this permil is
665// NOT df's Use%. df computes Used/(Used+Available), which EXCLUDES the root-reserved blocks from its
666// denominator; this computes (blocks-bavail)/blocks, which counts the reserve as used. VERIFIED against df
667// on 2026-08-28: avail_bytes came back 958449582080, which is EXACTLY df's Available of 935985920 KiB, while
668// the same volume read 113 permil here and 7% there -- both correct, measuring different things. Both reach
669// their maximum at the SAME event (bavail = 0), so a threshold calibrated against THIS metric alarms at the
670// same moment a writer actually hits the wall; it simply sits higher below that. Calibrate thresholds to
671// this definition, and do not import a df-derived number as if it were the same quantity.
672const SYS_STATFS_X86_MEASURED: i64 = 137
673const STATFS_BUF_BYTES: i64 = 144
674const STATFS_I_BSIZE: i64 = 1
675const STATFS_I_BLOCKS: i64 = 2
676const STATFS_I_BAVAIL: i64 = 4
677const STATFS_PERMIL: i64 = 1000
678const STATFS_ERR: i64 = 0 - 1
679
680// raw statfs into a caller-supplied 144-byte buffer. 0 = ok, non-zero = the kernel's negative errno.
681func sys_statfs(path: *u8, buf: *i64) -> i64 {
682 return __syscall(SYS_STATFS_X86_MEASURED, path, buf, 0, 0, 0, 0)
683}
684
685// bytes available to a non-root writer on the filesystem holding `path`; STATFS_ERR if statfs failed.
686func sys_fs_avail_bytes(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 return buf[STATFS_I_BSIZE] * buf[STATFS_I_BAVAIL]
690}
691
692// USED per-mille of the filesystem holding `path`, counted against what a non-root writer can reach:
693// (blocks - bavail) * 1000 / blocks. STATFS_ERR if statfs failed or the volume reports zero blocks --
694// an UNMEASURABLE volume must never read as 0 permil used, which is the most flattering possible lie.
695func sys_fs_used_permil(path: *u8) -> i64 {
696 let buf: *i64 = sys_mmap(STATFS_BUF_BYTES) as *i64
697 if sys_statfs(path, buf) != 0 { return STATFS_ERR }
698 let blocks: i64 = buf[STATFS_I_BLOCKS]
699 if blocks <= 0 { return STATFS_ERR }
700 let avail: i64 = buf[STATFS_I_BAVAIL]
701 return ((blocks - avail) * STATFS_PERMIL) / blocks
702}
703
704func sys_exit(code: i64) -> i64 {
705 return __syscall(SYS_EXIT, code, 0, 0, 0, 0, 0)
706}
707
708// mmap anonymous R/W memory; returns raw bytes. Fixed flags:
709// PROT_READ|PROT_WRITE = 3, MAP_PRIVATE|MAP_ANONYMOUS = 0x22, fd=-1.
710// FAIL-CLOSED ON A REFUSED MAPPING (2026-08-07). MEASURED: the corpus has 90,817 sys_mmap call sites
711// and SIX of them check the result -- all six in test probes whose response is sys_exit anyway. So
712// 90,811 sites take whatever this returns and write through it. When the kernel refuses, that value is
713// -errno, and the write lands at 0xfffffffffffffff4 (-12, ENOMEM). That is not a hypothetical: dmesg
714// on this host recorded it hourly in nx_web_shard_compact, and 18 times in nx_web_crawl_step.
715// Returning a poisoned pointer to 90,811 unguarded callers is the defect. Dying here is strictly safer
716// than dying there: the process ends either way, but this way there is no memory corruption first and
717// the failure is NAMED instead of arriving as a bare segfault address an operator has to decode.
718// This is the never-brick shape -- fail-safe BY CONSTRUCTION, not by every caller remembering.
719// KNOWN COST, stated: nx_mmap_probe / test_munmap deliberately provoke a refusal to observe it. They
720// now exit here with code 12 rather than printing their own verdict. Six probes lose a diagnostic;
721// 90,811 sites stop corrupting memory.
722// ===== TEMPORARY DIAGNOSTIC -- ARENA OVERRUN CANARY (2026-08-07) =====================================
723// ⛔DO NOT BLESS A COMPILER BUILT WITH THIS. The canary writes 0xC7 into the NXA_GAP slack that a
724// caller could otherwise legitimately read as zeros, so it changes observable behaviour for any code
725// that reads past its declared size -- which is precisely the code being hunted.
726// PURPOSE: at NXA_SMALL_MAX=256 the compiler produces 14 SPURIOUS type diagnostics (it reports
727// `arg 2 is an INTEGER but the parameter is a POINTER` against a parameter DECLARED `j: *u8`), i.e.
728// something writes past its allocation and corrupts the parser's type table. At threshold 64 the same
729// requests each get a 4096-byte page whose slack absorbs it. Reading the source found nothing: the
730// two obvious suspects (nx_ir.nx:70 sys_mmap(104), nx_parse.nx:868 sys_mmap(256)) are both correctly
731// sized and bounded. So stop reading and MEASURE: stamp each small allocation's gap, verify the
732// PREVIOUS one on the next call, and print the size of whichever allocation was overrun.
733// Writes to fd 2 without allocating -- it borrows scratch inside the arena state page, because a
734// reporter that called sys_mmap would recurse into the thing it is instrumenting.
735// Dump n bytes at src to fd 2, unprintables as '.', using scratch at state+256 (the ring starts at
736// state+512 and the decimal scratch sits at +64/+128, so this cannot collide with either). n is
737// capped by callers at 48 so the buffer stays clear of the ring.
738func nxa_dump_printable(src: i64, n: i64) -> i64 {
739 let o: *u8 = ((nxa_st as i64) + 256) as *u8
740 var i: i64 = 0
741 while i < n {
742 let sp: *u8 = (src + i) as *u8
743 var c: i64 = sp[0] as i64
744 if c < 32 { c = 46 }
745 if c > 126 { c = 46 }
746 o[i] = c as u8
747 i = i + 1
748 }
749 o[n] = 10 as u8
750 sys_write(2, o, n + 1)
751 return 0
752}
753
754// FINGERPRINT (2026-08-12): the size alone + all-zeros byte dump never named the site. The ring already
755// records each allocation's REQUESTED size in counter order, so the recent size SEQUENCE fingerprints the
756// code path that was running when the overrun landed (a distinctive run of sizes is near-unique to a
757// function). Writes to fd 2 borrowing state-page scratch at bytes 320/340 (clear of the ring at byte 512,
758// the reporter decimals at 64/128, and the byte-dump at 256). No allocation -- must not recurse into sys_mmap.
759func nxa_dump_sizes() -> i64 {
760 sys_write(2, " ring_sizes(old->recent): " as *u8, 27)
761 let scr: *u8 = ((nxa_st as i64) + 320) as *u8
762 let out2: *u8 = ((nxa_st as i64) + 340) as *u8
763 let cnt: i64 = nxa_st[2]
764 var start: i64 = cnt - 32
765 if start < 0 { start = 0 }
766 var idx: i64 = start
767 while idx < cnt {
768 let slot: i64 = idx % NXA_RING
769 let szv: i64 = nxa_st[NXA_RBASE + slot * 2 + 1]
770 var m: i64 = szv
771 var k: i64 = 0
772 if m == 0 { scr[0] = 48 as u8; k = 1 }
773 while m > 0 { scr[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
774 var j: i64 = 0
775 while j < k { out2[j] = scr[k - 1 - j]; j = j + 1 }
776 out2[k] = 44 as u8
777 sys_write(2, out2, k + 1)
778 idx = idx + 1
779 }
780 sys_write(2, "\n" as *u8, 1)
781 return 0
782}
783
784func nxa_report_overrun(sz: i64, gs: i64) -> i64 {
785 let msg: *u8 = "ARENA-OVERRUN prev_alloc_size=" as *u8
786 var n: i64 = 0
787 while msg[n] != (0 as u8) { n = n + 1 }
788 sys_write(2, msg, n)
789 let b: *u8 = ((nxa_st as i64) + 64) as *u8
790 let o: *u8 = ((nxa_st as i64) + 128) as *u8
791 var m: i64 = sz
792 var k: i64 = 0
793 if m == 0 { b[0] = 48 as u8; k = 1 }
794 while m > 0 { b[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
795 var i: i64 = 0
796 while i < k { o[i] = b[k - 1 - i]; i = i + 1 }
797 o[k] = 10 as u8
798 sys_write(2, o, k + 1)
799 // The SIZE alone did not name the site (four 80-byte victims, and the two unbounded 80-byte
800 // buffers in nx_parse.nx were sized from their inputs with no effect). So show the DATA: the
801 // victim's own bytes identify the buffer, and the bytes written past its end identify the WRITER.
802 let algn: i64 = (sz + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN
803 let base: i64 = gs - algn
804 var dn: i64 = sz
805 if dn > 48 { dn = 48 }
806 sys_write(2, " own : " as *u8, 8)
807 nxa_dump_printable(base, dn)
808 sys_write(2, " over: " as *u8, 8)
809 nxa_dump_printable(gs, 16)
810 nxa_dump_sizes()
811 return 0
812}
813
814func nxa_die(msg: *u8) -> i64 {
815 var n: i64 = 0
816 while msg[n] != (0 as u8) { n = n + 1 }
817 sys_write(2, msg, n)
818 sys_exit(12)
819 return 0
820}
821
822// Address of the arena lock word. Valid only once nxa_st exists; every caller below has already
823// ensured that, and the state-page creation itself is discussed at the take site.
824func nxa_lock_addr() -> *i64 {
825 return ((nxa_st as i64) + NXA_LOCK * 8) as *i64
826}
827
828// __atomic_cas_i64 returns 1 when it wrote and 0 when it did not, so the spin condition is == 0.
829// It is a COMPILER INTRINSIC, not a call into nx_atom -- that module imports THIS file, so importing
830// it back would be a cycle. Verified in nx_x86_64_ctx rather than assumed: it lowers to a genuine
831// `lock cmpxchgq` followed by sete, which is a full barrier on x86-64 whatever ordering is passed.
832func nxa_lock_take() -> i64 {
833 var spins: i64 = 0
834 while __atomic_cas_i64(nxa_lock_addr(), 0, 1, NX_MO_ACQUIRE) == 0 {
835 spins = spins + 1
836 // Fires EXACTLY ONCE, on equality rather than on exceeding, so a genuinely long wait reports
837 // itself without turning the allocator into a log generator.
838 if spins == NXA_LOCK_WARN {
839 // ONCE PER PROCESS, not once per acquisition. MEASURED 2026-08-25 and this is a
840 // correction to the first cut of this very function: it fired on equality per CALL, and
841 // eight workers contending LEGITIMATELY produced hundreds of identical lines in a single
842 // gate run. A DIAGNOSTIC THAT FIRES CONSTANTLY IS ONE EVERY READER LEARNS TO IGNORE, and
843 // this one writes to the stderr of every organ in the estate.
844 // The threshold was derived from the longest the critical section can run, which bounds
845 // ONE hold and says nothing about QUEUE DEPTH: with N threads waiting, a legitimate wait
846 // is N holds and can exceed any per-section derivation. So this is a NOISE FLOOR for a
847 // hint, never a correctness bound -- it never fails, never delays, and never repeats.
848 // The flag is set through a CAS so the once-ness cannot itself race.
849 let wflag: *i64 = ((nxa_st as i64) + NXA_LOCK_WARNED * 8) as *i64
850 if __atomic_cas_i64(wflag, 0, 1, NX_MO_ACQ_REL) == 1 {
851 let m: *u8 = "ARENA-LOCK: sustained allocator contention seen (reported once per process; a hint, not an error -- allocation proceeds normally).\n" as *u8
852 var mn: i64 = 0
853 while m[mn] != (0 as u8) { mn = mn + 1 }
854 sys_write(2, m, mn)
855 }
856 }
857 }
858 return 0
859}
860
861func nxa_lock_give() -> i64 {
862 // nx_cc refuses a bare intrinsic statement ("computes a value and never uses it") and an atomic
863 // store has no result worth using, so it is bound and discarded -- the same shape nx_atom uses
864 // for exactly this reason. The contract is unchanged: this returns 0 either way.
865 let discarded: i64 = __atomic_store_i64(nxa_lock_addr(), 0, NX_MO_RELEASE)
866 if discarded != 0 { return 0 }
867 return 0
868}
869
870// Optional mapping for request boundaries that must report allocation refusal.
871// Unlike sys_mmap, this never aborts the process and never consumes arena storage.
872// Release successful mappings with sys_munmap_direct, not the arena-aware sys_munmap.
873// A successful reservation can still fail on later physical-memory pressure; callers
874// must not describe virtual address admission as guaranteed resident RAM.
875func sys_mmap_try(size:i64)->*u8 {
876 if size<=0 { return 0 as *u8 }
877 let mapped:i64=__syscall(SYS_MMAP,0,size,3,0x22,-1,0)
878 if mapped<=0 { return 0 as *u8 }
879 return mapped as *u8
880}
881
882func sys_mmap(size: i64) -> *u8 {
883 // Large requests keep the EXACT original behaviour, byte for byte: page-aligned, own VMA. Any
884 // caller that depends on page alignment is allocating at least a page, so the arena cannot reach
885 // it. Every failure path below also falls back to this same call, so an exhausted arena degrades
886 // to the old allocator rather than returning a bad pointer.
887 if size > NXA_SMALL_MAX {
888 let big: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
889 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) }
890 return big as *u8
891 }
892 if (nxa_st as i64) == 0 {
893 let s: i64 = __syscall(SYS_MMAP, 0, NXA_STATE, 3, 0x22, -1, 0)
894 if s <= 0 {
895 // arena state page refused -- degrade to the plain allocator, and only die if THAT fails too
896 let f1: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
897 if f1 <= 0 { nxa_die("FATAL sys_mmap: kernel refused the arena state page AND the fallback mapping (ENOMEM).\n" as *u8) }
898 return f1 as *u8
899 }
900 nxa_st = s as *i64
901 }
902 // EVERYTHING FROM HERE TO THE RETURN TOUCHES SHARED STATE: the cursor, the limit, the chunk
903 // table, the canary ring and the ring counter. It is ONE critical section because the refill
904 // decision and the bump that depends on it cannot be separated without reintroducing the race.
905 // The state page itself is created ABOVE this point, unlocked: two threads arriving there
906 // together would each map a page and one would win the static, leaking the other's 4 KiB but
907 // corrupting nothing, and in practice the arena is warm long before any thread is spawned
908 // because spawning one allocates. That residual is NAMED here rather than papered over.
909 nxa_lock_take()
910 var need: i64 = size
911 if need <= 0 { need = 1 }
912 need = (need + NXA_ALIGN - 1) / NXA_ALIGN * NXA_ALIGN + NXA_GAP
913 if nxa_st[0] + need > nxa_st[1] {
914 let c: i64 = __syscall(SYS_MMAP, 0, NXA_CHUNK, 3, 0x22, -1, 0)
915 if c <= 0 {
916 // chunk refused -- degrade to the plain allocator, and only die if THAT fails too.
917 // RELEASE FIRST: this is the one path that leaves the critical section early, and a lock
918 // held across a degraded return would wedge every other allocator in the process.
919 nxa_lock_give()
920 let f2: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x22, -1, 0)
921 if f2 <= 0 { nxa_die("FATAL sys_mmap: kernel refused an arena chunk AND the fallback mapping (ENOMEM).\n" as *u8) }
922 return f2 as *u8
923 }
924 nxa_st[0] = c
925 nxa_st[1] = c + NXA_CHUNK
926 // track the chunk base so arena_reset can munmap post-mark chunks (additive; guarded at cap).
927 if nxa_st[3] < NXA_CHUNKMAX { nxa_st[NXA_CHUNKBASE + nxa_st[3]] = c; nxa_st[3] = nxa_st[3] + 1 }
928 }
929 // ---- RING CANARY (temporary diagnostic) ----
930 var rk: i64 = 0
931 while rk < NXA_RING {
932 let gs0: i64 = nxa_st[NXA_RBASE + rk * 2]
933 if gs0 != 0 {
934 var bi: i64 = 0
935 var bad: i64 = 0
936 while bi < 8 {
937 let bp: *u8 = (gs0 + bi) as *u8
938 if bp[0] != (199 as u8) { bad = 1; bi = 8 } else { bi = bi + 1 }
939 }
940 if bad == 1 {
941 nxa_report_overrun(nxa_st[NXA_RBASE + rk * 2 + 1], gs0)
942 nxa_st[NXA_RBASE + rk * 2] = 0
943 }
944 }
945 rk = rk + 1
946 }
947 let p: i64 = nxa_st[0]
948 nxa_st[0] = p + need
949 let gs: i64 = p + need - NXA_GAP
950 var gj: i64 = 0
951 while gj < NXA_GAP { let q: *u8 = (gs + gj) as *u8; q[0] = 199 as u8; gj = gj + 1 }
952 let slot: i64 = nxa_st[2] % NXA_RING
953 nxa_st[NXA_RBASE + slot * 2] = gs
954 nxa_st[NXA_RBASE + slot * 2 + 1] = size
955 nxa_st[2] = nxa_st[2] + 1
956 // The ONLY other exit from the critical section is the degraded chunk-refill path above, which
957 // releases before it returns. Every shared write is now behind this pair.
958 nxa_lock_give()
959 return p as *u8
960}
961
962// arena_mark: force the arena warm (so a first chunk + state page exist), then record the current
963// position as the reset barrier. Returns 1. A daemon calls this ONCE after startup, before its loop.
964func sys_arena_mark() -> i64 {
965 let warm: *u8 = sys_mmap(1) // ensures nxa_st + chunk[0] exist; the 1 byte is itself arena scratch
966 if (warm as i64) == 0 { return 0 }
967 nxa_st[4] = 1
968 nxa_st[5] = nxa_st[0]
969 nxa_st[6] = nxa_st[1]
970 nxa_st[7] = nxa_st[3]
971 return 1
972}
973
974// arena_reset: reclaim everything allocated since the mark. munmap post-mark chunks, restore the bump
975// to the mark, ZERO the marked chunk's reclaimed tail (preserves the mmap-returns-zeroed contract for
976// recycled bytes), and CLEAR the ring canary (its stamps may point into a just-munmap'd chunk, and a
977// stale deref on the next alloc would SEGV). Returns 1 on reset, 0 if no mark was set.
978func sys_arena_reset() -> i64 {
979 if (nxa_st as i64) == 0 { return 0 }
980 if nxa_st[4] != 1 { return 0 }
981 var i: i64 = nxa_st[7]
982 while i < nxa_st[3] {
983 let cb: i64 = nxa_st[NXA_CHUNKBASE + i]
984 if cb != 0 { __syscall(11, cb, NXA_CHUNK, 0, 0, 0, 0); nxa_st[NXA_CHUNKBASE + i] = 0 }
985 i = i + 1
986 }
987 nxa_st[3] = nxa_st[7]
988 nxa_st[0] = nxa_st[5]
989 nxa_st[1] = nxa_st[6]
990 var z: i64 = nxa_st[0]
991 while z < nxa_st[1] { let q: *u8 = z as *u8; q[0] = 0 as u8; z = z + 1 }
992 var r: i64 = 0
993 while r < NXA_RING * 2 { nxa_st[NXA_RBASE + r] = 0; r = r + 1 }
994 nxa_st[2] = 0
995 return 1
996}
997
998// mmap anonymous SHARED R/W memory -- ONE region that survives fork() so all
999// children see each other's writes (MAP_SHARED|MAP_ANONYMOUS = 0x21). Allocate
1000// in the PARENT before fork. Foundation for the fork-per-connection video relay
1001// (peers in separate children share the per-room frame table).
1002func sys_mmap_shared(size: i64) -> *u8 {
1003 let r: i64 = __syscall(SYS_MMAP, 0, size, 3, 0x21, -1, 0)
1004 return r as *u8
1005}
1006
1007// madvise(2) -- prefetch/advice hints for mapped ranges. MADV_WILLNEED=3 batches page-ins so a
1008// serial fault loop over a cold file-backed mmap becomes parallel disk readahead (the dp-web-pub
1009// stage-2 p95 fix, 2026-08-12). RAW x86_64 NUMBER 28 ON PURPOSE (sys_exit_group's raw-231 pattern):
1010// the portable rv64/asm-generic number is 233 and x86ctx_rv64_to_x86_64_syscall has no 233 row in
1011// the DEPLOYED compiler, so a portable const would emit x86_64 233 = epoll_ctl (the wrong-syscall-
1012// not-an-error class; see the setpgid/flock rows). The 233->28 row is staged in nx_x86_64_ctx.nx and
1013// activates on the next nx_cc self-host rebuild; flip this to the portable const AFTER that lands.
1014// Signature bite-proven by nx_madvise_probe (0 / -12 ENOMEM / -22 EINVAL). Advisory contract: callers
1015// may ignore the return value -- a failed hint costs nothing but the cold-read behaviour it hints away.
1016func sys_madvise(addr: *u8, len: i64, advice: i64) -> i64 {
1017 return __syscall(28, addr, len, advice, 0, 0, 0)
1018}
1019
1020// openat flavors used by the compiler driver. AT_FDCWD = -100 (declared ABOVE, next to its first
1021// reader -- see the miscompile note there; do NOT move it back down).
1022// O_RDONLY = 0; O_CREAT|O_WRONLY|O_TRUNC = 0x241 on Linux RV64.
1023const O_RDONLY: i64 = 0
1024const O_WRONLY_CT: i64 = 0x241 // O_CREAT | O_WRONLY | O_TRUNC
1025const O_WRONLY_CA: i64 = 0x441 // O_CREAT | O_WRONLY | O_APPEND
1026
1027func sys_openat_rd(path: *u8) -> i64 {
1028 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY, 0, 0, 0)
1029}
1030
1031// O_RDWR|O_CREAT (NO truncate) -- for offset-addressed persistent files like the metrics ring TSDB
1032// (create if missing, then lseek+read/write records in place, never truncating existing history).
1033const O_RDWR_CREATE: i64 = 0x42
1034func sys_openat_rdwr(path: *u8, mode: i64) -> i64 {
1035 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDWR_CREATE, mode, 0, 0)
1036}
1037
1038// ★★★THE FILE MODE IS THE HALF OF THIS INTERFACE THAT WAS NEVER NAMED. The O_ flags above are named
1039// consts in hex WITH a decoding comment; the mode passed beside them is a bare literal at every call
1040// site. MEASURED 2026-08-14 (coverage_complete=1 corpus_complete=1 over 23,053 files):
1041// - 29 organs passed the mode as a bare DECIMAL literal, which no reader decodes as rw-r--r--
1042// without stopping to convert it. ⚠THE FIRST COUNT PUBLISHED HERE WAS 26: the scan was scoped to
1043// runtime/_hdl_build/ and the SUBDIRECTORY's count was published as the estate figure -- three
1044// more (nx_forge_rag, nx_gpu_export, nx_bvhfk) sat one level up in runtime/.
1045// ★A COUNT INHERITS THE SCOPE OF ITS SCAN, AND THE SCOPE IS THE PART NOBODY PRINTS BESIDE IT.
1046// ⚠The offending call is deliberately NOT spelled out literally in this comment: prose is source
1047// bytes, so writing the pattern here would make every future grep for it match this note;
1048// - 10 MORE each define their OWN private 0644 const (IP_ VR_ VP_ LIVE_ FD_ FP_ WL_ PUB_ REG_ HFF_),
1049// nine written 0x1a4 and one written 420 -- THE SAME CONSTANT IN TWO DIFFERENT BASES.
1050// Ten seats each solved this privately and none put the answer where the next one would look. That is
1051// the duplicate-ruler defect precisely: changing the estate's default artifact mode today means finding
1052// 39 sites in two notations and hoping none was missed. One name, in the shim every organ already
1053// imports, is the entire fix -- and it belongs HERE, beside the flags, not in a 40th private copy.
1054const MODE_0644: i64 = 0x1a4 // rw-r--r-- : default mode for a generated artifact
1055// rwxr-xr-x : default mode for a created DIRECTORY. A directory without the execute bit cannot be
1056// traversed, so MODE_0644 is not merely stricter here -- it is wrong, and the failure surfaces later
1057// as an unopenable path rather than as a refused mkdir. Named beside its sibling so the choice is a
1058// lookup rather than a recollection; the estate otherwise spells this as a raw 0x1ed at every site.
1059const MODE_0755: i64 = 0x1ed
1060// Seconds of ZERO PROGRESS on one socket operation before an accepted connection is abandoned.
1061// A single-threaded accept-loop daemon that loop-reads to Content-Length can be starved FOREVER by one
1062// peer that declares a body it never finishes sending -- a one-request DoS, hostile OR merely buggy.
1063// nx_dos_timeout_scan supervises the class and named 16 daemons carrying no timeout at all; the cure is
1064// sys_set_socket_timeout(cfd, ACCEPT_TMO_S) folded in right after accept.
1065// WHY 30 AND NOT THE 5 THE LOGIN DAEMONS USE: this bound must be wrong in the direction of SERVING, not
1066// of dropping. The attack is an UNBOUNDED wait, so ANY finite bound closes it; a short one additionally
1067// risks aborting a legitimate slow client. 30s of zero progress on a single recv/send is pathological
1068// for every daemon in the class -- including the streaming ones, where data is flowing and the timer
1069// never approaches its bound -- while still converting an infinite starvation into a bounded one.
1070// It is the calibration nx_galx_bridge already uses for an accepted cfd; named here rather than copied
1071// into a 16th private literal, exactly as MODE_0644 above.
1072const ACCEPT_TMO_S: i64 = 30
1073func sys_openat_wr(path: *u8, mode: i64) -> i64 {
1074 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CT, mode, 0, 0)
1075}
1076
1077// Linux O_WRONLY | O_CREAT | O_EXCL. An existing final component, including
1078// a symlink, is a conflict; callers acquire ownership only on success.
1079const O_WRONLY_CREATE_EXCLUSIVE: i64 = 0x1 | 0x40 | 0x80
1080func sys_openat_exclusive(path: *u8, mode: i64) -> i64 {
1081 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CREATE_EXCLUSIVE, mode, 0, 0)
1082}
1083
1084// Linux O_DIRECTORY: require a directory, rather than merely an openable node.
1085const O_DIRECTORY: i64 = 0x10000
1086func sys_openat_directory(path: *u8) -> i64 {
1087 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_RDONLY | O_DIRECTORY, 0, 0, 0)
1088}
1089
1090// Open path for append (create if missing). Used by append-only
1091// journals such as .race_telemetry.tsv. RV64 syscall numbers; the
1092// x86_64 mirror lives in nx_syscalls_x86_64.nx.
1093func sys_openat_append(path: *u8, mode: i64) -> i64 {
1094 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA, mode, 0, 0)
1095}
1096
1097// Linux open ABI flags: acquire close-on-exec atomically and refuse a final
1098// symlink. Nonblocking also prevents an unexpected FIFO from stalling admission.
1099const O_CLOEXEC: i64 = 0x80000
1100const O_NOFOLLOW: i64 = 0x20000
1101const O_NONBLOCK: i64 = 0x800
1102const MODE_0600: i64 = 0x180
1103func sys_openat_lock(path: *u8) -> i64 {
1104 return __syscall(SYS_OPENAT, AT_FDCWD, path, O_WRONLY_CA | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK, MODE_0600, 0, 0)
1105}
1106
1107// symlinkat(target, AT_FDCWD, linkpath) -- raw x86_64 266 forced RUNTIME (the chdir escape, same as
1108// readlinkat below). THE atomic-repoint primitive for release management: create releases/current.new ->
1109// sys_renameat over releases/current = an atomic symlink swap (golive/rollback are instant + crash-safe).
1110// 0 on success, -errno (notably -EEXIST=-17 if linkpath exists -- create the .new name, then rename).
1111func sys_symlinkat(target: *u8, linkpath: *u8) -> i64 {
1112 let nbox: *i64 = sys_mmap(16) as *i64
1113 nbox[0] = 266
1114 let r: i64 = __syscall(nbox[0], target as i64, AT_FDCWD, linkpath as i64, 0, 0, 0)
1115 sys_munmap(nbox as *u8, 16)
1116 return r
1117}
1118
1119// readlinkat(AT_FDCWD, path, buf, cap) -- raw x86_64 267 forced RUNTIME (the chdir escape: keep the
1120// number out of the rv64->x86 constant-translate path). Returns link length (NO NUL appended), -errno
1121// on fail. nbox is munmap'd before return: the daemon supervisor calls this hundreds of times PER CYCLE
1122// (exe-identity sweeps), and a leaked page per call is exactly the VSZ-balloon class that broke fork.
1123func sys_readlinkat(path: *u8, buf: *u8, cap: i64) -> i64 {
1124 let nbox: *i64 = sys_mmap(16) as *i64
1125 nbox[0] = 267
1126 let r: i64 = __syscall(nbox[0], AT_FDCWD, path as i64, buf as i64, cap, 0, 0)
1127 sys_munmap(nbox as *u8, 16)
1128 return r
1129}
1130
1131// Atomically replace newpath with oldpath (rename(2) on one filesystem: a concurrent reader sees the
1132// whole old file or the whole new file, never a torn read). The S-class content-publish primitive:
1133// write the new page to a temp file, then sys_renameat(tmp, live) -> hot-swap, NO rm+ln race.
1134// renameat2: rv64=276, x86_64=316, flags=0. The known-good compiler translates most rv64 syscall
1135// numbers to the x86_64 target but its table MISSES 276 -- verified 2026-06-14 via nx_rename_probe:
1136// raw 276 -> -EINVAL (lands on x86_64 `tee`), raw 316 -> renames OK. That silently broke every
1137// cst_write_atomic publish (page.html.new written, never swapped in). Try the x86_64 number first
1138// (works on every x86_64 build incl. known-good); fall back to the rv64 number for native-rv64 or
1139// translating compilers that do map it. flags=0 so renameat2 == renameat semantics.
1140func sys_renameat(oldpath: *u8, newpath: *u8) -> i64 {
1141 let r: i64 = __syscall(316, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
1142 if r == 0 { return 0 }
1143 return __syscall(276, AT_FDCWD, oldpath, AT_FDCWD, newpath, 0, 0)
1144}
1145
1146// fsync(2): flush file (or directory) data+metadata to stable storage.
1147// PROBE-PROVEN 2026-06-10 (_fsync_probe): rv64 82 is NOT in the compiler's
1148// translation table (lands on x86 rename -> -EFAULT both ways); direct
1149// x86_64 74 passes through raw (the unlinkat-263 precedent) and behaves as
1150// fsync (0 on a valid fd, -9 EBADF on a bad one). Storage commit points
1151// fsync the data files AND their directory around rename(2) so a committed
1152// segment survives power loss, not just process death.
1153func sys_fsync(fd: i64) -> i64 {
1154 return __syscall(74, fd, 0, 0, 0, 0, 0)
1155}
1156
1157// flock(2): BSD-style whole-file ADVISORY lock. rv64 32 -> x86_64 73 via the compiler's
1158// x86ctx_rv64_to_x86_64_syscall table (nx_x86_64_ctx.nx:961, PROVEN LIVE in flock_deploy.log).
1159// op: SYS_LOCK_SH=1 / SYS_LOCK_EX=2 / SYS_LOCK_NB=4 (OR) / SYS_LOCK_UN=8. Returns 0 on success,
1160// -errno on failure. Used by the framed-append durability floor to serialize the write-until-
1161// complete loop so a partial/short write under contention can NEVER misalign a concurrent appender
1162// (O_APPEND single-write atomicity is necessary but not sufficient on every fs -- the lock makes
1163// the whole framed record write atomic against other lockers). Additive: no existing caller in
1164// this file changes. NOTE: nx_flock.nx is a separate organ importing the LEGACY "syscalls.nx"
1165// name; this wrapper lives HERE so organs already on nx_syscalls.nx (e.g. nx_framed_append) get
1166// flock without a second import (double-import rc=6 trap).
1167const SYS_LOCK_SH: i64 = 1
1168const SYS_LOCK_EX: i64 = 2
1169const SYS_LOCK_NB: i64 = 4
1170const SYS_LOCK_UN: i64 = 8
1171func sys_flock(fd: i64, op: i64) -> i64 {
1172 return __syscall(32, fd, op, 0, 0, 0, 0)
1173}
1174
1175// newfstatat(2): stat `path` into a 144-byte x86-64 struct stat at `statbuf`. x86_64 nr 262 is passed
1176// DIRECTLY (the unlinkat-263 / fsync-74 precedent: stat-family rv64 numbers aren't in the compiler's
1177// translation table, so a raw x86_64 number passes through untranslated). Returns 0 on success, <0
1178// (e.g. -2 ENOENT) on error. st_mtim.tv_sec @ offset 88, st_mtim.tv_nsec @ 96 (the freshness channel).
1179func sys_fstatat(path: *u8, statbuf: *u8) -> i64 {
1180 return __syscall(262, AT_FDCWD, path, statbuf, 0, 0, 0)
1181}
1182
1183// utimensat(2): set `path` atime+mtime from `times` (a struct timespec[2] = [atime.sec,atime.nsec,
1184// mtime.sec,mtime.nsec]). x86_64 nr 280 passed DIRECTLY. A sovereign `touch`; also makes freshness
1185// tests deterministic. Returns 0 on success, <0 on error.
1186func sys_utimensat(path: *u8, times: *i64) -> i64 {
1187 return __syscall(280, AT_FDCWD, path, times as i64, 0, 0, 0)
1188}
1189
1190// ---- sovereign host control-plane syscalls (x86_64; single unconditional consts,
1191// per the known-good-compiler @ifdef finding). The Nishi supervisor uses these to
1192// manage the daemon lifecycle WITHOUT any shell (no pkill / mkdir / chmod glue). ----
1193
1194// COMPILER NOTE: the known-good compiler BAKES whole function bodies by NAME for some syscalls
1195// (proven via emitted .s: a function literally named sys_kill emits number 8, sys_chmod emits 155
1196// -- both wrong, regardless of the const referenced). So these wrappers use NON-baked names
1197// (nx_kill / nx_chmod). sys_mkdir / sys_renameat are not baked, so those keep the sys_ name.
1198
1199// DESIGN: __syscall takes the RV64/generic number; the compiler's x86ctx_rv64_to_x86_64_syscall table
1200// (nx_x86_64_ctx.nx) translates it to the build target. So pass the RV64 number. These four were added
1201// to that sovereign table 2026-06-06 (kill 129->62, mkdirat 34->258, fchmodat 53->268, renameat2
1202// 276->316); x86 kill(62) had collided with rv64 lseek(62), x86 fchmodat(268) with rv64 pivot_root(268).
1203
1204// kill(pid, sig) -- rv64 129 -> x86_64 62. SIGTERM=15 / SIGKILL=9. Host control plane.
1205func nx_kill(pid: i64, sig: i64) -> i64 { return __syscall(129, pid, sig, 0, 0, 0, 0) }
1206
1207// setpgid(pid, pgid) -- put a process in its own PROCESS GROUP so a killer can reach its whole
1208// subtree. nx_kill(0 - pgid, sig) signals every member, not just the one process you forked.
1209// A BOUND THAT ONLY REACHES THE PROCESS YOU FORKED IS NOT A BOUND ON THE WORK IT STARTED.
1210// Per-target const, NOT a bare generic number: x86ctx_rv64_to_x86_64_syscall translates only the
1211// numbers it knows and FALLS THROUGH for the rest. MEASURED on the laptop lane 2026-08-10: a bare
1212// generic 154 reached x86_64 as 154 and returned -38 (ENOSYS), silently -- and a fix built on it
1213// reproduced the original bug exactly. Callers must treat setpgid as BEST-EFFORT.
1214@ifdef TARGET_X86_64
1215const SYS_SETPGID: i64 = 109
1216@endif
1217@ifndef TARGET_X86_64
1218const SYS_SETPGID: i64 = 154
1219@endif
1220func sys_setpgid(pid: i64, pgid: i64) -> i64 { return __syscall(SYS_SETPGID, pid, pgid, 0, 0, 0, 0) }
1221
1222// prlimit64(pid, resource, new_limit, old_limit) -- the Linux RESOURCE-LIMIT primitive =
1223// the Job-Object ActiveProcessLimit / memory-limit analog for the sovereign supervisor (M5).
1224// x86_64 prlimit64 = 302 (PASSED DIRECTLY, the unlinkat-263 / fsync-74 / fstatat-262
1225// precedent: a raw x86_64 number not in the compiler's rv64->x86 swap table passes through
1226// untranslated). NOTE: rv64 prlimit64 IS 261 but x86_64 261 = futimesat -- so the naive
1227// "261 is the same on both" is WRONG (PROBE-PROVEN: 261 returned EFAULT/EINVAL because it
1228// hit futimesat); the build target here is x86_64, so we emit 302 directly. pid=0 => the
1229// calling process (a forked child caps ITSELF before running its payload). new_limit /
1230// old_limit each point at a struct rlimit64 { rlim_cur: i64, rlim_max: i64 } (16 bytes);
1231// pass 0 for old_limit to skip read-back. Returns 0 on success, -errno (e.g. -1 EPERM if
1232// raising a hard limit unprivileged) on failure. NON-baked name (the compiler bakes some
1233// sys_* bodies by name; the nx_ prefix avoids that trap).
1234func nx_prlimit(pid: i64, resource: i64, new_limit: *u8, old_limit: *u8) -> i64 {
1235 return __syscall(302, pid, resource, new_limit as i64, old_limit as i64, 0, 0)
1236}
1237
1238// RLIMIT resource ids (Linux generic; identical rv64/x86_64). RLIMIT_AS = address-space
1239// (virtual memory) cap -- the cleanest userspace-settable "memory budget" for a supervised
1240// job. RLIMIT_CPU = CPU-seconds cap. WNOHANG=1 = wait4 non-blocking liveness poll option.
1241const RLIMIT_CPU: i64 = 0
1242const RLIMIT_AS: i64 = 9
1243const WNOHANG: i64 = 1
1244
1245// mkdirat -- rv64 34 -> x86_64 258. Create a doc-root directory. mode e.g. 0x1ed (0755).
1246func sys_mkdir(path: *u8, mode: i64) -> i64 { return __syscall(34, AT_FDCWD, path, mode, 0, 0, 0) }
1247
1248// fchmodat -- rv64 53 -> x86_64 268. +x a freshly-deployed daemon binary (mode 0x1ed). flags=0.
1249func nx_chmod(path: *u8, mode: i64) -> i64 { return __syscall(53, AT_FDCWD, path, mode, 0, 0, 0) }
1250
1251// setsid -- x86_64 = 112 (not in the rv64->x86 table, so the literal passes through). Detach a forked
1252// process into a NEW session so it survives the SSH/parent close -- sovereign daemonization (no shell setsid).
1253func nx_setsid() -> i64 { return __syscall(112, 0, 0, 0, 0, 0, 0) }
1254
1255// CLOCK_MONOTONIC = 1. ts is 16 bytes {sec: i64, nsec: i64}.
1256// Returns 0 / -errno.
1257func sys_clock_gettime_mono(ts: *i64) -> i64 {
1258 return __syscall(SYS_CLOCK_GETTIME, 1, ts, 0, 0, 0, 0)
1259}
1260
1261// CLOCK_REALTIME = 0 -- wall-clock seconds since the Unix epoch. Use
1262// this (NOT monotonic) for anything that must match calendar time:
1263// X.509 notBefore/notAfter, logs, TLS timestamps. Monotonic returns
1264// time-since-boot, which encodes as ~1970 when (mis)used as an epoch.
1265func sys_clock_gettime_real(ts: *i64) -> i64 {
1266 return __syscall(SYS_CLOCK_GETTIME, 0, ts, 0, 0, 0, 0)
1267}
1268
1269// Wall-clock seconds since the Unix epoch.
1270func sys_now_realtime_sec() -> i64 {
1271 let ts: *i64 = sys_mmap(16) as *i64
1272 sys_clock_gettime_real(ts)
1273 return ts[0]
1274}
1275
1276// Wall-clock milliseconds since the Unix epoch.
1277func sys_now_realtime_ms() -> i64 {
1278 let ts: *i64 = sys_mmap(16) as *i64
1279 sys_clock_gettime_real(ts)
1280 return ts[0] * 1000 + ts[1] / SYS_MAGIC_1000000
1281}
1282
1283// Wall-clock MICROSECONDS since the Unix epoch -- the CROSS-MACHINE stamp.
1284// ★ Use this, never sys_now_us(), for any value one machine writes and ANOTHER machine judges
1285// (fleet beats, lease expiry, telemetry rows). Monotonic counts from each machine's OWN boot, so
1286// subtracting one node's monotonic stamp from another's monotonic now yields the difference of two
1287// unrelated boot epochs -- the remote row then reads as ancient (or future-forged) and a freshness
1288// guard rejects every honest remote node while looking like it is working.
1289func sys_now_realtime_us() -> i64 {
1290 let ts: *i64 = sys_mmap(16) as *i64
1291 sys_clock_gettime_real(ts)
1292 return ts[0] * SYS_MAGIC_1000000 + ts[1] / 1000
1293}
1294
1295// Convenience: monotonic time in milliseconds. Caller does not own
1296// the timespec buffer -- it is mmap'd once per call (cheap; the
1297// underlying syscall already costs more than the page fault).
1298func sys_now_ms() -> i64 {
1299 let ts: *i64 = sys_mmap(16) as *i64
1300 sys_clock_gettime_mono(ts)
1301 let sec_part: i64 = ts[0] * 1000
1302 let nsec_part: i64 = ts[1] / SYS_MAGIC_1000000
1303 return sec_part + nsec_part
1304}
1305
1306// Convenience: monotonic time in microseconds. Used by per-request
1307// elapsed-time tracking in search engines + benches where ms is too
1308// coarse. Same caller-ownership rules as sys_now_ms.
1309func sys_now_us() -> i64 {
1310 let ts: *i64 = sys_mmap(16) as *i64
1311 sys_clock_gettime_mono(ts)
1312 let sec_part: i64 = ts[0] * SYS_MAGIC_1000000
1313 let nsec_part: i64 = ts[1] / 1000
1314 return sec_part + nsec_part
1315}
1316
1317// Alias used by nx_search_onsite_engine etc. Matches `_us` naming
1318// convention. Substrate-canonical name is sys_now_us; this alias
1319// preserves existing call sites without churn.
1320func sys_clock_now_us() -> i64 {
1321 return sys_now_us()
1322}
1323
1324// Read the entire file at `path` into a fresh mmap'd buffer. Returns
1325// a null-terminated *u8 plus writes the byte count to *out_len. On
1326// error (open failure, oversize) returns null and leaves out_len = 0.
1327// Uses a fixed 1 MiB buffer for the first pass; larger sources need a
1328// growth loop.
1329// ---- process control (Linux RV64) ----------------------------
1330//
1331// Lets NishiLang programs spawn other processes -- prerequisite
1332// for replacing shell scripts (f6_gate.sh) with .nx equivalents.
1333// NishiOS will expose a different process model (capability-based);
1334// these wrappers are the Linux-host compatibility layer.
1335
1336@ifdef TARGET_X86_64
1337const SYS_CLONE: i64 = 56
1338const SYS_EXECVE: i64 = 59
1339const SYS_WAIT4: i64 = 61
1340const SYS_PIPE2: i64 = 293
1341const SYS_DUP3: i64 = 292
1342@endif
1343
1344@ifndef TARGET_X86_64
1345const SYS_CLONE: i64 = 220
1346const SYS_EXECVE: i64 = 221
1347const SYS_WAIT4: i64 = 260
1348const SYS_PIPE2: i64 = 59
1349const SYS_DUP3: i64 = 24
1350@endif
1351
1352// Clone flags (subset). CLONE_VFORK blocks parent until child
1353// exec's or exits, matching fork() semantics closely enough for
1354// our spawn-then-wait patterns.
1355const CLONE_VM: i64 = 0x00000100
1356const CLONE_VFORK: i64 = 0x00004000
1357const SIGCHLD: i64 = 17
1358
1359// Create a child process via Linux clone(). Returns:
1360// > 0 in the parent: child PID
1361// == 0 in the child: child should exec or exit
1362// < 0 on error: -errno
1363// Uses SIGCHLD as the signal that parent receives on child exit
1364// (the libc fork() default); no shared memory or thread flags.
1365// ---- namespace / container family (debt 1785528831) ----------------
1366// Moved here from nx_syscalls_x86_64.nx so ONE module owns the wrapper set. Their
1367// absence here is why nx_container.nx had to import that module as a SECOND syscall
1368// layer, which put every wrapper in the TU twice and let definition ORDER pick the
1369// winner, silently, until the duplicate-definition guard made it fail closed.
1370func sys_unshare(flags: i64) -> i64 {
1371 return __syscall(SYS_UNSHARE, flags, 0, 0, 0, 0, 0)
1372}
1373func sys_mount(source: *u8, target: *u8, fs_type: *u8, mountflags: i64, data: *u8) -> i64 {
1374 return __syscall(SYS_MOUNT, source, target, fs_type, mountflags, data, 0)
1375}
1376func sys_chroot(path: *u8) -> i64 {
1377 return __syscall(SYS_CHROOT, path, 0, 0, 0, 0, 0)
1378}
1379func sys_getuid() -> i64 {
1380 return __syscall(SYS_GETUID, 0, 0, 0, 0, 0, 0)
1381}
1382func sys_getgid() -> i64 {
1383 return __syscall(SYS_GETGID, 0, 0, 0, 0, 0, 0)
1384}
1385
1386func sys_fork() -> i64 {
1387 return __syscall(SYS_CLONE, SIGCHLD, 0, 0, 0, 0, 0)
1388}
1389
1390// Replace the current process image. `path` is the executable
1391// (absolute or in $PATH if the child first does a fresh clone).
1392// `argv` is a null-terminated array of *u8 (already-marshalled).
1393// `envp` same shape, or null for "inherit parent's env".
1394// Only returns on failure (-errno).
1395// EXEC WITH A CLEAN FD TABLE (seq1785451144). A child inherits every fd its parent held, INCLUDING
1396// listen sockets, across fork AND execve. That is how nx_opaque_login came to hold mgmt s :18098
1397// alongside mgmt itself -- two listeners on one port, connections split between them, a VALID route
1398// answering 404 on some requests. There is no error anywhere in that state, which is why it was
1399// filed as a transport flake for months.
1400// ADDITIVE ON PURPOSE: sys_execve is left byte-identical (910 call sites across 719 files -- a
1401// global change there is unverifiable in one session). Spawners opt in by calling THIS instead.
1402// AUDIT THAT MAKES IT SAFE: zero call sites in the tree dup3 to a target fd above 2, so no exec d
1403// child is deliberately handed a high fd; 0/1/2 are preserved untouched.
1404// Linux child lifetime binding: call in the freshly forked child, before exec.
1405// The expected parent PID is captured before fork, closing the pre-arm death race.
1406// Kernel semantics bind to the creating thread; privileged exec can clear this.
1407const NX_SYS_PRCTL: i64 = 167
1408const NX_PR_SET_PDEATHSIG: i64 = 1
1409const NX_PR_SET_CHILD_SUBREAPER: i64 = 36
1410func sys_prctl(option: i64, arg: i64) -> i64 {
1411 return __syscall(NX_SYS_PRCTL,option,arg,0,0,0,0)
1412}
1413func sys_bind_parent_lifetime(expected_parent: i64, signal: i64) -> i64 {
1414 if expected_parent <= 0 || signal <= 0 { return 0-22 }
1415 let armed: i64=sys_prctl(NX_PR_SET_PDEATHSIG,signal)
1416 if armed < 0 { return armed }
1417 let parent: i64=__syscall(173,0,0,0,0,0,0)
1418 if parent != expected_parent { return 0-10 }
1419 return 0
1420}
1421
1422// Linux waitid observes termination without releasing the child's PID when WNOWAIT is set.
1423// Portable syscall 95 requires the matching x86 backend translation to 247.
1424const SYS_WAITID_PORTABLE: i64 = 95
1425const NX_WAIT_P_PID: i64 = 1
1426const NX_WAIT_EXITED: i64 = 4
1427const NX_WAIT_NOWAIT: i64 = 0x01000000
1428const NX_WAIT_SIGINFO_BYTES: i64 = 128
1429func sys_waitid(idtype: i64, id: i64, info: *u8, options: i64) -> i64 {
1430 return __syscall(SYS_WAITID_PORTABLE,idtype,id,info as i64,options,0,0)
1431}
1432
1433// Post-fork only: the child owns its descriptor table. The buffer bounds a
1434// getdents batch, never the descriptor numbers or number of open handles.
1435const NX_FD_DENT_BUFFER: i64 = 4096
1436const NX_SYS_CLOSE_RANGE: i64 = 436 // Linux x86_64 and asm-generic ABI
1437const NX_FD_UINT_MAX: i64 = 4294967295
1438func sys_close_inherited_proc(first: i64) -> i64 {
1439 let directory: i64=sys_openat_rd("/proc/self/fd")
1440 if directory < 0 { return directory }
1441 let buf: *u8=sys_mmap(NX_FD_DENT_BUFFER)
1442 var result: i64=0
1443 var running: i64=1
1444 while running == 1 {
1445 let n: i64=sys_getdents64(directory,buf,NX_FD_DENT_BUFFER)
1446 if n == (0-4) { continue }
1447 if n <= 0 { result=n; break }
1448 var off: i64=0
1449 while off < n {
1450 if n-off < 20 { result=0-5; running=0; break }
1451 let rec: *u8=buf+off
1452 let size: i64=dirent_reclen(rec)
1453 if size < 20 || size > n-off { result=0-5; running=0; break }
1454 var i: i64=19
1455 var fd: i64=0
1456 var valid: i64=1
1457 while i < size {
1458 let c: i64=rec[i] as i64
1459 if c == 0 { break }
1460 if c < 48 || c > 57 { valid=0; break }
1461 if fd > (2147483647-(c-48))/10 { valid=0; break }
1462 fd=fd*10+c-48; i=i+1
1463 }
1464 if i == 19 || i == size { valid=0 }
1465 if valid == 1 && fd >= first && fd != directory {
1466 // Linux releases the descriptor even when close reports a late
1467 // I/O error; never retry close and risk a reused descriptor.
1468 let closed: i64=sys_close(fd)
1469 if closed < 0 && closed != (0-9) { result=closed; running=0; break }
1470 }
1471 off=off+size
1472 }
1473 }
1474 let closedir: i64=sys_close(directory)
1475 sys_munmap(buf,NX_FD_DENT_BUFFER)
1476 if result == 0 && closedir < 0 { result=closedir }
1477 return result
1478}
1479func sys_close_inherited(first: i64) -> i64 {
1480 if first < 0 { return 0-22 }
1481 let rc: i64=__syscall(NX_SYS_CLOSE_RANGE,first,NX_FD_UINT_MAX,0,0,0,0)
1482 if rc == (0-38) { return sys_close_inherited_proc(first) }
1483 return rc
1484}
1485func sys_execve_clean(path: *u8, argv: *i64, envp: *i64) -> i64 {
1486 let rc: i64=sys_close_inherited(3)
1487 if rc < 0 { return rc }
1488 return sys_execve(path,argv,envp)
1489}
1490
1491func sys_execve(path: *u8, argv: *i64, envp: *i64) -> i64 {
1492 return __syscall(SYS_EXECVE, path, argv, envp, 0, 0, 0)
1493}
1494
1495// Wait for a child to exit. `pid` = -1 waits for ANY child,
1496// otherwise waits for that specific PID. `status` is a caller-
1497// mmapped i64 slot: on exit the low 16 bits carry Linux's w* status
1498// flags (WIFEXITED / WEXITSTATUS). Returns the reaped child's PID
1499// or -errno.
1500func sys_wait4(pid: i64, status: *i64, options: i64) -> i64 {
1501 return __syscall(SYS_WAIT4, pid, status, options, 0, 0, 0)
1502}
1503
1504// Extract exit code from a wait4 status word. Matches the glibc
1505// WEXITSTATUS macro: bits 8-15 of the low 16.
1506func wait_exit_code(status: i64) -> i64 {
1507 return (status >> 8) & 0xFF
1508}
1509
1510// Terminating signal from a wait4 status (0 when the child exited normally). Sibling of
1511// wait_exit_code; RESTORED 2026-07-30 after a stale whole-tree push erased both it and
1512// sys_ignore_sigpipe below, while three files still CALLED them (nx_http_server, nx_sigpipe_gate,
1513// nx_tools_api_serve) -- so the tree could not build until they came back.
1514func wait_term_signal(status: i64) -> i64 {
1515 return status & 0x7f
1516}
1517
1518// THE ONE RULER for "what result code did this process actually produce". Use this, not
1519// wait_exit_code, anywhere the answer becomes a VERDICT.
1520//
1521// WHY IT EXISTS, MEASURED 2026-08-25. wait_exit_code is WEXITSTATUS and is correctly named:
1522// bits 8-15 of the status word. But a child KILLED BY A SIGNAL has no exit status at all, and
1523// those bits are ZERO -- so a SEGFAULTING process is indistinguishable from a clean exit 0 to
1524// every caller that reads only wait_exit_code. Measured live: a gate that SIGSEGV'd mid-run was
1525// served by /api/gate_run as exit_code 0, verdict GREEN. A CRASHED GATE WORE A PASS.
1526//
1527// This is not a new discovery in this estate -- and that is the point. nx_gatekit_lib's
1528// gk_wait_code already carried exactly this rule, with its own measurement recorded (two gates
1529// the 60 s watchdog KILLED journaled `GREEN exit=0 ms=60443`). It was fixed THERE in August and
1530// left unfixed in nx_tool_run, which is the shared exec primitive sitting behind /api/gate_run,
1531// /api/build and 51 other consumers. A LAW APPLIED IN ONE ORGAN AND NOT ITS SIBLING IS HALF A
1532// LAW, AND THE HALF LEFT UNDONE IS THE ONE ON THE PRODUCTION PATH. So the rule now lives HERE,
1533// beside the two accessors it is composed of, and gk_wait_code delegates to it: one ruler.
1534//
1535// Shell convention 128+signal (137 SIGKILL, 139 SIGSEGV) is deliberate: it makes the death both
1536// VISIBLE and NON-ZERO, so every existing caller that branches on rc != 0 sees it with no change.
1537// wait_exit_code is left EXACTLY as it was -- 85 call sites across the corpus (corpus_complete=1)
1538// read it, and silently redefining WEXITSTATUS under them would be the cure being worse.
1539func wait_status_rc(status: i64) -> i64 {
1540 let sig: i64 = wait_term_signal(status)
1541 if sig != 0 { return 128 + sig }
1542 return wait_exit_code(status)
1543}
1544
1545// Ignore SIGPIPE process-wide, so writing to a socket the peer already closed returns -EPIPE
1546// instead of KILLING the process. SIGPIPE default action is TERMINATE, which for a daemon means
1547// every client that walks away mid-response is an outage -- this one call at the listen primitive
1548// is inherited by all 52 consumers of nx_http_server_listen.
1549// rt_sigaction(SIGPIPE, {handler=SIG_IGN}, NULL, 8): syscall 13 on x86-64, which happens to equal
1550// the signal number. SA_RESTORER is deliberately NOT set -- the kernel consults it only when it
1551// DELIVERS a handler frame, and SIG_IGN never delivers one.
1552// PROVEN, not asserted: nx_sigpipe_gate forks a child that writes to a closed pipe and demands
1553// death-by-signal-13 WITHOUT this call and a clean -EPIPE WITH it.
1554// Restore a signal to its DEFAULT disposition. THE INVERSE OF sys_ignore_sigpipe, and it exists
1555// because SIG_IGN is inherited across BOTH fork and execve: a daemon that ignores SIGPIPE hands
1556// that ignore to every child it spawns, FOREVER. That silently corrupted verification -- the
1557// sigpipe gate reported 4/5 RED under /api/gate_run and 5/5 GREEN under a shell, same binary,
1558// same minute, because its DISEASE control (writing to a closed peer must KILL) could not be
1559// observed inside an environment where the kill was already disabled (seq1463). A harness must
1560// not change the state it is verifying; where it must, it has to hand back a clean slate.
1561// ⚠the same inheritance can also produce a FALSE GREEN, which is the far more dangerous half.
1562func sys_default_signal(sig: i64) -> i64 {
1563 let act: *i64 = sys_mmap(64) as *i64
1564 act[0] = 0
1565 act[1] = 0
1566 act[2] = 0
1567 act[3] = 0
1568 return __syscall(13, sig, act as i64, 0, 8, 0, 0)
1569}
1570
1571func sys_ignore_sigpipe() -> i64 {
1572 let act: *i64 = sys_mmap(64) as *i64
1573 act[0] = 1
1574 act[1] = 0
1575 act[2] = 0
1576 act[3] = 0
1577 return __syscall(13, 13, act as i64, 0, 8, 0, 0)
1578}
1579
1580// Create a pipe. `fds` must point at 8+ writable bytes; the kernel
1581// packs BOTH int32 fds into fds[0]: read end = low 32 bits, write end
1582// = HIGH 32 bits (fds[1] is never written -- the old comment claiming
1583// fds[1]=write-end caused a false-pass KAT + a hung gate, 2026-07-16).
1584// Extract: rfd = fds[0] & 0xffffffff; wfd = (fds[0] / 4294967296) &
1585// 0xffffffff. Returns 0 on success, -errno on failure.
1586func sys_pipe2(fds: *i64, flags: i64) -> i64 {
1587 return __syscall(SYS_PIPE2, fds, flags, 0, 0, 0, 0)
1588}
1589
1590// Duplicate `oldfd` onto `newfd`, closing `newfd` first if open.
1591// Used to wire child stdout to a pipe: dup3(pipe_write_end, 1).
1592func sys_dup3(oldfd: i64, newfd: i64, flags: i64) -> i64 {
1593 return __syscall(SYS_DUP3, oldfd, newfd, flags, 0, 0, 0)
1594}
1595
1596// ---- directory listing (Linux RV64 getdents64) ---------------
1597//
1598// Foundation for ls / glob / dir-walk helpers. Linux returns
1599// linux_dirent64 records:
1600// u64 d_ino (inode, ignored here)
1601// s64 d_off (next-record offset)
1602// u16 d_reclen (this record's byte length)
1603// u8 d_type (file type; DT_DIR=4, DT_REG=8, DT_LNK=10)
1604// char d_name[] (null-terminated name, padded so d_reclen
1605// carries us to the next record boundary)
1606// Total struct header: 19 bytes, then name up to d_reclen - 19.
1607
1608@ifdef TARGET_X86_64
1609const SYS_GETDENTS64: i64 = 217
1610@endif
1611@ifndef TARGET_X86_64
1612const SYS_GETDENTS64: i64 = 61
1613@endif
1614
1615const DT_UNKNOWN: i64 = 0
1616const DT_FIFO: i64 = 1
1617const DT_CHR: i64 = 2
1618const DT_DIR: i64 = 4
1619const DT_BLK: i64 = 6
1620const DT_REG: i64 = 8
1621const DT_LNK: i64 = 10
1622const DT_SOCK: i64 = 12
1623
1624// Raw syscall. Returns bytes written on success (0 = end-of-dir),
1625// or -errno on failure.
1626func sys_getdents64(fd: i64, buf: *u8, buf_len: i64) -> i64 {
1627 return __syscall(SYS_GETDENTS64, fd, buf, buf_len, 0, 0, 0)
1628}
1629
1630// Extract fields from a linux_dirent64 record. `rec` points at
1631// the start of the record; fields are at fixed offsets.
1632func dirent_reclen(rec: *u8) -> i64 {
1633 // d_reclen is u16 at offset 16. Read as two bytes little-endian.
1634 let lo: i64 = rec[16]
1635 let hi: i64 = rec[17]
1636 return lo | (hi << 8)
1637}
1638
1639func dirent_type(rec: *u8) -> i64 {
1640 return rec[18]
1641}
1642
1643// Pointer to the null-terminated name inside the record.
1644func dirent_name(rec: *u8) -> *u8 {
1645 let base: i64 = rec as i64
1646 return (base + 19) as *u8
1647}
1648
1649// ---- content-addressed file reader ---------------------------
1650
1651func sys_read_file(path: *u8, out_len: *i64) -> *u8 {
1652 let fd: i64 = sys_openat_rd(path)
1653 if fd < 0 {
1654 *out_len = 0
1655 return 0 as *u8
1656 }
1657 // DEBT-EATEN 2026-07-15: the old fixed 4 GiB cap SILENTLY TRUNCATED bigger files (a 9 GB gguf would
1658 // short-read into plausible-garbage tensors -- the worst failure class). Now the buffer is sized from
1659 // the file itself (lseek END), so ANY size reads fully. Physical pages still allocate on-demand. For
1660 // zero-copy any-size READ-ONLY access prefer sys_map_file (below).
1661 // DEBT-EATEN 2026-08-19 (1787076780): when the size is UNKNOWABLE (lseek END <= 0: /proc files, pipes
1662 // -- AND every empty regular file, which reports 0 just the same) this used to reserve
1663 // SYS_MAGIC_4294967296 of address space per call. Untouched pages were never resident, but the
1664 // mapping WAS: a daemon that read an empty registry every sweep ballooned its VmSize by 4 GiB per
1665 // read (measured: smoke instances at a 4.2 GB base), the leak screens flagged it, and sys_free_file
1666 // could only release what was read. The size-unknowable path now GROWS: start at SYS_READ_GROW_INIT,
1667 // double while the window fills, and hand back an EXACT mapping (total + 16) so sys_free_file
1668 // releases all of it. An empty file costs one small read and a 16-byte arena cell; /proc/stat fits
1669 // the first window; a pipe of any length still reads whole. The known-size path is unchanged.
1670 let fsz: i64 = sys_lseek(fd, 0, 2)
1671 sys_lseek(fd, 0, 0)
1672 var cap: i64 = SYS_READ_GROW_INIT
1673 var grow: i64 = 1
1674 if fsz > 0 { cap = fsz; grow = 0 }
1675 var buf: *u8 = sys_mmap(cap + 16)
1676 var total: i64 = 0
1677 var go: i64 = 1
1678 while go == 1 {
1679 let base: i64 = buf as i64
1680 let tail: *u8 = (base + total) as *u8
1681 let n: i64 = sys_read(fd, tail, cap - total)
1682 if n <= 0 { go = 0 }
1683 if n > 0 { total = total + n }
1684 if total >= cap {
1685 if grow == 0 { go = 0 } else {
1686 // the window filled and the size is unknown: double it, copy, release the old mapping
1687 let ncap: i64 = cap * 2
1688 let nb: *u8 = sys_mmap(ncap + 16)
1689 var ci: i64 = 0
1690 let obase: i64 = buf as i64
1691 let nbase: i64 = nb as i64
1692 while ci < total { let src: *u8 = (obase + ci) as *u8; let dst: *u8 = (nbase + ci) as *u8; dst[0] = src[0]; ci = ci + 1 }
1693 sys_munmap(buf, cap + 16)
1694 buf = nb
1695 cap = ncap
1696 }
1697 }
1698 }
1699 sys_close(fd)
1700 if grow == 1 {
1701 // hand back an EXACT mapping so the paired free releases everything (the doubled window would
1702 // otherwise leave its slack mapped forever -- the address-space leak this change exists to end)
1703 let xb: *u8 = sys_mmap(total + 16)
1704 var xi: i64 = 0
1705 let gbase: i64 = buf as i64
1706 let xbase: i64 = xb as i64
1707 while xi < total { let gsrc: *u8 = (gbase + xi) as *u8; let xdst: *u8 = (xbase + xi) as *u8; xdst[0] = gsrc[0]; xi = xi + 1 }
1708 sys_munmap(buf, cap + 16)
1709 buf = xb
1710 }
1711 // Null-terminate for the lexer.
1712 let bbase: i64 = buf as i64
1713 let term: *u8 = (bbase + total) as *u8
1714 term[0] = 0
1715 *out_len = total
1716 return buf
1717}
1718
1719// PAIRED FREE FOR sys_read_file (2026-08-17). sys_read_file mmaps `cap + 16` where cap is the FILE SIZE
1720// and returns only the pointer -- so any caller that frees it must know the padding, and a caller that
1721// unmaps `len` alone leaks the tail page whenever the file size sits just under a page boundary.
1722// ★A CALLER FORCED TO KNOW ITS ALLOCATOR'S PADDING IS A COUPLING THAT WILL DRIFT -- so the +16 lives
1723// HERE, beside the +16 it mirrors, instead of being retyped at every call site.
1724// Pass the length sys_read_file reported through out_len; this re-derives the mapping from it.
1725// Null-safe by construction: sys_read_file returns 0 on failure, so callers need no extra guard --
1726// ★A FREE THAT REFUSES NULL IS A FREE NOBODY HAS TO WRAP IN AN IF.
1727// EXACT for every path since 2026-08-19: the size-unknowable fallback (lseek <= 0: /proc, pipes, empty
1728// regular files) now returns a mapping of exactly total + 16, so this releases ALL of it. (It used to
1729// map SYS_MAGIC_4294967296 of address space and release only what was read -- stated then, ended now.)
1730// WHY IT EXISTS: nx_sites_daemon serves /wiki/roadmap by calling sys_read_file PER REQUEST inside a loop
1731// that runs up to NX_SD_MAX_REQ_PER_CONN (64) times per connection and never released it -- an 8,408 B
1732// file became 3 fresh pages and a fresh kernel VMA on every hit, held until the child exited.
1733func sys_free_file(buf: *u8, len: i64) -> i64 {
1734 if (buf as i64) == 0 { return 0 }
1735 if len < 0 { return 0 }
1736 return sys_munmap(buf, len + 16)
1737}
1738
1739// Read-only FILE-BACKED map of the whole file (PROT_READ=1, MAP_PRIVATE=2): any size, zero-copy -- only
1740// touched pages become resident (the lazy-MoE shape: a 9 GB model serves in ~active-set RSS, and load
1741// time is ~0 because nothing is copied). NO NUL pad (a file mapping cannot be extended) -- BINARY
1742// consumers only; text/lexer callers keep sys_read_file. Returns 0 on failure; *out_len = file size.
1743// Read-only by construction (PROT_READ; writes fault -- Rule 26-friendly).
1744func sys_map_file(path: *u8, out_len: *i64) -> *u8 {
1745 *out_len = 0
1746 let fd: i64 = sys_openat_rd(path)
1747 if fd < 0 { return 0 as *u8 }
1748 let fsz: i64 = sys_lseek(fd, 0, 2)
1749 if fsz <= 0 { sys_close(fd); return 0 as *u8 }
1750 let r: i64 = __syscall(SYS_MMAP, 0, fsz, 1, 2, fd, 0)
1751 sys_close(fd)
1752 if r <= 0 { return 0 as *u8 }
1753 *out_len = fsz
1754 return r as *u8
1755}
1756
1757// Sleep for `ms` milliseconds against CLOCK_MONOTONIC (relative).
1758// Returns 0 on success, negative errno on failure. Caller-supplied
1759// budget: ms <= 0 is a no-op; very large values are accepted as-is
1760// (the kernel will saturate to its own clamp). Defined at the bottom
1761// of this file so sys_mmap is in scope (single-pass parser).
1762func sys_sleep_ms(ms: i64) -> i64 {
1763 if ms <= 0 { return 0 }
1764 // struct timespec { sec: i64, nsec: i64 } -- 16 bytes RV64.
1765 let req: *u8 = sys_mmap(16)
1766 let rem: *u8 = sys_mmap(16)
1767 let secs: i64 = ms / 1000
1768 let nsec: i64 = (ms - secs * 1000) * SYS_MAGIC_1000000 // remainder ms -> ns
1769 let req_sec: *i64 = req as *i64
1770 let req_nsec: *i64 = ((req as i64) + 8) as *i64
1771 req_sec[0] = secs
1772 req_nsec[0] = nsec
1773 // clock_nanosleep(CLOCK_MONOTONIC=1, flags=0, req, rem). On EINTR (-4) a signal (e.g. SIGCHLD from a
1774 // reaped child) cut the sleep short and wrote the leftover into rem -- RESUME it, otherwise a caller
1775 // that uses the sleep as a timer (the torrent pool's 2s tick) gets spun into a busy loop by child
1776 // deaths and any tick-based budget collapses to milliseconds. A sleep must sleep its full duration.
1777 var r: i64 = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
1778 var guard: i64 = 0
1779 while r == (0 - 4) {
1780 if guard > SYS_MAGIC_100000 { r = 0 } else {
1781 let rs: *i64 = rem as *i64
1782 let rn: *i64 = ((rem as i64) + 8) as *i64
1783 req_sec[0] = rs[0]
1784 req_nsec[0] = rn[0]
1785 r = __syscall(SYS_CLOCK_NANOSLEEP, 1, 0, req as i64, rem as i64, 0, 0)
1786 guard = guard + 1
1787 }
1788 }
1789 sys_munmap(req, 16); sys_munmap(rem, 16) // FREE the timespec pages -- every call mmap'd 2 pages; in a
1790 // long-running poll loop (the supervisor's 15s tick) that leaked ~8KB/iter until mmap -> -12 -> SEGFAULT.
1791 return r
1792}
1793
1794// ---- sockets (RV64 generic syscall numbers) ----------------------
1795//
1796// Source uses RV64 numbers; the x86_64 backend's
1797// x86ctx_rv64_to_x86_64_syscall table translates at codegen time.
1798// Numbers from arch/arm64/include/asm/unistd.h (RV64 inherits the
1799// generic ABI).
1800
1801// Socket-family syscall numbers via @ifdef macro -- mirrors the
1802// pattern already used for SYS_READ/WRITE/MMAP/etc. above. Without
1803// this gate, --target x86_64 compiled the RV64 numbers as literals
1804// into the `syscall` instruction (e.g. 198 = sched_setaffinity on
1805// x86_64, not socket) and any daemon using sys_socket() died with
1806// ENOSYS before printing its banner -- caught by the nx_signaling
1807// stone S2 deploy on 2026-05-20 (see [[project-cross-isa-syscall-
1808// unification-gap-2026-05-20]]).
1809@ifdef TARGET_X86_64
1810const SYS_SOCKET: i64 = 41
1811const SYS_BIND: i64 = 49
1812const SYS_LISTEN: i64 = 50
1813const SYS_ACCEPT: i64 = 43
1814const SYS_CONNECT: i64 = 42
1815const SYS_SETSOCKOPT: i64 = 54
1816const SYS_SENDTO: i64 = 44
1817const SYS_RECVFROM: i64 = 45
1818const SYS_SHUTDOWN: i64 = 48
1819@endif
1820
1821@ifndef TARGET_X86_64
1822const SYS_SOCKET: i64 = 198
1823const SYS_BIND: i64 = 200
1824const SYS_LISTEN: i64 = 201
1825const SYS_ACCEPT: i64 = 202
1826const SYS_CONNECT: i64 = 203
1827const SYS_SETSOCKOPT: i64 = 208
1828const SYS_SENDTO: i64 = 206
1829const SYS_RECVFROM: i64 = 207
1830const SYS_SHUTDOWN: i64 = 210
1831@endif
1832
1833// Socket-option constants used by nx_http_server / nx_https_server.
1834const SOL_SOCKET: i64 = 1
1835const SO_REUSEADDR: i64 = 2
1836// Receive/send timeouts (Linux x86_64). optval is a struct timeval
1837// {tv_sec: i64, tv_usec: i64} (16 bytes). Essential on PUBLIC sockets:
1838// without them, a single silent/slow client hangs a blocking read
1839// forever -> trivial DoS on a single-threaded accept loop.
1840const SO_SNDTIMEO: i64 = 21
1841const SO_RCVTIMEO: i64 = 20
1842
1843// setsockopt(2) -- set a socket option. Defined BEFORE its first caller
1844// (sys_set_socket_timeout, below): NishiLang forbids forward references,
1845// so the definition must precede every use.
1846func sys_setsockopt(fd: i64, level: i64, optname: i64,
1847 optval: *u8, optlen: i64) -> i64 {
1848 return __syscall(SYS_SETSOCKOPT, fd, level, optname, optval, optlen, 0)
1849}
1850
1851// Set a receive+send timeout (in whole seconds) on a socket fd.
1852// tv is munmap'd before return (LEAK FIXED 2026-07-16): this is called once per PROBE by the daemon
1853// supervisor (35/cycle forever -> ~800MB VSZ/day) and once per CONNECTION by fork-per-connection daemons.
1854// The unfreed page-per-call ballooned VSZ until heuristic overcommit made fork() return -ENOMEM (the
1855// proven pid=-12 failure class) -- likely the historical VSZ pressure behind the vsz_watchdog.
1856func sys_set_socket_timeout(fd: i64, secs: i64) -> i64 {
1857 let tv: *i64 = (sys_mmap(16)) as *i64
1858 tv[0] = secs // tv_sec
1859 tv[1] = 0 // tv_usec
1860 sys_setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv as *u8, 16)
1861 sys_setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, tv as *u8, 16)
1862 sys_munmap(tv as *u8, 16)
1863 return 0
1864}
1865
1866// alarm(2): deliver SIGALRM after `secs` seconds (0 cancels a pending alarm). No SIGALRM handler is installed, so
1867// the default action TERMINATES the process. Used as a per-request watchdog inside a forked request-child: a
1868// pathologically-slow page can then never hang the child forever (which would leak its buffers + pile up procs).
1869const SYS_ALARM: i64 = 37
1870func sys_alarm(secs: i64) -> i64 { return __syscall(SYS_ALARM, secs, 0, 0, 0, 0, 0) }
1871
1872const AF_INET: i64 = 2
1873const SOCK_STREAM: i64 = 1
1874const SOCK_DGRAM: i64 = 2
1875
1876func sys_socket(domain: i64, sock_type: i64, protocol: i64) -> i64 {
1877 return __syscall(SYS_SOCKET, domain, sock_type, protocol, 0, 0, 0)
1878}
1879// Pack an AF_INET any-address sockaddr_in (16 bytes) for `port` at `addr`.
1880// RESTORED INTO THE OWNER 2026-08-19: this lived in the old full nx_syscalls_x86_64.nx and was the
1881// one wrapper WITH LIVE CALLERS (nx_nishipages_serve, nx_udp) that the 2026-07-31 alias-stub
1882// consolidation dropped -- both lanes sat NAS-unbuildable ("I do not know the name") until the
1883// rebuild-drain surfaced them. Body verbatim from the old file, including its documented
1884// workaround: NO `as u8` casts on the byte stores -- the array-element-store already truncates
1885// when the lvalue is *u8, and casts on this path once tripped a codegen defect.
1886// (The old file's other two uncalled orphans, sys_pivot_root/sys_umount2, were left dead on a
1887// zero-caller full-tree grep -- restoring an uncalled wrapper is inventory, not capability.)
1888func sockaddr_in_init(addr: *u8, port: i64) -> i64 {
1889 addr[0] = 2 // AF_INET low byte
1890 addr[1] = 0
1891 // Port in network byte order (big-endian).
1892 let hi: i64 = (port >> 8) & 0xFF
1893 let lo: i64 = port & 0xFF
1894 addr[2] = hi
1895 addr[3] = lo
1896 addr[4] = 0
1897 addr[5] = 0
1898 addr[6] = 0
1899 addr[7] = 0
1900 addr[8] = 0
1901 addr[9] = 0
1902 addr[10] = 0
1903 addr[11] = 0
1904 addr[12] = 0
1905 addr[13] = 0
1906 addr[14] = 0
1907 addr[15] = 0
1908 return 0
1909}
1910
1911func sys_bind(fd: i64, addr: *u8, addr_len: i64) -> i64 {
1912 return __syscall(SYS_BIND, fd, addr, addr_len, 0, 0, 0)
1913}
1914func sys_listen(fd: i64, backlog: i64) -> i64 {
1915 return __syscall(SYS_LISTEN, fd, backlog, 0, 0, 0, 0)
1916}
1917// accept(2) -- accept the next pending connection on a listening socket.
1918// Single-arg form (kernel ignores NULL addr/addr_len writes). Existing
1919// nx_http_server callers use this signature; the 3-arg form is provided
1920// as sys_accept_with_addr for outliers needing peer address.
1921func sys_accept(fd: i64) -> i64 {
1922 return __syscall(SYS_ACCEPT, fd, 0, 0, 0, 0, 0)
1923}
1924func sys_accept_with_addr(fd: i64, addr: *u8, addr_len: *i64) -> i64 {
1925 return __syscall(SYS_ACCEPT, fd, addr, addr_len, 0, 0, 0)
1926}
1927// shutdown(2) -- half-close a socket. how: 0=RD, 1=WR, 2=RDWR.
1928func sys_shutdown(fd: i64, how: i64) -> i64 {
1929 return __syscall(SYS_SHUTDOWN, fd, how, 0, 0, 0, 0)
1930}
1931func sys_connect(fd: i64, addr: *u8, addr_len: i64) -> i64 {
1932 return __syscall(SYS_CONNECT, fd, addr, addr_len, 0, 0, 0)
1933}
1934func sys_sendto(fd: i64, buf: *u8, n: i64, flags: i64,
1935 dest_addr: *u8, addr_len: i64) -> i64 {
1936 return __syscall(SYS_SENDTO, fd, buf, n, flags, dest_addr, addr_len)
1937}
1938func sys_recvfrom(fd: i64, buf: *u8, n: i64, flags: i64,
1939 src_addr: *u8, addr_len: *i64) -> i64 {
1940 return __syscall(SYS_RECVFROM, fd, buf, n, flags, src_addr, addr_len)
1941}
1942
1943// ---- SCM_RIGHTS DESCRIPTOR PASSING (sendmsg/recvmsg over AF_UNIX) -----------------------------
1944// ADDED 2026-08-21 for /compare/trafficsafety TS1. Until now sys_sendmsg was ABSENT-PROVEN from the
1945// whole tree (corpus_complete=1), so the mechanism nginx, HAProxy and Envoy all use for hitless
1946// replacement -- MOVING the listening descriptor rather than re-binding it -- could not be written
1947// at all. SO_REUSEPORT co-binding is an ACCEPT-DISTRIBUTION primitive, NOT a handoff primitive:
1948// LWN documents that changing the set of listening sockets on a port drops connections during the
1949// three-way handshake, so co-binding proves two binders and can never prove zero drops.
1950//
1951// EVERY OFFSET BELOW IS MEASURED, NOT RECALLED. They were read out of the platform's own headers
1952// with offsetof/sizeof/CMSG_LEN compiled for x86_64:
1953// msghdr 56 = name 0 | namelen 8 (u32) | iov 16 | iovlen 24 | control 32 | controllen 40 | flags 48 (u32)
1954// iovec 16 = base 0 | len 8
1955// cmsghdr 16 = len 0 (u64) | level 8 (u32) | type 12 (u32), data at 16
1956// CMSG_LEN(4)=20 CMSG_SPACE(4)=24 sendmsg=46 recvmsg=47 socketpair=53
1957// AF_UNIX=1 SOL_SOCKET=1 SCM_RIGHTS=1 MSG_CMSG_CLOEXEC=1073741824
1958// A WRONG LAYOUT HERE DOES NOT FAIL LOUD. The syscall still returns a positive byte count and
1959// simply transfers no descriptor, which is why the gate for this proves the property by passing a
1960// REAL descriptor between two REAL processes and then USING it, never by reading a return code.
1961// x86_64 Linux numbers, DELIBERATELY UNGUARDED, and the reason is a measurement rather than a
1962// preference. The first draft of this block wrapped these three in the same
1963// @ifdef TARGET_X86_64 / @ifndef pair every other syscall number in this file uses. On an x86 build
1964// that made every call ENOSYS, and the probe that caught it printed why:
1965// CONSTS SYS_SENDMSG=211 SYS_RECVMSG=212 SYS_SOCKETPAIR=199 SYS_WRITE=64
1966// N sendmsg PLAIN via the CONST rc=-38 (211 is unassigned on x86_64)
1967// N2 sendmsg PLAIN via the LITERAL rc=1
1968// SYS_WRITE reading 64 is the tell and it is NOT MINE: the file's own original guarded block
1969// resolves to its RV64 branch when the constant is referenced, on a build whose sys_write plainly
1970// works. So a constant inside these guards is not reliably the value the guard appears to select.
1971// !! A GUARD THAT SILENTLY SELECTS THE OTHER TARGET'S NUMBER IS WORSE THAN NO GUARD: the call still
1972// compiles, still returns, and dispatches a DIFFERENT SYSCALL. Syscall 199 on x86_64 is
1973// fremovexattr, which is why socketpair appeared to answer EFAULT for every input including a NULL
1974// vector and an unsupported domain -- varying the ARGUMENTS can never reveal that the NUMBER is
1975// wrong, because every variant was equally wrong.
1976// => RV64 support for these three is an OPEN, NAMED requirement, blocked on that toolchain
1977// behaviour. It is left undone and stated rather than papered over with a guard measured not to
1978// work. The estate already keeps nx_syscalls_x86_64.nx as the explicit single-target mirror for
1979// exactly this class of problem.
1980const SYS_SENDMSG: i64 = 46
1981const SYS_RECVMSG: i64 = 47
1982const SYS_SOCKETPAIR: i64 = 53
1983const SCM_AF_UNIX: i64 = 1
1984const SCM_SOL_SOCKET: i64 = 1
1985const SCM_RIGHTS_TYPE: i64 = 1
1986const SCM_MSG_CMSG_CLOEXEC: i64 = 1073741824
1987const SCM_MSGHDR_BYTES: i64 = 56
1988const SCM_MSGHDR_OFF_IOV: i64 = 16
1989const SCM_MSGHDR_OFF_IOVLEN: i64 = 24
1990const SCM_MSGHDR_OFF_CTRL: i64 = 32
1991const SCM_MSGHDR_OFF_CTRLLEN: i64 = 40
1992const SCM_IOVEC_BYTES: i64 = 16
1993const SCM_IOVEC_OFF_BASE: i64 = 0
1994const SCM_IOVEC_OFF_LEN: i64 = 8
1995const SCM_CMSG_OFF_LEN: i64 = 0
1996const SCM_CMSG_OFF_LEVEL: i64 = 8
1997const SCM_CMSG_OFF_TYPE: i64 = 12
1998const SCM_CMSG_OFF_DATA: i64 = 16
1999const SCM_CMSG_LEN_1FD: i64 = 20
2000const SCM_CMSG_SPACE_1FD: i64 = 24
2001const SCM_IOV_COUNT_ONE: i64 = 1
2002const SCM_U32_BYTES: i64 = 4
2003const SCM_BYTE_RADIX: i64 = 256
2004const SCM_FDPAIR_BYTES: i64 = 8
2005// One real data byte travels with the ancillary data ON PURPOSE: a sendmsg carrying SCM_RIGHTS and
2006// NO ordinary payload is the classic silent no-transfer, and it returns 0 rather than an error.
2007const SCM_PAYLOAD_BYTES: i64 = 1
2008const SCM_PAYLOAD_BYTE: i64 = 70
2009// Distinguishable refusals, each naming WHICH conjunct failed -- a compound assertion that will not
2010// name its failing conjunct is a false-alarm generator. All are negative and all sit far outside the
2011// errno range, so no caller can confuse one with a kernel error or with a valid descriptor.
2012const SCM_ERR_NO_CMSG: i64 = 0 - 901
2013const SCM_ERR_CMSG_LEN: i64 = 0 - 902
2014const SCM_ERR_CMSG_LEVEL: i64 = 0 - 903
2015const SCM_ERR_CMSG_TYPE: i64 = 0 - 904
2016
2017func scm_zero(base: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { base[i] = 0; i = i + 1 } return 0 }
2018func scm_put_i64(base: *u8, off: i64, v: i64) -> i64 {
2019 let p: *i64 = ((base as i64) + off) as *i64
2020 p[0] = v
2021 return 0
2022}
2023func scm_get_i64(base: *u8, off: i64) -> i64 {
2024 let p: *i64 = ((base as i64) + off) as *i64
2025 return p[0]
2026}
2027// The two cmsg header fields and the descriptor slot itself are 4-byte ints, so they are packed and
2028// unpacked byte by byte in little-endian order. Radix arithmetic rather than bit shifts, matching
2029// sockaddr_in_init's documented style on this exact path.
2030func scm_put_u32(base: *u8, off: i64, v: i64) -> i64 {
2031 var i: i64 = 0
2032 var m: i64 = v
2033 while i < SCM_U32_BYTES {
2034 base[off + i] = m % SCM_BYTE_RADIX
2035 m = m / SCM_BYTE_RADIX
2036 i = i + 1
2037 }
2038 return 0
2039}
2040func scm_get_u32(base: *u8, off: i64) -> i64 {
2041 var v: i64 = 0
2042 var mult: i64 = 1
2043 var i: i64 = 0
2044 while i < SCM_U32_BYTES {
2045 v = v + (base[off + i] as i64) * mult
2046 mult = mult * SCM_BYTE_RADIX
2047 i = i + 1
2048 }
2049 return v
2050}
2051
2052func sys_sendmsg(fd: i64, msg: *u8, flags: i64) -> i64 {
2053 return __syscall(SYS_SENDMSG, fd, msg, flags, 0, 0, 0)
2054}
2055func sys_recvmsg(fd: i64, msg: *u8, flags: i64) -> i64 {
2056 return __syscall(SYS_RECVMSG, fd, msg, flags, 0, 0, 0)
2057}
2058// socketpair(2). sv receives TWO 4-byte descriptors, so it is a *u8 read with scm_get_u32 -- a
2059// single *i64 read would splice both descriptors into one number and the second would vanish.
2060// !! THIS NUMBER IS NOT REACHING socketpair, AND THE FIRST DIAGNOSIS OF THAT WAS WRONG.
2061// Measured 2026-08-21: every call returns -14 (EFAULT) -- with a valid pointer, with a NULL vector,
2062// and with an UNSUPPORTED DOMAIN alike. The first reading of that evidence was "the host refuses
2063// this call for every input", and it was REFUTED by measuring the emitted constants instead of the
2064// arguments. TARGET_X86_64 is hard-pinned UNDEFINED in this toolchain (see nx_syscalls_x86_64.nx
2065// and nx_tokenizer.nx), so the @ifndef branch is what compiles and the x86 backend TRANSLATES RV64
2066// syscall numbers at emit time. Under that translation 53 is RV64 fchmodat, whose SECOND argument
2067// is a path pointer -- and SOCK_STREAM==1 as a path pointer is exactly EFAULT, every time,
2068// regardless of the other arguments.
2069// * VARYING THE ARGUMENTS CAN NEVER REVEAL THAT THE SYSCALL NUMBER IS WRONG: every variant is
2070// equally wrong, so a set of controls that all agree reads as a confident finding about the host.
2071// The control that actually discriminated was PRINTING THE CONSTANT the binary emits.
2072// => The likely correct value here is the RV64 number 199, exactly as sendmsg/recvmsg above needed
2073// their own numbers rather than the guarded pair. That is NOT asserted: it is UNTESTED, and this
2074// comment says so rather than shipping a plausible number with a confident sentence.
2075// => NOTHING DEPENDS ON IT. The descriptor-passing lane uses a NAMED AF_UNIX rendezvous
2076// (sys_unix_listen + sys_unix_connect_fd below), which is proven end to end by nx_scm_rights_gate
2077// and is also what nginx, HAProxy and systemd actually use to move a listener between processes.
2078// socketpair was only ever the convenience.
2079func sys_socketpair(domain: i64, sock_type: i64, protocol: i64, sv: *u8) -> i64 {
2080 return __syscall(SYS_SOCKETPAIR, domain, sock_type, protocol, sv, 0, 0)
2081}
2082
2083// Bind+listen a NAMED AF_UNIX stream socket -- the accepting half of the rendezvous whose
2084// connecting half is nx_unix_connect. Returns the listening fd, or a negative errno.
2085// The caller owns the path: unlink it first (a stale node makes bind return EADDRINUSE) and unlink
2086// it after, because an AF_UNIX bind leaves a filesystem entry that outlives the process.
2087const SCM_SUN_PATH_OFF: i64 = 2 // sockaddr_un = [sa_family: u16][sun_path: 108]
2088const SCM_SUN_BYTES: i64 = 110
2089const SCM_SUN_PATH_MAX: i64 = 107
2090func sys_unix_listen(path: *u8, backlog: i64) -> i64 {
2091 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0)
2092 if fd < 0 { return fd }
2093 let sa: *u8 = sys_mmap(SCM_SUN_BYTES)
2094 var i: i64 = 0
2095 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 }
2096 sa[0] = SCM_AF_UNIX
2097 sa[1] = 0
2098 var p: i64 = 0
2099 while path[p] != (0 as u8) {
2100 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 }
2101 sa[SCM_SUN_PATH_OFF + p] = path[p]
2102 p = p + 1
2103 }
2104 let br: i64 = sys_bind(fd, sa, SCM_SUN_PATH_OFF + p + 1)
2105 if br < 0 { sys_close(fd); return br }
2106 let lr: i64 = sys_listen(fd, backlog)
2107 if lr < 0 { sys_close(fd); return lr }
2108 return fd
2109}
2110
2111// The CONNECTING half of the same rendezvous. Returns the connected fd or a negative errno.
2112// RESIDUAL NAMED RATHER THAN LEFT SILENT: nx_unix_socket.nx already carries an nx_unix_connect with
2113// this exact body. It is not composed here because that file also defines a main(), so importing it
2114// would inject a second main into every one of the 52 daemons that reach nx_http_server -- a
2115// resolution-by-definition-order hazard this tree has already been bitten by. The primitive belongs
2116// in the shim; the older standalone file should be reduced to a caller of this one, and that is a
2117// separate change to a file with its own consumers rather than something to fold in silently here.
2118func sys_unix_connect_fd(path: *u8) -> i64 {
2119 let fd: i64 = sys_socket(SCM_AF_UNIX, SOCK_STREAM, 0)
2120 if fd < 0 { return fd }
2121 let sa: *u8 = sys_mmap(SCM_SUN_BYTES)
2122 var i: i64 = 0
2123 while i < SCM_SUN_BYTES { sa[i] = 0; i = i + 1 }
2124 sa[0] = SCM_AF_UNIX
2125 sa[1] = 0
2126 var p: i64 = 0
2127 while path[p] != (0 as u8) {
2128 if p >= SCM_SUN_PATH_MAX { sys_close(fd); return 0 - 36 }
2129 sa[SCM_SUN_PATH_OFF + p] = path[p]
2130 p = p + 1
2131 }
2132 let cr: i64 = sys_connect(fd, sa, SCM_SUN_PATH_OFF + p + 1)
2133 if cr < 0 { sys_close(fd); return cr }
2134 return fd
2135}
2136
2137// Send ONE open descriptor over a connected AF_UNIX socket. Returns the sendmsg result: the number
2138// of ordinary data bytes sent (SCM_PAYLOAD_BYTES on success) or a negative errno. The descriptor
2139// itself is NOT closed here -- both ends legitimately hold it until the sender chooses to let go,
2140// and that overlap is the entire point: there must be no instant at which zero processes hold the
2141// listening socket.
2142func sys_send_fd(sock: i64, fd: i64) -> i64 {
2143 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES)
2144 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES)
2145 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD)
2146 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES)
2147 scm_zero(msg, SCM_MSGHDR_BYTES)
2148 scm_zero(cbuf, SCM_CMSG_SPACE_1FD)
2149 data[0] = SCM_PAYLOAD_BYTE
2150 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64)
2151 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES)
2152 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64)
2153 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE)
2154 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64)
2155 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD)
2156 scm_put_i64(cbuf, SCM_CMSG_OFF_LEN, SCM_CMSG_LEN_1FD)
2157 scm_put_u32(cbuf, SCM_CMSG_OFF_LEVEL, SCM_SOL_SOCKET)
2158 scm_put_u32(cbuf, SCM_CMSG_OFF_TYPE, SCM_RIGHTS_TYPE)
2159 scm_put_u32(cbuf, SCM_CMSG_OFF_DATA, fd)
2160 let r: i64 = sys_sendmsg(sock, msg, 0)
2161 sys_munmap(msg, SCM_MSGHDR_BYTES)
2162 sys_munmap(iov, SCM_IOVEC_BYTES)
2163 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD)
2164 sys_munmap(data, SCM_PAYLOAD_BYTES)
2165 return r
2166}
2167
2168// Receive ONE descriptor from a connected AF_UNIX socket. Returns the NEW descriptor number in this
2169// process (>= 0), a negative errno from recvmsg, or one of the SCM_ERR_* codes above.
2170// flags: 0, or SCM_MSG_CMSG_CLOEXEC so the arriving descriptor is not leaked into grandchildren --
2171// the estate has already lost a port for six days to exactly that inheritance (nx_cloexec_gate).
2172// THE VALIDATION IS THE WHOLE POINT. recvmsg happily returns a positive byte count having delivered
2173// no ancillary data at all, so the kernel's REWRITTEN msg_controllen is read back rather than the
2174// value we asked for, and each of the three cmsg header fields is checked separately so a failure
2175// says which one.
2176func sys_recv_fd(sock: i64, flags: i64) -> i64 {
2177 let msg: *u8 = sys_mmap(SCM_MSGHDR_BYTES)
2178 let iov: *u8 = sys_mmap(SCM_IOVEC_BYTES)
2179 let cbuf: *u8 = sys_mmap(SCM_CMSG_SPACE_1FD)
2180 let data: *u8 = sys_mmap(SCM_PAYLOAD_BYTES)
2181 scm_zero(msg, SCM_MSGHDR_BYTES)
2182 scm_zero(cbuf, SCM_CMSG_SPACE_1FD)
2183 scm_put_i64(iov, SCM_IOVEC_OFF_BASE, data as i64)
2184 scm_put_i64(iov, SCM_IOVEC_OFF_LEN, SCM_PAYLOAD_BYTES)
2185 scm_put_i64(msg, SCM_MSGHDR_OFF_IOV, iov as i64)
2186 scm_put_i64(msg, SCM_MSGHDR_OFF_IOVLEN, SCM_IOV_COUNT_ONE)
2187 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRL, cbuf as i64)
2188 scm_put_i64(msg, SCM_MSGHDR_OFF_CTRLLEN, SCM_CMSG_SPACE_1FD)
2189 let r: i64 = sys_recvmsg(sock, msg, flags)
2190 var out: i64 = r
2191 if r >= 0 {
2192 out = SCM_ERR_NO_CMSG
2193 if scm_get_i64(msg, SCM_MSGHDR_OFF_CTRLLEN) >= SCM_CMSG_LEN_1FD {
2194 out = SCM_ERR_CMSG_LEN
2195 if scm_get_i64(cbuf, SCM_CMSG_OFF_LEN) == SCM_CMSG_LEN_1FD {
2196 out = SCM_ERR_CMSG_LEVEL
2197 if scm_get_u32(cbuf, SCM_CMSG_OFF_LEVEL) == SCM_SOL_SOCKET {
2198 out = SCM_ERR_CMSG_TYPE
2199 if scm_get_u32(cbuf, SCM_CMSG_OFF_TYPE) == SCM_RIGHTS_TYPE {
2200 out = scm_get_u32(cbuf, SCM_CMSG_OFF_DATA)
2201 }
2202 }
2203 }
2204 }
2205 }
2206 sys_munmap(msg, SCM_MSGHDR_BYTES)
2207 sys_munmap(iov, SCM_IOVEC_BYTES)
2208 sys_munmap(cbuf, SCM_CMSG_SPACE_1FD)
2209 sys_munmap(data, SCM_PAYLOAD_BYTES)
2210 return out
2211}
2212
2213// Ordinary permission bits only. Special privilege bits are never copied by staging.
2214const NX_FILE_PERMISSION_MASK:i64=511
2215const NX_FILE_DESCRIPTOR_INVALID:i64=0-22
2216func sys_fchmod_fd(fd:i64,mode:i64)->i64{
2217 if fd<0 || mode<0 || mode>NX_FILE_PERMISSION_MASK {return NX_FILE_DESCRIPTOR_INVALID}
2218 return __syscall(52,fd,mode,0,0,0,0)
2219}
2220// Portable descriptor syscall; consumers below use the x86-64 stat ABI layout.
2221const NX_STAT_X64_BYTES:i64=144
2222const NX_STAT_X64_MODE_OFFSET:i64=24
2223const NX_STAT_X64_BLOCK_OFFSET:i64=56
2224const NX_STAT_X64_SIZE_OFFSET:i64=48
2225const NX_STAT_X64_DEVICE_OFFSET:i64=0
2226const NX_STAT_X64_INODE_OFFSET:i64=8
2227const NX_STAT_TYPE_MASK:i64=61440
2228const NX_STAT_REGULAR_FILE:i64=32768
2229func sys_fstat_fd(fd:i64,stat:*u8)->i64{
2230 if fd<0 || (stat as i64)==0{return NX_FILE_DESCRIPTOR_INVALID}
2231 return __syscall(80,fd,stat,0,0,0,0)
2232}
2233func sys_stat_permissions(stat:*u8)->i64{
2234 let mode:i64=(stat[NX_STAT_X64_MODE_OFFSET] as i64)+((stat[NX_STAT_X64_MODE_OFFSET+1] as i64)<<8)
2235 return mode & NX_FILE_PERMISSION_MASK
2236}
2237
2238
2239
2240func nx_quicksort_partition(arr: *nx_int, lo: nx_idx, hi: nx_idx) -> nx_idx {
2241 let pivot: nx_int = arr[hi]
2242 var i: nx_idx = lo
2243 var j: nx_idx = lo
2244 while j < hi {
2245 if arr[j] <= pivot {
2246 let tmp: nx_int = arr[i]
2247 arr[i] = arr[j]
2248 arr[j] = tmp
2249 i = i + 1
2250 }
2251 j = j + 1
2252 }
2253 let tmp2: nx_int = arr[i]
2254 arr[i] = arr[hi]
2255 arr[hi] = tmp2
2256 return i
2257}
2258
2259func nx_quicksort_recur(arr: *nx_int, lo: nx_idx, hi: nx_idx) -> nx_int {
2260 if lo < hi {
2261 let p: nx_idx = nx_quicksort_partition(arr, lo, hi)
2262 if p > 0 { nx_quicksort_recur(arr, lo, p - 1) }
2263 nx_quicksort_recur(arr, p + 1, hi)
2264 }
2265 return 0
2266}
2267
2268// Sort arr[0..n) in place, ascending.
2269func nx_quicksort(arr: *nx_int, n: nx_idx) -> nx_int {
2270 if n > 1 { nx_quicksort_recur(arr, 0, n - 1) }
2271 return 0
2272}
2273
2274// Private candidate: generic surface query composed from canonical NXA attachment validation.
2275// Returns existing face,bary0,bary1,bary2,denominator tuple; no new disk section or material kind.
2276// Deterministic garment-to-body binding, shared by asset preparation and worker conversion.
2277// Native NXA geometry conversion for GPU upload; no platform API or allocator dependency.
2278// nx_nxa.nx -- Provides shared identity and integrity primitives for the NXA (Nishi Animated 3D) format version 1.
2279const NXA_MAGIC_1000003: i64 = 1000003
2280// nx_nxa.nx -- NXA (Nishi Animated 3D) format v1: shared identity + integrity primitives.
2281// The format spec lives in knowledge/nxa_format_spec.md. This organ is the ONLY place the
2282// magic, tags, and checksum are defined -- writer (nx_fbx_measure) and readers (nx_mesh_view)
2283// import it so they can never drift (DRY, rule-15).
2284// license_tier: ORIGINAL
2285
2286const NXA_VER: i64 = 1
2287
2288// magic = the 8 ASCII bytes "NXANIM01" read as one little-endian i64 (no 64-bit hex literal risk)
2289func nxa_magic() -> i64 {
2290 let s: *u8 = "NXANIM01" as *u8
2291 return ((s[0] & 0xff) as i64) | (((s[1] & 0xff) as i64) << 8)
2292 | (((s[2] & 0xff) as i64) << 16) | (((s[3] & 0xff) as i64) << 24)
2293 | (((s[4] & 0xff) as i64) << 32) | (((s[5] & 0xff) as i64) << 40)
2294 | (((s[6] & 0xff) as i64) << 48) | (((s[7] & 0xff) as i64) << 56)
2295}
2296// 4-char section tag ("VERT", "TRIS", ...) as u32
2297func nxa_tag4(s: *u8) -> i64 {
2298 return ((s[0] & 0xff) as i64) | (((s[1] & 0xff) as i64) << 8)
2299 | (((s[2] & 0xff) as i64) << 16) | (((s[3] & 0xff) as i64) << 24)
2300}
2301// order-sensitive rolling checksum over i64 words (seeded so writers can fold split buffers)
2302func nxa_check2(seed: i64, w: *i64, nw: i64) -> i64 {
2303 var c: i64 = seed
2304 var i: i64 = 0
2305 while i < nw { c = c*NXA_MAGIC_1000003 + w[i]; i = i + 1 }
2306 return c
2307}
2308// Validate a section and return its TOC entry's word offset, preserving payload extent metadata.
2309// Returns a nonnegative entry index,
2310// or -1 not-found/not-NXA, -2 future-version (refuse), -3 corrupt (refuse).
2311func nxa_section_entry(b: *u8, flen: i64, tag: i64) -> i64 {
2312 if flen < 32 { return 0 - 1 }
2313 let h: *i64 = b as *i64
2314 if h[0] != nxa_magic() { return 0 - 1 }
2315 if h[1] > NXA_VER { return 0 - 2 }
2316 if h[1] < 1 { return 0 - 3 }
2317 let ns: i64 = h[2]
2318 if ns < 1 { return 0 - 3 }
2319 // The available TOC bytes bound the count before multiplication; v1 declares no 64-section ceiling.
2320 if ns > (flen - 32)/32 { return 0 - 3 }
2321 let tb: *i64 = ((b as i64) + 32) as *i64
2322 if h[3] != nxa_check2(1, tb, ns*4) { return 0 - 3 }
2323 var s: i64 = 0
2324 while s < ns {
2325 if tb[s*4] == tag {
2326 let off: i64 = tb[s*4+1]
2327 let wl: i64 = tb[s*4+2]
2328 if off < 32 + ns*32 { return 0 - 3 }
2329 // Wire offsets are byte offsets, but callers receive an i64-word index.
2330 // Validate alignment and remaining bytes before address/length arithmetic.
2331 if off % 8 != 0 { return 0 - 3 }
2332 if off > flen { return 0 - 3 }
2333 if wl < 0 { return 0 - 3 }
2334 if wl > (flen - off)/8 { return 0 - 3 }
2335 let pw: *i64 = ((b as i64) + off) as *i64
2336 if tb[s*4+3] != nxa_check2(1, pw, wl) { return 0 - 3 }
2337 return 4+s*4
2338 }
2339 s = s + 1
2340 }
2341 return 0 - 1
2342}
2343
2344// Compatibility entry point: payload word offset, with unchanged negative return classes.
2345func nxa_find(b: *u8,flen: i64,tag: i64) -> i64 {
2346 let entry: i64=nxa_section_entry(b,flen,tag)
2347 if entry<0 { return entry }
2348 let h: *i64=b as *i64
2349 return h[entry+1]/8
2350}
2351
2352// Fixed-stride v1 arrays carry [count][count * stride words].
2353// Check exact shape by division before multiplication or any element access.
2354func nxa_counted_section(b: *u8,flen: i64,tag: i64,stride: i64) -> i64 {
2355 if stride<=0 { return 0-3 }
2356 let entry: i64=nxa_section_entry(b,flen,tag)
2357 if entry<0 { return entry }
2358 let h: *i64=b as *i64
2359 let words: i64=h[entry+2]
2360 if words<1 { return 0-3 }
2361 let offset: i64=h[entry+1]/8
2362 let count: i64=h[offset]
2363 if count<0 { return 0-3 }
2364 let remaining: i64=words-1
2365 if remaining%stride!=0 { return 0-3 }
2366 if count!=remaining/stride { return 0-3 }
2367 return offset
2368}
2369
2370
2371const NGP_COMPONENTS: i64 = 3 // VERT xyz and TRIS corners in the NXA v1 schema.
2372const NGP_GPU_WORD: i64 = 4 // WebGPU float32/uint32 wire representation.
2373const NGP_U32_MAX: i64 = 4294967295
2374const NGP_E_OUTPUT: i64 = 0-4
2375const NGP_E_INDEX: i64 = 0-5
2376const NGP_E_OVERLAP: i64 = 0-6
2377
2378const NGP_SKIN_INFLUENCES: i64 = 4 // NXA v1: four joint indices, then four Q12 weights.
2379const NGP_SKIN_WORDS: i64 = NGP_SKIN_INFLUENCES*2
2380const NGP_SKEL_WORDS: i64 = 8 // NXA v1 parent, bind translation xyz, quaternion xyzw.
2381const NGP_WEIGHT_ONE: i64 = 4096 // NXA v1 Q12 weight denominator.
2382const NGP_F32_INTEGER_BITS: i64 = 24 // IEEE-754 binary32 significand precision.
2383const NGP_E_SKIN_COUNT: i64 = 0-7
2384const NGP_E_SKIN_JOINT: i64 = 0-8
2385const NGP_E_SKIN_WEIGHT: i64 = 0-9
2386const NGP_E_TEXC_SHAPE: i64=0-10
2387const NGP_E_TEXC_VALUE: i64=0-11
2388const NGP_F32_ABS_MASK: i64=2147483647
2389const NGP_F32_SIGN_MASK: i64=2147483648
2390
2391func ngp_reason(rc: i64) -> *u8 {
2392 if rc==0-1 { return "NXA identity or required section absent" as *u8 }
2393 if rc==0-2 { return "NXA version newer than this reader" as *u8 }
2394 if rc==0-3 { return "NXA checksum, extent or element shape invalid" as *u8 }
2395 if rc==NGP_E_OUTPUT { return "output extent insufficient; allocate the reported geometry byte requirement" as *u8 }
2396 if rc==NGP_E_INDEX { return "triangle index outside vertex range or uint32 representation" as *u8 }
2397 if rc==NGP_E_OVERLAP { return "output overlaps immutable source; provide a distinct upload buffer" as *u8 }
2398 if rc==NGP_E_SKIN_COUNT { return "SKIN vertex count differs from VERT; repair the source binding" as *u8 }
2399 if rc==NGP_E_SKIN_JOINT { return "SKIN joint is outside SKEL or exact GPU index representation" as *u8 }
2400 if rc==NGP_E_SKIN_WEIGHT { return "SKIN Q12 weights are out of range or do not sum to one" as *u8 }
2401 if rc==NGP_E_TEXC_SHAPE { return "TEXC vertex count, stride or extent differs from the source geometry" as *u8 }
2402 if rc==NGP_E_TEXC_VALUE { return "TEXC UV or owning joint is outside the NXA v1 atlas representation" as *u8 }
2403 return "asset buffers packed" as *u8
2404}
2405func ngp_section(b: *u8,n: i64,tag: *u8) -> i64 {
2406 return nxa_counted_section(b,n,nxa_tag4(tag),NGP_COMPONENTS)
2407}
2408func ngp_bytes(b: *u8,n: i64,tag: *u8) -> i64 {
2409 let at: i64=ngp_section(b,n,tag)
2410 if at<0 { return at }
2411 let h: *i64=b as *i64
2412 // Validated i64 payload fits n; its float32/uint32 representation is half its size.
2413 return h[at]*NGP_COMPONENTS*NGP_GPU_WORD
2414}
2415func ngp_output(b: *u8,n: i64,out: *u8,cap: i64,need: i64) -> i64 {
2416 if cap<need { return NGP_E_OUTPUT }
2417 if need==0 { return 0 }
2418 if (out as i64)<=0 { return NGP_E_OUTPUT }
2419 let src: i64=b as i64
2420 let dst: i64=out as i64
2421 if dst>=src {
2422 if dst-src<n { return NGP_E_OVERLAP }
2423 } else {
2424 if src-dst<need { return NGP_E_OVERLAP }
2425 }
2426 return 0
2427}
2428func ngp_vertices(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
2429 let at: i64=ngp_section(b,n,"VERT" as *u8)
2430 if at<0 { return at }
2431 let h: *i64=b as *i64
2432 let count: i64=h[at]*NGP_COMPONENTS
2433 let need: i64=count*NGP_GPU_WORD
2434 let valid: i64=ngp_output(b,n,out,cap,need)
2435 if valid<0 { return valid }
2436 let dst: *u32=out as *u32
2437 var i: i64=0
2438 while i<count { dst[i]=__f32_from_i64(h[at+1+i]) as u32; i=i+1 }
2439 return need
2440}
2441func ngp_indices(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
2442 let va: i64=ngp_section(b,n,"VERT" as *u8)
2443 if va<0 { return va }
2444 let ta: i64=ngp_section(b,n,"TRIS" as *u8)
2445 if ta<0 { return ta }
2446 let h: *i64=b as *i64
2447 let nv: i64=h[va]
2448 let count: i64=h[ta]*NGP_COMPONENTS
2449 let need: i64=count*NGP_GPU_WORD
2450 let valid: i64=ngp_output(b,n,out,cap,need)
2451 if valid<0 { return valid }
2452 // Validate every index before modifying the destination, including late invalid elements.
2453 var i: i64=0
2454 while i<count {
2455 let v: i64=h[ta+1+i]
2456 if v<0 { return NGP_E_INDEX }
2457 if v>=nv { return NGP_E_INDEX }
2458 if v>NGP_U32_MAX { return NGP_E_INDEX }
2459 i=i+1
2460 }
2461 let dst: *u32=out as *u32
2462 i=0
2463 while i<count { dst[i]=h[ta+1+i] as u32; i=i+1 }
2464 return need
2465}
2466
2467// Validate source-to-skeleton binding before any derived GPU allocation or write.
2468func ngp_skin_section(b: *u8,n: i64) -> i64 {
2469 let va: i64=ngp_section(b,n,"VERT" as *u8)
2470 if va<0 { return va }
2471 let sa: i64=nxa_counted_section(b,n,nxa_tag4("SKEL" as *u8),NGP_SKEL_WORDS)
2472 if sa<0 { return sa }
2473 let ka: i64=nxa_counted_section(b,n,nxa_tag4("SKIN" as *u8),NGP_SKIN_WORDS)
2474 if ka<0 { return ka }
2475 let h: *i64=b as *i64
2476 if h[ka]!=h[va] { return NGP_E_SKIN_COUNT }
2477 let count: i64=h[ka]
2478 let joints: i64=h[sa]
2479 var v: i64=0
2480 while v<count {
2481 let at: i64=ka+1+v*NGP_SKIN_WORDS
2482 var sum: i64=0
2483 var k: i64=0
2484 while k<NGP_SKIN_INFLUENCES {
2485 let joint: i64=h[at+k]
2486 if joint<0 { return NGP_E_SKIN_JOINT }
2487 if joint>=joints { return NGP_E_SKIN_JOINT }
2488 if joint>(1<<NGP_F32_INTEGER_BITS) { return NGP_E_SKIN_JOINT }
2489 let weight: i64=h[at+NGP_SKIN_INFLUENCES+k]
2490 if weight<0 { return NGP_E_SKIN_WEIGHT }
2491 if weight>NGP_WEIGHT_ONE { return NGP_E_SKIN_WEIGHT }
2492 sum=sum+weight
2493 k=k+1
2494 }
2495 if sum!=NGP_WEIGHT_ONE { return NGP_E_SKIN_WEIGHT }
2496 v=v+1
2497 }
2498 return ka
2499}
2500func ngp_skin_bytes(b: *u8,n: i64) -> i64 {
2501 let at: i64=ngp_skin_section(b,n)
2502 if at<0 { return at }
2503 let h: *i64=b as *i64
2504 return h[at]*NGP_SKIN_WORDS*NGP_GPU_WORD
2505}
2506// Interleaved float32x4 joints + float32x4 weights; offsets/stride derive from the v1 schema.
2507// Q12 weights convert exactly to binary32; never silently normalize malformed source weights.
2508func ngp_skin(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
2509 let at: i64=ngp_skin_section(b,n)
2510 if at<0 { return at }
2511 let h: *i64=b as *i64
2512 let count: i64=h[at]
2513 let need: i64=count*NGP_SKIN_WORDS*NGP_GPU_WORD
2514 let valid: i64=ngp_output(b,n,out,cap,need)
2515 if valid<0 { return valid }
2516 let dst: *u32=out as *u32
2517 let denominator: i64=__f32_from_i64(NGP_WEIGHT_ONE)
2518 var v: i64=0
2519 while v<count {
2520 let src: i64=at+1+v*NGP_SKIN_WORDS
2521 let target: i64=v*NGP_SKIN_WORDS
2522 var k: i64=0
2523 while k<NGP_SKIN_INFLUENCES {
2524 dst[target+k]=__f32_from_i64(h[src+k]) as u32
2525 dst[target+NGP_SKIN_INFLUENCES+k]=__f32_div(__f32_from_i64(h[src+NGP_SKIN_INFLUENCES+k]),denominator) as u32
2526 k=k+1
2527 }
2528 v=v+1
2529 }
2530 return need
2531}
2532
2533const NGP_TEXC_HEADER: i64=4 // NXA TEXC nv, stride, atlas grid, optional region table offset.
2534const NGP_TEXC_STRIDE: i64=3 // u Q16, v Q16, owning joint.
2535const NGP_TEXC_UV: i64=2
2536const NGP_TEXC_ONE: i64=65536
2537
2538// Read UVs from the source atlas; optional region metadata remains in the immutable NXA.
2539func ngp_texcoord_section(b: *u8,n: i64) -> i64 {
2540 let va: i64=ngp_section(b,n,"VERT" as *u8)
2541 if va<0 { return va }
2542 let sk: i64=nxa_counted_section(b,n,nxa_tag4("SKEL" as *u8),NGP_SKEL_WORDS)
2543 if sk<0 { return sk }
2544 let entry: i64=nxa_section_entry(b,n,nxa_tag4("TEXC" as *u8))
2545 if entry<0 { return entry }
2546 let h: *i64=b as *i64
2547 let words: i64=h[entry+2]
2548 if words<NGP_TEXC_HEADER { return NGP_E_TEXC_SHAPE }
2549 let at: i64=h[entry+1]/8
2550 let count: i64=h[at]
2551 if count!=h[va] || h[at+1]!=NGP_TEXC_STRIDE { return NGP_E_TEXC_SHAPE }
2552 if count>(words-NGP_TEXC_HEADER)/NGP_TEXC_STRIDE { return NGP_E_TEXC_SHAPE }
2553 var v: i64=0
2554 while v<count {
2555 let row: i64=at+NGP_TEXC_HEADER+v*NGP_TEXC_STRIDE
2556 var k: i64=0
2557 while k<NGP_TEXC_UV {
2558 if h[row+k]<0 || h[row+k]>=NGP_TEXC_ONE { return NGP_E_TEXC_VALUE }
2559 k=k+1
2560 }
2561 if h[row+NGP_TEXC_UV]<0 || h[row+NGP_TEXC_UV]>=h[sk] { return NGP_E_TEXC_VALUE }
2562 v=v+1
2563 }
2564 return at
2565}
2566func ngp_texcoord_bytes(b: *u8,n: i64) -> i64 {
2567 let at: i64=ngp_texcoord_section(b,n)
2568 if at<0 { return at }
2569 let h: *i64=b as *i64
2570 return h[at]*NGP_TEXC_UV*NGP_GPU_WORD
2571}
2572func ngp_texcoords(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
2573 let at: i64=ngp_texcoord_section(b,n)
2574 if at<0 { return at }
2575 let h: *i64=b as *i64
2576 let count: i64=h[at]
2577 let need: i64=count*NGP_TEXC_UV*NGP_GPU_WORD
2578 let valid: i64=ngp_output(b,n,out,cap,need)
2579 if valid<0 { return valid }
2580 let dst: *u32=out as *u32
2581 let denominator: i64=__f32_from_i64(NGP_TEXC_ONE)
2582 var v: i64=0
2583 while v<count {
2584 var k: i64=0
2585 while k<NGP_TEXC_UV {
2586 let value: i64=h[at+NGP_TEXC_HEADER+v*NGP_TEXC_STRIDE+k]
2587 dst[v*NGP_TEXC_UV+k]=__f32_div(__f32_from_i64(value),denominator) as u32
2588 k=k+1
2589 }
2590 v=v+1
2591 }
2592 return need
2593}
2594
2595// Normal sums are scaled before squaring, so length squared is in [1,3].
2596// Newton refinement stops on identical binary32 bits; precision bounds the iterations.
2597func ngp_normal_length(square: i64) -> i64 {
2598 var root: i64=__f32_from_i64(1)
2599 let two: i64=__f32_from_i64(2)
2600 var i: i64=0
2601 while i<NGP_F32_INTEGER_BITS {
2602 let next: i64=__f32_div(__f32_add(root,__f32_div(square,root)),two)
2603 if next==root { return root }
2604 root=next;i=i+1
2605 }
2606 return root
2607}
2608func ngp_normal_edge(h: *i64,va: i64,a: i64,b: i64,k: i64,scale: i64) -> i64 {
2609 let av: i64=__f32_div(__f32_from_i64(h[va+1+a*NGP_COMPONENTS+k]),scale)
2610 let bv: i64=__f32_div(__f32_from_i64(h[va+1+b*NGP_COMPONENTS+k]),scale)
2611 return ngp_float_difference(bv,av)
2612}
2613func ngp_float_difference(a: i64,b: i64) -> i64 { return __f32_add(a,b^NGP_F32_SIGN_MASK) }
2614// Area-weighted normals for a mesh lacking authored normals. Never weld source vertices:
2615// duplicated seam/hard-edge vertices remain independent. Degenerate sums remain zero.
2616func ngp_normals(b: *u8,n: i64,out: *u8,cap: i64) -> i64 {
2617 let va: i64=ngp_section(b,n,"VERT" as *u8)
2618 if va<0 { return va }
2619 let ta: i64=ngp_section(b,n,"TRIS" as *u8)
2620 if ta<0 { return ta }
2621 let h: *i64=b as *i64
2622 let nv: i64=h[va]
2623 let count: i64=h[ta]*NGP_COMPONENTS
2624 let need: i64=nv*NGP_COMPONENTS*NGP_GPU_WORD
2625 let valid: i64=ngp_output(b,n,out,cap,need)
2626 if valid<0 { return valid }
2627 var i: i64=0
2628 while i<count {
2629 let index: i64=h[ta+1+i]
2630 if index<0 || index>=nv || index>NGP_U32_MAX { return NGP_E_INDEX }
2631 i=i+1
2632 }
2633 // One common scale preserves relative face areas while keeping cross products finite.
2634 var scale: i64=__f32_from_i64(1)
2635 i=0
2636 while i<nv*NGP_COMPONENTS {
2637 let magnitude: i64=__f32_from_i64(h[va+1+i])&NGP_F32_ABS_MASK
2638 if magnitude>scale { scale=magnitude }
2639 i=i+1
2640 }
2641 let dst: *u32=out as *u32
2642 i=0
2643 while i<nv*NGP_COMPONENTS { dst[i]=0 as u32;i=i+1 }
2644 i=0
2645 while i<count {
2646 let a: i64=h[ta+1+i];let c: i64=h[ta+2+i];let d: i64=h[ta+3+i]
2647 var k: i64=0
2648 while k<NGP_COMPONENTS {
2649 let u: i64=(k+1)%NGP_COMPONENTS;let v: i64=(k+2)%NGP_COMPONENTS
2650 let face: i64=ngp_float_difference(__f32_mul(ngp_normal_edge(h,va,a,c,u,scale),ngp_normal_edge(h,va,a,d,v,scale)),__f32_mul(ngp_normal_edge(h,va,a,c,v,scale),ngp_normal_edge(h,va,a,d,u,scale)))
2651 var corner: i64=0
2652 while corner<NGP_COMPONENTS {
2653 let at: i64=h[ta+1+i+corner]*NGP_COMPONENTS+k
2654 dst[at]=__f32_add(dst[at] as i64,face) as u32
2655 corner=corner+1
2656 }
2657 k=k+1
2658 }
2659 i=i+NGP_COMPONENTS
2660 }
2661 i=0
2662 while i<nv {
2663 var largest: i64=0;var k: i64=0
2664 while k<NGP_COMPONENTS {
2665 let magnitude: i64=(dst[i*NGP_COMPONENTS+k] as i64)&NGP_F32_ABS_MASK
2666 if magnitude>largest { largest=magnitude }
2667 k=k+1
2668 }
2669 if largest>0 {
2670 var square: i64=0;k=0
2671 while k<NGP_COMPONENTS {
2672 let at: i64=i*NGP_COMPONENTS+k
2673 let value: i64=__f32_div(dst[at] as i64,largest)
2674 dst[at]=value as u32;square=__f32_add(square,__f32_mul(value,value));k=k+1
2675 }
2676 let length: i64=ngp_normal_length(square);k=0
2677 while k<NGP_COMPONENTS {
2678 let at: i64=i*NGP_COMPONENTS+k
2679 dst[at]=__f32_div(dst[at] as i64,length) as u32;k=k+1
2680 }
2681 }
2682 i=i+1
2683 }
2684 return need
2685}
2686
2687
2688const NGB_WORD: i64=8
2689const NGB_COORD_LIMIT: i64=1<<NGP_F32_INTEGER_BITS // Exact integer positions in the incumbent float32 upload.
2690const NGB_E_POLICY: i64=0-20
2691const NGB_E_COORD: i64=0-21
2692const NGB_E_CANDIDATE: i64=0-22
2693
2694func ngb_coordinates(h: *i64,at: i64) -> i64 {
2695 var i: i64=0
2696 while i<h[at]*NGP_COMPONENTS {
2697 let v: i64=h[at+1+i]
2698 if v<0-NGB_COORD_LIMIT || v>NGB_COORD_LIMIT { return NGB_E_COORD }
2699 i=i+1
2700 }
2701 return 0
2702}
2703// step and lower_band are caller-owned binding policy, never hidden asset-specific constants.
2704// Below lower_band, queries bind only above that band; otherwise the candidate floor is zero.
2705// Coordinates are exact float32 integers and squared distances fit exact binary64 integers.
2706// Ties select the lowest eligible source index. No candidate is an error, never vertex-zero fallback.
2707func ngb_map(b: *u8,n: i64,out: *u8,cap: i64,step: i64,lower_band: i64) -> i64 {
2708 if step<=0 || lower_band<0 || lower_band>NGB_COORD_LIMIT { return NGB_E_POLICY }
2709 let va: i64=ngp_section(b,n,"VERT" as *u8)
2710 if va<0 { return va }
2711 let ga: i64=ngp_section(b,n,"GVRT" as *u8)
2712 if ga<0 { return ga }
2713 let h: *i64=b as *i64
2714 let nv: i64=h[va];let ng: i64=h[ga]
2715 let need: i64=ng*NGB_WORD
2716 let valid: i64=ngp_output(b,n,out,cap,need)
2717 if valid<0 { return valid }
2718 let vc: i64=ngb_coordinates(h,va)
2719 if vc<0 { return vc }
2720 let gc: i64=ngb_coordinates(h,ga)
2721 if gc<0 { return gc }
2722 var above_zero: i64=0;var above_band: i64=0
2723 var u: i64=0
2724 while u<nv {
2725 let z: i64=h[va+1+u*NGP_COMPONENTS+2]
2726 if z>=0 { above_zero=1 }
2727 if z>=lower_band { above_band=1 }
2728 // Subtraction prevents an arbitrary caller stride overflowing the loop index.
2729 if step>=nv-u { u=nv } else { u=u+step }
2730 }
2731 var v: i64=0
2732 while v<ng {
2733 let z: i64=h[ga+1+v*NGP_COMPONENTS+2]
2734 if z<lower_band { if above_band==0 { return NGB_E_CANDIDATE } }
2735 else { if above_zero==0 { return NGB_E_CANDIDATE } }
2736 v=v+1
2737 }
2738 // Every refusal precedes the first destination write.
2739 let dst: *i64=out as *i64
2740 v=0
2741 while v<ng {
2742 let query: i64=ga+1+v*NGP_COMPONENTS
2743 var floor: i64=0
2744 if h[query+2]<lower_band { floor=lower_band }
2745 var best: i64=0-1;var distance: i64=0
2746 u=0
2747 while u<nv {
2748 let candidate: i64=va+1+u*NGP_COMPONENTS
2749 if h[candidate+2]>=floor {
2750 let dx: i64=h[candidate]-h[query]
2751 let dy: i64=h[candidate+1]-h[query+1]
2752 let dz: i64=h[candidate+2]-h[query+2]
2753 let d: i64=dx*dx+dy*dy+dz*dz
2754 if best<0 || d<distance { best=u;distance=d }
2755 }
2756 if step>=nv-u { u=nv } else { u=u+step }
2757 }
2758 dst[v]=best;v=v+1
2759 }
2760 return need
2761}
2762
2763const NGB_CLIP_E_INPUT:i64=-30
2764const NGB_CLIP_E_RANGE:i64=-32
2765const NGB_CLIP_I64_MAX:i64=9223372036854775807
2766func ngb_abs(x:i64)->i64{if x<0{return 0-x};return x}
2767func ngb_clip_product(a:i64,b:i64)->i64 {
2768 if a<0||b<0{return -1};if b>0{if a>NGB_CLIP_I64_MAX/b{return -1}};return a*b
2769}
2770func ngb_clip_gcd(a:i64,b:i64)->i64 {var x:i64=a;var y:i64=b;while y>0{let z:i64=x%y;x=y;y=z};return x}
2771
2772// Twice polygon area in the canonical source triangle's (bary1,bary2) plane.
2773// Exact numerator/denominator; positive winding and <=1 means no inverted/expanded source coverage.
2774// Input records are already validated by the clipping boundary. Large common denominators refuse.
2775func ngb_clip_area_ratio_v1(poly:*i64,n:i64,result:*i64)->i64{
2776 if n<3||n>7{return NGB_CLIP_E_INPUT};var den:i64=1;var i:i64=0
2777 while i<n{if poly[i*4+3]<=0{return NGB_CLIP_E_INPUT};den=ngb_clip_product(den,poly[i*4+3]);if den<0{return NGB_CLIP_E_RANGE};i=i+1}
2778 var sum:i64=0;i=0
2779 while i<n{let j:i64=(i+1)%n;let a:i64=ngb_clip_product(poly[i*4+1],poly[j*4+2]);let b:i64=ngb_clip_product(poly[i*4+2],poly[j*4+1]);if a<0||b<0{return NGB_CLIP_E_RANGE}
2780 let pair:i64=ngb_clip_product(poly[i*4+3],poly[j*4+3]);if pair<=0{return NGB_CLIP_E_RANGE}
2781 let term:i64=ngb_clip_product(ngb_abs(a-b),den/pair);if term<0{return NGB_CLIP_E_RANGE}
2782 if a>=b{if sum>NGB_CLIP_I64_MAX-term{return NGB_CLIP_E_RANGE};sum=sum+term}
2783 else{if sum<0-NGB_CLIP_I64_MAX+term{return NGB_CLIP_E_RANGE};sum=sum-term};i=i+1
2784 }
2785 let div:i64=ngb_clip_gcd(ngb_abs(sum),den);result[0]=sum/div;result[1]=den/div;return 0
2786}
2787
2788// GAT1 v1: additive source-surface attachments; legacy GARM is unchanged.
2789// Header 12 words: version,record_count,triangle_count,record_stride,VERT_check,TRIS_check,
2790// source_nv,source_nt,kind,total_words,triangle_stride,reserved.
2791// Record 8 words: source_face,bary0,bary1,bary2,denominator,normal_offset_units,material,flags.
2792// Triangle 3 words: attachment record indices. Coefficients remain exact i64 on disk.
2793const NGB_ATTACH_HDR:i64=12
2794const NGB_ATTACH_REC:i64=8
2795const NGB_ATTACH_COORD_MAX:i64=16777216
2796const NGB_ATTACH_E_SOURCE:i64=-40
2797const NGB_ATTACH_E_SHAPE:i64=-41
2798const NGB_ATTACH_E_GEOMETRY:i64=-42
2799// Exact integer coordinates and products of differences fit i64 under this boundary.
2800func ngb_attach_source_v1(b:*u8,n:i64)->i64{
2801 if (b as i64)==0{return NGB_ATTACH_E_SOURCE}
2802 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
2803 if va<0||ta<0{return NGB_ATTACH_E_SOURCE};let h:*i64=b as *i64;let nv:i64=h[va];let nt:i64=h[ta]
2804 if nv<3||nt<1{return NGB_ATTACH_E_SOURCE};var i:i64=0
2805 while i<nv*3{let p:i64=h[va+1+i];if p<0-NGB_ATTACH_COORD_MAX||p>NGB_ATTACH_COORD_MAX{return NGB_ATTACH_E_SOURCE};i=i+1}
2806 i=0;while i<nt*3{let v:i64=h[ta+1+i];if v<0||v>=nv{return NGB_ATTACH_E_SOURCE};i=i+1};return 0
2807}
2808func ngb_attach_face_v1(pos:*i64,idx:*i64,face:i64)->i64{
2809 let a:i64=idx[face*3]*3;let b:i64=idx[face*3+1]*3;let c:i64=idx[face*3+2]*3
2810 let x:i64=pos[b]-pos[a];let y:i64=pos[b+1]-pos[a+1];let z:i64=pos[b+2]-pos[a+2]
2811 let u:i64=pos[c]-pos[a];let v:i64=pos[c+1]-pos[a+1];let w:i64=pos[c+2]-pos[a+2]
2812 if y*w-z*v==0&&z*u-x*w==0&&x*v-y*u==0{return NGB_ATTACH_E_GEOMETRY};return 0
2813}
2814// Validate before consumer upload. Native NXA checks are integrity checks, not cryptographic authentication.
2815// Caller workspace: 14 i64 words, disjoint from section/source; no output mesh or device mutation here.
2816func ngb_attach_validate_v1(b:*u8,n:i64,s:*i64,nw:i64,work:*i64,work_words:i64)->i64{
2817 if (s as i64)==0||(work as i64)==0||nw<NGB_ATTACH_HDR||work_words<14{return NGB_ATTACH_E_SHAPE}
2818 let sr:i64=ngb_attach_source_v1(b,n);if sr<0{return sr}
2819 if s[0]!=1||s[3]!=NGB_ATTACH_REC||s[10]!=3||s[11]!=0||s[9]!=nw{return NGB_ATTACH_E_SHAPE}
2820 let nr:i64=s[1];let nt:i64=s[2];if nr<3||nt<1||nr>(nw-NGB_ATTACH_HDR)/NGB_ATTACH_REC{return NGB_ATTACH_E_SHAPE}
2821 let to:i64=NGB_ATTACH_HDR+nr*NGB_ATTACH_REC;if (nw-to)%3!=0||nt!=(nw-to)/3{return NGB_ATTACH_E_SHAPE}
2822 if s[8]<1||s[8]>9{return NGB_ATTACH_E_SHAPE}
2823 let ve:i64=nxa_section_entry(b,n,nxa_tag4("VERT"));let te:i64=nxa_section_entry(b,n,nxa_tag4("TRIS"));let h:*i64=b as *i64
2824 let va:i64=h[ve+1]/8;let ta:i64=h[te+1]/8
2825 if s[4]!=h[ve+3]||s[5]!=h[te+3]||s[6]!=h[va]||s[7]!=h[ta]{return NGB_ATTACH_E_SOURCE}
2826 let pos:*i64=((b as i64)+(va+1)*8) as *i64;let idx:*i64=((b as i64)+(ta+1)*8) as *i64
2827 var i:i64=0;while i<nr{let r:i64=NGB_ATTACH_HDR+i*NGB_ATTACH_REC;let face:i64=s[r];let den:i64=s[r+4]
2828 if face<0||face>=s[7]||den<=0{return NGB_ATTACH_E_SHAPE}
2829 var sum:i64=0;var k:i64=1;while k<4{let a:i64=s[r+k];if a<0||a>den-sum{return NGB_ATTACH_E_SHAPE};sum=sum+a;k=k+1}
2830 if sum!=den||s[r+5]<0||s[r+5]>NGB_ATTACH_COORD_MAX||s[r+6]!=s[8]||s[r+7]!=0{return NGB_ATTACH_E_SHAPE}
2831 if ngb_attach_face_v1(pos,idx,face)<0{return NGB_ATTACH_E_GEOMETRY};i=i+1
2832 }
2833 i=0;while i<nt{var face:i64=-1;var k:i64=0
2834 while k<3{let v:i64=s[to+i*3+k];if v<0||v>=nr{return NGB_ATTACH_E_SHAPE};let r:i64=NGB_ATTACH_HDR+v*NGB_ATTACH_REC
2835 if k==0{face=s[r]}else{if face!=s[r]{return NGB_ATTACH_E_GEOMETRY}}
2836 var j:i64=0;while j<4{work[k*4+j]=s[r+1+j];j=j+1};k=k+1
2837 }
2838 let area:*i64=((work as i64)+96) as *i64;let ar:i64=ngb_clip_area_ratio_v1(work,3,area)
2839 if ar<0{return ar};if area[0]<=0||area[0]>area[1]{return NGB_ATTACH_E_GEOMETRY};i=i+1
2840 };return 0
2841}
2842
2843// GAP1 upload v1: 16 uint32 header lanes, then position xyz, source-index xyzw,
2844// barycentric xyz/source-unit normal-offset w and uint32 triangle indices.
2845// Disk GAT1 remains exact i64. GPU conversion occurs only after complete native validation.
2846const NGB_PACK_HEADER:i64=64
2847const NGB_PACK_VERTEX:i64=44
2848const NGB_E_PACK_INDEX:i64=-43
2849const NGB_E_PACK_EXTENT:i64=-44
2850// This is an integer-only exactness test; it does not round before checking.
2851func ngb_source_index_f32_v1(v:i64,nv:i64)->i64{
2852 if v<0||v>=nv||v>2147483647{return NGB_E_PACK_INDEX}
2853 var top:i64=16777216;var spacing:i64=1
2854 while v>=top*2{top=top*2;spacing=spacing*2}
2855 if v>top{spacing=spacing*2}
2856 if v%spacing!=0{return NGB_E_PACK_INDEX};return 0
2857}
2858func ngb_attachment_bytes_v1(b:*u8,n:i64,work:*i64,work_bytes:i64)->i64{
2859 if (b as i64)==0||work_bytes<112{return NGB_ATTACH_E_SHAPE}
2860 let wr:i64=ngp_output(b,n,work as *u8,work_bytes,112);if wr<0{return wr}
2861 let ae:i64=nxa_section_entry(b,n,nxa_tag4("GAT1"));if ae<0{return ae}
2862 let h:*i64=b as *i64;var vi:i64=0;var ti:i64=0;var ai:i64=0;var entry:i64=0
2863 while entry<h[2]{let tag:i64=h[4+entry*4];if tag==nxa_tag4("VERT"){vi=vi+1};if tag==nxa_tag4("TRIS"){ti=ti+1};if tag==nxa_tag4("GAT1"){ai=ai+1};entry=entry+1}
2864 if vi!=1||ti!=1||ai!=1{return NGB_ATTACH_E_SHAPE}
2865 let s:*i64=((b as i64)+h[ae+1]) as *i64
2866 let rc:i64=ngb_attach_validate_v1(b,n,s,h[ae+2],work,14);if rc<0{return rc}
2867 let nr:i64=s[1];let nt:i64=s[2]
2868 if nr>(4294967295-NGB_PACK_HEADER)/NGB_PACK_VERTEX{return NGB_E_PACK_EXTENT}
2869 let base:i64=NGB_PACK_HEADER+nr*NGB_PACK_VERTEX
2870 if nt>(4294967295-base)/12{return NGB_E_PACK_EXTENT}
2871 let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
2872 var i:i64=0;while i<nr{let face:i64=s[NGB_ATTACH_HDR+i*NGB_ATTACH_REC];var k:i64=0
2873 while k<3{let si:i64=h[ta+1+face*3+k];if ngb_source_index_f32_v1(si,s[6])<0{return NGB_E_PACK_INDEX};k=k+1};i=i+1}
2874 return base+nt*12
2875}
2876func ngb_attachment_pack_v1(b:*u8,n:i64,out:*u8,cap:i64,work:*i64,work_bytes:i64)->i64{
2877 if cap>0&&(out as i64)>0{let early:i64=ngp_output(work as *u8,112,out,cap,cap);if early<0{return early}}
2878 let need:i64=ngb_attachment_bytes_v1(b,n,work,work_bytes);if need<0{return need}
2879 let rc:i64=ngp_output(b,n,out,cap,need);if rc<0{return rc}
2880 let overlap:i64=ngp_output(work as *u8,112,out,cap,need);if overlap<0{return overlap}
2881 let ae:i64=nxa_section_entry(b,n,nxa_tag4("GAT1"));let h:*i64=b as *i64;let s:*i64=((b as i64)+h[ae+1]) as *i64
2882 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3)
2883 let nr:i64=s[1];let nt:i64=s[2];let po:i64=64;let jo:i64=po+nr*12;let wo:i64=jo+nr*16;let io:i64=wo+nr*16
2884 let dst:*u32=out as *u32;var i:i64=0;while i<16{dst[i]=0 as u32;i=i+1}
2885 dst[0]=827343175 as u32;dst[1]=1 as u32;dst[2]=nr as u32;dst[3]=nt as u32;dst[4]=s[6] as u32;dst[5]=s[8] as u32
2886 dst[6]=po as u32;dst[7]=jo as u32;dst[8]=wo as u32;dst[9]=io as u32;dst[10]=need as u32;dst[11]=1 as u32
2887 i=0;while i<nr{let r:i64=NGB_ATTACH_HDR+i*NGB_ATTACH_REC;let face:i64=s[r];let den:i64=__f32_from_i64(s[r+4]);var k:i64=0
2888 while k<3{let si:i64=h[ta+1+face*3+k];dst[jo/4+i*4+k]=__f32_from_i64(si) as u32;dst[wo/4+i*4+k]=__f32_div(__f32_from_i64(s[r+1+k]),den) as u32;k=k+1}
2889 dst[jo/4+i*4+3]=0 as u32;dst[wo/4+i*4+3]=__f32_from_i64(s[r+5]) as u32
2890 var q:i64=0;while q<3{var acc:i64=__f32_from_i64(0);k=0
2891 while k<3{let si:i64=h[ta+1+face*3+k];let ratio:i64=dst[wo/4+i*4+k] as i64;acc=__f32_add(acc,__f32_mul(__f32_from_i64(h[va+1+si*3+q]),ratio));k=k+1}
2892 dst[po/4+i*3+q]=acc as u32;q=q+1};i=i+1}
2893 let to:i64=NGB_ATTACH_HDR+nr*NGB_ATTACH_REC;i=0;while i<nt*3{dst[io/4+i]=s[to+i] as u32;i=i+1};return need
2894}
2895
2896const NSA_E_INPUT:i64=-50
2897const NSA_E_RANGE:i64=-51
2898const NSA_E_MISS:i64=-52
2899const NSA_E_AMBIGUOUS:i64=-53
2900// Exact fraction comparison without cross-products. Inputs nonnegative, denominators positive.
2901func nsa_unsigned_ratio_cmp(a:i64,b:i64,c:i64,d:i64)->i64{
2902 var x:i64=a;var y:i64=b;var z:i64=c;var w:i64=d;var sign:i64=1
2903 while true{let p:i64=x/y;let q:i64=z/w;if p<q{return 0-sign};if p>q{return sign}
2904 let r:i64=x%y;let s:i64=z%w;if r==0{if s==0{return 0};return 0-sign};if s==0{return sign}
2905 x=y;y=r;z=w;w=s;sign=0-sign
2906 };return 0
2907}
2908func nsa_ratio_cmp(a:i64,b:i64,c:i64,d:i64)->i64{
2909 if a<0{if c>=0{return -1};return 0-nsa_unsigned_ratio_cmp(0-a,b,0-c,d)}
2910 if c<0{return 1};return nsa_unsigned_ratio_cmp(a,b,c,d)
2911}
2912// Coordinate bounds inherited from canonical source validation make 2D determinants exact i64.
2913// y numerator is checked independently, since determinant times depth can exceed i64.
2914func nsa_triangle_y_v1(p:*i64,idx:*i64,face:i64,qx:i64,qz:i64,result:*i64)->i64{
2915 let a:i64=idx[face*3]*3;let b:i64=idx[face*3+1]*3;let c:i64=idx[face*3+2]*3
2916 let x:i64=p[b]-p[a];let z:i64=p[b+2]-p[a+2];let u:i64=p[c]-p[a];let v:i64=p[c+2]-p[a+2]
2917 let dx:i64=qx-p[a];let dz:i64=qz-p[a+2];var den:i64=x*v-z*u
2918 if den==0{return NSA_E_MISS};var w1:i64=dx*v-dz*u;var w2:i64=x*dz-z*dx
2919 if den<0{den=0-den;w1=0-w1;w2=0-w2};if w1<0||w2<0||w1>den||w2>den-w1{return NSA_E_MISS}
2920 var w0:i64=den-w1-w2;let gcd:i64=ngb_clip_gcd(ngb_clip_gcd(w0,w1),ngb_clip_gcd(w2,den));w0=w0/gcd;w1=w1/gcd;w2=w2/gcd;den=den/gcd
2921 result[0]=w0;result[1]=w1;result[2]=w2;result[3]=den;var depth:i64=0;var k:i64=0
2922 while k<3{let y:i64=p[idx[face*3+k]*3+1];let mag:i64=ngb_clip_product(ngb_abs(y),result[k]);if mag<0{return NSA_E_RANGE}
2923 if y<0{if depth<0-NGB_CLIP_I64_MAX+mag{return NSA_E_RANGE};depth=depth-mag}
2924 else{if depth>NGB_CLIP_I64_MAX-mag{return NSA_E_RANGE};depth=depth+mag};k=k+1
2925 };result[4]=depth;return 0
2926}
2927// direction=-1 chooses smallest Y, +1 greatest Y. Integer query coordinates are caller data.
2928// Caller output: five i64 words; scratch: twelve disjoint i64 words. Output unchanged on refusal.
2929// Equal-depth overlapping hits refuse instead of guessing an attachment seam.
2930func nsa_landmark_y_v1(b:*u8,n:i64,qx:i64,qz:i64,direction:i64,out:*i64,cap:i64,work:*i64,words:i64)->i64{
2931 if direction!=-1&&direction!=1{return NSA_E_INPUT}
2932 if qx<0-NGB_ATTACH_COORD_MAX||qx>NGB_ATTACH_COORD_MAX||qz<0-NGB_ATTACH_COORD_MAX||qz>NGB_ATTACH_COORD_MAX{return NSA_E_RANGE}
2933 if cap<5||words<12{return NSA_E_INPUT};let valid:i64=ngb_attach_source_v1(b,n);if valid<0{return valid}
2934 let ow:i64=ngp_output(b,n,out as *u8,40,40);if ow<0{return ow}
2935 let sw:i64=ngp_output(b,n,work as *u8,96,96);if sw<0{return sw}
2936 let overlap:i64=ngp_output(work as *u8,96,out as *u8,40,40);if overlap<0{return overlap}
2937 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3);let h:*i64=b as *i64
2938 let p:*i64=((b as i64)+(va+1)*8) as *i64;let idx:*i64=((b as i64)+(ta+1)*8) as *i64
2939 return nsa_landmark_mesh_y_v1(p,idx,h[ta],qx,qz,direction,out,work)
2940}
2941// Internal mesh view: source and disjoint storage already validated by boundary.
2942func nsa_landmark_mesh_y_v1(p:*i64,idx:*i64,nt:i64,qx:i64,qz:i64,direction:i64,out:*i64,work:*i64)->i64{
2943 let trial:*i64=work;let best:*i64=((work as i64)+48) as *i64;var face:i64=0;var selected:i64=-1;var ambiguous:i64=0
2944 while face<nt{let rc:i64=nsa_triangle_y_v1(p,idx,face,qx,qz,trial);if rc==NSA_E_RANGE{return rc}
2945 if rc==0{var comparison:i64=0;if selected>=0{comparison=nsa_ratio_cmp(trial[4],trial[3],best[4],best[3])}
2946 if selected<0||comparison==direction{selected=face;ambiguous=0;var k:i64=0;while k<5{best[k]=trial[k];k=k+1}}
2947 else{if comparison==0{ambiguous=1}}
2948 };face=face+1
2949 };if selected<0{return NSA_E_MISS};if ambiguous!=0{return NSA_E_AMBIGUOUS}
2950 out[0]=selected;var k:i64=0;while k<4{out[k+1]=best[k];k=k+1};return 0
2951}
2952
2953// Topological correspondence only: labels identify connected source vertices, never anatomy.
2954// Stable label is the minimum source vertex index, independent of triangle order.
2955func nsa_component_root_v1(labels:*i64,v:i64)->i64{
2956 var root:i64=v;while labels[root]!=root{root=labels[root]}
2957 var q:i64=v;while labels[q]!=q{let next:i64=labels[q];labels[q]=root;q=next};return root
2958}
2959func nsa_component_join_v1(labels:*i64,a:i64,b:i64)->i64{
2960 let x:i64=nsa_component_root_v1(labels,a);let y:i64=nsa_component_root_v1(labels,b)
2961 if x<y{labels[y]=x}else{if y<x{labels[x]=y}};return 0
2962}
2963// Boundary validation completes before output writes. Capacity is measured in i64 labels.
2964// Required capacity derives from the validated VERT count; isolated vertices remain components.
2965func nsa_component_labels_v1(b:*u8,n:i64,labels:*i64,capacity:i64)->i64{
2966 let valid:i64=ngb_attach_source_v1(b,n);if valid<0{return valid}
2967 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3);let h:*i64=b as *i64
2968 let nv:i64=h[va];let nt:i64=h[ta];if capacity<nv{return NSA_E_INPUT}
2969 if nv>NGB_CLIP_I64_MAX/8{return NSA_E_RANGE}
2970 let space:i64=ngp_output(b,n,labels as *u8,nv*8,nv*8);if space<0{return space}
2971 let idx:*i64=((b as i64)+(ta+1)*8) as *i64
2972 var i:i64=0;while i<nv{labels[i]=i;i=i+1}
2973 i=0;while i<nt{let a:i64=idx[i*3];nsa_component_join_v1(labels,a,idx[i*3+1]);nsa_component_join_v1(labels,a,idx[i*3+2]);i=i+1}
2974 var count:i64=0;i=0;while i<nv{labels[i]=nsa_component_root_v1(labels,i);if labels[i]==i{count=count+1};i=i+1};return count
2975}
2976
2977const NSA_E_MIXED_SUPPORT:i64=-54
2978// Derive rigid attachment support from the actual connected component and skin weights.
2979// A mixed-joint component requires shared deformation, never an inferred replacement joint.
2980func nsa_rigid_component_joint_v1(b:*u8,n:i64,seed:i64,labels:*i64,capacity:i64)->i64{
2981 let rc:i64=nsa_component_labels_v1(b,n,labels,capacity);if rc<0{return rc}
2982 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let sk:i64=nxa_counted_section(b,n,nxa_tag4("SKIN"),8);let sj:i64=nxa_counted_section(b,n,nxa_tag4("SKEL"),8);if va<0||sk<0||sj<0{return NSA_E_INPUT}
2983 let h:*i64=b as *i64;let nv:i64=h[va];let nj:i64=h[sj];if seed<0||seed>=nv||h[sk]!=nv||nj<1{return NSA_E_INPUT}
2984 let component:i64=labels[seed];var selected:i64=-1;var v:i64=0
2985 while v<nv{if labels[v]==component{var sum:i64=0;var k:i64=0;while k<4{let j:i64=h[sk+1+v*8+k];let w:i64=h[sk+1+v*8+4+k];if j<0||j>=nj||w<0||w>4096{return NSA_E_INPUT};sum=sum+w;if w>0{if selected<0{selected=j}else{if selected!=j{return NSA_E_MIXED_SUPPORT}}};k=k+1};if sum!=4096{return NSA_E_INPUT}};v=v+1};return selected
2986}
2987
2988// Exact source component boundary edges, not anatomical landmarks.
2989// A measure call uses out=null,out_words=0 and still requires labels/sort workspace.
2990// Labels need nv words; sort workspace needs 3*nt words. Output is [a,b] per edge.
2991// All storage is caller owned. Output remains unchanged on refusal.
2992const NSA_E_NONMANIFOLD:i64=-55
2993func nsa_component_boundary_v1(b:*u8,n:i64,seed:i64,labels:*i64,label_words:i64,work:*i64,work_words:i64,out:*i64,out_words:i64)->i64{
2994 let valid:i64=ngb_attach_source_v1(b,n);if valid<0{return valid}
2995 let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3);let h:*i64=b as *i64;let nv:i64=h[va];let nt:i64=h[ta]
2996 if seed<0||seed>=nv||label_words<nv||nt>NGB_CLIP_I64_MAX/24||nv>NGB_CLIP_I64_MAX/8||work_words<nt*3||out_words<0||out_words>NGB_CLIP_I64_MAX/8{return NSA_E_INPUT}
2997 if ngb_clip_product(nv,nv)<0{return NSA_E_RANGE}
2998 let lb:i64=nv*8;let wb:i64=nt*24;let ob:i64=out_words*8
2999 if ngp_output(b,n,labels as *u8,lb,lb)<0||ngp_output(b,n,work as *u8,wb,wb)<0||ngp_output(labels as *u8,lb,work as *u8,wb,wb)<0{return NSA_E_INPUT}
3000 if ob>0{if ngp_output(b,n,out as *u8,ob,ob)<0||ngp_output(labels as *u8,lb,out as *u8,ob,ob)<0||ngp_output(work as *u8,wb,out as *u8,ob,ob)<0{return NSA_E_INPUT}}
3001 if ob==0&&(out as i64)!=0{return NSA_E_INPUT}
3002 let rc:i64=nsa_component_labels_v1(b,n,labels,label_words);if rc<0{return rc};let root:i64=labels[seed];let idx:*i64=((b as i64)+(ta+1)*8) as *i64
3003 var count:i64=0;var t:i64=0;while t<nt{if labels[idx[t*3]]==root{var k:i64=0;while k<3{let a:i64=idx[t*3+k];let c:i64=idx[t*3+(k+1)%3];if a<c{work[count]=a*nv+c}else{work[count]=c*nv+a};count=count+1;k=k+1}};t=t+1}
3004 nx_quicksort(work as *nx_int,count);var at:i64=0;var boundary:i64=0
3005 while at<count{var end:i64=at+1;while end<count&&work[end]==work[at]{end=end+1};if end-at>2{return NSA_E_NONMANIFOLD};if end-at==1{boundary=boundary+1};at=end}
3006 if ob==0{return boundary};if boundary>out_words/2{return NSA_E_INPUT}
3007 at=0;var written:i64=0;while at<count{var end:i64=at+1;while end<count&&work[end]==work[at]{end=end+1};if end-at==1{out[written*2]=work[at]/nv;out[written*2+1]=work[at]%nv;written=written+1};at=end}
3008 return written
3009}
3010
3011
3012func sa_put(s:*u8)->i64{var n:i64=0;while s[n]!=0{n=n+1};return sys_write(1,s,n)}
3013func sa_num(x:i64)->i64{let p:*u8=sys_mmap(32) as *u8;if (p as i64)<=0{return -1};var n:i64=x;var i:i64=31;if n<0{sa_put("-");n=0-n};p[i]=0;i=i-1;if n==0{p[i]=48;i=i-1};while n>0{p[i]=(48+n%10) as u8;n=n/10;i=i-1};sa_put(((p as i64)+i+1) as *u8);return sys_munmap(p,32)}
3014func sa_check(ok:i64,name:*u8,c:*i64)->i64{c[0]=c[0]+1;if ok==1{sa_put("PASS ")}else{sa_put("FAIL ");c[1]=c[1]+1};sa_put(name);sa_put("
3015");return ok}
3016
3017func main(argc:i64,argv:*i64)->i64{
3018 if argc!=2{return 2};let len:*i64=sys_mmap(16) as *i64;let b:*u8=sys_map_file(argv[1] as *u8,len);if (b as i64)==0{return 3};let n:i64=len[0];let valid:i64=ngb_attach_source_v1(b,n);if valid<0{return 4}
3019 let h:*i64=b as *i64;let va:i64=nxa_counted_section(b,n,nxa_tag4("VERT"),3);let ta:i64=nxa_counted_section(b,n,nxa_tag4("TRIS"),3);let nv:i64=h[va];let nt:i64=h[ta]
3020 let labels:*i64=sys_mmap_try(nv*8) as *i64;let work:*i64=sys_mmap_try(nt*24) as *i64;let out:*i64=sys_mmap_try(nt*48) as *i64;let ctr:*i64=sys_mmap_try(16) as *i64;if (labels as i64)<=0||(work as i64)<=0||(out as i64)<=0||(ctr as i64)<=0{return 5};ctr[0]=0;ctr[1]=0;var rc:i64=0;var same:i64=0;
3021 rc=nsa_component_boundary_v1(b,n,11012,labels,nv,work,nt*3,out,nt*6);sa_check(rc==10,"actual component11012 boundary cardinality",ctr);
3022same=1;if out[0]!=11012||out[1]!=11015{same=0};if out[2]!=11012||out[3]!=11024{same=0};if out[4]!=11015||out[5]!=11017{same=0};if out[6]!=11017||out[7]!=11036{same=0};if out[8]!=11024||out[9]!=11026{same=0};if out[10]!=11026||out[11]!=11028{same=0};if out[12]!=11028||out[13]!=11030{same=0};if out[14]!=11030||out[15]!=11032{same=0};if out[16]!=11032||out[17]!=11034{same=0};if out[18]!=11034||out[19]!=11036{same=0};sa_check(same==1,"actual component11012 exact endpoint identity",ctr);
3023rc=nsa_component_boundary_v1(b,n,13110,labels,nv,work,nt*3,out,nt*6);sa_check(rc==10,"actual component13110 boundary cardinality",ctr);
3024same=1;if out[0]!=13112||out[1]!=13113{same=0};if out[2]!=13112||out[3]!=13123{same=0};if out[4]!=13113||out[5]!=13115{same=0};if out[6]!=13115||out[7]!=13135{same=0};if out[8]!=13123||out[9]!=13125{same=0};if out[10]!=13125||out[11]!=13126{same=0};if out[12]!=13126||out[13]!=13128{same=0};if out[14]!=13128||out[15]!=13131{same=0};if out[16]!=13131||out[17]!=13133{same=0};if out[18]!=13133||out[19]!=13135{same=0};sa_check(same==1,"actual component13110 exact endpoint identity",ctr);
3025rc=nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,out,nt*6);sa_check(rc==48,"actual component13411 boundary cardinality",ctr);
3026same=1;if out[0]!=13412||out[1]!=13413{same=0};if out[2]!=13412||out[3]!=13482{same=0};if out[4]!=13413||out[5]!=13442{same=0};if out[6]!=13426||out[7]!=13427{same=0};if out[8]!=13426||out[9]!=13472{same=0};if out[10]!=13427||out[11]!=13433{same=0};if out[12]!=13430||out[13]!=13431{same=0};if out[14]!=13430||out[15]!=13460{same=0};if out[16]!=13431||out[17]!=13472{same=0};if out[18]!=13433||out[19]!=13481{same=0};if out[20]!=13435||out[21]!=13436{same=0};if out[22]!=13435||out[23]!=13439{same=0};if out[24]!=13436||out[25]!=13437{same=0};if out[26]!=13437||out[27]!=13459{same=0};if out[28]!=13439||out[29]!=13442{same=0};if out[30]!=13459||out[31]!=13460{same=0};if out[32]!=13476||out[33]!=13477{same=0};if out[34]!=13476||out[35]!=13478{same=0};if out[36]!=13477||out[37]!=13487{same=0};if out[38]!=13478||out[39]!=13479{same=0};if out[40]!=13479||out[41]!=13480{same=0};if out[42]!=13480||out[43]!=13481{same=0};if out[44]!=13482||out[45]!=13672{same=0};if out[46]!=13487||out[47]!=13762{same=0};if out[48]!=13492||out[49]!=13493{same=0};if out[50]!=13492||out[51]!=13645{same=0};if out[52]!=13493||out[53]!=13498{same=0};if out[54]!=13498||out[55]!=13634{same=0};if out[56]!=13501||out[57]!=13502{same=0};if out[58]!=13501||out[59]!=13650{same=0};if out[60]!=13502||out[61]!=13651{same=0};if out[62]!=13504||out[63]!=13505{same=0};if out[64]!=13504||out[65]!=13600{same=0};if out[66]!=13505||out[67]!=13672{same=0};if out[68]!=13600||out[69]!=13620{same=0};if out[70]!=13608||out[71]!=13609{same=0};if out[72]!=13608||out[73]!=13612{same=0};if out[74]!=13609||out[75]!=13645{same=0};if out[76]!=13612||out[77]!=13651{same=0};if out[78]!=13613||out[79]!=13614{same=0};if out[80]!=13613||out[81]!=13634{same=0};if out[82]!=13614||out[83]!=13615{same=0};if out[84]!=13615||out[85]!=13619{same=0};if out[86]!=13619||out[87]!=13620{same=0};if out[88]!=13648||out[89]!=13649{same=0};if out[90]!=13648||out[91]!=13656{same=0};if out[92]!=13649||out[93]!=13650{same=0};if out[94]!=13656||out[95]!=13762{same=0};sa_check(same==1,"actual component13411 exact endpoint identity",ctr);
3027rc=nsa_component_boundary_v1(b,n,13510,labels,nv,work,nt*3,out,nt*6);sa_check(rc==52,"actual component13510 boundary cardinality",ctr);
3028same=1;if out[0]!=13512||out[1]!=13513{same=0};if out[2]!=13512||out[3]!=13523{same=0};if out[4]!=13513||out[5]!=13596{same=0};if out[6]!=13522||out[7]!=13523{same=0};if out[8]!=13522||out[9]!=13557{same=0};if out[10]!=13527||out[11]!=13528{same=0};if out[12]!=13527||out[13]!=13599{same=0};if out[14]!=13528||out[15]!=13537{same=0};if out[16]!=13531||out[17]!=13532{same=0};if out[18]!=13531||out[19]!=13535{same=0};if out[20]!=13532||out[21]!=13560{same=0};if out[22]!=13535||out[23]!=13537{same=0};if out[24]!=13557||out[25]!=13559{same=0};if out[26]!=13559||out[27]!=13565{same=0};if out[28]!=13560||out[29]!=13561{same=0};if out[30]!=13561||out[31]!=13588{same=0};if out[32]!=13562||out[33]!=13563{same=0};if out[34]!=13562||out[35]!=13592{same=0};if out[36]!=13563||out[37]!=13564{same=0};if out[38]!=13564||out[39]!=13565{same=0};if out[40]!=13573||out[41]!=13574{same=0};if out[42]!=13573||out[43]!=13576{same=0};if out[44]!=13574||out[45]!=13592{same=0};if out[46]!=13576||out[47]!=13588{same=0};if out[48]!=13596||out[49]!=13678{same=0};if out[50]!=13599||out[51]!=13684{same=0};if out[52]!=13677||out[53]!=13678{same=0};if out[54]!=13677||out[55]!=13688{same=0};if out[56]!=13683||out[57]!=13684{same=0};if out[58]!=13683||out[59]!=13702{same=0};if out[60]!=13685||out[61]!=13688{same=0};if out[62]!=13685||out[63]!=13698{same=0};if out[64]!=13698||out[65]!=13699{same=0};if out[66]!=13699||out[67]!=13731{same=0};if out[68]!=13701||out[69]!=13702{same=0};if out[70]!=13701||out[71]!=13711{same=0};if out[72]!=13705||out[73]!=13706{same=0};if out[74]!=13705||out[75]!=13735{same=0};if out[76]!=13706||out[77]!=13709{same=0};if out[78]!=13709||out[79]!=13711{same=0};if out[80]!=13731||out[81]!=13733{same=0};if out[82]!=13733||out[83]!=13739{same=0};if out[84]!=13735||out[85]!=13736{same=0};if out[86]!=13736||out[87]!=13756{same=0};if out[88]!=13737||out[89]!=13738{same=0};if out[90]!=13737||out[91]!=13740{same=0};if out[92]!=13738||out[93]!=13759{same=0};if out[94]!=13739||out[95]!=13740{same=0};if out[96]!=13743||out[97]!=13744{same=0};if out[98]!=13743||out[99]!=13759{same=0};if out[100]!=13744||out[101]!=13746{same=0};if out[102]!=13746||out[103]!=13756{same=0};sa_check(same==1,"actual component13510 exact endpoint identity",ctr);
3029rc=nsa_component_boundary_v1(b,n,13763,labels,nv,work,nt*3,out,nt*6);sa_check(rc==48,"actual component13763 boundary cardinality",ctr);
3030same=1;if out[0]!=13764||out[1]!=13765{same=0};if out[2]!=13764||out[3]!=13834{same=0};if out[4]!=13765||out[5]!=13794{same=0};if out[6]!=13778||out[7]!=13779{same=0};if out[8]!=13778||out[9]!=13824{same=0};if out[10]!=13779||out[11]!=13785{same=0};if out[12]!=13781||out[13]!=13782{same=0};if out[14]!=13781||out[15]!=13814{same=0};if out[16]!=13782||out[17]!=13824{same=0};if out[18]!=13785||out[19]!=13833{same=0};if out[20]!=13787||out[21]!=13788{same=0};if out[22]!=13787||out[23]!=13790{same=0};if out[24]!=13788||out[25]!=13813{same=0};if out[26]!=13790||out[27]!=13791{same=0};if out[28]!=13791||out[29]!=13794{same=0};if out[30]!=13813||out[31]!=13814{same=0};if out[32]!=13828||out[33]!=13829{same=0};if out[34]!=13828||out[35]!=13839{same=0};if out[36]!=13829||out[37]!=13830{same=0};if out[38]!=13830||out[39]!=13831{same=0};if out[40]!=13831||out[41]!=13832{same=0};if out[42]!=13832||out[43]!=13833{same=0};if out[44]!=13834||out[45]!=14024{same=0};if out[46]!=13839||out[47]!=14114{same=0};if out[48]!=13844||out[49]!=13847{same=0};if out[50]!=13844||out[51]!=13850{same=0};if out[52]!=13847||out[53]!=13997{same=0};if out[54]!=13850||out[55]!=13986{same=0};if out[56]!=13852||out[57]!=13853{same=0};if out[58]!=13852||out[59]!=14002{same=0};if out[60]!=13853||out[61]!=14003{same=0};if out[62]!=13856||out[63]!=13857{same=0};if out[64]!=13856||out[65]!=13952{same=0};if out[66]!=13857||out[67]!=14024{same=0};if out[68]!=13952||out[69]!=13972{same=0};if out[70]!=13960||out[71]!=13961{same=0};if out[72]!=13960||out[73]!=13964{same=0};if out[74]!=13961||out[75]!=13997{same=0};if out[76]!=13964||out[77]!=14003{same=0};if out[78]!=13965||out[79]!=13966{same=0};if out[80]!=13965||out[81]!=13968{same=0};if out[82]!=13966||out[83]!=13971{same=0};if out[84]!=13968||out[85]!=13986{same=0};if out[86]!=13971||out[87]!=13972{same=0};if out[88]!=14000||out[89]!=14001{same=0};if out[90]!=14000||out[91]!=14008{same=0};if out[92]!=14001||out[93]!=14002{same=0};if out[94]!=14008||out[95]!=14114{same=0};sa_check(same==1,"actual component13763 exact endpoint identity",ctr);
3031rc=nsa_component_boundary_v1(b,n,13862,labels,nv,work,nt*3,out,nt*6);sa_check(rc==52,"actual component13862 boundary cardinality",ctr);
3032same=1;if out[0]!=13864||out[1]!=13865{same=0};if out[2]!=13864||out[3]!=13875{same=0};if out[4]!=13865||out[5]!=13948{same=0};if out[6]!=13874||out[7]!=13875{same=0};if out[8]!=13874||out[9]!=13908{same=0};if out[10]!=13879||out[11]!=13880{same=0};if out[12]!=13879||out[13]!=13951{same=0};if out[14]!=13880||out[15]!=13889{same=0};if out[16]!=13883||out[17]!=13884{same=0};if out[18]!=13883||out[19]!=13887{same=0};if out[20]!=13884||out[21]!=13912{same=0};if out[22]!=13887||out[23]!=13889{same=0};if out[24]!=13908||out[25]!=13910{same=0};if out[26]!=13910||out[27]!=13916{same=0};if out[28]!=13912||out[29]!=13913{same=0};if out[30]!=13913||out[31]!=13940{same=0};if out[32]!=13914||out[33]!=13915{same=0};if out[34]!=13914||out[35]!=13917{same=0};if out[36]!=13915||out[37]!=13944{same=0};if out[38]!=13916||out[39]!=13917{same=0};if out[40]!=13925||out[41]!=13926{same=0};if out[42]!=13925||out[43]!=13928{same=0};if out[44]!=13926||out[45]!=13944{same=0};if out[46]!=13928||out[47]!=13940{same=0};if out[48]!=13948||out[49]!=14029{same=0};if out[50]!=13951||out[51]!=14036{same=0};if out[52]!=14029||out[53]!=14030{same=0};if out[54]!=14030||out[55]!=14040{same=0};if out[56]!=14035||out[57]!=14036{same=0};if out[58]!=14035||out[59]!=14054{same=0};if out[60]!=14037||out[61]!=14040{same=0};if out[62]!=14037||out[63]!=14050{same=0};if out[64]!=14050||out[65]!=14051{same=0};if out[66]!=14051||out[67]!=14083{same=0};if out[68]!=14053||out[69]!=14054{same=0};if out[70]!=14053||out[71]!=14063{same=0};if out[72]!=14057||out[73]!=14058{same=0};if out[74]!=14057||out[75]!=14087{same=0};if out[76]!=14058||out[77]!=14061{same=0};if out[78]!=14061||out[79]!=14063{same=0};if out[80]!=14083||out[81]!=14085{same=0};if out[82]!=14085||out[83]!=14091{same=0};if out[84]!=14087||out[85]!=14088{same=0};if out[86]!=14088||out[87]!=14108{same=0};if out[88]!=14089||out[89]!=14090{same=0};if out[90]!=14089||out[91]!=14092{same=0};if out[92]!=14090||out[93]!=14111{same=0};if out[94]!=14091||out[95]!=14092{same=0};if out[96]!=14095||out[97]!=14096{same=0};if out[98]!=14095||out[99]!=14111{same=0};if out[100]!=14096||out[101]!=14098{same=0};if out[102]!=14098||out[103]!=14108{same=0};sa_check(same==1,"actual component13862 exact endpoint identity",ctr);
3033
3034 out[0]=991;out[1]=992;sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,0 as *i64,0)==48&&out[0]==991,"measure returns required edges without output",ctr);
3035 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,out,95)==NSA_E_INPUT&&out[0]==991&&out[1]==992,"one word short output refuses before write",ctr);
3036 sa_check(nsa_component_boundary_v1(b,n,-1,labels,nv,work,nt*3,out,96)==NSA_E_INPUT&&out[0]==991,"negative seed refuses",ctr);
3037 sa_check(nsa_component_boundary_v1(b,n,nv,labels,nv,work,nt*3,out,96)==NSA_E_INPUT&&out[0]==991,"one past seed refuses",ctr);
3038 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv-1,work,nt*3,out,96)==NSA_E_INPUT&&out[0]==991,"short label storage refuses",ctr);
3039 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3-1,out,96)==NSA_E_INPUT&&out[0]==991,"short sort workspace refuses",ctr);
3040 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,labels,96)==NSA_E_INPUT,"output-label alias refuses",ctr);
3041 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,work,96)==NSA_E_INPUT,"output-sort alias refuses",ctr);
3042 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,labels,nt*3,out,96)==NSA_E_INPUT,"label-sort alias refuses",ctr);
3043 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,b as *i64,96)==NSA_E_INPUT,"source output alias refuses",ctr);
3044 sa_check(nsa_component_boundary_v1(b,n,13411,labels,nv,work,nt*3,out,0)==NSA_E_INPUT,"non-null zero-capacity output refuses",ctr);
3045 sa_put("BOUNDARY-GATE total=");sa_num(ctr[0]);sa_put(" fail=");sa_num(ctr[1]);sa_put("\n");if ctr[1]!=0{return 1};return 0
3046}