ludic/selfhost/emit_stmt.ludic
Orkuncakilkaya feb3a71e56 Phase 7i: byte indexing p[i] / p[i] = v (retire peek8/poke8)
Raw byte access is now indexing, not C-style peek/poke:

  peek8(src, i)          -> src[i]        (reads a byte, widened to int)
  poke8(out, j, r)       -> out[j] = r    (narrows the int to a byte)

E_INDEX on a non-slice pointer/string lowers to a `getelementptr i8` + load/zext
(read) or trunc/store (write) — byte-identical to the old peek8/poke8, so the
migration reproduces the compiler exactly. A "byte" element type (llty i8) drives
the widen/narrow. The compiler's own byte work now reads naturally, e.g.
`is_slice_ty` is `t[0] == 91 and t[1] == 93`.

Subtlety fixed on the way: g_addr_ty (the out-param carrying the indexed element
type) must be set AFTER evaluating the index expression, since a member/index in
the index would otherwise clobber it — doing it early made a byte read load a
full pointer from a byte address and crash the self-compile.

Two reseeds: add byte-index support keeping peek8/poke8, then migrate 148 call
sites and delete the intrinsics (+ the now-dead emit_gep_i8). Vocabulary drops
peek8/poke8. Reseeded (22604 lines); C-free fixpoint holds; goldens identical;
18/18; vocab clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-28 02:01:59 +03:00

201 lines
7.2 KiB
Text

# emit_stmt.ludic — lower statements. Terminators set g_term so the rest of a
# block is skipped until a new basic block opens.
fn emit_block(b: Node) -> void {
var i = 0
while i < len(b.kids) {
if g_term { return }
emit_stmt(b.kids[i])
i = i + 1
}
}
# store `val` (llvm type `lt`) into address `addr`
fn store_at(lt: ptr, v: ptr, addr: ptr) -> void {
emit(" store "); emit(lt); emit(" "); emit(v); emit(", ptr "); emit(addr); emit("\n")
}
fn emit_assign(st: Node) -> void {
# resolve the target's address and type
let t = st.a
var addr = "0"
var ty = "int"
if t.kind == E_ID {
let li = loc_find(t.s)
if li >= 0 {
# a `let` binding is immutable: rebinding it is an error (mutating THROUGH
# it — `x.field = …`, `x[i] = …` — is fine, and lands in the branches below).
if loc_mut[li] == 0 { perr(`cannot assign to immutable '{t.s}' (declared with let; use var) — line {itoa(st.line)}`) }
addr = loc_reg[li]; ty = loc_ty[li]
}
else {
let g = find_global(t.s)
if (g == null) { perr(`assign to unknown {t.s}`) }
addr = `@g_{t.s}`; ty = g.ty
}
} else {
if t.kind == E_MEMBER { addr = emit_member_addr(t); ty = g_addr_ty }
else { if t.kind == E_INDEX { addr = emit_index_addr(t); ty = g_addr_ty }
else { perr("bad assignment target") } }
}
let lt = llty(ty)
let rv = emit_expr(st.b)
var v = rv.code
if not (st.s == "=") {
let cur = emit_bind(`load {lt}, ptr {addr}`)
var opc = "add"
if (st.s == ("-=")) { opc = "sub" }
if (st.s == ("*=")) { opc = "mul" }
if (st.s == ("/=")) { opc = "sdiv" }
v = emit_bind(`{opc} i32 {cur}, {v}`)
}
if (lt == "i8") { v = emit_bind(`trunc i32 {v} to i8`) } # narrow to a byte for p[i] = v
store_at(lt, v, addr)
}
fn emit_if(st: Node) -> void {
let c = emit_expr(st.a)
let cc = emit_bind(`icmp ne i32 {c.code}, 0`)
let has_else = (st.c != null)
let tl = lbl("then"); let el = lbl("else"); let en = lbl("ifend")
if has_else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(el); emit("\n") }
else { emit(" br i1 "); emit(cc); emit(", label %"); emit(tl); emit(", label %"); emit(en); emit("\n") }
emit(tl); emit(":\n"); g_term = false
emit_block(st.b)
if not g_term { emit(" br label %"); emit(en); emit("\n") }
if has_else {
emit(el); emit(":\n"); g_term = false
# else is either a block or a nested if-statement
if st.c.kind == S_IF { emit_stmt(st.c) } else { emit_block(st.c) }
if not g_term { emit(" br label %"); emit(en); emit("\n") }
}
emit(en); emit(":\n"); g_term = false
}
fn emit_while(st: Node) -> void {
let cl = lbl("wcond"); let bl = lbl("wbody"); let en = lbl("wend")
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let c = emit_expr(st.a)
let cc = emit_bind(`icmp ne i32 {c.code}, 0`)
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n"); g_term = false
loop_push(cl, en)
emit_block(st.b)
loop_pop()
if not g_term { emit(" br label %"); emit(cl); emit("\n") }
emit(en); emit(":\n"); g_term = false
}
fn emit_for(st: Node) -> void {
let slot = emit_alloca("i32")
let lo = emit_expr(st.a)
store_at("i32", lo.code, slot)
loc_push(st.s, slot, "int")
let cl = lbl("fcond"); let bl = lbl("fbody"); let ct = lbl("fcont"); let en = lbl("fend")
emit(" br label %"); emit(cl); emit("\n")
emit(cl); emit(":\n")
let iv = emit_bind(`load i32, ptr {slot}`)
let hi = emit_expr(st.b)
let cc = emit_bind(`icmp slt i32 {iv}, {hi.code}`)
emit(" br i1 "); emit(cc); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
emit(bl); emit(":\n"); g_term = false
loop_push(ct, en)
emit_block(st.c)
loop_pop()
if not g_term { emit(" br label %"); emit(ct); emit("\n") }
emit(ct); emit(":\n")
let i2 = emit_bind(`load i32, ptr {slot}`)
let i3 = emit_bind(`add i32 {i2}, 1`)
store_at("i32", i3, slot)
emit(" br label %"); emit(cl); emit("\n")
emit(en); emit(":\n"); g_term = false
}
fn emit_return(st: Node) -> void {
if (st.a != null) {
let v = emit_expr(st.a)
store_at(llty(ret_ty), v.code, "%retval")
}
emit(" br label %ret\n")
g_term = true
}
fn arm_is_default(arm: Node) -> bool {
var p = 0
while p < len(arm.kids) { if arm.kids[p].kind == E_ID and (arm.kids[p].s == "_") { return true }; p = p + 1 }
return false
}
fn emit_match(st: Node) -> void {
let sv = emit_expr(st.a)
let endl = lbl("mend")
var deflt: Node = null
var i = 0
while i < len(st.kids) {
let arm = st.kids[i]
if arm_is_default(arm) { deflt = arm }
else {
var acc = "0"
var first = true
var p = 0
while p < len(arm.kids) {
let pv = emit_expr(arm.kids[p])
let c = emit_bind(`icmp eq i32 {sv.code}, {pv.code}`)
if first { acc = c; first = false }
else { acc = emit_bind(`or i1 {acc}, {c}`) }
p = p + 1
}
let bodyl = lbl("mbody"); let nextl = lbl("marm")
emit(" br i1 "); emit(acc); emit(", label %"); emit(bodyl); emit(", label %"); emit(nextl); emit("\n")
emit(bodyl); emit(":\n"); g_term = false
emit_block(arm.a)
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(nextl); emit(":\n"); g_term = false
}
i = i + 1
}
if (deflt != null) { emit_block(deflt.a) }
if not g_term { emit(" br label %"); emit(endl); emit("\n") }
emit(endl); emit(":\n"); g_term = false
}
fn emit_stmt(st: Node) -> void {
if st.kind == S_LET {
var ty = st.ty
if (ty == null) { let v0 = emit_expr(st.a); ty = v0.ty
let slot = emit_alloca(llty(ty)); store_at(llty(ty), v0.code, slot); loc_push(st.s, slot, ty); loc_set_mut(st.ival); return }
let slot = emit_alloca(llty(ty))
if (st.a == null) {
var z = "0"
if (llty(ty) == "ptr") { z = "null" }
store_at(llty(ty), z, slot)
} else { let v = emit_expr(st.a); store_at(llty(ty), v.code, slot) }
loc_push(st.s, slot, ty)
loc_set_mut(st.ival)
return
}
if st.kind == S_ASSIGN { emit_assign(st); return }
if st.kind == S_IF { emit_if(st); return }
if st.kind == S_WHILE { emit_while(st); return }
if st.kind == S_FOR { emit_for(st); return }
if st.kind == S_RETURN { emit_return(st); return }
if st.kind == S_BREAK { emit(" br label %"); emit(brk_lbl[nloop - 1]); emit("\n"); g_term = true; return }
if st.kind == S_CONTINUE { emit(" br label %"); emit(cnt_lbl[nloop - 1]); emit("\n"); g_term = true; return }
if st.kind == S_MATCH { emit_match(st); return }
if st.kind == S_QUERY { emit_query(st); return }
if st.kind == S_SPAWN { emit_spawn(st); return }
if st.kind == S_DESPAWN { emit_despawn(st); return }
if st.kind == S_TOGGLE { emit_toggle(st); return }
if st.kind == S_MACHINE { emit_machine(st); return }
if st.kind == S_BECOME { emit_become(st); return }
if st.kind == S_EXPR { let v = emit_expr(st.a); return }
perr("cannot emit statement")
}
fn loop_push(cont: ptr, brk: ptr) -> void {
if nloop < len(cnt_lbl) { cnt_lbl[nloop] = cont; brk_lbl[nloop] = brk }
else { push(cnt_lbl, cont); push(brk_lbl, brk) }
nloop = nloop + 1
}
fn loop_pop() -> void { nloop = nloop - 1 }