2716 lines
82 KiB
Plaintext
2716 lines
82 KiB
Plaintext
// 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 = []
|
|
|
|
// --- 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_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
|
|
}
|
|
|
|
// is_text: immediate text OR ptr->header type check (OBJ_TEXT=2), chase forwards
|
|
h[] = `export function $__is_text_ss(l %fp, l %dest, l %src) {
|
|
@entry
|
|
${sr("a", "%src")}
|
|
%imm =l and %a, 31
|
|
%is_imm =w ceql %imm, 11
|
|
jnz %is_imm, @yes, @chk_ptr
|
|
@chk_ptr
|
|
%ptag =l and %a, 7
|
|
%is_ptr =w ceql %ptag, 1
|
|
jnz %is_ptr, @ptr, @no
|
|
@ptr
|
|
%ptr =l and %a, -8
|
|
%hdr =l loadl %ptr
|
|
@chase
|
|
%ht =l and %hdr, 7
|
|
%is_fwd =w ceql %ht, 7
|
|
jnz %is_fwd, @follow, @chk
|
|
@follow
|
|
%ptr =l shr %hdr, 3
|
|
%hdr =l loadl %ptr
|
|
jmp @chase
|
|
@chk
|
|
%cr =w ceql %ht, 2
|
|
jmp @pack
|
|
@yes
|
|
%cr =w copy 1
|
|
jmp @pack
|
|
@no
|
|
%cr =w copy 0
|
|
@pack
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
|
|
// is_record: pointer + header type check (OBJ_RECORD=3), chase forwards
|
|
h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) {
|
|
@entry
|
|
${sr("a", "%src")}
|
|
%ptag =l and %a, 7
|
|
%is_ptr =w ceql %ptag, 1
|
|
jnz %is_ptr, @ptr, @no
|
|
@ptr
|
|
%ptr =l and %a, -8
|
|
%hdr =l loadl %ptr
|
|
@chase
|
|
%ht =l and %hdr, 7
|
|
%is_fwd =w ceql %ht, 7
|
|
jnz %is_fwd, @follow, @chk
|
|
@follow
|
|
%ptr =l shr %hdr, 3
|
|
%hdr =l loadl %ptr
|
|
jmp @chase
|
|
@chk
|
|
%cr =w ceql %ht, 3
|
|
jmp @pack
|
|
@no
|
|
%cr =w copy 0
|
|
@pack
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
|
|
// is_array: inline pointer+header check (OBJ_ARRAY=0), chase forwards
|
|
h[] = `export function $__is_array_ss(l %fp, l %dest, l %src) {
|
|
@entry
|
|
${sr("a", "%src")}
|
|
%ptag =l and %a, 7
|
|
%is_ptr =w ceql %ptag, 1
|
|
jnz %is_ptr, @ptr, @no
|
|
@ptr
|
|
%ptr =l and %a, -8
|
|
%hdr =l loadl %ptr
|
|
@chase
|
|
%ht =l and %hdr, 7
|
|
%is_fwd =w ceql %ht, 7
|
|
jnz %is_fwd, @follow, @chk
|
|
@follow
|
|
%ptr =l shr %hdr, 3
|
|
%hdr =l loadl %ptr
|
|
jmp @chase
|
|
@chk
|
|
%cr =w ceql %ht, 0
|
|
jmp @pack
|
|
@no
|
|
%cr =w copy 0
|
|
@pack
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
|
|
// is_func: inline pointer+header check (OBJ_FUNCTION=4), chase forwards
|
|
h[] = `export function $__is_func_ss(l %fp, l %dest, l %src) {
|
|
@entry
|
|
${sr("a", "%src")}
|
|
%ptag =l and %a, 7
|
|
%is_ptr =w ceql %ptag, 1
|
|
jnz %is_ptr, @ptr, @no
|
|
@ptr
|
|
%ptr =l and %a, -8
|
|
%hdr =l loadl %ptr
|
|
@chase
|
|
%ht =l and %hdr, 7
|
|
%is_fwd =w ceql %ht, 7
|
|
jnz %is_fwd, @follow, @chk
|
|
@follow
|
|
%ptr =l shr %hdr, 3
|
|
%hdr =l loadl %ptr
|
|
jmp @chase
|
|
@chk
|
|
%cr =w ceql %ht, 4
|
|
jmp @pack
|
|
@no
|
|
%cr =w copy 0
|
|
@pack
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
|
|
// Float comparisons: decode short-float/int inline, then compare in QBE.
|
|
var fc_ops = [
|
|
["eq_float", "ceqd"], ["ne_float", "cned"], ["lt_float", "cltd"],
|
|
["le_float", "cled"], ["gt_float", "cgtd"], ["ge_float", "cged"]
|
|
]
|
|
i = 0
|
|
while (i < length(fc_ops)) {
|
|
h[] = `export function $__${fc_ops[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%a_is_int =w ceql %a_tag, 0
|
|
jnz %a_is_int, @a_int, @a_float
|
|
@a_int
|
|
%a_isl =l sar %a, 1
|
|
%a_iw =w copy %a_isl
|
|
%ad =d swtof %a_iw
|
|
jmp @a_done
|
|
@a_float
|
|
%a_sexp =l shr %a, 55
|
|
%a_sexp =l and %a_sexp, 255
|
|
%a_is_zero =w ceql %a_sexp, 0
|
|
jnz %a_is_zero, @a_zero, @a_decode
|
|
@a_zero
|
|
%ad =d copy d_0.0
|
|
jmp @a_done
|
|
@a_decode
|
|
%a_sign =l shr %a, 63
|
|
%a_mant =l shr %a, 3
|
|
%a_mant =l and %a_mant, 4503599627370495
|
|
%a_dexp =l sub %a_sexp, 127
|
|
%a_dexp =l add %a_dexp, 1023
|
|
%a_s63 =l shl %a_sign, 63
|
|
%a_e52 =l shl %a_dexp, 52
|
|
%a_bits =l or %a_s63, %a_e52
|
|
%a_bits =l or %a_bits, %a_mant
|
|
%ad =d cast %a_bits
|
|
@a_done
|
|
%b_tag =l and %b, 1
|
|
%b_is_int =w ceql %b_tag, 0
|
|
jnz %b_is_int, @b_int, @b_float
|
|
@b_int
|
|
%b_isl =l sar %b, 1
|
|
%b_iw =w copy %b_isl
|
|
%bd =d swtof %b_iw
|
|
jmp @b_done
|
|
@b_float
|
|
%b_sexp =l shr %b, 55
|
|
%b_sexp =l and %b_sexp, 255
|
|
%b_is_zero =w ceql %b_sexp, 0
|
|
jnz %b_is_zero, @b_zero, @b_decode
|
|
@b_zero
|
|
%bd =d copy d_0.0
|
|
jmp @b_done
|
|
@b_decode
|
|
%b_sign =l shr %b, 63
|
|
%b_mant =l shr %b, 3
|
|
%b_mant =l and %b_mant, 4503599627370495
|
|
%b_dexp =l sub %b_sexp, 127
|
|
%b_dexp =l add %b_dexp, 1023
|
|
%b_s63 =l shl %b_sign, 63
|
|
%b_e52 =l shl %b_dexp, 52
|
|
%b_bits =l or %b_s63, %b_e52
|
|
%b_bits =l or %b_bits, %b_mant
|
|
%bd =d cast %b_bits
|
|
@b_done
|
|
%cr =w ${fc_ops[i][1]} %ad, %bd
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
i = i + 1
|
|
}
|
|
|
|
// Text comparisons: eq/ne via js_string_compare_value, others via cell_rt_*
|
|
var txcmp_sv = [
|
|
["eq_text", "ceqw", 1], ["ne_text", "cnew", 1]
|
|
]
|
|
i = 0
|
|
while (i < length(txcmp_sv)) {
|
|
h[] = `export function $__${txcmp_sv[i][0]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%scmp =w call $js_string_compare_value(l %ctx, l %a, l %b, w ${txcmp_sv[i][2]})
|
|
%cr =w ${txcmp_sv[i][1]} %scmp, 0
|
|
%crext =l extuw %cr
|
|
%sh =l shl %crext, 5
|
|
%r =l or %sh, 3
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
i = i + 1
|
|
}
|
|
|
|
// lt/le/gt/ge_text via cell_rt_* (return tagged JSValue directly)
|
|
var txcmp_rt = ["lt_text", "gt_text", "le_text", "ge_text"]
|
|
i = 0
|
|
while (i < length(txcmp_rt)) {
|
|
h[] = `export function $__${txcmp_rt[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%r =l call $cell_rt_${txcmp_rt[i]}(l %ctx, l %a, l %b)
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
}`
|
|
i = i + 1
|
|
}
|
|
|
|
// eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value)
|
|
var tol_ops = ["eq_tol", "ne_tol"]
|
|
i = 0
|
|
while (i < length(tol_ops)) {
|
|
h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) {
|
|
@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) {
|
|
@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
|
|
}`
|
|
|
|
// 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")}
|
|
%tag =l and %a, 1
|
|
%is_int =w ceql %tag, 0
|
|
jnz %is_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%aiw =w copy %ai
|
|
%rw =w xor %aiw, -1
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
// Bitwise binary ops (int-only; type checks should be inserted upstream)
|
|
h[] = `export function $__band_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%rw =w and %aiw, %biw
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
h[] = `export function $__bor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%rw =w or %aiw, %biw
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
h[] = `export function $__bxor_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%rw =w xor %aiw, %biw
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
h[] = `export function $__bshl_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%sh =w and %biw, 31
|
|
%rw =w shl %aiw, %sh
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
h[] = `export function $__bshr_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%sh =w and %biw, 31
|
|
%rw =w sar %aiw, %sh
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
h[] = `export function $__bushr_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%a_tag =l and %a, 1
|
|
%b_tag =l and %b, 1
|
|
%a_int =w ceql %a_tag, 0
|
|
%b_int =w ceql %b_tag, 0
|
|
%both_int =w and %a_int, %b_int
|
|
jnz %both_int, @ok, @bad
|
|
@ok
|
|
%ai =l sar %a, 1
|
|
%bi =l sar %b, 1
|
|
%aiw =w copy %ai
|
|
%biw =w copy %bi
|
|
%sh =w and %biw, 31
|
|
%rw =w shr %aiw, %sh
|
|
%rl =l extsw %rw
|
|
%r =l shl %rl, 1
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret
|
|
@bad
|
|
call $cell_rt_disrupt(l %ctx)
|
|
ret
|
|
}`
|
|
|
|
// ============================================================
|
|
// Category C: Allocating helpers (return fp or 0)
|
|
// ============================================================
|
|
|
|
// concat allocates; keep refresh path
|
|
h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) {
|
|
@entry
|
|
${sr("a", "%s1")}
|
|
${sr("b", "%s2")}
|
|
%r =l call $JS_ConcatString(l %ctx, l %a, l %b)
|
|
${alloc_tail("%r")}
|
|
}`
|
|
|
|
// access_lit(ctx, fp, dest, lit_idx)
|
|
h[] = `export function l $__access_lit_ss(l %ctx, l %fp, l %dest, l %lit_idx) {
|
|
@entry
|
|
%r =l call $cell_rt_access_lit(l %ctx, l %lit_idx)
|
|
%is_exc =w ceql %r, 15
|
|
jnz %is_exc, @exc, @ok
|
|
@ok
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret %fp
|
|
@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) {
|
|
@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) {
|
|
@entry
|
|
${sr("a", "%obj_slot")}
|
|
${sr("b", "%key_slot")}
|
|
%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) {
|
|
@entry
|
|
${sr("a", "%arr_slot")}
|
|
${sr("b", "%idx_slot")}
|
|
%r =l call $cell_rt_load_index(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
|
|
}`
|
|
|
|
// store_field(ctx, fp, obj_slot, val_slot, lit_idx) — no dest write
|
|
h[] = `export function l $__store_field_ss(l %ctx, l %fp, l %obj_slot, l %val_slot, l %lit_idx) {
|
|
@entry
|
|
${sr("a", "%obj_slot")}
|
|
${sr("b", "%val_slot")}
|
|
%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) {
|
|
@entry
|
|
${sr("a", "%obj_slot")}
|
|
${sr("b", "%val_slot")}
|
|
${sr("c", "%key_slot")}
|
|
%ok =w call $cell_rt_store_dynamic(l %ctx, l %b, l %a, l %c)
|
|
jnz %ok, @ok, @exc
|
|
@ok
|
|
ret %fp
|
|
@exc
|
|
ret 0
|
|
}`
|
|
|
|
// store_index(ctx, fp, obj_slot, val_slot, idx_slot) — no dest write
|
|
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")}
|
|
%ok =w call $cell_rt_store_index(l %ctx, l %b, l %a, l %c)
|
|
jnz %ok, @ok, @exc
|
|
@ok
|
|
ret %fp
|
|
@exc
|
|
ret 0
|
|
}`
|
|
|
|
// frame(ctx, fp, dest, fn_slot, nargs)
|
|
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, arity, nr_slots)
|
|
h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity, l %nr_slots) {
|
|
@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) {
|
|
@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)
|
|
${sw("w", "%fp", "%dest", "%r")}
|
|
ret %fp
|
|
}`
|
|
|
|
// get_intrinsic(ctx, fp, dest, lit_idx)
|
|
h[] = `export function l $__get_intrinsic_ss(l %ctx, l %fp, l %dest, l %lit_idx) {
|
|
@entry
|
|
%r =l call $cell_rt_get_intrinsic_lit(l %ctx, l %lit_idx)
|
|
${alloc_tail("%r")}
|
|
}`
|
|
|
|
// push(ctx, fp, arr_slot, val_slot) — write back arr in case GC moved it
|
|
h[] = `export function l $__push_ss(l %ctx, l %fp, l %arr_slot, l %val_slot) {
|
|
@entry
|
|
${sr("a", "%arr_slot")}
|
|
${sr("b", "%val_slot")}
|
|
%r =l call $cell_rt_push(l %ctx, l %a, l %b)
|
|
%fp2 =l call $cell_rt_refresh_fp_checked(l %ctx)
|
|
jnz %fp2, @ok, @exc
|
|
@ok
|
|
${sw("w", "%fp2", "%arr_slot", "%r")}
|
|
ret %fp2
|
|
@exc
|
|
ret 0
|
|
}`
|
|
|
|
// 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, lit_idx)
|
|
h[] = `export function l $__delete_field_ss(l %ctx, l %fp, l %dest, l %obj_slot, l %lit_idx) {
|
|
@entry
|
|
${sr("a", "%obj_slot")}
|
|
%r =l call $cell_rt_delete_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) {
|
|
@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 = []
|
|
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 nr_slots = fn.nr_slots
|
|
var nr_args = fn.nr_args
|
|
var disruption_pc = fn.disruption_pc != null ? fn.disruption_pc : 0
|
|
var has_handler = disruption_pc > 0
|
|
var name = is_main ? (export_name ? export_name : "cell_main") : "cell_fn_" + text(fn_idx)
|
|
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 fop_id = 0
|
|
var nr_elems = 0
|
|
var ei = 0
|
|
var elem_slot = 0
|
|
var v = null
|
|
var rv = null
|
|
var lhs = null
|
|
var rhs = null
|
|
var obj = null
|
|
var chk = null
|
|
var pat_label = null
|
|
var flg_label = null
|
|
var in_handler = false
|
|
var tol = null
|
|
var fn_arity = 0
|
|
var arity_tmp = null
|
|
var fn_nr_slots = 0
|
|
var invoke_count = 0
|
|
var si = 0
|
|
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 seg_num = 0
|
|
var resume_val = 0
|
|
// Native calls should mirror MACH semantics: function calls are mediated
|
|
// by the frame dispatcher, not recursive C calls.
|
|
var use_invoke_trampoline = true
|
|
var j_lbl = null
|
|
var j_idx = null
|
|
var jt_lbl = null
|
|
var jt_idx = null
|
|
var jt_backedge = false
|
|
var jf_lbl = null
|
|
var jf_idx = null
|
|
var jf_backedge = false
|
|
var jn_lbl = null
|
|
var jn_idx = null
|
|
var jn_backedge = false
|
|
var jnn_lbl = null
|
|
var jnn_idx = null
|
|
var jnn_backedge = false
|
|
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 floor_frame_slot = 0
|
|
var floor_this_slot = 0
|
|
var floor_arg_slot = 0
|
|
var floor_dest_slot = 0
|
|
var text_frame_slot = 0
|
|
var text_this_slot = 0
|
|
var text_arg_slot = 0
|
|
var text_dest_slot = 0
|
|
|
|
// 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 floor intrinsic call sequence does not emit an invoke.
|
|
if (false && scan_op == "access" && is_object(scan[2]) && scan[2].make == "intrinsic" && scan[2].name == "floor") {
|
|
if (si + 4 < length(instrs)) {
|
|
peek1 = instrs[si]
|
|
peek2 = instrs[si + 1]
|
|
peek3 = instrs[si + 2]
|
|
peek4 = instrs[si + 3]
|
|
peek5 = instrs[si + 4]
|
|
if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == scan[1] && peek1[3] == 1 &&
|
|
is_array(peek2) && peek2[0] == "null" &&
|
|
is_array(peek3) && peek3[0] == "setarg" &&
|
|
is_array(peek4) && peek4[0] == "setarg" &&
|
|
is_array(peek5) && peek5[0] == "invoke") {
|
|
floor_frame_slot = peek1[1]
|
|
floor_this_slot = peek2[1]
|
|
if (peek3[1] == floor_frame_slot && peek3[2] == 0 && peek3[3] == floor_this_slot &&
|
|
peek4[1] == floor_frame_slot && peek4[2] == 1 &&
|
|
peek5[1] == floor_frame_slot && peek5[2] == floor_this_slot) {
|
|
si = si + 5
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep invoke segment counting consistent with main-loop peephole:
|
|
// inline text intrinsic call sequence does not emit an invoke.
|
|
if (scan_op == "access" && is_object(scan[2]) && scan[2].make == "intrinsic" && scan[2].name == "text") {
|
|
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 (use_invoke_trampoline && (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 = use_invoke_trampoline && 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) {
|
|
var chk_lbl = fresh()
|
|
emit(` %${chk_lbl} =w call $cell_rt_check_backedge(l %ctx)`)
|
|
if (has_handler && !in_handler) {
|
|
emit(` jnz %${chk_lbl}, @disruption_handler, @${target_label}`)
|
|
} else {
|
|
needs_exc_ret = true
|
|
emit(` jnz %${chk_lbl}, @_exc_ret, @${target_label}`)
|
|
}
|
|
}
|
|
|
|
// Inline JS_ToBool equivalent for hot branch paths.
|
|
// Returns a `%name` holding w 0/1 truthiness.
|
|
var emit_truthy_w = function(val) {
|
|
var tp = fresh()
|
|
emit(` %${tp}_t5 =l and ${val}, 31`)
|
|
emit(` %${tp}_is_bool =w ceql %${tp}_t5, 3`)
|
|
emit(` jnz %${tp}_is_bool, @${tp}_bool, @${tp}_chk_null`)
|
|
emit(`@${tp}_bool`)
|
|
emit(` %${tp}_truthy =w cnel ${val}, 3`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_chk_null`)
|
|
emit(` %${tp}_is_null =w ceql %${tp}_t5, 7`)
|
|
emit(` jnz %${tp}_is_null, @${tp}_falsey, @${tp}_chk_int`)
|
|
emit(`@${tp}_chk_int`)
|
|
emit(` %${tp}_t1 =l and ${val}, 1`)
|
|
emit(` %${tp}_is_int =w ceql %${tp}_t1, 0`)
|
|
emit(` jnz %${tp}_is_int, @${tp}_int_path, @${tp}_chk_imm_text`)
|
|
emit(`@${tp}_int_path`)
|
|
emit(` %${tp}_truthy =w cnel ${val}, 0`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_chk_imm_text`)
|
|
emit(` %${tp}_is_imm_text =w ceql %${tp}_t5, 11`)
|
|
emit(` jnz %${tp}_is_imm_text, @${tp}_imm_text, @${tp}_chk_ptr`)
|
|
emit(`@${tp}_imm_text`)
|
|
emit(` %${tp}_truthy =w cnel ${val}, 11`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_chk_ptr`)
|
|
emit(` %${tp}_ptag =l and ${val}, 7`)
|
|
emit(` %${tp}_is_ptr =w ceql %${tp}_ptag, 1`)
|
|
emit(` jnz %${tp}_is_ptr, @${tp}_ptr_path, @${tp}_chk_sfloat`)
|
|
emit(`@${tp}_chk_sfloat`)
|
|
emit(` %${tp}_is_sfloat =w ceql %${tp}_ptag, 5`)
|
|
emit(` jnz %${tp}_is_sfloat, @${tp}_sfloat_path, @${tp}_other_imm`)
|
|
emit(`@${tp}_sfloat_path`)
|
|
emit(` %${tp}_sexp =l shr ${val}, 55`)
|
|
emit(` %${tp}_sexp =l and %${tp}_sexp, 255`)
|
|
emit(` %${tp}_truthy =w cnel %${tp}_sexp, 0`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_other_imm`)
|
|
emit(` %${tp}_truthy =w copy 1`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_ptr_path`)
|
|
emit(` %${tp}_ptr =l and ${val}, -8`)
|
|
emit(` %${tp}_hdr =l loadl %${tp}_ptr`)
|
|
emit(`@${tp}_chase`)
|
|
emit(` %${tp}_ht =l and %${tp}_hdr, 7`)
|
|
emit(` %${tp}_is_fwd =w ceql %${tp}_ht, 7`)
|
|
emit(` jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk_text_ptr`)
|
|
emit(`@${tp}_follow`)
|
|
emit(` %${tp}_ptr =l shr %${tp}_hdr, 3`)
|
|
emit(` %${tp}_hdr =l loadl %${tp}_ptr`)
|
|
emit(` jmp @${tp}_chase`)
|
|
emit(`@${tp}_chk_text_ptr`)
|
|
emit(` %${tp}_is_text_ptr =w ceql %${tp}_ht, 2`)
|
|
emit(` jnz %${tp}_is_text_ptr, @${tp}_text_ptr, @${tp}_ptr_truthy`)
|
|
emit(`@${tp}_text_ptr`)
|
|
emit(` %${tp}_len =l shr %${tp}_hdr, 8`)
|
|
emit(` %${tp}_truthy =w cnel %${tp}_len, 0`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_ptr_truthy`)
|
|
emit(` %${tp}_truthy =w copy 1`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_falsey`)
|
|
emit(` %${tp}_truthy =w copy 0`)
|
|
emit(`@${tp}_done`)
|
|
return `%${tp}_truthy`
|
|
}
|
|
|
|
// Returns w 0/1 for JS text (immediate or heap), following forwards.
|
|
var emit_is_text_w = function(val) {
|
|
var tp = fresh()
|
|
emit(` %${tp}_imm =l and ${val}, 31`)
|
|
emit(` %${tp}_is_imm =w ceql %${tp}_imm, 11`)
|
|
emit(` jnz %${tp}_is_imm, @${tp}_yes, @${tp}_chk_ptr`)
|
|
emit(`@${tp}_chk_ptr`)
|
|
emit(` %${tp}_ptag =l and ${val}, 7`)
|
|
emit(` %${tp}_is_ptr =w ceql %${tp}_ptag, 1`)
|
|
emit(` jnz %${tp}_is_ptr, @${tp}_ptr, @${tp}_no`)
|
|
emit(`@${tp}_ptr`)
|
|
emit(` %${tp}_ptr =l and ${val}, -8`)
|
|
emit(` %${tp}_hdr =l loadl %${tp}_ptr`)
|
|
emit(`@${tp}_chase`)
|
|
emit(` %${tp}_ht =l and %${tp}_hdr, 7`)
|
|
emit(` %${tp}_is_fwd =w ceql %${tp}_ht, 7`)
|
|
emit(` jnz %${tp}_is_fwd, @${tp}_follow, @${tp}_chk`)
|
|
emit(`@${tp}_follow`)
|
|
emit(` %${tp}_ptr =l shr %${tp}_hdr, 3`)
|
|
emit(` %${tp}_hdr =l loadl %${tp}_ptr`)
|
|
emit(` jmp @${tp}_chase`)
|
|
emit(`@${tp}_chk`)
|
|
emit(` %${tp}_is_text =w ceql %${tp}_ht, 2`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_yes`)
|
|
emit(` %${tp}_is_text =w copy 1`)
|
|
emit(` jmp @${tp}_done`)
|
|
emit(`@${tp}_no`)
|
|
emit(` %${tp}_is_text =w copy 0`)
|
|
emit(`@${tp}_done`)
|
|
return `%${tp}_is_text`
|
|
}
|
|
|
|
// Returns w 0/1 for JS numbers (int or short-float).
|
|
var emit_is_num_w = function(val) {
|
|
var np = fresh()
|
|
emit(` %${np}_t1 =l and ${val}, 1`)
|
|
emit(` %${np}_ii =w ceql %${np}_t1, 0`)
|
|
emit(` %${np}_t2 =l and ${val}, 7`)
|
|
emit(` %${np}_fi =w ceql %${np}_t2, 5`)
|
|
emit(` %${np}_is_num =w or %${np}_ii, %${np}_fi`)
|
|
return `%${np}_is_num`
|
|
}
|
|
|
|
// Pack w 0/1 into tagged JS bool (JS_FALSE/JS_TRUE).
|
|
var emit_pack_bool_js = function(wv) {
|
|
var bp = fresh()
|
|
emit(` %${bp}_ext =l extuw ${wv}`)
|
|
emit(` %${bp}_sh =l shl %${bp}_ext, 5`)
|
|
emit(` %${bp}_js =l or %${bp}_sh, 3`)
|
|
return `%${bp}_js`
|
|
}
|
|
|
|
// Convert a known numeric JSValue (int or short-float) to QBE double.
|
|
// Type checks happen earlier in mcode/streamline.
|
|
var emit_num_to_double = function(val) {
|
|
var np = fresh()
|
|
emit(` %${np}_tag =l and ${val}, 1`)
|
|
emit(` %${np}_is_int =w ceql %${np}_tag, 0`)
|
|
emit(` jnz %${np}_is_int, @${np}_int, @${np}_float`)
|
|
emit(`@${np}_int`)
|
|
emit(` %${np}_isl =l sar ${val}, 1`)
|
|
emit(` %${np}_iw =w copy %${np}_isl`)
|
|
emit(` %${np}_d =d swtof %${np}_iw`)
|
|
emit(` jmp @${np}_done`)
|
|
emit(`@${np}_float`)
|
|
emit(` %${np}_sexp =l shr ${val}, 55`)
|
|
emit(` %${np}_sexp =l and %${np}_sexp, 255`)
|
|
emit(` %${np}_is_zero =w ceql %${np}_sexp, 0`)
|
|
emit(` jnz %${np}_is_zero, @${np}_fzero, @${np}_fdecode`)
|
|
emit(`@${np}_fzero`)
|
|
emit(` %${np}_d =d copy d_0.0`)
|
|
emit(` jmp @${np}_done`)
|
|
emit(`@${np}_fdecode`)
|
|
emit(` %${np}_sign =l shr ${val}, 63`)
|
|
emit(` %${np}_mant =l shr ${val}, 3`)
|
|
emit(` %${np}_mant =l and %${np}_mant, 4503599627370495`)
|
|
emit(` %${np}_dexp =l sub %${np}_sexp, 127`)
|
|
emit(` %${np}_dexp =l add %${np}_dexp, 1023`)
|
|
emit(` %${np}_s63 =l shl %${np}_sign, 63`)
|
|
emit(` %${np}_e52 =l shl %${np}_dexp, 52`)
|
|
emit(` %${np}_bits =l or %${np}_s63, %${np}_e52`)
|
|
emit(` %${np}_bits =l or %${np}_bits, %${np}_mant`)
|
|
emit(` %${np}_d =d cast %${np}_bits`)
|
|
emit(`@${np}_done`)
|
|
return `%${np}_d`
|
|
}
|
|
|
|
// Walk instructions
|
|
var last_was_term = false
|
|
i = 0
|
|
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
|
|
|
|
// Peephole: inline `floor(x)` intrinsic call sequence
|
|
// access floor; frame; null this; setarg 0 this; setarg 1 x; invoke
|
|
if (false && op == "access" && is_object(a2) && a2.make == "intrinsic" && a2.name == "floor") {
|
|
if (instr_idx + 5 < length(instrs)) {
|
|
peek1 = instrs[instr_idx + 1]
|
|
peek2 = instrs[instr_idx + 2]
|
|
peek3 = instrs[instr_idx + 3]
|
|
peek4 = instrs[instr_idx + 4]
|
|
peek5 = instrs[instr_idx + 5]
|
|
if (is_array(peek1) && peek1[0] == "frame" && peek1[2] == a1 && peek1[3] == 1 &&
|
|
is_array(peek2) && peek2[0] == "null" &&
|
|
is_array(peek3) && peek3[0] == "setarg" &&
|
|
is_array(peek4) && peek4[0] == "setarg" &&
|
|
is_array(peek5) && peek5[0] == "invoke") {
|
|
floor_frame_slot = peek1[1]
|
|
floor_this_slot = peek2[1]
|
|
if (peek3[1] == floor_frame_slot && peek3[2] == 0 && peek3[3] == floor_this_slot &&
|
|
peek4[1] == floor_frame_slot && peek4[2] == 1 &&
|
|
peek5[1] == floor_frame_slot && peek5[2] == floor_this_slot) {
|
|
floor_arg_slot = peek4[3]
|
|
floor_dest_slot = peek5[2]
|
|
v = s_read(floor_arg_slot)
|
|
p = fresh()
|
|
emit(` %${p}_is_num =w copy ${emit_is_num_w(v)}`)
|
|
emit(` jnz %${p}_is_num, @${p}_ok, @${p}_bad`)
|
|
emit(`@${p}_bad`)
|
|
s_write(floor_dest_slot, text(qbe.js_null))
|
|
emit(` jmp @${p}_done`)
|
|
emit(`@${p}_ok`)
|
|
lhs_d = emit_num_to_double(v)
|
|
emit(` %${p}_fd =d call $floor(d ${lhs_d})`)
|
|
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_fd)`)
|
|
s_write(floor_dest_slot, `%${p}_r`)
|
|
emit(`@${p}_done`)
|
|
i = instr_idx + 6
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Peephole: inline `text(x)` intrinsic call sequence
|
|
// access text; frame; null this; setarg 0 this; setarg 1 x; invoke
|
|
if (op == "access" && is_object(a2) && a2.make == "intrinsic" && a2.name == "text") {
|
|
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
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Constants ---
|
|
|
|
if (op == "int") {
|
|
s_write(a1, text(a2 * 2))
|
|
continue
|
|
}
|
|
if (op == "null") {
|
|
s_write(a1, text(qbe.js_null))
|
|
continue
|
|
}
|
|
if (op == "true") {
|
|
s_write(a1, text(qbe.js_true))
|
|
continue
|
|
}
|
|
if (op == "false") {
|
|
s_write(a1, text(qbe.js_false))
|
|
continue
|
|
}
|
|
if (op == "access") {
|
|
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 $__get_intrinsic_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(sl.idx)})`)
|
|
emit_exc_check()
|
|
} else if (a2.kind == "number") {
|
|
if (a2.number != null && is_integer(a2.number)) {
|
|
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))
|
|
}
|
|
continue
|
|
}
|
|
|
|
// --- Movement ---
|
|
|
|
if (op == "move") {
|
|
v = s_read(a2)
|
|
s_write(a1, v)
|
|
continue
|
|
}
|
|
|
|
// --- Generic arithmetic (VM dispatches int/float) ---
|
|
|
|
if (op == "add") {
|
|
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`)
|
|
emit(` # mixed add: numeric add, text concat, else disrupt`)
|
|
emit(` %${p}_a_num =w copy ${emit_is_num_w(lhs)}`)
|
|
emit(` %${p}_b_num =w copy ${emit_is_num_w(rhs)}`)
|
|
emit(` %${p}_both_num =w and %${p}_a_num, %${p}_b_num`)
|
|
emit(` jnz %${p}_both_num, @${p}_num_add, @${p}_chk_text`)
|
|
emit(`@${p}_num_add`)
|
|
lhs_d = emit_num_to_double(lhs)
|
|
rhs_d = emit_num_to_double(rhs)
|
|
emit(` %${p}_rd =d add ${lhs_d}, ${rhs_d}`)
|
|
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
|
|
s_write(a1, `%${p}_r`)
|
|
emit(` jmp @${p}_done`)
|
|
emit(`@${p}_chk_text`)
|
|
emit(` %${p}_a_txt =w copy ${emit_is_text_w(lhs)}`)
|
|
emit(` %${p}_b_txt =w copy ${emit_is_text_w(rhs)}`)
|
|
emit(` %${p}_both_txt =w and %${p}_a_txt, %${p}_b_txt`)
|
|
emit(` jnz %${p}_both_txt, @${p}_txt_add, @${p}_bad`)
|
|
emit(`@${p}_txt_add`)
|
|
emit(` %fp =l call $__concat_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
emit_exc_check()
|
|
emit(` jmp @${p}_done`)
|
|
emit(`@${p}_bad`)
|
|
emit(` call $cell_rt_disrupt(l %ctx)`)
|
|
if (has_handler && !in_handler) {
|
|
emit(` jmp @disruption_handler`)
|
|
} else {
|
|
emit(` ret 15`)
|
|
}
|
|
emit(`@${p}_done`)
|
|
continue
|
|
}
|
|
if (op == "subtract") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "multiply") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "divide") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
lhs_d = emit_num_to_double(lhs)
|
|
rhs_d = emit_num_to_double(rhs)
|
|
emit(` %${p}_rd =d div ${lhs_d}, ${rhs_d}`)
|
|
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "modulo") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "remainder") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "max" || op == "min") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "abs") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "sign") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "fraction") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "integer") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "floor" || op == "ceiling" || op == "round" || op == "trunc") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "negate") {
|
|
lhs = s_read(a2)
|
|
p = fresh()
|
|
lhs_d = emit_num_to_double(lhs)
|
|
emit(` %${p}_rd =d neg ${lhs_d}`)
|
|
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
|
|
if (op == "pow") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
lhs_d = emit_num_to_double(lhs)
|
|
rhs_d = emit_num_to_double(rhs)
|
|
emit(` %${p}_rd =d call $pow(d ${lhs_d}, d ${rhs_d})`)
|
|
emit(` %${p}_r =l call $qbe_new_float64(l %ctx, d %${p}_rd)`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
|
|
// --- String concat ---
|
|
|
|
if (op == "concat") {
|
|
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(` %${p}_tag =l and ${v}, 1`)
|
|
emit(` %${p}_w =w ceql %${p}_tag, 0`)
|
|
s_write(a1, emit_pack_bool_js(`%${p}_w`))
|
|
continue
|
|
}
|
|
if (op == "is_text") {
|
|
v = s_read(a2)
|
|
s_write(a1, emit_pack_bool_js(emit_is_text_w(v)))
|
|
continue
|
|
}
|
|
if (op == "is_num") {
|
|
v = s_read(a2)
|
|
s_write(a1, emit_pack_bool_js(emit_is_num_w(v)))
|
|
continue
|
|
}
|
|
if (op == "is_bool") {
|
|
v = s_read(a2)
|
|
p = fresh()
|
|
emit(` %${p}_t5 =l and ${v}, 31`)
|
|
emit(` %${p}_w =w ceql %${p}_t5, 3`)
|
|
s_write(a1, emit_pack_bool_js(`%${p}_w`))
|
|
continue
|
|
}
|
|
if (op == "is_null") {
|
|
v = s_read(a2)
|
|
p = fresh()
|
|
emit(` %${p}_t5 =l and ${v}, 31`)
|
|
emit(` %${p}_w =w ceql %${p}_t5, 7`)
|
|
s_write(a1, emit_pack_bool_js(`%${p}_w`))
|
|
continue
|
|
}
|
|
if (op == "is_identical") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_w =w ceql ${lhs}, ${rhs}`)
|
|
s_write(a1, emit_pack_bool_js(`%${p}_w`))
|
|
continue
|
|
}
|
|
if (op == "is_array") {
|
|
emit(` call $__is_array_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "is_func") {
|
|
emit(` call $__is_func_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "is_record") {
|
|
emit(` call $__is_record_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "is_stone") {
|
|
emit(` call $__is_stone_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "is_proxy") {
|
|
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(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w ceqw %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "ne_int") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w cnew %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "lt_int") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w csltw %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "gt_int") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w csgtw %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "le_int") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w cslew %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
if (op == "ge_int") {
|
|
lhs = s_read(a2)
|
|
rhs = s_read(a3)
|
|
p = fresh()
|
|
emit(` %${p}_ai =l sar ${lhs}, 1`)
|
|
emit(` %${p}_bi =l sar ${rhs}, 1`)
|
|
emit(` %${p}_aiw =w copy %${p}_ai`)
|
|
emit(` %${p}_biw =w copy %${p}_bi`)
|
|
emit(` %${p}_cr =w csgew %${p}_aiw, %${p}_biw`)
|
|
emit(` %${p}_crext =l extuw %${p}_cr`)
|
|
emit(` %${p}_sh =l shl %${p}_crext, 5`)
|
|
emit(` %${p}_r =l or %${p}_sh, 3`)
|
|
s_write(a1, `%${p}_r`)
|
|
continue
|
|
}
|
|
|
|
// --- Comparisons (float/text/bool) ---
|
|
|
|
if (op == "eq_float") {
|
|
emit(` call $__eq_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "ne_float") {
|
|
emit(` call $__ne_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "lt_float") {
|
|
emit(` call $__lt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "le_float") {
|
|
emit(` call $__le_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "gt_float") {
|
|
emit(` call $__gt_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "ge_float") {
|
|
emit(` call $__ge_float_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "eq_text") {
|
|
emit(` call $__eq_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "ne_text") {
|
|
emit(` call $__ne_text_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "lt_text" || op == "gt_text" || op == "le_text" || op == "ge_text") {
|
|
emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "eq_bool") {
|
|
emit(` call $__eq_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "ne_bool") {
|
|
emit(` call $__ne_bool_ss(l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "eq_tol" || op == "ne_tol") {
|
|
a4 = instr[4]
|
|
emit(` call $__${op}_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)}, l ${text(a4)})`)
|
|
continue
|
|
}
|
|
|
|
// --- Boolean ops ---
|
|
|
|
if (op == "not") {
|
|
emit(` call $__not_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "and") {
|
|
emit(` call $__and_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "or") {
|
|
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") {
|
|
emit(` call $__bnot_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
if (op == "bitand") {
|
|
emit(` call $__band_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "bitor") {
|
|
emit(` call $__bor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "bitxor") {
|
|
emit(` call $__bxor_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "shl") {
|
|
emit(` call $__bshl_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "shr") {
|
|
emit(` call $__bshr_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
|
|
continue
|
|
}
|
|
if (op == "ushr") {
|
|
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") {
|
|
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()
|
|
continue
|
|
}
|
|
if (op == "load_index") {
|
|
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") {
|
|
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()
|
|
continue
|
|
}
|
|
if (op == "store_field") {
|
|
// 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()
|
|
continue
|
|
}
|
|
if (op == "store_index") {
|
|
// 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]
|
|
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()
|
|
continue
|
|
}
|
|
|
|
// --- Closure access (no GC) ---
|
|
|
|
if (op == "get") {
|
|
// mcode: get(dest, slot, depth) — a2=slot, a3=depth
|
|
p = fresh()
|
|
emit(` %${p} =l call $cell_rt_get_closure(l %ctx, l %fp, l ${text(a3)}, l ${text(a2)})`)
|
|
s_write(a1, `%${p}`)
|
|
continue
|
|
}
|
|
if (op == "put") {
|
|
// mcode: put(val, slot, depth) — a2=slot, a3=depth
|
|
v = s_read(a1)
|
|
emit(` call $cell_rt_put_closure(l %ctx, l %fp, l ${v}, l ${text(a3)}, l ${text(a2)})`)
|
|
continue
|
|
}
|
|
|
|
// --- Control flow ---
|
|
|
|
if (op == "jump") {
|
|
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
|
|
continue
|
|
}
|
|
if (op == "jump_true") {
|
|
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
|
|
truthy = emit_truthy_w(v)
|
|
emit(` jnz ${truthy}, @${p}_take, @${p}_f`)
|
|
emit(`@${p}_take`)
|
|
if (jt_backedge) {
|
|
emit_backedge_branch(jt_lbl)
|
|
} else {
|
|
emit(` jmp @${jt_lbl}`)
|
|
}
|
|
emit(`@${p}_f`)
|
|
continue
|
|
}
|
|
if (op == "jump_false") {
|
|
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
|
|
truthy = emit_truthy_w(v)
|
|
emit(` jnz ${truthy}, @${p}_t, @${p}_take`)
|
|
emit(`@${p}_take`)
|
|
if (jf_backedge) {
|
|
emit_backedge_branch(jf_lbl)
|
|
} else {
|
|
emit(` jmp @${jf_lbl}`)
|
|
}
|
|
emit(`@${p}_t`)
|
|
continue
|
|
}
|
|
if (op == "jump_null") {
|
|
v = s_read(a1)
|
|
p = fresh()
|
|
jn_lbl = sanitize(a2)
|
|
jn_idx = label_pos[jn_lbl]
|
|
jn_backedge = jn_idx != null && jn_idx < instr_idx
|
|
emit(` %${p} =w ceql ${v}, ${text(qbe.js_null)}`)
|
|
if (jn_backedge) {
|
|
emit(` jnz %${p}, @${p}_bn, @${p}_nn`)
|
|
emit(`@${p}_bn`)
|
|
emit_backedge_branch(jn_lbl)
|
|
} else {
|
|
emit(` jnz %${p}, @${jn_lbl}, @${p}_nn`)
|
|
}
|
|
emit(`@${p}_nn`)
|
|
continue
|
|
}
|
|
if (op == "jump_not_null") {
|
|
v = s_read(a1)
|
|
p = fresh()
|
|
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`)
|
|
continue
|
|
}
|
|
|
|
// --- Function calls [G] ---
|
|
|
|
if (op == "frame") {
|
|
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") {
|
|
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`)
|
|
continue
|
|
}
|
|
if (op == "invoke" || op == "tail_invoke") {
|
|
if (use_invoke_trampoline) {
|
|
// Signal dispatcher to call frame in slot a1 and resume at @_segN.
|
|
seg_counter = seg_counter + 1
|
|
resume_val = seg_counter * 65536 + a2
|
|
p = fresh()
|
|
emit(` %${p}_addrp =l sub %fp, 8`)
|
|
// frame->address holds JS_NewInt32((seg << 16) | ret_slot), tagged.
|
|
emit(` storel ${text(resume_val * 2)}, %${p}_addrp`)
|
|
emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`)
|
|
emit(` ret ${text(qbe.js_null)}`)
|
|
emit(`@_seg${text(seg_counter)}`)
|
|
// Dispatcher writes JS_EXCEPTION into ret slot on error; branch here.
|
|
v = s_read(a2)
|
|
emit(` %${p}_exc =w ceql ${v}, ${text(qbe.js_exception)}`)
|
|
if (has_handler && !in_handler) {
|
|
emit(` jnz %${p}_exc, @disruption_handler, @${p}_ok`)
|
|
} else {
|
|
needs_exc_ret = true
|
|
emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`)
|
|
}
|
|
emit(`@${p}_ok`)
|
|
} else {
|
|
// Direct helper invoke path (disabled by default).
|
|
emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
emit_exc_check()
|
|
}
|
|
continue
|
|
}
|
|
if (op == "goframe") {
|
|
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") {
|
|
if (use_invoke_trampoline) {
|
|
// Tail call via dispatcher: no resume in this frame.
|
|
emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`)
|
|
emit(` ret ${text(qbe.js_null)}`)
|
|
} else {
|
|
// Direct helper goinvoke path (disabled by default).
|
|
v = s_read(a1)
|
|
p = fresh()
|
|
emit(` %${p}_r =l call $cell_rt_goinvoke(l %ctx, l ${v})`)
|
|
emit(` %${p}_exc =w ceql %${p}_r, ${text(qbe.js_exception)}`)
|
|
if (has_handler && !in_handler) {
|
|
emit(` jnz %${p}_exc, @${p}_exc, @${p}_ok`)
|
|
emit(`@${p}_exc`)
|
|
emit(` %fp =l call $cell_rt_refresh_fp(l %ctx)`)
|
|
emit(` jmp @disruption_handler`)
|
|
emit(`@${p}_ok`)
|
|
emit(` ret %${p}_r`)
|
|
} else {
|
|
needs_exc_ret = true
|
|
emit(` jnz %${p}_exc, @_exc_ret, @${p}_ok`)
|
|
emit(`@${p}_ok`)
|
|
emit(` ret %${p}_r`)
|
|
}
|
|
}
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
|
|
// --- Function object creation [G] ---
|
|
|
|
if (op == "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()
|
|
continue
|
|
}
|
|
|
|
// --- Record/Array creation [G] ---
|
|
|
|
if (op == "record") {
|
|
emit(` %fp =l call $__new_record_ss(l %ctx, l %fp, l ${text(a1)})`)
|
|
emit_exc_check()
|
|
continue
|
|
}
|
|
if (op == "array") {
|
|
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") {
|
|
emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
emit_exc_check()
|
|
continue
|
|
}
|
|
if (op == "pop") {
|
|
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") {
|
|
emit(` %fp =l call $__length_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
|
emit_exc_check()
|
|
continue
|
|
}
|
|
|
|
// --- Misc ---
|
|
|
|
if (op == "return") {
|
|
v = s_read(a1)
|
|
emit(` ret ${v}`)
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
if (op == "disrupt") {
|
|
emit(` call $cell_rt_disrupt(l %ctx)`)
|
|
if (has_handler && !in_handler) {
|
|
emit(" jmp @disruption_handler")
|
|
} else {
|
|
emit(` ret 15`)
|
|
}
|
|
last_was_term = true
|
|
continue
|
|
}
|
|
if (op == "delete") {
|
|
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()
|
|
continue
|
|
}
|
|
|
|
// --- in [G] ---
|
|
|
|
if (op == "in") {
|
|
// 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()
|
|
continue
|
|
}
|
|
|
|
// --- regexp [G] ---
|
|
|
|
if (op == "regexp") {
|
|
// 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()
|
|
continue
|
|
}
|
|
|
|
// --- Unknown opcode ---
|
|
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
|