code wiki / _hdl_build / nx_eqsat_constfold_bench.nx
nx_eqsat_constfold_bench.nx source
↩ module page · 131 lines · 7162 B
1// nx_eqsat_constfold_bench.nx -- CONST-FOLD-HEAVY race vs egg 0.11.0.
2//
3// THE RACE (engine-only, SAME const-heavy expression both sides):
4// egg target = nxc2/bench/nx_constfold_egg_bench.rs (copy into
5// /tmp/race_egg/examples/), the const-fold ANALYSIS graph build(n):
6// acc = 1; for i in 0..N: leaf=(i%7)+2; acc = acc {+,*,-} leaf (cycle by i%3)
7// so EVERY subtree is a known constant -- the const-fold analysis must evaluate +
8// collapse all N nodes, folding the whole chain to a single Num. egg does this via
9// its ConstantFold Analysis (make/merge/modify); WE do it via make-on-add + a CONST
10// merge through the union chokepoint citing NX_EQSAT_RULE_CONSTFOLD.
11//
12// OUR side, per timed iteration (the SAME end-to-end optimize pipeline egg ran):
13// nx_eqsat_init (fresh e-graph)
14// enable_constfold (egg's ConstantFold analysis ON)
15// build the all-const chain (egg's RecExpr build; fold fires at add-time)
16// nx_eqsat_saturate (egg's Runner::run -- analysis to fixpoint)
17// nx_eqsat_recompute_best (egg's Extractor bottom-up cost)
18// nx_eqsat_extract_best_node(root) (egg's find_best -> a single Num)
19//
20// FAIL LOUD: before timing, one verified iteration asserts the WHOLE chain folded
21// -- root's canonical best_node is a CONST (cost 0). A fast wrong answer (chain not
22// folded) loses, exit nonzero.
23//
24// Output (one line):
25// NX_CONSTFOLD_ENGINE_ONLY N=<n> ITERS=<it> FOLD=<v> TOTAL_US=<t> US_PER_OP_NS=<u>
26// (US_PER_OP_NS = total_us*1000/iters = nanoseconds/op; /1000 for us/op.)
27//
28// SOVEREIGN: no .sh; runs on the pinned NishiLang compiler. license_tier: ORIGINAL
29
30import "nx_eqsat.nx"
31const K_MAGIC_2000: i64 = 2000
32const K_MAGIC_4096: i64 = 4096
33
34func _emit_num(v: i64) -> i64 {
35 let b: *u8 = sys_mmap(28); var n: i64 = v; if n < 0 { n = 0 - n; sys_write(1, "-" as *u8, 1) }
36 let t2: *u8 = sys_mmap(28); var t: i64 = 0
37 if n == 0 { t2[0] = 48; t = 1 }
38 while n > 0 { t2[t] = 48 + (n % 10); n = n / 10; t = t + 1 }
39 var i: i64 = 0; while i < t { b[i] = t2[t - 1 - i]; i = i + 1 }
40 b[t] = 32; sys_write(1, b, t + 1); return 0
41}
42func _emit_str(s: *u8, len: i64) -> i64 { sys_write(1, s, len); return 0 }
43func _nl() -> i64 { let z: *u8 = sys_mmap(2); z[0] = 10; sys_write(1, z, 1); return 0 }
44
45// Build the all-const chain in `g`, run the analysis to fixpoint, extract. Writes
46// the folded constant value to fold_out[0] and returns 1 on success (the whole
47// chain collapsed to ONE CONST), or 0 on failure. Mirrors egg's build(n) +
48// Runner::run(&[]) (NO rewrite rules -- const-fold is an ANALYSIS, not a rewrite):
49// egg's analysis runs during build + rebuild; OUR analysis runs eagerly at add-time
50// (make-on-add), so the equivalent of egg's empty-rules run is just the (already-
51// done) eager fold + an extract. We do NOT run the 7 strength-reduction inline
52// rules -- that would be EXTRA work egg's const-fold bench never does (unfair). The
53// folded value is read off the root's canonical best_node, which the CONST merge
54// makes the cheapest node (CONST cost 0). cycle +,*,- over leaves like egg's build.
55func _one_iter(g: *NxEGraph, nodes: *NxENode, cap_nodes: i64,
56 classes: *NxEClass, cap_cls: i64, n: i64, fold_out: *i64) -> i64 {
57 if nx_eqsat_init(g, nodes, cap_nodes, classes, cap_cls) != NX_EQSAT_OK { return 0 }
58 if nx_eqsat_enable_constfold(g) != NX_EQSAT_OK { return 0 }
59 var acc: i64 = nx_eqsat_add_const(g, 1)
60 if acc < 0 { return 0 }
61 var i: i64 = 0
62 while i < n {
63 let leaf: i64 = nx_eqsat_add_const(g, (i % 7) + 2)
64 if leaf < 0 { return 0 }
65 let sel: i64 = i % 3
66 if sel == 0 { acc = nx_eqsat_add_binary(g, NX_EQ_OP_ADD, acc, leaf) }
67 if sel == 1 { acc = nx_eqsat_add_binary(g, NX_EQ_OP_MUL, acc, leaf) }
68 if sel == 2 { acc = nx_eqsat_add_binary(g, NX_EQ_OP_SUB, acc, leaf) }
69 if acc < 0 { return 0 }
70 i = i + 1
71 }
72 // EXTRACT (egg's Extractor::find_best) -- the const-fold merges already happened
73 // eagerly at add-time, so recompute_best + extract reads the fully-folded root.
74 if nx_eqsat_recompute_best(g) != NX_EQSAT_OK { return 0 }
75 let best: i64 = nx_eqsat_extract_best_node(g, acc)
76 if best < 0 { return 0 }
77 // the whole chain must have folded to a single CONST (egg's fully-folded root).
78 if g.nodes[best].op != NX_EQ_OP_CONST { return 0 }
79 fold_out[0] = g.nodes[best].payload
80 return 1
81}
82
83func main() -> i64 {
84 let n: i64 = 64 // chain length (matches egg default N=64)
85 let iters: i64 = K_MAGIC_2000 // matches egg iters for N=64
86
87 // Per iter we add ~ 1 + 2*N nodes (op + its folded CONST per step) + interned
88 // CONST leaves. N=64 -> well under 1024; size caps generously.
89 let cap_nodes: i64 = K_MAGIC_4096
90 let cap_cls: i64 = K_MAGIC_4096
91 let nodes: *NxENode = sys_mmap(cap_nodes * 64) as *NxENode
92 let classes: *NxEClass = sys_mmap(cap_cls * 32) as *NxEClass
93 let g: *NxEGraph = sys_mmap(128) as *NxEGraph
94
95 // ---- CORRECTNESS GATE (FAIL LOUD before timing): the chain MUST fully fold.
96 let fbuf: *i64 = sys_mmap(8) as *i64
97 if _one_iter(g, nodes, cap_nodes, classes, cap_cls, n, fbuf) != 1 { sys_exit(80); return 80 }
98 let fold: i64 = fbuf[0]
99
100 // ---- TIMED LOOP -------------------------------------------------------------
101 let t0: i64 = sys_clock_now_us()
102 var k: i64 = 0
103 while k < iters {
104 if _one_iter(g, nodes, cap_nodes, classes, cap_cls, n, fbuf) != 1 { sys_exit(81); return 81 } // FAIL LOUD mid-run (no fast wrong answer)
105 k = k + 1
106 }
107 let t1: i64 = sys_clock_now_us()
108 let total_us: i64 = t1 - t0
109 let us_per_op_ns: i64 = (total_us * 1000) / iters
110
111 // ---- REPORT -----------------------------------------------------------------
112 let tag: *u8 = sys_mmap(40)
113 // "NX_CONSTFOLD_ENGINE_ONLY "
114 tag[0]=78; tag[1]=88; tag[2]=95; tag[3]=67; tag[4]=79; tag[5]=78; tag[6]=83; tag[7]=84
115 tag[8]=70; tag[9]=79; tag[10]=76; tag[11]=68; tag[12]=95; tag[13]=69; tag[14]=78; tag[15]=71
116 tag[16]=73; tag[17]=78; tag[18]=69; tag[19]=95; tag[20]=79; tag[21]=78; tag[22]=76; tag[23]=89; tag[24]=32
117 _emit_str(tag, 25)
118 let s_n: *u8 = sys_mmap(4); s_n[0]=78; s_n[1]=61 // "N="
119 _emit_str(s_n, 2); _emit_num(n)
120 let s_it: *u8 = sys_mmap(8); s_it[0]=73; s_it[1]=84; s_it[2]=69; s_it[3]=82; s_it[4]=83; s_it[5]=61 // "ITERS="
121 _emit_str(s_it, 6); _emit_num(iters)
122 let s_fd: *u8 = sys_mmap(8); s_fd[0]=70; s_fd[1]=79; s_fd[2]=76; s_fd[3]=68; s_fd[4]=61 // "FOLD="
123 _emit_str(s_fd, 5); _emit_num(fold)
124 let s_to: *u8 = sys_mmap(16); s_to[0]=84; s_to[1]=79; s_to[2]=84; s_to[3]=65; s_to[4]=76; s_to[5]=95; s_to[6]=85; s_to[7]=83; s_to[8]=61 // "TOTAL_US="
125 _emit_str(s_to, 9); _emit_num(total_us)
126 let s_up: *u8 = sys_mmap(24); s_up[0]=85; s_up[1]=83; s_up[2]=95; s_up[3]=80; s_up[4]=69; s_up[5]=82; s_up[6]=95; s_up[7]=79; s_up[8]=80; s_up[9]=95; s_up[10]=78; s_up[11]=83; s_up[12]=61 // "US_PER_OP_NS="
127 _emit_str(s_up, 13); _emit_num(us_per_op_ns)
128 _nl()
129
130 sys_exit(0); return 0
131}