Search code examples
javascriptrustwasm-bindgenwebassembly

Why do the values of Rust WASM pointer and JS pointer differ?


Let's say I have the following definitions in the Rust code:

#[wasm_bindgen]
pub struct RustType {
    foo: usize
}

#[wasm_bindgen]
impl RustType {
    #[wasm_bindgen(constructor)]
    pub fn new() -> Self {
        Self { foo: 100 }
    }
}

#[wasm_bindgen]
pub fn print_addr(obj: &RustType) {
    console_log!("rust addr = {}", obj as *const _ as u32);
}

JS code creates an instance of RustType and passes it to the print_addr function:

var obj = new RustType();
print_addr(obj);

After modifying the generated print_addr function in index_bg.js like this:

export function print_addr(obj) {
    _assertClass(obj, RustType);
    console.log("js addr = ", obj.ptr); // <== added this line
    if (obj.ptr === 0) {
        throw new Error('Attempt to use a moved value');
    }
    wasm.print_addr(obj.ptr);
}

in the dev console I'm getting the following output:

js addr =  1114120
rust addr = 1114124

The question is why the values of Rust pointer and JS pointer differ? Also according to my observations, the diff between Rust pointer and JS pointer is always equal to 4. Why is it like this?


Solution

  • If you look at exporting a struct to JS, a bit down the page you can see the generated code for some of the functions. One which looks relevant is:

    #[export_name = "foo_new"]
    pub extern "C" fn __wasm_bindgen_generated_Foo_new(arg0: i32) -> u32
        let ret = Foo::new(arg0);
        Box::into_raw(Box::new(WasmRefCell::new(ret))) as u32
    }
    

    So we've got a pointer through a Box, nbd, however you can see that Foo (the struct being exposed to Javascript) is wrapped in a WasmRefCell, which is a vendored version of RefCell but more importantly a structure with two fields:

    pub struct WasmRefCell<T> {
        borrow: Cell<usize>,
        value: UnsafeCell<T>,
    }
    

    Here T is the Rust type, so inside of the Rust type you see that address, however what's given to Javascript is the address of the WasmRefCell, which means it could be the address of the Cell<usize> which precedes the struct in the source: rustc doesn't guarantee it will match the source layout (unless you annotate the structure with repr(C)) but here it has little reason to touch anything so it's unsurprising that it does not.

    WebAssembly is a 32-bit architecture, so size_of::<usize>() == 4, therefore the pointer which is returned to the JS is 4 bytes before the location visible inside the Rust structure. QED.