wasmer_compiler/engine/
link.rs

1//! Linking for Universal-compiled code.
2
3use crate::{
4    FunctionExtent, get_libcall_trampoline,
5    types::{
6        relocation::{RelocationKind, RelocationLike, RelocationTarget},
7        section::SectionIndex,
8    },
9};
10use std::{
11    collections::{HashMap, HashSet},
12    ptr::{read_unaligned, write_unaligned},
13};
14
15use wasmer_types::{FunctionIndex, LocalFunctionIndex, ModuleInfo, entity::PrimaryMap};
16use wasmer_vm::{FunctionBodyPtr, SectionBodyPtr, libcalls::function_pointer};
17
18#[allow(clippy::too_many_arguments)]
19fn apply_relocation(
20    body: usize,
21    r: &impl RelocationLike,
22    allocated_functions: &PrimaryMap<LocalFunctionIndex, FunctionExtent>,
23    allocated_dynamic_function_trampolines: &PrimaryMap<FunctionIndex, FunctionBodyPtr>,
24    allocated_sections: &PrimaryMap<SectionIndex, SectionBodyPtr>,
25    libcall_trampolines_sec_idx: SectionIndex,
26    libcall_trampoline_len: usize,
27    riscv_pcrel_hi20s: &mut HashMap<usize, u32>,
28    get_got_address: &dyn Fn(RelocationTarget) -> Option<usize>,
29) {
30    let reloc_target = r.reloc_target();
31
32    // Note: if the relocation needs GOT and its addend is not zero we will relax the
33    // relocation and, instead of making it use the GOT entry, we will fixup the assembly to
34    // use the final pointer directly, without any indirection. Also, see the comment in
35    // compiler-llvm/src/object_file.rs:288.
36    let target_func_address: usize = if r.kind().needs_got() && r.addend() == 0 {
37        if let Some(got_address) = get_got_address(reloc_target) {
38            got_address
39        } else {
40            panic!("No GOT entry for reloc target {reloc_target:?}")
41        }
42    } else {
43        match reloc_target {
44            RelocationTarget::LocalFunc(index) => *allocated_functions[index].ptr as usize,
45            RelocationTarget::DynamicTrampoline(index) => {
46                *allocated_dynamic_function_trampolines[index] as usize
47            }
48            RelocationTarget::LibCall(libcall) => {
49                // Use the direct target of the libcall if the relocation supports
50                // a full 64-bit address. Otherwise use a trampoline.
51                if matches!(
52                    r.kind(),
53                    RelocationKind::Abs8
54                        | RelocationKind::X86PCRel8
55                        | RelocationKind::MachoArm64RelocUnsigned
56                        | RelocationKind::MachoX86_64RelocUnsigned
57                ) {
58                    function_pointer(libcall)
59                } else {
60                    get_libcall_trampoline(
61                        libcall,
62                        allocated_sections[libcall_trampolines_sec_idx].0 as usize,
63                        libcall_trampoline_len,
64                    )
65                }
66            }
67            RelocationTarget::CustomSection(custom_section) => {
68                *allocated_sections[custom_section] as usize
69            }
70        }
71    };
72
73    // A set of addresses at which a SUBTRACTOR relocation was applied.
74    let mut macho_aarch64_subtractor_addresses = HashSet::new();
75
76    match r.kind() {
77        RelocationKind::Abs8 => unsafe {
78            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
79            write_unaligned(reloc_address as *mut u64, reloc_delta);
80        },
81        RelocationKind::X86PCRel4 => unsafe {
82            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
83            write_unaligned(reloc_address as *mut u32, reloc_delta as _);
84        },
85        RelocationKind::X86PCRel8 => unsafe {
86            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
87            write_unaligned(reloc_address as *mut u64, reloc_delta);
88        },
89        RelocationKind::X86CallPCRel4 => unsafe {
90            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
91            write_unaligned(reloc_address as *mut u32, reloc_delta as _);
92        },
93        RelocationKind::Arm64Call => unsafe {
94            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
95            if (reloc_delta as i64).abs() >= 0x1000_0000 {
96                panic!(
97                    "Relocation to big for {:?} for {:?} with {:x}, current val {:x}",
98                    r.kind(),
99                    r.reloc_target(),
100                    reloc_delta,
101                    read_unaligned(reloc_address as *mut u32)
102                )
103            }
104            let reloc_delta = (((reloc_delta / 4) as u32) & 0x3ff_ffff)
105                | (read_unaligned(reloc_address as *mut u32) & 0xfc00_0000);
106            write_unaligned(reloc_address as *mut u32, reloc_delta);
107        },
108        RelocationKind::Arm64Movw0 => unsafe {
109            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
110            let reloc_delta =
111                (((reloc_delta & 0xffff) as u32) << 5) | read_unaligned(reloc_address as *mut u32);
112            write_unaligned(reloc_address as *mut u32, reloc_delta);
113        },
114        RelocationKind::Arm64Movw1 => unsafe {
115            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
116            let reloc_delta = ((((reloc_delta >> 16) & 0xffff) as u32) << 5)
117                | read_unaligned(reloc_address as *mut u32);
118            write_unaligned(reloc_address as *mut u32, reloc_delta);
119        },
120        RelocationKind::Arm64Movw2 => unsafe {
121            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
122            let reloc_delta = ((((reloc_delta >> 32) & 0xffff) as u32) << 5)
123                | read_unaligned(reloc_address as *mut u32);
124            write_unaligned(reloc_address as *mut u32, reloc_delta);
125        },
126        RelocationKind::Arm64Movw3 => unsafe {
127            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
128            let reloc_delta = ((((reloc_delta >> 48) & 0xffff) as u32) << 5)
129                | read_unaligned(reloc_address as *mut u32);
130            write_unaligned(reloc_address as *mut u32, reloc_delta);
131        },
132        RelocationKind::RiscvPCRelHi20 => unsafe {
133            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
134
135            // save for later reference with RiscvPCRelLo12I
136            riscv_pcrel_hi20s.insert(reloc_address, reloc_delta as u32);
137
138            let reloc_delta = ((reloc_delta.wrapping_add(0x800) & 0xfffff000) as u32)
139                | read_unaligned(reloc_address as *mut u32);
140            write_unaligned(reloc_address as *mut u32, reloc_delta);
141        },
142        RelocationKind::RiscvPCRelLo12I => unsafe {
143            let (reloc_address, reloc_abs) = r.for_address(body, target_func_address as u64);
144            let reloc_delta = ((riscv_pcrel_hi20s.get(&(reloc_abs as usize)).expect(
145                "R_RISCV_PCREL_LO12_I relocation target must be a symbol with R_RISCV_PCREL_HI20",
146            ) & 0xfff)
147                << 20)
148                | read_unaligned(reloc_address as *mut u32);
149            write_unaligned(reloc_address as *mut u32, reloc_delta);
150        },
151        RelocationKind::RiscvCall => unsafe {
152            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
153            let reloc_delta = ((reloc_delta & 0xfff) << 52)
154                | (reloc_delta.wrapping_add(0x800) & 0xfffff000)
155                | read_unaligned(reloc_address as *mut u64);
156            write_unaligned(reloc_address as *mut u64, reloc_delta);
157        },
158        RelocationKind::LArchAbsHi20 | RelocationKind::LArchPCAlaHi20 => unsafe {
159            let (reloc_address, reloc_abs) = r.for_address(body, target_func_address as u64);
160            let reloc_abs = ((((reloc_abs >> 12) & 0xfffff) as u32) << 5)
161                | read_unaligned(reloc_address as *mut u32);
162            write_unaligned(reloc_address as *mut u32, reloc_abs);
163        },
164        RelocationKind::LArchAbsLo12 | RelocationKind::LArchPCAlaLo12 => unsafe {
165            let (reloc_address, reloc_abs) = r.for_address(body, target_func_address as u64);
166            let reloc_abs =
167                (((reloc_abs & 0xfff) as u32) << 10) | read_unaligned(reloc_address as *mut u32);
168            write_unaligned(reloc_address as *mut u32, reloc_abs);
169        },
170        RelocationKind::LArchAbs64Hi12 | RelocationKind::LArchPCAla64Hi12 => unsafe {
171            let (reloc_address, reloc_abs) = r.for_address(body, target_func_address as u64);
172            let reloc_abs = ((((reloc_abs >> 52) & 0xfff) as u32) << 10)
173                | read_unaligned(reloc_address as *mut u32);
174            write_unaligned(reloc_address as *mut u32, reloc_abs);
175        },
176        RelocationKind::LArchAbs64Lo20 | RelocationKind::LArchPCAla64Lo20 => unsafe {
177            let (reloc_address, reloc_abs) = r.for_address(body, target_func_address as u64);
178            let reloc_abs = ((((reloc_abs >> 32) & 0xfffff) as u32) << 5)
179                | read_unaligned(reloc_address as *mut u32);
180            write_unaligned(reloc_address as *mut u32, reloc_abs);
181        },
182        RelocationKind::LArchCall36 => unsafe {
183            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
184            let reloc_delta1 = ((((reloc_delta >> 18) & 0xfffff) as u32) << 5)
185                | read_unaligned(reloc_address as *mut u32);
186            write_unaligned(reloc_address as *mut u32, reloc_delta1);
187            let reloc_delta2 = ((((reloc_delta >> 2) & 0xffff) as u32) << 10)
188                | read_unaligned((reloc_address + 4) as *mut u32);
189            write_unaligned((reloc_address + 4) as *mut u32, reloc_delta2);
190        },
191        RelocationKind::Aarch64AdrPrelPgHi21 => unsafe {
192            let (reloc_address, delta) = r.for_address(body, target_func_address as u64);
193
194            let delta = delta as isize;
195            assert!(
196                ((-1 << 32)..(1 << 32)).contains(&delta),
197                "can't generate page-relative relocation with ±4GB `adrp` instruction"
198            );
199
200            let op = read_unaligned(reloc_address as *mut u32);
201            let delta = delta >> 12;
202            let immlo = ((delta as u32) & 0b11) << 29;
203            let immhi = (((delta as u32) >> 2) & 0x7ffff) << 5;
204            let mask = !((0x7ffff << 5) | (0b11 << 29));
205            let op = (op & mask) | immlo | immhi;
206
207            write_unaligned(reloc_address as *mut u32, op);
208        },
209        RelocationKind::Aarch64AdrPrelLo21 => unsafe {
210            let (reloc_address, delta) = r.for_address(body, target_func_address as u64);
211
212            let delta = delta as isize;
213            assert!(
214                ((-1 << 20)..(1 << 20)).contains(&delta),
215                "can't generate an ADR_PREL_LO21 relocation with an immediate larger than 20 bits"
216            );
217
218            let op = read_unaligned(reloc_address as *mut u32);
219            let immlo = ((delta as u32) & 0b11) << 29;
220            let immhi = (((delta as u32) >> 2) & 0x7ffff) << 5;
221            let mask = !((0x7ffff << 5) | (0b11 << 29));
222            let op = (op & mask) | immlo | immhi;
223
224            write_unaligned(reloc_address as *mut u32, op);
225        },
226        RelocationKind::Aarch64AddAbsLo12Nc => unsafe {
227            let (reloc_address, delta) = r.for_address(body, target_func_address as u64);
228
229            let delta = delta as isize;
230            let op = read_unaligned(reloc_address as *mut u32);
231            let imm = ((delta as u32) & 0xfff) << 10;
232            let mask = !((0xfff) << 10);
233            let op = (op & mask) | imm;
234
235            write_unaligned(reloc_address as *mut u32, op);
236        },
237        RelocationKind::Aarch64Ldst128AbsLo12Nc => unsafe {
238            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
239            let reloc_delta = ((reloc_delta as u32 & 0xfff) >> 4) << 10
240                | (read_unaligned(reloc_address as *mut u32) & 0xFFC003FF);
241            write_unaligned(reloc_address as *mut u32, reloc_delta);
242        },
243        RelocationKind::Aarch64Ldst64AbsLo12Nc => unsafe {
244            let (reloc_address, reloc_delta) = r.for_address(body, target_func_address as u64);
245            let reloc_delta = ((reloc_delta as u32 & 0xfff) >> 3) << 10
246                | (read_unaligned(reloc_address as *mut u32) & 0xFFC003FF);
247            write_unaligned(reloc_address as *mut u32, reloc_delta);
248        },
249        RelocationKind::MachoArm64RelocSubtractor | RelocationKind::MachoX86_64RelocSubtractor => unsafe {
250            let (reloc_address, reloc_sub) = r.for_address(body, target_func_address as u64);
251            macho_aarch64_subtractor_addresses.insert(reloc_address);
252            write_unaligned(reloc_address as *mut u64, reloc_sub);
253        },
254        RelocationKind::MachoArm64RelocGotLoadPage21
255        | RelocationKind::MachoArm64RelocTlvpLoadPage21 => unsafe {
256            let (reloc_address, _) = r.for_address(body, target_func_address as u64);
257            let target_func_page = target_func_address & !0xfff;
258            let reloc_at_page = reloc_address & !0xfff;
259            let pcrel = (target_func_page as isize)
260                .checked_sub(reloc_at_page as isize)
261                .unwrap();
262            assert!(
263                (-1 << 32) <= (pcrel as i64) && (pcrel as i64) < (1 << 32),
264                "can't reach GOT page with ±4GB `adrp` instruction"
265            );
266            let val = pcrel >> 12;
267
268            let immlo = ((val as u32) & 0b11) << 29;
269            let immhi = (((val as u32) >> 2) & 0x7ffff) << 5;
270            let mask = !((0x7ffff << 5) | (0b11 << 29));
271            let op = read_unaligned(reloc_address as *mut u32);
272            write_unaligned(reloc_address as *mut u32, (op & mask) | immlo | immhi);
273        },
274
275        RelocationKind::MachoArm64RelocPage21 => unsafe {
276            let target_page: u64 =
277                ((target_func_address.wrapping_add(r.addend() as _)) & !0xfff) as u64;
278            let reloc_address = body.wrapping_add(r.offset() as _);
279            let pc_page: u64 = (reloc_address & !0xfff) as u64;
280            let page_delta = target_page - pc_page;
281            let raw_instr = read_unaligned(reloc_address as *mut u32);
282            assert_eq!(
283                (raw_instr & 0xffffffe0),
284                0x90000000,
285                "raw_instr isn't an ADRP instruction"
286            );
287
288            let immlo: u32 = ((page_delta >> 12) & 0x3) as _;
289            let immhi: u32 = ((page_delta >> 14) & 0x7ffff) as _;
290            let fixed_instr = raw_instr | (immlo << 29) | (immhi << 5);
291            write_unaligned(reloc_address as *mut u32, fixed_instr);
292        },
293        RelocationKind::MachoArm64RelocPageoff12 => unsafe {
294            let target_offset: u64 =
295                ((target_func_address.wrapping_add(r.addend() as _)) & 0xfff) as u64;
296
297            let reloc_address = body.wrapping_add(r.offset() as _);
298            let raw_instr = read_unaligned(reloc_address as *mut u32);
299            let imm_shift = {
300                const VEC128_MASK: u32 = 0x04800000;
301
302                const LOAD_STORE_IMM12_MASK: u32 = 0x3b000000;
303                let is_load_store_imm12 = (raw_instr & LOAD_STORE_IMM12_MASK) == 0x39000000;
304
305                if is_load_store_imm12 {
306                    let mut implicit_shift = raw_instr >> 30;
307
308                    if implicit_shift == 0 && (raw_instr & VEC128_MASK) == VEC128_MASK {
309                        implicit_shift = 4;
310                    }
311
312                    implicit_shift
313                } else {
314                    0
315                }
316            };
317
318            assert_eq!(
319                target_offset & ((1 << imm_shift) - 1),
320                0,
321                "PAGEOFF12 target is not aligned"
322            );
323
324            let encoded_imm: u32 = ((target_offset as u32) >> imm_shift) << 10;
325            let fixed_instr: u32 = raw_instr | encoded_imm;
326            write_unaligned(reloc_address as *mut u32, fixed_instr);
327        },
328
329        RelocationKind::MachoArm64RelocGotLoadPageoff12 => unsafe {
330            // See comment at the top of the function. TLDR: if addend != 0 we can't really use the
331            // GOT entry. We fixup this relocation to use a `add` rather than a `ldr` instruction,
332            // skipping the indirection from the GOT.
333            if r.addend() == 0 {
334                let (reloc_address, _) = r.for_address(body, target_func_address as u64);
335                assert_eq!(target_func_address & 0b111, 0);
336                let val = target_func_address >> 3;
337                let imm9 = ((val & 0x1ff) << 10) as u32;
338                let mask = !(0x1ff << 10);
339                let op = read_unaligned(reloc_address as *mut u32);
340                write_unaligned(reloc_address as *mut u32, (op & mask) | imm9);
341            } else {
342                let fixup_ptr = body + r.offset() as usize;
343                let target_address: usize = target_func_address + r.addend() as usize;
344
345                let raw_instr = read_unaligned(fixup_ptr as *mut u32);
346
347                assert_eq!(
348                    raw_instr & 0xfffffc00,
349                    0xf9400000,
350                    "raw_instr isn't a 64-bit LDR immediate (bits: {raw_instr:032b}, hex: {raw_instr:x})"
351                );
352
353                let reg: u32 = raw_instr & 0b11111;
354
355                let mut fixup_ldr = 0x91000000 | (reg << 5) | reg;
356                fixup_ldr |= ((target_address & 0xfff) as u32) << 10;
357
358                write_unaligned(fixup_ptr as *mut u32, fixup_ldr);
359            }
360        },
361        RelocationKind::MachoArm64RelocUnsigned | RelocationKind::MachoX86_64RelocUnsigned => unsafe {
362            let (reloc_address, mut reloc_delta) = r.for_address(body, target_func_address as u64);
363
364            if macho_aarch64_subtractor_addresses.contains(&reloc_address) {
365                reloc_delta -= read_unaligned(reloc_address as *mut u64);
366            }
367
368            write_unaligned(reloc_address as *mut u64, reloc_delta);
369        },
370
371        RelocationKind::MachoArm64RelocPointerToGot => unsafe {
372            let at = body + r.offset() as usize;
373            let pcrel = i32::try_from((target_func_address as isize) - (at as isize)).unwrap();
374            write_unaligned(at as *mut i32, pcrel);
375        },
376
377        RelocationKind::MachoArm64RelocBranch26 => unsafe {
378            let fixup_ptr = body + r.offset() as usize;
379            assert_eq!(fixup_ptr & 0x3, 0, "Branch-inst is not 32-bit aligned");
380            let value = i32::try_from((target_func_address as isize) - (fixup_ptr as isize))
381                .unwrap()
382                .wrapping_add(r.addend() as _);
383            assert!(
384                value & 0x3 == 0,
385                "BranchPCRel26 target is not 32-bit aligned"
386            );
387
388            assert!(
389                (-(1 << 27)..=((1 << 27) - 1)).contains(&value),
390                "out of range BranchPCRel26 target"
391            );
392
393            let raw_instr = read_unaligned(fixup_ptr as *mut u32);
394
395            assert_eq!(
396                raw_instr & 0x7fffffff,
397                0x14000000,
398                "RawInstr isn't a B or BR immediate instruction"
399            );
400            let imm: u32 = ((value as u32) & ((1 << 28) - 1)) >> 2;
401            let fixed_instr: u32 = raw_instr | imm;
402
403            write_unaligned(fixup_ptr as *mut u32, fixed_instr);
404        },
405        kind => panic!("Relocation kind unsupported in the current architecture: {kind}"),
406    }
407}
408
409/// Links a module, patching the allocated functions with the
410/// required relocations and jump tables.
411#[allow(clippy::too_many_arguments)]
412pub fn link_module<'a>(
413    _module: &ModuleInfo,
414    allocated_functions: &PrimaryMap<LocalFunctionIndex, FunctionExtent>,
415    allocated_dynamic_function_trampolines: &PrimaryMap<FunctionIndex, FunctionBodyPtr>,
416    function_relocations: impl Iterator<
417        Item = (
418            LocalFunctionIndex,
419            impl Iterator<Item = &'a (impl RelocationLike + 'a)>,
420        ),
421    >,
422    allocated_sections: &PrimaryMap<SectionIndex, SectionBodyPtr>,
423    section_relocations: impl Iterator<
424        Item = (
425            SectionIndex,
426            impl Iterator<Item = &'a (impl RelocationLike + 'a)>,
427        ),
428    >,
429    libcall_trampolines: SectionIndex,
430    trampoline_len: usize,
431    get_got_address: &'a dyn Fn(RelocationTarget) -> Option<usize>,
432) {
433    let mut riscv_pcrel_hi20s: HashMap<usize, u32> = HashMap::new();
434
435    for (i, section_relocs) in section_relocations {
436        let body = *allocated_sections[i] as usize;
437        for r in section_relocs {
438            apply_relocation(
439                body,
440                r,
441                allocated_functions,
442                allocated_dynamic_function_trampolines,
443                allocated_sections,
444                libcall_trampolines,
445                trampoline_len,
446                &mut riscv_pcrel_hi20s,
447                get_got_address,
448            );
449        }
450    }
451    for (i, function_relocs) in function_relocations {
452        let body = *allocated_functions[i].ptr as usize;
453        for r in function_relocs {
454            apply_relocation(
455                body,
456                r,
457                allocated_functions,
458                allocated_dynamic_function_trampolines,
459                allocated_sections,
460                libcall_trampolines,
461                trampoline_len,
462                &mut riscv_pcrel_hi20s,
463                get_got_address,
464            );
465        }
466    }
467}