code wiki / _hdl_build / nx_js_binopfuse_gate.nx
nx_js_binopfuse_gate.nx source
↩ module page · 119 lines · 7719 B
1// nx_js_binopfuse_gate.nx -- DIFFERENTIAL correctness gate for the JIT const-operand BINOP fusion peephole
2// (`BC_PUSH k ; BC_BINOP op` -> fold the int const k into an x86 IMMEDIATE, skipping the const's stack push
3// AND its tag-check). Fusion touches ONLY the VM/JIT tier, so the STRONGEST check is: for the same source the
4// JIT (compile_run) and the tree-walker (js_run_source = the parity oracle) must produce a BIT-IDENTICAL
5// result. A fusion miscompile shows up as tree != jit. Covers every fusable op (+,-,*,%,<,<=,>,>=,==,!=) with
6// an int const RHS inside a JIT-compiled loop (so fusion actually fires), the float-LHS and string-LHS
7// FALLBACKS (materialize-const + jit_slow_binop), MOD-by-const, a >2^31 const that must NOT fuse, and a
8// non-fusable op (/). Each case is ALSO anchored to a V8-computed value (catches both-tiers-wrong).
9// expect_exit: 0 license_tier: ORIGINAL
10import "nx_js_vm.nx"
11func gw(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
12// stringify a result cell [tag,pay] into buf; return length. int->decimal, float->ev_f64_str, str->bytes, bool.
13func g_rstr(cell: *i64, buf: *u8) -> i64 {
14 if cell[0] == VAL_NUM {
15 var m: i64 = cell[1]
16 if m == 0 { buf[0] = 48 as u8; return 1 }
17 var neg: i64 = 0
18 if m < 0 { neg = 1; m = 0 - m }
19 let t: *u8 = sys_mmap(24)
20 var k: i64 = 0
21 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
22 var p: i64 = 0
23 if neg == 1 { buf[0] = 45 as u8; p = 1 }
24 var j: i64 = 0
25 while j < k { buf[p] = t[k - 1 - j]; p = p + 1; j = j + 1 }
26 return p
27 }
28 if cell[0] == VAL_FLOAT {
29 let s: *i64 = ev_f64_str(cell[1])
30 let b: *u8 = ev_str_bytes(s)
31 let n: i64 = ev_str_len(s)
32 var i: i64 = 0
33 while i < n { buf[i] = b[i]; i = i + 1 }
34 return n
35 }
36 if cell[0] == VAL_STR {
37 let b: *u8 = ev_str_bytes((cell[1]) as *i64)
38 let n: i64 = ev_str_len((cell[1]) as *i64)
39 var i: i64 = 0
40 while i < n { buf[i] = b[i]; i = i + 1 }
41 return n
42 }
43 if cell[0] == VAL_BOOL {
44 if cell[1] == 0 { buf[0]=102 as u8;buf[1]=97 as u8;buf[2]=108 as u8;buf[3]=115 as u8;buf[4]=101 as u8; return 5 }
45 buf[0]=116 as u8;buf[1]=114 as u8;buf[2]=117 as u8;buf[3]=101 as u8; return 4
46 }
47 buf[0] = 63 as u8
48 return 1
49}
50func g_bslen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
51func g_eq(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { if an != bn { return 0 } var i: i64=0; while i<an { if (a[i]&0xff)!=(b[i]&0xff){return 0} i=i+1 } return 1 }
52// run src on BOTH tiers + assert tree==jit (core differential) AND jit==expect (V8 anchor). tal[0]=pass tal[1]=total.
53func dcheck(src: *u8, expect: *u8, tal: *i64) -> i64 {
54 tal[1] = tal[1] + 1
55 let slen: i64 = g_bslen(src)
56 let o1: *i64 = sys_mmap(16) as *i64
57 let o2: *i64 = sys_mmap(16) as *i64
58 let rc1: i64 = js_run_source(src, slen, o1) // tree-walker (parity oracle)
59 let rc2: i64 = compile_run(src, o2) // VM/JIT (fusion under test)
60 let b1: *u8 = sys_mmap(128)
61 let b2: *u8 = sys_mmap(128)
62 let n1: i64 = g_rstr(o1, b1)
63 let n2: i64 = g_rstr(o2, b2)
64 let en: i64 = g_bslen(expect)
65 var diff: i64 = 1
66 if g_eq(b1, n1, b2, n2) == 1 { diff = 0 } // 0 = tree==jit (good)
67 var anok: i64 = 0
68 if g_eq(b2, n2, expect, en) == 1 { anok = 1 }
69 var ok: i64 = 0
70 if diff == 0 { if anok == 1 { if rc1 == 0 { if rc2 == 0 { ok = 1 } } } }
71 gw(" ")
72 if ok == 1 { gw("OK " as *u8); tal[0] = tal[0] + 1 }
73 else { if diff == 1 { gw("MISCOMPILE" as *u8) } else { gw("want-diff " as *u8) } }
74 gw(" tree='"); sys_write(1, b1, n1); gw("' jit='"); sys_write(1, b2, n2); gw("' want='"); gw(expect); gw("' <= ")
75 sys_write(1, src, slen)
76 gw("\n" as *u8)
77 return 0
78}
79func main(argc: i64, argv: *i64) -> i64 {
80 gw("=== nx_js_binopfuse_gate: JIT const-BINOP fusion == tree-walker (differential) + V8 anchors ===\n" as *u8)
81 let tal: *i64 = sys_mmap(16) as *i64
82 tal[0]=0; tal[1]=0
83 // (A) every fusable op, int const RHS, inside a JIT loop -> fusion fires on the fast (int) path:
84 dcheck("var s=0;var i=0;while(i<1000){s=s+(i+3);i=i+1;}s" as *u8, "502500" as *u8, tal) // ADD
85 dcheck("var s=0;var i=0;while(i<1000){s=s+(i-3);i=i+1;}s" as *u8, "496500" as *u8, tal) // SUB
86 dcheck("var s=0;var i=0;while(i<1000){s=s+(i*3);i=i+1;}s" as *u8, "1498500" as *u8, tal) // MUL
87 dcheck("var s=0;var i=0;while(i<1000){s=s+(i%7);i=i+1;}s" as *u8, "2997" as *u8, tal) // MOD (k!=0)
88 dcheck("var c=0;var i=0;while(i<1000){if(i<500){c=c+1;}i=i+1;}c" as *u8, "500" as *u8, tal) // LT
89 dcheck("var c=0;var i=0;while(i<1000){if(i<=500){c=c+1;}i=i+1;}c" as *u8, "501" as *u8, tal) // LE
90 dcheck("var c=0;var i=0;while(i<1000){if(i>500){c=c+1;}i=i+1;}c" as *u8, "499" as *u8, tal) // GT
91 dcheck("var c=0;var i=0;while(i<1000){if(i>=500){c=c+1;}i=i+1;}c" as *u8, "500" as *u8, tal) // GE
92 dcheck("var c=0;var i=0;while(i<1000){if(i==500){c=c+1;}i=i+1;}c" as *u8, "1" as *u8, tal) // EQ
93 dcheck("var c=0;var i=0;while(i<1000){if(i!=500){c=c+1;}i=i+1;}c" as *u8, "999" as *u8, tal) // NE
94 // (B) FALLBACK: fused op but LHS is a float/string at runtime -> materialize-const + jit_slow_binop:
95 dcheck("var x=2.5;var r=0;var i=0;while(i<100){r=x+7;i=i+1;}r" as *u8, "9.5" as *u8, tal) // float-LHS ADD
96 dcheck("var x=2.5;var r=0;var i=0;while(i<100){r=x-1;i=i+1;}r" as *u8, "1.5" as *u8, tal) // float-LHS SUB
97 dcheck("var s='ab';var r=0;var i=0;while(i<5){r=s+3;i=i+1;}r" as *u8, "ab3" as *u8, tal) // string-LHS concat
98 // (C) GUARDS: const > 2^31 must NOT fuse (imm32 range) + non-fusable op (/) still correct:
99 dcheck("var i=5;var j=i+3000000000;j" as *u8, "3000000005" as *u8, tal) // big const, no fuse
100 dcheck("var i=1000000;var j=i*1000;j" as *u8, "1000000000" as *u8, tal) // fused MUL, big LHS
101 dcheck("var i=13;var j=i%5;j" as *u8, "3" as *u8, tal) // fused MOD scalar
102 // (E) STRENGTH-REDUCED const-% on the register tier (magic multiply-shift; d=7 exercises the wrapped-M
103 // add-correction, d=8 a power of 2, d=1 the always-0 case, and (0-i)%7 NEGATIVE dividends -> sign follows
104 // the dividend). All bit-differential vs the tree-walker.
105 dcheck("var s=0;var i=0;while(i<100){s=s+(i%8);i=i+1;}s" as *u8, "342" as *u8, tal) // %8 pow2
106 dcheck("var s=0;var i=0;while(i<50){s=s+(i%3);i=i+1;}s" as *u8, "49" as *u8, tal) // %3
107 dcheck("var s=0;var i=0;while(i<10){s=s+(i%1);i=i+1;}s" as *u8, "0" as *u8, tal) // %1 -> 0
108 dcheck("var s=0;var i=0;while(i<100){s=s+((0-i)%7);i=i+1;}s" as *u8, "-295" as *u8, tal) // negative dividend
109 gw(" --- ")
110 var pn: i64 = tal[0]
111 if pn == 0 { sys_write(1, "0" as *u8, 1) } else { let t: *u8 = sys_mmap(8); var m: i64=pn; var k: i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var q: i64=k-1; while q>=0{sys_write(1,((t as i64)+q) as *u8,1);q=q-1} }
112 gw("/")
113 var tn: i64 = tal[1]
114 if tn == 0 { sys_write(1, "0" as *u8, 1) } else { let t: *u8 = sys_mmap(8); var m: i64=tn; var k: i64=0; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1} var q: i64=k-1; while q>=0{sys_write(1,((t as i64)+q) as *u8,1);q=q-1} }
115 gw(" passed ---\n" as *u8)
116 if tal[0] == tal[1] { gw("=== GREEN: JIT const-BINOP fusion is BIT-IDENTICAL to the tree-walker on all cases ===\n" as *u8); return 0 }
117 gw("=== RED: a fusion case diverged from the tree-walker (see MISCOMPILE rows) ===\n" as *u8)
118 return 1
119}