352 lines
11 KiB
Plaintext
352 lines
11 KiB
Plaintext
// streamline.cm — mcode IR optimizer
|
|
// Single forward pass: type inference + strength reduction
|
|
|
|
var streamline = function(ir) {
|
|
// 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"
|
|
|
|
// 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,
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Determine the type of an access literal value
|
|
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
|
|
}
|
|
|
|
// 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") {
|
|
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])]
|
|
if (src_type != null) {
|
|
slot_types[text(instr[1])] = src_type
|
|
} else {
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
}
|
|
} else if (int_result_ops[op] == true) {
|
|
slot_types[text(instr[1])] = T_INT
|
|
} else if (float_result_ops[op] == true) {
|
|
slot_types[text(instr[1])] = T_FLOAT
|
|
} 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") {
|
|
slot_types[text(instr[2])] = T_UNKNOWN
|
|
} else if (op == "pop" || op == "get" || op == "function") {
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
} else if (op == "typeof") {
|
|
slot_types[text(instr[1])] = T_TEXT
|
|
} else if (op == "neg_int") {
|
|
slot_types[text(instr[1])] = T_INT
|
|
} else if (op == "neg_float") {
|
|
slot_types[text(instr[1])] = T_FLOAT
|
|
} 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
|
|
}
|
|
|
|
// 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) {
|
|
return false
|
|
}
|
|
if (known == typ) {
|
|
return true
|
|
}
|
|
if (typ == T_NUM && (known == T_INT || known == T_FLOAT)) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Optimize a single function's instructions
|
|
var optimize_function = function(func) {
|
|
var instructions = func.instructions
|
|
var num_instr = 0
|
|
var slot_types = null
|
|
var nop_counter = 0
|
|
var i = 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 j = 0
|
|
var peek = null
|
|
|
|
if (instructions == null || length(instructions) == 0) {
|
|
return null
|
|
}
|
|
|
|
num_instr = length(instructions)
|
|
slot_types = {}
|
|
|
|
// 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 = {}
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
if (!is_array(instr)) {
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
op = instr[0]
|
|
|
|
// --- Peephole: type-check + jump where we know the type ---
|
|
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]
|
|
|
|
// 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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
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 ---
|
|
if (op == "load_dynamic") {
|
|
if (slot_is(slot_types, instr[3], T_TEXT)) {
|
|
instr[0] = "load_field"
|
|
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
|
instr[0] = "load_index"
|
|
}
|
|
slot_types[text(instr[1])] = T_UNKNOWN
|
|
i = i + 1
|
|
continue
|
|
}
|
|
if (op == "store_dynamic") {
|
|
if (slot_is(slot_types, instr[3], T_TEXT)) {
|
|
instr[0] = "store_field"
|
|
} else if (slot_is(slot_types, instr[3], T_INT)) {
|
|
instr[0] = "store_index"
|
|
}
|
|
i = i + 1
|
|
continue
|
|
}
|
|
|
|
// --- Standard type tracking ---
|
|
track_types(slot_types, instr)
|
|
|
|
i = i + 1
|
|
}
|
|
|
|
// Second pass: remove dead jumps (jump to the immediately next label)
|
|
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)
|
|
}
|
|
break
|
|
}
|
|
if (is_array(peek)) {
|
|
break
|
|
}
|
|
j = j + 1
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// Process main function
|
|
if (ir.main != null) {
|
|
optimize_function(ir.main)
|
|
}
|
|
|
|
// Process all sub-functions
|
|
var fi = 0
|
|
if (ir.functions != null) {
|
|
fi = 0
|
|
while (fi < length(ir.functions)) {
|
|
optimize_function(ir.functions[fi])
|
|
fi = fi + 1
|
|
}
|
|
}
|
|
|
|
return ir
|
|
}
|
|
|
|
return streamline
|