wasmer_compiler/engine/
link.rs

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