asm tier: spawn-process-stdio + flush-port

Adds the two primitives lumbda's bend pattern needs to host GPU workers
on the asm tier, byte-identical to Python & C tiers shipped earlier.

  (spawn-process-stdio path '(args …))
    → (stdin-port . stdout-port) or #f

    Implementation:
      - pipe2 + pipe2 (in_pipe, out_pipe)
      - fork via SYS_FORK = 57
      - parent: close child-side ends, encode_port both fds, make_pair
      - child: dup2(in_pipe[0], 0); dup2(out_pipe[1], 1); close all 4;
               execve(path, argv, NULL); exit(127) on failure

    Stack frame: 4 KB scratch at %rbp (saved by RET_VAL via the
    caller's pushq sequence). Layout:
      0..15   = in_pipe + out_pipe (4 × int32 fds)
      16..143 = argv[] (16 ptrs × 8 B)
      144..399 = path buf (256 B, null-term)
      400..2447 = arg bufs (8 args × 256 B)

    Limits: ≤ 8 args, ≤ 255 bytes per string. Symbols not yet
    converted to strings (the Python & C tiers do that; asm callers
    must pass actual strings).

  (flush-port port)
    → void, no-op. asm tier uses raw fds with no userspace buffering
    (unlike the C tier's FILE* wrappers), so there's nothing to flush.

New syscall constants:
  SYS_PIPE2 = 293
  SYS_FORK  = 57
  SYS_EXECVE= 59
  SYS_DUP2  = 33

Two new BI_ codes (BI_SPAWNPROC = 121, BI_FLUSHPORT = 122) with the
GC_* renumbered to 123..127 and BI_COUNT = 128 (GC_NAIVE) / 123.

bn_spawnproc + bn_flushport added to symbol table + builtin name array.

Verified on 3090-ai:

  λ> (display (spawn-process-stdio "/bin/true" (quote ())))
  (#<port> . #<port>)

  λ> (define p (spawn-process-stdio "/bin/echo" (quote ("hello" "from" "asm"))))
  λ> (display p)
  (#<port> . #<port>)

Critical debug: %r15 is lumbda asm's heap bump pointer (line 14:
"# %r15 = heap bump pointer"). Initial draft used %r15 as scratch
base — make_pair → heap_alloc segfaulted immediately. Reverted &
re-wrote with %rbp (callee-saved + RET_VAL restores it). All future
asm primitives that need a scratch register MUST avoid %r15.

Per-tier matrix now:
  Python tier ✓ host + client
  C tier      ✓ host + client (cross-tier byte-identical to Python)
  asm tier    ✓ spawn-process-stdio + flush-port working;
               read-line on subprocess pipe still missing
               (~20 LoC asm) before asm can fully host workers.
This commit is contained in:
russell@unturf.com 2026-06-04 20:41:19 -04:00
parent aa428cdeb2
commit 4f03c48e56
No known key found for this signature in database

View file

@ -38,6 +38,10 @@
.equ SYS_SENDFILE, 40 .equ SYS_SENDFILE, 40
.equ SYS_CLOCK_GETTIME, 228 .equ SYS_CLOCK_GETTIME, 228
.equ CLOCK_REALTIME, 0 .equ CLOCK_REALTIME, 0
.equ SYS_PIPE2, 293
.equ SYS_FORK, 57
.equ SYS_EXECVE,59
.equ SYS_DUP2, 33
.equ AF_INET, 2 .equ AF_INET, 2
.equ SOCK_STREAM,1 .equ SOCK_STREAM,1
@ -206,15 +210,17 @@
.equ BI_EXIT, 118 .equ BI_EXIT, 118
.equ BI_VALUES, 119 .equ BI_VALUES, 119
.equ BI_CWV, 120 .equ BI_CWV, 120
.equ BI_SPAWNPROC, 121
.equ BI_FLUSHPORT, 122
.ifdef GC_NAIVE .ifdef GC_NAIVE
.equ BI_GC_COLLECT, 121 .equ BI_GC_COLLECT, 123
.equ BI_GC_STATS, 122 .equ BI_GC_STATS, 124
.equ BI_WITH_ARENA, 123 .equ BI_WITH_ARENA, 125
.equ BI_ARENA_STATS, 124 .equ BI_ARENA_STATS, 126
.equ BI_ARENA_SET_MODE, 125 .equ BI_ARENA_SET_MODE, 127
.equ BI_COUNT, 126 .equ BI_COUNT, 128
.else .else
.equ BI_COUNT, 121 .equ BI_COUNT, 123
.endif .endif
# ============================================================ # ============================================================
@ -343,6 +349,8 @@ bn_tcprecv: .byte 8; .ascii "tcp-recv"
bn_tcpsend: .byte 8; .ascii "tcp-send" bn_tcpsend: .byte 8; .ascii "tcp-send"
bn_tcpclose: .byte 9; .ascii "tcp-close" bn_tcpclose: .byte 9; .ascii "tcp-close"
bn_tcpsendfile: .byte 12; .ascii "tcp-sendfile" bn_tcpsendfile: .byte 12; .ascii "tcp-sendfile"
bn_spawnproc: .byte 19; .ascii "spawn-process-stdio"
bn_flushport: .byte 10; .ascii "flush-port"
bn_heapsnap: .byte 13; .ascii "heap-snapshot" bn_heapsnap: .byte 13; .ascii "heap-snapshot"
bn_heaprest: .byte 12; .ascii "heap-restore" bn_heaprest: .byte 12; .ascii "heap-restore"
bn_curtime: .byte 15; .ascii "current-time-ms" bn_curtime: .byte 15; .ascii "current-time-ms"
@ -487,6 +495,7 @@ bi_names:
.quad bn_randomseed, bn_randomint, bn_randomstate, bn_randomstateset .quad bn_randomseed, bn_randomint, bn_randomstate, bn_randomstateset
.quad bn_randomseedfromos .quad bn_randomseedfromos
.quad bn_cadr, bn_sort, bn_gensym, bn_exit, bn_values, bn_cwv .quad bn_cadr, bn_sort, bn_gensym, bn_exit, bn_values, bn_cwv
.quad bn_spawnproc, bn_flushport
.ifdef GC_NAIVE .ifdef GC_NAIVE
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode .quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
.endif .endif
@ -4273,6 +4282,10 @@ eval_list:
je bi_hash_set_to_list je bi_hash_set_to_list
cmpq $BI_TCPSENDFILE, %rax cmpq $BI_TCPSENDFILE, %rax
je bi_tcp_sendfile je bi_tcp_sendfile
cmpq $BI_SPAWNPROC, %rax
je bi_spawn_process_stdio
cmpq $BI_FLUSHPORT, %rax
je bi_flush_port
.ifdef GC_NAIVE .ifdef GC_NAIVE
cmpq $BI_GC_COLLECT, %rax cmpq $BI_GC_COLLECT, %rax
je bi_gc_collect_user je bi_gc_collect_user
@ -8647,6 +8660,184 @@ bi_tcp_sendfile:
movq $VAL_FALSE, %rax movq $VAL_FALSE, %rax
RET_VAL RET_VAL
# ============================================================
# bi_spawn_process_stdio: (spawn-process-stdio "path" '("arg1" ...))
# (stdin-port . stdout-port) or #f
#
# %r15 is lumbda's heap bump pointer DO NOT clobber.
# Scratch base lives in %rbp (callee-saved; RET_VAL restores it).
#
# Stack scratch layout (4 KB):
# 0..15 = in_pipe + out_pipe (4 × int32 fds)
# 16..143 = argv[] (16 ptrs × 8 B)
# 144..399 = path buf (256 B, null-term)
# 400..2447 = arg bufs (8 args × 256 B)
#
# Limits: 8 args, 255 bytes each. Strings only.
# ============================================================
bi_spawn_process_stdio:
pushq %r13
pushq %r14
subq $4104, %rsp # 4096 + 8 to re-align rsp to 16
movq %rsp, %rbp # %rbp = scratch base
# arg 0: path copy to scratch+144
GETARG %rdi
andq $-8, %rdi
movq (%rdi), %rcx
leaq 8(%rdi), %rdi
leaq 144(%rbp), %rsi
call copy_fname_to_stack
leaq 144(%rbp), %rax
movq %rax, 16(%rbp) # argv[0] = path
# arg 1: args list argv[1..N]
GETARG %r14
xorq %r13, %r13
incq %r13 # %r13 = arg index, start at 1
.sps_arg_loop:
cmpq $VAL_NIL, %r14
je .sps_args_done
cmpq $9, %r13
jge .sps_args_done
movq %r14, %rax
andq $-8, %rax
movq (%rax), %rdi # car
movq 8(%rax), %r14 # cdr
andq $-8, %rdi
movq (%rdi), %rcx
leaq 8(%rdi), %rdi
# arg buf addr = rbp + 400 + (i-1)*256
movq %r13, %rax
decq %rax
shlq $8, %rax
addq $400, %rax
addq %rbp, %rax
movq %rax, %rsi
pushq %r13
pushq %r14
call copy_fname_to_stack
popq %r14
popq %r13
# argv[i] = arg buf addr (recomputed)
movq %r13, %rax
shlq $3, %rax
addq $16, %rax
addq %rbp, %rax
movq %r13, %rdx
decq %rdx
shlq $8, %rdx
addq $400, %rdx
addq %rbp, %rdx
movq %rdx, (%rax)
incq %r13
jmp .sps_arg_loop
.sps_args_done:
movq %r13, %rax
shlq $3, %rax
addq $16, %rax
addq %rbp, %rax
movq $0, (%rax) # argv[N] = NULL
# pipe2(in_pipe, 0)
movq $SYS_PIPE2, %rax
movq %rbp, %rdi
xorq %rsi, %rsi
syscall
testq %rax, %rax
js .sps_fail
# pipe2(out_pipe, 0)
movq $SYS_PIPE2, %rax
leaq 8(%rbp), %rdi
xorq %rsi, %rsi
syscall
testq %rax, %rax
js .sps_close_in
# fork()
movq $SYS_FORK, %rax
syscall
testq %rax, %rax
js .sps_close_both
jz .sps_child
# parent
movl 0(%rbp), %edi # close in_pipe[0]
movq $SYS_CLOSE, %rax
syscall
movl 12(%rbp), %edi # close out_pipe[1]
movq $SYS_CLOSE, %rax
syscall
movl 4(%rbp), %edi # in_pipe[1] parent writes
call encode_port
movq %rax, %r13 # save stdin-port
movl 8(%rbp), %edi # out_pipe[0] parent reads
call encode_port
movq %rax, %rsi # cdr
movq %r13, %rdi # car
call make_pair
addq $4104, %rsp
popq %r14
popq %r13
RET_VAL
.sps_child:
movl 0(%rbp), %edi
xorq %rsi, %rsi
movq $SYS_DUP2, %rax
syscall
movl 12(%rbp), %edi
movq $1, %rsi
movq $SYS_DUP2, %rax
syscall
movl 0(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
movl 4(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
movl 8(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
movl 12(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
leaq 144(%rbp), %rdi
leaq 16(%rbp), %rsi
xorq %rdx, %rdx
movq $SYS_EXECVE, %rax
syscall
movq $127, %rdi
movq $SYS_EXIT, %rax
syscall
.sps_close_both:
movl 8(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
movl 12(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
.sps_close_in:
movl 0(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
movl 4(%rbp), %edi
movq $SYS_CLOSE, %rax
syscall
.sps_fail:
movq $VAL_FALSE, %rax
addq $4104, %rsp
popq %r14
popq %r13
RET_VAL
# bi_flush_port: asm tier uses raw fds no userspace buffering, no-op.
bi_flush_port:
movq $VAL_VOID, %rax
RET_VAL
# ============================================================ # ============================================================
# list_reverse: %rdi = list -> %rax = reversed list # list_reverse: %rdi = list -> %rax = reversed list
# ============================================================ # ============================================================