wasmtime_environ/compile/address_map.rs
1//! Data structures to provide transformation of the source
2
3use crate::InstructionAddressMap;
4use crate::address_map::ADDRMAP_BLOCK_SIZE;
5use crate::bytes::{write_sleb, write_uleb};
6use crate::obj::ELF_WASMTIME_ADDRMAP;
7use crate::prelude::*;
8use object::write::{Object, StandardSegment};
9use object::{LittleEndian, SectionKind, U32};
10use std::ops::Range;
11
12/// Builder for the address map section of a wasmtime compilation image.
13///
14/// This builder is used to conveniently build the `ELF_WASMTIME_ADDRMAP`
15/// section by compilers, and provides utilities to directly insert the results
16/// into an `Object`.
17///
18/// # Section format
19///
20/// The section encodes a sequence of `(text_offset, file_pos)` entries, sorted
21/// by `text_offset`, where `text_offset` is the location of an instruction
22/// relative to the start of the text section and `file_pos` is the offset
23/// within the original wasm file of the instruction it was compiled from, or
24/// the `FilePos::none()` sentinel for generated code with no wasm-level
25/// source. Unlike the trap section each entry here describes a range of pcs:
26/// an entry covers addresses from its own `text_offset` up to the next
27/// entry's. This format is optimized to enable cheap (O(log n)) lookup given
28/// an offset to find a source location while also being relatively compact as
29/// this is included in all modules by default and is, uncompressed, the
30/// largest of Wasmtime's metadata sections. To satisfy this the section is
31/// encoded as two major pieces: an index and a sequence of blocks.
32///
33/// The index is used to perform a binary search given a particular
34/// `text_offset` to find a particular block. The index stores text offsets as
35/// well as byte offsets in the "block bodies" section. Once a block is found
36/// each block contains up to `ADDRMAP_BLOCK_SIZE` entries encoded next to each
37/// other. Blocks take up a variable width of bytes to encode. More information
38/// on decoding each block is below, but the general layout of the section looks
39/// like:
40///
41/// ```text
42/// ┌───────────────────────────────────┐
43/// │ entry_count: u32 │
44/// │ block_count: u32 │
45/// ├───────────────────────────────────┤
46/// │ block index │
47/// │ ┌───────────────────────────────┐ │
48/// │ │ first_offset: u32 │ │ one pair per block, sorted by
49/// │ │ block_pos: u32 │ │ `first_offset`; `block_pos` is
50/// │ ├───────────────────────────────┤ │ relative to the start of the
51/// │ │ ... │ │ block bodies area below
52/// │ └───────────────────────────────┘ │
53/// ├───────────────────────────────────┤
54/// │ block bodies │
55/// │ ┌───────────────────────────────┐ │
56/// │ │ entry: uleb token │ │ one entry per instruction
57/// │ │ [file_pos: uleb] │ │ mapping in the block,
58/// │ ├───────────────────────────────┤ │ `ADDRMAP_BLOCK_SIZE` max
59/// │ │ ... │ │
60/// │ └───────────────────────────────┘ │
61/// │ ┌───────────────────────────────┐ │
62/// │ │ ... │ │
63/// │ └───────────────────────────────┘ │
64/// └───────────────────────────────────┘
65/// ```
66///
67/// * `entry_count` is the total number of entries (pc/srcloc combos) in the
68/// section and `block_count` is the number of blocks, `ceil(entry_count /
69/// ADDRMAP_BLOCK_SIZE)`.
70/// * In the block index, `first_offset` is the `text_offset` of the block's
71/// first entry and `block_pos` is the position of the block's body,
72/// relative to the start of the bodies area (i.e. the end of the index).
73/// * Each entry is a uleb-encoded token `(pc_delta << 1) | pos_is_none`.
74/// Here `pc_delta` is this entry's `text_offset` minus the previous
75/// entry's (the first entry's delta is relative to the block's
76/// `first_offset` and is therefore 0). If `pos_is_none` is set this entry's
77/// file position is `FilePos::none()` and nothing else follows the token.
78/// Otherwise the token is followed by the entry's file position: the first
79/// non-none position in a block is uleb-encoded absolutely and subsequent
80/// positions are sleb-encoded deltas from the previous non-none position.
81/// Delta chains restart at each block so blocks can be decoded
82/// independently.
83///
84/// Lookup (`lookup_file_pos`) binary searches the fixed-width block index for
85/// the last block whose `first_offset` is `<=` the pc in question, then
86/// linearly decodes at most `ADDRMAP_BLOCK_SIZE` entries of that block's body
87/// looking for the last entry whose `text_offset` is `<=` the pc.
88///
89/// This encoding leans on a few properties of address map metadata:
90/// consecutive instructions are close together (pc deltas almost always fit in
91/// a single-byte leb), consecutive source locations are close together and
92/// mostly increasing (position deltas almost always fit in a single-byte
93/// sleb), and entries with no source location are common (a quarter of all
94/// entries) and cost only the token's flag bit. This is all in service of shrinking the
95/// previous 8 bytes per entry (u32 offset, u32 file position) to roughly 2
96/// bytes per entry in practice.
97///
98/// Note that at this time this section has an alignment of 1. Additionally
99/// due to the 32-bit offsets in the block index this doesn't support images
100/// >= 4GB.
101#[derive(Default)]
102pub struct AddressMapSection {
103 entries: usize,
104 block_index: Vec<[U32<LittleEndian>; 2]>,
105 block_bodies: Vec<u8>,
106 pending: Vec<(u32, u32)>,
107 last_offset: u32,
108}
109
110impl AddressMapSection {
111 /// Pushes a new set of instruction mapping information for a function added
112 /// in the executable.
113 ///
114 /// The `func` argument here is the range of the function, relative to the
115 /// start of the text section in the executable. The `instrs` provided are
116 /// the descriptors for instructions in the function and their various
117 /// mappings back to original source positions.
118 ///
119 /// This is required to be called for `func` values that are strictly
120 /// increasing in addresses (e.g. as the object is built). Additionally the
121 /// `instrs` map must be sorted based on code offset in the native text
122 /// section.
123 pub fn push(&mut self, func: Range<u64>, instrs: &[InstructionAddressMap]) {
124 // NB: for now this only supports <=4GB text sections in object files.
125 // Alternative schemes will need to be created for >32-bit offsets to
126 // avoid making this section overly large.
127 let func_start = u32::try_from(func.start).unwrap();
128 let func_end = u32::try_from(func.end).unwrap();
129
130 let mut last_srcloc = None;
131 for map in instrs {
132 // Sanity-check to ensure that functions are pushed in-order, otherwise
133 // the encoded blocks won't be sorted which is our goal.
134 let pos = func_start + map.code_offset;
135 assert!(pos >= self.last_offset);
136 self.last_offset = pos;
137
138 // Drop duplicate instruction mappings that match what was
139 // previously pushed into the array since the representation used
140 // here will naturally cover `pos` with the previous entry.
141 let srcloc = map.srcloc.file_offset().unwrap_or(u32::MAX);
142 if Some(srcloc) == last_srcloc {
143 continue;
144 }
145 last_srcloc = Some(srcloc);
146
147 self.pending.push((pos, srcloc));
148 self.entries += 1;
149 if self.pending.len() == ADDRMAP_BLOCK_SIZE {
150 self.seal_block();
151 }
152 }
153 self.last_offset = func_end;
154 }
155
156 /// Flushes `self.pending` into one encoded block, appending to the index
157 /// and data arrays.
158 fn seal_block(&mut self) {
159 let first_offset = match self.pending.first() {
160 Some((offset, _)) => *offset,
161 None => return,
162 };
163 let block_pos = u32::try_from(self.block_bodies.len()).unwrap();
164 self.block_index.push([
165 U32::new(LittleEndian, first_offset),
166 U32::new(LittleEndian, block_pos),
167 ]);
168
169 let mut prev_offset = first_offset;
170 let mut prev_pos = None;
171 for (offset, pos) in self.pending.drain(..) {
172 let delta = offset - prev_offset;
173 prev_offset = offset;
174 let is_none = pos == u32::MAX;
175 write_uleb(
176 &mut self.block_bodies,
177 (u64::from(delta) << 1) | u64::from(is_none),
178 );
179 if is_none {
180 continue;
181 }
182 match prev_pos {
183 // The first non-none position of a block is encoded absolutely
184 // and subsequent positions are deltas from the previous one,
185 // ensuring each block can be decoded independently.
186 None => write_uleb(&mut self.block_bodies, u64::from(pos)),
187 Some(prev) => write_sleb(&mut self.block_bodies, i64::from(pos) - i64::from(prev)),
188 }
189 prev_pos = Some(pos);
190 }
191 }
192
193 /// Finishes encoding this section into the `Object` provided.
194 pub fn append_to(self, obj: &mut Object) {
195 let section = obj.add_section(
196 obj.segment_name(StandardSegment::Data).to_vec(),
197 ELF_WASMTIME_ADDRMAP.as_bytes().to_vec(),
198 SectionKind::ReadOnlyData,
199 );
200
201 obj.append_section_data(section, &self.finish(), 1);
202 }
203
204 /// Finishes encoding and returns the raw section contents, as decoded by
205 /// `lookup_file_pos` and `iterate_address_map`.
206 fn finish(mut self) -> Vec<u8> {
207 self.seal_block();
208 let entries = u32::try_from(self.entries).unwrap();
209 let num_blocks = u32::try_from(self.block_index.len()).unwrap();
210 let mut ret = Vec::with_capacity(8 + self.block_index.len() * 8 + self.block_bodies.len());
211 ret.extend_from_slice(&entries.to_le_bytes());
212 ret.extend_from_slice(&num_blocks.to_le_bytes());
213 ret.extend_from_slice(object::bytes_of_slice(&self.block_index));
214 ret.extend_from_slice(&self.block_bodies);
215 ret
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::{FilePos, iterate_address_map, lookup_file_pos};
223
224 fn encode(funcs: &[(Range<u64>, &[InstructionAddressMap])]) -> Vec<u8> {
225 let mut builder = AddressMapSection::default();
226 for (func, instrs) in funcs {
227 builder.push(func.clone(), instrs);
228 }
229 builder.finish()
230 }
231
232 fn map(code_offset: u32, srcloc: FilePos) -> InstructionAddressMap {
233 InstructionAddressMap {
234 srcloc,
235 code_offset,
236 }
237 }
238
239 #[test]
240 fn smoke() {
241 let section = encode(&[]);
242 assert_eq!(lookup_file_pos(§ion, 0), None);
243 assert_eq!(iterate_address_map(§ion).unwrap().count(), 0);
244
245 let section = encode(&[(0..0x100, &[])]);
246 assert_eq!(lookup_file_pos(§ion, 0x50), None);
247 assert_eq!(iterate_address_map(§ion).unwrap().count(), 0);
248
249 let section = encode(&[(
250 0..0x100,
251 &[
252 map(10, FilePos::new(100)),
253 map(20, FilePos::none()),
254 map(30, FilePos::new(90)),
255 ],
256 )]);
257 // pcs before the first entry have no mapping
258 assert_eq!(lookup_file_pos(§ion, 9), None);
259 // each entry covers pcs from its own offset until the next entry
260 assert_eq!(lookup_file_pos(§ion, 10), Some(FilePos::new(100)));
261 assert_eq!(lookup_file_pos(§ion, 19), Some(FilePos::new(100)));
262 assert_eq!(lookup_file_pos(§ion, 20), Some(FilePos::none()));
263 assert_eq!(lookup_file_pos(§ion, 29), Some(FilePos::none()));
264 assert_eq!(lookup_file_pos(§ion, 30), Some(FilePos::new(90)));
265 // ... with the last entry covering everything afterwards
266 assert_eq!(lookup_file_pos(§ion, 0x1000), Some(FilePos::new(90)));
267 }
268
269 #[test]
270 fn many_blocks() {
271 // Enough entries to span multiple blocks, mixing forward and backward
272 // source-position movement with `FilePos::none()` entries, including
273 // at block boundaries.
274 let maps = (0..1000)
275 .map(|i| {
276 let srcloc = match i % 3 {
277 0 => FilePos::none(),
278 1 => FilePos::new(20_000 + i),
279 _ => FilePos::new(20_000 - i),
280 };
281 map(i * 3, srcloc)
282 })
283 .collect::<Vec<_>>();
284 let section = encode(&[(0..0x10000, &maps)]);
285
286 let decoded = iterate_address_map(§ion).unwrap().collect::<Vec<_>>();
287 assert_eq!(decoded.len(), maps.len());
288 for (map, (offset, pos)) in maps.iter().zip(&decoded) {
289 assert_eq!(*offset, map.code_offset);
290 assert_eq!(*pos, map.srcloc);
291 }
292
293 // Both an entry's exact pc and a pc inside its bucket resolve to it.
294 for map in &maps {
295 let offset = usize::try_from(map.code_offset).unwrap();
296 assert_eq!(lookup_file_pos(§ion, offset), Some(map.srcloc));
297 assert_eq!(lookup_file_pos(§ion, offset + 1), Some(map.srcloc));
298 }
299 }
300}