/* * 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/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 std::env; use std::fs; use std::io::Write; use std::path::PathBuf; // --------------------------------------------------------------------------- // .def file parser (mirrors Meta/libjs_bytecode_def.py) // --------------------------------------------------------------------------- #[derive(Debug, Clone)] struct Field { name: String, ty: String, is_array: bool, } #[derive(Debug)] struct OpDef { name: String, fields: Vec, is_terminator: bool, } fn parse_bytecode_def(path: &std::path::Path) -> Vec { let content = fs::read_to_string(path).expect("Failed to read Bytecode.def"); let mut ops = Vec::new(); let mut current: Option = None; let mut in_op = false; for raw_line in content.lines() { let stripped = raw_line.trim(); if stripped.is_empty() || stripped.starts_with("//") || stripped.starts_with('#') { continue; } if stripped.starts_with("op ") { assert!(!in_op, "Nested op blocks"); in_op = true; let rest = stripped.strip_prefix("op ").unwrap().trim(); let name = if let Some(idx) = rest.find('<') { rest[..idx].trim().to_string() } else { rest.to_string() }; current = Some(OpDef { name, fields: Vec::new(), is_terminator: false, }); continue; } if stripped == "endop" { assert!(in_op && current.is_some(), "endop without op"); ops.push(current.take().unwrap()); in_op = false; continue; } if !in_op { continue; } if stripped.starts_with('@') { if stripped == "@terminator" { current.as_mut().unwrap().is_terminator = true; } // @nothrow is C++-only, ignore continue; } let (lhs, rhs) = stripped.split_once(':').expect("Malformed field line"); let field_name = lhs.trim().to_string(); let mut field_type = rhs.trim().to_string(); let is_array = field_type.ends_with("[]"); if is_array { field_type = field_type[..field_type.len() - 2].trim().to_string(); } current.as_mut().unwrap().fields.push(Field { name: field_name, ty: field_type, is_array, }); } assert!(!in_op, "Unclosed op block"); // Remove the base "Instruction" definition (not an actual opcode). ops.retain(|op| op.name != "Instruction"); ops } struct FieldType { r_type: &'static str, align: usize, size: usize, kind: &'static str, } impl From<(&'static str, usize, usize, &'static str)> for FieldType { fn from(v: (&'static str, usize, usize, &'static str)) -> Self { Self { r_type: v.0, align: v.1, size: v.2, kind: v.3, } } } fn field_type_info(ty: &str) -> FieldType { match ty { "bool" => ("bool", 1, 1, "bool"), "u32" => ("u32", 4, 4, "u32"), "Operand" => ("Operand", 4, 4, "operand"), "Optional" => ("Option", 4, 4, "optional_operand"), "Label" => ("Label", 4, 4, "label"), "Optional