Refactored memory usage to use impl-abstract mmap

This commit is contained in:
Syrus
2018-12-14 17:32:35 -08:00
parent a2bcdb658f
commit fd5554c3bd
9 changed files with 285 additions and 66 deletions

13
Cargo.lock generated
View File

@ -373,15 +373,6 @@ dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "memmap"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.2.1" version = "0.2.1"
@ -876,11 +867,11 @@ dependencies = [
"cranelift-native 0.23.0", "cranelift-native 0.23.0",
"cranelift-wasm 0.23.0", "cranelift-wasm 0.23.0",
"docopt 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
"error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"indicatif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", "indicatif 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.44 (git+https://github.com/rust-lang/libc)", "libc 0.2.44 (git+https://github.com/rust-lang/libc)",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"region 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "region 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -891,6 +882,7 @@ dependencies = [
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"wabt 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"wasmparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -960,7 +952,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f" "checksum log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
"checksum mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9" "checksum mach 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2fd13ee2dd61cc82833ba05ade5a30bb3d63f7ced605ef827063c63078302de9"
"checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b" "checksum memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4b3629fe9fdbff6daa6c33b90f7c08355c1aca05a3d01fa8063b822fcf185f3b"
"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
"checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f" "checksum nix 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "921f61dc817b379d0834e45d5ec45beaacfae97082090a49c2cf30dcbc30206f"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"

View File

@ -33,11 +33,12 @@ serde = "1.0.55"
serde_derive = "1.0.55" serde_derive = "1.0.55"
tempdir = "0.3.7" tempdir = "0.3.7"
error-chain = "0.12.0" error-chain = "0.12.0"
errno = "0.2.4"
structopt = "0.2.11" structopt = "0.2.11"
wabt = "0.7.1" wabt = "0.7.1"
wasmparser = "0.20.0" wasmparser = "0.20.0"
winapi = "0.3.6"
region = "0.3.0" region = "0.3.0"
memmap = "0.6.2"
# spin = "0.4.10" # spin = "0.4.10"
log = "0.4.5" log = "0.4.5"
target-lexicon = "0.2.0" target-lexicon = "0.2.0"

18
src/common/mmap/common.rs Normal file
View File

@ -0,0 +1,18 @@
/// Round `size` up to the nearest multiple of `page_size`.
pub fn round_up_to_page_size(size: usize, page_size: usize) -> usize {
(size + (page_size - 1)) & !(page_size - 1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_round_up_to_page_size() {
assert_eq!(round_up_to_page_size(0, 4096), 0);
assert_eq!(round_up_to_page_size(1, 4096), 4096);
assert_eq!(round_up_to_page_size(4096, 4096), 4096);
assert_eq!(round_up_to_page_size(4097, 4096), 8192);
}
}

13
src/common/mmap/mod.rs Normal file
View File

@ -0,0 +1,13 @@
//! A cross-platform Rust API for memory mapped buffers.
// TODO: Refactor this into it's own lib
mod common;
#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub use self::windows::Mmap;
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::Mmap;

96
src/common/mmap/unix.rs Normal file
View File

@ -0,0 +1,96 @@
//! Low-level abstraction for allocating and managing zero-filled pages
//! of memory.
use errno;
use libc;
use region;
use std::ptr;
use std::slice;
use std::string::String;
use super::common::round_up_to_page_size;
/// A simple struct consisting of a page-aligned pointer to page-aligned
/// and initially-zeroed memory and a length.
#[derive(Debug)]
pub struct Mmap {
ptr: *mut u8,
len: usize,
}
impl Mmap {
/// Construct a new empty instance of `Mmap`.
pub fn new() -> Self {
Self {
ptr: ptr::null_mut(),
len: 0,
}
}
/// Create a new `Mmap` pointing to at least `size` bytes of memory,
/// suitably sized and aligned for memory protection.
#[cfg(not(target_os = "windows"))]
pub fn with_size(size: usize) -> Result<Self, String> {
// Mmap may return EINVAL if the size is zero, so just
// special-case that.
if size == 0 {
return Ok(Self::new());
}
let page_size = region::page::size();
let alloc_size = round_up_to_page_size(size, page_size);
let ptr = unsafe {
libc::mmap(
ptr::null_mut(),
alloc_size,
// libc::PROT_READ | libc::PROT_WRITE,
libc::PROT_NONE,
libc::MAP_PRIVATE | libc::MAP_ANON,
-1,
0,
)
};
if ptr as isize == -1isize {
Err(errno::errno().to_string())
} else {
Ok(Self {
ptr: ptr as *mut u8,
len: alloc_size,
})
}
}
/// Return the allocated memory as a slice of u8.
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
/// Return the allocated memory as a mutable slice of u8.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
}
/// Return the allocated memory as a pointer to u8.
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
/// Return the allocated memory as a mutable pointer to u8.
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
/// Return the lengthof the allocated memory.
pub fn len(&self) -> usize {
self.len
}
}
impl Drop for Mmap {
fn drop(&mut self) {
if !self.ptr.is_null() {
let r = unsafe { libc::munmap(self.ptr as *mut libc::c_void, self.len) };
assert_eq!(r, 0, "munmap failed: {}", errno::errno());
}
}
}

View File

@ -0,0 +1,89 @@
//! Low-level abstraction for allocating and managing zero-filled pages
//! of memory.
use errno;
use region;
use std::ptr;
use std::slice;
use std::string::String;
use winapi::um::memoryapi::{VirtualAlloc, VirtualFree};
use winapi::um::winnt::{MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE, MEM_RELEASE};
use super::common::round_up_to_page_size;
/// A simple struct consisting of a page-aligned pointer to page-aligned
/// and initially-zeroed memory and a length.
#[derive(Debug)]
pub struct Mmap {
ptr: *mut u8,
len: usize,
}
impl Mmap {
/// Construct a new empty instance of `Mmap`.
pub fn new() -> Self {
Self {
ptr: ptr::null_mut(),
len: 0,
}
}
/// Create a new `Mmap` pointing to at least `size` bytes of memory,
/// suitably sized and aligned for memory protection.
pub fn with_size(size: usize) -> Result<Self, String> {
let page_size = region::page::size();
// VirtualAlloc always rounds up to the next multiple of the page size
let ptr = unsafe {
VirtualAlloc(
ptr::null_mut(),
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
)
};
if !ptr.is_null() {
Ok(Self {
ptr: ptr as *mut u8,
len: round_up_to_page_size(size, page_size),
})
} else {
Err(errno::errno().to_string())
}
}
/// Return the allocated memory as a slice of u8.
pub fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
/// Return the allocated memory as a mutable slice of u8.
pub fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
}
/// Return the allocated memory as a pointer to u8.
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
/// Return the allocated memory as a mutable pointer to u8.
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.ptr
}
/// Return the lengthof the allocated memory.
pub fn len(&self) -> usize {
self.len
}
}
impl Drop for Mmap {
fn drop(&mut self) {
if !self.ptr.is_null() {
let r = unsafe { VirtualFree(self.ptr, self.len, MEM_RELEASE) };
assert_eq!(r, 0);
}
}
}

View File

@ -1,2 +1,3 @@
pub mod slice; pub mod slice;
pub mod stdio; pub mod stdio;
pub mod mmap;

View File

@ -5,7 +5,6 @@ extern crate cranelift_entity;
extern crate cranelift_native; extern crate cranelift_native;
extern crate cranelift_wasm; extern crate cranelift_wasm;
extern crate libc; extern crate libc;
extern crate memmap;
extern crate region; extern crate region;
extern crate structopt; extern crate structopt;
extern crate wabt; extern crate wabt;
@ -17,6 +16,8 @@ pub extern crate nix; // re-exported for usage in macros
extern crate rayon; extern crate rayon;
extern crate indicatif; extern crate indicatif;
extern crate console; extern crate console;
#[cfg(windows)]
extern crate winapi;
#[macro_use] #[macro_use]
mod macros; mod macros;

View File

@ -3,17 +3,21 @@
//! webassembly::Instance. //! webassembly::Instance.
//! A memory created by Rust or in WebAssembly code will be accessible and //! A memory created by Rust or in WebAssembly code will be accessible and
//! mutable from both Rust and WebAssembly. //! mutable from both Rust and WebAssembly.
use nix::libc::{c_void, mprotect, PROT_READ, PROT_WRITE};
use nix::sys::mman::{mmap, MapFlags, ProtFlags};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::slice; use std::slice;
use region;
use crate::common::mmap::Mmap;
/// A linear memory instance. /// A linear memory instance.
//
#[derive(Debug)] #[derive(Debug)]
pub struct LinearMemory { pub struct LinearMemory {
base: *mut c_void, // The size will always be `LinearMemory::DEFAULT_SIZE` // The mmap allocation
current: u32, // current number of wasm pages mmap: Mmap,
// current number of wasm pages
current: u32,
// The maximum size the WebAssembly Memory is allowed to grow // The maximum size the WebAssembly Memory is allowed to grow
// to, in units of WebAssembly pages. When present, the maximum // to, in units of WebAssembly pages. When present, the maximum
// parameter acts as a hint to the engine to reserve memory up // parameter acts as a hint to the engine to reserve memory up
@ -21,6 +25,8 @@ pub struct LinearMemory {
// request. In general, most WebAssembly modules shouldn't need // request. In general, most WebAssembly modules shouldn't need
// to set a maximum. // to set a maximum.
maximum: Option<u32>, maximum: Option<u32>,
offset_guard_size: usize,
} }
/// It holds the raw bytes of memory accessed by a WebAssembly Instance /// It holds the raw bytes of memory accessed by a WebAssembly Instance
@ -43,45 +49,37 @@ impl LinearMemory {
initial, maximum initial, maximum
); );
// TODO: Investigate if memory is zeroed out let offset_guard_size = LinearMemory::DEFAULT_SIZE as usize;
let base = unsafe { let mut mmap = Mmap::with_size(offset_guard_size).expect("Can't create mmap");
mmap(
0 as _,
LinearMemory::DEFAULT_SIZE,
ProtFlags::PROT_NONE,
MapFlags::MAP_ANON | MapFlags::MAP_PRIVATE,
-1,
0,
).unwrap()
};
if initial > 0 { let base = mmap.as_mut_ptr();
assert_eq!(
if initial != 0 {
unsafe { unsafe {
mprotect( region::protect(
base, base as _,
initial as usize * Self::PAGE_SIZE as usize, initial as usize * Self::PAGE_SIZE as usize,
// Self::DEFAULT_HEAP_SIZE, region::Protection::Read | region::Protection::Write,
PROT_READ | PROT_WRITE, // region::Protection::None,
) )
}, }
0 .expect("unable to make memory inaccessible");
);
} }
debug!("LinearMemory instantiated"); debug!("LinearMemory instantiated");
debug!(" - usable: {:#x}..{:#x}", base as usize, (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE); debug!(" - usable: {:#x}..{:#x}", base as usize, (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE);
debug!(" - guard: {:#x}..{:#x}", (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE, (base as usize) + LinearMemory::DEFAULT_SIZE); debug!(" - guard: {:#x}..{:#x}", (base as usize) + LinearMemory::DEFAULT_HEAP_SIZE, (base as usize) + LinearMemory::DEFAULT_SIZE);
Self { Self {
base, mmap,
current: initial, current: initial,
offset_guard_size,
maximum, maximum,
} }
} }
/// Returns an base address of this linear memory. /// Returns an base address of this linear memory.
pub fn base_addr(&mut self) -> *mut u8 { pub fn base(&mut self) -> *mut u8 {
self.base as _ self.mmap.as_mut_ptr() as _
} }
/// Returns a number of allocated wasm pages. /// Returns a number of allocated wasm pages.
@ -130,32 +128,43 @@ impl LinearMemory {
let new_bytes = (new_pages * Self::PAGE_SIZE) as usize; let new_bytes = (new_pages * Self::PAGE_SIZE) as usize;
unsafe { unsafe {
assert_eq!( region::protect(
mprotect( self.mmap.as_mut_ptr().add(prev_bytes) as _,
self.base.add(prev_bytes),
new_bytes - prev_bytes, new_bytes - prev_bytes,
PROT_READ | PROT_WRITE, region::Protection::Read | region::Protection::Write,
), // region::Protection::None,
0 )
);
} }
.expect("unable to make memory inaccessible");
// if new_bytes > self.mmap.len() - self.offset_guard_size {
// // If we have no maximum, this is a "dynamic" heap, and it's allowed to move.
// assert!(self.maximum.is_none());
// let guard_bytes = self.offset_guard_size;
// let request_bytes = new_bytes.checked_add(guard_bytes)?;
// let mut new_mmap = Mmap::with_size(request_bytes).ok()?;
// // Make the offset-guard pages inaccessible.
// unsafe {
// region::protect(
// new_mmap.as_ptr().add(new_bytes),
// guard_bytes,
// region::Protection::Read | region::Protection::Write,
// // region::Protection::None,
// )
// }
// .expect("unable to make memory inaccessible");
// let copy_len = self.mmap.len() - self.offset_guard_size;
// new_mmap.as_mut_slice()[..copy_len].copy_from_slice(&self.mmap.as_slice()[..copy_len]);
// self.mmap = new_mmap;
// }
self.current = new_pages; self.current = new_pages;
Some(prev_pages as i32) Some(prev_pages as i32)
} }
pub fn carve_slice(&self, offset: u32, size: u32) -> Option<&[u8]> {
let start = offset as usize;
let end = start + size as usize;
let slice: &[u8] = &*self;
if end <= self.current_size() as usize {
Some(&slice[start..end])
} else {
None
}
}
} }
// Not comparing based on memory content. That would be inefficient. // Not comparing based on memory content. That would be inefficient.
@ -168,12 +177,12 @@ impl PartialEq for LinearMemory {
impl Deref for LinearMemory { impl Deref for LinearMemory {
type Target = [u8]; type Target = [u8];
fn deref(&self) -> &[u8] { fn deref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.base as _, self.current as usize * Self::PAGE_SIZE as usize) } unsafe { slice::from_raw_parts(self.mmap.as_ptr() as _, self.current as usize * Self::PAGE_SIZE as usize) }
} }
} }
impl DerefMut for LinearMemory { impl DerefMut for LinearMemory {
fn deref_mut(&mut self) -> &mut [u8] { fn deref_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.base as _, self.current as usize * Self::PAGE_SIZE as usize) } unsafe { slice::from_raw_parts_mut(self.mmap.as_mut_ptr() as _, self.current as usize * Self::PAGE_SIZE as usize) }
} }
} }