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>
708 lines
28 KiB
LLVM
708 lines
28 KiB
LLVM
; ============================================================================
|
|
; http.ll — the macOS HTTP transport, written in LLVM IR.
|
|
;
|
|
; Like cocoa.ll / audio.ll, no Objective-C and no C source: this drives
|
|
; NSURLConnection through the objc runtime's C ABI, and runs the (blocking)
|
|
; request on a detached pthread so the game's frame loop never stalls. TLS is
|
|
; the system's, on by default with certificate verification — an https:// URL
|
|
; just works. ludicc splices this in and links Foundation only when a program
|
|
; uses Http.*.
|
|
;
|
|
; A small fixed pool of slots (0..HTTP_SLOTS-1) each hold one in-flight request:
|
|
; the pending NSURLRequest, the worker's pthread id, and — once the worker
|
|
; finishes — the status code, a malloc'd copy of the body bytes, and the
|
|
; retained NSHTTPURLResponse (so header queries can be answered later). The done
|
|
; flag is an atomic released by the worker and acquired by the poller, so the
|
|
; body / status / response the poller reads are the ones the worker wrote.
|
|
;
|
|
; Exposed to Ludic (through the http_* intrinsics):
|
|
; http_req_new(method, url) -> req http_req_header(req, name, val)
|
|
; http_req_body(req, bytes, len) http_send(slot, req)
|
|
; http_done(slot) -> bool http_status(slot) -> int
|
|
; http_body(slot) -> ptr http_blen(slot) -> int
|
|
; http_header(slot, name) -> cstr http_free(slot)
|
|
;
|
|
; Streaming (Http.save_to), which a synchronous NSURLConnection cannot do:
|
|
; hs_save_to(slot, path) hs_received(slot) -> int
|
|
; hs_expected(slot) -> int hs_cancel(slot)
|
|
; A slot given a path before hs_send is sent through one shared NSURLSession whose
|
|
; delegate - a class built at run time, like cocoa.ll's view - appends each chunk to the
|
|
; file as it arrives (URLSession:dataTask:didReceiveData:) and publishes the status and the
|
|
; done flag when the task ends (URLSession:task:didCompleteWithError:). The callbacks run
|
|
; on the session's own serial queue; the byte counters are atomics the poller reads.
|
|
; ============================================================================
|
|
|
|
declare ptr @objc_getClass(ptr)
|
|
declare ptr @sel_registerName(ptr)
|
|
declare ptr @objc_msgSend(ptr, ptr, ...)
|
|
declare ptr @malloc(i64)
|
|
declare void @free(ptr)
|
|
declare ptr @memcpy(ptr, ptr, i64)
|
|
declare i32 @pthread_create(ptr, ptr, ptr, ptr)
|
|
declare i32 @pthread_detach(ptr)
|
|
declare ptr @strdup(ptr)
|
|
declare ptr @fopen(ptr, ptr)
|
|
declare i64 @fwrite(ptr, i64, i64, ptr)
|
|
declare i32 @fclose(ptr)
|
|
declare ptr @objc_allocateClassPair(ptr, ptr, i64)
|
|
declare void @objc_registerClassPair(ptr)
|
|
declare i8 @class_addMethod(ptr, ptr, ptr, ptr)
|
|
declare ptr @objc_autoreleasePoolPush()
|
|
declare void @objc_autoreleasePoolPop(ptr)
|
|
|
|
@.h_nsurl = private unnamed_addr constant [6 x i8] c"NSURL\00"
|
|
@.h_nsstr = private unnamed_addr constant [9 x i8] c"NSString\00"
|
|
@.h_nsdata = private unnamed_addr constant [7 x i8] c"NSData\00"
|
|
@.h_murl = private unnamed_addr constant [20 x i8] c"NSMutableURLRequest\00"
|
|
@.h_conn = private unnamed_addr constant [16 x i8] c"NSURLConnection\00"
|
|
@.h_pool = private unnamed_addr constant [18 x i8] c"NSAutoreleasePool\00"
|
|
|
|
@.h_urlws = private unnamed_addr constant [15 x i8] c"URLWithString:\00"
|
|
@.h_utf8 = private unnamed_addr constant [22 x i8] c"stringWithUTF8String:\00"
|
|
@.h_reqw = private unnamed_addr constant [16 x i8] c"requestWithURL:\00"
|
|
@.h_smeth = private unnamed_addr constant [15 x i8] c"setHTTPMethod:\00"
|
|
@.h_shdr = private unnamed_addr constant [29 x i8] c"setValue:forHTTPHeaderField:\00"
|
|
@.h_sbody = private unnamed_addr constant [13 x i8] c"setHTTPBody:\00"
|
|
@.h_dwbl = private unnamed_addr constant [22 x i8] c"dataWithBytes:length:\00"
|
|
@.h_bytes = private unnamed_addr constant [6 x i8] c"bytes\00"
|
|
@.h_length = private unnamed_addr constant [7 x i8] c"length\00"
|
|
@.h_ssync = private unnamed_addr constant [48 x i8] c"sendSynchronousRequest:returningResponse:error:\00"
|
|
@.h_status = private unnamed_addr constant [11 x i8] c"statusCode\00"
|
|
@.h_vhdr = private unnamed_addr constant [25 x i8] c"valueForHTTPHeaderField:\00"
|
|
@.h_utf8g = private unnamed_addr constant [11 x i8] c"UTF8String\00"
|
|
@.h_alloc = private unnamed_addr constant [6 x i8] c"alloc\00"
|
|
@.h_init = private unnamed_addr constant [5 x i8] c"init\00"
|
|
@.h_drain = private unnamed_addr constant [6 x i8] c"drain\00"
|
|
@.h_retain = private unnamed_addr constant [7 x i8] c"retain\00"
|
|
@.h_rel = private unnamed_addr constant [8 x i8] c"release\00"
|
|
@.h_empty = private unnamed_addr constant [1 x i8] c"\00"
|
|
|
|
@.h_nsobj = private unnamed_addr constant [9 x i8] c"NSObject\00"
|
|
@.h_dlgcls = private unnamed_addr constant [18 x i8] c"LudicHttpDelegate\00"
|
|
@.h_sesscl = private unnamed_addr constant [13 x i8] c"NSURLSession\00"
|
|
@.h_cfgcl = private unnamed_addr constant [26 x i8] c"NSURLSessionConfiguration\00"
|
|
@.h_defcfg = private unnamed_addr constant [28 x i8] c"defaultSessionConfiguration\00"
|
|
@.h_swc = private unnamed_addr constant [49 x i8] c"sessionWithConfiguration:delegate:delegateQueue:\00"
|
|
@.h_dtwr = private unnamed_addr constant [21 x i8] c"dataTaskWithRequest:\00"
|
|
@.h_resume = private unnamed_addr constant [7 x i8] c"resume\00"
|
|
@.h_cancel = private unnamed_addr constant [7 x i8] c"cancel\00"
|
|
@.h_respg = private unnamed_addr constant [9 x i8] c"response\00"
|
|
@.h_cbexp = private unnamed_addr constant [30 x i8] c"countOfBytesExpectedToReceive\00"
|
|
@.h_ecl = private unnamed_addr constant [22 x i8] c"expectedContentLength\00"
|
|
@.h_seldat = private unnamed_addr constant [36 x i8] c"URLSession:dataTask:didReceiveData:\00"
|
|
@.h_seldon = private unnamed_addr constant [38 x i8] c"URLSession:task:didCompleteWithError:\00"
|
|
@.h_enc5 = private unnamed_addr constant [7 x i8] c"v@:@@@\00"
|
|
@.h_rts = private unnamed_addr constant [20 x i8] c"respondsToSelector:\00"
|
|
@.h_wb = private unnamed_addr constant [3 x i8] c"wb\00"
|
|
@.h_aenc = private unnamed_addr constant [16 x i8] c"Accept-Encoding\00"
|
|
@.h_ident = private unnamed_addr constant [9 x i8] c"identity\00"
|
|
|
|
; HTTP_SLOTS = 8
|
|
@H_req = internal global [8 x ptr] zeroinitializer
|
|
@H_resp = internal global [8 x ptr] zeroinitializer
|
|
@H_body = internal global [8 x ptr] zeroinitializer
|
|
@H_blen = internal global [8 x i32] zeroinitializer
|
|
@H_status = internal global [8 x i32] zeroinitializer
|
|
@H_done = internal global [8 x i32] zeroinitializer
|
|
; streaming (Http.save_to)
|
|
@H_path = internal global [8 x ptr] zeroinitializer ; strdup'd file path, set before send
|
|
@H_file = internal global [8 x ptr] zeroinitializer ; FILE* while the task runs
|
|
@H_task = internal global [8 x ptr] zeroinitializer ; the retained NSURLSessionDataTask
|
|
@H_recv = internal global [8 x i32] zeroinitializer ; body bytes written (atomic)
|
|
@H_exp = internal global [8 x i32] zeroinitializer ; Content-Length or -1 (atomic)
|
|
@H_werr = internal global [8 x i32] zeroinitializer ; a write to the file failed
|
|
@H_strm = internal global [8 x i32] zeroinitializer ; this slot was sent streaming
|
|
@H_sess = internal global ptr null ; the shared streaming session
|
|
|
|
; --- request construction ---------------------------------------------------
|
|
|
|
; Build an NSMutableURLRequest for `method` + `url`; returns it retained.
|
|
define ptr @hs_req_new(ptr %method, ptr %url) {
|
|
entry:
|
|
%strcls = call ptr @objc_getClass(ptr @.h_nsstr)
|
|
%sel_u = call ptr @sel_registerName(ptr @.h_utf8)
|
|
%nsurlstr = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr %url)
|
|
%urlcls = call ptr @objc_getClass(ptr @.h_nsurl)
|
|
%sel_uw = call ptr @sel_registerName(ptr @.h_urlws)
|
|
%nsurl = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %urlcls, ptr %sel_uw, ptr %nsurlstr)
|
|
%nou = icmp eq ptr %nsurl, null
|
|
br i1 %nou, label %fail, label %mk
|
|
mk:
|
|
%reqcls = call ptr @objc_getClass(ptr @.h_murl)
|
|
%sel_rw = call ptr @sel_registerName(ptr @.h_reqw)
|
|
%req = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %reqcls, ptr %sel_rw, ptr %nsurl)
|
|
%nor = icmp eq ptr %req, null
|
|
br i1 %nor, label %fail, label %setm
|
|
setm:
|
|
%mstr = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr %method)
|
|
%sel_m = call ptr @sel_registerName(ptr @.h_smeth)
|
|
%rm = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_m, ptr %mstr)
|
|
; retain so it survives the caller's autorelease context until http_send.
|
|
%sel_ret = call ptr @sel_registerName(ptr @.h_retain)
|
|
%rr = call ptr (ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_ret)
|
|
ret ptr %req
|
|
fail:
|
|
ret ptr null
|
|
}
|
|
|
|
define void @hs_req_header(ptr %req, ptr %name, ptr %val) {
|
|
entry:
|
|
%nil = icmp eq ptr %req, null
|
|
br i1 %nil, label %out, label %go
|
|
go:
|
|
%strcls = call ptr @objc_getClass(ptr @.h_nsstr)
|
|
%sel_u = call ptr @sel_registerName(ptr @.h_utf8)
|
|
%n = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr %name)
|
|
%v = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr %val)
|
|
%sel_sh = call ptr @sel_registerName(ptr @.h_shdr)
|
|
%r = call ptr (ptr, ptr, ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_sh, ptr %v, ptr %n)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
define void @hs_req_body(ptr %req, ptr %bytes, i32 %len) {
|
|
entry:
|
|
%nil = icmp eq ptr %req, null
|
|
br i1 %nil, label %out, label %go
|
|
go:
|
|
%datacls = call ptr @objc_getClass(ptr @.h_nsdata)
|
|
%sel_d = call ptr @sel_registerName(ptr @.h_dwbl)
|
|
%l64 = sext i32 %len to i64
|
|
%data = call ptr (ptr, ptr, ptr, i64) @objc_msgSend(ptr %datacls, ptr %sel_d, ptr %bytes, i64 %l64)
|
|
%sel_sb = call ptr @sel_registerName(ptr @.h_sbody)
|
|
%r = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_sb, ptr %data)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; --- the worker + dispatch --------------------------------------------------
|
|
|
|
; The detached worker: run the blocking request for one slot and publish the
|
|
; result. arg is the slot index encoded as a pointer.
|
|
define ptr @hs_worker(ptr %arg) {
|
|
entry:
|
|
%slot64 = ptrtoint ptr %arg to i64
|
|
%slot = trunc i64 %slot64 to i32
|
|
%poolcls = call ptr @objc_getClass(ptr @.h_pool)
|
|
%sel_a = call ptr @sel_registerName(ptr @.h_alloc)
|
|
%p0 = call ptr (ptr, ptr) @objc_msgSend(ptr %poolcls, ptr %sel_a)
|
|
%sel_i = call ptr @sel_registerName(ptr @.h_init)
|
|
%pool = call ptr (ptr, ptr) @objc_msgSend(ptr %p0, ptr %sel_i)
|
|
|
|
%reqp = getelementptr [8 x ptr], ptr @H_req, i32 0, i32 %slot
|
|
%req = load ptr, ptr %reqp
|
|
|
|
%respslot = alloca ptr
|
|
%errslot = alloca ptr
|
|
store ptr null, ptr %respslot
|
|
store ptr null, ptr %errslot
|
|
%conncls = call ptr @objc_getClass(ptr @.h_conn)
|
|
%sel_ss = call ptr @sel_registerName(ptr @.h_ssync)
|
|
%data = call ptr (ptr, ptr, ptr, ptr, ptr) @objc_msgSend(ptr %conncls, ptr %sel_ss, ptr %req, ptr %respslot, ptr %errslot)
|
|
|
|
%nodata = icmp eq ptr %data, null
|
|
br i1 %nodata, label %publish, label %havebody
|
|
havebody:
|
|
%sel_len = call ptr @sel_registerName(ptr @.h_length)
|
|
%blen64 = call i64 (ptr, ptr) @objc_msgSend(ptr %data, ptr %sel_len)
|
|
%blen = trunc i64 %blen64 to i32
|
|
%sel_by = call ptr @sel_registerName(ptr @.h_bytes)
|
|
%src = call ptr (ptr, ptr) @objc_msgSend(ptr %data, ptr %sel_by)
|
|
; malloc blen+1 and copy, NUL-terminating so the Ludic side can read text.
|
|
%cap = add i64 %blen64, 1
|
|
%buf = call ptr @malloc(i64 %cap)
|
|
%cp = call ptr @memcpy(ptr %buf, ptr %src, i64 %blen64)
|
|
%term = getelementptr i8, ptr %buf, i64 %blen64
|
|
store i8 0, ptr %term
|
|
%bodyp = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
store ptr %buf, ptr %bodyp
|
|
%blenp = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
store i32 %blen, ptr %blenp
|
|
br label %publish
|
|
publish:
|
|
; status + retained response (for later header queries), from the out-param.
|
|
%resp = load ptr, ptr %respslot
|
|
%noresp = icmp eq ptr %resp, null
|
|
br i1 %noresp, label %done, label %withresp
|
|
withresp:
|
|
%sel_ret = call ptr @sel_registerName(ptr @.h_retain)
|
|
%rr = call ptr (ptr, ptr) @objc_msgSend(ptr %resp, ptr %sel_ret)
|
|
%respp = getelementptr [8 x ptr], ptr @H_resp, i32 0, i32 %slot
|
|
store ptr %resp, ptr %respp
|
|
%sel_st = call ptr @sel_registerName(ptr @.h_status)
|
|
%st64 = call i64 (ptr, ptr) @objc_msgSend(ptr %resp, ptr %sel_st)
|
|
%st = trunc i64 %st64 to i32
|
|
%stp = getelementptr [8 x i32], ptr @H_status, i32 0, i32 %slot
|
|
store i32 %st, ptr %stp
|
|
br label %done
|
|
done:
|
|
; release the request we retained in http_send, drain the pool, then publish
|
|
; the done flag last with release ordering so the reader sees the writes above.
|
|
%sel_rel = call ptr @sel_registerName(ptr @.h_rel)
|
|
%rrel = call ptr (ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_rel)
|
|
%sel_dr = call ptr @sel_registerName(ptr @.h_drain)
|
|
%dr = call ptr (ptr, ptr) @objc_msgSend(ptr %pool, ptr %sel_dr)
|
|
%donep = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
store atomic i32 1, ptr %donep release, align 4
|
|
ret ptr null
|
|
}
|
|
|
|
; Start the request on `slot` running on a detached worker thread.
|
|
define void @hs_send(i32 %slot, ptr %req) {
|
|
entry:
|
|
; reset the slot
|
|
%donep = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
store atomic i32 0, ptr %donep release, align 4
|
|
%stp = getelementptr [8 x i32], ptr @H_status, i32 0, i32 %slot
|
|
store i32 0, ptr %stp
|
|
%bodyp = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
store ptr null, ptr %bodyp
|
|
%blenp = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
store i32 0, ptr %blenp
|
|
%respp = getelementptr [8 x ptr], ptr @H_resp, i32 0, i32 %slot
|
|
store ptr null, ptr %respp
|
|
%reqp = getelementptr [8 x ptr], ptr @H_req, i32 0, i32 %slot
|
|
store ptr %req, ptr %reqp
|
|
%pathp = getelementptr [8 x ptr], ptr @H_path, i32 0, i32 %slot
|
|
%path = load ptr, ptr %pathp
|
|
%strm = icmp ne ptr %path, null
|
|
br i1 %strm, label %stream, label %thread
|
|
stream:
|
|
call void @hs_stream_start(i32 %slot, ptr %req, ptr %path)
|
|
ret void
|
|
thread:
|
|
%tid = alloca i64
|
|
%arg = inttoptr i32 %slot to ptr
|
|
%rc = call i32 @pthread_create(ptr %tid, ptr null, ptr @hs_worker, ptr %arg)
|
|
%t = load i64, ptr %tid
|
|
%tp = inttoptr i64 %t to ptr
|
|
%d = call i32 @pthread_detach(ptr %tp)
|
|
ret void
|
|
}
|
|
|
|
; --- result accessors -------------------------------------------------------
|
|
|
|
define i32 @hs_done(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
%v = load atomic i32, ptr %p acquire, align 4
|
|
ret i32 %v
|
|
}
|
|
|
|
define i32 @hs_status(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_status, i32 0, i32 %slot
|
|
%v = load i32, ptr %p
|
|
ret i32 %v
|
|
}
|
|
|
|
define ptr @hs_body(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
%v = load ptr, ptr %p
|
|
ret ptr %v
|
|
}
|
|
|
|
define i32 @hs_blen(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
%v = load i32, ptr %p
|
|
ret i32 %v
|
|
}
|
|
|
|
; A response header value as a C string, or "" if absent / no response yet.
|
|
define ptr @hs_header(i32 %slot, ptr %name) {
|
|
entry:
|
|
%rp = getelementptr [8 x ptr], ptr @H_resp, i32 0, i32 %slot
|
|
%resp = load ptr, ptr %rp
|
|
%nores = icmp eq ptr %resp, null
|
|
br i1 %nores, label %empty, label %ask
|
|
ask:
|
|
; only an HTTP response has headers (a file:// one does not)
|
|
%sel_vh = call ptr @sel_registerName(ptr @.h_vhdr)
|
|
%sel_rts = call ptr @sel_registerName(ptr @.h_rts)
|
|
%has = call i8 (ptr, ptr, ptr) @objc_msgSend(ptr %resp, ptr %sel_rts, ptr %sel_vh)
|
|
%nohdr = icmp eq i8 %has, 0
|
|
br i1 %nohdr, label %empty, label %lookup
|
|
lookup:
|
|
%strcls = call ptr @objc_getClass(ptr @.h_nsstr)
|
|
%sel_u = call ptr @sel_registerName(ptr @.h_utf8)
|
|
%ns = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr %name)
|
|
%val = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %resp, ptr %sel_vh, ptr %ns)
|
|
%noval = icmp eq ptr %val, null
|
|
br i1 %noval, label %empty, label %tocstr
|
|
tocstr:
|
|
%sel_g = call ptr @sel_registerName(ptr @.h_utf8g)
|
|
%c = call ptr (ptr, ptr) @objc_msgSend(ptr %val, ptr %sel_g)
|
|
ret ptr %c
|
|
empty:
|
|
ret ptr @.h_empty
|
|
}
|
|
|
|
; Free a slot's owned resources (the body copy and the retained response).
|
|
define void @hs_free(i32 %slot) {
|
|
entry:
|
|
%bp = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
%b = load ptr, ptr %bp
|
|
%nb = icmp eq ptr %b, null
|
|
br i1 %nb, label %resp, label %fb
|
|
fb:
|
|
call void @free(ptr %b)
|
|
store ptr null, ptr %bp
|
|
br label %resp
|
|
resp:
|
|
%rp = getelementptr [8 x ptr], ptr @H_resp, i32 0, i32 %slot
|
|
%r = load ptr, ptr %rp
|
|
%nr = icmp eq ptr %r, null
|
|
br i1 %nr, label %out, label %rr
|
|
rr:
|
|
%sel_rel = call ptr @sel_registerName(ptr @.h_rel)
|
|
%x = call ptr (ptr, ptr) @objc_msgSend(ptr %r, ptr %sel_rel)
|
|
store ptr null, ptr %rp
|
|
br label %out
|
|
out:
|
|
call void @hs_stream_free(i32 %slot)
|
|
ret void
|
|
}
|
|
|
|
; --- streaming (Http.save_to) -------------------------------------------------
|
|
|
|
define void @hs_save_to(i32 %slot, ptr %path) {
|
|
entry:
|
|
%pp = getelementptr [8 x ptr], ptr @H_path, i32 0, i32 %slot
|
|
%old = load ptr, ptr %pp
|
|
%had = icmp ne ptr %old, null
|
|
br i1 %had, label %drop, label %set
|
|
drop:
|
|
call void @free(ptr %old)
|
|
br label %set
|
|
set:
|
|
%cp = call ptr @strdup(ptr %path)
|
|
store ptr %cp, ptr %pp
|
|
ret void
|
|
}
|
|
|
|
define i32 @hs_received(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
%v = load atomic i32, ptr %p acquire, align 4
|
|
ret i32 %v
|
|
}
|
|
|
|
define i32 @hs_expected(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
%v = load atomic i32, ptr %p acquire, align 4
|
|
ret i32 %v
|
|
}
|
|
|
|
; Stop a running streaming task; its completion callback then publishes status 0. An
|
|
; in-memory request cannot be interrupted and simply runs to its end.
|
|
define void @hs_cancel(i32 %slot) {
|
|
entry:
|
|
%sp = getelementptr [8 x i32], ptr @H_strm, i32 0, i32 %slot
|
|
%s = load i32, ptr %sp
|
|
%is = icmp ne i32 %s, 0
|
|
br i1 %is, label %task, label %out
|
|
task:
|
|
%tp = getelementptr [8 x ptr], ptr @H_task, i32 0, i32 %slot
|
|
%t = load ptr, ptr %tp
|
|
%not = icmp eq ptr %t, null
|
|
br i1 %not, label %out, label %go
|
|
go:
|
|
%sel = call ptr @sel_registerName(ptr @.h_cancel)
|
|
%r = call ptr (ptr, ptr) @objc_msgSend(ptr %t, ptr %sel)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
define internal void @hs_stream_free(i32 %slot) {
|
|
entry:
|
|
%tp = getelementptr [8 x ptr], ptr @H_task, i32 0, i32 %slot
|
|
%t = load ptr, ptr %tp
|
|
%not = icmp eq ptr %t, null
|
|
br i1 %not, label %file, label %rel
|
|
rel:
|
|
%sel = call ptr @sel_registerName(ptr @.h_rel)
|
|
%r = call ptr (ptr, ptr) @objc_msgSend(ptr %t, ptr %sel)
|
|
store ptr null, ptr %tp
|
|
br label %file
|
|
file:
|
|
%fp = getelementptr [8 x ptr], ptr @H_file, i32 0, i32 %slot
|
|
%f = load ptr, ptr %fp
|
|
%nof = icmp eq ptr %f, null
|
|
br i1 %nof, label %path, label %close
|
|
close:
|
|
%c = call i32 @fclose(ptr %f)
|
|
store ptr null, ptr %fp
|
|
br label %path
|
|
path:
|
|
%pp = getelementptr [8 x ptr], ptr @H_path, i32 0, i32 %slot
|
|
%p = load ptr, ptr %pp
|
|
%nop = icmp eq ptr %p, null
|
|
br i1 %nop, label %out, label %freep
|
|
freep:
|
|
call void @free(ptr %p)
|
|
store ptr null, ptr %pp
|
|
br label %out
|
|
out:
|
|
%sp = getelementptr [8 x i32], ptr @H_strm, i32 0, i32 %slot
|
|
store i32 0, ptr %sp
|
|
ret void
|
|
}
|
|
|
|
; the slot whose task this is, or -1
|
|
define internal i32 @hs_slot_of(ptr %task) {
|
|
entry:
|
|
br label %loop
|
|
loop:
|
|
%i = phi i32 [ 0, %entry ], [ %i1, %next ]
|
|
%tp = getelementptr [8 x ptr], ptr @H_task, i32 0, i32 %i
|
|
%t = load ptr, ptr %tp
|
|
%hit = icmp eq ptr %t, %task
|
|
br i1 %hit, label %found, label %next
|
|
next:
|
|
%i1 = add i32 %i, 1
|
|
%more = icmp slt i32 %i1, 8
|
|
br i1 %more, label %loop, label %none
|
|
found:
|
|
ret i32 %i
|
|
none:
|
|
ret i32 -1
|
|
}
|
|
|
|
; -URLSession:dataTask:didReceiveData: - append the chunk, count it, note the length
|
|
define internal void @hs_dlg_data(ptr %self, ptr %cmd, ptr %session, ptr %task, ptr %data) {
|
|
entry:
|
|
%slot = call i32 @hs_slot_of(ptr %task)
|
|
%bad = icmp slt i32 %slot, 0
|
|
br i1 %bad, label %out, label %file
|
|
file:
|
|
%fp = getelementptr [8 x ptr], ptr @H_file, i32 0, i32 %slot
|
|
%f = load ptr, ptr %fp
|
|
%nof = icmp eq ptr %f, null
|
|
br i1 %nof, label %out, label %write
|
|
write:
|
|
%sel_len = call ptr @sel_registerName(ptr @.h_length)
|
|
%n = call i64 (ptr, ptr) @objc_msgSend(ptr %data, ptr %sel_len)
|
|
%sel_by = call ptr @sel_registerName(ptr @.h_bytes)
|
|
%b = call ptr (ptr, ptr) @objc_msgSend(ptr %data, ptr %sel_by)
|
|
%w = call i64 @fwrite(ptr %b, i64 1, i64 %n, ptr %f)
|
|
%short = icmp ne i64 %w, %n
|
|
br i1 %short, label %failed, label %count
|
|
failed:
|
|
%ep = getelementptr [8 x i32], ptr @H_werr, i32 0, i32 %slot
|
|
store i32 1, ptr %ep
|
|
%sel_c = call ptr @sel_registerName(ptr @.h_cancel)
|
|
%rc = call ptr (ptr, ptr) @objc_msgSend(ptr %task, ptr %sel_c)
|
|
br label %count
|
|
count:
|
|
%rp = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
%r0 = load atomic i32, ptr %rp acquire, align 4
|
|
%w32 = trunc i64 %w to i32
|
|
%r1 = add i32 %r0, %w32
|
|
store atomic i32 %r1, ptr %rp release, align 4
|
|
%sel_x = call ptr @sel_registerName(ptr @.h_cbexp)
|
|
%x = call i64 (ptr, ptr) @objc_msgSend(ptr %task, ptr %sel_x)
|
|
%x32 = trunc i64 %x to i32
|
|
%neg = icmp slt i64 %x, 0
|
|
%xv = select i1 %neg, i32 -1, i32 %x32
|
|
%xp = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
store atomic i32 %xv, ptr %xp release, align 4
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; -URLSession:task:didCompleteWithError: - close the file, publish status and done
|
|
define internal void @hs_dlg_done(ptr %self, ptr %cmd, ptr %session, ptr %task, ptr %error) {
|
|
entry:
|
|
%slot = call i32 @hs_slot_of(ptr %task)
|
|
%bad = icmp slt i32 %slot, 0
|
|
br i1 %bad, label %out, label %close
|
|
close:
|
|
%fp = getelementptr [8 x ptr], ptr @H_file, i32 0, i32 %slot
|
|
%f = load ptr, ptr %fp
|
|
%nof = icmp eq ptr %f, null
|
|
br i1 %nof, label %judge, label %doclose
|
|
doclose:
|
|
%cr = call i32 @fclose(ptr %f)
|
|
store ptr null, ptr %fp
|
|
%cfail = icmp ne i32 %cr, 0
|
|
br i1 %cfail, label %markw, label %judge
|
|
markw:
|
|
%ep0 = getelementptr [8 x i32], ptr @H_werr, i32 0, i32 %slot
|
|
store i32 1, ptr %ep0
|
|
br label %judge
|
|
judge:
|
|
%ep = getelementptr [8 x i32], ptr @H_werr, i32 0, i32 %slot
|
|
%we = load i32, ptr %ep
|
|
%werr = icmp ne i32 %we, 0
|
|
%neterr = icmp ne ptr %error, null
|
|
%failed = or i1 %werr, %neterr
|
|
br i1 %failed, label %publish, label %resp
|
|
resp:
|
|
%sel_r = call ptr @sel_registerName(ptr @.h_respg)
|
|
%rs = call ptr (ptr, ptr) @objc_msgSend(ptr %task, ptr %sel_r)
|
|
%nors = icmp eq ptr %rs, null
|
|
br i1 %nors, label %publish, label %keep
|
|
keep:
|
|
%sel_ret = call ptr @sel_registerName(ptr @.h_retain)
|
|
%rr = call ptr (ptr, ptr) @objc_msgSend(ptr %rs, ptr %sel_ret)
|
|
%respp = getelementptr [8 x ptr], ptr @H_resp, i32 0, i32 %slot
|
|
store ptr %rs, ptr %respp
|
|
; a file:// URL answers with a plain NSURLResponse, which has no status code: one that
|
|
; completed without an error is reported as 200
|
|
%sel_st = call ptr @sel_registerName(ptr @.h_status)
|
|
%sel_rts = call ptr @sel_registerName(ptr @.h_rts)
|
|
%ishttp = call i8 (ptr, ptr, ptr) @objc_msgSend(ptr %rs, ptr %sel_rts, ptr %sel_st)
|
|
%nothttp = icmp eq i8 %ishttp, 0
|
|
br i1 %nothttp, label %plain, label %httpst
|
|
plain:
|
|
br label %setst
|
|
httpst:
|
|
%st64 = call i64 (ptr, ptr) @objc_msgSend(ptr %rs, ptr %sel_st)
|
|
%st0 = trunc i64 %st64 to i32
|
|
br label %setst
|
|
setst:
|
|
%st = phi i32 [ 200, %plain ], [ %st0, %httpst ]
|
|
%stp = getelementptr [8 x i32], ptr @H_status, i32 0, i32 %slot
|
|
store i32 %st, ptr %stp
|
|
; an empty body never reached didReceiveData: take the length from the response
|
|
%xp = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
%x0 = load atomic i32, ptr %xp acquire, align 4
|
|
%unk = icmp slt i32 %x0, 0
|
|
br i1 %unk, label %fromresp, label %publish
|
|
fromresp:
|
|
%sel_e = call ptr @sel_registerName(ptr @.h_ecl)
|
|
%e64 = call i64 (ptr, ptr) @objc_msgSend(ptr %rs, ptr %sel_e)
|
|
%e32 = trunc i64 %e64 to i32
|
|
%eneg = icmp slt i64 %e64, 0
|
|
%ev = select i1 %eneg, i32 -1, i32 %e32
|
|
store atomic i32 %ev, ptr %xp release, align 4
|
|
br label %publish
|
|
publish:
|
|
%rp = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
%got = load atomic i32, ptr %rp acquire, align 4
|
|
%blenp = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
store i32 %got, ptr %blenp
|
|
%donep = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
store atomic i32 1, ptr %donep release, align 4
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; The one session every streaming request goes through, made on first use (hs_send runs on
|
|
; the game thread only). Its delegate is an instance of a class built here.
|
|
define internal ptr @hs_session() {
|
|
entry:
|
|
%have = load ptr, ptr @H_sess
|
|
%got = icmp ne ptr %have, null
|
|
br i1 %got, label %ready, label %make
|
|
ready:
|
|
ret ptr %have
|
|
make:
|
|
%cls0 = call ptr @objc_getClass(ptr @.h_dlgcls)
|
|
%exists = icmp ne ptr %cls0, null
|
|
br i1 %exists, label %inst, label %build
|
|
build:
|
|
%super = call ptr @objc_getClass(ptr @.h_nsobj)
|
|
%nc = call ptr @objc_allocateClassPair(ptr %super, ptr @.h_dlgcls, i64 0)
|
|
%sd = call ptr @sel_registerName(ptr @.h_seldat)
|
|
%m1 = call i8 @class_addMethod(ptr %nc, ptr %sd, ptr @hs_dlg_data, ptr @.h_enc5)
|
|
%sc = call ptr @sel_registerName(ptr @.h_seldon)
|
|
%m2 = call i8 @class_addMethod(ptr %nc, ptr %sc, ptr @hs_dlg_done, ptr @.h_enc5)
|
|
call void @objc_registerClassPair(ptr %nc)
|
|
br label %inst
|
|
inst:
|
|
%cls = phi ptr [ %cls0, %make ], [ %nc, %build ]
|
|
%sel_a = call ptr @sel_registerName(ptr @.h_alloc)
|
|
%d0 = call ptr (ptr, ptr) @objc_msgSend(ptr %cls, ptr %sel_a)
|
|
%sel_i = call ptr @sel_registerName(ptr @.h_init)
|
|
%dlg = call ptr (ptr, ptr) @objc_msgSend(ptr %d0, ptr %sel_i)
|
|
%cfgcls = call ptr @objc_getClass(ptr @.h_cfgcl)
|
|
%sel_dc = call ptr @sel_registerName(ptr @.h_defcfg)
|
|
%cfg = call ptr (ptr, ptr) @objc_msgSend(ptr %cfgcls, ptr %sel_dc)
|
|
%sesscls = call ptr @objc_getClass(ptr @.h_sesscl)
|
|
%sel_sw = call ptr @sel_registerName(ptr @.h_swc)
|
|
%sess = call ptr (ptr, ptr, ptr, ptr, ptr) @objc_msgSend(ptr %sesscls, ptr %sel_sw, ptr %cfg, ptr %dlg, ptr null)
|
|
%none = icmp eq ptr %sess, null
|
|
br i1 %none, label %fail, label %hold
|
|
hold:
|
|
; the session retains its delegate; it is kept for the life of the program
|
|
%sel_ret = call ptr @sel_registerName(ptr @.h_retain)
|
|
%rs = call ptr (ptr, ptr) @objc_msgSend(ptr %sess, ptr %sel_ret)
|
|
store ptr %sess, ptr @H_sess
|
|
ret ptr %sess
|
|
fail:
|
|
ret ptr null
|
|
}
|
|
|
|
; Send `req` on `slot` as a streaming download into `path`. The file is created first; if
|
|
; it cannot be, or the task cannot be made, the request finishes at once with status 0.
|
|
define internal void @hs_stream_start(i32 %slot, ptr %req, ptr %path) {
|
|
entry:
|
|
%pool = call ptr @objc_autoreleasePoolPush()
|
|
%sp = getelementptr [8 x i32], ptr @H_strm, i32 0, i32 %slot
|
|
store i32 1, ptr %sp
|
|
%rp = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
store atomic i32 0, ptr %rp release, align 4
|
|
%xp = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
store atomic i32 -1, ptr %xp release, align 4
|
|
%ep = getelementptr [8 x i32], ptr @H_werr, i32 0, i32 %slot
|
|
store i32 0, ptr %ep
|
|
%sel_rel = call ptr @sel_registerName(ptr @.h_rel)
|
|
%f = call ptr @fopen(ptr %path, ptr @.h_wb)
|
|
%nof = icmp eq ptr %f, null
|
|
br i1 %nof, label %finish, label %opened
|
|
opened:
|
|
%fp = getelementptr [8 x ptr], ptr @H_file, i32 0, i32 %slot
|
|
store ptr %f, ptr %fp
|
|
; the bytes on disk are the bytes the counter reports: ask for no transfer compression
|
|
; unless the caller chose an encoding
|
|
%strcls = call ptr @objc_getClass(ptr @.h_nsstr)
|
|
%sel_u = call ptr @sel_registerName(ptr @.h_utf8)
|
|
%aen = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr @.h_aenc)
|
|
%sel_vh = call ptr @sel_registerName(ptr @.h_vhdr)
|
|
%cur = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_vh, ptr %aen)
|
|
%unset = icmp eq ptr %cur, null
|
|
br i1 %unset, label %setenc, label %session
|
|
setenc:
|
|
%idn = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %strcls, ptr %sel_u, ptr @.h_ident)
|
|
%sel_sh = call ptr @sel_registerName(ptr @.h_shdr)
|
|
%r0 = call ptr (ptr, ptr, ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_sh, ptr %idn, ptr %aen)
|
|
br label %session
|
|
session:
|
|
%sess = call ptr @hs_session()
|
|
%nosess = icmp eq ptr %sess, null
|
|
br i1 %nosess, label %closefile, label %mktask
|
|
mktask:
|
|
%sel_dt = call ptr @sel_registerName(ptr @.h_dtwr)
|
|
%task = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %sess, ptr %sel_dt, ptr %req)
|
|
%notask = icmp eq ptr %task, null
|
|
br i1 %notask, label %closefile, label %run
|
|
run:
|
|
%sel_ret = call ptr @sel_registerName(ptr @.h_retain)
|
|
%rt = call ptr (ptr, ptr) @objc_msgSend(ptr %task, ptr %sel_ret)
|
|
%tp = getelementptr [8 x ptr], ptr @H_task, i32 0, i32 %slot
|
|
store ptr %task, ptr %tp
|
|
%rr = call ptr (ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_rel)
|
|
%sel_go = call ptr @sel_registerName(ptr @.h_resume)
|
|
%g = call ptr (ptr, ptr) @objc_msgSend(ptr %task, ptr %sel_go)
|
|
call void @objc_autoreleasePoolPop(ptr %pool)
|
|
ret void
|
|
closefile:
|
|
%c = call i32 @fclose(ptr %f)
|
|
store ptr null, ptr %fp
|
|
br label %finish
|
|
finish:
|
|
%rr2 = call ptr (ptr, ptr) @objc_msgSend(ptr %req, ptr %sel_rel)
|
|
%donep = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
store atomic i32 1, ptr %donep release, align 4
|
|
call void @objc_autoreleasePoolPop(ptr %pool)
|
|
ret void
|
|
}
|