ludic/runtime/native/process.ll
Orkuncakilkaya 43d5eb5b88 feat(launcher): Process.*, Http.save_to/received/expected, App.window_hide/show
Process.spawn/poll/kill/free - non-blocking child processes with no shell: posix_spawn on
macOS (process.ll), CreateProcessW with MSVC-quoted arguments and no console window on
Windows (process_win.ll), linked only when a program uses Process.*.

Http.save_to streams a response body into a file (NSURLSession with a run-time delegate
class on macOS, the WinHTTP read loop on Windows); Http.received / Http.expected report
progress while it is pending. Freeing a pending request cancels it and parks the slot
until the worker has finished.

App.window_hide / App.window_show take the game's window off the screen and back without
closing it; the run goes on while hidden. Docs, examples, tests and a changeset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:32:23 +03:00

187 lines
5.8 KiB
LLVM

; ============================================================================
; process.ll — child processes behind Process.* (macOS / POSIX, posix_spawn).
;
; Linked into any program that uses Process.* (selfhost/main.ludic, tools/ludic-cli/build.ludic).
; process_win.ll is the same interface over CreateProcessW.
;
; lu_proc_spawn(path, argv, cmdline) -> int start path with the NULL-terminated argv
; (argv[0] = path) and this process's
; environment and working directory; a handle
; 0..15, or -1. cmdline is Windows' and unused.
; lu_proc_poll(h) -> int -1 while running, else the exit code
; (128 + signal when a signal ended it)
; lu_proc_kill(h) SIGKILL
; lu_proc_free(h) drop the handle; a running child is reaped
; later, by whichever call next looks
;
; A slot is free (0), running (1), finished with its code cached (2), or let go while still
; running (3) - kept only so the child is reaped rather than left a zombie.
; ============================================================================
declare i32 @posix_spawn(ptr, ptr, ptr, ptr, ptr, ptr)
declare ptr @_NSGetEnviron()
declare i32 @waitpid(i32, ptr, i32)
declare i32 @kill(i32, i32)
@P_pid = internal global [16 x i32] zeroinitializer
@P_state = internal global [16 x i32] zeroinitializer
@P_code = internal global [16 x i32] zeroinitializer
define internal i1 @pr_bad(i32 %h) {
entry:
%lo = icmp slt i32 %h, 0
%hi = icmp sgt i32 %h, 15
%bad = or i1 %lo, %hi
ret i1 %bad
}
; Ask once, without waiting, whether slot i's child has ended. For a running slot the code
; is cached (state 2); a let-go slot becomes free. Returns 1 when it has ended.
define internal i32 @pr_reap(i32 %i) {
entry:
%status = alloca i32, align 4
%stp = getelementptr [16 x i32], ptr @P_state, i32 0, i32 %i
%st = load i32, ptr %stp
%run = icmp eq i32 %st, 1
%gone = icmp eq i32 %st, 3
%live = or i1 %run, %gone
br i1 %live, label %ask, label %no
ask:
%pidp = getelementptr [16 x i32], ptr @P_pid, i32 0, i32 %i
%pid = load i32, ptr %pidp
store i32 0, ptr %status
%w = call i32 @waitpid(i32 %pid, ptr %status, i32 1) ; WNOHANG
%still = icmp eq i32 %w, 0
br i1 %still, label %no, label %ended
ended:
; waitpid failing (the child is not ours to wait for any more) reads as 255
%err = icmp slt i32 %w, 0
%s = load i32, ptr %status
%sig = and i32 %s, 127
%exited = icmp eq i32 %sig, 0
%hi = lshr i32 %s, 8
%ecode = and i32 %hi, 255
%scode = add i32 %sig, 128
%c0 = select i1 %exited, i32 %ecode, i32 %scode
%code = select i1 %err, i32 255, i32 %c0
%cp = getelementptr [16 x i32], ptr @P_code, i32 0, i32 %i
store i32 %code, ptr %cp
%next = select i1 %run, i32 2, i32 0
store i32 %next, ptr %stp
ret i32 1
no:
ret i32 0
}
define i32 @lu_proc_spawn(ptr %path, ptr %argv, ptr %cmdline) {
entry:
%pidv = alloca i32, align 4
br label %find
find:
%i = phi i32 [ 0, %entry ], [ %i1, %next ]
%stp = getelementptr [16 x i32], ptr @P_state, i32 0, i32 %i
%st = load i32, ptr %stp
%isgone = icmp eq i32 %st, 3
br i1 %isgone, label %reap, label %check
reap:
%r = call i32 @pr_reap(i32 %i)
br label %check
check:
%st2 = load i32, ptr %stp
%free = icmp eq i32 %st2, 0
br i1 %free, label %spawn, label %next
next:
%i1 = add i32 %i, 1
%more = icmp slt i32 %i1, 16
br i1 %more, label %find, label %fail
spawn:
%envpp = call ptr @_NSGetEnviron()
%envp = load ptr, ptr %envpp
store i32 0, ptr %pidv
%rc = call i32 @posix_spawn(ptr %pidv, ptr %path, ptr null, ptr null, ptr %argv, ptr %envp)
%ok = icmp eq i32 %rc, 0
br i1 %ok, label %keep, label %fail
keep:
%pid = load i32, ptr %pidv
%pidp = getelementptr [16 x i32], ptr @P_pid, i32 0, i32 %i
store i32 %pid, ptr %pidp
store i32 1, ptr %stp
%cp = getelementptr [16 x i32], ptr @P_code, i32 0, i32 %i
store i32 -1, ptr %cp
ret i32 %i
fail:
ret i32 -1
}
define i32 @lu_proc_poll(i32 %h) {
entry:
%bad = call i1 @pr_bad(i32 %h)
br i1 %bad, label %none, label %look
look:
%stp = getelementptr [16 x i32], ptr @P_state, i32 0, i32 %h
%st = load i32, ptr %stp
%run = icmp eq i32 %st, 1
br i1 %run, label %ask, label %known
ask:
%r = call i32 @pr_reap(i32 %h)
%ended = icmp eq i32 %r, 1
br i1 %ended, label %known, label %running
running:
ret i32 -1
known:
%st2 = load i32, ptr %stp
%done = icmp eq i32 %st2, 2
br i1 %done, label %code, label %none
code:
%cp = getelementptr [16 x i32], ptr @P_code, i32 0, i32 %h
%c = load i32, ptr %cp
ret i32 %c
none:
; not a handle: nothing is running under it
ret i32 255
}
define void @lu_proc_kill(i32 %h) {
entry:
%bad = call i1 @pr_bad(i32 %h)
br i1 %bad, label %out, label %look
look:
%stp = getelementptr [16 x i32], ptr @P_state, i32 0, i32 %h
%st = load i32, ptr %stp
%run = icmp eq i32 %st, 1
br i1 %run, label %go, label %out
go:
%pidp = getelementptr [16 x i32], ptr @P_pid, i32 0, i32 %h
%pid = load i32, ptr %pidp
%k = call i32 @kill(i32 %pid, i32 9)
br label %out
out:
ret void
}
define void @lu_proc_free(i32 %h) {
entry:
%bad = call i1 @pr_bad(i32 %h)
br i1 %bad, label %out, label %look
look:
%stp = getelementptr [16 x i32], ptr @P_state, i32 0, i32 %h
%st = load i32, ptr %stp
%run = icmp eq i32 %st, 1
br i1 %run, label %try, label %drop
try:
%r = call i32 @pr_reap(i32 %h)
%ended = icmp eq i32 %r, 1
br i1 %ended, label %drop, label %orphan
orphan:
store i32 3, ptr %stp
br label %out
drop:
%st2 = load i32, ptr %stp
%gone = icmp eq i32 %st2, 3
br i1 %gone, label %out, label %clear
clear:
store i32 0, ptr %stp
br label %out
out:
ret void
}