diff --git a/mcode.cm b/mcode.cm index 163c1073..bb7bbaaf 100644 --- a/mcode.cm +++ b/mcode.cm @@ -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 = {} diff --git a/streamline.cm b/streamline.cm index bb4c2c9e..3c4805f4 100644 --- a/streamline.cm +++ b/streamline.cm @@ -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) }