JavaScript string is empty when passed to Rust WebAssembly module

心不动则不痛 提交于 2021-01-29 12:30:54

问题


When passing a string to a Rust WASM module, the passed data shows up as blank, as per the pattern matching in the real_code::compute function

The following code is what I've tried. I don't know if it has to do with how its being returned, but when I pass a hardcoded &str, it works fine. However, the JsInteropString shows as blank.

Here is how I encoded the string before sending it to WASM (from Passing a JavaScript string to a Rust function compiled to WebAssembly)

const memory = new WebAssembly.Memory({ initial: 20, 
                                        maximum: 100 });

const importObject = {
  env: { memory }
};

const memoryManager = (memory) => {
  var base = 0;

  // NULL is conventionally at address 0, so we "use up" the first 4
  // bytes of address space to make our lives a bit simpler.
  base += 4;

  return {
    encodeString: (jsString) => {
      // Convert the JS String to UTF-8 data
      const encoder = new TextEncoder();
      const encodedString = encoder.encode(jsString);

      // Organize memory with space for the JsInteropString at the
      // beginning, followed by the UTF-8 string bytes.
      const asU32 = new Uint32Array(memory.buffer, base, 2);
      const asBytes = new Uint8Array(memory.buffer, asU32.byteOffset + asU32.byteLength, encodedString.length);

      // Copy the UTF-8 into the WASM memory.
      asBytes.set(encodedString);

      // Assign the data pointer and length values.
      asU32[0] = asBytes.byteOffset;
      asU32[1] = asBytes.length;

      // Update our memory allocator base address for the next call
      const originalBase = base;
      base += asBytes.byteOffset + asBytes.byteLength;

      return originalBase;
    }
  };
};

invoking wasm like this:

//...loading and compiling WASM, getting instance from promise (standard)
const testStr = "TEST"
const input = myMemory.encodeString(testStr);
const offset = instance.exports.func_to_call(input);
// A struct with a known memory layout that we can pass string information in
#[repr(C)]
pub struct JsInteropString {
    data: *const u8,
    len: usize,
}

// Our FFI shim function
#[no_mangle]
pub unsafe extern "C" fn func_to_call(s: *const JsInteropString) -> *mut c_char {
    // ... check for nulls etc

    /*
    THROWS ERROR
    error[E0609]: no field `data` on type `*const JsInteropString`
    */
    //////let data = std::slice::from_raw_parts(s.data, s.len);

    //this fixes the above error
    let data = std::slice::from_raw_parts((*s).data, (*s).len);

    let dataToPrint: Result<_, _> = std::str::from_utf8(data);

    let real_res: &str = match dataToPrint {
        Ok(s) => real_code::compute(dataToPrint.unwrap()), //IS BLANK
        //Ok(s) => real_code::compute("SUCCESS"), //RETURNS "SUCCESS made it"
        Err(_) => real_code::compute("ERROR"),
    };

    unsafe {
        let s = CString::new(real_res).unwrap();
        println!("result: {:?}", &s);
        s.into_raw()
    }
}

mod real_code {
    pub fn compute(operator: &str) -> &str {
        match operator {
            "SUCCESS" => "SUCCESS made it",
            "ERROR" => "ERROR made it",
            "TEST" => "TEST made it",
            _ => operator,
        }
    }
}

When the function is called from JavaScript, it should return the same string passed. I even went as far as to update my Rust environment with rustup... any ideas?

UPDATE

Using a more representative version of the referenced post:

#[no_mangle]
pub unsafe extern "C" fn compute(s: *const JsInteropString) -> i32 {

    let s = match s.as_ref() {
        Some(s) => s,
        None => return -1,
    };

    // Convert the pointer and length to a `&[u8]`.
    let data = std::slice::from_raw_parts(s.data, s.len);

    // Convert the `&[u8]` to a `&str`    
    match std::str::from_utf8(data) {
        Ok(s) => real_code::compute(s),
        Err(_) => -2,
    }

}

mod real_code {
    pub fn compute(operator: &str) -> i32 {
        match operator {
            "SUCCESS"  => 1,
            "ERROR" => 2,
            "TEST" => 3,
            _ => 10,
        }
    }
}

regardless on the string passed through the js encoding, it still returns the default value from compute. I don't know if the referenced post actually solves the problem.

So yes, the code compiles. It is a runtime issue I am trying to solve. Something is off about how the string is being encoded and passed to WASM.

Update 2

Tried switching my toolchain to Nightly, and what I also found is that the following macros/attributes throw an error regarding unstable attributes

#![feature(wasm_import_memory)]
#![wasm_import_memory]

After realizing that I need to tell rust that memory will be imported, this seems to have fixed the problem of the string being passed as empty. It wasn't empty, it wasnt even passed it seems like.

Inside the .cargo file

[target.wasm32-unknown-unknown]
rustflags = [
    "-Clink-args=-s EXPORTED_FUNCTIONS=['_func_to_call'] -s ASSERTIONS=1",
    "-C", "link-args=--import-memory",
]

This seems to may have done the trick! @Shepmaster, update your answer from last year for others who stumble across it, as it has good SEO. This is due to changes in the rust environment.

来源:https://stackoverflow.com/questions/56248080/javascript-string-is-empty-when-passed-to-rust-webassembly-module

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!