code wiki / _hdl_build / nx_mulchain.nx

nx_mulchain.nx source

↩ module page · 140 lines · 5846 B

1// nx_mulchain.nx -- a FASTER, deeper superoptimizer for multiply-by-constant: search the 2// MULTIPLIER space (addition/subtraction/shift chains) instead of evaluating raw programs 3// on many inputs. Every slot holds a multiplier k (meaning k*x); ops grow the set: 4// ADD(k1,k2)=k1+k2, SUB(k1,k2)=k1-k2, SHL(k,s)=k<<s. Iterative deepening over op-count 5// returns the SHORTEST chain to C (provably minimal op count). Magnitude pruning (|k| <= 6// 4|C|) keeps it fast and lets it reach constants the raw-op search timed out on -- the 7// team escalating its OWN search to close its OWN gaps. Output is the emitter's slot form 8// (0=x, op t writes slot t+3), so se_emit_full emits it directly. license_tier: ORIGINAL 9// 10// Refs: Bernstein "Multiplication by integer constants" 1986; Lefevre, INRIA RR-4192 2001; 11// addition-chain / shift-add literature. Op constants reuse SO_ADD/SO_SUB/SO_SHL. 12 13import "nx_superopt_opt.nx" // SO_ADD(0), SO_SUB(1), SO_SHL(3) 14 15func mc_abs(v: i64) -> i64 { if v < 0 { return 0 - v } return v } 16 17// k-th available operand slot at a position: 0 -> x (slot 0); k>=1 -> computed slot k+2. 18func mc_avslot(k: i64) -> i64 { if k == 0 { return 0 } return k + 2 } 19 20// recursive iterative-deepening enumeration; sv[] holds the multiplier per emitter slot. 21func mc_enum(op: *i64, a: *i64, b: *i64, t: i64, L: i64, sv: *i64, C: i64, bound: i64, maxsh: i64) -> i64 { 22 if t == L { if sv[L + 2] == C { return 1 } return 0 } 23 let nav: i64 = t + 1 24 let rslot: i64 = t + 3 25 // ADD (i <= j, commutative) 26 var i: i64 = 0 27 while i < nav { 28 var j: i64 = i 29 while j < nav { 30 let nv: i64 = sv[mc_avslot(i)] + sv[mc_avslot(j)] 31 if mc_abs(nv) <= bound { 32 sv[rslot] = nv; op[t] = SO_ADD; a[t] = mc_avslot(i); b[t] = mc_avslot(j) 33 if mc_enum(op, a, b, t + 1, L, sv, C, bound, maxsh) == 1 { return 1 } 34 } 35 j = j + 1 36 } 37 i = i + 1 38 } 39 // SUB (ordered, both directions) 40 i = 0 41 while i < nav { 42 var j: i64 = 0 43 while j < nav { 44 if i != j { 45 let nv: i64 = sv[mc_avslot(i)] - sv[mc_avslot(j)] 46 if mc_abs(nv) <= bound { 47 sv[rslot] = nv; op[t] = SO_SUB; a[t] = mc_avslot(i); b[t] = mc_avslot(j) 48 if mc_enum(op, a, b, t + 1, L, sv, C, bound, maxsh) == 1 { return 1 } 49 } 50 } 51 j = j + 1 52 } 53 i = i + 1 54 } 55 // SHL (slot i by sh = 1..maxsh) 56 i = 0 57 while i < nav { 58 var sh: i64 = 1 59 while sh <= maxsh { 60 let nv: i64 = sv[mc_avslot(i)] << sh 61 if mc_abs(nv) <= bound { 62 sv[rslot] = nv; op[t] = SO_SHL; a[t] = mc_avslot(i); b[t] = sh 63 if mc_enum(op, a, b, t + 1, L, sv, C, bound, maxsh) == 1 { return 1 } 64 } 65 sh = sh + 1 66 } 67 i = i + 1 68 } 69 // LEA: base + index*scale, scale in {2,4,8} (one x86 lea; base==index gives *3/*5/*9) 70 i = 0 71 while i < nav { 72 var j2: i64 = 0 73 while j2 < nav { 74 var si: i64 = 0 75 while si < 3 { 76 var sc: i64 = 2 77 if si == 1 { sc = 4 } 78 if si == 2 { sc = 8 } 79 let nv: i64 = sv[mc_avslot(i)] + sv[mc_avslot(j2)] * sc 80 if mc_abs(nv) <= bound { 81 var oo: i64 = SO_LEA2 82 if sc == 4 { oo = SO_LEA4 } 83 if sc == 8 { oo = SO_LEA8 } 84 sv[rslot] = nv; op[t] = oo; a[t] = mc_avslot(i); b[t] = mc_avslot(j2) 85 if mc_enum(op, a, b, t + 1, L, sv, C, bound, maxsh) == 1 { return 1 } 86 } 87 si = si + 1 88 } 89 j2 = j2 + 1 90 } 91 i = i + 1 92 } 93 return 0 94} 95 96// find the SHORTEST shift-add chain for x*C, length 1..maxL. Writes op/a/b (emitter form), 97// returns L (op count) or 0 if not found within maxL. 98func mulchain_find(C: i64, maxL: i64, op: *i64, a: *i64, b: *i64) -> i64 { 99 let sv: *i64 = sys_mmap(8 * (maxL + 6)) as *i64 100 sv[0] = 1 // x's multiplier is 1 101 var bound: i64 = mc_abs(C) * 4 + 8 102 var L: i64 = 1 103 while L <= maxL { 104 if mc_enum(op, a, b, 0, L, sv, C, bound, 16) == 1 { return L } 105 L = L + 1 106 } 107 return 0 108} 109 110// EVALUATE a found chain program on input x (the Engineer's independent oracle, so a consumer 111// can VERIFY the generator's output instead of trusting it). slot 0 = x, op t -> slot t+3. 112func mc_eval(op: *i64, a: *i64, b: *i64, L: i64, x: i64) -> i64 { 113 let sv: *i64 = sys_mmap(8 * (L + 6)) as *i64 114 sv[0] = x 115 var t: i64 = 0 116 while t < L { 117 let o: i64 = op[t] 118 let va: i64 = sv[a[t]] 119 if o == SO_ADD { sv[t + 3] = va + sv[b[t]] } 120 if o == SO_SUB { sv[t + 3] = va - sv[b[t]] } 121 if o == SO_SHL { sv[t + 3] = va << b[t] } 122 if o == SO_LEA2 { sv[t + 3] = va + sv[b[t]] * 2 } 123 if o == SO_LEA4 { sv[t + 3] = va + sv[b[t]] * 4 } 124 if o == SO_LEA8 { sv[t + 3] = va + sv[b[t]] * 8 } 125 t = t + 1 126 } 127 return sv[L + 2] 128} 129 130// the trustworthy work function: get a chain, then VERIFY it actually computes c*x on 131// independent inputs. returns the op count if VERIFIED, or 0-1 if not found / not verified 132// (so the caller never banks an unchecked result -- the Engineer's "verify, don't trust"). 133func mulchain_verified(c: i64, maxL: i64, op: *i64, a: *i64, b: *i64) -> i64 { 134 let L: i64 = mulchain_find(c, maxL, op, a, b) 135 if L <= 0 { return 0 - 1 } // not found (covers the 0-on-fail contract) 136 if mc_eval(op, a, b, L, 7) != c * 7 { return 0 - 1 } 137 if mc_eval(op, a, b, L, 131) != c * 131 { return 0 - 1 } 138 if mc_eval(op, a, b, L, 0 - 9) != c * (0 - 9) { return 0 - 1 } 139 return L 140}