Skip to content

Simplifying memory access #106

@sapessi

Description

@sapessi

I'm using wasmtime on a project that requires me to write to linear memory directly before invoking the wasm function. While this is possible today, the APIs are not easy to use. Below is a proposal to simplify memory interaction for discussion.

  • Add a method to directly access the exported VMMemoryDefinition to InstanceHandle:
/// Returns the exported module memory for the given name. 
/// When the export_name parameter is not passed defaults to "memory".
/// Returns None if the memory export could not be found
pub fn memory(&mut self, export_name: Option<&str>) -> Option<&VMMemoryDefinition>
  • Add a as_slice and as_mut_slice to the VMMemoryDefintion object
/// Converts the raw pointer to a u8 slice
pub fn as_slice(&self) -> &[u8] {}

/// Returns a mutable u8 slice pointing to the VMMemoryDefinition index.
pub fn as_mut_slice(&mut self) -> &mut [u8] {}

With these basic changes in place, after initializing a module, developers can quickly access its linear memory for reading and doing a lot of "unsafe" writing.

The next evolution of this would be to encapsulate all unsafe code in wasmtime's memory objects and give high-level abstractions for developers to use. The proposal below is informed by my use-case and I would like to hear others thoughts on what I'm missing or not understanding correctly. My objectives are:

  1. Make allocations easy and, whenever possible, automatically zero out the memory once the object is out of scope
  2. Make it easy to find the next available memory location and grow the memory when necessary
  3. Make it easy to read return values from functions

I'm using Box<Error> in the examples below for the sake of simplicity. We may want to go with specific error types

  • Introduce a new Allocation type which has fundamentally the same properties as VMMemoryDefintion with additional utility methods. I'm thinking of creating a separate object instead of re-using VMMemoryDefinition to prevent developers from calling potentially destructive new operations such as zero on the entire instance memory.
pub struct Allocation {
    pub base: *const u8,
    pub current_length: usize,

    /// The index in linear memory where the allocation occurred
    pub idx: i32,

    // Tells whether the Drop trait should execute the zero operation on this object.
    // See VMMemoryDefinition changes for the purpose of this flag. 
    auto_deallocate: bool,
}
  • The allocation method exposes an as_slice method to easily read the value as well as a zero method to zero out the memory.
impl Allocation {
    /// Returns a slice containing the allocation data
    pub fn as_slice(&self) -> &[u8] {}
    /// Zeros out the memory this aloocation points to
    pub fn zero(&mut self) -> Result<(), Box<Error>> {}
}

impl Into<DefinedMemoryIndex> for Allocation {}
impl Into<RuntimeValue::I32> for Allocation {}
impl Drop for Allocation {}
  • Add an alloc method to the VMMemoryDefinition that finds the next available memory location for the given value and copies the data to memroy. The output of this operation is an Allocation object,
impl VMMemoryDefintion {
    /// Writes the given u8 slice in the first available location in linear memory.
    /// When the grow parameter is set to true the method automatically grows the instance memory 
    /// if it cannot find an appropriate location for the data.
    /// The auto_deallocate parameter activates the `Drop` trait implementation for the returned 
    /// `Allocation` object. `drop` automatically zeroes out the memory when the object goes out of scope.
    pub fn alloc(&mut self, data: &[u8], grow: bool, auto_deallocate: bool) -> Result<Allocation, Box<Error>> {}

    /// Returns a raw pointer to the next available memory location of the given length. None if there isn't one.
    pub fn next_available(&self, data_len: usize) -> Option<*mut u8> {}

    /// Grows the instance memory by the given number of pages
    pub fn grow(&mut self, pages: u32) -> Result<(), Box<Error>> {}

    /// Grows the instnace memory by a number of pages sufficient to contain the given byte length
    pub fn grow_by_bytes(&mut self, bytes: u32) -> Result<(), Box<Error>> {}

    /// Returns an allocation object that points to the value returned in the given ActionOutcome
    pub fn get_output(&self, out_value: ActionOutcome) -> Result<Allocation, Box<Error>> {}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions