WASM Memory.buffer empty after allocating Vec<u8>


#1

I want to allocate some memory (size first known at runtime) to eventually pass some data to wasm. However my code seems to break wasm. Any ideas?

Allocation code in Rust

#[no_mangle]
pub extern "C" fn malloc(size: usize) -> *const Vec<u8> {
    Box::into_raw(Box::new(vec![0;size]))
}

Javascript:

fetch('wasm.wasm')
    .then(response => response.arrayBuffer())
    .then(bytes => WebAssembly.instantiate(bytes, {}))
    .then(m => {
        let exports = m.instance.exports;
        let buffer = exports.memory.buffer;
        console.log("buffer", buffer); // usual ArrayBuffer
        let ptr_array = exports.malloc(16);
        console.log("ptr_array", ptr_array, buffer); // ArrayBuffer len=0
    });

#2

Your alloc looks strange and probably not what your wanting. see example that has been written previously.

exports.memory is fixed but buffer gets invalidated on memory growth. So each time you call a wasm function you have to assume it has.


#3

Why?

exports.memory is fixed but buffer gets invalidated on memory growth

Right. That fixes it. Thanks!

console.log("buffer", memory.buffer);
let ptr_array = exports.malloc(16);
console.log("ptr_array", ptr_array, memory.buffer);

Are you aware that your example does not respect this fact? It creates an Uint8Array on startup and never updates it.


#4

I ran into this pretty quickly when working with wasm. My current approach is something like this, where the rust code returns a buffer via get_result_buffer:

var $buffer_ptr = exports.get_result_buffer(BUFFER_SIZE);
var $buffer_view = new Int32Array(exports.memory.buffer, $buffer_ptr, BUFFER_SIZE);
function get_buffer_view() {
    if ($buffer_view.byteLength === 0) {
        $buffer_view = new Int32Array(exports.memory.buffer, $buffer_ptr, BUFFER_SIZE);
    }
    return $buffer_view;
}

Constantly checking the state of this in case the buffers have become detached feels like pointless boilerplate – is there any discussion of providing js types that handle this automatically? Is there a better approach to what I’m doing above?


#5

It is returning a pointer to what was the vector structure (no ABI so just raw memory outside rust.) Rather than pointer to first item (u8) that’s allocated on the heap.

Not something I have looked at recently. I know the cargo-web builder injects a callback to handle the growth; not sure if it would work once multi-threading gets added.


#6

Still trying to pass an u8 array to WASM. I found this git: https://gist.github.com/thomas-jeepe/ff938fe2eff616f7bbe4bd3dca91a550

#[repr(C)]
#[derive(Debug)]
pub struct JsBytes {
    ptr: u32,
    len: u32,
    cap: u32,
}

impl JsBytes {
    pub fn new(mut bytes: Vec<u8>) -> *mut JsBytes {
        let ptr = bytes.as_mut_ptr() as u32;
        let len = bytes.len() as u32;
        let cap = bytes.capacity() as u32;
        mem::forget(bytes);
        let boxed = Box::new(JsBytes { ptr, len, cap });
        Box::into_raw(boxed)
    }
}

For testing I return the first element of the array in the ‘consume’ method:

#[no_mangle]
pub extern "C" fn malloc(size: usize) -> *mut JsBytes {
    JsBytes::new(vec![0u8;size])
}

#[no_mangle]
pub extern "C" fn consume( ptr: *mut JsBytes ) -> u32 {
    let vector = unsafe {
        let boxed: Box<JsBytes> = Box::from_raw(ptr);
        Vec::from_raw_parts(boxed.ptr as *mut u8, boxed.len as usize, boxed.cap as usize)
    };
    vector[0] as u32
}

However I always receive a zero:

    console.log("buffer", memory.buffer);
    let ptr_array = exports.malloc(16);
    console.log("ptr_array", ptr_array);
    memory.buffer[ptr_array] = 31;
    console.log(exports.consume(ptr_array)); // 0 (should be 31)

What am I missing?


#7

Your not dereferencing. Without running (so possibly wrong)

const ptr_array = exports.malloc(16);
console.log("ptr_array", ptr_array);
const memu32 = new Uint32Array(memory.buffer, ptr_array, 1);
const ptr = memu32[0];
const memu8 = new Uint8Array(memory.buffer, ptr, 16);
memu8[0] = 31;
console.log(exports.consume(ptr_array));

#8

Oh my yes. It works and I understand now my misconception. Thanks!