# ---- ludic deps --------------------------------------------------------------- # How tangled a program's modules are, from the compiler's own view: it compiles the program # with LUDIC_DEPS set (selfhost/backend/emit_deps.ludic), which records every reference the # visibility pass resolves - from the module it is written in to the module of what it names - # and every assignment to another module's global. A package's modules are listed but not # counted; the program's own modules are. # # ludic deps [file] the numbers (the same five tools/deps.py printed) # ludic deps --graph every module: its declared uses and the edges seen # ludic deps --dot the graph for Graphviz (dot -Tsvg) # ludic deps --writes every write to another module's global, then (as warnings) # every write through a local bound to one (let t = g; t.f = 1) # ludic deps --uses MOD what the other modules use of MOD # ludic deps --check FILE fail if any number is above FILE's # ludic deps --baseline FILE write the numbers to FILE # ludic deps --widest N the N functions that take the most states, and what each reaches # ludic deps --reach N the same, the N that reach the most # ludic deps --wreach N the N that can come to CHANGE the most states # ludic deps --allocs every allocation a frame can come to (25.2), with its chain from a root # ludic deps --keeps what frame code makes and keeps past its frame (25.3), and where # ludic deps --births what is made, kept by nothing and not the arena's: never given back # ludic deps --resources a handle a @creates(Kind) function made, thrown away or never handed on # # dependencies: module pairs (a, b) where a uses something of b, not counting b when b uses no # 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); # cross_writes: assignments to a global of another module; globals_written_from_outside: those # 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); # 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. # english_left: text still written in English where a key belongs (phase 26, the compiler's i18n # checks) - a template's words, a text attribute, a @Text row's English in the data; 0 without a # `lang` line in package.ludic. var dp_mods: []pointer = null var dp_pkg: []int = null var dp_uses: []pointer = null var dp_layer: []pointer = null var dp_ef: []int = null var dp_et: []int = null var dp_ec: []int = null var dp_en: []pointer = null var dp_writes: []pointer = null var dp_aliases: []pointer = null # writes through a local holding another module's global var dp_wowner: []pointer = null var dp_wname: []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 = "" var dp_reach_n: int = 0 # 0.R4: the most states a function reaches, fn values included var dp_reach_at: pointer = "" var dp_reach_own: int = 0 # ... of which it takes itself var dp_fn_at: []pointer = null # each of the program's own functions: where, what it takes, what it reaches 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 ` var dp_frame_keeps: int = 0 var dp_fbirths: []pointer = new []pointer # 25.2: `fbirth ` var dp_birth_leaks: int = 0 var dp_rdrops: []pointer = new []pointer # 25.5e: `rdrop ` var dp_resource_drops: int = 0 var dp_oleaks: []pointer = new []pointer # 25.5e: `oleak ` var dp_owned_leaks: int = 0 var dp_english_left: int = 0 # phase 26: a template's words or a @Text row's English, not yet keys var dp_wreach_n: int = 0 var dp_wreach_at: pointer = "" function dp_words(line: pointer) -> []pointer { let out = new []pointer let n = slen(line) var a = 0 var i = 0 while i <= n { if i == n or line[i] == 32 { if i > a { push(out, sslice(line, a, i)) } a = i + 1 } i += 1 } return out } function dp_mod(name: pointer) -> int { var i = 0 while i < len(dp_mods) { if dp_mods[i] == name { return i } i += 1 } push(dp_mods, name) push(dp_pkg, 1) push(dp_uses, "-") push(dp_layer, "-") return len(dp_mods) - 1 } function dp_load(path: pointer) -> bool { let text = read_file(path) if text == null { return false } 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_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_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 dp_fbirths = new []pointer; dp_birth_leaks = 0 dp_rdrops = new []pointer; dp_resource_drops = 0 dp_oleaks = new []pointer; dp_owned_leaks = 0 dp_english_left = 0 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) for i in 0 .. len(lines) { let w = dp_words(lines[i]) if len(w) >= 4 and w[0] == "module" { let k = dp_mod(w[1]) dp_pkg[k] = 0 if w[2] == "1" { dp_pkg[k] = 1 } dp_uses[k] = w[3] if len(w) >= 5 { dp_layer[k] = w[4] } } } for i in 0 .. len(lines) { let w = dp_words(lines[i]) if len(w) >= 5 and w[0] == "edge" { push(dp_ef, dp_mod(w[1])) push(dp_et, dp_mod(w[2])) push(dp_ec, s_to_int(w[3])) push(dp_en, w[4]) } if len(w) >= 6 and w[0] == "alias" { push(dp_aliases, lines[i]) } if len(w) >= 4 and w[0] == "falloc" { push(dp_fallocs, lines[i]) } if len(w) == 2 and w[0] == "frame_allocs" { dp_frame_allocs = s_to_int(w[1]) } if len(w) >= 5 and w[0] == "fkeep" { push(dp_fkeeps, lines[i]) } if len(w) == 2 and w[0] == "frame_keeps" { dp_frame_keeps = s_to_int(w[1]) } if len(w) >= 4 and w[0] == "fbirth" { push(dp_fbirths, lines[i]) } if len(w) == 2 and w[0] == "birth_leaks" { dp_birth_leaks = s_to_int(w[1]) } if len(w) >= 5 and w[0] == "rdrop" { push(dp_rdrops, lines[i]) } if len(w) == 2 and w[0] == "resource_drops" { dp_resource_drops = s_to_int(w[1]) } if len(w) >= 5 and w[0] == "oleak" { push(dp_oleaks, lines[i]) } if len(w) == 2 and w[0] == "owned_leaks" { dp_owned_leaks = s_to_int(w[1]) } if len(w) == 2 and w[0] == "english_left" { dp_english_left = s_to_int(w[1]) } 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 = `{dp_fn_name(w[2])} ({w[3]})` } 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]) push(dp_wname, w[2]) push(dp_wfrom, w[3]) push(dp_writes, lines[i]) } } return true } # a function of the program's own, by `name (file:line)` function dp_fn(at: pointer) -> int { for i in 0 .. len(dp_fn_at) { if dp_fn_at[i] == at { return i } } push(dp_fn_at, at) 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_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_x2[i] > dp_wreach_n { dp_wreach_n = dp_fn_x2[i] dp_wreach_at = dp_fn_at[i] } } } # --widest N / --reach N: the top N functions by what they take (or by what they reach), both shown function dp_top(n: int, by: int) -> void { let order = new []int for i in 0 .. len(dp_fn_at) { push(order, i) } for a in 1 .. len(order) { let x = order[a] var b = a - 1 while b >= 0 and dp_top_before(x, order[b], by) { order[b + 1] = order[b] b -= 1 } order[b + 1] = x } print("takes reaches changes function") var shown = 0 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) { 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 } } } # by: 0 what it takes, 1 what it reaches, 2 what it can come to change function dp_top_before(x: int, y: int, by: int) -> bool { var kx = dp_fn_w[x] var ky = dp_fn_w[y] var tx = dp_fn_r[x] var ty = dp_fn_r[y] if by == 1 { kx = dp_fn_r[x] ky = dp_fn_r[y] tx = dp_fn_w[x] ty = dp_fn_w[y] } if by == 2 { kx = dp_fn_x[x] ky = dp_fn_x[y] } if kx != ky { return kx > ky } return tx > ty } function dp_pad(s: pointer, n: int) -> pointer { var out = s while slen(out) < n { out = " " + out } return out } # a function as its source names it: a reducer is `reducer Bag on PickUp`, not its symbol function dp_fn_name(n: pointer) -> pointer { if not s_starts(n, "ludic_reduce__") and not s_starts(n, "ludic_rowred__") { return n } let rest = n[14 .. slen(n)] for i in 0 .. slen(rest) - 1 { if rest[i] == '_' and rest[i + 1] == '_' { return `reducer {dp_row_name(rest[i + 2 .. slen(rest)])} on {rest[0 .. i]}` } } return n } # 27.3: a row reducer's symbol says `Deer__in__Herd__deer` for `Deer in Herd.deer`; one a @Machine # wrote ends `__machine__DeerSteps` (27.1) function dp_row_name(s0: pointer) -> pointer { var s = s0 var tail = "" var k = 0 while k + 11 <= slen(s0) { if s0[k .. k + 11] == "__machine__" { s = s0[0 .. k] tail = ` (machine {s0[k + 11 .. slen(s0)]})` break } k += 1 } var out = "" var seen = false var i = 0 while i < slen(s) { if not seen and i + 6 <= slen(s) and s[i .. i + 6] == "__in__" { out = out + " in " seen = true i += 6 } else if seen and i + 2 <= slen(s) and s[i .. i + 2] == "__" { out = out + "." i += 2 } else { out = out + s[i .. i + 1] i += 1 } } return out + tail } function dp_own(k: int) -> bool { return dp_pkg[k] == 0 } # an edge the numbers count: between two of the program's own modules function dp_counted(e: int) -> bool { return dp_own(dp_ef[e]) and dp_own(dp_et[e]) } # an edge inside a declared layer: allowed to go round, so not part of the cycle the numbers count function dp_in_layer(e: int) -> bool { let a = dp_layer[dp_ef[e]] return a != "-" and a == dp_layer[dp_et[e]] } function dp_leaf(k: int) -> bool { for e in 0 .. len(dp_ef) { if dp_ef[e] == k and dp_counted(e) { return false } } return true } # ---- the largest strongly connected set (Tarjan) ---------------------------- var tj_index: []int = null var tj_low: []int = null var tj_on: []int = null var tj_stack: []int = null var tj_sp: int = 0 var tj_n: int = 0 var tj_best: []int = null var dp_skip_layers: bool = true # the largest cycle leaves out the edges inside a declared layer function tj_visit(v: int) -> void { tj_index[v] = tj_n tj_low[v] = tj_n tj_n += 1 tj_stack[tj_sp] = v tj_sp += 1 tj_on[v] = 1 for e in 0 .. len(dp_ef) { if dp_ef[e] == v and dp_counted(e) and not (dp_skip_layers and dp_in_layer(e)) { let w = dp_et[e] if tj_index[w] < 0 { tj_visit(w) if tj_low[w] < tj_low[v] { tj_low[v] = tj_low[w] } } else if tj_on[w] == 1 and tj_index[w] < tj_low[v] { tj_low[v] = tj_index[w] } } } if tj_low[v] == tj_index[v] { let comp = new []int while true { tj_sp -= 1 let w = tj_stack[tj_sp] tj_on[w] = 0 push(comp, w) if w == v { break } } if len(comp) > len(tj_best) { tj_best = comp } } } function dp_largest_cycle() -> []int { let n = len(dp_mods) tj_index = new []int; tj_low = new []int; tj_on = new []int; tj_stack = new []int for i in 0 .. n { push(tj_index, -1) push(tj_low, 0) push(tj_on, 0) push(tj_stack, 0) } tj_sp = 0 tj_n = 0 tj_best = new []int for v in 0 .. n { if dp_own(v) and tj_index[v] < 0 { tj_visit(v) } } return tj_best } function dp_sorted_names(ks: []int) -> pointer { let names = new []pointer for i in 0 .. len(ks) { push(names, dp_mods[ks[i]]) } # a handful to a few hundred: an insertion sort var i = 1 while i < len(names) { let x = names[i] var j = i - 1 while j >= 0 and s_less(x, names[j]) { names[j + 1] = names[j] j -= 1 } names[j + 1] = x i += 1 } var out = "" for k in 0 .. len(names) { if k > 0 { out = out + " " } out = out + names[k] } return out } function s_less(a: pointer, b: pointer) -> bool { var i = 0 while a[i] != 0 and b[i] != 0 { if a[i] != b[i] { return a[i] < b[i] } i += 1 } return a[i] == 0 and b[i] != 0 } # ---- the numbers ------------------------------------------------------------ var dp_names: []pointer = null var dp_vals: []int = null var dp_cycle: []int = null var dp_cycle_all: []int = null # the same with the layers' own edges counted function dp_numbers() -> void { dp_names = ["modules", "dependencies", "largest_cycle", "cross_writes", "globals_written_from_outside", "widest_function", "widest_reach", "widest_write_reach", "frame_allocs", "frame_keeps", "birth_leaks", "resource_drops", "owned_leaks", "english_left"] var mods = 0 for k in 0 .. len(dp_mods) { if dp_own(k) { mods += 1 } } var deps = 0 for e in 0 .. len(dp_ef) { if dp_counted(e) and not dp_leaf(dp_et[e]) { deps += 1 } } dp_skip_layers = false dp_cycle_all = dp_largest_cycle() dp_skip_layers = true dp_cycle = dp_largest_cycle() var writes = 0 let seen = new []pointer for w in 0 .. len(dp_writes) { let ok = dp_own(dp_mod(dp_wowner[w])) if ok { writes += 1 let key = `{dp_wowner[w]}.{dp_wname[w]}` var dup = false for s in 0 .. len(seen) { if seen[s] == key { dup = true } } if not dup { push(seen, key) } } } dp_reach_best() dp_vals = [mods, deps, len(dp_cycle), writes, len(seen), dp_wide_n, dp_reach_n, dp_wreach_n, dp_frame_allocs, dp_frame_keeps, dp_birth_leaks, dp_resource_drops, dp_owned_leaks, dp_english_left] } # ---- the command -------------------------------------------------------------- function dp_print_numbers() -> void { 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)}`) 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`) } 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}`) print(`largest cycle counting the layers' own edges: {string(len(dp_cycle_all))}`) } } # FILE: `name value` lines, `#` comments; every number but modules may only go down function dp_check(path: pointer) -> int { let text = read_file(path) if text == null { err(`ludic deps: cannot read {path}\n`); return 2 } let lines = split_lines(text) var worse = 0 var better = 0 for i in 0 .. len(dp_names) { var base = -1 for l in 0 .. len(lines) { let w = dp_words(lines[l]) if len(w) == 2 and w[0] == dp_names[i] { base = s_to_int(w[1]) } } var shown = "-" if base >= 0 { shown = string(base) } var mark = "" if i > 0 and base >= 0 and dp_vals[i] > base { mark = " WORSE" worse += 1 } if i > 0 and base >= 0 and dp_vals[i] < base { better += 1 } print(` deps: {dp_names[i]} {string(dp_vals[i])} (baseline {shown}){mark}`) } if worse > 0 { print("deps: the code is more tangled than the baseline - see ludic deps --writes and --graph") return 1 } if better > 0 { print(`deps: better than the baseline - lower it: ludic deps --baseline {path}`) } print(" deps: OK") return 0 } function dp_baseline(path: pointer) -> int { var s = "# ludic deps --check: each number may only go down\n" for i in 1 .. len(dp_names) { s = s + `{dp_names[i]} {string(dp_vals[i])}\n` } if not write_file(path, s) { err(`ludic deps: cannot write {path}\n`); return 2 } print(`deps: wrote {path}`) return 0 } # "app (11: core flow ...), ..." for the declared layers, "" when there are none function dp_layers() -> pointer { var out = "" let seen = new []pointer for k in 0 .. len(dp_mods) { let l = dp_layer[k] if l != "-" { var dup = false for s in 0 .. len(seen) { if seen[s] == l { dup = true } } if not dup { push(seen, l) let members = new []int for m in 0 .. len(dp_mods) { if dp_layer[m] == l { push(members, m) } } if out != "" { out = out + "; " } out = out + `{l} ({string(len(members))}: {dp_sorted_names(members)})` } } } return out } function dp_declared(from: int, to: int) -> bool { if dp_layer[from] != "-" and dp_layer[from] == dp_layer[to] { return true } let u = dp_uses[from] if u == "-" { return false } return s_contains(u, `,{dp_mods[to]},`) } # ",a,b," as "a, b" ("nothing" for ",") function dp_list(u: pointer) -> pointer { let n = slen(u) if n <= 1 { return "nothing" } var out = "" var i = 1 while i < n - 1 { if u[i] == 44 { out = out + ", " } else { out = out + sslice(u, i, i + 1) } i += 1 } return out } function dp_graph() -> void { for k in 0 .. len(dp_mods) { if dp_own(k) { var uses = "(no uses line)" if dp_uses[k] != "-" { uses = `uses {dp_list(dp_uses[k])}` } if dp_layer[k] != "-" { uses = `layer {dp_layer[k]}, {uses}` } var to = "" for e in 0 .. len(dp_ef) { if dp_ef[e] == k { var tag = "" if dp_uses[k] != "-" and not dp_declared(k, dp_et[e]) { tag = "!" } if dp_pkg[dp_et[e]] == 1 { tag = tag + "(pkg)" } to = to + ` {dp_mods[dp_et[e]]}{tag}:{string(dp_ec[e])}` } } print(`{dp_mods[k]} {uses} ->{to}`) } } } function dp_in(xs: []int, k: int) -> bool { for i in 0 .. len(xs) { if xs[i] == k { return true } } return false } function dp_dot() -> void { print("digraph modules {") print(" rankdir=LR; node [shape=box, fontname=Helvetica];") for k in 0 .. len(dp_mods) { if dp_own(k) { var style = "" if dp_in(dp_cycle, k) { style = ", style=filled, fillcolor=\"#f6d6d6\"" } print(` "{dp_mods[k]}" [label="{dp_mods[k]}"{style}];`) } } for e in 0 .. len(dp_ef) { if dp_counted(e) { let f = dp_ef[e] let t = dp_et[e] var style = "" if dp_uses[f] != "-" and not dp_declared(f, t) { style = ", color=red" } if dp_in(dp_cycle, f) and dp_in(dp_cycle, t) { style = style + ", penwidth=2" } print(` "{dp_mods[f]}" -> "{dp_mods[t]}" [label="{string(dp_ec[e])}"{style}];`) } } print("}") } function dp_uses_of(target: pointer) -> void { for e in 0 .. len(dp_ef) { if dp_mods[dp_et[e]] == target { print(`{dp_mods[dp_ef[e]]} {string(dp_ec[e])} uses, e.g. {dp_en[e]}`) } } } function cmd_deps() -> int { var src = "" var mode = "numbers" var arg2 = "" var ai = 2 while ai < arg_count() { let a = arg(ai) if a == "--dot" { mode = "dot" } else if a == "--graph" { mode = "graph" } else if a == "--writes" { mode = "writes" } else if a == "--allocs" { mode = "allocs" } else if a == "--keeps" { mode = "keeps" } else if a == "--births" { mode = "births" } else if a == "--resources" { mode = "resources" } else if a == "--owned" { mode = "resources" } else if a == "--uses" or a == "--check" or a == "--baseline" or a == "--widest" or a == "--reach" or a == "--wreach" { if ai + 1 >= arg_count() { err(`ludic deps: {a} needs an argument\n`); return 2 } mode = sslice(a, 2, slen(a)) ai += 1 arg2 = arg(ai) } else if a[0] == '-' { err(`ludic deps: unknown option {a}\n`) err(" usage: ludic deps [file] [--graph | --dot | --writes | --uses MOD | --check FILE | --baseline FILE | --widest N | --reach N | --wreach N]\n") return 2 } else { src = a } ai += 1 } let entry = find_entry(src) if entry == "" { return no_entry() } ensure_ludicc() let graph = tmp_path("deps.txt") let ll = tmp_path("deps.ll") # the compiler records the graph as it resolves names; a module rule broken on the way is listed # (LUDIC_VIS_REPORT) rather than stopping the count if not shq(`LUDIC_DEPS={graph} LUDIC_VIS_REPORT=1 {ludicc()} --headless{unsafe_flag()} {entry} --emit-llvm -o {ll} 2> {tmp_path("deps.err")}`) { err(`ludic deps: {entry} did not compile:\n`) err(capture(`grep -i error {tmp_path("deps.err")} | head -5`)) return 1 } shell(`rm -f {ll}`) if not dp_load(graph) { err("ludic deps: the compiler wrote no graph (is it older than ludic deps?)\n"); return 1 } dp_numbers() if mode == "dot" { dp_dot(); return 0 } if mode == "graph" { dp_graph(); return 0 } if mode == "writes" { for w in 0 .. len(dp_writes) { print(sslice(dp_writes[w], 6, slen(dp_writes[w]))) } # a warning, not a number: a write through a local bound to the global (not counted above) for a in 0 .. len(dp_aliases) { let w = dp_words(dp_aliases[a]) print(`warning: {w[1]} {w[2]} {w[3]} {w[4]} through the local {w[5]}`) } return 0 } if mode == "uses" { dp_uses_of(arg2); return 0 } if mode == "allocs" { # 25.2: every allocation a frame can come to, with its chain for a in 0 .. len(dp_fallocs) { let w = dp_words(dp_fallocs[a]) print(`frame alloc: {w[2]} {w[1]} {w[3]}`) } print(`frame_allocs: {string(dp_frame_allocs)}`) return 0 } if mode == "keeps" { # 25.3: what frame code makes and keeps, and where it is kept for a in 0 .. len(dp_fkeeps) { let w = dp_words(dp_fkeeps[a]) print(`frame keep: {w[2]} {w[1]} kept at {w[3]} {w[4]}`) } print(`frame_keeps: {string(dp_frame_keeps)}`) return 0 } if mode == "births" { # 25.2: made, kept by nothing, and on the heap: never given back for a in 0 .. len(dp_fbirths) { let w = dp_words(dp_fbirths[a]) var where = "" if len(w) >= 5 { where = w[4] } print(`leak at birth: {w[2]} {w[1]} in {w[3]} {where}`) } print(`birth_leaks: {string(dp_birth_leaks)}`) return 0 } if mode == "resources" { # 25.5e: a handle made (@creates) and never given back for a in 0 .. len(dp_rdrops) { let w = dp_words(dp_rdrops[a]) print(`resource dropped: {w[1]} at {w[2]} in {w[3]}: {w[4]}`) } print(`resource_drops: {string(dp_resource_drops)}`) for a in 0 .. len(dp_oleaks) { let w = dp_words(dp_oleaks[a]) print(`owned field lost: {w[1]} at {w[2]} in {w[3]}, which releases {w[4]}`) } print(`owned_leaks: {string(dp_owned_leaks)}`) return 0 } if mode == "widest" { dp_top(s_to_int(arg2), 0); return 0 } if mode == "reach" { dp_top(s_to_int(arg2), 1); return 0 } if mode == "wreach" { dp_top(s_to_int(arg2), 2); return 0 } if mode == "check" { return dp_check(arg2) } if mode == "baseline" { return dp_baseline(arg2) } dp_print_numbers() return 0 }