Access WebAssembly Memory Directly from JavaScript

Nik Graf
InstructorNik Graf

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 3 years ago

While JavaScript has a garbage-collected heap, WebAssembly has a linear memory space. Nevertheless using a JavaScript ArrayBuffer, we can read and write to WebAssembly’s memory space.

Instructor: [00:00] Here, we start with a little refactoring for easier use of importing modules later in the lesson. We'll introduce an app.js file, and make sure our index.js file only takes care of loading the app module dynamically.

[00:18] We did this change to make sure we can use the static import statements inside app.js. Now, we can focus on the actual goal, accessing memory of a WebAssembly module from Javascript.

[00:32] Each WebAssembly module has a linear memory. From Javascript, we can freely read and write to it. To do so, we can directly import a variable memory from the WASM module. Let's log out the memory. It contains a buffer matching the linear memory of the WebAssembly module.

[01:03] How can we leverage this now? Let's write a small image library in Rust. We define a struct, color, containing red, green, and blue and unsigned integers. Next up, we implement the image. Our image contains pixel, which is a vector of colors.

[01:23] We create the function new, and inside it, add two different pixels, one pure red, with the red value 255, green and blue being 0The second one is a dark gray, with a red part of 60, green 70, and blue 90. Then we store the two pixels in the vector and return the image. Finally, we add a function pixel_pointer, returning the pointer to the pixel vector.

[01:56] Let's move on with Javascript. There, we instantiate a new image and retrieve our pixel pointer. We know all our pixels are unsigned integers with eight bits. In our pixels vector, we store the two pixels. This means we stored six color values.

[02:20] We leverage this information by only accessing the first six values out of our buffer, and store them in a typed array representing eight-bit unsigned integers. Here, the amazing part. These six values match exactly the color parts of our two pixels.

[02:39] This means we can extract the color of the two pixels directly from the memory, without any serialization or deserialization overhead. This is super useful in case serialization and deserialization is your performance bottleneck.

[02:55] In this example, we leverage this functionality to draw pixels to a canvas. Therefore, we create the helper function, drawPixel, accepting an X and Y position, and an array for the colors, red, green, and blue. Using context field style, we can convert the numbers to a hex code and draw the pixel.

[03:33] Be aware, instead of drawing just one pixel, we're drawing a larger rectangle in this case, so we can actually see the result. Finally, we can create our canvas, and use our drawPixel function to actually draw the pixels. We slice the array to retrieve the correct pixel colors.

[03:54] Voila. This works like a charm. We access the linear memory of our WebAssembly module directly from Javascript. While we won't do it in this lesson, you can imagine that this can be used to draw a canvas from the game loop managed from our Rust code.

[04:11] Be aware that WebAssembly's memory can be accessed from Javascript, but not the other way around. WebAssembly doesn't have direct access to Javascript values. That said, we can work around this by storing a Javascript value inside the WebAssembly memory, and then use it inside our Rust code.

NK
NK
~ 3 years ago

Looks like there's been some reshuffling of directories, so rather than using

import { memory } from "../crate/pkg/rust_webpack_bg";
import { Image } from "../crate/pkg/rust_webpack";

I imported content from the wasm-pack-generated files into app.js using:

import { memory } from "../pkg/index_bg";
import { Image } from "../pkg/index";
Andrey Kolybelnikov
Andrey Kolybelnikov
~ 3 years ago

The const pixels = new Uint8Array(memory.buffer, pixelsPointer, 6) is always [0,0,0,0,0,0] in my case. I can't figure out what's going on, because the const pixelsPointer = image.pixels_ptr() is not empty, but it is of type number. Should it be an array of numbers? It's a vec on the Rust's side. Because of the newer version of the template generator, memory is not explicitly exported in pkg and I have to construct it: const memory = new WebAssembly.Memory({ initial: 256 }). Could this be the issue?

Andrey Kolybelnikov
Andrey Kolybelnikov
~ 3 years ago

I have found out that to use the memory exported from warm-pack we need to run was-pack build and import the memory in app.js like so: import { memory } from '../pkg/index_bg'. The rest of the syntax is intact.

Farid Murzone
Farid Murzone
~ 6 months ago

Thanks @NK and @Andrey Kolybelnikov, it works for the last versions