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

Commit 28258c4

Browse files
committed
[spec] Introduce annotated select and bot type
1 parent 2364a56 commit 28258c4

File tree

11 files changed

+121
-95
lines changed

11 files changed

+121
-95
lines changed

document/core/appendix/algorithm.rst

Lines changed: 69 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,29 @@ Data Structures
2222
~~~~~~~~~~~~~~~
2323

2424
Types are representable as an enumeration.
25-
In addition to the plain types from the WebAssembly validation semantics, an extended notion of operand type includes a bottom type `Unknown` that is used as the type of :ref:`polymorphic <polymorphism>` operands.
26-
2725
A simple subtyping check can be defined on these types.
28-
In addition, corresponding functions computing the join (least upper bound) and meet (greatest lower bound) of two types are used.
29-
Because there is no top type, a join does not exist in all cases.
30-
Similarly, a meet must always be defined on known value types that exclude the auxiliary bottom type `Unknown`,
31-
hence a meet does not exist in all cases either.
32-
A type error is encountered if a join or meet is required when it does not exist.
3326

3427
.. code-block:: pseudo
3528
36-
type val_type = I32 | I64 | F32 | F64 | Anyref | Funcref | Nullref
37-
type opd_type = val_type | Unknown
29+
type val_type = I32 | I64 | F32 | F64 | Anyref | Funcref | Nullref | Bot
30+
31+
func is_num(t : val_type) : bool =
32+
return t = I32 || t = I64 || t = F32 || t = F64 || t = Bot
3833
39-
func is_ref(t : opd_type) : bool =
40-
return t = Anyref || t = Funcref || t = Nullref
34+
func is_ref(t : val_type) : bool =
35+
return t = Anyref || t = Funcref || t = Nullref || t = Bot
4136
42-
func matches(t1 : opd_type, t2 : opd_type) : bool =
43-
return t1 = t2 || t1 = Unknown ||
37+
func matches(t1 : val_type, t2 : val_type) : bool =
38+
return t1 = t2 || t1 = Bot ||
4439
(t1 = Nullref && is_ref(t2)) || (is_ref(t1) && t2 = Anyref)
4540
46-
func join(t1 : opd_type, t2 : opd_type) : opd_type =
47-
if (t1 = t2) return t1
48-
if (matches(t1, t2)) return t2
49-
if (matches(t2, t1)) return t1
50-
error_if(not (is_ref(t1) && is_ref(t2)))
51-
return Anyref
52-
53-
func meet(t1 : val_type, t2 : val_type) : val_type =
54-
if (t1 = t2) return t1
55-
if (matches(t1, t2)) return t1
56-
if (matches(t2, t1)) return t2
57-
error_if(not (is_ref(t1) && is_ref(t2)))
58-
return Nullref
59-
60-
The algorithm uses two separate stacks: the *operand stack* and the *control stack*.
41+
The algorithm uses two separate stacks: the *value stack* and the *control stack*.
6142
The former tracks the :ref:`types <syntax-valtype>` of operand values on the :ref:`stack <stack>`,
6243
the latter surrounding :ref:`structured control instructions <syntax-instr-control>` and their associated :ref:`blocks <syntax-instr-control>`.
6344

6445
.. code-block:: pseudo
6546
66-
type opd_stack = stack(opd_type)
47+
type val_stack = stack(val_type)
6748
6849
type ctrl_stack = stack(ctrl_frame)
6950
type ctrl_frame = {
@@ -73,7 +54,7 @@ the latter surrounding :ref:`structured control instructions <syntax-instr-contr
7354
unreachable : bool
7455
}
7556
76-
For each value, the operand stack records its :ref:`value type <syntax-valtype>`, or :code:`Unknown` when the type is not known.
57+
For each value, the value stack records its :ref:`value type <syntax-valtype>`.
7758

7859
For each entered block, the control stack records a *control frame* with the type of the associated :ref:`label <syntax-label>` (used to type-check branches), the result type of the block (used to check its result), the height of the operand stack at the start of the block (used to check that operands do not underflow the current block), and a flag recording whether the remainder of the block is unreachable (used to handle :ref:`stack-polymorphic <polymorphism>` typing after branches).
7960

@@ -85,63 +66,68 @@ For the purpose of presenting the algorithm, the operand and control stacks are
8566

8667
.. code-block:: pseudo
8768
88-
var opds : opd_stack
69+
var vals : val_stack
8970
var ctrls : ctrl_stack
9071
9172
However, these variables are not manipulated directly by the main checking function, but through a set of auxiliary functions:
9273

9374
.. code-block:: pseudo
9475
95-
func push_opd(type : opd_type) =
96-
opds.push(type)
76+
func push_val(type : val_type) =
77+
vals.push(type)
9778
98-
func pop_opd() : opd_type =
99-
if (opds.size() = ctrls[0].height && ctrls[0].unreachable) return Unknown
100-
error_if(opds.size() = ctrls[0].height)
101-
return opds.pop()
79+
func pop_val() : val_type =
80+
if (vals.size() = ctrls[0].height && ctrls[0].unreachable) return Bot
81+
error_if(vals.size() = ctrls[0].height)
82+
return vals.pop()
10283
103-
func pop_opd(expect : val_type) =
104-
let actual = pop_opd()
84+
func pop_val(expect : val_type) : val_type =
85+
let actual = pop_val()
10586
error_if(not matches(actual, expect))
87+
return actual
10688
107-
func push_opds(types : list(val_type)) = foreach (t in types) push_opd(t)
108-
func pop_opds(types : list(val_type)) = foreach (t in reverse(types)) pop_opd(t)
89+
func push_vals(types : list(val_type)) = foreach (t in types) push_val(t)
90+
func pop_vals(types : list(val_type)) : list(val_type) =
91+
var popped := []
92+
foreach (t in reverse(types)) popped.append(pop_val(t))
93+
return popped
10994
110-
Pushing an operand simply pushes the respective type to the operand stack.
95+
Pushing an operand value simply pushes the respective type to the value stack.
11196

112-
Popping an operand checks that the operand stack does not underflow the current block and then removes one type.
113-
But first, a special case is handled where the block contains no known operands, but has been marked as unreachable.
97+
Popping an operand value checks that the value stack does not underflow the current block and then removes one type.
98+
But first, a special case is handled where the block contains no known values, but has been marked as unreachable.
11499
That can occur after an unconditional branch, when the stack is typed :ref:`polymorphically <polymorphism>`.
115-
In that case, an unknown type is returned.
100+
In that case, the :code:`Bot` type is returned.
116101

117-
A second function for popping an operand takes an expected (known) type, which the actual operand type is checked against.
118-
The types may differ by subtyping, inlcuding the case where the actual type is unknown.
102+
A second function for popping an operand value takes an expected type, which the actual operand type is checked against.
103+
The types may differ by subtyping, including the case where the actual type is :code:`Bot`, and thereby matches unconditionally.
104+
The function returns the actual type popped from the stack.
119105

120106
Finally, there are accumulative functions for pushing or popping multiple operand types.
121107

122108
.. note::
123109
The notation :code:`stack[i]` is meant to index the stack from the top,
124-
so that :code:`ctrls[0]` accesses the element pushed last.
110+
so that, e.g., :code:`ctrls[0]` accesses the element pushed last.
125111

126112

127113
The control stack is likewise manipulated through auxiliary functions:
128114

129115
.. code-block:: pseudo
130116
131117
func push_ctrl(label : list(val_type), out : list(val_type)) =
132-
 let frame = ctrl_frame(label, out, opds.size(), false)
118+
 let frame = ctrl_frame(label, out, vals.size(), false)
133119
  ctrls.push(frame)
134120
135121
func pop_ctrl() : list(val_type) =
136122
 error_if(ctrls.is_empty())
137123
 let frame = ctrls[0]
138-
  pop_opds(frame.end_types)
139-
  error_if(opds.size() =/= frame.height)
124+
  pop_vals(frame.end_types)
125+
  error_if(vals.size() =/= frame.height)
140126
ctrls.pop()
141127
  return frame.end_types
142128
143129
func unreachable() =
144-
  opds.resize(ctrls[0].height)
130+
  vals.resize(ctrls[0].height)
145131
  ctrls[0].unreachable := true
146132
147133
Pushing a control frame takes the types of the label and result values.
@@ -152,18 +138,18 @@ It then verifies that the operand stack contains the right types of values expec
152138
Afterwards, it checks that the stack has shrunk back to its initial height.
153139

154140
Finally, the current frame can be marked as unreachable.
155-
In that case, all existing operand types are purged from the operand stack, in order to allow for the :ref:`stack-polymorphism <polymorphism>` logic in :code:`pop_opd` to take effect.
141+
In that case, all existing operand types are purged from the value stack, in order to allow for the :ref:`stack-polymorphism <polymorphism>` logic in :code:`pop_val` to take effect.
156142

157143
.. note::
158144
Even with the unreachable flag set, consecutive operands are still pushed to and popped from the operand stack.
159145
That is necessary to detect invalid :ref:`examples <polymorphism>` like :math:`(\UNREACHABLE~(\I32.\CONST)~\I64.\ADD)`.
160-
However, a polymorphic stack cannot underflow, but instead generates :code:`Unknown` types as needed.
146+
However, a polymorphic stack cannot underflow, but instead generates :code:`Bot` types as needed.
161147

162148

163149
.. index:: opcode
164150

165-
Validation of Opcode Sequences
166-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
151+
Validation of Instruction Sequences
152+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
167153

168154
The following function shows the validation of a number of representative instructions that manipulate the stack.
169155
Other instructions are checked in a similar manner.
@@ -177,18 +163,26 @@ Other instructions are checked in a similar manner.
177163
func validate(opcode) =
178164
switch (opcode)
179165
case (i32.add)
180-
pop_opd(I32)
181-
pop_opd(I32)
182-
push_opd(I32)
166+
pop_val(I32)
167+
pop_val(I32)
168+
push_val(I32)
183169
184170
case (drop)
185-
pop_opd()
171+
pop_val()
186172
187173
case (select)
188-
pop_opd(I32)
189-
let t1 = pop_opd()
190-
let t2 = pop_opd()
191-
push_opd(join(t1, t2))
174+
pop_val(I32)
175+
let t1 = pop_val()
176+
let t2 = pop_val()
177+
error_if(not (is_num(t1) && is_num(t2)))
178+
error_if(t1 =/= t2 && t1 =/= Bot && t2 =/= Bot)
179+
push_val(if (t1 = Bot) t2 else t1)
180+
181+
case (select t)
182+
pop_val(I32)
183+
pop_val(t)
184+
pop_val(t)
185+
push_val(t)
192186
193187
   case (unreachable)
194188
      unreachable()
@@ -200,39 +194,35 @@ Other instructions are checked in a similar manner.
200194
push_ctrl([], [t*])
201195
202196
case (if t*)
203-
pop_opd(I32)
197+
pop_val(I32)
204198
push_ctrl([t*], [t*])
205199
206200
case (end)
207201
let results = pop_ctrl()
208-
push_opds(results)
202+
push_vals(results)
209203
210204
case (else)
211205
let results = pop_ctrl()
212206
push_ctrl(results, results)
213207
214208
case (br n)
215209
     error_if(ctrls.size() < n)
216-
      pop_opds(ctrls[n].label_types)
210+
      pop_vals(ctrls[n].label_types)
217211
      unreachable()
218212
219213
case (br_if n)
220214
     error_if(ctrls.size() < n)
221-
pop_opd(I32)
222-
      pop_opds(ctrls[n].label_types)
223-
      push_opds(ctrls[n].label_types)
215+
pop_val(I32)
216+
      pop_vals(ctrls[n].label_types)
217+
      push_vals(ctrls[n].label_types)
224218
225219
   case (br_table n* m)
220+
pop_val(I32)
226221
      error_if(ctrls.size() < m)
227-
var ts = ctrls[m].label_types
222+
let arity = ctrls[m].label_types.size()
228223
      foreach (n in n*)
229224
        error_if(ctrls.size() < n)
230-
ts := meet(ts, ctrls[n].label_types)
231-
pop_opd(I32)
232-
      pop_opds(ts)
225+
        error_if(ctrls[n].label_types.size() =/= arity)
226+
push_vals(pop_vals(ctrls[n].label_types))
227+
pop_vals(ctrls[m].label_types)
233228
      unreachable()
234-
235-
.. note::
236-
It is an invariant under the current WebAssembly instruction set that an operand of :code:`Unknown` type is never duplicated on the stack.
237-
This would change if the language were extended with stack operators like :code:`dup`.
238-
Under such an extension, the above algorithm would need to be refined by replacing the :code:`Unknown` type with proper *type variables* to ensure that all uses are consistent.

document/core/appendix/index-instructions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Instruction Binary Opcode Type
3535
(reserved) :math:`\hex{19}`
3636
:math:`\DROP` :math:`\hex{1A}` :math:`[t] \to []` :ref:`validation <valid-drop>` :ref:`execution <exec-drop>`
3737
:math:`\SELECT` :math:`\hex{1B}` :math:`[t~t~\I32] \to [t]` :ref:`validation <valid-select>` :ref:`execution <exec-select>`
38-
(reserved) :math:`\hex{1C}`
38+
:math:`\SELECT~t` :math:`\hex{1C}` :math:`[t~t~\I32] \to [t]` :ref:`validation <valid-select>` :ref:`execution <exec-select>`
3939
(reserved) :math:`\hex{1D}`
4040
(reserved) :math:`\hex{1E}`
4141
(reserved) :math:`\hex{1F}`

document/core/binary/instructions.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ Reference Instructions
9292
Parametric Instructions
9393
~~~~~~~~~~~~~~~~~~~~~~~
9494

95-
:ref:`Parametric instructions <syntax-instr-parametric>` are represented by single byte codes.
95+
:ref:`Parametric instructions <syntax-instr-parametric>` are represented by single byte codes, possibly followd by a type annotation.
9696

9797
.. _binary-drop:
9898
.. _binary-select:
@@ -101,7 +101,8 @@ Parametric Instructions
101101
\begin{array}{llclll}
102102
\production{instruction} & \Binstr &::=& \dots \\ &&|&
103103
\hex{1A} &\Rightarrow& \DROP \\ &&|&
104-
\hex{1B} &\Rightarrow& \SELECT \\
104+
\hex{1B} &\Rightarrow& \SELECT \\ &&|&
105+
\hex{1C}~~t{:}\Bvaltype &\Rightarrow& \SELECT~t \\
105106
\end{array}
106107
107108

document/core/binary/types.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ Reference Types
4545
\hex{6F} &\Rightarrow& \ANYREF \\
4646
\end{array}
4747
48+
.. note::
49+
The type :math:`\NULLREF` cannot occur in a module.
50+
4851

4952
.. index:: value type, number type, reference type
5053
pair: binary format; value type
@@ -62,6 +65,9 @@ Value Types
6265
t{:}\Breftype &\Rightarrow& t \\
6366
\end{array}
6467
68+
.. note::
69+
The type :math:`\BOT` cannot occur in a module.
70+
6571

6672
.. index:: result type, value type
6773
pair: binary format; result type

document/core/exec/instructions.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ Parametric Instructions
264264
265265
.. _exec-select:
266266

267-
:math:`\SELECT`
268-
...............
267+
:math:`\SELECT~t^?`
268+
...................
269269

270270
1. Assert: due to :ref:`validation <valid-select>`, a value of :ref:`value type <syntax-valtype>` |I32| is on the top of the stack.
271271

@@ -287,9 +287,9 @@ Parametric Instructions
287287

288288
.. math::
289289
\begin{array}{lcl@{\qquad}l}
290-
\val_1~\val_2~(\I32\K{.}\CONST~c)~\SELECT &\stepto& \val_1
290+
\val_1~\val_2~(\I32\K{.}\CONST~c)~\SELECT~t^? &\stepto& \val_1
291291
& (\iff c \neq 0) \\
292-
\val_1~\val_2~(\I32\K{.}\CONST~c)~\SELECT &\stepto& \val_2
292+
\val_1~\val_2~(\I32\K{.}\CONST~c)~\SELECT~t^? &\stepto& \val_2
293293
& (\iff c = 0) \\
294294
\end{array}
295295

document/core/syntax/instructions.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,13 @@ Instructions in this group can operate on operands of any :ref:`value type <synt
205205
\production{instruction} & \instr &::=&
206206
\dots \\&&|&
207207
\DROP \\&&|&
208-
\SELECT
208+
\SELECT~\valtype^? \\
209209
\end{array}
210210
211211
The |DROP| operator simply throws away a single operand.
212212

213213
The |SELECT| operator selects one of its first two operands based on whether its third operand is zero or not.
214+
It may be include by a :ref:`value type <syntax-valtype>` determining the type of these operands. If missing, the operands must be of :ref:`numeric type <syntax-numtype>`.
214215

215216

216217
.. index:: ! variable instruction, local, global, local index, global index

document/core/syntax/types.rst

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ The type |FUNCREF| denotes the infinite union of all references to :ref:`functio
6363

6464
The type |NULLREF| only contains a single value: the :ref:`null <syntax-ref.null>` reference.
6565
It is a :ref:`subtype <match-reftype>` of all other reference types.
66-
By virtue of not being representable in either the :ref:`binary format <binary-reftype>` nor the :ref:`text format <text-reftype>`, the |NULLREF| type cannot be used in a program;
66+
By virtue of being representable in neither the :ref:`binary format <binary-reftype>` nor the :ref:`text format <text-reftype>`, the |NULLREF| type cannot be used in a program;
6767
it only occurs during :ref:`validation <valid>`.
6868

6969
.. note::
@@ -73,7 +73,7 @@ Reference types are *opaque*, meaning that neither their size nor their bit patt
7373
Values of reference type can be stored in :ref:`tables <syntax-table>`.
7474

7575

76-
.. index:: ! value type, number type, reference type
76+
.. index:: ! value type, number type, reference type, ! bottom type
7777
pair: abstract syntax; value type
7878
pair: value; type
7979
.. _syntax-valtype:
@@ -82,11 +82,16 @@ Value Types
8282
~~~~~~~~~~~
8383

8484
*Value types* classify the individual values that WebAssembly code can compute with and the values that a variable accepts.
85+
They are either :ref:`number types <syntax-numtype>`, :ref:`reference type <syntax-reftype>`, or the unique *bottom type*, written :math:`\BOT`.
86+
87+
The type :math:`\BOT` is a :ref:`subtype <match-valtype>` of all other types.
88+
By virtue of being representable in neither the :ref:`binary format <binary-valtype>` nor the :ref:`text format <text-valtype>`, it cannot be used in a program;
89+
it only occurs during :ref:`validation <valid>`.
8590

8691
.. math::
8792
\begin{array}{llll}
8893
\production{value type} & \valtype &::=&
89-
\numtype ~|~ \reftype \\
94+
\numtype ~|~ \reftype ~|~ \BOT \\
9095
\end{array}
9196
9297
Conventions

document/core/text/instructions.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ Parametric Instructions
163163
\begin{array}{llclll}
164164
\production{instruction} & \Tplaininstr_I &::=& \dots \\ &&|&
165165
\text{drop} &\Rightarrow& \DROP \\ &&|&
166-
\text{select} &\Rightarrow& \SELECT \\
166+
\text{select}~(t{:}\Tresult)^? &\Rightarrow& \SELECT~t^? \\
167167
\end{array}
168168
169169

0 commit comments

Comments
 (0)