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.
1332 lines
52 KiB
Rust
1332 lines
52 KiB
Rust
/*
|
|
* Copyright (c) 2026-present, the Ladybird developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
//! Build script that generates Rust bytecode instruction types from Bytecode.def.
|
|
//!
|
|
//! This mirrors Meta/Generators/generate_libjs_bytecode_def_derived.py but generates Rust
|
|
//! code instead of C++. The generated code lives in $OUT_DIR/instruction_generated.rs
|
|
//! and is included! from src/bytecode/instruction.rs.
|
|
|
|
use bytecode_def::Field;
|
|
use bytecode_def::OpDef;
|
|
use bytecode_def::STRUCT_ALIGN;
|
|
use bytecode_def::compute_layouts;
|
|
use bytecode_def::field_type_info;
|
|
use bytecode_def::find_m_length_offset;
|
|
use bytecode_def::round_up;
|
|
use bytecode_def::user_fields;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::io::Write;
|
|
use std::path::PathBuf;
|
|
|
|
fn rust_field_name(name: &str) -> String {
|
|
if let Some(stripped) = name.strip_prefix("m_") {
|
|
stripped.to_string()
|
|
} else {
|
|
name.to_string()
|
|
}
|
|
}
|
|
|
|
/// Find the count field corresponding to a given array field, using the same
|
|
/// heuristic as the Python generator: prefer `<name>_count`, fall back to
|
|
/// `<singularized name>_count`. Panics if no matching u32/size_t field is
|
|
/// found, mirroring the Python `find_count_field_name_or_die`.
|
|
fn find_count_field_name(op: &OpDef, array_field: &Field) -> String {
|
|
let mut candidates = vec![format!("{}_count", array_field.name)];
|
|
if let Some(stripped) = array_field.name.strip_suffix('s') {
|
|
candidates.push(format!("{stripped}_count"));
|
|
}
|
|
for candidate in &candidates {
|
|
for f in &op.fields {
|
|
if f.is_array {
|
|
continue;
|
|
}
|
|
if &f.name == candidate && (f.ty == "u32" || f.ty == "size_t") {
|
|
return candidate.clone();
|
|
}
|
|
}
|
|
}
|
|
panic!(
|
|
"No count field (u32/size_t) found for array field '{}' in op '{}'",
|
|
array_field.name, op.name
|
|
);
|
|
}
|
|
|
|
fn generate_rust_code(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, "// @generated from Libraries/LibJS/Bytecode/Bytecode.def")?;
|
|
writeln!(w, "// Do not edit manually.")?;
|
|
writeln!(w)?;
|
|
writeln!(w, "use super::operand::*;")?;
|
|
writeln!(w)?;
|
|
|
|
generate_opcode_enum(&mut w, ops)?;
|
|
generate_num_opcodes_const(&mut w, ops)?;
|
|
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())?;
|
|
writeln!(w)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_instruction_length_from_bytes(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(
|
|
w,
|
|
"/// Returns the encoded length in bytes of the instruction at `bytes[at..]`."
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
"/// Reads `m_length` from the buffer for variable-length instructions; for fixed-"
|
|
)?;
|
|
writeln!(w, "/// length instructions, returns the statically-known size.")?;
|
|
writeln!(
|
|
w,
|
|
"pub fn instruction_length_from_bytes(opcode: u8, bytes: &[u8], at: usize) -> Result<usize, super::validator::ValidationErrorKind> {{"
|
|
)?;
|
|
writeln!(w, " use super::validator::ValidationErrorKind;")?;
|
|
writeln!(w, " match opcode {{")?;
|
|
|
|
for (i, op) in ops.iter().enumerate() {
|
|
let has_array = op.fields.iter().any(|f| f.is_array);
|
|
|
|
if !has_array {
|
|
let mut offset: usize = 2;
|
|
for f in &op.fields {
|
|
if f.is_array || f.name == "m_type" || f.name == "m_strict" {
|
|
continue;
|
|
}
|
|
let info = field_type_info(&f.ty);
|
|
offset = round_up(offset, info.align);
|
|
offset += info.size;
|
|
}
|
|
let final_size = round_up(offset, STRUCT_ALIGN);
|
|
let op_name = &op.name;
|
|
writeln!(w, " {i} => Ok({final_size}), // {op_name}")?;
|
|
} else {
|
|
let mut fixed_offset: usize = 2;
|
|
for f in &op.fields {
|
|
if f.is_array || f.name == "m_type" || f.name == "m_strict" {
|
|
continue;
|
|
}
|
|
let info = field_type_info(&f.ty);
|
|
fixed_offset = round_up(fixed_offset, info.align);
|
|
fixed_offset += info.size;
|
|
}
|
|
let minimum_length = round_up(fixed_offset, STRUCT_ALIGN);
|
|
let m_length_offset = find_m_length_offset(&op.fields);
|
|
let op_name = &op.name;
|
|
writeln!(w, " {i} => {{ // {op_name} (variable-length)")?;
|
|
writeln!(w, " let m_length_end = at + {m_length_offset} + 4;")?;
|
|
writeln!(w, " if m_length_end > bytes.len() {{")?;
|
|
writeln!(
|
|
w,
|
|
" return Err(ValidationErrorKind::TruncatedInstruction);"
|
|
)?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(
|
|
w,
|
|
" let raw = u32::from_ne_bytes(bytes[at + {m_length_offset}..m_length_end].try_into().unwrap());"
|
|
)?;
|
|
writeln!(w, " if raw < {minimum_length} {{")?;
|
|
writeln!(w, " return Err(ValidationErrorKind::InvalidLength);")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " Ok(raw as usize)")?;
|
|
writeln!(w, " }}")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " _ => Err(ValidationErrorKind::UnknownOpcode),")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, "}}")?;
|
|
writeln!(w)?;
|
|
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,
|
|
ty: &str,
|
|
offset: usize,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
match ty {
|
|
"Operand" => writeln!(w, " validate_operand(read_u32(bytes, at + {offset}), ctx)?;")?,
|
|
"Optional<Operand>" => writeln!(
|
|
w,
|
|
" validate_optional_operand(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"Label" => writeln!(w, " validate_label(read_u32(bytes, at + {offset}), ctx)?;")?,
|
|
"Optional<Label>" => {
|
|
// Encoded as 8 bytes: u32 value + u8 has_value + 3 bytes pad.
|
|
writeln!(w, " if bytes[at + {offset} + 4] != 0 {{")?;
|
|
writeln!(
|
|
w,
|
|
" validate_label(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?;
|
|
writeln!(w, " }}")?;
|
|
}
|
|
"IdentifierTableIndex" => writeln!(
|
|
w,
|
|
" validate_identifier_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"Optional<IdentifierTableIndex>" => writeln!(
|
|
w,
|
|
" validate_optional_identifier_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"StringTableIndex" => writeln!(
|
|
w,
|
|
" validate_string_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"Optional<StringTableIndex>" => writeln!(
|
|
w,
|
|
" validate_optional_string_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"PropertyKeyTableIndex" => writeln!(
|
|
w,
|
|
" validate_property_key_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"RegexTableIndex" => {
|
|
// The regex table is not consulted at runtime; skip range-checking.
|
|
}
|
|
"PropertyLookupCacheIndex" => writeln!(
|
|
w,
|
|
" validate_property_lookup_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"GlobalVariableCacheIndex" => writeln!(
|
|
w,
|
|
" validate_global_variable_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"EnvironmentCoordinateCacheIndex" => writeln!(
|
|
w,
|
|
" validate_environment_coordinate_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"TemplateObjectCacheIndex" => writeln!(
|
|
w,
|
|
" validate_template_object_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"ObjectShapeCacheIndex" => writeln!(
|
|
w,
|
|
" validate_object_shape_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"ObjectPropertyIteratorCacheIndex" => writeln!(
|
|
w,
|
|
" validate_object_property_iterator_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"u32" => {
|
|
// The .def gives us no first-class types for SFD, class-blueprint,
|
|
// or object-shape cache references stored as u32. Recognize the
|
|
// canonical field names so these still get range-checked.
|
|
if field_name == "m_shared_function_data_index" {
|
|
writeln!(
|
|
w,
|
|
" validate_shared_function_data_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?;
|
|
} else if field_name == "m_class_blueprint_index" {
|
|
writeln!(
|
|
w,
|
|
" validate_class_blueprint_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?;
|
|
} else if field_name == "m_shape_cache_index" {
|
|
writeln!(
|
|
w,
|
|
" validate_object_shape_cache_index(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?;
|
|
}
|
|
}
|
|
"Completion::Type" => writeln!(
|
|
w,
|
|
" validate_completion_type(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"IteratorHint" => writeln!(
|
|
w,
|
|
" validate_iterator_hint(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"EnvironmentMode" => writeln!(
|
|
w,
|
|
" validate_environment_mode(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"PutKind" => writeln!(
|
|
w,
|
|
" validate_put_kind(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"ArgumentsKind" => writeln!(
|
|
w,
|
|
" validate_arguments_kind(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
"FunctionNamePrefix" => writeln!(
|
|
w,
|
|
" validate_function_name_prefix(read_u32(bytes, at + {offset}), ctx)?;"
|
|
)?,
|
|
// bool, u64, Value, EnvironmentCoordinate, Builtin: no per-field
|
|
// bound applied here.
|
|
_ => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn emit_array_elem_check(mut w: impl Write, ty: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
match ty {
|
|
"Operand" => writeln!(w, " validate_operand(read_u32(bytes, __off), ctx)?;")?,
|
|
"Optional<Operand>" => writeln!(
|
|
w,
|
|
" validate_optional_operand(read_u32(bytes, __off), ctx)?;"
|
|
)?,
|
|
"Value" => {
|
|
// Trailing Value array (NewPrimitiveArray): no per-element check;
|
|
// the count was already bounded against the instruction length.
|
|
}
|
|
other => panic!("Array element type not supported: {other}"),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_validate_instruction(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
let layouts = compute_layouts(ops);
|
|
|
|
writeln!(
|
|
w,
|
|
"/// Per-opcode field validation, dispatched by Pass 2 of the validator."
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
"pub fn validate_instruction(opcode: u8, ctx: &super::validator::ValidationContext, at: usize) -> Result<(), super::validator::ValidationErrorKind> {{"
|
|
)?;
|
|
writeln!(w, " use super::validator::*;")?;
|
|
writeln!(w, " let bytes = ctx.bytes;")?;
|
|
writeln!(w, " match opcode {{")?;
|
|
|
|
for (i, op) in ops.iter().enumerate() {
|
|
let layout = layouts.get(&op.name).expect("layout missing for op");
|
|
let arrays: Vec<&Field> = op.fields.iter().filter(|f| f.is_array).collect();
|
|
let has_array = !arrays.is_empty();
|
|
|
|
// Map each count field's name to the array it sizes, so we can skip
|
|
// the count field when emitting scalar checks (it gets read alongside
|
|
// the array bound below) and avoid duplicate work.
|
|
let mut count_field_names: std::collections::HashSet<String> = std::collections::HashSet::new();
|
|
for af in &arrays {
|
|
count_field_names.insert(find_count_field_name(op, af));
|
|
}
|
|
|
|
let op_name = &op.name;
|
|
writeln!(w, " {i} => {{ // {op_name}")?;
|
|
|
|
for f in &op.fields {
|
|
if f.is_array {
|
|
continue;
|
|
}
|
|
if f.name == "m_type" || f.name == "m_strict" || f.name == "m_length" {
|
|
continue;
|
|
}
|
|
if count_field_names.contains(&f.name) {
|
|
continue;
|
|
}
|
|
let offset = *layout.field_offsets.get(&f.name).expect("missing field offset");
|
|
emit_scalar_field_check(&mut w, &f.name, &f.ty, offset)?;
|
|
}
|
|
|
|
if has_array {
|
|
let m_length_offset = *layout
|
|
.field_offsets
|
|
.get("m_length")
|
|
.expect("variable-length op missing m_length");
|
|
// All variable-length ops in Bytecode.def carry exactly one trailing
|
|
// array; if that ever changes, this loop validates each independently.
|
|
for af in &arrays {
|
|
let array_offset = *layout.field_offsets.get(&af.name).expect("missing array offset");
|
|
let count_field = find_count_field_name(op, af);
|
|
let count_offset = *layout
|
|
.field_offsets
|
|
.get(&count_field)
|
|
.expect("missing count field offset");
|
|
let elem_size = field_type_info(&af.ty).size;
|
|
|
|
writeln!(
|
|
w,
|
|
" let __m_length = read_u32(bytes, at + {m_length_offset}) as usize;"
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
" let __count = read_u32(bytes, at + {count_offset}) as usize;"
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
" let __array_bytes = __count.saturating_mul({elem_size});"
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
" if {array_offset}usize.saturating_add(__array_bytes) > __m_length {{"
|
|
)?;
|
|
writeln!(w, " return Err(ValidationErrorKind::InvalidLength);")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " let __array_off = at + {array_offset};")?;
|
|
writeln!(w, " for __i in 0..__count {{")?;
|
|
writeln!(w, " let __off = __array_off + __i * {elem_size};")?;
|
|
emit_array_elem_check(&mut w, &af.ty)?;
|
|
writeln!(w, " }}")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
}
|
|
|
|
writeln!(w, " _ => {{}}")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " Ok(())")?;
|
|
writeln!(w, "}}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_opcode_enum(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(
|
|
w,
|
|
"/// Bytecode opcode (u8), matching the C++ `Instruction::Type` enum."
|
|
)?;
|
|
writeln!(w, "#[derive(Debug, Clone, Copy, PartialEq, Eq)]")?;
|
|
writeln!(w, "#[repr(u8)]")?;
|
|
writeln!(w, "pub enum OpCode {{")?;
|
|
for (i, op) in ops.iter().enumerate() {
|
|
writeln!(w, " {} = {},", op.name, i)?;
|
|
}
|
|
writeln!(w, "}}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_instruction_enum(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, "/// A bytecode instruction with typed fields.")?;
|
|
writeln!(w, "///")?;
|
|
writeln!(w, "/// Each variant corresponds to one C++ instruction class.")?;
|
|
writeln!(
|
|
w,
|
|
"/// During codegen, instructions are stored as these typed variants."
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
"/// During flattening, they are serialized to bytes matching C++ layw."
|
|
)?;
|
|
writeln!(w, "#[derive(Debug, Clone)]")?;
|
|
writeln!(w, "pub enum Instruction {{")?;
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
if fields.is_empty() {
|
|
writeln!(w, " {},", op.name)?;
|
|
} else {
|
|
writeln!(w, " {} {{", op.name)?;
|
|
for f in &fields {
|
|
let info = field_type_info(&f.ty);
|
|
let r_name = rust_field_name(&f.name);
|
|
if f.is_array {
|
|
writeln!(w, " {}: Vec<{}>,", r_name, info.rust_type)?;
|
|
} else {
|
|
writeln!(w, " {}: {},", r_name, info.rust_type)?;
|
|
}
|
|
}
|
|
writeln!(w, " }},")?;
|
|
}
|
|
}
|
|
writeln!(w, "}}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_instruction_impl(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, "impl Instruction {{").unwrap();
|
|
generate_opcode_method(&mut w, ops)?;
|
|
generate_is_terminator_method(&mut w, ops)?;
|
|
generate_encode_method(&mut w, ops)?;
|
|
generate_encoded_size_method(&mut w, ops)?;
|
|
generate_visit_operands_method(&mut w, ops)?;
|
|
generate_visit_labels_method(&mut w, ops)?;
|
|
writeln!(w, " }}")?;
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_opcode_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, " pub fn opcode(&self) -> OpCode {{")?;
|
|
writeln!(w, " match self {{")?;
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
if fields.is_empty() {
|
|
writeln!(w, " Instruction::{} => OpCode::{},", op.name, op.name)?;
|
|
} else {
|
|
writeln!(
|
|
w,
|
|
" Instruction::{} {{ .. }} => OpCode::{},",
|
|
op.name, op.name
|
|
)?;
|
|
}
|
|
}
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_is_terminator_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, " pub fn is_terminator(&self) -> bool {{")?;
|
|
writeln!(w, " matches!(self, ")?;
|
|
let terminators: Vec<&OpDef> = ops.iter().filter(|op| op.is_terminator).collect();
|
|
for (i, op) in terminators.iter().enumerate() {
|
|
let sep = if i + 1 < terminators.len() { " |" } else { "" };
|
|
let fields = user_fields(op);
|
|
if fields.is_empty() {
|
|
writeln!(w, " Instruction::{}{}", op.name, sep)?;
|
|
} else {
|
|
writeln!(w, " Instruction::{} {{ .. }}{}", op.name, sep)?;
|
|
}
|
|
}
|
|
writeln!(w, " )")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_encoded_size_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, " /// Returns the encoded size of this instruction in bytes.")?;
|
|
writeln!(w, " pub fn encoded_size(&self) -> usize {{")?;
|
|
writeln!(w, " match self {{")?;
|
|
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
let has_array = op.fields.iter().any(|f| f.is_array);
|
|
|
|
if !has_array {
|
|
// Fixed-length: compute size statically
|
|
let mut offset: usize = 2; // header
|
|
for f in &op.fields {
|
|
if f.is_array || f.name == "m_type" || f.name == "m_strict" {
|
|
continue;
|
|
}
|
|
let info = field_type_info(&f.ty);
|
|
offset = round_up(offset, info.align);
|
|
offset += info.size;
|
|
}
|
|
let final_size = round_up(offset, 8);
|
|
let pat = if fields.is_empty() {
|
|
format!("Instruction::{}", op.name)
|
|
} else {
|
|
format!("Instruction::{} {{ .. }}", op.name)
|
|
};
|
|
writeln!(w, " {pat} => {final_size},")?;
|
|
} else {
|
|
// Variable-length: depends on array size
|
|
// Compute fixed part size
|
|
let mut fixed_offset: usize = 2;
|
|
for f in &op.fields {
|
|
if f.is_array || f.name == "m_type" || f.name == "m_strict" {
|
|
continue;
|
|
}
|
|
let info = field_type_info(&f.ty);
|
|
fixed_offset = round_up(fixed_offset, info.align);
|
|
fixed_offset += info.size;
|
|
}
|
|
|
|
// Find the array field and its element size
|
|
let Some(array_field) = op.fields.iter().find(|f| f.is_array) else {
|
|
continue;
|
|
};
|
|
let info = field_type_info(&array_field.ty);
|
|
let arr_name = rust_field_name(&array_field.name);
|
|
// C++ computes m_length as:
|
|
// round_up(alignof(void*), sizeof(*this) + sizeof(elem) * count)
|
|
// sizeof(*this) = round_up(fixed_offset, STRUCT_ALIGN) due to alignas(void*).
|
|
let sizeof_this = round_up(fixed_offset, STRUCT_ALIGN);
|
|
|
|
// Bind only the array field
|
|
let bindings: Vec<String> = fields
|
|
.iter()
|
|
.map(|f| {
|
|
let rname = rust_field_name(&f.name);
|
|
if rname == arr_name {
|
|
rname
|
|
} else {
|
|
format!("{rname}: _")
|
|
}
|
|
})
|
|
.collect();
|
|
writeln!(
|
|
w,
|
|
" Instruction::{} {{ {} }} => {{",
|
|
op.name,
|
|
bindings.join(", ")
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
" let base = {} + {}.len() * {};",
|
|
sizeof_this, arr_name, info.size
|
|
)?;
|
|
writeln!(w, " (base + 7) & !7 // round up to 8")?;
|
|
writeln!(w, " }}")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_encode_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(
|
|
w,
|
|
" /// Encode this instruction into bytes matching the C++ struct layout."
|
|
)?;
|
|
writeln!(w, " pub fn encode(&self, strict: bool, buf: &mut Vec<u8>) {{")?;
|
|
writeln!(w, " let start = buf.len();")?;
|
|
writeln!(w, " match self {{")?;
|
|
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
let has_array = op.fields.iter().any(|f| f.is_array);
|
|
let has_m_length = op.fields.iter().any(|f| f.name == "m_length");
|
|
|
|
// Generate match arm with field bindings
|
|
if fields.is_empty() {
|
|
writeln!(w, " Instruction::{} => {{", op.name)?;
|
|
} else {
|
|
let bindings: Vec<String> = fields.iter().map(|f| rust_field_name(&f.name)).collect();
|
|
writeln!(
|
|
w,
|
|
" Instruction::{} {{ {} }} => {{",
|
|
op.name,
|
|
bindings.join(", ")
|
|
)?;
|
|
}
|
|
|
|
// Write header: opcode (u8) + strict (u8) = 2 bytes
|
|
writeln!(w, " buf.push(OpCode::{} as u8);", op.name)?;
|
|
writeln!(w, " buf.push(strict as u8);")?;
|
|
|
|
// Track offset for C++ struct layw.
|
|
// We iterate ALL fields (including m_type, m_strict, m_length) for
|
|
// accurate alignment but only emit writes for user fields.
|
|
let mut offset: usize = 2;
|
|
|
|
// Iterate all non-array fields in declaration order
|
|
for f in &op.fields {
|
|
if f.is_array {
|
|
continue;
|
|
}
|
|
// m_type and m_strict are already written as the header
|
|
if f.name == "m_type" || f.name == "m_strict" {
|
|
continue;
|
|
}
|
|
|
|
let info = field_type_info(&f.ty);
|
|
|
|
// Pad to alignment
|
|
let aligned_offset = round_up(offset, info.align);
|
|
let pad = aligned_offset - offset;
|
|
if pad > 0 {
|
|
writeln!(w, " buf.extend_from_slice(&[0u8; {pad}]);")?;
|
|
}
|
|
offset = aligned_offset;
|
|
|
|
if f.name == "m_length" {
|
|
// Write placeholder (patched at end for variable-length instructions)
|
|
writeln!(
|
|
w,
|
|
" buf.extend_from_slice(&[0u8; 4]); // m_length placeholder"
|
|
)?;
|
|
} else {
|
|
let rname = rust_field_name(&f.name);
|
|
emit_field_write(&mut w, &rname, info.kind, false)?;
|
|
}
|
|
offset += info.size;
|
|
}
|
|
|
|
// Write trailing array elements
|
|
if has_array {
|
|
// sizeof(*this) in C++ = round_up(fixed_offset, STRUCT_ALIGN)
|
|
let sizeof_this = round_up(offset, STRUCT_ALIGN);
|
|
|
|
for f in &op.fields {
|
|
if !f.is_array {
|
|
continue;
|
|
}
|
|
let info = field_type_info(&f.ty);
|
|
let rname = rust_field_name(&f.name);
|
|
|
|
// Pad before first element if needed
|
|
let aligned_offset = round_up(offset, info.align);
|
|
let pad = aligned_offset - offset;
|
|
if pad > 0 {
|
|
writeln!(w, " buf.extend_from_slice(&[0u8; {pad}]);")?;
|
|
}
|
|
|
|
writeln!(w, " for item in {rname} {{")?;
|
|
emit_field_write(&mut w, "item", info.kind, true)?;
|
|
writeln!(w, " }}")?;
|
|
|
|
// Compute target size matching C++:
|
|
// round_up(STRUCT_ALIGN, sizeof(*this) + count * elem_size)
|
|
writeln!(
|
|
w,
|
|
" let target = ({} + {}.len() * {} + 7) & !7;",
|
|
sizeof_this, rname, info.size
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
" while (buf.len() - start) < target {{ buf.push(0); }}"
|
|
)?;
|
|
}
|
|
|
|
if has_m_length {
|
|
// Patch m_length: it's the first u32 field after the header
|
|
let m_length_offset = find_m_length_offset(&op.fields);
|
|
writeln!(w, " let total_len = (buf.len() - start) as u32;")?;
|
|
writeln!(
|
|
w,
|
|
" buf[start + {}..start + {}].copy_from_slice(&total_len.to_ne_bytes());",
|
|
m_length_offset,
|
|
m_length_offset + 4
|
|
)?;
|
|
}
|
|
} else {
|
|
// Fixed-length: pad statically
|
|
let final_size = round_up(offset, 8);
|
|
let tail_pad = final_size - offset;
|
|
if tail_pad > 0 {
|
|
writeln!(w, " buf.extend_from_slice(&[0u8; {tail_pad}]);")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Emit code to write a field value into `w`.
|
|
///
|
|
/// All bindings from pattern matching and loop iteration are references (`&T`).
|
|
/// Rust auto-derefs for method calls, but explicit `*` is needed for casts
|
|
/// and direct pushes of Copy types.
|
|
fn emit_field_write(
|
|
mut w: impl Write,
|
|
name: &str,
|
|
kind: &str,
|
|
is_loop_item: bool,
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let prefix = " ".repeat(if is_loop_item { 20 } else { 16 });
|
|
match kind {
|
|
"bool" => writeln!(w, "{prefix}buf.push(*{name} as u8);")?,
|
|
"u8" => writeln!(w, "{prefix}buf.push(*{name});")?,
|
|
"u32" => writeln!(w, "{prefix}buf.extend_from_slice(&{name}.to_ne_bytes());")?,
|
|
"u64" => writeln!(w, "{prefix}buf.extend_from_slice(&{name}.to_ne_bytes());")?,
|
|
"operand" => writeln!(w, "{prefix}buf.extend_from_slice(&{name}.raw().to_ne_bytes());")?,
|
|
"optional_operand" => {
|
|
writeln!(w, "{prefix}match {name} {{")?;
|
|
writeln!(
|
|
w,
|
|
"{prefix} Some(op) => buf.extend_from_slice(&op.raw().to_ne_bytes()),"
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
"{prefix} None => buf.extend_from_slice(&Operand::INVALID.to_ne_bytes()),"
|
|
)?;
|
|
writeln!(w, "{prefix}}}")?;
|
|
}
|
|
"label" => writeln!(w, "{prefix}buf.extend_from_slice(&{name}.0.to_ne_bytes());")?,
|
|
"optional_label" => {
|
|
// C++ Optional<Label> layw: u32 value, bool has_value, 3 bytes padding = 8 bytes total
|
|
writeln!(w, "{prefix}match {name} {{")?;
|
|
writeln!(w, "{prefix} Some(lbl) => {{")?;
|
|
writeln!(w, "{prefix} buf.extend_from_slice(&lbl.0.to_ne_bytes());")?;
|
|
writeln!(w, "{prefix} buf.push(1); buf.push(0); buf.push(0); buf.push(0);")?;
|
|
writeln!(w, "{prefix} }}")?;
|
|
writeln!(w, "{prefix} None => {{")?;
|
|
writeln!(w, "{prefix} buf.extend_from_slice(&0u32.to_ne_bytes());")?;
|
|
writeln!(w, "{prefix} buf.push(0); buf.push(0); buf.push(0); buf.push(0);")?;
|
|
writeln!(w, "{prefix} }}")?;
|
|
writeln!(w, "{prefix}}}")?;
|
|
}
|
|
"u32_newtype" => writeln!(w, "{prefix}buf.extend_from_slice(&{name}.0.to_ne_bytes());")?,
|
|
"optional_u32_newtype" => {
|
|
writeln!(w, "{prefix}match {name} {{")?;
|
|
writeln!(
|
|
w,
|
|
"{prefix} Some(idx) => buf.extend_from_slice(&idx.0.to_ne_bytes()),"
|
|
)?;
|
|
writeln!(
|
|
w,
|
|
"{prefix} None => buf.extend_from_slice(&0xFFFF_FFFFu32.to_ne_bytes()),"
|
|
)?;
|
|
writeln!(w, "{prefix}}}")?;
|
|
}
|
|
"env_coord" => {
|
|
writeln!(w, "{prefix}buf.extend_from_slice(&{name}.hops.to_ne_bytes());")?;
|
|
writeln!(w, "{prefix}buf.extend_from_slice(&{name}.index.to_ne_bytes());")?;
|
|
}
|
|
other => panic!("Unknown encoding kind: {other}"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_visit_operands_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, " /// Visit all `Operand` fields (for operand rewriting).")?;
|
|
writeln!(
|
|
w,
|
|
" pub fn visit_operands(&mut self, visitor: &mut dyn FnMut(&mut Operand)) {{"
|
|
)?;
|
|
writeln!(w, " match self {{")?;
|
|
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
let operand_fields: Vec<&&Field> = fields
|
|
.iter()
|
|
.filter(|f| f.ty == "Operand" || f.ty == "Optional<Operand>")
|
|
.collect();
|
|
|
|
if operand_fields.is_empty() {
|
|
let pat = if fields.is_empty() {
|
|
format!("Instruction::{}", op.name)
|
|
} else {
|
|
format!("Instruction::{} {{ .. }}", op.name)
|
|
};
|
|
writeln!(w, " {pat} => {{}}")?;
|
|
continue;
|
|
}
|
|
|
|
// Bind the operand fields
|
|
let bindings: Vec<String> = fields
|
|
.iter()
|
|
.map(|f| {
|
|
let rname = rust_field_name(&f.name);
|
|
if f.ty == "Operand" || f.ty == "Optional<Operand>" {
|
|
rname
|
|
} else {
|
|
format!("{rname}: _")
|
|
}
|
|
})
|
|
.collect();
|
|
writeln!(
|
|
w,
|
|
" Instruction::{} {{ {} }} => {{",
|
|
op.name,
|
|
bindings.join(", ")
|
|
)?;
|
|
|
|
for f in &operand_fields {
|
|
let rname = rust_field_name(&f.name);
|
|
if f.is_array {
|
|
if f.ty == "Optional<Operand>" {
|
|
writeln!(
|
|
w,
|
|
" for op in {rname}.iter_mut().flatten() {{ visitor(op); }}"
|
|
)?;
|
|
} else {
|
|
writeln!(w, " for item in {rname}.iter_mut() {{ visitor(item); }}")?;
|
|
}
|
|
} else if f.ty == "Optional<Operand>" {
|
|
writeln!(w, " if let Some(op) = {rname} {{ visitor(op); }}")?;
|
|
} else {
|
|
writeln!(w, " visitor({rname});")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " }}")?;
|
|
writeln!(w)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_visit_labels_method(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box<dyn std::error::Error>> {
|
|
writeln!(w, " /// Visit all `Label` fields (for label linking).")?;
|
|
writeln!(
|
|
w,
|
|
" pub fn visit_labels(&mut self, visitor: &mut dyn FnMut(&mut Label)) {{"
|
|
)?;
|
|
writeln!(w, " match self {{")?;
|
|
|
|
for op in ops {
|
|
let fields = user_fields(op);
|
|
let label_fields: Vec<&&Field> = fields
|
|
.iter()
|
|
.filter(|f| f.ty == "Label" || f.ty == "Optional<Label>")
|
|
.collect();
|
|
|
|
if label_fields.is_empty() {
|
|
let pat = if fields.is_empty() {
|
|
format!("Instruction::{}", op.name)
|
|
} else {
|
|
format!("Instruction::{} {{ .. }}", op.name)
|
|
};
|
|
writeln!(w, " {pat} => {{}}")?;
|
|
continue;
|
|
}
|
|
|
|
let bindings: Vec<String> = fields
|
|
.iter()
|
|
.map(|f| {
|
|
let rname = rust_field_name(&f.name);
|
|
if f.ty == "Label" || f.ty == "Optional<Label>" {
|
|
rname
|
|
} else {
|
|
format!("{rname}: _")
|
|
}
|
|
})
|
|
.collect();
|
|
writeln!(
|
|
w,
|
|
" Instruction::{} {{ {} }} => {{",
|
|
op.name,
|
|
bindings.join(", ")
|
|
)?;
|
|
|
|
for f in &label_fields {
|
|
let rname = rust_field_name(&f.name);
|
|
if f.is_array {
|
|
if f.ty == "Optional<Label>" {
|
|
writeln!(w, " for item in {rname}.iter_mut() {{")?;
|
|
writeln!(w, " if let Some(lbl) = item {{ visitor(lbl); }}")?;
|
|
writeln!(w, " }}")?;
|
|
} else {
|
|
writeln!(w, " for item in {rname}.iter_mut() {{ visitor(item); }}")?;
|
|
}
|
|
} else if f.ty == "Optional<Label>" {
|
|
writeln!(w, " if let Some(lbl) = {rname} {{ visitor(lbl); }}")?;
|
|
} else {
|
|
writeln!(w, " visitor({rname});")?;
|
|
}
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
}
|
|
|
|
writeln!(w, " }}")?;
|
|
writeln!(w, " }}")?;
|
|
Ok(())
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
|
|
let def_path = manifest_dir.join("../Bytecode/Bytecode.def");
|
|
|
|
println!("cargo:rerun-if-changed={}", def_path.display());
|
|
println!("cargo:rerun-if-changed=build.rs");
|
|
println!("cargo:rerun-if-changed=cbindgen.toml");
|
|
println!("cargo:rerun-if-env-changed=FFI_OUTPUT_DIR");
|
|
println!("cargo:rerun-if-changed=src");
|
|
|
|
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
|
|
|
let ffi_out_dir = env::var("FFI_OUTPUT_DIR")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|_| out_dir.clone());
|
|
|
|
cbindgen::generate(manifest_dir).map_or_else(
|
|
|error| match error {
|
|
cbindgen::Error::ParseSyntaxError { .. } => {}
|
|
e => panic!("{e:?}"),
|
|
},
|
|
|bindings| {
|
|
let header_path = out_dir.join("RustFFI.h");
|
|
bindings.write_to_file(&header_path);
|
|
|
|
if ffi_out_dir != out_dir {
|
|
bindings.write_to_file(ffi_out_dir.join("RustFFI.h"));
|
|
}
|
|
},
|
|
);
|
|
|
|
let file = fs::OpenOptions::new()
|
|
.write(true)
|
|
.create(true)
|
|
.truncate(true) // empties contents of the file
|
|
.open(out_dir.join("instruction_generated.rs"))?;
|
|
let content = fs::read_to_string(&def_path).expect("Failed to read Bytecode.def");
|
|
let ops = bytecode_def::parse_bytecode_def(&content);
|
|
generate_rust_code(file, &ops)
|
|
}
|