From e5b07858a2d85f03de50a0437d3876a182536d9e Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 30 Aug 2025 10:36:32 +0200 Subject: [PATCH] LibJS: Allocate Call{Construct,DirectEval,Builtin) contexts up front We already do this for normal Call contexts, so this is just continuing to propagate the same pattern to other instructions. Fixes #6026 --- Libraries/LibJS/Bytecode/Interpreter.cpp | 68 ++++++++----------- .../LibJS/Runtime/AbstractOperations.cpp | 13 +++- Libraries/LibJS/Runtime/BoundFunction.cpp | 17 +++-- Libraries/LibJS/Runtime/BoundFunction.h | 2 +- .../Runtime/ECMAScriptFunctionObject.cpp | 36 ++-------- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 2 +- Libraries/LibJS/Runtime/FunctionObject.h | 2 +- Libraries/LibJS/Runtime/NativeFunction.cpp | 22 +++--- Libraries/LibJS/Runtime/NativeFunction.h | 2 +- Libraries/LibJS/Runtime/ProxyObject.cpp | 11 ++- Libraries/LibJS/Runtime/ProxyObject.h | 4 +- 11 files changed, 81 insertions(+), 98 deletions(-) diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index e6387e8e71a..9dc8a9bf86c 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -2836,80 +2836,72 @@ static ThrowCompletionOr dispatch_builtin_call(Bytecode::Interpreter& int VERIFY_NOT_REACHED(); } -ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) const +template +static ThrowCompletionOr execute_call( + Bytecode::Interpreter& interpreter, + Value callee, + Value this_value, + ReadonlySpan arguments, + Operand dst, + Optional const& expression_string) { - auto callee = interpreter.get(m_callee); - - if (!callee.is_function()) [[unlikely]] { - return throw_type_error_for_callee(interpreter, callee, "function"sv, m_expression_string); - } + TRY(throw_if_needed_for_call(interpreter, callee, call_type, expression_string)); 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; + size_t argument_count = arguments.size(); 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)); + ALLOCATE_EXECUTION_CONTEXT_ON_NATIVE_STACK_WITHOUT_CLEARING_ARGS(callee_context, registers_and_constants_and_locals_count, max(arguments.size(), 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; + auto const insn_argument_count = arguments.size(); for (size_t i = 0; i < insn_argument_count; ++i) - callee_context_argument_values[i] = interpreter.get(m_arguments[i]); + 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; - auto retval = TRY(function.internal_call(*callee_context, interpreter.get(m_this_value))); - interpreter.set(m_dst, retval); + 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(), interpreter.vm().in_strict_mode() ? 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 {}; } -ThrowCompletionOr CallConstruct::execute_impl(Bytecode::Interpreter& interpreter) const +ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) const { - auto callee = interpreter.get(m_callee); + return execute_call(interpreter, interpreter.get(m_callee), interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string); +} - TRY(throw_if_needed_for_call(interpreter, callee, CallType::Construct, 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, Value(), CallType::Construct, callee, argument_values))); - return {}; +NEVER_INLINE ThrowCompletionOr CallConstruct::execute_impl(Bytecode::Interpreter& interpreter) const +{ + return execute_call(interpreter, interpreter.get(m_callee), js_undefined(), { m_arguments, m_argument_count }, m_dst, m_expression_string); } ThrowCompletionOr CallDirectEval::execute_impl(Bytecode::Interpreter& interpreter) const { - auto callee = interpreter.get(m_callee); - - TRY(throw_if_needed_for_call(interpreter, callee, CallType::DirectEval, 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::DirectEval, callee, argument_values))); - return {}; + return execute_call(interpreter, interpreter.get(m_callee), interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string); } ThrowCompletionOr CallBuiltin::execute_impl(Bytecode::Interpreter& interpreter) const { auto callee = interpreter.get(m_callee); - TRY(throw_if_needed_for_call(interpreter, callee, CallType::Call, expression_string())); - if (m_argument_count == Bytecode::builtin_argument_count(m_builtin) && callee.is_object() && interpreter.realm().get_builtin_value(m_builtin) == &callee.as_object()) { interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin, { m_arguments, m_argument_count }))); - return {}; } - 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))); - return {}; + return execute_call(interpreter, callee, interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string); } ThrowCompletionOr CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const diff --git a/Libraries/LibJS/Runtime/AbstractOperations.cpp b/Libraries/LibJS/Runtime/AbstractOperations.cpp index 6de450b741b..45565729b12 100644 --- a/Libraries/LibJS/Runtime/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/AbstractOperations.cpp @@ -105,7 +105,18 @@ ThrowCompletionOr> construct_impl(VM&, FunctionObject& function, // 2. If argumentsList is not present, set argumentsList to a new empty List. // 3. Return ? F.[[Construct]](argumentsList, newTarget). - return function.internal_construct(arguments_list, *new_target); + 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_construct(*callee_context, *new_target); } // 7.3.19 LengthOfArrayLike ( obj ), https://tc39.es/ecma262/#sec-lengthofarraylike diff --git a/Libraries/LibJS/Runtime/BoundFunction.cpp b/Libraries/LibJS/Runtime/BoundFunction.cpp index 249ef51874b..6bc28bf3480 100644 --- a/Libraries/LibJS/Runtime/BoundFunction.cpp +++ b/Libraries/LibJS/Runtime/BoundFunction.cpp @@ -69,10 +69,8 @@ ThrowCompletionOr BoundFunction::internal_call(ExecutionContext& callee_c } // 10.4.1.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget -ThrowCompletionOr> BoundFunction::internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) +ThrowCompletionOr> BoundFunction::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) { - auto& vm = this->vm(); - // 1. Let target be F.[[BoundTargetFunction]]. auto& target = *m_bound_target_function; @@ -83,9 +81,14 @@ ThrowCompletionOr> BoundFunction::internal_construct(ReadonlySpa auto& bound_args = m_bound_arguments; // 4. Let args be the list-concatenation of boundArgs and argumentsList. - auto args = GC::RootVector { heap() }; - args.extend(bound_args); - args.append(arguments_list.data(), arguments_list.size()); + auto* argument_values = callee_context.arguments.data(); + + for (ssize_t i = static_cast(callee_context.arguments.size()) - 1; i >= static_cast(bound_args.size()); --i) + argument_values[i] = argument_values[i - bound_args.size()]; + for (size_t i = 0; i < bound_args.size(); ++i) + argument_values[i] = bound_args[i]; + + callee_context.passed_argument_count += bound_args.size(); // 5. If SameValue(F, newTarget) is true, set newTarget to target. auto* final_new_target = &new_target; @@ -93,7 +96,7 @@ ThrowCompletionOr> BoundFunction::internal_construct(ReadonlySpa final_new_target = ⌖ // 6. Return ? Construct(target, args, newTarget). - return construct(vm, target, args.span(), final_new_target); + return target.internal_construct(callee_context, *final_new_target); } void BoundFunction::visit_edges(Visitor& visitor) diff --git a/Libraries/LibJS/Runtime/BoundFunction.h b/Libraries/LibJS/Runtime/BoundFunction.h index bf3d7b9c012..b22673cb0c5 100644 --- a/Libraries/LibJS/Runtime/BoundFunction.h +++ b/Libraries/LibJS/Runtime/BoundFunction.h @@ -21,7 +21,7 @@ public: virtual ~BoundFunction() override = default; virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; - virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; + virtual ThrowCompletionOr> internal_construct(ExecutionContext& arguments_list, FunctionObject& new_target) override; virtual bool is_strict_mode() const override { return m_bound_target_function->is_strict_mode(); } virtual bool has_constructor() const override { return m_bound_target_function->has_constructor(); } diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index bbf80d89ab4..65ea723d08e 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -546,35 +546,11 @@ FLATTEN ThrowCompletionOr ECMAScriptFunctionObject::internal_call(Executi } // 10.2.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget -ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) +ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) { auto& vm = this->vm(); - 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()))); - } else { - const_cast(ecmascript_code()).set_bytecode_executable(TRY(Bytecode::compile(vm, *this))); - } - } - m_bytecode_executable = ecmascript_code().bytecode_executable(); - } - - 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); - - // 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(); - } + ASSERT(m_bytecode_executable); // 1. Let callerContext be the running execution context. // NOTE: No-op, kept by the VM in its execution context stack. @@ -591,16 +567,16 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct( } // 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget). - prepare_for_ordinary_call(vm, *callee_context, &new_target); + prepare_for_ordinary_call(vm, callee_context, &new_target); // 5. Assert: calleeContext is now the running execution context. - VERIFY(&vm.running_execution_context() == callee_context); + VERIFY(&vm.running_execution_context() == &callee_context); // 6. If kind is base, then if (kind == ConstructorKind::Base) { // a. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument). if (uses_this()) - ordinary_call_bind_this(vm, *callee_context, this_argument); + ordinary_call_bind_this(vm, callee_context, this_argument); // b. Let initializeResult be Completion(InitializeInstanceElements(thisArgument, F)). auto initialize_result = this_argument->initialize_instance_elements(*this); @@ -616,7 +592,7 @@ ThrowCompletionOr> ECMAScriptFunctionObject::internal_construct( } // 7. Let constructorEnv be the LexicalEnvironment of calleeContext. - auto constructor_env = callee_context->lexical_environment; + auto constructor_env = callee_context.lexical_environment; // 8. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)). auto result = ordinary_call_evaluate_body(vm); diff --git a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index 9f5a2f1a537..c3f6d58ac44 100644 --- a/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -119,7 +119,7 @@ public: 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; + virtual ThrowCompletionOr> internal_construct(ExecutionContext&, FunctionObject& new_target) override; void make_method(Object& home_object); diff --git a/Libraries/LibJS/Runtime/FunctionObject.h b/Libraries/LibJS/Runtime/FunctionObject.h index e7ba55d5385..78355a2ce31 100644 --- a/Libraries/LibJS/Runtime/FunctionObject.h +++ b/Libraries/LibJS/Runtime/FunctionObject.h @@ -26,7 +26,7 @@ public: 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(); } + virtual ThrowCompletionOr> internal_construct(ExecutionContext&, [[maybe_unused]] FunctionObject& new_target) { VERIFY_NOT_REACHED(); } void set_function_name(Variant const& name_arg, Optional const& prefix = {}); void set_function_length(double length); diff --git a/Libraries/LibJS/Runtime/NativeFunction.cpp b/Libraries/LibJS/Runtime/NativeFunction.cpp index 259bad65e09..d9c4435ab6d 100644 --- a/Libraries/LibJS/Runtime/NativeFunction.cpp +++ b/Libraries/LibJS/Runtime/NativeFunction.cpp @@ -170,7 +170,7 @@ ThrowCompletionOr NativeFunction::internal_call(ExecutionContext& callee_ } // 10.3.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget -ThrowCompletionOr> NativeFunction::internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) +ThrowCompletionOr> NativeFunction::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) { auto& vm = this->vm(); @@ -179,16 +179,10 @@ ThrowCompletionOr> NativeFunction::internal_construct(ReadonlySp // 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()); - // 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; - 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; @@ -202,21 +196,21 @@ ThrowCompletionOr> NativeFunction::internal_construct(ReadonlySp 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. - 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: This is a LibJS specific hack for NativeFunction to inherit the strictness of its caller. - callee_context->is_strict_mode = caller_context.is_strict_mode; + callee_context.is_strict_mode = caller_context.is_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. The this value is uninitialized, argumentsList provides the named parameters, and newTarget provides the NewTarget value. auto result = construct(new_target); diff --git a/Libraries/LibJS/Runtime/NativeFunction.h b/Libraries/LibJS/Runtime/NativeFunction.h index f91e489d240..cff1f6a1e56 100644 --- a/Libraries/LibJS/Runtime/NativeFunction.h +++ b/Libraries/LibJS/Runtime/NativeFunction.h @@ -29,7 +29,7 @@ public: virtual ~NativeFunction() override = default; virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; - virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; + virtual ThrowCompletionOr> internal_construct(ExecutionContext&, FunctionObject& new_target) override; // Used for [[Call]] / [[Construct]]'s "...result of evaluating F in a manner that conforms to the specification of F". // Needs to be overridden by all NativeFunctions without an m_native_function. diff --git a/Libraries/LibJS/Runtime/ProxyObject.cpp b/Libraries/LibJS/Runtime/ProxyObject.cpp index ab8cd25a4d5..be0a88100ee 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.cpp +++ b/Libraries/LibJS/Runtime/ProxyObject.cpp @@ -827,7 +827,7 @@ bool ProxyObject::has_constructor() const } // 10.5.13 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget -ThrowCompletionOr> ProxyObject::internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) +ThrowCompletionOr> ProxyObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target) { LIMIT_PROXY_RECURSION_DEPTH(); @@ -850,11 +850,11 @@ ThrowCompletionOr> ProxyObject::internal_construct(ReadonlySpan< // 7. If trap is undefined, then if (!trap) { // a. Return ? Construct(target, argumentsList, newTarget). - return construct(vm, static_cast(*m_target), arguments_list, &new_target); + return as(*m_target).internal_construct(callee_context, new_target); } // 8. Let argArray be CreateArrayFromList(argumentsList). - auto arguments_array = Array::create_from(realm, arguments_list); + auto arguments_array = Array::create_from(realm, callee_context.arguments); // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). auto new_object = TRY(call(vm, trap, m_handler, m_target, arguments_array, &new_target)); @@ -890,4 +890,9 @@ void ProxyObject::visit_edges(Cell::Visitor& visitor) visitor.visit(m_handler); } +ThrowCompletionOr ProxyObject::get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count) +{ + return as(*m_target).get_stack_frame_size(registers_and_constants_and_locals_count, argument_count); +} + } diff --git a/Libraries/LibJS/Runtime/ProxyObject.h b/Libraries/LibJS/Runtime/ProxyObject.h index c499d608493..46f27f12916 100644 --- a/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Libraries/LibJS/Runtime/ProxyObject.h @@ -43,7 +43,7 @@ public: virtual ThrowCompletionOr internal_delete(PropertyKey const&) override; virtual ThrowCompletionOr> internal_own_property_keys() const override; virtual ThrowCompletionOr internal_call(ExecutionContext&, Value this_argument) override; - virtual ThrowCompletionOr> internal_construct(ReadonlySpan arguments_list, FunctionObject& new_target) override; + virtual ThrowCompletionOr> internal_construct(ExecutionContext&, FunctionObject& new_target) override; ThrowCompletionOr validate_non_revoked_proxy() const; private: @@ -54,6 +54,8 @@ private: virtual bool is_function() const override { return m_target->is_function(); } virtual bool is_proxy_object() const final { return true; } + virtual ThrowCompletionOr get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count) override; + GC::Ref m_target; GC::Ref m_handler; bool m_is_revoked { false };