plotnik_lib/bytecode/
header.rs

1//! Bytecode file header (64 bytes).
2
3use super::{MAGIC, VERSION};
4
5/// Header flags (bit field).
6pub mod flags {
7    /// Bit 0: If set, bytecode is linked (instructions contain NodeTypeId/NodeFieldId).
8    /// If clear, bytecode is unlinked (instructions contain StringId references).
9    pub const LINKED: u16 = 0x0001;
10}
11
12/// File header - first 64 bytes of the bytecode file.
13///
14/// Note: TypeMeta sub-section counts are stored in the TypeMetaHeader,
15/// not in the main header. See type_meta.rs for details.
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17#[repr(C, align(64))]
18pub struct Header {
19    /// Magic bytes: b"PTKQ"
20    pub magic: [u8; 4],
21    /// Format version (currently 1)
22    pub version: u32,
23    /// CRC32 checksum of everything after the header
24    pub checksum: u32,
25    /// Total file size in bytes
26    pub total_size: u32,
27
28    // Section offsets (absolute byte offsets)
29    pub str_blob_offset: u32,
30    pub str_table_offset: u32,
31    pub node_types_offset: u32,
32    pub node_fields_offset: u32,
33    pub trivia_offset: u32,
34    pub type_meta_offset: u32,
35    pub entrypoints_offset: u32,
36    pub transitions_offset: u32,
37
38    // Element counts (type counts are in TypeMetaHeader at type_meta_offset)
39    pub str_table_count: u16,
40    pub node_types_count: u16,
41    pub node_fields_count: u16,
42    pub trivia_count: u16,
43    pub entrypoints_count: u16,
44    pub transitions_count: u16,
45    /// Header flags (see `flags` module for bit definitions).
46    pub flags: u16,
47    /// Padding to maintain 64-byte size.
48    pub(crate) _pad: u16,
49}
50
51const _: () = assert!(std::mem::size_of::<Header>() == 64);
52
53impl Default for Header {
54    fn default() -> Self {
55        Self {
56            magic: MAGIC,
57            version: VERSION,
58            checksum: 0,
59            total_size: 0,
60            str_blob_offset: 0,
61            str_table_offset: 0,
62            node_types_offset: 0,
63            node_fields_offset: 0,
64            trivia_offset: 0,
65            type_meta_offset: 0,
66            entrypoints_offset: 0,
67            transitions_offset: 0,
68            str_table_count: 0,
69            node_types_count: 0,
70            node_fields_count: 0,
71            trivia_count: 0,
72            entrypoints_count: 0,
73            transitions_count: 0,
74            flags: 0,
75            _pad: 0,
76        }
77    }
78}
79
80impl Header {
81    /// Decode header from 64 bytes.
82    pub fn from_bytes(bytes: &[u8]) -> Self {
83        assert!(bytes.len() >= 64, "header too short");
84
85        Self {
86            magic: [bytes[0], bytes[1], bytes[2], bytes[3]],
87            version: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
88            checksum: u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
89            total_size: u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
90            str_blob_offset: u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
91            str_table_offset: u32::from_le_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]),
92            node_types_offset: u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
93            node_fields_offset: u32::from_le_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]),
94            trivia_offset: u32::from_le_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
95            type_meta_offset: u32::from_le_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]),
96            entrypoints_offset: u32::from_le_bytes([bytes[40], bytes[41], bytes[42], bytes[43]]),
97            transitions_offset: u32::from_le_bytes([bytes[44], bytes[45], bytes[46], bytes[47]]),
98            str_table_count: u16::from_le_bytes([bytes[48], bytes[49]]),
99            node_types_count: u16::from_le_bytes([bytes[50], bytes[51]]),
100            node_fields_count: u16::from_le_bytes([bytes[52], bytes[53]]),
101            trivia_count: u16::from_le_bytes([bytes[54], bytes[55]]),
102            entrypoints_count: u16::from_le_bytes([bytes[56], bytes[57]]),
103            transitions_count: u16::from_le_bytes([bytes[58], bytes[59]]),
104            flags: u16::from_le_bytes([bytes[60], bytes[61]]),
105            _pad: u16::from_le_bytes([bytes[62], bytes[63]]),
106        }
107    }
108
109    /// Encode header to 64 bytes.
110    pub fn to_bytes(&self) -> [u8; 64] {
111        let mut bytes = [0u8; 64];
112        bytes[0..4].copy_from_slice(&self.magic);
113        bytes[4..8].copy_from_slice(&self.version.to_le_bytes());
114        bytes[8..12].copy_from_slice(&self.checksum.to_le_bytes());
115        bytes[12..16].copy_from_slice(&self.total_size.to_le_bytes());
116        bytes[16..20].copy_from_slice(&self.str_blob_offset.to_le_bytes());
117        bytes[20..24].copy_from_slice(&self.str_table_offset.to_le_bytes());
118        bytes[24..28].copy_from_slice(&self.node_types_offset.to_le_bytes());
119        bytes[28..32].copy_from_slice(&self.node_fields_offset.to_le_bytes());
120        bytes[32..36].copy_from_slice(&self.trivia_offset.to_le_bytes());
121        bytes[36..40].copy_from_slice(&self.type_meta_offset.to_le_bytes());
122        bytes[40..44].copy_from_slice(&self.entrypoints_offset.to_le_bytes());
123        bytes[44..48].copy_from_slice(&self.transitions_offset.to_le_bytes());
124        bytes[48..50].copy_from_slice(&self.str_table_count.to_le_bytes());
125        bytes[50..52].copy_from_slice(&self.node_types_count.to_le_bytes());
126        bytes[52..54].copy_from_slice(&self.node_fields_count.to_le_bytes());
127        bytes[54..56].copy_from_slice(&self.trivia_count.to_le_bytes());
128        bytes[56..58].copy_from_slice(&self.entrypoints_count.to_le_bytes());
129        bytes[58..60].copy_from_slice(&self.transitions_count.to_le_bytes());
130        bytes[60..62].copy_from_slice(&self.flags.to_le_bytes());
131        bytes[62..64].copy_from_slice(&self._pad.to_le_bytes());
132        bytes
133    }
134
135    pub fn validate_magic(&self) -> bool {
136        self.magic == MAGIC
137    }
138
139    pub fn validate_version(&self) -> bool {
140        self.version == VERSION
141    }
142
143    /// Returns true if the bytecode is linked (contains resolved grammar IDs).
144    pub fn is_linked(&self) -> bool {
145        self.flags & flags::LINKED != 0
146    }
147
148    /// Set the linked flag.
149    pub fn set_linked(&mut self, linked: bool) {
150        if linked {
151            self.flags |= flags::LINKED;
152        } else {
153            self.flags &= !flags::LINKED;
154        }
155    }
156}