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:
parent
984d3033e9
commit
e5bcffc3d5
8 changed files with 47 additions and 188 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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("")
|
||||
|
|
|
|||
Loading…
Reference in a new issue