use crate::TrapInformation;
use crate::bytes::write_uleb;
use crate::obj::ELF_WASMTIME_TRAPS;
use crate::prelude::*;
use crate::trap_encoding::TRAP_BLOCK_SIZE;
use object::write::{Object, StandardSegment};
use object::{LittleEndian, SectionKind, U32};
use std::ops::Range;
#[derive(Default)]
pub struct TrapEncodingBuilder {
entries: usize,
block_index: Vec<[U32<LittleEndian>; 2]>,
block_bodies: Vec<u8>,
pending: Vec<(u32, u8)>,
last_offset: u32,
}
impl TrapEncodingBuilder {
pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
let func_start = u32::try_from(func.start).unwrap();
let func_end = u32::try_from(func.end).unwrap();
assert!(func_start >= self.last_offset);
for info in traps {
let pos = func_start + info.code_offset;
assert!(pos >= self.last_offset);
self.pending.push((pos, info.trap_code.as_u8()));
self.entries += 1;
self.last_offset = pos;
if self.pending.len() == TRAP_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 body_pos = u32::try_from(self.block_bodies.len()).unwrap();
self.block_index.push([
U32::new(LittleEndian, first_offset),
U32::new(LittleEndian, body_pos),
]);
let default_code = most_common_code(&self.pending);
self.block_bodies.push(default_code);
let mut prev = first_offset;
for (pc, code) in self.pending.drain(..) {
let delta = pc - prev;
prev = pc;
let differs = code != default_code;
write_uleb(
&mut self.block_bodies,
(u64::from(delta) << 1) | u64::from(differs),
);
if differs {
self.block_bodies.push(code);
}
}
}
pub fn append_to(self, obj: &mut Object) {
let section = obj.add_section(
obj.segment_name(StandardSegment::Data).to_vec(),
ELF_WASMTIME_TRAPS.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
}
}
fn most_common_code(entries: &[(u32, u8)]) -> u8 {
let mut counts = [0u16; 256];
let mut best = entries[0].1;
for (_, code) in entries {
let count = &mut counts[usize::from(*code)];
*count += 1;
if *count > counts[usize::from(best)] {
best = *code;
}
}
best
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Trap, iterate_traps, lookup_trap_code};
fn encode(funcs: &[(Range<u64>, &[TrapInformation])]) -> Vec<u8> {
let mut builder = TrapEncodingBuilder::default();
for (func, traps) in funcs {
builder.push(func.clone(), traps);
}
builder.finish()
}
fn info(code_offset: u32, trap: Trap) -> TrapInformation {
TrapInformation {
code_offset,
trap_code: trap.into(),
}
}
#[test]
fn smoke() {
let section = encode(&[]);
assert_eq!(lookup_trap_code(§ion, 0), None);
assert_eq!(iterate_traps(§ion).unwrap().count(), 0);
let section = encode(&[(0..0x100, &[])]);
assert_eq!(lookup_trap_code(§ion, 0x50), None);
assert_eq!(iterate_traps(§ion).unwrap().count(), 0);
let section = encode(&[(
0..0x100,
&[
info(10, Trap::MemoryOutOfBounds),
info(20, Trap::StackOverflow),
],
)]);
assert_eq!(lookup_trap_code(§ion, 0x50), None);
assert_eq!(
lookup_trap_code(§ion, 10),
Some(Trap::MemoryOutOfBounds.into())
);
assert_eq!(
lookup_trap_code(§ion, 20),
Some(Trap::StackOverflow.into())
);
}
}