LibJS: Allocate ExecutionContext memory using alloca() when possible

This should be faster than heap allocation. However, heap allocation is
still necessary in some cases, such as with generators and async
functions.
This commit is contained in:
Aliaksandr Kalenik 2025-04-23 23:44:24 +02:00 committed by Andreas Kling
commit a329868c1b
Notes: github-actions[bot] 2025-04-24 08:32:11 +00:00
10 changed files with 42 additions and 12 deletions

View file

@ -662,7 +662,8 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
executable->dump();
// 20. Let evalContext be a new ECMAScript code execution context.
auto eval_context = ExecutionContext::create(executable->number_of_registers + executable->constants.size() + executable->local_variable_names.size(), 0);
ExecutionContext* eval_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(eval_context, executable->number_of_registers + executable->constants.size() + executable->local_variable_names.size(), 0);
// 21. Set evalContext's Function to null.
// NOTE: This was done in the construction of eval_context.

View file

@ -501,7 +501,9 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
}
u32 arguments_count = max(arguments_list.size(), formal_parameters().size());
auto callee_context = ExecutionContext::create(m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size() + m_bytecode_executable->local_variable_names.size(), arguments_count);
auto registers_and_constants_and_locals_count = m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size() + m_bytecode_executable->local_variable_names.size();
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, arguments_count);
// Non-standard
auto arguments = callee_context->arguments();
@ -571,7 +573,9 @@ ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(
}
u32 arguments_count = max(arguments_list.size(), formal_parameters().size());
auto callee_context = ExecutionContext::create(m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size() + m_bytecode_executable->local_variable_names.size(), arguments_count);
auto registers_and_constants_and_locals_count = m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size() + m_bytecode_executable->local_variable_names.size();
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, arguments_count);
// Non-standard
auto arguments = callee_context->arguments();

View file

@ -43,9 +43,9 @@ struct ExecutionContext {
private:
friend class ExecutionContextAllocator;
public:
ExecutionContext(u32 registers_and_constants_and_locals_count, u32 arguments_count);
public:
void operator delete(void* ptr);
GC::Ptr<FunctionObject> function; // [[Function]]
@ -123,6 +123,22 @@ private:
u32 registers_and_constants_and_locals_and_arguments_count { 0 };
};
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(execution_context, \
registers_and_constants_and_locals_count, \
arguments_count) \
auto execution_context_size = sizeof(JS::ExecutionContext) \
+ ((registers_and_constants_and_locals_count) + (arguments_count)) \
* sizeof(JS::Value); \
\
void* execution_context_memory = alloca(execution_context_size); \
\
execution_context = new (execution_context_memory) \
JS::ExecutionContext((registers_and_constants_and_locals_count), (arguments_count)); \
\
ScopeGuard run_execution_context_destructor([execution_context] { \
execution_context->~ExecutionContext(); \
})
struct StackTraceElement {
ExecutionContext* execution_context;
RefPtr<CachedSourceRange> source_range;

View file

@ -119,7 +119,8 @@ ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, Read
// 2. If callerContext is not already suspended, suspend callerContext.
// 3. Let calleeContext be a new execution context.
auto callee_context = ExecutionContext::create(0, arguments_list.size());
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, arguments_list.size());
// 4. Set the Function of calleeContext to F.
callee_context->function = this;
@ -181,7 +182,8 @@ ThrowCompletionOr<GC::Ref<Object>> NativeFunction::internal_construct(ReadonlySp
// 2. If callerContext is not already suspended, suspend callerContext.
// 3. Let calleeContext be a new execution context.
auto callee_context = ExecutionContext::create(0, arguments_list.size());
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, arguments_list.size());
// 4. Set the Function of calleeContext to F.
callee_context->function = this;

View file

@ -62,7 +62,9 @@ ThrowCompletionOr<Value> WrappedFunction::internal_call(Value this_argument, Rea
// NOTE: No-op, kept by the VM in its execution context stack.
// 2. Let calleeContext be PrepareForWrappedFunctionCall(F).
auto callee_context = ExecutionContext::create(0, 0);
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, 0);
prepare_for_wrapped_function_call(*this, *callee_context);
// 3. Assert: calleeContext is now the running execution context.