LibJS: Sync AsmInt program counter before slow paths

Move the execution context program counter update from ASM_TRY() to the
generated slow-path call boundary. Slow paths still enter C++ with the
current bytecode offset visible to stack and source location code, while
ASM_TRY() only handles completion unwrapping and exception dispatch.
This commit is contained in:
Andreas Kling 2026-06-14 14:46:23 +02:00 committed by Andreas Kling
parent ad7002ba99
commit 0af548b27d
3 changed files with 28 additions and 11 deletions

View file

@ -386,6 +386,7 @@ fn generate_fallback_handler(out: &mut String, program: &Program, _pinned: &Pinn
// Set up args: x0=vm (x20), w1=pc (ip - pb), x2=instruction (ip)
w!(out, " mov x0, x20");
w!(out, " sub w1, w21, w26");
emit_sync_pc_to_execution_context(out, program);
w!(out, " mov x2, x21");
w!(out, " bl CSYM(asm_fallback_handler)");
// Check for exit (return < 0)
@ -430,6 +431,15 @@ fn emit_state_reload(out: &mut String, program: &Program) {
emit_add_imm(out, "x27", "x28", sizeof_execctx);
}
fn emit_sync_pc_to_execution_context(out: &mut String, program: &Program) {
let program_counter = program
.constants
.get("EXECUTION_CONTEXT_PROGRAM_COUNTER")
.copied()
.expect("EXECUTION_CONTEXT_PROGRAM_COUNTER constant required");
emit_str32(out, "w1", "x28", program_counter);
}
/// Emit a dispatch sequence: recompute x21 from w25 + x26, then dispatch.
/// Used only by dispatch_current (where DSL code has written to x25 directly).
fn emit_dispatch(out: &mut String) {
@ -1347,6 +1357,7 @@ fn emit_instruction(
if let Some(Operand::Register(func_name)) = insn.operands.first() {
w!(out, " mov x0, x20"); // vm
w!(out, " sub w1, w21, w26"); // pc = ip - pb
emit_sync_pc_to_execution_context(out, program);
w!(out, " mov x2, x21"); // instruction
w!(out, " bl CSYM({func_name})");
w!(out, " tbnz x0, #63, .Lexit");

View file

@ -309,6 +309,7 @@ fn generate_fallback_handler(out: &mut String, program: &Program, abi: X86_64Abi
// Returns >= 0: new pc to dispatch to. Returns < 0: exit.
w!(out, ".p2align 4");
w!(out, "asm_handler_fallback:");
emit_sync_pc_to_execution_context(out, program);
emit_vm_pc_instruction_args(out, abi);
w!(out, " call CSYM(asm_fallback_handler)");
// Check for exit (return < 0)
@ -388,6 +389,15 @@ fn emit_vm_pc_instruction_args(out: &mut String, abi: X86_64Abi) {
}
}
fn emit_sync_pc_to_execution_context(out: &mut String, program: &Program) {
let program_counter = program
.constants
.get("EXECUTION_CONTEXT_PROGRAM_COUNTER")
.copied()
.expect("EXECUTION_CONTEXT_PROGRAM_COUNTER constant required");
w!(out, " mov DWORD PTR [rbx + {program_counter}], r13d");
}
fn emit_dispatch(out: &mut String) {
w!(out, " movzx eax, BYTE PTR [r14 + r13]");
w!(out, " jmp [r12 + rax * 8]");
@ -765,6 +775,7 @@ fn emit_instruction(
// context, since exception handling may have unwound inline frames.
"call_slow_path" => {
if let Some(Operand::Register(func_name)) = insn.operands.first() {
emit_sync_pc_to_execution_context(out, program);
emit_vm_pc_instruction_args(out, abi);
w!(out, " call CSYM({func_name})");
// Check for exit (return < 0)

View file

@ -60,7 +60,6 @@ static i64 handle_asm_exception(VM& vm, u32 pc, Value exception)
({ \
auto& asm_try_vm = (vm); \
auto asm_try_pc = (pc); \
asm_try_vm.running_execution_context().program_counter = asm_try_pc; \
auto&& asm_try_result = (expression); \
if (asm_try_result.is_error()) [[unlikely]] \
return handle_asm_exception(asm_try_vm, asm_try_pc, asm_try_result.release_error().value()); \
@ -803,18 +802,18 @@ i64 asm_slow_path_decrement(VM* vm, u32 pc, Op::Decrement const* instruction)
return static_cast<i64>(instruction->false_target().address()); \
}
DEFINE_JUMP_COMPARISON_SLOW_PATH(less_than, LessThan, less_than(VM::the(), lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(greater_than, GreaterThan, greater_than(VM::the(), lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(less_than_equals, LessThanEquals, less_than_equals(VM::the(), lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(greater_than_equals, GreaterThanEquals, greater_than_equals(VM::the(), lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(loosely_equals, LooselyEquals, is_loosely_equal(VM::the(), lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(less_than, LessThan, less_than(*vm, lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(greater_than, GreaterThan, greater_than(*vm, lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(less_than_equals, LessThanEquals, less_than_equals(*vm, lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(greater_than_equals, GreaterThanEquals, greater_than_equals(*vm, lhs, rhs))
DEFINE_JUMP_COMPARISON_SLOW_PATH(loosely_equals, LooselyEquals, is_loosely_equal(*vm, lhs, rhs))
#undef DEFINE_JUMP_COMPARISON_SLOW_PATH
i64 asm_slow_path_jump_loosely_inequals(VM* vm, u32 pc, Op::JumpLooselyInequals const* instruction)
{
auto lhs = vm->get(instruction->lhs());
auto rhs = vm->get(instruction->rhs());
if (!ASM_TRY(*vm, pc, is_loosely_equal(VM::the(), lhs, rhs)))
if (!ASM_TRY(*vm, pc, is_loosely_equal(*vm, lhs, rhs)))
return static_cast<i64>(instruction->true_target().address());
return static_cast<i64>(instruction->false_target().address());
}
@ -2963,8 +2962,6 @@ i64 asm_slow_path_resolve_this_binding(VM* vm, u32 pc, Op::ResolveThisBinding co
// Direct handler for GetPrivateById: bypasses Reference indirection.
i64 asm_slow_path_get_private_by_id(VM* vm, u32 pc, Op::GetPrivateById const* instruction)
{
vm->running_execution_context().program_counter = pc;
auto base_value = vm->get(instruction->base());
auto& current_vm = *vm;
@ -2989,8 +2986,6 @@ i64 asm_slow_path_get_private_by_id(VM* vm, u32 pc, Op::GetPrivateById const* in
// Direct handler for PutPrivateById: bypasses Reference indirection.
i64 asm_slow_path_put_private_by_id(VM* vm, u32 pc, Op::PutPrivateById const* instruction)
{
vm->running_execution_context().program_counter = pc;
auto base_value = vm->get(instruction->base());
auto& current_vm = *vm;
auto value = vm->get(instruction->src());