feat(cli): 0.R2 - ludic deps reports the widest function and ratchets it

widest_function: the most states any function or entry point of the program's own takes, with
which one; --check holds it like the other numbers and --baseline writes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 19:13:46 +03:00
parent 808c4a6f7a
commit 55c1de8734
5 changed files with 64933 additions and 59230 deletions

View file

@ -116,9 +116,46 @@ function deps_flush() -> void {
deps_line(f, g_dp_aliases[i]) deps_line(f, g_dp_aliases[i])
i += 1 i += 1
} }
deps_widths(f)
file_close(f) file_close(f)
} }
# 0.R2: how many states each function and entry point takes - `width <n> <name> <file>:<line> <pkg>`,
# the program's own code only (not the runtime's)
function deps_widths(f: pointer) -> void {
var i = 0
while i < len(prog) {
let d = prog[i]
if d.file != null and not is_runtime_file(d.file) {
var n = 0
var name = d.s
if d.kind == N_FN {
var k = 0
while k < len(d.kids) {
if d.kids[k].kind == N_PARAM and is_state_ty(d.kids[k].ty) { n += 1 }
k += 1
}
}
if (d.kind == N_MAIN or d.kind == N_SYS) and d.a != null {
if d.kind == N_MAIN { name = "entry" }
n = deps_body_states(d.a)
}
var pkg = 0
if not (pkg_of_file(d.file) == "") { pkg = 1 }
if n > 0 { deps_line(f, `width {itoa(n)} {name} {d.file}:{itoa(d.line)} {itoa(pkg)}`) }
}
i += 1
}
}
function deps_body_states(b: Node) -> int {
var n = 0
var k = 0
while k < len(b.kids) and b.kids[k].tps != null and (b.kids[k].tps == "state") {
n += 1
k += 1
}
return n
}
# A local that holds another module's global record (or a piece of one) - `let t = thing_cur`, # A local that holds another module's global record (or a piece of one) - `let t = thing_cur`,
# `let s = slots[i]` - writes into that module's state when it is written through: `t.used = 1`. # `let s = slots[i]` - writes into that module's state when it is written through: `t.used = 1`.
# That is caught here for a local bound straight from the global (or from such a local) in the # That is caught here for a local bound straight from the global (or from such a local) in the

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,8 @@
# other module (a leaf); largest_cycle: the largest set of modules that all reach each other, not # other module (a leaf); largest_cycle: the largest set of modules that all reach each other, not
# counting the edges inside a declared layer (which may go round by design - reported beside it); # counting the edges inside a declared layer (which may go round by design - reported beside it);
# cross_writes: assignments to a global of another module; globals_written_from_outside: those # cross_writes: assignments to a global of another module; globals_written_from_outside: those
# globals, each counted once. # globals, each counted once; widest_function: the most states any function or entry point of the
# program's own takes (0.R2 - a reducer takes one, and the dispatchers should come down to a few).
var dp_mods: []pointer = null var dp_mods: []pointer = null
var dp_pkg: []int = null var dp_pkg: []int = null
var dp_uses: []pointer = null var dp_uses: []pointer = null
@ -32,6 +33,8 @@ var dp_aliases: []pointer = null # writes through a local holding another m
var dp_wowner: []pointer = null var dp_wowner: []pointer = null
var dp_wname: []pointer = null var dp_wname: []pointer = null
var dp_wfrom: []pointer = null var dp_wfrom: []pointer = null
var dp_wide_n: int = 0 # 0.R2: the widest function's state parameters, and which
var dp_wide_at: pointer = ""
function dp_words(line: pointer) -> []pointer { function dp_words(line: pointer) -> []pointer {
let out = new []pointer let out = new []pointer
@ -64,6 +67,7 @@ function dp_load(path: pointer) -> bool {
if text == null { return false } if text == null { return false }
dp_mods = new []pointer; dp_pkg = new []int; dp_uses = new []pointer; dp_layer = new []pointer dp_mods = new []pointer; dp_pkg = new []int; dp_uses = new []pointer; dp_layer = new []pointer
dp_ef = new []int; dp_et = new []int; dp_ec = new []int; dp_en = new []pointer dp_ef = new []int; dp_et = new []int; dp_ec = new []int; dp_en = new []pointer
dp_wide_n = 0; dp_wide_at = ""
dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer dp_writes = new []pointer; dp_aliases = new []pointer; dp_wowner = new []pointer; dp_wname = new []pointer; dp_wfrom = new []pointer
let lines = split_lines(text) let lines = split_lines(text)
for i in 0 .. len(lines) { for i in 0 .. len(lines) {
@ -85,6 +89,10 @@ function dp_load(path: pointer) -> bool {
push(dp_en, w[4]) push(dp_en, w[4])
} }
if len(w) >= 6 and w[0] == "alias" { push(dp_aliases, lines[i]) } if len(w) >= 6 and w[0] == "alias" { push(dp_aliases, lines[i]) }
if len(w) >= 5 and w[0] == "width" and w[4] == "0" and s_to_int(w[1]) > dp_wide_n {
dp_wide_n = s_to_int(w[1])
dp_wide_at = `{w[2]} ({w[3]})`
}
if len(w) >= 5 and w[0] == "write" { if len(w) >= 5 and w[0] == "write" {
push(dp_wowner, w[1]) push(dp_wowner, w[1])
push(dp_wname, w[2]) push(dp_wname, w[2])
@ -196,7 +204,7 @@ var dp_vals: []int = null
var dp_cycle: []int = null var dp_cycle: []int = null
var dp_cycle_all: []int = null # the same with the layers' own edges counted var dp_cycle_all: []int = null # the same with the layers' own edges counted
function dp_numbers() -> void { function dp_numbers() -> void {
dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside"] dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function"]
var mods = 0 var mods = 0
for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } } for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } }
var deps = 0 var deps = 0
@ -217,13 +225,15 @@ function dp_numbers() -> void {
if not dup { push(seen, key) } if not dup { push(seen, key) }
} }
} }
dp_vals = [mods, deps, len(dp_cycle), writes, len(seen)] dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n]
} }
# ---- the command -------------------------------------------------------------- # ---- the command --------------------------------------------------------------
function dp_print_numbers() -> void { function dp_print_numbers() -> void {
for i in 0 .. len(dp_names) { print(`{dp_names[i]}: {string(dp_vals[i])}`) } for i in 0 .. len(dp_names) { print(`{dp_names[i]}: {string(dp_vals[i])}`) }
print(`in the largest cycle: {dp_sorted_names(dp_cycle)}`) print(`in the largest cycle: {dp_sorted_names(dp_cycle)}`)
if dp_wide_n == 1 { print(`the widest function: {dp_wide_at}, 1 state`) }
if dp_wide_n > 1 { print(`the widest function: {dp_wide_at}, {string(dp_wide_n)} states`) }
let layers = dp_layers() let layers = dp_layers()
if layers != "" { if layers != "" {
print(`layers: {layers}`) print(`layers: {layers}`)

View file

@ -476,13 +476,19 @@ function deps_case() -> void {
let lbl = "ludic deps: the numbers, --check against a baseline, --dot, --writes" let lbl = "ludic deps: the numbers, --check against a baseline, --dot, --writes"
let p = "examples/modules/tangle.ludic" let p = "examples/modules/tangle.ludic"
let got = capture(`bin/ludic deps {p} 2>&1`) let got = capture(`bin/ludic deps {p} 2>&1`)
let want = "modules: 3\ndependencies: 2\nlargest_cycle: 2\ncross_writes: 1\nglobals_written_from_outside: 1\nin the largest cycle: a b\n" let want = "modules: 3\ndependencies: 2\nlargest_cycle: 2\ncross_writes: 1\nglobals_written_from_outside: 1\nwidest_function: 1\nin the largest cycle: a b\nthe widest function: a_run (examples/modules/tangle/a/index.ludic:3), 1 state\n"
if not (s_trim(got) == s_trim(want)) { bad2(lbl, `said [{s_trim(got)}]`); return } if not (s_trim(got) == s_trim(want)) { bad2(lbl, `said [{s_trim(got)}]`); return }
let base = `{tmp_dir()}/deps-base.txt` let base = `{tmp_dir()}/deps-base.txt`
if not shq(`bin/ludic deps {p} --baseline {base} > /dev/null`) { bad2(lbl, "--baseline failed"); return } if not shq(`bin/ludic deps {p} --baseline {base} > /dev/null`) { bad2(lbl, "--baseline failed"); return }
if not shq(`bin/ludic deps {p} --check {base} > /dev/null`) { bad2(lbl, "--check failed against its own baseline"); return } if not shq(`bin/ludic deps {p} --check {base} > /dev/null`) { bad2(lbl, "--check failed against its own baseline"); return }
write_file(base, "dependencies 1\nlargest_cycle 2\ncross_writes 1\nglobals_written_from_outside 1\n") write_file(base, "dependencies 1\nlargest_cycle 2\ncross_writes 1\nglobals_written_from_outside 1\n")
if shq(`bin/ludic deps {p} --check {base} > /dev/null`) { bad2(lbl, "--check passed with more dependencies than the baseline"); return } if shq(`bin/ludic deps {p} --check {base} > /dev/null`) { bad2(lbl, "--check passed with more dependencies than the baseline"); return }
# 0.R2: the widest function is a ratchet too
write_file(base, "dependencies 2\nlargest_cycle 2\ncross_writes 1\nglobals_written_from_outside 1\nwidest_function 0\n")
if shq(`bin/ludic deps {p} --check {base} > {tmp_dir()}/wide.out`) { bad2(lbl, "--check passed with a wider function than the baseline"); return }
if not shq(`grep -q 'widest_function 1 (baseline 0) WORSE' {tmp_dir()}/wide.out`) { bad2(lbl, capture(`cat {tmp_dir()}/wide.out`)); return }
let pk = capture(`bin/ludic deps examples/actions/pack.ludic 2>&1`)
if not s_contains(pk, "the widest function: entry (examples/actions/pack.ludic:29), 2 states") { bad2(lbl, `widest: [{s_trim(pk)}]`); return }
if not shq(`bin/ludic deps {p} --dot | grep -q '"a" -> "b"'`) { bad2(lbl, "--dot has no a -> b edge"); return } if not shq(`bin/ludic deps {p} --dot | grep -q '"a" -> "b"'`) { bad2(lbl, "--dot has no a -> b edge"); return }
if not shq(`bin/ludic deps {p} --writes | grep -q 'b b_count a examples/modules/tangle/a/index.ludic:4'`) { bad2(lbl, "--writes misses a's write to b_count"); return } if not shq(`bin/ludic deps {p} --writes | grep -q 'b b_count a examples/modules/tangle/a/index.ludic:4'`) { bad2(lbl, "--writes misses a's write to b_count"); return }
if not shq(`bin/ludic deps {p} --writes | grep -q 'warning: b b_rec a examples/modules/tangle/a/index.ludic:12 through the local r'`) { bad2(lbl, "--writes misses the write through a local"); return } if not shq(`bin/ludic deps {p} --writes | grep -q 'warning: b b_rec a examples/modules/tangle/a/index.ludic:12 through the local r'`) { bad2(lbl, "--writes misses the write through a local"); return }