diff --git a/asm/lumbda.s b/asm/lumbda.s index e30c8b2..1a46201 100644 --- a/asm/lumbda.s +++ b/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