Some checks are pending
docs / build-and-deploy (push) Waiting to run
Grow List sorting from a numeric-only insertion sort into a small, game-friendly toolkit that sorts records and query results by a key or a full comparator, stably and in O(n log n). - List.sort_by(s, keyfn) ascending by a key (draw order, price) - List.sort_desc_by(s, keyfn) descending (leaderboards) - List.sort_with(s, cmpfn) full cmp(a,b)->int comparator (multi-field) Comparators/keys are passed as named top-level functions rather than lambdas, so the toolkit ships without waiting on closures (#1). Engine: a stable bottom-up merge sort. emit_takeright is the single place stability is decided ("take the right run's head only on a strict win" -> equal keys keep prior order). List.sort becomes a hybrid: insertion sort for n<32, merge sort above; both stable, so output is unchanged. Key functions must return an integer-ish type; record slices hold pointer elements, so the key/comparator receives the record pointer. Tests: selfhost/tests/sort.ludic (scalar large-n, sort_by, sort_desc_by, stability, sort_with). Docs: list-sort_by/desc_by/with + updated list-sort. All suites green (28 self-host / 46 test / 29 test-tools); reseeded, C-free bootstrap fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
506 lines
25 KiB
Text
506 lines
25 KiB
Text
# emit_list.ludic — the List.* namespace over []T slices. A slice is the
|
|
# { data, len, cap } %LSlice header (see emit_new.ludic); every op here reads or
|
|
# mutates that header in place, so all holders of the slice observe the change.
|
|
# Element comparison (contains/index_of) is by value for scalars and by identity
|
|
# for reference elements (structs/strings), matching how `==` behaves elsewhere.
|
|
|
|
# address of element `idx` (an i32 code) in slice header `h`, element LLVM type `elt`
|
|
function list_elem_addr(h: pointer, elt: pointer, idx: pointer) -> pointer {
|
|
let dp = slice_field(h, 0)
|
|
let data = emit_bind(`load ptr, ptr {dp}`)
|
|
return emit_bind(`getelementptr inbounds {elt}, ptr {data}, i32 {idx}`)
|
|
}
|
|
|
|
function is_list_ns(meth: pointer) -> bool {
|
|
if (meth == "len") or (meth == "push") or (meth == "clear") { return true }
|
|
if (meth == "first") or (meth == "last") or (meth == "pop") or (meth == "swap") { return true }
|
|
if (meth == "contains") or (meth == "index_of") or (meth == "reverse") { return true }
|
|
if (meth == "insert") or (meth == "remove_at") or (meth == "remove") or (meth == "sort") { return true }
|
|
if (meth == "sort_by") or (meth == "sort_desc_by") or (meth == "sort_with") { return true }
|
|
return false
|
|
}
|
|
|
|
# grow the slice's backing buffer if it is full, exactly as push does (double,
|
|
# or 8 from empty). Leaves length untouched; only capacity/data may change.
|
|
function list_grow_if_full(h: pointer, elt: pointer) -> void {
|
|
let lp = slice_field(h, 1); let cp = slice_field(h, 2); let dp = slice_field(h, 0)
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let c = emit_bind(`load i32, ptr {cp}`)
|
|
let full = emit_bind(`icmp sge i32 {l}, {c}`)
|
|
let grow = lbl("ig"); let done = lbl("igd")
|
|
emit(" br i1 "); emit(full); emit(", label %"); emit(grow); emit(", label %"); emit(done); emit("\n")
|
|
emit(grow); emit(":\n")
|
|
let dbl = emit_bind(`mul i32 {c}, 2`)
|
|
let isz = emit_bind(`icmp eq i32 {c}, 0`)
|
|
let nc = emit_bind(`select i1 {isz}, i32 8, i32 {dbl}`)
|
|
let esz = emit_sizeof(elt)
|
|
let ncw = emit_bind(`zext i32 {nc} to i64`)
|
|
let byts = emit_bind(`mul i64 {ncw}, {esz}`)
|
|
let old = emit_bind(`load ptr, ptr {dp}`)
|
|
let nd = emit_bind(`call ptr @realloc(ptr {old}, i64 {byts})`)
|
|
emit(" store ptr "); emit(nd); emit(", ptr "); emit(dp); emit("\n")
|
|
emit(" store i32 "); emit(nc); emit(", ptr "); emit(cp); emit("\n")
|
|
emit(" br label %"); emit(done); emit("\n")
|
|
emit(done); emit(":\n")
|
|
}
|
|
|
|
# ---- the sorting toolkit ---------------------------------------------------
|
|
# sort_by / sort_desc_by / sort_with lower to a stable, bottom-up (iterative)
|
|
# merge sort: O(n log n) time, O(n) scratch, and stable — equal elements keep
|
|
# their prior order, which is what tie-breaks (leaderboards, draw order) rely
|
|
# on. The ordering rule is supplied per call site as one of these modes; every
|
|
# mode reduces to a single "should the right run's head come out before the
|
|
# left's?" predicate (emit_takeright), the only place stability is decided.
|
|
const SORT_WITH: int = 0 # comparator fn(a, b) -> int; a after b when cmp(a, b) > 0
|
|
const SORT_KEY_ASC: int = 1 # key fn(x) -> K; ascending by key
|
|
const SORT_KEY_DESC: int = 2 # key fn(x) -> K; descending by key
|
|
const SORT_SCALAR: int = 3 # bare ascending over scalar elements (List.sort's large-n path)
|
|
|
|
# address of element `idx` (an i32 code) within a raw base pointer `base`
|
|
function gep_at(base: pointer, elt: pointer, idx: pointer) -> pointer {
|
|
return emit_bind(`getelementptr inbounds {elt}, ptr {base}, i32 {idx}`)
|
|
}
|
|
|
|
# i1 code for "take the right run's head (`bv`) before the left's (`av`)". True
|
|
# only on a STRICT win for the right, so equal keys keep the left (= earlier)
|
|
# element first — that is the stability guarantee. `fnsym` is the @fn_ name of
|
|
# the user comparator/key function; `keyll` its key's LLVM type (key modes only).
|
|
function emit_takeright(elt: pointer, mode: int, fnsym: pointer, keyll: pointer, av: pointer, bv: pointer) -> pointer {
|
|
if (mode == SORT_SCALAR) {
|
|
return emit_bind(`icmp slt {elt} {bv}, {av}`) # right strictly less than left
|
|
}
|
|
if (mode == SORT_WITH) {
|
|
let c = emit_bind(`call i32 @fn_{fnsym}({elt} {av}, {elt} {bv})`)
|
|
return emit_bind(`icmp sgt i32 {c}, 0`) # cmp(left, right) > 0 -> left comes after
|
|
}
|
|
let kl = emit_bind(`call {keyll} @fn_{fnsym}({elt} {av})`)
|
|
let kr = emit_bind(`call {keyll} @fn_{fnsym}({elt} {bv})`)
|
|
if (mode == SORT_KEY_DESC) {
|
|
return emit_bind(`icmp slt {keyll} {kl}, {kr}`) # desc: right first when its key is larger
|
|
}
|
|
return emit_bind(`icmp sgt {keyll} {kl}, {kr}`) # asc: right first when its key is smaller
|
|
}
|
|
|
|
# emit a stable bottom-up merge sort of slice header `h` (element LLVM type
|
|
# `elt`), ordered by `mode`. Passes double the run width each round, merging
|
|
# adjacent runs through a malloc'd scratch buffer and copying the result back,
|
|
# so the slice is sorted in place from the caller's view.
|
|
function emit_merge_sort(h: pointer, elt: pointer, mode: int, fnsym: pointer, keyll: pointer) -> void {
|
|
let dp = slice_field(h, 0)
|
|
let data = emit_bind(`load ptr, ptr {dp}`) # data pointer is stable during the sort
|
|
let lp = slice_field(h, 1)
|
|
let n = emit_bind(`load i32, ptr {lp}`)
|
|
let esz = emit_sizeof(elt)
|
|
let nz = emit_bind(`zext i32 {n} to i64`)
|
|
let byts = emit_bind(`mul i64 {nz}, {esz}`)
|
|
let scratch = emit_bind(`call ptr @malloc(i64 {byts})`)
|
|
|
|
# outer: for (width = 1; width < n; width *= 2)
|
|
let wp = emit_alloca("i32"); store_at("i32", "1", wp)
|
|
let wc = lbl("ms_wc"); let wb = lbl("ms_wb"); let we = lbl("ms_we")
|
|
emit(" br label %"); emit(wc); emit("\n"); emit(wc); emit(":\n")
|
|
let w = emit_bind(`load i32, ptr {wp}`)
|
|
let wgo = emit_bind(`icmp slt i32 {w}, {n}`)
|
|
emit(" br i1 "); emit(wgo); emit(", label %"); emit(wb); emit(", label %"); emit(we); emit("\n")
|
|
emit(wb); emit(":\n")
|
|
let w2 = emit_bind(`mul i32 {w}, 2`)
|
|
|
|
# middle: for (i = 0; i < n; i += 2*width) merge [i, i+w) with [i+w, i+2w)
|
|
let ip = emit_alloca("i32"); store_at("i32", "0", ip)
|
|
let ic = lbl("ms_ic"); let ib = lbl("ms_ib"); let ie = lbl("ms_ie")
|
|
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
|
|
let i = emit_bind(`load i32, ptr {ip}`)
|
|
let igo = emit_bind(`icmp slt i32 {i}, {n}`)
|
|
emit(" br i1 "); emit(igo); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
|
|
emit(ib); emit(":\n")
|
|
let iw = emit_bind(`add i32 {i}, {w}`)
|
|
let mlt = emit_bind(`icmp slt i32 {iw}, {n}`)
|
|
let mid = emit_bind(`select i1 {mlt}, i32 {iw}, i32 {n}`) # mid = min(i+w, n)
|
|
let iw2 = emit_bind(`add i32 {i}, {w2}`)
|
|
let hlt = emit_bind(`icmp slt i32 {iw2}, {n}`)
|
|
let hi = emit_bind(`select i1 {hlt}, i32 {iw2}, i32 {n}`) # hi = min(i+2w, n)
|
|
|
|
let ap = emit_alloca("i32"); store_at("i32", i, ap) # a: cursor in left run [i, mid)
|
|
let bp = emit_alloca("i32"); store_at("i32", mid, bp) # b: cursor in right run [mid, hi)
|
|
let kp = emit_alloca("i32"); store_at("i32", i, kp) # k: write cursor in scratch
|
|
|
|
# merge while both runs have elements
|
|
let mc = lbl("ms_mc"); let mbb = lbl("ms_mb"); let mend = lbl("ms_mend")
|
|
emit(" br label %"); emit(mc); emit("\n"); emit(mc); emit(":\n")
|
|
let a = emit_bind(`load i32, ptr {ap}`)
|
|
let b = emit_bind(`load i32, ptr {bp}`)
|
|
let aok = emit_bind(`icmp slt i32 {a}, {mid}`)
|
|
let bok = emit_bind(`icmp slt i32 {b}, {hi}`)
|
|
let both = emit_bind(`and i1 {aok}, {bok}`)
|
|
emit(" br i1 "); emit(both); emit(", label %"); emit(mbb); emit(", label %"); emit(mend); emit("\n")
|
|
emit(mbb); emit(":\n")
|
|
let aad = gep_at(data, elt, a)
|
|
let av = emit_bind(`load {elt}, ptr {aad}`)
|
|
let bad = gep_at(data, elt, b)
|
|
let bv = emit_bind(`load {elt}, ptr {bad}`)
|
|
let tr = emit_takeright(elt, mode, fnsym, keyll, av, bv)
|
|
let k = emit_bind(`load i32, ptr {kp}`)
|
|
let sad = gep_at(scratch, elt, k)
|
|
let tR = lbl("ms_takeR"); let tL = lbl("ms_takeL"); let tD = lbl("ms_takeD")
|
|
emit(" br i1 "); emit(tr); emit(", label %"); emit(tR); emit(", label %"); emit(tL); emit("\n")
|
|
emit(tR); emit(":\n")
|
|
emit(" store "); emit(elt); emit(" "); emit(bv); emit(", ptr "); emit(sad); emit("\n")
|
|
let b1 = emit_bind(`add i32 {b}, 1`); store_at("i32", b1, bp)
|
|
emit(" br label %"); emit(tD); emit("\n")
|
|
emit(tL); emit(":\n")
|
|
emit(" store "); emit(elt); emit(" "); emit(av); emit(", ptr "); emit(sad); emit("\n")
|
|
let a1 = emit_bind(`add i32 {a}, 1`); store_at("i32", a1, ap)
|
|
emit(" br label %"); emit(tD); emit("\n")
|
|
emit(tD); emit(":\n")
|
|
let k1 = emit_bind(`add i32 {k}, 1`); store_at("i32", k1, kp)
|
|
emit(" br label %"); emit(mc); emit("\n")
|
|
emit(mend); emit(":\n")
|
|
|
|
# drain the tail of the left run
|
|
let dac = lbl("ms_dac"); let dab = lbl("ms_dab"); let dae = lbl("ms_dae")
|
|
emit(" br label %"); emit(dac); emit("\n"); emit(dac); emit(":\n")
|
|
let a2 = emit_bind(`load i32, ptr {ap}`)
|
|
let amore = emit_bind(`icmp slt i32 {a2}, {mid}`)
|
|
emit(" br i1 "); emit(amore); emit(", label %"); emit(dab); emit(", label %"); emit(dae); emit("\n")
|
|
emit(dab); emit(":\n")
|
|
let aad2 = gep_at(data, elt, a2)
|
|
let av2 = emit_bind(`load {elt}, ptr {aad2}`)
|
|
let k2 = emit_bind(`load i32, ptr {kp}`)
|
|
let sad2 = gep_at(scratch, elt, k2)
|
|
emit(" store "); emit(elt); emit(" "); emit(av2); emit(", ptr "); emit(sad2); emit("\n")
|
|
let a3 = emit_bind(`add i32 {a2}, 1`); store_at("i32", a3, ap)
|
|
let k3 = emit_bind(`add i32 {k2}, 1`); store_at("i32", k3, kp)
|
|
emit(" br label %"); emit(dac); emit("\n")
|
|
emit(dae); emit(":\n")
|
|
|
|
# drain the tail of the right run
|
|
let dbc = lbl("ms_dbc"); let dbb = lbl("ms_dbb"); let dbe = lbl("ms_dbe")
|
|
emit(" br label %"); emit(dbc); emit("\n"); emit(dbc); emit(":\n")
|
|
let b2 = emit_bind(`load i32, ptr {bp}`)
|
|
let bmore = emit_bind(`icmp slt i32 {b2}, {hi}`)
|
|
emit(" br i1 "); emit(bmore); emit(", label %"); emit(dbb); emit(", label %"); emit(dbe); emit("\n")
|
|
emit(dbb); emit(":\n")
|
|
let bad2 = gep_at(data, elt, b2)
|
|
let bv2 = emit_bind(`load {elt}, ptr {bad2}`)
|
|
let k4 = emit_bind(`load i32, ptr {kp}`)
|
|
let sad3 = gep_at(scratch, elt, k4)
|
|
emit(" store "); emit(elt); emit(" "); emit(bv2); emit(", ptr "); emit(sad3); emit("\n")
|
|
let b3 = emit_bind(`add i32 {b2}, 1`); store_at("i32", b3, bp)
|
|
let k5 = emit_bind(`add i32 {k4}, 1`); store_at("i32", k5, kp)
|
|
emit(" br label %"); emit(dbc); emit("\n")
|
|
emit(dbe); emit(":\n")
|
|
|
|
# copy the merged run scratch[i, hi) back into data[i, hi)
|
|
let jp = emit_alloca("i32"); store_at("i32", i, jp)
|
|
let cbc = lbl("ms_cbc"); let cbb = lbl("ms_cbb"); let cbe = lbl("ms_cbe")
|
|
emit(" br label %"); emit(cbc); emit("\n"); emit(cbc); emit(":\n")
|
|
let j = emit_bind(`load i32, ptr {jp}`)
|
|
let jmore = emit_bind(`icmp slt i32 {j}, {hi}`)
|
|
emit(" br i1 "); emit(jmore); emit(", label %"); emit(cbb); emit(", label %"); emit(cbe); emit("\n")
|
|
emit(cbb); emit(":\n")
|
|
let ssad = gep_at(scratch, elt, j)
|
|
let sv = emit_bind(`load {elt}, ptr {ssad}`)
|
|
let ddad = gep_at(data, elt, j)
|
|
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(ddad); emit("\n")
|
|
let j1 = emit_bind(`add i32 {j}, 1`); store_at("i32", j1, jp)
|
|
emit(" br label %"); emit(cbc); emit("\n")
|
|
emit(cbe); emit(":\n")
|
|
|
|
let inx = emit_bind(`add i32 {i}, {w2}`); store_at("i32", inx, ip)
|
|
emit(" br label %"); emit(ic); emit("\n")
|
|
emit(ie); emit(":\n")
|
|
store_at("i32", w2, wp) # width *= 2
|
|
emit(" br label %"); emit(wc); emit("\n")
|
|
emit(we); emit(":\n")
|
|
emit(" call void @free(ptr "); emit(scratch); emit(")\n")
|
|
}
|
|
|
|
# resolve a sort_by/sort_with function argument (a bare identifier naming a
|
|
# top-level function) to its name, erroring clearly on misuse.
|
|
function sort_fn_arg(meth: pointer, e: Node) -> pointer {
|
|
if (len(e.kids) < 2) { perr(`List.{meth} needs (slice, function)`) }
|
|
if (e.kids[1].kind != E_ID) { perr(`List.{meth}: the second argument must be a function name`) }
|
|
let fname = e.kids[1].s
|
|
if (find_fn(fname) == null) { perr(`List.{meth}: no function named '{fname}'`) }
|
|
return fname
|
|
}
|
|
|
|
function emit_list_ns(meth: pointer, e: Node) -> Val {
|
|
if (meth == "len") { return emit_len(e) } # same header length as len(s)
|
|
if (meth == "push") { return emit_push(e) } # same as push(s, v)
|
|
let s = emit_expr(e.kids[0])
|
|
if not is_slice_ty(s.ty) { perr(`List.{meth} needs a slice`) }
|
|
let el = slice_elem(s.ty)
|
|
let elt = llty(el)
|
|
let h = s.code
|
|
let lp = slice_field(h, 1)
|
|
|
|
if (meth == "clear") { # drop to length 0 (keeps capacity)
|
|
emit(" store i32 0, ptr "); emit(lp); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "first") { # element 0 (assumes non-empty)
|
|
let a = list_elem_addr(h, elt, "0")
|
|
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
|
}
|
|
if (meth == "last") { # element len-1 (assumes non-empty)
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let l1 = emit_bind(`sub i32 {l}, 1`)
|
|
let a = list_elem_addr(h, elt, l1)
|
|
return val(emit_bind(`load {elt}, ptr {a}`), el)
|
|
}
|
|
if (meth == "pop") { # remove & return the last element
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let l1 = emit_bind(`sub i32 {l}, 1`)
|
|
let a = list_elem_addr(h, elt, l1)
|
|
let v = emit_bind(`load {elt}, ptr {a}`)
|
|
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
|
return val(v, el)
|
|
}
|
|
if (meth == "swap") { # exchange elements i and j
|
|
let i = emit_expr(e.kids[1]); let j = emit_expr(e.kids[2])
|
|
let ai = list_elem_addr(h, elt, i.code)
|
|
let aj = list_elem_addr(h, elt, j.code)
|
|
let vi = emit_bind(`load {elt}, ptr {ai}`)
|
|
let vj = emit_bind(`load {elt}, ptr {aj}`)
|
|
emit(" store "); emit(elt); emit(" "); emit(vj); emit(", ptr "); emit(ai); emit("\n")
|
|
emit(" store "); emit(elt); emit(" "); emit(vi); emit(", ptr "); emit(aj); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "contains") or (meth == "index_of") { # linear scan; index_of -> -1 if absent
|
|
let needle = emit_expr(e.kids[1])
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let res = emit_alloca("i32")
|
|
store_at("i32", "-1", res)
|
|
let ix = emit_alloca("i32")
|
|
store_at("i32", "0", ix)
|
|
let cl = lbl("lc_cond"); let bl = lbl("lc_body"); let hit = lbl("lc_hit")
|
|
let nx = lbl("lc_next"); let en = lbl("lc_end")
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(cl); emit(":\n")
|
|
let iv = emit_bind(`load i32, ptr {ix}`)
|
|
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
|
emit(" br i1 "); emit(more); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n")
|
|
let a = list_elem_addr(h, elt, iv)
|
|
let ev = emit_bind(`load {elt}, ptr {a}`)
|
|
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
|
emit(" br i1 "); emit(eq); emit(", label %"); emit(hit); emit(", label %"); emit(nx); emit("\n")
|
|
emit(hit); emit(":\n")
|
|
store_at("i32", iv, res)
|
|
emit(" br label %"); emit(en); emit("\n")
|
|
emit(nx); emit(":\n")
|
|
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
|
store_at("i32", iv1, ix)
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(en); emit(":\n")
|
|
let found = emit_bind(`load i32, ptr {res}`)
|
|
if (meth == "index_of") { return val(found, "int") }
|
|
let hasit = emit_bind(`icmp sge i32 {found}, 0`) # contains -> found >= 0
|
|
return val(emit_bind(`zext i1 {hasit} to i32`), "bool")
|
|
}
|
|
if (meth == "insert") { # insert v at index i, shifting the rest up
|
|
let idx = emit_expr(e.kids[1]); let vv = emit_expr(e.kids[2])
|
|
list_grow_if_full(h, elt)
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let jp = emit_alloca("i32")
|
|
store_at("i32", l, jp)
|
|
let cl = lbl("li_c"); let bl = lbl("li_b"); let en = lbl("li_e")
|
|
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
|
let j = emit_bind(`load i32, ptr {jp}`)
|
|
let go = emit_bind(`icmp sgt i32 {j}, {idx.code}`)
|
|
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n")
|
|
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
|
let src = list_elem_addr(h, elt, jm1)
|
|
let sv = emit_bind(`load {elt}, ptr {src}`)
|
|
let dst = list_elem_addr(h, elt, j)
|
|
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
|
store_at("i32", jm1, jp)
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(en); emit(":\n")
|
|
let at = list_elem_addr(h, elt, idx.code)
|
|
emit(" store "); emit(elt); emit(" "); emit(vv.code); emit(", ptr "); emit(at); emit("\n")
|
|
let l1 = emit_bind(`add i32 {l}, 1`)
|
|
emit(" store i32 "); emit(l1); emit(", ptr "); emit(lp); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "remove_at") { # remove index i, shifting the rest down
|
|
let idx = emit_expr(e.kids[1])
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
|
let jp = emit_alloca("i32")
|
|
store_at("i32", idx.code, jp)
|
|
let cl = lbl("lr_c"); let bl = lbl("lr_b"); let en = lbl("lr_e")
|
|
emit(" br label %"); emit(cl); emit("\n"); emit(cl); emit(":\n")
|
|
let j = emit_bind(`load i32, ptr {jp}`)
|
|
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
|
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n")
|
|
let jp1 = emit_bind(`add i32 {j}, 1`)
|
|
let src = list_elem_addr(h, elt, jp1)
|
|
let sv = emit_bind(`load {elt}, ptr {src}`)
|
|
let dst = list_elem_addr(h, elt, j)
|
|
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
|
store_at("i32", jp1, jp)
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(en); emit(":\n")
|
|
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "remove") { # remove the first element equal to v
|
|
let needle = emit_expr(e.kids[1])
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
# scan for the index of v (-1 if absent)
|
|
let fp = emit_alloca("i32")
|
|
store_at("i32", "-1", fp)
|
|
let ip = emit_alloca("i32")
|
|
store_at("i32", "0", ip)
|
|
let sc = lbl("lv_c"); let sb = lbl("lv_b"); let sh = lbl("lv_h"); let sn = lbl("lv_n"); let se = lbl("lv_e")
|
|
emit(" br label %"); emit(sc); emit("\n"); emit(sc); emit(":\n")
|
|
let iv = emit_bind(`load i32, ptr {ip}`)
|
|
let more = emit_bind(`icmp slt i32 {iv}, {l}`)
|
|
let seen = emit_bind(`load i32, ptr {fp}`)
|
|
let notyet = emit_bind(`icmp slt i32 {seen}, 0`)
|
|
let cont = emit_bind(`and i1 {more}, {notyet}`)
|
|
emit(" br i1 "); emit(cont); emit(", label %"); emit(sb); emit(", label %"); emit(se); emit("\n")
|
|
emit(sb); emit(":\n")
|
|
let a = list_elem_addr(h, elt, iv)
|
|
let ev = emit_bind(`load {elt}, ptr {a}`)
|
|
let eq = emit_bind(`icmp eq {elt} {ev}, {needle.code}`)
|
|
emit(" br i1 "); emit(eq); emit(", label %"); emit(sh); emit(", label %"); emit(sn); emit("\n")
|
|
emit(sh); emit(":\n"); store_at("i32", iv, fp); emit(" br label %"); emit(sc); emit("\n")
|
|
emit(sn); emit(":\n")
|
|
let iv1 = emit_bind(`add i32 {iv}, 1`)
|
|
store_at("i32", iv1, ip)
|
|
emit(" br label %"); emit(sc); emit("\n")
|
|
emit(se); emit(":\n")
|
|
# if found, shift the tail down and shrink
|
|
let found = emit_bind(`load i32, ptr {fp}`)
|
|
let has = emit_bind(`icmp sge i32 {found}, 0`)
|
|
let doit = lbl("lv_do"); let end = lbl("lv_end")
|
|
emit(" br i1 "); emit(has); emit(", label %"); emit(doit); emit(", label %"); emit(end); emit("\n")
|
|
emit(doit); emit(":\n")
|
|
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
|
let jp = emit_alloca("i32")
|
|
store_at("i32", found, jp)
|
|
let dc = lbl("lv_dc"); let db = lbl("lv_db"); let de = lbl("lv_de")
|
|
emit(" br label %"); emit(dc); emit("\n"); emit(dc); emit(":\n")
|
|
let j = emit_bind(`load i32, ptr {jp}`)
|
|
let go = emit_bind(`icmp slt i32 {j}, {lm1}`)
|
|
emit(" br i1 "); emit(go); emit(", label %"); emit(db); emit(", label %"); emit(de); emit("\n")
|
|
emit(db); emit(":\n")
|
|
let jp1 = emit_bind(`add i32 {j}, 1`)
|
|
let src = list_elem_addr(h, elt, jp1)
|
|
let sv = emit_bind(`load {elt}, ptr {src}`)
|
|
let dst = list_elem_addr(h, elt, j)
|
|
emit(" store "); emit(elt); emit(" "); emit(sv); emit(", ptr "); emit(dst); emit("\n")
|
|
store_at("i32", jp1, jp)
|
|
emit(" br label %"); emit(dc); emit("\n")
|
|
emit(de); emit(":\n")
|
|
emit(" store i32 "); emit(lm1); emit(", ptr "); emit(lp); emit("\n")
|
|
emit(" br label %"); emit(end); emit("\n")
|
|
emit(end); emit(":\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "sort") { # stable ascending sort in place
|
|
# small lists take the simple insertion sort (fast, cache-friendly, and
|
|
# what nearly-sorted per-frame lists usually are); larger lists take the
|
|
# O(n log n) stable merge sort. Both are stable, so the result is identical.
|
|
let n0 = emit_bind(`load i32, ptr {lp}`)
|
|
let small = emit_bind(`icmp slt i32 {n0}, 32`)
|
|
let sIns = lbl("sort_ins"); let sMrg = lbl("sort_mrg"); let sEnd = lbl("sort_end")
|
|
emit(" br i1 "); emit(small); emit(", label %"); emit(sIns); emit(", label %"); emit(sMrg); emit("\n")
|
|
emit(sIns); emit(":\n")
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let ip = emit_alloca("i32")
|
|
store_at("i32", "1", ip)
|
|
let oc = lbl("ls_oc"); let ob = lbl("ls_ob"); let oe = lbl("ls_oe")
|
|
emit(" br label %"); emit(oc); emit("\n"); emit(oc); emit(":\n")
|
|
let i = emit_bind(`load i32, ptr {ip}`)
|
|
let omore = emit_bind(`icmp slt i32 {i}, {l}`)
|
|
emit(" br i1 "); emit(omore); emit(", label %"); emit(ob); emit(", label %"); emit(oe); emit("\n")
|
|
emit(ob); emit(":\n")
|
|
let ai = list_elem_addr(h, elt, i)
|
|
let key = emit_bind(`load {elt}, ptr {ai}`)
|
|
let jp = emit_alloca("i32")
|
|
let im1 = emit_bind(`sub i32 {i}, 1`)
|
|
store_at("i32", im1, jp)
|
|
let ic = lbl("ls_ic"); let ib = lbl("ls_ib"); let ie = lbl("ls_ie")
|
|
emit(" br label %"); emit(ic); emit("\n"); emit(ic); emit(":\n")
|
|
let j = emit_bind(`load i32, ptr {jp}`)
|
|
let jok = emit_bind(`icmp sge i32 {j}, 0`)
|
|
let aj = list_elem_addr(h, elt, j)
|
|
let ev = emit_bind(`load {elt}, ptr {aj}`)
|
|
let gt = emit_bind(`icmp sgt {elt} {ev}, {key}`)
|
|
let shift = emit_bind(`and i1 {jok}, {gt}`)
|
|
emit(" br i1 "); emit(shift); emit(", label %"); emit(ib); emit(", label %"); emit(ie); emit("\n")
|
|
emit(ib); emit(":\n")
|
|
let jp1 = emit_bind(`add i32 {j}, 1`)
|
|
let dst = list_elem_addr(h, elt, jp1)
|
|
emit(" store "); emit(elt); emit(" "); emit(ev); emit(", ptr "); emit(dst); emit("\n")
|
|
let jm1 = emit_bind(`sub i32 {j}, 1`)
|
|
store_at("i32", jm1, jp)
|
|
emit(" br label %"); emit(ic); emit("\n")
|
|
emit(ie); emit(":\n")
|
|
let j2 = emit_bind(`load i32, ptr {jp}`)
|
|
let slot = emit_bind(`add i32 {j2}, 1`)
|
|
let sa = list_elem_addr(h, elt, slot)
|
|
emit(" store "); emit(elt); emit(" "); emit(key); emit(", ptr "); emit(sa); emit("\n")
|
|
let i1 = emit_bind(`add i32 {i}, 1`)
|
|
store_at("i32", i1, ip)
|
|
emit(" br label %"); emit(oc); emit("\n")
|
|
emit(oe); emit(":\n")
|
|
emit(" br label %"); emit(sEnd); emit("\n")
|
|
emit(sMrg); emit(":\n")
|
|
emit_merge_sort(h, elt, SORT_SCALAR, "", "")
|
|
emit(" br label %"); emit(sEnd); emit("\n")
|
|
emit(sEnd); emit(":\n")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "sort_with") { # full comparator: fn(a, b) -> int (a<b when <0)
|
|
let fname = sort_fn_arg(meth, e)
|
|
if (len(param_types(find_fn(fname))) != 2) { perr(`List.sort_with: '{fname}' must take two arguments`) }
|
|
emit_merge_sort(h, elt, SORT_WITH, fname, "i32")
|
|
return val("0", "void")
|
|
}
|
|
if (meth == "sort_by") or (meth == "sort_desc_by") { # key selector: fn(x) -> number
|
|
let fname = sort_fn_arg(meth, e)
|
|
let kf = find_fn(fname)
|
|
if (len(param_types(kf)) != 1) { perr(`List.{meth}: key '{fname}' must take one argument`) }
|
|
let keyll = llty(kf.ty)
|
|
if (keyll == "ptr") or (keyll == "void") { perr(`List.{meth}: key '{fname}' must return a number`) }
|
|
var m = SORT_KEY_ASC
|
|
if (meth == "sort_desc_by") { m = SORT_KEY_DESC }
|
|
emit_merge_sort(h, elt, m, fname, keyll)
|
|
return val("0", "void")
|
|
}
|
|
# reverse: swap ends inward until the cursors meet
|
|
let l = emit_bind(`load i32, ptr {lp}`)
|
|
let loi = emit_alloca("i32")
|
|
store_at("i32", "0", loi)
|
|
let hii = emit_alloca("i32")
|
|
let lm1 = emit_bind(`sub i32 {l}, 1`)
|
|
store_at("i32", lm1, hii)
|
|
let cl = lbl("lr_cond"); let bl = lbl("lr_body"); let en = lbl("lr_end")
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(cl); emit(":\n")
|
|
let lo = emit_bind(`load i32, ptr {loi}`)
|
|
let hi = emit_bind(`load i32, ptr {hii}`)
|
|
let go = emit_bind(`icmp slt i32 {lo}, {hi}`)
|
|
emit(" br i1 "); emit(go); emit(", label %"); emit(bl); emit(", label %"); emit(en); emit("\n")
|
|
emit(bl); emit(":\n")
|
|
let alo = list_elem_addr(h, elt, lo)
|
|
let ahi = list_elem_addr(h, elt, hi)
|
|
let vlo = emit_bind(`load {elt}, ptr {alo}`)
|
|
let vhi = emit_bind(`load {elt}, ptr {ahi}`)
|
|
emit(" store "); emit(elt); emit(" "); emit(vhi); emit(", ptr "); emit(alo); emit("\n")
|
|
emit(" store "); emit(elt); emit(" "); emit(vlo); emit(", ptr "); emit(ahi); emit("\n")
|
|
let lo1 = emit_bind(`add i32 {lo}, 1`)
|
|
store_at("i32", lo1, loi)
|
|
let hi1 = emit_bind(`sub i32 {hi}, 1`)
|
|
store_at("i32", hi1, hii)
|
|
emit(" br label %"); emit(cl); emit("\n")
|
|
emit(en); emit(":\n")
|
|
return val("0", "void")
|
|
}
|