LibJS: Move bytecode instruction dumping to Rust
Generate Rust bytecode dump helpers from Bytecode.def and route Executable::dump() through them for instruction stream formatting. Add a small Rust runtime::value helper for decoding encoded LibJS Values so immediate Value operands are formatted on the Rust side. C++ callbacks remain only for local names and GC-backed Value payloads that still need LibJS object access. Remove the generated C++ to_byte_string_impl() methods and the old Instruction::to_byte_string() dispatch. The bytecode dump tests cover output compatibility.
This commit is contained in:
parent
5aac297558
commit
7a6af95db3
15 changed files with 1098 additions and 326 deletions
|
|
@ -20,6 +20,7 @@
|
|||
#include <LibJS/Runtime/ExternalMemory.h>
|
||||
#include <LibJS/Runtime/SharedFunctionInstanceData.h>
|
||||
#include <LibJS/Runtime/Value.h>
|
||||
#include <LibJS/RustIntegration.h>
|
||||
#include <LibJS/SourceCode.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
|
@ -490,31 +491,6 @@ static void dump_metadata(StringBuilder& output, Executable const& executable)
|
|||
}
|
||||
}
|
||||
|
||||
static void dump_bytecode(StringBuilder& output, Executable const& executable)
|
||||
{
|
||||
auto constexpr magenta = "\033[35;1m"sv;
|
||||
auto constexpr reset = "\033[0m"sv;
|
||||
|
||||
InstructionStreamIterator it(executable.bytecode, &executable);
|
||||
auto basic_block_start_offsets = collect_basic_block_start_offsets(executable);
|
||||
|
||||
size_t basic_block_offset_index = 0;
|
||||
|
||||
while (!it.at_end()) {
|
||||
if (basic_block_offset_index < basic_block_start_offsets.size()
|
||||
&& it.offset() == basic_block_start_offsets[basic_block_offset_index]) {
|
||||
if (basic_block_offset_index > 0)
|
||||
output.append('\n');
|
||||
output.appendff("{}block{}{}:\n", magenta, basic_block_offset_index, reset);
|
||||
++basic_block_offset_index;
|
||||
}
|
||||
|
||||
output.appendff(" [{:4x}] {}\n", it.offset(), (*it).to_byte_string(executable));
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void Executable::dump() const
|
||||
{
|
||||
StringBuilder output;
|
||||
|
|
@ -522,7 +498,7 @@ void Executable::dump() const
|
|||
dump_header(output, *this);
|
||||
dump_metadata(output, *this);
|
||||
output.append('\n');
|
||||
dump_bytecode(output, *this);
|
||||
RustIntegration::dump_bytecode(output, *this);
|
||||
|
||||
if (!exception_handlers.is_empty()) {
|
||||
output.append("\nException handlers:\n"sv);
|
||||
|
|
|
|||
|
|
@ -81,28 +81,4 @@ inline ByteString format_operand(StringView name, Operand encoded_operand, Bytec
|
|||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
inline ByteString format_operand_list(StringView name, ReadonlySpan<Operand> operands, Bytecode::Executable const& executable)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\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();
|
||||
}
|
||||
|
||||
inline ByteString format_value_list(StringView name, ReadonlySpan<Value> values)
|
||||
{
|
||||
StringBuilder builder;
|
||||
if (!name.is_empty())
|
||||
builder.appendff("\033[32m{}\033[0m:[", name);
|
||||
builder.join(", "sv, values);
|
||||
builder.append("]"sv);
|
||||
return builder.to_byte_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ public:
|
|||
|
||||
Type type() const { return m_type; }
|
||||
size_t length() const;
|
||||
ByteString to_byte_string(Bytecode::Executable const&) const;
|
||||
void visit_labels(Function<void(Label&)> visitor);
|
||||
void visit_operands(Function<void(Operand&)> visitor);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibJS/Bytecode/Debug.h>
|
||||
#include <LibJS/Bytecode/FormatOperand.h>
|
||||
#include <LibJS/Bytecode/Instruction.h>
|
||||
#include <LibJS/Bytecode/Label.h>
|
||||
#include <LibJS/Bytecode/Op.h>
|
||||
|
|
@ -313,19 +312,4 @@ ThrowCompletionOr<Value> VM::run_executable(ExecutionContext& context, Executabl
|
|||
return reg(Register::return_value());
|
||||
}
|
||||
|
||||
ByteString Instruction::to_byte_string(Bytecode::Executable const& executable) const
|
||||
{
|
||||
#define __BYTECODE_OP(op) \
|
||||
case Instruction::Type::op: \
|
||||
return static_cast<Bytecode::Op::op const&>(*this).to_byte_string_impl(executable);
|
||||
|
||||
switch (type()) {
|
||||
ENUMERATE_BYTECODE_OPS(__BYTECODE_OP)
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
#undef __BYTECODE_OP
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ public:
|
|||
|
||||
explicit Operand(Register);
|
||||
|
||||
static Operand from_raw(u32 raw)
|
||||
{
|
||||
Operand operand;
|
||||
operand.m_raw = raw;
|
||||
return operand;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_invalid() const { return m_raw == 0xffffffffu; }
|
||||
[[nodiscard]] bool is_register() const { return type() == Type::Register; }
|
||||
[[nodiscard]] bool is_local() const { return type() == Type::Local; }
|
||||
|
|
@ -54,6 +61,8 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
Operand() = default;
|
||||
|
||||
u32 m_raw { 0 };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -68,11 +68,31 @@ fn generate_rust_code(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn st
|
|||
generate_instruction_enum(&mut w, ops)?;
|
||||
generate_instruction_impl(&mut w, ops)?;
|
||||
generate_instruction_length_from_bytes(&mut w, ops)?;
|
||||
generate_instruction_dump_from_bytes(&mut w, ops)?;
|
||||
generate_visit_labels_from_bytes(&mut w, ops)?;
|
||||
generate_instruction_is_terminator_from_opcode(&mut w, ops)?;
|
||||
generate_validate_instruction(&mut w, ops)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_instruction_is_terminator_from_opcode(
|
||||
mut w: impl Write,
|
||||
ops: &[OpDef],
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
writeln!(w, "pub fn instruction_is_terminator_from_opcode(opcode: u8) -> bool {{")?;
|
||||
let terminators = ops
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, op)| op.is_terminator)
|
||||
.map(|(i, _)| i.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
writeln!(w, " matches!(opcode, {})", terminators.join(" | "))?;
|
||||
writeln!(w, "}}")?;
|
||||
writeln!(w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_num_opcodes_const(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
writeln!(w, "/// Number of distinct opcodes (the valid range for the type byte).")?;
|
||||
writeln!(w, "pub const NUM_OPCODES: u32 = {};", ops.len())?;
|
||||
|
|
@ -153,6 +173,351 @@ fn generate_instruction_length_from_bytes(mut w: impl Write, ops: &[OpDef]) -> R
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn read_expr_for_type(ty: &str, offset: usize) -> String {
|
||||
match ty {
|
||||
"bool" => format!("bytes[at + {offset}] != 0"),
|
||||
"u32"
|
||||
| "Completion::Type"
|
||||
| "IteratorHint"
|
||||
| "EnvironmentMode"
|
||||
| "PutKind"
|
||||
| "ArgumentsKind"
|
||||
| "FunctionNamePrefix"
|
||||
| "PropertyLookupCacheIndex"
|
||||
| "GlobalVariableCacheIndex"
|
||||
| "EnvironmentCoordinateCacheIndex"
|
||||
| "TemplateObjectCacheIndex"
|
||||
| "ObjectShapeCacheIndex"
|
||||
| "ObjectPropertyIteratorCacheIndex" => {
|
||||
format!("super::validator::read_u32(bytes, at + {offset})")
|
||||
}
|
||||
"u64" | "Value" => format!("super::validator::read_u64(bytes, at + {offset})"),
|
||||
"Operand" => format!("Operand::from_raw(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"Optional<Operand>" => format!("Operand::optional_from_raw(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"Label" => format!("Label(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"Optional<Label>" => {
|
||||
format!(
|
||||
"if bytes[at + {offset} + 4] != 0 {{ Some(Label(super::validator::read_u32(bytes, at + {offset}))) }} else {{ None }}"
|
||||
)
|
||||
}
|
||||
"IdentifierTableIndex" => format!("IdentifierTableIndex(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"Optional<IdentifierTableIndex>" => {
|
||||
format!("IdentifierTableIndex::optional_from_raw(super::validator::read_u32(bytes, at + {offset}))")
|
||||
}
|
||||
"PropertyKeyTableIndex" => format!("PropertyKeyTableIndex(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"StringTableIndex" => format!("StringTableIndex(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"Optional<StringTableIndex>" => {
|
||||
format!("StringTableIndex::optional_from_raw(super::validator::read_u32(bytes, at + {offset}))")
|
||||
}
|
||||
"RegexTableIndex" => format!("RegexTableIndex(super::validator::read_u32(bytes, at + {offset}))"),
|
||||
"EnvironmentCoordinate" => {
|
||||
format!(
|
||||
"EnvironmentCoordinate {{ hops: super::validator::read_u32(bytes, at + {offset}), index: super::validator::read_u32(bytes, at + {offset} + 4) }}"
|
||||
)
|
||||
}
|
||||
"Builtin" => format!("bytes[at + {offset}]"),
|
||||
other => unreachable!("Unknown field type: {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_field_reads(
|
||||
w: &mut impl Write,
|
||||
op: &OpDef,
|
||||
layouts: &std::collections::HashMap<String, bytecode_def::OpLayout>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = layouts.get(&op.name).expect("layout missing for op");
|
||||
for f in user_fields(op) {
|
||||
if f.is_array {
|
||||
continue;
|
||||
}
|
||||
let rname = rust_field_name(&f.name);
|
||||
let offset = layout.field_offsets.get(&f.name).expect("field offset missing");
|
||||
let expr = read_expr_for_type(&f.ty, *offset);
|
||||
writeln!(w, " let {rname} = {expr};")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_array_bounds(
|
||||
w: &mut impl Write,
|
||||
op: &OpDef,
|
||||
layouts: &std::collections::HashMap<String, bytecode_def::OpLayout>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layout = layouts.get(&op.name).expect("layout missing for op");
|
||||
for f in user_fields(op) {
|
||||
if !f.is_array {
|
||||
continue;
|
||||
}
|
||||
let rname = rust_field_name(&f.name);
|
||||
let count_name = rust_field_name(&find_count_field_name(op, f));
|
||||
let offset = layout.field_offsets.get(&f.name).expect("array offset missing");
|
||||
let elem_size = field_type_info(&f.ty).size;
|
||||
writeln!(w, " let {rname}_offset = at + {offset};")?;
|
||||
writeln!(w, " let {rname}_count = {count_name} as usize;")?;
|
||||
writeln!(
|
||||
w,
|
||||
" let {rname}_end = {rname}_offset + {rname}_count * {elem_size};"
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_instruction_dump_from_bytes(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layouts = compute_layouts(ops);
|
||||
|
||||
writeln!(w, "#[allow(unused_variables)]")?;
|
||||
writeln!(
|
||||
w,
|
||||
"pub fn dump_instruction_from_bytes(opcode: u8, bytes: &[u8], at: usize, dumper: &mut super::dump::BytecodeDumper<'_>) {{"
|
||||
)?;
|
||||
writeln!(w, " match opcode {{")?;
|
||||
|
||||
for (i, op) in ops.iter().enumerate() {
|
||||
if op.name == "Instruction" {
|
||||
continue;
|
||||
}
|
||||
writeln!(w, " {i} => {{")?;
|
||||
generate_field_reads(&mut w, op, &layouts)?;
|
||||
generate_array_bounds(&mut w, op, &layouts)?;
|
||||
writeln!(w, " dumper.begin_instruction(\"{}\");", op.name)?;
|
||||
|
||||
let arrays: Vec<&Field> = op.fields.iter().filter(|f| f.is_array).collect();
|
||||
let mut array_to_count = std::collections::HashMap::new();
|
||||
let mut count_fields = std::collections::HashSet::new();
|
||||
for af in arrays {
|
||||
let count_field_name = find_count_field_name(op, af);
|
||||
count_fields.insert(count_field_name.clone());
|
||||
array_to_count.insert(af.name.clone(), rust_field_name(&count_field_name));
|
||||
}
|
||||
|
||||
for f in &op.fields {
|
||||
if f.name == "m_length" || f.name == "m_cache" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ty = f.ty.trim();
|
||||
let label = rust_field_name(&f.name);
|
||||
let rname = rust_field_name(&f.name);
|
||||
|
||||
if f.is_array {
|
||||
let count_name = array_to_count.get(&f.name).expect("array count missing");
|
||||
match ty {
|
||||
"Operand" => {
|
||||
writeln!(w, " if {count_name} != 0 {{")?;
|
||||
writeln!(w, " dumper.append_piece(|dumper| {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_operand_list(\"{label}\", bytes, {rname}_offset, {rname}_count);"
|
||||
)?;
|
||||
writeln!(w, " }});")?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"Optional<Operand>" => {
|
||||
writeln!(w, " if {count_name} != 0 {{")?;
|
||||
writeln!(w, " dumper.append_piece(|dumper| {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_optional_operand_list(\"{label}\", bytes, {rname}_offset, {rname}_count);"
|
||||
)?;
|
||||
writeln!(w, " }});")?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"Value" => {
|
||||
writeln!(w, " if {count_name} != 0 {{")?;
|
||||
writeln!(w, " dumper.append_piece(|dumper| {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_value_list(\"{label}\", bytes, {rname}_offset, {rname}_count);"
|
||||
)?;
|
||||
writeln!(w, " }});")?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"Label" => {
|
||||
writeln!(w, " if {count_name} != 0 {{")?;
|
||||
writeln!(w, " dumper.append_piece(|dumper| {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_label_list(\"{label}\", bytes, {rname}_offset, {rname}_count);"
|
||||
)?;
|
||||
writeln!(w, " }});")?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"Optional<Label>" => {
|
||||
writeln!(w, " if {count_name} != 0 {{")?;
|
||||
writeln!(w, " dumper.append_piece(|dumper| {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_optional_label_list(\"{label}\", bytes, {rname}_offset, {rname}_count);"
|
||||
)?;
|
||||
writeln!(w, " }});")?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
match ty {
|
||||
"Operand" => writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_operand(\"{label}\", {rname}));"
|
||||
)?,
|
||||
"Optional<Operand>" => {
|
||||
writeln!(w, " if let Some({rname}) = {rname} {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_operand(\"{label}\", {rname}));"
|
||||
)?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"Label" => writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_label(\"{label}\", {rname}.0));"
|
||||
)?,
|
||||
"Optional<Label>" => {
|
||||
writeln!(w, " if let Some({rname}) = {rname} {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_label(\"{label}\", {rname}.0));"
|
||||
)?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"PropertyKeyTableIndex" => {
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_property_key_quoted({rname}.0));"
|
||||
)?;
|
||||
}
|
||||
"IdentifierTableIndex" => {
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_identifier_quoted({rname}.0));"
|
||||
)?;
|
||||
}
|
||||
"Optional<IdentifierTableIndex>" => {
|
||||
let mut property_key_field = None;
|
||||
let mut property_operand_field = None;
|
||||
for other in &op.fields {
|
||||
if other.ty.trim() == "PropertyKeyTableIndex" {
|
||||
property_key_field = Some(rust_field_name(&other.name));
|
||||
break;
|
||||
}
|
||||
if other.ty.trim() == "Operand" && other.name == "m_property" {
|
||||
property_operand_field = Some(rust_field_name(&other.name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(w, " if let Some({rname}) = {rname} {{")?;
|
||||
if let Some(property_key_field) = property_key_field {
|
||||
writeln!(w, " dumper.append(\" \\u{{1b}}[37;1m(\");")?;
|
||||
writeln!(w, " dumper.append_identifier_plain({rname}.0);")?;
|
||||
writeln!(w, " dumper.append(\".\");")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_property_key_plain({property_key_field}.0);"
|
||||
)?;
|
||||
writeln!(w, " dumper.append(\")\\u{{1b}}[0m\");")?;
|
||||
} else if let Some(property_operand_field) = property_operand_field {
|
||||
writeln!(w, " dumper.append(\" \\u{{1b}}[37;1m(\");")?;
|
||||
writeln!(w, " dumper.append_identifier_plain({rname}.0);")?;
|
||||
writeln!(w, " dumper.append(\"[\\u{{1b}}[0m\");")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_operand(\"\", {property_operand_field});"
|
||||
)?;
|
||||
writeln!(w, " dumper.append(\"\\u{{1b}}[37;1m])\\u{{1b}}[0m\");")?;
|
||||
} else if op.name == "GetLength" {
|
||||
writeln!(w, " dumper.append(\" \\u{{1b}}[37;1m(\");")?;
|
||||
writeln!(w, " dumper.append_identifier_plain({rname}.0);")?;
|
||||
writeln!(w, " dumper.append(\".length)\\u{{1b}}[0m\");")?;
|
||||
} else {
|
||||
writeln!(w, " dumper.append(\" \\u{{1b}}[37;1m(\");")?;
|
||||
writeln!(w, " dumper.append_identifier_plain({rname}.0);")?;
|
||||
writeln!(w, " dumper.append(\")\\u{{1b}}[0m\");")?;
|
||||
}
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"StringTableIndex" => writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_string({rname}.0));"
|
||||
)?,
|
||||
"Optional<StringTableIndex>" => {
|
||||
writeln!(w, " if let Some({rname}) = {rname} {{")?;
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_string({rname}.0));"
|
||||
)?;
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
"bool" => writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_bool(\"{label}\", {rname}));"
|
||||
)?,
|
||||
"PutKind" => writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_put_kind(\"{label}\", {rname}));"
|
||||
)?,
|
||||
_ if (ty == "u32" || ty == "u64" || ty == "u8") && !count_fields.contains(&f.name) => {
|
||||
writeln!(
|
||||
w,
|
||||
" dumper.append_piece(|dumper| dumper.append_number(\"{label}\", {rname}));"
|
||||
)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
|
||||
writeln!(w, " _ => unreachable!(\"unknown bytecode opcode\"),")?;
|
||||
writeln!(w, " }}")?;
|
||||
writeln!(w, "}}")?;
|
||||
writeln!(w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_visit_labels_from_bytes(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let layouts = compute_layouts(ops);
|
||||
writeln!(w, "#[allow(unused_variables)]")?;
|
||||
writeln!(
|
||||
w,
|
||||
"pub fn visit_labels_from_bytes(opcode: u8, bytes: &[u8], at: usize, visitor: &mut dyn FnMut(u32)) {{"
|
||||
)?;
|
||||
writeln!(w, " match opcode {{")?;
|
||||
|
||||
for (i, op) in ops.iter().enumerate() {
|
||||
let label_fields: Vec<&Field> = user_fields(op)
|
||||
.into_iter()
|
||||
.filter(|f| f.ty == "Label" || f.ty == "Optional<Label>")
|
||||
.collect();
|
||||
if label_fields.is_empty() {
|
||||
continue;
|
||||
}
|
||||
writeln!(w, " {i} => {{")?;
|
||||
generate_field_reads(&mut w, op, &layouts)?;
|
||||
for f in label_fields {
|
||||
let rname = rust_field_name(&f.name);
|
||||
if f.ty == "Label" {
|
||||
writeln!(w, " visitor({rname}.0);")?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
" if let Some({rname}) = {rname} {{ visitor({rname}.0); }}"
|
||||
)?;
|
||||
}
|
||||
}
|
||||
writeln!(w, " }}")?;
|
||||
}
|
||||
|
||||
writeln!(w, " _ => {{}}")?;
|
||||
writeln!(w, " }}")?;
|
||||
writeln!(w, "}}")?;
|
||||
writeln!(w)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn emit_scalar_field_check(
|
||||
mut w: impl Write,
|
||||
field_name: &str,
|
||||
|
|
|
|||
463
Libraries/LibJS/Rust/src/bytecode/dump.rs
Normal file
463
Libraries/LibJS/Rust/src/bytecode/dump.rs
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use super::instruction::dump_instruction_from_bytes;
|
||||
use super::instruction::instruction_is_terminator_from_opcode;
|
||||
use super::instruction::instruction_length_from_bytes;
|
||||
use super::instruction::visit_labels_from_bytes;
|
||||
use super::operand::Operand;
|
||||
use super::validator::read_u32;
|
||||
use crate::abort_on_panic;
|
||||
use crate::runtime::value::EncodedValue;
|
||||
use crate::runtime::value::EncodedValueKind;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FFIDumpExceptionHandler {
|
||||
pub start_offset: usize,
|
||||
pub end_offset: usize,
|
||||
pub handler_offset: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FFIBytecodeDumpCallbacks {
|
||||
pub append: unsafe extern "C" fn(ctx: *mut c_void, data: *const u8, len: usize),
|
||||
pub append_local: unsafe extern "C" fn(ctx: *mut c_void, index: u32),
|
||||
pub append_identifier: unsafe extern "C" fn(ctx: *mut c_void, index: u32, quoted: bool),
|
||||
pub append_property_key: unsafe extern "C" fn(ctx: *mut c_void, index: u32, quoted: bool),
|
||||
pub append_string: unsafe extern "C" fn(ctx: *mut c_void, index: u32),
|
||||
pub append_value_double: unsafe extern "C" fn(ctx: *mut c_void, value: f64),
|
||||
pub append_value_string: unsafe extern "C" fn(ctx: *mut c_void, encoded: u64),
|
||||
pub append_value_bigint: unsafe extern "C" fn(ctx: *mut c_void, encoded: u64),
|
||||
pub append_value_fallback: unsafe extern "C" fn(ctx: *mut c_void, encoded: u64),
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct FFIBytecodeDumpMetadata {
|
||||
pub number_of_registers: u32,
|
||||
pub registers_and_locals_count: u32,
|
||||
pub local_index_base: u32,
|
||||
pub argument_index_base: u32,
|
||||
pub constants: *const u64,
|
||||
pub constant_count: usize,
|
||||
}
|
||||
|
||||
pub struct BytecodeDumper<'a> {
|
||||
ctx: *mut c_void,
|
||||
callbacks: &'a FFIBytecodeDumpCallbacks,
|
||||
metadata: &'a FFIBytecodeDumpMetadata,
|
||||
constants: &'a [u64],
|
||||
basic_block_start_offsets: &'a [u32],
|
||||
first_piece: bool,
|
||||
}
|
||||
|
||||
impl<'a> BytecodeDumper<'a> {
|
||||
fn new(
|
||||
ctx: *mut c_void,
|
||||
callbacks: &'a FFIBytecodeDumpCallbacks,
|
||||
metadata: &'a FFIBytecodeDumpMetadata,
|
||||
constants: &'a [u64],
|
||||
basic_block_start_offsets: &'a [u32],
|
||||
) -> Self {
|
||||
Self {
|
||||
ctx,
|
||||
callbacks,
|
||||
metadata,
|
||||
constants,
|
||||
basic_block_start_offsets,
|
||||
first_piece: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, text: &str) {
|
||||
unsafe {
|
||||
(self.callbacks.append)(self.ctx, text.as_ptr(), text.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_instruction(&mut self, name: &str) {
|
||||
self.first_piece = true;
|
||||
self.append(name);
|
||||
}
|
||||
|
||||
pub fn append_piece(&mut self, append_piece: impl FnOnce(&mut Self)) {
|
||||
if self.first_piece {
|
||||
self.append(" ");
|
||||
self.first_piece = false;
|
||||
} else {
|
||||
self.append(", ");
|
||||
}
|
||||
append_piece(self);
|
||||
}
|
||||
|
||||
pub fn append_operand(&mut self, name: &str, operand: Operand) {
|
||||
if !name.is_empty() {
|
||||
self.append("\x1b[32m");
|
||||
self.append(name);
|
||||
self.append("\x1b[0m:");
|
||||
}
|
||||
|
||||
let raw = operand.raw();
|
||||
if raw < self.metadata.number_of_registers {
|
||||
if raw == 2 {
|
||||
self.append("\x1b[33mthis\x1b[0m");
|
||||
} else {
|
||||
self.append("\x1b[33mreg");
|
||||
self.append(&raw.to_string());
|
||||
self.append("\x1b[0m");
|
||||
}
|
||||
} else if raw < self.metadata.registers_and_locals_count {
|
||||
let index = raw - self.metadata.local_index_base;
|
||||
self.append("\x1b[34m");
|
||||
unsafe {
|
||||
(self.callbacks.append_local)(self.ctx, index);
|
||||
}
|
||||
self.append("~");
|
||||
self.append(&index.to_string());
|
||||
self.append("\x1b[0m");
|
||||
} else if raw < self.metadata.argument_index_base {
|
||||
let index = raw - self.metadata.registers_and_locals_count;
|
||||
self.append_value(self.constants[index as usize]);
|
||||
} else {
|
||||
let index = raw - self.metadata.argument_index_base;
|
||||
self.append("\x1b[34marg");
|
||||
self.append(&index.to_string());
|
||||
self.append("\x1b[0m");
|
||||
}
|
||||
}
|
||||
|
||||
fn append_value(&mut self, encoded: u64) {
|
||||
match EncodedValue::from_encoded(encoded).kind() {
|
||||
EncodedValueKind::Empty => self.append("<Empty>"),
|
||||
EncodedValueKind::Boolean(value) => {
|
||||
self.append("Bool(");
|
||||
self.append(if value { "true" } else { "false" });
|
||||
self.append(")");
|
||||
}
|
||||
EncodedValueKind::Int32(value) => {
|
||||
self.append("Int32(");
|
||||
self.append(&value.to_string());
|
||||
self.append(")");
|
||||
}
|
||||
EncodedValueKind::Double(value) => {
|
||||
self.append("Double(");
|
||||
unsafe {
|
||||
(self.callbacks.append_value_double)(self.ctx, value);
|
||||
}
|
||||
self.append(")");
|
||||
}
|
||||
EncodedValueKind::BigInt => {
|
||||
self.append("BigInt(");
|
||||
unsafe {
|
||||
(self.callbacks.append_value_bigint)(self.ctx, encoded);
|
||||
}
|
||||
self.append(")");
|
||||
}
|
||||
EncodedValueKind::String => {
|
||||
self.append("String(\"");
|
||||
unsafe {
|
||||
(self.callbacks.append_value_string)(self.ctx, encoded);
|
||||
}
|
||||
self.append("\")");
|
||||
}
|
||||
EncodedValueKind::Undefined => self.append("Undefined"),
|
||||
EncodedValueKind::Null => self.append("Null"),
|
||||
EncodedValueKind::Other => {
|
||||
self.append("Value(");
|
||||
unsafe {
|
||||
(self.callbacks.append_value_fallback)(self.ctx, encoded);
|
||||
}
|
||||
self.append(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn append_value_without_side_effects(&mut self, encoded: u64) {
|
||||
match EncodedValue::from_encoded(encoded).kind() {
|
||||
EncodedValueKind::Empty => self.append("<empty>"),
|
||||
EncodedValueKind::Boolean(value) => self.append(if value { "true" } else { "false" }),
|
||||
EncodedValueKind::Int32(value) => self.append(&value.to_string()),
|
||||
EncodedValueKind::Double(value) => unsafe {
|
||||
(self.callbacks.append_value_double)(self.ctx, value);
|
||||
},
|
||||
EncodedValueKind::BigInt => unsafe {
|
||||
(self.callbacks.append_value_bigint)(self.ctx, encoded);
|
||||
},
|
||||
EncodedValueKind::String => unsafe {
|
||||
(self.callbacks.append_value_string)(self.ctx, encoded);
|
||||
},
|
||||
EncodedValueKind::Undefined => self.append("undefined"),
|
||||
EncodedValueKind::Null => self.append("null"),
|
||||
EncodedValueKind::Other => unsafe {
|
||||
(self.callbacks.append_value_fallback)(self.ctx, encoded);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_label(&mut self, name: &str, address: u32) {
|
||||
if !name.is_empty() {
|
||||
self.append("\x1b[32m");
|
||||
self.append(name);
|
||||
self.append("\x1b[0m:");
|
||||
}
|
||||
|
||||
if let Ok(index) = self.basic_block_start_offsets.binary_search(&address) {
|
||||
self.append("\x1b[35mblock");
|
||||
self.append(&index.to_string());
|
||||
self.append("\x1b[0m");
|
||||
} else {
|
||||
self.append("@");
|
||||
self.append(&format!("{address:x}"));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_identifier_quoted(&mut self, index: u32) {
|
||||
unsafe {
|
||||
(self.callbacks.append_identifier)(self.ctx, index, true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_identifier_plain(&mut self, index: u32) {
|
||||
unsafe {
|
||||
(self.callbacks.append_identifier)(self.ctx, index, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_property_key_quoted(&mut self, index: u32) {
|
||||
unsafe {
|
||||
(self.callbacks.append_property_key)(self.ctx, index, true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_property_key_plain(&mut self, index: u32) {
|
||||
unsafe {
|
||||
(self.callbacks.append_property_key)(self.ctx, index, false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_string(&mut self, index: u32) {
|
||||
unsafe {
|
||||
(self.callbacks.append_string)(self.ctx, index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append_bool(&mut self, name: &str, value: bool) {
|
||||
self.append(name);
|
||||
self.append(":");
|
||||
self.append(if value { "true" } else { "false" });
|
||||
}
|
||||
|
||||
pub fn append_number(&mut self, name: &str, value: impl std::fmt::Display) {
|
||||
self.append(name);
|
||||
self.append(":");
|
||||
self.append(&value.to_string());
|
||||
}
|
||||
|
||||
pub fn append_put_kind(&mut self, name: &str, value: u32) {
|
||||
let kind = match value {
|
||||
0 => "Normal",
|
||||
1 => "Getter",
|
||||
2 => "Setter",
|
||||
3 => "Prototype",
|
||||
4 => "Own",
|
||||
_ => unreachable!("invalid PutKind"),
|
||||
};
|
||||
self.append(name);
|
||||
self.append(":");
|
||||
self.append(kind);
|
||||
}
|
||||
|
||||
pub fn append_operand_list(&mut self, name: &str, bytes: &[u8], offset: usize, count: usize) {
|
||||
self.append("\x1b[32m");
|
||||
self.append(name);
|
||||
self.append("\x1b[0m:[");
|
||||
for i in 0..count {
|
||||
if i != 0 {
|
||||
self.append(", ");
|
||||
}
|
||||
self.append_operand("", Operand::from_raw(read_u32(bytes, offset + i * 4)));
|
||||
}
|
||||
self.append("]");
|
||||
}
|
||||
|
||||
pub fn append_optional_operand_list(&mut self, name: &str, bytes: &[u8], offset: usize, count: usize) {
|
||||
self.append(name);
|
||||
self.append(":[");
|
||||
let mut first_elem = true;
|
||||
for i in 0..count {
|
||||
let raw = read_u32(bytes, offset + i * 4);
|
||||
let Some(operand) = Operand::optional_from_raw(raw) else {
|
||||
continue;
|
||||
};
|
||||
if !first_elem {
|
||||
self.append(", ");
|
||||
}
|
||||
first_elem = false;
|
||||
self.append_operand(name, operand);
|
||||
}
|
||||
self.append("]");
|
||||
}
|
||||
|
||||
pub fn append_label_list(&mut self, name: &str, bytes: &[u8], offset: usize, count: usize) {
|
||||
self.append(name);
|
||||
self.append(":[");
|
||||
for i in 0..count {
|
||||
if i != 0 {
|
||||
self.append(", ");
|
||||
}
|
||||
self.append_label("", read_u32(bytes, offset + i * 4));
|
||||
}
|
||||
self.append("]");
|
||||
}
|
||||
|
||||
pub fn append_optional_label_list(&mut self, name: &str, bytes: &[u8], offset: usize, count: usize) {
|
||||
self.append(name);
|
||||
self.append(":[");
|
||||
let mut first_elem = true;
|
||||
for i in 0..count {
|
||||
let element_offset = offset + i * 8;
|
||||
if bytes[element_offset + 4] == 0 {
|
||||
continue;
|
||||
}
|
||||
if !first_elem {
|
||||
self.append(", ");
|
||||
}
|
||||
first_elem = false;
|
||||
self.append_label("", read_u32(bytes, element_offset));
|
||||
}
|
||||
self.append("]");
|
||||
}
|
||||
|
||||
pub fn append_value_list(&mut self, name: &str, bytes: &[u8], offset: usize, count: usize) {
|
||||
if !name.is_empty() {
|
||||
self.append("\x1b[32m");
|
||||
self.append(name);
|
||||
self.append("\x1b[0m:[");
|
||||
}
|
||||
for i in 0..count {
|
||||
if i != 0 {
|
||||
self.append(", ");
|
||||
}
|
||||
let value = u64::from_ne_bytes(bytes[offset + i * 8..offset + (i + 1) * 8].try_into().unwrap());
|
||||
self.append_value_without_side_effects(value);
|
||||
}
|
||||
self.append("]");
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_basic_block_start_offsets(bytecode: &[u8], exception_handlers: &[FFIDumpExceptionHandler]) -> Vec<u32> {
|
||||
let mut offsets = vec![0];
|
||||
let append_offset = |offsets: &mut Vec<u32>, offset: usize| {
|
||||
let offset = u32::try_from(offset).expect("bytecode offset exceeds u32::MAX");
|
||||
if !offsets.contains(&offset) {
|
||||
offsets.push(offset);
|
||||
}
|
||||
};
|
||||
let append_instruction_offset = |offsets: &mut Vec<u32>, offset: usize| {
|
||||
if offset < bytecode.len() {
|
||||
append_offset(offsets, offset);
|
||||
}
|
||||
};
|
||||
|
||||
let mut at = 0;
|
||||
while at < bytecode.len() {
|
||||
let opcode = bytecode[at];
|
||||
let length = instruction_length_from_bytes(opcode, bytecode, at)
|
||||
.expect("validated bytecode should have valid instruction lengths");
|
||||
let next_offset = at + length;
|
||||
|
||||
visit_labels_from_bytes(opcode, bytecode, at, &mut |address| {
|
||||
append_offset(&mut offsets, address as usize);
|
||||
});
|
||||
|
||||
if instruction_is_terminator_from_opcode(opcode) && next_offset < bytecode.len() {
|
||||
append_offset(&mut offsets, next_offset);
|
||||
}
|
||||
|
||||
at = next_offset;
|
||||
}
|
||||
|
||||
for handler in exception_handlers {
|
||||
append_instruction_offset(&mut offsets, handler.start_offset);
|
||||
append_instruction_offset(&mut offsets, handler.end_offset);
|
||||
append_instruction_offset(&mut offsets, handler.handler_offset);
|
||||
}
|
||||
|
||||
offsets.sort_unstable();
|
||||
offsets
|
||||
}
|
||||
|
||||
/// Dump a validated bytecode instruction stream through C++ formatting callbacks.
|
||||
///
|
||||
/// # 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.
|
||||
/// `metadata` and `callbacks` must point to valid structs, and every callback
|
||||
/// must remain callable for the duration of this function. `metadata.constants`
|
||||
/// must point to `metadata.constant_count` encoded Values, or be null when the
|
||||
/// count is zero. `ctx` is passed through to callbacks and must remain valid
|
||||
/// for their requirements.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rust_dump_bytecode(
|
||||
bytecode_ptr: *const u8,
|
||||
bytecode_len: usize,
|
||||
exception_handlers: *const FFIDumpExceptionHandler,
|
||||
exception_handler_count: usize,
|
||||
metadata: *const FFIBytecodeDumpMetadata,
|
||||
ctx: *mut c_void,
|
||||
callbacks: *const FFIBytecodeDumpCallbacks,
|
||||
) {
|
||||
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)
|
||||
};
|
||||
let metadata = metadata.as_ref().expect("rust_dump_bytecode metadata must not be null");
|
||||
let constants = if metadata.constant_count == 0 {
|
||||
&[]
|
||||
} else {
|
||||
std::slice::from_raw_parts(metadata.constants, metadata.constant_count)
|
||||
};
|
||||
let callbacks = callbacks
|
||||
.as_ref()
|
||||
.expect("rust_dump_bytecode callbacks must not be null");
|
||||
let basic_block_start_offsets = collect_basic_block_start_offsets(bytecode, exception_handlers);
|
||||
let mut dumper = BytecodeDumper::new(ctx, callbacks, metadata, constants, &basic_block_start_offsets);
|
||||
let mut basic_block_offset_index = 0;
|
||||
|
||||
let mut at = 0;
|
||||
while at < bytecode.len() {
|
||||
if basic_block_offset_index < basic_block_start_offsets.len()
|
||||
&& at == basic_block_start_offsets[basic_block_offset_index] as usize
|
||||
{
|
||||
if basic_block_offset_index > 0 {
|
||||
dumper.append("\n");
|
||||
}
|
||||
dumper.append("\x1b[35;1mblock");
|
||||
dumper.append(&basic_block_offset_index.to_string());
|
||||
dumper.append("\x1b[0m:\n");
|
||||
basic_block_offset_index += 1;
|
||||
}
|
||||
|
||||
dumper.append(" [");
|
||||
dumper.append(&format!("{at:4x}"));
|
||||
dumper.append("] ");
|
||||
dump_instruction_from_bytes(bytecode[at], bytecode, at, &mut dumper);
|
||||
dumper.append("\n");
|
||||
|
||||
at += instruction_length_from_bytes(bytecode[at], bytecode, at)
|
||||
.expect("validated bytecode should have valid instruction lengths");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
pub mod basic_block;
|
||||
pub mod codegen;
|
||||
pub mod dump;
|
||||
pub mod ffi;
|
||||
pub mod generator;
|
||||
pub mod instruction;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,14 @@ impl Operand {
|
|||
self.0
|
||||
}
|
||||
|
||||
pub fn from_raw(raw: u32) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
|
||||
pub fn optional_from_raw(raw: u32) -> Option<Self> {
|
||||
if raw == Self::INVALID { None } else { Some(Self(raw)) }
|
||||
}
|
||||
|
||||
/// Offset the index by the given amount, stripping the type tag and
|
||||
/// leaving a flat index into the combined
|
||||
/// [registers | locals | constants | arguments] array.
|
||||
|
|
@ -134,6 +142,10 @@ pub struct StringTableIndex(pub u32);
|
|||
|
||||
impl StringTableIndex {
|
||||
pub const INVALID: u32 = 0xFFFF_FFFF;
|
||||
|
||||
pub fn optional_from_raw(raw: u32) -> Option<Self> {
|
||||
if raw == Self::INVALID { None } else { Some(Self(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Index into the identifier table.
|
||||
|
|
@ -142,6 +154,10 @@ pub struct IdentifierTableIndex(pub u32);
|
|||
|
||||
impl IdentifierTableIndex {
|
||||
pub const INVALID: u32 = 0xFFFF_FFFF;
|
||||
|
||||
pub fn optional_from_raw(raw: u32) -> Option<Self> {
|
||||
if raw == Self::INVALID { None } else { Some(Self(raw)) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Index into the property key table.
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ mod bytecode_cache;
|
|||
pub mod fast_hash;
|
||||
pub mod lexer;
|
||||
pub mod parser;
|
||||
pub mod runtime;
|
||||
pub mod scope_collector;
|
||||
pub mod token;
|
||||
|
||||
|
|
|
|||
7
Libraries/LibJS/Rust/src/runtime/mod.rs
Normal file
7
Libraries/LibJS/Rust/src/runtime/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
pub mod value;
|
||||
104
Libraries/LibJS/Rust/src/runtime/value.rs
Normal file
104
Libraries/LibJS/Rust/src/runtime/value.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum EncodedValueKind {
|
||||
Empty,
|
||||
Boolean(bool),
|
||||
Int32(i32),
|
||||
Double(f64),
|
||||
String,
|
||||
BigInt,
|
||||
Undefined,
|
||||
Null,
|
||||
Other,
|
||||
}
|
||||
|
||||
/// The encoded representation of a LibJS `Value`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct EncodedValue(u64);
|
||||
|
||||
impl EncodedValue {
|
||||
const CANON_NAN_BITS: u64 = 0x7ff8_0000_0000_0000;
|
||||
const TAG_SHIFT: u64 = 48;
|
||||
const BASE_TAG: u64 = 0x7ff8;
|
||||
const IS_CELL_BIT: u64 = 0x8000 | Self::BASE_TAG;
|
||||
const STRING_TAG: u64 = 0b010 | Self::IS_CELL_BIT;
|
||||
const BIGINT_TAG: u64 = 0b101 | Self::IS_CELL_BIT;
|
||||
const UNDEFINED_TAG: u64 = 0b110 | Self::BASE_TAG;
|
||||
const NULL_TAG: u64 = 0b111 | Self::BASE_TAG;
|
||||
const BOOLEAN_TAG: u64 = 0b001 | Self::BASE_TAG;
|
||||
const INT32_TAG: u64 = 0b010 | Self::BASE_TAG;
|
||||
const EMPTY_TAG: u64 = 0b011 | Self::BASE_TAG;
|
||||
|
||||
pub const fn from_encoded(encoded: u64) -> Self {
|
||||
Self(encoded)
|
||||
}
|
||||
|
||||
pub const fn encoded(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn kind(self) -> EncodedValueKind {
|
||||
let tag = self.0 >> Self::TAG_SHIFT;
|
||||
if self.0 == (Self::EMPTY_TAG << Self::TAG_SHIFT) {
|
||||
EncodedValueKind::Empty
|
||||
} else if tag == Self::BOOLEAN_TAG {
|
||||
EncodedValueKind::Boolean(self.0 & 1 != 0)
|
||||
} else if tag == Self::INT32_TAG {
|
||||
EncodedValueKind::Int32((self.0 & 0xffff_ffff) as u32 as i32)
|
||||
} else if (self.0 & Self::CANON_NAN_BITS) != Self::CANON_NAN_BITS || self.0 == Self::CANON_NAN_BITS {
|
||||
EncodedValueKind::Double(f64::from_bits(self.0))
|
||||
} else if tag == Self::BIGINT_TAG {
|
||||
EncodedValueKind::BigInt
|
||||
} else if tag == Self::STRING_TAG {
|
||||
EncodedValueKind::String
|
||||
} else if self.0 == (Self::UNDEFINED_TAG << Self::TAG_SHIFT) {
|
||||
EncodedValueKind::Undefined
|
||||
} else if self.0 == (Self::NULL_TAG << Self::TAG_SHIFT) {
|
||||
EncodedValueKind::Null
|
||||
} else {
|
||||
EncodedValueKind::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decodes_immediate_values() {
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded(EncodedValue::EMPTY_TAG << EncodedValue::TAG_SHIFT).kind(),
|
||||
EncodedValueKind::Empty
|
||||
);
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded((EncodedValue::BOOLEAN_TAG << EncodedValue::TAG_SHIFT) | 1).kind(),
|
||||
EncodedValueKind::Boolean(true)
|
||||
);
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded((EncodedValue::BOOLEAN_TAG << EncodedValue::TAG_SHIFT) | 0).kind(),
|
||||
EncodedValueKind::Boolean(false)
|
||||
);
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded((EncodedValue::INT32_TAG << EncodedValue::TAG_SHIFT) | 0xffff_ffff).kind(),
|
||||
EncodedValueKind::Int32(-1)
|
||||
);
|
||||
assert!(matches!(
|
||||
EncodedValue::from_encoded(EncodedValue::CANON_NAN_BITS).kind(),
|
||||
EncodedValueKind::Double(value) if value.to_bits() == EncodedValue::CANON_NAN_BITS
|
||||
));
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded(EncodedValue::UNDEFINED_TAG << EncodedValue::TAG_SHIFT).kind(),
|
||||
EncodedValueKind::Undefined
|
||||
);
|
||||
assert_eq!(
|
||||
EncodedValue::from_encoded(EncodedValue::NULL_TAG << EncodedValue::TAG_SHIFT).kind(),
|
||||
EncodedValueKind::Null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <LibJS/RustIntegration.h>
|
||||
|
||||
#include <AK/BitCast.h>
|
||||
#include <AK/NumericLimits.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <AK/Utf16String.h>
|
||||
|
|
@ -71,6 +72,16 @@ static Utf16String utf16_from_raw(uint16_t const* data, size_t len)
|
|||
return Utf16String::from_utf16(utf16_view_from_bytes(data, len));
|
||||
}
|
||||
|
||||
static StringView string_view_from_rust_bytes(uint8_t const* data, size_t len)
|
||||
{
|
||||
return { reinterpret_cast<char const*>(data), len };
|
||||
}
|
||||
|
||||
struct BytecodeDumpBuilder {
|
||||
StringBuilder& output;
|
||||
GC::Ref<Bytecode::Executable const> executable;
|
||||
};
|
||||
|
||||
// --- Error collection callbacks ---
|
||||
|
||||
// Collects parse errors as a Vector<ParserError> (for Script/Module compilation).
|
||||
|
|
@ -110,6 +121,122 @@ struct ScriptGdiBuilder {
|
|||
|
||||
namespace JS::FFI {
|
||||
|
||||
static void bytecode_dump_append(void* ctx, uint8_t const* data, size_t len)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
builder.output.append(JS::RustIntegration::string_view_from_rust_bytes(data, len));
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_local(void* ctx, uint32_t index)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
builder.output.append(builder.executable->local_variable_names[index].name);
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_identifier(void* ctx, uint32_t index, bool quoted)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
auto identifier = builder.executable->identifier_table->get(JS::Bytecode::IdentifierTableIndex { index });
|
||||
if (quoted)
|
||||
builder.output.appendff("\033[36m`{}`\033[0m", identifier);
|
||||
else
|
||||
builder.output.append(identifier);
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_property_key(void* ctx, uint32_t index, bool quoted)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
auto const& property_key = builder.executable->property_key_table->get(JS::Bytecode::PropertyKeyTableIndex { index });
|
||||
if (quoted)
|
||||
builder.output.appendff("\033[36m`{}`\033[0m", property_key);
|
||||
else
|
||||
builder.output.appendff("{}", property_key);
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_string(void* ctx, uint32_t index)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
builder.output.append(builder.executable->get_string(JS::Bytecode::StringTableIndex { index }));
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_value_double(void* ctx, double value)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
builder.output.appendff("{}", value);
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_value_string(void* ctx, uint64_t encoded)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
auto value = bit_cast<Value>(encoded);
|
||||
builder.output.append(value.as_string().utf8_string_view());
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_value_bigint(void* ctx, uint64_t encoded)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
auto value = bit_cast<Value>(encoded);
|
||||
builder.output.append(MUST(value.as_bigint().to_string()));
|
||||
}
|
||||
|
||||
static void bytecode_dump_append_value_fallback(void* ctx, uint64_t encoded)
|
||||
{
|
||||
auto& builder = *static_cast<JS::RustIntegration::BytecodeDumpBuilder*>(ctx);
|
||||
auto value = bit_cast<Value>(encoded);
|
||||
builder.output.appendff("{}", value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace JS::RustIntegration {
|
||||
|
||||
void dump_bytecode(StringBuilder& output, 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({
|
||||
.start_offset = handler.start_offset,
|
||||
.end_offset = handler.end_offset,
|
||||
.handler_offset = handler.handler_offset,
|
||||
});
|
||||
}
|
||||
|
||||
BytecodeDumpBuilder builder { output, executable };
|
||||
FFI::FFIBytecodeDumpCallbacks callbacks {
|
||||
.append = FFI::bytecode_dump_append,
|
||||
.append_local = FFI::bytecode_dump_append_local,
|
||||
.append_identifier = FFI::bytecode_dump_append_identifier,
|
||||
.append_property_key = FFI::bytecode_dump_append_property_key,
|
||||
.append_string = FFI::bytecode_dump_append_string,
|
||||
.append_value_double = FFI::bytecode_dump_append_value_double,
|
||||
.append_value_string = FFI::bytecode_dump_append_value_string,
|
||||
.append_value_bigint = FFI::bytecode_dump_append_value_bigint,
|
||||
.append_value_fallback = FFI::bytecode_dump_append_value_fallback,
|
||||
};
|
||||
FFI::FFIBytecodeDumpMetadata metadata {
|
||||
.number_of_registers = executable.number_of_registers,
|
||||
.registers_and_locals_count = executable.registers_and_locals_count,
|
||||
.local_index_base = executable.local_index_base,
|
||||
.argument_index_base = executable.argument_index_base,
|
||||
.constants = reinterpret_cast<uint64_t const*>(executable.constants.data()),
|
||||
.constant_count = executable.constants.size(),
|
||||
};
|
||||
|
||||
FFI::rust_dump_bytecode(
|
||||
executable.bytecode.data(),
|
||||
executable.bytecode.size(),
|
||||
exception_handlers.data(),
|
||||
exception_handlers.size(),
|
||||
&metadata,
|
||||
&builder,
|
||||
&callbacks);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace JS::FFI {
|
||||
|
||||
extern "C" void script_gdi_push_lexical_name(void* ctx, uint16_t const* name, size_t len)
|
||||
{
|
||||
static_cast<JS::RustIntegration::ScriptGdiBuilder*>(ctx)->result.lexical_names.append(JS::RustIntegration::utf16_fly_from(name, len));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <AK/Optional.h>
|
||||
#include <AK/Result.h>
|
||||
#include <AK/Span.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16FlyString.h>
|
||||
#include <LibCore/Forward.h>
|
||||
#include <LibCore/ImmutableBytes.h>
|
||||
|
|
@ -191,6 +192,8 @@ Optional<Vector<GC::Root<SharedFunctionInstanceData>>> compile_builtin_file(
|
|||
// Returns nullptr if Rust is not available or the SFD doesn't use Rust compilation.
|
||||
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 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);
|
||||
// Attach a previously compiled function for lazy materialization.
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@ def is_optional_label_type(t: str) -> bool:
|
|||
return t.strip() == "Optional<Label>"
|
||||
|
||||
|
||||
def is_value_type(t: str) -> bool:
|
||||
t = t.strip()
|
||||
return t == "Value" or t == "Optional<Value>"
|
||||
|
||||
|
||||
def find_count_field_name(op: OpDef, array_field: Field) -> Optional[str]:
|
||||
"""
|
||||
Heuristic: look for a u32/size_t field matching
|
||||
|
|
@ -283,8 +278,6 @@ def generate_class(op: OpDef) -> str:
|
|||
lines.append(" }")
|
||||
lines.append("")
|
||||
|
||||
lines.append(" ByteString to_byte_string_impl(Bytecode::Executable const&) const;")
|
||||
|
||||
visit_operands = generate_visit_operands(op)
|
||||
if visit_operands:
|
||||
lines.append(visit_operands)
|
||||
|
|
@ -325,21 +318,6 @@ def generate_op_namespace_body(ops: List[OpDef]) -> str:
|
|||
return "\n".join(lines)
|
||||
|
||||
|
||||
NUMERIC_TYPES = {
|
||||
"u8",
|
||||
"u16",
|
||||
"u32",
|
||||
"u64",
|
||||
"i8",
|
||||
"i16",
|
||||
"i32",
|
||||
"i64",
|
||||
"int",
|
||||
"unsigned",
|
||||
"unsigned int",
|
||||
"size_t",
|
||||
}
|
||||
|
||||
CACHE_INDEX_TYPES = {
|
||||
"PropertyLookupCacheIndex",
|
||||
"GlobalVariableCacheIndex",
|
||||
|
|
@ -361,247 +339,10 @@ def cpp_type_for_field(t: str) -> str:
|
|||
return t
|
||||
|
||||
|
||||
def generate_to_byte_string_impl(op: OpDef) -> str:
|
||||
if op.name == "Instruction":
|
||||
return ""
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append(
|
||||
f"ByteString {op.name}::to_byte_string_impl([[maybe_unused]] Bytecode::Executable const& executable) const"
|
||||
)
|
||||
lines.append("{")
|
||||
lines.append(" StringBuilder builder;")
|
||||
lines.append(f' builder.append("{op.name}"sv);')
|
||||
lines.append("")
|
||||
lines.append(" bool first = true;")
|
||||
lines.append(" [[maybe_unused]] auto append_piece = [&](auto const& piece) {")
|
||||
lines.append(" if (first) {")
|
||||
lines.append(" builder.append(' ');")
|
||||
lines.append(" first = false;")
|
||||
lines.append(" } else {")
|
||||
lines.append(' builder.append(", "sv);')
|
||||
lines.append(" }")
|
||||
lines.append(" builder.append(piece);")
|
||||
lines.append(" };")
|
||||
lines.append("")
|
||||
|
||||
arrays: List[Field] = [f for f in op.fields if f.is_array]
|
||||
array_to_count = {af.name: get_count_field_name_or_die(op, af) for af in arrays}
|
||||
count_fields = set(array_to_count.values())
|
||||
|
||||
for f in op.fields:
|
||||
if f.name == "m_length" or f.name == "m_cache":
|
||||
continue
|
||||
|
||||
t = f.type.strip()
|
||||
label = getter_name_for_field(f.name)
|
||||
|
||||
if f.is_array:
|
||||
count_name = array_to_count[f.name]
|
||||
|
||||
if t == "Operand":
|
||||
lines.append(f" if ({count_name} != 0)")
|
||||
lines.append(
|
||||
f' append_piece(format_operand_list("{label}"sv, {{ {f.name}, {count_name} }}, executable));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Operand>":
|
||||
lines.append(f" if ({count_name} != 0) {{")
|
||||
lines.append(" StringBuilder list_builder;")
|
||||
lines.append(f' list_builder.appendff("{{}}:[", "{label}"sv);')
|
||||
lines.append(" bool first_elem = true;")
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(f" if (!{f.name}[i].has_value())")
|
||||
lines.append(" continue;")
|
||||
lines.append(" if (!first_elem)")
|
||||
lines.append(' list_builder.append(", "sv);')
|
||||
lines.append(" first_elem = false;")
|
||||
lines.append(
|
||||
f' list_builder.append(format_operand("{label}"sv, {f.name}[i].value(), executable));'
|
||||
)
|
||||
lines.append(" }")
|
||||
lines.append(" list_builder.append(']');")
|
||||
lines.append(" append_piece(list_builder.to_byte_string());")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Value":
|
||||
lines.append(f" if ({count_name} != 0)")
|
||||
lines.append(
|
||||
f' append_piece(format_value_list("{label}"sv, ReadonlySpan<Value> {{ {f.name}, {count_name} }}));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Label":
|
||||
lines.append(f" if ({count_name} != 0) {{")
|
||||
lines.append(" StringBuilder list_builder;")
|
||||
lines.append(f' list_builder.appendff("{{}}:[", "{label}"sv);')
|
||||
lines.append(" bool first_elem = true;")
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(" if (!first_elem)")
|
||||
lines.append(' list_builder.append(", "sv);')
|
||||
lines.append(" first_elem = false;")
|
||||
lines.append(f' list_builder.append(format_label(""sv, {f.name}[i], executable));')
|
||||
lines.append(" }")
|
||||
lines.append(" list_builder.append(']');")
|
||||
lines.append(" append_piece(list_builder.to_byte_string());")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Label>":
|
||||
lines.append(f" if ({count_name} != 0) {{")
|
||||
lines.append(" StringBuilder list_builder;")
|
||||
lines.append(f' list_builder.appendff("{{}}:[", "{label}"sv);')
|
||||
lines.append(" bool first_elem = true;")
|
||||
lines.append(f" for (size_t i = 0; i < {count_name}; ++i) {{")
|
||||
lines.append(f" if (!{f.name}[i].has_value())")
|
||||
lines.append(" continue;")
|
||||
lines.append(" if (!first_elem)")
|
||||
lines.append(' list_builder.append(", "sv);')
|
||||
lines.append(" first_elem = false;")
|
||||
lines.append(f' list_builder.append(format_label(""sv, {f.name}[i].value(), executable));')
|
||||
lines.append(" }")
|
||||
lines.append(" list_builder.append(']');")
|
||||
lines.append(" append_piece(list_builder.to_byte_string());")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
# other array types not printed
|
||||
continue
|
||||
|
||||
if t == "Operand":
|
||||
lines.append(f' append_piece(format_operand("{label}"sv, {f.name}, executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Operand>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f' append_piece(format_operand("{label}"sv, {f.name}.value(), executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Label":
|
||||
lines.append(f' append_piece(format_label("{label}"sv, {f.name}, executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<Label>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f' append_piece(format_label("{label}"sv, {f.name}.value(), executable));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "PropertyKeyTableIndex":
|
||||
lines.append(
|
||||
f' append_piece(ByteString::formatted("\\033[36m`{{}}`\\033[0m", executable.property_key_table->get({f.name})));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "IdentifierTableIndex":
|
||||
lines.append(
|
||||
f' append_piece(ByteString::formatted("\\033[36m`{{}}`\\033[0m", executable.identifier_table->get({f.name})));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<IdentifierTableIndex>":
|
||||
# Find a property field in the same op to join with.
|
||||
property_key_field = None
|
||||
property_operand_field = None
|
||||
for other in op.fields:
|
||||
if other.type.strip() == "PropertyKeyTableIndex":
|
||||
property_key_field = other
|
||||
break
|
||||
if other.type.strip() == "Operand" and other.name == "m_property":
|
||||
property_operand_field = other
|
||||
break
|
||||
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
if property_key_field:
|
||||
lines.append(
|
||||
f' builder.appendff(" \\033[37;1m({{}}.{{}})\\033[0m", executable.identifier_table->get({f.name}.value()), executable.property_key_table->get({property_key_field.name}));'
|
||||
)
|
||||
elif property_operand_field:
|
||||
lines.append(" {")
|
||||
lines.append(
|
||||
f' auto property_hint = format_operand(""sv, {property_operand_field.name}, executable);'
|
||||
)
|
||||
lines.append(
|
||||
f' builder.appendff(" \\033[37;1m({{}}[\\033[0m{{}}\\033[37;1m])\\033[0m", executable.identifier_table->get({f.name}.value()), property_hint);'
|
||||
)
|
||||
lines.append(" }")
|
||||
elif op.name == "GetLength":
|
||||
lines.append(
|
||||
f' builder.appendff(" \\033[37;1m({{}}.length)\\033[0m", executable.identifier_table->get({f.name}.value()));'
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
f' builder.appendff(" \\033[37;1m({{}})\\033[0m", executable.identifier_table->get({f.name}.value()));'
|
||||
)
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "StringTableIndex":
|
||||
lines.append(f" append_piece(executable.get_string({f.name}));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "Optional<StringTableIndex>":
|
||||
lines.append(f" if ({f.name}.has_value())")
|
||||
lines.append(f" append_piece(executable.get_string({f.name}.value()));")
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if is_value_type(t):
|
||||
# not printed for now
|
||||
continue
|
||||
|
||||
if t == "bool":
|
||||
lines.append(f' append_piece(ByteString::formatted("{label}:{{}}", {f.name}));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t in NUMERIC_TYPES and f.name not in count_fields:
|
||||
lines.append(f' append_piece(ByteString::formatted("{label}:{{}}", {f.name}));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
if t == "PutKind":
|
||||
lines.append(f' append_piece(ByteString::formatted("{label}:{{}}", put_kind_to_string({f.name})));')
|
||||
lines.append("")
|
||||
continue
|
||||
|
||||
# other types (enums, refs, etc.) skipped
|
||||
|
||||
lines.append(" return builder.to_byte_string();")
|
||||
lines.append("}")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def generate_op_cpp_body(ops: List[OpDef]) -> str:
|
||||
lines: List[str] = []
|
||||
lines.append("#include <AK/StringBuilder.h>")
|
||||
lines.append("#include <AK/StringView.h>")
|
||||
lines.append("#include <LibJS/Bytecode/FormatOperand.h>")
|
||||
lines.append("#include <LibJS/Bytecode/Op.h>")
|
||||
lines.append("")
|
||||
lines.append("namespace JS::Bytecode::Op {")
|
||||
lines.append("")
|
||||
|
||||
for op in ops:
|
||||
impl = generate_to_byte_string_impl(op)
|
||||
if impl:
|
||||
lines.append(impl)
|
||||
|
||||
lines.append("} // namespace JS::Bytecode::Op")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue