asm tier: fork-self + waitpid-nonblock + exit-immediate + sleep primitives
Cross-tier API parity with c-tier (81ac49e) + python-tier — all three lumbda runtimes now share the substrate for fork-per-accept patterns. Implementation: direct syscalls (no libc): - SYS_FORK=57 → bi_forkself, returns 0/pid via make_int - SYS_WAIT4=61 + WNOHANG=1 → bi_waitpid_nonblock, returns pid or 0 - SYS_EXIT=60 → bi_exit_immediate (same as bi_exit on asm — no atexit to bypass; present for cross-tier API parity) - SYS_NANOSLEEP=35 → bi_sleep, stack-allocated timespec (tv_sec=N, tv_nsec=0), returns VAL_VOID Built + tested in vm-runner.sh VM (Ubuntu 2G/2vCPU): both lumbda and lumbda-gc + fork-cycle test = 3/3 children reap clean, exit- immediate returns to parent waitpid correctly. Same behavioral contract as c-tier (commit81ac49e) and python-tier.
This commit is contained in:
parent
092a8ed741
commit
7c99df99cf
1 changed files with 91 additions and 2 deletions
93
asm/lumbda.s
93
asm/lumbda.s
|
|
@ -43,6 +43,9 @@
|
|||
.equ SYS_EXECVE,59
|
||||
.equ SYS_DUP2, 33
|
||||
.equ SYS_UNLINK, 87
|
||||
.equ SYS_WAIT4, 61
|
||||
.equ SYS_NANOSLEEP, 35
|
||||
.equ WNOHANG, 1
|
||||
|
||||
.equ AF_INET, 2
|
||||
.equ SOCK_STREAM,1
|
||||
|
|
@ -227,11 +230,19 @@
|
|||
.equ BI_ARENA_SET_MODE, 128
|
||||
.equ BI_STRTOSYM, 129
|
||||
.equ BI_DELETEFILE, 130
|
||||
.equ BI_COUNT, 131
|
||||
.equ BI_FORKSELF, 131
|
||||
.equ BI_WAITPIDNB, 132
|
||||
.equ BI_EXITIMM, 133
|
||||
.equ BI_SLEEP, 134
|
||||
.equ BI_COUNT, 135
|
||||
.else
|
||||
.equ BI_STRTOSYM, 124
|
||||
.equ BI_DELETEFILE, 125
|
||||
.equ BI_COUNT, 126
|
||||
.equ BI_FORKSELF, 126
|
||||
.equ BI_WAITPIDNB, 127
|
||||
.equ BI_EXITIMM, 128
|
||||
.equ BI_SLEEP, 129
|
||||
.equ BI_COUNT, 130
|
||||
.endif
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -394,6 +405,10 @@ bn_gensym: .byte 6; .ascii "gensym"
|
|||
bn_exit: .byte 4; .ascii "exit"
|
||||
bn_values: .byte 6; .ascii "values"
|
||||
bn_cwv: .byte 16; .ascii "call-with-values"
|
||||
bn_forkself: .byte 9; .ascii "fork-self"
|
||||
bn_waitpidnb: .byte 16; .ascii "waitpid-nonblock"
|
||||
bn_exitimm: .byte 14; .ascii "exit-immediate"
|
||||
bn_sleep: .byte 5; .ascii "sleep"
|
||||
.ifdef GC_NAIVE
|
||||
bn_gccollect: .byte 10; .ascii "gc-collect"
|
||||
bn_gcstats: .byte 8; .ascii "gc-stats"
|
||||
|
|
@ -514,6 +529,7 @@ bi_names:
|
|||
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
|
||||
.endif
|
||||
.quad bn_strtosym, bn_deletefile
|
||||
.quad bn_forkself, bn_waitpidnb, bn_exitimm, bn_sleep
|
||||
|
||||
# Error messages
|
||||
err_unbound: .ascii "Error: unbound variable: "
|
||||
|
|
@ -4260,6 +4276,14 @@ eval_list:
|
|||
je bi_gensym
|
||||
cmpq $BI_EXIT, %rax
|
||||
je bi_exit
|
||||
cmpq $BI_FORKSELF, %rax
|
||||
je bi_forkself
|
||||
cmpq $BI_WAITPIDNB, %rax
|
||||
je bi_waitpid_nonblock
|
||||
cmpq $BI_EXITIMM, %rax
|
||||
je bi_exit_immediate
|
||||
cmpq $BI_SLEEP, %rax
|
||||
je bi_sleep
|
||||
cmpq $BI_VALUES, %rax
|
||||
je bi_values
|
||||
cmpq $BI_CWV, %rax
|
||||
|
|
@ -5148,6 +5172,71 @@ bi_exit:
|
|||
movq $SYS_EXIT, %rax
|
||||
syscall
|
||||
|
||||
# (fork-self) — fork() wrapper. Child sees 0, parent sees pid, -1/#f on fail.
|
||||
# Caller MUST use exit-immediate in child to avoid dual-cleanup hang.
|
||||
bi_forkself:
|
||||
movq $SYS_FORK, %rax
|
||||
syscall
|
||||
# %rax = -errno on fail, 0 in child, pid in parent. asm tier has
|
||||
# no #f-on-fail convention here; return -errno as a fixnum (will
|
||||
# be negative — caller can check). For our gpu-worker pattern,
|
||||
# success path (0/pid) is the only path we exercise.
|
||||
movq %rax, %rdi
|
||||
call make_int
|
||||
RET_VAL
|
||||
|
||||
# (waitpid-nonblock) — reap one completed child non-blocking. Returns pid or 0.
|
||||
bi_waitpid_nonblock:
|
||||
movq $-1, %rdi # pid = -1 (any child)
|
||||
xorq %rsi, %rsi # status* = NULL
|
||||
movq $WNOHANG, %rdx # options
|
||||
xorq %r10, %r10 # rusage* = NULL
|
||||
movq $SYS_WAIT4, %rax
|
||||
syscall
|
||||
# %rax <= 0 means no child reaped (or error) — return 0.
|
||||
testq %rax, %rax
|
||||
jg .bwn_ok
|
||||
xorq %rax, %rax
|
||||
.bwn_ok:
|
||||
movq %rax, %rdi
|
||||
call make_int
|
||||
RET_VAL
|
||||
|
||||
# (exit-immediate [code]) — _exit() wrapper, identical to bi_exit on asm
|
||||
# tier (no atexit handlers / stdio buffers to bypass). Present for
|
||||
# cross-tier API parity with c-tier / python-tier where the distinction
|
||||
# matters.
|
||||
bi_exit_immediate:
|
||||
cmpq $VAL_NIL, %r12
|
||||
je .beim_zero
|
||||
GETARG %rax
|
||||
sarq $3, %rax
|
||||
movq %rax, %rdi
|
||||
movq $SYS_EXIT, %rax
|
||||
syscall
|
||||
.beim_zero:
|
||||
xorq %rdi, %rdi
|
||||
movq $SYS_EXIT, %rax
|
||||
syscall
|
||||
|
||||
# (sleep N) — wall-clock sleep for N seconds via nanosleep. Returns void.
|
||||
# Uses stack-allocated timespec.
|
||||
bi_sleep:
|
||||
GETARG %rax
|
||||
sarq $3, %rax # untag fixnum
|
||||
# Build timespec on stack: tv_sec (8 bytes), tv_nsec (8 bytes)
|
||||
subq $16, %rsp
|
||||
movq %rax, 0(%rsp) # tv_sec = N
|
||||
xorq %rax, %rax
|
||||
movq %rax, 8(%rsp) # tv_nsec = 0
|
||||
movq %rsp, %rdi # req timespec
|
||||
xorq %rsi, %rsi # rem = NULL
|
||||
movq $SYS_NANOSLEEP, %rax
|
||||
syscall
|
||||
addq $16, %rsp
|
||||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
|
||||
# (values . xs) — packs its args as a multi-value packet.
|
||||
# Calling convention: if xs has exactly one element, that element is
|
||||
# returned directly (so code that ignores multi-value semantics still
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue