// ir_stats.cm — IR statistics, fingerprinting, and canonical printing // // Usage: var ir_stats = use("ir_stats") // ir_stats.detailed_stats(func) // ir_stats.ir_fingerprint(func) // ir_stats.canonical_ir(func, name, opts) // ir_stats.type_snapshot(slot_types) // ir_stats.type_delta(before_types, after_types) var json = use("json") // --- Category maps --- var load_ops = { load_field: true, load_index: true, load_dynamic: true, get: true } var store_ops = { store_field: true, store_index: true, store_dynamic: true, set_var: true, put: true, push: true } var branch_ops = { jump: true, jump_true: true, jump_false: true, jump_not_null: true } var call_ops = { invoke: true, goinvoke: true } var guard_ops = { is_int: true, is_text: true, is_num: true, is_bool: true, is_null: true, is_array: true, is_func: true, is_record: true, is_stone: true } var arith_ops = { add_int: true, sub_int: true, mul_int: true, div_int: true, mod_int: true, add_float: true, sub_float: true, mul_float: true, div_float: true, mod_float: true, concat: true, neg_int: true, neg_float: true, bitnot: true, bitand: true, bitor: true, bitxor: true, shl: true, shr: true, ushr: true } var move_ops = { move: true } var const_ops = { int: true, true: true, false: true, null: true } var nop_reasons = { tc: "tc", bl: "bl", mv: "mv", dj: "dj", ur: "ur" } var category_tag = function(op) { if (guard_ops[op] == true) { return "guard" } if (branch_ops[op] == true) { return "branch" } if (load_ops[op] == true) { return "load" } if (store_ops[op] == true) { return "store" } if (call_ops[op] == true) { return "call" } if (arith_ops[op] == true) { return "arith" } if (move_ops[op] == true) { return "move" } if (const_ops[op] == true) { return "const" } return null } // --- detailed_stats --- var detailed_stats = function(func) { var instructions = func.instructions var stats = { instr: 0, nop: 0, load: 0, store: 0, branch: 0, call: 0, guard: 0, arith: 0, move: 0, const: 0, label: 0, other: 0 } var i = 0 var instr = null var op = null var num = 0 if (instructions == null) { return stats } num = length(instructions) while (i < num) { instr = instructions[i] if (is_text(instr)) { if (starts_with(instr, "_nop_")) { stats.nop = stats.nop + 1 stats.instr = stats.instr + 1 } else { stats.label = stats.label + 1 } } else if (is_array(instr)) { stats.instr = stats.instr + 1 op = instr[0] if (op == "access" && !is_number(instr[2]) && !is_logical(instr[2])) { stats.load = stats.load + 1 } else if (op == "access") { stats.const = stats.const + 1 } else if (load_ops[op] == true) { stats.load = stats.load + 1 } else if (store_ops[op] == true) { stats.store = stats.store + 1 } else if (branch_ops[op] == true) { stats.branch = stats.branch + 1 } else if (call_ops[op] == true) { stats.call = stats.call + 1 } else if (guard_ops[op] == true) { stats.guard = stats.guard + 1 } else if (arith_ops[op] == true) { stats.arith = stats.arith + 1 } else if (move_ops[op] == true) { stats.move = stats.move + 1 } else if (const_ops[op] == true) { stats.const = stats.const + 1 } else { stats.other = stats.other + 1 } } i = i + 1 } return stats } // --- ir_fingerprint --- // djb2 hash computed over the JSON-encoded instructions var djb2 = function(s) { var chars = array(s) var hash = 5381 var i = 0 var num = length(chars) while (i < num) { hash = ((hash * 33) + number(chars[i])) % 4294967296 i = i + 1 } return text(hash, 16) } var ir_fingerprint = function(func) { return djb2(json.encode(func.instructions)) } // --- canonical_ir --- var pad_right = function(s, w) { var r = s while (length(r) < w) { r = r + " " } return r } var nop_reason = function(s) { // extract reason from _nop_XX_NNN var parts = array(s, "_") // parts: ["", "nop", "XX", "NNN"] if (length(parts) >= 3) { return parts[2] } return "?" } var fmt_operand = function(v) { if (is_null(v)) { return "null" } if (is_number(v)) { return text(v) } if (is_text(v)) { return `"${v}"` } if (is_logical(v)) { if (v) { return "true" } return "false" } return text(v) } var canonical_ir = function(func, name, opts) { var instructions = func.instructions var nr_args = func.nr_args != null ? func.nr_args : 0 var nr_slots = func.nr_slots != null ? func.nr_slots : 0 var show_nops = false var show_types = false var slot_types = null var lines = [] var i = 0 var instr = null var op = null var n = 0 var parts = null var j = 0 var idx_str = null var op_str = null var operands = null var suffix = null var tag = null var typ = null var reason = null var num = 0 if (opts != null) { if (opts.show_nops == true) { show_nops = true } if (opts.show_types == true) { show_types = true } if (opts.slot_types != null) { slot_types = opts.slot_types } } lines[] = `fn ${name != null ? name : ""} (args=${text(nr_args)}, slots=${text(nr_slots)})` if (instructions == null) { return text(lines, "\n") } num = length(instructions) while (i < num) { instr = instructions[i] if (is_text(instr)) { if (starts_with(instr, "_nop_")) { if (show_nops) { reason = nop_reason(instr) idx_str = pad_right(`@${text(i)}`, 6) lines[] = ` ${idx_str}--- nop (${reason}) ---` } } else { lines[] = ` ${instr}:` } i = i + 1 continue } if (!is_array(instr)) { i = i + 1 continue } op = instr[0] n = length(instr) parts = [] j = 1 while (j < n - 2) { if (is_number(instr[j]) && op != "int" && !(op == "access" && j == 2)) { parts[] = `s${text(instr[j])}` } else { parts[] = fmt_operand(instr[j]) } j = j + 1 } operands = text(parts, ", ") idx_str = pad_right(`@${text(i)}`, 6) op_str = pad_right(op, 16) suffix = "" tag = category_tag(op) if (show_types && slot_types != null) { // show type for dest slot if known if (is_number(instr[1])) { typ = slot_types[text(instr[1])] if (typ != null) { suffix = `; -> ${typ}` } } if (tag != null) { suffix = suffix + ` [${tag}]` } } else if (tag != null) { suffix = suffix + `; [${tag}]` } if (length(suffix) > 0) { lines[] = ` ${idx_str}${op_str}${operands} ${suffix}` } else { lines[] = ` ${idx_str}${op_str}${operands}` } i = i + 1 } return text(lines, "\n") } // --- type_snapshot --- var type_snapshot = function(slot_types) { if (slot_types == null) { return {} } return stone(record(slot_types)) } // --- type_delta --- var type_delta = function(before_types, after_types) { var result = { added: {}, removed: {}, strengthened: {}, weakened: {} } var bt = before_types != null ? before_types : {} var at = after_types != null ? after_types : {} var keys = null var i = 0 var k = null var bv = null var av = null // check after for added/changed keys = array(at) i = 0 while (i < length(keys)) { k = keys[i] av = at[k] bv = bt[k] if (bv == null) { result.added[k] = av } else if (bv != av) { if (bv == "unknown" || (bv == "num" && (av == "int" || av == "float"))) { result.strengthened[k] = {from: bv, to: av} } else if (av == "unknown" || (av == "num" && (bv == "int" || bv == "float"))) { result.weakened[k] = {from: bv, to: av} } else { result.strengthened[k] = {from: bv, to: av} } } i = i + 1 } // check before for removed keys = array(bt) i = 0 while (i < length(keys)) { k = keys[i] if (at[k] == null) { result.removed[k] = bt[k] } i = i + 1 } return result } return { detailed_stats: detailed_stats, ir_fingerprint: ir_fingerprint, canonical_ir: canonical_ir, type_snapshot: type_snapshot, type_delta: type_delta, category_tag: category_tag }