From 99701c863ee5238eae962571de0f9b4e8aca7cb6 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sat, 6 Jun 2026 09:16:34 -0400 Subject: [PATCH] host: walk-circuit-ops + op-specs->bytes + count-lumbda-ops primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three Python-tier primitives that lift foxhop ecdsa's emit-ops-bin pipeline out of the Scheme interpreter: - walk-circuit-ops registers ops → list of op-spec vectors. Mirrors ecdsa/lumbda/emit-ops-bin.lsp's walk-op dispatch table (alloc, free, x, z, cx, cz, swap, ccx, ccz) in pure Python with interned-symbol identity dispatch & a Python dict for the qubit-layout. Replaces a Scheme named-let walk that paid ~5-9 ms per op via per-iteration closure / let* / cons / append overhead — wall dropped 252 sec to 237 ms on a 32 k-op p=11 case. - op-specs->bytes vector-list → 56*N latin-1 string. Each op-spec is a 7-element vector packed via struct.Struct('ops walk loop. Walking ~32K lumbda ops +# inside the Scheme interpreter costs ~5-9 ms per iteration on Python +# tier (per-call closure / let* / cons / append overhead), so the toy +# p=11 emit ran past 4 minutes just on the walk. This primitive does +# the entire walk in pure Python and returns a Scheme list of 7-element +# vectors (kind q2 q1 qt ct cc rt) — the same op-spec shape Scheme +# would have produced — in O(N) wall, dispatch by interned-symbol +# identity per op. +# +# Op shapes recognized (mirror lumbda ecdsa/lumbda/emit-ops-bin.lsp +# walk-op): +# (alloc name width) +# (free name) +# (x (reg idx)) +# (z (reg idx)) +# (cx (reg idx) (reg idx)) +# (cz (reg idx) (reg idx)) +# (swap (reg idx) (reg idx)) +# (ccx (reg idx) (reg idx) (reg idx)) +# (ccz (reg idx) (reg idx) (reg idx)) +# Anything else raises LispErr so a new op tag fails loud. +# +# Kind enum mirrors emit-ops-bin.lsp: +# 1 Register, 2 AppendToRegister, 6 X, 7 Z, 8 CX, 9 CZ, 10 Swap, +# 13 CCX, 14 CCZ. Sentinel NO_SLOT = u64::MAX = 2^64 - 1. +# +# alloc grows the layout & emits Register + width × AppendToRegister. +# free drops the name; we do NOT emit an upstream op (matches Bennett +# pattern where ancillae stay reserved). Each register name occupies +# one qubit-range; re-alloc of the same name (after a prior free) gets +# a fresh range with a new reg-id, which is the cumulative qubit base. + +def _walk_circuit_ops(registers_lst, ops_lst): + NO_SLOT = 18446744073709551615 + s_alloc = S('alloc'); s_free = S('free') + s_x = S('x'); s_z = S('z') + s_cx = S('cx'); s_cz = S('cz') + s_ccx = S('ccx'); s_ccz = S('ccz') + s_swap = S('swap') + + layout = {} # name (Symbol) -> base (int) + next_q = 0 + result = [] # list of 7-element op-spec records + + def emit_register(name, width): + nonlocal next_q + base = next_q + reg_id = base + layout[name] = base + result.append([1, NO_SLOT, NO_SLOT, NO_SLOT, NO_SLOT, NO_SLOT, reg_id]) + for i in range(width): + result.append([2, NO_SLOT, NO_SLOT, base + i, NO_SLOT, NO_SLOT, reg_id]) + next_q += width + + # Declared registers first (boilerplate before any ops). + n = registers_lst + while isinstance(n, Pair): + rec = n.car + # rec = (name width) — Pair(name, Pair(width, NIL)) + rec_name = rec.car + rec_width = rec.cdr.car + emit_register(rec_name, rec_width) + n = n.cdr + + # Now walk ops. + n = ops_lst + while isinstance(n, Pair): + op = n.car + tag = op.car + rest = op.cdr + if tag is s_ccx: + c1 = rest.car + c2 = rest.cdr.car + tgt = rest.cdr.cdr.car + q1 = layout[c1.car] + c1.cdr.car + q2 = layout[c2.car] + c2.cdr.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([13, q2, q1, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_cx: + c1 = rest.car + tgt = rest.cdr.car + q1 = layout[c1.car] + c1.cdr.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([8, NO_SLOT, q1, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_x: + tgt = rest.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([6, NO_SLOT, NO_SLOT, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_alloc: + name = rest.car + width = rest.cdr.car + emit_register(name, width) + elif tag is s_free: + name = rest.car + if name in layout: + del layout[name] + elif tag is s_z: + tgt = rest.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([7, NO_SLOT, NO_SLOT, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_cz: + c1 = rest.car + tgt = rest.cdr.car + q1 = layout[c1.car] + c1.cdr.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([9, NO_SLOT, q1, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_swap: + a = rest.car + b = rest.cdr.car + q1 = layout[a.car] + a.cdr.car + qt = layout[b.car] + b.cdr.car + result.append([10, NO_SLOT, q1, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + elif tag is s_ccz: + c1 = rest.car + c2 = rest.cdr.car + tgt = rest.cdr.cdr.car + q1 = layout[c1.car] + c1.cdr.car + q2 = layout[c2.car] + c2.cdr.car + qt = layout[tgt.car] + tgt.cdr.car + result.append([14, q2, q1, qt, NO_SLOT, NO_SLOT, NO_SLOT]) + else: + raise LispErr(f'walk-circuit-ops: unknown op tag: {show(tag)}') + n = n.cdr + + return _P(result) + +# op-specs->bytes — serialize a Scheme list of op-spec vectors +# (the output of walk-circuit-ops) into the QECCOPS1 body byte string. +# Each op-spec is a 7-element vector [kind, q2, q1, qt, ct, cc, rt] +# packed as 56 bytes little-endian: u32 kind, u32 pad, then 6× u64. +# Returns a Latin-1 string so the existing write-binary-file primitive +# ships it byte-for-byte. Replaces the Scheme-level op-spec->bytes + +# (apply string-append parts) pipeline in emit-ops-bin.lsp — that loop +# spent ~66 sec on 32K ops on Python tier through interpreter overhead; +# host runs the same packing in milliseconds via struct.pack + b''.join. + +import struct as _struct +_OP_SPEC_PACK = _struct.Struct('bytes'), lambda a, _: _op_specs_to_bytes(a[0])) + d(S('count-lumbda-ops'), lambda a, _: _count_lumbda_ops(a[0])) # heap-snapshot/heap-restore are asm-only arena primitives. Python has # real GC so these are no-ops here — they exist only to let portable # .lsp code call them unconditionally.