Skip to main content

asmkit/core/
buffer.rs

1use alloc::{borrow::Cow, collections::BinaryHeap, vec::Vec};
2use core::fmt;
3
4use smallvec::SmallVec;
5
6use crate::AsmError;
7use crate::core::arch_traits::Arch;
8use crate::core::patch::{
9    PatchBlock, PatchBlockId, PatchCatalog, PatchSite, PatchSiteId, PatchableBlock, fill_with_nops,
10    minimum_patch_alignment,
11};
12#[cfg(feature = "riscv")]
13use crate::riscv;
14
15#[cfg(feature = "jit")]
16use crate::core::jit_allocator::{JitAllocator, Span};
17
18use super::{
19    operand::{Label, Sym},
20    target::Environment,
21};
22
23/// A buffer of output to be produced, fixed up, and then emitted to a CodeSink
24/// in bulk.
25///
26/// This struct uses `SmallVec`s to support small-ish function bodies without
27/// any heap allocation. As such, it will be several kilobytes large. This is
28/// likely fine as long as it is stack-allocated for function emission then
29/// thrown away; but beware if many buffer objects are retained persistently.
30pub struct CodeBuffer {
31    env: Environment,
32    data: SmallVec<[u8; 1024]>,
33    relocs: SmallVec<[AsmReloc; 16]>,
34    symbols: SmallVec<[SymData; 16]>,
35    defined_symbols: SmallVec<[(ExternalName, Label); 4]>,
36    label_offsets: SmallVec<[CodeOffset; 16]>,
37    pending_fixup_records: SmallVec<[AsmFixup; 16]>,
38    pending_fixup_deadline: u32,
39    pending_constants: SmallVec<[Constant; 16]>,
40    pending_constants_size: CodeOffset,
41    used_constants: SmallVec<[(Constant, CodeOffset); 4]>,
42    constants: SmallVec<[(ConstantData, AsmConstant); 4]>,
43    fixup_records: BinaryHeap<AsmFixup>,
44    patch_blocks: SmallVec<[PendingPatchBlock; 4]>,
45    patch_sites: SmallVec<[PendingPatchSite; 8]>,
46    #[cfg(feature = "x86")]
47    x86_branch_relaxations: SmallVec<[X86BranchRelaxation; 8]>,
48    #[cfg(feature = "x86")]
49    alignment_constraints: SmallVec<[(CodeOffset, CodeOffset); 4]>,
50    error: Option<AsmError>,
51}
52
53/// Private rollback point for one raw instruction emission.
54///
55/// Raw backend emission may append bytes and metadata, and x86 may rewrite
56/// bytes appended by the same attempt. It must not mutate metadata or bytes
57/// that predate this checkpoint; island emission is a finalization operation.
58#[derive(Clone, Copy)]
59#[cfg(any(feature = "x86", feature = "aarch64", feature = "riscv"))]
60pub(crate) struct EmissionCheckpoint {
61    data_len: usize,
62    relocs_len: usize,
63    symbols_len: usize,
64    defined_symbols_len: usize,
65    label_offsets_len: usize,
66    pending_fixup_records_len: usize,
67    pending_fixup_deadline: u32,
68    pending_constants_len: usize,
69    pending_constants_size: CodeOffset,
70    used_constants_len: usize,
71    constants_len: usize,
72    patch_blocks_len: usize,
73    patch_sites_len: usize,
74    #[cfg(feature = "x86")]
75    x86_branch_relaxations_len: usize,
76    #[cfg(feature = "x86")]
77    alignment_constraints_len: usize,
78}
79
80#[cfg(feature = "x86")]
81#[derive(Clone, Copy)]
82struct X86BranchRelaxation {
83    opcode_offset: CodeOffset,
84    label: Label,
85    short_opcode: u8,
86    near_size: u8,
87}
88
89#[derive(Clone, Copy)]
90struct PendingPatchBlock {
91    offset: CodeOffset,
92    size: CodeOffset,
93    align: CodeOffset,
94}
95
96#[derive(Clone, Copy)]
97enum PendingPatchTarget {
98    Offset(CodeOffset),
99    Label(Label),
100}
101
102#[derive(Clone, Copy)]
103struct PendingPatchSite {
104    offset: CodeOffset,
105    kind: LabelUse,
106    target: PendingPatchTarget,
107    addend: i64,
108}
109
110/// An external name in a user-defined symbol table.
111///
112/// Cranelift-style opaque key: asmkit does not interpret `namespace` or `index`.
113/// Hosts commonly use separate namespaces for functions vs data, but that
114/// convention is not enforced here.
115#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
116pub struct UserExternalName {
117    pub namespace: u32,
118    pub index: u32,
119}
120
121impl UserExternalName {
122    /// Creates a new user external name.
123    pub const fn new(namespace: u32, index: u32) -> Self {
124        Self { namespace, index }
125    }
126}
127
128impl fmt::Display for UserExternalName {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "u{}:{}", self.namespace, self.index)
131    }
132}
133
134/// Name of an external (or exported) symbol.
135///
136/// Either a string [`Symbol`](ExternalName::Symbol) for human-readable /
137/// ELF-like names, or a [`User`](ExternalName::User) namespace+index key for
138/// backends that do not use string symbols.
139#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
140pub enum ExternalName {
141    Symbol(Cow<'static, str>),
142    User(UserExternalName),
143}
144
145impl ExternalName {
146    /// Creates a user-defined external name from `namespace` and `index`.
147    pub const fn user(namespace: u32, index: u32) -> Self {
148        Self::User(UserExternalName::new(namespace, index))
149    }
150}
151
152impl fmt::Display for ExternalName {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match self {
155            Self::Symbol(name) => f.write_str(name),
156            Self::User(name) => fmt::Display::fmt(name, f),
157        }
158    }
159}
160
161impl From<UserExternalName> for ExternalName {
162    fn from(name: UserExternalName) -> Self {
163        Self::User(name)
164    }
165}
166
167impl From<&'static str> for ExternalName {
168    fn from(name: &'static str) -> Self {
169        Self::Symbol(Cow::Borrowed(name))
170    }
171}
172
173impl From<alloc::string::String> for ExternalName {
174    fn from(name: alloc::string::String) -> Self {
175        Self::Symbol(Cow::Owned(name))
176    }
177}
178
179impl From<Cow<'static, str>> for ExternalName {
180    fn from(name: Cow<'static, str>) -> Self {
181        Self::Symbol(name)
182    }
183}
184#[derive(Clone, PartialEq, Eq, Hash, Debug)]
185pub enum RelocTarget {
186    Sym(Sym),
187    Label(Label),
188}
189
190#[derive(Copy, Clone, PartialEq, Eq, Debug)]
191pub enum RelocDistance {
192    Near,
193    Far,
194}
195
196#[derive(Clone, PartialEq, Eq)]
197pub(crate) struct SymData {
198    pub(crate) name: ExternalName,
199    pub(crate) distance: RelocDistance,
200}
201
202/// A relocation resulting from emitting assembly.
203#[derive(Clone, PartialEq, Eq, Hash, Debug)]
204pub struct AsmReloc {
205    pub offset: CodeOffset,
206    pub kind: Reloc,
207    pub addend: i64,
208    pub target: RelocTarget,
209}
210/// A fixup to perform on the buffer once code is emitted.
211/// Fixups always refer to labels and patch the code based on label offsets.
212/// Hence, they are like relocations, but internal to one buffer.
213#[derive(Clone, Copy, PartialEq, PartialOrd, Ord, Eq)]
214pub(crate) struct AsmFixup {
215    pub label: Label,
216    pub offset: CodeOffset,
217    pub kind: LabelUse,
218}
219
220impl AsmFixup {
221    fn deadline(&self) -> CodeOffset {
222        self.offset.saturating_sub(self.kind.max_pos_range())
223    }
224}
225
226/// Metadata about a constant.
227#[derive(Clone, Copy)]
228struct AsmConstant {
229    /// A label which has not yet been bound which can be used for this
230    /// constant.
231    ///
232    /// This is lazily created when a label is requested for a constant and is
233    /// cleared when a constant is emitted.
234    upcoming_label: Option<Label>,
235    /// Required alignment.
236    align: CodeOffset,
237    /// The byte size of this constant.
238    size: usize,
239}
240
241/// A `CodeBuffer` once emission is completed: holds generated code and records,
242/// without fixups. This allows the type to be independent of the backend.
243pub struct CodeBufferFinalized {
244    pub(crate) data: SmallVec<[u8; 1024]>,
245    pub(crate) relocs: SmallVec<[AsmReloc; 16]>,
246    pub(crate) symbols: SmallVec<[SymData; 16]>,
247    pub(crate) label_offsets: SmallVec<[CodeOffset; 16]>,
248    pub(crate) defined_symbols: SmallVec<[(ExternalName, CodeOffset); 4]>,
249    pub(crate) alignment: u32,
250    pub(crate) patch_catalog: PatchCatalog,
251}
252
253/// Executable memory loaded from a finalized code buffer with relocations applied.
254#[cfg(feature = "jit")]
255pub struct LoadedRelocatedCode {
256    span: Span,
257    code_size: usize,
258    got_targets: Vec<RelocTarget>,
259}
260
261#[cfg(feature = "jit")]
262impl LoadedRelocatedCode {
263    pub const fn rx(&self) -> *const u8 {
264        self.span.rx()
265    }
266
267    pub const fn rw(&self) -> *mut u8 {
268        self.span.rw()
269    }
270
271    pub const fn span(&self) -> &Span {
272        &self.span
273    }
274
275    pub const fn code_size(&self) -> usize {
276        self.code_size
277    }
278
279    pub fn got_targets(&self) -> &[RelocTarget] {
280        &self.got_targets
281    }
282
283    pub fn got_size(&self) -> usize {
284        self.got_targets.len() * core::mem::size_of::<usize>()
285    }
286
287    pub fn got_rx(&self) -> *const u8 {
288        self.rx().wrapping_add(self.code_size)
289    }
290
291    pub fn got_rw(&self) -> *mut u8 {
292        self.rw().wrapping_add(self.code_size)
293    }
294}
295
296pub fn reloc_uses_got(kind: Reloc) -> bool {
297    matches!(
298        kind,
299        Reloc::X86GOTPCRel4
300            | Reloc::RiscvGotHi20
301            | Reloc::RiscvPCRelLo12I
302            | Reloc::Aarch64AdrGotPage21
303            | Reloc::Aarch64Ld64GotLo12Nc
304    )
305}
306
307#[cfg(feature = "jit")]
308pub(crate) fn got_slot_index(got_targets: &[RelocTarget], target: &RelocTarget) -> Option<usize> {
309    got_targets.iter().position(|item| item == target)
310}
311
312impl CodeBufferFinalized {
313    pub fn total_size(&self) -> usize {
314        self.data.len()
315    }
316
317    pub fn data(&self) -> &[u8] {
318        &self.data[..]
319    }
320
321    pub fn symbol_name(&self, sym: Sym) -> Option<&ExternalName> {
322        self.symbols
323            .get(sym.id() as usize)
324            .map(|symbol| &symbol.name)
325    }
326
327    pub fn symbol_distance(&self, sym: Sym) -> Option<RelocDistance> {
328        self.symbols
329            .get(sym.id() as usize)
330            .map(|symbol| symbol.distance)
331    }
332
333    /// Offset of a symbol exported with [`CodeBuffer::bind_symbol`], if present.
334    ///
335    /// After loading, the runtime address of the symbol is `rx + offset`.
336    pub fn defined_symbol_offset(&self, name: &ExternalName) -> Option<CodeOffset> {
337        self.defined_symbols
338            .iter()
339            .find(|(defined, _)| defined == name)
340            .map(|(_, offset)| *offset)
341    }
342
343    /// Like [`Self::defined_symbol_offset`], for string [`ExternalName::Symbol`] exports.
344    pub fn defined_symbol_str(&self, name: &str) -> Option<CodeOffset> {
345        self.defined_symbols
346            .iter()
347            .find(|(defined, _)| matches!(defined, ExternalName::Symbol(s) if s.as_ref() == name))
348            .map(|(_, offset)| *offset)
349    }
350
351    pub fn relocs(&self) -> &[AsmReloc] {
352        &self.relocs[..]
353    }
354
355    pub fn alignment(&self) -> u32 {
356        self.alignment
357    }
358
359    /// Allocate this code buffer in executable memory and return a `Span` referring to it.
360    /// This will also write the code into the allocated memory. To execute
361    /// code you can simply use [`span.rx()`](Span::rx) to get a pointer to read+exec memory
362    /// and transmute that to a function pointer of the appropriate type.
363    #[cfg(feature = "jit")]
364    pub fn allocate(&self, jit_allocator: &mut JitAllocator) -> Result<Span, AsmError> {
365        let mut span = jit_allocator.alloc(self.data().len())?;
366
367        // SAFETY: `span` was just allocated by `jit_allocator` with exactly
368        // `self.data().len()` bytes.
369        unsafe {
370            jit_allocator.write(&mut span, |span| {
371                span.rw()
372                    .copy_from_nonoverlapping(self.data().as_ptr(), self.data().len());
373            })?;
374        }
375
376        Ok(span)
377    }
378
379    /// Allocate executable memory and apply relocations, including GOT setup in JIT mode.
380    ///
381    /// GOT entries are created automatically for relocations that require them and populated
382    /// with values returned by `get_address`.
383    ///
384    /// Relocations targeting a [`Label`](RelocTarget::Label) are resolved internally against
385    /// the label offsets recorded at finalize time; the callbacks only see external symbols.
386    #[cfg(feature = "jit")]
387    pub fn allocate_relocated(
388        &self,
389        jit_allocator: &mut JitAllocator,
390        get_address: impl Fn(&RelocTarget) -> *const u8,
391        get_plt_entry: impl Fn(&RelocTarget) -> *const u8,
392    ) -> Result<LoadedRelocatedCode, AsmError> {
393        let mut got_targets = Vec::new();
394
395        for reloc in &self.relocs {
396            if reloc_uses_got(reloc.kind) && !got_targets.iter().any(|item| item == &reloc.target) {
397                got_targets.push(reloc.target.clone());
398            }
399        }
400
401        let got_size = got_targets
402            .len()
403            .checked_mul(core::mem::size_of::<usize>())
404            .ok_or(AsmError::TooLarge)?;
405        let total_size = self
406            .data()
407            .len()
408            .checked_add(got_size)
409            .ok_or(AsmError::TooLarge)?;
410        let mut span = jit_allocator.alloc(total_size)?;
411
412        let mut relocation_result = Ok(());
413        // SAFETY: `span` was just allocated with `total_size` bytes (code plus
414        // GOT); `JitAllocator::write` validates it and handles JIT write access
415        // and the instruction-cache flush. Every write inside `perform_relocations`
416        // is bounds-checked against `code_size`, and the GOT entries are written
417        // through `got_rw`, which points inside the same mapping.
418        unsafe {
419            jit_allocator.write(&mut span, |span| {
420                relocation_result = (|| {
421                    span.rw()
422                        .copy_from_nonoverlapping(self.data().as_ptr(), self.data().len());
423
424                    let rx = span.rx();
425                    let resolve = |target: &RelocTarget,
426                                   fallback: &dyn Fn(&RelocTarget) -> *const u8|
427                     -> Result<*const u8, AsmError> {
428                        if let RelocTarget::Label(label) = target {
429                            let offset = self
430                                .label_offsets
431                                .get(label.id() as usize)
432                                .copied()
433                                .ok_or(AsmError::InvalidArgument)?;
434                            if offset == u32::MAX || offset as usize > self.data().len() {
435                                return Err(AsmError::UnboundLabel);
436                            }
437                            return Ok(rx.add(offset as usize));
438                        }
439                        let address = fallback(target);
440                        if address.is_null() {
441                            return Err(AsmError::InvalidArgument);
442                        }
443                        Ok(address)
444                    };
445
446                    let got_rw = span.rw().add(self.data().len()) as *mut usize;
447                    for (index, target) in got_targets.iter().enumerate() {
448                        let addr = resolve(target, &get_address)?;
449                        got_rw.add(index).write_unaligned(addr.addr());
450                    }
451
452                    let got_rx = rx.add(self.data().len());
453                    perform_relocations(
454                        span.rw(),
455                        rx,
456                        self.data().len(),
457                        &self.relocs,
458                        |target| resolve(target, &get_address),
459                        |target| {
460                            let index = got_slot_index(&got_targets, target)
461                                .ok_or(AsmError::InvalidState)?;
462                            let offset = index
463                                .checked_mul(core::mem::size_of::<usize>())
464                                .ok_or(AsmError::TooLarge)?;
465                            Ok(got_rx.add(offset))
466                        },
467                        |target| resolve(target, &get_plt_entry),
468                    )
469                })();
470            })?;
471        }
472        relocation_result?;
473
474        Ok(LoadedRelocatedCode {
475            span,
476            code_size: self.data().len(),
477            got_targets,
478        })
479    }
480
481    /// Allocate executable memory and apply relocations, resolving external
482    /// symbols through `resolve`.
483    ///
484    /// This is the ergonomic counterpart of [`Self::allocate_relocated`]: every
485    /// undefined external symbol (declared with [`CodeBuffer::extern_sym`],
486    /// [`CodeBuffer::extern_user`], or surviving a
487    /// [`Linker`](crate::core::linker::Linker) link) is passed to `resolve`,
488    /// which must return its address. Symbols defined inside the image (label
489    /// targets and linked-in definitions) are resolved internally.
490    #[cfg(feature = "jit")]
491    pub fn allocate_resolved(
492        &self,
493        jit_allocator: &mut JitAllocator,
494        resolve: impl Fn(&ExternalName) -> *const u8,
495    ) -> Result<LoadedRelocatedCode, AsmError> {
496        let by_name = |target: &RelocTarget| match target {
497            RelocTarget::Sym(sym) => match self.symbol_name(*sym) {
498                Some(name) => resolve(name),
499                None => core::ptr::null(),
500            },
501            // Label targets are resolved internally by `allocate_relocated`.
502            RelocTarget::Label(_) => core::ptr::null(),
503        };
504
505        self.allocate_relocated(jit_allocator, by_name, by_name)
506    }
507}
508
509impl CodeBuffer {
510    /// Creates a buffer for `env`.
511    pub fn new(env: Environment) -> Self {
512        Self {
513            env,
514            data: SmallVec::new(),
515            relocs: SmallVec::new(),
516            symbols: SmallVec::new(),
517            defined_symbols: SmallVec::new(),
518            label_offsets: SmallVec::new(),
519            pending_fixup_records: SmallVec::new(),
520            pending_fixup_deadline: 0,
521            pending_constants: SmallVec::new(),
522            pending_constants_size: 0,
523            used_constants: SmallVec::new(),
524            constants: SmallVec::new(),
525            fixup_records: BinaryHeap::new(),
526            patch_blocks: SmallVec::new(),
527            patch_sites: SmallVec::new(),
528            #[cfg(feature = "x86")]
529            x86_branch_relaxations: SmallVec::new(),
530            #[cfg(feature = "x86")]
531            alignment_constraints: SmallVec::new(),
532            error: None,
533        }
534    }
535
536    /// Creates a buffer for the host target.
537    pub fn host() -> Self {
538        Self::new(Environment::host())
539    }
540
541    pub fn clear(&mut self) {
542        self.data.clear();
543        self.relocs.clear();
544        self.label_offsets.clear();
545        self.pending_fixup_records.clear();
546        self.constants.clear();
547        self.fixup_records.clear();
548        self.symbols.clear();
549        self.defined_symbols.clear();
550        self.used_constants.clear();
551        self.pending_fixup_deadline = 0;
552        self.pending_constants_size = 0;
553        self.pending_constants.clear();
554        self.patch_blocks.clear();
555        self.patch_sites.clear();
556        #[cfg(feature = "x86")]
557        self.x86_branch_relaxations.clear();
558        #[cfg(feature = "x86")]
559        self.alignment_constraints.clear();
560        self.error = None;
561    }
562
563    /// Returns the first error recorded by a void assembler operation.
564    pub fn error(&self) -> Option<&AsmError> {
565        self.error.as_ref()
566    }
567
568    pub(crate) fn record_error(&mut self, error: AsmError) {
569        if self.error.is_none() {
570            self.error = Some(error);
571        }
572    }
573
574    #[cfg(any(feature = "x86", feature = "aarch64", feature = "riscv"))]
575    pub(crate) fn checkpoint(&self) -> EmissionCheckpoint {
576        EmissionCheckpoint {
577            data_len: self.data.len(),
578            relocs_len: self.relocs.len(),
579            symbols_len: self.symbols.len(),
580            defined_symbols_len: self.defined_symbols.len(),
581            label_offsets_len: self.label_offsets.len(),
582            pending_fixup_records_len: self.pending_fixup_records.len(),
583            pending_fixup_deadline: self.pending_fixup_deadline,
584            pending_constants_len: self.pending_constants.len(),
585            pending_constants_size: self.pending_constants_size,
586            used_constants_len: self.used_constants.len(),
587            constants_len: self.constants.len(),
588            patch_blocks_len: self.patch_blocks.len(),
589            patch_sites_len: self.patch_sites.len(),
590            #[cfg(feature = "x86")]
591            x86_branch_relaxations_len: self.x86_branch_relaxations.len(),
592            #[cfg(feature = "x86")]
593            alignment_constraints_len: self.alignment_constraints.len(),
594        }
595    }
596
597    #[cfg(any(feature = "x86", feature = "aarch64", feature = "riscv"))]
598    pub(crate) fn rollback(&mut self, checkpoint: EmissionCheckpoint) {
599        self.data.truncate(checkpoint.data_len);
600        self.relocs.truncate(checkpoint.relocs_len);
601        self.symbols.truncate(checkpoint.symbols_len);
602        self.defined_symbols
603            .truncate(checkpoint.defined_symbols_len);
604        self.label_offsets.truncate(checkpoint.label_offsets_len);
605        self.pending_fixup_records
606            .truncate(checkpoint.pending_fixup_records_len);
607        self.pending_fixup_deadline = checkpoint.pending_fixup_deadline;
608        self.pending_constants
609            .truncate(checkpoint.pending_constants_len);
610        self.pending_constants_size = checkpoint.pending_constants_size;
611        self.used_constants.truncate(checkpoint.used_constants_len);
612        self.constants.truncate(checkpoint.constants_len);
613        self.patch_blocks.truncate(checkpoint.patch_blocks_len);
614        self.patch_sites.truncate(checkpoint.patch_sites_len);
615        #[cfg(feature = "x86")]
616        self.x86_branch_relaxations
617            .truncate(checkpoint.x86_branch_relaxations_len);
618        #[cfg(feature = "x86")]
619        self.alignment_constraints
620            .truncate(checkpoint.alignment_constraints_len);
621    }
622    pub fn env(&self) -> &Environment {
623        &self.env
624    }
625
626    pub fn data(&self) -> &[u8] {
627        &self.data
628    }
629
630    /// Returns the byte at `offset`.
631    ///
632    /// Used by encoder patch-up paths (e.g. X86 LEA/abs32 fixups).
633    #[cfg(feature = "x86")]
634    pub(crate) fn byte_at(&self, offset: CodeOffset) -> u8 {
635        self.data[offset as usize]
636    }
637
638    /// Overwrites the byte at `offset`.
639    #[cfg(feature = "x86")]
640    pub(crate) fn set_byte_at(&mut self, offset: CodeOffset, value: u8) {
641        self.data[offset as usize] = value;
642    }
643
644    /// Inserts a byte at `offset`, shifting all subsequent bytes.
645    ///
646    /// This is a rare encoder patch-up operation; prefer appending.
647    #[cfg(feature = "x86")]
648    pub(crate) fn insert_at(&mut self, offset: CodeOffset, value: u8) {
649        self.data.insert(offset as usize, value);
650    }
651
652    /// Removes the byte at `offset`, shifting all subsequent bytes.
653    ///
654    /// This is a rare encoder patch-up operation; prefer appending.
655    #[cfg(feature = "x86")]
656    pub(crate) fn remove_at(&mut self, offset: CodeOffset) {
657        self.data.remove(offset as usize);
658    }
659
660    #[cfg(feature = "x86")]
661    pub(crate) fn record_x86_branch_relaxation(
662        &mut self,
663        opcode_offset: CodeOffset,
664        label: Label,
665        short_opcode: u8,
666        near_size: u8,
667    ) {
668        debug_assert!(matches!(near_size, 5 | 6));
669        debug_assert!((opcode_offset as usize) + near_size as usize <= self.data.len());
670        debug_assert!(self.label_offsets.get(label.id() as usize).is_some());
671        self.x86_branch_relaxations.push(X86BranchRelaxation {
672            opcode_offset,
673            label,
674            short_opcode,
675            near_size,
676        });
677    }
678
679    pub fn relocs(&self) -> &[AsmReloc] {
680        &self.relocs
681    }
682
683    pub fn put1(&mut self, value: u8) {
684        if self.error.is_some() {
685            return;
686        }
687        self.data.push(value);
688    }
689
690    pub fn put2(&mut self, value: u16) {
691        if self.error.is_some() {
692            return;
693        }
694        self.data.extend_from_slice(&value.to_ne_bytes());
695    }
696
697    pub fn put4(&mut self, value: u32) {
698        if self.error.is_some() {
699            return;
700        }
701        self.data.extend_from_slice(&value.to_ne_bytes());
702    }
703
704    pub fn put8(&mut self, value: u64) {
705        if self.error.is_some() {
706            return;
707        }
708        self.data.extend_from_slice(&value.to_ne_bytes());
709    }
710
711    pub fn write_u8(&mut self, value: u8) {
712        if self.error.is_some() {
713            return;
714        }
715        self.data.push(value);
716    }
717
718    pub fn write_u16(&mut self, value: u16) {
719        if self.error.is_some() {
720            return;
721        }
722        self.data.extend_from_slice(&value.to_ne_bytes());
723    }
724
725    pub fn write_u32(&mut self, value: u32) {
726        if self.error.is_some() {
727            return;
728        }
729        self.data.extend_from_slice(&value.to_ne_bytes());
730    }
731
732    pub fn write_u64(&mut self, value: u64) {
733        if self.error.is_some() {
734            return;
735        }
736        self.data.extend_from_slice(&value.to_ne_bytes());
737    }
738
739    pub fn add_symbol(&mut self, name: impl Into<ExternalName>, distance: RelocDistance) -> Sym {
740        if self.error.is_some() {
741            return Sym::new();
742        }
743        let ix = self.symbols.len();
744        self.symbols.push(SymData {
745            distance,
746            name: name.into(),
747        });
748
749        Sym::from_id(ix as u32)
750    }
751
752    /// Declares an external symbol by name, deduplicating: repeated calls with
753    /// the same name return the same `Sym` (the distance of the first
754    /// declaration wins).
755    ///
756    /// The returned symbol can be passed to the architecture assemblers (e.g.
757    /// `ptr64_sym` on x86) which then record the appropriate relocation based
758    /// on the symbol's distance.
759    pub fn extern_sym(
760        &mut self,
761        name: impl Into<Cow<'static, str>>,
762        distance: RelocDistance,
763    ) -> Sym {
764        if self.error.is_some() {
765            return Sym::new();
766        }
767        let name = ExternalName::Symbol(name.into());
768        if let Some(ix) = self.symbols.iter().position(|sym| sym.name == name) {
769            return Sym::from_id(ix as u32);
770        }
771
772        self.add_symbol(name, distance)
773    }
774
775    /// Declares an external symbol by user namespace+index, deduplicating:
776    /// repeated calls with the same key return the same `Sym` (the distance of
777    /// the first declaration wins).
778    pub fn extern_user(&mut self, namespace: u32, index: u32, distance: RelocDistance) -> Sym {
779        if self.error.is_some() {
780            return Sym::new();
781        }
782        let name = ExternalName::user(namespace, index);
783        if let Some(ix) = self.symbols.iter().position(|sym| sym.name == name) {
784            return Sym::from_id(ix as u32);
785        }
786
787        self.add_symbol(name, distance)
788    }
789
790    /// Exports `label` under `name`, making it a defined symbol that other
791    /// modules can resolve at link time (see [`crate::core::linker::Linker`]).
792    ///
793    /// `name` may be a string ([`ExternalName::Symbol`]) or a user key
794    /// ([`ExternalName::User`]).
795    pub fn bind_symbol(&mut self, name: impl Into<ExternalName>, label: Label) {
796        if self.error.is_some() {
797            return;
798        }
799        if self.label_offsets.get(label.id() as usize).is_none() {
800            self.record_error(AsmError::InvalidArgument);
801            return;
802        }
803        self.defined_symbols.push((name.into(), label));
804    }
805
806    pub fn symbol_distance(&self, sym: Sym) -> Option<RelocDistance> {
807        self.symbols
808            .get(sym.id() as usize)
809            .map(|symbol| symbol.distance)
810    }
811
812    pub fn symbol_name(&self, sym: Sym) -> Option<&ExternalName> {
813        self.symbols
814            .get(sym.id() as usize)
815            .map(|symbol| &symbol.name)
816    }
817
818    pub fn get_label(&mut self) -> Label {
819        if self.error.is_some() {
820            return Label::new();
821        }
822        let l = self.label_offsets.len();
823        self.label_offsets.push(u32::MAX);
824        Label::from_id(l as _)
825    }
826
827    pub fn is_bound(&self, label: Label) -> bool {
828        self.label_offsets
829            .get(label.id() as usize)
830            .is_some_and(|offset| *offset != u32::MAX)
831    }
832
833    /// Number of labels created so far; label ids below this count are valid.
834    pub fn label_count(&self) -> u32 {
835        self.label_offsets.len() as u32
836    }
837
838    pub fn get_label_for_constant(&mut self, constant: Constant) -> Label {
839        if self.error.is_some() {
840            return Label::new();
841        }
842        let Some((_, metadata)) = self.constants.get(constant.0 as usize) else {
843            self.record_error(AsmError::InvalidArgument);
844            return Label::new();
845        };
846        let metadata = *metadata;
847        let AsmConstant {
848            upcoming_label,
849            size,
850            ..
851        } = metadata;
852        if let Some(label) = upcoming_label {
853            return label;
854        }
855
856        let label = self.get_label();
857        self.pending_constants.push(constant);
858        self.pending_constants_size += size as u32;
859        self.constants[constant.0 as usize].1.upcoming_label = Some(label);
860        label
861    }
862
863    pub fn add_constant(&mut self, constant: impl Into<ConstantData>) -> Constant {
864        if self.error.is_some() {
865            return Constant(u32::MAX);
866        }
867        let c = self.constants.len() as u32;
868        let data = constant.into();
869        let x = AsmConstant {
870            upcoming_label: None,
871            align: data.alignment() as _,
872            size: data.as_slice().len(),
873        };
874        self.constants.push((data, x));
875        Constant(c)
876    }
877
878    pub fn use_label_at_offset(&mut self, offset: CodeOffset, label: Label, kind: LabelUse) {
879        if self.error.is_some() {
880            return;
881        }
882        if let Err(error) = kind.validate_for_arch(self.env.arch()) {
883            self.record_error(error);
884            return;
885        }
886        if (offset as usize).checked_add(kind.patch_size()).is_none() {
887            self.record_error(AsmError::TooLarge);
888            return;
889        }
890        if self.label_offsets.get(label.id() as usize).is_none() {
891            self.record_error(AsmError::InvalidArgument);
892            return;
893        }
894        let fixup = AsmFixup {
895            kind,
896            label,
897            offset,
898        };
899
900        self.pending_fixup_records.push(fixup);
901        self.pending_fixup_deadline = self.pending_fixup_deadline.min(fixup.deadline());
902    }
903
904    /// Align up to the given alignment.
905    pub fn try_align_to(&mut self, align_to: CodeOffset) -> Result<(), AsmError> {
906        if let Some(error) = self.error.clone() {
907            return Err(error);
908        }
909        if !align_to.is_power_of_two() {
910            return Err(AsmError::InvalidArgument);
911        }
912        while self.cur_offset() & (align_to - 1) != 0 {
913            self.write_u8(0);
914        }
915        #[cfg(feature = "x86")]
916        if align_to > 1 {
917            self.alignment_constraints
918                .push((self.cur_offset(), align_to));
919        }
920        Ok(())
921    }
922
923    pub fn align_to(&mut self, align_to: CodeOffset) {
924        if let Err(error) = self.try_align_to(align_to) {
925            self.record_error(error);
926        }
927    }
928
929    pub fn cur_offset(&self) -> CodeOffset {
930        self.data.len() as _
931    }
932
933    pub fn try_bind_label(&mut self, label: Label) -> Result<(), AsmError> {
934        if let Some(error) = self.error.clone() {
935            return Err(error);
936        }
937        let current_offset = self.cur_offset();
938        let Some(offset) = self.label_offsets.get_mut(label.id() as usize) else {
939            return Err(AsmError::InvalidArgument);
940        };
941        if *offset != u32::MAX {
942            return Err(AsmError::InvalidState);
943        }
944        *offset = current_offset;
945        Ok(())
946    }
947
948    pub fn bind_label(&mut self, label: Label) {
949        if self.error.is_some() {
950            return;
951        }
952        if let Err(error) = self.try_bind_label(label) {
953            self.record_error(error);
954        }
955    }
956
957    pub fn label_offset(&self, label: Label) -> u32 {
958        self.label_offsets
959            .get(label.id() as usize)
960            .copied()
961            .unwrap_or(u32::MAX)
962    }
963
964    pub fn add_reloc(&mut self, kind: Reloc, target: RelocTarget, addend: i64) {
965        let offset = self.cur_offset();
966        self.add_reloc_at_offset(offset, kind, target, addend);
967    }
968
969    pub fn add_reloc_at_offset(
970        &mut self,
971        offset: CodeOffset,
972        kind: Reloc,
973        target: RelocTarget,
974        addend: i64,
975    ) {
976        if self.error.is_some() {
977            return;
978        }
979        if !kind.supports_arch(self.env.arch()) {
980            self.record_error(AsmError::InvalidArch);
981            return;
982        }
983        let valid_target = match target {
984            RelocTarget::Sym(sym) => self.symbols.get(sym.id() as usize).is_some(),
985            RelocTarget::Label(label) => self.label_offsets.get(label.id() as usize).is_some(),
986        };
987        if !valid_target {
988            self.record_error(AsmError::InvalidArgument);
989            return;
990        }
991        self.relocs.push(AsmReloc {
992            addend,
993            kind,
994            offset,
995            target,
996        })
997    }
998
999    /// Reserve a nop-filled island for later custom rewriting (JSC `padBeforePatch`).
1000    ///
1001    /// Returns a [`PatchableBlock`] handle; the block is also recorded in the patch catalog
1002    /// for `finish_patched` / linker rebasing.
1003    pub fn reserve_patch_block(
1004        &mut self,
1005        size: CodeOffset,
1006        align: CodeOffset,
1007    ) -> Result<PatchableBlock, AsmError> {
1008        if let Some(error) = self.error.clone() {
1009            return Err(error);
1010        }
1011        let min_align = minimum_patch_alignment(self.env.arch());
1012        let align = align.max(min_align);
1013        if size == 0 || !align.is_power_of_two() {
1014            return Err(AsmError::InvalidArgument);
1015        }
1016        let nop_size = match self.env.arch() {
1017            Arch::X86 | Arch::X64 => 1,
1018            Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64 => 4,
1019            _ => return Err(AsmError::InvalidArch),
1020        };
1021        if size as usize % nop_size != 0 {
1022            return Err(AsmError::InvalidArgument);
1023        }
1024
1025        self.try_align_to(align)?;
1026        let arch = self.env.arch();
1027        let offset = self.cur_offset();
1028        let block = self.get_appended_space(size as usize);
1029        fill_with_nops(arch, block)?;
1030
1031        self.patch_blocks.push(PendingPatchBlock {
1032            offset,
1033            size,
1034            align,
1035        });
1036        // SAFETY: we just reserved and recorded this nop island.
1037        Ok(unsafe { PatchableBlock::new(offset, size, arch) })
1038    }
1039
1040    pub fn record_patch_block(
1041        &mut self,
1042        offset: CodeOffset,
1043        size: CodeOffset,
1044        align: CodeOffset,
1045    ) -> PatchBlockId {
1046        match self.try_record_patch_block(offset, size, align) {
1047            Ok(id) => id,
1048            Err(error) => {
1049                self.record_error(error);
1050                PatchBlockId::from_index(usize::MAX)
1051            }
1052        }
1053    }
1054
1055    pub fn try_record_patch_block(
1056        &mut self,
1057        offset: CodeOffset,
1058        size: CodeOffset,
1059        align: CodeOffset,
1060    ) -> Result<PatchBlockId, AsmError> {
1061        if let Some(error) = self.error.clone() {
1062            return Err(error);
1063        }
1064        if size == 0 || align == 0 || !align.is_power_of_two() || offset & (align - 1) != 0 {
1065            return Err(AsmError::InvalidArgument);
1066        }
1067        let end = (offset as usize)
1068            .checked_add(size as usize)
1069            .ok_or(AsmError::TooLarge)?;
1070        if end > self.data.len() {
1071            return Err(AsmError::InvalidArgument);
1072        }
1073        let id = PatchBlockId::from_index(self.patch_blocks.len());
1074        self.patch_blocks.push(PendingPatchBlock {
1075            offset,
1076            size,
1077            align,
1078        });
1079        Ok(id)
1080    }
1081
1082    pub fn record_patch_site(
1083        &mut self,
1084        offset: CodeOffset,
1085        kind: LabelUse,
1086        target_offset: CodeOffset,
1087    ) -> PatchSiteId {
1088        match self.try_record_patch_site(offset, kind, target_offset) {
1089            Ok(id) => id,
1090            Err(error) => {
1091                self.record_error(error);
1092                PatchSiteId::from_index(usize::MAX)
1093            }
1094        }
1095    }
1096
1097    pub fn try_record_patch_site(
1098        &mut self,
1099        offset: CodeOffset,
1100        kind: LabelUse,
1101        target_offset: CodeOffset,
1102    ) -> Result<PatchSiteId, AsmError> {
1103        if let Some(error) = self.error.clone() {
1104            return Err(error);
1105        }
1106        self.validate_patch_site_offset(offset, kind)?;
1107        let id = PatchSiteId::from_index(self.patch_sites.len());
1108        self.patch_sites.push(PendingPatchSite {
1109            offset,
1110            kind,
1111            target: PendingPatchTarget::Offset(target_offset),
1112            addend: 0,
1113        });
1114        Ok(id)
1115    }
1116
1117    pub fn record_label_patch_site(
1118        &mut self,
1119        offset: CodeOffset,
1120        label: Label,
1121        kind: LabelUse,
1122    ) -> PatchSiteId {
1123        match self.try_record_label_patch_site(offset, label, kind) {
1124            Ok(id) => id,
1125            Err(error) => {
1126                self.record_error(error);
1127                PatchSiteId::from_index(usize::MAX)
1128            }
1129        }
1130    }
1131
1132    pub fn try_record_label_patch_site(
1133        &mut self,
1134        offset: CodeOffset,
1135        label: Label,
1136        kind: LabelUse,
1137    ) -> Result<PatchSiteId, AsmError> {
1138        if let Some(error) = self.error.clone() {
1139            return Err(error);
1140        }
1141        if self.label_offsets.get(label.id() as usize).is_none() {
1142            return Err(AsmError::InvalidArgument);
1143        }
1144        self.validate_patch_site_offset(offset, kind)?;
1145        let id = PatchSiteId::from_index(self.patch_sites.len());
1146        self.patch_sites.push(PendingPatchSite {
1147            offset,
1148            kind,
1149            target: PendingPatchTarget::Label(label),
1150            addend: 0,
1151        });
1152        Ok(id)
1153    }
1154
1155    fn validate_patch_site_offset(
1156        &self,
1157        offset: CodeOffset,
1158        kind: LabelUse,
1159    ) -> Result<(), AsmError> {
1160        kind.validate_for_arch(self.env.arch())?;
1161        let end = (offset as usize)
1162            .checked_add(kind.patch_size())
1163            .ok_or(AsmError::TooLarge)?;
1164        if end > self.data.len() {
1165            return Err(AsmError::InvalidArgument);
1166        }
1167        Ok(())
1168    }
1169
1170    fn handle_fixup(&mut self, fixup: AsmFixup) -> Result<(), AsmError> {
1171        let AsmFixup {
1172            kind,
1173            label,
1174            offset,
1175        } = fixup;
1176        let start = offset;
1177        let end = (offset as usize)
1178            .checked_add(kind.patch_size())
1179            .ok_or(AsmError::TooLarge)?;
1180        if end > self.data.len() {
1181            return Err(AsmError::InvalidArgument);
1182        }
1183
1184        let label_offset = self.label_offset(label);
1185        if label_offset != u32::MAX {
1186            if !kind.can_reach(offset, label_offset) {
1187                if label_offset < offset {
1188                    self.emit_veneer(label, offset, kind)?;
1189                } else {
1190                    return Err(AsmError::TooLarge);
1191                }
1192            } else {
1193                let slice = &mut self.data[start as usize..end];
1194
1195                kind.patch(slice, start, label_offset);
1196            }
1197        } else {
1198            // If the offset of this label is not known at this time then
1199            // that means that a veneer is required because after this
1200            // island the target can't be in range of the original target.
1201            self.emit_veneer(label, offset, kind)?;
1202        }
1203        Ok(())
1204    }
1205
1206    /// Emits a "veneer" the `kind` code at `offset` to jump to `label`.
1207    ///
1208    /// This will generate extra machine code, using `kind`, to get a
1209    /// larger-jump-kind than `kind` allows. The code at `offset` is then
1210    /// patched to jump to our new code, and then the new code is enqueued for
1211    /// a fixup to get processed at some later time.
1212    pub fn emit_veneer(
1213        &mut self,
1214        label: Label,
1215        offset: CodeOffset,
1216        kind: LabelUse,
1217    ) -> Result<(), AsmError> {
1218        if let Some(error) = self.error.clone() {
1219            return Err(error);
1220        }
1221        kind.validate_for_arch(self.env.arch())?;
1222        if !kind.supports_veneer() {
1223            return Err(AsmError::UnsupportedInstruction {
1224                reason: "branch range requires an unsupported veneer",
1225            });
1226        }
1227        if self.label_offsets.get(label.id() as usize).is_none() {
1228            return Err(AsmError::InvalidArgument);
1229        }
1230        let start = offset as usize;
1231        let end = start
1232            .checked_add(kind.patch_size())
1233            .ok_or(AsmError::TooLarge)?;
1234        if end > self.data.len() {
1235            return Err(AsmError::InvalidArgument);
1236        }
1237        #[cfg(not(feature = "riscv"))]
1238        {
1239            let _ = (label, offset, kind);
1240            Err(AsmError::UnsupportedInstruction {
1241                reason: "RISC-V veneer support is disabled",
1242            })
1243        }
1244
1245        #[cfg(feature = "riscv")]
1246        {
1247            self.try_align_to(kind.align() as _)?;
1248            let veneer_offset = self.cur_offset();
1249            let slice = &mut self.data[start..end];
1250
1251            kind.patch(slice, offset, veneer_offset);
1252            let veneer_slice = self.get_appended_space(kind.veneer_size());
1253            let (veneer_fixup_off, veneer_label_use) =
1254                kind.generate_veneer(veneer_slice, veneer_offset);
1255
1256            // Register a new use of `label` with our new veneer fixup and
1257            // offset. This'll recalculate deadlines accordingly and
1258            // enqueue this fixup to get processed at some later
1259            // time.
1260            self.use_label_at_offset(veneer_fixup_off, label, veneer_label_use);
1261            if let Some(error) = self.error.clone() {
1262                return Err(error);
1263            }
1264            Ok(())
1265        }
1266    }
1267
1268    /// Reserve appended space and return a mutable slice referring to it.
1269    pub(crate) fn get_appended_space(&mut self, len: usize) -> &mut [u8] {
1270        let off = self.data.len();
1271        let new_len = self.data.len() + len;
1272        self.data.resize(new_len, 0);
1273        &mut self.data[off..]
1274
1275        // Post-invariant: as for `put1()`.
1276    }
1277
1278    /// Returns the maximal offset that islands can reach if `distance` more
1279    /// bytes are appended.
1280    ///
1281    /// This is used to determine if veneers need insertions since jumps that
1282    /// can't reach past this point must get a veneer of some form.
1283    fn worst_case_end_of_island(&self, distance: CodeOffset) -> CodeOffset {
1284        // Assume that all fixups will require veneers and that the veneers are
1285        // the worst-case size for each platform. This is an over-generalization
1286        // to avoid iterating over the `fixup_records` list or maintaining
1287        // information about it as we go along.
1288        let island_worst_case_size =
1289            ((self.fixup_records.len() + self.pending_fixup_records.len()) as u32) * 20
1290                + self.pending_constants_size;
1291        self.cur_offset()
1292            .saturating_add(distance)
1293            .saturating_add(island_worst_case_size)
1294    }
1295
1296    fn should_apply_fixup(&self, fixup: &AsmFixup, forced_threshold: CodeOffset) -> bool {
1297        let label_offset = self.label_offset(fixup.label);
1298        label_offset != u32::MAX
1299            || fixup.offset.saturating_add(fixup.kind.max_pos_range()) < forced_threshold
1300    }
1301    /// Is an island needed within the next N bytes?
1302    pub fn island_needed(&mut self, distance: CodeOffset) -> bool {
1303        let deadline = match self.fixup_records.peek() {
1304            Some(fixup) => fixup
1305                .offset
1306                .saturating_add(fixup.kind.max_pos_range())
1307                .min(self.pending_fixup_deadline),
1308            None => self.pending_fixup_deadline,
1309        };
1310
1311        deadline < u32::MAX && self.worst_case_end_of_island(distance) > deadline
1312    }
1313
1314    /// Emit all pending constants and required pending veneers.
1315    pub fn emit_island(&mut self, distance: CodeOffset) -> Result<(), AsmError> {
1316        if let Some(error) = self.error.clone() {
1317            return Err(error);
1318        }
1319        let forced_threshold = self.worst_case_end_of_island(distance);
1320
1321        for constant in core::mem::take(&mut self.pending_constants) {
1322            let (_, AsmConstant { align, size, .. }) = self.constants[constant.0 as usize];
1323            let label = match self.constants[constant.0 as usize].1.upcoming_label.take() {
1324                Some(label) => label,
1325                None => unreachable!("pending constants always have an upcoming label"),
1326            };
1327            self.try_align_to(align as _)?;
1328            self.try_bind_label(label)?;
1329            self.used_constants.push((constant, self.cur_offset()));
1330            self.get_appended_space(size);
1331        }
1332        // Either handle all pending fixups because they're ready or move them
1333        // onto the `BinaryHeap` tracking all pending fixups if they aren't
1334        // ready.
1335        for fixup in core::mem::take(&mut self.pending_fixup_records) {
1336            if self.should_apply_fixup(&fixup, forced_threshold) {
1337                self.handle_fixup(fixup)?;
1338            } else {
1339                self.fixup_records.push(fixup);
1340            }
1341        }
1342
1343        self.pending_fixup_deadline = u32::MAX;
1344
1345        while let Some(fixup) = self.fixup_records.peek() {
1346            // If this fixup shouldn't be applied, that means its label isn't
1347            // defined yet and there'll be remaining space to apply a veneer if
1348            // necessary in the future after this island. In that situation
1349            // because `fixup_records` is sorted by deadline this loop can
1350            // exit.
1351            if !self.should_apply_fixup(fixup, forced_threshold) {
1352                break;
1353            }
1354            let fixup = match self.fixup_records.pop() {
1355                Some(fixup) => fixup,
1356                None => unreachable!("peek confirmed a fixup is present"),
1357            };
1358            self.handle_fixup(fixup)?;
1359        }
1360        Ok(())
1361    }
1362
1363    fn finish_emission_maybe_forcing_veneers(&mut self) -> Result<(), AsmError> {
1364        while !self.pending_constants.is_empty()
1365            || !self.pending_fixup_records.is_empty()
1366            || !self.fixup_records.is_empty()
1367        {
1368            // `emit_island()` will emit any pending veneers and constants, and
1369            // as a side-effect, will also take care of any fixups with resolved
1370            // labels eagerly.
1371            self.emit_island(u32::MAX)?;
1372        }
1373        Ok(())
1374    }
1375
1376    #[cfg(feature = "x86")]
1377    fn relax_x86_branches(&mut self) -> Result<(), AsmError> {
1378        loop {
1379            let mut selected = None;
1380
1381            for (index, &candidate) in self.x86_branch_relaxations.iter().enumerate() {
1382                let start = candidate.opcode_offset as usize;
1383                let near_size = candidate.near_size as usize;
1384                let end = start.checked_add(near_size).ok_or(AsmError::TooLarge)?;
1385                if end > self.data.len() {
1386                    return Err(AsmError::InvalidState);
1387                }
1388                let valid_encoding = match candidate.near_size {
1389                    5 => self.data[start] == 0xE9 && candidate.short_opcode == 0xEB,
1390                    6 => {
1391                        self.data[start] == 0x0F
1392                            && self.data[start + 1] == candidate.short_opcode.wrapping_add(0x10)
1393                    }
1394                    _ => false,
1395                };
1396                if !valid_encoding {
1397                    return Err(AsmError::InvalidState);
1398                }
1399
1400                let target = self.label_offset(candidate.label);
1401                if target == u32::MAX {
1402                    return Err(AsmError::UnboundLabel);
1403                }
1404                let old_end = candidate
1405                    .opcode_offset
1406                    .checked_add(candidate.near_size as u32)
1407                    .ok_or(AsmError::TooLarge)?;
1408                if target > candidate.opcode_offset && target < old_end {
1409                    return Err(AsmError::InvalidState);
1410                }
1411
1412                let removed = candidate.near_size as u32 - 2;
1413                let target_after = if target >= old_end {
1414                    target - removed
1415                } else {
1416                    target
1417                };
1418                let short_end = candidate
1419                    .opcode_offset
1420                    .checked_add(2)
1421                    .ok_or(AsmError::TooLarge)?;
1422                let displacement = i64::from(target_after) - i64::from(short_end);
1423                if i8::try_from(displacement).is_err() {
1424                    continue;
1425                }
1426
1427                let cut_start = short_end;
1428                let cut_end = old_end;
1429                let preserves_alignment =
1430                    self.alignment_constraints.iter().all(|&(offset, align)| {
1431                        offset < cut_end || (offset - removed) & (align - 1) == 0
1432                    }) && self.patch_blocks.iter().all(|block| {
1433                        block.offset < cut_end || (block.offset - removed) & (block.align - 1) == 0
1434                    });
1435                if !preserves_alignment {
1436                    continue;
1437                }
1438
1439                selected = Some((index, candidate, cut_start, cut_end));
1440                break;
1441            }
1442
1443            let Some((index, candidate, cut_start, cut_end)) = selected else {
1444                return Ok(());
1445            };
1446            let removed = cut_end - cut_start;
1447            let fixup_offset = cut_end - LabelUse::X86JmpRel32.patch_size() as u32;
1448            let is_candidate_fixup = |fixup: &AsmFixup| {
1449                fixup.offset == fixup_offset
1450                    && fixup.label == candidate.label
1451                    && fixup.kind == LabelUse::X86JmpRel32
1452            };
1453
1454            let overlaps_cut = |offset: CodeOffset, size: usize| -> Result<bool, AsmError> {
1455                let end = offset
1456                    .checked_add(u32::try_from(size).map_err(|_| AsmError::TooLarge)?)
1457                    .ok_or(AsmError::TooLarge)?;
1458                Ok(offset < cut_end && end > cut_start)
1459            };
1460            for reloc in &self.relocs {
1461                if overlaps_cut(reloc.offset, relocation_patch_size(reloc.kind)?)? {
1462                    return Err(AsmError::InvalidState);
1463                }
1464            }
1465            for fixup in self
1466                .pending_fixup_records
1467                .iter()
1468                .chain(self.fixup_records.iter())
1469            {
1470                if !is_candidate_fixup(fixup)
1471                    && overlaps_cut(fixup.offset, fixup.kind.patch_size())?
1472                {
1473                    return Err(AsmError::InvalidState);
1474                }
1475            }
1476            for block in &self.patch_blocks {
1477                if overlaps_cut(block.offset, block.size as usize)? {
1478                    return Err(AsmError::InvalidState);
1479                }
1480            }
1481            for site in &self.patch_sites {
1482                if overlaps_cut(site.offset, site.kind.patch_size())?
1483                    || matches!(site.target, PendingPatchTarget::Offset(offset) if (cut_start..cut_end).contains(&offset))
1484                {
1485                    return Err(AsmError::InvalidState);
1486                }
1487            }
1488            if self
1489                .label_offsets
1490                .iter()
1491                .any(|&offset| offset != u32::MAX && (cut_start..cut_end).contains(&offset))
1492                || self
1493                    .used_constants
1494                    .iter()
1495                    .any(|&(_, offset)| (cut_start..cut_end).contains(&offset))
1496                || self
1497                    .alignment_constraints
1498                    .iter()
1499                    .any(|&(offset, _)| (cut_start..cut_end).contains(&offset))
1500            {
1501                return Err(AsmError::InvalidState);
1502            }
1503            for (other_index, other) in self.x86_branch_relaxations.iter().enumerate() {
1504                if other_index != index
1505                    && overlaps_cut(other.opcode_offset, other.near_size as usize)?
1506                {
1507                    return Err(AsmError::InvalidState);
1508                }
1509            }
1510
1511            let start = candidate.opcode_offset as usize;
1512            self.data[start] = candidate.short_opcode;
1513            self.data.drain(cut_start as usize..cut_end as usize);
1514            self.x86_branch_relaxations.remove(index);
1515
1516            // The shrunk branch keeps a rel8 fixup (rewritten here, or
1517            // freshly recorded when its near fixup was already applied)
1518            // so later shrinks keep rebasing it; the final displacement
1519            // is written at fixup application.
1520            let disp_offset = cut_start - 1;
1521            let mut rerecorded = false;
1522            for fixup in &mut self.pending_fixup_records {
1523                if is_candidate_fixup(fixup) {
1524                    fixup.kind = LabelUse::X86BranchRel8;
1525                    fixup.offset = disp_offset;
1526                    rerecorded = true;
1527                }
1528            }
1529            let mut fixups = core::mem::take(&mut self.fixup_records).into_vec();
1530            for fixup in &mut fixups {
1531                if is_candidate_fixup(fixup) {
1532                    fixup.kind = LabelUse::X86BranchRel8;
1533                    fixup.offset = disp_offset;
1534                    rerecorded = true;
1535                }
1536            }
1537            if !rerecorded {
1538                self.pending_fixup_records.push(AsmFixup {
1539                    label: candidate.label,
1540                    offset: disp_offset,
1541                    kind: LabelUse::X86BranchRel8,
1542                });
1543            }
1544
1545            let rebase = |offset: &mut CodeOffset| -> Result<(), AsmError> {
1546                if *offset >= cut_end {
1547                    *offset -= removed;
1548                } else if *offset >= cut_start {
1549                    return Err(AsmError::InvalidState);
1550                }
1551                Ok(())
1552            };
1553
1554            for reloc in &mut self.relocs {
1555                rebase(&mut reloc.offset)?;
1556            }
1557            for offset in &mut self.label_offsets {
1558                if *offset != u32::MAX {
1559                    rebase(offset)?;
1560                }
1561            }
1562            for fixup in &mut self.pending_fixup_records {
1563                rebase(&mut fixup.offset)?;
1564            }
1565            for fixup in &mut fixups {
1566                rebase(&mut fixup.offset)?;
1567            }
1568            self.fixup_records = BinaryHeap::from(fixups);
1569            self.pending_fixup_deadline = self
1570                .pending_fixup_records
1571                .iter()
1572                .map(AsmFixup::deadline)
1573                .min()
1574                .unwrap_or(u32::MAX);
1575            for (_, offset) in &mut self.used_constants {
1576                rebase(offset)?;
1577            }
1578            for block in &mut self.patch_blocks {
1579                rebase(&mut block.offset)?;
1580            }
1581            for site in &mut self.patch_sites {
1582                rebase(&mut site.offset)?;
1583                if let PendingPatchTarget::Offset(offset) = &mut site.target {
1584                    rebase(offset)?;
1585                }
1586            }
1587            for relaxation in &mut self.x86_branch_relaxations {
1588                rebase(&mut relaxation.opcode_offset)?;
1589            }
1590            for (offset, _) in &mut self.alignment_constraints {
1591                rebase(offset)?;
1592            }
1593        }
1594    }
1595
1596    /// Reject finalization failures that can be determined before island
1597    /// emission mutates the buffer.
1598    fn preflight_finalization(&self) -> Result<(), AsmError> {
1599        for fixup in self
1600            .pending_fixup_records
1601            .iter()
1602            .chain(self.fixup_records.iter())
1603        {
1604            let end = (fixup.offset as usize)
1605                .checked_add(fixup.kind.patch_size())
1606                .ok_or(AsmError::TooLarge)?;
1607            if end > self.data.len() {
1608                return Err(AsmError::InvalidArgument);
1609            }
1610            let label_offset = self.label_offset(fixup.label);
1611            if label_offset == u32::MAX {
1612                return Err(AsmError::UnboundLabel);
1613            }
1614            if fixup.kind.can_reach(fixup.offset, label_offset) {
1615                continue;
1616            }
1617            if matches!(
1618                fixup.kind,
1619                LabelUse::A64Branch14 | LabelUse::A64Branch19 | LabelUse::A64Branch26
1620            ) && !fixup.kind.supports_veneer()
1621            {
1622                return Err(AsmError::UnsupportedInstruction {
1623                    reason: "AArch64 branch veneers are not implemented",
1624                });
1625            }
1626            if label_offset >= fixup.offset {
1627                return Err(AsmError::TooLarge);
1628            }
1629            if !fixup.kind.supports_veneer() {
1630                return Err(AsmError::UnsupportedInstruction {
1631                    reason: "branch range requires an unsupported veneer",
1632                });
1633            }
1634            #[cfg(not(feature = "riscv"))]
1635            return Err(AsmError::UnsupportedInstruction {
1636                reason: "RISC-V veneer support is disabled",
1637            });
1638        }
1639        Ok(())
1640    }
1641
1642    fn finish_constants(&mut self) -> u32 {
1643        let mut alignment = 32;
1644
1645        for (constant, offset) in core::mem::take(&mut self.used_constants) {
1646            let constant = &self.constants[constant.0 as usize].0;
1647            let data = constant.as_slice();
1648            self.data[offset as usize..][..data.len()].copy_from_slice(data);
1649            alignment = constant.alignment().max(alignment);
1650        }
1651
1652        alignment as _
1653    }
1654
1655    fn resolve_patch_catalog(&self, validate_ranges: bool) -> Result<PatchCatalog, AsmError> {
1656        let mut blocks = SmallVec::new();
1657        let mut sites = SmallVec::new();
1658
1659        for block in &self.patch_blocks {
1660            blocks.push(PatchBlock {
1661                offset: block.offset,
1662                size: block.size,
1663                align: block.align,
1664            });
1665        }
1666
1667        for site in &self.patch_sites {
1668            let target_offset = match site.target {
1669                PendingPatchTarget::Offset(offset) => offset,
1670                PendingPatchTarget::Label(label) => self.label_offset(label),
1671            };
1672
1673            if target_offset == u32::MAX {
1674                return Err(AsmError::InvalidState);
1675            }
1676
1677            if validate_ranges && !site.kind.can_reach(site.offset, target_offset) {
1678                return Err(AsmError::TooLarge);
1679            }
1680
1681            sites.push(PatchSite {
1682                offset: site.offset,
1683                kind: site.kind,
1684                current_target: target_offset,
1685                addend: site.addend,
1686            });
1687        }
1688
1689        Ok(PatchCatalog::with_parts(self.env.arch(), blocks, sites))
1690    }
1691
1692    fn resolved_defined_symbols(&self) -> SmallVec<[(ExternalName, CodeOffset); 4]> {
1693        // Unbound labels keep the sentinel offset; the linker reports them as
1694        // an error.
1695        self.defined_symbols
1696            .iter()
1697            .map(|(name, label)| (name.clone(), self.label_offset(*label)))
1698            .collect()
1699    }
1700
1701    pub fn finish_patched(mut self) -> Result<CodeBufferFinalized, AsmError> {
1702        if let Some(error) = self.error.take() {
1703            return Err(error);
1704        }
1705        if self.has_unbound_labels() {
1706            return Err(AsmError::UnboundLabel);
1707        }
1708        self.preflight_finalization()?;
1709        #[cfg(feature = "x86")]
1710        self.relax_x86_branches()?;
1711        #[cfg(feature = "x86")]
1712        self.preflight_finalization()?;
1713        self.finish_emission_maybe_forcing_veneers()?;
1714        let patch_catalog = self.resolve_patch_catalog(true)?;
1715        let alignment = self.finish_constants();
1716        let defined_symbols = self.resolved_defined_symbols();
1717        Ok(CodeBufferFinalized {
1718            data: self.data,
1719            relocs: self.relocs,
1720            symbols: self.symbols,
1721            label_offsets: self.label_offsets,
1722            defined_symbols,
1723            alignment,
1724            patch_catalog,
1725        })
1726    }
1727
1728    pub fn finish(&mut self) -> Result<CodeBufferFinalized, AsmError> {
1729        if let Some(error) = self.error.clone() {
1730            return Err(error);
1731        }
1732        if self.has_unbound_labels() {
1733            return Err(AsmError::UnboundLabel);
1734        }
1735        self.preflight_finalization()?;
1736        #[cfg(feature = "x86")]
1737        self.relax_x86_branches()?;
1738        #[cfg(feature = "x86")]
1739        self.preflight_finalization()?;
1740        self.finish_emission_maybe_forcing_veneers()?;
1741        let patch_catalog = self.resolve_patch_catalog(false)?;
1742        let alignment = self.finish_constants();
1743        Ok(CodeBufferFinalized {
1744            data: self.data.clone(),
1745            relocs: self.relocs.clone(),
1746            symbols: self.symbols.clone(),
1747            label_offsets: self.label_offsets.clone(),
1748            defined_symbols: self.resolved_defined_symbols(),
1749            alignment,
1750            patch_catalog,
1751        })
1752    }
1753
1754    fn has_unbound_labels(&self) -> bool {
1755        let is_unbound = |label: Label| {
1756            self.label_offsets
1757                .get(label.id() as usize)
1758                .is_none_or(|offset| *offset == u32::MAX)
1759        };
1760        self.pending_fixup_records
1761            .iter()
1762            .any(|fixup| is_unbound(fixup.label))
1763            || self
1764                .fixup_records
1765                .iter()
1766                .any(|fixup| is_unbound(fixup.label))
1767            || self
1768                .relocs
1769                .iter()
1770                .any(|reloc| matches!(reloc.target, RelocTarget::Label(label) if is_unbound(label)))
1771            || self
1772                .defined_symbols
1773                .iter()
1774                .any(|(_, label)| is_unbound(*label))
1775            || self.patch_sites.iter().any(
1776                |site| matches!(site.target, PendingPatchTarget::Label(label) if is_unbound(label)),
1777            )
1778    }
1779}
1780
1781#[derive(Clone, PartialEq, Eq, Debug, Hash)]
1782pub enum ConstantData {
1783    WellKnown(&'static [u8]),
1784    U64([u8; 8]),
1785    Bytes(Vec<u8>),
1786}
1787
1788impl ConstantData {
1789    pub fn as_slice(&self) -> &[u8] {
1790        match self {
1791            ConstantData::WellKnown(data) => data,
1792            ConstantData::U64(data) => data.as_ref(),
1793            ConstantData::Bytes(data) => data,
1794        }
1795    }
1796
1797    pub fn alignment(&self) -> usize {
1798        if self.as_slice().len() <= 8 { 8 } else { 16 }
1799    }
1800}
1801
1802/// A use of a constant by one or mroe assembly instructions.
1803#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1804pub struct Constant(pub(crate) u32);
1805
1806impl From<&'static str> for ConstantData {
1807    fn from(value: &'static str) -> Self {
1808        Self::WellKnown(value.as_bytes())
1809    }
1810}
1811
1812impl From<[u8; 8]> for ConstantData {
1813    fn from(value: [u8; 8]) -> Self {
1814        Self::U64(value)
1815    }
1816}
1817
1818impl From<Vec<u8>> for ConstantData {
1819    fn from(value: Vec<u8>) -> Self {
1820        Self::Bytes(value)
1821    }
1822}
1823
1824impl From<&'static [u8]> for ConstantData {
1825    fn from(value: &'static [u8]) -> Self {
1826        Self::WellKnown(value)
1827    }
1828}
1829
1830impl From<u64> for ConstantData {
1831    fn from(value: u64) -> Self {
1832        Self::U64(value.to_ne_bytes())
1833    }
1834}
1835
1836/// Offset in bytes from the beginning of the function.
1837///
1838/// Cranelift can be used as a cross compiler, so we don't want to use a type like `usize` which
1839/// depends on the *host* platform, not the *target* platform.
1840pub type CodeOffset = u32;
1841
1842/// Addend to add to the symbol value.
1843pub type Addend = i64;
1844
1845/// Relocation kinds for every ISA
1846#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1847pub enum Reloc {
1848    /// absolute 4-byte
1849    Abs4,
1850    /// absolute 8-byte
1851    Abs8,
1852    /// x86 PC-relative 4-byte
1853    X86PCRel4,
1854    /// x86 call to PC-relative 4-byte
1855    X86CallPCRel4,
1856    /// x86 call to PLT-relative 4-byte
1857    X86CallPLTRel4,
1858    /// x86 GOT PC-relative 4-byte
1859    X86GOTPCRel4,
1860    /// The 32-bit offset of the target from the beginning of its section.
1861    /// Equivalent to `IMAGE_REL_AMD64_SECREL`.
1862    /// See: [PE Format](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format)
1863    X86SecRel,
1864    /// Arm32 call target
1865    Arm32Call,
1866    /// Arm64 call target. Encoded as bottom 26 bits of instruction. This
1867    /// value is sign-extended, multiplied by 4, and added to the PC of
1868    /// the call instruction to form the destination address.
1869    Arm64Call,
1870
1871    /// Elf x86_64 32 bit signed PC relative offset to two GOT entries for GD symbol.
1872    ElfX86_64TlsGd,
1873
1874    /// Mach-O x86_64 32 bit signed PC relative offset to a `__thread_vars` entry.
1875    MachOX86_64Tlv,
1876
1877    /// Mach-O Aarch64 TLS
1878    /// PC-relative distance to the page of the TLVP slot.
1879    MachOAarch64TlsAdrPage21,
1880
1881    /// Mach-O Aarch64 TLS
1882    /// Offset within page of TLVP slot.
1883    MachOAarch64TlsAdrPageOff12,
1884
1885    /// Aarch64 TLSDESC Adr Page21
1886    /// This is equivalent to `R_AARCH64_TLSDESC_ADR_PAGE21` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#57105thread-local-storage-descriptors)
1887    Aarch64TlsDescAdrPage21,
1888
1889    /// Aarch64 TLSDESC Ld64 Lo12
1890    /// This is equivalent to `R_AARCH64_TLSDESC_LD64_LO12` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#57105thread-local-storage-descriptors)
1891    Aarch64TlsDescLd64Lo12,
1892
1893    /// Aarch64 TLSDESC Add Lo12
1894    /// This is equivalent to `R_AARCH64_TLSGD_ADD_LO12` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#57105thread-local-storage-descriptors)
1895    Aarch64TlsDescAddLo12,
1896
1897    /// Aarch64 TLSDESC Call
1898    /// This is equivalent to `R_AARCH64_TLSDESC_CALL` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#57105thread-local-storage-descriptors)
1899    Aarch64TlsDescCall,
1900
1901    /// AArch64 GOT Page
1902    /// Set the immediate value of an ADRP to bits 32:12 of X; check that –2^32 <= X < 2^32
1903    /// This is equivalent to `R_AARCH64_ADR_GOT_PAGE` (311) in the  [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#static-aarch64-relocations)
1904    Aarch64AdrGotPage21,
1905
1906    /// AArch64 GOT Low bits
1907
1908    /// Set the LD/ST immediate field to bits 11:3 of X. No overflow check; check that X&7 = 0
1909    /// This is equivalent to `R_AARCH64_LD64_GOT_LO12_NC` (312) in the  [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#static-aarch64-relocations)
1910    Aarch64Ld64GotLo12Nc,
1911
1912    /// Equivalent of `R_AARCH64_ADR_PREL_PG_HI21`.
1913    Aarch64AdrPrelPgHi21,
1914    /// Equivalent of `R_AARCH64_ADD_ABS_LO12_NC`.
1915    Aarch64AddAbsLo12Nc,
1916
1917    /// RISC-V Absolute address: 64-bit address.
1918    RiscvAbs8,
1919
1920    /// RISC-V Call PLT: 32-bit PC-relative function call, macros call, tail (PIC)
1921    ///
1922    /// Despite having PLT in the name, this relocation is also used for normal calls.
1923    /// The non-PLT version of this relocation has been deprecated.
1924    ///
1925    /// This is the `R_RISCV_CALL_PLT` relocation from the RISC-V ELF psABI document.
1926    /// <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#procedure-calls>
1927    RiscvCallPlt,
1928
1929    /// RISC-V TLS GD: High 20 bits of 32-bit PC-relative TLS GD GOT reference,
1930    ///
1931    /// This is the `R_RISCV_TLS_GD_HI20` relocation from the RISC-V ELF psABI document.
1932    /// <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#global-dynamic>
1933    RiscvTlsGdHi20,
1934
1935    /// Low 12 bits of a 32-bit PC-relative relocation (I-Type instruction)
1936    ///
1937    /// This is the `R_RISCV_PCREL_LO12_I` relocation from the RISC-V ELF psABI document.
1938    /// <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses>
1939    RiscvPCRelLo12I,
1940
1941    /// High 20 bits of a 32-bit PC-relative GOT offset relocation
1942    ///
1943    /// This is the `R_RISCV_GOT_HI20` relocation from the RISC-V ELF psABI document.
1944    /// <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses>
1945    RiscvGotHi20,
1946}
1947
1948impl Reloc {
1949    const fn supports_arch(self, arch: Arch) -> bool {
1950        match self {
1951            Self::Abs4 | Self::Abs8 => true,
1952            Self::X86PCRel4
1953            | Self::X86CallPCRel4
1954            | Self::X86CallPLTRel4
1955            | Self::X86GOTPCRel4
1956            | Self::X86SecRel
1957            | Self::ElfX86_64TlsGd
1958            | Self::MachOX86_64Tlv => {
1959                cfg!(feature = "x86") && matches!(arch, Arch::X86 | Arch::X64)
1960            }
1961            Self::Arm32Call => false,
1962            Self::Arm64Call
1963            | Self::MachOAarch64TlsAdrPage21
1964            | Self::MachOAarch64TlsAdrPageOff12
1965            | Self::Aarch64TlsDescAdrPage21
1966            | Self::Aarch64TlsDescLd64Lo12
1967            | Self::Aarch64TlsDescAddLo12
1968            | Self::Aarch64TlsDescCall
1969            | Self::Aarch64AdrGotPage21
1970            | Self::Aarch64Ld64GotLo12Nc
1971            | Self::Aarch64AdrPrelPgHi21
1972            | Self::Aarch64AddAbsLo12Nc => {
1973                cfg!(feature = "aarch64") && matches!(arch, Arch::AArch64)
1974            }
1975            Self::RiscvAbs8
1976            | Self::RiscvCallPlt
1977            | Self::RiscvTlsGdHi20
1978            | Self::RiscvPCRelLo12I
1979            | Self::RiscvGotHi20 => {
1980                cfg!(feature = "riscv") && matches!(arch, Arch::RISCV32 | Arch::RISCV64)
1981            }
1982        }
1983    }
1984}
1985
1986#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
1987pub enum LabelUse {
1988    X86JmpRel32,
1989    /// 8-bit PC-relative branch displacement (short `jmp`/`jcc`). Recorded
1990    /// by x86 branch relaxation for already-shrunk branches so later
1991    /// shrinks keep rebasing them; written at fixup application.
1992    X86BranchRel8,
1993    /// 20-bit branch offset (unconditional branches). PC-rel, offset is
1994    /// imm << 1. Immediate is 20 signed bits. Use in Jal instructions.
1995    RVJal20,
1996    /// The unconditional jump instructions all use PC-relative
1997    /// addressing to help support position independent code. The JALR
1998    /// instruction was defined to enable a two-instruction sequence to
1999    /// jump anywhere in a 32-bit absolute address range. A LUI
2000    /// instruction can first load rs1 with the upper 20 bits of a
2001    /// target address, then JALR can add in the lower bits. Similarly,
2002    /// AUIPC then JALR can jump anywhere in a 32-bit pc-relative
2003    /// address range.
2004    RVPCRel32,
2005
2006    /// All branch instructions use the B-type instruction format. The
2007    /// 12-bit B-immediate encodes signed offsets in multiples of 2, and
2008    /// is added to the current pc to give the target address. The
2009    /// conditional branch range is ±4 KiB.
2010    RVB12,
2011
2012    /// Equivalent to the `R_RISCV_PCREL_HI20` relocation, Allows setting
2013    /// the immediate field of an `auipc` instruction.
2014    RVPCRelHi20,
2015
2016    /// Similar to the `R_RISCV_PCREL_LO12_I` relocation but pointing to
2017    /// the final address, instead of the `PCREL_HI20` label. Allows setting
2018    /// the immediate field of I Type instructions such as `addi` or `lw`.
2019    ///
2020    /// Since we currently don't support offsets in labels, this relocation has
2021    /// an implicit offset of 4.
2022    RVPCRelLo12I,
2023
2024    /// 11-bit PC-relative jump offset. Equivalent to the `RVC_JUMP` relocation
2025    RVCJump,
2026    /// 9-bit PC-relative branch offset.
2027    RVCB9,
2028    /// 14-bit branch offset (conditional branches). PC-rel, offset is imm <<
2029    /// 2. Immediate is 14 signed bits, in bits 18:5. Used by tbz and tbnz.
2030    A64Branch14,
2031    /// 19-bit branch offset (conditional branches). PC-rel, offset is imm << 2. Immediate is 19
2032    /// signed bits, in bits 23:5. Used by cbz, cbnz, b.cond.
2033    A64Branch19,
2034    /// 26-bit branch offset (unconditional branches). PC-rel, offset is imm << 2. Immediate is 26
2035    /// signed bits, in bits 25:0. Used by b, bl.
2036    A64Branch26,
2037    /// 19-bit offset for LDR (load literal). PC-rel, offset is imm << 2. Immediate is 19 signed bits,
2038    /// in bits 23:5.
2039    A64Ldr19,
2040    /// 21-bit offset for ADR (get address of label). PC-rel, offset is not shifted. Immediate is
2041    /// 21 signed bits, with high 19 bits in bits 23:5 and low 2 bits in bits 30:29.
2042    A64Adr21,
2043    /// 21-bit offset for ADRP (get address of label). PC-rel, offset is shifted. Immediate is
2044    /// 21 signed bits, with high 19 bits in bits 23:5 and low 2 bits in bits 30:29.
2045    A64Adrp21,
2046    A64Ldr12,
2047
2048    /// Absolute low 12 bits of a label address for ADD (pairs with a
2049    /// preceding ADRP to form the full address). Unlike the PC-relative
2050    /// uses, this encodes `label & 0xfff`, which is address-correct
2051    /// whenever the image loads at a page-aligned base (as JIT mappings
2052    /// do, and as ADRP itself requires).
2053    A64AddAbsLo12,
2054}
2055
2056impl LabelUse {
2057    fn validate_for_arch(self, arch: Arch) -> Result<(), AsmError> {
2058        if self == Self::A64Ldr12 {
2059            return Err(AsmError::UnsupportedInstruction {
2060                reason: "AArch64 LDR12 label patching is not implemented",
2061            });
2062        }
2063        if !self.supports_arch(arch) {
2064            return Err(AsmError::InvalidArch);
2065        }
2066        Ok(())
2067    }
2068
2069    const fn supports_arch(self, arch: Arch) -> bool {
2070        match self {
2071            Self::X86JmpRel32 | Self::X86BranchRel8 => {
2072                cfg!(feature = "x86") && matches!(arch, Arch::X86 | Arch::X64)
2073            }
2074            Self::RVJal20
2075            | Self::RVPCRel32
2076            | Self::RVB12
2077            | Self::RVPCRelHi20
2078            | Self::RVPCRelLo12I
2079            | Self::RVCJump
2080            | Self::RVCB9 => {
2081                cfg!(feature = "riscv") && matches!(arch, Arch::RISCV32 | Arch::RISCV64)
2082            }
2083            Self::A64Branch14
2084            | Self::A64Branch19
2085            | Self::A64Branch26
2086            | Self::A64Ldr19
2087            | Self::A64Adr21
2088            | Self::A64Adrp21
2089            | Self::A64Ldr12
2090            | Self::A64AddAbsLo12 => cfg!(feature = "aarch64") && matches!(arch, Arch::AArch64),
2091        }
2092    }
2093
2094    pub fn can_reach(&self, use_offset: CodeOffset, label_offset: CodeOffset) -> bool {
2095        let delta = (label_offset as i64) - (use_offset as i64);
2096
2097        match self {
2098            Self::X86JmpRel32 => {
2099                let disp = delta - 4;
2100                i32::try_from(disp).is_ok()
2101            }
2102            Self::X86BranchRel8 => {
2103                let disp = delta - 1;
2104                i8::try_from(disp).is_ok()
2105            }
2106            Self::RVJal20 => delta % 2 == 0 && (-(1 << 20)..=((1 << 20) - 2)).contains(&delta),
2107            Self::RVB12 => delta % 2 == 0 && (-(1 << 12)..=((1 << 12) - 2)).contains(&delta),
2108            Self::RVCJump => delta % 2 == 0 && (-(1 << 11)..=((1 << 11) - 2)).contains(&delta),
2109            Self::RVCB9 => delta % 2 == 0 && (-(1 << 8)..=((1 << 8) - 2)).contains(&delta),
2110            Self::RVPCRelHi20 | Self::RVPCRelLo12I | Self::RVPCRel32 => {
2111                i32::try_from(delta).is_ok()
2112            }
2113            Self::A64Branch14 => delta % 4 == 0 && (-(1 << 15)..=((1 << 15) - 4)).contains(&delta),
2114            Self::A64Branch19 | Self::A64Ldr19 => {
2115                delta % 4 == 0 && (-(1 << 20)..=((1 << 20) - 4)).contains(&delta)
2116            }
2117            Self::A64Branch26 => delta % 4 == 0 && (-(1 << 27)..=((1 << 27) - 4)).contains(&delta),
2118            Self::A64Adr21 => (-(1 << 20)..=((1 << 20) - 1)).contains(&delta),
2119            Self::A64Adrp21 => {
2120                let page_delta = ((label_offset & !0xfff) as i64) - ((use_offset & !0xfff) as i64);
2121                page_delta % 4096 == 0 && (-(1 << 32)..=((1 << 32) - 4096)).contains(&page_delta)
2122            }
2123
2124            Self::A64AddAbsLo12 => {
2125                // Reachable exactly when the paired ADRP reaches the
2126                // label's page.
2127                let page_delta = ((label_offset & !0xfff) as i64) - ((use_offset & !0xfff) as i64);
2128                page_delta % 4096 == 0 && (-(1 << 32)..=((1 << 32) - 4096)).contains(&page_delta)
2129            }
2130
2131            Self::A64Ldr12 => true,
2132        }
2133    }
2134
2135    /// Maximum PC-relative range (positive), inclusive.
2136    pub const fn max_pos_range(self) -> CodeOffset {
2137        match self {
2138            LabelUse::RVJal20 => ((1 << 19) - 1) * 2,
2139            LabelUse::RVPCRelLo12I | LabelUse::RVPCRelHi20 | LabelUse::RVPCRel32 => {
2140                let imm20_max: i64 = ((1 << 19) - 1) << 12;
2141                let imm12_max = (1 << 11) - 1;
2142                (imm20_max + imm12_max) as _
2143            }
2144            LabelUse::RVB12 => ((1 << 11) - 1) * 2,
2145            LabelUse::RVCB9 => ((1 << 8) - 1) * 2,
2146            LabelUse::RVCJump => ((1 << 10) - 1) * 2,
2147            LabelUse::X86JmpRel32 => i32::MAX as _,
2148            LabelUse::X86BranchRel8 => 127,
2149            _ => u32::MAX,
2150        }
2151    }
2152
2153    pub const fn max_neg_range(self) -> CodeOffset {
2154        match self {
2155            LabelUse::RVPCRel32 => {
2156                let imm20_max: i64 = (1 << 19) << 12;
2157                let imm12_max = 1 << 11;
2158                (-imm20_max - imm12_max) as CodeOffset
2159            }
2160            _ => self.max_pos_range() + 2,
2161        }
2162    }
2163
2164    pub const fn patch_size(&self) -> usize {
2165        match self {
2166            Self::X86JmpRel32 => 4,
2167            Self::X86BranchRel8 => 1,
2168            Self::RVCJump | Self::RVCB9 => 2,
2169            Self::RVJal20 | Self::RVB12 | Self::RVPCRelHi20 | Self::RVPCRelLo12I => 4,
2170            Self::RVPCRel32 => 8,
2171            _ => 4,
2172        }
2173    }
2174
2175    pub const fn align(&self) -> usize {
2176        match self {
2177            Self::X86JmpRel32 | Self::X86BranchRel8 => 1,
2178            Self::RVCJump => 4,
2179            Self::RVJal20 | Self::RVB12 | Self::RVCB9 | Self::RVPCRelHi20 | Self::RVPCRelLo12I => 4,
2180            Self::RVPCRel32 => 4,
2181            _ => 4,
2182        }
2183    }
2184
2185    pub const fn supports_veneer(&self) -> bool {
2186        matches!(self, Self::RVB12 | Self::RVJal20 | Self::RVCJump)
2187    }
2188
2189    #[cfg(feature = "riscv")]
2190    pub(crate) fn veneer_size(&self) -> usize {
2191        debug_assert!(self.supports_veneer());
2192        8
2193    }
2194
2195    #[cfg(feature = "riscv")]
2196    pub(crate) fn generate_veneer(
2197        &self,
2198        buffer: &mut [u8],
2199        veneer_offset: CodeOffset,
2200    ) -> (CodeOffset, Self) {
2201        debug_assert!(self.supports_veneer());
2202        let base = riscv::X31;
2203
2204        {
2205            let x = riscv::opcodes::Inst::new(riscv::Opcode::AUIPC)
2206                .encode()
2207                .set_rd(base.id())
2208                .value
2209                .to_le_bytes();
2210            buffer[0] = x[0];
2211            buffer[1] = x[1];
2212            buffer[2] = x[2];
2213            buffer[3] = x[3];
2214        }
2215
2216        {
2217            let x = riscv::opcodes::Inst::new(riscv::Opcode::JALR)
2218                .encode()
2219                .set_rd(riscv::ZERO.id())
2220                .set_rs1(base.id())
2221                .value
2222                .to_le_bytes();
2223            buffer[4] = x[0];
2224            buffer[5] = x[1];
2225            buffer[6] = x[2];
2226            buffer[7] = x[3];
2227        }
2228
2229        (veneer_offset, LabelUse::RVPCRel32)
2230    }
2231    pub(crate) fn patch(
2232        &self,
2233        buffer: &mut [u8],
2234        use_offset: CodeOffset,
2235        label_offset: CodeOffset,
2236    ) {
2237        let addend = match self {
2238            Self::X86JmpRel32 => i64::from(u32::from_le_bytes([
2239                buffer[0], buffer[1], buffer[2], buffer[3],
2240            ])),
2241            _ => 0,
2242        };
2243
2244        self.patch_with_addend(buffer, use_offset, label_offset, addend);
2245    }
2246
2247    pub(crate) fn patch_with_addend(
2248        &self,
2249        buffer: &mut [u8],
2250        use_offset: CodeOffset,
2251        label_offset: CodeOffset,
2252        addend: i64,
2253    ) {
2254        let pc_reli = (label_offset as i64) - (use_offset as i64);
2255
2256        let pc_rel = pc_reli as u32;
2257
2258        match self {
2259            Self::X86JmpRel32 => {
2260                let value = pc_rel.wrapping_add(addend as u32).wrapping_sub(4);
2261
2262                buffer.copy_from_slice(&value.to_le_bytes());
2263            }
2264
2265            Self::X86BranchRel8 => {
2266                let value = pc_reli.wrapping_add(addend).wrapping_sub(1) as u8;
2267
2268                buffer[0] = value;
2269            }
2270
2271            Self::RVJal20 => {
2272                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2273                let offset = pc_rel;
2274                let v = ((offset >> 12 & 0b1111_1111) << 12)
2275                    | ((offset >> 11 & 0b1) << 20)
2276                    | ((offset >> 1 & 0b11_1111_1111) << 21)
2277                    | ((offset >> 20 & 0b1) << 31);
2278                // The immediate occupies bits 31..12; clear the old value
2279                // (retargets must not OR into a previous immediate).
2280                buffer[0..4].clone_from_slice(&u32::to_le_bytes((insn & 0xFFF) | v));
2281            }
2282
2283            Self::RVPCRel32 => {
2284                #[cfg(feature = "riscv")]
2285                {
2286                    let (imm20, imm12) = generate_imm(pc_rel as u64);
2287                    let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2288                    let insn2 = u32::from_le_bytes([buffer[4], buffer[5], buffer[6], buffer[7]]);
2289
2290                    let auipc = riscv::opcodes::Inst::new(riscv::Opcode::AUIPC)
2291                        .encode()
2292                        .set_imm20(0);
2293                    let jalr = riscv::opcodes::Inst::new(riscv::Opcode::JALR)
2294                        .encode()
2295                        .set_rd(0)
2296                        .set_rs1(0)
2297                        .set_imm12(0);
2298
2299                    buffer[0..4].copy_from_slice(&(insn | auipc.value | imm20).to_le_bytes());
2300                    buffer[4..8].copy_from_slice(&(insn2 | jalr.value | imm12).to_le_bytes());
2301                }
2302                #[cfg(not(feature = "riscv"))]
2303                {
2304                    panic!("RISC-V veneers aren't supported without the `riscv` feature");
2305                }
2306            }
2307
2308            Self::RVB12 => {
2309                #[cfg(feature = "riscv")]
2310                {
2311                    let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2312                    let offset = pc_rel;
2313                    let v = ((offset >> 11 & 0b1) << 7)
2314                        | ((offset >> 1 & 0b1111) << 8)
2315                        | ((offset >> 5 & 0b11_1111) << 25)
2316                        | ((offset >> 12 & 0b1) << 31);
2317                    // B-type immediate fields are bits 31, 30..25, 11..8,
2318                    // and 7; clear them before OR-ing in the new offset.
2319                    buffer[0..4].clone_from_slice(&u32::to_le_bytes((insn & 0x01FF_F07F) | v));
2320                }
2321                #[cfg(not(feature = "riscv"))]
2322                {
2323                    panic!("RISC-V veneers aren't supported without the `riscv` feature");
2324                }
2325            }
2326
2327            Self::RVPCRelHi20 => {
2328                // See https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses
2329                //
2330                // We need to add 0x800 to ensure that we land at the next page as soon as it goes out of range for the
2331                // Lo12 relocation. That relocation is signed and has a maximum range of -2048..2047. So when we get an
2332                // offset of 2048, we need to land at the next page and subtract instead.
2333                let offset = pc_reli as u32;
2334                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2335                let hi20 = offset.wrapping_add(0x800) >> 12;
2336                let insn = (insn & 0xfff) | (hi20 << 12);
2337                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2338            }
2339
2340            Self::RVPCRelLo12I => {
2341                // `offset` is the offset from the current instruction to the target address.
2342                //
2343                // However we are trying to compute the offset to the target address from the previous instruction.
2344                // The previous instruction should be the one that contains the PCRelHi20 relocation and
2345                // stores/references the program counter (`auipc` usually).
2346                //
2347                // Since we are trying to compute the offset from the previous instruction, we can
2348                // represent it as offset = target_address - (current_instruction_address - 4)
2349                // which is equivalent to offset = target_address - current_instruction_address + 4.
2350                //
2351                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2352
2353                let lo12 = (pc_reli + 4) as u32 & 0xfff;
2354                let insn = (insn & 0xFFFFF) | (lo12 << 20);
2355                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2356            }
2357
2358            Self::RVCJump => {
2359                debug_assert!(pc_rel & 1 == 0);
2360
2361                #[cfg(feature = "riscv")]
2362                {
2363                    let insn = riscv::opcodes::Inst::new(riscv::Opcode::CJ)
2364                        .encode()
2365                        .set_c_imm12(pc_rel as _);
2366                    buffer[0..2].clone_from_slice(&(insn.value as u16).to_le_bytes());
2367                }
2368                #[cfg(not(feature = "riscv"))]
2369                {
2370                    panic!("RISC-V jumps aren't supported without the `riscv` feature");
2371                }
2372            }
2373
2374            Self::RVCB9 => {
2375                debug_assert!(pc_rel & 1 == 0);
2376
2377                #[cfg(feature = "riscv")]
2378                {
2379                    let insn = riscv::opcodes::Inst::new(riscv::Opcode::BEQZ)
2380                        .encode()
2381                        .set_c_bimm9lohi(pc_rel as _);
2382                    buffer[0..2].clone_from_slice(&(insn.value as u16).to_le_bytes());
2383                }
2384                #[cfg(not(feature = "riscv"))]
2385                {
2386                    panic!("RISC-V veneers aren't supported without the `riscv` feature");
2387                }
2388            }
2389
2390            Self::A64Branch14 => {
2391                debug_assert!(pc_reli & 0b11 == 0);
2392
2393                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2394                let imm14 = ((pc_reli >> 2) as i32 as u32) & 0x3fff;
2395                let insn = (insn & !0x0007ffe0) | (imm14 << 5);
2396                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2397            }
2398
2399            Self::A64Branch19 | Self::A64Ldr19 => {
2400                debug_assert!(pc_reli & 0b11 == 0);
2401
2402                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2403                let imm19 = ((pc_reli >> 2) as i32 as u32) & 0x7ffff;
2404                let insn = (insn & !0x00ffffe0) | (imm19 << 5);
2405                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2406            }
2407
2408            Self::A64Branch26 => {
2409                debug_assert!(pc_reli & 0b11 == 0);
2410
2411                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2412                let imm26 = ((pc_reli >> 2) as i32 as u32) & 0x03ff_ffff;
2413                let insn = (insn & !0x03ff_ffff) | imm26;
2414                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2415            }
2416
2417            Self::A64Adr21 => {
2418                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2419                let imm21 = (pc_reli as i32 as u32) & 0x1f_ffff;
2420                let immlo = imm21 & 0x3;
2421                let immhi = (imm21 >> 2) & 0x7ffff;
2422                let insn = (insn & !0x60ff_ffe0) | (immlo << 29) | (immhi << 5);
2423                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2424            }
2425
2426            Self::A64Adrp21 => {
2427                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2428
2429                // 1. Calculate the page-aligned PC and Target
2430                let pc_page = (use_offset as i64) & !0xFFF;
2431                let target_page = ((label_offset as i64) + addend) & !0xFFF;
2432
2433                // 2. Calculate the offset in pages
2434                let page_offset = (target_page - pc_page) >> 12;
2435
2436                // 3. Encode the 21-bit signed immediate
2437                let imm21 = (page_offset as u32) & 0x1F_FFFF;
2438                let immlo = imm21 & 0x3; // Lowest 2 bits
2439                let immhi = (imm21 >> 2) & 0x7FFFF; // Upper 19 bits
2440
2441                // 4. Clear existing immediate bits and insert new ones
2442                // Bits 29..31 (immlo) and Bits 5..24 (immhi)
2443                let insn = (insn & !0x60FF_FFE0) | (immlo << 29) | (immhi << 5);
2444
2445                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2446            }
2447
2448            Self::A64AddAbsLo12 => {
2449                let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2450
2451                // Absolute page offset of the label (see the variant docs).
2452                let imm12 = (label_offset & 0xfff) << 10;
2453                let insn = insn | imm12;
2454                buffer[0..4].copy_from_slice(&insn.to_le_bytes());
2455            }
2456
2457            _ => todo!(),
2458        }
2459    }
2460}
2461
2462pub const fn is_imm12(val: i64) -> bool {
2463    val >= -2048 && val <= 2047
2464}
2465
2466#[allow(dead_code)]
2467pub(crate) fn generate_imm(value: u64) -> (u32, u32) {
2468    #[cfg(not(feature = "riscv"))]
2469    {
2470        let _ = value;
2471        panic!("Can't generate RISC-V immediates without the `riscv` feature");
2472    }
2473    #[cfg(feature = "riscv")]
2474    {
2475        if is_imm12(value as _) {
2476            return (
2477                0,
2478                riscv::opcodes::InstructionValue::new(0)
2479                    .set_imm12(value as i64 as i32)
2480                    .value,
2481            );
2482        }
2483
2484        let value = value as i64;
2485
2486        let mod_num = 4096i64;
2487        let (imm20, imm12) = if value > 0 {
2488            let mut imm20 = value / mod_num;
2489            let mut imm12 = value % mod_num;
2490
2491            if imm12 >= 2048 {
2492                imm12 -= mod_num;
2493                imm20 += 1;
2494            }
2495
2496            (imm20, imm12)
2497        } else {
2498            let value_abs = value.abs();
2499            let imm20 = value_abs / mod_num;
2500            let imm12 = value_abs % mod_num;
2501            let mut imm20 = -imm20;
2502            let mut imm12 = -imm12;
2503            if imm12 < -2048 {
2504                imm12 += mod_num;
2505                imm20 -= 1;
2506            }
2507            (imm20, imm12)
2508        };
2509        (
2510            riscv::opcodes::InstructionValue::new(0)
2511                .set_imm20(imm20 as _)
2512                .value,
2513            riscv::opcodes::InstructionValue::new(0)
2514                .set_imm12(imm12 as _)
2515                .value,
2516        )
2517    }
2518}
2519
2520pub(crate) fn relocation_patch_size(kind: Reloc) -> Result<usize, AsmError> {
2521    match kind {
2522        Reloc::Abs4
2523        | Reloc::X86PCRel4
2524        | Reloc::X86CallPCRel4
2525        | Reloc::X86CallPLTRel4
2526        | Reloc::X86GOTPCRel4
2527        | Reloc::RiscvGotHi20
2528        | Reloc::RiscvPCRelLo12I
2529        | Reloc::Aarch64AdrPrelPgHi21
2530        | Reloc::Aarch64AddAbsLo12Nc
2531        | Reloc::Aarch64AdrGotPage21
2532        | Reloc::Aarch64Ld64GotLo12Nc => Ok(4),
2533        Reloc::Abs8 | Reloc::RiscvAbs8 | Reloc::RiscvCallPlt => Ok(8),
2534        _ => Err(AsmError::InvalidArgument),
2535    }
2536}
2537
2538fn checked_address(base: usize, addend: i64) -> Result<usize, AsmError> {
2539    if addend >= 0 {
2540        base.checked_add(usize::try_from(addend).map_err(|_| AsmError::TooLarge)?)
2541    } else {
2542        base.checked_sub(usize::try_from(addend.unsigned_abs()).map_err(|_| AsmError::TooLarge)?)
2543    }
2544    .ok_or(AsmError::TooLarge)
2545}
2546
2547fn checked_pcrel(target: usize, instruction: usize) -> Result<i64, AsmError> {
2548    i64::try_from(target as i128 - instruction as i128).map_err(|_| AsmError::TooLarge)
2549}
2550
2551/// Applies relocations to one writable code span.
2552///
2553/// Unsupported relocation kinds, invalid resolver results, out-of-range patch
2554/// spans, address overflow, and displacement overflow are returned as errors.
2555///
2556/// # Safety
2557///
2558/// `code` and `code_rx` must refer to writable and executable views of the same
2559/// `code_size`-byte allocation. Resolver pointers are treated as addresses and
2560/// are never dereferenced.
2561pub unsafe fn perform_relocations(
2562    code: *mut u8,
2563    code_rx: *const u8,
2564    code_size: usize,
2565    relocs: &[AsmReloc],
2566    get_address: impl Fn(&RelocTarget) -> Result<*const u8, AsmError>,
2567    get_got_entry: impl Fn(&RelocTarget) -> Result<*const u8, AsmError>,
2568    get_plt_entry: impl Fn(&RelocTarget) -> Result<*const u8, AsmError>,
2569) -> Result<(), AsmError> {
2570    use core::ptr::write_unaligned;
2571
2572    if code.is_null() || code_rx.is_null() {
2573        return Err(AsmError::InvalidArgument);
2574    }
2575
2576    for &AsmReloc {
2577        addend,
2578        kind,
2579        offset,
2580        ref target,
2581    } in relocs
2582    {
2583        let patch_size = relocation_patch_size(kind)?;
2584        let patch_start = offset as usize;
2585        let patch_end = patch_start
2586            .checked_add(patch_size)
2587            .ok_or(AsmError::TooLarge)?;
2588        if patch_end > code_size {
2589            return Err(AsmError::InvalidArgument);
2590        }
2591        // SAFETY: `patch_end <= code_size` was checked above, so `patch_start` is
2592        // within the caller-provided writable/executable mapping.
2593        let at = unsafe { code.add(patch_start) };
2594        let atrx = code_rx
2595            .addr()
2596            .checked_add(patch_start)
2597            .ok_or(AsmError::TooLarge)?;
2598        let resolve = |resolver: &dyn Fn(&RelocTarget) -> Result<*const u8, AsmError>| {
2599            let base = resolver(target)?;
2600            if base.is_null() {
2601                return Err(AsmError::InvalidArgument);
2602            }
2603            checked_address(base.addr(), addend)
2604        };
2605
2606        match kind {
2607            Reloc::Abs4 => {
2608                let what = resolve(&get_address)?;
2609                let what = u32::try_from(what).map_err(|_| AsmError::TooLarge)?;
2610                // SAFETY: `relocation_patch_size(Abs4) == 4` and the patch span
2611                // was bounds-checked; `at` is 4-byte sized and the store is
2612                // unaligned-safe.
2613                unsafe {
2614                    write_unaligned(at as *mut u32, what);
2615                }
2616            }
2617
2618            Reloc::Abs8 | Reloc::RiscvAbs8 => {
2619                let what = resolve(&get_address)?;
2620                let what = u64::try_from(what).map_err(|_| AsmError::TooLarge)?;
2621                // SAFETY: `relocation_patch_size` is 8 for these kinds and the
2622                // patch span was bounds-checked; the store is unaligned-safe.
2623                unsafe {
2624                    write_unaligned(at as *mut u64, what);
2625                }
2626            }
2627
2628            Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => {
2629                let what = resolve(&get_address)?;
2630                let pcrel =
2631                    i32::try_from(checked_pcrel(what, atrx)?).map_err(|_| AsmError::TooLarge)?;
2632
2633                // SAFETY: 4-byte rel32 patch span, bounds-checked above.
2634                unsafe {
2635                    write_unaligned(at as *mut i32, pcrel);
2636                }
2637            }
2638
2639            Reloc::X86GOTPCRel4 => {
2640                let what = resolve(&get_got_entry)?;
2641                let pcrel =
2642                    i32::try_from(checked_pcrel(what, atrx)?).map_err(|_| AsmError::TooLarge)?;
2643
2644                // SAFETY: 4-byte GOT-relative patch span, bounds-checked above.
2645                unsafe {
2646                    write_unaligned(at as *mut i32, pcrel);
2647                }
2648            }
2649
2650            Reloc::X86CallPLTRel4 => {
2651                let what = resolve(&get_plt_entry)?;
2652                let pcrel =
2653                    i32::try_from(checked_pcrel(what, atrx)?).map_err(|_| AsmError::TooLarge)?;
2654                // SAFETY: 4-byte PLT-relative patch span, bounds-checked above.
2655                unsafe { write_unaligned(at as *mut i32, pcrel) };
2656            }
2657
2658            Reloc::RiscvGotHi20 => {
2659                let what = resolve(&get_got_entry)?;
2660                let pc_rel =
2661                    i32::try_from(checked_pcrel(what, atrx)?).map_err(|_| AsmError::TooLarge)?;
2662                // SAFETY: a RISC-V HI20 patch covers one 4-byte instruction, so
2663                // the slice from `at` is in bounds of the checked patch span.
2664                unsafe {
2665                    let buffer = core::slice::from_raw_parts_mut(at, 4);
2666                    let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2667                    let hi20 = (pc_rel as u32).wrapping_add(0x800) >> 12;
2668                    let insn = (insn & 0xfff) | (hi20 << 12);
2669                    buffer.copy_from_slice(&insn.to_le_bytes());
2670                }
2671            }
2672
2673            Reloc::RiscvPCRelLo12I => {
2674                let what = resolve(&get_got_entry)?;
2675                let pc_rel = checked_pcrel(what, atrx)?;
2676                let pc_rel = i32::try_from(pc_rel).map_err(|_| AsmError::TooLarge)?;
2677
2678                // SAFETY: a RISC-V LO12 patch covers one 4-byte instruction, so
2679                // the slice from `at` is in bounds of the checked patch span.
2680                unsafe {
2681                    let buffer = core::slice::from_raw_parts_mut(at, 4);
2682                    let insn = u32::from_le_bytes([buffer[0], buffer[1], buffer[2], buffer[3]]);
2683                    let lo12 = ((pc_rel as i64 + 4) as u32) & 0xfff;
2684                    let insn = (insn & 0xFFFFF) | (lo12 << 20);
2685                    buffer.copy_from_slice(&insn.to_le_bytes());
2686                }
2687            }
2688
2689            Reloc::RiscvCallPlt => {
2690                #[cfg(not(feature = "riscv"))]
2691                {
2692                    return Err(AsmError::InvalidArgument);
2693                }
2694                #[cfg(feature = "riscv")]
2695                {
2696                    // A R_RISCV_CALL_PLT relocation expects auipc+jalr instruction pair.
2697                    // It is the equivalent of two relocations:
2698                    // 1. R_RISCV_PCREL_HI20 on the `auipc`
2699                    // 2. R_RISCV_PCREL_LO12_I on the `jalr`
2700
2701                    let what = resolve(&get_address)?;
2702                    let pcrel = i32::try_from(checked_pcrel(what, atrx)?)
2703                        .map_err(|_| AsmError::TooLarge)?;
2704
2705                    // See https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses
2706                    // for a better explanation of the following code.
2707                    //
2708                    // Unlike the regular symbol relocations, here both "sub-relocations" point to the same address.
2709                    //
2710                    // `pcrel` is a signed value (+/- 2GiB range), when splitting it into two parts, we need to
2711                    // ensure that `hi20` is close enough to `pcrel` to be able to add `lo12` to it and still
2712                    // get a valid address.
2713                    //
2714                    // `lo12` is also a signed offset (+/- 2KiB range) relative to the `hi20` value.
2715                    //
2716                    // `hi20` should also be shifted right to be the "true" value. But we also need it
2717                    // left shifted for the `lo12` calculation and it also matches the instruction encoding.
2718                    let hi20 = pcrel.wrapping_add(0x800) as u32 & 0xFFFFF000u32;
2719                    let lo12 = (pcrel as u32).wrapping_sub(hi20) & 0xFFF;
2720
2721                    // SAFETY: a RiscvCallPlt patch spans two 4-byte instructions
2722                    // (`relocation_patch_size` returns 8), both within the checked
2723                    // patch span; the read-modify-write preserves the register
2724                    // fields and only ORs in the immediate.
2725                    unsafe {
2726                        let auipc_addr = at as *mut u32;
2727                        let auipc = riscv::opcodes::Inst::new(riscv::Opcode::AUIPC)
2728                            .encode()
2729                            .set_imm20(hi20 as _)
2730                            .value;
2731                        write_unaligned(auipc_addr, auipc_addr.read_unaligned() | auipc);
2732
2733                        let jalr_addr = at.add(4) as *mut u32;
2734                        let jalr = riscv::opcodes::Inst::new(riscv::Opcode::JALR)
2735                            .encode()
2736                            .set_imm12(lo12 as _)
2737                            .value;
2738                        write_unaligned(jalr_addr, jalr_addr.read_unaligned() | jalr);
2739                    }
2740                }
2741            }
2742
2743            Reloc::Aarch64AdrPrelPgHi21 => {
2744                let what = resolve(&get_address)?;
2745                let pages = ((what & !0xfff) as i128 - (atrx & !0xfff) as i128) >> 12;
2746                if !(-(1i128 << 20)..(1i128 << 20)).contains(&pages) {
2747                    return Err(AsmError::TooLarge);
2748                }
2749                let iptr = at as *mut u32;
2750                let imm21 = pages as u32 & 0x1f_ffff;
2751                let lo = (imm21 & 0x3) << 29;
2752                let hi = ((imm21 >> 2) & 0x7ffff) << 5;
2753                // SAFETY: an AArch64 ADR page patch covers one 4-byte instruction,
2754                // inside the checked patch span; the OR preserves the opcode and
2755                // register fields.
2756                unsafe {
2757                    let insn = iptr.read_unaligned();
2758                    write_unaligned(iptr, insn | lo | hi);
2759                }
2760            }
2761
2762            Reloc::Aarch64AddAbsLo12Nc => {
2763                let what = resolve(&get_address)?;
2764                let iptr = at as *mut u32;
2765                let imm12 = (what as u32 & 0xfff) << 10;
2766                // SAFETY: a 4-byte instruction inside the checked patch span.
2767                unsafe {
2768                    let insn = iptr.read_unaligned();
2769                    write_unaligned(iptr, insn | imm12);
2770                }
2771            }
2772
2773            Reloc::Aarch64AdrGotPage21 => {
2774                let what = resolve(&get_got_entry)?;
2775                let pages = ((what & !0xfff) as i128 - (atrx & !0xfff) as i128) >> 12;
2776                if !(-(1i128 << 20)..(1i128 << 20)).contains(&pages) {
2777                    return Err(AsmError::TooLarge);
2778                }
2779                let iptr = at as *mut u32;
2780                let imm21 = pages as u32 & 0x1f_ffff;
2781                let lo = (imm21 & 0x3) << 29;
2782                let hi = ((imm21 >> 2) & 0x7ffff) << 5;
2783                // SAFETY: a 4-byte instruction inside the checked patch span.
2784                unsafe {
2785                    let insn = iptr.read_unaligned();
2786                    write_unaligned(iptr, insn | lo | hi);
2787                }
2788            }
2789
2790            Reloc::Aarch64Ld64GotLo12Nc => {
2791                let what = resolve(&get_got_entry)?;
2792                if what & 7 != 0 {
2793                    return Err(AsmError::InvalidArgument);
2794                }
2795                let iptr = at as *mut u32;
2796                let imm12 = ((what as u32 & 0xfff) >> 3) << 10;
2797                // SAFETY: a 4-byte instruction inside the checked patch span.
2798                unsafe {
2799                    let insn = iptr.read_unaligned();
2800                    write_unaligned(iptr, insn | imm12);
2801                }
2802            }
2803
2804            _ => return Err(AsmError::InvalidArgument),
2805        }
2806    }
2807    Ok(())
2808}
2809
2810#[cfg(test)]
2811mod tests {
2812    use super::*;
2813
2814    fn resolved(address: usize) -> impl Fn(&RelocTarget) -> Result<*const u8, AsmError> {
2815        move |_| Ok(address as *const u8)
2816    }
2817
2818    fn unresolved(_: &RelocTarget) -> Result<*const u8, AsmError> {
2819        Ok(core::ptr::null())
2820    }
2821
2822    #[test]
2823    fn relocation_rejects_patch_outside_code() {
2824        let mut code = [0u8; 4];
2825        let relocs = [AsmReloc {
2826            offset: 1,
2827            kind: Reloc::Abs4,
2828            addend: 0,
2829            target: RelocTarget::Label(Label::from_id(0)),
2830        }];
2831
2832        let result = unsafe {
2833            perform_relocations(
2834                code.as_mut_ptr(),
2835                code.as_ptr(),
2836                code.len(),
2837                &relocs,
2838                resolved(1),
2839                resolved(1),
2840                resolved(1),
2841            )
2842        };
2843
2844        assert_eq!(result, Err(AsmError::InvalidArgument));
2845        assert_eq!(code, [0; 4]);
2846    }
2847
2848    #[test]
2849    fn relocation_rejects_null_and_overflowing_targets() {
2850        let mut code = [0u8; 8];
2851        let reloc = AsmReloc {
2852            offset: 0,
2853            kind: Reloc::Abs8,
2854            addend: 0,
2855            target: RelocTarget::Label(Label::from_id(0)),
2856        };
2857
2858        let null_result = unsafe {
2859            perform_relocations(
2860                code.as_mut_ptr(),
2861                code.as_ptr(),
2862                code.len(),
2863                core::slice::from_ref(&reloc),
2864                unresolved,
2865                unresolved,
2866                unresolved,
2867            )
2868        };
2869        assert_eq!(null_result, Err(AsmError::InvalidArgument));
2870
2871        let overflowing = AsmReloc {
2872            addend: -2,
2873            ..reloc
2874        };
2875        let overflow_result = unsafe {
2876            perform_relocations(
2877                code.as_mut_ptr(),
2878                code.as_ptr(),
2879                code.len(),
2880                core::slice::from_ref(&overflowing),
2881                resolved(1),
2882                resolved(1),
2883                resolved(1),
2884            )
2885        };
2886        assert_eq!(overflow_result, Err(AsmError::TooLarge));
2887    }
2888
2889    #[test]
2890    fn poisoned_buffer_rejects_raw_mutation_and_patch_metadata() {
2891        let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
2892        buffer.write_u32(0);
2893        let bytes = buffer.data().to_vec();
2894        buffer.record_error(AsmError::InvalidOperand);
2895
2896        buffer.write_u8(1);
2897        buffer.put8(2);
2898        assert!(
2899            !buffer
2900                .add_symbol(ExternalName::user(0, 1), RelocDistance::Far)
2901                .is_valid()
2902        );
2903        assert_eq!(buffer.add_constant(3u64), Constant(u32::MAX));
2904        assert!(!buffer.get_label().is_valid());
2905        buffer.add_reloc(Reloc::Abs4, RelocTarget::Label(Label::from_id(0)), 0);
2906        let patch = buffer.try_record_patch_site(0, LabelUse::X86JmpRel32, 0);
2907
2908        assert_eq!(patch, Err(AsmError::InvalidOperand));
2909        assert_eq!(buffer.data(), bytes);
2910        assert!(buffer.relocs().is_empty());
2911        assert!(buffer.patch_sites.is_empty());
2912        assert!(matches!(buffer.finish(), Err(AsmError::InvalidOperand)));
2913    }
2914
2915    #[test]
2916    fn target_rejects_foreign_relocations_and_patch_kinds() {
2917        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
2918        let label = buffer.get_label();
2919        buffer.add_reloc(Reloc::X86PCRel4, RelocTarget::Label(label), 0);
2920        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2921        assert!(buffer.relocs().is_empty());
2922
2923        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
2924        buffer.write_u32(0);
2925        assert_eq!(
2926            buffer.try_record_patch_site(0, LabelUse::X86JmpRel32, 0),
2927            Err(AsmError::InvalidArch)
2928        );
2929        assert!(buffer.patch_sites.is_empty());
2930
2931        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
2932        let label = buffer.get_label();
2933        buffer.use_label_at_offset(0, label, LabelUse::RVJal20);
2934        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2935        assert!(buffer.pending_fixup_records.is_empty());
2936
2937        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
2938        let label = buffer.get_label();
2939        assert_eq!(
2940            buffer.emit_veneer(label, 0, LabelUse::RVJal20),
2941            Err(AsmError::InvalidArch)
2942        );
2943        assert!(buffer.data().is_empty());
2944
2945        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
2946        buffer.write_u32(0);
2947        assert_eq!(
2948            buffer.try_record_patch_site(0, LabelUse::A64Ldr12, 0),
2949            Err(AsmError::UnsupportedInstruction {
2950                reason: "AArch64 LDR12 label patching is not implemented",
2951            })
2952        );
2953    }
2954
2955    #[cfg(not(feature = "x86"))]
2956    #[test]
2957    fn disabled_x86_label_use_is_rejected_without_a_fixup() {
2958        let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
2959        let label = buffer.get_label();
2960        buffer.write_u32(0);
2961        let bytes = buffer.data().to_vec();
2962
2963        buffer.use_label_at_offset(0, label, LabelUse::X86JmpRel32);
2964
2965        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2966        assert_eq!(buffer.data(), bytes);
2967        assert!(buffer.pending_fixup_records.is_empty());
2968        assert_eq!(buffer.finish().err(), Some(AsmError::InvalidArch));
2969
2970        let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
2971        let label = buffer.get_label();
2972        buffer.add_reloc(Reloc::X86PCRel4, RelocTarget::Label(label), 0);
2973        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2974        assert!(buffer.relocs().is_empty());
2975    }
2976
2977    #[cfg(not(feature = "riscv"))]
2978    #[test]
2979    fn disabled_riscv_label_use_is_rejected_without_a_fixup() {
2980        let mut buffer = CodeBuffer::new(Environment::new(Arch::RISCV64));
2981        let label = buffer.get_label();
2982        buffer.write_u32(0);
2983        let bytes = buffer.data().to_vec();
2984
2985        buffer.use_label_at_offset(0, label, LabelUse::RVPCRel32);
2986
2987        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2988        assert_eq!(buffer.data(), bytes);
2989        assert!(buffer.pending_fixup_records.is_empty());
2990        assert_eq!(buffer.finish().err(), Some(AsmError::InvalidArch));
2991
2992        let mut buffer = CodeBuffer::new(Environment::new(Arch::RISCV64));
2993        let label = buffer.get_label();
2994        buffer.add_reloc(Reloc::RiscvCallPlt, RelocTarget::Label(label), 0);
2995        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
2996        assert!(buffer.relocs().is_empty());
2997    }
2998
2999    #[cfg(not(feature = "aarch64"))]
3000    #[test]
3001    fn disabled_aarch64_label_use_is_rejected_without_a_fixup() {
3002        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
3003        let label = buffer.get_label();
3004        buffer.write_u32(0);
3005        let bytes = buffer.data().to_vec();
3006
3007        buffer.use_label_at_offset(0, label, LabelUse::A64Branch26);
3008
3009        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
3010        assert_eq!(buffer.data(), bytes);
3011        assert!(buffer.pending_fixup_records.is_empty());
3012        assert_eq!(buffer.finish().err(), Some(AsmError::InvalidArch));
3013
3014        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
3015        let label = buffer.get_label();
3016        buffer.add_reloc(Reloc::Arm64Call, RelocTarget::Label(label), 0);
3017        assert_eq!(buffer.error(), Some(&AsmError::InvalidArch));
3018        assert!(buffer.relocs().is_empty());
3019    }
3020
3021    #[test]
3022    fn poisoned_buffer_does_not_consume_pending_island_state() {
3023        let mut buffer = CodeBuffer::new(Environment::new(Arch::AArch64));
3024        buffer.add_constant(7u64);
3025        let pending_constants = buffer.pending_constants.len();
3026        buffer.record_error(AsmError::InvalidOperand);
3027
3028        assert_eq!(buffer.emit_island(0), Err(AsmError::InvalidOperand));
3029        assert_eq!(buffer.pending_constants.len(), pending_constants);
3030        assert!(buffer.data().is_empty());
3031    }
3032
3033    #[cfg(feature = "jit")]
3034    #[test]
3035    fn allocate_resolved_rejects_unresolved_symbols() {
3036        let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
3037        let symbol = buffer.add_symbol(ExternalName::user(0, 7), RelocDistance::Far);
3038        buffer.add_reloc(Reloc::Abs8, RelocTarget::Sym(symbol), 0);
3039        buffer.write_u64(0);
3040        let code = buffer.finish().unwrap();
3041        let mut allocator = JitAllocator::new(Default::default());
3042
3043        let result = code.allocate_resolved(&mut allocator, |_| core::ptr::null());
3044
3045        assert_eq!(result.err(), Some(AsmError::InvalidArgument));
3046    }
3047
3048    #[cfg(feature = "jit")]
3049    #[test]
3050    fn allocate_resolved_resolves_user_external_names() {
3051        let mut buffer = CodeBuffer::new(Environment::new(Arch::X64));
3052        let symbol = buffer.extern_user(1, 2, RelocDistance::Far);
3053        buffer.add_reloc(Reloc::Abs8, RelocTarget::Sym(symbol), 0);
3054        buffer.write_u64(0);
3055        let code = buffer.finish().unwrap();
3056        let mut allocator = JitAllocator::new(Default::default());
3057
3058        // Any non-null address is accepted; Abs8 just patches the pointer.
3059        let target = 0x1000usize as *const u8;
3060        let loaded = code
3061            .allocate_resolved(&mut allocator, |name| match name {
3062                ExternalName::User(u) if u.namespace == 1 && u.index == 2 => target,
3063                _ => core::ptr::null(),
3064            })
3065            .unwrap();
3066
3067        unsafe {
3068            let patched = core::ptr::read_unaligned(loaded.rx() as *const usize);
3069            assert_eq!(patched, target as usize);
3070        }
3071    }
3072
3073    #[test]
3074    fn extern_sym_deduplicates_by_name() {
3075        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3076        let first = buf.extern_sym("puts", RelocDistance::Far);
3077        let other = buf.extern_sym("printf", RelocDistance::Near);
3078        let again = buf.extern_sym("puts", RelocDistance::Near);
3079
3080        assert_eq!(first, again);
3081        assert_ne!(first, other);
3082        // The first declaration's distance wins.
3083        assert_eq!(buf.symbol_distance(first), Some(RelocDistance::Far));
3084        assert_eq!(buf.symbol_distance(other), Some(RelocDistance::Near));
3085    }
3086
3087    #[test]
3088    fn extern_user_deduplicates_by_namespace_and_index() {
3089        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3090        let first = buf.extern_user(0, 1, RelocDistance::Far);
3091        let other_ns = buf.extern_user(1, 1, RelocDistance::Near);
3092        let other_idx = buf.extern_user(0, 2, RelocDistance::Near);
3093        let again = buf.extern_user(0, 1, RelocDistance::Near);
3094
3095        assert_eq!(first, again);
3096        assert_ne!(first, other_ns);
3097        assert_ne!(first, other_idx);
3098        assert_eq!(buf.symbol_distance(first), Some(RelocDistance::Far));
3099        assert_eq!(buf.symbol_name(first), Some(&ExternalName::user(0, 1)));
3100    }
3101
3102    #[test]
3103    fn unknown_symbols_are_fallible() {
3104        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3105        let missing = Sym::from_id(u32::MAX);
3106
3107        assert_eq!(buf.symbol_name(missing), None);
3108        assert_eq!(buf.symbol_distance(missing), None);
3109
3110        let finalized = buf.finish().unwrap();
3111        assert_eq!(finalized.symbol_name(missing), None);
3112        assert_eq!(finalized.symbol_distance(missing), None);
3113    }
3114
3115    #[test]
3116    fn defined_symbols_are_resolved_at_finish() {
3117        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3118        buf.write_u8(0x90);
3119        let entry = buf.get_label();
3120        buf.bind_label(entry);
3121        buf.bind_symbol("entry", entry);
3122        buf.write_u8(0xC3);
3123
3124        let result = buf.finish().unwrap();
3125        assert_eq!(result.defined_symbol_str("entry"), Some(1));
3126        assert_eq!(result.defined_symbol_str("missing"), None);
3127        assert_eq!(
3128            result.defined_symbol_offset(&ExternalName::from("entry")),
3129            Some(1)
3130        );
3131    }
3132
3133    #[test]
3134    fn invalid_label_binding_is_reported_without_mutating_labels() {
3135        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3136        let invalid = Label::from_id(0);
3137
3138        assert_eq!(buf.try_bind_label(invalid), Err(AsmError::InvalidArgument));
3139        assert_eq!(buf.label_count(), 0);
3140
3141        buf.bind_label(invalid);
3142        assert_eq!(buf.error(), Some(&AsmError::InvalidArgument));
3143        assert!(matches!(buf.finish(), Err(AsmError::InvalidArgument)));
3144    }
3145
3146    #[cfg(feature = "x86")]
3147    #[test]
3148    fn invalid_patch_registration_does_not_create_metadata() {
3149        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3150
3151        assert_eq!(
3152            buf.try_record_patch_site(0, LabelUse::X86JmpRel32, 0),
3153            Err(AsmError::InvalidArgument)
3154        );
3155        assert!(buf.patch_sites.is_empty());
3156
3157        buf.record_patch_site(0, LabelUse::X86JmpRel32, 0);
3158        assert_eq!(buf.error(), Some(&AsmError::InvalidArgument));
3159        assert!(buf.patch_sites.is_empty());
3160    }
3161
3162    #[cfg(feature = "x86")]
3163    #[test]
3164    fn finish_rejects_an_unbound_fixup() {
3165        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3166        let label = buf.get_label();
3167        buf.write_u32(0);
3168        buf.use_label_at_offset(0, label, LabelUse::X86JmpRel32);
3169
3170        assert!(matches!(buf.finish(), Err(AsmError::UnboundLabel)));
3171    }
3172
3173    #[cfg(feature = "x86")]
3174    #[test]
3175    fn unsupported_veneer_leaves_buffer_unchanged() {
3176        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3177        let label = buf.get_label();
3178        buf.write_u32(0);
3179        let original = buf.data().to_vec();
3180
3181        assert_eq!(
3182            buf.emit_veneer(label, 0, LabelUse::X86JmpRel32),
3183            Err(AsmError::UnsupportedInstruction {
3184                reason: "branch range requires an unsupported veneer",
3185            })
3186        );
3187        assert_eq!(buf.data(), original);
3188    }
3189
3190    #[test]
3191    fn branch_ranges_include_the_last_encodable_offset() {
3192        for (kind, range) in [
3193            (LabelUse::A64Branch14, 1 << 15),
3194            (LabelUse::A64Branch19, 1 << 20),
3195            (LabelUse::A64Branch26, 1 << 27),
3196        ] {
3197            assert!(kind.can_reach(0, range - 4));
3198            assert!(!kind.can_reach(0, range));
3199            assert!(kind.can_reach(range, 0));
3200            assert!(!kind.can_reach(range + 4, 0));
3201        }
3202
3203        assert!(LabelUse::RVB12.can_reach(0, (1 << 12) - 2));
3204        assert!(!LabelUse::RVB12.can_reach(0, 1 << 12));
3205        assert!(LabelUse::RVB12.can_reach(1 << 12, 0));
3206        assert!(!LabelUse::RVB12.can_reach((1 << 12) + 2, 0));
3207    }
3208
3209    #[test]
3210    fn supported_veneer_ranges_cover_both_exact_boundaries() {
3211        for (kind, positive, negative, step) in [
3212            (LabelUse::RVJal20, (1 << 20) - 2, 1 << 20, 2),
3213            (LabelUse::RVB12, (1 << 12) - 2, 1 << 12, 2),
3214            (LabelUse::RVCJump, (1 << 11) - 2, 1 << 11, 2),
3215        ] {
3216            assert!(kind.supports_veneer());
3217            assert!(kind.can_reach(0, positive));
3218            assert!(!kind.can_reach(0, positive + step));
3219            assert!(kind.can_reach(negative, 0));
3220            assert!(!kind.can_reach(negative + step, 0));
3221        }
3222    }
3223
3224    #[cfg(feature = "riscv")]
3225    #[test]
3226    fn riscv_veneer_is_finalized_as_a_fixup() {
3227        let mut buf = CodeBuffer::new(Environment::new(Arch::RISCV64));
3228        let label = buf.get_label();
3229        buf.bind_label(label);
3230        buf.write_u32(0);
3231
3232        buf.emit_veneer(label, 0, LabelUse::RVB12).unwrap();
3233        let code = buf.finish().unwrap();
3234        assert_eq!(code.data().len(), 12);
3235        // The source branch reaches the veneer inserted directly after it.
3236        assert_eq!(&code.data()[0..4], &[0x00, 0x02, 0x00, 0x00]);
3237    }
3238
3239    #[cfg(feature = "riscv")]
3240    #[test]
3241    fn riscv_supported_veneers_are_inserted_by_islands() {
3242        for kind in [LabelUse::RVJal20, LabelUse::RVB12, LabelUse::RVCJump] {
3243            let mut buf = CodeBuffer::new(Environment::new(Arch::RISCV64));
3244            let label = buf.get_label();
3245            let patch_size = kind.patch_size();
3246            buf.get_appended_space(patch_size);
3247            buf.use_label_at_offset(0, label, kind);
3248
3249            buf.emit_island(u32::MAX).unwrap();
3250            buf.bind_label(label);
3251            let code = buf.finish().unwrap();
3252
3253            assert_eq!(code.data().len(), 12);
3254            assert!(kind.can_reach(0, 4));
3255        }
3256    }
3257
3258    #[cfg(feature = "riscv")]
3259    #[test]
3260    fn riscv_large_images_hit_exact_branch_boundaries() {
3261        for (kind, positive, step) in [
3262            (LabelUse::RVJal20, (1 << 20) - 2, 2),
3263            (LabelUse::RVB12, (1 << 12) - 2, 2),
3264            (LabelUse::RVCJump, (1 << 11) - 2, 2),
3265        ] {
3266            let mut exact = CodeBuffer::new(Environment::new(Arch::RISCV64));
3267            let label = exact.get_label();
3268            exact.get_appended_space(kind.patch_size());
3269            exact.use_label_at_offset(0, label, kind);
3270            exact.get_appended_space(positive as usize - kind.patch_size());
3271            exact.bind_label(label);
3272            assert!(exact.finish().is_ok());
3273
3274            let mut outside = CodeBuffer::new(Environment::new(Arch::RISCV64));
3275            let label = outside.get_label();
3276            outside.get_appended_space(kind.patch_size());
3277            outside.use_label_at_offset(0, label, kind);
3278            outside.get_appended_space((positive + step) as usize - kind.patch_size());
3279            outside.bind_label(label);
3280            assert_eq!(outside.finish().err(), Some(AsmError::TooLarge));
3281        }
3282    }
3283
3284    #[cfg(feature = "aarch64")]
3285    #[test]
3286    fn out_of_range_aarch64_branch_reports_unsupported_veneer() {
3287        let mut buf = CodeBuffer::new(Environment::new(Arch::AArch64));
3288        let label = buf.get_label();
3289        buf.write_u32(0);
3290        buf.use_label_at_offset(0, label, LabelUse::A64Branch14);
3291        buf.get_appended_space((1 << 15) - 4);
3292        buf.bind_label(label);
3293
3294        assert_eq!(
3295            buf.finish().err(),
3296            Some(AsmError::UnsupportedInstruction {
3297                reason: "AArch64 branch veneers are not implemented",
3298            })
3299        );
3300    }
3301
3302    #[cfg(feature = "x86")]
3303    #[test]
3304    fn x86_branch_relaxation_honors_rel8_boundaries() {
3305        use crate::x86::{Assembler, JEmitter, JmpEmitter};
3306
3307        let forward = |padding: usize, conditional: bool| {
3308            let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3309            let target = buf.get_label();
3310            {
3311                let mut asm = Assembler::new(&mut buf);
3312                if conditional {
3313                    asm.jz(target);
3314                } else {
3315                    asm.jmp(target);
3316                }
3317            }
3318            for _ in 0..padding {
3319                buf.write_u8(0x90);
3320            }
3321            buf.bind_label(target);
3322            buf.finish().unwrap().data().to_vec()
3323        };
3324        for conditional in [false, true] {
3325            let short = forward(127, conditional);
3326            assert_eq!(short[0], if conditional { 0x74 } else { 0xEB });
3327            assert_eq!(short[1], 127);
3328
3329            let near = forward(128, conditional);
3330            if conditional {
3331                assert_eq!(&near[..2], &[0x0F, 0x84]);
3332            } else {
3333                assert_eq!(near[0], 0xE9);
3334            }
3335        }
3336
3337        let backward = |padding: usize, conditional: bool| {
3338            let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3339            let target = buf.get_label();
3340            buf.bind_label(target);
3341            for _ in 0..padding {
3342                buf.write_u8(0x90);
3343            }
3344            {
3345                let mut asm = Assembler::new(&mut buf);
3346                if conditional {
3347                    asm.jz(target);
3348                } else {
3349                    asm.jmp(target);
3350                }
3351            }
3352            buf.finish().unwrap().data().to_vec()
3353        };
3354        for conditional in [false, true] {
3355            let short = backward(126, conditional);
3356            assert_eq!(
3357                &short[126..],
3358                &[if conditional { 0x74 } else { 0xEB }, 0x80]
3359            );
3360
3361            let near = backward(127, conditional);
3362            assert_eq!(near[127], if conditional { 0x0F } else { 0xE9 });
3363        }
3364    }
3365
3366    #[cfg(feature = "x86")]
3367    #[test]
3368    fn x86_branch_relaxation_reaches_a_bounded_fixed_point() {
3369        use crate::x86::{Assembler, JmpEmitter};
3370
3371        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3372        let target = buf.get_label();
3373        {
3374            let mut asm = Assembler::new(&mut buf);
3375            asm.jmp(target);
3376        }
3377        for _ in 0..120 {
3378            buf.write_u8(0x90);
3379        }
3380        {
3381            let mut asm = Assembler::new(&mut buf);
3382            asm.jmp(target);
3383        }
3384        for _ in 0..3 {
3385            buf.write_u8(0x90);
3386        }
3387        buf.bind_label(target);
3388
3389        let code = buf.finish().unwrap();
3390        assert_eq!(&code.data()[..2], &[0xEB, 125]);
3391        assert_eq!(&code.data()[122..124], &[0xEB, 3]);
3392        assert_eq!(code.label_offsets[target.id() as usize], 127);
3393    }
3394
3395    #[cfg(feature = "x86")]
3396    #[test]
3397    fn x86_branch_relaxation_rebases_metadata_and_is_deterministic() {
3398        use crate::x86::{Assembler, JmpEmitter};
3399
3400        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3401        let target = buf.get_label();
3402        {
3403            let mut asm = Assembler::new(&mut buf);
3404            asm.jmp(target);
3405        }
3406        buf.write_u8(0x90);
3407        buf.bind_label(target);
3408        buf.bind_symbol("target", target);
3409
3410        buf.add_reloc(Reloc::Abs4, RelocTarget::Label(target), 0);
3411        buf.write_u32(0);
3412        let block = buf.reserve_patch_block(4, 1).unwrap();
3413        let block_offset = block.offset();
3414        let site = buf
3415            .try_record_patch_site(
3416                block_offset,
3417                LabelUse::X86JmpRel32,
3418                buf.label_offset(target),
3419            )
3420            .unwrap();
3421
3422        let later = buf.get_label();
3423        {
3424            let mut asm = Assembler::new(&mut buf);
3425            asm.long().jmp(later);
3426        }
3427        buf.write_u8(0x90);
3428        buf.bind_label(later);
3429
3430        let first = buf.finish().unwrap();
3431        assert_eq!(&first.data()[..3], &[0xEB, 1, 0x90]);
3432        assert_eq!(first.defined_symbol_str("target"), Some(3));
3433        assert_eq!(first.relocs()[0].offset, 3);
3434        assert_eq!(
3435            first
3436                .patch_catalog()
3437                .block(PatchBlockId::from_index(0))
3438                .unwrap()
3439                .offset,
3440            7
3441        );
3442        let patch_site = first.patch_catalog().site(site).unwrap();
3443        assert_eq!(patch_site.offset, 7);
3444        assert_eq!(patch_site.current_target, 3);
3445        assert_eq!(&first.data()[11..], &[0xE9, 1, 0, 0, 0, 0x90]);
3446
3447        let second = buf.finish().unwrap();
3448        assert_eq!(second.data(), first.data());
3449        assert_eq!(second.relocs(), first.relocs());
3450        assert_eq!(second.patch_catalog(), first.patch_catalog());
3451    }
3452
3453    #[cfg(feature = "x86")]
3454    #[test]
3455    fn x86_branch_relaxation_preserves_recorded_alignment() {
3456        use crate::x86::{Assembler, JmpEmitter};
3457
3458        let mut buf = CodeBuffer::new(Environment::new(Arch::X64));
3459        let target = buf.get_label();
3460        {
3461            let mut asm = Assembler::new(&mut buf);
3462            asm.jmp(target);
3463        }
3464        buf.try_align_to(16).unwrap();
3465        buf.bind_label(target);
3466
3467        let code = buf.finish().unwrap();
3468        assert_eq!(code.data()[0], 0xE9);
3469        assert_eq!(code.label_offsets[target.id() as usize], 16);
3470    }
3471}