Skip to main content

wasmer_compiler_cranelift/
eh.rs

1//! Helpers for generating DWARF LSDA data for Cranelift-compiled functions.
2//!
3//! The structures and encoding implemented here mirror what LLVM produces for
4//! Wasm exception handling so that Wasmer's libunwind personalities can parse
5//! the tables without any runtime changes.
6
7use 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/// Relocation information for an LSDA entry that references a tag constant.
25#[derive(Debug, Clone)]
26pub struct TagRelocation {
27    /// Offset within the LSDA blob where the relocation should be applied.
28    pub offset: u32,
29    /// The module-local exception tag value.
30    pub tag: u32,
31}
32
33/// Fully encoded LSDA bytes for a single function, together with pending tag
34/// relocations that will be resolved once the global tag section is built.
35#[derive(Debug, Clone)]
36pub struct FunctionLsdaData {
37    pub bytes: Vec<u8>,
38    pub relocations: Vec<TagRelocation>,
39}
40
41/// Build the LSDA for a single function given the finalized Cranelift
42/// call-site metadata.
43pub 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        // Our landing pads handle all the tags considered for a call instruction, thus
56        // we use the latest landing pad.
57        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                    // Context records are used by Cranelift to thread VMContext
71                    // information through the landing pad. We emit the LSDA
72                    // regardless of whether we see them; nothing to do here.
73                    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    // Ensure all instructions in the function are covered by filling gaps with
100    // default unwinding behavior (no catch actions).
101    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            // Gap found: add a default site that covers instructions with no handlers
107            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    // Cover any remaining instructions at the end of the function
119    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            // CatchAll must always be the last item in the action list; otherwise, the tags that follow
137            // it will be ignored.
138            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            // Reverse actions to ensure CatchAll is always last in the chain, since the action table
153            // encoding uses back references and relies on this ordering.
154            .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(); // lpstart encoding omitted (relative to function start)
173
174    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        // PC-relative, 4-byte entries. This keeps the `.gcc_except_table`
180        // section relocations position-independent (e.g. `R_X86_64_PC32`).
181        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 // call-site encoding byte
197            + 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
228/// Build the global tag section and a tag->offset map.
229pub 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
264/// Build the LSDA custom section and record the offset for each function.
265///
266/// Returns the section (if any) and a vector mapping each function index to
267/// its LSDA offset inside the section. Even when utilizing the same landing pad for exception tags,
268/// Cranelift generates separate landing pad locations.
269/// These locations are essentially small trampolines that redirect to the basic block we established (the EH dispatch block).
270///
271/// The section can be dumped using the elfutils' readelf tool:
272/// ```shell
273/// objcopy -I binary -O elf64-x86-64 --rename-section .data=.gcc_except_table,alloc,contents lsda.bin object.o && eu-readelf -w object.o
274/// ```
275pub 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
341/// Build the 64-bit Mach-O `__compact_unwind` section consumed by the
342/// runtime compact-unwind publisher.
343pub 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
402// Constants are defined in compact_unwind_encoding.h file.
403const 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        // The indices are one-based!
542        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        // Note the exception types must be streamed in the reverse order!
553        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    /// Encode the type table as PC-relative, 4-byte slots resolved through
570    /// relocations against the per-object tag section. Used by the ELF
571    /// artifact format, where each function's LSDA lives in its own object.
572    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        // Note the exception types must be streamed in the reverse order!
578        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                            // Make a linked list to the previous action
623                            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    // We use DW_EH_PE_udata4 for all offsets.
663    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}