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}