diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 3c81dae364..81bfbba25d 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1463,8 +1463,6 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode generator.emit(callee_reg); } - TRY(arguments_to_array_for_call(generator, arguments())); - Bytecode::Op::CallType call_type; if (is(*this)) { call_type = Bytecode::Op::CallType::Construct; @@ -1478,7 +1476,26 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode if (auto expression_string = this->expression_string(); expression_string.has_value()) expression_string_index = generator.intern_string(expression_string.release_value()); - generator.emit(call_type, callee_reg, this_reg, expression_string_index); + bool has_spread = any_of(arguments(), [](auto& argument) { return argument.is_spread; }); + + if (has_spread) { + TRY(arguments_to_array_for_call(generator, arguments())); + generator.emit(call_type, callee_reg, this_reg, expression_string_index); + } else { + Optional first_argument_reg {}; + for (size_t i = 0; i < arguments().size(); ++i) { + auto reg = generator.allocate_register(); + if (!first_argument_reg.has_value()) + first_argument_reg = reg; + } + u32 register_offset = 0; + for (auto const& argument : arguments()) { + TRY(argument.value->generate_bytecode(generator)); + generator.emit(Bytecode::Register { first_argument_reg.value().index() + register_offset }); + register_offset += 1; + } + generator.emit(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index); + } return {}; } diff --git a/Userland/Libraries/LibJS/Bytecode/Instruction.h b/Userland/Libraries/LibJS/Bytecode/Instruction.h index 3fee52806f..6e033e2ba5 100644 --- a/Userland/Libraries/LibJS/Bytecode/Instruction.h +++ b/Userland/Libraries/LibJS/Bytecode/Instruction.h @@ -18,6 +18,7 @@ O(BitwiseOr) \ O(BitwiseXor) \ O(BlockDeclarationInstantiation) \ + O(Call) \ O(CallWithArgumentArray) \ O(ConcatString) \ O(ContinuePendingUnwind) \ diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 7fecd59ba5..1fee5143ad 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -694,42 +694,39 @@ static MarkedVector argument_list_evaluation(Bytecode::Interpreter& inter return argument_values; } -Completion CallWithArgumentArray::throw_type_error_for_callee(Bytecode::Interpreter& interpreter, StringView callee_type) const +static Completion throw_type_error_for_callee(Bytecode::Interpreter& interpreter, auto& call, StringView callee_type) { auto& vm = interpreter.vm(); - auto callee = interpreter.reg(m_callee); + auto callee = interpreter.reg(call.callee()); - if (m_expression_string.has_value()) - return vm.throw_completion(ErrorType::IsNotAEvaluatedFrom, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type, interpreter.current_executable().get_string(m_expression_string->value())); + if (call.expression_string().has_value()) + return vm.throw_completion(ErrorType::IsNotAEvaluatedFrom, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type, interpreter.current_executable().get_string(call.expression_string()->value())); return vm.throw_completion(ErrorType::IsNotA, TRY_OR_THROW_OOM(vm, callee.to_string_without_side_effects()), callee_type); } -ThrowCompletionOr CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const +static ThrowCompletionOr throw_if_needed_for_call(Interpreter& interpreter, auto& call, Value callee) +{ + if (call.call_type() == CallType::Call && !callee.is_function()) + return throw_type_error_for_callee(interpreter, call, "function"sv); + if (call.call_type() == CallType::Construct && !callee.is_constructor()) + return throw_type_error_for_callee(interpreter, call, "constructor"sv); + return {}; +} + +static ThrowCompletionOr perform_call(Interpreter& interpreter, auto& call, Value callee, MarkedVector argument_values) { auto& vm = interpreter.vm(); - - auto callee = interpreter.reg(m_callee); - - if (m_type == CallType::Call && !callee.is_function()) - return throw_type_error_for_callee(interpreter, "function"sv); - if (m_type == CallType::Construct && !callee.is_constructor()) - return throw_type_error_for_callee(interpreter, "constructor"sv); - + auto this_value = interpreter.reg(call.this_value()); auto& function = callee.as_function(); - - auto this_value = interpreter.reg(m_this_value); - - auto argument_values = argument_list_evaluation(interpreter); - Value return_value; - if (m_type == CallType::DirectEval) { + if (call.call_type() == CallType::DirectEval) { if (callee == interpreter.realm().intrinsics().eval_function()) return_value = TRY(perform_eval(vm, !argument_values.is_empty() ? argument_values[0].value_or(JS::js_undefined()) : js_undefined(), vm.in_strict_mode() ? CallerMode::Strict : CallerMode::NonStrict, EvalMode::Direct)); else - return_value = TRY(call(vm, function, this_value, move(argument_values))); - } else if (m_type == CallType::Call) - return_value = TRY(call(vm, function, this_value, move(argument_values))); + return_value = TRY(JS::call(vm, function, this_value, move(argument_values))); + } else if (call.call_type() == CallType::Call) + return_value = TRY(JS::call(vm, function, this_value, move(argument_values))); else return_value = TRY(construct(vm, function, move(argument_values))); @@ -737,6 +734,29 @@ ThrowCompletionOr CallWithArgumentArray::execute_impl(Bytecode::Interprete return {}; } +ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto& vm = interpreter.vm(); + auto callee = interpreter.reg(m_callee); + + TRY(throw_if_needed_for_call(interpreter, *this, callee)); + + MarkedVector argument_values(vm.heap()); + argument_values.ensure_capacity(m_argument_count); + for (u32 i = 0; i < m_argument_count; ++i) { + argument_values.unchecked_append(interpreter.reg(Register { m_first_argument.index() + i })); + } + return perform_call(interpreter, *this, callee, move(argument_values)); +} + +ThrowCompletionOr CallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const +{ + auto callee = interpreter.reg(m_callee); + TRY(throw_if_needed_for_call(interpreter, *this, callee)); + auto argument_values = argument_list_evaluation(interpreter); + return perform_call(interpreter, *this, callee, move(argument_values)); +} + // 13.3.7.1 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation ThrowCompletionOr SuperCallWithArgumentArray::execute_impl(Bytecode::Interpreter& interpreter) const { @@ -896,6 +916,18 @@ void CopyObjectExcludingProperties::replace_references_impl(Register from, Regis } } +void Call::replace_references_impl(Register from, Register to) +{ + if (m_callee == from) + m_callee = to; + + if (m_this_value == from) + m_this_value = to; + + if (m_first_argument == from) + m_first_argument = to; +} + void CallWithArgumentArray::replace_references_impl(Register from, Register to) { if (m_callee == from) @@ -1383,24 +1415,32 @@ DeprecatedString JumpUndefined::to_deprecated_string_impl(Bytecode::Executable c return DeprecatedString::formatted("JumpUndefined undefined:{} not undefined:{}", true_string, false_string); } +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(); +} + +DeprecatedString Call::to_deprecated_string_impl(Bytecode::Executable const& executable) const +{ + auto type = call_type_to_string(m_type); + if (m_expression_string.has_value()) + return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} ({})", type, m_callee, m_this_value, m_first_argument, executable.get_string(m_expression_string.value())); + return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{}", type, m_callee, m_first_argument, m_this_value); +} + DeprecatedString CallWithArgumentArray::to_deprecated_string_impl(Bytecode::Executable const& executable) const { - StringView type; - switch (m_type) { - case CallType::Call: - type = ""sv; - break; - case CallType::Construct: - type = " (Construct)"sv; - break; - case CallType::DirectEval: - type = " (DirectEval)"sv; - break; - } - + auto type = call_type_to_string(m_type); if (m_expression_string.has_value()) return DeprecatedString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc] ({})", type, m_callee, m_this_value, executable.get_string(m_expression_string.value())); - return DeprecatedString::formatted("CallWithArgumentArray{} callee:{}, this:{}, arguments:[...acc]", type, m_callee, m_this_value); } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index ff4cf1cce5..ad070e6a8c 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -776,6 +776,38 @@ enum class CallType { DirectEval, }; +class Call final : public Instruction { +public: + Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional expression_string = {}) + : Instruction(Type::Call) + , m_callee(callee) + , m_this_value(this_value) + , m_first_argument(first_argument) + , m_argument_count(argument_count) + , m_type(type) + , m_expression_string(expression_string) + { + } + + CallType call_type() const { return m_type; } + Register callee() const { return m_callee; } + Register this_value() const { return m_this_value; } + Optional const& expression_string() const { return m_expression_string; } + + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; + DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; + void replace_references_impl(BasicBlock const&, BasicBlock const&) { } + void replace_references_impl(Register, Register); + +private: + Register m_callee; + Register m_this_value; + Register m_first_argument; + u32 m_argument_count { 0 }; + CallType m_type; + Optional m_expression_string; +}; + class CallWithArgumentArray final : public Instruction { public: CallWithArgumentArray(CallType type, Register callee, Register this_value, Optional expression_string = {}) @@ -787,13 +819,16 @@ public: { } + CallType call_type() const { return m_type; } + Register callee() const { return m_callee; } + Register this_value() const { return m_this_value; } + Optional const& expression_string() const { return m_expression_string; } + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; void replace_references_impl(BasicBlock const&, BasicBlock const&) { } void replace_references_impl(Register, Register); - Completion throw_type_error_for_callee(Bytecode::Interpreter&, StringView callee_type) const; - private: Register m_callee; Register m_this_value; diff --git a/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp b/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp index c91e943ce9..ad34b8ec77 100644 --- a/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Pass/LoadElimination.cpp @@ -124,6 +124,7 @@ static NonnullOwnPtr eliminate_loads(BasicBlock const& block, size_t // Attribute accesses (`a.o` or `a[o]`) may result in calls to getters or setters // or may trigger proxies // So these are treated like calls + case Call: case CallWithArgumentArray: // Calls, especially to local functions and eval, may poison visible and // cached variables, hence we need to clear the lookup cache after emitting them