// 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)) { log.error(`[verify_ir] ${errs[i]}`) i = i + 1 } if (length(errs) > 0) { log.error(`[verify_ir] ${text(length(errs))} errors after ${pass_name}`) } } } // 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, remainder: true, max: true, min: true, pow: true } var bool_result_ops = { eq: true, ne: true, lt: true, gt: true, le: true, ge: true, eq_tol: true, ne_tol: true, in: 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, is_blob: true, is_data: true, is_true: true, is_false: true, is_fit: true, is_char: true, is_digit: true, is_letter: true, is_lower: true, is_upper: true, is_ws: true, is_actor: 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, is_blob: T_BLOB } // simplify_algebra dispatch tables var self_true_ops = { eq: true, is_identical: true, le: true, ge: true } var self_false_ops = { ne: true, lt: true, gt: 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, wary_true: true, wary_false: true, jump_null: true, jump_empty: true, return: true, disrupt: true, store_field: true, store_index: true, store_dynamic: true, push: true, setarg: true, invoke: true, tail_invoke: true, stone_text: true } var is_cond_jump = function(op) { return op == "jump_true" || op == "jump_false" || op == "jump_not_null" || op == "wary_true" || op == "wary_false" || op == "jump_null" || op == "jump_empty" } // --- 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 // Ops safe to narrow from T_NUM to T_INT when both operands are T_INT. // Excludes divide (int/int can produce float) and pow (int**neg produces float). var int_narrowable_ops = { add: true, subtract: true, multiply: true, remainder: true, modulo: true, max: true, min: true } 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 } if (op == "load_index") { slot_types[instr[2]] = T_ARRAY slot_types[instr[3]] = T_INT } else if (op == "store_index") { slot_types[instr[1]] = T_ARRAY slot_types[instr[3]] = T_INT } else if (op == "load_field") { slot_types[instr[2]] = T_RECORD } else if (op == "store_field") { slot_types[instr[1]] = T_RECORD } else if (op == "push") { slot_types[instr[1]] = T_ARRAY } else if (op == "pop") { slot_types[instr[2]] = T_ARRAY } rule = write_rules[op] if (rule != null) { typ = rule[1] if (typ == null) { typ = access_value_type(instr[2]) } // Narrow T_NUM to T_INT when both operands are T_INT if (typ == T_NUM && instr[3] != null && int_narrowable_ops[op] == true) { if (slot_is(slot_types, instr[2], T_INT) && slot_is(slot_types, instr[3], T_INT)) { typ = T_INT } } 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], remainder: [2, T_NUM, 3, T_NUM], max: [2, T_NUM, 3, T_NUM], min: [2, T_NUM, 3, T_NUM], pow: [2, T_NUM, 3, T_NUM], negate: [2, T_NUM], abs: [2, T_NUM], sign: [2, T_NUM], fraction: [2, T_NUM], integer: [2, T_NUM], floor: [2, T_NUM], ceiling: [2, T_NUM], round: [2, T_NUM], trunc: [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], 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 iter = 0 var instr = null var bt = null var src = 0 var dst = 0 var old_bt = null var changed = false 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 } // Propagate typed constraints backward through move chains. changed = true iter = 0 while (changed && iter < num_instr + 4) { changed = false i = 0 while (i < num_instr) { instr = instructions[i] if (is_array(instr) && instr[0] == "move") { dst = instr[1] src = instr[2] bt = backward_types[dst] if (bt != null && bt != T_UNKNOWN) { old_bt = backward_types[src] merge_backward(backward_types, src, bt) if (backward_types[src] != old_bt) { changed = true } } } i = i + 1 } iter = iter + 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], abs: [1, T_NUM], sign: [1, T_INT], fraction: [1, T_NUM], integer: [1, T_NUM], floor: [1, T_NUM], ceiling: [1, T_NUM], round: [1, T_NUM], trunc: [1, T_NUM], 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], remainder: [1, T_NUM], max: [1, T_NUM], min: [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_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], is_blob: [1, T_BOOL], is_data: [1, T_BOOL], is_true: [1, T_BOOL], is_false: [1, T_BOOL], is_fit: [1, T_BOOL], is_char: [1, T_BOOL], is_digit: [1, T_BOOL], is_letter: [1, T_BOOL], is_lower: [1, T_BOOL], is_upper: [1, T_BOOL], is_ws: [1, T_BOOL], is_actor: [1, T_BOOL] } // Known intrinsic return types for invoke result inference. var intrinsic_return_types = { abs: T_NUM, floor: T_NUM, ceiling: T_NUM, round: T_NUM, trunc: T_NUM, fraction: T_NUM, integer: T_NUM, whole: T_NUM, sign: T_NUM, max: T_NUM, min: T_NUM, remainder: T_NUM, modulo: T_NUM, is_integer: T_BOOL, is_text: T_BOOL, is_number: T_BOOL, is_null: T_BOOL, is_array: T_BOOL, is_function: T_BOOL, is_object: T_BOOL, is_logical: T_BOOL, is_stone: T_BOOL, is_blob: T_BOOL, starts_with: T_BOOL, ends_with: T_BOOL, some: T_BOOL, every: T_BOOL } var narrow_arith_type = function(write_types, param_types, instr, typ) { var s2 = null var s3 = null var t2 = null var t3 = null if (typ != T_NUM || instr[3] == null || int_narrowable_ops[instr[0]] != true) { return typ } s2 = instr[2] s3 = instr[3] if (is_number(s2)) { t2 = write_types[s2] if (t2 == null && param_types != null && s2 < length(param_types)) { t2 = param_types[s2] } } if (is_number(s3)) { t3 = write_types[s3] if (t3 == null && param_types != null && s3 < length(param_types)) { t3 = param_types[s3] } } if (t2 == T_INT && t3 == T_INT) { return T_INT } return typ } var infer_slot_write_types = function(func, param_types) { var instructions = func.instructions var nr_args = func.nr_args != null ? func.nr_args : 0 var num_instr = 0 var write_types = null var frame_callee = null var intrinsic_slots = null var move_dests = null var move_srcs = null var i = 0 var k = 0 var iter = 0 var instr = null var op = null var src = 0 var slot = 0 var old_typ = null var src_typ = null var typ = null var callee_slot = null var changed = false var rule = null var cw_keys = null if (instructions == null) { return array(func.nr_slots) } num_instr = length(instructions) write_types = array(func.nr_slots) frame_callee = array(func.nr_slots) intrinsic_slots = array(func.nr_slots) move_dests = [] move_srcs = [] i = 0 while (i < num_instr) { instr = instructions[i] if (is_array(instr)) { op = instr[0] if (op == "access") { slot = instr[1] if (slot > 0 && slot > nr_args) { merge_backward(write_types, slot, access_value_type(instr[2])) } if (is_object(instr[2]) && instr[2].make == "intrinsic") { typ = intrinsic_return_types[instr[2].name] if (typ != null && slot >= 0 && slot < length(intrinsic_slots)) { intrinsic_slots[slot] = typ } } i = i + 1 continue } if (op == "move") { slot = instr[1] if (slot > 0 && slot > nr_args) { move_dests[] = slot move_srcs[] = instr[2] } i = i + 1 continue } if (op == "frame" || op == "goframe") { if (is_number(instr[1]) && instr[1] >= 0 && instr[1] < length(frame_callee)) { frame_callee[instr[1]] = instr[2] } i = i + 1 continue } if (op == "invoke" || op == "tail_invoke") { slot = instr[2] typ = T_UNKNOWN callee_slot = frame_callee[instr[1]] if (is_number(callee_slot) && callee_slot >= 0 && callee_slot < length(intrinsic_slots)) { if (intrinsic_slots[callee_slot] != null) { typ = intrinsic_slots[callee_slot] } } if (slot > 0 && slot > nr_args) { merge_backward(write_types, slot, typ) } i = i + 1 continue } if (op == "get" && func._closure_slot_types != null) { slot = instr[1] typ = T_UNKNOWN src_typ = func._closure_slot_types[text(instr[2]) + "_" + text(instr[3])] if (src_typ != null) { typ = src_typ } if (slot > 0 && slot > nr_args) { merge_backward(write_types, slot, typ) } i = i + 1 continue } rule = write_rules[op] if (rule != null) { slot = instr[rule[0]] typ = rule[1] if (typ == null) { typ = access_value_type(instr[2]) } typ = narrow_arith_type(write_types, param_types, instr, typ) if (slot > 0 && slot > nr_args) { merge_backward(write_types, slot, typ) } } } i = i + 1 } // Resolve move writes from known source invariants (fixed-point). changed = true iter = 0 while (changed && iter < length(write_types) + 4) { changed = false k = 0 while (k < length(move_dests)) { slot = move_dests[k] src = move_srcs[k] src_typ = null if (is_number(src) && src >= 0) { if (src < length(write_types) && write_types[src] != null) { src_typ = write_types[src] } else if (param_types != null && src < length(param_types) && param_types[src] != null) { src_typ = param_types[src] } } if (src_typ != null) { old_typ = write_types[slot] merge_backward(write_types, slot, src_typ) if (write_types[slot] != old_typ) { changed = true } } k = k + 1 } iter = iter + 1 } // Any remaining unresolved move write can carry arbitrary type. k = 0 while (k < length(move_dests)) { slot = move_dests[k] src = move_srcs[k] src_typ = null if (is_number(src) && src >= 0) { if (src < length(write_types) && write_types[src] != null) { src_typ = write_types[src] } else if (param_types != null && src < length(param_types) && param_types[src] != null) { src_typ = param_types[src] } } if (src_typ == null && slot > 0 && slot > nr_args) { merge_backward(write_types, slot, T_UNKNOWN) } k = k + 1 } // Closure-written slots can have any type at runtime — mark unknown if (func.closure_written != null) { cw_keys = array(func.closure_written) k = 0 while (k < length(cw_keys)) { slot = number(cw_keys[k]) if (slot >= 0 && slot < length(write_types)) { write_types[slot] = T_UNKNOWN } k = k + 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_/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] // is_null + jump fusion: replace with jump_null / jump_not_null if (op == "is_null" && (next_op == "jump_true" || next_op == "wary_true") && next[1] == dest) { jlen = length(next) nc = nc + 1 instructions[i] = "_nop_tc_" + text(nc) instructions[i + 1] = ["jump_null", src, next[2], next[jlen - 2], next[jlen - 1]] if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "is_null_jump_fusion", at: i, before: [instr, next], after: [instructions[i], instructions[i + 1]], why: {slot: src, fused_to: "jump_null"} } } slot_types[dest] = T_BOOL i = i + 2 continue } if (op == "is_null" && (next_op == "jump_false" || next_op == "wary_false") && next[1] == dest) { jlen = length(next) nc = nc + 1 instructions[i] = "_nop_tc_" + text(nc) instructions[i + 1] = ["jump_not_null", src, next[2], next[jlen - 2], next[jlen - 1]] if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "is_null_jump_fusion", at: i, before: [instr, next], after: [instructions[i], instructions[i + 1]], why: {slot: src, fused_to: "jump_not_null"} } } slot_types[dest] = T_BOOL i = i + 2 continue } if ((next_op == "jump_false" || next_op == "wary_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_op == "wary_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[2], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) { instr[0] = "load_field" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "dynamic_record_to_field", at: i, before: old_op, after: instr[0], why: { object_slot: instr[2], object_type: slot_types[instr[2]], key_slot: instr[3], key_type: slot_types[instr[3]] } } } } else if (slot_is(slot_types, instr[2], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) { instr[0] = "load_index" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "dynamic_array_to_index", at: i, before: old_op, after: instr[0], why: { object_slot: instr[2], object_type: slot_types[instr[2]], key_slot: instr[3], key_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[1], T_RECORD) && slot_is(slot_types, instr[3], T_TEXT)) { instr[0] = "store_field" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "dynamic_record_to_field", at: i, before: old_op, after: instr[0], why: { object_slot: instr[1], object_type: slot_types[instr[1]], key_slot: instr[3], key_type: slot_types[instr[3]] } } } } else if (slot_is(slot_types, instr[1], T_ARRAY) && slot_is(slot_types, instr[3], T_INT)) { instr[0] = "store_index" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "dynamic_array_to_index", at: i, before: old_op, after: instr[0], why: { object_slot: instr[1], object_type: slot_types[instr[1]], key_slot: instr[3], key_type: slot_types[instr[3]] } } } } i = i + 1 continue } // Wary-to-certain: if slot type is T_BOOL, downgrade wary to certain jump if (op == "wary_true" && slot_is(slot_types, instr[1], T_BOOL)) { instr[0] = "jump_true" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "wary_to_certain", at: i, before: "wary_true", after: "jump_true", why: {slot: instr[1], known_type: T_BOOL} } } } if (op == "wary_false" && slot_is(slot_types, instr[1], T_BOOL)) { instr[0] = "jump_false" if (events != null) { events[] = { event: "rewrite", pass: "eliminate_type_checks", rule: "wary_to_certain", at: i, before: "wary_false", after: "jump_false", why: {slot: instr[1], known_type: T_BOOL} } } } 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 → wary_true x, label // (removing `not` removes coercion, so result must be wary) if (next_op == "jump_false" && next[1] == instr[1]) { nc = nc + 1 instructions[i] = "_nop_bl_" + text(nc) instructions[i + 1] = ["wary_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 → wary_false x, label if (next_op == "jump_true" && next[1] == instr[1]) { nc = nc + 1 instructions[i] = "_nop_bl_" + text(nc) instructions[i + 1] = ["wary_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 d, x; wary_false d, label → wary_true x, label if (next_op == "wary_false" && next[1] == instr[1]) { nc = nc + 1 instructions[i] = "_nop_bl_" + text(nc) instructions[i + 1] = ["wary_true", instr[2], next[2], next[nlen - 2], next[nlen - 1]] if (events != null) { events[] = { event: "rewrite", pass: "simplify_booleans", rule: "not_wary_false_fusion", at: i, before: [instr, next], after: [instructions[i], instructions[i + 1]] } } i = i + 2 continue } // not d, x; wary_true d, label → wary_false x, label if (next_op == "wary_true" && next[1] == instr[1]) { nc = nc + 1 instructions[i] = "_nop_bl_" + text(nc) instructions[i + 1] = ["wary_false", instr[2], next[2], next[nlen - 2], next[nlen - 1]] if (events != null) { events[] = { event: "rewrite", pass: "simplify_booleans", rule: "not_wary_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 — copy propagation + self-move nop // Tracks move chains within basic blocks, substitutes read // operands to use the original source, and nops self-moves. // ========================================================= 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 var copies = null var key = null var actual = null var dest = 0 var src = 0 var wr = null var write_pos = null var op = null var j = 0 var k = 0 var keys = null var special = null var limit = 0 if (instructions == null || length(instructions) == 0) { return null } if (log != null && log.events != null) { events = log.events } copies = {} num_instr = length(instructions) i = 0 while (i < num_instr) { instr = instructions[i] // Labels: clear copies at join points if (is_text(instr)) { if (!starts_with(instr, "_nop_")) { copies = {} } i = i + 1 continue } if (!is_array(instr)) { i = i + 1 continue } op = instr[0] // Control flow without reads: clear copies if (op == "jump" || op == "disrupt") { copies = {} i = i + 1 continue } // Control flow with a read at position 1: substitute then clear if (op == "return" || is_cond_jump(op)) { actual = copies[text(instr[1])] if (actual != null) { instr[1] = actual } copies = {} i = i + 1 continue } // Move: copy propagation if (op == "move") { dest = instr[1] src = instr[2] // Follow transitive chain for src actual = copies[text(src)] if (actual == null) { actual = src } // Rewrite the move's src operand instr[2] = actual // Kill stale entries for dest key = text(dest) copies[key] = null keys = array(copies) k = 0 while (k < length(keys)) { if (copies[keys[k]] == dest) { copies[keys[k]] = null } k = k + 1 } // Record the new copy copies[text(dest)] = actual // Self-move after substitution → nop if (dest == actual) { nc = nc + 1 instructions[i] = "_nop_mv_" + text(nc) if (events != null) { events[] = { event: "rewrite", pass: "eliminate_moves", rule: "self_move", at: i, before: ["move", dest, src], after: instructions[i] } } } i = i + 1 continue } // General instruction: substitute reads, then kill write wr = write_rules[op] write_pos = null if (wr != null) { write_pos = wr[0] } // Substitute read operands special = slot_idx_special[op] if (special != null) { j = 0 while (j < length(special)) { k = special[j] if (k != write_pos && is_number(instr[k])) { actual = copies[text(instr[k])] if (actual != null) { instr[k] = actual } } j = j + 1 } } else { limit = length(instr) - 2 j = 1 while (j < limit) { if (j != write_pos && is_number(instr[j])) { actual = copies[text(instr[j])] if (actual != null) { instr[j] = actual } } j = j + 1 } } // Kill write destination if (write_pos != null && is_number(instr[write_pos])) { dest = instr[write_pos] key = text(dest) copies[key] = null keys = array(copies) k = 0 while (k < length(keys)) { if (copies[keys[k]] == dest) { copies[keys[k]] = null } k = k + 1 } } i = i + 1 } return null } // ========================================================= // Pass: insert_stone_text — freeze mutable text at escape points // Only inserts stone_text when the slot is provably T_TEXT. // Escape points: setfield, setindex, store_field, store_index, // store_dynamic, push, setarg, put (value leaving its slot). // move: stone source only if source is still live after the move. // ========================================================= // Map: escape opcode → index of the escaping slot in the instruction var escape_slot_index = { setfield: 3, setindex: 3, store_field: 3, store_index: 3, store_dynamic: 3, push: 2, setarg: 3, put: 1 } // Build last_ref liveness array for a function's instructions. // Returns array where last_ref[slot] = last instruction index referencing that slot. // Uses get_slot_refs to only visit actual slot reference positions. var build_slot_liveness = function(instructions, nr_slots) { var last_ref = array(nr_slots, -1) var n = length(instructions) var refs = null var i = 0 var j = 0 var s = 0 var instr = null var label_map = null var changed = false var op = null var target = null var tpos = 0 // Scan instructions for slot references 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) { last_ref[s] = i } j = j + 1 } } i = i + 1 } // 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)) { target = null op = instr[0] if (op == "jump") { target = instr[1] } else if (is_cond_jump(op)) { target = instr[2] } if (target != null && is_text(target)) { tpos = label_map[target] if (tpos != null && tpos < i) { s = 0 while (s < nr_slots) { if (last_ref[s] >= 0 && last_ref[s] >= tpos && last_ref[s] < i) { last_ref[s] = i changed = true } s = s + 1 } } } } i = i + 1 } } return last_ref } var insert_stone_text = function(func, log) { var instructions = func.instructions var nr_slots = func.nr_slots var dpc = func.disruption_pc var events = null var slot_types = null var result = null var i = 0 var n = 0 var instr = null var op = null var esc = null var slot = 0 var nc = 0 var shift = 0 var last_ref = null if (instructions == null || length(instructions) == 0) { return null } if (log != null && log.events != null) { events = log.events } // Build liveness info (in separate function to stay under slot limit) last_ref = build_slot_liveness(instructions, nr_slots) // Walk instructions, tracking types, inserting stone_text n = length(instructions) slot_types = array(nr_slots, T_UNKNOWN) result = [] i = 0 while (i < n) { instr = instructions[i] if (is_array(instr)) { op = instr[0] esc = escape_slot_index[op] if (esc != null) { slot = instr[esc] if (is_number(slot) && slot_is(slot_types, slot, T_TEXT)) { result[] = ["stone_text", slot] nc = nc + 1 if (is_number(dpc) && i < dpc) shift = shift + 1 if (events != null) { events[] = { event: "insert", pass: "insert_stone_text", rule: "escape_stone", at: i, slot: slot, op: op } } } } else if (op == "move") { // Stone source before move only if source is provably text // AND source slot is still live after this instruction slot = instr[2] if (is_number(slot) && slot_is(slot_types, slot, T_TEXT) && last_ref[slot] > i) { result[] = ["stone_text", slot] nc = nc + 1 if (is_number(dpc) && i < dpc) shift = shift + 1 if (events != null) { events[] = { event: "insert", pass: "insert_stone_text", rule: "move_alias_stone", at: i, slot: slot } } } } track_types(slot_types, instr) } result[] = instr i = i + 1 } if (nc > 0) { func.instructions = result if (is_number(dpc) && shift > 0) { func.disruption_pc = dpc + shift } } 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_unreachable_cfg — nop blocks not reachable // from function entry under explicit jump control-flow. // ========================================================= var eliminate_unreachable_cfg = function(func) { var instructions = func.instructions var num_instr = 0 var disruption_pc = -1 var label_map = null var reachable = null var stack = null var sp = 0 var idx = 0 var tgt = null var instr = null var op = null var nc = 0 if (instructions == null || length(instructions) == 0) { return null } num_instr = length(instructions) if (is_number(func.disruption_pc)) { disruption_pc = func.disruption_pc } label_map = {} idx = 0 while (idx < num_instr) { instr = instructions[idx] if (is_text(instr) && !starts_with(instr, "_nop_")) { label_map[instr] = idx } idx = idx + 1 } reachable = array(num_instr, false) stack = [0] if (disruption_pc > 0 && disruption_pc < num_instr) { stack[] = disruption_pc } sp = 0 while (sp < length(stack)) { idx = stack[sp] sp = sp + 1 if (idx < 0 || idx >= num_instr || reachable[idx]) { continue } reachable[idx] = true instr = instructions[idx] if (!is_array(instr)) { stack[] = idx + 1 continue } op = instr[0] if (op == "jump") { tgt = label_map[instr[1]] if (is_number(tgt)) stack[] = tgt continue } if (is_cond_jump(op)) { tgt = label_map[instr[2]] if (is_number(tgt)) stack[] = tgt stack[] = idx + 1 continue } if (op == "return" || op == "disrupt") { continue } stack[] = idx + 1 } idx = 0 while (idx < num_instr) { if (!reachable[idx] && is_array(instructions[idx]) && (disruption_pc < 0 || idx >= disruption_pc)) { nc = nc + 1 instructions[idx] = "_nop_ucfg_" + text(nc) } idx = idx + 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 (starts_with(peek, "_nop_")) { j = j + 1 continue } 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], wary_true: [1], wary_false: [1], jump_null: [1], jump_empty: [1], return: [1], stone_text: [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 } // DEF/USE classification: which instruction positions are definitions vs uses var slot_def_special = { get: [1], put: [], access: [1], int: [1], function: [1], regexp: [1], true: [1], false: [1], null: [1], record: [1], array: [1], invoke: [2], tail_invoke: [2], goinvoke: [], move: [1], load_field: [1], load_index: [1], load_dynamic: [1], pop: [1], frame: [1], goframe: [1], setarg: [], store_field: [], store_index: [], store_dynamic: [], push: [], set_var: [], stone_text: [], jump: [], jump_true: [], jump_false: [], jump_not_null: [], wary_true: [], wary_false: [], jump_null: [], jump_empty: [], return: [], disrupt: [] } var slot_use_special = { get: [], put: [1], access: [], int: [], function: [], regexp: [], true: [], false: [], null: [], record: [], array: [], invoke: [1], tail_invoke: [1], goinvoke: [1], move: [2], load_field: [2], load_index: [2, 3], load_dynamic: [2, 3], pop: [2], frame: [2], goframe: [2], setarg: [1, 3], store_field: [1, 3], store_index: [1, 2, 3], store_dynamic: [1, 2, 3], push: [1, 2], set_var: [1], stone_text: [1], jump: [], jump_true: [1], jump_false: [1], jump_not_null: [1], wary_true: [1], wary_false: [1], jump_null: [1], jump_empty: [1], return: [1], disrupt: [] } var get_slot_defs = function(instr) { var special = slot_def_special[instr[0]] if (special != null) return special return [1] } var get_slot_uses = function(instr) { var special = slot_use_special[instr[0]] var result = null var j = 0 var limit = 0 if (special != null) return special result = [] limit = length(instr) - 2 j = 2 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 (is_cond_jump(op)) { 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 max_close = null var needed = 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 // and track the max close slot per ancestor for nr_close_slots update max_close = array(func_count + 1, -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")) { 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] } } if (ancestor >= 0 && instr[2] > max_close[ancestor]) { max_close[ancestor] = instr[2] } } i = i + 1 } } fi = fi + 1 } // Update nr_close_slots for functions whose close slots were remapped. // Frame shortening keeps 1 + nr_args + nr_close_slots slots, so // nr_close_slots must cover the highest-numbered close slot. fi = 0 while (fi < func_count) { if (max_close[fi] >= 0) { needed = max_close[fi] - (functions[fi].nr_args != null ? functions[fi].nr_args : 0) if (needed > functions[fi].nr_close_slots) { functions[fi].nr_close_slots = needed } } fi = fi + 1 } if (max_close[func_count] >= 0 && ir.main != null) { needed = max_close[func_count] - (ir.main.nr_args != null ? ir.main.nr_args : 0) if (needed > ir.main.nr_close_slots) { ir.main.nr_close_slots = needed } } return null } // ========================================================= // Pre-pass: mark closure-written slots // Scans child functions for 'put' instructions and annotates // the target ancestor func with closure_written[slot] = true. // ========================================================= var mark_closure_writes = function(ir) { var functions = ir.functions != null ? ir.functions : [] var fc = length(functions) var parent_of = array(fc, -1) var instrs = null var instr = null var fi = 0 var i = 0 var j = 0 var level = 0 var anc = 0 var slot = 0 var target = null if (fc == 0) { return null } // Build parent_of map 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") { if (instr[2] >= 0 && instr[2] < fc) { parent_of[instr[2]] = fc } } i = i + 1 } } fi = 0 while (fi < fc) { instrs = functions[fi].instructions if (instrs != null) { i = 0 while (i < length(instrs)) { instr = instrs[i] if (is_array(instr) && instr[0] == "function") { if (instr[2] >= 0 && instr[2] < fc) { parent_of[instr[2]] = fi } } i = i + 1 } } fi = fi + 1 } // Scan for 'put' instructions and mark ancestor slots fi = 0 while (fi < fc) { instrs = functions[fi].instructions if (instrs != null) { i = 0 while (i < length(instrs)) { instr = instrs[i] if (is_array(instr) && instr[0] == "put") { slot = instr[2] level = instr[3] anc = fi j = 0 while (j < level && anc >= 0) { anc = parent_of[anc] j = j + 1 } if (anc >= 0) { if (anc == fc) { target = ir.main } else { target = functions[anc] } if (target != null) { if (target.closure_written == null) { target.closure_written = {} } target.closure_written[text(slot)] = true } } } i = i + 1 } } fi = fi + 1 } ir._parent_of = parent_of ir._parent_fc = fc return null } // ========================================================= // Resolve closure slot types from parent write_types. // For each `get` in func, walk the parent chain and look up // the ancestor's inferred write type for that closure slot. // ========================================================= var resolve_closure_types = function(func, fi, ir) { var parent_of = ir._parent_of var fc = ir._parent_fc var instructions = func.instructions var num_instr = 0 var closure_types = null var i = 0 var instr = null var slot = 0 var depth = 0 var anc = 0 var j = 0 var target = null var typ = null var key = null if (instructions == null || parent_of == null) { return null } num_instr = length(instructions) closure_types = {} i = 0 while (i < num_instr) { instr = instructions[i] if (is_array(instr) && instr[0] == "get") { slot = instr[2] depth = instr[3] key = text(slot) + "_" + text(depth) if (closure_types[key] == null) { anc = fi j = 0 while (j < depth && anc >= 0) { anc = parent_of[anc] j = j + 1 } if (anc >= 0) { if (anc == fc) { target = ir.main } else { target = ir.functions[anc] } if (target != null && target._write_types != null) { typ = target._write_types[slot] if (typ != null) { closure_types[key] = typ } } } } } i = i + 1 } func._closure_slot_types = closure_types return null } // ========================================================= // Pass: diagnose_function — emit diagnostics from type info // Runs after dead code elimination on surviving instructions. // ========================================================= var diagnose_function = function(func, ctx, ir) { var param_types = ctx.param_types var write_types = ctx.write_types var instructions = func.instructions var nr_args = func.nr_args != null ? func.nr_args : 0 var num_instr = 0 var base_types = null var cur_types = null var i = 0 var j = 0 var instr = null var op = null var n = 0 var line = 0 var col = 0 var known = null var filename = ir.filename != null ? ir.filename : "" var frame_callee = {} var frame_argc = {} var callee_slot = null var obj_type = null var key_type = null var module_slots = {} var slot_arity = {} var ms_i = 0 var ms = null var exp_info = null var f_slot_key = null var cs = null var argc = null var known_arity = null var load_field_null = false // Build module_slots map from ir._module_summaries if (ir._module_summaries != null) { ms_i = 0 while (ms_i < length(ir._module_summaries)) { ms = ir._module_summaries[ms_i] module_slots[text(ms.slot)] = ms.summary ms_i = ms_i + 1 } } if (instructions == null || length(instructions) == 0) return null num_instr = length(instructions) // Pre-compute base types from params + write-invariant types base_types = array(func.nr_slots) j = 1 while (j <= nr_args) { if (param_types != null && param_types[j] != null) { base_types[j] = param_types[j] } j = j + 1 } if (write_types != null) { j = 0 while (j < length(write_types)) { if (write_types[j] != null) { base_types[j] = write_types[j] } j = j + 1 } } cur_types = array(base_types) var emit = function(severity, line, col, message) { ir._diagnostics[] = { severity: severity, file: filename, line: line, col: col, message: message } } i = 0 while (i < num_instr) { instr = instructions[i] if (is_text(instr)) { // Label — reset types to base if (!starts_with(instr, "_nop_")) { cur_types = array(base_types) } i = i + 1 continue } if (!is_array(instr)) { i = i + 1 continue } op = instr[0] n = length(instr) line = instr[n - 2] col = instr[n - 1] // Track frame/invoke correlation if (op == "frame" || op == "goframe") { frame_callee[text(instr[1])] = instr[2] if (n > 4) { frame_argc[text(instr[1])] = instr[3] } } // --- Error checks (proven to always disrupt) --- if (op == "frame" || op == "goframe") { callee_slot = instr[2] known = cur_types[callee_slot] if (known == T_NULL) { emit("error", line, col, "invoking null — will always disrupt") } else if (known != null && known != T_UNKNOWN && known != T_FUNCTION && known != T_RECORD) { emit("error", line, col, `invoking ${known} — will always disrupt`) } } if (op == "invoke" || op == "tail_invoke") { f_slot_key = text(instr[1]) cs = frame_callee[f_slot_key] argc = frame_argc[f_slot_key] if (cs != null && argc != null) { known_arity = slot_arity[text(cs)] if (known_arity != null) { if (argc > known_arity) { emit("error", line, col, `function expects ${text(known_arity)} args, called with ${text(argc)}`) } else if (argc < known_arity) { emit("warning", line, col, `function expects ${text(known_arity)} args, called with ${text(argc)}`) } } } } if (op == "store_field") { obj_type = cur_types[instr[1]] if (obj_type == T_TEXT) { emit("error", line, col, "storing property on text — text is immutable") } else if (obj_type == T_ARRAY) { emit("error", line, col, "storing named property on array — use index or push") } } if (op == "store_index") { obj_type = cur_types[instr[1]] if (obj_type == T_TEXT) { emit("error", line, col, "storing index on text — text is immutable") } else if (obj_type == T_RECORD) { emit("error", line, col, "storing numeric index on record — use text key") } } if (op == "store_dynamic") { obj_type = cur_types[instr[1]] if (obj_type == T_TEXT) { emit("error", line, col, "storing on text — text is immutable") } } if (op == "push") { obj_type = cur_types[instr[1]] if (obj_type != null && obj_type != T_UNKNOWN && obj_type != T_ARRAY) { emit("error", line, col, `push on ${obj_type} — only arrays support push`) } } // Note: arithmetic (add/subtract/etc), bitwise, and concat ops are NOT // checked here because the mcode generator emits type-dispatch guards // before these instructions. The guards ensure correct types at runtime. // --- Warning checks (likely bug) --- load_field_null = false if (op == "load_field") { obj_type = cur_types[instr[2]] if (obj_type == T_ARRAY) { emit("warning", line, col, "named property access on array — always returns null") load_field_null = true } else if (obj_type == T_TEXT) { emit("warning", line, col, "named property access on text — always returns null") load_field_null = true } // Cross-module: check if obj is a module with known exports ms = module_slots[text(instr[2])] if (ms != null && ms.exports != null && is_text(instr[3])) { exp_info = ms.exports[instr[3]] if (exp_info == null) { emit("warning", line, col, `module does not export '${instr[3]}'`) } else if (exp_info.type == "function") { cur_types[instr[1]] = T_FUNCTION slot_arity[text(instr[1])] = exp_info.arity } } } if (op == "load_dynamic") { obj_type = cur_types[instr[2]] key_type = cur_types[instr[3]] if (obj_type == T_ARRAY && key_type == T_TEXT) { emit("warning", line, col, "text key on array — always returns null") } if (obj_type == T_TEXT && key_type != null && key_type != T_UNKNOWN && key_type != T_INT) { emit("warning", line, col, `${key_type} key on text — requires integer index`) } if (obj_type == T_RECORD && key_type != null && key_type != T_UNKNOWN && key_type != T_TEXT) { emit("warning", line, col, `${key_type} key on record — requires text key`) } } // Update types for this instruction track_types(cur_types, instr) // Override: load_field on array/text always returns null if (load_field_null) { cur_types[instr[1]] = T_NULL } i = i + 1 } return null } // ========================================================= // Sensory function IR synthesis for callback inlining // ========================================================= var sensory_opcodes = { is_array: "is_array", is_function: "is_func", is_object: "is_record", is_stone: "is_stone", is_integer: "is_int", is_text: "is_text", is_number: "is_num", is_logical: "is_bool", is_null: "is_null", is_blob: "is_blob", is_data: "is_data", is_true: "is_true", is_false: "is_false", is_fit: "is_fit", is_character: "is_char", is_digit: "is_digit", is_letter: "is_letter", is_lower: "is_lower", is_upper: "is_upper", is_whitespace: "is_ws", is_actor: "is_actor", length: "length" } var make_sensory_ir = function(name) { var opcode = sensory_opcodes[name] if (opcode == null) return null return { name: name, nr_args: 1, nr_close_slots: 0, nr_slots: 3, instructions: [[opcode, 2, 1, 0, 0], ["return", 2, 0, 0]] } } // ========================================================= // Inline eligibility check // ========================================================= var prefer_inline_set = { filter: true, every: true, some: true, arrfor: true, reduce: true, array: true } // Structural eligibility: closures, get/put, disruption, nested functions var can_inline_structural = function(callee_func) { var instrs = null var i = 0 var instr = null if (callee_func.nr_close_slots > 0) return false instrs = callee_func.instructions if (instrs == null) return false i = 0 while (i < length(instrs)) { instr = instrs[i] if (is_array(instr)) { if (instr[0] == "get" || instr[0] == "put") { return false } // Reject if function creates child functions (closures may capture // from the inlined frame, breaking get/put slot references) if (instr[0] == "function") { return false } } i = i + 1 } if (callee_func.disruption_pc != null && callee_func.disruption_pc > 0) { return false } return true } // Size eligibility: instruction count check var can_inline_size = function(callee_func, is_prefer) { var instrs = callee_func.instructions var count = 0 var i = 0 var limit = 0 if (instrs == null) return false i = 0 while (i < length(instrs)) { if (is_array(instrs[i])) count = count + 1 i = i + 1 } limit = is_prefer ? 200 : 40 return count <= limit } var can_inline = function(callee_func, is_prefer) { if (!can_inline_structural(callee_func)) return false return can_inline_size(callee_func, is_prefer) } // ========================================================= // Pass: inline_calls — inline same-module + sensory functions // ========================================================= var inline_counter = 0 var inline_calls = function(func, ir, log) { var instructions = func.instructions var num_instr = 0 var i = 0 var j = 0 var k = 0 var instr = null var op = null var changed = false var inline_count = 0 var max_inlines = 20 var slot_to_func_idx = {} var slot_to_intrinsic = {} var callee_slot = 0 var frame_slot = 0 var argc = 0 var result_slot = 0 var call_start = 0 var call_end = 0 var arg_slots = null var callee_func = null var is_prefer = false var base = 0 var remap = null var cinstr = null var cop = null var new_instr = null var refs = null var label_prefix = null var cont_label = null var spliced = null var before = null var after = null var inlined_body = null var fi = null var intrinsic_name = null var is_single_use = false var ref_count = 0 var ri = 0 if (instructions == null) return false num_instr = length(instructions) if (num_instr == 0) return false // Build resolution maps i = 0 while (i < num_instr) { instr = instructions[i] if (is_array(instr)) { op = instr[0] if (op == "function") { slot_to_func_idx[text(instr[1])] = instr[2] } else if (op == "access" && is_object(instr[2]) && instr[2].make == "intrinsic") { slot_to_intrinsic[text(instr[1])] = instr[2].name } } i = i + 1 } // Scan for frame/setarg/invoke sequences and inline i = 0 while (i < length(instructions)) { instr = instructions[i] if (!is_array(instr) || instr[0] != "frame") { i = i + 1 continue } if (inline_count >= max_inlines) { i = i + 1 continue } frame_slot = instr[1] callee_slot = instr[2] argc = instr[3] call_start = i // Collect setarg and find invoke arg_slots = array(argc + 1, -1) j = i + 1 call_end = -1 while (j < length(instructions)) { instr = instructions[j] if (!is_array(instr)) { j = j + 1 continue } op = instr[0] if (op == "setarg" && instr[1] == frame_slot) { arg_slots[instr[2]] = instr[3] } else if ((op == "invoke" || op == "tail_invoke") && instr[1] == frame_slot) { result_slot = instr[2] call_end = j j = j + 1 break } else if (op == "frame" || op == "goframe") { // Another frame before invoke — abort this pattern break } j = j + 1 } if (call_end < 0) { i = i + 1 continue } // Resolve callee callee_func = null is_prefer = false fi = slot_to_func_idx[text(callee_slot)] if (fi != null && ir.functions != null && fi >= 0 && fi < length(ir.functions)) { callee_func = ir.functions[fi] } if (callee_func == null) { intrinsic_name = slot_to_intrinsic[text(callee_slot)] if (intrinsic_name != null) { if (sensory_opcodes[intrinsic_name] != null) { callee_func = make_sensory_ir(intrinsic_name) } if (callee_func != null) { is_prefer = true } } } if (callee_func == null) { i = i + 1 continue } // Check if callee is a single-use function literal — skip size limit is_single_use = false if (fi != null) { ref_count = 0 ri = 0 while (ri < length(instructions)) { if (is_array(instructions[ri])) { // Count frame instructions that use this slot as callee (position 2) if (instructions[ri][0] == "frame" && instructions[ri][2] == callee_slot) { ref_count = ref_count + 1 } // Also count setarg where slot is passed as value (position 3) if (instructions[ri][0] == "setarg" && instructions[ri][3] == callee_slot) { ref_count = ref_count + 1 } } ri = ri + 1 } if (ref_count <= 1) is_single_use = true } // Check eligibility if (!can_inline_structural(callee_func)) { i = i + 1 continue } if (!is_single_use && !can_inline_size(callee_func, is_prefer)) { i = i + 1 continue } // Slot remapping base = func.nr_slots func.nr_slots = func.nr_slots + callee_func.nr_slots remap = array(callee_func.nr_slots, -1) // Slot 0 (this) → arg_slots[0] if provided, else allocate a null slot if (length(arg_slots) > 0 && arg_slots[0] >= 0) { remap[0] = arg_slots[0] } else { remap[0] = base } // Params 1..nr_args → corresponding arg_slots j = 1 while (j <= callee_func.nr_args) { if (j < length(arg_slots) && arg_slots[j] >= 0) { remap[j] = arg_slots[j] } else { remap[j] = base + j } j = j + 1 } // Temporaries → fresh slots j = callee_func.nr_args + 1 while (j < callee_func.nr_slots) { remap[j] = base + j j = j + 1 } // Generate unique label prefix inline_counter = inline_counter + 1 label_prefix = "_inl" + text(inline_counter) + "_" cont_label = label_prefix + "cont" // Build inlined body with remapping // Unmapped params (e.g. caller passes 1 arg to a 2-param function) // must be explicitly nulled. compress_slots may merge the fresh // slot (base+j) with a previously-live slot that holds a non-null // value, so the default-param jump_not_null preamble would skip // the default assignment and use a stale value instead. inlined_body = [] j = 0 while (j <= callee_func.nr_args) { if (!(j < length(arg_slots) && arg_slots[j] >= 0)) { inlined_body[] = ["null", remap[j], 0, 0] } j = j + 1 } k = 0 while (k < length(callee_func.instructions)) { cinstr = callee_func.instructions[k] // Labels (strings that aren't nop markers) if (is_text(cinstr)) { if (starts_with(cinstr, "_nop_")) { inlined_body[] = cinstr } else { inlined_body[] = label_prefix + cinstr } k = k + 1 continue } if (!is_array(cinstr)) { inlined_body[] = cinstr k = k + 1 continue } cop = cinstr[0] // Handle return → move + jump to continuation if (cop == "return") { new_instr = ["move", result_slot, remap[cinstr[1]], cinstr[2], cinstr[3]] inlined_body[] = new_instr inlined_body[] = ["jump", cont_label, cinstr[2], cinstr[3]] k = k + 1 continue } // Clone and remap the instruction new_instr = array(cinstr) refs = get_slot_refs(cinstr) j = 0 while (j < length(refs)) { if (new_instr[refs[j]] >= 0 && new_instr[refs[j]] < length(remap)) { new_instr[refs[j]] = remap[new_instr[refs[j]]] } j = j + 1 } // Remap labels in jump instructions if (cop == "jump" && is_text(cinstr[1]) && !starts_with(cinstr[1], "_nop_")) { new_instr[1] = label_prefix + cinstr[1] } else if (is_cond_jump(cop) && is_text(cinstr[2]) && !starts_with(cinstr[2], "_nop_")) { new_instr[2] = label_prefix + cinstr[2] } // Skip function instructions (don't inline nested function definitions) if (cop == "function") { // Keep the instruction but don't remap func_id — it still refers to ir.functions // Only remap slot position 1 (the destination slot) new_instr = array(cinstr) if (cinstr[1] >= 0 && cinstr[1] < length(remap)) { new_instr[1] = remap[cinstr[1]] } } inlined_body[] = new_instr k = k + 1 } // Add continuation label inlined_body[] = cont_label // Splice: replace instructions[call_start..call_end] with inlined_body before = array(instructions, 0, call_start) after = array(instructions, call_end + 1, length(instructions)) spliced = array(before, inlined_body) instructions = array(spliced, after) func.instructions = instructions changed = true inline_count = inline_count + 1 // Continue scanning from after the inlined body i = call_start + length(inlined_body) } return changed } // ========================================================= // Compose all passes // ========================================================= var optimize_function = function(func, log) { var param_types = null var write_types = null var slot_types = null var run_cycle = function(suffix) { var name = null if (param_types == null) { name = "infer_param_types" + suffix run_pass(func, name, function() { param_types = infer_param_types(func) return param_types }) if (verify_fn) verify_fn(func, "after " + name) } name = "infer_slot_write_types" + suffix run_pass(func, name, function() { write_types = infer_slot_write_types(func, param_types) return write_types }) if (verify_fn) verify_fn(func, "after " + name) name = "eliminate_type_checks" + suffix run_pass(func, name, function() { slot_types = eliminate_type_checks(func, param_types, write_types, log) return slot_types }) if (verify_fn) verify_fn(func, "after " + name) if (log != null && log.type_deltas != null && slot_types != null) { log.type_deltas[] = { fn: func.name, cycle: suffix == "" ? 1 : 2, param_types: param_types, slot_types: slot_types } } name = "simplify_algebra" + suffix run_pass(func, name, function() { return simplify_algebra(func, log) }) if (verify_fn) verify_fn(func, "after " + name) name = "simplify_booleans" + suffix run_pass(func, name, function() { return simplify_booleans(func, log) }) if (verify_fn) verify_fn(func, "after " + name) name = "eliminate_moves" + suffix run_pass(func, name, function() { return eliminate_moves(func, log) }) if (verify_fn) verify_fn(func, "after " + name) name = "eliminate_unreachable" + suffix run_pass(func, name, function() { return eliminate_unreachable(func) }) if (verify_fn) verify_fn(func, "after " + name) name = "eliminate_dead_jumps" + suffix run_pass(func, name, function() { return eliminate_dead_jumps(func, log) }) if (verify_fn) verify_fn(func, "after " + name) name = "eliminate_unreachable_cfg" + suffix run_pass(func, name, function() { return eliminate_unreachable_cfg(func) }) if (verify_fn) verify_fn(func, "after " + name) return null } if (func.instructions == null || length(func.instructions) == 0) { return null } run_cycle("") run_cycle("_2") func._write_types = write_types if (ir._warn) { diagnose_function(func, {param_types: param_types, write_types: write_types}, ir) } return null } // Pre-pass: mark slots written by child closures via 'put' instructions. // Without this, infer_slot_write_types would assume those slots keep their // initial type (e.g. T_NULL from 'var x = null'), causing the type-check // eliminator to mis-optimize comparisons on closure-written variables. mark_closure_writes(ir) if (ir._warn) { ir._diagnostics = [] } // Phase 1: Optimize all functions (bottom-up, existing behavior) if (ir.main != null) { optimize_function(ir.main, log) insert_stone_text(ir.main, log) } var fi = 0 if (ir.functions != null) { fi = 0 while (fi < length(ir.functions)) { resolve_closure_types(ir.functions[fi], fi, ir) optimize_function(ir.functions[fi], log) insert_stone_text(ir.functions[fi], log) fi = fi + 1 } } // Phase 2: Inline pass if (ir._no_inline) { return ir } var changed_main = false var changed_fns = null if (ir.main != null) { changed_main = inline_calls(ir.main, ir, log) } if (ir.functions != null) { changed_fns = array(length(ir.functions), false) fi = 0 while (fi < length(ir.functions)) { changed_fns[fi] = inline_calls(ir.functions[fi], ir, log) fi = fi + 1 } } // Phase 3: Re-optimize inlined functions if (changed_main) { optimize_function(ir.main, log) insert_stone_text(ir.main, log) } if (ir.functions != null) { fi = 0 while (fi < length(ir.functions)) { if (changed_fns != null && changed_fns[fi]) { optimize_function(ir.functions[fi], log) insert_stone_text(ir.functions[fi], log) } fi = fi + 1 } } // Phase 4: Cascade — second inline round (callbacks inside inlined bodies) if (changed_main) { changed_main = inline_calls(ir.main, ir, log) if (changed_main) { optimize_function(ir.main, log) insert_stone_text(ir.main, log) } } if (ir.functions != null) { fi = 0 while (fi < length(ir.functions)) { if (changed_fns != null && changed_fns[fi]) { changed_fns[fi] = inline_calls(ir.functions[fi], ir, log) if (changed_fns[fi]) { optimize_function(ir.functions[fi], log) insert_stone_text(ir.functions[fi], log) } } fi = fi + 1 } } // Phase 5: Compress slots across all functions (must run after per-function passes) compress_slots(ir) // Expose DEF/USE functions via log if requested if (log != null) { if (log.request_def_use) { log.get_slot_defs = get_slot_defs log.get_slot_uses = get_slot_uses } } return ir } return streamline