ludic/runtime/native/process_win.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

196 lines
6.4 KiB
LLVM

; ============================================================================
; process_win.ll — child processes behind Process.* (Windows, CreateProcessW).
;
; process.ll's interface. ludicc links it when a Windows program uses Process.*.
;
; lu_proc_spawn(path, argv, cmdline) -> int CreateProcessW(path, cmdline): a handle
; 0..15, or -1. argv is POSIX's and unused;
; cmdline was quoted by process.ludic.
; lu_proc_poll(h) -> int -1 while running, else GetExitCodeProcess
; lu_proc_kill(h) TerminateProcess, exit code 137
; lu_proc_free(h) CloseHandle
;
; The child inherits the environment and the working directory and no handles, and is
; started with CREATE_NO_WINDOW: a GUI parent starting a console program must not flash a
; console window up (a GUI child ignores the flag). Paths and the command line cross in
; UTF-8 and are converted to UTF-16 here.
;
; An exit code is a DWORD. One that reads as -1 (0xFFFFFFFF) is reported as 255, so -1
; always means "still running"; an NTSTATUS crash code such as 0xC0000005 arrives as a
; negative int.
; ============================================================================
declare ptr @malloc(i64)
declare void @free(ptr)
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
declare i32 @MultiByteToWideChar(i32, i32, ptr, i32, ptr, i32)
declare i32 @CreateProcessW(ptr, ptr, ptr, ptr, i32, i32, ptr, ptr, ptr, ptr)
declare i32 @WaitForSingleObject(ptr, i32)
declare i32 @GetExitCodeProcess(ptr, ptr)
declare i32 @TerminateProcess(ptr, i32)
declare i32 @CloseHandle(ptr)
@P_handle = internal global [16 x ptr] zeroinitializer
@P_state = internal global [16 x i32] zeroinitializer ; 0 free, 1 running, 2 ended
@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
}
; a fresh NUL-terminated UTF-16 copy of a UTF-8 string (CP_UTF8 = 65001), or null
define internal ptr @pr_wide(ptr %s) {
entry:
%n = call i32 @MultiByteToWideChar(i32 65001, i32 0, ptr %s, i32 -1, ptr null, i32 0)
%bad = icmp sle i32 %n, 0
br i1 %bad, label %none, label %conv
none:
ret ptr null
conv:
%n64 = sext i32 %n to i64
%bytes = mul i64 %n64, 2
%w = call ptr @malloc(i64 %bytes)
%r = call i32 @MultiByteToWideChar(i32 65001, i32 0, ptr %s, i32 -1, ptr %w, i32 %n)
ret ptr %w
}
define i32 @lu_proc_spawn(ptr %path, ptr %argv, ptr %cmdline) {
entry:
%si = alloca [104 x i8], align 8 ; STARTUPINFOW
%pi = alloca [24 x i8], align 8 ; PROCESS_INFORMATION
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
%free = icmp eq i32 %st, 0
br i1 %free, label %conv, label %next
next:
%i1 = add i32 %i, 1
%more = icmp slt i32 %i1, 16
br i1 %more, label %find, label %fail
conv:
%wpath = call ptr @pr_wide(ptr %path)
%wcmd = call ptr @pr_wide(ptr %cmdline)
%np = icmp eq ptr %wpath, null
%nc = icmp eq ptr %wcmd, null
%either = or i1 %np, %nc
br i1 %either, label %cleanup_fail, label %spawn
spawn:
call void @llvm.memset.p0.i64(ptr %si, i8 0, i64 104, i1 false)
store i32 104, ptr %si
call void @llvm.memset.p0.i64(ptr %pi, i8 0, i64 24, i1 false)
; CREATE_NO_WINDOW = 0x08000000; no inherited handles, the parent's environment and directory
%ok = call i32 @CreateProcessW(ptr %wpath, ptr %wcmd, ptr null, ptr null, i32 0, i32 134217728, ptr null, ptr null, ptr %si, ptr %pi)
%failed = icmp eq i32 %ok, 0
br i1 %failed, label %cleanup_fail, label %keep
keep:
call void @free(ptr %wpath)
call void @free(ptr %wcmd)
%hproc = load ptr, ptr %pi
%thp = getelementptr i8, ptr %pi, i64 8
%hthr = load ptr, ptr %thp
%c = call i32 @CloseHandle(ptr %hthr)
%hp = getelementptr [16 x ptr], ptr @P_handle, i32 0, i32 %i
store ptr %hproc, ptr %hp
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
cleanup_fail:
%fp = icmp eq ptr %wpath, null
br i1 %fp, label %fc, label %freep
freep:
call void @free(ptr %wpath)
br label %fc
fc:
%fcm = icmp eq ptr %wcmd, null
br i1 %fcm, label %fail, label %freec
freec:
call void @free(ptr %wcmd)
br label %fail
fail:
ret i32 -1
}
define i32 @lu_proc_poll(i32 %h) {
entry:
%code = alloca i32, align 4
%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
%cp = getelementptr [16 x i32], ptr @P_code, i32 0, i32 %h
%done = icmp eq i32 %st, 2
br i1 %done, label %cached, label %isrun
isrun:
%run = icmp eq i32 %st, 1
br i1 %run, label %ask, label %none
ask:
%hp = getelementptr [16 x ptr], ptr @P_handle, i32 0, i32 %h
%hproc = load ptr, ptr %hp
%w = call i32 @WaitForSingleObject(ptr %hproc, i32 0)
%ended = icmp eq i32 %w, 0 ; WAIT_OBJECT_0
br i1 %ended, label %getcode, label %running
running:
ret i32 -1
getcode:
store i32 255, ptr %code
%g = call i32 @GetExitCodeProcess(ptr %hproc, ptr %code)
%v = load i32, ptr %code
%neg1 = icmp eq i32 %v, -1
%v2 = select i1 %neg1, i32 255, i32 %v
%gfail = icmp eq i32 %g, 0
%v3 = select i1 %gfail, i32 255, i32 %v2
store i32 %v3, ptr %cp
store i32 2, ptr %stp
ret i32 %v3
cached:
%c = load i32, ptr %cp
ret i32 %c
none:
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:
%hp = getelementptr [16 x ptr], ptr @P_handle, i32 0, i32 %h
%hproc = load ptr, ptr %hp
%t = call i32 @TerminateProcess(ptr %hproc, i32 137)
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
%used = icmp ne i32 %st, 0
br i1 %used, label %close, label %out
close:
%hp = getelementptr [16 x ptr], ptr @P_handle, i32 0, i32 %h
%hproc = load ptr, ptr %hp
%c = call i32 @CloseHandle(ptr %hproc)
store ptr null, ptr %hp
store i32 0, ptr %stp
br label %out
out:
ret void
}