From 996ea109b3987de957318142f44f9c021c4ada30 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 30 Aug 2025 11:00:54 +0200 Subject: [PATCH] LibJS: Allocate context up front when calling with argument array This necessitated splitting CallWithArgumentArray into three variants, one for each call type (call, construct and direct eval). --- Libraries/LibJS/Bytecode/ASTCodegen.cpp | 18 ++-- Libraries/LibJS/Bytecode/Instruction.h | 2 + Libraries/LibJS/Bytecode/Interpreter.cpp | 112 ++++++++++++++++++----- Libraries/LibJS/Bytecode/Op.h | 47 ++++++++-- 4 files changed, 143 insertions(+), 36 deletions(-) diff --git a/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 50018c2aa3a..66d2637d68e 100644 --- a/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1777,7 +1777,13 @@ Bytecode::CodeGenerationErrorOr> CallExpression::generat if (has_spread) { auto arguments = TRY(arguments_to_array_for_call(generator, this->arguments())).value(); - generator.emit(call_type, dst, callee, this_value, arguments, expression_string_index); + if (call_type == Op::CallType::Construct) { + generator.emit(dst, callee, this_value, arguments, expression_string_index); + } else if (call_type == Op::CallType::DirectEval) { + generator.emit(dst, callee, this_value, arguments, expression_string_index); + } else { + generator.emit(dst, callee, this_value, arguments, expression_string_index); + } } else { Vector argument_operands; argument_operands.ensure_capacity(arguments().size()); @@ -2019,7 +2025,7 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::genera auto array = generator.allocate_register(); generator.emit_with_extra_operand_slots(1, array, ReadonlySpan { &received_completion_value, 1 }); auto inner_result = generator.allocate_register(); - generator.emit(Bytecode::Op::CallType::Call, inner_result, next_method, iterator, array); + generator.emit(inner_result, next_method, iterator, array); // ii. If generatorKind is async, set innerResult to ? Await(innerResult). if (generator.is_in_async_generator_function()) { @@ -2108,7 +2114,7 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::genera // 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »). auto received_value_array = generator.allocate_register(); generator.emit_with_extra_operand_slots(1, received_value_array, ReadonlySpan { &received_completion_value, 1 }); - generator.emit(Bytecode::Op::CallType::Call, inner_result, throw_method, iterator, received_value_array); + generator.emit(inner_result, throw_method, iterator, received_value_array); // 2. If generatorKind is async, set innerResult to ? Await(innerResult). if (generator.is_in_async_generator_function()) { @@ -2205,7 +2211,7 @@ Bytecode::CodeGenerationErrorOr> YieldExpression::genera auto call_array = generator.allocate_register(); generator.emit_with_extra_operand_slots(1, call_array, ReadonlySpan { &received_completion_value, 1 }); auto inner_return_result = generator.allocate_register(); - generator.emit(Bytecode::Op::CallType::Call, inner_return_result, return_method, iterator, call_array); + generator.emit(inner_return_result, return_method, iterator, call_array); // v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult). if (generator.is_in_async_generator_function()) { @@ -2542,7 +2548,7 @@ Bytecode::CodeGenerationErrorOr> TaggedTemplateLiteral:: generator.emit(arguments); auto dst = choose_dst(generator, preferred_dst); - generator.emit(Bytecode::Op::CallType::Call, dst, tag, this_value, arguments); + generator.emit(dst, tag, this_value, arguments); return dst; } @@ -3502,7 +3508,7 @@ static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::G TRY(reference.visit( [&](OptionalChain::Call const& call) -> Bytecode::CodeGenerationErrorOr { auto arguments = TRY(arguments_to_array_for_call(generator, call.arguments)).value(); - generator.emit(Bytecode::Op::CallType::Call, current_value, current_value, current_base, arguments); + generator.emit(current_value, current_value, current_base, arguments); generator.emit_mov(current_base, generator.add_constant(js_undefined())); return {}; }, diff --git a/Libraries/LibJS/Bytecode/Instruction.h b/Libraries/LibJS/Bytecode/Instruction.h index 045b79d57f6..d141b0d0cf4 100644 --- a/Libraries/LibJS/Bytecode/Instruction.h +++ b/Libraries/LibJS/Bytecode/Instruction.h @@ -27,7 +27,9 @@ O(Call) \ O(CallBuiltin) \ O(CallConstruct) \ + O(CallConstructWithArgumentArray) \ O(CallDirectEval) \ + O(CallDirectEvalWithArgumentArray) \ O(CallWithArgumentArray) \ O(Catch) \ O(ConcatString) \ diff --git a/Libraries/LibJS/Bytecode/Interpreter.cpp b/Libraries/LibJS/Bytecode/Interpreter.cpp index 9dc8a9bf86c..1a8f7d74cc5 100644 --- a/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -587,7 +587,9 @@ FLATTEN_ON_CLANG void Interpreter::run_bytecode(size_t entry_point) HANDLE_INSTRUCTION(Call); HANDLE_INSTRUCTION(CallBuiltin); HANDLE_INSTRUCTION(CallConstruct); + HANDLE_INSTRUCTION(CallConstructWithArgumentArray); HANDLE_INSTRUCTION(CallDirectEval); + HANDLE_INSTRUCTION(CallDirectEvalWithArgumentArray); HANDLE_INSTRUCTION(CallWithArgumentArray); HANDLE_INSTRUCTION_WITHOUT_EXCEPTION_CHECK(Catch); HANDLE_INSTRUCTION(ConcatString); @@ -2904,13 +2906,69 @@ ThrowCompletionOr CallBuiltin::execute_impl(Bytecode::Interpreter& interpr return execute_call(interpreter, callee, interpreter.get(m_this_value), { m_arguments, m_argument_count }, m_dst, m_expression_string); } +template +static ThrowCompletionOr call_with_argument_array( + Bytecode::Interpreter& interpreter, + Value callee, + Value this_value, + Value arguments, + Operand dst, + Optional const& expression_string) +{ + TRY(throw_if_needed_for_call(interpreter, callee, call_type, expression_string)); + + auto& function = callee.as_function(); + + auto& argument_array = arguments.as_array(); + auto argument_array_length = argument_array.indexed_properties().array_like_size(); + + ExecutionContext* callee_context = nullptr; + size_t argument_count = argument_array_length; + size_t registers_and_constants_and_locals_count = 0; + 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(argument_array_length, 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 = argument_array_length; + + for (size_t i = 0; i < insn_argument_count; ++i) { + if (auto maybe_value = argument_array.indexed_properties().get(i); maybe_value.has_value()) + callee_context_argument_values[i] = maybe_value.release_value().value; + else + callee_context_argument_values[i] = js_undefined(); + } + for (size_t i = insn_argument_count; i < callee_context_argument_count; ++i) + callee_context_argument_values[i] = js_undefined(); + callee_context->passed_argument_count = insn_argument_count; + + Value retval; + if (call_type == CallType::DirectEval && callee == interpreter.realm().intrinsics().eval_function()) { + auto& vm = interpreter.vm(); + retval = TRY(perform_eval(vm, !callee_context->arguments.is_empty() ? callee_context->arguments[0] : js_undefined(), 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 CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const { - auto callee = interpreter.get(m_callee); - TRY(throw_if_needed_for_call(interpreter, callee, call_type(), expression_string())); - auto argument_values = argument_list_evaluation(interpreter, interpreter.get(arguments())); - interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, move(argument_values)))); - return {}; + return call_with_argument_array(interpreter, interpreter.get(callee()), interpreter.get(this_value()), interpreter.get(arguments()), dst(), expression_string()); +} + +ThrowCompletionOr CallDirectEvalWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const +{ + return call_with_argument_array(interpreter, interpreter.get(callee()), interpreter.get(this_value()), interpreter.get(arguments()), dst(), expression_string()); +} + +ThrowCompletionOr CallConstructWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const +{ + return call_with_argument_array(interpreter, interpreter.get(callee()), js_undefined(), interpreter.get(arguments()), dst(), expression_string()); } // 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation @@ -3645,19 +3703,6 @@ ByteString JumpUndefined::to_byte_string_impl(Bytecode::Executable const& execut m_false_target); } -static StringView call_type_to_string(CallType type) -{ - switch (type) { - case CallType::Call: - return ""sv; - case CallType::Construct: - return " (Construct)"sv; - case CallType::DirectEval: - return " (DirectEval)"sv; - } - VERIFY_NOT_REACHED(); -} - ByteString Call::to_byte_string_impl(Bytecode::Executable const& executable) const { StringBuilder builder; @@ -3729,10 +3774,8 @@ ByteString CallBuiltin::to_byte_string_impl(Bytecode::Executable const& executab ByteString CallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const { - auto type = call_type_to_string(m_type); StringBuilder builder; - builder.appendff("CallWithArgumentArray{} {}, {}, {}, {}", - type, + builder.appendff("CallWithArgumentArray {}, {}, {}, {}", format_operand("dst"sv, m_dst, executable), format_operand("callee"sv, m_callee, executable), format_operand("this"sv, m_this_value, executable), @@ -3743,6 +3786,33 @@ ByteString CallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const return builder.to_byte_string(); } +ByteString CallDirectEvalWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + StringBuilder builder; + builder.appendff("CallDirectEvalWithArgumentArray {}, {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("this"sv, m_this_value, executable), + format_operand("arguments"sv, m_arguments, executable)); + + if (m_expression_string.has_value()) + builder.appendff(" ({})", executable.get_string(m_expression_string.value())); + return builder.to_byte_string(); +} + +ByteString CallConstructWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const +{ + StringBuilder builder; + builder.appendff("CallConstructWithArgumentArray {}, {}, {}", + format_operand("dst"sv, m_dst, executable), + format_operand("callee"sv, m_callee, executable), + format_operand("arguments"sv, m_arguments, executable)); + + if (m_expression_string.has_value()) + builder.appendff(" ({})", executable.get_string(m_expression_string.value())); + return builder.to_byte_string(); +} + ByteString SuperCallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const { return ByteString::formatted("SuperCallWithArgumentArray {}, {}", diff --git a/Libraries/LibJS/Bytecode/Op.h b/Libraries/LibJS/Bytecode/Op.h index 479fce53a60..c7877ba8c42 100644 --- a/Libraries/LibJS/Bytecode/Op.h +++ b/Libraries/LibJS/Bytecode/Op.h @@ -2005,28 +2005,25 @@ private: Operand m_arguments[]; }; -class CallWithArgumentArray final : public Instruction { +template +class CallWithArgumentArrayBase : public Instruction { public: - CallWithArgumentArray(CallType type, Operand dst, Operand callee, Operand this_value, Operand arguments, Optional expression_string = {}) - : Instruction(Type::CallWithArgumentArray) + CallWithArgumentArrayBase(Type type, Operand dst, Operand callee, Operand this_value, Operand arguments, Optional expression_string = {}) + : Instruction(type) , m_dst(dst) , m_callee(callee) , m_this_value(this_value) , m_arguments(arguments) - , m_type(type) , m_expression_string(expression_string) { } Operand dst() const { return m_dst; } - CallType call_type() const { return m_type; } Operand callee() const { return m_callee; } Operand this_value() const { return m_this_value; } Operand arguments() const { return m_arguments; } Optional const& expression_string() const { return m_expression_string; } - ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; - ByteString to_byte_string_impl(Bytecode::Executable const&) const; void visit_operands_impl(Function visitor) { visitor(m_dst); @@ -2035,15 +2032,47 @@ public: visitor(m_arguments); } -private: +protected: Operand m_dst; Operand m_callee; Operand m_this_value; Operand m_arguments; - CallType m_type; Optional m_expression_string; }; +class CallWithArgumentArray final : public CallWithArgumentArrayBase { +public: + CallWithArgumentArray(Operand dst, Operand callee, Operand this_value, Operand arguments, Optional expression_string = {}) + : CallWithArgumentArrayBase(Type::CallWithArgumentArray, dst, callee, this_value, arguments, expression_string) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; +}; + +class CallDirectEvalWithArgumentArray final : public CallWithArgumentArrayBase { +public: + CallDirectEvalWithArgumentArray(Operand dst, Operand callee, Operand this_value, Operand arguments, Optional expression_string = {}) + : CallWithArgumentArrayBase(Type::CallDirectEvalWithArgumentArray, dst, callee, this_value, arguments, expression_string) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; +}; + +class CallConstructWithArgumentArray final : public CallWithArgumentArrayBase { +public: + CallConstructWithArgumentArray(Operand dst, Operand callee, Operand this_value, Operand arguments, Optional expression_string = {}) + : CallWithArgumentArrayBase(Type::CallConstructWithArgumentArray, dst, callee, this_value, arguments, expression_string) + { + } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + ByteString to_byte_string_impl(Bytecode::Executable const&) const; +}; + class SuperCallWithArgumentArray : public Instruction { public: explicit SuperCallWithArgumentArray(Operand dst, Operand arguments, bool is_synthetic)