💾
Interacting with memory
This example illustrates the basics of the basics of interacting with Wasm module memory.
A Wasm module can export its memory. With Wasmer you'll be able to interact with this memory.
In this example we'll illustrate the basics of interacting with the module memory:
- How to query information about the memory;
- How to load read from the memory;
- How to write to the memory.
There are mainly two ways of interacting with the memory, either through exported function or by calling the memory API directly. It really depends on how the memory is exported.
First we are going to want to initialize a new project. To do this we can navigate to our project folder, or create one. In this example, we will create a new project. Lets create it and navigate to it:
Rust
Go
C/C++
cargo new memory
cd memory
We have to modify
Cargo.toml
to add the Wasmer dependencies as shown below:[dependencies]
# The Wasmer API
wasmer = "3.0"
mkdir wasmer-example-memory
cd wasmer-example-memory
go mod init github.com/$USER/wasmer-example-memory
mkdir wasmer-example-memory
cd wasmer-example-memory
vim Makefile
Let's create a simple
Makefile
:CFLAGS = -g -I$(shell $(WASMER_DIR)/bin/wasmer config --includedir)
LDFLAGS = -Wl,-rpath,$(shell $(WASMER_DIR)/bin/wasmer config --libdir)
LDLIBS = $(shell $(WASMER_DIR)/bin/wasmer config --libs)
.SILENT: memory memory.o
memory: memory.o
.PHONY: clean
.SILENT: clean
clean:
rm -f memory.o memory
Now that we have everything set up, let's go ahead and try it out!
The first interesting thing to do is to query information about the memory. To do that we must either have access to the memory (i.e it has to be exported) or we must have access to an exported function which is able to give us this information.
One important thing to note: the size of the memory can be expressed as a number of pages or a number of bytes.
Each page of memory is 64 KiB in size.
Rust
Go
C/C++
let mem_size: TypedFunction<(), i32> = instance
.exports
.get_typed_function(&store, "mem_size")?;
let memory = instance.exports.get_memory("memory")?;
assert_eq!(memory.size(&store), Pages::from(1));
assert_eq!(memory.size(&store).bytes(), Bytes::from(65536 as usize));
assert_eq!(memory.data_size(&store), 65536);
let result = mem_size.call()?;
assert_eq!(Pages::from(result as u32), memory.size());
memSize, err := instance.Exports.GetFunction("mem_size")
if err != nil {
panic(fmt.Sprintln("Failed to retrieve the `mem_size` function:", err))
}
memory, err := instance.Exports.GetMemory("memory")
if err != nil {
panic(fmt.Sprintln("Failed to get the `memory` memory:", err))
}
size := memory.Size()
fmt.Println("Memory size (pages):", size)
fmt.Println("Memory size (pages as bytes):", size.ToBytes())
fmt.Println("Memory size (bytes):", memory.DataSize())
result, err := memSize()
if err != nil {
panic(fmt.Sprintln("Failed to call the `mem_size` function:", err))
}
fmt.Println("Memory size (pages):", result)
wasm_memory_pages_t pages = wasm_memory_size(memory);
size_t data_size = wasm_memory_data_size(memory);
printf("Memory size (pages): %d\n", pages);
printf("Memory size (bytes): %d\n", (int) data_size);
Now that we know the size of our memory it's time to see how we can change this.
A memory can be grown to allow storing more things into it. This is easily done through
Rust
Go
C/C++
memory.grow(&mut store, 2)?;
assert_eq!(memory.size(&store), Pages::from(3));
assert_eq!(memory.data_size(&store), 65536 * 3);
memory.Grow(2)
fmt.Println("New memory size (pages):", memory.Size())
if (!wasm_memory_grow(memory, 2)) {
printf("> Error growing memory!\n");
return 1;
}
wasm_memory_pages_t new_pages = wasm_memory_size(memory);
printf("New memory size (pages): %d\n", new_pages);
To grow a memory you have to call the dedicated method and provide the number of pages, called the delta, you want to add to the memory.
Now that we know how to query and adjust the size of the memory, let's see how we can write to it or read from it.
We'll only focus on how to do this using exported functions, the goal is to show how to work with memory addresses.
Let's start by using absolute memory addresses to write and read a value.
Rust
Go
C/C++
let get_at: TypedFunction<i32, i32> = instance
.exports
.get_typed_function(&store, "get_at")?;
let set_at: TypedFunction<(i32, i32), ()> = instance
.exports
.get_typed_function(&store, "set_at")?;
let mem_addr = 0x2220;
let val = 0xFEFEFFE;
set_at.call(&mut store, mem_addr, val)?;
let result = get_at.call(&mut store, mem_addr)?;
println!("Value at {:#x?}: {:?}", mem_addr, result);
getAt, err := instance.Exports.GetFunction("get_at")
if err != nil {
panic(fmt.Sprintln("Failed to retrieve the `get_at` function:", err))
}
setAt, err := instance.Exports.GetFunction("set_at")
if err != nil {
panic(fmt.Sprintln("Failed to retrieve the `set_at` function:", err))
}
memAddr := 0x2220
val := 0xFEFEFFE
_, err = setAt(memAddr, val)
if err != nil {
panic(fmt.Sprintln("Failed to call the `set_at` function:", err))
}
result, err = getAt(memAddr)
if err != nil {
panic(fmt.Sprintln("Failed to call the `get_at` function:", err))
}