use rustix::fd::AsRawFd;
use rustix::mm::{mmap, mmap_anonymous, mprotect, MapFlags, MprotectFlags, ProtFlags};
use std::fs::File;
use std::io;
use std::sync::Arc;
pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
mprotect(ptr.cast(), len, MprotectFlags::READ | MprotectFlags::WRITE)?;
Ok(())
}
pub unsafe fn hide_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
mprotect(ptr.cast(), len, MprotectFlags::empty())?;
Ok(())
}
pub unsafe fn erase_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
let ret = mmap_anonymous(
ptr.cast(),
len,
ProtFlags::empty(),
MapFlags::PRIVATE | MapFlags::FIXED,
)?;
assert_eq!(ptr, ret.cast());
Ok(())
}
#[cfg(feature = "pooling-allocator")]
unsafe fn decommit(addr: *mut u8, len: usize) -> io::Result<()> {
if len == 0 {
return Ok(());
}
unsafe {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
use rustix::mm::{madvise, Advice};
madvise(addr as _, len, Advice::LinuxDontNeed)?;
} else {
mmap_anonymous(
addr as _,
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | MapFlags::FIXED,
)?;
}
}
}
Ok(())
}
#[cfg(feature = "pooling-allocator")]
pub unsafe fn commit_table_pages(_addr: *mut u8, _len: usize) -> io::Result<()> {
Ok(())
}
#[cfg(feature = "pooling-allocator")]
pub unsafe fn decommit_table_pages(addr: *mut u8, len: usize) -> io::Result<()> {
decommit(addr, len)
}
#[cfg(all(feature = "pooling-allocator", feature = "async"))]
pub unsafe fn commit_stack_pages(_addr: *mut u8, _len: usize) -> io::Result<()> {
Ok(())
}
#[cfg(all(feature = "pooling-allocator", feature = "async"))]
pub unsafe fn reset_stack_pages_to_zero(addr: *mut u8, len: usize) -> io::Result<()> {
decommit(addr, len)
}
pub fn get_page_size() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() }
}
pub fn supports_madvise_dontneed() -> bool {
cfg!(target_os = "linux")
}
pub unsafe fn madvise_dontneed(ptr: *mut u8, len: usize) -> io::Result<()> {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
rustix::mm::madvise(ptr.cast(), len, rustix::mm::Advice::LinuxDontNeed)?;
Ok(())
} else {
let _ = (ptr, len);
unreachable!();
}
}
}
#[derive(Debug)]
pub enum MemoryImageSource {
Mmap(Arc<File>),
#[cfg(target_os = "linux")]
Memfd(memfd::Memfd),
}
impl MemoryImageSource {
pub fn from_file(file: &Arc<File>) -> Option<MemoryImageSource> {
Some(MemoryImageSource::Mmap(file.clone()))
}
#[cfg(not(target_os = "linux"))]
pub fn from_data(_data: &[u8]) -> io::Result<Option<MemoryImageSource>> {
Ok(None)
}
#[cfg(target_os = "linux")]
pub fn from_data(data: &[u8]) -> anyhow::Result<Option<MemoryImageSource>> {
use std::io::{ErrorKind, Write};
let memfd = match memfd::MemfdOptions::new()
.allow_sealing(true)
.create("wasm-memory-image")
{
Ok(memfd) => memfd,
Err(memfd::Error::Create(err)) if err.kind() == ErrorKind::Unsupported => {
return Ok(None)
}
Err(e) => return Err(e.into()),
};
memfd.as_file().write_all(data)?;
memfd.add_seals(&[
memfd::FileSeal::SealGrow,
memfd::FileSeal::SealShrink,
memfd::FileSeal::SealWrite,
memfd::FileSeal::SealSeal,
])?;
Ok(Some(MemoryImageSource::Memfd(memfd)))
}
fn as_file(&self) -> &File {
match self {
MemoryImageSource::Mmap(file) => file,
#[cfg(target_os = "linux")]
MemoryImageSource::Memfd(memfd) => memfd.as_file(),
}
}
pub unsafe fn map_at(&self, base: *mut u8, len: usize, offset: u64) -> io::Result<()> {
let ptr = mmap(
base.cast(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | MapFlags::FIXED,
self.as_file(),
offset,
)?;
assert_eq!(base, ptr.cast());
Ok(())
}
pub unsafe fn remap_as_zeros_at(&self, base: *mut u8, len: usize) -> io::Result<()> {
let ptr = mmap_anonymous(
base.cast(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | MapFlags::FIXED,
)?;
assert_eq!(base, ptr.cast());
Ok(())
}
}
impl PartialEq for MemoryImageSource {
fn eq(&self, other: &MemoryImageSource) -> bool {
self.as_file().as_raw_fd() == other.as_file().as_raw_fd()
}
}