Path: blob/main/crates/jit-icache-coherence/src/libc.rs
1691 views
use std::ffi::c_void;12#[cfg(any(target_os = "linux", target_os = "android"))]3pub use std::io::Result;45#[cfg(not(any(target_os = "linux", target_os = "android")))]6pub use anyhow::Result;78#[cfg(all(9target_arch = "aarch64",10any(target_os = "linux", target_os = "android")11))]12mod details {1314use super::*;15use libc::{EINVAL, EPERM, syscall};16use std::io::Error;1718const MEMBARRIER_CMD_GLOBAL: libc::c_int = 1;19const MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 32;20const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 64;2122/// See docs on [crate::pipeline_flush_mt] for a description of what this function is trying to do.23#[inline]24pub(crate) fn pipeline_flush_mt() -> Result<()> {25// Ensure that no processor has fetched a stale instruction stream.26//27// On AArch64 we try to do this by executing a "broadcast" `ISB` which is not something28// that the architecture provides us but we can emulate it using the membarrier kernel29// interface.30//31// This behaviour was documented in a patch, however it seems that it hasn't been32// upstreamed yet Nevertheless it clearly explains the guarantees that the Linux kernel33// provides us regarding the membarrier interface, and how to use it for JIT contexts.34// https://lkml.kernel.org/lkml/07a8b963002cb955b7516e61bad19514a3acaa82.1623813516.git.luto@kernel.org/35//36// I couldn't find the follow up for that patch but there doesn't seem to be disagreement37// about that specific part in the replies.38// TODO: Check if the kernel has updated the membarrier documentation39//40// See the following issues for more info:41// * https://github.com/bytecodealliance/wasmtime/pull/342642// * https://github.com/bytecodealliance/wasmtime/pull/499743//44// TODO: x86 and s390x have coherent caches so they don't need this, but RISCV does not45// guarantee that, so we may need to do something similar for it. However as noted in the46// above kernel patch the SYNC_CORE membarrier has different guarantees on each47// architecture so we need follow up and check what it provides us.48// See: https://github.com/bytecodealliance/wasmtime/issues/503349match membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE) {50Ok(_) => {}5152// EPERM happens if the calling process hasn't yet called the register membarrier.53// We can call the register membarrier now, and then retry the actual membarrier,54//55// This does have some overhead since on the first time we call this function we56// actually execute three membarriers, but this only happens once per process and only57// one slow membarrier is actually executed (The last one, which actually generates an58// IPI).59Err(e) if e.raw_os_error().unwrap() == EPERM => {60membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE)?;61membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE)?;62}6364// On kernels older than 4.16 the above syscall does not exist, so we can65// fallback to MEMBARRIER_CMD_GLOBAL which is an alias for MEMBARRIER_CMD_SHARED66// that has existed since 4.3. GLOBAL is a lot slower, but allows us to have67// compatibility with older kernels.68Err(e) if e.raw_os_error().unwrap() == EINVAL => {69membarrier(MEMBARRIER_CMD_GLOBAL)?;70}7172// In any other case we got an actual error, so lets propagate that up73e => e?,74}7576Ok(())77}7879fn membarrier(barrier: libc::c_int) -> Result<()> {80let flags: libc::c_int = 0;81let res = unsafe { syscall(libc::SYS_membarrier, barrier, flags) };82if res == 0 {83Ok(())84} else {85Err(Error::last_os_error())86}87}88}8990#[cfg(not(all(91target_arch = "aarch64",92any(target_os = "linux", target_os = "android")93)))]94mod details {95// NB: this uses `anyhow::Result` instead of `std::io::Result` to compile on96// `no_std`.97pub(crate) fn pipeline_flush_mt() -> super::Result<()> {98Ok(())99}100}101102#[cfg(all(target_arch = "riscv64", target_os = "linux"))]103fn riscv_flush_icache(start: u64, end: u64) -> Result<()> {104cfg_if::cfg_if! {105if #[cfg(feature = "one-core")] {106use std::arch::asm;107let _ = (start, end);108unsafe {109asm!("fence.i");110};111Ok(())112} else {113#[expect(non_upper_case_globals, reason = "matching C style")]114match unsafe {115libc::syscall(116{117// The syscall isn't defined in `libc`, so we define the syscall number here.118// https://github.com/torvalds/linux/search?q=__NR_arch_specific_syscall119const __NR_arch_specific_syscall :i64 = 244;120// https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/tools/arch/riscv/include/uapi/asm/unistd.h#L40121const sys_riscv_flush_icache :i64 = __NR_arch_specific_syscall + 15;122sys_riscv_flush_icache123},124// Currently these parameters are not used, but they are still defined.125start, // start126end, // end127{128const SYS_RISCV_FLUSH_ICACHE_LOCAL :i64 = 1;129const SYS_RISCV_FLUSH_ICACHE_ALL :i64 = SYS_RISCV_FLUSH_ICACHE_LOCAL;130SYS_RISCV_FLUSH_ICACHE_ALL131}, // flags132)133} {1340 => { Ok(()) }135_ => Err(std::io::Error::last_os_error()),136}137}138}139}140141pub(crate) use details::*;142143/// See docs on [crate::clear_cache] for a description of what this function is trying to do.144#[inline]145pub(crate) fn clear_cache(_ptr: *const c_void, _len: usize) -> Result<()> {146// TODO: On AArch64 we currently rely on the `mprotect` call that switches the memory from W+R147// to R+X to do this for us, however that is an implementation detail and should not be relied148// upon.149// We should call some implementation of `clear_cache` here.150//151// See: https://github.com/bytecodealliance/wasmtime/issues/3310152#[cfg(all(target_arch = "riscv64", target_os = "linux"))]153riscv_flush_icache(_ptr as u64, (_ptr as u64) + (_len as u64))?;154Ok(())155}156157158