1 use std::ffi::c_void;
2 
3 #[cfg(any(target_os = "linux", target_os = "android"))]
4 pub use std::io::Result;
5 
6 #[cfg(not(any(target_os = "linux", target_os = "android")))]
7 pub use wasmtime_core::error::Result;
8 
9 #[cfg(all(
10     target_arch = "aarch64",
11     any(target_os = "linux", target_os = "android")
12 ))]
13 mod details {
14 
15     use super::*;
16     use libc::{EINVAL, EPERM, syscall};
17     use std::io::Error;
18 
19     const MEMBARRIER_CMD_GLOBAL: libc::c_int = 1;
20     const MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 32;
21     const MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE: libc::c_int = 64;
22 
23     /// See docs on [crate::pipeline_flush_mt] for a description of what this function is trying to do.
24     #[inline]
pipeline_flush_mt() -> Result<()>25     pub(crate) fn pipeline_flush_mt() -> Result<()> {
26         // Ensure that no processor has fetched a stale instruction stream.
27         //
28         // On AArch64 we try to do this by executing a "broadcast" `ISB` which is not something
29         // that the architecture provides us but we can emulate it using the membarrier kernel
30         // interface.
31         //
32         // This behaviour was documented in a patch, however it seems that it hasn't been
33         // upstreamed yet Nevertheless it clearly explains the guarantees that the Linux kernel
34         // provides us regarding the membarrier interface, and how to use it for JIT contexts.
35         // https://lkml.kernel.org/lkml/07a8b963002cb955b7516e61bad19514a3acaa82.1623813516.git.luto@kernel.org/
36         //
37         // I couldn't find the follow up for that patch but there doesn't seem to be disagreement
38         // about that specific part in the replies.
39         // TODO: Check if the kernel has updated the membarrier documentation
40         //
41         // See the following issues for more info:
42         //  * https://github.com/bytecodealliance/wasmtime/pull/3426
43         //  * https://github.com/bytecodealliance/wasmtime/pull/4997
44         //
45         // TODO: x86 and s390x have coherent caches so they don't need this, but RISCV does not
46         // guarantee that, so we may need to do something similar for it. However as noted in the
47         // above kernel patch the SYNC_CORE membarrier has different guarantees on each
48         // architecture so we need follow up and check what it provides us.
49         // See: https://github.com/bytecodealliance/wasmtime/issues/5033
50         match membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE) {
51             Ok(_) => {}
52 
53             // EPERM happens if the calling process hasn't yet called the register membarrier.
54             // We can call the register membarrier now, and then retry the actual membarrier,
55             //
56             // This does have some overhead since on the first time we call this function we
57             // actually execute three membarriers, but this only happens once per process and only
58             // one slow membarrier is actually executed (The last one, which actually generates an
59             // IPI).
60             Err(e) if e.raw_os_error().unwrap() == EPERM => {
61                 membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED_SYNC_CORE)?;
62                 membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED_SYNC_CORE)?;
63             }
64 
65             // On kernels older than 4.16 the above syscall does not exist, so we can
66             // fallback to MEMBARRIER_CMD_GLOBAL which is an alias for MEMBARRIER_CMD_SHARED
67             // that has existed since 4.3. GLOBAL is a lot slower, but allows us to have
68             // compatibility with older kernels.
69             Err(e) if e.raw_os_error().unwrap() == EINVAL => {
70                 membarrier(MEMBARRIER_CMD_GLOBAL)?;
71             }
72 
73             // In any other case we got an actual error, so lets propagate that up
74             e => e?,
75         }
76 
77         Ok(())
78     }
79 
membarrier(barrier: libc::c_int) -> Result<()>80     fn membarrier(barrier: libc::c_int) -> Result<()> {
81         let flags: libc::c_int = 0;
82         let res = unsafe { syscall(libc::SYS_membarrier, barrier, flags) };
83         if res == 0 {
84             Ok(())
85         } else {
86             Err(Error::last_os_error())
87         }
88     }
89 }
90 
91 #[cfg(not(all(
92     target_arch = "aarch64",
93     any(target_os = "linux", target_os = "android")
94 )))]
95 mod details {
96     // NB: this uses `wasmtime_environ::error::Result` instead of `std::io::Result` to compile on
97     // `no_std`.
pipeline_flush_mt() -> super::Result<()>98     pub(crate) fn pipeline_flush_mt() -> super::Result<()> {
99         Ok(())
100     }
101 }
102 
103 #[cfg(all(target_arch = "riscv64", target_os = "linux"))]
riscv_flush_icache(start: u64, end: u64) -> Result<()>104 fn riscv_flush_icache(start: u64, end: u64) -> Result<()> {
105     cfg_if::cfg_if! {
106         if #[cfg(feature = "one-core")] {
107             use std::arch::asm;
108             let _ = (start, end);
109             unsafe {
110                 asm!("fence.i");
111             };
112             Ok(())
113         } else {
114             #[expect(non_upper_case_globals, reason = "matching C style")]
115             match unsafe {
116                 libc::syscall(
117                     {
118                         // The syscall isn't defined in `libc`, so we define the syscall number here.
119                         // https://github.com/torvalds/linux/search?q=__NR_arch_specific_syscall
120                         const  __NR_arch_specific_syscall :i64 = 244;
121                         // https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/tools/arch/riscv/include/uapi/asm/unistd.h#L40
122                         const sys_riscv_flush_icache :i64 =  __NR_arch_specific_syscall + 15;
123                         sys_riscv_flush_icache
124                     },
125                     // Currently these parameters are not used, but they are still defined.
126                     start, // start
127                     end, // end
128                     {
129                         const SYS_RISCV_FLUSH_ICACHE_LOCAL :i64 = 1;
130                         const SYS_RISCV_FLUSH_ICACHE_ALL :i64 = SYS_RISCV_FLUSH_ICACHE_LOCAL;
131                         SYS_RISCV_FLUSH_ICACHE_ALL
132                     }, // flags
133                 )
134             } {
135                 0 => { Ok(()) }
136                 _ => Err(std::io::Error::last_os_error()),
137             }
138         }
139     }
140 }
141 
142 #[cfg(target_arch = "aarch64")]
aarch64_flush_icache(start: u64, end: u64)143 fn aarch64_flush_icache(start: u64, end: u64) {
144     use core::arch::asm;
145 
146     // See `sys_icache_invalidate` implementation in Darwin at
147     // https://github.com/apple/darwin-libplatform/blob/main/src/cachecontrol/arm64/cache.s. It
148     // turns out that all of these instructions work in userspace, so
149     // we can do this portably without having to rely on OS-specific
150     // syscalls like `sys_cache_invalidate()` on macOS or something
151     // equivalent on Linux.
152     const CACHE_LINE_SIZE: u64 = 64;
153     // For each cache line, flush the icache.
154     // Round down the start and round up the end.
155     let mut start = (start - CACHE_LINE_SIZE + 1).next_multiple_of(CACHE_LINE_SIZE);
156     let end = end.next_multiple_of(CACHE_LINE_SIZE);
157     while start < end {
158         unsafe {
159             asm!("ic ivau, {}", in(reg) start);
160         }
161         start += CACHE_LINE_SIZE;
162     }
163 
164     // Flush the dcache, and then issue an instruction barrier so
165     // fetch can't restart until that's done. All cache lines we are
166     // about to execute (in the flushed range) are now guaranteed to
167     // see the new data.
168     unsafe {
169         asm!("dsb ish"); // Flush dcache.
170         asm!("isb"); // Instruction fetch barrier.
171     }
172 }
173 
174 pub(crate) use details::*;
175 
176 /// See docs on [crate::clear_cache] for a description of what this function is trying to do.
177 #[inline]
clear_cache(_ptr: *const c_void, _len: usize) -> Result<()>178 pub(crate) fn clear_cache(_ptr: *const c_void, _len: usize) -> Result<()> {
179     #[cfg(target_arch = "aarch64")]
180     aarch64_flush_icache(_ptr as u64, (_ptr as u64) + (_len as u64));
181     #[cfg(all(target_arch = "riscv64", target_os = "linux"))]
182     riscv_flush_icache(_ptr as u64, (_ptr as u64) + (_len as u64))?;
183     Ok(())
184 }
185