1149 lines
35 KiB
Plaintext
1149 lines
35 KiB
Plaintext
// streamline.cm — mcode IR optimizer
|
|
// Composed of independent passes, each a separate function.
|
|
// Optional `log` parameter enables structured observability.
|
|
|
|
var streamline = function(ir, log) {
|
|
// IR verification support — verifier module passed via ir._verify_mod
|
|
// (streamline's use() is use_basic from bootstrap, which can't load source)
|
|
var verify_fn = null
|
|
var verifier = null
|
|
if (ir._verify && ir._verify_mod) {
|
|
verifier = ir._verify_mod
|
|
verify_fn = function(func, pass_name) {
|
|
var errs = verifier.verify_all(func, pass_name)
|
|
var i = 0
|
|
while (i < length(errs)) {
|
|
print(`[verify_ir] ${errs[i]}\n`)
|
|
i = i + 1
|
|
}
|
|
if (length(errs) > 0) {
|
|
print(`[verify_ir] ${text(length(errs))} errors after ${pass_name}\n`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Type constants
|
|
var T_UNKNOWN = "unknown"
|
|
var T_INT = "int"
|
|
var T_FLOAT = "float"
|
|
var T_NUM = "num"
|
|
var T_TEXT = "text"
|
|
var T_BOOL = "bool"
|
|
var T_NULL = "null"
|
|
var T_ARRAY = "array"
|
|
var T_RECORD = "record"
|
|
var T_FUNCTION = "function"
|
|
var T_BLOB = "blob"
|
|
|
|
var numeric_ops = {
|
|
add: true, subtract: true, multiply: true,
|
|
divide: true, modulo: true, pow: true
|
|
}
|
|
var bool_result_ops = {
|
|
eq_int: true, ne_int: true, lt_int: true, gt_int: true,
|
|
le_int: true, ge_int: true,
|
|
eq_float: true, ne_float: true, lt_float: true, gt_float: true,
|
|
le_float: true, ge_float: true,
|
|
eq_text: true, ne_text: true, lt_text: true, gt_text: true,
|
|
le_text: true, ge_text: true,
|
|
eq_bool: true, ne_bool: true,
|
|
eq_tol: true, ne_tol: true,
|
|
not: true, and: true, or: true,
|
|
is_int: true, is_text: true, is_num: true,
|
|
is_bool: true, is_null: true, is_identical: true,
|
|
is_array: true, is_func: true, is_record: true, is_stone: true
|
|
}
|
|
var type_check_map = {
|
|
is_int: T_INT, is_text: T_TEXT, is_num: T_NUM,
|
|
is_bool: T_BOOL, is_null: T_NULL,
|
|
is_array: T_ARRAY, is_func: T_FUNCTION,
|
|
is_record: T_RECORD
|
|
}
|
|
|
|
// --- Logging support ---
|
|
|
|
var ir_stats = null
|
|
var time_mod = null
|
|
if (log != null) {
|
|
ir_stats = use("ir_stats")
|
|
time_mod = use("time")
|
|
}
|
|
|
|
var run_pass = function(func, pass_name, pass_fn) {
|
|
var before = null
|
|
var after = null
|
|
var t0 = null
|
|
var t1 = null
|
|
var ms = null
|
|
var changed = false
|
|
var result = null
|
|
if (log == null) {
|
|
return pass_fn()
|
|
}
|
|
before = ir_stats.detailed_stats(func)
|
|
t0 = time_mod.number()
|
|
result = pass_fn()
|
|
t1 = time_mod.number()
|
|
after = ir_stats.detailed_stats(func)
|
|
ms = (t1 - t0) * 1000
|
|
changed = before.instr != after.instr ||
|
|
before.nop != after.nop ||
|
|
before.guard != after.guard
|
|
log.passes[] = {
|
|
pass: pass_name,
|
|
fn: func.name,
|
|
ms: ms,
|
|
before: before,
|
|
after: after,
|
|
changed: changed,
|
|
changes: {
|
|
nops_added: after.nop - before.nop,
|
|
guards_removed: before.guard - after.guard
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// --- Shared helpers ---
|
|
|
|
var access_value_type = function(val) {
|
|
if (is_number(val)) {
|
|
if (is_integer(val)) {
|
|
return T_INT
|
|
}
|
|
return T_FLOAT
|
|
}
|
|
if (is_text(val)) {
|
|
return T_TEXT
|
|
}
|
|
return T_UNKNOWN
|
|
}
|
|
|
|
var track_types = function(slot_types, instr) {
|
|
var op = instr[0]
|
|
var src_type = null
|
|
if (op == "access") {
|
|
slot_types[text(instr[1])] = access_value_type(instr[2])
|
|
} else if (op == "int") {
|
|
slot_types[text(instr[1])] = T_INT
|
|
} else if (op == "true" || op == "false") {
|
|
slot_types[text(instr[1])] = T_BOOL
|
|
} else if (op == "null") {
|
|
slot_types[text(instr[1])] = T_NULL
|
|
} else if (op == "move") {
|
|
src_type = slot_types[text(instr[2])]
|
|
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN
|
|
} else if (op == "concat") {
|
|
slot_types[text(instr[1])] = T_TEXT
|
|
} else if (bool_result_ops[op] == true) {
|
|
slot_types[text(instr[1])] = T_BOOL
|
|
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
} else if (op == "invoke" || op == "tail_invoke") {
|
|
slot_types[text(instr[2])] = T_UNKNOWN
|
|
} else if (op == "pop" || op == "get") {
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
} else if (op == "array") {
|
|
slot_types[text(instr[1])] = T_ARRAY
|
|
} else if (op == "record") {
|
|
slot_types[text(instr[1])] = T_RECORD
|
|
} else if (op == "function") {
|
|
slot_types[text(instr[1])] = T_FUNCTION
|
|
} else if (op == "length") {
|
|
slot_types[text(instr[1])] = T_INT
|
|
} else if (op == "negate" || numeric_ops[op] == true) {
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
|
|
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
|
|
slot_types[text(instr[1])] = T_INT
|
|
}
|
|
return null
|
|
}
|
|
|
|
var slot_is = function(slot_types, slot, typ) {
|
|
var known = slot_types[text(slot)]
|
|
if (known == null) {
|
|
return false
|
|
}
|
|
if (known == typ) {
|
|
return true
|
|
}
|
|
if (typ == T_NUM && (known == T_INT || known == T_FLOAT)) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
var merge_backward = function(backward_types, slot, typ) {
|
|
var sk = null
|
|
var existing = null
|
|
if (!is_number(slot)) {
|
|
return null
|
|
}
|
|
sk = text(slot)
|
|
existing = backward_types[sk]
|
|
if (existing == null) {
|
|
backward_types[sk] = typ
|
|
} else if (existing != typ && existing != T_UNKNOWN) {
|
|
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
|
|
// Keep more specific
|
|
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
|
|
backward_types[sk] = typ
|
|
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) {
|
|
backward_types[sk] = T_NUM
|
|
} else {
|
|
backward_types[sk] = T_UNKNOWN
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
var seed_params = function(slot_types, param_types, nr_args) {
|
|
var j = 1
|
|
while (j <= nr_args) {
|
|
if (param_types[text(j)] != null) {
|
|
slot_types[text(j)] = param_types[text(j)]
|
|
}
|
|
j = j + 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
var seed_writes = function(slot_types, write_types) {
|
|
var keys = array(write_types)
|
|
var k = 0
|
|
while (k < length(keys)) {
|
|
slot_types[keys[k]] = write_types[keys[k]]
|
|
k = k + 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: infer_param_types — backward type inference
|
|
// Scans typed operators to infer immutable parameter types.
|
|
// =========================================================
|
|
var infer_param_types = function(func) {
|
|
var instructions = func.instructions
|
|
var nr_args = func.nr_args != null ? func.nr_args : 0
|
|
var num_instr = 0
|
|
var backward_types = null
|
|
var param_types = null
|
|
var i = 0
|
|
var j = 0
|
|
var instr = null
|
|
var op = null
|
|
var bt = null
|
|
|
|
if (instructions == null || nr_args == 0) {
|
|
return {}
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
backward_types = {}
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (is_array(instr)) {
|
|
op = instr[0]
|
|
if (op == "subtract" || op == "multiply" ||
|
|
op == "divide" || op == "modulo" || op == "pow") {
|
|
merge_backward(backward_types, instr[2], T_NUM)
|
|
merge_backward(backward_types, instr[3], T_NUM)
|
|
} else if (op == "negate") {
|
|
merge_backward(backward_types, instr[2], T_NUM)
|
|
} else if (op == "eq_int" || op == "ne_int" || op == "lt_int" ||
|
|
op == "gt_int" || op == "le_int" || op == "ge_int" ||
|
|
op == "bitand" || op == "bitor" || op == "bitxor" ||
|
|
op == "shl" || op == "shr" || op == "ushr") {
|
|
merge_backward(backward_types, instr[2], T_INT)
|
|
merge_backward(backward_types, instr[3], T_INT)
|
|
} else if (op == "bitnot") {
|
|
merge_backward(backward_types, instr[2], T_INT)
|
|
} else if (op == "eq_float" || op == "ne_float" || op == "lt_float" ||
|
|
op == "gt_float" || op == "le_float" || op == "ge_float") {
|
|
merge_backward(backward_types, instr[2], T_FLOAT)
|
|
merge_backward(backward_types, instr[3], T_FLOAT)
|
|
} else if (op == "concat" ||
|
|
op == "eq_text" || op == "ne_text" || op == "lt_text" ||
|
|
op == "gt_text" || op == "le_text" || op == "ge_text") {
|
|
merge_backward(backward_types, instr[2], T_TEXT)
|
|
merge_backward(backward_types, instr[3], T_TEXT)
|
|
} else if (op == "eq_bool" || op == "ne_bool") {
|
|
merge_backward(backward_types, instr[2], T_BOOL)
|
|
merge_backward(backward_types, instr[3], T_BOOL)
|
|
} else if (op == "not") {
|
|
merge_backward(backward_types, instr[2], T_BOOL)
|
|
} else if (op == "and" || op == "or") {
|
|
merge_backward(backward_types, instr[2], T_BOOL)
|
|
merge_backward(backward_types, instr[3], T_BOOL)
|
|
} else if (op == "store_index") {
|
|
merge_backward(backward_types, instr[1], T_ARRAY)
|
|
merge_backward(backward_types, instr[2], T_INT)
|
|
} else if (op == "store_field") {
|
|
merge_backward(backward_types, instr[1], T_RECORD)
|
|
} else if (op == "push") {
|
|
merge_backward(backward_types, instr[1], T_ARRAY)
|
|
} else if (op == "load_index") {
|
|
merge_backward(backward_types, instr[2], T_ARRAY)
|
|
merge_backward(backward_types, instr[3], T_INT)
|
|
} else if (op == "load_field") {
|
|
merge_backward(backward_types, instr[2], T_RECORD)
|
|
} else if (op == "pop") {
|
|
merge_backward(backward_types, instr[2], T_ARRAY)
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
param_types = {}
|
|
j = 1
|
|
while (j <= nr_args) {
|
|
bt = backward_types[text(j)]
|
|
if (bt != null && bt != T_UNKNOWN) {
|
|
param_types[text(j)] = bt
|
|
}
|
|
j = j + 1
|
|
}
|
|
return param_types
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: infer_slot_write_types — slot write-type invariance
|
|
// Scans all instructions to find non-parameter slots where
|
|
// every write produces the same type. These types persist
|
|
// across label join points.
|
|
// =========================================================
|
|
var infer_slot_write_types = function(func) {
|
|
var instructions = func.instructions
|
|
var nr_args = func.nr_args != null ? func.nr_args : 0
|
|
var num_instr = 0
|
|
var write_types = null
|
|
var result = null
|
|
var keys = null
|
|
var i = 0
|
|
var k = 0
|
|
var instr = null
|
|
var op = null
|
|
var slot = 0
|
|
var typ = null
|
|
var wt = null
|
|
|
|
if (instructions == null) {
|
|
return {}
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
write_types = {}
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (!is_array(instr)) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
op = instr[0]
|
|
slot = -1
|
|
typ = null
|
|
|
|
if (op == "int") {
|
|
slot = instr[1]
|
|
typ = T_INT
|
|
} else if (op == "true" || op == "false") {
|
|
slot = instr[1]
|
|
typ = T_BOOL
|
|
} else if (op == "null") {
|
|
slot = instr[1]
|
|
typ = T_NULL
|
|
} else if (op == "access") {
|
|
slot = instr[1]
|
|
typ = access_value_type(instr[2])
|
|
} else if (op == "array") {
|
|
slot = instr[1]
|
|
typ = T_ARRAY
|
|
} else if (op == "record") {
|
|
slot = instr[1]
|
|
typ = T_RECORD
|
|
} else if (op == "function") {
|
|
slot = instr[1]
|
|
typ = T_FUNCTION
|
|
} else if (op == "length") {
|
|
slot = instr[1]
|
|
typ = T_INT
|
|
} else if (op == "bitnot" || op == "bitand" ||
|
|
op == "bitor" || op == "bitxor" || op == "shl" ||
|
|
op == "shr" || op == "ushr") {
|
|
slot = instr[1]
|
|
typ = T_INT
|
|
} else if (op == "negate") {
|
|
slot = instr[1]
|
|
typ = T_UNKNOWN
|
|
} else if (op == "concat") {
|
|
slot = instr[1]
|
|
typ = T_TEXT
|
|
} else if (bool_result_ops[op] == true) {
|
|
slot = instr[1]
|
|
typ = T_BOOL
|
|
} else if (op == "eq" || op == "ne" || op == "lt" ||
|
|
op == "le" || op == "gt" || op == "ge" || op == "in") {
|
|
slot = instr[1]
|
|
typ = T_BOOL
|
|
} else if (op == "add" || op == "subtract" || op == "multiply" ||
|
|
op == "divide" || op == "modulo" || op == "pow") {
|
|
slot = instr[1]
|
|
typ = T_UNKNOWN
|
|
} else if (op == "move" || op == "load_field" || op == "load_index" ||
|
|
op == "load_dynamic" || op == "pop" || op == "get") {
|
|
slot = instr[1]
|
|
typ = T_UNKNOWN
|
|
} else if (op == "invoke" || op == "tail_invoke") {
|
|
slot = instr[2]
|
|
typ = T_UNKNOWN
|
|
}
|
|
|
|
if (slot > 0 && slot > nr_args) {
|
|
merge_backward(write_types, slot, typ != null ? typ : T_UNKNOWN)
|
|
}
|
|
|
|
i = i + 1
|
|
}
|
|
|
|
// Filter to only slots with known (non-unknown) types
|
|
result = {}
|
|
keys = array(write_types)
|
|
k = 0
|
|
while (k < length(keys)) {
|
|
wt = write_types[keys[k]]
|
|
if (wt != null && wt != T_UNKNOWN) {
|
|
result[keys[k]] = wt
|
|
}
|
|
k = k + 1
|
|
}
|
|
return result
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: eliminate_type_checks — language-level type narrowing
|
|
// Eliminates is_<type>/jump pairs when type is known.
|
|
// Reduces load_dynamic/store_dynamic to field/index forms.
|
|
// =========================================================
|
|
var eliminate_type_checks = function(func, param_types, write_types, log) {
|
|
var instructions = func.instructions
|
|
var nr_args = func.nr_args != null ? func.nr_args : 0
|
|
var has_params = false
|
|
var has_writes = false
|
|
var num_instr = 0
|
|
var slot_types = null
|
|
var nc = 0
|
|
var i = 0
|
|
var j = 0
|
|
var instr = null
|
|
var op = null
|
|
var dest = 0
|
|
var src = 0
|
|
var checked_type = null
|
|
var next = null
|
|
var next_op = null
|
|
var target_label = null
|
|
var src_known = null
|
|
var jlen = 0
|
|
var events = null
|
|
var old_op = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return {}
|
|
}
|
|
|
|
if (log != null && log.events != null) {
|
|
events = log.events
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
j = 1
|
|
while (j <= nr_args) {
|
|
if (param_types[text(j)] != null) {
|
|
has_params = true
|
|
}
|
|
j = j + 1
|
|
}
|
|
has_writes = length(array(write_types)) > 0
|
|
|
|
slot_types = {}
|
|
if (has_params) {
|
|
seed_params(slot_types, param_types, nr_args)
|
|
}
|
|
if (has_writes) {
|
|
seed_writes(slot_types, write_types)
|
|
}
|
|
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
|
|
if (is_text(instr)) {
|
|
slot_types = {}
|
|
if (has_params) {
|
|
seed_params(slot_types, param_types, nr_args)
|
|
}
|
|
if (has_writes) {
|
|
seed_writes(slot_types, write_types)
|
|
}
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
if (!is_array(instr)) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
op = instr[0]
|
|
|
|
// Type-check + jump elimination
|
|
if (type_check_map[op] != null && i + 1 < num_instr) {
|
|
dest = instr[1]
|
|
src = instr[2]
|
|
checked_type = type_check_map[op]
|
|
next = instructions[i + 1]
|
|
|
|
if (is_array(next)) {
|
|
next_op = next[0]
|
|
|
|
if (next_op == "jump_false" && next[1] == dest) {
|
|
target_label = next[2]
|
|
if (slot_is(slot_types, src, checked_type)) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
nc = nc + 1
|
|
instructions[i + 1] = "_nop_tc_" + text(nc)
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "known_type_eliminates_guard",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
src_known = slot_types[text(src)]
|
|
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
|
|
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
nc = nc + 1
|
|
instructions[i + 1] = "_nop_tc_" + text(nc)
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "num_subsumes_int_float",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
jlen = length(next)
|
|
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "incompatible_type_forces_jump",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_UNKNOWN
|
|
i = i + 2
|
|
continue
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
slot_types[text(src)] = checked_type
|
|
i = i + 2
|
|
continue
|
|
}
|
|
|
|
if (next_op == "jump_true" && next[1] == dest) {
|
|
target_label = next[2]
|
|
if (slot_is(slot_types, src, checked_type)) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
jlen = length(next)
|
|
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "known_type_eliminates_guard",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: slot_types[text(src)], checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
src_known = slot_types[text(src)]
|
|
if (src_known != null && src_known != T_UNKNOWN && src_known != checked_type) {
|
|
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
jlen = length(next)
|
|
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "num_subsumes_int_float",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_tc_" + text(nc)
|
|
nc = nc + 1
|
|
instructions[i + 1] = "_nop_tc_" + text(nc)
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "incompatible_type_forces_jump",
|
|
at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]],
|
|
why: {slot: src, known_type: src_known, checked_type: checked_type}
|
|
}
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 2
|
|
continue
|
|
}
|
|
}
|
|
|
|
slot_types[text(dest)] = T_BOOL
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
// Dynamic access reduction
|
|
if (op == "load_dynamic") {
|
|
old_op = op
|
|
if (slot_is(slot_types, instr[3], T_TEXT)) {
|
|
instr[0] = "load_field"
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "dynamic_to_field",
|
|
at: i, before: old_op, after: instr[0],
|
|
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
|
}
|
|
}
|
|
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
|
instr[0] = "load_index"
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "dynamic_to_index",
|
|
at: i, before: old_op, after: instr[0],
|
|
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
|
}
|
|
}
|
|
}
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
i = i + 1
|
|
continue
|
|
}
|
|
if (op == "store_dynamic") {
|
|
old_op = op
|
|
if (slot_is(slot_types, instr[3], T_TEXT)) {
|
|
instr[0] = "store_field"
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "dynamic_to_field",
|
|
at: i, before: old_op, after: instr[0],
|
|
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
|
}
|
|
}
|
|
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
|
instr[0] = "store_index"
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite",
|
|
pass: "eliminate_type_checks",
|
|
rule: "dynamic_to_index",
|
|
at: i, before: old_op, after: instr[0],
|
|
why: {slot: instr[3], known_type: slot_types[text(instr[3])]}
|
|
}
|
|
}
|
|
}
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
track_types(slot_types, instr)
|
|
i = i + 1
|
|
}
|
|
|
|
return slot_types
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: simplify_algebra — algebraic identity & comparison
|
|
// Tracks known constant values. Rewrites identity ops to
|
|
// moves or constants. Folds same-slot comparisons.
|
|
// =========================================================
|
|
var simplify_algebra = function(func, log) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var slot_values = null
|
|
var nc = 0
|
|
var i = 0
|
|
var instr = null
|
|
var op = null
|
|
var ilen = 0
|
|
var sv = null
|
|
var events = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
if (log != null && log.events != null) {
|
|
events = log.events
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
slot_values = {}
|
|
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
|
|
if (is_text(instr)) {
|
|
slot_values = {}
|
|
i = i + 1
|
|
continue
|
|
}
|
|
if (!is_array(instr)) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
op = instr[0]
|
|
ilen = length(instr)
|
|
|
|
// Track known constant values
|
|
if (op == "int") {
|
|
slot_values[text(instr[1])] = instr[2]
|
|
} else if (op == "access" && is_number(instr[2])) {
|
|
slot_values[text(instr[1])] = instr[2]
|
|
} else if (op == "true") {
|
|
slot_values[text(instr[1])] = true
|
|
} else if (op == "false") {
|
|
slot_values[text(instr[1])] = false
|
|
} else if (op == "move") {
|
|
sv = slot_values[text(instr[2])]
|
|
if (sv != null) {
|
|
slot_values[text(instr[1])] = sv
|
|
} else {
|
|
slot_values[text(instr[1])] = null
|
|
}
|
|
}
|
|
|
|
// Same-slot comparisons
|
|
if (is_number(instr[2]) && instr[2] == instr[3]) {
|
|
if (op == "eq_int" || op == "eq_float" || op == "eq_text" ||
|
|
op == "eq_bool" || op == "is_identical" ||
|
|
op == "le_int" || op == "le_float" || op == "le_text" ||
|
|
op == "ge_int" || op == "ge_float" || op == "ge_text") {
|
|
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "simplify_algebra",
|
|
rule: "self_eq", at: i,
|
|
before: instr, after: instructions[i],
|
|
why: {op: op, slot: instr[2]}
|
|
}
|
|
}
|
|
slot_values[text(instr[1])] = true
|
|
i = i + 1
|
|
continue
|
|
}
|
|
if (op == "ne_int" || op == "ne_float" || op == "ne_text" ||
|
|
op == "ne_bool" ||
|
|
op == "lt_int" || op == "lt_float" || op == "lt_text" ||
|
|
op == "gt_int" || op == "gt_float" || op == "gt_text") {
|
|
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "simplify_algebra",
|
|
rule: "self_ne", at: i,
|
|
before: instr, after: instructions[i],
|
|
why: {op: op, slot: instr[2]}
|
|
}
|
|
}
|
|
slot_values[text(instr[1])] = false
|
|
i = i + 1
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Clear value tracking for dest-producing ops (not reads-only)
|
|
if (op == "invoke" || op == "tail_invoke") {
|
|
slot_values[text(instr[2])] = null
|
|
} else if (op != "int" && op != "access" && op != "true" &&
|
|
op != "false" && op != "move" && op != "null" &&
|
|
op != "jump" && op != "jump_true" && op != "jump_false" &&
|
|
op != "jump_not_null" && op != "return" && op != "disrupt" &&
|
|
op != "store_field" && op != "store_index" &&
|
|
op != "store_dynamic" && op != "push" && op != "setarg") {
|
|
if (is_number(instr[1])) {
|
|
slot_values[text(instr[1])] = null
|
|
}
|
|
}
|
|
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: simplify_booleans — not+jump fusion, double-not
|
|
// =========================================================
|
|
var simplify_booleans = function(func, log) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var nc = 0
|
|
var i = 0
|
|
var instr = null
|
|
var next = null
|
|
var next_op = null
|
|
var nlen = 0
|
|
var events = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
if (log != null && log.events != null) {
|
|
events = log.events
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (!is_array(instr) || instr[0] != "not" || i + 1 >= num_instr) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
next = instructions[i + 1]
|
|
if (!is_array(next)) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
next_op = next[0]
|
|
nlen = length(next)
|
|
|
|
// not d, x; jump_false d, label → jump_true x, label
|
|
if (next_op == "jump_false" && next[1] == instr[1]) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_bl_" + text(nc)
|
|
instructions[i + 1] = ["jump_true", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "simplify_booleans",
|
|
rule: "not_jump_false_fusion", at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]]
|
|
}
|
|
}
|
|
i = i + 2
|
|
continue
|
|
}
|
|
|
|
// not d, x; jump_true d, label → jump_false x, label
|
|
if (next_op == "jump_true" && next[1] == instr[1]) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_bl_" + text(nc)
|
|
instructions[i + 1] = ["jump_false", instr[2], next[2], next[nlen - 2], next[nlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "simplify_booleans",
|
|
rule: "not_jump_true_fusion", at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]]
|
|
}
|
|
}
|
|
i = i + 2
|
|
continue
|
|
}
|
|
|
|
// not d1, x; not d2, d1 → move d2, x
|
|
if (next_op == "not" && next[2] == instr[1]) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_bl_" + text(nc)
|
|
instructions[i + 1] = ["move", next[1], instr[2], next[nlen - 2], next[nlen - 1]]
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "simplify_booleans",
|
|
rule: "double_not", at: i,
|
|
before: [instr, next],
|
|
after: [instructions[i], instructions[i + 1]]
|
|
}
|
|
}
|
|
i = i + 2
|
|
continue
|
|
}
|
|
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: eliminate_moves — move a, a → nop
|
|
// =========================================================
|
|
var eliminate_moves = function(func, log) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var nc = 0
|
|
var i = 0
|
|
var instr = null
|
|
var events = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
if (log != null && log.events != null) {
|
|
events = log.events
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (is_array(instr) && instr[0] == "move" && instr[1] == instr[2]) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_mv_" + text(nc)
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "eliminate_moves",
|
|
rule: "self_move", at: i,
|
|
before: instr, after: instructions[i]
|
|
}
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: eliminate_unreachable — nop code after return/disrupt
|
|
// =========================================================
|
|
var eliminate_unreachable = function(func) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var nc = 0
|
|
var after_return = false
|
|
var i = 0
|
|
var instr = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (is_text(instr)) {
|
|
if (!starts_with(instr, "_nop_")) {
|
|
after_return = false
|
|
}
|
|
} else if (is_array(instr)) {
|
|
if (after_return) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_ur_" + text(nc)
|
|
} else if (instr[0] == "return") {
|
|
after_return = true
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Pass: eliminate_dead_jumps — jump to next label → nop
|
|
// =========================================================
|
|
var eliminate_dead_jumps = function(func, log) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var nc = 0
|
|
var i = 0
|
|
var j = 0
|
|
var instr = null
|
|
var target_label = null
|
|
var peek = null
|
|
var events = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
if (log != null && log.events != null) {
|
|
events = log.events
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
i = 0
|
|
while (i < num_instr) {
|
|
instr = instructions[i]
|
|
if (is_array(instr) && instr[0] == "jump") {
|
|
target_label = instr[1]
|
|
j = i + 1
|
|
while (j < num_instr) {
|
|
peek = instructions[j]
|
|
if (is_text(peek)) {
|
|
if (peek == target_label) {
|
|
nc = nc + 1
|
|
instructions[i] = "_nop_dj_" + text(nc)
|
|
if (events != null) {
|
|
events[] = {
|
|
event: "rewrite", pass: "eliminate_dead_jumps",
|
|
rule: "jump_to_next", at: i,
|
|
before: instr, after: instructions[i],
|
|
why: {label: target_label}
|
|
}
|
|
}
|
|
}
|
|
break
|
|
}
|
|
if (is_array(peek)) {
|
|
break
|
|
}
|
|
j = j + 1
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// =========================================================
|
|
// Compose all passes
|
|
// =========================================================
|
|
var optimize_function = function(func, log) {
|
|
var param_types = null
|
|
var write_types = null
|
|
var slot_types = null
|
|
if (func.instructions == null || length(func.instructions) == 0) {
|
|
return null
|
|
}
|
|
run_pass(func, "infer_param_types", function() {
|
|
param_types = infer_param_types(func)
|
|
return param_types
|
|
})
|
|
if (verify_fn) verify_fn(func, "after infer_param_types")
|
|
run_pass(func, "infer_slot_write_types", function() {
|
|
write_types = infer_slot_write_types(func)
|
|
return write_types
|
|
})
|
|
if (verify_fn) verify_fn(func, "after infer_slot_write_types")
|
|
run_pass(func, "eliminate_type_checks", function() {
|
|
slot_types = eliminate_type_checks(func, param_types, write_types, log)
|
|
return slot_types
|
|
})
|
|
if (verify_fn) verify_fn(func, "after eliminate_type_checks")
|
|
if (log != null && log.type_deltas != null && slot_types != null) {
|
|
log.type_deltas[] = {
|
|
fn: func.name,
|
|
param_types: param_types,
|
|
slot_types: slot_types
|
|
}
|
|
}
|
|
run_pass(func, "simplify_algebra", function() {
|
|
return simplify_algebra(func, log)
|
|
})
|
|
if (verify_fn) verify_fn(func, "after simplify_algebra")
|
|
run_pass(func, "simplify_booleans", function() {
|
|
return simplify_booleans(func, log)
|
|
})
|
|
if (verify_fn) verify_fn(func, "after simplify_booleans")
|
|
run_pass(func, "eliminate_moves", function() {
|
|
return eliminate_moves(func, log)
|
|
})
|
|
if (verify_fn) verify_fn(func, "after eliminate_moves")
|
|
run_pass(func, "eliminate_unreachable", function() {
|
|
return eliminate_unreachable(func)
|
|
})
|
|
if (verify_fn) verify_fn(func, "after eliminate_unreachable")
|
|
run_pass(func, "eliminate_dead_jumps", function() {
|
|
return eliminate_dead_jumps(func, log)
|
|
})
|
|
if (verify_fn) verify_fn(func, "after eliminate_dead_jumps")
|
|
return null
|
|
}
|
|
|
|
// Process main function
|
|
if (ir.main != null) {
|
|
optimize_function(ir.main, log)
|
|
}
|
|
|
|
// Process all sub-functions
|
|
var fi = 0
|
|
if (ir.functions != null) {
|
|
fi = 0
|
|
while (fi < length(ir.functions)) {
|
|
optimize_function(ir.functions[fi], log)
|
|
fi = fi + 1
|
|
}
|
|
}
|
|
|
|
return ir
|
|
}
|
|
|
|
return streamline
|