Memory Model in WebAssembly
Tags: rust, javascript, webdev, beginners
Published: 2020-01-10 15:53:02


For JavaScript to execute, the JavaScript engine should download the resources. The JavaScript engine waits until the resources are downloaded. Once downloaded, the JavaScript engine parses. The parser converts the source code to byte code that JavaScript interpreter runs.

When a function is called multiple times. The `baseline compiler` (in v8) compiles the code. The compilation happens in the main thread. The compiler spends time for compilation. But the compiled code runs faster than the interpreted code. The compiled code is optimised by the `optimising compiler`.

When the function is called a lot more. The compiler marks the function and tries to optimise further. During this `re-optimisation`, the compiler assumes and produces even more optimised code. This optimisation takes a bit of time but the generated code is much faster.

The function is executed. Finally, the code is garbage collected.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/rn4e806iw9gdkrups5gj.png)

> WebAssembly is fast. 🚀

The JavaScript engine downloads the WebAssembly module. Once downloaded the WebAssembly module is decoded.

> Decoding is faster than parsing.

Once decoded, the WebAssembly module is compiled and optimised. This step is fast because the module has already been compiled and optimised.

The module is finally executed.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/5vvxkdgt4p837yfvwskm.png)

> Note: there is no separate garbage collection step. The WebAssembly module takes care of allocating and de-allocating the memory.

---

Check out my book on Rust and WebAssembly [here](https://sendilkumarn.com/wasm-book)

---

In the quest of speeding up WebAssembly execution, the browser vendors implemented streaming compilation. Streaming compilation enables JavaScript engines to compile and optimise the module while the WebAssembly module is still downloading. Unlike JavaScript, where the engines should wait until the file is completely downloaded. This speeds up the process.

---

JavaScript and WebAssembly are two different things at the browser level. Calling WebAssembly from JavaScript or vice versa is slow. (This holds good for calls between any two languages). This is because crossing boundaries has a cost attached to it.

The browser vendors (especially Firefox) are trying to reduce the cost of boundary-crossing. In fact, in Firefox the JavaScript to WebAssembly call is much faster than the non-inlined JavaScript to JavaScript calls.

But still, proper care should be given to the boundary-crossing while designing your application. They can be a major performance bottleneck for the application. In those cases, it is important to understand the memory model of the WebAssembly module.

# Memory model in WebAssembly

The `memory section` of the WebAssembly module is a vector of linear memories.

## Linear Memory Model

> A linear memory model is a memory addressing technique in which the memory is organized in a single contagious address space. It is also known as Flat memory model.

While the linear memory model makes it easier to understand, program, and represent the memory.

They have huge disadvantages like
* high execution time for rearranging elements
* wastes a lot of memory area

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/vwxu05g3hqwry835233f.png)

The memory is a vector of raw bytes of uninterpreted data. They use resizable array buffers to hold the raw bytes of memory. JavaScript and WebAssembly can synchronously read and write into the memory.

We can allocate the memory using `WebAssembly.memory()` constructor from JavaScript.

---

## Write some code ✍️

### Passing from WebAssembly to JavaScript

Let us first see how to pass values through memory from WebAssembly Module (written with Rust) to JavaScript.

Create a new project using `cargo`.

```
$ cargo new --lib memory_world
```

Once the project is successfully created. Open the project in your favourite editor. Let us edit the `src/lib.rs` with the following contents

```
#![no_std]

use core::panic::PanicInfo;
use core::slice::from_raw_parts_mut;

#[no_mangle]
fn memory_to_js() {
    let obj: &mut [u8];

    unsafe {
        obj = from_raw_parts_mut::<u8>(0 as *mut u8, 1);
    }

    obj[0] = 13;
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> !{
    loop{}
}
```

Add this to the `Cargo.toml`:

```toml
[lib]
crate-type = ["cdylib"]
```

### What is there?

The rust file starts with `#![no_std]`. The `#![no_std]` attribute instructs the rust compiler to fallback to core crate instead of std crate. The core crate is platform agnostic. The core crate is a smaller subset of the std crate. This reduces the binary size dramatically. 

The function `memory_to_js` is annotated with `#[no_mangle]`. This function does not return any value, because it changes the value in the shared memory.

We define a mutable slice of type `u8` and name it as `obj`. Then we use `from_raw_parts_mut` to create a `u8` using a pointer and length. By default the memory starts at `0` and we just take `1` element. 

We are accessing the raw memory so we wrap the calls inside the `unsafe` block. The generated slice from `from_raw_parts_mut` is mutable.

Finally, we assign `13` in the first index.

```
unsafe {
    obj = from_raw_parts_mut::<u8>(0 as *mut u8, 1);
}

obj[0] = 13;
```

We have also defined a `panic_handler` to capture any panics and ignore it for now (do not do this in your production application).

> Note that we are not using `wasm_bindgen` here.

In JavaScript, we load the WebAssembly module and access the memory straightaway from the module.

First, fetch and instantiate the WebAssembly module.

```
const bytes = await fetch("target/wasm32-unknown-unknown/debug/memory_world.wasm");
const response = await bytes.arrayBuffer();
const result = await WebAssembly.instantiate(response, {});
```

The result object is the WebAssembly object that contains all the imported and exported functions. We call the exported `memory_to_js` function from the `result.exports`.

```
result.exports.memory_to_js();
```

This calls the WebAssembly module's `memory_to_js` function and assigns the value in the shared memory.

The shared memory is exported by `result.exports.memory.buffer` object.

```
const memObj = new UInt8Array(result.exports.memory.buffer, 0).slice(0, 1);
console.log(memObj[0]); // 13
```

The memory is accessed via `load` and `store` binary instructions. These binary instructions are accessed with the `offset` and the `alignment`. The `alignment` is in base 2 logarithmic representation.

> Note: WebAssembly currently provides only `32-bit` address ranges. In future, WebAssembly may provide `64-bit ` address range.

---

### Passing from JavaScript to WebAssembly

We have seen how to share memory between JavaScript and WebAssembly, by creating the memory in Rust. Now it is time to create a memory in JavaScript and use it inside Rust.

The memory in the JavaScript land has no way to tell the WebAssembly land what to allocate and when to free them. Being type, WebAssembly expects explicit type information. We need to tell the WebAssembly land how to allocate the memory and then how to free them.

To create the memory via JavaScript, use the `WebAssembly.Memory()` constructor.

The memory constructor takes in an object to set the defaults. They are

* initial - The initial size of the memory
* maximum - The maximum size of the memory (Optional)
* shared - to denote whether to use the shared memory

The unit for initial and maximum is (WebAssembly) pages. Each page holds up to 64KB.

---

## Write some code ✍️

Initialise the memory,

```
const memory = new WebAssembly.Memory({initial: 10, maximum: 100});
```

The memory is initialized with `WebAssembly.Memory()` constructor with an initial value of `10 pages` and a maximum value of `100 pages`. This translates to 640KB and 6.4MB initial and maximum respectively.

```
const bytes = await fetch("target/wasm32-unknown-unknown/debug/memory_world.wasm");
const response = await bytes.arrayBuffer();
const instance = await WebAssembly.instantiate(response, { js: { mem: memory } });
```

We fetch the WebAssembly Module and instantiate them. But while instantiating we pass in the memory object.

```
const s = new Set([1, 2, 3]);
let jsArr = Uint8Array.from(s);
```

We create a `typedArray` (`UInt8Array`) with values 1, 2, and 3.

```
const len = jsArr.length;
let wasmArrPtr = instance.exports.malloc(length);
```

WebAssembly modules will not have any clue about the size of the objects that are created in the memory. WebAssembly needs to allocate memory. We have to manually write the allocation and freeing of memory. In this step, we send the length of the array and allocate that memory. This will give us a pointer to the location of the memory.

```
let wasmArr = new Uint8Array(instance.exports.memory.buffer, wasmArrPtr, len);
```

We then create a new typedArray with the buffer (total available memory), memory offset (wasmAttrPtr), and the length of the memory.

```
wasmArr.set(jsArr);
```

We finally set the locally created typedArray (`jsArr`) into the typedArray `wasmArrPtr`.

```
const sum = instance.exports.accumulate(wasmArrPtr, len); // -> 7
console.log(sum);
```

We are sending the `pointer` (to memory) and `length` to WebAssembly module. In the WebAssembly module, we fetch the value from the memory and use them.

In the Rust, the `malloc` and `accumulate` functions are as follows:

```
use std::alloc::{alloc, dealloc,  Layout};
use std::mem;

#[no_mangle]
fn malloc(size: usize) -> *mut u8 {
    let align = std::mem::align_of::<usize>();
    if let Ok(layout) = Layout::from_size_align(size, align) {
        unsafe {
            if layout.size() > 0 {
                let ptr = alloc(layout);
                if !ptr.is_null() {
                    return ptr
                }
            } else {
                return align as *mut u8
            }
        }
    }
    std::process::abort
}
```

Given the size, the malloc function allocates a block of memory.

```
#[no_mangle]
fn accumulate(data: *mut u8, len: usize) -> i32 {
    let y = unsafe { std::slice::from_raw_parts(data as *const u8, len) };
    let mut sum = 0;
    for i in 0..len {
        sum = sum + y[i];
    }
    sum as i32
}
```

The `accumulate` function takes in the shared array and the size (`len`). It then recovers the `data` from the shared memory. Then runs through the `data` and returns the sum of all the elements passed in the data.
---

If you have enjoyed the post, then you might like my book on Rust and WebAssembly. Check them out [here](https://sendilkumarn.com/wasm-book)

---
---

👇 Repo 👇

{% github sendilkumarn/rustwasm-memory-model no-readme %}

---

## Interested to explore further

WebAssembly Memory using JavaScript API at [here](https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API#Memory)

Memory access in the WebAssembly is safer check at [here](https://hacks.mozilla.org/2017/07/memory-in-webassembly-and-why-its-safer-than-you-think/)

Check out more about `from_raw_parts_mut` at [here](https://doc.rust-lang.org/nightly/std/slice/fn.from_raw_parts_mut.html)

Check out more about TypedArray [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)

---


[🐦 Twitter](https://twitter.com/sendilkumarn) // [💻 GitHub](https://github.com/sendilkumarn) // [✍️ Blog](https://sendilkumarn.com/blog)  // [🔶 hackernews](https://news.ycombinator.com/item?id=22037558)


If you like this article, please leave a like or a comment. ❤️

---