streamline mcode
This commit is contained in:
621
streamline.cm
621
streamline.cm
@@ -1,5 +1,5 @@
|
||||
// streamline.cm — mcode IR optimizer
|
||||
// Single forward pass: type inference + strength reduction
|
||||
// Composed of independent passes, each a separate function.
|
||||
|
||||
var streamline = function(ir) {
|
||||
// Type constants
|
||||
@@ -10,20 +10,19 @@ var streamline = function(ir) {
|
||||
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"
|
||||
|
||||
// Integer arithmetic ops that produce integer results
|
||||
var int_result_ops = {
|
||||
add_int: true, sub_int: true, mul_int: true,
|
||||
div_int: true, mod_int: true
|
||||
}
|
||||
|
||||
// Float arithmetic ops that produce float results
|
||||
var float_result_ops = {
|
||||
add_float: true, sub_float: true, mul_float: true,
|
||||
div_float: true, mod_float: true
|
||||
}
|
||||
|
||||
// Comparison ops that produce bool results
|
||||
var bool_result_ops = {
|
||||
eq_int: true, ne_int: true, lt_int: true, gt_int: true,
|
||||
le_int: true, ge_int: true,
|
||||
@@ -35,19 +34,18 @@ var streamline = function(ir) {
|
||||
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_bool: true, is_null: true, is_identical: true,
|
||||
is_array: true, is_func: true, is_record: true, is_stone: true
|
||||
}
|
||||
|
||||
// Type check opcodes and what type they verify
|
||||
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_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_stone: T_RECORD
|
||||
}
|
||||
|
||||
// Determine the type of an access literal value
|
||||
// --- Shared helpers ---
|
||||
|
||||
var access_value_type = function(val) {
|
||||
if (is_number(val)) {
|
||||
if (is_integer(val)) {
|
||||
@@ -61,11 +59,9 @@ var streamline = function(ir) {
|
||||
return T_UNKNOWN
|
||||
}
|
||||
|
||||
// Update slot_types for an instruction (shared tracking logic)
|
||||
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") {
|
||||
@@ -76,11 +72,7 @@ var streamline = function(ir) {
|
||||
slot_types[text(instr[1])] = T_NULL
|
||||
} else if (op == "move") {
|
||||
src_type = slot_types[text(instr[2])]
|
||||
if (src_type != null) {
|
||||
slot_types[text(instr[1])] = src_type
|
||||
} else {
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
}
|
||||
slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN
|
||||
} else if (int_result_ops[op] == true) {
|
||||
slot_types[text(instr[1])] = T_INT
|
||||
} else if (float_result_ops[op] == true) {
|
||||
@@ -93,8 +85,16 @@ var streamline = function(ir) {
|
||||
slot_types[text(instr[1])] = T_UNKNOWN
|
||||
} else if (op == "invoke") {
|
||||
slot_types[text(instr[2])] = T_UNKNOWN
|
||||
} else if (op == "pop" || op == "get" || op == "function") {
|
||||
} 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 == "typeof") {
|
||||
slot_types[text(instr[1])] = T_TEXT
|
||||
} else if (op == "neg_int") {
|
||||
@@ -108,7 +108,6 @@ var streamline = function(ir) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Check if a slot has a known type (with T_NUM subsumption)
|
||||
var slot_is = function(slot_types, slot, typ) {
|
||||
var known = slot_types[text(slot)]
|
||||
if (known == null) {
|
||||
@@ -123,13 +122,137 @@ var streamline = function(ir) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Optimize a single function's instructions
|
||||
var optimize_function = function(func) {
|
||||
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
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// 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 == "add_int" || op == "sub_int" || op == "mul_int" ||
|
||||
op == "div_int" || op == "mod_int" ||
|
||||
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 == "neg_int" || op == "bitnot") {
|
||||
merge_backward(backward_types, instr[2], T_INT)
|
||||
} else if (op == "add_float" || op == "sub_float" || op == "mul_float" ||
|
||||
op == "div_float" || op == "mod_float" ||
|
||||
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 == "neg_float") {
|
||||
merge_backward(backward_types, instr[2], 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)
|
||||
}
|
||||
}
|
||||
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: eliminate_type_checks — language-level type narrowing
|
||||
// Eliminates is_<type>/jump pairs when type is known.
|
||||
// Reduces load_dynamic/store_dynamic to field/index forms.
|
||||
// =========================================================
|
||||
var eliminate_type_checks = function(func, param_types) {
|
||||
var instructions = func.instructions
|
||||
var nr_args = func.nr_args != null ? func.nr_args : 0
|
||||
var has_params = false
|
||||
var num_instr = 0
|
||||
var slot_types = null
|
||||
var nop_counter = 0
|
||||
var nc = 0
|
||||
var i = 0
|
||||
var j = 0
|
||||
var instr = null
|
||||
var op = null
|
||||
var dest = 0
|
||||
@@ -140,24 +263,34 @@ var streamline = function(ir) {
|
||||
var target_label = null
|
||||
var src_known = null
|
||||
var jlen = 0
|
||||
var j = 0
|
||||
var peek = null
|
||||
|
||||
if (instructions == null || length(instructions) == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
slot_types = {}
|
||||
j = 1
|
||||
while (j <= nr_args) {
|
||||
if (param_types[text(j)] != null) {
|
||||
has_params = true
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
|
||||
slot_types = {}
|
||||
if (has_params) {
|
||||
seed_params(slot_types, param_types, nr_args)
|
||||
}
|
||||
|
||||
// Peephole optimization pass: type tracking + strength reduction
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
|
||||
// Labels are join points: clear all type info (conservative)
|
||||
if (is_text(instr)) {
|
||||
slot_types = {}
|
||||
if (has_params) {
|
||||
seed_params(slot_types, param_types, nr_args)
|
||||
}
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
@@ -169,7 +302,7 @@ var streamline = function(ir) {
|
||||
|
||||
op = instr[0]
|
||||
|
||||
// --- Peephole: type-check + jump where we know the type ---
|
||||
// Type-check + jump elimination
|
||||
if (type_check_map[op] != null && i + 1 < num_instr) {
|
||||
dest = instr[1]
|
||||
src = instr[2]
|
||||
@@ -179,102 +312,84 @@ var streamline = function(ir) {
|
||||
if (is_array(next)) {
|
||||
next_op = next[0]
|
||||
|
||||
// Pattern: is_<type> t, x -> jump_false t, label
|
||||
if (next_op == "jump_false" && next[1] == dest) {
|
||||
target_label = next[2]
|
||||
|
||||
if (slot_is(slot_types, src, checked_type)) {
|
||||
// Known match: check always true, never jumps — eliminate both
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i + 1] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
nc = nc + 1
|
||||
instructions[i + 1] = "_nop_tc_" + text(nc)
|
||||
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) {
|
||||
// Check for T_NUM subsumption: INT and FLOAT match T_NUM
|
||||
if (checked_type == T_NUM && (src_known == T_INT || src_known == T_FLOAT)) {
|
||||
// Actually matches — eliminate both
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i + 1] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
nc = nc + 1
|
||||
instructions[i + 1] = "_nop_tc_" + text(nc)
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
// Known mismatch: always jumps — nop the check, rewrite jump
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
jlen = length(next)
|
||||
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
||||
slot_types[text(dest)] = T_UNKNOWN
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
|
||||
// Unknown: can't eliminate, but narrow type on fallthrough
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
slot_types[text(src)] = checked_type
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
|
||||
// Pattern: is_<type> t, x -> jump_true t, label
|
||||
if (next_op == "jump_true" && next[1] == dest) {
|
||||
target_label = next[2]
|
||||
|
||||
if (slot_is(slot_types, src, checked_type)) {
|
||||
// Known match: always true, always jumps — nop check, rewrite to jump
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
jlen = length(next)
|
||||
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
||||
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)) {
|
||||
// Actually matches T_NUM — always jumps
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
jlen = length(next)
|
||||
instructions[i + 1] = ["jump", target_label, next[jlen - 2], next[jlen - 1]]
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
// Known mismatch: never jumps — eliminate both
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i + 1] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_tc_" + text(nc)
|
||||
nc = nc + 1
|
||||
instructions[i + 1] = "_nop_tc_" + text(nc)
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
|
||||
// Unknown: can't optimize
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone type check (no jump following): just track the result
|
||||
slot_types[text(dest)] = T_BOOL
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Strength reduction: load_dynamic / store_dynamic ---
|
||||
// Dynamic access reduction
|
||||
if (op == "load_dynamic") {
|
||||
if (slot_is(slot_types, instr[3], T_TEXT)) {
|
||||
instr[0] = "load_field"
|
||||
@@ -295,26 +410,361 @@ var streamline = function(ir) {
|
||||
continue
|
||||
}
|
||||
|
||||
// --- Standard type tracking ---
|
||||
track_types(slot_types, instr)
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// 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) {
|
||||
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 v2 = null
|
||||
var v3 = null
|
||||
var sv = null
|
||||
|
||||
if (instructions == null || length(instructions) == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Integer: x+0, x-0 → move x
|
||||
if (op == "add_int" || op == "sub_int") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
if (v3 == 0) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (op == "add_int") {
|
||||
v2 = slot_values[text(instr[2])]
|
||||
if (v2 == 0) {
|
||||
instructions[i] = ["move", instr[1], instr[3], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if (op == "mul_int") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
v2 = slot_values[text(instr[2])]
|
||||
if (v3 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (v2 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[3], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (v3 == 0 || v2 == 0) {
|
||||
instructions[i] = ["int", instr[1], 0, instr[ilen - 2], instr[ilen - 1]]
|
||||
slot_values[text(instr[1])] = 0
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
} else if (op == "div_int") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
if (v3 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Float: x+0, x-0 → move x; x*1, x/1 → move x
|
||||
// (skip mul_float * 0 — not safe with NaN/Inf)
|
||||
if (op == "add_float" || op == "sub_float") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
if (v3 == 0) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (op == "add_float") {
|
||||
v2 = slot_values[text(instr[2])]
|
||||
if (v2 == 0) {
|
||||
instructions[i] = ["move", instr[1], instr[3], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else if (op == "mul_float") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
v2 = slot_values[text(instr[2])]
|
||||
if (v3 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
if (v2 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[3], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
} else if (op == "div_float") {
|
||||
v3 = slot_values[text(instr[3])]
|
||||
if (v3 == 1) {
|
||||
instructions[i] = ["move", instr[1], instr[2], instr[ilen - 2], instr[ilen - 1]]
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 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]]
|
||||
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]]
|
||||
slot_values[text(instr[1])] = false
|
||||
i = i + 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Clear value tracking for dest-producing ops (not reads-only)
|
||||
if (op == "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
|
||||
}
|
||||
|
||||
// Second pass: remove dead jumps (jump to the immediately next label)
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Pass: simplify_booleans — not+jump fusion, double-not
|
||||
// =========================================================
|
||||
var simplify_booleans = function(func) {
|
||||
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
|
||||
|
||||
if (instructions == null || length(instructions) == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
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]]
|
||||
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]]
|
||||
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]]
|
||||
i = i + 2
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Pass: eliminate_moves — move a, a → nop
|
||||
// =========================================================
|
||||
var eliminate_moves = function(func) {
|
||||
var instructions = func.instructions
|
||||
var num_instr = 0
|
||||
var nc = 0
|
||||
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_array(instr) && instr[0] == "move" && instr[1] == instr[2]) {
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_mv_" + text(nc)
|
||||
}
|
||||
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" || instr[0] == "disrupt") {
|
||||
after_return = true
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Pass: eliminate_dead_jumps — jump to next label → nop
|
||||
// =========================================================
|
||||
var eliminate_dead_jumps = function(func) {
|
||||
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
|
||||
|
||||
if (instructions == null || length(instructions) == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
num_instr = length(instructions)
|
||||
i = 0
|
||||
while (i < num_instr) {
|
||||
instr = instructions[i]
|
||||
if (is_array(instr) && instr[0] == "jump") {
|
||||
target_label = instr[1]
|
||||
// Check if the very next non-nop item is that label
|
||||
j = i + 1
|
||||
while (j < num_instr) {
|
||||
peek = instructions[j]
|
||||
if (is_text(peek)) {
|
||||
if (peek == target_label) {
|
||||
nop_counter = nop_counter + 1
|
||||
instructions[i] = "_nop_" + text(nop_counter)
|
||||
nc = nc + 1
|
||||
instructions[i] = "_nop_dj_" + text(nc)
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -330,6 +780,27 @@ var streamline = function(ir) {
|
||||
return null
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Compose all passes
|
||||
// =========================================================
|
||||
var optimize_function = function(func) {
|
||||
var param_types = null
|
||||
if (func.instructions == null || length(func.instructions) == 0) {
|
||||
return null
|
||||
}
|
||||
param_types = infer_param_types(func)
|
||||
eliminate_type_checks(func, param_types)
|
||||
simplify_algebra(func)
|
||||
simplify_booleans(func)
|
||||
eliminate_moves(func)
|
||||
// NOTE: eliminate_unreachable is disabled because disruption handler
|
||||
// code is placed after return/disrupt without label boundaries.
|
||||
// Re-enable once mcode.cm emits labels for handler entry points.
|
||||
//eliminate_unreachable(func)
|
||||
eliminate_dead_jumps(func)
|
||||
return null
|
||||
}
|
||||
|
||||
// Process main function
|
||||
if (ir.main != null) {
|
||||
optimize_function(ir.main)
|
||||
|
||||
Reference in New Issue
Block a user