LibJS: Allocate callee execution contexts in call instruction handler

By handling call instructions in an inline (C++) function, we were
breaking the alloca() optimization and adding stack overhead. We fix
this by using a macro instead. It looks awful but it works.

1.07x speedup on MicroBench/call-00-args.js
This commit is contained in:
Andreas Kling 2025-10-29 23:24:14 +01:00 committed by Andreas Kling
parent 667354fd12
commit cdcbbcf48b

View File

@ -2844,61 +2844,49 @@ static ThrowCompletionOr<Value> dispatch_builtin_call(Bytecode::Interpreter& int
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
template<CallType call_type> // NOTE: This is a macro instead of an inline function because it needs to alloca() in the callers scope.
static ThrowCompletionOr<void> execute_call( #define IMPLEMENT_CALL_INSTRUCTION(call_type, callee, this_value) \
Bytecode::Interpreter& interpreter, TRY(throw_if_needed_for_call(interpreter, callee, call_type, m_expression_string)); \
Value callee, auto& function = callee.as_function(); \
Value this_value, ExecutionContext* callee_context = nullptr; \
ReadonlySpan<Operand> arguments, size_t registers_and_constants_and_locals_count = 0; \
Operand dst, size_t argument_count = m_argument_count; \
Optional<StringTableIndex> const& expression_string, TRY(function.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count)); \
Strict strict) ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(callee_context, registers_and_constants_and_locals_count, max(m_argument_count, argument_count)); \
{ auto* callee_context_argument_values = callee_context->arguments.data(); \
TRY(throw_if_needed_for_call(interpreter, callee, call_type, expression_string)); auto const callee_context_argument_count = callee_context->arguments.size(); \
for (size_t i = 0; i < m_argument_count; ++i) \
auto& function = callee.as_function(); callee_context_argument_values[i] = interpreter.get(m_arguments[i]); \
for (size_t i = m_argument_count; i < callee_context_argument_count; ++i) \
ExecutionContext* callee_context = nullptr; callee_context_argument_values[i] = js_undefined(); \
size_t registers_and_constants_and_locals_count = 0; callee_context->passed_argument_count = m_argument_count; \
size_t argument_count = arguments.size(); Value retval; \
TRY(function.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count)); if (call_type == CallType::DirectEval && callee == interpreter.realm().intrinsics().eval_function()) { \
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(callee_context, registers_and_constants_and_locals_count, max(arguments.size(), argument_count)); retval = TRY(perform_eval(interpreter.vm(), !callee_context->arguments.is_empty() ? callee_context->arguments[0] : js_undefined(), strict() == Strict::Yes ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct)); \
} else if (call_type == CallType::Construct) { \
auto* callee_context_argument_values = callee_context->arguments.data(); retval = TRY(function.internal_construct(*callee_context, function)); \
auto const callee_context_argument_count = callee_context->arguments.size(); } else { \
auto const insn_argument_count = arguments.size(); retval = TRY(function.internal_call(*callee_context, this_value)); \
} \
for (size_t i = 0; i < insn_argument_count; ++i) interpreter.set(m_dst, retval); \
callee_context_argument_values[i] = interpreter.get(arguments[i]);
for (size_t i = insn_argument_count; i < callee_context_argument_count; ++i)
callee_context_argument_values[i] = js_undefined();
callee_context->passed_argument_count = insn_argument_count;
Value retval;
if (call_type == CallType::DirectEval && callee == interpreter.realm().intrinsics().eval_function()) {
retval = TRY(perform_eval(interpreter.vm(), !callee_context->arguments.is_empty() ? callee_context->arguments[0] : js_undefined(), strict == Strict::Yes ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct));
} else if (call_type == CallType::Construct) {
retval = TRY(function.internal_construct(*callee_context, function));
} else {
retval = TRY(function.internal_call(*callee_context, this_value));
}
interpreter.set(dst, retval);
return {}; return {};
}
ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) const ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
return execute_call<CallType::Call>(interpreter, interpreter.get(m_callee), interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string, strict()); auto callee = interpreter.get(m_callee);
IMPLEMENT_CALL_INSTRUCTION(CallType::Call, callee, interpreter.get(m_this_value));
} }
NEVER_INLINE ThrowCompletionOr<void> CallConstruct::execute_impl(Bytecode::Interpreter& interpreter) const NEVER_INLINE ThrowCompletionOr<void> CallConstruct::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
return execute_call<CallType::Construct>(interpreter, interpreter.get(m_callee), js_undefined(), { m_arguments, m_argument_count }, m_dst, m_expression_string, strict()); auto callee = interpreter.get(m_callee);
IMPLEMENT_CALL_INSTRUCTION(CallType::Construct, callee, Value());
} }
ThrowCompletionOr<void> CallDirectEval::execute_impl(Bytecode::Interpreter& interpreter) const ThrowCompletionOr<void> CallDirectEval::execute_impl(Bytecode::Interpreter& interpreter) const
{ {
return execute_call<CallType::DirectEval>(interpreter, interpreter.get(m_callee), interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string, strict()); auto callee = interpreter.get(m_callee);
IMPLEMENT_CALL_INSTRUCTION(CallType::DirectEval, callee, Value());
} }
ThrowCompletionOr<void> CallBuiltin::execute_impl(Bytecode::Interpreter& interpreter) const ThrowCompletionOr<void> CallBuiltin::execute_impl(Bytecode::Interpreter& interpreter) const
@ -2910,7 +2898,7 @@ ThrowCompletionOr<void> CallBuiltin::execute_impl(Bytecode::Interpreter& interpr
return {}; return {};
} }
return execute_call<CallType::Call>(interpreter, callee, interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string, strict()); IMPLEMENT_CALL_INSTRUCTION(CallType::Call, callee, interpreter.get(m_this_value));
} }
template<CallType call_type> template<CallType call_type>