Files
cell/slots.ce
2026-02-26 08:13:18 -06:00

304 lines
7.9 KiB
Plaintext

// slots.ce — slot data flow / use-def chains
//
// Usage:
// cell slots --fn <N|name> <file> Slot summary for function
// cell slots --slot <N> --fn <N|name> <file> Trace slot N in function
// cell slots <file> Slot summary for all functions
var shop = use("internal/shop")
var pad_right = function(s, w) {
var r = s
while (length(r) < w) {
r = r + " "
}
return r
}
var fmt_val = function(v) {
if (is_null(v)) return "null"
if (is_number(v)) return text(v)
if (is_text(v)) return `"${v}"`
if (is_object(v)) return text(v)
if (is_logical(v)) return v ? "true" : "false"
return text(v)
}
// DEF/USE functions — populated from streamline's log hooks
var sl_get_defs = null
var sl_get_uses = null
var run = function() {
var filename = null
var fn_filter = null
var slot_filter = null
var i = 0
var compiled = null
var type_info = {}
var sl_log = null
var td = null
var main_name = null
var fi = 0
var func = null
var fname = null
while (i < length(args)) {
if (args[i] == '--fn') {
i = i + 1
fn_filter = args[i]
} else if (args[i] == '--slot') {
i = i + 1
slot_filter = number(args[i])
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell slots [--fn <N|name>] [--slot <N>] <file>")
log.console("")
log.console(" --fn <N|name> Filter to function by index or name")
log.console(" --slot <N> Trace a specific slot")
return null
} else if (!starts_with(args[i], '-')) {
filename = args[i]
}
i = i + 1
}
if (!filename) {
log.console("Usage: cell slots [--fn <N|name>] [--slot <N>] <file>")
return null
}
compiled = shop.mcode_file(filename)
// Try to get type info from streamline
var get_type_info = function() {
var mcode_copy = shop.mcode_file(filename)
var streamline = use("streamline")
var ti = 0
sl_log = {
passes: [],
events: null,
type_deltas: [],
request_def_use: true
}
streamline(mcode_copy, sl_log)
if (sl_log.get_slot_defs != null) {
sl_get_defs = sl_log.get_slot_defs
sl_get_uses = sl_log.get_slot_uses
}
if (sl_log.type_deltas != null) {
ti = 0
while (ti < length(sl_log.type_deltas)) {
td = sl_log.type_deltas[ti]
if (td.fn != null) {
type_info[td.fn] = td.slot_types
}
ti = ti + 1
}
}
return null
} disruption {
// Type info is optional
}
get_type_info()
var fn_matches = function(index, name) {
var match = null
if (fn_filter == null) return true
if (index >= 0 && fn_filter == text(index)) return true
if (name != null) {
match = search(name, fn_filter)
if (match != null && match >= 0) return true
}
return false
}
var analyze_function = function(func, name, index) {
var nr_args = func.nr_args != null ? func.nr_args : 0
var nr_slots = func.nr_slots != null ? func.nr_slots : 0
var instrs = func.instructions
var defs = {}
var uses = {}
var first_def = {}
var first_def_op = {}
var events = []
var pc = 0
var ii = 0
var instr = null
var op = null
var n = 0
var def_positions = null
var use_positions = null
var di = 0
var ui = 0
var slot_num = null
var operand_val = null
var parts = null
var j = 0
var operands = null
var slot_types = null
var type_key = null
var ei = 0
var evt = null
var found = false
var line_str = null
var si = 0
var slot_key = null
var d_count = 0
var u_count = 0
var t = null
var first = null
var dead_marker = null
if (instrs == null) instrs = []
// Walk instructions, build def/use chains
ii = 0
while (ii < length(instrs)) {
instr = instrs[ii]
if (is_text(instr)) {
ii = ii + 1
continue
}
if (!is_array(instr)) {
ii = ii + 1
continue
}
op = instr[0]
n = length(instr)
def_positions = sl_get_defs(instr)
use_positions = sl_get_uses(instr)
di = 0
while (di < length(def_positions)) {
operand_val = instr[def_positions[di]]
if (is_number(operand_val)) {
slot_num = text(operand_val)
if (!defs[slot_num]) defs[slot_num] = 0
defs[slot_num] = defs[slot_num] + 1
if (first_def[slot_num] == null) {
first_def[slot_num] = pc
first_def_op[slot_num] = op
}
events[] = {kind: "DEF", slot: operand_val, pc: pc, instr: instr}
}
di = di + 1
}
ui = 0
while (ui < length(use_positions)) {
operand_val = instr[use_positions[ui]]
if (is_number(operand_val)) {
slot_num = text(operand_val)
if (!uses[slot_num]) uses[slot_num] = 0
uses[slot_num] = uses[slot_num] + 1
events[] = {kind: "USE", slot: operand_val, pc: pc, instr: instr}
}
ui = ui + 1
}
pc = pc + 1
ii = ii + 1
}
// Get type info for this function
type_key = func.name != null ? func.name : name
if (type_info[type_key]) {
slot_types = type_info[type_key]
}
// --slot mode: show trace
if (slot_filter != null) {
log.compile(`\n=== slot ${text(slot_filter)} in ${name} ===`)
ei = 0
found = false
while (ei < length(events)) {
evt = events[ei]
if (evt.slot == slot_filter) {
found = true
n = length(evt.instr)
parts = []
j = 1
while (j < n - 2) {
parts[] = fmt_val(evt.instr[j])
j = j + 1
}
operands = text(parts, ", ")
line_str = evt.instr[n - 2] != null ? `:${text(evt.instr[n - 2])}` : ""
log.compile(` ${pad_right(evt.kind, 5)}pc ${pad_right(text(evt.pc) + ":", 6)} ${pad_right(evt.instr[0], 15)}${pad_right(operands, 30)}${line_str}`)
}
ei = ei + 1
}
if (!found) {
log.compile(" (no activity)")
}
return null
}
// Summary mode
log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`)
log.compile(` ${pad_right("slot", 8)}${pad_right("defs", 8)}${pad_right("uses", 8)}${pad_right("type", 12)}first-def`)
si = 0
while (si < nr_slots) {
slot_key = text(si)
d_count = defs[slot_key] != null ? defs[slot_key] : 0
u_count = uses[slot_key] != null ? uses[slot_key] : 0
// Skip slots with no activity unless they're args or have type info
if (d_count == 0 && u_count == 0 && si >= nr_args + 1) {
si = si + 1
continue
}
t = "-"
if (slot_types != null && slot_types[slot_key] != null) {
t = slot_types[slot_key]
}
first = ""
if (si == 0) {
first = "(this)"
} else if (si > 0 && si <= nr_args) {
first = `(arg ${text(si - 1)})`
} else if (first_def[slot_key] != null) {
first = `pc ${text(first_def[slot_key])}: ${first_def_op[slot_key]}`
}
dead_marker = ""
if (d_count > 0 && u_count == 0 && si > nr_args) {
dead_marker = " <- dead"
}
log.compile(` ${pad_right("s" + slot_key, 8)}${pad_right(text(d_count), 8)}${pad_right(text(u_count), 8)}${pad_right(t, 12)}${first}${dead_marker}`)
si = si + 1
}
return null
}
// Process functions
main_name = compiled.name != null ? compiled.name : "<main>"
if (compiled.main != null) {
if (fn_matches(-1, main_name)) {
analyze_function(compiled.main, main_name, -1)
}
}
if (compiled.functions != null) {
fi = 0
while (fi < length(compiled.functions)) {
func = compiled.functions[fi]
fname = func.name != null ? func.name : "<anonymous>"
if (fn_matches(fi, fname)) {
analyze_function(func, `[${text(fi)}] ${fname}`, fi)
}
fi = fi + 1
}
}
return null
}
run()
$stop()