code wiki / _hdl_build / nx_superopt_opt.nx
nx_superopt_opt.nx source
↩ module page · 119 lines · 4308 B
1// nx_superopt_opt.nx -- a COST-OPTIMAL (exhaustive-shortest) superoptimizer over the
2// full integer/bit op set {add,sub,mul,shl,sar,xor,and,or}. Given a function as
3// input/output examples it returns the SHORTEST straight-line program that matches,
4// searching length 1 then 2 then 3 -- so the result is provably minimal within the
5// bound (the superoptimizer's defining property: optimality, which heuristic
6// compilers sacrifice). Verified over FULL-RANGE 64-bit inputs (sign bit + negatives),
7// not toy ranges. This is the engine for an honest 1:1 board vs gcc -O2.
8//
9// Refs: Massalin, "Superoptimizer," AFIPS/ASPLOS 1987; Warren, Hacker's Delight 2e
10// (the branchless bit-tricks); GNU superopt. Slots: 0=x, 1=0, 2=1, then computed.
11
12import "nx_syscalls.nx"
13
14const SO_ADD: i64 = 0
15const SO_SUB: i64 = 1
16const SO_MUL: i64 = 2
17const SO_SHL: i64 = 3
18const SO_SAR: i64 = 4
19const SO_XOR: i64 = 5
20const SO_AND: i64 = 6
21const SO_OR: i64 = 7
22// fused x86 lea = base + index*scale, scale in {2,4,8} (one instruction; base==index gives
23// *3/*5/*9). Used by the multiplier-chain generator + the emitter to match gcc's lea.
24const SO_LEA2: i64 = 9
25const SO_LEA4: i64 = 10
26const SO_LEA8: i64 = 11
27
28func so_is_shift(o: i64) -> i64 { if o == SO_SHL { return 1 } if o == SO_SAR { return 1 } return 0 }
29
30// shift immediates worth trying (small + the sign positions).
31func so_imm(k: i64) -> i64 {
32 if k == 0 { return 1 }
33 if k == 1 { return 2 }
34 if k == 2 { return 3 }
35 if k == 3 { return 4 }
36 if k == 4 { return 8 }
37 if k == 5 { return 16 }
38 if k == 6 { return 31 }
39 if k == 7 { return 32 }
40 return 63
41}
42const SO_NIMM: i64 = 9
43
44// s = caller scratch slot buffer (size >= L+4), reused across millions of calls
45// (allocating here would mmap per eval and is fatally slow).
46func so_eval(op: *i64, a: *i64, b: *i64, L: i64, x: i64, s: *i64) -> i64 {
47 s[0] = x; s[1] = 0; s[2] = 1
48 var t: i64 = 0
49 while t < L {
50 let o: i64 = op[t]
51 let va: i64 = s[a[t]]
52 var r: i64 = 0
53 if o == SO_ADD { r = va + s[b[t]] }
54 if o == SO_SUB { r = va - s[b[t]] }
55 if o == SO_MUL { r = va * s[b[t]] }
56 if o == SO_SHL { r = va << b[t] }
57 if o == SO_SAR { r = va >> b[t] }
58 if o == SO_XOR { r = va ^ s[b[t]] }
59 if o == SO_AND { r = va & s[b[t]] }
60 if o == SO_OR { r = va | s[b[t]] }
61 s[t + 3] = r
62 t = t + 1
63 }
64 return s[L + 2]
65}
66
67func so_matches(op: *i64, a: *i64, b: *i64, L: i64, ex_x: *i64, ex_y: *i64, nex: i64, s: *i64) -> i64 {
68 var k: i64 = 0
69 while k < nex { if so_eval(op, a, b, L, ex_x[k], s) != ex_y[k] { return 0 } k = k + 1 }
70 return 1
71}
72
73// recursive exhaustive enumeration of program positions t..L-1; nslot = slots
74// available at this position (grows by 1 per instruction). Returns 1 on a match.
75func so_enum(op: *i64, a: *i64, b: *i64, t: i64, L: i64, nslot: i64,
76 ex_x: *i64, ex_y: *i64, nex: i64, s: *i64) -> i64 {
77 if t == L {
78 if so_matches(op, a, b, L, ex_x, ex_y, nex, s) == 1 { return 1 }
79 return 0
80 }
81 var o: i64 = 0
82 while o < 8 {
83 op[t] = o
84 var av: i64 = 0
85 while av < nslot {
86 a[t] = av
87 if so_is_shift(o) == 1 {
88 var ki: i64 = 0
89 while ki < SO_NIMM {
90 b[t] = so_imm(ki)
91 if so_enum(op, a, b, t + 1, L, nslot + 1, ex_x, ex_y, nex, s) == 1 { return 1 }
92 ki = ki + 1
93 }
94 } else {
95 var bv: i64 = 0
96 while bv < nslot {
97 b[t] = bv
98 if so_enum(op, a, b, t + 1, L, nslot + 1, ex_x, ex_y, nex, s) == 1 { return 1 }
99 bv = bv + 1
100 }
101 }
102 av = av + 1
103 }
104 o = o + 1
105 }
106 return 0
107}
108
109// find the SHORTEST program (length 1..maxL) matching the examples. Returns L (op
110// count), or 0 if none. Writes the program into op/a/b. Allocates ONE scratch.
111func so_find(ex_x: *i64, ex_y: *i64, nex: i64, op: *i64, a: *i64, b: *i64, maxL: i64) -> i64 {
112 let s: *i64 = sys_mmap(8 * (maxL + 6))
113 var L: i64 = 1
114 while L <= maxL {
115 if so_enum(op, a, b, 0, L, 3, ex_x, ex_y, nex, s) == 1 { return L }
116 L = L + 1
117 }
118 return 0
119}