diff --git a/qbe_emit.cm b/qbe_emit.cm index 8a7f3a7c..7b1b759e 100644 --- a/qbe_emit.cm +++ b/qbe_emit.cm @@ -11,6 +11,7 @@ var emit_helpers = function(qbe) { var h = [] + var lb = "{" // --- Slot access IL fragments --- var sr = function(name, slot) { @@ -36,146 +37,10 @@ ${sw("w", "%fp2", "%dest", result_var)} ret 0` } - // --- Allocating tail without dest write --- - var alloc_tail_nw = function() { - return ` %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) - jnz %fp2, @ok, @exc -@ok - ret %fp2 -@exc - ret 0` - } - // ============================================================ // Category A: Pure QBE helpers (no C calls) // ============================================================ - // move - h[] = `export function $__move_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} -${sw("w", "%fp", "%dest", "%a")} - ret -}` - - // int comparisons - var int_ops = [ - ["eq_int", "ceqw"], ["ne_int", "cnew"], ["lt_int", "csltw"], - ["le_int", "cslew"], ["gt_int", "csgtw"], ["ge_int", "csgew"] - ] - var i = 0 - while (i < length(int_ops)) { - h[] = `export function $__${int_ops[i][0]}_ss(l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %ia =l sar %a, 1 - %ib =l sar %b, 1 - %iaw =w copy %ia - %ibw =w copy %ib - %cr =w ${int_ops[i][1]} %iaw, %ibw - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - i = i + 1 - } - - // bool comparisons - h[] = `export function $__eq_bool_ss(l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %cr =w ceql %a, %b - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - h[] = `export function $__ne_bool_ss(l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %cr =w cnel %a, %b - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - // is_identical (same as eq_bool) - h[] = `export function $__is_identical_ss(l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %cr =w ceql %a, %b - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - // is_int: (val & 1) == 0 - h[] = `export function $__is_int_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %t =l and %a, 1 - %cr =w ceql %t, 0 - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - // is_null: (val & 31) == 7 - h[] = `export function $__is_null_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %t =l and %a, 31 - %cr =w ceql %t, 7 - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - // is_bool: (val & 31) == 3 - h[] = `export function $__is_bool_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %t =l and %a, 31 - %cr =w ceql %t, 3 - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - - // is_num: (val & 1 == 0) || (val & 7 == 5) - h[] = `export function $__is_num_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %t1 =l and %a, 1 - %ii =w ceql %t1, 0 - %t2 =l and %a, 7 - %fi =w ceql %t2, 5 - %cr =w or %ii, %fi - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - // ============================================================ // Category B: Non-allocating C call helpers // ============================================================ @@ -185,16 +50,16 @@ ${sw("w", "%fp", "%dest", "%r")} ["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 - i = 0 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) { + 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) @@ -205,7 +70,7 @@ ${sw("w", "%fp", "%dest", "%r")} ret }` } else { - h[] = `export function $__${tc_name}_ss(l %fp, l %dest, l %src) { + h[] = `export function $__${tc_name}_ss(l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %cr =w call $${tc_cfn}(l %a) @@ -219,46 +84,8 @@ ${sw("w", "%fp", "%dest", "%r")} i = i + 1 } - // is_text: immediate text OR ptr->header type check (OBJ_TEXT=2), chase forwards - h[] = `export function $__is_text_ss(l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %imm =l and %a, 31 - %is_imm =w ceql %imm, 11 - jnz %is_imm, @yes, @chk_ptr -@chk_ptr - %ptag =l and %a, 7 - %is_ptr =w ceql %ptag, 1 - jnz %is_ptr, @ptr, @no -@ptr - %ptr =l and %a, -8 - %hdr =l loadl %ptr -@chase - %ht =l and %hdr, 7 - %is_fwd =w ceql %ht, 7 - jnz %is_fwd, @follow, @chk -@follow - %ptr =l shr %hdr, 3 - %hdr =l loadl %ptr - jmp @chase -@chk - %cr =w ceql %ht, 2 - jmp @pack -@yes - %cr =w copy 1 - jmp @pack -@no - %cr =w copy 0 -@pack - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - // is_record: pointer + header type check (OBJ_RECORD=3), chase forwards - h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) { + h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %ptag =l and %a, 7 @@ -289,7 +116,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // is_array: inline pointer+header check (OBJ_ARRAY=0), chase forwards - h[] = `export function $__is_array_ss(l %fp, l %dest, l %src) { + h[] = `export function $__is_array_ss(l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %ptag =l and %a, 7 @@ -320,7 +147,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // is_func: inline pointer+header check (OBJ_FUNCTION=4), chase forwards - h[] = `export function $__is_func_ss(l %fp, l %dest, l %src) { + h[] = `export function $__is_func_ss(l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %ptag =l and %a, 7 @@ -350,124 +177,11 @@ ${sw("w", "%fp", "%dest", "%r")} ret }` - // Float comparisons: decode short-float/int inline, then compare in QBE. - var fc_ops = [ - ["eq_float", "ceqd"], ["ne_float", "cned"], ["lt_float", "cltd"], - ["le_float", "cled"], ["gt_float", "cgtd"], ["ge_float", "cged"] - ] - i = 0 - while (i < length(fc_ops)) { - h[] = `export function $__${fc_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %a_tag =l and %a, 1 - %a_is_int =w ceql %a_tag, 0 - jnz %a_is_int, @a_int, @a_float -@a_int - %a_isl =l sar %a, 1 - %a_iw =w copy %a_isl - %ad =d swtof %a_iw - jmp @a_done -@a_float - %a_sexp =l shr %a, 55 - %a_sexp =l and %a_sexp, 255 - %a_is_zero =w ceql %a_sexp, 0 - jnz %a_is_zero, @a_zero, @a_decode -@a_zero - %ad =d copy d_0.0 - jmp @a_done -@a_decode - %a_sign =l shr %a, 63 - %a_mant =l shr %a, 3 - %a_mant =l and %a_mant, 4503599627370495 - %a_dexp =l sub %a_sexp, 127 - %a_dexp =l add %a_dexp, 1023 - %a_s63 =l shl %a_sign, 63 - %a_e52 =l shl %a_dexp, 52 - %a_bits =l or %a_s63, %a_e52 - %a_bits =l or %a_bits, %a_mant - %ad =d cast %a_bits -@a_done - %b_tag =l and %b, 1 - %b_is_int =w ceql %b_tag, 0 - jnz %b_is_int, @b_int, @b_float -@b_int - %b_isl =l sar %b, 1 - %b_iw =w copy %b_isl - %bd =d swtof %b_iw - jmp @b_done -@b_float - %b_sexp =l shr %b, 55 - %b_sexp =l and %b_sexp, 255 - %b_is_zero =w ceql %b_sexp, 0 - jnz %b_is_zero, @b_zero, @b_decode -@b_zero - %bd =d copy d_0.0 - jmp @b_done -@b_decode - %b_sign =l shr %b, 63 - %b_mant =l shr %b, 3 - %b_mant =l and %b_mant, 4503599627370495 - %b_dexp =l sub %b_sexp, 127 - %b_dexp =l add %b_dexp, 1023 - %b_s63 =l shl %b_sign, 63 - %b_e52 =l shl %b_dexp, 52 - %b_bits =l or %b_s63, %b_e52 - %b_bits =l or %b_bits, %b_mant - %bd =d cast %b_bits -@b_done - %cr =w ${fc_ops[i][1]} %ad, %bd - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - i = i + 1 - } - - // Text comparisons: eq/ne via js_string_compare_value, others via cell_rt_* - var txcmp_sv = [ - ["eq_text", "ceqw", 1], ["ne_text", "cnew", 1] - ] - i = 0 - while (i < length(txcmp_sv)) { - h[] = `export function $__${txcmp_sv[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %scmp =w call $js_string_compare_value(l %ctx, l %a, l %b, w ${txcmp_sv[i][2]}) - %cr =w ${txcmp_sv[i][1]} %scmp, 0 - %crext =l extuw %cr - %sh =l shl %crext, 5 - %r =l or %sh, 3 -${sw("w", "%fp", "%dest", "%r")} - ret -}` - i = i + 1 - } - - // lt/le/gt/ge_text via cell_rt_* (return tagged JSValue directly) - var txcmp_rt = ["lt_text", "gt_text", "le_text", "ge_text"] - i = 0 - while (i < length(txcmp_rt)) { - h[] = `export function $__${txcmp_rt[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %r =l call $cell_rt_${txcmp_rt[i]}(l %ctx, l %a, l %b) -${sw("w", "%fp", "%dest", "%r")} - ret -}` - i = i + 1 - } - // eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value) var tol_ops = ["eq_tol", "ne_tol"] 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) { + h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} @@ -480,7 +194,7 @@ ${sw("w", "%fp", "%dest", "%r")} } // not: inline truthiness (no JS_ToBool call) - h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) { + h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %t5 =l and %a, 31 @@ -553,7 +267,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // and, or (return tagged JSValue directly) - h[] = `export function $__and_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function $__and_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} @@ -562,7 +276,7 @@ ${sw("w", "%fp", "%dest", "%r")} ret }` - h[] = `export function $__or_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function $__or_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} @@ -571,178 +285,40 @@ ${sw("w", "%fp", "%dest", "%r")} ret }` - // Bitwise unary: bnot - h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) { + // 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 ${sr("a", "%src")} - %tag =l and %a, 1 - %is_int =w ceql %tag, 0 - jnz %is_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %aiw =w copy %ai - %rw =w xor %aiw, -1 - %rl =l extsw %rw - %r =l shl %rl, 1 + %r =l call $qbe_bnot(l %ctx, l %a) ${sw("w", "%fp", "%dest", "%r")} ret -@bad - call $cell_rt_disrupt(l %ctx) - ret }` - // Bitwise binary ops (int-only; type checks should be inserted upstream) - h[] = `export function $__band_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function $__band_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %rw =w and %aiw, %biw - %rl =l extsw %rw - %r =l shl %rl, 1 + %r =l call $qbe_bitwise_and(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret -@bad - call $cell_rt_disrupt(l %ctx) - ret }` - h[] = `export function $__bor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function $__bor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %rw =w or %aiw, %biw - %rl =l extsw %rw - %r =l shl %rl, 1 + %r =l call $qbe_bitwise_or(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret -@bad - call $cell_rt_disrupt(l %ctx) - ret }` - h[] = `export function $__bxor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function $__bxor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %rw =w xor %aiw, %biw - %rl =l extsw %rw - %r =l shl %rl, 1 + %r =l call $qbe_bitwise_xor(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret -@bad - call $cell_rt_disrupt(l %ctx) - ret -}` - - h[] = `export function $__bshl_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_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %sh =w and %biw, 31 - %rw =w shl %aiw, %sh - %rl =l extsw %rw - %r =l shl %rl, 1 -${sw("w", "%fp", "%dest", "%r")} - ret -@bad - call $cell_rt_disrupt(l %ctx) - ret -}` - - h[] = `export function $__bshr_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_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %sh =w and %biw, 31 - %rw =w sar %aiw, %sh - %rl =l extsw %rw - %r =l shl %rl, 1 -${sw("w", "%fp", "%dest", "%r")} - ret -@bad - call $cell_rt_disrupt(l %ctx) - ret -}` - - h[] = `export function $__bushr_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_int =w ceql %a_tag, 0 - %b_int =w ceql %b_tag, 0 - %both_int =w and %a_int, %b_int - jnz %both_int, @ok, @bad -@ok - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %aiw =w copy %ai - %biw =w copy %bi - %sh =w and %biw, 31 - %rw =w shr %aiw, %sh - %rl =l extsw %rw - %r =l shl %rl, 1 -${sw("w", "%fp", "%dest", "%r")} - ret -@bad - call $cell_rt_disrupt(l %ctx) - ret }` // ============================================================ @@ -750,16 +326,26 @@ ${sw("w", "%fp", "%dest", "%r")} // ============================================================ // concat allocates; keep refresh path - h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %r =l call $JS_ConcatString(l %ctx, l %a, l %b) -${alloc_tail("%r")} + %same =w ceql %dest, %s1 + %r =l call $cell_rt_concat(l %ctx, l %a, l %b, w %same) + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @refresh +@refresh + %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) + jnz %fp2, @ok, @exc +@ok +${sw("w", "%fp2", "%dest", "%r")} + ret %fp2 +@exc + ret 0 }` // access_lit(ctx, fp, dest, lit_idx) - h[] = `export function l $__access_lit_ss(l %ctx, l %fp, l %dest, l %lit_idx) { + h[] = `export function l $__access_lit_ss(l %ctx, l %fp, l %dest, l %lit_idx) ${lb} @entry %r =l call $cell_rt_access_lit(l %ctx, l %lit_idx) %is_exc =w ceql %r, 15 @@ -772,7 +358,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // Property access: load_field(ctx, fp, dest, obj_slot, lit_idx) - h[] = `export function l $__load_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) { + h[] = `export function l $__load_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) ${lb} @entry ${sr("a", "%obj_slot")} %r =l call $cell_rt_load_field_lit(l %ctx, l %a, l %lit_idx) @@ -786,7 +372,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // load_dynamic(ctx, fp, dest, obj_slot, key_slot) - h[] = `export function l $__load_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) { + h[] = `export function l $__load_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} @@ -845,7 +431,7 @@ ${sw("w", "%fp", "%dest", "%r")} }` // load_index(ctx, fp, dest, arr_slot, idx_slot) - h[] = `export function l $__load_index_ss(l %ctx, l %fp, l %dest, l %arr_slot, l %idx_slot) { + h[] = `export function l $__load_index_ss(l %ctx, l %fp, l %dest, l %arr_slot, l %idx_slot) ${lb} @entry ${sr("a", "%arr_slot")} ${sr("b", "%idx_slot")} @@ -895,7 +481,7 @@ ${sw("w", "%fp", "%dest", text(qbe.js_null))} }` // store_field(ctx, fp, obj_slot, val_slot, lit_idx) — no dest write - h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %lit_idx) { + h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %lit_idx) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} @@ -908,7 +494,7 @@ ${sr("b", "%val_slot")} }` // store_dynamic(ctx, fp, obj_slot, val_slot, key_slot) — no dest write - h[] = `export function l $__store_dynamic_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot) { + h[] = `export function l $__store_dynamic_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} @@ -949,7 +535,7 @@ ${sr("c", "%key_slot")} }` // store_index(ctx, fp, obj_slot, val_slot, idx_slot) — no dest write - h[] = `export function l $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %idx_slot) { + h[] = `export function l $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %idx_slot) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} @@ -1104,7 +690,7 @@ ${sr("gc", "%idx_slot")} }` // frame(ctx, fp, dest, fn_slot, nargs) - h[] = `export function l $__frame_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) { + h[] = `export function l $__frame_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) ${lb} @entry ${sr("a", "%fn_slot")} %r =l call $cell_rt_frame(l %ctx, l %a, l %nargs) @@ -1112,60 +698,36 @@ ${alloc_tail("%r")} }` // goframe(ctx, fp, dest, fn_slot, nargs) - h[] = `export function l $__goframe_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) { + h[] = `export function l $__goframe_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) ${lb} @entry ${sr("a", "%fn_slot")} %r =l call $cell_rt_goframe(l %ctx, l %a, l %nargs) ${alloc_tail("%r")} }` - // invoke(ctx, fp, frame_slot, result_slot) — two checks: exc + refresh - h[] = `export function l $__invoke_ss(l %ctx, l %fp, l %frame_slot, l %result_slot) { -@entry -${sr("a", "%frame_slot")} - %r =l call $cell_rt_invoke(l %ctx, l %a) - %is_exc =w ceql %r, 15 - jnz %is_exc, @exc, @ok1 -@ok1 - %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) - jnz %fp2, @ok2, @exc -@ok2 -${sw("w", "%fp2", "%result_slot", "%r")} - ret %fp2 -@exc - ret 0 -}` - // function(ctx, fp, dest, fn_idx, arity, nr_slots) - h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity, l %nr_slots) { + h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity, l %nr_slots) ${lb} @entry %r =l call $cell_rt_make_function(l %ctx, l %fn_idx, l %fp, l %arity, l %nr_slots) ${alloc_tail("%r")} }` // new_record(ctx, fp, dest) - h[] = `export function l $__new_record_ss(l %ctx, l %fp, l %dest) { + h[] = `export function l $__new_record_ss(l %ctx, l %fp, l %dest) ${lb} @entry %r =l call $JS_NewObject(l %ctx) ${alloc_tail("%r")} }` // new_array(ctx, fp, dest) - h[] = `export function l $__new_array_ss(l %ctx, l %fp, l %dest) { + h[] = `export function l $__new_array_ss(l %ctx, l %fp, l %dest) ${lb} @entry %r =l call $JS_NewArray(l %ctx) ${alloc_tail("%r")} -}` - - // new_string(ctx, fp, dest, str_ptr) - h[] = `export function l $__new_string_ss(l %ctx, l %fp, l %dest, l %str_ptr) { -@entry - %r =l call $qbe_new_string(l %ctx, l %str_ptr) -${alloc_tail("%r")} }` // new_float64(ctx, fp, dest, val) — val is a double - h[] = `export function l $__new_float64_ss(l %ctx, l %fp, l %dest, d %val) { + h[] = `export function l $__new_float64_ss(l %ctx, l %fp, l %dest, d %val) ${lb} @entry %r =l call $qbe_new_float64(l %ctx, d %val) ${sw("w", "%fp", "%dest", "%r")} @@ -1173,14 +735,14 @@ ${sw("w", "%fp", "%dest", "%r")} }` // get_intrinsic(ctx, fp, dest, lit_idx) - h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %lit_idx) { + h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %lit_idx) ${lb} @entry %r =l call $cell_rt_get_intrinsic_lit(l %ctx, l %lit_idx) ${alloc_tail("%r")} }` // push(ctx, fp, arr_slot, val_slot) — write back arr in case GC moved it - h[] = `export function l $__push_ss(l %ctx, l %fp, l %arr_slot, l %val_slot) { + h[] = `export function l $__push_ss(l %ctx, l %fp, l %arr_slot, l %val_slot) ${lb} @entry ${sr("a", "%arr_slot")} ${sr("b", "%val_slot")} @@ -1316,7 +878,7 @@ ${sr("gb", "%val_slot")} }` // pop(ctx, fp, dest, arr_slot) - h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) { + h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) ${lb} @entry ${sr("a", "%arr_slot")} %ptag =l and %a, 7 @@ -1367,7 +929,7 @@ ${sw("w", "%fp", "%dest", text(qbe.js_null))} }` // length(ctx, fp, dest, src) - h[] = `export function l $__length_ss(l %ctx, l %fp, l %dest, l %src) { + h[] = `export function l $__length_ss(l %ctx, l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %r =l call $JS_CellLength(l %ctx, l %a) @@ -1375,7 +937,7 @@ ${alloc_tail("%r")} }` // delete_field(ctx, fp, dest, obj_slot, lit_idx) - h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) { + h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) ${lb} @entry ${sr("a", "%obj_slot")} %r =l call $cell_rt_delete_lit(l %ctx, l %a, l %lit_idx) @@ -1383,7 +945,7 @@ ${alloc_tail("%r")} }` // delete_dynamic(ctx, fp, dest, obj_slot, key_slot) - h[] = `export function l $__delete_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) { + h[] = `export function l $__delete_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} @@ -1392,7 +954,7 @@ ${alloc_tail("%r")} }` // in(ctx, fp, dest, key_slot, obj_slot) - h[] = `export function l $__in_ss(l %ctx, l %fp, l %dest, l %key_slot, l %obj_slot) { + h[] = `export function l $__in_ss(l %ctx, l %fp, l %dest, l %key_slot, l %obj_slot) ${lb} @entry ${sr("a", "%key_slot")} ${sr("b", "%obj_slot")} @@ -1401,7 +963,7 @@ ${alloc_tail("%r")} }` // regexp(ctx, fp, dest, pat_ptr, flg_ptr) - h[] = `export function l $__regexp_ss(l %ctx, l %fp, l %dest, l %pat, l %flg) { + h[] = `export function l $__regexp_ss(l %ctx, l %fp, l %dest, l %pat, l %flg) ${lb} @entry %r =l call $cell_rt_regexp(l %ctx, l %pat, l %flg) ${alloc_tail("%r")} @@ -1479,8 +1041,6 @@ var qbe_emit = function(ir, qbe, export_name) { var compile_fn = function(fn, fn_idx, is_main) { var instrs = fn.instructions - var nr_slots = fn.nr_slots - var nr_args = fn.nr_args var disruption_pc = fn.disruption_pc != null ? fn.disruption_pc : 0 var has_handler = disruption_pc > 0 var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx) @@ -1496,22 +1056,13 @@ var qbe_emit = function(ir, qbe, export_name) { var pn = null var sl = null var lbl = null - var fop_id = 0 - var nr_elems = 0 - var ei = 0 - var elem_slot = 0 var v = null - var rv = null var lhs = null var rhs = null - var obj = null - var chk = null var pat_label = null var flg_label = null var in_handler = false - var tol = null var fn_arity = 0 - var arity_tmp = null var fn_nr_slots = 0 var invoke_count = 0 var si = 0 @@ -1522,11 +1073,7 @@ var qbe_emit = function(ir, qbe, export_name) { var has_invokes = false var seg_counter = 0 var ri = 0 - var seg_num = 0 var resume_val = 0 - // Native calls should mirror MACH semantics: function calls are mediated - // by the frame dispatcher, not recursive C calls. - var use_invoke_trampoline = true var j_lbl = null var j_idx = null var jt_lbl = null @@ -1535,9 +1082,6 @@ var qbe_emit = function(ir, qbe, export_name) { 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 @@ -1549,14 +1093,11 @@ var qbe_emit = function(ir, qbe, export_name) { var peek3 = null var peek4 = null var peek5 = null - var floor_frame_slot = 0 - var floor_this_slot = 0 - var floor_arg_slot = 0 - var floor_dest_slot = 0 var text_frame_slot = 0 var text_this_slot = 0 var text_arg_slot = 0 var text_dest_slot = 0 + var cmp_id = 0 // Pre-scan: count invoke/tail_invoke points to assign segment numbers. // Must skip dead code (instructions after terminators) the same way @@ -1579,32 +1120,6 @@ var qbe_emit = function(ir, qbe, export_name) { if (!is_array(scan)) continue scan_op = scan[0] - // Keep invoke segment counting consistent with main-loop peephole: - // inline floor intrinsic call sequence does not emit an invoke. - if (false && scan_op == "access" && is_object(scan[2]) && scan[2].make == "intrinsic" && scan[2].name == "floor") { - if (si + 4 < length(instrs)) { - peek1 = instrs[si] - peek2 = instrs[si + 1] - peek3 = instrs[si + 2] - peek4 = instrs[si + 3] - peek5 = instrs[si + 4] - if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == scan[1] && peek1[3] == 1 && - is_array(peek2) && peek2[0] == "null" && - is_array(peek3) && peek3[0] == "setarg" && - is_array(peek4) && peek4[0] == "setarg" && - is_array(peek5) && peek5[0] == "invoke") { - floor_frame_slot = peek1[1] - floor_this_slot = peek2[1] - if (peek3[1] == floor_frame_slot && peek3[2] == 0 && peek3[3] == floor_this_slot && - peek4[1] == floor_frame_slot && peek4[2] == 1 && - peek5[1] == floor_frame_slot && peek5[2] == floor_this_slot) { - si = si + 5 - continue - } - } - } - } - // Keep invoke segment counting consistent with main-loop peephole: // inline text intrinsic call sequence does not emit an invoke. if (scan_op == "access" && is_object(scan[2]) && scan[2].make == "intrinsic" && scan[2].name == "text") { @@ -1631,7 +1146,7 @@ var qbe_emit = function(ir, qbe, export_name) { } } - if (use_invoke_trampoline && (scan_op == "invoke" || scan_op == "tail_invoke")) { + if (scan_op == "invoke" || scan_op == "tail_invoke") { invoke_count = invoke_count + 1 } // Track terminators — same set as in the main loop @@ -1639,7 +1154,7 @@ var qbe_emit = function(ir, qbe, export_name) { scan_dead = true } } - has_invokes = use_invoke_trampoline && invoke_count > 0 + has_invokes = invoke_count > 0 // Function signature: (ctx, frame_ptr) → JSValue emit(`export function l $${name}(l %ctx, l %fp) {`) @@ -1885,6 +1400,23 @@ var qbe_emit = function(ir, qbe, export_name) { return `%${np}_d` } + // Convert a known numeric JSValue (int or short-float) to int32 in w. + var emit_num_to_int32_w = function(val) { + var np = fresh() + emit(` %${np}_tag =l and ${val}, 1`) + emit(` %${np}_is_int =w ceql %${np}_tag, 0`) + emit(` jnz %${np}_is_int, @${np}_int, @${np}_float`) + emit(`@${np}_int`) + emit(` %${np}_isl =l sar ${val}, 1`) + emit(` %${np}_iw =w copy %${np}_isl`) + emit(` jmp @${np}_done`) + emit(`@${np}_float`) + emit(` %${np}_fd =d copy ${emit_num_to_double(val)}`) + emit(` %${np}_iw =w dtosi %${np}_fd`) + emit(`@${np}_done`) + return `%${np}_iw` + } + // Walk instructions var last_was_term = false i = 0 @@ -1927,47 +1459,6 @@ var qbe_emit = function(ir, qbe, export_name) { a3 = instr[3] last_was_term = false - // Peephole: inline `floor(x)` intrinsic call sequence - // access floor; frame; null this; setarg 0 this; setarg 1 x; invoke - if (false && op == "access" && is_object(a2) && a2.make == "intrinsic" && a2.name == "floor") { - if (instr_idx + 5 < length(instrs)) { - peek1 = instrs[instr_idx + 1] - peek2 = instrs[instr_idx + 2] - peek3 = instrs[instr_idx + 3] - peek4 = instrs[instr_idx + 4] - peek5 = instrs[instr_idx + 5] - if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == a1 && peek1[3] == 1 && - is_array(peek2) && peek2[0] == "null" && - is_array(peek3) && peek3[0] == "setarg" && - is_array(peek4) && peek4[0] == "setarg" && - is_array(peek5) && peek5[0] == "invoke") { - floor_frame_slot = peek1[1] - floor_this_slot = peek2[1] - if (peek3[1] == floor_frame_slot && peek3[2] == 0 && peek3[3] == floor_this_slot && - peek4[1] == floor_frame_slot && peek4[2] == 1 && - peek5[1] == floor_frame_slot && peek5[2] == floor_this_slot) { - floor_arg_slot = peek4[3] - floor_dest_slot = peek5[2] - v = s_read(floor_arg_slot) - p = fresh() - emit(` %${p}_is_num =w copy ${emit_is_num_w(v)}`) - emit(` jnz %${p}_is_num, @${p}_ok, @${p}_bad`) - emit(`@${p}_bad`) - s_write(floor_dest_slot, text(qbe.js_null)) - emit(` jmp @${p}_done`) - emit(`@${p}_ok`) - lhs_d = emit_num_to_double(v) - emit(` %${p}_fd =d call $floor(d ${lhs_d})`) - emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_fd)`) - s_write(floor_dest_slot, `%${p}_r`) - emit(`@${p}_done`) - i = instr_idx + 6 - continue - } - } - } - } - // Peephole: inline `text(x)` intrinsic call sequence // access text; frame; null this; setarg 0 this; setarg 1 x; invoke if (op == "access" && is_object(a2) && a2.make == "intrinsic" && a2.name == "text") { @@ -2097,34 +1588,11 @@ var qbe_emit = function(ir, qbe, export_name) { s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) - emit(` # mixed add: numeric add, text concat, else disrupt`) - emit(` %${p}_a_num =w copy ${emit_is_num_w(lhs)}`) - emit(` %${p}_b_num =w copy ${emit_is_num_w(rhs)}`) - emit(` %${p}_both_num =w and %${p}_a_num, %${p}_b_num`) - emit(` jnz %${p}_both_num, @${p}_num_add, @${p}_chk_text`) - emit(`@${p}_num_add`) lhs_d = emit_num_to_double(lhs) rhs_d = emit_num_to_double(rhs) emit(` %${p}_rd =d add ${lhs_d}, ${rhs_d}`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) - emit(` jmp @${p}_done`) - emit(`@${p}_chk_text`) - emit(` %${p}_a_txt =w copy ${emit_is_text_w(lhs)}`) - emit(` %${p}_b_txt =w copy ${emit_is_text_w(rhs)}`) - emit(` %${p}_both_txt =w and %${p}_a_txt, %${p}_b_txt`) - emit(` jnz %${p}_both_txt, @${p}_txt_add, @${p}_bad`) - emit(`@${p}_txt_add`) - emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - emit_exc_check() - emit(` jmp @${p}_done`) - emit(`@${p}_bad`) - emit(` call $cell_rt_disrupt(l %ctx)`) - if (has_handler && !in_handler) { - emit(` jmp @disruption_handler`) - } else { - emit(` ret 15`) - } emit(`@${p}_done`) continue } @@ -2424,6 +1892,11 @@ var qbe_emit = function(ir, qbe, export_name) { emit_exc_check() continue } + if (op == "stone_text") { + v = s_read(a1) + emit(` call $cell_rt_stone_text(l ${v})`) + continue + } // --- Type checks — use qbe.cm macros (no GC, no refresh) --- @@ -2489,146 +1962,63 @@ var qbe_emit = function(ir, qbe, export_name) { emit(` call $__is_proxy_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } + if (op == "is_blob" || op == "is_data" || op == "is_fit" || + op == "is_char" || 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})`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_true") { + v = s_read(a2) + p = fresh() + emit(` %${p}_w =w ceql ${v}, ${text(qbe.js_true)}`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_false") { + v = s_read(a2) + p = fresh() + emit(` %${p}_w =w ceql ${v}, ${text(qbe.js_false)}`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } + if (op == "is_actor") { + v = s_read(a2) + p = fresh() + emit(` %${p}_w =w call $cell_rt_is_actor(l %ctx, l ${v})`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) + continue + } // --- Comparisons (int path, no GC) --- - if (op == "eq_int") { + if (op == "eq" || op == "ne" || op == "lt" || op == "le" || op == "gt" || op == "ge") { 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") { - 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") { - 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") { - 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") { - 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") { - 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`) + cmp_id = 0 + if (op == "eq") cmp_id = 0 + else if (op == "ne") cmp_id = 1 + else if (op == "lt") cmp_id = 2 + else if (op == "le") cmp_id = 3 + else if (op == "gt") cmp_id = 4 + else cmp_id = 5 + emit(` %${p}_r =l call $cell_rt_cmp(l %ctx, w ${text(cmp_id)}, l ${lhs}, l ${rhs})`) + emit(` %${p}_exc =w ceql %${p}_r, ${text(qbe.js_exception)}`) + if (has_handler && !in_handler) { + emit(` jnz %${p}_exc, @disruption_handler, @${p}_ok`) + } else { + needs_exc_ret = true + emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`) + } + emit(`@${p}_ok`) s_write(a1, `%${p}_r`) continue } - // --- Comparisons (float/text/bool) --- - - if (op == "eq_float") { - emit(` call $__eq_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "ne_float") { - emit(` call $__ne_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "lt_float") { - emit(` call $__lt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "le_float") { - emit(` call $__le_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "gt_float") { - emit(` call $__gt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "ge_float") { - emit(` call $__ge_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "eq_text") { - emit(` call $__eq_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "ne_text") { - emit(` call $__ne_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") { - emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "eq_bool") { - emit(` call $__eq_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } - if (op == "ne_bool") { - emit(` call $__ne_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - continue - } if (op == "eq_tol" || op == "ne_tol") { a4 = instr[4] emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)}, l ${text(a4)})`) @@ -2669,15 +2059,84 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "shl") { - emit(` call $__bshl_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + emit(` %${p}_a_num =w copy ${emit_is_num_w(lhs)}`) + emit(` %${p}_b_num =w copy ${emit_is_num_w(rhs)}`) + emit(` %${p}_both_num =w and %${p}_a_num, %${p}_b_num`) + emit(` jnz %${p}_both_num, @${p}_ok, @${p}_bad`) + emit(`@${p}_ok`) + emit(` %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`) + emit(` %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`) + emit(` %${p}_sh =w and %${p}_biw, 31`) + emit(` %${p}_rw =w shl %${p}_aiw, %${p}_sh`) + emit(` %${p}_rl =l extsw %${p}_rw`) + emit(` %${p}_r =l shl %${p}_rl, 1`) + s_write(a1, `%${p}_r`) + emit(` jmp @${p}_done`) + emit(`@${p}_bad`) + emit(` call $cell_rt_disrupt(l %ctx)`) + if (has_handler && !in_handler) { + emit(` jmp @disruption_handler`) + } else { + emit(` ret 15`) + } + emit(`@${p}_done`) continue } if (op == "shr") { - emit(` call $__bshr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + emit(` %${p}_a_num =w copy ${emit_is_num_w(lhs)}`) + emit(` %${p}_b_num =w copy ${emit_is_num_w(rhs)}`) + emit(` %${p}_both_num =w and %${p}_a_num, %${p}_b_num`) + emit(` jnz %${p}_both_num, @${p}_ok, @${p}_bad`) + emit(`@${p}_ok`) + emit(` %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`) + emit(` %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`) + emit(` %${p}_sh =w and %${p}_biw, 31`) + emit(` %${p}_rw =w sar %${p}_aiw, %${p}_sh`) + emit(` %${p}_rl =l extsw %${p}_rw`) + emit(` %${p}_r =l shl %${p}_rl, 1`) + s_write(a1, `%${p}_r`) + emit(` jmp @${p}_done`) + emit(`@${p}_bad`) + emit(` call $cell_rt_disrupt(l %ctx)`) + if (has_handler && !in_handler) { + emit(` jmp @disruption_handler`) + } else { + emit(` ret 15`) + } + emit(`@${p}_done`) continue } if (op == "ushr") { - emit(` call $__bushr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + emit(` %${p}_a_num =w copy ${emit_is_num_w(lhs)}`) + emit(` %${p}_b_num =w copy ${emit_is_num_w(rhs)}`) + emit(` %${p}_both_num =w and %${p}_a_num, %${p}_b_num`) + emit(` jnz %${p}_both_num, @${p}_ok, @${p}_bad`) + emit(`@${p}_ok`) + emit(` %${p}_aiw =w copy ${emit_num_to_int32_w(lhs)}`) + emit(` %${p}_biw =w copy ${emit_num_to_int32_w(rhs)}`) + emit(` %${p}_sh =w and %${p}_biw, 31`) + emit(` %${p}_rw =w shr %${p}_aiw, %${p}_sh`) + emit(` %${p}_rl =l extsw %${p}_rw`) + emit(` %${p}_r =l shl %${p}_rl, 1`) + s_write(a1, `%${p}_r`) + emit(` jmp @${p}_done`) + emit(`@${p}_bad`) + emit(` call $cell_rt_disrupt(l %ctx)`) + if (has_handler && !in_handler) { + emit(` jmp @disruption_handler`) + } else { + emit(` ret 15`) + } + emit(`@${p}_done`) continue } @@ -2840,23 +2299,6 @@ var qbe_emit = function(ir, qbe, export_name) { 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)}`) - 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() @@ -2894,32 +2336,26 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "invoke" || op == "tail_invoke") { - if (use_invoke_trampoline) { - // Signal dispatcher to call frame in slot a1 and resume at @_segN. - seg_counter = seg_counter + 1 - resume_val = seg_counter * 65536 + a2 - p = fresh() - emit(` %${p}_addrp =l sub %fp, 8`) - // frame->address holds JS_NewInt32((seg << 16) | ret_slot), tagged. - emit(` storel ${text(resume_val * 2)}, %${p}_addrp`) - emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`) - emit(` ret ${text(qbe.js_null)}`) - emit(`@_seg${text(seg_counter)}`) - // Dispatcher writes JS_EXCEPTION into ret slot on error; branch here. - v = s_read(a2) - emit(` %${p}_exc =w ceql ${v}, ${text(qbe.js_exception)}`) - if (has_handler && !in_handler) { - emit(` jnz %${p}_exc, @disruption_handler, @${p}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`) - } - emit(`@${p}_ok`) + // Signal dispatcher to call frame in slot a1 and resume at @_segN. + seg_counter = seg_counter + 1 + resume_val = seg_counter * 65536 + a2 + p = fresh() + emit(` %${p}_addrp =l sub %fp, 8`) + // frame->address holds JS_NewInt32((seg << 16) | ret_slot), tagged. + emit(` storel ${text(resume_val * 2)}, %${p}_addrp`) + emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`) + emit(` ret ${text(qbe.js_null)}`) + emit(`@_seg${text(seg_counter)}`) + // Dispatcher writes JS_EXCEPTION into ret slot on error; branch here. + v = s_read(a2) + emit(` %${p}_exc =w ceql ${v}, ${text(qbe.js_exception)}`) + if (has_handler && !in_handler) { + emit(` jnz %${p}_exc, @disruption_handler, @${p}_ok`) } else { - // Direct helper invoke path (disabled by default). - emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) - emit_exc_check() + needs_exc_ret = true + emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`) } + emit(`@${p}_ok`) continue } if (op == "goframe") { @@ -2928,30 +2364,9 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "goinvoke") { - if (use_invoke_trampoline) { - // Tail call via dispatcher: no resume in this frame. - emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`) - emit(` ret ${text(qbe.js_null)}`) - } else { - // Direct helper goinvoke path (disabled by default). - v = s_read(a1) - p = fresh() - emit(` %${p}_r =l call $cell_rt_goinvoke(l %ctx, l ${v})`) - emit(` %${p}_exc =w ceql %${p}_r, ${text(qbe.js_exception)}`) - if (has_handler && !in_handler) { - emit(` jnz %${p}_exc, @${p}_exc, @${p}_ok`) - emit(`@${p}_exc`) - emit(` %fp =l call $cell_rt_refresh_fp(l %ctx)`) - emit(` jmp @disruption_handler`) - emit(`@${p}_ok`) - emit(` ret %${p}_r`) - } else { - needs_exc_ret = true - emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`) - emit(`@${p}_ok`) - emit(` ret %${p}_r`) - } - } + // Tail call via dispatcher: no resume in this frame. + emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`) + emit(` ret ${text(qbe.js_null)}`) last_was_term = true continue } @@ -3104,8 +2519,8 @@ var qbe_emit = function(ir, qbe, export_name) { // Export nr_slots for main function so the module loader can use right-sized frames var main_name = export_name ? sanitize(export_name) : "cell_main" - push(data_out, `export data $${main_name}_nr_slots = { w ${text(ir.main.nr_slots)} }`) - push(data_out, `export data $cell_lit_count = { w ${text(length(str_entries))} }`) + push(data_out, "export data $" + main_name + "_nr_slots = { w " + text(ir.main.nr_slots) + " }") + push(data_out, "export data $cell_lit_count = { w " + text(length(str_entries)) + " }") if (length(str_entries) > 0) { lit_data = [] si = 0 @@ -3113,7 +2528,7 @@ var qbe_emit = function(ir, qbe, export_name) { push(lit_data, `l ${str_entries[si].label}`) si = si + 1 } - push(data_out, `export data $cell_lit_table = { ${text(lit_data, ", ")} }`) + push(data_out, "export data $cell_lit_table = { " + text(lit_data, ", ") + " }") } return { diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index dfd77c83..bab79945 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -17,10 +17,6 @@ JSValue qbe_new_float64(JSContext *ctx, double d) { return __JS_NewFloat64(ctx, d); } -JSValue qbe_new_string(JSContext *ctx, const char *str) { - return JS_NewString(ctx, str); -} - /* Comparison op IDs (must match qbe.cm float_cmp_op_id values) */ enum { QBE_CMP_EQ = 0, @@ -31,128 +27,89 @@ enum { QBE_CMP_GE = 5 }; -/* ============================================================ - Float binary arithmetic - ============================================================ */ - -static inline JSValue qbe_float_binop(JSContext *ctx, JSValue a, JSValue b, - double (*op)(double, double)) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - double r = op(da, db); - if (!isfinite(r)) - return JS_NULL; - return JS_NewFloat64(ctx, r); -} - -static double op_add(double a, double b) { return a + b; } -static double op_sub(double a, double b) { return a - b; } -static double op_mul(double a, double b) { return a * b; } - -JSValue qbe_float_add(JSContext *ctx, JSValue a, JSValue b) { - return qbe_float_binop(ctx, a, b, op_add); -} - -/* Generic add: concat if both text, float add if both numeric, else type error */ -JSValue cell_rt_add(JSContext *ctx, JSValue a, JSValue b) { - if (JS_IsText(a) && JS_IsText(b)) - return JS_ConcatString(ctx, a, b); - if (JS_IsNumber(a) && JS_IsNumber(b)) - return qbe_float_binop(ctx, a, b, op_add); - JS_RaiseDisrupt(ctx, "cannot add incompatible types"); - return JS_NULL; -} - -JSValue qbe_float_sub(JSContext *ctx, JSValue a, JSValue b) { - return qbe_float_binop(ctx, a, b, op_sub); -} - -JSValue qbe_float_mul(JSContext *ctx, JSValue a, JSValue b) { - return qbe_float_binop(ctx, a, b, op_mul); -} - -JSValue qbe_float_div(JSContext *ctx, JSValue a, JSValue b) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - if (db == 0.0) - return JS_NULL; - double r = da / db; - if (!isfinite(r)) - return JS_NULL; - return JS_NewFloat64(ctx, r); -} - -JSValue qbe_float_mod(JSContext *ctx, JSValue a, JSValue b) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - if (db == 0.0) - return JS_NULL; - double r = fmod(da, db); - if (!isfinite(r)) - return JS_NULL; - return JS_NewFloat64(ctx, r); -} - -JSValue qbe_float_pow(JSContext *ctx, JSValue a, JSValue b) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - double r = pow(da, db); - if (!isfinite(r) && isfinite(da) && isfinite(db)) - return JS_NULL; - return JS_NewFloat64(ctx, r); -} - -/* ============================================================ - Float unary ops - ============================================================ */ - -JSValue qbe_float_neg(JSContext *ctx, JSValue v) { - double d; - JS_ToFloat64(ctx, &d, v); - return JS_NewFloat64(ctx, -d); -} - -JSValue qbe_float_inc(JSContext *ctx, JSValue v) { - double d; - JS_ToFloat64(ctx, &d, v); - return JS_NewFloat64(ctx, d + 1); -} - -JSValue qbe_float_dec(JSContext *ctx, JSValue v) { - double d; - JS_ToFloat64(ctx, &d, v); - return JS_NewFloat64(ctx, d - 1); -} - -/* ============================================================ - Float comparison — returns 0 or 1 for QBE branching - ============================================================ */ - -int qbe_float_cmp(JSContext *ctx, int op, JSValue a, JSValue b) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - switch (op) { - case QBE_CMP_EQ: return da == db; - case QBE_CMP_NE: return da != db; - case QBE_CMP_LT: return da < db; - case QBE_CMP_LE: return da <= db; - case QBE_CMP_GT: return da > db; - case QBE_CMP_GE: return da >= db; - default: return 0; +/* Generic comparison helper matching MACH eq/ne/lt/le/gt/ge semantics. */ +JSValue cell_rt_cmp(JSContext *ctx, int op, JSValue a, JSValue b) { + if (JS_VALUE_IS_BOTH_INT(a, b)) { + int32_t ia = JS_VALUE_GET_INT(a); + int32_t ib = JS_VALUE_GET_INT(b); + switch (op) { + case QBE_CMP_EQ: return JS_NewBool(ctx, ia == ib); + case QBE_CMP_NE: return JS_NewBool(ctx, ia != ib); + case QBE_CMP_LT: return JS_NewBool(ctx, ia < ib); + case QBE_CMP_LE: return JS_NewBool(ctx, ia <= ib); + case QBE_CMP_GT: return JS_NewBool(ctx, ia > ib); + case QBE_CMP_GE: return JS_NewBool(ctx, ia >= ib); + default: return JS_NewBool(ctx, 0); + } } -} -/* ============================================================ - Boolean conversion wrapper - ============================================================ */ + /* Fast path: identity after chasing forward pointers (matches MACH). */ + { + JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a; + JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b; + if (ca == cb) { + if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE) + return JS_TRUE; + if (op == QBE_CMP_NE) + return JS_FALSE; + } + } -int qbe_to_bool(JSContext *ctx, JSValue v) { - return JS_ToBool(ctx, v); + if (JS_IsNumber(a) && JS_IsNumber(b)) { + double da, db; + JS_ToFloat64(ctx, &da, a); + JS_ToFloat64(ctx, &db, b); + switch (op) { + case QBE_CMP_EQ: return JS_NewBool(ctx, da == db); + case QBE_CMP_NE: return JS_NewBool(ctx, da != db); + case QBE_CMP_LT: return JS_NewBool(ctx, da < db); + case QBE_CMP_LE: return JS_NewBool(ctx, da <= db); + case QBE_CMP_GT: return JS_NewBool(ctx, da > db); + case QBE_CMP_GE: return JS_NewBool(ctx, da >= db); + default: return JS_NewBool(ctx, 0); + } + } + + if (mist_is_text(a) && mist_is_text(b)) { + int cmp = js_string_compare_value(ctx, a, b, FALSE); + switch (op) { + case QBE_CMP_EQ: return JS_NewBool(ctx, cmp == 0); + case QBE_CMP_NE: return JS_NewBool(ctx, cmp != 0); + case QBE_CMP_LT: return JS_NewBool(ctx, cmp < 0); + case QBE_CMP_LE: return JS_NewBool(ctx, cmp <= 0); + case QBE_CMP_GT: return JS_NewBool(ctx, cmp > 0); + case QBE_CMP_GE: return JS_NewBool(ctx, cmp >= 0); + default: return JS_NewBool(ctx, 0); + } + } + + if (JS_IsNull(a) && JS_IsNull(b)) { + if (op == QBE_CMP_EQ || op == QBE_CMP_LE || op == QBE_CMP_GE) + return JS_TRUE; + return JS_FALSE; + } + + if (JS_IsBool(a) && JS_IsBool(b)) { + int ba = JS_VALUE_GET_BOOL(a); + int bb = JS_VALUE_GET_BOOL(b); + switch (op) { + case QBE_CMP_EQ: return JS_NewBool(ctx, ba == bb); + case QBE_CMP_NE: return JS_NewBool(ctx, ba != bb); + case QBE_CMP_LT: return JS_NewBool(ctx, ba < bb); + case QBE_CMP_LE: return JS_NewBool(ctx, ba <= bb); + case QBE_CMP_GT: return JS_NewBool(ctx, ba > bb); + case QBE_CMP_GE: return JS_NewBool(ctx, ba >= bb); + default: return JS_NewBool(ctx, 0); + } + } + + if (op == QBE_CMP_EQ) + return JS_NewBool(ctx, 0); + if (op == QBE_CMP_NE) + return JS_NewBool(ctx, 1); + + JS_RaiseDisrupt(ctx, "cannot compare: operands must be same type"); + return JS_EXCEPTION; } /* ============================================================ @@ -190,29 +147,37 @@ JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) { return JS_NewInt32(ctx, ia ^ ib); } -/* ============================================================ - Shift ops on floats (convert to int32, shift, re-tag) - ============================================================ */ +/* 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; + int rlen = js_string_value_len(right); + int cap = (int)objhdr_cap56(s->hdr); + 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; + } -JSValue qbe_shift_shl(JSContext *ctx, JSValue a, JSValue b) { - int32_t ia, ib; - JS_ToInt32(ctx, &ia, a); - JS_ToInt32(ctx, &ib, b); - return JS_NewInt32(ctx, ia << (ib & 31)); -} - -JSValue qbe_shift_sar(JSContext *ctx, JSValue a, JSValue b) { - int32_t ia, ib; - JS_ToInt32(ctx, &ia, a); - JS_ToInt32(ctx, &ib, b); - return JS_NewInt32(ctx, ia >> (ib & 31)); -} - -JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) { - int32_t ia, ib; - JS_ToInt32(ctx, &ia, a); - JS_ToInt32(ctx, &ib, b); - return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); + /* Different target: exact-fit stoned path. */ + JSValue res = JS_ConcatString(ctx, left, right); + if (JS_IsException(res)) + return JS_EXCEPTION; + return res; } /* ============================================================ @@ -381,13 +346,6 @@ static JSValue cell_rt_load_field_key(JSContext *ctx, JSValue obj, JSValue key) return JS_GetProperty(ctx, obj, key); } -JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) { - JSValue key = aot_key_from_cstr(ctx, name); - if (JS_IsException(key)) - return JS_EXCEPTION; - return cell_rt_load_field_key(ctx, obj, key); -} - JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) { JSValue key = aot_lit_from_index(ctx, lit_idx); if (JS_IsException(key)) @@ -395,29 +353,12 @@ JSValue cell_rt_load_field_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) { return cell_rt_load_field_key(ctx, obj, key); } -/* Like cell_rt_load_field but without the function guard. - Used by load_dynamic when the key happens to be a static string. */ -JSValue cell_rt_load_prop_str(JSContext *ctx, JSValue obj, const char *name) { - JSValue key = aot_key_from_cstr(ctx, name); - if (JS_IsException(key)) - return JS_EXCEPTION; - return JS_GetProperty(ctx, obj, key); -} - static int cell_rt_store_field_key(JSContext *ctx, JSValue val, JSValue obj, JSValue key) { int ret = JS_SetProperty(ctx, obj, key, val); return (ret < 0 || JS_HasException(ctx)) ? 0 : 1; } -int cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj, - const char *name) { - JSValue key = aot_key_from_cstr(ctx, name); - if (JS_IsException(key)) - return 0; - return cell_rt_store_field_key(ctx, val, obj, key); -} - int cell_rt_store_field_lit(JSContext *ctx, JSValue val, JSValue obj, int64_t lit_idx) { JSValue key = aot_lit_from_index(ctx, lit_idx); @@ -455,25 +396,6 @@ int cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj, } } -JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) { - if (JS_IsInt(idx)) - return JS_GetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx)); - return JS_GetProperty(ctx, arr, idx); -} - -int cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr, - JSValue idx) { - int ret = 0; - JSValue nr = JS_NULL; - if (JS_IsInt(idx)) - nr = JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val); - else - ret = JS_SetProperty(ctx, arr, idx, val); - if (JS_IsInt(idx)) - return JS_IsException(nr) ? 0 : 1; - return (ret < 0 || JS_HasException(ctx)) ? 0 : 1; -} - /* --- Intrinsic/global lookup --- */ void cell_rt_set_native_env(JSContext *ctx, JSValue env) { @@ -895,6 +817,12 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, if (!JS_IsFunction(callee_fn_val)) { JS_RaiseDisrupt(ctx, "not a function"); + { + int ret_info = JS_VALUE_GET_INT(frame->address); + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) + fp[ret_slot] = JS_EXCEPTION; + } /* Resume caller with exception pending */ JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; @@ -905,6 +833,10 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val); if (!cell_check_call_arity(ctx, callee_fn, callee_argc)) { + int ret_info = JS_VALUE_GET_INT(frame->address); + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) + fp[ret_slot] = JS_EXCEPTION; JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); fn = (cell_compiled_fn)JS_VALUE_GET_CODE(FN_READ_CODE(exc_fn))->u.native.fn_ptr; cell_sync_dl_from_native_fn(st, exc_fn); @@ -1191,56 +1123,10 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) { return JS_MKPTR(new_frame); } -void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) { - if (frame_val == JS_EXCEPTION || frame_val == JS_NULL) return; - JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); - fr->slots[idx] = val; -} - -/* cell_rt_invoke — still used for non-dispatch-loop paths (e.g. old code) */ -JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) { - if (frame_val == JS_EXCEPTION) return JS_EXCEPTION; - JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); - int c_argc = JS_VALUE_GET_INT(fr->address); - if (c_argc < 0) c_argc = 0; - JSValue fn_val = fr->function; - - if (!JS_IsFunction(fn_val)) { - JS_RaiseDisrupt(ctx, "not a function"); - return JS_EXCEPTION; - } - - JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val); - JSValue result; - if (!cell_check_call_arity(ctx, fn, c_argc)) - return JS_EXCEPTION; - - if (fn->kind == JS_FUNC_KIND_C) { - result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]); - } else if (fn->kind == JS_FUNC_KIND_NATIVE) { - result = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]); - } else { - JSValue args[c_argc > 0 ? c_argc : 1]; - for (int i = 0; i < c_argc; i++) - args[i] = fr->slots[i + 1]; - result = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, args, 0); - } - - if (JS_IsException(result)) - return JS_EXCEPTION; - if (JS_HasException(ctx)) - JS_GetException(ctx); - return result; -} - JSValue cell_rt_goframe(JSContext *ctx, JSValue fn, int64_t nargs) { return cell_rt_frame(ctx, fn, nargs); } -JSValue cell_rt_goinvoke(JSContext *ctx, JSValue frame_val) { - return cell_rt_invoke(ctx, frame_val); -} - /* --- Array push/pop --- */ JSValue cell_rt_push(JSContext *ctx, JSValue arr, JSValue val) { @@ -1268,13 +1154,6 @@ static JSValue cell_rt_delete_key(JSContext *ctx, JSValue obj, JSValue key) { return JS_NewBool(ctx, ret >= 0); } -JSValue cell_rt_delete_str(JSContext *ctx, JSValue obj, const char *name) { - JSValue key = aot_key_from_cstr(ctx, name); - if (JS_IsException(key)) - return JS_EXCEPTION; - return cell_rt_delete_key(ctx, obj, key); -} - JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) { JSValue key = aot_lit_from_index(ctx, lit_idx); if (JS_IsException(key)) @@ -1282,49 +1161,6 @@ JSValue cell_rt_delete_lit(JSContext *ctx, JSValue obj, int64_t lit_idx) { return cell_rt_delete_key(ctx, obj, key); } -/* --- Typeof --- */ - -JSValue cell_rt_typeof(JSContext *ctx, JSValue val) { - if (JS_IsNull(val)) return JS_NewString(ctx, "null"); - if (JS_IsInt(val) || JS_IsNumber(val)) return JS_NewString(ctx, "number"); - if (JS_IsBool(val)) return JS_NewString(ctx, "logical"); - if (JS_IsText(val)) return JS_NewString(ctx, "text"); - if (JS_IsFunction(val)) return JS_NewString(ctx, "function"); - if (JS_IsArray(val)) return JS_NewString(ctx, "array"); - if (JS_IsRecord(val)) return JS_NewString(ctx, "object"); - return JS_NewString(ctx, "unknown"); -} - -/* --- Text comparison stubs (called from QBE type-dispatch branches) --- */ - -JSValue cell_rt_lt_text(JSContext *ctx, JSValue a, JSValue b) { - const char *sa = JS_ToCString(ctx, a); - const char *sb = JS_ToCString(ctx, b); - int r = (sa && sb) ? strcmp(sa, sb) < 0 : 0; - return JS_NewBool(ctx, r); -} - -JSValue cell_rt_gt_text(JSContext *ctx, JSValue a, JSValue b) { - const char *sa = JS_ToCString(ctx, a); - const char *sb = JS_ToCString(ctx, b); - int r = (sa && sb) ? strcmp(sa, sb) > 0 : 0; - return JS_NewBool(ctx, r); -} - -JSValue cell_rt_le_text(JSContext *ctx, JSValue a, JSValue b) { - const char *sa = JS_ToCString(ctx, a); - const char *sb = JS_ToCString(ctx, b); - int r = (sa && sb) ? strcmp(sa, sb) <= 0 : 0; - return JS_NewBool(ctx, r); -} - -JSValue cell_rt_ge_text(JSContext *ctx, JSValue a, JSValue b) { - const char *sa = JS_ToCString(ctx, a); - const char *sb = JS_ToCString(ctx, b); - int r = (sa && sb) ? strcmp(sa, sb) >= 0 : 0; - return JS_NewBool(ctx, r); -} - static int cell_rt_tol_eq_inner(JSContext *ctx, JSValue a, JSValue b, JSValue tol) { if (JS_IsNumber(a) && JS_IsNumber(b) && JS_IsNumber(tol)) { @@ -1358,6 +1194,108 @@ 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)) + result = JS_HasPropertyKey(ctx, v, ctx->actor_sym) > 0; + return result; +} + /* --- Type check: is_proxy (function with arity 2) --- */ int cell_rt_is_proxy(JSContext *ctx, JSValue v) { @@ -1367,14 +1305,6 @@ int cell_rt_is_proxy(JSContext *ctx, JSValue v) { return fn->length == 2; } -/* --- Identity check (chases forwarding pointers) --- */ - -JSValue cell_rt_is_identical(JSContext *ctx, JSValue a, JSValue b) { - if (JS_IsPtr(a)) a = JS_MKPTR(chase(a)); - if (JS_IsPtr(b)) b = JS_MKPTR(chase(b)); - return JS_NewBool(ctx, a == b); -} - /* --- Short-circuit and/or (non-allocating) --- */ JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {