From 9a0a5a79f4a6ee5bfdd8ac5fad805ca959eeb253 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 20 Feb 2024 15:59:45 +0100 Subject: [PATCH] LibJS/Bytecode: Put arguments directly in the Call instruction Instead of having Call refer to a range of VM registers, it now has a trailing list of argument operands as part of the instruction. This means we no longer have to shuffle every argument value into a register before making a call, making bytecode smaller & faster. :^) --- .../Libraries/LibJS/Bytecode/ASTCodegen.cpp | 22 ++--- .../Libraries/LibJS/Bytecode/Interpreter.cpp | 84 +++++++++++-------- Userland/Libraries/LibJS/Bytecode/Op.h | 17 ++-- 3 files changed, 70 insertions(+), 53 deletions(-) diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 69d4f58bac..6781a0756c 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1627,19 +1627,19 @@ Bytecode::CodeGenerationErrorOr> CallExpression::gen auto arguments = TRY(arguments_to_array_for_call(generator, this->arguments())).value(); generator.emit(call_type, dst, callee, this_value, arguments, 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; + Vector argument_operands; for (auto const& argument : arguments()) { - auto value = TRY(argument.value->generate_bytecode(generator)).value(); - generator.emit(Bytecode::Operand(Bytecode::Register(first_argument_reg.value().index() + register_offset)), value); - register_offset += 1; + argument_operands.append(TRY(argument.value->generate_bytecode(generator)).value()); } - generator.emit(call_type, dst, callee, this_value, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin); + generator.emit_with_extra_operand_slots( + argument_operands.size(), + call_type, + dst, + callee, + this_value, + argument_operands, + expression_string_index, + builtin); } return dst; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index de2cdbeff3..e6b4e6ff5e 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -42,7 +42,8 @@ bool g_dump_bytecode = false; static ByteString format_operand(StringView name, Operand operand, Bytecode::Executable const& executable) { StringBuilder builder; - builder.appendff("\033[32m{}\033[0m:", name); + if (!name.is_empty()) + builder.appendff("\033[32m{}\033[0m:", name); switch (operand.type()) { case Operand::Type::Register: builder.appendff("\033[33mreg{}\033[0m", operand.index()); @@ -77,6 +78,20 @@ static ByteString format_operand(StringView name, Operand operand, Bytecode::Exe return builder.to_byte_string(); } +static ByteString format_operand_list(StringView name, ReadonlySpan operands, Bytecode::Executable const& executable) +{ + StringBuilder builder; + if (!name.is_empty()) + builder.appendff(", \033[32{}\033[0m:[", name); + for (size_t i = 0; i < operands.size(); ++i) { + if (i != 0) + builder.append(", "sv); + builder.appendff("{}", format_operand(""sv, operands[i], executable)); + } + builder.append("]"sv); + return builder.to_byte_string(); +} + NonnullOwnPtr CallFrame::create(size_t register_count) { size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count; @@ -1202,27 +1217,25 @@ ThrowCompletionOr Mov::execute_impl(Bytecode::Interpreter&) const __builtin_unreachable(); } -static ThrowCompletionOr dispatch_builtin_call(Bytecode::Interpreter& interpreter, Bytecode::Builtin builtin, Register first_argument) +static ThrowCompletionOr dispatch_builtin_call(Bytecode::Interpreter& interpreter, Bytecode::Builtin builtin, ReadonlySpan arguments) { switch (builtin) { case Builtin::MathAbs: - return TRY(MathObject::abs_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::abs_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathLog: - return TRY(MathObject::log_impl(interpreter.vm(), interpreter.reg(first_argument))); - case Builtin::MathPow: { - auto exponent = interpreter.reg(Register { first_argument.index() + 1 }); - return TRY(MathObject::pow_impl(interpreter.vm(), interpreter.reg(first_argument), exponent)); - } + return TRY(MathObject::log_impl(interpreter.vm(), interpreter.get(arguments[0]))); + case Builtin::MathPow: + return TRY(MathObject::pow_impl(interpreter.vm(), interpreter.get(arguments[0]), interpreter.get(arguments[1]))); case Builtin::MathExp: - return TRY(MathObject::exp_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::exp_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathCeil: - return TRY(MathObject::ceil_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::ceil_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathFloor: - return TRY(MathObject::floor_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::floor_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathRound: - return TRY(MathObject::round_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::round_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Builtin::MathSqrt: - return TRY(MathObject::sqrt_impl(interpreter.vm(), interpreter.reg(first_argument))); + return TRY(MathObject::sqrt_impl(interpreter.vm(), interpreter.get(arguments[0]))); case Bytecode::Builtin::__Count: VERIFY_NOT_REACHED(); } @@ -1236,11 +1249,15 @@ ThrowCompletionOr Call::execute_impl(Bytecode::Interpreter& interpreter) c TRY(throw_if_needed_for_call(interpreter, callee, call_type(), expression_string())); if (m_builtin.has_value() && m_argument_count == Bytecode::builtin_argument_count(m_builtin.value()) && interpreter.realm().get_builtin_value(m_builtin.value()) == callee) { - interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin.value(), m_first_argument))); + interpreter.set(dst(), TRY(dispatch_builtin_call(interpreter, m_builtin.value(), { m_arguments, m_argument_count }))); return {}; } - interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, interpreter.registers().slice(m_first_argument.index(), m_argument_count)))); + Vector argument_values; + argument_values.ensure_capacity(m_argument_count); + for (size_t i = 0; i < m_argument_count; ++i) + argument_values.unchecked_append(interpreter.get(m_arguments[i])); + interpreter.set(dst(), TRY(perform_call(interpreter, interpreter.get(m_this_value), call_type(), callee, argument_values))); return {}; } @@ -1893,30 +1910,25 @@ static StringView call_type_to_string(CallType type) ByteString Call::to_byte_string_impl(Bytecode::Executable const& executable) const { auto type = call_type_to_string(m_type); - if (m_builtin.has_value()) { - return ByteString::formatted("Call{} {}, {}, {}, first_arg:{} (builtin {})", - type, - format_operand("dst"sv, m_dst, executable), - format_operand("callee"sv, m_callee, executable), - format_operand("this"sv, m_this_value, executable), - m_first_argument, - m_builtin.value()); - } - if (m_expression_string.has_value()) { - return ByteString::formatted("Call{} {}, {}, {}, first_arg:{} ({})", - type, - format_operand("dst"sv, m_dst, executable), - format_operand("callee"sv, m_callee, executable), - format_operand("this"sv, m_this_value, executable), - m_first_argument, - executable.get_string(m_expression_string.value())); - } - return ByteString::formatted("Call{} {}, {}, {}, first_arg:{}", + + StringBuilder builder; + builder.appendff("Call{} {}, {}, {}"sv, type, format_operand("dst"sv, m_dst, executable), format_operand("callee"sv, m_callee, executable), - format_operand("this"sv, m_this_value, executable), - m_first_argument); + format_operand("this"sv, m_this_value, executable)); + + builder.append(format_operand_list("args"sv, { m_arguments, m_argument_count }, executable)); + + if (m_builtin.has_value()) { + builder.appendff(", (builtin:{})", m_builtin.value()); + } + + if (m_expression_string.has_value()) { + builder.appendff(", `{}`", executable.get_string(m_expression_string.value())); + } + + return builder.to_byte_string(); } ByteString CallWithArgumentArray::to_byte_string_impl(Bytecode::Executable const& executable) const diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 89ceaaa474..9a1fc909e5 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -1152,17 +1152,23 @@ enum class CallType { class Call final : public Instruction { public: - Call(CallType type, Operand dst, Operand callee, Operand this_value, Register first_argument, u32 argument_count, Optional expression_string = {}, Optional builtin = {}) - : Instruction(Type::Call, sizeof(*this)) + Call(CallType type, Operand dst, Operand callee, Operand this_value, ReadonlySpan arguments, Optional expression_string = {}, Optional builtin = {}) + : Instruction(Type::Call, length_impl(arguments.size())) , m_dst(dst) , m_callee(callee) , m_this_value(this_value) - , m_first_argument(first_argument) - , m_argument_count(argument_count) + , m_argument_count(arguments.size()) , m_type(type) , m_expression_string(expression_string) , m_builtin(builtin) { + for (size_t i = 0; i < arguments.size(); ++i) + m_arguments[i] = arguments[i]; + } + + size_t length_impl(size_t argument_count) const + { + return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * argument_count); } CallType call_type() const { return m_type; } @@ -1171,7 +1177,6 @@ public: Operand this_value() const { return m_this_value; } Optional const& expression_string() const { return m_expression_string; } - Register first_argument() const { return m_first_argument; } u32 argument_count() const { return m_argument_count; } Optional const& builtin() const { return m_builtin; } @@ -1183,11 +1188,11 @@ private: Operand m_dst; Operand m_callee; Operand m_this_value; - Register m_first_argument; u32 m_argument_count { 0 }; CallType m_type; Optional m_expression_string; Optional m_builtin; + Operand m_arguments[]; }; class CallWithArgumentArray final : public Instruction {