diff --git a/build.cm b/build.cm index 5d14d501..b181cc8e 100644 --- a/build.cm +++ b/build.cm @@ -81,7 +81,7 @@ function content_hash(str) { } // Bump when native codegen/runtime ABI changes so stale dylibs are not reused. -def NATIVE_CACHE_VERSION = "native-v8" +def NATIVE_CACHE_VERSION = "native-v16" // Enable AOT ASan by creating .cell/asan_aot in the package root. function native_sanitize_flags() { diff --git a/qbe.cm b/qbe.cm index 2424b566..25e1e53c 100644 --- a/qbe.cm +++ b/qbe.cm @@ -11,7 +11,7 @@ def js_null = 7 def js_false = 3 def js_true = 35 def js_exception = 15 -def js_empty_text = 27 +def js_empty_text = 11 // Shared closure vars for functions with >4 params var _qop = null @@ -67,13 +67,13 @@ var is_ptr = function(p, v) { var is_imm_text = function(p, v) { return ` %${p}.t =l and ${v}, 31 - %${p} =w ceql %${p}.t, 27 + %${p} =w ceql %${p}.t, 11 ` } var is_text = function(p, v) { return ` %${p}.imm =l and ${v}, 31 - %${p}.is_imm =w ceql %${p}.imm, 27 + %${p}.is_imm =w ceql %${p}.imm, 11 jnz %${p}.is_imm, @${p}.yes, @${p}.chk_ptr @${p}.chk_ptr %${p}.ptag =l and ${v}, 7 diff --git a/qbe_emit.cm b/qbe_emit.cm index e62b419f..43674cd6 100644 --- a/qbe_emit.cm +++ b/qbe_emit.cm @@ -182,10 +182,6 @@ ${sw("w", "%fp", "%dest", "%r")} // Type checks via C (no ctx needed except is_proxy) var tc_ops = [ - ["is_text", "JS_IsText", false], - ["is_array", "JS_IsArray", false], - ["is_func", "JS_IsFunction", false], - ["is_record", "JS_IsRecord", false], ["is_stone", "JS_IsStone", false], ["is_proxy", "cell_rt_is_proxy", true] ] @@ -223,10 +219,141 @@ ${sw("w", "%fp", "%dest", "%r")} i = i + 1 } - // Float comparisons: call qbe_float_cmp(ctx, op_id, a, b) → w, tag + // 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) { +@entry +${sr("a", "%src")} + %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, 3 + 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_array: inline pointer+header check (OBJ_ARRAY=0), chase forwards + h[] = `export function $__is_array_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %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, 0 + 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_func: inline pointer+header check (OBJ_FUNCTION=4), chase forwards + h[] = `export function $__is_func_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %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, 4 + 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 +}` + + // Float comparisons: decode short-float/int inline, then compare in QBE. var fc_ops = [ - ["eq_float", 0], ["ne_float", 1], ["lt_float", 2], - ["le_float", 3], ["gt_float", 4], ["ge_float", 5] + ["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)) { @@ -234,7 +361,63 @@ ${sw("w", "%fp", "%dest", "%r")} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %cr =w call $qbe_float_cmp(l %ctx, w ${fc_ops[i][1]}, l %a, l %b) + %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 @@ -296,12 +479,72 @@ ${sw("w", "%fp", "%dest", "%r")} i = i + 1 } - // not: JS_ToBool + negate + tag + // not: inline truthiness (no JS_ToBool call) h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} - %bval =w call $JS_ToBool(l %ctx, l %a) - %neg =w ceqw %bval, 0 + %t5 =l and %a, 31 + %is_bool =w ceql %t5, 3 + jnz %is_bool, @bool, @chk_null +@bool + %truthy =w cnel %a, 3 + jmp @truthy_done +@chk_null + %is_null =w ceql %t5, 7 + jnz %is_null, @falsey, @chk_int +@chk_int + %t1 =l and %a, 1 + %is_int =w ceql %t1, 0 + jnz %is_int, @int_path, @chk_imm_text +@int_path + %truthy =w cnel %a, 0 + jmp @truthy_done +@chk_imm_text + %is_imm_text =w ceql %t5, 11 + jnz %is_imm_text, @imm_text, @chk_ptr +@imm_text + %truthy =w cnel %a, 11 + jmp @truthy_done +@chk_ptr + %ptag =l and %a, 7 + %is_ptr =w ceql %ptag, 1 + jnz %is_ptr, @ptr_path, @chk_sfloat +@chk_sfloat + %is_sfloat =w ceql %ptag, 5 + jnz %is_sfloat, @sfloat_path, @other_imm +@sfloat_path + %sexp =l shr %a, 55 + %sexp =l and %sexp, 255 + %truthy =w cnel %sexp, 0 + jmp @truthy_done +@other_imm + %truthy =w copy 1 + jmp @truthy_done +@ptr_path + %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_text_ptr +@follow + %ptr =l shr %hdr, 3 + %hdr =l loadl %ptr + jmp @chase +@chk_text_ptr + %is_text_ptr =w ceql %ht, 2 + jnz %is_text_ptr, @text_ptr, @ptr_truthy +@text_ptr + %len =l shr %hdr, 8 + %truthy =w cnel %len, 0 + jmp @truthy_done +@ptr_truthy + %truthy =w copy 1 + jmp @truthy_done +@falsey + %truthy =w copy 0 +@truthy_done + %neg =w ceqw %truthy, 0 %nex =l extuw %neg %sh =l shl %nex, 5 %r =l or %sh, 3 @@ -332,150 +575,214 @@ ${sw("w", "%fp", "%dest", "%r")} h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) { @entry ${sr("a", "%src")} - %r =l call $qbe_bnot(l %ctx, l %a) + %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 ${sw("w", "%fp", "%dest", "%r")} ret +@bad + call $cell_rt_disrupt(l %ctx) + ret }` - // Bitwise binary ops - var bw_ops = [ - ["band", "qbe_bitwise_and"], ["bor", "qbe_bitwise_or"], - ["bxor", "qbe_bitwise_xor"], ["bshl", "qbe_shift_shl"], - ["bshr", "qbe_shift_sar"], ["bushr", "qbe_shift_shr"] - ] - i = 0 - while (i < length(bw_ops)) { - h[] = `export function $__${bw_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + // 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) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %r =l call $${bw_ops[i][1]}(l %ctx, l %a, l %b) + %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 ${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) { +@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 +${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) { +@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 +${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 }` - i = i + 1 - } // ============================================================ // Category C: Allocating helpers (return fp or 0) // ============================================================ - // add: int fast path in-helper, slow path calls runtime - h[] = `export function l $__add_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { + // concat allocates; keep refresh path + h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { @entry ${sr("a", "%s1")} ${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_is_int =w ceql %a_tag, 0 - %b_is_int =w ceql %b_tag, 0 - %both_int =w and %a_is_int, %b_is_int - jnz %both_int, @int_fast, @slow -@int_fast - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %sum =l add %ai, %bi - %sumw =w copy %sum - %sumext =l extsw %sumw - %sum_ok =w ceql %sumext, %sum - jnz %sum_ok, @int_store, @slow -@int_store - %rtag =l shl %sum, 1 -${sw("w", "%fp", "%dest", "%rtag")} + %r =l call $JS_ConcatString(l %ctx, l %a, l %b) +${alloc_tail("%r")} +}` + + // access_lit(ctx, fp, dest, lit_idx) + h[] = `export function l $__access_lit_ss(l %ctx, l %fp, l %dest, l %lit_idx) { +@entry + %r =l call $cell_rt_access_lit(l %ctx, l %lit_idx) + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @ok +@ok +${sw("w", "%fp", "%dest", "%r")} ret %fp -@slow - %r =l call $cell_rt_add(l %ctx, l %a, l %b) -${alloc_tail("%r")} +@exc + ret 0 }` - // sub: int fast path in-helper, slow path calls float helper - h[] = `export function l $__sub_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_is_int =w ceql %a_tag, 0 - %b_is_int =w ceql %b_tag, 0 - %both_int =w and %a_is_int, %b_is_int - jnz %both_int, @int_fast, @slow -@int_fast - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %diff =l sub %ai, %bi - %diffw =w copy %diff - %diffext =l extsw %diffw - %diff_ok =w ceql %diffext, %diff - jnz %diff_ok, @int_store, @slow -@int_store - %rtag =l shl %diff, 1 -${sw("w", "%fp", "%dest", "%rtag")} - ret %fp -@slow - %r =l call $qbe_float_sub(l %ctx, l %a, l %b) -${alloc_tail("%r")} -}` - - // mul: int fast path in-helper, slow path calls float helper - h[] = `export function l $__mul_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %a_tag =l and %a, 1 - %b_tag =l and %b, 1 - %a_is_int =w ceql %a_tag, 0 - %b_is_int =w ceql %b_tag, 0 - %both_int =w and %a_is_int, %b_is_int - jnz %both_int, @int_fast, @slow -@int_fast - %ai =l sar %a, 1 - %bi =l sar %b, 1 - %prod =l mul %ai, %bi - %prodw =w copy %prod - %prodext =l extsw %prodw - %prod_ok =w ceql %prodext, %prod - jnz %prod_ok, @int_store, @slow -@int_store - %rtag =l shl %prod, 1 -${sw("w", "%fp", "%dest", "%rtag")} - ret %fp -@slow - %r =l call $qbe_float_mul(l %ctx, l %a, l %b) -${alloc_tail("%r")} -}` - - // Remaining allocating binary ops: call C, refresh, write dest - var ab_ops = [ - ["div", "qbe_float_div"], - ["mod", "qbe_float_mod"], ["pow", "qbe_float_pow"], - ["concat", "JS_ConcatString"] - ] - i = 0 - while (i < length(ab_ops)) { - h[] = `export function l $__${ab_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { -@entry -${sr("a", "%s1")} -${sr("b", "%s2")} - %r =l call $${ab_ops[i][1]}(l %ctx, l %a, l %b) -${alloc_tail("%r")} -}` - i = i + 1 - } - - // Allocating unary: negate - h[] = `export function l $__neg_ss(l %ctx, l %fp, l %dest, l %src) { -@entry -${sr("a", "%src")} - %r =l call $qbe_float_neg(l %ctx, l %a) -${alloc_tail("%r")} -}` - - // Property access: load_field(ctx, fp, dest, obj_slot, name_ptr) - h[] = `export function l $__load_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %name) { + // 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) { @entry ${sr("a", "%obj_slot")} - %r =l call $cell_rt_load_field(l %ctx, l %a, l %name) -${alloc_tail("%r")} + %r =l call $cell_rt_load_field_lit(l %ctx, l %a, l %lit_idx) + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @ok +@ok +${sw("w", "%fp", "%dest", "%r")} + ret %fp +@exc + ret 0 }` // load_dynamic(ctx, fp, dest, obj_slot, key_slot) @@ -484,7 +791,13 @@ ${alloc_tail("%r")} ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} %r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b) -${alloc_tail("%r")} + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @ok +@ok +${sw("w", "%fp", "%dest", "%r")} + ret %fp +@exc + ret 0 }` // load_index(ctx, fp, dest, arr_slot, idx_slot) @@ -493,16 +806,26 @@ ${alloc_tail("%r")} ${sr("a", "%arr_slot")} ${sr("b", "%idx_slot")} %r =l call $cell_rt_load_index(l %ctx, l %a, l %b) -${alloc_tail("%r")} + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @ok +@ok +${sw("w", "%fp", "%dest", "%r")} + ret %fp +@exc + ret 0 }` - // store_field(ctx, fp, obj_slot, val_slot, name_ptr) — no dest write - h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %name) { + // 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) { @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} - call $cell_rt_store_field(l %ctx, l %b, l %a, l %name) -${alloc_tail_nw()} + %ok =w call $cell_rt_store_field_lit(l %ctx, l %b, l %a, l %lit_idx) + jnz %ok, @ok, @exc +@ok + ret %fp +@exc + ret 0 }` // store_dynamic(ctx, fp, obj_slot, val_slot, key_slot) — no dest write @@ -511,8 +834,12 @@ ${alloc_tail_nw()} ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} ${sr("c", "%key_slot")} - call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c) -${alloc_tail_nw()} + %ok =w call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c) + jnz %ok, @ok, @exc +@ok + ret %fp +@exc + ret 0 }` // store_index(ctx, fp, obj_slot, val_slot, idx_slot) — no dest write @@ -521,8 +848,12 @@ ${alloc_tail_nw()} ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} ${sr("c", "%idx_slot")} - call $cell_rt_store_index(l %ctx, l %b, l %a, l %c) -${alloc_tail_nw()} + %ok =w call $cell_rt_store_index(l %ctx, l %b, l %a, l %c) + jnz %ok, @ok, @exc +@ok + ret %fp +@exc + ret 0 }` // frame(ctx, fp, dest, fn_slot, nargs) @@ -590,13 +921,14 @@ ${alloc_tail("%r")} h[] = `export function l $__new_float64_ss(l %ctx, l %fp, l %dest, d %val) { @entry %r =l call $qbe_new_float64(l %ctx, d %val) -${alloc_tail("%r")} +${sw("w", "%fp", "%dest", "%r")} + ret %fp }` - // get_intrinsic(ctx, fp, dest, name_ptr) - h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %name_ptr) { + // get_intrinsic(ctx, fp, dest, lit_idx) + h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %lit_idx) { @entry - %r =l call $cell_rt_get_intrinsic(l %ctx, l %name_ptr) + %r =l call $cell_rt_get_intrinsic_lit(l %ctx, l %lit_idx) ${alloc_tail("%r")} }` @@ -631,11 +963,11 @@ ${sr("a", "%src")} ${alloc_tail("%r")} }` - // delete_field(ctx, fp, dest, obj_slot, name_ptr) - h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %name) { + // 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) { @entry ${sr("a", "%obj_slot")} - %r =l call $cell_rt_delete_str(l %ctx, l %a, l %name) + %r =l call $cell_rt_delete_lit(l %ctx, l %a, l %lit_idx) ${alloc_tail("%r")} }` @@ -671,8 +1003,11 @@ var qbe_emit = function(ir, qbe, export_name) { var out = [] var data_out = [] var str_table = {} + var str_entries = [] var str_id = 0 var uid = 0 + var lit_data = null + var si = 0 // ============================================================ // Output helpers @@ -714,8 +1049,10 @@ var qbe_emit = function(ir, qbe, export_name) { escaped = replace(escaped, "\t", "\\t") var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }' push(data_out, line) - str_table[val] = label - return label + var entry = { label: label, idx: length(str_entries) } + push(str_entries, entry) + str_table[val] = entry + return entry } // ============================================================ @@ -753,6 +1090,7 @@ var qbe_emit = function(ir, qbe, export_name) { var ei = 0 var elem_slot = 0 var v = null + var rv = null var lhs = null var rhs = null var obj = null @@ -789,6 +1127,9 @@ var qbe_emit = function(ir, qbe, export_name) { var jnn_lbl = null var jnn_idx = null var jnn_backedge = false + var truthy = null + var lhs_d = null + var rhs_d = null // Pre-scan: count invoke/tail_invoke points to assign segment numbers. // Must skip dead code (instructions after terminators) the same way @@ -907,6 +1248,162 @@ var qbe_emit = function(ir, qbe, export_name) { } } + // Inline JS_ToBool equivalent for hot branch paths. + // Returns a `%name` holding w 0/1 truthiness. + var emit_truthy_w = function(val) { + var tp = fresh() + emit(` %${tp}_t5 =l and ${val}, 31`) + emit(` %${tp}_is_bool =w ceql %${tp}_t5, 3`) + emit(` jnz %${tp}_is_bool, @${tp}_bool, @${tp}_chk_null`) + emit(`@${tp}_bool`) + emit(` %${tp}_truthy =w cnel ${val}, 3`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_chk_null`) + emit(` %${tp}_is_null =w ceql %${tp}_t5, 7`) + emit(` jnz %${tp}_is_null, @${tp}_falsey, @${tp}_chk_int`) + emit(`@${tp}_chk_int`) + emit(` %${tp}_t1 =l and ${val}, 1`) + emit(` %${tp}_is_int =w ceql %${tp}_t1, 0`) + emit(` jnz %${tp}_is_int, @${tp}_int_path, @${tp}_chk_imm_text`) + emit(`@${tp}_int_path`) + emit(` %${tp}_truthy =w cnel ${val}, 0`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_chk_imm_text`) + emit(` %${tp}_is_imm_text =w ceql %${tp}_t5, 11`) + emit(` jnz %${tp}_is_imm_text, @${tp}_imm_text, @${tp}_chk_ptr`) + emit(`@${tp}_imm_text`) + emit(` %${tp}_truthy =w cnel ${val}, 11`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_chk_ptr`) + emit(` %${tp}_ptag =l and ${val}, 7`) + emit(` %${tp}_is_ptr =w ceql %${tp}_ptag, 1`) + emit(` jnz %${tp}_is_ptr, @${tp}_ptr_path, @${tp}_chk_sfloat`) + emit(`@${tp}_chk_sfloat`) + emit(` %${tp}_is_sfloat =w ceql %${tp}_ptag, 5`) + emit(` jnz %${tp}_is_sfloat, @${tp}_sfloat_path, @${tp}_other_imm`) + emit(`@${tp}_sfloat_path`) + emit(` %${tp}_sexp =l shr ${val}, 55`) + emit(` %${tp}_sexp =l and %${tp}_sexp, 255`) + emit(` %${tp}_truthy =w cnel %${tp}_sexp, 0`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_other_imm`) + emit(` %${tp}_truthy =w copy 1`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_ptr_path`) + emit(` %${tp}_ptr =l and ${val}, -8`) + emit(` %${tp}_hdr =l loadl %${tp}_ptr`) + emit(`@${tp}_chase`) + emit(` %${tp}_ht =l and %${tp}_hdr, 7`) + emit(` %${tp}_is_fwd =w ceql %${tp}_ht, 7`) + emit(` jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk_text_ptr`) + emit(`@${tp}_follow`) + emit(` %${tp}_ptr =l shr %${tp}_hdr, 3`) + emit(` %${tp}_hdr =l loadl %${tp}_ptr`) + emit(` jmp @${tp}_chase`) + emit(`@${tp}_chk_text_ptr`) + emit(` %${tp}_is_text_ptr =w ceql %${tp}_ht, 2`) + emit(` jnz %${tp}_is_text_ptr, @${tp}_text_ptr, @${tp}_ptr_truthy`) + emit(`@${tp}_text_ptr`) + emit(` %${tp}_len =l shr %${tp}_hdr, 8`) + emit(` %${tp}_truthy =w cnel %${tp}_len, 0`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_ptr_truthy`) + emit(` %${tp}_truthy =w copy 1`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_falsey`) + emit(` %${tp}_truthy =w copy 0`) + emit(`@${tp}_done`) + return `%${tp}_truthy` + } + + // Returns w 0/1 for JS text (immediate or heap), following forwards. + var emit_is_text_w = function(val) { + var tp = fresh() + emit(` %${tp}_imm =l and ${val}, 31`) + emit(` %${tp}_is_imm =w ceql %${tp}_imm, 11`) + emit(` jnz %${tp}_is_imm, @${tp}_yes, @${tp}_chk_ptr`) + emit(`@${tp}_chk_ptr`) + emit(` %${tp}_ptag =l and ${val}, 7`) + emit(` %${tp}_is_ptr =w ceql %${tp}_ptag, 1`) + emit(` jnz %${tp}_is_ptr, @${tp}_ptr, @${tp}_no`) + emit(`@${tp}_ptr`) + emit(` %${tp}_ptr =l and ${val}, -8`) + emit(` %${tp}_hdr =l loadl %${tp}_ptr`) + emit(`@${tp}_chase`) + emit(` %${tp}_ht =l and %${tp}_hdr, 7`) + emit(` %${tp}_is_fwd =w ceql %${tp}_ht, 7`) + emit(` jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk`) + emit(`@${tp}_follow`) + emit(` %${tp}_ptr =l shr %${tp}_hdr, 3`) + emit(` %${tp}_hdr =l loadl %${tp}_ptr`) + emit(` jmp @${tp}_chase`) + emit(`@${tp}_chk`) + emit(` %${tp}_is_text =w ceql %${tp}_ht, 2`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_yes`) + emit(` %${tp}_is_text =w copy 1`) + emit(` jmp @${tp}_done`) + emit(`@${tp}_no`) + emit(` %${tp}_is_text =w copy 0`) + emit(`@${tp}_done`) + return `%${tp}_is_text` + } + + // Returns w 0/1 for JS numbers (int or short-float). + var emit_is_num_w = function(val) { + var np = fresh() + emit(` %${np}_t1 =l and ${val}, 1`) + emit(` %${np}_ii =w ceql %${np}_t1, 0`) + emit(` %${np}_t2 =l and ${val}, 7`) + emit(` %${np}_fi =w ceql %${np}_t2, 5`) + emit(` %${np}_is_num =w or %${np}_ii, %${np}_fi`) + return `%${np}_is_num` + } + + // Pack w 0/1 into tagged JS bool (JS_FALSE/JS_TRUE). + var emit_pack_bool_js = function(wv) { + var bp = fresh() + emit(` %${bp}_ext =l extuw ${wv}`) + emit(` %${bp}_sh =l shl %${bp}_ext, 5`) + emit(` %${bp}_js =l or %${bp}_sh, 3`) + return `%${bp}_js` + } + + // Convert a known numeric JSValue (int or short-float) to QBE double. + // Type checks happen earlier in mcode/streamline. + var emit_num_to_double = 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(` %${np}_d =d swtof %${np}_iw`) + emit(` jmp @${np}_done`) + emit(`@${np}_float`) + emit(` %${np}_sexp =l shr ${val}, 55`) + emit(` %${np}_sexp =l and %${np}_sexp, 255`) + emit(` %${np}_is_zero =w ceql %${np}_sexp, 0`) + emit(` jnz %${np}_is_zero, @${np}_fzero, @${np}_fdecode`) + emit(`@${np}_fzero`) + emit(` %${np}_d =d copy d_0.0`) + emit(` jmp @${np}_done`) + emit(`@${np}_fdecode`) + emit(` %${np}_sign =l shr ${val}, 63`) + emit(` %${np}_mant =l shr ${val}, 3`) + emit(` %${np}_mant =l and %${np}_mant, 4503599627370495`) + emit(` %${np}_dexp =l sub %${np}_sexp, 127`) + emit(` %${np}_dexp =l add %${np}_dexp, 1023`) + emit(` %${np}_s63 =l shl %${np}_sign, 63`) + emit(` %${np}_e52 =l shl %${np}_dexp, 52`) + emit(` %${np}_bits =l or %${np}_s63, %${np}_e52`) + emit(` %${np}_bits =l or %${np}_bits, %${np}_mant`) + emit(` %${np}_d =d cast %${np}_bits`) + emit(`@${np}_done`) + return `%${np}_d` + } + // Walk instructions var last_was_term = false i = 0 @@ -977,12 +1474,12 @@ var qbe_emit = function(ir, qbe, export_name) { } } else if (is_text(a2)) { sl = intern_str(a2) - emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + emit(` %fp =l call $__access_lit_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`) emit_exc_check() } else if (is_object(a2)) { if (a2.make == "intrinsic") { sl = intern_str(a2.name) - emit(` %fp =l call $__get_intrinsic_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + emit(` %fp =l call $__get_intrinsic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`) emit_exc_check() } else if (a2.kind == "number") { if (a2.number != null && is_integer(a2.number)) { @@ -995,7 +1492,7 @@ var qbe_emit = function(ir, qbe, export_name) { } } else if (a2.kind == "text") { sl = intern_str(a2.value) - emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + emit(` %fp =l call $__access_lit_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`) emit_exc_check() } else if (a2.kind == "true") { s_write(a1, text(qbe.js_true)) @@ -1015,7 +1512,8 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Movement --- if (op == "move") { - emit(` call $__move_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + s_write(a1, v) continue } @@ -1044,18 +1542,34 @@ var qbe_emit = function(ir, qbe, export_name) { s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) - emit(` %${p}_r =l call $cell_rt_add(l %ctx, l ${lhs}, l ${rhs})`) - emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`) - chk = fresh() - emit(` %${chk} =w ceql %fp, 0`) - if (has_handler && !in_handler) { - emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) - } - emit(`@${chk}_ok`) + 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 } @@ -1082,17 +1596,10 @@ var qbe_emit = function(ir, qbe, export_name) { s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) - emit(` %${p}_r =l call $qbe_float_sub(l %ctx, l ${lhs}, l ${rhs})`) - emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`) - chk = fresh() - emit(` %${chk} =w ceql %fp, 0`) - if (has_handler && !in_handler) { - emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) - } - emit(`@${chk}_ok`) + lhs_d = emit_num_to_double(lhs) + rhs_d = emit_num_to_double(rhs) + emit(` %${p}_rd =d sub ${lhs_d}, ${rhs_d}`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) emit(`@${p}_done`) continue @@ -1120,40 +1627,55 @@ var qbe_emit = function(ir, qbe, export_name) { s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) - emit(` %${p}_r =l call $qbe_float_mul(l %ctx, l ${lhs}, l ${rhs})`) - emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`) - chk = fresh() - emit(` %${chk} =w ceql %fp, 0`) - if (has_handler && !in_handler) { - emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) - } - emit(`@${chk}_ok`) + lhs_d = emit_num_to_double(lhs) + rhs_d = emit_num_to_double(rhs) + emit(` %${p}_rd =d mul ${lhs_d}, ${rhs_d}`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) emit(`@${p}_done`) continue } if (op == "divide") { - emit(` %fp =l call $__div_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - emit_exc_check() + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + lhs_d = emit_num_to_double(lhs) + rhs_d = emit_num_to_double(rhs) + emit(` %${p}_rd =d div ${lhs_d}, ${rhs_d}`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) + s_write(a1, `%${p}_r`) continue } if (op == "modulo") { - emit(` %fp =l call $__mod_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - emit_exc_check() + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + lhs_d = emit_num_to_double(lhs) + rhs_d = emit_num_to_double(rhs) + emit(` %${p}_rd =d call $fmod(d ${lhs_d}, d ${rhs_d})`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) + s_write(a1, `%${p}_r`) continue } if (op == "negate") { - emit(` %fp =l call $__neg_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) - emit_exc_check() + lhs = s_read(a2) + p = fresh() + lhs_d = emit_num_to_double(lhs) + emit(` %${p}_rd =d neg ${lhs_d}`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) + s_write(a1, `%${p}_r`) continue } if (op == "pow") { - emit(` %fp =l call $__pow_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) - emit_exc_check() + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + lhs_d = emit_num_to_double(lhs) + rhs_d = emit_num_to_double(rhs) + emit(` %${p}_rd =d call $pow(d ${lhs_d}, d ${rhs_d})`) + emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) + s_write(a1, `%${p}_r`) continue } @@ -1168,27 +1690,45 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Type checks — use qbe.cm macros (no GC, no refresh) --- if (op == "is_int") { - emit(` call $__is_int_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + p = fresh() + emit(` %${p}_tag =l and ${v}, 1`) + emit(` %${p}_w =w ceql %${p}_tag, 0`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } if (op == "is_text") { - emit(` call $__is_text_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + s_write(a1, emit_pack_bool_js(emit_is_text_w(v))) continue } if (op == "is_num") { - emit(` call $__is_num_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + s_write(a1, emit_pack_bool_js(emit_is_num_w(v))) continue } if (op == "is_bool") { - emit(` call $__is_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + p = fresh() + emit(` %${p}_t5 =l and ${v}, 31`) + emit(` %${p}_w =w ceql %${p}_t5, 3`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } if (op == "is_null") { - emit(` call $__is_null_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) + v = s_read(a2) + p = fresh() + emit(` %${p}_t5 =l and ${v}, 31`) + emit(` %${p}_w =w ceql %${p}_t5, 7`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } if (op == "is_identical") { - emit(` call $__is_identical_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + lhs = s_read(a2) + rhs = s_read(a3) + p = fresh() + emit(` %${p}_w =w ceql ${lhs}, ${rhs}`) + s_write(a1, emit_pack_bool_js(`%${p}_w`)) continue } if (op == "is_array") { @@ -1418,7 +1958,7 @@ var qbe_emit = function(ir, qbe, export_name) { } if (pn != null) { sl = intern_str(pn) - emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) + emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(sl.idx)})`) } else { emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } @@ -1443,7 +1983,7 @@ var qbe_emit = function(ir, qbe, export_name) { } if (pn != null) { sl = intern_str(pn) - emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) + emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(sl.idx)})`) } else { emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } @@ -1464,7 +2004,7 @@ var qbe_emit = function(ir, qbe, export_name) { } if (pn != null) { sl = intern_str(pn) - emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) + emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(sl.idx)})`) } else { emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } @@ -1491,7 +2031,7 @@ var qbe_emit = function(ir, qbe, export_name) { } if (pn != null) { sl = intern_str(pn) - emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) + emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(sl.idx)})`) } else { emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } @@ -1534,17 +2074,8 @@ var qbe_emit = function(ir, qbe, export_name) { jt_lbl = sanitize(a2) jt_idx = label_pos[jt_lbl] jt_backedge = jt_idx != null && jt_idx < instr_idx - emit(` %${p}_is_true =w ceql ${v}, ${text(qbe.js_true)}`) - emit(` jnz %${p}_is_true, @${p}_take, @${p}_chk_fast`) - emit(`@${p}_chk_fast`) - emit(` %${p}_tag =l and ${v}, 31`) - emit(` %${p}_is_bool =w ceql %${p}_tag, 3`) - emit(` %${p}_is_null =w ceql %${p}_tag, 7`) - emit(` %${p}_is_falsey =w or %${p}_is_bool, %${p}_is_null`) - emit(` jnz %${p}_is_falsey, @${p}_f, @${p}_tb`) - emit(`@${p}_tb`) - emit(` %${p}_tbv =w call $JS_ToBool(l %ctx, l ${v})`) - emit(` jnz %${p}_tbv, @${p}_take, @${p}_f`) + truthy = emit_truthy_w(v) + emit(` jnz ${truthy}, @${p}_take, @${p}_f`) emit(`@${p}_take`) if (jt_backedge) { emit_backedge_branch(jt_lbl) @@ -1560,17 +2091,8 @@ var qbe_emit = function(ir, qbe, export_name) { jf_lbl = sanitize(a2) jf_idx = label_pos[jf_lbl] jf_backedge = jf_idx != null && jf_idx < instr_idx - emit(` %${p}_is_true =w ceql ${v}, ${text(qbe.js_true)}`) - emit(` jnz %${p}_is_true, @${p}_t, @${p}_chk_fast`) - emit(`@${p}_chk_fast`) - emit(` %${p}_tag =l and ${v}, 31`) - emit(` %${p}_is_bool =w ceql %${p}_tag, 3`) - emit(` %${p}_is_null =w ceql %${p}_tag, 7`) - emit(` %${p}_is_fast_false =w or %${p}_is_bool, %${p}_is_null`) - emit(` jnz %${p}_is_fast_false, @${p}_take, @${p}_tb`) - emit(`@${p}_tb`) - emit(` %${p}_tbv =w call $JS_ToBool(l %ctx, l ${v})`) - emit(` jnz %${p}_tbv, @${p}_t, @${p}_take`) + truthy = emit_truthy_w(v) + emit(` jnz ${truthy}, @${p}_t, @${p}_take`) emit(`@${p}_take`) if (jf_backedge) { emit_backedge_branch(jf_lbl) @@ -1625,7 +2147,12 @@ var qbe_emit = function(ir, qbe, export_name) { if (op == "setarg") { v = s_read(a1) lhs = s_read(a3) - emit(` call $cell_rt_setarg(l ${v}, l ${text(a2)}, l ${lhs})`) + p = fresh() + // JSFrame layout: [header,function,caller,address,slots...] + // slots start at byte offset 32. + emit(` %${p}_fr =l and ${v}, -8`) + emit(` %${p}_slot =l add %${p}_fr, ${text(32 + a2 * 8)}`) + emit(` storel ${lhs}, %${p}_slot`) continue } if (op == "invoke") { @@ -1640,9 +2167,11 @@ var qbe_emit = function(ir, qbe, export_name) { emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`) emit(" ret 0") emit(`@_seg${text(seg_num)}`) - // Check for exception after dispatch loop resumes us + // Check for exception marker in destination slot after resume. + // Dispatch writes JS_EXCEPTION into ret_slot on exceptional return. + rv = s_read(a2) p = fresh() - emit(` %${p} =w call $JS_HasException(l %ctx)`) + emit(` %${p} =w ceql ${rv}, ${text(qbe.js_exception)}`) if (has_handler && !in_handler) { emit(` jnz %${p}, @disruption_handler, @${p}_ok`) } else { @@ -1760,7 +2289,7 @@ var qbe_emit = function(ir, qbe, export_name) { } if (pn != null) { sl = intern_str(pn) - emit(` %fp =l call $__delete_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) + emit(` %fp =l call $__delete_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(sl.idx)})`) } else { emit(` %fp =l call $__delete_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } @@ -1783,7 +2312,7 @@ var qbe_emit = function(ir, qbe, export_name) { // IR: ["regexp", dest_slot, pattern_string, flags_string] pat_label = intern_str(a2) flg_label = intern_str(a3) - emit(` %fp =l call $__regexp_ss(l %ctx, l %fp, l ${text(a1)}, l ${pat_label}, l ${flg_label})`) + emit(` %fp =l call $__regexp_ss(l %ctx, l %fp, l ${text(a1)}, l ${pat_label.label}, l ${flg_label.label})`) emit_exc_check() continue } @@ -1830,6 +2359,16 @@ 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))} }`) + if (length(str_entries) > 0) { + lit_data = [] + si = 0 + while (si < length(str_entries)) { + push(lit_data, `l ${str_entries[si].label}`) + si = si + 1 + } + push(data_out, `export data $cell_lit_table = { ${text(lit_data, ", ")} }`) + } return { data: text(data_out, "\n"), diff --git a/source/qbe_helpers.c b/source/qbe_helpers.c index 31154a24..8d638a61 100644 --- a/source/qbe_helpers.c +++ b/source/qbe_helpers.c @@ -230,23 +230,188 @@ JSValue qbe_shift_shr(JSContext *ctx, JSValue a, JSValue b) { /* --- Property access --- */ -JSValue cell_rt_load_field(JSContext *ctx, JSValue obj, const char *name) { +/* Current module handle for active native dispatch. */ +static CELL_THREAD_LOCAL void *g_current_dl_handle = NULL; + +typedef struct { + void *dl_handle; + JSContext *ctx; + JSGCRef *vals; + int count; +} AOTLiteralPool; + +static CELL_THREAD_LOCAL AOTLiteralPool g_aot_lit_pool = {0}; + +static void aot_clear_lit_pool(void) { + if (g_aot_lit_pool.vals) { + if (g_aot_lit_pool.ctx) { + for (int i = 0; i < g_aot_lit_pool.count; i++) + JS_DeleteGCRef(g_aot_lit_pool.ctx, &g_aot_lit_pool.vals[i]); + } + free(g_aot_lit_pool.vals); + } + g_aot_lit_pool.dl_handle = NULL; + g_aot_lit_pool.ctx = NULL; + g_aot_lit_pool.vals = NULL; + g_aot_lit_pool.count = 0; +} + +static int aot_load_lit_pool(JSContext *ctx, void *dl_handle) { + aot_clear_lit_pool(); + g_aot_lit_pool.dl_handle = dl_handle; + g_aot_lit_pool.ctx = ctx; + if (!dl_handle) + return 1; + + int *count_ptr = (int *)dlsym(dl_handle, "cell_lit_count"); + const char **table_ptr = (const char **)dlsym(dl_handle, "cell_lit_table"); + int count = count_ptr ? *count_ptr : 0; + if (count <= 0 || !table_ptr) + return 1; + + g_aot_lit_pool.vals = (JSGCRef *)calloc((size_t)count, sizeof(JSGCRef)); + if (!g_aot_lit_pool.vals) { + JS_RaiseOOM(ctx); + return 0; + } + g_aot_lit_pool.count = 0; + + for (int i = 0; i < count; i++) { + const char *cstr = table_ptr[i] ? table_ptr[i] : ""; + JS_AddGCRef(ctx, &g_aot_lit_pool.vals[i]); + g_aot_lit_pool.count = i + 1; + g_aot_lit_pool.vals[i].val = js_key_new(ctx, cstr); + if (JS_IsException(g_aot_lit_pool.vals[i].val)) { + aot_clear_lit_pool(); + return 0; + } + } + return 1; +} + +static JSValue aot_lit_from_index(JSContext *ctx, int64_t lit_idx) { + if (lit_idx < 0) { + JS_RaiseDisrupt(ctx, "literal index out of range"); + return JS_EXCEPTION; + } + + if (g_aot_lit_pool.dl_handle != g_current_dl_handle || g_aot_lit_pool.ctx != ctx) { + if (!aot_load_lit_pool(ctx, g_current_dl_handle)) + return JS_EXCEPTION; + } + + if (lit_idx >= g_aot_lit_pool.count) { + JS_RaiseDisrupt(ctx, "literal index out of range"); + return JS_EXCEPTION; + } + return g_aot_lit_pool.vals[lit_idx].val; +} + +typedef struct { + const char *name; + JSValue key; +} AOTKeyCacheEntry; + +static CELL_THREAD_LOCAL JSContext *g_aot_key_cache_ctx = NULL; +static CELL_THREAD_LOCAL AOTKeyCacheEntry *g_aot_key_cache = NULL; +static CELL_THREAD_LOCAL int g_aot_key_cache_count = 0; +static CELL_THREAD_LOCAL int g_aot_key_cache_cap = 0; + +/* Convert a static C string to an interned JSValue key. + Uses a small thread-local cache keyed by C-string pointer to avoid + repeated UTF-8 decoding in hot property paths. */ +static JSValue aot_key_from_cstr(JSContext *ctx, const char *name) { + if (!name) + return JS_NULL; + + if (g_aot_key_cache_ctx != ctx) { + free(g_aot_key_cache); + g_aot_key_cache = NULL; + g_aot_key_cache_count = 0; + g_aot_key_cache_cap = 0; + g_aot_key_cache_ctx = ctx; + } + + for (int i = 0; i < g_aot_key_cache_count; i++) { + if (g_aot_key_cache[i].name == name) + return g_aot_key_cache[i].key; + } + + JSValue key = js_key_new(ctx, name); + if (JS_IsNull(key)) + return JS_RaiseDisrupt(ctx, "invalid property key"); + + if (g_aot_key_cache_count >= g_aot_key_cache_cap) { + int new_cap = g_aot_key_cache_cap ? (g_aot_key_cache_cap * 2) : 64; + AOTKeyCacheEntry *new_cache = + (AOTKeyCacheEntry *)realloc(g_aot_key_cache, (size_t)new_cap * sizeof(*new_cache)); + if (!new_cache) + return JS_RaiseOOM(ctx); + g_aot_key_cache = new_cache; + g_aot_key_cache_cap = new_cap; + } + + g_aot_key_cache[g_aot_key_cache_count].name = name; + g_aot_key_cache[g_aot_key_cache_count].key = key; + g_aot_key_cache_count++; + return key; +} + +static JSValue cell_rt_load_field_key(JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsFunction(obj)) { JS_RaiseDisrupt(ctx, "cannot read property of function"); return JS_EXCEPTION; } - return JS_GetPropertyStr(ctx, obj, name); + 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)) + return JS_EXCEPTION; + 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) { - return JS_GetPropertyStr(ctx, obj, name); + JSValue key = aot_key_from_cstr(ctx, name); + if (JS_IsException(key)) + return JS_EXCEPTION; + return JS_GetProperty(ctx, obj, key); } -void cell_rt_store_field(JSContext *ctx, JSValue val, JSValue obj, - const char *name) { - JS_SetPropertyStr(ctx, obj, name, val); +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); + if (JS_IsException(key)) + return 0; + return cell_rt_store_field_key(ctx, val, obj, key); +} + +JSValue cell_rt_access_lit(JSContext *ctx, int64_t lit_idx) { + return aot_lit_from_index(ctx, lit_idx); } JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) { @@ -255,16 +420,22 @@ JSValue cell_rt_load_dynamic(JSContext *ctx, JSValue obj, JSValue key) { return JS_GetProperty(ctx, obj, key); } -void cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj, - JSValue key) { +int cell_rt_store_dynamic(JSContext *ctx, JSValue val, JSValue obj, + JSValue key) { + int ret = 0; + JSValue nr = JS_NULL; if (JS_IsInt(key)) { - JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val); + nr = JS_SetPropertyNumber(ctx, obj, (uint32_t)JS_VALUE_GET_INT(key), val); + return JS_IsException(nr) ? 0 : 1; } else if (JS_IsArray(obj) && !JS_IsInt(key)) { JS_RaiseDisrupt(ctx, "array index must be a number"); + return 0; } else if (JS_IsBool(key) || JS_IsNull(key) || JS_IsArray(key) || JS_IsFunction(key)) { JS_RaiseDisrupt(ctx, "object key must be text"); + return 0; } else { - JS_SetProperty(ctx, obj, key, val); + ret = JS_SetProperty(ctx, obj, key, val); + return (ret < 0 || JS_HasException(ctx)) ? 0 : 1; } } @@ -274,12 +445,17 @@ JSValue cell_rt_load_index(JSContext *ctx, JSValue arr, JSValue idx) { return JS_GetProperty(ctx, arr, idx); } -void cell_rt_store_index(JSContext *ctx, JSValue val, JSValue arr, - JSValue 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)) - JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val); + nr = JS_SetPropertyNumber(ctx, arr, (uint32_t)JS_VALUE_GET_INT(idx), val); else - JS_SetProperty(ctx, arr, idx, val); + 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 --- */ @@ -294,6 +470,17 @@ void cell_rt_set_native_env(JSContext *ctx, JSValue env) { fprintf(stderr, "cell_rt_set_native_env: ERROR env not stone\n"); abort(); } + /* Drop module literal pool roots before switching native env/module. */ + aot_clear_lit_pool(); + + /* Native module boundary: clear per-thread key cache so stale keys + cannot survive across context/module lifetimes. */ + free(g_aot_key_cache); + g_aot_key_cache = NULL; + g_aot_key_cache_count = 0; + g_aot_key_cache_cap = 0; + g_aot_key_cache_ctx = ctx; + if (g_has_native_env) JS_DeleteGCRef(ctx, &g_native_env_ref); if (!JS_IsNull(env)) { @@ -305,10 +492,10 @@ void cell_rt_set_native_env(JSContext *ctx, JSValue env) { } } -JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) { +static JSValue cell_rt_get_intrinsic_key(JSContext *ctx, JSValue key) { /* Check native env first (runtime-provided functions like log) */ if (g_has_native_env) { - JSValue v = JS_GetPropertyStr(ctx, g_native_env_ref.val, name); + JSValue v = JS_GetProperty(ctx, g_native_env_ref.val, key); if (!JS_IsNull(v)) return v; } @@ -319,14 +506,28 @@ JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) { JSRecord *rec = (JSRecord *)chase(gobj); uint64_t mask = objhdr_cap56(rec->mist_hdr); for (uint64_t i = 1; i <= mask; i++) { - if (js_key_equal_str(rec->slots[i].key, name)) + if (js_key_equal(rec->slots[i].key, key)) return rec->slots[i].val; } } - JS_RaiseDisrupt(ctx, "'%s' is not defined", name); + JS_RaiseDisrupt(ctx, "name is not defined"); return JS_EXCEPTION; } +JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) { + JSValue key = aot_key_from_cstr(ctx, name); + if (JS_IsException(key)) + return JS_EXCEPTION; + return cell_rt_get_intrinsic_key(ctx, key); +} + +JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) { + JSValue key = aot_lit_from_index(ctx, lit_idx); + if (JS_IsException(key)) + return JS_EXCEPTION; + 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 @@ -436,6 +637,17 @@ JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) { return (JSValue *)frame->slots; } +/* Push an already-allocated frame onto the active AOT frame stack. */ +static int cell_rt_push_existing_frame(JSContext *ctx, JSValue frame_val) { + if (!ensure_aot_gc_ref_slot(ctx, g_aot_depth)) + return 0; + JSGCRef *ref = aot_gc_ref_at(g_aot_depth); + JS_AddGCRef(ctx, ref); + ref->val = frame_val; + g_aot_depth++; + return 1; +} + JSValue *cell_rt_refresh_fp(JSContext *ctx) { (void)ctx; if (g_aot_depth <= 0) { @@ -484,7 +696,7 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp); /* Set before executing a native module's cell_main — used by cell_rt_make_function to resolve fn_ptr via dlsym */ -static CELL_THREAD_LOCAL void *g_current_dl_handle = NULL; +/* g_current_dl_handle is defined near property/literal helpers. */ /* ============================================================ Dispatch loop — the core of native function execution. @@ -604,8 +816,9 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JS_PushGCRef(ctx, &callee_ref); callee_ref.val = callee_frame_val; JSFrameRegister *callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val); - int callee_argc = (int)objhdr_cap56(callee_fr->header); - callee_argc = (callee_argc >= 2) ? callee_argc - 2 : 0; + int callee_argc = JS_VALUE_GET_INT(callee_fr->address); + if (callee_argc < 0) + callee_argc = 0; JSValue callee_fn_val = callee_fr->function; if (!JS_IsFunction(callee_fn_val)) { @@ -625,47 +838,44 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, if (callee_fn->kind == JS_FUNC_KIND_NATIVE) { /* Native-to-native call — no C stack growth */ cell_compiled_fn callee_ptr = (cell_compiled_fn)callee_fn->u.native.fn_ptr; - int callee_slots = callee_fn->u.native.nr_slots; if (pending_is_tail) { - /* Tail call: replace frame instead of mutating in place. - In-place reuse breaks closures that captured the caller frame. */ + /* Tail call: replace current frame with the prepared callee frame. */ JSValue saved_caller = frame->caller; - int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length; - if (cc < 0) cc = callee_argc; /* Pop old frame */ cell_rt_leave_frame(ctx); - /* Push new right-sized frame */ - JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots); - if (!new_fp) { + callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val); + callee_fn_val = callee_fn_ref.val; + callee_fr->function = callee_fn_val; + callee_fr->caller = saved_caller; + callee_fr->address = JS_NewInt32(ctx, 0); + + if (!cell_rt_push_existing_frame(ctx, callee_ref.val)) { JS_PopGCRef(ctx, &callee_fn_ref); JS_PopGCRef(ctx, &callee_ref); RETURN_DISPATCH(JS_EXCEPTION); } - callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val); - JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots)); - callee_fn_val = callee_fn_ref.val; - new_frame->function = callee_fn_val; - new_frame->caller = saved_caller; - new_frame->slots[0] = callee_fr->slots[0]; - for (int i = 0; i < cc && i < callee_slots - 1; i++) - new_frame->slots[1 + i] = callee_fr->slots[1 + i]; - frame = new_frame; - fp = new_fp; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(g_aot_depth - 1)->val); + fp = (JSValue *)frame->slots; fn = callee_ptr; } else { - /* Regular call: push new frame, link caller */ + /* Regular call: link caller and push prepared callee frame. */ int ret_info = JS_VALUE_GET_INT(frame->address); int resume_seg = ret_info >> 16; int ret_slot = ret_info & 0xFFFF; - int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length; - if (cc < 0) cc = callee_argc; + /* Save return address in caller */ + frame->address = JS_NewInt32(ctx, (resume_seg << 16) | ret_slot); - JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots); - if (!new_fp) { + callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val); + callee_fn_val = callee_fn_ref.val; + callee_fr->function = callee_fn_val; + callee_fr->caller = JS_MKPTR(frame); + callee_fr->address = JS_NewInt32(ctx, 0); + + if (!cell_rt_push_existing_frame(ctx, callee_ref.val)) { /* Resume caller with exception pending */ frame_val = aot_gc_ref_at(g_aot_depth - 1)->val; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); @@ -676,29 +886,8 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JS_PopGCRef(ctx, &callee_ref); continue; } - callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_ref.val); - - /* Re-derive caller frame after alloc */ - if (g_aot_depth <= 1) { - fprintf(stderr, "[BUG] native dispatch bad depth while linking caller: %d\n", g_aot_depth); - abort(); - } - frame_val = aot_gc_ref_at(g_aot_depth - 2)->val; - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); - - JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots)); - callee_fn_val = callee_fn_ref.val; - new_frame->function = callee_fn_val; - new_frame->caller = JS_MKPTR(frame); - new_frame->slots[0] = callee_fr->slots[0]; - for (int i = 0; i < cc && i < callee_slots - 1; i++) - new_frame->slots[1 + i] = callee_fr->slots[1 + i]; - - /* Save return address in caller */ - frame->address = JS_NewInt32(ctx, (resume_seg << 16) | ret_slot); - - frame = new_frame; - fp = new_fp; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(aot_gc_ref_at(g_aot_depth - 1)->val); + fp = (JSValue *)frame->slots; fn = callee_ptr; } } else { @@ -719,11 +908,16 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, if (JS_IsException(ret)) { /* Non-native callee threw — resume caller with exception pending. - The caller's generated code checks JS_HasException at resume. */ + Tag the pending return slot with JS_EXCEPTION so generated code + can branch without an extra JS_HasException C call. */ if (!JS_HasException(ctx)) JS_Disrupt(ctx); + int ret_info = JS_VALUE_GET_INT(frame->address); + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) + fp[ret_slot] = JS_EXCEPTION; /* fn and fp still point to the calling native function's frame. - Just resume it — it will detect the exception. */ + Just resume it — it will detect JS_EXCEPTION in the return slot. */ JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function); fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr; JS_PopGCRef(ctx, &callee_ref); @@ -789,10 +983,14 @@ JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, RETURN_DISPATCH(JS_EXCEPTION); } - /* Resume caller — it will check JS_HasException and branch to handler */ + /* Resume caller and tag the return slot with JS_EXCEPTION. */ frame_val = aot_gc_ref_at(g_aot_depth - 1)->val; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val); fp = (JSValue *)frame->slots; + 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_caller_fn = JS_VALUE_GET_FUNCTION(frame->function); fn = (cell_compiled_fn)exc_caller_fn->u.native.fn_ptr; @@ -859,9 +1057,13 @@ JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) { return JS_EXCEPTION; } int nr_slots = (int)nargs + 2; + JSFunction *f = JS_VALUE_GET_FUNCTION(fn); + if (f->kind == JS_FUNC_KIND_NATIVE && f->u.native.nr_slots > nr_slots) + nr_slots = f->u.native.nr_slots; JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots); if (!new_frame) return JS_EXCEPTION; new_frame->function = fn; + new_frame->address = JS_NewInt32(ctx, (int)nargs); return JS_MKPTR(new_frame); } @@ -875,8 +1077,8 @@ void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) { 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 nr_slots = (int)objhdr_cap56(fr->header); - int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0; + 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)) { @@ -933,14 +1135,27 @@ JSValue cell_rt_delete(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 = JS_NewString(ctx, name); +static JSValue cell_rt_delete_key(JSContext *ctx, JSValue obj, JSValue key) { int ret = JS_DeleteProperty(ctx, obj, key); if (ret < 0) return JS_EXCEPTION; 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)) + return JS_EXCEPTION; + return cell_rt_delete_key(ctx, obj, key); +} + /* --- Typeof --- */ JSValue cell_rt_typeof(JSContext *ctx, JSValue val) {