mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibJS/Bytecode: Make NewArray a variable-length instruction
This removes a layer of indirection in the bytecode where we had to make sure all the initializer elements were laid out in sequential registers. Array expressions no longer clobber registers permanently, and they can be reused immediately afterwards.
This commit is contained in:
parent
cea59b6642
commit
6873628317
Notes:
sideshowbarker
2024-07-17 03:59:29 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/6873628317 Pull-request: https://github.com/SerenityOS/serenity/pull/24260 Reviewed-by: https://github.com/Hendiadyoin1 Reviewed-by: https://github.com/mattco98
5 changed files with 40 additions and 81 deletions
|
@ -350,24 +350,18 @@ static Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> arguments_to_arr
|
|||
|
||||
auto first_spread = find_if(arguments.begin(), arguments.end(), [](auto el) { return el.is_spread; });
|
||||
|
||||
Vector<ScopedOperand> registers;
|
||||
Bytecode::Register args_start_reg { 0 };
|
||||
Vector<ScopedOperand> args;
|
||||
args.ensure_capacity(first_spread.index());
|
||||
for (auto it = arguments.begin(); it != first_spread; ++it) {
|
||||
auto reg = generator.allocate_sequential_register();
|
||||
registers.append(reg);
|
||||
if (args_start_reg.index() == 0)
|
||||
args_start_reg = reg.operand().as_register();
|
||||
}
|
||||
u32 i = 0;
|
||||
for (auto it = arguments.begin(); it != first_spread; ++it, ++i) {
|
||||
VERIFY(it->is_spread == false);
|
||||
Bytecode::Register reg { args_start_reg.index() + i };
|
||||
VERIFY(!it->is_spread);
|
||||
auto reg = generator.allocate_register();
|
||||
auto value = TRY(it->value->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(reg), value);
|
||||
generator.emit<Bytecode::Op::Mov>(reg, value);
|
||||
args.append(move(reg));
|
||||
}
|
||||
|
||||
if (first_spread.index() != 0)
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, dst, AK::Array { Bytecode::Operand(args_start_reg), Bytecode::Operand { Bytecode::Register { args_start_reg.index() + static_cast<u32>(first_spread.index() - 1) } } });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(args.size(), dst, args);
|
||||
else
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
|
||||
|
@ -1037,27 +1031,22 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> ArrayExpression::genera
|
|||
|
||||
auto first_spread = find_if(m_elements.begin(), m_elements.end(), [](auto el) { return el && is<SpreadExpression>(*el); });
|
||||
|
||||
Vector<ScopedOperand> registers;
|
||||
Optional<Bytecode::Register> args_start_reg;
|
||||
Vector<ScopedOperand> args;
|
||||
args.ensure_capacity(m_elements.size());
|
||||
for (auto it = m_elements.begin(); it != first_spread; ++it) {
|
||||
auto reg = generator.allocate_sequential_register();
|
||||
registers.append(reg);
|
||||
if (!args_start_reg.has_value())
|
||||
args_start_reg = reg.operand().as_register();
|
||||
}
|
||||
u32 i = 0;
|
||||
for (auto it = m_elements.begin(); it != first_spread; ++it, ++i) {
|
||||
Bytecode::Register reg { args_start_reg->index() + i };
|
||||
if (*it) {
|
||||
auto reg = generator.allocate_register();
|
||||
auto value = TRY((*it)->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::Mov>(Bytecode::Operand(reg), value);
|
||||
args.append(move(reg));
|
||||
} else {
|
||||
args.append(generator.add_constant(Value()));
|
||||
}
|
||||
}
|
||||
|
||||
auto dst = choose_dst(generator, preferred_dst);
|
||||
if (first_spread.index() != 0) {
|
||||
auto reg = Bytecode::Register { args_start_reg->index() + static_cast<u32>(first_spread.index() - 1) };
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, dst, AK::Array { Bytecode::Operand(*args_start_reg), Bytecode::Operand(reg) });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(args.size(), dst, args);
|
||||
} else {
|
||||
generator.emit<Bytecode::Op::NewArray>(dst);
|
||||
}
|
||||
|
@ -1623,6 +1612,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
|
|||
generator.emit<Bytecode::Op::CallWithArgumentArray>(call_type, dst, callee, this_value, arguments, expression_string_index);
|
||||
} else {
|
||||
Vector<ScopedOperand> argument_operands;
|
||||
argument_operands.ensure_capacity(arguments().size());
|
||||
for (auto const& argument : arguments()) {
|
||||
auto argument_value = TRY(argument.value->generate_bytecode(generator)).value();
|
||||
auto temporary = generator.allocate_register();
|
||||
|
@ -1857,7 +1847,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
|
||||
// i. Let innerResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « received.[[Value]] »).
|
||||
auto array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, array, AK::Array { received_completion_value.operand(), received_completion_value.operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
auto inner_result = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, inner_result, next_method, iterator, array);
|
||||
|
||||
|
@ -1949,7 +1939,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
|
||||
// 1. Let innerResult be ? Call(throw, iterator, « received.[[Value]] »).
|
||||
auto received_value_array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, received_value_array, AK::Array { received_completion_value.operand(), received_completion_value.operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, received_value_array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, inner_result, throw_method, iterator, received_value_array);
|
||||
|
||||
// 2. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
|
@ -2046,7 +2036,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> YieldExpression::genera
|
|||
|
||||
// iv. Let innerReturnResult be ? Call(return, iterator, « received.[[Value]] »).
|
||||
auto call_array = generator.allocate_register();
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2, call_array, AK::Array { received_completion_value.operand(), received_completion_value.operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(1, call_array, ReadonlySpan<ScopedOperand> { &received_completion_value, 1 });
|
||||
auto inner_return_result = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, inner_return_result, return_method, iterator, call_array);
|
||||
|
||||
|
@ -2306,8 +2296,6 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
Bytecode::Generator::SourceLocationScope scope(generator, *this);
|
||||
auto tag = TRY(m_tag->generate_bytecode(generator)).value();
|
||||
|
||||
// FIXME: We only need to record the first and last register,
|
||||
// due to packing everything in an array, same goes for argument_regs
|
||||
// FIXME: Follow
|
||||
// 13.2.8.3 GetTemplateObject ( templateLiteral ), https://tc39.es/ecma262/#sec-gettemplateobject
|
||||
// more closely, namely:
|
||||
|
@ -2316,70 +2304,61 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> TaggedTemplateLiteral::
|
|||
// * freeze array and raw member
|
||||
Vector<ScopedOperand> string_regs;
|
||||
auto& expressions = m_template_literal->expressions();
|
||||
for (size_t i = 0; i < expressions.size(); ++i) {
|
||||
if (i % 2 != 0)
|
||||
continue;
|
||||
string_regs.append(generator.allocate_sequential_register());
|
||||
}
|
||||
|
||||
size_t reg_index = 0;
|
||||
for (size_t i = 0; i < expressions.size(); ++i) {
|
||||
if (i % 2 != 0)
|
||||
continue;
|
||||
// NOTE: If the string contains invalid escapes we get a null expression here,
|
||||
// which we then convert to the expected `undefined` TV. See
|
||||
// 12.9.6.1 Static Semantics: TV, https://tc39.es/ecma262/#sec-static-semantics-tv
|
||||
auto string_reg = string_regs[reg_index++];
|
||||
auto string_reg = generator.allocate_register();
|
||||
if (is<NullLiteral>(expressions[i])) {
|
||||
generator.emit<Bytecode::Op::Mov>(string_reg, generator.add_constant(js_undefined()));
|
||||
} else {
|
||||
auto value = TRY(expressions[i]->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::Mov>(string_reg, value);
|
||||
}
|
||||
string_regs.append(move(string_reg));
|
||||
}
|
||||
|
||||
auto strings_array = generator.allocate_sequential_register();
|
||||
auto strings_array = generator.allocate_register();
|
||||
if (string_regs.is_empty()) {
|
||||
generator.emit<Bytecode::Op::NewArray>(strings_array);
|
||||
} else {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, strings_array, AK::Array { string_regs.first().operand(), string_regs.last().operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(string_regs.size(), strings_array, string_regs);
|
||||
}
|
||||
|
||||
Vector<ScopedOperand> argument_regs;
|
||||
argument_regs.append(strings_array);
|
||||
for (size_t i = 1; i < expressions.size(); i += 2)
|
||||
argument_regs.append(generator.allocate_sequential_register());
|
||||
|
||||
for (size_t i = 1; i < expressions.size(); i += 2) {
|
||||
auto string_reg = argument_regs[1 + i / 2];
|
||||
auto string_reg = generator.allocate_register();
|
||||
auto string = TRY(expressions[i]->generate_bytecode(generator)).value();
|
||||
generator.emit<Bytecode::Op::Mov>(string_reg, string);
|
||||
argument_regs.append(move(string_reg));
|
||||
}
|
||||
|
||||
Vector<ScopedOperand> raw_string_regs;
|
||||
for ([[maybe_unused]] auto& raw_string : m_template_literal->raw_strings())
|
||||
string_regs.append(generator.allocate_sequential_register());
|
||||
|
||||
reg_index = 0;
|
||||
raw_string_regs.ensure_capacity(m_template_literal->raw_strings().size());
|
||||
for (auto& raw_string : m_template_literal->raw_strings()) {
|
||||
auto value = TRY(raw_string->generate_bytecode(generator)).value();
|
||||
auto raw_string_reg = string_regs[reg_index++];
|
||||
auto raw_string_reg = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::Mov>(raw_string_reg, value);
|
||||
raw_string_regs.append(raw_string_reg);
|
||||
raw_string_regs.append(move(raw_string_reg));
|
||||
}
|
||||
|
||||
auto raw_strings_array = generator.allocate_register();
|
||||
if (raw_string_regs.is_empty()) {
|
||||
generator.emit<Bytecode::Op::NewArray>(raw_strings_array);
|
||||
} else {
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2u, raw_strings_array, AK::Array { raw_string_regs.first().operand(), raw_string_regs.last().operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(raw_string_regs.size(), raw_strings_array, raw_string_regs);
|
||||
}
|
||||
|
||||
generator.emit<Bytecode::Op::PutById>(strings_array, generator.intern_identifier("raw"), raw_strings_array, Bytecode::Op::PropertyKind::KeyValue, generator.next_property_lookup_cache());
|
||||
|
||||
auto arguments = generator.allocate_register();
|
||||
if (!argument_regs.is_empty())
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(2, arguments, AK::Array { argument_regs.first().operand(), argument_regs.last().operand() });
|
||||
generator.emit_with_extra_operand_slots<Bytecode::Op::NewArray>(argument_regs.size(), arguments, argument_regs);
|
||||
else
|
||||
generator.emit<Bytecode::Op::NewArray>(arguments);
|
||||
|
||||
|
|
|
@ -220,18 +220,13 @@ void Generator::grow(size_t additional_size)
|
|||
m_current_basic_block->grow(additional_size);
|
||||
}
|
||||
|
||||
ScopedOperand Generator::allocate_sequential_register()
|
||||
{
|
||||
VERIFY(m_next_register != NumericLimits<u32>::max());
|
||||
return ScopedOperand { *this, Operand { Register { m_next_register++ } } };
|
||||
}
|
||||
|
||||
ScopedOperand Generator::allocate_register()
|
||||
{
|
||||
if (!m_free_registers.is_empty()) {
|
||||
return ScopedOperand { *this, Operand { m_free_registers.take_last() } };
|
||||
}
|
||||
return allocate_sequential_register();
|
||||
VERIFY(m_next_register != NumericLimits<u32>::max());
|
||||
return ScopedOperand { *this, Operand { Register { m_next_register++ } } };
|
||||
}
|
||||
|
||||
void Generator::free_register(Register reg)
|
||||
|
|
|
@ -34,8 +34,6 @@ public:
|
|||
};
|
||||
static CodeGenerationErrorOr<NonnullGCPtr<Executable>> generate(VM&, ASTNode const&, ReadonlySpan<FunctionParameter> parameters, FunctionKind = FunctionKind::Normal);
|
||||
|
||||
[[nodiscard]] ScopedOperand allocate_sequential_register();
|
||||
|
||||
[[nodiscard]] ScopedOperand allocate_register();
|
||||
[[nodiscard]] ScopedOperand local(u32 local_index);
|
||||
[[nodiscard]] ScopedOperand accumulator();
|
||||
|
|
|
@ -86,7 +86,7 @@ static ByteString format_operand_list(StringView name, ReadonlySpan<Operand> ope
|
|||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff(", \033[32m{}\033[0m:[", name);
|
||||
builder.appendff("\033[32m{}\033[0m:[", name);
|
||||
for (size_t i = 0; i < operands.size(); ++i) {
|
||||
if (i != 0)
|
||||
builder.append(", "sv);
|
||||
|
@ -1095,8 +1095,7 @@ ThrowCompletionOr<void> NewArray::execute_impl(Bytecode::Interpreter& interprete
|
|||
{
|
||||
auto array = MUST(Array::create(interpreter.realm(), 0));
|
||||
for (size_t i = 0; i < m_element_count; i++) {
|
||||
auto& value = interpreter.reg(Register(m_elements[0].index() + i));
|
||||
array->indexed_properties().put(i, value, default_attributes);
|
||||
array->indexed_properties().put(i, interpreter.get(m_elements[i]), default_attributes);
|
||||
}
|
||||
interpreter.set(dst(), array);
|
||||
return {};
|
||||
|
@ -1883,7 +1882,7 @@ ByteString NewArray::to_byte_string_impl(Bytecode::Executable const& executable)
|
|||
StringBuilder builder;
|
||||
builder.appendff("NewArray {}", format_operand("dst"sv, dst(), executable));
|
||||
if (m_element_count != 0) {
|
||||
builder.appendff(", [{}-{}]", format_operand("from"sv, m_elements[0], executable), format_operand("to"sv, m_elements[1], executable));
|
||||
builder.appendff(", {}", format_operand_list("args"sv, { m_elements, m_element_count }, executable));
|
||||
}
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
@ -2169,7 +2168,7 @@ ByteString Call::to_byte_string_impl(Bytecode::Executable const& executable) con
|
|||
auto type = call_type_to_string(m_type);
|
||||
|
||||
StringBuilder builder;
|
||||
builder.appendff("Call{} {}, {}, {}"sv,
|
||||
builder.appendff("Call{} {}, {}, {}, "sv,
|
||||
type,
|
||||
format_operand("dst"sv, m_dst, executable),
|
||||
format_operand("callee"sv, m_callee, executable),
|
||||
|
|
|
@ -254,13 +254,13 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
NewArray(Operand dst, AK::Array<Operand, 2> const& elements_range)
|
||||
NewArray(Operand dst, ReadonlySpan<ScopedOperand> elements)
|
||||
: Instruction(Type::NewArray)
|
||||
, m_dst(dst)
|
||||
, m_element_count(elements_range[1].index() - elements_range[0].index() + 1)
|
||||
, m_element_count(elements.size())
|
||||
{
|
||||
m_elements[0] = elements_range[0];
|
||||
m_elements[1] = elements_range[1];
|
||||
for (size_t i = 0; i < m_element_count; ++i)
|
||||
m_elements[i] = elements[i];
|
||||
}
|
||||
|
||||
ThrowCompletionOr<void> execute_impl(Bytecode::Interpreter&) const;
|
||||
|
@ -270,19 +270,7 @@ public:
|
|||
|
||||
size_t length_impl() const
|
||||
{
|
||||
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * (m_element_count == 0 ? 0 : 2));
|
||||
}
|
||||
|
||||
Operand start() const
|
||||
{
|
||||
VERIFY(m_element_count);
|
||||
return m_elements[0];
|
||||
}
|
||||
|
||||
Operand end() const
|
||||
{
|
||||
VERIFY(m_element_count);
|
||||
return m_elements[1];
|
||||
return round_up_to_power_of_two(alignof(void*), sizeof(*this) + sizeof(Operand) * m_element_count);
|
||||
}
|
||||
|
||||
size_t element_count() const { return m_element_count; }
|
||||
|
|
Loading…
Reference in a new issue