Path: blob/main/cranelift/jit/src/compiled_blob.rs
3072 views
use std::ptr;12use cranelift_codegen::binemit::Reloc;3use cranelift_module::{ModuleError, ModuleReloc, ModuleRelocTarget, ModuleResult};45use crate::JITMemoryProvider;6use crate::memory::JITMemoryKind;78const VENEER_SIZE: usize = 24; // ldr + br + pointer910/// Reads a 32bit instruction at `iptr`, and writes it again after11/// being altered by `modifier`12unsafe fn modify_inst32(iptr: *mut u32, modifier: impl FnOnce(u32) -> u32) {13let inst = iptr.read_unaligned();14let new_inst = modifier(inst);15iptr.write_unaligned(new_inst);16}1718#[derive(Clone)]19pub(crate) struct CompiledBlob {20ptr: *mut u8,21size: usize,22relocs: Vec<ModuleReloc>,23veneer_count: usize,24#[cfg(feature = "wasmtime-unwinder")]25wasmtime_exception_data: Option<Vec<u8>>,26}2728unsafe impl Send for CompiledBlob {}2930impl CompiledBlob {31pub(crate) fn new(32memory: &mut dyn JITMemoryProvider,33data: &[u8],34align: u64,35relocs: Vec<ModuleReloc>,36#[cfg(feature = "wasmtime-unwinder")] wasmtime_exception_data: Option<Vec<u8>>,37kind: JITMemoryKind,38) -> ModuleResult<Self> {39// Reserve veneers for all function calls just in case40let mut veneer_count = 0;41for reloc in &relocs {42match reloc.kind {43Reloc::Arm64Call => veneer_count += 1,44_ => {}45}46}4748let ptr = memory49.allocate(data.len() + veneer_count * VENEER_SIZE, align, kind)50.map_err(|e| ModuleError::Allocation { err: e })?;5152unsafe {53ptr::copy_nonoverlapping(data.as_ptr(), ptr, data.len());54}5556Ok(CompiledBlob {57ptr,58size: data.len(),59relocs,60veneer_count,61#[cfg(feature = "wasmtime-unwinder")]62wasmtime_exception_data,63})64}6566pub(crate) fn new_zeroed(67memory: &mut dyn JITMemoryProvider,68size: usize,69align: u64,70relocs: Vec<ModuleReloc>,71#[cfg(feature = "wasmtime-unwinder")] wasmtime_exception_data: Option<Vec<u8>>,72kind: JITMemoryKind,73) -> ModuleResult<Self> {74let ptr = memory75.allocate(size, align, kind)76.map_err(|e| ModuleError::Allocation { err: e })?;7778unsafe { ptr::write_bytes(ptr, 0, size) };7980Ok(CompiledBlob {81ptr,82size,83relocs,84veneer_count: 0,85#[cfg(feature = "wasmtime-unwinder")]86wasmtime_exception_data,87})88}8990pub(crate) fn ptr(&self) -> *const u8 {91self.ptr92}9394pub(crate) fn size(&self) -> usize {95self.size96}9798#[cfg(feature = "wasmtime-unwinder")]99pub(crate) fn wasmtime_exception_data(&self) -> Option<&[u8]> {100self.wasmtime_exception_data.as_deref()101}102103pub(crate) fn perform_relocations(104&self,105get_address: impl Fn(&ModuleRelocTarget) -> *const u8,106) {107use std::ptr::write_unaligned;108109let mut next_veneer_idx = 0;110111for (112i,113&ModuleReloc {114kind,115offset,116ref name,117addend,118},119) in self.relocs.iter().enumerate()120{121debug_assert!((offset as usize) < self.size);122let at = unsafe { self.ptr.offset(isize::try_from(offset).unwrap()) };123match kind {124Reloc::Abs4 => {125let base = get_address(name);126let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };127unsafe {128write_unaligned(at as *mut u32, u32::try_from(what as usize).unwrap())129};130}131Reloc::Abs8 => {132let base = get_address(name);133let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };134unsafe {135write_unaligned(at as *mut u64, u64::try_from(what as usize).unwrap())136};137}138Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => {139let base = get_address(name);140let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };141let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap();142unsafe { write_unaligned(at as *mut i32, pcrel) };143}144Reloc::X86GOTPCRel4 => {145panic!("GOT relocation shouldn't be generated when !is_pic");146}147Reloc::X86CallPLTRel4 => {148panic!("PLT relocation shouldn't be generated when !is_pic");149}150Reloc::S390xPCRel32Dbl | Reloc::S390xPLTRel32Dbl => {151let base = get_address(name);152let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };153let pcrel = i32::try_from(((what as isize) - (at as isize)) >> 1).unwrap();154unsafe { write_unaligned(at as *mut i32, pcrel) };155}156Reloc::Arm64Call => {157let base = get_address(name);158let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };159// The instruction is 32 bits long.160let iptr = at as *mut u32;161162// The offset encoded in the `bl` instruction is the163// number of bytes divided by 4.164let diff = ((what as isize) - (at as isize)) >> 2;165// Sign propagating right shift disposes of the166// included bits, so the result is expected to be167// either all sign bits or 0 when in-range, depending168// on if the original value was negative or positive.169if (diff >> 25 == -1) || (diff >> 25 == 0) {170// The lower 26 bits of the `bl` instruction form the171// immediate offset argument.172let chop = 32 - 26;173let imm26 = (diff as u32) << chop >> chop;174unsafe { modify_inst32(iptr, |inst| inst | imm26) };175} else {176// If the target is out of range for a direct call, insert a veneer at the177// end of the function.178let veneer_idx = next_veneer_idx;179next_veneer_idx += 1;180assert!(veneer_idx <= self.veneer_count);181let veneer =182unsafe { self.ptr.byte_add(self.size + veneer_idx * VENEER_SIZE) };183184// Write the veneer185// x16 is reserved as scratch register to be used by veneers and PLT entries186unsafe {187write_unaligned(188veneer.cast::<u32>(),1890x58000050, // ldr x16, 0x8190);191write_unaligned(192veneer.byte_add(4).cast::<u32>(),1930xd61f0200, // br x16194);195write_unaligned(veneer.byte_add(8).cast::<u64>(), what.addr() as u64);196};197198// Set the veneer as target of the call199let diff = ((veneer as isize) - (at as isize)) >> 2;200assert!((diff >> 25 == -1) || (diff >> 25 == 0));201let chop = 32 - 26;202let imm26 = (diff as u32) << chop >> chop;203unsafe { modify_inst32(iptr, |inst| inst | imm26) };204}205}206Reloc::Aarch64AdrGotPage21 => {207panic!("GOT relocation shouldn't be generated when !is_pic");208}209Reloc::Aarch64Ld64GotLo12Nc => {210panic!("GOT relocation shouldn't be generated when !is_pic");211}212Reloc::Aarch64AdrPrelPgHi21 => {213let base = get_address(name);214let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };215let get_page = |x| x & (!0xfff);216// NOTE: This should technically be i33 given that this relocation type allows217// a range from -4GB to +4GB, not -2GB to +2GB. But this doesn't really matter218// as the target is unlikely to be more than 2GB from the adrp instruction. We219// need to be careful to not cast to an unsigned int until after doing >> 12 to220// compute the upper 21bits of the pcrel address however as otherwise the top221// bit of the 33bit pcrel address would be forced 0 through zero extension222// instead of being sign extended as it should be.223let pcrel =224i32::try_from(get_page(what as isize) - get_page(at as isize)).unwrap();225let iptr = at as *mut u32;226let hi21 = (pcrel >> 12).cast_unsigned();227let lo = (hi21 & 0x3) << 29;228let hi = (hi21 & 0x1ffffc) << 3;229unsafe { modify_inst32(iptr, |inst| inst | lo | hi) };230}231Reloc::Aarch64AddAbsLo12Nc => {232let base = get_address(name);233let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };234let iptr = at as *mut u32;235let imm12 = (what.addr() as u32 & 0xfff) << 10;236unsafe { modify_inst32(iptr, |inst| inst | imm12) };237}238Reloc::RiscvCallPlt => {239// A R_RISCV_CALL_PLT relocation expects auipc+jalr instruction pair.240// It is the equivalent of two relocations:241// 1. R_RISCV_PCREL_HI20 on the `auipc`242// 2. R_RISCV_PCREL_LO12_I on the `jalr`243244let base = get_address(name);245let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };246let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap() as u32;247248// See https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses249// for a better explanation of the following code.250//251// Unlike the regular symbol relocations, here both "sub-relocations" point to the same address.252//253// `pcrel` is a signed value (+/- 2GiB range), when splitting it into two parts, we need to254// ensure that `hi20` is close enough to `pcrel` to be able to add `lo12` to it and still255// get a valid address.256//257// `lo12` is also a signed offset (+/- 2KiB range) relative to the `hi20` value.258//259// `hi20` should also be shifted right to be the "true" value. But we also need it260// left shifted for the `lo12` calculation and it also matches the instruction encoding.261let hi20 = pcrel.wrapping_add(0x800) & 0xFFFFF000;262let lo12 = pcrel.wrapping_sub(hi20) & 0xFFF;263264unsafe {265// Do a R_RISCV_PCREL_HI20 on the `auipc`266let auipc_addr = at as *mut u32;267modify_inst32(auipc_addr, |auipc| (auipc & 0xFFF) | hi20);268269// Do a R_RISCV_PCREL_LO12_I on the `jalr`270let jalr_addr = at.offset(4) as *mut u32;271modify_inst32(jalr_addr, |jalr| (jalr & 0xFFFFF) | (lo12 << 20));272}273}274Reloc::PulleyPcRel => {275let base = get_address(name);276let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };277let pcrel = i32::try_from((what as isize) - (at as isize)).unwrap();278let at = at as *mut i32;279unsafe {280at.write_unaligned(at.read_unaligned().wrapping_add(pcrel));281}282}283284// See <https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc#pc-relative-symbol-addresses>285// for why `0x800` is added here.286Reloc::RiscvPCRelHi20 => {287let base = get_address(name);288let what = unsafe { base.offset(isize::try_from(addend).unwrap()) };289let pcrel = i32::try_from((what as isize) - (at as isize) + 0x800)290.unwrap()291.cast_unsigned();292let at = at as *mut u32;293unsafe {294modify_inst32(at, |i| i | (pcrel & 0xfffff000));295}296}297298// The target of this relocation is the `auipc` preceding this299// instruction which should be `RiscvPCRelHi20`, and the actual300// target that we're relocating against is the target of that301// relocation. Assume for now that the previous relocation is302// the target of this relocation, and then use that.303Reloc::RiscvPCRelLo12I => {304let prev_reloc = &self.relocs[i - 1];305assert_eq!(prev_reloc.kind, Reloc::RiscvPCRelHi20);306let lo_target = get_address(name);307let hi_address =308unsafe { self.ptr.offset(isize::try_from(prev_reloc.offset).unwrap()) };309assert_eq!(lo_target, hi_address);310let hi_target = get_address(&prev_reloc.name);311let pcrel = i32::try_from((hi_target as isize) - (hi_address as isize))312.unwrap()313.cast_unsigned();314let at = at as *mut u32;315unsafe {316modify_inst32(at, |i| i | ((pcrel & 0xfff) << 20));317}318}319320other => unimplemented!("unimplemented reloc {other:?}"),321}322}323}324}325326327