# emit_expr.ludic — lower an expression to IR, returning its register and type. function emit_load_at(addr: pointer, ty: pointer) -> Val { let r = emit_bind(`load {llty(ty)}, ptr {addr}`) return val(r, ty) } # short-circuit `and`/`or`: seed a slot with (left!=0), branch to decide whether # to overwrite with (right!=0). function emit_logic(e: Node) -> Val { let slot = emit_alloca("i32") let la = emit_expr(e.a) let lc = emit_bind(`icmp ne i32 {la.code}, 0`) let lz = emit_bind(`zext i1 {lc} to i32`) emit(" store i32 "); emit(lz); emit(", ptr "); emit(slot); emit("\n") let ev = lbl("sc"); let done = lbl("scend") if (e.s == "and") { emit(" br i1 "); emit(lc); emit(", label %"); emit(ev); emit(", label %"); emit(done); emit("\n") } else { emit(" br i1 "); emit(lc); emit(", label %"); emit(done); emit(", label %"); emit(ev); emit("\n") } emit(ev); emit(":\n") let rb = emit_expr(e.b) let rc = emit_bind(`icmp ne i32 {rb.code}, 0`) let rz = emit_bind(`zext i1 {rc} to i32`) emit(" store i32 "); emit(rz); emit(", ptr "); emit(slot); emit("\n") emit(" br label %"); emit(done); emit("\n") emit(done); emit(":\n") return val(emit_bind(`load i32, ptr {slot}`), "bool") } function cmp_code(op: pointer) -> pointer { if (op == ("<")) { return "slt" } if (op == ("<=")) { return "sle" } if (op == (">")) { return "sgt" } if (op == (">=")) { return "sge" } if (op == ("==")) { return "eq" } return "ne" } function is_cmp(op: pointer) -> bool { return (op == ("<")) or (op == ("<=")) or (op == (">")) or (op == (">=")) or (op == ("==")) or (op == ("!=")) } function arith_code(op: pointer) -> pointer { if (op == ("+")) { return "add" } if (op == ("-")) { return "sub" } if (op == ("*")) { return "mul" } if (op == ("/")) { return "sdiv" } if (op == ("&")) { return "and" } if (op == ("|")) { return "or" } if (op == ("^")) { return "xor" } if (op == ("<<")) { return "shl" } if (op == (">>")) { return "lshr" } # logical shift (unsigned) return "srem" } # widen an int value to Q16.16 by shifting left 16 (a fixed value passes through) function to_fixed(v: Val) -> pointer { if (v.ty == "fixed") { return v.code } return emit_bind(`shl i32 {v.code}, 16`) } # coerce a value's code to the LLVM type of `target`, for the only cross-width # pair the language has: int (i32) <-> long (i64). int widens with sext, long # narrows with trunc; everything else (same width, or ptr) passes through. function coerce_code(v: Val, target: pointer) -> pointer { let lt = llty(target) let vt = llty(v.ty) if (lt == vt) { return v.code } if (lt == "i64") and (vt == "i32") { return emit_bind(`sext i32 {v.code} to i64`) } if (lt == "i32") and (vt == "i64") { return emit_bind(`trunc i64 {v.code} to i32`) } return v.code } # widen an int value to i64 (a long passes through) — the long analogue of to_fixed function to_long(v: Val) -> pointer { if (llty(v.ty) == "i64") { return v.code } return emit_bind(`sext i32 {v.code} to i64`) } # string operators: `a + b` concatenates, `a == b` / `a != b` compare by content. # Both call the @fn_str_* prelude (emitted once per program that uses them). function emit_str_op(op: pointer, a: Val, b: Val) -> Val { g_uses_str = true if (op == ("+")) { return val(emit_bind(`call ptr @fn_str_concat(ptr {a.code}, ptr {b.code})`), "string") } let r = emit_bind(`call i32 @fn_str_eq(ptr {a.code}, ptr {b.code})`) if (op == ("!=")) { let c = emit_bind(`icmp eq i32 {r}, 0`) return val(emit_bind(`zext i1 {c} to i32`), "bool") } return val(r, "bool") } function emit_bin(e: Node) -> Val { if (e.s == "and") or (e.s == "or") { return emit_logic(e) } let a = emit_expr(e.a) let b = emit_expr(e.b) # strings are pointer-typed, so any `+` with a pointer operand is concatenation, # and `==`/`!=` between pointers is content comparison — except `x == null`, # which is a pointer-identity test and falls through to the icmp below. let ptrish = (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") let isnull = e.a.kind == E_NULL or e.b.kind == E_NULL if ptrish { if (e.s == ("+")) { return emit_str_op("+", a, b) } if ((e.s == ("==")) or (e.s == ("!="))) and not isnull { return emit_str_op(e.s, a, b) } } let fx = (a.ty == "fixed") or (b.ty == "fixed") # a 64-bit operand (and no fixed/ptr involved) promotes the whole expression to # i64: the other side widens with sext, and the result stays `long`. let lng = ((llty(a.ty) == "i64") or (llty(b.ty) == "i64")) and not fx and not ptrish if is_cmp(e.s) { var ac = a.code; var bc = b.code var ct = "i32" if fx { ac = to_fixed(a); bc = to_fixed(b) } else { if lng { ct = "i64"; ac = to_long(a); bc = to_long(b) } else { if (llty(a.ty) == "ptr") or (llty(b.ty) == "ptr") { ct = "ptr" } } } # `p == null`, str/record identity let c = emit_bind(`icmp {cmp_code(e.s)} {ct} {ac}, {bc}`) return val(emit_bind(`zext i1 {c} to i32`), "bool") } if lng { let al = to_long(a); let bl = to_long(b) return val(emit_bind(`{arith_code(e.s)} i64 {al}, {bl}`), "long") } if fx { let af = to_fixed(a); let bf = to_fixed(b) if (e.s == ("*")) { let a64 = emit_bind(`sext i32 {af} to i64`) let b64 = emit_bind(`sext i32 {bf} to i64`) let m = emit_bind(`mul i64 {a64}, {b64}`) let sh = emit_bind(`ashr i64 {m}, 16`) return val(emit_bind(`trunc i64 {sh} to i32`), "fixed") } if (e.s == ("/")) { let a64 = emit_bind(`sext i32 {af} to i64`) let ash = emit_bind(`shl i64 {a64}, 16`) let b64 = emit_bind(`sext i32 {bf} to i64`) let dv = emit_bind(`sdiv i64 {ash}, {b64}`) return val(emit_bind(`trunc i64 {dv} to i32`), "fixed") } let r = emit_bind(`{arith_code(e.s)} i32 {af}, {bf}`) return val(r, "fixed") } let r = emit_bind(`{arith_code(e.s)} i32 {a.code}, {b.code}`) return val(r, "int") } # ---- named arguments ------------------------------------------------------- # An argument list is either all-positional or all-named. When named, each kid # is an E_FINIT (label -> value); this rewrites e.kids into plain value exprs in # the order the callee declares its parameters, so the rest of emit_call is # oblivious to whether the caller used names. function args_are_named(e: Node) -> bool { var i = 0 while i < len(e.kids) { if e.kids[i].kind == E_FINIT { return true }; i = i + 1 } return false } function reorder_named(e: Node, labels: []pointer) -> void { if not args_are_named(e) { return } var i = 0 while i < len(e.kids) { if e.kids[i].kind != E_FINIT { perr("named and positional arguments cannot be mixed in one call") } i = i + 1 } if len(e.kids) != len(labels) { perr("wrong number of arguments") } let out = new []Node var li = 0 while li < len(labels) { var found: Node = null var k = 0 while k < len(e.kids) { if (e.kids[k].s == labels[li]) { found = e.kids[k] }; k = k + 1 } if (found == null) { perr(`no argument named {labels[li]}`) } push(out, found.a) li = li + 1 } e.kids = out } # The parameter labels of a resolved fn/extern, in declaration order. function param_labels(fn: Node) -> []pointer { let out = new []pointer var i = 0 while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].s) }; i = i + 1 } return out } function param_types(fn: Node) -> []pointer { let out = new []pointer var i = 0 while i < len(fn.kids) { if fn.kids[i].kind == N_PARAM { push(out, fn.kids[i].ty) }; i = i + 1 } return out } # ---- namespaced builtins: Screen.* / Random.* / Input.* -------------------- # The game-facing API reads as `subject.action(...)`. Each method maps to a bare # runtime builtin plus the parameter labels callers may use as named arguments; # after reordering we rewrite the callee to that bare name and fall back into the # ordinary builtin path (which resolves it to its rt_ function). function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val { # Math.* is computed inline (deterministic fixed-point), not routed through a # bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace. if (ns == "Math") { if is_math_ns(meth) { return emit_math_ns(meth, e) } perr(`unknown builtin Math.{meth}`) } if (ns == "Text") { if is_text_ns(meth) { return emit_text_ns(meth, e) } perr(`unknown builtin Text.{meth}`) } if (ns == "List") { if is_list_ns(meth) { return emit_list_ns(meth, e) } perr(`unknown builtin List.{meth}`) } if (ns == "Ease") { if is_ease_ns(meth) { return emit_ease_ns(meth, e) } perr(`unknown builtin Ease.{meth}`) } if (ns == "Collision") { if is_collide_ns(meth) { return emit_collide_ns(meth, e) } perr(`unknown builtin Collision.{meth}`) } if (ns == "Memory") { if is_mem_ns(meth) { return emit_mem_ns(meth, e) } perr(`unknown builtin Memory.{meth}`) } if (ns == "Color") { if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) } perr(`unknown builtin Color.{meth}`) } if (ns == "Time") { if is_time_ns(meth) { return emit_time_ns(meth, e) } perr(`unknown builtin Time.{meth}`) } if (ns == "Hash") { if is_hash_ns(meth) { return emit_hash_ns(meth, e) } perr(`unknown builtin Hash.{meth}`) } if (ns == "Crypto") { if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) } perr(`unknown builtin Crypto.{meth}`) } if (ns == "Vector") { if is_vector_ns(meth) { return emit_vector_ns(meth, e) } perr(`unknown builtin Vector.{meth}`) } if (ns == "Duration") { if is_duration_ns(meth) { return emit_duration_ns(meth, e) } perr(`unknown builtin Duration.{meth}`) } if (ns == "Date") { if is_date_ns(meth) { return emit_date_ns(meth, e) } perr(`unknown builtin Date.{meth}`) } if (ns == "DateTime") { if is_datetime_ns(meth) { return emit_datetime_ns(meth, e) } perr(`unknown builtin DateTime.{meth}`) } if (ns == "Clock") { if is_clock_ns(meth) { return emit_clock_ns(meth, e) } perr(`unknown builtin Clock.{meth}`) } var bare: pointer = null let labels = new []pointer if (ns == "Screen") { if (meth == "clear") { bare = "clear"; push(labels, "color") } if (meth == "fill_rectangle") { bare = "fill_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "color") } if (meth == "draw_rectangle") { bare = "frame_rect"; push(labels, "x"); push(labels, "y"); push(labels, "width"); push(labels, "height"); push(labels, "color") } if (meth == "put_pixel") { bare = "put_px"; push(labels, "x"); push(labels, "y"); push(labels, "color") } if (meth == "draw_text") { bare = "text"; push(labels, "x"); push(labels, "y"); push(labels, "text"); push(labels, "color"); push(labels, "scale") } if (meth == "draw_number") { bare = "text_int"; push(labels, "x"); push(labels, "y"); push(labels, "value"); push(labels, "color"); push(labels, "scale") } if (meth == "show") { bare = "present" } if (meth == "width") { bare = "screen_w" } if (meth == "height") { bare = "screen_h" } if (meth == "status") { bare = "status"; push(labels, "text") } if (meth == "line") { bare = "line"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "color") } if (meth == "circle") { bare = "circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") } if (meth == "fill_circle") { bare = "fill_circle"; push(labels, "x"); push(labels, "y"); push(labels, "radius"); push(labels, "color") } if (meth == "triangle") { bare = "triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") } if (meth == "fill_triangle") { bare = "fill_triangle"; push(labels, "x1"); push(labels, "y1"); push(labels, "x2"); push(labels, "y2"); push(labels, "x3"); push(labels, "y3"); push(labels, "color") } if (meth == "sprite") { bare = "draw_sprite"; push(labels, "id"); push(labels, "x"); push(labels, "y") } if (meth == "sprite_scaled") { bare = "draw_sprite_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") } } if (ns == "Map") { if (meth == "size") { bare = "map_size"; push(labels, "width"); push(labels, "height") } if (meth == "row") { bare = "map_row"; push(labels, "y"); push(labels, "cells") } if (meth == "tile") { bare = "tile"; push(labels, "x"); push(labels, "y") } } if (ns == "Random") { if (meth == "range") { bare = "rng_range"; push(labels, "low"); push(labels, "high") } if (meth == "chance") { bare = "rng_chance"; push(labels, "percent") } if (meth == "seed") { bare = "seed"; push(labels, "value") } if (meth == "value") { bare = "rng_value" } if (meth == "int") { bare = "rng_int"; push(labels, "max") } if (meth == "sign") { bare = "rng_sign" } } if (ns == "Input") { if (meth == "key") { bare = "key" } } # Phase 3: the bare reflection / networking / process builtins, namespaced. # Each is a pure alias — the callee is rewritten to the bare name below. if (ns == "World") { if (meth == "get") { bare = "world_get" } if (meth == "set") { bare = "world_set" } if (meth == "has") { bare = "world_has" } if (meth == "count") { bare = "world_count" } if (meth == "size") { bare = "world_size" } if (meth == "spawn") { bare = "world_spawn" } if (meth == "save") { bare = "world_save" } if (meth == "load") { bare = "world_load" } if (meth == "prop_id") { bare = "world_prop_id" } if (meth == "field_id") { bare = "world_field_id" } if (meth == "model_id") { bare = "world_model_id" } if (meth == "kind") { bare = "world_kind" } if (meth == "register_prop") { bare = "world_register_prop" } if (meth == "attach") { bare = "world_attach_dyn" } if (meth == "detach") { bare = "world_detach_dyn" } if (meth == "query_next") { bare = "world_query_next" } } if (ns == "Network") { if (meth == "send") { bare = "net_send" } if (meth == "poll") { bare = "net_poll" } if (meth == "serialize") { bare = "serialize" } if (meth == "apply") { bare = "apply" } if (meth == "owner") { bare = "owner" } if (meth == "set_owner") { bare = "set_owner" } if (meth == "is_server") { bare = "is_server" } if (meth == "is_owner") { bare = "is_owner" } if (meth == "local_id") { bare = "local_id" } } if (ns == "System") { if (meth == "arg") { bare = "arg" } if (meth == "arg_count") { bare = "arg_count" } if (meth == "exit") { bare = "exit" } if (meth == "run") { bare = "run" } if (meth == "env") { bare = "getenv" } if (meth == "read_char") { bare = "read_char" } if (meth == "file_open") { bare = "file_open" } if (meth == "file_read") { bare = "file_read" } if (meth == "file_write") { bare = "file_write" } if (meth == "file_seek") { bare = "file_seek" } if (meth == "file_tell") { bare = "file_tell" } if (meth == "file_close") { bare = "file_close" } if (meth == "stdout") { bare = "file_stdout" } if (meth == "stderr") { bare = "file_stderr" } } if (ns == "Save") { if (meth == "write") { bare = "save" } if (meth == "read") { bare = "load" } } if (bare == null) { perr(`unknown builtin {ns}.{meth}`) } reorder_named(e, labels) let id = node(E_ID); id.s = bare; e.a = id return emit_call(e) } function emit_call(e: Node) -> Val { # `Subject.action(...)` — a namespaced builtin (Screen/Random/Input). if e.a.kind == E_MEMBER { if e.a.a.kind == E_ID { return emit_ns_call(e.a.a.s, e.a.s, e) } perr("call target is not a function") } let name = e.a.s if (name == "self") { if nself == 0 { return val("0", "entity") }; return val(emit_bind(`load i32, ptr {self_stk[nself - 1]}`), "entity") } if (name == "key") { return val(emit_bind("load i32, ptr @L_key"), "int") } if (name == "save") { emit(" call void @L_save()\n"); return val("0", "void") } if (name == "ui_build") { emit(" call void @ui_build()\n"); return val("0", "void") } if (name == "load") { return val(emit_bind("call i32 @L_load()"), "bool") } if (name == "world_size") { return val(emit_bind("call i32 @L_world_size()"), "int") } if (name == "world_save") { # world_save(buf) -> bytes written let b = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_world_save(ptr {b.code})`), "int") } if (name == "world_load") { # world_load(buf, len) let b = emit_expr(e.kids[0]) let l = emit_expr(e.kids[1]) emit(" call void @L_world_load(ptr "); emit(b.code); emit(", i32 "); emit(l.code); emit(")\n") return val("0", "void") } if (name == "quit") { emit(" store i32 0, ptr @L_running\n"); return val("0", "void") } # NETWORKING (NETWORKING-DESIGN §5) — the low-level freedom layer, callable from # Ludic. serialize/apply/sync_size lower to the @Sync by-kind dispatchers (N2); # owner/set_owner/is_owner to the @Owned storage (N3); is_server/local_id read # the runtime-set role registers (N5). Offline these hold their single-player # default (@L_role=1 → is_server()==true), so guards collapse to "run here" (§8). if (name == "serialize") { # serialize(e, buf) -> bytes written let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) return val(emit_bind(`call i32 @ludic_serialize(i32 {a.code}, ptr {b.code})`), "int") } if (name == "apply") { # apply(e, buf, len) let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) emit(" call void @ludic_apply(i32 "); emit(a.code); emit(", ptr "); emit(b.code); emit(", i32 "); emit(c.code); emit(")\n") return val("0", "void") } if (name == "sync_size") { # sync_size(e) -> replicated byte count for e's model let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @ludic_sync_size(i32 {a.code})`), "int") } if (name == "owner") { # owner(e) -> peer id (-1 = unowned) let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_owner(i32 {a.code})`), "int") } if (name == "set_owner") { # set_owner(e, id) let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) emit(" call void @L_set_owner(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n") return val("0", "void") } if (name == "is_owner") { # is_owner(e) -> owner(e) == local_id() let a = emit_expr(e.kids[0]) return val(emit_bind(`call i32 @L_is_owner(i32 {a.code})`), "bool") } if (name == "is_server") { # is_server() -> the local peer is the authority let r = emit_bind("load i32, ptr @L_role") let c = emit_bind(`icmp eq i32 {r}, 1`) return val(emit_bind(`zext i1 {c} to i32`), "bool") } if (name == "local_id") { return val(emit_bind("load i32, ptr @L_localid"), "int") } if (name == "net_pump") { emit(" call void @L_net_pump()\n"); return val("0", "void") } # N4: drain + re-emit inbound RPCs if (name == "tick_fixed") { emit(" call void @L_tick_fixed()\n"); return val("0", "void") } # N5: run the sim phases if (name == "tick_render") { emit(" call void @L_tick_render()\n"); return val("0", "void") } # N5: run the Render phase if (name == "set_role") { # N5: the runtime sets the peer's role (1=server, 0=client) let a = emit_expr(e.kids[0]); emit(" store i32 "); emit(a.code); emit(", ptr @L_role\n"); return val("0", "void") } if (name == "set_local_id") { # N5: the runtime sets this peer's id let a = emit_expr(e.kids[0]); emit(" store i32 "); emit(a.code); emit(", ptr @L_localid\n"); return val("0", "void") } # net_send(peer, buf, len) / net_poll(buf, cap): the transport seam. An # `extern fn` of the same name (a real socket) wins; absent one, these lower to # the compiler's built-in loopback so a game is networked with zero foreign code. if (name == "net_send") and (find_extern("net_send") == null) { g_uses_loopback = true let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) emit(" call void @L_net_send(i32 "); emit(a.code); emit(", ptr "); emit(b.code); emit(", i32 "); emit(c.code); emit(")\n") return val("0", "void") } if (name == "net_poll") and (find_extern("net_poll") == null) { g_uses_loopback = true let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]) return val(emit_bind(`call i32 @L_net_poll(ptr {a.code}, i32 {b.code})`), "int") } if (name == "len") { return emit_len(e) } if (name == "push") { return emit_push(e) } if (name == "string") { # string(x): int/bool/fixed/long -> text, a string passes through let a = emit_expr(e.kids[0]) if (llty(a.ty) == "ptr") { return a } if (llty(a.ty) == "i64") { g_uses_longstr = true; return val(emit_bind(`call ptr @fn_long_str(i64 {a.code})`), "string") } g_uses_intstr = true return val(emit_bind(`call ptr @fn_int_str(i32 {a.code})`), "string") } if (name == "print") { # print(x): a value + newline (string, long, or int) let a = emit_expr(e.kids[0]) if (llty(a.ty) == "ptr") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_line, ptr " + `{a.code})\n`) } else { if (llty(a.ty) == "i64") { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_long, i64 " + `{a.code})\n`) } else { emit(" call i32 (ptr, ...) @printf(ptr @.fmt_int, i32 " + `{a.code})\n`) } } return val("0", "void") } if (name == "bytes") { # bytes(n): allocate n bytes -> a byte buffer let n = emit_expr(e.kids[0]) let w = emit_bind(`zext i32 {n.code} to i64`) return val(emit_bind(`call ptr @malloc(i64 {w})`), "pointer") } if (name == "words") { # words(n): allocate n 32-bit words let n = emit_expr(e.kids[0]) let by = emit_bind(`mul i32 {n.code}, 4`) let w = emit_bind(`zext i32 {by} to i64`) return val(emit_bind(`call ptr @malloc(i64 {w})`), "words") } if (name == "fixed") { let a = emit_expr(e.kids[0]); return val(emit_bind(`shl i32 {a.code}, 16`), "fixed") } if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") } # The EV2 reflection ABI (the world table), exposed to Ludic so a Ludic mod can # introspect the world by name — the same functions a foreign mod binds. Emitted # only for a modding program (ECS + events), so a plain game is unchanged. if (name == "world_prop_id") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_prop_id(ptr {a.code})`), "int") } if (name == "world_field_id") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_field_id(i32 {a.code}, ptr {b.code})`), "int") } if (name == "world_get") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]) let r = emit_bind(`call i64 @ludic_get(i32 {a.code}, i32 {b.code}, i32 {c.code})`) return val(emit_bind(`trunc i64 {r} to i32`), "int") } if (name == "world_set") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); let c = emit_expr(e.kids[2]); let d = emit_expr(e.kids[3]) let v64 = emit_bind(`sext i32 {d.code} to i64`) emit(" call void @ludic_set(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(", i32 "); emit(c.code); emit(", i64 "); emit(v64); emit(")\n") return val("0", "void") } if (name == "world_has") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_has(i32 {a.code}, i32 {b.code})`), "int") } if (name == "world_count") { return val(emit_bind("call i32 @ludic_entity_count()"), "int") } if (name == "world_kind") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_kind(i32 {a.code})`), "int") } if (name == "world_model_id") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_model_id(ptr {a.code})`), "int") } if (name == "world_query_next") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_query_next(i32 {a.code}, i32 {b.code})`), "int") } if (name == "world_register_prop") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); return val(emit_bind(`call i32 @ludic_register_prop(ptr {a.code}, i32 {b.code})`), "int") } if (name == "world_attach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_attach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") } if (name == "world_detach_dyn") { let a = emit_expr(e.kids[0]); let b = emit_expr(e.kids[1]); emit(" call void @ludic_detach_dyn(i32 "); emit(a.code); emit(", i32 "); emit(b.code); emit(")\n"); return val("0", "void") } if (name == "world_spawn") { let a = emit_expr(e.kids[0]); return val(emit_bind(`call i32 @ludic_spawn(i32 {a.code})`), "int") } if is_intrinsic(name) { return emit_intrinsic(name, e) } if is_intrinsic2(name) { return emit_intrinsic2(name, e) } if is_math_builtin(name) { return emit_math_builtin(name, e) } # extern fn: a direct call to the declared link symbol (no @fn_ prefix) let ext = find_extern(name) if (ext != null) { reorder_named(e, param_labels(ext)) let eargs = new []pointer let eatys = new []pointer var ei = 0 while ei < len(e.kids) { let v = emit_expr(e.kids[ei]); push(eargs, v.code); push(eatys, v.ty); ei = ei + 1 } let erl = llty(ext.ty) emit(" ") var erreg = "0" if not (erl == "void") { erreg = nreg(); emit(erreg); emit(" = ") } emit("call "); emit(erl); emit(" @"); emit(ext.a.s); emit("(") ei = 0 while ei < len(eargs) { if ei > 0 { emit(", ") } emit(llty(eatys[ei])); emit(" "); emit(eargs[ei]) ei = ei + 1 } emit(")\n") return val(erreg, ext.ty) } var fn2 = find_fn(name) var cname = name if (fn2 == null) { # a builtin like clear()/reg() is satisfied by its rt_ function let rtname = `rt_{name}` fn2 = find_fn(rtname) if (fn2 == null) { perr(`unknown function {name}`) } cname = rtname } reorder_named(e, param_labels(fn2)) # evaluate args first (their IR is emitted before the call instruction), coercing # each to the parameter's declared type so an int passed for a `long` widens. let ptys = param_types(fn2) let args = new []pointer let atys = new []pointer var i = 0 while i < len(e.kids) { let v = emit_expr(e.kids[i]) var pty = v.ty if (i < len(ptys)) { pty = ptys[i] } push(args, coerce_code(v, pty)); push(atys, pty); i = i + 1 } let rl = llty(fn2.ty) emit(" ") var rreg = "0" if not (rl == "void") { rreg = nreg(); emit(rreg); emit(" = ") } emit("call "); emit(rl); emit(" @fn_"); emit(cname); emit("(") i = 0 while i < len(args) { if i > 0 { emit(", ") } emit(llty(atys[i])); emit(" "); emit(args[i]) i = i + 1 } emit(")\n") return val(rreg, fn2.ty) } function emit_expr(e: Node) -> Val { if (e == null) { return val("0", "int") } if e.kind == E_INT { return val(itoa(e.ival), "int") } if e.kind == E_FLOAT { return val(itoa(e.ival), "fixed") } if e.kind == E_BOOL { return val(itoa(e.ival), "bool") } if e.kind == E_NULL { return val("null", "pointer") } if e.kind == E_SLICE { # s[a..b] -> a fresh substring let base = emit_expr(e.a) let lo = emit_expr(e.b) let hi = emit_expr(e.c) g_uses_strslice = true return val(emit_bind(`call ptr @fn_str_slice(ptr {base.code}, i32 {lo.code}, i32 {hi.code})`), "string") } if e.kind == E_STR { return val(emit_str_const(e.s), "string") } if e.kind == E_NEW { if is_slice_ty(e.s) { return emit_new_slice(e.s) } return emit_new_struct(e.s) } if e.kind == E_ID { let li = loc_find(e.s) if li >= 0 { return emit_load_at(loc_reg[li], loc_ty[li]) } let g = find_global(e.s) if (g != null) { if g.kind == N_CONST { return val(itoa(g.a.ival), "int") } let r = emit_bind(`load {llty(g.ty)}, ptr @g_{e.s}`) return val(r, g.ty) } # a UI_ that is not a const/var resolves to its widget index if is_ui_ident(e.s) { return val(itoa(ui_index_of(e.s)), "int") } perr(`unknown identifier {e.s}`) } if e.kind == E_MEMBER { if e.a.kind == E_ID { if (e.a.s == "Color") { # `Color.Name` -> its 0xRRGGBB int, at compile time let cv = color_lookup(e.s) if (cv < 0) { perr(`unknown color Color.{e.s}`) } return val(itoa(cv), "int") } let ord = enum_ordinal(e.a.s, e.s) # `Enum.Variant` -> its ordinal, a compile-time int if ord >= 0 { return val(itoa(ord), "int") } } let bt = static_type(e.a) # `x.field` where field is @Computed -> inline it if (bt != null) { let cx = computed_expr(bt, e.s) if (cx != null) { return emit_expr(qualify_fields(cx, e.a)) } } let a = emit_member_addr(e); return emit_load_at(a, g_addr_ty) } if e.kind == E_INDEX { let a = emit_index_addr(e) if (g_addr_ty == "byte") { # a byte read, widened to int let b = emit_bind(`load i8, ptr {a}`) return val(emit_bind(`zext i8 {b} to i32`), "int") } return emit_load_at(a, g_addr_ty) } if e.kind == S_EMIT { return emit_emit(e) } # emit as an expression -> cancelled flag if e.kind == E_CALL { return emit_call(e) } if e.kind == E_BIN { return emit_bin(e) } if e.kind == E_UN { let a = emit_expr(e.a) if (llty(a.ty) == "i64") { # negate / bit-flip a long, staying 64-bit if (e.s == ("-")) { return val(emit_bind(`sub i64 0, {a.code}`), "long") } if (e.s == "~") { return val(emit_bind(`xor i64 {a.code}, -1`), "long") } } if (e.s == ("-")) { return val(emit_bind(`sub i32 0, {a.code}`), "int") } if (e.s == "~") { return val(emit_bind(`xor i32 {a.code}, -1`), "int") } let c = emit_bind(`icmp eq i32 {a.code}, 0`) return val(emit_bind(`zext i1 {c} to i32`), "bool") } perr("cannot emit expression") return val("0", "int") }