use crate::obj::ELF_WASMTIME_STACK_MAP;
use crate::prelude::*;
use cranelift_bitset::CompoundBitSet;
use object::write::{Object, StandardSegment};
use object::{LittleEndian, SectionKind, U32Bytes};
#[derive(Default)]
pub struct StackMapSection {
pcs: Vec<U32Bytes<LittleEndian>>,
pointers_to_stack_map: Vec<U32Bytes<LittleEndian>>,
stack_map_data: Vec<U32Bytes<LittleEndian>>,
last_offset: u32,
}
impl StackMapSection {
pub fn push(
&mut self,
code_offset: u64,
frame_size: u32,
frame_offsets: impl ExactSizeIterator<Item = u32>,
) {
let code_offset = u32::try_from(code_offset).unwrap();
assert!(code_offset >= self.last_offset);
self.last_offset = code_offset;
if frame_offsets.len() == 0 {
return;
}
self.pcs.push(U32Bytes::new(LittleEndian, code_offset));
self.pointers_to_stack_map.push(U32Bytes::new(
LittleEndian,
u32::try_from(self.stack_map_data.len()).unwrap(),
));
self.stack_map_data
.push(U32Bytes::new(LittleEndian, frame_size));
let mut bits = CompoundBitSet::<u32>::default();
for offset in frame_offsets {
assert!(offset % 4 == 0);
bits.insert((offset / 4) as usize);
}
let count = bits.iter_scalars().count();
self.stack_map_data
.push(U32Bytes::new(LittleEndian, count as u32));
for scalar in bits.iter_scalars() {
self.stack_map_data
.push(U32Bytes::new(LittleEndian, scalar.0));
}
}
pub fn append_to(self, obj: &mut Object) {
if self.pcs.is_empty() {
return;
}
let section = obj.add_section(
obj.segment_name(StandardSegment::Data).to_vec(),
ELF_WASMTIME_STACK_MAP.as_bytes().to_vec(),
SectionKind::ReadOnlyData,
);
let amt = u32::try_from(self.pcs.len()).unwrap();
obj.append_section_data(section, &amt.to_le_bytes(), 1);
obj.append_section_data(section, object::bytes_of_slice(&self.pcs), 1);
obj.append_section_data(
section,
object::bytes_of_slice(&self.pointers_to_stack_map),
1,
);
obj.append_section_data(section, object::bytes_of_slice(&self.stack_map_data), 1);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stack_map::StackMap;
use object::{Object, ObjectSection};
fn roundtrip(maps: &[(u64, u32, &[u32])]) {
let mut section = StackMapSection::default();
for (pc, frame, offsets) in maps {
println!("append {pc}");
section.push(*pc, *frame, offsets.iter().copied());
}
let mut object = object::write::Object::new(
object::BinaryFormat::Elf,
object::Architecture::X86_64,
object::Endianness::Little,
);
section.append_to(&mut object);
let elf = object.write().unwrap();
let image = object::File::parse(&elf[..]).unwrap();
let data = image
.sections()
.find(|s| s.name().ok() == Some(ELF_WASMTIME_STACK_MAP))
.unwrap()
.data()
.unwrap();
for (pc, frame, offsets) in maps {
println!("lookup {pc}");
let map = match StackMap::lookup(*pc as u32, data) {
Some(map) => map,
None => {
assert!(offsets.is_empty());
continue;
}
};
assert_eq!(map.frame_size(), *frame);
let map_offsets = map.offsets().collect::<Vec<_>>();
assert_eq!(map_offsets, *offsets);
}
let mut expected = maps.iter();
'outer: for (pc, map) in StackMap::iter(data).unwrap() {
while let Some((expected_pc, expected_frame, expected_offsets)) = expected.next() {
if expected_offsets.is_empty() {
continue;
}
assert_eq!(*expected_pc, u64::from(pc));
assert_eq!(*expected_frame, map.frame_size());
let offsets = map.offsets().collect::<Vec<_>>();
assert_eq!(offsets, *expected_offsets);
continue 'outer;
}
panic!("didn't find {pc:#x} in expected list");
}
assert!(expected.next().is_none());
}
#[test]
fn roundtrip_many() {
roundtrip(&[(0, 4, &[0])]);
roundtrip(&[
(0, 4, &[0]),
(4, 200, &[0, 4, 20, 180]),
(200, 20, &[12]),
(600, 0, &[]),
(800, 20, &[0, 4, 8, 12, 16]),
(1200, 2000, &[1800, 1804, 1808, 1900]),
]);
}
}