#binding-generator #bindgen #multi #value #javascript #wasm-bindgen #transformation #js #multi-value #interactions

wasm-bindgen-multi-value-xform

Internal multi-value transformations for wasm-bindgen

34 releases

0.2.84 Feb 1, 2023
0.2.83 Sep 12, 2022
0.2.82 Jul 25, 2022
0.2.79 Jan 19, 2022
0.2.55 Nov 19, 2019

#108 in WebAssembly

Download history 6888/week @ 2022-12-02 6215/week @ 2022-12-09 7781/week @ 2022-12-16 4581/week @ 2022-12-23 7384/week @ 2022-12-30 7324/week @ 2023-01-06 8194/week @ 2023-01-13 6732/week @ 2023-01-20 9546/week @ 2023-01-27 9230/week @ 2023-02-03 9374/week @ 2023-02-10 9044/week @ 2023-02-17 8251/week @ 2023-02-24 9535/week @ 2023-03-03 9173/week @ 2023-03-10 8886/week @ 2023-03-17

37,642 downloads per month
Used in 23 crates (via wasm-bindgen-cli-support)

MIT/Apache

13KB
188 lines

The wasm-bindgen multi-value transformation.

This crate provides a transformation to turn exported functions that use a return pointer into exported functions that use multi-value.

Consider the following function:

#[no_mangle]
pub extern "C" fn pair(a: u32, b: u32) -> [u32; 2] {
    [a, b]
}

LLVM will by default compile this down into the following Wasm:

(func $pair (param i32 i32 i32)
  local.get 0
  local.get 2
  i32.store offset=4
  local.get 0
  local.get 1
  i32.store)

What's happening here is that the function is not directly returning the pair at all, but instead the first i32 parameter is a pointer to some scratch space, and the return value is written into the scratch space. LLVM does this because it doesn't yet have support for multi-value Wasm, and so it only knows how to return a single value at a time.

Ideally, with multi-value, what we would like instead is this:

(func $pair (param i32 i32) (result i32 i32)
  local.get 0
  local.get 1)

However, that's not what this transformation does at the moment. This transformation is a little simpler than mutating existing functions to produce a multi-value result, instead it introduces new functions that wrap the original function and translate the return pointer to multi-value results in this wrapper function.

With our running example, we end up with this:

;; The original function.
(func $pair (param i32 i32 i32)
  local.get 0
  local.get 2
  i32.store offset=4
  local.get 0
  local.get 1
  i32.store)

(func $pairWrapper (param i32 i32) (result i32 i32)
  ;; Our return pointer that points to the scratch space we are allocating
  ;; on the shadow stack for calling `$pair`.
  (local i32)

  ;; Allocate space on the shadow stack for the result.
  global.get $shadowStackPointer
  i32.const 8
  i32.sub
  local.tee 2
  global.set $shadowStackPointer

  ;; Call `$pair` with our allocated shadow stack space for its results.
  local.get 2
  local.get 0
  local.get 1
  call $pair

  ;; Copy the return values from the shadow stack to the wasm stack.
  local.get 2
  i32.load
  local.get 2 offset=4
  i32.load

  ;; Finally, restore the shadow stack pointer.
  local.get 2
  i32.const 8
  i32.add
  global.set $shadowStackPointer)

This $pairWrapper function is what we actually end up exporting instead of $pair.

Dependencies

~3MB
~63K SLoC