How do I keep internal state in a WebAssembly modu

2019-08-24 23:42发布

I want to do computations on a large set of data each frame of my web app. Only a subset of this will be used by JavaScript, so instead of sending the entire set of data back and forth between WebAssembly and JavaScript each frame, it would be nice if the data was maintained internally in my WebAssembly module.

In C, something like this works:

#include <emscripten/emscripten.h>

int state = 0;

void EMSCRIPTEN_KEEPALIVE inc() {
    state++;
}

int EMSCRIPTEN_KEEPALIVE get() {
    return state;
}

Is the same thing possible in Rust? I tried doing it with a static like this:

static mut state: i32 = 0;

pub fn main() {}

#[no_mangle]
pub fn add() {
    state += 1;
}

#[no_mangle]
pub fn get() -> i32 {
    state
}

But it seems static variables cannot be mutable.

2条回答
霸刀☆藐视天下
2楼-- · 2019-08-25 00:07
error[E0133]: use of mutable static requires unsafe function or block

In general, accessing mutable global variables is unsafe, which means that you can only do it in an unsafe block. With mutable global variables, it's easy to accidentally create dangling references (think of a reference to an item of a global mutable Vec), data races (if you have multiple threads – Rust doesn't care that you don't actually use threads) or otherwise invoke undefined behavior.

Global variables are usually not the best solution to a problem because it makes your software less flexible and less reusable. Instead, consider passing the state explicitly (by reference, so you don't need to copy it) to the functions that need to operate on it. This lets the calling code work with multiple independent states.


Here's an example of allocating unique state and modifying that:

type State = i32;

#[no_mangle]
pub extern fn new() -> *mut State {
    Box::into_raw(Box::new(0))
}

#[no_mangle]
pub extern fn free(state: *mut State) {
    unsafe { Box::from_raw(state) };
}

#[no_mangle]
pub extern fn add(state: *mut State) {
    unsafe { *state += 1 };
}

#[no_mangle]
pub extern fn get(state: *mut State) -> i32 {
    unsafe { *state }
}
const fs = require('fs-extra');

fs.readFile(__dirname + '/target/wasm32-unknown-unknown/release/state.wasm')
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(({ module, instance }) => {
    const { new: newFn, free, get, add } = instance.exports;

    const state1 = newFn();
    const state2 = newFn();

    add(state1);
    add(state2);
    add(state1);

    console.log(get(state1));
    console.log(get(state2));

    free(state1);
    free(state2);
});
2
1

Note — This currently needs to be compiled in release mode to work. Debugging mode has some issues at the moment.

Admittedly, this is not less unsafe because you're passing raw pointers around, but it makes it clearer in the calling code that there is some mutable state being manipulated. Also note that it is now the responsibility of the caller to ensure that the state pointer is being handled correctly.

查看更多
做自己的国王
3楼-- · 2019-08-25 00:29

Francis Gagné is absolutely correct that global variables generally make your code worse and you should avoid them.

However, for the specific case of WebAssembly as it is today, we don't have to worry about this concern:

if you have multiple threads

We can thus choose to use mutable static variables, if we have a very good reason to do so:

// Only valid because we are using this in a WebAssembly
// context without threads.
static mut STATE: i32 = 0;

#[no_mangle]
pub extern fn add() {
    unsafe { STATE += 1 };
}

#[no_mangle]
pub extern fn get() -> i32 {
    unsafe { STATE }
}

We can see the behavior with this NodeJS driver program:

const fs = require('fs-extra');

fs.readFile(__dirname + '/target/wasm32-unknown-unknown/release/state.wasm')
  .then(bytes => WebAssembly.instantiate(bytes))
  .then(({ module, instance }) => {
    const { get, add } = instance.exports;
    console.log(get());
    add();
    add();
    console.log(get());
});
0
2
查看更多
登录 后发表回答