1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 23:07:35 +00:00

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. :^)
This commit is contained in:
Andreas Kling 2024-02-20 15:59:45 +01:00
parent da107ec9fb
commit 9a0a5a79f4
3 changed files with 70 additions and 53 deletions

View file

@ -1627,19 +1627,19 @@ Bytecode::CodeGenerationErrorOr<Optional<Bytecode::Operand>> CallExpression::gen
auto arguments = TRY(arguments_to_array_for_call(generator, this->arguments())).value();
generator.emit<Bytecode::Op::CallWithArgumentArray>(call_type, dst, callee, this_value, arguments, expression_string_index);
} else {
Optional<Bytecode::Register> 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<Bytecode::Operand> argument_operands;
for (auto const& argument : arguments()) {
auto value = TRY(argument.value->generate_bytecode(generator)).value();
generator.emit<Bytecode::Op::Mov>(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<Bytecode::Op::Call>(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<Bytecode::Op::Call>(
argument_operands.size(),
call_type,
dst,
callee,
this_value,
argument_operands,
expression_string_index,
builtin);
}
return dst;

View file

@ -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<Operand> 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> CallFrame::create(size_t register_count)
{
size_t allocation_size = sizeof(CallFrame) + sizeof(Value) * register_count;
@ -1202,27 +1217,25 @@ ThrowCompletionOr<void> Mov::execute_impl(Bytecode::Interpreter&) const
__builtin_unreachable();
}
static ThrowCompletionOr<Value> dispatch_builtin_call(Bytecode::Interpreter& interpreter, Bytecode::Builtin builtin, Register first_argument)
static ThrowCompletionOr<Value> dispatch_builtin_call(Bytecode::Interpreter& interpreter, Bytecode::Builtin builtin, ReadonlySpan<Operand> 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<void> 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<Value> 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

View file

@ -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<StringTableIndex> expression_string = {}, Optional<Builtin> builtin = {})
: Instruction(Type::Call, sizeof(*this))
Call(CallType type, Operand dst, Operand callee, Operand this_value, ReadonlySpan<Operand> arguments, Optional<StringTableIndex> expression_string = {}, Optional<Builtin> 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<StringTableIndex> 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<Builtin> 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<StringTableIndex> m_expression_string;
Optional<Builtin> m_builtin;
Operand m_arguments[];
};
class CallWithArgumentArray final : public Instruction {