LibJS: Move bytecode block counting to Rust

Use the Rust bytecode dumper's basic block collection logic for the
metadata block count. This removes the last C++ bytecode label walk and
lets us delete the generated C++ label and operand visitor helpers.
This commit is contained in:
Andreas Kling 2026-06-14 20:51:57 +02:00 committed by Andreas Kling
parent 984d3033e9
commit e5bcffc3d5
8 changed files with 47 additions and 188 deletions

View file

@ -7,7 +7,6 @@
#include <AK/BinarySearch.h>
#include <AK/NeverDestroyed.h>
#include <AK/NumericLimits.h>
#include <AK/QuickSort.h>
#include <AK/StdLibExtras.h>
#include <LibGC/Heap.h>
#include <LibGC/HeapBlock.h>
@ -373,71 +372,6 @@ static void dump_header(StringBuilder& output, Executable const& executable)
output.append('\n');
}
static bool instruction_is_terminator(Instruction const& instruction)
{
#define __BYTECODE_OP(op) \
case Instruction::Type::op: \
return Op::op::IsTerminator;
switch (instruction.type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
static Vector<u32> collect_basic_block_start_offsets(Executable const& executable)
{
Vector<u32> offsets;
auto append_offset = [&](size_t offset) {
VERIFY(offset <= NumericLimits<u32>::max());
auto offset32 = static_cast<u32>(offset);
if (!offsets.contains_slow(offset32))
offsets.append(offset32);
};
auto append_instruction_offset = [&](size_t offset) {
if (offset < executable.bytecode.size())
append_offset(offset);
};
append_offset(0);
for (InstructionStreamIterator it(executable.bytecode, &executable); !it.at_end(); ++it) {
auto const& instruction = *it;
auto next_offset = it.offset() + instruction.length();
const_cast<Instruction&>(instruction).visit_labels([&](Label& label) {
append_offset(label.address());
});
if (instruction_is_terminator(instruction) && next_offset < executable.bytecode.size())
append_offset(next_offset);
}
for (auto const& handler : executable.exception_handlers) {
append_instruction_offset(handler.start_offset);
append_instruction_offset(handler.end_offset);
append_instruction_offset(handler.handler_offset);
}
quick_sort(offsets);
return offsets;
}
Optional<size_t> Executable::basic_block_index_for_offset(size_t offset) const
{
VERIFY(offset <= NumericLimits<u32>::max());
auto basic_block_start_offsets = collect_basic_block_start_offsets(*this);
size_t index = 0;
if (binary_search(basic_block_start_offsets, static_cast<u32>(offset), &index))
return index;
return {};
}
static void dump_metadata(StringBuilder& output, Executable const& executable)
{
auto constexpr green = "\033[32m"sv;
@ -447,7 +381,7 @@ static void dump_metadata(StringBuilder& output, Executable const& executable)
auto constexpr reset = "\033[0m"sv;
output.appendff(" {}Registers{}: {}\n", green, reset, executable.number_of_registers);
output.appendff(" {}Blocks{}: {}\n", green, reset, collect_basic_block_start_offsets(executable).size());
output.appendff(" {}Blocks{}: {}\n", green, reset, RustIntegration::count_bytecode_basic_blocks(executable));
if (!executable.local_variable_names.is_empty()) {
output.appendff(" {}Locals{}: ", green, reset);

View file

@ -347,7 +347,6 @@ public:
return get_identifier(*index);
}
[[nodiscard]] COLD Optional<size_t> basic_block_index_for_offset(size_t offset) const;
void copy_runtime_caches_from(Executable const&);
[[nodiscard]] COLD Optional<ExceptionHandlers const&> exception_handlers_for_offset(size_t offset) const;

View file

@ -10,38 +10,6 @@
namespace JS::Bytecode {
void Instruction::visit_labels(Function<void(JS::Bytecode::Label&)> visitor)
{
#define __BYTECODE_OP(op) \
case Type::op: \
static_cast<Op::op&>(*this).visit_labels_impl(move(visitor)); \
return;
switch (type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
void Instruction::visit_operands(Function<void(JS::Bytecode::Operand&)> visitor)
{
#define __BYTECODE_OP(op) \
case Type::op: \
static_cast<Op::op&>(*this).visit_operands_impl(move(visitor)); \
return;
switch (type()) {
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
default:
VERIFY_NOT_REACHED();
}
#undef __BYTECODE_OP
}
template<typename Op>
concept HasVariableLength = Op::IsVariableLength;

View file

@ -7,7 +7,6 @@
#pragma once
#include <AK/Forward.h>
#include <AK/Function.h>
#include <LibJS/Bytecode/Executable.h>
#include <LibJS/Bytecode/OpCodes.h>
#include <LibJS/Forward.h>
@ -80,8 +79,6 @@ public:
Type type() const { return m_type; }
size_t length() const;
void visit_labels(Function<void(Label&)> visitor);
void visit_operands(Function<void(Operand&)> visitor);
Strict strict() const { return m_strict; }
void set_strict(Strict strict) { m_strict = strict; }
@ -92,9 +89,6 @@ protected:
{
}
void visit_labels_impl(Function<void(Label&)>) { }
void visit_operands_impl(Function<void(Operand&)>) { }
private:
Type m_type {};
Strict m_strict {};

View file

@ -408,6 +408,34 @@ fn collect_basic_block_start_offsets(bytecode: &[u8], exception_handlers: &[FFID
offsets
}
/// Count basic blocks in a validated bytecode instruction stream.
///
/// # Safety
/// `bytecode_ptr` must point to `bytecode_len` bytes of validated bytecode, or
/// be null when `bytecode_len` is zero. `exception_handlers` must point to
/// `exception_handler_count` valid entries, or be null when the count is zero.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_count_basic_blocks(
bytecode_ptr: *const u8,
bytecode_len: usize,
exception_handlers: *const FFIDumpExceptionHandler,
exception_handler_count: usize,
) -> usize {
abort_on_panic(|| unsafe {
let bytecode = if bytecode_len == 0 {
&[]
} else {
std::slice::from_raw_parts(bytecode_ptr, bytecode_len)
};
let exception_handlers = if exception_handler_count == 0 {
&[]
} else {
std::slice::from_raw_parts(exception_handlers, exception_handler_count)
};
collect_basic_block_start_offsets(bytecode, exception_handlers).len()
})
}
/// Dump a validated bytecode instruction stream through C++ formatting callbacks.
///
/// # Safety

View file

@ -190,18 +190,23 @@ static void bytecode_dump_append_value_fallback(void* ctx, uint64_t encoded)
namespace JS::RustIntegration {
void dump_bytecode(StringBuilder& output, Bytecode::Executable const& executable)
static Vector<FFI::FFIDumpExceptionHandler> make_ffi_exception_handlers(Bytecode::Executable const& executable)
{
Vector<FFI::FFIDumpExceptionHandler> exception_handlers;
exception_handlers.ensure_capacity(executable.exception_handlers.size());
for (auto const& handler : executable.exception_handlers) {
exception_handlers.append({
exception_handlers.unchecked_append({
.start_offset = handler.start_offset,
.end_offset = handler.end_offset,
.handler_offset = handler.handler_offset,
});
}
return exception_handlers;
}
void dump_bytecode(StringBuilder& output, Bytecode::Executable const& executable)
{
auto exception_handlers = make_ffi_exception_handlers(executable);
BytecodeDumpBuilder builder { output, executable };
FFI::FFIBytecodeDumpCallbacks callbacks {
.append = FFI::bytecode_dump_append,
@ -233,6 +238,16 @@ void dump_bytecode(StringBuilder& output, Bytecode::Executable const& executable
&callbacks);
}
size_t count_bytecode_basic_blocks(Bytecode::Executable const& executable)
{
auto exception_handlers = make_ffi_exception_handlers(executable);
return FFI::rust_count_basic_blocks(
executable.bytecode.data(),
executable.bytecode.size(),
exception_handlers.data(),
exception_handlers.size());
}
}
namespace JS::FFI {

View file

@ -193,6 +193,7 @@ Optional<Vector<GC::Root<SharedFunctionInstanceData>>> compile_builtin_file(
GC::Ptr<Bytecode::Executable> compile_function(VM& vm, SharedFunctionInstanceData& shared_data, bool builtin_abstract_operations_enabled);
JS_API void dump_bytecode(StringBuilder&, Bytecode::Executable const&);
JS_API size_t count_bytecode_basic_blocks(Bytecode::Executable const&);
JS_API void* clone_function_ast(void const*);
JS_API FFI::CompiledFunction* compile_function_off_thread(void* function_ast, size_t length_in_code_units, bool builtin_abstract_operations_enabled);

View file

@ -78,78 +78,6 @@ def generate_enum_macro(ops: List[OpDef]) -> str:
return "\n".join(lines)
def generate_visit_operands(op: OpDef) -> Optional[str]:
has_any_operand = any(is_operand_type(f.type) for f in op.fields)
if not has_any_operand:
return None
lines: List[str] = []
lines.append(" void visit_operands_impl(Function<void(Operand&)> visitor)")
lines.append(" {")
for f in op.fields:
t = f.type.strip()
if not is_operand_type(t):
continue
if not f.is_array:
if is_optional_operand_type(t):
lines.append(f" if ({f.name}.has_value())")
lines.append(f" visitor({f.name}.value());")
else:
lines.append(f" visitor({f.name});")
else:
count_name = get_count_field_name_or_die(op, f)
if is_optional_operand_type(t):
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
lines.append(f" if ({f.name}[i].has_value())")
lines.append(f" visitor({f.name}[i].value());")
lines.append(" }")
else:
lines.append(f" for (size_t i = 0; i < {count_name}; ++i)")
lines.append(f" visitor({f.name}[i]);")
lines.append(" }")
return "\n".join(lines)
def generate_visit_labels(op: OpDef) -> Optional[str]:
has_any_label = any(is_label_type(f.type) for f in op.fields)
if not has_any_label:
return None
lines: List[str] = []
lines.append(" void visit_labels_impl(Function<void(Label&)> visitor)")
lines.append(" {")
for f in op.fields:
t = f.type.strip()
if not is_label_type(t):
continue
if not f.is_array:
if is_optional_label_type(t):
lines.append(f" if ({f.name}.has_value())")
lines.append(f" visitor({f.name}.value());")
else:
lines.append(f" visitor({f.name});")
else:
count_name = get_count_field_name_or_die(op, f)
if is_optional_label_type(t):
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
lines.append(f" if ({f.name}[i].has_value())")
lines.append(f" visitor({f.name}[i].value());")
lines.append(" }")
else:
lines.append(f" for (size_t i = 0; i < {count_name}; ++i)")
lines.append(f" visitor({f.name}[i]);")
lines.append(" }")
return "\n".join(lines)
def generate_getters(op: OpDef) -> List[str]:
lines: List[str] = []
for f in op.fields:
@ -278,14 +206,6 @@ def generate_class(op: OpDef) -> str:
lines.append(" }")
lines.append("")
visit_operands = generate_visit_operands(op)
if visit_operands:
lines.append(visit_operands)
visit_labels = generate_visit_labels(op)
if visit_labels:
lines.append(visit_labels)
getters = generate_getters(op)
if getters:
lines.append("")