// qbe_emit.cm — mcode IR → QBE IL compiler // Takes mcode IR (from mcode.cm) and uses qbe.cm macros to produce // a complete QBE IL program ready for the qbe compiler. // qbe module is passed via env as 'qbe' // ============================================================ // QBE IL helper function generation // Generates helper functions that are defined once and called // from each operation site, reducing code duplication. // ============================================================ var emit_helpers = function(qbe) { var h = [] var lb = "{" // --- Slot access IL fragments --- var sr = function(name, slot) { return ` %${name}.o =l shl ${slot}, 3 %${name}.p =l add %fp, %${name}.o %${name} =l loadl %${name}.p` } var sw = function(name, fp_var, slot, val) { return ` %${name}.o =l shl ${slot}, 3 %${name}.p =l add ${fp_var}, %${name}.o storel ${val}, %${name}.p` } // --- Allocating tail: refresh fp, write result, return fp --- var alloc_tail = function(result_var) { return ` %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @ok, @exc @ok ${sw("w", "%fp2", "%dest", result_var)} ret %fp2 @exc ret 0` } // ============================================================ // Category A: Pure QBE helpers (no C calls) // ============================================================ // is_record: pointer + header type check (OBJ_RECORD=3), chase forwards h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) ${lb} @entry ${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) ${lb} @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) ${lb} @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 }` // eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value) var tol_ops = ["eq_tol", "ne_tol"] var i = 0 while (i < length(tol_ops)) { h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} ${sr("c", "%s3")} %r =l call $cell_rt_${tol_ops[i]}(l %ctx, l %a, l %b, l %c) ${sw("w", "%fp", "%dest", "%r")} ret }` i = i + 1 } // not: inline truthiness (no JS_ToBool call) h[] = `export function $__not_ss(l %ctx, l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %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 ${sw("w", "%fp", "%dest", "%r")} ret }` // Bitwise unary/binary ops: mirror MACH mcode op behavior via JS_ToInt32 coercion. h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) ${lb} @entry ${sr("a", "%src")} %r =l call $qbe_bnot(l %ctx, l %a) ${sw("w", "%fp", "%dest", "%r")} ret }` h[] = `export function $__band_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $qbe_bitwise_and(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` h[] = `export function $__bor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $qbe_bitwise_or(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` h[] = `export function $__bxor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %r =l call $qbe_bitwise_xor(l %ctx, l %a, l %b) ${sw("w", "%fp", "%dest", "%r")} ret }` // ============================================================ // Category C: Allocating helpers (return fp or 0) // ============================================================ // concat allocates; runtime helper mirrors MACH self-assign fast path h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb} @entry ${sr("a", "%s1")} ${sr("b", "%s2")} %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) ${lb} @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 @exc ret 0 }` // 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) ${lb} @entry ${sr("a", "%obj_slot")} %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) 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")} %ptag =l and %a, 7 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_ptr, @fallback @arr_ptr %arr_ptr =l and %a, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_chk @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_chk %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @arr_index, @fallback @arr_index %idx_tag =l and %b, 1 %idx_is_int =w ceql %idx_tag, 0 jnz %idx_is_int, @idx_ok, @ret_null @idx_ok %idx_l =l sar %b, 1 %idx_w =w copy %idx_l %idx_neg =w csltw %idx_w, 0 jnz %idx_neg, @ret_null, @arr_len @arr_len %len_p =l add %arr_ptr, 8 %len_l =l loadl %len_p %len_w =w copy %len_l %in =w csltw %idx_w, %len_w jnz %in, @load, @ret_null @load %idx_off_l =l extsw %idx_w %idx_off_l =l shl %idx_off_l, 3 %vals_p =l add %arr_ptr, 16 %elem_p =l add %vals_p, %idx_off_l %r =l loadl %elem_p ${sw("w", "%fp", "%dest", "%r")} ret %fp @ret_null ${sw("w", "%fp", "%dest", text(qbe.js_null))} ret %fp @fallback %r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b) %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) 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")} %idx_tag =l and %b, 1 %idx_is_int =w ceql %idx_tag, 0 jnz %idx_is_int, @idx_ok, @ret_null @idx_ok %idx_l =l sar %b, 1 %idx_w =w copy %idx_l %idx_neg =w csltw %idx_w, 0 jnz %idx_neg, @ret_null, @arr_init @arr_init %ptag =l and %a, 7 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_ptr_ok, @ret_null @arr_ptr_ok %arr_ptr =l and %a, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_chk @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_chk %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @arr_len, @ret_null @arr_len %len_p =l add %arr_ptr, 8 %len_l =l loadl %len_p %len_w =w copy %len_l %in =w csltw %idx_w, %len_w jnz %in, @load, @ret_null @load %idx_off_l =l extsw %idx_w %idx_off_l =l shl %idx_off_l, 3 %vals_p =l add %arr_ptr, 16 %elem_p =l add %vals_p, %idx_off_l %r =l loadl %elem_p ${sw("w", "%fp", "%dest", "%r")} ret %fp @ret_null ${sw("w", "%fp", "%dest", text(qbe.js_null))} ret %fp }` // 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) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} %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 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")} ${sr("c", "%key_slot")} %ptag =l and %a, 7 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_ptr, @fallback @arr_ptr %arr_ptr =l and %a, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_chk @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_chk %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @arr_key_chk, @fallback @arr_key_chk %idx_tag =l and %c, 1 %idx_is_int =w ceql %idx_tag, 0 jnz %idx_is_int, @arr_store, @bad @arr_store %fp2 =l call $__store_index_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %key_slot) ret %fp2 @fallback %ok =w call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c) jnz %ok, @ok, @exc @ok ret %fp @bad call $cell_rt_disrupt(l %ctx) @exc ret 0 }` // 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) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%val_slot")} ${sr("c", "%idx_slot")} %idx_tag =l and %c, 1 %idx_is_int =w ceql %idx_tag, 0 jnz %idx_is_int, @idx_ok, @bad @idx_ok %idx_l =l sar %c, 1 %idx_w =w copy %idx_l %idx_neg =w csltw %idx_w, 0 jnz %idx_neg, @bad, @arr_init @arr_init %ptag =l and %a, 7 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_ptr_ok, @bad @arr_ptr_ok %arr_val =l copy %a %arr_ptr =l and %arr_val, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_chk @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_chk %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @stone_chk, @bad @stone_chk %arr_stone =l and %arr_hdr, 8 %arr_is_stone =w cnel %arr_stone, 0 jnz %arr_is_stone, @bad, @lens @lens %len_p =l add %arr_ptr, 8 %len_l =l loadl %len_p %len_w =w copy %len_l %cap_l =l shr %arr_hdr, 8 %cap_w =w copy %cap_l %need_grow =w csgew %idx_w, %cap_w jnz %need_grow, @grow_init, @set_item @grow_init %new_cap_w =w copy %cap_w %cap_zero =w ceqw %new_cap_w, 0 jnz %cap_zero, @grow_cap0, @grow_check @grow_cap0 %new_cap_w =w copy 2 jmp @grow_check @grow_loop %new_cap_w =w shl %new_cap_w, 1 %new_cap_neg =w csltw %new_cap_w, 0 jnz %new_cap_neg, @bad, @grow_check @grow_check %need_more =w cslew %new_cap_w, %idx_w jnz %need_more, @grow_loop, @grow_alloc @grow_alloc %new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w) %new_exc =w ceql %new_arr, 15 jnz %new_exc, @exc, @grow_refresh @grow_refresh %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @grow_reload, @exc @grow_reload %fp =l copy %fp2 ${sr("ga", "%obj_slot")} ${sr("gb", "%val_slot")} ${sr("gc", "%idx_slot")} %a =l copy %ga %b =l copy %gb %c =l copy %gc %arr_val =l copy %a %arr_ptr =l and %arr_val, -8 %arr_hdr =l loadl %arr_ptr @grow_arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok @grow_arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @grow_arr_chase @grow_arr_ok %grow_arr_is_array =w ceql %arr_ty, 0 jnz %grow_arr_is_array, @grow_arr_type_ok, @bad @grow_arr_type_ok %old_cap_l =l shr %arr_hdr, 8 %old_len_p =l add %arr_ptr, 8 %old_len_l =l loadl %old_len_p %old_len_w =w copy %old_len_l %new_ptr =l and %new_arr, -8 %old_vals =l add %arr_ptr, 16 %new_vals =l add %new_ptr, 16 %i_w =w copy 0 @copy_cond %copy_more =w csltw %i_w, %old_len_w jnz %copy_more, @copy_body, @copy_done @copy_body %i_l =l extsw %i_w %i_off =l shl %i_l, 3 %old_ep =l add %old_vals, %i_off %new_ep =l add %new_vals, %i_off %ev =l loadl %old_ep %ev_is_self =w ceql %ev, %arr_val jnz %ev_is_self, @copy_self, @copy_store @copy_self storel %new_arr, %new_ep jmp @copy_next @copy_store storel %ev, %new_ep @copy_next %i_w =w add %i_w, 1 jmp @copy_cond @copy_done storel %old_len_l, %old_len_p %old_size =l shl %old_cap_l, 3 %old_size =l add %old_size, 16 %fwd =l shl %new_ptr, 3 %fwd =l or %fwd, 7 storel %fwd, %arr_ptr %arr_size_p =l add %arr_ptr, 8 storel %old_size, %arr_size_p %obj_slot_o =l shl %obj_slot, 3 %obj_slot_p =l add %fp2, %obj_slot_o storel %new_arr, %obj_slot_p %arr_val =l copy %new_arr %arr_ptr =l copy %new_ptr %arr_hdr =l loadl %arr_ptr %len_p =l add %arr_ptr, 8 storel %old_len_l, %len_p %len_l =l copy %old_len_l %len_w =w copy %old_len_w @set_item %need_len =w csgew %idx_w, %len_w jnz %need_len, @bump_len, @store_item @bump_len %next_len_w =w add %idx_w, 1 %next_len_l =l extsw %next_len_w storel %next_len_l, %len_p @store_item %idx2_l =l extsw %idx_w %idx2_off =l shl %idx2_l, 3 %vals_p =l add %arr_ptr, 16 %item_p =l add %vals_p, %idx2_off storel %b, %item_p ret %fp @bad call $cell_rt_disrupt(l %ctx) @exc ret 0 }` // frame(ctx, fp, dest, fn_slot, 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) ${alloc_tail("%r")} }` // apply(ctx, fp, dest, fn_slot, arg_slot) h[] = `export function l $__apply_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %arg_slot) ${lb} @entry ${sr("a", "%fn_slot")} ${sr("b", "%arg_slot")} %r =l call $cell_rt_apply(l %ctx, l %a, l %b) ${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) ${lb} @entry ${sr("a", "%fn_slot")} %r =l call $cell_rt_goframe(l %ctx, l %a, l %nargs) ${alloc_tail("%r")} }` // 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) ${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) ${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) ${lb} @entry %r =l call $JS_NewArray(l %ctx) ${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) ${lb} @entry %r =l call $qbe_new_float64(l %ctx, d %val) ${sw("w", "%fp", "%dest", "%r")} ret %fp }` // access_env(ctx, fp, dest, lit_idx) — read env from frame->function->env_record h[] = `export function l $__access_env_ss(l %ctx, l %fp, l %dest, l %lit_idx) ${lb} @entry %fn_p =l sub %fp, 24 %fn =l loadl %fn_p %fn_ptr =l and %fn, -8 %env_p =l add %fn_ptr, 32 %env =l loadl %env_p %r =l call $cell_rt_access_env(l %ctx, l %env, 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) ${lb} @entry ${sr("a", "%arr_slot")} ${sr("b", "%val_slot")} %ptag =l and %a, 7 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_init, @bad @arr_init %arr_val =l copy %a %arr_ptr =l and %arr_val, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_ok @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_ok %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @arr_type_ok, @bad @arr_type_ok %arr_stone =l and %arr_hdr, 8 %arr_is_stone =w cnel %arr_stone, 0 jnz %arr_is_stone, @bad, @lens @lens %len_p =l add %arr_ptr, 8 %len_l =l loadl %len_p %len_w =w copy %len_l %cap_l =l shr %arr_hdr, 8 %cap_w =w copy %cap_l %need_grow =w csgew %len_w, %cap_w jnz %need_grow, @grow, @store_push @grow %new_cap_w =w copy %cap_w %cap_zero =w ceqw %new_cap_w, 0 jnz %cap_zero, @grow_cap0, @grow_dbl @grow_cap0 %new_cap_w =w copy 2 jmp @grow_alloc @grow_dbl %new_cap_w =w shl %new_cap_w, 1 %new_cap_neg =w csltw %new_cap_w, 0 jnz %new_cap_neg, @bad, @grow_alloc @grow_alloc %new_arr =l call $JS_NewArrayCap(l %ctx, w %new_cap_w) %new_exc =w ceql %new_arr, 15 jnz %new_exc, @exc, @grow_refresh @grow_refresh %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) jnz %fp2, @grow_reload, @exc @grow_reload %fp =l copy %fp2 ${sr("ga", "%arr_slot")} ${sr("gb", "%val_slot")} %a =l copy %ga %b =l copy %gb %arr_val =l copy %a %arr_ptr =l and %arr_val, -8 %arr_hdr =l loadl %arr_ptr @grow_arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @grow_arr_follow, @grow_arr_ok @grow_arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @grow_arr_chase @grow_arr_ok %grow_arr_is_array =w ceql %arr_ty, 0 jnz %grow_arr_is_array, @grow_arr_type_ok, @bad @grow_arr_type_ok %old_cap_l =l shr %arr_hdr, 8 %old_len_p =l add %arr_ptr, 8 %old_len_l =l loadl %old_len_p %old_len_w =w copy %old_len_l %new_ptr =l and %new_arr, -8 %old_vals =l add %arr_ptr, 16 %new_vals =l add %new_ptr, 16 %i_w =w copy 0 @copy_cond %copy_more =w csltw %i_w, %old_len_w jnz %copy_more, @copy_body, @copy_done @copy_body %i_l =l extsw %i_w %i_off =l shl %i_l, 3 %old_ep =l add %old_vals, %i_off %new_ep =l add %new_vals, %i_off %ev =l loadl %old_ep %ev_is_self =w ceql %ev, %arr_val jnz %ev_is_self, @copy_self, @copy_store @copy_self storel %new_arr, %new_ep jmp @copy_next @copy_store storel %ev, %new_ep @copy_next %i_w =w add %i_w, 1 jmp @copy_cond @copy_done storel %old_len_l, %old_len_p %old_size =l shl %old_cap_l, 3 %old_size =l add %old_size, 16 %fwd =l shl %new_ptr, 3 %fwd =l or %fwd, 7 storel %fwd, %arr_ptr %arr_size_p =l add %arr_ptr, 8 storel %old_size, %arr_size_p %arr_slot_o =l shl %arr_slot, 3 %arr_slot_p =l add %fp2, %arr_slot_o storel %new_arr, %arr_slot_p %arr_val =l copy %new_arr %arr_ptr =l copy %new_ptr %arr_hdr =l loadl %arr_ptr %len_p =l add %arr_ptr, 8 storel %old_len_l, %len_p %len_l =l copy %old_len_l %len_w =w copy %old_len_w @store_push %idx_l =l extsw %len_w %idx_off =l shl %idx_l, 3 %vals_p =l add %arr_ptr, 16 %item_p =l add %vals_p, %idx_off storel %b, %item_p %next_len_w =w add %len_w, 1 %next_len_l =l extsw %next_len_w storel %next_len_l, %len_p ret %fp @bad call $cell_rt_disrupt(l %ctx) @exc ret 0 }` // pop(ctx, fp, dest, 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 %is_ptr =w ceql %ptag, 1 jnz %is_ptr, @arr_init, @bad @arr_init %arr_ptr =l and %a, -8 %arr_hdr =l loadl %arr_ptr @arr_chase %arr_ty =l and %arr_hdr, 7 %arr_is_fwd =w ceql %arr_ty, 7 jnz %arr_is_fwd, @arr_follow, @arr_ok @arr_follow %arr_ptr =l shr %arr_hdr, 3 %arr_hdr =l loadl %arr_ptr jmp @arr_chase @arr_ok %arr_is_array =w ceql %arr_ty, 0 jnz %arr_is_array, @arr_type_ok, @bad @arr_type_ok %arr_stone =l and %arr_hdr, 8 %arr_is_stone =w cnel %arr_stone, 0 jnz %arr_is_stone, @bad, @len_chk @len_chk %len_p =l add %arr_ptr, 8 %len_l =l loadl %len_p %len_w =w copy %len_l %empty =w ceqw %len_w, 0 jnz %empty, @ret_null, @do_pop @do_pop %last_w =w sub %len_w, 1 %last_l =l extsw %last_w %last_off =l shl %last_l, 3 %vals_p =l add %arr_ptr, 16 %item_p =l add %vals_p, %last_off %r =l loadl %item_p storel ${text(qbe.js_null)}, %item_p %new_len_l =l extsw %last_w storel %new_len_l, %len_p ${sw("w", "%fp", "%dest", "%r")} ret %fp @ret_null ${sw("w", "%fp", "%dest", text(qbe.js_null))} ret %fp @bad call $cell_rt_disrupt(l %ctx) ret 0 }` // length(ctx, fp, dest, 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) ${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) ${lb} @entry ${sr("a", "%obj_slot")} %r =l call $cell_rt_delete_lit(l %ctx, l %a, l %lit_idx) ${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) ${lb} @entry ${sr("a", "%obj_slot")} ${sr("b", "%key_slot")} %r =l call $cell_rt_delete(l %ctx, l %a, l %b) ${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) ${lb} @entry ${sr("a", "%key_slot")} ${sr("b", "%obj_slot")} %r =l call $cell_rt_in(l %ctx, l %a, l %b) ${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) ${lb} @entry %r =l call $cell_rt_regexp(l %ctx, l %pat, l %flg) ${alloc_tail("%r")} }` return h } 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 // ============================================================ var emit = function(s) { push(out, s) } var fresh = function() { uid = uid + 1 return "u" + text(uid) } var sanitize = function(lbl) { var r = replace(lbl, ".", "_") r = replace(r, "-", "_") r = replace(r, " ", "_") r = replace(r, "/", "_") r = replace(r, "<", "") r = replace(r, ">", "") r = replace(r, "(", "") r = replace(r, ")", "") return r } // ============================================================ // String interning — emit data section entries // ============================================================ var intern_str = function(val) { if (str_table[val] != null) return str_table[val] var label = "$d_str_" + text(str_id) str_id = str_id + 1 var escaped = replace(val, "\\", "\\\\") escaped = replace(escaped, "\"", "\\\"") escaped = replace(escaped, "\n", "\\n") escaped = replace(escaped, "\r", "\\r") escaped = replace(escaped, "\t", "\\t") var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }' push(data_out, line) var entry = { label: label, idx: length(str_entries) } push(str_entries, entry) str_table[val] = entry return entry } // ============================================================ // Extract property name from mcode operand // ============================================================ // prop_name inlined at each call site — closures have bytecode bugs with // early returns so we extract the property name inline via pn variable // ============================================================ // Compile one function's instructions // ============================================================ var compile_fn = function(fn, fn_idx, is_main) { var instrs = fn.instructions 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) name = sanitize(name) var i = 0 var instr = null var op = null var a1 = null var a2 = null var a3 = null var a4 = null var p = null var pn = null var sl = null var lbl = null var v = null var lhs = null var rhs = null var pat_label = null var flg_label = null var in_handler = false var fn_arity = 0 var fn_nr_slots = 0 var invoke_count = 0 var si = 0 var scan = null var scan_op = null var label_pos = {} var instr_idx = 0 var has_invokes = false var seg_counter = 0 var ri = 0 var resume_val = 0 var j_lbl = null var j_idx = null var jt_lbl = null var jt_idx = null var jt_backedge = false var jf_lbl = null var jf_idx = null var jf_backedge = false var jnn_lbl = null var jnn_idx = null var jnn_backedge = false var wt_lbl = null var wt_idx = null var wt_backedge = false var wf_lbl = null var wf_idx = null var wf_backedge = false var jn_lbl = null var jn_idx = null var jn_backedge = false var je_lbl = null var je_idx = null var je_backedge = false var truthy = null var lhs_d = null var rhs_d = null var peek1 = null var peek2 = null var peek3 = null var peek4 = null var peek5 = null var text_frame_slot = 0 var text_this_slot = 0 var text_arg_slot = 0 var text_dest_slot = 0 var cmp_id = 0 var depth = 0 var d = 0 var handler = null // Pre-scan: count invoke/tail_invoke points to assign segment numbers. // Must skip dead code (instructions after terminators) the same way // the main emission loop does, otherwise we create jump table entries // for segments that never get emitted. var scan_dead = false si = 0 while (si < length(instrs)) { scan = instrs[si] si = si + 1 if (is_text(scan)) { // Skip optimizer nop pseudo-labels entirely. if (starts_with(scan, "_nop_")) continue label_pos[sanitize(scan)] = si - 1 // Real labels reset dead code state. scan_dead = false continue } if (scan_dead) continue if (!is_array(scan)) continue scan_op = scan[0] // 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") { 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") { text_frame_slot = peek1[1] text_this_slot = peek2[1] if (peek3[1] == text_frame_slot && peek3[2] == 0 && peek3[3] == text_this_slot && peek4[1] == text_frame_slot && peek4[2] == 1 && peek5[1] == text_frame_slot && peek5[2] == text_this_slot) { si = si + 5 continue } } } } if (scan_op == "invoke" || scan_op == "tail_invoke") { invoke_count = invoke_count + 1 } // Track terminators — same set as in the main loop if (scan_op == "return" || scan_op == "jump" || scan_op == "goinvoke" || scan_op == "disrupt") { scan_dead = true } } has_invokes = invoke_count > 0 // Function signature: (ctx, frame_ptr) → JSValue emit(`export function l $${name}(l %ctx, l %fp) {`) emit("@entry") // Resume dispatch: if this function has invoke points, read the segment // number from frame->address and jump to the right resume point. // frame->address is at fp - 8 (last field before slots[]). if (has_invokes) { emit(" %addr_ptr =l sub %fp, 8") emit(" %addr_raw =l loadl %addr_ptr") // address is stored as JS_NewInt32 tagged value: n << 1 emit(" %addr =l sar %addr_raw, 1") emit(" %resume =l shr %addr, 16") emit(` jnz %resume, @_rcheck1, @_seg0`) ri = 1 while (ri <= invoke_count) { emit(`@_rcheck${text(ri)}`) emit(` %_rc${text(ri)} =w ceql %resume, ${text(ri)}`) if (ri < invoke_count) { emit(` jnz %_rc${text(ri)}, @_seg${text(ri)}, @_rcheck${text(ri + 1)}`) } else { // Last check — if no match, fall through to seg0 emit(` jnz %_rc${text(ri)}, @_seg${text(ri)}, @_seg0`) } ri = ri + 1 } emit("@_seg0") } // GC-safe slot access: every read/write goes through frame memory. // %fp may become stale after GC-triggering calls — use refresh_fp(). var s_read = function(slot) { var t = fresh() emit(` %${t} =l add %fp, ${text(slot * 8)}`) emit(` %${t}v =l loadl %${t}`) return `%${t}v` } var s_write = function(slot, val) { var t = fresh() var sv = val if (!starts_with(val, "%")) { sv = `%${t}c` emit(` ${sv} =l copy ${val}`) } emit(` %${t} =l add %fp, ${text(slot * 8)}`) emit(` storel ${sv}, %${t}`) } var needs_exc_ret = false var refresh_fp = function() { emit(` %fp =l call $cell_rt_refresh_fp_checked(l %ctx)`) var exc = fresh() emit(` %${exc} =w ceql %fp, 0`) if (has_handler && !in_handler) { emit(` jnz %${exc}, @disruption_handler, @${exc}_ok`) } else { needs_exc_ret = true emit(` jnz %${exc}, @_exc_ret, @${exc}_ok`) } emit(`@${exc}_ok`) } // Exception check after allocating helper call (helper returns fp or 0) var emit_exc_check = function() { var lbl = fresh() if (has_handler && !in_handler) { emit(` jnz %fp, @${lbl}_ok, @disruption_handler`) } else { needs_exc_ret = true emit(` jnz %fp, @${lbl}_ok, @_exc_ret`) } emit(`@${lbl}_ok`) } // Poll pause/interrupt state on taken backward jumps. var emit_backedge_branch = function(target_label) { emit(` jmp @${target_label}`) } // 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` } // 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` } // --- Opcode handlers (record dispatch) --- var handlers = {} handlers["int"] = function() { s_write(a1, text(a2 * 2)) } handlers["null"] = function() { s_write(a1, text(qbe.js_null)) } handlers["true"] = function() { s_write(a1, text(qbe.js_true)) } handlers["false"] = function() { s_write(a1, text(qbe.js_false)) } handlers["access"] = function() { // Peephole: inline `text(x)` intrinsic call sequence // access text; frame; null this; setarg 0 this; setarg 1 x; invoke if (is_object(a2) && a2.make == "intrinsic" && a2.name == "text") { 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") { text_frame_slot = peek1[1] text_this_slot = peek2[1] if (peek3[1] == text_frame_slot && peek3[2] == 0 && peek3[3] == text_this_slot && peek4[1] == text_frame_slot && peek4[2] == 1 && peek5[1] == text_frame_slot && peek5[2] == text_this_slot) { text_arg_slot = peek4[3] text_dest_slot = peek5[2] v = s_read(text_arg_slot) p = fresh() emit(` %${p}_r =l call $JS_CellText(l %ctx, l ${v})`) refresh_fp() s_write(text_dest_slot, `%${p}_r`) i = instr_idx + 6 return } } } } if (is_number(a2)) { if (is_integer(a2)) { s_write(a1, text(a2 * 2)) } else { emit(` %fp =l call $__new_float64_ss(l %ctx, l %fp, l ${text(a1)}, d d_${text(a2)})`) emit_exc_check() } } else if (is_text(a2)) { sl = intern_str(a2) 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 $__access_env_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)) { s_write(a1, text(a2.number * 2)) } else if (a2.number != null) { emit(` %fp =l call $__new_float64_ss(l %ctx, l %fp, l ${text(a1)}, d d_${text(a2.number)})`) emit_exc_check() } else { s_write(a1, text(qbe.js_null)) } } else if (a2.kind == "text") { sl = intern_str(a2.value) 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)) } else if (a2.kind == "false") { s_write(a1, text(qbe.js_false)) } else if (a2.kind == "null") { s_write(a1, text(qbe.js_null)) } else { s_write(a1, text(qbe.js_null)) } } else { s_write(a1, text(qbe.js_null)) } } handlers.move = function() { v = s_read(a2) s_write(a1, v) } handlers.add = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() emit(` %${p}_a_tag =l and ${lhs}, 1`) emit(` %${p}_b_tag =l and ${rhs}, 1`) emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`) emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`) emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`) emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`) emit(`@${p}_int`) emit(` %${p}_ai =l sar ${lhs}, 1`) emit(` %${p}_bi =l sar ${rhs}, 1`) emit(` %${p}_sum =l add %${p}_ai, %${p}_bi`) emit(` %${p}_sumw =w copy %${p}_sum`) emit(` %${p}_sumext =l extsw %${p}_sumw`) emit(` %${p}_sum_ok =w ceql %${p}_sumext, %${p}_sum`) emit(` jnz %${p}_sum_ok, @${p}_int_store, @${p}_slow`) emit(`@${p}_int_store`) emit(` %${p}_tag =l shl %${p}_sum, 1`) s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) 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(`@${p}_done`) } handlers.subtract = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() emit(` %${p}_a_tag =l and ${lhs}, 1`) emit(` %${p}_b_tag =l and ${rhs}, 1`) emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`) emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`) emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`) emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`) emit(`@${p}_int`) emit(` %${p}_ai =l sar ${lhs}, 1`) emit(` %${p}_bi =l sar ${rhs}, 1`) emit(` %${p}_diff =l sub %${p}_ai, %${p}_bi`) emit(` %${p}_diffw =w copy %${p}_diff`) emit(` %${p}_diffext =l extsw %${p}_diffw`) emit(` %${p}_diff_ok =w ceql %${p}_diffext, %${p}_diff`) emit(` jnz %${p}_diff_ok, @${p}_int_store, @${p}_slow`) emit(`@${p}_int_store`) emit(` %${p}_tag =l shl %${p}_diff, 1`) s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) 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`) } handlers.multiply = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() emit(` %${p}_a_tag =l and ${lhs}, 1`) emit(` %${p}_b_tag =l and ${rhs}, 1`) emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`) emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`) emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`) emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`) emit(`@${p}_int`) emit(` %${p}_ai =l sar ${lhs}, 1`) emit(` %${p}_bi =l sar ${rhs}, 1`) emit(` %${p}_prod =l mul %${p}_ai, %${p}_bi`) emit(` %${p}_prodw =w copy %${p}_prod`) emit(` %${p}_prodext =l extsw %${p}_prodw`) emit(` %${p}_prod_ok =w ceql %${p}_prodext, %${p}_prod`) emit(` jnz %${p}_prod_ok, @${p}_int_store, @${p}_slow`) emit(`@${p}_int_store`) emit(` %${p}_tag =l shl %${p}_prod, 1`) s_write(a1, `%${p}_tag`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) 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`) } handlers.divide = function() { 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`) } handlers.modulo = function() { 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}_lhs_nan =w cned ${lhs_d}, ${lhs_d}`) emit(` %${p}_rhs_nan =w cned ${rhs_d}, ${rhs_d}`) emit(` %${p}_has_nan =w or %${p}_lhs_nan, %${p}_rhs_nan`) emit(` jnz %${p}_has_nan, @${p}_bad, @${p}_chk0`) emit(`@${p}_chk0`) emit(` %${p}_rhs0 =w ceqd ${rhs_d}, d_0.0`) emit(` jnz %${p}_rhs0, @${p}_bad, @${p}_calc`) emit(`@${p}_calc`) emit(` %${p}_q =d div ${lhs_d}, ${rhs_d}`) emit(` %${p}_qf =d call $floor(d %${p}_q)`) emit(` %${p}_m =d mul ${rhs_d}, %${p}_qf`) emit(` %${p}_rd =d sub ${lhs_d}, %${p}_m`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) emit(` jmp @${p}_done`) emit(`@${p}_bad`) s_write(a1, text(qbe.js_null)) emit(`@${p}_done`) } handlers.remainder = function() { 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}_rhs0 =w ceqd ${rhs_d}, d_0.0`) emit(` jnz %${p}_rhs0, @${p}_bad, @${p}_calc`) emit(`@${p}_calc`) emit(` %${p}_q =d div ${lhs_d}, ${rhs_d}`) emit(` %${p}_qt =d call $trunc(d %${p}_q)`) emit(` %${p}_m =d mul ${rhs_d}, %${p}_qt`) emit(` %${p}_rd =d sub ${lhs_d}, %${p}_m`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) emit(` jmp @${p}_done`) emit(`@${p}_bad`) s_write(a1, text(qbe.js_null)) emit(`@${p}_done`) } var max_min_handler = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() lhs_d = emit_num_to_double(lhs) rhs_d = emit_num_to_double(rhs) if (op == "max") { emit(` %${p}_take_l =w cgtd ${lhs_d}, ${rhs_d}`) } else { emit(` %${p}_take_l =w cltd ${lhs_d}, ${rhs_d}`) } emit(` jnz %${p}_take_l, @${p}_lhs, @${p}_rhs`) emit(`@${p}_lhs`) emit(` %${p}_rd =d copy ${lhs_d}`) emit(` jmp @${p}_done_math`) emit(`@${p}_rhs`) emit(` %${p}_rd =d copy ${rhs_d}`) emit(`@${p}_done_math`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) } handlers.max = max_min_handler handlers.min = max_min_handler handlers.abs = function() { lhs = s_read(a2) p = fresh() lhs_d = emit_num_to_double(lhs) emit(` %${p}_rd =d call $fabs(d ${lhs_d})`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) } handlers.sign = function() { lhs = s_read(a2) p = fresh() lhs_d = emit_num_to_double(lhs) emit(` %${p}_lt0 =w cltd ${lhs_d}, d_0.0`) emit(` jnz %${p}_lt0, @${p}_neg, @${p}_chk_pos`) emit(`@${p}_chk_pos`) emit(` %${p}_gt0 =w cgtd ${lhs_d}, d_0.0`) emit(` jnz %${p}_gt0, @${p}_pos, @${p}_zero`) emit(`@${p}_neg`) s_write(a1, text(-2)) emit(` jmp @${p}_done`) emit(`@${p}_pos`) s_write(a1, text(2)) emit(` jmp @${p}_done`) emit(`@${p}_zero`) s_write(a1, text(0)) emit(`@${p}_done`) } handlers.fraction = function() { lhs = s_read(a2) p = fresh() lhs_d = emit_num_to_double(lhs) emit(` %${p}_ti =d call $trunc(d ${lhs_d})`) emit(` %${p}_rd =d sub ${lhs_d}, %${p}_ti`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) } handlers.integer = function() { lhs = s_read(a2) p = fresh() lhs_d = emit_num_to_double(lhs) emit(` %${p}_rd =d call $trunc(d ${lhs_d})`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) } var floor_ceil_round_trunc_handler = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() lhs_d = emit_num_to_double(lhs) emit(` %${p}_lhs_num =w copy ${emit_is_num_w(lhs)}`) emit(` jnz %${p}_lhs_num, @${p}_place, @${p}_bad`) emit(`@${p}_place`) emit(` %${p}_t1 =l and ${rhs}, 1`) emit(` %${p}_is_int =w ceql %${p}_t1, 0`) emit(` jnz %${p}_is_int, @${p}_pi_int, @${p}_pi_not_int`) emit(`@${p}_pi_int`) emit(` %${p}_pil =l sar ${rhs}, 1`) emit(` %${p}_piw =w copy %${p}_pil`) emit(` jmp @${p}_pi_done`) emit(`@${p}_pi_not_int`) emit(` %${p}_t5 =l and ${rhs}, 31`) emit(` %${p}_is_null =w ceql %${p}_t5, 7`) emit(` jnz %${p}_is_null, @${p}_pi_zero, @${p}_pi_chk_bool`) emit(`@${p}_pi_zero`) emit(` %${p}_piw =w copy 0`) emit(` jmp @${p}_pi_done`) emit(`@${p}_pi_chk_bool`) emit(` %${p}_is_bool =w ceql %${p}_t5, 3`) emit(` jnz %${p}_is_bool, @${p}_pi_bool, @${p}_pi_chk_float`) emit(`@${p}_pi_bool`) emit(` %${p}_bl =l shr ${rhs}, 5`) emit(` %${p}_bw =w copy %${p}_bl`) emit(` %${p}_piw =w and %${p}_bw, 1`) emit(` jmp @${p}_pi_done`) emit(`@${p}_pi_chk_float`) emit(` %${p}_t3 =l and ${rhs}, 7`) emit(` %${p}_is_float =w ceql %${p}_t3, 5`) emit(` jnz %${p}_is_float, @${p}_pi_float, @${p}_bad`) emit(`@${p}_pi_float`) rhs_d = emit_num_to_double(rhs) emit(` %${p}_piw =w dtosi ${rhs_d}`) emit(`@${p}_pi_done`) emit(` %${p}_is_zero =w ceqw %${p}_piw, 0`) emit(` jnz %${p}_is_zero, @${p}_direct, @${p}_scaled`) emit(`@${p}_direct`) if (op == "floor") { emit(` %${p}_rd =d call $floor(d ${lhs_d})`) } else if (op == "ceiling") { emit(` %${p}_rd =d call $ceil(d ${lhs_d})`) } else if (op == "round") { emit(` %${p}_rd =d call $round(d ${lhs_d})`) } else { emit(` %${p}_rd =d call $trunc(d ${lhs_d})`) } emit(` jmp @${p}_store`) emit(`@${p}_scaled`) emit(` %${p}_pl =l extsw %${p}_piw`) emit(` %${p}_pd =d sltof %${p}_pl`) emit(` %${p}_negpd =d neg %${p}_pd`) emit(` %${p}_mult =d call $pow(d d_10.0, d %${p}_negpd)`) emit(` %${p}_sd =d mul ${lhs_d}, %${p}_mult`) if (op == "floor") { emit(` %${p}_sr =d call $floor(d %${p}_sd)`) } else if (op == "ceiling") { emit(` %${p}_sr =d call $ceil(d %${p}_sd)`) } else if (op == "round") { emit(` %${p}_sr =d call $round(d %${p}_sd)`) } else { emit(` %${p}_sr =d call $trunc(d %${p}_sd)`) } emit(` %${p}_rd =d div %${p}_sr, %${p}_mult`) emit(` jmp @${p}_store`) emit(`@${p}_bad`) s_write(a1, text(qbe.js_null)) emit(` jmp @${p}_done`) emit(`@${p}_store`) emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`) s_write(a1, `%${p}_r`) emit(`@${p}_done`) } handlers.floor = floor_ceil_round_trunc_handler handlers.ceiling = floor_ceil_round_trunc_handler handlers.round = floor_ceil_round_trunc_handler handlers.trunc = floor_ceil_round_trunc_handler handlers.negate = function() { 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`) } handlers.pow = function() { 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`) } handlers.concat = function() { emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.stone_text = function() { v = s_read(a1) p = fresh() emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_done`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_text =w ceql %${p}_ht, 2`) emit(` jnz %${p}_is_text, @${p}_stone_chk, @${p}_done`) emit(`@${p}_stone_chk`) emit(` %${p}_s =l and %${p}_hdr, 8`) emit(` %${p}_is_stone =w cnel %${p}_s, 0`) emit(` jnz %${p}_is_stone, @${p}_done, @${p}_set`) emit(`@${p}_set`) emit(` %${p}_new_hdr =l or %${p}_hdr, 8`) emit(` storel %${p}_new_hdr, %${p}_ptr`) emit(`@${p}_done`) } handlers.is_int = function() { 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`)) } handlers.is_text = function() { v = s_read(a2) s_write(a1, emit_pack_bool_js(emit_is_text_w(v))) } handlers.is_num = function() { v = s_read(a2) s_write(a1, emit_pack_bool_js(emit_is_num_w(v))) } handlers.is_bool = function() { 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`)) } handlers.is_null = function() { 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`)) } handlers.is_identical = function() { 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`)) } handlers.is_array = function() { emit(` call $__is_array_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) } handlers.is_func = function() { emit(` call $__is_func_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) } handlers.is_record = function() { emit(` call $__is_record_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) } handlers.is_stone = function() { v = s_read(a2) p = fresh() emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_yes`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_chk`) emit(` %${p}_s =l and %${p}_hdr, 8`) emit(` %${p}_w =w cnel %${p}_s, 0`) emit(` jmp @${p}_done`) emit(`@${p}_yes`) emit(` %${p}_w =w copy 1`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_proxy = function() { v = s_read(a2) p = fresh() emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_chk`) emit(` %${p}_is_fn =w ceql %${p}_ht, 4`) emit(` jnz %${p}_is_fn, @${p}_len_chk, @${p}_no`) emit(`@${p}_len_chk`) emit(` %${p}_len_p =l add %${p}_ptr, 16`) emit(` %${p}_len_q =l loadl %${p}_len_p`) emit(` %${p}_len =l and %${p}_len_q, 65535`) emit(` %${p}_w =w ceql %${p}_len, 2`) emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_blob = function() { v = s_read(a2) p = fresh() emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_chk`) emit(` %${p}_w =w ceql %${p}_ht, 1`) emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_data = function() { v = s_read(a2) p = fresh() emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_chk`) emit(` %${p}_is_arr =w ceql %${p}_ht, 0`) emit(` %${p}_is_fn =w ceql %${p}_ht, 4`) emit(` %${p}_is_blob =w ceql %${p}_ht, 1`) emit(` %${p}_bad =w or %${p}_is_arr, %${p}_is_fn`) emit(` %${p}_bad =w or %${p}_bad, %${p}_is_blob`) emit(` %${p}_w =w ceqw %${p}_bad, 0`) emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_fit = function() { v = s_read(a2) p = fresh() emit(` %${p}_tag =l and ${v}, 1`) emit(` %${p}_is_int =w ceql %${p}_tag, 0`) emit(` jnz %${p}_is_int, @${p}_yes, @${p}_sfloat_chk`) emit(`@${p}_sfloat_chk`) emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_sfloat =w ceql %${p}_ptag, 5`) emit(` jnz %${p}_is_sfloat, @${p}_sfloat, @${p}_no`) emit(`@${p}_sfloat`) lhs_d = emit_num_to_double(v) emit(` %${p}_ti =d call $trunc(d ${lhs_d})`) emit(` %${p}_eqi =w ceqd ${lhs_d}, %${p}_ti`) emit(` %${p}_ad =d call $fabs(d ${lhs_d})`) emit(` %${p}_ltlim =w cltd %${p}_ad, d_9007199254740992.0`) emit(` %${p}_eqlim =w ceqd %${p}_ad, d_9007199254740992.0`) emit(` %${p}_lim =w or %${p}_ltlim, %${p}_eqlim`) emit(` %${p}_w =w and %${p}_eqi, %${p}_lim`) emit(` jmp @${p}_done`) emit(`@${p}_yes`) emit(` %${p}_w =w copy 1`) emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_char = function() { v = s_read(a2) p = fresh() emit(` %${p}_imm =l and ${v}, 31`) emit(` %${p}_is_imm =w ceql %${p}_imm, 11`) emit(` jnz %${p}_is_imm, @${p}_imm_path, @${p}_ptr_chk`) emit(`@${p}_imm_path`) emit(` %${p}_ilen =l shr ${v}, 5`) emit(` %${p}_ilen =l and %${p}_ilen, 7`) emit(` %${p}_w =w ceql %${p}_ilen, 1`) emit(` jmp @${p}_done`) emit(`@${p}_ptr_chk`) emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_chk`) emit(` %${p}_is_text =w ceql %${p}_ht, 2`) emit(` jnz %${p}_is_text, @${p}_len_chk, @${p}_no`) emit(`@${p}_len_chk`) emit(` %${p}_len_p =l add %${p}_ptr, 8`) emit(` %${p}_len_l =l loadl %${p}_len_p`) emit(` %${p}_w =w ceql %${p}_len_l, 1`) emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } var is_char_class_handler = function() { v = s_read(a2) p = fresh() emit(` %${p}_imm =l and ${v}, 31`) emit(` %${p}_is_imm =w ceql %${p}_imm, 11`) emit(` jnz %${p}_is_imm, @${p}_imm_len, @${p}_ptr_chk`) emit(`@${p}_imm_len`) emit(` %${p}_ilen =l shr ${v}, 5`) emit(` %${p}_ilen =l and %${p}_ilen, 7`) emit(` %${p}_imm_one =w ceql %${p}_ilen, 1`) emit(` jnz %${p}_imm_one, @${p}_imm_char, @${p}_no`) emit(`@${p}_imm_char`) emit(` %${p}_ch_l =l shr ${v}, 8`) emit(` %${p}_ch_l =l and %${p}_ch_l, 255`) emit(` %${p}_ch_w =w copy %${p}_ch_l`) emit(` jmp @${p}_pred`) emit(`@${p}_ptr_chk`) emit(` %${p}_ptag =l and ${v}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`) emit(`@${p}_ptr`) emit(` %${p}_ptr =l and ${v}, -8`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(`@${p}_chase`) emit(` %${p}_ht =l and %${p}_hdr, 7`) emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`) emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_text_chk`) emit(`@${p}_follow`) emit(` %${p}_ptr =l shr %${p}_hdr, 3`) emit(` %${p}_hdr =l loadl %${p}_ptr`) emit(` jmp @${p}_chase`) emit(`@${p}_text_chk`) emit(` %${p}_is_text =w ceql %${p}_ht, 2`) emit(` jnz %${p}_is_text, @${p}_text_len, @${p}_no`) emit(`@${p}_text_len`) emit(` %${p}_len_p =l add %${p}_ptr, 8`) emit(` %${p}_len_l =l loadl %${p}_len_p`) emit(` %${p}_text_one =w ceql %${p}_len_l, 1`) emit(` jnz %${p}_text_one, @${p}_text_char, @${p}_no`) emit(`@${p}_text_char`) emit(` %${p}_pack_p =l add %${p}_ptr, 24`) emit(` %${p}_pack =l loadl %${p}_pack_p`) emit(` %${p}_ch_l =l shr %${p}_pack, 32`) emit(` %${p}_ch_l =l and %${p}_ch_l, 255`) emit(` %${p}_ch_w =w copy %${p}_ch_l`) emit(`@${p}_pred`) if (op == "is_digit") { emit(` %${p}_lt_0 =w csltw %${p}_ch_w, 48`) emit(` %${p}_ge_0 =w ceqw %${p}_lt_0, 0`) emit(` %${p}_lt_9 =w csltw %${p}_ch_w, 58`) emit(` %${p}_w =w and %${p}_ge_0, %${p}_lt_9`) } else if (op == "is_lower") { emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`) emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`) emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`) emit(` %${p}_w =w and %${p}_ge_a, %${p}_lt_z`) } else if (op == "is_upper") { emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`) emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`) emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`) emit(` %${p}_w =w and %${p}_ge_A, %${p}_lt_Z`) } else if (op == "is_letter") { emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`) emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`) emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`) emit(` %${p}_is_upper =w and %${p}_ge_A, %${p}_lt_Z`) emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`) emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`) emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`) emit(` %${p}_is_lower =w and %${p}_ge_a, %${p}_lt_z`) emit(` %${p}_w =w or %${p}_is_upper, %${p}_is_lower`) } else { emit(` %${p}_is_sp =w ceqw %${p}_ch_w, 32`) emit(` %${p}_is_tb =w ceqw %${p}_ch_w, 9`) emit(` %${p}_is_nl =w ceqw %${p}_ch_w, 10`) emit(` %${p}_is_cr =w ceqw %${p}_ch_w, 13`) emit(` %${p}_is_ff =w ceqw %${p}_ch_w, 12`) emit(` %${p}_is_vt =w ceqw %${p}_ch_w, 11`) emit(` %${p}_w =w or %${p}_is_sp, %${p}_is_tb`) emit(` %${p}_w =w or %${p}_w, %${p}_is_nl`) emit(` %${p}_w =w or %${p}_w, %${p}_is_cr`) emit(` %${p}_w =w or %${p}_w, %${p}_is_ff`) emit(` %${p}_w =w or %${p}_w, %${p}_is_vt`) } emit(` jmp @${p}_done`) emit(`@${p}_no`) emit(` %${p}_w =w copy 0`) emit(`@${p}_done`) s_write(a1, emit_pack_bool_js(`%${p}_w`)) } handlers.is_digit = is_char_class_handler handlers.is_letter = is_char_class_handler handlers.is_lower = is_char_class_handler handlers.is_upper = is_char_class_handler handlers.is_ws = is_char_class_handler handlers.is_true = function() { 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`)) } handlers.is_false = function() { 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`)) } handlers.is_actor = function() { 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`)) } var cmp_handler = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() emit(` %${p}_a_tag =l and ${lhs}, 1`) emit(` %${p}_b_tag =l and ${rhs}, 1`) emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`) emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`) emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`) emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`) emit(`@${p}_int`) emit(` %${p}_ai =l sar ${lhs}, 1`) emit(` %${p}_bi =l sar ${rhs}, 1`) emit(` %${p}_aiw =w copy %${p}_ai`) emit(` %${p}_biw =w copy %${p}_bi`) if (op == "eq") { emit(` %${p}_w =w ceqw %${p}_aiw, %${p}_biw`) } else if (op == "ne") { emit(` %${p}_w =w cnew %${p}_aiw, %${p}_biw`) } else if (op == "lt") { emit(` %${p}_w =w csltw %${p}_aiw, %${p}_biw`) } else if (op == "le") { emit(` %${p}_w =w cslew %${p}_aiw, %${p}_biw`) } else if (op == "gt") { emit(` %${p}_w =w csgtw %${p}_aiw, %${p}_biw`) } else { emit(` %${p}_w =w csgew %${p}_aiw, %${p}_biw`) } s_write(a1, emit_pack_bool_js(`%${p}_w`)) emit(` jmp @${p}_done`) emit(`@${p}_slow`) cmp_id = 0 if (op == "eq") cmp_id = 0 else if (op == "ne") cmp_id = 1 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`) emit(`@${p}_done`) } handlers.eq = cmp_handler handlers.ne = cmp_handler handlers.lt = cmp_handler handlers.le = cmp_handler handlers.gt = cmp_handler handlers.ge = cmp_handler var eq_ne_tol_handler = function() { a4 = instr[4] emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)}, l ${text(a4)})`) } handlers.eq_tol = eq_ne_tol_handler handlers.ne_tol = eq_ne_tol_handler handlers.not = function() { emit(` call $__not_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) } handlers["and"] = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() truthy = emit_truthy_w(lhs) emit(` jnz ${truthy}, @${p}_t, @${p}_f`) emit(`@${p}_t`) s_write(a1, rhs) emit(` jmp @${p}_done`) emit(`@${p}_f`) s_write(a1, lhs) emit(`@${p}_done`) } handlers["or"] = function() { lhs = s_read(a2) rhs = s_read(a3) p = fresh() truthy = emit_truthy_w(lhs) emit(` jnz ${truthy}, @${p}_t, @${p}_f`) emit(`@${p}_t`) s_write(a1, lhs) emit(` jmp @${p}_done`) emit(`@${p}_f`) s_write(a1, rhs) emit(`@${p}_done`) } handlers.bitnot = function() { emit(` call $__bnot_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) } handlers.bitand = function() { emit(` call $__band_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } handlers.bitor = function() { emit(` call $__bor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } handlers.bitxor = function() { emit(` call $__bxor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } handlers.shl = function() { 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`) } handlers.shr = function() { 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`) } handlers.ushr = function() { 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`) } handlers.load_field = function() { pn = null if (is_text(a3)) { pn = a3 } else if (is_object(a3)) { if (a3.name != null) { pn = a3.name } else if (a3.value != null) { pn = a3.value } } 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 ${text(sl.idx)})`) } else { emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } emit_exc_check() } handlers.load_index = function() { emit(` %fp =l call $__load_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.load_dynamic = function() { pn = null if (is_text(a3)) { pn = a3 } else if (is_object(a3)) { if (a3.name != null) { pn = a3.name } else if (a3.value != null) { pn = a3.value } } 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 ${text(sl.idx)})`) } else { emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } emit_exc_check() } handlers.store_field = function() { // IR: ["store_field", obj, val, prop] pn = null if (is_text(a3)) { pn = a3 } else if (is_object(a3)) { if (a3.name != null) { pn = a3.name } else if (a3.value != null) { pn = a3.value } } 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 ${text(sl.idx)})`) } else { emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } emit_exc_check() } handlers.store_index = function() { // IR: ["store_index", obj, val, idx] lhs = s_read(a1) rhs = s_read(a2) v = s_read(a3) p = fresh() emit(` %${p}_idx_tag =l and ${v}, 1`) emit(` %${p}_idx_is_int =w ceql %${p}_idx_tag, 0`) emit(` jnz %${p}_idx_is_int, @${p}_idx_ok, @${p}_slow`) emit(`@${p}_idx_ok`) emit(` %${p}_idx_l =l sar ${v}, 1`) emit(` %${p}_idx_w =w copy %${p}_idx_l`) emit(` %${p}_idx_neg =w csltw %${p}_idx_w, 0`) emit(` jnz %${p}_idx_neg, @${p}_slow, @${p}_arr_ptr_chk`) emit(`@${p}_arr_ptr_chk`) emit(` %${p}_ptag =l and ${lhs}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`) emit(`@${p}_arr_ptr`) emit(` %${p}_arr_ptr =l and ${lhs}, -8`) emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) emit(`@${p}_arr_chase`) emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`) emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`) emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`) emit(`@${p}_arr_follow`) emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`) emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) emit(` jmp @${p}_arr_chase`) emit(`@${p}_arr_chk`) emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`) emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`) emit(`@${p}_arr_stone_chk`) emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`) emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`) emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_cap_chk`) emit(`@${p}_cap_chk`) emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`) emit(` %${p}_cap_w =w copy %${p}_cap_l`) emit(` %${p}_in_cap =w csltw %${p}_idx_w, %${p}_cap_w`) emit(` jnz %${p}_in_cap, @${p}_len_chk, @${p}_slow`) emit(`@${p}_len_chk`) emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`) emit(` %${p}_len_l =l loadl %${p}_len_p`) emit(` %${p}_len_w =w copy %${p}_len_l`) emit(` %${p}_need_len =w csgew %${p}_idx_w, %${p}_len_w`) emit(` jnz %${p}_need_len, @${p}_bump_len, @${p}_store`) emit(`@${p}_bump_len`) emit(` %${p}_next_len_w =w add %${p}_idx_w, 1`) emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`) emit(` storel %${p}_next_len_l, %${p}_len_p`) emit(`@${p}_store`) emit(` %${p}_idx2_l =l extsw %${p}_idx_w`) emit(` %${p}_idx2_off =l shl %${p}_idx2_l, 3`) emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`) emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx2_off`) emit(` storel ${rhs}, %${p}_item_p`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() emit(`@${p}_done`) } handlers.store_dynamic = function() { // IR: ["store_dynamic", obj, val, key] pn = null if (is_text(a3)) { pn = a3 } else if (is_object(a3)) { if (a3.name != null) { pn = a3.name } else if (a3.value != null) { pn = a3.value } } 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 ${text(sl.idx)})`) } else { emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } emit_exc_check() } handlers.get = function() { // mcode: get(dest, slot, depth) — a2=slot, a3=depth depth = a3 if (depth == 0) { v = s_read(a2) s_write(a1, v) } else { p = fresh() emit(` %${p}_fp =l copy %fp`) d = 0 while (d < depth) { emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`) emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`) emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`) emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`) emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`) emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`) emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`) d = d + 1 } emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`) emit(` %${p}_val =l loadl %${p}_slotp`) s_write(a1, `%${p}_val`) } } handlers.put = function() { // mcode: put(val, slot, depth) — a2=slot, a3=depth v = s_read(a1) depth = a3 if (depth == 0) { s_write(a2, v) } else { p = fresh() emit(` %${p}_fp =l copy %fp`) d = 0 while (d < depth) { emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`) emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`) emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`) emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`) emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`) emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`) emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`) d = d + 1 } emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`) emit(` storel ${v}, %${p}_slotp`) } } handlers.jump = function() { j_lbl = sanitize(a1) j_idx = label_pos[j_lbl] if (j_idx != null && j_idx < instr_idx) { emit_backedge_branch(j_lbl) } else { emit(` jmp @${j_lbl}`) } last_was_term = true } handlers.jump_true = function() { v = s_read(a1) p = fresh() jt_lbl = sanitize(a2) jt_idx = label_pos[jt_lbl] jt_backedge = jt_idx != null && jt_idx < instr_idx emit(` %${p}_take =w ceql ${v}, ${text(qbe.js_true)}`) emit(` jnz %${p}_take, @${p}_take, @${p}_f`) emit(`@${p}_take`) if (jt_backedge) { emit_backedge_branch(jt_lbl) } else { emit(` jmp @${jt_lbl}`) } emit(`@${p}_f`) } handlers.jump_false = function() { v = s_read(a1) p = fresh() jf_lbl = sanitize(a2) jf_idx = label_pos[jf_lbl] jf_backedge = jf_idx != null && jf_idx < instr_idx emit(` %${p}_take =w ceql ${v}, ${text(qbe.js_false)}`) emit(` jnz %${p}_take, @${p}_take, @${p}_t`) emit(`@${p}_take`) if (jf_backedge) { emit_backedge_branch(jf_lbl) } else { emit(` jmp @${jf_lbl}`) } emit(`@${p}_t`) } handlers.wary_true = function() { v = s_read(a1) p = fresh() wt_lbl = sanitize(a2) wt_idx = label_pos[wt_lbl] wt_backedge = wt_idx != null && wt_idx < instr_idx truthy = emit_truthy_w(v) emit(` jnz ${truthy}, @${p}_take, @${p}_f`) emit(`@${p}_take`) if (wt_backedge) { emit_backedge_branch(wt_lbl) } else { emit(` jmp @${wt_lbl}`) } emit(`@${p}_f`) } handlers.wary_false = function() { v = s_read(a1) p = fresh() wf_lbl = sanitize(a2) wf_idx = label_pos[wf_lbl] wf_backedge = wf_idx != null && wf_idx < instr_idx truthy = emit_truthy_w(v) emit(` jnz ${truthy}, @${p}_t, @${p}_take`) emit(`@${p}_take`) if (wf_backedge) { emit_backedge_branch(wf_lbl) } else { emit(` jmp @${wf_lbl}`) } emit(`@${p}_t`) } handlers.jump_null = function() { 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}_n`) emit(`@${p}_bn`) emit_backedge_branch(jn_lbl) } else { emit(` jnz %${p}, @${jn_lbl}, @${p}_n`) } emit(`@${p}_n`) } handlers.jump_empty = function() { v = s_read(a1) p = fresh() je_lbl = sanitize(a2) je_idx = label_pos[je_lbl] je_backedge = je_idx != null && je_idx < instr_idx emit(` %${p} =w ceql ${v}, ${text(qbe.js_empty_text)}`) if (je_backedge) { emit(` jnz %${p}, @${p}_bn, @${p}_n`) emit(`@${p}_bn`) emit_backedge_branch(je_lbl) } else { emit(` jnz %${p}, @${je_lbl}, @${p}_n`) } emit(`@${p}_n`) } handlers.jump_not_null = function() { v = s_read(a1) p = fresh() jnn_lbl = sanitize(a2) jnn_idx = label_pos[jnn_lbl] jnn_backedge = jnn_idx != null && jnn_idx < instr_idx emit(` %${p} =w cnel ${v}, ${text(qbe.js_null)}`) if (jnn_backedge) { emit(` jnz %${p}, @${p}_bn, @${p}_n`) emit(`@${p}_bn`) emit_backedge_branch(jnn_lbl) } else { emit(` jnz %${p}, @${jnn_lbl}, @${p}_n`) } emit(`@${p}_n`) } handlers.frame = function() { emit(` %fp =l call $__frame_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.apply = function() { emit(` %fp =l call $__apply_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.setarg = function() { v = s_read(a1) lhs = s_read(a3) 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`) } var invoke_handler = function() { // 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`) } handlers.invoke = invoke_handler handlers.tail_invoke = invoke_handler handlers.goframe = function() { emit(` %fp =l call $__goframe_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.goinvoke = function() { // 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 } handlers["function"] = function() { fn_arity = 0 fn_nr_slots = 0 if (a2 >= 0 && a2 < length(ir.functions)) { fn_arity = ir.functions[a2].nr_args fn_nr_slots = ir.functions[a2].nr_slots } emit(` %fp =l call $__function_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(fn_arity)}, l ${text(fn_nr_slots)})`) emit_exc_check() } handlers.record = function() { emit(` %fp =l call $__new_record_ss(l %ctx, l %fp, l ${text(a1)})`) emit_exc_check() } handlers.array = function() { emit(` %fp =l call $__new_array_ss(l %ctx, l %fp, l ${text(a1)})`) emit_exc_check() } handlers.push = function() { lhs = s_read(a1) rhs = s_read(a2) p = fresh() emit(` %${p}_ptag =l and ${lhs}, 7`) emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`) emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`) emit(`@${p}_arr_ptr`) emit(` %${p}_arr_ptr =l and ${lhs}, -8`) emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) emit(`@${p}_arr_chase`) emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`) emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`) emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`) emit(`@${p}_arr_follow`) emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`) emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`) emit(` jmp @${p}_arr_chase`) emit(`@${p}_arr_chk`) emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`) emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`) emit(`@${p}_arr_stone_chk`) emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`) emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`) emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_lens`) emit(`@${p}_lens`) emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`) emit(` %${p}_len_l =l loadl %${p}_len_p`) emit(` %${p}_len_w =w copy %${p}_len_l`) emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`) emit(` %${p}_cap_w =w copy %${p}_cap_l`) emit(` %${p}_in_cap =w csltw %${p}_len_w, %${p}_cap_w`) emit(` jnz %${p}_in_cap, @${p}_store, @${p}_slow`) emit(`@${p}_store`) emit(` %${p}_idx_l =l extsw %${p}_len_w`) emit(` %${p}_idx_off =l shl %${p}_idx_l, 3`) emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`) emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx_off`) emit(` storel ${rhs}, %${p}_item_p`) emit(` %${p}_next_len_w =w add %${p}_len_w, 1`) emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`) emit(` storel %${p}_next_len_l, %${p}_len_p`) emit(` jmp @${p}_done`) emit(`@${p}_slow`) emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() emit(`@${p}_done`) } handlers.pop = function() { emit(` %fp =l call $__pop_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() } handlers.length = function() { emit(` %fp =l call $__length_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) emit_exc_check() } handlers["return"] = function() { v = s_read(a1) emit(` ret ${v}`) last_was_term = true } handlers.disrupt = function() { emit(` call $cell_rt_disrupt(l %ctx)`) if (has_handler && !in_handler) { emit(" jmp @disruption_handler") } else { emit(` ret 15`) } last_was_term = true } handlers["delete"] = function() { pn = null if (is_text(a3)) { pn = a3 } else if (is_object(a3)) { if (a3.name != null) { pn = a3.name } else if (a3.value != null) { pn = a3.value } } 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 ${text(sl.idx)})`) } else { emit(` %fp =l call $__delete_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } emit_exc_check() } handlers["in"] = function() { // IR: ["in", dest, key_slot, obj_slot] emit(` %fp =l call $__in_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) emit_exc_check() } handlers.regexp = function() { // 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.label}, l ${flg_label.label})`) emit_exc_check() } // Walk instructions var last_was_term = false i = 0 while (i < length(instrs)) { instr = instrs[i] instr_idx = i // Emit @disruption_handler at the right flat index // disruption_pc counts all entries (labels + instructions) if (has_handler && i == disruption_pc) { if (!last_was_term) { emit(" jmp @disruption_handler") } emit("@disruption_handler") emit(" call $cell_rt_clear_exception(l %ctx)") emit(` %fp =l call $cell_rt_refresh_fp(l %ctx)`) last_was_term = false in_handler = true } i = i + 1 // Labels are plain strings; skip nop pseudo-labels from streamline if (is_text(instr)) { if (starts_with(instr, "_nop_")) continue lbl = sanitize(instr) if (!last_was_term) { emit(` jmp @${lbl}`) } emit("@" + lbl) last_was_term = false continue } // Skip dead code: non-label instructions after a terminator are unreachable if (last_was_term) continue op = instr[0] a1 = instr[1] a2 = instr[2] a3 = instr[3] last_was_term = false handler = handlers[op] if (handler) { handler() } else { emit(` # unknown: ${op}`) } } // Emit @disrupt landing pad for arithmetic type-error branches if (!last_was_term) { emit(" jmp @disrupt") } emit("@disrupt") emit(` call $cell_rt_disrupt(l %ctx)`) emit(` ret 15`) // Shared exception return (for functions without disruption handler) if (needs_exc_ret) { emit("@_exc_ret") emit(" ret 15") } emit("}") emit("") } // ============================================================ // Main: compile all functions then main // ============================================================ var fn_bodies = [] var fi = 0 while (fi < length(ir.functions)) { out = [] compile_fn(ir.functions[fi], fi, false) fn_bodies[] = text(out, "\n") fi = fi + 1 } out = [] compile_fn(ir.main, -1, true) fn_bodies[] = text(out, "\n") // 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"), functions: fn_bodies, helpers: emit_helpers(qbe) } } return qbe_emit