WebAssembly Linear Memory

What is Linear Memory in WebAssembly?

Linear memory in WebAssembly is a contiguous block of memory (an array of bytes) that is used by WebAssembly modules to store data. This memory is:

  • Dynamic: It can grow in size during runtime as required.
  • Sandboxed: It is isolated to prevent unauthorized access by other applications or modules.
  • Shared: It can be accessed by both the WebAssembly module and the host environment (e.g., JavaScript).

Characteristics of WebAssembly Linear Memory

  1. Contiguous Block:
    • Memory is represented as a flat, sequential array of bytes, starting at address 0.
  2. Page-Based Allocation:
    • Memory is allocated in units called pages, each 64 KB in size.
  3. Dynamic Growth:
    • Modules can request additional memory pages during runtime, up to a defined maximum.
  4. Sandboxing:
    • Memory is confined to the WebAssembly module, ensuring security and preventing external tampering.

Defining Linear Memory in WebAssembly

Linear memory must be explicitly declared in a WebAssembly module. It can either be defined internally or imported from the host environment.

Example: Declaring Memory in WAT (WebAssembly Text Format)

(module
(memory (export "mem") 1 4) ;; Define memory with an initial size of 1 page and a maximum of 4 pages.
)
  • 1: Initial memory size (1 page = 64 KB).
  • 4: Maximum memory size (4 pages = 256 KB).
  • export “mem”: Exports the memory for host access.

Using Linear Memory in WebAssembly

Linear memory is used to store and retrieve data during the execution of a module. This is done using load and store instructions, which allow you to interact with memory at specific offsets.

Example: Working with Memory in WAT

(module
(memory (export "mem") 1)
;; Store a 32-bit integer at memory offset 0
(func (export "writeMemory")
i32.const 0 ;; Specify the memory offset
i32.const 42 ;; Value to store
i32.store ;; Store the value
)

;; Read a 32-bit integer from memory offset 0
(func (export "readMemory") (result i32)
i32.const 0 ;; Specify the memory offset
i32.load ;; Load the value
)
)

Accessing Linear Memory in JavaScript

The host environment, such as JavaScript, can interact with WebAssembly’s linear memory via the ArrayBuffer interface. Developers can use typed arrays like Uint8Array or Int32Array to manipulate memory directly.

Example: Reading and Writing Linear Memory in JavaScript

// Instantiate the WebAssembly module
fetch('linear_memory_example.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(({ instance }) => {
const memory = new Uint8Array(instance.exports.mem.buffer);

// Write to memory
memory[0] = 65; // ASCII value of 'A'
memory[1] = 66; // ASCII value of 'B'

// Read from memory
console.log(String.fromCharCode(memory[0], memory[1])); // Output: AB
});

Growing Linear Memory

WebAssembly allows memory to grow dynamically by adding pages. This is achieved using the grow method, either in WebAssembly itself or via the host environment.

Example: Growing Memory in JavaScript

const memory = new WebAssembly.Memory({ initial: 1, maximum: 10 }); // Initial size: 1 page, Max size: 10 pages

console.log(memory.buffer.byteLength); // Output: 65536 (64 KB)

memory.grow(2); // Grow memory by 2 pages
console.log(memory.buffer.byteLength); // Output: 196608 (192 KB)

Example: Growing Memory in WAT

(module
(memory (export "mem") 1 10) ;; Memory with max size of 10 pages
(func (export "growMemory") (param i32) (result i32)
memory.grow (local.get 0) ;; Grow memory by the specified number of pages
)
)

Use Cases for Linear Memory

  1. Data Storage:
    • Linear memory is used to store variables, arrays, strings and other data structures.
  2. Interfacing with Host Environment:
    • WebAssembly modules use memory to exchange data with JavaScript or other host environments.
  3. Game Development:
    • Linear memory is ideal for managing large datasets like game states, textures or assets.
  4. Efficient Computation:
    • Store intermediate results for complex computations such as image processing or machine learning.

Best Practices for Using Linear Memory

  1. Pre-Allocate Memory Efficiently:
    • Start with a reasonable initial size to avoid frequent memory growth during runtime.
  2. Avoid Over-Growing:
    • Set a practical maximum size to prevent excessive resource consumption.
  3. Use Typed Arrays:
    • Leverage JavaScript typed arrays for efficient memory access and manipulation.
  4. Keep Offsets Aligned:
    • Align memory offsets (e.g., 4-byte boundaries) to optimize performance for load/store operations.

Leave a Comment