Currently, the Wasm design explicitly forbids tail call optimisations
Want support to enable
Conceptually, tail-calling a function unwinds the current call frame before performing the actual call. This can be applied to any form of call, that is:
call_indirect)Tail calls are performed via separate, explicit call instructions (existing call instructions explicitly disallow TCE)
The proposal thus introduces a tail version of every call instruction
An alternative scheme introducing a single instruction prefix applicable to every call instruction was considered but rejected by the CG
call_refTail calls behave like a combination of return followed by a respective call
Hence they unwind the operand stack like return does
Only keeps the necessary call arguments
Tail calls to host functions cannot guarantee tail behaviour (outside the scope of the spec)
Tail calls across WebAssembly module boundaries do guarantee tail behavior
Typing rule for tail call instruction is derived by their nature of merging call and return
Because tail calls transfer control and unwind the stack they are stack-polymorphic
Previously open question: should tail calls induce different function types? Possibilities:
Considerations:
CG resolution was to go with option 4 as the conceptually simplest.
A simple boring example of a tail-recursive factorial funciton.
(func $fac (param $x i64) (result i64)
(return_call $fac-aux (get_local $x) (i64.const 1))
)
(func $fac-aux (param $x i64) (param $r i64) (result i64)
(if (i64.eqz (get_local $x))
(then (return (get_local $r)))
(else
(return_call $fac-aux
(i64.sub (get_local $x) (i64.const 1))
(i64.mul (get_local $x) (get_local $r))
)
)
)
)
Add two instructions:
return_call <funcidx>, the tail-call version of callreturn_call_indirect <tableidx> <typeidx>, the tail-call version of call_indirectOther language extensions like typed function references that introduce new call instructions will also introduce tail versions of these new instructions.
Validation of the new instructions is simply a combination of the typing rules for return and those for basic calls (and thus is stack-polymorphic).
If x refers to a function of type [t1*] -> [t2*], then the instruction return_call x has type [t3* t1*] -> [t4*], for any t3* and t4*, provided that the current function has return type [t2*].
If x refers to a function type [t1*] -> [t2*], then the instruction return_call_indirect x has type [t3* t1* i32] -> [t4*], for any t3* and t4*, provided that the current function has return type [t2*].
Note that caller‘s and callee’s parameter types do not need to match.
Execution semantics of the new instructions would
return doesUse the reserved opcodes after existing call instructions, i.e.:
return_call is 0x12return_call_indirect is 0x13The text format is extended with two new instructions in the obvious manner.