99 lines
3.3 KiB
Rust
99 lines
3.3 KiB
Rust
//! 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<FunctionLayout> {
|
|
// Build index array and sort by start offset (stable to preserve relative order).
|
|
let mut idxs: Vec<usize> = (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<FunctionMeta> {
|
|
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)
|
|
);
|
|
}
|
|
}
|
|
}
|