From 40d78cbd92023476a829e53907c1f75c0627b9fb Mon Sep 17 00:00:00 2001 From: Orkuncakilkaya Date: Sun, 13 Sep 2026 03:24:50 +0300 Subject: [PATCH] feat(windows): Http.* over WinHTTP, and the splash and window icon through WIC http_win.ll implements http.ll's hs_* contract over WinHTTP: the request is a record built on the game thread, the whole exchange runs on a worker thread, TLS uses the system's certificate checks, and headers are answered from the kept request handle. ludicc links it with winhttp instead of refusing Http.* on Windows. win32.ll decodes the splash and App.set_icon images with WIC (ole32): the splash is a topmost borderless window at the artwork's size in points times the display scale, composited over its background colour; the icon becomes an HICON set on the window now or when win_open makes one. Verified on the PC: examples/library/http.ludic prints its expected output, a real GET to https://git.workshopsoft.io/ returns 200 with its body and Content-Type, and the bundled Maroon Lake shows its splash centred at launch and its icon in the title bar. Co-Authored-By: Claude Opus 5 --- changes/windows-target.md | 8 +- runtime/native/http_win.ll | 534 +++++++++++++++++++++++++++++++++++++ runtime/native/win32.ll | 442 +++++++++++++++++++++++++++++- selfhost/main.ludic | 16 +- 4 files changed, 988 insertions(+), 12 deletions(-) create mode 100644 runtime/native/http_win.ll diff --git a/changes/windows-target.md b/changes/windows-target.md index 8bf8de09..cad489eb 100644 --- a/changes/windows-target.md +++ b/changes/windows-target.md @@ -34,8 +34,12 @@ type: feat - **`ludic bundle` on Windows** — `build//` holding a GUI-subsystem `.exe`, the `game.lpak` and `packs.index`; an `.ico` beside `app icon` is compiled with `llvm-rc` and linked in. `ludicc` gains `--gui` (no console behind the window) and `--link `. -- **Not yet** — the splash and `App.set_icon` do nothing, and `Http.*` is still a macOS - platform layer; a Windows build that uses it stops with a message saying so. +- **`Http.*` on Windows** — `http_win.ll` is `http.ll`'s contract over WinHTTP: a request + built on the game thread, the exchange on a worker thread, TLS with the system's + certificate checks, and response headers answered from the kept request handle. +- **The splash and the window icon** — decoded by WIC from the bytes in the pack. The splash + is a topmost borderless window at the artwork's own size in points times the display's + scale; `App.set_icon` sets the title bar and taskbar icon of a build with no icon resource. - **`Gl.*` on Windows** — `gl_win.ll` makes a WGL 4.1 core context on a hidden window's DC, and `gl_thunks_win.ll` (generated by `ludic-dev glgen` beside `gl_thunks.ll`) calls every entry point through a table `@lgl_win_load` fills from `wglGetProcAddress`, falling diff --git a/runtime/native/http_win.ll b/runtime/native/http_win.ll new file mode 100644 index 00000000..0c8968bb --- /dev/null +++ b/runtime/native/http_win.ll @@ -0,0 +1,534 @@ +; ============================================================================ +; 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) +; +; 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) + +@.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 + +; ---- 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 + ; 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 + %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) + %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 + %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 + %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 +} diff --git a/runtime/native/win32.ll b/runtime/native/win32.ll index 0bdc3fac..e7296d60 100644 --- a/runtime/native/win32.ll +++ b/runtime/native/win32.ll @@ -237,6 +237,7 @@ show: store ptr %hwnd, ptr @W_hwnd %hdc = call ptr @GetDC(ptr %hwnd) store ptr %hdc, ptr @W_hdc + call void @w_apply_icon() ; an App.set_icon that ran before there was a window %s1 = call i32 @ShowWindow(ptr %hwnd, i32 5) ; SW_SHOW %s2 = call i32 @SetForegroundWindow(ptr %hwnd) ; RAWINPUTDEVICE { usUsagePage 1, usUsage 2 (mouse), dwFlags 0, hwndTarget }: the raw @@ -893,19 +894,452 @@ entry: ret void } -; ---- the splash and the icon --------------------------------------------------- -; Not yet: both need an image decoder (WIC), and on Windows the icon a player sees on -; the taskbar and in Explorer is the one linked into the .exe, which `ludic bundle` -; will do. Until then these keep the contract - safe to call, change nothing. +; ---- images: a PNG or JPEG decoded by WIC -------------------------------------- +; +; The splash and the window icon arrive as file bytes (out of the pack), and WIC - +; the codecs every Windows install carries - turns them into 32-bit BGRA, as NSImage +; does on macOS. COM slots, from wincodec.h: +; IWICImagingFactory 4 CreateDecoderFromStream 10 CreateFormatConverter 14 CreateStream +; IWICStream 16 InitializeFromMemory +; IWICBitmapDecoder 13 GetFrame +; IWICBitmapFrameDecode 5 GetResolution +; IWICFormatConverter 3 GetSize 7 CopyPixels 8 Initialize +; and 2 Release on all of them. + +declare i32 @CoInitializeEx(ptr, i32) +declare i32 @CoCreateInstance(ptr, ptr, i32, ptr, ptr) +declare ptr @CreateDIBSection(ptr, ptr, i32, ptr, ptr, i32) +declare ptr @CreateBitmap(i32, i32, i32, i32, ptr) +declare ptr @CreateIconIndirect(ptr) +declare i32 @DeleteObject(ptr) +declare i64 @SendMessageA(ptr, i32, i64, i64) +declare i32 @DestroyWindow(ptr) +declare i32 @UpdateWindow(ptr) +declare ptr @BeginPaint(ptr, ptr) +declare i32 @EndPaint(ptr, ptr) +declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1) + +; CLSID_WICImagingFactory {cacaf262-9370-4615-a13b-9f5539da4c0a}, +; IID_IWICImagingFactory {ec5ec8a9-c395-4314-9c77-54d7a935ff70}, +; GUID_WICPixelFormat32bppBGRA {6fddc324-4e03-4bfe-b185-3d77768dc90f} - in memory order +@.wic_clsid = private unnamed_addr constant [16 x i8] c"\62\F2\CA\CA\70\93\15\46\A1\3B\9F\55\39\DA\4C\0A" +@.wic_iid = private unnamed_addr constant [16 x i8] c"\A9\C8\5E\EC\95\C3\14\43\9C\77\54\D7\A9\35\FF\70" +@.wic_bgra = private unnamed_addr constant [16 x i8] c"\24\C3\DD\6F\03\4E\FE\4B\B1\85\3D\77\76\8D\C9\0F" +@.w_splash_class = private unnamed_addr constant [12 x i8] c"LudicSplash\00" +@.w_empty = private unnamed_addr constant [1 x i8] c"\00" + +define internal ptr @w_vt(ptr %obj, i64 %slot) { +entry: + %vt = load ptr, ptr %obj + %p = getelementptr ptr, ptr %vt, i64 %slot + %f = load ptr, ptr %p + ret ptr %f +} + +define internal void @w_release(ptr %pp) { +entry: + %o = load ptr, ptr %pp + %none = icmp eq ptr %o, null + br i1 %none, label %out, label %go +go: + %rel = call ptr @w_vt(ptr %o, i64 2) + %r = call i32 %rel(ptr %o) + store ptr null, ptr %pp + br label %out +out: + ret void +} + +; bytes -> a malloc'd w x h BGRA buffer (straight alpha), its size, and the image's own +; horizontal dpi (72 when it names none); null when WIC cannot read it +define internal ptr @w_decode(ptr %bytes, i32 %len, ptr %wout, ptr %hout, ptr %dpiout) { +entry: + %fp = alloca ptr, align 8 + %sp = alloca ptr, align 8 + %dp = alloca ptr, align 8 + %frp = alloca ptr, align 8 + %cp = alloca ptr, align 8 + %wp = alloca i32, align 4 + %hp = alloca i32, align 4 + %dx = alloca double, align 8 + %dy = alloca double, align 8 + store ptr null, ptr %fp + store ptr null, ptr %sp + store ptr null, ptr %dp + store ptr null, ptr %frp + store ptr null, ptr %cp + store double 0.000000e+00, ptr %dx + %nob = icmp eq ptr %bytes, null + %empty = icmp sle i32 %len, 0 + %skip = or i1 %nob, %empty + br i1 %skip, label %fail, label %com +com: + ; COINIT_APARTMENTTHREADED; a thread already initialised answers S_FALSE or RPC_E_CHANGED_MODE, both fine + %ci = call i32 @CoInitializeEx(ptr null, i32 2) + %hr0 = call i32 @CoCreateInstance(ptr @.wic_clsid, ptr null, i32 1, ptr @.wic_iid, ptr %fp) ; CLSCTX_INPROC_SERVER + %b0 = icmp slt i32 %hr0, 0 + br i1 %b0, label %fail, label %stream +stream: + %f = load ptr, ptr %fp + %cs = call ptr @w_vt(ptr %f, i64 14) + %hr1 = call i32 %cs(ptr %f, ptr %sp) + %b1 = icmp slt i32 %hr1, 0 + br i1 %b1, label %fail, label %meminit +meminit: + %s = load ptr, ptr %sp + %im = call ptr @w_vt(ptr %s, i64 16) + %hr2 = call i32 %im(ptr %s, ptr %bytes, i32 %len) + %b2 = icmp slt i32 %hr2, 0 + br i1 %b2, label %fail, label %decoder +decoder: + %cdfs = call ptr @w_vt(ptr %f, i64 4) + %hr3 = call i32 %cdfs(ptr %f, ptr %s, ptr null, i32 0, ptr %dp) ; WICDecodeMetadataCacheOnDemand + %b3 = icmp slt i32 %hr3, 0 + br i1 %b3, label %fail, label %frame +frame: + %d = load ptr, ptr %dp + %gf = call ptr @w_vt(ptr %d, i64 13) + %hr4 = call i32 %gf(ptr %d, i32 0, ptr %frp) + %b4 = icmp slt i32 %hr4, 0 + br i1 %b4, label %fail, label %resolution +resolution: + %fr = load ptr, ptr %frp + %gr = call ptr @w_vt(ptr %fr, i64 5) + %hr5 = call i32 %gr(ptr %fr, ptr %dx, ptr %dy) + %conv = call ptr @w_vt(ptr %f, i64 10) + %hr6 = call i32 %conv(ptr %f, ptr %cp) + %b6 = icmp slt i32 %hr6, 0 + br i1 %b6, label %fail, label %convert +convert: + %c = load ptr, ptr %cp + %ini = call ptr @w_vt(ptr %c, i64 8) + ; to 32bpp BGRA, no dither, no palette, alpha threshold 0, WICBitmapPaletteTypeCustom + %hr7 = call i32 %ini(ptr %c, ptr %fr, ptr @.wic_bgra, i32 0, ptr null, double 0.000000e+00, i32 0) + %b7 = icmp slt i32 %hr7, 0 + br i1 %b7, label %fail, label %size +size: + %gs = call ptr @w_vt(ptr %c, i64 3) + %hr8 = call i32 %gs(ptr %c, ptr %wp, ptr %hp) + %w = load i32, ptr %wp + %h = load i32, ptr %hp + %zw = icmp sle i32 %w, 0 + %zh = icmp sle i32 %h, 0 + %zero = or i1 %zw, %zh + br i1 %zero, label %fail, label %pixels +pixels: + %w64 = sext i32 %w to i64 + %h64 = sext i32 %h to i64 + %px = mul i64 %w64, %h64 + %bytesz = mul i64 %px, 4 + %buf = call ptr @malloc(i64 %bytesz) + %stride = mul i32 %w, 4 + %bsz32 = trunc i64 %bytesz to i32 + %cpx = call ptr @w_vt(ptr %c, i64 7) + %hr9 = call i32 %cpx(ptr %c, ptr null, i32 %stride, i32 %bsz32, ptr %buf) + %b9 = icmp slt i32 %hr9, 0 + br i1 %b9, label %freebuf, label %ok +freebuf: + call void @free(ptr %buf) + br label %fail +ok: + store i32 %w, ptr %wout + store i32 %h, ptr %hout + %dpd = load double, ptr %dx + %dpi0 = fptosi double %dpd to i32 + %nodpi = icmp slt i32 %dpi0, 1 + %dpi = select i1 %nodpi, i32 72, i32 %dpi0 + store i32 %dpi, ptr %dpiout + call void @w_release(ptr %cp) + call void @w_release(ptr %frp) + call void @w_release(ptr %dp) + call void @w_release(ptr %sp) + call void @w_release(ptr %fp) + ret ptr %buf +fail: + call void @w_release(ptr %cp) + call void @w_release(ptr %frp) + call void @w_release(ptr %dp) + call void @w_release(ptr %sp) + call void @w_release(ptr %fp) + ret ptr null +} + +; ---- the boot splash --------------------------------------------------------------- +; +; cocoa.ll's splash, on Windows: a borderless, topmost window with the game's artwork, +; shown from lp_pak_boot before main and taken down by App.splash_hide(). It takes the +; artwork's own size in points (pixels x 72 / its dpi, as NSImage measures it) at the +; display's whole-number scale, so a 1440 x 810 image saved at 144 dpi is 720 x 405 +; points on either platform. The image is composited over the background colour once, +; and painted from that on every WM_PAINT. + +@W_splash = internal global ptr null +@W_splash_px = internal global ptr null +@W_splash_w = internal global i32 0 +@W_splash_h = internal global i32 0 +@W_splash_dw = internal global i32 0 +@W_splash_dh = internal global i32 0 + +define internal i64 @w_splash_proc(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp) { +entry: + %ps = alloca [72 x i8], align 8 ; PAINTSTRUCT + %bmi = alloca [44 x i8], align 4 + switch i32 %msg, label %def [ + i32 15, label %paint ; WM_PAINT + i32 20, label %erase ; WM_ERASEBKGND + ] +erase: + ret i64 1 +paint: + %hdc = call ptr @BeginPaint(ptr %hwnd, ptr %ps) + %px = load ptr, ptr @W_splash_px + %nopx = icmp eq ptr %px, null + br i1 %nopx, label %endp, label %blit +blit: + %w = load i32, ptr @W_splash_w + %h = load i32, ptr @W_splash_h + %dw = load i32, ptr @W_splash_dw + %dh = load i32, ptr @W_splash_dh + call void @llvm.memset.p0.i64(ptr %bmi, i8 0, i64 44, i1 false) + store i32 40, ptr %bmi + %bw = getelementptr i8, ptr %bmi, i64 4 + store i32 %w, ptr %bw + %nh = sub i32 0, %h + %bh = getelementptr i8, ptr %bmi, i64 8 + store i32 %nh, ptr %bh + %bp = getelementptr i8, ptr %bmi, i64 12 + store i16 1, ptr %bp + %bb = getelementptr i8, ptr %bmi, i64 14 + store i16 32, ptr %bb + %r = call i32 @StretchDIBits(ptr %hdc, i32 0, i32 0, i32 %dw, i32 %dh, i32 0, i32 0, i32 %w, i32 %h, ptr %px, ptr %bmi, i32 0, i32 13369376) + br label %endp +endp: + %e = call i32 @EndPaint(ptr %hwnd, ptr %ps) + ret i64 0 +def: + %d = call i64 @DefWindowProcA(ptr %hwnd, i32 %msg, i64 %wp, i64 %lp) + ret i64 %d +} + define void @splash_show(ptr %bytes, i32 %len, i32 %bg) { entry: + %wp = alloca i32, align 4 + %hp = alloca i32, align 4 + %dpip = alloca i32, align 4 + %wc = alloca [80 x i8], align 8 + %had = load ptr, ptr @W_splash + %again = icmp ne ptr %had, null + br i1 %again, label %out, label %decode +decode: + %px = call ptr @w_decode(ptr %bytes, i32 %len, ptr %wp, ptr %hp, ptr %dpip) + %nopx = icmp eq ptr %px, null + br i1 %nopx, label %out, label %over +over: + %w = load i32, ptr %wp + %h = load i32, ptr %hp + ; composite over the background colour, so transparent edges sit on the game's backdrop + %bgb = and i32 %bg, 255 + %bgg0 = lshr i32 %bg, 8 + %bgg = and i32 %bgg0, 255 + %bgr0 = lshr i32 %bg, 16 + %bgr = and i32 %bgr0, 255 + %w64 = sext i32 %w to i64 + %h64 = sext i32 %h to i64 + %n = mul i64 %w64, %h64 + br label %mix +mix: + %i = phi i64 [ 0, %over ], [ %i1, %mixb ] + %more = icmp slt i64 %i, %n + br i1 %more, label %mixb, label %placed +mixb: + %o = mul i64 %i, 4 + %pb = getelementptr i8, ptr %px, i64 %o + %o1 = add i64 %o, 1 + %pg = getelementptr i8, ptr %px, i64 %o1 + %o2 = add i64 %o, 2 + %pr = getelementptr i8, ptr %px, i64 %o2 + %o3 = add i64 %o, 3 + %pa = getelementptr i8, ptr %px, i64 %o3 + %a8 = load i8, ptr %pa + %a = zext i8 %a8 to i32 + %ia = sub i32 255, %a + %sb8 = load i8, ptr %pb + %sb = zext i8 %sb8 to i32 + %mb0 = mul i32 %sb, %a + %mb1 = mul i32 %bgb, %ia + %mb2 = add i32 %mb0, %mb1 + %mb = udiv i32 %mb2, 255 + %tb = trunc i32 %mb to i8 + store i8 %tb, ptr %pb + %sg8 = load i8, ptr %pg + %sg = zext i8 %sg8 to i32 + %mg0 = mul i32 %sg, %a + %mg1 = mul i32 %bgg, %ia + %mg2 = add i32 %mg0, %mg1 + %mg = udiv i32 %mg2, 255 + %tg = trunc i32 %mg to i8 + store i8 %tg, ptr %pg + %sr8 = load i8, ptr %pr + %sr = zext i8 %sr8 to i32 + %mr0 = mul i32 %sr, %a + %mr1 = mul i32 %bgr, %ia + %mr2 = add i32 %mr0, %mr1 + %mr = udiv i32 %mr2, 255 + %tr = trunc i32 %mr to i8 + store i8 %tr, ptr %pr + store i8 -1, ptr %pa + %i1 = add i64 %i, 1 + br label %mix +placed: + store ptr %px, ptr @W_splash_px + store i32 %w, ptr @W_splash_w + store i32 %h, ptr @W_splash_h + ; points = pixels x 72 / dpi; pixels on screen = points x the display's scale + %dpi = load i32, ptr %dpip + %pw0 = mul i32 %w, 72 + %ptw = sdiv i32 %pw0, %dpi + %ph0 = mul i32 %h, 72 + %pth = sdiv i32 %ph0, %dpi + %d0 = call i32 @SetProcessDpiAwarenessContext(ptr inttoptr (i64 -4 to ptr)) + %sysdpi = call i32 @GetDpiForSystem() + %ds = call i32 @w_fit_scale(i32 %sysdpi, i32 %ptw, i32 %pth) + %dw = mul i32 %ptw, %ds + %dh = mul i32 %pth, %ds + store i32 %dw, ptr @W_splash_dw + store i32 %dh, ptr @W_splash_dh + %inst = call ptr @GetModuleHandleA(ptr null) + call void @llvm.memset.p0.i64(ptr %wc, i8 0, i64 80, i1 false) + store i32 80, ptr %wc + %wwp = getelementptr i8, ptr %wc, i64 8 + store ptr @w_splash_proc, ptr %wwp + %whi = getelementptr i8, ptr %wc, i64 24 + store ptr %inst, ptr %whi + %arrow = call ptr @LoadCursorA(ptr null, ptr inttoptr (i64 32512 to ptr)) + %whc = getelementptr i8, ptr %wc, i64 40 + store ptr %arrow, ptr %whc + %wcn = getelementptr i8, ptr %wc, i64 64 + store ptr @.w_splash_class, ptr %wcn + %atom = call i16 @RegisterClassExA(ptr %wc) + %sw = call i32 @GetSystemMetrics(i32 0) + %sh = call i32 @GetSystemMetrics(i32 1) + %xw = sub i32 %sw, %dw + %x = sdiv i32 %xw, 2 + %yh = sub i32 %sh, %dh + %y = sdiv i32 %yh, 2 + ; WS_EX_TOPMOST | WS_EX_TOOLWINDOW (no taskbar button), WS_POPUP | WS_VISIBLE + %win = call ptr @CreateWindowExA(i32 136, ptr @.w_splash_class, ptr @.w_empty, i32 -1879048192, i32 %x, i32 %y, i32 %dw, i32 %dh, ptr null, ptr null, ptr %inst, ptr null) + %nowin = icmp eq ptr %win, null + br i1 %nowin, label %out, label %show +show: + store ptr %win, ptr @W_splash + ; paint it now: the game is about to spend a while loading and pumps nothing meanwhile + %u = call i32 @UpdateWindow(ptr %win) + br label %out +out: ret void } + define void @splash_hide() { entry: + %win = load ptr, ptr @W_splash + %none = icmp eq ptr %win, null + br i1 %none, label %out, label %go +go: + %d = call i32 @DestroyWindow(ptr %win) + store ptr null, ptr @W_splash + %px = load ptr, ptr @W_splash_px + call void @free(ptr %px) + store ptr null, ptr @W_splash_px + br label %out +out: ret void } + +; ---- the window icon --------------------------------------------------------------- +; +; App.set_icon(path): the title bar and the taskbar button of a build with no icon +; resource - which is every `ludic build`; a bundled .exe carries its own. The image +; becomes an HICON from a 32-bit DIB with its alpha, set on the window now, or as soon +; as win_open makes one. + +@W_icon = internal global ptr null + +declare void @free(ptr) +declare ptr @malloc(i64) + +define internal void @w_apply_icon() { +entry: + %hwnd = load ptr, ptr @W_hwnd + %icon = load ptr, ptr @W_icon + %nw = icmp eq ptr %hwnd, null + %ni = icmp eq ptr %icon, null + %skip = or i1 %nw, %ni + br i1 %skip, label %out, label %set +set: + %iv = ptrtoint ptr %icon to i64 + %r1 = call i64 @SendMessageA(ptr %hwnd, i32 128, i64 1, i64 %iv) ; WM_SETICON, ICON_BIG + %r0 = call i64 @SendMessageA(ptr %hwnd, i32 128, i64 0, i64 %iv) ; ICON_SMALL + br label %out +out: + ret void +} + define void @app_set_icon(ptr %bytes, i32 %len) { entry: + %wp = alloca i32, align 4 + %hp = alloca i32, align 4 + %dpip = alloca i32, align 4 + %bmi = alloca [44 x i8], align 4 + %bitsp = alloca ptr, align 8 + %ii = alloca [32 x i8], align 8 ; ICONINFO: fIcon 0, hbmMask 16, hbmColor 24 + %px = call ptr @w_decode(ptr %bytes, i32 %len, ptr %wp, ptr %hp, ptr %dpip) + %nopx = icmp eq ptr %px, null + br i1 %nopx, label %out, label %dib +dib: + %w = load i32, ptr %wp + %h = load i32, ptr %hp + call void @llvm.memset.p0.i64(ptr %bmi, i8 0, i64 44, i1 false) + store i32 40, ptr %bmi + %bw = getelementptr i8, ptr %bmi, i64 4 + store i32 %w, ptr %bw + %nh = sub i32 0, %h + %bh = getelementptr i8, ptr %bmi, i64 8 + store i32 %nh, ptr %bh + %bpl = getelementptr i8, ptr %bmi, i64 12 + store i16 1, ptr %bpl + %bbc = getelementptr i8, ptr %bmi, i64 14 + store i16 32, ptr %bbc + store ptr null, ptr %bitsp + %color = call ptr @CreateDIBSection(ptr null, ptr %bmi, i32 0, ptr %bitsp, ptr null, i32 0) + %nocolor = icmp eq ptr %color, null + br i1 %nocolor, label %freepx, label %fill +fill: + %bits = load ptr, ptr %bitsp + %w64 = sext i32 %w to i64 + %h64 = sext i32 %h to i64 + %n = mul i64 %w64, %h64 + %nb = mul i64 %n, 4 + call void @llvm.memcpy.p0.p0.i64(ptr %bits, ptr %px, i64 %nb, i1 false) + ; with an alpha channel in the colour bitmap, an all-zero mask leaves every pixel to it + %mask = call ptr @CreateBitmap(i32 %w, i32 %h, i32 1, i32 1, ptr null) + call void @llvm.memset.p0.i64(ptr %ii, i8 0, i64 32, i1 false) + store i32 1, ptr %ii + %mp = getelementptr i8, ptr %ii, i64 16 + store ptr %mask, ptr %mp + %cp = getelementptr i8, ptr %ii, i64 24 + store ptr %color, ptr %cp + %icon = call ptr @CreateIconIndirect(ptr %ii) + %d1 = call i32 @DeleteObject(ptr %mask) + %d2 = call i32 @DeleteObject(ptr %color) + %noicon = icmp eq ptr %icon, null + br i1 %noicon, label %freepx, label %keep +keep: + store ptr %icon, ptr @W_icon + call void @w_apply_icon() + br label %freepx +freepx: + call void @free(ptr %px) + br label %out +out: ret void } diff --git a/selfhost/main.ludic b/selfhost/main.ludic index 66091c9d..ba75919e 100644 --- a/selfhost/main.ludic +++ b/selfhost/main.ludic @@ -203,9 +203,6 @@ entry { if emit_ir { ir_flush(out); return } if not has_target { ir_flush(null); return } - # Http.* is still a macOS platform layer (NSURLConnection) with no Windows - # counterpart; linking it would fail on Objective-C symbols rather than say so. - if g_target_win and g_uses_http { die("ludicc: Http.* is not available on Windows yet\n") } # a run with no explicit -o lands in a temp file if run and (out == null) { @@ -243,7 +240,8 @@ entry { let win32 = join_path(home, "runtime/native/win32.ll") # xinput.lib carries an import named DllMain, which lld skips with a warning on every # link; it is the SDK's, not a mistake in the program, so it is silenced - cmd = `{cmd} {win32} -luser32 -lgdi32 -lxinput -Wl,/ignore:importeddllmain` + # ole32 for WIC, which decodes the splash and the window icon + cmd = `{cmd} {win32} -luser32 -lgdi32 -lxinput -lole32 -Wl,/ignore:importeddllmain` if g_uses_audio { let audiow = join_path(home, "runtime/native/audio_win.ll") cmd = `{cmd} {audiow} -lxaudio2 -lole32` @@ -268,9 +266,15 @@ entry { # Http.* (#6) links the native transport and Foundation (NSURLConnection etc., # reached by name). Works headless too, so it is outside the windowed block — # macOS-only for now, which is where the toolchain runs. + # On Windows the transport is http_win.ll, over WinHTTP. if g_uses_http { - let http = join_path(home, "runtime/native/http.ll") - cmd = `{cmd} {http} -Wl,-needed_framework,Foundation` + if g_target_win { + let httpw = join_path(home, "runtime/native/http_win.ll") + cmd = `{cmd} {httpw} -lwinhttp` + } else { + let http = join_path(home, "runtime/native/http.ll") + cmd = `{cmd} {http} -Wl,-needed_framework,Foundation` + } } # Gl.* links the OpenGL backend: gl.ll (offscreen contexts, float helpers), the # generated per-entry-point ABI thunks, and OpenGL.framework. Windowed or not.