diff --git a/selfhost/backend/emit_deps.ludic b/selfhost/backend/emit_deps.ludic index 03c349d6..0a863d34 100644 --- a/selfhost/backend/emit_deps.ludic +++ b/selfhost/backend/emit_deps.ludic @@ -279,6 +279,12 @@ var g_dr_find_k: []pointer = new []pointer var g_dr_rl_name: []pointer = new []pointer # a local holding one entry of a registry ... var g_dr_rl_reg: []pointer = new []pointer # ... and which registry var g_dr_find_v: []Node = new []Node +# a ROOT reaches through a table of fn values (a step list, a registry of systems), or calls what does: +# it reaches every state by definition, so the ratchet's widest_reach and widest_write_reach leave it out +var g_dr_disp: bool = false # the function being walked reads a fn value out of a table +var g_dr_disp_of: []bool = new []bool +var g_dr_root: []bool = new []bool +var g_dr_hf: []int = new []int # per node: does a global hold fn values (-1 not yet asked) function dr_state_ix(t: pointer) -> int { var i = 0 while i < len(g_state_names) { @@ -328,7 +334,10 @@ function dr_walk(n: Node, refs: []int) -> void { let pre = `ludic_reduce__{n.s}__` var ri = 0 while ri < len(g_dr_name) { - if str_starts(g_dr_name[ri], pre) { push(refs, ri) } + if str_starts(g_dr_name[ri], pre) { + push(refs, ri) + push(g_dr_cur2, ri) + } ri += 1 } } @@ -337,7 +346,10 @@ function dr_walk(n: Node, refs: []int) -> void { while li < len(g_onlisten) { if (g_onlisten[li].s == n.s) { let lk = dr_index(`@On {n.s}#{itoa(li)}`) - if lk >= 0 { push(refs, lk) } + if lk >= 0 { + push(refs, lk) + push(g_dr_cur2, lk) + } } li += 1 } @@ -345,9 +357,14 @@ function dr_walk(n: Node, refs: []int) -> void { 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) } + if k >= 0 and n.kind == E_ID { push(g_dr_cur2, k) } # a call or a read; a `fn f` written is not one + if n.kind == E_ID and k >= 0 and dr_holds_fn(k) { g_dr_disp = true } # a table of fn values read # a registry entry held in a local and handed on whole reaches the whole registry let rl = dr_reg_local(n.s) - if k < 0 and rl != null and dr_index(rl) >= 0 { push(refs, dr_index(rl)) } + if k < 0 and rl != null and dr_index(rl) >= 0 { + push(refs, dr_index(rl)) + push(g_dr_cur2, dr_index(rl)) + } } dr_walk(n.a, refs) dr_walk(n.b, refs) @@ -360,6 +377,77 @@ function dr_walk(n: Node, refs: []int) -> void { } } } +# a global whose value holds `fn f` somewhere (a step list, a registry of systems), asked once +function dr_holds_fn(k: int) -> bool { + while len(g_dr_hf) <= k { push(g_dr_hf, -1) } + if g_dr_hf[k] < 0 { + let d = g_dr_node[k] + g_dr_hf[k] = 0 + if d.kind == N_VAR and dr_has_fnref(d.a, 0) { g_dr_hf[k] = 1 } + } + return g_dr_hf[k] == 1 +} +function dr_has_fnref(n: Node, depth: int) -> bool { + if n == null or depth > 6 { return false } + if n.kind == E_FNREF { return true } + if dr_has_fnref(n.a, depth + 1) or dr_has_fnref(n.b, depth + 1) { return true } + if n.kids != null { + var i = 0 + while i < len(n.kids) { + if dr_has_fnref(n.kids[i], depth + 1) { return true } + i += 1 + } + } + return false +} +# the roots are the dispatchers: a function that reads fn values out of a table (a step list's walker, +# a registry of systems) reaches every state by definition. Every other function is also measured +# WITHOUT going through one, and through its calls only - a `fn f` it writes into a list is supplied its +# states where the list is walked (g_dr_bits2) - so the frame, the boot and a list's builder count only +# their own work, and a refactor of them moves the number +var g_dr_bits2: [][]int = new [][]int +var g_dr_cur2: []int = new []int # the function being walked: its calls and reads, no `fn f` written +var g_dr_refs2: [][]int = new [][]int # per node: those, the edges the second count follows +var g_dr_wbits2: [][]int = new [][]int +function dr_roots(nw: int) -> void { + g_dr_root = new []bool + g_dr_bits2 = new [][]int + g_dr_wbits2 = new [][]int + var j = 0 + while j < len(g_dr_node) { + push(g_dr_root, g_dr_disp_of[j]) + let b = new []int + let wb = new []int + var w = 0 + while w < nw { + push(b, 0) + push(wb, 0) + w += 1 + } + dr_own(g_dr_node[j], b, false) + dr_own(g_dr_node[j], wb, true) + push(g_dr_bits2, b) + push(g_dr_wbits2, wb) + j += 1 + } + var changed = true + while changed { + changed = false + j = 0 + while j < len(g_dr_node) { + let refs = g_dr_refs2[j] + var r = 0 + while r < len(refs) { + if not g_dr_disp_of[refs[r]] { + if dr_join(g_dr_bits2[j], g_dr_bits2[refs[r]], nw) { changed = true } + if dr_join(g_dr_wbits2[j], g_dr_wbits2[refs[r]], nw) { changed = true } + } + r += 1 + } + j += 1 + } + } +} # `Port.member` reaches what that member is bound to (or its default), not every member of the port: # a port asked for its save is not asked for its load function dr_port_member(n: Node, refs: []int) -> bool { @@ -416,7 +504,10 @@ function dr_reg_field(n: Node, refs: []int) -> bool { if e.kind == E_NEW and e.a != null { var j = 0 while j < len(e.a.kids) { - if (e.a.kids[j].s == n.s) { dr_walk(e.a.kids[j].a, refs) } + if (e.a.kids[j].s == n.s) { + if e.a.kids[j].a != null and e.a.kids[j].a.kind == E_FNREF { g_dr_disp = true } # a step list walked + dr_walk(e.a.kids[j].a, refs) + } j += 1 } } @@ -497,7 +588,11 @@ function deps_reach(f: pointer) -> void { 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) } + g_dr_disp = false + g_dr_cur2 = new []int dr_walk(dn, refs) + push(g_dr_disp_of, g_dr_disp) + push(g_dr_refs2, g_dr_cur2) # 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 @@ -542,6 +637,7 @@ function deps_reach(f: pointer) -> void { j += 1 } } + dr_roots(nw) j = 0 while j < len(g_dr_node) { let d = g_dr_node[j] @@ -549,9 +645,12 @@ function deps_reach(f: pointer) -> void { let n = dr_count(g_dr_bits[j]) var pkg = 0 if not (pkg_of_file(d.file) == "") { pkg = 1 } - if n > 0 { deps_line(f, `reach {itoa(n)} {d.s} {d.file}:{itoa(d.line)} {itoa(pkg)}`) } + var root = "0" + if g_dr_root[j] { root = "1" } + # the reach, whether it is a root, and the reach not going through any dispatcher + if n > 0 { deps_line(f, `reach {itoa(n)} {d.s} {d.file}:{itoa(d.line)} {itoa(pkg)} {root} {itoa(dr_count(g_dr_bits2[j]))}`) } let wn = dr_count(g_dr_wbits[j]) - if wn > 0 { deps_line(f, `wreach {itoa(wn)} {d.s} {d.file}:{itoa(d.line)} {itoa(pkg)}`) } + if wn > 0 { deps_line(f, `wreach {itoa(wn)} {d.s} {d.file}:{itoa(d.line)} {itoa(pkg)} {root} {itoa(dr_count(g_dr_wbits2[j]))}`) } } j += 1 } diff --git a/tools/ludic-cli/deps.ludic b/tools/ludic-cli/deps.ludic index ce0f44b7..5d8a8316 100644 --- a/tools/ludic-cli/deps.ludic +++ b/tools/ludic-cli/deps.ludic @@ -30,6 +30,9 @@ # widest_reach: the most states any of them can come to - what it takes, and through calls, `fn f` # and globals holding fn values (a step list, a registry of systems) what those take (0.R4); # widest_write_reach: of those, the most it can come to change - taken `mut` somewhere down (0.R5). +# Both leave out the ROOTS - a function that reads fn values out of a table (a dispatcher), which reaches +# every state by definition - and count every other function without going through one, so the frame +# and the boot count their own work; the roots are listed on a line of their own. var dp_mods: []pointer = null var dp_pkg: []int = null var dp_uses: []pointer = null @@ -52,6 +55,12 @@ var dp_fn_at: []pointer = null # each of the program's own functions: whe var dp_fn_w: []int = null var dp_fn_r: []int = null var dp_fn_x: []int = null # ... and the states it can come to change +var dp_fn_root: []bool = null # ... whether it is a ROOT: reads fn values out of a table (a dispatcher) +var dp_fn_r2: []int = null # ... what it reaches and changes NOT going through a dispatcher +var dp_fn_x2: []int = null +var dp_root_n: int = 0 # the roots, left out of widest_reach / widest_write_reach, and the widest of them +var dp_root_reach: int = 0 +var dp_root_at: pointer = "" var dp_fallocs: []pointer = new []pointer # 25.2: `falloc `, what a frame can allocate var dp_frame_allocs: int = 0 var dp_fkeeps: []pointer = new []pointer # 25.3: `fkeep ` @@ -98,7 +107,8 @@ function dp_load(path: pointer) -> bool { dp_ef = new []int; dp_et = new []int; dp_ec = new []int; dp_en = new []pointer dp_wide_n = 0; dp_wide_at = "" dp_reach_n = 0; dp_reach_at = ""; dp_reach_own = 0 - dp_fn_at = new []pointer; dp_fn_w = new []int; dp_fn_r = new []int; dp_fn_x = new []int + dp_fn_at = new []pointer; dp_fn_w = new []int; dp_fn_r = new []int; dp_fn_x = new []int; dp_fn_root = new []bool; dp_fn_r2 = new []int; dp_fn_x2 = new []int + dp_root_n = 0; dp_root_reach = 0; dp_root_at = "" dp_wreach_n = 0; dp_wreach_at = "" dp_fallocs = new []pointer; dp_frame_allocs = 0 dp_fkeeps = new []pointer; dp_frame_keeps = 0 @@ -143,6 +153,9 @@ function dp_load(path: pointer) -> bool { if len(w) >= 5 and (w[0] == "width" or w[0] == "reach" or w[0] == "wreach") and w[4] == "0" { let k = dp_fn(`{dp_fn_name(w[2])} ({w[3]})`) if w[0] == "width" { dp_fn_w[k] = s_to_int(w[1]) } else if w[0] == "reach" { dp_fn_r[k] = s_to_int(w[1]) } else { dp_fn_x[k] = s_to_int(w[1]) } + if len(w) >= 6 and w[5] == "1" { dp_fn_root[k] = true } + if len(w) >= 7 and w[0] == "reach" { dp_fn_r2[k] = s_to_int(w[6]) } + if len(w) >= 7 and w[0] == "wreach" { dp_fn_x2[k] = s_to_int(w[6]) } } if len(w) >= 5 and w[0] == "write" { push(dp_wowner, w[1]) @@ -160,19 +173,34 @@ function dp_fn(at: pointer) -> int { push(dp_fn_w, 0) push(dp_fn_r, 0) push(dp_fn_x, 0) + push(dp_fn_root, false) + push(dp_fn_r2, 0) + push(dp_fn_x2, 0) return len(dp_fn_at) - 1 } # after the load: the widest reach (a function reaches at least what it takes) +# over the functions that are not roots, each counted without going through a dispatcher: a root (a +# step list's walker, a registry of systems) reaches every state by definition, and what calls one would +# only rise with each new state - no split could lower it. The roots are listed on a line of their own. function dp_reach_best() -> void { for i in 0 .. len(dp_fn_at) { if dp_fn_r[i] < dp_fn_w[i] { dp_fn_r[i] = dp_fn_w[i] } - if dp_fn_r[i] > dp_reach_n { - dp_reach_n = dp_fn_r[i] + if dp_fn_r2[i] < dp_fn_w[i] { dp_fn_r2[i] = dp_fn_w[i] } + if dp_fn_root[i] { + dp_root_n += 1 + if dp_fn_r[i] > dp_root_reach { + dp_root_reach = dp_fn_r[i] + dp_root_at = dp_fn_at[i] + } + continue + } + if dp_fn_r2[i] > dp_reach_n { + dp_reach_n = dp_fn_r2[i] dp_reach_at = dp_fn_at[i] dp_reach_own = dp_fn_w[i] } - if dp_fn_x[i] > dp_wreach_n { - dp_wreach_n = dp_fn_x[i] + if dp_fn_x2[i] > dp_wreach_n { + dp_wreach_n = dp_fn_x2[i] dp_wreach_at = dp_fn_at[i] } } @@ -195,7 +223,9 @@ function dp_top(n: int, by: int) -> void { for i in 0 .. len(order) { let k = order[i] if shown < n and (dp_fn_w[k] > 0 or dp_fn_r[k] > 0) { - print(`{dp_pad(string(dp_fn_w[k]), 5)} {dp_pad(string(dp_fn_r[k]), 7)} {dp_pad(string(dp_fn_x[k]), 7)} {dp_fn_at[k]}`) + var mark = "" + if dp_fn_root[k] { mark = " (root)" } + print(`{dp_pad(string(dp_fn_w[k]), 5)} {dp_pad(string(dp_fn_r[k]), 7)} {dp_pad(string(dp_fn_x[k]), 7)} {dp_fn_at[k]}{mark}`) shown += 1 } } @@ -369,6 +399,7 @@ function dp_print_numbers() -> void { if dp_reach_n > dp_reach_own { print(`the widest reach: {dp_reach_at}, {string(dp_reach_n)} states ({string(dp_reach_n - dp_reach_own)} through calls and fn values it does not take)`) } if dp_wreach_n == 1 { print(`the widest write reach: {dp_wreach_at}, 1 state it can come to change`) } if dp_wreach_n > 1 { print(`the widest write reach: {dp_wreach_at}, {string(dp_wreach_n)} states it can come to change`) } + if dp_root_n > 0 { print(`roots, left out of the reaches (they walk a table of fn values; the rest are counted without going through one): {string(dp_root_n)}, the widest {dp_root_at}, {string(dp_root_reach)} states`) } let layers = dp_layers() if layers != "" { print(`layers: {layers}`) diff --git a/tools/ludic-cli/test.ludic b/tools/ludic-cli/test.ludic index 2bca1f10..b6076ea1 100644 --- a/tools/ludic-cli/test.ludic +++ b/tools/ludic-cli/test.ludic @@ -621,14 +621,15 @@ function actions_deps_case() -> void { let got = capture(`bin/ludic deps examples/actions/modules.ludic 2>&1`) if s_contains(got, "largest_cycle: 1") and s_contains(got, "dependencies: 0") and s_contains(got, "the widest function: reducer Bag on PickUp (") { ok(lbl) } else { bad2(lbl, s_trim(got)) } } -# 0.R4: a step list takes no state and reaches every state its steps take; --widest and --reach -# list the functions by what they take and by what they reach +# 0.R4: a step list takes no state and reaches every state its steps take - a ROOT, left out of +# widest_reach and listed on its own line, and what calls it (one) is counted without going through +# it; --widest and --reach list the functions by what they take and by what they reach, a root marked function deps_reach_case() -> void { - let lbl = "ludic deps: the reach through fn values, --widest N and --reach N" + let lbl = "ludic deps: the reach through fn values, the roots apart, --widest N and --reach N" let got = capture(`bin/ludic deps examples/state/reach.ludic 2>&1`) - if not s_contains(got, "widest_reach: 3") or not s_contains(got, "the widest reach: one (examples/state/reach.ludic:13), 3 states (2 through calls and fn values it does not take)") { bad2(lbl, s_trim(got)); return } + if not s_contains(got, "widest_reach: 1") or not s_contains(got, "roots, left out of the reaches (they walk a table of fn values; the rest are counted without going through one): 1, the widest run_steps (examples/state/reach.ludic:10), 2 states") { bad2(lbl, s_trim(got)); return } let top = capture(`bin/ludic deps examples/state/reach.ludic --reach 2 2>&1`) - if not s_contains(top, " 1 3 3 one (examples/state/reach.ludic:13)\n 0 2 2 run_steps (examples/state/reach.ludic:10)") { bad2(lbl, s_trim(top)); return } + if not s_contains(top, " 1 3 3 one (examples/state/reach.ludic:13)\n 0 2 2 run_steps (examples/state/reach.ludic:10) (root)") { bad2(lbl, s_trim(top)); return } let wide = capture(`bin/ludic deps examples/state/reach.ludic --widest 1 2>&1`) if s_contains(wide, "takes reaches changes function") and s_contains(wide, "one (examples/state/reach.ludic:13)") and not s_contains(wide, "step_a") { ok(lbl) } else { bad2(lbl, s_trim(wide)) } }