Files
cell/streamline.cm
2026-02-17 03:33:21 -06:00

1555 lines
44 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
}
// simplify_algebra dispatch tables
var self_true_ops = {
eq_int: true, eq_float: true, eq_text: true, eq_bool: true,
is_identical: true,
le_int: true, le_float: true, le_text: true,
ge_int: true, ge_float: true, ge_text: true
}
var self_false_ops = {
ne_int: true, ne_float: true, ne_text: true, ne_bool: true,
lt_int: true, lt_float: true, lt_text: true,
gt_int: true, gt_float: true, gt_text: true
}
var no_clear_ops = {
int: true, access: true, true: true, false: true, move: true, null: true,
jump: true, jump_true: true, jump_false: true, jump_not_null: true,
return: true, disrupt: true,
store_field: true, store_index: true, store_dynamic: true,
push: true, setarg: true, invoke: true, tail_invoke: true
}
// --- 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
}
// track_types reuses write_rules table; move handled specially
var track_types = function(slot_types, instr) {
var op = instr[0]
var rule = null
var src_type = null
var typ = null
if (op == "move") {
src_type = slot_types[instr[2]]
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
return null
}
rule = write_rules[op]
if (rule != null) {
typ = rule[1]
if (typ == null) {
typ = access_value_type(instr[2])
}
slot_types[instr[rule[0]]] = typ
}
return null
}
var slot_is = function(slot_types, slot, typ) {
var known = slot_types[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 existing = null
if (!is_number(slot)) {
return null
}
existing = backward_types[slot]
if (existing == null) {
backward_types[slot] = typ
} else if (existing != typ && existing != T_UNKNOWN) {
if ((existing == T_INT || existing == T_FLOAT) && typ == T_NUM) {
backward_types[slot] = T_NUM
} else if (existing == T_NUM && (typ == T_INT || typ == T_FLOAT)) {
// Keep wider T_NUM
} else if ((existing == T_INT && typ == T_FLOAT) || (existing == T_FLOAT && typ == T_INT)) {
backward_types[slot] = T_NUM
} else {
backward_types[slot] = T_UNKNOWN
}
}
return null
}
var seed_params = function(slot_types, param_types, nr_args) {
var j = 1
while (j <= nr_args) {
if (param_types[j] != null) {
slot_types[j] = param_types[j]
}
j = j + 1
}
return null
}
var seed_writes = function(slot_types, write_types) {
var k = 0
while (k < length(write_types)) {
if (write_types[k] != null) {
slot_types[k] = write_types[k]
}
k = k + 1
}
return null
}
// =========================================================
// Pass: infer_param_types — backward type inference
// Scans typed operators to infer immutable parameter types.
// Uses data-driven dispatch: each rule is [pos1, type1] or
// [pos1, type1, pos2, type2] for operand positions to merge.
// =========================================================
var param_rules = {
add: [2, T_NUM, 3, T_NUM],
subtract: [2, T_NUM, 3, T_NUM], multiply: [2, T_NUM, 3, T_NUM],
divide: [2, T_NUM, 3, T_NUM], modulo: [2, T_NUM, 3, T_NUM],
pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM],
bitand: [2, T_INT, 3, T_INT], bitor: [2, T_INT, 3, T_INT],
bitxor: [2, T_INT, 3, T_INT], shl: [2, T_INT, 3, T_INT],
shr: [2, T_INT, 3, T_INT], ushr: [2, T_INT, 3, T_INT],
bitnot: [2, T_INT],
concat: [2, T_TEXT, 3, T_TEXT],
not: [2, T_BOOL], and: [2, T_BOOL, 3, T_BOOL], or: [2, T_BOOL, 3, T_BOOL],
store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD],
push: [1, T_ARRAY],
load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD],
pop: [2, T_ARRAY]
}
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 bt = null
var rule = null
if (instructions == null || nr_args == 0) {
return array(func.nr_slots)
}
num_instr = length(instructions)
backward_types = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr)) {
rule = param_rules[instr[0]]
if (rule != null) {
merge_backward(backward_types, instr[rule[0]], rule[1])
if (length(rule) > 2) {
merge_backward(backward_types, instr[rule[2]], rule[3])
}
}
}
i = i + 1
}
param_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
bt = backward_types[j]
if (bt != null && bt != T_UNKNOWN) {
param_types[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.
// Uses data-driven dispatch: each rule is [dest_pos, type].
// =========================================================
var write_rules = {
int: [1, T_INT], true: [1, T_BOOL], false: [1, T_BOOL],
null: [1, T_NULL], access: [1, null],
array: [1, T_ARRAY], record: [1, T_RECORD],
function: [1, T_FUNCTION], length: [1, T_INT],
bitnot: [1, T_INT], bitand: [1, T_INT], bitor: [1, T_INT],
bitxor: [1, T_INT], shl: [1, T_INT], shr: [1, T_INT], ushr: [1, T_INT],
negate: [1, T_NUM], concat: [1, T_TEXT],
eq: [1, T_BOOL], ne: [1, T_BOOL], lt: [1, T_BOOL],
le: [1, T_BOOL], gt: [1, T_BOOL], ge: [1, T_BOOL], in: [1, T_BOOL],
add: [1, T_NUM], subtract: [1, T_NUM], multiply: [1, T_NUM],
divide: [1, T_NUM], modulo: [1, T_NUM], pow: [1, T_NUM],
move: [1, T_UNKNOWN], load_field: [1, T_UNKNOWN],
load_index: [1, T_UNKNOWN], load_dynamic: [1, T_UNKNOWN],
pop: [1, T_UNKNOWN], get: [1, T_UNKNOWN],
invoke: [2, T_UNKNOWN], tail_invoke: [2, T_UNKNOWN],
eq_int: [1, T_BOOL], ne_int: [1, T_BOOL], lt_int: [1, T_BOOL],
gt_int: [1, T_BOOL], le_int: [1, T_BOOL], ge_int: [1, T_BOOL],
eq_float: [1, T_BOOL], ne_float: [1, T_BOOL], lt_float: [1, T_BOOL],
gt_float: [1, T_BOOL], le_float: [1, T_BOOL], ge_float: [1, T_BOOL],
eq_text: [1, T_BOOL], ne_text: [1, T_BOOL], lt_text: [1, T_BOOL],
gt_text: [1, T_BOOL], le_text: [1, T_BOOL], ge_text: [1, T_BOOL],
eq_bool: [1, T_BOOL], ne_bool: [1, T_BOOL],
eq_tol: [1, T_BOOL], ne_tol: [1, T_BOOL],
not: [1, T_BOOL], and: [1, T_BOOL], or: [1, T_BOOL],
is_int: [1, T_BOOL], is_text: [1, T_BOOL], is_num: [1, T_BOOL],
is_bool: [1, T_BOOL], is_null: [1, T_BOOL], is_identical: [1, T_BOOL],
is_array: [1, T_BOOL], is_func: [1, T_BOOL],
is_record: [1, T_BOOL], is_stone: [1, T_BOOL]
}
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 i = 0
var k = 0
var instr = null
var slot = 0
var typ = null
var rule = null
if (instructions == null) {
return array(func.nr_slots)
}
num_instr = length(instructions)
write_types = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_array(instr)) {
rule = write_rules[instr[0]]
if (rule != null) {
slot = instr[rule[0]]
typ = rule[1]
if (typ == null) {
typ = access_value_type(instr[2])
}
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ)
}
}
}
i = i + 1
}
// Filter to only slots with known (non-unknown) types
k = 0
while (k < length(write_types)) {
if (write_types[k] == T_UNKNOWN) {
write_types[k] = null
}
k = k + 1
}
return write_types
}
// =========================================================
// 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 num_instr = 0
var base_types = null
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)
// Pre-compute base types: params + write-invariant types
base_types = array(func.nr_slots)
j = 1
while (j <= nr_args) {
if (param_types[j] != null) {
base_types[j] = param_types[j]
}
j = j + 1
}
j = 0
while (j < length(write_types)) {
if (write_types[j] != null) {
base_types[j] = write_types[j]
}
j = j + 1
}
slot_types = array(base_types)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_text(instr)) {
slot_types = array(base_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[src], checked_type: checked_type}
}
}
slot_types[dest] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[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[dest] = T_BOOL
i = i + 2
continue
}
if ((checked_type == T_INT || checked_type == T_FLOAT) && src_known == T_NUM) {
// T_NUM could be int or float — not a mismatch, keep check
slot_types[dest] = T_BOOL
slot_types[src] = checked_type
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[dest] = T_UNKNOWN
i = i + 2
continue
}
slot_types[dest] = T_BOOL
slot_types[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[src], checked_type: checked_type}
}
}
slot_types[dest] = T_BOOL
i = i + 2
continue
}
src_known = slot_types[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[dest] = T_BOOL
i = i + 2
continue
}
if ((checked_type == T_INT || checked_type == T_FLOAT) && src_known == T_NUM) {
// T_NUM could be int or float — not a mismatch, keep check
slot_types[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[dest] = T_BOOL
i = i + 2
continue
}
slot_types[dest] = T_BOOL
i = i + 2
continue
}
}
slot_types[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[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[instr[3]]}
}
}
}
slot_types[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[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[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 = array(func.nr_slots)
i = 0
while (i < num_instr) {
instr = instructions[i]
if (is_text(instr)) {
slot_values = array(func.nr_slots)
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[instr[1]] = instr[2]
} else if (op == "access" && is_number(instr[2])) {
slot_values[instr[1]] = instr[2]
} else if (op == "true") {
slot_values[instr[1]] = true
} else if (op == "false") {
slot_values[instr[1]] = false
} else if (op == "move") {
sv = slot_values[instr[2]]
if (sv != null) {
slot_values[instr[1]] = sv
} else {
slot_values[instr[1]] = null
}
}
// Same-slot comparisons
if (is_number(instr[2]) && instr[2] == instr[3]) {
if (self_true_ops[op] == true) {
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[instr[1]] = true
i = i + 1
continue
}
if (self_false_ops[op] == true) {
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[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[instr[2]] = null
} else if (no_clear_ops[op] != true) {
if (is_number(instr[1])) {
slot_values[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
}
// =========================================================
// Pass: compress_slots — linear-scan register allocation
// Reuses slots with non-overlapping live ranges to reduce
// nr_slots. Mirrors mcode_compress_regs from mach.c.
// Works across all functions for captured-slot tracking.
// =========================================================
// Which instruction positions hold slot references (special cases)
var slot_idx_special = {
get: [1], put: [1],
access: [1], int: [1], function: [1], regexp: [1],
true: [1], false: [1], null: [1],
record: [1], array: [1],
invoke: [1, 2], tail_invoke: [1, 2],
goinvoke: [1],
setarg: [1, 3],
frame: [1, 2], goframe: [1, 2],
jump: [], disrupt: [],
jump_true: [1], jump_false: [1], jump_not_null: [1],
return: [1]
}
var get_slot_refs = function(instr) {
var special = slot_idx_special[instr[0]]
var result = null
var j = 0
var limit = 0
if (special != null) return special
result = []
limit = length(instr) - 2
j = 1
while (j < limit) {
if (is_number(instr[j])) result[] = j
j = j + 1
}
return result
}
var compress_one_fn = function(func, captured_slots) {
var instructions = func.instructions
var nr_slots = func.nr_slots
var nr_args = func.nr_args != null ? func.nr_args : 0
var n = 0
var pinned = 0
var first_ref = null
var last_ref = null
var i = 0
var j = 0
var k = 0
var s = 0
var instr = null
var refs = null
var op = null
var target = null
var tpos = 0
var changed = false
var label_map = null
var live_slots = null
var live_first = null
var live_last = null
var cnt = 0
var key_s = 0
var key_f = 0
var key_l = 0
var remap = null
var pool = null
var next_phys = 0
var active_phys = null
var active_last = null
var phys = 0
var mi = 0
var new_max = 0
var old_val = 0
var new_active_phys = null
var new_active_last = null
var new_pool = null
if (instructions == null || !is_number(nr_slots) || nr_slots <= 1) return null
n = length(instructions)
pinned = 1 + nr_args
// Step 1: build live ranges
first_ref = array(nr_slots, -1)
last_ref = array(nr_slots, -1)
// Pin this + args
k = 0
while (k < pinned) {
first_ref[k] = 0
last_ref[k] = n
k = k + 1
}
// Scan instructions for slot references
i = 0
while (i < n) {
instr = instructions[i]
if (is_array(instr)) {
refs = get_slot_refs(instr)
j = 0
while (j < length(refs)) {
s = instr[refs[j]]
if (is_number(s) && s >= 0 && s < nr_slots) {
if (first_ref[s] < 0) first_ref[s] = i
last_ref[s] = i
}
j = j + 1
}
}
i = i + 1
}
// Pin captured slots (AFTER scan so last_ref isn't overwritten)
if (captured_slots != null) {
k = 0
while (k < length(captured_slots)) {
s = captured_slots[k]
if (s >= 0 && s < nr_slots) {
if (first_ref[s] < 0) first_ref[s] = 0
last_ref[s] = n
}
k = k + 1
}
}
// Step 1b: extend for backward jumps (loops)
label_map = {}
i = 0
while (i < n) {
instr = instructions[i]
if (is_text(instr) && !starts_with(instr, "_nop_")) {
label_map[instr] = i
}
i = i + 1
}
changed = true
while (changed) {
changed = false
i = 0
while (i < n) {
instr = instructions[i]
if (!is_array(instr)) {
i = i + 1
continue
}
op = instr[0]
target = null
if (op == "jump") {
target = instr[1]
} else if (op == "jump_true" || op == "jump_false" || op == "jump_not_null") {
target = instr[2]
}
if (target == null || !is_text(target)) {
i = i + 1
continue
}
tpos = label_map[target]
if (tpos == null || tpos >= i) {
i = i + 1
continue
}
// Backward jump: extend slots live into loop
s = pinned
while (s < nr_slots) {
if (first_ref[s] >= 0 && first_ref[s] < tpos && last_ref[s] >= tpos && last_ref[s] < i) {
last_ref[s] = i
changed = true
}
s = s + 1
}
i = i + 1
}
}
// Step 2: sort live intervals by first_ref
live_slots = []
live_first = []
live_last = []
s = pinned
while (s < nr_slots) {
if (first_ref[s] >= 0) {
live_slots[] = s
live_first[] = first_ref[s]
live_last[] = last_ref[s]
}
s = s + 1
}
cnt = length(live_slots)
i = 1
while (i < cnt) {
key_s = live_slots[i]
key_f = live_first[i]
key_l = live_last[i]
j = i - 1
while (j >= 0 && (live_first[j] > key_f || (live_first[j] == key_f && live_slots[j] > key_s))) {
live_slots[j + 1] = live_slots[j]
live_first[j + 1] = live_first[j]
live_last[j + 1] = live_last[j]
j = j - 1
}
live_slots[j + 1] = key_s
live_first[j + 1] = key_f
live_last[j + 1] = key_l
i = i + 1
}
// Linear-scan allocation
remap = array(nr_slots)
s = 0
while (s < nr_slots) {
remap[s] = s
s = s + 1
}
pool = []
next_phys = pinned
active_phys = []
active_last = []
i = 0
while (i < cnt) {
// Expire intervals whose last < live_first[i]
new_active_phys = []
new_active_last = []
j = 0
while (j < length(active_phys)) {
if (active_last[j] < live_first[i]) {
pool[] = active_phys[j]
} else {
new_active_phys[] = active_phys[j]
new_active_last[] = active_last[j]
}
j = j + 1
}
active_phys = new_active_phys
active_last = new_active_last
// Pick lowest available physical register
if (length(pool) > 0) {
mi = 0
j = 1
while (j < length(pool)) {
if (pool[j] < pool[mi]) mi = j
j = j + 1
}
phys = pool[mi]
new_pool = []
j = 0
while (j < length(pool)) {
if (j != mi) new_pool[] = pool[j]
j = j + 1
}
pool = new_pool
} else {
phys = next_phys
next_phys = next_phys + 1
}
remap[live_slots[i]] = phys
active_phys[] = phys
active_last[] = live_last[i]
i = i + 1
}
// Compute new nr_slots
new_max = pinned
s = 0
while (s < nr_slots) {
if (first_ref[s] >= 0 && remap[s] >= new_max) {
new_max = remap[s] + 1
}
s = s + 1
}
if (new_max >= nr_slots) return null
// Step 3: apply remap to instructions
i = 0
while (i < n) {
instr = instructions[i]
if (is_array(instr)) {
refs = get_slot_refs(instr)
j = 0
while (j < length(refs)) {
old_val = instr[refs[j]]
if (is_number(old_val) && old_val >= 0 && old_val < nr_slots) {
instr[refs[j]] = remap[old_val]
}
j = j + 1
}
}
i = i + 1
}
func.nr_slots = new_max
return remap
}
var compress_slots = function(ir) {
if (ir == null || ir.main == null) return null
var functions = ir.functions != null ? ir.functions : []
var func_count = length(functions)
var parent_of = null
var captured = null
var remaps = null
var remap_sizes = null
var instrs = null
var instr = null
var child_idx = 0
var parent_slot = 0
var level = 0
var ancestor = 0
var caps = null
var found = false
var anc_remap = null
var old_slot = 0
var fi = 0
var i = 0
var j = 0
var k = 0
// Build parent_of: parent_of[i] = parent index, func_count = main
parent_of = array(func_count, -1)
// Scan main for function instructions
if (ir.main != null && ir.main.instructions != null) {
instrs = ir.main.instructions
i = 0
while (i < length(instrs)) {
instr = instrs[i]
if (is_array(instr) && instr[0] == "function") {
child_idx = instr[2]
if (child_idx >= 0 && child_idx < func_count) {
parent_of[child_idx] = func_count
}
}
i = i + 1
}
}
// Scan each function for function instructions
fi = 0
while (fi < func_count) {
instrs = functions[fi].instructions
if (instrs != null) {
i = 0
while (i < length(instrs)) {
instr = instrs[i]
if (is_array(instr) && instr[0] == "function") {
child_idx = instr[2]
if (child_idx >= 0 && child_idx < func_count) {
parent_of[child_idx] = fi
}
}
i = i + 1
}
}
fi = fi + 1
}
// Build captured slots per function
captured = array(func_count + 1)
i = 0
while (i < func_count + 1) {
captured[i] = []
i = i + 1
}
fi = 0
while (fi < func_count) {
instrs = functions[fi].instructions
if (instrs != null) {
i = 0
while (i < length(instrs)) {
instr = instrs[i]
if (is_array(instr) && (instr[0] == "get" || instr[0] == "put")) {
parent_slot = instr[2]
level = instr[3]
ancestor = fi
j = 0
while (j < level && ancestor >= 0) {
ancestor = parent_of[ancestor]
j = j + 1
}
if (ancestor >= 0) {
caps = captured[ancestor]
found = false
k = 0
while (k < length(caps)) {
if (caps[k] == parent_slot) {
found = true
k = length(caps)
}
k = k + 1
}
if (!found) caps[] = parent_slot
}
}
i = i + 1
}
}
fi = fi + 1
}
// Compress each function and save remap tables
remaps = array(func_count + 1)
remap_sizes = array(func_count + 1, 0)
fi = 0
while (fi < func_count) {
remap_sizes[fi] = functions[fi].nr_slots
remaps[fi] = compress_one_fn(functions[fi], captured[fi])
fi = fi + 1
}
if (ir.main != null) {
remap_sizes[func_count] = ir.main.nr_slots
remaps[func_count] = compress_one_fn(ir.main, captured[func_count])
}
// Fix get/put parent_slot references using ancestor remap tables
fi = 0
while (fi < func_count) {
instrs = functions[fi].instructions
if (instrs != null) {
i = 0
while (i < length(instrs)) {
instr = instrs[i]
if (is_array(instr) && (instr[0] == "get" || instr[0] == "put")) {
level = instr[3]
ancestor = fi
j = 0
while (j < level && ancestor >= 0) {
ancestor = parent_of[ancestor]
j = j + 1
}
if (ancestor >= 0 && remaps[ancestor] != null) {
anc_remap = remaps[ancestor]
old_slot = instr[2]
if (old_slot >= 0 && old_slot < remap_sizes[ancestor]) {
instr[2] = anc_remap[old_slot]
}
}
}
i = i + 1
}
}
fi = fi + 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
}
}
// Compress slots across all functions (must run after per-function passes)
compress_slots(ir)
return ir
}
return streamline