Files
cell/disasm.ce
2026-02-21 00:41:47 -06:00

266 lines
6.9 KiB
Plaintext

// disasm.ce — source-interleaved disassembly
//
// Usage:
// cell disasm <file> Disassemble all functions (mcode)
// cell disasm --optimized <file> Disassemble optimized IR (streamline)
// cell disasm --fn <N|name> <file> Show only function N or named function
// cell disasm --line <N> <file> Show instructions from source line N
var fd = use("fd")
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)
}
var run = function() {
var use_optimized = false
var fn_filter = null
var line_filter = null
var filename = null
var i = 0
var compiled = null
var source_text = null
var source_lines = null
var main_name = null
var fi = 0
var func = null
var fname = null
while (i < length(args)) {
if (args[i] == '--optimized') {
use_optimized = true
} else if (args[i] == '--fn') {
i = i + 1
fn_filter = args[i]
} else if (args[i] == '--line') {
i = i + 1
line_filter = number(args[i])
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell disasm [--optimized] [--fn <N|name>] [--line <N>] <file>")
log.console("")
log.console(" --optimized Use optimized IR (streamline) instead of raw mcode")
log.console(" --fn <N|name> Filter to function by index or name")
log.console(" --line <N> Show only instructions from source line N")
return null
} else if (!starts_with(args[i], '-')) {
filename = args[i]
}
i = i + 1
}
if (!filename) {
log.console("Usage: cell disasm [--optimized] [--fn <N|name>] [--line <N>] <file>")
return null
}
// Compile
if (use_optimized) {
compiled = shop.compile_file(filename)
} else {
compiled = shop.mcode_file(filename)
}
// Read source file
source_text = text(fd.slurp(filename))
source_lines = array(source_text, "\n")
// Helpers
var get_source_line = function(line_num) {
if (line_num < 1 || line_num > length(source_lines)) return null
return source_lines[line_num - 1]
}
var first_instr_line = function(func) {
var instrs = func.instructions
var i = 0
var n = 0
if (instrs == null) return null
while (i < length(instrs)) {
if (is_array(instrs[i])) {
n = length(instrs[i])
return instrs[i][n - 2]
}
i = i + 1
}
return null
}
var func_has_line = function(func, target) {
var instrs = func.instructions
var i = 0
var n = 0
if (instrs == null) return false
while (i < length(instrs)) {
if (is_array(instrs[i])) {
n = length(instrs[i])
if (instrs[i][n - 2] == target) return true
}
i = i + 1
}
return false
}
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 func_name_by_index = function(fi) {
var f = null
if (compiled.functions == null) return null
if (fi < 0 || fi >= length(compiled.functions)) return null
f = compiled.functions[fi]
return f.name
}
var dump_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 nr_close = func.nr_close_slots != null ? func.nr_close_slots : 0
var instrs = func.instructions
var start_line = first_instr_line(func)
var header = null
var i = 0
var pc = 0
var instr = null
var op = null
var n = 0
var parts = null
var j = 0
var operands = null
var instr_line = null
var last_line = null
var src = null
var line_str = null
var instr_text = null
var target_name = null
header = `\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}, closures=${text(nr_close)})`
if (start_line != null) {
header = header + ` [line ${text(start_line)}]`
}
header = header + " ==="
log.compile(header)
if (instrs == null || length(instrs) == 0) {
log.compile(" (empty)")
return null
}
while (i < length(instrs)) {
instr = instrs[i]
if (is_text(instr)) {
if (!starts_with(instr, "_nop_") && line_filter == null) {
log.compile(` ${instr}:`)
}
} else if (is_array(instr)) {
op = instr[0]
n = length(instr)
instr_line = instr[n - 2]
if (line_filter != null && instr_line != line_filter) {
pc = pc + 1
i = i + 1
continue
}
if (instr_line != last_line && instr_line != null) {
src = get_source_line(instr_line)
if (src != null) {
src = trim(src)
}
if (last_line != null) {
log.compile("")
}
if (src != null && length(src) > 0) {
log.compile(` --- line ${text(instr_line)}: ${src} ---`)
} else {
log.compile(` --- line ${text(instr_line)} ---`)
}
last_line = instr_line
}
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
j = j + 1
}
operands = text(parts, ", ")
line_str = instr_line != null ? `:${text(instr_line)}` : ""
instr_text = ` ${pad_right(text(pc), 6)}${pad_right(op, 15)}${operands}`
// Cross-reference for function creation instructions
target_name = null
if (op == "function" && n >= 5) {
target_name = func_name_by_index(instr[2])
}
if (target_name != null) {
instr_text = pad_right(instr_text, 65) + line_str + ` ; -> [${text(instr[2])}] ${target_name}`
} else {
instr_text = pad_right(instr_text, 65) + line_str
}
log.compile(instr_text)
pc = pc + 1
}
i = i + 1
}
return null
}
// Process functions
main_name = compiled.name != null ? compiled.name : "<main>"
if (compiled.main != null) {
if (fn_matches(-1, main_name)) {
if (line_filter == null || func_has_line(compiled.main, line_filter)) {
dump_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)) {
if (line_filter == null || func_has_line(func, line_filter)) {
dump_function(func, `[${text(fi)}] ${fname}`, fi)
}
}
fi = fi + 1
}
}
return null
}
run()
$stop()