native function type

This commit is contained in:
2026-02-17 17:40:44 -06:00
parent b25285f2e1
commit ad419797b4
8 changed files with 603 additions and 265 deletions

View File

@@ -475,10 +475,10 @@ ${sw("w", "%fp2", "%result_slot", "%r")}
ret 0
}`
// function(ctx, fp, dest, fn_idx, arity)
h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity) {
// 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)
%r =l call $cell_rt_make_function(l %ctx, l %fn_idx, l %fp, l %arity, l %nr_slots)
${alloc_tail("%r")}
}`
@@ -680,11 +680,74 @@ var qbe_emit = function(ir, qbe, export_name) {
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 has_invokes = false
var seg_counter = 0
var ri = 0
var seg_num = 0
var resume_val = 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)) {
// Labels reset dead code state (unless they're nop pseudo-labels)
if (!starts_with(scan, "_nop_ur_") && !starts_with(scan, "_nop_tc_"))
scan_dead = false
continue
}
if (scan_dead) continue
if (!is_array(scan)) continue
scan_op = scan[0]
if (scan_op == "invoke" || scan_op == "tail_invoke") {
invoke_count = invoke_count + 1
}
// Track terminators — same set as in the main loop
if (scan_op == "return" || scan_op == "jump" || scan_op == "goinvoke" || scan_op == "disrupt") {
scan_dead = true
}
}
has_invokes = invoke_count > 0
// Function signature: (ctx, frame_ptr) → JSValue
emit(`export function l $${name}(l %ctx, l %fp) {`)
emit("@entry")
// Resume dispatch: if this function has invoke points, read the segment
// number from frame->address and jump to the right resume point.
// frame->address is at fp - 8 (last field before slots[]).
if (has_invokes) {
emit(" %addr_ptr =l sub %fp, 8")
emit(" %addr_raw =l loadl %addr_ptr")
// address is stored as JS_NewInt32 tagged value: n << 1
emit(" %addr =l sar %addr_raw, 1")
emit(" %resume =l shr %addr, 16")
emit(` jnz %resume, @_rcheck1, @_seg0`)
ri = 1
while (ri <= invoke_count) {
emit(`@_rcheck${text(ri)}`)
emit(` %_rc${text(ri)} =w ceql %resume, ${text(ri)}`)
if (ri < invoke_count) {
emit(` jnz %_rc${text(ri)}, @_seg${text(ri)}, @_rcheck${text(ri + 1)}`)
} else {
// Last check — if no match, fall through to seg0
emit(` jnz %_rc${text(ri)}, @_seg${text(ri)}, @_seg0`)
}
ri = ri + 1
}
emit("@_seg0")
}
// GC-safe slot access: every read/write goes through frame memory.
// %fp may become stale after GC-triggering calls — use refresh_fp().
var s_read = function(slot) {
@@ -1228,13 +1291,51 @@ var qbe_emit = function(ir, qbe, export_name) {
continue
}
if (op == "invoke") {
emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
// Dispatch loop invoke: store resume info, signal, return 0
seg_counter = seg_counter + 1
seg_num = seg_counter
// Store (seg_num << 16 | result_slot) as tagged int in frame->address
resume_val = seg_num * 65536 + a2
// frame->address is at fp - 8, store as tagged int (n << 1)
emit(` %_inv_addr${text(seg_num)} =l sub %fp, 8`)
emit(` storel ${text(resume_val * 2)}, %_inv_addr${text(seg_num)}`)
emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`)
emit(" ret 0")
emit(`@_seg${text(seg_num)}`)
// Check for exception after dispatch loop resumes us
p = fresh()
emit(` %${p} =w call $JS_HasException(l %ctx)`)
if (has_handler && !in_handler) {
emit(` jnz %${p}, @disruption_handler, @${p}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${p}, @_exc_ret, @${p}_ok`)
}
emit(`@${p}_ok`)
last_was_term = false
continue
}
if (op == "tail_invoke") {
emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
emit_exc_check()
// Same as invoke — dispatch loop regular call with resume
seg_counter = seg_counter + 1
seg_num = seg_counter
resume_val = seg_num * 65536 + a2
emit(` %_tinv_addr${text(seg_num)} =l sub %fp, 8`)
emit(` storel ${text(resume_val * 2)}, %_tinv_addr${text(seg_num)}`)
emit(` call $cell_rt_signal_call(l %ctx, l %fp, l ${text(a1)})`)
emit(" ret 0")
emit(`@_seg${text(seg_num)}`)
// Check for exception after dispatch loop resumes us
p = fresh()
emit(` %${p} =w call $JS_HasException(l %ctx)`)
if (has_handler && !in_handler) {
emit(` jnz %${p}, @disruption_handler, @${p}_ok`)
} else {
needs_exc_ret = true
emit(` jnz %${p}, @_exc_ret, @${p}_ok`)
}
emit(`@${p}_ok`)
last_was_term = false
continue
}
if (op == "goframe") {
@@ -1243,22 +1344,13 @@ var qbe_emit = function(ir, qbe, export_name) {
continue
}
if (op == "goinvoke") {
v = s_read(a1)
// Dispatch loop tail call: signal tail call and return 0
// Use 0xFFFF as ret_slot (no result to store — it's a tail call)
p = fresh()
emit(` %${p} =l call $cell_rt_goinvoke(l %ctx, l ${v})`)
chk = fresh()
emit(` %${chk} =w ceql %${p}, 15`)
if (has_handler) {
emit(` jnz %${chk}, @disruption_handler, @${chk}_ok`)
emit(`@${chk}_ok`)
refresh_fp()
emit(` ret %${p}`)
} else {
needs_exc_ret = true
emit(` jnz %${chk}, @_exc_ret, @${chk}_ok`)
emit(`@${chk}_ok`)
emit(` ret %${p}`)
}
emit(` %${p}_addr =l sub %fp, 8`)
emit(` storel ${text(65535 * 2)}, %${p}_addr`)
emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`)
emit(" ret 0")
last_was_term = true
continue
}
@@ -1267,10 +1359,12 @@ var qbe_emit = function(ir, qbe, export_name) {
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)})`)
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
}
@@ -1407,6 +1501,10 @@ var qbe_emit = function(ir, qbe, export_name) {
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)} }`)
return {
data: text(data_out, "\n"),
functions: fn_bodies,