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