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>
685 lines
24 KiB
LLVM
685 lines
24 KiB
LLVM
; ============================================================================
|
|
; http_win.ll — the Windows HTTP transport, written in LLVM IR.
|
|
;
|
|
; http.ll's contract over WinHTTP (winhttp.dll). ludicc links it with winhttp
|
|
; when a Windows program uses Http.*. TLS is the system's, with certificate
|
|
; verification: an https:// URL just works, as on macOS.
|
|
;
|
|
; The shape is http.ll's. A request is built on the game thread as a plain record
|
|
; (method, URL, header lines, body); hs_send hands one slot to a worker thread,
|
|
; which runs the whole blocking exchange and publishes the status, a malloc'd
|
|
; NUL-terminated copy of the body, and the WinHTTP request handle, kept open so
|
|
; header queries can be answered later. The done flag is written last, atomically,
|
|
; so what the poller reads is what the worker finished writing.
|
|
;
|
|
; hs_req_new(method, url) -> req hs_req_header(req, name, val)
|
|
; hs_req_body(req, bytes, len) hs_send(slot, req)
|
|
; hs_done(slot) -> bool hs_status(slot) -> int
|
|
; hs_body(slot) -> ptr hs_blen(slot) -> int
|
|
; hs_header(slot, name) -> cstr hs_free(slot)
|
|
;
|
|
; Streaming (Http.save_to): hs_save_to(slot, path) before hs_send makes the worker create
|
|
; the file first and write each chunk into it as it is read, counting the bytes in an
|
|
; atomic the poller reads (hs_received); hs_expected is the Content-Length, or -1.
|
|
; hs_cancel asks the worker to stop between chunks.
|
|
;
|
|
; WinHTTP speaks UTF-16; every string crosses in UTF-8 and is converted at the call.
|
|
; A request record: method 0, url 8, header lines 16 ("Name: value\r\n"...), body 24,
|
|
; body length 32.
|
|
; ============================================================================
|
|
|
|
declare ptr @malloc(i64)
|
|
declare ptr @realloc(ptr, i64)
|
|
declare void @free(ptr)
|
|
declare i64 @strlen(ptr)
|
|
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
|
|
declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)
|
|
declare i32 @MultiByteToWideChar(i32, i32, ptr, i32, ptr, i32)
|
|
declare i32 @WideCharToMultiByte(i32, i32, ptr, i32, ptr, i32, ptr, ptr)
|
|
declare ptr @CreateThread(ptr, i64, ptr, ptr, i32, ptr)
|
|
declare i32 @CloseHandle(ptr)
|
|
declare ptr @WinHttpOpen(ptr, i32, ptr, ptr, i32)
|
|
declare i32 @WinHttpCrackUrl(ptr, i32, i32, ptr)
|
|
declare ptr @WinHttpConnect(ptr, ptr, i16, i32)
|
|
declare ptr @WinHttpOpenRequest(ptr, ptr, ptr, ptr, ptr, ptr, i32)
|
|
declare i32 @WinHttpAddRequestHeaders(ptr, ptr, i32, i32)
|
|
declare i32 @WinHttpSendRequest(ptr, ptr, i32, ptr, i32, i32, i64)
|
|
declare i32 @WinHttpReceiveResponse(ptr, ptr)
|
|
declare i32 @WinHttpQueryHeaders(ptr, i32, ptr, ptr, ptr, ptr)
|
|
declare i32 @WinHttpQueryDataAvailable(ptr, ptr)
|
|
declare i32 @WinHttpReadData(ptr, ptr, i32, ptr)
|
|
declare i32 @WinHttpCloseHandle(ptr)
|
|
declare ptr @CreateFileW(ptr, i32, i32, ptr, i32, i32, ptr)
|
|
declare i32 @WriteFile(ptr, ptr, i32, ptr, ptr)
|
|
|
|
@.h_agent = private unnamed_addr constant [6 x i16] [i16 76, i16 117, i16 100, i16 105, i16 99, i16 0] ; L"Ludic"
|
|
@.h_empty = private unnamed_addr constant [1 x i8] c"\00"
|
|
@.h_colon = private unnamed_addr constant [3 x i8] c": \00"
|
|
@.h_crlf = private unnamed_addr constant [3 x i8] c"\0D\0A\00"
|
|
|
|
; HTTP_SLOTS = 8
|
|
@H_req = internal global [8 x ptr] zeroinitializer
|
|
@H_sess = internal global [8 x ptr] zeroinitializer
|
|
@H_conn = internal global [8 x ptr] zeroinitializer
|
|
@H_hreq = 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
|
|
@H_hdr = internal global [8 x ptr] zeroinitializer
|
|
; streaming (Http.save_to)
|
|
@H_path = internal global [8 x ptr] zeroinitializer ; UTF-8 file path, set before send
|
|
@H_fileh = internal global [8 x ptr] zeroinitializer ; the file's HANDLE while the worker writes
|
|
@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_cancel = internal global [8 x i32] zeroinitializer ; stop between chunks (atomic)
|
|
|
|
; ---- strings -----------------------------------------------------------------
|
|
|
|
define internal ptr @h_dup(ptr %s) {
|
|
entry:
|
|
%n = call i64 @strlen(ptr %s)
|
|
%n1 = add i64 %n, 1
|
|
%m = call ptr @malloc(i64 %n1)
|
|
call void @llvm.memcpy.p0.p0.i64(ptr %m, ptr %s, i64 %n1, i1 false)
|
|
ret ptr %m
|
|
}
|
|
|
|
; a fresh NUL-terminated UTF-16 copy of a UTF-8 string (CP_UTF8 = 65001)
|
|
define internal ptr @h_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 %empty, label %conv
|
|
empty:
|
|
%e = call ptr @malloc(i64 2)
|
|
store i16 0, ptr %e
|
|
ret ptr %e
|
|
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
|
|
}
|
|
|
|
; `a` (or nothing, when null) followed by `b`, in a buffer that replaces `a`
|
|
define internal ptr @h_append(ptr %a, ptr %b) {
|
|
entry:
|
|
%none = icmp eq ptr %a, null
|
|
br i1 %none, label %zero, label %len
|
|
zero:
|
|
br label %grow
|
|
len:
|
|
%la0 = call i64 @strlen(ptr %a)
|
|
br label %grow
|
|
grow:
|
|
%la = phi i64 [ 0, %zero ], [ %la0, %len ]
|
|
%lb = call i64 @strlen(ptr %b)
|
|
%sum = add i64 %la, %lb
|
|
%cap = add i64 %sum, 1
|
|
%m = call ptr @realloc(ptr %a, i64 %cap)
|
|
%dst = getelementptr i8, ptr %m, i64 %la
|
|
%lb1 = add i64 %lb, 1
|
|
call void @llvm.memcpy.p0.p0.i64(ptr %dst, ptr %b, i64 %lb1, i1 false)
|
|
ret ptr %m
|
|
}
|
|
|
|
define internal void @h_free_if(ptr %p) {
|
|
entry:
|
|
%none = icmp eq ptr %p, null
|
|
br i1 %none, label %out, label %go
|
|
go:
|
|
call void @free(ptr %p)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
define internal void @h_close_if(ptr %h) {
|
|
entry:
|
|
%none = icmp eq ptr %h, null
|
|
br i1 %none, label %out, label %go
|
|
go:
|
|
%r = call i32 @WinHttpCloseHandle(ptr %h)
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; ---- request construction -------------------------------------------------------
|
|
|
|
define ptr @hs_req_new(ptr %method, ptr %url) {
|
|
entry:
|
|
%nou = icmp eq ptr %url, null
|
|
br i1 %nou, label %fail, label %mk
|
|
mk:
|
|
%rec = call ptr @malloc(i64 40)
|
|
call void @llvm.memset.p0.i64(ptr %rec, i8 0, i64 40, i1 false)
|
|
%m = call ptr @h_dup(ptr %method)
|
|
store ptr %m, ptr %rec
|
|
%u = call ptr @h_dup(ptr %url)
|
|
%up = getelementptr i8, ptr %rec, i64 8
|
|
store ptr %u, ptr %up
|
|
ret ptr %rec
|
|
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:
|
|
%hp = getelementptr i8, ptr %req, i64 16
|
|
%h0 = load ptr, ptr %hp
|
|
%h1 = call ptr @h_append(ptr %h0, ptr %name)
|
|
%h2 = call ptr @h_append(ptr %h1, ptr @.h_colon)
|
|
%h3 = call ptr @h_append(ptr %h2, ptr %val)
|
|
%h4 = call ptr @h_append(ptr %h3, ptr @.h_crlf)
|
|
store ptr %h4, ptr %hp
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; the body is copied, as NSData copies it on macOS: the caller may free its buffer
|
|
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:
|
|
%bp = getelementptr i8, ptr %req, i64 24
|
|
%old = load ptr, ptr %bp
|
|
call void @h_free_if(ptr %old)
|
|
%l64 = sext i32 %len to i64
|
|
%cap = add i64 %l64, 1
|
|
%b = call ptr @malloc(i64 %cap)
|
|
call void @llvm.memcpy.p0.p0.i64(ptr %b, ptr %bytes, i64 %l64, i1 false)
|
|
store ptr %b, ptr %bp
|
|
%lp = getelementptr i8, ptr %req, i64 32
|
|
store i32 %len, ptr %lp
|
|
br label %out
|
|
out:
|
|
ret void
|
|
}
|
|
|
|
; ---- the worker --------------------------------------------------------------------
|
|
|
|
; One whole exchange for one slot, on its own thread. arg is the slot index.
|
|
define internal i32 @h_worker(ptr %arg) {
|
|
entry:
|
|
%uc = alloca [104 x i8], align 8 ; URL_COMPONENTS
|
|
%num = alloca i32, align 4
|
|
%numlen = alloca i32, align 4
|
|
%avail = alloca i32, align 4
|
|
%got = alloca i32, align 4
|
|
%wurlp = alloca ptr, align 8
|
|
%hostp = alloca ptr, align 8
|
|
%verbp = alloca ptr, align 8
|
|
%whdrp = alloca ptr, align 8
|
|
%bufp = alloca ptr, align 8
|
|
%lenp = alloca i64, align 8
|
|
%capp = alloca i64, align 8
|
|
store ptr null, ptr %wurlp
|
|
store ptr null, ptr %hostp
|
|
store ptr null, ptr %verbp
|
|
store ptr null, ptr %whdrp
|
|
%slot64 = ptrtoint ptr %arg to i64
|
|
%slot = trunc i64 %slot64 to i32
|
|
%reqp = getelementptr [8 x ptr], ptr @H_req, i32 0, i32 %slot
|
|
%req = load ptr, ptr %reqp
|
|
%murlp = getelementptr i8, ptr %req, i64 8
|
|
%murl = load ptr, ptr %murlp
|
|
%wurl = call ptr @h_wide(ptr %murl)
|
|
store ptr %wurl, ptr %wurlp
|
|
; a streaming request creates (truncates) its file before anything goes on the wire
|
|
%spathp = getelementptr [8 x ptr], ptr @H_path, i32 0, i32 %slot
|
|
%spath = load ptr, ptr %spathp
|
|
%strm = icmp ne ptr %spath, null
|
|
br i1 %strm, label %mkfile, label %opensess
|
|
mkfile:
|
|
%wpath = call ptr @h_wide(ptr %spath)
|
|
; GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL
|
|
%fh = call ptr @CreateFileW(ptr %wpath, i32 1073741824, i32 1, ptr null, i32 2, i32 128, ptr null)
|
|
call void @free(ptr %wpath)
|
|
%badfh = icmp eq ptr %fh, inttoptr (i64 -1 to ptr)
|
|
br i1 %badfh, label %done, label %keepfh
|
|
keepfh:
|
|
%fhp = getelementptr [8 x ptr], ptr @H_fileh, i32 0, i32 %slot
|
|
store ptr %fh, ptr %fhp
|
|
br label %opensess
|
|
opensess:
|
|
; WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY: the system's proxy settings, as NSURLConnection uses
|
|
%sess = call ptr @WinHttpOpen(ptr @.h_agent, i32 4, ptr null, ptr null, i32 0)
|
|
%sessp = getelementptr [8 x ptr], ptr @H_sess, i32 0, i32 %slot
|
|
store ptr %sess, ptr %sessp
|
|
%nosess = icmp eq ptr %sess, null
|
|
br i1 %nosess, label %done, label %crack
|
|
crack:
|
|
; a non-zero length asks WinHTTP for a pointer into the URL instead of a copy
|
|
call void @llvm.memset.p0.i64(ptr %uc, i8 0, i64 104, i1 false)
|
|
store i32 104, ptr %uc
|
|
%sl = getelementptr i8, ptr %uc, i64 16
|
|
store i32 -1, ptr %sl
|
|
%hl = getelementptr i8, ptr %uc, i64 32
|
|
store i32 -1, ptr %hl
|
|
%pl = getelementptr i8, ptr %uc, i64 80
|
|
store i32 -1, ptr %pl
|
|
%xl = getelementptr i8, ptr %uc, i64 96
|
|
store i32 -1, ptr %xl
|
|
%ok = call i32 @WinHttpCrackUrl(ptr %wurl, i32 0, i32 0, ptr %uc)
|
|
%noparse = icmp eq i32 %ok, 0
|
|
br i1 %noparse, label %done, label %connect
|
|
connect:
|
|
%hsp = getelementptr i8, ptr %uc, i64 24
|
|
%hs = load ptr, ptr %hsp
|
|
%hlen = load i32, ptr %hl
|
|
%hlen64 = zext i32 %hlen to i64
|
|
%hbytes = mul i64 %hlen64, 2
|
|
%hcap = add i64 %hbytes, 2
|
|
%host = call ptr @malloc(i64 %hcap)
|
|
call void @llvm.memcpy.p0.p0.i64(ptr %host, ptr %hs, i64 %hbytes, i1 false)
|
|
%hend = getelementptr i8, ptr %host, i64 %hbytes
|
|
store i16 0, ptr %hend
|
|
store ptr %host, ptr %hostp
|
|
%portp = getelementptr i8, ptr %uc, i64 36
|
|
%port = load i16, ptr %portp
|
|
%conn = call ptr @WinHttpConnect(ptr %sess, ptr %host, i16 %port, i32 0)
|
|
%connp = getelementptr [8 x ptr], ptr @H_conn, i32 0, i32 %slot
|
|
store ptr %conn, ptr %connp
|
|
%noconn = icmp eq ptr %conn, null
|
|
br i1 %noconn, label %done, label %open
|
|
open:
|
|
; the path runs on to the end of the URL, so it carries the query with it
|
|
%pathp = getelementptr i8, ptr %uc, i64 72
|
|
%path = load ptr, ptr %pathp
|
|
%schp = getelementptr i8, ptr %uc, i64 20
|
|
%sch = load i32, ptr %schp
|
|
%https = icmp eq i32 %sch, 2 ; INTERNET_SCHEME_HTTPS
|
|
%flags = select i1 %https, i32 8388608, i32 0 ; WINHTTP_FLAG_SECURE
|
|
%mp = load ptr, ptr %req
|
|
%verb = call ptr @h_wide(ptr %mp)
|
|
store ptr %verb, ptr %verbp
|
|
%hreq = call ptr @WinHttpOpenRequest(ptr %conn, ptr %verb, ptr %path, ptr null, ptr null, ptr null, i32 %flags)
|
|
%hreqp = getelementptr [8 x ptr], ptr @H_hreq, i32 0, i32 %slot
|
|
store ptr %hreq, ptr %hreqp
|
|
%noreq = icmp eq ptr %hreq, null
|
|
br i1 %noreq, label %done, label %headers
|
|
headers:
|
|
%hdrsp = getelementptr i8, ptr %req, i64 16
|
|
%hdrs = load ptr, ptr %hdrsp
|
|
%nohdrs = icmp eq ptr %hdrs, null
|
|
br i1 %nohdrs, label %send, label %addh
|
|
addh:
|
|
%wh = call ptr @h_wide(ptr %hdrs)
|
|
store ptr %wh, ptr %whdrp
|
|
; WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE: a header set twice is the last value, as setValue:forHTTPHeaderField:
|
|
%ah = call i32 @WinHttpAddRequestHeaders(ptr %hreq, ptr %wh, i32 -1, i32 -1610612736)
|
|
br label %send
|
|
send:
|
|
%bodyp = getelementptr i8, ptr %req, i64 24
|
|
%body = load ptr, ptr %bodyp
|
|
%blp = getelementptr i8, ptr %req, i64 32
|
|
%bl = load i32, ptr %blp
|
|
%sent = call i32 @WinHttpSendRequest(ptr %hreq, ptr null, i32 0, ptr %body, i32 %bl, i32 %bl, i64 0)
|
|
%nosend = icmp eq i32 %sent, 0
|
|
br i1 %nosend, label %done, label %receive
|
|
receive:
|
|
%rr = call i32 @WinHttpReceiveResponse(ptr %hreq, ptr null)
|
|
%norecv = icmp eq i32 %rr, 0
|
|
br i1 %norecv, label %done, label %status
|
|
status:
|
|
; WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER
|
|
store i32 0, ptr %num
|
|
store i32 4, ptr %numlen
|
|
%qs = call i32 @WinHttpQueryHeaders(ptr %hreq, i32 536870931, ptr null, ptr %num, ptr %numlen, ptr null)
|
|
%st = load i32, ptr %num
|
|
%stp = getelementptr [8 x i32], ptr @H_status, i32 0, i32 %slot
|
|
store i32 %st, ptr %stp
|
|
br i1 %strm, label %stream, label %inmem
|
|
stream:
|
|
; WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER
|
|
store i32 -1, ptr %num
|
|
store i32 4, ptr %numlen
|
|
%qcl = call i32 @WinHttpQueryHeaders(ptr %hreq, i32 536870917, ptr null, ptr %num, ptr %numlen, ptr null)
|
|
%cl = load i32, ptr %num
|
|
%noclq = icmp eq i32 %qcl, 0
|
|
%clv = select i1 %noclq, i32 -1, i32 %cl
|
|
%xp = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
store atomic i32 %clv, ptr %xp release, align 4
|
|
%sbuf = call ptr @malloc(i64 65536)
|
|
store ptr %sbuf, ptr %bufp
|
|
%sfhp = getelementptr [8 x ptr], ptr @H_fileh, i32 0, i32 %slot
|
|
%sfh = load ptr, ptr %sfhp
|
|
%rcp = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
%canp = getelementptr [8 x i32], ptr @H_cancel, i32 0, i32 %slot
|
|
br label %sloop
|
|
sloop:
|
|
%cn = load atomic i32, ptr %canp acquire, align 4
|
|
%cancelled = icmp ne i32 %cn, 0
|
|
br i1 %cancelled, label %sfail, label %savail
|
|
savail:
|
|
store i32 0, ptr %avail
|
|
%sqa = call i32 @WinHttpQueryDataAvailable(ptr %hreq, ptr %avail)
|
|
%sav = load i32, ptr %avail
|
|
%sqfail = icmp eq i32 %sqa, 0
|
|
br i1 %sqfail, label %sfail, label %sany
|
|
sany:
|
|
%snone = icmp eq i32 %sav, 0
|
|
br i1 %snone, label %sdone, label %sread
|
|
sread:
|
|
%sbig = icmp ugt i32 %sav, 65536
|
|
%sn = select i1 %sbig, i32 65536, i32 %sav
|
|
store i32 0, ptr %got
|
|
%srd = call i32 @WinHttpReadData(ptr %hreq, ptr %sbuf, i32 %sn, ptr %got)
|
|
%sg = load i32, ptr %got
|
|
%srfail = icmp eq i32 %srd, 0
|
|
br i1 %srfail, label %sfail, label %sgot
|
|
sgot:
|
|
%sg0 = icmp eq i32 %sg, 0
|
|
br i1 %sg0, label %sdone, label %swrite
|
|
swrite:
|
|
store i32 0, ptr %num
|
|
%wr = call i32 @WriteFile(ptr %sfh, ptr %sbuf, i32 %sg, ptr %num, ptr null)
|
|
%wn = load i32, ptr %num
|
|
%wfail = icmp eq i32 %wr, 0
|
|
%wshort = icmp ne i32 %wn, %sg
|
|
%wbad = or i1 %wfail, %wshort
|
|
br i1 %wbad, label %sfail, label %scount
|
|
scount:
|
|
%r0 = load atomic i32, ptr %rcp acquire, align 4
|
|
%r1 = add i32 %r0, %sg
|
|
store atomic i32 %r1, ptr %rcp release, align 4
|
|
br label %sloop
|
|
sfail:
|
|
store i32 0, ptr %stp
|
|
br label %sdone
|
|
sdone:
|
|
call void @free(ptr %sbuf)
|
|
%total = load atomic i32, ptr %rcp acquire, align 4
|
|
%sblenslot = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
store i32 %total, ptr %sblenslot
|
|
br label %done
|
|
inmem:
|
|
%b0 = call ptr @malloc(i64 4097)
|
|
store ptr %b0, ptr %bufp
|
|
store i64 0, ptr %lenp
|
|
store i64 4096, ptr %capp
|
|
br label %more
|
|
more:
|
|
store i32 0, ptr %avail
|
|
%qa = call i32 @WinHttpQueryDataAvailable(ptr %hreq, ptr %avail)
|
|
%av = load i32, ptr %avail
|
|
%qfail = icmp eq i32 %qa, 0
|
|
%nothing = icmp eq i32 %av, 0
|
|
%stop = or i1 %qfail, %nothing
|
|
br i1 %stop, label %finish, label %room
|
|
room:
|
|
%len = load i64, ptr %lenp
|
|
%cap = load i64, ptr %capp
|
|
%av64 = zext i32 %av to i64
|
|
%need = add i64 %len, %av64
|
|
%small = icmp ugt i64 %need, %cap
|
|
br i1 %small, label %enlarge, label %read
|
|
enlarge:
|
|
%ncap = mul i64 %need, 2
|
|
store i64 %ncap, ptr %capp
|
|
%ob = load ptr, ptr %bufp
|
|
%ncap1 = add i64 %ncap, 1
|
|
%nb = call ptr @realloc(ptr %ob, i64 %ncap1)
|
|
store ptr %nb, ptr %bufp
|
|
br label %read
|
|
read:
|
|
%buf = load ptr, ptr %bufp
|
|
%len2 = load i64, ptr %lenp
|
|
%dst = getelementptr i8, ptr %buf, i64 %len2
|
|
store i32 0, ptr %got
|
|
%rd = call i32 @WinHttpReadData(ptr %hreq, ptr %dst, i32 %av, ptr %got)
|
|
%g = load i32, ptr %got
|
|
%g64 = zext i32 %g to i64
|
|
%len3 = add i64 %len2, %g64
|
|
store i64 %len3, ptr %lenp
|
|
%rfail = icmp eq i32 %rd, 0
|
|
%none = icmp eq i32 %g, 0
|
|
%end = or i1 %rfail, %none
|
|
br i1 %end, label %finish, label %more
|
|
finish:
|
|
%fb = load ptr, ptr %bufp
|
|
%fl = load i64, ptr %lenp
|
|
%term = getelementptr i8, ptr %fb, i64 %fl
|
|
store i8 0, ptr %term
|
|
%bodyslot = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
store ptr %fb, ptr %bodyslot
|
|
%fl32 = trunc i64 %fl to i32
|
|
%blenslot = getelementptr [8 x i32], ptr @H_blen, i32 0, i32 %slot
|
|
store i32 %fl32, ptr %blenslot
|
|
br label %done
|
|
done:
|
|
%f1 = load ptr, ptr %wurlp
|
|
call void @h_free_if(ptr %f1)
|
|
%f2 = load ptr, ptr %hostp
|
|
call void @h_free_if(ptr %f2)
|
|
%f3 = load ptr, ptr %verbp
|
|
call void @h_free_if(ptr %f3)
|
|
%f4 = load ptr, ptr %whdrp
|
|
call void @h_free_if(ptr %f4)
|
|
; a streaming request's file is closed before done is published: done means the bytes
|
|
; are on disk
|
|
%dfhp = getelementptr [8 x ptr], ptr @H_fileh, i32 0, i32 %slot
|
|
%dfh = load ptr, ptr %dfhp
|
|
%nodfh = icmp eq ptr %dfh, null
|
|
br i1 %nodfh, label %publish, label %closefh
|
|
closefh:
|
|
%cfh = call i32 @CloseHandle(ptr %dfh)
|
|
store ptr null, ptr %dfhp
|
|
br label %publish
|
|
publish:
|
|
%donep = getelementptr [8 x i32], ptr @H_done, i32 0, i32 %slot
|
|
store atomic i32 1, ptr %donep release, align 4
|
|
ret i32 0
|
|
}
|
|
|
|
; Start the request on `slot` running on a worker thread of its own.
|
|
define void @hs_send(i32 %slot, ptr %req) {
|
|
entry:
|
|
%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
|
|
%reqp = getelementptr [8 x ptr], ptr @H_req, i32 0, i32 %slot
|
|
store ptr %req, ptr %reqp
|
|
%rcp = getelementptr [8 x i32], ptr @H_recv, i32 0, i32 %slot
|
|
store atomic i32 0, ptr %rcp release, align 4
|
|
%xp = getelementptr [8 x i32], ptr @H_exp, i32 0, i32 %slot
|
|
store atomic i32 -1, ptr %xp release, align 4
|
|
%canp = getelementptr [8 x i32], ptr @H_cancel, i32 0, i32 %slot
|
|
store atomic i32 0, ptr %canp release, align 4
|
|
%nil = icmp eq ptr %req, null
|
|
br i1 %nil, label %nothing, label %start
|
|
nothing:
|
|
; a request that could not be built finishes at once, with status 0, as a failed one does
|
|
store atomic i32 1, ptr %donep release, align 4
|
|
ret void
|
|
start:
|
|
%arg = inttoptr i32 %slot to ptr
|
|
%t = call ptr @CreateThread(ptr null, i64 0, ptr @h_worker, ptr %arg, i32 0, ptr null)
|
|
%not = icmp eq ptr %t, null
|
|
br i1 %not, label %nothing, label %detach
|
|
detach:
|
|
%c = call i32 @CloseHandle(ptr %t)
|
|
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. The string
|
|
; lives until the slot's next header query or hs_free.
|
|
define ptr @hs_header(i32 %slot, ptr %name) {
|
|
entry:
|
|
%lenp = alloca i32, align 4
|
|
%hp = getelementptr [8 x ptr], ptr @H_hreq, i32 0, i32 %slot
|
|
%h = load ptr, ptr %hp
|
|
%noh = icmp eq ptr %h, null
|
|
br i1 %noh, label %empty, label %size
|
|
size:
|
|
%wn = call ptr @h_wide(ptr %name)
|
|
; WINHTTP_QUERY_CUSTOM with no buffer answers the size needed, in bytes
|
|
store i32 0, ptr %lenp
|
|
%q0 = call i32 @WinHttpQueryHeaders(ptr %h, i32 65535, ptr %wn, ptr null, ptr %lenp, ptr null)
|
|
%need = load i32, ptr %lenp
|
|
%absent = icmp sle i32 %need, 0
|
|
br i1 %absent, label %nothere, label %fetch
|
|
nothere:
|
|
call void @free(ptr %wn)
|
|
br label %empty
|
|
fetch:
|
|
%n64 = zext i32 %need to i64
|
|
%cap = add i64 %n64, 2
|
|
%wb = call ptr @malloc(i64 %cap)
|
|
%q1 = call i32 @WinHttpQueryHeaders(ptr %h, i32 65535, ptr %wn, ptr %wb, ptr %lenp, ptr null)
|
|
call void @free(ptr %wn)
|
|
%got = load i32, ptr %lenp
|
|
%g64 = zext i32 %got to i64
|
|
%endp = getelementptr i8, ptr %wb, i64 %g64
|
|
store i16 0, ptr %endp
|
|
%failq = icmp eq i32 %q1, 0
|
|
br i1 %failq, label %freewb, label %utf8
|
|
freewb:
|
|
call void @free(ptr %wb)
|
|
br label %empty
|
|
utf8:
|
|
%n8 = call i32 @WideCharToMultiByte(i32 65001, i32 0, ptr %wb, i32 -1, ptr null, i32 0, ptr null, ptr null)
|
|
%n8z = icmp sle i32 %n8, 0
|
|
br i1 %n8z, label %freewb, label %conv
|
|
conv:
|
|
%n864 = sext i32 %n8 to i64
|
|
%out = call ptr @malloc(i64 %n864)
|
|
%c = call i32 @WideCharToMultiByte(i32 65001, i32 0, ptr %wb, i32 -1, ptr %out, i32 %n8, ptr null, ptr null)
|
|
call void @free(ptr %wb)
|
|
%oldp = getelementptr [8 x ptr], ptr @H_hdr, i32 0, i32 %slot
|
|
%old = load ptr, ptr %oldp
|
|
call void @h_free_if(ptr %old)
|
|
store ptr %out, ptr %oldp
|
|
ret ptr %out
|
|
empty:
|
|
ret ptr @.h_empty
|
|
}
|
|
|
|
; Free a slot's owned resources: the body copy, the last header string, the three
|
|
; WinHTTP handles, and the request record.
|
|
define void @hs_free(i32 %slot) {
|
|
entry:
|
|
%bp = getelementptr [8 x ptr], ptr @H_body, i32 0, i32 %slot
|
|
%b = load ptr, ptr %bp
|
|
call void @h_free_if(ptr %b)
|
|
store ptr null, ptr %bp
|
|
%hdp = getelementptr [8 x ptr], ptr @H_hdr, i32 0, i32 %slot
|
|
%hd = load ptr, ptr %hdp
|
|
call void @h_free_if(ptr %hd)
|
|
store ptr null, ptr %hdp
|
|
%rqp = getelementptr [8 x ptr], ptr @H_hreq, i32 0, i32 %slot
|
|
%rq = load ptr, ptr %rqp
|
|
call void @h_close_if(ptr %rq)
|
|
store ptr null, ptr %rqp
|
|
%cnp = getelementptr [8 x ptr], ptr @H_conn, i32 0, i32 %slot
|
|
%cn = load ptr, ptr %cnp
|
|
call void @h_close_if(ptr %cn)
|
|
store ptr null, ptr %cnp
|
|
%ssp = getelementptr [8 x ptr], ptr @H_sess, i32 0, i32 %slot
|
|
%ss = load ptr, ptr %ssp
|
|
call void @h_close_if(ptr %ss)
|
|
store ptr null, ptr %ssp
|
|
%pthp = getelementptr [8 x ptr], ptr @H_path, i32 0, i32 %slot
|
|
%pth = load ptr, ptr %pthp
|
|
call void @h_free_if(ptr %pth)
|
|
store ptr null, ptr %pthp
|
|
%recp = getelementptr [8 x ptr], ptr @H_req, i32 0, i32 %slot
|
|
%rec = load ptr, ptr %recp
|
|
%norec = icmp eq ptr %rec, null
|
|
br i1 %norec, label %out, label %freerec
|
|
freerec:
|
|
%m = load ptr, ptr %rec
|
|
call void @h_free_if(ptr %m)
|
|
%up = getelementptr i8, ptr %rec, i64 8
|
|
%u = load ptr, ptr %up
|
|
call void @h_free_if(ptr %u)
|
|
%hp = getelementptr i8, ptr %rec, i64 16
|
|
%h = load ptr, ptr %hp
|
|
call void @h_free_if(ptr %h)
|
|
%bdp = getelementptr i8, ptr %rec, i64 24
|
|
%bd = load ptr, ptr %bdp
|
|
call void @h_free_if(ptr %bd)
|
|
call void @free(ptr %rec)
|
|
store ptr null, ptr %recp
|
|
br label %out
|
|
out:
|
|
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
|
|
call void @h_free_if(ptr %old)
|
|
%cp = call ptr @h_dup(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
|
|
}
|
|
|
|
; ask a streaming worker to stop before its next chunk (it then publishes status 0); an
|
|
; in-memory request runs to its end
|
|
define void @hs_cancel(i32 %slot) {
|
|
entry:
|
|
%p = getelementptr [8 x i32], ptr @H_cancel, i32 0, i32 %slot
|
|
store atomic i32 1, ptr %p release, align 4
|
|
ret void
|
|
}
|