wasmtime_jit_icache_coherence/
libc.rs

1use std::ffi::c_void;
2use std::io::Result;
3
4#[cfg(all(
5    target_arch = "aarch64",
6    any(target_os = "linux", target_os = "android")
7))]
8mod details {
9    use super::*;
10    use libc::{syscall, EINVAL, EPERM};
11    use std::io::Error;
12
13    const MEMBARRIER_CMD_GLOBAL: libc::c_int = 1;
14    const MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 32;
15    const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 64;
16
17    /// See docs on [crate::pipeline_flush_mt] for a description of what this function is trying to do.
18    #[inline]
19    pub(crate) fn pipeline_flush_mt() -> Result<()> {
20        // Ensure that no processor has fetched a stale instruction stream.
21        //
22        // On AArch64 we try to do this by executing a "broadcast" `ISB` which is not something
23        // that the architecture provides us but we can emulate it using the membarrier kernel
24        // interface.
25        //
26        // This behaviour was documented in a patch, however it seems that it hasn't been
27        // upstreamed yet Nevertheless it clearly explains the guarantees that the Linux kernel
28        // provides us regarding the membarrier interface, and how to use it for JIT contexts.
29        // https://lkml.kernel.org/lkml/07a8b963002cb955b7516e61bad19514a3acaa82.1623813516.git.luto@kernel.org/
30        //
31        // I couldn't find the follow up for that patch but there doesn't seem to be disagreement
32        // about that specific part in the replies.
33        // TODO: Check if the kernel has updated the membarrier documentation
34        //
35        // See the following issues for more info:
36        //  * https://github.com/bytecodealliance/wasmtime/pull/3426
37        //  * https://github.com/bytecodealliance/wasmtime/pull/4997
38        //
39        // TODO: x86 and s390x have coherent caches so they don't need this, but RISCV does not
40        // guarantee that, so we may need to do something similar for it. However as noted in the
41        // above kernel patch the SYNC_CORE membarrier has different guarantees on each
42        // architecture so we need follow up and check what it provides us.
43        // See: https://github.com/bytecodealliance/wasmtime/issues/5033
44        match membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE) {
45            Ok(_) => {}
46
47            // EPERM happens if the calling process hasn't yet called the register membarrier.
48            // We can call the register membarrier now, and then retry the actual membarrier,
49            //
50            // This does have some overhead since on the first time we call this function we
51            // actually execute three membarriers, but this only happens once per process and only
52            // one slow membarrier is actually executed (The last one, which actually generates an
53            // IPI).
54            Err(e) if e.raw_os_error().unwrap() == EPERM => {
55                membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE)?;
56                membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE)?;
57            }
58
59            // On kernels older than 4.16 the above syscall does not exist, so we can
60            // fallback to MEMBARRIER_CMD_GLOBAL which is an alias for MEMBARRIER_CMD_SHARED
61            // that has existed since 4.3. GLOBAL is a lot slower, but allows us to have
62            // compatibility with older kernels.
63            Err(e) if e.raw_os_error().unwrap() == EINVAL => {
64                membarrier(MEMBARRIER_CMD_GLOBAL)?;
65            }
66
67            // In any other case we got an actual error, so lets propagate that up
68            e => e?,
69        }
70
71        Ok(())
72    }
73
74    fn membarrier(barrier: libc::c_int) -> Result<()> {
75        let flags: libc::c_int = 0;
76        let res = unsafe { syscall(libc::SYS_membarrier, barrier, flags) };
77        if res == 0 {
78            Ok(())
79        } else {
80            Err(Error::last_os_error())
81        }
82    }
83}
84
85#[cfg(not(all(
86    target_arch = "aarch64",
87    any(target_os = "linux", target_os = "android")
88)))]
89mod details {
90    pub(crate) fn pipeline_flush_mt() -> std::io::Result<()> {
91        Ok(())
92    }
93}
94#[cfg(all(target_arch = "riscv64", target_os = "linux"))]
95fn riscv_flush_icache(start: u64, end: u64) -> Result<()> {
96    cfg_if::cfg_if! {
97        if #[cfg(feature = "one-core")] {
98            use std::arch::asm;
99            unsafe {
100                asm!("fence.i");
101            };
102            Ok(())
103        } else {
104            match unsafe {
105                libc::syscall(
106                    {
107                        // The syscall isn't defined in `libc`, so we definfe the syscall number here.
108                        // https://github.com/torvalds/linux/search?q=__NR_arch_specific_syscall
109                        #[allow(non_upper_case_globals)]
110                        const  __NR_arch_specific_syscall :i64 = 244;
111                        // https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/tools/arch/riscv/include/uapi/asm/unistd.h#L40
112                        #[allow(non_upper_case_globals)]
113                        const sys_riscv_flush_icache :i64 =  __NR_arch_specific_syscall + 15;
114                        sys_riscv_flush_icache
115                    },
116                    // Currently these parameters are not used, but they are still defined.
117                    start, // start
118                    end, // end
119                    {
120                        #[allow(non_snake_case)]
121                        const SYS_RISCV_FLUSH_ICACHE_LOCAL :i64 = 1;
122                        #[allow(non_snake_case)]
123                        const SYS_RISCV_FLUSH_ICACHE_ALL :i64 = SYS_RISCV_FLUSH_ICACHE_LOCAL;
124                        SYS_RISCV_FLUSH_ICACHE_ALL
125                    }, // flags
126                )
127            } {
128                0 => { Ok(()) }
129                _ => Err(std::io::Error::last_os_error()),
130            }
131        }
132    }
133}
134
135pub(crate) use details::*;
136
137/// See docs on [crate::clear_cache] for a description of what this function is trying to do.
138#[inline]
139pub(crate) fn clear_cache(_ptr: *const c_void, _len: usize) -> Result<()> {
140    // TODO: On AArch64 we currently rely on the `mprotect` call that switches the memory from W+R
141    // to R+X to do this for us, however that is an implementation detail and should not be relied
142    // upon.
143    // We should call some implementation of `clear_cache` here.
144    //
145    // See: https://github.com/bytecodealliance/wasmtime/issues/3310
146    #[cfg(all(target_arch = "riscv64", target_os = "linux"))]
147    riscv_flush_icache(_ptr as u64, (_ptr as u64) + (_len as u64))?;
148    Ok(())
149}