rustwasm / wasm-bindgen

Facilitating high-level interactions between Wasm modules and JavaScript
https://rustwasm.github.io/docs/wasm-bindgen/
Apache License 2.0
7.65k stars 1.05k forks source link

How to expose a function to Javascript that takes a Uint32Array and returns a Uint32Array? #2402

Open githubaccount256 opened 3 years ago

githubaccount256 commented 3 years ago

Hello, sorry for the noob question but I am extremely new to WASM and this is the final step I need to get my code working, and I can't quite figure it out.

For my website I am looking to offload a very intensive computation to WASM, and I thought Rust would be a good fit for this. Basically, I would like to send a matrix from Javascript to my WASM library and have it process it and then return a new matrix.

I have already written all the Rust code that computes the matrix (in Rust I am using a Vec) and I have verified that it works. The only thing I need to do now is somehow figure out how to get Javascript to be able to send a Uint32Array to this function and then have it send back a Uint32Array back to Javascript.

Unfortunately, I am getting some errors. First I tried doing this:

#[wasm_bindgen]
pub fn calculate_matrix(pixels: &mut Vec<usize>) -> Vec<usize> {
  // elided
}

But that was giving me this compiler error:

error[E0277]: the trait bound `Vec<usize>: RefMutFromWasmAbi` is not satisfied
  --> src\lib.rs:23:1
   |
23 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^ the trait `RefMutFromWasmAbi` is not implemented for `Vec<usize>`

I thought maybe it was because it was only supported for u32 and not usize and that it's not allowed to be a mutable reference, so I had my function take a Vec<u32> instead and just clone the vec in the function back to usize, and that kind of worked, except now I'm getting a runtime error.

When I run wasm-pack build I get around 1000 pages of compiled WASM text output, and I have figured out that it is due to me trying to return a Vec<u32 from the function. If I remove the return value then it compiles successfully. For some reason it is not letting me return a Vec<u32>, but I'm not sure why. I thought that it would allow this but Javascript would just interpret it as a Uint32Array.

I was looking through the runtime compile logs and found these snippets:

Fatal: error in validating input
Error: failed to execute `wasm-opt`: exited with exit code: 1
  full command: "C:\\Users\\Admin\\AppData\\Local\\.wasm-pack\\wasm-opt-171374efd61df962\\wasm-opt.exe" "C:\\Users\\Admin\\Desktop\\wasm-rust\\pkg\\wasm_rust_bg.wasm" "-o" "C:\\Users\\Admin\\Desktop\\wasm-rust\\pkg\\wasm_rust_bg.wasm-opt.wasm" "-O"
To disable `wasm-opt`, add `wasm-opt = false` to your package metadata in your `Cargo.toml`.
githubaccount256 commented 3 years ago

Hmm, okay so I think what I might need to do is have the function take a &[u32] and return a Box<[u32]>, but that is still resulting in the same error in validating input runtime error. Even stranger, I found this page:

https://rustwasm.github.io/wasm-bindgen/reference/types/boxed-number-slices.html

And one of the code snippets gives me the same error:

#[wasm_bindgen]
pub fn return_boxed_number_slice() -> Box<[u32]> {
    (0..42).collect::<Vec<u32>>().into_boxed_slice()
}

Also results in me getting the same runtime error. Is it possible this used to be allowed but is now different and the docs haven't been updated? Or maybe I'm doing something else wrong?

githubaccount256 commented 3 years ago

Ah, it appears that was an unrelated bug (from here: https://github.com/rustwasm/wasm-pack/issues/886#issuecomment-667669802) and it has now compiled. Nice, progress!

alexcrichton commented 3 years ago

You should be able to return Vec<u32> from a function as well as take &[u32] as well as an argument. Additionally you can take/return js_sys::Uint32Array which should work to. Have your issues been fixed at this point or are you still loooking for help?

alpha027 commented 1 year ago

I am a newbie too and I encountered this problem, I wrote a dummy function to test but it did not work:

// Rust
#[wasm_bindgen]
pub fn my_dummy_function(
        num_samples: u32,
    ) -> Vec<u32>
{
  let my_array: Vec<u32> = vec![12; num_samples as usize];

  //Clone::clone(&my_array)
  my_array
}

In the frontend the rust module is loaded like this:

import('../wasm-test-pack/pkg/myDummyPackage_bg.wasm').then((response) => {
      this.myFirstResult = response.add_one(1);
      console.log("Test dummy add: " + this.myFirstResult); // Works fine !! returns 2
      console.log("Test dummy vector: " + response.my_dummy_function(1,1)); // Only works if I take two arguments
    });

The strange thing is after compiling the rust-wasm pack, the myDummyPackage.d.ts has the following exports: Note that the function add_one is correct and it's working while my_dummy_function takes two numbers when in rust it takes only one input.


export const memory: WebAssembly.Memory;
export function my_dummy_function(a: number, b: number): void;
export function add_one(a: number): number;
...

I am on ubuntu 22.04, rustc 1.71.0, I would really appreciate some help here :pray:  
Liamolucko commented 1 year ago

That's happening because you're importing the wrong file: you should be importing pkg/myDummyPackage.js, not pkg/myDummyPackage_bg.wasm.

pkg/myDummyPackage_bg.wasm is the underlying WebAssembly that gets created by rustc, and pkg/myDummyPackage.js is a JS wrapper for it created by wasm-bindgen. The reason that it needs to exist is that WebAssembly can only directly pass numbers to and from JS, which is what you're seeing in those TypeScript declarations; the JS wrapper then converts them to JS values (in this case, a Uint32Array).

alpha027 commented 1 year ago

Actually, when using the ".js" file I was not even able to get the result from the function add_one(a: number): number;, I also have not specified, but I use the angular framework. I use similar approach as this repo. When I use import and specify the ".js" file, this is what I get: Screenshot from 2023-07-24 07-31-44 When I import using the "wasm" file: Screenshot from 2023-07-24 07-30-41

I am actually not sure how much of the problem angular-related, also if there is another way to make the import using js that would fix the problem I would appreciate to know how :-)

daxpedda commented 1 year ago

The repo you linked instantiates the Wasm module by itself calling WebAssembly.instantiate(), if you gave it the JS file then the error message isn't surprising. The generated JS shim by wasm-bindgen exports a default function, which you should use to initialize the Wasm module.

Unfortunately I know nothing about Angular, but it would certainly help to see the code again you changed to get to where you are now.