aot pass all tests

This commit is contained in:
2026-02-18 16:53:33 -06:00
parent 469b7ac478
commit c33c35de87
6 changed files with 583 additions and 149 deletions

View File

@@ -360,10 +360,93 @@ ${sw("w", "%fp", "%dest", "%r")}
// Category C: Allocating helpers (return fp or 0)
// ============================================================
// Allocating binary ops: read 2 slots, call C, refresh, write dest
// add: int fast path in-helper, slow path calls runtime
h[] = `export function l $__add_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
%a_tag =l and %a, 1
%b_tag =l and %b, 1
%a_is_int =w ceql %a_tag, 0
%b_is_int =w ceql %b_tag, 0
%both_int =w and %a_is_int, %b_is_int
jnz %both_int, @int_fast, @slow
@int_fast
%ai =l sar %a, 1
%bi =l sar %b, 1
%sum =l add %ai, %bi
%sumw =w copy %sum
%sumext =l extsw %sumw
%sum_ok =w ceql %sumext, %sum
jnz %sum_ok, @int_store, @slow
@int_store
%rtag =l shl %sum, 1
${sw("w", "%fp", "%dest", "%rtag")}
ret %fp
@slow
%r =l call $cell_rt_add(l %ctx, l %a, l %b)
${alloc_tail("%r")}
}`
// sub: int fast path in-helper, slow path calls float helper
h[] = `export function l $__sub_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
%a_tag =l and %a, 1
%b_tag =l and %b, 1
%a_is_int =w ceql %a_tag, 0
%b_is_int =w ceql %b_tag, 0
%both_int =w and %a_is_int, %b_is_int
jnz %both_int, @int_fast, @slow
@int_fast
%ai =l sar %a, 1
%bi =l sar %b, 1
%diff =l sub %ai, %bi
%diffw =w copy %diff
%diffext =l extsw %diffw
%diff_ok =w ceql %diffext, %diff
jnz %diff_ok, @int_store, @slow
@int_store
%rtag =l shl %diff, 1
${sw("w", "%fp", "%dest", "%rtag")}
ret %fp
@slow
%r =l call $qbe_float_sub(l %ctx, l %a, l %b)
${alloc_tail("%r")}
}`
// mul: int fast path in-helper, slow path calls float helper
h[] = `export function l $__mul_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
@entry
${sr("a", "%s1")}
${sr("b", "%s2")}
%a_tag =l and %a, 1
%b_tag =l and %b, 1
%a_is_int =w ceql %a_tag, 0
%b_is_int =w ceql %b_tag, 0
%both_int =w and %a_is_int, %b_is_int
jnz %both_int, @int_fast, @slow
@int_fast
%ai =l sar %a, 1
%bi =l sar %b, 1
%prod =l mul %ai, %bi
%prodw =w copy %prod
%prodext =l extsw %prodw
%prod_ok =w ceql %prodext, %prod
jnz %prod_ok, @int_store, @slow
@int_store
%rtag =l shl %prod, 1
${sw("w", "%fp", "%dest", "%rtag")}
ret %fp
@slow
%r =l call $qbe_float_mul(l %ctx, l %a, l %b)
${alloc_tail("%r")}
}`
// Remaining allocating binary ops: call C, refresh, write dest
var ab_ops = [
["add", "cell_rt_add"], ["sub", "qbe_float_sub"],
["mul", "qbe_float_mul"], ["div", "qbe_float_div"],
["div", "qbe_float_div"],
["mod", "qbe_float_mod"], ["pow", "qbe_float_pow"],
["concat", "JS_ConcatString"]
]
@@ -685,11 +768,27 @@ var qbe_emit = function(ir, qbe, export_name) {
var si = 0
var scan = null
var scan_op = null
var label_pos = {}
var instr_idx = 0
var has_invokes = false
var seg_counter = 0
var ri = 0
var seg_num = 0
var resume_val = 0
var j_lbl = null
var j_idx = null
var jt_lbl = null
var jt_idx = null
var jt_backedge = false
var jf_lbl = null
var jf_idx = null
var jf_backedge = false
var jn_lbl = null
var jn_idx = null
var jn_backedge = false
var jnn_lbl = null
var jnn_idx = null
var jnn_backedge = false
// Pre-scan: count invoke/tail_invoke points to assign segment numbers.
// Must skip dead code (instructions after terminators) the same way
@@ -701,6 +800,7 @@ var qbe_emit = function(ir, qbe, export_name) {
scan = instrs[si]
si = si + 1
if (is_text(scan)) {
label_pos[sanitize(scan)] = si - 1
// Labels reset dead code state (unless they're nop pseudo-labels)
if (!starts_with(scan, "_nop_ur_") && !starts_with(scan, "_nop_tc_"))
scan_dead = false
@@ -709,11 +809,11 @@ var qbe_emit = function(ir, qbe, export_name) {
if (scan_dead) continue
if (!is_array(scan)) continue
scan_op = scan[0]
if (scan_op == "invoke" || scan_op == "tail_invoke") {
if (scan_op == "invoke") {
invoke_count = invoke_count + 1
}
// Track terminators — same set as in the main loop
if (scan_op == "return" || scan_op == "jump" || scan_op == "goinvoke" || scan_op == "disrupt") {
if (scan_op == "return" || scan_op == "jump" || scan_op == "goinvoke" || scan_op == "tail_invoke" || scan_op == "disrupt") {
scan_dead = true
}
}
@@ -795,11 +895,24 @@ var qbe_emit = function(ir, qbe, export_name) {
emit(`@${lbl}_ok`)
}
// Poll pause/interrupt state on taken backward jumps.
var emit_backedge_branch = function(target_label) {
var chk_lbl = fresh()
emit(` %${chk_lbl} =w call $cell_rt_check_backedge(l %ctx)`)
if (has_handler && !in_handler) {
emit(` jnz %${chk_lbl}, @disruption_handler, @${target_label}`)
} else {
needs_exc_ret = true
emit(` jnz %${chk_lbl}, @_exc_ret, @${target_label}`)
}
}
// Walk instructions
var last_was_term = false
i = 0
while (i < length(instrs)) {
instr = instrs[i]
instr_idx = i
// Emit @disruption_handler at the right flat index
// disruption_pc counts all entries (labels + instructions)
@@ -909,18 +1022,117 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Generic arithmetic (VM dispatches int/float) ---
if (op == "add") {
emit(` %fp =l call $__add_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_a_tag =l and ${lhs}, 1`)
emit(` %${p}_b_tag =l and ${rhs}, 1`)
emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`)
emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`)
emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`)
emit(`@${p}_int`)
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_sum =l add %${p}_ai, %${p}_bi`)
emit(` %${p}_sumw =w copy %${p}_sum`)
emit(` %${p}_sumext =l extsw %${p}_sumw`)
emit(` %${p}_sum_ok =w ceql %${p}_sumext, %${p}_sum`)
emit(` jnz %${p}_sum_ok, @${p}_int_store, @${p}_slow`)
emit(`@${p}_int_store`)
emit(` %${p}_tag =l shl %${p}_sum, 1`)
s_write(a1, `%${p}_tag`)
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
emit(` %${p}_r =l call $cell_rt_add(l %ctx, l ${lhs}, l ${rhs})`)
emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`)
chk = fresh()
emit(` %${chk} =w ceql %fp, 0`)
if (has_handler && !in_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`)
}
emit(`@${chk}_ok`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "subtract") {
emit(` %fp =l call $__sub_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_a_tag =l and ${lhs}, 1`)
emit(` %${p}_b_tag =l and ${rhs}, 1`)
emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`)
emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`)
emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`)
emit(`@${p}_int`)
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_diff =l sub %${p}_ai, %${p}_bi`)
emit(` %${p}_diffw =w copy %${p}_diff`)
emit(` %${p}_diffext =l extsw %${p}_diffw`)
emit(` %${p}_diff_ok =w ceql %${p}_diffext, %${p}_diff`)
emit(` jnz %${p}_diff_ok, @${p}_int_store, @${p}_slow`)
emit(`@${p}_int_store`)
emit(` %${p}_tag =l shl %${p}_diff, 1`)
s_write(a1, `%${p}_tag`)
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
emit(` %${p}_r =l call $qbe_float_sub(l %ctx, l ${lhs}, l ${rhs})`)
emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`)
chk = fresh()
emit(` %${chk} =w ceql %fp, 0`)
if (has_handler && !in_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`)
}
emit(`@${chk}_ok`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "multiply") {
emit(` %fp =l call $__mul_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_a_tag =l and ${lhs}, 1`)
emit(` %${p}_b_tag =l and ${rhs}, 1`)
emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`)
emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`)
emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`)
emit(`@${p}_int`)
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_prod =l mul %${p}_ai, %${p}_bi`)
emit(` %${p}_prodw =w copy %${p}_prod`)
emit(` %${p}_prodext =l extsw %${p}_prodw`)
emit(` %${p}_prod_ok =w ceql %${p}_prodext, %${p}_prod`)
emit(` jnz %${p}_prod_ok, @${p}_int_store, @${p}_slow`)
emit(`@${p}_int_store`)
emit(` %${p}_tag =l shl %${p}_prod, 1`)
s_write(a1, `%${p}_tag`)
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
emit(` %${p}_r =l call $qbe_float_mul(l %ctx, l ${lhs}, l ${rhs})`)
emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`)
chk = fresh()
emit(` %${chk} =w ceql %fp, 0`)
if (has_handler && !in_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`)
}
emit(`@${chk}_ok`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
if (op == "divide") {
@@ -1003,27 +1215,93 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Comparisons (int path, no GC) ---
if (op == "eq_int") {
emit(` call $__eq_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w ceqw %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "ne_int") {
emit(` call $__ne_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w cnew %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "lt_int") {
emit(` call $__lt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w csltw %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "gt_int") {
emit(` call $__gt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w csgtw %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "le_int") {
emit(` call $__le_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w cslew %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
if (op == "ge_int") {
emit(` call $__ge_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
emit(` %${p}_cr =w csgew %${p}_aiw, %${p}_biw`)
emit(` %${p}_crext =l extuw %${p}_cr`)
emit(` %${p}_sh =l shl %${p}_crext, 5`)
emit(` %${p}_r =l or %${p}_sh, 3`)
s_write(a1, `%${p}_r`)
continue
}
@@ -1240,39 +1518,99 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Control flow ---
if (op == "jump") {
emit(` jmp @${sanitize(a1)}`)
j_lbl = sanitize(a1)
j_idx = label_pos[j_lbl]
if (j_idx != null && j_idx < instr_idx) {
emit_backedge_branch(j_lbl)
} else {
emit(` jmp @${j_lbl}`)
}
last_was_term = true
continue
}
if (op == "jump_true") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_f`)
jt_lbl = sanitize(a2)
jt_idx = label_pos[jt_lbl]
jt_backedge = jt_idx != null && jt_idx < instr_idx
emit(` %${p}_is_true =w ceql ${v}, ${text(qbe.js_true)}`)
emit(` jnz %${p}_is_true, @${p}_take, @${p}_chk_fast`)
emit(`@${p}_chk_fast`)
emit(` %${p}_tag =l and ${v}, 31`)
emit(` %${p}_is_bool =w ceql %${p}_tag, 3`)
emit(` %${p}_is_null =w ceql %${p}_tag, 7`)
emit(` %${p}_is_falsey =w or %${p}_is_bool, %${p}_is_null`)
emit(` jnz %${p}_is_falsey, @${p}_f, @${p}_tb`)
emit(`@${p}_tb`)
emit(` %${p}_tbv =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}_tbv, @${p}_take, @${p}_f`)
emit(`@${p}_take`)
if (jt_backedge) {
emit_backedge_branch(jt_lbl)
} else {
emit(` jmp @${jt_lbl}`)
}
emit(`@${p}_f`)
continue
}
if (op == "jump_false") {
v = s_read(a1)
p = fresh()
emit(` %${p} =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}, @${p}_t, @${sanitize(a2)}`)
jf_lbl = sanitize(a2)
jf_idx = label_pos[jf_lbl]
jf_backedge = jf_idx != null && jf_idx < instr_idx
emit(` %${p}_is_true =w ceql ${v}, ${text(qbe.js_true)}`)
emit(` jnz %${p}_is_true, @${p}_t, @${p}_chk_fast`)
emit(`@${p}_chk_fast`)
emit(` %${p}_tag =l and ${v}, 31`)
emit(` %${p}_is_bool =w ceql %${p}_tag, 3`)
emit(` %${p}_is_null =w ceql %${p}_tag, 7`)
emit(` %${p}_is_fast_false =w or %${p}_is_bool, %${p}_is_null`)
emit(` jnz %${p}_is_fast_false, @${p}_take, @${p}_tb`)
emit(`@${p}_tb`)
emit(` %${p}_tbv =w call $JS_ToBool(l %ctx, l ${v})`)
emit(` jnz %${p}_tbv, @${p}_t, @${p}_take`)
emit(`@${p}_take`)
if (jf_backedge) {
emit_backedge_branch(jf_lbl)
} else {
emit(` jmp @${jf_lbl}`)
}
emit(`@${p}_t`)
continue
}
if (op == "jump_null") {
v = s_read(a1)
p = fresh()
jn_lbl = sanitize(a2)
jn_idx = label_pos[jn_lbl]
jn_backedge = jn_idx != null && jn_idx < instr_idx
emit(` %${p} =w ceql ${v}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_nn`)
if (jn_backedge) {
emit(` jnz %${p}, @${p}_bn, @${p}_nn`)
emit(`@${p}_bn`)
emit_backedge_branch(jn_lbl)
} else {
emit(` jnz %${p}, @${jn_lbl}, @${p}_nn`)
}
emit(`@${p}_nn`)
continue
}
if (op == "jump_not_null") {
v = s_read(a1)
p = fresh()
jnn_lbl = sanitize(a2)
jnn_idx = label_pos[jnn_lbl]
jnn_backedge = jnn_idx != null && jnn_idx < instr_idx
emit(` %${p} =w cnel ${v}, ${text(qbe.js_null)}`)
emit(` jnz %${p}, @${sanitize(a2)}, @${p}_n`)
if (jnn_backedge) {
emit(` jnz %${p}, @${p}_bn, @${p}_n`)
emit(`@${p}_bn`)
emit_backedge_branch(jnn_lbl)
} else {
emit(` jnz %${p}, @${jnn_lbl}, @${p}_n`)
}
emit(`@${p}_n`)
continue
}
@@ -1316,26 +1654,14 @@ var qbe_emit = function(ir, qbe, export_name) {
continue
}
if (op == "tail_invoke") {
// Same as invoke — dispatch loop regular call with resume
seg_counter = seg_counter + 1
seg_num = seg_counter
resume_val = seg_num * 65536 + a2
emit(` %_tinv_addr${text(seg_num)} =l sub %fp, 8`)
emit(` storel ${text(resume_val * 2)}, %_tinv_addr${text(seg_num)}`)
emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`)
emit(" ret 0")
emit(`@_seg${text(seg_num)}`)
// Check for exception after dispatch loop resumes us
// Tail call: hand control to dispatch loop and do not resume this segment.
// Use 0xFFFF as ret_slot (no result writeback into current frame).
p = fresh()
emit(` %${p} =w call $JS_HasException(l %ctx)`)
if (has_handler && !in_handler) {
emit(` jnz %${p}, @disruption_handler, @${p}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${p}, @_exc_ret, @${p}_ok`)
}
emit(`@${p}_ok`)
last_was_term = false
emit(` %${p}_addr =l sub %fp, 8`)
emit(` storel ${text(65535 * 2)}, %${p}_addr`)
emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`)
emit(" ret 0")
last_was_term = true
continue
}
if (op == "goframe") {