1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct OpcodeHandlerEntry {
41 pub handler_offset: u32,
44 pub handler_arity: u8,
47 pub side_effecting: bool,
50 pub control_flow: bool,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum PackError {
60 OffsetTooLarge {
62 opcode: usize,
64 offset: u32,
66 },
67 ArityTooLarge {
69 opcode: usize,
71 arity: u8,
73 },
74 Allocation {
76 requested: usize,
78 source: String,
80 },
81}
82
83#[must_use]
85#[inline]
86pub const fn packed_dispatch_table_len(entries_len: usize) -> usize {
87 entries_len
88}
89
90pub fn pack_dispatch_table(entries: &[OpcodeHandlerEntry]) -> Result<Vec<u32>, PackError> {
100 let mut out = Vec::new();
101 pack_dispatch_table_into(entries, &mut out)?;
102 Ok(out)
103}
104
105pub fn pack_dispatch_table_into(
115 entries: &[OpcodeHandlerEntry],
116 out: &mut Vec<u32>,
117) -> Result<(), PackError> {
118 for (idx, entry) in entries.iter().enumerate() {
119 if entry.handler_offset >= (1u32 << 24) {
120 return Err(PackError::OffsetTooLarge {
121 opcode: idx,
122 offset: entry.handler_offset,
123 });
124 }
125 if entry.handler_arity > 15 {
126 return Err(PackError::ArityTooLarge {
127 opcode: idx,
128 arity: entry.handler_arity,
129 });
130 }
131 }
132 let len = packed_dispatch_table_len(entries.len());
133 vyre_foundation::allocation::reserve_exact_cleared(out, len).map_err(|source| PackError::Allocation {
134 requested: len,
135 source: source.to_string(),
136 })?;
137 out.extend(entries.iter().map(|entry| {
138 let mut packed: u32 = entry.handler_offset & 0x00FF_FFFF;
139 packed |= (u32::from(entry.handler_arity) & 0xF) << 24;
140 if entry.side_effecting {
141 packed |= 1 << 28;
142 }
143 if entry.control_flow {
144 packed |= 1 << 29;
145 }
146 packed
147 }));
148 Ok(())
149}
150
151#[must_use]
153pub fn pack_entry(entry: OpcodeHandlerEntry) -> u32 {
154 let mut packed = entry.handler_offset & 0x00FF_FFFF;
155 packed |= (u32::from(entry.handler_arity) & 0xF) << 24;
156 if entry.side_effecting {
157 packed |= 1 << 28;
158 }
159 if entry.control_flow {
160 packed |= 1 << 29;
161 }
162 packed
163}
164
165#[must_use]
169pub fn unpack_entry(packed: u32) -> OpcodeHandlerEntry {
170 OpcodeHandlerEntry {
171 handler_offset: packed & 0x00FF_FFFF,
172 handler_arity: ((packed >> 24) & 0xF) as u8,
173 side_effecting: (packed >> 28) & 0x1 == 1,
174 control_flow: (packed >> 29) & 0x1 == 1,
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn round_trip_preserves_entry_fields() {
184 let entry = OpcodeHandlerEntry {
185 handler_offset: 0x123456,
186 handler_arity: 7,
187 side_effecting: true,
188 control_flow: false,
189 };
190 let packed = pack_dispatch_table(&[entry]).expect("Fix: pack must succeed");
191 assert_eq!(packed.len(), 1);
192 let recovered = unpack_entry(packed[0]);
193 assert_eq!(recovered, entry, "round-trip must preserve every field");
194 assert_eq!(unpack_entry(pack_entry(entry)), entry);
195 }
196
197 #[test]
198 fn pack_into_reuses_output_and_is_transactional_on_invalid_entry() {
199 let entries = [
200 OpcodeHandlerEntry {
201 handler_offset: 1,
202 handler_arity: 2,
203 side_effecting: false,
204 control_flow: false,
205 },
206 OpcodeHandlerEntry {
207 handler_offset: 3,
208 handler_arity: 4,
209 side_effecting: true,
210 control_flow: true,
211 },
212 ];
213 let mut out = Vec::with_capacity(8);
214 out.extend_from_slice(&[u32::MAX; 8]);
215 let ptr = out.as_ptr();
216
217 pack_dispatch_table_into(&entries, &mut out).unwrap();
218
219 assert_eq!(
220 out,
221 entries.iter().copied().map(pack_entry).collect::<Vec<_>>()
222 );
223 assert_eq!(out.as_ptr(), ptr);
224 let before = out.clone();
225 let bad = [OpcodeHandlerEntry {
226 handler_offset: 1 << 24,
227 handler_arity: 0,
228 side_effecting: false,
229 control_flow: false,
230 }];
231 assert!(matches!(
232 pack_dispatch_table_into(&bad, &mut out),
233 Err(PackError::OffsetTooLarge { .. })
234 ));
235 assert_eq!(out, before);
236 }
237
238 #[test]
239 fn round_trip_handles_all_flag_combinations() {
240 for side_effecting in [false, true] {
241 for control_flow in [false, true] {
242 let entry = OpcodeHandlerEntry {
243 handler_offset: 42,
244 handler_arity: 3,
245 side_effecting,
246 control_flow,
247 };
248 let packed = pack_dispatch_table(&[entry]).unwrap();
249 assert_eq!(unpack_entry(packed[0]), entry);
250 }
251 }
252 }
253
254 #[test]
255 fn pack_rejects_offset_at_field_boundary() {
256 let entry = OpcodeHandlerEntry {
257 handler_offset: 1u32 << 24, handler_arity: 0,
259 side_effecting: false,
260 control_flow: false,
261 };
262 match pack_dispatch_table(&[entry]) {
263 Err(PackError::OffsetTooLarge { opcode: 0, offset }) => {
264 assert_eq!(offset, 1u32 << 24);
265 }
266 other => panic!("expected OffsetTooLarge at the 24-bit boundary; got {other:?}"),
267 }
268 }
269
270 #[test]
271 fn pack_rejects_arity_at_field_boundary() {
272 let entry = OpcodeHandlerEntry {
273 handler_offset: 0,
274 handler_arity: 16, side_effecting: false,
276 control_flow: false,
277 };
278 match pack_dispatch_table(&[entry]) {
279 Err(PackError::ArityTooLarge { opcode: 0, arity }) => {
280 assert_eq!(arity, 16);
281 }
282 other => panic!("expected ArityTooLarge at the 4-bit boundary; got {other:?}"),
283 }
284 }
285
286 #[test]
287 fn pack_preserves_per_entry_index_in_error() {
288 let mut entries = vec![
290 OpcodeHandlerEntry {
291 handler_offset: 0,
292 handler_arity: 0,
293 side_effecting: false,
294 control_flow: false,
295 };
296 10
297 ];
298 entries[7].handler_offset = 1u32 << 25; match pack_dispatch_table(&entries) {
300 Err(PackError::OffsetTooLarge { opcode: 7, .. }) => {}
301 other => panic!("expected error at opcode 7; got {other:?}"),
302 }
303 }
304
305 #[test]
306 fn pack_empty_table_returns_empty_vec() {
307 let packed = pack_dispatch_table(&[]).expect("Fix: empty pack must succeed");
308 assert!(packed.is_empty());
309 }
310
311 #[test]
312 fn pack_into_reuses_existing_capacity() {
313 let entries = [
314 OpcodeHandlerEntry {
315 handler_offset: 8,
316 handler_arity: 2,
317 side_effecting: false,
318 control_flow: true,
319 },
320 OpcodeHandlerEntry {
321 handler_offset: 16,
322 handler_arity: 3,
323 side_effecting: true,
324 control_flow: false,
325 },
326 ];
327 let mut out = Vec::with_capacity(64);
328 let before = out.capacity();
329 pack_dispatch_table_into(&entries, &mut out).expect("Fix: pack_into must succeed");
330 assert_eq!(out.len(), entries.len());
331 assert_eq!(
332 out.capacity(),
333 before,
334 "pack_into must reuse caller-owned capacity"
335 );
336 assert_eq!(unpack_entry(out[0]), entries[0]);
337 assert_eq!(unpack_entry(out[1]), entries[1]);
338 }
339
340 #[test]
341 fn required_len_matches_entry_count() {
342 assert_eq!(packed_dispatch_table_len(0), 0);
343 assert_eq!(packed_dispatch_table_len(256), 256);
344 }
345
346 #[test]
347 fn pack_full_256_opcode_table_succeeds() {
348 let entries: Vec<_> = (0..256u32)
350 .map(|opcode| OpcodeHandlerEntry {
351 handler_offset: opcode * 16,
352 handler_arity: (opcode % 4) as u8,
353 side_effecting: opcode % 2 == 0,
354 control_flow: opcode % 8 == 0,
355 })
356 .collect();
357 let packed = pack_dispatch_table(&entries).expect("Fix: full 256-table must pack");
358 assert_eq!(packed.len(), 256);
359 assert_eq!(unpack_entry(packed[0]), entries[0]);
361 assert_eq!(unpack_entry(packed[127]), entries[127]);
362 assert_eq!(unpack_entry(packed[255]), entries[255]);
363 }
364
365 #[test]
366 fn handler_arity_zero_packs_cleanly() {
367 let entry = OpcodeHandlerEntry {
368 handler_offset: 100,
369 handler_arity: 0,
370 side_effecting: false,
371 control_flow: false,
372 };
373 let packed = pack_dispatch_table(&[entry]).unwrap();
374 assert_eq!(packed[0], 100, "arity=0 + flags=0 packs as just the offset");
375 }
376}