frame allocs: a local shadowing a function is no edge, the drain's calls are no edges, HEAP keeps only a growth off the arena, and the arena starts at its first use
- the call graph is by name, and a local or a parameter named as a function (a float bd, part, bx) linked to that function: a name the function binds itself is never an edge now. - drain_actions, generated, calls every reducer; a reducer is reached from its action's dispatch, so the drain's calls are not edges. - a fresh value flowing into a local that also holds kept memory is still the frame's (storing it anywhere kept would have made it ESC): HEAP now keeps only a push's growth off the arena. - the arena starts at its first use rather than at the first frame mark, so boot's temporaries are scratch too - dead once the Start handlers return - and a scratch site is never a birth. Plus ludic.hints' rail and three of ludic.update's one-off lines declared. The arena goldens pass poisoned. Maroon Lake (d79d189f): 39/11/66 -> 30/9/0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8228597ade
commit
d483c9283c
9 changed files with 62846 additions and 61736 deletions
|
|
@ -3,5 +3,6 @@
|
|||
export function hints_rail_into(hints_st: HintsState, out: []int) -> void {
|
||||
List.clear(out)
|
||||
if not HintsWorld.showing() { return }
|
||||
@alloc_ok("into the caller's kept list, at most the rail's hints")
|
||||
for k in 0 .. hints_st.hn_n { push(out, hints_st.hn_top[k]) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,11 +87,13 @@ function update__installed_now(update_st: UpdateState) -> bool {
|
|||
}
|
||||
|
||||
# a page in the player's browser (the site, the changelog)
|
||||
@alloc_ok("a click: the page opened in the browser")
|
||||
export function update_open(url: string) -> void {
|
||||
if update__mac() { System.run(`open "{url}"`) } else { System.run(`cmd /c start "" "{url}"`) }
|
||||
}
|
||||
|
||||
# a path inside a PowerShell single-quoted string: a quote is written twice
|
||||
@alloc_ok("a PowerShell line: once per update started")
|
||||
function update__psq(p: string) -> string { return "'" + Text.replace(p, "'", "''") + "'" }
|
||||
|
||||
function update__powershell() -> string {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ function update__fetch(update_st: mut UpdateState, i: int) -> void {
|
|||
update_st.update__state = UPDATE_FETCH
|
||||
}
|
||||
|
||||
@alloc_ok("once per package downloaded and checked")
|
||||
function update__hash_file(update_st: UpdateState) -> string {
|
||||
if len(update__conf(update_st).scratch) > 0 { return update__conf(update_st).scratch }
|
||||
return Os.temp_dir() + "/ludic-update-hash.txt"
|
||||
|
|
|
|||
|
|
@ -294,6 +294,26 @@ function dr_index(name: pointer) -> int {
|
|||
if n == null { return -1 }
|
||||
return n.ival
|
||||
}
|
||||
# the names the function being walked binds itself - its parameters, lets and loop variables - which
|
||||
# are not the top-level declarations of the same name (a local `bd` is not the function bd)
|
||||
var g_dr_locals: []pointer = new []pointer
|
||||
function dr_is_local(name: pointer) -> bool {
|
||||
var i = 0
|
||||
while i < len(g_dr_locals) { if (g_dr_locals[i] == name) { return true }; i += 1 }
|
||||
return false
|
||||
}
|
||||
function dr_collect_locals(n: Node) -> void {
|
||||
if n == null { return }
|
||||
if (n.kind == S_LET or n.kind == S_FOR or n.kind == N_PARAM) and n.s != null { push(g_dr_locals, n.s) }
|
||||
dr_collect_locals(n.a)
|
||||
dr_collect_locals(n.b)
|
||||
dr_collect_locals(n.c)
|
||||
if n.kids != null {
|
||||
var i = 0
|
||||
while i < len(n.kids) { dr_collect_locals(n.kids[i]); i += 1 }
|
||||
}
|
||||
}
|
||||
|
||||
function dr_walk(n: Node, refs: []int) -> void {
|
||||
if n == null { return }
|
||||
if dr_port_member(n, refs) { return }
|
||||
|
|
@ -322,7 +342,7 @@ function dr_walk(n: Node, refs: []int) -> void {
|
|||
li += 1
|
||||
}
|
||||
}
|
||||
if (n.kind == E_ID or n.kind == E_FNREF) and n.s != null {
|
||||
if (n.kind == E_ID or n.kind == E_FNREF) and n.s != null and not (n.kind == E_ID and dr_is_local(n.s)) {
|
||||
let k = dr_index(n.s)
|
||||
if k >= 0 { push(refs, k) }
|
||||
# a registry entry held in a local and handed on whole reaches the whole registry
|
||||
|
|
@ -471,10 +491,23 @@ function deps_reach(f: pointer) -> void {
|
|||
let nw = dr_words()
|
||||
var j = 0
|
||||
while j < len(g_dr_node) {
|
||||
let refs = new []int
|
||||
var refs = new []int
|
||||
g_dr_rl_name = new []pointer
|
||||
g_dr_rl_reg = new []pointer
|
||||
dr_walk(g_dr_node[j], refs)
|
||||
g_dr_locals = new []pointer
|
||||
let dn = g_dr_node[j]
|
||||
if dn.kind == N_FN or dn.kind == N_SYS or dn.kind == N_BLOCK { dr_collect_locals(dn) }
|
||||
dr_walk(dn, refs)
|
||||
# the generated drain calls every reducer; a reducer is reached from its action's dispatch instead
|
||||
if dn.s != null and (dn.s == "drain_actions") {
|
||||
let kept = new []int
|
||||
var q = 0
|
||||
while q < len(refs) {
|
||||
if not str_starts(g_dr_name[refs[q]], "ludic_reduce__") { push(kept, refs[q]) }
|
||||
q += 1
|
||||
}
|
||||
refs = kept
|
||||
}
|
||||
push(g_dr_refs, refs)
|
||||
let bits = new []int
|
||||
var w = 0
|
||||
|
|
|
|||
|
|
@ -147,6 +147,15 @@ function es_type(e: Node) -> pointer {
|
|||
}
|
||||
return null
|
||||
}
|
||||
# LOCAL: never kept (ESC); and for a push's growth, whose buffer is its list's, the list may not be
|
||||
# one that outlives the frame (HEAP) either. A fresh value that flows into a local also holding kept
|
||||
# memory is still the frame's - storing it anywhere kept would have made it ESC
|
||||
function es_local_class(c: int, grow: bool) -> bool {
|
||||
if (g_es_flag[c] & ES_ESC) != 0 { return false }
|
||||
if grow and (g_es_flag[c] & ES_HEAP) != 0 { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
# a value's class, or -1 when its type holds no reference
|
||||
function es_ref(e: Node) -> int {
|
||||
let v = es_val(e)
|
||||
|
|
@ -655,7 +664,7 @@ function escape_analyse() -> void {
|
|||
let spans = fi < 0 or g_es_fspans[fi]
|
||||
# LOCAL only when it can neither be kept (ESC) nor share storage with what is (HEAP): a push's
|
||||
# growth into a parameter's list is the list's own buffer, and the list may be a state's
|
||||
if c >= 0 and (g_es_flag[c] & (ES_ESC | ES_HEAP)) == 0 and not spans {
|
||||
if c >= 0 and es_local_class(c, g_es_site_grow[s]) and not spans {
|
||||
n.uns = ES_SCRATCH
|
||||
g_es_local_n += 1
|
||||
} else {
|
||||
|
|
@ -674,7 +683,7 @@ function escape_analyse() -> void {
|
|||
let c2 = g_es_site_cls[u]
|
||||
let fi2 = g_es_site_fn[u]
|
||||
let spans2 = fi2 < 0 or g_es_fspans[fi2]
|
||||
if n2.uns == ES_SCRATCH and (c2 < 0 or (g_es_flag[c2] & (ES_ESC | ES_HEAP)) != 0 or spans2) { n2.uns = 0 }
|
||||
if n2.uns == ES_SCRATCH and (c2 < 0 or not es_local_class(c2, g_es_site_grow[u]) or spans2) { n2.uns = 0 }
|
||||
u += 1
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -281,7 +281,14 @@ function emit_fence_entry() -> void {
|
|||
emith("entry:\n")
|
||||
emith(" %on = load i8, ptr @lp_ar_on\n")
|
||||
emith(" %onq = icmp ne i8 %on, 0\n")
|
||||
emith(" br i1 %onq, label %thread, label %heap\n")
|
||||
emith(" br i1 %onq, label %thread, label %start\n")
|
||||
emith("start:\n")
|
||||
emith(" ; the arena starts at its first use, so boot's temporaries are scratch too: dead once the Start\n")
|
||||
emith(" ; handlers return, and their half is started again two frame marks on\n")
|
||||
emith(" call void @lp_ar_frame()\n")
|
||||
emith(" %on2 = load i8, ptr @lp_ar_on\n")
|
||||
emith(" %on2q = icmp ne i8 %on2, 0\n")
|
||||
emith(" br i1 %on2q, label %thread, label %heap\n")
|
||||
emith("thread:\n")
|
||||
emith(" %me = call ptr @pthread_self()\n")
|
||||
emith(" %main = load ptr, ptr @lp_ar_main\n")
|
||||
|
|
|
|||
|
|
@ -325,7 +325,7 @@ function deps_births(f: pointer, seen: []bool) -> void {
|
|||
let inframe = k >= 0 and seen[k]
|
||||
# a scratch site is the arena's whenever the game's frames are running - a frame's code, a click
|
||||
# handler, a reducer - and the heap's only before the first frame: boot's code
|
||||
let scratch = g_arena and site.uns == ES_SCRATCH and not (k >= 0 and boot[k])
|
||||
let scratch = g_arena and site.uns == ES_SCRATCH # boot's too: the arena starts at its first use
|
||||
if not scratch and d.file != null and not is_runtime_file(d.file) and not fr_alloc_ok(d.s) and not fr_consumed(site) {
|
||||
var kind = fr_alloc_kind(site, false)
|
||||
if kind == null { kind = "text" }
|
||||
|
|
|
|||
62278
selfhost/ludicc.seed.ll
62278
selfhost/ludicc.seed.ll
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue