code wiki / _hdl_build / nx_superopt_strength.nx
nx_superopt_strength.nx source
↩ module page · 98 lines · 3775 B
1// nx_superopt_strength.nx -- a GENERATOR: it AUTHORS strength-reduction lowerings
2// for `x * C` by SEARCH, not by me hand-writing each rule. Given a constant C, it
3// enumerates candidate implementations -- a single shift (x<<a), or a shift-op pair
4// ((x<<a) + (x<<b) / (x<<a) - (x<<b)), or the fallback multiply -- VERIFIES each is
5// 1:1 equal to x*C over many inputs, scores each by the measured cost model
6// (mul > shift+add, per nx_measure_cost), and RETURNS the cheapest verified one.
7//
8// THIS is a building capability: the team discovers `x*7 = (x<<3)-x`, `x*9 =
9// (x<<3)+x`, `x*1024 = x<<10` ITSELF -- I wrote the searcher; the search wrote the
10// answers. It is the Generator leg the codegen loop consumes (each authored
11// lowering is a verified, cheaper codegen rewrite). Refs: Warren, Hacker's Delight
12// 2e ch.8 (strength reduction); the e-graph superopt idea (egg, Willsey POPL 2021).
13
14import "nx_syscalls.nx"
15const SO_MAGIC_19088743: i64 = 19088743
16const SO_MAGIC_6364136223846793005: i64 = 6364136223846793005
17const SO_MAGIC_1442695040888963407: i64 = 1442695040888963407
18
19const SO_PLAN_MUL: i64 = 0
20const SO_PLAN_SHL: i64 = 1
21const SO_PLAN_SHIFTOP: i64 = 2
22const SO_ADD: i64 = 0
23const SO_SUB: i64 = 1
24
25// cost model (measured ordering: mul costs several adds; a shift ~ an add).
26const SO_MUL_COST: i64 = 4
27const SO_SHL_COST: i64 = 1
28const SO_OP_COST: i64 = 1
29const SO_SHIFT_MAX: i64 = 62
30
31// evaluate a candidate plan on input x.
32func so_eval(kind: i64, a: i64, op: i64, b: i64, C: i64, x: i64) -> i64 {
33 if kind == SO_PLAN_MUL { return x * C }
34 if kind == SO_PLAN_SHL { return x << a }
35 let ta: i64 = x << a
36 let tb: i64 = x << b
37 if op == SO_ADD { return ta + tb }
38 return ta - tb
39}
40
41// cost of a plan (lower = better).
42func so_cost(kind: i64, a: i64, b: i64) -> i64 {
43 if kind == SO_PLAN_MUL { return SO_MUL_COST }
44 if kind == SO_PLAN_SHL { if a == 0 { return 0 } return SO_SHL_COST }
45 var c: i64 = SO_OP_COST
46 if a > 0 { c = c + SO_SHL_COST }
47 if b > 0 { c = c + SO_SHL_COST }
48 return c
49}
50
51// VERIFY a candidate computes x*C for all test inputs (1:1). 0 = no, 1 = yes.
52func so_verify(kind: i64, a: i64, op: i64, b: i64, C: i64) -> i64 {
53 if so_eval(kind, a, op, b, C, 0) != 0 { return 0 }
54 if so_eval(kind, a, op, b, C, 1) != C { return 0 }
55 var s: i64 = SO_MAGIC_19088743
56 var t: i64 = 0
57 while t < 32 {
58 s = s * SO_MAGIC_6364136223846793005 + SO_MAGIC_1442695040888963407
59 let x: i64 = s
60 if so_eval(kind, a, op, b, C, x) != x * C { return 0 }
61 t = t + 1
62 }
63 return 1
64}
65
66// THE SEARCH: author the cheapest verified lowering of x*C. Writes the winning
67// plan into out[0..3] = {kind, a, op, b}; returns its cost.
68func so_best(C: i64, out: *i64) -> i64 {
69 var best_cost: i64 = SO_MUL_COST
70 out[0] = SO_PLAN_MUL; out[1] = 0; out[2] = 0; out[3] = 0 // multiply always works
71 // single shift
72 var a: i64 = 0
73 while a < SO_SHIFT_MAX {
74 if so_verify(SO_PLAN_SHL, a, 0, 0, C) == 1 {
75 let c: i64 = so_cost(SO_PLAN_SHL, a, 0)
76 if c < best_cost { best_cost = c; out[0] = SO_PLAN_SHL; out[1] = a; out[2] = 0; out[3] = 0 }
77 }
78 a = a + 1
79 }
80 // (x<<a) op (x<<b)
81 a = 0
82 while a < SO_SHIFT_MAX {
83 var b: i64 = 0
84 while b < SO_SHIFT_MAX {
85 var op: i64 = 0
86 while op < 2 {
87 if so_verify(SO_PLAN_SHIFTOP, a, op, b, C) == 1 {
88 let c: i64 = so_cost(SO_PLAN_SHIFTOP, a, b)
89 if c < best_cost { best_cost = c; out[0] = SO_PLAN_SHIFTOP; out[1] = a; out[2] = op; out[3] = b }
90 }
91 op = op + 1
92 }
93 b = b + 1
94 }
95 a = a + 1
96 }
97 return best_cost
98}