asserts only for frame gets

This commit is contained in:
2026-02-21 19:06:41 -06:00
parent bbeb757e40
commit 99fa86a09c
3 changed files with 490 additions and 284 deletions

View File

@@ -41,49 +41,6 @@ ${sw("w", "%fp2", "%dest", result_var)}
// Category A: Pure QBE helpers (no C calls)
// ============================================================
// ============================================================
// 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 i = 0
var tc_name = null
var tc_cfn = null
var tc_ctx = null
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) ${lb}
@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) ${lb}
@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_record: pointer + header type check (OBJ_RECORD=3), chase forwards
h[] = `export function $__is_record_ss(l %fp, l %dest, l %src) ${lb}
@entry
@@ -179,7 +136,7 @@ ${sw("w", "%fp", "%dest", "%r")}
// eq_tol, ne_tol (return tagged JSValue directly) — needs tolerance (3rd value)
var tol_ops = ["eq_tol", "ne_tol"]
i = 0
var i = 0
while (i < length(tol_ops)) {
h[] = `export function $__${tol_ops[i]}_ss(l %ctx, l %fp, l %dest, l %s1, l %s2, l %s3) ${lb}
@entry
@@ -266,25 +223,6 @@ ${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) ${lb}
@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) ${lb}
@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/binary ops: mirror MACH mcode op behavior via JS_ToInt32 coercion.
h[] = `export function $__bnot_ss(l %ctx, l %fp, l %dest, l %src) ${lb}
@entry
@@ -325,7 +263,7 @@ ${sw("w", "%fp", "%dest", "%r")}
// Category C: Allocating helpers (return fp or 0)
// ============================================================
// concat allocates; keep refresh path
// concat allocates; runtime helper mirrors MACH self-assign fast path
h[] = `export function l $__concat_ss(l %ctx, l %fp, l %dest, l %s1, l %s2) ${lb}
@entry
${sr("a", "%s1")}
@@ -1098,6 +1036,8 @@ var qbe_emit = function(ir, qbe, export_name) {
var text_arg_slot = 0
var text_dest_slot = 0
var cmp_id = 0
var depth = 0
var d = 0
// Pre-scan: count invoke/tail_invoke points to assign segment numbers.
// Must skip dead code (instructions after terminators) the same way
@@ -1234,14 +1174,7 @@ var qbe_emit = function(ir, qbe, export_name) {
// 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}`)
}
emit(` jmp @${target_label}`)
}
// Inline JS_ToBool equivalent for hot branch paths.
@@ -1894,7 +1827,24 @@ var qbe_emit = function(ir, qbe, export_name) {
}
if (op == "stone_text") {
v = s_read(a1)
emit(` call $cell_rt_stone_text(l ${v})`)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_done`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_text =w ceql %${p}_ht, 2`)
emit(` jnz %${p}_is_text, @${p}_stone_chk, @${p}_done`)
emit(`@${p}_stone_chk`)
emit(` %${p}_s =l and %${p}_hdr, 8`)
emit(` %${p}_is_stone =w cnel %${p}_s, 0`)
emit(` jnz %${p}_is_stone, @${p}_done, @${p}_set`)
emit(`@${p}_set`)
emit(` %${p}_new_hdr =l or %${p}_hdr, 8`)
emit(` storel %${p}_new_hdr, %${p}_ptr`)
emit(`@${p}_done`)
continue
}
@@ -1955,19 +1905,279 @@ var qbe_emit = function(ir, qbe, export_name) {
continue
}
if (op == "is_stone") {
emit(` call $__is_stone_ss(l %fp, l ${text(a1)}, l ${text(a2)})`)
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_yes`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_chk`)
emit(` %${p}_s =l and %${p}_hdr, 8`)
emit(` %${p}_w =w cnel %${p}_s, 0`)
emit(` jmp @${p}_done`)
emit(`@${p}_yes`)
emit(` %${p}_w =w copy 1`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_proxy") {
emit(` call $__is_proxy_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_chk`)
emit(` %${p}_is_fn =w ceql %${p}_ht, 4`)
emit(` jnz %${p}_is_fn, @${p}_len_chk, @${p}_no`)
emit(`@${p}_len_chk`)
emit(` %${p}_len_p =l add %${p}_ptr, 16`)
emit(` %${p}_len_q =l loadl %${p}_len_p`)
emit(` %${p}_len =l and %${p}_len_q, 65535`)
emit(` %${p}_w =w ceql %${p}_len, 2`)
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_blob" || op == "is_data" || op == "is_fit" ||
op == "is_char" || op == "is_digit" || op == "is_letter" ||
if (op == "is_blob") {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_chk`)
emit(` %${p}_w =w ceql %${p}_ht, 1`)
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_data") {
v = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_chk`)
emit(` %${p}_is_arr =w ceql %${p}_ht, 0`)
emit(` %${p}_is_fn =w ceql %${p}_ht, 4`)
emit(` %${p}_is_blob =w ceql %${p}_ht, 1`)
emit(` %${p}_bad =w or %${p}_is_arr, %${p}_is_fn`)
emit(` %${p}_bad =w or %${p}_bad, %${p}_is_blob`)
emit(` %${p}_w =w ceqw %${p}_bad, 0`)
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_fit") {
v = s_read(a2)
p = fresh()
emit(` %${p}_tag =l and ${v}, 1`)
emit(` %${p}_is_int =w ceql %${p}_tag, 0`)
emit(` jnz %${p}_is_int, @${p}_yes, @${p}_sfloat_chk`)
emit(`@${p}_sfloat_chk`)
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_sfloat =w ceql %${p}_ptag, 5`)
emit(` jnz %${p}_is_sfloat, @${p}_sfloat, @${p}_no`)
emit(`@${p}_sfloat`)
lhs_d = emit_num_to_double(v)
emit(` %${p}_ti =d call $trunc(d ${lhs_d})`)
emit(` %${p}_eqi =w ceqd ${lhs_d}, %${p}_ti`)
emit(` %${p}_ad =d call $fabs(d ${lhs_d})`)
emit(` %${p}_ltlim =w cltd %${p}_ad, d_9007199254740992.0`)
emit(` %${p}_eqlim =w ceqd %${p}_ad, d_9007199254740992.0`)
emit(` %${p}_lim =w or %${p}_ltlim, %${p}_eqlim`)
emit(` %${p}_w =w and %${p}_eqi, %${p}_lim`)
emit(` jmp @${p}_done`)
emit(`@${p}_yes`)
emit(` %${p}_w =w copy 1`)
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_char") {
v = s_read(a2)
p = fresh()
emit(` %${p}_imm =l and ${v}, 31`)
emit(` %${p}_is_imm =w ceql %${p}_imm, 11`)
emit(` jnz %${p}_is_imm, @${p}_imm_path, @${p}_ptr_chk`)
emit(`@${p}_imm_path`)
emit(` %${p}_ilen =l shr ${v}, 5`)
emit(` %${p}_ilen =l and %${p}_ilen, 7`)
emit(` %${p}_w =w ceql %${p}_ilen, 1`)
emit(` jmp @${p}_done`)
emit(`@${p}_ptr_chk`)
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_chk`)
emit(` %${p}_is_text =w ceql %${p}_ht, 2`)
emit(` jnz %${p}_is_text, @${p}_len_chk, @${p}_no`)
emit(`@${p}_len_chk`)
emit(` %${p}_len_p =l add %${p}_ptr, 8`)
emit(` %${p}_len_l =l loadl %${p}_len_p`)
emit(` %${p}_w =w ceql %${p}_len_l, 1`)
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
if (op == "is_digit" || op == "is_letter" ||
op == "is_lower" || op == "is_upper" || op == "is_ws") {
v = s_read(a2)
p = fresh()
emit(` %${p}_w =w call $cell_rt_${op}(l ${v})`)
emit(` %${p}_imm =l and ${v}, 31`)
emit(` %${p}_is_imm =w ceql %${p}_imm, 11`)
emit(` jnz %${p}_is_imm, @${p}_imm_len, @${p}_ptr_chk`)
emit(`@${p}_imm_len`)
emit(` %${p}_ilen =l shr ${v}, 5`)
emit(` %${p}_ilen =l and %${p}_ilen, 7`)
emit(` %${p}_imm_one =w ceql %${p}_ilen, 1`)
emit(` jnz %${p}_imm_one, @${p}_imm_char, @${p}_no`)
emit(`@${p}_imm_char`)
emit(` %${p}_ch_l =l shr ${v}, 8`)
emit(` %${p}_ch_l =l and %${p}_ch_l, 255`)
emit(` %${p}_ch_w =w copy %${p}_ch_l`)
emit(` jmp @${p}_pred`)
emit(`@${p}_ptr_chk`)
emit(` %${p}_ptag =l and ${v}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_ptr, @${p}_no`)
emit(`@${p}_ptr`)
emit(` %${p}_ptr =l and ${v}, -8`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(`@${p}_chase`)
emit(` %${p}_ht =l and %${p}_hdr, 7`)
emit(` %${p}_is_fwd =w ceql %${p}_ht, 7`)
emit(` jnz %${p}_is_fwd, @${p}_follow, @${p}_text_chk`)
emit(`@${p}_follow`)
emit(` %${p}_ptr =l shr %${p}_hdr, 3`)
emit(` %${p}_hdr =l loadl %${p}_ptr`)
emit(` jmp @${p}_chase`)
emit(`@${p}_text_chk`)
emit(` %${p}_is_text =w ceql %${p}_ht, 2`)
emit(` jnz %${p}_is_text, @${p}_text_len, @${p}_no`)
emit(`@${p}_text_len`)
emit(` %${p}_len_p =l add %${p}_ptr, 8`)
emit(` %${p}_len_l =l loadl %${p}_len_p`)
emit(` %${p}_text_one =w ceql %${p}_len_l, 1`)
emit(` jnz %${p}_text_one, @${p}_text_char, @${p}_no`)
emit(`@${p}_text_char`)
emit(` %${p}_pack_p =l add %${p}_ptr, 24`)
emit(` %${p}_pack =l loadl %${p}_pack_p`)
emit(` %${p}_ch_l =l shr %${p}_pack, 32`)
emit(` %${p}_ch_l =l and %${p}_ch_l, 255`)
emit(` %${p}_ch_w =w copy %${p}_ch_l`)
emit(`@${p}_pred`)
if (op == "is_digit") {
emit(` %${p}_lt_0 =w csltw %${p}_ch_w, 48`)
emit(` %${p}_ge_0 =w ceqw %${p}_lt_0, 0`)
emit(` %${p}_lt_9 =w csltw %${p}_ch_w, 58`)
emit(` %${p}_w =w and %${p}_ge_0, %${p}_lt_9`)
} else if (op == "is_lower") {
emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`)
emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`)
emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`)
emit(` %${p}_w =w and %${p}_ge_a, %${p}_lt_z`)
} else if (op == "is_upper") {
emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`)
emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`)
emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`)
emit(` %${p}_w =w and %${p}_ge_A, %${p}_lt_Z`)
} else if (op == "is_letter") {
emit(` %${p}_lt_A =w csltw %${p}_ch_w, 65`)
emit(` %${p}_ge_A =w ceqw %${p}_lt_A, 0`)
emit(` %${p}_lt_Z =w csltw %${p}_ch_w, 91`)
emit(` %${p}_is_upper =w and %${p}_ge_A, %${p}_lt_Z`)
emit(` %${p}_lt_a =w csltw %${p}_ch_w, 97`)
emit(` %${p}_ge_a =w ceqw %${p}_lt_a, 0`)
emit(` %${p}_lt_z =w csltw %${p}_ch_w, 123`)
emit(` %${p}_is_lower =w and %${p}_ge_a, %${p}_lt_z`)
emit(` %${p}_w =w or %${p}_is_upper, %${p}_is_lower`)
} else {
emit(` %${p}_is_sp =w ceqw %${p}_ch_w, 32`)
emit(` %${p}_is_tb =w ceqw %${p}_ch_w, 9`)
emit(` %${p}_is_nl =w ceqw %${p}_ch_w, 10`)
emit(` %${p}_is_cr =w ceqw %${p}_ch_w, 13`)
emit(` %${p}_is_ff =w ceqw %${p}_ch_w, 12`)
emit(` %${p}_is_vt =w ceqw %${p}_ch_w, 11`)
emit(` %${p}_w =w or %${p}_is_sp, %${p}_is_tb`)
emit(` %${p}_w =w or %${p}_w, %${p}_is_nl`)
emit(` %${p}_w =w or %${p}_w, %${p}_is_cr`)
emit(` %${p}_w =w or %${p}_w, %${p}_is_ff`)
emit(` %${p}_w =w or %${p}_w, %${p}_is_vt`)
}
emit(` jmp @${p}_done`)
emit(`@${p}_no`)
emit(` %${p}_w =w copy 0`)
emit(`@${p}_done`)
s_write(a1, emit_pack_bool_js(`%${p}_w`))
continue
}
@@ -1999,6 +2209,33 @@ var qbe_emit = function(ir, qbe, export_name) {
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
emit(` %${p}_a_tag =l and ${lhs}, 1`)
emit(` %${p}_b_tag =l and ${rhs}, 1`)
emit(` %${p}_a_int =w ceql %${p}_a_tag, 0`)
emit(` %${p}_b_int =w ceql %${p}_b_tag, 0`)
emit(` %${p}_both_int =w and %${p}_a_int, %${p}_b_int`)
emit(` jnz %${p}_both_int, @${p}_int, @${p}_slow`)
emit(`@${p}_int`)
emit(` %${p}_ai =l sar ${lhs}, 1`)
emit(` %${p}_bi =l sar ${rhs}, 1`)
emit(` %${p}_aiw =w copy %${p}_ai`)
emit(` %${p}_biw =w copy %${p}_bi`)
if (op == "eq") {
emit(` %${p}_w =w ceqw %${p}_aiw, %${p}_biw`)
} else if (op == "ne") {
emit(` %${p}_w =w cnew %${p}_aiw, %${p}_biw`)
} else if (op == "lt") {
emit(` %${p}_w =w csltw %${p}_aiw, %${p}_biw`)
} else if (op == "le") {
emit(` %${p}_w =w cslew %${p}_aiw, %${p}_biw`)
} else if (op == "gt") {
emit(` %${p}_w =w csgtw %${p}_aiw, %${p}_biw`)
} else {
emit(` %${p}_w =w csgew %${p}_aiw, %${p}_biw`)
}
s_write(a1, emit_pack_bool_js(`%${p}_w`))
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
cmp_id = 0
if (op == "eq") cmp_id = 0
else if (op == "ne") cmp_id = 1
@@ -2016,6 +2253,7 @@ var qbe_emit = function(ir, qbe, export_name) {
}
emit(`@${p}_ok`)
s_write(a1, `%${p}_r`)
emit(`@${p}_done`)
continue
}
@@ -2032,11 +2270,31 @@ var qbe_emit = function(ir, qbe, export_name) {
continue
}
if (op == "and") {
emit(` call $__and_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
truthy = emit_truthy_w(lhs)
emit(` jnz ${truthy}, @${p}_t, @${p}_f`)
emit(`@${p}_t`)
s_write(a1, rhs)
emit(` jmp @${p}_done`)
emit(`@${p}_f`)
s_write(a1, lhs)
emit(`@${p}_done`)
continue
}
if (op == "or") {
emit(` call $__or_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
lhs = s_read(a2)
rhs = s_read(a3)
p = fresh()
truthy = emit_truthy_w(lhs)
emit(` jnz ${truthy}, @${p}_t, @${p}_f`)
emit(`@${p}_t`)
s_write(a1, lhs)
emit(` jmp @${p}_done`)
emit(`@${p}_f`)
s_write(a1, rhs)
emit(`@${p}_done`)
continue
}
@@ -2210,8 +2468,66 @@ var qbe_emit = function(ir, qbe, export_name) {
}
if (op == "store_index") {
// IR: ["store_index", obj, val, idx]
lhs = s_read(a1)
rhs = s_read(a2)
v = s_read(a3)
p = fresh()
emit(` %${p}_idx_tag =l and ${v}, 1`)
emit(` %${p}_idx_is_int =w ceql %${p}_idx_tag, 0`)
emit(` jnz %${p}_idx_is_int, @${p}_idx_ok, @${p}_slow`)
emit(`@${p}_idx_ok`)
emit(` %${p}_idx_l =l sar ${v}, 1`)
emit(` %${p}_idx_w =w copy %${p}_idx_l`)
emit(` %${p}_idx_neg =w csltw %${p}_idx_w, 0`)
emit(` jnz %${p}_idx_neg, @${p}_slow, @${p}_arr_ptr_chk`)
emit(`@${p}_arr_ptr_chk`)
emit(` %${p}_ptag =l and ${lhs}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`)
emit(`@${p}_arr_ptr`)
emit(` %${p}_arr_ptr =l and ${lhs}, -8`)
emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
emit(`@${p}_arr_chase`)
emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`)
emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`)
emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`)
emit(`@${p}_arr_follow`)
emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`)
emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
emit(` jmp @${p}_arr_chase`)
emit(`@${p}_arr_chk`)
emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`)
emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`)
emit(`@${p}_arr_stone_chk`)
emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`)
emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`)
emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_cap_chk`)
emit(`@${p}_cap_chk`)
emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`)
emit(` %${p}_cap_w =w copy %${p}_cap_l`)
emit(` %${p}_in_cap =w csltw %${p}_idx_w, %${p}_cap_w`)
emit(` jnz %${p}_in_cap, @${p}_len_chk, @${p}_slow`)
emit(`@${p}_len_chk`)
emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`)
emit(` %${p}_len_l =l loadl %${p}_len_p`)
emit(` %${p}_len_w =w copy %${p}_len_l`)
emit(` %${p}_need_len =w csgew %${p}_idx_w, %${p}_len_w`)
emit(` jnz %${p}_need_len, @${p}_bump_len, @${p}_store`)
emit(`@${p}_bump_len`)
emit(` %${p}_next_len_w =w add %${p}_idx_w, 1`)
emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`)
emit(` storel %${p}_next_len_l, %${p}_len_p`)
emit(`@${p}_store`)
emit(` %${p}_idx2_l =l extsw %${p}_idx_w`)
emit(` %${p}_idx2_off =l shl %${p}_idx2_l, 3`)
emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`)
emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx2_off`)
emit(` storel ${rhs}, %${p}_item_p`)
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
emit(` %fp =l call $__store_index_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)}, l ${text(a3)})`)
emit_exc_check()
emit(`@${p}_done`)
continue
}
if (op == "store_dynamic") {
@@ -2240,15 +2556,53 @@ var qbe_emit = function(ir, qbe, export_name) {
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}`)
depth = a3
if (depth == 0) {
v = s_read(a2)
s_write(a1, v)
} else {
p = fresh()
emit(` %${p}_fp =l copy %fp`)
d = 0
while (d < depth) {
emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`)
emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`)
emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`)
emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`)
emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`)
emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`)
emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`)
d = d + 1
}
emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`)
emit(` %${p}_val =l loadl %${p}_slotp`)
s_write(a1, `%${p}_val`)
}
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)})`)
depth = a3
if (depth == 0) {
s_write(a2, v)
} else {
p = fresh()
emit(` %${p}_fp =l copy %fp`)
d = 0
while (d < depth) {
emit(` %${p}_fn_p_${text(d)} =l sub %${p}_fp, 24`)
emit(` %${p}_fn_${text(d)} =l loadl %${p}_fn_p_${text(d)}`)
emit(` %${p}_fn_ptr_${text(d)} =l and %${p}_fn_${text(d)}, -8`)
emit(` %${p}_outer_p_${text(d)} =l add %${p}_fn_ptr_${text(d)}, 40`)
emit(` %${p}_outer_${text(d)} =l loadl %${p}_outer_p_${text(d)}`)
emit(` %${p}_outer_ptr_${text(d)} =l and %${p}_outer_${text(d)}, -8`)
emit(` %${p}_fp =l add %${p}_outer_ptr_${text(d)}, 32`)
d = d + 1
}
emit(` %${p}_slotp =l add %${p}_fp, ${text(a2 * 8)}`)
emit(` storel ${v}, %${p}_slotp`)
}
continue
}
@@ -2401,8 +2755,52 @@ var qbe_emit = function(ir, qbe, export_name) {
// --- Array push/pop [G] ---
if (op == "push") {
lhs = s_read(a1)
rhs = s_read(a2)
p = fresh()
emit(` %${p}_ptag =l and ${lhs}, 7`)
emit(` %${p}_is_ptr =w ceql %${p}_ptag, 1`)
emit(` jnz %${p}_is_ptr, @${p}_arr_ptr, @${p}_slow`)
emit(`@${p}_arr_ptr`)
emit(` %${p}_arr_ptr =l and ${lhs}, -8`)
emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
emit(`@${p}_arr_chase`)
emit(` %${p}_arr_ty =l and %${p}_arr_hdr, 7`)
emit(` %${p}_arr_is_fwd =w ceql %${p}_arr_ty, 7`)
emit(` jnz %${p}_arr_is_fwd, @${p}_arr_follow, @${p}_arr_chk`)
emit(`@${p}_arr_follow`)
emit(` %${p}_arr_ptr =l shr %${p}_arr_hdr, 3`)
emit(` %${p}_arr_hdr =l loadl %${p}_arr_ptr`)
emit(` jmp @${p}_arr_chase`)
emit(`@${p}_arr_chk`)
emit(` %${p}_arr_is_array =w ceql %${p}_arr_ty, 0`)
emit(` jnz %${p}_arr_is_array, @${p}_arr_stone_chk, @${p}_slow`)
emit(`@${p}_arr_stone_chk`)
emit(` %${p}_arr_stone =l and %${p}_arr_hdr, 8`)
emit(` %${p}_arr_is_stone =w cnel %${p}_arr_stone, 0`)
emit(` jnz %${p}_arr_is_stone, @${p}_slow, @${p}_lens`)
emit(`@${p}_lens`)
emit(` %${p}_len_p =l add %${p}_arr_ptr, 8`)
emit(` %${p}_len_l =l loadl %${p}_len_p`)
emit(` %${p}_len_w =w copy %${p}_len_l`)
emit(` %${p}_cap_l =l shr %${p}_arr_hdr, 8`)
emit(` %${p}_cap_w =w copy %${p}_cap_l`)
emit(` %${p}_in_cap =w csltw %${p}_len_w, %${p}_cap_w`)
emit(` jnz %${p}_in_cap, @${p}_store, @${p}_slow`)
emit(`@${p}_store`)
emit(` %${p}_idx_l =l extsw %${p}_len_w`)
emit(` %${p}_idx_off =l shl %${p}_idx_l, 3`)
emit(` %${p}_vals_p =l add %${p}_arr_ptr, 16`)
emit(` %${p}_item_p =l add %${p}_vals_p, %${p}_idx_off`)
emit(` storel ${rhs}, %${p}_item_p`)
emit(` %${p}_next_len_w =w add %${p}_len_w, 1`)
emit(` %${p}_next_len_l =l extsw %${p}_next_len_w`)
emit(` storel %${p}_next_len_l, %${p}_len_p`)
emit(` jmp @${p}_done`)
emit(`@${p}_slow`)
emit(` %fp =l call $__push_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
emit(`@${p}_done`)
continue
}
if (op == "pop") {

View File

@@ -24,6 +24,7 @@
*/
#include "quickjs-internal.h"
#include <assert.h>
/* ============================================================
Mach VM instruction definitions (private to mach.c)
@@ -2018,21 +2019,12 @@ vm_dispatch:
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!target) {
fprintf(stderr, "GETUP: NULL outer_frame at depth 0! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
pc-1, a, depth, c, code->nr_slots, instr);
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame");
goto disrupt;
}
assert(depth > 0);
assert(target != NULL);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
JSFrameRegister *next = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
if (!next) {
fprintf(stderr, "GETUP: NULL outer_frame at depth %d! pc=%d a=%d depth=%d slot=%d nr_slots=%d instr=0x%08x\n",
d, pc-1, a, depth, c, code->nr_slots, instr);
result = JS_RaiseDisrupt(ctx, "GETUP: NULL outer_frame at depth %d", d);
goto disrupt;
}
assert(next != NULL);
target = next;
}
stone_mutable_text(target->slots[c]);
@@ -2045,22 +2037,14 @@ vm_dispatch:
int depth = b;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
assert(depth > 0);
assert(target != NULL);
for (int d = 1; d < depth; d++) {
fn = JS_VALUE_GET_FUNCTION(target->function);
target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.cell.outer_frame);
assert(target != NULL);
}
{
uint64_t tcap = objhdr_cap56(target->header);
if ((unsigned)c >= tcap) {
fprintf(stderr, "MACH_SETUP OOB: slot=%d >= target_cap=%llu depth=%d "
"cur_fn=%s (%s) pc=%u\n",
c, (unsigned long long)tcap, depth,
code->name_cstr ? code->name_cstr : "?",
code->filename_cstr ? code->filename_cstr : "?", pc - 1);
fflush(stderr);
VM_BREAK();
}
}
assert((unsigned)c < objhdr_cap56(target->header));
target->slots[c] = frame->slots[a];
VM_BREAK();
}

View File

@@ -150,7 +150,6 @@ JSValue qbe_bitwise_xor(JSContext *ctx, JSValue a, JSValue b) {
/* Concat helper matching MACH_CONCAT semantics exactly. */
JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_assign) {
if (self_assign) {
/* Self-assign pattern: slot[a] = slot[a] + slot[c]. */
if (JS_IsPtr(left)) {
JSText *s = (JSText *)chase(left);
int slen = (int)s->length;
@@ -159,21 +158,18 @@ JSValue cell_rt_concat(JSContext *ctx, JSValue left, JSValue right, int self_ass
if (objhdr_type(s->hdr) == OBJ_TEXT
&& !(s->hdr & OBJHDR_S_MASK)
&& slen + rlen <= cap) {
/* In-place append, no allocation. */
for (int i = 0; i < rlen; i++)
string_put(s, slen + i, js_string_value_get(right, i));
s->length = slen + rlen;
return left;
}
}
/* Allocate with growth factor, leave unstoned. */
JSValue res = JS_ConcatStringGrow(ctx, left, right);
if (JS_IsException(res))
return JS_EXCEPTION;
return res;
}
/* Different target: exact-fit stoned path. */
JSValue res = JS_ConcatString(ctx, left, right);
if (JS_IsException(res))
return JS_EXCEPTION;
@@ -468,52 +464,6 @@ JSValue cell_rt_get_intrinsic_lit(JSContext *ctx, int64_t lit_idx) {
return cell_rt_get_intrinsic_key(ctx, key);
}
/* --- Closure access ---
Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE).
The frame's function field links to the JSFunction, whose
u.native.outer_frame points to the enclosing frame.
GC traces outer_frame naturally — no registry needed. */
/* Get the outer frame's slots from a frame pointer.
The frame's function must be JS_FUNC_KIND_NATIVE. */
static JSValue *get_outer_frame_slots(JSValue *fp) {
/* fp points to frame->slots[0]; frame header is before it */
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
if (JS_IsNull(frame->function))
return NULL;
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
if (fn->kind != JS_FUNC_KIND_NATIVE)
return NULL;
JSValue outer = fn->u.cell.outer_frame;
if (JS_IsNull(outer))
return NULL;
JSFrameRegister *outer_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(outer);
return (JSValue *)outer_frame->slots;
}
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
int64_t slot) {
(void)ctx;
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
frame = get_outer_frame_slots(frame);
if (!frame)
return JS_NULL;
}
return frame[slot];
}
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
int64_t slot) {
(void)ctx;
JSValue *frame = (JSValue *)fp;
for (int64_t d = 0; d < depth; d++) {
frame = get_outer_frame_slots(frame);
if (!frame) return;
}
frame[slot] = val;
}
/* --- GC-managed AOT frame stack ---
Each native dispatch loop pushes a GC ref so the GC can find and
update the current frame pointer when it moves objects.
@@ -662,18 +612,6 @@ typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
to call another function (instead of recursing via C stack).
============================================================ */
/* Poll pause state on taken backward jumps (AOT backedges).
MACH can suspend/resume a register VM frame at pc granularity; native AOT
does not currently have an equivalent resume point, so we acknowledge timer
pauses by clearing pause_flag and continuing the current turn. */
int cell_rt_check_backedge(JSContext *ctx) {
int pf = atomic_load_explicit(&ctx->pause_flag, memory_order_relaxed);
if (pf >= 1) {
atomic_store_explicit(&ctx->pause_flag, 0, memory_order_relaxed);
}
return 0;
}
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
NativeRTState *st = native_state(ctx);
if (!st) return;
@@ -1194,101 +1132,6 @@ JSValue cell_rt_ne_tol(JSContext *ctx, JSValue a, JSValue b, JSValue tol) {
return JS_NewBool(ctx, !cell_rt_tol_eq_inner(ctx, a, b, tol));
}
/* --- Extended type checks and text stoning --- */
void cell_rt_stone_text(JSValue v) {
stone_mutable_text(v);
}
int cell_rt_is_blob(JSValue v) {
return mist_is_blob(v);
}
int cell_rt_is_data(JSValue v) {
return mist_is_gc_object(v) && !mist_is_array(v)
&& !mist_is_function(v) && !mist_is_blob(v);
}
int cell_rt_is_fit(JSValue v) {
if (JS_IsInt(v))
return 1;
if (JS_IsShortFloat(v)) {
double d = JS_VALUE_GET_FLOAT64(v);
return isfinite(d) && trunc(d) == d && fabs(d) <= 9007199254740992.0;
}
return 0;
}
int cell_rt_is_char(JSValue v) {
if (MIST_IsImmediateASCII(v))
return MIST_GetImmediateASCIILen(v) == 1;
if (mist_is_text(v))
return js_string_value_len(v) == 1;
return 0;
}
int cell_rt_is_digit(JSValue v) {
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
return ch >= '0' && ch <= '9';
}
if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
return ch >= '0' && ch <= '9';
}
return 0;
}
int cell_rt_is_letter(JSValue v) {
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
}
if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
}
return 0;
}
int cell_rt_is_lower(JSValue v) {
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
return ch >= 'a' && ch <= 'z';
}
if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
return ch >= 'a' && ch <= 'z';
}
return 0;
}
int cell_rt_is_upper(JSValue v) {
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
return ch >= 'A' && ch <= 'Z';
}
if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
return ch >= 'A' && ch <= 'Z';
}
return 0;
}
int cell_rt_is_ws(JSValue v) {
if (MIST_IsImmediateASCII(v) && MIST_GetImmediateASCIILen(v) == 1) {
int ch = MIST_GetImmediateASCIIChar(v, 0);
return ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v';
}
if (mist_is_text(v) && js_string_value_len(v) == 1) {
uint32_t ch = js_string_value_get(v, 0);
return ch == ' ' || ch == '\t' || ch == '\n'
|| ch == '\r' || ch == '\f' || ch == '\v';
}
return 0;
}
int cell_rt_is_actor(JSContext *ctx, JSValue v) {
int result = 0;
if (mist_is_record(v) && !JS_IsNull(ctx->actor_sym))
@@ -1296,25 +1139,6 @@ int cell_rt_is_actor(JSContext *ctx, JSValue v) {
return result;
}
/* --- Type check: is_proxy (function with arity 2) --- */
int cell_rt_is_proxy(JSContext *ctx, JSValue v) {
(void)ctx;
if (!JS_IsFunction(v)) return 0;
JSFunction *fn = JS_VALUE_GET_FUNCTION(v);
return fn->length == 2;
}
/* --- Short-circuit and/or (non-allocating) --- */
JSValue cell_rt_and(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? right : left;
}
JSValue cell_rt_or(JSContext *ctx, JSValue left, JSValue right) {
return JS_ToBool(ctx, left) ? left : right;
}
/* --- Exception checking ---
After potentially-throwing runtime calls, QBE-generated code needs to
check for pending exceptions and branch to the disruption handler. */