# Multi-value Extension ## Introduction ### Background * Currently, functions and instructions consume multiple operands but can produce at most one result - functions: `value* -> value?` - instructions: `value* -> value?` - blocks: `[] -> value?` * In a stack machine, these asymmetries are artifical restrictions - were imposed to simplify the initial WebAssembly release (multiple results deferred to post-MVP) - can easily be lifted by generalising to value* -> value* * Generalised semantics is well-understood - https://github.com/WebAssembly/spec/tree/master/papers/pldi2017.pdf * Semi-complete implementation of multiple results in V8 ### Motivation * Multiple return values for functions: - enable unboxing of tuples or structs returned by value - efficient compilation of multiple return values * Multiple results for instructions: - enable instructions producing several results (divmod, arithmetics with carry) * Inputs to blocks: - loop labels can have arguments - can represent phis on backward edges - enable future `pick` operator to cross block boundary - macro definability of instructons with inputs * `i32.select3` = `dup if ... else ... end` ## Examples ### Functions with multiple return Values A simple swap function. ```wasm (func $swap (param i32 i32) (result i32 i32) (get_local 1) (get_local 0) ) ``` An addition function returning an additional carry bit. ```wasm (func $add64_u_with_carry (param $i i64) (param $j i64) (param $c i32) (result i64 i32) (local $k i64) (set_local $k (i64.add (i64.add (get_local $i) (get_local $j)) (i64.extend_u/i32 (get_local $c))) ) (return (get_local $k) (i64.lt_u (get_local $k) (get_local $i))) ) ``` ### Instructions with multiple results * `iNN.divrem` : \[iNN iNN\] -> \[iNN iNN\] * `iNN.add_carry` : \[iNN iNN i32\] -> \[iNN i32\] * `iNN.sub_carry` : \[iNN iNN i32\] -> \[iNN i32\] * etc. ### Blocks with inputs Conditionally manipulating a stack operand without using a local. ```wasm (func $add64_u_saturated (param i64 i64) (result i64) ($i64.add_u_carry (get_local 0) (get_local 1) (i32.const 0)) (if (param i64) (result i64) (then (drop) (i64.const 0xffff_ffff_ffff_ffff)) ) ) ``` An iterative factorial function whose loop doesn't use locals, but uses arguments like phis. ```wasm (func $fac (param i64) (result i64) (i64.const 1) (get_local 0) (loop $l (param i64 i64) (result i64) (pick 1) (pick 1) (i64.mul) (pick 1) (i64.const 1) (i64.sub) (pick 0) (i64.const 0) (i64.gt_u) (br_if $l) (pick 1) (return) ) ) ``` Macro definition of an instruction expanding into an `if`. ``` i64.select3 = dup if (param i64 i64 i64 i32) (result i64) … select ... else … end ``` Macro expansion of `if` itself. ``` if (param t*) (result u*) A else B end = block (param t* i32) (result u*) block (param t* i32) (result t*) (br_if 0) B (br 1) end A end ``` ## Spec Changes ### Structure The structure of the language is mostly unaffected. The only changes are to the type syntax: * *resulttype* is generalised from \[*valtype*?\] to \[*valtype*\*\] * block types (in `block`, `loop`, `if` instructions) are generalised from *resulttype* to *functype* ### Validation Arity restrictions are removed: * no arity check is imposed for valid *functype* * all occurrences of superscript "?" are replaced with superscript "\*" (e.g. blocks, calls, return) Validation for block instructions is generalised: * The type of `block`, `loop`, and `if` is the *functype* \[t1\*\] -> \[t2\*\] given as the block type * The type of the label of `block` and `if` is \[t2\*\] * The type of the label of `loop` is \[t1\*\] ### Execution Nothing much needs to be done for multiple results: * replace all occurrences of superscript "?" with superscript "\*". The only non-mechanical change involves entering blocks with operands: * The operand values are popped of the stack, and pushed right back after the label. * See paper for formulation of formal reduction rules ### Binary Format The binary requires a change to allow function types as block types. That requires extending the current ad-hoc encoding to allow references to function types. * `blocktype` is extended to the following format: ``` blocktype ::= 0x40 => [] -> [] | t:valtype => [] -> [t] | ft:typeidx => ft ``` ### Text Format The text format is mostly unaffected, except that the syntax for block types is generalised: * `resulttype` is replaced with `blocktype`, whose syntax is ``` blocktype ::= vec(param) vec(result) ``` * `block`, `loop`, and `if` instructions contain a `blocktype` instead of `resulttype`. * The existing abbreviations for functions apply to block types. ### Soundness Proof The typing of administrative instructions need to be generalised, see the paper. ## Possible Alternatives and Extensions ### More Flexible Block and Function Types * Instead of inline function types, could use references to the type section - more bureaucracy in the semantics, but otherwise no problem * Could also allow both - inline function types are slightly more compact for one-off uses * Could even unify the encoding of block types with function types everywhere - allow inline types even for functions, for the same benefits ## Open Questions * Destructuring or reshuffling multiple values requires locals, is that enough? - could add `pick` instruction (generalised `dup`) - could add `let` instruction (if you squint, a generalised `swap`) - different use cases?