1use cranelift_codegen::{
8 ExceptionContextLoc, FinalizedMachCallSite, FinalizedMachExceptionHandler,
9 isa::unwind::UnwindInst,
10};
11use cranelift_entity::EntityRef;
12use itertools::Itertools;
13use std::collections::hash_map::Entry;
14use std::collections::{HashMap, HashSet};
15use std::convert::TryFrom;
16use std::io::{Cursor, Write};
17
18use wasmer_compiler::types::{
19 relocation::{Relocation, RelocationKind, RelocationTarget},
20 section::{CustomSection, CustomSectionProtection, SectionBody, SectionIndex},
21};
22use wasmer_types::{LibCall, LocalFunctionIndex};
23
24#[derive(Debug, Clone)]
26pub struct TagRelocation {
27 pub offset: u32,
29 pub tag: u32,
31}
32
33#[derive(Debug, Clone)]
36pub struct FunctionLsdaData {
37 pub bytes: Vec<u8>,
38 pub relocations: Vec<TagRelocation>,
39}
40
41pub fn build_function_lsda<'a>(
44 call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
45 function_length: usize,
46 pointer_bytes: u8,
47 pcrel_type_table: bool,
48) -> Option<FunctionLsdaData> {
49 let mut sites = Vec::new();
50
51 for site in call_sites {
52 let mut catches = Vec::new();
53 let mut landing_pad = None;
54
55 for handler in site.exception_handlers {
58 match handler {
59 FinalizedMachExceptionHandler::Tag(tag, offset) => {
60 landing_pad = Some(landing_pad.unwrap_or(*offset));
61 catches.push(ExceptionType::Tag {
62 tag: u32::try_from(tag.index()).expect("tag index fits in u32"),
63 });
64 }
65 FinalizedMachExceptionHandler::Default(offset) => {
66 landing_pad = Some(landing_pad.unwrap_or(*offset));
67 catches.push(ExceptionType::CatchAll);
68 }
69 FinalizedMachExceptionHandler::Context(context) => {
70 match context {
74 ExceptionContextLoc::SPOffset(_) | ExceptionContextLoc::GPR(_) => {}
75 }
76 }
77 }
78 }
79
80 if catches.is_empty() {
81 continue;
82 }
83
84 let landing_pad = landing_pad.expect("landing pad offset set when catches exist");
85 let cs_start = site.ret_addr.saturating_sub(1);
86
87 sites.push(CallSiteDesc {
88 start: cs_start,
89 len: 1,
90 landing_pad,
91 actions: catches,
92 });
93 }
94
95 if sites.is_empty() {
96 return None;
97 }
98
99 let mut current_pos = 0u32;
102 let mut filled_sites = Vec::new();
103
104 for site in sites {
105 if site.start > current_pos {
106 filled_sites.push(CallSiteDesc {
108 start: current_pos,
109 len: site.start - current_pos,
110 landing_pad: 0,
111 actions: Vec::new(),
112 });
113 }
114 current_pos = site.start + site.len;
115 filled_sites.push(site);
116 }
117
118 if current_pos < function_length as u32 {
120 filled_sites.push(CallSiteDesc {
121 start: current_pos,
122 len: function_length as u32 - current_pos,
123 landing_pad: 0,
124 actions: Vec::new(),
125 });
126 }
127
128 let sites = filled_sites;
129
130 let mut type_entries = TypeTable::new();
131 let mut callsite_actions = Vec::with_capacity(sites.len());
132
133 for site in &sites {
134 #[cfg(debug_assertions)]
135 {
136 let catch_all_positions = site
139 .actions
140 .iter()
141 .positions(|a| matches!(a, ExceptionType::CatchAll))
142 .collect_vec();
143 assert!(catch_all_positions.iter().at_most_one().is_ok());
144 if let Some(&i) = catch_all_positions.first() {
145 assert!(i == site.actions.len() - 1);
146 }
147 }
148
149 let action_indices = site
150 .actions
151 .iter()
152 .rev()
155 .map(|action| type_entries.get_or_insert(*action) as i32)
156 .collect_vec();
157 callsite_actions.push(action_indices);
158 }
159
160 let action_table = encode_action_table(&callsite_actions);
161 let call_site_table = encode_call_site_table(&sites, &action_table);
162 let (type_table_bytes, type_table_relocs) = if pcrel_type_table {
163 type_entries.encode_relocated()
164 } else {
165 type_entries.encode(pointer_bytes)
166 };
167
168 let call_site_table_len = call_site_table.len() as u64;
169 let mut writer = Cursor::new(Vec::new());
170 writer
171 .write_all(&cranelift_codegen::gimli::DW_EH_PE_omit.0.to_le_bytes())
172 .unwrap(); if type_entries.is_empty() {
175 writer
176 .write_all(&cranelift_codegen::gimli::DW_EH_PE_omit.0.to_le_bytes())
177 .unwrap();
178 } else if pcrel_type_table {
179 writer
182 .write_all(
183 &(cranelift_codegen::gimli::DW_EH_PE_pcrel
184 | cranelift_codegen::gimli::DW_EH_PE_sdata4)
185 .0
186 .to_le_bytes(),
187 )
188 .unwrap();
189 } else {
190 writer
191 .write_all(&cranelift_codegen::gimli::DW_EH_PE_absptr.0.to_le_bytes())
192 .unwrap();
193 }
194
195 if !type_entries.is_empty() {
196 let ttype_table_end = 1 + uleb128_len(call_site_table_len)
198 + call_site_table.len()
199 + action_table.bytes.len()
200 + type_table_bytes.len();
201 leb128::write::unsigned(&mut writer, ttype_table_end as u64).unwrap();
202 }
203
204 writer
205 .write_all(&cranelift_codegen::gimli::DW_EH_PE_udata4.0.to_le_bytes())
206 .unwrap();
207 leb128::write::unsigned(&mut writer, call_site_table_len).unwrap();
208 writer.write_all(&call_site_table).unwrap();
209 writer.write_all(&action_table.bytes).unwrap();
210
211 let type_table_offset = writer.position() as u32;
212 writer.write_all(&type_table_bytes).unwrap();
213
214 let mut relocations = Vec::new();
215 for reloc in type_table_relocs {
216 relocations.push(TagRelocation {
217 offset: type_table_offset + reloc.offset,
218 tag: reloc.tag,
219 });
220 }
221
222 Some(FunctionLsdaData {
223 bytes: writer.into_inner(),
224 relocations,
225 })
226}
227
228pub fn build_tag_section(
230 lsda_data: &[Option<FunctionLsdaData>],
231) -> Option<(CustomSection, HashMap<u32, u32>)> {
232 let mut unique_tags = HashSet::new();
233 for data in lsda_data.iter().flatten() {
234 for reloc in &data.relocations {
235 unique_tags.insert(reloc.tag);
236 }
237 }
238
239 if unique_tags.is_empty() {
240 return None;
241 }
242
243 let mut tags: Vec<u32> = unique_tags.into_iter().collect();
244 tags.sort_unstable();
245
246 let mut bytes = Vec::with_capacity(tags.len() * std::mem::size_of::<u32>());
247 let mut offsets = HashMap::new();
248 for tag in tags {
249 let offset = bytes.len() as u32;
250 bytes.extend_from_slice(&tag.to_ne_bytes());
251 offsets.insert(tag, offset);
252 }
253
254 let section = CustomSection {
255 protection: CustomSectionProtection::Read,
256 alignment: None,
257 bytes: SectionBody::new_with_vec(bytes),
258 relocations: Vec::new(),
259 };
260
261 Some((section, offsets))
262}
263
264pub fn build_lsda_section(
276 lsda_data: Vec<Option<FunctionLsdaData>>,
277 pointer_bytes: u8,
278 tag_offsets: &HashMap<u32, u32>,
279 tag_section_index: Option<SectionIndex>,
280) -> (Option<CustomSection>, Vec<Option<u32>>) {
281 let mut bytes = Vec::new();
282 let mut relocations = Vec::new();
283 let mut offsets_per_function = Vec::with_capacity(lsda_data.len());
284
285 let pointer_kind = match pointer_bytes {
286 4 => RelocationKind::Abs4,
287 8 => RelocationKind::Abs8,
288 other => panic!("unsupported pointer size {other} for LSDA generation"),
289 };
290
291 for data in lsda_data.into_iter() {
292 if let Some(data) = data {
293 let base = bytes.len() as u32;
294 bytes.extend_from_slice(&data.bytes);
295
296 for reloc in &data.relocations {
297 let target_offset = tag_offsets
298 .get(&reloc.tag)
299 .copied()
300 .expect("missing tag offset for relocation");
301 relocations.push(Relocation {
302 kind: pointer_kind,
303 reloc_target: RelocationTarget::CustomSection(
304 tag_section_index
305 .expect("tag section index must exist when relocations are present"),
306 ),
307 offset: base + reloc.offset,
308 addend: target_offset as i64,
309 });
310 }
311
312 offsets_per_function.push(Some(base));
313 } else {
314 offsets_per_function.push(None);
315 }
316 }
317
318 if bytes.is_empty() {
319 (None, offsets_per_function)
320 } else {
321 (
322 Some(CustomSection {
323 protection: CustomSectionProtection::Read,
324 alignment: None,
325 bytes: SectionBody::new_with_vec(bytes),
326 relocations,
327 }),
328 offsets_per_function,
329 )
330 }
331}
332
333#[derive(Debug, Clone)]
334pub struct CompactUnwindEntryData {
335 pub function: LocalFunctionIndex,
336 pub function_length: u32,
337 pub compact_encoding: u32,
338 pub lsda_offset: Option<u32>,
339}
340
341pub fn build_compact_unwind_section(
344 entries: impl IntoIterator<Item = CompactUnwindEntryData>,
345 lsda_section_index: Option<SectionIndex>,
346) -> Option<CustomSection> {
347 const ENTRY_SIZE: usize = 32;
348 const FUNCTION_ADDR_OFFSET: u32 = 0;
349 const PERSONALITY_ADDR_OFFSET: u32 = 16;
350 const LSDA_ADDR_OFFSET: u32 = 24;
351
352 let entries = entries.into_iter().collect::<Vec<_>>();
353 if entries.is_empty() {
354 return None;
355 }
356
357 let mut bytes = Vec::with_capacity(entries.len() * ENTRY_SIZE);
358 let mut relocations = Vec::new();
359
360 for entry in entries {
361 let base = bytes.len() as u32;
362
363 bytes.extend_from_slice(&0u64.to_le_bytes());
364 bytes.extend_from_slice(&entry.function_length.to_le_bytes());
365 bytes.extend_from_slice(&entry.compact_encoding.to_le_bytes());
366 bytes.extend_from_slice(&0u64.to_le_bytes());
367 bytes.extend_from_slice(&0u64.to_le_bytes());
368
369 relocations.push(Relocation {
370 kind: RelocationKind::Abs8,
371 reloc_target: RelocationTarget::LocalFunc(entry.function),
372 offset: base + FUNCTION_ADDR_OFFSET,
373 addend: 0,
374 });
375 relocations.push(Relocation {
376 kind: RelocationKind::Abs8,
377 reloc_target: RelocationTarget::LibCall(LibCall::EHPersonality),
378 offset: base + PERSONALITY_ADDR_OFFSET,
379 addend: 0,
380 });
381
382 if let Some(lsda_offset) = entry.lsda_offset {
383 relocations.push(Relocation {
384 kind: RelocationKind::Abs8,
385 reloc_target: RelocationTarget::CustomSection(
386 lsda_section_index.expect("LSDA section index required for LSDA relocation"),
387 ),
388 offset: base + LSDA_ADDR_OFFSET,
389 addend: lsda_offset as i64,
390 });
391 }
392 }
393
394 Some(CustomSection {
395 protection: CustomSectionProtection::Read,
396 alignment: Some(8),
397 bytes: SectionBody::new_with_vec(bytes),
398 relocations,
399 })
400}
401
402const UNWIND_ARM64_MODE_FRAMELESS: u32 = 0x02000000;
404const UNWIND_ARM64_MODE_FRAME: u32 = 0x04000000;
405
406const UNWIND_ARM64_FRAMELESS_STACK_SIZE_SHIFT: u32 = 12;
407const UNWIND_ARM64_FRAME_X19_X20_PAIR: u32 = 0x00000001;
408const UNWIND_ARM64_FRAME_X21_X22_PAIR: u32 = 0x00000002;
409const UNWIND_ARM64_FRAME_X23_X24_PAIR: u32 = 0x00000004;
410const UNWIND_ARM64_FRAME_X25_X26_PAIR: u32 = 0x00000008;
411const UNWIND_ARM64_FRAME_X27_X28_PAIR: u32 = 0x00000010;
412const UNWIND_ARM64_FRAME_D8_D9_PAIR: u32 = 0x00000100;
413const UNWIND_ARM64_FRAME_D10_D11_PAIR: u32 = 0x00000200;
414const UNWIND_ARM64_FRAME_D12_D13_PAIR: u32 = 0x00000400;
415const UNWIND_ARM64_FRAME_D14_D15_PAIR: u32 = 0x00000800;
416
417const STACK_SIZE_UNIT: u32 = 16;
418
419pub fn compact_unwind_encoding_aarch64(unwind_info: &[(u32, UnwindInst)]) -> Result<u32, String> {
420 let mut has_frame = false;
421 let mut stack_size = 0u32;
422 let mut saved_int = HashSet::new();
423 let mut saved_float = HashSet::new();
424
425 for (_, inst) in unwind_info {
426 match inst {
427 UnwindInst::PushFrameRegs { .. } | UnwindInst::DefineNewFrame { .. } => {
428 has_frame = true;
429 }
430 UnwindInst::StackAlloc { size } => {
431 stack_size = stack_size
432 .checked_add(*size)
433 .ok_or_else(|| "aarch64 compact-unwind stack size overflow".to_string())?;
434 }
435 UnwindInst::SaveReg { reg, .. } => match reg.class() {
436 regalloc2::RegClass::Int => {
437 saved_int.insert(reg.hw_enc());
438 }
439 regalloc2::RegClass::Float => {
440 saved_float.insert(reg.hw_enc());
441 }
442 regalloc2::RegClass::Vector => {
443 return Err(
444 "aarch64 compact-unwind cannot encode vector register saves".to_owned()
445 );
446 }
447 },
448 UnwindInst::RegStackOffset { .. } => {
449 return Err("aarch64 compact-unwind cannot encode RegStackOffset".to_owned());
450 }
451 UnwindInst::Aarch64SetPointerAuth { .. } => {}
452 }
453 }
454
455 if !has_frame {
456 if !saved_int.is_empty() || !saved_float.is_empty() {
457 return Err("aarch64 frameless compact-unwind cannot encode saved registers".into());
458 }
459 if !stack_size.is_multiple_of(STACK_SIZE_UNIT) {
460 return Err("aarch64 compact-unwind stack size must be 16-byte aligned".into());
461 }
462 let stack_units = stack_size / STACK_SIZE_UNIT;
463 if stack_units > 0x0fff {
464 return Err("aarch64 compact-unwind stack size is too large".into());
465 }
466 return Ok(
467 UNWIND_ARM64_MODE_FRAMELESS | (stack_units << UNWIND_ARM64_FRAMELESS_STACK_SIZE_SHIFT)
468 );
469 }
470
471 let encode_saved_pair = |saved: &mut HashSet<_>, lo, hi, bit, class_name| match (
472 saved.remove(&lo),
473 saved.remove(&hi),
474 ) {
475 (false, false) => Ok(0),
476 (true, true) => Ok(bit),
477 _ => Err(format!(
478 "aarch64 compact-unwind cannot encode unpaired {class_name}{lo}/{class_name}{hi} save"
479 )),
480 };
481
482 let mut encoding = UNWIND_ARM64_MODE_FRAME;
483 for (lo, hi, bit) in [
484 (19, 20, UNWIND_ARM64_FRAME_X19_X20_PAIR),
485 (21, 22, UNWIND_ARM64_FRAME_X21_X22_PAIR),
486 (23, 24, UNWIND_ARM64_FRAME_X23_X24_PAIR),
487 (25, 26, UNWIND_ARM64_FRAME_X25_X26_PAIR),
488 (27, 28, UNWIND_ARM64_FRAME_X27_X28_PAIR),
489 ] {
490 encoding |= encode_saved_pair(&mut saved_int, lo, hi, bit, "x")?;
491 }
492 for (lo, hi, bit) in [
493 (8, 9, UNWIND_ARM64_FRAME_D8_D9_PAIR),
494 (10, 11, UNWIND_ARM64_FRAME_D10_D11_PAIR),
495 (12, 13, UNWIND_ARM64_FRAME_D12_D13_PAIR),
496 (14, 15, UNWIND_ARM64_FRAME_D14_D15_PAIR),
497 ] {
498 encoding |= encode_saved_pair(&mut saved_float, lo, hi, bit, "d")?;
499 }
500
501 if !saved_int.is_empty() || !saved_float.is_empty() {
502 return Err("aarch64 compact-unwind encountered unsupported saved register".to_owned());
503 }
504
505 Ok(encoding)
506}
507
508#[derive(Debug)]
509struct CallSiteDesc {
510 start: u32,
511 len: u32,
512 landing_pad: u32,
513 actions: Vec<ExceptionType>,
514}
515
516#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
517enum ExceptionType {
518 Tag { tag: u32 },
519 CatchAll,
520}
521
522#[derive(Debug)]
523struct TypeTable {
524 entries: indexmap::IndexSet<ExceptionType>,
525}
526
527impl TypeTable {
528 fn new() -> Self {
529 Self {
530 entries: indexmap::IndexSet::new(),
531 }
532 }
533
534 fn is_empty(&self) -> bool {
535 self.entries.is_empty()
536 }
537
538 fn get_or_insert(&mut self, exception: ExceptionType) -> usize {
539 self.entries.insert(exception);
540
541 self.entries
543 .get_index_of(&exception)
544 .expect("must be already inserted")
545 + 1
546 }
547
548 fn encode(&self, pointer_bytes: u8) -> (Vec<u8>, Vec<TagRelocation>) {
549 let mut bytes = Vec::with_capacity(self.entries.len() * pointer_bytes as usize);
550 let mut relocations = Vec::new();
551
552 for entry in self.entries.iter().rev() {
554 let offset = bytes.len() as u32;
555 match entry {
556 ExceptionType::Tag { tag } => {
557 bytes.extend(std::iter::repeat_n(0, pointer_bytes as usize));
558 relocations.push(TagRelocation { offset, tag: *tag });
559 }
560 ExceptionType::CatchAll => {
561 bytes.extend(std::iter::repeat_n(0, pointer_bytes as usize));
562 }
563 }
564 }
565
566 (bytes, relocations)
567 }
568
569 fn encode_relocated(&self) -> (Vec<u8>, Vec<TagRelocation>) {
573 const ENTRY_SIZE: usize = 4;
574 let mut bytes = Vec::with_capacity(self.entries.len() * ENTRY_SIZE);
575 let mut relocations = Vec::new();
576
577 for entry in self.entries.iter().rev() {
579 let offset = bytes.len() as u32;
580 match entry {
581 ExceptionType::Tag { tag } => {
582 bytes.extend(std::iter::repeat_n(0, ENTRY_SIZE));
583 relocations.push(TagRelocation { offset, tag: *tag });
584 }
585 ExceptionType::CatchAll => {
586 bytes.extend(std::iter::repeat_n(0, ENTRY_SIZE));
587 }
588 }
589 }
590
591 (bytes, relocations)
592 }
593}
594
595struct ActionTable {
596 bytes: Vec<u8>,
597 first_action_offsets: Vec<Option<u32>>,
598}
599
600fn encode_action_table(callsite_actions: &[Vec<i32>]) -> ActionTable {
601 let mut writer = Cursor::new(Vec::new());
602 let mut first_action_offsets = Vec::new();
603
604 let mut cache = HashMap::new();
605
606 for actions in callsite_actions {
607 if actions.is_empty() {
608 first_action_offsets.push(None);
609 } else {
610 match cache.entry(actions.clone()) {
611 Entry::Occupied(entry) => {
612 first_action_offsets.push(Some(*entry.get()));
613 }
614 Entry::Vacant(entry) => {
615 let mut last_action_start = 0;
616 for (i, &ttype_index) in actions.iter().enumerate() {
617 let next_action_start = writer.position();
618 leb128::write::signed(&mut writer, ttype_index as i64)
619 .expect("leb128 write failed");
620
621 if i != 0 {
622 let displacement = last_action_start - writer.position() as i64;
624 leb128::write::signed(&mut writer, displacement)
625 .expect("leb128 write failed");
626 } else {
627 leb128::write::signed(&mut writer, 0).expect("leb128 write failed");
628 }
629 last_action_start = next_action_start as i64;
630 }
631 let last_action_start = last_action_start as u32;
632 entry.insert(last_action_start);
633 first_action_offsets.push(Some(last_action_start));
634 }
635 }
636 }
637 }
638
639 ActionTable {
640 bytes: writer.into_inner(),
641 first_action_offsets,
642 }
643}
644
645fn encode_call_site_table(callsites: &[CallSiteDesc], action_table: &ActionTable) -> Vec<u8> {
646 let mut writer = Cursor::new(Vec::new());
647 for (idx, site) in callsites.iter().enumerate() {
648 write_encoded_offset(site.start, &mut writer);
649 write_encoded_offset(site.len, &mut writer);
650 write_encoded_offset(site.landing_pad, &mut writer);
651
652 let action = match action_table.first_action_offsets[idx] {
653 Some(offset) => offset as u64 + 1,
654 None => 0,
655 };
656 leb128::write::unsigned(&mut writer, action).expect("leb128 write failed");
657 }
658 writer.into_inner()
659}
660
661fn write_encoded_offset(val: u32, out: &mut impl Write) {
662 out.write_all(&val.to_le_bytes())
664 .expect("write to buffer failed")
665}
666
667fn uleb128_len(value: u64) -> usize {
668 let mut cursor = Cursor::new([0u8; 10]);
669 leb128::write::unsigned(&mut cursor, value).unwrap()
670}