0

I was trying to find a way to export vector or other heap alocated data to Javascript. I tried this:

use wasm_bindgen::prelude::*;
use std::alloc::{Layout, dealloc};

#[wasm_bindgen]
pub struct ArrayBuffer {
    ptr: *const u8,
    len: usize,
}

#[wasm_bindgen]
impl ArrayBuffer {
    #[wasm_bindgen(getter)]
    pub fn ptr(&self) -> *const u8 {
        self.ptr
    }

    #[wasm_bindgen(getter)]
    pub fn len(&self) -> usize {
        self.len
    }
}

#[wasm_bindgen]
pub fn get_array() -> ArrayBuffer {
    let data = vec![1, 2, 3, 4, 5];
    let ptr = data.as_ptr();
    let len = data.len();

    std::mem::forget(data);

    ArrayBuffer { ptr, len }
}

#[wasm_bindgen]
pub fn free_array_raw(ptr: *mut u8, len: usize) {
    unsafe {
        let mut v = Vec::from_raw_parts(ptr, len, len);
        v.fill(0); 
        drop(v);  
    }
}

The I compile with wasm-pack targetting nodejs

wasm-pack build --target nodejs

Here is my Javascript code I used to test it

const wasm = require('./pkg/wasm.js');

async function run() {
  const arrayBuffer = wasm.get_array();
  const ptr = arrayBuffer.ptr;
  const len = arrayBuffer.len;

  const memory = wasm.__wasm.memory;

  const uint8Array = new Uint8Array(memory.buffer, ptr, len);

  console.log(uint8Array);

  arrayBuffer.free();
  wasm.free_array_raw(ptr, len);
  
  console.log(uint8Array);
}

run().catch(console.error);

The memory does not get deallocated, I still can access the data

[root@localhost wasm]# node ./main.js
Uint8Array(5) [ 1, 2, 3, 4, 5 ]
Uint8Array(5) [ 1, 2, 3, 4, 5 ]

How is the correct way to deallocate the heap memory?

5
  • What do you expect the second print to show? Sounds like a fundamental misunderstanding as to what "freeing memory" means. Normally it does not alter what has been written. See this C question for instance. Commented May 24 at 20:47
  • Ah thank you. So I just need to make uint8Array become null right? Also do you know how to properly free without creating vector first? Because I feel like creating vector before freeing memory is doing unnecerery operation, but I can't find other example that directly freeing the pointer memory Commented May 24 at 20:58
  • Your questions don't make sense to me. Let me try rephrasing - your code is deallocating memory correctly but accessing the memory through uint8Array after calling free_array_raw was improper. You are reading memory that was freed. Commented May 24 at 21:33
  • Yeah I have successfully understand the deallocation part, so I just need to assign null to uint8Array variable after the free right to make Javascript can't access it again. And then in this part: #[wasm_bindgen] pub fn free_array_raw(ptr: *mut u8, len: usize) { unsafe { let mut v = Vec::from_raw_parts(ptr, len, len); v.fill(0); drop(v); } } Do you know example to free the memory without creating a vector first? Commented May 24 at 21:48
  • That is the correct way to deallocate - Vec::from_raw_parts is a trivial call Commented May 24 at 21:56

1 Answer 1

2

In WebAssembly (wasm), memory is managed manually (Linear memory is a continuous buffer of unsigned bytes that can be read from and stored into by both Wasm and Javascript),it does not automatically deallocate anything for you, and memory.buffer is just a reference to the whole linear memory, which stays alive as long as the WebAssembly instance exists.

You can't deallocate memory.buffer from JavaScript. It's managed by the Wasm instance and will stay alive for the lifetime of that instance. If you want to free specific memory regions inside that buffer, you need to Export and call a custom deallocation function, if your Wasm module provides it. you might try to use __wbindgen_free:

import * as wasm from './foo_bg';

function passStringToWasm(arg) {
 const buf = new TextEncoder('utf-8').encode(arg);
 const len = buf.length;
 const ptr = wasm.__wbindgen_malloc(len, 1);
 let array = new Uint8Array(wasm.memory.buffer);
 array.set(buf, ptr);
 return [ptr, len];
}

function getStringFromWasm(ptr, len) {
 const mem = new Uint8Array(wasm.memory.buffer);
 const slice = mem.slice(ptr, ptr + len);
 const ret = new TextDecoder('utf-8').decode(slice);
 return ret;
}

export function greet(arg0) {
 const [ptr0, len0] = passStringToWasm(arg0);
 try {
   const ret = wasm.greet(ptr0, len0);
   const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
   const len = wasm.__wbindgen_boxed_str_len(ret);
   const realRet = getStringFromWasm(ptr, len);
   wasm.__wbindgen_boxed_str_free(ret);
   return realRet;
 } finally {
   wasm.__wbindgen_free(ptr0, len0, 1);
 }
}

[...]

  • Some cleanup ends up happening in wasm. The __wbindgen_boxed_str_free function is used to free the return value of greet after it's been decoded onto the JS heap (using TextDecoder). The __wbindgen_free is then used to free the space we allocated to pass the string argument once the function call is done.
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.