The maintainer of this website has a Spotify Coding Playlist of their Lo-fi Hip Hop beats!

WebAssembly Logo

Wasm By Example

Language:

WebAssembly Linear Memory

Overview

Another feature of WebAssembly, is its linear memory. Linear memory is a continuous buffer of unsigned bytes that can be read from and stored into by both Wasm and Javascript. In other words, Wasm memory is an expandable array of bytes that Javascript and Wasm can synchronously read and modify. Linear memory can be used for many things, one of them being passing values back and forth between Wasm and Javascript.

In rust, tools like wasm-bindgen, which is part of wasm-pack workflow, abstracts away linear memory, and allows using native data structures between rust and Javascript. But for this example, we will use simple byte (Unsigned 8-bit integer) buffers and pointers (Wasm memory array indexes) as a simple(r) way to pass memory back and forth, and show off the concept.

Let's see how we can use linear memory:


Implementation

First, let's add the following to our src/lib.rs file:

// The wasm-pack uses wasm-bindgen to build and generate JavaScript binding file.
// Import the wasm-bindgen crate.
use wasm_bindgen::prelude::*;

// Create a static mutable byte buffer.
// We will use for passing memory between js and wasm.
// NOTE: global `static mut` means we will have "unsafe" code
// but for passing memory between js and wasm should be fine.
const WASM_MEMORY_BUFFER_SIZE: usize = 2;
static mut WASM_MEMORY_BUFFER: [u8; WASM_MEMORY_BUFFER_SIZE] = [0; WASM_MEMORY_BUFFER_SIZE];

// Function to store the passed value at index 0,
// in our buffer
#[wasm_bindgen]
pub fn store_value_in_wasm_memory_buffer_index_zero(value: u8) {
  unsafe {
    WASM_MEMORY_BUFFER[0] = value;
  }
}

// Function to return a pointer to our buffer
// in wasm memory
#[wasm_bindgen]
pub fn get_wasm_memory_buffer_pointer() -> *const u8 {
  let pointer: *const u8;
  unsafe {
    pointer = WASM_MEMORY_BUFFER.as_ptr();
  }

  return pointer;
}

// Function to read from index 1 of our buffer
// And return the value at the index
#[wasm_bindgen]
pub fn read_wasm_memory_buffer_and_return_index_one() -> u8 {
  let value: u8;
  unsafe {
    value = WASM_MEMORY_BUFFER[1];
  }
  return value;
}

Then, let's compile that using wasm-pack, which will create a pkg/ directory:

wasm-pack build --target web

Next, lets create an index.js file to load and run our wasm output. Let's import the wasm initialization module from pkg/webassembly_linear_memory.js that was generated by wasm-pack. Then, let's call the module passing in the path to our wasm file at pkg/webassembly_linear_memory_bg.wasm that was generated by wasm-pack. Then, let's go ahead and read and write to memory from both Wasm and JS. Please read through the commented code for context. And be sure to read the note at the bottom of this code example. Let's dive into our resulting index.js:

NOTE: In this example, we are using the exported function from the wasm module directly to help highlight the WebAssembly API. wasm-bindgen generates JavaScript bindings code that can be imported as an ES6 import, and is the reccomended way to work with your Rust Wasm modules. These JavaScript bindings are shown in the "Passing High Level Data Types with wasm-bindgen" example.

const runWasm = async () => {
  const rustWasm = await wasmInit("./pkg/webassembly_linear_memory_bg.wasm");

  /**
   * Part one: Write in Wasm, Read in JS
   */
  console.log("Write in Wasm, Read in JS, Index 0:");

  // First, let's have wasm write to our buffer
  rustWasm.store_value_in_wasm_memory_buffer_index_zero(24);

  // Next, let's create a Uint8Array of our wasm memory
  let wasmMemory = new Uint8Array(rustWasm.memory.buffer);

  // Then, let's get the pointer to our buffer that is within wasmMemory
  let bufferPointer = rustWasm.get_wasm_memory_buffer_pointer();

  // Then, let's read the written value at index zero of the buffer,
  // by accessing the index of wasmMemory[bufferPointer + bufferIndex]
  console.log(wasmMemory[bufferPointer + 0]); // Should log "24"

  /**
   * Part two: Write in JS, Read in Wasm
   */
  console.log("Write in JS, Read in Wasm, Index 1:");

  // First, let's write to index one of our buffer
  wasmMemory[bufferPointer + 1] = 15;

  // Then, let's have wasm read index one of the buffer,
  // and return the result
  console.log(rustWasm.read_wasm_memory_buffer_and_return_index_one()); // Should log "15"

  /**
   * NOTE: if we were to continue reading and writing memory,
   * depending on how the memory is grown by rust, you may have
   * to re-create the Uint8Array since memory layout could change.
   * For example, `let wasmMemory = new Uint8Array(rustWasm.memory.buffer);`
   * In this example, we did not, but be aware this may happen :)
   */
};
runWasm();

Lastly, lets load our ES6 Module, index.js Javascript file in our index.html. And you should get something similar to the demo (Source Code) below!


Demo

Next let's take a look at importing JavaScript functions into WebAssembly.