From a05be67e4ae9e22b0aa8b207fd425c66438ee727 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 27 Apr 2025 11:53:11 +0200 Subject: [PATCH] LibJS: Let invokers (callers) of [[Call]] allocate ExecutionContext 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 --- Libraries/LibJS/Bytecode/Interpreter.cpp | 28 +++++++++--- .../LibJS/Runtime/AbstractOperations.cpp | 27 +++++++++++- Libraries/LibJS/Runtime/BoundFunction.cpp | 25 +++++++---- Libraries/LibJS/Runtime/BoundFunction.h | 2 +- .../Runtime/ECMAScriptFunctionObject.cpp | 43 ++++++++----------- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 3 +- Libraries/LibJS/Runtime/ExecutionContext.h | 12 +++++- Libraries/LibJS/Runtime/FunctionObject.h | 3 +- Libraries/LibJS/Runtime/FunctionPrototype.cpp | 2 +- Libraries/LibJS/Runtime/FunctionPrototype.h | 2 +- Libraries/LibJS/Runtime/NativeFunction.cpp | 32 ++++++-------- Libraries/LibJS/Runtime/NativeFunction.h | 2 +- Libraries/LibJS/Runtime/ProxyObject.cpp | 6 +-- Libraries/LibJS/Runtime/ProxyObject.h | 2 +- Libraries/LibJS/Runtime/WrappedFunction.cpp | 22 +++++----- Libraries/LibJS/Runtime/WrappedFunction.h | 8 ++-- Libraries/LibWeb/WebDriver/ExecuteScript.cpp | 2 +- Libraries/LibWeb/WebDriver/JSON.cpp | 2 +- 18 files changed, 139 insertions(+), 84 deletions(-) diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 4f41a4c8aa2..db85ab05055 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -2616,12 +2616,30 @@ ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) c { 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); - for (size_t i = 0; i < m_argument_count; ++i) - argument_values[i] = interpreter.get(m_arguments[i]); - interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), CallType::Call, callee, argument_values))); + auto& function = callee.as_function(); + + ExecutionContext* callee_context = nullptr; + 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 {}; } diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp index 82965d1317f..ed3bc322f56 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -59,7 +59,19 @@ ThrowCompletionOr call_impl(VM& vm, Value function, Value this_value, Rea return vm.throw_completion(ErrorType::NotAFunction, function.to_string_without_side_effects()); // 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 call_impl(VM&, FunctionObject& function, Value this_value, ReadonlySpan arguments_list) @@ -70,7 +82,18 @@ ThrowCompletionOr call_impl(VM&, FunctionObject& function, Value this_val // Note: Called with a FunctionObject ref // 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 diff --git a/Libraries/LibJS/Runtime/BoundFunction.cpp b/Libraries/LibJS/Runtime/BoundFunction.cpp index a8d4be7df0b..6356e3b8faf 100644 --- a/Libraries/LibJS/Runtime/BoundFunction.cpp +++ b/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -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 -ThrowCompletionOr BoundFunction::internal_call([[maybe_unused]] Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr BoundFunction::internal_call(ExecutionContext& outer_context, [[maybe_unused]] Value this_argument) { - auto& vm = this->vm(); - // 1. Let target be F.[[BoundTargetFunction]]. auto& target = *m_bound_target_function; @@ -57,13 +55,24 @@ ThrowCompletionOr BoundFunction::internal_call([[maybe_unused]] Value thi auto& bound_args = m_bound_arguments; // 4. Let args be the list-concatenation of boundArgs and argumentsList. - Vector args; - args.ensure_capacity(bound_args.size() + arguments_list.size()); - args.extend(bound_args); - args.append(arguments_list.data(), arguments_list.size()); + + ExecutionContext* callee_context = nullptr; + size_t registers_and_constants_and_locals_count = 0; + 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). - 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 diff --git a/Libraries/LibJS/Runtime/BoundFunction.h b/Libraries/LibJS/Runtime/BoundFunction.h index 9f90e3e7779..2f4665726b5 100644 --- a/Libraries/LibJS/Runtime/BoundFunction.h +++ b/Libraries/LibJS/Runtime/BoundFunction.h @@ -20,7 +20,7 @@ public: virtual ~BoundFunction() override = default; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); } diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index 87f11fda074..3eec2d9e1b4 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -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 ECMAScriptFunctionObject::internal_call(Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr ECMAScriptFunctionObject::get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count) { - 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 (!ecmascript_code().bytecode_executable()) { if (is_module_wrapper()) { - const_cast(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm, ecmascript_code(), kind(), name()))); + const_cast(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm(), ecmascript_code(), kind(), name()))); } else { - const_cast(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm, *this))); + const_cast(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm(), *this))); } } 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(); + argument_count = max(argument_count, formal_parameters().size()); + return {}; +} - u32 arguments_count = max(arguments_list.size(), 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(); - ExecutionContext* callee_context = nullptr; - ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, registers_and_constants_and_locals_count, arguments_count); +// 10.2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist +ThrowCompletionOr ECMAScriptFunctionObject::internal_call(ExecutionContext& callee_context, Value this_argument) +{ + auto& vm = this->vm(); - // 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(); - } + 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). // 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. - VERIFY(&vm.running_execution_context() == callee_context); + VERIFY(&vm.running_execution_context() == &callee_context); // 4. If F.[[IsClassConstructor]] is true, then if (is_class_constructor()) { @@ -537,7 +530,7 @@ ThrowCompletionOr ECMAScriptFunctionObject::internal_call(Value this_argu // 5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). 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)). auto result = ordinary_call_evaluate_body(); diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 0bfd51aa4dd..920463c54fe 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -116,7 +116,8 @@ public: virtual void initialize(Realm&) override; virtual ~ECMAScriptFunctionObject() override = default; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr get_stack_frame_size(size_t& registers_and_constants_and_locals_slots, size_t& argument_count) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; void make_method(Object& home_object); diff --git a/Libraries/LibJS/Runtime/ExecutionContext.h b/Libraries/LibJS/Runtime/ExecutionContext.h index 8a759de8644..a171491a510 100644 --- a/Libraries/LibJS/Runtime/ExecutionContext.h +++ b/Libraries/LibJS/Runtime/ExecutionContext.h @@ -114,7 +114,7 @@ private: 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, \ arguments_count) \ auto execution_context_size = sizeof(JS::ExecutionContext) \ @@ -130,6 +130,16 @@ private: 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 { ExecutionContext* execution_context; RefPtr source_range; diff --git a/Libraries/LibJS/Runtime/FunctionObject.h b/Libraries/LibJS/Runtime/FunctionObject.h index ac1b8803c5b..c87945bab3c 100644 --- a/Libraries/LibJS/Runtime/FunctionObject.h +++ b/Libraries/LibJS/Runtime/FunctionObject.h @@ -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 - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) = 0; + virtual ThrowCompletionOr get_stack_frame_size([[maybe_unused]] size_t& registers_and_constants_and_locals_count, [[maybe_unused]] size_t& argument_count) { return {}; } + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) = 0; virtual ThrowCompletionOr> internal_construct([[maybe_unused]] ReadonlySpan arguments_list, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); } void set_function_name(Variant const& name_arg, Optional const& prefix = {}); diff --git a/Libraries/LibJS/Runtime/FunctionPrototype.cpp b/Libraries/LibJS/Runtime/FunctionPrototype.cpp index d5522add542..dce44e3232e 100644 --- a/Libraries/LibJS/Runtime/FunctionPrototype.cpp +++ b/Libraries/LibJS/Runtime/FunctionPrototype.cpp @@ -40,7 +40,7 @@ void FunctionPrototype::initialize(Realm& realm) define_direct_property(vm.names.name, PrimitiveString::create(vm, String {}), Attribute::Configurable); } -ThrowCompletionOr FunctionPrototype::internal_call(Value, ReadonlySpan) +ThrowCompletionOr FunctionPrototype::internal_call(ExecutionContext&, Value) { // The Function prototype object: // - accepts any arguments and returns undefined when invoked. diff --git a/Libraries/LibJS/Runtime/FunctionPrototype.h b/Libraries/LibJS/Runtime/FunctionPrototype.h index 493fcd53ca4..d56b755dc50 100644 --- a/Libraries/LibJS/Runtime/FunctionPrototype.h +++ b/Libraries/LibJS/Runtime/FunctionPrototype.h @@ -18,7 +18,7 @@ public: virtual void initialize(Realm&) override; virtual ~FunctionPrototype() override = default; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; private: explicit FunctionPrototype(Realm&); diff --git a/Libraries/LibJS/Runtime/NativeFunction.cpp b/Libraries/LibJS/Runtime/NativeFunction.cpp index 4592c616269..68e2bad14d5 100644 --- a/Libraries/LibJS/Runtime/NativeFunction.cpp +++ b/Libraries/LibJS/Runtime/NativeFunction.cpp @@ -110,7 +110,7 @@ NativeFunction::NativeFunction(FlyString name, Object& prototype) // 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 -ThrowCompletionOr NativeFunction::internal_call(Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr NativeFunction::internal_call(ExecutionContext& callee_context, Value this_argument) { auto& vm = this->vm(); @@ -119,12 +119,10 @@ ThrowCompletionOr NativeFunction::internal_call(Value this_argument, Read // 2. If callerContext is not already suspended, suspend callerContext. // 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. - callee_context->function = this; - callee_context->function_name = m_name_string; + callee_context.function = this; + callee_context.function_name = m_name_string; // 5. Let calleeRealm be F.[[Realm]]. auto callee_realm = m_realm; @@ -138,29 +136,27 @@ ThrowCompletionOr NativeFunction::internal_call(Value this_argument, Read VERIFY(callee_realm); // 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. // Note: This is already the default value. // 8. Perform any necessary implementation-defined initialization of calleeContext. - 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.this_value = this_argument; - callee_context->lexical_environment = caller_context.lexical_environment; - callee_context->variable_environment = caller_context.variable_environment; + callee_context.lexical_environment = caller_context.lexical_environment; + callee_context.variable_environment = caller_context.variable_environment; // 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. - 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. - callee_context->is_strict_mode = vm.in_strict_mode(); + callee_context.is_strict_mode = vm.in_strict_mode(); // -------------------------------------------------------------------------- // 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. auto result = call(); @@ -184,6 +180,10 @@ ThrowCompletionOr> NativeFunction::internal_construct(ReadonlySp // 3. Let calleeContext be a new execution context. ExecutionContext* callee_context = nullptr; 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. callee_context->function = this; @@ -206,10 +206,6 @@ ThrowCompletionOr> NativeFunction::internal_construct(ReadonlySp // 7. Set the ScriptOrModule of calleeContext to null. // 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->variable_environment = caller_context.variable_environment; diff --git a/Libraries/LibJS/Runtime/NativeFunction.h b/Libraries/LibJS/Runtime/NativeFunction.h index 502be0d77e9..3835b0bf81a 100644 --- a/Libraries/LibJS/Runtime/NativeFunction.h +++ b/Libraries/LibJS/Runtime/NativeFunction.h @@ -26,7 +26,7 @@ public: virtual ~NativeFunction() override = default; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; virtual ThrowCompletionOr> internal_construct(ReadonlySpan 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". diff --git a/Libraries/LibJS/Runtime/ProxyObject.cpp b/Libraries/LibJS/Runtime/ProxyObject.cpp index abc2b9714e1..4bb1b760bd0 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -779,7 +779,7 @@ ThrowCompletionOr> 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 -ThrowCompletionOr ProxyObject::internal_call(Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr ProxyObject::internal_call(ExecutionContext& callee_context, Value this_argument) { LIMIT_PROXY_RECURSION_DEPTH(); @@ -802,11 +802,11 @@ ThrowCompletionOr ProxyObject::internal_call(Value this_argument, Readonl // 6. If trap is undefined, then if (!trap) { // 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). - 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 »). return call(vm, trap, m_handler, m_target, this_argument, arguments_array); diff --git a/Libraries/LibJS/Runtime/ProxyObject.h b/Libraries/LibJS/Runtime/ProxyObject.h index 8f77eea9738..b49105e6cf4 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Libraries/LibJS/Runtime/ProxyObject.h @@ -42,7 +42,7 @@ public: virtual ThrowCompletionOr internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override; virtual ThrowCompletionOr internal_delete(PropertyKey const&) override; virtual ThrowCompletionOr> internal_own_property_keys() const override; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; ThrowCompletionOr validate_non_revoked_proxy() const; diff --git a/Libraries/LibJS/Runtime/WrappedFunction.cpp b/Libraries/LibJS/Runtime/WrappedFunction.cpp index e3d0bcb3de4..2be73e70eb2 100644 --- a/Libraries/LibJS/Runtime/WrappedFunction.cpp +++ b/Libraries/LibJS/Runtime/WrappedFunction.cpp @@ -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 -ThrowCompletionOr WrappedFunction::internal_call(Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr WrappedFunction::internal_call(ExecutionContext& callee_context, Value this_argument) { auto& vm = this->vm(); @@ -62,16 +62,13 @@ ThrowCompletionOr 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). - ExecutionContext* callee_context = nullptr; - ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK(callee_context, 0, 0); - - prepare_for_wrapped_function_call(*this, *callee_context); + prepare_for_wrapped_function_call(*this, callee_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)). - 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. vm.pop_execution_context(); @@ -80,13 +77,18 @@ ThrowCompletionOr WrappedFunction::internal_call(Value this_argument, Rea return result; } +ThrowCompletionOr 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 -ThrowCompletionOr ordinary_wrapped_function_call(WrappedFunction const& function, Value this_argument, ReadonlySpan arguments_list) +ThrowCompletionOr ordinary_wrapped_function_call(WrappedFunction& function, Value this_argument, Span arguments_list) { auto& vm = function.vm(); // 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. VERIFY(Value(&target).is_function()); @@ -134,7 +136,7 @@ ThrowCompletionOr 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 -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(); diff --git a/Libraries/LibJS/Runtime/WrappedFunction.h b/Libraries/LibJS/Runtime/WrappedFunction.h index d29e67db66a..86bc0a7c95c 100644 --- a/Libraries/LibJS/Runtime/WrappedFunction.h +++ b/Libraries/LibJS/Runtime/WrappedFunction.h @@ -20,13 +20,15 @@ public: virtual ~WrappedFunction() = default; - virtual ThrowCompletionOr internal_call(Value this_argument, ReadonlySpan arguments_list) override; + virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; virtual Realm* realm() const override { return m_realm; } FunctionObject const& wrapped_target_function() const { return m_wrapped_target_function; } FunctionObject& wrapped_target_function() { return m_wrapped_target_function; } + virtual ThrowCompletionOr get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count) override; + private: WrappedFunction(Realm&, FunctionObject&, Object& prototype); @@ -37,7 +39,7 @@ private: GC::Ref m_realm; // [[Realm]] }; -ThrowCompletionOr ordinary_wrapped_function_call(WrappedFunction const&, Value this_argument, ReadonlySpan arguments_list); -void prepare_for_wrapped_function_call(WrappedFunction const&, ExecutionContext& callee_context); +ThrowCompletionOr ordinary_wrapped_function_call(WrappedFunction&, Value this_argument, Span arguments_list); +void prepare_for_wrapped_function_call(WrappedFunction&, ExecutionContext& callee_context); } diff --git a/Libraries/LibWeb/WebDriver/ExecuteScript.cpp b/Libraries/LibWeb/WebDriver/ExecuteScript.cpp index e4951b8c10a..1a0fefb0931 100644 --- a/Libraries/LibWeb/WebDriver/ExecuteScript.cpp +++ b/Libraries/LibWeb/WebDriver/ExecuteScript.cpp @@ -92,7 +92,7 @@ static JS::ThrowCompletionOr execute_a_function_body(HTML::BrowsingCo // 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 // 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. HTML::clean_up_after_running_callback(realm); diff --git a/Libraries/LibWeb/WebDriver/JSON.cpp b/Libraries/LibWeb/WebDriver/JSON.cpp index f614f9f26b5..6ae29a2809c 100644 --- a/Libraries/LibWeb/WebDriver/JSON.cpp +++ b/Libraries/LibWeb/WebDriver/JSON.cpp @@ -253,7 +253,7 @@ static Response internal_json_clone(HTML::BrowsingContext const& browsing_contex // -> 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()) { // 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 { vm.heap() })); + auto to_json_result = TRY_OR_JS_ERROR(JS::call(vm, to_json.as_function(), value)); if (!to_json_result.is_string()) return WebDriver::Error::from_code(ErrorCode::JavascriptError, "toJSON did not return a String"sv);