code wiki / _hdl_build / nx_synth.nx

nx_synth.nx source

↩ module page · 104 lines · 3838 B

1// nx_synth.nx -- a PROGRAM SYNTHESIZER: the team WRITES a program from a spec, by 2// search. Given only input/output examples (NOT the formula), it enumerates small 3// straight-line programs over {ADD,SUB,MUL,SHL} and returns the shortest one that 4// reproduces every example -- then the caller checks it on held-out inputs so it is 5// a real program, not an overfit. This is "writing code on its own": the search 6// discovers the program; I only wrote the searcher + the DSL. 7// 8// Enumerative program synthesis (Massalin superoptimizer, AFIPS 1987; modern: CEGIS 9// -- Solar-Lezama, ASPLOS 2006). Slots: 0=x (input), 1=1 (a constant), 2.. computed. 10// Bounded to length <= 2 over a few ops -- small, but genuinely authored from a goal. 11 12import "nx_syscalls.nx" 13 14const OP_ADD: i64 = 0 15const OP_SUB: i64 = 1 16const OP_MUL: i64 = 2 17const OP_SHL: i64 = 3 18 19// run a candidate program on input x. op/a/b are L-element arrays. For SHL, b[t] is 20// an immediate shift; for the others b[t] is a slot index. Result = last slot. 21// s = caller scratch (size >= L+4), reused across millions of calls (allocating 22// here would mmap per eval and exhaust the address space). 23func synth_eval(op: *i64, a: *i64, b: *i64, L: i64, x: i64, s: *i64) -> i64 { 24 s[0] = x 25 s[1] = 1 26 var t: i64 = 0 27 while t < L { 28 let o: i64 = op[t] 29 let va: i64 = s[a[t]] 30 var r: i64 = 0 31 if o == OP_ADD { r = va + s[b[t]] } 32 if o == OP_SUB { r = va - s[b[t]] } 33 if o == OP_MUL { r = va * s[b[t]] } 34 if o == OP_SHL { r = va << b[t] } 35 s[t + 2] = r 36 t = t + 1 37 } 38 return s[L + 1] 39} 40 41func synth_matches(op: *i64, a: *i64, b: *i64, L: i64, ex_x: *i64, ex_y: *i64, nex: i64, s: *i64) -> i64 { 42 var k: i64 = 0 43 while k < nex { 44 if synth_eval(op, a, b, L, ex_x[k], s) != ex_y[k] { return 0 } 45 k = k + 1 46 } 47 return 1 48} 49 50// SEARCH: find the shortest program (L=1 then L=2) matching all examples. Writes it 51// into op/a/b and returns L, or 0 if none found within the bound. 52func synth_find(ex_x: *i64, ex_y: *i64, nex: i64, op: *i64, a: *i64, b: *i64) -> i64 { 53 let s: *i64 = sys_mmap(8 * 8) // one scratch, reused across the whole search 54 // ---- L = 1 : slot2 = OP(slot a, slot/imm b), operands from slots {0,1} ---- 55 var o1: i64 = 0 56 while o1 < 4 { 57 var a1: i64 = 0 58 while a1 < 2 { 59 var bmax: i64 = 2 60 if o1 == OP_SHL { bmax = 6 } 61 var b1: i64 = 0 62 while b1 < bmax { 63 op[0] = o1; a[0] = a1; b[0] = b1 64 if synth_matches(op, a, b, 1, ex_x, ex_y, nex, s) == 1 { return 1 } 65 b1 = b1 + 1 66 } 67 a1 = a1 + 1 68 } 69 o1 = o1 + 1 70 } 71 // ---- L = 2 : slot2 = OP1(.. slots 0,1); slot3 = OP2(.. slots 0,1,2) ---- 72 o1 = 0 73 while o1 < 4 { 74 var a1: i64 = 0 75 while a1 < 2 { 76 var bm1: i64 = 2 77 if o1 == OP_SHL { bm1 = 6 } 78 var b1: i64 = 0 79 while b1 < bm1 { 80 var o2: i64 = 0 81 while o2 < 4 { 82 var a2: i64 = 0 83 while a2 < 3 { 84 var bm2: i64 = 3 85 if o2 == OP_SHL { bm2 = 6 } 86 var b2: i64 = 0 87 while b2 < bm2 { 88 op[0] = o1; a[0] = a1; b[0] = b1 89 op[1] = o2; a[1] = a2; b[1] = b2 90 if synth_matches(op, a, b, 2, ex_x, ex_y, nex, s) == 1 { return 2 } 91 b2 = b2 + 1 92 } 93 a2 = a2 + 1 94 } 95 o2 = o2 + 1 96 } 97 b1 = b1 + 1 98 } 99 a1 = a1 + 1 100 } 101 o1 = o1 + 1 102 } 103 return 0 104}