Memory Manipulation

In this example, we'll present how to manipulate the linear memory with the APIs defined in wasmedge_sdk::Memory.

N.B. In this example, wasmedge-sdk v0.1.1, wasmedge-sys v0.7.1 and wasmedge-types v0.1.3 are used.

Wasm module

Before talk about the code, let's first see the wasm module we use in this example. In the wasm module, a linear memory of 1-page (64KiB) size is defined; in addition, three functions are exported from this module: get_at, set_at, and mem_size.

(module
  (type $mem_size_t (func (result i32)))
  (type $get_at_t (func (param i32) (result i32)))
  (type $set_at_t (func (param i32) (param i32)))

  # A memory with initial size of 1 page
  (memory $mem 1)

  (func $get_at (type $get_at_t) (param $idx i32) (result i32)
    (i32.load (local.get $idx)))

  (func $set_at (type $set_at_t) (param $idx i32) (param $val i32)
    (i32.store (local.get $idx) (local.get $val)))

  (func $mem_size (type $mem_size_t) (result i32)
    (memory.size))
  
  # Exported functions
  (export "get_at" (func $get_at))
  (export "set_at" (func $set_at))
  (export "mem_size" (func $mem_size))
  (export "memory" (memory $mem)))

Next, we'll demonstrate how to manipulate the linear memory by calling the exported functions.

Load and Register Module

Let's start off by getting all imports right away so you can follow along

#![allow(unused)]
fn main() {
// please add this feature if you're using rust of version < 1.63
// #![feature(explicit_generic_args_with_impl_trait)]

use wasmedge_sdk::{params, Executor, ImportObjectBuilder, Module, Store};
use wasmedge_sys::WasmValue;
use wasmedge_types::wat2wasm;
}

To load a Module, wasmedge-sdk defines two methods:

  • from_file loads a wasm module from a file, and meanwhile, validates the loaded wasm module.

  • from_bytes loads a wasm module from an array of in-memory bytes, and meanwhile, validates the loaded wasm module.

Here we use Module::from_bytes method to load our wasm module from an array of in-memory bytes.

#![allow(unused)]
fn main() {
let wasm_bytes = wat2wasm(
        r#"
(module
  (type $mem_size_t (func (result i32)))
  (type $get_at_t (func (param i32) (result i32)))
  (type $set_at_t (func (param i32) (param i32)))

  (memory $mem 1)

  (func $get_at (type $get_at_t) (param $idx i32) (result i32)
    (i32.load (local.get $idx)))

  (func $set_at (type $set_at_t) (param $idx i32) (param $val i32)
    (i32.store (local.get $idx) (local.get $val)))

  (func $mem_size (type $mem_size_t) (result i32)
    (memory.size))

  (export "get_at" (func $get_at))
  (export "set_at" (func $set_at))
  (export "mem_size" (func $mem_size))
  (export "memory" (memory $mem)))
"#
    .as_bytes(),
)?;

// loads a wasm module from the given in-memory bytes
let module = Module::from_bytes(None, &wasm_bytes)?;
}

The module returned by Module::from_bytes is a compiled module, also called AST Module in WasmEdge terminology. To use it in WasmEdge runtime environment, we need to instantiate the AST module. We use Store::register_named_module API to achieve the goal.

#![allow(unused)]
fn main() {
// create an executor
let mut executor = Executor::new(None, None)?;

// create a store
let mut store = Store::new()?;

// register the module into the store
let extern_instance = store.register_named_module(&mut executor, "extern", &module)?;
}

In the code above, we register the AST module into a Store, in which the module is instantiated, and as a result, a module instance named extern is returned.

Memory

In the previous section, we get an instance by registering a compiled module into the runtime environment. Now we retrieve the memory instance from the module instance, and make use of the APIs defined in Memory to manipulate the linear memory.

#![allow(unused)]
fn main() {
// get the exported memory instance
let mut memory = extern_instance
    .memory("memory")
    .ok_or_else(|| anyhow::anyhow!("failed to get memory instance named 'memory'"))?;

// check memory size
assert_eq!(memory.size(), 1);
assert_eq!(memory.data_size(), 65536);

// grow memory size
memory.grow(2)?;
// check the size (in pages) and the data size (in bytes)
assert_eq!(memory.size(), 3);
assert_eq!(memory.data_size(), 3 * 65536);

// get the exported functions: "set_at" and "get_at"
let set_at = extern_instance
    .func("set_at")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'set_at'."))?;
let get_at = extern_instance
    .func("get_at")
    .ok_or_else(|| anyhow::Error::msg("Not found exported function named 'get_at`."))?;

// set val at mem_addr by calling "set_at"
let mem_addr = 0x2220;
let val = 0xFEFEFFE;
set_at.call(&mut executor, params!(mem_addr, val))?;

// get the value at mem_addr by calling "get_at"
let returns = get_at.call(&mut executor, params!(mem_addr))?;
assert_eq!(returns[0].to_i32(), val);

// call the exported function named "set_at"
let page_size = 0x1_0000;
let mem_addr = (page_size * 2) - std::mem::size_of_val(&val) as i32;
let val = 0xFEA09;
set_at.call(&mut executor, params!(mem_addr, val))?;

// call the exported function named "get_at"
let returns = get_at.call(&mut executor, params!(mem_addr))?;
assert_eq!(returns[0].to_i32(), val);
}

The comments in the code explain the meaning of the code sample above, so we don't describe more.

The complete code of this example can be found here.