Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experiment: Enable multi-value #1459

Draft
wants to merge 23 commits into
base: master
Choose a base branch
from
Draft

Experiment: Enable multi-value #1459

wants to merge 23 commits into from

Conversation

nomeata
Copy link
Collaborator

@nomeata nomeata commented May 2, 2020

drun now uses wasmtime-0.15, which supports multi-value.

We can test all that on branches. Likely, though, allowing multi-value needs to
be part of a Public Spec release, so this can’t be the default for a while.

@nomeata
Copy link
Collaborator Author

nomeata commented May 2, 2020

Ugh, we’d have to switch back to a fork of the wasm ocaml library that is not yet on Opam to get multi-value support. Not sure if it’s worth it.

@nomeata
Copy link
Collaborator Author

nomeata commented May 2, 2020

Maybe there is a benefit in not trying to use any not-yet-fully-approved wasm featues. Because image we support more than one (say, multi-value and bulk memory). Then we’d have to maintain our own merge of the wasm library.

Or we stop using the ocaml wasm library, for these reasons? It’s a reference implementation after all, and not necessarily a production ready, convenient-to-use, new features avaiable behind feature flags library… But it’s certainly convenient sometimes to use the wasm library.

@rossberg @ggreif what do you think?

@crusso
Copy link
Contributor

crusso commented May 2, 2020

What's the status on tail calls? I think that's actually a more important feature for us because of our cps conversion....

@nomeata
Copy link
Collaborator Author

nomeata commented May 3, 2020

Also some relevant more general discussion:
https://dfinity.slack.com/archives/CH4CADCJX/p1588453621167800

@rossberg
Copy link
Contributor

rossberg commented May 4, 2020

Multi-value is fully approved and merged into the official spec at this point. It's also in the wasm interpreter. I merely didn't think of creating a new opam package. Will do today.

@nomeata
Copy link
Collaborator Author

nomeata commented May 4, 2020

Thanks! I missed that it had been merged. Also, the README of https://github.com/WebAssembly/multi-value/ maybe ought to indicate that.

@rossberg
Copy link
Contributor

rossberg commented May 4, 2020

Good point. Added a note to the README.

@rossberg
Copy link
Contributor

rossberg commented May 4, 2020

@crusso, tail calls are still at stage 3, awaiting a second implementation besides V8.

nomeata added a commit that referenced this pull request May 4, 2020
will help with #1459
@nomeata nomeata mentioned this pull request May 4, 2020
nomeata added a commit that referenced this pull request May 4, 2020
will help with #1459
mergify bot added a commit that referenced this pull request May 4, 2020
will help with #1459

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
@ggreif
Copy link
Contributor

ggreif commented May 5, 2020

This needs a rebase now, to reduce the diffs. Also note #1467, which is in the works.

@nomeata
Copy link
Collaborator Author

nomeata commented May 5, 2020

Updated (I tend to avoid rebase these days)

@ghost ghost assigned nomeata May 5, 2020
@ghost ghost added the P3 low priority, resolve when there is time label May 5, 2020
@nomeata
Copy link
Collaborator Author

nomeata commented May 5, 2020

Nice, tests.run passes.
And so do the ic-ref-run tests…

nomeata added a commit that referenced this pull request May 6, 2020
inspired by the work in #1459 I figured it would be nice if Hydra runs
the tests in `tests/run-drun` separately with `drun` and `ic-ref-run`.
This way

 * We can see from the status list already if only one of the two break
   (e.g. after bumping dependencies or changing Wasm featurs)
 * We do less rebuilds
   (e.g. after bumping dependencies)
 * We have higher nix-level parallelism
   (relevant as `tests.run-drun` is one of the longest running tests)

Thanks to #1216 this only needs a small change to `default.nix`.
mergify bot added a commit that referenced this pull request May 8, 2020
* nix, tests: Split run-drun

inspired by the work in #1459 I figured it would be nice if Hydra runs
the tests in `tests/run-drun` separately with `drun` and `ic-ref-run`.
This way

 * We can see from the status list already if only one of the two break
   (e.g. after bumping dependencies or changing Wasm featurs)
 * We do less rebuilds
   (e.g. after bumping dependencies)
 * We have higher nix-level parallelism
   (relevant as `tests.run-drun` is one of the longest running tests)

Thanks to #1216 this only needs a small change to `default.nix`.

* nix: Simplify name handling for test derivations

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
@nomeata
Copy link
Collaborator Author

nomeata commented Aug 26, 2021

I was curious if recent updates to drun (in particular newer wasmtime) fixed this, but we still get

ingress Err: IC0505: Wasm module of canister rwlgt-iiaaa-aaaaa-aaaaa-cai is not valid: Failed to deserialize wasm module with Return types length should be 0 or 1

@osa1
Copy link
Contributor

osa1 commented Aug 26, 2021

I suspect the problem is parity-wasm, which is used for instrumentation. See paritytech/parity-wasm#292

@rossberg
Copy link
Contributor

Other than @osa1's contribution of trunc_sat instructions, the parity impl seems to have made practically no progress over the last two years. That's rather concerning for a dependency. :(

@nomeata
Copy link
Collaborator Author

nomeata commented Oct 22, 2021

The base tests fall over this code

let as_block_type : stack_type -> block_type = function
  | [] -> ValBlockType None
  | [t] -> ValBlockType (Some t)
  | _ -> raise (Invalid_argument "instrList block combinators do not support multi-value yet")

which I introduced in 2ad8eed, three days after I branched off this branch. This will require a bit of refactoring. Thinking out loud: With multi-value returns, we need to produce a VarBlockType here, and we need to register the type with the type tabe, so as_block_type becomes effectful with regard to the wasm module. Either we put the burden on compile.ml, passing a block_type to G.if_ etc, or we make the G.t abstraction keep track of which types need to be added to the environment.

incidentially, this raise is not triggered by tests in tests/run, only the base tests. That is not good.

@nomeata
Copy link
Collaborator Author

nomeata commented Oct 22, 2021

Fixed that raise and added a simple test that exercised it. Also in #2853.

nomeata added a commit that referenced this pull request Oct 22, 2021
These Wasm nodes need to be annotated with their return type, and if the
return type is more than nullary or unary, then this type needs to be
defined in the Wasm module type table.

This refactors the story around `if_` etc. to allow that:
`E.if_ env tys thn else` will register the types as needed

But in many places we know statically that there is only zero or one
return type. For them, `G.if0` and `G.if1` can be used for cleaner code
and to emphasize their “purity”.

Extracted from #1459, but I’d like to have it on master to prevent merge
conflicts in #1459 (which is a long-running branch since May 2020…)
@nomeata
Copy link
Collaborator Author

nomeata commented Oct 22, 2021

BTW, Claudio, do you (internally) already have canisters that use this? Becaues “multi-value compilation” is still an open issue in the Rust repo: rust-lang/rust#73755. It seems dangerous to turn on this feature if it isn’t fully supported by upstream rust already

mergify bot pushed a commit that referenced this pull request Oct 23, 2021
These Wasm nodes need to be annotated with their return type, and if the
return type is more than nullary or unary, then this type needs to be
defined in the Wasm module type table.

This refactors the story around `if_` etc. to allow that:
`E.if_ env tys thn else` will register the types as needed

But in many places we know statically that there is only zero or one
return type. For them, `G.if0` and `G.if1` can be used for cleaner code
and to emphasize their “purity”. In a similar vein, `G.loop0` is introduced;
since our loops never produce values on the stack, there is no `loop_` with
multi-value result.

Extracted from #1459, but I’d like to have it on master to prevent merge
conflicts in #1459 (which is a long-running branch since May 2020…)
@crusso
Copy link
Contributor

crusso commented Oct 24, 2021

BTW, Claudio, do you (internally) already have canisters that use this? Becaues “multi-value compilation” is still an open issue in the Rust repo: rust-lang/rust#73755. It seems dangerous to turn on this feature if it isn’t fully supported by upstream rust already

No, the only reason I was asking about multi-value is that I've been asked to support the 128 bit cycles API which appears to use multi-value returns for System functions. I assume the replica has some tests, but perhaps those were hand-crafted wasm, not rust compiled.

@nomeata
Copy link
Collaborator Author

nomeata commented Dec 12, 2021

Gave it another shot. Fun fact: tests.drun all succeed, but tests.perf fails in qr.mo with

ingress Err: IC0505: Wasm module of canister rwlgt-iiaaa-aaaaa-aaaaa-cai is not valid: Failed to deserialize wasm module with Invalid or unknown value type 34

The fact that this only shows up in perf means our test suite is incomplete. I found a simple reproducer, and added it to this PR. It seems that the replica does not yet accept multi-value files after all.

nomeata added a commit that referenced this pull request Dec 13, 2021
mergify bot pushed a commit that referenced this pull request Dec 13, 2021
nomeata added a commit that referenced this pull request Nov 10, 2022
So far, the IC does not support wasm using multi-value returns from
functions and blocks (see #1459). The backend was originally written
with that feature in mind, and later we added a work-around (module
`FakeMultiVal`).

For functions we work around it by storing multiple values in fixed
globals, and reading them after the call. No allocations needed.

For blocks with multiple values, however, we allocate a tuple (array) on the
heap, write the values there, and read them later. This adds unnecessary
heap churn and fills up memory faster.

So this PR uses the mechanism from the functions also for blocks.

Goes well with #3556
mergify bot pushed a commit that referenced this pull request Nov 12, 2022
So far, the IC does not support wasm using multi-value returns from functions and blocks (see #1459). The backend was originally written with that feature in mind, and later we added a work-around (module `FakeMultiVal`).

For functions we work around it by storing multiple values in fixed globals, and reading them after the call. No allocations needed.

For blocks with multiple values, however, we allocate a tuple (array) on the heap, write the values there, and read them later. This adds unnecessary heap churn and fills up memory faster.

So this PR uses the mechanism from the functions also for blocks.

Goes well with #3556

Here is a (particularly bad) example:

In
```motoko
func multi_value_blocks(n : Nat) : (Nat, Nat) {
  return (
    if (n == 0) {
      returns_tuple()
    } else {
      if (n == 1) {
        returns_tuple()
      } else {
        returns_tuple()
      }
   }
 );
};
```
Now no allocation happens (and all the reads and writes to the global variables are eliminated due to #3556):

```diff
   (func $multi_value_blocks (type 1) (param $clos i32) (param $n i32)
     local.get $n
     i32.const 0
     call $B_eq
-    if (result i32)  ;; label = @1
+    if  ;; label = @1
       i32.const 0
       call $returns_tuple
-      global.get 4
-      global.get 5
-      call $to_2_tuple
     else
       local.get $n
       i32.const 2
       call $B_eq
-      if (result i32)  ;; label = @2
+      if  ;; label = @2
         i32.const 0
         call $returns_tuple
-        global.get 4
-        global.get 5
-        call $to_2_tuple
       else
         i32.const 0
         call $returns_tuple
-        global.get 4
-        global.get 5
-        call $to_2_tuple
       end
     end
-    call $from_2_tuple
     return)
```
@nomeata
Copy link
Collaborator Author

nomeata commented Mar 6, 2023

The Haskell to WebAssembly story hasn't enabled multi-value yet due to llvm/llvm-project#59095. Worth keeping an eye on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla:agreed P3 low priority, resolve when there is time
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants