From 99fa86a09c885230892dbc55dd9f055a7d9073e7 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Sat, 21 Feb 2026 19:06:41 -0600 Subject: [PATCH] asserts only for frame gets --- qbe_emit.cm | 566 ++++++++++++++++++++++++++++++++++++------- source/mach.c | 32 +-- source/qbe_helpers.c | 176 -------------- 3 files changed, 490 insertions(+), 284 deletions(-) diff --git a/qbe_emit.cm b/qbe_emit.cm index 7b1b759e..8aade4c9 100644 --- a/qbe_emit.cm +++ b/qbe_emit.cm @@ -41,49 +41,6 @@ ${sw("w", "%fp2", "%dest", result_var)} // Category A: Pure QBE helpers (no C calls) // ============================================================ - // ============================================================ - // Category B: Non-allocating C call helpers - // ============================================================ - - // Type checks via C (no ctx needed except is_proxy) - var tc_ops = [ - ["is_stone", "JS_IsStone", false], - ["is_proxy", "cell_rt_is_proxy", true] - ] - var i = 0 - var tc_name = null - var tc_cfn = null - var tc_ctx = null - while (i < length(tc_ops)) { - tc_name = tc_ops[i][0] - tc_cfn = tc_ops[i][1] - tc_ctx = tc_ops[i][2] - if (tc_ctx) { - h[] = `export function $__${tc_name}_ss(l %ctx, l %fp, l %dest, l %src) ${lb} -@entry -${sr("a", "%src")} - %cr =w call $${tc_cfn}(l %ctx, l %a) - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - } else { - h[] = `export function $__${tc_name}_ss(l %fp, l %dest, l %src) ${lb} -@entry -${sr("a", "%src")} - %cr =w call $${tc_cfn}(l %a) - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - } - i = i + 1 - } - // is_record: pointer + header type check (OBJ_RECORD=3), chase forwards h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) ${lb} @entry @@ -179,7 +136,7 @@ ${sw("w", "%fp", "%dest", "%r")} // eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value) var tol_ops = ["eq_tol", "ne_tol"] - i = 0 + var i = 0 while (i < length(tol_ops)) { h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) ${lb} @entry @@ -266,25 +223,6 @@ ${sw("w", "%fp", "%dest", "%r")} ret }` - // and, or (return tagged JSValue directly) - h[] = `export function $__and_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %r =l call $cell_rt_and(l %ctx, l %a, l %b) -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - h[] = `export function $__or_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %r =l call $cell_rt_or(l %ctx, l %a, l %b) -${sw("w", "%fp", "%dest", "%r")} - ret -}` - // Bitwise unary/binary ops: mirror MACH mcode op behavior via JS_ToInt32 coercion. h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) ${lb} @entry @@ -325,7 +263,7 @@ ${sw("w", "%fp", "%dest", "%r")} // Category C: Allocating helpers (return fp or 0) // ============================================================ - // concat allocates; keep refresh path + // concat allocates; runtime helper mirrors MACH self-assign fast path h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} @@ -1098,6 +1036,8 @@ var qbe_emit = function(ir, qbe, export_name) { var text_arg_slot = 0 var text_dest_slot = 0 var cmp_id = 0 + var depth = 0 + var d = 0 // Pre-scan: count invoke/tail_invoke points to assign segment numbers. // Must skip dead code (instructions after terminators) the same way @@ -1234,14 +1174,7 @@ var qbe_emit = function(ir, qbe, export_name) { // 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}`) - } + emit(` jmp @${target_label}`) } // Inline JS_ToBool equivalent for hot branch paths. @@ -1894,7 +1827,24 @@ var qbe_emit = function(ir, qbe, export_name) { } if (op == "stone_text") { v = s_read(a1) - emit(` call $cell_rt_stone_text(l ${v})`) + p = fresh() + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_done`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_text =w ceql %${p}_ht, 2`) + emit(` jnz %${p}_is_text, @${p}_stone_chk, @${p}_done`) + emit(`@${p}_stone_chk`) + emit(` %${p}_s =l and %${p}_hdr, 8`) + emit(` %${p}_is_stone =w cnel %${p}_s, 0`) + emit(` jnz %${p}_is_stone, @${p}_done, @${p}_set`) + emit(`@${p}_set`) + emit(` %${p}_new_hdr =l or %${p}_hdr, 8`) + emit(` storel %${p}_new_hdr, %${p}_ptr`) + emit(`@${p}_done`) continue } @@ -1955,19 +1905,279 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "is_stone") { - emit(` call $__is_stone_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + p = fresh() + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_yes`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_chk`) + emit(` %${p}_s =l and %${p}_hdr, 8`) + emit(` %${p}_w =w cnel %${p}_s, 0`) + emit(` jmp @${p}_done`) + emit(`@${p}_yes`) + emit(` %${p}_w =w copy 1`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } if (op == "is_proxy") { - emit(` call $__is_proxy_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + p = fresh() + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_chk`) + emit(` %${p}_is_fn =w ceql %${p}_ht, 4`) + emit(` jnz %${p}_is_fn, @${p}_len_chk, @${p}_no`) + emit(`@${p}_len_chk`) + emit(` %${p}_len_p =l add %${p}_ptr, 16`) + emit(` %${p}_len_q =l loadl %${p}_len_p`) + emit(` %${p}_len =l and %${p}_len_q, 65535`) + emit(` %${p}_w =w ceql %${p}_len, 2`) + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } - if (op == "is_blob" || op == "is_data" || op == "is_fit" || - op == "is_char" || op == "is_digit" || op == "is_letter" || + if (op == "is_blob") { + v = s_read(a2) + p = fresh() + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_chk`) + emit(` %${p}_w =w ceql %${p}_ht, 1`) + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_data") { + v = s_read(a2) + p = fresh() + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_chk`) + emit(` %${p}_is_arr =w ceql %${p}_ht, 0`) + emit(` %${p}_is_fn =w ceql %${p}_ht, 4`) + emit(` %${p}_is_blob =w ceql %${p}_ht, 1`) + emit(` %${p}_bad =w or %${p}_is_arr, %${p}_is_fn`) + emit(` %${p}_bad =w or %${p}_bad, %${p}_is_blob`) + emit(` %${p}_w =w ceqw %${p}_bad, 0`) + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_fit") { + v = s_read(a2) + p = fresh() + emit(` %${p}_tag =l and ${v}, 1`) + emit(` %${p}_is_int =w ceql %${p}_tag, 0`) + emit(` jnz %${p}_is_int, @${p}_yes, @${p}_sfloat_chk`) + emit(`@${p}_sfloat_chk`) + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_sfloat =w ceql %${p}_ptag, 5`) + emit(` jnz %${p}_is_sfloat, @${p}_sfloat, @${p}_no`) + emit(`@${p}_sfloat`) + lhs_d = emit_num_to_double(v) + emit(` %${p}_ti =d call $trunc(d ${lhs_d})`) + emit(` %${p}_eqi =w ceqd ${lhs_d}, %${p}_ti`) + emit(` %${p}_ad =d call $fabs(d ${lhs_d})`) + emit(` %${p}_ltlim =w cltd %${p}_ad, d_9007199254740992.0`) + emit(` %${p}_eqlim =w ceqd %${p}_ad, d_9007199254740992.0`) + emit(` %${p}_lim =w or %${p}_ltlim, %${p}_eqlim`) + emit(` %${p}_w =w and %${p}_eqi, %${p}_lim`) + emit(` jmp @${p}_done`) + emit(`@${p}_yes`) + emit(` %${p}_w =w copy 1`) + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_char") { + v = s_read(a2) + p = fresh() + emit(` %${p}_imm =l and ${v}, 31`) + emit(` %${p}_is_imm =w ceql %${p}_imm, 11`) + emit(` jnz %${p}_is_imm, @${p}_imm_path, @${p}_ptr_chk`) + emit(`@${p}_imm_path`) + emit(` %${p}_ilen =l shr ${v}, 5`) + emit(` %${p}_ilen =l and %${p}_ilen, 7`) + emit(` %${p}_w =w ceql %${p}_ilen, 1`) + emit(` jmp @${p}_done`) + emit(`@${p}_ptr_chk`) + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_chk`) + emit(` %${p}_is_text =w ceql %${p}_ht, 2`) + emit(` jnz %${p}_is_text, @${p}_len_chk, @${p}_no`) + emit(`@${p}_len_chk`) + emit(` %${p}_len_p =l add %${p}_ptr, 8`) + emit(` %${p}_len_l =l loadl %${p}_len_p`) + emit(` %${p}_w =w ceql %${p}_len_l, 1`) + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_digit" || op == "is_letter" || op == "is_lower" || op == "is_upper" || op == "is_ws") { v = s_read(a2) p = fresh() - emit(` %${p}_w =w call $cell_rt_${op}(l ${v})`) + emit(` %${p}_imm =l and ${v}, 31`) + emit(` %${p}_is_imm =w ceql %${p}_imm, 11`) + emit(` jnz %${p}_is_imm, @${p}_imm_len, @${p}_ptr_chk`) + emit(`@${p}_imm_len`) + emit(` %${p}_ilen =l shr ${v}, 5`) + emit(` %${p}_ilen =l and %${p}_ilen, 7`) + emit(` %${p}_imm_one =w ceql %${p}_ilen, 1`) + emit(` jnz %${p}_imm_one, @${p}_imm_char, @${p}_no`) + emit(`@${p}_imm_char`) + emit(` %${p}_ch_l =l shr ${v}, 8`) + emit(` %${p}_ch_l =l and %${p}_ch_l, 255`) + emit(` %${p}_ch_w =w copy %${p}_ch_l`) + emit(` jmp @${p}_pred`) + emit(`@${p}_ptr_chk`) + emit(` %${p}_ptag =l and ${v}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) + emit(`@${p}_ptr`) + emit(` %${p}_ptr =l and ${v}, -8`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(`@${p}_chase`) + emit(` %${p}_ht =l and %${p}_hdr, 7`) + emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) + emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_text_chk`) + emit(`@${p}_follow`) + emit(` %${p}_ptr =l shr %${p}_hdr, 3`) + emit(` %${p}_hdr =l loadl %${p}_ptr`) + emit(` jmp @${p}_chase`) + emit(`@${p}_text_chk`) + emit(` %${p}_is_text =w ceql %${p}_ht, 2`) + emit(` jnz %${p}_is_text, @${p}_text_len, @${p}_no`) + emit(`@${p}_text_len`) + emit(` %${p}_len_p =l add %${p}_ptr, 8`) + emit(` %${p}_len_l =l loadl %${p}_len_p`) + emit(` %${p}_text_one =w ceql %${p}_len_l, 1`) + emit(` jnz %${p}_text_one, @${p}_text_char, @${p}_no`) + emit(`@${p}_text_char`) + emit(` %${p}_pack_p =l add %${p}_ptr, 24`) + emit(` %${p}_pack =l loadl %${p}_pack_p`) + emit(` %${p}_ch_l =l shr %${p}_pack, 32`) + emit(` %${p}_ch_l =l and %${p}_ch_l, 255`) + emit(` %${p}_ch_w =w copy %${p}_ch_l`) + emit(`@${p}_pred`) + if (op == "is_digit") { + emit(` %${p}_lt_0 =w csltw %${p}_ch_w, 48`) + emit(` %${p}_ge_0 =w ceqw %${p}_lt_0, 0`) + emit(` %${p}_lt_9 =w csltw %${p}_ch_w, 58`) + emit(` %${p}_w =w and %${p}_ge_0, %${p}_lt_9`) + } else if (op == "is_lower") { + emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`) + emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`) + emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`) + emit(` %${p}_w =w and %${p}_ge_a, %${p}_lt_z`) + } else if (op == "is_upper") { + emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`) + emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`) + emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`) + emit(` %${p}_w =w and %${p}_ge_A, %${p}_lt_Z`) + } else if (op == "is_letter") { + emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`) + emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`) + emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`) + emit(` %${p}_is_upper =w and %${p}_ge_A, %${p}_lt_Z`) + emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`) + emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`) + emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`) + emit(` %${p}_is_lower =w and %${p}_ge_a, %${p}_lt_z`) + emit(` %${p}_w =w or %${p}_is_upper, %${p}_is_lower`) + } else { + emit(` %${p}_is_sp =w ceqw %${p}_ch_w, 32`) + emit(` %${p}_is_tb =w ceqw %${p}_ch_w, 9`) + emit(` %${p}_is_nl =w ceqw %${p}_ch_w, 10`) + emit(` %${p}_is_cr =w ceqw %${p}_ch_w, 13`) + emit(` %${p}_is_ff =w ceqw %${p}_ch_w, 12`) + emit(` %${p}_is_vt =w ceqw %${p}_ch_w, 11`) + emit(` %${p}_w =w or %${p}_is_sp, %${p}_is_tb`) + emit(` %${p}_w =w or %${p}_w, %${p}_is_nl`) + emit(` %${p}_w =w or %${p}_w, %${p}_is_cr`) + emit(` %${p}_w =w or %${p}_w, %${p}_is_ff`) + emit(` %${p}_w =w or %${p}_w, %${p}_is_vt`) + } + emit(` jmp @${p}_done`) + emit(`@${p}_no`) + emit(` %${p}_w =w copy 0`) + emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } @@ -1999,6 +2209,33 @@ var qbe_emit = function(ir, qbe, export_name) { 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}_aiw =w copy %${p}_ai`) + emit(` %${p}_biw =w copy %${p}_bi`) + if (op == "eq") { + emit(` %${p}_w =w ceqw %${p}_aiw, %${p}_biw`) + } else if (op == "ne") { + emit(` %${p}_w =w cnew %${p}_aiw, %${p}_biw`) + } else if (op == "lt") { + emit(` %${p}_w =w csltw %${p}_aiw, %${p}_biw`) + } else if (op == "le") { + emit(` %${p}_w =w cslew %${p}_aiw, %${p}_biw`) + } else if (op == "gt") { + emit(` %${p}_w =w csgtw %${p}_aiw, %${p}_biw`) + } else { + emit(` %${p}_w =w csgew %${p}_aiw, %${p}_biw`) + } + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + emit(` jmp @${p}_done`) + emit(`@${p}_slow`) cmp_id = 0 if (op == "eq") cmp_id = 0 else if (op == "ne") cmp_id = 1 @@ -2016,6 +2253,7 @@ var qbe_emit = function(ir, qbe, export_name) { } emit(`@${p}_ok`) s_write(a1, `%${p}_r`) + emit(`@${p}_done`) continue } @@ -2032,11 +2270,31 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "and") { - emit(` call $__and_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + truthy = emit_truthy_w(lhs) + emit(` jnz ${truthy}, @${p}_t, @${p}_f`) + emit(`@${p}_t`) + s_write(a1, rhs) + emit(` jmp @${p}_done`) + emit(`@${p}_f`) + s_write(a1, lhs) + emit(`@${p}_done`) continue } if (op == "or") { - emit(` call $__or_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + truthy = emit_truthy_w(lhs) + emit(` jnz ${truthy}, @${p}_t, @${p}_f`) + emit(`@${p}_t`) + s_write(a1, lhs) + emit(` jmp @${p}_done`) + emit(`@${p}_f`) + s_write(a1, rhs) + emit(`@${p}_done`) continue } @@ -2210,8 +2468,66 @@ var qbe_emit = function(ir, qbe, export_name) { } if (op == "store_index") { // IR: ["store_index", obj, val, idx] + lhs = s_read(a1) + rhs = s_read(a2) + v = s_read(a3) + p = fresh() + emit(` %${p}_idx_tag =l and ${v}, 1`) + emit(` %${p}_idx_is_int =w ceql %${p}_idx_tag, 0`) + emit(` jnz %${p}_idx_is_int, @${p}_idx_ok, @${p}_slow`) + emit(`@${p}_idx_ok`) + emit(` %${p}_idx_l =l sar ${v}, 1`) + emit(` %${p}_idx_w =w copy %${p}_idx_l`) + emit(` %${p}_idx_neg =w csltw %${p}_idx_w, 0`) + emit(` jnz %${p}_idx_neg, @${p}_slow, @${p}_arr_ptr_chk`) + emit(`@${p}_arr_ptr_chk`) + emit(` %${p}_ptag =l and ${lhs}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`) + emit(`@${p}_arr_ptr`) + emit(` %${p}_arr_ptr =l and ${lhs}, -8`) + emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) + emit(`@${p}_arr_chase`) + emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`) + emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`) + emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`) + emit(`@${p}_arr_follow`) + emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`) + emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) + emit(` jmp @${p}_arr_chase`) + emit(`@${p}_arr_chk`) + emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`) + emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`) + emit(`@${p}_arr_stone_chk`) + emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`) + emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`) + emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_cap_chk`) + emit(`@${p}_cap_chk`) + emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`) + emit(` %${p}_cap_w =w copy %${p}_cap_l`) + emit(` %${p}_in_cap =w csltw %${p}_idx_w, %${p}_cap_w`) + emit(` jnz %${p}_in_cap, @${p}_len_chk, @${p}_slow`) + emit(`@${p}_len_chk`) + emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`) + emit(` %${p}_len_l =l loadl %${p}_len_p`) + emit(` %${p}_len_w =w copy %${p}_len_l`) + emit(` %${p}_need_len =w csgew %${p}_idx_w, %${p}_len_w`) + emit(` jnz %${p}_need_len, @${p}_bump_len, @${p}_store`) + emit(`@${p}_bump_len`) + emit(` %${p}_next_len_w =w add %${p}_idx_w, 1`) + emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`) + emit(` storel %${p}_next_len_l, %${p}_len_p`) + emit(`@${p}_store`) + emit(` %${p}_idx2_l =l extsw %${p}_idx_w`) + emit(` %${p}_idx2_off =l shl %${p}_idx2_l, 3`) + emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`) + emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx2_off`) + emit(` storel ${rhs}, %${p}_item_p`) + emit(` jmp @${p}_done`) + emit(`@${p}_slow`) emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() + emit(`@${p}_done`) continue } if (op == "store_dynamic") { @@ -2240,15 +2556,53 @@ var qbe_emit = function(ir, qbe, export_name) { if (op == "get") { // mcode: get(dest, slot, depth) — a2=slot, a3=depth - p = fresh() - emit(` %${p} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`) - s_write(a1, `%${p}`) + depth = a3 + if (depth == 0) { + v = s_read(a2) + s_write(a1, v) + } else { + p = fresh() + emit(` %${p}_fp =l copy %fp`) + d = 0 + while (d < depth) { + emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`) + emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`) + emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`) + emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`) + emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`) + emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`) + emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`) + d = d + 1 + } + emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`) + emit(` %${p}_val =l loadl %${p}_slotp`) + s_write(a1, `%${p}_val`) + } continue } if (op == "put") { // mcode: put(val, slot, depth) — a2=slot, a3=depth v = s_read(a1) - emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${v}, l ${text(a3)}, l ${text(a2)})`) + depth = a3 + if (depth == 0) { + s_write(a2, v) + } else { + p = fresh() + emit(` %${p}_fp =l copy %fp`) + d = 0 + while (d < depth) { + emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`) + emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`) + emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`) + emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`) + emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`) + emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`) + emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`) + d = d + 1 + } + emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`) + emit(` storel ${v}, %${p}_slotp`) + } continue } @@ -2401,8 +2755,52 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Array push/pop [G] --- if (op == "push") { + lhs = s_read(a1) + rhs = s_read(a2) + p = fresh() + emit(` %${p}_ptag =l and ${lhs}, 7`) + emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) + emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`) + emit(`@${p}_arr_ptr`) + emit(` %${p}_arr_ptr =l and ${lhs}, -8`) + emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) + emit(`@${p}_arr_chase`) + emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`) + emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`) + emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`) + emit(`@${p}_arr_follow`) + emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`) + emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) + emit(` jmp @${p}_arr_chase`) + emit(`@${p}_arr_chk`) + emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`) + emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`) + emit(`@${p}_arr_stone_chk`) + emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`) + emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`) + emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_lens`) + emit(`@${p}_lens`) + emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`) + emit(` %${p}_len_l =l loadl %${p}_len_p`) + emit(` %${p}_len_w =w copy %${p}_len_l`) + emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`) + emit(` %${p}_cap_w =w copy %${p}_cap_l`) + emit(` %${p}_in_cap =w csltw %${p}_len_w, %${p}_cap_w`) + emit(` jnz %${p}_in_cap, @${p}_store, @${p}_slow`) + emit(`@${p}_store`) + emit(` %${p}_idx_l =l extsw %${p}_len_w`) + emit(` %${p}_idx_off =l shl %${p}_idx_l, 3`) + emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`) + emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx_off`) + emit(` storel ${rhs}, %${p}_item_p`) + emit(` %${p}_next_len_w =w add %${p}_len_w, 1`) + emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`) + emit(` storel %${p}_next_len_l, %${p}_len_p`) + emit(` jmp @${p}_done`) + emit(`@${p}_slow`) emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() + emit(`@${p}_done`) continue } if (op == "pop") { diff --git a/source/mach.c b/source/mach.c index f3261c60..1bb25edf 100644 --- a/source/mach.c +++ b/source/mach.c @@ -24,6 +24,7 @@ */ #include "quickjs-internal.h" +#include /* ============================================================ Mach VM instruction definitions (private to mach.c) @@ -2018,21 +2019,12 @@ vm_dispatch: int depth = b; JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame); - if (!target) { - fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n", - pc-1, a, depth, c, code->nr_slots, instr); - result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame"); - goto disrupt; - } + assert(depth > 0); + assert(target != NULL); for (int d = 1; d < depth; d++) { fn = JS_VALUE_GET_FUNCTION(target->function); JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame); - if (!next) { - fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n", - d, pc-1, a, depth, c, code->nr_slots, instr); - result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame at depth %d", d); - goto disrupt; - } + assert(next != NULL); target = next; } stone_mutable_text(target->slots[c]); @@ -2045,22 +2037,14 @@ vm_dispatch: int depth = b; JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame); + assert(depth > 0); + assert(target != NULL); for (int d = 1; d < depth; d++) { fn = JS_VALUE_GET_FUNCTION(target->function); target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame); + assert(target != NULL); } - { - uint64_t tcap = objhdr_cap56(target->header); - if ((unsigned)c >= tcap) { - fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d " - "cur_fn=%s (%s) pc=%u\n", - c, (unsigned long long)tcap, depth, - code->name_cstr ? code->name_cstr : "?", - code->filename_cstr ? code->filename_cstr : "?", pc - 1); - fflush(stderr); - VM_BREAK(); - } - } + assert((unsigned)c < objhdr_cap56(target->header)); target->slots[c] = frame->slots[a]; VM_BREAK(); } diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index bab79945..a684b8a1 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -150,7 +150,6 @@ JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) { /* Concat helper matching MACH_CONCAT semantics exactly. */ JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_assign) { if (self_assign) { - /* Self-assign pattern: slot[a] = slot[a] + slot[c]. */ if (JS_IsPtr(left)) { JSText *s = (JSText *)chase(left); int slen = (int)s->length; @@ -159,21 +158,18 @@ JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_ass if (objhdr_type(s->hdr) == OBJ_TEXT && !(s->hdr & OBJHDR_S_MASK) && slen + rlen <= cap) { - /* In-place append, no allocation. */ for (int i = 0; i < rlen; i++) string_put(s, slen + i, js_string_value_get(right, i)); s->length = slen + rlen; return left; } } - /* Allocate with growth factor, leave unstoned. */ JSValue res = JS_ConcatStringGrow(ctx, left, right); if (JS_IsException(res)) return JS_EXCEPTION; return res; } - /* Different target: exact-fit stoned path. */ JSValue res = JS_ConcatString(ctx, left, right); if (JS_IsException(res)) return JS_EXCEPTION; @@ -468,52 +464,6 @@ JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) { return cell_rt_get_intrinsic_key(ctx, key); } -/* --- Closure access --- - Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE). - The frame's function field links to the JSFunction, whose - u.native.outer_frame points to the enclosing frame. - GC traces outer_frame naturally — no registry needed. */ - -/* Get the outer frame's slots from a frame pointer. - The frame's function must be JS_FUNC_KIND_NATIVE. */ -static JSValue *get_outer_frame_slots(JSValue *fp) { - /* fp points to frame->slots[0]; frame header is before it */ - JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots)); - if (JS_IsNull(frame->function)) - return NULL; - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - if (fn->kind != JS_FUNC_KIND_NATIVE) - return NULL; - JSValue outer = fn->u.cell.outer_frame; - if (JS_IsNull(outer)) - return NULL; - JSFrameRegister *outer_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(outer); - return (JSValue *)outer_frame->slots; -} - -JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth, - int64_t slot) { - (void)ctx; - JSValue *frame = (JSValue *)fp; - for (int64_t d = 0; d < depth; d++) { - frame = get_outer_frame_slots(frame); - if (!frame) - return JS_NULL; - } - return frame[slot]; -} - -void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth, - int64_t slot) { - (void)ctx; - JSValue *frame = (JSValue *)fp; - for (int64_t d = 0; d < depth; d++) { - frame = get_outer_frame_slots(frame); - if (!frame) return; - } - frame[slot] = val; -} - /* --- GC-managed AOT frame stack --- Each native dispatch loop pushes a GC ref so the GC can find and update the current frame pointer when it moves objects. @@ -662,18 +612,6 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp); to call another function (instead of recursing via C stack). ============================================================ */ -/* Poll pause state on taken backward jumps (AOT backedges). - MACH can suspend/resume a register VM frame at pc granularity; native AOT - does not currently have an equivalent resume point, so we acknowledge timer - pauses by clearing pause_flag and continuing the current turn. */ -int cell_rt_check_backedge(JSContext *ctx) { - int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed); - if (pf >= 1) { - atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed); - } - return 0; -} - void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) { NativeRTState *st = native_state(ctx); if (!st) return; @@ -1194,101 +1132,6 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) { return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol)); } -/* --- Extended type checks and text stoning --- */ - -void cell_rt_stone_text(JSValue v) { - stone_mutable_text(v); -} - -int cell_rt_is_blob(JSValue v) { - return mist_is_blob(v); -} - -int cell_rt_is_data(JSValue v) { - return mist_is_gc_object(v) && !mist_is_array(v) - && !mist_is_function(v) && !mist_is_blob(v); -} - -int cell_rt_is_fit(JSValue v) { - if (JS_IsInt(v)) - return 1; - if (JS_IsShortFloat(v)) { - double d = JS_VALUE_GET_FLOAT64(v); - return isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0; - } - return 0; -} - -int cell_rt_is_char(JSValue v) { - if (MIST_IsImmediateASCII(v)) - return MIST_GetImmediateASCIILen(v) == 1; - if (mist_is_text(v)) - return js_string_value_len(v) == 1; - return 0; -} - -int cell_rt_is_digit(JSValue v) { - if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) { - int ch = MIST_GetImmediateASCIIChar(v, 0); - return ch >= '0' && ch <= '9'; - } - if (mist_is_text(v) && js_string_value_len(v) == 1) { - uint32_t ch = js_string_value_get(v, 0); - return ch >= '0' && ch <= '9'; - } - return 0; -} - -int cell_rt_is_letter(JSValue v) { - if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) { - int ch = MIST_GetImmediateASCIIChar(v, 0); - return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); - } - if (mist_is_text(v) && js_string_value_len(v) == 1) { - uint32_t ch = js_string_value_get(v, 0); - return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'); - } - return 0; -} - -int cell_rt_is_lower(JSValue v) { - if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) { - int ch = MIST_GetImmediateASCIIChar(v, 0); - return ch >= 'a' && ch <= 'z'; - } - if (mist_is_text(v) && js_string_value_len(v) == 1) { - uint32_t ch = js_string_value_get(v, 0); - return ch >= 'a' && ch <= 'z'; - } - return 0; -} - -int cell_rt_is_upper(JSValue v) { - if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) { - int ch = MIST_GetImmediateASCIIChar(v, 0); - return ch >= 'A' && ch <= 'Z'; - } - if (mist_is_text(v) && js_string_value_len(v) == 1) { - uint32_t ch = js_string_value_get(v, 0); - return ch >= 'A' && ch <= 'Z'; - } - return 0; -} - -int cell_rt_is_ws(JSValue v) { - if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) { - int ch = MIST_GetImmediateASCIIChar(v, 0); - return ch == ' ' || ch == '\t' || ch == '\n' - || ch == '\r' || ch == '\f' || ch == '\v'; - } - if (mist_is_text(v) && js_string_value_len(v) == 1) { - uint32_t ch = js_string_value_get(v, 0); - return ch == ' ' || ch == '\t' || ch == '\n' - || ch == '\r' || ch == '\f' || ch == '\v'; - } - return 0; -} - int cell_rt_is_actor(JSContext *ctx, JSValue v) { int result = 0; if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym)) @@ -1296,25 +1139,6 @@ int cell_rt_is_actor(JSContext *ctx, JSValue v) { return result; } -/* --- Type check: is_proxy (function with arity 2) --- */ - -int cell_rt_is_proxy(JSContext *ctx, JSValue v) { - (void)ctx; - if (!JS_IsFunction(v)) return 0; - JSFunction *fn = JS_VALUE_GET_FUNCTION(v); - return fn->length == 2; -} - -/* --- Short-circuit and/or (non-allocating) --- */ - -JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) { - return JS_ToBool(ctx, left) ? right : left; -} - -JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) { - return JS_ToBool(ctx, left) ? left : right; -} - /* --- Exception checking --- After potentially-throwing runtime calls, QBE-generated code needs to check for pending exceptions and branch to the disruption handler. */