Files
wasmtime/crates/lightbeam
Alex Crichton 195bf0e29a Fully support multiple returns in Wasmtime (#2806)
* Fully support multiple returns in Wasmtime

For quite some time now Wasmtime has "supported" multiple return values,
but only in the mose bare bones ways. Up until recently you couldn't get
a typed version of functions with multiple return values, and never have
you been able to use `Func::wrap` with functions that return multiple
values. Even recently where `Func::typed` can call functions that return
multiple values it uses a double-indirection by calling a trampoline
which calls the real function.

The underlying reason for this lack of support is that cranelift's ABI
for returning multiple values is not possible to write in Rust. For
example if a wasm function returns two `i32` values there is no Rust (or
C!) function you can write to correspond to that. This commit, however
fixes that.

This commit adds two new ABIs to Cranelift: `WasmtimeSystemV` and
`WasmtimeFastcall`. The intention is that these Wasmtime-specific ABIs
match their corresponding ABI (e.g. `SystemV` or `WindowsFastcall`) for
everything *except* how multiple values are returned. For multiple
return values we simply define our own version of the ABI which Wasmtime
implements, which is that for N return values the first is returned as
if the function only returned that and the latter N-1 return values are
returned via an out-ptr that's the last parameter to the function.

These custom ABIs provides the ability for Wasmtime to bind these in
Rust meaning that `Func::wrap` can now wrap functions that return
multiple values and `Func::typed` no longer uses trampolines when
calling functions that return multiple values. Although there's lots of
internal changes there's no actual changes in the API surface area of
Wasmtime, just a few more impls of more public traits which means that
more types are supported in more places!

Another change made with this PR is a consolidation of how the ABI of
each function in a wasm module is selected. The native `SystemV` ABI,
for example, is more efficient at returning multiple values than the
wasmtime version of the ABI (since more things are in more registers).
To continue to take advantage of this Wasmtime will now classify some
functions in a wasm module with the "fast" ABI. Only functions that are
not reachable externally from the module are classified with the fast
ABI (e.g. those not exported, used in tables, or used with `ref.func`).
This should enable purely internal functions of modules to have a faster
calling convention than those which might be exposed to Wasmtime itself.

Closes #1178

* Tweak some names and add docs

* "fix" lightbeam compile

* Fix TODO with dummy environ

* Unwind info is a property of the target, not the ABI

* Remove lightbeam unused imports

* Attempt to fix arm64

* Document new ABIs aren't stable

* Fix filetests to use the right target

* Don't always do 64-bit stores with cranelift

This was overwriting upper bits when 32-bit registers were being stored
into return values, so fix the code inline to do a sized store instead
of one-size-fits-all store.

* At least get tests passing on the old backend

* Fix a typo

* Add some filetests with mixed abi calls

* Get `multi` example working

* Fix doctests on old x86 backend

* Add a mixture of wasmtime/system_v tests
2021-04-07 12:34:26 -05:00
..
2021-02-16 14:10:05 -08:00
2019-11-08 16:16:12 -06:00
2019-11-08 06:35:40 -08:00

Lightbeam

Lightbeam is an optimising one-pass streaming compiler for WebAssembly, intended for use in Wasmtime.

Quality of output

Already - with a very small number of relatively simple optimisation rules - Lightbeam produces surprisingly high-quality output considering how restricted it is. It even produces better code than Cranelift, Firefox or both for some workloads. Here's a very simple example, this recursive fibonacci function in Rust:

fn fib(n: i32) -> i32 {
    if n == 0 || n == 1 {
        1
    } else {
        fib(n - 1) + fib(n - 2)
    }
}

When compiled with optimisations enabled, rustc will produce the following WebAssembly:

(module
  (func $fib (param $p0 i32) (result i32)
    (local $l1 i32)
    (set_local $l1
      (i32.const 1))
    (block $B0
      (br_if $B0
        (i32.lt_u
          (get_local $p0)
          (i32.const 2)))
      (set_local $l1
        (i32.const 1))
      (loop $L1
        (set_local $l1
          (i32.add
            (call $fib
              (i32.add
                (get_local $p0)
                (i32.const -1)))
            (get_local $l1)))
        (br_if $L1
          (i32.gt_u
            (tee_local $p0
              (i32.add
                (get_local $p0)
                (i32.const -2)))
            (i32.const 1)))))
    (get_local $l1)))

Firefox's optimising compiler produces the following assembly (labels cleaned up somewhat):

fib:
  sub rsp, 0x18
  cmp qword ptr [r14 + 0x28], rsp
  jae stack_overflow
  mov dword ptr [rsp + 0xc], edi
  cmp edi, 2
  jae .Lelse
  mov eax, 1
  mov dword ptr [rsp + 8], eax
  jmp .Lreturn
.Lelse:
  mov dword ptr [rsp + 0xc], edi
  mov eax, 1
  mov dword ptr [rsp + 8], eax
.Lloop:
  mov edi, dword ptr [rsp + 0xc]
  add edi, -1
  call 0
  mov ecx, dword ptr [rsp + 8]
  add ecx, eax
  mov dword ptr [rsp + 8], ecx
  mov ecx, dword ptr [rsp + 0xc]
  add ecx, -2
  mov dword ptr [rsp + 0xc], ecx
  cmp ecx, 1
  ja .Lloop
.Lreturn:
  mov eax, dword ptr [rsp + 8]
  nop
  add rsp, 0x18
  ret

Cranelift with optimisations enabled produces similar:

fib:
  push   rbp
  mov    rbp, rsp
  sub    rsp, 0x20
  mov    qword ptr [rsp + 0x10], rdi
  mov    dword ptr [rsp + 0x1c], esi
  mov    eax, 1
  mov    dword ptr [rsp + 0x18], eax
  mov    eax, dword ptr [rsp + 0x1c]
  cmp    eax, 2
  jb     .Lreturn
  movabs rax, 0
  mov    qword ptr [rsp + 8], rax
.Lloop:
  mov    eax, dword ptr [rsp + 0x1c]
  add    eax, -1
  mov    rcx, qword ptr [rsp + 8]
  mov    rdx, qword ptr [rsp + 0x10]
  mov    rdi, rdx
  mov    esi, eax
  call   rcx
  mov    ecx, dword ptr [rsp + 0x18]
  add    eax, ecx
  mov    dword ptr [rsp + 0x18], eax
  mov    eax, dword ptr [rsp + 0x1c]
  add    eax, -2
  mov    dword ptr [rsp + 0x1c], eax
  mov    eax, dword ptr [rsp + 0x1c]
  cmp    eax, 1
  ja     .Lloop
.Lreturn:
  mov    eax, dword ptr [rsp + 0x18]
  add    rsp, 0x20
  pop    rbp
  ret

Whereas Lightbeam produces smaller code with far fewer memory accesses than both (and fewer blocks than Firefox's output):

fib:
  cmp  esi, 2
  mov  eax, 1
  jb   .Lreturn
  mov  eax, 1
.Lloop:
  mov  rcx, rsi
  add  ecx, 0xffffffff
  push rsi
  push rax
  push rax
  mov  rsi, rcx
  call fib
  add  eax, [rsp + 8]
  mov  rcx, [rsp + 0x10]
  add  ecx, 0xfffffffe
  cmp  ecx, 1
  mov  rsi, rcx
  lea  rsp, [rsp + 0x18]
  ja   .Lloop
.Lreturn:
  ret

Now obviously I'm not advocating for replacing Firefox's optimising compiler with Lightbeam since the latter can only really produce better code when receiving optimised WebAssembly (and so debug-mode or hand-written WebAssembly may produce much worse output). However, this shows that even with the restrictions of a streaming compiler it's absolutely possible to produce high-quality assembly output. For the assembly above, the Lightbeam output runs within 15% of native speed. This is paramount for one of Lightbeam's intended usecases for real-time systems that want good runtime performance but cannot tolerate compiler bombs.

Specification compliance

Lightbeam passes 100% of the specification test suite, but that doesn't necessarily mean that it's 100% specification-compliant. Hopefully as we run a fuzzer against it we can find any issues and get Lightbeam to a state where it can be used in production.

Getting involved

You can file issues in the Wasmtime issue tracker. If you want to get involved jump into the Bytecode Alliance Zulip and someone can direct you to the right place. I wish I could say "the most useful thing you can do is play with it and open issues where you find problems" but until it passes the spec suite that won't be very helpful.