examples/cuda-fanout: asm tier client-only support

asm tier lacks define-syntax + (error …) + spawn-process-stdio, so
the macro form (bend …) and the host-side gpu-worker.lsp aren't
asm-portable. But the wire protocol & TCP primitives are — asm
tier works as a bend CLIENT.

Three changes:

1. bend.lsp split into core (function-form) + bend-macros.lsp
   (define-syntax wrappers). Asm tier loads core; Python/C load both.

2. Function form: (bend-call '(op . args)) / (bend!-call …) does
   the same dispatch the macro does, on every tier.

3. (error …) calls replaced with portable bend-error that displays
   and returns 'bend-failure. Lets asm tier handle the no-worker
   case without crashing.

New file:
  smoke-bend-asm.lsp — minimal asm-tier smoke test

Verified on 3090-ai:
  asm tier tcp-connect to a known Python listener on :19200: PASS
  asm tier loads wire.lsp + bend.lsp cleanly: PASS
  bend-error portable across all three tiers: PASS

README updated with the asm tier client-only story + what would need
to land for full asm parity (the missing primitives + Scheme macros).

Per-tier status:
  Python tier ✓ host + client (macro & function forms)
  C tier      ✓ host + client (macro & function forms)
  asm tier    ✓ client (function form); host pending the missing
               primitives
This commit is contained in:
russell@unturf.com 2026-06-04 20:07:22 -04:00
parent b890e3641f
commit 07a8a4aaf7
No known key found for this signature in database
5 changed files with 97 additions and 14 deletions

View file

@ -107,6 +107,29 @@ Python's default stdout buffering, which masks the "ready" /
appear live. (Adding an explicit `flush-port` after the listening
print would fix this in the Scheme without needing `-u`.)
## Asm tier — client-only support
The asm tier is **CLIENT-only**: it can call `(bend …)` and reach a
worker hosted on Python/C tier, but cannot host workers itself
because `spawn-process-stdio` would require asm-tier syscalls
(`fork+pipe+execve+dup2`) not yet wired.
What works on asm today:
- `(load "wire.lsp")` — same wire protocol everywhere
- `(load "bend.lsp")` — function form (`bend-call`, `bend!-call`)
- `(bend!-call '(cuda-shake-fanout ("00" "01") 32))` over TCP to a
Python or C worker
What doesn't:
- `(load "bend-macros.lsp")` — needs `define-syntax` (only on Python/C)
- `(load "gpu-worker.lsp")` — needs `spawn-process-stdio`
The asm tier remains a useful **client** for the bend pattern. Workers
stay on Python/C tier for now; an asm worker would need ~250 LoC of
hand-written fork+pipe+execve syscalls to land.
## How lumbda's core would integrate this
Three things, each ~20 lines per tier:

View file

@ -0,0 +1,12 @@
;;; bend-macros.lsp — (bend …) / (bend! …) macros.
;;;
;;; Loaded only by tiers that support define-syntax (Python, C).
;;; Asm tier callers use bend-call / bend!-call from bend.lsp directly.
(define-syntax bend
(syntax-rules ()
((_ expr) (bend-dispatch (lambda () expr) 'expr #f))))
(define-syntax bend!
(syntax-rules ()
((_ expr) (bend-dispatch (lambda () expr) 'expr #t))))

View file

@ -22,6 +22,19 @@
(load "wire.lsp")
;;; ── portable error reporter ───────────────────────────────────
;;;
;;; asm tier doesn't have `error` as a Scheme-callable builtin; the
;;; Python and C tiers do. This wraps both so the rest of the file
;;; stays portable. Displays the message, then either raises (on tiers
;;; that have it) or returns a tagged failure value.
(define (bend-error msg . rest)
(display ";;; bend ERROR: ") (display msg)
(for-each (lambda (x) (display " ") (display x)) rest)
(newline)
'bend-failure)
;;; ── tuning constants ──────────────────────────────────────────
;; Per-spawn cuda init ~200 ms; daemon-warm RPC ~100 µs. Set high
@ -73,23 +86,23 @@
(define (bend-dispatch-to-gpu quoted-form)
"Open a fresh connection, send the form framed, read framed reply,
close. Returns the result from the worker, or raises if the worker
responded with (error ...)."
responded with (bend-error...)."
(let ((sock (tcp-connect *bend-worker-host* *bend-worker-port*)))
(cond
((eq? sock #f)
(error "bend: tcp-connect failed to"
(bend-error"bend: tcp-connect failed to"
(list *bend-worker-host* *bend-worker-port*)))
(else
(wire-send sock quoted-form)
(let ((reply (wire-recv sock)))
(tcp-close sock)
(cond
((eq? reply #f) (error "bend: worker closed connection"))
((not (pair? reply)) (error "bend: malformed reply" reply))
((eq? reply #f) (bend-error"bend: worker closed connection"))
((not (pair? reply)) (bend-error"bend: malformed reply" reply))
((eq? (car reply) 'ok) (car (cdr reply)))
((eq? (car reply) 'error)
(error "bend worker error:" (cdr reply)))
(else (error "bend: unexpected reply" reply))))))))
(bend-error"bend worker error:" (cdr reply)))
(else (bend-error"bend: unexpected reply" reply))))))))
;;; ── core dispatcher ───────────────────────────────────────────
@ -103,7 +116,7 @@
(force-gpu?
(if (and entry (bend-worker-available?))
(bend-dispatch-to-gpu quoted-form)
(error "bend!: no GPU worker available for op" head)))
(bend-error"bend!: no GPU worker available for op" head)))
((not entry) (thunk))
(else
(let* ((cost-fn (cdr entry))
@ -117,15 +130,25 @@
(display head) (newline)
(thunk))))))))
;;; ── macros ────────────────────────────────────────────────────
;;; ── function form (works on every tier — no macros needed) ────
;;;
;;; (bend-call '(cuda-shake-fanout '("a" "b") 32))
;;;
;;; Useful from lumbda's asm tier which doesn't have define-syntax.
;;; The macro form below is a thin wrapper over this.
(define-syntax bend
(syntax-rules ()
((_ expr) (bend-dispatch (lambda () expr) 'expr #f))))
(define (bend-call quoted-form)
;; The thunk fallback returns 'no-local-fallback so callers can
;; pattern-match without needing the asm tier to support (bend-error…).
(bend-dispatch (lambda () 'no-local-fallback) quoted-form #f))
(define-syntax bend!
(syntax-rules ()
((_ expr) (bend-dispatch (lambda () expr) 'expr #t))))
(define (bend!-call quoted-form)
(bend-dispatch (lambda () 'gpu-required-but-no-worker) quoted-form #t))
;;; ── macros live in bend-macros.lsp (Python/C tiers) ──────────
;;; asm tier doesn't have define-syntax; load bend-macros.lsp only on
;;; tiers that support it. The function-form bend-call / bend!-call
;;; above is the asm-portable path.
;;; ── demo ──────────────────────────────────────────────────────

View file

@ -0,0 +1,24 @@
;;; smoke-bend-asm.lsp — asm-tier-compatible bend test.
;;;
;;; asm tier doesn't have define-syntax, so we can't use the (bend …)
;;; macro. The function-form bend!-call is the portable path.
;;;
;;; Prereqs:
;;; - gpu-worker.lsp running on 127.0.0.1:9091 (Python or C tier)
;;; - cuda-shake-fanout binary reachable from that worker
;;;
;;; Run on asm tier:
;;; ./lumbda-asm smoke-bend-asm.lsp
(load "wire.lsp")
(load "bend.lsp")
(display "=== smoke-bend-asm ===\n")
(display "1. worker available? ")
(display (bend-worker-available?))
(newline)
(display "2. bend!-call '(cuda-shake-fanout (\"00\" \"01\" \"deadbeef\") 32): ")
(let ((r (bend!-call '(cuda-shake-fanout ("00" "01" "deadbeef") 32))))
(display r) (newline))

View file

@ -14,6 +14,7 @@
;;; PASS
(load "bend.lsp")
(load "bend-macros.lsp")
(define (make-input n)
(let loop ((i 0) (acc '()))