/* * Copyright (c) 2026-present, the Ladybird developers. * * SPDX-License-Identifier: BSD-2-Clause */ //! Shared Bytecode.def parser and layout computation. //! //! Used by both `Libraries/LibJS/Rust/build.rs` (bytecode codegen) and //! `Libraries/LibJS/AsmIntGen` (assembly interpreter codegen) to ensure a single source //! of truth for instruction field offsets and sizes. use std::collections::HashMap; #[derive(Debug, Clone)] pub struct Field { pub name: String, pub ty: String, pub is_array: bool, } #[derive(Debug, Clone)] pub struct OpDef { pub name: String, pub fields: Vec, pub is_terminator: bool, } pub struct FieldType { pub rust_type: &'static str, pub align: usize, pub size: usize, pub kind: &'static str, } impl From<(&'static str, usize, usize, &'static str)> for FieldType { fn from(v: (&'static str, usize, usize, &'static str)) -> Self { Self { rust_type: v.0, align: v.1, size: v.2, kind: v.3, } } } /// The alignment of the C++ Instruction base class (`alignas(void*)`). /// On 64-bit: alignof(void*) = 8. pub const STRUCT_ALIGN: usize = 8; pub fn parse_bytecode_def(content: &str) -> Vec { 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; } 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 } pub fn field_type_info(ty: &str) -> FieldType { match ty { "bool" => ("bool", 1, 1, "bool"), "u32" => ("u32", 4, 4, "u32"), "u64" => ("u64", 8, 8, "u64"), "Operand" => ("Operand", 4, 4, "operand"), "Optional" => ("Option", 4, 4, "optional_operand"), "Label" => ("Label", 4, 4, "label"), "Optional