# emit_head.ludic — string constants and the module header (libc declarations, # the slice header type, struct layouts, globals, argv, format strings). function hexdig(n: int) -> int { if n < 10 { return 48 + n }; return 55 + n } # 0-9 A-F # emit `@.strN = ... c"escaped\00"` and return its name; % and non-print -> \XX function emit_str_const(s: pointer) -> pointer { let name = `@.str{itoa(ll_str)}` ll_str += 1 let n = len(s) emith(name); emith(" = private unnamed_addr constant [") emith(itoa(n + 1)); emith(" x i8] c\"") emit_escaped(s) emith("\\00\"\n") return name } # the bytes of s as the body of an IR c"..." string: " \ and non-print -> \XX function emit_escaped(s: pointer) -> void { let n = len(s) var i = 0 while i < n { let c = s[i] if c == '"' or c == CH_BACKSLASH or c < ' ' or c > '~' { buf_putc(head, CH_BACKSLASH) # \XX hex escape buf_putc(head, hexdig(c / 16)) buf_putc(head, hexdig(c % 16)) } else { buf_putc(head, c) } i += 1 } } # an int literal's LLVM constant: its value, or a long literal's own digits function int_lit_code(e: Node) -> pointer { if e.s != null { return e.s } return itoa(e.ival) } # the constant initializer for a global var: a literal, or 0/null function global_init(d: Node) -> pointer { if (d.a == null) or is_fp(d.ty) { return zero_of(llty(d.ty)) } # a float's value is set by L_init_globals let e = d.a if e.kind == E_INT { return int_lit_code(e) } if e.kind == E_FLOAT or e.kind == E_BOOL { return itoa(e.ival) } if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return (("-") + int_lit_code(e.a)) } if e.kind == E_MEMBER and e.a.kind == E_ID { # `Enum.Variant` is a compile-time int let ord = enum_ordinal(e.a.s, e.s) if ord >= 0 { return itoa(ord) } } if (llty(d.ty) == "ptr") { return "null" } return "0" } # does a global's initializer need code at startup (anything global_init cannot fold)? function global_needs_init_code(d: Node) -> bool { if (d.a == null) { return false } if is_fp(d.ty) { return true } let e = d.a if e.kind == E_INT or e.kind == E_FLOAT or e.kind == E_BOOL or e.kind == E_NULL { return false } if e.kind == E_UN and (e.s == ("-")) and e.a.kind == E_INT { return false } if e.kind == E_MEMBER and e.a.kind == E_ID { if enum_ordinal(e.a.s, e.s) >= 0 { return false } } return true } # @L_init_globals(): evaluate every global initializer global_init could not fold # (`var run: Progress = new Progress`, `var speed: int = BASE * 2`, a call), in # declaration order, once at startup — after the runtime boots, before Start. function emit_global_init_fn() -> void { emit_init_fn("L_init_runtime", true) emit_init_fn("L_init_globals", false) emit_state_getters() } # 0.S: the runtime's states are made first, before it boots (rt_init reads them); the rest after function is_runtime_state_var(d: Node) -> bool { return d.uns == 1 and is_state_ty(d.ty) and is_runtime_file(d.file) } function emit_init_fn(name: pointer, runtime: bool) -> void { fence_enter(name) ll_t = 0; ll_lbl = 0; g_term = false; loc_reset(); nloop = 0; nself = 0 ret_ty = "void" let fbody = buf_new() falloc = buf_new() let saved = code code = fbody # in the order their initializers need each other: a state's field default may read a registry # (Species[sp].population) and a table may read a state's instance, whatever order they were declared in let order = gi_order() var oi = 0 while oi < len(order) { let i = order[oi] oi += 1 let d = prog[i] if d.kind == N_VAR and global_needs_init_code(d) and is_runtime_state_var(d) == runtime and not is_lazy_state(d) { # an initializer is the global's own file's code: its errors, and what its module # may see (L3), are that file's - not whichever statement was lowered last g_err_file = d.file g_err_line = d.line # a port's bind is written in the app and checked there (frontend/ports.ludic) if is_port_var(d) { g_err_file = d.a.file g_err_line = d.a.line g_vis_off = true } let v = emit_expr(d.a) g_vis_off = false let cv = coerce_code(v, d.ty) # any conversion is its own line, before the store emit(" store "); emit(llty(d.ty)); emit(" "); emit(cv); emit(", ptr @g_"); emit(d.s); emit("\n") } } emit(" ret void\n") code = saved emit(`define void @{name}() {{\nentry:\n`) emit(buf_str(falloc)) emit(buf_str(fbody)) emit("}\n\n") } function emit_header() -> void { emith("; Ludic (self-hosted) -> LLVM IR\n") emith("declare i32 @printf(ptr, ...)\n") emith("declare ptr @malloc(i64)\n") emith("declare ptr @calloc(i64, i64)\n") emith("declare ptr @realloc(ptr, i64)\n") emith("declare void @free(ptr)\n") emith("declare i64 @fread(ptr, i64, i64, ptr)\n") emith("declare i64 @fwrite(ptr, i64, i64, ptr)\n") emith("declare i32 @fclose(ptr)\n") # on Windows these four are defined by emit_win_prelude, below if not g_target_win { emith("declare ptr @fopen(ptr, ptr)\n") emith("declare i32 @fseek(ptr, i64, i32)\n") emith("declare i64 @ftell(ptr)\n") emith("declare i64 @time(ptr)\n") } emith("declare void @exit(i32)\n") emith("declare i32 @system(ptr)\n") emith("declare ptr @getenv(ptr)\n") emith("declare ptr @memcpy(ptr, ptr, i64)\n") emith("declare ptr @memset(ptr, i32, i64)\n") emith("declare ptr @memmove(ptr, ptr, i64)\n") emith("declare i64 @strlen(ptr)\n") emith("declare i32 @strcmp(ptr, ptr)\n") emith("declare i32 @strncmp(ptr, ptr, i64)\n") emith("declare ptr @strstr(ptr, ptr)\n") emith("declare i32 @atoi(ptr)\n") emith("declare i32 @getchar()\n") emith("declare i32 @putchar(i32)\n") emith("declare void @win_open(i32, i32, i32, ptr)\n") emith("declare i32 @win_poll()\n") emith("declare void @win_present(ptr, i32, i32)\n") emith("declare i32 @win_running()\n") emith("declare void @win_close()\n") emith("declare void @win_held(ptr)\n") emith("declare i32 @win_key_char(i32)\n") emith("declare i32 @win_text(ptr, i32)\n") emith("declare void @win_mouse(ptr)\n") emith("declare void @win_pad(ptr)\n") emith("declare void @win_touch(ptr)\n") emith("declare void @win_cursor_mode(i32)\n") emith("declare ptr @snd_load(ptr)\n") # #22 audio backend (audio.ll) emith("declare void @snd_play(ptr, i32, i32, i32)\n") emith("declare void @snd_stop(ptr)\n") emith("declare i32 @snd_playing(ptr)\n") emith("declare void @snd_set_volume(ptr, i32)\n") emith("declare void @snd_set_rate(ptr, i32)\n") emith("declare void @snd_set_pan(ptr, i32)\n") emith("declare ptr @hs_req_new(ptr, ptr)\n") # #6 HTTP transport (http.ll) emith("declare void @hs_req_header(ptr, ptr, ptr)\n") emith("declare void @hs_req_body(ptr, ptr, i32)\n") emith("declare void @hs_send(i32, ptr)\n") emith("declare i32 @hs_done(i32)\n") emith("declare i32 @hs_status(i32)\n") emith("declare ptr @hs_body(i32)\n") emith("declare i32 @hs_blen(i32)\n") emith("declare ptr @hs_header(i32, ptr)\n") emith("declare void @hs_free(i32)\n") if g_target_win { emit_win_prelude() } # the UCRT/Win32 definitions of the POSIX names above else { emith("@__stderrp = external global ptr\n") emith("@__stdoutp = external global ptr\n") } emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n") emith("@.fmt_long = private unnamed_addr constant [6 x i8] c\"%lld\\0A\\00\"\n") emith("@.fmt_line = private unnamed_addr constant [4 x i8] c\"%s\\0A\\00\"\n") emith("@L_argc = internal global i32 0\n") emith("@L_argv = internal global ptr null\n") emith("@L_clock = internal global i32 0\n") # Clock.* — the game-controlled simulated clock emith("@.gametitle = private unnamed_addr constant [") emith(itoa(len(g_game_name) + 1)); emith(" x i8] c\""); emit_escaped(g_game_name); emith("\\00\"\n") emith("%LSlice = type { ptr, i32, i32 }\n") # property layouts — a %Cmp_ record of named fields, emitted here so `new` # works whether or not the program runs the ECS. The per-entity @S_/@H_ arrays # are separate (emit_ecs_storage), emitted only for a program that runs the ECS. var i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_COMP { var j = 0 # the same record twice would silently use the first while j < i { if prog[j].kind == N_COMP and (prog[j].s == d.s) { if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`property {d.s} is also a property of the engine runtime; choose another name`) } perr(`property {d.s} is declared twice`) } j += 1 } emith(layout_ty(d.s)); emith(" = type { ") if len(d.kids) == 0 { emith("i32") } var f = 0 while f < len(d.kids) { if f > 0 { emith(", ") } emith(llty(d.kids[f].ty)) f += 1 } emith(" }\n") } i += 1 } # globals (vars) — aggregates/pointers default to null, scalars to 0 i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_VAR { var j = 0 # the same name twice would be one LLVM global while j < i { if prog[j].kind == N_VAR and (prog[j].s == d.s) { if (j < g_prog_user_end) and (i >= g_prog_user_end) { perr(`variable {d.s} is also a variable of the engine runtime; choose another name`) } perr(`variable {d.s} is declared twice`) } j += 1 } emith("@g_"); emith(d.s); emith(" = internal global ") if (llty(d.ty) == "ptr") { push(g_scan_roots, d.s) } # 25.5b: where the reachability scan starts emith(llty(d.ty)); emith(" ") emith(global_init(d)) emith("\n") } i += 1 } } # One `declare @()` per `extern fn`, so the linker resolves the # call to the bound symbol. Emitted after the header; a program with no `extern fn` # emits nothing here, so un-networked builds stay byte-identical. function emit_extern_decls() -> void { var i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_EXTERN { emith("declare "); emith(llty(d.ty)); emith(" @"); emith(d.a.s); emith("(") var f = 0 while f < len(d.kids) { if f > 0 { emith(", ") } emith(llty(d.kids[f].ty)) f += 1 } emith(")\n") } i += 1 } } # The string runtime, emitted (once) into any program that uses `+`/`==`/`!=` # on strings. Hand-written IR over NUL-terminated byte buffers: str_eq walks both # until a mismatch or a shared terminator; str_concat measures both, mallocs # len+len+1, copies each half, and NUL-terminates. @malloc is always declared. function emit_str_prelude() -> void { emith("define i32 @lp_str_eq(ptr %a, ptr %b) {\n") emith("entry:\n br label %loop\n") emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %cont ]\n") emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %i\n") emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %i\n") emith(" %ca = load i8, ptr %pa\n %cb = load i8, ptr %pb\n") emith(" %df = icmp ne i8 %ca, %cb\n br i1 %df, label %ret0, label %chk\n") emith("chk:\n %zt = icmp eq i8 %ca, 0\n br i1 %zt, label %ret1, label %cont\n") emith("cont:\n %i1 = add i32 %i, 1\n br label %loop\n") emith("ret1:\n ret i32 1\nret0:\n ret i32 0\n}\n") emith("define ptr @lp_str_concat(ptr %a, ptr %b) {\n") emith("entry:\n br label %al\n") emith("al:\n %ia = phi i32 [ 0, %entry ], [ %ia1, %alb ]\n") emith(" %pa = getelementptr inbounds i8, ptr %a, i32 %ia\n %cca = load i8, ptr %pa\n") emith(" %za = icmp eq i8 %cca, 0\n br i1 %za, label %bl0, label %alb\n") emith("alb:\n %ia1 = add i32 %ia, 1\n br label %al\n") emith("bl0:\n br label %bl\n") emith("bl:\n %ib = phi i32 [ 0, %bl0 ], [ %ib1, %blb ]\n") emith(" %pb = getelementptr inbounds i8, ptr %b, i32 %ib\n %ccb = load i8, ptr %pb\n") emith(" %zb = icmp eq i8 %ccb, 0\n br i1 %zb, label %alloc, label %blb\n") emith("blb:\n %ib1 = add i32 %ib, 1\n br label %bl\n") emith("alloc:\n %sum = add i32 %ia, %ib\n %sz = add i32 %sum, 1\n") emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @lp_malloc(i64 %sz64)\n br label %c1\n") emith("c1:\n %i = phi i32 [ 0, %alloc ], [ %i1, %c1b ]\n") emith(" %d1 = icmp slt i32 %i, %ia\n br i1 %d1, label %c1b, label %c2i\n") emith("c1b:\n %s1 = getelementptr inbounds i8, ptr %a, i32 %i\n %v1 = load i8, ptr %s1\n") emith(" %o1 = getelementptr inbounds i8, ptr %out, i32 %i\n store i8 %v1, ptr %o1\n") emith(" %i1 = add i32 %i, 1\n br label %c1\n") emith("c2i:\n br label %c2\n") emith("c2:\n %j = phi i32 [ 0, %c2i ], [ %j1, %c2b ]\n") emith(" %d2 = icmp slt i32 %j, %ib\n br i1 %d2, label %c2b, label %fin\n") emith("c2b:\n %s2 = getelementptr inbounds i8, ptr %b, i32 %j\n %v2 = load i8, ptr %s2\n") emith(" %oj = add i32 %ia, %j\n %o2 = getelementptr inbounds i8, ptr %out, i32 %oj\n store i8 %v2, ptr %o2\n") emith(" %j1 = add i32 %j, 1\n br label %c2\n") emith("fin:\n %pe = getelementptr inbounds i8, ptr %out, i32 %sum\n store i8 0, ptr %pe\n ret ptr %out\n}\n") } # int -> decimal string, emitted (once) into any program that uses string(int) # (string interpolation of a number). Writes digits from the end of a 24-byte # buffer, prepends '-' for negatives, and moves them to the buffer's start. function emit_int_str() -> void { emith("define ptr @lp_int_str(i32 %n0) {\n") emith("entry:\n %buf = call ptr @lp_malloc(i64 24)\n") emith(" %isneg = icmp slt i32 %n0, 0\n %neg = sub i32 0, %n0\n") emith(" %n = select i1 %isneg, i32 %neg, i32 %n0\n") emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n") emith(" %iszero = icmp eq i32 %n0, 0\n br i1 %iszero, label %zc, label %dl\n") emith("zc:\n store i8 48, ptr %buf\n %z1 = getelementptr inbounds i8, ptr %buf, i32 1\n store i8 0, ptr %z1\n ret ptr %buf\n") emith("dl:\n br label %dloop\n") emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i32 [ %n, %dl ], [ %cur2, %dbody ]\n") emith(" %done = icmp eq i32 %cur, 0\n br i1 %done, label %sign, label %dbody\n") emith("dbody:\n %d = urem i32 %cur, 10\n %ch = add i32 %d, 48\n %ch8 = trunc i32 %ch to i8\n") emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n") emith(" %cur2 = udiv i32 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n") emith("sign:\n br i1 %isneg, label %addneg, label %fin\n") emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n") emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n") emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n") # the digits to the buffer's start, so what is returned is what was malloc'd and can be freed emith(" %cnt = sub i32 24, %rpos\n %cnt64 = sext i32 %cnt to i64\n call ptr @memmove(ptr %buf, ptr %res, i64 %cnt64)\n ret ptr %buf\n}\n") } # the i64 twin of fn_int_str: a signed 64-bit integer -> decimal text. Emitted # once per program that stringifies a `long` (g_uses_longstr). A 64-bit value is # at most 20 digits plus sign and NUL, so the 24-byte scratch buffer still fits. function emit_long_str() -> void { emith("define ptr @lp_long_str(i64 %n0) {\n") emith("entry:\n %buf = call ptr @lp_malloc(i64 24)\n") emith(" %isneg = icmp slt i64 %n0, 0\n %neg = sub i64 0, %n0\n") emith(" %n = select i1 %isneg, i64 %neg, i64 %n0\n") emith(" %term = getelementptr inbounds i8, ptr %buf, i32 23\n store i8 0, ptr %term\n") emith(" %iszero = icmp eq i64 %n0, 0\n br i1 %iszero, label %zc, label %dl\n") emith("zc:\n store i8 48, ptr %buf\n %z1 = getelementptr inbounds i8, ptr %buf, i32 1\n store i8 0, ptr %z1\n ret ptr %buf\n") emith("dl:\n br label %dloop\n") emith("dloop:\n %pos = phi i32 [ 22, %dl ], [ %pos2, %dbody ]\n %cur = phi i64 [ %n, %dl ], [ %cur2, %dbody ]\n") emith(" %done = icmp eq i64 %cur, 0\n br i1 %done, label %sign, label %dbody\n") emith("dbody:\n %d = urem i64 %cur, 10\n %d32 = trunc i64 %d to i32\n %ch = add i32 %d32, 48\n %ch8 = trunc i32 %ch to i8\n") emith(" %pp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 %ch8, ptr %pp\n") emith(" %cur2 = udiv i64 %cur, 10\n %pos2 = sub i32 %pos, 1\n br label %dloop\n") emith("sign:\n br i1 %isneg, label %addneg, label %fin\n") emith("addneg:\n %sp = getelementptr inbounds i8, ptr %buf, i32 %pos\n store i8 45, ptr %sp\n %posn = sub i32 %pos, 1\n br label %fin\n") emith("fin:\n %fpos = phi i32 [ %pos, %sign ], [ %posn, %addneg ]\n") emith(" %rpos = add i32 %fpos, 1\n %res = getelementptr inbounds i8, ptr %buf, i32 %rpos\n") # the digits to the buffer's start, so what is returned is what was malloc'd and can be freed emith(" %cnt = sub i32 24, %rpos\n %cnt64 = sext i32 %cnt to i64\n call ptr @memmove(ptr %buf, ptr %res, i64 %cnt64)\n ret ptr %buf\n}\n") } # s[a..b] -> a fresh NUL-terminated copy of the bytes [a, b), emitted (once) into # any program that slices a string. Mallocs (b-a)+1, copies, terminates. function emit_str_slice() -> void { emith("define ptr @lp_str_slice(ptr %s, i32 %start, i32 %end) {\n") emith("entry:\n %len = sub i32 %end, %start\n %sz = add i32 %len, 1\n") emith(" %sz64 = sext i32 %sz to i64\n %out = call ptr @lp_malloc(i64 %sz64)\n br label %loop\n") emith("loop:\n %i = phi i32 [ 0, %entry ], [ %i1, %body ]\n") emith(" %d = icmp slt i32 %i, %len\n br i1 %d, label %body, label %fin\n") emith("body:\n %si = add i32 %start, %i\n %sp = getelementptr inbounds i8, ptr %s, i32 %si\n %c = load i8, ptr %sp\n") emith(" %op = getelementptr inbounds i8, ptr %out, i32 %i\n store i8 %c, ptr %op\n %i1 = add i32 %i, 1\n br label %loop\n") emith("fin:\n %tp = getelementptr inbounds i8, ptr %out, i32 %len\n store i8 0, ptr %tp\n ret ptr %out\n}\n") } # ---- the order globals are initialized in ---------------------------------------------------- # each global's initializer, followed through the functions it calls and a record's field defaults, # names the globals it reads; they are initialized first (a depth-first post-order, the source order # kept between globals that need nothing of each other, and where a cycle leaves no order) var g_gi_idx_k: []pointer = new []pointer var g_gi_idx_v: []Node = new []Node var g_gi_fn_seen: []pointer = new []pointer var g_gi_state: []int = new []int # 0 not visited, 1 on the path, 2 placed var g_gi_out: []int = new []int function gi_index(name: pointer) -> int { let h = ck_tab_get(g_gi_idx_k, g_gi_idx_v, name) if h == null { return -1 } return h.ival } function gi_fn_seen(name: pointer) -> bool { var i = 0 while i < len(g_gi_fn_seen) { if (g_gi_fn_seen[i] == name) { return true }; i += 1 } return false } # the globals an expression reads, into out, through calls and defaults (each function once per global) function gi_reads(n: Node, out: []int) -> void { if n == null { return } if (n.kind == E_ID or n.kind == E_FNREF) and n.s != null { let k = gi_index(n.s) if k >= 0 { let d = prog[k] if d.kind == N_VAR { push(out, k) } if d.kind == N_FN and not gi_fn_seen(n.s) { push(g_gi_fn_seen, n.s) gi_reads(d.a, out) } } } if n.kind == E_CALL and n.a != null and n.a.kind == E_ID and n.a.s != null { let k = gi_index(n.a.s) if k >= 0 and prog[k].kind == N_FN and not gi_fn_seen(n.a.s) { push(g_gi_fn_seen, n.a.s) gi_reads(prog[k].a, out) } } if n.kind == E_NEW and n.s != null { let lay = find_comp(n.s) if lay != null and lay.kids != null { var f = 0 while f < len(lay.kids) { gi_reads(lay.kids[f].a, out); f += 1 } } } gi_reads(n.a, out) gi_reads(n.b, out) gi_reads(n.c, out) if n.kids != null { var i = 0 while i < len(n.kids) { gi_reads(n.kids[i], out); i += 1 } } } function gi_visit(i: int) -> void { if g_gi_state[i] != 0 { return } g_gi_state[i] = 1 let d = prog[i] if d.a != null { let needs = new []int g_gi_fn_seen = new []pointer gi_reads(d.a, needs) var k = 0 while k < len(needs) { if needs[k] != i and g_gi_state[needs[k]] == 0 { gi_visit(needs[k]) } k += 1 } } g_gi_state[i] = 2 push(g_gi_out, i) } function gi_order() -> []int { if len(g_gi_out) > 0 { return g_gi_out } ck_tab_init(g_gi_idx_k, g_gi_idx_v) g_gi_state = new []int var i = 0 while i < len(prog) { let d = prog[i] push(g_gi_state, 0) if (d.kind == N_VAR or d.kind == N_FN) and d.s != null and ck_tab_get(g_gi_idx_k, g_gi_idx_v, d.s) == null { let h = new Node h.ival = i ck_tab_put(g_gi_idx_k, g_gi_idx_v, d.s, h) } i += 1 } i = 0 while i < len(prog) { if prog[i].kind == N_VAR { gi_visit(i) } i += 1 } return g_gi_out }