Resolve libcall relocations for older CPUs (#5567)
* Resolve libcall relocations for older CPUs Long ago Wasmtime used to have logic for resolving relocations post-compilation for libcalls which I ended up removing during refactorings last year. As #5563 points out, however, it's possible to get Wasmtime to panic by disabling SSE features which forces Cranelift to use libcalls for some floating-point operations instead. Note that this also requires disabling SIMD because SIMD support has a baseline of SSE 4.2. This commit pulls back the old implementations of various libcalls and reimplements logic necessary to have them work on CPUs without SSE 4.2 Closes #5563 * Fix log message in `wast` support * Fix offset listed in relocations Be sure to factor in the offset of the function itself * Review comments
This commit is contained in:
@@ -4,12 +4,14 @@ use crate::subslice_range;
|
||||
use crate::unwind::UnwindRegistration;
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use object::read::{File, Object, ObjectSection};
|
||||
use object::ObjectSymbol;
|
||||
use std::mem;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::ops::Range;
|
||||
use wasmtime_environ::obj;
|
||||
use wasmtime_environ::FunctionLoc;
|
||||
use wasmtime_jit_icache_coherence as icache_coherence;
|
||||
use wasmtime_runtime::libcalls;
|
||||
use wasmtime_runtime::{MmapVec, VMTrampoline};
|
||||
|
||||
/// Management of executable memory within a `MmapVec`
|
||||
@@ -24,6 +26,8 @@ pub struct CodeMemory {
|
||||
published: bool,
|
||||
enable_branch_protection: bool,
|
||||
|
||||
relocations: Vec<(usize, obj::LibCall)>,
|
||||
|
||||
// Ranges within `self.mmap` of where the particular sections lie.
|
||||
text: Range<usize>,
|
||||
unwind: Range<usize>,
|
||||
@@ -60,6 +64,7 @@ impl CodeMemory {
|
||||
let obj = File::parse(&mmap[..])
|
||||
.with_context(|| "failed to parse internal compilation artifact")?;
|
||||
|
||||
let mut relocations = Vec::new();
|
||||
let mut text = 0..0;
|
||||
let mut unwind = 0..0;
|
||||
let mut enable_branch_protection = None;
|
||||
@@ -93,11 +98,28 @@ impl CodeMemory {
|
||||
".text" => {
|
||||
text = range;
|
||||
|
||||
// Double-check there are no relocations in the text section. At
|
||||
// this time relocations are not expected at all from loaded code
|
||||
// since everything should be resolved at compile time. Handling
|
||||
// must be added here, though, if relocations pop up.
|
||||
assert!(section.relocations().count() == 0);
|
||||
// The text section might have relocations for things like
|
||||
// libcalls which need to be applied, so handle those here.
|
||||
//
|
||||
// Note that only a small subset of possible relocations are
|
||||
// handled. Only those required by the compiler side of
|
||||
// things are processed.
|
||||
for (offset, reloc) in section.relocations() {
|
||||
assert_eq!(reloc.kind(), object::RelocationKind::Absolute);
|
||||
assert_eq!(reloc.encoding(), object::RelocationEncoding::Generic);
|
||||
assert_eq!(usize::from(reloc.size()), std::mem::size_of::<usize>());
|
||||
assert_eq!(reloc.addend(), 0);
|
||||
let sym = match reloc.target() {
|
||||
object::RelocationTarget::Symbol(id) => id,
|
||||
other => panic!("unknown relocation target {other:?}"),
|
||||
};
|
||||
let sym = obj.symbol_by_index(sym).unwrap().name().unwrap();
|
||||
let libcall = obj::LibCall::from_str(sym)
|
||||
.unwrap_or_else(|| panic!("unknown symbol relocation: {sym}"));
|
||||
|
||||
let offset = usize::try_from(offset).unwrap();
|
||||
relocations.push((offset, libcall));
|
||||
}
|
||||
}
|
||||
UnwindRegistration::SECTION_NAME => unwind = range,
|
||||
obj::ELF_WASM_DATA => wasm_data = range,
|
||||
@@ -124,6 +146,7 @@ impl CodeMemory {
|
||||
dwarf,
|
||||
info_data,
|
||||
wasm_data,
|
||||
relocations,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -214,6 +237,8 @@ impl CodeMemory {
|
||||
// both the actual unwinding tables as well as the validity of the
|
||||
// pointers we pass in itself.
|
||||
unsafe {
|
||||
self.apply_relocations()?;
|
||||
|
||||
let text = self.text();
|
||||
|
||||
// Clear the newly allocated code from cache if the processor requires it
|
||||
@@ -243,6 +268,35 @@ impl CodeMemory {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn apply_relocations(&mut self) -> Result<()> {
|
||||
if self.relocations.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Mmaps currently all start as readonly so before updating relocations
|
||||
// the mapping needs to be made writable first. Note that this isn't
|
||||
// reset back to readonly since the `make_executable` call, which
|
||||
// happens after this, will implicitly remove the writable bit and leave
|
||||
// it as just read/execute.
|
||||
self.mmap.make_writable(self.text.clone())?;
|
||||
|
||||
for (offset, libcall) in self.relocations.iter() {
|
||||
let offset = self.text.start + offset;
|
||||
let libcall = match libcall {
|
||||
obj::LibCall::FloorF32 => libcalls::relocs::floorf32 as usize,
|
||||
obj::LibCall::FloorF64 => libcalls::relocs::floorf64 as usize,
|
||||
obj::LibCall::NearestF32 => libcalls::relocs::nearestf32 as usize,
|
||||
obj::LibCall::NearestF64 => libcalls::relocs::nearestf64 as usize,
|
||||
obj::LibCall::CeilF32 => libcalls::relocs::ceilf32 as usize,
|
||||
obj::LibCall::CeilF64 => libcalls::relocs::ceilf64 as usize,
|
||||
obj::LibCall::TruncF32 => libcalls::relocs::truncf32 as usize,
|
||||
obj::LibCall::TruncF64 => libcalls::relocs::truncf64 as usize,
|
||||
};
|
||||
*self.mmap.as_mut_ptr().add(offset).cast::<usize>() = libcall;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn register_unwind_info(&mut self) -> Result<()> {
|
||||
if self.unwind.len() == 0 {
|
||||
return Ok(());
|
||||
|
||||
Reference in New Issue
Block a user