This commit is contained in:
2026-02-20 21:55:12 -06:00
parent 6f932cd3c3
commit f46784d884
2 changed files with 137 additions and 11 deletions

105
mcode.cm
View File

@@ -88,6 +88,7 @@ var mcode = function(ast) {
var s_cur_col = 0
var s_filename = null
var s_has_disruption = false
var s_slot_types = {}
// Shared closure vars for binop helpers (avoids >4 param functions)
var _bp_dest = 0
@@ -116,7 +117,8 @@ var mcode = function(ast) {
intrinsic_cache: s_intrinsic_cache,
cur_line: s_cur_line,
cur_col: s_cur_col,
has_disruption: s_has_disruption
has_disruption: s_has_disruption,
slot_types: s_slot_types
}
}
@@ -138,6 +140,7 @@ var mcode = function(ast) {
s_cur_line = saved.cur_line
s_cur_col = saved.cur_col
s_has_disruption = saved.has_disruption
s_slot_types = saved.slot_types
}
// Slot allocation
@@ -330,20 +333,47 @@ var mcode = function(ast) {
return node.kind == "null"
}
// Slot-type tracking helpers
var slot_is_num = function(slot) {
var t = s_slot_types[text(slot)]
return t == "num" || t == "int"
}
var slot_is_int = function(slot) {
return s_slot_types[text(slot)] == "int"
}
var slot_is_text = function(slot) {
return s_slot_types[text(slot)] == "text"
}
var mark_slot = function(slot, typ) {
s_slot_types[text(slot)] = typ
}
// emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() {
if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) {
emit_3("concat", _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "text")
return null
}
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
emit_3("add", _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "num")
return null
}
// If either operand is a known number, concat is impossible
if (is_known_number(_bp_ln) || is_known_number(_bp_rn)) {
// If either operand is a known number (AST or slot), concat is impossible
if (is_known_number(_bp_ln) || is_known_number(_bp_rn)
|| slot_is_num(_bp_left) || slot_is_num(_bp_right)) {
emit_numeric_binop("add")
mark_slot(_bp_dest, "num")
return null
}
if (slot_is_text(_bp_left) && slot_is_text(_bp_right)) {
emit_3("concat", _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "text")
return null
}
// Unknown types: emit full dispatch
@@ -380,8 +410,10 @@ var mcode = function(ast) {
// emit_numeric_binop: emit type-guarded numeric binary op
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_numeric_binop = function(op_str) {
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) {
if ((is_known_number(_bp_ln) && is_known_number(_bp_rn))
|| (slot_is_num(_bp_left) && slot_is_num(_bp_right))) {
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "num")
return null
}
var t0 = alloc_slot()
@@ -399,6 +431,7 @@ var mcode = function(ast) {
emit_log_error("cannot apply '" + _bp_op_sym + "': operands must be numbers")
emit_0("disrupt")
emit_label(done)
mark_slot(_bp_dest, "num")
return null
}
@@ -410,6 +443,26 @@ var mcode = function(ast) {
var right = _bp_right
var t0 = 0
var t1 = 0
// Known-int fast path
if ((is_known_int(_bp_ln) || slot_is_int(left))
&& (is_known_int(_bp_rn) || slot_is_int(right))) {
emit_3("eq_int", dest, left, right)
return null
}
// Known-num fast path
if ((is_known_number(_bp_ln) || slot_is_num(left))
&& (is_known_number(_bp_rn) || slot_is_num(right))) {
emit_3("eq_float", dest, left, right)
return null
}
// Known-text fast path
if ((is_known_text(_bp_ln) || slot_is_text(left))
&& (is_known_text(_bp_rn) || slot_is_text(right))) {
emit_3("eq_text", dest, left, right)
return null
}
var done = gen_label("eq_done")
var not_int = gen_label("eq_ni")
var not_num = gen_label("eq_nn")
@@ -482,6 +535,26 @@ var mcode = function(ast) {
var right = _bp_right
var t0 = 0
var t1 = 0
// Known-int fast path
if ((is_known_int(_bp_ln) || slot_is_int(left))
&& (is_known_int(_bp_rn) || slot_is_int(right))) {
emit_3("ne_int", dest, left, right)
return null
}
// Known-num fast path
if ((is_known_number(_bp_ln) || slot_is_num(left))
&& (is_known_number(_bp_rn) || slot_is_num(right))) {
emit_3("ne_float", dest, left, right)
return null
}
// Known-text fast path
if ((is_known_text(_bp_ln) || slot_is_text(left))
&& (is_known_text(_bp_rn) || slot_is_text(right))) {
emit_3("ne_text", dest, left, right)
return null
}
var done = gen_label("ne_done")
var not_ident = gen_label("ne_nid")
var not_int = gen_label("ne_ni")
@@ -563,12 +636,12 @@ var mcode = function(ast) {
var right = _bp_right
var t0 = 0
var t1 = 0
var left_is_int = is_known_int(_bp_ln)
var left_is_num = is_known_number(_bp_ln)
var left_is_text = is_known_text(_bp_ln)
var right_is_int = is_known_int(_bp_rn)
var right_is_num = is_known_number(_bp_rn)
var right_is_text = is_known_text(_bp_rn)
var left_is_int = is_known_int(_bp_ln) || slot_is_int(left)
var left_is_num = is_known_number(_bp_ln) || slot_is_num(left)
var left_is_text = is_known_text(_bp_ln) || slot_is_text(left)
var right_is_int = is_known_int(_bp_rn) || slot_is_int(right)
var right_is_num = is_known_number(_bp_rn) || slot_is_num(right)
var right_is_text = is_known_text(_bp_rn) || slot_is_text(right)
var not_num = null
var done = null
var err = null
@@ -619,8 +692,9 @@ var mcode = function(ast) {
// emit_neg_decomposed: emit type-guarded negate
var emit_neg_decomposed = function(dest, src, src_node) {
if (is_known_number(src_node)) {
if (is_known_number(src_node) || slot_is_num(src)) {
emit_2("negate", dest, src)
mark_slot(dest, "num")
return null
}
var t0 = alloc_slot()
@@ -635,6 +709,7 @@ var mcode = function(ast) {
emit_log_error("cannot negate: operand must be a number")
emit_0("disrupt")
emit_label(done)
mark_slot(dest, "num")
return null
}
@@ -1794,6 +1869,7 @@ var mcode = function(ast) {
if (kind == "number") {
slot = target >= 0 ? target : alloc_slot()
emit_const_num(slot, expr.number)
mark_slot(slot, is_integer(expr.number) ? "int" : "num")
return slot
}
if (kind == "text") {
@@ -1803,6 +1879,7 @@ var mcode = function(ast) {
val = ""
}
emit_const_str(slot, val)
mark_slot(slot, "text")
return slot
}
// Template literal
@@ -1839,6 +1916,7 @@ var mcode = function(ast) {
// Call format(fmt_str, array)
result_slot = target >= 0 ? target : alloc_slot()
emit_call(result_slot, fmt_func_slot, [fmt_str_slot, arr_slot])
mark_slot(result_slot, "text")
return result_slot
}
if (kind == "regexp") {
@@ -1857,16 +1935,19 @@ var mcode = function(ast) {
if (kind == "true") {
slot = target >= 0 ? target : alloc_slot()
emit_const_bool(slot, true)
mark_slot(slot, "bool")
return slot
}
if (kind == "false") {
slot = target >= 0 ? target : alloc_slot()
emit_const_bool(slot, false)
mark_slot(slot, "bool")
return slot
}
if (kind == "null") {
slot = target >= 0 ? target : alloc_slot()
emit_const_null(slot)
mark_slot(slot, null)
return slot
}
if (kind == "this") {
@@ -2760,6 +2841,7 @@ var mcode = function(ast) {
s_instructions = []
s_vars = []
s_intrinsic_cache = []
s_slot_types = {}
s_loop_break = null
s_loop_continue = null
s_label_map = {}
@@ -2951,6 +3033,7 @@ var mcode = function(ast) {
s_max_slot = 1
s_label_counter = 0
s_func_counter = 0
s_slot_types = {}
s_loop_break = null
s_loop_continue = null
s_label_map = {}

View File

@@ -141,6 +141,13 @@ var streamline = function(ir, log) {
}
// track_types reuses write_rules table; move handled specially
// Ops safe to narrow from T_NUM to T_INT when both operands are T_INT.
// Excludes divide (int/int can produce float) and pow (int**neg produces float).
var int_narrowable_ops = {
add: true, subtract: true, multiply: true,
remainder: true, modulo: true, max: true, min: true
}
var track_types = function(slot_types, instr) {
var op = instr[0]
var rule = null
@@ -157,6 +164,13 @@ var streamline = function(ir, log) {
if (typ == null) {
typ = access_value_type(instr[2])
}
// Narrow T_NUM to T_INT when both operands are T_INT
if (typ == T_NUM && instr[3] != null && int_narrowable_ops[op] == true) {
if (slot_is(slot_types, instr[2], T_INT)
&& slot_is(slot_types, instr[3], T_INT)) {
typ = T_INT
}
}
slot_types[instr[rule[0]]] = typ
}
return null
@@ -373,6 +387,34 @@ var streamline = function(ir, log) {
max: T_NUM, min: T_NUM, remainder: T_NUM, modulo: T_NUM
}
var narrow_arith_type = function(write_types, param_types, instr, typ) {
var s2 = null
var s3 = null
var t2 = null
var t3 = null
if (typ != T_NUM || instr[3] == null || int_narrowable_ops[instr[0]] != true) {
return typ
}
s2 = instr[2]
s3 = instr[3]
if (is_number(s2)) {
t2 = write_types[s2]
if (t2 == null && param_types != null && s2 < length(param_types)) {
t2 = param_types[s2]
}
}
if (is_number(s3)) {
t3 = write_types[s3]
if (t3 == null && param_types != null && s3 < length(param_types)) {
t3 = param_types[s3]
}
}
if (t2 == T_INT && t3 == T_INT) {
return T_INT
}
return typ
}
var infer_slot_write_types = function(func, param_types) {
var instructions = func.instructions
var nr_args = func.nr_args != null ? func.nr_args : 0
@@ -478,6 +520,7 @@ var streamline = function(ir, log) {
if (typ == null) {
typ = access_value_type(instr[2])
}
typ = narrow_arith_type(write_types, param_types, instr, typ)
if (slot > 0 && slot > nr_args) {
merge_backward(write_types, slot, typ)
}