Download src/netlist_eval.c from phanerozoic/threshold-computers: direct link, hf CLI and curl.
- Browser
- Download file 16.9 kB
-
https://huggingface.co/phanerozoic/threshold-computers/resolve/main/src/netlist_eval.c
- Command line
-
hf download hf://phanerozoic/threshold-computers/src/netlist_eval.c
-
curl -L -o netlist_eval.c https://huggingface.co/phanerozoic/threshold-computers/resolve/main/src/netlist_eval.c
16.9 kB
| /* An evaluator of the SUBLEQ host family written in C against Definitions 5.1 | |
| * (the tape transducer) and 5.2 (SUBLEQ) and the layout of the serialization, | |
| * sharing no code with the Python evaluators. | |
| * | |
| * netlist_eval rand NET N SEED one step of the netlist against | |
| * SUBLEQ on N random states, 64 per | |
| * batch, and the halted fixed point | |
| * netlist_eval run NET MEM TAPE OUT MODE [CAP] | |
| * run the transducer; MODE is ref, net | |
| * or lock (lock runs both and compares | |
| * the full state, the head and the | |
| * output after every step); with CAP | |
| * the run stops after CAP steps | |
| * netlist_eval ref A MEM TAPE OUT [CAP] the reference machine alone, for a | |
| * word width A, without a netlist | |
| * netlist_eval iter NET STATE K OUT iterate any flat netlist K times | |
| * from STATE (one byte per state bit) | |
| * and write the final state | |
| * | |
| * NET is a flat netlist: the state width n, the unit count v and the edge count | |
| * E, then v biases, v fan-ins, E (index, weight) pairs and n next-state | |
| * indices, all little-endian int32. For the host family the state is the A | |
| * counter bits, the halt bit and 2^A words of A bits, most significant bit | |
| * first, and A is recovered from n = A + 1 + A 2^A. For the amplified host of | |
| * Section 8.2 the state is R copies of that, copy c occupying the block | |
| * c n0 .. (c+1) n0 - 1, and (R, A) is recovered from n = R (A + 1 + A 2^A) with | |
| * the smallest R; the device reads each bit of a cell as the majority of its | |
| * copies and writes every copy, and rand replicates each random state and | |
| * checks every copy of the result. MEM holds 2^A words of ceil(A/8) | |
| * little-endian bytes. The device cells are the top seven words, a request | |
| * fires on the value 1, and an emission writes the low byte of R_out. | |
| */ | |
| static int n, v, E; | |
| static int *bias, *fanin, *pairs, *nxt, *start, *kind; | |
| /* units grouped by depth: level l holds order[lstart[l] .. lstart[l+1]); units of | |
| * one level read no unit of the same level, so a level is evaluated in parallel | |
| * when the netlist is large enough for the threads to pay */ | |
| static int *order, *lstart, nlev, parallel; | |
| static uint64_t *tt; | |
| static uint8_t *sig; | |
| static uint64_t *sig64; | |
| static int A = 8, rep = 1, n0; | |
| static uint32_t SZ = 256, MASK = 255, SIGN = 128; | |
| static uint32_t C_WR, C_EOT, C_RW, C_RD, R_IN, R_OUT, HALT_PC; | |
| enum { K_TT, K_AND, K_OR, K_GEN }; | |
| static void set_width(int a) | |
| { | |
| A = a; | |
| SZ = 1u << A; | |
| MASK = SZ - 1; | |
| SIGN = 1u << (A - 1); | |
| C_WR = SZ - 7; C_EOT = SZ - 6; C_RW = SZ - 5; C_RD = SZ - 4; | |
| R_IN = SZ - 3; R_OUT = SZ - 2; HALT_PC = SZ - 1; | |
| } | |
| static void load_net(const char *path) | |
| { | |
| FILE *f = fopen(path, "rb"); | |
| if (!f) { perror(path); exit(2); } | |
| int hdr[3]; | |
| if (fread(hdr, 4, 3, f) != 3) exit(2); | |
| n = hdr[0]; v = hdr[1]; E = hdr[2]; | |
| bias = malloc(4 * (size_t)v); fanin = malloc(4 * (size_t)v); | |
| pairs = malloc(8 * (size_t)E); nxt = malloc(4 * (size_t)n); | |
| if (fread(bias, 4, v, f) != (size_t)v || fread(fanin, 4, v, f) != (size_t)v || | |
| fread(pairs, 8, E, f) != (size_t)E || fread(nxt, 4, n, f) != (size_t)n) { | |
| fprintf(stderr, "short read\n"); exit(2); | |
| } | |
| fclose(f); | |
| start = malloc(4 * (size_t)(v + 1)); | |
| start[0] = 0; | |
| for (int j = 0; j < v; j++) start[j + 1] = start[j] + fanin[j]; | |
| sig = malloc((size_t)(n + v)); | |
| sig64 = malloc(8 * (size_t)(n + v)); | |
| kind = malloc(4 * (size_t)(v > 0 ? v : 1)); | |
| tt = calloc((size_t)(v > 0 ? v : 1), 8); | |
| int gen = 0; | |
| for (int j = 0; j < v; j++) { | |
| const int *p = pairs + 2 * start[j]; | |
| int k = fanin[j], pos = 0, neg = 0; | |
| for (int e = 0; e < k; e++) { | |
| if (p[2 * e + 1] > 0) pos++; | |
| else neg++; | |
| } | |
| if (k <= 2) { | |
| kind[j] = K_TT; | |
| for (int x = 0; x < (1 << k); x++) { | |
| int acc = bias[j]; | |
| for (int e = 0; e < k; e++) acc += p[2 * e + 1] * ((x >> e) & 1); | |
| if (acc >= 0) tt[j] |= 1ull << x; | |
| } | |
| } else if (bias[j] == -pos) kind[j] = K_AND; | |
| else if (bias[j] == neg - 1) kind[j] = K_OR; | |
| else { kind[j] = K_GEN; gen++; } | |
| } | |
| { | |
| int *depth = calloc((size_t)(n + v), 4); | |
| int dmax = 0; | |
| for (int j = 0; j < v; j++) { | |
| const int *p = pairs + 2 * start[j]; | |
| int d = 0; | |
| for (int e = 0, k = fanin[j]; e < k; e++) if (depth[p[2 * e]] > d) d = depth[p[2 * e]]; | |
| depth[n + j] = d + 1; | |
| if (d + 1 > dmax) dmax = d + 1; | |
| } | |
| nlev = dmax; | |
| lstart = calloc((size_t)nlev + 2, 4); | |
| for (int j = 0; j < v; j++) lstart[depth[n + j]]++; | |
| for (int l = 1, acc = 0; l <= nlev + 1; l++) { int c = lstart[l]; lstart[l] = acc; acc += c; } | |
| order = malloc(4 * (size_t)(v > 0 ? v : 1)); | |
| int *fill = calloc((size_t)nlev + 2, 4); | |
| for (int j = 0; j < v; j++) { int l = depth[n + j]; order[lstart[l] + fill[l]++] = j; } | |
| free(fill); | |
| free(depth); | |
| parallel = E >= 200000; | |
| parallel = 0; | |
| } | |
| n0 = n; | |
| for (int r = 1; r < 64 && n0 == n; r++) | |
| for (int a = 3; a < 31; a++) | |
| if ((long)r * (a + 1 + (long)a * (1L << a)) == n) { set_width(a); rep = r; n0 = n / r; break; } | |
| fprintf(stderr, "net: %d state bits, %d units, %d edges, %d generic units, depth %d, A = %d, %d cop%s%s\n", | |
| n, v, E, gen, nlev, A, rep, rep == 1 ? "y" : "ies", parallel ? ", levels in parallel" : ""); | |
| } | |
| static inline void unit_eval(int j) | |
| { | |
| int acc = bias[j]; | |
| const int *p = pairs + 2 * start[j]; | |
| for (int e = 0, k = fanin[j]; e < k; e++) acc += p[2 * e + 1] * sig[p[2 * e]]; | |
| sig[n + j] = acc >= 0; | |
| } | |
| static void net_step(const uint8_t *s, uint8_t *o) | |
| { | |
| memcpy(sig, s, (size_t)n); | |
| if (!parallel) { | |
| for (int j = 0; j < v; j++) unit_eval(j); | |
| } else { | |
| for (int l = 1; l <= nlev; l++) { | |
| int lo = lstart[l], hi = lstart[l + 1]; | |
| for (int q = lo; q < hi; q++) unit_eval(order[q]); | |
| } | |
| } | |
| for (int i = 0; i < n; i++) { | |
| int k = nxt[i]; | |
| o[i] = k >= 0 ? sig[k] : (k == -2); | |
| } | |
| } | |
| static void net_step64(const uint64_t *s, uint64_t *o) | |
| { | |
| memcpy(sig64, s, 8 * (size_t)n); | |
| for (int j = 0; j < v; j++) { | |
| const int *p = pairs + 2 * start[j]; | |
| int k = fanin[j]; | |
| uint64_t r; | |
| if (kind[j] == K_TT) { | |
| uint64_t t = tt[j]; | |
| if (k == 0) r = -(t & 1); | |
| else if (k == 1) { | |
| uint64_t x = sig64[p[0]]; | |
| r = (x & -(t >> 1 & 1)) | (~x & -(t & 1)); | |
| } else { | |
| uint64_t x = sig64[p[0]], y = sig64[p[2]]; | |
| r = (~x & ~y & -(t & 1)) | (x & ~y & -(t >> 1 & 1)) | | |
| (~x & y & -(t >> 2 & 1)) | (x & y & -(t >> 3 & 1)); | |
| } | |
| } else if (kind[j] == K_AND) { | |
| r = ~0ull; | |
| for (int e = 0; e < k; e++) { | |
| uint64_t x = sig64[p[2 * e]]; | |
| r &= p[2 * e + 1] > 0 ? x : ~x; | |
| } | |
| } else if (kind[j] == K_OR) { | |
| r = 0; | |
| for (int e = 0; e < k; e++) { | |
| uint64_t x = sig64[p[2 * e]]; | |
| r |= p[2 * e + 1] > 0 ? x : ~x; | |
| } | |
| } else { | |
| r = 0; | |
| for (int l = 0; l < 64; l++) { | |
| int acc = bias[j]; | |
| for (int e = 0; e < k; e++) acc += p[2 * e + 1] * (int)(sig64[p[2 * e]] >> l & 1); | |
| if (acc >= 0) r |= 1ull << l; | |
| } | |
| } | |
| sig64[n + j] = r; | |
| } | |
| for (int i = 0; i < n; i++) { | |
| int k = nxt[i]; | |
| o[i] = k >= 0 ? sig64[k] : (k == -2 ? ~0ull : 0); | |
| } | |
| } | |
| /* ---- the machine of Definition 5.2 ------------------------------------ */ | |
| static void ref_step(uint32_t *M, uint32_t *pc) | |
| { | |
| uint32_t p = *pc; | |
| uint32_t a = M[p], b = M[(p + 1) & MASK], c = M[(p + 2) & MASK]; | |
| uint32_t r = (M[b] - M[a]) & MASK; | |
| M[b] = r; | |
| *pc = (r == 0 || (r & SIGN)) ? c : (p + 3) & MASK; | |
| } | |
| /* ---- the device of Definition 5.1 on a memory image --------------------- */ | |
| typedef struct { const uint8_t *tau; long len, h; uint8_t *out; long olen, ocap; } Dev; | |
| static void dev_apply(Dev *d, uint32_t *M) | |
| { | |
| if (M[C_WR] == 1) { | |
| if (d->olen == d->ocap) { d->ocap = d->ocap ? 2 * d->ocap : 1 << 16; d->out = realloc(d->out, (size_t)d->ocap); } | |
| d->out[d->olen++] = (uint8_t)(M[R_OUT] & 255); | |
| M[R_OUT] = 0; | |
| } | |
| if (M[C_RD] == 1) { | |
| if (d->h < d->len) { M[R_IN] = d->tau[d->h]; M[C_EOT] = 0; d->h++; } | |
| else M[C_EOT] = 1; | |
| } | |
| if (M[C_RW] == 1) d->h = 0; | |
| M[C_WR] = 0; M[C_RD] = 0; M[C_RW] = 0; | |
| } | |
| /* copy c of the state occupies s + c n0; a word is read from one copy or as the | |
| * bitwise majority of all copies, and written to every copy */ | |
| static uint32_t get_word_c(const uint8_t *s, int c, uint32_t j) | |
| { | |
| uint32_t x = 0; | |
| const uint8_t *b = s + (size_t)c * n0 + A + 1 + (size_t)A * j; | |
| for (int k = 0; k < A; k++) x = (x << 1) | b[k]; | |
| return x; | |
| } | |
| static uint32_t get_word(const uint8_t *s, uint32_t j) | |
| { | |
| if (rep == 1) return get_word_c(s, 0, j); | |
| uint32_t x = 0; | |
| for (int k = 0; k < A; k++) { | |
| int ones = 0; | |
| for (int c = 0; c < rep; c++) ones += s[(size_t)c * n0 + A + 1 + (size_t)A * j + k]; | |
| x = (x << 1) | (2 * ones > rep); | |
| } | |
| return x; | |
| } | |
| static void set_word(uint8_t *s, uint32_t j, uint32_t val) | |
| { | |
| for (int c = 0; c < rep; c++) { | |
| uint8_t *b = s + (size_t)c * n0 + A + 1 + (size_t)A * j; | |
| for (int k = 0; k < A; k++) b[k] = (uint8_t)((val >> (A - 1 - k)) & 1); | |
| } | |
| } | |
| static uint32_t get_pc_c(const uint8_t *s, int c) | |
| { | |
| uint32_t x = 0; | |
| for (int k = 0; k < A; k++) x = (x << 1) | s[(size_t)c * n0 + k]; | |
| return x; | |
| } | |
| static void net_dev_apply(Dev *d, uint8_t *s) | |
| { | |
| static uint32_t *W = NULL; | |
| if (!W) W = calloc(SZ, 4); | |
| uint32_t cells[6] = { C_WR, C_EOT, C_RW, C_RD, R_IN, R_OUT }; | |
| for (int c = 0; c < 6; c++) W[cells[c]] = get_word(s, cells[c]); | |
| dev_apply(d, W); | |
| for (int c = 0; c < 6; c++) set_word(s, cells[c], W[cells[c]]); | |
| } | |
| static uint8_t *slurp(const char *path, long *len) | |
| { | |
| FILE *f = fopen(path, "rb"); | |
| if (!f) { perror(path); exit(2); } | |
| fseek(f, 0, SEEK_END); *len = ftell(f); fseek(f, 0, SEEK_SET); | |
| uint8_t *b = malloc((size_t)*len + 1); | |
| if (*len && fread(b, 1, (size_t)*len, f) != (size_t)*len) exit(2); | |
| fclose(f); | |
| return b; | |
| } | |
| static uint32_t *load_mem(const char *path) | |
| { | |
| long mlen; | |
| uint8_t *m = slurp(path, &mlen); | |
| int wb = (A + 7) / 8; | |
| if (mlen != (long)SZ * wb) { | |
| fprintf(stderr, "memory image is %ld bytes, expected %ld\n", mlen, (long)SZ * wb); | |
| exit(2); | |
| } | |
| uint32_t *M = calloc(SZ, 4); | |
| for (uint32_t j = 0; j < SZ; j++) { | |
| uint32_t x = 0; | |
| for (int b = 0; b < wb; b++) x |= (uint32_t)m[(size_t)j * wb + b] << (8 * b); | |
| M[j] = x & MASK; | |
| } | |
| free(m); | |
| return M; | |
| } | |
| static uint64_t rng = 88172645463325252ull; | |
| static uint64_t xr(void) { rng ^= rng << 13; rng ^= rng >> 7; rng ^= rng << 17; return rng; } | |
| static int cmd_rand(long N) | |
| { | |
| uint64_t *s = malloc(8 * (size_t)n), *o = malloc(8 * (size_t)n); | |
| uint32_t *M = calloc(SZ, 4); | |
| long bad = 0, tested = 0, fixed_bad = 0, latch_bad = 0; | |
| for (long b = 0; b * 64 < N; b++) { | |
| for (int i = 0; i < n0; i++) s[i] = xr(); | |
| s[A] = 0; | |
| for (int c = 1; c < rep; c++) memcpy(s + (size_t)c * n0, s, 8 * (size_t)n0); | |
| net_step64(s, o); | |
| for (int l = 0; l < 64; l++) { | |
| uint32_t pc = 0; | |
| for (int k = 0; k < A; k++) pc = (pc << 1) | (uint32_t)(s[k] >> l & 1); | |
| for (uint32_t j = 0; j < SZ; j++) { | |
| uint32_t x = 0; | |
| for (int k = 0; k < A; k++) x = (x << 1) | (uint32_t)(s[A + 1 + (size_t)A * j + k] >> l & 1); | |
| M[j] = x; | |
| } | |
| ref_step(M, &pc); | |
| int ok = 1; | |
| for (int c = 0; c < rep && ok; c++) { | |
| const uint64_t *oc = o + (size_t)c * n0; | |
| uint32_t npc = 0; | |
| for (int k = 0; k < A; k++) npc = (npc << 1) | (uint32_t)(oc[k] >> l & 1); | |
| if (npc != pc) ok = 0; | |
| for (uint32_t j = 0; j < SZ && ok; j++) { | |
| uint32_t x = 0; | |
| for (int k = 0; k < A; k++) x = (x << 1) | (uint32_t)(oc[A + 1 + (size_t)A * j + k] >> l & 1); | |
| if (x != M[j]) ok = 0; | |
| } | |
| if ((int)(oc[A] >> l & 1) != (pc == HALT_PC)) latch_bad++; | |
| } | |
| bad += !ok; | |
| tested++; | |
| } | |
| for (int i = 0; i < n0; i++) s[i] = xr(); | |
| s[A] = ~0ull; | |
| for (int c = 1; c < rep; c++) memcpy(s + (size_t)c * n0, s, 8 * (size_t)n0); | |
| net_step64(s, o); | |
| for (int i = 0; i < n; i++) if (s[i] != o[i]) { fixed_bad++; break; } | |
| } | |
| printf("random states: %ld tested, %ld disagree with SUBLEQ, %ld halt-latch mismatches, " | |
| "%ld halted batches not fixed\n", tested, bad, latch_bad, fixed_bad); | |
| return bad || latch_bad || fixed_bad; | |
| } | |
| static int cmd_run(const char *memp, const char *tapep, const char *outp, const char *mode, long cap) | |
| { | |
| long tlen; | |
| uint8_t *tau = slurp(tapep, &tlen); | |
| uint32_t *M = load_mem(memp); | |
| int use_ref = strcmp(mode, "net") != 0, use_net = strcmp(mode, "ref") != 0; | |
| uint32_t pc = 0; | |
| uint8_t *s = NULL, *y = NULL; | |
| if (use_net) { | |
| s = calloc((size_t)n, 1); y = calloc((size_t)n, 1); | |
| for (uint32_t j = 0; j < SZ; j++) set_word(s, j, M[j]); | |
| } | |
| Dev dr = { tau, tlen, 0, NULL, 0, 0 }, dn = { tau, tlen, 0, NULL, 0, 0 }; | |
| long steps = 0; | |
| int halted = 0; | |
| for (;;) { | |
| int halt_r = 0, halt_n = 0; | |
| if (use_ref) { ref_step(M, &pc); dev_apply(&dr, M); halt_r = pc == HALT_PC; } | |
| if (use_net) { net_step(s, y); halt_n = y[A]; net_dev_apply(&dn, y); uint8_t *t = s; s = y; y = t; } | |
| steps++; | |
| if (use_ref && use_net) { | |
| int ok = halt_n == halt_r && dr.h == dn.h && dr.olen == dn.olen && | |
| (dr.olen == 0 || dr.out[dr.olen - 1] == dn.out[dn.olen - 1]); | |
| for (int c = 0; c < rep && ok; c++) { | |
| ok = get_pc_c(s, c) == pc && s[(size_t)c * n0 + A] == halt_r; | |
| for (uint32_t j = 0; j < SZ && ok; j++) ok = get_word_c(s, c, j) == M[j]; | |
| } | |
| if (!ok) { printf("lockstep divergence at step %ld\n", steps); return 1; } | |
| } | |
| if ((use_ref && halt_r) || (!use_ref && halt_n)) { halted = 1; break; } | |
| if (cap && steps >= cap) break; | |
| } | |
| Dev *d = use_ref ? &dr : &dn; | |
| FILE *f = fopen(outp, "wb"); | |
| if (d->olen) fwrite(d->out, 1, (size_t)d->olen, f); | |
| fclose(f); | |
| printf("mode %s: %s after %ld steps, head %ld of %ld, %ld bytes emitted\n", | |
| mode, halted ? "halted" : "stopped", steps, d->h, tlen, d->olen); | |
| return 0; | |
| } | |
| static int cmd_iter(const char *statep, long K, const char *outp) | |
| { | |
| long len; | |
| uint8_t *s = slurp(statep, &len); | |
| if (len != n) { fprintf(stderr, "state has %ld bits, the netlist %d\n", len, n); return 2; } | |
| uint8_t *y = malloc((size_t)n); | |
| for (long t = 0; t < K; t++) { | |
| net_step(s, y); | |
| uint8_t *tmp = s; s = y; y = tmp; | |
| } | |
| FILE *f = fopen(outp, "wb"); | |
| fwrite(s, 1, (size_t)n, f); | |
| fclose(f); | |
| return 0; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| if (argc >= 6 && !strcmp(argv[1], "iter")) { | |
| load_net(argv[2]); | |
| return cmd_iter(argv[3], atol(argv[4]), argv[5]); | |
| } | |
| if (argc >= 5 && !strcmp(argv[1], "rand")) { | |
| load_net(argv[2]); | |
| rng ^= (uint64_t)atoll(argv[4]) * 0x9E3779B97F4A7C15ull; | |
| return cmd_rand(atol(argv[3])); | |
| } | |
| if (argc >= 7 && !strcmp(argv[1], "run")) { | |
| load_net(argv[2]); | |
| return cmd_run(argv[3], argv[4], argv[5], argv[6], argc >= 8 ? atol(argv[7]) : 0); | |
| } | |
| if (argc >= 6 && !strcmp(argv[1], "ref")) { | |
| set_width(atoi(argv[2])); | |
| return cmd_run(argv[3], argv[4], argv[5], "ref", argc >= 7 ? atol(argv[6]) : 0); | |
| } | |
| fprintf(stderr, "usage: netlist_eval rand NET N SEED | run NET MEM TAPE OUT ref|net|lock [CAP]" | |
| " | ref A MEM TAPE OUT [CAP] | iter NET STATE K OUT\n"); | |
| return 2; | |
| } | |