wasmer_compiler_cranelift/
eh.rs1use cranelift_codegen::{
8 ExceptionContextLoc, FinalizedMachCallSite, FinalizedMachExceptionHandler,
9};
10use cranelift_entity::EntityRef;
11use std::collections::hash_map::Entry;
12use std::collections::{HashMap, HashSet};
13use std::convert::TryFrom;
14use std::io::{Cursor, Write};
15
16use wasmer_compiler::types::{
17 relocation::{Relocation, RelocationKind, RelocationTarget},
18 section::{CustomSection, CustomSectionProtection, SectionBody, SectionIndex},
19};
20
21#[derive(Debug, Clone)]
23pub struct TagRelocation {
24 pub offset: u32,
26 pub tag: u32,
28}
29
30#[derive(Debug, Clone)]
33pub struct FunctionLsdaData {
34 pub bytes: Vec<u8>,
35 pub relocations: Vec<TagRelocation>,
36}
37
38pub fn build_function_lsda<'a>(
41 call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
42 function_length: usize,
43 pointer_bytes: u8,
44) -> Option<FunctionLsdaData> {
45 let mut sites = Vec::new();
46
47 for site in call_sites {
48 let mut catches = Vec::new();
49 let mut landing_pad = None;
50
51 for handler in site.exception_handlers {
54 match handler {
55 FinalizedMachExceptionHandler::Tag(tag, offset) => {
56 landing_pad = Some(landing_pad.unwrap_or(*offset));
57 catches.push(ActionKind::Tag {
58 tag: u32::try_from(tag.index()).expect("tag index fits in u32"),
59 });
60 }
61 FinalizedMachExceptionHandler::Default(offset) => {
62 landing_pad = Some(landing_pad.unwrap_or(*offset));
63 catches.push(ActionKind::CatchAll);
64 }
65 FinalizedMachExceptionHandler::Context(context) => {
66 match context {
70 ExceptionContextLoc::SPOffset(_) | ExceptionContextLoc::GPR(_) => {}
71 }
72 }
73 }
74 }
75
76 if catches.is_empty() {
77 continue;
78 }
79
80 let landing_pad = landing_pad.expect("landing pad offset set when catches exist");
81 let cs_start = site.ret_addr.saturating_sub(1);
82
83 sites.push(CallSiteDesc {
84 start: cs_start,
85 len: 1,
86 landing_pad,
87 actions: catches,
88 });
89 }
90
91 if sites.is_empty() {
92 return None;
93 }
94
95 let mut current_pos = 0u32;
98 let mut filled_sites = Vec::new();
99
100 for site in sites {
101 if site.start > current_pos {
102 filled_sites.push(CallSiteDesc {
104 start: current_pos,
105 len: site.start - current_pos,
106 landing_pad: 0,
107 actions: Vec::new(),
108 });
109 }
110 current_pos = site.start + site.len;
111 filled_sites.push(site);
112 }
113
114 if current_pos < function_length as u32 {
116 filled_sites.push(CallSiteDesc {
117 start: current_pos,
118 len: function_length as u32 - current_pos,
119 landing_pad: 0,
120 actions: Vec::new(),
121 });
122 }
123
124 let sites = filled_sites;
125
126 let mut type_entries = TypeTable::new();
127 let mut callsite_actions = Vec::with_capacity(sites.len());
128
129 for site in &sites {
130 let mut action_indices = Vec::new();
131 for action in &site.actions {
132 let index = match action {
133 ActionKind::Tag { tag } => type_entries.get_or_insert_tag(*tag),
134 ActionKind::CatchAll => type_entries.get_or_insert_catch_all(),
135 };
136 action_indices.push(index as i32);
137 }
138 callsite_actions.push(action_indices);
139 }
140
141 let action_table = encode_action_table(&callsite_actions);
142 let call_site_table = encode_call_site_table(&sites, &action_table);
143 let (type_table_bytes, type_table_relocs) = type_entries.encode(pointer_bytes);
144
145 let call_site_table_len = call_site_table.len() as u64;
146 let mut writer = Cursor::new(Vec::new());
147 writer
148 .write_all(&gimli::DW_EH_PE_omit.0.to_le_bytes())
149 .unwrap(); if type_entries.is_empty() {
152 writer
153 .write_all(&gimli::DW_EH_PE_omit.0.to_le_bytes())
154 .unwrap();
155 } else {
156 writer
157 .write_all(&gimli::DW_EH_PE_absptr.0.to_le_bytes())
158 .unwrap();
159 }
160
161 if !type_entries.is_empty() {
162 let ttype_table_end = 1 + uleb128_len(call_site_table_len)
164 + call_site_table.len()
165 + action_table.bytes.len()
166 + type_table_bytes.len();
167 leb128::write::unsigned(&mut writer, ttype_table_end as u64).unwrap();
168 }
169
170 writer
171 .write_all(&gimli::DW_EH_PE_udata4.0.to_le_bytes())
172 .unwrap();
173 leb128::write::unsigned(&mut writer, call_site_table_len).unwrap();
174 writer.write_all(&call_site_table).unwrap();
175 writer.write_all(&action_table.bytes).unwrap();
176
177 let type_table_offset = writer.position() as u32;
178 writer.write_all(&type_table_bytes).unwrap();
179
180 let mut relocations = Vec::new();
181 for reloc in type_table_relocs {
182 relocations.push(TagRelocation {
183 offset: type_table_offset + reloc.offset,
184 tag: reloc.tag,
185 });
186 }
187
188 Some(FunctionLsdaData {
189 bytes: writer.into_inner(),
190 relocations,
191 })
192}
193
194pub fn build_tag_section(
196 lsda_data: &[Option<FunctionLsdaData>],
197) -> Option<(CustomSection, HashMap<u32, u32>)> {
198 let mut unique_tags = HashSet::new();
199 for data in lsda_data.iter().flatten() {
200 for reloc in &data.relocations {
201 unique_tags.insert(reloc.tag);
202 }
203 }
204
205 if unique_tags.is_empty() {
206 return None;
207 }
208
209 let mut tags: Vec<u32> = unique_tags.into_iter().collect();
210 tags.sort_unstable();
211
212 let mut bytes = Vec::with_capacity(tags.len() * std::mem::size_of::<u32>());
213 let mut offsets = HashMap::new();
214 for tag in tags {
215 let offset = bytes.len() as u32;
216 bytes.extend_from_slice(&tag.to_ne_bytes());
217 offsets.insert(tag, offset);
218 }
219
220 let section = CustomSection {
221 protection: CustomSectionProtection::Read,
222 alignment: None,
223 bytes: SectionBody::new_with_vec(bytes),
224 relocations: Vec::new(),
225 };
226
227 Some((section, offsets))
228}
229
230pub fn build_lsda_section(
242 lsda_data: Vec<Option<FunctionLsdaData>>,
243 pointer_bytes: u8,
244 tag_offsets: &HashMap<u32, u32>,
245 tag_section_index: Option<SectionIndex>,
246) -> (Option<CustomSection>, Vec<Option<u32>>) {
247 let mut bytes = Vec::new();
248 let mut relocations = Vec::new();
249 let mut offsets_per_function = Vec::with_capacity(lsda_data.len());
250
251 let pointer_kind = match pointer_bytes {
252 4 => RelocationKind::Abs4,
253 8 => RelocationKind::Abs8,
254 other => panic!("unsupported pointer size {other} for LSDA generation"),
255 };
256
257 for data in lsda_data.into_iter() {
258 if let Some(data) = data {
259 let base = bytes.len() as u32;
260 bytes.extend_from_slice(&data.bytes);
261
262 for reloc in &data.relocations {
263 let target_offset = tag_offsets
264 .get(&reloc.tag)
265 .copied()
266 .expect("missing tag offset for relocation");
267 relocations.push(Relocation {
268 kind: pointer_kind,
269 reloc_target: RelocationTarget::CustomSection(
270 tag_section_index
271 .expect("tag section index must exist when relocations are present"),
272 ),
273 offset: base + reloc.offset,
274 addend: target_offset as i64,
275 });
276 }
277
278 offsets_per_function.push(Some(base));
279 } else {
280 offsets_per_function.push(None);
281 }
282 }
283
284 if bytes.is_empty() {
285 (None, offsets_per_function)
286 } else {
287 (
288 Some(CustomSection {
289 protection: CustomSectionProtection::Read,
290 alignment: None,
291 bytes: SectionBody::new_with_vec(bytes),
292 relocations,
293 }),
294 offsets_per_function,
295 )
296 }
297}
298
299#[derive(Debug)]
300struct CallSiteDesc {
301 start: u32,
302 len: u32,
303 landing_pad: u32,
304 actions: Vec<ActionKind>,
305}
306
307#[derive(Debug)]
308enum ActionKind {
309 Tag { tag: u32 },
310 CatchAll,
311}
312
313#[derive(Debug)]
314struct TypeTable {
315 entries: Vec<TypeEntry>,
316 index_map: HashMap<TypeKey, usize>,
317}
318
319impl TypeTable {
320 fn new() -> Self {
321 Self {
322 entries: Vec::new(),
323 index_map: HashMap::new(),
324 }
325 }
326
327 fn is_empty(&self) -> bool {
328 self.entries.is_empty()
329 }
330
331 fn get_or_insert_tag(&mut self, tag: u32) -> usize {
332 let key = TypeKey::Tag(tag);
333 if let Some(idx) = self.index_map.get(&key) {
334 *idx
335 } else {
336 let idx = self.entries.len() + 1;
337 self.entries.push(TypeEntry::Tag { tag });
338 self.index_map.insert(key, idx);
339 idx
340 }
341 }
342
343 fn get_or_insert_catch_all(&mut self) -> usize {
344 let key = TypeKey::CatchAll;
345 if let Some(idx) = self.index_map.get(&key) {
346 *idx
347 } else {
348 let idx = self.entries.len() + 1;
349 self.entries.push(TypeEntry::CatchAll);
350 self.index_map.insert(key, idx);
351 idx
352 }
353 }
354
355 fn encode(&self, pointer_bytes: u8) -> (Vec<u8>, Vec<TagRelocation>) {
356 let mut bytes = Vec::with_capacity(self.entries.len() * pointer_bytes as usize);
357 let mut relocations = Vec::new();
358
359 for entry in self.entries.iter().rev() {
360 let offset = bytes.len() as u32;
361 match entry {
362 TypeEntry::Tag { tag } => {
363 bytes.extend_from_slice(&vec![0; pointer_bytes as usize]);
364 relocations.push(TagRelocation { offset, tag: *tag });
365 }
366 TypeEntry::CatchAll => {
367 bytes.extend_from_slice(&vec![0; pointer_bytes as usize]);
368 }
369 }
370 }
371
372 (bytes, relocations)
373 }
374}
375
376#[derive(Debug, Hash, PartialEq, Eq)]
377enum TypeKey {
378 Tag(u32),
379 CatchAll,
380}
381
382#[derive(Debug)]
383enum TypeEntry {
384 Tag { tag: u32 },
385 CatchAll,
386}
387
388struct ActionTable {
389 bytes: Vec<u8>,
390 first_action_offsets: Vec<Option<u32>>,
391}
392
393fn encode_action_table(callsite_actions: &[Vec<i32>]) -> ActionTable {
394 let mut writer = Cursor::new(Vec::new());
395 let mut first_action_offsets = Vec::new();
396
397 let mut cache = HashMap::new();
398
399 for actions in callsite_actions {
400 if actions.is_empty() {
401 first_action_offsets.push(None);
402 } else {
403 match cache.entry(actions.clone()) {
404 Entry::Occupied(entry) => {
405 first_action_offsets.push(Some(*entry.get()));
406 }
407 Entry::Vacant(entry) => {
408 let mut last_action_start = 0;
409 for (i, &ttype_index) in actions.iter().enumerate() {
410 let next_action_start = writer.position();
411 leb128::write::signed(&mut writer, ttype_index as i64)
412 .expect("leb128 write failed");
413
414 if i != 0 {
415 let displacement = last_action_start - writer.position() as i64;
417 leb128::write::signed(&mut writer, displacement)
418 .expect("leb128 write failed");
419 } else {
420 leb128::write::signed(&mut writer, 0).expect("leb128 write failed");
421 }
422 last_action_start = next_action_start as i64;
423 }
424 let last_action_start = last_action_start as u32;
425 entry.insert(last_action_start);
426 first_action_offsets.push(Some(last_action_start));
427 }
428 }
429 }
430 }
431
432 ActionTable {
433 bytes: writer.into_inner(),
434 first_action_offsets,
435 }
436}
437
438fn encode_call_site_table(callsites: &[CallSiteDesc], action_table: &ActionTable) -> Vec<u8> {
439 let mut writer = Cursor::new(Vec::new());
440 for (idx, site) in callsites.iter().enumerate() {
441 write_encoded_offset(site.start, &mut writer);
442 write_encoded_offset(site.len, &mut writer);
443 write_encoded_offset(site.landing_pad, &mut writer);
444
445 let action = match action_table.first_action_offsets[idx] {
446 Some(offset) => offset as u64 + 1,
447 None => 0,
448 };
449 leb128::write::unsigned(&mut writer, action).expect("leb128 write failed");
450 }
451 writer.into_inner()
452}
453
454fn write_encoded_offset(val: u32, out: &mut impl Write) {
455 out.write_all(&val.to_le_bytes())
457 .expect("write to buffer failed")
458}
459
460fn uleb128_len(value: u64) -> usize {
461 let mut cursor = Cursor::new([0u8; 10]);
462 leb128::write::unsigned(&mut cursor, value).unwrap()
463}