Accessing and storing to memory in Wasm
Wasm has also a memory which helps you to store and access data at any point in your module.
Memory can be created in the module itself or imported into the module. This post has a main focus on the first case, but will also introduce the latter case in the following section.
The concept of memory
In Wasm, the memory is linear. So from JavaScript perspective it is just a buffer of unsigned bytes from which you can read from and store into.
You can initialize a memory instance in Javascript with the following code:
const memory = new WebAssembly.Memory({ initial: 1 });
This initializes an instance with a size of 1 page. 1 page has the size of 64kb. Technically, this will create an ArrayBuffer that is imported into the Wasm module.
The memory can also grow - you can either use the JS API with Memory.grow() or do it in Wasm.
As ArrayBuffers are not resizable, a grow operation creates a copy of the existing buffer with its data and the new size.
Initializing the memory in Wasm
In Wasm you can also create a memory, like the following sample shows
(memory 1)
In the current specification of Wasm, you can have just one such memory instruction.
You can also make it accessible with the export instruction:
(module
(memory 1)
(export "memory" (memory 0))
)
If you are interested in the investigation of how the memory is build up, take a look into the binary code!
wat2wasm can help you with that:
wat2wasm main.wat -v
Using memory instructions
To operate on the memory, Wasm defines a set of instructions.
To store or retrieve data, it has the store and load instructions on the number types.
Storing into the memory
As an example, check the following code:
(module
(memory 1)
(export "memory" (memory 0))
(func $initDataInMemory
i32.const 0 ;; address of memory where to start storing
i32.const 300 ;; value to store
i32.store
)
(start $initDataInMemory)
)
First, it defines the memory of one page.
The $initDataInMemory function stores the value 300 in the memory at position 0. For that it first defines the position and then the value to store.
The i32.store instructions takes this two params from the stack and evaluates them.
Reading from the memory
Reading from the memory is also pretty easy: You need to make use of the i32.load instruction. The parameter for this instruction is the address to read from.
The following example extends the code before by adding the showMem function to read the stored value from the memory:
(module
(memory 1)
(export "memory" (memory 0))
(func $showMem (export "showMem") (result i32)
i32.const 0
i32.load
)
(func $initDataInMemory
i32.const 0
i32.const 300
i32.store
)
(start $initDataInMemory)
)
Compile this Wasm module and invoke the showMem function. This will return the value 300.
Current size of the memory
The current size of the memory can be returned with the memory.size instruction.
Let’s make use of it to show the current size after growing the memory!
Growing the memory
The memory.grow instruction grows the memory by the given pages.
memory.grow returns the old size of the memory if the growing succeeded and -1 if the resizing failed.
In the following example invoking the grow function leads to a resizing of the memory. Afterwards it returns the new size of the memory.
(module
(memory 1)
(export "memory" (memory 0))
(func $grow (export "grow") (param $x i32) (result i32)
(memory.grow (local.get $x))
drop
memory.size
)
(func $initDataInMemory
i32.const 0
i32.const 300
i32.store
)
(start $initDataInMemory)
)
Storing strings
The good thing about memory in Wasm is that it allows to also store strings.
For this data segments are used.
The syntax for this is the following one:
data $memory $offset $data
- $memory is optional and the id of the memory (as we can have just one memory, this is also implicit a zero)
- $offset is the offset at which the data needs to be stored.
- $data is the data store.
The data is a string which will be encoded in a simple binary format defined in the spec.
The following example stores a string at the offset 10 in the memory:
(data 0 (i32.const 10) "Hello, World!\n")
Now, when the Wasm module exports the memory, the string can be decoded with the knowledge of the string offset and position. In Javascript this can look like the following:
function logString(offset, length) {
var memory = instance.exports.memory;
const bytes = new Uint8Array(memory.buffer, offset, length);
const string = new TextDecoder("utf8").decode(bytes);
console.log(string);
}
Conclusion
This post showed how memory can be initialized and accessed with the store and load instructions. Furthermore we had a look on the resizing of the memory and how to store strings with the data operation.
Check the documentation for more memory instructions.