code wiki / (root) / nx_f64_gate.nx

nx_f64_gate.nx source

↩ module page · 801 lines · 45837 B

1// nx_f64_gate.nx -- LN16 "WORKING f64 ARITHMETIC": the x86_64 backend's IEEE-754 2// binary64 arithmetic AND ordering, measured BIT FOR BIT against independent 3// software implementations of the same standard. 4// 5// WHAT THIS MEASURES THAT THE INCUMBENT CANNOT. nx_f64_adversary (T12, shipped 6// 2026-07-16) is a DECIMAL instrument: every check truncates a scaled product to 7// six or nine digits and compares a hand-typed integer. That shape cannot see a 8// one-ulp error, cannot say WHICH bit moved, and asks the reader to take six 9// magic constants on trust. It also compares only NON-NEGATIVE values, which is 10// why it never found the ordering defect this gate reports. Every expectation 11// here is DERIVED at run time from nx_f64_oracle_sov (orc_add/orc_sub/orc_mul/ 12// orc_div) and nx_f64 (nx_f64_lt/gt/eq) -- pure-integer implementations whose 13// anchor proof is the 1217 hardware-IEEE KAT values in _f64_soak_gate_authored. 14// There is not one hand-typed floating-point constant in this file, and operand 15// bit patterns come from nx_i64_to_f64, a THIRD path, so an operand and its 16// expectation are never produced by the same code. 17// 18// WHY BITS AND NOT PRINTED DECIMALS. Two doubles one ulp apart print identically 19// at nine significant digits. The defect this rung exists to remove -- f64 20// arithmetic silently executed at single precision -- is a low-mantissa error, so 21// a decimal comparison is precisely the instrument that cannot see the end of it. 22// 23// WHY ORDERING IS IN SCOPE. A double rides in an i64 register, and x86ctx_emit_cmp 24// lowers EVERY compare to a signed integer cmpq on those bits. Among two negative 25// doubles the signed-integer order is the REVERSE of the numeric order, so 26// `-1.0 < -2.0` evaluates TRUE. The rung's own done-rule program compares two f64 27// values, so ordering is already inside the capability the row claims; flipping 28// the row while `<` is inverted would publish a capability that is not there. 29// Tooth `neg-control-chosen-negative-pair-is-exactly-where-integer-compare-fails` 30// proves the operands chosen are the discriminating ones and not a lucky pair. 31// 32// HOW A FIXTURE REPORTS. A wait status carries eight bits of exit code. A value 33// fixture returns 0 when the bits matched and F6_EXITBASE + k when the HIGHEST 34// differing bit is k (2..65, inside 255), so a failure NAMES the field that moved: 35// 63 sign, 52..62 exponent, 0..51 significand. A predicate fixture returns 36// F6_EXITBASE + the boolean the compiler actually produced, so a failure says 37// which way it went. A pass/fail bit would have said only "wrong". 38// 39// HOW THE BITS ARE READ. NishiLang carries an f64 in an i64 register and `as` is a 40// BIT-REINTERPRET, never a value conversion -- only the __f64_to_i64 intrinsic 41// truncates (nx_parse.nx: `as` emits no FCAST). Case 0 asserts that mechanism 42// against a software-derived value before any arithmetic tooth depends on it. 43// 44// NO SILENT DEFAULT COMPILER. The subject is argv[1]. With no argument this gate 45// REFUSES: nx_cc_equiv_gate learned on 2026-08-14 that a default path finds a 46// stale binary left by an earlier run and returns a fully formed verdict about the 47// wrong artifact. argv[2] is OPTIONAL and is the reference for the integer 48// neutrality axis alone; absent, that axis reports UNOBSERVABLE via gv_need and 49// the run ends SKIP -- an axis that cannot see must abstain, never acquit. 50// 51// DECLARED IMPRECISION. Nothing here exercises NaN or infinity ordering. The 52// ordering fix this gate is built to prove uses a monotone integer key, which is 53// a TOTAL order and therefore cannot express IEEE unorderedness: a comparison 54// against NaN will answer as though NaN were an ordered value. That residual is 55// named in the emitter, named here, and named on the matrix row rather than left 56// for the next reader to discover. Signed zero IS covered: the key maps -0.0 and 57// +0.0 to the same value, which is the IEEE answer. 58// 59// SCRATCH SPLITS BY FILESYSTEM, NOT BY TIDINESS. NAS /tmp is mounted NOEXEC, so 60// every product .elf goes to _build/ and only sources, asm and logs live under 61// /tmp/nx_f64_gate/. An ELF written to /tmp builds clean and then dies exec-127 on 62// every run, which reads exactly like a miscompiling compiler. That is what the 63// positive control is for. 64// 65// RESOURCE ENVELOPE. Per run: one compiler fork, one nxasm fork and one exec per 66// fixture (F6_ACASES + F6_PCASES + 3), all sequential. Every allocation is a 67// sys_mmap sized from a measured string length or from F6_PATH_CAP, all of it 68// process-lifetime and reclaimed at exit; nothing is allocated inside a loop. No 69// file outside /tmp/nx_f64_gate/ and _build/nx_f64_gate_* is written. 70// 71// license_tier: ORIGINAL No hw writes (Rule 26). 72 73import "nx_syscalls.nx" 74import "nx_gate_verdict.nx" 75import "nx_ccbuild_lib.nx" 76import "nx_f64.nx" 77import "nx_f64_cvt.nx" 78import "nx_f64_oracle_sov.nx" 79 80const F6_EXIT_USAGE: i64 = 2 81const F6_EXIT_NOTREE: i64 = 4 82const F6_MODE_0644: i64 = 0x1a4 83const F6_MODE_0755: i64 = 0x1ed 84// binary64 is 64 bits wide (IEEE 754 clause 3.6). The fixture's diff scan starts 85// at the top bit, so its loop bound is DERIVED from this and never typed. 86const F6_BITS: i64 = 64 87// Value-fixture exit protocol: 0 bits matched; 1 unreachable (differed yet no bit 88// differs); F6_EXITBASE + k the highest differing bit is k. 2 + 63 = 65 <= 255. 89// Predicate-fixture exit protocol: 0 matched; F6_EXITBASE + the boolean produced. 90const F6_EXITBASE: i64 = 2 91// A wait status holds the exit code in bits 8..15. 92const F6_WAIT_SHIFT: i64 = 8 93const F6_WAIT_MASK: i64 = 255 94// Widest decimal rendering of an i64 (-9223372036854775808) is 20 characters. 95const F6_DIGITS_MAX: i64 = 20 96const F6_PATH_CAP: i64 = 4096 // Linux PATH_MAX 97// Integer neutrality workload: a loop that exercises add, multiply, subtract, 98// compare and a backward branch through the SHARED backend the f64 work touches. 99const F6_INT_N: i64 = 100 100const F6_INT_MUL: i64 = 3 101const F6_INT_SUB: i64 = 1 102// Slot layout. Value cases 0..F6_ACASES-1, predicate cases from F6_PSLOT, then the 103// three whole-file fixtures. Gaps are deliberate: adding a case must never 104// silently collide with a later slot. 105const F6_ACASES: i64 = 12 106const F6_PSLOT: i64 = 20 107const F6_PCASES: i64 = 6 108const F6_SLOT_NAN: i64 = 30 // LN38: the IEEE-unordered fixture program 109const F6_SLOT_LIT: i64 = 31 // LN36/LN40: exponent and long-literal packing fixture 110const F6_SLOT_CONST: i64 = 32 // LN39: f64 const-expression fixture 111const F6_SLOT_IOVER: i64 = 33 // LN40: integer literal that exceeds i64 must be REFUSED 112const F6_SLOT_EXP: i64 = 34 // LN36: a malformed exponent must be REFUSED 113const F6_SLOT_IMAX: i64 = 35 // neg-control: the largest i64 literal still compiles 114const F6_SLOT_U64: i64 = 36 // positive control: a u64-range decimal keeps its bit pattern (FNV constants) 115// The refusal probes read the compiler's cclog to prove the diagnostic NAMED the defect. A teaching 116// diagnostic is a few hundred bytes; this ceiling is two orders above that, and a message long enough to 117// reach it would itself be the defect. Bounded because a probe must never depend on the size of a message. 118const F6_CCLOG_READ_CAP: i64 = 65536 119const F6_SLOT_PROBE: i64 = 80 120const F6_SLOT_INT_A: i64 = 90 121const F6_SLOT_INT_B: i64 = 91 122const F6_SLOTS: i64 = 92 123 124func f6_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 125 126// ---- value-fixture template, in pieces ------------------------------------ 127// The three run-time numbers (expected bit pattern, top bit of the scan, exit 128// base) are FORMATTED IN rather than typed inside a quoted program, so the 129// emitted fixture inherits this file's derivations instead of restating them. 130func f6_t0() -> *u8 { return "func main(argc: i64, argv: *i64) -> i64 {\n\x00" as *u8 } 131func f6_t1() -> *u8 { return " let r: f64 = \x00" as *u8 } 132func f6_t2() -> *u8 { return "\n let got: i64 = r as i64\n let want: i64 = \x00" as *u8 } 133func f6_t3() -> *u8 { return "\n if got == want { return 0 }\n var k: i64 = \x00" as *u8 } 134func f6_t4() -> *u8 { return "\n while k >= 0 {\n let gb: i64 = (got >> k) & 1\n let wb: i64 = (want >> k) & 1\n if gb > wb { return \x00" as *u8 } 135func f6_t5() -> *u8 { return " + k }\n if wb > gb { return \x00" as *u8 } 136func f6_t6() -> *u8 { return " + k }\n k = k - 1\n }\n return 1\n}\n\x00" as *u8 } 137 138// ---- predicate-fixture template ------------------------------------------- 139func f6_q0() -> *u8 { return "func main(argc: i64, argv: *i64) -> i64 {\n let a: f64 = \x00" as *u8 } 140func f6_q1() -> *u8 { return "\n let b: f64 = \x00" as *u8 } 141func f6_q2() -> *u8 { return "\n var got: i64 = 0\n if a \x00" as *u8 } 142func f6_q3() -> *u8 { return " b { got = 1 }\n let want: i64 = \x00" as *u8 } 143func f6_q4() -> *u8 { return "\n if got == want { return 0 }\n return \x00" as *u8 } 144func f6_q5() -> *u8 { return " + got\n}\n\x00" as *u8 } 145 146func f6_d0() -> *u8 { return " let a: f64 = \x00" as *u8 } 147func f6_d1() -> *u8 { return "\n let b: f64 = \x00" as *u8 } 148func f6_d2() -> *u8 { return "\n\x00" as *u8 } 149func f6_c0() -> *u8 { return "__f64_from_i64(\x00" as *u8 } 150func f6_c1() -> *u8 { return ")\x00" as *u8 } 151func f6_n0() -> *u8 { return "0 - \x00" as *u8 } 152 153// A negative expectation must be emitted as `0 - N`: a bare minus in front of a 154// literal is a parse question this gate has no business asking, and silently 155// emitting one would make a case fail for a reason that is not f64 arithmetic. 156func f6_catwant(b: *u8, o: i64, v: i64) -> i64 { 157 if v >= 0 { return gv_catn(b, o, v) } 158 let p: i64 = gv_cat(b, o, f6_n0()) 159 return gv_catn(b, p, 0 - v) 160} 161 162func f6_build_src(decls: *u8, expr: *u8, want: i64, outlen: *i64) -> *u8 { 163 let need: i64 = f6_slen(f6_t0()) + f6_slen(decls) + f6_slen(f6_t1()) + f6_slen(expr) 164 + f6_slen(f6_t2()) + f6_slen(f6_t3()) + f6_slen(f6_t4()) 165 + f6_slen(f6_t5()) + f6_slen(f6_t6()) + F6_DIGITS_MAX * 3 + 1 166 let b: *u8 = sys_mmap(need) 167 var o: i64 = gv_cat(b, 0, f6_t0()) 168 o = gv_cat(b, o, decls) 169 o = gv_cat(b, o, f6_t1()) 170 o = gv_cat(b, o, expr) 171 o = gv_cat(b, o, f6_t2()) 172 o = f6_catwant(b, o, want) 173 o = gv_cat(b, o, f6_t3()) 174 o = gv_catn(b, o, F6_BITS - 1) 175 o = gv_cat(b, o, f6_t4()) 176 o = gv_catn(b, o, F6_EXITBASE) 177 o = gv_cat(b, o, f6_t5()) 178 o = gv_catn(b, o, F6_EXITBASE) 179 o = gv_cat(b, o, f6_t6()) 180 b[o] = 0 as u8 181 outlen[0] = o 182 return b 183} 184 185func f6_pred_src(aexpr: *u8, bexpr: *u8, op: *u8, want: i64, outlen: *i64) -> *u8 { 186 let need: i64 = f6_slen(f6_q0()) + f6_slen(aexpr) + f6_slen(f6_q1()) + f6_slen(bexpr) 187 + f6_slen(f6_q2()) + f6_slen(op) + f6_slen(f6_q3()) 188 + f6_slen(f6_q4()) + f6_slen(f6_q5()) + F6_DIGITS_MAX * 2 + 1 189 let b: *u8 = sys_mmap(need) 190 var o: i64 = gv_cat(b, 0, f6_q0()) 191 o = gv_cat(b, o, aexpr) 192 o = gv_cat(b, o, f6_q1()) 193 o = gv_cat(b, o, bexpr) 194 o = gv_cat(b, o, f6_q2()) 195 o = gv_cat(b, o, op) 196 o = gv_cat(b, o, f6_q3()) 197 o = gv_catn(b, o, want) 198 o = gv_cat(b, o, f6_q4()) 199 o = gv_catn(b, o, F6_EXITBASE) 200 o = gv_cat(b, o, f6_q5()) 201 b[o] = 0 as u8 202 outlen[0] = o 203 return b 204} 205 206func f6_decls2(a: *u8, b: *u8) -> *u8 { 207 let need: i64 = f6_slen(a) + f6_slen(b) + f6_slen(f6_d0()) + f6_slen(f6_d1()) 208 + f6_slen(f6_d2()) + 1 209 let s: *u8 = sys_mmap(need) 210 var o: i64 = gv_cat(s, 0, f6_d0()) 211 o = gv_cat(s, o, a) 212 o = gv_cat(s, o, f6_d1()) 213 o = gv_cat(s, o, b) 214 o = gv_cat(s, o, f6_d2()) 215 s[o] = 0 as u8 216 return s 217} 218 219// `__f64_from_i64(N)` -- an INTEGER literal converted by the hardware path, so a 220// large operand never depends on decimal float literal parsing. Case 1 proves 221// that conversion against software before any case that uses it. 222func f6_fromi(n: i64) -> *u8 { 223 let need: i64 = f6_slen(f6_c0()) + F6_DIGITS_MAX + f6_slen(f6_c1()) + 1 224 let s: *u8 = sys_mmap(need) 225 var o: i64 = gv_cat(s, 0, f6_c0()) 226 o = gv_catn(s, o, n) 227 o = gv_cat(s, o, f6_c1()) 228 s[o] = 0 as u8 229 return s 230} 231 232func f6_path(pre: *u8, idx: i64, suf: *u8) -> *u8 { 233 let b: *u8 = sys_mmap(F6_PATH_CAP) 234 var o: i64 = gv_cat(b, 0, pre) 235 o = gv_catn(b, o, idx) 236 o = gv_cat(b, o, suf) 237 b[o] = 0 as u8 238 return b 239} 240func f6_tmp(idx: i64, suf: *u8) -> *u8 { return f6_path("/tmp/nx_f64_gate/c\x00" as *u8, idx, suf) } 241func f6_bld(idx: i64, suf: *u8) -> *u8 { return f6_path("_build/nx_f64_gate_c\x00" as *u8, idx, suf) } 242 243func f6_write(path: *u8, s: *u8, n: i64) -> i64 { 244 let fd: i64 = sys_openat_wr(path, F6_MODE_0644) 245 if fd < 0 { return 0 - 1 } 246 sys_write(fd, s, n) 247 sys_close(fd) 248 return n 249} 250 251// Compile an EXISTING source path at slot idx and run the product. 252// out[0] build rc, out[1] raw wait status, out[2] decoded exit code (-1 not run). 253func f6_build_run(cc: *u8, idx: i64, p_nx: *u8, envp: *i64, devnull: i64, out: *i64) -> i64 { 254 let p_s: *u8 = f6_tmp(idx, ".s\x00" as *u8) 255 let p_al: *u8 = f6_tmp(idx, ".asmlog\x00" as *u8) 256 let p_o: *u8 = f6_tmp(idx, ".out\x00" as *u8) 257 let p_cl: *u8 = f6_tmp(idx, ".cclog\x00" as *u8) 258 let p_e: *u8 = f6_bld(idx, ".elf\x00" as *u8) 259 let p_t: *u8 = f6_bld(idx, ".tmpelf\x00" as *u8) 260 let cclog: i64 = sys_openat_wr(p_cl, F6_MODE_0644) 261 let brc: i64 = cb_build(cc, p_nx, p_s, p_e, envp, cclog, p_t, p_al) 262 sys_close(cclog) 263 out[0] = brc 264 out[1] = 0 - 1 265 out[2] = 0 - 1 266 if brc == 0 { 267 out[1] = cb_run_capture(p_e, p_o, envp, devnull) 268 out[2] = (out[1] >> F6_WAIT_SHIFT) & F6_WAIT_MASK 269 } 270 return 0 271} 272 273func f6_run_src(cc: *u8, idx: i64, src: *u8, srclen: i64, envp: *i64, devnull: i64, out: *i64) -> i64 { 274 let p_nx: *u8 = f6_tmp(idx, ".nx\x00" as *u8) 275 f6_write(p_nx, src, srclen) 276 return f6_build_run(cc, idx, p_nx, envp, devnull, out) 277} 278 279func f6_report(idx: i64, label: *u8, want: i64, kindbits: i64, out: *i64) -> i64 { 280 gv_puts("F64GATE case=" as *u8); gv_num(idx) 281 gv_puts(" want=" as *u8); gv_num(want) 282 gv_puts(" build=" as *u8); gv_num(out[0]) 283 gv_puts(" waitstatus=" as *u8); gv_num(out[1]) 284 gv_puts(" exit=" as *u8); gv_num(out[2]) 285 if out[2] >= F6_EXITBASE { 286 if kindbits == 1 { gv_puts(" HIGHEST-DIFFERING-BIT=" as *u8) } 287 if kindbits == 0 { gv_puts(" COMPILER-ANSWERED=" as *u8) } 288 gv_num(out[2] - F6_EXITBASE) 289 } 290 gv_puts(" elfbytes=" as *u8); gv_num(cb_fsize(f6_bld(idx, ".elf\x00" as *u8))) 291 gv_puts(" " as *u8); gv_puts(label) 292 gv_puts("\n" as *u8) 293 return 0 294} 295 296// One f64 VALUE case end to end. Returns 1 when the emitted bits equalled the 297// software expectation. builds[slot] records the build rc so a later tooth can 298// separate "wrong answer" from "never compiled". 299func f6_case(cc: *u8, idx: i64, label: *u8, decls: *u8, expr: *u8, want: i64, 300 envp: *i64, devnull: i64, out: *i64, builds: *i64) -> i64 { 301 let lp: *i64 = sys_mmap(F6_DIGITS_MAX) as *i64 302 let src: *u8 = f6_build_src(decls, expr, want, lp) 303 f6_run_src(cc, idx, src, lp[0], envp, devnull, out) 304 builds[idx] = out[0] 305 f6_report(idx, label, want, 1, out) 306 return ((out[0] == 0) & (out[2] == 0)) as i64 307} 308 309// One f64 PREDICATE case end to end. 310func f6_pcase(cc: *u8, idx: i64, label: *u8, aexpr: *u8, bexpr: *u8, op: *u8, want: i64, 311 envp: *i64, devnull: i64, out: *i64, builds: *i64) -> i64 { 312 let lp: *i64 = sys_mmap(F6_DIGITS_MAX) as *i64 313 let src: *u8 = f6_pred_src(aexpr, bexpr, op, want, lp) 314 f6_run_src(cc, idx, src, lp[0], envp, devnull, out) 315 builds[idx] = out[0] 316 f6_report(idx, label, want, 0, out) 317 return ((out[0] == 0) & (out[2] == 0)) as i64 318} 319 320// LN38 fixture: IEEE-754 UNORDERED compares. Returns a BITMASK of failing checks (bit k = check k), so a RED 321// names which predicate lied; checks 7-9 are ORDERED controls an always-false compare cannot pass. Kept in 322// lockstep with runtime/nx_fx_f64_nan.nx (the standalone witness); the mask semantics are the contract. 323func f6_nan_src() -> *u8 { 324 return "func fx_nan(z: f64) -> f64 { return z / z } 325func fx_id(x: f64) -> f64 { return x } 326func main() -> i64 { 327 let z: f64 = fx_id(0.0) 328 let n: f64 = fx_nan(z) 329 let one: f64 = fx_id(1.0) 330 var bad: i64 = 0 331 if n != n { } else { bad = bad + 1 } 332 if n == n { bad = bad + 2 } 333 if n < one { bad = bad + 4 } 334 if n > one { bad = bad + 8 } 335 if one < n { bad = bad + 16 } 336 if one >= n { bad = bad + 32 } 337 if n <= n { bad = bad + 64 } 338 if one < 2.0 { } else { bad = bad + 128 } 339 if (0.0 - one) < (0.0 - 2.0) { bad = bad + 256 } 340 if one == one { } else { bad = bad + 512 } 341 return bad 342} 343\x00" as *u8 344} 345// A compiler WITHOUT the LN40 packer fix does not refuse the long literals in the LAYER 6 fixture, it LOOPS on 346// them (measured: the LN38 toolchain hung on /tmp/nx_f64_gate/c31.nx until killed), so against such a subject 347// this gate does not go RED, it times out -- the gate roster reads that as RED-by-timeout, and that is the 348// honest verdict: a hang IS the defect. The bite proof for LAYER 6 therefore ran each fixture under `timeout` 349// against the pre-fix compiler (lit: timeout, const: refused, overflow literal: wrongly compiled). 350// LN36 / LN39 / LN40 fixtures (2026-09-03). Each returns a BITMASK of failing checks so a RED names the 351// predicate; the refusal probes must NOT build and their cclog must carry the teaching message by name. 352// Kept in lockstep with runtime/nx_fx_f64_lit.nx, nx_fx_f64_const.nx and nx_fx_int_overflow_refused.nx. 353func f6_lit_src() -> *u8 { return "func fx_id(x: f64) -> f64 { return x }\nfunc main() -> i64 {\n var bad: i64 = 0\n let ten9: f64 = fx_id(1000000000.0)\n let e21: f64 = fx_id(1000000000000000000000.0)\n let e20: f64 = fx_id(100000000000000000000.0)\n if 1e9 != ten9 { bad = bad + 1 }\n if 2.5e-3 != fx_id(2.5) / fx_id(1000.0) { bad = bad + 2 }\n if 1e-9 != fx_id(1.0) / ten9 { bad = bad + 4 }\n if 6.02e23 != fx_id(602.0) * e21 { bad = bad + 8 }\n if 1e-20 != fx_id(1.0) / e20 { bad = bad + 16 }\n if 1E+2 != fx_id(100.0) { bad = bad + 32 }\n if 123.456e2 != fx_id(12345.6) { bad = bad + 64 }\n if 9007199254740993.0 != fx_id(9007199254740992.0) { bad = bad + 128 }\n if 9007199254740995.0 != fx_id(9007199254740996.0) { bad = bad + 256 }\n if 0.1 + 0.2 == 0.3 { bad = bad + 512 }\n if 1e400 <= 1e308 { bad = bad + 1024 }\n if 12345678901234567890.5 != fx_id(12345678901234567890.0) + fx_id(0.5) { bad = bad + 2048 }\n if 1e0 != fx_id(1.0) { bad = bad + 4096 }\n return bad\n}\n\x00" as *u8 } 354func f6_const_src() -> *u8 { return "const PI: f64 = 3.141592653589793\nconst HALF_PI: f64 = PI / 2.0\nconst TWO_PI: f64 = 2.0 * PI\nconst NEG_1P5: f64 = -1.5\nconst K: i64 = 3\nconst KF: f64 = K * 2.0\nconst E9: f64 = 1e9\nconst MIX: f64 = (PI - 1.0) / (K + 1)\nfunc fx_id(x: f64) -> f64 { return x }\nfunc main() -> i64 {\n var bad: i64 = 0\n let pi: f64 = fx_id(3.141592653589793)\n if HALF_PI != pi / fx_id(2.0) { bad = bad + 1 }\n if TWO_PI != fx_id(2.0) * pi { bad = bad + 2 }\n if NEG_1P5 != fx_id(0.0) - fx_id(1.5) { bad = bad + 4 }\n if KF != fx_id(6.0) { bad = bad + 8 }\n if E9 != fx_id(1000000000.0) { bad = bad + 16 }\n if MIX != (pi - fx_id(1.0)) / fx_id(4.0) { bad = bad + 32 }\n if PI != pi { bad = bad + 64 }\n return bad\n}\n\x00" as *u8 } 355func f6_iover_src() -> *u8 { return "func main() -> i64 {\n let x: i64 = 99999999999999999999\n if x == 0 { return 1 }\n return 0\n}\n\x00" as *u8 } 356func f6_badexp_src() -> *u8 { return "func main() -> i64 {\n let y: f64 = 1e-\n if y == 0.0 { return 1 }\n return 0\n}\n\x00" as *u8 } 357func f6_imax_src() -> *u8 { return "func main() -> i64 {\n let x: i64 = 9223372036854775807\n if x == 9223372036854775807 { return 0 }\n return 1\n}\n\x00" as *u8 } 358 359func f6_u64_src() -> *u8 { return "func main() -> i64 {\n let x: i64 = 18446744073709551615\n let y: i64 = 14695981039346656037\n if x != 0 - 1 { return 1 }\n if y == 0 { return 2 }\n return 0\n}\n\x00" as *u8 } 360 361// 1 iff the file at path contains needle (a bounded read: a cclog is a few KB; 65536 is a ceiling that 362// ANNOUNCES nothing because a diagnostic that long is itself the defect). 363func f6_file_has(path: *u8, needle: *u8) -> i64 { 364 let fd: i64 = sys_openat_rd(path) 365 if fd < 0 { return 0 } 366 let cap: i64 = F6_CCLOG_READ_CAP 367 let b: *u8 = sys_mmap(cap) 368 let n: i64 = sys_read(fd, b, cap - 1) 369 sys_close(fd) 370 if n <= 0 { return 0 } 371 var nl: i64 = 0 372 while needle[nl] != 0 { nl = nl + 1 } 373 var i: i64 = 0 374 while i + nl <= n { 375 var j: i64 = 0 376 while j < nl { if b[i + j] != needle[j] { j = nl + 1 } else { j = j + 1 } } 377 if j == nl { return 1 } 378 i = i + 1 379 } 380 return 0 381} 382 383// The integer neutrality / positive-control workload. Its expected sum is computed 384// by THIS function with the same recurrence the fixture runs, so the number in the 385// emitted program is derived, never typed. 386func f6_int_sum() -> i64 { 387 var s: i64 = 0 388 var i: i64 = 0 389 while i < F6_INT_N { s = s + i * F6_INT_MUL - F6_INT_SUB; i = i + 1 } 390 return s 391} 392func f6_i0() -> *u8 { return "func main(argc: i64, argv: *i64) -> i64 {\n var s: i64 = 0\n var i: i64 = 0\n while i < \x00" as *u8 } 393func f6_i1() -> *u8 { return " {\n s = s + i * \x00" as *u8 } 394func f6_i2() -> *u8 { return " - \x00" as *u8 } 395func f6_i3() -> *u8 { return "\n i = i + 1\n }\n if s == \x00" as *u8 } 396func f6_i4() -> *u8 { return " { return 0 }\n return 1\n}\n\x00" as *u8 } 397 398func f6_int_src(outlen: *i64) -> *u8 { 399 let need: i64 = f6_slen(f6_i0()) + f6_slen(f6_i1()) + f6_slen(f6_i2()) 400 + f6_slen(f6_i3()) + f6_slen(f6_i4()) + F6_DIGITS_MAX * 4 + 1 401 let b: *u8 = sys_mmap(need) 402 var o: i64 = gv_cat(b, 0, f6_i0()) 403 o = gv_catn(b, o, F6_INT_N) 404 o = gv_cat(b, o, f6_i1()) 405 o = gv_catn(b, o, F6_INT_MUL) 406 o = gv_cat(b, o, f6_i2()) 407 o = gv_catn(b, o, F6_INT_SUB) 408 o = gv_cat(b, o, f6_i3()) 409 o = gv_catn(b, o, f6_int_sum()) 410 o = gv_cat(b, o, f6_i4()) 411 b[o] = 0 as u8 412 outlen[0] = o 413 return b 414} 415 416func main(argc: i64, argv: *i64) -> i64 { 417 sys_ignore_sigpipe() 418 if argc < 2 { 419 gv_puts("F64GATE verdict=NO-COMPILER-GIVEN\n" as *u8) 420 gv_puts(" this gate measures a COMPILER and cannot guess which one you mean.\n" as *u8) 421 gv_puts(" usage: nx_f64_gate <path-to-cc> [path-to-reference-cc]\n" as *u8) 422 gv_puts(" challenger: ../nx_compile_x86.sov.elf.new live: _offc/nx_cc_sovereign.elf\n" as *u8) 423 gv_puts(" argv[2] is OPTIONAL and feeds the integer-neutrality axis alone.\n" as *u8) 424 sys_exit(F6_EXIT_USAGE) 425 } 426 if cb_anchor_root() == 0 { 427 gv_puts("F64GATE verdict=NO-TREE (buildroot/runtime/nx_compile_x86.nx not found from this cwd)\n" as *u8) 428 sys_exit(F6_EXIT_NOTREE) 429 } 430 let cc: *u8 = argv[1] as *u8 431 var refcc: *u8 = 0 as *u8 432 if argc >= 3 { refcc = argv[2] as *u8 } 433 434 sys_mkdir("/tmp/nx_f64_gate\x00" as *u8, F6_MODE_0755) 435 let envp: *i64 = sys_mmap(8 * 2) as *i64 436 envp[0] = "PATH=/usr/bin:/bin\x00" as *u8 as i64 437 envp[1] = 0 438 let devnull: i64 = sys_openat_wr("/dev/null\x00" as *u8, F6_MODE_0644) 439 let r: *i64 = sys_mmap(8 * 4) as *i64 440 let builds: *i64 = sys_mmap(8 * F6_SLOTS) as *i64 441 442 gv_head("nx_f64_gate -- LN16: IEEE-754 binary64 arithmetic and ordering, bit for bit" as *u8) 443 gv_puts("F64GATE cc=" as *u8); gv_puts(cc) 444 gv_puts(" ccbytes=" as *u8); gv_num(cb_fsize(cc)) 445 gv_puts("\n" as *u8) 446 447 // ---- DERIVED OPERANDS AND EXPECTATIONS --------------------------------- 448 // Operand bits come from nx_i64_to_f64 (the nx_f64 implementation path). 449 // Expected results come from orc_* (the oracle path) and nx_f64_lt/gt/eq. 450 let f0: i64 = nx_i64_to_f64(0) 451 let f1: i64 = nx_i64_to_f64(1) 452 let f2: i64 = nx_i64_to_f64(2) 453 let f3: i64 = nx_i64_to_f64(3) 454 let f4: i64 = nx_i64_to_f64(4) 455 let f5: i64 = nx_i64_to_f64(5) 456 let f6: i64 = nx_i64_to_f64(6) 457 let one_five: i64 = orc_div(f3, f2) // 1.5, exact 458 let k52: i64 = (1 << 52) + 1 // odd 53-bit integer: exact in f64, NOT in f32 459 let p53: i64 = 1 << 53 // 2^53 + 1 is NOT representable -> add must round 460 let p54: i64 = 1 << 54 // 2^54 - 1 needs 54 bits -> sub must round 461 let km: i64 = (1 << 27) + 1 // km*km needs 55 bits -> mul must round 462 let fk52: i64 = nx_i64_to_f64(k52) 463 let fp53: i64 = nx_i64_to_f64(p53) 464 let fp54: i64 = nx_i64_to_f64(p54) 465 let fkm: i64 = nx_i64_to_f64(km) 466 let n1: i64 = orc_sub(f0, f1) // -1.0 467 let n2: i64 = orc_sub(f0, f2) // -2.0 468 469 let w_add_x: i64 = orc_add(one_five, one_five) // 3.0 470 let w_sub_x: i64 = orc_sub(f5, one_five) // 3.5 471 let w_mul_x: i64 = orc_mul(one_five, f3) // 4.5 472 let w_div_x: i64 = orc_div(f6, f4) // 1.5 473 let w_add_r: i64 = orc_add(fp53, f1) 474 let w_sub_r: i64 = orc_sub(fp54, f1) 475 let w_mul_r: i64 = orc_mul(fkm, fkm) 476 let w_div_r: i64 = orc_div(f1, f3) 477 let w_neg: i64 = orc_mul(fk52, f1) 478 let w_negres: i64 = orc_sub(one_five, f5) // -3.5, a NEGATIVE result 479 480 gv_puts("F64GATE derived one_five=" as *u8); gv_num(one_five) 481 gv_puts(" minus_one=" as *u8); gv_num(n1) 482 gv_puts(" minus_two=" as *u8); gv_num(n2) 483 gv_puts(" k52=" as *u8); gv_num(k52) 484 gv_puts(" p53=" as *u8); gv_num(p53) 485 gv_puts(" p54=" as *u8); gv_num(p54) 486 gv_puts(" km=" as *u8); gv_num(km) 487 gv_puts("\n" as *u8) 488 489 let ctr: *i64 = gv_ctr() 490 491 // A missing subject is not a verdict about the language. 492 gv_need("subject-compiler-exists\x00" as *u8, (cb_fsize(cc) > 0) as i64, ctr) 493 gv_subjects("f64-fixtures-compiled-and-run\x00" as *u8, F6_ACASES + F6_PCASES + 2, ctr) 494 495 // ---- LAYER 1: the two mechanisms every later tooth stands on ----------- 496 let c0: i64 = f6_case(cc, 0, "literal 1.5 read back through `as i64`\x00" as *u8, 497 "\x00" as *u8, "1.5\x00" as *u8, one_five, envp, devnull, r, builds) 498 gv_check("decimal-literal-and-as-cast-give-the-software-derived-binary64-bits\x00" as *u8, c0, ctr) 499 500 let c1: i64 = f6_case(cc, 1, "__f64_from_i64 of an odd 53-bit integer\x00" as *u8, 501 "\x00" as *u8, f6_fromi(k52), fk52, envp, devnull, r, builds) 502 gv_check("cvtsi2sd-i64-to-f64-conversion-matches-software-nx_i64_to_f64\x00" as *u8, c1, ctr) 503 504 // ---- LAYER 2: one exactly-representable case per operator -------------- 505 // Case 2 IS the rung's own done-rule (1.5 plus 1.5 equals 3.0), promoted from 506 // an exit code to a bit comparison. The four expectations are DELIBERATELY 507 // different numbers; the distinctness tooth below enforces that, after a first 508 // draft of this gate chose 1.5*2.0 and 6.0/2.0 and made three of them 3.0 -- 509 // which its own control caught. 510 let c2: i64 = f6_case(cc, 2, "1.5 + 1.5 (the LN16 done-rule)\x00" as *u8, 511 f6_decls2("1.5\x00" as *u8, "1.5\x00" as *u8), "a + b\x00" as *u8, 512 w_add_x, envp, devnull, r, builds) 513 gv_check("addsd-exact-1p5-plus-1p5-equals-3p0-bit-for-bit\x00" as *u8, c2, ctr) 514 515 let c3: i64 = f6_case(cc, 3, "5.0 - 1.5\x00" as *u8, 516 f6_decls2("5.0\x00" as *u8, "1.5\x00" as *u8), "a - b\x00" as *u8, 517 w_sub_x, envp, devnull, r, builds) 518 gv_check("subsd-exact-5p0-minus-1p5-equals-3p5-bit-for-bit\x00" as *u8, c3, ctr) 519 520 let c4: i64 = f6_case(cc, 4, "1.5 * 3.0\x00" as *u8, 521 f6_decls2("1.5\x00" as *u8, "3.0\x00" as *u8), "a * b\x00" as *u8, 522 w_mul_x, envp, devnull, r, builds) 523 gv_check("mulsd-exact-1p5-times-3p0-equals-4p5-bit-for-bit\x00" as *u8, c4, ctr) 524 525 let c5: i64 = f6_case(cc, 5, "6.0 / 4.0\x00" as *u8, 526 f6_decls2("6.0\x00" as *u8, "4.0\x00" as *u8), "a / b\x00" as *u8, 527 w_div_x, envp, devnull, r, builds) 528 gv_check("divsd-exact-6p0-over-4p0-equals-1p5-bit-for-bit\x00" as *u8, c5, ctr) 529 530 // ---- LAYER 3: one ROUNDING case per operator --------------------------- 531 // An exact result is reachable by several wrong implementations; a result that 532 // needs round-to-nearest-even in the 53rd bit is not. These are the teeth a 533 // single-precision path, a truncating rounder, or an x87 80-bit temporary 534 // cannot pass. 535 let c6: i64 = f6_case(cc, 6, "2^53 + 1.0 must round to 2^53 (ties to even)\x00" as *u8, 536 f6_decls2(f6_fromi(p53), "1.0\x00" as *u8), "a + b\x00" as *u8, 537 w_add_r, envp, devnull, r, builds) 538 gv_check("addsd-rounding-2pow53-plus-one-rounds-to-nearest-even\x00" as *u8, c6, ctr) 539 540 let c7: i64 = f6_case(cc, 7, "2^54 - 1.0 needs 54 significand bits\x00" as *u8, 541 f6_decls2(f6_fromi(p54), "1.0\x00" as *u8), "a - b\x00" as *u8, 542 w_sub_r, envp, devnull, r, builds) 543 gv_check("subsd-rounding-2pow54-minus-one-rounds-to-nearest-even\x00" as *u8, c7, ctr) 544 545 let c8: i64 = f6_case(cc, 8, "(2^27+1)^2 needs 55 significand bits\x00" as *u8, 546 f6_decls2(f6_fromi(km), f6_fromi(km)), "a * b\x00" as *u8, 547 w_mul_r, envp, devnull, r, builds) 548 gv_check("mulsd-rounding-square-of-2pow27-plus-one-rounds-to-nearest-even\x00" as *u8, c8, ctr) 549 550 // 1/3 is the strongest single-precision discriminator available in one op: it 551 // fills all 52 stored significand bits, so an f32 path differs from bit 28 down. 552 let c9: i64 = f6_case(cc, 9, "1.0 / 3.0 fills the whole significand\x00" as *u8, 553 f6_decls2("1.0\x00" as *u8, "3.0\x00" as *u8), "a / b\x00" as *u8, 554 w_div_r, envp, devnull, r, builds) 555 gv_check("divsd-rounding-one-third-fills-all-52-stored-significand-bits\x00" as *u8, c9, ctr) 556 557 // ---- LAYER 4: sign ----------------------------------------------------- 558 // Every case above has a POSITIVE result, which is exactly the blind spot that 559 // let the ordering defect live: nx_f64_adversary is positive-only too. 560 let c10: i64 = f6_case(cc, 10, "1.5 - 5.0 gives a NEGATIVE result\x00" as *u8, 561 f6_decls2("1.5\x00" as *u8, "5.0\x00" as *u8), "a - b\x00" as *u8, 562 w_negres, envp, devnull, r, builds) 563 gv_check("subsd-negative-result-carries-the-sign-bit-correctly\x00" as *u8, c10, ctr) 564 565 // ---- NEG CONTROL: a value the emitter must NOT mangle ------------------- 566 // Multiplying an odd 53-bit integer by 1.0 is the identity in binary64 and is 567 // NOT the identity through any narrower carrier, so this goes red on precision 568 // loss that an exactly-representable case would survive. 569 let c11: i64 = f6_case(cc, 11, "odd 53-bit value times 1.0 must be the identity\x00" as *u8, 570 f6_decls2(f6_fromi(k52), "1.0\x00" as *u8), "a * b\x00" as *u8, 571 w_neg, envp, devnull, r, builds) 572 gv_check("neg-control-odd-53-bit-value-times-one-comes-back-unmangled\x00" as *u8, c11, ctr) 573 574 // ---- LAYER 5: ORDERING ------------------------------------------------- 575 // p20 must pass on ANY build: it is the positive control for this axis, so an 576 // environment fault cannot be read as an ordering verdict. 577 let mo: *u8 = "0.0 - 1.0\x00" as *u8 578 let mt: *u8 = "0.0 - 2.0\x00" as *u8 579 let p20: i64 = f6_pcase(cc, F6_PSLOT + 0, "1.0 < 2.0 (positive control for ordering)\x00" as *u8, 580 "1.0\x00" as *u8, "2.0\x00" as *u8, "<\x00" as *u8, 581 nx_f64_lt(f1, f2), envp, devnull, r, builds) 582 gv_check("positive-control-ordering-of-two-positive-doubles-is-correct\x00" as *u8, p20, ctr) 583 584 let p21: i64 = f6_pcase(cc, F6_PSLOT + 1, "-1.0 < -2.0 must be FALSE\x00" as *u8, 585 mo, mt, "<\x00" as *u8, 586 nx_f64_lt(n1, n2), envp, devnull, r, builds) 587 gv_check("lt-of-two-negative-doubles-is-not-inverted\x00" as *u8, p21, ctr) 588 589 let p22: i64 = f6_pcase(cc, F6_PSLOT + 2, "-1.0 > -2.0 must be TRUE\x00" as *u8, 590 mo, mt, ">\x00" as *u8, 591 nx_f64_gt(n1, n2), envp, devnull, r, builds) 592 gv_check("gt-of-two-negative-doubles-is-not-inverted\x00" as *u8, p22, ctr) 593 594 let p23: i64 = f6_pcase(cc, F6_PSLOT + 3, "-2.0 < -1.0 must be TRUE\x00" as *u8, 595 mt, mo, "<\x00" as *u8, 596 nx_f64_lt(n2, n1), envp, devnull, r, builds) 597 gv_check("lt-of-two-negative-doubles-holds-in-the-true-direction-too\x00" as *u8, p23, ctr) 598 599 let p24: i64 = f6_pcase(cc, F6_PSLOT + 4, "-1.0 < 1.0 across the sign boundary\x00" as *u8, 600 mo, "1.0\x00" as *u8, "<\x00" as *u8, 601 nx_f64_lt(n1, f1), envp, devnull, r, builds) 602 gv_check("lt-across-the-sign-boundary-stays-correct\x00" as *u8, p24, ctr) 603 604 var le_nn: i64 = 0 605 if nx_f64_lt(n2, n2) == 1 { le_nn = 1 } 606 if nx_f64_eq(n2, n2) == 1 { le_nn = 1 } 607 let p25: i64 = f6_pcase(cc, F6_PSLOT + 5, "-2.0 <= -2.0 must be TRUE\x00" as *u8, 608 mt, mt, "<=\x00" as *u8, le_nn, envp, devnull, r, builds) 609 gv_check("le-of-equal-negative-doubles-is-true\x00" as *u8, p25, ctr) 610 611 // ---- LAYER 5b: IEEE UNORDERED (LN38) ------------------------------------ 612 // Under the ordering-key compare a NaN equalled itself and sorted below every number 613 // (fixture exit 103 on the pre-fix compiler). The fixture returns the failing-check 614 // bitmask, so the exit IS the diagnosis; checks 7-9 are ordered controls. 615 let nsrc: *u8 = f6_nan_src() 616 f6_run_src(cc, F6_SLOT_NAN, nsrc, f6_slen(nsrc), envp, devnull, r) 617 builds[F6_SLOT_NAN] = r[0] 618 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_NAN) 619 gv_puts(" build=" as *u8); gv_num(r[0]) 620 gv_puts(" exit=" as *u8); gv_num(r[2]) 621 gv_puts(" failing-check-bitmask (0 = every IEEE unordered rule holds) LN38 NaN fixture 622" as *u8) 623 gv_check("ieee-unordered-nan-every-ordered-predicate-false-and-ne-true (LN38 fixture exits 0)\x00" as *u8, ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 624 gv_check("neg-control-nan-fixture-ordered-controls-block-an-always-false-compare (bits 7-9 clear)\x00" as *u8, ((r[0] == 0) & ((r[2] / 128) == 0)) as i64, ctr) 625 626 // ---- LAYER 6: DECIMAL LITERALS (LN36 exponent form, LN40 long literals, LN39 f64 consts) ---- 627 // The oracle for a literal is ONE IEEE operation on exactly-representable operands (correctly rounded 628 // by IEEE-754), so each check compares bits, never approximations. 629 let lsrc: *u8 = f6_lit_src() 630 f6_run_src(cc, F6_SLOT_LIT, lsrc, f6_slen(lsrc), envp, devnull, r) 631 builds[F6_SLOT_LIT] = r[0] 632 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_LIT); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" exit=" as *u8); gv_num(r[2]) 633 gv_puts(" failing-check-bitmask LN36/LN40 literal fixture\n" as *u8) 634 gv_check("exponent-and-long-literals-pack-to-the-correctly-rounded-bits (LN36/LN40 fixture exits 0)\x00" as *u8, 635 ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 636 let csrc: *u8 = f6_const_src() 637 f6_run_src(cc, F6_SLOT_CONST, csrc, f6_slen(csrc), envp, devnull, r) 638 builds[F6_SLOT_CONST] = r[0] 639 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_CONST); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" exit=" as *u8); gv_num(r[2]) 640 gv_puts(" failing-check-bitmask LN39 f64 const fixture\n" as *u8) 641 gv_check("f64-const-expressions-fold-to-the-bits-the-program-computes-at-run-time (LN39 fixture exits 0)\x00" as *u8, 642 ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 643 let osrc: *u8 = f6_iover_src() 644 f6_run_src(cc, F6_SLOT_IOVER, osrc, f6_slen(osrc), envp, devnull, r) 645 let onamed: i64 = f6_file_has(f6_tmp(F6_SLOT_IOVER, ".cclog\x00" as *u8), "does not fit in 64 bits\x00" as *u8) 646 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_IOVER); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" named=" as *u8); gv_num(onamed) 647 gv_puts(" LN40 integer literal past i64 must be refused by name\n" as *u8) 648 gv_check("neg-control-integer-literal-past-i64-is-REFUSED-by-name-never-wrapped (LN40)\x00" as *u8, 649 ((r[0] != 0) & (onamed == 1)) as i64, ctr) 650 let esrc: *u8 = f6_badexp_src() 651 f6_run_src(cc, F6_SLOT_EXP, esrc, f6_slen(esrc), envp, devnull, r) 652 let enamed: i64 = f6_file_has(f6_tmp(F6_SLOT_EXP, ".cclog\x00" as *u8), "exponent of this number has no digits\x00" as *u8) 653 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_EXP); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" named=" as *u8); gv_num(enamed) 654 gv_puts(" LN36 malformed exponent must be refused by name\n" as *u8) 655 gv_check("neg-control-malformed-exponent-is-REFUSED-by-name (LN36)\x00" as *u8, 656 ((r[0] != 0) & (enamed == 1)) as i64, ctr) 657 let msrc: *u8 = f6_imax_src() 658 f6_run_src(cc, F6_SLOT_IMAX, msrc, f6_slen(msrc), envp, devnull, r) 659 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_IMAX); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" exit=" as *u8); gv_num(r[2]) 660 gv_puts(" positive control: the largest i64 literal still compiles and compares equal\n" as *u8) 661 gv_check("positive-control-largest-i64-literal-9223372036854775807-still-compiles-so-the-refusal-is-not-blanket\x00" as *u8, 662 ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 663 let usrc: *u8 = f6_u64_src() 664 f6_run_src(cc, F6_SLOT_U64, usrc, f6_slen(usrc), envp, devnull, r) 665 gv_puts("F64GATE case=" as *u8); gv_num(F6_SLOT_U64); gv_puts(" build=" as *u8); gv_num(r[0]); gv_puts(" exit=" as *u8); gv_num(r[2]) 666 gv_puts(" positive control: decimal literals in [2^63, 2^64) keep their unsigned bit pattern\n" as *u8) 667 gv_check("positive-control-u64-range-decimal-literal-keeps-its-bit-pattern-so-FNV-and-all-ones-constants-still-compile\x00" as *u8, 668 ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 669 670 671 // ---- ANTI-VACUITY CONTROLS -------------------------------------------- 672 // The expectations must be operator-specific, or a fixture that ignored the 673 // operator entirely could satisfy every value tooth with one shared answer. 674 // Full pairwise scan over all ten expectations, bound to its own denominator. 675 let wl: *i64 = sys_mmap(8 * 10) as *i64 676 wl[0] = w_add_x; wl[1] = w_sub_x; wl[2] = w_mul_x; wl[3] = w_div_x; wl[4] = w_add_r 677 wl[5] = w_sub_r; wl[6] = w_mul_r; wl[7] = w_div_r; wl[8] = w_neg; wl[9] = w_negres 678 var pairs: i64 = 0 679 var collide: i64 = 0 680 var wi: i64 = 0 681 while wi < 10 { 682 var wj: i64 = wi + 1 683 while wj < 10 { 684 pairs = pairs + 1 685 if wl[wi] == wl[wj] { collide = collide + 1 } 686 wj = wj + 1 687 } 688 wi = wi + 1 689 } 690 gv_puts("F64GATE expectations add_x=" as *u8); gv_num(w_add_x) 691 gv_puts(" sub_x=" as *u8); gv_num(w_sub_x) 692 gv_puts(" mul_x=" as *u8); gv_num(w_mul_x) 693 gv_puts(" div_x=" as *u8); gv_num(w_div_x) 694 gv_puts(" add_r=" as *u8); gv_num(w_add_r) 695 gv_puts(" sub_r=" as *u8); gv_num(w_sub_r) 696 gv_puts(" mul_r=" as *u8); gv_num(w_mul_r) 697 gv_puts(" div_r=" as *u8); gv_num(w_div_r) 698 gv_puts(" neg=" as *u8); gv_num(w_neg) 699 gv_puts(" negres=" as *u8); gv_num(w_negres) 700 gv_puts(" pairs=" as *u8); gv_num(pairs) 701 gv_puts(" collisions=" as *u8); gv_num(collide) 702 gv_puts("\n" as *u8) 703 gv_check("neg-control-all-45-expectation-pairs-are-distinct-not-one-shared-answer\x00" as *u8, 704 ((pairs == 45) & (collide == 0)) as i64, ctr) 705 706 // The rounding cases must not be secretly exact, or layer 3 is layer 2 twice. 707 var rounded: i64 = 1 708 if w_add_r != fp53 { rounded = 0 } 709 gv_check("neg-control-2pow53-plus-one-really-does-round-back-to-2pow53\x00" as *u8, rounded, ctr) 710 711 // THE ORDERING CASES MUST BE THE DISCRIMINATING ONES. A signed integer compare 712 // of these two bit patterns must DISAGREE with IEEE, or p21/p22/p23 would pass 713 // on a backend that never learned about floats at all. 714 var intsays: i64 = 0 715 if n1 < n2 { intsays = 1 } 716 gv_puts("F64GATE ordering minus_one_lt_minus_two: signed-int-compare=" as *u8); gv_num(intsays) 717 gv_puts(" ieee=" as *u8); gv_num(nx_f64_lt(n1, n2)) 718 gv_puts("\n" as *u8) 719 gv_check("neg-control-chosen-negative-pair-is-exactly-where-integer-compare-fails\x00" as *u8, 720 ((intsays == 1) & (nx_f64_lt(n1, n2) == 0)) as i64, ctr) 721 722 // ---- THE RUNG'S OWN DONE-RULE, ON ITS OWN FILE ------------------------- 723 // The board states LN16 as "nx_probe_float run-exit=0". Re-creating that program 724 // here would prove something about MY copy; compiling the tree's actual file is 725 // the only thing that discharges the rule as written. 726 f6_build_run(cc, F6_SLOT_PROBE, "runtime/nx_probe_float.nx\x00" as *u8, envp, devnull, r) 727 builds[F6_SLOT_PROBE] = r[0] 728 f6_report(F6_SLOT_PROBE, "runtime/nx_probe_float.nx (the board's stated done-rule)\x00" as *u8, 0, 1, r) 729 gv_check("done-rule-runtime-nx_probe_float-nx-compiles-and-exits-zero\x00" as *u8, 730 ((r[0] == 0) & (r[2] == 0)) as i64, ctr) 731 732 // ---- POSITIVE CONTROL -------------------------------------------------- 733 // A GUARD THAT REFUSES EVERYTHING PASSES EVERY NEGATIVE TEST, and most teeth 734 // above are satisfied by a process exiting 0. Without an ordinary integer 735 // program that must simply build and run, a dead compiler, a NOEXEC scratch 736 // directory or a missing assembler would be read as a verdict about f64. 737 let ilp: *i64 = sys_mmap(F6_DIGITS_MAX) as *i64 738 let isrc: *u8 = f6_int_src(ilp) 739 f6_run_src(cc, F6_SLOT_INT_A, isrc, ilp[0], envp, devnull, r) 740 builds[F6_SLOT_INT_A] = r[0] 741 f6_report(F6_SLOT_INT_A, "integer positive control\x00" as *u8, f6_int_sum(), 1, r) 742 let pc: i64 = ((r[0] == 0) & (r[2] == 0)) as i64 743 gv_check("positive-control-ordinary-integer-program-builds-and-runs-clean\x00" as *u8, pc, ctr) 744 745 // ---- INTEGER NEUTRALITY ------------------------------------------------ 746 // This is a SHARED backend: f64 work lands in the same emitter integer code 747 // rides through. Compiling the SAME source file with the subject and with a 748 // reference and byte-comparing the emitted assembly is the strongest available 749 // statement that the integer path did not move. Without a reference the axis 750 // is UNOBSERVABLE and says so; it never scores a pass it did not earn. 751 var refok: i64 = 0 752 if refcc != (0 as *u8) { if cb_fsize(refcc) > 0 { refok = 1 } } 753 if gv_need("reference-compiler-for-integer-neutrality\x00" as *u8, refok, ctr) == 1 { 754 let p_isrc: *u8 = f6_tmp(F6_SLOT_INT_A, ".nx\x00" as *u8) 755 let s_b: *u8 = f6_tmp(F6_SLOT_INT_B, ".s\x00" as *u8) 756 let l_b: i64 = sys_openat_wr(f6_tmp(F6_SLOT_INT_B, ".cclog\x00" as *u8), F6_MODE_0644) 757 let brc_b: i64 = cb_build(refcc, p_isrc, s_b, f6_bld(F6_SLOT_INT_B, ".elf\x00" as *u8), 758 envp, l_b, f6_bld(F6_SLOT_INT_B, ".tmpelf\x00" as *u8), 759 f6_tmp(F6_SLOT_INT_B, ".asmlog\x00" as *u8)) 760 sys_close(l_b) 761 let s_a: *u8 = f6_tmp(F6_SLOT_INT_A, ".s\x00" as *u8) 762 let same: i64 = cb_files_equal(s_a, s_b) 763 gv_puts("F64GATE neutrality refcc=" as *u8); gv_puts(refcc) 764 gv_puts(" refbytes=" as *u8); gv_num(cb_fsize(refcc)) 765 gv_puts(" refbuild=" as *u8); gv_num(brc_b) 766 gv_puts(" asmbytes_subject=" as *u8); gv_num(cb_fsize(s_a)) 767 gv_puts(" asmbytes_reference=" as *u8); gv_num(cb_fsize(s_b)) 768 gv_puts(" identical=" as *u8); gv_num(same) 769 gv_puts("\n" as *u8) 770 gv_check("integer-path-emits-byte-identical-assembly-under-subject-and-reference\x00" as *u8, 771 ((brc_b == 0) & (same == 1)) as i64, ctr) 772 } 773 774 // ---- NON-VACUITY ------------------------------------------------------- 775 // ASSERT THE FIXTURE REACHED THE CONDITION BEFORE ASSERTING THE OUTCOME. Every 776 // fixture here is supposed to compile; if one silently did not, its tooth 777 // failed for a reason that has nothing to do with f64, and a red for the wrong 778 // reason is not evidence. Counted, so an empty scan cannot pass. 779 var allbuilt: i64 = 1 780 var checked: i64 = 0 781 var bi: i64 = 0 782 while bi < F6_ACASES { if builds[bi] != 0 { allbuilt = 0 } checked = checked + 1; bi = bi + 1 } 783 var pi: i64 = 0 784 while pi < F6_PCASES { 785 if builds[F6_PSLOT + pi] != 0 { allbuilt = 0 } 786 checked = checked + 1 787 pi = pi + 1 788 } 789 if builds[F6_SLOT_PROBE] != 0 { allbuilt = 0 } 790 checked = checked + 1 791 if builds[F6_SLOT_INT_A] != 0 { allbuilt = 0 } 792 checked = checked + 1 793 gv_puts("F64GATE fixtures_checked=" as *u8); gv_num(checked) 794 gv_puts(" all_built=" as *u8); gv_num(allbuilt) 795 gv_puts("\n" as *u8) 796 gv_check("fixture-reached-condition-every-must-compile-fixture-actually-built\x00" as *u8, 797 ((allbuilt == 1) & (checked == (F6_ACASES + F6_PCASES + 2))) as i64, ctr) 798 799 return gv_verdict("F64-GATE\x00" as *u8, ctr, 800 "LN16: binary64 add sub mul div and ordering agree with independent software IEEE-754, exactly and under rounding; NaN operands make every ordered predicate false and only != true, per IEEE-754 (LN38); decimal literals with exponents or beyond 18 digits pack to the correctly rounded bits and f64 consts fold to run-time bits (LN36 LN39 LN40)\x00" as *u8) 801}