wasmtime_environ/compile/trap_encoding.rs
1use crate::TrapInformation;
2use crate::bytes::write_uleb;
3use crate::obj::ELF_WASMTIME_TRAPS;
4use crate::prelude::*;
5use crate::trap_encoding::TRAP_BLOCK_SIZE;
6use object::write::{Object, StandardSegment};
7use object::{LittleEndian, SectionKind, U32};
8use std::ops::Range;
9
10/// A helper structure to build the custom-encoded section of a wasmtime
11/// compilation image which encodes trap information.
12///
13/// This structure is incrementally fed the results of compiling individual
14/// functions and handles all the encoding internally, allowing usage of
15/// `lookup_trap_code` with the resulting section.
16///
17/// # Section format
18///
19/// The section encodes a sequence of `(text_offset, trap_code)` entries,
20/// sorted by `text_offset`, where `text_offset` is the location of a
21/// trapping instruction relative to the start of the text section and
22/// `trap_code` is the byte encoding of its `CompiledTrap`. This format is
23/// optimized to enable cheap (O(log n)) lookup given an offset to find a trap
24/// code while also being relatively compact as this is included in all modules
25/// by default. To satisfy this the section is encoded as two major pieces: an
26/// index and a sequence of blocks.
27///
28/// The index is used to perform a binary search given a particular
29/// `text_offset` to find a particular block. The index stores text offsets as
30/// well as byte offsets in the "block bodies" section. Once a block is found
31/// each block contains up to `TRAP_BLOCK_SIZE` entries encoded next to each
32/// other. Blocks take up a variable width of bytes to encode. More information
33/// on decoding each block is below, but the general layout of the section looks
34/// like:
35///
36/// ```text
37/// ┌───────────────────────────────────┐
38/// │ entry_count: u32 │
39/// │ block_count: u32 │
40/// ├───────────────────────────────────┤
41/// │ block index │
42/// │ ┌───────────────────────────────┐ │
43/// │ │ first_offset: u32 │ │ one pair per block, sorted by
44/// │ │ data_pos: u32 │ │ `first_offset`; `data_pos` is
45/// │ ├───────────────────────────────┤ │ relative to the start of the
46/// │ │ ... │ │ block bodies area below
47/// │ └───────────────────────────────┘ │
48/// ├───────────────────────────────────┤
49/// │ block bodies │
50/// │ ┌───────────────────────────────┐ │
51/// │ │ default_code: u8 │ │
52/// │ ├───────────────────────────────┤ │
53/// │ │ entry: uleb token │ │ one entry per trap in the
54/// │ │ [trap_code: u8] │ │ block, `TRAP_BLOCK_SIZE` max
55/// │ ├───────────────────────────────┤ │
56/// │ │ ... │ │
57/// │ └───────────────────────────────┘ │
58/// │ ┌───────────────────────────────┐ │
59/// │ │ ... │ │
60/// │ └───────────────────────────────┘ │
61/// └───────────────────────────────────┘
62/// ```
63///
64/// * `entry_count` is the total number of entries (pc/trap combos) in the
65/// section and `block_count` is the number of blocks, `ceil(entry_count /
66/// TRAP_BLOCK_SIZE)`.
67/// * In the block index, `first_offset` is the `text_offset` of the block's
68/// first entry and `data_pos` is the position of the block's body,
69/// relative to the start of the bodies area (i.e. the end of the index).
70/// * Each block body starts with `default_code`, the block's "default" trap
71/// code, chosen as the most common code among the block's entries.
72/// * Each entry is a uleb-encoded token `(pc_delta << 1) | code_differs`.
73/// Here `pc_delta` is this entry's `text_offset` minus the previous
74/// entry's (the first entry's delta is relative to the block's
75/// `first_offset` and is therefore 0). If `code_differs` is set the token
76/// is followed by one byte holding this entry's trap code, otherwise the
77/// entry has the block's default code.
78///
79/// Lookup (`lookup_trap_code`) binary searches the fixed-width block index
80/// for the last block whose `first_offset` is `<=` the pc in question, then
81/// linearly decodes at most `TRAP_BLOCK_SIZE` entries of that block's body
82/// looking for an exact match.
83///
84/// This encoding leans on two properties of trap metadata: consecutive trap
85/// sites are generally close together (pc deltas almost always fit in a
86/// single-byte leb) and most entries share one trap code (typically
87/// `MemoryOutOfBounds` for gc-less wasm), making explicit code bytes rare. This
88/// is all in service of shrinking the minimum 5 bytes per entry (u32 offset, u8
89/// code), to a bit more than one byte per entry in practice.
90///
91/// Note that at this time this section has an alignment of 1. Additionally
92/// due to the 32-bit offsets in the block index this doesn't support images
93/// >= 4GB.
94#[derive(Default)]
95pub struct TrapEncodingBuilder {
96 entries: usize,
97 block_index: Vec<[U32<LittleEndian>; 2]>,
98 block_bodies: Vec<u8>,
99 pending: Vec<(u32, u8)>,
100 last_offset: u32,
101}
102
103impl TrapEncodingBuilder {
104 /// Appends trap information about a function into this section.
105 ///
106 /// This function is called to describe traps for the `func` range
107 /// specified. The `func` offsets are specified relative to the text section
108 /// itself, and the `traps` offsets are specified relative to the start of
109 /// `func`.
110 ///
111 /// This is required to be called in-order for increasing ranges of `func`
112 /// to ensure the final array is properly sorted. Additionally `traps` must
113 /// be sorted.
114 pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
115 // NB: for now this only supports <=4GB text sections in object files.
116 // Alternative schemes will need to be created for >32-bit offsets to
117 // avoid making this section overly large.
118 let func_start = u32::try_from(func.start).unwrap();
119 let func_end = u32::try_from(func.end).unwrap();
120
121 // Sanity-check to ensure that functions are pushed in-order, otherwise
122 // the encoded blocks won't be sorted which is our goal.
123 assert!(func_start >= self.last_offset);
124
125 for info in traps {
126 let pos = func_start + info.code_offset;
127 assert!(pos >= self.last_offset);
128 self.pending.push((pos, info.trap_code.as_u8()));
129 self.entries += 1;
130 self.last_offset = pos;
131 if self.pending.len() == TRAP_BLOCK_SIZE {
132 self.seal_block();
133 }
134 }
135
136 self.last_offset = func_end;
137 }
138
139 /// Flushes `self.pending` into one encoded block, appending to the index
140 /// and data arrays.
141 fn seal_block(&mut self) {
142 let first_offset = match self.pending.first() {
143 Some((offset, _)) => *offset,
144 None => return,
145 };
146 let body_pos = u32::try_from(self.block_bodies.len()).unwrap();
147 self.block_index.push([
148 U32::new(LittleEndian, first_offset),
149 U32::new(LittleEndian, body_pos),
150 ]);
151
152 // The block's default code is its most common one, making the common
153 // case of a run of identical codes free to encode.
154 let default_code = most_common_code(&self.pending);
155 self.block_bodies.push(default_code);
156
157 let mut prev = first_offset;
158 for (pc, code) in self.pending.drain(..) {
159 let delta = pc - prev;
160 prev = pc;
161 let differs = code != default_code;
162 write_uleb(
163 &mut self.block_bodies,
164 (u64::from(delta) << 1) | u64::from(differs),
165 );
166 if differs {
167 self.block_bodies.push(code);
168 }
169 }
170 }
171
172 /// Encodes this section into the object provided.
173 pub fn append_to(self, obj: &mut Object) {
174 let section = obj.add_section(
175 obj.segment_name(StandardSegment::Data).to_vec(),
176 ELF_WASMTIME_TRAPS.as_bytes().to_vec(),
177 SectionKind::ReadOnlyData,
178 );
179
180 obj.append_section_data(section, &self.finish(), 1);
181 }
182
183 /// Finishes encoding and returns the raw section contents, as decoded by
184 /// `lookup_trap_code` and `iterate_traps`.
185 fn finish(mut self) -> Vec<u8> {
186 self.seal_block();
187 let entries = u32::try_from(self.entries).unwrap();
188 let num_blocks = u32::try_from(self.block_index.len()).unwrap();
189 let mut ret = Vec::with_capacity(8 + self.block_index.len() * 8 + self.block_bodies.len());
190 ret.extend_from_slice(&entries.to_le_bytes());
191 ret.extend_from_slice(&num_blocks.to_le_bytes());
192 ret.extend_from_slice(object::bytes_of_slice(&self.block_index));
193 ret.extend_from_slice(&self.block_bodies);
194 ret
195 }
196}
197
198fn most_common_code(entries: &[(u32, u8)]) -> u8 {
199 let mut counts = [0u16; 256];
200 let mut best = entries[0].1;
201 for (_, code) in entries {
202 let count = &mut counts[usize::from(*code)];
203 *count += 1;
204 if *count > counts[usize::from(best)] {
205 best = *code;
206 }
207 }
208 best
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use crate::{Trap, iterate_traps, lookup_trap_code};
215
216 fn encode(funcs: &[(Range<u64>, &[TrapInformation])]) -> Vec<u8> {
217 let mut builder = TrapEncodingBuilder::default();
218 for (func, traps) in funcs {
219 builder.push(func.clone(), traps);
220 }
221 builder.finish()
222 }
223
224 fn info(code_offset: u32, trap: Trap) -> TrapInformation {
225 TrapInformation {
226 code_offset,
227 trap_code: trap.into(),
228 }
229 }
230
231 #[test]
232 fn smoke() {
233 let section = encode(&[]);
234 assert_eq!(lookup_trap_code(§ion, 0), None);
235 assert_eq!(iterate_traps(§ion).unwrap().count(), 0);
236
237 let section = encode(&[(0..0x100, &[])]);
238 assert_eq!(lookup_trap_code(§ion, 0x50), None);
239 assert_eq!(iterate_traps(§ion).unwrap().count(), 0);
240
241 let section = encode(&[(
242 0..0x100,
243 &[
244 info(10, Trap::MemoryOutOfBounds),
245 info(20, Trap::StackOverflow),
246 ],
247 )]);
248 assert_eq!(lookup_trap_code(§ion, 0x50), None);
249 assert_eq!(
250 lookup_trap_code(§ion, 10),
251 Some(Trap::MemoryOutOfBounds.into())
252 );
253 assert_eq!(
254 lookup_trap_code(§ion, 20),
255 Some(Trap::StackOverflow.into())
256 );
257 }
258}