native function type
This commit is contained in:
144
qbe_emit.cm
144
qbe_emit.cm
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user