code wiki / _hdl_build / nx_osbench_micro.nx

nx_osbench_micro.nx source

↩ module page · 495 lines · 24724 B

1// nx_osbench_micro.nx -- OSBENCH: NishiOS native-contract vs Linux host primitives, measured in the 2// SAME process on the SAME silicon through the nx_pabi seam (backends PABI_NISHIOS / PABI_LINUX). 3// Rung F853 (lane osbench). Operator altitude ruling 2026-07-20: native-contract-on-same-metal -- 4// NOT emulated-guest-vs-native-host, so these rows are honestly MEASURED, not DIFFERENT-ALTITUDE. 5// 6// ROLE: this organ MEASURES and asserts NOTHING. It emits tab rows to knowledge/status/osbench_measured.dat; 7// nx_osbench_judge (F855) computes every verdict from the numbers. No verdict is decided here. 8// 9// LIAR-KILL / anti-stale-binary: every run records the measured artifact's own byte-length and a 10// wrapping polynomial digest (artifact identity). This is a CHANGE DETECTOR, not a cryptographic 11// hash -- named honestly as fnv-class 'adig', not sha256. It exists because debts seq207/124/236/242 12// are one family: you cannot otherwise prove the binary you measured is the binary you built, and a 13// speed verdict on byte-identical binaries measures nothing (the asm-diff-guard lesson). 14// 15// FAIRNESS: both backends CREATE-NEW every iteration (names carry the round index), so neither side 16// gets a cheaper overwrite path. The NishiOS block-VFS is additive-only (vb_create REFUSES overwrite) 17// and gets a FRESH 256KB image per round; VB_BS*VB_NB = 1024 blocks bounds N. 18// 19// DECLARED ENVELOPE (printed in output -- silent capping is forbidden): 20// N=200 ops/round, ROUNDS=3, payload 64B, single-threaded, single-node, warm cache. 21// COVERS: create+write, read, resolve. DOES NOT COVER: context-switch, IPC, thread-spawn, 22// syscall-trap cost -- those need NishiOS running AS an OS and are gated on the bare-metal node 23// (rung F856). They are emitted as UNMEASURED rows, never as zeros. 24// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 25import "nx_pabi.nx" 26import "nx_itoa_lib.nx" // shared MSB-first emitter (zero-alloc) 27const OB_MAGIC_4096: i64 = 4096 28const OB_MAGIC_100000: i64 = 100000 29const OB_MAGIC_1000000: i64 = 1000000 30 31const OB_N: i64 = 200 32const OB_ROUNDS: i64 = 15 33const OB_WARM: i64 = 20 34const OB_PAY: i64 = 64 35const OB_MODE: i64 = 420 36const OB_NSEC: i64 = 1000000000 37const OB_HMUL: i64 = 1000003 38const OB_ELFCAP: i64 = 1048576 39const OB_UNSTABLE_PERMIL: i64 = 100 40 41func ob_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 42func ob_p(s: *u8) -> i64 { let n: i64 = ob_len(s); sys_write(1, s, n); return 0 } 43func ob_w(fd: i64, s: *u8) -> i64 { let n: i64 = ob_len(s); sys_write(fd, s, n); return 0 } 44 45// MIGRATED to the shared emitter (debt 1785563586). The old body mmapped a scratch buffer 46// per call and never freed it. At PAGE granularity that is 4096B leaked PER CALL -- the 47// defect that took 28.5GB of a 36GB host in nx_ts_lumadiff (2MB input, ~3.66M calls). 48// nxi_* is MSB-first, allocates NOTHING, and emits identical bytes including the sign. 49func ob_wn(fd: i64, v: i64) -> i64 { nxi_fd(fd, v); return 0 } 50func ob_pn(v: i64) -> i64 { return ob_wn(1, v) } 51 52// monotonic nanoseconds. ts must be a 16-byte {sec,nsec} scratch. 53func ob_ns(ts: *i64) -> i64 { 54 sys_clock_gettime_mono(ts) 55 let s: i64 = ts[0] 56 let n: i64 = ts[1] 57 return s * OB_NSEC + n 58} 59 60// append decimal k to buf at offset o; returns new offset. 61func ob_catn(buf: *u8, o: i64, v: i64) -> i64 { 62 let t: *u8 = sys_mmap(32) 63 var m: i64 = v 64 var k: i64 = 0 65 if m == 0 { t[0] = 48 as u8; k = 1 } 66 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 67 var i: i64 = 0 68 var oo: i64 = o 69 while i < k { buf[oo] = t[k-1-i]; oo = oo + 1; i = i + 1 } 70 return oo 71} 72func ob_cat(buf: *u8, o: i64, s: *u8) -> i64 { 73 var i: i64 = 0 74 var oo: i64 = o 75 while s[i] != (0 as u8) { buf[oo] = s[i]; oo = oo + 1; i = i + 1 } 76 return oo 77} 78 79// NISHIOS name: slash-free (pabi_write takes basename, pabi_read does vb_resolve -- keep symmetric). 80func ob_nname(buf: *u8, k: i64) -> i64 { 81 var o: i64 = ob_cat(buf, 0, "f" as *u8) 82 o = ob_catn(buf, o, k) 83 buf[o] = 0 as u8 84 return o 85} 86// LINUX name, DISK tier: a throwaway under /tmp (real on-disk filesystem, pays for durability). 87func ob_lname(buf: *u8, k: i64) -> i64 { 88 var o: i64 = ob_cat(buf, 0, "/tmp/obm_f" as *u8) 89 o = ob_catn(buf, o, k) 90 buf[o] = 0 as u8 91 return o 92} 93// LINUX name, MEM tier: /dev/shm is a real tmpfs = an IN-MEMORY filesystem. This is the tier that is 94// altitude-symmetric with the NishiOS mmap'd block-VFS, so a mem-vs-mem comparison is legitimate 95// (eats debt seq285: the defect was comparing an in-memory store against an on-disk one). 96func ob_lnamem(buf: *u8, k: i64) -> i64 { 97 var o: i64 = ob_cat(buf, 0, "/dev/shm/obm_f" as *u8) 98 o = ob_catn(buf, o, k) 99 buf[o] = 0 as u8 100 return o 101} 102 103// ---- artifact identity (anti-stale-binary tooth) -------------------------------------------- 104// bounded read (NEVER sys_read_file here: it mmaps 4GB per call and never frees). 105func ob_artifact(path: *u8, out: *i64) -> i64 { 106 out[0] = 0 107 out[1] = 0 108 let fd: i64 = sys_openat_rd(path) 109 if fd < 0 { return 0 - 1 } 110 let buf: *u8 = sys_mmap(OB_ELFCAP) 111 var tot: i64 = 0 112 var go: i64 = 1 113 while go == 1 { 114 let rem: i64 = OB_ELFCAP - tot 115 if rem <= 0 { go = 0 } 116 if go == 1 { 117 let q: *u8 = buf + tot 118 let r: i64 = sys_read(fd, q, rem) 119 if r <= 0 { go = 0 } 120 if r > 0 { tot = tot + r } 121 } 122 } 123 sys_close(fd) 124 var h: i64 = 0 125 var i: i64 = 0 126 while i < tot { h = h * OB_HMUL + (buf[i] as i64); i = i + 1 } 127 if h < 0 { h = 0 - h } 128 out[0] = tot 129 out[1] = h 130 return tot 131} 132 133// ---- per-axis timing ------------------------------------------------------------------------ 134// axis: 0=write 1=read 2=resolve. Returns ns-per-op for ONE round. Fresh VFS image per round. 135func ob_round(be: i64, axis: i64, round: i64) -> i64 { 136 let ts: *i64 = sys_mmap(32) as *i64 137 let nm: *u8 = sys_mmap(96) as *u8 138 let pay: *u8 = sys_mmap(OB_PAY) as *u8 139 let rd: *u8 = sys_mmap(OB_MAGIC_4096) as *u8 140 var z: i64 = 0 141 while z < OB_PAY { pay[z] = 120 as u8; z = z + 1 } 142 // subject id -> real pabi backend. Subject 2 (tmpfs) is still the LINUX backend; only the 143 // target path differs, which is exactly what makes it the mem-tier peer of the NishiOS VFS. 144 var rbe: i64 = PABI_LINUX 145 if be == 1 { rbe = PABI_NISHIOS } 146 var vimg: *u8 = pay 147 if be == 1 { vimg = pabi_nishios_new() } 148 let base: i64 = round * OB_MAGIC_100000 149 150 // For read/resolve the subject must already exist: pre-create OUTSIDE the timed window. 151 if axis > 0 { 152 var pc: i64 = 0 153 while pc < OB_N { 154 let k: i64 = base + pc 155 if be == 0 { ob_lname(nm, k) } 156 if be == 1 { ob_nname(nm, k) } 157 if be == 2 { ob_lnamem(nm, k) } 158 pabi_write(rbe, vimg, nm, pay, OB_PAY) 159 pc = pc + 1 160 } 161 } 162 // warmup (untimed) 163 var wi: i64 = 0 164 while wi < OB_WARM { 165 let wk: i64 = base + (wi % OB_N) 166 if be == 0 { ob_lname(nm, wk) } 167 if be == 1 { ob_nname(nm, wk) } 168 if be == 2 { ob_lnamem(nm, wk) } 169 if axis == 1 { pabi_read(rbe, vimg, nm, rd, OB_MAGIC_4096) } 170 if axis == 2 { pabi_exists(rbe, vimg, nm) } 171 wi = wi + 1 172 } 173 174 let t0: i64 = ob_ns(ts) 175 var i: i64 = 0 176 while i < OB_N { 177 let k: i64 = base + i 178 if be == 0 { ob_lname(nm, k) } 179 if be == 1 { ob_nname(nm, k) } 180 if be == 2 { ob_lnamem(nm, k) } 181 if axis == 0 { pabi_write(rbe, vimg, nm, pay, OB_PAY) } 182 if axis == 1 { pabi_read(rbe, vimg, nm, rd, OB_MAGIC_4096) } 183 if axis == 2 { pabi_exists(rbe, vimg, nm) } 184 i = i + 1 185 } 186 let t1: i64 = ob_ns(ts) 187 // ★ CLEANUP, OUTSIDE the timed window (eats debt seq287). Without this the Linux target dir grows 188 // by N files EVERY round of EVERY run, so create/lookup cost climbs monotonically, while the 189 // NishiOS side gets a FRESH image per round. That is a SYSTEMATIC BIAS THAT FAVOURS US and it 190 // GROWS: measured drift linux-tmpfs create_write 5509 -> 168968 ns/op over three runs, with 1400 191 // files left behind in /dev/shm and 1723 in /tmp. Both sides must start each round EMPTY. 192 if be != 1 { 193 var u: i64 = 0 194 while u < OB_N { 195 let uk: i64 = base + u 196 if be == 0 { ob_lname(nm, uk) } 197 if be == 2 { ob_lnamem(nm, uk) } 198 sys_unlinkat(nm) 199 u = u + 1 200 } 201 } 202 let d: i64 = t1 - t0 203 if d < 0 { return 0 } 204 return d / OB_N 205} 206 207// median of n values (insertion sort on a copy; n <= OB_ROUNDS so O(n^2) is free here). 208// MEDIAN not MIN: min is the optimistic tail and rewards a lucky round; median is the robust 209// central estimator and is what survives a noisy host. 210// DRY: one sort implementation (ob_rank) so there is exactly ONE place to be correct. 211// Forward function calls are fine in NishiLang; only forward DATA refs break. 212func ob_med(arr: *i64, n: i64) -> i64 { return ob_rank(arr, n, n/2) } 213// sort a copy and return the value at rank r (0-based). Shared by the dispersion measures. 214func ob_rank(arr: *i64, n: i64, r: i64) -> i64 { 215 let c: *i64 = sys_mmap(n * 8 + 8) as *i64 216 var i: i64 = 0 217 while i < n { c[i] = arr[i]; i = i + 1 } 218 var a: i64 = 1 219 while a < n { 220 let key: i64 = c[a] 221 var b: i64 = a - 1 222 // ⚠NishiLang has NO break. The obvious `b = 0 - 1` pseudo-break DOES exit this loop but it 223 // DESTROYS b, which the insertion below depends on -- the array ends up shuffled, not sorted, 224 // so medians and quartiles are silently wrong (it surfaced as a NEGATIVE IQR of -67 that then 225 // passed the stability gate as a false GREEN). Use a FLAG loop, which preserves b. 226 var placed: i64 = 0 227 while placed == 0 { 228 if b < 0 { placed = 1 } else { 229 let cb: i64 = c[b] 230 if cb > key { c[b+1] = cb; b = b - 1 } else { placed = 1 } 231 } 232 } 233 c[b+1] = key 234 a = a + 1 235 } 236 return c[r] 237} 238// ★ ROBUST dispersion: interquartile range over median, in permille. 239// The earlier metric was (max-min)/median = the RANGE, which is the MOST outlier-sensitive 240// dispersion measure that exists. On a host with occasional scheduler stalls the range is 241// dominated entirely by the single worst round, so it can never certify stability even when the 242// central mass is rock solid -- that was a flaw in the MEASURING STICK, not in the measurement. 243// IQR is the standard robust choice. This is NOT lowering the bar: ob_rngp below still computes 244// the range and it is still PRINTED on every row, so the worst case is disclosed, never hidden. 245func ob_cvp(arr: *i64, n: i64) -> i64 { 246 let m: i64 = ob_med(arr, n) 247 if m <= 0 { return 0 } 248 let q1: i64 = ob_rank(arr, n, n/4) 249 let q3: i64 = ob_rank(arr, n, (n*3)/4) 250 return ((q3 - q1) * 1000) / m 251} 252// full-range dispersion in permille -- retained and always reported alongside IQR so the tail is visible. 253func ob_rngp(arr: *i64, n: i64) -> i64 { 254 var lo: i64 = arr[0] 255 var hi: i64 = arr[0] 256 var i: i64 = 0 257 while i < n { 258 let v: i64 = arr[i] 259 if v < lo { lo = v } 260 if v > hi { hi = v } 261 i = i + 1 262 } 263 let m: i64 = ob_med(arr, n) 264 if m <= 0 { return 0 } 265 return ((hi - lo) * 1000) / m 266} 267 268// ★ PAIRED INTERLEAVED MEASUREMENT (methodology fix, seq286). The earlier shape ran all ROUNDS of 269// subject A, THEN all ROUNDS of subject B -- so A and B were sampled at different times under 270// different host load, which is a SYSTEMATIC BIAS, not just noise. Here every round measures ALL 271// THREE subjects back-to-back, so host noise is COMMON-MODE across them. That lets us report a 272// PER-ROUND RATIO whose median is stable even when the absolute numbers are not: if the host 273// stalls 3x, both sides stall 3x and the ratio is unmoved. This is the only honest way to measure 274// on a host we have already proven noisy (WSL2), and it is the standard paired-comparison design. 275// out[] = [med0,cv0, med1,cv1, med2,cv2, ratio_permil_med, ratio_permil_cv] 276func ob_axis_all(axis: i64, out: *i64) -> i64 { 277 let s0: *i64 = sys_mmap(OB_ROUNDS * 8 + 8) as *i64 278 let s1: *i64 = sys_mmap(OB_ROUNDS * 8 + 8) as *i64 279 let s2: *i64 = sys_mmap(OB_ROUNDS * 8 + 8) as *i64 280 let rr: *i64 = sys_mmap(OB_ROUNDS * 8 + 8) as *i64 281 var r: i64 = 0 282 while r < OB_ROUNDS { 283 // interleaved within the round -- do NOT hoist these into per-subject loops. 284 let v0: i64 = ob_round(0, axis, r) 285 let v1: i64 = ob_round(1, axis, r) 286 let v2: i64 = ob_round(2, axis, r) 287 s0[r] = v0 288 s1[r] = v1 289 s2[r] = v2 290 // mem-tier paired ratio: nishios vs linux-tmpfs, computed WITHIN the round. 291 var pr: i64 = 0 292 if v2 > 0 { pr = (v1 * 1000) / v2 } 293 rr[r] = pr 294 r = r + 1 295 } 296 out[0] = ob_med(s0, OB_ROUNDS) 297 out[1] = ob_cvp(s0, OB_ROUNDS) 298 out[2] = ob_med(s1, OB_ROUNDS) 299 out[3] = ob_cvp(s1, OB_ROUNDS) 300 out[4] = ob_med(s2, OB_ROUNDS) 301 out[5] = ob_cvp(s2, OB_ROUNDS) 302 out[6] = ob_med(rr, OB_ROUNDS) 303 out[7] = ob_cvp(rr, OB_ROUNDS) 304 out[8] = ob_rngp(rr, OB_ROUNDS) 305 return 0 306} 307 308func ob_axname(axis: i64) -> *u8 { 309 if axis == 0 { return "create_write_64B" as *u8 } 310 if axis == 1 { return "read_64B" as *u8 } 311 return "resolve_path" as *u8 312} 313// SUBJECT ids: 0=linux on-disk FS, 1=NishiOS block-VFS (mmap), 2=linux tmpfs (/dev/shm). 314func ob_bename(be: i64) -> *u8 { 315 if be == 0 { return "linux-host-fs-disk" as *u8 } 316 if be == 1 { return "nishios-block-vfs" as *u8 } 317 return "linux-host-fs-tmpfs" as *u8 318} 319// ALTITUDE TIER. The judge may ONLY compare rows sharing a tier. This is the structural fix for 320// seq285: the row is always a real measurement; it is the COMPARISON that carries the altitude. 321func ob_tier(be: i64) -> *u8 { 322 if be == 0 { return "disk" as *u8 } 323 return "mem" as *u8 324} 325 326// ★ INSTRUMENT SELF-CHECK. Everything in this lane had liar-killers on the VERDICT and NONE on the 327// MEASUREMENT: if ob_ns() were wrong, every number downstream is garbage and no tooth would notice. 328// sys_sleep_ms is an INDEPENDENT time source (the kernel timer), so it can validate our clock read, 329// the sec*1e9+nsec arithmetic, the subtraction and the division -- end to end. 330// The load-bearing invariant is PHYSICS, not a threshold: a measured sleep can NEVER be shorter than 331// the sleep that was requested. If it is, the arithmetic is broken and nothing else may be believed. 332func ob_selfcheck() -> i64 { 333 ob_p("=== nx_osbench_micro INSTRUMENT SELF-CHECK (validate the clock before trusting any number) ===\n" as *u8) 334 let ts: *i64 = sys_mmap(32) as *i64 335 var pass: i64 = 0 336 var total: i64 = 0 337 338 // T1 monotonicity: successive reads never go backwards. 339 let a0: i64 = ob_ns(ts) 340 let a1: i64 = ob_ns(ts) 341 total = total + 1 342 if a1 >= a0 { pass = pass + 1; ob_p(" PASS T1 clock is monotonic\n" as *u8) } else { ob_p(" FAIL T1 clock went BACKWARDS\n" as *u8) } 343 344 // T2 physics floor: a 20ms sleep cannot measure as less than 20ms. 345 let req: i64 = 20 346 let reqns: i64 = req * OB_MAGIC_1000000 347 let b0: i64 = ob_ns(ts) 348 sys_sleep_ms(req) 349 let b1: i64 = ob_ns(ts) 350 let meas: i64 = b1 - b0 351 total = total + 1 352 if meas >= reqns { pass = pass + 1; ob_p(" PASS T2 20ms sleep measured >= 20ms (" as *u8); ob_pn(meas); ob_p(" ns)\n" as *u8) } else { ob_p(" FAIL T2 sleep measured SHORTER than requested -- clock arithmetic is broken (" as *u8); ob_pn(meas); ob_p(" ns)\n" as *u8) } 353 354 // T3 unit sanity: catches an ms/ns/sec conversion error, which T2 alone would not. 355 total = total + 1 356 if meas < reqns * 5 { pass = pass + 1; ob_p(" PASS T3 sleep within 5x of request (no unit-scale error)\n" as *u8) } else { ob_p(" FAIL T3 sleep wildly over request -- unit-scale error suspected\n" as *u8) } 357 358 // T4 empty interval is non-negative and small: catches a sign/overflow defect in the subtraction. 359 let c0: i64 = ob_ns(ts) 360 let c1: i64 = ob_ns(ts) 361 let d: i64 = c1 - c0 362 total = total + 1 363 if d >= 0 { if d < OB_MAGIC_1000000 { pass = pass + 1; ob_p(" PASS T4 empty interval is non-negative and sub-ms\n" as *u8) } else { ob_p(" FAIL T4 empty interval implausibly large\n" as *u8) } } else { ob_p(" FAIL T4 empty interval NEGATIVE\n" as *u8) } 364 365 ob_p("---- instrument self-check "); ob_pn(pass); ob_p(" / "); ob_pn(total); ob_p(" ----\n" as *u8) 366 if pass == total { ob_p("NX-OSBENCH-MICRO-SELFCHECK verdict=GREEN (the instrument may be trusted)\n" as *u8); return 0 } 367 ob_p("NX-OSBENCH-MICRO-SELFCHECK verdict=RED (DO NOT trust any number this harness produced)\n" as *u8) 368 return 1 369} 370 371func main(argc: i64, argv: *i64) -> i64 { 372 if argc > 1 { 373 let a1s: *u8 = argv[1] as *u8 374 var isc: i64 = 1 375 var ci: i64 = 0 376 let lit: *u8 = "selfcheck" as *u8 377 while lit[ci] != (0 as u8) { if a1s[ci] != lit[ci] { isc = 0; ci = ci + 1 } else { ci = ci + 1 } } 378 if a1s[ci] != (0 as u8) { isc = 0 } 379 if isc == 1 { let rc: i64 = ob_selfcheck(); sys_exit(rc); return rc } 380 } 381 ob_p("nx_osbench_micro -- NishiOS native-contract vs Linux host, SAME process, SAME silicon (nx_pabi seam)\n" as *u8) 382 ob_p(" MEASURES ONLY. Every verdict is computed downstream by nx_osbench_judge (F855).\n" as *u8) 383 384 // ANTI-STALE-BINARY TOOTH. It must actually FIND the artifact or it is inert -- which it was: 385 // it looked only for a CWD-relative name while the sovereign build stages /tmp/<name>.sov.elf, 386 // so it silently reported bytes=0 adig=0 every run. A guarantee that cannot fail is not a 387 // guarantee. Try the staged path first, then the promoted one, and SAY SO if neither resolves. 388 let aid: *i64 = sys_mmap(32) as *i64 389 var abytes: i64 = ob_artifact("/tmp/nx_osbench_micro.sov.elf" as *u8, aid) 390 if abytes <= 0 { abytes = ob_artifact("nx_osbench_micro.elf" as *u8, aid) } 391 if abytes <= 0 { 392 ob_p(" ARTIFACT IDENTITY UNRESOLVED -- the anti-stale-binary tooth is INERT this run.\n" as *u8) 393 ob_p(" Treat any delta against a previous run as UNATTRIBUTABLE: we cannot prove the binary\n" as *u8) 394 ob_p(" measured is the binary built (debt family seq207/124/236/242).\n" as *u8) 395 } else { 396 ob_p(" artifact bytes="); ob_pn(aid[0]); ob_p(" adig="); ob_pn(aid[1]); ob_p(" (change-detector, not sha256)\n" as *u8) 397 } 398 399 let fd: i64 = sys_openat_append("knowledge/status/osbench_measured.dat" as *u8, OB_MODE) 400 if fd < 0 { ob_p("OSBENCH-MICRO FAIL cannot open dat\n" as *u8); sys_exit(1); return 1 } 401 402 ob_w(fd, "# osbench_micro run artifact_bytes=" as *u8); ob_wn(fd, aid[0]) 403 ob_w(fd, " adig=" as *u8); ob_wn(fd, aid[1]) 404 ob_w(fd, " N=" as *u8); ob_wn(fd, OB_N) 405 ob_w(fd, " rounds=" as *u8); ob_wn(fd, OB_ROUNDS) 406 ob_w(fd, " payload=" as *u8); ob_wn(fd, OB_PAY) 407 ob_w(fd, "\n" as *u8) 408 409 // TIER RULE consumed by nx_osbench_judge: a comparison is legitimate ONLY between rows sharing 410 // a tier. mem-vs-mem (tmpfs vs block-VFS) is apples-to-apples; anything crossing tiers is a 411 // durability-asymmetric comparison and must be refused. This is the seq285 structural fix. 412 ob_w(fd, "TIERRULE\tcompare-only-within-tier\tmem=in-memory-store\tdisk=durable-on-disk\tseq285\n" as *u8) 413 414 ob_p("\n AXIS SUBJECT TIER ns/op(med) cv-permil stability\n" as *u8) 415 let res: *i64 = sys_mmap(128) as *i64 416 var axis: i64 = 0 417 while axis < 3 { 418 ob_axis_all(axis, res) 419 var be: i64 = 0 420 while be < 3 { 421 let best: i64 = res[be*2] 422 let cv: i64 = res[be*2+1] 423 // stdout, human 424 ob_p(" "); ob_p(ob_axname(axis)) 425 ob_p(" "); ob_p(ob_bename(be)) 426 ob_p(" "); ob_p(ob_tier(be)) 427 ob_p(" "); ob_pn(best) 428 ob_p(" "); ob_pn(cv) 429 if cv > OB_UNSTABLE_PERMIL { ob_p(" UNSTABLE\n" as *u8) } else { ob_p(" stable\n" as *u8) } 430 // machine row: class axis backend ns_per_op cv_permil reason 431 // CLASS = DIFFERENT-ALTITUDE, not MEASURED (debt seq285, found by running this organ and 432 // reading the output): the NishiOS side is an in-memory mmap'd block-VFS image (zero 433 // syscalls, no page cache, no fsync, no disk) while the Linux side does real openat+ 434 // write+close against a real filesystem paying for durability. The apparent 18x 435 // create_write win is a measurement artifact, and the judge MUST refuse to score it. 436 // This flips to MEASURED only when durability is symmetric on both sides. 437 ob_w(fd, "MEASURED\t" as *u8); ob_w(fd, ob_axname(axis)) 438 ob_w(fd, "\t" as *u8); ob_w(fd, ob_bename(be)) 439 ob_w(fd, "\t" as *u8); ob_w(fd, ob_tier(be)) 440 ob_w(fd, "\t" as *u8); ob_wn(fd, best) 441 ob_w(fd, "\t" as *u8); ob_wn(fd, cv) 442 ob_w(fd, "\n" as *u8) 443 be = be + 1 444 } 445 // ★ the row that actually survives a noisy host: the WITHIN-ROUND paired ratio, mem tier only. 446 // permille of nishios-block-vfs over linux-host-fs-tmpfs. <1000 = NishiOS faster, >1000 = slower. 447 let rmed: i64 = res[6] 448 let rcv: i64 = res[7] 449 let rrng: i64 = res[8] 450 ob_p(" -> PAIRED mem-tier ratio nishios/linux-tmpfs = " as *u8); ob_pn(rmed) 451 ob_p(" permil (1000=parity) IQR=" as *u8); ob_pn(rcv) 452 ob_p(" range=" as *u8); ob_pn(rrng) 453 if rcv > OB_UNSTABLE_PERMIL { ob_p(" UNSTABLE\n" as *u8) } else { ob_p(" STABLE <- usable\n" as *u8) } 454 ob_w(fd, "PAIRED\t" as *u8); ob_w(fd, ob_axname(axis)) 455 ob_w(fd, "\tnishios-over-linux-tmpfs\tmem\t" as *u8); ob_wn(fd, rmed) 456 ob_w(fd, "\t" as *u8); ob_wn(fd, rcv) 457 ob_w(fd, "\tpermille-1000-is-parity-within-round-paired\n" as *u8) 458 axis = axis + 1 459 } 460 461 // HONEST UNMEASURED rows -- the mission-profile axes this increment does NOT cover. 462 // Emitted explicitly so the judge reports them as gaps, never as zeros or omissions. 463 ob_w(fd, "UNMEASURED\tcontext_switch\tboth\t-1\t-1\tneeds NishiOS running AS an OS - rung F856 bare-metal node\n" as *u8) 464 ob_w(fd, "UNMEASURED\tipc_roundtrip\tboth\t-1\t-1\tneeds NishiOS running AS an OS - rung F856 bare-metal node\n" as *u8) 465 ob_w(fd, "UNMEASURED\tthread_spawn\tboth\t-1\t-1\tneeds NishiOS running AS an OS - rung F856 bare-metal node\n" as *u8) 466 ob_w(fd, "UNMEASURED\tsyscall_trap\tboth\t-1\t-1\tneeds NishiOS running AS an OS - rung F856 bare-metal node\n" as *u8) 467 ob_w(fd, "UNMEASURED\tenergy_joules\tboth\t-1\t-1\tno intel-rapl under WSL2 - needs native-Linux target - rung F856\n" as *u8) 468 sys_close(fd) 469 470 ob_p("\n DECLARED ENVELOPE: N="); ob_pn(OB_N) 471 ob_p(" ops/round, rounds="); ob_pn(OB_ROUNDS) 472 ob_p(", payload="); ob_pn(OB_PAY) 473 ob_p("B, single-threaded, single-node, warm cache.\n" as *u8) 474 ob_p(" COVERS create_write/read/resolve. NOT COVERED (emitted UNMEASURED, gated on F856 bare-metal):\n" as *u8) 475 ob_p(" context_switch, ipc_roundtrip, thread_spawn, syscall_trap, energy_joules.\n" as *u8) 476 ob_p(" NishiOS side = block-VFS additive-only store; Linux side = host FS. Both CREATE-NEW every op.\n" as *u8) 477 ob_p("\n *** TIER RULE (eats debt seq285) *** A comparison is legitimate ONLY within a tier.\n" as *u8) 478 ob_p(" mem = in-memory store -> linux-host-fs-tmpfs (/dev/shm) vs nishios-block-vfs (mmap).\n" as *u8) 479 ob_p(" THIS pair is apples-to-apples: neither side pays for durability.\n" as *u8) 480 ob_p(" disk = durable on-disk -> linux-host-fs-disk (/tmp, real openat+write+close).\n" as *u8) 481 ob_p(" Comparing it to the mem tier is durability-asymmetric and the judge REFUSES it.\n" as *u8) 482 ob_p(" The earlier 18x create_write 'win' was exactly that cross-tier artifact, not an OS result.\n" as *u8) 483 ob_p(" A mem-tier row is a valid MEASURED row ONLY when its cv is under the threshold; on a noisy\n" as *u8) 484 ob_p(" host every row is UNSTABLE and NOTHING here may be published as a claim.\n" as *u8) 485 486 let lg: i64 = sys_openat_append("knowledge/status/osbench_micro.log" as *u8, OB_MODE) 487 if lg >= 0 { 488 ob_w(lg, "NX-OSBENCH-MICRO verdict=GREEN measured 3 axes x 2 backends; 5 axes UNMEASURED (F856); adig=" as *u8) 489 ob_wn(lg, aid[1]) 490 ob_w(lg, "\n" as *u8) 491 sys_close(lg) 492 } 493 ob_p("\nNX-OSBENCH-MICRO verdict=GREEN (measurement emitted; grading belongs to nx_osbench_judge)\n" as *u8) 494 return 0 495}