diff --git a/build.cm b/build.cm index 8ed32357..4a1b8016 100644 --- a/build.cm +++ b/build.cm @@ -489,6 +489,9 @@ function compile_native_batched(il_parts, cc, tmp_prefix) { var ai = 0 var rc = null var parallel_cmd = null + var helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0) + ? text(il_parts.helpers, "\n") : "" + var prefix = null if (nfuncs < nbatch) nbatch = nfuncs if (nbatch < 1) nbatch = 1 @@ -501,7 +504,9 @@ function compile_native_batched(il_parts, cc, tmp_prefix) { batch_fns[] = il_parts.functions[fi] fi = fi + 1 } - batch_il = il_parts.data + "\n\n" + text(batch_fns, "\n") + // Batch 0 includes helper functions; others reference them as external symbols + prefix = (bi == 0 && helpers_il != "") ? helpers_il + "\n\n" : "" + batch_il = il_parts.data + "\n\n" + prefix + text(batch_fns, "\n") asm_text = os.qbe(batch_il) s_path = tmp_prefix + '_b' + text(bi) + '.s' o_path = tmp_prefix + '_b' + text(bi) + '.o' @@ -595,7 +600,6 @@ Build.compile_native = function(src_path, target, buildtype, pkg) { sym_name = shop.c_symbol_for_file(pkg, fd.basename(src_path)) } var il_parts = qbe_emit(optimized, qbe_macros, sym_name) - var il = il_parts.data + "\n\n" + text(il_parts.functions, "\n") // Content hash for cache key var hash = content_hash(src + '\n' + _target + '\nnative') diff --git a/qbe_emit.cm b/qbe_emit.cm index c0f7f130..2b1246c2 100644 --- a/qbe_emit.cm +++ b/qbe_emit.cm @@ -3,6 +3,580 @@ // 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 = [] + + // --- 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` + } + + // --- Allocating tail without dest write --- + var alloc_tail_nw = function() { + return ` %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) + jnz %fp2, @ok, @exc +@ok + ret %fp2 +@exc + ret 0` + } + + // ============================================================ + // Category A: Pure QBE helpers (no C calls) + // ============================================================ + + // move + h[] = `export function $__move_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} +${sw("w", "%fp", "%dest", "%a")} + ret +}` + + // int comparisons + var int_ops = [ + ["eq_int", "ceqw"], ["ne_int", "cnew"], ["lt_int", "csltw"], + ["le_int", "cslew"], ["gt_int", "csgtw"], ["ge_int", "csgew"] + ] + var i = 0 + while (i < length(int_ops)) { + h[] = `export function $__${int_ops[i][0]}_ss(l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %ia =l sar %a, 1 + %ib =l sar %b, 1 + %iaw =w copy %ia + %ibw =w copy %ib + %cr =w ${int_ops[i][1]} %iaw, %ibw + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // bool comparisons + h[] = `export function $__eq_bool_ss(l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %cr =w ceql %a, %b + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + h[] = `export function $__ne_bool_ss(l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %cr =w cnel %a, %b + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // is_identical (same as eq_bool) + h[] = `export function $__is_identical_ss(l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %cr =w ceql %a, %b + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // is_int: (val & 1) == 0 + h[] = `export function $__is_int_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %t =l and %a, 1 + %cr =w ceql %t, 0 + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // is_null: (val & 31) == 7 + h[] = `export function $__is_null_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %t =l and %a, 31 + %cr =w ceql %t, 7 + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // is_bool: (val & 31) == 3 + h[] = `export function $__is_bool_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %t =l and %a, 31 + %cr =w ceql %t, 3 + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // is_num: (val & 1 == 0) || (val & 7 == 5) + h[] = `export function $__is_num_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %t1 =l and %a, 1 + %ii =w ceql %t1, 0 + %t2 =l and %a, 7 + %fi =w ceql %t2, 5 + %cr =w or %ii, %fi + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // ============================================================ + // Category B: Non-allocating C call helpers + // ============================================================ + + // 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] + ] + var tc_name = null + var tc_cfn = null + var tc_ctx = null + i = 0 + while (i < length(tc_ops)) { + tc_name = tc_ops[i][0] + tc_cfn = tc_ops[i][1] + tc_ctx = tc_ops[i][2] + if (tc_ctx) { + h[] = `export function $__${tc_name}_ss(l %ctx, l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %cr =w call $${tc_cfn}(l %ctx, l %a) + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + } else { + h[] = `export function $__${tc_name}_ss(l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %cr =w call $${tc_cfn}(l %a) + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + } + i = i + 1 + } + + // Float comparisons: call qbe_float_cmp(ctx, op_id, a, b) → w, tag + var fc_ops = [ + ["eq_float", 0], ["ne_float", 1], ["lt_float", 2], + ["le_float", 3], ["gt_float", 4], ["ge_float", 5] + ] + i = 0 + while (i < length(fc_ops)) { + h[] = `export function $__${fc_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %cr =w call $qbe_float_cmp(l %ctx, w ${fc_ops[i][1]}, l %a, l %b) + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // Text comparisons: eq/ne via js_string_compare_value, others via cell_rt_* + var txcmp_sv = [ + ["eq_text", "ceqw", 1], ["ne_text", "cnew", 1] + ] + i = 0 + while (i < length(txcmp_sv)) { + h[] = `export function $__${txcmp_sv[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %scmp =w call $js_string_compare_value(l %ctx, l %a, l %b, w ${txcmp_sv[i][2]}) + %cr =w ${txcmp_sv[i][1]} %scmp, 0 + %crext =l extuw %cr + %sh =l shl %crext, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // lt/le/gt/ge_text via cell_rt_* (return tagged JSValue directly) + var txcmp_rt = ["lt_text", "gt_text", "le_text", "ge_text"] + i = 0 + while (i < length(txcmp_rt)) { + h[] = `export function $__${txcmp_rt[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %r =l call $cell_rt_${txcmp_rt[i]}(l %ctx, l %a, l %b) +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // eq_tol, ne_tol (return tagged JSValue directly) + var tol_ops = ["eq_tol", "ne_tol"] + i = 0 + while (i < length(tol_ops)) { + h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %r =l call $cell_rt_${tol_ops[i]}(l %ctx, l %a, l %b) +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // not: JS_ToBool + negate + tag + 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 + %nex =l extuw %neg + %sh =l shl %nex, 5 + %r =l or %sh, 3 +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // and, or (return tagged JSValue directly) + h[] = `export function $__and_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %r =l call $cell_rt_and(l %ctx, l %a, l %b) +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + h[] = `export function $__or_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %r =l call $cell_rt_or(l %ctx, l %a, l %b) +${sw("w", "%fp", "%dest", "%r")} + ret +}` + + // Bitwise unary: bnot + 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) +${sw("w", "%fp", "%dest", "%r")} + 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) { +@entry +${sr("a", "%s1")} +${sr("b", "%s2")} + %r =l call $${bw_ops[i][1]}(l %ctx, l %a, l %b) +${sw("w", "%fp", "%dest", "%r")} + ret +}` + i = i + 1 + } + + // ============================================================ + // Category C: Allocating helpers (return fp or 0) + // ============================================================ + + // Allocating binary ops: read 2 slots, call C, refresh, write dest + var ab_ops = [ + ["add", "cell_rt_add"], ["sub", "qbe_float_sub"], + ["mul", "qbe_float_mul"], ["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) { +@entry +${sr("a", "%obj_slot")} + %r =l call $cell_rt_load_field(l %ctx, l %a, l %name) +${alloc_tail("%r")} +}` + + // load_dynamic(ctx, fp, dest, obj_slot, key_slot) + h[] = `export function l $__load_dynamic_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %key_slot) { +@entry +${sr("a", "%obj_slot")} +${sr("b", "%key_slot")} + %r =l call $cell_rt_load_dynamic(l %ctx, l %a, l %b) +${alloc_tail("%r")} +}` + + // load_index(ctx, fp, dest, arr_slot, idx_slot) + h[] = `export function l $__load_index_ss(l %ctx, l %fp, l %dest, l %arr_slot, l %idx_slot) { +@entry +${sr("a", "%arr_slot")} +${sr("b", "%idx_slot")} + %r =l call $cell_rt_load_index(l %ctx, l %a, l %b) +${alloc_tail("%r")} +}` + + // 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) { +@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()} +}` + + // 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) { +@entry +${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()} +}` + + // 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) { +@entry +${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()} +}` + + // frame(ctx, fp, dest, fn_slot, nargs) + h[] = `export function l $__frame_ss(l %ctx, l %fp, l %dest, l %fn_slot, l %nargs) { +@entry +${sr("a", "%fn_slot")} + %r =l call $cell_rt_frame(l %ctx, l %a, l %nargs) +${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) { +@entry +${sr("a", "%fn_slot")} + %r =l call $cell_rt_goframe(l %ctx, l %a, l %nargs) +${alloc_tail("%r")} +}` + + // invoke(ctx, fp, frame_slot, result_slot) — two checks: exc + refresh + h[] = `export function l $__invoke_ss(l %ctx, l %fp, l %frame_slot, l %result_slot) { +@entry +${sr("a", "%frame_slot")} + %r =l call $cell_rt_invoke(l %ctx, l %a) + %is_exc =w ceql %r, 15 + jnz %is_exc, @exc, @ok1 +@ok1 + %fp2 =l call $cell_rt_refresh_fp_checked(l %ctx) + jnz %fp2, @ok2, @exc +@ok2 +${sw("w", "%fp2", "%result_slot", "%r")} + ret %fp2 +@exc + ret 0 +}` + + // function(ctx, fp, dest, fn_idx) + h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx) { +@entry + %r =l call $cell_rt_make_function(l %ctx, l %fn_idx, l %fp) +${alloc_tail("%r")} +}` + + // new_record(ctx, fp, dest) + h[] = `export function l $__new_record_ss(l %ctx, l %fp, l %dest) { +@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) { +@entry + %r =l call $JS_NewArray(l %ctx) +${alloc_tail("%r")} +}` + + // new_string(ctx, fp, dest, str_ptr) + h[] = `export function l $__new_string_ss(l %ctx, l %fp, l %dest, l %str_ptr) { +@entry + %r =l call $qbe_new_string(l %ctx, l %str_ptr) +${alloc_tail("%r")} +}` + + // new_float64(ctx, fp, dest, val) — val is a double + h[] = `export function l $__new_float64_ss(l %ctx, l %fp, l %dest, d %val) { +@entry + %r =l call $qbe_new_float64(l %ctx, d %val) +${alloc_tail("%r")} +}` + + // get_intrinsic(ctx, fp, dest, name_ptr) + h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %name_ptr) { +@entry + %r =l call $cell_rt_get_intrinsic(l %ctx, l %name_ptr) +${alloc_tail("%r")} +}` + + // push(ctx, fp, arr_slot, val_slot) — no dest write + h[] = `export function l $__push_ss(l %ctx, l %fp, l %arr_slot, l %val_slot) { +@entry +${sr("a", "%arr_slot")} +${sr("b", "%val_slot")} + call $cell_rt_push(l %ctx, l %a, l %b) +${alloc_tail_nw()} +}` + + // pop(ctx, fp, dest, arr_slot) + h[] = `export function l $__pop_ss(l %ctx, l %fp, l %dest, l %arr_slot) { +@entry +${sr("a", "%arr_slot")} + %r =l call $cell_rt_pop(l %ctx, l %a) +${alloc_tail("%r")} +}` + + // length(ctx, fp, dest, src) + h[] = `export function l $__length_ss(l %ctx, l %fp, l %dest, l %src) { +@entry +${sr("a", "%src")} + %r =l call $JS_CellLength(l %ctx, l %a) +${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) { +@entry +${sr("a", "%obj_slot")} + %r =l call $cell_rt_delete(l %ctx, l %a, l %name) +${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) { +@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) { +@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) { +@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 = [] @@ -142,6 +716,18 @@ var qbe_emit = function(ir, qbe, export_name) { 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`) + } + // Walk instructions var last_was_term = false i = 0 @@ -202,41 +788,35 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "access") { - p = fresh() if (is_number(a2)) { if (is_integer(a2)) { s_write(a1, text(a2 * 2)) } else { - emit(` %${p} =l call $qbe_new_float64(l %ctx, d d_${text(a2)})`) - refresh_fp() - s_write(a1, `%${p}`) + 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(` %${p} =l call $qbe_new_string(l %ctx, l ${sl})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + emit_exc_check() } else if (is_object(a2)) { if (a2.make == "intrinsic") { sl = intern_str(a2.name) - emit(` %${p} =l call $cell_rt_get_intrinsic(l %ctx, l ${sl})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__get_intrinsic_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + 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(` %${p} =l call $qbe_new_float64(l %ctx, d d_${text(a2.number)})`) - refresh_fp() - s_write(a1, `%${p}`) + 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(` %${p} =l call $qbe_new_string(l %ctx, l ${sl})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__new_string_ss(l %ctx, l %fp, l ${text(a1)}, l ${sl})`) + emit_exc_check() } else if (a2.kind == "true") { s_write(a1, text(qbe.js_true)) } else if (a2.kind == "false") { @@ -255,507 +835,285 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Movement --- if (op == "move") { - v = s_read(a2) - s_write(a1, v) + emit(` call $__move_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } // --- Generic arithmetic (VM dispatches int/float) --- if (op == "add") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_add(l %ctx, l ${lhs}, l ${rhs})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__add_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "subtract") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.sub(p, "%ctx", lhs, rhs)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__sub_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "multiply") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.mul(p, "%ctx", lhs, rhs)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__mul_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "divide") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.div(p, "%ctx", lhs, rhs)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__div_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "modulo") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.mod(p, "%ctx", lhs, rhs)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__mod_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "negate") { - v = s_read(a2) - p = fresh() - emit(qbe.neg(p, "%ctx", v)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__neg_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } if (op == "pow") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $qbe_float_pow(l %ctx, l ${lhs}, l ${rhs})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__pow_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } // --- String concat --- if (op == "concat") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.concat(p, "%ctx", lhs, rhs)) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } // --- Type checks — use qbe.cm macros (no GC, no refresh) --- if (op == "is_int") { - v = s_read(a2) - p = fresh() - emit(qbe.is_int(p, v)) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_int_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_text") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $JS_IsText(l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_text_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_num") { - v = s_read(a2) - p = fresh() - emit(qbe.is_number(p, v)) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_num_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_bool") { - v = s_read(a2) - p = fresh() - emit(qbe.is_bool(p, v)) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_null") { - v = s_read(a2) - p = fresh() - emit(qbe.is_null(p, v)) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_null_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_identical") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.is_identical(p, lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__is_identical_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "is_array") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $JS_IsArray(l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_array_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_func") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $JS_IsFunction(l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_func_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_record") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $JS_IsRecord(l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_record_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_stone") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $JS_IsStone(l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_stone_ss(l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "is_proxy") { - v = s_read(a2) - p = fresh() - emit(` %${p} =w call $cell_rt_is_proxy(l %ctx, l ${v})`) - emit(qbe.new_bool(p + ".r", "%" + p)) - s_write(a1, `%${p}.r`) + emit(` call $__is_proxy_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } // --- Comparisons (int path, no GC) --- if (op == "eq_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.eq_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__eq_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ne_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ne_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.lt_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__lt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "gt_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.gt_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__gt_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "le_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.le_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__le_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ge_int") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ge_int(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ge_int_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Comparisons (float/text/bool) --- if (op == "eq_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.eq_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__eq_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ne_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ne_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.lt_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__lt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "le_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.le_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__le_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "gt_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.gt_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__gt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ge_float") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ge_float(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ge_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_text") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.eq_text(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__eq_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_text") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ne_text(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ne_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs})`) - s_write(a1, `%${p}`) + emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_bool") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.eq_bool(p, lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__eq_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ne_bool") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ne_bool(p, lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__ne_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "eq_tol" || op == "ne_tol") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_${op}(l %ctx, l ${lhs}, l ${rhs})`) - s_write(a1, `%${p}`) + emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Boolean ops --- if (op == "not") { - v = s_read(a2) - p = fresh() - emit(qbe.lnot(p, "%ctx", v)) - s_write(a1, `%${p}`) + emit(` call $__not_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "and") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_and(l %ctx, l ${lhs}, l ${rhs})`) - s_write(a1, `%${p}`) + emit(` call $__and_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "or") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_or(l %ctx, l ${lhs}, l ${rhs})`) - s_write(a1, `%${p}`) + emit(` call $__or_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Bitwise ops — use qbe.cm macros (no GC) --- if (op == "bitnot") { - v = s_read(a2) - p = fresh() - emit(qbe.bnot(p, "%ctx", v)) - s_write(a1, `%${p}`) + emit(` call $__bnot_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) continue } if (op == "bitand") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.band(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__band_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "bitor") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.bor(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__bor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "bitxor") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.bxor(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__bxor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "shl") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.shl(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__bshl_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "shr") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.shr(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__bshr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } if (op == "ushr") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(qbe.ushr(p, "%ctx", lhs, rhs)) - s_write(a1, `%${p}`) + emit(` call $__bushr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) continue } // --- Property access — runtime calls [G] --- if (op == "load_field") { - v = s_read(a2) - pn = null - if (is_text(a3)) pn = a3 - else if (is_object(a3) && a3.name != null) pn = a3.name - else if (is_object(a3) && a3.value != null) pn = a3.value - p = fresh() + pn = prop_name(a3) if (pn != null) { sl = intern_str(pn) - emit(` %${p} =l call $cell_rt_load_field(l %ctx, l ${v}, l ${sl})`) + emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { - lhs = s_read(a3) - emit(` %${p} =l call $cell_rt_load_dynamic(l %ctx, l ${v}, l ${lhs})`) + emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } - refresh_fp() - s_write(a1, `%${p}`) + emit_exc_check() continue } if (op == "load_index") { - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_load_index(l %ctx, l ${lhs}, l ${rhs})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__load_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "load_dynamic") { - v = s_read(a2) - pn = null - if (is_text(a3)) pn = a3 - else if (is_object(a3) && a3.name != null) pn = a3.name - else if (is_object(a3) && a3.value != null) pn = a3.value - p = fresh() + pn = prop_name(a3) if (pn != null) { sl = intern_str(pn) - emit(` %${p} =l call $cell_rt_load_field(l %ctx, l ${v}, l ${sl})`) + emit(` %fp =l call $__load_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { - lhs = s_read(a3) - emit(` %${p} =l call $cell_rt_load_dynamic(l %ctx, l ${v}, l ${lhs})`) + emit(` %fp =l call $__load_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } - refresh_fp() - s_write(a1, `%${p}`) + emit_exc_check() continue } if (op == "store_field") { - // IR: ["store_field", obj, val, prop] → C: (ctx, val, obj, name) - obj = s_read(a1) - v = s_read(a2) - 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 - } + // IR: ["store_field", obj, val, prop] + pn = prop_name(a3) if (pn != null) { sl = intern_str(pn) - emit(` call $cell_rt_store_field(l %ctx, l ${v}, l ${obj}, l ${sl})`) + emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { - lhs = s_read(a3) - emit(` call $cell_rt_store_dynamic(l %ctx, l ${v}, l ${obj}, l ${lhs})`) + emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } - refresh_fp() + emit_exc_check() continue } if (op == "store_index") { - // IR: ["store_index", obj, val, idx] → C: (ctx, val, obj, idx) - obj = s_read(a1) - v = s_read(a2) - lhs = s_read(a3) - emit(` call $cell_rt_store_index(l %ctx, l ${v}, l ${obj}, l ${lhs})`) - refresh_fp() + // IR: ["store_index", obj, val, idx] + emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "store_dynamic") { - // IR: ["store_dynamic", obj, val, key] → C: (ctx, val, obj, key) - obj = s_read(a1) - v = s_read(a2) - pn = null - if (is_text(a3)) pn = a3 - else if (is_object(a3) && a3.name != null) pn = a3.name - else if (is_object(a3) && a3.value != null) pn = a3.value + // IR: ["store_dynamic", obj, val, key] + pn = prop_name(a3) if (pn != null) { sl = intern_str(pn) - emit(` call $cell_rt_store_field(l %ctx, l ${v}, l ${obj}, l ${sl})`) + emit(` %fp =l call $__store_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { - lhs = s_read(a3) - emit(` call $cell_rt_store_dynamic(l %ctx, l ${v}, l ${obj}, l ${lhs})`) + emit(` %fp =l call $__store_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } - refresh_fp() + emit_exc_check() continue } @@ -818,11 +1176,8 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Function calls [G] --- if (op == "frame") { - v = s_read(a2) - p = fresh() - emit(` %${p} =l call $cell_rt_frame(l %ctx, l ${v}, l ${text(a3)})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__frame_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "setarg") { @@ -832,45 +1187,18 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "invoke") { - v = s_read(a1) - p = fresh() - emit(` %${p} =l call $cell_rt_invoke(l %ctx, l ${v})`) - chk = fresh() - emit(` %${chk} =w ceql %${p}, 15`) - if (has_handler) { - emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) - } - emit(`@${chk}_ok`) - refresh_fp() - s_write(a2, `%${p}`) + emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } if (op == "tail_invoke") { - v = s_read(a1) - p = fresh() - emit(` %${p} =l call $cell_rt_invoke(l %ctx, l ${v})`) - chk = fresh() - emit(` %${chk} =w ceql %${p}, 15`) - if (has_handler) { - emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`) - } else { - needs_exc_ret = true - emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`) - } - emit(`@${chk}_ok`) - refresh_fp() - s_write(a2, `%${p}`) + emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } if (op == "goframe") { - v = s_read(a2) - p = fresh() - emit(` %${p} =l call $cell_rt_goframe(l %ctx, l ${v}, l ${text(a3)})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__goframe_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } if (op == "goinvoke") { @@ -897,57 +1225,42 @@ var qbe_emit = function(ir, qbe, export_name) { // --- Function object creation [G] --- if (op == "function") { - p = fresh() - emit(` %${p} =l call $cell_rt_make_function(l %ctx, l ${text(a2)}, l %fp)`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__function_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } // --- Record/Array creation [G] --- if (op == "record") { - p = fresh() - emit(` %${p} =l call $JS_NewObject(l %ctx)`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__new_record_ss(l %ctx, l %fp, l ${text(a1)})`) + emit_exc_check() continue } if (op == "array") { - // a2 is a size hint; elements are pushed via separate push instructions - p = fresh() - emit(` %${p} =l call $JS_NewArray(l %ctx)`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__new_array_ss(l %ctx, l %fp, l ${text(a1)})`) + emit_exc_check() continue } // --- Array push/pop [G] --- if (op == "push") { - lhs = s_read(a1) - rhs = s_read(a2) - emit(` call $cell_rt_push(l %ctx, l ${lhs}, l ${rhs})`) - refresh_fp() + emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } if (op == "pop") { - v = s_read(a2) - p = fresh() - emit(` %${p} =l call $cell_rt_pop(l %ctx, l ${v})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__pop_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } // --- Length [G] --- if (op == "length") { - v = s_read(a2) - p = fresh() - emit(` %${p} =l call $JS_CellLength(l %ctx, l ${v})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__length_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`) + emit_exc_check() continue } @@ -970,21 +1283,14 @@ var qbe_emit = function(ir, qbe, export_name) { continue } if (op == "delete") { - v = s_read(a2) - pn = null - if (is_text(a3)) pn = a3 - else if (is_object(a3) && a3.name != null) pn = a3.name - else if (is_object(a3) && a3.value != null) pn = a3.value - p = fresh() + pn = prop_name(a3) if (pn != null) { sl = intern_str(pn) - emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${sl})`) + emit(` %fp =l call $__delete_field_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${sl})`) } else { - lhs = s_read(a3) - emit(` %${p} =l call $cell_rt_delete(l %ctx, l ${v}, l ${lhs})`) + emit(` %fp =l call $__delete_dynamic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) } - refresh_fp() - s_write(a1, `%${p}`) + emit_exc_check() continue } @@ -992,12 +1298,8 @@ var qbe_emit = function(ir, qbe, export_name) { if (op == "in") { // IR: ["in", dest, key_slot, obj_slot] - lhs = s_read(a2) - rhs = s_read(a3) - p = fresh() - emit(` %${p} =l call $cell_rt_in(l %ctx, l ${lhs}, l ${rhs})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__in_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`) + emit_exc_check() continue } @@ -1007,10 +1309,8 @@ 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) - p = fresh() - emit(` %${p} =l call $cell_rt_regexp(l %ctx, l ${pat_label}, l ${flg_label})`) - refresh_fp() - s_write(a1, `%${p}`) + emit(` %fp =l call $__regexp_ss(l %ctx, l %fp, l ${text(a1)}, l ${pat_label}, l ${flg_label})`) + emit_exc_check() continue } @@ -1055,7 +1355,8 @@ var qbe_emit = function(ir, qbe, export_name) { return { data: text(data_out, "\n"), - functions: fn_bodies + functions: fn_bodies, + helpers: emit_helpers(qbe) } }