# glgen.ludic — the OpenGL binding generator, in Ludic (ported off glgen.py, the # same way docgen.ludic was ported off gen.py). One x subcommand: # # ludic-dev glgen [--check] read the platform gl3.h and emit # runtime/native/gl_api.ludic + runtime/native/gl_thunks.ll; # --check regenerates into scratch files and compares, # so the drift guard judges the working tree. # # Both outputs are tracked, so a build never runs this: it is the tool you run # when the SDK's gl3.h changes. Output is byte-identical to the Python generator # it replaces (verified by the drift guard in `ludic-dev test`). # ---- parsed constants ------------------------------------------------------- var glg_cname: []pointer = null # GL_DEPTH_BUFFER_BIT var glg_ctype: []pointer = null # "int" | "long" var glg_cval: []pointer = null # the literal as it is emitted # ---- parsed entry points ---------------------------------------------------- # Parallel arrays; the params of function f are the pcount[f] entries of the flat # pp_* arrays starting at poff[f]. Flat arrays keep this free of nested slices. var glg_fname: []pointer = null # "CullFace" (the gl prefix already stripped) var glg_frbase: []pointer = null # return base type ("void", "GLuint", …) var glg_frstar: []int = null # return pointer depth var glg_fpoff: []int = null var glg_fpcnt: []int = null var glg_ppbase: []pointer = null var glg_ppstar: []int = null var glg_ppname: []pointer = null # ---- character helpers ------------------------------------------------------ function glg_upper(c: int) -> bool { return c >= 'A' and c <= 'Z' } function glg_lower(c: int) -> bool { return c >= 'a' and c <= 'z' } function glg_digit(c: int) -> bool { return c >= '0' and c <= '9' } function glg_hexdig(c: int) -> bool { return glg_digit(c) or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F') } # a GL_* macro name character function glg_namech(c: int) -> bool { return glg_upper(c) or glg_digit(c) or c == '_' } # a fresh NUL-terminated copy of s[a..b) # CamelCase -> snake_case, exactly as glgen.py's snake(): an underscore goes in # before an upper-case letter that follows a lower-case one, nowhere else. function glg_snake(n: pointer) -> pointer { let b = sb_new() let m = slen(n) var i = 0 while i < m { let c = n[i] if glg_upper(c) and i > 0 and glg_lower(n[i - 1]) { sb_putc(b, '_') } if glg_upper(c) { sb_putc(b, c + 32) } else { sb_putc(b, c) } i += 1 } return sb_str(b) } # ---- the type tables (glgen.py's INT / LONG / FLT / DBL) -------------------- # Membership in a space-delimited list, matched as whole tokens — one line per # table instead of a chain of `or`s that a newline would cut in half. function glg_in(t: pointer, set: pointer) -> bool { let n = slen(set) var p = 0 while p <= n { var q = p while q < n and set[q] != 32 { q += 1 } if str_sub(set, p, q) == t { return true } if q >= n { break } p = q + 1 } return false } function glg_is_int(t: pointer) -> bool { return glg_in(t, "GLenum GLuint GLint GLsizei GLbitfield GLshort GLushort GLbyte GLubyte GLhalf GLchar GLboolean GLfixed") } function glg_is_long(t: pointer) -> bool { return glg_in(t, "GLsizeiptr GLintptr GLint64 GLuint64 GLint64EXT GLuint64EXT") } function glg_is_flt(t: pointer) -> bool { return glg_in(t, "GLfloat GLclampf") } function glg_is_dbl(t: pointer) -> bool { return glg_in(t, "GLdouble GLclampd") } # the LLVM type a C parameter of this shape has function glg_ir_ty(base: pointer, star: int) -> pointer { if star > 0 { return "ptr" } if base == "GLboolean" { return "i8" } if base == "GLbyte" or base == "GLubyte" or base == "GLchar" { return "i8" } if base == "GLshort" or base == "GLushort" or base == "GLhalf" { return "i16" } if glg_is_int(base) { return "i32" } if glg_is_long(base) { return "i64" } if glg_is_flt(base) { return "float" } if glg_is_dbl(base) { return "double" } if base == "GLsync" { return "ptr" } if base == "void" { return "void" } return null } # the Ludic type it is bound as function glg_ludic_ty(base: pointer, star: int) -> pointer { if star > 0 or base == "GLsync" { return "pointer" } if glg_is_int(base) { return "int" } if glg_is_long(base) { return "long" } if glg_is_flt(base) or glg_is_dbl(base) { return "fixed" } if base == "void" { return "void" } return null } # the Ludic ABI type carrying an LLVM type across the extern boundary function glg_abi_ty(ct: pointer) -> pointer { if ct == "i64" { return "i64" } if ct == "ptr" { return "ptr" } if ct == "void" { return "void" } return "i32" } # ---- parameter names -------------------------------------------------------- # A Ludic keyword or type name cannot label a parameter. glVertexAttribPointer's # `pointer` is a byte offset, so it says so rather than wearing a trailing _. function glg_reserved(n: pointer) -> bool { if glg_in(n, "program import property model enum ui namespace const var function extern handler entry") { return true } if glg_in(n, "event scene test phase query on cancellable public layer start") { return true } if glg_in(n, "let return if else while for in spawn despawn enable disable match machine state become where prefab") { return true } if glg_in(n, "and or not break continue new emit cancel try") { return true } if glg_in(n, "int long fixed countdown bool entity string pointer byte words fixeds pointers") { return true } if glg_in(n, "Vector IVec2 Rect void true false null") { return true } return false } function glg_pname(raw: pointer, i: int) -> pointer { var n = raw if slen(n) == 0 { n = `a{string(i)}` } n = glg_snake(n) if n == "pointer" { return "offset" } if n == "program" { return "prog" } if n == "start" { return "first" } if n == "string" { return "text" } if n == "layer" { return "level" } if glg_reserved(n) { return n + "_" } return n } # ---- one C declarator -> (base, star, name) --------------------------------- # glgen.py's ctype_of: count the stars, drop every `const`, split what is left. # Sets glg_t_base / glg_t_star / glg_t_name (Ludic has no tuple return). var glg_t_base: pointer = null var glg_t_star: int = 0 var glg_t_name: pointer = null function glg_ctype_of(decl: pointer) -> void { let m = slen(decl) var star = 0 var i = 0 while i < m { if decl[i] == '*' { star += 1 } i += 1 } # drop `const` (whole words) and turn '*' into a separator let b = sb_new() i = 0 while i < m { if decl[i] == 'c' and i + 5 <= m and str_sub(decl, i, i + 5) == "const" { i += 5 sb_putc(b, 32) } else { if decl[i] == '*' { sb_putc(b, 32) } else { sb_putc(b, decl[i]) } i += 1 } } let flat = sb_str(b) # split on whitespace let parts = new []pointer let fn = slen(flat) var p = 0 while p < fn { while p < fn and str_space(flat[p]) { p += 1 } if p >= fn { break } var q = p while q < fn and not str_space(flat[q]) { q += 1 } push(parts, str_sub(flat, p, q)) p = q } glg_t_star = star if len(parts) == 0 { glg_t_base = ""; glg_t_name = ""; return } glg_t_base = parts[0] if len(parts) > 1 { glg_t_name = parts[1] } else { glg_t_name = "" } } # ---- constants -------------------------------------------------------------- # `#define GL_NAME <0xHEX|-?DEC>[u|U|ull|ULL]` and nothing else on the line — # the shape glgen.py's regex accepted. Returns false when the line is not one. var glg_v_hex: bool = false var glg_v_digits: pointer = null # the numeric text, suffix stripped var glg_v_neg: bool = false function glg_num_of(tok: pointer) -> bool { let n = slen(tok) var i = 0 glg_v_neg = false glg_v_hex = false if i < n and tok[i] == '-' { glg_v_neg = true; i += 1 } let numstart = i if i + 1 < n and tok[i] == '0' and tok[i + 1] == 'x' { if glg_v_neg { return false } glg_v_hex = true i += 2 let ds = i while i < n and glg_hexdig(tok[i]) { i += 1 } if i == ds { return false } } else { let ds = i while i < n and glg_digit(tok[i]) { i += 1 } if i == ds { return false } } glg_v_digits = str_sub(tok, numstart, i) # what is left must be one of the accepted integer suffixes let suf = str_sub(tok, i, n) if suf == "" or suf == "u" or suf == "U" or suf == "ull" or suf == "ULL" { return true } return false } # accumulate hex digits into an i32: the wrap makes an 8-digit value with the top # bit set come out as the signed integer with those exact bits (glgen.py's v - 2^32). function glg_hex_i32(h: pointer) -> int { var v = 0 var i = 0 while i < slen(h) { let c = h[i] var d = 0 if glg_digit(c) { d = c - '0' } if c >= 'a' and c <= 'f' { d = c - 'a' + 10 } if c >= 'A' and c <= 'F' { d = c - 'A' + 10 } v = v * 16 + d i += 1 } return v } function glg_parse_define(line: pointer) -> void { let n = slen(line) if n < 8 { return } if str_sub(line, 0, 8) != "#define " { return } var i = 8 while i < n and str_space(line[i]) { i += 1 } let ns = i while i < n and glg_namech(line[i]) { i += 1 } let name = str_sub(line, ns, i) if slen(name) < 4 { return } if str_sub(name, 0, 3) != "GL_" { return } # the name must be followed by whitespace (not '(' — that is a macro) if i >= n or not str_space(line[i]) { return } while i < n and str_space(line[i]) { i += 1 } let vs = i while i < n and not str_space(line[i]) { i += 1 } let tok = str_sub(line, vs, i) # trailing whitespace only while i < n and str_space(line[i]) { i += 1 } if i != n { return } if slen(tok) == 0 { return } # glgen.py skips the GL_VERSION_* feature macros and any repeated name if slen(name) >= 11 and str_sub(name, 0, 11) == "GL_VERSION_" { return } var k = 0 while k < len(glg_cname) { if glg_cname[k] == name { return } k += 1 } if not glg_num_of(tok) { return } # classify the width exactly as glgen.py did, but by digit count so a 64-bit # all-ones literal never has to be parsed into a signed 64-bit register. if glg_v_hex { var d = glg_v_digits var h = str_sub(d, 2, slen(d)) # drop the 0x var z = 0 while z < slen(h) - 1 and h[z] == '0' { z += 1 } h = str_sub(h, z, slen(h)) let hn = slen(h) if hn > 8 { push(glg_cname, name); push(glg_ctype, "long"); push(glg_cval, "-1") return } if hn == 8 and not (h[0] >= '0' and h[0] <= '7') { # 0x8… — the same bits as a negative i32; emit the signed decimal let v = glg_hex_i32(h) push(glg_cname, name); push(glg_ctype, "int"); push(glg_cval, string(v)) return } push(glg_cname, name); push(glg_ctype, "int"); push(glg_cval, glg_v_digits) return } var lit = glg_v_digits if glg_v_neg { lit = "-" + lit } push(glg_cname, name); push(glg_ctype, "int"); push(glg_cval, lit) } # ---- entry points ----------------------------------------------------------- # `GLAPI APIENTRY gl () …;` — every gl3.h declaration is on one # line, so a line scan matches what glgen.py's re.M regex did. function glg_parse_glapi(line: pointer) -> void { let n = slen(line) if n < 6 { return } if str_sub(line, 0, 6) != "GLAPI " { return } let ap = Text.index_of(line, " APIENTRY ") if ap < 0 { return } let ret = Text.trim(str_sub(line, 6, ap)) var i = ap + 10 while i < n and str_space(line[i]) { i += 1 } let ns = i while i < n and (glg_upper(line[i]) or glg_lower(line[i]) or glg_digit(line[i]) or line[i] == '_') { i += 1 } let fname = str_sub(line, ns, i) if slen(fname) < 3 { return } if str_sub(fname, 0, 2) != "gl" { return } while i < n and str_space(line[i]) { i += 1 } if i >= n or line[i] != '(' { return } let argstart = i + 1 var depth = 1 i += 1 while i < n and depth > 0 { if line[i] == '(' { depth += 1 } if line[i] == ')' { depth -= 1 } if depth == 0 { break } i += 1 } if depth != 0 { return } let args = str_sub(line, argstart, i) let bare = str_sub(fname, 2, slen(fname)) var k = 0 while k < len(glg_fname) { if glg_fname[k] == bare { return } k += 1 } glg_ctype_of(ret) let rbase = glg_t_base let rstar = glg_t_star let poff = len(glg_ppbase) var pcnt = 0 let at = Text.trim(args) if at != "void" and at != "" { # split the argument list on commas (no function-pointer args in gl3.h) let an = slen(args) var p = 0 while p <= an { var q = p while q < an and args[q] != ',' { q += 1 } let one = str_sub(args, p, q) glg_ctype_of(one) push(glg_ppbase, glg_t_base) push(glg_ppstar, glg_t_star) push(glg_ppname, glg_t_name) pcnt += 1 if q >= an { break } p = q + 1 } } push(glg_fname, bare) push(glg_frbase, rbase) push(glg_frstar, rstar) push(glg_fpoff, poff) push(glg_fpcnt, pcnt) } function glg_parse(h: pointer) -> void { glg_cname = new []pointer; glg_ctype = new []pointer; glg_cval = new []pointer glg_fname = new []pointer; glg_frbase = new []pointer; glg_frstar = new []int glg_fpoff = new []int; glg_fpcnt = new []int glg_ppbase = new []pointer; glg_ppstar = new []int; glg_ppname = new []pointer let n = slen(h) var i = 0 while i < n { var j = i while j < n and h[j] != 10 { j += 1 } let line = str_sub(h, i, j) if slen(line) > 0 { if line[0] == '#' { glg_parse_define(line) } if line[0] == 'G' { glg_parse_glapi(line) } } i = j + 1 } } # ---- emit gl_api.ludic ------------------------------------------------------ function glg_emit_ludic(path: pointer) -> bool { let b = sb_new() sb_puts(b, "# ============================================================================\n") sb_puts(b, "# gl_api.ludic — the OpenGL 4.1 core API, bound for Ludic. GENERATED by\n") sb_puts(b, "# `ludic-dev glgen` from the platform gl3.h: every entry point and every GL_* constant.\n") sb_puts(b, "# Do not edit by hand; regenerate. float/double parameters take `fixed`; the IR\n") sb_puts(b, "# thunks in runtime/native/gl.ll (also generated) convert at the C boundary.\n") sb_puts(b, "# ============================================================================\n") sb_puts(b, "\n") var i = 0 while i < len(glg_cname) { sb_puts(b, `const {glg_cname[i]}: {glg_ctype[i]} = {glg_cval[i]}\n`) i += 1 } sb_puts(b, "\n") i = 0 while i < len(glg_fname) { let lname = "gl_" + glg_snake(glg_fname[i]) let ps = sb_new() var p = 0 while p < glg_fpcnt[i] { let ix = glg_fpoff[i] + p if p > 0 { sb_puts(ps, ", ") } let lt = glg_ludic_ty(glg_ppbase[ix], glg_ppstar[ix]) sb_puts(ps, `{glg_pname(glg_ppname[ix], p)}: {lt}`) p += 1 } var rt = glg_ludic_ty(glg_frbase[i], glg_frstar[i]) # a returned `const GLubyte *` is the driver's text (glGetString): a string, not memory if glg_frstar[i] == 1 and (glg_frbase[i] == "GLubyte" or glg_frbase[i] == "GLchar") { rt = "string" } var rets = "" if rt != "void" { rets = " -> " + rt } sb_puts(b, `extern function {lname}({sb_str(ps)}){rets} = "lgl_{glg_fname[i]}"\n`) i += 1 } return write_file(path, sb_str(b)) } # ---- emit gl_thunks.ll ------------------------------------------------------ function glg_emit_thunks(path: pointer) -> bool { let b = sb_new() sb_puts(b, "; ============================================================================\n") sb_puts(b, "; gl_thunks.ll — one thunk per OpenGL 4.1 core entry point. GENERATED by\n") sb_puts(b, "; `ludic-dev glgen` from gl3.h. Each @lgl_* takes the Ludic ABI (i32 / i64 / ptr,\n") sb_puts(b, "; float and double as Q16.16 fixed) and calls the real gl* with exact C types.\n") sb_puts(b, "; ============================================================================\n") sb_puts(b, "\n") var i = 0 while i < len(glg_fname) { let cret = glg_ir_ty(glg_frbase[i], glg_frstar[i]) let ps = sb_new() var p = 0 while p < glg_fpcnt[i] { let ix = glg_fpoff[i] + p if p > 0 { sb_puts(ps, ", ") } sb_puts(ps, glg_ir_ty(glg_ppbase[ix], glg_ppstar[ix])) p += 1 } sb_puts(b, `declare {cret} @gl{glg_fname[i]}({sb_str(ps)})\n`) i += 1 } sb_puts(b, "\n") i = 0 while i < len(glg_fname) { let cret = glg_ir_ty(glg_frbase[i], glg_frstar[i]) let lret = glg_abi_ty(cret) let sig = sb_new() let body = sb_new() let call = sb_new() var p = 0 while p < glg_fpcnt[i] { let ix = glg_fpoff[i] + p let ct = glg_ir_ty(glg_ppbase[ix], glg_ppstar[ix]) let lt = glg_abi_ty(ct) if p > 0 { sb_puts(sig, ", "); sb_puts(call, ", ") } sb_puts(sig, `{lt} %a{string(p)}`) if ct == lt { sb_puts(call, `{ct} %a{string(p)}`) } else { if ct == "i8" or ct == "i16" { sb_puts(body, ` %c{string(p)} = trunc i32 %a{string(p)} to {ct}\n`) sb_puts(call, `{ct} %c{string(p)}`) } else { # float / double: the Ludic side passes Q16.16, so scale by 1/65536 sb_puts(body, ` %f{string(p)} = sitofp i32 %a{string(p)} to {ct}\n`) sb_puts(body, ` %c{string(p)} = fmul {ct} %f{string(p)}, 0x3EF0000000000000\n`) sb_puts(call, `{ct} %c{string(p)}`) } } p += 1 } sb_puts(b, `define {lret} @lgl_{glg_fname[i]}({sb_str(sig)}) `) sb_puts(b, "{\n") sb_puts(b, "entry:\n") sb_puts(b, sb_str(body)) let invoke = `call {cret} @gl{glg_fname[i]}({sb_str(call)})` if cret == "void" { sb_puts(b, ` {invoke}\n`) sb_puts(b, " ret void\n") } else { if cret == lret { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` ret {lret} %r\n`) } else { if cret == "i8" or cret == "i16" { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` %z = zext {cret} %r to i32\n`) sb_puts(b, " ret i32 %z\n") } else { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` %m = fmul {cret} %r, 65536.0\n`) sb_puts(b, ` %z = fptosi {cret} %m to i32\n`) sb_puts(b, " ret i32 %z\n") } } } sb_puts(b, "}\n") i += 1 } return write_file(path, sb_str(b)) } # ---- the task --------------------------------------------------------------- # `ludic-dev glgen` rewrites the two tracked outputs; `--check` regenerates into # scratch files and compares, so the guard judges the working tree, not git HEAD. function cmd_glgen() -> int { let check = arg_count() > 2 and arg(2) == "--check" let sdk = Text.trim(capture("xcrun --show-sdk-path")) if sdk == "" { err("glgen: no macOS SDK (xcrun --show-sdk-path)\n"); return 1 } let hpath = `{sdk}/System/Library/Frameworks/OpenGL.framework/Headers/gl3.h` let h = read_file(hpath) if h == null { err(`glgen: cannot read {hpath}\n`); return 1 } glg_parse(h) if len(glg_fname) == 0 { err("glgen: no GLAPI declarations found\n"); return 1 } # every type in the header must be one this generator knows how to bind var i = 0 while i < len(glg_fname) { if glg_ir_ty(glg_frbase[i], glg_frstar[i]) == null or glg_ludic_ty(glg_frbase[i], glg_frstar[i]) == null { err(`glgen: unknown return type {glg_frbase[i]} on gl{glg_fname[i]}\n`); return 1 } var p = 0 while p < glg_fpcnt[i] { let ix = glg_fpoff[i] + p if glg_ir_ty(glg_ppbase[ix], glg_ppstar[ix]) == null or glg_ludic_ty(glg_ppbase[ix], glg_ppstar[ix]) == null { err(`glgen: unknown parameter type {glg_ppbase[ix]} on gl{glg_fname[i]}\n`); return 1 } p += 1 } i += 1 } var api_out = "runtime/native/gl_api.ludic" var thunk_out = "runtime/native/gl_thunks.ll" var win_out = "runtime/native/gl_thunks_win.ll" if check { api_out = tmp_path("gl_api.ludic"); thunk_out = tmp_path("gl_thunks.ll"); win_out = tmp_path("gl_thunks_win.ll") } if not glg_emit_ludic(api_out) { err("glgen: cannot write gl_api.ludic\n"); return 1 } if not glg_emit_thunks(thunk_out) { err("glgen: cannot write gl_thunks.ll\n"); return 1 } if not glg_emit_thunks_win(win_out) { err("glgen: cannot write gl_thunks_win.ll\n"); return 1 } if check { if not shq(`cmp -s {api_out} runtime/native/gl_api.ludic`) { err("gl_api.ludic drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 } if not shq(`cmp -s {thunk_out} runtime/native/gl_thunks.ll`) { err("gl_thunks.ll drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 } if not shq(`cmp -s {win_out} runtime/native/gl_thunks_win.ll`) { err("gl_thunks_win.ll drifted from gl3.h (run: ludic-dev glgen)\n"); return 1 } } print(`OK {string(len(glg_cname))} constants, {string(len(glg_fname))} entry points`) return 0 } # ---- emit gl_thunks_win.ll -------------------------------------------------- # The Windows thunks. Same Ludic ABI and the same conversions as gl_thunks.ll, but # no thunk names a gl* symbol: opengl32.dll exports only OpenGL 1.1, and every later # entry point belongs to the driver, reachable only through wglGetProcAddress while # a context is current. So each thunk calls through @lgl_p_, and # @lgl_win_load (called by gl_win.ll once its 4.1 core context is current) fills # the table - from the driver first, then from opengl32.dll for the 1.1 functions, # which wglGetProcAddress does not return. function glg_emit_thunks_win(path: pointer) -> bool { let b = sb_new() sb_puts(b, "; ============================================================================\n") sb_puts(b, "; gl_thunks_win.ll — one thunk per OpenGL 4.1 core entry point, for Windows.\n") sb_puts(b, "; GENERATED by `ludic-dev glgen` from gl3.h. The thunks of gl_thunks.ll, calling\n") sb_puts(b, "; through @lgl_p_* pointers that @lgl_win_load fills from the current context.\n") sb_puts(b, "; ============================================================================\n") sb_puts(b, "\n") sb_puts(b, "declare ptr @wglGetProcAddress(ptr)\n") sb_puts(b, "declare ptr @LoadLibraryA(ptr)\n") sb_puts(b, "declare ptr @GetProcAddress(ptr, ptr)\n") sb_puts(b, "@.lgl_dll = private unnamed_addr constant [13 x i8] c\"opengl32.dll\\00\"\n") var i = 0 while i < len(glg_fname) { let nm = glg_fname[i] sb_puts(b, `@lgl_p_{nm} = internal global ptr null\n`) sb_puts(b, `@.lgl_n_{nm} = private unnamed_addr constant [{string(len(nm) + 3)} x i8] c"gl{nm}\\00"\n`) i += 1 } sb_puts(b, "\n") i = 0 while i < len(glg_fname) { let cret = glg_ir_ty(glg_frbase[i], glg_frstar[i]) let lret = glg_abi_ty(cret) let sig = sb_new() let body = sb_new() let call = sb_new() var p = 0 while p < glg_fpcnt[i] { let ix = glg_fpoff[i] + p let ct = glg_ir_ty(glg_ppbase[ix], glg_ppstar[ix]) let lt = glg_abi_ty(ct) if p > 0 { sb_puts(sig, ", "); sb_puts(call, ", ") } sb_puts(sig, `{lt} %a{string(p)}`) if ct == lt { sb_puts(call, `{ct} %a{string(p)}`) } else { if ct == "i8" or ct == "i16" { sb_puts(body, ` %c{string(p)} = trunc i32 %a{string(p)} to {ct}\n`) sb_puts(call, `{ct} %c{string(p)}`) } else { sb_puts(body, ` %f{string(p)} = sitofp i32 %a{string(p)} to {ct}\n`) sb_puts(body, ` %c{string(p)} = fmul {ct} %f{string(p)}, 0x3EF0000000000000\n`) sb_puts(call, `{ct} %c{string(p)}`) } } p += 1 } sb_puts(b, `define {lret} @lgl_{glg_fname[i]}({sb_str(sig)}) `) sb_puts(b, "{\n") sb_puts(b, "entry:\n") sb_puts(b, ` %fp = load ptr, ptr @lgl_p_{glg_fname[i]}\n`) sb_puts(b, sb_str(body)) let invoke = `call {cret} %fp({sb_str(call)})` if cret == "void" { sb_puts(b, ` {invoke}\n`) sb_puts(b, " ret void\n") } else { if cret == lret { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` ret {lret} %r\n`) } else { if cret == "i8" or cret == "i16" { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` %z = zext {cret} %r to i32\n`) sb_puts(b, " ret i32 %z\n") } else { sb_puts(b, ` %r = {invoke}\n`) sb_puts(b, ` %m = fmul {cret} %r, 65536.0\n`) sb_puts(b, ` %z = fptosi {cret} %m to i32\n`) sb_puts(b, " ret i32 %z\n") } } } sb_puts(b, "}\n") i += 1 } # the loader: wglGetProcAddress returns 0, 1, 2, 3 or -1 for a name it does not # know (drivers differ), and never answers for a 1.1 function; both fall back to # opengl32.dll's own export. Returns how many names are still unresolved. sb_puts(b, "\ndefine i32 @lgl_win_load() {\n") sb_puts(b, "entry:\n %dll = call ptr @LoadLibraryA(ptr @.lgl_dll)\n br label %f0\n") i = 0 while i < len(glg_fname) { let nm = glg_fname[i] let k = string(i) sb_puts(b, `f{k}:\n %m{k} = phi i32 [ {glg_load_in(i)} ]\n`) sb_puts(b, ` %w{k} = call ptr @wglGetProcAddress(ptr @.lgl_n_{nm})\n`) sb_puts(b, ` %wi{k} = ptrtoint ptr %w{k} to i64\n %wa{k} = add i64 %wi{k}, 1\n %bad{k} = icmp ult i64 %wa{k}, 5\n`) sb_puts(b, ` br i1 %bad{k}, label %g{k}, label %s{k}\n`) sb_puts(b, `g{k}:\n %e{k} = call ptr @GetProcAddress(ptr %dll, ptr @.lgl_n_{nm})\n br label %s{k}\n`) sb_puts(b, `s{k}:\n %p{k} = phi ptr [ %w{k}, %f{k} ], [ %e{k}, %g{k} ]\n store ptr %p{k}, ptr @lgl_p_{nm}\n`) sb_puts(b, ` %u{k} = icmp eq ptr %p{k}, null\n %ui{k} = zext i1 %u{k} to i32\n %m{string(i + 1)}x = add i32 %m{k}, %ui{k}\n`) sb_puts(b, ` br label %f{string(i + 1)}\n`) i += 1 } let n = string(len(glg_fname)) sb_puts(b, `f{n}:\n %m{n} = phi i32 [ %m{n}x, %s{glg_prev_idx(len(glg_fname))} ]\n ret i32 %m{n}\n`) sb_puts(b, "}\n") return write_file(path, sb_str(b)) } # the loader's blocks: f0 is entered only from `entry` with a count of 0, every # later fN only from s(N-1), carrying the count that block computed function glg_load_in(i: int) -> pointer { if i == 0 { return "0, %entry" } return `%m{string(i)}x, %s{string(i - 1)}` } function glg_prev_idx(i: int) -> pointer { if i == 0 { return "0" } return string(i - 1) }