Files
ladybird/Libraries/LibJS/Bytecode/Executable.cpp
Andreas Kling 9f822345bf LibJS: Flatten Operand to 32-bit index in bytecode instruction stream
While we're in the bytecode compiler, we want to know which type of
Operand we're dealing with, but once we've generated the bytecode
stream, we only ever need its index.

This patch simplifies Operand by removing the aarch64 bitfield hacks
and makes it 32-bit on all platforms. We keep 3 type bits in the high
bits of the index while compiling, and then zero them out when
flattening the final bytecode stream.

This makes bytecode more compact on x86_64, and avoids bit twiddling
on aarch64. Everyone wins something!

When stringifying bytecode for debugging output, we now have an API in
Executable that can look at a raw operand index and tell you what type
of operand it was, based on known quantities of each type in the stack
frame.
2025-12-09 21:44:13 -06:00

130 lines
4.0 KiB
C++

/*
* Copyright (c) 2021-2025, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Bytecode/BasicBlock.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/Instruction.h>
#include <LibJS/Bytecode/RegexTable.h>
#include <LibJS/Runtime/Value.h>
#include <LibJS/SourceCode.h>
namespace JS::Bytecode {
GC_DEFINE_ALLOCATOR(Executable);
Executable::Executable(
Vector<u8> bytecode,
NonnullOwnPtr<IdentifierTable> identifier_table,
NonnullOwnPtr<StringTable> string_table,
NonnullOwnPtr<RegexTable> regex_table,
Vector<Value> constants,
NonnullRefPtr<SourceCode const> source_code,
size_t number_of_property_lookup_caches,
size_t number_of_global_variable_caches,
size_t number_of_registers,
Strict strict)
: bytecode(move(bytecode))
, string_table(move(string_table))
, identifier_table(move(identifier_table))
, regex_table(move(regex_table))
, constants(move(constants))
, source_code(move(source_code))
, number_of_registers(number_of_registers)
, is_strict_mode(strict == Strict::Yes)
{
property_lookup_caches.resize(number_of_property_lookup_caches);
global_variable_caches.resize(number_of_global_variable_caches);
}
Executable::~Executable() = default;
void Executable::dump() const
{
warnln("\033[37;1mJS bytecode executable\033[0m \"{}\"", name);
InstructionStreamIterator it(bytecode, this);
size_t basic_block_offset_index = 0;
while (!it.at_end()) {
bool print_basic_block_marker = false;
if (basic_block_offset_index < basic_block_start_offsets.size()
&& it.offset() == basic_block_start_offsets[basic_block_offset_index]) {
++basic_block_offset_index;
print_basic_block_marker = true;
}
StringBuilder builder;
builder.appendff("[{:4x}] ", it.offset());
if (print_basic_block_marker)
builder.appendff("{:4}: ", basic_block_offset_index - 1);
else
builder.append(" "sv);
builder.append((*it).to_byte_string(*this));
warnln("{}", builder.string_view());
++it;
}
if (!exception_handlers.is_empty()) {
warnln("");
warnln("Exception handlers:");
for (auto& handlers : exception_handlers) {
warnln(" from {:4x} to {:4x} handler {:4x} finalizer {:4x}",
handlers.start_offset,
handlers.end_offset,
handlers.handler_offset.value_or(0),
handlers.finalizer_offset.value_or(0));
}
}
warnln("");
}
void Executable::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(constants);
}
Optional<Executable::ExceptionHandlers const&> Executable::exception_handlers_for_offset(size_t offset) const
{
for (auto& handlers : exception_handlers) {
if (handlers.start_offset <= offset && offset < handlers.end_offset)
return handlers;
}
return {};
}
UnrealizedSourceRange Executable::source_range_at(size_t offset) const
{
if (offset >= bytecode.size())
return {};
auto it = InstructionStreamIterator(bytecode.span().slice(offset), this);
VERIFY(!it.at_end());
auto mapping = source_map.get(offset);
if (!mapping.has_value())
return {};
return UnrealizedSourceRange {
.source_code = source_code,
.start_offset = mapping->source_start_offset,
.end_offset = mapping->source_end_offset,
};
}
Operand Executable::original_operand_from_raw(u32 raw) const
{
if (raw < number_of_registers)
return Operand { Operand::Type::Register, raw };
if (raw < local_index_base)
return Operand { Operand::Type::Constant, raw - static_cast<u32>(number_of_registers) };
if (raw < argument_index_base)
return Operand { Operand::Type::Local, raw - static_cast<u32>(local_index_base) };
return Operand { Operand::Type::Argument, raw - static_cast<u32>(argument_index_base) };
}
}