diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index dea665a4b5..19a2435dc2 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -523,8 +523,6 @@ Bytecode::CodeGenerationErrorOr Identifier::generate_bytecode(Bytecode::Ge Bytecode::CodeGenerationErrorOr SuperCall::generate_bytecode(Bytecode::Generator& generator) const { - Vector argument_registers; - if (m_is_synthetic == IsPartOfSyntheticConstructor::Yes) { // NOTE: This is the case where we have a fake constructor(...args) { super(...args); } which // shouldn't call @@iterator of %Array.prototype%. @@ -534,17 +532,24 @@ Bytecode::CodeGenerationErrorOr SuperCall::generate_bytecode(Bytecode::Gen // This generates a single argument, which will be implicitly passed in accumulator MUST(argument.value->generate_bytecode(generator)); } else { + Vector argument_registers; argument_registers.ensure_capacity(m_arguments.size()); - for (auto const& arg : m_arguments) { - TRY(arg.value->generate_bytecode(generator)); + for (size_t i = 0; i < m_arguments.size(); ++i) { auto arg_reg = generator.allocate_register(); - generator.emit(arg_reg); argument_registers.unchecked_append(arg_reg); } + for (size_t i = 0; i < m_arguments.size(); ++i) { + TRY(m_arguments[i].value->generate_bytecode(generator)); + generator.emit(argument_registers[i]); + } + if (!argument_registers.is_empty()) + generator.emit_with_extra_register_slots(2, AK::Array { argument_registers.first(), argument_registers.last() }); + else + generator.emit(); } - generator.emit_with_extra_register_slots(argument_registers.size(), m_is_synthetic == IsPartOfSyntheticConstructor::Yes, argument_registers); + generator.emit(m_is_synthetic == IsPartOfSyntheticConstructor::Yes); return {}; } @@ -1489,13 +1494,16 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode generator.emit(callee_reg); } + // FIXME: We only need to record the first and last register, due to packing everything in an array Vector argument_registers; - for (auto& arg : m_arguments) { - TRY(arg.value->generate_bytecode(generator)); + for (size_t i = 0; i < m_arguments.size(); ++i) { auto arg_reg = generator.allocate_register(); - generator.emit(arg_reg); argument_registers.append(arg_reg); } + for (size_t i = 0; i < m_arguments.size(); ++i) { + TRY(m_arguments[i].value->generate_bytecode(generator)); + generator.emit(argument_registers[i]); + } Bytecode::Op::Call::CallType call_type; if (is(*this)) { @@ -1504,7 +1512,11 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode call_type = Bytecode::Op::Call::CallType::Call; } - generator.emit_with_extra_register_slots(argument_registers.size(), call_type, callee_reg, this_reg, argument_registers); + if (!argument_registers.is_empty()) + generator.emit_with_extra_register_slots(2, AK::Array { argument_registers.first(), argument_registers.last() }); + else + generator.emit(); + generator.emit(call_type, callee_reg, this_reg); return {}; } @@ -1676,6 +1688,8 @@ Bytecode::CodeGenerationErrorOr TaggedTemplateLiteral::generate_bytecode(B auto tag_reg = generator.allocate_register(); generator.emit(tag_reg); + // FIXME: We only need to record the first and last register, + // due to packing everything in an array, same goes for argument_regs Vector string_regs; auto& expressions = m_template_literal->expressions(); for (size_t i = 0; i < expressions.size(); ++i) { @@ -1704,14 +1718,13 @@ Bytecode::CodeGenerationErrorOr TaggedTemplateLiteral::generate_bytecode(B Vector argument_regs; argument_regs.append(strings_reg); - for (size_t i = 0; i < expressions.size(); ++i) { - if (i % 2 == 0) - continue; + for (size_t i = 1; i < expressions.size(); i += 2) + argument_regs.append(generator.allocate_register()); + for (size_t i = 1; i < expressions.size(); i += 2) { + auto string_reg = argument_regs[1 + i / 2]; TRY(expressions[i].generate_bytecode(generator)); - auto string_reg = generator.allocate_register(); generator.emit(string_reg); - argument_regs.append(string_reg); } Vector raw_string_regs; @@ -1741,7 +1754,12 @@ Bytecode::CodeGenerationErrorOr TaggedTemplateLiteral::generate_bytecode(B auto this_reg = generator.allocate_register(); generator.emit(this_reg); - generator.emit_with_extra_register_slots(argument_regs.size(), Bytecode::Op::Call::CallType::Call, tag_reg, this_reg, move(argument_regs)); + if (!argument_regs.is_empty()) + generator.emit_with_extra_register_slots(2, AK::Array { argument_regs.first(), argument_regs.last() }); + else + generator.emit(); + + generator.emit(Bytecode::Op::Call::CallType::Call, tag_reg, this_reg); return {}; } diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index a9f41f994b..54fa8d31ab 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -496,6 +496,33 @@ ThrowCompletionOr JumpUndefined::execute_impl(Bytecode::Interpreter& inter return {}; } +// 13.3.8.1 https://tc39.es/ecma262/#sec-runtime-semantics-argumentlistevaluation +static MarkedVector argument_list_evaluation(Bytecode::Interpreter& interpreter) +{ + // Note: Any spreading and actual evaluation is handled in preceding opcodes + // Note: The spec uses the concept of a list, while we create a temporary array + // in the preceding opcodes, so we have to convert in a manner that is not + // visible to the user + auto& vm = interpreter.vm(); + + MarkedVector argument_values { vm.heap() }; + auto arguments = interpreter.accumulator(); + + auto& argument_array = arguments.as_array(); + auto array_length = argument_array.indexed_properties().array_like_size(); + + argument_values.ensure_capacity(array_length); + + for (size_t i = 0; i < array_length; ++i) { + if (auto maybe_value = argument_array.indexed_properties().get(i); maybe_value.has_value()) + argument_values.append(maybe_value.release_value().value); + else + argument_values.append(js_undefined()); + } + + return argument_values; +} + ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) const { auto& vm = interpreter.vm(); @@ -512,9 +539,7 @@ ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) c auto this_value = interpreter.reg(m_this_value); - MarkedVector argument_values { vm.heap() }; - for (size_t i = 0; i < m_argument_count; ++i) - argument_values.append(interpreter.reg(m_arguments[i])); + auto argument_values = argument_list_evaluation(interpreter); Value return_value; if (m_type == CallType::Call) @@ -549,8 +574,7 @@ ThrowCompletionOr SuperCall::execute_impl(Bytecode::Interpreter& interpret for (size_t i = 0; i < length; ++i) arg_list.append(array_value.get_without_side_effects(PropertyKey { i })); } else { - for (size_t i = 0; i < m_argument_count; ++i) - arg_list.append(interpreter.reg(m_arguments[i])); + arg_list = argument_list_evaluation(interpreter); } // 5. If IsConstructor(func) is false, throw a TypeError exception. @@ -1045,28 +1069,12 @@ String JumpUndefined::to_string_impl(Bytecode::Executable const&) const String Call::to_string_impl(Bytecode::Executable const&) const { - StringBuilder builder; - builder.appendff("Call callee:{}, this:{}", m_callee, m_this_value); - if (m_argument_count != 0) { - builder.append(", arguments:["sv); - builder.join(", "sv, Span(m_arguments, m_argument_count)); - builder.append(']'); - } - return builder.to_string(); + return String::formatted("Call callee:{}, this:{}, arguments:[...acc]", m_callee, m_this_value); } String SuperCall::to_string_impl(Bytecode::Executable const&) const { - StringBuilder builder; - builder.append("SuperCall"sv); - if (m_is_synthetic) { - builder.append(" arguments:[...acc]"sv); - } else if (m_argument_count != 0) { - builder.append(" arguments:["sv); - builder.join(", "sv, Span(m_arguments, m_argument_count)); - builder.append(']'); - } - return builder.to_string(); + return "SuperCall arguments:[...acc]"sv; } String NewFunction::to_string_impl(Bytecode::Executable const&) const diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 3be84e0465..cdecccd4bf 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -585,59 +585,39 @@ public: Construct, }; - Call(CallType type, Register callee, Register this_value, Vector const& arguments) + Call(CallType type, Register callee, Register this_value) : Instruction(Type::Call) , m_callee(callee) , m_this_value(this_value) , m_type(type) - , m_argument_count(arguments.size()) { - for (size_t i = 0; i < m_argument_count; ++i) - m_arguments[i] = arguments[i]; } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; String to_string_impl(Bytecode::Executable const&) const; void replace_references_impl(BasicBlock const&, BasicBlock const&) { } - size_t length_impl() const - { - return sizeof(*this) + sizeof(Register) * m_argument_count; - } - private: Register m_callee; Register m_this_value; CallType m_type; - size_t m_argument_count { 0 }; - Register m_arguments[]; }; // NOTE: This instruction is variable-width depending on the number of arguments! class SuperCall : public Instruction { public: - explicit SuperCall(bool is_synthetic, Vector const& arguments) + explicit SuperCall(bool is_synthetic) : Instruction(Type::SuperCall) , m_is_synthetic(is_synthetic) - , m_argument_count(arguments.size()) { - for (size_t i = 0; i < m_argument_count; ++i) - m_arguments[i] = arguments[i]; } ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; String to_string_impl(Bytecode::Executable const&) const; void replace_references_impl(BasicBlock const&, BasicBlock const&) { } - size_t length_impl() const - { - return sizeof(*this) + sizeof(Register) * m_argument_count; - } - private: bool m_is_synthetic; - size_t m_argument_count { 0 }; - Register m_arguments[]; }; class NewClass final : public Instruction { @@ -991,10 +971,6 @@ ALWAYS_INLINE void Instruction::replace_references(BasicBlock const& from, Basic ALWAYS_INLINE size_t Instruction::length() const { - if (type() == Type::Call) - return static_cast(*this).length_impl(); - if (type() == Type::SuperCall) - return static_cast(*this).length_impl(); if (type() == Type::NewArray) return static_cast(*this).length_impl(); if (type() == Type::CopyObjectExcludingProperties)