diff --git a/mcode.cm b/mcode.cm index e5cbb58f..92eb9344 100644 --- a/mcode.cm +++ b/mcode.cm @@ -557,7 +557,7 @@ var mcode = function(ast) { // emit_relational: int -> float -> text -> disrupt // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure - var emit_relational = function(int_op, float_op, text_op) { + var emit_relational = function(poly_op, int_op, float_op, text_op) { var dest = _bp_dest var left = _bp_left var right = _bp_right @@ -590,26 +590,17 @@ var mcode = function(ast) { return null } - not_int = gen_label("rel_ni") not_num = gen_label("rel_nn") done = gen_label("rel_done") err = gen_label("rel_err") t0 = alloc_slot() - emit_2("is_int", t0, left) - emit_jump_cond("jump_false", t0, not_int) - t1 = alloc_slot() - emit_2("is_int", t1, right) - emit_jump_cond("jump_false", t1, not_int) - emit_3(int_op, dest, left, right) - emit_jump(done) - - emit_label(not_int) emit_2("is_num", t0, left) emit_jump_cond("jump_false", t0, not_num) + t1 = alloc_slot() emit_2("is_num", t1, right) emit_jump_cond("jump_false", t1, not_num) - emit_3(float_op, dest, left, right) + emit_3(poly_op, dest, left, right) emit_jump(done) emit_label(not_num) @@ -651,10 +642,10 @@ var mcode = function(ast) { // Central router: maps op string to decomposition helper // Sets _bp_* closure vars then calls helper with reduced args var relational_ops = { - lt: ["lt_int", "lt_float", "lt_text"], - le: ["le_int", "le_float", "le_text"], - gt: ["gt_int", "gt_float", "gt_text"], - ge: ["ge_int", "ge_float", "ge_text"] + lt: ["lt", "lt_int", "lt_float", "lt_text"], + le: ["le", "le_int", "le_float", "le_text"], + gt: ["gt", "gt_int", "gt_float", "gt_text"], + ge: ["ge", "ge_int", "ge_float", "ge_text"] } var emit_binop = function(op_str, dest, left, right) { var rel = null @@ -671,7 +662,7 @@ var mcode = function(ast) { } else { rel = relational_ops[op_str] if (rel != null) { - emit_relational(rel[0], rel[1], rel[2]) + emit_relational(rel[0], rel[1], rel[2], rel[3]) } else if (op_str == "subtract" || op_str == "multiply" || op_str == "divide" || op_str == "modulo" || op_str == "remainder" || op_str == "pow") { diff --git a/source/mach.c b/source/mach.c index 93341ac9..5d921d8e 100644 --- a/source/mach.c +++ b/source/mach.c @@ -1112,7 +1112,10 @@ static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) { default: break; } } - /* Different types: EQ→false, NEQ→true, others→false */ + /* Different types for ordering comparisons: disrupt */ + if (op >= MACH_LT && op <= MACH_GE) + return JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type"); + /* EQ/NEQ with different types: false/true */ if (op == MACH_NEQ) return JS_NewBool(ctx, 1); return JS_NewBool(ctx, 0); } @@ -3045,6 +3048,13 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) { else if (strcmp(op, "ceiling") == 0) { ABC3(MACH_CEILING); } else if (strcmp(op, "round") == 0) { ABC3(MACH_ROUND); } else if (strcmp(op, "trunc") == 0) { ABC3(MACH_TRUNC); } + /* Generic comparisons */ + else if (strcmp(op, "eq") == 0) { ABC3(MACH_EQ); } + else if (strcmp(op, "ne") == 0) { ABC3(MACH_NEQ); } + else if (strcmp(op, "lt") == 0) { ABC3(MACH_LT); } + else if (strcmp(op, "le") == 0) { ABC3(MACH_LE); } + else if (strcmp(op, "gt") == 0) { ABC3(MACH_GT); } + else if (strcmp(op, "ge") == 0) { ABC3(MACH_GE); } /* Typed integer comparisons */ else if (strcmp(op, "eq_int") == 0) { ABC3(MACH_EQ_INT); } else if (strcmp(op, "ne_int") == 0) { ABC3(MACH_NE_INT); } diff --git a/streamline.cm b/streamline.cm index f7858c78..4357d46f 100644 --- a/streamline.cm +++ b/streamline.cm @@ -1212,7 +1212,7 @@ var streamline = function(ir, log) { idx = 0 while (idx < num_instr) { - if (!reachable[idx] && is_array(instructions[idx])) { + if (!reachable[idx] && is_array(instructions[idx]) && (disruption_pc < 0 || idx >= disruption_pc)) { nc = nc + 1 instructions[idx] = "_nop_ucfg_" + text(nc) } @@ -1222,6 +1222,145 @@ var streamline = function(ir, log) { return null } + // ========================================================= + // Pass: hoist_loop_invariant — move loop-invariant `get` + // instructions before the loop header. A `get` is invariant + // if the closure slot it reads is not `put` inside the loop. + // Uses in-place nop swapping to preserve instruction indices. + // ========================================================= + var hoist_loop_invariant = function(func) { + var instructions = func.instructions + var num_instr = 0 + var label_map = null + var i = 0 + var j = 0 + var k = 0 + var instr = null + var candidate = null + var tgt_label = null + var tgt_idx = 0 + var loop_start = 0 + var loop_end = 0 + var put_slots = null + var write_regs = null + var wr = null + var src_slot = 0 + var safe_nop = -1 + var nc = 0 + + if (instructions == null || length(instructions) == 0) { + return null + } + + num_instr = length(instructions) + + // Build label → index map + label_map = {} + i = 0 + while (i < num_instr) { + instr = instructions[i] + if (is_text(instr) && !starts_with(instr, "_nop_")) { + label_map[instr] = i + } + i = i + 1 + } + + // Find backward jumps (loop back-edges) and hoist invariant gets + i = 0 + while (i < num_instr) { + instr = instructions[i] + if (is_array(instr) && instr[0] == "jump") { + tgt_label = instr[1] + tgt_idx = label_map[tgt_label] + if (is_number(tgt_idx) && tgt_idx <= i) { + loop_start = tgt_idx + loop_end = i + + // Collect closure slots written by put and registers + // written by any instruction inside the loop + put_slots = {} + write_regs = {} + j = loop_start + while (j <= loop_end) { + instr = instructions[j] + if (is_array(instr)) { + if (instr[0] == "put") { + put_slots[text(instr[2])] = true + } + // Track destination registers written by instructions + wr = write_rules[instr[0]] + if (wr != null && wr[0] == 1 && instr[0] != "get") { + write_regs[text(instr[1])] = true + } + if (instr[0] == "move") { + write_regs[text(instr[1])] = true + } + if (instr[0] == "invoke" || instr[0] == "tail_invoke") { + write_regs[text(instr[1])] = true + } + } + j = j + 1 + } + + // Find safe nop position: scan backward from loop_start + // for a nop in the linear fall-through path (no jumps between) + safe_nop = -1 + k = loop_start - 1 + while (k >= 0) { + candidate = instructions[k] + if (is_text(candidate) && starts_with(candidate, "_nop_")) { + safe_nop = k + k = -1 + } + if (is_array(candidate)) { + // Hit an actual instruction — stop if it's a jump or branch + if (candidate[0] == "jump" || candidate[0] == "jump_true" || candidate[0] == "jump_false" || candidate[0] == "jump_not_null" || candidate[0] == "return" || candidate[0] == "disrupt") { + k = -1 + } + } + k = k - 1 + } + + // Find and hoist invariant get instructions + if (safe_nop >= 0) { + j = loop_start + while (j <= loop_end) { + instr = instructions[j] + if (is_array(instr) && instr[0] == "get") { + src_slot = instr[2] + if (put_slots[text(src_slot)] != true && write_regs[text(instr[1])] != true && safe_nop >= 0) { + instructions[safe_nop] = instr + nc = nc + 1 + instructions[j] = "_nop_hli_" + text(nc) + // Find next safe nop for additional gets + k = safe_nop - 1 + safe_nop = -1 + while (k >= 0) { + candidate = instructions[k] + if (is_text(candidate) && starts_with(candidate, "_nop_")) { + safe_nop = k + k = -1 + } + if (is_array(candidate)) { + if (candidate[0] == "jump" || candidate[0] == "jump_true" || candidate[0] == "jump_false" || candidate[0] == "jump_not_null" || candidate[0] == "return" || candidate[0] == "disrupt") { + k = -1 + } + } + k = k - 1 + } + } + } + j = j + 1 + } + } + } + } + i = i + 1 + } + + return null + } + // ========================================================= // Pass: eliminate_dead_jumps — jump to next label → nop // ========================================================= @@ -2068,12 +2207,14 @@ var streamline = function(ir, log) { var slot_types = null var run_cycle = function(suffix) { var name = null - name = "infer_param_types" + suffix - run_pass(func, name, function() { - param_types = infer_param_types(func) - return param_types - }) - if (verify_fn) verify_fn(func, "after " + name) + if (param_types == null) { + name = "infer_param_types" + suffix + run_pass(func, name, function() { + param_types = infer_param_types(func) + return param_types + }) + if (verify_fn) verify_fn(func, "after " + name) + } name = "infer_slot_write_types" + suffix run_pass(func, name, function() { @@ -2127,6 +2268,12 @@ var streamline = function(ir, log) { return eliminate_dead_jumps(func, log) }) if (verify_fn) verify_fn(func, "after " + name) + + name = "eliminate_unreachable_cfg" + suffix + run_pass(func, name, function() { + return eliminate_unreachable_cfg(func) + }) + if (verify_fn) verify_fn(func, "after " + name) return null } @@ -2135,6 +2282,7 @@ var streamline = function(ir, log) { } run_cycle("") + run_cycle("_2") if (ir._warn) { diagnose_function(func, {param_types: param_types, write_types: write_types}, ir) }