Commit graph

11 commits

Author SHA1 Message Date
b8d6afdeb3 heap-snapshot + native HTTP client + MOAD-0001 cleanup
Three wins in one commit.

1) heap-snapshot / heap-restore (asm arena primitive)
   asm has no GC. Long-running servers leaked ~64 MB per heap growth.
   Two new builtins let a programmer capture r15 and later rewind to
   it, recycling intermediate allocations in O(1) memory.
   Python + C get no-op versions so portable .lsp code can call them
   unconditionally.

   examples/http-server.lsp now takes a snapshot at top level and
   rewinds after every request. Measured asm RSS: 88 KB initial,
   100 KB after 100 requests, 100 KB after 1100 requests — flat.
   Prior behavior was +64 MB per few thousand requests.

2) examples/http-client-bench.lsp — native HTTP load generator
   Uses only the six tcp-* primitives + current-time-ms. Runs
   identically in all three impls. Eliminates curl's ~2 ms/req
   fork+exec overhead, so real server throughput shows up:

     Python server ← Python client   2403 rps
     C      server ← C      client   2439 rps
     asm    server ← asm    client   2994 rps
     asm    server ← C      client   2500 rps

   The earlier curl-based bench was clamped near 400 rps by the
   client; the actual servers handle 6–7× that.

3) MOAD-0001 cleanup
   - c/builtins.c bi_string_replace: strncmp-at-every-position
     (hand-rolled, sedimentary) → strstr (libc-tuned, typically
     Boyer-Moore-Horspool). O(N*k) → O(N + matches*k).
   - uncommonlisp.py _tokenize_lines: per-token src.count('\n', 0, pos)
     → precompute line_starts once, bisect_right per token.
     O(N*M) → O(M + N log M).

Also adds current-time-ms to all three impls so benchmarks can
time themselves without relying on the Python/C float `current-time`
(asm has no floats). Seconds-since-epoch tagged as a 61-bit int.

Test counts unchanged: 571 py + 132 asm + 189 shared + 83 c = 975.
All green via make test-all.
2026-04-16 20:49:32 -04:00
bfd4ec7ec8 sockets + portable HTTP server — 6 primitives, same server runs in all 3
Added tcp-listen/accept/connect/recv/send/close to Python, C, and asm.
One examples/http-server.lsp runs identically in all three impls and
serves HTTP/1.0 with routing, content-type, and content-length headers.

asm additions:
- SYS_SOCKET/BIND/LISTEN/ACCEPT/CONNECT/SETSOCKOPT syscalls
- 6 tcp-* builtins using the existing port encoding (SPECIAL ≥ 1000)
- bi_tcp_connect: dotted-quad IPv4 parser, no DNS dependency

Defects fixed along the way (surfaced by the HTTP server):
- string-append: was 2-arg only; now variadic (walks arg list twice)
- number->string: was stubbed to VAL_VOID; now correctly writes digits
  into a heap-allocated string (incl. negative handling)
- String-literal reader: \r and \0 escape sequences now handled (was
  silently dropping backslash, treating them as literal 'r' / '0')
- tcp_accept: sockaddr buffer was 8 bytes, now 16 (was corrupting
  caller's stack when accept wrote full struct sockaddr_in)

Pinocchio benchmark (tests/web-benchmark.sh):
At concurrency=20, 1000 requests, serving a 1KB body:

  uncommonlisp Python   373 req/s
  uncommonlisp C        370 req/s
  uncommonlisp asm      370 req/s
  python3 http.server   381 req/s  (stdlib reference)
  busybox httpd         382 req/s  (production reference)

All five converge within 3% — the client (curl fork/exec) is the
bottleneck, not the server. Our single-threaded blocking servers
are indistinguishable from battle-tested ones at this load.

Binary sizes:
  uncommonlisp asm    45 KB   (HTTP + everything else)
  busybox httpd       2.1 MB  (multi-call binary)
  python3             8 MB    (interpreter)

The asm HTTP server is 46× smaller than busybox and 176× smaller
than Python, serves from 7 Linux syscalls, and the entire protocol
handler is 70 lines of portable Scheme.

Test counts: 132 asm (up 1), rest unchanged. All green.
2026-04-16 18:58:27 -04:00
57f3c9fab1 asm/c/py: add (load), ports, write-file/file->string — full cross-impl parity
Asm gains the file I/O surface Python and C already had, unlocking
9/9 cells of the portal producer×consumer matrix (previously 6/9).

asm:
- (load "path") — mmaps file, swaps input source, loops scheme_read+eval,
  restores on exit. Nestable. Uses SYS_LSEEK + SYS_MUNMAP.
- Output ports: (open-output-file), (close-port), (port?). Encoded as
  SPECIAL values ≥ 1000 (fd = (val>>3) − PORT_SPECIAL_BASE), no tag-bit
  expansion needed.
- (display), (write), (newline) accept optional port arg; printer
  writes via output_fd global, swapped by port-aware builtins.
- (write-file path content) / (file->string path) — bytes in/out.

c, py: (write-file) / (file->string) added for parity.

tests: 131 asm (up 23), 189 functional (up 8, shared py+c),
tests/portal-cross-test.sh exercises 3×3 save×load matrix.
2026-04-16 16:37:40 -04:00
610c93e8c8 C: add full continuations, portal save/resume, 7 new tests
Full continuations: FullCont captures frames/stack/env with deep copy.
VM trampoline via setjmp/longjmp. Multi-shot safe via deep_copy_env.

Portal (new c/portal.c): serialize env + continuation to JSON,
resume on another machine. portal-checkpoint! triggers mid-VM save.

83/83 C unit tests + 181/181 functional tests pass.
2026-04-15 20:07:09 -04:00
30d7279be2 C: add deep_copy_env, VM frame stack for continuations
asm: fix builtin dispatch, improve apply_proc_raw

C changes: deep_copy_env() for multi-shot continuations,
explicit frame stack in VM for compiled code call/cc support.

asm changes: improved builtin implementations, fixed dispatch paths.

All tests pass: asm 75, C 76+114 functional.
2026-04-15 19:57:17 -04:00
22571fa470 Fix MOAD-0001 defects across all implementations
asm/uncommonlisp.s — intern_symbol: replaced O(N) linear scan with
djb2 hash table (1024 buckets, chaining). 2.9x faster symbol interning
on programs with many symbols. 75 tests pass.

uncommonlisp.py — _define_record_type: replaced list.index() O(N)
with dict lookup O(1) for field→index mapping. 571 tests pass.

MOAD-0002 documented: _portal_checkpoint, _call_stack, _auto_compile
are intentional globals (hot loop performance). cc_escape_val/cc_active_jmp
are required by setjmp/longjmp call/cc approach. Comments added.

All 836 assertions pass across Python + C + Assembly + functional.
2026-04-15 14:29:58 -04:00
d373f80aaf JIT: add named-let loops, let/let*, and/or, car/cdr/cons, 18 new tests
JIT now covers: if, cond, and, or, let, let*, named-let (native loops),
car, cdr, cons, null?, pair?, arithmetic, comparisons, recursion, TCO.
1309 lines of x86_64 codegen. 76 C tests + 114 functional tests pass.

Named-let loops compile to native jmp (zero call overhead):
  sum-to(50k): 0.33ms JIT vs 7.8ms CPython (24x faster than Python)
  ack(3,4):    0.20ms JIT vs 2.0ms CPython (10x faster)
  fib-rec(20): 0.42ms JIT vs 2.7ms CPython (6x faster)

EML benchmark added: integer-domain exp/ln composition under JIT.
2026-04-14 20:50:24 -04:00
c80eabac47 x86_64 JIT: 12-21x faster than CPython, 230x faster than interpreter
Real native machine code via mmap(PROT_EXEC). No exec(). No strings.
Raw x86_64 bytes: mov, add, sub, imul, cmp, je, jne, call, ret, jmp.

ack(3,4):    0.12ms JIT vs 1.5ms CPython vs 28ms interpreter
fib-rec(20): 0.16ms JIT vs 3.4ms CPython vs 40ms interpreter

Added cond support to JIT (cascaded comparisons → conditional jumps).
Fixed JIT cache: sentinel value prevents retry on unjittable functions.
System V AMD64 ABI: args in rdi/rsi/rdx, callee-saved r12-r15.
Tail calls use jmp (true TCO at machine code level).

691 lines of jit.c. 114 functional tests pass. All C tests pass.
2026-04-14 19:58:26 -04:00
46812e1885 Add GPU architecture notes and JIT header
docs/gpu-architecture.md — roadmap for GPU lambda execution:
  Phase 1: map/reduce (CUDA thread per element)
  Phase 2: trampolining (recursive lambdas without stack)
  Phase 3: interaction combinators (Bend/HVM approach, 74K MIPS)

c/jit.h — x86_64 JIT header: JitBlock, JitFunc typedef,
  jit_compile/jit_free API. Uses mmap for executable memory.
  System V AMD64 ABI calling convention.

jit.c implementation in progress (x86 instruction encoding).
2026-04-14 19:43:16 -04:00
db2cd77c62 Add shared functional test suite: 114 tests, both implementations pass
tests/functional.lsp — single .lsp file, runs identically in Python and C.
Covers: arithmetic, comparison, booleans, pairs, lists, strings, characters,
vectors, hash tables, control flow, let/lambda/closures, do loops, define,
recursion, TCO (100k depth), quasiquote, macros, type predicates, call/cc,
error handling, mergesort, higher-order programs.

Fixed C call/cc: proper escape continuations via setjmp/longjmp.

make test-all runs: Python unit (571) + C unit (58) + shared functional (114).
2026-04-14 15:21:17 -04:00
fc9eb5350c Add C implementation: 7,429 lines, 58 tests, identical output
Complete C port of the Scheme interpreter. Same .lsp files run in
both Python and C with identical output.

Architecture:
- NaN-boxed 64-bit values (zero-alloc numbers)
- Hash-map environments with parent chain + global shortcut
- Interned symbols
- TCO via explicit loop (eval) and TAIL_CALL/SELF_TAIL_CALL (VM)
- Bytecode compiler with all opcodes including superinstructions
- 58 unit + integration tests

Makefile targets:
  make test-all    run Python (571) + C (58) tests
  make examples    run examples in both, compare output
  make friction    benchmark same .lsp in Python vs C
  make c-build     build C interpreter
  make c-test      run C tests
  make c-repl      C REPL
2026-04-14 14:55:17 -04:00