use std::fmt::Debug;
use miden_core::utils::ToHex;
use midenc_hir::SmallVec;
use midenc_session::diagnostics::Report;
const WORD_SIZE_BYTES: usize = 16;
#[derive(Clone)]
pub struct ResolvedDataSegment {
pub offset: u32,
pub data: Vec<u8>,
pub readonly: bool,
}
impl ResolvedDataSegment {
fn end_offset(&self) -> u32 {
self.offset + self.data.len() as u32
}
pub fn pad_to_word_alignment(&mut self) {
let remainder = self.data.len() % WORD_SIZE_BYTES;
if remainder != 0 {
let padding_size = WORD_SIZE_BYTES - remainder;
self.data.resize(self.data.len() + padding_size, 0);
}
}
}
impl Debug for ResolvedDataSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResolvedDataSegment")
.field("offset", &self.offset)
.field("size", &self.data.len())
.field("readonly", &self.readonly)
.field("data", &self.data.to_hex())
.finish()
}
}
fn validate_no_overlaps(segments: &[ResolvedDataSegment]) -> Result<(), Report> {
assert!(segments.is_sorted_by_key(|s| s.offset), "Segments must be sorted by offset");
for window in segments.windows(2) {
let current = &window[0];
let next = &window[1];
if current.end_offset() > next.offset {
let error_msg = format!(
"Data segments overlap: segment at offset {:#x} (size {}) overlaps with segment \
at offset {:#x}",
current.offset,
current.data.len(),
next.offset
);
return Err(Report::msg(error_msg));
}
}
Ok(())
}
fn merge_metadata(segments: &[ResolvedDataSegment]) -> bool {
let mut all_readonly = true;
for segment in segments.iter() {
all_readonly = all_readonly && segment.readonly;
}
all_readonly
}
fn copy_segments_to_buffer(segments: &[ResolvedDataSegment], buffer: &mut [u8], base_offset: u32) {
for segment in segments {
let relative_offset = (segment.offset - base_offset) as usize;
let end = relative_offset + segment.data.len();
buffer[relative_offset..end].copy_from_slice(&segment.data);
}
}
pub fn merge_data_segments(
mut segments: SmallVec<[ResolvedDataSegment; 2]>,
) -> Result<Option<ResolvedDataSegment>, Report> {
if segments.is_empty() {
return Ok(None);
}
if segments.len() == 1 {
let mut segment = segments.pop().unwrap();
segment.pad_to_word_alignment();
return Ok(Some(segment));
}
segments.sort_by_key(|s| s.offset);
validate_no_overlaps(&segments)?;
let base_offset = segments[0].offset;
let last_segment = segments.last().unwrap();
let initial_size = (last_segment.end_offset() - base_offset) as usize;
let mut merged_data = vec![0u8; initial_size];
copy_segments_to_buffer(&segments, &mut merged_data, base_offset);
let all_readonly = merge_metadata(&segments);
let mut merged_segment = ResolvedDataSegment {
offset: base_offset,
data: merged_data,
readonly: all_readonly,
};
merged_segment.pad_to_word_alignment();
Ok(Some(merged_segment))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_segments() {
let segments = SmallVec::new();
let merged = merge_data_segments(segments).unwrap();
assert!(merged.is_none());
}
#[test]
fn test_single_segment_with_padding() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 10,
data: vec![1, 2, 3],
readonly: true,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
assert_eq!(merged.offset, 10);
assert_eq!(merged.data.len(), 16);
assert_eq!(&merged.data[0..3], &[1, 2, 3]);
assert_eq!(&merged.data[3..], &[0; 13]);
}
#[test]
fn test_padding_bytes_are_zeros() {
let test_cases = vec![
(vec![0x42], 1usize, 15), (vec![0xaa, 0xbb], 2, 14), (vec![0x11; 5], 5, 11), (vec![0xff; 13], 13, 3), (vec![0x77; 15], 15, 1), (vec![0x88; 16], 16, 0), (vec![0x99; 17], 17, 15), (vec![0xcc; 31], 31, 1), ];
for (data, data_len, expected_padding) in test_cases {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 1000,
data: data.clone(),
readonly: false,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
let result_data = &merged.data;
let expected_total_len = data_len.next_multiple_of(16); assert_eq!(result_data.len(), expected_total_len);
assert_eq!(&result_data[0..data_len], &data[..]);
if expected_padding > 0 {
let padding_slice = &result_data[data_len..];
assert_eq!(padding_slice.len(), expected_padding);
assert!(
padding_slice.iter().all(|&b| b == 0),
"Padding bytes should all be zero for {data_len} bytes of data"
);
}
}
}
#[test]
fn test_data_accessible_at_original_offsets() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 100,
data: vec![0xaa, 0xbb, 0xcc, 0xdd],
readonly: false,
});
segments.push(ResolvedDataSegment {
offset: 110,
data: vec![0x11, 0x22],
readonly: false,
});
segments.push(ResolvedDataSegment {
offset: 120,
data: vec![0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa],
readonly: false,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
assert_eq!(merged.offset, 100);
let data = &merged.data;
assert_eq!(&data[0..4], &[0xaa, 0xbb, 0xcc, 0xdd]);
assert_eq!(&data[4..10], &[0, 0, 0, 0, 0, 0]);
assert_eq!(&data[10..12], &[0x11, 0x22]);
assert_eq!(&data[12..20], &[0, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(&data[20..26], &[0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa]);
assert_eq!(data.len(), 32);
assert_eq!(&data[26..], &[0; 6]);
}
#[test]
fn test_merge_segments_with_gap() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 0,
data: vec![1, 2, 3, 4],
readonly: true,
});
segments.push(ResolvedDataSegment {
offset: 8,
data: vec![5, 6, 7, 8],
readonly: true,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
assert_eq!(merged.offset, 0);
assert_eq!(merged.data.len(), 16);
assert_eq!(merged.data, vec![1, 2, 3, 4, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0]);
}
#[test]
fn test_overlapping_segments_error() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 0,
data: vec![1, 2, 3, 4, 5, 6],
readonly: true,
});
segments.push(ResolvedDataSegment {
offset: 4,
data: vec![7, 8],
readonly: false,
});
let result = merge_data_segments(segments);
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = format!("{err}");
assert!(err_msg.contains("Data segments overlap"));
}
#[test]
fn test_p2id_case() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 1048576, data: vec![0; 44], readonly: true,
});
segments.push(ResolvedDataSegment {
offset: 1048620, data: vec![0; 76], readonly: false,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
assert_eq!(merged.offset, 1048576);
assert_eq!(merged.data.len(), 128);
assert!(!merged.readonly);
}
#[test]
fn test_already_aligned_size() {
let mut segments = SmallVec::new();
segments.push(ResolvedDataSegment {
offset: 100,
data: vec![1; 32], readonly: true,
});
let merged = merge_data_segments(segments).unwrap().unwrap();
assert_eq!(merged.offset, 100);
assert_eq!(merged.data.len(), 32); }
}