Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time

Multi-value Extension



  • 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

  • Semi-complete implementation of multiple results in V8


  • 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


Functions with multiple return Values

A simple swap function.

(func $swap (param i32 i32) (result i32 i32)
	(get_local 1) (get_local 0)

An addition function returning an additional carry bit.

(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.

(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.

(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

Spec Changes


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


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*]


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?