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:
Alex Crichton
2023-01-18 09:04:10 -06:00
committed by GitHub
parent 94b51cdb17
commit 9b896d2a70
7 changed files with 298 additions and 14 deletions

View File

@@ -492,3 +492,85 @@ unsafe fn out_of_gas(vmctx: *mut VMContext) -> Result<()> {
unsafe fn new_epoch(vmctx: *mut VMContext) -> Result<u64> {
(*(*vmctx).instance().store()).new_epoch()
}
/// This module contains functions which are used for resolving relocations at
/// runtime if necessary.
///
/// These functions are not used by default and currently the only platform
/// they're used for is on x86_64 when SIMD is disabled and then SSE features
/// are further disabled. In these configurations Cranelift isn't allowed to use
/// native CPU instructions so it falls back to libcalls and we rely on the Rust
/// standard library generally for implementing these.
#[allow(missing_docs)]
pub mod relocs {
pub extern "C" fn floorf32(f: f32) -> f32 {
f.floor()
}
pub extern "C" fn floorf64(f: f64) -> f64 {
f.floor()
}
pub extern "C" fn ceilf32(f: f32) -> f32 {
f.ceil()
}
pub extern "C" fn ceilf64(f: f64) -> f64 {
f.ceil()
}
pub extern "C" fn truncf32(f: f32) -> f32 {
f.trunc()
}
pub extern "C" fn truncf64(f: f64) -> f64 {
f.trunc()
}
const TOINT_32: f32 = 1.0 / f32::EPSILON;
const TOINT_64: f64 = 1.0 / f64::EPSILON;
// NB: replace with `round_ties_even` from libstd when it's stable as
// tracked by rust-lang/rust#96710
pub extern "C" fn nearestf32(x: f32) -> f32 {
// Rust doesn't have a nearest function; there's nearbyint, but it's not
// stabilized, so do it manually.
// Nearest is either ceil or floor depending on which is nearest or even.
// This approach exploited round half to even default mode.
let i = x.to_bits();
let e = i >> 23 & 0xff;
if e >= 0x7f_u32 + 23 {
// Check for NaNs.
if e == 0xff {
// Read the 23-bits significand.
if i & 0x7fffff != 0 {
// Ensure it's arithmetic by setting the significand's most
// significant bit to 1; it also works for canonical NaNs.
return f32::from_bits(i | (1 << 22));
}
}
x
} else {
(x.abs() + TOINT_32 - TOINT_32).copysign(x)
}
}
pub extern "C" fn nearestf64(x: f64) -> f64 {
let i = x.to_bits();
let e = i >> 52 & 0x7ff;
if e >= 0x3ff_u64 + 52 {
// Check for NaNs.
if e == 0x7ff {
// Read the 52-bits significand.
if i & 0xfffffffffffff != 0 {
// Ensure it's arithmetic by setting the significand's most
// significant bit to 1; it also works for canonical NaNs.
return f64::from_bits(i | (1 << 51));
}
}
x
} else {
(x.abs() + TOINT_64 - TOINT_64).copysign(x)
}
}
}