-
Currently, functions and instructions consume multiple operands but can produce at most one result
- functions:
value* -> value?
- instructions:
value* -> value?
- blocks:
[] -> value?
- functions:
-
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
A simple swap function.
(func $swap (param i32 i32) (result i32 i32)
(local.get 1) (local.get 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)
(local.set $k
(i64.add (i64.add (local.get $i) (local.get $j)) (i64.extend_i32_u (local.get $c)))
)
(return (local.get $k) (i64.lt_u (local.get $k) (local.get $i)))
)
iNN.divrem
: [iNN iNN] -> [iNN iNN]iNN.add_carry
: [iNN iNN i32] -> [iNN i32]iNN.sub_carry
: [iNN iNN i32] -> [iNN i32]- etc.
Conditionally manipulating a stack operand without using a local.
(func $add64_u_saturated (param i64 i64) (result i64)
(call $add64_u_with_carry (local.get 0) (local.get 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 $pick0 (param i64) (result i64 i64)
(local.get 0) (local.get 0)
)
(func $pick1 (param i64 i64) (result i64 i64 i64)
(local.get 0) (local.get 1) (local.get 0)
)
(func $fac (param i64) (result i64)
(i64.const 1) (local.get 0)
(loop $l (param i64 i64) (result i64)
(call $pick1) (call $pick1) (i64.mul)
(call $pick1) (i64.const 1) (i64.sub)
(call $pick0) (i64.const 0) (i64.gt_u)
(br_if $l)
(call $pick1) (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
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
, andif
is the functype [t1*] -> [t2*] given as the block type - The type of the label of
block
andif
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
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
The text format is mostly unaffected, except that the syntax for block types is generalised:
-
resulttype
is replaced withblocktype
, whose syntax isblocktype ::= vec(param) vec(result)
-
block
,loop
, andif
instructions contain ablocktype
instead ofresulttype
. -
The existing abbreviations for functions apply to block types.
The typing of administrative instructions need to be generalised, see the paper.
-
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
- Destructuring or reshuffling multiple values requires locals, is that enough?
- could add
pick
instruction (generaliseddup
) - could add
let
instruction (if you squint, a generalisedswap
) - different use cases?
- could add