/* * 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 `_count`, fall back to /// `_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> { 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_validate_instruction(&mut w, ops)?; Ok(()) } fn generate_num_opcodes_const(mut w: impl Write, ops: &[OpDef]) -> Result<(), Box> { 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> { 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 {{" )?; 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 emit_scalar_field_check( mut w: impl Write, field_name: &str, ty: &str, offset: usize, ) -> Result<(), Box> { match ty { "Operand" => writeln!(w, " validate_operand(read_u32(bytes, at + {offset}), ctx)?;")?, "Optional" => writeln!( w, " validate_optional_operand(read_u32(bytes, at + {offset}), ctx)?;" )?, "Label" => writeln!(w, " validate_label(read_u32(bytes, at + {offset}), ctx)?;")?, "Optional