use crate::InstructionAddressMap;
use crate::address_map::ADDRMAP_BLOCK_SIZE;
use crate::bytes::{write_sleb, write_uleb};
use crate::obj::ELF_WASMTIME_ADDRMAP;
use crate::prelude::*;
use object::write::{Object, StandardSegment};
use object::{LittleEndian, SectionKind, U32};
use std::ops::Range;
#[derive(Default)]
pub struct AddressMapSection {
entries: usize,
block_index: Vec<[U32<LittleEndian>; 2]>,
block_bodies: Vec<u8>,
pending: Vec<(u32, u32)>,
last_offset: u32,
}
impl AddressMapSection {
pub fn push(&mut self, func: Range<u64>, instrs: &[InstructionAddressMap]) {
let func_start = u32::try_from(func.start).unwrap();
let func_end = u32::try_from(func.end).unwrap();
let mut last_srcloc = None;
for map in instrs {
let pos = func_start + map.code_offset;
assert!(pos >= self.last_offset);
self.last_offset = pos;
let srcloc = map.srcloc.file_offset().unwrap_or(u32::MAX);
if Some(srcloc) == last_srcloc {
continue;
}
last_srcloc = Some(srcloc);
self.pending.push((pos, srcloc));
self.entries += 1;
if self.pending.len() == ADDRMAP_BLOCK_SIZE {
self.seal_block();
}
}
self.last_offset = func_end;
}
fn seal_block(&mut self) {
let first_offset = match self.pending.first() {
Some((offset, _)) => *offset,
None => return,
};
let block_pos = u32::try_from(self.block_bodies.len()).unwrap();
self.block_index.push([
U32::new(LittleEndian, first_offset),
U32::new(LittleEndian, block_pos),
]);
let mut prev_offset = first_offset;
let mut prev_pos = None;
for (offset, pos) in self.pending.drain(..) {
let delta = offset - prev_offset;
prev_offset = offset;
let is_none = pos == u32::MAX;
write_uleb(
&mut self.block_bodies,
(u64::from(delta) << 1) | u64::from(is_none),
);
if is_none {
continue;
}
match prev_pos {
None => write_uleb(&mut self.block_bodies, u64::from(pos)),
Some(prev) => write_sleb(&mut self.block_bodies, i64::from(pos) - i64::from(prev)),
}
prev_pos = Some(pos);
}
}
pub fn append_to(self, obj: &mut Object) {
let section = obj.add_section(
obj.segment_name(StandardSegment::Data).to_vec(),
ELF_WASMTIME_ADDRMAP.as_bytes().to_vec(),
SectionKind::ReadOnlyData,
);
obj.append_section_data(section, &self.finish(), 1);
}
fn finish(mut self) -> Vec<u8> {
self.seal_block();
let entries = u32::try_from(self.entries).unwrap();
let num_blocks = u32::try_from(self.block_index.len()).unwrap();
let mut ret = Vec::with_capacity(8 + self.block_index.len() * 8 + self.block_bodies.len());
ret.extend_from_slice(&entries.to_le_bytes());
ret.extend_from_slice(&num_blocks.to_le_bytes());
ret.extend_from_slice(object::bytes_of_slice(&self.block_index));
ret.extend_from_slice(&self.block_bodies);
ret
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FilePos, iterate_address_map, lookup_file_pos};
fn encode(funcs: &[(Range<u64>, &[InstructionAddressMap])]) -> Vec<u8> {
let mut builder = AddressMapSection::default();
for (func, instrs) in funcs {
builder.push(func.clone(), instrs);
}
builder.finish()
}
fn map(code_offset: u32, srcloc: FilePos) -> InstructionAddressMap {
InstructionAddressMap {
srcloc,
code_offset,
}
}
#[test]
fn smoke() {
let section = encode(&[]);
assert_eq!(lookup_file_pos(§ion, 0), None);
assert_eq!(iterate_address_map(§ion).unwrap().count(), 0);
let section = encode(&[(0..0x100, &[])]);
assert_eq!(lookup_file_pos(§ion, 0x50), None);
assert_eq!(iterate_address_map(§ion).unwrap().count(), 0);
let section = encode(&[(
0..0x100,
&[
map(10, FilePos::new(100)),
map(20, FilePos::none()),
map(30, FilePos::new(90)),
],
)]);
assert_eq!(lookup_file_pos(§ion, 9), None);
assert_eq!(lookup_file_pos(§ion, 10), Some(FilePos::new(100)));
assert_eq!(lookup_file_pos(§ion, 19), Some(FilePos::new(100)));
assert_eq!(lookup_file_pos(§ion, 20), Some(FilePos::none()));
assert_eq!(lookup_file_pos(§ion, 29), Some(FilePos::none()));
assert_eq!(lookup_file_pos(§ion, 30), Some(FilePos::new(90)));
assert_eq!(lookup_file_pos(§ion, 0x1000), Some(FilePos::new(90)));
}
#[test]
fn many_blocks() {
let maps = (0..1000)
.map(|i| {
let srcloc = match i % 3 {
0 => FilePos::none(),
1 => FilePos::new(20_000 + i),
_ => FilePos::new(20_000 - i),
};
map(i * 3, srcloc)
})
.collect::<Vec<_>>();
let section = encode(&[(0..0x10000, &maps)]);
let decoded = iterate_address_map(§ion).unwrap().collect::<Vec<_>>();
assert_eq!(decoded.len(), maps.len());
for (map, (offset, pos)) in maps.iter().zip(&decoded) {
assert_eq!(*offset, map.code_offset);
assert_eq!(*pos, map.srcloc);
}
for map in &maps {
let offset = usize::try_from(map.code_offset).unwrap();
assert_eq!(lookup_file_pos(§ion, offset), Some(map.srcloc));
assert_eq!(lookup_file_pos(§ion, offset + 1), Some(map.srcloc));
}
}
}