remove if/else dispatch from compile chain

This commit is contained in:
2026-02-14 17:57:48 -06:00
parent a93218e1ff
commit a765872017
16 changed files with 184177 additions and 231986 deletions

View File

@@ -60,6 +60,26 @@ var streamline = function(ir, log) {
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
@@ -119,43 +139,24 @@ var streamline = function(ir, log) {
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
if (op == "access") {
slot_types[instr[1]] = access_value_type(instr[2])
} else if (op == "int") {
slot_types[instr[1]] = T_INT
} else if (op == "true" || op == "false") {
slot_types[instr[1]] = T_BOOL
} else if (op == "null") {
slot_types[instr[1]] = T_NULL
} else if (op == "move") {
var typ = null
if (op == "move") {
src_type = slot_types[instr[2]]
slot_types[instr[1]] = src_type != null ? src_type : T_UNKNOWN
} else if (op == "concat") {
slot_types[instr[1]] = T_TEXT
} else if (bool_result_ops[op] == true) {
slot_types[instr[1]] = T_BOOL
} else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
slot_types[instr[1]] = T_UNKNOWN
} else if (op == "invoke" || op == "tail_invoke") {
slot_types[instr[2]] = T_UNKNOWN
} else if (op == "pop" || op == "get") {
slot_types[instr[1]] = T_UNKNOWN
} else if (op == "array") {
slot_types[instr[1]] = T_ARRAY
} else if (op == "record") {
slot_types[instr[1]] = T_RECORD
} else if (op == "function") {
slot_types[instr[1]] = T_FUNCTION
} else if (op == "length") {
slot_types[instr[1]] = T_INT
} else if (op == "negate" || numeric_ops[op] == true) {
slot_types[instr[1]] = T_UNKNOWN
} else if (op == "bitnot" || op == "bitand" || op == "bitor" ||
op == "bitxor" || op == "shl" || op == "shr" || op == "ushr") {
slot_types[instr[1]] = T_INT
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
}
@@ -221,7 +222,35 @@ var streamline = function(ir, log) {
// =========================================================
// 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 = {
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],
eq_int: [2, T_INT, 3, T_INT], ne_int: [2, T_INT, 3, T_INT],
lt_int: [2, T_INT, 3, T_INT], gt_int: [2, T_INT, 3, T_INT],
le_int: [2, T_INT, 3, T_INT], ge_int: [2, T_INT, 3, T_INT],
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],
eq_float: [2, T_FLOAT, 3, T_FLOAT], ne_float: [2, T_FLOAT, 3, T_FLOAT],
lt_float: [2, T_FLOAT, 3, T_FLOAT], gt_float: [2, T_FLOAT, 3, T_FLOAT],
le_float: [2, T_FLOAT, 3, T_FLOAT], ge_float: [2, T_FLOAT, 3, T_FLOAT],
concat: [2, T_TEXT, 3, T_TEXT],
eq_text: [2, T_TEXT, 3, T_TEXT], ne_text: [2, T_TEXT, 3, T_TEXT],
lt_text: [2, T_TEXT, 3, T_TEXT], gt_text: [2, T_TEXT, 3, T_TEXT],
le_text: [2, T_TEXT, 3, T_TEXT], ge_text: [2, T_TEXT, 3, T_TEXT],
eq_bool: [2, T_BOOL, 3, T_BOOL], ne_bool: [2, T_BOOL, 3, T_BOOL],
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
@@ -231,8 +260,8 @@ var streamline = function(ir, log) {
var i = 0
var j = 0
var instr = null
var op = null
var bt = null
var rule = null
if (instructions == null || nr_args == 0) {
return array(func.nr_slots)
@@ -244,52 +273,12 @@ var streamline = function(ir, log) {
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)
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
@@ -312,7 +301,39 @@ var streamline = function(ir, log) {
// 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_UNKNOWN], 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_UNKNOWN], subtract: [1, T_UNKNOWN], multiply: [1, T_UNKNOWN],
divide: [1, T_UNKNOWN], modulo: [1, T_UNKNOWN], pow: [1, T_UNKNOWN],
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
@@ -321,9 +342,9 @@ var streamline = function(ir, log) {
var i = 0
var k = 0
var instr = null
var op = null
var slot = 0
var typ = null
var rule = null
if (instructions == null) {
return array(func.nr_slots)
@@ -334,74 +355,19 @@ var streamline = function(ir, log) {
i = 0
while (i < num_instr) {
instr = instructions[i]
if (!is_array(instr)) {
i = i + 1
continue
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)
}
}
}
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
}
@@ -773,10 +739,7 @@ var streamline = function(ir, log) {
// 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") {
if (self_true_ops[op] == true) {
instructions[i] = ["true", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) {
events[] = {
@@ -790,10 +753,7 @@ var streamline = function(ir, log) {
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") {
if (self_false_ops[op] == true) {
instructions[i] = ["false", instr[1], instr[ilen - 2], instr[ilen - 1]]
if (events != null) {
events[] = {
@@ -812,12 +772,7 @@ var streamline = function(ir, log) {
// Clear value tracking for dest-producing ops (not reads-only)
if (op == "invoke" || op == "tail_invoke") {
slot_values[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") {
} else if (no_clear_ops[op] != true) {
if (is_number(instr[1])) {
slot_values[instr[1]] = null
}