// 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) } // Classify instruction operands as DEF or USE // Returns {defs: [operand_positions], uses: [operand_positions]} // Positions are 1-based indices into the instruction array var classify_operands = function(op) { // Binary ops: DEF=[1], USE=[2,3] if (op == "add" || op == "subtract" || op == "multiply" || op == "divide" || op == "modulo" || op == "pow" || op == "remainder" || op == "add_int" || op == "sub_int" || op == "mul_int" || op == "div_int" || op == "mod_int" || op == "pow_int" || op == "rem_int" || op == "add_float" || op == "sub_float" || op == "mul_float" || op == "div_float" || op == "mod_float" || op == "pow_float" || op == "eq" || op == "ne" || op == "lt" || op == "gt" || op == "le" || op == "ge" || op == "eq_int" || op == "ne_int" || op == "lt_int" || op == "gt_int" || op == "le_int" || op == "ge_int" || op == "eq_float" || op == "ne_float" || op == "lt_float" || op == "gt_float" || op == "le_float" || op == "ge_float" || op == "eq_text" || op == "ne_text" || op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text" || op == "eq_bool" || op == "ne_bool" || op == "concat" || op == "bitand" || op == "bitor" || op == "bitxor" || op == "shl" || op == "shr" || op == "ushr" || op == "and" || op == "or" || op == "is_identical") { return {defs: [1], uses: [2, 3]} } // Unary ops: DEF=[1], USE=[2] if (op == "not" || op == "negate" || op == "neg_int" || op == "neg_float" || op == "bitnot" || op == "typeof" || op == "length" || op == "is_int" || op == "is_num" || op == "is_text" || op == "is_bool" || op == "is_null" || op == "is_array" || op == "is_func" || op == "is_record" || op == "is_stone" || op == "is_integer") { return {defs: [1], uses: [2]} } // Constants: DEF=[1], USE=[] if (op == "int" || op == "true" || op == "false" || op == "null" || op == "access") { return {defs: [1], uses: []} } if (op == "move") return {defs: [1], uses: [2]} if (op == "function") return {defs: [1], uses: []} if (op == "array") return {defs: [1], uses: []} if (op == "record") return {defs: [1], uses: []} if (op == "frame") return {defs: [1], uses: [2]} if (op == "setarg") return {defs: [], uses: [1, 3]} if (op == "invoke") return {defs: [2], uses: [1]} if (op == "tail_invoke" || op == "goinvoke") return {defs: [], uses: [1]} if (op == "load_field") return {defs: [1], uses: [2]} if (op == "store_field") return {defs: [], uses: [1, 3]} if (op == "load_index" || op == "load_dynamic") return {defs: [1], uses: [2, 3]} if (op == "store_index" || op == "store_dynamic") return {defs: [], uses: [1, 2, 3]} if (op == "push") return {defs: [], uses: [1, 2]} if (op == "pop") return {defs: [1], uses: [2]} if (op == "jump_true" || op == "jump_false" || op == "jump_null" || op == "jump_not_null") return {defs: [], uses: [1]} if (op == "jump") return {defs: [], uses: []} if (op == "return") return {defs: [], uses: [1]} if (op == "disrupt") return {defs: [], uses: []} if (op == "get") return {defs: [1], uses: []} if (op == "set_var") return {defs: [], uses: [1]} return {defs: [], uses: []} } 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: [] } streamline(mcode_copy, sl_log) 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 cls = 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) cls = classify_operands(op) di = 0 while (di < length(cls.defs)) { operand_val = instr[cls.defs[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 } push(events, {kind: "DEF", slot: operand_val, pc: pc, instr: instr}) } di = di + 1 } ui = 0 while (ui < length(cls.uses)) { operand_val = instr[cls.uses[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 push(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) { push(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()