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}