Files
cell/ir_stats.cm

358 lines
8.2 KiB
Plaintext

// 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 : "<anon>"} (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
}