LibJS: Move final simple opcodes into AsmInt

Move the remaining simple SetLexicalEnvironment, IsCallable and
LeavePrivateEnvironment opcodes into the AsmInt DSL. These handlers do
not need C++ slow-path support.
This commit is contained in:
Andreas Kling 2026-06-14 16:07:02 +02:00 committed by Andreas Kling
parent c1415a544f
commit 2aa9535635
6 changed files with 121 additions and 161 deletions

View file

@ -1250,7 +1250,12 @@ fn emit_instruction(
let cc = if m == "assert_tag" { "b.eq" } else { "b.ne" };
w!(out, " lsr x9, {value}, #48");
if let Some(val) = get_immediate_value(&insn.operands[1], program) {
emit_cmp_imm(out, "x9", val, pinned);
if (val as u64) <= 4095 || pinned.get(val).is_some() || is_cmn_candidate(val) {
emit_cmp_imm(out, "x9", val, pinned);
} else {
emit_mov_imm(out, "x10", val);
w!(out, " cmp x9, x10");
}
} else {
let tag = resolve_op(&insn.operands[1], handler, program);
w!(out, " cmp x9, {tag}");

View file

@ -662,13 +662,17 @@ fn emit_instruction(
"assert_tag" | "assert_not_tag" if program.enable_assertions => {
if insn.operands.len() == 2 {
let value = resolve_op(&insn.operands[0], handler, program);
let tag = resolve_op(&insn.operands[1], handler, program);
let ok_label = format!(".Lasm_{}.assert_ok_{}", handler.name, state.unique_counter);
state.unique_counter += 1;
let cc = if m == "assert_tag" { "je" } else { "jne" };
w!(out, " mov r11, {value}");
w!(out, " shr r11, 48");
w!(out, " cmp r11, {tag}");
if let Some(tag) = get_immediate_value(&insn.operands[1], program) {
w!(out, " cmp r11, {tag}");
} else {
let tag = resolve_op(&insn.operands[1], handler, program);
w!(out, " cmp r11, {tag}");
}
w!(out, " {cc} {ok_label}");
w!(out, " ud2");
w!(out, "{ok_label}:");

View file

@ -981,7 +981,7 @@ pub const INSTRUCTIONS: &[InstructionInfo] = &[
false,
false,
ArchSpec { clobbers_gpr: &["r11"], ..ArchSpec::NONE },
ArchSpec { clobbers_gpr: &["x9"], ..ArchSpec::NONE },
ArchSpec { clobbers_gpr: &["x9", "x10"], ..ArchSpec::NONE },
),
info(
"assert_not_tag",
@ -990,7 +990,7 @@ pub const INSTRUCTIONS: &[InstructionInfo] = &[
false,
false,
ArchSpec { clobbers_gpr: &["r11"], ..ArchSpec::NONE },
ArchSpec { clobbers_gpr: &["x9"], ..ArchSpec::NONE },
ArchSpec { clobbers_gpr: &["x9", "x10"], ..ArchSpec::NONE },
),
];

View file

@ -627,7 +627,6 @@ i64 asm_slow_path_jump_loosely_equals(VM*, u32 pc);
i64 asm_slow_path_jump_loosely_inequals(VM*, u32 pc);
i64 asm_slow_path_jump_strictly_equals(VM*, u32 pc);
i64 asm_slow_path_jump_strictly_inequals(VM*, u32 pc);
i64 asm_slow_path_set_lexical_environment(VM*, u32 pc);
i64 asm_slow_path_postfix_increment(VM*, u32 pc);
i64 asm_slow_path_get_by_id(VM*, u32 pc);
i64 asm_slow_path_get_by_id_with_this(VM*, u32 pc);
@ -734,7 +733,6 @@ i64 asm_slow_path_create_variable(VM*, u32 pc);
i64 asm_slow_path_enter_object_environment(VM*, u32 pc);
i64 asm_slow_path_bitwise_not(VM*, u32 pc);
i64 asm_slow_path_unary_plus(VM*, u32 pc);
i64 asm_slow_path_is_callable(VM*, u32 pc);
i64 asm_slow_path_is_constructor(VM*, u32 pc);
i64 asm_slow_path_add_private_name(VM*, u32 pc);
i64 asm_slow_path_create_async_from_sync_iterator(VM*, u32 pc);
@ -754,7 +752,6 @@ i64 asm_slow_path_get_completion_fields(VM*, u32 pc);
i64 asm_slow_path_set_completion_type(VM*, u32 pc);
i64 asm_slow_path_get_template_object(VM*, u32 pc);
i64 asm_slow_path_new_function(VM*, u32 pc);
i64 asm_slow_path_leave_private_environment(VM*, u32 pc);
i64 asm_slow_path_throw(VM*, u32 pc);
i64 asm_slow_path_throw_if_tdz(VM*, u32 pc);
i64 asm_slow_path_throw_if_not_object(VM*, u32 pc);
@ -943,15 +940,6 @@ i64 asm_slow_path_jump_strictly_inequals(VM* vm, u32 pc)
// ===== Dedicated slow paths for hot instructions =====
i64 asm_slow_path_set_lexical_environment(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);
auto* bytecode = vm->current_executable().bytecode.data();
auto& insn = *reinterpret_cast<Op::SetLexicalEnvironment const*>(&bytecode[pc]);
vm->running_execution_context().lexical_environment = &as<Environment>(vm->get(insn.environment()).as_cell());
return static_cast<i64>(pc + sizeof(Op::SetLexicalEnvironment));
}
i64 asm_slow_path_get_initialized_binding(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);
@ -2889,15 +2877,6 @@ i64 asm_slow_path_unary_plus(VM* vm, u32 pc)
return static_cast<i64>(pc + sizeof(Op::UnaryPlus));
}
i64 asm_slow_path_is_callable(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);
auto* bytecode = vm->current_executable().bytecode.data();
auto& insn = *reinterpret_cast<Op::IsCallable const*>(&bytecode[pc]);
vm->set(insn.dst(), Value(vm->get(insn.value()).is_function()));
return static_cast<i64>(pc + sizeof(Op::IsCallable));
}
i64 asm_slow_path_is_constructor(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);
@ -3210,14 +3189,6 @@ i64 asm_slow_path_new_function(VM* vm, u32 pc)
return static_cast<i64>(pc + sizeof(Op::NewFunction));
}
i64 asm_slow_path_leave_private_environment(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);
auto& running_execution_context = vm->running_execution_context();
running_execution_context.private_environment = running_execution_context.private_environment->outer_environment();
return static_cast<i64>(pc + sizeof(Op::LeavePrivateEnvironment));
}
i64 asm_slow_path_throw(VM* vm, u32 pc)
{
bump_slow_path(*vm, pc);

View file

@ -368,6 +368,74 @@ macro bitwise_op(op_insn, slow_path_func)
call_slow_path slow_path_func
end
macro prefix_inc_dec(op32_overflow, fp_op, slow_path_func)
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_dst
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
unbox_int32 int_value, value
op32_overflow int_value, 1, .overflow
box_int32_clean dst, int_value
store_operand m_dst, dst
dispatch_next
.overflow:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_op result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path slow_path_func
end
macro postfix_inc_dec(op32_overflow, fp_op, slow_path_func)
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_src
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
store_operand m_dst, value
unbox_int32 int_value, value
op32_overflow int_value, 1, .overflow_after_store
box_int32_clean dst, int_value
store_operand m_src, dst
dispatch_next
.overflow_after_store:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_op result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_src, dst
dispatch_next
.slow:
call_slow_path slow_path_func
end
macro int32_shift_op(op_insn, slow_path_func)
temp lhs, rhs, lhs_tag, rhs_tag, lhs_int, count, dst
load_operand lhs, m_lhs
load_operand rhs, m_rhs
extract_tag lhs_tag, lhs
branch_ne lhs_tag, INT32_TAG, .slow
extract_tag rhs_tag, rhs
branch_ne rhs_tag, INT32_TAG, .slow
unbox_int32 lhs_int, lhs
unbox_int32 count, rhs
and count, 31
op_insn lhs_int, count
box_int32 dst, lhs_int
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path slow_path_func
end
# Validate that the callee still points at the expected builtin function.
# Jumps to fail if the call target has been replaced or is not a function.
macro validate_callee_builtin(expected_builtin, fail)
@ -895,52 +963,12 @@ end
# Fast path for ++x: int32 + 1 with overflow check.
# On overflow, convert to double and add 1.0.
handler Increment
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_dst
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
unbox_int32 int_value, value
add32_overflow int_value, 1, .overflow
box_int32_clean dst, int_value
store_operand m_dst, dst
dispatch_next
.overflow:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_add result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_increment
prefix_inc_dec add32_overflow, fp_add, asm_slow_path_increment
end
# Fast path for --x: int32 - 1 with overflow check.
handler Decrement
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_dst
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
unbox_int32 int_value, value
sub32_overflow int_value, 1, .overflow
box_int32_clean dst, int_value
store_operand m_dst, dst
dispatch_next
.overflow:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_sub result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_decrement
prefix_inc_dec sub32_overflow, fp_sub, asm_slow_path_decrement
end
handler Not
@ -1056,7 +1084,7 @@ handler GetLexicalEnvironment
temp env, tag
load64 env, [exec_ctx, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT]
assert_nonzero env
mov tag, CELL_TAG_SHIFTED
mov tag, SHIFTED_IS_CELL_PATTERN
or env, tag
store_operand m_dst, env
dispatch_next
@ -1075,7 +1103,12 @@ handler GetSuperConstructor
end
handler SetLexicalEnvironment
call_slow_path asm_slow_path_set_lexical_environment
temp env
load_operand env, m_environment
assert_tag env, IS_CELL_PATTERN
unbox_object env, env
store64 [exec_ctx, EXECUTION_CONTEXT_LEXICAL_ENVIRONMENT], env
dispatch_next
end
# ============================================================================
@ -1329,30 +1362,7 @@ end
# x++: save original to dst first, then increment src in-place.
handler PostfixIncrement
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_src
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
# Save original value to dst (the "postfix" part)
store_operand m_dst, value
# Increment in-place: src = src + 1
unbox_int32 int_value, value
add32_overflow int_value, 1, .overflow_after_store
box_int32_clean dst, int_value
store_operand m_src, dst
dispatch_next
.overflow_after_store:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_add result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_src, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_postfix_increment
postfix_inc_dec add32_overflow, fp_add, asm_slow_path_postfix_increment
end
# Division result is stored as int32 when representable (e.g. 6/3 = 2),
@ -1562,41 +1572,11 @@ end
# Shift ops: int32-only fast path, shift count masked to 0-31 per spec.
handler LeftShift
temp lhs, rhs, lhs_tag, rhs_tag, lhs_int, count, dst
load_operand lhs, m_lhs
load_operand rhs, m_rhs
extract_tag lhs_tag, lhs
branch_ne lhs_tag, INT32_TAG, .slow
extract_tag rhs_tag, rhs
branch_ne rhs_tag, INT32_TAG, .slow
unbox_int32 lhs_int, lhs
unbox_int32 count, rhs
and count, 31
shl lhs_int, count
box_int32 dst, lhs_int
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_left_shift
int32_shift_op shl, asm_slow_path_left_shift
end
handler RightShift
temp lhs, rhs, lhs_tag, rhs_tag, lhs_int, count, dst
load_operand lhs, m_lhs
load_operand rhs, m_rhs
extract_tag lhs_tag, lhs
branch_ne lhs_tag, INT32_TAG, .slow
extract_tag rhs_tag, rhs
branch_ne rhs_tag, INT32_TAG, .slow
unbox_int32 lhs_int, lhs
unbox_int32 count, rhs
and count, 31
sar lhs_int, count
box_int32 dst, lhs_int
store_operand m_dst, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_right_shift
int32_shift_op sar, asm_slow_path_right_shift
end
# Unsigned right shift: result is always unsigned, so values > INT32_MAX
@ -1757,30 +1737,7 @@ end
# x--: save original to dst first, then decrement src in-place.
handler PostfixDecrement
temp value, tag, int_value, dst
ftemp result_dbl, one_dbl
load_operand value, m_src
extract_tag tag, value
branch_ne tag, INT32_TAG, .slow
# Save original value to dst (the "postfix" part)
store_operand m_dst, value
# Decrement in-place: src = src - 1
unbox_int32 int_value, value
sub32_overflow int_value, 1, .overflow_after_store
box_int32_clean dst, int_value
store_operand m_src, dst
dispatch_next
.overflow_after_store:
unbox_int32 int_value, value
int_to_double result_dbl, int_value
mov dst, DOUBLE_ONE
fp_mov one_dbl, dst
fp_sub result_dbl, one_dbl
fp_mov dst, result_dbl
store_operand m_src, dst
dispatch_next
.slow:
call_slow_path asm_slow_path_postfix_decrement
postfix_inc_dec sub32_overflow, fp_sub, asm_slow_path_postfix_decrement
end
handler ToInt32
@ -3258,7 +3215,20 @@ handler In
end
handler IsCallable
call_slow_path asm_slow_path_is_callable
temp value, tag, object, flags, result
load_operand value, m_value
extract_tag tag, value
branch_ne tag, OBJECT_TAG, .not_callable
unbox_object object, value
load8 flags, [object, OBJECT_FLAGS]
branch_bits_clear flags, OBJECT_FLAG_IS_FUNCTION, .not_callable
mov result, BOOLEAN_TRUE
store_operand m_dst, result
dispatch_next
.not_callable:
mov result, BOOLEAN_FALSE
store_operand m_dst, result
dispatch_next
end
handler IsConstructor
@ -3338,7 +3308,12 @@ handler Typeof
end
handler LeavePrivateEnvironment
call_slow_path asm_slow_path_leave_private_environment
temp private_environment, outer_environment
load64 private_environment, [exec_ctx, EXECUTION_CONTEXT_PRIVATE_ENVIRONMENT]
assert_nonzero private_environment
load64 outer_environment, [private_environment, PRIVATE_ENVIRONMENT_OUTER]
store64 [exec_ctx, EXECUTION_CONTEXT_PRIVATE_ENVIRONMENT], outer_environment
dispatch_next
end
# Fast path: if this_value register is already cached (non-empty), skip the slow path.

View file

@ -328,6 +328,10 @@ int main()
EMIT_OFFSET(ENVIRONMENT_DECLARATIVE, Environment, m_declarative);
EMIT_OFFSET(ENVIRONMENT_OUTER, Environment, m_outer_environment);
// PrivateEnvironment layout
outln("\n# PrivateEnvironment layout");
EMIT_OFFSET(PRIVATE_ENVIRONMENT_OUTER, PrivateEnvironment, m_outer_environment);
// DeclarativeEnvironment binding storage layout
outln("\n# DeclarativeEnvironment binding storage layout");
EMIT_OFFSET(DECLARATIVE_ENVIRONMENT_SHAPE, DeclarativeEnvironment, m_shape);
@ -388,6 +392,7 @@ int main()
outln("const SYMBOL_TAG = 0x{:X}", static_cast<u64>(SYMBOL_TAG));
outln("const BIGINT_TAG = 0x{:X}", static_cast<u64>(BIGINT_TAG));
outln("const ACCESSOR_TAG = 0x{:X}", static_cast<u64>(ACCESSOR_TAG));
outln("const IS_CELL_PATTERN = 0x{:X}", static_cast<u64>(GC::IS_CELL_PATTERN));
outln("const INT32_TAG = 0x{:X}", static_cast<u64>(INT32_TAG));
outln("const BOOLEAN_TAG = 0x{:X}", static_cast<u64>(BOOLEAN_TAG));
outln("const UNDEFINED_TAG = 0x{:X}", static_cast<u64>(UNDEFINED_TAG));
@ -406,7 +411,7 @@ int main()
outln("const CANON_NAN_BITS = 0x{:X}", static_cast<u64>(GC::CANON_NAN_BITS));
outln("const DOUBLE_ONE = 0x{:X}", bit_cast<u64>(1.0));
outln("const NEGATIVE_ZERO = 0x{:X}", static_cast<u64>(NEGATIVE_ZERO_BITS));
outln("const CELL_TAG_SHIFTED = 0x{:X}", static_cast<u64>(GC::SHIFTED_IS_CELL_PATTERN));
outln("const SHIFTED_IS_CELL_PATTERN = 0x{:X}", static_cast<u64>(GC::SHIFTED_IS_CELL_PATTERN));
outln("const ACCUMULATOR_REG_OFFSET = {}", static_cast<size_t>(Register::accumulator().index()) * sizeof(Value));
outln("const EXCEPTION_REG_OFFSET = {}", static_cast<size_t>(Register::exception().index()) * sizeof(Value));