LibJS: Let invokers (callers) of [[Call]] allocate ExecutionContext
Some checks are pending
CI / Lagom (arm64, Sanitizer_CI, false, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (x86_64, Fuzzers_CI, false, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, false, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (x86_64, Sanitizer_CI, true, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (arm64, macos-15, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (x86_64, ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Label PRs with merge conflicts / auto-labeler (push) Waiting to run
Push notes / build (push) Waiting to run

Instead of letting every [[Call]] implementation allocate an
ExecutionContext, we now make that a responsibility of the caller.

The main point of this exercise is to allow the Call instruction
to write function arguments directly into the callee ExecutionContext
instead of copying them later.

This makes function calls significantly faster:
- 10-20% faster on micro-benchmarks (depending on argument count)
- 4% speedup on Kraken
- 2% speedup on Octane
- 5% speedup on JetStream
This commit is contained in:
Andreas Kling 2025-04-27 11:53:11 +02:00 committed by Andreas Kling
commit a05be67e4a
Notes: github-actions[bot] 2025-04-27 23:24:56 +00:00
18 changed files with 139 additions and 84 deletions

View file

@ -2616,12 +2616,30 @@ ThrowCompletionOr<void> Call::execute_impl(Bytecode::Interpreter& interpreter) c
{ {
auto callee = interpreter.get(m_callee); auto callee = interpreter.get(m_callee);
TRY(throw_if_needed_for_call(interpreter, callee, CallType::Call, expression_string())); if (!callee.is_function()) [[unlikely]] {
return throw_type_error_for_callee(interpreter, callee, "function"sv, m_expression_string);
}
auto argument_values = interpreter.allocate_argument_values(m_argument_count); auto& function = callee.as_function();
for (size_t i = 0; i < m_argument_count; ++i)
argument_values[i] = interpreter.get(m_arguments[i]); ExecutionContext* callee_context = nullptr;
interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::Call, callee, argument_values))); size_t registers_and_constants_and_locals_count = 0;
size_t argument_count = m_argument_count;
TRY(function.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count));
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();
auto const callee_context_argument_count = callee_context->arguments.size();
auto const insn_argument_count = m_argument_count;
for (size_t i = 0; i < insn_argument_count; ++i)
callee_context_argument_values[i] = interpreter.get(m_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;
auto retval = TRY(function.internal_call(*callee_context, interpreter.get(m_this_value)));
interpreter.set(m_dst, retval);
return {}; return {};
} }

View file

@ -59,7 +59,19 @@ ThrowCompletionOr<Value> call_impl(VM& vm, Value function, Value this_value, Rea
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, function.to_string_without_side_effects()); return vm.throw_completion<TypeError>(ErrorType::NotAFunction, function.to_string_without_side_effects());
// 3. Return ? F.[[Call]](V, argumentsList). // 3. Return ? F.[[Call]](V, argumentsList).
return function.as_function().internal_call(this_value, arguments_list); ExecutionContext* callee_context = nullptr;
auto& function_object = function.as_function();
size_t registers_and_constants_and_locals_count = 0;
size_t argument_count = arguments_list.size();
TRY(function_object.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count));
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, argument_count);
auto* argument_values = callee_context->arguments.data();
for (size_t i = 0; i < arguments_list.size(); ++i)
argument_values[i] = arguments_list[i];
callee_context->passed_argument_count = arguments_list.size();
return function_object.internal_call(*callee_context, this_value);
} }
ThrowCompletionOr<Value> call_impl(VM&, FunctionObject& function, Value this_value, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> call_impl(VM&, FunctionObject& function, Value this_value, ReadonlySpan<Value> arguments_list)
@ -70,7 +82,18 @@ ThrowCompletionOr<Value> call_impl(VM&, FunctionObject& function, Value this_val
// Note: Called with a FunctionObject ref // Note: Called with a FunctionObject ref
// 3. Return ? F.[[Call]](V, argumentsList). // 3. Return ? F.[[Call]](V, argumentsList).
return function.internal_call(this_value, arguments_list); ExecutionContext* callee_context = nullptr;
size_t registers_and_constants_and_locals_count = 0;
size_t argument_count = arguments_list.size();
TRY(function.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count));
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, argument_count);
auto* argument_values = callee_context->arguments.data();
for (size_t i = 0; i < arguments_list.size(); ++i)
argument_values[i] = arguments_list[i];
callee_context->passed_argument_count = arguments_list.size();
return function.internal_call(*callee_context, this_value);
} }
// 7.3.15 Construct ( F [ , argumentsList [ , newTarget ] ] ), https://tc39.es/ecma262/#sec-construct // 7.3.15 Construct ( F [ , argumentsList [ , newTarget ] ] ), https://tc39.es/ecma262/#sec-construct

View file

@ -43,10 +43,8 @@ BoundFunction::BoundFunction(Realm& realm, FunctionObject& bound_target_function
} }
// 10.4.1.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist // 10.4.1.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist
ThrowCompletionOr<Value> BoundFunction::internal_call([[maybe_unused]] Value this_argument, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> BoundFunction::internal_call(ExecutionContext& outer_context, [[maybe_unused]] Value this_argument)
{ {
auto& vm = this->vm();
// 1. Let target be F.[[BoundTargetFunction]]. // 1. Let target be F.[[BoundTargetFunction]].
auto& target = *m_bound_target_function; auto& target = *m_bound_target_function;
@ -57,13 +55,24 @@ ThrowCompletionOr<Value> BoundFunction::internal_call([[maybe_unused]] Value thi
auto& bound_args = m_bound_arguments; auto& bound_args = m_bound_arguments;
// 4. Let args be the list-concatenation of boundArgs and argumentsList. // 4. Let args be the list-concatenation of boundArgs and argumentsList.
Vector<Value> args;
args.ensure_capacity(bound_args.size() + arguments_list.size()); ExecutionContext* callee_context = nullptr;
args.extend(bound_args); size_t registers_and_constants_and_locals_count = 0;
args.append(arguments_list.data(), arguments_list.size()); size_t argument_count = bound_args.size() + outer_context.arguments.size();
TRY(target.get_stack_frame_size(registers_and_constants_and_locals_count, argument_count));
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, argument_count);
auto* argument_values = callee_context->arguments.data();
for (size_t i = 0; i < bound_args.size(); ++i)
argument_values[i] = bound_args[i];
for (size_t i = 0; i < outer_context.arguments.size(); ++i)
argument_values[bound_args.size() + i] = outer_context.arguments[i];
callee_context->passed_argument_count = bound_args.size() + outer_context.arguments.size();
// 5. Return ? Call(target, boundThis, args). // 5. Return ? Call(target, boundThis, args).
return call(vm, &target, bound_this, args.span()); return target.internal_call(*callee_context, bound_this);
} }
// 10.4.1.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget // 10.4.1.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget

View file

@ -20,7 +20,7 @@ public:
virtual ~BoundFunction() override = default; virtual ~BoundFunction() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override; virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override;
virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); } virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); }

View file

@ -481,46 +481,39 @@ void ECMAScriptFunctionObject::initialize(Realm& realm)
} }
} }
// 10.2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist ThrowCompletionOr<void> ECMAScriptFunctionObject::get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count)
ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argument, ReadonlySpan<Value> arguments_list)
{ {
auto& vm = this->vm();
// 1. Let callerContext be the running execution context.
// NOTE: No-op, kept by the VM in its execution context stack.
if (!m_bytecode_executable) { if (!m_bytecode_executable) {
if (!ecmascript_code().bytecode_executable()) { if (!ecmascript_code().bytecode_executable()) {
if (is_module_wrapper()) { if (is_module_wrapper()) {
const_cast<Statement&>(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm, ecmascript_code(), kind(), name()))); const_cast<Statement&>(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm(), ecmascript_code(), kind(), name())));
} else { } else {
const_cast<Statement&>(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm, *this))); const_cast<Statement&>(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm(), *this)));
} }
} }
m_bytecode_executable = ecmascript_code().bytecode_executable(); m_bytecode_executable = ecmascript_code().bytecode_executable();
} }
registers_and_constants_and_locals_count = m_bytecode_executable->number_of_registers + m_bytecode_executable->constants.size() + m_bytecode_executable->local_variable_names.size();
u32 arguments_count = max(arguments_list.size(), formal_parameters().size()); argument_count = max(argument_count, formal_parameters().size());
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(); return {};
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;
if (!arguments_list.is_empty())
arguments.overwrite(0, arguments_list.data(), arguments_list.size() * sizeof(Value));
callee_context->passed_argument_count = arguments_list.size();
if (arguments_list.size() < formal_parameters().size()) {
for (size_t i = arguments_list.size(); i < formal_parameters().size(); ++i)
arguments[i] = js_undefined();
} }
// 10.2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(ExecutionContext& callee_context, Value this_argument)
{
auto& vm = this->vm();
VERIFY(m_bytecode_executable);
// 1. Let callerContext be the running execution context.
// NOTE: No-op, kept by the VM in its execution context stack.
// 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined). // 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
// NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check. // NOTE: We throw if the end of the native stack is reached, so unlike in the spec this _does_ need an exception check.
TRY(prepare_for_ordinary_call(*callee_context, nullptr)); TRY(prepare_for_ordinary_call(callee_context, nullptr));
// 3. Assert: calleeContext is now the running execution context. // 3. Assert: calleeContext is now the running execution context.
VERIFY(&vm.running_execution_context() == callee_context); VERIFY(&vm.running_execution_context() == &callee_context);
// 4. If F.[[IsClassConstructor]] is true, then // 4. If F.[[IsClassConstructor]] is true, then
if (is_class_constructor()) { if (is_class_constructor()) {
@ -537,7 +530,7 @@ ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(Value this_argu
// 5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). // 5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
if (uses_this()) if (uses_this())
ordinary_call_bind_this(*callee_context, this_argument); ordinary_call_bind_this(callee_context, this_argument);
// 6. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)). // 6. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)).
auto result = ordinary_call_evaluate_body(); auto result = ordinary_call_evaluate_body();

View file

@ -116,7 +116,8 @@ public:
virtual void initialize(Realm&) override; virtual void initialize(Realm&) override;
virtual ~ECMAScriptFunctionObject() override = default; virtual ~ECMAScriptFunctionObject() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<void> get_stack_frame_size(size_t& registers_and_constants_and_locals_slots, size_t& argument_count) override;
virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override; virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override;
void make_method(Object& home_object); void make_method(Object& home_object);

View file

@ -114,7 +114,7 @@ private:
u32 registers_and_constants_and_locals_and_arguments_count { 0 }; u32 registers_and_constants_and_locals_and_arguments_count { 0 };
}; };
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(execution_context, \ #define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(execution_context, \
registers_and_constants_and_locals_count, \ registers_and_constants_and_locals_count, \
arguments_count) \ arguments_count) \
auto execution_context_size = sizeof(JS::ExecutionContext) \ auto execution_context_size = sizeof(JS::ExecutionContext) \
@ -130,6 +130,16 @@ private:
execution_context->~ExecutionContext(); \ execution_context->~ExecutionContext(); \
}) })
#define ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(execution_context, registers_and_constants_and_locals_count, \
arguments_count) \
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(execution_context, \
registers_and_constants_and_locals_count, arguments_count); \
do { \
for (size_t i = 0; i < execution_context->arguments.size(); i++) { \
execution_context->arguments[i] = JS::js_undefined(); \
} \
} while (0)
struct StackTraceElement { struct StackTraceElement {
ExecutionContext* execution_context; ExecutionContext* execution_context;
RefPtr<CachedSourceRange> source_range; RefPtr<CachedSourceRange> source_range;

View file

@ -23,7 +23,8 @@ public:
// Table 5: Additional Essential Internal Methods of Function Objects, https://tc39.es/ecma262/#table-additional-essential-internal-methods-of-function-objects // Table 5: Additional Essential Internal Methods of Function Objects, https://tc39.es/ecma262/#table-additional-essential-internal-methods-of-function-objects
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) = 0; virtual ThrowCompletionOr<void> get_stack_frame_size([[maybe_unused]] size_t& registers_and_constants_and_locals_count, [[maybe_unused]] size_t& argument_count) { return {}; }
virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) = 0;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct([[maybe_unused]] ReadonlySpan<Value> arguments_list, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); } virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct([[maybe_unused]] ReadonlySpan<Value> arguments_list, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); }
void set_function_name(Variant<PropertyKey, PrivateName> const& name_arg, Optional<StringView> const& prefix = {}); void set_function_name(Variant<PropertyKey, PrivateName> const& name_arg, Optional<StringView> const& prefix = {});

View file

@ -40,7 +40,7 @@ void FunctionPrototype::initialize(Realm& realm)
define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), Attribute::Configurable); define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), Attribute::Configurable);
} }
ThrowCompletionOr<Value> FunctionPrototype::internal_call(Value, ReadonlySpan<Value>) ThrowCompletionOr<Value> FunctionPrototype::internal_call(ExecutionContext&, Value)
{ {
// The Function prototype object: // The Function prototype object:
// - accepts any arguments and returns undefined when invoked. // - accepts any arguments and returns undefined when invoked.

View file

@ -18,7 +18,7 @@ public:
virtual void initialize(Realm&) override; virtual void initialize(Realm&) override;
virtual ~FunctionPrototype() override = default; virtual ~FunctionPrototype() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
private: private:
explicit FunctionPrototype(Realm&); explicit FunctionPrototype(Realm&);

View file

@ -110,7 +110,7 @@ NativeFunction::NativeFunction(FlyString name, Object& prototype)
// these good candidates for a bit of code duplication :^) // these good candidates for a bit of code duplication :^)
// 10.3.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist // 10.3.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist
ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> NativeFunction::internal_call(ExecutionContext& callee_context, Value this_argument)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
@ -119,12 +119,10 @@ ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, Read
// 2. If callerContext is not already suspended, suspend callerContext. // 2. If callerContext is not already suspended, suspend callerContext.
// 3. Let calleeContext be a new execution context. // 3. Let calleeContext be a new execution context.
ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, arguments_list.size());
// 4. Set the Function of calleeContext to F. // 4. Set the Function of calleeContext to F.
callee_context->function = this; callee_context.function = this;
callee_context->function_name = m_name_string; callee_context.function_name = m_name_string;
// 5. Let calleeRealm be F.[[Realm]]. // 5. Let calleeRealm be F.[[Realm]].
auto callee_realm = m_realm; auto callee_realm = m_realm;
@ -138,29 +136,27 @@ ThrowCompletionOr<Value> NativeFunction::internal_call(Value this_argument, Read
VERIFY(callee_realm); VERIFY(callee_realm);
// 6. Set the Realm of calleeContext to calleeRealm. // 6. Set the Realm of calleeContext to calleeRealm.
callee_context->realm = callee_realm; callee_context.realm = callee_realm;
// 7. Set the ScriptOrModule of calleeContext to null. // 7. Set the ScriptOrModule of calleeContext to null.
// Note: This is already the default value. // Note: This is already the default value.
// 8. Perform any necessary implementation-defined initialization of calleeContext. // 8. Perform any necessary implementation-defined initialization of calleeContext.
callee_context->this_value = this_argument; callee_context.this_value = this_argument;
if (!arguments_list.is_empty())
callee_context->arguments.overwrite(0, arguments_list.data(), arguments_list.size() * sizeof(Value));
callee_context->lexical_environment = caller_context.lexical_environment; callee_context.lexical_environment = caller_context.lexical_environment;
callee_context->variable_environment = caller_context.variable_environment; callee_context.variable_environment = caller_context.variable_environment;
// Note: Keeping the private environment is probably only needed because of async methods in classes // Note: Keeping the private environment is probably only needed because of async methods in classes
// calling async_block_start which goes through a NativeFunction here. // calling async_block_start which goes through a NativeFunction here.
callee_context->private_environment = caller_context.private_environment; callee_context.private_environment = caller_context.private_environment;
// NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller. // NOTE: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller.
callee_context->is_strict_mode = vm.in_strict_mode(); callee_context.is_strict_mode = vm.in_strict_mode();
// </8.> -------------------------------------------------------------------------- // </8.> --------------------------------------------------------------------------
// 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context. // 9. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
TRY(vm.push_execution_context(*callee_context, {})); TRY(vm.push_execution_context(callee_context, {}));
// 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. thisArgument is the this value, argumentsList provides the named parameters, and the NewTarget value is undefined. // 10. Let result be the Completion Record that is the result of evaluating F in a manner that conforms to the specification of F. thisArgument is the this value, argumentsList provides the named parameters, and the NewTarget value is undefined.
auto result = call(); auto result = call();
@ -184,6 +180,10 @@ ThrowCompletionOr<GC::Ref<Object>> NativeFunction::internal_construct(ReadonlySp
// 3. Let calleeContext be a new execution context. // 3. Let calleeContext be a new execution context.
ExecutionContext* callee_context = nullptr; ExecutionContext* callee_context = nullptr;
ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, arguments_list.size()); ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, arguments_list.size());
// 8. Perform any necessary implementation-defined initialization of calleeContext.
for (size_t i = 0; i < arguments_list.size(); ++i)
callee_context->arguments[i] = arguments_list[i];
callee_context->passed_argument_count = arguments_list.size();
// 4. Set the Function of calleeContext to F. // 4. Set the Function of calleeContext to F.
callee_context->function = this; callee_context->function = this;
@ -206,10 +206,6 @@ ThrowCompletionOr<GC::Ref<Object>> NativeFunction::internal_construct(ReadonlySp
// 7. Set the ScriptOrModule of calleeContext to null. // 7. Set the ScriptOrModule of calleeContext to null.
// Note: This is already the default value. // Note: This is already the default value.
// 8. Perform any necessary implementation-defined initialization of calleeContext.
if (!arguments_list.is_empty())
callee_context->arguments.overwrite(0, arguments_list.data(), arguments_list.size() * sizeof(Value));
callee_context->lexical_environment = caller_context.lexical_environment; callee_context->lexical_environment = caller_context.lexical_environment;
callee_context->variable_environment = caller_context.variable_environment; callee_context->variable_environment = caller_context.variable_environment;

View file

@ -26,7 +26,7 @@ public:
virtual ~NativeFunction() override = default; virtual ~NativeFunction() override = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override; virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override;
// Used for [[Call]] / [[Construct]]'s "...result of evaluating F in a manner that conforms to the specification of F". // Used for [[Call]] / [[Construct]]'s "...result of evaluating F in a manner that conforms to the specification of F".

View file

@ -779,7 +779,7 @@ ThrowCompletionOr<GC::RootVector<Value>> ProxyObject::internal_own_property_keys
} }
// 10.5.12 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist // 10.5.12 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
ThrowCompletionOr<Value> ProxyObject::internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> ProxyObject::internal_call(ExecutionContext& callee_context, Value this_argument)
{ {
LIMIT_PROXY_RECURSION_DEPTH(); LIMIT_PROXY_RECURSION_DEPTH();
@ -802,11 +802,11 @@ ThrowCompletionOr<Value> ProxyObject::internal_call(Value this_argument, Readonl
// 6. If trap is undefined, then // 6. If trap is undefined, then
if (!trap) { if (!trap) {
// a. Return ? Call(target, thisArgument, argumentsList). // a. Return ? Call(target, thisArgument, argumentsList).
return call(vm, m_target, this_argument, arguments_list); return call(vm, m_target, this_argument, callee_context.arguments);
} }
// 7. Let argArray be CreateArrayFromList(argumentsList). // 7. Let argArray be CreateArrayFromList(argumentsList).
auto arguments_array = Array::create_from(realm, arguments_list); auto arguments_array = Array::create_from(realm, callee_context.arguments);
// 8. Return ? Call(trap, handler, « target, thisArgument, argArray »). // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »).
return call(vm, trap, m_handler, m_target, this_argument, arguments_array); return call(vm, trap, m_handler, m_target, this_argument, arguments_array);

View file

@ -42,7 +42,7 @@ public:
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override; virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override;
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override; virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override; virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override; virtual ThrowCompletionOr<GC::Ref<Object>> internal_construct(ReadonlySpan<Value> arguments_list, FunctionObject& new_target) override;
ThrowCompletionOr<void> validate_non_revoked_proxy() const; ThrowCompletionOr<void> validate_non_revoked_proxy() const;

View file

@ -54,7 +54,7 @@ void WrappedFunction::visit_edges(Visitor& visitor)
} }
// 2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist // 2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/proposal-shadowrealm/#sec-wrapped-function-exotic-objects-call-thisargument-argumentslist
ThrowCompletionOr<Value> WrappedFunction::internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> WrappedFunction::internal_call(ExecutionContext& callee_context, Value this_argument)
{ {
auto& vm = this->vm(); auto& vm = this->vm();
@ -62,16 +62,13 @@ ThrowCompletionOr<Value> WrappedFunction::internal_call(Value this_argument, Rea
// NOTE: No-op, kept by the VM in its execution context stack. // NOTE: No-op, kept by the VM in its execution context stack.
// 2. Let calleeContext be PrepareForWrappedFunctionCall(F). // 2. Let calleeContext be PrepareForWrappedFunctionCall(F).
ExecutionContext* callee_context = nullptr; prepare_for_wrapped_function_call(*this, callee_context);
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. // 3. Assert: calleeContext is now the running execution context.
VERIFY(&vm.running_execution_context() == callee_context); VERIFY(&vm.running_execution_context() == &callee_context);
// 4. Let result be Completion(OrdinaryWrappedFunctionCall(F, thisArgument, argumentsList)). // 4. Let result be Completion(OrdinaryWrappedFunctionCall(F, thisArgument, argumentsList)).
auto result = ordinary_wrapped_function_call(*this, this_argument, arguments_list); auto result = ordinary_wrapped_function_call(*this, this_argument, callee_context.arguments);
// 5. Remove calleeContext from the execution context stack and restore callerContext as the running execution context. // 5. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
vm.pop_execution_context(); vm.pop_execution_context();
@ -80,13 +77,18 @@ ThrowCompletionOr<Value> WrappedFunction::internal_call(Value this_argument, Rea
return result; return result;
} }
ThrowCompletionOr<void> WrappedFunction::get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count)
{
return m_wrapped_target_function->get_stack_frame_size(registers_and_constants_and_locals_count, argument_count);
}
// 2.2 OrdinaryWrappedFunctionCall ( F: a wrapped function exotic object, thisArgument: an ECMAScript language value, argumentsList: a List of ECMAScript language values, ), https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call // 2.2 OrdinaryWrappedFunctionCall ( F: a wrapped function exotic object, thisArgument: an ECMAScript language value, argumentsList: a List of ECMAScript language values, ), https://tc39.es/proposal-shadowrealm/#sec-ordinary-wrapped-function-call
ThrowCompletionOr<Value> ordinary_wrapped_function_call(WrappedFunction const& function, Value this_argument, ReadonlySpan<Value> arguments_list) ThrowCompletionOr<Value> ordinary_wrapped_function_call(WrappedFunction& function, Value this_argument, Span<Value> arguments_list)
{ {
auto& vm = function.vm(); auto& vm = function.vm();
// 1. Let target be F.[[WrappedTargetFunction]]. // 1. Let target be F.[[WrappedTargetFunction]].
auto const& target = function.wrapped_target_function(); auto& target = function.wrapped_target_function();
// 2. Assert: IsCallable(target) is true. // 2. Assert: IsCallable(target) is true.
VERIFY(Value(&target).is_function()); VERIFY(Value(&target).is_function());
@ -134,7 +136,7 @@ ThrowCompletionOr<Value> ordinary_wrapped_function_call(WrappedFunction const& f
} }
// 2.3 PrepareForWrappedFunctionCall ( F: a wrapped function exotic object, ), https://tc39.es/proposal-shadowrealm/#sec-prepare-for-wrapped-function-call // 2.3 PrepareForWrappedFunctionCall ( F: a wrapped function exotic object, ), https://tc39.es/proposal-shadowrealm/#sec-prepare-for-wrapped-function-call
void prepare_for_wrapped_function_call(WrappedFunction const& function, ExecutionContext& callee_context) void prepare_for_wrapped_function_call(WrappedFunction& function, ExecutionContext& callee_context)
{ {
auto& vm = function.vm(); auto& vm = function.vm();

View file

@ -20,13 +20,15 @@ public:
virtual ~WrappedFunction() = default; virtual ~WrappedFunction() = default;
virtual ThrowCompletionOr<Value> internal_call(Value this_argument, ReadonlySpan<Value> arguments_list) override; virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;
virtual Realm* realm() const override { return m_realm; } virtual Realm* realm() const override { return m_realm; }
FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; } FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; }
FunctionObject& wrapped_target_function() { return m_wrapped_target_function; } FunctionObject& wrapped_target_function() { return m_wrapped_target_function; }
virtual ThrowCompletionOr<void> get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count) override;
private: private:
WrappedFunction(Realm&, FunctionObject&, Object& prototype); WrappedFunction(Realm&, FunctionObject&, Object& prototype);
@ -37,7 +39,7 @@ private:
GC::Ref<Realm> m_realm; // [[Realm]] GC::Ref<Realm> m_realm; // [[Realm]]
}; };
ThrowCompletionOr<Value> ordinary_wrapped_function_call(WrappedFunction const&, Value this_argument, ReadonlySpan<Value> arguments_list); ThrowCompletionOr<Value> ordinary_wrapped_function_call(WrappedFunction&, Value this_argument, Span<Value> arguments_list);
void prepare_for_wrapped_function_call(WrappedFunction const&, ExecutionContext& callee_context); void prepare_for_wrapped_function_call(WrappedFunction&, ExecutionContext& callee_context);
} }

View file

@ -92,7 +92,7 @@ static JS::ThrowCompletionOr<JS::Value> execute_a_function_body(HTML::BrowsingCo
// 9. Let completion be Function.[[Call]](window, parameters) with function as the this value. // 9. Let completion be Function.[[Call]](window, parameters) with function as the this value.
// NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as // NOTE: This is not entirely clear, but I don't think they mean actually passing `function` as
// the this value argument, but using it as the object [[Call]] is executed on. // the this value argument, but using it as the object [[Call]] is executed on.
auto completion = function->internal_call(window, parameters); auto completion = JS::call(realm.vm(), *function, window, parameters);
// 10. Clean up after running a callback with environment settings. // 10. Clean up after running a callback with environment settings.
HTML::clean_up_after_running_callback(realm); HTML::clean_up_after_running_callback(realm);

View file

@ -253,7 +253,7 @@ static Response internal_json_clone(HTML::BrowsingContext const& browsing_contex
// -> has an own property named "toJSON" that is a Function // -> has an own property named "toJSON" that is a Function
if (auto to_json = object.get_without_side_effects(vm.names.toJSON); to_json.is_function()) { if (auto to_json = object.get_without_side_effects(vm.names.toJSON); to_json.is_function()) {
// Return success with the value returned by Function.[[Call]](toJSON) with value as the this value. // Return success with the value returned by Function.[[Call]](toJSON) with value as the this value.
auto to_json_result = TRY_OR_JS_ERROR(to_json.as_function().internal_call(value, GC::RootVector<JS::Value> { vm.heap() })); auto to_json_result = TRY_OR_JS_ERROR(JS::call(vm, to_json.as_function(), value));
if (!to_json_result.is_string()) if (!to_json_result.is_string())
return WebDriver::Error::from_code(ErrorCode::JavascriptError, "toJSON did not return a String"sv); return WebDriver::Error::from_code(ErrorCode::JavascriptError, "toJSON did not return a String"sv);