Skip to content
This repository was archived by the owner on Mar 3, 2025. It is now read-only.

Commit 8e82d8c

Browse files
committed
[spec/interpreter/test] Implement basic tail-call proposal
1 parent fd9dd22 commit 8e82d8c

File tree

20 files changed

+1017
-28
lines changed

20 files changed

+1017
-28
lines changed

document/core/binary/instructions.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ Control Instructions
3333
.. _binary-return:
3434
.. _binary-call:
3535
.. _binary-call_indirect:
36+
.. _binary-return_call:
37+
.. _binary-return_call_indirect:
3638

3739
.. math::
3840
\begin{array}{llclll}
@@ -54,15 +56,17 @@ Control Instructions
5456
&\Rightarrow& \BRTABLE~l^\ast~l_N \\ &&|&
5557
\hex{0F} &\Rightarrow& \RETURN \\ &&|&
5658
\hex{10}~~x{:}\Bfuncidx &\Rightarrow& \CALL~x \\ &&|&
57-
\hex{11}~~x{:}\Btypeidx~~\hex{00} &\Rightarrow& \CALLINDIRECT~x \\
59+
\hex{11}~~x{:}\Btypeidx~~\hex{00} &\Rightarrow& \CALLINDIRECT~x \\ &&|&
60+
\hex{12}~~x{:}\Bfuncidx &\Rightarrow& \RETURNCALL~x \\ &&|&
61+
\hex{13}~~x{:}\Btypeidx~~\hex{00} &\Rightarrow& \RETURNCALLINDIRECT~x \\
5862
\end{array}
5963
6064
.. note::
6165
The |ELSE| opcode :math:`\hex{05}` in the encoding of an |IF| instruction can be omitted if the following instruction sequence is empty.
6266

6367
.. note::
6468
In future versions of WebAssembly, the zero byte occurring in the encoding
65-
of the |CALLINDIRECT| instruction may be used to index additional tables.
69+
of the |CALLINDIRECT| and |RETURNCALLINDIRECT| instructions may be used to index additional tables.
6670

6771
.. index:: value type, polymorphism
6872
pair: binary format; instruction

document/core/exec/instructions.rst

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,82 @@ Control Instructions
934934
\end{array}
935935
936936
937+
.. _exec-return_call:
938+
939+
:math:`\RETURNCALL~x`
940+
.....................
941+
942+
1. Let :math:`F` be the :ref:`current <exec-notation-textual>` :ref:`frame <syntax-frame>`.
943+
944+
2. Assert: due to :ref:`validation <valid-call>`, :math:`F.\AMODULE.\MIFUNCS[x]` exists.
945+
946+
3. Let :math:`a` be the :ref:`function address <syntax-funcaddr>` :math:`F.\AMODULE.\MIFUNCS[x]`.
947+
948+
4. :ref:`Tail-invoke <exec-return-invoke>` the function instance at address :math:`a`.
949+
950+
951+
.. math::
952+
\begin{array}{lcl@{\qquad}l}
953+
(\RETURNCALL~x) &\stepto& (\RETURNINVOKE~a)
954+
& (\iff \CALL~x \stepto \INVOKE~a)
955+
\end{array}
956+
957+
958+
.. _exec-return_call_indirect:
959+
960+
:math:`\RETURNCALLINDIRECT~x`
961+
.............................
962+
963+
1. Let :math:`F` be the :ref:`current <exec-notation-textual>` :ref:`frame <syntax-frame>`.
964+
965+
2. Assert: due to :ref:`validation <valid-call_indirect>`, :math:`F.\AMODULE.\MITABLES[0]` exists.
966+
967+
3. Let :math:`\X{ta}` be the :ref:`table address <syntax-tableaddr>` :math:`F.\AMODULE.\MITABLES[0]`.
968+
969+
4. Assert: due to :ref:`validation <valid-call_indirect>`, :math:`S.\STABLES[\X{ta}]` exists.
970+
971+
5. Let :math:`\X{tab}` be the :ref:`table instance <syntax-tableinst>` :math:`S.\STABLES[\X{ta}]`.
972+
973+
6. Assert: due to :ref:`validation <valid-call_indirect>`, :math:`F.\AMODULE.\MITYPES[x]` exists.
974+
975+
7. Let :math:`\X{ft}_{\F{expect}}` be the :ref:`function type <syntax-functype>` :math:`F.\AMODULE.\MITYPES[x]`.
976+
977+
8. Assert: due to :ref:`validation <valid-call_indirect>`, a value with :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
978+
979+
9. Pop the value :math:`\I32.\CONST~i` from the stack.
980+
981+
10. If :math:`i` is not smaller than the length of :math:`\X{tab}.\TIELEM`, then:
982+
983+
a. Trap.
984+
985+
11. If :math:`\X{tab}.\TIELEM[i]` is uninitialized, then:
986+
987+
a. Trap.
988+
989+
12. Let :math:`a` be the :ref:`function address <syntax-funcaddr>` :math:`\X{tab}.\TIELEM[i]`.
990+
991+
13. Assert: due to :ref:`validation <valid-call_indirect>`, :math:`S.\SFUNCS[a]` exists.
992+
993+
14. Let :math:`\X{f}` be the :ref:`function instance <syntax-funcinst>` :math:`S.\SFUNCS[a]`.
994+
995+
15. Let :math:`\X{ft}_{\F{actual}}` be the :ref:`function type <syntax-functype>` :math:`\X{f}.\FITYPE`.
996+
997+
16. If :math:`\X{ft}_{\F{actual}}` and :math:`\X{ft}_{\F{expect}}` differ, then:
998+
999+
a. Trap.
1000+
1001+
17. :ref:`Tail-invoke <exec-return-invoke>` the function instance at address :math:`a`.
1002+
1003+
1004+
.. math::
1005+
\begin{array}{lcl@{\qquad}l}
1006+
(\RETURNCALLINDIRECT~x) &\stepto& (\RETURNINVOKE~a)
1007+
& (\iff \CALLINDIRECT~x \stepto \INVOKE~a) \\
1008+
(\RETURNCALLINDIRECT~x) &\stepto& \TRAP
1009+
& (\iff \CALLINDIRECT~x \stepto \TRAP) \\
1010+
\end{array}
1011+
1012+
9371013
.. index:: instruction, instruction sequence, block
9381014
.. _exec-instr-seq:
9391015

@@ -1044,6 +1120,42 @@ Invocation of :ref:`function address <syntax-funcaddr>` :math:`a`
10441120
\end{array}
10451121
10461122
1123+
.. _exec-return-invoke:
1124+
1125+
Tail-invocation of :ref:`function address <syntax-funcaddr>` :math:`a`
1126+
......................................................................
1127+
1128+
1. Assert: due to :ref:`validation <valid-call>`, :math:`S.\SFUNCS[a]` exists.
1129+
1130+
2. Let :math:`[t_1^m] \to [t_2^n]` be the :ref:`function type <syntax-functype>` :math:`S.\SFUNCS[a].\FITYPE`.
1131+
1132+
3. Assert: due to :ref:`validation <valid-return_call>`, there are at least :math:`m` values on the top of the stack.
1133+
1134+
4. Pop the results :math:`\val^m` from the stack.
1135+
1136+
5. Assert: due to :ref:`validation <valid-return_call>`, the stack contains at least one :ref:`frame <syntax-frame>`.
1137+
1138+
6. While the top of the stack is not a frame, do:
1139+
1140+
a. Pop the top element from the stack.
1141+
1142+
7. Assert: the top of the stack is a frame.
1143+
1144+
8. Pop the frame from the stack.
1145+
1146+
9. Push :math:`\val^m` to the stack.
1147+
1148+
10. :ref:`Invoke <exec-invoke>` the function instance at address :math:`a`.
1149+
1150+
.. math::
1151+
~\\[-1ex]
1152+
\begin{array}{lcl@{\qquad}l}
1153+
S; \FRAME_n\{F\}~B^*[\val^m~(\RETURNINVOKE~a)]~\END &\stepto&
1154+
\val^m~(\INVOKE~a)
1155+
& (\iff S.\SFUNCS[a].\FITYPE = [t_1^m] \to [t_2^n])
1156+
\end{array}
1157+
1158+
10471159
.. _exec-invoke-exit:
10481160

10491161
Returning from a function

document/core/exec/runtime.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,7 @@ In order to express the reduction of :ref:`traps <trap>`, :ref:`calls <syntax-ca
443443
\dots \\ &&|&
444444
\TRAP \\ &&|&
445445
\INVOKE~\funcaddr \\ &&|&
446+
\RETURNINVOKE~\funcaddr \\ &&|&
446447
\INITELEM~\tableaddr~\u32~\funcidx^\ast \\ &&|&
447448
\INITDATA~\memaddr~\u32~\byte^\ast \\ &&|&
448449
\LABEL_n\{\instr^\ast\}~\instr^\ast~\END \\ &&|&
@@ -454,6 +455,7 @@ Traps are bubbled up through nested instruction sequences, ultimately reducing t
454455

455456
The |INVOKE| instruction represents the imminent invocation of a :ref:`function instance <syntax-funcinst>`, identified by its :ref:`address <syntax-funcaddr>`.
456457
It unifies the handling of different forms of calls.
458+
Analogously, |RETURNINVOKE| represents the imminent tail invocation of a function instance.
457459

458460
The |INITELEM| and |INITDATA| instructions perform initialization of :ref:`element <syntax-elem>` and :ref:`data <syntax-data>` segments during module :ref:`instantiation <exec-instantiation>`.
459461

document/core/syntax/instructions.rst

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,9 @@ Instructions in this group affect the flow of control.
301301
\BRTABLE~\vec(\labelidx)~\labelidx \\&&|&
302302
\RETURN \\&&|&
303303
\CALL~\funcidx \\&&|&
304-
\CALLINDIRECT~\typeidx \\
304+
\CALLINDIRECT~\typeidx \\&&|&
305+
\RETURNCALL~\funcidx \\&&|&
306+
\RETURNCALLINDIRECT~\typeidx \\
305307
\end{array}
306308
307309
The |NOP| instruction does nothing.
@@ -344,9 +346,13 @@ The |CALLINDIRECT| instruction calls a function indirectly through an operand in
344346
Since tables may contain function elements of heterogeneous type |ANYFUNC|,
345347
the callee is dynamically checked against the :ref:`function type <syntax-functype>` indexed by the instruction's immediate, and the call aborted with a :ref:`trap <trap>` if it does not match.
346348

349+
The |RETURNCALL| and |RETURNCALLINDIRECT| instructions are *tail-call* variants of the previous ones.
350+
That is, they first return from the current function before actually performing the respective call.
351+
It is guaranteed that no sequence of nested calls using only these instructions can cause resource exhaustion due to hitting an :ref:`implementation's limit <impl-exec>` on the number of active calls.
352+
347353
.. note::
348354
In the current version of WebAssembly,
349-
|CALLINDIRECT| implicitly operates on :ref:`table <syntax-table>` :ref:`index <syntax-tableidx>` :math:`0`.
355+
|CALLINDIRECT| and |RETURNCALLINDIRECT| implicitly operate on :ref:`table <syntax-table>` :ref:`index <syntax-tableidx>` :math:`0`.
350356
This restriction may be lifted in future versions.
351357

352358

document/core/text/instructions.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ The same label identifier may optionally be repeated after the corresponding :ma
8383
.. _text-return:
8484
.. _text-call:
8585
.. _text-call_indirect:
86+
.. _text-return_call:
87+
.. _text-return_call_indirect:
8688

8789
All other control instruction are represented verbatim.
8890

@@ -98,6 +100,9 @@ All other control instruction are represented verbatim.
98100
\text{return} &\Rightarrow& \RETURN \\ &&|&
99101
\text{call}~~x{:}\Tfuncidx_I &\Rightarrow& \CALL~x \\ &&|&
100102
\text{call\_indirect}~~x,I'{:}\Ttypeuse_I &\Rightarrow& \CALLINDIRECT~x
103+
& (\iff I' = \{\}) \\ &&|&
104+
\text{return\_call}~~x{:}\Tfuncidx_I &\Rightarrow& \RETURNCALL~x \\ &&|&
105+
\text{return\_call\_indirect}~~x,I'{:}\Ttypeuse_I &\Rightarrow& \RETURNCALLINDIRECT~x
101106
& (\iff I' = \{\}) \\
102107
\end{array}
103108

document/core/util/macros.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@
313313
.. |RETURN| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return}}
314314
.. |CALL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{call}}
315315
.. |CALLINDIRECT| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{call\_indirect}}
316+
.. |RETURNCALL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return\_call}}
317+
.. |RETURNCALLINDIRECT| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return\_call\_indirect}}
316318

317319
.. |DROP| mathdef:: \xref{syntax/instructions}{syntax-instr-parametric}{\K{drop}}
318320
.. |SELECT| mathdef:: \xref{syntax/instructions}{syntax-instr-parametric}{\K{select}}
@@ -865,6 +867,7 @@
865867

866868
.. |TRAP| mathdef:: \xref{exec/runtime}{syntax-trap}{\K{trap}}
867869
.. |INVOKE| mathdef:: \xref{exec/runtime}{syntax-invoke}{\K{invoke}}
870+
.. |RETURNINVOKE| mathdef:: \xref{exec/runtime}{syntax-return_invoke}{\K{return\_invoke}}
868871
.. |INITELEM| mathdef:: \xref{exec/runtime}{syntax-init_elem}{\K{init\_elem}}
869872
.. |INITDATA| mathdef:: \xref{exec/runtime}{syntax-init_data}{\K{init\_data}}
870873

document/core/valid/instructions.rst

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,69 @@ Control Instructions
681681
}
682682
683683
684+
.. _valid-return_call:
685+
686+
:math:`\RETURNCALL~x`
687+
.....................
688+
689+
* The return type :math:`C.\CRETURN` must not be empty in the context.
690+
691+
* The function :math:`C.\CFUNCS[x]` must be defined in the context.
692+
693+
* Let :math:`[t_1^\ast] \to [t_2^?]` be the :ref:`function type <syntax-functype>` :math:`C.\CFUNCS[x]`.
694+
695+
* The :ref:`result type <syntax-resulttype>` must be the same as :math:`C.\CRETURN`.
696+
697+
* Then the instruction is valid with type :math:`[t_3^\ast~t_1^\ast] \to [t_4^\ast]`, for any sequences of :ref:`value types <syntax-valtype>` :math:`t_3^\ast` and :math:`t_4^\ast`.
698+
699+
.. math::
700+
\frac{
701+
C.\CFUNCS[x] = [t_1^\ast] \to [t_2^?]
702+
\qquad
703+
C.\CRETURN = [t_2^?]
704+
}{
705+
C \vdashinstr \CALL~x : [t_3^\ast~t_1^\ast] \to [t_4^\ast]
706+
}
707+
708+
.. note::
709+
The |RETURNCALL| instruction is :ref:`stack-polymorphic <polymorphism>`.
710+
711+
712+
.. _valid-return_call_indirect:
713+
714+
:math:`\RETURNCALLINDIRECT~x`
715+
.............................
716+
717+
* The return type :math:`C.\CRETURN` must not be empty in the context.
718+
719+
* The table :math:`C.\CTABLES[0]` must be defined in the context.
720+
721+
* Let :math:`\limits~\elemtype` be the :ref:`table type <syntax-tabletype>` :math:`C.\CTABLES[0]`.
722+
723+
* The :ref:`element type <syntax-elemtype>` :math:`\elemtype` must be |ANYFUNC|.
724+
725+
* The type :math:`C.\CTYPES[x]` must be defined in the context.
726+
727+
* Let :math:`[t_1^\ast] \to [t_2^?]` be the :ref:`function type <syntax-functype>` :math:`C.\CTYPES[x]`.
728+
729+
* Then the instruction is valid with type :math:`[t_3^\ast~t_1^\ast~\I32] \to [t_4^\ast]`, for any sequences of :ref:`value types <syntax-valtype>` :math:`t_3^\ast` and :math:`t_4^\ast`.
730+
731+
732+
.. math::
733+
\frac{
734+
C.\CTABLES[0] = \limits~\ANYFUNC
735+
\qquad
736+
C.\CTYPES[x] = [t_1^\ast] \to [t_2^?]
737+
\qquad
738+
C.\CRETURN = [t_2^?]
739+
}{
740+
C \vdashinstr \CALLINDIRECT~x : [t_3^\ast~t_1^\ast~\I32] \to [t_4^\ast]
741+
}
742+
743+
.. note::
744+
The |RETURNCALLINDIRECT| instruction is :ref:`stack-polymorphic <polymorphism>`.
745+
746+
684747
.. index:: instruction, instruction sequence
685748
.. _valid-instr-seq:
686749

interpreter/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ op:
214214
br_if <var>
215215
br_table <var>+
216216
return
217+
return_call <var>
218+
return_call_indirect <func_type>
217219
call <var>
218220
call_indirect <func_type>
219221
drop

interpreter/binary/decode.ml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,13 @@ let rec instr s =
246246
let x = at var s in
247247
expect 0x00 s "zero flag expected";
248248
call_indirect x
249+
| 0x12 -> return_call (at var s)
250+
| 0x13 ->
251+
let x = at var s in
252+
expect 0x00 s "zero flag expected";
253+
return_call_indirect x
249254

250-
| 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b
255+
| 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b
251256

252257
| 0x1a -> drop
253258
| 0x1b -> select

interpreter/binary/encode.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ let encode m =
157157
| Return -> op 0x0f
158158
| Call x -> op 0x10; var x
159159
| CallIndirect x -> op 0x11; var x; u8 0x00
160+
| ReturnCall x -> op 0x12; var x
161+
| ReturnCallIndirect x -> op 0x13; var x; u8 0x00
160162

161163
| Drop -> op 0x1a
162164
| Select -> op 0x1b

0 commit comments

Comments
 (0)