use crate::bytes::{read_sleb, read_uleb};
use core::fmt;
use object::{Bytes, LittleEndian, U32};
use serde_derive::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct InstructionAddressMap {
pub srcloc: FilePos,
pub code_offset: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FilePos(u32);
impl FilePos {
pub fn new(pos: u32) -> FilePos {
assert!(pos != u32::MAX);
FilePos(pos)
}
pub fn none() -> FilePos {
FilePos(u32::MAX)
}
#[inline]
pub fn is_none(&self) -> bool {
*self == FilePos::none()
}
pub fn file_offset(self) -> Option<u32> {
if self.0 == u32::MAX {
None
} else {
Some(self.0)
}
}
}
impl Default for FilePos {
fn default() -> FilePos {
FilePos::none()
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComponentPC(u32);
impl ComponentPC {
pub fn new(offset: u32) -> Self {
Self(offset)
}
pub fn raw(self) -> u32 {
self.0
}
pub fn to_module_pc(self, wasm_module_offset: u64) -> ModulePC {
let offset = u32::try_from(wasm_module_offset).unwrap();
ModulePC(self.0 - offset)
}
}
impl fmt::Debug for ComponentPC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ComponentPC({:#x})", self.0)
}
}
impl fmt::Display for ComponentPC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#x}", self.0)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ModulePC(u32);
impl ModulePC {
pub fn new(offset: u32) -> Self {
Self(offset)
}
pub fn raw(self) -> u32 {
self.0
}
}
impl fmt::Debug for ModulePC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ModulePC({:#x})", self.0)
}
}
impl fmt::Display for ModulePC {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#x}", self.0)
}
}
pub(crate) const ADDRMAP_BLOCK_SIZE: usize = 128;
#[derive(Clone, Copy)]
struct AddressMap<'a> {
entries: usize,
block_index: &'a [[U32<LittleEndian>; 2]],
block_bodies: &'a [u8],
}
impl<'a> AddressMap<'a> {
fn block_entries(&self, block_index: usize) -> Option<BlockEntries<'a>> {
let [first_offset, block_pos] = self.block_index.get(block_index)?;
let first_offset = first_offset.get(LittleEndian);
let block_pos = block_pos.get(LittleEndian);
let block = self.block_bodies.get(usize::try_from(block_pos).ok()?..)?;
let remaining = core::cmp::min(
ADDRMAP_BLOCK_SIZE,
self.entries.checked_sub(block_index * ADDRMAP_BLOCK_SIZE)?,
);
Some(BlockEntries {
block,
prev_offset: first_offset,
prev_pos: None,
remaining,
})
}
}
struct BlockEntries<'a> {
block: &'a [u8],
prev_offset: u32,
prev_pos: Option<u32>,
remaining: usize,
}
impl Iterator for BlockEntries<'_> {
type Item = (u32, FilePos);
fn next(&mut self) -> Option<(u32, FilePos)> {
self.remaining = self.remaining.checked_sub(1)?;
let token = read_uleb(&mut self.block)?;
let delta = u32::try_from(token >> 1).ok()?;
let cur_offset = self.prev_offset.checked_add(delta)?;
self.prev_offset = cur_offset;
if token & 1 != 0 {
return Some((cur_offset, FilePos::none()));
}
let pos = match self.prev_pos {
None => u32::try_from(read_uleb(&mut self.block)?).ok()?,
Some(prev) => {
let delta = read_sleb(&mut self.block)?;
prev.checked_add_signed(i32::try_from(delta).ok()?)?
}
};
self.prev_pos = Some(pos);
Some((cur_offset, FilePos(pos)))
}
}
fn parse(section: &[u8]) -> Option<AddressMap<'_>> {
let mut section = Bytes(section);
let entries = section.read::<U32<LittleEndian>>().ok()?;
let entries = usize::try_from(entries.get(LittleEndian)).ok()?;
let num_blocks = section.read::<U32<LittleEndian>>().ok()?;
let num_blocks = usize::try_from(num_blocks.get(LittleEndian)).ok()?;
let (block_index, block_bodies) =
object::slice_from_bytes::<[U32<LittleEndian>; 2]>(section.0, num_blocks).ok()?;
Some(AddressMap {
entries,
block_index,
block_bodies,
})
}
pub fn lookup_file_pos(section: &[u8], offset: usize) -> Option<FilePos> {
let section = parse(section)?;
let offset = u32::try_from(offset).ok()?;
let block = section
.block_index
.partition_point(|[first_offset, _]| first_offset.get(LittleEndian) <= offset)
.checked_sub(1)?;
let mut pos = None;
for (entry_offset, entry_pos) in section.block_entries(block)? {
if entry_offset > offset {
break;
}
pos = Some(entry_pos);
}
pos
}
pub fn iterate_address_map<'a>(
section: &'a [u8],
) -> Option<impl Iterator<Item = (u32, FilePos)> + 'a> {
let section = parse(section)?;
Some(
(0..section.block_index.len())
.flat_map(move |block| section.block_entries(block).into_iter().flatten()),
)
}