// slots.ce — slot data flow / use-def chains // // Usage: // cell slots --fn Slot summary for function // cell slots --slot --fn Trace slot N in function // cell slots 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 ] [--slot ] ") log.console("") log.console(" --fn Filter to function by index or name") log.console(" --slot 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 ] [--slot ] ") 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 : "
" 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 : "" if (fn_matches(fi, fname)) { analyze_function(func, `[${text(fi)}] ${fname}`, fi) } fi = fi + 1 } } return null } run() $stop()