ludic/tools/ludic-cli/deps.ludic
Orkuncakilkaya e009ea313b resources (25.5e, first half): @creates(Kind) / @releases(Kind), and resource_drops
A function marked @creates(PhysShape) makes a handle one marked @releases(PhysShape) gives back.
ludic deps --resources lists every creating call whose handle is thrown away, or bound to a local
that is never released, passed on, stored or returned, and resource_drops is a number --check
ratchets. A test: a thrown-away create and one bound and never handed on are the two found; one
stored in a state and one released are not. A record's owned fields and a borrow form (a shape
used by several scaled ones) are the second half, with a resource type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:54:27 +03:00

595 lines
23 KiB
Text

# ---- 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).
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_fallocs: []pointer = new []pointer # 25.2: `falloc <kind> <file:line> <chain>`, what a frame can allocate
var dp_frame_allocs: int = 0
var dp_fkeeps: []pointer = new []pointer # 25.3: `fkeep <kind> <file:line> <kept at> <chain>`
var dp_frame_keeps: int = 0
var dp_fbirths: []pointer = new []pointer # 25.2: `fbirth <kind> <file:line> <fn> <where>`
var dp_birth_leaks: int = 0
var dp_rdrops: []pointer = new []pointer # 25.5e: `rdrop <kind> <file:line> <fn> <how>`
var dp_resource_drops: int = 0
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_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_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] == "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) >= 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)
return len(dp_fn_at) - 1
}
# after the load: the widest reach (a function reaches at least what it takes)
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]
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]
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) {
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]}`)
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__") { return n }
let rest = n[14 .. slen(n)]
for i in 0 .. slen(rest) - 1 {
if rest[i] == '_' and rest[i + 1] == '_' { return `reducer {rest[i + 2 .. slen(rest)]} on {rest[0 .. i]}` }
}
return n
}
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"]
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]
}
# ---- 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`) }
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 == "--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)}`)
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
}