//! Shared bytecode layout utilities, used by both compiler (emitter/linker) //! and the VM (verifier/loader). This ensures a single source of truth for //! how function ranges, instruction boundaries, and pc→function lookups are //! interpreted post-link. use crate::model::FunctionMeta; #[derive(Clone, Debug, PartialEq, Eq)] pub struct FunctionLayout { pub start: usize, pub end: usize, // exclusive } /// Precompute canonical [start, end) ranges for all functions. /// /// Contract: /// - Ranges are computed by sorting functions by `code_offset` (stable), /// then using the next function's start as the current end; the last /// function ends at `code_len_total`. /// - The returned vector is indexed by the original function indices. pub fn compute_function_layouts( functions: &[FunctionMeta], code_len_total: usize, ) -> Vec { // Build index array and sort by start offset (stable to preserve relative order). let mut idxs: Vec = (0..functions.len()).collect(); idxs.sort_by_key(|&i| functions[i].code_offset as usize); // Optional guard: offsets should be strictly increasing (duplicates are suspicious). for w in idxs.windows(2) { if let [a, b] = *w { let sa = functions[a].code_offset as usize; let sb = functions[b].code_offset as usize; debug_assert!( sa < sb, "Function code_offset must be strictly increasing: {} vs {} (indices {} and {})", sa, sb, a, b ); } } let mut out = vec![FunctionLayout { start: 0, end: 0 }; functions.len()]; for (pos, &i) in idxs.iter().enumerate() { let start = functions[i].code_offset as usize; let end = if pos + 1 < idxs.len() { functions[idxs[pos + 1]].code_offset as usize } else { code_len_total }; out[i] = FunctionLayout { start, end }; } out } #[cfg(test)] mod tests { use super::*; fn build_funcs(offsets: &[usize], lens: Option<&[usize]>) -> Vec { let mut v = Vec::new(); for (i, off) in offsets.iter().copied().enumerate() { let len_u32 = lens.and_then(|ls| ls.get(i).copied()).unwrap_or(0) as u32; v.push(FunctionMeta { code_offset: off as u32, code_len: len_u32, param_slots: 0, local_slots: 0, return_slots: 0, max_stack_slots: 0, }); } v } #[test] fn compute_function_layouts_end_is_next_start() { // Synthetic functions with known offsets: 0, 10, 25; total_len = 40 let funcs = build_funcs(&[0, 10, 25], None); let layouts = compute_function_layouts(&funcs, 40); assert_eq!(layouts.len(), 3); assert_eq!(layouts[0], FunctionLayout { start: 0, end: 10 }); assert_eq!(layouts[1], FunctionLayout { start: 10, end: 25 }); assert_eq!(layouts[2], FunctionLayout { start: 25, end: 40 }); for i in 0..3 { let l = &layouts[i]; assert_eq!( l.end - l.start, (funcs.get(i + 1).map(|n| n.code_offset as usize).unwrap_or(40)) - (funcs[i].code_offset as usize) ); } } }