WASM from Rust not returning the expected types

Hey @all I was playing with WebAssembly Studio and created an empty Rust project.

In my Rust code, I'm returning the pointer and the length for my "Hello World" string. Compiling worked fine, but I was expecting the resulting WASM to have a function returning two i32. But I found a function taking one i32 and returning nothing.

  1. Why is the function not having the signature fn () -> (i32,i32) ?

  2. How am I supposed to extract the two values from the WASM module? (Using Rust-wasmtime)

Below you can find the code snippets I'm talking about. Thanks in advance!

#[no_mangle]
pub extern "C" fn say() -> (*const u8,i32)  {
    let pointcut = "Hello World";

    (pointcut.as_ptr(),11)
}
(module
  (type $t0 (func (param i32)))
  (func $say (export "say") (type $t0) (param $p0 i32)
    get_local $p0
    i32.const 11
    i32.store offset=4
    get_local $p0
    i32.const 1024
    i32.store)
  (table $T0 1 1 anyfunc)
  (memory $memory (export "memory") 17)
  (data (i32.const 1024) "Hello World"))

WebAssembly got the ability to return multiple values just recently. The compiler used doesn't seem to support this yet or doesn't use it for compatibility reasons, i.e. for runtimes that don't know this feature yet.

Therefore, the compiler rewrites the code as follows:

#[no_mangle]
pub extern "C" fn say(output: &mut (*const u8, i32)) {
    let pointcut = "Hello World";

    output.1 = 11;
    output.0 = pointcut.as_ptr();
}

At least this leads to the same WebAssembly code as your code. The parameter output corresponds to $p0 in the WebAssembly code.

The WebAssembly code now does the following:

First, the number 11 is written to the second item in the tuple, so at the memory address of output + 4. The offset is 4 bytes because the first i32 of the tuple has 4 bytes.

Second, the memory address of the string pointcut is written to the first value of the tuple. As you can see in the data section of the generated WebAssembly code, the string was put into linear memory at memory address 1024. Note: pointcut is a string literal and therefore static, so it does not live on the stack, which would be illegal (as already commented by Coder-256) and would result in a compiler error. EDIT: No compile error because of raw pointer. Lifetime checks are only performed for references.

So to get the two values, you have to allocate 8 bytes of memory (for the two i32) in linear memory, call the function with a pointer to this memory area, and then read both values. It may be more convenient to split the function into two separate functions, where one returns the pointer and the other the length. At least, this would make extracting the values easier because both functions would return a single i32.

EDIT: I found an article from Mozilla which also explains my thoughts in the section about "wasm-bindgen".