// diff_ir.ce — mcode vs streamline diff // // Usage: // cell diff_ir Diff all functions // cell diff_ir --fn Diff only one function // cell diff_ir --summary Counts only 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 fn_filter = null var show_summary = false var filename = null var i = 0 var mcode_ir = null var opt_ir = null var source_text = null var source_lines = null var main_name = null var fi = 0 var func = null var opt_func = null var fname = null while (i < length(args)) { if (args[i] == '--fn') { i = i + 1 fn_filter = args[i] } else if (args[i] == '--summary') { show_summary = true } else if (args[i] == '--help' || args[i] == '-h') { log.console("Usage: cell diff_ir [--fn ] [--summary] ") log.console("") log.console(" --fn Filter to function by index or name") log.console(" --summary Show counts only") return null } else if (!starts_with(args[i], '-')) { filename = args[i] } i = i + 1 } if (!filename) { log.console("Usage: cell diff_ir [--fn ] [--summary] ") return null } mcode_ir = shop.mcode_file(filename) opt_ir = shop.compile_file(filename) source_text = text(fd.slurp(filename)) source_lines = array(source_text, "\n") 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 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 fmt_instr = function(instr) { var op = instr[0] var n = length(instr) var parts = [] var j = 1 var operands = null var line_str = null while (j < n - 2) { push(parts, fmt_val(instr[j])) j = j + 1 } operands = text(parts, ", ") line_str = instr[n - 2] != null ? `:${text(instr[n - 2])}` : "" return pad_right(`${pad_right(op, 15)}${operands}`, 45) + line_str } var classify = function(before, after) { var bn = 0 var an = 0 var k = 0 if (is_text(after) && starts_with(after, "_nop_")) return "eliminated" if (is_array(before) && is_array(after)) { if (before[0] != after[0]) return "rewritten" bn = length(before) an = length(after) if (bn != an) return "rewritten" k = 1 while (k < bn - 2) { if (before[k] != after[k]) return "rewritten" k = k + 1 } return "identical" } return "identical" } var total_eliminated = 0 var total_rewritten = 0 var total_funcs = 0 var diff_function = function(mcode_func, opt_func, name, index) { var nr_args = mcode_func.nr_args != null ? mcode_func.nr_args : 0 var nr_slots = mcode_func.nr_slots != null ? mcode_func.nr_slots : 0 var m_instrs = mcode_func.instructions var o_instrs = opt_func.instructions var eliminated = 0 var rewritten = 0 var mi = 0 var oi = 0 var pc = 0 var m_instr = null var o_instr = null var kind = null var last_line = null var instr_line = null var n = 0 var src = null var annotation = null if (m_instrs == null) m_instrs = [] if (o_instrs == null) o_instrs = [] // First pass: count changes mi = 0 oi = 0 while (mi < length(m_instrs) && oi < length(o_instrs)) { m_instr = m_instrs[mi] o_instr = o_instrs[oi] if (is_text(m_instr)) { mi = mi + 1 oi = oi + 1 continue } if (is_text(o_instr) && starts_with(o_instr, "_nop_")) { if (is_array(m_instr)) { eliminated = eliminated + 1 } mi = mi + 1 oi = oi + 1 continue } if (is_array(m_instr) && is_array(o_instr)) { kind = classify(m_instr, o_instr) if (kind == "rewritten") { rewritten = rewritten + 1 } } mi = mi + 1 oi = oi + 1 } total_eliminated = total_eliminated + eliminated total_rewritten = total_rewritten + rewritten total_funcs = total_funcs + 1 if (show_summary) { if (eliminated == 0 && rewritten == 0) { log.compile(` ${pad_right(name + ":", 40)} 0 eliminated, 0 rewritten (unchanged)`) } else { log.compile(` ${pad_right(name + ":", 40)} ${text(eliminated)} eliminated, ${text(rewritten)} rewritten`) } return null } if (eliminated == 0 && rewritten == 0) return null log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`) log.compile(` ${text(eliminated)} eliminated, ${text(rewritten)} rewritten`) // Second pass: show diffs mi = 0 oi = 0 pc = 0 last_line = null while (mi < length(m_instrs) && oi < length(o_instrs)) { m_instr = m_instrs[mi] o_instr = o_instrs[oi] if (is_text(m_instr) && !starts_with(m_instr, "_nop_")) { mi = mi + 1 oi = oi + 1 continue } if (is_text(m_instr) && starts_with(m_instr, "_nop_")) { mi = mi + 1 oi = oi + 1 continue } if (is_text(o_instr) && starts_with(o_instr, "_nop_")) { if (is_array(m_instr)) { n = length(m_instr) instr_line = m_instr[n - 2] 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} ---`) } last_line = instr_line } log.compile(` - ${pad_right(text(pc), 6)}${fmt_instr(m_instr)}`) log.compile(` + ${pad_right(text(pc), 6)}${pad_right(o_instr, 45)} (eliminated)`) } mi = mi + 1 oi = oi + 1 pc = pc + 1 continue } if (is_array(m_instr) && is_array(o_instr)) { kind = classify(m_instr, o_instr) if (kind != "identical") { n = length(m_instr) instr_line = m_instr[n - 2] 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} ---`) } last_line = instr_line } annotation = "" if (kind == "rewritten") { if (o_instr[0] == "concat" && m_instr[0] != "concat") { annotation = "(specialized)" } else { annotation = "(rewritten)" } } log.compile(` - ${pad_right(text(pc), 6)}${fmt_instr(m_instr)}`) log.compile(` + ${pad_right(text(pc), 6)}${fmt_instr(o_instr)} ${annotation}`) } pc = pc + 1 } mi = mi + 1 oi = oi + 1 } return null } // Process functions main_name = mcode_ir.name != null ? mcode_ir.name : "
" if (mcode_ir.main != null && opt_ir.main != null) { if (fn_matches(-1, main_name)) { diff_function(mcode_ir.main, opt_ir.main, main_name, -1) } } if (mcode_ir.functions != null && opt_ir.functions != null) { fi = 0 while (fi < length(mcode_ir.functions) && fi < length(opt_ir.functions)) { func = mcode_ir.functions[fi] opt_func = opt_ir.functions[fi] fname = func.name != null ? func.name : "" if (fn_matches(fi, fname)) { diff_function(func, opt_func, `[${text(fi)}] ${fname}`, fi) } fi = fi + 1 } } if (show_summary) { log.compile(`\n total: ${text(total_eliminated)} eliminated, ${text(total_rewritten)} rewritten across ${text(total_funcs)} functions`) } return null } run() $stop()