Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert "Remove virtual memory support (paritytech#408)" #2

Merged
merged 14 commits into from
Jul 11, 2023
19 changes: 16 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Rust - Continuous Integration

on:
push:
branches: [ master ]
branches: [ "gear-v0.30.0" ]
pull_request:
branches: [ master ]
branches: [ "gear-v0.30.0" ]

jobs:
check:
Expand Down Expand Up @@ -62,8 +62,20 @@ jobs:
# windows-latest was pinned to windows-2019
# because of https://github.com/paritytech/wasmi/runs/5021520759
os: [ubuntu-latest, windows-latest, macos-latest]
include:
# Include a new variable `rustc-args` with `-- --test-threads 1`
# for windows-latest to be used with virtual_memory crate feature
# enabled while testing.
- os: windows-latest
test-args: "--test-threads 1"
runs-on: ${{ matrix.os }}
steps:
- name: Configure Pagefile for Windows
if: matrix.os == 'windows-latest'
uses: al-cheb/[email protected]
with:
minimum-size: 6GB
maximum-size: 32GB
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
Expand Down Expand Up @@ -95,9 +107,10 @@ jobs:
uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '--cfg debug_assertions'
TEST_FLAGS: ${{ matrix.test-args }}
with:
command: test
args: --workspace --release --all-features
args: --workspace --release --all-features -- $TEST_FLAGS

fmt:
name: Formatting
Expand Down
11 changes: 11 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ libm = "0.2.1"
num-traits = { version = "0.2.8", default-features = false }
downcast-rs = { version = "1.2", default-features = false }
paste = "1"
region = { version = "3.0.0", optional = true }

[dev-dependencies]
rand = "0.8.2"
Expand All @@ -24,6 +25,16 @@ rand = "0.8.2"
default = ["std"]
# Use `no-default-features` for a `no_std` build.
std = ["num-traits/std", "downcast-rs/std"]
# Enables OS supported virtual memory.
#
# Note
#
# - This feature is only supported on 64-bit platforms.
# For 32-bit platforms the linear memory will fallback to using the Vec
# based implementation.
# - The default is to fall back is an inefficient vector based implementation.
# - By nature this feature requires `region` and the Rust standard library.
virtual_memory = ["region", "std"]

[package.metadata.cargo-udeps.ignore]
# cargo-udeps cannot detect that libm is used for no_std targets only.
Expand Down
6 changes: 6 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ mod units;
mod untyped;
mod value;

#[cfg(feature = "virtual_memory")]
mod vmem;

#[cfg(not(feature = "std"))]
extern crate alloc;

Expand All @@ -43,3 +46,6 @@ pub use self::{
untyped::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue},
value::ValueType,
};

#[cfg(feature = "virtual_memory")]
pub use self::vmem::{VirtualMemory, VirtualMemoryError};
106 changes: 106 additions & 0 deletions crates/core/src/vmem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! An implementation of a byte buffer based on virtual memory.
//!
//! This implementation uses `mmap` on POSIX systems (and should use `VirtualAlloc` on windows).
//! There are possibilities to improve the performance for the reallocating case by reserving
//! memory up to maximum. This might be a problem for systems that don't have a lot of virtual
//! memory (i.e. 32-bit platforms).

use core::{
fmt,
fmt::{Debug, Display},
slice,
};
use region::{Allocation, Protection};

/// Dummy error for fallible `Vec`-based virtual memory operations.
#[derive(Debug)]
pub enum VirtualMemoryError {
Region(region::Error),
AllocationOutOfBounds,
}

impl From<region::Error> for VirtualMemoryError {
#[inline]
fn from(error: region::Error) -> Self {
Self::Region(error)
}
}

impl Display for VirtualMemoryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Region(error) => write!(
f,
"encountered failure while operating with virtual memory: {}",
error
),
Self::AllocationOutOfBounds => write!(f, "virtual memory allocation is too big"),
}
}
}

/// A virtual memory buffer.
pub struct VirtualMemory {
/// The virtual memory allocation.
allocation: Allocation,
}

impl Debug for VirtualMemory {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("VirtualMemory")
.field("len", &self.allocation.len())
.finish()
}
}

unsafe impl Sync for VirtualMemory {}

unsafe impl Send for VirtualMemory {}

impl VirtualMemory {
/// The maximum allocation size for a `wasmi` virtual memory.
const MAX_ALLOCATION_SIZE: usize = u32::MAX as usize;

/// Create a new virtual memory allocation.
///
/// # Note
///
/// The allocated virtual memory allows for read and write operations.
///
/// # Errors
///
/// - If `len` should not exceed `isize::max_value()`
/// - If `len` should be greater than 0.
/// - If the operating system returns an error upon virtual memory allocation.
pub fn new(len: usize) -> Result<Self, VirtualMemoryError> {
assert_ne!(len, 0, "cannot allocate empty virtual memory");
if len > Self::MAX_ALLOCATION_SIZE {
return Err(VirtualMemoryError::AllocationOutOfBounds);
}
let allocation = region::alloc(len, Protection::READ_WRITE)?;
Ok(Self { allocation })
}

/// Returns a shared slice over the bytes of the virtual memory allocation.
#[inline]
pub fn data(&self) -> &[u8] {
// # SAFETY
//
// The operation is safe since we assume that the virtual memory allocation
// has been successful and allocated exactly `self.allocation.len()` bytes.
// Therefore creating a slice with `self.len` elements is valid.
// Aliasing guarantees are not violated since `self` is the only owner
// of the underlying virtual memory allocation.
unsafe { slice::from_raw_parts(self.allocation.as_ptr(), self.allocation.len()) }
}

/// Returns an exclusive slice over the bytes of the virtual memory allocation.
#[inline]
pub fn data_mut(&mut self) -> &mut [u8] {
// # SAFETY
//
// See safety proof of the `as_slice` method.
// Additionally, it is not possible to obtain two mutable references for the same memory area.
unsafe { slice::from_raw_parts_mut(self.allocation.as_mut_ptr(), self.allocation.len()) }
}
}
10 changes: 10 additions & 0 deletions crates/wasmi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ criterion = { version = "0.4", default-features = false }
[features]
default = ["std"]
std = ["wasmi_core/std", "wasmi_arena/std", "wasmparser/std", "spin/std"]
# Enables OS supported virtual memory.
#
# Note
#
# - This feature is only supported on 64-bit platforms.
# For 32-bit platforms the linear memory will fallback to using the Vec
# based implementation.
# - The default is to fall back is an inefficient vector based implementation.
# - By nature this feature requires `region` and the Rust standard library.
virtual_memory = ["wasmi_core/virtual_memory", "std"]

[[bench]]
name = "benches"
Expand Down
4 changes: 1 addition & 3 deletions crates/wasmi/src/func/typed_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,7 @@ impl<Results> Default for CallResultsTuple<Results> {
impl<Results> Copy for CallResultsTuple<Results> {}
impl<Results> Clone for CallResultsTuple<Results> {
fn clone(&self) -> Self {
Self {
_marker: PhantomData,
}
*self
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
use alloc::{vec, vec::Vec};
use core::{fmt, fmt::Display};

/// Dummy error for fallible `Vec`-based virtual memory operations.
#[derive(Debug)]
pub struct VirtualMemoryError {}
ark0f marked this conversation as resolved.
Show resolved Hide resolved

impl Display for VirtualMemoryError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "encountered failure while operating with virtual memory")
}
}

/// A `Vec`-based byte buffer implementation.
///
Expand All @@ -19,20 +30,21 @@ impl ByteBuffer {
///
/// - If the initial length is 0.
/// - If the initial length exceeds the maximum supported limit.
pub fn new(initial_len: usize) -> Self {
Self {
pub fn new(initial_len: usize) -> Result<Self, VirtualMemoryError> {
Ok(Self {
bytes: vec![0x00_u8; initial_len],
}
})
}

/// Grows the byte buffer to the given `new_size`.
///
/// # Panics
///
/// If the current size of the [`ByteBuffer`] is larger than `new_size`.
pub fn grow(&mut self, new_size: usize) {
pub fn grow(&mut self, new_size: usize) -> Result<(), VirtualMemoryError> {
assert!(new_size >= self.len());
self.bytes.resize(new_size, 0x00_u8);
Ok(())
}

/// Returns the length of the byte buffer in bytes.
Expand Down
77 changes: 77 additions & 0 deletions crates/wasmi/src/memory/buffer_vmem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use super::MemoryError;
use core::fmt::Debug;
use wasmi_core::VirtualMemory;

pub use wasmi_core::VirtualMemoryError;

/// A virtual memory based byte buffer implementation.
///
/// # Note
///
/// - This is a more efficient implementation of the byte buffer that
/// makes use of operating system provided virtual memory abstractions.
/// - This implementation allocates 4GB of virtual memory up front so
/// that grow operations later on are no-ops. The downside to this is
/// that this implementation is only supported on 64-bit systems.
/// 32-bit systems will fall back to the `Vec`-based implementation
/// even if the respective crate feature is enabled.
#[derive(Debug)]
pub struct ByteBuffer {
bytes: VirtualMemory,
len: usize,
}

impl ByteBuffer {
/// Determines the initial size of the virtual memory allocation.
///
/// # Note
///
/// In this implementation we won't reallocate the virtually allocated
/// buffer and instead simply adjust the `len` field of the `ByteBuf`
/// wrapper in order to efficiently grow the virtual memory.
const ALLOCATION_SIZE: usize = u32::MAX as usize;

/// Creates a new byte buffer with the given initial length.
///
/// # Errors
///
/// - If the initial length is 0.
/// - If the initial length exceeds the maximum supported limit.
pub fn new(initial_len: usize) -> Result<Self, MemoryError> {
let bytes = VirtualMemory::new(Self::ALLOCATION_SIZE)?;
Ok(Self {
bytes,
len: initial_len,
})
}

/// Grows the byte buffer by the given delta.
///
/// # Errors
///
/// If the new length of the byte buffer would exceed the maximum supported limit.
pub fn grow(&mut self, new_size: usize) -> Result<(), MemoryError> {
if new_size > Self::ALLOCATION_SIZE {
return Err(MemoryError::OutOfBoundsGrowth)?;
}

assert!(new_size >= self.len());
self.len = new_size;
Ok(())
}

/// Returns the length of the byte buffer in bytes.
pub fn len(&self) -> usize {
self.len
}

/// Returns a shared slice to the bytes underlying to the byte buffer.
pub fn data(&self) -> &[u8] {
&self.bytes.data()[..self.len]
}

/// Returns an exclusive slice to the bytes underlying to the byte buffer.
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.bytes.data_mut()[..self.len]
}
}
10 changes: 10 additions & 0 deletions crates/wasmi/src/memory/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::MemoryType;
use crate::memory::VirtualMemoryError;
use core::{fmt, fmt::Display};

/// An error that may occur upon operating with virtual or linear memory.
Expand All @@ -13,6 +14,8 @@ pub enum MemoryError {
OutOfBoundsAccess,
/// Tried to create an invalid linear memory type.
InvalidMemoryType,
/// A generic virtual memory error.
Vmem(VirtualMemoryError),
/// Occurs when `ty` is not a subtype of `other`.
InvalidSubtype {
/// The [`MemoryType`] which is not a subtype of `other`.
Expand All @@ -37,9 +40,16 @@ impl Display for MemoryError {
Self::InvalidMemoryType => {
write!(f, "tried to create an invalid virtual memory type")
}
Self::Vmem(error) => Display::fmt(error, f),
Self::InvalidSubtype { ty, other } => {
write!(f, "memory type {ty:?} is not a subtype of {other:?}",)
}
}
}
}

impl From<VirtualMemoryError> for MemoryError {
fn from(error: VirtualMemoryError) -> Self {
Self::Vmem(error)
}
}
Loading
Loading