native function type
This commit is contained in:
65
build.cm
65
build.cm
@@ -509,62 +509,21 @@ Build.build_static = function(packages, target, output, buildtype) {
|
|||||||
// il_parts: {data: text, functions: [text, ...]}
|
// il_parts: {data: text, functions: [text, ...]}
|
||||||
// cc: C compiler path
|
// cc: C compiler path
|
||||||
// tmp_prefix: prefix for temp files (e.g. /tmp/cell_native_<hash>)
|
// tmp_prefix: prefix for temp files (e.g. /tmp/cell_native_<hash>)
|
||||||
function compile_native_batched(il_parts, cc, tmp_prefix) {
|
function compile_native_single(il_parts, cc, tmp_prefix) {
|
||||||
var nfuncs = length(il_parts.functions)
|
|
||||||
var nbatch = 8
|
|
||||||
var o_paths = []
|
|
||||||
var s_paths = []
|
|
||||||
var asm_cmds = []
|
|
||||||
var batch_fns = null
|
|
||||||
var batch_il = null
|
|
||||||
var asm_text = null
|
|
||||||
var s_path = null
|
|
||||||
var o_path = null
|
|
||||||
var end = 0
|
|
||||||
var bi = 0
|
|
||||||
var fi = 0
|
|
||||||
var ai = 0
|
|
||||||
var rc = null
|
|
||||||
var parallel_cmd = null
|
|
||||||
var helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
|
var helpers_il = (il_parts.helpers && length(il_parts.helpers) > 0)
|
||||||
? text(il_parts.helpers, "\n") : ""
|
? text(il_parts.helpers, "\n") : ""
|
||||||
var prefix = null
|
var all_fns = text(il_parts.functions, "\n")
|
||||||
|
var full_il = il_parts.data + "\n\n" + helpers_il + "\n\n" + all_fns
|
||||||
if (nfuncs < nbatch) nbatch = nfuncs
|
var asm_text = os.qbe(full_il)
|
||||||
if (nbatch < 1) nbatch = 1
|
var s_path = tmp_prefix + '.s'
|
||||||
|
var o_path = tmp_prefix + '.o'
|
||||||
// Generate .s files: run QBE on each batch
|
var rc = null
|
||||||
while (bi < nbatch) {
|
|
||||||
batch_fns = []
|
|
||||||
end = nfuncs * (bi + 1) / nbatch
|
|
||||||
while (fi < end) {
|
|
||||||
batch_fns[] = il_parts.functions[fi]
|
|
||||||
fi = fi + 1
|
|
||||||
}
|
|
||||||
// Batch 0 includes helper functions; others reference them as external symbols
|
|
||||||
prefix = (bi == 0 && helpers_il != "") ? helpers_il + "\n\n" : ""
|
|
||||||
batch_il = il_parts.data + "\n\n" + prefix + text(batch_fns, "\n")
|
|
||||||
asm_text = os.qbe(batch_il)
|
|
||||||
s_path = tmp_prefix + '_b' + text(bi) + '.s'
|
|
||||||
o_path = tmp_prefix + '_b' + text(bi) + '.o'
|
|
||||||
fd.slurpwrite(s_path, stone(blob(asm_text)))
|
fd.slurpwrite(s_path, stone(blob(asm_text)))
|
||||||
s_paths[] = s_path
|
rc = os.system(cc + ' -c ' + s_path + ' -o ' + o_path)
|
||||||
o_paths[] = o_path
|
|
||||||
bi = bi + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble all batches in parallel
|
|
||||||
while (ai < length(s_paths)) {
|
|
||||||
asm_cmds[] = cc + ' -c ' + s_paths[ai] + ' -o ' + o_paths[ai]
|
|
||||||
ai = ai + 1
|
|
||||||
}
|
|
||||||
parallel_cmd = text(asm_cmds, ' & ') + ' & wait'
|
|
||||||
rc = os.system(parallel_cmd)
|
|
||||||
if (rc != 0) {
|
if (rc != 0) {
|
||||||
print('Parallel assembly failed'); disrupt
|
print('Assembly failed'); disrupt
|
||||||
}
|
}
|
||||||
|
return [o_path]
|
||||||
return o_paths
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post-process QBE IL: insert dead labels after ret/jmp (QBE requirement)
|
// Post-process QBE IL: insert dead labels after ret/jmp (QBE requirement)
|
||||||
@@ -651,7 +610,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
|
|||||||
var tmp = '/tmp/cell_native_' + hash
|
var tmp = '/tmp/cell_native_' + hash
|
||||||
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
||||||
|
|
||||||
var o_paths = compile_native_batched(il_parts, cc, tmp)
|
var o_paths = compile_native_single(il_parts, cc, tmp)
|
||||||
|
|
||||||
// Compile QBE runtime stubs if needed
|
// Compile QBE runtime stubs if needed
|
||||||
var rc = null
|
var rc = null
|
||||||
@@ -734,7 +693,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
|
|||||||
var tmp = '/tmp/cell_native_' + hash
|
var tmp = '/tmp/cell_native_' + hash
|
||||||
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
var rt_o_path = '/tmp/cell_qbe_rt.o'
|
||||||
|
|
||||||
var o_paths = compile_native_batched(il_parts, cc, tmp)
|
var o_paths = compile_native_single(il_parts, cc, tmp)
|
||||||
|
|
||||||
// Compile QBE runtime stubs if needed
|
// Compile QBE runtime stubs if needed
|
||||||
var rc = null
|
var rc = null
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ if host_machine.system() == 'darwin'
|
|||||||
foreach fkit : fworks
|
foreach fkit : fworks
|
||||||
deps += dependency('appleframeworks', modules: fkit)
|
deps += dependency('appleframeworks', modules: fkit)
|
||||||
endforeach
|
endforeach
|
||||||
# 32MB stack for deep native recursion (CPS patterns without TCO)
|
# Native code uses dispatch loop (no C stack recursion)
|
||||||
link += ['-Wl,-stack_size,0x2000000']
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if host_machine.system() == 'playdate'
|
if host_machine.system() == 'playdate'
|
||||||
|
|||||||
144
qbe_emit.cm
144
qbe_emit.cm
@@ -475,10 +475,10 @@ ${sw("w", "%fp2", "%result_slot", "%r")}
|
|||||||
ret 0
|
ret 0
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// function(ctx, fp, dest, fn_idx, 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) {
|
h[] = `export function l $__function_ss(l %ctx, l %fp, l %dest, l %fn_idx, l %arity, l %nr_slots) {
|
||||||
@entry
|
@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")}
|
${alloc_tail("%r")}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
@@ -680,11 +680,74 @@ var qbe_emit = function(ir, qbe, export_name) {
|
|||||||
var tol = null
|
var tol = null
|
||||||
var fn_arity = 0
|
var fn_arity = 0
|
||||||
var arity_tmp = null
|
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
|
// Function signature: (ctx, frame_ptr) → JSValue
|
||||||
emit(`export function l $${name}(l %ctx, l %fp) {`)
|
emit(`export function l $${name}(l %ctx, l %fp) {`)
|
||||||
emit("@entry")
|
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.
|
// GC-safe slot access: every read/write goes through frame memory.
|
||||||
// %fp may become stale after GC-triggering calls — use refresh_fp().
|
// %fp may become stale after GC-triggering calls — use refresh_fp().
|
||||||
var s_read = function(slot) {
|
var s_read = function(slot) {
|
||||||
@@ -1228,13 +1291,51 @@ var qbe_emit = function(ir, qbe, export_name) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (op == "invoke") {
|
if (op == "invoke") {
|
||||||
emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
// Dispatch loop invoke: store resume info, signal, return 0
|
||||||
emit_exc_check()
|
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
|
continue
|
||||||
}
|
}
|
||||||
if (op == "tail_invoke") {
|
if (op == "tail_invoke") {
|
||||||
emit(` %fp =l call $__invoke_ss(l %ctx, l %fp, l ${text(a1)}, l ${text(a2)})`)
|
// Same as invoke — dispatch loop regular call with resume
|
||||||
emit_exc_check()
|
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
|
continue
|
||||||
}
|
}
|
||||||
if (op == "goframe") {
|
if (op == "goframe") {
|
||||||
@@ -1243,22 +1344,13 @@ var qbe_emit = function(ir, qbe, export_name) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (op == "goinvoke") {
|
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()
|
p = fresh()
|
||||||
emit(` %${p} =l call $cell_rt_goinvoke(l %ctx, l ${v})`)
|
emit(` %${p}_addr =l sub %fp, 8`)
|
||||||
chk = fresh()
|
emit(` storel ${text(65535 * 2)}, %${p}_addr`)
|
||||||
emit(` %${chk} =w ceql %${p}, 15`)
|
emit(` call $cell_rt_signal_tail_call(l %ctx, l %fp, l ${text(a1)})`)
|
||||||
if (has_handler) {
|
emit(" ret 0")
|
||||||
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}`)
|
|
||||||
}
|
|
||||||
last_was_term = true
|
last_was_term = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1267,10 +1359,12 @@ var qbe_emit = function(ir, qbe, export_name) {
|
|||||||
|
|
||||||
if (op == "function") {
|
if (op == "function") {
|
||||||
fn_arity = 0
|
fn_arity = 0
|
||||||
|
fn_nr_slots = 0
|
||||||
if (a2 >= 0 && a2 < length(ir.functions)) {
|
if (a2 >= 0 && a2 < length(ir.functions)) {
|
||||||
fn_arity = ir.functions[a2].nr_args
|
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()
|
emit_exc_check()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1407,6 +1501,10 @@ var qbe_emit = function(ir, qbe, export_name) {
|
|||||||
compile_fn(ir.main, -1, true)
|
compile_fn(ir.main, -1, true)
|
||||||
fn_bodies[] = text(out, "\n")
|
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 {
|
return {
|
||||||
data: text(data_out, "\n"),
|
data: text(data_out, "\n"),
|
||||||
functions: fn_bodies,
|
functions: fn_bodies,
|
||||||
|
|||||||
@@ -490,6 +490,32 @@ JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue e
|
|||||||
return JS_MKPTR(fn);
|
return JS_MKPTR(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create a native (QBE-compiled) function */
|
||||||
|
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle,
|
||||||
|
uint16_t nr_slots, int arity, JSValue outer_frame) {
|
||||||
|
JSGCRef frame_ref;
|
||||||
|
JS_PushGCRef(ctx, &frame_ref);
|
||||||
|
frame_ref.val = outer_frame;
|
||||||
|
|
||||||
|
JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction));
|
||||||
|
if (!fn) {
|
||||||
|
JS_PopGCRef(ctx, &frame_ref);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
|
||||||
|
fn->kind = JS_FUNC_KIND_NATIVE;
|
||||||
|
fn->length = arity;
|
||||||
|
fn->name = JS_NULL;
|
||||||
|
fn->u.native.fn_ptr = fn_ptr;
|
||||||
|
fn->u.native.dl_handle = dl_handle;
|
||||||
|
fn->u.native.nr_slots = nr_slots;
|
||||||
|
fn->u.native.outer_frame = frame_ref.val;
|
||||||
|
|
||||||
|
JS_PopGCRef(ctx, &frame_ref);
|
||||||
|
return JS_MKPTR(fn);
|
||||||
|
}
|
||||||
|
|
||||||
/* Binary operations helper */
|
/* Binary operations helper */
|
||||||
static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) {
|
||||||
/* Fast path for integers */
|
/* Fast path for integers */
|
||||||
@@ -1924,12 +1950,14 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
|||||||
env = fn->u.reg.env_record;
|
env = fn->u.reg.env_record;
|
||||||
pc = code->entry_point;
|
pc = code->entry_point;
|
||||||
} else {
|
} else {
|
||||||
/* C or bytecode function: args already in fr->slots (GC-protected via frame chain) */
|
/* C, native, or bytecode function */
|
||||||
ctx->reg_current_frame = frame_ref.val;
|
ctx->reg_current_frame = frame_ref.val;
|
||||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||||
JSValue ret;
|
JSValue ret;
|
||||||
if (fn->kind == JS_FUNC_KIND_C)
|
if (fn->kind == JS_FUNC_KIND_C)
|
||||||
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
|
else if (fn->kind == JS_FUNC_KIND_NATIVE)
|
||||||
|
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
else
|
else
|
||||||
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
||||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
@@ -2007,12 +2035,14 @@ JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code,
|
|||||||
pc = code->entry_point;
|
pc = code->entry_point;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* C/bytecode function: call it, then return result to our caller */
|
/* C, native, or bytecode function: call it, then return result to our caller */
|
||||||
ctx->reg_current_frame = frame_ref.val;
|
ctx->reg_current_frame = frame_ref.val;
|
||||||
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
|
||||||
JSValue ret;
|
JSValue ret;
|
||||||
if (fn->kind == JS_FUNC_KIND_C)
|
if (fn->kind == JS_FUNC_KIND_C)
|
||||||
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
ret = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
|
else if (fn->kind == JS_FUNC_KIND_NATIVE)
|
||||||
|
ret = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
else
|
else
|
||||||
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
ret = JS_CallInternal(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1], 0);
|
||||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
|
||||||
|
|||||||
@@ -320,25 +320,34 @@ JSValue cell_rt_get_intrinsic(JSContext *ctx, const char *name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* --- Closure access ---
|
/* --- Closure access ---
|
||||||
Slot 511 in each frame stores the magic ID (registry index) of the
|
Walk the outer_frame chain on JSFunction (JS_FUNC_KIND_NATIVE).
|
||||||
function that owns this frame. cell_rt_get/put_closure re-derive
|
The frame's function field links to the JSFunction, whose
|
||||||
the enclosing frame from the function's GC ref at call time, so
|
u.native.outer_frame points to the enclosing frame.
|
||||||
pointers stay valid even if GC moves frames. */
|
GC traces outer_frame naturally — no registry needed. */
|
||||||
|
|
||||||
#define QBE_FRAME_OUTER_SLOT 511
|
/* Get the outer frame's slots from a frame pointer.
|
||||||
|
The frame's function must be JS_FUNC_KIND_NATIVE. */
|
||||||
static JSValue *derive_outer_fp(int magic);
|
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.native.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,
|
JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
||||||
int64_t slot) {
|
int64_t slot) {
|
||||||
|
(void)ctx;
|
||||||
JSValue *frame = (JSValue *)fp;
|
JSValue *frame = (JSValue *)fp;
|
||||||
for (int64_t d = 0; d < depth; d++) {
|
for (int64_t d = 0; d < depth; d++) {
|
||||||
/* fp[511] stores the magic ID (registry index) of the function
|
frame = get_outer_frame_slots(frame);
|
||||||
that owns this frame. derive_outer_fp re-derives the enclosing
|
|
||||||
frame from the function's GC ref, so it's always current even
|
|
||||||
if GC moved the frame. */
|
|
||||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
|
||||||
frame = derive_outer_fp(magic);
|
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return JS_NULL;
|
return JS_NULL;
|
||||||
}
|
}
|
||||||
@@ -347,42 +356,26 @@ JSValue cell_rt_get_closure(JSContext *ctx, void *fp, int64_t depth,
|
|||||||
|
|
||||||
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
void cell_rt_put_closure(JSContext *ctx, void *fp, JSValue val, int64_t depth,
|
||||||
int64_t slot) {
|
int64_t slot) {
|
||||||
|
(void)ctx;
|
||||||
JSValue *frame = (JSValue *)fp;
|
JSValue *frame = (JSValue *)fp;
|
||||||
for (int64_t d = 0; d < depth; d++) {
|
for (int64_t d = 0; d < depth; d++) {
|
||||||
int magic = (int)(int64_t)frame[QBE_FRAME_OUTER_SLOT];
|
frame = get_outer_frame_slots(frame);
|
||||||
frame = derive_outer_fp(magic);
|
|
||||||
if (!frame) return;
|
if (!frame) return;
|
||||||
}
|
}
|
||||||
frame[slot] = val;
|
frame[slot] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- GC-managed AOT frame stack ---
|
/* --- GC-managed AOT frame stack ---
|
||||||
Each AOT function call pushes a GC ref so the GC can find and
|
Each native dispatch loop pushes a GC ref so the GC can find and
|
||||||
update frame pointers when it moves objects. cell_rt_refresh_fp
|
update the current frame pointer when it moves objects.
|
||||||
re-derives the slot pointer after any GC-triggering call. */
|
cell_rt_refresh_fp re-derives the slot pointer after any GC call. */
|
||||||
|
|
||||||
#define MAX_AOT_DEPTH 65536
|
#define MAX_AOT_DEPTH 8192
|
||||||
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
|
static JSGCRef g_aot_gc_refs[MAX_AOT_DEPTH];
|
||||||
static int g_aot_depth = 0;
|
static int g_aot_depth = 0;
|
||||||
|
|
||||||
/* Check remaining C stack space to prevent segfaults from deep recursion */
|
|
||||||
static int stack_space_ok(void) {
|
|
||||||
#ifdef __APPLE__
|
|
||||||
char local;
|
|
||||||
void *stack_addr = pthread_get_stackaddr_np(pthread_self());
|
|
||||||
size_t stack_size = pthread_get_stacksize_np(pthread_self());
|
|
||||||
/* stack_addr is the TOP of the stack (highest address); stack grows down */
|
|
||||||
uintptr_t stack_bottom = (uintptr_t)stack_addr - stack_size;
|
|
||||||
uintptr_t current = (uintptr_t)&local;
|
|
||||||
/* Keep 128KB of reserve for unwinding and error handling */
|
|
||||||
return (current - stack_bottom) > (128 * 1024);
|
|
||||||
#else
|
|
||||||
return g_aot_depth < MAX_AOT_DEPTH;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
|
JSValue *cell_rt_enter_frame(JSContext *ctx, int64_t nr_slots) {
|
||||||
if (g_aot_depth >= MAX_AOT_DEPTH || !stack_space_ok()) {
|
if (g_aot_depth >= MAX_AOT_DEPTH) {
|
||||||
JS_ThrowTypeError(ctx, "native call stack overflow (depth %d)", g_aot_depth);
|
JS_ThrowTypeError(ctx, "native call stack overflow (depth %d)", g_aot_depth);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -411,9 +404,7 @@ JSValue *cell_rt_refresh_fp(JSContext *ctx) {
|
|||||||
return (JSValue *)frame->slots;
|
return (JSValue *)frame->slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Combined refresh + exception check in a single call.
|
/* Combined refresh + exception check in a single call. */
|
||||||
Returns the refreshed fp, or NULL if there is a pending exception.
|
|
||||||
This avoids QBE register-allocation issues from two consecutive calls. */
|
|
||||||
JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
|
JSValue *cell_rt_refresh_fp_checked(JSContext *ctx) {
|
||||||
if (JS_HasException(ctx))
|
if (JS_HasException(ctx))
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -439,126 +430,346 @@ void cell_rt_leave_frame(JSContext *ctx) {
|
|||||||
|
|
||||||
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
typedef JSValue (*cell_compiled_fn)(JSContext *ctx, void *fp);
|
||||||
|
|
||||||
/* Per-module function registry.
|
/* Set before executing a native module's cell_main —
|
||||||
Each native .cm module gets its own dylib. When a module creates closures
|
used by cell_rt_make_function to resolve fn_ptr via dlsym */
|
||||||
via cell_rt_make_function, we record the dylib handle so the trampoline
|
|
||||||
can look up the correct cell_fn_N in the right dylib. */
|
|
||||||
#define MAX_NATIVE_FN 32768
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
void *dl_handle;
|
|
||||||
int fn_idx;
|
|
||||||
JSGCRef frame_ref; /* independent GC ref for enclosing frame */
|
|
||||||
int has_frame_ref;
|
|
||||||
} g_native_fn_registry[MAX_NATIVE_FN];
|
|
||||||
|
|
||||||
static int g_native_fn_count = 0;
|
|
||||||
|
|
||||||
/* Set before executing a native module's cell_main */
|
|
||||||
static void *g_current_dl_handle = NULL;
|
static void *g_current_dl_handle = NULL;
|
||||||
|
|
||||||
/* Derive the outer frame's slots pointer from the closure's own GC ref.
|
/* ============================================================
|
||||||
Each closure keeps an independent GC ref so the enclosing frame
|
Dispatch loop — the core of native function execution.
|
||||||
survives even after cell_rt_leave_frame pops the stack ref. */
|
Each compiled cell_fn_N returns to this loop when it needs
|
||||||
static JSValue *derive_outer_fp(int magic) {
|
to call another function (instead of recursing via C stack).
|
||||||
if (!g_native_fn_registry[magic].has_frame_ref) return NULL;
|
============================================================ */
|
||||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(
|
|
||||||
g_native_fn_registry[magic].frame_ref.val);
|
/* Pending call state — set by cell_rt_signal_call / cell_rt_signal_tail_call,
|
||||||
return (JSValue *)frame->slots;
|
read by the dispatch loop. */
|
||||||
|
static JSValue g_pending_callee_frame = 0; /* JSFrameRegister ptr */
|
||||||
|
static int g_pending_is_tail = 0;
|
||||||
|
|
||||||
|
void cell_rt_signal_call(JSContext *ctx, void *fp, int64_t frame_slot) {
|
||||||
|
(void)ctx;
|
||||||
|
JSValue *slots = (JSValue *)fp;
|
||||||
|
g_pending_callee_frame = slots[frame_slot];
|
||||||
|
g_pending_is_tail = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reclaim_native_fns(JSContext *ctx, int saved_count) {
|
void cell_rt_signal_tail_call(JSContext *ctx, void *fp, int64_t frame_slot) {
|
||||||
/* Free GC refs for temporary closures created during a call */
|
(void)ctx;
|
||||||
for (int i = saved_count; i < g_native_fn_count; i++) {
|
JSValue *slots = (JSValue *)fp;
|
||||||
if (g_native_fn_registry[i].has_frame_ref) {
|
g_pending_callee_frame = slots[frame_slot];
|
||||||
JS_DeleteGCRef(ctx, &g_native_fn_registry[i].frame_ref);
|
g_pending_is_tail = 1;
|
||||||
g_native_fn_registry[i].has_frame_ref = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_native_fn_count = saved_count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static JSValue cell_fn_trampoline(JSContext *ctx, JSValue this_val,
|
/* Entry point called from JS_CallInternal / JS_Call / MACH_INVOKE
|
||||||
int argc, JSValue *argv, int magic) {
|
for JS_FUNC_KIND_NATIVE functions. */
|
||||||
if (magic < 0 || magic >= g_native_fn_count)
|
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj,
|
||||||
return JS_ThrowTypeError(ctx, "invalid native function id %d", magic);
|
JSValue this_obj, int argc, JSValue *argv) {
|
||||||
|
JSFunction *f = JS_VALUE_GET_FUNCTION(func_obj);
|
||||||
|
cell_compiled_fn fn = (cell_compiled_fn)f->u.native.fn_ptr;
|
||||||
|
int nr_slots = f->u.native.nr_slots;
|
||||||
|
int arity = f->length;
|
||||||
|
|
||||||
void *handle = g_native_fn_registry[magic].dl_handle;
|
/* Root func_obj across allocation — GC can move it */
|
||||||
int fn_idx = g_native_fn_registry[magic].fn_idx;
|
JSGCRef func_ref;
|
||||||
|
JS_PushGCRef(ctx, &func_ref);
|
||||||
|
func_ref.val = func_obj;
|
||||||
|
|
||||||
char name[64];
|
/* Allocate initial frame */
|
||||||
snprintf(name, sizeof(name), "cell_fn_%d", fn_idx);
|
JSValue *fp = cell_rt_enter_frame(ctx, nr_slots);
|
||||||
|
if (!fp) {
|
||||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(handle, name);
|
JS_PopGCRef(ctx, &func_ref);
|
||||||
if (!fn)
|
|
||||||
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
|
|
||||||
|
|
||||||
/* Allocate GC-managed frame: slot 0 = this, slots 1..argc = args */
|
|
||||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
|
||||||
if (!fp) return JS_EXCEPTION;
|
|
||||||
fp[0] = this_val;
|
|
||||||
for (int i = 0; i < argc && i < 510; i++)
|
|
||||||
fp[1 + i] = argv[i];
|
|
||||||
|
|
||||||
/* Store the magic ID (registry index) so cell_rt_get/put_closure
|
|
||||||
can re-derive the enclosing frame from the GC ref at call time,
|
|
||||||
surviving GC moves */
|
|
||||||
fp[QBE_FRAME_OUTER_SLOT] = (JSValue)(int64_t)magic;
|
|
||||||
|
|
||||||
/* Set g_current_dl_handle so any closures created during this call
|
|
||||||
(e.g. inner functions returned by factory functions) are registered
|
|
||||||
against the correct dylib */
|
|
||||||
void *prev_handle = g_current_dl_handle;
|
|
||||||
g_current_dl_handle = handle;
|
|
||||||
|
|
||||||
/* At top-level (depth 1 = this is the outermost native call),
|
|
||||||
save the fn count so we can reclaim temporary closures after */
|
|
||||||
int saved_fn_count = (g_aot_depth == 1) ? g_native_fn_count : -1;
|
|
||||||
|
|
||||||
JSValue result = fn(ctx, fp);
|
|
||||||
cell_rt_leave_frame(ctx);
|
|
||||||
g_current_dl_handle = prev_handle;
|
|
||||||
|
|
||||||
/* Reclaim temporary closures created during this top-level call */
|
|
||||||
if (saved_fn_count >= 0)
|
|
||||||
reclaim_native_fns(ctx, saved_fn_count);
|
|
||||||
|
|
||||||
if (result == JS_EXCEPTION) {
|
|
||||||
/* Ensure there is a pending exception. QBE @_exc_ret returns 15
|
|
||||||
but may not have set one (e.g. if cell_rt_enter_frame failed). */
|
|
||||||
if (!JS_HasException(ctx))
|
|
||||||
JS_Throw(ctx, JS_NULL);
|
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Re-derive func_obj after potential GC */
|
||||||
|
func_obj = func_ref.val;
|
||||||
|
JS_PopGCRef(ctx, &func_ref);
|
||||||
|
|
||||||
|
/* Set up frame: this in slot 0, args in slots 1..N */
|
||||||
|
fp[0] = this_obj;
|
||||||
|
int copy = (argc < arity) ? argc : arity;
|
||||||
|
if (copy < 0) copy = argc; /* variadic: copy all */
|
||||||
|
for (int i = 0; i < copy && i < nr_slots - 1; i++)
|
||||||
|
fp[1 + i] = argv[i];
|
||||||
|
|
||||||
|
/* Link function to frame for closure access */
|
||||||
|
JSFrameRegister *frame = (JSFrameRegister *)((char *)fp - offsetof(JSFrameRegister, slots));
|
||||||
|
frame->function = func_obj;
|
||||||
|
|
||||||
|
int base_depth = g_aot_depth; /* remember entry depth for return detection */
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
g_pending_callee_frame = 0;
|
||||||
|
|
||||||
|
JSValue result = fn(ctx, fp);
|
||||||
|
|
||||||
|
/* Re-derive frame after potential GC */
|
||||||
|
JSValue frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
|
||||||
|
if (g_pending_callee_frame != 0) {
|
||||||
|
/* Function signaled a call — dispatch it */
|
||||||
|
JSValue callee_frame_val = g_pending_callee_frame;
|
||||||
|
g_pending_callee_frame = 0;
|
||||||
|
JSFrameRegister *callee_fr = (JSFrameRegister *)JS_VALUE_GET_PTR(callee_frame_val);
|
||||||
|
int callee_argc = (int)objhdr_cap56(callee_fr->header);
|
||||||
|
callee_argc = (callee_argc >= 2) ? callee_argc - 2 : 0;
|
||||||
|
JSValue callee_fn_val = callee_fr->function;
|
||||||
|
|
||||||
|
if (!JS_IsFunction(callee_fn_val)) {
|
||||||
|
JS_ThrowTypeError(ctx, "not a function");
|
||||||
|
/* Resume caller with exception pending */
|
||||||
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSFunction *callee_fn = JS_VALUE_GET_FUNCTION(callee_fn_val);
|
||||||
|
|
||||||
|
if (callee_fn->kind == JS_FUNC_KIND_NATIVE) {
|
||||||
|
/* Native-to-native call — no C stack growth */
|
||||||
|
cell_compiled_fn callee_ptr = (cell_compiled_fn)callee_fn->u.native.fn_ptr;
|
||||||
|
int callee_slots = callee_fn->u.native.nr_slots;
|
||||||
|
|
||||||
|
if (g_pending_is_tail) {
|
||||||
|
/* Tail call: reuse or replace current frame */
|
||||||
|
if (callee_slots <= (int)objhdr_cap56(frame->header)) {
|
||||||
|
/* Reuse current frame */
|
||||||
|
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
|
||||||
|
if (cc < 0) cc = callee_argc;
|
||||||
|
frame->slots[0] = callee_fr->slots[0]; /* this */
|
||||||
|
for (int i = 0; i < cc && i < callee_slots - 1; i++)
|
||||||
|
frame->slots[1 + i] = callee_fr->slots[1 + i];
|
||||||
|
/* Null out remaining slots */
|
||||||
|
int cur_slots = (int)objhdr_cap56(frame->header);
|
||||||
|
for (int i = 1 + cc; i < cur_slots; i++)
|
||||||
|
frame->slots[i] = JS_NULL;
|
||||||
|
frame->function = callee_fn_val;
|
||||||
|
frame->address = JS_NewInt32(ctx, 0);
|
||||||
|
fn = callee_ptr;
|
||||||
|
/* fp stays the same (same frame) */
|
||||||
|
} else {
|
||||||
|
/* Need bigger frame — save callee info, pop+push */
|
||||||
|
JSValue saved_caller = frame->caller;
|
||||||
|
JSValue callee_this = callee_fr->slots[0];
|
||||||
|
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
|
||||||
|
if (cc < 0) cc = callee_argc;
|
||||||
|
JSValue callee_args[cc > 0 ? cc : 1];
|
||||||
|
for (int i = 0; i < cc; i++)
|
||||||
|
callee_args[i] = callee_fr->slots[1 + i];
|
||||||
|
|
||||||
|
/* Pop old frame */
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
|
||||||
|
/* Push new right-sized frame */
|
||||||
|
JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots);
|
||||||
|
if (!new_fp)
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots));
|
||||||
|
new_frame->function = callee_fn_val;
|
||||||
|
new_frame->caller = saved_caller;
|
||||||
|
new_frame->slots[0] = callee_this;
|
||||||
|
for (int i = 0; i < cc && i < callee_slots - 1; i++)
|
||||||
|
new_frame->slots[1 + i] = callee_args[i];
|
||||||
|
frame = new_frame;
|
||||||
|
fp = new_fp;
|
||||||
|
fn = callee_ptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Regular call: push new frame, link caller */
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int resume_seg = ret_info >> 16;
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
|
||||||
|
/* Save callee info before allocation */
|
||||||
|
JSValue callee_this = callee_fr->slots[0];
|
||||||
|
int cc = (callee_argc < callee_fn->length) ? callee_argc : callee_fn->length;
|
||||||
|
if (cc < 0) cc = callee_argc;
|
||||||
|
JSValue callee_args[cc > 0 ? cc : 1];
|
||||||
|
for (int i = 0; i < cc; i++)
|
||||||
|
callee_args[i] = callee_fr->slots[1 + i];
|
||||||
|
|
||||||
|
JSValue *new_fp = cell_rt_enter_frame(ctx, callee_slots);
|
||||||
|
if (!new_fp) {
|
||||||
|
/* Resume caller with exception pending */
|
||||||
|
frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Re-derive caller frame after alloc */
|
||||||
|
frame_val = g_aot_gc_refs[g_aot_depth - 2].val;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||||
|
|
||||||
|
JSFrameRegister *new_frame = (JSFrameRegister *)((char *)new_fp - offsetof(JSFrameRegister, slots));
|
||||||
|
new_frame->function = callee_fn_val;
|
||||||
|
new_frame->caller = JS_MKPTR(frame);
|
||||||
|
new_frame->slots[0] = callee_this;
|
||||||
|
for (int i = 0; i < cc && i < callee_slots - 1; i++)
|
||||||
|
new_frame->slots[1 + i] = callee_args[i];
|
||||||
|
|
||||||
|
/* Save return address in caller */
|
||||||
|
frame->address = JS_NewInt32(ctx, (resume_seg << 16) | ret_slot);
|
||||||
|
|
||||||
|
frame = new_frame;
|
||||||
|
fp = new_fp;
|
||||||
|
fn = callee_ptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Non-native callee (C function, register VM, etc.) —
|
||||||
|
call it via the standard path and store the result */
|
||||||
|
JSValue ret;
|
||||||
|
if (callee_fn->kind == JS_FUNC_KIND_C)
|
||||||
|
ret = js_call_c_function(ctx, callee_fn_val, callee_fr->slots[0],
|
||||||
|
callee_argc, &callee_fr->slots[1]);
|
||||||
|
else
|
||||||
|
ret = JS_CallInternal(ctx, callee_fn_val, callee_fr->slots[0],
|
||||||
|
callee_argc, &callee_fr->slots[1], 0);
|
||||||
|
|
||||||
|
/* Re-derive frame after call */
|
||||||
|
frame_val = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
|
||||||
|
if (JS_IsException(ret)) {
|
||||||
|
/* Non-native callee threw — resume caller with exception pending.
|
||||||
|
The caller's generated code checks JS_HasException at resume. */
|
||||||
|
if (!JS_HasException(ctx))
|
||||||
|
JS_Throw(ctx, JS_NULL);
|
||||||
|
/* fn and fp still point to the calling native function's frame.
|
||||||
|
Just resume it — it will detect the exception. */
|
||||||
|
JSFunction *exc_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)exc_fn->u.native.fn_ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Clear stale exception */
|
||||||
|
if (JS_HasException(ctx))
|
||||||
|
JS_GetException(ctx);
|
||||||
|
|
||||||
|
if (g_pending_is_tail) {
|
||||||
|
/* Tail call to non-native: return its result up the chain */
|
||||||
|
/* Pop current frame and return to caller */
|
||||||
|
if (g_aot_depth <= base_depth) {
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
/* Pop current frame, return to caller frame */
|
||||||
|
JSValue caller_val = frame->caller;
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
if (JS_IsNull(caller_val) || g_aot_depth < base_depth) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(caller_val);
|
||||||
|
/* Update GC ref to point to caller */
|
||||||
|
g_aot_gc_refs[g_aot_depth - 1].val = caller_val;
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
if (ret_slot != 0xFFFF)
|
||||||
|
fp[ret_slot] = ret;
|
||||||
|
/* Resume caller */
|
||||||
|
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)caller_fn->u.native.fn_ptr;
|
||||||
|
} else {
|
||||||
|
/* Regular call: store result and resume current function */
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
if (ret_slot != 0xFFFF)
|
||||||
|
fp[ret_slot] = ret;
|
||||||
|
/* fn stays the same — we resume the same function at next segment */
|
||||||
|
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)cur_fn->u.native.fn_ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No pending call — function returned a value or exception */
|
||||||
|
if (result == JS_EXCEPTION) {
|
||||||
|
/* Exception: pop this frame and propagate to caller.
|
||||||
|
The caller's generated code has exception checks at resume points. */
|
||||||
|
if (!JS_HasException(ctx))
|
||||||
|
JS_Throw(ctx, JS_NULL);
|
||||||
|
|
||||||
|
if (g_aot_depth <= base_depth) {
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue exc_caller_val = frame->caller;
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
|
||||||
|
if (JS_IsNull(exc_caller_val) || g_aot_depth < base_depth) {
|
||||||
|
return JS_EXCEPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resume caller — it will check JS_HasException and branch to handler */
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(exc_caller_val);
|
||||||
|
g_aot_gc_refs[g_aot_depth - 1].val = exc_caller_val;
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
|
||||||
|
JSFunction *exc_caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)exc_caller_fn->u.native.fn_ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Normal return — pop frame and store result in caller */
|
||||||
|
if (g_aot_depth <= base_depth) {
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue caller_val = frame->caller;
|
||||||
|
cell_rt_leave_frame(ctx);
|
||||||
|
|
||||||
|
if (JS_IsNull(caller_val) || g_aot_depth < base_depth) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return to caller frame */
|
||||||
|
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(caller_val);
|
||||||
|
g_aot_gc_refs[g_aot_depth - 1].val = caller_val;
|
||||||
|
fp = (JSValue *)frame->slots;
|
||||||
|
int ret_info = JS_VALUE_GET_INT(frame->address);
|
||||||
|
int ret_slot = ret_info & 0xFFFF;
|
||||||
|
if (ret_slot != 0xFFFF)
|
||||||
|
fp[ret_slot] = result;
|
||||||
|
|
||||||
|
JSFunction *caller_fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||||
|
fn = (cell_compiled_fn)caller_fn->u.native.fn_ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create a native function object from a compiled fn_idx.
|
||||||
|
Called from QBE-generated code during function creation. */
|
||||||
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
|
JSValue cell_rt_make_function(JSContext *ctx, int64_t fn_idx, void *outer_fp,
|
||||||
int64_t nr_args) {
|
int64_t nr_args, int64_t nr_slots) {
|
||||||
(void)outer_fp;
|
if (!g_current_dl_handle)
|
||||||
if (g_native_fn_count >= MAX_NATIVE_FN)
|
return JS_ThrowTypeError(ctx, "no native module loaded");
|
||||||
return JS_ThrowTypeError(ctx, "too many native functions (max %d)", MAX_NATIVE_FN);
|
|
||||||
|
|
||||||
int global_id = g_native_fn_count++;
|
/* Resolve fn_ptr via dlsym at creation time — cached in the function object */
|
||||||
g_native_fn_registry[global_id].dl_handle = g_current_dl_handle;
|
char name[64];
|
||||||
g_native_fn_registry[global_id].fn_idx = (int)fn_idx;
|
snprintf(name, sizeof(name), "cell_fn_%lld", (long long)fn_idx);
|
||||||
|
void *fn_ptr = dlsym(g_current_dl_handle, name);
|
||||||
|
if (!fn_ptr)
|
||||||
|
return JS_ThrowTypeError(ctx, "native function %s not found in dylib", name);
|
||||||
|
|
||||||
/* Create independent GC ref so the enclosing frame survives
|
/* Get the current frame as outer_frame for closures */
|
||||||
even after cell_rt_leave_frame pops the stack ref */
|
JSValue outer_frame = JS_NULL;
|
||||||
if (g_aot_depth > 0) {
|
if (g_aot_depth > 0)
|
||||||
JSGCRef *ref = &g_native_fn_registry[global_id].frame_ref;
|
outer_frame = g_aot_gc_refs[g_aot_depth - 1].val;
|
||||||
JS_AddGCRef(ctx, ref);
|
|
||||||
ref->val = g_aot_gc_refs[g_aot_depth - 1].val;
|
return js_new_native_function(ctx, fn_ptr, g_current_dl_handle,
|
||||||
g_native_fn_registry[global_id].has_frame_ref = 1;
|
(uint16_t)nr_slots, (int)nr_args, outer_frame);
|
||||||
} else {
|
|
||||||
g_native_fn_registry[global_id].has_frame_ref = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JS_NewCFunction2(ctx, (JSCFunction *)cell_fn_trampoline, "native_fn",
|
/* --- Frame-based function calling ---
|
||||||
(int)nr_args, JS_CFUNC_generic_magic, global_id);
|
Still used by QBE-generated code for building call frames
|
||||||
}
|
before signaling the dispatch loop. */
|
||||||
|
|
||||||
/* --- Frame-based function calling --- */
|
|
||||||
|
|
||||||
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
JSValue cell_rt_frame(JSContext *ctx, JSValue fn, int64_t nargs) {
|
||||||
if (!JS_IsFunction(fn)) {
|
if (!JS_IsFunction(fn)) {
|
||||||
@@ -578,6 +789,7 @@ void cell_rt_setarg(JSValue frame_val, int64_t idx, JSValue val) {
|
|||||||
fr->slots[idx] = val;
|
fr->slots[idx] = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* cell_rt_invoke — still used for non-dispatch-loop paths (e.g. old code) */
|
||||||
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
||||||
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
|
if (frame_val == JS_EXCEPTION) return JS_EXCEPTION;
|
||||||
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
JSFrameRegister *fr = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_val);
|
||||||
@@ -594,11 +806,10 @@ JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
|||||||
JSValue result;
|
JSValue result;
|
||||||
|
|
||||||
if (fn->kind == JS_FUNC_KIND_C) {
|
if (fn->kind == JS_FUNC_KIND_C) {
|
||||||
/* Match MACH_INVOKE: C functions go directly to js_call_c_function,
|
|
||||||
bypassing JS_Call's arity check. Extra args are silently available. */
|
|
||||||
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
result = js_call_c_function(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
|
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
||||||
|
result = cell_native_dispatch(ctx, fn_val, fr->slots[0], c_argc, &fr->slots[1]);
|
||||||
} else {
|
} else {
|
||||||
/* Register/bytecode functions — use JS_CallInternal (no arity gate) */
|
|
||||||
JSValue args[c_argc > 0 ? c_argc : 1];
|
JSValue args[c_argc > 0 ? c_argc : 1];
|
||||||
for (int i = 0; i < c_argc; i++)
|
for (int i = 0; i < c_argc; i++)
|
||||||
args[i] = fr->slots[i + 1];
|
args[i] = fr->slots[i + 1];
|
||||||
@@ -607,9 +818,6 @@ JSValue cell_rt_invoke(JSContext *ctx, JSValue frame_val) {
|
|||||||
|
|
||||||
if (JS_IsException(result))
|
if (JS_IsException(result))
|
||||||
return JS_EXCEPTION;
|
return JS_EXCEPTION;
|
||||||
/* Clear any stale exception left by functions that returned a valid
|
|
||||||
value despite internal error (e.g., sign("text") returns null
|
|
||||||
but JS_ToFloat64 leaves an exception flag) */
|
|
||||||
if (JS_HasException(ctx))
|
if (JS_HasException(ctx))
|
||||||
JS_GetException(ctx);
|
JS_GetException(ctx);
|
||||||
return result;
|
return result;
|
||||||
@@ -765,8 +973,11 @@ void cell_rt_clear_exception(JSContext *ctx) {
|
|||||||
|
|
||||||
/* --- Disruption --- */
|
/* --- Disruption --- */
|
||||||
|
|
||||||
|
/* Disrupt: silently set exception flag like the bytecode VM does.
|
||||||
|
Does NOT call JS_ThrowTypeError — that would print to stderr
|
||||||
|
even when a disruption handler will catch it. */
|
||||||
void cell_rt_disrupt(JSContext *ctx) {
|
void cell_rt_disrupt(JSContext *ctx) {
|
||||||
JS_ThrowTypeError(ctx, "type error in native code");
|
JS_Throw(ctx, JS_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- in: key in obj --- */
|
/* --- in: key in obj --- */
|
||||||
@@ -793,67 +1004,72 @@ JSValue cell_rt_regexp(JSContext *ctx, const char *pattern, const char *flags) {
|
|||||||
Looks up cell_main, builds a heap-allocated frame, sets
|
Looks up cell_main, builds a heap-allocated frame, sets
|
||||||
g_current_dl_handle so closures register in the right module. */
|
g_current_dl_handle so closures register in the right module. */
|
||||||
|
|
||||||
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env) {
|
/* Helper: run a native module's entry point through the dispatch loop.
|
||||||
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
Creates a temporary JS_FUNC_KIND_NATIVE function so that the full
|
||||||
if (!fn)
|
dispatch loop (tail calls, closures, etc.) works for module-level code. */
|
||||||
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
|
static JSValue native_module_run(JSContext *ctx, void *dl_handle,
|
||||||
|
cell_compiled_fn entry, int nr_slots) {
|
||||||
/* Set current handle so cell_rt_make_function registers closures
|
|
||||||
against this module's dylib */
|
|
||||||
void *prev_handle = g_current_dl_handle;
|
void *prev_handle = g_current_dl_handle;
|
||||||
g_current_dl_handle = dl_handle;
|
g_current_dl_handle = dl_handle;
|
||||||
|
|
||||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
/* Create a native function object for the entry point */
|
||||||
cell_rt_set_native_env(ctx, env);
|
JSValue func_obj = js_new_native_function(ctx, (void *)entry, dl_handle,
|
||||||
|
(uint16_t)nr_slots, 0, JS_NULL);
|
||||||
/* GC-managed frame for module execution */
|
if (JS_IsException(func_obj)) {
|
||||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
|
||||||
if (!fp) {
|
|
||||||
g_current_dl_handle = prev_handle;
|
g_current_dl_handle = prev_handle;
|
||||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
return JS_EXCEPTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Clear any stale exception left by a previous interpreted run */
|
/* Clear any stale exception left by a previous interpreted run */
|
||||||
if (JS_HasException(ctx))
|
if (JS_HasException(ctx))
|
||||||
JS_GetException(ctx);
|
JS_GetException(ctx);
|
||||||
|
|
||||||
JSValue result = fn(ctx, fp);
|
JSValue result = cell_native_dispatch(ctx, func_obj, JS_NULL, 0, NULL);
|
||||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
|
||||||
g_current_dl_handle = prev_handle;
|
g_current_dl_handle = prev_handle;
|
||||||
if (result == JS_EXCEPTION)
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JSValue cell_rt_native_module_load(JSContext *ctx, void *dl_handle, JSValue env) {
|
||||||
|
cell_compiled_fn fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||||
|
if (!fn)
|
||||||
|
return JS_ThrowTypeError(ctx, "cell_main not found in native module dylib");
|
||||||
|
|
||||||
|
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||||
|
cell_rt_set_native_env(ctx, env);
|
||||||
|
|
||||||
|
/* Try to read nr_slots from the module (exported by emitter) */
|
||||||
|
int *slots_ptr = (int *)dlsym(dl_handle, "cell_main_nr_slots");
|
||||||
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
||||||
|
|
||||||
|
return native_module_run(ctx, dl_handle, fn, nr_slots);
|
||||||
|
}
|
||||||
|
|
||||||
/* Load a native module from a dylib handle, trying a named symbol first.
|
/* Load a native module from a dylib handle, trying a named symbol first.
|
||||||
Falls back to cell_main if the named symbol is not found. */
|
Falls back to cell_main if the named symbol is not found. */
|
||||||
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env) {
|
JSValue cell_rt_native_module_load_named(JSContext *ctx, void *dl_handle, const char *sym_name, JSValue env) {
|
||||||
cell_compiled_fn fn = NULL;
|
cell_compiled_fn fn = NULL;
|
||||||
if (sym_name)
|
const char *used_name = NULL;
|
||||||
|
if (sym_name) {
|
||||||
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
fn = (cell_compiled_fn)dlsym(dl_handle, sym_name);
|
||||||
if (!fn)
|
if (fn) used_name = sym_name;
|
||||||
|
}
|
||||||
|
if (!fn) {
|
||||||
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
fn = (cell_compiled_fn)dlsym(dl_handle, "cell_main");
|
||||||
|
used_name = "cell_main";
|
||||||
|
}
|
||||||
if (!fn)
|
if (!fn)
|
||||||
return JS_ThrowTypeError(ctx, "symbol not found in native module dylib");
|
return JS_ThrowTypeError(ctx, "symbol not found in native module dylib");
|
||||||
|
|
||||||
void *prev_handle = g_current_dl_handle;
|
|
||||||
g_current_dl_handle = dl_handle;
|
|
||||||
|
|
||||||
/* Make env available for cell_rt_get_intrinsic lookups */
|
/* Make env available for cell_rt_get_intrinsic lookups */
|
||||||
cell_rt_set_native_env(ctx, env);
|
cell_rt_set_native_env(ctx, env);
|
||||||
|
|
||||||
JSValue *fp = cell_rt_enter_frame(ctx, 512);
|
/* Try to read nr_slots from the module */
|
||||||
if (!fp) {
|
char slots_sym[128];
|
||||||
g_current_dl_handle = prev_handle;
|
snprintf(slots_sym, sizeof(slots_sym), "%s_nr_slots", used_name);
|
||||||
return JS_ThrowTypeError(ctx, "frame allocation failed");
|
int *slots_ptr = (int *)dlsym(dl_handle, slots_sym);
|
||||||
}
|
int nr_slots = slots_ptr ? *slots_ptr : 512;
|
||||||
|
|
||||||
JSValue result = fn(ctx, fp);
|
return native_module_run(ctx, dl_handle, fn, nr_slots);
|
||||||
cell_rt_leave_frame(ctx); /* safe — closures have independent GC refs */
|
|
||||||
g_current_dl_handle = prev_handle;
|
|
||||||
if (result == JS_EXCEPTION)
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Backward-compat: uses RTLD_DEFAULT (works when dylib opened with RTLD_GLOBAL) */
|
/* Backward-compat: uses RTLD_DEFAULT (works when dylib opened with RTLD_GLOBAL) */
|
||||||
|
|||||||
@@ -1322,6 +1322,7 @@ typedef enum {
|
|||||||
JS_FUNC_KIND_BYTECODE,
|
JS_FUNC_KIND_BYTECODE,
|
||||||
JS_FUNC_KIND_C_DATA,
|
JS_FUNC_KIND_C_DATA,
|
||||||
JS_FUNC_KIND_REGISTER, /* register-based VM function */
|
JS_FUNC_KIND_REGISTER, /* register-based VM function */
|
||||||
|
JS_FUNC_KIND_NATIVE, /* QBE-compiled native function */
|
||||||
} JSFunctionKind;
|
} JSFunctionKind;
|
||||||
|
|
||||||
typedef struct JSFunction {
|
typedef struct JSFunction {
|
||||||
@@ -1340,6 +1341,12 @@ typedef struct JSFunction {
|
|||||||
JSValue env_record; /* stone record, module environment */
|
JSValue env_record; /* stone record, module environment */
|
||||||
JSValue outer_frame; /* JSFrame JSValue, for closures */
|
JSValue outer_frame; /* JSFrame JSValue, for closures */
|
||||||
} reg;
|
} reg;
|
||||||
|
struct {
|
||||||
|
void *fn_ptr; /* compiled cell_fn_N pointer */
|
||||||
|
void *dl_handle; /* dylib handle for dlsym lookups */
|
||||||
|
uint16_t nr_slots; /* frame size for this function */
|
||||||
|
JSValue outer_frame; /* GC-traced, for closures */
|
||||||
|
} native;
|
||||||
} u;
|
} u;
|
||||||
} JSFunction;
|
} JSFunction;
|
||||||
|
|
||||||
@@ -1362,6 +1369,7 @@ typedef struct JSFunction {
|
|||||||
JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
|
JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
|
||||||
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
|
JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags);
|
||||||
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
||||||
|
JSValue cell_native_dispatch(JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv);
|
||||||
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
|
int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop);
|
||||||
JSValue __attribute__ ((format (printf, 2, 3)))
|
JSValue __attribute__ ((format (printf, 2, 3)))
|
||||||
JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...);
|
JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...);
|
||||||
@@ -1652,6 +1660,7 @@ JSValue js_key_from_string (JSContext *ctx, JSValue val);
|
|||||||
|
|
||||||
/* mach.c exports */
|
/* mach.c exports */
|
||||||
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame);
|
||||||
|
JSValue js_new_native_function(JSContext *ctx, void *fn_ptr, void *dl_handle, uint16_t nr_slots, int arity, JSValue outer_frame);
|
||||||
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
|
JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count);
|
||||||
int reg_vm_check_interrupt(JSContext *ctx);
|
int reg_vm_check_interrupt(JSContext *ctx);
|
||||||
|
|
||||||
|
|||||||
@@ -1442,6 +1442,8 @@ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *fro
|
|||||||
/* Scan outer_frame and env_record */
|
/* Scan outer_frame and env_record */
|
||||||
fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
||||||
fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end);
|
fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end);
|
||||||
|
} else if (fn->kind == JS_FUNC_KIND_NATIVE) {
|
||||||
|
fn->u.native.outer_frame = gc_copy_value (ctx, fn->u.native.outer_frame, from_base, from_end, to_base, to_free, to_end);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -4732,6 +4734,8 @@ JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj,
|
|||||||
case JS_FUNC_KIND_REGISTER:
|
case JS_FUNC_KIND_REGISTER:
|
||||||
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
|
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
|
||||||
f->u.reg.env_record, f->u.reg.outer_frame);
|
f->u.reg.env_record, f->u.reg.outer_frame);
|
||||||
|
case JS_FUNC_KIND_NATIVE:
|
||||||
|
return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv);
|
||||||
default:
|
default:
|
||||||
return JS_ThrowTypeError (ctx, "not a function");
|
return JS_ThrowTypeError (ctx, "not a function");
|
||||||
}
|
}
|
||||||
@@ -4753,6 +4757,8 @@ JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, J
|
|||||||
case JS_FUNC_KIND_REGISTER:
|
case JS_FUNC_KIND_REGISTER:
|
||||||
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
|
return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv,
|
||||||
f->u.reg.env_record, f->u.reg.outer_frame);
|
f->u.reg.env_record, f->u.reg.outer_frame);
|
||||||
|
case JS_FUNC_KIND_NATIVE:
|
||||||
|
return cell_native_dispatch (ctx, func_obj, this_obj, argc, argv);
|
||||||
default:
|
default:
|
||||||
return JS_ThrowTypeError (ctx, "not a function");
|
return JS_ThrowTypeError (ctx, "not a function");
|
||||||
}
|
}
|
||||||
|
|||||||
21
vm_suite.ce
21
vm_suite.ce
@@ -827,6 +827,27 @@ run("disruption handler accesses object from outer scope", function() {
|
|||||||
if (obj.y != 20) fail("handler mutation lost, y=" + text(obj.y))
|
if (obj.y != 20) fail("handler mutation lost, y=" + text(obj.y))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
run("disruption in callback with multiple calls after", function() {
|
||||||
|
// Regression: a function with a disruption handler that calls a
|
||||||
|
// callback which disrupts, followed by more successful calls.
|
||||||
|
// In native mode, cell_rt_disrupt must NOT use JS_ThrowTypeError
|
||||||
|
// (which prints to stderr) — it must silently set the exception.
|
||||||
|
var log = []
|
||||||
|
var run_inner = function(name, fn) {
|
||||||
|
fn()
|
||||||
|
log[] = "pass:" + name
|
||||||
|
} disruption {
|
||||||
|
log[] = "fail:" + name
|
||||||
|
}
|
||||||
|
run_inner("a", function() { var x = 1 })
|
||||||
|
run_inner("b", function() { disrupt })
|
||||||
|
run_inner("c", function() { var y = 2 })
|
||||||
|
if (length(log) != 3) fail("expected 3 log entries, got " + text(length(log)))
|
||||||
|
if (log[0] != "pass:a") fail("expected pass:a, got " + log[0])
|
||||||
|
if (log[1] != "fail:b") fail("expected fail:b, got " + log[1])
|
||||||
|
if (log[2] != "pass:c") fail("expected pass:c, got " + log[2])
|
||||||
|
})
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// TYPE CHECKING WITH is_* FUNCTIONS
|
// TYPE CHECKING WITH is_* FUNCTIONS
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user