// 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_/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 } // ========================================================= // Compose all passes // ========================================================= var optimize_function = function(func, log) { var param_types = null var write_types = null var slot_types = null if (func.instructions == null || length(func.instructions) == 0) { return null } run_pass(func, "infer_param_types", function() { param_types = infer_param_types(func) return param_types }) if (verify_fn) verify_fn(func, "after infer_param_types") run_pass(func, "infer_slot_write_types", function() { write_types = infer_slot_write_types(func) return write_types }) if (verify_fn) verify_fn(func, "after infer_slot_write_types") run_pass(func, "eliminate_type_checks", function() { slot_types = eliminate_type_checks(func, param_types, write_types, log) return slot_types }) if (verify_fn) verify_fn(func, "after eliminate_type_checks") if (log != null && log.type_deltas != null && slot_types != null) { log.type_deltas[] = { fn: func.name, param_types: param_types, slot_types: slot_types } } run_pass(func, "simplify_algebra", function() { return simplify_algebra(func, log) }) if (verify_fn) verify_fn(func, "after simplify_algebra") run_pass(func, "simplify_booleans", function() { return simplify_booleans(func, log) }) if (verify_fn) verify_fn(func, "after simplify_booleans") run_pass(func, "eliminate_moves", function() { return eliminate_moves(func, log) }) if (verify_fn) verify_fn(func, "after eliminate_moves") run_pass(func, "eliminate_unreachable", function() { return eliminate_unreachable(func) }) if (verify_fn) verify_fn(func, "after eliminate_unreachable") run_pass(func, "eliminate_dead_jumps", function() { return eliminate_dead_jumps(func, log) }) if (verify_fn) verify_fn(func, "after eliminate_dead_jumps") return null } // Process main function if (ir.main != null) { optimize_function(ir.main, log) } // Process all sub-functions var fi = 0 if (ir.functions != null) { fi = 0 while (fi < length(ir.functions)) { optimize_function(ir.functions[fi], log) fi = fi + 1 } } return ir } return streamline