# emit_core.ludic — emitter state, type mapping, struct/slice helpers, and the # module header. Mirrors the pieces of compiler/back/ that this subset needs. # structs and slices are references, so every non-scalar type lowers to `ptr`. struct Val { code: ptr = ptr_null(), ty: ptr = ptr_null() } fn val(code: ptr, ty: ptr) -> Val { let v = new Val; v.code = code; v.ty = ty; return v } var head: Buf # module-level: types, globals, string constants var code: Buf # function bodies var falloc: Buf # entry-block allocas for the current function var ll_t: int = 0 # temp register counter (reset per function) var ll_lbl: int = 0 # label counter var ll_str: int = 0 # string-constant counter # local environment (parallel slices), reset per function var loc_name: []ptr var loc_reg: []ptr var loc_ty: []ptr var nloc: int = 0 # loop targets for break/continue (innermost last) var brk_lbl: []ptr var cnt_lbl: []ptr var nloop: int = 0 var ret_ty: ptr # current function's return type var g_term: bool = false # did the current block end in a terminator? var self_stk: []ptr # entity-index slot (ip) per enclosing query, for self() var nself: int = 0 var mach_stk: []Node # enclosing `machine` nodes, so `become` finds its register var nmach: int = 0 fn emit(s: ptr) -> void { buf_puts(code, s) } fn emith(s: ptr) -> void { buf_puts(head, s) } # stack slots MUST live in the entry block (an alloca in a loop walks the stack # off its end), so they go into a per-function buffer spliced in at entry. fn emit_alloca(llt: ptr) -> ptr { let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1 buf_puts(falloc, " "); buf_puts(falloc, r); buf_puts(falloc, " = alloca "); buf_puts(falloc, llt); buf_puts(falloc, "\n") return r } # "%t" fresh register fn sconcat(a: ptr, b: ptr) -> ptr { let la = slen(a); let lb = slen(b) let out = mem_alloc(la + lb + 1) let i = 0 while i < la { poke8(out, i, peek8(a, i)); i = i + 1 } let j = 0 while j < lb { poke8(out, la + j, peek8(b, j)); j = j + 1 } poke8(out, la + lb, 0) return out } fn nreg() -> ptr { let r = sconcat("%t", itoa(ll_t)); ll_t = ll_t + 1; return r } fn lbl(pfx: ptr) -> ptr { let r = sconcat(pfx, itoa(ll_lbl)); ll_lbl = ll_lbl + 1; return r } # Ludic type -> LLVM type. int/bool are i32; everything else (ptr/str/struct/ # slice) is a pointer; void is void. fn llty(t: ptr) -> ptr { if streq(t, "int") or streq(t, "bool") or streq(t, "fixed") { return "i32" } if streq(t, "void") { return "void" } return "ptr" } fn is_slice_ty(t: ptr) -> bool { return peek8(t, 0) == 91 and peek8(t, 1) == 93 } # "[]" fn slice_elem(t: ptr) -> ptr { return substr(t, 2, slen(t) - 2) } fn find_struct(name: ptr) -> Node { let i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_STRUCT and streq(d.s, name) { return d } i = i + 1 } return ptr_null() } fn is_struct_ty(t: ptr) -> bool { return not ptr_is_null(find_struct(t)) } fn find_arch(name: ptr) -> Node { let i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_ARCH and streq(d.s, name) { return d }; i = i + 1 } return ptr_null() } fn find_comp(name: ptr) -> Node { let i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_COMP and streq(d.s, name) { return d }; i = i + 1 } return ptr_null() } # a struct or a component — both have %Str_/%Cmp_ layouts with named fields fn layout_node(name: ptr) -> Node { let s = find_struct(name); if not ptr_is_null(s) { return s } return find_comp(name) } fn layout_ty(name: ptr) -> ptr { if not ptr_is_null(find_struct(name)) { return sconcat("%Str_", name) } return sconcat("%Cmp_", name) } fn field_index(s: Node, fname: ptr) -> int { let i = 0 while i < len(s.kids) { if streq(s.kids[i].s, fname) { return i }; i = i + 1 } return 0 - 1 } fn field_type(s: Node, fname: ptr) -> ptr { let i = 0 while i < len(s.kids) { if streq(s.kids[i].s, fname) { return s.kids[i].ty }; i = i + 1 } return "int" } # find a global var/const by name fn find_global(name: ptr) -> Node { let i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_VAR and streq(d.s, name) { return d } if d.kind == N_CONST and streq(d.s, name) { return d } i = i + 1 } return ptr_null() } # `Enum.Variant` -> the variant's ordinal (its index), or -1 if `ename` names no # enum with that variant. Enum names live in `prog` like any other declaration. fn enum_ordinal(ename: ptr, vname: ptr) -> int { let i = 0 while i < len(prog) { let d = prog[i] if d.kind == N_ENUM and streq(d.s, ename) { let j = 0 while j < len(d.kids) { if streq(d.kids[j].s, vname) { return j }; j = j + 1 } } i = i + 1 } return 0 - 1 } fn find_fn(name: ptr) -> Node { let i = 0 while i < len(prog) { let d = prog[i]; if d.kind == N_FN and streq(d.s, name) { return d }; i = i + 1 } return ptr_null() } # local variable environment fn loc_reset() -> void { nloc = 0 } fn loc_push(name: ptr, r: ptr, ty: ptr) -> void { if nloc < len(loc_name) { loc_name[nloc] = name; loc_reg[nloc] = r; loc_ty[nloc] = ty } else { push(loc_name, name); push(loc_reg, r); push(loc_ty, ty) } nloc = nloc + 1 } fn loc_find(name: ptr) -> int { let i = nloc - 1 while i >= 0 { if streq(loc_name[i], name) { return i }; i = i - 1 } return 0 - 1 }