more build tools
This commit is contained in:
357
slots.ce
Normal file
357
slots.ce
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 <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: []
|
||||
}
|
||||
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 : "<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()
|
||||
Reference in New Issue
Block a user