diff --git a/.github/workflows/ci-interpreter.yml b/.github/workflows/ci-interpreter.yml index 866a8da707..91a5fc4235 100644 --- a/.github/workflows/ci-interpreter.yml +++ b/.github/workflows/ci-interpreter.yml @@ -21,7 +21,7 @@ jobs: - name: Setup OCaml uses: ocaml/setup-ocaml@v2 with: - ocaml-compiler: 4.12.x + ocaml-compiler: 4.14.x - name: Setup OCaml tools run: opam install --yes ocamlfind.1.9.5 js_of_ocaml.4.0.0 js_of_ocaml-ppx.4.0.0 - name: Setup Node.js @@ -31,6 +31,6 @@ jobs: - name: Build interpreter run: cd interpreter && opam exec make - name: Run tests - # Re-enable the JS tests once table64 is available under node - #run: cd interpreter && opam exec make JS="node --experimental-wasm-memory64" ci + # TODO: reactiate node once it supports all of Wasm 3.0 + # run: cd interpreter && opam exec make JS=node ci run: cd interpreter && opam exec make ci diff --git a/README.md b/README.md index 853a2e86c2..23392f1d5c 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,18 @@ Original `README` from upstream repository follows... # spec -This repository holds the sources for the WebAssembly draft specification -(to seed a future -[WebAssembly Working Group](https://lists.w3.org/Archives/Public/public-new-work/2017Jun/0005.html)), -a reference implementation, and the official testsuite. +This repository holds a prototypical reference implementation for WebAssembly, +which is currently serving as the official specification. Eventually, we expect +to produce a specification either written in human-readable prose or in a formal +specification language. -A formatted version of the spec is available here: -[webassembly.github.io/spec](https://webassembly.github.io/spec/), +It also holds the WebAssembly testsuite, which tests numerous aspects of +conformance to the spec. + +View the work-in-progress spec at [webassembly.github.io/spec](https://webassembly.github.io/spec/). + +At this time, the contents of this repository are under development and known +to be "incomplet and inkorrect". Participation is welcome. Discussions about new features, significant semantic changes, or any specification change likely to generate substantial discussion diff --git a/deploy_key b/deploy_key new file mode 100644 index 0000000000..f4e5b00055 --- /dev/null +++ b/deploy_key @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAgEAn9FUrQzWcgEOHsDJ+f7L0g7xIYrdBb3K1dQqFWFidZcL8KYMMR0/ +CEYRXfSn3EcvpYnRoIjWfGQgqOpA90FMaAwqdoK72ByW7q2C4ymjX5xR8XFyG5YJVvjaMe +STXZlynQSOH9lXU5RooeEwRatgQizuwB/Bqahry/8FOfDhVlNO26IbEyJPd23qeyAd+42C +OTE5oWHEJ3TKSWSfwTAhfrIlIg+dWiSx/IUcLc+7Ms5EpQh3ebX49oaW00SWRDnyqoM95m +D+Uc3DuTFJyKdHlLouESdNrE9LeBms5N/bHSgedVyZ5fL9SHC6P1HmgDPFspmO6z1s15/a +5y5hf9zHVoj1ha54LSaPlE5/L5hpY6PCH7fepRXhw84VIQV74IsJow1XUbkmfKkjW3PeBq +CF5cTKF2LK65SraxEfNMLU0ThOH6B6yzePq7JF+05VGMh2G2Wkwy11pezeqW5tPU1M2qwS +RN8jAFKIwuC7B73drz8yMhF8MfTW4iGM4RqhQRCK22xmVtzwYFwRKUM++p2iOggGi42jnV +skv7/yQ6XcaEm+2Jx3C2zy/5OdLql9z8gmKsH4jpQUADmae8KBfJCqHZrtvhRTqH99E3th +pIKcpzY+n5uhNCyrY+hTfB44/EIntWkXDTLwVVRmmOyHSvEA7/Dz1vtA7gY9Nu25xY/SXS +sAAAc4dIF4rHSBeKwAAAAHc3NoLXJzYQAAAgEAn9FUrQzWcgEOHsDJ+f7L0g7xIYrdBb3K +1dQqFWFidZcL8KYMMR0/CEYRXfSn3EcvpYnRoIjWfGQgqOpA90FMaAwqdoK72ByW7q2C4y +mjX5xR8XFyG5YJVvjaMeSTXZlynQSOH9lXU5RooeEwRatgQizuwB/Bqahry/8FOfDhVlNO +26IbEyJPd23qeyAd+42COTE5oWHEJ3TKSWSfwTAhfrIlIg+dWiSx/IUcLc+7Ms5EpQh3eb +X49oaW00SWRDnyqoM95mD+Uc3DuTFJyKdHlLouESdNrE9LeBms5N/bHSgedVyZ5fL9SHC6 +P1HmgDPFspmO6z1s15/a5y5hf9zHVoj1ha54LSaPlE5/L5hpY6PCH7fepRXhw84VIQV74I +sJow1XUbkmfKkjW3PeBqCF5cTKF2LK65SraxEfNMLU0ThOH6B6yzePq7JF+05VGMh2G2Wk +wy11pezeqW5tPU1M2qwSRN8jAFKIwuC7B73drz8yMhF8MfTW4iGM4RqhQRCK22xmVtzwYF +wRKUM++p2iOggGi42jnVskv7/yQ6XcaEm+2Jx3C2zy/5OdLql9z8gmKsH4jpQUADmae8KB +fJCqHZrtvhRTqH99E3thpIKcpzY+n5uhNCyrY+hTfB44/EIntWkXDTLwVVRmmOyHSvEA7/ +Dz1vtA7gY9Nu25xY/SXSsAAAADAQABAAACAFMayDxgY5bOw6fsOlscSqKFkJAPpJUat0Hv +3J5XkJpzHAtcXRShD6jevqMr2Knr/nPHMdGXtmjirDUJ8xRfyTqFsQMFQmbDnxyn71ruyP +yrzdSOWHbN0zd9mgC9yn+ujnHl733SR923W51p+u8PibN/p/sRyGPPp5Zhmzcg8hwwn94H +8qpFeisxZe/2qICpeiEBXuVzcEvQKGx3vbb4r0IxoquOkRVR5ZfZI+kSj1aA+iMTPwV0Qe +z32bAshzMdKvnN2z9UCotBQ1imr6Z+jfNhyRi0ZmiGp0jhmQ0+9rK3rPb8Wy6+50RnEgJh +NUpPIauYvD/JJjMN9genD54skR61JnwRSmMUcuYFvcPKip1FYugYtZY/a9waqcSA73TcuZ +DQzihYs4fdr2aD3pH8QchYwo5vZFzPCVuXF387pYUmj8u3RLDhemSYjwuG/NWdVKnYnZ2B +EVOMi4YZ6Kms7rac8zzgFUonxDWLCigOPI0HPfrDKQ7P6NyiAKEEEfK6g2KvnDJaaCdfpb +70UTFG6YyN+1qa0NWVcU6iEGd/ziT7xPDT3WgJd4SAYkllycQkg5zdFz4T1SqABMrWqjK7 +1Xk//kZxboYZFwBoODiNd2dcLU1XOBhNvoAEajKQNyzAhET6eC02olwUwl7ZwdMxMH8C9H +/j4lAq+v6PYzFHN/uZAAABAQCExWknkwPAu+eDulQTW1T5kIhsD3Ffe+9yg1Ps83NPST9w +7dddykxOzZJ4j8WjpsvwCXuxyxPqt3UI3y2t4nC2aIWq5gPoIfDvtt5J8j6i4RwrI2AU3j +tKdPyLD4qKOCvuqThRCUz3GffU8QNknLYT1jGhO8p//nZq68SVIhtcL8CG1/mQQVApgvd+ +VrBIytptBk0wn4ufMY11CjRTLjASJzBsiT/VmUkQVBQDn6/yvCSxx7nYzMt0XcDqH1/KO7 +iqEN6HfkTNTKBXcRWIS+c7OvAixJTGXRCE2xcQindaHQ3HNK+6r1qAXp65XfsTJUw+FIdc +4OXiRdnAAanVy6tAAAABAQDKduhs+s+AKQWsuwkzWdnLQdMP+98vKBkMH7/dBA73xDd+hR +8sHOhoyxYoNoUhvYWcoip2Mqsn/+F5QLvKxaaccQOtfQpYEeB0koL3QEHUIBMwzgEW112T +ATa8KR6okRAUxn7tqswebAFPmdpGS1YkQAyAQQoPr2NQpPSWN0cKXQZUYLn5r6HSZ7isAm +K/6mrF+TqK80055A+duZggLIKyMAKDTdgtIu4D/wZIqZYcY8uiA2ZhGM3XEQutTjo4xemu +V2X+WSwXhrXiSAWqbCBxCRcCLKktweihb1nOkXIOspKr7Adj/ctmlqO875GHuwlrGaNfe2 +DFo67i4udsdrc9AAABAQDKE5rUxfN6K51jWdgiBVx1XVH0YenLGHECYcxg5AHkDTJUOl9F +SqiP4w128fFjHEO+GGltgkruQ94k+wINub/e7k1NYSpgj03BDs6swpRG7Y3uot1aBFAati +ITJF6p1rmjmBxtDhaVX9nA6GyOmzXO4Tb6niBHO5u7B3dqZI/iXHUmsZOsa1ijuE8YL5P7 +SzxbkiGGsWv5gfs8RcYuOmGhTx2LxOTNh3kovj4xQSoJVH3IikpodQAuXTdL5utuAzgVEL +Xpk1XVyVPkFGitmNqTr3D2gKnPnkQf8KYsRmzt4bPKDrKOBleqYbFSabyHUNJEaW7pmf8+ +fNbVF9dWMmyHAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/deploy_key.pub b/deploy_key.pub new file mode 100644 index 0000000000..313f1bd8e1 --- /dev/null +++ b/deploy_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCf0VStDNZyAQ4ewMn5/svSDvEhit0FvcrV1CoVYWJ1lwvwpgwxHT8IRhFd9KfcRy+lidGgiNZ8ZCCo6kD3QUxoDCp2grvYHJburYLjKaNfnFHxcXIblglW+Nox5JNdmXKdBI4f2VdTlGih4TBFq2BCLO7AH8GpqGvL/wU58OFWU07bohsTIk93bep7IB37jYI5MTmhYcQndMpJZJ/BMCF+siUiD51aJLH8hRwtz7syzkSlCHd5tfj2hpbTRJZEOfKqgz3mYP5RzcO5MUnIp0eUui4RJ02sT0t4Gazk39sdKB51XJnl8v1IcLo/UeaAM8WymY7rPWzXn9rnLmF/3MdWiPWFrngtJo+UTn8vmGljo8Ift96lFeHDzhUhBXvgiwmjDVdRuSZ8qSNbc94GoIXlxMoXYsrrlKtrER80wtTROE4foHrLN4+rskX7TlUYyHYbZaTDLXWl7N6pbm09TUzarBJE3yMAUojC4LsHvd2vPzIyEXwx9NbiIYzhGqFBEIrbbGZW3PBgXBEpQz76naI6CAaLjaOdWyS/v/JDpdxoSb7YnHcLbPL/k50uqX3PyCYqwfiOlBQAOZp7woF8kKodmu2+FFOof30Te2GkgpynNj6fm6E0LKtj6FN8Hjj8Qie1aRcNMvBVVGaY7IdK8QDv8PPW+0DuBj027bnFj9JdKw== diff --git a/document/core/appendix/algorithm.rst b/document/core/appendix/algorithm.rst index 9b2c9a5b8d..72f9eda2ef 100644 --- a/document/core/appendix/algorithm.rst +++ b/document/core/appendix/algorithm.rst @@ -16,69 +16,187 @@ Consequently, it can be integrated directly into a decoder. The algorithm is expressed in typed pseudo code whose semantics is intended to be self-explanatory. -.. index:: value type, stack, label, frame, instruction +.. index:: value type, reference type, vector type, number type, packed type, field type, structure type, array type, function type, composite type, sub type, recursive type, defined type, stack, label, frame, instruction Data Structures ~~~~~~~~~~~~~~~ -Types are representable as an enumeration. +Types +..... + +Value types are representable as sets of enumerations: .. code-block:: pseudo - type val_type = I32 | I64 | F32 | F64 | V128 | Funcref | Externref + type num_type = I32 | I64 | F32 | F64 + type vec_type = V128 + type heap_type = + Any | Eq | I31 | Struct | Array | None | + Func | Nofunc | Extern | Noextern | Bot | + Def(def : def_type) + type ref_type = Ref(heap : heap_type, null : bool) + type val_type = num_type | vec_type | ref_type | Bot - func is_num(t : val_type | Unknown) : bool = - return t = I32 || t = I64 || t = F32 || t = F64 || t = Unknown + func is_num(t : val_type) : bool = + return t = I32 || t = I64 || t = F32 || t = F64 || t = Bot - func is_vec(t : val_type | Unknown) : bool = - return t = V128 || t = Unknown + func is_vec(t : val_type) : bool = + return t = V128 || t = Bot - func is_ref(t : val_type | Unknown) : bool = - return t = Funcref || t = Externref || t = Unknown + func is_ref(t : val_type) : bool = + return not (is_num t || is_vec t) || t = Bot -The algorithm uses two separate stacks: the *value stack* and the *control stack*. -The former tracks the :ref:`types ` of operand values on the :ref:`stack `, -the latter surrounding :ref:`structured control instructions ` and their associated :ref:`blocks `. +Similarly, :ref:`defined types ` :code:`def_type` can be represented: .. code-block:: pseudo - type val_stack = stack(val_type | Unknown) + type packed_type = I8 | I16 + type field_type = Field(val : val_type | packed_type, mut : bool) + + type struct_type = Struct(fields : list(field_type)) + type array_type = Array(fields : field_type) + type func_type = Func(params : list(val_type), results : list(val_type)) + type comp_type = struct_type | array_type | func_type + + type sub_type = Sub(super : list(def_type), body : comp_type, final : bool) + type rec_type = Rec(types : list(sub_type)) + + type def_type = Def(rec : rec_type, proj : int32) + + func unpack_field(t : field_type) : val_type = + if (it = I8 || t = I16) return I32 + return t + + func expand_def(t : def_type) : comp_type = + return t.rec.types[t.proj].body + +These representations assume that all types have been :ref:`closed ` by :ref:`substituting ` all :ref:`type indices ` (in :ref:`concrete heap types ` and in :ref:`sub types `) with their respective :ref:`defined types `. +This includes *recursive* references to enclosing :ref:`defined types `, +such that type representations form graphs and may be *cyclic* for :ref:`recursive types `. + +We assume that all types have been *canonicalized*, such that equality on two type representations holds if and only if their :ref:`closures ` are syntactically equivalent, making it a constant-time check. + +.. note:: + For the purpose of type canonicalization, recursive references from a :ref:`heap type ` to an enclosing :ref:`recursive type ` (i.e., forward edges in the graph that form a cycle) need to be distinguished from references to previously defined types. + However, this distinction does not otherwise affect validation, so is ignored here. + In the graph representation, all recursive types are effectively infinitely :ref:`unrolled `. + +We further assume that :ref:`validation ` and :ref:`subtyping ` checks are defined on value types, as well as a few auxiliary functions on composite types: + +.. code-block:: pseudo + + func validate_val_type(t : val_type) + func validate_ref_type(t : ref_type) + + func matches_val(t1 : val_type, t2 : val_type) : bool + func matches_ref(t1 : val_type, t2 : val_type) : bool + + func is_func(t : comp_type) : bool + func is_struct(t : comp_type) : bool + func is_array(t : comp_type) : bool + +Finally, the following function computes the least precise supertype of a given :ref:`heap type ` (its corresponding top type): + +.. code-block:: pseudo + + func top_heap_type(t : heap_type) : heap_type = + switch (t) + case (Any | Eq | I31 | Struct | Array | None) + return Any + case (Func | Nofunc) + return Func + case (Extern | Noextern) + return Extern + case (Def(dt)) + switch (dt.rec.types[dt.proj].body) + case (Struct(_) | Array(_)) + return Any + case (Func(_)) + return Func + case (Bot) + raise CannotOccurInSource + + +Context +....... + +Validation requires a :ref:`context ` for checking uses of :ref:`indices `. +For the purpose of presenting the algorithm, it is maintained in a set of global variables: + +.. code-block:: pseudo + + var return_type : list(val_type) + var types : array(def_type) + var locals : array(val_type) + var locals_init : array(bool) + var globals : array(global_type) + var funcs : array(func_type) + var tables : array(table_type) + var mems : array(mem_type) + +This assumes suitable representations for the various :ref:`types ` besides :code:`val_type`, which are omitted here. + +For locals, there is an additional array recording the initialization status of each local. + +Stacks +...... + +The algorithm uses three separate stacks: the *value stack*, the *control stack*, and the *initialization stack*. +The value stack tracks the :ref:`types ` of operand values on the :ref:`stack `. +The control stack tracks surrounding :ref:`structured control instructions ` and their associated :ref:`blocks `. +The initialization stack records all :ref:`locals ` that have been initialized since the beginning of the function. + +.. code-block:: pseudo + + type val_stack = stack(val_type) + type init_stack = stack(u32) type ctrl_stack = stack(ctrl_frame) type ctrl_frame = { opcode : opcode start_types : list(val_type) end_types : list(val_type) - height : nat + val_height : nat + init_height : nat unreachable : bool } -For each value, the value stack records its :ref:`value type `, or :code:`Unknown` when the type is not known. - -For each entered block, the control stack records a *control frame* with the originating opcode, the types on the top of the operand stack at the start and end of the block (used to check its result as well as branches), 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 ` typing after branches). +For each entered block, the control stack records a *control frame* with the originating opcode, the types on the top of the operand stack at the start and end of the block (used to check its result as well as branches), the height of the operand stack at the start of the block (used to check that operands do not underflow the current block), the height of the initialization stack at the start of the block (used to reset initialization status at the end of the block), and a flag recording whether the remainder of the block is unreachable (used to handle :ref:`stack-polymorphic ` typing after branches). -For the purpose of presenting the algorithm, the operand and control stacks are simply maintained as global variables: +For the purpose of presenting the algorithm, these stacks are simply maintained as global variables: .. code-block:: pseudo var vals : val_stack + var inits : init_stack var ctrls : ctrl_stack However, these variables are not manipulated directly by the main checking function, but through a set of auxiliary functions: .. code-block:: pseudo - func push_val(type : val_type | Unknown) = + func push_val(type : val_type) = vals.push(type) - func pop_val() : val_type | Unknown = - if (vals.size() = ctrls[0].height && ctrls[0].unreachable) return Unknown + func pop_val() : val_type = + if (vals.size() = ctrls[0].height && ctrls[0].unreachable) return Bot error_if(vals.size() = ctrls[0].height) return vals.pop() - func pop_val(expect : val_type | Unknown) : val_type | Unknown = + func pop_val(expect : val_type) : val_type = + let actual = pop_val() + error_if(not matches_val(actual, expect)) + return actual + + func pop_num() : num_type | Bot = let actual = pop_val() - error_if(actual =/= expect && actual =/= Unknown && expect =/= Unknown) + error_if(not is_num(actual)) + return actual + + func pop_ref() : ref_type = + let actual = pop_val() + error_if(not is_ref(actual)) + if (actual = Bot) return Ref(Bot, false) return actual func push_vals(types : list(val_type)) = foreach (t in types) push_val(t) @@ -92,10 +210,10 @@ Pushing an operand value simply pushes the respective type to the value stack. Popping an operand value checks that the value stack does not underflow the current block and then removes one type. But first, a special case is handled where the block contains no known values, but has been marked as unreachable. That can occur after an unconditional branch, when the stack is typed :ref:`polymorphically `. -In that case, an unknown type is returned. +In that case, the :code:`Bot` type is returned, because that is a *principal* choice trivially satisfying all use constraints. A second function for popping an operand value takes an expected type, which the actual operand type is checked against. -The types may differ in case one of them is Unknown. +The types may differ by subtyping, including the case where the actual type is :code:`Bot`, and thereby matches unconditionally. The function returns the actual type popped from the stack. Finally, there are accumulative functions for pushing or popping multiple operand types. @@ -105,12 +223,35 @@ Finally, there are accumulative functions for pushing or popping multiple operan so that, e.g., :code:`ctrls[0]` accesses the element pushed last. +The initialization stack and the initialization status of locals is manipulated through the following functions: + +.. code-block:: pseudo + + func get_local(idx : u32) = + error_if(not locals_init[idx]) + + func set_local(idx : u32) = + if (not locals_init[idx]) + inits.push(idx) + locals_init[idx] := true + + func reset_locals(height : nat) = + while (inits.size() > height) + locals_init[inits.pop()] := false + +Getting a local verifies that it is known to be initialized. +When a local is set that was not set already, +then its initialization status is updated and the change is recorded in the initialization stack. +Thus, the initialization status of all locals can be reset to a previous state by denoting a specific height in the initialization stack. + +The size of the initialization stack is bounded by the number of (non-defaultable) locals in a function, so can be preallocated by an algorithm. + The control stack is likewise manipulated through auxiliary functions: .. code-block:: pseudo func push_ctrl(opcode : opcode, in : list(val_type), out : list(val_type)) = - let frame = ctrl_frame(opcode, in, out, vals.size(), false) + let frame = ctrl_frame(opcode, in, out, vals.size(), inits.size(), false) ctrls.push(frame) push_vals(in) @@ -118,12 +259,13 @@ The control stack is likewise manipulated through auxiliary functions: error_if(ctrls.is_empty()) let frame = ctrls[0] pop_vals(frame.end_types) - error_if(vals.size() =/= frame.height) + error_if(vals.size() =/= frame.val_height) + reset_locals(frame.init_height) ctrls.pop() return frame func label_types(frame : ctrl_frame) : list(val_types) = - return (if frame.opcode == loop then frame.start_types else frame.end_types) + return (if (frame.opcode = loop) frame.start_types else frame.end_types) func unreachable() = vals.resize(ctrls[0].height) @@ -135,6 +277,7 @@ It allocates a new frame record recording them along with the current height of Popping a frame first checks that the control stack is not empty. It then verifies that the operand stack contains the right types of values expected at the end of the exited block and pops them off the operand stack. Afterwards, it checks that the stack has shrunk back to its initial height. +Finally, it undoes all changes to the initialization status of locals that happend inside the block. The type of the :ref:`label ` associated with a control frame is either that of the stack at the start or the end of the frame, determined by the opcode that it originates from. @@ -146,7 +289,7 @@ it is an invariant of the validation algorithm that there always is at least one .. note:: Even with the unreachable flag set, consecutive operands are still pushed to and popped from the operand stack. That is necessary to detect invalid :ref:`examples ` like :math:`(\UNREACHABLE~(\I32.\CONST)~\I64.\ADD)`. - However, a polymorphic stack cannot underflow, but instead generates :code:`Unknown` types as needed. + However, a polymorphic stack cannot underflow, but instead generates :code:`Bot` types as needed. .. index:: opcode @@ -157,10 +300,6 @@ Validation of Opcode Sequences The following function shows the validation of a number of representative instructions that manipulate the stack. Other instructions are checked in a similar manner. -.. note:: - Various instructions not shown here will additionally require the presence of a validation :ref:`context ` for checking uses of :ref:`indices `. - That is an easy addition and therefore omitted from this presentation. - .. code-block:: pseudo func validate(opcode) = @@ -177,9 +316,9 @@ Other instructions are checked in a similar manner. pop_val(I32) let t1 = pop_val() let t2 = pop_val() - error_if(not ((is_num(t1) && is_num(t2)) || (is_vec(t1) && is_vec(t2)))) - error_if(t1 =/= t2 && t1 =/= Unknown && t2 =/= Unknown) - push_val(if (t1 = Unknown) t2 else t1) + error_if(not (is_num(t1) && is_num(t2) || is_vec(t1) && is_vec(t2))) + error_if(t1 =/= t2 && t1 =/= Bot && t2 =/= Bot) + push_val(if (t1 = Bot) t2 else t1) case (select t) pop_val(I32) @@ -187,6 +326,27 @@ Other instructions are checked in a similar manner. pop_val(t) push_val(t) + case (ref.is_null) + pop_ref() + push_val(I32) + + case (ref.as_non_null) + let rt = pop_ref() + push_val(Ref(rt.heap, false)) + + case (ref.test rt) + validate_ref_type(rt) + pop_val(Ref(top_heap_type(rt), true)) + push_val(I32) + + case (local.get x) + get_local(x) + push_val(locals[x]) + + case (local.set x) + pop_val(locals[x]) + set_local(x) + case (unreachable) unreachable() @@ -234,8 +394,58 @@ Other instructions are checked in a similar manner. pop_vals(label_types(ctrls[m])) unreachable() + case (br_on_null n) + error_if(ctrls.size() < n) + let rt = pop_ref() + pop_vals(label_types(ctrls[n])) + push_vals(label_types(ctrls[n])) + push_val(Ref(rt.heap, false)) + + case (br_on_cast n rt1 rt2) + validate_ref_type(rt1) + validate_ref_type(rt2) + pop_val(rt1) + push_val(rt2) + pop_vals(label_types(ctrls[n])) + push_vals(label_types(ctrls[n])) + pop_val(rt2) + push_val(diff_ref_type(rt2, rt1)) + + case (return) + pop_vals(return_types) + unreachable() + + case (call_ref x) + let t = expand_def(types[x]) + error_if(not is_func(t)) + pop_vals(t.params) + pop_val(Ref(Def(types[x]))) + push_vals(t.results) + + case (return_call_ref x) + let t = expand_def(types[x]) + error_if(not is_func(t)) + pop_vals(t.params) + pop_val(Ref(Def(types[x]))) + error_if(t.results.len() =/= return_types.len()) + push_vals(t.results) + pop_vals(return_types) + unreachable() + + case (struct.new x) + let t = expand_def(types[x]) + error_if(not is_struct(t)) + for (ti in reverse(t.fields)) + pop_val(unpack_field(ti)) + push_val(Ref(Def(types[x]))) + + case (struct.set x n) + let t = expand_def(types[x]) + error_if(not is_struct(t) || n >= t.fields.len()) + pop_val(Ref(Def(types[x]))) + pop_val(unpack_field(st.fields[n])) .. note:: - It is an invariant under the current WebAssembly instruction set that an operand of :code:`Unknown` type is never duplicated on the stack. + It is an invariant under the current WebAssembly instruction set that an operand of :code:`Bot` type is never duplicated on the stack. This would change if the language were extended with stack instructions like :code:`dup`. - 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. + Under such an extension, the above algorithm would need to be refined by replacing the :code:`Bot` type with proper *type variables* to ensure that all uses are consistent. diff --git a/document/core/appendix/changes.rst b/document/core/appendix/changes.rst index 339cb25f9d..f8e5659e6a 100644 --- a/document/core/appendix/changes.rst +++ b/document/core/appendix/changes.rst @@ -15,7 +15,7 @@ Release 2.0 Sign extension instructions ........................... -Added new numeric instructions for performing sign extension within integer representations [#proposal-signext]_. +Added new numeric instructions for performing sign extension within integer representations. [#proposal-signext]_ * New :ref:`numeric instructions `: :math:`\K{i}\X{nn}\K{.}\EXTEND\X{N}\K{\_s}` @@ -25,7 +25,7 @@ Added new numeric instructions for performing sign extension within integer repr Non-trapping float-to-int conversions ..................................... -Added new conversion instructions that avoid trapping when converting a floating-point number to an integer [#proposal-cvtsat]_. +Added new conversion instructions that avoid trapping when converting a floating-point number to an integer. [#proposal-cvtsat]_ * New :ref:`numeric instructions `: :math:`\K{i}\X{nn}\K{.}\TRUNC\K{\_sat\_f}\X{mm}\K{\_}\sx` @@ -35,7 +35,7 @@ Added new conversion instructions that avoid trapping when converting a floating Multiple values ............... -Generalized the result type of blocks and functions to allow for multiple values; in addition, introduced the ability to have block parameters [#proposal-multivalue]_. +Generalized the result type of blocks and functions to allow for multiple values; in addition, introduced the ability to have block parameters. [#proposal-multivalue]_ * :ref:`Function types ` allow more than one result @@ -47,13 +47,13 @@ Generalized the result type of blocks and functions to allow for multiple values Reference types ............... -Added |FUNCREF| and |EXTERNREF| as new value types and respective instructions [#proposal-reftype]_. +Added |FUNCREF| and |EXTERNREF| as new value types and respective instructions. [#proposal-reftype]_ * New :ref:`value types `: :ref:`reference types ` |FUNCREF| and |EXTERNREF| * New :ref:`reference instructions `: |REFNULL|, |REFFUNC|, |REFISNULL| -* Enrich :ref:`parametric instruction `: |SELECT| with optional type immediate +* Extended :ref:`parametric instruction `: |SELECT| with optional type immediate * New :ref:`declarative ` form of :ref:`element segment ` @@ -63,19 +63,19 @@ Added |FUNCREF| and |EXTERNREF| as new value types and respective instructions [ Table instructions .................. -Added instructions to directly access and modify tables [#proposal-reftype]_. +Added instructions to directly access and modify tables. [#proposal-reftype]_ * :ref:`Table types ` allow any :ref:`reference type ` as element type * New :ref:`table instructions `: |TABLEGET|, |TABLESET|, |TABLESIZE|, |TABLEGROW| -.. index:: table, instruction, table index, element segment +.. index:: table, instruction, table index, element segment, import, export Multiple tables ............... -Added the ability to use multiple tables per module [#proposal-reftype]_. +Added the ability to use multiple tables per module. [#proposal-reftype]_ * :ref:`Modules ` may :ref:`define `, :ref:`import `, and :ref:`export ` multiple tables @@ -89,7 +89,7 @@ Added the ability to use multiple tables per module [#proposal-reftype]_. Bulk memory and table instructions .................................. -Added instructions that modify ranges of memory or table entries [#proposal-reftype]_ [#proposal-bulk]_ +Added instructions that modify ranges of memory or table entries. [#proposal-reftype]_ [#proposal-bulk]_ * New :ref:`memory instructions `: |MEMORYFILL|, |MEMORYINIT|, |MEMORYCOPY|, |DATADROP| @@ -155,3 +155,112 @@ Added vector type and instructions that manipulate multiple numeric values in pa .. [#proposal-vectype] https://github.com/WebAssembly/spec/tree/main/proposals/simd/ + + +Release 3.0 +~~~~~~~~~~~ + +.. index: instruction, expression, constant + +Extended constant expressions +............................. + +Allowed basic numeric computations in constant expressions. [#proposal-extconst]_ + +* Extended set of :ref:`constant instructions ` with :math:`\K{i}\X{nn}\K{.add}`, :math:`\K{i}\X{nn}\K{.sub}`, and :math:`\K{i}\X{nn}\K{.mul}`, and |GLOBALGET| for any previously declared immutable :ref:`global ` + +.. note:: + The :ref:`garbage collection ` added further constant instructions. + + +.. index: instruction, function, call + +Tail calls +.......... + +Added instructions to perform tail calls. [#proposal-tailcall]_ + +* New :ref:`control instructions `: |RETURNCALL| and |RETURNCALLINDIRECT| + + +.. index: instruction, memory, memory index, data segment, import, export + +Multiple memories +................. + +Added the ability to use multiple memories per module. [#proposal-multimem]_ + +* :ref:`Modules ` may :ref:`define `, :ref:`import `, and :ref:`export ` multiple memories + +* :ref:`Memory instructions ` take a :ref:`memory index ` immediate: |MEMORYSIZE|, |MEMORYGROW|, |MEMORYFILL|, |MEMORYCOPY|, |MEMORYINIT|, :math:`t\K{.load}`, :math:`t\K{.store}`, :math:`t\K{.load}\!N\!\K{\_}\sx`, :math:`t\K{.store}\!N`, :math:`\K{v128.load}\!N\!\K{x}\!M\!\K{\_}\sx`, :math:`\K{v128.load}\!N\!\K{\_zero}`, :math:`\K{v128.load}\!N\!\K{\_splat}`, :math:`\K{v128.load}\!N\!\K{\_lane}`, :math:`\K{v128.store}\!N\!\K{\_lane}` + +* :ref:`Data segments ` take a :ref:`memory index ` + + +.. index:: reference, reference type, heap type, value type, local, local type, instruction, instruction type, table, function, function type, matching, subtyping + +Typeful references +.................. + +Added more precise types for references. [#proposal-typedref]_ + +* New generalised form of :ref:`reference types `: :math:`(\REF~\NULL^?~\heaptype)` + +* New class of :ref:`heap types `: |FUNC|, |EXTERN|, :math:`\typeidx` + +* Basic :ref:`subtyping ` on :ref:`reference ` and :ref:`value ` types + +* New :ref:`reference instructions `: |REFASNONNULL|, |BRONNULL|, |BRONNONNULL| + +* New :ref:`control instruction `: |CALLREF| + +* Refined typing of :ref:`reference instruction ` |REFFUNC| with more precise result type + +* Refined typing of :ref:`local instructions ` and :ref:`instruction sequences ` to track the :ref:`initialization status ` of :ref:`locals ` with non-:ref:`defaultable ` type + +* Extended :ref:`table definitions ` with optional initializer expression + + +.. index:: reference, reference type, heap type, field type, storage type, structure type, array type, composite type, sub type, recursive type +.. _extension-gc: + +Garbage collection +.................. + +Added managed reference types. [#proposal-gc]_ + +* New forms of :ref:`heap types `: |ANY|, |EQT|, |I31|, |STRUCT|, |ARRAY|, |NONE|, |NOFUNC|, |NOEXTERN| + +* New :ref:`reference type ` short-hands: |ANYREF|, |EQREF|, |I31REF|, |STRUCTREF|, |ARRAYREF|, |NULLREF|, |NULLFUNCREF|, |NULLEXTERNREF| + +* New forms of type definitions: :ref:`structure ` and :ref:`array types `, :ref:`sub types `, and :ref:`recursive types ` + +* Enriched :ref:`subtyping ` based on explicitly declared :ref:`sub types ` and the new heap types + +* New generic :ref:`reference instructions `: |REFEQ|, |REFTEST|, |REFCAST|, |BRONCAST|, |BRONCASTFAIL| + +* New :ref:`reference instructions ` for :ref:`unboxed scalars `: |REFI31|, :math:`\I31GET\K{\_}\sx` + +* New :ref:`reference instructions ` for :ref:`structure types `: |STRUCTNEW|, |STRUCTNEWDEFAULT|, :math:`\STRUCTGET\K{\_}\sx^?`, |STRUCTSET| + +* New :ref:`reference instructions ` for :ref:`array types `: |ARRAYNEW|, |ARRAYNEWDEFAULT|, |ARRAYNEWFIXED|, |ARRAYNEWDATA|, |ARRAYNEWELEM|, :math:`\ARRAYGET\K{\_}\sx^?`, |ARRAYSET|, |ARRAYLEN|, |ARRAYFILL|, |ARRAYCOPY|, |ARRAYINITDATA|, |ARRAYINITELEM| + +* New :ref:`reference instructions ` for converting :ref:`host types `: |ANYCONVERTEXTERN|, |EXTERNCONVERTANY| + +* Extended set of :ref:`constant instructions ` with |REFI31|, |STRUCTNEW|, |STRUCTNEWDEFAULT|, |ARRAYNEW|, |ARRAYNEWDEFAULT|, |ARRAYNEWFIXED|, |ANYCONVERTEXTERN|, |EXTERNCONVERTANY| + + +.. [#proposal-extconst] + https://github.com/WebAssembly/extended-const/blob/main/proposals/extended-const/ + +.. [#proposal-tailcall] + https://github.com/WebAssembly/spec/tree/main/proposals/tail-call/ + +.. [#proposal-multimem] + https://github.com/WebAssembly/multi-memory/blob/main/proposals/multi-memory/ + +.. [#proposal-typedref] + https://github.com/WebAssembly/spec/tree/main/proposals/function-references/ + +.. [#proposal-gc] + https://github.com/WebAssembly/spec/tree/main/proposals/gc/ diff --git a/document/core/appendix/custom.rst b/document/core/appendix/custom.rst index c12ebf1830..ee9b39c908 100644 --- a/document/core/appendix/custom.rst +++ b/document/core/appendix/custom.rst @@ -60,6 +60,8 @@ Id Subsection 0 :ref:`module name ` 1 :ref:`function names ` 2 :ref:`local names ` + 4 :ref:`type names ` +10 :ref:`field names ` == =========================================== Each subsection may occur at most once, and in order of increasing id. @@ -143,3 +145,35 @@ It consists of an :ref:`indirect name map ` assigning lo \production{local name subsection} & \Blocalnamesubsec &::=& \Bnamesubsection_2(\Bindirectnamemap) \\ \end{array} + + +.. index:: type, type index +.. _binary-typenamesec: + +Type Names +.............. + +The *type name subsection* has the id 4. +It consists of a :ref:`name map ` assigning type names to :ref:`type indices `. + +.. math:: + \begin{array}{llclll} + \production{type name subsection} & \Btypenamesubsec &::=& + \Bnamesubsection_1(\Bnamemap) \\ + \end{array} + + +.. index:: type, field, type index, field index +.. _binary-fieldnamesec: + +Field Names +........... + +The *field name subsection* has the id 10. +It consists of an :ref:`indirect name map ` assigning field names to :ref:`field indices ` grouped by :ref:`type indices `. + +.. math:: + \begin{array}{llclll} + \production{field name subsection} & \Bfieldnamesubsec &::=& + \Bnamesubsection_2(\Bindirectnamemap) \\ + \end{array} diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index 96fa6b2fc3..b42e707cfc 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -24,6 +24,19 @@ Hence, these syntactic classes can also be interpreted as types. For numeric parameters, notation like :math:`n:\u32` is used to specify a symbolic name in addition to the respective value range. +.. _embed-bool: + +Booleans +~~~~~~~~ + +Interface operation that are predicates return Boolean values: + +.. math:: + \begin{array}{llll} + \production{Boolean} & \bool &::=& \FALSE ~|~ \TRUE \\ + \end{array} + + .. _embed-error: Errors @@ -80,7 +93,6 @@ Store \end{array} - .. index:: module .. _embed-module: @@ -133,7 +145,7 @@ Modules .. math:: \begin{array}{lclll} - \F{module\_validate}(m) &=& \epsilon && (\iff {} \vdashmodule m : \externtype^\ast \to {\externtype'}^\ast) \\ + \F{module\_validate}(m) &=& \epsilon && (\iff {} \vdashmodule m : \externtype^\ast \rightarrow {\externtype'}^\ast) \\ \F{module\_validate}(m) &=& \ERROR && (\otherwise) \\ \end{array} @@ -168,7 +180,7 @@ Modules :math:`\F{module\_imports}(\module) : (\name, \name, \externtype)^\ast` ....................................................................... -1. Pre-condition: :math:`\module` is :ref:`valid ` with external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. +1. Pre-condition: :math:`\module` is :ref:`valid ` with the external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. 2. Let :math:`\import^\ast` be the :ref:`imports ` :math:`\module.\MIMPORTS`. @@ -180,13 +192,13 @@ Modules 5. Return the concatenation of all :math:`\X{result}_i`, in index order. -6. Post-condition: each :math:`\externtype_i` is :ref:`valid `. +6. Post-condition: each :math:`\externtype_i` is :ref:`valid ` under the empty :ref:`context `. .. math:: ~ \\ \begin{array}{lclll} \F{module\_imports}(m) &=& (\X{im}.\IMODULE, \X{im}.\INAME, \externtype)^\ast \\ - && \qquad (\iff \X{im}^\ast = m.\MIMPORTS \wedge {} \vdashmodule m : \externtype^\ast \to {\externtype'}^\ast) \\ + && \qquad (\iff \X{im}^\ast = m.\MIMPORTS \wedge {} \vdashmodule m : \externtype^\ast \rightarrow {\externtype'}^\ast) \\ \end{array} @@ -196,7 +208,7 @@ Modules :math:`\F{module\_exports}(\module) : (\name, \externtype)^\ast` ................................................................ -1. Pre-condition: :math:`\module` is :ref:`valid ` with external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. +1. Pre-condition: :math:`\module` is :ref:`valid ` with the external import types :math:`\externtype^\ast` and external export types :math:`{\externtype'}^\ast`. 2. Let :math:`\export^\ast` be the :ref:`exports ` :math:`\module.\MEXPORTS`. @@ -208,13 +220,13 @@ Modules 5. Return the concatenation of all :math:`\X{result}_i`, in index order. -6. Post-condition: each :math:`\externtype'_i` is :ref:`valid `. +6. Post-condition: each :math:`\externtype'_i` is :ref:`valid ` under the empty :ref:`context `. .. math:: ~ \\ \begin{array}{lclll} \F{module\_exports}(m) &=& (\X{ex}.\ENAME, \externtype')^\ast \\ - && \qquad (\iff \X{ex}^\ast = m.\MEXPORTS \wedge {} \vdashmodule m : \externtype^\ast \to {\externtype'}^\ast) \\ + && \qquad (\iff \X{ex}^\ast = m.\MEXPORTS \wedge {} \vdashmodule m : \externtype^\ast \rightarrow {\externtype'}^\ast) \\ \end{array} @@ -258,7 +270,7 @@ Functions :math:`\F{func\_alloc}(\store, \functype, \hostfunc) : (\store, \funcaddr)` ........................................................................... -1. Pre-condition: :math:`\functype` is :ref:`valid `. +1. Pre-condition: the :math:`\functype` is :ref:`valid ` under the empty :ref:`context `. 2. Let :math:`\funcaddr` be the result of :ref:`allocating a host function ` in :math:`\store` with :ref:`function type ` :math:`\functype` and host function code :math:`\hostfunc`. @@ -266,7 +278,7 @@ Functions .. math:: \begin{array}{lclll} - \F{func\_alloc}(S, \X{ft}, \X{code}) &=& (S', \X{a}) && (\iff \allochostfunc(S, \X{ft}, \X{code}) = S', \X{a}) \\ + \F{func\_alloc}(S, \X{ta}, \X{code}) &=& (S', \X{a}) && (\iff \allochostfunc(S, \X{ta}, \X{code}) = S', \X{a}) \\ \end{array} .. note:: @@ -280,9 +292,11 @@ Functions :math:`\F{func\_type}(\store, \funcaddr) : \functype` ..................................................... -1. Return :math:`S.\SFUNCS[a].\FITYPE`. +1. Let :math:`\functype` be the :ref:`function type ` :math:`S.\SFUNCS[a].\FITYPE`. + +2. Return :math:`\functype`. -2. Post-condition: the returned :ref:`function type ` is :ref:`valid `. +3. Post-condition: the returned :ref:`function type ` is :ref:`valid `. .. math:: \begin{array}{lclll} @@ -326,7 +340,7 @@ Tables :math:`\F{table\_alloc}(\store, \tabletype, \reff) : (\store, \tableaddr)` .......................................................................... -1. Pre-condition: :math:`\tabletype` is :ref:`valid `. +1. Pre-condition: the :math:`\tabletype` is :ref:`valid ` under the empty :ref:`context `. 2. Let :math:`\tableaddr` be the result of :ref:`allocating a table ` in :math:`\store` with :ref:`table type ` :math:`\tabletype` and initialization value :math:`\reff`. @@ -345,7 +359,7 @@ Tables 1. Return :math:`S.\STABLES[a].\TITYPE`. -2. Post-condition: the returned :ref:`table type ` is :ref:`valid `. +2. Post-condition: the returned :ref:`table type ` is :ref:`valid ` under the empty :ref:`context `. .. math:: \begin{array}{lclll} @@ -438,7 +452,7 @@ Memories :math:`\F{mem\_alloc}(\store, \memtype) : (\store, \memaddr)` ................................................................ -1. Pre-condition: :math:`\memtype` is :ref:`valid `. +1. Pre-condition: the :math:`\memtype` is :ref:`valid ` under the empty :ref:`context `. 2. Let :math:`\memaddr` be the result of :ref:`allocating a memory ` in :math:`\store` with :ref:`memory type ` :math:`\memtype`. @@ -457,7 +471,7 @@ Memories 1. Return :math:`S.\SMEMS[a].\MITYPE`. -2. Post-condition: the returned :ref:`memory type ` is :ref:`valid `. +2. Post-condition: the returned :ref:`memory type ` is :ref:`valid ` under the empty :ref:`context `. .. math:: \begin{array}{lclll} @@ -551,7 +565,7 @@ Globals :math:`\F{global\_alloc}(\store, \globaltype, \val) : (\store, \globaladdr)` ............................................................................ -1. Pre-condition: :math:`\globaltype` is :ref:`valid `. +1. Pre-condition: the :math:`\globaltype` is :ref:`valid ` under the empty :ref:`context `. 2. Let :math:`\globaladdr` be the result of :ref:`allocating a global ` in :math:`\store` with :ref:`global type ` :math:`\globaltype` and initialization value :math:`\val`. @@ -570,7 +584,7 @@ Globals 1. Return :math:`S.\SGLOBALS[a].\GITYPE`. -2. Post-condition: the returned :ref:`global type ` is :ref:`valid `. +2. Post-condition: the returned :ref:`global type ` is :ref:`valid ` under the empty :ref:`context `. .. math:: \begin{array}{lclll} @@ -614,3 +628,83 @@ Globals \F{global\_write}(S, a, v) &=& S' && (\iff S.\SGLOBALS[a].\GITYPE = \MVAR~t \wedge S' = S \with \SGLOBALS[a].\GIVALUE = v) \\ \F{global\_write}(S, a, v) &=& \ERROR && (\otherwise) \\ \end{array} + + +.. index:: reference, reference type, value type, value +.. _embed-ref-type: +.. _embed-val-default: + +Values +~~~~~~ + +:math:`\F{ref\_type}(\store, \reff) : \reftype` +............................................... + +1. Pre-condition: the :ref:`reference ` :math:`\reff` is :ref:`valid ` under store :math:`S`. + +2. Return the :ref:`reference type ` :math:`t` with which :math:`\reff` is valid. + +3. Post-condition: the returned :ref:`reference type ` is :ref:`valid ` under the empty :ref:`context `. + +.. math:: + \begin{array}{lclll} + \F{ref\_type}(S, r) &=& t && (\iff S \vdashval r : t) \\ + \end{array} + +.. note:: + In future versions of WebAssembly, + not all references may carry precise type information at run time. + In such cases, this function may return a less precise supertype. + + +:math:`\F{val\_default}(\valtype) : \val` +............................................... + +1. If :math:`\default_{valtype}` is not defined, then return :math:`\ERROR`. + +1. Else, return the :ref:`value ` :math:`\default_{valtype}`. + +.. math:: + \begin{array}{lclll} + \F{val\_default}(t) &=& v && (\iff \default_t = v) \\ + \F{val\_default}(t) &=& \ERROR && (\iff \default_t = \epsilon) \\ + \end{array} + + +.. index:: value type, external type, subtyping +.. _embed-match-valtype: +.. _embed-match-externtype: + +Matching +~~~~~~~~ + +:math:`\F{match\_valtype}(\valtype_1, \valtype_2) : \bool` +.......................................................... + +1. Pre-condition: the :ref:`value types ` :math:`\valtype_1` and :math:`\valtype_2` are :ref:`valid ` under the empty :ref:`context `. + +2. If :math:`\valtype_1` :ref:`matches ` :math:`\valtype_2`, then return :math:`\TRUE`. + +3. Else, return :math:`\FALSE`. + +.. math:: + \begin{array}{lclll} + \F{match\_reftype}(t_1, t_2) &=& \TRUE && (\iff {} \vdashvaltypematch t_1 \matchesvaltype t_2) \\ + \F{match\_reftype}(t_1, t_2) &=& \FALSE && (\otherwise) \\ + \end{array} + + +:math:`\F{match\_externtype}(\externtype_1, \externtype_2) : \bool` +................................................................... + +1. Pre-condition: the :ref:`extern types ` :math:`\externtype_1` and :math:`\externtype_2` are :ref:`valid ` under the empty :ref:`context `. + +2. If :math:`\externtype_1` :ref:`matches ` :math:`\externtype_2`, then return :math:`\TRUE`. + +3. Else, return :math:`\FALSE`. + +.. math:: + \begin{array}{lclll} + \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \TRUE && (\iff {} \vdashexterntypematch \X{et}_1 \matchesexterntype \X{et}_2) \\ + \F{match\_externtype}(\X{et}_1, \X{et}_2) &=& \FALSE && (\otherwise) \\ + \end{array} diff --git a/document/core/appendix/implementation.rst b/document/core/appendix/implementation.rst index d5e53920fa..6de60fb587 100644 --- a/document/core/appendix/implementation.rst +++ b/document/core/appendix/implementation.rst @@ -41,16 +41,21 @@ An implementation may impose restrictions on the following dimensions of a modul * the number of :ref:`data segments ` in a :ref:`module ` * the number of :ref:`imports ` to a :ref:`module ` * the number of :ref:`exports ` from a :ref:`module ` +* the number of :ref:`sub types ` in a :ref:`recursive type ` +* the subtyping depth of a :ref:`sub type ` +* the number of fields in a :ref:`structure type ` * the number of parameters in a :ref:`function type ` * the number of results in a :ref:`function type ` * the number of parameters in a :ref:`block type ` * the number of results in a :ref:`block type ` * the number of :ref:`locals ` in a :ref:`function ` -* the size of a :ref:`function ` body -* the size of a :ref:`structured control instruction ` +* the number of :ref:`instructions ` in a :ref:`function ` body +* the number of :ref:`instructions ` in a :ref:`structured control instruction ` * the number of :ref:`structured control instructions ` in a :ref:`function ` * the nesting depth of :ref:`structured control instructions ` -* the number of :ref:`label indices ` in a |brtable| instruction +* the number of :ref:`label indices ` in a |BRTABLE| instruction +* the number of instructions in a :ref:`constant ` :ref:`expression ` +* the length of the array in a |ARRAYNEWFIXED| instruction * the length of an :ref:`element segment ` * the length of a :ref:`data segment ` * the length of a :ref:`name ` @@ -76,7 +81,9 @@ For a module given in :ref:`binary format `, additional limitations may * the size of a :ref:`module ` * the size of any :ref:`section ` -* the size of an individual function's :ref:`code ` +* the size of an individual :ref:`function`'s :ref:`code ` +* the size of a :ref:`structured control instruction ` +* the size of an individual :ref:`constant ` :ref:`expression`'s instruction sequence * the number of :ref:`sections ` @@ -124,8 +131,11 @@ Restrictions on the following dimensions may be imposed during :ref:`execution < * the number of allocated :ref:`table instances ` * the number of allocated :ref:`memory instances ` * the number of allocated :ref:`global instances ` +* the number of allocated :ref:`structure instances ` +* the number of allocated :ref:`array instances ` * the size of a :ref:`table instance ` * the size of a :ref:`memory instance ` +* the size of an :ref:`array instance ` * the number of :ref:`frames ` on the :ref:`stack ` * the number of :ref:`labels ` on the :ref:`stack ` * the number of :ref:`values ` on the :ref:`stack ` diff --git a/document/core/appendix/index-instructions.py b/document/core/appendix/index-instructions.py index ac94dd1acf..a5aade6638 100755 --- a/document/core/appendix/index-instructions.py +++ b/document/core/appendix/index-instructions.py @@ -86,10 +86,10 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\RETURN', r'\hex{0F}', r'[t_1^\ast~t^\ast] \to [t_2^\ast]', r'valid-return', r'exec-return'), Instruction(r'\CALL~x', r'\hex{10}', r'[t_1^\ast] \to [t_2^\ast]', r'valid-call', r'exec-call'), Instruction(r'\CALLINDIRECT~x~y', r'\hex{11}', r'[t_1^\ast~\I32] \to [t_2^\ast]', r'valid-call_indirect', r'exec-call_indirect'), - Instruction(None, r'\hex{12}'), - Instruction(None, r'\hex{13}'), - Instruction(None, r'\hex{14}'), - Instruction(None, r'\hex{15}'), + Instruction(r'\RETURNCALL~x', r'\hex{12}', r'[t_1^\ast] \to [t_2^\ast]', r'valid-return_call', r'exec-return_call'), + Instruction(r'\RETURNCALLINDIRECT~x~y', r'\hex{13}', r'[t_1^\ast~\I32] \to [t_2^\ast]', r'valid-return_call_indirect', r'exec-return_call_indirect'), + Instruction(r'\CALLREF~x', r'\hex{14}', r'[t_1^\ast~(\REF~\NULL~x)] \to [t_2^\ast]', r'valid-call_ref', r'exec-call_ref'), + Instruction(r'\RETURNCALLREF~x', r'\hex{15}', r'[t_1^\ast~(\REF~\NULL~x)] \to [t_2^\ast]', r'valid-return_call_ref', r'exec-return_call_ref'), Instruction(None, r'\hex{16}'), Instruction(None, r'\hex{17}'), Instruction(None, r'\hex{18}'), @@ -108,31 +108,31 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\TABLEGET~x', r'\hex{25}', r'[\I32] \to [t]', r'valid-table.get', r'exec-table.get'), Instruction(r'\TABLESET~x', r'\hex{26}', r'[\I32~t] \to []', r'valid-table.set', r'exec-table.set'), Instruction(None, r'\hex{27}'), - Instruction(r'\I32.\LOAD~\memarg', r'\hex{28}', r'[\I32] \to [\I32]', r'valid-load', r'exec-load'), - Instruction(r'\I64.\LOAD~\memarg', r'\hex{29}', r'[\I32] \to [\I64]', r'valid-load', r'exec-load'), - Instruction(r'\F32.\LOAD~\memarg', r'\hex{2A}', r'[\I32] \to [\F32]', r'valid-load', r'exec-load'), - Instruction(r'\F64.\LOAD~\memarg', r'\hex{2B}', r'[\I32] \to [\F64]', r'valid-load', r'exec-load'), - Instruction(r'\I32.\LOAD\K{8\_s}~\memarg', r'\hex{2C}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I32.\LOAD\K{8\_u}~\memarg', r'\hex{2D}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I32.\LOAD\K{16\_s}~\memarg', r'\hex{2E}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I32.\LOAD\K{16\_u}~\memarg', r'\hex{2F}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{8\_s}~\memarg', r'\hex{30}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{8\_u}~\memarg', r'\hex{31}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{16\_s}~\memarg', r'\hex{32}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{16\_u}~\memarg', r'\hex{33}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{32\_s}~\memarg', r'\hex{34}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I64.\LOAD\K{32\_u}~\memarg', r'\hex{35}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), - Instruction(r'\I32.\STORE~\memarg', r'\hex{36}', r'[\I32~\I32] \to []', r'valid-store', r'exec-store'), - Instruction(r'\I64.\STORE~\memarg', r'\hex{37}', r'[\I32~\I64] \to []', r'valid-store', r'exec-store'), - Instruction(r'\F32.\STORE~\memarg', r'\hex{38}', r'[\I32~\F32] \to []', r'valid-store', r'exec-store'), - Instruction(r'\F64.\STORE~\memarg', r'\hex{39}', r'[\I32~\F64] \to []', r'valid-store', r'exec-store'), - Instruction(r'\I32.\STORE\K{8}~\memarg', r'\hex{3A}', r'[\I32~\I32] \to []', r'valid-storen', r'exec-storen'), - Instruction(r'\I32.\STORE\K{16}~\memarg', r'\hex{3B}', r'[\I32~\I32] \to []', r'valid-storen', r'exec-storen'), - Instruction(r'\I64.\STORE\K{8}~\memarg', r'\hex{3C}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), - Instruction(r'\I64.\STORE\K{16}~\memarg', r'\hex{3D}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), - Instruction(r'\I64.\STORE\K{32}~\memarg', r'\hex{3E}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), - Instruction(r'\MEMORYSIZE', r'\hex{3F}', r'[] \to [\I32]', r'valid-memory.size', r'exec-memory.size'), - Instruction(r'\MEMORYGROW', r'\hex{40}', r'[\I32] \to [\I32]', r'valid-memory.grow', r'exec-memory.grow'), + Instruction(r'\I32.\LOAD~x~\memarg', r'\hex{28}', r'[\I32] \to [\I32]', r'valid-load', r'exec-load'), + Instruction(r'\I64.\LOAD~x~\memarg', r'\hex{29}', r'[\I32] \to [\I64]', r'valid-load', r'exec-load'), + Instruction(r'\F32.\LOAD~x~\memarg', r'\hex{2A}', r'[\I32] \to [\F32]', r'valid-load', r'exec-load'), + Instruction(r'\F64.\LOAD~x~\memarg', r'\hex{2B}', r'[\I32] \to [\F64]', r'valid-load', r'exec-load'), + Instruction(r'\I32.\LOAD\K{8\_s}~x~\memarg', r'\hex{2C}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I32.\LOAD\K{8\_u}~x~\memarg', r'\hex{2D}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I32.\LOAD\K{16\_s}~x~\memarg', r'\hex{2E}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I32.\LOAD\K{16\_u}~x~\memarg', r'\hex{2F}', r'[\I32] \to [\I32]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{8\_s}~x~\memarg', r'\hex{30}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{8\_u}~x~\memarg', r'\hex{31}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{16\_s}~x~\memarg', r'\hex{32}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{16\_u}~x~\memarg', r'\hex{33}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{32\_s}~x~\memarg', r'\hex{34}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I64.\LOAD\K{32\_u}~x~\memarg', r'\hex{35}', r'[\I32] \to [\I64]', r'valid-loadn', r'exec-loadn'), + Instruction(r'\I32.\STORE~x~\memarg', r'\hex{36}', r'[\I32~\I32] \to []', r'valid-store', r'exec-store'), + Instruction(r'\I64.\STORE~x~\memarg', r'\hex{37}', r'[\I32~\I64] \to []', r'valid-store', r'exec-store'), + Instruction(r'\F32.\STORE~x~\memarg', r'\hex{38}', r'[\I32~\F32] \to []', r'valid-store', r'exec-store'), + Instruction(r'\F64.\STORE~x~\memarg', r'\hex{39}', r'[\I32~\F64] \to []', r'valid-store', r'exec-store'), + Instruction(r'\I32.\STORE\K{8}~x~\memarg', r'\hex{3A}', r'[\I32~\I32] \to []', r'valid-storen', r'exec-storen'), + Instruction(r'\I32.\STORE\K{16}~x~\memarg', r'\hex{3B}', r'[\I32~\I32] \to []', r'valid-storen', r'exec-storen'), + Instruction(r'\I64.\STORE\K{8}~x~\memarg', r'\hex{3C}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), + Instruction(r'\I64.\STORE\K{16}~x~\memarg', r'\hex{3D}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), + Instruction(r'\I64.\STORE\K{32}~x~\memarg', r'\hex{3E}', r'[\I32~\I64] \to []', r'valid-storen', r'exec-storen'), + Instruction(r'\MEMORYSIZE~x', r'\hex{3F}', r'[] \to [\I32]', r'valid-memory.size', r'exec-memory.size'), + Instruction(r'\MEMORYGROW~x', r'\hex{40}', r'[\I32] \to [\I32]', r'valid-memory.grow', r'exec-memory.grow'), Instruction(r'\I32.\CONST~\i32', r'\hex{41}', r'[] \to [\I32]', r'valid-const', r'exec-const'), Instruction(r'\I64.\CONST~\i64', r'\hex{42}', r'[] \to [\I64]', r'valid-const', r'exec-const'), Instruction(r'\F32.\CONST~\f32', r'\hex{43}', r'[] \to [\F32]', r'valid-const', r'exec-const'), @@ -276,13 +276,13 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(None, r'\hex{CD}'), Instruction(None, r'\hex{CE}'), Instruction(None, r'\hex{CF}'), - Instruction(r'\REFNULL~t', r'\hex{D0}', r'[] \to [t]', r'valid-ref.null', r'exec-ref.null'), - Instruction(r'\REFISNULL', r'\hex{D1}', r'[t] \to [\I32]', r'valid-ref.is_null', r'exec-ref.is_null'), - Instruction(r'\REFFUNC~x', r'\hex{D2}', r'[] \to [\FUNCREF]', r'valid-ref.func', r'exec-ref.func'), - Instruction(None, r'\hex{D3}'), - Instruction(None, r'\hex{D4}'), - Instruction(None, r'\hex{D5}'), - Instruction(None, r'\hex{D6}'), + Instruction(r'\REFNULL~\X{ht}', r'\hex{D0}', r'[] \to [(\REF~\NULL~\X{ht})]', r'valid-ref.null', r'exec-ref.null'), + Instruction(r'\REFISNULL', r'\hex{D1}', r'[(\REF~\NULL~\X{ht})] \to [\I32]', r'valid-ref.is_null', r'exec-ref.is_null'), + Instruction(r'\REFFUNC~x', r'\hex{D2}', r'[] \to [\REF~\X{ht}]', r'valid-ref.func', r'exec-ref.func'), + Instruction(r'\REFEQ', r'\hex{D3}', r'[\EQREF~\EQREF] \to [\I32]', r'valid-ref.eq', r'exec-ref.eq'), + Instruction(r'\REFASNONNULL', r'\hex{D4}', r'[(\REF~\NULL~\X{ht})] \to [(\REF~\X{ht})]', r'valid-ref.as_non_null', r'exec-ref.as_non_null'), + Instruction(r'\BRONNULL~l', r'\hex{D5}', r'[t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast~(\REF~\X{ht})]', r'valid-br_on_null', r'exec-br_on_null'), + Instruction(r'\BRONNONNULL~l', r'\hex{D6}', r'[t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast]', r'valid-br_on_non_null', r'exec-br_on_non_null'), Instruction(None, r'\hex{D7}'), Instruction(None, r'\hex{D8}'), Instruction(None, r'\hex{D9}'), @@ -319,7 +319,38 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(None, r'\hex{F8}'), Instruction(None, r'\hex{F9}'), Instruction(None, r'\hex{FA}'), - Instruction(None, r'\hex{FB}'), + Instruction(r'\STRUCTNEW~x', r'\hex{FB}~\hex{00}', r'[t^\ast] \to [(\REF~x)]', r'valid-struct.new', r'exec-struct.new'), + Instruction(r'\STRUCTNEWDEFAULT~x', r'\hex{FB}~\hex{01}', r'[] \to [(\REF~x)]', r'valid-struct.new_default', r'exec-struct.new_default'), + Instruction(r'\STRUCTGET~x~y', r'\hex{FB}~\hex{02}', r'[(\REF~\NULL~x)] \to [t]', r'valid-struct.get', r'exec-struct.get'), + Instruction(r'\STRUCTGETS~x~y', r'\hex{FB}~\hex{03}', r'[(\REF~\NULL~x)] \to [\I32]', r'valid-struct.get', r'exec-struct.get'), + Instruction(r'\STRUCTGETU~x~y', r'\hex{FB}~\hex{04}', r'[(\REF~\NULL~x)] \to [\I32]', r'valid-struct.get', r'exec-struct.get'), + Instruction(r'\STRUCTSET~x~y', r'\hex{FB}~\hex{05}', r'[(\REF~\NULL~x)~t] \to []', r'valid-struct.set', r'exec-struct.set'), + Instruction(r'\ARRAYNEW~x', r'\hex{FB}~\hex{06}', r'[t] \to [(\REF~x)]', r'valid-array.new', r'exec-array.new'), + Instruction(r'\ARRAYNEWDEFAULT~x', r'\hex{FB}~\hex{07}', r'[\I32] \to [(\REF~x)]', r'valid-array.new', r'exec-array.new'), + Instruction(r'\ARRAYNEWFIXED~x~n', r'\hex{FB}~\hex{08}', r'[t^n] \to [(\REF~x)]', r'valid-array.new_fixed', r'exec-array.new_fixed'), + Instruction(r'\ARRAYNEWDATA~x~y', r'\hex{FB}~\hex{09}', r'[\I32~\I32] \to [(\REF~x)]', r'valid-array.new_data', r'exec-array.new_data'), + Instruction(r'\ARRAYNEWELEM~x~y', r'\hex{FB}~\hex{0A}', r'[\I32~\I32] \to [(\REF~x)]', r'valid-array.new_elem', r'exec-array.new_elem'), + Instruction(r'\ARRAYGET~x', r'\hex{FB}~\hex{0B}', r'[(\REF~\NULL~x)~\I32] \to [t]', r'valid-array.get', r'exec-array.get'), + Instruction(r'\ARRAYGETS~x', r'\hex{FB}~\hex{0C}', r'[(\REF~\NULL~x)~\I32] \to [\I32]', r'valid-array.get', r'exec-array.get'), + Instruction(r'\ARRAYGETU~x', r'\hex{FB}~\hex{0D}', r'[(\REF~\NULL~x)~\I32] \to [\I32]', r'valid-array.get', r'exec-array.get'), + Instruction(r'\ARRAYSET~x', r'\hex{FB}~\hex{0E}', r'[(\REF~\NULL~x)~\I32~t] \to []', r'valid-array.set', r'exec-array.set'), + Instruction(r'\ARRAYLEN', r'\hex{FB}~\hex{0F}', r'[(\REF~\NULL~\ARRAY)] \to [\I32]', r'valid-array.len', r'exec-array.len'), + Instruction(r'\ARRAYFILL~x', r'\hex{FB}~\hex{10}', r'[(\REF~\NULL~x)~\I32~t~\I32] \to []', r'valid-array.fill', r'exec-array.fill'), + Instruction(r'\ARRAYCOPY~x~y', r'\hex{FB}~\hex{11}', r'[(\REF~\NULL~x)~\I32~(\REF~\NULL~y)~\I32~\I32] \to []', r'valid-array.copy', r'exec-array.copy'), + Instruction(r'\ARRAYINITDATA~x~y', r'\hex{FB}~\hex{12}', r'[(\REF~\NULL~x)~\I32~\I32~\I32] \to []', r'valid-array.init_data', r'exec-array.init_data'), + Instruction(r'\ARRAYINITELEM~x~y', r'\hex{FB}~\hex{13}', r'[(\REF~\NULL~x)~\I32~\I32~\I32] \to []', r'valid-array.init_elem', r'exec-array.init_elem'), + Instruction(r'\REFTEST~(\REF~t)', r'\hex{FB}~\hex{14}', r"[(\REF~t')] \to [\I32]", r'valid-ref.test', r'exec-ref.test'), + Instruction(r'\REFTEST~(\REF~\NULL~t)', r'\hex{FB}~\hex{15}', r"[(REF~\NULL~t')] \to [\I32]", r'valid-ref.test', r'exec-ref.test'), + Instruction(r'\REFCAST~(\REF~t)', r'\hex{FB}~\hex{16}', r"[(\REF~t')] \to [(\REF~t)]", r'valid-ref.cast', r'exec-ref.cast'), + Instruction(r'\REFCAST~(\REF~\NULL~t)', r'\hex{FB}~\hex{17}', r"[(\REF~\NULL~t')] \to [(\REF~\NULL~t)]", r'valid-ref.cast', r'exec-ref.cast'), + Instruction(r'\BRONCAST~t_1~t_2', r'\hex{FB}~\hex{18}', r'[t_1] \to [t_1\reftypediff t_2]', r'valid-br_on_cast', r'exec-br_on_cast'), + Instruction(r'\BRONCASTFAIL~t_1~t_2', r'\hex{FB}~\hex{19}', r'[t_1] \to [t_2]', r'valid-br_on_cast_fail', r'exec-br_on_cast_fail'), + Instruction(r'\ANYCONVERTEXTERN', r'\hex{FB}~\hex{1A}', r'[(\REF~\NULL~\EXTERN)] \to [(\REF~\NULL~\ANY)]', r'valid-any.convert_extern', r'exec-any.convert_extern'), + Instruction(r'\EXTERNCONVERTANY', r'\hex{FB}~\hex{1B}', r'[(\REF~\NULL~\ANY)] \to [(\REF~\NULL~\EXTERN)]', r'valid-extern.convert_any', r'exec-extern.convert_any'), + Instruction(r'\REFI31', r'\hex{FB}~\hex{1C}', r'[\I32] \to [(\REF~\I31)]', r'valid-ref.i31', r'exec-ref.i31'), + Instruction(r'\I31GETS', r'\hex{FB}~\hex{1D}', r'[\I31REF] \to [\I32]', r'valid-i31.get_sx', r'exec-i31.get_sx'), + Instruction(r'\I31GETU', r'\hex{FB}~\hex{1E}', r'[\I31REF] \to [\I32]', r'valid-i31.get_sx', r'exec-i31.get_sx'), + Instruction(None, r'\hex{FB}~\hex{1E} \dots'), Instruction(r'\I32.\TRUNC\K{\_sat\_}\F32\K{\_s}', r'\hex{FC}~\hex{00}', r'[\F32] \to [\I32]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_s'), Instruction(r'\I32.\TRUNC\K{\_sat\_}\F32\K{\_u}', r'\hex{FC}~\hex{01}', r'[\F32] \to [\I32]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_u'), Instruction(r'\I32.\TRUNC\K{\_sat\_}\F64\K{\_s}', r'\hex{FC}~\hex{02}', r'[\F64] \to [\I32]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_s'), @@ -328,28 +359,29 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I64.\TRUNC\K{\_sat\_}\F32\K{\_u}', r'\hex{FC}~\hex{05}', r'[\F32] \to [\I64]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_u'), Instruction(r'\I64.\TRUNC\K{\_sat\_}\F64\K{\_s}', r'\hex{FC}~\hex{06}', r'[\F64] \to [\I64]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_s'), Instruction(r'\I64.\TRUNC\K{\_sat\_}\F64\K{\_u}', r'\hex{FC}~\hex{07}', r'[\F64] \to [\I64]', r'valid-cvtop', r'exec-cvtop', r'op-trunc_sat_u'), - Instruction(r'\MEMORYINIT~x', r'\hex{FC}~\hex{08}', r'[\I32~\I32~\I32] \to []', r'valid-memory.init', r'exec-memory.init'), + Instruction(r'\MEMORYINIT~x~y', r'\hex{FC}~\hex{08}', r'[\I32~\I32~\I32] \to []', r'valid-memory.init', r'exec-memory.init'), Instruction(r'\DATADROP~x', r'\hex{FC}~\hex{09}', r'[] \to []', r'valid-data.drop', r'exec-data.drop'), - Instruction(r'\MEMORYCOPY', r'\hex{FC}~\hex{0A}', r'[\I32~\I32~\I32] \to []', r'valid-memory.copy', r'exec-memory.copy'), - Instruction(r'\MEMORYFILL', r'\hex{FC}~\hex{0B}', r'[\I32~\I32~\I32] \to []', r'valid-memory.fill', r'exec-memory.fill'), + Instruction(r'\MEMORYCOPY~x~y', r'\hex{FC}~\hex{0A}', r'[\I32~\I32~\I32] \to []', r'valid-memory.copy', r'exec-memory.copy'), + Instruction(r'\MEMORYFILL~y', r'\hex{FC}~\hex{0B}', r'[\I32~\I32~\I32] \to []', r'valid-memory.fill', r'exec-memory.fill'), Instruction(r'\TABLEINIT~x~y', r'\hex{FC}~\hex{0C}', r'[\I32~\I32~\I32] \to []', r'valid-table.init', r'exec-table.init'), Instruction(r'\ELEMDROP~x', r'\hex{FC}~\hex{0D}', r'[] \to []', r'valid-elem.drop', r'exec-elem.drop'), Instruction(r'\TABLECOPY~x~y', r'\hex{FC}~\hex{0E}', r'[\I32~\I32~\I32] \to []', r'valid-table.copy', r'exec-table.copy'), Instruction(r'\TABLEGROW~x', r'\hex{FC}~\hex{0F}', r'[t~\I32] \to [\I32]', r'valid-table.grow', r'exec-table.grow'), Instruction(r'\TABLESIZE~x', r'\hex{FC}~\hex{10}', r'[] \to [\I32]', r'valid-table.size', r'exec-table.size'), Instruction(r'\TABLEFILL~x', r'\hex{FC}~\hex{11}', r'[\I32~t~\I32] \to []', r'valid-table.fill', r'exec-table.fill'), - Instruction(r'\V128.\LOAD~\memarg', r'\hex{FD}~~\hex{00}', r'[\I32] \to [\V128]', r'valid-load', r'exec-load'), - Instruction(r'\V128.\LOAD\K{8x8\_s}~\memarg', r'\hex{FD}~~\hex{01}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{8x8\_u}~\memarg', r'\hex{FD}~~\hex{02}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{16x4\_s}~\memarg', r'\hex{FD}~~\hex{03}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{16x4\_u}~\memarg', r'\hex{FD}~~\hex{04}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{32x2\_s}~\memarg', r'\hex{FD}~~\hex{05}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{32x2\_u}~\memarg', r'\hex{FD}~~\hex{06}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), - Instruction(r'\V128.\LOAD\K{8\_splat}~\memarg', r'\hex{FD}~~\hex{07}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), - Instruction(r'\V128.\LOAD\K{16\_splat}~\memarg', r'\hex{FD}~~\hex{08}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), - Instruction(r'\V128.\LOAD\K{32\_splat}~\memarg', r'\hex{FD}~~\hex{09}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), - Instruction(r'\V128.\LOAD\K{64\_splat}~\memarg', r'\hex{FD}~~\hex{0A}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), - Instruction(r'\V128.\STORE~\memarg', r'\hex{FD}~~\hex{0B}', r'[\I32~\V128] \to []', r'valid-store', r'exec-store'), + Instruction(None, r'\hex{FC}~\hex{1E} \dots'), + Instruction(r'\V128.\LOAD~x~\memarg', r'\hex{FD}~~\hex{00}', r'[\I32] \to [\V128]', r'valid-load', r'exec-load'), + Instruction(r'\V128.\LOAD\K{8x8\_s}~x~\memarg', r'\hex{FD}~~\hex{01}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{8x8\_u}~x~\memarg', r'\hex{FD}~~\hex{02}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{16x4\_s}~x~\memarg', r'\hex{FD}~~\hex{03}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{16x4\_u}~x~\memarg', r'\hex{FD}~~\hex{04}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{32x2\_s}~x~\memarg', r'\hex{FD}~~\hex{05}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{32x2\_u}~x~\memarg', r'\hex{FD}~~\hex{06}', r'[\I32] \to [\V128]', r'valid-load-extend', r'exec-load-extend'), + Instruction(r'\V128.\LOAD\K{8\_splat}~x~\memarg', r'\hex{FD}~~\hex{07}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), + Instruction(r'\V128.\LOAD\K{16\_splat}~x~\memarg', r'\hex{FD}~~\hex{08}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), + Instruction(r'\V128.\LOAD\K{32\_splat}~x~\memarg', r'\hex{FD}~~\hex{09}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), + Instruction(r'\V128.\LOAD\K{64\_splat}~x~\memarg', r'\hex{FD}~~\hex{0A}', r'[\I32] \to [\V128]', r'valid-load-splat', r'exec-load-splat'), + Instruction(r'\V128.\STORE~x~\memarg', r'\hex{FD}~~\hex{0B}', r'[\I32~\V128] \to []', r'valid-store', r'exec-store'), Instruction(r'\V128.\VCONST~\i128', r'\hex{FD}~~\hex{0C}', r'[] \to [\V128]', r'valid-vconst', r'exec-vconst'), Instruction(r'\I8X16.\SHUFFLE~\laneidx^{16}', r'\hex{FD}~~\hex{0D}', r'[\V128~\V128] \to [\V128]', r'valid-vec-shuffle', r'exec-vec-shuffle'), Instruction(r'\I8X16.\SWIZZLE', r'\hex{FD}~~\hex{0E}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vec-swizzle'), @@ -492,6 +524,7 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I16X8.\VMIN\K{\_u}', r'\hex{FD}~~\hex{97}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imin_u'), Instruction(r'\I16X8.\VMAX\K{\_s}', r'\hex{FD}~~\hex{98}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imax_s'), Instruction(r'\I16X8.\VMAX\K{\_u}', r'\hex{FD}~~\hex{99}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imax_u'), + Instruction(None, r'\hex{FD}~\hex{9A}~\hex{01}'), Instruction(r'\I16X8.\AVGR\K{\_u}', r'\hex{FD}~~\hex{9B}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-iavgr_u'), Instruction(r'\I16X8.\EXTMUL\K{\_low\_i8x16\_s}', r'\hex{FD}~~\hex{9C}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vec-extmul', r'exec-vec-extmul'), Instruction(r'\I16X8.\EXTMUL\K{\_high\_i8x16\_s}', r'\hex{FD}~~\hex{9D}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vec-extmul', r'exec-vec-extmul'), @@ -499,8 +532,11 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I16X8.\EXTMUL\K{\_high\_i8x16\_u}', r'\hex{FD}~~\hex{9F}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vec-extmul', r'exec-vec-extmul'), Instruction(r'\I32X4.\VABS', r'\hex{FD}~~\hex{A0}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-iabs'), Instruction(r'\I32X4.\VNEG', r'\hex{FD}~~\hex{A1}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-ineg'), + Instruction(None, r'\hex{FD}~\hex{A2}~\hex{01}'), Instruction(r'\I32X4.\ALLTRUE', r'\hex{FD}~~\hex{A3}~~\hex{01}', r'[\V128] \to [\I32]', r'valid-vtestop', r'exec-vtestop'), Instruction(r'\I32X4.\BITMASK', r'\hex{FD}~~\hex{A4}~~\hex{01}', r'[\V128] \to [\I32]', r'valid-vec-bitmask', r'exec-vec-bitmask'), + Instruction(None, r'\hex{FD}~\hex{A5}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{A6}~\hex{01}'), Instruction(r'\I32X4.\VEXTEND\K{\_low\_i16x8\_s}', r'\hex{FD}~~\hex{A7}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), Instruction(r'\I32X4.\VEXTEND\K{\_high\_i16x8\_s}', r'\hex{FD}~~\hex{A8}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), Instruction(r'\I32X4.\VEXTEND\K{\_low\_i16x8\_u}', r'\hex{FD}~~\hex{A9}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), @@ -509,7 +545,12 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I32X4.\VSHR\K{\_s}', r'\hex{FD}~~\hex{AC}~~\hex{01}', r'[\V128~\I32] \to [\V128]', r'valid-vishiftop', r'exec-vishiftop', r'op-ishr_s'), Instruction(r'\I32X4.\VSHR\K{\_u}', r'\hex{FD}~~\hex{AD}~~\hex{01}', r'[\V128~\I32] \to [\V128]', r'valid-vishiftop', r'exec-vishiftop', r'op-ishr_u'), Instruction(r'\I32X4.\VADD', r'\hex{FD}~~\hex{AE}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-iadd'), + Instruction(None, r'\hex{FD}~\hex{AF}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{B0}~\hex{01}'), Instruction(r'\I32X4.\VSUB', r'\hex{FD}~~\hex{B1}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-isub'), + Instruction(None, r'\hex{FD}~\hex{B2}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{B3}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{B4}~\hex{01}'), Instruction(r'\I32X4.\VMUL', r'\hex{FD}~~\hex{B5}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imul'), Instruction(r'\I32X4.\VMIN\K{\_s}', r'\hex{FD}~~\hex{B6}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imin_s'), Instruction(r'\I32X4.\VMIN\K{\_u}', r'\hex{FD}~~\hex{B7}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imin_u'), @@ -522,8 +563,11 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I32X4.\EXTMUL\K{\_high\_i16x8\_u}', r'\hex{FD}~~\hex{BF}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vec-extmul', r'exec-vec-extmul'), Instruction(r'\I64X2.\VABS', r'\hex{FD}~~\hex{C0}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-iabs'), Instruction(r'\I64X2.\VNEG', r'\hex{FD}~~\hex{C1}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-ineg'), + Instruction(None, r'\hex{FD}~\hex{C2}~\hex{01}'), Instruction(r'\I64X2.\ALLTRUE', r'\hex{FD}~~\hex{C3}~~\hex{01}', r'[\V128] \to [\I32]', r'valid-vtestop', r'exec-vtestop'), Instruction(r'\I64X2.\BITMASK', r'\hex{FD}~~\hex{C4}~~\hex{01}', r'[\V128] \to [\I32]', r'valid-vec-bitmask', r'exec-vec-bitmask'), + Instruction(None, r'\hex{FD}~\hex{C5}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{C6}~\hex{01}'), Instruction(r'\I64X2.\VEXTEND\K{\_low\_i32x4\_s}', r'\hex{FD}~~\hex{C7}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), Instruction(r'\I64X2.\VEXTEND\K{\_high\_i32x4\_s}', r'\hex{FD}~~\hex{C8}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), Instruction(r'\I64X2.\VEXTEND\K{\_low\_i32x4\_u}', r'\hex{FD}~~\hex{C9}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vcvtop'), @@ -532,7 +576,12 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I64X2.\VSHR\K{\_s}', r'\hex{FD}~~\hex{CC}~~\hex{01}', r'[\V128~\I32] \to [\V128]', r'valid-vishiftop', r'exec-vishiftop', r'op-ishr_s'), Instruction(r'\I64X2.\VSHR\K{\_u}', r'\hex{FD}~~\hex{CD}~~\hex{01}', r'[\V128~\I32] \to [\V128]', r'valid-vishiftop', r'exec-vishiftop', r'op-ishr_u'), Instruction(r'\I64X2.\VADD', r'\hex{FD}~~\hex{CE}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-iadd'), + Instruction(None, r'\hex{FD}~\hex{CF}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{D0}~\hex{01}'), Instruction(r'\I64X2.\VSUB', r'\hex{FD}~~\hex{D1}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-isub'), + Instruction(None, r'\hex{FD}~\hex{D2}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{D3}~\hex{01}'), + Instruction(None, r'\hex{FD}~\hex{D4}~\hex{01}'), Instruction(r'\I64X2.\VMUL', r'\hex{FD}~~\hex{D5}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-imul'), Instruction(r'\I64X2.\VEQ', r'\hex{FD}~~\hex{D6}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-ieq'), Instruction(r'\I64X2.\VNE', r'\hex{FD}~~\hex{D7}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-ine'), @@ -546,6 +595,7 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I64X2.\EXTMUL\K{\_high\_i32x4\_u}', r'\hex{FD}~~\hex{DF}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vec-extmul', r'exec-vec-extmul'), Instruction(r'\F32X4.\VABS', r'\hex{FD}~~\hex{E0}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-fabs'), Instruction(r'\F32X4.\VNEG', r'\hex{FD}~~\hex{E1}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-fneg'), + Instruction(None, r'\hex{FD}~\hex{E2}~\hex{01}'), Instruction(r'\F32X4.\VSQRT', r'\hex{FD}~~\hex{E3}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vunop', r'exec-vunop', r'op-fsqrt'), Instruction(r'\F32X4.\VADD', r'\hex{FD}~~\hex{E4}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-fadd'), Instruction(r'\F32X4.\VSUB', r'\hex{FD}~~\hex{E5}~~\hex{01}', r'[\V128~\V128] \to [\V128]', r'valid-vbinop', r'exec-vbinop', r'op-fsub'), @@ -574,9 +624,13 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\I32X4.\VTRUNC\K{\_sat\_f64x2\_u\_zero}', r'\hex{FD}~~\hex{FD}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vcvtop', r'exec-vcvtop', r'op-trunc_sat_u'), Instruction(r'\F64X2.\VCONVERT\K{\_low\_i32x4\_s}', r'\hex{FD}~~\hex{FE}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vcvtop', r'exec-vcvtop', r'op-convert_s'), Instruction(r'\F64X2.\VCONVERT\K{\_low\_i32x4\_u}', r'\hex{FD}~~\hex{FF}~~\hex{01}', r'[\V128] \to [\V128]', r'valid-vcvtop', r'exec-vcvtop', r'op-convert_u'), + Instruction(None, r'\hex{FD}~\hex{00}~\hex{02} \dots'), + Instruction(None, r'\hex{FE}'), + Instruction(None, r'\hex{FF}'), ] + def ColumnWidth(n): return max([len(instr[n]) for instr in INSTRUCTIONS]) diff --git a/document/core/appendix/index-rules.rst b/document/core/appendix/index-rules.rst index 8fa955d191..eefd550ee4 100644 --- a/document/core/appendix/index-rules.rst +++ b/document/core/appendix/index-rules.rst @@ -4,28 +4,55 @@ Index of Semantic Rules ----------------------- -.. index:: validation +.. index:: validation, type .. _index-valid: +Well-formedness of Types +~~~~~~~~~~~~~~~~~~~~~~~~ + +=============================================== =============================================================================== +Construct Judgement +=============================================== =============================================================================== +:ref:`Numeric type ` :math:`C \vdashnumtype \numtype \ok` +:ref:`Vector type ` :math:`C \vdashvectype \vectype \ok` +:ref:`Heap type ` :math:`C \vdashheaptype \heaptype \ok` +:ref:`Reference type ` :math:`C \vdashreftype \reftype \ok` +:ref:`Value type ` :math:`C \vdashvaltype \valtype \ok` +:ref:`Packed type ` :math:`C \vdashpackedtype \packedtype \ok` +:ref:`Storage type ` :math:`C \vdashstoragetype \storagetype \ok` +:ref:`Field type ` :math:`C \vdashfieldtype \fieldtype \ok` +:ref:`Result type ` :math:`C \vdashresulttype \resulttype \ok` +:ref:`Instruction type ` :math:`C \vdashinstrtype \instrtype \ok` +:ref:`Function type ` :math:`C \vdashfunctype \functype \ok` +:ref:`Structure type ` :math:`C \vdashstructtype \structtype \ok` +:ref:`Array type ` :math:`C \vdasharraytype \arraytype \ok` +:ref:`Composite type ` :math:`C \vdashcomptype \comptype \ok` +:ref:`Sub type ` :math:`C \vdashsubtype \subtype \ok` +:ref:`Recursive type ` :math:`C \vdashrectype \rectype \ok` +:ref:`Defined type ` :math:`C \vdashdeftype \deftype \ok` +:ref:`Block type ` :math:`C \vdashblocktype \blocktype : \instrtype` +:ref:`Table type ` :math:`C \vdashtabletype \tabletype \ok` +:ref:`Memory type ` :math:`C \vdashmemtype \memtype \ok` +:ref:`Global type ` :math:`C \vdashglobaltype \globaltype \ok` +:ref:`External type ` :math:`C \vdashexterntype \externtype \ok` +:ref:`Type definitions ` :math:`C \vdashtypes \type^\ast \ok` +=============================================== =============================================================================== + + Typing of Static Constructs ~~~~~~~~~~~~~~~~~~~~~~~~~~~ =============================================== =============================================================================== Construct Judgement =============================================== =============================================================================== -:ref:`Limits ` :math:`\vdashlimits \limits : k` -:ref:`Function type ` :math:`\vdashfunctype \functype \ok` -:ref:`Block type ` :math:`\vdashblocktype \blocktype \ok` -:ref:`Table type ` :math:`\vdashtabletype \tabletype \ok` -:ref:`Memory type ` :math:`\vdashmemtype \memtype \ok` -:ref:`Global type ` :math:`\vdashglobaltype \globaltype \ok` -:ref:`External type ` :math:`\vdashexterntype \externtype \ok` -:ref:`Instruction ` :math:`S;C \vdashinstr \instr : \stacktype` -:ref:`Instruction sequence ` :math:`S;C \vdashinstrseq \instr^\ast : \stacktype` +:ref:`Instruction ` :math:`S;C \vdashinstr \instr : \functype` +:ref:`Instruction sequence ` :math:`S;C \vdashinstrseq \instr^\ast : \functype` :ref:`Expression ` :math:`C \vdashexpr \expr : \resulttype` :ref:`Function ` :math:`C \vdashfunc \func : \functype` +:ref:`Local ` :math:`C \vdashlocal \local : \localtype` :ref:`Table ` :math:`C \vdashtable \table : \tabletype` :ref:`Memory ` :math:`C \vdashmem \mem : \memtype` +:ref:`Limits ` :math:`C \vdashlimits \limits : k` :ref:`Global ` :math:`C \vdashglobal \global : \globaltype` :ref:`Element segment ` :math:`C \vdashelem \elem : \reftype` :ref:`Element mode ` :math:`C \vdashelemmode \elemmode : \reftype` @@ -36,7 +63,7 @@ Construct Judgement :ref:`Export description ` :math:`C \vdashexportdesc \exportdesc : \externtype` :ref:`Import ` :math:`C \vdashimport \import : \externtype` :ref:`Import description ` :math:`C \vdashimportdesc \importdesc : \externtype` -:ref:`Module ` :math:`\vdashmodule \module : \externtype^\ast \to \externtype^\ast` +:ref:`Module ` :math:`\vdashmodule \module : \externtype^\ast \rightarrow \externtype^\ast` =============================================== =============================================================================== @@ -50,6 +77,8 @@ Construct Judgement =============================================== =============================================================================== :ref:`Value ` :math:`S \vdashval \val : \valtype` :ref:`Result ` :math:`S \vdashresult \result : \resulttype` +:ref:`Packed value ` :math:`S \vdashpackedval \packedval : \packedtype` +:ref:`Field value ` :math:`S \vdashfieldval \fieldval : \storagetype` :ref:`External value ` :math:`S \vdashexternval \externval : \externtype` :ref:`Function instance ` :math:`S \vdashfuncinst \funcinst : \functype` :ref:`Table instance ` :math:`S \vdashtableinst \tableinst : \tabletype` @@ -57,6 +86,8 @@ Construct Judgement :ref:`Global instance ` :math:`S \vdashglobalinst \globalinst : \globaltype` :ref:`Element instance ` :math:`S \vdasheleminst \eleminst : t` :ref:`Data instance ` :math:`S \vdashdatainst \datainst \ok` +:ref:`Structure instance ` :math:`S \vdashstructinst \structinst \ok` +:ref:`Array instance ` :math:`S \vdasharrayinst \arrayinst \ok` :ref:`Export instance ` :math:`S \vdashexportinst \exportinst \ok` :ref:`Module instance ` :math:`S \vdashmoduleinst \moduleinst : C` :ref:`Store ` :math:`\vdashstore \store \ok` @@ -66,6 +97,16 @@ Construct Judgement =============================================== =============================================================================== +Defaultability +~~~~~~~~~~~~~~ + +================================================= =============================================================================== +Construct Judgement +================================================= =============================================================================== +:ref:`Defaultable value type ` :math:`C \vdashvaltypedefaultable \valtype \defaultable` +================================================= =============================================================================== + + Constantness ~~~~~~~~~~~~ @@ -80,12 +121,30 @@ Construct Judgement Matching ~~~~~~~~ -=============================================== =============================================================================== +=============================================== ================================================================================== Construct Judgement -=============================================== =============================================================================== -:ref:`External type ` :math:`\vdashexterntypematch \externtype_1 \matchesexterntype \externtype_2` -:ref:`Limits ` :math:`\vdashlimitsmatch \limits_1 \matcheslimits \limits_2` -=============================================== =============================================================================== +=============================================== ================================================================================== +:ref:`Number type ` :math:`C \vdashnumtypematch \numtype_1 \matchesnumtype \numtype_2` +:ref:`Vector type ` :math:`C \vdashvectypematch \vectype_1 \matchesvectype \vectype_2` +:ref:`Heap type ` :math:`C \vdashheaptypematch \heaptype_1 \matchesheaptype \heaptype_2` +:ref:`Reference type ` :math:`C \vdashreftypematch \reftype_1 \matchesreftype \reftype_2` +:ref:`Value type ` :math:`C \vdashvaltypematch \valtype_1 \matchesvaltype \valtype_2` +:ref:`Packed type ` :math:`C \vdashpackedtypematch \packedtype_1 \matchespackedtype \packedtype_2` +:ref:`Storage type ` :math:`C \vdashstoragetypematch \storagetype_1 \matchesstoragetype \storagetype_2` +:ref:`Field type ` :math:`C \vdashfieldtypematch \fieldtype_1 \matchesfieldtype \fieldtype_2` +:ref:`Result type ` :math:`C \vdashresulttypematch \resulttype_1 \matchesresulttype \resulttype_2` +:ref:`Instruction type ` :math:`C \vdashinstrtypematch \instrtype_1 \matchesinstrtype \instrtype_2` +:ref:`Function type ` :math:`C \vdashfunctypematch \functype_1 \matchesfunctype \functype_2` +:ref:`Structure type ` :math:`C \vdashstructtypematch \structtype_1 \matchesstructtype \structtype_2` +:ref:`Array type ` :math:`C \vdasharraytypematch \arraytype_1 \matchesarraytype \arraytype_2` +:ref:`Composite type ` :math:`C \vdashcomptypematch \comptype_1 \matchescomptype \comptype_2` +:ref:`Defined type ` :math:`C \vdashdeftypematch \deftype_1 \matchesdeftype \deftype_2` +:ref:`Table type ` :math:`C \vdashtabletypematch \tabletype_1 \matchestabletype \tabletype_2` +:ref:`Memory type ` :math:`C \vdashmemtypematch \memtype_1 \matchesmemtype \memtype_2` +:ref:`Global type ` :math:`C \vdashglobaltypematch \globaltype_1 \matchesglobaltype \globaltype_2` +:ref:`External type ` :math:`C \vdashexterntypematch \externtype_1 \matchesexterntype \externtype_2` +:ref:`Limits ` :math:`C \vdashlimitsmatch \limits_1 \matcheslimits \limits_2` +=============================================== ================================================================================== Store Extension @@ -98,8 +157,10 @@ Construct Judgement :ref:`Table instance ` :math:`\vdashtableinstextends \tableinst_1 \extendsto \tableinst_2` :ref:`Memory instance ` :math:`\vdashmeminstextends \meminst_1 \extendsto \meminst_2` :ref:`Global instance ` :math:`\vdashglobalinstextends \globalinst_1 \extendsto \globalinst_2` -:ref:`Element instance ` :math:`\vdasheleminstextends \eleminst_1 \extendsto \eleminst_2` +:ref:`Element instance ` :math:`\vdasheleminstextends \eleminst_1 \extendsto \eleminst_2` :ref:`Data instance ` :math:`\vdashdatainstextends \datainst_1 \extendsto \datainst_2` +:ref:`Structure instance ` :math:`\vdashstructinstextends \structinst_1 \extendsto \structinst_2` +:ref:`Array instance ` :math:`\vdasharrayinstextends \arrayinst_1 \extendsto \arrayinst_2` :ref:`Store ` :math:`\vdashstoreextends \store_1 \extendsto \store_2` =============================================== =============================================================================== diff --git a/document/core/appendix/index-types.rst b/document/core/appendix/index-types.rst index 715233ceae..bbf638bfa9 100644 --- a/document/core/appendix/index-types.rst +++ b/document/core/appendix/index-types.rst @@ -4,23 +4,43 @@ Index of Types -------------- -======================================== =========================================== =============================================================================== +======================================== ================================================== =============================================================== Category Constructor Binary Opcode -======================================== =========================================== =============================================================================== -:ref:`Type index ` :math:`x` (positive number as |Bs32| or |Bu32|) -:ref:`Number type ` |I32| :math:`\hex{7F}` (-1 as |Bs7|) -:ref:`Number type ` |I64| :math:`\hex{7E}` (-2 as |Bs7|) -:ref:`Number type ` |F32| :math:`\hex{7D}` (-3 as |Bs7|) -:ref:`Number type ` |F64| :math:`\hex{7C}` (-4 as |Bs7|) -:ref:`Vector type ` |V128| :math:`\hex{7B}` (-5 as |Bs7|) -(reserved) :math:`\hex{7A}` .. :math:`\hex{71}` -:ref:`Reference type ` |FUNCREF| :math:`\hex{70}` (-16 as |Bs7|) -:ref:`Reference type ` |EXTERNREF| :math:`\hex{6F}` (-17 as |Bs7|) -(reserved) :math:`\hex{6E}` .. :math:`\hex{61}` -:ref:`Function type ` :math:`[\valtype^\ast] \to [\valtype^\ast]` :math:`\hex{60}` (-32 as |Bs7|) -(reserved) :math:`\hex{5F}` .. :math:`\hex{41}` -:ref:`Result type ` :math:`[\epsilon]` :math:`\hex{40}` (-64 as |Bs7|) -:ref:`Table type ` :math:`\limits~\reftype` (none) -:ref:`Memory type ` :math:`\limits` (none) -:ref:`Global type ` :math:`\mut~\valtype` (none) -======================================== =========================================== =============================================================================== +======================================== ================================================== =============================================================== +:ref:`Type index ` :math:`x` (positive number as |Bs32| or |Bu32|) +:ref:`Number type ` |I32| :math:`\hex{7F}` (-1 as |Bs7|) +:ref:`Number type ` |I64| :math:`\hex{7E}` (-2 as |Bs7|) +:ref:`Number type ` |F32| :math:`\hex{7D}` (-3 as |Bs7|) +:ref:`Number type ` |F64| :math:`\hex{7C}` (-4 as |Bs7|) +:ref:`Vector type ` |V128| :math:`\hex{7B}` (-5 as |Bs7|) +(reserved) :math:`\hex{7A}` .. :math:`\hex{79}` +:ref:`Packed type ` |I8| :math:`\hex{78}` (-8 as |Bs7|) +:ref:`Packed type ` |I16| :math:`\hex{77}` (-9 as |Bs7|) +(reserved) :math:`\hex{78}` .. :math:`\hex{74}` +:ref:`Heap type ` |NOFUNC| :math:`\hex{73}` (-13 as |Bs7|) +:ref:`Heap type ` |NOEXTERN| :math:`\hex{72}` (-14 as |Bs7|) +:ref:`Heap type ` |NONE| :math:`\hex{71}` (-15 as |Bs7|) +:ref:`Heap type ` |FUNC| :math:`\hex{70}` (-16 as |Bs7|) +:ref:`Heap type ` |EXTERN| :math:`\hex{6F}` (-17 as |Bs7|) +:ref:`Heap type ` |ANY| :math:`\hex{6E}` (-18 as |Bs7|) +:ref:`Heap type ` |EQT| :math:`\hex{6D}` (-19 as |Bs7|) +:ref:`Heap type ` |I31| :math:`\hex{6C}` (-20 as |Bs7|) +:ref:`Heap type ` |STRUCT| :math:`\hex{6B}` (-21 as |Bs7|) +:ref:`Heap type ` |ARRAY| :math:`\hex{6A}` (-22 as |Bs7|) +(reserved) :math:`\hex{69}` .. :math:`\hex{65}` +:ref:`Reference type ` |REF| :math:`\hex{64}` (-28 as |Bs7|) +:ref:`Reference type ` |REF| |NULL| :math:`\hex{63}` (-29 as |Bs7|) +(reserved) :math:`\hex{62}` .. :math:`\hex{61}` +:ref:`Composite type ` :math:`\TFUNC~[\valtype^\ast] \toF[\valtype^\ast]` :math:`\hex{60}` (-32 as |Bs7|) +:ref:`Composite type ` :math:`\TSTRUCT~\fieldtype^\ast` :math:`\hex{5F}` (-33 as |Bs7|) +:ref:`Composite type ` :math:`\TARRAY~\fieldtype` :math:`\hex{5E}` (-34 as |Bs7|) +(reserved) :math:`\hex{5D}` .. :math:`\hex{51}` +:ref:`Sub type ` :math:`\TSUB~\typeidx^\ast~\comptype` :math:`\hex{50}` (-48 as |Bs7|) +:ref:`Sub type ` :math:`\TSUB~\TFINAL~\typeidx^\ast~\comptype` :math:`\hex{4F}` (-49 as |Bs7|) +:ref:`Recursive type ` :math:`\TREC~\subtype^\ast` :math:`\hex{4E}` (-50 as |Bs7|) +(reserved) :math:`\hex{4D}` .. :math:`\hex{41}` +:ref:`Result type ` :math:`[\epsilon]` :math:`\hex{40}` (-64 as |Bs7|) +:ref:`Table type ` :math:`\limits~\reftype` (none) +:ref:`Memory type ` :math:`\limits` (none) +:ref:`Global type ` :math:`\mut~\valtype` (none) +======================================== ================================================== =============================================================== diff --git a/document/core/appendix/index.rst b/document/core/appendix/index.rst index c4173e9558..9054e64e34 100644 --- a/document/core/appendix/index.rst +++ b/document/core/appendix/index.rst @@ -8,16 +8,11 @@ Appendix embedding implementation + properties algorithm custom - properties changes -.. only:: singlehtml - - .. toctree:: - - index-types - index-instructions - index-rules - + index-types + index-instructions + index-rules diff --git a/document/core/appendix/properties.rst b/document/core/appendix/properties.rst index 36bc6f8020..ec04f07d3c 100644 --- a/document/core/appendix/properties.rst +++ b/document/core/appendix/properties.rst @@ -1,8 +1,8 @@ .. index:: ! soundness, type system .. _soundness: -Soundness ---------- +Type Soundness +-------------- The :ref:`type system ` of WebAssembly is *sound*, implying both *type safety* and *memory safety* with respect to the WebAssembly semantics. For example: @@ -20,6 +20,205 @@ The typing rules defining WebAssembly :ref:`validation ` only cover the * In order to state and prove soundness precisely, the typing rules must be extended to the *dynamic* components of the abstract :ref:`runtime `, that is, the :ref:`store `, :ref:`configurations `, and :ref:`administrative instructions `. [#cite-pldi2017]_ +.. index:: context, recursive type, recursive type index +.. _context-ext: + +Contexts +~~~~~~~~ + +In order to check :ref:`rolled up ` recursive types, +the :ref:`context ` is locally extended with an additional component that records the :ref:`sub type ` corresponding to each :ref:`recursive type index ` within the current :ref:`recursive type `: + +.. math:: + \begin{array}{llll} + \production{context} & C &::=& + \{~ \dots, \CRECS ~ \subtype^\ast ~\} \\ + \end{array} + + +.. index:: value type, reference type, heap type, bottom type, sub type, recursive type, recursive type index +.. _valid-types-ext: + +Types +~~~~~ + +Well-formedness for :ref:`extended type forms ` is defined as follows. + + +.. _valid-heaptype-ext: + +:ref:`Heap Type ` :math:`\BOTH` +.................................................... + +* The heap type is valid. + +.. math:: + \frac{ + }{ + C \vdashheaptype \BOTH \ok + } + +:ref:`Heap Type ` :math:`\REC~i` +..................................................... + +* The recursive type index :math:`i` must exist in :math:`C.\CRECS`. + +* Then the heap type is valid. + +.. math:: + \frac{ + C.\CRECS[i] = \subtype + }{ + C \vdashheaptype \REC~i \ok + } + + +.. _valid-valtype-ext: + +:ref:`Value Type ` :math:`\BOT` +................................................... + +* The value type is valid. + +.. math:: + \frac{ + }{ + C \vdashvaltype \BOT \ok + } + + +.. _valid-rectype-ext: + +:ref:`Recursive Types ` :math:`\TREC~\subtype^\ast` +................................................................... + +* Let :math:`C'` be the current :ref:`context ` :math:`C`, but where |CRECS| is :math:`\subtype^\ast`. + +* There must be a :ref:`type index ` :math:`x`, such that for each :ref:`sub type ` :math:`\subtype_i` in :math:`\subtype^\ast`: + + * Under the context :math:`C'`, the :ref:`sub type ` :math:`\subtype_i` must be :ref:`valid ` for :ref:`type index ` :math:`x+i` and :ref:`recursive type index ` :math:`i`. + +* Then the recursive type is valid for the :ref:`type index ` :math:`x`. + +.. math:: + \frac{ + C,\CRECS~\subtype^\ast \vdashrectype \TREC~\subtype^\ast ~{\ok}(x,0) + }{ + C \vdashrectype \TREC~\subtype^\ast ~{\ok}(x) + } + +.. math:: + \frac{ + }{ + C \vdashrectype \TREC~\epsilon ~{\ok}(x,i) + } + \qquad + \frac{ + C \vdashsubtype \subtype ~{\ok}(x,i) + \qquad + C \vdashrectype \TREC~{\subtype'}^\ast ~{\ok}(x+1,i+1) + }{ + C \vdashrectype \TREC~\subtype~{\subtype'}^\ast ~{\ok}(x,i) + } + +.. note:: + These rules are a generalisation of the ones :ref:`previously given `. + + +.. _valid-subtype-ext: + +:ref:`Sub types ` :math:`\TSUB~\TFINAL^?~\X{ht}^\ast~\comptype` +............................................................................... + +* The :ref:`composite type ` :math:`\comptype` must be :ref:`valid `. + +* The sequence :math:`\X{ht}^\ast` may be no longer than :math:`1`. + +* For every :ref:`heap type ` :math:`\X{ht}_k` in :math:`\X{ht}^\ast`: + + * The :ref:`heap type ` :math:`\X{ht}_k` must be ordered before a :ref:`type index ` :math:`x` and :ref:`recursive type index ` a :math:`i`, meaning: + + - Either :math:`\X{ht}_k` is a :ref:`defined type `. + + - Or :math:`\X{ht}_k` is a :ref:`type index ` :math:`y_k` that is smaller than :math:`x`. + + - Or :math:`\X{ht}_k` is a :ref:`recursive type index ` :math:`\REC~j_k` where :math:`j_k` is smaller than :math:`i`. + + * Let :ref:`sub type ` :math:`\subtype_k` be the :ref:`unrolling ` of the :ref:`heap type ` :math:`\X{ht}_k`, meaning: + + - Either :math:`\X{ht}_k` is a :ref:`defined type ` :math:`\deftype_k`, then :math:`\subtype_k` must be the :ref:`unrolling ` of :math:`\deftype_k`. + + - Or :math:`\X{ht}_k` is a :ref:`type index ` :math:`y_k`, then :math:`\subtype_k` must be the :ref:`unrolling ` of the :ref:`defined type ` :math:`C.\CTYPES[y_k]`. + + - Or :math:`\X{ht}_k` is a :ref:`recursive type index ` :math:`\REC~j_k`, then :math:`\subtype_k` must be :math:`C.\CRECS[j_k]`. + + * The :ref:`sub type ` :math:`\subtype_k` must not contain :math:`\TFINAL`. + + * Let :math:`\comptype'_k` be the :ref:`composite type ` in :math:`\subtype_k`. + + * The :ref:`composite type ` :math:`\comptype` must :ref:`match ` :math:`\comptype'_k`. + +* Then the sub type is valid for the :ref:`type index ` :math:`x` and :ref:`recursive type index ` :math:`i`. + +.. math:: + \frac{ + \begin{array}{@{}c@{}} + |\X{ht}^\ast| \leq 1 + \qquad + (\X{ht} \prec x,i)^\ast + \qquad + (\unrollht_{C}(\X{ht}) = \TSUB~{\X{ht}'}^\ast~\comptype')^\ast + \\ + C \vdashcomptype \comptype \ok + \qquad + (C \vdashcomptypematch \comptype \matchescomptype \comptype')^\ast + \end{array} + }{ + C \vdashsubtype \TSUB~\TFINAL^?~\X{ht}^\ast~\comptype ~{\ok}(x,i) + } + +.. _aux-unroll-heaptype: + +where: + +.. math:: + \begin{array}{@{}lll@{}} + (\deftype \prec x,i) &=& {\F{true}} \\ + (y \prec x,i) &=& y < x \\ + (\REC~j \prec x,i) &=& j < i \\ + [2ex] + \unrollht_{C}(\deftype) &=& \unrolldt(\deftype) \\ + \unrollht_{C}(y) &=& \unrolldt(C.\CTYPES[y]) \\ + \unrollht_{C}(\REC~j) &=& C.\CRECS[j] \\ + \end{array} + +.. note:: + This rule is a generalisation of the ones :ref:`previously given `, which only allowed type indices as supertypes. + + +.. index:: heap type, recursive type, recursive type index +.. _match-heaptype-ext: + +Subtyping +~~~~~~~~~ + +In a :ref:`rolled-up ` :ref:`recursive type `, a :ref:`recursive type indices ` :math:`\REC~i` :ref:`matches ` another :ref:`heap type ` :math:`\X{ht}` if: + +* Let :math:`\TSUB~\TFINAL^?~{\X{ht}'}^\ast~\comptype` be the :ref:`sub type ` :math:`C.\CRECS[i]`. + +* The heap type :math:`\X{ht}` is contained in :math:`{\X{ht}'}^\ast`. + +.. math:: + \frac{ + C.\CRECS[i] = \TSUB~\TFINAL^?~(\X{ht}_1^\ast~\X{ht}~\X{ht}_2^\ast)~\comptype + }{ + C \vdashheaptypematch \REC~i \matchesheaptype \X{ht} + } + +.. note:: + This rule is only invoked when checking :ref:`validity ` of :ref:`rolled-up ` :ref:`recursive types `. + + .. index:: value, value type, result, result type, trap .. _valid-result: @@ -50,10 +249,11 @@ Results :ref:`Results ` :math:`\TRAP` ............................................ -* The result is valid with :ref:`result type ` :math:`[t^\ast]`, for any sequence :math:`t^\ast` of :ref:`value types `. +* The result is valid with :ref:`result type ` :math:`[t^\ast]`, for any :ref:`valid ` :ref:`closed ` :ref:`result types `. .. math:: \frac{ + \vdashresulttype [t^\ast] \ok }{ S \vdashresult \TRAP : [t^\ast] } @@ -74,7 +274,7 @@ Module instances are classified by *module contexts*, which are regular :ref:`co -.. index:: store, function instance, table instance, memory instance, global instance, function type, table type, memory type, global type +.. index:: store, function instance, table instance, memory instance, structure instance, array instance, global instance, function type, table type, memory type, global type, defined type, structure type, array type :ref:`Store ` :math:`S` ..................................... @@ -91,13 +291,21 @@ Module instances are classified by *module contexts*, which are regular :ref:`co * Each :ref:`data instance ` :math:`\datainst_i` in :math:`S.\SDATAS` must be :ref:`valid `. +* Each :ref:`structure instance ` :math:`\structinst_i` in :math:`S.\SSTRUCTS` must be :ref:`valid `. + +* Each :ref:`array instance ` :math:`\arrayinst_i` in :math:`S.\SARRAYS` must be :ref:`valid `. + +* No :ref:`reference ` to a bound :ref:`structure address ` must be reachable from itself through a path consisting only of indirections through immutable structure or array :ref:`fields `. + +* No :ref:`reference ` to a bound :ref:`array address ` must be reachable from itself through a path consisting only of indirections through immutable structure or array :ref:`fields `. + * Then the store is valid. .. math:: ~\\[-1ex] \frac{ \begin{array}{@{}c@{}} - (S \vdashfuncinst \funcinst : \functype)^\ast + (S \vdashfuncinst \funcinst : \deftype)^\ast \qquad (S \vdashtableinst \tableinst : \tabletype)^\ast \\ @@ -109,18 +317,51 @@ Module instances are classified by *module contexts*, which are regular :ref:`co \qquad (S \vdashdatainst \datainst \ok)^\ast \\ + (S \vdashstructinst \structinst \ok)^\ast + \qquad + (S \vdasharrayinst \arrayinst \ok)^\ast + \\ S = \{ + \begin{array}[t]{@{}l@{}} \SFUNCS~\funcinst^\ast, - \STABLES~\tableinst^\ast, - \SMEMS~\meminst^\ast, \SGLOBALS~\globalinst^\ast, + \STABLES~\tableinst^\ast, + \SMEMS~\meminst^\ast, \\ \SELEMS~\eleminst^\ast, - \SDATAS~\datainst^\ast \} + \SDATAS~\datainst^\ast, + \SSTRUCTS~\structinst^\ast, + \SARRAYS~\arrayinst^\ast \} + \end{array} + \\ + (S.\SSTRUCTS[a_{\F{s}}] = \structinst)^\ast + \qquad + ((\REFSTRUCTADDR~a_{\F{s}}) \not\gg^+_S (\REFSTRUCTADDR~a_{\F{s}}))^\ast + \\ + (S.\SARRAYS[a_{\F{a}}] = \arrayinst)^\ast + \qquad + ((\REFARRAYADDR~a_{\F{a}}) \not\gg^+_S (\REFARRAYADDR~a_{\F{a}}))^\ast \end{array} }{ \vdashstore S \ok } +.. index:: reachability + +where :math:`\val_1 \gg^+_S \val_2` denotes the transitive closure of the following *reachability* relation on :ref:`values `: + +.. math:: + \begin{array}{@{}lcll@{}} + (\REFSTRUCTADDR~a) &\gg_S& S.\SSTRUCTS[a].\SIFIELDS[i] + & \iff \expanddt(S.\SSTRUCTS[a].\SITYPE) = \TSTRUCT~\X{ft}_1^i~(\MCONST~\X{st})~\X{ft}_2^\ast \\ + (\REFARRAYADDR~a) &\gg_S& S.\SARRAYS[a].\AIFIELDS[i] + & \iff \expanddt(S.\SARRAYS[a].\AITYPE) = \TARRAY~(\MCONST~\X{st}) \\ + (\REFEXTERN~\reff) &\gg_S& \reff \\ + \end{array} + +.. note:: + The constraint on reachability through immutable fields prevents the presence of cyclic data structures that can not be constructed in the language. + Cycles can only be formed using mutation. + .. index:: function type, function instance .. _valid-funcinst: @@ -128,21 +369,29 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Function Instances ` :math:`\{\FITYPE~\functype, \FIMODULE~\moduleinst, \FICODE~\func\}` ....................................................................................................................... -* The :ref:`function type ` :math:`\functype` must be :ref:`valid `. +* The :ref:`function type ` :math:`\functype` must be :ref:`valid ` under an empty :ref:`context `. * The :ref:`module instance ` :math:`\moduleinst` must be :ref:`valid ` with some :ref:`context ` :math:`C`. -* Under :ref:`context ` :math:`C`, the :ref:`function ` :math:`\func` must be :ref:`valid ` with :ref:`function type ` :math:`\functype`. +* Under :ref:`context ` :math:`C`: + + * The :ref:`function ` :math:`\func` must be :ref:`valid ` with some :ref:`function type ` :math:`\functype'`. + + * The :ref:`function type ` :math:`\functype'` must :ref:`match ` :math:`\functype`. * Then the function instance is valid with :ref:`function type ` :math:`\functype`. .. math:: \frac{ + \begin{array}{@{}c@{}} \vdashfunctype \functype \ok \qquad S \vdashmoduleinst \moduleinst : C + \\ + C \vdashfunc \func : \functype' \qquad - C \vdashfunc \func : \functype + C \vdashfunctypematch \functype' \matchesfunctype \functype + \end{array} }{ S \vdashfuncinst \{\FITYPE~\functype, \FIMODULE~\moduleinst, \FICODE~\func\} : \functype } @@ -154,9 +403,9 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Host Function Instances ` :math:`\{\FITYPE~\functype, \FIHOSTCODE~\X{hf}\}` .................................................................................................. -* The :ref:`function type ` :math:`\functype` must be :ref:`valid `. +* The :ref:`function type ` :math:`\functype` must be :ref:`valid ` under an empty :ref:`context `. -* Let :math:`[t_1^\ast] \to [t_2^\ast]` be the :ref:`function type ` :math:`\functype`. +* Let :math:`[t_1^\ast] \toF [t_2^\ast]` be the :ref:`function type ` :math:`\functype`. * For every :ref:`valid ` :ref:`store ` :math:`S_1` :ref:`extending ` :math:`S` and every sequence :math:`\val^\ast` of :ref:`values ` whose :ref:`types ` coincide with :math:`t_1^\ast`: @@ -173,7 +422,7 @@ Module instances are classified by *module contexts*, which are regular :ref:`co .. math:: \frac{ \begin{array}[b]{@{}l@{}} - \vdashfunctype [t_1^\ast] \to [t_2^\ast] \ok \\ + \vdashfunctype [t_1^\ast] \toF [t_2^\ast] \ok \\ \end{array} \quad \begin{array}[b]{@{}l@{}} @@ -209,13 +458,15 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Table Instances ` :math:`\{ \TITYPE~(\limits~t), \TIELEM~\reff^\ast \}` ............................................................................................... -* The :ref:`table type ` :math:`\limits~t` must be :ref:`valid `. +* The :ref:`table type ` :math:`\limits~t` must be :ref:`valid ` under the empty :ref:`context `. * The length of :math:`\reff^\ast` must equal :math:`\limits.\LMIN`. * For each :ref:`reference ` :math:`\reff_i` in the table's elements :math:`\reff^n`: - * The :ref:`reference ` :math:`\reff_i` must be :ref:`valid ` with :ref:`reference type ` :math:`t`. + * The :ref:`reference ` :math:`\reff_i` must be :ref:`valid ` with some :ref:`reference type ` :math:`t'_i`. + + * The :ref:`reference type ` :math:`t'_i` must :ref:`match ` the :ref:`reference type ` :math:`t`. * Then the table instance is valid with :ref:`table type ` :math:`\limits~t`. @@ -225,7 +476,9 @@ Module instances are classified by *module contexts*, which are regular :ref:`co \qquad n = \limits.\LMIN \qquad - (S \vdash \reff : t)^n + (S \vdash \reff : t')^n + \qquad + (\vdashreftypematch t' \matchesvaltype t)^n }{ S \vdashtableinst \{ \TITYPE~(\limits~t), \TIELEM~\reff^n \} : \limits~t } @@ -237,7 +490,7 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Memory Instances ` :math:`\{ \MITYPE~\limits, \MIDATA~b^\ast \}` ...................................................................................... -* The :ref:`memory type ` :math:`\limits` must be :ref:`valid `. +* The :ref:`memory type ` :math:`\limits` must be :ref:`valid ` under the empty :ref:`context `. * The length of :math:`b^\ast` must equal :math:`\limits.\LMIN` multiplied by the :ref:`page size ` :math:`64\,\F{Ki}`. @@ -259,9 +512,11 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Global Instances ` :math:`\{ \GITYPE~(\mut~t), \GIVALUE~\val \}` ......................................................................................... -* The :ref:`global type ` :math:`\mut~t` must be :ref:`valid `. +* The :ref:`global type ` :math:`\mut~t` must be :ref:`valid ` under the empty :ref:`context `. + +* The :ref:`value ` :math:`\val` must be :ref:`valid ` with some :ref:`value type ` :math:`t'`. -* The :ref:`value ` :math:`\val` must be :ref:`valid ` with :ref:`value type ` :math:`t`. +* The :ref:`value type ` :math:`t'` must :ref:`match ` the :ref:`value type ` :math:`t`. * Then the global instance is valid with :ref:`global type ` :math:`\mut~t`. @@ -269,7 +524,9 @@ Module instances are classified by *module contexts*, which are regular :ref:`co \frac{ \vdashglobaltype \mut~t \ok \qquad - S \vdashval \val : t + S \vdashval \val : t' + \qquad + \vdashvaltypematch t' \matchesvaltype t }{ S \vdashglobalinst \{ \GITYPE~(\mut~t), \GIVALUE~\val \} : \mut~t } @@ -281,15 +538,23 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Element Instances ` :math:`\{ \EITYPE~t, \EIELEM~\reff^\ast \}` ...................................................................................... +* The :ref:`reference type ` :math:`t` must be :ref:`valid ` under the empty :ref:`context `. + * For each :ref:`reference ` :math:`\reff_i` in the elements :math:`\reff^n`: - * The :ref:`reference ` :math:`\reff_i` must be :ref:`valid ` with :ref:`reference type ` :math:`t`. + * The :ref:`reference ` :math:`\reff_i` must be :ref:`valid ` with some :ref:`reference type ` :math:`t'_i`. + + * The :ref:`reference type ` :math:`t'_i` must :ref:`match ` the :ref:`reference type ` :math:`t`. * Then the element instance is valid with :ref:`reference type ` :math:`t`. .. math:: \frac{ - (S \vdash \reff : t)^\ast + \vdashreftype t \ok + \qquad + (S \vdashval \reff : t')^\ast + \qquad + (\vdashreftypematch t' \matchesvaltype t)^\ast }{ S \vdasheleminst \{ \EITYPE~t, \EIELEM~\reff^\ast \} : t } @@ -310,6 +575,94 @@ Module instances are classified by *module contexts*, which are regular :ref:`co } +.. index:: structure instance, field value, field type, storage type, defined type +.. _valid-structinst: + +:ref:`Structure Instances ` :math:`\{ \SITYPE~\deftype, \SIFIELDS~\fieldval^\ast \}` +....................................................................................................... + +* The :ref:`defined type ` :math:`\deftype` must be :ref:`valid `. + +* The :ref:`expansion ` of :math:`\deftype` must be a :ref:`structure type ` :math:`\TSTRUCT~\fieldtype^\ast`. + +* The length of the sequence of :ref:`field values ` :math:`\fieldval^\ast` must be the same as the length of the sequence of :ref:`field types ` :math:`\fieldtype^\ast`. + +* For each :ref:`field value ` :math:`\fieldval_i` in :math:`\fieldval^\ast` and corresponding :ref:`field type ` :math:`\fieldtype_i` in :math:`\fieldtype^\ast`: + + - Let :math:`\fieldtype_i` be :math:`\mut~\storagetype_i`. + + - The :ref:`field value ` :math:`\fieldval_i` must be :ref:`valid ` with :ref:`storage type ` :math:`\storagetype_i`. + +* Then the structure instance is valid. + +.. math:: + \frac{ + \vdashdeftype \X{dt} \ok + \qquad + \expanddt(\X{dt}) = \TSTRUCT~(\mut~\X{st})^\ast + \qquad + (S \vdashfieldval \X{fv} : \X{st})^\ast + }{ + S \vdashstructinst \{ \SITYPE~\X{dt}, \SIFIELDS~\X{fv}^\ast \} \ok + } + + +.. index:: array instance, field value, field type, storage type, defined type +.. _valid-arrayinst: + +:ref:`Array Instances ` :math:`\{ \AITYPE~\deftype, \AIFIELDS~\fieldval^\ast \}` +.................................................................................................. + +* The :ref:`defined type ` :math:`\deftype` must be :ref:`valid `. + +* The :ref:`expansion ` of :math:`\deftype` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* For each :ref:`field value ` :math:`\fieldval_i` in :math:`\fieldval^\ast`: + + - The :ref:`field value ` :math:`\fieldval_i` must be :ref:`valid ` with :ref:`storage type ` :math:`\storagetype`. + +* Then the array instance is valid. + +.. math:: + \frac{ + \vdashdeftype \X{dt} \ok + \qquad + \expanddt(\X{dt}) = \TARRAY~(\mut~\X{st}) + \qquad + (S \vdashfieldval \X{fv} : \X{st})^\ast + }{ + S \vdasharrayinst \{ \AITYPE~\X{dt}, \AIFIELDS~\X{fv}^\ast \} \ok + } + + +.. index:: field value, field type, validation, store, packed value, packed type +.. _valid-fieldval: +.. _valid-packedval: + +:ref:`Field Values ` :math:`\fieldval` +....................................................... + +* If :math:`\fieldval` is a :ref:`value ` :math:`\val`, then: + + - The value :math:`\val` must be :ref:`valid ` with :ref:`value type ` :math:`t`. + + - Then the field value is valid with :ref:`value type ` :math:`t`. + +* Else, :math:`\fieldval` is a :ref:`packed value ` :math:`\packedval`: + + - Let :math:`\packedtype.\PACK~i` be the field value :math:`\fieldval`. + + - Then the field value is valid with :ref:`packed type ` :math:`\packedtype`. + +.. math:: + \frac{ + }{ + S \vdashpackedval \X{pt}.\PACK~i : \X{pt} + } + + .. index:: external type, export instance, name, external value .. _valid-exportinst: @@ -334,9 +687,9 @@ Module instances are classified by *module contexts*, which are regular :ref:`co :ref:`Module Instances ` :math:`\moduleinst` ............................................................... -* Each :ref:`function type ` :math:`\functype_i` in :math:`\moduleinst.\MITYPES` must be :ref:`valid `. +* Each :ref:`defined type ` :math:`\deftype_i` in :math:`\moduleinst.\MITYPES` must be :ref:`valid ` under the empty :ref:`context `. -* For each :ref:`function address ` :math:`\funcaddr_i` in :math:`\moduleinst.\MIFUNCS`, the :ref:`external value ` :math:`\EVFUNC~\funcaddr_i` must be :ref:`valid ` with some :ref:`external type ` :math:`\ETFUNC~\functype'_i`. +* For each :ref:`function address ` :math:`\funcaddr_i` in :math:`\moduleinst.\MIFUNCS`, the :ref:`external value ` :math:`\EVFUNC~\funcaddr_i` must be :ref:`valid ` with some :ref:`external type ` :math:`\ETFUNC~\functype_i`. * For each :ref:`table address ` :math:`\tableaddr_i` in :math:`\moduleinst.\MITABLES`, the :ref:`external value ` :math:`\EVTABLE~\tableaddr_i` must be :ref:`valid ` with some :ref:`external type ` :math:`\ETTABLE~\tabletype_i`. @@ -352,7 +705,9 @@ Module instances are classified by *module contexts*, which are regular :ref:`co * For each :ref:`export instance ` :math:`\exportinst_i` in :math:`\moduleinst.\MIEXPORTS`, the :ref:`name ` :math:`\exportinst_i.\EINAME` must be different from any other name occurring in :math:`\moduleinst.\MIEXPORTS`. -* Let :math:`{\functype'}^\ast` be the concatenation of all :math:`\functype'_i` in order. +* Let :math:`\deftype^\ast` be the concatenation of all :math:`\deftype_i` in order. + +* Let :math:`\functype^\ast` be the concatenation of all :math:`\functype_i` in order. * Let :math:`\tabletype^\ast` be the concatenation of all :math:`\tabletype_i` in order. @@ -369,15 +724,15 @@ Module instances are classified by *module contexts*, which are regular :ref:`co * Let :math:`x^\ast` be the sequence of :ref:`function indices ` from :math:`0` to :math:`m-1`. * Then the module instance is valid with :ref:`context ` - :math:`\{\CTYPES~\functype^\ast,` :math:`\CFUNCS~{\functype'}^\ast,` :math:`\CTABLES~\tabletype^\ast,` :math:`\CMEMS~\memtype^\ast,` :math:`\CGLOBALS~\globaltype^\ast,` :math:`\CELEMS~\reftype^\ast,` :math:`\CDATAS~{\ok}^n,` :math:`\CREFS~x^\ast\}`. + :math:`\{\CTYPES~\deftype^\ast,` :math:`\CFUNCS~\functype^\ast,` :math:`\CTABLES~\tabletype^\ast,` :math:`\CMEMS~\memtype^\ast,` :math:`\CGLOBALS~\globaltype^\ast,` :math:`\CELEMS~\reftype^\ast,` :math:`\CDATAS~{\ok}^n,` :math:`\CREFS~x^\ast\}`. .. math:: ~\\[-1ex] \frac{ \begin{array}{@{}c@{}} - (\vdashfunctype \functype \ok)^\ast + (\vdashdeftype \deftype \ok)^\ast \\ - (S \vdashexternval \EVFUNC~\funcaddr : \ETFUNC~\functype')^\ast + (S \vdashexternval \EVFUNC~\funcaddr : \ETFUNC~\functype)^\ast \qquad (S \vdashexternval \EVTABLE~\tableaddr : \ETTABLE~\tabletype)^\ast \\ @@ -396,7 +751,7 @@ Module instances are classified by *module contexts*, which are regular :ref:`co }{ S \vdashmoduleinst \{ \begin{array}[t]{@{}l@{~}l@{}} - \MITYPES & \functype^\ast, \\ + \MITYPES & \deftype^\ast, \\ \MIFUNCS & \funcaddr^\ast, \\ \MITABLES & \tableaddr^\ast, \\ \MIMEMS & \memaddr^\ast, \\ @@ -405,8 +760,8 @@ Module instances are classified by *module contexts*, which are regular :ref:`co \MIDATAS & \dataaddr^n, \\ \MIEXPORTS & \exportinst^\ast ~\} : \{ \begin{array}[t]{@{}l@{~}l@{}} - \CTYPES & \functype^\ast, \\ - \CFUNCS & {\functype'}^\ast, \\ + \CTYPES & \deftype^\ast, \\ + \CFUNCS & \functype^\ast, \\ \CTABLES & \tabletype^\ast, \\ \CMEMS & \memtype^\ast, \\ \CGLOBALS & \globaltype^\ast, \\ @@ -418,6 +773,51 @@ Module instances are classified by *module contexts*, which are regular :ref:`co } +.. scratch + .. index:: context, store, frame + .. _valid-context: + + Context Validity + ~~~~~~~~~~~~~~~~ + + A :ref:`context ` :math:`C` is valid when every type occurring in it is valid. + + .. math:: + \frac{ + \begin{array}{@{}c@{}} + x^n = 0 \dots (n-1) + \qquad + (S; \{CTYPES~\functype^n[0 \slice x]\} \vdashfunctype \functype \ok)^n + \\ + (S; C \vdashfunctype \functype' \ok)^\ast + \qquad + (S; C \vdashtabletype \tabletype \ok)^\ast + \\ + (S; C \vdashmemtype \memtype \ok)^\ast + \qquad + (S; C \vdashglobaltype \globaltype \ok)^\ast + \qquad + (S; C \vdashreftype \reftype \ok)^\ast + \\ + C = \{ + \begin{array}[t]{@{}l@{~}l@{}} + \CTYPES & \functype^n, \\ + \CFUNCS & {\functype'}^\ast, \\ + \CTABLES & \tabletype^\ast, \\ + \CMEMS & \memtype^\ast, \\ + \CGLOBALS & \globaltype^\ast, \\ + \CELEMS & \reftype^\ast, \\ + \CDATAS & {\ok}^\ast ~\} + \end{array} + \end{array} + }{ + S \vdashcontext C \ok + } + + .. note:: + It is an invariant of the semantics that every context either consists of only static types or only dynamic types. + + .. index:: configuration, administrative instruction, store, frame .. _frame-context: .. _valid-config: @@ -518,6 +918,7 @@ Administrative Instructions Typing rules for :ref:`administrative instructions ` are specified as follows. In addition to the :ref:`context ` :math:`C`, typing of these instructions is defined under a given :ref:`store ` :math:`S`. + To that end, all previous typing judgements :math:`C \vdash \X{prop}` are generalized to include the store, as in :math:`S; C \vdash \X{prop}`, by implicitly adding :math:`S` to all rules -- :math:`S` is never modified by the pre-existing rules, but it is accessed in the extra rules for :ref:`administrative instructions ` given below. @@ -526,43 +927,30 @@ To that end, all previous typing judgements :math:`C \vdash \X{prop}` are genera :math:`\TRAP` ............. -* The instruction is valid with type :math:`[t_1^\ast] \to [t_2^\ast]`, for any sequences of :ref:`value types ` :math:`t_1^\ast` and :math:`t_2^\ast`. +* The instruction is valid with any :ref:`valid ` :ref:`instruction type ` of the form :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ + C \vdashinstrtype [t_1^\ast] \to [t_2^\ast] \ok }{ S; C \vdashadmininstr \TRAP : [t_1^\ast] \to [t_2^\ast] } -.. index:: extern address - -:math:`\REFEXTERNADDR~\externaddr` -.................................. - -* The instruction is valid with type :math:`[] \to [\EXTERNREF]`. - -.. math:: - \frac{ - }{ - S; C \vdashadmininstr \REFEXTERNADDR~\externaddr : [] \to [\EXTERNREF] - } - - -.. index:: function address, extern value, extern type, function type +.. index:: value, value type -:math:`\REFFUNCADDR~\funcaddr` -.............................. +:math:`\val` +............ -* The :ref:`external function value ` :math:`\EVFUNC~\funcaddr` must be :ref:`valid ` with :ref:`external function type ` :math:`\ETFUNC~\functype`. +* The value :math:`\val` must be valid with :ref:`value type ` :math:`t`. -* Then the instruction is valid with type :math:`[] \to [\FUNCREF]`. +* Then it is valid as an instruction with type :math:`[] \to [t]`. .. math:: \frac{ - S \vdashexternval \EVFUNC~\funcaddr : \ETFUNC~\functype + S \vdashval \val : t }{ - S; C \vdashadmininstr \REFFUNCADDR~\funcaddr : [] \to [\FUNCREF] + S; C \vdashadmininstr \val : [] \to [t] } @@ -571,13 +959,15 @@ To that end, all previous typing judgements :math:`C \vdash \X{prop}` are genera :math:`\INVOKE~\funcaddr` ......................... -* The :ref:`external function value ` :math:`\EVFUNC~\funcaddr` must be :ref:`valid ` with :ref:`external function type ` :math:`\ETFUNC ([t_1^\ast] \to [t_2^\ast])`. +* The :ref:`external function value ` :math:`\EVFUNC~\funcaddr` must be :ref:`valid ` with :ref:`external function type ` :math:`\ETFUNC \functype'`. + +* Let :math:`[t_1^\ast] \toF [t_2^\ast])` be the :ref:`function type ` :math:`\functype`. * Then the instruction is valid with type :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ - S \vdashexternval \EVFUNC~\funcaddr : \ETFUNC~[t_1^\ast] \to [t_2^\ast] + S \vdashexternval \EVFUNC~\funcaddr : \ETFUNC~[t_1^\ast] \toF [t_2^\ast] }{ S; C \vdashadmininstr \INVOKE~\funcaddr : [t_1^\ast] \to [t_2^\ast] } @@ -588,20 +978,20 @@ To that end, all previous typing judgements :math:`C \vdash \X{prop}` are genera :math:`\LABEL_n\{\instr_0^\ast\}~\instr^\ast~\END` .................................................. -* The instruction sequence :math:`\instr_0^\ast` must be :ref:`valid ` with some type :math:`[t_1^n] \to [t_2^*]`. +* The instruction sequence :math:`\instr_0^\ast` must be :ref:`valid ` with some type :math:`[t_1^n] \to_{x^\ast} [t_2^*]`. * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with the :ref:`result type ` :math:`[t_1^n]` prepended to the |CLABELS| vector. * Under context :math:`C'`, - the instruction sequence :math:`\instr^\ast` must be :ref:`valid ` with type :math:`[] \to [t_2^*]`. + the instruction sequence :math:`\instr^\ast` must be :ref:`valid ` with type :math:`[] \to_{{x'}^\ast} [t_2^*]`. * Then the compound instruction is valid with type :math:`[] \to [t_2^*]`. .. math:: \frac{ - S; C \vdashinstrseq \instr_0^\ast : [t_1^n] \to [t_2^*] + S; C \vdashinstrseq \instr_0^\ast : [t_1^n] \to_{x^\ast} [t_2^*] \qquad - S; C,\CLABELS\,[t_1^n] \vdashinstrseq \instr^\ast : [] \to [t_2^*] + S; C,\CLABELS\,[t_1^n] \vdashinstrseq \instr^\ast : [] \to_{{x'}^\ast} [t_2^*] }{ S; C \vdashadmininstr \LABEL_n\{\instr_0^\ast\}~\instr^\ast~\END : [] \to [t_2^*] } @@ -612,13 +1002,15 @@ To that end, all previous typing judgements :math:`C \vdash \X{prop}` are genera :math:`\FRAME_n\{F\}~\instr^\ast~\END` ........................................... -* Under the return type :math:`[t^n]`, +* Under the :ref:`valid ` return type :math:`[t^n]`, the :ref:`thread ` :math:`F; \instr^\ast` must be :ref:`valid ` with :ref:`result type ` :math:`[t^n]`. * Then the compound instruction is valid with type :math:`[] \to [t^n]`. .. math:: \frac{ + C \vdashresulttype [t^n] \ok + \qquad S; [t^n] \vdashinstrseq F; \instr^\ast : [t^n] }{ S; C \vdashadmininstr \FRAME_n\{F\}~\instr^\ast~\END : [] \to [t^n] @@ -662,6 +1054,10 @@ a store state :math:`S'` extends state :math:`S`, written :math:`S \extendsto S' * The length of :math:`S.\SDATAS` must not shrink. +* The length of :math:`S.\SSTRUCTS` must not shrink. + +* The length of :math:`S.\SARRAYS` must not shrink. + * For each :ref:`function instance ` :math:`\funcinst_i` in the original :math:`S.\SFUNCS`, the new function instance must be an :ref:`extension ` of the old. * For each :ref:`table instance ` :math:`\tableinst_i` in the original :math:`S.\STABLES`, the new table instance must be an :ref:`extension ` of the old. @@ -674,6 +1070,10 @@ a store state :math:`S'` extends state :math:`S`, written :math:`S \extendsto S' * For each :ref:`data instance ` :math:`\datainst_i` in the original :math:`S.\SDATAS`, the new data instance must be an :ref:`extension ` of the old. +* For each :ref:`structure instance ` :math:`\structinst_i` in the original :math:`S.\SSTRUCTS`, the new structure instance must be an :ref:`extension ` of the old. + +* For each :ref:`array instance ` :math:`\arrayinst_i` in the original :math:`S.\SARRAYS`, the new array instance must be an :ref:`extension ` of the old. + .. math:: \frac{ \begin{array}{@{}ccc@{}} @@ -695,6 +1095,12 @@ a store state :math:`S'` extends state :math:`S`, written :math:`S \extendsto S' S_1.\SDATAS = \datainst_1^\ast & S_2.\SDATAS = {\datainst'_1}^\ast~\datainst_2^\ast & (\vdashdatainstextends \datainst_1 \extendsto \datainst'_1)^\ast \\ + S_1.\SSTRUCTS = \structinst_1^\ast & + S_2.\SSTRUCTS = {\structinst'_1}^\ast~\structinst_2^\ast & + (\vdashstructinstextends \structinst_1 \extendsto \structinst'_1)^\ast \\ + S_1.\SARRAYS = \arrayinst_1^\ast & + S_2.\SARRAYS = {\arrayinst'_1}^\ast~\arrayinst_2^\ast & + (\vdasharrayinstextends \arrayinst_1 \extendsto \arrayinst'_1)^\ast \\ \end{array} }{ \vdashstoreextends S_1 \extendsto S_2 @@ -778,13 +1184,24 @@ a store state :math:`S'` extends state :math:`S`, written :math:`S \extendsto S' :ref:`Element Instance ` :math:`\eleminst` ........................................................... -* The vector :math:`\eleminst.\EIELEM` must either remain unchanged or shrink to length :math:`0`. +* The :ref:`reference type ` :math:`\eleminst.\EITYPE` must remain unchanged. + +* The vector :math:`\eleminst.\EIELEM` must: + + * either remain unchanged, + + * or shrink to length :math:`0`. + +.. math:: + \frac{ + }{ + \vdasheleminstextends \{\EITYPE~t, \EIELEM~a^\ast\} \extendsto \{\EITYPE~t, \EIELEM~a^\ast\} + } .. math:: \frac{ - \X{fa}_1^\ast = \X{fa}_2^\ast \vee \X{fa}_2^\ast = \epsilon }{ - \vdasheleminstextends \{\EIELEM~\X{fa}_1^\ast\} \extendsto \{\EIELEM~\X{fa}_2^\ast\} + \vdasheleminstextends \{\EITYPE~t, \EIELEM~a^\ast\} \extendsto \{\EITYPE~t, \EIELEM~\epsilon\} } @@ -794,13 +1211,78 @@ a store state :math:`S'` extends state :math:`S`, written :math:`S \extendsto S' :ref:`Data Instance ` :math:`\datainst` ........................................................ -* The vector :math:`\datainst.\DIDATA` must either remain unchanged or shrink to length :math:`0`. +* The vector :math:`\datainst.\DIDATA` must: + + * either remain unchanged, + + * or shrink to length :math:`0`. + +.. math:: + \frac{ + }{ + \vdashdatainstextends \{\DIDATA~b^\ast\} \extendsto \{\DIDATA~b^\ast\} + } + +.. math:: + \frac{ + }{ + \vdashdatainstextends \{\DIDATA~b^\ast\} \extendsto \{\DIDATA~\epsilon\} + } + + +.. index:: structure instance, field value, field type +.. _extend-structinst: + +:ref:`Structure Instance ` :math:`\structinst` +................................................................. + +* The :ref:`defined type ` :math:`\structinst.\SITYPE` must remain unchanged. + +* Assert: due to :ref:`store well-formedness `, the :ref:`expansion ` of :math:`\structinst.\SITYPE` is a :ref:`structure type `. + +* Let :math:`\TSTRUCT~\fieldtype^\ast` be the :ref:`expansion ` of :math:`\structinst.\SITYPE`. + +* The length of the vector :math:`\structinst.\SIFIELDS` must remain unchanged. + +* Assert: due to :ref:`store well-formedness `, the length of :math:`\structinst.\SIFIELDS` is the same as the length of :math:`\fieldtype^\ast`. + +* For each :ref:`field value ` :math:`\fieldval_i` in :math:`\structinst.\SIFIELDS` and corresponding :ref:`field type ` :math:`\fieldtype_i` in :math:`\fieldtype^\ast`: + + * Let :math:`\mut_i~\X{st}_i` be the structure of :math:`\fieldtype_i`. + + * If :math:`\mut_i` is |MCONST|, then the :ref:`field value ` :math:`\fieldval_i` must remain unchanged. + +.. math:: + \frac{ + (\mut = \MVAR \vee \fieldval_1 = \fieldval_2)^\ast + }{ + \vdashstructinstextends \{\SITYPE~(\mut~\X{st})^\ast, \SIFIELDS~\fieldval_1^\ast\} \extendsto \{\SITYPE~(\mut~\X{st})^\ast, \SIFIELDS~\fieldval_2^\ast\} + } + + +.. index:: array instance, field value, field type +.. _extend-arrayinst: + +:ref:`Array Instance ` :math:`\arrayinst` +........................................................... + +* The :ref:`defined type ` :math:`\arrayinst.\AITYPE` must remain unchanged. + +* Assert: due to :ref:`store well-formedness `, the :ref:`expansion ` of :math:`\arrayinst.\AITYPE` is an :ref:`array type `. + +* Let :math:`\TARRAY~\fieldtype` be the :ref:`expansion ` of :math:`\arrayinst.\AITYPE`. + +* The length of the vector :math:`\arrayinst.\AIFIELDS` must remain unchanged. + +* Let :math:`\mut~\X{st}` be the structure of :math:`\fieldtype`. + +* If :math:`\mut` is |MCONST|, then the sequence of :ref:`field values ` :math:`\arrayinst.\AIFIELDS` must remain unchanged. .. math:: \frac{ - b_1^\ast = b_2^\ast \vee b_2^\ast = \epsilon + \mut = \MVAR \vee \fieldval_1^\ast = \fieldval_2^\ast }{ - \vdashdatainstextends \{\DIDATA~b_1^\ast\} \extendsto \{\DIDATA~b_2^\ast\} + \vdasharrayinstextends \{\AITYPE~(\mut~\X{st}), \AIFIELDS~\fieldval_1^\ast\} \extendsto \{\AITYPE~(\mut~\X{st}), \AIFIELDS~\fieldval_2^\ast\} } @@ -850,3 +1332,156 @@ Consequently, given a :ref:`valid store `, no computation defined b .. [#cite-fm2021] Machine-verified formalizations and soundness proofs of the semantics from the official specification are described in the following article: Conrad Watt, Xiaojia Rao, Jean Pichon-Pharabod, Martin Bodin, Philippa Gardner. |FM2021|_. Proceedings of the 24th International Symposium on Formal Methods (FM 2021). Springer 2021. + + +.. index:: type system + +Type System Properties +---------------------- + +.. index:: ! principal types, type system, subtyping, polymorphism, instruction, syntax, instruction type +.. _principality: + +Principal Types +~~~~~~~~~~~~~~~ + +The :ref:`type system ` of WebAssembly features both :ref:`subtyping ` and simple forms of :ref:`polymorphism ` for :ref:`instruction types `. +That has the effect that every instruction or instruction sequence can be classified with multiple different instruction types. + +However, the typing rules still allow deriving *principal types* for instruction sequences. +That is, every valid instruction sequence has one particular type scheme, possibly containing some unconstrained place holder *type variables*, that is a subtype of all its valid instruction types, after substituting its type variables with suitable specific types. + +Moreover, when deriving an instruction type in a "forward" manner, i.e., the *input* of the instruction sequence is already fixed to specific types, +then it has a principal *output* type expressible without type variables, up to a possibly :ref:`polymorphic stack ` bottom representable with one single variable. +In other words, "forward" principal types are effectively *closed*. + +.. note:: + For example, in isolation, the instruction :math:`\REFASNONNULL` has the type :math:`[(\REF~\NULL~\X{ht})] \to [(\REF~\X{ht})]` for any choice of valid :ref:`heap type ` :math:`\X{ht}`. + Moreover, if the input type :math:`[(\REF~\NULL~\X{ht})]` is already determined, i.e., a specific :math:`\X{ht}` is given, then the output type :math:`[(\REF~\X{ht})]` is fully determined as well. + + The implication of the latter property is that a validator for *complete* instruction sequences (as they occur in valid modules) can be implemented with a simple left-to-right :ref:`algorithm ` that does not require the introduction of type variables. + + A typing algorithm capable of handling *partial* instruction sequences (as might be considered for program analysis or program manipulation) + needs to introduce type variables and perform substitutions, + but it does not need to perform backtracking or record any non-syntactic constraints on these type variables. + +Technically, the :ref:`syntax ` of :ref:`heap `, :ref:`value `, and :ref:`result ` types can be enriched with type variables as follows: + +.. math:: + \begin{array}{llll} + \production{nullability} & \X{null} &::=& + \NULL^? ~|~ \alpha_{\X{null}} \\ + \production{heap type} & \heaptype &::=& + \dots ~|~ \alpha_{\heaptype} \\ + \production{reference type} & \reftype &::=& + \REF~\X{null}~\heaptype \\ + \production{value type} & \valtype &::=& + \dots ~|~ \alpha_{\valtype} ~|~ \alpha_{\X{numvectype}} \\ + \production{result type} & \resulttype &::=& + [\alpha_{\valtype^\ast}^?~\valtype^\ast] \\ + \end{array} + +where each :math:`\alpha_{\X{xyz}}` ranges over a set of type variables for syntactic class :math:`\X{xyz}`, respectively. +The special class :math:`\X{numvectype}` is defined as :math:`\numtype ~|~ \vectype ~|~ \BOT`, +and is only needed to handle unannotated |SELECT| instructions. + +A type is *closed* when it does not contain any type variables, and *open* otherwise. +A *type substitution* :math:`\sigma` is a finite mapping from type variables to closed types of the respective syntactic class. +When applied to an open type, it replaces the type variables :math:`\alpha` from its domain with the respective :math:`\sigma(\alpha)`. + +**Theorem (Principal Types).** +If an instruction sequence :math:`\instr^\ast` is :ref:`valid ` with some closed :ref:`instruction type ` :math:`\instrtype` (i.e., :math:`C \vdashinstrseq \instr^\ast : \instrtype`), +then it is also valid with a possibly open instruction type :math:`\instrtype_{\min}` (i.e., :math:`C \vdashinstrseq \instr^\ast : \instrtype_{\min}`), +such that for *every* closed type :math:`\instrtype'` with which :math:`\instr^\ast` is valid (i.e., for all :math:`C \vdashinstrseq \instr^\ast : \instrtype'`), +there exists a substitution :math:`\sigma`, +such that :math:`\sigma(\instrtype_{\min})` is a subtype of :math:`\instrtype'` (i.e., :math:`C \vdashinstrtypematch \sigma(\instrtype_{\min}) \matchesinstrtype \instrtype'`). +Furthermore, :math:`\instrtype_{\min}` is unique up to the choice of type variables. + +**Theorem (Closed Principal Forward Types).** +If closed input type :math:`[t_1^\ast]` is given and the instruction sequence :math:`\instr^\ast` is :ref:`valid ` with :ref:`instruction type ` :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]` (i.e., :math:`C \vdashinstrseq \instr^\ast : [t_1^\ast] \to_{x^\ast} [t_2^\ast]`), +then it is also valid with instruction type :math:`[t_1^\ast] \to_{x^\ast} [\alpha_{\valtype^\ast}~t^\ast]` (i.e., :math:`C \vdashinstrseq \instr^\ast : [t_1^\ast] \to_{x^\ast} [\alpha_{\valtype^\ast}~t^\ast]`), +where all :math:`t^\ast` are closed, +such that for *every* closed result type :math:`[{t'_2}^\ast]` with which :math:`\instr^\ast` is valid (i.e., for all :math:`C \vdashinstrseq \instr^\ast : [t_1^\ast] \to_{x^\ast} [{t'_2}^\ast]`), +there exists a substitution :math:`\sigma`, +such that :math:`[{t'_2}^\ast] = [\sigma(\alpha_{\valtype^\ast})~t^\ast]`. + + +.. index:: ! type lattice, subtyping, least upper bound, greatest lower bound, instruction type + +Type Lattice +~~~~~~~~~~~~ + +The :ref:`Principal Types ` property depends on the existence of a *greatest lower bound* for any pair of types. + +**Theorem (Greatest Lower Bounds for Value Types).** +For any two value types :math:`t_1` and :math:`t_2` that are :ref:`valid ` +(i.e., :math:`C \vdashvaltype t_1 \ok` and :math:`C \vdashvaltype t_2 \ok`), +there exists a valid value type :math:`t` that is a subtype of both :math:`t_1` and :math:`t_2` +(i.e., :math:`C \vdashvaltype t \ok` and :math:`C \vdashvaltypematch t \matchesvaltype t_1` and :math:`C \vdashvaltypematch t \matchesvaltype t_2`), +such that *every* valid value type :math:`t'` that also is a subtype of both :math:`t_1` and :math:`t_2` +(i.e., for all :math:`C \vdashvaltype t' \ok` and :math:`C \vdashvaltypematch t' \matchesvaltype t_1` and :math:`C \vdashvaltypematch t' \matchesvaltype t_2`), +is a subtype of :math:`t` +(i.e., :math:`C \vdashvaltypematch t' \matchesvaltype t`). + +.. note:: + The greatest lower bound of two types may be |BOT|. + +**Theorem (Conditional Least Upper Bounds for Value Types).** +Any two value types :math:`t_1` and :math:`t_2` that are :ref:`valid ` +(i.e., :math:`C \vdashvaltype t_1 \ok` and :math:`C \vdashvaltype t_2 \ok`) +either have no common supertype, +or there exists a valid value type :math:`t` that is a supertype of both :math:`t_1` and :math:`t_2` +(i.e., :math:`C \vdashvaltype t \ok` and :math:`C \vdashvaltypematch t_1 \matchesvaltype t` and :math:`C \vdashvaltypematch t_2 \matchesvaltype t`), +such that *every* valid value type :math:`t'` that also is a supertype of both :math:`t_1` and :math:`t_2` +(i.e., for all :math:`C \vdashvaltype t' \ok` and :math:`C \vdashvaltypematch t_1 \matchesvaltype t'` and :math:`C \vdashvaltypematch t_2 \matchesvaltype t'`), +is a supertype of :math:`t` +(i.e., :math:`C \vdashvaltypematch t \matchesvaltype t'`). + +.. note:: + If a top type was added to the type system, + a least upper bound would exist for any two types. + +**Corollary (Type Lattice).** +Assuming the addition of a provisional top type, +:ref:`value types ` form a lattice with respect to their :ref:`subtype ` relation. + +Finally, value types can be partitioned into multiple disjoint hierarchies that are not related by subtyping, except through |BOT|. + +**Theorem (Disjoint Subtype Hierarchies).** +The greatest lower bound of two :ref:`value types ` is :math:`\BOT` or :math:`\REF~\BOT` +if and only if they do not have a least upper bound. + +In other words, types that do not have common supertypes, +do not have common subtypes either (other than :math:`\BOT` or :math:`\REF~\BOT`), and vice versa. + +.. note:: + Types from disjoint hierarchies can safely be represented in mutually incompatible ways in an implementation, + because their values can never flow to the same place. + + +.. index:: ! compositionality, instruction type, subtyping + +Compositionality +~~~~~~~~~~~~~~~~ + +:ref:`Valid ` :ref:`instruction sequences ` can be freely *composed*, as long as their types match up. + +**Theorem (Composition).** +If two instruction sequences :math:`\instr_1^\ast` and :math:`\instr_2^\ast` are valid with types :math:`[t_1^\ast] \to_{x_1^\ast} [t^\ast]` and :math:`[t^\ast] \to_{x_2^\ast} [t_2^\ast]`, respectively (i.e., :math:`C \vdashinstrseq \instr_1^\ast : [t_1^\ast] \to_{x_1^\ast} [t^\ast]` and :math:`C \vdashinstrseq \instr_1^\ast : [t^\ast] \to_{x_2^\ast} [t_2^\ast]`), +then the concatenated instruction sequence :math:`(\instr_1^\ast\;\instr_2^\ast)` is valid with type :math:`[t_1^\ast] \to_{x_1^\ast\,x_2^\ast} [t_2^\ast]` (i.e., :math:`C \vdashinstrseq \instr_1^\ast\;\instr_2^\ast : [t_1^\ast] \to_{x_1^\ast\,x_2^\ast} [t_2^\ast]`). + +.. note:: + More generally, instead of a shared type :math:`[t^\ast]`, it suffices if the output type of :math:`\instr_1^\ast` is a :ref:`subtype ` of the input type of :math:`\instr_1^\ast`, + since the subtype can always be weakened to its supertype by subsumption. + +Inversely, valid instruction sequences can also freely be *decomposed*, that is, splitting them anywhere produces two instruction sequences that are both :ref:`valid `. + +**Theorem (Decomposition).** +If an instruction sequence :math:`\instr^\ast` that is valid with type :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]` (i.e., :math:`C \vdashinstrseq \instr^\ast : [t_1^\ast] \to_{x^\ast} [t_2^\ast]`) +is split into two instruction sequences :math:`\instr_1^\ast` and :math:`\instr_2^\ast` at any point (i.e., :math:`\instr^\ast = \instr_1^\ast\;\instr_2^\ast`), +then these are separately valid with some types :math:`[t_1^\ast] \to_{x_1^\ast} [t^\ast]` and :math:`[t^\ast] \to_{x_2^\ast} [t_2^\ast]`, respectively (i.e., :math:`C \vdashinstrseq \instr_1^\ast : [t_1^\ast] \to_{x_1^\ast} [t^\ast]` and :math:`C \vdashinstrseq \instr_1^\ast : [t^\ast] \to_{x_2^\ast} [t_2^\ast]`), +where :math:`x^\ast = x_1^\ast\;x_2^\ast`. + +.. note:: + This property holds because validation is required even for unreachable code. + Without that, :math:`\instr_2^\ast` might not be valid in isolation. diff --git a/document/core/binary/instructions.rst b/document/core/binary/instructions.rst index e13a03236e..f054013e6c 100644 --- a/document/core/binary/instructions.rst +++ b/document/core/binary/instructions.rst @@ -26,6 +26,7 @@ Control Instructions :ref:`Block types ` are encoded in special compressed form, by either the byte :math:`\hex{40}` indicating the empty type, as a single :ref:`value type `, or as a :ref:`type index ` encoded as a positive :ref:`signed integer `. .. _binary-blocktype: +.. _binary-castflags: .. _binary-nop: .. _binary-unreachable: .. _binary-block: @@ -34,16 +35,24 @@ Control Instructions .. _binary-br: .. _binary-br_if: .. _binary-br_table: +.. _binary-br_on_null: +.. _binary-br_on_non_null: +.. _binary-br_on_cast: +.. _binary-br_on_cast_fail: .. _binary-return: .. _binary-call: +.. _binary-call_ref: .. _binary-call_indirect: +.. _binary-return_call: +.. _binary-return_call_ref: +.. _binary-return_call_indirect: .. math:: - \begin{array}{llcllll} + \begin{array}{@{}llcllll} \production{block type} & \Bblocktype &::=& \hex{40} &\Rightarrow& \epsilon \\ &&|& t{:}\Bvaltype &\Rightarrow& t \\ &&|& - x{:}\Bs33 &\Rightarrow& x & (\iff x \geq 0) \\ + x{:}\Bs33 &\Rightarrow& x \qquad (\iff x \geq 0) \\ \production{instruction} & \Binstr &::=& \hex{00} &\Rightarrow& \UNREACHABLE \\ &&|& \hex{01} &\Rightarrow& \NOP \\ &&|& @@ -53,7 +62,7 @@ Control Instructions &\Rightarrow& \LOOP~\X{bt}~\X{in}^\ast~\END \\ &&|& \hex{04}~~\X{bt}{:}\Bblocktype~~(\X{in}{:}\Binstr)^\ast~~\hex{0B} &\Rightarrow& \IF~\X{bt}~\X{in}^\ast~\ELSE~\epsilon~\END \\ &&|& - \hex{04}~~\X{bt}{:}\Bblocktype~~(\X{in}_1{:}\Binstr)^\ast~~ + \hex{04}~~\X{bt}{:}\Bblocktype~~(\X{in}_1{:}\Binstr)^\ast\\&&&~~ \hex{05}~~(\X{in}_2{:}\Binstr)^\ast~~\hex{0B} &\Rightarrow& \IF~\X{bt}~\X{in}_1^\ast~\ELSE~\X{in}_2^\ast~\END \\ &&|& \hex{0C}~~l{:}\Blabelidx &\Rightarrow& \BR~l \\ &&|& @@ -62,7 +71,20 @@ Control Instructions &\Rightarrow& \BRTABLE~l^\ast~l_N \\ &&|& \hex{0F} &\Rightarrow& \RETURN \\ &&|& \hex{10}~~x{:}\Bfuncidx &\Rightarrow& \CALL~x \\ &&|& - \hex{11}~~y{:}\Btypeidx~~x{:}\Btableidx &\Rightarrow& \CALLINDIRECT~x~y \\ + \hex{11}~~y{:}\Btypeidx~~x{:}\Btableidx &\Rightarrow& \CALLINDIRECT~x~y \\ &&|& + \hex{12}~~x{:}\Bfuncidx &\Rightarrow& \RETURNCALL~x \\ &&|& + \hex{13}~~y{:}\Btypeidx~~x{:}\Btableidx &\Rightarrow& \RETURNCALLINDIRECT~x~y \\ &&|& + \hex{14}~~x{:}\Btypeidx &\Rightarrow& \CALLREF~x \\ &&|& + \hex{15}~~x{:}\Btypeidx &\Rightarrow& \RETURNCALLREF~x \\ &&|& + \hex{D5}~~l{:}\Blabelidx &\Rightarrow& \BRONNULL~l \\ &&|& + \hex{D6}~~l{:}\Blabelidx &\Rightarrow& \BRONNONNULL~l \\ &&|& + \hex{FB}~~24{:}\Bu32~~(\NULL_1^?,\NULL_2^?){:}\Bcastflags\\&&&~~~~l{:}\Blabelidx~~\X{ht}_1{:}\Bheaptype~~\X{ht}_2{:}\Bheaptype &\Rightarrow& \BRONCAST~l~(\REF~\NULL_1^?~\X{ht}_1)~(\REF~\NULL_2^?~\X{ht}_2) \\ &&|& + \hex{FB}~~25{:}\Bu32~~(\NULL_1^?,\NULL_2^?){:}\Bcastflags\\&&&~~~~l{:}\Blabelidx~~\X{ht}_1{:}\Bheaptype~~\X{ht}_2{:}\Bheaptype &\Rightarrow& \BRONCASTFAIL~l~(\REF~\NULL_1^?~\X{ht}_1)~(\REF~\NULL_2^?~\X{ht}_2) \\ + \production{cast flags} & \Bcastflags &::=& + 0{:}\Bu8 &\Rightarrow& (\epsilon, \epsilon) \\ &&|& + 1{:}\Bu8 &\Rightarrow& (\NULL, \epsilon) \\ &&|& + 2{:}\Bu8 &\Rightarrow& (\epsilon, \NULL) \\ &&|& + 3{:}\Bu8 &\Rightarrow& (\NULL, \NULL) \\ \end{array} .. note:: @@ -79,21 +101,81 @@ Control Instructions Reference Instructions ~~~~~~~~~~~~~~~~~~~~~~ -:ref:`Reference instructions ` are represented by single byte codes. +Generic :ref:`reference instructions ` are represented by single byte codes, others use prefixes and type operands. .. _binary-ref.null: .. _binary-ref.func: .. _binary-ref.is_null: +.. _binary-ref.as_non_null: +.. _binary-struct.new: +.. _binary-struct.new_default: +.. _binary-struct.get: +.. _binary-struct.get_s: +.. _binary-struct.get_u: +.. _binary-struct.set: +.. _binary-array.new: +.. _binary-array.new_default: +.. _binary-array.new_fixed: +.. _binary-array.new_elem: +.. _binary-array.new_data: +.. _binary-array.get: +.. _binary-array.get_s: +.. _binary-array.get_u: +.. _binary-array.set: +.. _binary-array.len: +.. _binary-array.fill: +.. _binary-array.copy: +.. _binary-array.init_data: +.. _binary-array.init_elem: +.. _binary-ref.i31: +.. _binary-i31.get_s: +.. _binary-i31.get_u: +.. _binary-ref.test: +.. _binary-ref.cast: +.. _binary-any.convert_extern: +.. _binary-extern.convert_any: .. math:: \begin{array}{llclll} \production{instruction} & \Binstr &::=& \dots \\ &&|& - \hex{D0}~~t{:}\Breftype &\Rightarrow& \REFNULL~t \\ &&|& + \hex{D0}~~t{:}\Bheaptype &\Rightarrow& \REFNULL~t \\ &&|& \hex{D1} &\Rightarrow& \REFISNULL \\ &&|& - \hex{D2}~~x{:}\Bfuncidx &\Rightarrow& \REFFUNC~x \\ + \hex{D2}~~x{:}\Bfuncidx &\Rightarrow& \REFFUNC~x \\ &&|& + \hex{D3} &\Rightarrow& \REFEQ \\ &&|& + \hex{D4} &\Rightarrow& \REFASNONNULL \\ &&|& + \hex{FB}~~0{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \STRUCTNEW~x \\ &&|& + \hex{FB}~~1{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \STRUCTNEWDEFAULT~x \\ &&|& + \hex{FB}~~2{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bfieldidx &\Rightarrow& \STRUCTGET~x~y \\ &&|& + \hex{FB}~~3{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bfieldidx &\Rightarrow& \STRUCTGETS~x~y \\ &&|& + \hex{FB}~~4{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bfieldidx &\Rightarrow& \STRUCTGETU~x~y \\ &&|& + \hex{FB}~~5{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bfieldidx &\Rightarrow& \STRUCTSET~x~y \\ &&|& + \hex{FB}~~6{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYNEW~x \\ &&|& + \hex{FB}~~7{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYNEWDEFAULT~x \\ &&|& + \hex{FB}~~8{:}\Bu32~~x{:}\Btypeidx~~n{:}\Bu32 &\Rightarrow& \ARRAYNEWFIXED~x~n \\ &&|& + \hex{FB}~~9{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bdataidx &\Rightarrow& \ARRAYNEWDATA~x~y \\ &&|& + \hex{FB}~~10{:}\Bu32~~x{:}\Btypeidx~~y{:}\Belemidx &\Rightarrow& \ARRAYNEWELEM~x~y \\ &&|& + \hex{FB}~~11{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYGET~x \\ &&|& + \hex{FB}~~12{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYGETS~x \\ &&|& + \hex{FB}~~13{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYGETU~x \\ &&|& + \hex{FB}~~14{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYSET~x \\ &&|& + \hex{FB}~~15{:}\Bu32 &\Rightarrow& \ARRAYLEN \\ &&|& + \hex{FB}~~16{:}\Bu32~~x{:}\Btypeidx &\Rightarrow& \ARRAYFILL~x \\ &&|& + \hex{FB}~~17{:}\Bu32~~x{:}\Btypeidx~~y{:}\Btypeidx &\Rightarrow& \ARRAYCOPY~x~y \\ &&|& + \hex{FB}~~18{:}\Bu32~~x{:}\Btypeidx~~y{:}\Bdataidx &\Rightarrow& \ARRAYINITDATA~x~y \\ &&|& + \hex{FB}~~19{:}\Bu32~~x{:}\Btypeidx~~y{:}\Belemidx &\Rightarrow& \ARRAYINITELEM~x~y \\ &&|& + \hex{FB}~~20{:}\Bu32~~\X{ht}{:}\Bheaptype &\Rightarrow& \REFTEST~(\REF~\X{ht}) \\ &&|& + \hex{FB}~~21{:}\Bu32~~\X{ht}{:}\Bheaptype &\Rightarrow& \REFTEST~(\REF~\NULL~\X{ht}) \\ &&|& + \hex{FB}~~22{:}\Bu32~~\X{ht}{:}\Bheaptype &\Rightarrow& \REFCAST~(\REF~\X{ht}) \\ &&|& + \hex{FB}~~23{:}\Bu32~~\X{ht}{:}\Bheaptype &\Rightarrow& \REFCAST~(\REF~\NULL~\X{ht}) \\ &&|& + \hex{FB}~~26{:}\Bu32 &\Rightarrow& \ANYCONVERTEXTERN \\ &&|& + \hex{FB}~~27{:}\Bu32 &\Rightarrow& \EXTERNCONVERTANY \\ &&|& + \hex{FB}~~28{:}\Bu32 &\Rightarrow& \REFI31 \\ &&|& + \hex{FB}~~29{:}\Bu32 &\Rightarrow& \I31GETS \\ &&|& + \hex{FB}~~30{:}\Bu32 &\Rightarrow& \I31GETU \\ \end{array} + .. index:: parametric instruction, value type, polymorphism pair: binary format; instruction .. _binary-instr-parametric: @@ -179,7 +261,7 @@ Table Instructions Memory Instructions ~~~~~~~~~~~~~~~~~~~ -Each variant of :ref:`memory instruction ` is encoded with a different byte code. Loads and stores are followed by the encoding of their |memarg| immediate. +Each variant of :ref:`memory instruction ` is encoded with a different byte code. Loads and stores are followed by the encoding of their |memarg| immediate, which includes the :ref:`memory index ` if bit 6 of the flags field containing alignment is set; the memory index defaults to 0 otherwise. .. _binary-memarg: .. _binary-load: @@ -194,9 +276,12 @@ Each variant of :ref:`memory instruction ` is encoded with .. _binary-data.drop: .. math:: - \begin{array}{llclll} + \begin{array}{llcllll} \production{memory argument} & \Bmemarg &::=& - a{:}\Bu32~~o{:}\Bu32 &\Rightarrow& \{ \ALIGN~a,~\OFFSET~o \} \\ + a{:}\Bu32~~o{:}\Bu32 &\Rightarrow& 0~\{ \ALIGN~a,~\OFFSET~o \} + & (\iff a < 2^6) \\ &&|& + a{:}\Bu32~~x{:}\memidx~~o{:}\Bu32 &\Rightarrow& x~\{ \ALIGN~(a - 2^6),~\OFFSET~o \} + & (\iff 2^6 \leq a < 2^7) \\ \production{instruction} & \Binstr &::=& \dots \\ &&|& \hex{28}~~m{:}\Bmemarg &\Rightarrow& \I32.\LOAD~m \\ &&|& \hex{29}~~m{:}\Bmemarg &\Rightarrow& \I64.\LOAD~m \\ &&|& @@ -221,18 +306,14 @@ Each variant of :ref:`memory instruction ` is encoded with \hex{3C}~~m{:}\Bmemarg &\Rightarrow& \I64.\STORE\K{8}~m \\ &&|& \hex{3D}~~m{:}\Bmemarg &\Rightarrow& \I64.\STORE\K{16}~m \\ &&|& \hex{3E}~~m{:}\Bmemarg &\Rightarrow& \I64.\STORE\K{32}~m \\ &&|& - \hex{3F}~~\hex{00} &\Rightarrow& \MEMORYSIZE \\ &&|& - \hex{40}~~\hex{00} &\Rightarrow& \MEMORYGROW \\ &&|& - \hex{FC}~~8{:}\Bu32~~x{:}\Bdataidx~\hex{00} &\Rightarrow& \MEMORYINIT~x \\ &&|& + \hex{3F}~~x{:}\Bmemidx &\Rightarrow& \MEMORYSIZE~x \\ &&|& + \hex{40}~~x{:}\Bmemidx &\Rightarrow& \MEMORYGROW~x \\ &&|& + \hex{FC}~~8{:}\Bu32~~y{:}\Bdataidx~x{:}\Bmemidx &\Rightarrow& \MEMORYINIT~x~y \\ &&|& \hex{FC}~~9{:}\Bu32~~x{:}\Bdataidx &\Rightarrow& \DATADROP~x \\ &&|& - \hex{FC}~~10{:}\Bu32~~\hex{00}~~\hex{00} &\Rightarrow& \MEMORYCOPY \\ &&|& - \hex{FC}~~11{:}\Bu32~~\hex{00} &\Rightarrow& \MEMORYFILL \\ + \hex{FC}~~10{:}\Bu32~~x{:}\Bmemidx~~y{:}\Bmemidx &\Rightarrow& \MEMORYCOPY~x~y \\ &&|& + \hex{FC}~~11{:}\Bu32~~x{:}\Bmemidx &\Rightarrow& \MEMORYFILL~x \\ \end{array} -.. note:: - In future versions of WebAssembly, the additional zero bytes occurring in the encoding of the |MEMORYSIZE|, |MEMORYGROW|, |MEMORYCOPY|, and |MEMORYFILL| instructions may be used to index additional memories. - - .. index:: numeric instruction pair: binary format; instruction .. _binary-instr-numeric: diff --git a/document/core/binary/modules.rst b/document/core/binary/modules.rst index 95ae768968..ac405f4eef 100644 --- a/document/core/binary/modules.rst +++ b/document/core/binary/modules.rst @@ -9,7 +9,7 @@ except that :ref:`function definitions ` are split into two section This separation enables *parallel* and *streaming* compilation of the functions in a module. -.. index:: index, type index, function index, table index, memory index, global index, element index, data index, local index, label index +.. index:: index, type index, function index, table index, memory index, global index, element index, data index, local index, label index, field index pair: binary format; type index pair: binary format; function index pair: binary format; table index @@ -19,6 +19,7 @@ except that :ref:`function definitions ` are split into two section pair: binary format; data index pair: binary format; local index pair: binary format; label index + pair: binary format; field index .. _binary-typeidx: .. _binary-funcidx: .. _binary-tableidx: @@ -28,6 +29,7 @@ except that :ref:`function definitions ` are split into two section .. _binary-dataidx: .. _binary-localidx: .. _binary-labelidx: +.. _binary-fieldidx: .. _binary-index: Indices @@ -46,6 +48,7 @@ All :ref:`indices ` are encoded with their respective value. \production{data index} & \Bdataidx &::=& x{:}\Bu32 &\Rightarrow& x \\ \production{local index} & \Blocalidx &::=& x{:}\Bu32 &\Rightarrow& x \\ \production{label index} & \Blabelidx &::=& l{:}\Bu32 &\Rightarrow& l \\ + \production{field index} & \Bfieldidx &::=& x{:}\Bu32 &\Rightarrow& x \\ \end{array} @@ -130,7 +133,7 @@ Their contents consist of a :ref:`name ` further identifying the cu If an implementation interprets the data of a custom section, then errors in that data, or the placement of the section, must not invalidate the module. -.. index:: ! type section, type definition +.. index:: ! type section, type definition, recursive type pair: binary format; type section pair: section; type .. _binary-typedef: @@ -140,12 +143,12 @@ Type Section ~~~~~~~~~~~~ The *type section* has the id 1. -It decodes into a vector of :ref:`function types ` that represent the |MTYPES| component of a :ref:`module `. +It decodes into a vector of :ref:`recursive types ` that represent the |MTYPES| component of a :ref:`module `. .. math:: \begin{array}{llclll} \production{type section} & \Btypesec &::=& - \X{ft}^\ast{:\,}\Bsection_1(\Bvec(\Bfunctype)) &\Rightarrow& \X{ft}^\ast \\ + \X{rt}^\ast{:\,}\Bsection_1(\Bvec(\Brectype)) &\Rightarrow& \X{rt}^\ast \\ \end{array} @@ -213,9 +216,18 @@ It decodes into a vector of :ref:`tables ` that represent the |MTA \production{table section} & \Btablesec &::=& \X{tab}^\ast{:}\Bsection_4(\Bvec(\Btable)) &\Rightarrow& \X{tab}^\ast \\ \production{table} & \Btable &::=& - \X{tt}{:}\Btabletype &\Rightarrow& \{ \TTYPE~\X{tt} \} \\ + \X{tt}{:}\Btabletype + &\Rightarrow& \{ \TTYPE~\X{tt}, \TINIT~(\REFNULL~\X{ht}) \} + \qquad \iff \X{tt} = \limits~(\REF~\NULL^?~\X{ht}) \\ &&|& + \hex{40}~~\hex{00}~~\X{tt}{:}\Btabletype~~e{:}\Bexpr + &\Rightarrow& \{ \TTYPE~\X{tt}, \TINIT~e \} \\ \end{array} +.. note:: + The encoding of a table type cannot start with byte :math:`\hex{40}`, + hence decoding is unambiguous. + The zero byte following it is reserved for future extensions. + .. index:: ! memory section, memory, memory type pair: binary format; memory @@ -332,7 +344,7 @@ It decodes into a vector of :ref:`element segments ` that represent \production{element segment} & \Belem &::=& 0{:}\Bu32~~e{:}\Bexpr~~y^\ast{:}\Bvec(\Bfuncidx) &\Rightarrow& \\&&&\quad - \{ \ETYPE~\FUNCREF, \EINIT~((\REFFUNC~y)~\END)^\ast, \EMODE~\EACTIVE~\{ \ETABLE~0, \EOFFSET~e \} \} \\ &&|& + \{ \ETYPE~(\REF~\FUNC), \EINIT~((\REFFUNC~y)~\END)^\ast, \EMODE~\EACTIVE~\{ \ETABLE~0, \EOFFSET~e \} \} \\ &&|& 1{:}\Bu32~~\X{et}:\Belemkind~~y^\ast{:}\Bvec(\Bfuncidx) &\Rightarrow& \\&&&\quad \{ \ETYPE~\X{et}, \EINIT~((\REFFUNC~y)~\END)^\ast, \EMODE~\EPASSIVE \} \\ &&|& @@ -344,7 +356,7 @@ It decodes into a vector of :ref:`element segments ` that represent \{ \ETYPE~\X{et}, \EINIT~((\REFFUNC~y)~\END)^\ast, \EMODE~\EDECLARATIVE \} \\ &&|& 4{:}\Bu32~~e{:}\Bexpr~~\X{el}^\ast{:}\Bvec(\Bexpr) &\Rightarrow& \\&&&\quad - \{ \ETYPE~\FUNCREF, \EINIT~\X{el}^\ast, \EMODE~\EACTIVE~\{ \ETABLE~0, \EOFFSET~e \} \} \\ &&|& + \{ \ETYPE~(\REF~\FUNC), \EINIT~\X{el}^\ast, \EMODE~\EACTIVE~\{ \ETABLE~0, \EOFFSET~e \} \} \\ &&|& 5{:}\Bu32~~\X{et}:\Breftype~~\X{el}^\ast{:}\Bvec(\Bexpr) &\Rightarrow& \\&&&\quad \{ \ETYPE~et, \EINIT~\X{el}^\ast, \EMODE~\EPASSIVE \} \\ &&|& @@ -355,7 +367,7 @@ It decodes into a vector of :ref:`element segments ` that represent &\Rightarrow& \\&&&\quad \{ \ETYPE~et, \EINIT~\X{el}^\ast, \EMODE~\EDECLARATIVE \} \\ \production{element kind} & \Belemkind &::=& - \hex{00} &\Rightarrow& \FUNCREF \\ + \hex{00} &\Rightarrow& (\REF~\FUNC) \\ \end{array} .. note:: @@ -408,15 +420,15 @@ denoting *count* locals of the same value type. \X{size}{:}\Bu32~~\X{code}{:}\Bfunc &\Rightarrow& \X{code} & (\iff \X{size} = ||\Bfunc||) \\ \production{function} & \Bfunc &::=& - (t^\ast)^\ast{:}\Bvec(\Blocals)~~e{:}\Bexpr - &\Rightarrow& \concat((t^\ast)^\ast), e - & (\iff |\concat((t^\ast)^\ast)| < 2^{32}) \\ + (\local^\ast)^\ast{:}\Bvec(\Blocals)~~e{:}\Bexpr + &\Rightarrow& \concat((\local^\ast)^\ast), e + & (\iff |\concat((\local^\ast)^\ast)| < 2^{32}) \\ \production{locals} & \Blocals &::=& - n{:}\Bu32~~t{:}\Bvaltype &\Rightarrow& t^n \\ + n{:}\Bu32~~t{:}\Bvaltype &\Rightarrow& \{ \LTYPE~t \}^n \\ \end{array} Here, :math:`\X{code}` ranges over pairs :math:`(\valtype^\ast, \expr)`. -The meta function :math:`\concat((t^\ast)^\ast)` concatenates all sequences :math:`t_i^\ast` in :math:`(t^\ast)^\ast`. +The meta function :math:`\concat((\local^\ast)^\ast)` concatenates all sequences :math:`\local_i^\ast` in :math:`(\local^\ast)^\ast`. Any code for which the length of the resulting sequence is out of bounds of the maximum size of a :ref:`vector ` is malformed. .. note:: @@ -519,7 +531,7 @@ Furthermore, it must be present if any :ref:`data index ` occurs \Bmagic \\ &&& \Bversion \\ &&& \Bcustomsec^\ast \\ &&& - \functype^\ast{:\,}\Btypesec \\ &&& + \rectype^\ast{:\,}\Btypesec \\ &&& \Bcustomsec^\ast \\ &&& \import^\ast{:\,}\Bimportsec \\ &&& \Bcustomsec^\ast \\ &&& @@ -545,7 +557,7 @@ Furthermore, it must be present if any :ref:`data index ` occurs \Bcustomsec^\ast \quad\Rightarrow\quad \{~ \begin{array}[t]{@{}l@{}} - \MTYPES~\functype^\ast, \\ + \MTYPES~\rectype^\ast, \\ \MFUNCS~\func^n, \\ \MTABLES~\table^\ast, \\ \MMEMS~\mem^\ast, \\ diff --git a/document/core/binary/types.rst b/document/core/binary/types.rst index 4df6618393..700cc2dbd6 100644 --- a/document/core/binary/types.rst +++ b/document/core/binary/types.rst @@ -45,6 +45,35 @@ Vector Types \end{array} +.. index:: heap type + pair: binary format; heap type +.. _binary-heaptype: +.. _binary-absheaptype: + +Heap Types +~~~~~~~~~~ + +:ref:`Heap types ` are encoded as either a single byte, or as a :ref:`type index ` encoded as a positive :ref:`signed integer `. + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{abstract heap type} & \Babsheaptype &::=& + \hex{73} &\Rightarrow& \NOFUNC \\ &&|& + \hex{72} &\Rightarrow& \NOEXTERN \\ &&|& + \hex{71} &\Rightarrow& \NONE \\ &&|& + \hex{70} &\Rightarrow& \FUNC \\ &&|& + \hex{6F} &\Rightarrow& \EXTERN \\ &&|& + \hex{6E} &\Rightarrow& \ANY \\ &&|& + \hex{6D} &\Rightarrow& \EQT \\ &&|& + \hex{6C} &\Rightarrow& \I31 \\ &&|& + \hex{6B} &\Rightarrow& \STRUCT \\ &&|& + \hex{6A} &\Rightarrow& \ARRAY \\ + \production{heap type} & \Bheaptype &::=& + \X{ht}{:}\Babsheaptype &\Rightarrow& \X{ht} \\ &&|& + x{:}\Bs33 &\Rightarrow& x & (\iff x \geq 0) \\ + \end{array} + + .. index:: reference type pair: binary format; reference type .. _binary-reftype: @@ -52,13 +81,14 @@ Vector Types Reference Types ~~~~~~~~~~~~~~~ -:ref:`Reference types ` are also encoded by a single byte. +:ref:`Reference types ` are either encoded by a single byte followed by a :ref:`heap type `, or, as a short form, directly as an :ref:`abstract heap type `. .. math:: \begin{array}{llclll@{\qquad\qquad}l} \production{reference type} & \Breftype &::=& - \hex{70} &\Rightarrow& \FUNCREF \\ &&|& - \hex{6F} &\Rightarrow& \EXTERNREF \\ + \hex{64}~~\X{ht}{:}\Bheaptype &\Rightarrow& \REF~\X{ht} \\ &&|& + \hex{63}~~\X{ht}{:}\Bheaptype &\Rightarrow& \REF~\NULL~\X{ht} \\ &&|& + \X{ht}{:}\Babsheaptype &\Rightarrow& \REF~\NULL~\X{ht} \\ \end{array} @@ -80,6 +110,8 @@ Value Types \end{array} .. note:: + The type :math:`\BOT` cannot occur in a module. + Value types can occur in contexts where :ref:`type indices ` are also allowed, such as in the case of :ref:`block types `. Thus, the binary format for types corresponds to the |SignedLEB128|_ :ref:`encoding ` of small negative :math:`\sN` values, so that they can coexist with (positive) type indices in the future. @@ -107,16 +139,109 @@ Result Types Function Types ~~~~~~~~~~~~~~ -:ref:`Function types ` are encoded by the byte :math:`\hex{60}` followed by the respective :ref:`vectors ` of parameter and result types. +:ref:`Function types ` are encoded by the respective :ref:`vectors ` of parameter and result types. .. math:: \begin{array}{llclll@{\qquad\qquad}l} \production{function type} & \Bfunctype &::=& - \hex{60}~~\X{rt}_1{:\,}\Bresulttype~~\X{rt}_2{:\,}\Bresulttype + \X{rt}_1{:\,}\Bresulttype~~\X{rt}_2{:\,}\Bresulttype &\Rightarrow& \X{rt}_1 \to \X{rt}_2 \\ \end{array} +.. index:: aggregate type, value type, structure type, array type, field type, storage type, packed type, mutability + pair: binary format; aggregate type + pair: binary format; structure type + pair: binary format; array type + pair: binary format; field type + pair: binary format; storage type + pair: binary format; packed type +.. _binary-aggrtype: +.. _binary-structtype: +.. _binary-arraytype: +.. _binary-fieldtype: +.. _binary-storagetype: +.. _binary-packedtype: + +Aggregate Types +~~~~~~~~~~~~~~~ + +:ref:`Aggregate types ` are encoded with their respective :ref:`field types `. + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{array type} & \Barraytype &::=& + \X{ft}{:\,}\Bfieldtype + &\Rightarrow& \X{ft} \\ + \production{structure type} & \Bstructtype &::=& + \X{ft}^\ast{:\,}\Bvec(\Bfieldtype) + &\Rightarrow& \X{ft}^\ast \\ + \production{field type} & \Bfieldtype &::=& + \X{st}{:}\Bstoragetype~~m{:}\Bmut + &\Rightarrow& m~\X{st} \\ + \production{storage type} & \Bstoragetype &::=& + t{:}\Bvaltype + &\Rightarrow& t \\ &&|& + t{:}\Bpackedtype + &\Rightarrow& t \\ + \production{packed type} & \Bpackedtype &::=& + \hex{78} + &\Rightarrow& \I8 \\ &&|& + \hex{77} + &\Rightarrow& \I16 \\ + \end{array} + + +.. index:: composite type, structure type, array type, function type + pair: binary format; composite type +.. _binary-comptype: + +Composite Types +~~~~~~~~~~~~~~~ + +:ref:`Composite types ` are encoded by a distinct byte followed by a type encoding of the respective form. + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{composite type} & \Bcomptype &::=& + \hex{5E}~~\X{at}{:}\Barraytype + &\Rightarrow& \TARRAY~\X{at} \\ &&|& + \hex{5F}~~\X{st}{:}\Bstructtype + &\Rightarrow& \TSTRUCT~\X{st} \\ &&|& + \hex{60}~~\X{ft}{:}\Bfunctype + &\Rightarrow& \TFUNC~\X{ft} \\ + \end{array} + + +.. index:: recursive type, sub type, composite type + pair: binary format; recursive type + pair: binary format; sub type +.. _binary-rectype: +.. _binary-subtype: + +Recursive Types +~~~~~~~~~~~~~~~ + +:ref:`Recursive types ` are encoded by the byte :math:`\hex{4E}` followed by a :ref:`vector ` of :ref:`sub types `. +Additional shorthands are recognized for unary recursions and sub types without super types. + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{recursive type} & \Brectype &::=& + \hex{4E}~~\X{st}^\ast{:\,}\Bvec(\Bsubtype) + &\Rightarrow& \TREC~\X{st}^\ast \\ &&|& + \X{st}{:}\Bsubtype + &\Rightarrow& \TREC~\X{st} \\ + \production{sub type} & \Bsubtype &::=& + \hex{50}~~x^\ast{:\,}\Bvec(\Btypeidx)~~\X{ct}{:}\Bcomptype + &\Rightarrow& \TSUB~x^\ast~\X{ct} \\ &&|& + \hex{4F}~~x^\ast{:\,}\Bvec(\Btypeidx)~~\X{ct}{:}\Bcomptype + &\Rightarrow& \TSUB~\TFINAL~x^\ast~\X{ct} \\ &&|& + \X{ct}{:}\Bcomptype + &\Rightarrow& \TSUB~\TFINAL~\epsilon~\X{ct} \\ + \end{array} + + .. index:: limits pair: binary format; limits .. _binary-limits: diff --git a/document/core/conf.py b/document/core/conf.py index 2d2ee81301..161faa4e78 100644 --- a/document/core/conf.py +++ b/document/core/conf.py @@ -60,7 +60,7 @@ name = 'WebAssembly' project = u'WebAssembly' title = u'WebAssembly Specification' -copyright = u'2022, WebAssembly Community Group' +copyright = u'2017-2024, WebAssembly Community Group' author = u'WebAssembly Community Group' editor = u'Andreas Rossberg (editor)' logo = 'static/webassembly.png' @@ -79,7 +79,7 @@ # built documents. # # The short X.Y version. -version = u'2.0' +version = u'3.0' # The full version, including alpha/beta/rc tags. release = version + ('' if proposal == '' else ' + ') + proposal + draft diff --git a/document/core/exec/index.rst b/document/core/exec/index.rst index d818126691..38764308b0 100644 --- a/document/core/exec/index.rst +++ b/document/core/exec/index.rst @@ -9,5 +9,7 @@ Execution conventions runtime numerics + types + values instructions modules diff --git a/document/core/exec/instructions.rst b/document/core/exec/instructions.rst index b03b600bfc..fdd0941b6f 100644 --- a/document/core/exec/instructions.rst +++ b/document/core/exec/instructions.rst @@ -192,61 +192,1384 @@ Reference Instructions .. _exec-ref.null: -:math:`\REFNULL~t` +:math:`\REFNULL~x` +....................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Push the value :math:`\REFNULL~\deftype` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + F; (\REFNULL~x) &\stepto& F; (\REFNULL~\deftype) + & (\iff \deftype = F.\AMODULE.\MITYPES[x]) \\ + \end{array} + +.. note:: + No formal reduction rule is required for the case |REFNULL| |ABSHEAPTYPE|, + since the instruction form is already a :ref:`value `. + + +.. _exec-ref.func: + +:math:`\REFFUNC~x` +.................. + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIFUNCS[x]` exists. + +3. Let :math:`a` be the :ref:`function address ` :math:`F.\AMODULE.\MIFUNCS[x]`. + +4. Push the value :math:`\REFFUNCADDR~a` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + F; (\REFFUNC~x) &\stepto& F; (\REFFUNCADDR~a) + & (\iff a = F.\AMODULE.\MIFUNCS[x]) \\ + \end{array} + + +.. _exec-ref.is_null: + +:math:`\REFISNULL` .................. -1. Push the value :math:`\REFNULL~t` to the stack. +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: + + a. Push the value :math:`\I32.\CONST~1` to the stack. + +4. Else: + + a. Push the value :math:`\I32.\CONST~0` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \reff~\REFISNULL &\stepto& (\I32.\CONST~1) + & (\iff \reff = \REFNULL~\X{ht}) \\ + \reff~\REFISNULL &\stepto& (\I32.\CONST~0) + & (\otherwise) \\ + \end{array} + + +.. _exec-ref.as_non_null: + +:math:`\REFASNONNULL` +..................... + +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: + + a. Trap. + +4. Push the value :math:`\reff` back to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \reff~\REFASNONNULL &\stepto& \TRAP + & (\iff \reff = \REFNULL~\X{ht}) \\ + \reff~\REFASNONNULL &\stepto& \reff + & (\otherwise) \\ + \end{array} + + +.. _exec-ref.eq: + +:math:`\REFEQ` +.............. + +1. Assert: due to :ref:`validation `, two :ref:`reference values ` are on the top of the stack. + +2. Pop the value :math:`\reff_2` from the stack. + +3. Pop the value :math:`\reff_1` from the stack. + +4. If :math:`\reff_1` is the same as :math:`\reff_2`, then: + + a. Push the value :math:`\I32.\CONST~1` to the stack. + +5. Else: + + a. Push the value :math:`\I32.\CONST~0` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \reff_1~\reff_2~\REFEQ &\stepto& (\I32.\CONST~1) + & (\iff \reff_1 = (\REFNULL~\X{ht}_1) \land \reff_2 = (\REFNULL~\X{ht}_2)) \\ + \reff_1~\reff_2~\REFEQ &\stepto& (\I32.\CONST~1) + & (\iff \reff_1 = \reff_2) \\ + \reff_1~\reff_2~\REFEQ &\stepto& (\I32.\CONST~0) + & (\otherwise) \\ + \end{array} + + +.. _exec-ref.test: + +:math:`\REFTEST~\X{rt}` +....................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Let :math:`\X{rt}_1` be the :ref:`reference type ` :math:`\insttype_{F.\AMODULE}(\X{rt})`. + +3. Assert: due to :ref:`validation `, :math:`\X{rt}_1` is :ref:`closed `. + +4. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +5. Pop the value :math:`\reff` from the stack. + +6. Assert: due to validation, the :ref:`reference value ` is :ref:`valid ` with some :ref:`reference type `. + +7. Let :math:`\X{rt}_2` be the :ref:`reference type ` of :math:`\reff`. + +8. If the :ref:`reference type ` :math:`\X{rt}_2` :ref:`matches ` :math:`\X{rt}_1`, then: + + a. Push the value :math:`\I32.\CONST~1` to the stack. + +9. Else: + + a. Push the value :math:`\I32.\CONST~0` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \reff~(\REFTEST~\X{rt}) &\stepto& (\I32.\CONST~1) + & (\iff S \vdashval \reff : \X{rt}' + \land \vdashreftypematch \X{rt}' \matchesreftype \insttype_{F.\AMODULE}(\X{rt})) \\ + S; F; \reff~(\REFTEST~\X{rt}) &\stepto& (\I32.\CONST~0) + & (\otherwise) \\ + \end{array} + + +.. _exec-ref.cast: + +:math:`\REFCAST~\X{rt}` +....................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Let :math:`\X{rt}_1` be the :ref:`reference type ` :math:`\insttype_{F.\AMODULE}(\X{rt})`. + +3. Assert: due to :ref:`validation `, :math:`\X{rt}_1` is :ref:`closed `. + +4. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +5. Pop the value :math:`\reff` from the stack. + +6. Assert: due to validation, the :ref:`reference value ` is :ref:`valid ` with some :ref:`reference type `. + +7. Let :math:`\X{rt}_2` be the :ref:`reference type ` of :math:`\reff`. + +8. If the :ref:`reference type ` :math:`\X{rt}_2` :ref:`matches ` :math:`\X{rt}_1`, then: + + a. Push the value :math:`\reff` back to the stack. + +9. Else: + + a. Trap. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \reff~(\REFCAST~\X{rt}) &\stepto& \reff + & (\iff S \vdashval \reff : \X{rt}' + \land \vdashreftypematch \X{rt}' \matchesreftype \insttype_{F.\AMODULE}(\X{rt})) \\ + S; F; \reff~(\REFCAST~\X{rt}) &\stepto& \TRAP + & (\otherwise) \\ + \end{array} + + + +.. _exec-ref.i31: + +:math:`\REFI31` +............... + +1. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` |I32| is on the top of the stack. + +2. Pop the value :math:`\I32.\CONST~i` from the stack. + +3. Let :math:`j` be the result of computing :math:`\wrap_{32,31}(i)`. + +4. Push the reference value :math:`(\REFI31NUM~j)` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + (\I32.\CONST~i)~\REFI31 &\stepto& (\REFI31NUM~\wrap_{32,31}(i)) + \end{array} + + +.. _exec-i31.get_sx: + +:math:`\I31GET\K{\_}\sx` +........................ + +1. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~\I31)` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +4. Assert: due to :ref:`validation `, a :math:`\reff` is a :ref:`scalar reference `. + +5. Let :math:`\REFI31NUM~i` be the reference value :math:`\reff`. + +6. Let :math:`j` be the result of computing :math:`\extend^{\sx}_{31,32}(i)`. + +7. Push the value :math:`\I32.\CONST~j` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + (\REFI31NUM~i)~\I31GET\K{\_}\sx &\stepto& (\I32.\CONST~\extend^{\sx}_{31,32}(i)) \\ + (\REFNULL~t)~\I31GET\K{\_}\sx &\stepto& \TRAP + \end{array} + + +.. _exec-struct.new: + +:math:`\STRUCTNEW~x` +.................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is a :ref:`structure type `. + +5. Let :math:`\TSTRUCT~\X{ft}^\ast` be the :ref:`expanded ` :ref:`structure type ` of :math:`\deftype`. + +6. Let :math:`n` be the length of the :ref:`field type ` sequence :math:`\X{ft}^\ast`. + +7. Assert: due to :ref:`validation `, :math:`n` :ref:`values ` are on the top of the stack. + +8. Pop the :math:`n` values :math:`\val^\ast` from the stack. + +9. For every value :math:`\val_i` in :math:`\val^\ast` and corresponding :ref:`field type ` :math:`\X{ft}_i` in :math:`\X{ft}^\ast`: + + a. Let :math:`\fieldval_i` be the result of computing :math:`\packval_{\X{ft}_i}(\val_i))`. + +10. Let :math:`\fieldval^\ast` the concatenation of all field values :math:`\fieldval_i`. + +11. Let :math:`\X{si}` be the :ref:`structure instance ` :math:`\{\SITYPE~\deftype, \SIFIELDS~\fieldval^\ast\}`. + +12. Let :math:`a` be the length of :math:`S.\SSTRUCTS`. + +13. Append :math:`\X{si}` to :math:`S.\SSTRUCTS`. + +14. Push the :ref:`structure reference ` :math:`\REFSTRUCTADDR~a` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \val^n~(\STRUCTNEW~x) &\stepto& S'; F; (\REFSTRUCTADDR~|S.\SSTRUCTS|) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TSTRUCT~\X{ft}^n \\ + \land & \X{si} = \{\SITYPE~F.\AMODULE.\MITYPES[x], \SIFIELDS~(\packval_{\X{ft}}(\val))^n\} \\ + \land & S' = S \with \SSTRUCTS = S.\SSTRUCTS~\X{si}) + \end{array} \\ + \end{array} + + +.. _exec-struct.new_default: + +:math:`\STRUCTNEWDEFAULT~x` +........................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is a :ref:`structure type `. + +5. Let :math:`\TSTRUCT~\X{ft}^\ast` be the :ref:`expanded ` :ref:`structure type ` of :math:`\deftype`. + +6. Let :math:`n` be the length of the :ref:`field type ` sequence :math:`\X{ft}^\ast`. + +7. For every :ref:`field type ` :math:`\X{ft}_i` in :math:`\X{ft}^\ast`: + + a. Let :math:`t_i` be the :ref:`value type ` :math:`\unpacktype(\X{ft}_i)`. + + b. Assert: due to :ref:`validation `, :math:`\default_{t_i}` is defined. + + c. Push the :ref:`value ` :math:`\default_{t_i}` to the stack. + +8. Execute the instruction :math:`(\STRUCTNEW~x)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + F; (\STRUCTNEWDEFAULT~x) &\stepto& (\default_{\unpacktype(\X{ft})}))^n~(\STRUCTNEW~x) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TSTRUCT~\X{ft}^n) + \end{array} \\ + \end{array} + +.. scratch + .. math:: + \begin{array}{lcl@{\qquad}l} + S; F; (\STRUCTNEWDEFAULT~x) &\stepto& S'; F; (\REFSTRUCTADDR~|S.\SSTRUCTS|) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TSTRUCT~\X{ft}^n \\ + \land & \X{si} = \{\SITYPE~F.\AMODULE.\MITYPES[x], \SIFIELDS~(\packval_{\X{ft}}(\default_{\unpacktype(\X{ft})}))^n\} \\ + \land & S' = S \with \SSTRUCTS = S.\SSTRUCTS~\X{si}) + \end{array} \\ + \end{array} + + +.. _exec-struct.get: +.. _exec-struct.get_sx: + +:math:`\STRUCTGET\K{\_}\sx^?~x~y` +................................. + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is a :ref:`structure type ` with at least :math:`y + 1` fields. + +5. Let :math:`\TSTRUCT~\X{ft}^\ast` be the :ref:`expanded ` :ref:`structure type ` of :math:`\deftype`. + +6. Let :math:`\X{ft}_y` be the :math:`y`-th :ref:`field type ` of :math:`\X{ft}^\ast`. + +7. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +8. Pop the value :math:`\reff` from the stack. + +9. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +10. Assert: due to :ref:`validation `, a :math:`\reff` is a :ref:`structure reference `. + +11. Let :math:`\REFSTRUCTADDR~a` be the reference value :math:`\reff`. + +12. Assert: due to :ref:`validation `, the :ref:`structure instance ` :math:`S.\SSTRUCTS[a]` exists and has at least :math:`y + 1` fields. + +13. Let :math:`\fieldval` be the :ref:`field value ` :math:`S.\SSTRUCTS[a].\SIFIELDS[y]`. + +14. Let :math:`\val` be the result of computing :math:`\unpackval^{\sx^?}_{\X{ft}_y}(\fieldval))`. + +15. Push the value :math:`\val` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; (\REFSTRUCTADDR~a)~(\STRUCTGET\K{\_}\sx^?~x~y) &\stepto& \val + & + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TSTRUCT~\X{ft}^n \\ + \land & \val = \unpackval^{\sx^?}_{\X{ft}^n[y]}(S.\SSTRUCTS[a].\SIFIELDS[y])) + \end{array} \\ + S; F; (\REFNULL~t)~(\STRUCTGET\K{\_}\sx^?~x~y) &\stepto& \TRAP + \end{array} + + +.. _exec-struct.set: + +:math:`\STRUCTSET~x~y` +...................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is a :ref:`structure type ` with at least :math:`y + 1` fields. + +5. Let :math:`\TSTRUCT~\X{ft}^\ast` be the :ref:`expanded ` :ref:`structure type ` of :math:`\deftype`. + +6. Let :math:`\X{ft}_y` be the :math:`y`-th :ref:`field type ` of :math:`\X{ft}^\ast`. + +7. Assert: due to :ref:`validation `, a :ref:`value ` is on the top of the stack. + +8. Pop the value :math:`\val` from the stack. + +9. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +10. Pop the value :math:`\reff` from the stack. + +11. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +12. Assert: due to :ref:`validation `, a :math:`\reff` is a :ref:`structure reference `. + +13. Let :math:`\REFSTRUCTADDR~a` be the reference value :math:`\reff`. + +14. Assert: due to :ref:`validation `, the :ref:`structure instance ` :math:`S.\SSTRUCTS[a]` exists and has at least :math:`y + 1` fields. + +15. Let :math:`\fieldval` be the result of computing :math:`\packval_{\X{ft}_y}(\val))`. + +16. Replace the :ref:`field value ` :math:`S.\SSTRUCTS[a].\SIFIELDS[y]` with :math:`\fieldval`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; (\REFSTRUCTADDR~a)~\val~(\STRUCTSET~x~y) &\stepto& S'; \epsilon + & + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TSTRUCT~\X{ft}^n \\ + \land & S' = S \with \SSTRUCTS[a].\SIFIELDS[y] = \packval_{\X{ft}^n[y]}(\val)) + \end{array} \\ + S; F; (\REFNULL~t)~\val~(\STRUCTSET~x~y) &\stepto& \TRAP + \end{array} + + +.. _exec-array.new: + +:math:`\ARRAYNEW~x` +................... + +1. Assert: due to :ref:`validation `, a :ref:`value ` of type :math:`\I32` is on the top of the stack. + +2. Pop the value :math:`(\I32.\CONST~n)` from the stack. + +3. Assert: due to :ref:`validation `, a :ref:`value ` is on the top of the stack. + +4. Pop the value :math:`\val` from the stack. + +5. Push the value :math:`\val` to the stack :math:`n` times. + +6. Execute the instruction :math:`(\ARRAYNEWFIXED~x~n)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \val~(\I32.\CONST~n)~(\ARRAYNEW~x) &\stepto& \val^n~(\ARRAYNEWFIXED~x~n) + \end{array} + +.. scratch + .. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \val~(\I32.\CONST~n)~(\ARRAYNEW~x) &\stepto& S'; F; (\REFARRAYADDR~|S.\SARRAYS|) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & \X{ai} = \{\AITYPE~F.\AMODULE.\MITYPES[x], \AIFIELDS~(\packval_{\X{ft}}(\val))^n\} \\ + \land & S' = S \with \SARRAYS = S.\SARRAYS~\X{ai}) + \end{array} \\ + \end{array} + + +.. _exec-array.new_default: + +:math:`\ARRAYNEWDEFAULT~x` +.......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` of :math:`\deftype`. + +6. Assert: due to :ref:`validation `, a :ref:`value ` of type :math:`\I32` is on the top of the stack. + +7. Pop the value :math:`\I32.\CONST~n` from the stack. + +8. Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\X{ft})`. + +9. Assert: due to :ref:`validation `, :math:`\default_t` is defined. + +10. Push the :ref:`value ` :math:`\default_t` to the stack :math:`n` times. + +11. Execute the instruction :math:`(\ARRAYNEWFIXED~x~n)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + F; (\I32.\CONST~n)~(\ARRAYNEWDEFAULT~x) &\stepto& (\default_{\unpacktype(\X{ft}}))^n~(\ARRAYNEWFIXED~x~n) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft}) + \end{array} \\ + \end{array} + +.. scratch + .. math:: + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~n)~(\ARRAYNEWDEFAULT~x) &\stepto& S'; F; (\REFARRAYADDR~|S.\SARRAYS|) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & \X{ai} = \{\AITYPE~F.\AMODULE.\MITYPES[x], \AIFIELDS~(\packval_{\X{ft}}(\default_{\unpacktype(\X{ft}}))^n\} \\ + \land & S' = S \with \SARRAYS = S.\SARRAYS~\X{ai}) + \end{array} \\ + \end{array} + + +.. _exec-array.new_fixed: + +:math:`\ARRAYNEWFIXED~x~n` +.......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is a :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` of :math:`\deftype`. + +6. Assert: due to :ref:`validation `, :math:`n` :ref:`values ` are on the top of the stack. + +7. Pop the :math:`n` values :math:`\val^\ast` from the stack. + +8. For every value :math:`\val_i` in :math:`\val^\ast`: + + a. Let :math:`\fieldval_i` be the result of computing :math:`\packval_{\X{ft}}(\val_i))`. + +9. Let :math:`\fieldval^\ast` be the concatenation of all field values :math:`\fieldval_i`. + +10. Let :math:`\X{ai}` be the :ref:`array instance ` :math:`\{\AITYPE~\deftype, \AIFIELDS~\fieldval^\ast\}`. + +11. Let :math:`a` be the length of :math:`S.\SARRAYS`. + +12. Append :math:`\X{ai}` to :math:`S.\SARRAYS`. + +13. Push the :ref:`array reference ` :math:`\REFARRAYADDR~a` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \val^n~(\ARRAYNEWFIXED~x~n) &\stepto& S'; F; (\REFARRAYADDR~|S.\SARRAYS|) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & \X{ai} = \{\AITYPE~F.\AMODULE.\MITYPES[x], \AIFIELDS~(\packval_{\X{ft}}(\val))^n\} \\ + \land & S' = S \with \SARRAYS = S.\SARRAYS~\X{ai}) + \end{array} \\ + \end{array} + + +.. _exec-array.new_data: + +:math:`\ARRAYNEWDATA~x~y` +......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` of :math:`\deftype`. + +6. Assert: due to :ref:`validation `, the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[y]` exists. + +7. Let :math:`\X{da}` be the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[y]`. + +8. Assert: due to :ref:`validation `, the :ref:`data instance ` :math:`S.\SDATAS[\X{da}]` exists. + +9. Let :math:`\datainst` be the :ref:`data instance ` :math:`S.\SDATAS[\X{da}]`. + +10. Assert: due to :ref:`validation `, two :ref:`values ` of type :math:`\I32` are on the top of the stack. + +11. Pop the value :math:`\I32.\CONST~n` from the stack. + +12. Pop the value :math:`\I32.\CONST~s` from the stack. + +13. Assert: due to :ref:`validation `, the :ref:`field type ` :math:`\X{ft}` has a defined :ref:`bit width `. + +14. Let :math:`z` be the :ref:`bit width ` of :ref:`field type ` :math:`\X{ft}` divided by eight. + +15. If the sum of :math:`s` and :math:`n` times :math:`z` is larger than the length of :math:`\datainst.\DIDATA`, then: + + a. Trap. + +16. Let :math:`b^\ast` be the :ref:`byte ` sequence :math:`\datainst.\DIDATA[s \slice n \cdot z]`. + +17. Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\X{ft})`. + +18. For each consecutive subsequence :math:`{b'}^n` of :math:`b^\ast`: + + a. Assert: due to :ref:`validation `, :math:`\bytes_{\X{ft}}` is defined. + + b. Let :math:`c_i` be the constant for which :math:`\bytes_{\X{ft}}(c_i)` is :math:`{b'}^n`. + + c. Push the value :math:`t.\CONST~c_i` to the stack. + +19. Execute the instruction :math:`(\ARRAYNEWFIXED~x~n)`. + +.. math:: + ~\\[-1ex] + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYNEWDATA~x~y) &\stepto& \TRAP + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & s + n\cdot|\X{ft}|/8 > |S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\DIDATA|) + \end{array} \\ + \\[1ex] + S; F; (\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYNEWDATA~x~y) &\stepto& (t.\CONST~c)^n~(\ARRAYNEWFIXED~x~n) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & t = \unpacktype(\X{ft}) \\ + \land & \concat((\bytes_{\X{ft}}(c))^n) = S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\DIDATA[s \slice n\cdot|\X{ft}|/8] \\ + \end{array} \\ + \end{array} + + +.. _exec-array.new_elem: + +:math:`\ARRAYNEWELEM~x~y` +......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`element address ` :math:`F.\AMODULE.\MIELEMS[y]` exists. + +3. Let :math:`\X{ea}` be the :ref:`element address ` :math:`F.\AMODULE.\MIELEMS[y]`. + +4. Assert: due to :ref:`validation `, the :ref:`element instance ` :math:`S.\SELEMS[\X{ea}]` exists. + +5. Let :math:`\eleminst` be the :ref:`element instance ` :math:`S.\SELEMS[\X{ea}]`. + +6. Assert: due to :ref:`validation `, two :ref:`values ` of type :math:`\I32` are on the top of the stack. + +7. Pop the value :math:`(\I32.\CONST~n)` from the stack. + +8. Pop the value :math:`(\I32.\CONST~s)` from the stack. + +9. If the sum of :math:`s` and :math:`n` is larger than the length of :math:`\eleminst.\EIELEM`, then: + + a. Trap. + +10. Let :math:`\reff^\ast` be the :ref:`reference ` sequence :math:`\eleminst.\EIELEM[s \slice n]`. + +11. Push the references :math:`\reff^\ast` to the stack. + +12. Execute the instruction :math:`(\ARRAYNEWFIXED~x~n)`. + +.. math:: + ~\\[-1ex] + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYNEWELEM~x~y) &\stepto& \TRAP + \\&& + (\iff s + n > |S.\SELEMS[F.\AMODULE.\MIELEMS[y]].\EIELEM|) + \\[1ex] + S; F; (\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYNEWELEM~x~y) &\stepto& \reff^n~(\ARRAYNEWFIXED~x~n) + \\&& + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \reff^n = S.\SELEMS[F.\AMODULE.\MIELEMS[y]].\EIELEM[s \slice n]) + \end{array} \\ + \end{array} + + +.. _exec-array.get: +.. _exec-array.get_sx: + +:math:`\ARRAYGET\K{\_}\sx^?~x` +.............................. + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` of :math:`\deftype`. + +6. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +7. Pop the value :math:`\I32.\CONST~i` from the stack. + +8. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +9. Pop the value :math:`\reff` from the stack. + +10. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +11. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +12. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +13. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +14. If :math:`n` is larger than or equal to the length of :math:`S.\SARRAYS[a].\AIFIELDS`, then: + + a. Trap. + +15. Let :math:`\fieldval` be the :ref:`field value ` :math:`S.\SARRAYS[a].\AIFIELDS[i]`. + +16. Let :math:`\val` be the result of computing :math:`\unpackval^{\sx^?}_{\X{ft}}(\fieldval))`. + +17. Push the value :math:`\val` to the stack. + +.. math:: + ~\\[-1ex] + \begin{array}{lcl@{\qquad}l} + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~i)~(\ARRAYGET\K{\_}\sx^?~x) \stepto \TRAP + \\ \qquad + (\iff i \geq |\SARRAYS[a].\AIFIELDS|) + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~i)~(\ARRAYGET\K{\_}\sx^?~x) \stepto \val + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & \val = \unpackval^{\sx^?}_{\X{ft}}(S.\SARRAYS[a].\AIFIELDS[i])) \\ + \end{array} + \\[1ex] + S; F; (\REFNULL~t)~(\I32.\CONST~i)~(\ARRAYGET\K{\_}\sx^?~x) \stepto \TRAP + \end{array} + + +.. _exec-array.set: + +:math:`\ARRAYSET~x` +................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` of :math:`\deftype`. + +6. Assert: due to :ref:`validation `, a :ref:`value ` is on the top of the stack. + +7. Pop the value :math:`\val` from the stack. + +8. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +9. Pop the value :math:`\I32.\CONST~i` from the stack. + +10. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +11. Pop the value :math:`\reff` from the stack. + +12. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +13. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +14. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +15. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +16. If :math:`n` is larger than or equal to the length of :math:`S.\SARRAYS[a].\AIFIELDS`, then: + + a. Trap. + +17. Let :math:`\fieldval` be the result of computing :math:`\packval_{\X{ft}}(\val))`. + +18. Replace the :ref:`field value ` :math:`S.\SARRAYS[a].\AIFIELDS[i]` with :math:`\fieldval`. + +.. math:: + ~\\[-1ex] + \begin{array}{lcl@{\qquad}l} + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~i)~\val~(\ARRAYSET~x) \stepto \TRAP + \\ \qquad + (\iff i \geq |\SARRAYS[a].\AIFIELDS|) + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~i)~\val~(\ARRAYSET~x) \stepto S'; \epsilon + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & \expanddt(F.\AMODULE.\MITYPES[x]) = \TARRAY~\X{ft} \\ + \land & S' = S \with \SARRAYS[a].\AIFIELDS[i] = \packval_{\X{ft}}(\val)) \\ + \end{array} + \\[1ex] + S; F; (\REFNULL~t)~(\I32.\CONST~i)~\val~(\ARRAYSET~x) \stepto \TRAP + \end{array} + + +.. _exec-array.len: + +:math:`\ARRAYLEN` +................. + +1. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~\ARRAY)` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +4. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +5. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +6. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +7. Let :math:`n` be the length of :math:`S.\SARRAYS[a].\AIFIELDS`. + +8. Push the :ref:`value ` :math:`(\I32.\CONST~n)` to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; (\REFARRAYADDR~a)~\ARRAYLEN &\stepto& (\I32.\CONST~|S.\SARRAYS[a].\AIFIELDS|) \\ + S; (\REFNULL~t)~\ARRAYLEN &\stepto& \TRAP + \end{array} + + +.. _exec-array.fill: + +:math:`\ARRAYFILL~x` +.................... + +1. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +2. Pop the value :math:`n` from the stack. + +3. Assert: due to :ref:`validation `, a :ref:`value ` is on the top of the stack. + +4. Pop the value :math:`\val` from the stack. + +5. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +6. Pop the value :math:`d` from the stack. + +7. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +8. Pop the value :math:`\reff` from the stack. + +9. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +10. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +11. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +12. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +13. If :math:`d + n` is larger than the length of :math:`S.\SARRAYS[a].\AIFIELDS`, then: + + a. Trap. + +14. If :math:`n = 0`, then: + + a. Return. + +15. Push the value :math:`\REFARRAYADDR~a` to the stack. + +16. Push the value :math:`\I32.\CONST~d` to the stack. + +17. Push the value :math:`\val` to the stack. + +18. Execute the instruction :math:`\ARRAYSET~x`. + +19. Push the value :math:`\REFARRAYADDR~a` to the stack. + +20. Assert: due to the earlier check against the array size, :math:`d+1 < 2^{32}`. + +21. Push the value :math:`\I32.\CONST~(d+1)` to the stack. + +22. Push the value :math:`\val` to the stack. + +23. Push the value :math:`\I32.\CONST~(n-1)` to the stack. + +24. Execute the instruction :math:`\ARRAYFILL~x`. + +.. math:: + ~\\[-1ex] + \begin{array}{l} + S; (\REFARRAYADDR~a)~(\I32.\CONST~d)~\val~(\I32.\CONST~n)~(\ARRAYFILL~x) + \quad\stepto\quad \TRAP + \\ \qquad + (\iff d + n > |S.\SARRAYS[a].\AIFIELDS|) + \\[1ex] + S; (\REFARRAYADDR~a)~(\I32.\CONST~d)~\val~(\I32.\CONST~0)~(\ARRAYFILL~x) + \quad\stepto\quad \epsilon + \\ \qquad + (\otherwise) + \\[1ex] + S; (\REFARRAYADDR~a)~(\I32.\CONST~d)~\val~(\I32.\CONST~n+1)~(\ARRAYFILL~x) + \quad\stepto + \\ \quad + \begin{array}[t]{@{}l@{}} + (\REFARRAYADDR~a)~(\I32.\CONST~d)~\val~(\ARRAYSET~x) \\ + (\REFARRAYADDR~a)~(\I32.\CONST~d+1)~\val~(\I32.\CONST~n)~(\ARRAYFILL~x) \\ + \end{array} + \\ \qquad + (\otherwise) + \\[1ex] + S; (\REFNULL~t)~(\I32.\CONST~d)~\val~(\I32.\CONST~n)~(\ARRAYFILL~x) \quad\stepto\quad \TRAP + \end{array} + + +.. _exec-array.copy: + +:math:`\ARRAYCOPY~x~y` +...................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[y]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[y]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\mut~\X{st}` be the :ref:`expanded ` :ref:`array type ` :math:`\deftype`. + +6. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +7. Pop the value :math:`\I32.\CONST~n` from the stack. + +8. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +9. Pop the value :math:`\I32.\CONST~s` from the stack. + +10. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~y)` is on the top of the stack. + +11. Pop the value :math:`\reff_2` from the stack. + +12. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`\I32` is on the top of the stack. + +13. Pop the value :math:`\I32.\CONST~d` from the stack. + +14. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +15. Pop the value :math:`\reff_1` from the stack. + +16. If :math:`\reff_1` is :math:`\REFNULL~t`, then: + + a. Trap. + +17. Assert: due to :ref:`validation `, :math:`\reff_1` is an :ref:`array reference `. + +18. Let :math:`\REFARRAYADDR~a_1` be the reference value :math:`\reff_1`. + +19. If :math:`\reff_2` is :math:`\REFNULL~t`, then: + + a. Trap. + +20. Assert: due to :ref:`validation `, :math:`\reff_2` is an :ref:`array reference `. + +21. Let :math:`\REFARRAYADDR~a_2` be the reference value :math:`\reff_2`. + +22. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a_1]` exists. + +23. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a_2]` exists. + +24. If :math:`d + n` is larger than the length of :math:`S.\SARRAYS[a_1].\AIFIELDS`, then: + + a. Trap. + +25. If :math:`s + n` is larger than the length of :math:`S.\SARRAYS[a_2].\AIFIELDS`, then: + + a. Trap. + +26. If :math:`n = 0`, then: + + a. Return. + +27. If :math:`d \leq s`, then: + + a. Push the value :math:`\REFARRAYADDR~a_1` to the stack. + + b. Push the value :math:`\I32.\CONST~d` to the stack. + + c. Push the value :math:`\REFARRAYADDR~a_2` to the stack. + + d. Push the value :math:`\I32.\CONST~s` to the stack. + + e. Execute :math:`\getfield(\X{st})`. + + f. Execute the instruction :math:`\ARRAYSET~x`. + + g. Push the value :math:`\REFARRAYADDR~a_1` to the stack. + + h. Assert: due to the earlier check against the array size, :math:`d+1 < 2^{32}`. + + i. Push the value :math:`\I32.\CONST~(d+1)` to the stack. + + j. Push the value :math:`\REFARRAYADDR~a_2` to the stack. + + k. Assert: due to the earlier check against the array size, :math:`s+1 < 2^{32}`. + + l. Push the value :math:`\I32.\CONST~(s+1)` to the stack. + +28. Else: + + a. Push the value :math:`\REFARRAYADDR~a_1` to the stack. + + b. Assert: due to the earlier check against the memory size, :math:`d+n-1 < 2^{32}`. + + c. Push the value :math:`\I32.\CONST~(d+n-1)` to the stack. + + d. Push the value :math:`\REFARRAYADDR~a_2` to the stack. + + e. Assert: due to the earlier check against the memory size, :math:`s+n-1 < 2^{32}`. + + f. Push the value :math:`\I32.\CONST~(s+n-1)` to the stack. + + g. Execute :math:`\getfield(\X{st})`. + + h. Execute the instruction :math:`\ARRAYSET~x`. + + i. Push the value :math:`\REFARRAYADDR~a_1` to the stack. + + j. Push the value :math:`\I32.\CONST~d` to the stack. + + k. Push the value :math:`\REFARRAYADDR~a_2` to the stack. + + l. Push the value :math:`\I32.\CONST~s` to the stack. + +29. Push the value :math:`\I32.\CONST~(n-1)` to the stack. + +30. Execute the instruction :math:`\ARRAYCOPY~x~y`. + +.. math:: + ~\\[-1ex] + \begin{array}{l} + S; F; (\REFARRAYADDR~a_1)~(\I32.\CONST~d)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYCOPY~x~y) + \quad\stepto\quad \TRAP + \\ \qquad + (\iff d + n > |S.\SARRAYS[a_1].\AIFIELDS| \vee s + n > |S.\SARRAYS[a_2].\AIFIELDS|) + \\[1ex] + S; F; (\REFARRAYADDR~a_1)~(\I32.\CONST~d)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s)~(\I32.\CONST~0)~(\ARRAYCOPY~x~y) + \quad\stepto\quad \epsilon + \\ \qquad + (\otherwise) + \\[1ex] + S; F; (\REFARRAYADDR~a_1)~(\I32.\CONST~d)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\ARRAYCOPY~x~y) + \quad\stepto + \\ \quad + \begin{array}[t]{@{}l@{}} + (\REFARRAYADDR~a_1)~(\I32.\CONST~d) \\ + (\REFARRAYADDR~a_2)~(\I32.\CONST~s)~\getfield(\X{st}) \\ + (\ARRAYSET~x) \\ + (\REFARRAYADDR~a_1)~(\I32.\CONST~d+1)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~(\ARRAYCOPY~x~y) \\ + \end{array} + \\ \qquad + (\otherwise, \iff d \leq s \land F.\AMODULE.\MITYPES[y] = \TARRAY~\mut~\X{st}) \\ + \\[1ex] + S; F; (\REFARRAYADDR~a_1)~(\I32.\CONST~d)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\ARRAYCOPY~x~y) + \quad\stepto + \\ \quad + \begin{array}[t]{@{}l@{}} + (\REFARRAYADDR~a_1)~(\I32.\CONST~d+n) \\ + (\REFARRAYADDR~a_2)~(\I32.\CONST~s+n)~\getfield(\X{st}) \\ + (\ARRAYSET~x) \\ + (\REFARRAYADDR~a_1)~(\I32.\CONST~d)~(\REFARRAYADDR~a_2)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYCOPY~x~y) \\ + \end{array} + \\ \qquad + (\otherwise, \iff d > s \land F.\AMODULE.\MITYPES[y] = \TARRAY~\mut~\X{st}) \\ + \\[1ex] + S; F; (\REFNULL~t)~(\I32.\CONST~d)~\val~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYCOPY~x~y) \quad\stepto\quad \TRAP + \\[1ex] + S; F; \val~(\I32.\CONST~d)~(\REFNULL~t)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYCOPY~x~y) \quad\stepto\quad \TRAP + \end{array} + +Where: + +.. _aux-getfield: + +.. math:: + \begin{array}{lll} + \getfield(\valtype) &=& \ARRAYGET~y \\ + \getfield(\packedtype) &=& \ARRAYGETU~y \\ + \end{array} + + +.. _exec-array.init_data: + +:math:`\ARRAYINITDATA~x~y` +.......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` :math:`\deftype`. + +6. Assert: due to :ref:`validation `, the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[y]` exists. + +7. Let :math:`\X{da}` be the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[y]`. + +8. Assert: due to :ref:`validation `, the :ref:`data instance ` :math:`S.\SDATAS[\X{da}]` exists. + +9. Let :math:`\datainst` be the :ref:`data instance ` :math:`S.\SDATAS[\X{da}]`. + +10. Assert: due to :ref:`validation `, three values of type :math:`\I32` are on the top of the stack. + +11. Pop the value :math:`\I32.\CONST~n` from the stack. + +12. Pop the value :math:`\I32.\CONST~s` from the stack. + +13. Pop the value :math:`\I32.\CONST~d` from the stack. + +14. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +15. Pop the value :math:`\reff` from the stack. + +16. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +17. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +18. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +19. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +20. Assert: due to :ref:`validation `, the :ref:`field type ` :math:`\X{ft}` has a defined :ref:`bit width `. + +21. Let :math:`z` be the :ref:`bit width ` of :ref:`field type ` :math:`\X{ft}` divided by eight. + +22. If :math:`d + n` is larger than the length of :math:`S.\SARRAYS[a].\AIFIELDS`, or the sum of :math:`s` and :math:`n` times :math:`z` is larger than the length of :math:`\datainst.\DIDATA`, then: + + a. Trap. + +23. If :math:`n = 0`, then: + + a. Return. + +24. Let :math:`b^\ast` be the :ref:`byte ` sequence :math:`\datainst.\DIDATA[s \slice z]`. + +25. Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\X{ft})`. + +26. Assert: due to :ref:`validation `, :math:`\bytes_{\X{ft}}` is defined. + +27. Let :math:`c` be the constant for which :math:`\bytes_{\X{ft}}(c)` is :math:`b^\ast`. + +28. Push the value :math:`\REFARRAYADDR~a` to the stack. + +29. Push the value :math:`\I32.\CONST~d` to the stack. + +30. Push the value :math:`t.\CONST~c` to the stack. + +31. Execute the instruction :math:`\ARRAYSET~x`. + +32. Push the value :math:`\REFARRAYADDR~a` to the stack. + +33. Push the value :math:`\I32.\CONST~(d+1)` to the stack. + +34. Push the value :math:`\I32.\CONST~(s+z)` to the stack. + +35. Push the value :math:`\I32.\CONST~(n-1)` to the stack. + +36. Execute the instruction :math:`\ARRAYINITDATA~x~y`. + +.. math:: + ~\\[-1ex] + \begin{array}{l} + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYINITDATA~x~y) \quad\stepto\quad \TRAP + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & d + n > |S.\SARRAYS[a].\AIFIELDS| \\ + \vee & (F.\AMODULE.\MITYPES[x] = \TARRAY~\X{ft} \land + s + n\cdot|\X{ft}|/8 > |S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\DIDATA|)) + \end{array} + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~(\ARRAYINITDATA~x~y) + \quad\stepto\quad \epsilon + \\ \qquad + (\otherwise) + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\ARRAYINITDATA~x~y) + \quad\stepto + \\ \quad + \begin{array}[t]{@{}l@{}} + (\REFARRAYADDR~a)~(\I32.\CONST~d)~(t.\CONST~c)~(\ARRAYSET~x) \\ + (\REFARRAYADDR~a)~(\I32.\CONST~d+1)~(\I32.\CONST~s+|\X{ft}|/8)~(\I32.\CONST~n)~(\ARRAYINITDATA~x~y) \\ + \end{array} + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\otherwise, \iff & F.\AMODULE.\MITYPES[x] = \TARRAY~\X{ft} \\ + \land & t = \unpacktype(\X{ft}) \\ + \land & \bytes_{\X{ft}}(c) = S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\DIDATA[s \slice |\X{ft}|/8] + \end{array} + \\[1ex] + S; F; (\REFNULL~t)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYINITDATA~x~y) \quad\stepto\quad \TRAP + \end{array} + + +.. _exec-array.init_elem: + +:math:`\ARRAYINITELEM~x~y` +.......................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]` exists. + +3. Let :math:`\deftype` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[x]`. + +4. Assert: due to :ref:`validation `, the :ref:`expansion ` of :math:`\deftype` is an :ref:`array type `. + +5. Let :math:`\TARRAY~\X{ft}` be the :ref:`expanded ` :ref:`array type ` :math:`\deftype`. + +6. Assert: due to :ref:`validation `, the :ref:`element address ` :math:`F.\AMODULE.\MIELEMS[y]` exists. + +7. Let :math:`\X{ea}` be the :ref:`element address ` :math:`F.\AMODULE.\MIELEMS[y]`. + +8. Assert: due to :ref:`validation `, the :ref:`element instance ` :math:`S.\SELEMS[\X{ea}]` exists. + +9. Let :math:`\eleminst` be the :ref:`element instance ` :math:`S.\SELEMS[\X{ea}]`. + +10. Assert: due to :ref:`validation `, three values of type :math:`\I32` are on the top of the stack. + +11. Pop the value :math:`\I32.\CONST~n` from the stack. + +12. Pop the value :math:`\I32.\CONST~s` from the stack. + +13. Pop the value :math:`\I32.\CONST~d` from the stack. + +14. Assert: due to :ref:`validation `, a :ref:`value ` of :ref:`type ` :math:`(\REF~\NULL~x)` is on the top of the stack. + +15. Pop the value :math:`\reff` from the stack. + +16. If :math:`\reff` is :math:`\REFNULL~t`, then: + + a. Trap. + +17. Assert: due to :ref:`validation `, :math:`\reff` is an :ref:`array reference `. + +18. Let :math:`\REFARRAYADDR~a` be the reference value :math:`\reff`. + +19. Assert: due to :ref:`validation `, the :ref:`array instance ` :math:`S.\SARRAYS[a]` exists. + +20. If :math:`d + n` is larger than the length of :math:`S.\SARRAYS[a].\AIFIELDS`, or :math:`s + n` is larger than the length of :math:`\eleminst.\EIELEM`, then: + + a. Trap. + +21. If :math:`n = 0`, then: + + a. Return. + +22. Let :math:`\reff'` be the :ref:`reference value ` :math:`\eleminst.\EIELEM[s]`. + +23. Push the value :math:`\REFARRAYADDR~a` to the stack. + +24. Push the value :math:`\I32.\CONST~d` to the stack. + +25. Push the value :math:`\reff'` to the stack. + +26. Execute the instruction :math:`\ARRAYSET~x`. + +27. Push the value :math:`\REFARRAYADDR~a` to the stack. + +28. Push the value :math:`\I32.\CONST~(d+1)` to the stack. + +29. Push the value :math:`\I32.\CONST~(s+1)` to the stack. + +30. Push the value :math:`\I32.\CONST~(n-1)` to the stack. + +31. Execute the instruction :math:`\ARRAYINITELEM~x~y`. + +.. math:: + ~\\[-1ex] + \begin{array}{l} + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYINITELEM~x~y) \quad\stepto\quad \TRAP + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & d + n > |S.\SARRAYS[a].\AIFIELDS| \\ + \vee & s + n > |S.\SELEMS[F.\AMODULE.\MIELEMS[y]].\EIELEM|) + \end{array} + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~(\ARRAYINITELEM~x~y) + \quad\stepto\quad \epsilon + \\ \qquad + (\otherwise) + \\[1ex] + S; F; (\REFARRAYADDR~a)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\ARRAYINITELEM~x~y) + \quad\stepto + \\ \quad + \begin{array}[t]{@{}l@{}} + (\REFARRAYADDR~a)~(\I32.\CONST~d)~\REF~(\ARRAYSET~x) \\ + (\REFARRAYADDR~a)~(\I32.\CONST~d+1)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~(\ARRAYINITELEM~x~y) \\ + \end{array} + \\ \qquad + (\otherwise, \iff \REF = S.\SELEMS[F.\AMODULE.\MIELEMS[y]].\EIELEM[s]) + \\[1ex] + S; F; (\REFNULL~t)~(\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\ARRAYINITELEM~x~y) \quad\stepto\quad \TRAP + \end{array} + -.. note:: - No formal reduction rule is required for this instruction, since the |REFNULL| instruction is already a :ref:`value `. +.. _exec-any.convert_extern: +:math:`\ANYCONVERTEXTERN` +......................... -.. _exec-ref.is_null: +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. -:math:`\REFISNULL` -.................. +2. Pop the value :math:`\reff` from the stack. -1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: -2. Pop the value :math:`\val` from the stack. + a. Push the reference value :math:`(\REFNULL~\ANY)` to the stack. -3. If :math:`\val` is :math:`\REFNULL~t`, then: +4. Else: - a. Push the value :math:`\I32.\CONST~1` to the stack. + a. Assert: due to :ref:`validation `, a :math:`\reff` is an :ref:`external reference `. -4. Else: + b. Let :math:`\REFEXTERN~\reff'` be the reference value :math:`\reff`. - a. Push the value :math:`\I32.\CONST~0` to the stack. + c. Push the reference value :math:`\reff'` to the stack. .. math:: \begin{array}{lcl@{\qquad}l} - \val~\REFISNULL &\stepto& (\I32.\CONST~1) - & (\iff \val = \REFNULL~t) \\ - \val~\REFISNULL &\stepto& (\I32.\CONST~0) - & (\otherwise) \\ + (\REFNULL~\X{ht})~\ANYCONVERTEXTERN &\stepto& (\REFNULL~\ANY) \\ + (\REFEXTERN~\reff)~\ANYCONVERTEXTERN &\stepto& \reff \\ \end{array} -.. _exec-ref.func: +.. _exec-extern.convert_any: -:math:`\REFFUNC~x` -.................. +:math:`\EXTERNCONVERTANY` +......................... -1. Let :math:`F` be the :ref:`current ` :ref:`frame `. +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIFUNCS[x]` exists. +2. Pop the value :math:`\reff` from the stack. -3. Let :math:`a` be the :ref:`function address ` :math:`F.\AMODULE.\MIFUNCS[x]`. +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: -4. Push the value :math:`\REFFUNCADDR~a` to the stack. + a. Push the reference value :math:`(\REFNULL~\EXTERN)` to the stack. + +4. Else: + + a. Let :math:`\reff'` be the reference value :math:`(\REFEXTERN~\reff)`. + + b. Push the reference value :math:`\reff'` to the stack. .. math:: \begin{array}{lcl@{\qquad}l} - F; (\REFFUNC~x) &\stepto& F; (\REFFUNCADDR~a) - & (\iff a = F.\AMODULE.\MIFUNCS[x]) \\ + (\REFNULL~\X{ht})~\EXTERNCONVERTANY &\stepto& (\REFNULL~\EXTERN) \\ + \reff~\EXTERNCONVERTANY &\stepto& (\REFEXTERN~\reff) & (\iff \reff \neq (\REFNULL~\X{ht})) \\ \end{array} + .. index:: vector instruction pair: execution; instruction single: abstract syntax; instruction @@ -453,7 +1776,7 @@ Most other vector instructions are defined in terms of numeric operators that ar :math:`\shape\K{.}\SPLAT` ......................... -1. Let :math:`t` be the type :math:`\unpacked(\shape)`. +1. Let :math:`t` be the type :math:`\unpackshape(\shape)`. 2. Assert: due to :ref:`validation `, a value of :ref:`value type ` :math:`t` is on the top of the stack. @@ -468,7 +1791,7 @@ Most other vector instructions are defined in terms of numeric operators that ar .. math:: \begin{array}{lcl@{\qquad}l} (t\K{.}\CONST~c_1)~\shape\K{.}\SPLAT &\stepto& (\V128\K{.}\VCONST~c) - & (\iff t = \unpacked(\shape) + & (\iff t = \unpackshape(\shape) \wedge c = \lanes^{-1}_{\shape}(c_1^{\dim(\shape)})) \\ \end{array} @@ -487,7 +1810,7 @@ Most other vector instructions are defined in terms of numeric operators that ar 4. Let :math:`i^\ast` be the result of computing :math:`\lanes_{t_1\K{x}N}(c_1)`. -5. Let :math:`t_2` be the type :math:`\unpacked(t_1\K{x}N)`. +5. Let :math:`t_2` be the type :math:`\unpackshape(t_1\K{x}N)`. 6. Let :math:`c_2` be the result of computing :math:`\extend^{sx^?}_{t_1,t_2}(i^\ast[x])`. @@ -500,7 +1823,7 @@ Most other vector instructions are defined in terms of numeric operators that ar \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & t_2 = \unpacked(t_1\K{x}N) \\ + (\iff & t_2 = \unpackshape(t_1\K{x}N) \\ \wedge & c_2 = \extend^{sx^?}_{t_1,t_2}(\lanes_{t_1\K{x}N}(c_1)[x])) \end{array} \end{array} @@ -513,7 +1836,7 @@ Most other vector instructions are defined in terms of numeric operators that ar 1. Assert: due to :ref:`validation `, :math:`x < \dim(\shape)`. -2. Let :math:`t_2` be the type :math:`\unpacked(\shape)`. +2. Let :math:`t_2` be the type :math:`\unpackshape(\shape)`. 3. Assert: due to :ref:`validation `, a value of :ref:`value type ` :math:`t_1` is on the top of the stack. @@ -673,7 +1996,7 @@ Most other vector instructions are defined in terms of numeric operators that ar 3. Let :math:`i_1^\ast` be the result of computing :math:`\lanes_{\shape}(c)`. -4. Let :math:`i` be the result of computing :math:`\bool(\bigwedge(i_1 \neq 0)^\ast)`. +4. Let :math:`i` be the result of computing :math:`\tobool(\bigwedge(i_1 \neq 0)^\ast)`. 5. Push the value :math:`\I32.\CONST~i` onto the stack. @@ -686,7 +2009,7 @@ Most other vector instructions are defined in terms of numeric operators that ar \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & i_1^\ast = \lanes_{\shape}(c) \\ - \wedge & i = \bool(\bigwedge(i_1 \neq 0)^\ast)) + \wedge & i = \tobool(\bigwedge(i_1 \neq 0)^\ast)) \end{array} \end{array} @@ -1076,7 +2399,7 @@ Variable Instructions 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\ALOCALS[x]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\ALOCALS[x]` exists and is non-empty. 3. Let :math:`\val` be the value :math:`F.\ALOCALS[x]`. @@ -1721,14 +3044,14 @@ Memory Instructions .. _exec-load: .. _exec-loadn: -:math:`t\K{.}\LOAD~\memarg` and :math:`t\K{.}\LOAD{N}\K{\_}\sx~\memarg` -....................................................................... +:math:`t\K{.}\LOAD~x~\memarg` and :math:`t\K{.}\LOAD{N}\K{\_}\sx~x~\memarg` +........................................................................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -1766,28 +3089,28 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\LOAD~\memarg) &\stepto& S; F; (t.\CONST~c) + S; F; (\I32.\CONST~i)~(t.\LOAD~x~\memarg) &\stepto& S; F; (t.\CONST~c) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + |t|/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_t(c) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice |t|/8]) \\[1ex] + \wedge & \X{ea} + |t|/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_t(c) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice |t|/8]) \\[1ex] \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\LOAD{N}\K{\_}\sx~\memarg) &\stepto& + S; F; (\I32.\CONST~i)~(t.\LOAD{N}\K{\_}\sx~x~\memarg) &\stepto& S; F; (t.\CONST~\extend^{\sx}_{N,|t|}(n)) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8]) \\[1ex] + \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8]) \\[1ex] \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\LOAD({N}\K{\_}\sx)^?~\memarg) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(t.\LOAD({N}\K{\_}\sx)^?~x~\memarg) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -1796,14 +3119,14 @@ Memory Instructions .. _exec-load-extend: -:math:`\V128\K{.}\LOAD{M}\K{x}N\_\sx~\memarg` -............................................. +:math:`\V128\K{.}\LOAD{M}\K{x}N\_\sx~x~\memarg` +............................................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -1835,20 +3158,20 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\LOAD{M}\K{x}N\_\sx~\memarg) &\stepto& + S; F; (\I32.\CONST~i)~(\V128.\LOAD{M}\K{x}N\_\sx~x~\memarg) &\stepto& S; F; (\V128.\CONST~c) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + M \cdot N / 8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_{\iM}(m_k) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} + k \cdot M/8 \slice M/8] \\ + \wedge & \X{ea} + M \cdot N / 8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_{\iM}(m_k) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} + k \cdot M/8 \slice M/8]) \\ \wedge & W = M \cdot 2 \\ \wedge & c = \lanes^{-1}_{\K{i}W\K{x}N}(\extend^{\sx}_{M,W}(m_0) \dots \extend^{\sx}_{M,W}(m_{N-1}))) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\LOAD{M}\K{x}N\K{\_}\sx~\memarg) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(\V128.\LOAD{M}\K{x}N\K{\_}\sx~x~\memarg) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -1857,14 +3180,14 @@ Memory Instructions .. _exec-load-splat: -:math:`\V128\K{.}\LOAD{N}\K{\_splat}~\memarg` -............................................. +:math:`\V128\K{.}\LOAD{N}\K{\_splat}~x~\memarg` +............................................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -1894,18 +3217,18 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128\K{.}\LOAD{N}\K{\_splat}~\memarg) &\stepto& S; F; (\V128.\CONST~c) + S; F; (\I32.\CONST~i)~(\V128\K{.}\LOAD{N}\K{\_splat}~x~\memarg) &\stepto& S; F; (\V128.\CONST~c) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8] \\ + \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8] \\ \wedge & c = \lanes^{-1}_{\IN\K{x}L}(n^L)) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\LOAD{N}\K{\_splat}~\memarg) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(\V128.\LOAD{N}\K{\_splat}~x~\memarg) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -1914,14 +3237,14 @@ Memory Instructions .. _exec-load-zero: -:math:`\V128\K{.}\LOAD{N}\K{\_zero}~\memarg` -............................................. +:math:`\V128\K{.}\LOAD{N}\K{\_zero}~x~\memarg` +.............................................. 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -1949,18 +3272,18 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128\K{.}\LOAD{N}\K{\_zero}~\memarg) &\stepto& S; F; (\V128.\CONST~c) + S; F; (\I32.\CONST~i)~(\V128\K{.}\LOAD{N}\K{\_zero}~x~\memarg) &\stepto& S; F; (\V128.\CONST~c) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8] \\ - \wedge & c = \extendu_{N,128}(n)) + \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_{\iN}(n) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8]) \\ + \wedge & c = \extendu_{N,128}(n) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\LOAD{N}\K{\_zero}~\memarg) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(\V128.\LOAD{N}\K{\_zero}~x~\memarg) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -1969,14 +3292,14 @@ Memory Instructions .. _exec-load-lane: -:math:`\V128\K{.}\LOAD{N}\K{\_lane}~\memarg~x` -..................................................... +:math:`\V128\K{.}\LOAD{N}\K{\_lane}~x~\memarg~y` +................................................ 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -2004,7 +3327,7 @@ Memory Instructions 15. Let :math:`j^\ast` be the result of computing :math:`\lanes_{\IN\K{x}L}(v)`. -16. Let :math:`c` be the result of computing :math:`\lanes^{-1}_{\IN\K{x}L}(j^\ast \with [x] = r)`. +16. Let :math:`c` be the result of computing :math:`\lanes^{-1}_{\IN\K{x}L}(j^\ast \with [y] = r)`. 17. Push the value :math:`\V128.\CONST~c` to the stack. @@ -2012,19 +3335,19 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\CONST~v)~(\V128\K{.}\LOAD{N}\K{\_lane}~\memarg~x) &\stepto& S; F; (\V128.\CONST~c) + S; F; (\I32.\CONST~i)~(\V128.\CONST~v)~(\V128\K{.}\LOAD{N}\K{\_lane}~x~\memarg~y) &\stepto& S; F; (\V128.\CONST~c) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & \bytes_{\iN}(r) = S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8] \\ + \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & \bytes_{\iN}(r) = S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8]) \\ \wedge & L = 128/N \\ - \wedge & c = \lanes^{-1}_{\IN\K{x}L}(\lanes_{\IN\K{x}L}(v) \with [x] = r)) + \wedge & c = \lanes^{-1}_{\IN\K{x}L}(\lanes_{\IN\K{x}L}(v) \with [y] = r)) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\CONST~v)~(\V128.\LOAD{N}\K{\_lane}~\memarg~x) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(\V128.\CONST~v)~(\V128.\LOAD{N}\K{\_lane}~x~\memarg~y) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -2034,14 +3357,14 @@ Memory Instructions .. _exec-store: .. _exec-storen: -:math:`t\K{.}\STORE~\memarg` and :math:`t\K{.}\STORE{N}~\memarg` -................................................................ +:math:`t\K{.}\STORE~x~\memarg` and :math:`t\K{.}\STORE{N}~x~\memarg` +.................................................................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -2081,27 +3404,27 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE~\memarg) &\stepto& S'; F; \epsilon + S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE~x~\memarg) &\stepto& S'; F; \epsilon \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + |t|/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice |t|/8] = \bytes_t(c)) \\[1ex] + \wedge & \X{ea} + |t|/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice |t|/8] = \bytes_t(c)) \\[1ex] \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE{N}~\memarg) &\stepto& S'; F; \epsilon + S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE{N}~x~\memarg) &\stepto& S'; F; \epsilon \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8] = \bytes_{\iN}(\wrap_{|t|,N}(c))) \\[1ex] + \wedge & \X{ea} + N/8 \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8] = \bytes_{\iN}(\wrap_{|t|,N}(c)) \\[1ex] \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE{N}^?~\memarg) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(t.\CONST~c)~(t.\STORE{N}^?~x~\memarg) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -2110,14 +3433,14 @@ Memory Instructions .. _exec-store-lane: -:math:`\V128\K{.}\STORE{N}\K{\_lane}~\memarg~x` -...................................................... +:math:`\V128\K{.}\STORE{N}\K{\_lane}~x~\memarg~y` +................................................. 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -2141,7 +3464,7 @@ Memory Instructions 13. Let :math:`j^\ast` be the result of computing :math:`\lanes_{\IN\K{x}L}(c)`. -14. Let :math:`b^\ast` be the result of computing :math:`\bytes_{\iN}(j^\ast[x])`. +14. Let :math:`b^\ast` be the result of computing :math:`\bytes_{\iN}(j^\ast[y])`. 15. Replace the bytes :math:`\X{mem}.\MIDATA[\X{ea} \slice N/8]` with :math:`b^\ast`. @@ -2149,18 +3472,18 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\CONST~c)~(\V128.\STORE{N}\K{\_lane}~\memarg~x) &\stepto& S'; F; \epsilon + S; F; (\I32.\CONST~i)~(\V128.\CONST~c)~(\V128.\STORE{N}\K{\_lane}~x~\memarg~y) &\stepto& S'; F; \epsilon \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & \X{ea} = i + \memarg.\OFFSET \\ - \wedge & \X{ea} + N \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ + \wedge & \X{ea} + N \leq |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ \wedge & L = 128/N \\ - \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA[\X{ea} \slice N/8] = \bytes_{\iN}(\lanes_{\IN\K{x}L}(c)[x])) + \wedge & S' = S \with \SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA[\X{ea} \slice N/8] = \bytes_{\iN}(\lanes_{\IN\K{x}L}(c)[y])) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~i)~(\V128.\CONST~c)~(\V128.\STORE{N}\K{\_lane}~\memarg~x) &\stepto& S; F; \TRAP + S; F; (\I32.\CONST~i)~(\V128.\CONST~c)~(\V128.\STORE{N}\K{\_lane}~x~\memarg~y) &\stepto& S; F; \TRAP \end{array} \\ \qquad (\otherwise) \\ @@ -2169,14 +3492,14 @@ Memory Instructions .. _exec-memory.size: -:math:`\MEMORYSIZE` -................... +:math:`\MEMORYSIZE~x` +..................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -2189,23 +3512,23 @@ Memory Instructions .. math:: \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; \MEMORYSIZE &\stepto& S; F; (\I32.\CONST~\X{sz}) + S; F; (\MEMORYSIZE~x) &\stepto& S; F; (\I32.\CONST~\X{sz}) \end{array} \\ \qquad - (\iff |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| = \X{sz}\cdot64\,\F{Ki}) \\ + (\iff |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| = \X{sz}\cdot64\,\F{Ki}) \\ \end{array} .. _exec-memory.grow: -:math:`\MEMORYGROW` -................... +:math:`\MEMORYGROW~x` +..................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`a` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[a]` exists. @@ -2237,17 +3560,17 @@ Memory Instructions ~\\[-1ex] \begin{array}{l} \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~n)~\MEMORYGROW &\stepto& S'; F; (\I32.\CONST~\X{sz}) + S; F; (\I32.\CONST~n)~(\MEMORYGROW~x) &\stepto& S'; F; (\I32.\CONST~\X{sz}) \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & F.\AMODULE.\MIMEMS[0] = a \\ + (\iff & F.\AMODULE.\MIMEMS[x] = a \\ \wedge & \X{sz} = |S.\SMEMS[a].\MIDATA|/64\,\F{Ki} \\ \wedge & S' = S \with \SMEMS[a] = \growmem(S.\SMEMS[a], n)) \\[1ex] \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} - S; F; (\I32.\CONST~n)~\MEMORYGROW &\stepto& S; F; (\I32.\CONST~\signed_{32}^{-1}(-1)) + S; F; (\I32.\CONST~n)~(\MEMORYGROW~x) &\stepto& S; F; (\I32.\CONST~\signed_{32}^{-1}(-1)) \end{array} \end{array} @@ -2262,14 +3585,14 @@ Memory Instructions .. _exec-memory.fill: -:math:`\MEMORYFILL` -................... +:math:`\MEMORYFILL~x` +..................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`\X{ma}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`\X{ma}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[\X{ma}]` exists. @@ -2309,27 +3632,27 @@ Memory Instructions 20. Push the value :math:`\I32.\CONST~(n-1)` to the stack. -21. Execute the instruction :math:`\MEMORYFILL`. +21. Execute the instruction :math:`\MEMORYFILL~x`. .. math:: ~\\[-1ex] \begin{array}{l} - S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~n)~\MEMORYFILL + S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~n)~\MEMORYFILL~x \quad\stepto\quad S; F; \TRAP \\ \qquad - (\iff d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA|) + (\iff d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA|) \\[1ex] - S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~0)~\MEMORYFILL + S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~0)~\MEMORYFILL~x \quad\stepto\quad S; F; \epsilon \\ \qquad (\otherwise) \\[1ex] - S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~n+1)~\MEMORYFILL + S; F; (\I32.\CONST~d)~\val~(\I32.\CONST~n+1)~\MEMORYFILL~x \quad\stepto \\ \qquad S; F; \begin{array}[t]{@{}l@{}} - (\I32.\CONST~d)~\val~(\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32.\CONST~d+1)~\val~(\I32.\CONST~n)~\MEMORYFILL \\ + (\I32.\CONST~d)~\val~(\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32.\CONST~d+1)~\val~(\I32.\CONST~n)~\MEMORYFILL~x \\ \end{array} \\ \qquad (\otherwise) \\ @@ -2338,48 +3661,56 @@ Memory Instructions .. _exec-memory.copy: -:math:`\MEMORYCOPY` -................... +:math:`\MEMORYCOPY~x~y` +....................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`\X{ma}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[y]` exists. -4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[\X{ma}]` exists. +4. Let :math:`\X{da}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. -5. Let :math:`\X{mem}` be the :ref:`memory instance ` :math:`S.\SMEMS[\X{ma}]`. +5. Let :math:`\X{sa}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[y]`. -6. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. +6. Assert: due to :ref:`validation `, :math:`S.\SMEMS[\X{da}]` exists. -7. Pop the value :math:`\I32.\CONST~n` from the stack. +7. Assert: due to :ref:`validation `, :math:`S.\SMEMS[\X{sa}]` exists. -8. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. +8. Let :math:`\X{mem}_d` be the :ref:`memory instance ` :math:`S.\SMEMS[\X{da}]`. -9. Pop the value :math:`\I32.\CONST~s` from the stack. +9. Let :math:`\X{mem}_s` be the :ref:`memory instance ` :math:`S.\SMEMS[\X{sa}]`. 10. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. -11. Pop the value :math:`\I32.\CONST~d` from the stack. +11. Pop the value :math:`\I32.\CONST~n` from the stack. + +12. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. + +13. Pop the value :math:`\I32.\CONST~s` from the stack. + +14. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. + +15. Pop the value :math:`\I32.\CONST~d` from the stack. -12. If :math:`s + n` is larger than the length of :math:`\X{mem}.\MIDATA` or :math:`d + n` is larger than the length of :math:`\X{mem}.\MIDATA`, then: +16. If :math:`s + n` is larger than the length of :math:`\X{mem}_s.\MIDATA` or :math:`d + n` is larger than the length of :math:`\X{mem}_d.\MIDATA`, then: a. Trap. -13. If :math:`n = 0`, then: +17. If :math:`n = 0`, then: a. Return. -14. If :math:`d \leq s`, then: +18. If :math:`d \leq s`, then: a. Push the value :math:`\I32.\CONST~d` to the stack. b. Push the value :math:`\I32.\CONST~s` to the stack. - c. Execute the instruction :math:`\I32\K{.}\LOAD\K{8\_u}~\{ \OFFSET~0, \ALIGN~0 \}`. + c. Execute the instruction :math:`\I32\K{.}\LOAD\K{8\_u}~y~\{ \OFFSET~0, \ALIGN~0 \}`. - d. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}`. + d. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}`. e. Assert: due to the earlier check against the memory size, :math:`d+1 < 2^{32}`. @@ -2389,7 +3720,7 @@ Memory Instructions h. Push the value :math:`\I32.\CONST~(s+1)` to the stack. -15. Else: +19. Else: a. Assert: due to the earlier check against the memory size, :math:`d+n-1 < 2^{32}`. @@ -2399,54 +3730,54 @@ Memory Instructions d. Push the value :math:`\I32.\CONST~(s+n-1)` to the stack. - e. Execute the instruction :math:`\I32\K{.}\LOAD\K{8\_u}~\{ \OFFSET~0, \ALIGN~0 \}`. + e. Execute the instruction :math:`\I32\K{.}\LOAD\K{8\_u}~y~\{ \OFFSET~0, \ALIGN~0 \}`. - f. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}`. + f. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}`. g. Push the value :math:`\I32.\CONST~d` to the stack. h. Push the value :math:`\I32.\CONST~s` to the stack. -16. Push the value :math:`\I32.\CONST~(n-1)` to the stack. +20. Push the value :math:`\I32.\CONST~(n-1)` to the stack. -17. Execute the instruction :math:`\MEMORYCOPY`. +21. Execute the instruction :math:`\MEMORYCOPY~x~y`. .. math:: ~\\[-1ex] \begin{array}{l} - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~\MEMORYCOPY + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~\MEMORYCOPY~x~y \quad\stepto\quad S; F; \TRAP \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & s + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA| \\ - \vee & d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA|) \\[1ex] + (\iff & d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\MIDATA| \\ + \vee & s + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[y]].\MIDATA|) \\[1ex] \end{array} \\[1ex] - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~\MEMORYCOPY + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~\MEMORYCOPY~x~y \quad\stepto\quad S; F; \epsilon \\ \qquad (\otherwise) \\[1ex] - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~\MEMORYCOPY + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~\MEMORYCOPY~x~y \quad\stepto \\ \qquad S; F; \begin{array}[t]{@{}l@{}} (\I32.\CONST~d) \\ - (\I32.\CONST~s)~(\I32\K{.}\LOAD\K{8\_u}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32.\CONST~d+1)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~\MEMORYCOPY \\ + (\I32.\CONST~s)~(\I32\K{.}\LOAD\K{8\_u}~y~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32.\CONST~d+1)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~\MEMORYCOPY~x~y \\ \end{array} \\ \qquad (\otherwise, \iff d \leq s) \\[1ex] - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~\MEMORYCOPY + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~\MEMORYCOPY~x~y \quad\stepto \\ \qquad S; F; \begin{array}[t]{@{}l@{}} (\I32.\CONST~d+n) \\ - (\I32.\CONST~s+n)~(\I32\K{.}\LOAD\K{8\_u}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~\MEMORYCOPY \\ + (\I32.\CONST~s+n)~(\I32\K{.}\LOAD\K{8\_u}~y~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~\MEMORYCOPY~x~y \\ \end{array} \\ \qquad (\otherwise, \iff d > s) \\ @@ -2455,22 +3786,22 @@ Memory Instructions .. _exec-memory.init: -:math:`\MEMORYINIT~x` -..................... +:math:`\MEMORYINIT~x~y` +....................... 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[0]` exists. +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIMEMS[x]` exists. -3. Let :math:`\X{ma}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[0]`. +3. Let :math:`\X{ma}` be the :ref:`memory address ` :math:`F.\AMODULE.\MIMEMS[x]`. 4. Assert: due to :ref:`validation `, :math:`S.\SMEMS[\X{ma}]` exists. 5. Let :math:`\X{mem}` be the :ref:`memory instance ` :math:`S.\SMEMS[\X{ma}]`. -6. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIDATAS[x]` exists. +6. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIDATAS[y]` exists. -7. Let :math:`\X{da}` be the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[x]`. +7. Let :math:`\X{da}` be the :ref:`data address ` :math:`F.\AMODULE.\MIDATAS[y]`. 8. Assert: due to :ref:`validation `, :math:`S.\SDATAS[\X{da}]` exists. @@ -2514,33 +3845,33 @@ Memory Instructions 26. Push the value :math:`\I32.\CONST~(n-1)` to the stack. -27. Execute the instruction :math:`\MEMORYINIT~x`. +27. Execute the instruction :math:`\MEMORYINIT~x~y`. .. math:: ~\\[-1ex] \begin{array}{l} - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\MEMORYINIT~x) + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n)~(\MEMORYINIT~x~y) \quad\stepto\quad S; F; \TRAP \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & s + n > |S.\SDATAS[F.\AMODULE.\MIDATAS[x]].\DIDATA| \\ - \vee & d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[0]].\MIDATA|) \\[1ex] + (\iff & d + n > |S.\SMEMS[F.\AMODULE.\MIMEMS[x]].\DIDATA| \\ + \vee & s + n > |S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\MIDATA|) \\[1ex] \end{array} \\[1ex] - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~(\MEMORYINIT~x) + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~0)~(\MEMORYINIT~x~y) \quad\stepto\quad S; F; \epsilon \\ \qquad (\otherwise) \\[1ex] - S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\MEMORYINIT~x) + S; F; (\I32.\CONST~d)~(\I32.\CONST~s)~(\I32.\CONST~n+1)~(\MEMORYINIT~x~y) \quad\stepto \\ \qquad S; F; \begin{array}[t]{@{}l@{}} - (\I32.\CONST~d)~(\I32.\CONST~b)~(\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}) \\ - (\I32.\CONST~d+1)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~(\MEMORYINIT~x) \\ + (\I32.\CONST~d)~(\I32.\CONST~b)~(\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}) \\ + (\I32.\CONST~d+1)~(\I32.\CONST~s+1)~(\I32.\CONST~n)~(\MEMORYINIT~x~y) \\ \end{array} \\ \qquad - (\otherwise, \iff b = S.\SDATAS[F.\AMODULE.\MIDATAS[x]].\DIDATA[s]) \\ + (\otherwise, \iff b = S.\SDATAS[F.\AMODULE.\MIDATAS[y]].\DIDATA[s]) \\ \end{array} @@ -2612,9 +3943,9 @@ Control Instructions 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`\expand_F(\blocktype)` is defined. +2. Assert: due to :ref:`validation `, :math:`\fblocktype_{S;F}(\blocktype)` is defined. -3. Let :math:`[t_1^m] \to [t_2^n]` be the :ref:`function type ` :math:`\expand_F(\blocktype)`. +3. Let :math:`[t_1^m] \to [t_2^n]` be the :ref:`instruction type ` :math:`\fblocktype_{S;F}(\blocktype)`. 4. Let :math:`L` be the label whose arity is :math:`n` and whose continuation is the end of the block. @@ -2627,9 +3958,9 @@ Control Instructions .. math:: ~\\[-1ex] \begin{array}{lcl} - F; \val^m~\BLOCK~\X{bt}~\instr^\ast~\END &\stepto& - F; \LABEL_n\{\epsilon\}~\val^m~\instr^\ast~\END - \\&&\quad (\iff \expand_F(\X{bt}) = [t_1^m] \to [t_2^n]) + S; F; \val^m~\BLOCK~\X{bt}~\instr^\ast~\END &\stepto& + S; F; \LABEL_n\{\epsilon\}~\val^m~\instr^\ast~\END + \\&&\quad (\iff \fblocktype_{S;F}(\X{bt}) = [t_1^m] \to [t_2^n]) \end{array} @@ -2640,9 +3971,9 @@ Control Instructions 1. Let :math:`F` be the :ref:`current ` :ref:`frame `. -2. Assert: due to :ref:`validation `, :math:`\expand_F(\blocktype)` is defined. +2. Assert: due to :ref:`validation `, :math:`\fblocktype_{S;F}(\blocktype)` is defined. -3. Let :math:`[t_1^m] \to [t_2^n]` be the :ref:`function type ` :math:`\expand_F(\blocktype)`. +3. Let :math:`[t_1^m] \to [t_2^n]` be the :ref:`instruction type ` :math:`\fblocktype_{S;F}(\blocktype)`. 4. Let :math:`L` be the label whose arity is :math:`m` and whose continuation is the start of the loop. @@ -2655,9 +3986,9 @@ Control Instructions .. math:: ~\\[-1ex] \begin{array}{lcl} - F; \val^m~\LOOP~\X{bt}~\instr^\ast~\END &\stepto& - F; \LABEL_m\{\LOOP~\X{bt}~\instr^\ast~\END\}~\val^m~\instr^\ast~\END - \\&&\quad (\iff \expand_F(\X{bt}) = [t_1^m] \to [t_2^n]) + S; F; \val^m~\LOOP~\X{bt}~\instr^\ast~\END &\stepto& + S; F; \LABEL_m\{\LOOP~\X{bt}~\instr^\ast~\END\}~\val^m~\instr^\ast~\END + \\&&\quad (\iff \fblocktype_{S;F}(\X{bt}) = [t_1^m] \to [t_2^n]) \end{array} @@ -2782,6 +4113,130 @@ Control Instructions \end{array} +.. _exec-br_on_null: + +:math:`\BRONNULL~l` +................... + +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: + + a. :ref:`Execute ` the instruction :math:`(\BR~l)`. + +4. Else: + + a. Push the value :math:`\reff` back to the stack. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \reff~(\BRONNULL~l) &\stepto& (\BR~l) + & (\iff \reff = \REFNULL~\X{ht}) \\ + \reff~(\BRONNULL~l) &\stepto& \reff + & (\otherwise) \\ + \end{array} + + +.. _exec-br_on_non_null: + +:math:`\BRONNONNULL~l` +...................... + +1. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +2. Pop the value :math:`\reff` from the stack. + +3. If :math:`\reff` is :math:`\REFNULL~\X{ht}`, then: + + a. Do nothing. + +4. Else: + + a. Push the value :math:`\reff` back to the stack. + + b. :ref:`Execute ` the instruction :math:`(\BR~l)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \reff~(\BRONNONNULL~l) &\stepto& \epsilon + & (\iff \reff = \REFNULL~\X{ht}) \\ + \reff~(\BRONNONNULL~l) &\stepto& \reff~(\BR~l) + & (\otherwise) \\ + \end{array} + + +.. _exec-br_on_cast: + +:math:`\BRONCAST~l~\X{rt}_1~\X{rt}_2` +..................................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Let :math:`\X{rt}'_2` be the :ref:`reference type ` :math:`\insttype_{F.\AMODULE}(\X{rt}_2)`. + +3. Assert: due to :ref:`validation `, :math:`\X{rt}'_2` is :ref:`closed `. + +4. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +5. Pop the value :math:`\reff` from the stack. + +6. Assert: due to validation, the :ref:`reference value ` is :ref:`valid ` with some :ref:`reference type `. + +7. Let :math:`\X{rt}` be the :ref:`reference type ` of :math:`\reff`. + +8. Push the value :math:`\reff` back to the stack. + +9. If the :ref:`reference type ` :math:`\X{rt}` :ref:`matches ` :math:`\X{rt}'_2`, then: + + a. :ref:`Execute ` the instruction :math:`(\BR~l)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \reff~(\BRONCAST~l~\X{rt}_1~\X{rt}_2) &\stepto& \reff~(\BR~l) + & (\iff S \vdashval \reff : \X{rt} + \land {} \vdashreftypematch \X{rt} \matchesreftype \insttype_{F.\AMODULE}(\X{rt}_2)) \\ + S; F; \reff~(\BRONCAST~l~\X{rt}_1~\X{rt}_2) &\stepto& \reff + & (\otherwise) \\ + \end{array} + + +.. _exec-br_on_cast_fail: + +:math:`\BRONCASTFAIL~l~\X{rt}_1~\X{rt}_2` +......................................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Let :math:`\X{rt}'_2` be the :ref:`reference type ` :math:`\insttype_{F.\AMODULE}(\X{rt}_2)`. + +3. Assert: due to :ref:`validation `, :math:`\X{rt}'_2` is :ref:`closed `. + +4. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +5. Pop the value :math:`\reff` from the stack. + +6. Assert: due to validation, the :ref:`reference value ` is :ref:`valid ` with some :ref:`reference type `. + +7. Let :math:`\X{rt}` be the :ref:`reference type ` of :math:`\reff`. + +8. Push the value :math:`\reff` back to the stack. + +9. If the :ref:`reference type ` :math:`\X{rt}` does not :ref:`match ` :math:`\X{rt}'_2`, then: + + a. :ref:`Execute ` the instruction :math:`(\BR~l)`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + S; F; \reff~(\BRONCASTFAIL~l~\X{rt}_1~\X{rt}_2) &\stepto& \reff + & (\iff S \vdashval \reff : \X{rt} + \land {} \vdashreftypematch \X{rt} \matchesreftype \insttype_{F.\AMODULE}(\X{rt}_2)) \\ + S; F; \reff~(\BRONCASTFAIL~l~\X{rt}_1~\X{rt}_2) &\stepto& \reff~(\BR~l) + & (\otherwise) \\ + \end{array} + + .. _exec-return: :math:`\RETURN` @@ -2812,7 +4267,7 @@ Control Instructions .. math:: ~\\[-1ex] \begin{array}{lcl@{\qquad}l} - \FRAME_n\{F\}~\XB^k[\val^n~\RETURN]~\END &\stepto& \val^n + \FRAME_n\{F\}~B^\ast[\val^n~\RETURN]~\END &\stepto& \val^n \end{array} @@ -2836,6 +4291,32 @@ Control Instructions \end{array} +.. _exec-call_ref: + +:math:`\CALLREF~x` +.................. + +1. Assert: due to :ref:`validation `, a null or :ref:`function reference ` is on the top of the stack. + +2. Pop the reference value :math:`r` from the stack. + +3. If :math:`r` is :math:`\REFNULL~\X{ht}`, then: + + a. Trap. + +4. Assert: due to :ref:`validation `, :math:`r` is a :ref:`function reference `. + +5. Let :math:`\REFFUNCADDR~a` be the reference :math:`r`. + +6. :ref:`Invoke ` the function instance at address :math:`a`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + F; (\REFFUNCADDR~a)~(\CALLREF~x) &\stepto& F; (\INVOKE~a) \\ + F; (\REFNULL~\X{ht})~(\CALLREF~x) &\stepto& F; \TRAP \\ + \end{array} + + .. _exec-call_indirect: :math:`\CALLINDIRECT~x~y` @@ -2851,9 +4332,9 @@ Control Instructions 5. Let :math:`\X{tab}` be the :ref:`table instance ` :math:`S.\STABLES[\X{ta}]`. -6. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITYPES[y]` exists. +6. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITYPES[y]` is defined. -7. Let :math:`\X{ft}_{\F{expect}}` be the :ref:`function type ` :math:`F.\AMODULE.\MITYPES[y]`. +7. Let :math:`\X{dt}_{\F{expect}}` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[y]`. 8. Assert: due to :ref:`validation `, a value with :ref:`value type ` |I32| is on the top of the stack. @@ -2865,7 +4346,7 @@ Control Instructions 11. Let :math:`r` be the :ref:`reference ` :math:`\X{tab}.\TIELEM[i]`. -12. If :math:`r` is :math:`\REFNULL~t`, then: +12. If :math:`r` is :math:`\REFNULL~\X{ht}`, then: a. Trap. @@ -2877,9 +4358,9 @@ Control Instructions 16. Let :math:`\X{f}` be the :ref:`function instance ` :math:`S.\SFUNCS[a]`. -17. Let :math:`\X{ft}_{\F{actual}}` be the :ref:`function type ` :math:`\X{f}.\FITYPE`. +17. Let :math:`\X{dt}_{\F{actual}}` be the :ref:`defined type ` :math:`\X{f}.\FITYPE`. -18. If :math:`\X{ft}_{\F{actual}}` and :math:`\X{ft}_{\F{expect}}` differ, then: +18. If :math:`\X{dt}_{\F{actual}}` does not :ref:`match ` :math:`\X{dt}_{\F{expect}}`, then: a. Trap. @@ -2895,7 +4376,7 @@ Control Instructions \begin{array}[t]{@{}r@{~}l@{}} (\iff & S.\STABLES[F.\AMODULE.\MITABLES[x]].\TIELEM[i] = \REFFUNCADDR~a \\ \wedge & S.\SFUNCS[a] = f \\ - \wedge & F.\AMODULE.\MITYPES[y] = f.\FITYPE) + \wedge & S \vdashdeftypematch F.\AMODULE.\MITYPES[y] \matchesdeftype f.\FITYPE) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} @@ -2906,6 +4387,111 @@ Control Instructions \end{array} +.. _exec-return_call: + +:math:`\RETURNCALL~x` +..................... + +.. todo: find a way to reuse call/call_indirect prose for tail call versions + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MIFUNCS[x]` exists. + +3. Let :math:`a` be the :ref:`function address ` :math:`F.\AMODULE.\MIFUNCS[x]`. + +4. :ref:`Tail-invoke ` the function instance at address :math:`a`. + + +.. math:: + \begin{array}{lcl@{\qquad}l} + (\RETURNCALL~x) &\stepto& (\RETURNINVOKE~a) + & (\iff (\CALL~x) \stepto (\INVOKE~a)) + \end{array} + + +.. _exec-return_call_ref: + +:math:`\RETURNCALLREF~x` +........................ + +1. Assert: due to :ref:`validation `, a :ref:`function reference ` is on the top of the stack. + +2. Pop the reference value :math:`r` from the stack. + +3. If :math:`r` is :math:`\REFNULL~\X{ht}`, then: + + a. Trap. + +4. Assert: due to :ref:`validation `, :math:`r` is a :ref:`function reference `. + +5. Let :math:`\REFFUNCADDR~a` be the reference :math:`r`. + +6. :ref:`Tail-invoke ` the function instance at address :math:`a`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \val~(\RETURNCALLREF~x) &\stepto& (\RETURNINVOKE~a) + & (\iff \val~(\CALLREF~x) \stepto (\INVOKE~a)) \\ + \val~(\RETURNCALLREF~x) &\stepto& \TRAP + & (\iff \val~(\CALLREF~x) \stepto \TRAP) \\ + \end{array} + + +.. _exec-return_call_indirect: + +:math:`\RETURNCALLINDIRECT~x~y` +............................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITABLES[x]` exists. + +3. Let :math:`\X{ta}` be the :ref:`table address ` :math:`F.\AMODULE.\MITABLES[x]`. + +4. Assert: due to :ref:`validation `, :math:`S.\STABLES[\X{ta}]` exists. + +5. Let :math:`\X{tab}` be the :ref:`table instance ` :math:`S.\STABLES[\X{ta}]`. + +6. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITYPES[y]` exists. + +7. Let :math:`\X{dt}_{\F{expect}}` be the :ref:`defined type ` :math:`F.\AMODULE.\MITYPES[y]`. + +8. Assert: due to :ref:`validation `, a value with :ref:`value type ` |I32| is on the top of the stack. + +9. Pop the value :math:`\I32.\CONST~i` from the stack. + +10. If :math:`i` is not smaller than the length of :math:`\X{tab}.\TIELEM`, then: + + a. Trap. + +11. If :math:`\X{tab}.\TIELEM[i]` is uninitialized, then: + + a. Trap. + +12. Let :math:`a` be the :ref:`function address ` :math:`\X{tab}.\TIELEM[i]`. + +13. Assert: due to :ref:`validation `, :math:`S.\SFUNCS[a]` exists. + +14. Let :math:`\X{f}` be the :ref:`function instance ` :math:`S.\SFUNCS[a]`. + +15. Let :math:`\X{dt}_{\F{actual}}` be the :ref:`defined type ` :math:`\X{f}.\FITYPE`. + +16. If :math:`\X{dt}_{\F{actual}}` does not :ref:`match ` :math:`\X{dt}_{\F{expect}}`, then: + + a. Trap. + +17. :ref:`Tail-invoke ` the function instance at address :math:`a`. + +.. math:: + \begin{array}{lcl@{\qquad}l} + \val~(\RETURNCALLINDIRECT~x~y) &\stepto& (\RETURNINVOKE~a) + & (\iff \val~(\CALLINDIRECT~x~y) \stepto (\INVOKE~a)) \\ + \val~(\RETURNCALLINDIRECT~x~y) &\stepto& \TRAP + & (\iff \val~(\CALLINDIRECT~x~y) \stepto \TRAP) \\ + \end{array} + + .. index:: instruction, instruction sequence, block .. _exec-instr-seq: @@ -2977,9 +4563,9 @@ Invocation of :ref:`function address ` :math:`a` 2. Let :math:`f` be the :ref:`function instance `, :math:`S.\SFUNCS[a]`. -3. Let :math:`[t_1^n] \to [t_2^m]` be the :ref:`function type ` :math:`f.\FITYPE`. +3. Let :math:`\TFUNC~[t_1^n] \toF [t_2^m]` be the :ref:`composite type ` :math:`\expanddt(\X{f}.\FITYPE)`. -4. Let :math:`t^\ast` be the list of :ref:`value types ` :math:`f.\FICODE.\FLOCALS`. +4. Let :math:`\local^\ast` be the list of :ref:`locals ` :math:`f.\FICODE.\FLOCALS`. 5. Let :math:`\instr^\ast~\END` be the :ref:`expression ` :math:`f.\FICODE.\FBODY`. @@ -3004,12 +4590,51 @@ Invocation of :ref:`function address ` :math:`a` \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} (\iff & S.\SFUNCS[a] = f \\ - \wedge & f.\FITYPE = [t_1^n] \to [t_2^m] \\ - \wedge & f.\FICODE = \{ \FTYPE~x, \FLOCALS~t^k, \FBODY~\instr^\ast~\END \} \\ + \wedge & \expanddt(f.\FITYPE) = \TFUNC~[t_1^n] \toF [t_2^m] \\ + \wedge & f.\FICODE = \{ \FTYPE~x, \FLOCALS~\{\LTYPE~t\}^k, \FBODY~\instr^\ast~\END \} \\ \wedge & F = \{ \AMODULE~f.\FIMODULE, ~\ALOCALS~\val^n~(\default_t)^k \}) \end{array} \\ \end{array} +.. note:: + For non-defaultable types, the respective local is left uninitialized by these rules. + + +.. _exec-return-invoke: + +Tail-invocation of :ref:`function address ` :math:`a` +...................................................................... + +1. Assert: due to :ref:`validation `, :math:`S.\SFUNCS[a]` exists. + +2. Let :math:`\TFUNC~[t_1^n] \toF [t_2^m]` be the :ref:`composite type ` :math:`\expanddt(S.\SFUNCS[a].\FITYPE)`. + +3. Assert: due to :ref:`validation `, there are at least :math:`n` values on the top of the stack. + +4. Pop the results :math:`\val^n` from the stack. + +5. Assert: due to :ref:`validation `, the stack contains at least one :ref:`frame `. + +6. While the top of the stack is not a frame, do: + + a. Pop the top element from the stack. + +7. Assert: the top of the stack is a frame. + +8. Pop the frame from the stack. + +9. Push :math:`\val^n` to the stack. + +10. :ref:`Invoke ` the function instance at address :math:`a`. + +.. math:: + ~\\[-1ex] + \begin{array}{lcl@{\qquad}l} + S; \FRAME_m\{F\}~B^\ast[\val^n~(\RETURNINVOKE~a)]~\END &\stepto& + \val^n~(\INVOKE~a) + & (\iff \expanddt(S.\SFUNCS[a].\FITYPE) = \TFUNC~[t_1^n] \toF [t_2^m]) + \end{array} + .. _exec-invoke-exit: @@ -3064,7 +4689,8 @@ Furthermore, the resulting store must be :ref:`valid `, i.e., all d \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & S.\SFUNCS[a] = \{ \FITYPE~[t_1^n] \to [t_2^m], \FIHOSTCODE~\X{hf} \} \\ + (\iff & S.\SFUNCS[a] = \{ \FITYPE~\deftype, \FIHOSTCODE~\X{hf} \} \\ + \wedge & \expanddt(\deftype) = \TFUNC~[t_1^n] \toF [t_2^m] \\ \wedge & (S'; \result) \in \X{hf}(S; \val^n)) \\ \end{array} \\ \begin{array}{lcl@{\qquad}l} @@ -3072,7 +4698,8 @@ Furthermore, the resulting store must be :ref:`valid `, i.e., all d \end{array} \\ \qquad \begin{array}[t]{@{}r@{~}l@{}} - (\iff & S.\SFUNCS[a] = \{ \FITYPE~[t_1^n] \to [t_2^m], \FIHOSTCODE~\X{hf} \} \\ + (\iff & S.\SFUNCS[a] = \{ \FITYPE~\deftype, \FIHOSTCODE~\X{hf} \} \\ + \wedge & \expanddt(\deftype) = \TFUNC~[t_1^n] \toF [t_2^m] \\ \wedge & \bot \in \X{hf}(S; \val^n)) \\ \end{array} \\ \end{array} diff --git a/document/core/exec/modules.rst b/document/core/exec/modules.rst index cdb20d03c4..9e73c97fe9 100644 --- a/document/core/exec/modules.rst +++ b/document/core/exec/modules.rst @@ -3,151 +3,6 @@ Modules For modules, the execution semantics primarily defines :ref:`instantiation `, which :ref:`allocates ` instances for a module and its contained definitions, initializes :ref:`tables ` and :ref:`memories ` from contained :ref:`element ` and :ref:`data ` segments, and invokes the :ref:`start function ` if present. It also includes :ref:`invocation ` of exported functions. -Instantiation depends on a number of auxiliary notions for :ref:`type-checking imports ` and :ref:`allocating ` instances. - - -.. index:: external value, external type, validation, import, store -.. _valid-externval: - -External Typing -~~~~~~~~~~~~~~~ - -For the purpose of checking :ref:`external values ` against :ref:`imports `, -such values are classified by :ref:`external types `. -The following auxiliary typing rules specify this typing relation relative to a :ref:`store ` :math:`S` in which the referenced instances live. - - -.. index:: function type, function address -.. _valid-externval-func: - -:math:`\EVFUNC~a` -................. - -* The store entry :math:`S.\SFUNCS[a]` must exist. - -* Then :math:`\EVFUNC~a` is valid with :ref:`external type ` :math:`\ETFUNC~S.\SFUNCS[a].\FITYPE`. - -.. math:: - \frac{ - }{ - S \vdashexternval \EVFUNC~a : \ETFUNC~S.\SFUNCS[a].\FITYPE - } - - -.. index:: table type, table address -.. _valid-externval-table: - -:math:`\EVTABLE~a` -.................. - -* The store entry :math:`S.\STABLES[a]` must exist. - -* Then :math:`\EVTABLE~a` is valid with :ref:`external type ` :math:`\ETTABLE~S.\STABLES[a].\TITYPE`. - -.. math:: - \frac{ - }{ - S \vdashexternval \EVTABLE~a : \ETTABLE~S.\STABLES[a].\TITYPE - } - - -.. index:: memory type, memory address -.. _valid-externval-mem: - -:math:`\EVMEM~a` -................ - -* The store entry :math:`S.\SMEMS[a]` must exist. - -* Then :math:`\EVMEM~a` is valid with :ref:`external type ` :math:`\ETMEM~S.\SMEMS[a].\MITYPE`. - -.. math:: - \frac{ - }{ - S \vdashexternval \EVMEM~a : \ETMEM~S.\SMEMS[a].\MITYPE - } - - -.. index:: global type, global address, value type, mutability -.. _valid-externval-global: - -:math:`\EVGLOBAL~a` -................... - -* The store entry :math:`S.\SGLOBALS[a]` must exist. - -* Then :math:`\EVGLOBAL~a` is valid with :ref:`external type ` :math:`\ETGLOBAL~S.\SGLOBALS[a].\GITYPE`. - -.. math:: - \frac{ - }{ - S \vdashexternval \EVGLOBAL~a : \ETGLOBAL~S.\SGLOBALS[a].\GITYPE - } - - -.. index:: value, value type, validation -.. _valid-val: - -Value Typing -~~~~~~~~~~~~ - -For the purpose of checking argument :ref:`values ` against the parameter types of exported :ref:`functions `, -values are classified by :ref:`value types `. -The following auxiliary typing rules specify this typing relation relative to a :ref:`store ` :math:`S` in which possibly referenced addresses live. - -.. _valid-num: - -:ref:`Numeric Values ` :math:`t.\CONST~c` -..................................................... - -* The value is valid with :ref:`number type ` :math:`t`. - -.. math:: - \frac{ - }{ - S \vdashval t.\CONST~c : t - } - -.. _valid-ref: - -:ref:`Null References ` :math:`\REFNULL~t` -...................................................... - -* The value is valid with :ref:`reference type ` :math:`t`. - -.. math:: - \frac{ - }{ - S \vdashval \REFNULL~t : t - } - - -:ref:`Function References ` :math:`\REFFUNCADDR~a` -.............................................................. - -* The :ref:`external value ` :math:`\EVFUNC~a` must be :ref:`valid `. - -* Then the value is valid with :ref:`reference type ` :math:`\FUNCREF`. - -.. math:: - \frac{ - S \vdashexternval \EVFUNC~a : \ETFUNC~\functype - }{ - S \vdashval \REFFUNCADDR~a : \FUNCREF - } - - -:ref:`External References ` :math:`\REFEXTERNADDR~a` -....................................................................... - -* The value is valid with :ref:`reference type ` :math:`\EXTERNREF`. - -.. math:: - \frac{ - }{ - S \vdashval \REFEXTERNADDR~a : \EXTERNREF - } - .. index:: ! allocation, store, address .. _alloc: @@ -166,24 +21,24 @@ New instances of :ref:`functions `, :ref:`tables ` to allocate and :math:`\moduleinst` its :ref:`module instance `. -2. Let :math:`a` be the first free :ref:`function address ` in :math:`S`. +2. Let :math:`\deftype` be the :ref:`defined type ` :math:`\moduleinst.\MITYPES[\func.\FTYPE]`. -3. Let :math:`\functype` be the :ref:`function type ` :math:`\moduleinst.\MITYPES[\func.\FTYPE]`. +3. Let :math:`a` be the first free :ref:`function address ` in :math:`S`. -4. Let :math:`\funcinst` be the :ref:`function instance ` :math:`\{ \FITYPE~\functype, \FIMODULE~\moduleinst, \FICODE~\func \}`. +4. Let :math:`\funcinst` be the :ref:`function instance ` :math:`\{ \FITYPE~\deftype, \FIMODULE~\moduleinst, \FICODE~\func \}`. -5. Append :math:`\funcinst` to the |SFUNCS| of :math:`S`. +6. Append :math:`\funcinst` to the |SFUNCS| of :math:`S`. -6. Return :math:`a`. +7. Return :math:`a`. .. math:: ~\\[-1ex] \begin{array}{rlll} \allocfunc(S, \func, \moduleinst) &=& S', \funcaddr \\[1ex] \mbox{where:} \hfill \\ + \deftype &=& \moduleinst.\MITYPES[\func.\FTYPE] \\ \funcaddr &=& |S.\SFUNCS| \\ - \functype &=& \moduleinst.\MITYPES[\func.\FTYPE] \\ - \funcinst &=& \{ \FITYPE~\functype, \FIMODULE~\moduleinst, \FICODE~\func \} \\ + \funcinst &=& \{ \FITYPE~\deftype, \FIMODULE~\moduleinst, \FICODE~\func \} \\ S' &=& S \compose \{\SFUNCS~\funcinst\} \\ \end{array} @@ -194,11 +49,11 @@ New instances of :ref:`functions `, :ref:`tables ` ....................................... -1. Let :math:`\hostfunc` be the :ref:`host function ` to allocate and :math:`\functype` its :ref:`function type `. +1. Let :math:`\hostfunc` be the :ref:`host function ` to allocate and :math:`\deftype` its :ref:`defined type `. 2. Let :math:`a` be the first free :ref:`function address ` in :math:`S`. -3. Let :math:`\funcinst` be the :ref:`function instance ` :math:`\{ \FITYPE~\functype, \FIHOSTCODE~\hostfunc \}`. +3. Let :math:`\funcinst` be the :ref:`function instance ` :math:`\{ \FITYPE~\deftype, \FIHOSTCODE~\hostfunc \}`. 4. Append :math:`\funcinst` to the |SFUNCS| of :math:`S`. @@ -207,10 +62,10 @@ New instances of :ref:`functions `, :ref:`tables `, :ref:`tables ` ................................ -1. Let :math:`\tabletype` be the :ref:`table type ` to allocate and :math:`\reff` the initialization value. +1. Let :math:`\tabletype` be the :ref:`table type ` of the table to allocate and :math:`\reff` the initialization value. 2. Let :math:`(\{\LMIN~n, \LMAX~m^?\}~\reftype)` be the structure of :ref:`table type ` :math:`\tabletype`. 3. Let :math:`a` be the first free :ref:`table address ` in :math:`S`. -4. Let :math:`\tableinst` be the :ref:`table instance ` :math:`\{ \TITYPE~\tabletype, \TIELEM~\reff^n \}` with :math:`n` elements set to :math:`\reff`. +4. Let :math:`\tableinst` be the :ref:`table instance ` :math:`\{ \TITYPE~\tabletype', \TIELEM~\reff^n \}` with :math:`n` elements set to :math:`\reff`. 5. Append :math:`\tableinst` to the |STABLES| of :math:`S`. @@ -254,7 +109,7 @@ New instances of :ref:`functions `, :ref:`tables ` ................................ -1. Let :math:`\memtype` be the :ref:`memory type ` to allocate. +1. Let :math:`\memtype` be the :ref:`memory type ` of the memory to allocate. 2. Let :math:`\{\LMIN~n, \LMAX~m^?\}` be the structure of :ref:`memory type ` :math:`\memtype`. @@ -283,7 +138,7 @@ New instances of :ref:`functions `, :ref:`tables ` .................................. -1. Let :math:`\globaltype` be the :ref:`global type ` to allocate and :math:`\val` the :ref:`value ` to initialize the global with. +1. Let :math:`\globaltype` be the :ref:`global type ` of the global to allocate and :math:`\val` its initialization :ref:`value `. 2. Let :math:`a` be the first free :ref:`global address ` in :math:`S`. @@ -435,59 +290,73 @@ Growing :ref:`memories ` :ref:`Modules ` .................................. +.. todo:: update prose for types + The allocation function for :ref:`modules ` requires a suitable list of :ref:`external values ` that are assumed to :ref:`match ` the :ref:`import ` vector of the module, a list of initialization :ref:`values ` for the module's :ref:`globals `, and list of :ref:`reference ` vectors for the module's :ref:`element segments `. -1. Let :math:`\module` be the :ref:`module ` to allocate and :math:`\externval_{\F{im}}^\ast` the vector of :ref:`external values ` providing the module's imports, :math:`\val^\ast` the initialization :ref:`values ` of the module's :ref:`globals `, and :math:`(\reff^\ast)^\ast` the :ref:`reference ` vectors of the module's :ref:`element segments `. +1. Let :math:`\module` be the :ref:`module ` to allocate and :math:`\externval_{\F{im}}^\ast` the vector of :ref:`external values ` providing the module's imports, :math:`\val_{\F{g}}^\ast` the initialization :ref:`values ` of the module's :ref:`globals `, :math:`\reff_{\F{t}}^\ast` the initializer :ref:`reference ` of the module's :ref:`tables `, and :math:`(\reff_{\F{e}}^\ast)^\ast` the :ref:`reference ` vectors of the module's :ref:`element segments `. + +2. For each :ref:`defined type ` :math:`\deftype'_i` in :math:`\module.\MTYPES`, do: -2. For each :ref:`function ` :math:`\func_i` in :math:`\module.\MFUNCS`, do: + a. Let :math:`\deftype_i` be the :ref:`instantiation ` :math:`\deftype'_i` in :math:`\moduleinst` defined below. + +3. For each :ref:`function ` :math:`\func_i` in :math:`\module.\MFUNCS`, do: a. Let :math:`\funcaddr_i` be the :ref:`function address ` resulting from :ref:`allocating ` :math:`\func_i` for the :ref:`\module instance ` :math:`\moduleinst` defined below. -3. For each :ref:`table ` :math:`\table_i` in :math:`\module.\MTABLES`, do: +4. For each :ref:`table ` :math:`\table_i` in :math:`\module.\MTABLES`, do: + + a. Let :math:`\limits_i~t_i` be the :ref:`table type ` obtained by :ref:`instantiating ` :math:`\table_i.\TTYPE` in :math:`\moduleinst` defined below. + + b. Let :math:`\tableaddr_i` be the :ref:`table address ` resulting from :ref:`allocating ` :math:`\table_i.\TTYPE` with initialization value :math:`\reff_{\F{t}}^\ast[i]`. - a. Let :math:`\limits_i~t_i` be the :ref:`table type ` :math:`\table_i.\TTYPE`. +5. For each :ref:`memory ` :math:`\mem_i` in :math:`\module.\MMEMS`, do: - b. Let :math:`\tableaddr_i` be the :ref:`table address ` resulting from :ref:`allocating ` :math:`\table_i.\TTYPE` with initialization value :math:`\REFNULL~t_i`. + a. Let :math:`\memtype_i` be the :ref:`memory type ` obtained by :ref:`insantiating ` :math:`\mem_i.\MTYPE` in :math:`\moduleinst` defined below. -4. For each :ref:`memory ` :math:`\mem_i` in :math:`\module.\MMEMS`, do: + b. Let :math:`\memaddr_i` be the :ref:`memory address ` resulting from :ref:`allocating ` :math:`\memtype_i`. - a. Let :math:`\memaddr_i` be the :ref:`memory address ` resulting from :ref:`allocating ` :math:`\mem_i.\MTYPE`. +6. For each :ref:`global ` :math:`\global_i` in :math:`\module.\MGLOBALS`, do: -5. For each :ref:`global ` :math:`\global_i` in :math:`\module.\MGLOBALS`, do: + a. Let :math:`\globaltype_i` be the :ref:`global type ` obtained by :ref:`instantiating ` :math:`\global_i.\GTYPE` in :math:`\moduleinst` defined below. - a. Let :math:`\globaladdr_i` be the :ref:`global address ` resulting from :ref:`allocating ` :math:`\global_i.\GTYPE` with initializer value :math:`\val^\ast[i]`. + b. Let :math:`\globaladdr_i` be the :ref:`global address ` resulting from :ref:`allocating ` :math:`\globaltype_i` with initializer value :math:`\val_{\F{g}}^\ast[i]`. -6. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS`, do: +7. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS`, do: - a. Let :math:`\elemaddr_i` be the :ref:`element address ` resulting from :ref:`allocating ` an :ref:`element instance ` of :ref:`reference type ` :math:`\elem_i.\ETYPE` with contents :math:`(\reff^\ast)^\ast[i]`. + a. Let :math:`\reftype_i` be the element :ref:`reference type ` obtained by `instantiating ` :math:`\elem_i.\ETYPE` in :math:`\moduleinst` defined below. -7. For each :ref:`data segment ` :math:`\data_i` in :math:`\module.\MDATAS`, do: + b. Let :math:`\elemaddr_i` be the :ref:`element address ` resulting from :ref:`allocating ` a :ref:`element instance ` of :ref:`reference type ` :math:`\reftype_i` with contents :math:`(\reff_{\F{e}}^\ast)^\ast[i]`. + +8. For each :ref:`data segment ` :math:`\data_i` in :math:`\module.\MDATAS`, do: a. Let :math:`\dataaddr_i` be the :ref:`data address ` resulting from :ref:`allocating ` a :ref:`data instance ` with contents :math:`\data_i.\DINIT`. -8. Let :math:`\funcaddr^\ast` be the concatenation of the :ref:`function addresses ` :math:`\funcaddr_i` in index order. +9. Let :math:`\deftype^\ast` be the concatenation of the :ref:`defined types ` :math:`\deftype_i` in index order. + +10. Let :math:`\funcaddr^\ast` be the concatenation of the :ref:`function addresses ` :math:`\funcaddr_i` in index order. -9. Let :math:`\tableaddr^\ast` be the concatenation of the :ref:`table addresses ` :math:`\tableaddr_i` in index order. +11. Let :math:`\tableaddr^\ast` be the concatenation of the :ref:`table addresses ` :math:`\tableaddr_i` in index order. -10. Let :math:`\memaddr^\ast` be the concatenation of the :ref:`memory addresses ` :math:`\memaddr_i` in index order. +12. Let :math:`\memaddr^\ast` be the concatenation of the :ref:`memory addresses ` :math:`\memaddr_i` in index order. -11. Let :math:`\globaladdr^\ast` be the concatenation of the :ref:`global addresses ` :math:`\globaladdr_i` in index order. +13. Let :math:`\globaladdr^\ast` be the concatenation of the :ref:`global addresses ` :math:`\globaladdr_i` in index order. -12. Let :math:`\elemaddr^\ast` be the concatenation of the :ref:`element addresses ` :math:`\elemaddr_i` in index order. +14. Let :math:`\elemaddr^\ast` be the concatenation of the :ref:`element addresses ` :math:`\elemaddr_i` in index order. -13. Let :math:`\dataaddr^\ast` be the concatenation of the :ref:`data addresses ` :math:`\dataaddr_i` in index order. +15. Let :math:`\dataaddr^\ast` be the concatenation of the :ref:`data addresses ` :math:`\dataaddr_i` in index order. -14. Let :math:`\funcaddr_{\F{mod}}^\ast` be the list of :ref:`function addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\funcaddr^\ast`. +16. Let :math:`\funcaddr_{\F{mod}}^\ast` be the list of :ref:`function addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\funcaddr^\ast`. -15. Let :math:`\tableaddr_{\F{mod}}^\ast` be the list of :ref:`table addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\tableaddr^\ast`. +17. Let :math:`\tableaddr_{\F{mod}}^\ast` be the list of :ref:`table addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\tableaddr^\ast`. -16. Let :math:`\memaddr_{\F{mod}}^\ast` be the list of :ref:`memory addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\memaddr^\ast`. +18. Let :math:`\memaddr_{\F{mod}}^\ast` be the list of :ref:`memory addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\memaddr^\ast`. -17. Let :math:`\globaladdr_{\F{mod}}^\ast` be the list of :ref:`global addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\globaladdr^\ast`. +19. Let :math:`\globaladdr_{\F{mod}}^\ast` be the list of :ref:`global addresses ` extracted from :math:`\externval_{\F{im}}^\ast`, concatenated with :math:`\globaladdr^\ast`. -18. For each :ref:`export ` :math:`\export_i` in :math:`\module.\MEXPORTS`, do: +20. For each :ref:`export ` :math:`\export_i` in :math:`\module.\MEXPORTS`, do: a. If :math:`\export_i` is a function export for :ref:`function index ` :math:`x`, then let :math:`\externval_i` be the :ref:`external value ` :math:`\EVFUNC~(\funcaddr_{\F{mod}}^\ast[x])`. @@ -499,17 +368,17 @@ and list of :ref:`reference ` vectors for the module's :ref:`element e. Let :math:`\exportinst_i` be the :ref:`export instance ` :math:`\{\EINAME~(\export_i.\ENAME), \EIVALUE~\externval_i\}`. -19. Let :math:`\exportinst^\ast` be the concatenation of the :ref:`export instances ` :math:`\exportinst_i` in index order. +21. Let :math:`\exportinst^\ast` be the concatenation of the :ref:`export instances ` :math:`\exportinst_i` in index order. -20. Let :math:`\moduleinst` be the :ref:`module instance ` :math:`\{\MITYPES~(\module.\MTYPES),` :math:`\MIFUNCS~\funcaddr_{\F{mod}}^\ast,` :math:`\MITABLES~\tableaddr_{\F{mod}}^\ast,` :math:`\MIMEMS~\memaddr_{\F{mod}}^\ast,` :math:`\MIGLOBALS~\globaladdr_{\F{mod}}^\ast,` :math:`\MIEXPORTS~\exportinst^\ast\}`. +22. Let :math:`\moduleinst` be the :ref:`module instance ` :math:`\{\MITYPES~\deftype^\ast,` :math:`\MIFUNCS~\funcaddr_{\F{mod}}^\ast,` :math:`\MITABLES~\tableaddr_{\F{mod}}^\ast,` :math:`\MIMEMS~\memaddr_{\F{mod}}^\ast,` :math:`\MIGLOBALS~\globaladdr_{\F{mod}}^\ast,` :math:`\MIEXPORTS~\exportinst^\ast\}`. -21. Return :math:`\moduleinst`. +23. Return :math:`\moduleinst`. .. math:: ~\\ \begin{array}{rlll} - \allocmodule(S, \module, \externval_{\F{im}}^\ast, \val^\ast, (\reff^\ast)^\ast) &=& S', \moduleinst + \allocmodule(S, \module, \externval_{\F{im}}^\ast, \val_{\F{g}}^\ast, \reff_{\F{t}}^\ast, (\reff_{\F{e}}^\ast)^\ast) &=& S', \moduleinst \end{array} where: @@ -524,7 +393,7 @@ where: \export^\ast &=& \module.\MEXPORTS \\[1ex] \moduleinst &=& \{~ \begin{array}[t]{@{}l@{}} - \MITYPES~\module.\MTYPES, \\ + \MITYPES~\deftype^\ast, \\ \MIFUNCS~\evfuncs(\externval_{\F{im}}^\ast)~\funcaddr^\ast, \\ \MITABLES~\evtables(\externval_{\F{im}}^\ast)~\tableaddr^\ast, \\ \MIMEMS~\evmems(\externval_{\F{im}}^\ast)~\memaddr^\ast, \\ @@ -533,19 +402,21 @@ where: \MIDATAS~\dataaddr^\ast, \\ \MIEXPORTS~\exportinst^\ast ~\} \end{array} \\[1ex] + \deftype^\ast &=& + \alloctype^\ast(\module.\MTYPES) \\ S_1, \funcaddr^\ast &=& \allocfunc^\ast(S, \module.\MFUNCS, \moduleinst) \\ S_2, \tableaddr^\ast &=& - \alloctable^\ast(S_1, (\table.\TTYPE)^\ast, (\REFNULL~t)^\ast) + \alloctable^\ast(S_1, \insttype_{\moduleinst}(\table.\TTYPE)^\ast, \reff_{\F{t}}^\ast) \quad (\where (\table.\TTYPE)^\ast = (\limits~t)^\ast) \\ S_3, \memaddr^\ast &=& - \allocmem^\ast(S_2, (\mem.\MTYPE)^\ast) \\ + \allocmem^\ast(S_2, \insttype_{\moduleinst}(\mem.\MTYPE)^\ast) \\ S_4, \globaladdr^\ast &=& - \allocglobal^\ast(S_3, (\global.\GTYPE)^\ast, \val^\ast) \\ + \allocglobal^\ast(S_3, \insttype_{\moduleinst}(\global.\GTYPE)^\ast, \val_{\F{g}}^\ast) \\ S_5, \elemaddr^\ast &=& - \allocelem^\ast(S_4, (\elem.\ETYPE)^\ast, (\reff^\ast)^\ast) \\ + \allocelem^\ast(S_4, \insttype_{\moduleinst}(\elem.\ETYPE)^\ast, (\reff_{\F{e}}^\ast)^\ast) \\ S', \dataaddr^\ast &=& - \allocdata^\ast(S_5, (\data.\DINIT)^\ast) \\ + \allocdata^\ast(S_5, \data.\DINIT^\ast) \\ \exportinst^\ast &=& \{ \EINAME~(\export.\ENAME), \EIVALUE~\externval_{\F{ex}} \}^\ast \\[1ex] \evfuncs(\externval_{\F{ex}}^\ast) &=& (\moduleinst.\MIFUNCS[x])^\ast @@ -579,8 +450,32 @@ Here, the notation :math:`\F{allocx}^\ast` is shorthand for multiple :ref:`alloc Moreover, if the dots :math:`\dots` are a sequence :math:`A^n` (as for globals or tables), then the elements of this sequence are passed to the allocation function pointwise. +For types, however, allocation is defined in terms of :ref:`rolling ` and :ref:`substitution ` of all preceding types to produce a list of :ref:`closed ` :ref:`defined types `: + +.. _alloc-type: + +.. math:: + \begin{array}{rlll} + \alloctype^\ast(\rectype^n) = \deftype^\ast \\[1ex] + \mbox{where for all $i < n$:} \hfill \\ + \rectype^n[i] &=& \REC~\subtype_i^{m_i} \\ + \deftype^\ast[x_i \slice m_i] &=& \rolldt_{x_i}(\REC~\subtype_i^{m_i})[\subst \deftype^\ast[0 \slice x_i]] \\ + x_{i+1} &=& x_i + m_i \\ + x_n &=& |\deftype^\ast| \\ + \end{array} + + +.. scratch + \begin{array}{rlll} + \alloctype^\ast(\epsilon) = \epsilon \\ + \alloctype^\ast(\rectype^\ast~\rectype') = \deftype^\ast~{\deftype'}^\ast \\[1ex] + \mbox{where:} \hfill \\ + \deftype^\ast &=& \alloctype^\ast(\reftype^\ast) \\ + {\deftype'}^\ast &=& \rolldt_{|\deftype^\ast|}(\rectype)[\subst \deftype^\ast) \\ + \end{array} + .. note:: - The definition of module allocation is mutually recursive with the allocation of its associated functions, because the resulting module instance :math:`\moduleinst` is passed to the function allocator as an argument, in order to form the necessary closures. + The definition of module allocation is mutually recursive with the allocation of its associated functions, because the resulting module instance :math:`\moduleinst` is passed to the allocators as an argument, in order to form the necessary closures. In an implementation, this recursion is easily unraveled by mutating one or the other in a secondary step. @@ -614,47 +509,55 @@ It is up to the :ref:`embedder ` to define how such conditions are rep i. Fail. - b. If :math:`\externtype_i` does not :ref:`match ` :math:`\externtype'_i`, then: + b. Let :math:`\externtype''_i` be the :ref:`external type ` obtained by :ref:`instantiating ` :math:`\externtype'_i` in :math:`\moduleinst` defined below. + + c. If :math:`\externtype_i` does not :ref:`match ` :math:`\externtype''_i`, then: i. Fail. .. _exec-initvals: -5. Let :math:`\moduleinst_{\F{init}}` be the auxiliary module :ref:`instance ` :math:`\{\MIGLOBALS~\evglobals(\externval^n), \MIFUNCS~\moduleinst.\MIFUNCS\}` that only consists of the imported globals and the imported and allocated functions from the final module instance :math:`\moduleinst`, defined below. - -6. Let :math:`F_{\F{init}}` be the auxiliary :ref:`frame ` :math:`\{ \AMODULE~\moduleinst_{\F{init}}, \ALOCALS~\epsilon \}`. +6. Let :math:`F` be the auxiliary :ref:`frame ` :math:`\{ \AMODULE~\moduleinst, \ALOCALS~\epsilon \}`, that consists of the final module instance :math:`\moduleinst`, defined below. -7. Push the frame :math:`F_{\F{init}}` to the stack. +7. Push the frame :math:`F` to the stack. -8. Let :math:`\val^\ast` be the vector of :ref:`global ` initialization :ref:`values ` determined by :math:`\module` and :math:`\externval^n`. These may be calculated as follows. +8. Let :math:`\val_{\F{g}}^\ast` be the vector of :ref:`global ` initialization :ref:`values ` determined by :math:`\module` and :math:`\externval^n`. These may be calculated as follows. a. For each :ref:`global ` :math:`\global_i` in :math:`\module.\MGLOBALS`, do: - i. Let :math:`\val_i` be the result of :ref:`evaluating ` the initializer expression :math:`\global_i.\GINIT`. + i. Let :math:`\val_{\F{g}i}` be the result of :ref:`evaluating ` the initializer expression :math:`\global_i.\GINIT`. - b. Assert: due to :ref:`validation `, the frame :math:`F_{\F{init}}` is now on the top of the stack. + b. Assert: due to :ref:`validation `, the frame :math:`F` is now on the top of the stack. - c. Let :math:`\val^\ast` be the concatenation of :math:`\val_i` in index order. + c. Let :math:`\val_{\F{g}}^\ast` be the concatenation of :math:`\val_{\F{g}i}` in index order. -9. Let :math:`(\reff^\ast)^\ast` be the list of :ref:`reference ` vectors determined by the :ref:`element segments ` in :math:`\module`. These may be calculated as follows. +9. Let :math:`\reff_{\F{t}}^\ast` be the vector of :ref:`table ` initialization :ref:`references ` determined by :math:`\module` and :math:`\externval^n`. These may be calculated as follows. - a. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS`, and for each element :ref:`expression ` :math:`\expr_{ij}` in :math:`\elem_i.\EINIT`, do: + a. For each :ref:`table ` :math:`\table_i` in :math:`\module.\MTABLES`, do: - i. Let :math:`\reff_{ij}` be the result of :ref:`evaluating ` the initializer expression :math:`\expr_{ij}`. + i. Let :math:`\val_{\F{t}i}` be the result of :ref:`evaluating ` the initializer expression :math:`\table_i.\TINIT`. - b. Let :math:`\reff^\ast_i` be the concatenation of function elements :math:`\reff_{ij}` in order of index :math:`j`. + ii. Assert: due to :ref:`validation `, :math:`\val_{\F{t}i}` is a :ref:`reference `. - c. Let :math:`(\reff^\ast)^\ast` be the concatenation of function element vectors :math:`\reff^\ast_i` in order of index :math:`i`. + iii. Let :math:`\reff_{\F{t}i}` be the reference :math:`\val_{\F{t}i}`. -10. Pop the frame :math:`F_{\F{init}}` from the stack. + b. Assert: due to :ref:`validation `, the frame :math:`F` is now on the top of the stack. -11. Let :math:`\moduleinst` be a new module instance :ref:`allocated ` from :math:`\module` in store :math:`S` with imports :math:`\externval^n`, global initializer values :math:`\val^\ast`, and element segment contents :math:`(\reff^\ast)^\ast`, and let :math:`S'` be the extended store produced by module allocation. + c. Let :math:`\reff_{\F{t}}^\ast` be the concatenation of :math:`\reff_{ti}` in index order. -12. Let :math:`F` be the auxiliary :ref:`frame ` :math:`\{ \AMODULE~\moduleinst, \ALOCALS~\epsilon \}`. +10. Let :math:`(\reff_{\F{e}}^\ast)^\ast` be the list of :ref:`reference ` vectors determined by the :ref:`element segments ` in :math:`\module`. These may be calculated as follows. -13. Push the frame :math:`F` to the stack. + a. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS`, and for each element :ref:`expression ` :math:`\expr_{ij}` in :math:`\elem_i.\EINIT`, do: -14. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS` whose :ref:`mode ` is of the form :math:`\EACTIVE~\{ \ETABLE~\tableidx_i, \EOFFSET~\X{einstr}^\ast_i~\END \}`, do: + i. Let :math:`\reff_{ij}` be the result of :ref:`evaluating ` the initializer expression :math:`\expr_{ij}`. + + b. Let :math:`\reff^\ast_i` be the concatenation of function elements :math:`\reff_{ij}` in order of index :math:`j`. + + c. Let :math:`(\reff_{\F{e}}^\ast)^\ast` be the concatenation of function element vectors :math:`\reff^\ast_i` in order of index :math:`i`. + +11. Let :math:`\moduleinst` be a new module instance :ref:`allocated ` from :math:`\module` in store :math:`S` with imports :math:`\externval^n`, global initializer values :math:`\val_{\F{g}}^\ast`, table initializer values :math:`\reff_{\F{t}}^\ast`, and element segment contents :math:`(\reff_{\F{e}}^\ast)^\ast`, and let :math:`S'` be the extended store produced by module allocation. + +12. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS` whose :ref:`mode ` is of the form :math:`\EACTIVE~\{ \ETABLE~\tableidx_i, \EOFFSET~\X{einstr}^\ast_i~\END \}`, do: a. Let :math:`n` be the length of the vector :math:`\elem_i.\EINIT`. @@ -668,11 +571,11 @@ It is up to the :ref:`embedder ` to define how such conditions are rep f. :ref:`Execute ` the instruction :math:`\ELEMDROP~i`. -15. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS` whose :ref:`mode ` is of the form :math:`\EDECLARATIVE`, do: +13. For each :ref:`element segment ` :math:`\elem_i` in :math:`\module.\MELEMS` whose :ref:`mode ` is of the form :math:`\EDECLARATIVE`, do: a. :ref:`Execute ` the instruction :math:`\ELEMDROP~i`. -16. For each :ref:`data segment ` :math:`\data_i` in :math:`\module.\MDATAS` whose :ref:`mode ` is of the form :math:`\DACTIVE~\{ \DMEM~\memidx_i, \DOFFSET~\X{dinstr}^\ast_i~\END \}`, do: +14. For each :ref:`data segment ` :math:`\data_i` in :math:`\module.\MDATAS` whose :ref:`mode ` is of the form :math:`\DACTIVE~\{ \DMEM~\memidx_i, \DOFFSET~\X{dinstr}^\ast_i~\END \}`, do: a. Assert: :math:`\memidx_i` is :math:`0`. @@ -688,15 +591,15 @@ It is up to the :ref:`embedder ` to define how such conditions are rep g. :ref:`Execute ` the instruction :math:`\DATADROP~i`. -17. If the :ref:`start function ` :math:`\module.\MSTART` is not empty, then: +15. If the :ref:`start function ` :math:`\module.\MSTART` is not empty, then: a. Let :math:`\start` be the :ref:`start function ` :math:`\module.\MSTART`. b. :ref:`Execute ` the instruction :math:`\CALL~\start.\SFUNC`. -18. Assert: due to :ref:`validation `, the frame :math:`F` is now on the top of the stack. +16. Assert: due to :ref:`validation `, the frame :math:`F` is now on the top of the stack. -19. Pop the frame :math:`F` from the stack. +17. Pop the frame :math:`F` from the stack. .. math:: @@ -709,19 +612,21 @@ It is up to the :ref:`embedder ` to define how such conditions are rep (\CALL~\start.\SFUNC)^? \\ \end{array} \\ &(\iff - & \vdashmodule \module : \externtype_{\F{im}}^k \to \externtype_{\F{ex}}^\ast \\ - &\wedge& (S \vdashexternval \externval : \externtype)^k \\ - &\wedge& (\vdashexterntypematch \externtype \matchesexterntype \externtype_{\F{im}})^k \\[1ex] + & \vdashmodule \module : \externtype_{\F{im}}^k \rightarrow \externtype_{\F{ex}}^\ast \\ + &\wedge& (S' \vdashexternval \externval : \externtype)^k \\ + &\wedge& (S' \vdashexterntypematch \externtype \matchesexterntype \insttype_{\moduleinst}(\externtype_{\F{im}}))^k \\[1ex] &\wedge& \module.\MGLOBALS = \global^\ast \\ &\wedge& \module.\MELEMS = \elem^n \\ &\wedge& \module.\MDATAS = \data^m \\ &\wedge& \module.\MSTART = \start^? \\ &\wedge& (\expr_{\F{g}} = \global.\GINIT)^\ast \\ + &\wedge& (\expr_{\F{t}} = \table.\GINIT)^\ast \\ &\wedge& (\expr_{\F{e}}^\ast = \elem.\EINIT)^n \\[1ex] &\wedge& S', \moduleinst = \allocmodule(S, \module, \externval^k, \val^\ast, (\reff^\ast)^n) \\ &\wedge& F = \{ \AMODULE~\moduleinst, \ALOCALS~\epsilon \} \\[1ex] - &\wedge& (S'; F; \expr_{\F{g}} \stepto^\ast S'; F; \val~\END)^\ast \\ - &\wedge& ((S'; F; \expr_{\F{e}} \stepto^\ast S'; F; \reff~\END)^\ast)^n) \\ + &\wedge& (S'; F; \expr_{\F{g}} \stepto^\ast S'; F; \val_{\F{g}}~\END)^\ast \\ + &\wedge& (S'; F; \expr_{\F{t}} \stepto^\ast S'; F; \reff_{\F{t}}~\END)^\ast \\ + &\wedge& ((S'; F; \expr_{\F{e}} \stepto^\ast S'; F; \reff_{\F{e}}~\END)^\ast)^n) \\ \end{array} where: @@ -733,15 +638,19 @@ where: \instr^\ast~(\I32.\CONST~0)~(\I32.\CONST~n)~(\TABLEINIT~x~i)~(\ELEMDROP~i) \\ \F{runelem}_i(\{\ETYPE~\X{et}, \EINIT~\expr^n, \EMODE~\EDECLARATIVE\}) \quad=\\ \qquad (\ELEMDROP~i) \\[1ex] - \F{rundata}_i(\{\DINIT~b^n, \DMODE~\DPASSIVE\}) \quad=\quad \epsilon \\ + \F{rundata}_i(\{\DINIT~b^n, \DMODE~\DPASSIVE\}) \quad=\\ \qquad \epsilon \\ \F{rundata}_i(\{\DINIT~b^n, \DMODE~\DACTIVE \{\DMEM~0, \DOFFSET~\instr^\ast~\END\}\}) \quad=\\ \qquad \instr^\ast~(\I32.\CONST~0)~(\I32.\CONST~n)~(\MEMORYINIT~i)~(\DATADROP~i) \\ \end{array} .. note:: - Module :ref:`allocation ` and the :ref:`evaluation ` of :ref:`global ` initializers and :ref:`element segments ` are mutually recursive because the global initialization :ref:`values ` :math:`\val^\ast` and element segment contents :math:`(\reff^\ast)^\ast` are passed to the module allocator while depending on the module instance :math:`\moduleinst` and store :math:`S'` returned by allocation. - However, this recursion is just a specification device. - In practice, the initialization values can :ref:`be determined ` beforehand by staging module allocation such that first, the module's own :ref:`function instances ` are pre-allocated in the store, then the initializer expressions are evaluated, then the rest of the module instance is allocated, and finally the new function instances' :math:`\AMODULE` fields are set to that module instance. + Checking import types assumes that the :ref:`module instance ` has already been :ref:`allocated ` to compute the respective :ref:`closed ` :ref:`defined types `. + However, this forward reference merely is a way to simplify the specification. + In practice, implementations will likely allocate or canonicalize types beforehand, when *compiling* a module, in a stage before instantiation and before imports are checked. + + Similarly, module :ref:`allocation ` and the :ref:`evaluation ` of :ref:`global ` and :ref:`table ` initializers as well as :ref:`element segments ` are mutually recursive because the global initialization :ref:`values ` :math:`\val_{\F{g}}^\ast`, :math:`\reff_{\F{t}}`, and element segment contents :math:`(\reff^\ast)^\ast` are passed to the module allocator while depending on the module instance :math:`\moduleinst` and store :math:`S'` returned by allocation. + Again, this recursion is just a specification device. + In practice, the initialization values can :ref:`be determined ` beforehand by staging module allocation such that first, the module's own :ref:`function instances ` are pre-allocated in the store, then the initializer expressions are evaluated in order, allocating globals on the way, then the rest of the module instance is allocated, and finally the new function instances' :math:`\AMODULE` fields are set to that module instance. This is possible because :ref:`validation ` ensures that initialization expressions cannot actually call a function, only take their reference. All failure conditions are checked before any observable mutation of the store takes place. @@ -772,7 +681,7 @@ The following steps are performed: 2. Let :math:`\funcinst` be the :ref:`function instance ` :math:`S.\SFUNCS[\funcaddr]`. -3. Let :math:`[t_1^n] \to [t_2^m]` be the :ref:`function type ` :math:`\funcinst.\FITYPE`. +3. Let :math:`\TFUNC~[t_1^n] \toF [t_2^m]` be the :ref:`composite type ` :math:`\expanddt(\funcinst.\FITYPE)`. 4. If the length :math:`|\val^\ast|` of the provided argument values is different from the number :math:`n` of expected arguments, then: @@ -808,7 +717,7 @@ The values :math:`\val_{\F{res}}^m` are returned as the results of the invocatio ~\\[-1ex] \begin{array}{@{}lcl} \invoke(S, \funcaddr, \val^n) &=& S; F; \val^n~(\INVOKE~\funcaddr) \\ - &(\iff & S.\SFUNCS[\funcaddr].\FITYPE = [t_1^n] \to [t_2^m] \\ + &(\iff & \expanddt(S.\SFUNCS[\funcaddr].\FITYPE) = \TFUNC~[t_1^n] \toF [t_2^m] \\ &\wedge& (S \vdashval \val : t_1)^n \\ &\wedge& F = \{ \AMODULE~\{\}, \ALOCALS~\epsilon \}) \\ \end{array} diff --git a/document/core/exec/numerics.rst b/document/core/exec/numerics.rst index b76190f6f2..093258e0a0 100644 --- a/document/core/exec/numerics.rst +++ b/document/core/exec/numerics.rst @@ -104,7 +104,7 @@ Conventions: -.. index:: bit, integer, floating-point, numeric vector +.. index:: bit, integer, floating-point, numeric vector, packed type, value type .. _aux-bits: Representations @@ -119,6 +119,8 @@ Numbers and numeric vectors have an underlying binary representation as a sequen \bits_{\VN}(i) &=& \ibits_N(i) \\ \end{array} +The first case of these applies to representations of both integer :ref:`value types ` and :ref:`packed types `. + Each of these functions is a bijection, hence they are invertible. @@ -234,7 +236,7 @@ This function is bijective, and hence invertible. .. index:: Boolean -.. _aux-bool: +.. _aux-tobool: Boolean Interpretation ...................... @@ -243,8 +245,8 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{lll@{\qquad}l} - \bool(C) &=& 1 & (\iff C) \\ - \bool(C) &=& 0 & (\otherwise) \\ + \tobool(C) &=& 1 & (\iff C) \\ + \tobool(C) &=& 0 & (\otherwise) \\ \end{array} @@ -561,7 +563,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \ieqz_N(i) &=& \bool(i = 0) + \ieqz_N(i) &=& \tobool(i = 0) \end{array} @@ -574,7 +576,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \ieq_N(i_1, i_2) &=& \bool(i_1 = i_2) + \ieq_N(i_1, i_2) &=& \tobool(i_1 = i_2) \end{array} @@ -587,7 +589,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \ine_N(i_1, i_2) &=& \bool(i_1 \neq i_2) + \ine_N(i_1, i_2) &=& \tobool(i_1 \neq i_2) \end{array} @@ -600,7 +602,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \iltu_N(i_1, i_2) &=& \bool(i_1 < i_2) + \iltu_N(i_1, i_2) &=& \tobool(i_1 < i_2) \end{array} @@ -617,7 +619,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \ilts_N(i_1, i_2) &=& \bool(\signed_N(i_1) < \signed_N(i_2)) + \ilts_N(i_1, i_2) &=& \tobool(\signed_N(i_1) < \signed_N(i_2)) \end{array} @@ -630,7 +632,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \igtu_N(i_1, i_2) &=& \bool(i_1 > i_2) + \igtu_N(i_1, i_2) &=& \tobool(i_1 > i_2) \end{array} @@ -647,7 +649,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \igts_N(i_1, i_2) &=& \bool(\signed_N(i_1) > \signed_N(i_2)) + \igts_N(i_1, i_2) &=& \tobool(\signed_N(i_1) > \signed_N(i_2)) \end{array} @@ -660,7 +662,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \ileu_N(i_1, i_2) &=& \bool(i_1 \leq i_2) + \ileu_N(i_1, i_2) &=& \tobool(i_1 \leq i_2) \end{array} @@ -677,7 +679,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \iles_N(i_1, i_2) &=& \bool(\signed_N(i_1) \leq \signed_N(i_2)) + \iles_N(i_1, i_2) &=& \tobool(\signed_N(i_1) \leq \signed_N(i_2)) \end{array} @@ -690,7 +692,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \igeu_N(i_1, i_2) &=& \bool(i_1 \geq i_2) + \igeu_N(i_1, i_2) &=& \tobool(i_1 \geq i_2) \end{array} @@ -707,7 +709,7 @@ The integer result of predicates -- i.e., :ref:`tests ` and :ref: .. math:: \begin{array}{@{}lcll} - \iges_N(i_1, i_2) &=& \bool(\signed_N(i_1) \geq \signed_N(i_2)) + \iges_N(i_1, i_2) &=& \tobool(\signed_N(i_1) \geq \signed_N(i_2)) \end{array} @@ -1507,7 +1509,7 @@ This non-deterministic result is expressed by the following auxiliary function p \feq_N(\pm \NAN(n), z_2) &=& 0 \\ \feq_N(z_1, \pm \NAN(n)) &=& 0 \\ \feq_N(\pm 0, \mp 0) &=& 1 \\ - \feq_N(z_1, z_2) &=& \bool(z_1 = z_2) \\ + \feq_N(z_1, z_2) &=& \tobool(z_1 = z_2) \\ \end{array} @@ -1529,7 +1531,7 @@ This non-deterministic result is expressed by the following auxiliary function p \fne_N(\pm \NAN(n), z_2) &=& 1 \\ \fne_N(z_1, \pm \NAN(n)) &=& 1 \\ \fne_N(\pm 0, \mp 0) &=& 0 \\ - \fne_N(z_1, z_2) &=& \bool(z_1 \neq z_2) \\ + \fne_N(z_1, z_2) &=& \tobool(z_1 \neq z_2) \\ \end{array} @@ -1566,7 +1568,7 @@ This non-deterministic result is expressed by the following auxiliary function p \flt_N(z_1, + \infty) &=& 1 \\ \flt_N(z_1, - \infty) &=& 0 \\ \flt_N(\pm 0, \mp 0) &=& 0 \\ - \flt_N(z_1, z_2) &=& \bool(z_1 < z_2) \\ + \flt_N(z_1, z_2) &=& \tobool(z_1 < z_2) \\ \end{array} @@ -1603,7 +1605,7 @@ This non-deterministic result is expressed by the following auxiliary function p \fgt_N(z_1, + \infty) &=& 0 \\ \fgt_N(z_1, - \infty) &=& 1 \\ \fgt_N(\pm 0, \mp 0) &=& 0 \\ - \fgt_N(z_1, z_2) &=& \bool(z_1 > z_2) \\ + \fgt_N(z_1, z_2) &=& \tobool(z_1 > z_2) \\ \end{array} @@ -1640,7 +1642,7 @@ This non-deterministic result is expressed by the following auxiliary function p \fle_N(z_1, + \infty) &=& 1 \\ \fle_N(z_1, - \infty) &=& 0 \\ \fle_N(\pm 0, \mp 0) &=& 1 \\ - \fle_N(z_1, z_2) &=& \bool(z_1 \leq z_2) \\ + \fle_N(z_1, z_2) &=& \tobool(z_1 \leq z_2) \\ \end{array} @@ -1677,7 +1679,7 @@ This non-deterministic result is expressed by the following auxiliary function p \fge_N(z_1, + \infty) &=& 0 \\ \fge_N(z_1, - \infty) &=& 1 \\ \fge_N(\pm 0, \mp 0) &=& 1 \\ - \fge_N(z_1, z_2) &=& \bool(z_1 \geq z_2) \\ + \fge_N(z_1, z_2) &=& \tobool(z_1 \geq z_2) \\ \end{array} diff --git a/document/core/exec/runtime.rst b/document/core/exec/runtime.rst index c7a6b9819f..c1ca0bc57b 100644 --- a/document/core/exec/runtime.rst +++ b/document/core/exec/runtime.rst @@ -7,13 +7,18 @@ Runtime Structure :ref:`Store `, :ref:`stack `, and other *runtime structure* forming the WebAssembly abstract machine, such as :ref:`values ` or :ref:`module instances `, are made precise in terms of additional auxiliary syntax. -.. index:: ! value, number, reference, constant, number type, vector type, reference type, ! host address, value type, integer, floating-point, vector number, ! default value +.. index:: ! value, number, reference, constant, number type, vector type, reference type, ! host address, value type, integer, floating-point, vector number, ! default value, unboxed scalar, structure, array, external reference pair: abstract syntax; value .. _syntax-num: .. _syntax-vecc: .. _syntax-ref: +.. _syntax-ref.i31num: +.. _syntax-ref.struct: +.. _syntax-ref.array: +.. _syntax-ref.host: .. _syntax-ref.extern: .. _syntax-val: +.. _syntax-null: Values ~~~~~~ @@ -25,8 +30,12 @@ In order to avoid ambiguities, values are therefore represented with an abstract It is convenient to reuse the same notation as for the |CONST| :ref:`instructions ` and |REFNULL| producing them. References other than null are represented with additional :ref:`administrative instructions `. -They either are *function references*, pointing to a specific :ref:`function address `, -or *external references* pointing to an uninterpreted form of :ref:`extern address ` that can be defined by the :ref:`embedder ` to represent its own objects. +They either are *scalar references*, containing a 31-bit :ref:`integer `, +*structure references*, pointing to a specific :ref:`structure address `, +*array references*, pointing to a specific :ref:`array address `, +*function references*, pointing to a specific :ref:`function address `, +or *host references* pointing to an uninterpreted form of :ref:`host address ` defined by the :ref:`embedder `. +Any of the aformentioned references can furthermore be wrapped up as an *external reference*. .. math:: \begin{array}{llcl} @@ -38,9 +47,13 @@ or *external references* pointing to an uninterpreted form of :ref:`extern addre \production{vector} & \vecc &::=& \V128.\CONST~\i128 \\ \production{reference} & \reff &::=& - \REFNULL~t \\&&|& + \REFNULL~(\absheaptype~|~\deftype) \\&&|& + \REFI31NUM~\u31 \\&&|& + \REFSTRUCTADDR~\structaddr \\&&|& + \REFARRAYADDR~\arrayaddr \\&&|& \REFFUNCADDR~\funcaddr \\&&|& - \REFEXTERNADDR~\externaddr \\ + \REFHOSTADDR~\hostaddr \\&&|& + \REFEXTERN~\reff \\ \production{value} & \val &::=& \num ~|~ \vecc ~|~ \reff \\ \end{array} @@ -50,14 +63,16 @@ or *external references* pointing to an uninterpreted form of :ref:`extern addre .. _default-val: -Each :ref:`value type ` has an associated *default value*; -it is the respective value :math:`0` for :ref:`number types `, :math:`0` for :ref:`vector types `, and null for :ref:`reference types `. +:ref:`Value types ` can have an associated *default value*; +it is the respective value :math:`0` for :ref:`number types `, :math:`0` for :ref:`vector types `, and null for nullable :ref:`reference types `. +For other references, no default value is defined, :math:`\default_t` hence is an optional value :math:`\val^?`. .. math:: \begin{array}{lcl@{\qquad}l} \default_t &=& t{.}\CONST~0 & (\iff t = \numtype) \\ \default_t &=& t{.}\CONST~0 & (\iff t = \vectype) \\ - \default_t &=& \REFNULL~t & (\iff t = \reftype) \\ + \default_t &=& \REFNULL~t & (\iff t = (\REF~\NULL~\heaptype)) \\ + \default_t &=& \epsilon & (\iff t = (\REF~\heaptype)) \\ \end{array} @@ -84,7 +99,8 @@ It is either a sequence of :ref:`values ` or a :ref:`trap `, :ref:`tables `, :ref:`memories `, and :ref:`globals `, :ref:`element segments `, and :ref:`data segments ` that have been :ref:`allocated ` during the life time of the abstract machine. [#gc]_ +It consists of the runtime representation of all *instances* of :ref:`functions `, :ref:`tables `, :ref:`memories `, and :ref:`globals `, :ref:`element segments `, :ref:`data segments `, and :ref:`structures ` or :ref:`arrays ` that have been :ref:`allocated ` during the life time of the abstract machine. [#gc]_ It is an invariant of the semantics that no element or data instance is :ref:`addressed ` from anywhere else but the owning module instances. @@ -108,7 +124,9 @@ Syntactically, the store is defined as a :ref:`record ` listing \SMEMS & \meminst^\ast, \\ \SGLOBALS & \globalinst^\ast, \\ \SELEMS & \eleminst^\ast, \\ - \SDATAS & \datainst^\ast ~\} \\ + \SDATAS & \datainst^\ast, \\ + \SSTRUCTS & \structinst^\ast, \\ + \SARRAYS & \arrayinst^\ast ~\} \end{array} \end{array} @@ -124,13 +142,15 @@ Convention * The meta variable :math:`S` ranges over stores where clear from context. -.. index:: ! address, store, function instance, table instance, memory instance, global instance, element instance, data instance, embedder +.. index:: ! address, store, function instance, table instance, memory instance, global instance, element instance, data instance, structure instance, array instance, embedder, host pair: abstract syntax; function address pair: abstract syntax; table address pair: abstract syntax; memory address pair: abstract syntax; global address pair: abstract syntax; element address pair: abstract syntax; data address + pair: abstract syntax; structure address + pair: abstract syntax; array address pair: abstract syntax; host address pair: function; address pair: table; address @@ -138,6 +158,8 @@ Convention pair: global; address pair: element; address pair: data; address + pair: structure; address + pair: array; address pair: host; address .. _syntax-funcaddr: .. _syntax-tableaddr: @@ -145,13 +167,15 @@ Convention .. _syntax-globaladdr: .. _syntax-elemaddr: .. _syntax-dataaddr: -.. _syntax-externaddr: +.. _syntax-structaddr: +.. _syntax-arrayaddr: +.. _syntax-hostaddr: .. _syntax-addr: Addresses ~~~~~~~~~ -:ref:`Function instances `, :ref:`table instances `, :ref:`memory instances `, and :ref:`global instances `, :ref:`element instances `, and :ref:`data instances ` in the :ref:`store ` are referenced with abstract *addresses*. +:ref:`Function instances `, :ref:`table instances `, :ref:`memory instances `, and :ref:`global instances `, :ref:`element instances `, :ref:`data instances ` and :ref:`structure ` or :ref:`array instances ` in the :ref:`store ` are referenced with abstract *addresses*. These are simply indices into the respective store component. In addition, an :ref:`embedder ` may supply an uninterpreted set of *host addresses*. @@ -171,7 +195,11 @@ In addition, an :ref:`embedder ` may supply an uninterpreted set of *h \addr \\ \production{data address} & \dataaddr &::=& \addr \\ - \production{extern address} & \externaddr &::=& + \production{structure address} & \structaddr &::=& + \addr \\ + \production{array address} & \arrayaddr &::=& + \addr \\ + \production{host address} & \hostaddr &::=& \addr \\ \end{array} @@ -190,7 +218,26 @@ even where this identity is not observable from within WebAssembly code itself hence logical addresses can be arbitrarily large natural numbers. -.. index:: ! instance, function type, function instance, table instance, memory instance, global instance, element instance, data instance, export instance, table address, memory address, global address, element address, data address, index, name +.. _free-funcaddr: +.. _free-tableaddr: +.. _free-memaddr: +.. _free-globaladdr: +.. _free-elemaddr: +.. _free-dataaddr: +.. _free-structaddr: +.. _free-arrayaddr: +.. _free-localaddr: +.. _free-labeladdr: +.. _free-addr: + +Conventions +........... + +* The notation :math:`\F{addr}(A)` denotes the set of addresses from address space :math:`\X{addr}` occurring free in :math:`A`. We sometimes reinterpret this set as the :ref:`vector ` of its elements. + + + +.. index:: ! instance, function type, type instance, function instance, table instance, memory instance, global instance, element instance, data instance, export instance, table address, memory address, global address, element address, data address, index, name pair: abstract syntax; module instance pair: module; instance .. _syntax-moduleinst: @@ -206,7 +253,7 @@ and collects runtime representations of all entities that are imported, defined, \begin{array}{llll} \production{module instance} & \moduleinst &::=& \{ \begin{array}[t]{l@{~}ll} - \MITYPES & \functype^\ast, \\ + \MITYPES & \deftype^\ast, \\ \MIFUNCS & \funcaddr^\ast, \\ \MITABLES & \tableaddr^\ast, \\ \MIMEMS & \memaddr^\ast, \\ @@ -239,8 +286,8 @@ The module instance is used to resolve references to other definitions during ex .. math:: \begin{array}{llll} \production{function instance} & \funcinst &::=& - \{ \FITYPE~\functype, \FIMODULE~\moduleinst, \FICODE~\func \} \\ &&|& - \{ \FITYPE~\functype, \FIHOSTCODE~\hostfunc \} \\ + \{ \FITYPE~\deftype, \FIMODULE~\moduleinst, \FICODE~\func \} \\ &&|& + \{ \FITYPE~\deftype, \FIHOSTCODE~\hostfunc \} \\ \production{host function} & \hostfunc &::=& \dots \\ \end{array} @@ -274,7 +321,7 @@ It records its :ref:`type ` and holds a vector of :ref:`refere Table elements can be mutated through :ref:`table instructions `, the execution of an active :ref:`element segment `, or by external means provided by the :ref:`embedder `. -It is an invariant of the semantics that all table elements have a type equal to the element type of :math:`\tabletype`. +It is an invariant of the semantics that all table elements have a type :ref:`matching ` the element type of :math:`\tabletype`. It also is an invariant that the length of the element vector never exceeds the maximum size of :math:`\tabletype`, if present. @@ -322,7 +369,7 @@ It records its :ref:`type ` and holds an individual :ref:`val The value of mutable globals can be mutated through :ref:`variable instructions ` or by external means provided by the :ref:`embedder `. -It is an invariant of the semantics that the value has a type equal to the :ref:`value type ` of :math:`\globaltype`. +It is an invariant of the semantics that the value has a type :ref:`matching ` the :ref:`value type ` of :math:`\globaltype`. .. index:: ! element instance, element segment, embedder, element expression @@ -379,7 +426,7 @@ It defines the export's :ref:`name ` and the associated :ref:`exter \end{array} -.. index:: ! external value, function address, table address, memory address, global address, store, function, table, memory, global +.. index:: ! external value, function address, table address, memory address, global address, store, function, table, memory, global, instruction type pair: abstract syntax; external value pair: external; value .. _syntax-externval: @@ -415,6 +462,62 @@ It filters out entries of a specific kind in an order-preserving fashion: * :math:`\evglobals(\externval^\ast) = [\globaladdr ~|~ (\EVGLOBAL~\globaladdr) \in \externval^\ast]` +.. index:: ! structure instance, ! array instance, structure type, array type, defined type, ! field value, ! packed value + pair: abstract syntax; field value + pair: abstract syntax; packed value + pair: abstract syntax; structure instance + pair: abstract syntax; array instance + pair: structure; instance + pair: array; instance +.. _syntax-fieldval: +.. _syntax-packedval: +.. _syntax-structinst: +.. _syntax-arrayinst: +.. _syntax-aggrinst: + +Aggregate Instances +~~~~~~~~~~~~~~~~~~~ + +A *structure instance* is the runtime representation of a heap object allocated from a :ref:`structure type `. +Likewise, an *array instance* is the runtime representation of a heap object allocated from an :ref:`array type `. +Both record their respective :ref:`defined type ` and hold a vector of the values of their *fields*. + +.. math:: + \begin{array}{llcl} + \production{structure instance} & \structinst &::=& + \{ \SITYPE~\deftype, \SIFIELDS~\vec(\fieldval) \} \\ + \production{array instance} & \arrayinst &::=& + \{ \AITYPE~\deftype, \AIFIELDS~\vec(\fieldval) \} \\ + \production{field value} & \fieldval &::=& + \val ~|~ \packedval \\ + \production{packed value} & \packedval &::=& + \I8PACK~\u8 ~|~ \I16PACK~\u16 \\ + \end{array} + + +.. _aux-packval: +.. _aux-unpackval: + +Conventions +........... + +* Conversion of a regular :ref:`value ` to a :ref:`field value ` is defined as follows: + + .. math:: + \begin{array}{@{}lcl} + \packval_{\valtype}(\val) &=& \val \\ + \packval_{\packedtype}(\I32.\CONST~i) &=& \packedtype.\PACK~(\wrap_{32,|\packedtype|}(i)) + \end{array} + +* The inverse conversion of a :ref:`field value ` to a regular :ref:`value ` is defined as follows: + + .. math:: + \begin{array}{@{}lcl} + \unpackval_{\valtype}(\val) &=& \val \\ + \unpackval^{\sx}_{\packedtype}(\packedtype.\PACK~i) &=& \I32.\CONST~(\extend^{\sx}_{|\packedtype|,32}(i)) + \end{array} + + .. index:: ! stack, ! frame, ! label, instruction, store, activation, function, call, local, module instance pair: abstract syntax; frame pair: abstract syntax; label @@ -489,13 +592,14 @@ and a reference to the function's own :ref:`module instance ` \production{frame} & \frame &::=& \FRAME_n\{ \framestate \} \\ \production{frame state} & \framestate &::=& - \{ \ALOCALS~\val^\ast, \AMODULE~\moduleinst \} \\ + \{ \ALOCALS~(\val^?)^\ast, \AMODULE~\moduleinst \} \\ \end{array} -The values of the locals are mutated by respective :ref:`variable instructions `. +Locals may be uninitialized, in which case they are empty. +Locals are mutated by respective :ref:`variable instructions `. -.. _exec-expand: +.. _aux-fblocktype: Conventions ........... @@ -504,12 +608,12 @@ Conventions * The meta variable :math:`F` ranges over frame states where clear from context. -* The following auxiliary definition takes a :ref:`block type ` and looks up the :ref:`function type ` that it denotes in the current frame: +* The following auxiliary definition takes a :ref:`block type ` and looks up the :ref:`instruction type ` that it denotes in the current frame: .. math:: - \begin{array}{lll} - \expand_F(\typeidx) &=& F.\AMODULE.\MITYPES[\typeidx] \\ - \expand_F([\valtype^?]) &=& [] \to [\valtype^?] \\ + \begin{array}{llll} + \fblocktype_{S;F}(\typeidx) &=& \functype & (\iff \expanddt(F.\AMODULE.\MITYPES[\typeidx]) = \TFUNC~\functype) \\ + \fblocktype_{S;F}([\valtype^?]) &=& [] \to [\valtype^?] \\ \end{array} @@ -518,6 +622,7 @@ Conventions .. _syntax-trap: .. _syntax-reffuncaddr: .. _syntax-invoke: +.. _syntax-return_invoke: .. _syntax-instr-admin: Administrative Instructions @@ -533,9 +638,14 @@ In order to express the reduction of :ref:`traps `, :ref:`calls `, :ref:`calls `. Similarly, |REFEXTERNADDR| represents :ref:`external references `. +The |REFI31NUM| instruction represents :ref:`unboxed scalar ` reference values, +|REFSTRUCTADDR| and |REFARRAYADDR| represent :ref:`structure ` and :ref:`array ` reference values, respectively, +and |REFFUNCADDR| instruction represents :ref:`function reference ` values. +Similarly, |REFHOSTADDR| represents :ref:`host references ` +and |REFEXTERN| represents any externalized reference. The |INVOKE| instruction represents the imminent invocation of a :ref:`function instance `, identified by its :ref:`address `. It unifies the handling of different forms of calls. +Analogously, |RETURNINVOKE| represents the imminent tail invocation of a function instance. The |LABEL| and |FRAME| instructions model :ref:`labels ` and :ref:`frames ` :ref:`"on the stack" `. Moreover, the administrative syntax maintains the nesting structure of the original :ref:`structured control instruction ` or :ref:`function body ` and their :ref:`instruction sequences ` with an |END| marker. diff --git a/document/core/exec/types.rst b/document/core/exec/types.rst new file mode 100644 index 0000000000..89f2e1fa42 --- /dev/null +++ b/document/core/exec/types.rst @@ -0,0 +1,29 @@ +.. index:: type, dynamic type +.. _exec-type: + +Types +----- + +Execution has to check and compare :ref:`types ` in a few places, such as :ref:`executing ` |CALLINDIRECT| or :ref:`instantiating ` :ref:`modules `. + +It is an invariant of the semantics that all types occurring during execution are :ref:`closed `. + +.. note:: + Runtime type checks generally involve types from multiple modules or types not defined by a module at all, such that module-local :ref:`type indices ` are not meaningful. + + + +.. index:: type index, defined type, type instantiation, module instance, dynamic type + +.. _type-inst: + +Instantiation +~~~~~~~~~~~~~ + +Any form of :ref:`type ` can be *instantiated* into a :ref:`closed ` type inside a :ref:`module instance ` by :ref:`substituting ` each :ref:`type index ` :math:`x` occurring in it with the corresponding :ref:`defined type ` :math:`\moduleinst.\MITYPES[x]`. + +.. math:: + \insttype_{\moduleinst}(t) = t[\subst \moduleinst.\MITYPES] + +.. note:: + This is the runtime equivalent to :ref:`type closure `. diff --git a/document/core/exec/values.rst b/document/core/exec/values.rst new file mode 100644 index 0000000000..145645c308 --- /dev/null +++ b/document/core/exec/values.rst @@ -0,0 +1,306 @@ +.. index:: value +.. exec-val: + +Values +------ + +.. index:: value, value type, validation, structure, structure type, structure instance, array, array type, array instance, function, function type, function instance, null reference, scalar reference, store +.. _valid-val: + +Value Typing +~~~~~~~~~~~~ + +For the purpose of checking argument :ref:`values ` against the parameter types of exported :ref:`functions `, +values are classified by :ref:`value types `. +The following auxiliary typing rules specify this typing relation relative to a :ref:`store ` :math:`S` in which possibly referenced addresses live. + +.. _valid-num: + +:ref:`Numeric Values ` :math:`t.\CONST~c` +..................................................... + +* The value is valid with :ref:`number type ` :math:`t`. + +.. math:: + \frac{ + }{ + S \vdashval t.\CONST~c : t + } + +.. _valid-vec: + +:ref:`Vector Values ` :math:`t.\CONST~c` +.................................................... + +* The value is valid with :ref:`vector type ` :math:`t`. + +.. math:: + \frac{ + }{ + S \vdashval t.\CONST~c : t + } + + +.. _valid-ref: + +:ref:`Null References ` :math:`\REFNULL~t` +...................................................... + +* The :ref:`heap type ` must be :ref:`valid ` under the empty :ref:`context `. + +* Then the value is valid with :ref:`reference type ` :math:`(\REF~\NULL~t')`, where the :ref:`heap type ` :math:`t'` is the least type that :ref:`matches ` :math:`t`. + +.. math:: + \frac{ + \vdashheaptype t \ok + \qquad + t' \in \{\NONE, \NOFUNC, \NOEXTERN\} + \qquad + \vdashheaptypematch t' \matchesheaptype t + }{ + S \vdashval \REFNULL~t : (\REF~\NULL~t') + } + +.. note:: + A null reference is typed with the least type in its respective hierarchy. + That ensures that it is compatible with any nullable type in that hierarchy. + + +.. _valid-ref.i31num: + +:ref:`Scalar References ` :math:`\REFI31NUM~i` +.......................................................... + +* The value is valid with :ref:`reference type ` :math:`(\REF~\I31)`. + +.. math:: + \frac{ + }{ + S \vdashval \REFI31NUM~i : \REF~\I31 + } + + +.. _valid-ref.struct: + +:ref:`Structure References ` :math:`\REFSTRUCTADDR~a` +................................................................. + +* The :ref:`structure address ` :math:`a` must exist in the store. + +* Let :math:`\structinst` be the :ref:`structure instance ` :math:`S.\SSTRUCTS[a]`. + +* Let :math:`\deftype` be the :ref:`defined type ` :math:`\structinst.\SITYPE`. + +* The :ref:`expansion ` of :math:`\deftype` must be a :ref:`struct type `. + +* Then the value is valid with :ref:`reference type ` :math:`(\REF~\deftype)`. + +.. math:: + \frac{ + \deftype = S.\SSTRUCTS[a].\SITYPE + \qquad + \expanddt(\deftype) = \TSTRUCT~\structtype + }{ + S \vdashval \REFSTRUCTADDR~a : \REF~\deftype + } + + +.. _valid-ref.array: + +:ref:`Array References ` :math:`\REFARRAYADDR~a` +............................................................ + +* The :ref:`array address ` :math:`a` must exist in the store. + +* Let :math:`\arrayinst` be the :ref:`array instance ` :math:`S.\SARRAYS[a]`. + +* Let :math:`\deftype` be the :ref:`defined type ` :math:`\arrayinst.\AITYPE`. + +* The :ref:`expansion ` of :math:`\deftype` must be an :ref:`array type `. + +* Then the value is valid with :ref:`reference type ` :math:`(\REF~\arraytype)`. + +.. math:: + \frac{ + \deftype = S.\SARRAYS[a].\AITYPE + \qquad + \expanddt(\deftype) = \TARRAY~\arraytype + }{ + S \vdashval \REFARRAYADDR~a : \REF~\deftype + } + + +:ref:`Function References ` :math:`\REFFUNCADDR~a` +.............................................................. + +* The :ref:`function address ` :math:`a` must exist in the store. + +* Let :math:`\funcinst` be the :ref:`function instance ` :math:`S.\SFUNCS[a]`. + +* Let :math:`\deftype` be the :ref:`defined type ` :math:`\funcinst.\FITYPE`. + +* The :ref:`expansion ` of :math:`\deftype` must be a :ref:`function type `. + +* Then the value is valid with :ref:`reference type ` :math:`(\REF~\functype)`. + +.. math:: + \frac{ + \deftype = S.\SFUNCS[a].\FITYPE + \qquad + \expanddt(\deftype) = \TFUNC~\functype + }{ + S \vdashval \REFFUNCADDR~a : \REF~\deftype + } + + +:ref:`Host References ` :math:`\REFHOSTADDR~a` +............................................................... + +* The value is valid with :ref:`reference type ` :math:`(\REF~\ANY)`. + +.. math:: + \frac{ + }{ + S \vdashval \REFHOSTADDR~a : \REF~\ANY + } + +.. note:: + A host reference is considered internalized by this rule. + + +:ref:`External References ` :math:`\REFEXTERN~\reff` +....................................................................... + +* The reference value :math:`\reff` must be valid with some :ref:`reference type ` :math:`(\REF~\NULL^?~t)`. + +* The :ref:`heap type ` :math:`t` must :ref:`match ` the heap type |ANY|. + +* Then the value is valid with :ref:`reference type ` :math:`(\REF~\NULL^?~\EXTERN)`. + +.. math:: + \frac{ + S \vdashval \reff : \REF~\NULL^?~t + \qquad + \vdashheaptypematch t \matchesheaptype \ANY + }{ + S \vdashval \REFEXTERN~\reff : \REF~\NULL^?~\EXTERN + } + +Subsumption +........... + +* The value must be valid with some value type :math:`t`. + +* The value type :math:`t` :ref:`matches ` another :ref:`valid ` type :math:`t'`. + +* Then the value is valid with type :math:`t'`. + +.. math:: + \frac{ + S \vdashval \val : t + \qquad + \vdashvaltype t' \ok + \qquad + \vdashvaltypematch t \matchesvaltype t' + }{ + S \vdashval \val : t' + } + + +.. index:: external value, external type, validation, import, store +.. _valid-externval: + +External Typing +~~~~~~~~~~~~~~~ + +For the purpose of checking :ref:`external values ` against :ref:`imports `, +such values are classified by :ref:`external types `. +The following auxiliary typing rules specify this typing relation relative to a :ref:`store ` :math:`S` in which the referenced instances live. + + +.. index:: function type, function address +.. _valid-externval-func: + +:math:`\EVFUNC~a` +................. + +* The store entry :math:`S.\SFUNCS[a]` must exist. + +* Then :math:`\EVFUNC~a` is valid with :ref:`external type ` :math:`\ETFUNC~S.\SFUNCS[a].\FITYPE`. + +.. math:: + \frac{ + }{ + S \vdashexternval \EVFUNC~a : \ETFUNC~S.\SFUNCS[a].\FITYPE + } + + +.. index:: table type, table address +.. _valid-externval-table: + +:math:`\EVTABLE~a` +.................. + +* The store entry :math:`S.\STABLES[a]` must exist. + +* Then :math:`\EVTABLE~a` is valid with :ref:`external type ` :math:`\ETTABLE~S.\STABLES[a].\TITYPE`. + +.. math:: + \frac{ + }{ + S \vdashexternval \EVTABLE~a : \ETTABLE~S.\STABLES[a].\TITYPE + } + + +.. index:: memory type, memory address +.. _valid-externval-mem: + +:math:`\EVMEM~a` +................ + +* The store entry :math:`S.\SMEMS[a]` must exist. + +* Then :math:`\EVMEM~a` is valid with :ref:`external type ` :math:`\ETMEM~S.\SMEMS[a].\MITYPE`. + +.. math:: + \frac{ + }{ + S \vdashexternval \EVMEM~a : \ETMEM~S.\SMEMS[a].\MITYPE + } + + +.. index:: global type, global address, value type, mutability +.. _valid-externval-global: + +:math:`\EVGLOBAL~a` +................... + +* The store entry :math:`S.\SGLOBALS[a]` must exist. + +* Then :math:`\EVGLOBAL~a` is valid with :ref:`external type ` :math:`\ETGLOBAL~S.\SGLOBALS[a].\GITYPE`. + +.. math:: + \frac{ + }{ + S \vdashexternval \EVGLOBAL~a : \ETGLOBAL~S.\SGLOBALS[a].\GITYPE + } + +Subsumption +........... + +* The external value must be valid with some external type :math:`\X{et}`. + +* The external type :math:`\X{et}` :ref:`matches ` another :ref:`valid ` type :math:`\X{et'}`. + +* Then the external value is valid with type :math:`\X{et'}`. + +.. math:: + \frac{ + S \vdashexternval \externval : \X{et} + \qquad + \vdashexterntype \X{et'} \ok + \qquad + \vdashexterntypematch \X{et} \matchesexterntype \X{et'} + }{ + S \vdashexternval \externval : \X{et'} + } diff --git a/document/core/index.bs b/document/core/index.bs index 830c0595c8..35fdf277a1 100644 --- a/document/core/index.bs +++ b/document/core/index.bs @@ -10,7 +10,7 @@ ED: https://webassembly.github.io/spec/core/bikeshed/ Editor: Andreas Rossberg, w3cid 82328 Repository: WebAssembly/spec Markup Shorthands: css no, markdown no, algorithm no, idl no -Abstract: This document describes release 2.0 of the core WebAssembly standard, a safe, portable, low-level code format designed for efficient execution and compact representation. +Abstract: This document describes release 3.0 of the core WebAssembly standard, a safe, portable, low-level code format designed for efficient execution and compact representation. Prepare For TR: true Date: now diff --git a/document/core/intro/overview.rst b/document/core/intro/overview.rst index 84c375e218..8087cc564e 100644 --- a/document/core/intro/overview.rst +++ b/document/core/intro/overview.rst @@ -68,11 +68,10 @@ This language is structured around the following concepts. .. _table: **Tables** - A *table* is an array of opaque values of a particular *element type*. + A *table* is an array of opaque values of a particular *reference type*. It allows programs to select such values indirectly through a dynamic index operand. - Currently, the only available element type is an untyped function reference or a reference to an external host value. - Thereby, a program can call functions indirectly through a dynamic index into a table. - For example, this allows emulating function pointers by way of table indices. + Thereby, for example, a program can call functions indirectly through a dynamic index into a table. + This allows emulating function pointers by way of table indices. .. _memory: diff --git a/document/core/syntax/instructions.rst b/document/core/syntax/instructions.rst index ddb51d7ee6..4b3bb08630 100644 --- a/document/core/syntax/instructions.rst +++ b/document/core/syntax/instructions.rst @@ -39,7 +39,7 @@ Numeric instructions provide basic operations over numeric :ref:`values ` of :ref:`vector type `. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{ishape} & \ishape &::=& \K{i8x16} ~|~ \K{i16x8} ~|~ \K{i32x4} ~|~ \K{i64x2} \\ \production{fshape} & \fshape &::=& @@ -211,7 +211,7 @@ Vector instructions (also known as *SIMD* instructions, *single instruction mult \end{array} .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{instruction} & \instr &::=& \dots \\&&|& \K{v128.}\VCONST~\i128 \\&&|& @@ -278,7 +278,7 @@ Vector instructions (also known as *SIMD* instructions, *single instruction mult \end{array} .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{vector bitwise unary operator} & \vvunop &::=& \K{not} \\ \production{vector bitwise binary operator} & \vvbinop &::=& @@ -351,7 +351,7 @@ Operations are performed point-wise on the values of each lane. .. note:: For example, the shape :math:`\K{i32x4}` interprets the operand as four |i32| values, packed into an |i128|. - The bitwidth of the numeric type :math:`t` times :math:`N` always is 128. + The bit width of the numeric type :math:`t` times :math:`N` always is 128. Instructions prefixed with :math:`\K{v128}` do not involve a specific interpretation, and treat the |V128| as an |i128| value or a vector of 128 individual bits. @@ -391,7 +391,7 @@ Conventions Occasionally, it is convenient to group operators together according to the following grammar shorthands: .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{unary operator} & \vunop &::=& \viunop ~|~ \vfunop ~|~ @@ -415,11 +415,15 @@ Occasionally, it is convenient to group operators together according to the foll \end{array} -.. index:: ! reference instruction, reference, null +.. index:: ! reference instruction, reference, null, cast, heap type, reference type pair: abstract syntax; instruction .. _syntax-ref.null: -.. _syntax-ref.is_null: .. _syntax-ref.func: +.. _syntax-ref.is_null: +.. _syntax-ref.as_non_null: +.. _syntax-ref.eq: +.. _syntax-ref.test: +.. _syntax-ref.cast: .. _syntax-instr-ref: Reference Instructions @@ -428,16 +432,114 @@ Reference Instructions Instructions in this group are concerned with accessing :ref:`references `. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{instruction} & \instr &::=& \dots \\&&|& - \REFNULL~\reftype \\&&|& + \REFNULL~\heaptype \\&&|& + \REFFUNC~\funcidx \\&&|& \REFISNULL \\&&|& - \REFFUNC~\funcidx \\ + \REFASNONNULL \\&&|& + \REFEQ \\&&|& + \REFTEST~\reftype \\&&|& + \REFCAST~\reftype \\ + \end{array} + +The |REFNULL| and |REFFUNC| instructions produce a :ref:`null ` value or a reference to a given function, respectively. + +The instruction |REFISNULL| checks for null, +while |REFASNONNULL| converts a :ref:`nullable ` to a non-null one, and :ref:`traps ` if it encounters null. + +The |REFEQ| compares two references. + +The instructions |REFTEST| and |REFCAST| test the :ref:`dynamic type ` of a reference operand. +The former merely returns the result of the test, +while the latter performs a downcast and :ref:`traps ` if the operand's type does not match. + +.. note:: + The |BRONCAST| and |BRONCASTFAIL| instructions provides versions of the latter that branch depending on the success of the downcast instead of trapping. + + +.. index:: reference instruction, reference, null, heap type, reference type + pair: abstract syntax; instruction + +.. _syntax-struct.new: +.. _syntax-struct.new_default: +.. _syntax-struct.get: +.. _syntax-struct.get_s: +.. _syntax-struct.get_u: +.. _syntax-struct.set: +.. _syntax-array.new: +.. _syntax-array.new_default: +.. _syntax-array.new_fixed: +.. _syntax-array.new_data: +.. _syntax-array.new_elem: +.. _syntax-array.get: +.. _syntax-array.get_s: +.. _syntax-array.get_u: +.. _syntax-array.set: +.. _syntax-array.len: +.. _syntax-array.fill: +.. _syntax-array.copy: +.. _syntax-array.init_data: +.. _syntax-array.init_elem: +.. _syntax-ref.i31: +.. _syntax-i31.get_s: +.. _syntax-i31.get_u: +.. _syntax-any.convert_extern: +.. _syntax-extern.convert_any: +.. _syntax-instr-struct: +.. _syntax-instr-array: +.. _syntax-instr-i31: +.. _syntax-instr-extern: + +Aggregate Instructions +~~~~~~~~~~~~~~~~~~~~~~ + +Instructions in this group are concerned with creating and accessing :ref:`references ` to :ref:`aggregate ` types. + +.. math:: + \begin{array}{llrl} + \production{instruction} & \instr &::=& + \dots \\&&|& + \STRUCTNEW~\typeidx \\&&|& + \STRUCTNEWDEFAULT~\typeidx \\&&|& + \STRUCTGET~\typeidx~\fieldidx \\&&|& + \STRUCTGET\K{\_}\sx~\typeidx~\fieldidx \\&&|& + \STRUCTSET~\typeidx~\fieldidx \\&&|& + \ARRAYNEW~\typeidx \\&&|& + \ARRAYNEWFIXED~\typeidx~\u32 \\&&|& + \ARRAYNEWDEFAULT~\typeidx \\&&|& + \ARRAYNEWDATA~\typeidx~\dataidx \\&&|& + \ARRAYNEWELEM~\typeidx~\elemidx \\&&|& + \ARRAYGET~\typeidx \\&&|& + \ARRAYGET\K{\_}\sx~\typeidx \\&&|& + \ARRAYSET~\typeidx \\&&|& + \ARRAYLEN \\&&|& + \ARRAYFILL~\typeidx \\&&|& + \ARRAYCOPY~\typeidx~\typeidx \\&&|& + \ARRAYINITDATA~\typeidx~\dataidx \\&&|& + \ARRAYINITELEM~\typeidx~\elemidx \\&&|& + \REFI31 \\&&|& + \I31GET\K{\_}\sx \\&&|& + \ANYCONVERTEXTERN \\&&|& + \EXTERNCONVERTANY \\ \end{array} -These instructions produce a null value, check for a null value, or produce a reference to a given function, respectively. +The instructions |STRUCTNEW| and |STRUCTNEWDEFAULT| allocate a new :ref:`structure `, initializing them either with operands or with default values. +The remaining instructions on structs access individual fields, +allowing for different sign extension modes in the case of :ref:`packed ` storage types. + +Similarly, :ref:`arrays ` can be allocated either with an explicit initialization operand or a default value. +Furthermore, |ARRAYNEWFIXED| allocates an array with statically fixed size, +and |ARRAYNEWDATA| and |ARRAYNEWELEM| allocate an array and initialize it from a :ref:`data ` or :ref:`element ` segment, respectively. +|ARRAYGET|, |ARRAYGETS|, |ARRAYGETU|, and |ARRAYSET| access individual slots, +again allowing for different sign extension modes in the case of a :ref:`packed ` storage type. +|ARRAYLEN| produces the length of an array. +|ARRAYFILL| fills a specified slice of an array with a given value and |ARRAYCOPY|, |ARRAYINITDATA|, and |ARRAYINITELEM| copy elements to a specified slice of an array from a given array, data segment, or element segment, respectively. + +The instructions |REFI31| and :math:`\I31GET\K{\_}\sx` convert between type |I31| and an unboxed :ref:`scalar `. +The instructions |ANYCONVERTEXTERN| and |EXTERNCONVERTANY| allow lossless conversion between references represented as type :math:`(\REF~\NULL~\EXTERN)`| and as :math:`(\REF~\NULL~\ANY)`. .. index:: ! parametric instruction, value type pair: abstract syntax; instruction @@ -449,7 +551,7 @@ Parametric Instructions Instructions in this group can operate on operands of any :ref:`value type `. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{instruction} & \instr &::=& \dots \\&&|& \DROP \\&&|& @@ -475,7 +577,7 @@ Variable Instructions Variable instructions are concerned with access to :ref:`local ` or :ref:`global ` variables. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{instruction} & \instr &::=& \dots \\&&|& \LOCALGET~\localidx \\&&|& @@ -504,7 +606,7 @@ Table Instructions Instructions in this group are concerned with tables :ref:`table `. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{instruction} & \instr &::=& \dots \\&&|& \TABLEGET~\tableidx \\&&|& @@ -546,43 +648,44 @@ Memory Instructions Instructions in this group are concerned with linear :ref:`memory `. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{memory immediate} & \memarg &::=& \{ \OFFSET~\u32, \ALIGN~\u32 \} \\ \production{lane width} & \X{ww} &::=& 8 ~|~ 16 ~|~ 32 ~|~ 64 \\ \production{instruction} & \instr &::=& \dots \\&&|& - \K{i}\X{nn}\K{.}\LOAD~\memarg ~|~ - \K{f}\X{nn}\K{.}\LOAD~\memarg ~|~ - \K{v128.}\LOAD~\memarg \\&&|& - \K{i}\X{nn}\K{.}\STORE~\memarg ~|~ - \K{f}\X{nn}\K{.}\STORE~\memarg ~|~ - \K{v128.}\STORE~\memarg \\&&|& - \K{i}\X{nn}\K{.}\LOAD\K{8\_}\sx~\memarg ~|~ - \K{i}\X{nn}\K{.}\LOAD\K{16\_}\sx~\memarg ~|~ - \K{i64.}\LOAD\K{32\_}\sx~\memarg \\&&|& - \K{i}\X{nn}\K{.}\STORE\K{8}~\memarg ~|~ - \K{i}\X{nn}\K{.}\STORE\K{16}~\memarg ~|~ - \K{i64.}\STORE\K{32}~\memarg \\&&|& - \K{v128.}\LOAD\K{8x8\_}\sx~\memarg ~|~ - \K{v128.}\LOAD\K{16x4\_}\sx~\memarg ~|~ - \K{v128.}\LOAD\K{32x2\_}\sx~\memarg \\&&|& - \K{v128.}\LOAD\K{32\_zero}~\memarg ~|~ - \K{v128.}\LOAD\K{64\_zero}~\memarg \\&&|& - \K{v128.}\LOAD\X{ww}\K{\_splat}~\memarg \\&&|& - \K{v128.}\LOAD\X{ww}\K{\_lane}~\memarg~\laneidx ~|~ - \K{v128.}\STORE\X{ww}\K{\_lane}~\memarg~\laneidx \\&&|& - \MEMORYSIZE \\&&|& - \MEMORYGROW \\&&|& - \MEMORYFILL \\&&|& - \MEMORYCOPY \\&&|& - \MEMORYINIT~\dataidx \\&&|& + \K{i}\X{nn}\K{.}\LOAD~\memidx~\memarg ~|~ + \K{f}\X{nn}\K{.}\LOAD~\memidx~\memarg \\&&|& + \K{v128.}\LOAD~\memidx~\memarg \\&&|& + \K{i}\X{nn}\K{.}\STORE~\memidx~\memarg ~|~ + \K{f}\X{nn}\K{.}\STORE~\memidx~\memarg \\&&|& + \K{v128.}\STORE~\memidx~\memarg \\&&|& + \K{i}\X{nn}\K{.}\LOAD\K{8\_}\sx~\memidx~\memarg ~|~ + \K{i}\X{nn}\K{.}\LOAD\K{16\_}\sx~\memidx~\memarg ~|~ + \K{i64.}\LOAD\K{32\_}\sx~\memidx~\memarg \\&&|& + \K{v128.}\LOAD\K{8x8\_}\sx~\memidx~\memarg ~|~ + \K{v128.}\LOAD\K{16x4\_}\sx~\memidx~\memarg ~|~ + \K{v128.}\LOAD\K{32x2\_}\sx~\memidx~\memarg \\&&|& + \K{v128.}\LOAD\K{32\_zero}~\memidx~\memarg ~|~ + \K{v128.}\LOAD\K{64\_zero}~\memidx~\memarg \\&&|& + \K{v128.}\LOAD\X{ww}\K{\_splat}~\memidx~\memarg \\&&|& + \K{v128.}\LOAD\X{ww}\K{\_lane}~\memidx~\memarg~\laneidx ~|~ + \K{i}\X{nn}\K{.}\STORE\K{8}~\memidx~\memarg ~|~ + \K{i}\X{nn}\K{.}\STORE\K{16}~\memidx~\memarg ~|~ + \K{i64.}\STORE\K{32}~\memidx~\memarg \\&&|& + \K{v128.}\STORE\X{ww}\K{\_lane}~\memidx~\memarg~\laneidx \\&&|& + \MEMORYSIZE~\memidx \\&&|& + \MEMORYGROW~\memidx \\&&|& + \MEMORYFILL~\memidx \\&&|& + \MEMORYCOPY~\memidx~\memidx \\&&|& + \MEMORYINIT~\memidx~\dataidx \\&&|& \DATADROP~\dataidx \\ \end{array} -Memory is accessed with |LOAD| and |STORE| instructions for the different :ref:`number types `. -They all take a *memory immediate* |memarg| that contains an address *offset* and the expected *alignment* (expressed as the exponent of a power of 2). +Memory is accessed with |LOAD| and |STORE| instructions for the different :ref:`number types ` and `vector types `. +They all take a :ref:`memory index ` and a *memory immediate* |memarg| that contains an address *offset* and the expected *alignment* (expressed as the exponent of a power of 2). + Integer loads and stores can optionally specify a *storage size* that is smaller than the :ref:`bit width ` of the respective value type. In the case of loads, a sign extension mode |sx| is then required to select appropriate behavior. @@ -597,21 +700,14 @@ A :ref:`trap ` results if any of the accessed memory bytes lies outside th Future versions of WebAssembly might provide memory instructions with 64 bit address ranges. The |MEMORYSIZE| instruction returns the current size of a memory. -The |MEMORYGROW| instruction grows memory by a given delta and returns the previous size, or :math:`-1` if enough memory cannot be allocated. +The |MEMORYGROW| instruction grows a memory by a given delta and returns the previous size, or :math:`-1` if enough memory cannot be allocated. Both instructions operate in units of :ref:`page size `. - -The |MEMORYFILL| instruction sets all values in a region to a given byte. -The |MEMORYCOPY| instruction copies data from a source memory region to a possibly overlapping destination region. +The |MEMORYFILL| instruction sets all values in a region of a memory to a given byte. +The |MEMORYCOPY| instruction copies data from a source memory region to a possibly overlapping destination region in another or the same memory; the first index denotes the destination. The |MEMORYINIT| instruction copies data from a :ref:`passive data segment ` into a memory. The |DATADROP| instruction prevents further use of a passive data segment. This instruction is intended to be used as an optimization hint. After a data segment is dropped its data can no longer be retrieved, so the memory used by this segment may be freed. -.. note:: - In the current version of WebAssembly, - all memory instructions implicitly operate on :ref:`memory ` :ref:`index ` :math:`0`. - This restriction may be lifted in future versions. - - -.. index:: ! control instruction, ! structured control, ! label, ! block, ! block type, ! branch, ! unwinding, stack type, label index, function index, type index, vector, trap, function, table, function type, value type, type index +.. index:: ! control instruction, ! structured control, ! label, ! block, ! block type, ! branch, ! unwinding, result type, label index, function index, type index, vector, trap, function, table, function type, value type, type index pair: abstract syntax; instruction pair: abstract syntax; block type pair: block; type @@ -624,6 +720,10 @@ The |DATADROP| instruction prevents further use of a passive data segment. This .. _syntax-br: .. _syntax-br_if: .. _syntax-br_table: +.. _syntax-br_on_null: +.. _syntax-br_on_non_null: +.. _syntax-br_on_cast: +.. _syntax-br_on_cast_fail: .. _syntax-return: .. _syntax-call: .. _syntax-call_indirect: @@ -636,7 +736,7 @@ Control Instructions Instructions in this group affect the flow of control. .. math:: - \begin{array}{llcl} + \begin{array}{llrl} \production{block type} & \blocktype &::=& \typeidx ~|~ \valtype^? \\ \production{instruction} & \instr &::=& @@ -649,9 +749,17 @@ Instructions in this group affect the flow of control. \BR~\labelidx \\&&|& \BRIF~\labelidx \\&&|& \BRTABLE~\vec(\labelidx)~\labelidx \\&&|& + \BRONNULL~\labelidx \\&&|& + \BRONNONNULL~\labelidx \\&&|& + \BRONCAST~\labelidx~\reftype~\reftype \\&&|& + \BRONCASTFAIL~\labelidx~\reftype~\reftype \\&&|& \RETURN \\&&|& \CALL~\funcidx \\&&|& - \CALLINDIRECT~\tableidx~\typeidx \\ + \CALLREF~\typeidx \\&&|& + \CALLINDIRECT~\tableidx~\typeidx \\&&|& + \RETURNCALL~\funcidx \\&&|& + \RETURNCALLREF~\funcidx \\&&|& + \RETURNCALLINDIRECT~\tableidx~\typeidx \\ \end{array} The |NOP| instruction does nothing. @@ -663,7 +771,7 @@ They bracket nested sequences of instructions, called *blocks*, terminated with, As the grammar prescribes, they must be well-nested. A structured instruction can consume *input* and produce *output* on the operand stack according to its annotated *block type*. -It is given either as a :ref:`type index ` that refers to a suitable :ref:`function type `, or as an optional :ref:`value type ` inline, which is a shorthand for the function type :math:`[] \to [\valtype^?]`. +It is given either as a :ref:`type index ` that refers to a suitable :ref:`function type ` reinterpreted as an :ref:`instruction type `, or as an optional :ref:`value type ` inline, which is a shorthand for the instruction type :math:`[] \to [\valtype^?]`. Each structured control instruction introduces an implicit *label*. Labels are targets for branch instructions that reference them with :ref:`label indices `. @@ -687,6 +795,9 @@ Branch instructions come in several flavors: |BR| performs an unconditional branch, |BRIF| performs a conditional branch, and |BRTABLE| performs an indirect branch through an operand indexing into the label vector that is an immediate to the instruction, or to a default target if the operand is out of bounds. +The |BRONNULL| and |BRONNONNULL| instructions check whether a reference operand is :ref:`null ` and branch if that is the case or not the case, respectively. +Similarly, |BRONCAST| and |BRONCASTFAIL| attempt a downcast on a reference operand and branch if that succeeds, or fails, respectively. + The |RETURN| instruction is a shortcut for an unconditional branch to the outermost block, which implicitly is the body of the current function. Taking a branch *unwinds* the operand stack up to the height where the targeted structured control instruction was entered. However, branches may additionally consume operands themselves, which they push back on the operand stack after unwinding. @@ -694,10 +805,15 @@ Forward branches require operands according to the output of the targeted block' Backward branches require operands according to the input of the targeted block's type, i.e., represent the values consumed by the restarted block. The |CALL| instruction invokes another :ref:`function `, consuming the necessary arguments from the stack and returning the result values of the call. -The |CALLINDIRECT| instruction calls a function indirectly through an operand indexing into a :ref:`table ` that is denoted by a :ref:`table index ` and must have type |FUNCREF|. +The |CALLREF| instruction invokes a function indirectly through a :ref:`function reference ` operand. +The |CALLINDIRECT| instruction calls a function indirectly through an operand indexing into a :ref:`table ` that is denoted by a :ref:`table index ` and must contain :ref:`function references `. Since it may contain functions of heterogeneous type, the callee is dynamically checked against the :ref:`function type ` indexed by the instruction's second immediate, and the call is aborted with a :ref:`trap ` if it does not match. +The |RETURNCALL|, |RETURNCALLREF|, and |RETURNCALLINDIRECT| instructions are *tail-call* variants of the previous ones. +That is, they first return from the current function before actually performing the respective call. +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 ` on the number of active calls. + .. index:: ! expression, constant, global, offset, element, data, instruction pair: abstract syntax; expression @@ -710,7 +826,7 @@ Expressions :ref:`Function ` bodies, initialization values for :ref:`globals `, elements and offsets of :ref:`element ` segments, and offsets of :ref:`data ` segments are given as expressions, which are sequences of :ref:`instructions ` terminated by an |END| marker. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{expression} & \expr &::=& \instr^\ast~\END \\ \end{array} diff --git a/document/core/syntax/modules.rst b/document/core/syntax/modules.rst index ec32935c8f..b75a323c52 100644 --- a/document/core/syntax/modules.rst +++ b/document/core/syntax/modules.rst @@ -12,9 +12,9 @@ In addition, it can declare :ref:`imports ` and :ref:`exports ` and :ref:`element ` segments, or a :ref:`start function `. .. math:: - \begin{array}{lllll} + \begin{array}{llrll} \production{module} & \module &::=& \{ & - \MTYPES~\vec(\functype), \\&&&& + \MTYPES~\vec(\rectype), \\&&&& \MFUNCS~\vec(\func), \\&&&& \MTABLES~\vec(\table), \\&&&& \MMEMS~\vec(\mem), \\&&&& @@ -29,7 +29,7 @@ and provide initialization in the form of :ref:`data ` and :ref:`el Each of the vectors -- and thus the entire module -- may be empty. -.. index:: ! index, ! index space, ! type index, ! function index, ! table index, ! memory index, ! global index, ! local index, ! label index, ! element index, ! data index, function, global, table, memory, element, data, local, parameter, import +.. index:: ! index, ! index space, ! type index, ! function index, ! table index, ! memory index, ! global index, ! local index, ! label index, ! element index, ! data index, ! field index, function, global, table, memory, element, data, local, parameter, import, field pair: abstract syntax; type index pair: abstract syntax; function index pair: abstract syntax; table index @@ -39,6 +39,7 @@ Each of the vectors -- and thus the entire module -- may be empty. pair: abstract syntax; data index pair: abstract syntax; local index pair: abstract syntax; label index + pair: abstract syntax; field index pair: type; index pair: function; index pair: table; index @@ -48,6 +49,7 @@ Each of the vectors -- and thus the entire module -- may be empty. pair: data; index pair: local; index pair: label; index + pair: field; index .. _syntax-typeidx: .. _syntax-funcidx: .. _syntax-tableidx: @@ -57,6 +59,7 @@ Each of the vectors -- and thus the entire module -- may be empty. .. _syntax-dataidx: .. _syntax-localidx: .. _syntax-labelidx: +.. _syntax-fieldidx: .. _syntax-index: Indices @@ -66,7 +69,7 @@ Definitions are referenced with zero-based *indices*. Each class of definition has its own *index space*, as distinguished by the following classes. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{type index} & \typeidx &::=& \u32 \\ \production{function index} & \funcidx &::=& \u32 \\ \production{table index} & \tableidx &::=& \u32 \\ @@ -76,6 +79,7 @@ Each class of definition has its own *index space*, as distinguished by the foll \production{data index} & \dataidx &::=& \u32 \\ \production{local index} & \localidx &::=& \u32 \\ \production{label index} & \labelidx &::=& \u32 \\ + \production{field index} & \fieldidx &::=& \u32 \\ \end{array} The index space for :ref:`functions `, :ref:`tables `, :ref:`memories ` and :ref:`globals ` includes respective :ref:`imports ` declared in the same module. @@ -87,6 +91,8 @@ The index space for :ref:`locals ` is only accessible inside a :re Label indices reference :ref:`structured control instructions ` inside an instruction sequence. +Each :ref:`aggregate type ` provides an index space for its :ref:`fields `. + .. _free-typeidx: .. _free-funcidx: @@ -97,6 +103,7 @@ Label indices reference :ref:`structured control instructions `. - -All function types used in a module must be defined in this component. -They are referenced by :ref:`type indices `. - -.. note:: - Future versions of WebAssembly may add additional forms of type definitions. +The |MTYPES| component of a module defines a vector of :ref:`recursive types `, each of consisting of a list of :ref:`sub types ` referenced by individual :ref:`type indices `. +All :ref:`function ` or :ref:`aggregate ` types used in a module must be defined in this component. .. index:: ! function, ! local, function index, local index, type index, value type, expression, import @@ -140,9 +142,11 @@ Functions The |MFUNCS| component of a module defines a vector of *functions* with the following structure: .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{function} & \func &::=& - \{ \FTYPE~\typeidx, \FLOCALS~\vec(\valtype), \FBODY~\expr \} \\ + \{ \FTYPE~\typeidx, \FLOCALS~\vec(\local), \FBODY~\expr \} \\ + \production{local} & \local &::=& + \{ \LTYPE~\valtype \} \\ \end{array} The |FTYPE| of a function declares its signature by reference to a :ref:`type ` defined in the module. @@ -168,15 +172,16 @@ Tables The |MTABLES| component of a module defines a vector of *tables* described by their :ref:`table type `: .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{table} & \table &::=& - \{ \TTYPE~\tabletype \} \\ + \{ \TTYPE~\tabletype, \TINIT~\expr \} \\ \end{array} -A table is a vector of opaque values of a particular :ref:`reference type `. -The |LMIN| size in the :ref:`limits ` of the table type specifies the initial size of that table, while its |LMAX|, if present, restricts the size to which it can grow later. +A table is an array of opaque values of a particular :ref:`reference type `. +Moreover, each table slot is initialized with the |TINIT| value given by a :ref:`constant ` initializer :ref:`expression `. +Tables can further be initialized through :ref:`element segments `. -Tables can be initialized through :ref:`element segments `. +The |LMIN| size in the :ref:`limits ` of the table type specifies the initial size of that table, while its |LMAX|, if present, restricts the size to which it can grow later. Tables are referenced through :ref:`table indices `, starting with the smallest index not referencing a table :ref:`import `. @@ -192,7 +197,7 @@ Memories The |MMEMS| component of a module defines a vector of *linear memories* (or *memories* for short) as described by their :ref:`memory type `: .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{memory} & \mem &::=& \{ \MTYPE~\memtype \} \\ \end{array} @@ -207,11 +212,6 @@ Memories are referenced through :ref:`memory indices `, starting with the smallest index not referencing a memory :ref:`import `. Most constructs implicitly reference memory index :math:`0`. -.. note:: - In the current version of WebAssembly, at most one memory may be defined or imported in a single module, - and *all* constructs implicitly reference this memory :math:`0`. - This restriction may be lifted in future versions. - .. index:: ! global, global index, global type, mutability, expression, constant, value, import pair: abstract syntax; global @@ -223,7 +223,7 @@ Globals The |MGLOBALS| component of a module defines a vector of *global variables* (or *globals* for short): .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{global} & \global &::=& \{ \GTYPE~\globaltype, \GINIT~\expr \} \\ \end{array} @@ -259,7 +259,7 @@ An active element segment copies its elements into a table during :ref:`instanti A declarative element segment is not available at runtime but merely serves to forward-declare references that are formed in code with instructions like :math:`\REFFUNC`. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{element segment} & \elem &::=& \{ \ETYPE~\reftype, \EINIT~\vec(\expr), \EMODE~\elemmode \} \\ \production{element segment mode} & \elemmode &::=& @@ -292,7 +292,7 @@ A passive data segment's contents can be copied into a memory using the |MEMORYI An active data segment copies its contents into a memory during :ref:`instantiation `, as specified by a :ref:`memory index ` and a :ref:`constant ` :ref:`expression ` defining an offset into that memory. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{data segment} & \data &::=& \{ \DINIT~\vec(\byte), \DMODE~\datamode \} \\ \production{data segment mode} & \datamode &::=& @@ -317,7 +317,7 @@ Start Function The |MSTART| component of a module declares the :ref:`function index ` of a *start function* that is automatically invoked when the module is :ref:`instantiated `, after :ref:`tables ` and :ref:`memories ` have been initialized. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{start function} & \start &::=& \{ \SFUNC~\funcidx \} \\ \end{array} @@ -386,7 +386,7 @@ Imports The |MIMPORTS| component of a module defines a set of *imports* that are required for :ref:`instantiation `. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{import} & \import &::=& \{ \IMODULE~\name, \INAME~\name, \IDESC~\importdesc \} \\ \production{import description} & \importdesc &::=& diff --git a/document/core/syntax/types.rst b/document/core/syntax/types.rst index 2704508237..406eebde7c 100644 --- a/document/core/syntax/types.rst +++ b/document/core/syntax/types.rst @@ -20,7 +20,7 @@ Number Types *Number types* classify numeric values. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{number type} & \numtype &::=& \I32 ~|~ \I64 ~|~ \F32 ~|~ \F64 \\ \end{array} @@ -34,7 +34,8 @@ They correspond to the respective binary floating-point representations, also kn Number types are *transparent*, meaning that their bit patterns can be observed. Values of number type can be stored in :ref:`memories `. -.. _bitwidth: +.. _bitwidth-numtype: +.. _bitwidth-valtype: Conventions ........... @@ -54,7 +55,7 @@ Vector Types *Vector types* classify vectors of :ref:`numeric ` values processed by vector instructions (also known as *SIMD* instructions, single instruction multiple data). .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{vector type} & \vectype &::=& \V128 \\ \end{array} @@ -66,35 +67,131 @@ values, or a single 128 bit type. The interpretation is determined by individual Vector types, like :ref:`number types ` are *transparent*, meaning that their bit patterns can be observed. Values of vector type can be stored in :ref:`memories `. +.. _bitwidth-vectype: + Conventions ........... -* The notation :math:`|t|` for :ref:`bit width ` extends to vector types as well, that is, :math:`|\V128| = 128`. +* The notation :math:`|t|` for :ref:`bit width ` extends to vector types as well, that is, :math:`|\V128| = 128`. + + + +.. index:: ! heap type, store, type index, ! abstract type, ! concrete type, ! unboxed scalar + pair: abstract syntax; heap type +.. _type-abstract: +.. _type-concrete: +.. _syntax-i31: +.. _syntax-heaptype: +.. _syntax-absheaptype: + +Heap Types +~~~~~~~~~~ + +*Heap types* classify objects in the runtime :ref:`store `. +There are three disjoint hierarchies of heap types: + +- *function types* classify :ref:`functions `, +- *aggregate types* classify dynamically allocated *managed* data, such as *structures*, *arrays*, or *unboxed scalars*, +- *external types* classify *external* references possibly owned by the :ref:`embedder `. + +The values from the latter two hierarchies are interconvertible by ways of the |EXTERNCONVERTANY| and |ANYCONVERTEXTERN| instructions. +That is, both type hierarchies are inhabited by an isomorphic set of values, but may have different, incompatible representations in practice. + +.. math:: + \begin{array}{llrl} + \production{abstract heap type} & \absheaptype &::=& + \FUNC ~|~ \NOFUNC \\&&|& + \EXTERN ~|~ \NOEXTERN \\&&|& + \ANY ~|~ \EQT ~|~ \I31 ~|~ \STRUCT ~|~ \ARRAY ~|~ \NONE \\ + \production{heap type} & \heaptype &::=& + \absheaptype ~|~ \typeidx \\ + \end{array} + +A heap type is either *abstract* or *concrete*. + +The abstract type |FUNC| denotes the common supertype of all :ref:`function types `, regardless of their concrete definition. +Dually, the type |NOFUNC| denotes the common subtype of all :ref:`function types `, regardless of their concrete definition. +This type has no values. + +The abstract type |EXTERN| denotes the common supertype of all external references received through the :ref:`embedder `. +This type has no concrete subtypes. +Dually, the type |NOEXTERN| denotes the common subtype of all forms of external references. +This type has no values. + +The abstract type |ANY| denotes the common supertype of all aggregate types, as well as possibly abstract values produced by *internalizing* an external reference of type |EXTERN|. +Dually, the type |NONE| denotes the common subtype of all forms of aggregate types. +This type has no values. + +The abstract type |EQT| is a subtype of |ANY| that includes all types for which references can be compared, i.e., aggregate values and |I31|. + +The abstract types |STRUCT| and |ARRAY| denote the common supertypes of all :ref:`structure ` and :ref:`array ` aggregates, respectively. + +The abstract type |I31| denotes *unboxed scalars*, that is, integers injected into references. +Their observable value range is limited to 31 bits. + +.. note:: + An |I31| is not actually allocated in the store, + but represented in a way that allows them to be mixed with actual references into the store without ambiguity. + Engines need to perform some form of *pointer tagging* to achieve this, + which is why 1 bit is reserved. + + Although the types |NONE|, |NOFUNC|, and |NOEXTERN| are not inhabited by any values, + they can be used to form the types of all null :ref:`references ` in their respective hierarchy. + For example, :math:`(\REF~\NULL~\NOFUNC)` is the generic type of a null reference compatible with all function reference types. + +A concrete heap type consists of a :ref:`type index ` and classifies an object of the respective :ref:`type ` defined in a module. + +The syntax of heap types is :ref:`extended ` with additional forms for the purpose of specifying :ref:`validation ` and :ref:`execution `. -.. index:: ! reference type, reference, table, function, function type, null +.. index:: ! reference type, heap type, reference, table, function, function type, null pair: abstract syntax; reference type pair: reference; type .. _syntax-reftype: +.. _syntax-nullable: Reference Types ~~~~~~~~~~~~~~~ -*Reference types* classify first-class references to objects in the runtime :ref:`store `. +*Reference types* classify :ref:`values ` that are first-class references to objects in the runtime :ref:`store `. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{reference type} & \reftype &::=& - \FUNCREF ~|~ \EXTERNREF \\ + \REF~\NULL^?~\heaptype \\ \end{array} -The type |FUNCREF| denotes the infinite union of all references to :ref:`functions `, regardless of their :ref:`function types `. +A reference type is characterised by the :ref:`heap type ` it points to. -The type |EXTERNREF| denotes the infinite union of all references to objects owned by the :ref:`embedder ` and that can be passed into WebAssembly under this type. +In addition, a reference type of the form :math:`\REF~\NULL~\X{ht}` is *nullable*, meaning that it can either be a proper reference to :math:`\X{ht}` or :ref:`null `. +Other references are *non-null*. Reference types are *opaque*, meaning that neither their size nor their bit pattern can be observed. Values of reference type can be stored in :ref:`tables `. +Conventions +........... + +* The reference type |ANYREF| is an abbreviation for :math:`\REF~\NULL~\ANY`. + +* The reference type |EQREF| is an abbreviation for :math:`\REF~\NULL~\EQT`. + +* The reference type |I31REF| is an abbreviation for :math:`\REF~\NULL~\I31`. + +* The reference type |STRUCTREF| is an abbreviation for :math:`\REF~\NULL~\STRUCT`. + +* The reference type |ARRAYREF| is an abbreviation for :math:`\REF~\NULL~\ARRAY`. + +* The reference type |FUNCREF| is an abbreviation for :math:`\REF~\NULL~\FUNC`. + +* The reference type |EXTERNREF| is an abbreviation for :math:`\REF~\NULL~\EXTERN`. + +* The reference type |NULLREF| is an abbreviation for :math:`\REF~\NULL~\NONE`. + +* The reference type |NULLFUNCREF| is an abbreviation for :math:`\REF~\NULL~\NOFUNC`. + +* The reference type |NULLEXTERNREF| is an abbreviation for :math:`\REF~\NULL~\NOEXTERN`. + .. index:: ! value type, number type, vector type, reference type pair: abstract syntax; value type @@ -108,11 +205,13 @@ Value Types They are either :ref:`number types `, :ref:`vector types `, or :ref:`reference types `. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{value type} & \valtype &::=& \numtype ~|~ \vectype ~|~ \reftype \\ \end{array} +The syntax of value types is :ref:`extended ` with additional forms for the purpose of specifying :ref:`validation `. + Conventions ........... @@ -131,7 +230,7 @@ Result Types which is a sequence of values, written with brackets. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{result type} & \resulttype &::=& [\vec(\valtype)] \\ \end{array} @@ -150,12 +249,106 @@ mapping a vector of parameters to a vector of results. They are also used to classify the inputs and outputs of :ref:`instructions `. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{function type} & \functype &::=& - \resulttype \to \resulttype \\ + \resulttype \toF \resulttype \\ \end{array} +.. index:: ! aggregate type, ! structure type, ! array type, ! field type, ! storage type, ! packed type, bit width + pair: abstract syntax; structure type + pair: abstract syntax; array type + pair: abstract syntax; field type + pair: abstract syntax; storage type + pair: abstract syntax; packed type +.. _syntax-aggrtype: +.. _syntax-structtype: +.. _syntax-arraytype: +.. _syntax-fieldtype: +.. _syntax-storagetype: +.. _syntax-packedtype: + +Aggregate Types +~~~~~~~~~~~~~~~ + +*Aggregate types* describe compound objects consisting of multiple values. +These are either *structures* or *arrays*, +which both consist of a list of possibly mutable and possibly packed *fields*. +Structures are heterogeneous, but require static indexing, while arrays need to be homogeneous, but allow dynamic indexing. + +.. math:: + \begin{array}{llrl} + \production{structure type} & \structtype &::=& + \fieldtype^\ast \\ + \production{array type} & \arraytype &::=& + \fieldtype \\ + \production{field type} & \fieldtype &::=& + \mut~\storagetype \\ + \production{storage type} & \storagetype &::=& + \valtype ~|~ \packedtype \\ + \production{packed type} & \packedtype &::=& + \I8 ~|~ \I16 \\ + \end{array} + +.. _bitwidth-fieldtype: +.. _aux-unpacktype: + +Conventions +........... + +* The notation :math:`|t|` for :ref:`bit width ` extends to packed types as well, that is, :math:`|\I8| = 8` and :math:`|\I16| = 16`. + +* The auxiliary function :math:`\unpacktype` maps a storage type to the :ref:`value type ` obtained when accessing a field: + + .. math:: + \begin{array}{lll} + \unpacktype(\valtype) &=& \valtype \\ + \unpacktype(\packedtype) &=& \I32 \\ + \end{array} + + +.. index:: ! composite type, function type, aggreagate type, structure type, array type + pair: abstract syntax; composite type +.. _syntax-comptype: + +Composite Types +~~~~~~~~~~~~~~~ + +*Composite types* are all types composed from simpler types, +including :ref:`function types ` and :ref:`aggregate types `. + +.. math:: + \begin{array}{llrl} + \production{composite type} & \comptype &::=& + \TFUNC~\functype ~|~ \TSTRUCT~\structtype ~|~ \TARRAY~\arraytype \\ + \end{array} + + +.. index:: ! recursive type, ! sub type, composite type, ! final, subtyping, ! roll, ! unroll, recursive type index + pair: abstract syntax; recursive type + pair: abstract syntax; sub type +.. _syntax-rectype: +.. _syntax-subtype: + +Recursive Types +~~~~~~~~~~~~~~~ + +*Recursive types* denote a group of mutually recursive :ref:`composite types `, each of which can optionally declare a list of :ref:`type indices ` of supertypes that it :ref:`matches `. +Each type can also be declared *final*, preventing further subtyping. + +.. math:: + \begin{array}{llrl} + \production{recursive type} & \rectype &::=& + \TREC~\subtype^\ast \\ + \production{sub types} & \subtype &::=& + \TSUB~\TFINAL^?~\typeidx^\ast~\comptype \\ + \end{array} + +In a :ref:`module `, each member of a recursive type is assigned a separate :ref:`type index `. + +The syntax of sub types is :ref:`generalized ` for the purpose of specifying :ref:`validation ` and :ref:`execution `. + + .. index:: ! limits, memory type, table type pair: abstract syntax; limits single: memory; limits @@ -168,7 +361,7 @@ Limits *Limits* classify the size range of resizeable storage associated with :ref:`memory types ` and :ref:`table types `. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{limits} & \limits &::=& \{ \LMIN~\u32, \LMAX~\u32^? \} \\ \end{array} @@ -188,7 +381,7 @@ Memory Types *Memory types* classify linear :ref:`memories ` and their size range. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{memory type} & \memtype &::=& \limits \\ \end{array} @@ -209,7 +402,7 @@ Table Types *Table types* classify :ref:`tables ` over elements of :ref:`reference type ` within a size range. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{table type} & \tabletype &::=& \limits~\reftype \\ \end{array} @@ -217,9 +410,6 @@ Table Types Like memories, tables are constrained by limits for their minimum and optionally maximum size. The limits are given in numbers of entries. -.. note:: - In future versions of WebAssembly, additional element types may be introduced. - .. index:: ! global type, ! mutability, value type, global, mutability pair: abstract syntax; global type @@ -235,7 +425,7 @@ Global Types *Global types* classify :ref:`global ` variables, which hold a value and can either be mutable or immutable. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{global type} & \globaltype &::=& \mut~\valtype \\ \production{mutability} & \mut &::=& @@ -244,7 +434,7 @@ Global Types \end{array} -.. index:: ! external type, function type, table type, memory type, global type, import, external value +.. index:: ! external type, defined type, function type, table type, memory type, global type, import, external value pair: abstract syntax; external type pair: external; type .. _syntax-externtype: @@ -255,9 +445,9 @@ External Types *External types* classify :ref:`imports ` and :ref:`external values ` with their respective types. .. math:: - \begin{array}{llll} + \begin{array}{llrl} \production{external types} & \externtype &::=& - \ETFUNC~\functype ~|~ + \ETFUNC~\deftype ~|~ \ETTABLE~\tabletype ~|~ \ETMEM~\memtype ~|~ \ETGLOBAL~\globaltype \\ @@ -270,7 +460,7 @@ Conventions The following auxiliary notation is defined for sequences of external types. It filters out entries of a specific kind in an order-preserving fashion: -* :math:`\etfuncs(\externtype^\ast) = [\functype ~|~ (\ETFUNC~\functype) \in \externtype^\ast]` +* :math:`\etfuncs(\externtype^\ast) = [\deftype ~|~ (\ETFUNC~\deftype) \in \externtype^\ast]` * :math:`\ettables(\externtype^\ast) = [\tabletype ~|~ (\ETTABLE~\tabletype) \in \externtype^\ast]` diff --git a/document/core/syntax/values.rst b/document/core/syntax/values.rst index 72ff7d3a06..42482ed110 100644 --- a/document/core/syntax/values.rst +++ b/document/core/syntax/values.rst @@ -103,7 +103,7 @@ NaN values have a *payload* that describes the mantissa bits in the underlying : No distinction is made between signalling and quiet NaNs. .. math:: - \begin{array}{llcll} + \begin{array}{llrll} \production{floating-point value} & \fN &::=& {+} \fNmag ~|~ {-} \fNmag \\ \production{floating-point magnitude} & \fNmag &::=& @@ -170,7 +170,7 @@ Names *Names* are sequences of *characters*, which are *scalar values* as defined by |Unicode|_ (Section 2.4). .. math:: - \begin{array}{llclll} + \begin{array}{llrlll} \production{name} & \name &::=& \char^\ast \qquad\qquad (\iff |\utf8(\char^\ast)| < 2^{32}) \\ \production{character} & \char &::=& diff --git a/document/core/text/conventions.rst b/document/core/text/conventions.rst index 1efc88b036..3a324af15d 100644 --- a/document/core/text/conventions.rst +++ b/document/core/text/conventions.rst @@ -129,14 +129,18 @@ It is convenient to define identifier contexts as :ref:`records ` assigned to the defined indices. Unnamed indices are associated with empty (:math:`\epsilon`) entries in these lists. +Fields have *dependent* name spaces, and hence a separate list of field identifiers per type. An identifier context is *well-formed* if no index space contains duplicate identifiers. +For fields, names need only be unique within a single type. + Conventions diff --git a/document/core/text/instructions.rst b/document/core/text/instructions.rst index 4592953613..dcc82abb9e 100644 --- a/document/core/text/instructions.rst +++ b/document/core/text/instructions.rst @@ -48,7 +48,7 @@ The following grammar handles the corresponding update to the :ref:`identifier c then it is shadowed and the earlier label becomes inaccessible. -.. index:: control instructions, structured control, label, block, branch, result type, label index, function index, type index, vector, polymorphism +.. index:: control instructions, structured control, label, block, branch, result type, label index, function index, type index, vector, polymorphism, reference pair: text format; instruction .. _text-blockinstr: .. _text-plaininstr: @@ -75,7 +75,7 @@ However, the special case of a type use that is syntactically empty or consists \begin{array}[t]{@{}c@{}} ::= \\ | \\ \end{array} & \begin{array}[t]{@{}lcll@{}} - (t{:}\Tresult)^? &\Rightarrow& t^? \\ + (t{:}\Tresult_I)^? &\Rightarrow& t^? \\ x,I'{:}\Ttypeuse_I &\Rightarrow& x & (\iff I' = \{\ILOCALS~(\epsilon)^\ast\}) \\ \end{array} \\ \production{block instruction} & \Tblockinstr_I &::=& @@ -100,9 +100,16 @@ However, the special case of a type use that is syntactically empty or consists .. _text-br: .. _text-br_if: .. _text-br_table: +.. _text-br_on_null: +.. _text-br_on_non_null: +.. _text-br_on_cast: +.. _text-br_on_cast_fail: .. _text-return: .. _text-call: +.. _text-call_ref: .. _text-call_indirect: +.. _text-return_call: +.. _text-return_call_indirect: All other control instruction are represented verbatim. @@ -115,9 +122,18 @@ All other control instruction are represented verbatim. \text{br\_if}~~l{:}\Tlabelidx_I &\Rightarrow& \BRIF~l \\ &&|& \text{br\_table}~~l^\ast{:}\Tvec(\Tlabelidx_I)~~l_N{:}\Tlabelidx_I &\Rightarrow& \BRTABLE~l^\ast~l_N \\ &&|& + \text{br\_on\_null}~~l{:}\Tlabelidx_I &\Rightarrow& \BRONNULL~l \\ &&|& + \text{br\_on\_non\_null}~~l{:}\Tlabelidx_I &\Rightarrow& \BRONNONNULL~l \\ &&|& + \text{br\_on\_cast}~~l{:}\Tlabelidx_I~~t_1{:}\Treftype~~t_2{:}\Treftype &\Rightarrow& \BRONCAST~l~t_1~t_2 \\ &&|& + \text{br\_on\_cast\_fail}~~l{:}\Tlabelidx_I~~t_1{:}\Treftype~~t_2{:}\Treftype &\Rightarrow& \BRONCASTFAIL~l~t_1~t_2 \\ &&|& \text{return} &\Rightarrow& \RETURN \\ &&|& \text{call}~~x{:}\Tfuncidx_I &\Rightarrow& \CALL~x \\ &&|& + \text{call\_ref}~~x{:}\Ttypeidx &\Rightarrow& \CALLREF~x \\ &&|& \text{call\_indirect}~~x{:}\Ttableidx~~y,I'{:}\Ttypeuse_I &\Rightarrow& \CALLINDIRECT~x~y + & (\iff I' = \{\ILOCALS~(\epsilon)^\ast\}) \\&&|& + \text{return\_call}~~x{:}\Tfuncidx_I &\Rightarrow& \RETURNCALL~x \\ &&|& + \text{return\_call\_ref}~~x{:}\Ttypeidx &\Rightarrow& \RETURNCALLREF~x \\ &&|& + \text{return\_call\_indirect}~~x{:}\Ttableidx~~y,I'{:}\Ttypeuse_I &\Rightarrow& \RETURNCALLINDIRECT~x~y & (\iff I' = \{\ILOCALS~(\epsilon)^\ast\}) \\ \end{array} @@ -133,19 +149,22 @@ The :math:`\text{else}` keyword of an :math:`\text{if}` instruction can be omitt .. math:: \begin{array}{llclll} \production{block instruction} & - \text{if}~~\Tlabel~~\Tblocktype~~\Tinstr^\ast~~\text{end} + \text{if}~~\Tlabel~~\Tblocktype_I~~\Tinstr^\ast~~\text{end} &\equiv& - \text{if}~~\Tlabel~~\Tblocktype~~\Tinstr^\ast~~\text{else}~~\text{end} + \text{if}~~\Tlabel~~\Tblocktype_I~~\Tinstr^\ast~~\text{else}~~\text{end} \end{array} -Also, for backwards compatibility, the table index to :math:`\text{call\_indirect}` can be omitted, defaulting to :math:`0`. +Also, for backwards compatibility, the table index to :math:`\text{call\_indirect}` and :math:`\text{return\_call\_indirect}` can be omitted, defaulting to :math:`0`. .. math:: \begin{array}{llclll} \production{plain instruction} & \text{call\_indirect}~~\Ttypeuse &\equiv& - \text{call\_indirect}~~0~~\Ttypeuse + \text{call\_indirect}~~0~~\Ttypeuse \\ + \text{return\_call\_indirect}~~\Ttypeuse + &\equiv& + \text{return\_call\_indirect}~~0~~\Ttypeuse \\ \end{array} @@ -157,15 +176,72 @@ Reference Instructions ~~~~~~~~~~~~~~~~~~~~~~ .. _text-ref.null: -.. _text-ref.is_null: .. _text-ref.func: +.. _text-ref.is_null: +.. _text-ref.as_non_null: +.. _text-struct.new: +.. _text-struct.new_default: +.. _text-struct.get: +.. _text-struct.get_s: +.. _text-struct.get_u: +.. _text-struct.set: +.. _text-array.new: +.. _text-array.new_default: +.. _text-array.new_fixed: +.. _text-array.new_elem: +.. _text-array.new_data: +.. _text-array.get: +.. _text-array.get_s: +.. _text-array.get_u: +.. _text-array.set: +.. _text-array.len: +.. _text-array.fill: +.. _text-array.copy: +.. _text-array.init_data: +.. _text-array.init_elem: +.. _text-ref.i31: +.. _text-i31.get_s: +.. _text-i31.get_u: +.. _text-ref.test: +.. _text-ref.cast: +.. _text-any.convert_extern: +.. _text-extern.convert_any: .. math:: \begin{array}{llclll} \production{instruction} & \Tplaininstr_I &::=& \dots \\ &&|& \text{ref.null}~~t{:}\Theaptype &\Rightarrow& \REFNULL~t \\ &&|& + \text{ref.func}~~x{:}\Tfuncidx &\Rightarrow& \REFFUNC~x \\ &&|& \text{ref.is\_null} &\Rightarrow& \REFISNULL \\ &&|& - \text{ref.func}~~x{:}\Tfuncidx &\Rightarrow& \REFFUNC~x \\ + \text{ref.as\_non\_null} &\Rightarrow& \REFASNONNULL \\ &&|& + \text{ref.eq} &\Rightarrow& \REFEQ \\ &&|& + \text{ref.test}~~t{:}\Treftype &\Rightarrow& \REFTEST~t \\ &&|& + \text{ref.cast}~~t{:}\Treftype &\Rightarrow& \REFCAST~t \\ &&|& + \text{struct.new}~~x{:}\Ttypeidx_I &\Rightarrow& \STRUCTNEW~x \\ &&|& + \text{struct.new\_default}~~x{:}\Ttypeidx_I &\Rightarrow& \STRUCTNEWDEFAULT~x \\ &&|& + \text{struct.get}~~x{:}\Ttypeidx_I~~y{:}\Tfieldidx_{I,x} &\Rightarrow& \STRUCTGET~x~y \\ &&|& + \text{struct.get\_u}~~x{:}\Ttypeidx_I~~y{:}\Tfieldidx_{I,x} &\Rightarrow& \STRUCTGETU~x~y \\ &&|& + \text{struct.get\_s}~~x{:}\Ttypeidx_I~~y{:}\Tfieldidx_{I,x} &\Rightarrow& \STRUCTGETS~x~y \\ &&|& + \text{struct.set}~~x{:}\Ttypeidx_I~~y{:}\Tfieldidx_{I,x} &\Rightarrow& \STRUCTSET~x~y \\ &&|& + \text{array.new}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYNEW~x \\ &&|& + \text{array.new\_default}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYNEWDEFAULT~x \\ &&|& + \text{array.new\_fixed}~~x{:}\Ttypeidx_I~~n{:}\Tu32 &\Rightarrow& \ARRAYNEWFIXED~x~n \\ &&|& + \text{array.new\_data}~~x{:}\Ttypeidx_I~~y{:}\Tdataidx_I &\Rightarrow& \ARRAYNEWDATA~x~y \\ &&|& + \text{array.new\_elem}~~x{:}\Ttypeidx_I~~y{:}\Telemidx_I &\Rightarrow& \ARRAYNEWELEM~x~y \\ &&|& + \text{array.get}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYGET~x \\ &&|& + \text{array.get\_u}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYGETU~x \\ &&|& + \text{array.get\_s}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYGETS~x \\ &&|& + \text{array.set}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYSET~x \\ &&|& + \text{array.len} &\Rightarrow& \ARRAYLEN \\ &&|& + \text{array.fill}~~x{:}\Ttypeidx_I &\Rightarrow& \ARRAYFILL~x \\ &&|& + \text{array.copy}~~x{:}\Ttypeidx_I~~y{:}\Ttypeidx_I &\Rightarrow& \ARRAYCOPY~x~y \\ &&|& + \text{array.init\_data}~~x{:}\Ttypeidx_I~~y{:}\Tdataidx_I &\Rightarrow& \ARRAYINITDATA~x~y \\ &&|& + \text{array.init\_elem}~~x{:}\Ttypeidx_I~~y{:}\Telemidx_I &\Rightarrow& \ARRAYINITELEM~x~y \\ &&|& + \text{ref.i31} &\Rightarrow& \REFI31 \\ &&|& + \text{i31.get\_u} &\Rightarrow& \I31GETU \\ &&|& + \text{i31.get\_s} &\Rightarrow& \I31GETS \\ &&|& + \text{any.convert\_extern} &\Rightarrow& \ANYCONVERTEXTERN \\ &&|& + \text{extern.convert\_any} &\Rightarrow& \EXTERNCONVERTANY \\ \end{array} @@ -183,7 +259,7 @@ Parametric Instructions \begin{array}{llclll} \production{instruction} & \Tplaininstr_I &::=& \dots \\ &&|& \text{drop} &\Rightarrow& \DROP \\ &&|& - \text{select}~((t{:}\Tresult)^\ast)^? &\Rightarrow& \SELECT~(t^\ast)^? \\ + \text{select}~((t{:}\Tresult_I)^\ast)^? &\Rightarrow& \SELECT~(t^\ast)^? \\ \end{array} @@ -292,39 +368,120 @@ Lexically, an |Toffset| or |Talign| phrase is considered a single :ref:`keyword \production{memory alignment} & \Talign_N &::=& \text{align{=}}a{:}\Tu32 &\Rightarrow& a \\ &&|& \epsilon &\Rightarrow& N \\ - \production{instruction} & \Tplaininstr_I &::=& \dots \\ &&|& - \text{i32.load}~~m{:}\Tmemarg_4 &\Rightarrow& \I32.\LOAD~m \\ &&|& - \text{i64.load}~~m{:}\Tmemarg_8 &\Rightarrow& \I64.\LOAD~m \\ &&|& - \text{f32.load}~~m{:}\Tmemarg_4 &\Rightarrow& \F32.\LOAD~m \\ &&|& - \text{f64.load}~~m{:}\Tmemarg_8 &\Rightarrow& \F64.\LOAD~m \\ &&|& - \text{i32.load8\_s}~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\LOAD\K{8\_s}~m \\ &&|& - \text{i32.load8\_u}~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\LOAD\K{8\_u}~m \\ &&|& - \text{i32.load16\_s}~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\LOAD\K{16\_s}~m \\ &&|& - \text{i32.load16\_u}~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\LOAD\K{16\_u}~m \\ &&|& - \text{i64.load8\_s}~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\LOAD\K{8\_s}~m \\ &&|& - \text{i64.load8\_u}~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\LOAD\K{8\_u}~m \\ &&|& - \text{i64.load16\_s}~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\LOAD\K{16\_s}~m \\ &&|& - \text{i64.load16\_u}~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\LOAD\K{16\_u}~m \\ &&|& - \text{i64.load32\_s}~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\LOAD\K{32\_s}~m \\ &&|& - \text{i64.load32\_u}~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\LOAD\K{32\_u}~m \\ &&|& - \text{i32.store}~~m{:}\Tmemarg_4 &\Rightarrow& \I32.\STORE~m \\ &&|& - \text{i64.store}~~m{:}\Tmemarg_8 &\Rightarrow& \I64.\STORE~m \\ &&|& - \text{f32.store}~~m{:}\Tmemarg_4 &\Rightarrow& \F32.\STORE~m \\ &&|& - \text{f64.store}~~m{:}\Tmemarg_8 &\Rightarrow& \F64.\STORE~m \\ &&|& - \text{i32.store8}~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\STORE\K{8}~m \\ &&|& - \text{i32.store16}~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\STORE\K{16}~m \\ &&|& - \text{i64.store8}~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\STORE\K{8}~m \\ &&|& - \text{i64.store16}~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\STORE\K{16}~m \\ &&|& - \text{i64.store32}~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\STORE\K{32}~m \\ &&|& - \text{memory.size} &\Rightarrow& \MEMORYSIZE \\ &&|& - \text{memory.grow} &\Rightarrow& \MEMORYGROW \\ &&|& - \text{memory.fill} &\Rightarrow& \MEMORYFILL \\ &&|& - \text{memory.copy} &\Rightarrow& \MEMORYCOPY \\ &&|& - \text{memory.init}~~x{:}\Tdataidx_I &\Rightarrow& \MEMORYINIT~x \\ &&|& + \production{instruction} & \Tplaininstr_I &::=& \dots \phantom{averylonginstructionnameforvectext} && \phantom{vechasreallylonginstructionnames} \\ &&|& + \text{i32.load}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \I32.\LOAD~x~m \\ &&|& + \text{i64.load}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \I64.\LOAD~x~m \\ &&|& + \text{f32.load}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \F32.\LOAD~x~m \\ &&|& + \text{f64.load}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \F64.\LOAD~x~m \\ &&|& + \text{v128.load}~~x{:}\Tmemidx~~m{:}\Tmemarg_{16} &\Rightarrow& \V128.\LOAD~x~m \\ &&|& + \text{i32.load8\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\LOAD\K{8\_s}~x~m \\ &&|& + \text{i32.load8\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\LOAD\K{8\_u}~x~m \\ &&|& + \text{i32.load16\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\LOAD\K{16\_s}~x~m \\ &&|& + \text{i32.load16\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\LOAD\K{16\_u}~x~m \\ &&|& + \text{i64.load8\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\LOAD\K{8\_s}~x~m \\ &&|& + \text{i64.load8\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\LOAD\K{8\_u}~x~m \\ &&|& + \text{i64.load16\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\LOAD\K{16\_s}~x~m \\ &&|& + \text{i64.load16\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\LOAD\K{16\_u}~x~m \\ &&|& + \text{i64.load32\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\LOAD\K{32\_s}~x~m \\ &&|& + \text{i64.load32\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\LOAD\K{32\_u}~x~m \\ &&|& + \text{v128.load8x8\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{8x8\_s}~x~m \\ &&|& + \text{v128.load8x8\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{8x8\_u}~x~m \\ &&|& + \text{v128.load16x4\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{16x4\_s}~x~m \\ &&|& + \text{v128.load16x4\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{16x4\_u}~x~m \\ &&|& + \text{v128.load32x2\_s}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{32x2\_s}~x~m \\ &&|& + \text{v128.load32x2\_u}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{32x2\_u}~x~m \\ &&|& + \text{v128.load8\_splat}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \V128.\LOAD\K{8\_splat}~x~m \\ &&|& + \text{v128.load16\_splat}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \V128.\LOAD\K{16\_splat}~x~m \\ &&|& + \text{v128.load32\_splat}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \V128.\LOAD\K{32\_splat}~x~m \\ &&|& + \text{v128.load64\_splat}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{64\_splat}~x~m \\ &&|& + \text{v128.load32\_zero}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \V128.\LOAD\K{32\_zero}~x~m \\ &&|& + \text{v128.load64\_zero}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{64\_zero}~x~m \\ &&|& + \text{v128.load8\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_1~~y{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{8\_lane}~x~m~y \\ &&|& + \text{v128.load16\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_2~~y{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{16\_lane}~x~m~y \\ &&|& + \text{v128.load32\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_4~~y{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{32\_lane}~x~m~y \\ &&|& + \text{v128.load64\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_8~~y{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{64\_lane}~x~m~y \\ &&|& + \text{i32.store}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \I32.\STORE~x~m \\ &&|& + \text{i64.store}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \I64.\STORE~x~m \\ &&|& + \text{f32.store}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \F32.\STORE~x~m \\ &&|& + \text{f64.store}~~x{:}\Tmemidx~~m{:}\Tmemarg_8 &\Rightarrow& \F64.\STORE~x~m \\ &&|& + \text{v128.store}~~x{:}\Tmemidx~~m{:}\Tmemarg_{16} &\Rightarrow& \V128.\STORE~x~m \\ &&|& + \text{i32.store8}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I32.\STORE\K{8}~x~m \\ &&|& + \text{i32.store16}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I32.\STORE\K{16}~x~m \\ &&|& + \text{i64.store8}~~x{:}\Tmemidx~~m{:}\Tmemarg_1 &\Rightarrow& \I64.\STORE\K{8}~x~m \\ &&|& + \text{i64.store16}~~x{:}\Tmemidx~~m{:}\Tmemarg_2 &\Rightarrow& \I64.\STORE\K{16}~x~m \\ &&|& + \text{i64.store32}~~x{:}\Tmemidx~~m{:}\Tmemarg_4 &\Rightarrow& \I64.\STORE\K{32}~x~m \\ &&|& + \text{v128.store8\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_1~~y{:}\Tu8 &\Rightarrow& \V128.\STORE\K{8\_lane}~x~m~y \\ &&|& + \text{v128.store16\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_2~~y{:}\Tu8 &\Rightarrow& \V128.\STORE\K{16\_lane}~x~m~y \\ &&|& + \text{v128.store32\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_4~~y{:}\Tu8 &\Rightarrow& \V128.\STORE\K{32\_lane}~x~m~y \\ &&|& + \text{v128.store64\_lane}~~x{:}\Tmemidx~~m{:}\Tmemarg_8~~y{:}\Tu8 &\Rightarrow& \V128.\STORE\K{64\_lane}~x~m~y \\ + \text{memory.size}~~x{:}\Tmemidx &\Rightarrow& \MEMORYSIZE~x \\ &&|& + \text{memory.grow}~~x{:}\Tmemidx &\Rightarrow& \MEMORYGROW~x \\ &&|& + \text{memory.fill}~~x{:}\Tmemidx &\Rightarrow& \MEMORYFILL~x \\ &&|& + \text{memory.copy}~~x{:}\Tmemidx~~y{:}\Tmemidx &\Rightarrow& \MEMORYCOPY~x~y \\ &&|& + \text{memory.init}~~x{:}\Tmemidx~~y{:}\Tdataidx_I &\Rightarrow& \MEMORYINIT~x~y \\ &&|& \text{data.drop}~~x{:}\Tdataidx_I &\Rightarrow& \DATADROP~x \\ \end{array} +Abbreviations +............. + +As an abbreviation, the memory index can be omitted in all memory instructions, defaulting to :math:`\T{0}`. + +.. math:: + \begin{array}{llclll} + \production{instruction} & + \Tnumtype\text{.load}~~\Tmemarg + &\equiv& + \Tnumtype\text{.load}~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.load}~~\Tmemarg + &\equiv& + \Tvectype\text{.load}~~\text{0}~~\Tmemarg \\& + \Tnumtype\text{.load}N\text{\_}\sx~~\Tmemarg + &\equiv& + \Tnumtype\text{.load}N\text{\_}\sx~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.load}{N}\K{x}M\text{\_}\sx~~\Tmemarg + &\equiv& + \Tvectype\text{.load}{N}\K{x}M\text{\_}\sx~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.load}N\text{\_splat}~~\Tmemarg + &\equiv& + \Tvectype\text{.load}N\text{\_splat}~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.load}N\text{\_zero}~~\Tmemarg + &\equiv& + \Tvectype\text{.load}N\text{\_zero}~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.load}N\text{\_lane}~~\Tmemarg~~\Tu8 + &\equiv& + \Tvectype\text{.load}N\text{\_lane}~~\text{0}~~\Tmemarg~~\Tu8 \\& + \Tnumtype\text{.store}~~\Tmemarg + &\equiv& + \Tnumtype\text{.store}~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.store}~~\Tmemarg + &\equiv& + \Tvectype\text{.store}~~\text{0}~~\Tmemarg \\& + \Tnumtype\text{.store}N~~\Tmemarg + &\equiv& + \Tnumtype\text{.store}N~~\text{0}~~\Tmemarg \\& + \Tvectype\text{.store}N\text{\_lane}~~\Tmemarg~~\Tu8 + &\equiv& + \Tvectype\text{.store}N\text{\_lane}~~\text{0}~~\Tmemarg~~\Tu8 \\& + \text{memory.size} + &\equiv& + \text{memory.size}~~\text{0} \\& + \text{memory.grow} + &\equiv& + \text{memory.grow}~~\text{0} \\& + \text{memory.fill} + &\equiv& + \text{memory.fill}~~\text{0} \\& + \text{memory.copy} + &\equiv& + \text{memory.copy}~~\text{0}~~\text{0} \\& + \text{memory.init}~~x{:}\Telemidx_I + &\equiv& + \text{memory.init}~~\text{0}~~x{:}\Telemidx_I + \end{array} + + .. index:: numeric instruction pair: text format; instruction .. _text-instr-numeric: @@ -545,35 +702,6 @@ Numeric Instructions Vector Instructions ~~~~~~~~~~~~~~~~~~~ -Vector memory instructions have optional offset and alignment immediates, like the :ref:`memory instructions `. - -.. math:: - \begin{array}{llclll} - \production{instruction} & \Tplaininstr_I &::=& \dots \phantom{averylonginstructionnameforvectext} && \phantom{vechasreallylonginstructionnames} \\ &&|& - \text{v128.load}~~m{:}\Tmemarg_{16} &\Rightarrow& \V128.\LOAD~m \\ &&|& - \text{v128.load8x8\_s}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{8x8\_s}~m \\ &&|& - \text{v128.load8x8\_u}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{8x8\_u}~m \\ &&|& - \text{v128.load16x4\_s}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{16x4\_s}~m \\ &&|& - \text{v128.load16x4\_u}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{16x4\_u}~m \\ &&|& - \text{v128.load32x2\_s}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{32x2\_s}~m \\ &&|& - \text{v128.load32x2\_u}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{32x2\_u}~m \\ &&|& - \text{v128.load8\_splat}~~m{:}\Tmemarg_1 &\Rightarrow& \V128.\LOAD\K{8\_splat}~m \\ &&|& - \text{v128.load16\_splat}~~m{:}\Tmemarg_2 &\Rightarrow& \V128.\LOAD\K{16\_splat}~m \\ &&|& - \text{v128.load32\_splat}~~m{:}\Tmemarg_4 &\Rightarrow& \V128.\LOAD\K{32\_splat}~m \\ &&|& - \text{v128.load64\_splat}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{64\_splat}~m \\ &&|& - \text{v128.load32\_zero}~~m{:}\Tmemarg_4 &\Rightarrow& \V128.\LOAD\K{32\_zero}~m \\ &&|& - \text{v128.load64\_zero}~~m{:}\Tmemarg_8 &\Rightarrow& \V128.\LOAD\K{64\_zero}~m \\ &&|& - \text{v128.store}~~m{:}\Tmemarg_{16} &\Rightarrow& \V128.\STORE~m \\ &&|& - \text{v128.load8\_lane}~~m{:}\Tmemarg_1~~laneidx{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{8\_lane}~m~laneidx \\ &&|& - \text{v128.load16\_lane}~~m{:}\Tmemarg_2~~laneidx{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{16\_lane}~m~laneidx \\ &&|& - \text{v128.load32\_lane}~~m{:}\Tmemarg_4~~laneidx{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{32\_lane}~m~laneidx \\ &&|& - \text{v128.load64\_lane}~~m{:}\Tmemarg_8~~laneidx{:}\Tu8 &\Rightarrow& \V128.\LOAD\K{64\_lane}~m~laneidx \\ &&|& - \text{v128.store8\_lane}~~m{:}\Tmemarg_1~~laneidx{:}\Tu8 &\Rightarrow& \V128.\STORE\K{8\_lane}~m~laneidx \\ &&|& - \text{v128.store16\_lane}~~m{:}\Tmemarg_2~~laneidx{:}\Tu8 &\Rightarrow& \V128.\STORE\K{16\_lane}~m~laneidx \\ &&|& - \text{v128.store32\_lane}~~m{:}\Tmemarg_4~~laneidx{:}\Tu8 &\Rightarrow& \V128.\STORE\K{32\_lane}~m~laneidx \\ &&|& - \text{v128.store64\_lane}~~m{:}\Tmemarg_8~~laneidx{:}\Tu8 &\Rightarrow& \V128.\STORE\K{64\_lane}~m~laneidx \\ - \end{array} - Vector constant instructions have a mandatory :ref:`shape ` descriptor, which determines how the following values are parsed. .. math:: diff --git a/document/core/text/modules.rst b/document/core/text/modules.rst index 5be5ce8441..591ca2e0a0 100644 --- a/document/core/text/modules.rst +++ b/document/core/text/modules.rst @@ -21,6 +21,7 @@ Modules .. _text-globalidx: .. _text-localidx: .. _text-labelidx: +.. _text-fieldidx: .. _text-index: Indices @@ -58,23 +59,9 @@ Such identifiers are looked up in the suitable space of the :ref:`identifier con \production{label index} & \Tlabelidx_I &::=& l{:}\Tu32 &\Rightarrow& l \\&&|& v{:}\Tid &\Rightarrow& l & (\iff I.\ILABELS[l] = v) \\ - \end{array} - - -.. index:: type definition, identifier - pair: text format; type definition -.. _text-typedef: - -Types -~~~~~ - -Type definitions can bind a symbolic :ref:`type identifier `. - -.. math:: - \begin{array}{llclll} - \production{type definition} & \Ttype &::=& - \text{(}~\text{type}~~\Tid^?~~\X{ft}{:}\Tfunctype~\text{)} - &\Rightarrow& \X{ft} \\ + \production{field index} & \Tfieldidx_{I,x} &::=& + i{:}\Tu32 &\Rightarrow& i \\&&|& + v{:}\Tid &\Rightarrow& i & (\iff I.\IFIELDS[x][i] = v) \\ \end{array} @@ -85,7 +72,7 @@ Type definitions can bind a symbolic :ref:`type identifier `. Type Uses ~~~~~~~~~ -A *type use* is a reference to a :ref:`type definition `. +A *type use* is a reference to a function :ref:`type definition `. It may optionally be augmented by explicit inlined :ref:`parameter ` and :ref:`result ` declarations. That allows binding symbolic :ref:`identifiers ` to name the :ref:`local indices ` of parameters. If inline declarations are given, then their types must match the referenced :ref:`function type `. @@ -96,18 +83,23 @@ If inline declarations are given, then their types must match the referenced :re \text{(}~\text{type}~~x{:}\Ttypeidx_I~\text{)} \quad\Rightarrow\quad x, I' \\ &&& \qquad (\iff \begin{array}[t]{@{}l@{}} - I.\ITYPEDEFS[x] = [t_1^n] \to [t_2^\ast] \wedge + I.\ITYPEDEFS[x] = \TSUB~\TFINAL~(\TFUNC~[t_1^n] \toF [t_2^\ast]) \wedge I' = \{\ILOCALS~(\epsilon)^n\}) \\ \end{array} \\[1ex] &&|& \text{(}~\text{type}~~x{:}\Ttypeidx_I~\text{)} ~~(t_1{:}\Tparam)^\ast~~(t_2{:}\Tresult)^\ast \quad\Rightarrow\quad x, I' \\ &&& \qquad (\iff \begin{array}[t]{@{}l@{}} - I.\ITYPEDEFS[x] = [t_1^\ast] \to [t_2^\ast] \wedge + I.\ITYPEDEFS[x] = \TSUB~\TFINAL~(\TFUNC~[t_1^\ast] \toF [t_2^\ast]) \wedge I' = \{\ILOCALS~\F{id}(\Tparam)^\ast\} \idcwellformed) \\ \end{array} \\ \end{array} +.. note:: + If inline declarations are given, their types must be *syntactically* equal to the types from the indexed definition; + possible type :ref:`substitutions ` from other definitions that might make them equal are not taken into account. + This is to simplify syntactic pre-processing. + The synthesized attribute of a |Ttypeuse| is a pair consisting of both the used :ref:`type index ` and the local :ref:`identifier context ` containing possible parameter identifiers. The following auxiliary function extracts optional identifiers from parameters: @@ -117,7 +109,7 @@ The following auxiliary function extracts optional identifiers from parameters: \end{array} .. note:: - Both productions overlap for the case that the function type is :math:`[] \to []`. + Both productions overlap for the case that the function type is :math:`[] \toF []`. However, in that case, they also produce the same results, so that the choice is immaterial. The :ref:`well-formedness ` condition on :math:`I'` ensures that the parameters do not contain duplicate identifiers. @@ -138,13 +130,12 @@ In that case, a :ref:`type index ` is automatically inserted: \text{(}~\text{type}~~x~\text{)}~~\Tparam^\ast~~\Tresult^\ast \\ \end{array} -where :math:`x` is the smallest existing :ref:`type index ` whose definition in the current module is the :ref:`function type ` :math:`[t_1^\ast] \to [t_2^\ast]`. -If no such index exists, then a new :ref:`type definition ` of the form +where :math:`x` is the smallest existing :ref:`type index ` whose :ref:`recursive type ` definition in the current module is of the form .. math:: - \text{(}~\text{type}~~\text{(}~\text{func}~~\Tparam^\ast~~\Tresult^\ast~\text{)}~\text{)} + \text{(}~\text{rec}~\text{(}~\text{type}~\text{(}~\text{sub}~\text{final}~~\text{(}~\text{func}~~\Tparam^\ast~~\Tresult^\ast~\text{)}~\text{)}~\text{)}~\text{)} -is inserted at the end of the module. +If no such index exists, then a new :ref:`recursive type ` of the same form is inserted at the end of the module. Abbreviations are expanded in the order they appear, such that previously inserted type definitions are reused by consecutive expansions. @@ -167,11 +158,11 @@ The descriptors in imports can bind a symbolic function, table, memory, or globa \production{import description} & \Timportdesc_I &::=& \text{(}~\text{func}~~\Tid^?~~x,I'{:}\Ttypeuse_I~\text{)} &\Rightarrow& \IDFUNC~x \\ &&|& - \text{(}~\text{table}~~\Tid^?~~\X{tt}{:}\Ttabletype~\text{)} + \text{(}~\text{table}~~\Tid^?~~\X{tt}{:}\Ttabletype_I~\text{)} &\Rightarrow& \IDTABLE~\X{tt} \\ &&|& - \text{(}~\text{memory}~~\Tid^?~~\X{mt}{:}\Tmemtype~\text{)} + \text{(}~\text{memory}~~\Tid^?~~\X{mt}{:}\Tmemtype_I~\text{)} &\Rightarrow& \IDMEM~~\X{mt} \\ &&|& - \text{(}~\text{global}~~\Tid^?~~\X{gt}{:}\Tglobaltype~\text{)} + \text{(}~\text{global}~~\Tid^?~~\X{gt}{:}\Tglobaltype_I~\text{)} &\Rightarrow& \IDGLOBAL~\X{gt} \\ \end{array} @@ -198,12 +189,12 @@ Function definitions can bind a symbolic :ref:`function identifier `, a \begin{array}{llclll} \production{function} & \Tfunc_I &::=& \text{(}~\text{func}~~\Tid^?~~x,I'{:}\Ttypeuse_I~~ - (t{:}\Tlocal)^\ast~~(\X{in}{:}\Tinstr_{I''})^\ast~\text{)} \\ &&& \qquad - \Rightarrow\quad \{ \FTYPE~x, \FLOCALS~t^\ast, \FBODY~\X{in}^\ast~\END \} \\ &&& \qquad\qquad\qquad + (\X{loc}{:}\Tlocal_I)^\ast~~(\X{in}{:}\Tinstr_{I''})^\ast~\text{)} \\ &&& \qquad + \Rightarrow\quad \{ \FTYPE~x, \FLOCALS~\X{loc}^\ast, \FBODY~\X{in}^\ast~\END \} \\ &&& \qquad\qquad\qquad (\iff I'' = I \compose I' \compose \{\ILOCALS~\F{id}(\Tlocal)^\ast\} \idcwellformed) \\[1ex] - \production{local} & \Tlocal &::=& - \text{(}~\text{local}~~\Tid^?~~t{:}\Tvaltype~\text{)} - \quad\Rightarrow\quad t \\ + \production{local} & \Tlocal_I &::=& + \text{(}~\text{local}~~\Tid^?~~t{:}\Tvaltype_I~\text{)} + \quad\Rightarrow\quad \{ \LTYPE~t \} \\ \end{array} The definition of the local :ref:`identifier context ` :math:`I''` uses the following auxiliary function to extract optional identifiers from locals: @@ -255,7 +246,7 @@ Functions can be defined as :ref:`imports ` or :ref:`exports `. .. math:: \begin{array}{llclll} \production{table} & \Ttable_I &::=& - \text{(}~\text{table}~~\Tid^?~~\X{tt}{:}\Ttabletype~\text{)} - &\Rightarrow& \{ \TTYPE~\X{tt} \} \\ + \text{(}~\text{table}~~\Tid^?~~\X{tt}{:}\Ttabletype_I~~e{:}\Texpr_I~\text{)} + &\Rightarrow& \{ \TTYPE~\X{tt}, \TINIT~e \} \\ \end{array} +.. index:: reference type, heap type .. index:: import, name pair: text format; import .. index:: export, name, index, table index @@ -285,6 +277,18 @@ Table definitions can bind a symbolic :ref:`table identifier `. Abbreviations ............. +A table's initialization :ref:`expression ` can be omitted, in which case it defaults to :math:`\REFNULL`: + +.. math:: + \begin{array}{llclll} + \production{module field} & + \text{(}~\text{table}~~\Tid^?~~\Ttabletype~\text{)} + &\equiv& + \text{(}~\text{table}~~\Tid^?~~\Ttabletype~~\text{(}~\REFNULL~\X{ht}~\text{)}~\text{)} + \\ &&& \qquad\qquad + (\iff \Ttabletype = \Tlimits~\text{(}~\text{ref}~\text{null}^?~\X{ht}~\text{)}) \\ + \end{array} + An :ref:`element segment ` can be given inline with a table definition, in which case its offset is :math:`0` and the :ref:`limits ` of the :ref:`table type ` are inferred from the length of the given segment: .. math:: @@ -302,7 +306,7 @@ An :ref:`element segment ` can be given inline with a table definitio \production{module field} & \text{(}~\text{table}~~\Tid^?~~\Treftype~~\text{(}~\text{elem}~~x^n{:}\Tvec(\Tfuncidx)~\text{)}~\text{)} \quad\equiv \\ & \qquad \text{(}~\text{table}~~\Tid'~~n~~n~~\Treftype~\text{)} \\ & \qquad - \text{(}~\text{elem}~~\text{(}~\text{table}~~\Tid'~\text{)}~~\text{(}~\text{i32.const}~~\text{0}~\text{)}~~\text{func}~~\Tvec(\Tfuncidx)~\text{)} + \text{(}~\text{elem}~~\text{(}~\text{table}~~\Tid'~\text{)}~~\text{(}~\text{i32.const}~~\text{0}~\text{)}~~\Treftype~~\Tvec(\text{(}~\text{ref.func}~~\Tfuncidx~\text{)})~\text{)} \\ & \qquad\qquad (\iff \Tid^? \neq \epsilon \wedge \Tid' = \Tid^? \vee \Tid^? = \epsilon \wedge \Tid' \idfresh) \\ \end{array} @@ -338,7 +342,7 @@ Memory definitions can bind a symbolic :ref:`memory identifier `. .. math:: \begin{array}{llclll} \production{memory} & \Tmem_I &::=& - \text{(}~\text{memory}~~\Tid^?~~\X{mt}{:}\Tmemtype~\text{)} + \text{(}~\text{memory}~~\Tid^?~~\X{mt}{:}\Tmemtype_I~\text{)} &\Rightarrow& \{ \MTYPE~\X{mt} \} \\ \end{array} @@ -400,7 +404,7 @@ Global definitions can bind a symbolic :ref:`global identifier `. .. math:: \begin{array}{llclll} \production{global} & \Tglobal_I &::=& - \text{(}~\text{global}~~\Tid^?~~\X{gt}{:}\Tglobaltype~~e{:}\Texpr_I~\text{)} + \text{(}~\text{global}~~\Tid^?~~\X{gt}{:}\Tglobaltype_I~~e{:}\Texpr_I~\text{)} &\Rightarrow& \{ \GTYPE~\X{gt}, \GINIT~e \} \\ \end{array} @@ -513,7 +517,7 @@ Element segments allow for an optional :ref:`table index ` to ide \text{(}~\text{elem}~~\Tid^?~~\text{declare}~~(et, y^\ast){:}\Telemlist_I~\text{)} \\ &&& \qquad \Rightarrow\quad \{ \ETYPE~et, \EINIT~y^\ast, \EMODE~\EDECLARATIVE \} \\ \production{element list} & \Telemlist_I &::=& - t{:}\Treftype~~y^\ast{:}\Tvec(\Telemexpr_I) \qquad\Rightarrow\quad ( \ETYPE~t, \EINIT~y^\ast ) \\ + t{:}\Treftype_I~~y^\ast{:}\Tvec(\Telemexpr_I) \qquad\Rightarrow\quad ( \ETYPE~t, \EINIT~y^\ast ) \\ \production{element expression} & \Telemexpr_I &::=& \text{(}~\text{item}~~e{:}\Texpr_I~\text{)} \quad\Rightarrow\quad e \\ @@ -544,7 +548,7 @@ Also, the element list may be written as just a sequence of :ref:`function indic \begin{array}{llcll} \production{element list} & \text{func}~~\Tvec(\Tfuncidx_I) &\equiv& - \text{funcref}~~\Tvec(\text{(}~\text{ref.func}~~\Tfuncidx_I~\text{)}) + \text{(ref}~\text{func)}~~\Tvec(\text{(}~\text{ref.func}~~\Tfuncidx_I~\text{)}) \end{array} A table use can be omitted, defaulting to :math:`\T{0}`. @@ -649,7 +653,7 @@ The name serves a documentary role only. \production{module field} & \Tmodulefield_I & \begin{array}[t]{@{}clll} ::=& - \X{ty}{:}\Ttype &\Rightarrow& \{\MTYPES~\X{ty}\} \\ |& + \X{ty}^\ast{:}\Trectype_I &\Rightarrow& \{\MTYPES~\X{ty}^\ast\} \\ |& \X{im}{:}\Timport_I &\Rightarrow& \{\MIMPORTS~\X{im}\} \\ |& \X{fn}{:}\Tfunc_I &\Rightarrow& \{\MFUNCS~\X{fn}\} \\ |& \X{ta}{:}\Ttable_I &\Rightarrow& \{\MTABLES~\X{ta}\} \\ |& @@ -679,8 +683,10 @@ The definition of the initial :ref:`identifier context ` :math:`I` .. math:: \begin{array}{@{}lcl@{\qquad\qquad}l} - \F{idc}(\text{(}~\text{type}~\Tid^?~\X{ft}{:}\Tfunctype~\text{)}) &=& - \{\ITYPES~(\Tid^?), \ITYPEDEFS~\X{ft}\} \\ + \F{idc}(\text{(}~\text{rec}~~\Ttypedef^\ast~\text{)}) &=& + \bigcompose \F{idc}(\Ttypedef)^\ast \\ + \F{idc}(\text{(}~\text{type}~\Tid^?~\Tsubtype~\text{)}) &=& + \{\ITYPES~(\Tid^?), \IFIELDS~\F{idf}(\Tsubtype), \ITYPEDEFS~\X{st}\} \\ \F{idc}(\text{(}~\text{func}~\Tid^?~\dots~\text{)}) &=& \{\IFUNCS~(\Tid^?)\} \\ \F{idc}(\text{(}~\text{table}~\Tid^?~\dots~\text{)}) &=& @@ -702,7 +708,17 @@ The definition of the initial :ref:`identifier context ` :math:`I` \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{global}~\Tid^?~\dots~\text{)}~\text{)}) &=& \{\IGLOBALS~(\Tid^?)\} \\ \F{idc}(\text{(}~\dots~\text{)}) &=& - \{\} \\ + \{\} \\[2ex] + \F{idf}(\text{(}~\text{sub}~\dots~\Tcomptype~\text{)}) &=& + \F{idf}(\Tcomptype) \\ + \F{idf}(\text{(}~\text{struct}~\X{Tfield}^\ast~\text{)}) &=& + \bigcompose \F{idf}(\Tfield)^\ast \\ + \F{idf}(\text{(}~\text{array}~\dots~\text{)}) &=& + \epsilon \\ + \F{idf}(\text{(}~\text{func}~\dots~\text{)}) &=& + \epsilon \\ + \F{idf}(\text{(}~\text{field}~\Tid^?~\dots~\text{)}) &=& + \Tid^? \\ \end{array} diff --git a/document/core/text/types.rst b/document/core/text/types.rst index 7705cc575e..731af495a8 100644 --- a/document/core/text/types.rst +++ b/document/core/text/types.rst @@ -13,8 +13,8 @@ Number Types ~~~~~~~~~~~~ .. math:: - \begin{array}{llcll@{\qquad\qquad}l} - \production{number type} & \Tnumtype &::=& + \begin{array}{llrll@{\qquad\qquad}l} + \production{number type} & \Tnumtype_I &::=& \text{i32} &\Rightarrow& \I32 \\ &&|& \text{i64} &\Rightarrow& \I64 \\ &&|& \text{f32} &\Rightarrow& \F32 \\ &&|& @@ -31,27 +31,72 @@ Vector Types .. math:: \begin{array}{llcll@{\qquad\qquad}l} - \production{vector type} & \Tvectype &::=& + \production{vector type} & \Tvectype_I &::=& \text{v128} &\Rightarrow& \V128 \\ \end{array} +.. index:: heap type + pair: text format; heap type +.. _text-heaptype: +.. _text-absheaptype: + +Heap Types +~~~~~~~~~~ + +.. math:: + \begin{array}{llrll@{\qquad\qquad}l} + \production{abstract heap type} & \Tabsheaptype &::=& + \text{any} &\Rightarrow& \ANY \\ &&|& + \text{eq} &\Rightarrow& \EQT \\ &&|& + \text{i31} &\Rightarrow& \I31 \\ &&|& + \text{struct} &\Rightarrow& \STRUCT \\ &&|& + \text{array} &\Rightarrow& \ARRAY \\ &&|& + \text{none} &\Rightarrow& \NONE \\ &&|& + \text{func} &\Rightarrow& \FUNC \\ &&|& + \text{nofunc} &\Rightarrow& \NOFUNC \\ &&|& + \text{extern} &\Rightarrow& \EXTERN \\ &&|& + \text{noextern} &\Rightarrow& \NOEXTERN \\ + \production{heap type} & \Theaptype_I &::=& + t{:}\Tabsheaptype &\Rightarrow& y \\ &&|& + x{:}\Ttypeidx_I &\Rightarrow& x \\ + \end{array} + + .. index:: reference type pair: text format; reference type .. _text-reftype: -.. _text-heaptype: Reference Types ~~~~~~~~~~~~~~~ .. math:: \begin{array}{llcll@{\qquad\qquad}l} - \production{reference type} & \Treftype &::=& - \text{funcref} &\Rightarrow& \FUNCREF \\ &&|& - \text{externref} &\Rightarrow& \EXTERNREF \\ - \production{heap type} & \Theaptype &::=& - \text{func} &\Rightarrow& \FUNCREF \\ &&|& - \text{extern} &\Rightarrow& \EXTERNREF \\ + \production{reference type} & \Treftype_I &::=& + \text{(}~\text{ref}~~\X{ht}{:}\Theaptype~\text{)} + &\Rightarrow& \REF~\X{ht} \\ &&|& + \text{(}~\text{ref}~~\text{null}~~\X{ht}{:}\Theaptype~\text{)} + &\Rightarrow& \REF~\NULL~\X{ht} \\ + \end{array} + +Abbreviations +............. + +There are shorthands for references to abstract heap types. + +.. math:: + \begin{array}{llclll} + \production{reference type} & + \text{anyref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{any}~\text{)} \\ + \text{eqref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{eq}~\text{)} \\ + \text{i31ref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{i31}~\text{)} \\ + \text{structref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{struct}~\text{)} \\ + \text{arrayref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{array}~\text{)} \\ + \text{nullref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{none}~\text{)} \\ + \text{funcref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{func}~\text{)} \\ + \text{nullfuncref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{nofunc}~\text{)} \\ + \text{externref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{extern}~\text{)} \\ + \text{nullexternref} &\equiv& \text{(}~\text{ref}~~\text{null}~~\text{noextern}~\text{)} \\ \end{array} @@ -64,10 +109,10 @@ Value Types .. math:: \begin{array}{llcll@{\qquad\qquad}l} - \production{value type} & \Tvaltype &::=& - t{:}\Tnumtype &\Rightarrow& t \\ &&|& - t{:}\Tvectype &\Rightarrow& t \\ &&|& - t{:}\Treftype &\Rightarrow& t \\ + \production{value type} & \Tvaltype_I &::=& + t{:}\Tnumtype_I &\Rightarrow& t \\ &&|& + t{:}\Tvectype_I &\Rightarrow& t \\ &&|& + t{:}\Treftype_I &\Rightarrow& t \\ \end{array} @@ -82,14 +127,14 @@ Function Types .. math:: \begin{array}{llclll@{\qquad\qquad}l} - \production{function type} & \Tfunctype &::=& - \text{(}~\text{func}~~t_1^\ast{:\,}\Tvec(\Tparam)~~t_2^\ast{:\,}\Tvec(\Tresult)~\text{)} + \production{function type} & \Tfunctype_I &::=& + \text{(}~\text{func}~~t_1^\ast{:\,}\Tvec(\Tparam_I)~~t_2^\ast{:\,}\Tvec(\Tresult_I)~\text{)} &\Rightarrow& [t_1^\ast] \to [t_2^\ast] \\ - \production{parameter} & \Tparam &::=& - \text{(}~\text{param}~~\Tid^?~~t{:}\Tvaltype~\text{)} + \production{parameter} & \Tparam_I &::=& + \text{(}~\text{param}~~\Tid^?~~t{:}\Tvaltype_I~\text{)} &\Rightarrow& t \\ - \production{result} & \Tresult &::=& - \text{(}~\text{result}~~t{:}\Tvaltype~\text{)} + \production{result} & \Tresult_I &::=& + \text{(}~\text{result}~~t{:}\Tvaltype_I~\text{)} &\Rightarrow& t \\ \end{array} @@ -114,6 +159,129 @@ Multiple anonymous parameters or results may be combined into a single declarati \end{array} +.. index:: aggregate type, value type, structure type, array type, field type, storage type, packed type, mutability + pair: text format; aggregate type + pair: text format; structure type + pair: text format; array type + pair: text format; field type + pair: text format; storage type + pair: text format; packed type +.. _text-aggrtype: +.. _text-structtype: +.. _text-arraytype: +.. _text-fieldtype: +.. _text-storagetype: +.. _text-packedtype: + +Aggregate Types +~~~~~~~~~~~~~~~ + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{array type} & \Tarraytype_I &::=& + \text{(}~\text{array}~~\X{ft}{:}\Tfieldtype_I~\text{)} + &\Rightarrow& \X{ft} \\ + \production{structure type} & \Tstructtype_I &::=& + \text{(}~\text{struct}~~\X{ft}^\ast{:\,}\Tvec(\Tfield_I)~\text{)} + &\Rightarrow& \X{ft}^\ast \\ + \production{field} & \Tfield_I &::=& + \text{(}~\text{field}~~\Tid^?~~\X{ft}{:}\Tfieldtype_I~\text{)} + &\Rightarrow& \X{ft} \\ + \production{field type} & \Tfieldtype_I &::=& + \X{st}{:}\Bstoragetype + &\Rightarrow& \MCONST~\X{st} \\ &&|& + \text{(}~\text{mut}~~\X{st}{:}\Bstoragetype~\text{)} + &\Rightarrow& \MVAR~\X{st} \\ + \production{storage type} & \Tstoragetype_I &::=& + t{:}\Tvaltype_I + &\Rightarrow& t \\ &&|& + t{:}\Tpackedtype + &\Rightarrow& t \\ + \production{packed type} & \Tpackedtype &::=& + \text{i8} + &\Rightarrow& \I8 \\ &&|& + \text{i16} + &\Rightarrow& \I16 \\ + \end{array} + +Abbreviations +............. + +Multiple anonymous structure fields may be combined into a single declaration: + +.. math:: + \begin{array}{llclll} + \production{field} & + \text{(}~~\text{field}~~\Tfieldtype^\ast~~\text{)} &\equiv& + (\text{(}~~\text{field}~~\Tfieldtype~~\text{)})^\ast \\ + \end{array} + + +.. index:: composite type, structure type, array type, function type + pair: text format; composite type +.. _text-comptype: + +Composite Types +~~~~~~~~~~~~~~~ + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{composite type} & \Tcomptype_I &::=& + \X{at}{:}\Tarraytype_I + &\Rightarrow& \TARRAY~\X{at} \\ &&|& + \X{st}{:}\Tstructtype_I + &\Rightarrow& \TSTRUCT~\X{at} \\ &&|& + \X{ft}{:}\Tfunctype_I + &\Rightarrow& \TFUNC~\X{ft} \\ + \end{array} + + +.. index:: recursive type, sub type, composite type + pair: text format; recursive type + pair: text format; sub type +.. _text-rectype: +.. _text-subtype: +.. _text-typedef: + +Recursive Types +~~~~~~~~~~~~~~~ + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{recursive type} & \Trectype_I &::=& + \text{(}~\text{rec}~~\X{st}^\ast{:\,}\Tvec(\Ttypedef_I)~\text{)} + &\Rightarrow& \TREC~\X{st}^\ast \\ + \production{defined type} & \Ttypedef_I &::=& + \text{(}~\text{type}~~\Tid^?~~\X{st}{:}\Tsubtype_I~\text{)} + &\Rightarrow& \X{st} \\ + \production{sub type} & \Tsubtype_I &::=& + \text{(}~\text{sub}~~\text{final}^?~~x^\ast{:\,}\Tvec(\Ttypeidx_I)~~\X{ct}{:}\Tcomptype_I~\text{)} + &\Rightarrow& \TSUB~\TFINAL^?~x^\ast~\X{ct} \\ + \end{array} + + +Abbreviations +............. + +Singular recursive types can omit the :math:`\text{rec}` keyword: + +.. math:: + \begin{array}{llclll} + \production{recursive type} & + \Ttypedef &\equiv& + \text{(}~~\text{rec}~~\Ttypedef~~\text{)} \\ + \end{array} + +Similarly, final sub types with no super-types can omit the |Tsub| keyword and arguments: + +.. math:: + \begin{array}{llclll} + \production{sub type} & + \Tcomptype &\equiv& + \text{(}~~\text{sub}~~\text{final}~~\epsilon~~\Tcomptype~~\text{)} \\ + \end{array} + + .. index:: limits pair: text format; limits .. _text-limits: @@ -138,7 +306,7 @@ Memory Types .. math:: \begin{array}{llclll@{\qquad\qquad}l} - \production{memory type} & \Tmemtype &::=& + \production{memory type} & \Tmemtype_I &::=& \X{lim}{:}\Tlimits &\Rightarrow& \X{lim} \\ \end{array} @@ -152,8 +320,8 @@ Table Types .. math:: \begin{array}{llclll} - \production{table type} & \Ttabletype &::=& - \X{lim}{:}\Tlimits~~\X{et}{:}\Treftype &\Rightarrow& \X{lim}~\X{et} \\ + \production{table type} & \Ttabletype_I &::=& + \X{lim}{:}\Tlimits~~\X{et}{:}\Treftype_I &\Rightarrow& \X{lim}~\X{et} \\ \end{array} @@ -167,7 +335,7 @@ Global Types .. math:: \begin{array}{llclll} - \production{global type} & \Tglobaltype &::=& + \production{global type} & \Tglobaltype_I &::=& t{:}\Tvaltype &\Rightarrow& \MCONST~t \\ &&|& - \text{(}~\text{mut}~~t{:}\Tvaltype~\text{)} &\Rightarrow& \MVAR~t \\ + \text{(}~\text{mut}~~t{:}\Tvaltype_I~\text{)} &\Rightarrow& \MVAR~t \\ \end{array} diff --git a/document/core/util/check_macros.sh b/document/core/util/check_macros.sh index 46d9d76803..2ac6101291 100644 --- a/document/core/util/check_macros.sh +++ b/document/core/util/check_macros.sh @@ -5,7 +5,7 @@ cd `dirname $0`/.. FILES=`ls */*.rst` ERRORS=0 -for XREF in `grep xref util/macros.def`; do +for XREF in `grep "[\\]xref" util/macros.def`; do if echo $XREF | grep -q "[|]"; then MACRO=`echo $XREF | sed 's/^[^|]*[|]//g' | sed 's/[|].*$//g'` elif echo $XREF | grep -q xref; then diff --git a/document/core/util/macros.def b/document/core/util/macros.def index 06b60fd6f4..84cb8ffb0e 100644 --- a/document/core/util/macros.def +++ b/document/core/util/macros.def @@ -101,6 +101,7 @@ .. Notation for Sequences & Records +.. |subst| mathdef:: \xref{valid/conventions}{notation-subst}{\mathrel{\mathbf{:=}}} .. |slice| mathdef:: \xref{syntax/conventions}{notation-slice}{\mathrel{\mathbf{:}}} .. |with| mathdef:: \xref{syntax/conventions}{notation-replace}{\mathrel{\mbox{with}}} .. |concat| mathdef:: \xref{syntax/conventions}{notation-concat}{\F{concat}} @@ -140,6 +141,7 @@ .. |u1| mathdef:: \xref{syntax/values}{syntax-int}{\X{u1}} .. |u8| mathdef:: \xref{syntax/values}{syntax-int}{\X{u8}} .. |u16| mathdef:: \xref{syntax/values}{syntax-int}{\X{u16}} +.. |u31| mathdef:: \xref{syntax/values}{syntax-int}{\X{u31}} .. |u32| mathdef:: \xref{syntax/values}{syntax-int}{\X{u32}} .. |u64| mathdef:: \xref{syntax/values}{syntax-int}{\X{u64}} @@ -175,8 +177,14 @@ .. Types, terminals -.. |to| mathdef:: \xref{syntax/types}{syntax-functype}{\rightarrow} +.. |toF| mathdef:: \xref{syntax/types}{syntax-functype}{\rightarrow} +.. |to| mathdef:: \mathrel{\xref{valid/conventions}{syntax-instrtype}{\rightarrow}} +.. |BOTH| mathdef:: \xref{valid/conventions}{syntax-heaptype-ext}{\K{bot}} +.. |BOT| mathdef:: \xref{valid/conventions}{syntax-valtype-ext}{\K{bot}} + +.. |I8| mathdef:: \xref{syntax/types}{syntax-storagetype}{\K{i8}} +.. |I16| mathdef:: \xref{syntax/types}{syntax-storagetype}{\K{i16}} .. |I32| mathdef:: \xref{syntax/types}{syntax-valtype}{\K{i32}} .. |I64| mathdef:: \xref{syntax/types}{syntax-valtype}{\K{i64}} .. |F32| mathdef:: \xref{syntax/types}{syntax-valtype}{\K{f32}} @@ -192,12 +200,44 @@ .. |F32X4| mathdef:: \xref{syntax/types}{syntax-valtype}{\K{f32x4}} .. |F64X2| mathdef:: \xref{syntax/types}{syntax-valtype}{\K{f64x2}} +.. |REF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{ref}} +.. |NULL| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{null}} +.. |ANY| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{any}} +.. |EQT| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{eq}} +.. |I31| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{i31}} +.. |STRUCT| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{struct}} +.. |ARRAY| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{array}} +.. |FUNC| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{func}} +.. |EXTERN| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{extern}} +.. |NONE| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{none}} +.. |NOFUNC| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{nofunc}} +.. |NOEXTERN| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{noextern}} +.. |ANYREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{anyref}} +.. |EQREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{eqref}} +.. |I31REF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{i31ref}} +.. |STRUCTREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{structref}} +.. |ARRAYREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{arrayref}} .. |FUNCREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{funcref}} .. |EXTERNREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{externref}} +.. |NULLREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{nullref}} +.. |NULLFUNCREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{nullfuncref}} +.. |NULLEXTERNREF| mathdef:: \xref{syntax/types}{syntax-reftype}{\K{nullexternref}} + +.. |REC| mathdef:: \xref{syntax/types}{syntax-heaptype}{\K{rec}} + +.. |TFUNC| mathdef:: \xref{syntax/types}{syntax-comptype}{\K{func}} +.. |TSTRUCT| mathdef:: \xref{syntax/types}{syntax-comptype}{\K{struct}} +.. |TARRAY| mathdef:: \xref{syntax/types}{syntax-comptype}{\K{array}} +.. |TSUB| mathdef:: \xref{syntax/types}{syntax-subtype}{\K{sub}} +.. |TREC| mathdef:: \xref{syntax/types}{syntax-rectype}{\K{rec}} +.. |TFINAL| mathdef:: \xref{syntax/types}{syntax-subtype}{\K{final}} .. |MVAR| mathdef:: \xref{syntax/types}{syntax-mut}{\K{var}} .. |MCONST| mathdef:: \xref{syntax/types}{syntax-mut}{\K{const}} +.. |SET| mathdef:: \xref{valid/conventions}{syntax-init}{\K{set}} +.. |UNSET| mathdef:: \xref{valid/conventions}{syntax-init}{\K{unset}} + .. |LMIN| mathdef:: \xref{syntax/types}{syntax-limits}{\K{min}} .. |LMAX| mathdef:: \xref{syntax/types}{syntax-limits}{\K{max}} @@ -211,10 +251,21 @@ .. |numtype| mathdef:: \xref{syntax/types}{syntax-numtype}{\X{numtype}} .. |vectype| mathdef:: \xref{syntax/types}{syntax-vectype}{\X{vectype}} +.. |heaptype| mathdef:: \xref{syntax/types}{syntax-heaptype}{\X{heaptype}} +.. |absheaptype| mathdef:: \xref{syntax/types}{syntax-absheaptype}{\X{absheaptype}} .. |reftype| mathdef:: \xref{syntax/types}{syntax-reftype}{\X{reftype}} .. |valtype| mathdef:: \xref{syntax/types}{syntax-valtype}{\X{valtype}} .. |resulttype| mathdef:: \xref{syntax/types}{syntax-resulttype}{\X{resulttype}} .. |functype| mathdef:: \xref{syntax/types}{syntax-functype}{\X{functype}} +.. |structtype| mathdef:: \xref{syntax/types}{syntax-structtype}{\X{structtype}} +.. |arraytype| mathdef:: \xref{syntax/types}{syntax-arraytype}{\X{arraytype}} +.. |fieldtype| mathdef:: \xref{syntax/types}{syntax-fieldtype}{\X{fieldtype}} +.. |storagetype| mathdef:: \xref{syntax/types}{syntax-storagetype}{\X{storagetype}} +.. |packedtype| mathdef:: \xref{syntax/types}{syntax-packedtype}{\X{packedtype}} +.. |comptype| mathdef:: \xref{syntax/types}{syntax-comptype}{\X{comptype}} +.. |subtype| mathdef:: \xref{syntax/types}{syntax-subtype}{\X{subtype}} +.. |rectype| mathdef:: \xref{syntax/types}{syntax-rectype}{\X{rectype}} +.. |deftype| mathdef:: \xref{valid/conventions}{syntax-deftype}{\X{deftype}} .. |globaltype| mathdef:: \xref{syntax/types}{syntax-globaltype}{\X{globaltype}} .. |tabletype| mathdef:: \xref{syntax/types}{syntax-tabletype}{\X{tabletype}} @@ -222,15 +273,26 @@ .. |limits| mathdef:: \xref{syntax/types}{syntax-limits}{\X{limits}} .. |mut| mathdef:: \xref{syntax/types}{syntax-mut}{\X{mut}} +.. |init| mathdef:: \xref{valid/conventions}{syntax-init}{\X{init}} +.. |instrtype| mathdef:: \xref{valid/conventions}{syntax-instrtype}{\X{instrtype}} +.. |localtype| mathdef:: \xref{valid/conventions}{syntax-localtype}{\X{localtype}} .. |externtype| mathdef:: \xref{syntax/types}{syntax-externtype}{\X{externtype}} -.. |stacktype| mathdef:: \xref{valid/instructions}{syntax-stacktype}{\X{stacktype}} -.. |opdtype| mathdef:: \xref{valid/instructions}{syntax-opdtype}{\X{opdtype}} - .. Types, meta functions +.. |reftypediff| mathdef:: \xref{valid/conventions}{aux-reftypediff}{\setminus} + +.. |rollrt| mathdef:: \xref{valid/conventions}{aux-roll-rectype}{\F{roll}} +.. |unrollrt| mathdef:: \xref{valid/conventions}{aux-unroll-rectype}{\F{unroll}} +.. |rolldt| mathdef:: \xref{valid/conventions}{aux-roll-deftype}{\F{roll}^\ast} +.. |unrolldt| mathdef:: \xref{valid/conventions}{aux-unroll-deftype}{\F{unroll}} +.. |expanddt| mathdef:: \xref{valid/conventions}{aux-expand-deftype}{\F{expand}} +.. |unrollht| mathdef:: \xref{appendix/properties}{aux-unroll-heaptype}{\F{unroll}} + +.. |unpacktype| mathdef:: \xref{syntax/types}{aux-unpacktype}{\F{unpack}} + .. |etfuncs| mathdef:: \xref{syntax/types}{syntax-externtype}{\F{funcs}} .. |ettables| mathdef:: \xref{syntax/types}{syntax-externtype}{\F{tables}} .. |etmems| mathdef:: \xref{syntax/types}{syntax-externtype}{\F{mems}} @@ -248,6 +310,7 @@ .. |dataidx| mathdef:: \xref{syntax/modules}{syntax-dataidx}{\X{dataidx}} .. |localidx| mathdef:: \xref{syntax/modules}{syntax-localidx}{\X{localidx}} .. |labelidx| mathdef:: \xref{syntax/modules}{syntax-labelidx}{\X{labelidx}} +.. |fieldidx| mathdef:: \xref{syntax/modules}{syntax-fieldidx}{\X{fieldidx}} .. Indices, meta functions @@ -280,7 +343,10 @@ .. |FLOCALS| mathdef:: \xref{syntax/modules}{syntax-func}{\K{locals}} .. |FBODY| mathdef:: \xref{syntax/modules}{syntax-func}{\K{body}} +.. |LTYPE| mathdef:: \xref{syntax/modules}{syntax-local}{\K{type}} + .. |TTYPE| mathdef:: \xref{syntax/modules}{syntax-table}{\K{type}} +.. |TINIT| mathdef:: \xref{syntax/modules}{syntax-table}{\K{init}} .. |MTYPE| mathdef:: \xref{syntax/modules}{syntax-mem}{\K{type}} @@ -324,8 +390,9 @@ .. Modules, non-terminals .. |module| mathdef:: \xref{syntax/modules}{syntax-module}{\X{module}} -.. |type| mathdef:: \xref{syntax/types}{syntax-functype}{\X{type}} +.. |type| mathdef:: \xref{syntax/types}{syntax-rectype}{\X{type}} .. |func| mathdef:: \xref{syntax/modules}{syntax-func}{\X{func}} +.. |local| mathdef:: \xref{syntax/modules}{syntax-local}{\X{local}} .. |table| mathdef:: \xref{syntax/modules}{syntax-table}{\X{table}} .. |mem| mathdef:: \xref{syntax/modules}{syntax-mem}{\X{mem}} .. |global| mathdef:: \xref{syntax/modules}{syntax-global}{\X{global}} @@ -363,9 +430,18 @@ .. |BR| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br}} .. |BRIF| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_if}} .. |BRTABLE| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_table}} +.. |BRONNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_on\_null}} +.. |BRONNONNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_on\_non\_null}} +.. |BRONCAST| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_on\_cast}} +.. |BRONCASTFAIL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{br\_on\_cast\_fail}} + .. |RETURN| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return}} .. |CALL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{call}} +.. |CALLREF| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{call\_ref}} .. |CALLINDIRECT| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{call\_indirect}} +.. |RETURNCALL| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return\_call}} +.. |RETURNCALLREF| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return\_call\_ref}} +.. |RETURNCALLINDIRECT| mathdef:: \xref{syntax/instructions}{syntax-instr-control}{\K{return\_call\_indirect}} .. |DROP| mathdef:: \xref{syntax/instructions}{syntax-instr-parametric}{\K{drop}} .. |SELECT| mathdef:: \xref{syntax/instructions}{syntax-instr-parametric}{\K{select}} @@ -394,9 +470,43 @@ .. |MEMORYINIT| mathdef:: \xref{syntax/instructions}{syntax-instr-memory}{\K{memory.init}} .. |DATADROP| mathdef:: \xref{syntax/instructions}{syntax-instr-memory}{\K{data.drop}} -.. |REFNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref{.}null}} -.. |REFISNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref{.}is\_null}} -.. |REFFUNC| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref{.}func}} +.. |REFNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.null}} +.. |REFFUNC| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.func}} +.. |REFISNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.is\_null}} +.. |REFASNONNULL| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.as\_non\_null}} +.. |REFEQ| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.eq}} +.. |REFTEST| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.test}} +.. |REFCAST| mathdef:: \xref{syntax/instructions}{syntax-instr-ref}{\K{ref.cast}} + +.. |STRUCTNEW| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.new}} +.. |STRUCTNEWDEFAULT| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.new\_default}} +.. |STRUCTGET| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.get}} +.. |STRUCTGETS| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.get\_s}} +.. |STRUCTGETU| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.get\_u}} +.. |STRUCTSET| mathdef:: \xref{syntax/instructions}{syntax-instr-struct}{\K{struct.set}} + +.. |ARRAYNEW| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.new}} +.. |ARRAYNEWDEFAULT| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.new\_default}} +.. |ARRAYNEWFIXED| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.new\_fixed}} +.. |ARRAYNEWDATA| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.new\_data}} +.. |ARRAYNEWELEM| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.new\_elem}} +.. |ARRAYGET| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.get}} +.. |ARRAYGETS| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.get\_s}} +.. |ARRAYGETU| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.get\_u}} +.. |ARRAYSET| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.set}} +.. |ARRAYLEN| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.len}} +.. |ARRAYFILL| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.fill}} +.. |ARRAYCOPY| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.copy}} +.. |ARRAYINITDATA| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.init\_data}} +.. |ARRAYINITELEM| mathdef:: \xref{syntax/instructions}{syntax-instr-array}{\K{array.init\_elem}} + +.. |REFI31| mathdef:: \xref{syntax/instructions}{syntax-instr-i31}{\K{ref.i31}} +.. |I31GET| mathdef:: \xref{syntax/instructions}{syntax-instr-i31}{\K{i31.get}} +.. |I31GETS| mathdef:: \xref{syntax/instructions}{syntax-instr-i31}{\K{i31.get\_s}} +.. |I31GETU| mathdef:: \xref{syntax/instructions}{syntax-instr-i31}{\K{i31.get\_u}} + +.. |ANYCONVERTEXTERN| mathdef:: \xref{syntax/instructions}{syntax-instr-extern}{\K{any.convert\_extern}} +.. |EXTERNCONVERTANY| mathdef:: \xref{syntax/instructions}{syntax-instr-extern}{\K{extern.convert\_any}} .. |CONST| mathdef:: \xref{syntax/instructions}{syntax-instr-numeric}{\K{const}} .. |EQZ| mathdef:: \xref{syntax/instructions}{syntax-instr-numeric}{\K{eqz}} @@ -550,6 +660,11 @@ .. |expr| mathdef:: \xref{syntax/instructions}{syntax-expr}{\X{expr}} +.. Instructions, meta functions + +.. |getfield| mathdef:: \xref{exec/instructions}{aux-getfield}{\F{getfield}} + + .. Binary Format .. ------------- @@ -601,10 +716,21 @@ .. |Bnumtype| mathdef:: \xref{binary/types}{binary-numtype}{\B{numtype}} .. |Bvectype| mathdef:: \xref{binary/types}{binary-vectype}{\B{vectype}} +.. |Bheaptype| mathdef:: \xref{binary/types}{binary-heaptype}{\B{heaptype}} +.. |Babsheaptype| mathdef:: \xref{binary/types}{binary-absheaptype}{\B{absheaptype}} .. |Breftype| mathdef:: \xref{binary/types}{binary-reftype}{\B{reftype}} .. |Bvaltype| mathdef:: \xref{binary/types}{binary-valtype}{\B{valtype}} .. |Bresulttype| mathdef:: \xref{binary/types}{binary-resulttype}{\B{resulttype}} .. |Bfunctype| mathdef:: \xref{binary/types}{binary-functype}{\B{functype}} +.. |Bstructtype| mathdef:: \xref{binary/types}{binary-structtype}{\B{structtype}} +.. |Barraytype| mathdef:: \xref{binary/types}{binary-arraytype}{\B{arraytype}} +.. |Baggrtype| mathdef:: \xref{binary/types}{binary-aggrtype}{\B{aggrtype}} +.. |Bfieldtype| mathdef:: \xref{binary/types}{binary-fieldtype}{\B{fieldtype}} +.. |Bstoragetype| mathdef:: \xref{binary/types}{binary-storagetype}{\B{storagetype}} +.. |Bpackedtype| mathdef:: \xref{binary/types}{binary-packedtype}{\B{packedtype}} +.. |Bcomptype| mathdef:: \xref{binary/types}{binary-comptype}{\B{comptype}} +.. |Bsubtype| mathdef:: \xref{binary/types}{binary-subtype}{\B{subtype}} +.. |Brectype| mathdef:: \xref{binary/types}{binary-rectype}{\B{rectype}} .. |Bglobaltype| mathdef:: \xref{binary/types}{binary-globaltype}{\B{globaltype}} .. |Btabletype| mathdef:: \xref{binary/types}{binary-tabletype}{\B{tabletype}} .. |Bmemtype| mathdef:: \xref{binary/types}{binary-memtype}{\B{memtype}} @@ -624,6 +750,7 @@ .. |Bdataidx| mathdef:: \xref{binary/modules}{binary-dataidx}{\B{dataidx}} .. |Blocalidx| mathdef:: \xref{binary/modules}{binary-localidx}{\B{localidx}} .. |Blabelidx| mathdef:: \xref{binary/modules}{binary-labelidx}{\B{labelidx}} +.. |Bfieldidx| mathdef:: \xref{binary/modules}{binary-fieldidx}{\B{fieldidx}} .. Modules, non-terminals @@ -670,6 +797,7 @@ .. |Bmemarg| mathdef:: \xref{binary/instructions}{binary-memarg}{\B{memarg}} .. |Bblocktype| mathdef:: \xref{binary/instructions}{binary-blocktype}{\B{blocktype}} +.. |Bcastflags| mathdef:: \xref{binary/instructions}{binary-castflags}{\B{castflags}} .. |Binstr| mathdef:: \xref{binary/instructions}{binary-instr}{\B{instr}} .. |Bexpr| mathdef:: \xref{binary/instructions}{binary-expr}{\B{expr}} @@ -767,10 +895,21 @@ .. |Tnumtype| mathdef:: \xref{text/types}{text-numtype}{\T{numtype}} .. |Tvectype| mathdef:: \xref{text/types}{text-vectype}{\T{vectype}} -.. |Treftype| mathdef:: \xref{text/types}{text-reftype}{\T{reftype}} .. |Theaptype| mathdef:: \xref{text/types}{text-heaptype}{\T{heaptype}} +.. |Tabsheaptype| mathdef:: \xref{text/types}{text-absheaptype}{\T{absheaptype}} +.. |Treftype| mathdef:: \xref{text/types}{text-reftype}{\T{reftype}} .. |Tvaltype| mathdef:: \xref{text/types}{text-valtype}{\T{valtype}} .. |Tfunctype| mathdef:: \xref{text/types}{text-functype}{\T{functype}} +.. |Tstructtype| mathdef:: \xref{text/types}{text-structtype}{\T{structtype}} +.. |Tarraytype| mathdef:: \xref{text/types}{text-arraytype}{\T{arraytype}} +.. |Taggrtype| mathdef:: \xref{text/types}{text-aggrtype}{\T{aggrtype}} +.. |Tfieldtype| mathdef:: \xref{text/types}{text-fieldtype}{\T{fieldtype}} +.. |Tstoragetype| mathdef:: \xref{text/types}{text-storagetype}{\T{storagetype}} +.. |Tpackedtype| mathdef:: \xref{text/types}{text-packedtype}{\T{packedtype}} +.. |Tcomptype| mathdef:: \xref{text/types}{text-comptype}{\T{comptype}} +.. |Tsubtype| mathdef:: \xref{text/types}{text-subtype}{\T{subtype}} +.. |Ttypedef| mathdef:: \xref{text/types}{text-typedef}{\T{typedef}} +.. |Trectype| mathdef:: \xref{text/types}{text-rectype}{\T{rectype}} .. |Tglobaltype| mathdef:: \xref{text/types}{text-globaltype}{\T{globaltype}} .. |Ttabletype| mathdef:: \xref{text/types}{text-tabletype}{\T{tabletype}} @@ -779,6 +918,7 @@ .. |Tparam| mathdef:: \xref{text/types}{text-functype}{\T{param}} .. |Tresult| mathdef:: \xref{text/types}{text-functype}{\T{result}} +.. |Tfield| mathdef:: \xref{text/types}{text-structtype}{\T{field}} .. Indices, non-terminals @@ -792,13 +932,14 @@ .. |Tdataidx| mathdef:: \xref{text/modules}{text-dataidx}{\T{dataidx}} .. |Tlocalidx| mathdef:: \xref{text/modules}{text-localidx}{\T{localidx}} .. |Tlabelidx| mathdef:: \xref{text/modules}{text-labelidx}{\T{labelidx}} +.. |Tfieldidx| mathdef:: \xref{text/modules}{text-fieldidx}{\T{fieldidx}} .. Modules, non-terminals .. |Tmodule| mathdef:: \xref{text/modules}{text-module}{\T{module}} .. |Tmodulefield| mathdef:: \xref{text/modules}{text-modulefield}{\T{modulefield}} -.. |Ttype| mathdef:: \xref{text/modules}{text-typedef}{\T{type}} +.. |Ttype| mathdef:: \xref{text/types}{text-typedef}{\T{type}} .. |Ttypeuse| mathdef:: \xref{text/modules}{text-typeuse}{\T{typeuse}} .. |Tfunc| mathdef:: \xref{text/modules}{text-func}{\T{func}} .. |Ttable| mathdef:: \xref{text/modules}{text-table}{\T{table}} @@ -843,6 +984,7 @@ .. Contexts .. |ITYPEDEFS| mathdef:: \xref{text/conventions}{text-context}{\K{typedefs}} +.. |IFIELDS| mathdef:: \xref{text/conventions}{text-context}{\K{fields}} .. |ITYPES| mathdef:: \xref{text/conventions}{text-context}{\K{types}} .. |IFUNCS| mathdef:: \xref{text/conventions}{text-context}{\K{funcs}} .. |ITABLES| mathdef:: \xref{text/conventions}{text-context}{\K{tables}} @@ -868,8 +1010,36 @@ .. Notation .. |ok| mathdef:: \mathrel{\mbox{ok}} +.. |defaultable| mathdef:: \xref{valid/types}{valid-defaultable}{\mathrel{\mbox{defaultable}}} .. |const| mathdef:: \xref{valid/instructions}{valid-constant}{\mathrel{\mbox{const}}} +.. |matches| mathdef:: \leq +.. |matchesnumtype| mathdef:: \xref{valid/matching}{match-numtype}{\leq} +.. |matchesvectype| mathdef:: \xref{valid/matching}{match-vectype}{\leq} +.. |matchesheaptype| mathdef:: \xref{valid/matching}{match-heaptype}{\leq} +.. |matchesreftype| mathdef:: \xref{valid/matching}{match-reftype}{\leq} +.. |matchesvaltype| mathdef:: \xref{valid/matching}{match-valtype}{\leq} +.. |matchesresulttype| mathdef:: \xref{valid/matching}{match-resulttype}{\leq} +.. |matchesinstrtype| mathdef:: \xref{valid/matching}{match-instrtype}{\leq} +.. |matchesfunctype| mathdef:: \xref{valid/matching}{match-functype}{\leq} +.. |matchesfieldtype| mathdef:: \xref{valid/matching}{match-fieldtype}{\leq} +.. |matchesstoragetype| mathdef:: \xref{valid/matching}{match-storagetype}{\leq} +.. |matchespackedtype| mathdef:: \xref{valid/matching}{match-packedtype}{\leq} +.. |matchesstructtype| mathdef:: \xref{valid/matching}{match-structtype}{\leq} +.. |matchesarraytype| mathdef:: \xref{valid/matching}{match-arraytype}{\leq} +.. |matchescomptype| mathdef:: \xref{valid/matching}{match-comptype}{\leq} +.. |matchesdeftype| mathdef:: \xref{valid/matching}{match-deftype}{\leq} +.. |matchestabletype| mathdef:: \xref{valid/matching}{match-tabletype}{\leq} +.. |matchesmemtype| mathdef:: \xref{valid/matching}{match-memtype}{\leq} +.. |matchesglobaltype| mathdef:: \xref{valid/matching}{match-globaltype}{\leq} +.. |matchesexterntype| mathdef:: \xref{valid/matching}{match-externtype}{\leq} +.. |matcheslimits| mathdef:: \xref{valid/matching}{match-limits}{\leq} + + +.. Meta functions + +.. |clostype| mathdef:: \xref{valid/conventions}{aux-clostype}{\K{clos}} + .. Contexts @@ -884,17 +1054,58 @@ .. |CLABELS| mathdef:: \xref{valid/conventions}{context}{\K{labels}} .. |CRETURN| mathdef:: \xref{valid/conventions}{context}{\K{return}} .. |CREFS| mathdef:: \xref{valid/conventions}{context}{\K{refs}} +.. |CRECS| mathdef:: \xref{appendix/properties}{context-ext}{\K{recs}} .. Judgments +.. |vdashnumtype| mathdef:: \xref{valid/types}{valid-numtype}{\vdash} +.. |vdashvectype| mathdef:: \xref{valid/types}{valid-vectype}{\vdash} +.. |vdashheaptype| mathdef:: \xref{valid/types}{valid-heaptype}{\vdash} +.. |vdashreftype| mathdef:: \xref{valid/types}{valid-reftype}{\vdash} +.. |vdashvaltype| mathdef:: \xref{valid/types}{valid-valtype}{\vdash} +.. |vdashresulttype| mathdef:: \xref{valid/types}{valid-resulttype}{\vdash} .. |vdashlimits| mathdef:: \xref{valid/types}{valid-limits}{\vdash} .. |vdashblocktype| mathdef:: \xref{valid/types}{valid-blocktype}{\vdash} .. |vdashfunctype| mathdef:: \xref{valid/types}{valid-functype}{\vdash} +.. |vdashstructtype| mathdef:: \xref{valid/types}{valid-structtype}{\vdash} +.. |vdasharraytype| mathdef:: \xref{valid/types}{valid-arraytype}{\vdash} +.. |vdashcomptype| mathdef:: \xref{valid/types}{valid-comptype}{\vdash} +.. |vdashfieldtype| mathdef:: \xref{valid/types}{valid-fieldtype}{\vdash} +.. |vdashpackedtype| mathdef:: \xref{valid/types}{valid-packedtype}{\vdash} +.. |vdashstoragetype| mathdef:: \xref{valid/types}{valid-storagetype}{\vdash} +.. |vdashsubtype| mathdef:: \xref{valid/types}{valid-subtype}{\vdash} +.. |vdashrectype| mathdef:: \xref{valid/types}{valid-rectype}{\vdash} .. |vdashtabletype| mathdef:: \xref{valid/types}{valid-tabletype}{\vdash} .. |vdashmemtype| mathdef:: \xref{valid/types}{valid-memtype}{\vdash} .. |vdashglobaltype| mathdef:: \xref{valid/types}{valid-globaltype}{\vdash} .. |vdashexterntype| mathdef:: \xref{valid/types}{valid-externtype}{\vdash} +.. |vdashdeftype| mathdef:: \xref{valid/types}{valid-deftype}{\vdash} + +.. |vdashinstrtype| mathdef:: \xref{valid/types}{valid-instrtype}{\vdash} + +.. |vdashvaltypedefaultable| mathdef:: \xref{valid/types}{valid-defaultable}{\vdash} + +.. |vdashnumtypematch| mathdef:: \xref{valid/matching}{match-numtype}{\vdash} +.. |vdashvectypematch| mathdef:: \xref{valid/matching}{match-vectype}{\vdash} +.. |vdashheaptypematch| mathdef:: \xref{valid/matching}{match-heaptype}{\vdash} +.. |vdashreftypematch| mathdef:: \xref{valid/matching}{match-reftype}{\vdash} +.. |vdashvaltypematch| mathdef:: \xref{valid/matching}{match-valtype}{\vdash} +.. |vdashresulttypematch| mathdef:: \xref{valid/matching}{match-resulttype}{\vdash} +.. |vdashinstrtypematch| mathdef:: \xref{valid/matching}{match-instrtype}{\vdash} +.. |vdashfunctypematch| mathdef:: \xref{valid/matching}{match-functype}{\vdash} +.. |vdashfieldtypematch| mathdef:: \xref{valid/matching}{match-fieldtype}{\vdash} +.. |vdashstoragetypematch| mathdef:: \xref{valid/matching}{match-storagetype}{\vdash} +.. |vdashpackedtypematch| mathdef:: \xref{valid/matching}{match-packedtype}{\vdash} +.. |vdashstructtypematch| mathdef:: \xref{valid/matching}{match-structtype}{\vdash} +.. |vdasharraytypematch| mathdef:: \xref{valid/matching}{match-arraytype}{\vdash} +.. |vdashcomptypematch| mathdef:: \xref{valid/matching}{match-comptype}{\vdash} +.. |vdashdeftypematch| mathdef:: \xref{valid/matching}{match-deftype}{\vdash} +.. |vdashtabletypematch| mathdef:: \xref{valid/matching}{match-tabletype}{\vdash} +.. |vdashmemtypematch| mathdef:: \xref{valid/matching}{match-memtype}{\vdash} +.. |vdashglobaltypematch| mathdef:: \xref{valid/matching}{match-globaltype}{\vdash} +.. |vdashexterntypematch| mathdef:: \xref{valid/matching}{match-externtype}{\vdash} +.. |vdashlimitsmatch| mathdef:: \xref{valid/matching}{match-limits}{\vdash} .. |vdashinstr| mathdef:: \xref{valid/instructions}{valid-instr}{\vdash} .. |vdashinstrseq| mathdef:: \xref{valid/instructions}{valid-instr-seq}{\vdash} @@ -902,10 +1113,13 @@ .. |vdashexprconst| mathdef:: \xref{valid/instructions}{valid-constant}{\vdash} .. |vdashinstrconst| mathdef:: \xref{valid/instructions}{valid-constant}{\vdash} +.. |vdashtypes| mathdef:: \xref{valid/modules}{valid-types}{\vdash} .. |vdashfunc| mathdef:: \xref{valid/modules}{valid-func}{\vdash} +.. |vdashlocal| mathdef:: \xref{valid/modules}{valid-local}{\vdash} .. |vdashtable| mathdef:: \xref{valid/modules}{valid-table}{\vdash} .. |vdashmem| mathdef:: \xref{valid/modules}{valid-mem}{\vdash} .. |vdashglobal| mathdef:: \xref{valid/modules}{valid-global}{\vdash} +.. |vdashglobals| mathdef:: \xref{valid/modules}{valid-globalseq}{\vdash} .. |vdashelem| mathdef:: \xref{valid/modules}{valid-elem}{\vdash} .. |vdashelemmode| mathdef:: \xref{valid/modules}{valid-elemmode}{\vdash} .. |vdashdata| mathdef:: \xref{valid/modules}{valid-data}{\vdash} @@ -917,7 +1131,7 @@ .. |vdashimportdesc| mathdef:: \xref{valid/modules}{valid-importdesc}{\vdash} .. |vdashmodule| mathdef:: \xref{valid/modules}{valid-module}{\vdash} -.. |unpacked| mathdef:: \xref{valid/instructions}{aux-unpacked}{\F{unpacked}} +.. |unpackshape| mathdef:: \xref{valid/instructions}{aux-unpackshape}{\F{unpack}} .. |dim| mathdef:: \xref{valid/instructions}{aux-dim}{\F{dim}} @@ -928,12 +1142,13 @@ .. |stepto| mathdef:: \xref{exec/conventions}{exec-notation}{\hookrightarrow} .. |extendsto| mathdef:: \xref{appendix/properties}{extend}{\preceq} -.. |matchesexterntype| mathdef:: \xref{valid/types}{match-externtype}{\leq} -.. |matcheslimits| mathdef:: \xref{valid/types}{match-limits}{\leq} .. Allocation +.. |insttype| mathdef:: \xref{exec/types}{type-inst}{\F{clos}} + +.. |alloctype| mathdef:: \xref{exec/modules}{alloc-type}{\F{alloctype}} .. |allocfunc| mathdef:: \xref{exec/modules}{alloc-func}{\F{allocfunc}} .. |allochostfunc| mathdef:: \xref{exec/modules}{alloc-hostfunc}{\F{allochostfunc}} .. |alloctable| mathdef:: \xref{exec/modules}{alloc-table}{\F{alloctable}} @@ -956,7 +1171,20 @@ .. |globaladdr| mathdef:: \xref{exec/runtime}{syntax-globaladdr}{\X{globaladdr}} .. |elemaddr| mathdef:: \xref{exec/runtime}{syntax-elemaddr}{\X{elemaddr}} .. |dataaddr| mathdef:: \xref{exec/runtime}{syntax-dataaddr}{\X{dataaddr}} -.. |externaddr| mathdef:: \xref{exec/runtime}{syntax-externaddr}{\X{externaddr}} +.. |structaddr| mathdef:: \xref{exec/runtime}{syntax-structaddr}{\X{structaddr}} +.. |arrayaddr| mathdef:: \xref{exec/runtime}{syntax-arrayaddr}{\X{arrayaddr}} +.. |hostaddr| mathdef:: \xref{exec/runtime}{syntax-hostaddr}{\X{hostaddr}} + + +.. Address, meta functions + +.. |freefuncaddr| mathdef:: \xref{exec/runtime}{syntax-funcaddr}{\F{funcaddr}} +.. |freetableaddr| mathdef:: \xref{exec/runtime}{syntax-tableaddr}{\F{tableaddr}} +.. |freememaddr| mathdef:: \xref{exec/runtime}{syntax-memaddr}{\F{memaddr}} +.. |freeglobaladdr| mathdef:: \xref{exec/runtime}{syntax-globaladdr}{\F{globaladdr}} +.. |freeelemaddr| mathdef:: \xref{exec/runtime}{syntax-elemaddr}{\F{elemaddr}} +.. |freedataaddr| mathdef:: \xref{exec/runtime}{syntax-dataaddr}{\F{dataaddr}} + .. Instances, terminals @@ -996,9 +1224,22 @@ .. |MIDATAS| mathdef:: \xref{exec/runtime}{syntax-moduleinst}{\K{dataaddrs}} .. |MIEXPORTS| mathdef:: \xref{exec/runtime}{syntax-moduleinst}{\K{exports}} +.. |SITYPE| mathdef:: \xref{exec/runtime}{syntax-structinst}{\K{type}} +.. |SIFIELDS| mathdef:: \xref{exec/runtime}{syntax-structinst}{\K{fields}} + +.. |AITYPE| mathdef:: \xref{exec/runtime}{syntax-arrayinst}{\K{type}} +.. |AIFIELDS| mathdef:: \xref{exec/runtime}{syntax-arrayinst}{\K{fields}} + +.. |PACK| mathdef:: \xref{exec/runtime}{syntax-packedval}{\K{pack}} +.. |I8PACK| mathdef:: \xref{exec/runtime}{syntax-packedval}{\K{i8.pack}} +.. |I16PACK| mathdef:: \xref{exec/runtime}{syntax-packedval}{\K{i16.pack}} + .. Instances, non-terminals +.. |fieldval| mathdef:: \xref{exec/runtime}{syntax-fieldval}{\X{fieldval}} +.. |packedval| mathdef:: \xref{exec/runtime}{syntax-packedval}{\X{packedval}} + .. |externval| mathdef:: \xref{exec/runtime}{syntax-externval}{\X{externval}} .. |moduleinst| mathdef:: \xref{exec/runtime}{syntax-moduleinst}{\X{moduleinst}} @@ -1008,6 +1249,8 @@ .. |globalinst| mathdef:: \xref{exec/runtime}{syntax-globalinst}{\X{globalinst}} .. |eleminst| mathdef:: \xref{exec/runtime}{syntax-eleminst}{\X{eleminst}} .. |datainst| mathdef:: \xref{exec/runtime}{syntax-datainst}{\X{datainst}} +.. |structinst| mathdef:: \xref{exec/runtime}{syntax-structinst}{\X{structinst}} +.. |arrayinst| mathdef:: \xref{exec/runtime}{syntax-arrayinst}{\X{arrayinst}} .. |exportinst| mathdef:: \xref{exec/runtime}{syntax-exportinst}{\X{exportinst}} .. |hostfunc| mathdef:: \xref{exec/runtime}{syntax-hostfunc}{\X{hostfunc}} @@ -1029,6 +1272,8 @@ .. |SGLOBALS| mathdef:: \xref{exec/runtime}{syntax-store}{\K{globals}} .. |SELEMS| mathdef:: \xref{exec/runtime}{syntax-store}{\K{elems}} .. |SDATAS| mathdef:: \xref{exec/runtime}{syntax-store}{\K{datas}} +.. |SSTRUCTS| mathdef:: \xref{exec/runtime}{syntax-store}{\K{structs}} +.. |SARRAYS| mathdef:: \xref{exec/runtime}{syntax-store}{\K{arrays}} .. Store, non-terminals @@ -1053,15 +1298,20 @@ .. Stack, meta functions -.. |expand| mathdef:: \xref{exec/runtime}{exec-expand}{\F{expand}} +.. |fblocktype| mathdef:: \xref{exec/runtime}{aux-fblocktype}{\F{instrtype}} .. Administrative Instructions, terminals -.. |REFFUNCADDR| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref}} -.. |REFEXTERNADDR| mathdef:: \xref{exec/runtime}{syntax-ref.extern}{\K{ref{.}extern}} +.. |REFI31NUM| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}i31}} +.. |REFFUNCADDR| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}func}} +.. |REFSTRUCTADDR| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}struct}} +.. |REFARRAYADDR| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}array}} +.. |REFHOSTADDR| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}host}} +.. |REFEXTERN| mathdef:: \xref{exec/runtime}{syntax-ref}{\K{ref{.}extern}} .. |TRAP| mathdef:: \xref{exec/runtime}{syntax-trap}{\K{trap}} .. |INVOKE| mathdef:: \xref{exec/runtime}{syntax-invoke}{\K{invoke}} +.. |RETURNINVOKE| mathdef:: \xref{exec/runtime}{syntax-return_invoke}{\K{return\_invoke}} .. Values & Results, non-terminals @@ -1078,6 +1328,12 @@ .. |default| mathdef:: \xref{exec/runtime}{default-val}{\F{default}} +.. Fields, meta-functions + +.. |packval| mathdef:: \xref{exec/runtime}{aux-packval}{\F{pack}} +.. |unpackval| mathdef:: \xref{exec/runtime}{aux-unpackval}{\F{unpack}} + + .. Administrative Instructions, non-terminals .. |XB| mathdef:: \xref{exec/runtime}{syntax-ctxt-block}{B} @@ -1188,7 +1444,7 @@ .. |bytes| mathdef:: \xref{exec/numerics}{aux-bytes}{\F{bytes}} .. |littleendian| mathdef:: \xref{exec/numerics}{aux-littleendian}{\F{littleendian}} .. |signed| mathdef:: \xref{exec/numerics}{aux-signed}{\F{signed}} -.. |bool| mathdef:: \xref{exec/numerics}{aux-bool}{\F{bool}} +.. |tobool| mathdef:: \xref{exec/numerics}{aux-tobool}{\F{bool}} .. |ieee| mathdef:: \xref{exec/numerics}{aux-ieee}{\F{float}} .. |nans| mathdef:: \xref{exec/numerics}{aux-nans}{\F{nans}} @@ -1207,10 +1463,7 @@ .. Judgements -.. |vdashexternval| mathdef:: \xref{exec/modules}{valid-externval}{\vdash} - -.. |vdashlimitsmatch| mathdef:: \xref{valid/types}{match-limits}{\vdash} -.. |vdashexterntypematch| mathdef:: \xref{valid/types}{match-externtype}{\vdash} +.. |vdashexternval| mathdef:: \xref{exec/values}{valid-externval}{\vdash} .. Soundness @@ -1220,8 +1473,10 @@ .. |vdashadmininstr| mathdef:: \xref{appendix/properties}{valid-instr-admin}{\vdash} -.. |vdashval| mathdef:: \xref{exec/modules}{valid-val}{\vdash} +.. |vdashval| mathdef:: \xref{exec/values}{valid-val}{\vdash} .. |vdashresult| mathdef:: \xref{appendix/properties}{valid-result}{\vdash} +.. |vdashfieldval| mathdef:: \xref{appendix/properties}{valid-fieldval}{\vdash} +.. |vdashpackedval| mathdef:: \xref{appendix/properties}{valid-packedval}{\vdash} .. |vdashfuncinst| mathdef:: \xref{appendix/properties}{valid-funcinst}{\vdash} .. |vdashtableinst| mathdef:: \xref{appendix/properties}{valid-tableinst}{\vdash} @@ -1229,9 +1484,12 @@ .. |vdashglobalinst| mathdef:: \xref{appendix/properties}{valid-globalinst}{\vdash} .. |vdasheleminst| mathdef:: \xref{appendix/properties}{valid-eleminst}{\vdash} .. |vdashdatainst| mathdef:: \xref{appendix/properties}{valid-datainst}{\vdash} +.. |vdashstructinst| mathdef:: \xref{appendix/properties}{valid-structinst}{\vdash} +.. |vdasharrayinst| mathdef:: \xref{appendix/properties}{valid-arrayinst}{\vdash} .. |vdashexportinst| mathdef:: \xref{appendix/properties}{valid-exportinst}{\vdash} .. |vdashmoduleinst| mathdef:: \xref{appendix/properties}{valid-moduleinst}{\vdash} +.. |vdashcontext| mathdef:: \xref{appendix/properties}{valid-context}{\vdash} .. |vdashstore| mathdef:: \xref{appendix/properties}{valid-store}{\vdash} .. |vdashconfig| mathdef:: \xref{appendix/properties}{valid-config}{\vdash} .. |vdashthread| mathdef:: \xref{appendix/properties}{valid-thread}{\vdash} @@ -1243,6 +1501,8 @@ .. |vdashglobalinstextends| mathdef:: \xref{appendix/properties}{extend-globalinst}{\vdash} .. |vdasheleminstextends| mathdef:: \xref{appendix/properties}{extend-eleminst}{\vdash} .. |vdashdatainstextends| mathdef:: \xref{appendix/properties}{extend-datainst}{\vdash} +.. |vdashstructinstextends| mathdef:: \xref{appendix/properties}{extend-structinst}{\vdash} +.. |vdasharrayinstextends| mathdef:: \xref{appendix/properties}{extend-arrayinst}{\vdash} .. |vdashstoreextends| mathdef:: \xref{appendix/properties}{extend-store}{\vdash} @@ -1263,10 +1523,16 @@ .. |Bmodulenamesubsec| mathdef:: \xref{appendix/custom}{binary-modulenamesec}{\B{modulenamesubsec}} .. |Bfuncnamesubsec| mathdef:: \xref{appendix/custom}{binary-funcnamesec}{\B{funcnamesubsec}} .. |Blocalnamesubsec| mathdef:: \xref{appendix/custom}{binary-localnamesec}{\B{localnamesubsec}} +.. |Btypenamesubsec| mathdef:: \xref{appendix/custom}{binary-typenamesec}{\B{typenamesubsec}} +.. |Bfieldnamesubsec| mathdef:: \xref{appendix/custom}{binary-fieldnamesec}{\B{fieldnamesubsec}} .. Embedding .. --------- +.. |bool| mathdef:: \xref{appendix/embedding}{embed-bool}{\X{bool}} +.. |TRUE| mathdef:: \xref{appendix/embedding}{embed-bool}{\X{true}} +.. |FALSE| mathdef:: \xref{appendix/embedding}{embed-bool}{\X{false}} + .. |error| mathdef:: \xref{appendix/embedding}{embed-error}{\X{error}} .. |ERROR| mathdef:: \xref{appendix/embedding}{embed-error}{\K{error}} diff --git a/document/core/valid/conventions.rst b/document/core/valid/conventions.rst index ad860ccc14..f443749941 100644 --- a/document/core/valid/conventions.rst +++ b/document/core/valid/conventions.rst @@ -24,7 +24,222 @@ That is, they only formulate the constraints, they do not define an algorithm. The skeleton of a sound and complete algorithm for type-checking instruction sequences according to this specification is provided in the :ref:`appendix `. -.. index:: ! context, function type, table type, memory type, global type, value type, result type, index space, module, function +.. index:: heap type, abstract type, concrete type, type index, ! recursive type index, ! closed type, rolling, unrolling, sub type, subtyping, ! bottom type + pair: abstract syntax; value type + pair: abstract syntax; heap type + pair: abstract syntax; sub type + pair: abstract syntax; recursive type index +.. _syntax-rectypeidx: +.. _syntax-valtype-ext: +.. _syntax-heaptype-ext: +.. _syntax-subtype-ext: +.. _type-ext: +.. _type-closed: + +Types +~~~~~ + +To define the semantics, the definition of some sorts of types is extended to include additional forms. +By virtue of not being representable in either the :ref:`binary format ` or the :ref:`text format `, +these forms cannot be used in a program; +they only occur during :ref:`validation ` or :ref:`execution `. + +.. math:: + \begin{array}{llrl} + \production{value type} & \valtype &::=& + \dots ~|~ \BOT \\ + \production{abstract heap type} & \absheaptype &::=& + \dots ~|~ \BOTH \\ + \production{heap type} & \heaptype &::=& + \dots ~|~ \deftype ~|~ \REC~i \\ + \production{sub types} & \subtype &::=& + \TSUB~\TFINAL^?~\heaptype^\ast~\comptype \\ + \end{array} + +The unique :ref:`value type ` |BOT| is a *bottom type* that :ref:`matches ` all value types. +Similarly, |BOTH| is also used as a bottom type of all :ref:`heap types `. + +.. note:: + No validation rule uses bottom types explicitly, + but various rules can pick any value or heap type, including bottom. + This ensures the existence of :ref:`principal types `, + and thus a :ref:`validation algorithm ` without back tracking. + +A :ref:`concrete heap type ` can consist of a :ref:`defined type ` directly. +this occurs as the result of :ref:`substituting ` a :ref:`type index ` with its definition. + +A concrete heap type may also be a *recursive type index*. +Such an index refers to the :math:`i`-th component of a surrounding :ref:`recursive type `. +It occurs as the result of :ref:`rolling up ` the definition of a :ref:`recursive type `. + +Finally, the representation of supertypes in a :ref:`sub type ` is generalized from mere :ref:`type indices ` to :ref:`heap types `. +They occur as :ref:`defined types ` or :ref:`recursive type indices ` after :ref:`substituting ` type indices or :ref:`rolling up ` :ref:`recursive types `. + +.. note:: + It is an invariant of the semantics that sub types occur only in one of two forms: + either as "syntactic" types as in a source module, where all supertypes are type indices, + or as "semantic" types, where all supertypes are resolved to either defined types or recursive type indices. + +A type of any form is *closed* when it does not contain a heap type that is a :ref:`type index ` or a recursive type index without a surrounding :ref:`recursive type `, +i.e., all :ref:`type indices ` have been :ref:`substituted ` with their :ref:`defined type ` and all free recursive type indices have been :ref:`unrolled `. + +.. note:: + Recursive type indices are internal to a recursive type. + They are distinguished from regular type indices and represented such that two closed types are syntactically equal if and only if they have the same recursive structure. + +.. _aux-reftypediff: + +Convention +.......... + +* The *difference* :math:`\X{rt}_1\reftypediff\X{rt}_2` between two :ref:`reference types ` is defined as follows: + + .. math:: + \begin{array}{lll} + (\REF~\NULL_1^?~\X{ht}_1) \reftypediff (\REF~\NULL~\X{ht}_2) &=& (\REF~\X{ht}_1) \\ + (\REF~\NULL_1^?~\X{ht}_1) \reftypediff (\REF~\X{ht}_2) &=& (\REF~\NULL_1^?~\X{ht}_1) \\ + \end{array} + +.. note:: + This definition computes an approximation of the reference type that is inhabited by all values from :math:`\X{rt}_1` except those from :math:`\X{rt}_2`. + Since the type system does not have general union types, + the defnition only affects the presence of null and cannot express the absence of other values. + + +.. index:: ! defined type, recursive type + pair: abstract syntax; defined type +.. _syntax-deftype: + +Defined Types +~~~~~~~~~~~~~ + +*Defined types* denote the individual types defined in a :ref:`module `. +Each such type is represented as a projection from the :ref:`recursive type ` group it originates from, indexed by its position in that group. + +.. math:: + \begin{array}{llrl} + \production{defined type} & \deftype &::=& + \rectype.i \\ + \end{array} + +Defined types do not occur in the :ref:`binary ` or :ref:`text ` format, +but are formed by :ref:`rolling up ` the :ref:`recursive types ` defined in a module. + +It is hence an invariant of the semantics that all :ref:`recursive types ` occurring in defined types are :ref:`rolled up `. + + +.. index:: ! substitution +.. _type-subst: +.. _notation-subst: + +Conventions +........... + +* :math:`t[x^\ast \subst \X{dt}^\ast]` denotes the parallel *substitution* of :ref:`type indices ` :math:`x^\ast` with :ref:`defined types ` :math:`\X{dt}^\ast` in type :math:`t`, provided :math:`|x^\ast| = |\X{dt}^\ast|`. + +* :math:`t[(\REC~i)^\ast \subst \X{dt}^\ast]` denotes the parallel *substitution* of :ref:`recursive type indices ` :math:`(\REC~i)^\ast` with :ref:`defined types ` :math:`\X{dt}^\ast` in type :math:`t`, provided :math:`|(\REC~i)^\ast| = |\X{dt}^\ast|`. + +* :math:`t[\subst \X{dt}^\ast]` is shorthand for the substitution :math:`t[x^\ast \subst \X{dt}^\ast]`, where :math:`x^\ast = 0 \cdots (|\X{dt}^\ast| - 1)`. + + +.. index:: recursive type, defined type, sub type, ! rolling, ! unrolling, ! expansion, type equivalence +.. _aux-roll-rectype: +.. _aux-unroll-rectype: +.. _aux-roll-deftype: +.. _aux-unroll-deftype: +.. _aux-expand-deftype: + +Rolling and Unrolling +~~~~~~~~~~~~~~~~~~~~~ + +In order to allow comparing :ref:`recursive types ` for :ref:`equivalence `, their representation is changed such that all :ref:`type indices ` internal to the same recursive type are replaced by :ref:`recursive type indices `. + +.. note:: + This representation is independent of the type index space, + so that it is meaningful across module boundaries. + Moreover, this representation ensures that types with equivalent recursive structure are also syntactically equal, + hence allowing a simple equality check on (closed) types. + It gives rise to an *iso-recursive* interpretation of types. + +The representation change is performed by two auxiliary operations on the syntax of :ref:`recursive types `: + +* *Rolling up* a recursive type :ref:`substitutes ` its internal :ref:`type indices ` with corresponding :ref:`recursive type indices `. + +* *Unrolling* a recursive type :ref:`substitutes ` its :ref:`recursive type indices ` with the corresponding :ref:`defined types `. + +These operations are extended to :ref:`defined types ` and defined as follows: + +.. math:: + \begin{array}{@{}l@{~}l@{~}l@{~}r@{~}l@{}} + \rollrt_{x}(\TREC~\subtype^\ast) &=& \TREC~(\subtype[(x + i)^\ast \subst (\REC~i)^\ast])^\ast + & (\iff & i^\ast = 0 \cdots (|\subtype^\ast| - 1)) \\ + \unrollrt(\TREC~\subtype^\ast) &=& \TREC~(\subtype[(\REC~i)^\ast \subst ((\TREC~\subtype^\ast).i)^\ast])^\ast + & (\iff & i^\ast = 0 \cdots (|\subtype^\ast| - 1)) \\[2ex] + \rolldt_{x}(\rectype) &=& ((\TREC~\subtype^\ast).i)^\ast + & (\iff & i^\ast = 0 \cdots (|\subtype^\ast| - 1) \\ + &&& \land & \rollrt_{x}(\rectype) = \TREC~\subtype^\ast) \\ + \unrolldt(\rectype.i) &=& \subtype^\ast[i] + & (\iff & \unrollrt(\rectype) = \TREC~\subtype^\ast) \\ + \end{array} + +In addition, the following auxiliary function denotes the *expansion* of a :ref:`defined type `: + +.. math:: + \begin{array}{@{}llll@{}} + \expanddt(\deftype) &=& \comptype & (\iff \unrolldt(\deftype) = \TSUB~\TFINAL^?~\X{ht}^\ast~\comptype) \\ + \end{array} + + +.. index:: ! instruction type, value type, result type, instruction, local, local index + pair: abstract syntax; instruction type + pair: instruction; type +.. _syntax-instrtype: + +Instruction Types +~~~~~~~~~~~~~~~~~ + +*Instruction types* classify the behaviour of :ref:`instructions ` or instruction sequences, by describing how they manipulate the :ref:`operand stack ` and the initialization status of :ref:`locals `: + +.. math:: + \begin{array}{llrl} + \production{instruction type} & \instrtype &::=& + \resulttype \to_{\localidx^\ast} \resulttype \\ + \end{array} + +An instruction type :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]` describes the required input stack with argument values of types :math:`t_1^\ast` that an instruction pops off +and the provided output stack with result values of types :math:`t_2^\ast` that it pushes back. +Moreover, it enumerates the :ref:`indices ` :math:`x^\ast` of locals that have been set by the instruction or sequence. + +.. note:: + Instruction types are only used for :ref:`validation `, + they do not occur in programs. + + +.. index:: ! local type, value type, local, local index + pair: abstract syntax; local type + pair: local; type +.. _syntax-init: +.. _syntax-localtype: + +Local Types +~~~~~~~~~~~ + +*Local types* classify :ref:`locals `, by describing their :ref:`value type ` as well as their *initialization status*: + +.. math:: + \begin{array}{llrl} + \production{initialization status} & \init &::=& + \SET ~|~ \UNSET \\ + \production{local type} & \localtype &::=& + \init~\valtype \\ + \end{array} + +.. note:: + Local types are only used for :ref:`validation `, + they do not occur in programs. + + +.. index:: ! context, local type, function type, table type, memory type, global type, value type, result type, index space, module, function, local type .. _context: Contexts @@ -33,16 +248,16 @@ Contexts Validity of an individual definition is specified relative to a *context*, which collects relevant information about the surrounding :ref:`module ` and the definitions in scope: -* *Types*: the list of types defined in the current module. -* *Functions*: the list of functions declared in the current module, represented by their function type. -* *Tables*: the list of tables declared in the current module, represented by their table type. -* *Memories*: the list of memories declared in the current module, represented by their memory type. -* *Globals*: the list of globals declared in the current module, represented by their global type. -* *Element Segments*: the list of element segments declared in the current module, represented by their element type. -* *Data Segments*: the list of data segments declared in the current module, each represented by an |ok| entry. -* *Locals*: the list of locals declared in the current function (including parameters), represented by their value type. -* *Labels*: the stack of labels accessible from the current position, represented by their result type. -* *Return*: the return type of the current function, represented as an optional result type that is absent when no return is allowed, as in free-standing expressions. +* *Types*: the list of :ref:`types ` defined in the current module. +* *Functions*: the list of :ref:`functions ` declared in the current module, represented by a :ref:`defined type ` that :ref:`expands ` to their :ref:`function type `. +* *Tables*: the list of :ref:`tables ` declared in the current module, represented by their :ref:`table type `. +* *Memories*: the list of :ref:`memories ` declared in the current module, represented by their :ref:`memory type `. +* *Globals*: the list of :ref:`globals ` declared in the current module, represented by their :ref:`global type `. +* *Element Segments*: the list of :ref:`element segments ` declared in the current module, represented by the elements' :ref:`reference type `. +* *Data Segments*: the list of :ref:`data segments ` declared in the current module, each represented by an |ok| entry. +* *Locals*: the list of :ref:`locals ` declared in the current :ref:`function ` (including parameters), represented by their :ref:`local type `. +* *Labels*: the stack of :ref:`labels ` accessible from the current position, represented by their :ref:`result type `. +* *Return*: the return type of the current :ref:`function `, represented as an optional :ref:`result type ` that is absent when no return is allowed, as in free-standing expressions. * *References*: the list of :ref:`function indices ` that occur in the module outside functions and can hence be used to form references inside them. In other words, a context contains a sequence of suitable :ref:`types ` for each :ref:`index space `, @@ -56,18 +271,18 @@ More concretely, contexts are defined as :ref:`records ` :math: \begin{array}{llll} \production{context} & C &::=& \begin{array}[t]{l@{~}ll} - \{ & \CTYPES & \functype^\ast, \\ - & \CFUNCS & \functype^\ast, \\ + \{ & \CTYPES & \deftype^\ast, \\ + & \CFUNCS & \deftype^\ast, \\ & \CTABLES & \tabletype^\ast, \\ & \CMEMS & \memtype^\ast, \\ & \CGLOBALS & \globaltype^\ast, \\ & \CELEMS & \reftype^\ast, \\ & \CDATAS & {\ok}^\ast, \\ - & \CLOCALS & \valtype^\ast, \\ + & \CLOCALS & \localtype^\ast, \\ & \CLABELS & \resulttype^\ast, \\ & \CRETURN & \resulttype^?, \\ & \CREFS & \funcidx^\ast ~\} \\ - \end{array} + \end{array} \\ \end{array} .. _notation-extend: @@ -84,6 +299,23 @@ In addition to field access written :math:`C.\K{field}` the following notation i Accordingly, the notation is defined to append at the *front* of the respective sequence, introducing a new relative index :math:`0` and shifting the existing ones. +.. index:: ! type closure +.. _type-closure: +.. _aux-clostype: + +Convention +.......... + +Any form of :ref:`type ` can be *closed* to bring it into :ref:`closed ` form relative to a :ref:`context ` it is :ref:`valid ` in by :ref:`substituting ` each :ref:`type index ` :math:`x` occurring in it with the corresponding :ref:`defined type ` :math:`C.\CTYPES[x]`, after first closing the the types in :math:`C.\CTYPES` themselves. + +.. math:: + \begin{array}{@{}lcll@{}} + \clostype_C(t) &=& t[\subst \clostype^\ast(C.\CTYPES)] \\[2ex] + \clostype^\ast(\epsilon) &=& \epsilon \\ + \clostype^\ast(\X{dt}^\ast~\X{dt}_N) &=& {\X{dt}'}^\ast~\X{dt}_N[\subst {\X{dt}'}^\ast] & (\iff {\X{dt}'}^\ast = \clostype^\ast(\X{dt}^\ast)) \\ + \end{array} + + .. _valid-notation-textual: Prose Notation @@ -158,15 +390,15 @@ and there is one respective rule for each relevant construct :math:`A` of the ab .. math:: \frac{ - C.\CLOCALS[x] = t + C.\CGLOBALS[x] = \mut~t }{ - C \vdash \LOCALGET~x : [] \to [t] + C \vdash \GLOBALGET~x : [] \to [t] } - Here, the premise enforces that the immediate :ref:`local index ` :math:`x` exists in the context. + Here, the premise enforces that the immediate :ref:`global index ` :math:`x` exists in the context. The instruction produces a value of its respective type :math:`t` (and does not consume any values). - If :math:`C.\CLOCALS[x]` does not exist then the premise does not hold, + If :math:`C.\CGLOBALS[x]` does not exist then the premise does not hold, and the instruction is ill-typed. Finally, a :ref:`structured ` instruction requires diff --git a/document/core/valid/index.rst b/document/core/valid/index.rst index 34cf3b9066..cd5fce1869 100644 --- a/document/core/valid/index.rst +++ b/document/core/valid/index.rst @@ -8,5 +8,6 @@ Validation conventions types + matching instructions modules diff --git a/document/core/valid/instructions.rst b/document/core/valid/instructions.rst index 44bd241912..139b5a5841 100644 --- a/document/core/valid/instructions.rst +++ b/document/core/valid/instructions.rst @@ -1,54 +1,22 @@ -.. index:: instruction, function type, context, value, operand stack, ! polymorphism, ! bottom type +.. index:: instruction, ! instruction type, context, value, operand stack, ! polymorphism .. _valid-instr: -.. _syntax-stacktype: -.. _syntax-opdtype: Instructions ------------ -:ref:`Instructions ` are classified by *stack types* :math:`[t_1^\ast] \to [t_2^\ast]` that describe how instructions manipulate the :ref:`operand stack `. - -.. math:: - \begin{array}{llll} - \production{stack type} & \stacktype &::=& - [\opdtype^\ast] \to [\opdtype^\ast] \\ - \production{operand type} & \opdtype &::=& - \valtype ~|~ \bot \\ - \end{array} - -The types describe the required input stack with *operand types* :math:`t_1^\ast` that an instruction pops off +:ref:`Instructions ` are classified by :ref:`instruction types ` that describe how they manipulate the :ref:`operand stack ` and initialize :ref:`locals `: +A type :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]` describes the required input stack with argument values of types :math:`t_1^\ast` that an instruction pops off and the provided output stack with result values of types :math:`t_2^\ast` that it pushes back. -Stack types are akin to :ref:`function types `, -except that they allow individual operands to be classified as :math:`\bot` (*bottom*), indicating that the type is unconstrained. -As an auxiliary notion, an operand type :math:`t_1` *matches* another operand type :math:`t_2`, if :math:`t_1` is either :math:`\bot` or equal to :math:`t_2`. -This is extended to stack types in a point-wise manner. - -.. _match-opdtype: - -.. math:: - \frac{ - }{ - \vdash t \leq t - } - \qquad - \frac{ - }{ - \vdash \bot \leq t - } - -.. math:: - \frac{ - (\vdash t \leq t')^\ast - }{ - \vdash [t^\ast] \leq [{t'}^\ast] - } +Moreover, it enumerates the :ref:`indices ` :math:`x^\ast` of locals that have been set by the instruction. +In most cases, this is empty. .. note:: For example, the instruction :math:`\I32.\ADD` has type :math:`[\I32~\I32] \to [\I32]`, consuming two |I32| values and producing one. + The instruction :math:`\LOCALSET~x` has type :math:`[t] \to_{x} []`, provided :math:`t` is the type declared for the local :math:`x`. Typing extends to :ref:`instruction sequences ` :math:`\instr^\ast`. -Such a sequence has a :ref:`stack type ` :math:`[t_1^\ast] \to [t_2^\ast]` if the accumulative effect of executing the instructions is consuming values of types :math:`t_1^\ast` off the operand stack and pushing new values of types :math:`t_2^\ast`. +Such a sequence has an instruction type :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]` if the accumulative effect of executing the instructions is consuming values of types :math:`t_1^\ast` off the operand stack, pushing new values of types :math:`t_2^\ast`, and setting all locals :math:`x^\ast`. .. _polymorphism: @@ -61,9 +29,8 @@ Two degrees of polymorphism can be distinguished: the :ref:`value type ` :math:`t` of one or several individual operands is unconstrained. That is the case for all :ref:`parametric instructions ` like |DROP| and |SELECT|. - * *stack-polymorphic*: - the entire (or most of the) :ref:`stack type ` :math:`[t_1^\ast] \to [t_2^\ast]` of the instruction is unconstrained. + the entire (or most of the) :ref:`instruction type ` :math:`[t_1^\ast] \to [t_2^\ast]` of the instruction is unconstrained. That is the case for all :ref:`control instructions ` that perform an *unconditional control transfer*, such as |UNREACHABLE|, |BR|, |BRTABLE|, and |RETURN|. In both cases, the unconstrained types or type sequences can be chosen arbitrarily, as long as they meet the constraints imposed for the surrounding parts of the program. @@ -81,13 +48,14 @@ In both cases, the unconstrained types or type sequences can be chosen arbitrari are valid, with :math:`t` in the typing of |SELECT| being instantiated to |I32| or |F64|, respectively. - The |UNREACHABLE| instruction is valid with type :math:`[t_1^\ast] \to [t_2^\ast]` for any possible sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. + The |UNREACHABLE| instruction is stack-polymorphic, + and hence valid with type :math:`[t_1^\ast] \to [t_2^\ast]` for any possible sequences of value types :math:`t_1^\ast` and :math:`t_2^\ast`. Consequently, .. math:: \UNREACHABLE~~\I32.\ADD - is valid by assuming type :math:`[] \to [\I32~\I32]` for the |UNREACHABLE| instruction. + is valid by assuming type :math:`[] \to [\I32]` for the |UNREACHABLE| instruction. In contrast, .. math:: @@ -200,60 +168,645 @@ Reference Instructions .. _valid-ref.null: -:math:`\REFNULL~t` -.................. +:math:`\REFNULL~\X{ht}` +....................... -* The instruction is valid with type :math:`[] \to [t]`. +* The :ref:`heap type ` :math:`\X{ht}` must be :ref:`valid `. + +* Then the instruction is valid with type :math:`[] \to [(\REF~\NULL~\X{ht})]`. .. math:: \frac{ + C \vdashheaptype \X{ht} \ok }{ - C \vdashinstr \REFNULL~t : [] \to [t] + C \vdashinstr \REFNULL~\X{ht} : [] \to [(\REF~\NULL~\X{ht})] } -.. note:: - In future versions of WebAssembly, there may be reference types for which no null reference is allowed. +.. _valid-ref.func: + +:math:`\REFFUNC~x` +.................. + +* The function :math:`C.\CFUNCS[x]` must be defined in the context. + +* Let :math:`\X{dt}` be the :ref:`defined type ` :math:`C.\CFUNCS[x]`. + +* The :ref:`function index ` :math:`x` must be contained in :math:`C.\CREFS`. + +* The instruction is valid with type :math:`[] \to [(\REF~\X{dt})]`. + +.. math:: + \frac{ + C.\CFUNCS[x] = \X{dt} + \qquad + x \in C.\CREFS + }{ + C \vdashinstr \REFFUNC~x : [] \to [(\REF~\X{dt})] + } .. _valid-ref.is_null: :math:`\REFISNULL` .................. -* The instruction is valid with type :math:`[t] \to [\I32]`, for any :ref:`reference type ` :math:`t`. +* The instruction is valid with type :math:`[(\REF~\NULL~\X{ht})] \to [\I32]`, for any :ref:`valid ` :ref:`heap type ` :math:`\X{ht}`. .. math:: \frac{ - t = \reftype + C \vdashheaptype \X{ht} \ok }{ - C \vdashinstr \REFISNULL : [t] \to [\I32] + C \vdashinstr \REFISNULL : [(\REF~\NULL~\X{ht})] \to [\I32] } -.. _valid-ref.func: +.. _valid-ref.as_non_null: -:math:`\REFFUNC~x` -.................. +:math:`\REFASNONNULL` +..................... -* The function :math:`C.\CFUNCS[x]` must be defined in the context. +* The instruction is valid with type :math:`[(\REF~\NULL~\X{ht})] \to [(\REF~\X{ht})]`, for any :ref:`valid ` :ref:`heap type ` :math:`\X{ht}`. -* The :ref:`function index ` :math:`x` must be contained in :math:`C.\CREFS`. +.. math:: + \frac{ + C \vdashheaptype \X{ht} \ok + }{ + C \vdashinstr \REFASNONNULL : [(\REF~\NULL~\X{ht})] \to [(\REF~\X{ht})] + } + +.. _valid-ref.eq: -* The instruction is valid with type :math:`[] \to [\FUNCREF]`. +:math:`\REFEQ` +.............. + +* The instruction is valid with type :math:`[(\REF~\NULL~\EQT) (\REF~\NULL~\EQT)] \to [\I32]`. .. math:: \frac{ - C.\CFUNCS[x] = \functype + }{ + C \vdashinstr \REFEQ : [(\REF~\NULL~\EQT)~(\REF~\NULL~\EQT)] \to [\I32] + } + +.. _valid-ref.test: + +:math:`\REFTEST~\X{rt}` +....................... + +* The :ref:`reference type ` :math:`\X{rt}` must be :ref:`valid `. + +* Then the instruction is valid with type :math:`[\X{rt}'] \to [\I32]` for any :ref:`valid ` :ref:`reference type ` :math:`\X{rt}'` for which :math:`\X{rt}` :ref:`matches ` :math:`\X{rt}'`. + +.. math:: + \frac{ + C \vdashreftype \X{rt} \ok \qquad - x \in C.\CREFS + C \vdashreftype \X{rt'} \ok + \qquad + C \vdashreftypematch \X{rt} \matchesreftype \X{rt}' + }{ + C \vdashinstr \REFTEST~\X{rt} : [\X{rt}'] \to [\I32] + } + +.. note:: + The liberty to pick a supertype :math:`\X{rt}'` allows typing the instruction with the least precise super type of :math:`\X{rt}` as input, that is, the top type in the corresponding heap subtyping hierarchy. + + +.. _valid-ref.cast: + +:math:`\REFCAST~\X{rt}` +....................... + +* The :ref:`reference type ` :math:`\X{rt}` must be :ref:`valid `. + +* Then the instruction is valid with type :math:`[\X{rt}'] \to [\X{rt}]` for any :ref:`valid ` :ref:`reference type ` :math:`\X{rt}'` for which :math:`\X{rt}` :ref:`matches ` :math:`\X{rt}'`. + +.. math:: + \frac{ + C \vdashreftype \X{rt} \ok + \qquad + C \vdashreftype \X{rt'} \ok + \qquad + C \vdashreftypematch \X{rt} \matchesreftype \X{rt}' + }{ + C \vdashinstr \REFCAST~\X{rt} : [\X{rt}'] \to [\X{rt}] + } + +.. note:: + The liberty to pick a supertype :math:`\X{rt}'` allows typing the instruction with the least precise super type of :math:`\X{rt}` as input, that is, the top type in the corresponding heap subtyping hierarchy. + + +.. index:: aggregate reference + +Aggregate Reference Instructions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _valid-struct.new: + +:math:`\STRUCTNEW~x` +.................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be a :ref:`structure type ` :math:`\TSTRUCT~\fieldtype^\ast`. + +* For each :ref:`field type ` :math:`\fieldtype_i` in :math:`\fieldtype^\ast`: + + - Let :math:`\fieldtype_i` be :math:`\mut~\storagetype_i`. + + - Let :math:`t_i` be the :ref:`value type ` :math:`\unpacktype(\storagetype_i)`. + +* Let :math:`t^\ast` be the concatenation of all :math:`t_i`. + +* Then the instruction is valid with type :math:`[t^\ast] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TSTRUCT~(\mut~\X{st})^\ast + }{ + C \vdashinstr \STRUCTNEW~x : [(\unpacktype(\X{st}))^\ast] \to [(\REF~x)] + } + +.. _valid-struct.new_default: + +:math:`\STRUCTNEWDEFAULT~x` +........................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be a :ref:`structure type ` :math:`\TSTRUCT~\fieldtype^\ast`. + +* For each :ref:`field type ` :math:`\fieldtype_i` in :math:`\fieldtype^\ast`: + + - Let :math:`\fieldtype_i` be :math:`\mut~\storagetype_i`. + + - Let :math:`t_i` be the :ref:`value type ` :math:`\unpacktype(\storagetype_i)`. + + - The type :math:`t_i` must be :ref:`defaultable `. + +* Let :math:`t^\ast` be the concatenation of all :math:`t_i`. + +* Then the instruction is valid with type :math:`[] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TSTRUCT~(\mut~\X{st})^\ast + \qquad + (C \vdashvaltypedefaultable \unpacktype(\X{st}) \defaultable)^\ast + }{ + C \vdashinstr \STRUCTNEWDEFAULT~x : [] \to [(\REF~x)] + } + +.. _valid-struct.get: +.. _valid-struct.get_u: +.. _valid-struct.get_s: + +:math:`\STRUCTGET\K{\_}\sx^?~x~y` +................................. + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be a :ref:`structure type ` :math:`\TSTRUCT~\fieldtype^\ast`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype^\ast[y]`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* The extension :math:`\sx` must be present if and only if :math:`\storagetype` is a :ref:`packed type `. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)] \to [t]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TSTRUCT~\X{ft}^\ast + \qquad + \X{ft}^\ast[y] = \mut~\X{st} + \qquad + \sx^? = \epsilon \Leftrightarrow \X{st} = \unpacktype(\X{st}) + }{ + C \vdashinstr \STRUCTGET\K{\_}\sx^?~x~y : [(\REF~\NULL~x)] \to [\unpacktype(\X{st})] + } + +.. _valid-struct.set: + +:math:`\STRUCTSET~x~y` +...................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be a :ref:`structure type ` :math:`\TSTRUCT~\fieldtype^\ast`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype^\ast[y]`. + +* The prefix :math:`\mut` must be :math:`\MVAR`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~t] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TSTRUCT~\X{ft}^\ast + \qquad + \X{ft}^\ast[y] = \MVAR~\X{st} + }{ + C \vdashinstr \STRUCTSET~x~y : [(\REF~\NULL~x)~\unpacktype(\X{st})] \to [] + } + + +.. _valid-array.new: + +:math:`\ARRAYNEW~x` +................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* Then the instruction is valid with type :math:`[t~\I32] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{st}) + }{ + C \vdashinstr \ARRAYNEW~x : [\unpacktype(\X{st})~\I32] \to [(\REF~x)] + } + +.. _valid-array.new_default: + +:math:`\ARRAYNEWDEFAULT~x` +.......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* The type :math:`t` must be :ref:`defaultable `. + +* Then the instruction is valid with type :math:`[\I32] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{st}) + \qquad + C \vdashvaltypedefaultable \unpacktype(\X{st}) \defaultable + }{ + C \vdashinstr \ARRAYNEW~x : [\I32] \to [(\REF~x)] + } + +.. _valid-array.new_fixed: + +:math:`\ARRAYNEWFIXED~x~n` +.......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* Then the instruction is valid with type :math:`[t^n] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{st}) + }{ + C \vdashinstr \ARRAYNEWFIXED~x~n : [\unpacktype(\X{st})^n] \to [(\REF~x)] + } + +.. _valid-array.new_elem: + +:math:`\ARRAYNEWELEM~x~y` +......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* The :ref:`storage type ` :math:`\storagetype` must be a :ref:`reference type ` :math:`\X{rt}`. + +* The :ref:`element segment ` :math:`C.\CELEMS[y]` must exist. + +* Let :math:`\X{rt}'` be the :ref:`reference type ` :math:`C.\CELEMS[y]`. + +* The :ref:`reference type ` :math:`\X{rt}'` must :ref:`match ` :math:`\X{rt}`. + +* Then the instruction is valid with type :math:`[\I32~\I32] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{rt}) + \qquad + C \vdashreftypematch C.\CELEMS[y] \matchesreftype \X{rt} + }{ + C \vdashinstr \ARRAYNEWELEM~x~y : [\I32~\I32] \to [(\REF~x)] + } + + +.. _valid-array.new_data: + +:math:`\ARRAYNEWDATA~x~y` +......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let :math:`\fieldtype` be :math:`\mut~\storagetype`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* The type :math:`t` must be a :ref:`numeric type ` or a :ref:`vector type `. + +* The :ref:`data segment ` :math:`C.\CDATAS[y]` must exist. + +* Then the instruction is valid with type :math:`[\I32~\I32] \to [(\REF~x)]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{st}) + \qquad + \unpacktype(\X{st}) = \numtype \lor \unpacktype(\X{st}) = \vectype + \qquad + C.\CDATAS[y] = {\ok} + }{ + C \vdashinstr \ARRAYNEWDATA~x~y : [\I32~\I32] \to [(\REF~x)] + } + + +.. _valid-array.get: +.. _valid-array.get_u: +.. _valid-array.get_s: + +:math:`\ARRAYGET\K{\_}\sx^?~x` +.............................. + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* The extension :math:`\sx` must be present if and only if :math:`\storagetype` is a :ref:`packed type `. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32] \to [t]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\mut~\X{st}) + \qquad + \sx^? = \epsilon \Leftrightarrow \X{st} = \unpacktype(\X{st}) + }{ + C \vdashinstr \ARRAYGET\K{\_}\sx^?~x : [(\REF~\NULL~x)~\I32] \to [\unpacktype(\X{st})] + } + +.. _valid-array.set: + +:math:`\ARRAYSET~x` +................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype`. + +* The prefix :math:`\mut` must be :math:`\MVAR`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32~t] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\MVAR~\X{st}) + }{ + C \vdashinstr \ARRAYSET~x : [(\REF~\NULL~x)~\I32~\unpacktype(\X{st})] \to [] + } + +.. _valid-array.len: + +:math:`\ARRAYLEN` +................. + +* The the instruction is valid with type :math:`[(\REF~\NULL~\ARRAY)] \to [\I32]`. + +.. math:: + \frac{ + }{ + C \vdashinstr \ARRAYLEN : [(\REF~\NULL~\ARRAY)] \to [\I32] + } + + +.. _valid-array.fill: + +:math:`\ARRAYFILL~x` +.................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype`. + +* The prefix :math:`\mut` must be :math:`\MVAR`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32~t~\I32] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\MVAR~\X{st}) }{ - C \vdashinstr \REFFUNC~x : [] \to [\FUNCREF] + C \vdashinstr \ARRAYFILL~x : [(\REF~\NULL~x)~\I32~\unpacktype(\X{st})~\I32] \to [] } + +.. _valid-array.copy: + +:math:`\ARRAYCOPY~x~y` +...................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype_1`. + +* Let the :ref:`field type ` :math:`\mut_1~\storagetype_1` be :math:`\fieldtype_1`. + +* The prefix :math:`\mut_1` must be :math:`\MVAR`. + +* The :ref:`defined type ` :math:`C.\CTYPES[y]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[y]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype_2`. + +* Let the :ref:`field type ` :math:`\mut_2~\storagetype_2` be :math:`\fieldtype_2`. + +* The :ref:`storage type ` :math:`\storagetype_2` must :ref:`match ` :math:`\storagetype_1`. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32~(\REF~\NULL~y)~\I32~\I32] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\MVAR~\X{st_1}) + \qquad + \expanddt(C.\CTYPES[y]) = \TARRAY~(\mut~\X{st_2}) + \qquad + C \vdashstoragetypematch \X{st_2} \matchesstoragetype \X{st_1} + }{ + C \vdashinstr \ARRAYCOPY~x~y : [(\REF~\NULL~x)~\I32~(\REF~\NULL~y)~\I32~\I32] \to [] + } + + +.. _valid-array.init_data: + +:math:`\ARRAYINITDATA~x~y` +.......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype`. + +* The prefix :math:`\mut` must be :math:`\MVAR`. + +* Let :math:`t` be the :ref:`value type ` :math:`\unpacktype(\storagetype)`. + +* The :ref:`value type ` :math:`t` must be a :ref:`numeric type ` or a :ref:`vector type `. + +* The :ref:`data segment ` :math:`C.\CDATAS[y]` must exist. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32~\I32~\I32] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\MVAR~\X{st}) + \qquad + \unpacktype(\X{st}) = \numtype \lor \unpacktype(\X{st}) = \vectype + \qquad + C.\CDATAS[y] = {\ok} + }{ + C \vdashinstr \ARRAYINITDATA~x~y : [(\REF~\NULL~x)~\I32~\I32~\I32] \to [] + } + + +.. _valid-array.init_elem: + +:math:`\ARRAYINITELEM~x~y` +.......................... + +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must exist. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be an :ref:`array type ` :math:`\TARRAY~\fieldtype`. + +* Let the :ref:`field type ` :math:`\mut~\storagetype` be :math:`\fieldtype`. + +* The prefix :math:`\mut` must be :math:`\MVAR`. + +* The :ref:`storage type ` :math:`\storagetype` must be a :ref:`reference type ` :math:`\X{rt}`. + +* The :ref:`element segment ` :math:`C.\CELEMS[y]` must exist. + +* Let :math:`\X{rt}'` be the :ref:`reference type ` :math:`C.\CELEMS[y]`. + +* The :ref:`reference type ` :math:`\X{rt}'` must :ref:`match ` :math:`\X{rt}`. + +* Then the instruction is valid with type :math:`[(\REF~\NULL~x)~\I32~\I32~\I32] \to []`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TARRAY~(\MVAR~\X{rt}) + \qquad + C \vdashreftypematch C.\CELEMS[y] \matchesreftype \X{rt} + }{ + C \vdashinstr \ARRAYINITELEM~x~y : [(\REF~\NULL~x)~\I32~\I32~\I32] \to [] + } + + +.. index:: scalar reference + +Scalar Reference Instructions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _valid-ref.i31: + +:math:`\REFI31` +............... + +* The instruction is valid with type :math:`[\I32] \to [(\REF~\I31)]`. + +.. math:: + \frac{ + }{ + C \vdashinstr \REFI31 : [\I32] \to [(\REF~\I31)] + } + +.. _valid-i31.get_sx: + +:math:`\I31GET\K{\_}\sx` +........................ + +* The instruction is valid with type :math:`[(\REF~\NULL~\I31)] \to [\I32]`. + +.. math:: + \frac{ + }{ + C \vdashinstr \I31GET\K{\_}\sx : [(\REF~\NULL~\I31)] \to [\I32] + } + + +.. index:: external reference + +External Reference Instructions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. _valid-any.convert_extern: + +:math:`\ANYCONVERTEXTERN` +......................... + +* The instruction is valid with type :math:`[(\REF~\NULL_1^?~\EXTERN)] \to [(\REF~\NULL_2^?~\ANY)]` for any :math:`\NULL_1^?` that equals :math:`\NULL_2^?`. + +.. math:: + \frac{ + \NULL_1^? = \NULL_2^? + }{ + C \vdashinstr \ANYCONVERTEXTERN : [(\REF~\NULL_1^?~\EXTERN)] \to [(\REF~\NULL_2^?~\ANY)] + } + +.. _valid-extern.convert_any: + +:math:`\EXTERNCONVERTANY` +......................... + +* The instruction is valid with type :math:`[(\REF~\NULL_1^?~\ANY)] \to [(\REF~\NULL_2^?~\EXTERN)]` for any :math:`\NULL_1^?` that equals :math:`\NULL_2^?`. + +.. math:: + \frac{ + \NULL_1^? = \NULL_2^? + }{ + C \vdashinstr \EXTERNCONVERTANY : [(\REF~\NULL_1^?~\ANY)] \to [(\REF~\NULL_2^?~\EXTERN)] + } + + .. index:: vector instruction pair: validation; instruction single: abstract syntax; instruction .. _valid-instr-vec: -.. _aux-unpacked: +.. _aux-unpackshape: Vector Instructions ~~~~~~~~~~~~~~~~~~~ @@ -262,9 +815,7 @@ Vector instructions can have a prefix to describe the :ref:`shape ` :math:`t`. +* The instruction is valid with type :math:`[t] \to []`, for any :ref:`valid ` :ref:`value type ` :math:`t`. .. math:: \frac{ + C \vdashvaltype t \ok }{ C \vdashinstr \DROP : [t] \to [] } @@ -610,7 +1162,6 @@ Parametric Instructions Both |DROP| and |SELECT| without annotation are :ref:`value-polymorphic ` instructions. - .. _valid-select: :math:`\SELECT~(t^\ast)^?` @@ -618,28 +1169,35 @@ Parametric Instructions * If :math:`t^\ast` is present, then: + * The :ref:`result type ` :math:`[t^\ast]` must be :ref:`valid `. + * The length of :math:`t^\ast` must be :math:`1`. * Then the instruction is valid with type :math:`[t^\ast~t^\ast~\I32] \to [t^\ast]`. * Else: - * The instruction is valid with type :math:`[t~t~\I32] \to [t]`, for any :ref:`operand type ` :math:`t` that :ref:`matches ` some :ref:`number type ` or :ref:`vector type `. + * The instruction is valid with type :math:`[t~t~\I32] \to [t]`, for any :ref:`valid ` :ref:`value type ` :math:`t` that :ref:`matches ` some :ref:`number type ` or :ref:`vector type `. .. math:: \frac{ + C \vdashresulttype [t] \ok }{ C \vdashinstr \SELECT~t : [t~t~\I32] \to [t] } \qquad \frac{ - \vdash t \leq \numtype + C \vdashresulttype [t] \ok + \qquad + C \vdashresulttypematch [t] \matchesresulttype [\numtype] }{ C \vdashinstr \SELECT : [t~t~\I32] \to [t] } \qquad \frac{ - \vdash t \leq \vectype + C \vdashresulttype [t] \ok + \qquad + C \vdash t \leq \vectype }{ C \vdashinstr \SELECT : [t~t~\I32] \to [t] } @@ -663,13 +1221,15 @@ Variable Instructions * The local :math:`C.\CLOCALS[x]` must be defined in the context. -* Let :math:`t` be the :ref:`value type ` :math:`C.\CLOCALS[x]`. +* Let :math:`\init~t` be the :ref:`local type ` :math:`C.\CLOCALS[x]`. + +* The :ref:`initialization status ` :math:`\init` must be :math:`\SET`. * Then the instruction is valid with type :math:`[] \to [t]`. .. math:: \frac{ - C.\CLOCALS[x] = t + C.\CLOCALS[x] = \SET~t }{ C \vdashinstr \LOCALGET~x : [] \to [t] } @@ -682,15 +1242,15 @@ Variable Instructions * The local :math:`C.\CLOCALS[x]` must be defined in the context. -* Let :math:`t` be the :ref:`value type ` :math:`C.\CLOCALS[x]`. +* Let :math:`\init~t` be the :ref:`local type ` :math:`C.\CLOCALS[x]`. -* Then the instruction is valid with type :math:`[t] \to []`. +* Then the instruction is valid with type :math:`[t] \to_{x} []`. .. math:: \frac{ - C.\CLOCALS[x] = t + C.\CLOCALS[x] = \init~t }{ - C \vdashinstr \LOCALSET~x : [t] \to [] + C \vdashinstr \LOCALSET~x : [t] \to_{x} [] } @@ -701,15 +1261,15 @@ Variable Instructions * The local :math:`C.\CLOCALS[x]` must be defined in the context. -* Let :math:`t` be the :ref:`value type ` :math:`C.\CLOCALS[x]`. +* Let :math:`\init~t` be the :ref:`local type ` :math:`C.\CLOCALS[x]`. -* Then the instruction is valid with type :math:`[t] \to [t]`. +* Then the instruction is valid with type :math:`[t] \to_{x} [t]`. .. math:: \frac{ - C.\CLOCALS[x] = t + C.\CLOCALS[x] = \init~t }{ - C \vdashinstr \LOCALTEE~x : [t] \to [t] + C \vdashinstr \LOCALTEE~x : [t] \to_{x} [t] } @@ -867,15 +1427,17 @@ Table Instructions * Let :math:`\limits_2~t_2` be the :ref:`table type ` :math:`C.\CTABLES[y]`. -* The :ref:`reference type ` :math:`t_1` must be the same as :math:`t_2`. +* The :ref:`reference type ` :math:`t_2` must :ref:`match ` :math:`t_1`. * Then the instruction is valid with type :math:`[\I32~\I32~\I32] \to []`. .. math:: \frac{ - C.\CTABLES[x] = \limits_1~t + C.\CTABLES[x] = \limits_1~t_1 + \qquad + C.\CTABLES[y] = \limits_2~t_2 \qquad - C.\CTABLES[y] = \limits_2~t + C \vdashreftypematch t_2 \matchesvaltype t_1 }{ C \vdashinstr \TABLECOPY~x~y : [\I32~\I32~\I32] \to [] } @@ -894,15 +1456,17 @@ Table Instructions * Let :math:`t_2` be the :ref:`reference type ` :math:`C.\CELEMS[y]`. -* The :ref:`reference type ` :math:`t_1` must be the same as :math:`t_2`. +* The :ref:`reference type ` :math:`t_2` must :ref:`match ` :math:`t_1`. * Then the instruction is valid with type :math:`[\I32~\I32~\I32] \to []`. .. math:: \frac{ - C.\CTABLES[x] = \limits~t + C.\CTABLES[x] = \limits~t_1 + \qquad + C.\CELEMS[y] = t_2 \qquad - C.\CELEMS[y] = t + C \vdashreftypematch t_2 \matchesvaltype t_1 }{ C \vdashinstr \TABLEINIT~x~y : [\I32~\I32~\I32] \to [] } @@ -936,10 +1500,10 @@ Memory Instructions .. _valid-load: -:math:`t\K{.}\LOAD~\memarg` -........................... +:math:`t\K{.}\LOAD~x~\memarg` +............................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than the :ref:`bit width ` of :math:`t` divided by :math:`8`. @@ -947,20 +1511,20 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq |t|/8 }{ - C \vdashinstr t\K{.load}~\memarg : [\I32] \to [t] + C \vdashinstr t\K{.load}~x~\memarg : [\I32] \to [t] } .. _valid-loadn: -:math:`t\K{.}\LOAD{N}\K{\_}\sx~\memarg` -....................................... +:math:`t\K{.}\LOAD{N}\K{\_}\sx~x~\memarg` +......................................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. @@ -968,18 +1532,18 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq N/8 }{ - C \vdashinstr t\K{.load}N\K{\_}\sx~\memarg : [\I32] \to [t] + C \vdashinstr t\K{.load}N\K{\_}\sx~x~\memarg : [\I32] \to [t] } -:math:`t\K{.}\STORE~\memarg` -............................ +:math:`t\K{.}\STORE~x~\memarg` +.............................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than the :ref:`bit width ` of :math:`t` divided by :math:`8`. @@ -987,20 +1551,20 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq |t|/8 }{ - C \vdashinstr t\K{.store}~\memarg : [\I32~t] \to [] + C \vdashinstr t\K{.store}~x~\memarg : [\I32~t] \to [] } .. _valid-storen: -:math:`t\K{.}\STORE{N}~\memarg` -............................... +:math:`t\K{.}\STORE{N}~x~\memarg` +................................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. @@ -1008,20 +1572,20 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq N/8 }{ - C \vdashinstr t\K{.store}N~\memarg : [\I32~t] \to [] + C \vdashinstr t\K{.store}N~x~\memarg : [\I32~t] \to [] } .. _valid-load-extend: -:math:`\K{v128.}\LOAD{N}\K{x}M\_\sx~\memarg` -............................................... +:math:`\K{v128.}\LOAD{N}\K{x}M\_\sx~x~\memarg` +.............................................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8 \cdot M`. @@ -1029,20 +1593,20 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq N/8 \cdot M }{ - C \vdashinstr \K{v128.}\LOAD{N}\K{x}M\_\sx~\memarg : [\I32] \to [\V128] + C \vdashinstr \K{v128.}\LOAD{N}\K{x}M\_\sx~x~\memarg : [\I32] \to [\V128] } .. _valid-load-splat: -:math:`\K{v128.}\LOAD{N}\K{\_splat}~\memarg` -............................................... +:math:`\K{v128.}\LOAD{N}\K{\_splat}~x~\memarg` +.............................................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. @@ -1050,20 +1614,20 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq N/8 }{ - C \vdashinstr \K{v128.}\LOAD{N}\K{\_splat}~\memarg : [\I32] \to [\V128] + C \vdashinstr \K{v128.}\LOAD{N}\K{\_splat}~x~\memarg : [\I32] \to [\V128] } .. _valid-load-zero: -:math:`\K{v128.}\LOAD{N}\K{\_zero}~\memarg` -........................................... +:math:`\K{v128.}\LOAD{N}\K{\_zero}~x~\memarg` +............................................. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. @@ -1071,149 +1635,154 @@ Memory Instructions .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} \leq N/8 }{ - C \vdashinstr \K{v128.}\LOAD{N}\K{\_zero}~\memarg : [\I32] \to [\V128] + C \vdashinstr \K{v128.}\LOAD{N}\K{\_zero}~x~\memarg : [\I32] \to [\V128] } .. _valid-load-lane: -:math:`\K{v128.}\LOAD{N}\K{\_lane}~\memarg~\laneidx` -.................................................... - -* The lane index :math:`\laneidx` must be smaller than :math:`128/N`. +:math:`\K{v128.}\LOAD{N}\K{\_lane}~x~\memarg~\laneidx` +...................................................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. +* The lane index :math:`\laneidx` must be smaller than :math:`128/N`. + * Then the instruction is valid with type :math:`[\I32~\V128] \to [\V128]`. .. math:: \frac{ - \laneidx < 128/N - \qquad - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} < N/8 + \qquad + \laneidx < 128/N }{ - C \vdashinstr \K{v128.}\LOAD{N}\K{\_lane}~\memarg~\laneidx : [\I32~\V128] \to [\V128] + C \vdashinstr \K{v128.}\LOAD{N}\K{\_lane}~x~\memarg~\laneidx : [\I32~\V128] \to [\V128] } -.. _valid-store-lane: -:math:`\K{v128.}\STORE{N}\K{\_lane}~\memarg~\laneidx` -..................................................... +.. _valid-store-lane: -* The lane index :math:`\laneidx` must be smaller than :math:`128/N`. +:math:`\K{v128.}\STORE{N}\K{\_lane}~x~\memarg~\laneidx` +....................................................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * The alignment :math:`2^{\memarg.\ALIGN}` must not be larger than :math:`N/8`. +* The lane index :math:`\laneidx` must be smaller than :math:`128/N`. + * Then the instruction is valid with type :math:`[\I32~\V128] \to [\V128]`. .. math:: \frac{ - \laneidx < 128/N - \qquad - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad 2^{\memarg.\ALIGN} < N/8 + \qquad + \laneidx < 128/N }{ - C \vdashinstr \K{v128.}\STORE{N}\K{\_lane}~\memarg~\laneidx : [\I32~\V128] \to [] + C \vdashinstr \K{v128.}\STORE{N}\K{\_lane}~x~\memarg~\laneidx : [\I32~\V128] \to [] } .. _valid-memory.size: -:math:`\MEMORYSIZE` -................... +:math:`\MEMORYSIZE~x` +..................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * Then the instruction is valid with type :math:`[] \to [\I32]`. .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype }{ - C \vdashinstr \MEMORYSIZE : [] \to [\I32] + C \vdashinstr \MEMORYSIZE~x : [] \to [\I32] } .. _valid-memory.grow: -:math:`\MEMORYGROW` -................... +:math:`\MEMORYGROW~x` +..................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * Then the instruction is valid with type :math:`[\I32] \to [\I32]`. .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype }{ - C \vdashinstr \MEMORYGROW : [\I32] \to [\I32] + C \vdashinstr \MEMORYGROW~x : [\I32] \to [\I32] } .. _valid-memory.fill: -:math:`\MEMORYFILL` -................... +:math:`\MEMORYFILL~x` +..................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. * Then the instruction is valid with type :math:`[\I32~\I32~\I32] \to []`. .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype }{ - C \vdashinstr \MEMORYFILL : [\I32~\I32~\I32] \to [] + C \vdashinstr \MEMORYFILL~x : [\I32~\I32~\I32] \to [] } .. _valid-memory.copy: -:math:`\MEMORYCOPY` -................... +:math:`\MEMORYCOPY~x~y` +....................... + +* The memory :math:`C.\CMEMS[x]` must be defined in the context. -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[y]` must be defined in the context. * Then the instruction is valid with type :math:`[\I32~\I32~\I32] \to []`. .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype + \qquad + C.\CMEMS[x] = \memtype }{ - C \vdashinstr \MEMORYCOPY : [\I32~\I32~\I32] \to [] + C \vdashinstr \MEMORYCOPY~x~y : [\I32~\I32~\I32] \to [] } .. _valid-memory.init: -:math:`\MEMORYINIT~x` -..................... +:math:`\MEMORYINIT~x~y` +....................... -* The memory :math:`C.\CMEMS[0]` must be defined in the context. +* The memory :math:`C.\CMEMS[x]` must be defined in the context. -* The data segment :math:`C.\CDATAS[x]` must be defined in the context. +* The data segment :math:`C.\CDATAS[y]` must be defined in the context. * Then the instruction is valid with type :math:`[\I32~\I32~\I32] \to []`. .. math:: \frac{ - C.\CMEMS[0] = \memtype + C.\CMEMS[x] = \memtype \qquad - C.\CDATAS[x] = {\ok} + C.\CDATAS[y] = {\ok} }{ - C \vdashinstr \MEMORYINIT~x : [\I32~\I32~\I32] \to [] + C \vdashinstr \MEMORYINIT~x~y : [\I32~\I32~\I32] \to [] } @@ -1262,10 +1831,11 @@ Control Instructions :math:`\UNREACHABLE` .................... -* The instruction is valid with type :math:`[t_1^\ast] \to [t_2^\ast]`, for any sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. +* The instruction is valid with any :ref:`valid ` type of the form :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ + C \vdashinstrtype [t_1^\ast] \to [t_2^\ast] \ok }{ C \vdashinstr \UNREACHABLE : [t_1^\ast] \to [t_2^\ast] } @@ -1279,7 +1849,7 @@ Control Instructions :math:`\BLOCK~\blocktype~\instr^\ast~\END` .......................................... -* The :ref:`block type ` must be :ref:`valid ` as some :ref:`function type ` :math:`[t_1^\ast] \to [t_2^\ast]`. +* The :ref:`block type ` must be :ref:`valid ` as some :ref:`instruction type ` :math:`[t_1^\ast] \to [t_2^\ast]`. * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with the :ref:`result type ` :math:`[t_2^\ast]` prepended to the |CLABELS| vector. @@ -1306,7 +1876,7 @@ Control Instructions :math:`\LOOP~\blocktype~\instr^\ast~\END` ......................................... -* The :ref:`block type ` must be :ref:`valid ` as some :ref:`function type ` :math:`[t_1^\ast] \to [t_2^\ast]`. +* The :ref:`block type ` must be :ref:`valid ` as some :ref:`instruction type ` :math:`[t_1^\ast] \to_{x^\ast} [t_2^\ast]`. * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with the :ref:`result type ` :math:`[t_1^\ast]` prepended to the |CLABELS| vector. @@ -1333,7 +1903,7 @@ Control Instructions :math:`\IF~\blocktype~\instr_1^\ast~\ELSE~\instr_2^\ast~\END` ............................................................. -* The :ref:`block type ` must be :ref:`valid ` as some :ref:`function type ` :math:`[t_1^\ast] \to [t_2^\ast]`. +* The :ref:`block type ` must be :ref:`valid ` as some :ref:`instruction type ` :math:`[t_1^\ast] \to [t_2^\ast]`. * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with the :ref:`result type ` :math:`[t_2^\ast]` prepended to the |CLABELS| vector. @@ -1369,11 +1939,13 @@ Control Instructions * Let :math:`[t^\ast]` be the :ref:`result type ` :math:`C.\CLABELS[l]`. -* Then the instruction is valid with type :math:`[t_1^\ast~t^\ast] \to [t_2^\ast]`, for any sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. +* Then the instruction is valid with any :ref:`valid ` type of the form :math:`[t_1^\ast~t^\ast] \to [t_2^\ast]`. .. math:: \frac{ C.\CLABELS[l] = [t^\ast] + \qquad + C \vdashinstrtype [t_1^\ast~t^\ast] \to [t_2^\ast] \ok }{ C \vdashinstr \BR~l : [t_1^\ast~t^\ast] \to [t_2^\ast] } @@ -1417,25 +1989,22 @@ Control Instructions * For each :ref:`label ` :math:`l_i` in :math:`l^\ast`, the label :math:`C.\CLABELS[l_i]` must be defined in the context. -* There must be a sequence :math:`t^\ast` of :ref:`operand types `, such that: - - * The length of the sequence :math:`t^\ast` is the same as the length of the sequence :math:`C.\CLABELS[l_N]`. +* There must be a sequence :math:`t^\ast` of :ref:`value types `, such that: - * For each :ref:`operand type ` :math:`t_j` in :math:`t^\ast` and corresponding type :math:`t'_{Nj}` in :math:`C.\CLABELS[l_N]`, :math:`t_j` :ref:`matches ` :math:`t'_{Nj}`. + * The result type :math:`[t^\ast]` :ref:`matches ` :math:`C.\CLABELS[l_N]`. - * For each :ref:`label ` :math:`l_i` in :math:`l^\ast`: + * For all :math:`l_i` in :math:`l^\ast`, + the result type :math:`[t^\ast]` :ref:`matches ` :math:`C.\CLABELS[l_i]`. - * The length of the sequence :math:`t^\ast` is the same as the length of the sequence :math:`C.\CLABELS[l_i]`. - - * For each :ref:`operand type ` :math:`t_j` in :math:`t^\ast` and corresponding type :math:`t'_{ij}` in :math:`C.\CLABELS[l_i]`, :math:`t_j` :ref:`matches ` :math:`t'_{ij}`. - -* Then the instruction is valid with type :math:`[t_1^\ast~t^\ast~\I32] \to [t_2^\ast]`, for any sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. +* Then the instruction is valid with any :ref:`valid ` type of the form :math:`[t_1^\ast~t^\ast~\I32] \to [t_2^\ast]`. .. math:: \frac{ - (\vdash [t^\ast] \leq C.\CLABELS[l])^\ast + (C \vdashresulttypematch [t^\ast] \matchesresulttype C.\CLABELS[l])^\ast \qquad - \vdash [t^\ast] \leq C.\CLABELS[l_N] + C \vdashresulttypematch [t^\ast] \matchesresulttype C.\CLABELS[l_N] + \qquad + C \vdashinstrtype [t_1^\ast~t^\ast~\I32] \to [t_2^\ast] \ok }{ C \vdashinstr \BRTABLE~l^\ast~l_N : [t_1^\ast~t^\ast~\I32] \to [t_2^\ast] } @@ -1445,6 +2014,134 @@ Control Instructions The |BRTABLE| instruction is :ref:`stack-polymorphic `. + Furthermore, the :ref:`result type ` :math:`[t^\ast]` is also chosen non-deterministically in this rule. + Although it may seem necessary to compute :math:`[t^\ast]` as the greatest lower bound of all label types in practice, + a simple :ref:`linear algorithm ` does not require this. + + +.. _valid-br_on_null: + +:math:`\BRONNULL~l` +................... + +* The label :math:`C.\CLABELS[l]` must be defined in the context. + +* Let :math:`[t^\ast]` be the :ref:`result type ` :math:`C.\CLABELS[l]`. + +* Then the instruction is valid with type :math:`[t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast~(\REF~\X{ht})]` for any :ref:`valid ` :ref:`heap type ` :math:`\X{ht}`. + +.. math:: + \frac{ + C.\CLABELS[l] = [t^\ast] + \qquad + C \vdashheaptype \X{ht} \ok + }{ + C \vdashinstr \BRONNULL~l : [t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast~(\REF~\X{ht})] + } + + +.. _valid-br_on_non_null: + +:math:`\BRONNONNULL~l` +...................... + +* The label :math:`C.\CLABELS[l]` must be defined in the context. + +* Let :math:`[{t'}^\ast]` be the :ref:`result type ` :math:`C.\CLABELS[l]`. + +* The result type :math:`[{t'}^\ast]` must contain at least one type. + +* Let the :ref:`value type ` :math:`t_l` be the last element in the sequence :math:`{t'}^\ast`, and :math:`[t^\ast]` the remainder of the sequence preceding it. + +* The value type :math:`t_l` must be a reference type of the form :math:`\REF~\NULL^?~\X{ht}`. + +* Then the instruction is valid with type :math:`[t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast]`. + +.. math:: + \frac{ + C.\CLABELS[l] = [t^\ast~(\REF~\X{ht})] + }{ + C \vdashinstr \BRONNONNULL~l : [t^\ast~(\REF~\NULL~\X{ht})] \to [t^\ast] + } + + +.. _valid-br_on_cast: + +:math:`\BRONCAST~l~\X{rt}_1~\X{rt}_2` +..................................... + +* The label :math:`C.\CLABELS[l]` must be defined in the context. + +* Let :math:`[t_l^\ast]` be the :ref:`result type ` :math:`C.\CLABELS[l]`. + +* The type sequence :math:`t_l^\ast` must be of the form :math:`t^\ast~\X{rt}'`. + +* The :ref:`reference type ` :math:`\X{rt}_1` must be :ref:`valid `. + +* The :ref:`reference type ` :math:`\X{rt}_2` must be :ref:`valid `. + +* The :ref:`reference type ` :math:`\X{rt}_2` must :ref:`match ` :math:`\X{rt}_1`. + +* The :ref:`reference type ` :math:`\X{rt}_2` must :ref:`match ` :math:`\X{rt}'`. + +* Let :math:`\X{rt}'_1` be the :ref:`type difference ` between :math:`\X{rt}_1` and :math:`\X{rt}_2`. + +* Then the instruction is valid with type :math:`[t^\ast~\X{rt}_1] \to [t^\ast~\X{rt}'_1]`. + +.. math:: + \frac{ + C.\CLABELS[l] = [t^\ast~\X{rt}] + \qquad + C \vdashreftype \X{rt}_1 \ok + \qquad + C \vdashreftype \X{rt}_2 \ok + \qquad + C \vdashreftypematch \X{rt}_2 \matchesreftype \X{rt}_1 + \qquad + C \vdashreftypematch \X{rt}_2 \matchesreftype \X{rt} + }{ + C \vdashinstr \BRONCAST~l~\X{rt}_1~\X{rt}_2 : [t^\ast~\X{rt}_1] \to [t^\ast~\X{rt}_1\reftypediff\X{rt}_2] + } + + +.. _valid-br_on_cast_fail: + +:math:`\BRONCASTFAIL~l~\X{rt}_1~\X{rt}_2` +......................................... + +* The label :math:`C.\CLABELS[l]` must be defined in the context. + +* Let :math:`[t_l^\ast]` be the :ref:`result type ` :math:`C.\CLABELS[l]`. + +* The type sequence :math:`t_l^\ast` must be of the form :math:`t^\ast~\X{rt}'`. + +* The :ref:`reference type ` :math:`\X{rt}_1` must be :ref:`valid `. + +* The :ref:`reference type ` :math:`\X{rt}_2` must be :ref:`valid `. + +* The :ref:`reference type ` :math:`\X{rt}_2` must :ref:`match ` :math:`\X{rt}_1`. + +* Let :math:`\X{rt}'_1` be the :ref:`type difference ` between :math:`\X{rt}_1` and :math:`\X{rt}_2`. + +* The :ref:`reference type ` :math:`\X{rt}'_1` must :ref:`match ` :math:`\X{rt}'`. + +* Then the instruction is valid with type :math:`[t^\ast~\X{rt}_1] \to [t^\ast~\X{rt}_2]`. + +.. math:: + \frac{ + C.\CLABELS[l] = [t^\ast~\X{rt}] + \qquad + C \vdashreftype \X{rt}_1 \ok + \qquad + C \vdashreftype \X{rt}_2 \ok + \qquad + C \vdashreftypematch \X{rt}_2 \matchesreftype \X{rt}_1 + \qquad + C \vdashreftypematch \X{rt}_1\reftypediff\X{rt}_2 \matchesreftype \X{rt} + }{ + C \vdashinstr \BRONCASTFAIL~l~\X{rt}_1~\X{rt}_2 : [t^\ast~\X{rt}_1] \to [t^\ast~\X{rt}_2] + } + .. _valid-return: @@ -1455,11 +2152,13 @@ Control Instructions * Let :math:`[t^\ast]` be the :ref:`result type ` of :math:`C.\CRETURN`. -* Then the instruction is valid with type :math:`[t_1^\ast~t^\ast] \to [t_2^\ast]`, for any sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. +* Then the instruction is valid with any :ref:`valid ` type of the form :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ C.\CRETURN = [t^\ast] + \qquad + C \vdashinstrtype [t_1^\ast~t^\ast] \to [t_2^\ast] \ok }{ C \vdashinstr \RETURN : [t_1^\ast~t^\ast] \to [t_2^\ast] } @@ -1479,16 +2178,37 @@ Control Instructions * The function :math:`C.\CFUNCS[x]` must be defined in the context. -* Then the instruction is valid with type :math:`C.\CFUNCS[x]`. +* The :ref:`expansion ` of :math:`C.\CFUNCS[x]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* Then the instruction is valid with type :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ - C.\CFUNCS[x] = [t_1^\ast] \to [t_2^\ast] + \expanddt(C.\CFUNCS[x]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] }{ C \vdashinstr \CALL~x : [t_1^\ast] \to [t_2^\ast] } +.. _valid-call_ref: + +:math:`\CALLREF~x` +.................. + +* The type :math:`C.\CTYPES[x]` must be defined in the context. + +* The :ref:`expansion ` of :math:`C.\CFUNCS[x]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* Then the instruction is valid with type :math:`[t_1^\ast~(\REF~\NULL~x)] \to [t_2^\ast]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] + }{ + C \vdashinstr \CALLREF~x : [t_1^\ast~(\REF~\NULL~x)] \to [t_2^\ast] + } + + .. _valid-call_indirect: :math:`\CALLINDIRECT~x~y` @@ -1498,25 +2218,125 @@ Control Instructions * Let :math:`\limits~t` be the :ref:`table type ` :math:`C.\CTABLES[x]`. -* The :ref:`reference type ` :math:`t` must be |FUNCREF|. +* The :ref:`reference type ` :math:`t` must :ref:`match ` type :math:`\REF~\NULL~\FUNC`. * The type :math:`C.\CTYPES[y]` must be defined in the context. -* Let :math:`[t_1^\ast] \to [t_2^\ast]` be the :ref:`function type ` :math:`C.\CTYPES[y]`. +* The :ref:`expansion ` of :math:`C.\CTYPES[y]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. * Then the instruction is valid with type :math:`[t_1^\ast~\I32] \to [t_2^\ast]`. .. math:: \frac{ - C.\CTABLES[x] = \limits~\FUNCREF + C.\CTABLES[x] = \limits~t \qquad - C.\CTYPES[y] = [t_1^\ast] \to [t_2^\ast] + C \vdashvaltypematch t \matchesreftype \REF~\NULL~\FUNC + \qquad + \expanddt(C.\CTYPES[y]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] }{ C \vdashinstr \CALLINDIRECT~x~y : [t_1^\ast~\I32] \to [t_2^\ast] } -.. index:: instruction, instruction sequence +.. _valid-return_call: + +:math:`\RETURNCALL~x` +..................... + +* The return type :math:`C.\CRETURN` must not be absent in the context. + +* The function :math:`C.\CFUNCS[x]` must be defined in the context. + +* The :ref:`expansion ` of :math:`C.\CFUNCS[x]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* The :ref:`result type ` :math:`[t_2^\ast]` must :ref:`match ` :math:`C.\CRETURN`. + +* Then the instruction is valid with any :ref:`valid ` type :math:`[t_3^\ast~t_1^\ast] \to [t_4^\ast]`. + +.. math:: + \frac{ + \expanddt(C.\CFUNCS[x]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] + \qquad + C \vdashresulttypematch [t_2^\ast] \matchesresulttype C.\CRETURN + \qquad + C \vdashinstrtype [t_3^\ast~t_1^\ast] \to [t_4^\ast] \ok + }{ + C \vdashinstr \RETURNCALL~x : [t_3^\ast~t_1^\ast] \to [t_4^\ast] + } + +.. note:: + The |RETURNCALL| instruction is :ref:`stack-polymorphic `. + + +.. _valid-return_call_ref: + +:math:`\RETURNCALLREF~x` +........................ + +* The type :math:`C.\CTYPES[x]` must be defined in the context. + +* The :ref:`expansion ` of :math:`C.\CTYPES[x]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* The :ref:`result type ` :math:`[t_2^\ast]` must :ref:`match ` :math:`C.\CRETURN`. + +* Then the instruction is valid with any :ref:`valid ` type :math:`[t_3^\ast~t_1^\ast~(\REF~\NULL~x)] \to [t_4^\ast]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] + \qquad + C \vdashresulttypematch [t_2^\ast] \matchesresulttype C.\CRETURN + \qquad + C \vdashinstrtype [t_3^\ast~t_1^\ast~(\REF~\NULL~x)] \to [t_4^\ast] \ok + }{ + C \vdashinstr \CALLREF~x : [t_3^\ast~t_1^\ast~(\REF~\NULL~x)] \to [t_4^\ast] + } + +.. note:: + The |RETURNCALLREF| instruction is :ref:`stack-polymorphic `. + + +.. _valid-return_call_indirect: + +:math:`\RETURNCALLINDIRECT~x~y` +............................... + +* The return type :math:`C.\CRETURN` must not be empty in the context. + +* The table :math:`C.\CTABLES[x]` must be defined in the context. + +* Let :math:`\limits~t` be the :ref:`table type ` :math:`C.\CTABLES[x]`. + +* The :ref:`reference type ` :math:`t` must :ref:`match ` type :math:`\REF~\NULL~\FUNC`. + +* The type :math:`C.\CTYPES[y]` must be defined in the context. + +* The :ref:`expansion ` of :math:`C.\CTYPES[y]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* The :ref:`result type ` :math:`[t_2^\ast]` must :ref:`match ` :math:`C.\CRETURN`. + +* 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 ` :math:`t_3^\ast` and :math:`t_4^\ast`. + +.. math:: + \frac{ + C.\CTABLES[x] = \limits~t + \qquad + C \vdashvaltypematch t \matchesreftype \REF~\NULL~\FUNC + \qquad + \expanddt(C.\CTYPES[y]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] + \qquad + C \vdashresulttypematch [t_2^\ast] \matchesresulttype C.\CRETURN + \qquad + C \vdashinstrtype [t_3^\ast~t_1^\ast~\I32] \to [t_4^\ast] \ok + }{ + C \vdashinstr \RETURNCALLINDIRECT~x~y : [t_3^\ast~t_1^\ast~\I32] \to [t_4^\ast] + } + +.. note:: + The |RETURNCALLINDIRECT| instruction is :ref:`stack-polymorphic `. + + +.. index:: instruction, instruction sequence, local type .. _valid-instr-seq: Instruction Sequences @@ -1528,43 +2348,83 @@ Typing of instruction sequences is defined recursively. Empty Instruction Sequence: :math:`\epsilon` ............................................ -* The empty instruction sequence is valid with type :math:`[t^\ast] \to [t^\ast]`, - for any sequence of :ref:`operand types ` :math:`t^\ast`. +* The empty instruction sequence is valid with type :math:`[] \to []`. .. math:: \frac{ }{ - C \vdashinstrseq \epsilon : [t^\ast] \to [t^\ast] + C \vdashinstrseq \epsilon : [] \to [] } -Non-empty Instruction Sequence: :math:`\instr^\ast~\instr_N` -............................................................ +Non-empty Instruction Sequence: :math:`\instr~{\instr'}^\ast` +............................................................. + +* The instruction :math:`\instr` must be valid with some type :math:`[t_1^\ast] \to_{x_1^\ast} [t_2^\ast]`. + +* Let :math:`C'` be the same :ref:`context ` as :math:`C`, + but with: + + * |CLOCALS| the same as in C, except that for every :ref:`local index ` :math:`x` in :math:`x_1^\ast`, the :ref:`local type ` :math:`\CLOCALS[x]` has been updated to :ref:`initialization status ` :math:`\SET`. + +* Under the context :math:`C'`, the instruction sequence :math:`{\instr'}^\ast` must be valid with some type :math:`[t_2^\ast] \to_{x_2^\ast} [t_3^\ast]`. + +* Then the combined instruction sequence is valid with type :math:`[t_1^\ast] \to_{x_1^\ast x_2^\ast} [t_3^\ast]`. + +.. math:: + \frac{ + \begin{array}{@{}l@{\qquad}l@{}} + C \vdashinstr \instr : [t_1^\ast] \to_{x_1^\ast} [t_2^\ast] + & + (C.\CLOCALS[x_1] = \init~t)^\ast + \\ + C' \vdashinstrseq {\instr'}^\ast : [t_2^\ast] \to_{x_2^\ast} [t_3^\ast] + & + C' = C~(\with C.\CLOCALS[x_1] = \SET~t)^\ast + \end{array} + }{ + C \vdashinstrseq \instr~{\instr'}^\ast : [t_1^\ast] \to_{x_1^\ast x_2^\ast} [t_2^\ast~t_3^\ast] + } -* The instruction sequence :math:`\instr^\ast` must be valid with type :math:`[t_1^\ast] \to [t_2^\ast]`, - for some sequences of :ref:`operand types ` :math:`t_1^\ast` and :math:`t_2^\ast`. -* The instruction :math:`\instr_N` must be valid with type :math:`[t^\ast] \to [t_3^\ast]`, - for some sequences of :ref:`operand types ` :math:`t^\ast` and :math:`t_3^\ast`. +Subsumption for :math:`\instr^\ast` +................................... -* There must be a sequence of :ref:`operand types ` :math:`t_0^\ast`, - such that :math:`t_2^\ast = t_0^\ast~{t'}^\ast` where the type sequence :math:`{t'}^\ast` is as long as :math:`t^\ast`. +* The instruction sequence :math:`\instr^\ast` must be valid with some type :math:`\instrtype`. -* For each :ref:`operand type ` :math:`t'_i` in :math:`{t'}^\ast` and corresponding type :math:`t_i` in :math:`t^\ast`, :math:`t'_i` :ref:`matches ` :math:`t_i`. +* The instruction type :math:`\instrtype'`: must be a :ref:`valid ` -* Then the combined instruction sequence is valid with type :math:`[t_1^\ast] \to [t_0^\ast~t_3^\ast]`. +* The instruction type :math:`\instrtype` must :ref:`match ` the type :math:`\instrtype'`. + +* Then the instruction sequence :math:`\instr^\ast` is also valid with type :math:`\instrtype'`. .. math:: \frac{ - C \vdashinstrseq \instr^\ast : [t_1^\ast] \to [t_0^\ast~{t'}^\ast] + \begin{array}{@{}c@{}} + C \vdashinstr \instr : \instrtype \qquad - \vdash [{t'}^\ast] \leq [t^\ast] + C \vdashinstrtype \instrtype' \ok \qquad - C \vdashinstr \instr_N : [t^\ast] \to [t_3^\ast] + C \vdashinstrtypematch \instrtype \matchesinstrtype \instrtype' + \end{array} }{ - C \vdashinstrseq \instr^\ast~\instr_N : [t_1^\ast] \to [t_0^\ast~t_3^\ast] + C \vdashinstrseq \instr^\ast : \instrtype' } +.. note:: + In combination with the previous rule, + subsumption allows to compose instructions whose types would not directly fit otherwise. + For example, consider the instruction sequence + + .. math:: + (\I32.\CONST~1)~(\I32.\CONST~1)~\I32.\ADD + + To type this sequence, its subsequence :math:`(\I32.\CONST~1)~\I32.\ADD` needs to be valid with an intermediate type. + But the direct type of :math:`(\I32.\CONST~1)` is :math:`[] \to [\I32]`, not matching the two inputs expected by :math:`\I32.\ADD`. + The subsumption rule allows to weaken the type of :math:`(\I32.\CONST~1)` to the supertype :math:`[\I32] \to [\I32~\I32]`, such that it can be composed with :math:`\I32.\ADD` and yields the intermediate type :math:`[\I32] \to [\I32]` for the subsequence. That can in turn be composed with the first constant. + + Furthermore, subsumption allows to drop init variables :math:`x^\ast` from the instruction type in a context where they are not needed, for example, at the end of the body of a :ref:`block `. + .. index:: expression, result type pair: validation; expression @@ -1581,17 +2441,13 @@ Expressions :math:`\expr` are classified by :ref:`result types ` with some :ref:`stack type ` :math:`[] \to [{t'}^\ast]`. - -* For each :ref:`operand type ` :math:`t'_i` in :math:`{t'}^\ast` and corresponding :ref:`value type ` :math:`t_i` in :math:`t^\ast`, :math:`t'_i` :ref:`matches ` :math:`t_i`. +* The instruction sequence :math:`\instr^\ast` must be :ref:`valid ` with :ref:`type ` :math:`[] \to [t^\ast]`. * Then the expression is valid with :ref:`result type ` :math:`[t^\ast]`. .. math:: \frac{ - C \vdashinstrseq \instr^\ast : [] \to [{t'}^\ast] - \qquad - \vdash [{t'}^\ast] \leq [t^\ast] + C \vdashinstrseq \instr^\ast : [] \to [t^\ast] }{ C \vdashexpr \instr^\ast~\END : [t^\ast] } @@ -1609,10 +2465,28 @@ Constant Expressions * either of the form :math:`t.\CONST~c`, + * or of the form :math:`\K{i}\X{nn}\K{.}\ibinop`, where :math:`\ibinop` is limited to :math:`\ADD`, :math:`\SUB`, or :math:`\MUL`. + * or of the form :math:`\REFNULL`, + * or of the form :math:`\REFI31`, + * or of the form :math:`\REFFUNC~x`, + * or of the form :math:`\STRUCTNEW~x`, + + * or of the form :math:`\STRUCTNEWDEFAULT~x`, + + * or of the form :math:`\ARRAYNEW~x`, + + * or of the form :math:`\ARRAYNEWDEFAULT~x`, + + * or of the form :math:`\ARRAYNEWFIXED~x`, + + * or of the form :math:`\ANYCONVERTEXTERN`, + + * or of the form :math:`\EXTERNCONVERTANY`, + * or of the form :math:`\GLOBALGET~x`, in which case :math:`C.\CGLOBALS[x]` must be a :ref:`global type ` of the form :math:`\CONST~t`. .. math:: @@ -1624,20 +2498,69 @@ Constant Expressions .. math:: \frac{ - }{ C \vdashinstrconst t.\CONST~c \const } \qquad + \frac{ + \ibinop \in \{\ADD, \SUB, \MUL\} + }{ + C \vdashinstrconst \K{i}\X{nn}\K{.}\ibinop \const + } + +.. math:: \frac{ }{ C \vdashinstrconst \REFNULL~t \const } \qquad \frac{ + }{ + C \vdashinstrconst \REFI31 \const + } + \qquad + \frac{ }{ C \vdashinstrconst \REFFUNC~x \const } +.. math:: + \frac{ + }{ + C \vdashinstrconst \STRUCTNEW~x \const + } + \qquad + \frac{ + }{ + C \vdashinstrconst \STRUCTNEWDEFAULT~x \const + } + +.. math:: + \frac{ + }{ + C \vdashinstrconst \ARRAYNEW~x \const + } + \qquad + \frac{ + }{ + C \vdashinstrconst \ARRAYNEWDEFAULT~x \const + } + \qquad + \frac{ + }{ + C \vdashinstrconst \ARRAYNEWFIXED~x \const + } + +.. math:: + \frac{ + }{ + C \vdashinstrconst \ANYCONVERTEXTERN \const + } + \qquad + \frac{ + }{ + C \vdashinstrconst \EXTERNCONVERTANY \const + } + .. math:: \frac{ C.\CGLOBALS[x] = \CONST~t @@ -1645,8 +2568,9 @@ Constant Expressions C \vdashinstrconst \GLOBALGET~x \const } + .. note:: - Currently, constant expressions occurring in :ref:`globals `, :ref:`element `, or :ref:`data ` segments are further constrained in that contained |GLOBALGET| instructions are only allowed to refer to *imported* globals. + Currently, constant expressions occurring in :ref:`globals ` are further constrained in that contained |GLOBALGET| instructions are only allowed to refer to *imported* or *previously defined* globals. Constant expressions occurring in :ref:`tables ` may only have |GLOBALGET| instructions that refer to *imported* globals. This is enforced in the :ref:`validation rule for modules ` by constraining the context :math:`C` accordingly. The definition of constant expression may be extended in future versions of WebAssembly. diff --git a/document/core/valid/matching.rst b/document/core/valid/matching.rst new file mode 100644 index 0000000000..0b160c2055 --- /dev/null +++ b/document/core/valid/matching.rst @@ -0,0 +1,640 @@ +.. index:: ! matching, ! subtyping +.. _subtyping: +.. _match: + +Matching +-------- + +On most types, a notion of *subtyping* is defined that is applicable in :ref:`validation ` rules, during :ref:`module instantiation ` when checking the types of imports, or during :ref:`execution `, when performing casts. + + +.. index:: number type +.. _match-numtype: + +Number Types +~~~~~~~~~~~~ + +A :ref:`number type ` :math:`\numtype_1` matches a :ref:`number type ` :math:`\numtype_2` if and only if: + +* Both :math:`\numtype_1` and :math:`\numtype_2` are the same. + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashnumtypematch \numtype \matchesnumtype \numtype + } + + +.. index:: vector type +.. _match-vectype: + +Vector Types +~~~~~~~~~~~~ + +A :ref:`vector type ` :math:`\vectype_1` matches a :ref:`vector type ` :math:`\vectype_2` if and only if: + +* Both :math:`\vectype_1` and :math:`\vectype_2` are the same. + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashvectypematch \vectype \matchesvectype \vectype + } + + +.. index:: heap type, defined type, structure type, array type, function type, unboxed scalar type +.. _match-heaptype: + +Heap Types +~~~~~~~~~~ + +A :ref:`heap type ` :math:`\heaptype_1` matches a :ref:`heap type ` :math:`\heaptype_2` if and only if: + +* Either both :math:`\heaptype_1` and :math:`\heaptype_2` are the same. + +* Or there exists a :ref:`valid ` :ref:`heap type ` :math:`\heaptype'`, such that :math:`\heaptype_1` :ref:`matches ` :math:`\heaptype'` and :math:`\heaptype'` :ref:`matches ` :math:`\heaptype_2`. + +* Or :math:`heaptype_1` is :math:`\EQT` and :math:`\heaptype_2` is :math:`\ANY`. + +* Or :math:`\heaptype_1` is one of :math:`\I31`, :math:`\STRUCT`, or :math:`\ARRAY` and :math:`heaptype_2` is :math:`\EQT`. + +* Or :math:`\heaptype_1` is a :ref:`defined type ` which :ref:`expands ` to a :ref:`structure type ` and :math:`\heaptype_2` is :math:`\STRUCT`. + +* Or :math:`\heaptype_1` is a :ref:`defined type ` which :ref:`expands ` to an :ref:`array type ` and :math:`\heaptype_2` is :math:`\ARRAY`. + +* Or :math:`\heaptype_1` is a :ref:`defined type ` which :ref:`expands ` to a :ref:`function type ` and :math:`\heaptype_2` is :math:`\FUNC`. + +* Or :math:`\heaptype_1` is a :ref:`defined type ` :math:`\deftype_1` and :math:`\heaptype_2` is a :ref:`defined type ` :math:`\deftype_2`, and :math:`\deftype_1` :ref:`matches ` :math:`\deftype_2`. + +* Or :math:`\heaptype_1` is a :ref:`type index ` :math:`x_1`, and the :ref:`defined type ` :math:`C.\CTYPES[x_1]` :ref:`matches ` :math:`\heaptype_2`. + +* Or :math:`\heaptype_2` is a :ref:`type index ` :math:`x_2`, and :math:`\heaptype_1` :ref:`matches ` the :ref:`defined type ` :math:`C.\CTYPES[x_2]`. + +* Or :math:`\heaptype_1` is :math:`\NONE` and :math:`\heaptype_2` :ref:`matches ` :math:`\ANY`. + +* Or :math:`\heaptype_1` is :math:`\NOFUNC` and :math:`\heaptype_2` :ref:`matches ` :math:`\FUNC`. + +* Or :math:`\heaptype_1` is :math:`\NOEXTERN` and :math:`\heaptype_2` :ref:`matches ` :math:`\EXTERN`. + +* Or :math:`\heaptype_1` is :math:`\BOTH`. + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashheaptypematch \heaptype \matchesheaptype \heaptype + } + \qquad + \frac{ + C \vdashheaptype \heaptype' \ok + \qquad + C \vdashheaptypematch \heaptype_1 \matchesheaptype \heaptype' + \qquad + C \vdashheaptypematch \heaptype' \matchesheaptype \heaptype_2 + }{ + C \vdashheaptypematch \heaptype_1 \matchesheaptype \heaptype_2 + } + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashheaptypematch \EQT \matchesheaptype \ANY + } + \qquad + \frac{ + }{ + C \vdashheaptypematch \I31 \matchesheaptype \EQT + } + \qquad + \frac{ + }{ + C \vdashheaptypematch \STRUCT \matchesheaptype \EQT + } + \qquad + \frac{ + }{ + C \vdashheaptypematch \ARRAY \matchesheaptype \EQT + } + +.. math:: + ~\\[-1ex] + \frac{ + \expanddt(\deftype) = \TSTRUCT~\X{st} + }{ + C \vdashheaptypematch \deftype \matchesheaptype \STRUCT + } + \qquad + \frac{ + \expanddt(\deftype) = \TARRAY~\X{at} + }{ + C \vdashheaptypematch \deftype \matchesheaptype \ARRAY + } + \qquad + \frac{ + \expanddt(\deftype) = \TFUNC~\X{ft} + }{ + C \vdashheaptypematch \deftype \matchesheaptype \FUNC + } + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashheaptypematch C.\CTYPES[\typeidx_1] \matchesheaptype \heaptype_2 + }{ + C \vdashheaptypematch \typeidx_1 \matchesheaptype \heaptype_2 + } + \qquad + \frac{ + C \vdashheaptypematch \heaptype_1 \matchesheaptype C.\CTYPES[\typeidx_2] + }{ + C \vdashheaptypematch \heaptype_1 \matchesheaptype \typeidx_2 + } + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashheaptypematch \X{ht} \matchesheaptype \ANY + }{ + C \vdashheaptypematch \NONE \matchesheaptype \X{ht} + } + \qquad + \frac{ + C \vdashheaptypematch \X{ht} \matchesheaptype \FUNC + }{ + C \vdashheaptypematch \NOFUNC \matchesheaptype \X{ht} + } + \qquad + \frac{ + C \vdashheaptypematch \X{ht} \matchesheaptype \EXTERN + }{ + C \vdashheaptypematch \NOEXTERN \matchesheaptype \X{ht} + } + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashheaptypematch \BOTH \matchesheaptype \heaptype + } + + + +.. index:: reference type +.. _match-reftype: + +Reference Types +~~~~~~~~~~~~~~~ + +A :ref:`reference type ` :math:`\REF~\NULL_1^?~heaptype_1` matches a :ref:`reference type ` :math:`\REF~\NULL_2^?~heaptype_2` if and only if: + +* The :ref:`heap type ` :math:`\heaptype_1` :ref:`matches ` :math:`\heaptype_2`. + +* :math:`\NULL_1` is absent or :math:`\NULL_2` is present. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashheaptypematch \heaptype_1 \matchesheaptype \heaptype_2 + }{ + C \vdashreftypematch \REF~\heaptype_1 \matchesreftype \REF~\heaptype_2 + } + \qquad + \frac{ + C \vdashheaptypematch \heaptype_1 \matchesheaptype \heaptype_2 + }{ + C \vdashreftypematch \REF~\NULL^?~\heaptype_1 \matchesreftype \REF~\NULL~\heaptype_2 + } + + +.. index:: value type, number type, reference type +.. _match-valtype: + +Value Types +~~~~~~~~~~~ + +A :ref:`value type ` :math:`\valtype_1` matches a :ref:`value type ` :math:`\valtype_2` if and only if: + +* Either both :math:`\valtype_1` and :math:`\valtype_2` are :ref:`number types ` and :math:`\valtype_1` :ref:`matches ` :math:`\valtype_2`. + +* Or both :math:`\valtype_1` and :math:`\valtype_2` are :ref:`reference types ` and :math:`\valtype_1` :ref:`matches ` :math:`\valtype_2`. + +* Or :math:`\valtype_1` is :math:`\BOT`. + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashvaltypematch \BOT \matchesvaltype \valtype + } + + +.. index:: result type, value type +.. _match-resulttype: + +Result Types +~~~~~~~~~~~~ + +Subtyping is lifted to :ref:`result types ` in a pointwise manner. +That is, a :ref:`result type ` :math:`[t_1^\ast]` matches a :ref:`result type ` :math:`[t_2^\ast]` if and only if: + +* Every :ref:`value type ` :math:`t_1` in :math:`[t_1^\ast]` :ref:`matches ` the corresponding :ref:`value type ` :math:`t_2` in :math:`[t_2^\ast]`. + +.. math:: + ~\\[-1ex] + \frac{ + (C \vdashvaltypematch t_1 \matchesvaltype t_2)^\ast + }{ + C \vdashresulttypematch [t_1^\ast] \matchesresulttype [t_2^\ast] + } + + +.. index:: instruction type, result type +.. _match-instrtype: + +Instruction Types +~~~~~~~~~~~~~~~~~ + +Subtyping is further lifted to :ref:`instruction types `. +An :ref:`instruction type ` :math:`[t_{11}^\ast] \to_{x_1^\ast} [t_{12}^\ast]` matches a type :math:`[t_{21}^ast] \to_{x_2^\ast} [t_{22}^\ast]` if and only if: + +* There is a common sequence of :ref:`value types ` :math:`t^\ast` such that :math:`t_{21}^\ast` equals :math:`t^\ast~{t'_{21}}^\ast` and :math:`t_{22}^\ast` equals :math:`t^\ast~{t'_{22}}^\ast`. + +* The :ref:`result type ` :math:`[{t'_{21}}^\ast]` :ref:`matches ` :math:`[t_{11}^\ast]`. + +* The :ref:`result type ` :math:`[t_{12}^\ast]` :ref:`matches ` :math:`[{t'_{22}}^\ast]`. + +* For every :ref:`local index ` :math:`x` that is in :math:`x_2^\ast` but not in :math:`x_1^\ast`, the :ref:`local type ` :math:`C.\CLOCALS[x]` is :math:`\SET~t_x` for some :ref:`value type ` :math:`t_x`. + +.. math:: + ~\\[-1ex] + \frac{ + \begin{array}{@{}c@{\qquad}l@{}} + C \vdashresulttypematch [t_{21}^\ast] \matchesresulttype [t_{11}^\ast] + & + \{ x^\ast \} = \{ x_2^\ast \} \setminus \{ x_1^\ast \} + \\ + C \vdashresulttypematch [t_{12}^\ast] \matchesresulttype [t_{22}^\ast] + & + (C.\CLOCALS[x] = \SET~t_x)^\ast + \end{array} + }{ + C \vdashinstrtypematch [t_{11}^\ast] \to_{x_1^\ast} [t_{12}^\ast] \matchesinstrtype [t^\ast~t_{21}^\ast] \to_{x_2^\ast} [t^\ast~t_{22}^\ast] + } + +.. note:: + Instruction types are contravariant in their input and covariant in their output. + Subtyping also incorporates a sort of "frame" condition, which allows adding arbitrary invariant stack elements on both sides in the super type. + + Finally, the supertype may ignore variables from the init set :math:`x_1^\ast`. + It may also *add* variables to the init set, provided these are already set in the context, i.e., are vacuously initialized. + + +.. index:: function type, result type +.. _match-functype: + +Function Types +~~~~~~~~~~~~~~ + +A :ref:`function type ` :math:`[t_{11}^\ast] \toF [t_{12}^\ast]` matches a type :math:`[t_{21}^ast] \toF [t_{22}^\ast]` if and only if: + +* The :ref:`result type ` :math:`[t_{21}^\ast]` :ref:`matches ` :math:`[t_{11}^\ast]`. + +* The :ref:`result type ` :math:`[t_{12}^\ast]` :ref:`matches ` :math:`[t_{22}^\ast]`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashresulttypematch [t_{21}^\ast] \matchesresulttype [t_{11}^\ast] + \qquad + C \vdashresulttypematch [t_{12}^\ast] \matchesresulttype [t_{22}^\ast] + }{ + C \vdashfunctypematch [t_{11}^\ast] \toF [t_{12}^\ast] \matchesfunctype [t_{21}^\ast] \toF [t_{22}^\ast] + } + + +.. index:: composite types, aggregate type, structure type, array type, field type +.. _match-comptype: +.. _match-structtype: +.. _match-arraytype: + +Composite Types +~~~~~~~~~~~~~~~ + +A :ref:`composite type ` :math:`\comptype_1` matches a type :math:`\comptype_2` if and only if: + +* Either the composite type :math:`\comptype_1` is :math:`\TFUNC~\functype_1` and :math:`\comptype_2` is :math:`\TFUNC~\functype_2` and: + + * The :ref:`function type ` :math:`\functype_1` :ref:`matches ` :math:`\functype_2`. + +* Or the composite type :math:`\comptype_1` is :math:`\TSTRUCT~\fieldtype_1^{n_1}` and :math:`\comptype_2` is :math:`\TSTRUCT~\fieldtype_2` and: + + * The arity :math:`n_1` is greater than or equal to :math:`n_2`. + + * For every :ref:`field type ` :math:`\fieldtype_{2i}` in :math:`\fieldtype_2^{n_2}` and corresponding :math:`\fieldtype_{1i}` in :math:`\fieldtype_1^{n_1}` + + * The :ref:`field type ` :math:`\fieldtype_{1i}` :ref:`matches ` :math:`\fieldtype_{2i}`. + +* Or the composite type :math:`\comptype_1` is :math:`\TARRAY~\fieldtype_1` and :math:`\comptype_2` is :math:`\TARRAY~\fieldtype_2` and: + + * The :ref:`field type ` :math:`\fieldtype_1` :ref:`matches ` :math:`\fieldtype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashfunctypematch \functype_1 \matchesfunctype \functype_2 + }{ + C \vdashcomptypematch \TFUNC~\functype_1 \matchescomptype \TFUNC~\functype_2 + } + +.. math:: + ~\\[-1ex] + \frac{ + (C \vdashfieldtypematch \fieldtype_1 \matchesfieldtype \fieldtype_2)^\ast + }{ + C \vdashcomptypematch \TSTRUCT~\fieldtype_1^\ast~{\fieldtype'}_1^\ast \matchescomptype \TSTRUCT~\fieldtype_2^\ast + } + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashfieldtypematch \fieldtype_1 \matchesfieldtype \fieldtype_2 + }{ + C \vdashcomptypematch \TARRAY~\fieldtype_1 \matchescomptype \TARRAY~\fieldtype_2 + } + + +.. index:: field type, storage type, value type, packed type, mutability +.. _match-fieldtype: +.. _match-storagetype: +.. _match-packedtype: + +Field Types +~~~~~~~~~~~ + +A :ref:`field type ` :math:`\mut_1~\storagetype_1` matches a type :math:`\mut_2~\storagetype_2` if and only if: + +* :ref:`Storage type ` :math:`\storagetype_1` :ref:`matches ` :math:`\storagetype_2`. + +* Either both :math:`\mut_1` and :math:`\mut_2` are :math:`\MCONST`. + +* Or both :math:`\mut_1` and :math:`\mut_2` are :math:`\MVAR` and :math:`\storagetype_2` :ref:`matches ` :math:`\storagetype_1` as well. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashstoragetypematch \storagetype_1 \matchesstoragetype \storagetype_2 + }{ + C \vdashfieldtypematch \MCONST~\storagetype_1 \matchescomptype \MCONST~\storagetype_2 + } + \qquad + \frac{ + \begin{array}[b]{@{}c@{}} + C \vdashstoragetypematch \storagetype_1 \matchesstoragetype \storagetype_2 + \\ + C \vdashstoragetypematch \storagetype_2 \matchesstoragetype \storagetype_1 + \end{array} + }{ + C \vdashfieldtypematch \MVAR~\storagetype_1 \matchescomptype \MVAR~\storagetype_2 + } + +A :ref:`storage type ` :math:`\storagetype_1` matches a type :math:`\storagetype_2` if and only if: + +* Either :math:`\storagetype_1` is a :ref:`value type ` :math:`\valtype_1` and :math:`\storagetype_2` is a :ref:`value type ` :math:`\valtype_2` and :math:`\valtype_1` :ref:`matches ` :math:`\valtype_2`. + +* Or :math:`\storagetype_1` is a :ref:`packed type ` :math:`\packedtype_1` and :math:`\storagetype_2` is a :ref:`packed type ` :math:`\packedtype_2` and :math:`\packedtype_1` :ref:`matches ` :math:`\packedtype_2`. + +A :ref:`packed type ` :math:`\packedtype_1` matches a type :math:`\packedtype_2` if and only if: + +* The :ref:`packed type ` :math:`\packedtype_1` is the same as :math:`\packedtype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + }{ + C \vdashpackedtypematch \packedtype \matchespackedtype \packedtype + } + + +.. index:: defined type, recursive type, unroll, type equivalence + pair: abstract syntax; defined type +.. _match-deftype: + +Defined Types +~~~~~~~~~~~~~ + +A :ref:`defined type ` :math:`\deftype_1` matches a type :math:`\deftype_2` if and only if: + +* Either :math:`\deftype_1` and :math:`\deftype_2` are equal when :ref:`closed ` under context :math:`C`. + +* Or: + + * Let the :ref:`sub type ` :math:`\TSUB~\TFINAL^?~\heaptype^\ast~\comptype` be the result of :ref:`unrolling ` :math:`\deftype_1`. + + * Then there must exist a :ref:`heap type ` :math:`\heaptype_i` in :math:`\heaptype^\ast` that :ref:`matches ` :math:`\deftype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + \clostype_C(\deftype_1) = \clostype_C(\deftype_2) + }{ + C \vdashdeftypematch \deftype_1 \matchesdeftype \deftype_2 + } + +.. math:: + ~\\[-1ex] + \frac{ + \unrolldt(\deftype_1) = \TSUB~\TFINAL^?~\heaptype^\ast~\comptype + \qquad + C \vdashheaptypematch \heaptype^\ast[i] \matchesheaptype \deftype_2 + }{ + C \vdashdeftypematch \deftype_1 \matchesdeftype \deftype_2 + } + +.. note:: + Note that there is no explicit definition of type _equivalence_, + since it coincides with syntactic equality, + as used in the premise of the fomer rule above. + + +.. index:: limits +.. _match-limits: + +Limits +~~~~~~ + +:ref:`Limits ` :math:`\{ \LMIN~n_1, \LMAX~m_1^? \}` match limits :math:`\{ \LMIN~n_2, \LMAX~m_2^? \}` if and only if: + +* :math:`n_1` is larger than or equal to :math:`n_2`. + +* Either: + + * :math:`m_2^?` is empty. + +* Or: + + * Both :math:`m_1^?` and :math:`m_2^?` are non-empty. + + * :math:`m_1` is smaller than or equal to :math:`m_2`. + +.. math:: + ~\\[-1ex] + \frac{ + n_1 \geq n_2 + }{ + C \vdashlimitsmatch \{ \LMIN~n_1, \LMAX~m_1^? \} \matcheslimits \{ \LMIN~n_2, \LMAX~\epsilon \} + } + \quad + \frac{ + n_1 \geq n_2 + \qquad + m_1 \leq m_2 + }{ + C \vdashlimitsmatch \{ \LMIN~n_1, \LMAX~m_1 \} \matcheslimits \{ \LMIN~n_2, \LMAX~m_2 \} + } + + +.. index:: table type, limits, element type +.. _match-tabletype: + +Table Types +~~~~~~~~~~~ + +A :ref:`table type ` :math:`(\limits_1~\reftype_1)` matches :math:`(\limits_2~\reftype_2)` if and only if: + +* Limits :math:`\limits_1` :ref:`match ` :math:`\limits_2`. + +* The :ref:`reference type ` :math:`\reftype_1` :ref:`matches ` :math:`\reftype_2`, and vice versa. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashlimitsmatch \limits_1 \matcheslimits \limits_2 + \qquad + C \vdashreftypematch \reftype_1 \matchesreftype \reftype_2 + \qquad + C \vdashreftypematch \reftype_2 \matchesreftype \reftype_1 + }{ + C \vdashtabletypematch \limits_1~\reftype_1 \matchestabletype \limits_2~\reftype_2 + } + + +.. index:: memory type, limits +.. _match-memtype: + +Memory Types +~~~~~~~~~~~~ + +A :ref:`memory type ` :math:`\limits_1` matches :math:`\limits_2` if and only if: + +* Limits :math:`\limits_1` :ref:`match ` :math:`\limits_2`. + + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashlimitsmatch \limits_1 \matcheslimits \limits_2 + }{ + C \vdashmemtypematch \limits_1 \matchesmemtype \limits_2 + } + + +.. index:: global type, value type, mutability +.. _match-globaltype: + +Global Types +~~~~~~~~~~~~ + +A :ref:`global type ` :math:`(\mut_1~t_1)` matches :math:`(\mut_2~t_2)` if and only if: + +* Either both :math:`\mut_1` and :math:`\mut_2` are |MVAR| and :math:`t_1` :ref:`matches ` :math:`t_2` and vice versa. + +* Or both :math:`\mut_1` and :math:`\mut_2` are |MCONST| and :math:`t_1` :ref:`matches ` :math:`t_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashvaltypematch t_1 \matchesvaltype t_2 + \qquad + C \vdashvaltypematch t_2 \matchesvaltype t_1 + }{ + C \vdashglobaltypematch \MVAR~t_1 \matchesglobaltype \MVAR~t_2 + } + \qquad + \frac{ + C \vdashvaltypematch t_1 \matchesvaltype t_2 + }{ + C \vdashglobaltypematch \MCONST~t_1 \matchesglobaltype \MCONST~t_2 + } + + +.. index:: external type, function type, table type, memory type, global type +.. _match-externtype: + +External Types +~~~~~~~~~~~~~~ + +Functions +......... + +An :ref:`external type ` :math:`\ETFUNC~\deftype_1` matches :math:`\ETFUNC~\deftype_2` if and only if: + +* The :ref:`defined type ` :math:`\deftype_1` :ref:`matches ` :math:`\deftype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashdeftypematch \deftype_1 \matchesfunctype \deftype_2 + }{ + C \vdashexterntypematch \ETFUNC~\deftype_1 \matchesexterntype \ETFUNC~\deftype_2 + } + + +Tables +...... + +An :ref:`external type ` :math:`\ETTABLE~\tabletype_1` matches :math:`\ETTABLE~\tabletype_2` if and only if: + +* Table type :math:`\tabletype_1` :ref:`matches ` :math:`\tabletype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashtabletypematch \tabletype_1 \matchestabletype \tabletype_2 + }{ + C \vdashexterntypematch \ETTABLE~\tabletype_1 \matchesexterntype \ETTABLE~\tabletype_2 + } + + +Memories +........ + +An :ref:`external type ` :math:`\ETMEM~\memtype_1` matches :math:`\ETMEM~\memtype_2` if and only if: + +* Memory type :math:`\memtype_1` :ref:`matches ` :math:`\memtype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashmemtypematch \memtype_1 \matchesmemtype \memtype_2 + }{ + C \vdashexterntypematch \ETMEM~\memtype_1 \matchesexterntype \ETMEM~\memtype_2 + } + + +Globals +....... + +An :ref:`external type ` :math:`\ETGLOBAL~\globaltype_1` matches :math:`\ETGLOBAL~\globaltype_2` if and only if: + +* Global type :math:`\globaltype_1` :ref:`matches ` :math:`\globaltype_2`. + +.. math:: + ~\\[-1ex] + \frac{ + C \vdashglobaltypematch \globaltype_1 \matchesglobaltype \globaltype_2 + }{ + C \vdashexterntypematch \ETGLOBAL~\globaltype_1 \matchesexterntype \ETGLOBAL~\globaltype_2 + } diff --git a/document/core/valid/modules.rst b/document/core/valid/modules.rst index 257c371be7..3379883de8 100644 --- a/document/core/valid/modules.rst +++ b/document/core/valid/modules.rst @@ -5,7 +5,63 @@ Modules Furthermore, most definitions are themselves classified with a suitable type. -.. index:: function, local, function index, local index, type index, function type, value type, expression, import +.. index:: type, type index, defined type, recursive type + pair: abstract syntax; type + single: abstract syntax; type +.. _valid-types: + +Types +~~~~~ + +The sequence of :ref:`types ` defined in a module is validated incrementally, yielding a suitable :ref:`context `. + +:math:`\type^\ast` +.................. + +* If the sequence is empty, then: + + * The :ref:`context ` :math:`C` must be empty. + + * Then the type sequence is valid. + +* Otherwise: + + * Let the :ref:`recursive type ` :math:`\rectype` be the last element in the sequence. + + * The sequence without :math:`\rectype` must be valid for some context :math:`C'`. + + * Let the :ref:`type index ` :math:`x` be the length of :math:`C'.\CTYPES`, i.e., the first type index free in :math:`C'`. + + * Let the sequence of :ref:`defined types ` :math:`\deftype^\ast` be the result :math:`\rolldt_{x}(\rectype)` of :ref:`rolling up ` into its sequence of :ref:`defined types `. + + * The :ref:`recursive type ` :math:`\rectype` must be :ref:`valid ` under the context :math:`C` for :ref:`type index ` :math:`x`. + + * The current :ref:`context ` :math:`C` be the same as :math:`C'`, but with :math:`\deftype^\ast` appended to |CTYPES|. + + * Then the type sequence is valid. + +.. math:: + \frac{ + }{ + \{\} \vdashtypes \epsilon \ok + } + +.. math:: + \frac{ + C' \vdashtypes \type^\ast \ok + \qquad + C = C' \with \CTYPES = C'.\CTYPES~\rolldt_{|C'.\CTYPES|}(\rectype) + \qquad + C \vdashrectype \rectype ~{\ok}(|C'.\CTYPES|) + }{ + C \vdashtypes \type^\ast~\rectype \ok + } + +.. note:: + Despite the appearance, the context :math:`C` is effectively an _output_ of this judgement. + + +.. index:: function, local, function index, local index, type index, function type, value type, local type, expression, import pair: abstract syntax; function single: abstract syntax; function .. _valid-local: @@ -14,20 +70,26 @@ Furthermore, most definitions are themselves classified with a suitable type. Functions ~~~~~~~~~ -Functions :math:`\func` are classified by :ref:`function types ` of the form :math:`[t_1^\ast] \to [t_2^\ast]`. +Functions :math:`\func` are classified by :ref:`defined types ` that :ref:`expand ` to :ref:`function types ` of the form :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. :math:`\{ \FTYPE~x, \FLOCALS~t^\ast, \FBODY~\expr \}` ..................................................... -* The type :math:`C.\CTYPES[x]` must be defined in the context. +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must be a :ref:`function type `. -* Let :math:`[t_1^\ast] \to [t_2^\ast]` be the :ref:`function type ` :math:`C.\CTYPES[x]`. +* Let :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]` be the :ref:`expansion ` of the :ref:`defined type ` :math:`C.\CTYPES[x]`. + +* For each local declared by a :ref:`value type ` :math:`t` in :math:`t^\ast`: + + * The local for type :math:`t` must be :ref:`valid ` with :ref:`local type ` :math:`\localtype_i`. + +* Let :math:`\localtype^\ast` be the concatenation of all :math:`\localtype_i`. * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with: - * |CLOCALS| set to the sequence of :ref:`value types ` :math:`t_1^\ast~t^\ast`, concatenating parameters and locals, + * |CLOCALS| set to the sequence of :ref:`value types ` :math:`(\SET~t_1)^\ast~\localtype^\ast`, concatenating parameters and locals, * |CLABELS| set to the singular sequence containing only :ref:`result type ` :math:`[t_2^\ast]`. @@ -36,19 +98,64 @@ Functions :math:`\func` are classified by :ref:`function types * Under the context :math:`C'`, the expression :math:`\expr` must be valid with type :math:`[t_2^\ast]`. -* Then the function definition is valid with type :math:`[t_1^\ast] \to [t_2^\ast]`. +* Then the function definition is valid with type :math:`C.\CTYPES[x]`. + +.. math:: + \frac{ + \expanddt(C.\CTYPES[x]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] + \qquad + (C \vdashlocal \{\LTYPE~t\} : \init~t)^\ast + \qquad + C,\CLOCALS\,(\SET~t_1)^\ast~(\init~t)^\ast,\CLABELS~[t_2^\ast],\CRETURN~[t_2^\ast] \vdashexpr \expr : [t_2^\ast] + }{ + C \vdashfunc \{ \FTYPE~x, \FLOCALS~\{\LTYPE~t\}^\ast, \FBODY~\expr \} : C.\CTYPES[x] + } + + +.. index:: local, local type, value type + pair: validation; local + single: abstract syntax; local +.. _valid-localtype: + +Locals +~~~~~~ + +:ref:`Locals ` are classified with :ref:`local types `. + +:math:`\{ \LTYPE~\valtype \}` +............................. + +* The :ref:`value type ` :math:`\valtype` must be :ref:`valid `. + +* If :math:`\valtype` is :ref:`defaultable `, then: + + * The local is valid with :ref:`local type ` :math:`\SET~\valtype`. + +* Else: + + * The local is valid with :ref:`local type ` :math:`\UNSET~\valtype`. .. math:: \frac{ - C.\CTYPES[x] = [t_1^\ast] \to [t_2^\ast] + C \vdashvaltype t \ok \qquad - C,\CLOCALS\,t_1^\ast~t^\ast,\CLABELS~[t_2^\ast],\CRETURN~[t_2^\ast] \vdashexpr \expr : [t_2^\ast] + C \vdashvaltypedefaultable t \defaultable }{ - C \vdashfunc \{ \FTYPE~x, \FLOCALS~t^\ast, \FBODY~\expr \} : [t_1^\ast] \to [t_2^\ast] + C \vdashlocal \{ \LTYPE~t \} : \SET~t } +.. math:: + \frac{ + C \vdashvaltype t \ok + }{ + C \vdashlocal \{ \LTYPE~t \} : \UNSET~t + } -.. index:: table, table type +.. note:: + For cases where both rules are applicable, the former yields the more permissable type. + + +.. index:: table, table type, reference type, expression, constant, defaultable pair: validation; table single: abstract syntax; table .. _valid-table: @@ -58,18 +165,30 @@ Tables Tables :math:`\table` are classified by :ref:`table types `. -:math:`\{ \TTYPE~\tabletype \}` -............................... +:math:`\{ \TTYPE~\tabletype, \TINIT~\expr \}` +............................................. * The :ref:`table type ` :math:`\tabletype` must be :ref:`valid `. +* Let :math:`t` be the element :ref:`reference type ` of :math:`\tabletype`. + +* The expression :math:`\expr` must be :ref:`valid ` with :ref:`result type ` :math:`[t]`. + +* The expression :math:`\expr` must be :ref:`constant `. + * Then the table definition is valid with type :math:`\tabletype`. .. math:: \frac{ - \vdashtabletype \tabletype \ok + C \vdashtabletype \tabletype \ok + \qquad + \tabletype = \limits~t + \qquad + C \vdashexpr \expr : [t] + \qquad + C \vdashexprconst \expr \const }{ - C \vdashtable \{ \TTYPE~\tabletype \} : \tabletype + C \vdashtable \{ \TTYPE~\tabletype, \TINIT~\expr \} : \tabletype } @@ -92,22 +211,25 @@ Memories :math:`\mem` are classified by :ref:`memory types `. .. math:: \frac{ - \vdashmemtype \memtype \ok + C \vdashmemtype \memtype \ok }{ C \vdashmem \{ \MTYPE~\memtype \} : \memtype } -.. index:: global, global type, expression +.. index:: global, global type, expression, constant pair: validation; global single: abstract syntax; global .. _valid-global: +.. _valid-globalseq: Globals ~~~~~~~ Globals :math:`\global` are classified by :ref:`global types ` of the form :math:`\mut~t`. +Sequences of globals are handled incrementally, such that each definition has access to previous definitions. + :math:`\{ \GTYPE~\mut~t, \GINIT~\expr \}` ......................................... @@ -122,7 +244,7 @@ Globals :math:`\global` are classified by :ref:`global types .. math:: \frac{ - \vdashglobaltype \mut~t \ok + C \vdashglobaltype \mut~t \ok \qquad C \vdashexpr \expr : [t] \qquad @@ -132,7 +254,39 @@ Globals :math:`\global` are classified by :ref:`global types } -.. index:: element, table, table index, expression, function index +:math:`\global^\ast` +.................... + +* If the sequence is empty, then it is valid with the empty sequence of :ref:`global types `. + +* Else: + + * The first global definition must be :ref:`valid ` with some type :ref:`global type ` :math:`\X{gt}_1`. + + * Let :math:`C'` be the same :ref:`context ` as :math:`C`, but with the :ref:`global type ` :math:`\X{gt}_1` apppended to the |CGLOBALS| vector. + + * Under context :math:`C'`, the remainder of the sequence must be valid with some sequence :math:`\X{gt}^\ast` of :ref:`global types `. + + * Then the sequence is valid with the sequence of :ref:`global types ` consisting of :math:`\X{gt}_1` prepended to :math:`\X{gt}^\ast`. + +.. math:: + ~\\ + \frac{ + }{ + C \vdashglobals \epsilon : \epsilon + } + \qquad + \frac{ + C \vdashglobal \global_1 : \X{gt}_1 + \qquad + C \compose \{\CGLOBALS~\X{gt}_1\} \vdashglobals \global^\ast : \X{gt}^\ast + }{ + C \vdashglobals \global_1~\global^\ast : \X{gt}_1~\X{gt}^\ast + } + + + +.. index:: element, table, table index, expression, constant, function index pair: validation; element single: abstract syntax; element single: table; element @@ -147,24 +301,32 @@ Element segments :math:`\elem` are classified by the :ref:`reference type ` :math:`t` must be :ref:`valid `. + +* For each :math:`e_i` in :math:`e^\ast`, * The expression :math:`e_i` must be :ref:`valid ` with some :ref:`result type ` :math:`[t]`. * The expression :math:`e_i` must be :ref:`constant `. -* The element mode :math:`\elemmode` must be valid with :ref:`reference type ` :math:`t`. +* The element mode :math:`\elemmode` must be valid with some :ref:`reference type ` :math:`t'`. + +* The reference type :math:`t` must :ref:`match ` the reference type :math:`t'`. * Then the element segment is valid with :ref:`reference type ` :math:`t`. .. math:: \frac{ + C \vdashreftype t \ok + \qquad (C \vdashexpr e : [t])^\ast \qquad (C \vdashexprconst e \const)^\ast \qquad - C \vdashelemmode \elemmode : t + C \vdashelemmode \elemmode : t' + \qquad + C \vdashreftypematch t \matchesreftype t' }{ C \vdashelem \{ \ETYPE~t, \EINIT~e^\ast, \EMODE~\elemmode \} : t } @@ -175,10 +337,11 @@ Element segments :math:`\elem` are classified by the :ref:`reference type `. +* The element mode is valid with any :ref:`valid ` :ref:`reference type `. .. math:: \frac{ + C \vdashreftype \reftype \ok }{ C \vdashelemmode \EPASSIVE : \reftype } @@ -213,17 +376,18 @@ Element segments :math:`\elem` are classified by the :ref:`reference type `. +* The element mode is valid with any :ref:`valid ` :ref:`reference type `. .. math:: \frac{ + C \vdashreftype \reftype \ok }{ C \vdashelemmode \EDECLARATIVE : \reftype } -.. index:: data, memory, memory index, expression, byte +.. index:: data, memory, memory index, expression, constant, byte pair: validation; data single: abstract syntax; data single: memory; data @@ -302,14 +466,14 @@ Start function declarations :math:`\start` are not classified by any type. * The function :math:`C.\CFUNCS[x]` must be defined in the context. -* The type of :math:`C.\CFUNCS[x]` must be :math:`[] \to []`. +* The :ref:`expansion ` of :math:`C.\CFUNCS[x]` must be a :ref:`function type ` :math:`\TFUNC~[] \toF []`. * Then the start function is valid. .. math:: \frac{ - C.\CFUNCS[x] = [] \to [] + \expanddt(C.\CFUNCS[x]) = \TFUNC~[] \toF [] }{ C \vdashstart \{ \SFUNC~x \} \ok } @@ -347,13 +511,15 @@ Exports :math:`\export` and export descriptions :math:`\exportdesc` are classifi * The function :math:`C.\CFUNCS[x]` must be defined in the context. -* Then the export description is valid with :ref:`external type ` :math:`\ETFUNC~C.\CFUNCS[x]`. +* Let :math:`\X{dt}` be the :ref:`defined type ` :math:`C.\CFUNCS[x]`. + +* Then the export description is valid with :ref:`external type ` :math:`\ETFUNC~\X{dt}`. .. math:: \frac{ - C.\CFUNCS[x] = \functype + C.\CFUNCS[x] = \X{dt} }{ - C \vdashexportdesc \EDFUNC~x : \ETFUNC~\functype + C \vdashexportdesc \EDFUNC~x : \ETFUNC~\X{dt} } @@ -432,17 +598,15 @@ Imports :math:`\import` and import descriptions :math:`\importdesc` are classifi :math:`\IDFUNC~x` ................. -* The function :math:`C.\CTYPES[x]` must be defined in the context. +* The :ref:`defined type ` :math:`C.\CTYPES[x]` must be a :ref:`function type `. -* Let :math:`[t_1^\ast] \to [t_2^\ast]` be the :ref:`function type ` :math:`C.\CTYPES[x]`. - -* Then the import description is valid with type :math:`\ETFUNC~[t_1^\ast] \to [t_2^\ast]`. +* Then the import description is valid with type :math:`\ETFUNC~C.\CTYPES[x]`. .. math:: \frac{ - C.\CTYPES[x] = [t_1^\ast] \to [t_2^\ast] + \expanddt(C.\CTYPES[x]) = \TFUNC~\functype }{ - C \vdashimportdesc \IDFUNC~x : \ETFUNC~[t_1^\ast] \to [t_2^\ast] + C \vdashimportdesc \IDFUNC~x : \ETFUNC~C.\CTYPES[x] } @@ -455,7 +619,7 @@ Imports :math:`\import` and import descriptions :math:`\importdesc` are classifi .. math:: \frac{ - \vdashtable \tabletype \ok + C \vdashtable \tabletype \ok }{ C \vdashimportdesc \IDTABLE~\tabletype : \ETTABLE~\tabletype } @@ -470,7 +634,7 @@ Imports :math:`\import` and import descriptions :math:`\importdesc` are classifi .. math:: \frac{ - \vdashmemtype \memtype \ok + C \vdashmemtype \memtype \ok }{ C \vdashimportdesc \IDMEM~\memtype : \ETMEM~\memtype } @@ -485,7 +649,7 @@ Imports :math:`\import` and import descriptions :math:`\importdesc` are classifi .. math:: \frac{ - \vdashglobaltype \globaltype \ok + C \vdashglobaltype \globaltype \ok }{ C \vdashimportdesc \IDGLOBAL~\globaltype : \ETGLOBAL~\globaltype } @@ -506,14 +670,19 @@ that is, its components can only refer to definitions that appear in the module Consequently, no initial :ref:`context ` is required. Instead, the context :math:`C` for validation of the module's content is constructed from the definitions in the module. +The :ref:`external types ` classifying a module may contain free :ref:`type indices ` that refer to types defined within the module. + + * Let :math:`\module` be the module to validate. +* The :ref:`types ` :math:`\module.\MTYPES` must be :ref:`valid ` yielding a :ref:`context ` :math:`C_0`. + * Let :math:`C` be a :ref:`context ` where: - * :math:`C.\CTYPES` is :math:`\module.\MTYPES`, + * :math:`C.\CTYPES` is :math:`C_0.\CTYPES`, - * :math:`C.\CFUNCS` is :math:`\etfuncs(\X{it}^\ast)` concatenated with :math:`\X{ft}^\ast`, - with the import's :ref:`external types ` :math:`\X{it}^\ast` and the internal :ref:`function types ` :math:`\X{ft}^\ast` as determined below, + * :math:`C.\CFUNCS` is :math:`\etfuncs(\X{it}^\ast)` concatenated with :math:`\X{dt}^\ast`, + with the import's :ref:`external types ` :math:`\X{it}^\ast` and the internal :ref:`defined types ` :math:`\X{dt}^\ast` as determined below, * :math:`C.\CTABLES` is :math:`\ettables(\X{it}^\ast)` concatenated with :math:`\X{tt}^\ast`, with the import's :ref:`external types ` :math:`\X{it}^\ast` and the internal :ref:`table types ` :math:`\X{tt}^\ast` as determined below, @@ -536,21 +705,36 @@ Instead, the context :math:`C` for validation of the module's content is constru * :math:`C.\CREFS` is the set :math:`\freefuncidx(\module \with \MFUNCS = \epsilon \with \MSTART = \epsilon)`, i.e., the set of :ref:`function indices ` occurring in the module, except in its :ref:`functions ` or :ref:`start function `. -* Let :math:`C'` be the same :ref:`context ` as :math:`C`, except that :math:`C'.\CGLOBALS` is just the sequence :math:`\etglobals(\X{it}^\ast)`. +* Let :math:`C'` be the :ref:`context ` where: + + * :math:`C'.\CGLOBALS` is the sequence :math:`\etglobals(\X{it}^\ast)`, + + * :math:`C'.\CTYPES` is the same as :math:`C.\CTYPES`, -* For each :math:`\functype_i` in :math:`\module.\MTYPES`, - the :ref:`function type ` :math:`\functype_i` must be :ref:`valid `. + * :math:`C'.\CFUNCS` is the same as :math:`C.\CFUNCS`, + + * :math:`C'.\CTABLES` is the same as :math:`C.\CTABLES`, + + * :math:`C'.\CMEMS` is the same as :math:`C.\CMEMS`, + + * :math:`C'.\CREFS` is the same as :math:`C.\CREFS`, + + * all other fields are empty. * Under the context :math:`C'`: + * The sequence :math:`\module.\MGLOBALS` of :ref:`globals ` must be :ref:`valid ` with a sequence :math:`\X{gt}^\ast` of :ref:`global types `. + * For each :math:`\table_i` in :math:`\module.\MTABLES`, the definition :math:`\table_i` must be :ref:`valid ` with a :ref:`table type ` :math:`\X{tt}_i`. * For each :math:`\mem_i` in :math:`\module.\MMEMS`, the definition :math:`\mem_i` must be :ref:`valid ` with a :ref:`memory type ` :math:`\X{mt}_i`. - * For each :math:`\global_i` in :math:`\module.\MGLOBALS`, - the definition :math:`\global_i` must be :ref:`valid ` with a :ref:`global type ` :math:`\X{gt}_i`. +* Under the context :math:`C`: + + * For each :math:`\func_i` in :math:`\module.\MFUNCS`, + the definition :math:`\func_i` must be :ref:`valid ` with a :ref:`defined type ` :math:`\X{dt}_i`. * For each :math:`\elem_i` in :math:`\module.\MELEMS`, the segment :math:`\elem_i` must be :ref:`valid ` with :ref:`reference type ` :math:`\X{rt}_i`. @@ -558,11 +742,6 @@ Instead, the context :math:`C` for validation of the module's content is constru * For each :math:`\data_i` in :math:`\module.\MDATAS`, the segment :math:`\data_i` must be :ref:`valid `. -* Under the context :math:`C`: - - * For each :math:`\func_i` in :math:`\module.\MFUNCS`, - the definition :math:`\func_i` must be :ref:`valid ` with a :ref:`function type ` :math:`\X{ft}_i`. - * If :math:`\module.\MSTART` is non-empty, then :math:`\module.\MSTART` must be :ref:`valid `. @@ -572,42 +751,40 @@ Instead, the context :math:`C` for validation of the module's content is constru * For each :math:`\export_i` in :math:`\module.\MEXPORTS`, the segment :math:`\export_i` must be :ref:`valid ` with :ref:`external type ` :math:`\X{et}_i`. -* The length of :math:`C.\CMEMS` must not be larger than :math:`1`. - -* All export names :math:`\export_i.\ENAME` must be different. - -* Let :math:`\X{ft}^\ast` be the concatenation of the internal :ref:`function types ` :math:`\X{ft}_i`, in index order. +* Let :math:`\X{dt}^\ast` be the concatenation of the internal :ref:`function types ` :math:`\X{dt}_i`, in index order. * Let :math:`\X{tt}^\ast` be the concatenation of the internal :ref:`table types ` :math:`\X{tt}_i`, in index order. * Let :math:`\X{mt}^\ast` be the concatenation of the internal :ref:`memory types ` :math:`\X{mt}_i`, in index order. -* Let :math:`\X{gt}^\ast` be the concatenation of the internal :ref:`global types ` :math:`\X{gt}_i`, in index order. - * Let :math:`\X{rt}^\ast` be the concatenation of the :ref:`reference types ` :math:`\X{rt}_i`, in index order. * Let :math:`\X{it}^\ast` be the concatenation of :ref:`external types ` :math:`\X{it}_i` of the imports, in index order. * Let :math:`\X{et}^\ast` be the concatenation of :ref:`external types ` :math:`\X{et}_i` of the exports, in index order. +* The length of :math:`C.\CMEMS` must not be larger than :math:`1`. + +* All export names :math:`\export_i.\ENAME` must be different. + * Then the module is valid with :ref:`external types ` :math:`\X{it}^\ast \to \X{et}^\ast`. .. math:: \frac{ \begin{array}{@{}c@{}} - (\vdashfunctype \type \ok)^\ast + C_0 \vdashtypes \type^\ast \ok \quad - (C \vdashfunc \func : \X{ft})^\ast + C' \vdashglobals \global^\ast : \X{gt}^\ast \quad (C' \vdashtable \table : \X{tt})^\ast \quad (C' \vdashmem \mem : \X{mt})^\ast \quad - (C' \vdashglobal \global : \X{gt})^\ast + (C \vdashfunc \func : \X{dt})^\ast \\ - (C' \vdashelem \elem : \X{rt})^\ast + (C \vdashelem \elem : \X{rt})^\ast \quad - (C' \vdashdata \data \ok)^n + (C \vdashdata \data \ok)^n \quad (C \vdashstart \start \ok)^? \quad @@ -615,7 +792,7 @@ Instead, the context :math:`C` for validation of the module's content is constru \quad (C \vdashexport \export : \X{et})^\ast \\ - \X{ift}^\ast = \etfuncs(\X{it}^\ast) + \X{idt}^\ast = \etfuncs(\X{it}^\ast) \qquad \X{itt}^\ast = \ettables(\X{it}^\ast) \qquad @@ -625,11 +802,9 @@ Instead, the context :math:`C` for validation of the module's content is constru \\ x^\ast = \freefuncidx(\module \with \MFUNCS = \epsilon \with \MSTART = \epsilon) \\ - C = \{ \CTYPES~\type^\ast, \CFUNCS~\X{ift}^\ast\,\X{ft}^\ast, \CTABLES~\X{itt}^\ast\,\X{tt}^\ast, \CMEMS~\X{imt}^\ast\,\X{mt}^\ast, \CGLOBALS~\X{igt}^\ast\,\X{gt}^\ast, \CELEMS~\X{rt}^\ast, \CDATAS~{\ok}^n, \CREFS~x^\ast \} + C = \{ \CTYPES~C_0.\CTYPES, \CFUNCS~\X{idt}^\ast\,\X{dt}^\ast, \CTABLES~\X{itt}^\ast\,\X{tt}^\ast, \CMEMS~\X{imt}^\ast\,\X{mt}^\ast, \CGLOBALS~\X{igt}^\ast\,\X{gt}^\ast, \CELEMS~\X{rt}^\ast, \CDATAS~{\ok}^n, \CREFS~x^\ast \} \\ - C' = C \with \CGLOBALS = \X{igt}^\ast - \qquad - |C.\CMEMS| \leq 1 + C' = \{ \CTYPES~C_0.\CTYPES, \CGLOBALS~\X{igt}^\ast, \CFUNCS~(C.\CFUNCS), \CREFS~(C.\CREFS) \} \qquad (\export.\ENAME)^\ast ~\F{disjoint} \\ @@ -652,15 +827,14 @@ Instead, the context :math:`C` for validation of the module's content is constru } .. note:: - Most definitions in a module -- particularly functions -- are mutually recursive. + All functions in a module are mutually recursive. Consequently, the definition of the :ref:`context ` :math:`C` in this rule is recursive: it depends on the outcome of validation of the function, table, memory, and global definitions contained in the module, which itself depends on :math:`C`. However, this recursion is just a specification device. All types needed to construct :math:`C` can easily be determined from a simple pre-pass over the module that does not perform any actual validation. - Globals, however, are not recursive and not accessible within :ref:`constant expressions ` when they are defined locally. - The effect of defining the limited context :math:`C'` for validating certain definitions is that they can only access functions and imported globals and nothing else. + Globals, however, are not recursive but evaluated sequentially, such that each :ref:`constant expressions ` only has access to imported or previously defined globals. .. note:: The restriction on the number of memories may be lifted in future versions of WebAssembly. diff --git a/document/core/valid/types.rst b/document/core/valid/types.rst index 0e78e59762..39ab9601ed 100644 --- a/document/core/valid/types.rst +++ b/document/core/valid/types.rst @@ -1,47 +1,121 @@ +.. _valid-type: + Types ----- -Most :ref:`types ` are universally valid. -However, restrictions apply to :ref:`limits `, which must be checked during validation. +Simple :ref:`types `, such as :ref:`number types ` are universally valid. +However, restrictions apply to most other types, such as :ref:`reference types `, :ref:`function types `, as well as the :ref:`limits ` of :ref:`table types ` and :ref:`memory types `, which must be checked during validation. + Moreover, :ref:`block types ` are converted to plain :ref:`function types ` for ease of processing. -.. index:: limits - pair: validation; limits - single: abstract syntax; limits -.. _valid-limits: +.. index:: number type + pair: validation; number type + single: abstract syntax; number type +.. _valid-numtype: -Limits -~~~~~~ +Number Types +~~~~~~~~~~~~ -:ref:`Limits ` must have meaningful bounds that are within a given range. +:ref:`Number types ` are always valid. -:math:`\{ \LMIN~n, \LMAX~m^? \}` -................................ +.. math:: + \frac{ + }{ + C \vdashnumtype \numtype \ok + } -* The value of :math:`n` must not be larger than :math:`k`. -* If the maximum :math:`m^?` is not empty, then: +.. index:: vector type + pair: validation; vector type + single: abstract syntax; vector type +.. _valid-vectype: - * Its value must not be larger than :math:`k`. +Vector Types +~~~~~~~~~~~~ - * Its value must not be smaller than :math:`n`. +:ref:`Vector types ` are always valid. -* Then the limit is valid within range :math:`k`. +.. math:: + \frac{ + }{ + C \vdashvectype \vectype \ok + } + + +.. index:: heap type, type identifier + pair: validation; heap type + single: abstract syntax; heap type +.. _valid-heaptype: + +Heap Types +~~~~~~~~~~ + +Concrete :ref:`Heap types ` are only valid when the :ref:`type index ` is. + +:math:`\absheaptype` +.................... + +* The heap type is valid. .. math:: \frac{ - n \leq k - \qquad - (m \leq k)^? - \qquad - (n \leq m)^? }{ - \vdashlimits \{ \LMIN~n, \LMAX~m^? \} : k + C \vdashheaptype \absheaptype \ok + } + +:math:`\typeidx` +................ + +* The type :math:`C.\CTYPES[\typeidx]` must be defined in the context. + +* Then the heap type is valid. + +.. math:: + \frac{ + C.\CTYPES[\typeidx] = \deftype + }{ + C \vdashheaptype \typeidx \ok + } + + +.. index:: reference type, heap type + pair: validation; reference type + single: abstract syntax; reference type +.. _valid-reftype: + +Reference Types +~~~~~~~~~~~~~~~ + +:ref:`Reference types ` are valid when the referenced :ref:`heap type ` is. + +:math:`\REF~\NULL^?~\heaptype` +.............................. + +* The heap type :math:`\heaptype` must be :ref:`valid `. + +* Then the reference type is valid. + +.. math:: + \frac{ + C \vdashreftype \heaptype \ok + }{ + C \vdashreftype \REF~\NULL^?~\heaptype \ok } -.. index:: block type +.. index:: value type, reference type, number type, vector type + pair: validation; value type + single: abstract syntax; value type +.. _valid-valtype: + +Value Types +~~~~~~~~~~~ + +Valid :ref:`value types ` are either valid :ref:`number types `, valid :ref:`vector types `, or valid :ref:`reference types `. + + +.. index:: block type, instruction type pair: validation; block type single: abstract syntax; block type .. _valid-blocktype: @@ -49,35 +123,94 @@ Limits Block Types ~~~~~~~~~~~ -:ref:`Block types ` may be expressed in one of two forms, both of which are converted to plain :ref:`function types ` by the following rules. +:ref:`Block types ` may be expressed in one of two forms, both of which are converted to :ref:`instruction types ` by the following rules. :math:`\typeidx` ................ * The type :math:`C.\CTYPES[\typeidx]` must be defined in the context. -* Then the block type is valid as :ref:`function type ` :math:`C.\CTYPES[\typeidx]`. +* The :ref:`expansion ` of :math:`C.\CFUNCS[\typeidx]` must be a :ref:`function type ` :math:`\TFUNC~[t_1^\ast] \toF [t_2^\ast]`. + +* Then the block type is valid as :ref:`instruction type ` :math:`[t_1^\ast] \to [t_2^\ast]`. .. math:: \frac{ - C.\CTYPES[\typeidx] = \functype + \expanddt(C.\CTYPES[\typeidx]) = \TFUNC~[t_1^\ast] \toF [t_2^\ast] }{ - C \vdashblocktype \typeidx : \functype + C \vdashblocktype \typeidx : [t_1^\ast] \to [t_2^\ast] } :math:`[\valtype^?]` .................... -* The block type is valid as :ref:`function type ` :math:`[] \to [\valtype^?]`. +* The value type :math:`\valtype` must either be absent, or :ref:`valid `. + +* Then the block type is valid as :ref:`instruction type ` :math:`[] \to [\valtype^?]`. .. math:: \frac{ + (C \vdashvaltype \valtype \ok)^? }{ C \vdashblocktype [\valtype^?] : [] \to [\valtype^?] } +.. index:: result type, value type + pair: validation; result type + single: abstract syntax; result type +.. _valid-resulttype: + +Result Types +~~~~~~~~~~~~ + +:math:`[t^\ast]` +................ + +* Each :ref:`value type ` :math:`t_i` in the type sequence :math:`t^\ast` must be :ref:`valid `. + +* Then the result type is valid. + +.. math:: + \frac{ + (C \vdashvaltype t \ok)^\ast + }{ + C \vdashresulttype [t^\ast] \ok + } + + +.. index:: instruction type + pair: validation; instruction type + single: abstract syntax; instruction type +.. _valid-instrtype: + +Instruction Types +~~~~~~~~~~~~~~~~~ + +:math:`[t_1^\ast] \rightarrow_{x^\ast} [t_2^\ast]` +.................................................. + +* The :ref:`result type ` :math:`[t_1^\ast]` must be :ref:`valid `. + +* The :ref:`result type ` :math:`[t_2^\ast]` must be :ref:`valid `. + +* Each :ref:`local index ` :math:`x_i` in :math:`x^\ast` must be defined in the context. + +* Then the instruction type is valid. + +.. math:: + \frac{ + C \vdashvaltype [t_1^\ast] \ok + \qquad + C \vdashvaltype [t_2^\ast] \ok + \qquad + (C.\CLOCALS[x] = \localtype)^\ast + }{ + C \vdashfunctype [t_1^\ast] \to_{x^\ast} [t_2^\ast] \ok + } + + .. index:: function type pair: validation; function type single: abstract syntax; function type @@ -86,17 +219,276 @@ Block Types Function Types ~~~~~~~~~~~~~~ -:ref:`Function types ` are always valid. +:math:`[t_1^\ast] \toF [t_2^\ast]` +.................................. + +* The :ref:`result type ` :math:`[t_1^\ast]` must be :ref:`valid `. + +* The :ref:`result type ` :math:`[t_2^\ast]` must be :ref:`valid `. + +* Then the function type is valid. + +.. math:: + \frac{ + C \vdashvaltype [t_1^\ast] \ok + \qquad + C \vdashvaltype [t_2^\ast] \ok + }{ + C \vdashfunctype [t_1^\ast] \toF [t_2^\ast] \ok + } + -:math:`[t_1^n] \to [t_2^m]` +.. index:: composite type, function type, aggregate type, structure type, array type, field type + pair: validation; composite type + pair: validation; aggregate type + pair: validation; structure type + pair: validation; array type + single: abstract syntax; composite type + single: abstract syntax; function type + single: abstract syntax; structure type + single: abstract syntax; array type + single: abstract syntax; field type +.. _valid-comptype: +.. _valid-aggrtype: +.. _valid-structtype: +.. _valid-arraytype: + +Composite Types +~~~~~~~~~~~~~~~ + +:math:`\TFUNC~\functype` +........................ + +* The :ref:`function type ` :math:`\functype` must be :ref:`valid `. + +* Then the composite type is valid. + +.. math:: + \frac{ + C \vdashfunctype \functype \ok + }{ + C \vdashcomptype \TFUNC~\functype \ok + } + +:math:`\TSTRUCT~\fieldtype^\ast` +................................ + +* For each :ref:`field type ` :math:`\fieldtype_i` in :math:`\fieldtype^\ast`: + + * The :ref:`field type ` :math:`\fieldtype_i` must be :ref:`valid `. + +* Then the composite type is valid. + +.. math:: + \frac{ + (C \vdashfieldtype \X{ft} \ok)^\ast + }{ + C \vdashcomptype \TSTRUCT~\X{ft}^\ast \ok + } + +:math:`\TARRAY~\fieldtype` +.......................... + +* The :ref:`field type ` :math:`\fieldtype` must be :ref:`valid `. + +* Then the composite type is valid. + +.. math:: + \frac{ + C \vdashfieldtype \X{ft} \ok + }{ + C \vdashcomptype \TARRAY~\X{ft} \ok + } + + +.. index:: field type, storage type, packed type, value type, mutability + pair: validation; field type + pair: validation; storage type + pair: validation; packed type + single: abstract syntax; field type + single: abstract syntax; storage type + single: abstract syntax; packed type + single: abstract syntax; value type +.. _valid-fieldtype: +.. _valid-storagetype: +.. _valid-packedtype: + +Field Types +~~~~~~~~~~~ + +:math:`\mut~\storagetype` +......................... + +* The :ref:`storage type ` :math:`\storagetype` must be :ref:`valid `. + +* Then the field type is valid. + +.. math:: + \frac{ + C \vdashstoragetype \X{st} \ok + }{ + C \vdashfieldtype \mut~\X{st} \ok + } + +:math:`\packedtype` +................... + +* The packed type is valid. + +.. math:: + \frac{ + }{ + C \vdashpackedtype \packedtype \ok + } + + +.. index:: recursive type, sub type, composite type, final, subtyping + pair: abstract syntax; recursive type + pair: abstract syntax; sub type +.. _valid-rectype: +.. _valid-subtype: + +Recursive Types +~~~~~~~~~~~~~~~ + +:ref:`Recursive types ` are validated for a specific :ref:`type index ` that denotes the index of the type defined by the recursive group. + +:math:`\TREC~\subtype^\ast` ........................... -* The function type is valid. +* Either the sequence :math:`\subtype^\ast` is empty. + +* Or: + + * The first :ref:`sub type ` of the sequence :math:`\subtype^\ast` must be :ref:`valid ` for the :ref:`type index ` :math:`x`. + + * The remaining sequence :math:`\subtype^\ast` must be :ref:`valid ` for the :ref:`type index ` :math:`x + 1`. + +* Then the recursive type is valid for the :ref:`type index ` :math:`x`. .. math:: \frac{ }{ - \vdashfunctype [t_1^\ast] \to [t_2^\ast] \ok + C \vdashrectype \TREC~\epsilon ~{\ok}(x) + } + \qquad + \frac{ + C \vdashsubtype \subtype ~{\ok}(x) + \qquad + C \vdashrectype \TREC~{\subtype'}^\ast ~{\ok}(x + 1) + }{ + C \vdashrectype \TREC~\subtype~{\subtype'}^\ast ~{\ok}(x) + } + +:math:`\TSUB~\TFINAL^?~y^\ast~\comptype` +........................................ + +* The :ref:`composite type ` :math:`\comptype` must be :ref:`valid `. + +* The sequence :math:`y^\ast` may be no longer than :math:`1`. + +* For every :ref:`type index ` :math:`y_i` in :math:`y^\ast`: + + * The :ref:`type index ` :math:`y_i` must be smaller than :math:`x`. + + * The :ref:`type index ` :math:`y_i` must exist in the context :math:`C`. + + * Let :math:`\subtype_i` be the :ref:`unrolling ` of the :ref:`defined type ` :math:`C.\CTYPES[y_i]`. + + * The :ref:`sub type ` :math:`\subtype_i` must not contain :math:`\TFINAL`. + + * Let :math:`\comptype'_i` be the :ref:`composite type ` in :math:`\subtype_i`. + + * The :ref:`composite type ` :math:`\comptype` must :ref:`match ` :math:`\comptype'_i`. + +* Then the sub type is valid for the :ref:`type index ` :math:`x`. + +.. math:: + \frac{ + \begin{array}{@{}c@{}} + |y^\ast| \leq 1 + \qquad + (y < x)^\ast + \qquad + (\unrolldt(C.\CTYPES[y]) = \TSUB~{y'}^\ast~\comptype')^\ast + \\ + C \vdashcomptype \comptype \ok + \qquad + (C \vdashcomptypematch \comptype \matchescomptype \comptype')^\ast + \end{array} + }{ + C \vdashsubtype \TSUB~\TFINAL^?~y^\ast~\comptype ~{\ok}(x) + } + +.. note:: + The side condition on the index ensures that a declared supertype is a previously defined types, + preventing cyclic subtype hierarchies. + + Future versions of WebAssembly may allow more than one supertype. + + +.. index:: defined type, recursive type, unroll, expand + pair: abstract syntax; defined type +.. _valid-deftype: + +Defined Types +~~~~~~~~~~~~~ + +:math:`\rectype.i` +.................. + +* The :ref:`recursive type ` :math:`\rectype` must be :ref:`valid ` for some :ref:`type index ` :math:`x`. + +* Let :math:`\TREC~\subtype^\ast` be the :ref:`defined type ` :math:`\rectype`. + +* The number :math:`i` must be smaller than the length of the sequence :math:`\subtype^\ast` of :ref:`sub types `. + +* Then the defined type is valid. + +.. math:: + \frac{ + C \vdashrectype \rectype ~{\ok}(x) + \qquad + \rectype = \TREC~\subtype^n + \qquad + i < n + }{ + C \vdashdeftype \rectype.i \ok + } + + +.. index:: limits + pair: validation; limits + single: abstract syntax; limits +.. _valid-limits: + +Limits +~~~~~~ + +:ref:`Limits ` must have meaningful bounds that are within a given range. + +:math:`\{ \LMIN~n, \LMAX~m^? \}` +................................ + +* The value of :math:`n` must not be larger than :math:`k`. + +* If the maximum :math:`m^?` is not empty, then: + + * Its value must not be larger than :math:`k`. + + * Its value must not be smaller than :math:`n`. + +* Then the limit is valid within range :math:`k`. + +.. math:: + \frac{ + n \leq k + \qquad + (m \leq k)^? + \qquad + (n \leq m)^? + }{ + C \vdashlimits \{ \LMIN~n, \LMAX~m^? \} : k } @@ -113,13 +505,17 @@ Table Types * The limits :math:`\limits` must be :ref:`valid ` within range :math:`2^{32}-1`. +* The reference type :math:`\reftype` must be :ref:`valid `. + * Then the table type is valid. .. math:: \frac{ - \vdashlimits \limits : 2^{32} - 1 + C \vdashlimits \limits : 2^{32} - 1 + \qquad + C \vdashreftype \reftype \ok }{ - \vdashtabletype \limits~\reftype \ok + C \vdashtabletype \limits~\reftype \ok } @@ -140,9 +536,9 @@ Memory Types .. math:: \frac{ - \vdashlimits \limits : 2^{16} + C \vdashlimits \limits : 2^{16} }{ - \vdashmemtype \limits \ok + C \vdashmemtype \limits \ok } @@ -157,12 +553,15 @@ Global Types :math:`\mut~\valtype` ..................... -* The global type is valid. +* The value type :math:`\valtype` must be :ref:`valid `. + +* Then the global type is valid. .. math:: \frac{ + C \vdashreftype \valtype \ok }{ - \vdashglobaltype \mut~\valtype \ok + C \vdashglobaltype \mut~\valtype \ok } @@ -174,18 +573,22 @@ Global Types External Types ~~~~~~~~~~~~~~ -:math:`\ETFUNC~\functype` -......................... +:math:`\ETFUNC~\deftype` +........................ -* The :ref:`function type ` :math:`\functype` must be :ref:`valid `. +* The :ref:`defined type ` :math:`\deftype` must be :ref:`valid `. + +* The :ref:`defined type ` :math:`\deftype` must be a :ref:`function type `. * Then the external type is valid. .. math:: \frac{ - \vdashfunctype \functype \ok + C \vdashdeftype \deftype \ok + \qquad + \expanddt(\deftype) = \TFUNC~\functype }{ - \vdashexterntype \ETFUNC~\functype \ok + C \vdashexterntype \ETFUNC~\deftype } :math:`\ETTABLE~\tabletype` @@ -197,9 +600,9 @@ External Types .. math:: \frac{ - \vdashtabletype \tabletype \ok + C \vdashtabletype \tabletype \ok }{ - \vdashexterntype \ETTABLE~\tabletype \ok + C \vdashexterntype \ETTABLE~\tabletype \ok } :math:`\ETMEM~\memtype` @@ -211,9 +614,9 @@ External Types .. math:: \frac{ - \vdashmemtype \memtype \ok + C \vdashmemtype \memtype \ok }{ - \vdashexterntype \ETMEM~\memtype \ok + C \vdashexterntype \ETMEM~\memtype \ok } :math:`\ETGLOBAL~\globaltype` @@ -225,131 +628,46 @@ External Types .. math:: \frac{ - \vdashglobaltype \globaltype \ok - }{ - \vdashexterntype \ETGLOBAL~\globaltype \ok - } - - -.. index:: ! matching, external type -.. _exec-import: -.. _match: - -Import Subtyping -~~~~~~~~~~~~~~~~ - -When :ref:`instantiating ` a module, -:ref:`external values ` must be provided whose :ref:`types ` are *matched* against the respective :ref:`external types ` classifying each import. -In some cases, this allows for a simple form of subtyping (written ":math:`\matchesexterntype`" formally), as defined here. - - -.. index:: limits -.. _match-limits: - -Limits -...... - -:ref:`Limits ` :math:`\{ \LMIN~n_1, \LMAX~m_1^? \}` match limits :math:`\{ \LMIN~n_2, \LMAX~m_2^? \}` if and only if: - -* :math:`n_1` is larger than or equal to :math:`n_2`. - -* Either: - - * :math:`m_2^?` is empty. - -* Or: - - * Both :math:`m_1^?` and :math:`m_2^?` are non-empty. - - * :math:`m_1` is smaller than or equal to :math:`m_2`. - -.. math:: - ~\\[-1ex] - \frac{ - n_1 \geq n_2 + C \vdashglobaltype \globaltype \ok }{ - \vdashlimitsmatch \{ \LMIN~n_1, \LMAX~m_1^? \} \matcheslimits \{ \LMIN~n_2, \LMAX~\epsilon \} + C \vdashexterntype \ETGLOBAL~\globaltype \ok } - \quad - \frac{ - n_1 \geq n_2 - \qquad - m_1 \leq m_2 - }{ - \vdashlimitsmatch \{ \LMIN~n_1, \LMAX~m_1 \} \matcheslimits \{ \LMIN~n_2, \LMAX~m_2 \} - } - -.. _match-externtype: -.. index:: function type -.. _match-functype: -Functions -......... +.. index:: value type, ! defaultable, number type, vector type, reference type, table type +.. _valid-defaultable: -An :ref:`external type ` :math:`\ETFUNC~\functype_1` matches :math:`\ETFUNC~\functype_2` if and only if: +Defaultable Types +~~~~~~~~~~~~~~~~~ -* Both :math:`\functype_1` and :math:`\functype_2` are the same. +A type is *defaultable* if it has a :ref:`default value ` for initialization. -.. math:: - ~\\[-1ex] - \frac{ - }{ - \vdashexterntypematch \ETFUNC~\functype \matchesexterntype \ETFUNC~\functype - } +Value Types +........... +* A defaultable :ref:`value type ` :math:`t` must be: -.. index:: table type, limits, element type -.. _match-tabletype: + - either a :ref:`number type `, -Tables -...... + - or a :ref:`vector type `, -An :ref:`external type ` :math:`\ETTABLE~(\limits_1~\reftype_1)` matches :math:`\ETTABLE~(\limits_2~\reftype_2)` if and only if: + - or a :ref:`nullable reference type `. -* Limits :math:`\limits_1` :ref:`match ` :math:`\limits_2`. - -* Both :math:`\reftype_1` and :math:`\reftype_2` are the same. .. math:: \frac{ - \vdashlimitsmatch \limits_1 \matcheslimits \limits_2 }{ - \vdashexterntypematch \ETTABLE~(\limits_1~\reftype) \matchesexterntype \ETTABLE~(\limits_2~\reftype) + C \vdashvaltypedefaultable \numtype \defaultable } - -.. index:: memory type, limits -.. _match-memtype: - -Memories -........ - -An :ref:`external type ` :math:`\ETMEM~\limits_1` matches :math:`\ETMEM~\limits_2` if and only if: - -* Limits :math:`\limits_1` :ref:`match ` :math:`\limits_2`. - .. math:: \frac{ - \vdashlimitsmatch \limits_1 \matcheslimits \limits_2 }{ - \vdashexterntypematch \ETMEM~\limits_1 \matchesexterntype \ETMEM~\limits_2 + C \vdashvaltypedefaultable \vectype \defaultable } - -.. index:: global type, value type, mutability -.. _match-globaltype: - -Globals -....... - -An :ref:`external type ` :math:`\ETGLOBAL~\globaltype_1` matches :math:`\ETGLOBAL~\globaltype_2` if and only if: - -* Both :math:`\globaltype_1` and :math:`\globaltype_2` are the same. - .. math:: - ~\\[-1ex] \frac{ }{ - \vdashexterntypematch \ETGLOBAL~\globaltype \matchesexterntype \ETGLOBAL~\globaltype + C \vdashvaltypedefaultable (\REF~\NULL~\heaptype) \defaultable } diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 9a5def866a..bc74cbd36c 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -41,11 +41,29 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT text: ? text: Type; url: sec-ecmascript-data-types-and-values text: current Realm; url: current-realm + text: ObjectCreate; url: sec-objectcreate + text: CreateBuiltinFunction; url: sec-createbuiltinfunction + text: SetFunctionName; url: sec-setfunctionname + text: SetFunctionLength; url: sec-setfunctionlength + text: the Number value; url: sec-ecmascript-language-types-number-type + text: is a Number; url: sec-ecmascript-language-types-number-type + text: NumberToRawBytes; url: sec-numbertorawbytes + text: Built-in Function Objects; url: sec-built-in-function-objects + text: NativeError Object Structure; url: sec-nativeerror-object-structure + text: CreateArrayFromList; url: sec-createarrayfromlist + text: GetMethod; url: sec-getmethod + text: IterableToList; url: sec-iterabletolist + text: ToBigInt64; url: #sec-tobigint64 + text: BigInt; url: #sec-ecmascript-language-types-bigint-type + text: MakeBasicObject; url: #sec-makebasicobject + text: ℝ; url: #ℝ text: Built-in Function Objects; url: sec-built-in-function-objects text: NativeError Object Structure; url: sec-nativeerror-object-structure text: 𝔽; url: #𝔽 text: ℤ; url: #ℤ text: SameValue; url: sec-samevalue + type: abstract-op + text: CreateMethodProperty; url: sec-createmethodproperty urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: dfn url: valid/modules.html#valid-module text: valid @@ -65,7 +83,11 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: f64.const text: v128.const text: ref.null + text: ref.i31 + text: ref.array + text: ref.struct text: ref.func + text: ref.host text: ref.extern text: function index; url: syntax/modules.html#syntax-funcidx text: function instance; url: exec/runtime.html#function-instances @@ -95,6 +117,9 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: global_type; url: appendix/embedding.html#embed-global-type text: global_read; url: appendix/embedding.html#embed-global-read text: global_write; url: appendix/embedding.html#embed-global-write + text: ref_type; url: appendix/embedding.html#embed-ref-type + text: val_default; url: appendix/embedding.html#embed-val-default + text: match_valtype; url: appendix/embedding.html#embed-match-valtype text: error; url: appendix/embedding.html#embed-error text: store; url: exec/runtime.html#syntax-store text: table type; url: syntax/types.html#syntax-tabletype @@ -102,6 +127,9 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: function address; url: exec/runtime.html#syntax-funcaddr text: memory address; url: exec/runtime.html#syntax-memaddr text: global address; url: exec/runtime.html#syntax-globaladdr + text: struct address; url: exec/runtime.html#syntax-structaddr + text: array address; url: exec/runtime.html#syntax-arrayaddr + text: host address; url: exec/runtime.html#syntax-hostaddr text: extern address; url: exec/runtime.html#syntax-externaddr text: page size; url: exec/runtime.html#page-size url: syntax/types.html#syntax-numtype @@ -115,6 +143,12 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: reftype text: funcref text: externref + text: ref + url: syntax/types.html#heap-types; for: heap-type + text: extern + text: func + text: i31 + text: any url: syntax/values.html#syntax-float text: +∞ text: −∞ @@ -129,7 +163,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: module; url: syntax/modules.html#syntax-module text: imports; url: syntax/modules.html#syntax-module text: import; url: syntax/modules.html#syntax-import - url: syntax/types.html#external-types + url: syntax/types.html#external-types; for: external-type text: external type text: func text: table @@ -140,6 +174,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: var text: const text: address; url: exec/runtime.html#addresses + text: signed_31; url: exec/numerics.html#aux-signed text: signed_32; url: exec/numerics.html#aux-signed text: memory.grow; url: exec/instructions.html#exec-memory-grow text: current frame; url: exec/conventions.html#exec-notation-textual @@ -235,8 +270,9 @@ Each [=agent=] is associated with the following [=ordered map=]s: * The Memory object cache, mapping [=memory address=]es to {{Memory}} objects. * The Table object cache, mapping [=table address=]es to {{Table}} objects. * The Exported Function cache, mapping [=function address=]es to [=Exported Function=] objects. + * The Exported GC Object cache, mapping [=struct address=]es and [=array address=]es to [=Exported GC Object=] objects. * The Global object cache, mapping [=global address=]es to {{Global}} objects. - * The Extern value cache, mapping [=extern address=]es to values. + * The Host value cache, mapping [=host address=]es to values.

The WebAssembly Namespace

@@ -326,7 +362,7 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje 1. Let |o| be [=?=] [$Get$](|importObject|, |moduleName|). 1. If [=Type=](|o|) is not Object, throw a {{TypeError}} exception. 1. Let |v| be [=?=] [$Get$](|o|, |componentName|). - 1. If |externtype| is of the form [=func=] |functype|, + 1. If |externtype| is of the form [=external-type/func=] |functype|, 1. If [$IsCallable$](|v|) is false, throw a {{LinkError}} exception. 1. If |v| has a \[[FunctionAddress]] internal slot, and therefore is an [=Exported Function=], 1. Let |funcaddr| be the value of |v|'s \[[FunctionAddress]] internal slot. @@ -336,21 +372,19 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje 1. Let |externfunc| be the [=external value=] [=external value|func=] |funcaddr|. 1. [=list/Append=] |externfunc| to |imports|. 1. If |externtype| is of the form [=global=] mut |valtype|, - 1. If [=Type=](|v|) is Number or BigInt, - 1. If |valtype| is [=i64=] and [=Type=](|v|) is Number, + 1. If |v| [=implements=] {{Global}}, + 1. Let |globaladdr| be |v|.\[[Global]]. + 1. Otherwise, + 1. If |valtype| is [=i64=] and [=Type=](|v|) is not BigInt, 1. Throw a {{LinkError}} exception. - 1. If |valtype| is not [=i64=] and [=Type=](|v|) is BigInt, + 1. If |valtype| is one of [=i32=], [=f32=] or [=f64=] and [=Type=](|v|) is not Number, 1. Throw a {{LinkError}} exception. 1. If |valtype| is [=v128=], 1. Throw a {{LinkError}} exception. - 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valtype|). + 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valtype|). If this operation throws a {{TypeError}}, catch it, and throw a {{LinkError}} exception. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (|store|, |globaladdr|) be [=global_alloc=](|store|, [=const=] |valtype|, |value|). 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. - 1. Otherwise, if |v| [=implements=] {{Global}}, - 1. Let |globaladdr| be |v|.\[[Global]]. - 1. Otherwise, - 1. Throw a {{LinkError}} exception. 1. Let |externglobal| be [=external value|global=] |globaladdr|. 1. [=list/Append=] |externglobal| to |imports|. 1. If |externtype| is of the form [=mem=] memtype, @@ -375,7 +409,7 @@ The verification of WebAssembly type requirements is deferred to the 1. [=list/iterate|For each=] (|name|, |externtype|) of [=module_exports=](|module|), 1. Let |externval| be [=instance_export=](|instance|, |name|). 1. Assert: |externval| is not [=error=]. - 1. If |externtype| is of the form [=func=] functype, + 1. If |externtype| is of the form [=external-type/func=] functype, 1. Assert: |externval| is of the form [=external value|func=] |funcaddr|. 1. Let [=external value|func=] |funcaddr| be |externval|. 1. Let |func| be the result of creating [=a new Exported Function=] from |funcaddr|. @@ -505,7 +539,7 @@ interface Module {
The string value of the extern type |type| is - * "function" if |type| is of the form [=func=] functype + * "function" if |type| is of the form [=external-type/func=] functype * "table" if |type| is of the form [=table=] tabletype * "memory" if |type| is of the form [=mem=] memtype * "global" if |type| is of the form [=global=] globaltype @@ -827,6 +861,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Assert: |ref| is not [=error=]. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |type| be the [=table type=] {[=table type|min=] |initial|, [=table type|max=] |maximum|} |elementType|. @@ -844,6 +879,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |result| be [=table_grow=](|store|, |tableaddr|, |delta|, |ref|). @@ -877,6 +913,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. @@ -953,13 +990,8 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each
The algorithm DefaultValue(|valuetype|) performs the following steps: - 1. If |valuetype| equals [=i32=], return [=i32.const=] 0. - 1. If |valuetype| equals [=i64=], return [=i64.const=] 0. - 1. If |valuetype| equals [=f32=], return [=f32.const=] 0. - 1. If |valuetype| equals [=f64=], return [=f64.const=] 0. - 1. If |valuetype| equals [=funcref=], return [=ref.null=] [=funcref=]. 1. If |valuetype| equals [=externref=], return [=ToWebAssemblyValue=](undefined, |valuetype|). - 1. Assert: This step is not reached. + 1. Return [=val_default=](|valuetype|).
@@ -970,6 +1002,7 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. Throw a {{TypeError}} exception. 1. If |v| is missing, 1. Let |value| be [=DefaultValue=](|valuetype|). + 1. Assert: |value| is not [=error=]. 1. Otherwise, 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valuetype|). 1. If |mutable| is true, let |globaltype| be [=var=] |valuetype|; otherwise, let |globaltype| be [=const=] |valuetype|. @@ -1132,10 +1165,12 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a JavaScript value by performing the following steps: 1. Assert: |w| is not of the form [=v128.const=] v128. -1. If |w| is of the form [=i64.const=] |i64|, - 1. Let |v| be [=signed_64=](|i64|). - 1. Return [=ℤ=](|v| interpreted as a mathematical value). -1. If |w| is of the form [=i32.const=] |i32|, return [=𝔽=]([=signed_32=](|i32| interpreted as a mathematical value)). +1. If |w| is of the form [=i64.const=] |u64|, + 1. Let |i64| be [=signed_64=](|u64|). + 1. Return [=ℤ=](|i64| interpreted as a mathematical value). +1. If |w| is of the form [=i32.const=] |u32|, + 1. Let |i32| be [=signed_32=](|i32|). + 2. Return [=𝔽=](|i32| interpreted as a mathematical value). 1. If |w| is of the form [=f32.const=] |f32|, 1. If |f32| is [=+∞=] or [=−∞=], return **+∞**𝔽 or **-∞**𝔽, respectively. 1. If |f32| is [=nan=], return **NaN**. @@ -1144,20 +1179,27 @@ The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a Jav 1. If |f64| is [=+∞=] or [=−∞=], return **+∞**𝔽 or **-∞**𝔽, respectively. 1. If |f64| is [=nan=], return **NaN**. 1. Return [=𝔽=](|f64| interpreted as a mathematical value). -1. If |w| is of the form [=ref.null=] t, return null. +1. If |w| is of the form [=ref.null=] |t|, return null. +1. If |w| is of the form [=ref.i31=] |u31|, + 1. Let |i31| be [=signed_31=](|u31|). + 1. Let return [=𝔽=](|i31|). +1. If |w| is of the form [=ref.struct=] |structaddr|, return the result of creating [=a new Exported GC Object=] from |structaddr| and "struct". +1. If |w| is of the form [=ref.array=] |arrayaddr|, return the result of creating [=a new Exported GC Object=] from |arrayaddr| and "array". 1. If |w| is of the form [=ref.func=] |funcaddr|, return the result of creating [=a new Exported Function=] from |funcaddr|. -1. If |w| is of the form [=ref.extern=] |externaddr|, return the result of [=retrieving an extern value=] from |externaddr|. +1. If |w| is of the form [=ref.host=] |hostaddr|, return the result of [=retrieving a host value=] from |hostaddr|. +1. If |w| is of the form [=ref.extern=] ref, return [=ToJSValue=](|ref|). + Note: Number values which are equal to NaN may have various observable NaN payloads; see [$NumericToRawBytes$] for details.
-For retrieving an extern value from an [=extern address=] |externaddr|, perform the following steps: +For retrieving a host value from an [=host address=] |hostaddr|, perform the following steps: -1. Let |map| be the [=surrounding agent=]'s associated [=extern value cache=]. -1. Assert: |map|[|externaddr|] [=map/exists=]. -1. Return |map|[|externaddr|]. +1. Let |map| be the [=surrounding agent=]'s associated [=host value cache=]. +1. Assert: |map|[|hostaddr|] [=map/exists=]. +1. Return |map|[|hostaddr|].
@@ -1167,10 +1209,12 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va 1. Assert: |type| is not [=v128=]. 1. If |type| is [=i64=], 1. Let |i64| be [=?=] [$ToBigInt64$](|v|). - 1. Return [=i64.const=] |i64|. + 1. Let |u64| be the unsigned integer such that |i64| is [=signed_64=](|u64|). + 1. Return [=i64.const=] |u64|. 1. If |type| is [=i32=], 1. Let |i32| be [=?=] [$ToInt32$](|v|). - 1. Return [=i32.const=] |i32|. + 1. Let |u32| be the unsigned integer such that |i32| is [=signed_32=](|u32|). + 1. Return [=i32.const=] |u32|. 1. If |type| is [=f32=], 1. Let |number| be [=?=] [$ToNumber$](|v|). 1. If |number| is **NaN**, @@ -1187,26 +1231,147 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va 1. Otherwise, 1. Let |f64| be |number|. 1. Return [=f64.const=] |f64|. -1. If |type| is [=funcref=], +1. If |type| is of the form [=ref=] |null| |heaptype|, 1. If |v| is null, - 1. Return [=ref.null=] [=funcref=]. - 1. If |v| is an [=Exported Function=], + 1. Let |r| be [=ref.null=] |heaptype|. + 1. Else if [=match_valtype=](|type|, [=ref=] |null| [=heap-type/extern=]), + 1. Let |ref| be [=ToWebAssemblyValue=](|v|, [=ref=] [=heap-type/any=]). + 1. Let |r| be [=ref.extern=] |ref|. + 1. Else if |v| is an [=Exported Function=] and [=match_valtype=](|type|, [=ref=] |null| [=heap-type/func=]), 1. Let |funcaddr| be the value of |v|'s \[[FunctionAddress]] internal slot. - 1. Return [=ref.func=] |funcaddr|. - 1. Throw a {{TypeError}}. -1. If |type| is [=externref=], - 1. If |v| is null, - 1. Return [=ref.null=] [=externref=]. - 1. Let |map| be the [=surrounding agent=]'s associated [=extern value cache=]. - 1. If a [=extern address=] |externaddr| exists such that |map|[|externaddr|] is the same as |v|, - 1. Return [=ref.extern=] |externaddr|. - 1. Let [=extern address=] |externaddr| be the smallest address such that |map|[|externaddr|] [=map/exists=] is false. - 1. [=map/Set=] |map|[|externaddr|] to |v|. - 1. Return [=ref.extern=] |externaddr|. + 1. Let |r| be [=ref.func=] |funcaddr|. + 1. Else if |v| [=is a Number=] and |v| is equal to [=?=] [$ToInt32$](|v|) and [=ℝ=](|v|) < 230 and [=ℝ=](|v|) ⩾ -230, + 1. Let |i31| [=?=] [$ToInt32$](|v|). + 1. Let |u31| be the unsigned integer such that |i31| is [=signed_31=](|i31|). + 1. Let |r| be [=ref.i31=] |u31|. + 1. Else if |v| is an [=Exported GC Object=], + 1. Let |objectaddr| be the value of |v|'s \[[ObjectAddress]] internal slot. + 1. Let |objectkind| be the value of |v|'s \[[ObjectKind]] internal slot. + 1. If |objectkind| is "array", + 1. Let |r| be [=ref.array=] |objectaddr|. + 1. If |objectkind| is "struct", + 1. Let |r| be [=ref.struct=] |objectaddr|. + 1. Else, + 1. Let |map| be the [=surrounding agent=]'s associated [=host value cache=]. + 1. If a [=host address=] |hostaddr| exists such that |map|[|hostaddr|] is the same as |v|, + 1. Return [=ref.host=] |hostaddr|. + 1. Let [=host address=] |hostaddr| be the smallest address such that |map|[|hostaddr|] [=map/exists=] is false. + 1. [=map/Set=] |map|[|hostaddr|] to |v|. + 1. Let |r| be [=ref.host=] |hostaddr|. + 1. Let |store| be the current agent's [=associated store=]. + 1. Let |actualtype| be [=ref_type=](|store|, |r|). + 1. If [=match_valtype=](|actualtype|, |type|) is false, + 1. Throw a {{TypeError}}. + 1. Return |r|. 1. Assert: This step is not reached.
+

Garbage Collected Objects

+ +A WebAssembly struct or array is made available in JavaScript as an Exported GC Object. +An [=Exported GC Object=] is an exotic object that wraps a garbage collected WebAssembly reference value. +Most JavaScript operations on an [=Exported GC Object=] will throw an exception or return undefined. + +Note: These operations may be refined in the future to allow richer interactions in JavaScript with WebAssembly structs and arrays. + +An [=Exported GC Object=] contains an \[[ObjectAddress]] internal slot, which holds a [=object address=] relative to the [=surrounding agent=]'s [=associated store=], +and an \[[ObjectKind]] internal slot, which holds the string value "struct" or "array". + +The internal methods of an [=Exported GC Object=] use the following implementations. + +
+ The \[[GetPrototypeOf]] internal method of an Exported GC Object O takes no arguments and returns null. It performs the following steps when called: + + 1. Return null. +
+ +
+ The \[[SetPrototypeOf]] internal method of an Exported GC Object O takes argument V (an Object or null) and returns a boolean. It performs the following steps when called: + + 1. Return false. +
+ +
+ The \[[IsExtensible]] internal method of an Exported GC Object O takes no arguments and returns a boolean. It performs the following steps when called: + + 1. Return false. +
+ +
+ The \[[PreventExtensions]] internal method of an Exported GC Object O takes no arguments and returns a boolean. It performs the following steps when called: + + 1. Return false. +
+ +
+ The \[[GetOwnProperty]] internal method of an Exported GC Object O takes argument P (a property key) and returns undefined. It performs the following steps when called: + + 1. Return undefined. +
+ +
+ The \[[DefineOwnProperty]] internal method of an Exported GC Object O takes arguments P (a property key) and Desc (a property descriptor) and returns a boolean. It performs the following steps when called: + + 1. Return false. +
+ +
+ The \[[HasProperty]] internal method of an Exported GC Object O takes argument P (a property key) and returns a boolean. It performs the following steps when called: + + 1. Return false. +
+ +
+ The \[[Get]] internal method of an Exported GC Object O takes arguments P (a property key) and Receiver (an ECMAScript language value) and returns undefined. It performs the following steps when called: + + 1. Return undefined. +
+ +
+ The \[[Set]] internal method of an Exported GC Object O takes arguments P (a property key), V (an ECMAScript language value), and Receiver (an ECMAScript language value) and throws an exception. It performs the following steps when called: + + 1. Throw a {{TypeError}}. +
+ +
+ The \[[Delete]] internal method of an Exported GC Object O takes argument P (a property key) and throws an exception. It performs the following steps when called: + + 1. Throw a {{TypeError}}. +
+ +
+ The \[[OwnPropertyKeys]] internal method of an Exported GC Object O takes no arguments and returns a list. It performs the following steps when called: + + 1. Let keys be a new empty list. + 1. Return keys. +
+ +
+ To create a new Exported GC Object from a WebAssembly [=object address=] |objectaddr| and a string |objectkind|, perform the following steps: + + 1. Assert: |objectkind| is either "array" or "struct". + 1. Let |map| be the [=surrounding agent=]'s associated [=exported GC object cache=]. + 1. If |map|[|objectaddr|] [=map/exists=], + 1. Return |map|[|objectaddr|]. + 1. Let |object| be [=MakeBasicObject=](« \[[ObjectAddress]] »). + 1. Set |object|.\[[ObjectAddress]] to |objectaddr|. + 1. Set |object|.\[[ObjectKind]] to |objectkind|. + 1. Set |object|.\[[GetPrototypeOf]] as specified in [=[[GetPrototypeOf]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[SetPrototypeOf]] as specified in [=[[SetPrototypeOf]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[IsExtensible]] as specified in [=[[IsExtensible]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[PreventExtensions]] as specified in [=[[PreventExtensions]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[GetOwnProperty]] as specified in [=[[GetOwnProperty]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[DefineOwnProperty]] as specified in [=[[DefineOwnProperty]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[HasProperty]] as specified in [=[[HasProperty]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[Get]] as specified in [=[[Get]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[Set]] as specified in [=[[Set]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[Delete]] as specified in [=[[Delete]] internal method of an Exported GC Object=]. + 1. Set |object|.\[[OwnPropertyKeys]] as specified in [=[[OwnPropertyKeys]] internal method of an Exported GC Object=]. + 1. [=map/Set=] |map|[|objectaddr|] to |object|. + 1. Return |object|. +
+

Error Objects

WebAssembly defines the following Error classes: CompileError, LinkError, and RuntimeError. @@ -1269,16 +1434,20 @@ Note: ECMAScript doesn't specify any sort of behavior on out-of-memory condition See [Issue 879](https://github.com/WebAssembly/spec/issues/879) for further discussion. +

Implementation-defined Limits

The WebAssembly core specification allows an implementation to define limits on the syntactic structure of the module. While each embedding of WebAssembly may choose to define its own limits, for predictability the standard WebAssembly JavaScript Interface described in this document defines the following exact limits. -An implementation must reject a module that exceeds one of the following limits with a {{CompileError}}: +An implementation must reject a module that exceeds one of the following limits with a {{CompileError}}. In practice, an implementation may run out of resources for valid modules below these limits.
  • The maximum size of a module is 1,073,741,824 bytes (1 GiB).
  • The maximum number of types defined in the types section is 1,000,000.
  • +
  • The maximum number of recursion groups defined in the types sections is 1,000,000.
  • +
  • The maximum number of types defined in a recursion group is 1,000,000.
  • +
  • The maximum depth of a defined subtype hierarchy is 63 (where a type defined with no supertype has depth 0).
  • The maximum number of functions defined in a module is 1,000,000.
  • The maximum number of imports declared in a module is 100,000.
  • The maximum number of exports declared in a module is 100,000.
  • @@ -1288,12 +1457,14 @@ In practice, an implementation may run out of resources for valid modules below
  • The maximum number of tables, including declared or imported tables, is 100,000.
  • The maximum size of a table is 10,000,000.
  • The maximum number of table entries in any table initialization is 10,000,000.
  • -
  • The maximum number of memories, including declared or imported memories, is 1.
  • +
  • The maximum number of memories, including defined and imported memories, is 100.
  • The maximum number of parameters to any function or block is 1,000.
  • The maximum number of return values for any function or block is 1,000.
  • The maximum size of a function body, including locals declarations, is 7,654,321 bytes.
  • The maximum number of locals declared in a function, including implicitly declared as parameters, is 50,000.
  • +
  • The maximum number of fields in a struct is 10,000.
  • +
  • The maximum number of operands to `array.new_fixed` is 10,000.
An implementation must throw a {{RuntimeError}} if one of the following limits is exceeded during runtime: diff --git a/interpreter/README.md b/interpreter/README.md index 1df7ff715a..7d317ae6b2 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -179,11 +179,36 @@ float: .?(e|E )? | 0x.?(p|P )? name: $( | | _ | . | + | - | * | / | \ | ^ | ~ | = | < | > | ! | ? | @ | # | $ | % | & | | | : | ' | `)+ string: "( | \n | \t | \\ | \' | \" | \ | \u{+})*" +num: | +var: | + +unop: ctz | clz | popcnt | ... +binop: add | sub | mul | ... +relop: eq | ne | lt | ... +sign: s | u +offset: offset= +align: align=(1|2|4|8|...) +cvtop: trunc | extend | wrap | ... +castop: data | array | i31 +externop: internalize | externalize + num_type: i32 | i64 | f32 | f64 vec_type: v128 vec_shape: i8x16 | i16x8 | i32x4 | i64x2 | f32x4 | f64x2 | v128 -ref_kind: func | extern -ref_type: funcref | externref +heap_type: any | eq | i31 | data | array | func | extern | none | nofunc | noextern | | (rtt ) +ref_type: + ( ref null? ) + ( rtt ) ;; = (ref (rtt )) + anyref ;; = (ref null any) + eqref ;; = (ref null eq) + i31ref ;; = (ref i31) + dataref ;; = (ref null data) + arrayref ;; = (ref null array) + funcref ;; = (ref null func) + externref ;; = (ref null extern) + nullref ;; = (ref null none) + nullfuncref ;; = (ref null nofunc) + nullexternref ;; = (ref null noextern) val_type: | | block_type : ( result * )* func_type: ( type )? * * @@ -202,7 +227,6 @@ sign: s | u offset: offset= align: align=(1|2|4|8|...) cvtop: trunc | extend | wrap | ... - vecunop: abs | neg | ... vecbinop: add | sub | min_ | ... vecternop: bitselect @@ -230,14 +254,22 @@ instr: op: unreachable nop + drop + select br br_if br_table + - return + br_on_null + br_on_non_null + br_on_cast + br_on_cast_fail call - call_indirect ? - drop - select + call_ref + call_indirect ? (type )? + return + return_call + return_call_ref + return_call_indirect ? (type )? local.get local.set local.tee @@ -263,9 +295,26 @@ op: memory.copy memory.init data.drop - ref.null - ref.is_null + ref.null ref.func + ref.is_null + ref_as_non_null + ref.test + ref.cast + ref.eq + i31.new + i31.get_ + struct.new(_)? + struct.get(_)? + struct.set + array.new(_)? + array.new_fixed + array.new_elem + array.new_data + array.get(_)? + array.set + array.len + extern. .const . . @@ -385,10 +434,11 @@ const: ( .const ) ;; number value ( + ) ;; vector value ( ref.null ) ;; null reference - ( ref.extern ) ;; host reference + ( ref.host ) ;; host reference + ( ref.extern ) ;; external host reference assertion: - ( assert_return * ) ;; assert action has expected results + ( assert_return * ) ;; assert action has expected results ( assert_trap ) ;; assert action traps with given failure string ( assert_exhaustion ) ;; assert action exhausts system resources ( assert_malformed ) ;; assert module cannot be decoded with given failure string @@ -396,12 +446,14 @@ assertion: ( assert_unlinkable ) ;; assert module fails to link ( assert_trap ) ;; assert module traps on instantiation -result: - +result_pat: ( .const ) ( .const + ) - ( ref.extern ) + ( ref ) + ( ref.null ) ( ref.func ) + ( ref.extern ) + ( ref. ) num_pat: ;; literal result @@ -475,11 +527,11 @@ module: ( module ? binary * ) ;; module in binary format (may be malformed) action: - ( invoke ? * ) ;; invoke function export + ( invoke ? * ) ;; invoke function export ( get ? ) ;; get global export assertion: - ( assert_return * ) ;; assert action has expected results + ( assert_return * ) ;; assert action has expected results ( assert_trap ) ;; assert action traps with given failure string ( assert_exhaustion ) ;; assert action exhausts system resources ( assert_malformed ) ;; assert module cannot be decoded with given failure string @@ -487,10 +539,13 @@ assertion: ( assert_unlinkable ) ;; assert module fails to link ( assert_trap ) ;; assert module traps on instantiation -result: +result_pat: ( .const ) - ( ref.extern ) + ( ref ) + ( ref.null ) ( ref.func ) + ( ref.extern ) + ( ref. ) num_pat: ;; literal result diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 08ba4fbbbf..aaf13e81f8 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -62,6 +62,8 @@ let at f s = (* Generic values *) +let bit i n = n land (1 lsl i) <> 0 + let byte s = get s @@ -106,7 +108,7 @@ let s33 s = I32_convert.wrap_i64 (sN 33 s) let s64 s = sN 64 s let f32 s = F32.of_bits (word32 s) let f64 s = F64.of_bits (word64 s) -let v128 s = V128.of_bits (get_string (Types.vec_size Types.V128Type) s) +let v128 s = V128.of_bits (get_string 16 s) let len32 s = let pos = pos s in @@ -143,41 +145,130 @@ let sized f s = open Types +let mutability s = + match byte s with + | 0 -> Cons + | 1 -> Var + | _ -> error s (pos s - 1) "malformed mutability" + +let var_type var s = + let pos = pos s in + match var s with + | i when i >= 0l -> StatX i + | _ -> error s pos "malformed type index" + let num_type s = match s7 s with - | -0x01 -> I32Type - | -0x02 -> I64Type - | -0x03 -> F32Type - | -0x04 -> F64Type + | -0x01 -> I32T + | -0x02 -> I64T + | -0x03 -> F32T + | -0x04 -> F64T | _ -> error s (pos s - 1) "malformed number type" let vec_type s = match s7 s with - | -0x05 -> V128Type + | -0x05 -> V128T | _ -> error s (pos s - 1) "malformed vector type" +let heap_type s = + let pos = pos s in + either [ + (fun s -> VarHT (var_type s33 s)); + (fun s -> + match s7 s with + | -0x0d -> NoFuncHT + | -0x0e -> NoExternHT + | -0x0f -> NoneHT + | -0x10 -> FuncHT + | -0x11 -> ExternHT + | -0x12 -> AnyHT + | -0x13 -> EqHT + | -0x14 -> I31HT + | -0x15 -> StructHT + | -0x16 -> ArrayHT + | _ -> error s pos "malformed heap type" + ) + ] s + let ref_type s = + let pos = pos s in + match s7 s with + | -0x0d -> (Null, NoFuncHT) + | -0x0e -> (Null, NoExternHT) + | -0x0f -> (Null, NoneHT) + | -0x10 -> (Null, FuncHT) + | -0x11 -> (Null, ExternHT) + | -0x12 -> (Null, AnyHT) + | -0x13 -> (Null, EqHT) + | -0x14 -> (Null, I31HT) + | -0x15 -> (Null, StructHT) + | -0x16 -> (Null, ArrayHT) + | -0x1c -> (NoNull, heap_type s) + | -0x1d -> (Null, heap_type s) + | _ -> error s pos "malformed reference type" + +let val_type s = + either [ + (fun s -> NumT (num_type s)); + (fun s -> VecT (vec_type s)); + (fun s -> RefT (ref_type s)); + ] s + +let result_type s = vec val_type s + +let pack_type s = + let pos = pos s in match s7 s with - | -0x10 -> FuncRefType - | -0x11 -> ExternRefType - | _ -> error s (pos s - 1) "malformed reference type" + | -0x08 -> Pack.Pack8 + | -0x09 -> Pack.Pack16 + | _ -> error s pos "malformed storage type" -let value_type s = +let storage_type s = either [ - (fun s -> NumType (num_type s)); - (fun s -> VecType (vec_type s)); - (fun s -> RefType (ref_type s)); + (fun s -> ValStorageT (val_type s)); + (fun s -> PackStorageT (pack_type s)); ] s -let result_type s = vec value_type s +let field_type s = + let t = storage_type s in + let mut = mutability s in + FieldT (mut, t) + +let struct_type s = + StructT (vec field_type s) + +let array_type s = + ArrayT (field_type s) let func_type s = + let ts1 = result_type s in + let ts2 = result_type s in + FuncT (ts1, ts2) + +let str_type s = match s7 s with - | -0x20 -> - let ts1 = result_type s in - let ts2 = result_type s in - FuncType (ts1, ts2) - | _ -> error s (pos s - 1) "malformed function type" + | -0x20 -> DefFuncT (func_type s) + | -0x21 -> DefStructT (struct_type s) + | -0x22 -> DefArrayT (array_type s) + | _ -> error s (pos s - 1) "malformed definition type" + +let sub_type s = + match peek s with + | Some i when i = -0x30 land 0x7f -> + skip 1 s; + let xs = vec (var_type u32) s in + SubT (NoFinal, List.map (fun x -> VarHT x) xs, str_type s) + | Some i when i = -0x31 land 0x7f -> + skip 1 s; + let xs = vec (var_type u32) s in + SubT (Final, List.map (fun x -> VarHT x) xs, str_type s) + | _ -> SubT (Final, [], str_type s) + +let rec_type s = + match peek s with + | Some i when i = -0x32 land 0x7f -> skip 1 s; RecT (vec sub_type s) + | _ -> RecT [sub_type s] + let limits uN s = let flags = byte s in @@ -191,22 +282,16 @@ let limits uN s = let table_type s = let t = ref_type s in let lim, is64 = limits u64 s in - TableType (lim, (if is64 then I64IndexType else I32IndexType), t) + TableT (lim, (if is64 then I64IndexType else I32IndexType), t) let memory_type s = let lim, is64 = limits u64 s in - MemoryType (lim, if is64 then I64IndexType else I32IndexType) - -let mutability s = - match byte s with - | 0 -> Immutable - | 1 -> Mutable - | _ -> error s (pos s - 1) "malformed mutability" + MemoryT (lim, if is64 then I64IndexType else I32IndexType) let global_type s = - let t = value_type s in + let t = val_type s in let mut = mutability s in - GlobalType (t, mut) + GlobalT (mut, t) (* Instructions *) @@ -221,19 +306,37 @@ let end_ s = expect 0x0b s "END opcode expected" let zero s = expect 0x00 s "zero byte expected" let memop s = - let align = u32 s in - require (I32.lt_u align 32l) s (pos s - 1) "malformed memop flags"; + let pos = pos s in + let flags = u32 s in + require (I32.lt_u flags 0x80l) s pos "malformed memop flags"; + let has_var = Int32.logand flags 0x40l <> 0l in + let x = if has_var then at var s else Source.(0l @@ no_region) in + let align = Int32.(to_int (logand flags 0x3fl)) in + require (align < 32) s pos "malformed memop alignment"; let offset = u64 s in - Int32.to_int align, offset + x, align, offset let block_type s = - let p = pos s in either [ - (fun s -> let x = at s33 s in require (x.it >= 0l) s p ""; VarBlockType x); + (fun s -> VarBlockType (at (fun s -> as_stat_var (var_type s33 s)) s)); (fun s -> expect 0x40 s ""; ValBlockType None); - (fun s -> ValBlockType (Some (value_type s))); + (fun s -> ValBlockType (Some (val_type s))); ] s +let local s = + let n = u32 s in + let t = at val_type s in + n, {ltype = t.it} @@ t.at + +let locals s = + let pos = pos s in + let nts = vec local s in + let ns = List.map (fun (n, _) -> I64_convert.extend_i32_u n) nts in + require (I64.lt_u (List.fold_left I64.add 0L ns) 0x1_0000_0000L) + s pos "too many locals"; + List.flatten (List.map (Lib.Fun.uncurry Lib.List32.make) nts) + + let rec instr s = let pos = pos s in match op s with @@ -280,12 +383,20 @@ let rec instr s = let y = at var s in let x = at var s in call_indirect x y + | 0x12 -> return_call (at var s) + | 0x13 -> + let y = at var s in + let x = at var s in + return_call_indirect x y + + | 0x14 -> call_ref (at var s) + | 0x15 -> return_call_ref (at var s) - | 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b + | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b | 0x1a -> drop | 0x1b -> select None - | 0x1c -> select (Some (vec value_type s)) + | 0x1c -> select (Some (vec val_type s)) | 0x1d | 0x1e | 0x1f as b -> illegal s pos b @@ -299,33 +410,33 @@ let rec instr s = | 0x27 as b -> illegal s pos b - | 0x28 -> let a, o = memop s in i32_load a o - | 0x29 -> let a, o = memop s in i64_load a o - | 0x2a -> let a, o = memop s in f32_load a o - | 0x2b -> let a, o = memop s in f64_load a o - | 0x2c -> let a, o = memop s in i32_load8_s a o - | 0x2d -> let a, o = memop s in i32_load8_u a o - | 0x2e -> let a, o = memop s in i32_load16_s a o - | 0x2f -> let a, o = memop s in i32_load16_u a o - | 0x30 -> let a, o = memop s in i64_load8_s a o - | 0x31 -> let a, o = memop s in i64_load8_u a o - | 0x32 -> let a, o = memop s in i64_load16_s a o - | 0x33 -> let a, o = memop s in i64_load16_u a o - | 0x34 -> let a, o = memop s in i64_load32_s a o - | 0x35 -> let a, o = memop s in i64_load32_u a o - - | 0x36 -> let a, o = memop s in i32_store a o - | 0x37 -> let a, o = memop s in i64_store a o - | 0x38 -> let a, o = memop s in f32_store a o - | 0x39 -> let a, o = memop s in f64_store a o - | 0x3a -> let a, o = memop s in i32_store8 a o - | 0x3b -> let a, o = memop s in i32_store16 a o - | 0x3c -> let a, o = memop s in i64_store8 a o - | 0x3d -> let a, o = memop s in i64_store16 a o - | 0x3e -> let a, o = memop s in i64_store32 a o - - | 0x3f -> zero s; memory_size - | 0x40 -> zero s; memory_grow + | 0x28 -> let x, a, o = memop s in i32_load x a o + | 0x29 -> let x, a, o = memop s in i64_load x a o + | 0x2a -> let x, a, o = memop s in f32_load x a o + | 0x2b -> let x, a, o = memop s in f64_load x a o + | 0x2c -> let x, a, o = memop s in i32_load8_s x a o + | 0x2d -> let x, a, o = memop s in i32_load8_u x a o + | 0x2e -> let x, a, o = memop s in i32_load16_s x a o + | 0x2f -> let x, a, o = memop s in i32_load16_u x a o + | 0x30 -> let x, a, o = memop s in i64_load8_s x a o + | 0x31 -> let x, a, o = memop s in i64_load8_u x a o + | 0x32 -> let x, a, o = memop s in i64_load16_s x a o + | 0x33 -> let x, a, o = memop s in i64_load16_u x a o + | 0x34 -> let x, a, o = memop s in i64_load32_s x a o + | 0x35 -> let x, a, o = memop s in i64_load32_u x a o + + | 0x36 -> let x, a, o = memop s in i32_store x a o + | 0x37 -> let x, a, o = memop s in i64_store x a o + | 0x38 -> let x, a, o = memop s in f32_store x a o + | 0x39 -> let x, a, o = memop s in f64_store x a o + | 0x3a -> let x, a, o = memop s in i32_store8 x a o + | 0x3b -> let x, a, o = memop s in i32_store16 x a o + | 0x3c -> let x, a, o = memop s in i64_store8 x a o + | 0x3d -> let x, a, o = memop s in i64_store16 x a o + | 0x3e -> let x, a, o = memop s in i64_store32 x a o + + | 0x3f -> memory_size (at var s) + | 0x40 -> memory_grow (at var s) | 0x41 -> i32_const (at s32 s) | 0x42 -> i64_const (at s64 s) @@ -474,9 +585,59 @@ let rec instr s = | 0xc5 | 0xc6 | 0xc7 | 0xc8 | 0xc9 | 0xca | 0xcb | 0xcc | 0xcd | 0xce | 0xcf as b -> illegal s pos b - | 0xd0 -> ref_null (ref_type s) + | 0xd0 -> ref_null (heap_type s) | 0xd1 -> ref_is_null | 0xd2 -> ref_func (at var s) + | 0xd3 -> ref_eq + | 0xd4 -> ref_as_non_null + | 0xd5 -> br_on_null (at var s) + | 0xd6 -> br_on_non_null (at var s) + + | 0xfb as b -> + (match u32 s with + | 0x00l -> struct_new (at var s) + | 0x01l -> struct_new_default (at var s) + | 0x02l -> let x = at var s in let y = at var s in struct_get x y + | 0x03l -> let x = at var s in let y = at var s in struct_get_s x y + | 0x04l -> let x = at var s in let y = at var s in struct_get_u x y + | 0x05l -> let x = at var s in let y = at var s in struct_set x y + + | 0x06l -> array_new (at var s) + | 0x07l -> array_new_default (at var s) + | 0x08l -> let x = at var s in let n = u32 s in array_new_fixed x n + | 0x09l -> let x = at var s in let y = at var s in array_new_data x y + | 0x0al -> let x = at var s in let y = at var s in array_new_elem x y + | 0x0bl -> array_get (at var s) + | 0x0cl -> array_get_s (at var s) + | 0x0dl -> array_get_u (at var s) + | 0x0el -> array_set (at var s) + | 0x0fl -> array_len + | 0x10l -> array_fill (at var s) + | 0x11l -> let x = at var s in let y = at var s in array_copy x y + | 0x12l -> let x = at var s in let y = at var s in array_init_data x y + | 0x13l -> let x = at var s in let y = at var s in array_init_elem x y + + | 0x14l -> ref_test (NoNull, heap_type s) + | 0x15l -> ref_test (Null, heap_type s) + | 0x16l -> ref_cast (NoNull, heap_type s) + | 0x17l -> ref_cast (Null, heap_type s) + | 0x18l | 0x19l as opcode -> + let flags = byte s in + require (flags land 0xfc = 0) s (pos + 2) "malformed br_on_cast flags"; + let x = at var s in + let rt1 = ((if bit 0 flags then Null else NoNull), heap_type s) in + let rt2 = ((if bit 1 flags then Null else NoNull), heap_type s) in + (if opcode = 0x18l then br_on_cast else br_on_cast_fail) x rt1 rt2 + + | 0x1al -> any_convert_extern + | 0x1bl -> extern_convert_any + + | 0x1cl -> ref_i31 + | 0x1dl -> i31_get_s + | 0x1el -> i31_get_u + + | n -> illegal2 s pos b n + ) | 0xfc as b -> (match u32 s with @@ -490,11 +651,15 @@ let rec instr s = | 0x07l -> i64_trunc_sat_f64_u | 0x08l -> + let y = at var s in let x = at var s in - zero s; memory_init x + memory_init x y | 0x09l -> data_drop (at var s) - | 0x0al -> zero s; zero s; memory_copy - | 0x0bl -> zero s; memory_fill + | 0x0al -> + let x = at var s in + let y = at var s in + memory_copy x y + | 0x0bl -> memory_fill (at var s) | 0x0cl -> let y = at var s in @@ -514,18 +679,18 @@ let rec instr s = | 0xfd -> (match u32 s with - | 0x00l -> let a, o = memop s in v128_load a o - | 0x01l -> let a, o = memop s in v128_load8x8_s a o - | 0x02l -> let a, o = memop s in v128_load8x8_u a o - | 0x03l -> let a, o = memop s in v128_load16x4_s a o - | 0x04l -> let a, o = memop s in v128_load16x4_u a o - | 0x05l -> let a, o = memop s in v128_load32x2_s a o - | 0x06l -> let a, o = memop s in v128_load32x2_u a o - | 0x07l -> let a, o = memop s in v128_load8_splat a o - | 0x08l -> let a, o = memop s in v128_load16_splat a o - | 0x09l -> let a, o = memop s in v128_load32_splat a o - | 0x0al -> let a, o = memop s in v128_load64_splat a o - | 0x0bl -> let a, o = memop s in v128_store a o + | 0x00l -> let x, a, o = memop s in v128_load x a o + | 0x01l -> let x, a, o = memop s in v128_load8x8_s x a o + | 0x02l -> let x, a, o = memop s in v128_load8x8_u x a o + | 0x03l -> let x, a, o = memop s in v128_load16x4_s x a o + | 0x04l -> let x, a, o = memop s in v128_load16x4_u x a o + | 0x05l -> let x, a, o = memop s in v128_load32x2_s x a o + | 0x06l -> let x, a, o = memop s in v128_load32x2_u x a o + | 0x07l -> let x, a, o = memop s in v128_load8_splat x a o + | 0x08l -> let x, a, o = memop s in v128_load16_splat x a o + | 0x09l -> let x, a, o = memop s in v128_load32_splat x a o + | 0x0al -> let x, a, o = memop s in v128_load64_splat x a o + | 0x0bl -> let x, a, o = memop s in v128_store x a o | 0x0cl -> v128_const (at v128 s) | 0x0dl -> i8x16_shuffle (List.init 16 (fun _ -> byte s)) | 0x0el -> i8x16_swizzle @@ -599,39 +764,39 @@ let rec instr s = | 0x52l -> v128_bitselect | 0x53l -> v128_any_true | 0x54l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_load8_lane a o lane + v128_load8_lane x a o lane | 0x55l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_load16_lane a o lane + v128_load16_lane x a o lane | 0x56l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_load32_lane a o lane + v128_load32_lane x a o lane | 0x57l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_load64_lane a o lane + v128_load64_lane x a o lane | 0x58l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_store8_lane a o lane + v128_store8_lane x a o lane | 0x59l -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_store16_lane a o lane + v128_store16_lane x a o lane | 0x5al -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_store32_lane a o lane + v128_store32_lane x a o lane | 0x5bl -> - let a, o = memop s in + let x, a, o = memop s in let lane = byte s in - v128_store64_lane a o lane - | 0x5cl -> let a, o = memop s in v128_load32_zero a o - | 0x5dl -> let a, o = memop s in v128_load64_zero a o + v128_store64_lane x a o lane + | 0x5cl -> let x, a, o = memop s in v128_load32_zero x a o + | 0x5dl -> let x, a, o = memop s in v128_load64_zero x a o | 0x5el -> f32x4_demote_f64x2_zero | 0x5fl -> f64x2_promote_low_f32x4 | 0x60l -> i8x16_abs @@ -692,6 +857,7 @@ let rec instr s = | 0x97l -> i16x8_min_u | 0x98l -> i16x8_max_s | 0x99l -> i16x8_max_u + | 0x9al as n -> illegal s pos (I32.to_int_u n) | 0x9bl -> i16x8_avgr_u | 0x9cl -> i16x8_extmul_low_i8x16_s | 0x9dl -> i16x8_extmul_high_i8x16_s @@ -699,8 +865,10 @@ let rec instr s = | 0x9fl -> i16x8_extmul_high_i8x16_u | 0xa0l -> i32x4_abs | 0xa1l -> i32x4_neg + | 0xa2l as n -> illegal s pos (I32.to_int_u n) | 0xa3l -> i32x4_all_true | 0xa4l -> i32x4_bitmask + | 0xa5l | 0xa6l as n -> illegal s pos (I32.to_int_u n) | 0xa7l -> i32x4_extend_low_i16x8_s | 0xa8l -> i32x4_extend_high_i16x8_s | 0xa9l -> i32x4_extend_low_i16x8_u @@ -709,7 +877,9 @@ let rec instr s = | 0xacl -> i32x4_shr_s | 0xadl -> i32x4_shr_u | 0xael -> i32x4_add + | 0xafl | 0xb0l as n -> illegal s pos (I32.to_int_u n) | 0xb1l -> i32x4_sub + | 0xb2l | 0xb3l | 0xb4l as n -> illegal s pos (I32.to_int_u n) | 0xb5l -> i32x4_mul | 0xb6l -> i32x4_min_s | 0xb7l -> i32x4_min_u @@ -722,8 +892,10 @@ let rec instr s = | 0xbfl -> i32x4_extmul_high_i16x8_u | 0xc0l -> i64x2_abs | 0xc1l -> i64x2_neg + | 0xc2l as n -> illegal s pos (I32.to_int_u n) | 0xc3l -> i64x2_all_true | 0xc4l -> i64x2_bitmask + | 0xc5l | 0xc6l as n -> illegal s pos (I32.to_int_u n) | 0xc7l -> i64x2_extend_low_i32x4_s | 0xc8l -> i64x2_extend_high_i32x4_s | 0xc9l -> i64x2_extend_low_i32x4_u @@ -732,7 +904,9 @@ let rec instr s = | 0xccl -> i64x2_shr_s | 0xcdl -> i64x2_shr_u | 0xcel -> i64x2_add + | 0xcfl | 0xd0l as n -> illegal s pos (I32.to_int_u n) | 0xd1l -> i64x2_sub + | 0xd2l | 0xd3l | 0xd4l as n -> illegal s pos (I32.to_int_u n) | 0xd5l -> i64x2_mul | 0xd6l -> i64x2_eq | 0xd7l -> i64x2_ne @@ -746,6 +920,7 @@ let rec instr s = | 0xdfl -> i64x2_extmul_high_i32x4_u | 0xe0l -> f32x4_abs | 0xe1l -> f32x4_neg + | 0xe2l as n -> illegal s pos (I32.to_int_u n) | 0xe3l -> f32x4_sqrt | 0xe4l -> f32x4_add | 0xe5l -> f32x4_sub @@ -827,7 +1002,7 @@ let section tag f default s = (* Type section *) -let type_ s = at func_type s +let type_ s = at rec_type s let type_section s = section `TypeSection (vec type_) [] s @@ -862,8 +1037,20 @@ let func_section s = (* Table section *) let table s = - let ttype = table_type s in - {ttype} + either [ + (fun s -> + expect 0x40 s ""; + zero s; + let ttype = table_type s in + let tinit = const s in + {ttype; tinit} + ); + (fun s -> + let at = region s (pos s) (pos s) in + let TableT (_, _it, (_, ht)) as ttype = table_type s in + {ttype; tinit = [RefNull ht @@ at] @@ at} + ); + ] s let table_section s = section `TableSection (vec (at table)) [] s @@ -921,19 +1108,6 @@ let start_section s = (* Code section *) -let local s = - let n = u32 s in - let t = value_type s in - n, t - -let locals s = - let pos = pos s in - let nts = vec local s in - let ns = List.map (fun (n, _) -> I64_convert.extend_i32_u n) nts in - require (I64.lt_u (List.fold_left I64.add 0L ns) 0x1_0000_0000L) - s pos "too many locals"; - List.flatten (List.map (Lib.Fun.uncurry Lib.List32.make) nts) - let code _ s = let locals = locals s in let body = instr_block s in @@ -968,7 +1142,7 @@ let elem_index s = let elem_kind s = match byte s with - | 0x00 -> FuncRefType + | 0x00 -> (NoNull, FuncHT) | _ -> error s (pos s - 1) "malformed element kind" let elem s = @@ -976,7 +1150,7 @@ let elem s = | 0x00l -> let emode = at active_zero s in let einit = vec (at elem_index) s in - {etype = FuncRefType; einit; emode} + {etype = (NoNull, FuncHT); einit; emode} | 0x01l -> let emode = at passive s in let etype = elem_kind s in @@ -995,7 +1169,7 @@ let elem s = | 0x04l -> let emode = at active_zero s in let einit = vec const s in - {etype = FuncRefType; einit; emode} + {etype = (Null, FuncHT); einit; emode} | 0x05l -> let emode = at passive s in let etype = ref_type s in diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index 743b0c5d13..abb9ca871e 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -40,6 +40,8 @@ struct (* Generic values *) + let bit i b = (if b then 1 else 0) lsl i + let byte i = put s (Char.chr (i land 0xff)) let word16 i = byte (i land 0xff); byte (i lsr 8) let word32 i = @@ -95,66 +97,145 @@ struct open Types + let mutability = function + | Cons -> byte 0 + | Var -> byte 1 + + let var_type var = function + | StatX x -> var x + | RecX _ -> assert false + let num_type = function - | I32Type -> s7 (-0x01) - | I64Type -> s7 (-0x02) - | F32Type -> s7 (-0x03) - | F64Type -> s7 (-0x04) + | I32T -> s7 (-0x01) + | I64T -> s7 (-0x02) + | F32T -> s7 (-0x03) + | F64T -> s7 (-0x04) let vec_type = function - | V128Type -> s7 (-0x05) + | V128T -> s7 (-0x05) + + let heap_type = function + | AnyHT -> s7 (-0x12) + | EqHT -> s7 (-0x13) + | I31HT -> s7 (-0x14) + | StructHT -> s7 (-0x15) + | ArrayHT -> s7 (-0x16) + | NoneHT -> s7 (-0x0f) + | FuncHT -> s7 (-0x10) + | NoFuncHT -> s7 (-0x0d) + | ExternHT -> s7 (-0x11) + | NoExternHT -> s7 (-0x0e) + | VarHT x -> var_type s33 x + | DefHT _ | BotHT -> assert false + + let var_heap_type = function + | VarHT x -> var_type u32 x + | _ -> assert false let ref_type = function - | FuncRefType -> s7 (-0x10) - | ExternRefType -> s7 (-0x11) - - let value_type = function - | NumType t -> num_type t - | VecType t -> vec_type t - | RefType t -> ref_type t + | (Null, AnyHT) -> s7 (-0x12) + | (Null, EqHT) -> s7 (-0x13) + | (Null, I31HT) -> s7 (-0x14) + | (Null, StructHT) -> s7 (-0x15) + | (Null, ArrayHT) -> s7 (-0x16) + | (Null, NoneHT) -> s7 (-0x0f) + | (Null, FuncHT) -> s7 (-0x10) + | (Null, NoFuncHT) -> s7 (-0x0d) + | (Null, ExternHT) -> s7 (-0x11) + | (Null, NoExternHT) -> s7 (-0x0e) + | (Null, t) -> s7 (-0x1d); heap_type t + | (NoNull, t) -> s7 (-0x1c); heap_type t + + let val_type = function + | NumT t -> num_type t + | VecT t -> vec_type t + | RefT t -> ref_type t + | BotT -> assert false + + let pack_type = function + | Pack.Pack8 -> s7 (-0x08) + | Pack.Pack16 -> s7 (-0x09) + | Pack.Pack32 | Pack.Pack64 -> assert false + + let storage_type = function + | ValStorageT t -> val_type t + | PackStorageT t -> pack_type t + + let field_type = function + | FieldT (mut, t) -> storage_type t; mutability mut + + let struct_type = function + | StructT fts -> vec field_type fts + + let array_type = function + | ArrayT ft -> field_type ft let func_type = function - | FuncType (ts1, ts2) -> - s7 (-0x20); vec value_type ts1; vec value_type ts2 + | FuncT (ts1, ts2) -> vec val_type ts1; vec val_type ts2 + + let str_type = function + | DefStructT st -> s7 (-0x21); struct_type st + | DefArrayT at -> s7 (-0x22); array_type at + | DefFuncT ft -> s7 (-0x20); func_type ft + let sub_type = function + | SubT (Final, [], st) -> str_type st + | SubT (Final, hts, st) -> s7 (-0x31); vec var_heap_type hts; str_type st + | SubT (NoFinal, hts, st) -> s7 (-0x30); vec var_heap_type hts; str_type st + + let rec_type = function + | RecT [st] -> sub_type st + | RecT sts -> s7 (-0x32); vec sub_type sts let limits vu {min; max} it = let flags = flag (max <> None) 0 + flag (it = I64IndexType) 2 in byte flags; vu min; opt vu max let table_type = function - | TableType (lim, it, t) -> ref_type t; limits u64 lim it + | TableT (lim, it, t) -> ref_type t; limits u64 lim it let memory_type = function - | MemoryType (lim, it) -> limits u64 lim it - - let mutability = function - | Immutable -> byte 0 - | Mutable -> byte 1 + | MemoryT (lim, it) -> limits u64 lim it let global_type = function - | GlobalType (t, mut) -> value_type t; mutability mut + | GlobalT (mut, t) -> val_type t; mutability mut (* Instructions *) open Source open Ast - open Values + open Value open V128 + open Pack let op n = byte n let vecop n = op 0xfd; u32 n let end_ () = op 0x0b - let memop {align; offset; _} = u32 (Int32.of_int align); u64 offset - let var x = u32 x.it + let memop x {align; offset; _} = + let has_var = x.it <> 0l in + let flags = + Int32.(logor (of_int align) (if has_var then 0x40l else 0x00l)) in + u32 flags; + if has_var then var x; + u64 offset + let block_type = function + | VarBlockType x -> var_type s33 (StatX x.it) | ValBlockType None -> s33 (-0x40l) - | ValBlockType (Some t) -> value_type t - | VarBlockType x -> s33 x.it + | ValBlockType (Some t) -> val_type t + + let local (n, loc) = len n; val_type loc.it.ltype + + let locals locs = + let combine loc = function + | (n, loc') :: nlocs' when loc.it.ltype = loc'.it.ltype -> + (n + 1, loc') :: nlocs' + | nlocs -> (1, loc) :: nlocs + in vec local (List.fold_right combine locs []) let rec instr e = match e.it with @@ -171,13 +252,25 @@ struct | Br x -> op 0x0c; var x | BrIf x -> op 0x0d; var x | BrTable (xs, x) -> op 0x0e; vec var xs; var x + | BrOnNull x -> op 0xd5; var x + | BrOnNonNull x -> op 0xd6; var x + | BrOnCast (x, (nul1, t1), (nul2, t2)) -> + let flags = bit 0 (nul1 = Null) + bit 1 (nul2 = Null) in + op 0xfb; op 0x18; byte flags; var x; heap_type t1; heap_type t2 + | BrOnCastFail (x, (nul1, t1), (nul2, t2)) -> + let flags = bit 0 (nul1 = Null) + bit 1 (nul2 = Null) in + op 0xfb; op 0x19; byte flags; var x; heap_type t1; heap_type t2 | Return -> op 0x0f | Call x -> op 0x10; var x + | CallRef x -> op 0x14; var x | CallIndirect (x, y) -> op 0x11; var y; var x + | ReturnCall x -> op 0x12; var x + | ReturnCallRef x -> op 0x15; var x + | ReturnCallIndirect (x, y) -> op 0x13; var y; var x | Drop -> op 0x1a | Select None -> op 0x1b - | Select (Some ts) -> op 0x1c; vec value_type ts + | Select (Some ts) -> op 0x1c; vec val_type ts | LocalGet x -> op 0x20; var x | LocalSet x -> op 0x21; var x @@ -194,113 +287,164 @@ struct | TableInit (x, y) -> op 0xfc; u32 0x0cl; var y; var x | ElemDrop x -> op 0xfc; u32 0x0dl; var x - | Load ({ty = I32Type; pack = None; _} as mo) -> op 0x28; memop mo - | Load ({ty = I64Type; pack = None; _} as mo) -> op 0x29; memop mo - | Load ({ty = F32Type; pack = None; _} as mo) -> op 0x2a; memop mo - | Load ({ty = F64Type; pack = None; _} as mo) -> op 0x2b; memop mo - | Load ({ty = I32Type; pack = Some (Pack8, SX); _} as mo) -> - op 0x2c; memop mo - | Load ({ty = I32Type; pack = Some (Pack8, ZX); _} as mo) -> - op 0x2d; memop mo - | Load ({ty = I32Type; pack = Some (Pack16, SX); _} as mo) -> - op 0x2e; memop mo - | Load ({ty = I32Type; pack = Some (Pack16, ZX); _} as mo) -> - op 0x2f; memop mo - | Load {ty = I32Type; pack = Some (Pack32, _); _} -> + | Load (x, ({ty = I32T; pack = None; _} as mo)) -> + op 0x28; memop x mo + | Load (x, ({ty = I64T; pack = None; _} as mo)) -> + op 0x29; memop x mo + | Load (x, ({ty = F32T; pack = None; _} as mo)) -> + op 0x2a; memop x mo + | Load (x, ({ty = F64T; pack = None; _} as mo)) -> + op 0x2b; memop x mo + | Load (x, ({ty = I32T; pack = Some (Pack8, SX); _} as mo)) -> + op 0x2c; memop x mo + | Load (x, ({ty = I32T; pack = Some (Pack8, ZX); _} as mo)) -> + op 0x2d; memop x mo + | Load (x, ({ty = I32T; pack = Some (Pack16, SX); _} as mo)) -> + op 0x2e; memop x mo + | Load (x, ({ty = I32T; pack = Some (Pack16, ZX); _} as mo)) -> + op 0x2f; memop x mo + | Load (x, ({ty = I32T; pack = Some (Pack32, _); _})) -> error e.at "illegal instruction i32.load32" - | Load ({ty = I64Type; pack = Some (Pack8, SX); _} as mo) -> - op 0x30; memop mo - | Load ({ty = I64Type; pack = Some (Pack8, ZX); _} as mo) -> - op 0x31; memop mo - | Load ({ty = I64Type; pack = Some (Pack16, SX); _} as mo) -> - op 0x32; memop mo - | Load ({ty = I64Type; pack = Some (Pack16, ZX); _} as mo) -> - op 0x33; memop mo - | Load ({ty = I64Type; pack = Some (Pack32, SX); _} as mo) -> - op 0x34; memop mo - | Load ({ty = I64Type; pack = Some (Pack32, ZX); _} as mo) -> - op 0x35; memop mo - | Load {ty = F32Type | F64Type; pack = Some _; _} -> - error e.at "illegal instruction fxx.loadN" - | Load {ty = I32Type | I64Type; pack = Some (Pack64, _); _} -> + | Load (x, ({ty = I64T; pack = Some (Pack8, SX); _} as mo)) -> + op 0x30; memop x mo + | Load (x, ({ty = I64T; pack = Some (Pack8, ZX); _} as mo)) -> + op 0x31; memop x mo + | Load (x, ({ty = I64T; pack = Some (Pack16, SX); _} as mo)) -> + op 0x32; memop x mo + | Load (x, ({ty = I64T; pack = Some (Pack16, ZX); _} as mo)) -> + op 0x33; memop x mo + | Load (x, ({ty = I64T; pack = Some (Pack32, SX); _} as mo)) -> + op 0x34; memop x mo + | Load (x, ({ty = I64T; pack = Some (Pack32, ZX); _} as mo)) -> + op 0x35; memop x mo + | Load (x, ({ty = I32T | I64T; pack = Some (Pack64, _); _})) -> error e.at "illegal instruction ixx.load64" + | Load (x, ({ty = F32T | F64T; pack = Some _; _})) -> + error e.at "illegal instruction fxx.loadN" - | Store ({ty = I32Type; pack = None; _} as mo) -> op 0x36; memop mo - | Store ({ty = I64Type; pack = None; _} as mo) -> op 0x37; memop mo - | Store ({ty = F32Type; pack = None; _} as mo) -> op 0x38; memop mo - | Store ({ty = F64Type; pack = None; _} as mo) -> op 0x39; memop mo - | Store ({ty = I32Type; pack = Some Pack8; _} as mo) -> op 0x3a; memop mo - | Store ({ty = I32Type; pack = Some Pack16; _} as mo) -> op 0x3b; memop mo - | Store {ty = I32Type; pack = Some Pack32; _} -> + | Store (x, ({ty = I32T; pack = None; _} as mo)) -> + op 0x36; memop x mo + | Store (x, ({ty = I64T; pack = None; _} as mo)) -> + op 0x37; memop x mo + | Store (x, ({ty = F32T; pack = None; _} as mo)) -> + op 0x38; memop x mo + | Store (x, ({ty = F64T; pack = None; _} as mo)) -> + op 0x39; memop x mo + | Store (x, ({ty = I32T; pack = Some Pack8; _} as mo)) -> + op 0x3a; memop x mo + | Store (x, ({ty = I32T; pack = Some Pack16; _} as mo)) -> + op 0x3b; memop x mo + | Store (x, {ty = I32T; pack = Some Pack32; _}) -> error e.at "illegal instruction i32.store32" - | Store ({ty = I64Type; pack = Some Pack8; _} as mo) -> op 0x3c; memop mo - | Store ({ty = I64Type; pack = Some Pack16; _} as mo) -> op 0x3d; memop mo - | Store ({ty = I64Type; pack = Some Pack32; _} as mo) -> op 0x3e; memop mo - | Store {ty = F32Type | F64Type; pack = Some _; _} -> - error e.at "illegal instruction fxx.storeN" - | Store {ty = (I32Type | I64Type); pack = Some Pack64; _} -> + | Store (x, ({ty = I64T; pack = Some Pack8; _} as mo)) -> + op 0x3c; memop x mo + | Store (x, ({ty = I64T; pack = Some Pack16; _} as mo)) -> + op 0x3d; memop x mo + | Store (x, ({ty = I64T; pack = Some Pack32; _} as mo)) -> + op 0x3e; memop x mo + | Store (x, ({ty = I32T | I64T; pack = Some Pack64; _})) -> error e.at "illegal instruction ixx.store64" + | Store (x, ({ty = F32T | F64T; pack = Some _; _})) -> + error e.at "illegal instruction fxx.storeN" - | VecLoad ({ty = V128Type; pack = None; _} as mo) -> - vecop 0x00l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack8x8, SX)); _} as mo) -> - vecop 0x01l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack8x8, ZX)); _} as mo) -> - vecop 0x02l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack16x4, SX)); _} as mo) -> - vecop 0x03l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack16x4, ZX)); _} as mo) -> - vecop 0x04l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack32x2, SX)); _} as mo) -> - vecop 0x05l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtLane (Pack32x2, ZX)); _} as mo) -> - vecop 0x06l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack8, ExtSplat); _} as mo) -> - vecop 0x07l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack16, ExtSplat); _} as mo) -> - vecop 0x08l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack32, ExtSplat); _} as mo) -> - vecop 0x09l; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtSplat); _} as mo) -> - vecop 0x0al; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack32, ExtZero); _} as mo) -> - vecop 0x5cl; memop mo - | VecLoad ({ty = V128Type; pack = Some (Pack64, ExtZero); _} as mo) -> - vecop 0x5dl; memop mo + | VecLoad (x, ({ty = V128T; pack = None; _} as mo)) -> + vecop 0x00l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack8x8, SX)); _} as mo)) -> + vecop 0x01l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack8x8, ZX)); _} as mo)) -> + vecop 0x02l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack16x4, SX)); _} as mo)) -> + vecop 0x03l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack16x4, ZX)); _} as mo)) -> + vecop 0x04l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack32x2, SX)); _} as mo)) -> + vecop 0x05l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtLane (Pack32x2, ZX)); _} as mo)) -> + vecop 0x06l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack8, ExtSplat); _} as mo)) -> + vecop 0x07l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack16, ExtSplat); _} as mo)) -> + vecop 0x08l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack32, ExtSplat); _} as mo)) -> + vecop 0x09l; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtSplat); _} as mo)) -> + vecop 0x0al; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack32, ExtZero); _} as mo)) -> + vecop 0x5cl; memop x mo + | VecLoad (x, ({ty = V128T; pack = Some (Pack64, ExtZero); _} as mo)) -> + vecop 0x5dl; memop x mo | VecLoad _ -> - error e.at "illegal instruction v128.loadNxM_" - - | VecLoadLane ({ty = V128Type; pack = Pack8; _} as mo, i) -> - vecop 0x54l; memop mo; byte i; - | VecLoadLane ({ty = V128Type; pack = Pack16; _} as mo, i) -> - vecop 0x55l; memop mo; byte i; - | VecLoadLane ({ty = V128Type; pack = Pack32; _} as mo, i) -> - vecop 0x56l; memop mo; byte i; - | VecLoadLane ({ty = V128Type; pack = Pack64; _} as mo, i) -> - vecop 0x57l; memop mo; byte i; - - | VecStore ({ty = V128Type; _} as mo) -> vecop 0x0bl; memop mo - - | VecStoreLane ({ty = V128Type; pack = Pack8; _} as mo, i) -> - vecop 0x58l; memop mo; byte i; - | VecStoreLane ({ty = V128Type; pack = Pack16; _} as mo, i) -> - vecop 0x59l; memop mo; byte i; - | VecStoreLane ({ty = V128Type; pack = Pack32; _} as mo, i) -> - vecop 0x5al; memop mo; byte i; - | VecStoreLane ({ty = V128Type; pack = Pack64; _} as mo, i) -> - vecop 0x5bl; memop mo; byte i; - - | MemorySize -> op 0x3f; byte 0x00 - | MemoryGrow -> op 0x40; byte 0x00 - | MemoryFill -> op 0xfc; u32 0x0bl; byte 0x00 - | MemoryCopy -> op 0xfc; u32 0x0al; byte 0x00; byte 0x00 - | MemoryInit x -> op 0xfc; u32 0x08l; var x; byte 0x00 + error e.at "illegal instruction v128.loadNxM_x" + + | VecLoadLane (x, ({ty = V128T; pack = Pack8; _} as mo), i) -> + vecop 0x54l; memop x mo; byte i; + | VecLoadLane (x, ({ty = V128T; pack = Pack16; _} as mo), i) -> + vecop 0x55l; memop x mo; byte i; + | VecLoadLane (x, ({ty = V128T; pack = Pack32; _} as mo), i) -> + vecop 0x56l; memop x mo; byte i; + | VecLoadLane (x, ({ty = V128T; pack = Pack64; _} as mo), i) -> + vecop 0x57l; memop x mo; byte i; + + | VecStore (x, ({ty = V128T; _} as mo)) -> + vecop 0x0bl; memop x mo + + | VecStoreLane (x, ({ty = V128T; pack = Pack8; _} as mo), i) -> + vecop 0x58l; memop x mo; byte i; + | VecStoreLane (x, ({ty = V128T; pack = Pack16; _} as mo), i) -> + vecop 0x59l; memop x mo; byte i; + | VecStoreLane (x, ({ty = V128T; pack = Pack32; _} as mo), i) -> + vecop 0x5al; memop x mo; byte i; + | VecStoreLane (x, ({ty = V128T; pack = Pack64; _} as mo), i) -> + vecop 0x5bl; memop x mo; byte i; + + | MemorySize x -> op 0x3f; var x + | MemoryGrow x -> op 0x40; var x + | MemoryFill x -> op 0xfc; u32 0x0bl; var x + | MemoryCopy (x, y) -> op 0xfc; u32 0x0al; var x; var y + | MemoryInit (x, y) -> op 0xfc; u32 0x08l; var y; var x | DataDrop x -> op 0xfc; u32 0x09l; var x - | RefNull t -> op 0xd0; ref_type t - | RefIsNull -> op 0xd1 + | RefNull t -> op 0xd0; heap_type t | RefFunc x -> op 0xd2; var x + | RefEq -> op 0xd3 + + | RefIsNull -> op 0xd1 + | RefAsNonNull -> op 0xd4 + | RefTest (NoNull, t) -> op 0xfb; op 0x14; heap_type t + | RefTest (Null, t) -> op 0xfb; op 0x15; heap_type t + | RefCast (NoNull, t) -> op 0xfb; op 0x16; heap_type t + | RefCast (Null, t) -> op 0xfb; op 0x17; heap_type t + + | RefI31 -> op 0xfb; op 0x1c + | I31Get SX -> op 0xfb; op 0x1d + | I31Get ZX -> op 0xfb; op 0x1e + + | StructNew (x, Explicit) -> op 0xfb; op 0x00; var x + | StructNew (x, Implicit) -> op 0xfb; op 0x01; var x + | StructGet (x, y, None) -> op 0xfb; op 0x02; var x; var y + | StructGet (x, y, Some SX) -> op 0xfb; op 0x03; var x; var y + | StructGet (x, y, Some ZX) -> op 0xfb; op 0x04; var x; var y + | StructSet (x, y) -> op 0xfb; op 0x05; var x; var y + + | ArrayNew (x, Explicit) -> op 0xfb; op 0x06; var x + | ArrayNew (x, Implicit) -> op 0xfb; op 0x07; var x + | ArrayNewFixed (x, n) -> op 0xfb; op 0x08; var x; u32 n + | ArrayNewElem (x, y) -> op 0xfb; op 0x0a; var x; var y + | ArrayNewData (x, y) -> op 0xfb; op 0x09; var x; var y + | ArrayGet (x, None) -> op 0xfb; op 0x0b; var x + | ArrayGet (x, Some SX) -> op 0xfb; op 0x0c; var x + | ArrayGet (x, Some ZX) -> op 0xfb; op 0x0d; var x + | ArraySet x -> op 0xfb; op 0x0e; var x + | ArrayLen -> op 0xfb; op 0x0f + | ArrayFill x -> op 0xfb; op 0x10; var x + | ArrayCopy (x, y) -> op 0xfb; op 0x11; var x; var y + | ArrayInitData (x, y) -> op 0xfb; op 0x12; var x; var y + | ArrayInitElem (x, y) -> op 0xfb; op 0x13; var x; var y + + | ExternConvert Internalize -> op 0xfb; op 0x1a + | ExternConvert Externalize -> op 0xfb; op 0x1b + | Const {it = I32 c; _} -> op 0x41; s32 c | Const {it = I64 c; _} -> op 0x42; s64 c | Const {it = F32 c; _} -> op 0x43; f32 c @@ -747,7 +891,7 @@ struct (* Type section *) - let type_ t = func_type t.it + let type_ t = rec_type t.it let type_section ts = section 1 (vec type_) ts (ts <> []) @@ -781,8 +925,11 @@ struct (* Table section *) let table tab = - let {ttype} = tab.it in - table_type ttype + let {ttype; tinit} = tab.it in + match ttype, tinit.it with + | TableT (_, _it, (_, ht1)), [{it = RefNull ht2; _}] when ht1 = ht2 -> + table_type ttype + | _ -> op 0x40; op 0x00; table_type ttype; const tinit let table_section tabs = section 4 (vec table) tabs (tabs <> []) @@ -837,14 +984,6 @@ struct (* Code section *) - let local (t, n) = len n; value_type t - - let locals locs = - let combine t = function - | (t', n) :: ts when t = t' -> (t, n + 1) :: ts - | ts -> (t, 1) :: ts - in vec local (List.fold_right combine locs []) - let code f = let {locals = locs; body; _} = f.it in let g = gap32 () in @@ -861,11 +1000,11 @@ struct (* Element section *) let is_elem_kind = function - | FuncRefType -> true + | (NoNull, FuncHT) -> true | _ -> false let elem_kind = function - | FuncRefType -> byte 0x00 + | (NoNull, FuncHT) -> byte 0x00 | _ -> assert false let is_elem_index e = @@ -884,7 +1023,7 @@ struct match emode.it with | Passive -> u32 0x01l; elem_kind etype; vec elem_index einit - | Active {index; offset} when index.it = 0l && is_elem_kind etype -> + | Active {index; offset} when index.it = 0l -> u32 0x00l; const offset; vec elem_index einit | Active {index; offset} -> u32 0x02l; @@ -895,7 +1034,7 @@ struct match emode.it with | Passive -> u32 0x05l; ref_type etype; vec const einit - | Active {index; offset} when index.it = 0l && is_elem_kind etype -> + | Active {index; offset} when index.it = 0l && etype = (Null, FuncHT) -> u32 0x04l; const offset; vec const einit | Active {index; offset} -> u32 0x06l; var index; const offset; ref_type etype; vec const einit diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index d6849b12ea..faae4f3db9 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -1,8 +1,9 @@ -open Values -open Types -open Instance open Ast +open Pack open Source +open Types +open Value +open Instance (* Errors *) @@ -35,10 +36,10 @@ let numeric_error at = function | Ixx.Overflow -> "integer overflow" | Ixx.DivideByZero -> "integer divide by zero" | Ixx.InvalidConversion -> "invalid conversion to integer" - | Values.TypeError (i, v, t) -> + | Value.TypeError (i, v, t) -> Crash.error at - ("type error, expected " ^ Types.string_of_num_type t ^ " as operand " ^ - string_of_int i ^ ", got " ^ Types.string_of_num_type (type_of_num v)) + ("type error, expected " ^ string_of_num_type t ^ " as operand " ^ + string_of_int i ^ ", got " ^ string_of_num_type (type_of_num v)) | exn -> raise exn @@ -49,7 +50,7 @@ type 'a stack = 'a list type frame = { inst : module_inst; - locals : value ref list; + locals : value option ref list; } type code = value stack * admin_instr list @@ -61,9 +62,10 @@ and admin_instr' = | Invoke of func_inst | Trapping of string | Returning of value stack + | ReturningInvoke of value stack * func_inst | Breaking of int32 * value stack - | Label of int32 * instr list * code - | Frame of int32 * frame * code + | Label of int * instr list * code + | Frame of int * frame * code type config = { @@ -78,6 +80,17 @@ let config inst vs es = let plain e = Plain e.it @@ e.at +let admin_instr_of_value (v : value) at : admin_instr' = + match v with + | Num n -> Plain (Const (n @@ at)) + | Vec v -> Plain (VecConst (v @@ at)) + | Ref r -> Refer r + +let is_jumping e = + match e.it with + | Trapping _ | Returning _ | ReturningInvoke _ | Breaking _ -> true + | _ -> false + let lookup category list x = try Lib.List32.nth list x.it with Failure _ -> Crash.error x.at ("undefined " ^ category ^ " " ^ Int32.to_string x.it) @@ -91,31 +104,40 @@ let elem (inst : module_inst) x = lookup "element segment" inst.elems x let data (inst : module_inst) x = lookup "data segment" inst.datas x let local (frame : frame) x = lookup "local" frame.locals x -let any_ref inst x i at = +let str_type (inst : module_inst) x = expand_def_type (type_ inst x) +let func_type (inst : module_inst) x = as_func_str_type (str_type inst x) +let struct_type (inst : module_inst) x = as_struct_str_type (str_type inst x) +let array_type (inst : module_inst) x = as_array_str_type (str_type inst x) + +let subst_of (inst : module_inst) = function + | StatX x when x < Lib.List32.length inst.types -> + DefHT (type_ inst (x @@ Source.no_region)) + | x -> VarHT x + +let any_ref (inst : module_inst) x i at = try Table.load (table inst x) i with Table.Bounds -> Trap.error at ("undefined element " ^ Int64.to_string i) -let func_ref inst x i at = +let func_ref (inst : module_inst) x i at = match any_ref inst x i at with | FuncRef f -> f | NullRef _ -> Trap.error at ("uninitialized element " ^ Int64.to_string i) | _ -> Crash.error at ("type mismatch for element " ^ Int64.to_string i) -let func_type_of = function - | Func.AstFunc (t, inst, f) -> t - | Func.HostFunc (t, _) -> t - -let block_type inst bt = +let block_type (inst : module_inst) bt at = match bt with - | VarBlockType x -> type_ inst x - | ValBlockType None -> FuncType ([], []) - | ValBlockType (Some t) -> FuncType ([], [t]) + | ValBlockType None -> InstrT ([], [], []) + | ValBlockType (Some t) -> InstrT ([], [subst_val_type (subst_of inst) t], []) + | VarBlockType x -> + let FuncT (ts1, ts2) = func_type inst x in InstrT (ts1, ts2, []) let take n (vs : 'a stack) at = - try Lib.List32.take n vs with Failure _ -> Crash.error at "stack underflow" + try Lib.List.take n vs with Failure _ -> Crash.error at "stack underflow" let drop n (vs : 'a stack) at = - try Lib.List32.drop n vs with Failure _ -> Crash.error at "stack underflow" + try Lib.List.drop n vs with Failure _ -> Crash.error at "stack underflow" + +let split n (vs : 'a stack) at = take n vs at, drop n vs at (* Evaluation *) @@ -129,6 +151,18 @@ let drop n (vs : 'a stack) at = * c : config *) +let inc_address i loc = + match i with + | I32 x -> (I32 (I32.add x 1l) @@ loc) + | I64 x -> (I64 (I64.add x 1L) @@ loc) + | _ -> Crash.error loc ("bad address type") + +let index_of_num x = + match x with + | I64 i -> i + | I32 i -> I64_convert.extend_i32_u i + | _ -> raise Type + let mem_oob frame x i n = let mem = (memory frame.inst x) in let start = Memory.address_of_num i in @@ -147,8 +181,20 @@ let elem_oob frame x i n = I64.gt_u (I64.add (Table.index_of_num i) (Table.index_of_num n)) (Elem.size (elem frame.inst x)) +let elem_oob2 frame x i n = + I64.gt_u (I64.add (Table.index_of_num i) (I64_convert.extend_i32_u n)) + (Elem.size (elem frame.inst x)) + +let array_oob a i n = + I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n)) + (I64_convert.extend_i32_u (Aggr.array_length a)) + +let array_oob2 a i n = + I64.gt_u (I64.add (I64_convert.extend_i32_u i) (index_of_num n)) + (I64_convert.extend_i32_u (Aggr.array_length a)) + let rec step (c : config) : config = - let {frame; code = vs, es; _} = c in + let vs, es = c.code in let e = List.hd es in let vs', es' = match e.it, vs with @@ -161,16 +207,16 @@ let rec step (c : config) : config = vs, [] | Block (bt, es'), vs -> - let FuncType (ts1, ts2) = block_type frame.inst bt in - let n1 = Lib.List32.length ts1 in - let n2 = Lib.List32.length ts2 in - let args, vs' = take n1 vs e.at, drop n1 vs e.at in + let InstrT (ts1, ts2, _xs) = block_type c.frame.inst bt e.at in + let n1 = List.length ts1 in + let n2 = List.length ts2 in + let args, vs' = split n1 vs e.at in vs', [Label (n2, [], (args, List.map plain es')) @@ e.at] | Loop (bt, es'), vs -> - let FuncType (ts1, ts2) = block_type frame.inst bt in - let n1 = Lib.List32.length ts1 in - let args, vs' = take n1 vs e.at, drop n1 vs e.at in + let InstrT (ts1, ts2, _xs) = block_type c.frame.inst bt e.at in + let n1 = List.length ts1 in + let args, vs' = split n1 vs e.at in vs', [Label (n1, [e' @@ e.at], (args, List.map plain es')) @@ e.at] | If (bt, es1, es2), Num (I32 i) :: vs' -> @@ -194,19 +240,78 @@ let rec step (c : config) : config = else vs', [Plain (Br (Lib.List32.nth xs i)) @@ e.at] + | BrOnNull x, Ref (NullRef _) :: vs' -> + vs', [Plain (Br x) @@ e.at] + + | BrOnNull x, Ref r :: vs' -> + Ref r :: vs', [] + + | BrOnNonNull x, Ref (NullRef _) :: vs' -> + vs', [] + + | BrOnNonNull x, Ref r :: vs' -> + Ref r :: vs', [Plain (Br x) @@ e.at] + + | BrOnCast (x, _rt1, rt2), Ref r :: vs' -> + let rt2' = subst_ref_type (subst_of c.frame.inst) rt2 in + if Match.match_ref_type [] (type_of_ref r) rt2' then + Ref r :: vs', [Plain (Br x) @@ e.at] + else + Ref r :: vs', [] + + | BrOnCastFail (x, _rt1, rt2), Ref r :: vs' -> + let rt2' = subst_ref_type (subst_of c.frame.inst) rt2 in + if Match.match_ref_type [] (type_of_ref r) rt2' then + Ref r :: vs', [] + else + Ref r :: vs', [Plain (Br x) @@ e.at] + | Return, vs -> [], [Returning vs @@ e.at] | Call x, vs -> - vs, [Invoke (func frame.inst x) @@ e.at] + vs, [Invoke (func c.frame.inst x) @@ e.at] + + | CallRef _x, Ref (NullRef _) :: vs -> + vs, [Trapping "null function reference" @@ e.at] + + | CallRef _x, Ref (FuncRef f) :: vs -> + vs, [Invoke f @@ e.at] | CallIndirect (x, y), Num n :: vs -> let i = Table.index_of_num n in - let func = func_ref frame.inst x i e.at in - if type_ frame.inst y <> Func.type_of func then - vs, [Trapping "indirect call type mismatch" @@ e.at] + let f = func_ref c.frame.inst x i e.at in + if Match.match_def_type [] (Func.type_of f) (type_ c.frame.inst y) then + vs, [Invoke f @@ e.at] else - vs, [Invoke func @@ e.at] + vs, [Trapping ("indirect call type mismatch, expected " ^ + string_of_def_type (type_ c.frame.inst y) ^ " but got " ^ + string_of_def_type (Func.type_of f)) @@ e.at] + + | ReturnCallRef _x, Ref (NullRef _) :: vs -> + vs, [Trapping "null function reference" @@ e.at] + + | ReturnCallRef x, vs -> + (match (step {c with code = (vs, [Plain (CallRef x) @@ e.at])}).code with + | vs', [{it = Invoke a; at}] -> vs', [ReturningInvoke (vs', a) @@ at] + | vs', [{it = Trapping s; at}] -> vs', [Trapping s @@ at] + | _ -> assert false + ) + + | ReturnCall x, vs -> + (match (step {c with code = (vs, [Plain (Call x) @@ e.at])}).code with + | vs', [{it = Invoke a; at}] -> vs', [ReturningInvoke (vs', a) @@ at] + | _ -> assert false + ) + + | ReturnCallIndirect (x, y), vs -> + (match + (step {c with code = (vs, [Plain (CallIndirect (x, y)) @@ e.at])}).code + with + | vs', [{it = Invoke a; at}] -> vs', [ReturningInvoke (vs', a) @@ at] + | vs', [{it = Trapping s; at}] -> vs', [Trapping s @@ at] + | _ -> assert false + ) | Drop, v :: vs' -> vs', [] @@ -218,40 +323,45 @@ let rec step (c : config) : config = v1 :: vs', [] | LocalGet x, vs -> - !(local frame x) :: vs, [] + (match !(local c.frame x) with + | Some v -> + v :: vs, [] + | None -> + Crash.error e.at "read of uninitialized local" + ) | LocalSet x, v :: vs' -> - local frame x := v; + local c.frame x := Some v; vs', [] | LocalTee x, v :: vs' -> - local frame x := v; + local c.frame x := Some v; v :: vs', [] | GlobalGet x, vs -> - Global.load (global frame.inst x) :: vs, [] + Global.load (global c.frame.inst x) :: vs, [] | GlobalSet x, v :: vs' -> - (try Global.store (global frame.inst x) v; vs', [] + (try Global.store (global c.frame.inst x) v; vs', [] with Global.NotMutable -> Crash.error e.at "write to immutable global" | Global.Type -> Crash.error e.at "type mismatch at global write") | TableGet x, Num n :: vs' -> let i = Table.index_of_num n in - (try Ref (Table.load (table frame.inst x) i) :: vs', [] + (try Ref (Table.load (table c.frame.inst x) i) :: vs', [] with exn -> vs', [Trapping (table_error e.at exn) @@ e.at]) | TableSet x, Ref r :: Num n :: vs' -> let i = Table.index_of_num n in - (try Table.store (table frame.inst x) i r; vs', [] + (try Table.store (table c.frame.inst x) i r; vs', [] with exn -> vs', [Trapping (table_error e.at exn) @@ e.at]) | TableSize x, vs -> - let tab = table frame.inst x in - value_of_index (Table.index_type_of tab) (Table.size (table frame.inst x)) :: vs, [] + let tab = table c.frame.inst x in + value_of_index (Table.index_type_of tab) (Table.size (table c.frame.inst x)) :: vs, [] | TableGrow x, Num delta :: Ref r :: vs' -> - let tab = table frame.inst x in + let tab = table c.frame.inst x in let old_size = Table.size tab in let result = try Table.grow tab (Table.index_of_num delta) r; old_size @@ -260,14 +370,14 @@ let rec step (c : config) : config = | TableFill x, Num n :: Ref r :: Num i :: vs' -> let n_64 = Table.index_of_num n in - if table_oob frame x i n then + if table_oob c.frame x i n then vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else let i_64 = Table.index_of_num i in let _ = assert (I64.lt_u i_64 0xffff_ffff_ffff_ffffL) in - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 i_64 @@ e.at)); Refer r; Plain (TableSet x); @@ -281,12 +391,12 @@ let rec step (c : config) : config = let n_64 = Table.index_of_num n in let s_64 = Table.index_of_num s in let d_64 = Table.index_of_num d in - if table_oob frame x d n || table_oob frame y s n then + if table_oob c.frame x d n || table_oob c.frame y s n then vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else if I64.le_u d_64 s_64 then - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 d_64 @@ e.at)); Plain (Const (I64 s_64 @@ e.at)); Plain (TableGet y); @@ -298,7 +408,7 @@ let rec step (c : config) : config = ] else (* d > s *) let n' = I64.sub n_64 1L in - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 (I64.add d_64 n') @@ e.at)); Plain (Const (I64 (I64.add s_64 n') @@ e.at)); Plain (TableGet y); @@ -311,15 +421,15 @@ let rec step (c : config) : config = | TableInit (x, y), Num n :: Num s :: Num d :: vs' -> let n_64 = Table.index_of_num n in - if table_oob frame x d n || elem_oob frame y s n then + if table_oob c.frame x d n || elem_oob c.frame y s n then vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else let d_64 = Table.index_of_num d in let s_64 = Table.index_of_num s in - let seg = elem frame.inst y in - vs', List.map (at e.at) [ + let seg = elem c.frame.inst y in + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 d_64 @@ e.at)); Refer (Elem.load seg s_64); Plain (TableSet x); @@ -330,12 +440,12 @@ let rec step (c : config) : config = ] | ElemDrop x, vs -> - let seg = elem frame.inst x in + let seg = elem c.frame.inst x in Elem.drop seg; vs, [] - | Load {offset; ty; pack; _}, Num i :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | Load (x, {offset; ty; pack; _}), Num i :: vs' -> + let mem = memory c.frame.inst x in let a = Memory.address_of_num i in (try let n = @@ -345,8 +455,8 @@ let rec step (c : config) : config = in Num n :: vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) - | Store {offset; pack; _}, Num n :: Num i :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | Store (x, {offset; pack; _}), Num n :: Num i :: vs' -> + let mem = memory c.frame.inst x in let a = Memory.address_of_num i in (try (match pack with @@ -356,49 +466,48 @@ let rec step (c : config) : config = vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]); - | VecLoad {offset; ty; pack; _}, Num (I32 i) :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in - let addr = I64_convert.extend_i32_u i in + | VecLoad (x, {offset; ty; pack; _}), Num (I32 i) :: vs' -> + let mem = memory c.frame.inst x in + let a = I64_convert.extend_i32_u i in (try let v = match pack with - | None -> Memory.load_vec mem addr offset ty - | Some (sz, ext) -> - Memory.load_vec_packed sz ext mem addr offset ty + | None -> Memory.load_vec mem a offset ty + | Some (sz, ext) -> Memory.load_vec_packed sz ext mem a offset ty in Vec v :: vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) - | VecStore {offset; _}, Vec v :: Num (I32 i) :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | VecStore (x, {offset; _}), Vec v :: Num (I32 i) :: vs' -> + let mem = memory c.frame.inst x in let addr = I64_convert.extend_i32_u i in (try Memory.store_vec mem addr offset v; vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]); - | VecLoadLane ({offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | VecLoadLane (x, {offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' -> + let mem = memory c.frame.inst x in let addr = I64_convert.extend_i32_u i in (try let v = match pack with | Pack8 -> V128.I8x16.replace_lane j v - (I32Num.of_num 0 (Memory.load_num_packed Pack8 SX mem addr offset I32Type)) + (I32Num.of_num 0 (Memory.load_num_packed Pack8 SX mem addr offset I32T)) | Pack16 -> V128.I16x8.replace_lane j v - (I32Num.of_num 0 (Memory.load_num_packed Pack16 SX mem addr offset I32Type)) + (I32Num.of_num 0 (Memory.load_num_packed Pack16 SX mem addr offset I32T)) | Pack32 -> V128.I32x4.replace_lane j v - (I32Num.of_num 0 (Memory.load_num mem addr offset I32Type)) + (I32Num.of_num 0 (Memory.load_num mem addr offset I32T)) | Pack64 -> V128.I64x2.replace_lane j v - (I64Num.of_num 0 (Memory.load_num mem addr offset I64Type)) + (I64Num.of_num 0 (Memory.load_num mem addr offset I64T)) in Vec (V128 v) :: vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) - | VecStoreLane ({offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | VecStoreLane (x, {offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' -> + let mem = memory c.frame.inst x in let addr = I64_convert.extend_i32_u i in (try (match pack with @@ -414,116 +523,405 @@ let rec step (c : config) : config = vs', [] with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) - | MemorySize, vs -> - let mem = memory frame.inst (0l @@ e.at) in - + | MemorySize x, vs -> + let mem = memory c.frame.inst x in value_of_index (Memory.index_type_of mem) (Memory.size mem) :: vs, [] - | MemoryGrow, Num delta :: vs' -> - let mem = memory frame.inst (0l @@ e.at) in + | MemoryGrow x, Num delta :: vs' -> + let mem = memory c.frame.inst x in let old_size = Memory.size mem in let result = try Memory.grow mem (Memory.address_of_num delta); old_size with Memory.SizeOverflow | Memory.SizeLimit | Memory.OutOfMemory -> -1L in (value_of_index (Memory.index_type_of mem) result) :: vs', [] - | MemoryFill, Num n :: Num k :: Num i :: vs' -> + | MemoryFill x, Num n :: Num k :: Num i :: vs' -> let n_64 = Memory.address_of_num n in - if mem_oob frame (0l @@ e.at) i n then + if mem_oob c.frame x i n then vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else - let i_64 = Memory.address_of_num i in - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (i @@ e.at)); Plain (Const (k @@ e.at)); Plain (Store - {ty = I32Type; align = 0; offset = 0L; pack = Some Pack8}); - Plain (Const (I64 (I64.add i_64 1L) @@ e.at)); + (x, {ty = I32T; align = 0; offset = 0L; pack = Some Pack8})); + Plain (Const (inc_address i e.at)); Plain (Const (k @@ e.at)); Plain (Const (I64 (I64.sub n_64 1L) @@ e.at)); - Plain (MemoryFill); + Plain (MemoryFill x); ] - | MemoryCopy, Num n :: Num s :: Num d :: vs' -> + | MemoryCopy (x, y), Num n :: Num s :: Num d :: vs' -> let n_64 = Memory.address_of_num n in let s_64 = Memory.address_of_num s in let d_64 = Memory.address_of_num d in - if mem_oob frame (0l @@ e.at) s n || mem_oob frame (0l @@ e.at) d n then + if mem_oob c.frame x d n || mem_oob c.frame y s n then vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else if I64.le_u d_64 s_64 then - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 d_64 @@ e.at)); Plain (Const (I64 s_64 @@ e.at)); Plain (Load - {ty = I32Type; align = 0; offset = 0L; pack = Some (Pack8, ZX)}); + (y, {ty = I32T; align = 0; offset = 0L; pack = Some (Pack8, ZX)})); Plain (Store - {ty = I32Type; align = 0; offset = 0L; pack = Some Pack8}); + (x, {ty = I32T; align = 0; offset = 0L; pack = Some Pack8})); Plain (Const (I64 (I64.add d_64 1L) @@ e.at)); Plain (Const (I64 (I64.add s_64 1L) @@ e.at)); Plain (Const (I64 (I64.sub n_64 1L) @@ e.at)); - Plain (MemoryCopy); + Plain (MemoryCopy (x, y)); ] else (* d > s *) let n' = I64.sub n_64 1L in - vs', List.map (at e.at) [ + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 (I64.add d_64 n') @@ e.at)); Plain (Const (I64 (I64.add s_64 n') @@ e.at)); Plain (Load - {ty = I32Type; align = 0; offset = 0L; pack = Some (Pack8, ZX)}); + (y, {ty = I32T; align = 0; offset = 0L; pack = Some (Pack8, ZX)})); Plain (Store - {ty = I32Type; align = 0; offset = 0L; pack = Some Pack8}); + (x, {ty = I32T; align = 0; offset = 0L; pack = Some Pack8})); Plain (Const (I64 d_64 @@ e.at)); Plain (Const (I64 s_64 @@ e.at)); Plain (Const (I64 n' @@ e.at)); - Plain (MemoryCopy); + Plain (MemoryCopy (x, y)); ] - | MemoryInit x, Num n :: Num s :: Num d :: vs' -> + | MemoryInit (x, y), Num n :: Num s :: Num d :: vs' -> let n_64 = Memory.address_of_num n in - if mem_oob frame (0l @@ e.at) d n || data_oob frame x s n then + if mem_oob c.frame x d n || data_oob c.frame y s n then vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at] else if n_64 = 0L then vs', [] else - let seg = data frame.inst x in + let seg = data c.frame.inst y in let s_64 = Memory.address_of_num s in let d_64 = Memory.address_of_num d in - let b = Data.load seg s_64 in - vs', List.map (at e.at) [ + let b = Data.load_byte seg s_64 in + vs', List.map (Lib.Fun.flip (@@) e.at) [ Plain (Const (I64 d_64 @@ e.at)); Plain (Const (I64 (I64.of_int_u (Char.code b)) @@ e.at)); Plain (Store - {ty = I64Type; align = 0; offset = 0L; pack = Some Pack8}); + (x, {ty = I64T; align = 0; offset = 0L; pack = Some Pack8})); Plain (Const (I64 (I64.add d_64 1L) @@ e.at)); Plain (Const (I64 (I64.add s_64 1L) @@ e.at)); Plain (Const (I64 (I64.sub n_64 1L) @@ e.at)); - Plain (MemoryInit x); + Plain (MemoryInit (x, y)); ] | DataDrop x, vs -> - let seg = data frame.inst x in + let seg = data c.frame.inst x in Data.drop seg; vs, [] | RefNull t, vs' -> - Ref (NullRef t) :: vs', [] - - | RefIsNull, Ref r :: vs' -> - (match r with - | NullRef _ -> - Num (I32 1l) :: vs', [] - | _ -> - Num (I32 0l) :: vs', [] - ) + Ref (NullRef (subst_heap_type (subst_of c.frame.inst) t)) :: vs', [] | RefFunc x, vs' -> - let f = func frame.inst x in + let f = func c.frame.inst x in Ref (FuncRef f) :: vs', [] + | RefIsNull, Ref (NullRef _) :: vs' -> + value_of_bool true :: vs', [] + + | RefIsNull, Ref _ :: vs' -> + value_of_bool false :: vs', [] + + | RefAsNonNull, Ref (NullRef _) :: vs' -> + vs', [Trapping "null reference" @@ e.at] + + | RefAsNonNull, Ref r :: vs' -> + Ref r :: vs', [] + + | RefTest rt, Ref r :: vs' -> + let rt' = subst_ref_type (subst_of c.frame.inst) rt in + value_of_bool (Match.match_ref_type [] (type_of_ref r) rt') :: vs', [] + + | RefCast rt, Ref r :: vs' -> + let rt' = subst_ref_type (subst_of c.frame.inst) rt in + if Match.match_ref_type [] (type_of_ref r) rt' then + Ref r :: vs', [] + else + vs', [Trapping ("cast failure, expected " ^ + string_of_ref_type rt ^ " but got " ^ + string_of_ref_type (type_of_ref r)) @@ e.at] + + | RefEq, Ref r1 :: Ref r2 :: vs' -> + value_of_bool (eq_ref r1 r2) :: vs', [] + + | RefI31, Num (I32 i) :: vs' -> + Ref (I31.I31Ref (I31.of_i32 i)) :: vs', [] + + | I31Get ext, Ref (NullRef _) :: vs' -> + vs', [Trapping "null i31 reference" @@ e.at] + + | I31Get ext, Ref (I31.I31Ref i) :: vs' -> + Num (I32 (I31.to_i32 ext i)) :: vs', [] + + | StructNew (x, initop), vs' -> + let StructT fts = struct_type c.frame.inst x in + let args, vs'' = + match initop with + | Explicit -> + let args, vs'' = split (List.length fts) vs' e.at in + List.rev args, vs'' + | Implicit -> + let ts = List.map unpacked_field_type fts in + try List.map Option.get (List.map default_value ts), vs' + with Invalid_argument _ -> Crash.error e.at "non-defaultable type" + in + let struct_ = + try Aggr.alloc_struct (type_ c.frame.inst x) args + with Failure _ -> Crash.error e.at "type mismatch packing value" + in Ref (Aggr.StructRef struct_) :: vs'', [] + + | StructGet (x, y, exto), Ref (NullRef _) :: vs' -> + vs', [Trapping "null structure reference" @@ e.at] + + | StructGet (x, y, exto), Ref Aggr.(StructRef (Struct (_, fs))) :: vs' -> + let f = + try Lib.List32.nth fs y.it + with Failure _ -> Crash.error y.at "undefined field" + in + (try Aggr.read_field f exto :: vs', [] + with Failure _ -> Crash.error e.at "type mismatch reading field") + + | StructSet (x, y), v :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null structure reference" @@ e.at] + + | StructSet (x, y), v :: Ref Aggr.(StructRef (Struct (_, fs))) :: vs' -> + let f = + try Lib.List32.nth fs y.it + with Failure _ -> Crash.error y.at "undefined field" + in + (try Aggr.write_field f v; vs', [] + with Failure _ -> Crash.error e.at "type mismatch writing field") + + | ArrayNew (x, initop), Num (I32 n) :: vs' -> + let ArrayT (FieldT (_mut, st)) = array_type c.frame.inst x in + let arg, vs'' = + match initop with + | Explicit -> List.hd vs', List.tl vs' + | Implicit -> + try Option.get (default_value (unpacked_storage_type st)), vs' + with Invalid_argument _ -> Crash.error e.at "non-defaultable type" + in + let array = + try Aggr.alloc_array (type_ c.frame.inst x) (Lib.List32.make n arg) + with Failure _ -> Crash.error e.at "type mismatch packing value" + in Ref (Aggr.ArrayRef array) :: vs'', [] + + | ArrayNewFixed (x, n), vs' -> + let args, vs'' = split (I32.to_int_u n) vs' e.at in + let array = + try Aggr.alloc_array (type_ c.frame.inst x) (List.rev args) + with Failure _ -> Crash.error e.at "type mismatch packing value" + in Ref (Aggr.ArrayRef array) :: vs'', [] + + | ArrayNewElem (x, y), Num n :: Num s :: vs' -> + if elem_oob c.frame y s n then + vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] + else + let seg = elem c.frame.inst y in + let s_64 = Table.index_of_num s in + let rs = Lib.List64.init (Table.index_of_num n) (fun i -> Elem.load seg (Int64.add s_64 i)) in + let args = List.map (fun r -> Ref r) rs in + let array = + try Aggr.alloc_array (type_ c.frame.inst x) args + with Failure _ -> Crash.error e.at "type mismatch packing value" + in Ref (Aggr.ArrayRef array) :: vs', [] + + | ArrayNewData (x, y), Num n :: Num s :: vs' -> + if data_oob c.frame y s n then + vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at] + else + let ArrayT (FieldT (_mut, st)) = array_type c.frame.inst x in + let seg = data c.frame.inst y in + let args = Lib.List64.init (Memory.address_of_num n) + (fun i -> + let a = I64.(add (Memory.address_of_num s) (mul i (I64.of_int_u (storage_size st)))) in + Data.load_val_storage seg a st + ) + in + let array = + try Aggr.alloc_array (type_ c.frame.inst x) args + with Failure _ -> Crash.error e.at "type mismatch packing value" + in Ref (Aggr.ArrayRef array) :: vs', [] + + | ArrayGet (x, exto), Num (I32 i) :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayGet (x, exto), Num (I32 i) :: Ref (Aggr.ArrayRef a) :: vs' + when array_oob a i 1l -> + vs', [Trapping "out of bounds array access" @@ e.at] + + | ArrayGet (x, exto), Num (I32 i) :: Ref Aggr.(ArrayRef (Array (_, fs))) :: vs' -> + (try Aggr.read_field (Lib.List32.nth fs i) exto :: vs', [] + with Failure _ -> Crash.error e.at "type mismatch reading array") + + | ArraySet x, v :: Num (I32 i) :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArraySet x, v :: Num (I32 i) :: Ref (Aggr.ArrayRef a) :: vs' + when array_oob a i 1l -> + vs', [Trapping "out of bounds array access" @@ e.at] + + | ArraySet x, v :: Num (I32 i) :: Ref Aggr.(ArrayRef (Array (_, fs))) :: vs' -> + (try Aggr.write_field (Lib.List32.nth fs i) v; vs', [] + with Failure _ -> Crash.error e.at "type mismatch writing array") + + | ArrayLen, Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayLen, Ref Aggr.(ArrayRef (Array (_, fs))) :: vs' -> + Num (I32 (Lib.List32.length fs)) :: vs', [] + + | ArrayCopy (x, y), + Num _ :: Num _ :: Ref (NullRef _) :: Num _ :: Ref _ :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayCopy (x, y), + Num _ :: Num _ :: Ref _ :: Num _ :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayCopy (x, y), + Num (I32 n) :: + Num (I32 s) :: Ref (Aggr.ArrayRef sa) :: + Num (I32 d) :: Ref (Aggr.ArrayRef da) :: vs' -> + if array_oob sa s n || array_oob da d n then + vs', [Trapping "out of bounds array access" @@ e.at] + else if n = 0l then + vs', [] + else + let exto = + match as_array_str_type (expand_def_type (Aggr.type_of_array sa)) with + | ArrayT (FieldT (_, PackStorageT _)) -> Some ZX + | _ -> None + in + if I32.le_u d s then + vs', List.map (Lib.Fun.flip (@@) e.at) [ + Refer (Aggr.ArrayRef da); + Plain (Const (I32 d @@ e.at)); + Refer (Aggr.ArrayRef sa); + Plain (Const (I32 s @@ e.at)); + Plain (ArrayGet (y, exto)); + Plain (ArraySet x); + Refer (Aggr.ArrayRef da); + Plain (Const (I32 (I32.add d 1l) @@ e.at)); + Refer (Aggr.ArrayRef sa); + Plain (Const (I32 (I32.add s 1l) @@ e.at)); + Plain (Const (I32 (I32.sub n 1l) @@ e.at)); + Plain (ArrayCopy (x, y)); + ] + else (* d > s *) + vs', List.map (Lib.Fun.flip (@@) e.at) [ + Refer (Aggr.ArrayRef da); + Plain (Const (I32 (I32.add d 1l) @@ e.at)); + Refer (Aggr.ArrayRef sa); + Plain (Const (I32 (I32.add s 1l) @@ e.at)); + Plain (Const (I32 (I32.sub n 1l) @@ e.at)); + Plain (ArrayCopy (x, y)); + Refer (Aggr.ArrayRef da); + Plain (Const (I32 d @@ e.at)); + Refer (Aggr.ArrayRef sa); + Plain (Const (I32 s @@ e.at)); + Plain (ArrayGet (y, exto)); + Plain (ArraySet x); + ] + + | ArrayFill x, Num (I32 n) :: v :: Num (I32 i) :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayFill x, Num (I32 n) :: v :: Num (I32 i) :: Ref (Aggr.ArrayRef a) :: vs' -> + if array_oob a i n then + vs', [Trapping "out of bounds array access" @@ e.at] + else if n = 0l then + vs', [] + else + vs', List.map (Lib.Fun.flip (@@) e.at) [ + Refer (Aggr.ArrayRef a); + Plain (Const (I32 i @@ e.at)); + admin_instr_of_value v e.at; + Plain (ArraySet x); + Refer (Aggr.ArrayRef a); + Plain (Const (I32 (I32.add i 1l) @@ e.at)); + admin_instr_of_value v e.at; + Plain (Const (I32 (I32.sub n 1l) @@ e.at)); + Plain (ArrayFill x); + ] + + | ArrayInitData (x, y), + Num _ :: Num _ :: Num _ :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayInitData (x, y), + Num n :: Num s :: Num (I32 d) :: Ref (Aggr.ArrayRef a) :: vs' -> + let n_64 = Memory.address_of_num n in + if array_oob2 a d n then + vs', [Trapping "out of bounds array access" @@ e.at] + else if data_oob c.frame y s n then + vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at] + else if n_64 = 0L then + vs', [] + else + let ArrayT (FieldT (_mut, st)) = array_type c.frame.inst x in + let seg = data c.frame.inst y in + let s_64 = Memory.address_of_num s in + let v = Data.load_val_storage seg s_64 st in + vs', List.map (Lib.Fun.flip (@@) e.at) [ + Refer (Aggr.ArrayRef a); + Plain (Const (I32 d @@ e.at)); + admin_instr_of_value v e.at; + Plain (ArraySet x); + Refer (Aggr.ArrayRef a); + Plain (Const (I32 (I32.add d 1l) @@ e.at)); + Plain (Const (I64 (I64.add s_64 (I64.of_int_u (storage_size st))) @@ e.at)); + Plain (Const (I64 (I64.sub n_64 1L) @@ e.at)); + Plain (ArrayInitData (x, y)); + ] + + | ArrayInitElem (x, y), + Num _ :: Num _ :: Num _ :: Ref (NullRef _) :: vs' -> + vs', [Trapping "null array reference" @@ e.at] + + | ArrayInitElem (x, y), + Num (I32 n) :: Num s :: Num (I32 d) :: Ref (Aggr.ArrayRef a) :: vs' -> + if array_oob a d n then + vs', [Trapping "out of bounds array access" @@ e.at] + else if elem_oob2 c.frame y s n then + vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] + else if n = 0l then + vs', [] + else + let seg = elem c.frame.inst y in + let s_64 = Table.index_of_num s in + let v = Ref (Elem.load seg s_64) in + vs', List.map (Lib.Fun.flip (@@) e.at) [ + Refer (Aggr.ArrayRef a); + Plain (Const (I32 d @@ e.at)); + admin_instr_of_value v e.at; + Plain (ArraySet x); + Refer (Aggr.ArrayRef a); + Plain (Const (I32 (I32.add d 1l) @@ e.at)); + Plain (Const (I64 (I64.add s_64 1L) @@ e.at)); + Plain (Const (I32 (I32.sub n 1l) @@ e.at)); + Plain (ArrayInitElem (x, y)); + ] + + | ExternConvert Internalize, Ref (NullRef _) :: vs' -> + Ref (NullRef NoneHT) :: vs', [] + + | ExternConvert Internalize, Ref (Extern.ExternRef r) :: vs' -> + Ref r :: vs', [] + + | ExternConvert Externalize, Ref (NullRef _) :: vs' -> + Ref (NullRef NoExternHT) :: vs', [] + + | ExternConvert Externalize, Ref r :: vs' -> + Ref (Extern.ExternRef r) :: vs', [] + | Const n, vs -> Num n.it :: vs, [] @@ -608,7 +1006,7 @@ let rec step (c : config) : config = | _ -> let s1 = string_of_values (List.rev vs) in - let s2 = string_of_value_types (List.map type_of_value (List.rev vs)) in + let s2 = string_of_result_type (List.map type_of_value (List.rev vs)) in Crash.error e.at ("missing or ill-typed operand on stack (" ^ s1 ^ " : " ^ s2 ^ ")") ) @@ -616,30 +1014,28 @@ let rec step (c : config) : config = | Refer r, vs -> Ref r :: vs, [] - | Trapping msg, vs -> + | Trapping _, vs -> assert false - | Returning vs', vs -> + | Returning _, vs + | ReturningInvoke _, vs -> Crash.error e.at "undefined frame" - | Breaking (k, vs'), vs -> + | Breaking _, vs -> Crash.error e.at "undefined label" | Label (n, es0, (vs', [])), vs -> vs' @ vs, [] - | Label (n, es0, (vs', {it = Trapping msg; at} :: es')), vs -> - vs, [Trapping msg @@ at] - - | Label (n, es0, (vs', {it = Returning vs0; at} :: es')), vs -> - vs, [Returning vs0 @@ at] - | Label (n, es0, (vs', {it = Breaking (0l, vs0); at} :: es')), vs -> take n vs0 e.at @ vs, List.map plain es0 | Label (n, es0, (vs', {it = Breaking (k, vs0); at} :: es')), vs -> vs, [Breaking (Int32.sub k 1l, vs0) @@ at] + | Label (n, es0, (vs', e' :: es')), vs when is_jumping e' -> + vs, [e'] + | Label (n, es0, code'), vs -> let c' = step {c with code = code'} in vs, [Label (n, es0, c'.code) @@ e.at] @@ -653,27 +1049,35 @@ let rec step (c : config) : config = | Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs -> take n vs0 e.at @ vs, [] + | Frame (n, frame', (vs', {it = ReturningInvoke (vs0, f); at} :: es')), vs -> + let FuncT (ts1, _ts2) = as_func_str_type (expand_def_type (Func.type_of f)) in + take (List.length ts1) vs0 e.at @ vs, [Invoke f @@ at] + | Frame (n, frame', code'), vs -> let c' = step {frame = frame'; code = code'; budget = c.budget - 1} in - vs, [Frame (n, c'.frame, c'.code) @@ e.at] + vs, [Frame (n, frame', c'.code) @@ e.at] - | Invoke func, vs when c.budget = 0 -> + | Invoke f, vs when c.budget = 0 -> Exhaustion.error e.at "call stack exhausted" - | Invoke func, vs -> - let FuncType (ins, out) = func_type_of func in - let n1, n2 = Lib.List32.length ins, Lib.List32.length out in - let args, vs' = take n1 vs e.at, drop n1 vs e.at in - (match func with - | Func.AstFunc (t, inst', f) -> - let locals' = List.rev args @ List.map default_value f.it.locals in - let frame' = {inst = !inst'; locals = List.map ref locals'} in - let instr' = [Label (n2, [], ([], List.map plain f.it.body)) @@ f.at] in + | Invoke f, vs -> + let FuncT (ts1, ts2) = as_func_str_type (expand_def_type (Func.type_of f)) in + let n1, n2 = List.length ts1, List.length ts2 in + let args, vs' = split n1 vs e.at in + (match f with + | Func.AstFunc (_, inst', func) -> + let {locals; body; _} = func.it in + let m = Lib.Promise.value inst' in + let s = subst_of m in + let ts = List.map (fun loc -> subst_val_type s loc.it.ltype) locals in + let locs' = List.(rev (map Option.some args) @ map default_value ts) in + let frame' = {inst = m; locals = List.map ref locs'} in + let instr' = [Label (n2, [], ([], List.map plain body)) @@ func.at] in vs', [Frame (n2, frame', ([], instr')) @@ e.at] - | Func.HostFunc (t, f) -> - try List.rev (f (List.rev args)) @ vs', [] - with Crash (_, msg) -> Crash.error e.at msg + | Func.HostFunc (_, f) -> + (try List.rev (f (List.rev args)) @ vs', [] + with Crash (_, msg) -> Crash.error e.at msg) ) in {c with code = vs', es' @ List.tl es} @@ -692,12 +1096,16 @@ let rec eval (c : config) : value stack = (* Functions & Constants *) +let at_func = function + | Func.AstFunc (_, _, f) -> f.at + | Func.HostFunc _ -> no_region + let invoke (func : func_inst) (vs : value list) : value list = - let at = match func with Func.AstFunc (_, _, f) -> f.at | _ -> no_region in - let FuncType (ins, out) = Func.type_of func in - if List.length vs <> List.length ins then + let at = at_func func in + let FuncT (ts1, _ts2) = as_func_str_type (expand_def_type (Func.type_of func)) in + if List.length vs <> List.length ts1 then Crash.error at "wrong number of arguments"; - if not (List.for_all2 (fun v -> (=) (type_of_value v)) vs ins) then + if not (List.for_all2 (fun v -> Match.match_val_type [] (type_of_value v)) vs ts1) then Crash.error at "wrong types of arguments"; let c = config empty_module_inst (List.rev vs) [Invoke func @@ at] in try List.rev (eval c) with Stack_overflow -> @@ -711,24 +1119,73 @@ let eval_const (inst : module_inst) (const : const) : value = (* Modules *) -let create_func (inst : module_inst) (f : func) : func_inst = - Func.alloc (type_ inst f.it.ftype) (ref inst) f - -let create_table (inst : module_inst) (tab : table) : table_inst = - let {ttype} = tab.it in - let TableType (_lim, _it, t) = ttype in - Table.alloc ttype (NullRef t) +let init_type (inst : module_inst) (type_ : type_) : module_inst = + let rt = subst_rec_type (subst_of inst) type_.it in + let x = Lib.List32.length inst.types in + {inst with types = inst.types @ roll_def_types x rt} + +let init_import (inst : module_inst) (ex : extern) (im : import) : module_inst = + let {idesc; _} = im.it in + let it = + match idesc.it with + | FuncImport x -> ExternFuncT (type_ inst x) + | TableImport tt -> ExternTableT tt + | MemoryImport mt -> ExternMemoryT mt + | GlobalImport gt -> ExternGlobalT gt + in + let et = subst_extern_type (subst_of inst) it in + let et' = extern_type_of inst.types ex in + if not (Match.match_extern_type [] et' et) then + Link.error im.at ("incompatible import type for " ^ + "\"" ^ Utf8.encode im.it.module_name ^ "\" " ^ + "\"" ^ Utf8.encode im.it.item_name ^ "\": " ^ + "expected " ^ Types.string_of_extern_type et ^ + ", got " ^ Types.string_of_extern_type et'); + match ex with + | ExternFunc func -> {inst with funcs = inst.funcs @ [func]} + | ExternTable tab -> {inst with tables = inst.tables @ [tab]} + | ExternMemory mem -> {inst with memories = inst.memories @ [mem]} + | ExternGlobal glob -> {inst with globals = inst.globals @ [glob]} + +let init_func (inst : module_inst) (f : func) : module_inst = + let func = Func.alloc (type_ inst f.it.ftype) (Lib.Promise.make ()) f in + {inst with funcs = inst.funcs @ [func]} + +let init_global (inst : module_inst) (glob : global) : module_inst = + let {gtype; ginit} = glob.it in + let gt = subst_global_type (subst_of inst) gtype in + let v = eval_const inst ginit in + let glob = Global.alloc gt v in + {inst with globals = inst.globals @ [glob]} + +let init_table (inst : module_inst) (tab : table) : module_inst = + let {ttype; tinit} = tab.it in + let tt = subst_table_type (subst_of inst) ttype in + let r = + match eval_const inst tinit with + | Ref r -> r + | _ -> Crash.error tinit.at "non-reference table initializer" + in + let tab = Table.alloc tt r in + {inst with tables = inst.tables @ [tab]} -let create_memory (inst : module_inst) (mem : memory) : memory_inst = +let init_memory (inst : module_inst) (mem : memory) : module_inst = let {mtype} = mem.it in - Memory.alloc mtype + let mt = subst_memory_type (subst_of inst) mtype in + let mem = Memory.alloc mt in + {inst with memories = inst.memories @ [mem]} -let create_global (inst : module_inst) (glob : global) : global_inst = - let {gtype; ginit} = glob.it in - let v = eval_const inst ginit in - Global.alloc gtype v +let init_elem (inst : module_inst) (seg : elem_segment) : module_inst = + let {etype; einit; _} = seg.it in + let elem = Elem.alloc (List.map (fun c -> as_ref (eval_const inst c)) einit) in + {inst with elems = inst.elems @ [elem]} + +let init_data (inst : module_inst) (seg : data_segment) : module_inst = + let {dinit; _} = seg.it in + let data = Data.alloc dinit in + {inst with datas = inst.datas @ [data]} -let create_export (inst : module_inst) (ex : export) : export_inst = +let init_export (inst : module_inst) (ex : export) : module_inst = let {name; edesc} = ex.it in let ext = match edesc.it with @@ -736,35 +1193,15 @@ let create_export (inst : module_inst) (ex : export) : export_inst = | TableExport x -> ExternTable (table inst x) | MemoryExport x -> ExternMemory (memory inst x) | GlobalExport x -> ExternGlobal (global inst x) - in (name, ext) - -let create_elem (inst : module_inst) (seg : elem_segment) : elem_inst = - let {etype; einit; _} = seg.it in - Elem.alloc (List.map (fun c -> as_ref (eval_const inst c)) einit) - -let create_data (inst : module_inst) (seg : data_segment) : data_inst = - let {dinit; _} = seg.it in - Data.alloc dinit + in + {inst with exports = inst.exports @ [(name, ext)]} -let add_import (m : module_) (ext : extern) (im : import) (inst : module_inst) - : module_inst = - if not (match_extern_type (extern_type_of ext) (import_type m im)) then - Link.error im.at ("incompatible import type for " ^ - "\"" ^ Utf8.encode im.it.module_name ^ "\" " ^ - "\"" ^ Utf8.encode im.it.item_name ^ "\": " ^ - "expected " ^ Types.string_of_extern_type (import_type m im) ^ - ", got " ^ Types.string_of_extern_type (extern_type_of ext)); - match ext with - | ExternFunc func -> {inst with funcs = func :: inst.funcs} - | ExternTable tab -> {inst with tables = tab :: inst.tables} - | ExternMemory mem -> {inst with memories = mem :: inst.memories} - | ExternGlobal glob -> {inst with globals = glob :: inst.globals} - -let init_func (inst : module_inst) (func : func_inst) = +let init_func_inst (inst : module_inst) (func : func_inst) = match func with - | Func.AstFunc (_, inst_ref, _) -> inst_ref := inst - | _ -> assert false + | Func.AstFunc (_, prom, _) when Lib.Promise.value_opt prom = None -> + Lib.Promise.fulfill prom inst + | _ -> () let run_elem i elem = let at = elem.it.emode.at in @@ -787,11 +1224,10 @@ let run_data i data = match data.it.dmode.it with | Passive -> [] | Active {index; offset} -> - assert (index.it = 0l); offset.it @ [ Const (I32 0l @@ at) @@ at; Const (I32 (Int32.of_int (String.length data.it.dinit)) @@ at) @@ at; - MemoryInit x @@ at; + MemoryInit (index, x) @@ at; DataDrop x @@ at ] | Declarative -> assert false @@ -799,37 +1235,31 @@ let run_data i data = let run_start start = [Call start.it.sfunc @@ start.at] + +let init_list f xs (inst : module_inst) : module_inst = + List.fold_left f inst xs + +let init_list2 f xs ys (inst : module_inst) : module_inst = + List.fold_left2 f inst xs ys + let init (m : module_) (exts : extern list) : module_inst = - let - { imports; tables; memories; globals; funcs; types; - exports; elems; datas; start - } = m.it - in - if List.length exts <> List.length imports then + if List.length exts <> List.length m.it.imports then Link.error m.at "wrong number of imports provided for initialisation"; - let inst0 = - { (List.fold_right2 (add_import m) exts imports empty_module_inst) with - types = List.map (fun type_ -> type_.it) types } - in - let fs = List.map (create_func inst0) funcs in - let inst1 = {inst0 with funcs = inst0.funcs @ fs} in - let inst2 = - { inst1 with - tables = inst1.tables @ List.map (create_table inst1) tables; - memories = inst1.memories @ List.map (create_memory inst1) memories; - globals = inst1.globals @ List.map (create_global inst1) globals; - } - in let inst = - { inst2 with - exports = List.map (create_export inst2) exports; - elems = List.map (create_elem inst2) elems; - datas = List.map (create_data inst2) datas; - } + empty_module_inst + |> init_list init_type m.it.types + |> init_list2 init_import exts m.it.imports + |> init_list init_func m.it.funcs + |> init_list init_global m.it.globals + |> init_list init_table m.it.tables + |> init_list init_memory m.it.memories + |> init_list init_elem m.it.elems + |> init_list init_data m.it.datas + |> init_list init_export m.it.exports in - List.iter (init_func inst) fs; - let es_elem = List.concat (Lib.List32.mapi run_elem elems) in - let es_data = List.concat (Lib.List32.mapi run_data datas) in - let es_start = Lib.Option.get (Lib.Option.map run_start start) [] in + List.iter (init_func_inst inst) inst.funcs; + let es_elem = List.concat (Lib.List32.mapi run_elem m.it.elems) in + let es_data = List.concat (Lib.List32.mapi run_data m.it.datas) in + let es_start = Lib.Option.get (Lib.Option.map run_start m.it.start) [] in ignore (eval (config inst [] (List.map plain (es_elem @ es_data @ es_start)))); inst diff --git a/interpreter/exec/eval.mli b/interpreter/exec/eval.mli index 825cc74f09..056fc05fec 100644 --- a/interpreter/exec/eval.mli +++ b/interpreter/exec/eval.mli @@ -1,4 +1,4 @@ -open Values +open Value open Instance exception Link of Source.region * string diff --git a/interpreter/exec/eval_num.ml b/interpreter/exec/eval_num.ml index 40dd1be07c..3ccfcac006 100644 --- a/interpreter/exec/eval_num.ml +++ b/interpreter/exec/eval_num.ml @@ -1,5 +1,5 @@ open Types -open Values +open Value (* Int operators *) @@ -14,7 +14,7 @@ struct | Clz -> IXX.clz | Ctz -> IXX.ctz | Popcnt -> IXX.popcnt - | ExtendS sz -> IXX.extend_s (8 * packed_size sz) + | ExtendS sz -> IXX.extend_s (8 * Pack.packed_size sz) in fun v -> to_num (f (of_num 1 v)) let binop op = @@ -124,8 +124,8 @@ struct | TruncSatUF64 -> I32_convert.trunc_sat_f64_u (F64Num.of_num 1 v) | TruncSatSF64 -> I32_convert.trunc_sat_f64_s (F64Num.of_num 1 v) | ReinterpretFloat -> I32_convert.reinterpret_f32 (F32Num.of_num 1 v) - | ExtendUI32 -> raise (TypeError (1, v, I32Type)) - | ExtendSI32 -> raise (TypeError (1, v, I32Type)) + | ExtendUI32 -> raise (TypeError (1, v, I32T)) + | ExtendSI32 -> raise (TypeError (1, v, I32T)) in I32Num.to_num i end @@ -146,7 +146,7 @@ struct | TruncSatUF64 -> I64_convert.trunc_sat_f64_u (F64Num.of_num 1 v) | TruncSatSF64 -> I64_convert.trunc_sat_f64_s (F64Num.of_num 1 v) | ReinterpretFloat -> I64_convert.reinterpret_f64 (F64Num.of_num 1 v) - | WrapI64 -> raise (TypeError (1, v, I64Type)) + | WrapI64 -> raise (TypeError (1, v, I64T)) in I64Num.to_num i end @@ -162,7 +162,7 @@ struct | ConvertSI64 -> F32_convert.convert_i64_s (I64Num.of_num 1 v) | ConvertUI64 -> F32_convert.convert_i64_u (I64Num.of_num 1 v) | ReinterpretInt -> F32_convert.reinterpret_i32 (I32Num.of_num 1 v) - | PromoteF32 -> raise (TypeError (1, v, F32Type)) + | PromoteF32 -> raise (TypeError (1, v, F32T)) in F32Num.to_num z end @@ -178,7 +178,7 @@ struct | ConvertSI64 -> F64_convert.convert_i64_s (I64Num.of_num 1 v) | ConvertUI64 -> F64_convert.convert_i64_u (I64Num.of_num 1 v) | ReinterpretInt -> F64_convert.reinterpret_i64 (I64Num.of_num 1 v) - | DemoteF64 -> raise (TypeError (1, v, F64Type)) + | DemoteF64 -> raise (TypeError (1, v, F64T)) in F64Num.to_num z end diff --git a/interpreter/exec/eval_num.mli b/interpreter/exec/eval_num.mli index 719c16f404..e5c53f11e1 100644 --- a/interpreter/exec/eval_num.mli +++ b/interpreter/exec/eval_num.mli @@ -1,4 +1,4 @@ -open Values +open Value val eval_unop : Ast.unop -> num -> num val eval_binop : Ast.binop -> num -> num -> num diff --git a/interpreter/exec/eval_vec.ml b/interpreter/exec/eval_vec.ml index d4c6e9648e..a3b9986ff8 100644 --- a/interpreter/exec/eval_vec.ml +++ b/interpreter/exec/eval_vec.ml @@ -1,5 +1,5 @@ -open Types -open Values +open Pack +open Value module V128Op = struct @@ -281,7 +281,7 @@ struct | F32x4 (Extract (i, ())) -> F32 (V128.F32x4.extract_lane i v128) | F64x2 (Extract (i, ())) -> F64 (V128.F64x2.extract_lane i v128) - let replaceop (op : replaceop) v (n : Values.num) = + let replaceop (op : replaceop) v (n : num) = let v128 = of_vec 1 v in let v128' = match op with | I8x16 (Replace i) -> V128.I8x16.replace_lane i v128 (I32Num.of_num 1 n) diff --git a/interpreter/exec/eval_vec.mli b/interpreter/exec/eval_vec.mli index be54f20b72..45b59033e5 100644 --- a/interpreter/exec/eval_vec.mli +++ b/interpreter/exec/eval_vec.mli @@ -1,4 +1,4 @@ -open Values +open Value val eval_testop : Ast.vec_testop -> vec -> bool val eval_unop : Ast.vec_unop -> vec -> vec diff --git a/interpreter/exec/v128.ml b/interpreter/exec/v128.ml index 873035ad35..c508f6b941 100644 --- a/interpreter/exec/v128.ml +++ b/interpreter/exec/v128.ml @@ -29,10 +29,10 @@ let num_lanes shape = | F64x2 _ -> 2 let type_of_lane = function - | I8x16 _ | I16x8 _ | I32x4 _ -> Types.I32Type - | I64x2 _ -> Types.I64Type - | F32x4 _ -> Types.F32Type - | F64x2 _ -> Types.F64Type + | I8x16 _ | I16x8 _ | I32x4 _ -> Types.I32T + | I64x2 _ -> Types.I64T + | F32x4 _ -> Types.F32T + | F64x2 _ -> Types.F64T (* Shape-based operations *) diff --git a/interpreter/host/env.ml b/interpreter/host/env.ml index 58239d10bc..ab871607ce 100644 --- a/interpreter/host/env.ml +++ b/interpreter/host/env.ml @@ -4,8 +4,8 @@ * we have agreement on what libc should look like. *) -open Values open Types +open Value open Instance @@ -13,8 +13,8 @@ let error msg = raise (Eval.Crash (Source.no_region, msg)) let type_error v t = error - ("type error, expected " ^ string_of_value_type t ^ - ", got " ^ string_of_value_type (type_of_value v)) + ("type error, expected " ^ string_of_val_type t ^ + ", got " ^ string_of_val_type (type_of_value v)) let empty = function | [] -> () @@ -27,7 +27,7 @@ let single = function let int = function | Num (I32 i) -> Int32.to_int i - | v -> type_error v (NumType I32Type) + | v -> type_error v (NumT I32T) let abort vs = @@ -39,8 +39,8 @@ let exit vs = exit (int (single vs)) -let lookup name t = - match Utf8.encode name, t with - | "abort", ExternFuncType t -> ExternFunc (Func.alloc_host t abort) - | "exit", ExternFuncType t -> ExternFunc (Func.alloc_host t exit) +let lookup name et = + match Utf8.encode name, et with + | "abort", ExternFuncT ct -> ExternFunc (Func.alloc_host ct abort) + | "exit", ExternFuncT ct -> ExternFunc (Func.alloc_host ct exit) | _ -> raise Not_found diff --git a/interpreter/host/spectest.ml b/interpreter/host/spectest.ml index 2555ec683a..0ab23ec4c6 100644 --- a/interpreter/host/spectest.ml +++ b/interpreter/host/spectest.ml @@ -3,57 +3,62 @@ *) open Types -open Values +open Value open Instance -let global (GlobalType (t, _) as gt) = +let global (GlobalT (_, t) as gt) = let v = match t with - | NumType I32Type -> Num (I32 666l) - | NumType I64Type -> Num (I64 666L) - | NumType F32Type -> Num (F32 (F32.of_float 666.6)) - | NumType F64Type -> Num (F64 (F64.of_float 666.6)) - | VecType V128Type -> Vec (V128 (V128.I32x4.of_lanes [666l; 666l; 666l; 666l])) - | RefType t -> Ref (NullRef t) - in Global.alloc gt v + | NumT I32T -> Num (I32 666l) + | NumT I64T -> Num (I64 666L) + | NumT F32T -> Num (F32 (F32.of_float 666.6)) + | NumT F64T -> Num (F64 (F64.of_float 666.6)) + | VecT V128T -> Vec (V128 (V128.I32x4.of_lanes [666l; 666l; 666l; 666l])) + | RefT (_, t) -> Ref (NullRef t) + | BotT -> assert false + in ExternGlobal (Global.alloc gt v) let table = - Table.alloc (TableType ({min = 10L; max = Some 20L}, I32IndexType, FuncRefType)) - (NullRef FuncRefType) + let tt = TableT ({min = 10L; max = Some 20L}, I32IndexType, (Null, FuncHT)) in + ExternTable (Table.alloc tt (NullRef FuncHT)) + let table64 = - Table.alloc (TableType ({min = 10L; max = Some 20L}, I64IndexType, FuncRefType)) - (NullRef FuncRefType) -let memory = Memory.alloc (MemoryType ({min = 1L; max = Some 2L}, I32IndexType)) -let func f t = Func.alloc_host t (f t) + let tt = TableT ({min = 10L; max = Some 20L}, I64IndexType, (Null, FuncHT)) in + ExternTable (Table.alloc tt (NullRef FuncHT)) + +let memory = + let mt = MemoryT ({min = 1L; max = Some 2L}, I32IndexType) in + ExternMemory (Memory.alloc mt) + +let func f ft = + let dt = DefT (RecT [SubT (Final, [], DefFuncT ft)], 0l) in + ExternFunc (Func.alloc_host dt (f ft)) let print_value v = Printf.printf "%s : %s\n" - (Values.string_of_value v) - (Types.string_of_value_type (Values.type_of_value v)) + (string_of_value v) (string_of_val_type (type_of_value v)) -let print (FuncType (_, out)) vs = +let print _ vs = List.iter print_value vs; flush_all (); - List.map default_value out + [] let lookup name t = match Utf8.encode name, t with - | "print", _ -> ExternFunc (func print (FuncType ([], []))) - | "print_i32", _ -> ExternFunc (func print (FuncType ([NumType I32Type], []))) - | "print_i64", _ -> ExternFunc (func print (FuncType ([NumType I64Type], []))) - | "print_f32", _ -> ExternFunc (func print (FuncType ([NumType F32Type], []))) - | "print_f64", _ -> ExternFunc (func print (FuncType ([NumType F64Type], []))) - | "print_i32_f32", _ -> - ExternFunc (func print (FuncType ([NumType I32Type; NumType F32Type], []))) - | "print_f64_f64", _ -> - ExternFunc (func print (FuncType ([NumType F64Type; NumType F64Type], []))) - | "global_i32", _ -> ExternGlobal (global (GlobalType (NumType I32Type, Immutable))) - | "global_i64", _ -> ExternGlobal (global (GlobalType (NumType I64Type, Immutable))) - | "global_f32", _ -> ExternGlobal (global (GlobalType (NumType F32Type, Immutable))) - | "global_f64", _ -> ExternGlobal (global (GlobalType (NumType F64Type, Immutable))) - | "table", _ -> ExternTable table - | "table64", _ -> ExternTable table64 - | "memory", _ -> ExternMemory memory + | "print", _ -> func print (FuncT ([], [])) + | "print_i32", _ -> func print (FuncT ([NumT I32T], [])) + | "print_i64", _ -> func print (FuncT ([NumT I64T], [])) + | "print_f32", _ -> func print (FuncT ([NumT F32T], [])) + | "print_f64", _ -> func print (FuncT ([NumT F64T], [])) + | "print_i32_f32", _ -> func print (FuncT ([NumT I32T; NumT F32T], [])) + | "print_f64_f64", _ -> func print (FuncT ([NumT F64T; NumT F64T], [])) + | "global_i32", _ -> global (GlobalT (Cons, NumT I32T)) + | "global_i64", _ -> global (GlobalT (Cons, NumT I64T)) + | "global_f32", _ -> global (GlobalT (Cons, NumT F32T)) + | "global_f64", _ -> global (GlobalT (Cons, NumT F64T)) + | "table", _ -> table + | "table64", _ -> table64 + | "memory", _ -> memory | _ -> raise Not_found diff --git a/interpreter/jslib/wast.ml b/interpreter/jslib/wast.ml index 54ea9fd4d3..bc7edef074 100644 --- a/interpreter/jslib/wast.ml +++ b/interpreter/jslib/wast.ml @@ -12,7 +12,7 @@ let () = let _, def = Parse.Module.parse_string (Js.to_string s) in let bs = match def.Source.it with - | Script.Textual m -> (Encode.encode m) + | Script.Textual m -> Encode.encode m | Script.Encoded (_, bs) -> bs | Script.Quoted (_, _) -> failwith "Unsupported" in let buf = new%js Typed_array.arrayBuffer (String.length bs) in diff --git a/interpreter/main/main.ml b/interpreter/main/main.ml index e626a27ded..2377a6f9aa 100644 --- a/interpreter/main/main.ml +++ b/interpreter/main/main.ml @@ -1,5 +1,5 @@ let name = "wasm" -let version = "2.0.1" +let version = "3.0.0" let configure () = Import.register (Utf8.decode "spectest") Spectest.lookup; diff --git a/interpreter/runtime/aggr.ml b/interpreter/runtime/aggr.ml new file mode 100644 index 0000000000..90d39ecb9a --- /dev/null +++ b/interpreter/runtime/aggr.ml @@ -0,0 +1,88 @@ +open Types +open Value + +type field = + | ValField of value ref + | PackField of Pack.pack_size * int ref + +type struct_ = Struct of def_type * field list +type array = Array of def_type * field list + +type ref_ += StructRef of struct_ +type ref_ += ArrayRef of array + + +let gap sz = 32 - 8 * Pack.packed_size sz +let wrap sz i = Int32.(to_int (logand i (shift_right_logical (-1l) (gap sz)))) +let extend_u sz i = Int32.of_int i +let extend_s sz i = Int32.(shift_right (shift_left (of_int i) (gap sz)) (gap sz)) + +let alloc_field ft v = + let FieldT (_mut, st) = ft in + match st, v with + | ValStorageT _, v -> ValField (ref v) + | PackStorageT sz, Num (I32 i) -> PackField (sz, ref (wrap sz i)) + | _, _ -> failwith "alloc_field" + +let write_field fld v = + match fld, v with + | ValField vr, v -> vr := v + | PackField (sz, ir), Num (I32 i) -> ir := wrap sz i + | _, _ -> failwith "write_field" + +let read_field fld exto = + match fld, exto with + | ValField vr, None -> !vr + | PackField (sz, ir), Some Pack.ZX -> Num (I32 (extend_u sz !ir)) + | PackField (sz, ir), Some Pack.SX -> Num (I32 (extend_s sz !ir)) + | _, _ -> failwith "read_field" + +let array_length (Array (_, fs)) = Lib.List32.length fs + + +let alloc_struct dt vs = + assert Free.((def_type dt).types = Set.empty); + let StructT fts = as_struct_str_type (expand_def_type dt) in + Struct (dt, List.map2 alloc_field fts vs) + +let alloc_array dt vs = + assert Free.((def_type dt).types = Set.empty); + let ArrayT ft = as_array_str_type (expand_def_type dt) in + Array (dt, List.map (alloc_field ft) vs) + + +let type_of_struct (Struct (dt, _)) = dt +let type_of_array (Array (dt, _)) = dt + + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | StructRef s -> DefHT (type_of_struct s) + | ArrayRef a -> DefHT (type_of_array a) + | r -> type_of_ref' r + +let string_of_field = function + | ValField vr -> string_of_value !vr + | PackField (_, ir) -> string_of_int !ir + +let string_of_fields nest fs = + if fs = [] then "" else + if !nest > 0 then " ..." else + let fs', ell = + if List.length fs > 5 + then Lib.List.take 5 fs, ["..."] + else fs, [] + in " " ^ String.concat " " (List.map string_of_field fs' @ ell) + +let string_of_aggr name nest fs = + Fun.protect (fun () -> incr nest; "(" ^ name ^ string_of_fields nest fs ^ ")") + ~finally:(fun () -> decr nest) + +let () = + let string_of_ref' = !Value.string_of_ref' in + let nest = ref 0 in + Value.string_of_ref' := function + | StructRef (Struct (_, fs)) -> string_of_aggr "struct" nest fs + | ArrayRef (Array (_, fs)) -> string_of_aggr "array" nest fs + | r -> string_of_ref' r diff --git a/interpreter/runtime/aggr.mli b/interpreter/runtime/aggr.mli new file mode 100644 index 0000000000..422018579d --- /dev/null +++ b/interpreter/runtime/aggr.mli @@ -0,0 +1,23 @@ +open Types +open Value + +type field = + | ValField of value ref + | PackField of Pack.pack_size * int ref + +type struct_ = Struct of def_type * field list +type array = Array of def_type * field list + +type ref_ += StructRef of struct_ +type ref_ += ArrayRef of array + +val alloc_struct : def_type -> value list -> struct_ +val alloc_array : def_type -> value list -> array + +val type_of_struct : struct_ -> def_type +val type_of_array : array -> def_type + +val read_field : field -> Pack.extension option -> value (* raises Failure *) +val write_field : field -> value -> unit (* raises Falure *) + +val array_length : array -> int32 diff --git a/interpreter/runtime/data.ml b/interpreter/runtime/data.ml index f63a2aaa3c..785dcc6081 100644 --- a/interpreter/runtime/data.ml +++ b/interpreter/runtime/data.ml @@ -1,5 +1,6 @@ type data = string ref type t = data +type address = Memory.address exception Bounds @@ -7,9 +8,41 @@ let alloc bs = ref bs let size seg = I64.of_int_u (String.length !seg) -let load seg i = - let i' = Int64.to_int i in - if i' < 0 || i' >= String.length !seg then raise Bounds; - !seg.[i'] - let drop seg = seg := "" + +let load_byte seg a = + let i = Int64.to_int a in + if i < 0 || i >= String.length !seg then raise Bounds; + !seg.[i] + +let load_bytes seg a n = + let i = Int64.to_int a in + if i < 0 || i + n < 0 || i + n > String.length !seg then raise Bounds; + String.sub !seg i n + + +(* Typed accessors *) + +let load_num seg a nt = + let bs = load_bytes seg a (Types.num_size nt) in + Value.num_of_bits nt bs + +let load_num_packed sz ext seg a nt = + let bs = load_bytes seg a (Pack.packed_size sz) in + Value.num_of_packed_bits nt sz ext bs + +let load_vec seg a vt = + let bs = load_bytes seg a (Types.vec_size vt) in + Value.vec_of_bits vt bs + +let load_vec_packed sz ext seg a t = + let bs = load_bytes seg a (Pack.packed_size sz) in + Value.vec_of_packed_bits t sz ext bs + +let load_val seg a t = + let bs = load_bytes seg a (Types.val_size t) in + Value.val_of_bits t bs + +let load_val_storage seg a st = + let bs = load_bytes seg a (Types.storage_size st) in + Value.val_of_storage_bits st bs diff --git a/interpreter/runtime/data.mli b/interpreter/runtime/data.mli index 498c45149f..f0074e366c 100644 --- a/interpreter/runtime/data.mli +++ b/interpreter/runtime/data.mli @@ -1,7 +1,31 @@ type data type t = data +type address = Memory.address + +exception Bounds val alloc : string -> data -val size : data -> Memory.address -val load : data -> Memory.address -> char +val size : data -> address val drop : data -> unit + +val load_byte : data -> address -> char (* raises Bounds *) +val load_bytes : data -> address -> int -> string (* raises Bounds *) + + +(* Typed accessors *) + +open Types +open Value + +val load_num : data -> address -> num_type -> num (* raises Bounds *) +val load_vec : data -> address -> vec_type -> vec (* raises Bounds *) +val load_val : data -> address -> val_type -> value (* raises Type, Bounds *) + +val load_num_packed : + Pack.pack_size -> Pack.extension -> data -> address -> num_type -> num + (* raises Type, Bounds *) +val load_vec_packed : + Pack.pack_size -> Pack.vec_extension -> data -> address -> vec_type -> vec + (* raises Type, Bounds *) +val load_val_storage : + data -> address -> storage_type -> value (* raises Type, Bounds *) diff --git a/interpreter/runtime/elem.ml b/interpreter/runtime/elem.ml index 816d532bc8..101474f978 100644 --- a/interpreter/runtime/elem.ml +++ b/interpreter/runtime/elem.ml @@ -1,4 +1,4 @@ -type elem = Values.ref_ list ref +type elem = Value.ref_ list ref type t = elem exception Bounds diff --git a/interpreter/runtime/elem.mli b/interpreter/runtime/elem.mli index 5eb0c0e56e..b9b35a11dd 100644 --- a/interpreter/runtime/elem.mli +++ b/interpreter/runtime/elem.mli @@ -1,9 +1,11 @@ -open Values +open Value type elem type t = elem +exception Bounds + val alloc : ref_ list -> elem val size : elem -> Table.size -val load : elem -> Table.index -> ref_ +val load : elem -> Table.index -> ref_ (* raises Bounds *) val drop : elem -> unit diff --git a/interpreter/runtime/extern.ml b/interpreter/runtime/extern.ml new file mode 100644 index 0000000000..e7a23b6cec --- /dev/null +++ b/interpreter/runtime/extern.ml @@ -0,0 +1,26 @@ +open Types +open Value + +type extern = ref_ +type t = extern + +type ref_ += ExternRef of extern + +let () = + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> + match r1, r2 with + | ExternRef r1', ExternRef r2' -> Value.eq_ref r1' r2' + | _, _ -> eq_ref' r1 r2 + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | ExternRef _ -> ExternHT + | r -> type_of_ref' r + +let () = + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function + | ExternRef r -> "(extern " ^ string_of_ref r ^ ")" + | r -> string_of_ref' r diff --git a/interpreter/runtime/extern.mli b/interpreter/runtime/extern.mli new file mode 100644 index 0000000000..adb3755be8 --- /dev/null +++ b/interpreter/runtime/extern.mli @@ -0,0 +1,6 @@ +open Value + +type extern = ref_ +type t = extern + +type ref_ += ExternRef of extern diff --git a/interpreter/runtime/func.ml b/interpreter/runtime/func.ml index 461cdc5bb3..a5aa85bf0f 100644 --- a/interpreter/runtime/func.ml +++ b/interpreter/runtime/func.ml @@ -1,14 +1,20 @@ open Types -open Values +open Value type 'inst t = 'inst func and 'inst func = - | AstFunc of func_type * 'inst * Ast.func - | HostFunc of func_type * (value list -> value list) + | AstFunc of def_type * 'inst * Ast.func + | HostFunc of def_type * (value list -> value list) -let alloc ft inst f = AstFunc (ft, inst, f) -let alloc_host ft f = HostFunc (ft, f) +let alloc dt inst f = + ignore (as_func_str_type (expand_def_type dt)); + assert Free.((def_type dt).types = Set.empty); + AstFunc (dt, inst, f) + +let alloc_host dt f = + ignore (as_func_str_type (expand_def_type dt)); + HostFunc (dt, f) let type_of = function - | AstFunc (ft, _, _) -> ft - | HostFunc (ft, _) -> ft + | AstFunc (dt, _, _) -> dt + | HostFunc (dt, _) -> dt diff --git a/interpreter/runtime/func.mli b/interpreter/runtime/func.mli index d5346263ff..d578a8bb05 100644 --- a/interpreter/runtime/func.mli +++ b/interpreter/runtime/func.mli @@ -1,11 +1,12 @@ open Types -open Values +open Value type 'inst t = 'inst func and 'inst func = - | AstFunc of func_type * 'inst * Ast.func - | HostFunc of func_type * (value list -> value list) + | AstFunc of def_type * 'inst * Ast.func + | HostFunc of def_type * (value list -> value list) -val alloc : func_type -> 'inst -> Ast.func -> 'inst func -val alloc_host : func_type -> (value list -> value list) -> 'inst func -val type_of : 'inst func -> func_type +val alloc : def_type -> 'inst -> Ast.func -> 'inst func +val alloc_host : def_type -> (value list -> value list) -> 'inst func + +val type_of : 'inst func -> def_type diff --git a/interpreter/runtime/global.ml b/interpreter/runtime/global.ml index 5e1729950a..3640cf404b 100644 --- a/interpreter/runtime/global.ml +++ b/interpreter/runtime/global.ml @@ -1,5 +1,5 @@ open Types -open Values +open Value type global = {ty : global_type; mutable content : value} type t = global @@ -7,8 +7,9 @@ type t = global exception Type exception NotMutable -let alloc (GlobalType (t, _) as ty) v = - if type_of_value v <> t then raise Type; +let alloc (GlobalT (_mut, t) as ty) v = + assert Free.((val_type t).types = Set.empty); + if not (Match.match_val_type [] (type_of_value v) t) then raise Type; {ty; content = v} let type_of glob = @@ -18,7 +19,7 @@ let load glob = glob.content let store glob v = - let GlobalType (t, mut) = glob.ty in - if mut <> Mutable then raise NotMutable; - if type_of_value v <> t then raise Type; + let GlobalT (mut, t) = glob.ty in + if mut <> Var then raise NotMutable; + if not (Match.match_val_type [] (type_of_value v) t) then raise Type; glob.content <- v diff --git a/interpreter/runtime/global.mli b/interpreter/runtime/global.mli index 75c52faf6a..bf93c1a8ce 100644 --- a/interpreter/runtime/global.mli +++ b/interpreter/runtime/global.mli @@ -1,5 +1,5 @@ open Types -open Values +open Value type global type t = global diff --git a/interpreter/runtime/i31.ml b/interpreter/runtime/i31.ml new file mode 100644 index 0000000000..ce908f62d6 --- /dev/null +++ b/interpreter/runtime/i31.ml @@ -0,0 +1,33 @@ +open Types +open Value + +type i31 = int +type t = i31 + +type ref_ += I31Ref of i31 + +let of_i32 i = Int32.to_int i land 0x7fff_ffff +let to_i32 ext i = + let i' = Int32.of_int i in + match ext with + | Pack.ZX -> i' + | Pack.SX -> Int32.(shift_right (shift_left i' 1) 1) + +let () = + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> + match r1, r2 with + | I31Ref i1, I31Ref i2 -> i1 = i2 + | _, _ -> eq_ref' r1 r2 + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | I31Ref f -> I31HT + | r -> type_of_ref' r + +let () = + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function + | I31Ref i -> "(i31 " ^ string_of_int i ^ ")" + | r -> string_of_ref' r diff --git a/interpreter/runtime/i31.mli b/interpreter/runtime/i31.mli new file mode 100644 index 0000000000..c61252ba72 --- /dev/null +++ b/interpreter/runtime/i31.mli @@ -0,0 +1,9 @@ +open Value + +type i31 = int +type t = i31 + +type ref_ += I31Ref of i31 + +val of_i32 : int32 -> i31 +val to_i32 : Pack.extension -> i31 -> int32 diff --git a/interpreter/runtime/instance.ml b/interpreter/runtime/instance.ml index 0ae878c377..677f91fcd6 100644 --- a/interpreter/runtime/instance.ml +++ b/interpreter/runtime/instance.ml @@ -2,7 +2,7 @@ open Types type module_inst = { - types : func_type list; + types : type_inst list; funcs : func_inst list; tables : table_inst list; memories : memory_inst list; @@ -12,7 +12,8 @@ type module_inst = exports : export_inst list; } -and func_inst = module_inst ref Func.t +and type_inst = def_type +and func_inst = module_inst Lib.Promise.t Func.t and table_inst = Table.t and memory_inst = Memory.t and global_inst = Global.t @@ -29,39 +30,54 @@ and extern = (* Reference types *) -type Values.ref_ += FuncRef of func_inst +type Value.ref_ += FuncRef of func_inst let () = - let type_of_ref' = !Values.type_of_ref' in - Values.type_of_ref' := function - | FuncRef _ -> FuncRefType + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> + match r1, r2 with + | FuncRef _, FuncRef _ -> failwith "eq_ref" + | _, _ -> eq_ref' r1 r2 + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | FuncRef f -> DefHT (Func.type_of f) | r -> type_of_ref' r let () = - let string_of_ref' = !Values.string_of_ref' in - Values.string_of_ref' := function + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function | FuncRef _ -> "func" | r -> string_of_ref' r let () = - let eq_ref' = !Values.eq_ref' in - Values.eq_ref' := fun r1 r2 -> + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> match r1, r2 with | FuncRef f1, FuncRef f2 -> f1 == f2 | _, _ -> eq_ref' r1 r2 +(* Projections *) + +let func_inst_of_extern = function ExternFunc f -> f | _ -> failwith "func_inst_of_extern" +let table_inst_of_extern = function ExternTable f -> f | _ -> failwith "table_inst_of_extern" +let memory_inst_of_extern = function ExternMemory f -> f | _ -> failwith "memory_inst_of_extern" +let global_inst_of_extern = function ExternGlobal f -> f | _ -> failwith "global_inst_of_extern" + + (* Auxiliary functions *) let empty_module_inst = { types = []; funcs = []; tables = []; memories = []; globals = []; elems = []; datas = []; exports = [] } -let extern_type_of = function - | ExternFunc func -> ExternFuncType (Func.type_of func) - | ExternTable tab -> ExternTableType (Table.type_of tab) - | ExternMemory mem -> ExternMemoryType (Memory.type_of mem) - | ExternGlobal glob -> ExternGlobalType (Global.type_of glob) +let extern_type_of c = function + | ExternFunc func -> ExternFuncT (Func.type_of func) + | ExternTable tab -> ExternTableT (Table.type_of tab) + | ExternMemory mem -> ExternMemoryT (Memory.type_of mem) + | ExternGlobal glob -> ExternGlobalT (Global.type_of glob) let export inst name = try Some (List.assoc name inst.exports) with Not_found -> None diff --git a/interpreter/runtime/memory.ml b/interpreter/runtime/memory.ml index ef0a982895..03571a7301 100644 --- a/interpreter/runtime/memory.ml +++ b/interpreter/runtime/memory.ml @@ -1,7 +1,7 @@ +open Types +open Value open Bigarray open Lib.Bigarray -open Types -open Values type size = int64 (* number of pages *) type address = int64 @@ -12,7 +12,7 @@ type memory' = (int, int8_unsigned_elt, c_layout) Array1.t type memory = {mutable ty : memory_type; mutable content : memory'} type t = memory -exception Type +exception Type = Value.Type exception Bounds exception SizeOverflow exception SizeLimit @@ -34,7 +34,8 @@ let create n it = mem with Out_of_memory -> raise OutOfMemory -let alloc (MemoryType (lim, it) as ty) = +let alloc (MemoryT (lim, it) as ty) = + assert Free.((memory_type ty).types = Set.empty); if not (valid_limits lim) then raise Type; {ty; content = create lim.min it} @@ -48,12 +49,12 @@ let type_of mem = mem.ty let index_type_of mem = - let (MemoryType (_, it)) = type_of mem in it + let (MemoryT (_, it)) = type_of mem in it let address_of_num x = match x with - | I64 i -> i | I32 i -> I64_convert.extend_i32_u i + | I64 i -> i | _ -> raise Type let address_of_value x = @@ -62,7 +63,7 @@ let address_of_value x = | _ -> raise Type let grow mem delta = - let MemoryType (lim, it) = mem.ty in + let MemoryT (lim, it) = mem.ty in assert (lim.min = size mem); let old_size = lim.min in let new_size = Int64.add old_size delta in @@ -72,7 +73,7 @@ let grow mem delta = let after = create new_size (index_type_of mem) in let dim = Array1_64.dim mem.content in Array1.blit (Array1_64.sub mem.content 0L dim) (Array1_64.sub after 0L dim); - mem.ty <- MemoryType (lim', it); + mem.ty <- MemoryT (lim', it); mem.content <- after let load_byte mem a = @@ -91,101 +92,59 @@ let load_bytes mem a n = Buffer.contents buf let store_bytes mem a bs = + if a < 0L then raise Bounds; for i = String.length bs - 1 downto 0 do store_byte mem Int64.(add a (of_int i)) (Char.code bs.[i]) done + +(* Typed accessors *) + let effective_address a o = let ea = Int64.(add a o) in if I64.lt_u ea a then raise Bounds; ea -let loadn mem a o n = - assert (n > 0 && n <= 8); - let rec loop a n = - if n = 0 then 0L else begin - let x = Int64.(shift_left (loop (add a 1L) (n - 1)) 8) in - Int64.logor (Int64.of_int (load_byte mem a)) x - end - in loop (effective_address a o) n - -let storen mem a o n x = - assert (n > 0 && n <= 8); - let rec loop a n x = - if n > 0 then begin - Int64.(loop (effective_address a 1L) (n - 1) (shift_right x 8)); - store_byte mem a (Int64.to_int x land 0xff) - end - in loop (effective_address a o) n x - -let load_num mem a o t = - let n = loadn mem a o (Types.num_size t) in - match t with - | I32Type -> I32 (Int64.to_int32 n) - | I64Type -> I64 n - | F32Type -> F32 (F32.of_bits (Int64.to_int32 n)) - | F64Type -> F64 (F64.of_bits n) +let load_num mem a o nt = + let bs = load_bytes mem (effective_address a o) (Types.num_size nt) in + Value.num_of_bits nt bs let store_num mem a o n = - let store = storen mem a o (Types.num_size (Values.type_of_num n)) in - match n with - | I32 x -> store (Int64.of_int32 x) - | I64 x -> store x - | F32 x -> store (Int64.of_int32 (F32.to_bits x)) - | F64 x -> store (F64.to_bits x) - -let extend x n = function - | ZX -> x - | SX -> let sh = 64 - 8 * n in Int64.(shift_right (shift_left x sh) sh) - -let load_num_packed sz ext mem a o t = - assert (packed_size sz <= num_size t); - let w = packed_size sz in - let x = extend (loadn mem a o w) w ext in - match t with - | I32Type -> I32 (Int64.to_int32 x) - | I64Type -> I64 x - | _ -> raise Type + let bs = Value.bits_of_num n in + store_bytes mem (effective_address a o) bs + +let load_num_packed sz ext mem a o nt = + let bs = load_bytes mem (effective_address a o) (Pack.packed_size sz) in + Value.num_of_packed_bits nt sz ext bs let store_num_packed sz mem a o n = - assert (packed_size sz <= num_size (Values.type_of_num n)); - let w = packed_size sz in - let x = - match n with - | I32 x -> Int64.of_int32 x - | I64 x -> x - | _ -> raise Type - in storen mem a o w x - -let load_vec mem a o t = - match t with - | V128Type -> - V128 (V128.of_bits (load_bytes mem (effective_address a o) (Types.vec_size t))) - -let store_vec mem a o n = - match n with - | V128 x -> store_bytes mem (effective_address a o) (V128.to_bits x) + let bs = Value.packed_bits_of_num sz n in + store_bytes mem (effective_address a o) bs + +let load_vec mem a o vt = + let bs = load_bytes mem (effective_address a o) (Types.vec_size vt) in + Value.vec_of_bits vt bs + +let store_vec mem a o v = + let bs = Value.bits_of_vec v in + store_bytes mem (effective_address a o) bs let load_vec_packed sz ext mem a o t = - assert (packed_size sz < vec_size t); - let x = loadn mem a o (packed_size sz) in - let b = Bytes.make 16 '\x00' in - Bytes.set_int64_le b 0 x; - let v = V128.of_bits (Bytes.to_string b) in - let r = - match sz, ext with - | Pack64, ExtLane (Pack8x8, SX) -> V128.I16x8_convert.extend_low_s v - | Pack64, ExtLane (Pack8x8, ZX) -> V128.I16x8_convert.extend_low_u v - | Pack64, ExtLane (Pack16x4, SX) -> V128.I32x4_convert.extend_low_s v - | Pack64, ExtLane (Pack16x4, ZX) -> V128.I32x4_convert.extend_low_u v - | Pack64, ExtLane (Pack32x2, SX) -> V128.I64x2_convert.extend_low_s v - | Pack64, ExtLane (Pack32x2, ZX) -> V128.I64x2_convert.extend_low_u v - | _, ExtLane _ -> assert false - | Pack8, ExtSplat -> V128.I8x16.splat (I8.of_int_s (Int64.to_int x)) - | Pack16, ExtSplat -> V128.I16x8.splat (I16.of_int_s (Int64.to_int x)) - | Pack32, ExtSplat -> V128.I32x4.splat (I32.of_int_s (Int64.to_int x)) - | Pack64, ExtSplat -> V128.I64x2.splat x - | Pack32, ExtZero -> v - | Pack64, ExtZero -> v - | _, ExtZero -> assert false - in V128 r + let bs = load_bytes mem (effective_address a o) (Pack.packed_size sz) in + Value.vec_of_packed_bits t sz ext bs + +let load_val mem a o t = + let bs = load_bytes mem (effective_address a o) (Types.val_size t) in + Value.val_of_bits t bs + +let store_val mem a o v = + let bs = Value.bits_of_val v in + store_bytes mem (effective_address a o) bs + +let load_val_storage mem a o st = + let bs = load_bytes mem (effective_address a o) (Types.storage_size st) in + Value.val_of_storage_bits st bs + +let store_val_storage mem a o st v = + let bs = Value.storage_bits_of_val st v in + store_bytes mem (effective_address a o) bs diff --git a/interpreter/runtime/memory.mli b/interpreter/runtime/memory.mli index 1bcaa3b870..ab06999d32 100644 --- a/interpreter/runtime/memory.mli +++ b/interpreter/runtime/memory.mli @@ -1,5 +1,5 @@ open Types -open Values +open Value type memory type t = memory @@ -32,15 +32,18 @@ val store_byte : memory -> address -> int -> unit (* raises Bounds *) val load_bytes : memory -> address -> int -> string (* raises Bounds *) val store_bytes : memory -> address -> string -> unit (* raises Bounds *) + +(* Typed accessors *) + val load_num : memory -> address -> offset -> num_type -> num (* raises Bounds *) val store_num : memory -> address -> offset -> num -> unit (* raises Bounds *) val load_num_packed : - pack_size -> extension -> memory -> address -> offset -> num_type -> num + Pack.pack_size -> Pack.extension -> memory -> address -> offset -> num_type -> num (* raises Type, Bounds *) val store_num_packed : - pack_size -> memory -> address -> offset -> num -> unit + Pack.pack_size -> memory -> address -> offset -> num -> unit (* raises Type, Bounds *) val load_vec : @@ -49,5 +52,14 @@ val store_vec : memory -> address -> offset -> vec -> unit (* raises Type, Bounds *) val load_vec_packed : - pack_size -> vec_extension -> memory -> address -> offset -> vec_type -> vec + Pack.pack_size -> Pack.vec_extension -> memory -> address -> offset -> vec_type -> vec (* raises Type, Bounds *) + +val load_val : + memory -> address -> offset -> val_type -> value (* raises Type, Bounds *) +val store_val : + memory -> address -> offset -> value -> unit (* raises Type, Bounds *) +val load_val_storage : + memory -> address -> offset -> storage_type -> value (* raises Type, Bounds *) +val store_val_storage : + memory -> address -> offset -> storage_type -> value -> unit (* raises Type, Bounds *) diff --git a/interpreter/runtime/table.ml b/interpreter/runtime/table.ml index ea532e49b7..0db065c906 100644 --- a/interpreter/runtime/table.ml +++ b/interpreter/runtime/table.ml @@ -1,5 +1,5 @@ open Types -open Values +open Value type size = int64 type index = int64 @@ -28,7 +28,8 @@ let create size r = try Lib.Array64.make size r with Out_of_memory | Invalid_argument _ -> raise OutOfMemory -let alloc (TableType (lim, it, _) as ty) r = +let alloc (TableT (lim, it, t) as ty) r = + assert Free.((ref_type t).types = Set.empty); if not (valid_limits lim) then raise Type; {ty; content = create lim.min r} @@ -39,7 +40,7 @@ let type_of tab = tab.ty let index_type_of tab = - let (TableType (_, it, _)) = type_of tab in it + let (TableT (_, it, _)) = type_of tab in it let index_of_num x = match x with @@ -48,7 +49,7 @@ let index_of_num x = | _ -> raise Type let grow tab delta r = - let TableType (lim, it, t) = tab.ty in + let TableT (lim, it, t) = tab.ty in assert (lim.min = size tab); let old_size = lim.min in let new_size = Int64.add old_size delta in @@ -58,7 +59,7 @@ let grow tab delta r = if not (valid_limits lim') then raise SizeLimit else let after = create new_size r in Array.blit tab.content 0 after 0 (Array.length tab.content); - tab.ty <- TableType (lim', it, t); + tab.ty <- TableT (lim', it, t); tab.content <- after let load tab i = @@ -66,8 +67,8 @@ let load tab i = Lib.Array64.get tab.content i let store tab i r = - let TableType (_lim, _it, t) = tab.ty in - if type_of_ref r <> t then raise Type; + let TableT (_lim, _it, t) = tab.ty in + if not (Match.match_ref_type [] (type_of_ref r) t) then raise Type; if i < 0L || i >= Lib.Array64.length tab.content then raise Bounds; Lib.Array64.set tab.content i r diff --git a/interpreter/runtime/table.mli b/interpreter/runtime/table.mli index b770938152..07e84d5b64 100644 --- a/interpreter/runtime/table.mli +++ b/interpreter/runtime/table.mli @@ -1,5 +1,5 @@ open Types -open Values +open Value type table type t = table diff --git a/interpreter/runtime/value.ml b/interpreter/runtime/value.ml new file mode 100644 index 0000000000..a871e939c9 --- /dev/null +++ b/interpreter/runtime/value.ml @@ -0,0 +1,316 @@ +open Types + + +(* Values and operators *) + +type ('i32, 'i64, 'f32, 'f64) op = + I32 of 'i32 | I64 of 'i64 | F32 of 'f32 | F64 of 'f64 + +type ('v128) vecop = + V128 of 'v128 + +type num = (I32.t, I64.t, F32.t, F64.t) op +type vec = (V128.t) vecop + +type ref_ = .. +type ref_ += NullRef of heap_type + +type value = Num of num | Vec of vec | Ref of ref_ +type t = value + + +(* Injection & projection *) + +let as_num = function + | Num n -> n + | _ -> failwith "as_num" + +let as_vec = function + | Vec i -> i + | _ -> failwith "as_vec" + +let as_ref = function + | Ref r -> r + | _ -> failwith "as_ref" + + +exception TypeError of int * num * num_type + +module type NumType = +sig + type t + val to_num : t -> num + val of_num : int -> num -> t +end + +module I32Num = +struct + type t = I32.t + let to_num i = I32 i + let of_num n = function I32 i -> i | v -> raise (TypeError (n, v, I32T)) +end + +module I64Num = +struct + type t = I64.t + let to_num i = I64 i + let of_num n = function I64 i -> i | v -> raise (TypeError (n, v, I64T)) +end + +module F32Num = +struct + type t = F32.t + let to_num i = F32 i + let of_num n = function F32 z -> z | v -> raise (TypeError (n, v, F32T)) +end + +module F64Num = +struct + type t = F64.t + let to_num i = F64 i + let of_num n = function F64 z -> z | v -> raise (TypeError (n, v, F64T)) +end + +module type VecType = +sig + type t + val to_vec : t -> vec + val of_vec : int -> vec -> t +end + +module V128Vec = +struct + type t = V128.t + let to_vec i = V128 i + let of_vec n = function V128 z -> z +end + +let is_null_ref = function + | NullRef _ -> true + | _ -> false + + +(* Typing *) + +let type_of_op = function + | I32 _ -> I32T + | I64 _ -> I64T + | F32 _ -> F32T + | F64 _ -> F64T + +let type_of_vecop = function + | V128 _ -> V128T + +let type_of_num = type_of_op +let type_of_vec = type_of_vecop + +let type_of_ref' = ref (function _ -> assert false) +let type_of_ref = function + | NullRef t -> (Null, Match.bot_of_heap_type [] t) + | r -> (NoNull, !type_of_ref' r) + +let type_of_value = function + | Num n -> NumT (type_of_num n) + | Vec i -> VecT (type_of_vec i) + | Ref r -> RefT (type_of_ref r) + + +(* Comparison *) + +let eq_num n1 n2 = n1 = n2 + +let eq_vec v1 v2 = v1 = v2 + +let eq_ref' = ref (fun r1 r2 -> + match r1, r2 with + | NullRef _, NullRef _ -> true + | _, _ -> r1 == r2 +) + +let eq_ref r1 r2 = !eq_ref' r1 r2 + +let eq v1 v2 = + match v1, v2 with + | Num n1, Num n2 -> eq_num n1 n2 + | Vec v1, Vec v2 -> eq_vec v1 v2 + | Ref r1, Ref r2 -> eq_ref r1 r2 + | _, _ -> false + + +(* Defaults *) + +let default_num = function + | I32T -> Some (Num (I32 I32.zero)) + | I64T -> Some (Num (I64 I64.zero)) + | F32T -> Some (Num (F32 F32.zero)) + | F64T -> Some (Num (F64 F64.zero)) + +let default_vec = function + | V128T -> Some (Vec (V128 V128.zero)) + +let default_ref = function + | (Null, t) -> Some (Ref (NullRef t)) + | (NoNull, _) -> None + +let default_value = function + | NumT t -> default_num t + | VecT t -> default_vec t + | RefT t -> default_ref t + | BotT -> assert false + + +(* Representation *) + +exception Type + +let rec i64_of_bits bs = + if bs = "" then 0L else + let bs' = String.sub bs 1 (String.length bs - 1) in + Int64.(logor (of_int (Char.code bs.[0])) (shift_left (i64_of_bits bs') 8)) + +let num_of_bits t bs = + let n = i64_of_bits bs in + match t with + | I32T -> I32 (Int64.to_int32 n) + | I64T -> I64 n + | F32T -> F32 (F32.of_bits (Int64.to_int32 n)) + | F64T -> F64 (F64.of_bits n) + +let vec_of_bits t bs = + match t with + | V128T -> V128 (V128.of_bits bs) + +let val_of_bits t bs = + match t with + | NumT nt -> Num (num_of_bits nt bs) + | VecT vt -> Vec (vec_of_bits vt bs) + | RefT _ -> raise Type + | BotT -> assert false + +let extend n ext x = + match ext with + | Pack.ZX -> x + | Pack.SX -> let sh = 64 - 8 * n in Int64.(shift_right (shift_left x sh) sh) + +let num_of_packed_bits t sz ext bs = + assert (Pack.packed_size sz <= num_size t); + let n = Pack.packed_size sz in + let x = extend n ext (i64_of_bits bs) in + match t with + | I32T -> I32 (Int64.to_int32 x) + | I64T -> I64 x + | _ -> raise Type + +let val_of_storage_bits st bs = + match st with + | ValStorageT t -> val_of_bits t bs + | PackStorageT sz -> Num (num_of_packed_bits I32T sz Pack.ZX bs) + + +let vec_of_packed_bits t sz ext bs = + let open Pack in + assert (packed_size sz < vec_size t); + let x = i64_of_bits bs in + let b = Bytes.make 16 '\x00' in + Bytes.set_int64_le b 0 x; + let v = V128.of_bits (Bytes.to_string b) in + let r = + match sz, ext with + | Pack64, ExtLane (Pack8x8, SX) -> V128.I16x8_convert.extend_low_s v + | Pack64, ExtLane (Pack8x8, ZX) -> V128.I16x8_convert.extend_low_u v + | Pack64, ExtLane (Pack16x4, SX) -> V128.I32x4_convert.extend_low_s v + | Pack64, ExtLane (Pack16x4, ZX) -> V128.I32x4_convert.extend_low_u v + | Pack64, ExtLane (Pack32x2, SX) -> V128.I64x2_convert.extend_low_s v + | Pack64, ExtLane (Pack32x2, ZX) -> V128.I64x2_convert.extend_low_u v + | _, ExtLane _ -> assert false + | Pack8, ExtSplat -> V128.I8x16.splat (I8.of_int_s (Int64.to_int x)) + | Pack16, ExtSplat -> V128.I16x8.splat (I16.of_int_s (Int64.to_int x)) + | Pack32, ExtSplat -> V128.I32x4.splat (I32.of_int_s (Int64.to_int x)) + | Pack64, ExtSplat -> V128.I64x2.splat x + | Pack32, ExtZero -> v + | Pack64, ExtZero -> v + | _, ExtZero -> assert false + in V128 r + + +let rec bits_of_i64 w n = + if w = 0 then "" else + let b = Char.chr (Int64.to_int n land 0xff) in + String.make 1 b ^ bits_of_i64 (w - 1) (Int64.shift_right n 8) + +let bits_of_num n = + let w = num_size (type_of_num n) in + match n with + | I32 x -> bits_of_i64 w (Int64.of_int32 x) + | I64 x -> bits_of_i64 w x + | F32 x -> bits_of_i64 w (Int64.of_int32 (F32.to_bits x)) + | F64 x -> bits_of_i64 w (F64.to_bits x) + +let bits_of_vec v = + match v with + | V128 x -> V128.to_bits x + +let bits_of_val v = + match v with + | Num n -> bits_of_num n + | Vec v -> bits_of_vec v + | Ref _ -> raise Type + +let wrap n x = + let sh = 64 - 8 * n in Int64.(shift_right_logical (shift_left x sh) sh) + +let packed_bits_of_num sz n = + assert (Pack.packed_size sz <= num_size (type_of_num n)); + let w = Pack.packed_size sz in + match n with + | I32 x -> bits_of_i64 w (wrap w (Int64.of_int32 x)) + | I64 x -> bits_of_i64 w (wrap w x) + | _ -> raise Type + +let storage_bits_of_val st v = + match st with + | ValStorageT t -> assert (t = type_of_value v); bits_of_val v + | PackStorageT sz -> + match v with + | Num n -> packed_bits_of_num sz n + | _ -> raise Type + + +(* Conversion *) + +let value_of_bool b = Num (I32 (if b then 1l else 0l)) + +let value_of_index it x = + match it with + | I64IndexType -> Num (I64 x) + | I32IndexType -> Num (I32 (Int64.to_int32 x)) + +let string_of_num = function + | I32 i -> I32.to_string_s i + | I64 i -> I64.to_string_s i + | F32 z -> F32.to_string z + | F64 z -> F64.to_string z + +let hex_string_of_num = function + | I32 i -> I32.to_hex_string i + | I64 i -> I64.to_hex_string i + | F32 z -> F32.to_hex_string z + | F64 z -> F64.to_hex_string z + +let string_of_vec = function + | V128 v -> V128.to_string v + +let hex_string_of_vec = function + | V128 v -> V128.to_hex_string v + +let string_of_ref' = ref (function NullRef t -> "null" | _ -> "ref") +let string_of_ref r = !string_of_ref' r + +let string_of_value = function + | Num n -> string_of_num n + | Vec i -> string_of_vec i + | Ref r -> string_of_ref r + +let string_of_values = function + | [v] -> string_of_value v + | vs -> "[" ^ String.concat " " (List.map string_of_value vs) ^ "]" diff --git a/interpreter/script/import.ml b/interpreter/script/import.ml index c9e65eafcd..1040aaa791 100644 --- a/interpreter/script/import.ml +++ b/interpreter/script/import.ml @@ -1,5 +1,6 @@ open Source open Ast +open Types module Unknown = Error.Make () exception Unknown = Unknown.Error (* indicates unknown import name *) @@ -9,12 +10,12 @@ let registry = ref Registry.empty let register name lookup = registry := Registry.add name lookup !registry -let lookup (m : module_) (im : import) : Instance.extern = - let {module_name; item_name; idesc} = im.it in - let t = import_type m im in - try Registry.find module_name !registry item_name t with Not_found -> - Unknown.error im.at - ("unknown import \"" ^ string_of_name module_name ^ - "\".\"" ^ string_of_name item_name ^ "\"") +let lookup (ImportT (et, module_name, item_name)) at : Instance.extern = + try Registry.find module_name !registry item_name et with Not_found -> + Unknown.error at + ("unknown import \"" ^ Types.string_of_name module_name ^ + "\".\"" ^ Types.string_of_name item_name ^ "\"") -let link m = List.map (lookup m) m.it.imports +let link m = + let ModuleT (its, _) = module_type_of m in + List.map2 lookup its (List.map Source.at m.it.imports) diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index 63c12b40d6..d5aa3a6010 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -1,5 +1,6 @@ open Types open Ast +open Value open Script open Source @@ -10,31 +11,19 @@ let harness = {| 'use strict'; -let externrefs = {}; -let externsym = Symbol("externref"); -function externref(s) { - if (! (s in externrefs)) externrefs[s] = {[externsym]: s}; - return externrefs[s]; +let hostrefs = {}; +let hostsym = Symbol("hostref"); +function hostref(s) { + if (! (s in hostrefs)) hostrefs[s] = {[hostsym]: s}; + return hostrefs[s]; } -function is_externref(x) { - return (x !== null && externsym in x) ? 1 : 0; -} -function is_funcref(x) { - return typeof x === "function" ? 1 : 0; -} -function eq_externref(x, y) { - return x === y ? 1 : 0; -} -function eq_funcref(x, y) { +function eq_ref(x, y) { return x === y ? 1 : 0; } let spectest = { - externref: externref, - is_externref: is_externref, - is_funcref: is_funcref, - eq_externref: eq_externref, - eq_funcref: eq_funcref, + hostref: hostref, + eq_ref: eq_ref, print: console.log.bind(console), print_i32: console.log.bind(console), print_i64: console.log.bind(console), @@ -168,6 +157,21 @@ function assert_return(action, ...expected) { throw new Error("Wasm return value NaN expected, got " + actual[i]); }; return; + case "ref.i31": + if (typeof actual[i] !== "number" || (actual[i] & 0x7fffffff) !== actual[i]) { + throw new Error("Wasm i31 return value expected, got " + actual[i]); + }; + return; + case "ref.any": + case "ref.eq": + case "ref.struct": + case "ref.array": + // For now, JS can't distinguish exported Wasm GC values, + // so we only test for object. + if (typeof actual[i] !== "object") { + throw new Error("Wasm function return value expected, got " + actual[i]); + }; + return; case "ref.func": if (typeof actual[i] !== "function") { throw new Error("Wasm function return value expected, got " + actual[i]); @@ -178,6 +182,11 @@ function assert_return(action, ...expected) { throw new Error("Wasm reference return value expected, got " + actual[i]); }; return; + case "ref.null": + if (actual[i] !== null) { + throw new Error("Wasm null return value expected, got " + actual[i]); + }; + return; default: if (!Object.is(actual[i], expected[i])) { throw new Error("Wasm return value " + expected[i] + " expected, got " + actual[i]); @@ -197,9 +206,9 @@ type exports = extern_type NameMap.t type modules = {mutable env : exports Map.t; mutable current : int} let exports m : exports = - List.fold_left - (fun map exp -> NameMap.add exp.it.name (export_type m exp) map) - NameMap.empty m.it.exports + let ModuleT (_, ets) = module_type_of m in + List.fold_left (fun map (ExportT (et, name)) -> NameMap.add name et map) + NameMap.empty ets let modules () : modules = {env = Map.empty; current = 0} @@ -228,48 +237,46 @@ let lookup (mods : modules) x_opt name at = (* Wrappers *) let subject_idx = 0l -let externref_idx = 1l -let is_externref_idx = 2l -let is_funcref_idx = 3l -let eq_externref_idx = 4l -let _eq_funcref_idx = 5l -let subject_type_idx = 6l +let hostref_idx = 1l +let eq_ref_idx = 2l +let subject_type_idx = 3l let eq_of = function - | I32Type -> Values.I32 I32Op.Eq - | I64Type -> Values.I64 I64Op.Eq - | F32Type -> Values.F32 F32Op.Eq - | F64Type -> Values.F64 F64Op.Eq + | I32T -> I32 I32Op.Eq + | I64T -> I64 I64Op.Eq + | F32T -> F32 F32Op.Eq + | F64T -> F64 F64Op.Eq let and_of = function - | I32Type | F32Type -> Values.I32 I32Op.And - | I64Type | F64Type -> Values.I64 I64Op.And + | I32T | F32T -> I32 I32Op.And + | I64T | F64T -> I64 I64Op.And let reinterpret_of = function - | I32Type -> I32Type, Nop - | I64Type -> I64Type, Nop - | F32Type -> I32Type, Convert (Values.I32 I32Op.ReinterpretFloat) - | F64Type -> I64Type, Convert (Values.I64 I64Op.ReinterpretFloat) + | I32T -> I32T, Nop + | I64T -> I64T, Nop + | F32T -> I32T, Convert (I32 I32Op.ReinterpretFloat) + | F64T -> I64T, Convert (I64 I64Op.ReinterpretFloat) let canonical_nan_of = function - | I32Type | F32Type -> Values.I32 (F32.to_bits F32.pos_nan) - | I64Type | F64Type -> Values.I64 (F64.to_bits F64.pos_nan) + | I32T | F32T -> I32 (F32.to_bits F32.pos_nan) + | I64T | F64T -> I64 (F64.to_bits F64.pos_nan) let abs_mask_of = function - | I32Type | F32Type -> Values.I32 Int32.max_int - | I64Type | F64Type -> Values.I64 Int64.max_int + | I32T | F32T -> I32 Int32.max_int + | I64T | F64T -> I64 Int64.max_int let value v = match v.it with - | Values.Num n -> [Const (n @@ v.at) @@ v.at] - | Values.Vec s -> [VecConst (s @@ v.at) @@ v.at] - | Values.Ref (Values.NullRef t) -> [RefNull t @@ v.at] - | Values.Ref (ExternRef n) -> - [Const (Values.I32 n @@ v.at) @@ v.at; Call (externref_idx @@ v.at) @@ v.at] - | Values.Ref _ -> assert false + | Num n -> [Const (n @@ v.at) @@ v.at] + | Vec s -> [VecConst (s @@ v.at) @@ v.at] + | Ref (NullRef ht) -> [RefNull (Match.bot_of_heap_type [] ht) @@ v.at] + | Ref (Extern.ExternRef (HostRef n)) -> + [Const (I32 n @@ v.at) @@ v.at; Call (hostref_idx @@ v.at) @@ v.at] + | Ref _ -> assert false let invoke ft vs at = - [ft @@ at], FuncImport (subject_type_idx @@ at) @@ at, + let dt = RecT [SubT (Final, [], DefFuncT ft)] in + [dt @@ at], FuncImport (subject_type_idx @@ at) @@ at, List.concat (List.map value vs) @ [Call (subject_idx @@ at) @@ at] let get t at = @@ -278,43 +285,58 @@ let get t at = let run ts at = [], [] +let nan_bitmask_of = function + | CanonicalNan -> abs_mask_of (* differ from canonical NaN in sign bit *) + | ArithmeticNan -> canonical_nan_of (* 1 everywhere canonical NaN is *) + +let type_of_num_pat = function + | NumPat num -> Value.type_of_num num.it + | NanPat op -> Value.type_of_op op.it + +let type_of_vec_pat = function + | VecPat vec -> Value.type_of_vec vec + +let type_of_ref_pat = function + | RefPat ref -> type_of_ref ref.it + | RefTypePat ht -> (NoNull, ht) + | NullPat -> (Null, BotHT) + +let type_of_result res = + match res.it with + | NumResult pat -> NumT (type_of_num_pat pat) + | VecResult pat -> VecT (type_of_vec_pat pat) + | RefResult pat -> RefT (type_of_ref_pat pat) + let assert_return ress ts at = - let test res = - let nan_bitmask_of = function - | CanonicalNan -> abs_mask_of (* must only differ from the canonical NaN in its sign bit *) - | ArithmeticNan -> canonical_nan_of (* can be any NaN that's one everywhere the canonical NaN is one *) - in + let test (res, t) = + if not (Match.match_val_type [] t (type_of_result res)) then + [ Br (0l @@ at) @@ at ] + else match res.it with | NumResult (NumPat {it = num; at = at'}) -> - let t', reinterpret = reinterpret_of (Values.type_of_num num) in + let t', reinterpret = reinterpret_of (Value.type_of_op num) in [ reinterpret @@ at; Const (num @@ at') @@ at; reinterpret @@ at; Compare (eq_of t') @@ at; - Test (Values.I32 I32Op.Eqz) @@ at; + Test (I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] | NumResult (NanPat nanop) -> let nan = match nanop.it with - | Values.I32 _ | Values.I64 _ -> . - | Values.F32 n | Values.F64 n -> n + | Value.I32 _ | Value.I64 _ -> . + | Value.F32 n | Value.F64 n -> n in - let t = Values.type_of_num nanop.it in - let t', reinterpret = reinterpret_of t in + let t', reinterpret = reinterpret_of (Value.type_of_op nanop.it) in [ reinterpret @@ at; Const (nan_bitmask_of nan t' @@ at) @@ at; Binary (and_of t') @@ at; Const (canonical_nan_of t' @@ at) @@ at; Compare (eq_of t') @@ at; - Test (Values.I32 I32Op.Eqz) @@ at; + Test (I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] - | VecResult (VecPat (Values.V128 (shape, pats))) -> - let open Values in - (* VecResult is a list of NumPat or LitPat. For float shapes, we can have a mix of literals - * and NaNs. For NaNs, we need to mask it and compare with a canonical NaN. To simplify - * comparison, we build masks even for literals (will just be all set), collect them into - * a v128, then compare the entire 128 bits. - *) + | VecResult (VecPat (Value.V128 (shape, pats))) -> + let open Value in let mask_and_canonical = function | NumPat {it = I32 _ as i; _} -> I32 (Int32.minus_one), i | NumPat {it = I64 _ as i; _} -> I64 (Int64.minus_one), i @@ -323,13 +345,15 @@ let assert_return ress ts at = | NumPat {it = F64 f; _} -> I64 (Int64.minus_one), I64 (I64_convert.reinterpret_f64 f) | NanPat {it = F32 nan; _} -> - nan_bitmask_of nan I32Type, canonical_nan_of I32Type + nan_bitmask_of nan I32T, canonical_nan_of I32T | NanPat {it = F64 nan; _} -> - nan_bitmask_of nan I64Type, canonical_nan_of I64Type + nan_bitmask_of nan I64T, canonical_nan_of I64T | _ -> . in - let masks, canons = List.split (List.map (fun p -> mask_and_canonical p) pats) in - let all_ones = V128.I32x4.of_lanes (List.init 4 (fun _ -> Int32.minus_one)) in + let masks, canons = + List.split (List.map (fun p -> mask_and_canonical p) pats) in + let all_ones = + V128.I32x4.of_lanes (List.init 4 (fun _ -> Int32.minus_one)) in let mask, expected = match shape with | V128.I8x16 () -> all_ones, V128.I8x16.of_lanes (List.map (I32Num.of_num 0) canons) @@ -354,53 +378,52 @@ let assert_return ress ts at = VecTest (V128 (V128.I8x16 V128Op.AllTrue)) @@ at; Test (I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] - | RefResult (RefPat {it = Values.NullRef t; _}) -> + | RefResult (RefPat {it = NullRef _; _}) -> [ RefIsNull @@ at; - Test (Values.I32 I32Op.Eqz) @@ at; + Test (Value.I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] - | RefResult (RefPat {it = ExternRef n; _}) -> - [ Const (Values.I32 n @@ at) @@ at; - Call (externref_idx @@ at) @@ at; - Call (eq_externref_idx @@ at) @@ at; - Test (Values.I32 I32Op.Eqz) @@ at; + | RefResult (RefPat {it = HostRef n; _}) -> + [ Const (Value.I32 n @@ at) @@ at; + Call (hostref_idx @@ at) @@ at; + Call (eq_ref_idx @@ at) @@ at; + Test (Value.I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] | RefResult (RefPat _) -> assert false + | RefResult (RefTypePat ExternHT) -> + [ BrOnNull (0l @@ at) @@ at ] | RefResult (RefTypePat t) -> - let is_ref_idx = - match t with - | FuncRefType -> is_funcref_idx - | ExternRefType -> is_externref_idx - in - [ Call (is_ref_idx @@ at) @@ at; - Test (Values.I32 I32Op.Eqz) @@ at; + [ RefTest (NoNull, t) @@ at; + Test (I32 I32Op.Eqz) @@ at; BrIf (0l @@ at) @@ at ] - in [], List.flatten (List.rev_map test ress) + | RefResult NullPat -> + [ RefIsNull @@ at; + Test (I32 I32Op.Eqz) @@ at; + BrIf (0l @@ at) @@ at ] + in [], List.flatten (List.rev_map test (List.combine ress ts)) + +let i32 = NumT I32T +let anyref = RefT (Null, AnyHT) +let eqref = RefT (Null, EqHT) +let func_rec_type ts1 ts2 at = + RecT [SubT (Final, [], DefFuncT (FuncT (ts1, ts2)))] @@ at let wrap item_name wrap_action wrap_assertion at = let itypes, idesc, action = wrap_action at in let locals, assertion = wrap_assertion at in let types = - (FuncType ([], []) @@ at) :: - (FuncType ([NumType I32Type], [RefType ExternRefType]) @@ at) :: - (FuncType ([RefType ExternRefType], [NumType I32Type]) @@ at) :: - (FuncType ([RefType FuncRefType], [NumType I32Type]) @@ at) :: - (FuncType ([RefType ExternRefType; RefType ExternRefType], [NumType I32Type]) @@ at) :: - (FuncType ([RefType FuncRefType; RefType FuncRefType], [NumType I32Type]) @@ at) :: + func_rec_type [] [] at :: + func_rec_type [i32] [anyref] at :: + func_rec_type [eqref; eqref] [i32] at :: itypes in let imports = [ {module_name = Utf8.decode "module"; item_name; idesc} @@ at; - {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "externref"; + {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "hostref"; idesc = FuncImport (1l @@ at) @@ at} @@ at; - {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "is_externref"; + {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "eq_ref"; idesc = FuncImport (2l @@ at) @@ at} @@ at; - {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "is_funcref"; - idesc = FuncImport (3l @@ at) @@ at} @@ at; - {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "eq_externref"; - idesc = FuncImport (4l @@ at) @@ at} @@ at; - {module_name = Utf8.decode "spectest"; item_name = Utf8.decode "eq_funcref"; - idesc = FuncImport (5l @@ at) @@ at} @@ at ] + ] in let item = List.fold_left @@ -420,19 +443,26 @@ let wrap item_name wrap_action wrap_assertion at = let is_js_num_type = function - | I32Type -> true - | I64Type | F32Type | F64Type -> false + | I32T -> true + | I64T | F32T | F64T -> false + +let is_js_vec_type = function + | _ -> false + +let is_js_ref_type = function + | _ -> true -let is_js_value_type = function - | NumType t -> is_js_num_type t - | VecType t -> false - | RefType t -> true +let is_js_val_type = function + | NumT t -> is_js_num_type t + | VecT t -> is_js_vec_type t + | RefT t -> is_js_ref_type t + | BotT -> assert false let is_js_global_type = function - | GlobalType (t, mut) -> is_js_value_type t && mut = Immutable + | GlobalT (mut, t) -> is_js_val_type t && mut = Cons let is_js_func_type = function - | FuncType (ins, out) -> List.for_all is_js_value_type (ins @ out) + | FuncT (ts1, ts2) -> List.for_all is_js_val_type (ts1 @ ts2) (* Script conversion *) @@ -470,7 +500,7 @@ let of_float z = | s -> s let of_num n = - let open Values in + let open Value in match n with | I32 i -> I32.to_string_s i | I64 i -> "int64(\"" ^ I64.to_string_s i ^ "\")" @@ -478,19 +508,18 @@ let of_num n = | F64 z -> of_float (F64.to_float z) let of_vec v = - let open Values in + let open Value in match v with | V128 v -> "v128(\"" ^ V128.to_string v ^ "\")" let of_ref r = - let open Values in + let open Value in match r with | NullRef _ -> "null" - | ExternRef n -> "externref(" ^ Int32.to_string n ^ ")" + | HostRef n | Extern.ExternRef (HostRef n) -> "hostref(" ^ Int32.to_string n ^ ")" | _ -> assert false let of_value v = - let open Values in match v.it with | Num n -> of_num n | Vec v -> of_vec v @@ -504,16 +533,17 @@ let of_num_pat = function | NumPat num -> of_num num.it | NanPat nanop -> match nanop.it with - | Values.I32 _ | Values.I64 _ -> . - | Values.F32 n | Values.F64 n -> of_nan n + | Value.I32 _ | Value.I64 _ -> . + | Value.F32 n | Value.F64 n -> of_nan n let of_vec_pat = function - | VecPat (Values.V128 (shape, pats)) -> + | VecPat (Value.V128 (shape, pats)) -> Printf.sprintf "v128(\"%s\")" (String.concat " " (List.map of_num_pat pats)) let of_ref_pat = function | RefPat r -> of_ref r.it - | RefTypePat t -> "\"ref." ^ string_of_refed_type t ^ "\"" + | RefTypePat t -> "\"ref." ^ string_of_heap_type t ^ "\"" + | NullPat -> "\"ref.null\"" let of_result res = match res.it with @@ -542,16 +572,19 @@ let of_action mods act = "call(" ^ of_var_opt mods x_opt ^ ", " ^ of_name name ^ ", " ^ "[" ^ String.concat ", " (List.map of_value vs) ^ "])", (match lookup mods x_opt name act.at with - | ExternFuncType ft when not (is_js_func_type ft) -> - let FuncType (_, out) = ft in - Some (of_wrapper mods x_opt name (invoke ft vs), out) + | ExternFuncT dt -> + let FuncT (_, out) as ft = as_func_str_type (expand_def_type dt) in + if is_js_func_type ft then + None + else + Some (of_wrapper mods x_opt name (invoke ft vs), out) | _ -> None ) | Get (x_opt, name) -> "get(" ^ of_var_opt mods x_opt ^ ", " ^ of_name name ^ ")", (match lookup mods x_opt name act.at with - | ExternGlobalType gt when not (is_js_global_type gt) -> - let GlobalType (t, _) = gt in + | ExternGlobalT gt when not (is_js_global_type gt) -> + let GlobalT (_, t) = gt in Some (of_wrapper mods x_opt name (get gt), [t]) | _ -> None ) diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index 6bcfc6c152..25ff83c060 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -211,73 +211,52 @@ let input_stdin run = (* Printing *) -let print_import m im = - let open Types in - let category, annotation = - match Ast.import_type m im with - | ExternFuncType t -> "func", string_of_func_type t - | ExternTableType t -> "table", string_of_table_type t - | ExternMemoryType t -> "memory", string_of_memory_type t - | ExternGlobalType t -> "global", string_of_global_type t - in - Printf.printf " import %s \"%s\" \"%s\" : %s\n" - category (Ast.string_of_name im.it.Ast.module_name) - (Ast.string_of_name im.it.Ast.item_name) annotation - -let print_export m ex = - let open Types in - let category, annotation = - match Ast.export_type m ex with - | ExternFuncType t -> "func", string_of_func_type t - | ExternTableType t -> "table", string_of_table_type t - | ExternMemoryType t -> "memory", string_of_memory_type t - | ExternGlobalType t -> "global", string_of_global_type t - in - Printf.printf " export %s \"%s\" : %s\n" - category (Ast.string_of_name ex.it.Ast.name) annotation +let indent s = + let lines = List.filter ((<>) "") (String.split_on_char '\n' s) in + String.concat "\n" (List.map ((^) " ") lines) ^ "\n" let print_module x_opt m = - Printf.printf "module%s :\n" - (match x_opt with None -> "" | Some x -> " " ^ x.it); - List.iter (print_import m) m.it.Ast.imports; - List.iter (print_export m) m.it.Ast.exports; - flush_all () + Printf.printf "module%s :\n%s%!" + (match x_opt with None -> "" | Some x -> " " ^ x.it) + (indent (Types.string_of_module_type (Ast.module_type_of m))) let print_values vs = - let ts = List.map Values.type_of_value vs in - Printf.printf "%s : %s\n" - (Values.string_of_values vs) (Types.string_of_value_types ts); - flush_all () + let ts = List.map Value.type_of_value vs in + Printf.printf "%s : %s\n%!" + (Value.string_of_values vs) (Types.string_of_result_type ts) let string_of_nan = function | CanonicalNan -> "nan:canonical" | ArithmeticNan -> "nan:arithmetic" let type_of_result r = + let open Types in match r with - | NumResult (NumPat n) -> Types.NumType (Values.type_of_num n.it) - | NumResult (NanPat n) -> Types.NumType (Values.type_of_num n.it) - | VecResult (VecPat _) -> Types.VecType Types.V128Type - | RefResult (RefPat r) -> Types.RefType (Values.type_of_ref r.it) - | RefResult (RefTypePat t) -> Types.RefType t + | NumResult (NumPat n) -> NumT (Value.type_of_num n.it) + | NumResult (NanPat n) -> NumT (Value.type_of_num n.it) + | VecResult (VecPat v) -> VecT (Value.type_of_vec v) + | RefResult (RefPat r) -> RefT (Value.type_of_ref r.it) + | RefResult (RefTypePat t) -> RefT (NoNull, t) (* assume closed *) + | RefResult (NullPat) -> RefT (Null, ExternHT) let string_of_num_pat (p : num_pat) = match p with - | NumPat n -> Values.string_of_num n.it + | NumPat n -> Value.string_of_num n.it | NanPat nanop -> match nanop.it with - | Values.I32 _ | Values.I64 _ -> assert false - | Values.F32 n | Values.F64 n -> string_of_nan n + | Value.I32 _ | Value.I64 _ -> assert false + | Value.F32 n | Value.F64 n -> string_of_nan n let string_of_vec_pat (p : vec_pat) = match p with - | VecPat (Values.V128 (shape, ns)) -> + | VecPat (Value.V128 (shape, ns)) -> String.concat " " (List.map string_of_num_pat ns) let string_of_ref_pat (p : ref_pat) = match p with - | RefPat r -> Values.string_of_ref r.it - | RefTypePat t -> Types.string_of_refed_type t + | RefPat r -> Value.string_of_ref r.it + | RefTypePat t -> Types.string_of_heap_type t + | NullPat -> "null" let string_of_result r = match r with @@ -291,9 +270,8 @@ let string_of_results = function let print_results rs = let ts = List.map type_of_result rs in - Printf.printf "%s : %s\n" - (string_of_results rs) (Types.string_of_value_types ts); - flush_all () + Printf.printf "%s : %s\n%!" + (string_of_results rs) (Types.string_of_result_type ts) (* Configuration *) @@ -343,27 +321,28 @@ let rec run_definition def : Ast.module_ = let _, def' = Parse.Module.parse_string s in run_definition def' -let run_action act : Values.value list = +let run_action act : Value.t list = match act.it with | Invoke (x_opt, name, vs) -> - trace ("Invoking function \"" ^ Ast.string_of_name name ^ "\"..."); + trace ("Invoking function \"" ^ Types.string_of_name name ^ "\"..."); let inst = lookup_instance x_opt act.at in (match Instance.export inst name with | Some (Instance.ExternFunc f) -> - let Types.FuncType (ins, out) = Func.type_of f in - if List.length vs <> List.length ins then + let Types.FuncT (ts1, _ts2) = + Types.(as_func_str_type (expand_def_type (Func.type_of f))) in + if List.length vs <> List.length ts1 then Script.error act.at "wrong number of arguments"; List.iter2 (fun v t -> - if Values.type_of_value v.it <> t then + if not (Match.match_val_type [] (Value.type_of_value v.it) t) then Script.error v.at "wrong type of argument" - ) vs ins; + ) vs ts1; Eval.invoke f (List.map (fun v -> v.it) vs) | Some _ -> Assert.error act.at "export is not a function" | None -> Assert.error act.at "undefined export" ) | Get (x_opt, name) -> - trace ("Getting global \"" ^ Ast.string_of_name name ^ "\"..."); + trace ("Getting global \"" ^ Types.string_of_name name ^ "\"..."); let inst = lookup_instance x_opt act.at in (match Instance.export inst name with | Some (Instance.ExternGlobal gl) -> [Global.load gl] @@ -371,9 +350,8 @@ let run_action act : Values.value list = | None -> Assert.error act.at "undefined export" ) - let assert_nan_pat n nan = - let open Values in + let open Value in match n, nan.it with | F32 z, F32 CanonicalNan -> z = F32.pos_nan || z = F32.neg_nan | F64 z, F64 CanonicalNan -> z = F64.pos_nan || z = F64.neg_nan @@ -391,7 +369,7 @@ let assert_num_pat n np = | NanPat nanop -> assert_nan_pat n nanop let assert_vec_pat v p = - let open Values in + let open Value in match v, p with | V128 v, VecPat (V128 (shape, ps)) -> let extract = match shape with @@ -406,14 +384,21 @@ let assert_vec_pat v p = (List.init (V128.num_lanes shape) (extract v)) ps let assert_ref_pat r p = - match r, p with - | r, RefPat r' -> r = r'.it - | Instance.FuncRef _, RefTypePat Types.FuncRefType - | ExternRef _, RefTypePat Types.ExternRefType -> true + match p, r with + | RefPat r', r -> Value.eq_ref r r'.it + | RefTypePat Types.AnyHT, Instance.FuncRef _ -> false + | RefTypePat Types.AnyHT, _ + | RefTypePat Types.EqHT, (I31.I31Ref _ | Aggr.StructRef _ | Aggr.ArrayRef _) + | RefTypePat Types.I31HT, I31.I31Ref _ + | RefTypePat Types.StructHT, Aggr.StructRef _ + | RefTypePat Types.ArrayHT, Aggr.ArrayRef _ -> true + | RefTypePat Types.FuncHT, Instance.FuncRef _ + | RefTypePat Types.ExternHT, _ -> true + | NullPat, Value.NullRef _ -> true | _ -> false let assert_pat v r = - let open Values in + let open Value in match v, r with | Num n, NumResult np -> assert_num_pat n np | Vec v, VecResult vp -> assert_vec_pat v vp @@ -533,7 +518,7 @@ let rec run_command cmd = | Register (name, x_opt) -> quote := cmd :: !quote; if not !Flags.dry then begin - trace ("Registering module \"" ^ Ast.string_of_name name ^ "\"..."); + trace ("Registering module \"" ^ Types.string_of_name name ^ "\"..."); let inst = lookup_instance x_opt cmd.at in registry := Map.add (Utf8.encode name) inst !registry; Import.register name (lookup_registry (Utf8.encode name)) diff --git a/interpreter/script/script.ml b/interpreter/script/script.ml index 93a6652591..431c9923a8 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -1,9 +1,9 @@ type var = string Source.phrase -type Values.ref_ += ExternRef of int32 -type num = Values.num Source.phrase -type ref_ = Values.ref_ Source.phrase -type literal = Values.value Source.phrase +type Value.ref_ += HostRef of int32 +type num = Value.num Source.phrase +type ref_ = Value.ref_ Source.phrase +type literal = Value.t Source.phrase type definition = definition' Source.phrase and definition' = @@ -17,7 +17,7 @@ and action' = | Get of var option * Ast.name type nanop = nanop' Source.phrase -and nanop' = (Lib.void, Lib.void, nan, nan) Values.op +and nanop' = (Lib.void, Lib.void, nan, nan) Value.op and nan = CanonicalNan | ArithmeticNan type num_pat = @@ -25,11 +25,12 @@ type num_pat = | NanPat of nanop type vec_pat = - | VecPat of (V128.shape * num_pat list) Values.vecop + | VecPat of (V128.shape * num_pat list) Value.vecop type ref_pat = | RefPat of ref_ - | RefTypePat of Types.ref_type + | RefTypePat of Types.heap_type + | NullPat type result = result' Source.phrase and result' = @@ -65,20 +66,20 @@ and script = command list let () = - let type_of_ref' = !Values.type_of_ref' in - Values.type_of_ref' := function - | ExternRef _ -> Types.ExternRefType + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | HostRef _ -> Types.AnyHT | r -> type_of_ref' r let () = - let string_of_ref' = !Values.string_of_ref' in - Values.string_of_ref' := function - | ExternRef n -> "ref " ^ Int32.to_string n + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function + | HostRef n -> "(host " ^ Int32.to_string n ^ ")" | r -> string_of_ref' r let () = - let eq_ref' = !Values.eq_ref' in - Values.eq_ref' := fun r1 r2 -> + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> match r1, r2 with - | ExternRef n1, ExternRef n2 -> n1 = n2 + | HostRef n1, HostRef n2 -> n1 = n2 | _, _ -> eq_ref' r1 r2 diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index e0cfc9e72c..f86ceba991 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -3,20 +3,23 @@ * syntactic elements, associated with the types defined here and in a few * other places: * - * x : var + * x : idx * v : value * e : instr * f : func * m : module_ * - * t : value_type + * t : val_type * s : func_type * c : context / config * * These conventions mostly follow standard practice in language semantics. *) +(* Types *) + open Types +open Pack type void = Lib.void @@ -95,26 +98,26 @@ struct type replaceop = (nreplaceop, nreplaceop, nreplaceop, nreplaceop, nreplaceop, nreplaceop) V128.laneop end -type testop = (I32Op.testop, I64Op.testop, F32Op.testop, F64Op.testop) Values.op -type unop = (I32Op.unop, I64Op.unop, F32Op.unop, F64Op.unop) Values.op -type binop = (I32Op.binop, I64Op.binop, F32Op.binop, F64Op.binop) Values.op -type relop = (I32Op.relop, I64Op.relop, F32Op.relop, F64Op.relop) Values.op -type cvtop = (I32Op.cvtop, I64Op.cvtop, F32Op.cvtop, F64Op.cvtop) Values.op - -type vec_testop = (V128Op.testop) Values.vecop -type vec_relop = (V128Op.relop) Values.vecop -type vec_unop = (V128Op.unop) Values.vecop -type vec_binop = (V128Op.binop) Values.vecop -type vec_cvtop = (V128Op.cvtop) Values.vecop -type vec_shiftop = (V128Op.shiftop) Values.vecop -type vec_bitmaskop = (V128Op.bitmaskop) Values.vecop -type vec_vtestop = (V128Op.vtestop) Values.vecop -type vec_vunop = (V128Op.vunop) Values.vecop -type vec_vbinop = (V128Op.vbinop) Values.vecop -type vec_vternop = (V128Op.vternop) Values.vecop -type vec_splatop = (V128Op.splatop) Values.vecop -type vec_extractop = (V128Op.extractop) Values.vecop -type vec_replaceop = (V128Op.replaceop) Values.vecop +type testop = (I32Op.testop, I64Op.testop, F32Op.testop, F64Op.testop) Value.op +type unop = (I32Op.unop, I64Op.unop, F32Op.unop, F64Op.unop) Value.op +type binop = (I32Op.binop, I64Op.binop, F32Op.binop, F64Op.binop) Value.op +type relop = (I32Op.relop, I64Op.relop, F32Op.relop, F64Op.relop) Value.op +type cvtop = (I32Op.cvtop, I64Op.cvtop, F32Op.cvtop, F64Op.cvtop) Value.op + +type vec_testop = (V128Op.testop) Value.vecop +type vec_relop = (V128Op.relop) Value.vecop +type vec_unop = (V128Op.unop) Value.vecop +type vec_binop = (V128Op.binop) Value.vecop +type vec_cvtop = (V128Op.cvtop) Value.vecop +type vec_shiftop = (V128Op.shiftop) Value.vecop +type vec_bitmaskop = (V128Op.bitmaskop) Value.vecop +type vec_vtestop = (V128Op.vtestop) Value.vecop +type vec_vunop = (V128Op.vunop) Value.vecop +type vec_vbinop = (V128Op.vbinop) Value.vecop +type vec_vternop = (V128Op.vternop) Value.vecop +type vec_splatop = (V128Op.splatop) Value.vecop +type vec_extractop = (V128Op.extractop) Value.vecop +type vec_replaceop = (V128Op.replaceop) Value.vecop type ('t, 'p) memop = {ty : 't; align : int; offset : int64; pack : 'p} type loadop = (num_type, (pack_size * extension) option) memop @@ -122,67 +125,99 @@ type storeop = (num_type, pack_size option) memop type vec_loadop = (vec_type, (pack_size * vec_extension) option) memop type vec_storeop = (vec_type, unit) memop -type vec_laneop = (vec_type, pack_size) memop * int +type vec_laneop = (vec_type, pack_size) memop + +type initop = Explicit | Implicit +type externop = Internalize | Externalize (* Expressions *) -type var = int32 Source.phrase -type num = Values.num Source.phrase -type vec = Values.vec Source.phrase +type idx = int32 Source.phrase +type num = Value.num Source.phrase +type vec = Value.vec Source.phrase type name = Utf8.unicode -type block_type = VarBlockType of var | ValBlockType of value_type option +type block_type = VarBlockType of idx | ValBlockType of val_type option type instr = instr' Source.phrase and instr' = | Unreachable (* trap unconditionally *) | Nop (* do nothing *) | Drop (* forget a value *) - | Select of value_type list option (* branchless conditional *) + | Select of val_type list option (* branchless conditional *) | Block of block_type * instr list (* execute in sequence *) | Loop of block_type * instr list (* loop header *) - | If of block_type * instr list * instr list (* conditional *) - | Br of var (* break to n-th surrounding label *) - | BrIf of var (* conditional break *) - | BrTable of var list * var (* indexed break *) + | If of block_type * instr list * instr list (* conditional *) + | Br of idx (* break to n-th surrounding label *) + | BrIf of idx (* conditional break *) + | BrTable of idx list * idx (* indexed break *) + | BrOnNull of idx (* break on type *) + | BrOnNonNull of idx (* break on type inverted *) + | BrOnCast of idx * ref_type * ref_type (* break on type *) + | BrOnCastFail of idx * ref_type * ref_type (* break on type inverted *) | Return (* break from function body *) - | Call of var (* call function *) - | CallIndirect of var * var (* call function through table *) - | LocalGet of var (* read local variable *) - | LocalSet of var (* write local variable *) - | LocalTee of var (* write local variable and keep value *) - | GlobalGet of var (* read global variable *) - | GlobalSet of var (* write global variable *) - | TableGet of var (* read table element *) - | TableSet of var (* write table element *) - | TableSize of var (* size of table *) - | TableGrow of var (* grow table *) - | TableFill of var (* fill table range with value *) - | TableCopy of var * var (* copy table range *) - | TableInit of var * var (* initialize table range from segment *) - | ElemDrop of var (* drop passive element segment *) - | Load of loadop (* read memory at address *) - | Store of storeop (* write memory at address *) - | VecLoad of vec_loadop (* read memory at address *) - | VecStore of vec_storeop (* write memory at address *) - | VecLoadLane of vec_laneop (* read single lane at address *) - | VecStoreLane of vec_laneop (* write single lane to address *) - | MemorySize (* size of memory *) - | MemoryGrow (* grow memory *) - | MemoryFill (* fill memory range with value *) - | MemoryCopy (* copy memory ranges *) - | MemoryInit of var (* initialize memory range from segment *) - | DataDrop of var (* drop passive data segment *) - | RefNull of ref_type (* null reference *) - | RefFunc of var (* function reference *) - | RefIsNull (* null test *) + | Call of idx (* call function *) + | CallRef of idx (* call function through reference *) + | CallIndirect of idx * idx (* call function through table *) + | ReturnCall of idx (* tail-call function *) + | ReturnCallRef of idx (* tail call through reference *) + | ReturnCallIndirect of idx * idx (* tail-call function through table *) + | LocalGet of idx (* read local idxiable *) + | LocalSet of idx (* write local idxiable *) + | LocalTee of idx (* write local idxiable and keep value *) + | GlobalGet of idx (* read global idxiable *) + | GlobalSet of idx (* write global idxiable *) + | TableGet of idx (* read table element *) + | TableSet of idx (* write table element *) + | TableSize of idx (* size of table *) + | TableGrow of idx (* grow table *) + | TableFill of idx (* fill table with unique value *) + | TableCopy of idx * idx (* copy table range *) + | TableInit of idx * idx (* initialize table range from segment *) + | ElemDrop of idx (* drop passive element segment *) + | Load of idx * loadop (* read memory at address *) + | Store of idx * storeop (* write memory at address *) + | VecLoad of idx * vec_loadop (* read memory at address *) + | VecStore of idx * vec_storeop (* write memory at address *) + | VecLoadLane of idx * vec_laneop * int (* read single lane at address *) + | VecStoreLane of idx * vec_laneop * int (* write single lane to address *) + | MemorySize of idx (* size of memory *) + | MemoryGrow of idx (* grow memory *) + | MemoryFill of idx (* fill memory range with value *) + | MemoryCopy of idx * idx (* copy memory ranges *) + | MemoryInit of idx * idx (* initialize memory range from segment *) + | DataDrop of idx (* drop passive data segment *) | Const of num (* constant *) | Test of testop (* numeric test *) | Compare of relop (* numeric comparison *) | Unary of unop (* unary numeric operator *) | Binary of binop (* binary numeric operator *) | Convert of cvtop (* conversion *) + | RefNull of heap_type (* null reference *) + | RefFunc of idx (* function reference *) + | RefIsNull (* type test *) + | RefAsNonNull (* type cast *) + | RefTest of ref_type (* type test *) + | RefCast of ref_type (* type cast *) + | RefEq (* reference equality *) + | RefI31 (* scalar reference *) + | I31Get of extension (* read scalar *) + | StructNew of idx * initop (* allocate structure *) + | StructGet of idx * idx * extension option (* read structure field *) + | StructSet of idx * idx (* write structure field *) + | ArrayNew of idx * initop (* allocate array *) + | ArrayNewFixed of idx * int32 (* allocate fixed array *) + | ArrayNewElem of idx * idx (* allocate array from element segment *) + | ArrayNewData of idx * idx (* allocate array from data segment *) + | ArrayGet of idx * extension option (* read array slot *) + | ArraySet of idx (* write array slot *) + | ArrayLen (* read array length *) + | ArrayCopy of idx * idx (* copy between two arrays *) + | ArrayFill of idx (* fill array with value *) + | ArrayInitData of idx * idx (* fill array from data segment *) + | ArrayInitElem of idx * idx (* fill array from elem segment *) + | ExternConvert of externop (* extern conversion *) | VecConst of vec (* constant *) | VecTest of vec_testop (* vector test *) | VecCompare of vec_relop (* vector comparison *) @@ -200,10 +235,16 @@ and instr' = | VecReplace of vec_replaceop (* replace lane in vector *) -(* Globals & Functions *) +(* Locals, globals & Functions *) type const = instr list Source.phrase +type local = local' Source.phrase +and local' = +{ + ltype : val_type; +} + type global = global' Source.phrase and global' = { @@ -214,8 +255,8 @@ and global' = type func = func' Source.phrase and func' = { - ftype : var; - locals : value_type list; + ftype : idx; + locals : local list; body : instr list; } @@ -226,6 +267,7 @@ type table = table' Source.phrase and table' = { ttype : table_type; + tinit : const; } type memory = memory' Source.phrase @@ -237,7 +279,7 @@ and memory' = type segment_mode = segment_mode' Source.phrase and segment_mode' = | Passive - | Active of {index : var; offset : const} + | Active of {index : idx; offset : const} | Declarative type elem_segment = elem_segment' Source.phrase @@ -258,14 +300,14 @@ and data_segment' = (* Modules *) -type type_ = func_type Source.phrase +type type_ = rec_type Source.phrase type export_desc = export_desc' Source.phrase and export_desc' = - | FuncExport of var - | TableExport of var - | MemoryExport of var - | GlobalExport of var + | FuncExport of idx + | TableExport of idx + | MemoryExport of idx + | GlobalExport of idx type export = export' Source.phrase and export' = @@ -276,7 +318,7 @@ and export' = type import_desc = import_desc' Source.phrase and import_desc' = - | FuncImport of var + | FuncImport of idx | TableImport of table_type | MemoryImport of memory_type | GlobalImport of global_type @@ -292,7 +334,7 @@ and import' = type start = start' Source.phrase and start' = { - sfunc : var; + sfunc : idx; } type module_ = module_' Source.phrase @@ -329,47 +371,47 @@ let empty_module = open Source -let func_type_for (m : module_) (x : var) : func_type = - (Lib.List32.nth m.it.types x.it).it - -let import_type (m : module_) (im : import) : extern_type = - let {idesc; _} = im.it in - match idesc.it with - | FuncImport x -> ExternFuncType (func_type_for m x) - | TableImport t -> ExternTableType t - | MemoryImport t -> ExternMemoryType t - | GlobalImport t -> ExternGlobalType t - -let export_type (m : module_) (ex : export) : extern_type = - let {edesc; _} = ex.it in - let its = List.map (import_type m) m.it.imports in - let open Lib.List32 in - match edesc.it with - | FuncExport x -> - let fts = - funcs its @ List.map (fun f -> func_type_for m f.it.ftype) m.it.funcs - in ExternFuncType (nth fts x.it) - | TableExport x -> - let tts = tables its @ List.map (fun t -> t.it.ttype) m.it.tables in - ExternTableType (nth tts x.it) - | MemoryExport x -> - let mts = memories its @ List.map (fun m -> m.it.mtype) m.it.memories in - ExternMemoryType (nth mts x.it) - | GlobalExport x -> - let gts = globals its @ List.map (fun g -> g.it.gtype) m.it.globals in - ExternGlobalType (nth gts x.it) - -let string_of_name n = - let b = Buffer.create 16 in - let escape uc = - if uc < 0x20 || uc >= 0x7f then - Buffer.add_string b (Printf.sprintf "\\u{%02x}" uc) - else begin - let c = Char.chr uc in - if c = '\"' || c = '\\' then Buffer.add_char b '\\'; - Buffer.add_char b c - end - in - List.iter escape n; - Buffer.contents b - +let def_types_of (m : module_) : def_type list = + let rts = List.map Source.it m.it.types in + List.fold_left (fun dts rt -> + let x = Lib.List32.length dts in + dts @ List.map (subst_def_type (subst_of dts)) (roll_def_types x rt) + ) [] rts + +let import_type_of (m : module_) (im : import) : import_type = + let {idesc; module_name; item_name} = im.it in + let dts = def_types_of m in + let et = + match idesc.it with + | FuncImport x -> ExternFuncT (Lib.List32.nth dts x.it) + | TableImport tt -> ExternTableT tt + | MemoryImport mt -> ExternMemoryT mt + | GlobalImport gt -> ExternGlobalT gt + in ImportT (subst_extern_type (subst_of dts) et, module_name, item_name) + +let export_type_of (m : module_) (ex : export) : export_type = + let {edesc; name} = ex.it in + let dts = def_types_of m in + let its = List.map (import_type_of m) m.it.imports in + let ets = List.map extern_type_of_import_type its in + let et = + match edesc.it with + | FuncExport x -> + let dts = funcs ets @ List.map (fun f -> + Lib.List32.nth dts f.it.ftype.it) m.it.funcs in + ExternFuncT (Lib.List32.nth dts x.it) + | TableExport x -> + let tts = tables ets @ List.map (fun t -> t.it.ttype) m.it.tables in + ExternTableT (Lib.List32.nth tts x.it) + | MemoryExport x -> + let mts = memories ets @ List.map (fun m -> m.it.mtype) m.it.memories in + ExternMemoryT (Lib.List32.nth mts x.it) + | GlobalExport x -> + let gts = globals ets @ List.map (fun g -> g.it.gtype) m.it.globals in + ExternGlobalT (Lib.List32.nth gts x.it) + in ExportT (subst_extern_type (subst_of dts) et, name) + +let module_type_of (m : module_) : module_type = + let its = List.map (import_type_of m) m.it.imports in + let ets = List.map (export_type_of m) m.it.exports in + ModuleT (its, ets) diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index 8e1a37a458..a86615df12 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -1,5 +1,6 @@ open Source open Ast +open Types module Set = Set.Make(Int32) @@ -52,64 +53,150 @@ let datas s = {empty with datas = s} let locals s = {empty with locals = s} let labels s = {empty with labels = s} -let var x = Set.singleton x.it -let zero = Set.singleton 0l +let idx' x' = Set.singleton x' +let idx x = Set.singleton x.it let shift s = Set.map (Int32.add (-1l)) (Set.remove 0l s) let (++) = union +let opt free xo = Lib.Option.get (Option.map free xo) empty let list free xs = List.fold_left union empty (List.map free xs) -let opt free xo = Lib.Option.get (Lib.Option.map free xo) empty + +let var_type = function + | StatX x -> types (idx' x) + | RecX _ -> empty + +let num_type = function + | I32T | I64T | F32T | F64T -> empty + +let vec_type = function + | V128T -> empty + +let heap_type = function + | AnyHT | NoneHT | EqHT + | I31HT | StructHT | ArrayHT -> empty + | FuncHT | NoFuncHT -> empty + | ExternHT | NoExternHT -> empty + | VarHT x -> var_type x + | DefHT _ct -> empty (* assume closed *) + | BotHT -> empty + +let ref_type = function + | (_, t) -> heap_type t + +let val_type = function + | NumT t -> num_type t + | VecT t -> vec_type t + | RefT t -> ref_type t + | BotT -> empty + +let pack_type t = empty + +let storage_type = function + | ValStorageT t -> val_type t + | PackStorageT t -> pack_type t + +let field_type (FieldT (_mut, st)) = storage_type st + +let struct_type (StructT fts) = list field_type fts +let array_type (ArrayT ft) = field_type ft +let func_type (FuncT (ts1, ts2)) = list val_type ts1 ++ list val_type ts2 + +let str_type = function + | DefStructT st -> struct_type st + | DefArrayT at -> array_type at + | DefFuncT ft -> func_type ft + +let sub_type = function + | SubT (_fin, hts, st) -> list heap_type hts ++ str_type st + +let rec_type = function + | RecT sts -> list sub_type sts + +let def_type = function + | DefT (rt, _i) -> rec_type rt + +let global_type (GlobalT (_mut, t)) = val_type t +let table_type (TableT (_lim, _it, t)) = ref_type t +let memory_type (MemoryT (_lim, _it)) = empty + +let extern_type = function + | ExternFuncT dt -> def_type dt + | ExternTableT tt -> table_type tt + | ExternMemoryT mt -> memory_type mt + | ExternGlobalT gt -> global_type gt let block_type = function - | VarBlockType x -> types (var x) - | ValBlockType _ -> empty + | VarBlockType x -> types (idx x) + | ValBlockType t -> opt val_type t let rec instr (e : instr) = match e.it with - | Unreachable | Nop | Drop | Select _ -> empty - | RefNull _ | RefIsNull -> empty - | RefFunc x -> funcs (var x) + | Unreachable | Nop | Drop -> empty + | Select tso -> list val_type (Lib.Option.get tso []) + | RefIsNull | RefAsNonNull -> empty + | RefTest t | RefCast t -> ref_type t + | RefEq -> empty + | RefNull t -> heap_type t + | RefFunc x -> funcs (idx x) + | RefI31 | I31Get _ -> empty + | StructNew (x, _) | ArrayNew (x, _) | ArrayNewFixed (x, _) -> types (idx x) + | ArrayNewElem (x, y) -> types (idx x) ++ elems (idx y) + | ArrayNewData (x, y) -> types (idx x) ++ datas (idx y) + | StructGet (x, _, _) | StructSet (x, _) -> types (idx x) + | ArrayGet (x, _) | ArraySet x -> types (idx x) + | ArrayLen -> empty + | ArrayCopy (x, y) -> types (idx x) ++ types (idx y) + | ArrayFill x -> types (idx x) + | ArrayInitData (x, y) -> types (idx x) ++ datas (idx y) + | ArrayInitElem (x, y) -> types (idx x) ++ elems (idx y) + | ExternConvert _ -> empty | Const _ | Test _ | Compare _ | Unary _ | Binary _ | Convert _ -> empty | Block (bt, es) | Loop (bt, es) -> block_type bt ++ block es | If (bt, es1, es2) -> block_type bt ++ block es1 ++ block es2 - | Br x | BrIf x -> labels (var x) - | BrTable (xs, x) -> list (fun x -> labels (var x)) (x::xs) + | Br x | BrIf x | BrOnNull x | BrOnNonNull x -> labels (idx x) + | BrOnCast (x, t1, t2) | BrOnCastFail (x, t1, t2) -> + labels (idx x) ++ ref_type t1 ++ ref_type t2 + | BrTable (xs, x) -> list (fun x -> labels (idx x)) (x::xs) | Return -> empty - | Call x -> funcs (var x) - | CallIndirect (x, y) -> tables (var x) ++ types (var y) - | LocalGet x | LocalSet x | LocalTee x -> locals (var x) - | GlobalGet x | GlobalSet x -> globals (var x) + | Call x | ReturnCall x -> funcs (idx x) + | CallRef x | ReturnCallRef x -> types (idx x) + | CallIndirect (x, y) | ReturnCallIndirect (x, y) -> + tables (idx x) ++ types (idx y) + | LocalGet x | LocalSet x | LocalTee x -> locals (idx x) + | GlobalGet x | GlobalSet x -> globals (idx x) | TableGet x | TableSet x | TableSize x | TableGrow x | TableFill x -> - tables (var x) - | TableCopy (x, y) -> tables (var x) ++ tables (var y) - | TableInit (x, y) -> tables (var x) ++ elems (var y) - | ElemDrop x -> elems (var x) - | Load _ | Store _ - | VecLoad _ | VecStore _ | VecLoadLane _ | VecStoreLane _ - | MemorySize | MemoryGrow | MemoryCopy | MemoryFill -> - memories zero + tables (idx x) + | TableCopy (x, y) -> tables (idx x) ++ tables (idx y) + | TableInit (x, y) -> tables (idx x) ++ elems (idx y) + | ElemDrop x -> elems (idx x) + | Load (x, _) | Store (x, _) | VecLoad (x, _) | VecStore (x, _) + | VecLoadLane (x, _, _) | VecStoreLane (x, _, _) + | MemorySize x | MemoryGrow x | MemoryFill x -> + memories (idx x) + | MemoryCopy (x, y) -> memories (idx x) ++ memories (idx y) + | MemoryInit (x, y) -> memories (idx x) ++ datas (idx y) + | DataDrop x -> datas (idx x) | VecConst _ | VecTest _ | VecUnary _ | VecBinary _ | VecCompare _ | VecConvert _ | VecShift _ | VecBitmask _ | VecTestBits _ | VecUnaryBits _ | VecBinaryBits _ | VecTernaryBits _ | VecSplat _ | VecExtract _ | VecReplace _ -> - memories zero - | MemoryInit x -> memories zero ++ datas (var x) - | DataDrop x -> datas (var x) + empty and block (es : instr list) = let free = list instr es in {free with labels = shift free.labels} let const (c : const) = block c.it -let global (g : global) = const g.it.ginit -let func (f : func) = {(block f.it.body) with locals = Set.empty} -let table (t : table) = empty -let memory (m : memory) = empty +let global (g : global) = global_type g.it.gtype ++ const g.it.ginit +let func (f : func) = + {(types (idx f.it.ftype) ++ block f.it.body) with locals = Set.empty} +let table (t : table) = table_type t.it.ttype ++ const t.it.tinit +let memory (m : memory) = memory_type m.it.mtype let segment_mode f (m : segment_mode) = match m.it with | Passive | Declarative -> empty - | Active {index; offset} -> f (var index) ++ const offset + | Active {index; offset} -> f (idx index) ++ const offset let elem (s : elem_segment) = list const s.it.einit ++ segment_mode tables s.it.emode @@ -117,26 +204,26 @@ let elem (s : elem_segment) = let data (s : data_segment) = segment_mode memories s.it.dmode -let type_ (t : type_) = empty +let type_ (t : type_) = rec_type t.it let export_desc (d : export_desc) = match d.it with - | FuncExport x -> funcs (var x) - | TableExport x -> tables (var x) - | MemoryExport x -> memories (var x) - | GlobalExport x -> globals (var x) + | FuncExport x -> funcs (idx x) + | TableExport x -> tables (idx x) + | MemoryExport x -> memories (idx x) + | GlobalExport x -> globals (idx x) let import_desc (d : import_desc) = match d.it with - | FuncImport x -> types (var x) - | TableImport tt -> empty - | MemoryImport mt -> empty - | GlobalImport gt -> empty + | FuncImport x -> types (idx x) + | TableImport tt -> table_type tt + | MemoryImport mt -> memory_type mt + | GlobalImport gt -> global_type gt let export (e : export) = export_desc e.it.edesc let import (i : import) = import_desc i.it.idesc -let start (s : start) = funcs (var s.it.sfunc) +let start (s : start) = funcs (idx s.it.sfunc) let module_ (m : module_) = list type_ m.it.types ++ diff --git a/interpreter/syntax/free.mli b/interpreter/syntax/free.mli index 32f57b96c9..11490dd367 100644 --- a/interpreter/syntax/free.mli +++ b/interpreter/syntax/free.mli @@ -16,6 +16,22 @@ type t = val empty : t val union : t -> t -> t +val num_type : Types.num_type -> t +val vec_type : Types.vec_type -> t +val ref_type : Types.ref_type -> t +val val_type : Types.val_type -> t + +val func_type : Types.func_type -> t +val global_type : Types.global_type -> t +val table_type : Types.table_type -> t +val memory_type : Types.memory_type -> t +val extern_type : Types.extern_type -> t + +val str_type : Types.str_type -> t +val sub_type : Types.sub_type -> t +val rec_type : Types.rec_type -> t +val def_type : Types.def_type -> t + val instr : Ast.instr -> t val block : Ast.instr list -> t val const : Ast.const -> t @@ -33,4 +49,5 @@ val start : Ast.start -> t val module_ : Ast.module_ -> t +val opt : ('a -> t) -> 'a option -> t val list : ('a -> t) -> 'a list -> t diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 296bb1e77e..e7fcba278b 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -1,9 +1,11 @@ open Source -open Types -open Values +open Value open V128 open Ast +open Types +open Pack +(* Instructions *) let i32_const n = Const (I32 n.it @@ n.at) let i64_const n = Const (I64 n.it @@ n.at) @@ -17,16 +19,26 @@ let unreachable = Unreachable let nop = Nop let drop = Drop let select t = Select t + let block bt es = Block (bt, es) let loop bt es = Loop (bt, es) let if_ bt es1 es2 = If (bt, es1, es2) + let br x = Br x let br_if x = BrIf x let br_table xs x = BrTable (xs, x) +let br_on_null x = BrOnNull x +let br_on_non_null x = BrOnNonNull x +let br_on_cast x t1 t2 = BrOnCast (x, t1, t2) +let br_on_cast_fail x t1 t2 = BrOnCastFail (x, t1, t2) let return = Return let call x = Call x +let call_ref x = CallRef x let call_indirect x y = CallIndirect (x, y) +let return_call x = ReturnCall x +let return_call_ref x = ReturnCallRef x +let return_call_indirect x y = ReturnCallIndirect (x, y) let local_get x = LocalGet x let local_set x = LocalSet x @@ -43,54 +55,139 @@ let table_copy x y = TableCopy (x, y) let table_init x y = TableInit (x, y) let elem_drop x = ElemDrop x -let i32_load align offset = Load {ty = I32Type; align; offset; pack = None} -let i64_load align offset = Load {ty = I64Type; align; offset; pack = None} -let f32_load align offset = Load {ty = F32Type; align; offset; pack = None} -let f64_load align offset = Load {ty = F64Type; align; offset; pack = None} -let i32_load8_s align offset = - Load {ty = I32Type; align; offset; pack = Some (Pack8, SX)} -let i32_load8_u align offset = - Load {ty = I32Type; align; offset; pack = Some (Pack8, ZX)} -let i32_load16_s align offset = - Load {ty = I32Type; align; offset; pack = Some (Pack16, SX)} -let i32_load16_u align offset = - Load {ty = I32Type; align; offset; pack = Some (Pack16, ZX)} -let i64_load8_s align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack8, SX)} -let i64_load8_u align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack8, ZX)} -let i64_load16_s align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack16, SX)} -let i64_load16_u align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack16, ZX)} -let i64_load32_s align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack32, SX)} -let i64_load32_u align offset = - Load {ty = I64Type; align; offset; pack = Some (Pack32, ZX)} +let i32_load x align offset = + Load (x, {ty = I32T; align; offset; pack = None}) +let i64_load x align offset = + Load (x, {ty = I64T; align; offset; pack = None}) +let f32_load x align offset = + Load (x, {ty = F32T; align; offset; pack = None}) +let f64_load x align offset = + Load (x, {ty = F64T; align; offset; pack = None}) +let i32_load8_s x align offset = + Load (x, {ty = I32T; align; offset; pack = Some (Pack8, SX)}) +let i32_load8_u x align offset = + Load (x, {ty = I32T; align; offset; pack = Some (Pack8, ZX)}) +let i32_load16_s x align offset = + Load (x, {ty = I32T; align; offset; pack = Some (Pack16, SX)}) +let i32_load16_u x align offset = + Load (x, {ty = I32T; align; offset; pack = Some (Pack16, ZX)}) +let i64_load8_s x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack8, SX)}) +let i64_load8_u x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack8, ZX)}) +let i64_load16_s x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack16, SX)}) +let i64_load16_u x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack16, ZX)}) +let i64_load32_s x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack32, SX)}) +let i64_load32_u x align offset = + Load (x, {ty = I64T; align; offset; pack = Some (Pack32, ZX)}) + +let i32_store x align offset = + Store (x, {ty = I32T; align; offset; pack = None}) +let i64_store x align offset = + Store (x, {ty = I64T; align; offset; pack = None}) +let f32_store x align offset = + Store (x, {ty = F32T; align; offset; pack = None}) +let f64_store x align offset = + Store (x, {ty = F64T; align; offset; pack = None}) +let i32_store8 x align offset = + Store (x, {ty = I32T; align; offset; pack = Some Pack8}) +let i32_store16 x align offset = + Store (x, {ty = I32T; align; offset; pack = Some Pack16}) +let i64_store8 x align offset = + Store (x, {ty = I64T; align; offset; pack = Some Pack8}) +let i64_store16 x align offset = + Store (x, {ty = I64T; align; offset; pack = Some Pack16}) +let i64_store32 x align offset = + Store (x, {ty = I64T; align; offset; pack = Some Pack32}) + +let v128_load x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = None}) +let v128_load8x8_s x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack8x8, SX))}) +let v128_load8x8_u x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack8x8, ZX))}) +let v128_load16x4_s x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack16x4, SX))}) +let v128_load16x4_u x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack16x4, ZX))}) +let v128_load32x2_s x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack32x2, SX))}) +let v128_load32x2_u x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtLane (Pack32x2, ZX))}) +let v128_load8_splat x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack8, ExtSplat)}) +let v128_load16_splat x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack16, ExtSplat)}) +let v128_load32_splat x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack32, ExtSplat)}) +let v128_load64_splat x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtSplat)}) +let v128_load32_zero x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack32, ExtZero)}) +let v128_load64_zero x align offset = + VecLoad (x, {ty = V128T; align; offset; pack = Some (Pack64, ExtZero)}) +let v128_load8_lane x align offset i = + VecLoadLane (x, {ty = V128T; align; offset; pack = Pack8}, i) +let v128_load16_lane x align offset i = + VecLoadLane (x, {ty = V128T; align; offset; pack = Pack16}, i) +let v128_load32_lane x align offset i = + VecLoadLane (x, {ty = V128T; align; offset; pack = Pack32}, i) +let v128_load64_lane x align offset i = + VecLoadLane (x, {ty = V128T; align; offset; pack = Pack64}, i) -let i32_store align offset = Store {ty = I32Type; align; offset; pack = None} -let i64_store align offset = Store {ty = I64Type; align; offset; pack = None} -let f32_store align offset = Store {ty = F32Type; align; offset; pack = None} -let f64_store align offset = Store {ty = F64Type; align; offset; pack = None} -let i32_store8 align offset = - Store {ty = I32Type; align; offset; pack = Some Pack8} -let i32_store16 align offset = - Store {ty = I32Type; align; offset; pack = Some Pack16} -let i64_store8 align offset = - Store {ty = I64Type; align; offset; pack = Some Pack8} -let i64_store16 align offset = - Store {ty = I64Type; align; offset; pack = Some Pack16} -let i64_store32 align offset = - Store {ty = I64Type; align; offset; pack = Some Pack32} +let v128_store x align offset = + VecStore (x, {ty = V128T; align; offset; pack = ()}) +let v128_store8_lane x align offset i = + VecStoreLane (x, {ty = V128T; align; offset; pack = Pack8}, i) +let v128_store16_lane x align offset i = + VecStoreLane (x, {ty = V128T; align; offset; pack = Pack16}, i) +let v128_store32_lane x align offset i = + VecStoreLane (x, {ty = V128T; align; offset; pack = Pack32}, i) +let v128_store64_lane x align offset i = + VecStoreLane (x, {ty = V128T; align; offset; pack = Pack64}, i) -let memory_size = MemorySize -let memory_grow = MemoryGrow -let memory_fill = MemoryFill -let memory_copy = MemoryCopy -let memory_init x = MemoryInit x +let memory_size x = MemorySize x +let memory_grow x = MemoryGrow x +let memory_fill x = MemoryFill x +let memory_copy x y = MemoryCopy (x, y) +let memory_init x y = MemoryInit (x, y) let data_drop x = DataDrop x let ref_is_null = RefIsNull +let ref_as_non_null = RefAsNonNull +let ref_test t = RefTest t +let ref_cast t = RefCast t +let ref_eq = RefEq + +let ref_i31 = RefI31 +let i31_get_u = I31Get ZX +let i31_get_s = I31Get SX +let struct_new x = StructNew (x, Explicit) +let struct_new_default x = StructNew (x, Implicit) +let struct_get x y = StructGet (x, y, None) +let struct_get_u x y = StructGet (x, y, Some ZX) +let struct_get_s x y = StructGet (x, y, Some SX) +let struct_set x y = StructSet (x, y) +let array_new x = ArrayNew (x, Explicit) +let array_new_default x = ArrayNew (x, Implicit) +let array_new_fixed x n = ArrayNewFixed (x, n) +let array_new_elem x y = ArrayNewElem (x, y) +let array_new_data x y = ArrayNewData (x, y) +let array_get x = ArrayGet (x, None) +let array_get_u x = ArrayGet (x, Some ZX) +let array_get_s x = ArrayGet (x, Some SX) +let array_set x = ArraySet x +let array_len = ArrayLen +let array_copy x y = ArrayCopy (x, y) +let array_fill x = ArrayFill x +let array_init_data x y = ArrayInitData (x, y) +let array_init_elem x y = ArrayInitElem (x, y) + +let any_convert_extern = ExternConvert Internalize +let extern_convert_any = ExternConvert Externalize let i32_clz = Unary (I32 I32Op.Clz) let i32_ctz = Unary (I32 I32Op.Ctz) @@ -234,52 +331,6 @@ let i64_reinterpret_f64 = Convert (I64 I64Op.ReinterpretFloat) let f32_reinterpret_i32 = Convert (F32 F32Op.ReinterpretInt) let f64_reinterpret_i64 = Convert (F64 F64Op.ReinterpretInt) -let v128_load align offset = VecLoad {ty = V128Type; align; offset; pack = None} -let v128_load8x8_s align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack8x8, SX))} -let v128_load8x8_u align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack8x8, ZX))} -let v128_load16x4_s align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack16x4, SX))} -let v128_load16x4_u align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack16x4, ZX))} -let v128_load32x2_s align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack32x2, SX))} -let v128_load32x2_u align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtLane (Pack32x2, ZX))} -let v128_load8_splat align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack8, ExtSplat)} -let v128_load16_splat align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack16, ExtSplat)} -let v128_load32_splat align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack32, ExtSplat)} -let v128_load64_splat align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtSplat)} -let v128_load32_zero align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack32, ExtZero)} -let v128_load64_zero align offset = - VecLoad {ty = V128Type; align; offset; pack = Some (Pack64, ExtZero)} - -let v128_store align offset = VecStore {ty = V128Type; align; offset; pack = ()} - -let v128_load8_lane align offset i = - VecLoadLane ({ty = V128Type; align; offset; pack = Pack8}, i) -let v128_load16_lane align offset i = - VecLoadLane ({ty = V128Type; align; offset; pack = Pack16}, i) -let v128_load32_lane align offset i = - VecLoadLane ({ty = V128Type; align; offset; pack = Pack32}, i) -let v128_load64_lane align offset i = - VecLoadLane ({ty = V128Type; align; offset; pack = Pack64}, i) - -let v128_store8_lane align offset i = - VecStoreLane ({ty = V128Type; align; offset; pack = Pack8}, i) -let v128_store16_lane align offset i = - VecStoreLane ({ty = V128Type; align; offset; pack = Pack16}, i) -let v128_store32_lane align offset i = - VecStoreLane ({ty = V128Type; align; offset; pack = Pack32}, i) -let v128_store64_lane align offset i = - VecStoreLane ({ty = V128Type; align; offset; pack = Pack64}, i) - let v128_not = VecUnaryBits (V128 V128Op.Not) let v128_and = VecBinaryBits (V128 V128Op.And) let v128_andnot = VecBinaryBits (V128 V128Op.AndNot) diff --git a/interpreter/syntax/pack.ml b/interpreter/syntax/pack.ml new file mode 100644 index 0000000000..edc1b8d5b5 --- /dev/null +++ b/interpreter/syntax/pack.ml @@ -0,0 +1,17 @@ +type pack_size = Pack8 | Pack16 | Pack32 | Pack64 +type extension = SX | ZX + +type pack_shape = Pack8x8 | Pack16x4 | Pack32x2 +type vec_extension = + | ExtLane of pack_shape * extension + | ExtSplat + | ExtZero + +let packed_size = function + | Pack8 -> 1 + | Pack16 -> 2 + | Pack32 -> 4 + | Pack64 -> 8 + +let packed_shape_size = function + | Pack8x8 | Pack16x4 | Pack32x2 -> 8 diff --git a/interpreter/syntax/types.ml b/interpreter/syntax/types.ml index 0e57290899..9da8f4e2e3 100644 --- a/interpreter/syntax/types.ml +++ b/interpreter/syntax/types.ml @@ -1,170 +1,442 @@ -(* Types *) +(* Generic Types *) -type num_type = I32Type | I64Type | F32Type | F64Type -type vec_type = V128Type -type ref_type = FuncRefType | ExternRefType -type value_type = NumType of num_type | VecType of vec_type | RefType of ref_type -type index_type = I32IndexType | I64IndexType -type result_type = value_type list -type func_type = FuncType of result_type * result_type +type type_idx = int32 +type local_idx = int32 +type name = Utf8.unicode +type null = NoNull | Null +type mut = Cons | Var +type init = Set | Unset +type final = NoFinal | Final type 'a limits = {min : 'a; max : 'a option} -type mutability = Immutable | Mutable -type table_type = TableType of Int64.t limits * index_type * ref_type -type memory_type = MemoryType of Int64.t limits * index_type -type global_type = GlobalType of value_type * mutability + +type var = StatX of type_idx | RecX of int32 + +type num_type = I32T | I64T | F32T | F64T +type vec_type = V128T +type heap_type = + | AnyHT | NoneHT | EqHT | I31HT | StructHT | ArrayHT + | FuncHT | NoFuncHT + | ExternHT | NoExternHT + | VarHT of var + | DefHT of def_type + | BotHT +and ref_type = null * heap_type +and val_type = NumT of num_type | VecT of vec_type | RefT of ref_type | BotT + +and index_type = I32IndexType | I64IndexType +and result_type = val_type list +and instr_type = InstrT of result_type * result_type * local_idx list + +and storage_type = ValStorageT of val_type | PackStorageT of Pack.pack_size +and field_type = FieldT of mut * storage_type + +and struct_type = StructT of field_type list +and array_type = ArrayT of field_type +and func_type = FuncT of result_type * result_type + +and str_type = + | DefStructT of struct_type + | DefArrayT of array_type + | DefFuncT of func_type + +and sub_type = SubT of final * heap_type list * str_type +and rec_type = RecT of sub_type list +and def_type = DefT of rec_type * int32 + +type table_type = TableT of Int64.t limits * index_type * ref_type +type memory_type = MemoryT of Int64.t limits * index_type +type global_type = GlobalT of mut * val_type +type local_type = LocalT of init * val_type type extern_type = - | ExternFuncType of func_type - | ExternTableType of table_type - | ExternMemoryType of memory_type - | ExternGlobalType of global_type + | ExternFuncT of def_type + | ExternTableT of table_type + | ExternMemoryT of memory_type + | ExternGlobalT of global_type -(* TODO: these types should move somewhere else *) -type pack_size = Pack8 | Pack16 | Pack32 | Pack64 -type extension = SX | ZX -type pack_shape = Pack8x8 | Pack16x4 | Pack32x2 -type vec_extension = - | ExtLane of pack_shape * extension - | ExtSplat - | ExtZero +type export_type = ExportT of extern_type * name +type import_type = ImportT of extern_type * name * name +type module_type = ModuleT of import_type list * export_type list (* Attributes *) let num_size = function - | I32Type | F32Type -> 4 - | I64Type | F64Type -> 8 + | I32T | F32T -> 4 + | I64T | F64T -> 8 let vec_size = function - | V128Type -> 16 + | V128T -> 16 + +let val_size = function + | NumT t -> num_size t + | VecT t -> vec_size t + | RefT _ | BotT -> failwith "val_size" + +let storage_size = function + | ValStorageT t -> val_size t + | PackStorageT p -> Pack.packed_size p + +let is_stat_var = function StatX _ -> true | _ -> false +let is_rec_var = function RecX _ -> true | _ -> false -let packed_size = function - | Pack8 -> 1 - | Pack16 -> 2 - | Pack32 -> 4 - | Pack64 -> 8 +let as_stat_var = function StatX x -> x | _ -> assert false +let as_rec_var = function RecX x -> x | _ -> assert false -let packed_shape_size = function - | Pack8x8 | Pack16x4 | Pack32x2 -> 8 let is_num_type = function - | NumType _ -> true + | NumT _ | BotT -> true | _ -> false let is_vec_type = function - | VecType _ -> true + | VecT _ | BotT -> true | _ -> false let is_ref_type = function - | RefType _ -> true + | RefT _ | BotT -> true | _ -> false +let is_packed_storage_type = function + | ValStorageT _ -> false + | PackStorageT _ -> true + + +let defaultable = function + | NumT _ -> true + | VecT _ -> true + | RefT (nul, _) -> nul = Null + | BotT -> assert false + + +(* Projections *) + +let unpacked_storage_type = function + | ValStorageT t -> t + | PackStorageT _ -> NumT I32T + +let unpacked_field_type (FieldT (_mut, t)) = unpacked_storage_type t + + +let as_func_str_type (st : str_type) : func_type = + match st with + | DefFuncT ft -> ft + | _ -> assert false + +let as_struct_str_type (st : str_type) : struct_type = + match st with + | DefStructT st -> st + | _ -> assert false + +let as_array_str_type (st : str_type) : array_type = + match st with + | DefArrayT at -> at + | _ -> assert false + +let extern_type_of_import_type (ImportT (et, _, _)) = et +let extern_type_of_export_type (ExportT (et, _)) = et + (* Filters *) -let funcs = - Lib.List.map_filter (function ExternFuncType t -> Some t | _ -> None) -let tables = - Lib.List.map_filter (function ExternTableType t -> Some t | _ -> None) -let memories = - Lib.List.map_filter (function ExternMemoryType t -> Some t | _ -> None) -let globals = - Lib.List.map_filter (function ExternGlobalType t -> Some t | _ -> None) +let funcs = List.filter_map (function ExternFuncT ft -> Some ft | _ -> None) +let tables = List.filter_map (function ExternTableT tt -> Some tt | _ -> None) +let memories = List.filter_map (function ExternMemoryT mt -> Some mt | _ -> None) +let globals = List.filter_map (function ExternGlobalT gt -> Some gt | _ -> None) let num_type_of_index_type = function - | I32IndexType -> I32Type - | I64IndexType -> I64Type + | I32IndexType -> I32T + | I64IndexType -> I64T + +let value_type_of_index_type t = NumT (num_type_of_index_type t) + +(* Substitution *) + +type subst = var -> heap_type + +let subst_of dts = function + | StatX x -> DefHT (Lib.List32.nth dts x) + | RecX i -> VarHT (RecX i) + +let subst_num_type s t = t + +let subst_vec_type s t = t + +let subst_heap_type s = function + | AnyHT -> AnyHT + | NoneHT -> NoneHT + | EqHT -> EqHT + | I31HT -> I31HT + | StructHT -> StructHT + | ArrayHT -> ArrayHT + | FuncHT -> FuncHT + | NoFuncHT -> NoFuncHT + | ExternHT -> ExternHT + | NoExternHT -> NoExternHT + | VarHT x -> s x + | DefHT dt -> DefHT dt (* assume closed *) + | BotHT -> BotHT + +let subst_ref_type s = function + | (nul, t) -> (nul, subst_heap_type s t) + +let subst_val_type s = function + | NumT t -> NumT (subst_num_type s t) + | VecT t -> VecT (subst_vec_type s t) + | RefT t -> RefT (subst_ref_type s t) + | BotT -> BotT + +let subst_result_type s = function + | ts -> List.map (subst_val_type s) ts -let value_type_of_index_type t = NumType (num_type_of_index_type t) -(* Subtyping *) +let subst_storage_type s = function + | ValStorageT t -> ValStorageT (subst_val_type s t) + | PackStorageT p -> PackStorageT p -let match_limits ge lim1 lim2 = - ge lim1.min lim2.min && - match lim1.max, lim2.max with - | _, None -> true - | None, Some _ -> false - | Some i, Some j -> ge j i +let subst_field_type s = function + | FieldT (mut, t) -> FieldT (mut, subst_storage_type s t) -let match_func_type ft1 ft2 = - ft1 = ft2 +let subst_struct_type s = function + | StructT ts -> StructT (List.map (subst_field_type s) ts) -let match_table_type (TableType (lim1, it1, et1)) (TableType (lim2, it2, et2)) = - it1 = it2 && et1 = et2 && match_limits I64.ge_u lim1 lim2 +let subst_array_type s = function + | ArrayT t -> ArrayT (subst_field_type s t) -let match_memory_type (MemoryType (lim1, it1)) (MemoryType (lim2, it2)) = - it1 = it2 && match_limits I64.ge_u lim1 lim2 +let subst_func_type s = function + | FuncT (ts1, ts2) -> FuncT (subst_result_type s ts1, subst_result_type s ts2) -let match_global_type gt1 gt2 = - gt1 = gt2 +let subst_str_type s = function + | DefStructT st -> DefStructT (subst_struct_type s st) + | DefArrayT at -> DefArrayT (subst_array_type s at) + | DefFuncT ft -> DefFuncT (subst_func_type s ft) -let match_extern_type et1 et2 = - match et1, et2 with - | ExternFuncType ft1, ExternFuncType ft2 -> match_func_type ft1 ft2 - | ExternTableType tt1, ExternTableType tt2 -> match_table_type tt1 tt2 - | ExternMemoryType mt1, ExternMemoryType mt2 -> match_memory_type mt1 mt2 - | ExternGlobalType gt1, ExternGlobalType gt2 -> match_global_type gt1 gt2 - | _, _ -> false +let subst_sub_type s = function + | SubT (fin, hts, st) -> + SubT (fin, List.map (subst_heap_type s) hts, subst_str_type s st) + +let subst_rec_type s = function + | RecT sts -> RecT (List.map (subst_sub_type s) sts) + +let subst_def_type s = function + | DefT (rt, i) -> DefT (subst_rec_type s rt, i) + + +let subst_memory_type s = function + | MemoryT (lim, it) -> MemoryT (lim, it) + +let subst_table_type s = function + | TableT (lim, it, t) -> TableT (lim, it, subst_ref_type s t) + +let subst_global_type s = function + | GlobalT (mut, t) -> GlobalT (mut, subst_val_type s t) + +let subst_extern_type s = function + | ExternFuncT dt -> ExternFuncT (subst_def_type s dt) + | ExternTableT tt -> ExternTableT (subst_table_type s tt) + | ExternMemoryT mt -> ExternMemoryT (subst_memory_type s mt) + | ExternGlobalT gt -> ExternGlobalT (subst_global_type s gt) + + +let subst_export_type s = function + | ExportT (et, name) -> ExportT (subst_extern_type s et, name) + +let subst_import_type s = function + | ImportT (et, module_name, name) -> + ImportT (subst_extern_type s et, module_name, name) + +let subst_module_type s = function + | ModuleT (its, ets) -> + ModuleT ( + List.map (subst_import_type s) its, + List.map (subst_export_type s) ets + ) + + +(* Recursive types *) + +let roll_rec_type x (rt : rec_type) : rec_type = + let RecT sts = rt in + let y = Int32.add x (Lib.List32.length sts) in + let s = function + | StatX x' when x <= x' && x' < y -> VarHT (RecX (Int32.sub x' x)) + | var -> VarHT var + in + subst_rec_type s rt + +let roll_def_types x (rt : rec_type) : def_type list = + let RecT sts as rt' = roll_rec_type x rt in + Lib.List32.mapi (fun i _ -> DefT (rt', i)) sts + + +let unroll_rec_type (rt : rec_type) : rec_type = + let s = function + | RecX i -> DefHT (DefT (rt, i)) + | var -> VarHT var + in + subst_rec_type s rt + +let unroll_def_type (dt : def_type) : sub_type = + let DefT (rt, i) = dt in + let RecT sts = unroll_rec_type rt in + Lib.List32.nth sts i + +let expand_def_type (dt : def_type) : str_type = + let SubT (_, _, st) = unroll_def_type dt in + st (* String conversion *) -let string_of_num_type = function - | I32Type -> "i32" - | I64Type -> "i64" - | F32Type -> "f32" - | F64Type -> "f64" +let string_of_idx x = + I32.to_string_u x -let string_of_vec_type = function - | V128Type -> "v128" +let string_of_name n = + let b = Buffer.create 16 in + let escape uc = + if uc < 0x20 || uc >= 0x7f then + Buffer.add_string b (Printf.sprintf "\\u{%02x}" uc) + else begin + let c = Char.chr uc in + if c = '\"' || c = '\\' then Buffer.add_char b '\\'; + Buffer.add_char b c + end + in + List.iter escape n; + Buffer.contents b -let string_of_ref_type = function - | FuncRefType -> "funcref" - | ExternRefType -> "externref" +let string_of_var = function + | StatX x -> I32.to_string_u x + | RecX x -> "rec." ^ I32.to_string_u x -let string_of_refed_type = function - | FuncRefType -> "func" - | ExternRefType -> "extern" +let string_of_null = function + | NoNull -> "" + | Null -> "null " -let string_of_value_type = function - | NumType t -> string_of_num_type t - | VecType t -> string_of_vec_type t - | RefType t -> string_of_ref_type t +let string_of_final = function + | NoFinal -> "" + | Final -> " final" -let string_of_value_types = function - | [t] -> string_of_value_type t - | ts -> "[" ^ String.concat " " (List.map string_of_value_type ts) ^ "]" +let string_of_mut s = function + | Cons -> s + | Var -> "(mut " ^ s ^ ")" -let string_of_index_type t = - string_of_value_type (value_type_of_index_type t) -let string_of_limits to_string {min; max} = - to_string min ^ - (match max with None -> "" | Some n -> " " ^ to_string n) +let string_of_num_type = function + | I32T -> "i32" + | I64T -> "i64" + | F32T -> "f32" + | F64T -> "f64" -let string_of_memory_type = function - | MemoryType (lim, it) -> - string_of_num_type (num_type_of_index_type it) ^ - " " ^ string_of_limits I64.to_string_u lim +let string_of_vec_type = function + | V128T -> "v128" + +let rec string_of_heap_type = function + | AnyHT -> "any" + | NoneHT -> "none" + | EqHT -> "eq" + | I31HT -> "i31" + | StructHT -> "struct" + | ArrayHT -> "array" + | FuncHT -> "func" + | NoFuncHT -> "nofunc" + | ExternHT -> "extern" + | NoExternHT -> "noextern" + | VarHT x -> string_of_var x + | DefHT dt -> "(" ^ string_of_def_type dt ^ ")" + | BotHT -> "something" + +and string_of_ref_type = function + | (nul, t) -> "(ref " ^ string_of_null nul ^ string_of_heap_type t ^ ")" + +and string_of_val_type = function + | NumT t -> string_of_num_type t + | VecT t -> string_of_vec_type t + | RefT t -> string_of_ref_type t + | BotT -> "bot" + +and string_of_index_type t = + string_of_val_type (value_type_of_index_type t) + +and string_of_result_type = function + | ts -> "[" ^ String.concat " " (List.map string_of_val_type ts) ^ "]" + + +and string_of_storage_type = function + | ValStorageT t -> string_of_val_type t + | PackStorageT p -> "i" ^ string_of_int (8 * Pack.packed_size p) + +and string_of_field_type = function + | FieldT (mut, t) -> string_of_mut (string_of_storage_type t) mut + +and string_of_struct_type = function + | StructT fts -> + String.concat " " (List.map (fun ft -> "(field " ^ string_of_field_type ft ^ ")") fts) + +and string_of_array_type = function + | ArrayT ft -> string_of_field_type ft + +and string_of_func_type = function + | FuncT (ts1, ts2) -> + string_of_result_type ts1 ^ " -> " ^ string_of_result_type ts2 + +and string_of_str_type = function + | DefStructT st -> "struct " ^ string_of_struct_type st + | DefArrayT at -> "array " ^ string_of_array_type at + | DefFuncT ft -> "func " ^ string_of_func_type ft + +and string_of_sub_type = function + | SubT (Final, [], st) -> string_of_str_type st + | SubT (fin, hts, st) -> + String.concat " " + (("sub" ^ string_of_final fin) :: List.map string_of_heap_type hts) ^ + " (" ^ string_of_str_type st ^ ")" + +and string_of_rec_type = function + | RecT [st] -> string_of_sub_type st + | RecT sts -> + "rec " ^ + String.concat " " (List.map (fun st -> "(" ^ string_of_sub_type st ^ ")") sts) + +and string_of_def_type = function + | DefT (RecT [st], 0l) -> string_of_sub_type st + | DefT (rt, i) -> "(" ^ string_of_rec_type rt ^ ")." ^ I32.to_string_u i + +let string_of_limits = function + | {min; max} -> + I64.to_string_u min ^ + (match max with None -> "" | Some n -> " " ^ I64.to_string_u n) +let string_of_memory_type = function + | MemoryT (lim, it) -> string_of_num_type (num_type_of_index_type it) ^ " " ^ string_of_limits lim let string_of_table_type = function - | TableType (lim, it, t) -> - string_of_num_type (num_type_of_index_type it) ^ - " " ^ string_of_limits I64.to_string_u lim ^ - " " ^ string_of_ref_type t + | TableT (lim, it, t) -> string_of_num_type (num_type_of_index_type it) ^ " " ^ string_of_limits lim ^ " " ^ string_of_ref_type t let string_of_global_type = function - | GlobalType (t, Immutable) -> string_of_value_type t - | GlobalType (t, Mutable) -> "(mut " ^ string_of_value_type t ^ ")" - -let string_of_result_type ts = - "[" ^ String.concat " " (List.map string_of_value_type ts) ^ "]" + | GlobalT (mut, t) -> string_of_mut (string_of_val_type t) mut -let string_of_func_type (FuncType (ins, out)) = - string_of_result_type ins ^ " -> " ^ string_of_result_type out +let string_of_local_type = function + | LocalT (Set, t) -> string_of_val_type t + | LocalT (Unset, t) -> "(unset " ^ string_of_val_type t ^ ")" let string_of_extern_type = function - | ExternFuncType ft -> "func " ^ string_of_func_type ft - | ExternTableType tt -> "table " ^ string_of_table_type tt - | ExternMemoryType mt -> "memory " ^ string_of_memory_type mt - | ExternGlobalType gt -> "global " ^ string_of_global_type gt + | ExternFuncT dt -> "func " ^ string_of_def_type dt + | ExternTableT tt -> "table " ^ string_of_table_type tt + | ExternMemoryT mt -> "memory " ^ string_of_memory_type mt + | ExternGlobalT gt -> "global " ^ string_of_global_type gt + + +let string_of_export_type = function + | ExportT (et, name) -> + "\"" ^ string_of_name name ^ "\" : " ^ string_of_extern_type et + +let string_of_import_type = function + | ImportT (et, module_name, name) -> + "\"" ^ string_of_name module_name ^ "\" \"" ^ + string_of_name name ^ "\" : " ^ string_of_extern_type et + +let string_of_module_type = function + | ModuleT (its, ets) -> + String.concat "" ( + List.map (fun it -> "import " ^ string_of_import_type it ^ "\n") its @ + List.map (fun et -> "export " ^ string_of_export_type et ^ "\n") ets + ) diff --git a/interpreter/syntax/values.ml b/interpreter/syntax/values.ml deleted file mode 100644 index 1a2168f6b8..0000000000 --- a/interpreter/syntax/values.ml +++ /dev/null @@ -1,187 +0,0 @@ -open Types - - -(* Values and operators *) - -type ('i32, 'i64, 'f32, 'f64) op = - I32 of 'i32 | I64 of 'i64 | F32 of 'f32 | F64 of 'f64 - -type ('v128) vecop = - V128 of 'v128 - -type num = (I32.t, I64.t, F32.t, F64.t) op -type vec = (V128.t) vecop - -type ref_ = .. -type ref_ += NullRef of ref_type - -type value = Num of num | Vec of vec | Ref of ref_ - - -(* Injection & projection *) - -let as_num = function - | Num n -> n - | _ -> failwith "as_num" - -let as_vec = function - | Vec i -> i - | _ -> failwith "as_vec" - -let as_ref = function - | Ref r -> r - | _ -> failwith "as_ref" - - -exception TypeError of int * num * num_type - -module type NumType = -sig - type t - val to_num : t -> num - val of_num : int -> num -> t -end - -module I32Num = -struct - type t = I32.t - let to_num i = I32 i - let of_num n = function I32 i -> i | v -> raise (TypeError (n, v, I32Type)) -end - -module I64Num = -struct - type t = I64.t - let to_num i = I64 i - let of_num n = function I64 i -> i | v -> raise (TypeError (n, v, I64Type)) -end - -module F32Num = -struct - type t = F32.t - let to_num i = F32 i - let of_num n = function F32 z -> z | v -> raise (TypeError (n, v, F32Type)) -end - -module F64Num = -struct - type t = F64.t - let to_num i = F64 i - let of_num n = function F64 z -> z | v -> raise (TypeError (n, v, F64Type)) -end - -module type VecType = -sig - type t - val to_vec : t -> vec - val of_vec : int -> vec -> t -end - -module V128Vec = -struct - type t = V128.t - let to_vec i = V128 i - let of_vec n = function V128 z -> z -end - - -(* Typing *) - -let type_of_num = function - | I32 _ -> I32Type - | I64 _ -> I64Type - | F32 _ -> F32Type - | F64 _ -> F64Type - -let type_of_vec = function - | V128 _ -> V128Type - -let type_of_ref' = ref (function NullRef t -> t | _ -> assert false) -let type_of_ref r = !type_of_ref' r - -let type_of_value = function - | Num n -> NumType (type_of_num n) - | Vec i -> VecType (type_of_vec i) - | Ref r -> RefType (type_of_ref r) - - -(* Comparison *) - -let eq_num n1 n2 = n1 = n2 - -let eq_vec v1 v2 = v1 = v2 - -let eq_ref' = ref (fun r1 r2 -> - match r1, r2 with - | NullRef _, NullRef _ -> true - | _, _ -> false -) - -let eq_ref r1 r2 = !eq_ref' r1 r2 - -let eq v1 v2 = - match v1, v2 with - | Num n1, Num n2 -> eq_num n1 n2 - | Vec v1, Vec v2 -> eq_vec v1 v2 - | Ref r1, Ref r2 -> eq_ref r1 r2 - | _, _ -> false - - -(* Defaults *) - -let default_num = function - | I32Type -> I32 I32.zero - | I64Type -> I64 I64.zero - | F32Type -> F32 F32.zero - | F64Type -> F64 F64.zero - -let default_vec = function - | V128Type -> V128 V128.zero - -let default_ref = function - | t -> NullRef t - -let default_value = function - | NumType t' -> Num (default_num t') - | VecType t' -> Vec (default_vec t') - | RefType t' -> Ref (default_ref t') - - -(* Conversion *) - -let value_of_bool b = Num (I32 (if b then 1l else 0l)) - -let value_of_index it x = - match it with - | I64IndexType -> Num (I64 x) - | I32IndexType -> Num (I32 (Int64.to_int32 x)) - -let string_of_num = function - | I32 i -> I32.to_string_s i - | I64 i -> I64.to_string_s i - | F32 z -> F32.to_string z - | F64 z -> F64.to_string z - -let hex_string_of_num = function - | I32 i -> I32.to_hex_string i - | I64 i -> I64.to_hex_string i - | F32 z -> F32.to_hex_string z - | F64 z -> F64.to_hex_string z - -let string_of_vec = function - | V128 v -> V128.to_string v - -let hex_string_of_vec = function - | V128 v -> V128.to_hex_string v - -let string_of_ref' = ref (function NullRef t -> "null" | _ -> "ref") -let string_of_ref r = !string_of_ref' r - -let string_of_value = function - | Num n -> string_of_num n - | Vec i -> string_of_vec i - | Ref r -> string_of_ref r - -let string_of_values = function - | [v] -> string_of_value v - | vs -> "[" ^ String.concat " " (List.map string_of_value vs) ^ "]" diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index af6e98ab69..9f076d1731 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -1,7 +1,8 @@ open Source open Ast +open Pack open Script -open Values +open Value open Types open Sexpr @@ -41,6 +42,7 @@ let list_of_opt = function None -> [] | Some x -> [x] let list f xs = List.map f xs let listi f xs = List.mapi f xs let opt f xo = list f (list_of_opt xo) +let opt_s f xo = Lib.Option.get (Lib.Option.map f xo) "" let tab head f xs = if xs = [] then [] else [Node (head, list f xs)] let atom f x = Atom (f x) @@ -56,27 +58,65 @@ let break_string s = (* Types *) +let mutability node = function + | Cons -> node + | Var -> Node ("mut", [node]) + let num_type t = string_of_num_type t let vec_type t = string_of_vec_type t -let ref_type t = string_of_ref_type t -let refed_type t = string_of_refed_type t -let value_type t = string_of_value_type t - -let index_type t = string_of_value_type (value_type_of_index_type t) - -let decls kind ts = tab kind (atom value_type) ts - -let func_type (FuncType (ins, out)) = - Node ("func", decls "param" ins @ decls "result" out) - -let struct_type = func_type +let ref_type t = + match t with + | (Null, AnyHT) -> "anyref" + | (Null, EqHT) -> "eqref" + | (Null, I31HT) -> "i31ref" + | (Null, StructHT) -> "structref" + | (Null, ArrayHT) -> "arrayref" + | (Null, FuncHT) -> "funcref" + | t -> string_of_ref_type t + +let index_type t = string_of_val_type (value_type_of_index_type t) +let heap_type t = string_of_heap_type t +let val_type t = string_of_val_type t +let storage_type t = string_of_storage_type t + +let final = function + | NoFinal -> "" + | Final -> " final" + +let decls kind ts = tab kind (atom val_type) ts + +let field_type (FieldT (mut, t)) = + mutability (atom storage_type t) mut + +let struct_type (StructT fts) = + Node ("struct", list (fun ft -> Node ("field", [field_type ft])) fts) + +let array_type (ArrayT ft) = + Node ("array", [field_type ft]) + +let func_type (FuncT (ts1, ts2)) = + Node ("func", decls "param" ts1 @ decls "result" ts2) + +let str_type st = + match st with + | DefStructT st -> struct_type st + | DefArrayT at -> array_type at + | DefFuncT ft -> func_type ft + +let sub_type = function + | SubT (Final, [], st) -> str_type st + | SubT (fin, xs, st) -> + Node (String.concat " " + (("sub" ^ final fin ):: List.map heap_type xs), [str_type st]) + +let rec_type i j st = + Node ("type $" ^ nat (i + j), [sub_type st]) let limits nat {min; max} = String.concat " " (nat min :: opt nat max) -let global_type = function - | GlobalType (t, Immutable) -> atom string_of_value_type t - | GlobalType (t, Mutable) -> Node ("mut", [atom string_of_value_type t]) +let global_type (GlobalT (mut, t)) = + mutability (atom string_of_val_type t) mut let pack_size = function | Pack8 -> "8" @@ -355,7 +395,7 @@ struct end let oper (iop, fop) op = - num_type (type_of_num op) ^ "." ^ + string_of_num_type (type_of_num op) ^ "." ^ (match op with | I32 o -> iop "32" o | I64 o -> iop "64" o @@ -392,43 +432,52 @@ let vec_splatop = vec_shape_oper (V128Op.splatop, V128Op.splatop, V128Op.splatop let vec_extractop = vec_shape_oper (V128Op.pextractop, V128Op.extractop, V128Op.extractop) let vec_replaceop = vec_shape_oper (V128Op.replaceop, V128Op.replaceop, V128Op.replaceop) -let memop name typ {ty; align; offset; _} sz = - typ ty ^ "." ^ name ^ +let var x = nat32 x.it +let num v = string_of_num v.it +let vec v = string_of_vec v.it + +let memop name x typ {ty; align; offset; _} sz = + typ ty ^ "." ^ name ^ " " ^ var x ^ (if offset = 0L then "" else " offset=" ^ nat64 offset) ^ (if 1 lsl align = sz then "" else " align=" ^ nat (1 lsl align)) -let loadop op = +let loadop x op = match op.pack with - | None -> memop "load" num_type op (num_size op.ty) + | None -> memop "load" x num_type op (num_size op.ty) | Some (sz, ext) -> - memop ("load" ^ pack_size sz ^ extension ext) num_type op (packed_size sz) + memop ("load" ^ pack_size sz ^ extension ext) x num_type op (packed_size sz) -let storeop op = +let storeop x op = match op.pack with - | None -> memop "store" num_type op (num_size op.ty) - | Some sz -> memop ("store" ^ pack_size sz) num_type op (packed_size sz) + | None -> memop "store" x num_type op (num_size op.ty) + | Some sz -> memop ("store" ^ pack_size sz) x num_type op (packed_size sz) -let vec_loadop (op : vec_loadop) = +let vec_loadop x (op : vec_loadop) = match op.pack with - | None -> memop "load" vec_type op (vec_size op.ty) + | None -> memop "load" x vec_type op (vec_size op.ty) | Some (sz, ext) -> - memop ("load" ^ vec_extension sz ext) vec_type op (packed_size sz) + memop ("load" ^ vec_extension sz ext) x vec_type op (packed_size sz) -let vec_storeop op = - memop "store" vec_type op (vec_size op.ty) +let vec_storeop x op = + memop "store" x vec_type op (vec_size op.ty) -let vec_laneop instr (op, i) = - memop (instr ^ pack_size op.pack ^ "_lane") vec_type op +let vec_laneop instr x op i = + memop (instr ^ pack_size op.pack ^ "_lane") x vec_type op (packed_size op.pack) ^ " " ^ nat i +let initop = function + | Explicit -> "" + | Implicit -> "_default" -(* Expressions *) +let constop v = string_of_num_type (type_of_num v) ^ ".const" +let vec_constop v = string_of_vec_type (type_of_vec v) ^ ".const i32x4" + +let externop = function + | Internalize -> "any.convert_extern" + | Externalize -> "extern.convert_any" -let var x = nat32 x.it -let num v = string_of_num v.it -let vec v = string_of_vec v.it -let constop v = num_type (type_of_num v) ^ ".const" -let vec_constop v = vec_type (type_of_vec v) ^ ".const i32x4" + +(* Expressions *) let block_type = function | VarBlockType x -> [Node ("type " ^ var x, [])] @@ -452,10 +501,21 @@ let rec instr e = | BrIf x -> "br_if " ^ var x, [] | BrTable (xs, x) -> "br_table " ^ String.concat " " (list var (xs @ [x])), [] + | BrOnNull x -> "br_on_null " ^ var x, [] + | BrOnNonNull x -> "br_on_non_null " ^ var x, [] + | BrOnCast (x, t1, t2) -> + "br_on_cast " ^ var x, [Atom (ref_type t1); Atom (ref_type t2)] + | BrOnCastFail (x, t1, t2) -> + "br_on_cast_fail " ^ var x, [Atom (ref_type t1); Atom (ref_type t2)] | Return -> "return", [] | Call x -> "call " ^ var x, [] + | CallRef x -> "call_ref " ^ var x, [] | CallIndirect (x, y) -> "call_indirect " ^ var x, [Node ("type " ^ var y, [])] + | ReturnCall x -> "return_call " ^ var x, [] + | ReturnCallRef x -> "return_call_ref " ^ var x, [] + | ReturnCallIndirect (x, y) -> + "return_call_indirect " ^ var x, [Node ("type " ^ var y, [])] | LocalGet x -> "local.get " ^ var x, [] | LocalSet x -> "local.set " ^ var x, [] | LocalTee x -> "local.tee " ^ var x, [] @@ -469,21 +529,43 @@ let rec instr e = | TableCopy (x, y) -> "table.copy " ^ var x ^ " " ^ var y, [] | TableInit (x, y) -> "table.init " ^ var x ^ " " ^ var y, [] | ElemDrop x -> "elem.drop " ^ var x, [] - | Load op -> loadop op, [] - | Store op -> storeop op, [] - | VecLoad op -> vec_loadop op, [] - | VecStore op -> vec_storeop op, [] - | VecLoadLane op -> vec_laneop "load" op, [] - | VecStoreLane op -> vec_laneop "store" op, [] - | MemorySize -> "memory.size", [] - | MemoryGrow -> "memory.grow", [] - | MemoryFill -> "memory.fill", [] - | MemoryCopy -> "memory.copy", [] - | MemoryInit x -> "memory.init " ^ var x, [] + | Load (x, op) -> loadop x op, [] + | Store (x, op) -> storeop x op, [] + | VecLoad (x, op) -> vec_loadop x op, [] + | VecStore (x, op) -> vec_storeop x op, [] + | VecLoadLane (x, op, i) -> vec_laneop "load" x op i, [] + | VecStoreLane (x, op, i) -> vec_laneop "store" x op i, [] + | MemorySize x -> "memory.size " ^ var x, [] + | MemoryGrow x -> "memory.grow " ^ var x, [] + | MemoryFill x -> "memory.fill " ^ var x, [] + | MemoryCopy (x, y) -> "memory.copy " ^ var x ^ " " ^ var y, [] + | MemoryInit (x, y) -> "memory.init " ^ var x ^ " " ^ var y, [] | DataDrop x -> "data.drop " ^ var x, [] - | RefNull t -> "ref.null", [Atom (refed_type t)] - | RefIsNull -> "ref.is_null", [] + | RefNull t -> "ref.null", [Atom (heap_type t)] | RefFunc x -> "ref.func " ^ var x, [] + | RefIsNull -> "ref.is_null", [] + | RefAsNonNull -> "ref.as_non_null", [] + | RefTest t -> "ref.test", [Atom (ref_type t)] + | RefCast t -> "ref.cast", [Atom (ref_type t)] + | RefEq -> "ref.eq", [] + | RefI31 -> "ref.i31", [] + | I31Get ext -> "i31.get" ^ extension ext, [] + | StructNew (x, op) -> "struct.new" ^ initop op ^ " " ^ var x, [] + | StructGet (x, y, exto) -> + "struct.get" ^ opt_s extension exto ^ " " ^ var x ^ " " ^ var y, [] + | StructSet (x, y) -> "struct.set " ^ var x ^ " " ^ var y, [] + | ArrayNew (x, op) -> "array.new" ^ initop op ^ " " ^ var x, [] + | ArrayNewFixed (x, n) -> "array.new_fixed " ^ var x ^ " " ^ nat32 n, [] + | ArrayNewElem (x, y) -> "array.new_elem " ^ var x ^ " " ^ var y, [] + | ArrayNewData (x, y) -> "array.new_data " ^ var x ^ " " ^ var y, [] + | ArrayGet (x, exto) -> "array.get" ^ opt_s extension exto ^ " " ^ var x, [] + | ArraySet x -> "array.set " ^ var x, [] + | ArrayLen -> "array.len", [] + | ArrayCopy (x, y) -> "array.copy " ^ var x ^ " " ^ var y, [] + | ArrayFill x -> "array.fill " ^ var x, [] + | ArrayInitData (x, y) -> "array.init_data " ^ var x ^ " " ^ var y, [] + | ArrayInitElem (x, y) -> "array.init_elem " ^ var x ^ " " ^ var y, [] + | ExternConvert op -> externop op, [] | Const n -> constop n.it ^ " " ^ num n, [] | Test op -> testop op, [] | Compare op -> relop op, [] @@ -519,7 +601,7 @@ let func_with_name name f = let {ftype; locals; body} = f.it in Node ("func" ^ name, [Node ("type " ^ var ftype, [])] @ - decls "local" locals @ + decls "local" (List.map (fun loc -> loc.it.ltype) locals) @ list instr body ) @@ -533,22 +615,21 @@ let func f = (* Tables & memories *) let table off i tab = - let {ttype = TableType (lim, it, t)} = tab.it in - Node ("table $" ^ nat (off + i) ^ " " ^ index_type it ^ " " ^ - limits nat64 lim, [atom ref_type t] + let {ttype = TableT (lim, it, t); tinit} = tab.it in + Node ("table $" ^ nat (off + i) ^ " " ^ index_type it ^ " " ^ limits nat64 lim, + atom ref_type t :: list instr tinit.it ) let memory off i mem = - let {mtype = MemoryType (lim, it)} = mem.it in - Node ("memory $" ^ nat (off + i) ^ " " ^ index_type it ^ " " ^ - limits nat64 lim, []) + let {mtype = MemoryT (lim, it)} = mem.it in + Node ("memory $" ^ nat (off + i) ^ " " ^ index_type it ^ " " ^ limits nat64 lim, []) let is_elem_kind = function - | FuncRefType -> true + | (NoNull, FuncHT) -> true | _ -> false let elem_kind = function - | FuncRefType -> "func" + | (NoNull, FuncHT) -> "func" | _ -> assert false let is_elem_index e = @@ -586,15 +667,19 @@ let data i seg = (* Modules *) -let typedef i ty = - Node ("type $" ^ nat i, [struct_type ty.it]) +let type_ (ns, i) ty = + match ty.it with + | RecT [st] when not Free.(Set.mem (Int32.of_int i) (type_ ty).types) -> + rec_type i 0 st :: ns, i + 1 + | RecT sts -> + Node ("rec", List.mapi (rec_type i) sts) :: ns, i + List.length sts let import_desc fx tx mx gx d = match d.it with | FuncImport x -> incr fx; Node ("func $" ^ nat (!fx - 1), [Node ("type", [atom var x])]) | TableImport t -> - incr tx; table 0 (!tx - 1) ({ttype = t} @@ d.at) + incr tx; table 0 (!tx - 1) ({ttype = t; tinit = [] @@ d.at} @@ d.at) | MemoryImport t -> incr mx; memory 0 (!mx - 1) ({mtype = t} @@ d.at) | GlobalImport t -> @@ -638,7 +723,7 @@ let module_with_var_opt x_opt m = let gx = ref 0 in let imports = list (import fx tx mx gx) m.it.imports in Node ("module" ^ var_opt x_opt, - listi typedef m.it.types @ + List.rev (fst (List.fold_left type_ ([], 0) m.it.types)) @ imports @ listi (table !tx) m.it.tables @ listi (memory !mx) m.it.memories @ @@ -665,8 +750,9 @@ let num mode = if mode = `Binary then hex_string_of_num else string_of_num let vec mode = if mode = `Binary then hex_string_of_vec else string_of_vec let ref_ = function - | NullRef t -> Node ("ref.null " ^ refed_type t, []) - | ExternRef n -> Node ("ref.extern " ^ nat32 n, []) + | NullRef t -> Node ("ref.null " ^ heap_type t, []) + | Script.HostRef n -> Node ("ref.host " ^ nat32 n, []) + | Extern.ExternRef (Script.HostRef n) -> Node ("ref.extern " ^ nat32 n, []) | _ -> assert false let literal mode lit = @@ -720,15 +806,15 @@ let nanop (n : nanop) = | _ -> . let num_pat mode = function - | NumPat n -> literal mode (Values.Num n.it @@ n.at) + | NumPat n -> literal mode (Value.Num n.it @@ n.at) | NanPat nan -> Node (constop nan.it ^ " " ^ nanop nan, []) let lane_pat mode pat shape = let choose fb ft = if mode = `Binary then fb else ft in match pat, shape with - | NumPat {it = Values.I32 i; _}, V128.I8x16 () -> + | NumPat {it = Value.I32 i; _}, V128.I8x16 () -> choose I8.to_hex_string I8.to_string_s i - | NumPat {it = Values.I32 i; _}, V128.I16x8 () -> + | NumPat {it = Value.I32 i; _}, V128.I16x8 () -> choose I16.to_hex_string I16.to_string_s i | NumPat n, _ -> num mode n.it | NanPat nan, _ -> nanop nan @@ -740,7 +826,8 @@ let vec_pat mode = function let ref_pat = function | RefPat r -> ref_ r.it - | RefTypePat t -> Node ("ref." ^ refed_type t, []) + | RefTypePat t -> Node ("ref." ^ heap_type t, []) + | NullPat -> Node ("ref.null", []) let result mode res = match res.it with @@ -777,4 +864,4 @@ let command mode cmd = | Assertion ass -> assertion mode ass | Meta _ -> assert false -let script mode scr = Lib.List.concat_map (command mode) scr +let script mode scr = List.concat_map (command mode) scr diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 1cda0087de..e154de3b51 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -112,8 +112,9 @@ let ixx = "i" ("32" | "64") let fxx = "f" ("32" | "64") let nxx = ixx | fxx let vxxx = "v128" -let mixx = "i" ("8" | "16" | "32" | "64") -let mfxx = "f" ("32" | "64") +let pixx = "i" ("8" | "16") +let mixx = ixx | pixx +let mfxx = fxx let sign = "s" | "u" let mem_size = "8" | "16" | "32" let v128_int_shape = "i8x16" | "i16x8" | "i32x4" | "i64x2" @@ -137,11 +138,13 @@ rule token = parse | keyword as s { match s with - | "i32" -> NUM_TYPE Types.I32Type - | "i64" -> NUM_TYPE Types.I64Type - | "f32" -> NUM_TYPE Types.F32Type - | "f64" -> NUM_TYPE Types.F64Type - | "v128" -> VEC_TYPE Types.V128Type + | "i8" -> PACK_TYPE Pack.Pack8 + | "i16" -> PACK_TYPE Pack.Pack16 + | "i32" -> NUM_TYPE Types.I32T + | "i64" -> NUM_TYPE Types.I64T + | "f32" -> NUM_TYPE Types.F32T + | "f64" -> NUM_TYPE Types.F64T + | "v128" -> VEC_TYPE Types.V128T | "i8x16" -> VEC_SHAPE (V128.I8x16 ()) | "i16x8" -> VEC_SHAPE (V128.I16x8 ()) | "i32x4" -> VEC_SHAPE (V128.I32x4 ()) @@ -149,10 +152,33 @@ rule token = parse | "f32x4" -> VEC_SHAPE (V128.F32x4 ()) | "f64x2" -> VEC_SHAPE (V128.F64x2 ()) + | "any" -> ANY + | "anyref" -> ANYREF + | "none" -> NONE + | "nullref" -> NULLREF + | "eq" -> EQ + | "eqref" -> EQREF + | "i31" -> I31 + | "i31ref" -> I31REF + | "structref" -> STRUCTREF + | "arrayref" -> ARRAYREF + | "nofunc" -> NOFUNC + | "funcref" -> FUNCREF + | "nullfuncref" -> NULLFUNCREF | "extern" -> EXTERN + | "noextern" -> NOEXTERN | "externref" -> EXTERNREF - | "funcref" -> FUNCREF + | "nullexternref" -> NULLEXTERNREF + | "ref" -> REF + | "null" -> NULL + + | "array" -> ARRAY + | "struct" -> STRUCT + | "field" -> FIELD | "mut" -> MUT + | "sub" -> SUB + | "final" -> FINAL + | "rec" -> REC | "nop" -> NOP | "unreachable" -> UNREACHABLE @@ -163,13 +189,21 @@ rule token = parse | "br" -> BR | "br_if" -> BR_IF | "br_table" -> BR_TABLE + | "br_on_null" -> BR_ON_NULL br_on_null + | "br_on_non_null" -> BR_ON_NULL br_on_non_null + | "br_on_cast" -> BR_ON_CAST br_on_cast + | "br_on_cast_fail" -> BR_ON_CAST br_on_cast_fail | "return" -> RETURN | "if" -> IF | "then" -> THEN | "else" -> ELSE | "select" -> SELECT | "call" -> CALL + | "call_ref" -> CALL_REF | "call_indirect" -> CALL_INDIRECT + | "return_call" -> RETURN_CALL + | "return_call_ref" -> RETURN_CALL_REF + | "return_call_indirect" -> RETURN_CALL_INDIRECT | "local.get" -> LOCAL_GET | "local.set" -> LOCAL_SET @@ -193,91 +227,128 @@ rule token = parse | "memory.init" -> MEMORY_INIT | "data.drop" -> DATA_DROP - | "i32.load" -> LOAD (fun a o -> i32_load (opt a 2) o) - | "i64.load" -> LOAD (fun a o -> i64_load (opt a 3) o) - | "f32.load" -> LOAD (fun a o -> f32_load (opt a 2) o) - | "f64.load" -> LOAD (fun a o -> f64_load (opt a 3) o) - | "i32.store" -> STORE (fun a o -> i32_store (opt a 2) o) - | "i64.store" -> STORE (fun a o -> i64_store (opt a 3) o) - | "f32.store" -> STORE (fun a o -> f32_store (opt a 2) o) - | "f64.store" -> STORE (fun a o -> f64_store (opt a 3) o) - - | "i32.load8_u" -> LOAD (fun a o -> i32_load8_u (opt a 0) o) - | "i32.load8_s" -> LOAD (fun a o -> i32_load8_s (opt a 0) o) - | "i32.load16_u" -> LOAD (fun a o -> i32_load16_u (opt a 1) o) - | "i32.load16_s" -> LOAD (fun a o -> i32_load16_s (opt a 1) o) - | "i64.load8_u" -> LOAD (fun a o -> i64_load8_u (opt a 0) o) - | "i64.load8_s" -> LOAD (fun a o -> i64_load8_s (opt a 0) o) - | "i64.load16_u" -> LOAD (fun a o -> i64_load16_u (opt a 1) o) - | "i64.load16_s" -> LOAD (fun a o -> i64_load16_s (opt a 1) o) - | "i64.load32_u" -> LOAD (fun a o -> i64_load32_u (opt a 2) o) - | "i64.load32_s" -> LOAD (fun a o -> i64_load32_s (opt a 2) o) - - | "i32.store8" -> LOAD (fun a o -> i32_store8 (opt a 0) o) - | "i32.store16" -> LOAD (fun a o -> i32_store16 (opt a 1) o) - | "i64.store8" -> LOAD (fun a o -> i64_store8 (opt a 0) o) - | "i64.store16" -> LOAD (fun a o -> i64_store16 (opt a 1) o) - | "i64.store32" -> LOAD (fun a o -> i64_store32 (opt a 2) o) - - | "v128.load" -> VEC_LOAD (fun a o -> v128_load (opt a 4) o) - | "v128.store" -> VEC_STORE (fun a o -> v128_store (opt a 4) o) - | "v128.load8x8_u" -> VEC_LOAD (fun a o -> v128_load8x8_u (opt a 3) o) - | "v128.load8x8_s" -> VEC_LOAD (fun a o -> v128_load8x8_s (opt a 3) o) - | "v128.load16x4_u" -> VEC_LOAD (fun a o -> v128_load16x4_u (opt a 3) o) - | "v128.load16x4_s" -> VEC_LOAD (fun a o -> v128_load16x4_s (opt a 3) o) - | "v128.load32x2_u" -> VEC_LOAD (fun a o -> v128_load32x2_u (opt a 3) o) - | "v128.load32x2_s" -> VEC_LOAD (fun a o -> v128_load32x2_s (opt a 3) o) + | "i32.load" -> LOAD (fun x a o -> i32_load x (opt a 2) o) + | "i64.load" -> LOAD (fun x a o -> i64_load x (opt a 3) o) + | "f32.load" -> LOAD (fun x a o -> f32_load x (opt a 2) o) + | "f64.load" -> LOAD (fun x a o -> f64_load x (opt a 3) o) + | "i32.store" -> STORE (fun x a o -> i32_store x (opt a 2) o) + | "i64.store" -> STORE (fun x a o -> i64_store x (opt a 3) o) + | "f32.store" -> STORE (fun x a o -> f32_store x (opt a 2) o) + | "f64.store" -> STORE (fun x a o -> f64_store x (opt a 3) o) + + | "i32.load8_u" -> LOAD (fun x a o -> i32_load8_u x (opt a 0) o) + | "i32.load8_s" -> LOAD (fun x a o -> i32_load8_s x (opt a 0) o) + | "i32.load16_u" -> LOAD (fun x a o -> i32_load16_u x (opt a 1) o) + | "i32.load16_s" -> LOAD (fun x a o -> i32_load16_s x (opt a 1) o) + | "i64.load8_u" -> LOAD (fun x a o -> i64_load8_u x (opt a 0) o) + | "i64.load8_s" -> LOAD (fun x a o -> i64_load8_s x (opt a 0) o) + | "i64.load16_u" -> LOAD (fun x a o -> i64_load16_u x (opt a 1) o) + | "i64.load16_s" -> LOAD (fun x a o -> i64_load16_s x (opt a 1) o) + | "i64.load32_u" -> LOAD (fun x a o -> i64_load32_u x (opt a 2) o) + | "i64.load32_s" -> LOAD (fun x a o -> i64_load32_s x (opt a 2) o) + + | "i32.store8" -> LOAD (fun x a o -> i32_store8 x (opt a 0) o) + | "i32.store16" -> LOAD (fun x a o -> i32_store16 x (opt a 1) o) + | "i64.store8" -> LOAD (fun x a o -> i64_store8 x (opt a 0) o) + | "i64.store16" -> LOAD (fun x a o -> i64_store16 x (opt a 1) o) + | "i64.store32" -> LOAD (fun x a o -> i64_store32 x (opt a 2) o) + + | "v128.load" -> VEC_LOAD (fun x a o -> v128_load x (opt a 4) o) + | "v128.store" -> VEC_STORE (fun x a o -> v128_store x (opt a 4) o) + | "v128.load8x8_u" -> VEC_LOAD (fun x a o -> v128_load8x8_u x (opt a 3) o) + | "v128.load8x8_s" -> VEC_LOAD (fun x a o -> v128_load8x8_s x (opt a 3) o) + | "v128.load16x4_u" -> VEC_LOAD (fun x a o -> v128_load16x4_u x (opt a 3) o) + | "v128.load16x4_s" -> VEC_LOAD (fun x a o -> v128_load16x4_s x (opt a 3) o) + | "v128.load32x2_u" -> VEC_LOAD (fun x a o -> v128_load32x2_u x (opt a 3) o) + | "v128.load32x2_s" -> VEC_LOAD (fun x a o -> v128_load32x2_s x (opt a 3) o) | "v128.load8_splat" -> - VEC_LOAD (fun a o -> v128_load8_splat (opt a 0) o) + VEC_LOAD (fun x a o -> v128_load8_splat x (opt a 0) o) | "v128.load16_splat" -> - VEC_LOAD (fun a o -> v128_load16_splat (opt a 1) o) + VEC_LOAD (fun x a o -> v128_load16_splat x (opt a 1) o) | "v128.load32_splat" -> - VEC_LOAD (fun a o -> v128_load32_splat (opt a 2) o) + VEC_LOAD (fun x a o -> v128_load32_splat x (opt a 2) o) | "v128.load64_splat" -> - VEC_LOAD (fun a o -> v128_load64_splat (opt a 3) o) + VEC_LOAD (fun x a o -> v128_load64_splat x (opt a 3) o) | "v128.load32_zero" -> - VEC_LOAD (fun a o -> v128_load32_zero (opt a 2) o) + VEC_LOAD (fun x a o -> v128_load32_zero x (opt a 2) o) | "v128.load64_zero" -> - VEC_LOAD (fun a o -> v128_load64_zero (opt a 3) o) + VEC_LOAD (fun x a o -> v128_load64_zero x (opt a 3) o) | "v128.load8_lane" -> - VEC_LOAD_LANE (fun a o i -> v128_load8_lane (opt a 0) o i) + VEC_LOAD_LANE (fun x a o i -> v128_load8_lane x (opt a 0) o i) | "v128.load16_lane" -> - VEC_LOAD_LANE (fun a o i -> v128_load16_lane (opt a 1) o i) + VEC_LOAD_LANE (fun x a o i -> v128_load16_lane x (opt a 1) o i) | "v128.load32_lane" -> - VEC_LOAD_LANE (fun a o i -> v128_load32_lane (opt a 2) o i) + VEC_LOAD_LANE (fun x a o i -> v128_load32_lane x (opt a 2) o i) | "v128.load64_lane" -> - VEC_LOAD_LANE (fun a o i -> v128_load64_lane (opt a 3) o i) + VEC_LOAD_LANE (fun x a o i -> v128_load64_lane x (opt a 3) o i) | "v128.store8_lane" -> - VEC_STORE_LANE (fun a o i -> v128_store8_lane (opt a 0) o i) + VEC_STORE_LANE (fun x a o i -> v128_store8_lane x (opt a 0) o i) | "v128.store16_lane" -> - VEC_STORE_LANE (fun a o i -> v128_store16_lane (opt a 1) o i) + VEC_STORE_LANE (fun x a o i -> v128_store16_lane x (opt a 1) o i) | "v128.store32_lane" -> - VEC_STORE_LANE (fun a o i -> v128_store32_lane (opt a 2) o i) + VEC_STORE_LANE (fun x a o i -> v128_store32_lane x (opt a 2) o i) | "v128.store64_lane" -> - VEC_STORE_LANE (fun a o i -> v128_store64_lane (opt a 3) o i) + VEC_STORE_LANE (fun x a o i -> v128_store64_lane x (opt a 3) o i) | "i32.const" -> CONST (fun s -> - let n = I32.of_string s.it in i32_const (n @@ s.at), Values.I32 n) + let n = I32.of_string s.it in i32_const (n @@ s.at), Value.I32 n) | "i64.const" -> CONST (fun s -> - let n = I64.of_string s.it in i64_const (n @@ s.at), Values.I64 n) + let n = I64.of_string s.it in i64_const (n @@ s.at), Value.I64 n) | "f32.const" -> CONST (fun s -> - let n = F32.of_string s.it in f32_const (n @@ s.at), Values.F32 n) + let n = F32.of_string s.it in f32_const (n @@ s.at), Value.F32 n) | "f64.const" -> CONST (fun s -> - let n = F64.of_string s.it in f64_const (n @@ s.at), Values.F64 n) + let n = F64.of_string s.it in f64_const (n @@ s.at), Value.F64 n) | "v128.const" -> VEC_CONST (fun shape ss at -> let v = V128.of_strings shape (List.map (fun s -> s.it) ss) in - (v128_const (v @@ at), Values.V128 v)) + (v128_const (v @@ at), Value.V128 v)) | "ref.null" -> REF_NULL | "ref.func" -> REF_FUNC + | "ref.struct" -> REF_STRUCT + | "ref.array" -> REF_ARRAY | "ref.extern" -> REF_EXTERN + | "ref.host" -> REF_HOST + | "ref.is_null" -> REF_IS_NULL + | "ref.as_non_null" -> REF_AS_NON_NULL + | "ref.test" -> REF_TEST + | "ref.cast" -> REF_CAST + | "ref.eq" -> REF_EQ + + | "ref.i31" -> REF_I31 + | "i31.get_u" -> I31_GET i31_get_u + | "i31.get_s" -> I31_GET i31_get_s + + | "struct.new" -> STRUCT_NEW struct_new + | "struct.new_default" -> STRUCT_NEW struct_new_default + | "struct.get" -> STRUCT_GET struct_get + | "struct.get_u" -> STRUCT_GET struct_get_u + | "struct.get_s" -> STRUCT_GET struct_get_s + | "struct.set" -> STRUCT_SET + + | "array.new" -> ARRAY_NEW array_new + | "array.new_default" -> ARRAY_NEW array_new_default + | "array.new_fixed" -> ARRAY_NEW_FIXED + | "array.new_elem" -> ARRAY_NEW_ELEM + | "array.new_data" -> ARRAY_NEW_DATA + | "array.get" -> ARRAY_GET array_get + | "array.get_u" -> ARRAY_GET array_get_u + | "array.get_s" -> ARRAY_GET array_get_s + | "array.set" -> ARRAY_SET + | "array.len" -> ARRAY_LEN + | "array.copy" -> ARRAY_COPY + | "array.fill" -> ARRAY_FILL + | "array.init_data" -> ARRAY_INIT_DATA + | "array.init_elem" -> ARRAY_INIT_ELEM + + | "any.convert_extern" -> EXTERN_CONVERT any_convert_extern + | "extern.convert_any" -> EXTERN_CONVERT extern_convert_any | "i32.clz" -> UNARY i32_clz | "i32.ctz" -> UNARY i32_ctz diff --git a/interpreter/text/parse.mli b/interpreter/text/parse.mli index e3d1e654fa..3a318683b6 100644 --- a/interpreter/text/parse.mli +++ b/interpreter/text/parse.mli @@ -3,10 +3,10 @@ exception Syntax of Source.region * string module type S = sig type t - val parse : string -> Lexing.lexbuf -> t - val parse_file : string -> t - val parse_string : string -> t - val parse_channel : in_channel -> t + val parse : string -> Lexing.lexbuf -> t (* raises Syntax *) + val parse_file : string -> t (* raises Syntax *) + val parse_string : string -> t (* raises Syntax *) + val parse_channel : in_channel -> t (* raises Syntax *) end module Module : S with type t = Script.var option * Script.definition diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 00a2188b5d..9eb0fc42f7 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -26,7 +26,8 @@ let positions_to_region position1 position2 = let at (l, r) = positions_to_region l r -let (@@) x loc = x @@ at loc +let (@@@) = Source.(@@) +let (@@) x loc = x @@@ at loc (* Literals *) @@ -39,24 +40,22 @@ let vec f shape ss loc = | Failure _ -> error (at loc) "constant out of range" | Invalid_argument _ -> error (at loc) "wrong number of lane literals" -let vec_lane_nan shape l at' = - let open Values in - let open Source in +let vec_lane_nan shape l at = + let open Value in match shape with - | V128.F32x4 () -> NanPat (F32 l @@ at') - | V128.F64x2 () -> NanPat (F64 l @@ at') - | _ -> error at' "invalid vector constant" + | V128.F32x4 () -> NanPat (F32 l @@@ at) + | V128.F64x2 () -> NanPat (F64 l @@@ at) + | _ -> error at "invalid vector constant" -let vec_lane_lit shape l at' = - let open Values in - let open Source in +let vec_lane_lit shape l at = + let open Value in match shape with - | V128.I8x16 () -> NumPat (I32 (I8.of_string l) @@ at') - | V128.I16x8 () -> NumPat (I32 (I16.of_string l) @@ at') - | V128.I32x4 () -> NumPat (I32 (I32.of_string l) @@ at') - | V128.I64x2 () -> NumPat (I64 (I64.of_string l) @@ at') - | V128.F32x4 () -> NumPat (F32 (F32.of_string l) @@ at') - | V128.F64x2 () -> NumPat (F64 (F64.of_string l) @@ at') + | V128.I8x16 () -> NumPat (I32 (I8.of_string l) @@@ at) + | V128.I16x8 () -> NumPat (I32 (I16.of_string l) @@@ at) + | V128.I32x4 () -> NumPat (I32 (I32.of_string l) @@@ at) + | V128.I64x2 () -> NumPat (I64 (I64.of_string l) @@@ at) + | V128.F32x4 () -> NumPat (F32 (F32.of_string l) @@@ at) + | V128.F64x2 () -> NumPat (F64 (F64.of_string l) @@@ at) let vec_lane_index s at = match int_of_string s with @@ -70,7 +69,7 @@ let shuffle_lit ss loc = let nanop f nan = let open Source in - let open Values in + let open Value in match snd (f ("0" @@ no_region)) with | F32 _ -> F32 nan.it @@ nan.at | F64 _ -> F64 nan.it @@ nan.at @@ -99,29 +98,54 @@ module VarMap = Map.Make(String) type space = {mutable map : int32 VarMap.t; mutable count : int32} let empty () = {map = VarMap.empty; count = 0l} -type types = {space : space; mutable list : type_ list} -let empty_types () = {space = empty (); list = []} +let shift category at n i = + let i' = Int32.add i n in + if I32.lt_u i' n then + error at ("too many " ^ category ^ " bindings"); + i' + +let bind category space n at = + let i = space.count in + space.count <- shift category at n i; + i + +let scoped category n space at = + {map = VarMap.map (shift category at n) space.map; count = space.count} + + +type types = + { space : space; + mutable fields : space list; + mutable list : type_ list; + mutable ctx : def_type list; + } +let empty_types () = {space = empty (); fields = []; list = []; ctx = []} type context = { types : types; tables : space; memories : space; funcs : space; locals : space; globals : space; - datas : space; elems : space; - labels : int32 VarMap.t; deferred_locals : (unit -> unit) list ref + datas : space; elems : space; labels : space; + deferred_locals : (unit -> unit) list ref } let empty_context () = { types = empty_types (); tables = empty (); memories = empty (); funcs = empty (); locals = empty (); globals = empty (); - datas = empty (); elems = empty (); - labels = VarMap.empty; deferred_locals = ref [] + datas = empty (); elems = empty (); labels = empty (); + deferred_locals = ref [] } +let enter_block (c : context) loc = {c with labels = scoped "label" 1l c.labels (at loc)} +let enter_let (c : context) loc = {c with locals = empty (); deferred_locals = ref []} +let enter_func (c : context) loc = {(enter_let c loc) with labels = empty ()} + +let defer_locals (c : context) f = + c.deferred_locals := (fun () -> ignore (f ())) :: !(c.deferred_locals) + let force_locals (c : context) = List.fold_right Stdlib.(@@) !(c.deferred_locals) (); c.deferred_locals := [] -let enter_func (c : context) = - {c with labels = VarMap.empty; locals = empty ()} let lookup category space x = try VarMap.find x.it space.map @@ -129,109 +153,130 @@ let lookup category space x = let type_ (c : context) x = lookup "type" c.types.space x let func (c : context) x = lookup "function" c.funcs x -let local (c : context) x = force_locals c; lookup "local" c.locals x +let local (c : context) x = lookup "local" c.locals x let global (c : context) x = lookup "global" c.globals x let table (c : context) x = lookup "table" c.tables x let memory (c : context) x = lookup "memory" c.memories x let elem (c : context) x = lookup "elem segment" c.elems x let data (c : context) x = lookup "data segment" c.datas x -let label (c : context) x = - try VarMap.find x.it c.labels - with Not_found -> error x.at ("unknown label " ^ x.it) +let label (c : context) x = lookup "label " c.labels x +let field x (c : context) y = + lookup "field " (Lib.List32.nth c.types.fields x) y let func_type (c : context) x = - try (Lib.List32.nth c.types.list x.it).it - with Failure _ -> error x.at ("unknown type " ^ Int32.to_string x.it) + match expand_def_type (Lib.List32.nth c.types.ctx x.it) with + | DefFuncT ft -> ft + | _ -> error x.at ("non-function type " ^ Int32.to_string x.it) + | exception Failure _ -> error x.at ("unknown type " ^ Int32.to_string x.it) -let anon category space n = - let i = space.count in - space.count <- Int32.add i n; - if I32.lt_u space.count n then - error no_region ("too many " ^ category ^ " bindings"); - i - -let bind category space x = - let i = anon category space 1l in +let bind_abs category space x = if VarMap.mem x.it space.map then error x.at ("duplicate " ^ category ^ " " ^ x.it); + let i = bind category space 1l x.at in space.map <- VarMap.add x.it i space.map; i -let bind_type (c : context) x ty = - c.types.list <- c.types.list @ [ty]; - bind "type" c.types.space x -let bind_func (c : context) x = bind "function" c.funcs x -let bind_local (c : context) x = force_locals c; bind "local" c.locals x -let bind_global (c : context) x = bind "global" c.globals x -let bind_table (c : context) x = bind "table" c.tables x -let bind_memory (c : context) x = bind "memory" c.memories x -let bind_elem (c : context) x = bind "elem segment" c.elems x -let bind_data (c : context) x = bind "data segment" c.datas x -let bind_label (c : context) x = - {c with labels = VarMap.add x.it 0l (VarMap.map (Int32.add 1l) c.labels)} - -let anon_type (c : context) ty = - c.types.list <- c.types.list @ [ty]; - anon "type" c.types.space 1l -let anon_func (c : context) = anon "function" c.funcs 1l -let anon_locals (c : context) lazy_ts = - let f () = - ignore (anon "local" c.locals (Lib.List32.length (Lazy.force lazy_ts))) - in c.deferred_locals := f :: !(c.deferred_locals) -let anon_global (c : context) = anon "global" c.globals 1l -let anon_table (c : context) = anon "table" c.tables 1l -let anon_memory (c : context) = anon "memory" c.memories 1l -let anon_elem (c : context) = anon "elem segment" c.elems 1l -let anon_data (c : context) = anon "data segment" c.datas 1l -let anon_label (c : context) = - {c with labels = VarMap.map (Int32.add 1l) c.labels} - - -let inline_type (c : context) ft loc = - match Lib.List.index_where (fun ty -> ty.it = ft) c.types.list with +let bind_rel category space x = + ignore (bind category space 1l x.at); + space.map <- VarMap.add x.it 0l space.map; + 0l + +let new_fields (c : context) = + c.types.fields <- c.types.fields @ [empty ()] + +let bind_type (c : context) x = new_fields c; bind_abs "type" c.types.space x +let bind_func (c : context) x = bind_abs "function" c.funcs x +let bind_local (c : context) x = force_locals c; bind_abs "local" c.locals x +let bind_global (c : context) x = bind_abs "global" c.globals x +let bind_table (c : context) x = bind_abs "table" c.tables x +let bind_memory (c : context) x = bind_abs "memory" c.memories x +let bind_elem (c : context) x = bind_abs "elem segment" c.elems x +let bind_data (c : context) x = bind_abs "data segment" c.datas x +let bind_label (c : context) x = bind_rel "label" c.labels x +let bind_field (c : context) x y = + bind_abs "field" (Lib.List32.nth c.types.fields x) y + +let define_type (c : context) (ty : type_) = + c.types.list <- c.types.list @ [ty] + +let define_def_type (c : context) (dt : def_type) = + assert (c.types.space.count > Lib.List32.length c.types.ctx); + c.types.ctx <- c.types.ctx @ [dt] + +let anon_type (c : context) loc = new_fields c; bind "type" c.types.space 1l (at loc) +let anon_func (c : context) loc = bind "function" c.funcs 1l (at loc) +let anon_locals (c : context) n loc = + defer_locals c (fun () -> bind "local" c.locals n (at loc)) +let anon_global (c : context) loc = bind "global" c.globals 1l (at loc) +let anon_table (c : context) loc = bind "table" c.tables 1l (at loc) +let anon_memory (c : context) loc = bind "memory" c.memories 1l (at loc) +let anon_elem (c : context) loc = bind "elem segment" c.elems 1l (at loc) +let anon_data (c : context) loc = bind "data segment" c.datas 1l (at loc) +let anon_label (c : context) loc = bind "label" c.labels 1l (at loc) +let anon_fields (c : context) x n loc = + bind "field" (Lib.List32.nth c.types.fields x) n (at loc) + + +let inline_func_type (c : context) ft loc = + let st = SubT (Final, [], DefFuncT ft) in + match + Lib.List.index_where (function + | DefT (RecT [st'], 0l) -> st = st' + | _ -> false + ) c.types.ctx + with | Some i -> Int32.of_int i @@ loc - | None -> anon_type c (ft @@ loc) @@ loc - -let inline_type_explicit (c : context) x ft loc = - if ft = FuncType ([], []) then - (* Laziness ensures that type lookup is only triggered when + | None -> + let i = anon_type c loc in + define_type c (RecT [st] @@ loc); + define_def_type c (DefT (RecT [st], 0l)); + i @@ loc + +let inline_func_type_explicit (c : context) x ft loc = + if ft = FuncT ([], []) then + (* Deferring ensures that type lookup is only triggered when symbolic identifiers are used, and not for desugared functions *) - anon_locals c (lazy (let FuncType (ts, _) = func_type c x in ts)) + defer_locals c (fun () -> + let FuncT (ts1, _ts2) = func_type c x in + bind "local" c.locals (Lib.List32.length ts1) (at loc) + ) else if ft <> func_type c x then error (at loc) "inline function type does not match explicit type"; x let index_type_of_num_type t loc = match t with - | I32Type -> I32IndexType - | I64Type -> I64IndexType + | I32T -> I32IndexType + | I64T -> I64IndexType | _ -> error (at loc) "illegal index type" let index_type_of_value_type t loc = match t with - | NumType t -> index_type_of_num_type t loc + | NumT t -> index_type_of_num_type t loc | _ -> error (at loc) "illegal index type" -let memory_data init it c x at = +let memory_data init it c x loc = let size = Int64.(div (add (of_int (String.length init)) 65535L) 65536L) in let instr = match it with - | I32IndexType -> i32_const (0l @@ at) - | I64IndexType -> i64_const (0L @@ at) in - [{mtype = MemoryType ({min = size; max = Some size}, it)} @@ at], - [{dinit = init; dmode = Active {index = x; offset = [instr @@ at] @@ at} @@ at} @@ at], + | I32IndexType -> i32_const (0l @@ loc) + | I64IndexType -> i64_const (0L @@ loc) in + let offset = [instr @@ loc] @@ loc in + [{mtype = MemoryT ({min = size; max = Some size}, it)} @@ loc], + [{dinit = init; dmode = Active {index = x; offset} @@ loc} @@ loc], [], [] -let table_data init it t c x at = +let table_data tinit init it etype c x loc = let instr = match it with - | I32IndexType -> i32_const (0l @@ at) - | I64IndexType -> i64_const (0L @@ at) in + | I32IndexType -> i32_const (0l @@ loc) + | I64IndexType -> i64_const (0L @@ loc) in + let offset = [instr @@ loc] @@ loc in let einit = init c in let size = Lib.List32.length einit in let size64 = Int64.of_int32 size in - let emode = Active {index = x; offset = [instr @@ at] @@ at} @@ at in - [{ttype = TableType ({min = size64; max = Some size64}, it, t)} @@ at], - [{etype = t; einit; emode} @@ at], + let emode = Active {index = x; offset} @@ loc in + [{ttype = TableT ({min = size64; max = Some size64}, it, etype); tinit} @@ loc], + [{etype; einit; emode} @@ loc], [], [] %} @@ -240,23 +285,40 @@ let table_data init it t c x at = %token NAT INT FLOAT STRING VAR %token NUM_TYPE %token VEC_TYPE +%token PACK_TYPE %token VEC_SHAPE -%token FUNCREF EXTERNREF EXTERN MUT +%token ANYREF NULLREF EQREF I31REF STRUCTREF ARRAYREF +%token FUNCREF NULLFUNCREF EXTERNREF NULLEXTERNREF +%token ANY NONE EQ I31 REF NOFUNC EXTERN NOEXTERN NULL +%token MUT FIELD STRUCT ARRAY SUB FINAL REC %token UNREACHABLE NOP DROP SELECT -%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE -%token CALL CALL_INDIRECT RETURN +%token BLOCK END IF THEN ELSE LOOP +%token BR BR_IF BR_TABLE +%token Ast.instr'> BR_ON_NULL +%token Types.ref_type -> Types.ref_type -> Ast.instr'> BR_ON_CAST +%token CALL CALL_REF CALL_INDIRECT +%token RETURN RETURN_CALL RETURN_CALL_REF RETURN_CALL_INDIRECT %token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET %token TABLE_GET TABLE_SET %token TABLE_SIZE TABLE_GROW TABLE_FILL TABLE_COPY TABLE_INIT ELEM_DROP %token MEMORY_SIZE MEMORY_GROW MEMORY_FILL MEMORY_COPY MEMORY_INIT DATA_DROP -%token Memory.offset -> Ast.instr'> LOAD STORE +%token int option -> Memory.offset -> Ast.instr'> LOAD STORE %token OFFSET_EQ_NAT ALIGN_EQ_NAT -%token Ast.instr' * Values.num> CONST +%token Ast.instr' * Value.num> CONST %token UNARY BINARY TEST COMPARE CONVERT -%token REF_NULL REF_FUNC REF_EXTERN REF_IS_NULL -%token Memory.offset -> Ast.instr'> VEC_LOAD VEC_STORE -%token Memory.offset -> int -> Ast.instr'> VEC_LOAD_LANE VEC_STORE_LANE -%token string Source.phrase list -> Source.region -> Ast.instr' * Values.vec> VEC_CONST +%token REF_NULL REF_FUNC REF_I31 REF_STRUCT REF_ARRAY REF_EXTERN REF_HOST +%token REF_EQ REF_IS_NULL REF_AS_NON_NULL REF_TEST REF_CAST +%token I31_GET +%token Ast.instr'> STRUCT_NEW ARRAY_NEW ARRAY_GET +%token STRUCT_SET +%token Ast.idx -> Ast.instr'> STRUCT_GET +%token ARRAY_NEW_FIXED ARRAY_NEW_ELEM ARRAY_NEW_DATA +%token ARRAY_SET ARRAY_LEN +%token ARRAY_COPY ARRAY_FILL ARRAY_INIT_DATA ARRAY_INIT_ELEM +%token EXTERN_CONVERT +%token int option -> Memory.offset -> Ast.instr'> VEC_LOAD VEC_STORE +%token int option -> Memory.offset -> int -> Ast.instr'> VEC_LOAD_LANE VEC_STORE_LANE +%token string Source.phrase list -> Source.region -> Ast.instr' * Value.vec> VEC_CONST %token VEC_UNARY VEC_BINARY VEC_TERNARY VEC_TEST %token VEC_SHIFT VEC_BITMASK VEC_SPLAT %token VEC_SHUFFLE @@ -290,73 +352,149 @@ string_list : /* Types */ -ref_kind : - | FUNC { FuncRefType } - | EXTERN { ExternRefType } +null_opt : + | /* empty */ { NoNull } + | NULL { Null } + +heap_type : + | ANY { fun c -> AnyHT } + | NONE { fun c -> NoneHT } + | EQ { fun c -> EqHT } + | I31 { fun c -> I31HT } + | STRUCT { fun c -> StructHT } + | ARRAY { fun c -> ArrayHT } + | FUNC { fun c -> FuncHT } + | NOFUNC { fun c -> NoFuncHT } + | EXTERN { fun c -> ExternHT } + | NOEXTERN { fun c -> NoExternHT } + | var { fun c -> VarHT (StatX ($1 c type_).it) } ref_type : - | FUNCREF { FuncRefType } - | EXTERNREF { ExternRefType } - -value_type : - | NUM_TYPE { NumType $1 } - | VEC_TYPE { VecType $1 } - | ref_type { RefType $1 } + | LPAR REF null_opt heap_type RPAR { fun c -> ($3, $4 c) } + | ANYREF { fun c -> (Null, AnyHT) } /* Sugar */ + | NULLREF { fun c -> (Null, NoneHT) } /* Sugar */ + | EQREF { fun c -> (Null, EqHT) } /* Sugar */ + | I31REF { fun c -> (Null, I31HT) } /* Sugar */ + | STRUCTREF { fun c -> (Null, StructHT) } /* Sugar */ + | ARRAYREF { fun c -> (Null, ArrayHT) } /* Sugar */ + | FUNCREF { fun c -> (Null, FuncHT) } /* Sugar */ + | NULLFUNCREF { fun c -> (Null, NoFuncHT) } /* Sugar */ + | EXTERNREF { fun c -> (Null, ExternHT) } /* Sugar */ + | NULLEXTERNREF { fun c -> (Null, NoExternHT) } /* Sugar */ + +val_type : + | NUM_TYPE { fun c -> NumT $1 } + | VEC_TYPE { fun c -> VecT $1 } + | ref_type { fun c -> RefT ($1 c) } + +val_type_list : + | list(val_type) + { Lib.List32.length $1, fun c -> List.map (fun f -> f c) $1 } global_type : - | value_type { GlobalType ($1, Immutable) } - | LPAR MUT value_type RPAR { GlobalType ($3, Mutable) } + | val_type { fun c -> GlobalT (Cons, $1 c) } + | LPAR MUT val_type RPAR { fun c -> GlobalT (Var, $3 c) } + +storage_type : + | val_type { fun c -> ValStorageT ($1 c) } + | PACK_TYPE { fun c -> PackStorageT $1 } + +field_type : + | storage_type { fun c -> FieldT (Cons, $1 c) } + | LPAR MUT storage_type RPAR { fun c -> FieldT (Var, $3 c) } + +field_type_list : + | /* empty */ { fun c -> [] } + | field_type field_type_list { fun c -> $1 c :: $2 c } + +struct_field_list : + | /* empty */ { fun c x -> [] } + | LPAR FIELD field_type_list RPAR struct_field_list + { fun c x -> let fts = $3 c in + ignore (anon_fields c x (Lib.List32.length fts) $loc($3)); fts @ $5 c x } + | LPAR FIELD bind_var field_type RPAR struct_field_list + { fun c x -> ignore (bind_field c x $3); $4 c :: $6 c x } -def_type : - | LPAR FUNC func_type RPAR { $3 } +struct_type : + | struct_field_list { fun c x -> StructT ($1 c x) } + +array_type : + | field_type { fun c -> ArrayT ($1 c) } func_type : | func_type_result - { FuncType ([], $1) } - | LPAR PARAM list(value_type) RPAR func_type - { let FuncType (ins, out) = $5 in FuncType ($3 @ ins, out) } - | LPAR PARAM bind_var value_type RPAR func_type /* Sugar */ - { let FuncType (ins, out) = $6 in FuncType ($4 :: ins, out) } + { fun c -> FuncT ([], $1 c) } + | LPAR PARAM val_type_list RPAR func_type + { fun c -> let FuncT (ts1, ts2) = $5 c in + FuncT (snd $3 c @ ts1, ts2) } + | LPAR PARAM bind_var val_type RPAR func_type /* Sugar */ + { fun c -> let FuncT (ts1, ts2) = $6 c in + FuncT ($4 c :: ts1, ts2) } func_type_result : | /* empty */ - { [] } - | LPAR RESULT list(value_type) RPAR func_type_result - { $3 @ $5 } + { fun c -> [] } + | LPAR RESULT val_type_list RPAR func_type_result + { fun c -> snd $3 c @ $5 c } + +str_type : + | LPAR STRUCT struct_type RPAR { fun c x -> DefStructT ($3 c x) } + | LPAR ARRAY array_type RPAR { fun c x -> DefArrayT ($3 c) } + | LPAR FUNC func_type RPAR { fun c x -> DefFuncT ($3 c) } + +sub_type : + | str_type { fun c x -> SubT (Final, [], $1 c x) } + | LPAR SUB var_list str_type RPAR + { fun c x -> SubT (NoFinal, + List.map (fun y -> VarHT (StatX y.it)) ($3 c type_), $4 c x) } + | LPAR SUB FINAL var_list str_type RPAR + { fun c x -> SubT (Final, + List.map (fun y -> VarHT (StatX y.it)) ($4 c type_), $5 c x) } table_type : - | value_type limits ref_type { TableType ($2, index_type_of_value_type $1 $sloc, $3) } - | limits ref_type { TableType ($1, I32IndexType, $2) } + | val_type limits ref_type { fun c -> TableT ($2, index_type_of_value_type ($1 c) $sloc, $3 c) } + | limits ref_type { fun c -> TableT ($1, I32IndexType, $2 c) } memory_type : - | value_type limits { MemoryType ($2, index_type_of_value_type $1 $sloc) } - | limits { MemoryType ($1, I32IndexType) } + | val_type limits { fun c -> MemoryT ($2, index_type_of_value_type ($1 c) $sloc) } + | limits { fun c -> MemoryT ($1, I32IndexType) } limits : | NAT { {min = nat64 $1 $loc($1); max = None} } | NAT NAT { {min = nat64 $1 $loc($1); max = Some (nat64 $2 $loc($2))} } type_use : - | LPAR TYPE var RPAR { $3 } + | LPAR TYPE var RPAR { fun c -> $3 c type_ } /* Immediates */ +nat32 : + | NAT { nat32 $1 $sloc } + num : | NAT { $1 @@ $sloc } | INT { $1 @@ $sloc } | FLOAT { $1 @@ $sloc } var : - | NAT { let at = $sloc in fun c lookup -> nat32 $1 at @@ at } - | VAR { let at = $sloc in fun c lookup -> lookup c ($1 @@ at) @@ at } + | NAT { fun c lookup -> nat32 $1 $sloc @@ $sloc } + | VAR { fun c lookup -> lookup c ($1 @@ $sloc) @@ $sloc } + +var_opt : + | /* empty */ { fun c lookup at -> 0l @@ at } + | var { fun c lookup at -> $1 c lookup } + +var_var_opt : + | /* empty */ { fun c lookup at -> 0l @@ at, 0l @@ at } + | var var { fun c lookup at -> $1 c lookup, $2 c lookup } var_list : | /* empty */ { fun c lookup -> [] } | var var_list { fun c lookup -> $1 c lookup :: $2 c lookup } bind_var_opt : - | /* empty */ { fun c anon bind -> anon c } + | /* empty */ { fun c anon bind -> anon c $sloc } | bind_var { fun c anon bind -> bind c $1 } /* Sugar */ bind_var : @@ -366,29 +504,35 @@ labeling_opt : | /* empty */ { fun c xs -> List.iter (fun x -> error x.at "mismatching label") xs; - anon_label c } + let c' = enter_block c $sloc in ignore (anon_label c' $sloc); c' } | bind_var { fun c xs -> List.iter (fun x -> if x.it <> $1.it then error x.at "mismatching label") xs; - bind_label c $1 } + let c' = enter_block c $sloc in ignore (bind_label c' $1); c' } labeling_end_opt : | /* empty */ { [] } | bind_var { [$1] } +offset_ : + | OFFSET_EQ_NAT { nat64 $1 $sloc } + offset_opt : | /* empty */ { 0L } - | OFFSET_EQ_NAT { nat64 $1 $sloc } + | offset_ { $1 } -align_opt : - | /* empty */ { None } +align : | ALIGN_EQ_NAT { let n = nat $1 $sloc in if not (Lib.Int.is_power_of_two n) then error (at $sloc) "alignment must be a power of two"; Some (Lib.Int.log2 n) } +align_opt : + | /* empty */ { None } + | align { $1 } + /* Instructions & Expressions */ @@ -399,8 +543,8 @@ instr_list : | call_instr_instr_list { $1 } instr1 : - | plain_instr { let at = $sloc in fun c -> [$1 c @@ at] } - | block_instr { let at = $sloc in fun c -> [$1 c @@ at] } + | plain_instr { fun c -> [$1 c @@ $sloc] } + | block_instr { fun c -> [$1 c @@ $sloc] } | expr { $1 } /* Sugar */ plain_instr : @@ -412,110 +556,158 @@ plain_instr : | BR_TABLE var var_list { fun c -> let xs, x = Lib.List.split_last ($2 c label :: $3 c label) in br_table xs x } + | BR_ON_NULL var { fun c -> $1 ($2 c label) } + | BR_ON_CAST var ref_type ref_type { fun c -> $1 ($2 c label) ($3 c) ($4 c) } | RETURN { fun c -> return } | CALL var { fun c -> call ($2 c func) } + | CALL_REF var { fun c -> call_ref ($2 c type_) } + | RETURN_CALL var { fun c -> return_call ($2 c func) } + | RETURN_CALL_REF var { fun c -> return_call_ref ($2 c type_) } | LOCAL_GET var { fun c -> local_get ($2 c local) } | LOCAL_SET var { fun c -> local_set ($2 c local) } | LOCAL_TEE var { fun c -> local_tee ($2 c local) } | GLOBAL_GET var { fun c -> global_get ($2 c global) } | GLOBAL_SET var { fun c -> global_set ($2 c global) } - | TABLE_GET var { fun c -> table_get ($2 c table) } - | TABLE_SET var { fun c -> table_set ($2 c table) } - | TABLE_SIZE var { fun c -> table_size ($2 c table) } - | TABLE_GROW var { fun c -> table_grow ($2 c table) } - | TABLE_FILL var { fun c -> table_fill ($2 c table) } - | TABLE_COPY var var { fun c -> table_copy ($2 c table) ($3 c table) } - | TABLE_INIT var var { fun c -> table_init ($2 c table) ($3 c elem) } - | TABLE_GET { let at = $sloc in fun c -> table_get (0l @@ at) } /* Sugar */ - | TABLE_SET { let at = $sloc in fun c -> table_set (0l @@ at) } /* Sugar */ - | TABLE_SIZE { let at = $sloc in fun c -> table_size (0l @@ at) } /* Sugar */ - | TABLE_GROW { let at = $sloc in fun c -> table_grow (0l @@ at) } /* Sugar */ - | TABLE_FILL { let at = $sloc in fun c -> table_fill (0l @@ at) } /* Sugar */ - | TABLE_COPY /* Sugar */ - { let at = $sloc in fun c -> table_copy (0l @@ at) (0l @@ at) } + | TABLE_GET var_opt { fun c -> table_get ($2 c table $loc($1)) } + | TABLE_SET var_opt { fun c -> table_set ($2 c table $loc($1)) } + | TABLE_SIZE var_opt { fun c -> table_size ($2 c table $loc($1)) } + | TABLE_GROW var_opt { fun c -> table_grow ($2 c table $loc($1)) } + | TABLE_FILL var_opt { fun c -> table_fill ($2 c table $loc($1)) } + | TABLE_COPY var_var_opt + { fun c -> let x, y = $2 c table $loc($1) in table_copy x y } + | TABLE_INIT var var + { fun c -> table_init ($2 c table) ($3 c elem) } | TABLE_INIT var /* Sugar */ - { let at = $sloc in fun c -> table_init (0l @@ at) ($2 c elem) } + { fun c -> table_init (0l @@ $loc($1)) ($2 c elem) } | ELEM_DROP var { fun c -> elem_drop ($2 c elem) } - | LOAD offset_opt align_opt { fun c -> $1 $3 $2 } - | STORE offset_opt align_opt { fun c -> $1 $3 $2 } - | VEC_LOAD offset_opt align_opt { fun c -> $1 $3 $2 } - | VEC_STORE offset_opt align_opt { fun c -> $1 $3 $2 } - | VEC_LOAD_LANE offset_opt align_opt NAT - { let at = at $sloc in fun c -> $1 $3 $2 (vec_lane_index $4 at) } - | VEC_STORE_LANE offset_opt align_opt NAT - { let at = at $sloc in fun c -> $1 $3 $2 (vec_lane_index $4 at) } - | MEMORY_SIZE { fun c -> memory_size } - | MEMORY_GROW { fun c -> memory_grow } - | MEMORY_FILL { fun c -> memory_fill } - | MEMORY_COPY { fun c -> memory_copy } - | MEMORY_INIT var { fun c -> memory_init ($2 c data) } + | LOAD var_opt offset_opt align_opt + { fun c -> $1 ($2 c memory $loc($1)) $4 $3 } + | STORE var_opt offset_opt align_opt + { fun c -> $1 ($2 c memory $loc($1)) $4 $3 } + | VEC_LOAD var_opt offset_opt align_opt + { fun c -> $1 ($2 c memory $loc($1)) $4 $3 } + | VEC_STORE var_opt offset_opt align_opt + { fun c -> $1 ($2 c memory $loc($1)) $4 $3 } + | VEC_LOAD_LANE lane_imms { fun c -> $2 $1 $loc($1) c } + | VEC_STORE_LANE lane_imms { fun c -> $2 $1 $loc($1) c } + | MEMORY_SIZE var_opt { fun c -> memory_size ($2 c memory $loc($1)) } + | MEMORY_GROW var_opt { fun c -> memory_grow ($2 c memory $loc($1)) } + | MEMORY_FILL var_opt { fun c -> memory_fill ($2 c memory $loc($1)) } + | MEMORY_COPY var_var_opt + { fun c -> let x, y = $2 c memory $loc($1) in memory_copy x y } + | MEMORY_INIT var var + { fun c -> memory_init ($2 c memory) ($3 c data) } + | MEMORY_INIT var /* Sugar */ + { fun c -> memory_init (0l @@ $loc($1)) ($2 c data) } | DATA_DROP var { fun c -> data_drop ($2 c data) } - | REF_NULL ref_kind { fun c -> ref_null $2 } - | REF_IS_NULL { fun c -> ref_is_null } + | REF_NULL heap_type { fun c -> ref_null ($2 c) } | REF_FUNC var { fun c -> ref_func ($2 c func) } + | REF_IS_NULL { fun c -> ref_is_null } + | REF_AS_NON_NULL { fun c -> ref_as_non_null } + | REF_TEST ref_type { fun c -> ref_test ($2 c) } + | REF_CAST ref_type { fun c -> ref_cast ($2 c) } + | REF_EQ { fun c -> ref_eq } + | REF_I31 { fun c -> ref_i31 } + | I31_GET { fun c -> $1 } + | STRUCT_NEW var { fun c -> $1 ($2 c type_) } + | STRUCT_GET var var { fun c -> let x = $2 c type_ in $1 x ($3 c (field x.it)) } + | STRUCT_SET var var { fun c -> let x = $2 c type_ in struct_set x ($3 c (field x.it)) } + | ARRAY_NEW var { fun c -> $1 ($2 c type_) } + | ARRAY_NEW_FIXED var nat32 { fun c -> array_new_fixed ($2 c type_) $3 } + | ARRAY_NEW_ELEM var var { fun c -> array_new_elem ($2 c type_) ($3 c elem) } + | ARRAY_NEW_DATA var var { fun c -> array_new_data ($2 c type_) ($3 c data) } + | ARRAY_GET var { fun c -> $1 ($2 c type_) } + | ARRAY_SET var { fun c -> array_set ($2 c type_) } + | ARRAY_LEN { fun c -> array_len } + | ARRAY_COPY var var { fun c -> array_copy ($2 c type_) ($3 c type_) } + | ARRAY_FILL var { fun c -> array_fill ($2 c type_) } + | ARRAY_INIT_DATA var var { fun c -> array_init_data ($2 c type_) ($3 c data) } + | ARRAY_INIT_ELEM var var { fun c -> array_init_elem ($2 c type_) ($3 c elem) } + | EXTERN_CONVERT { fun c -> $1 } | CONST num { fun c -> fst (num $1 $2) } | TEST { fun c -> $1 } | COMPARE { fun c -> $1 } | UNARY { fun c -> $1 } | BINARY { fun c -> $1 } | CONVERT { fun c -> $1 } - | VEC_CONST VEC_SHAPE list(num) { let at = $sloc in fun c -> fst (vec $1 $2 $3 at) } + | VEC_CONST VEC_SHAPE list(num) { fun c -> fst (vec $1 $2 $3 $sloc) } | VEC_UNARY { fun c -> $1 } | VEC_BINARY { fun c -> $1 } | VEC_TERNARY { fun c -> $1 } | VEC_TEST { fun c -> $1 } | VEC_SHIFT { fun c -> $1 } | VEC_BITMASK { fun c -> $1 } - | VEC_SHUFFLE list(num) { let at = $sloc in fun c -> i8x16_shuffle (shuffle_lit $2 at) } + | VEC_SHUFFLE list(num) { fun c -> i8x16_shuffle (shuffle_lit $2 $sloc) } | VEC_SPLAT { fun c -> $1 } - | VEC_EXTRACT NAT { let at = at $sloc in fun c -> $1 (vec_lane_index $2 at) } - | VEC_REPLACE NAT { let at = at $sloc in fun c -> $1 (vec_lane_index $2 at) } + | VEC_EXTRACT NAT { fun c -> $1 (vec_lane_index $2 (at $sloc)) } + | VEC_REPLACE NAT { fun c -> $1 (vec_lane_index $2 (at $sloc)) } + + +lane_imms : + /* Need to multiply out options and var to avoid spurious conflicts */ + | NAT offset_opt align_opt NAT + { fun instr at0 c -> + instr (nat32 $1 $loc($1) @@ $loc($1)) $3 $2 + (vec_lane_index $4 (at $loc($4))) } + | VAR offset_opt align_opt NAT /* Sugar */ + { fun instr at0 c -> instr (memory c ($1 @@ $loc($1)) @@ $loc($1)) $3 $2 + (vec_lane_index $4 (at $loc($4))) } + | offset_ align_opt NAT /* Sugar */ + { fun instr at0 c -> instr (0l @@ at0) $2 $1 + (vec_lane_index $3 (at $loc($3))) } + | align NAT /* Sugar */ + { fun instr at0 c -> instr (0l @@ at0) $1 0L + (vec_lane_index $2 (at $loc($2))) } + | NAT /* Sugar */ + { fun instr at0 c -> instr (0l @@ at0) None 0L + (vec_lane_index $1 (at $loc($1))) } select_instr_instr_list : | SELECT select_instr_results_instr_list - { let at1 = $loc($1) in - fun c -> let b, ts, es = $2 c in - (select (if b then (Some ts) else None) @@ at1) :: es } + { fun c -> let b, ts, es = $2 c in + (select (if b then (Some ts) else None) @@ $loc($1)) :: es } select_instr_results_instr_list : - | LPAR RESULT list(value_type) RPAR select_instr_results_instr_list - { fun c -> let _, ts, es = $5 c in true, $3 @ ts, es } + | LPAR RESULT val_type_list RPAR select_instr_results_instr_list + { fun c -> let _, ts, es = $5 c in true, snd $3 c @ ts, es } | instr_list { fun c -> false, [], $1 c } call_instr_instr_list : | CALL_INDIRECT var call_instr_type_instr_list - { let at1 = $loc($1) in - fun c -> let x, es = $3 c in - (call_indirect ($2 c table) x @@ at1) :: es } + { fun c -> let x, es = $3 c in + (call_indirect ($2 c table) x @@ $loc($1)) :: es } | CALL_INDIRECT call_instr_type_instr_list /* Sugar */ - { let at1 = $loc($1) in - fun c -> let x, es = $2 c in - (call_indirect (0l @@ at1) x @@ at1) :: es } + { fun c -> let x, es = $2 c in + (call_indirect (0l @@ $loc($1)) x @@ $loc($1)) :: es } + | RETURN_CALL_INDIRECT var call_instr_type_instr_list + { fun c -> let x, es = $3 c in + (return_call_indirect ($2 c table) x @@ $loc($1)) :: es } + | RETURN_CALL_INDIRECT call_instr_type_instr_list /* Sugar */ + { fun c -> let x, es = $2 c in + (return_call_indirect (0l @@ $loc($1)) x @@ $loc($1)) :: es } call_instr_type_instr_list : | type_use call_instr_params_instr_list - { let at1 = $loc($1) in - fun c -> + { fun c -> match $2 c with - | FuncType ([], []), es -> $1 c type_, es - | ft, es -> inline_type_explicit c ($1 c type_) ft at1, es } + | FuncT ([], []), es -> $1 c, es + | ft, es -> inline_func_type_explicit c ($1 c) ft $loc($1), es } | call_instr_params_instr_list - { let at = $sloc in - fun c -> let ft, es = $1 c in inline_type c ft at, es } + { fun c -> let ft, es = $1 c in inline_func_type c ft $sloc, es } call_instr_params_instr_list : - | LPAR PARAM list(value_type) RPAR call_instr_params_instr_list - { fun c -> - let FuncType (ts1, ts2), es = $5 c in FuncType ($3 @ ts1, ts2), es } + | LPAR PARAM val_type_list RPAR call_instr_params_instr_list + { fun c -> let FuncT (ts1, ts2), es = $5 c in + FuncT (snd $3 c @ ts1, ts2), es } | call_instr_results_instr_list - { fun c -> let ts, es = $1 c in FuncType ([], ts), es } + { fun c -> let ts, es = $1 c in FuncT ([], ts), es } call_instr_results_instr_list : - | LPAR RESULT list(value_type) RPAR call_instr_results_instr_list - { fun c -> let ts, es = $5 c in $3 @ ts, es } + | LPAR RESULT val_type_list RPAR call_instr_results_instr_list + { fun c -> let ts, es = $5 c in snd $3 c @ ts, es } | instr_list { fun c -> [], $1 c } @@ -533,36 +725,34 @@ block_instr : block : | type_use block_param_body - { let at1 = $loc($1) in - fun c -> - VarBlockType (inline_type_explicit c ($1 c type_) (fst $2) at1), - snd $2 c } + { fun c -> let ft, es = $2 c in + let x = inline_func_type_explicit c ($1 c) ft $loc($1) in + VarBlockType x, es } | block_param_body /* Sugar */ - { let at = $sloc in - fun c -> + { fun c -> let ft, es = $1 c in let bt = - match fst $1 with - | FuncType ([], []) -> ValBlockType None - | FuncType ([], [t]) -> ValBlockType (Some t) - | ft -> VarBlockType (inline_type c ft at) - in bt, snd $1 c } + match ft with + | FuncT ([], []) -> ValBlockType None + | FuncT ([], [t]) -> ValBlockType (Some t) + | ft -> VarBlockType (inline_func_type c ft $sloc) + in bt, es } block_param_body : | block_result_body { $1 } - | LPAR PARAM list(value_type) RPAR block_param_body - { let FuncType (ins, out) = fst $5 in - FuncType ($3 @ ins, out), snd $5 } + | LPAR PARAM val_type_list RPAR block_param_body + { fun c -> let FuncT (ts1, ts2), es = $5 c in + FuncT (snd $3 c @ ts1, ts2), es } block_result_body : - | instr_list { FuncType ([], []), $1 } - | LPAR RESULT list(value_type) RPAR block_result_body - { let FuncType (ins, out) = fst $5 in - FuncType (ins, $3 @ out), snd $5 } + | instr_list { fun c -> FuncT ([], []), $1 c } + | LPAR RESULT val_type_list RPAR block_result_body + { fun c -> let FuncT (ts1, ts2), es = $5 c in + FuncT (ts1, snd $3 c @ ts2), es } expr : /* Sugar */ | LPAR expr1 RPAR - { let at = $sloc in fun c -> let es, e' = $2 c in es @ [e' @@ at] } + { fun c -> let es, e' = $2 c in es @ [e' @@ $sloc] } expr1 : /* Sugar */ | plain_instr expr_list { fun c -> $2 c, $1 c } @@ -571,8 +761,11 @@ expr1 : /* Sugar */ | CALL_INDIRECT var call_expr_type { fun c -> let x, es = $3 c in es, call_indirect ($2 c table) x } | CALL_INDIRECT call_expr_type /* Sugar */ - { let at1 = $loc($1) in - fun c -> let x, es = $2 c in es, call_indirect (0l @@ at1) x } + { fun c -> let x, es = $2 c in es, call_indirect (0l @@ $loc($1)) x } + | RETURN_CALL_INDIRECT var call_expr_type + { fun c -> let x, es = $3 c in es, return_call_indirect ($2 c table) x } + | RETURN_CALL_INDIRECT call_expr_type /* Sugar */ + { fun c -> let x, es = $2 c in es, return_call_indirect (0l @@ $loc($1)) x } | BLOCK labeling_opt block { fun c -> let c' = $2 c [] in let bt, es = $3 c' in [], block bt es } | LOOP labeling_opt block @@ -582,63 +775,59 @@ expr1 : /* Sugar */ let bt, (es, es1, es2) = $3 c c' in es, if_ bt es1 es2 } select_expr_results : - | LPAR RESULT list(value_type) RPAR select_expr_results - { fun c -> let _, ts, es = $5 c in true, $3 @ ts, es } + | LPAR RESULT val_type_list RPAR select_expr_results + { fun c -> let _, ts, es = $5 c in true, snd $3 c @ ts, es } | expr_list { fun c -> false, [], $1 c } call_expr_type : | type_use call_expr_params - { let at1 = $loc($1) in - fun c -> + { fun c -> match $2 c with - | FuncType ([], []), es -> $1 c type_, es - | ft, es -> inline_type_explicit c ($1 c type_) ft at1, es } + | FuncT ([], []), es -> $1 c, es + | ft, es -> inline_func_type_explicit c ($1 c) ft $loc($1), es } | call_expr_params - { let at1 = $loc($1) in - fun c -> let ft, es = $1 c in inline_type c ft at1, es } + { fun c -> let ft, es = $1 c in inline_func_type c ft $loc($1), es } call_expr_params : - | LPAR PARAM list(value_type) RPAR call_expr_params - { fun c -> - let FuncType (ts1, ts2), es = $5 c in FuncType ($3 @ ts1, ts2), es } + | LPAR PARAM val_type_list RPAR call_expr_params + { fun c -> let FuncT (ts1, ts2), es = $5 c in + FuncT (snd $3 c @ ts1, ts2), es } | call_expr_results - { fun c -> let ts, es = $1 c in FuncType ([], ts), es } + { fun c -> let ts, es = $1 c in FuncT ([], ts), es } call_expr_results : - | LPAR RESULT list(value_type) RPAR call_expr_results - { fun c -> let ts, es = $5 c in $3 @ ts, es } + | LPAR RESULT val_type_list RPAR call_expr_results + { fun c -> let ts, es = $5 c in snd $3 c @ ts, es } | expr_list { fun c -> [], $1 c } if_block : | type_use if_block_param_body - { let at = $sloc in - fun c c' -> - VarBlockType (inline_type_explicit c ($1 c type_) (fst $2) at), - snd $2 c c' } + { fun c c' -> let ft, es = $2 c c' in + let x = inline_func_type_explicit c ($1 c) ft $sloc in + VarBlockType x, es } | if_block_param_body /* Sugar */ - { let at = $sloc in - fun c c' -> + { fun c c' -> let ft, es = $1 c c' in let bt = - match fst $1 with - | FuncType ([], []) -> ValBlockType None - | FuncType ([], [t]) -> ValBlockType (Some t) - | ft -> VarBlockType (inline_type c ft at) - in bt, snd $1 c c' } + match ft with + | FuncT ([], []) -> ValBlockType None + | FuncT ([], [t]) -> ValBlockType (Some t) + | ft -> VarBlockType (inline_func_type c ft $sloc) + in bt, es } if_block_param_body : | if_block_result_body { $1 } - | LPAR PARAM list(value_type) RPAR if_block_param_body - { let FuncType (ins, out) = fst $5 in - FuncType ($3 @ ins, out), snd $5 } + | LPAR PARAM val_type_list RPAR if_block_param_body + { fun c c' -> let FuncT (ts1, ts2), es = $5 c c' in + FuncT (snd $3 c @ ts1, ts2), es } if_block_result_body : - | if_ { FuncType ([], []), $1 } - | LPAR RESULT list(value_type) RPAR if_block_result_body - { let FuncType (ins, out) = fst $5 in - FuncType (ins, $3 @ out), snd $5 } + | if_ { fun c c' -> FuncT ([], []), $1 c c' } + | LPAR RESULT val_type_list RPAR if_block_result_body + { fun c c' -> let FuncT (ts1, ts2), es = $5 c c' in + FuncT (ts1, snd $3 c @ ts2), es } if_ : | expr if_ @@ -654,82 +843,93 @@ expr_list : | expr expr_list { fun c -> $1 c @ $2 c } const_expr : - | instr_list { let at = $sloc in fun c -> $1 c @@ at } + | instr_list { fun c -> $1 c @@ $sloc } + +const_expr1 : + | instr1 instr_list { fun c -> ($1 c @ $2 c) @@ $sloc } /* Functions */ func : | LPAR FUNC bind_var_opt func_fields RPAR - { let at = $sloc in - fun c -> let x = $3 c anon_func bind_func @@ at in fun () -> $4 c x at } + { fun c -> let x = $3 c anon_func bind_func @@ $sloc in + fun () -> $4 c x $sloc } func_fields : | type_use func_fields_body - { fun c x at -> - let c' = enter_func c in - let y = inline_type_explicit c' ($1 c' type_) (fst $2) at in - [{(snd $2 c') with ftype = y} @@ at], [], [] } + { fun c x loc -> + let c' = enter_func c loc in + let y = inline_func_type_explicit c' ($1 c') (fst $2 c') loc in + [{(snd $2 c') with ftype = y} @@ loc], [], [] } | func_fields_body /* Sugar */ - { fun c x at -> - let c' = enter_func c in - let y = inline_type c' (fst $1) at in - [{(snd $1 c') with ftype = y} @@ at], [], [] } + { fun c x loc -> + let c' = enter_func c loc in + let y = inline_func_type c' (fst $1 c') loc in + [{(snd $1 c') with ftype = y} @@ loc], [], [] } | inline_import type_use func_fields_import /* Sugar */ - { fun c x at -> - let y = inline_type_explicit c ($2 c type_) $3 at in + { fun c x loc -> + let y = inline_func_type_explicit c ($2 c) ($3 c) loc in [], [{ module_name = fst $1; item_name = snd $1; - idesc = FuncImport y @@ at } @@ at ], [] } + idesc = FuncImport y @@ loc } @@ loc ], [] } | inline_import func_fields_import /* Sugar */ - { fun c x at -> - let y = inline_type c $2 at in + { fun c x loc -> + let y = inline_func_type c ($2 c) loc in [], [{ module_name = fst $1; item_name = snd $1; - idesc = FuncImport y @@ at } @@ at ], [] } + idesc = FuncImport y @@ loc } @@ loc ], [] } | inline_export func_fields /* Sugar */ - { fun c x at -> - let fns, ims, exs = $2 c x at in fns, ims, $1 (FuncExport x) c :: exs } + { fun c x loc -> + let fns, ims, exs = $2 c x loc in fns, ims, $1 (FuncExport x) c :: exs } func_fields_import : /* Sugar */ | func_fields_import_result { $1 } - | LPAR PARAM list(value_type) RPAR func_fields_import - { let FuncType (ins, out) = $5 in FuncType ($3 @ ins, out) } - | LPAR PARAM bind_var value_type RPAR func_fields_import /* Sugar */ - { let FuncType (ins, out) = $6 in FuncType ($4 :: ins, out) } + | LPAR PARAM val_type_list RPAR func_fields_import + { fun c -> let FuncT (ts1, ts2) = $5 c in FuncT (snd $3 c @ ts1, ts2) } + | LPAR PARAM bind_var val_type RPAR func_fields_import /* Sugar */ + { fun c -> let FuncT (ts1, ts2) = $6 c in FuncT ($4 c :: ts1, ts2) } func_fields_import_result : /* Sugar */ - | /* empty */ { FuncType ([], []) } - | LPAR RESULT list(value_type) RPAR func_fields_import_result - { let FuncType (ins, out) = $5 in FuncType (ins, $3 @ out) } + | /* empty */ { fun c -> FuncT ([], []) } + | LPAR RESULT val_type_list RPAR func_fields_import_result + { fun c -> let FuncT (ts1, ts2) = $5 c in FuncT (ts1, snd $3 c @ ts2) } func_fields_body : | func_result_body { $1 } - | LPAR PARAM list(value_type) RPAR func_fields_body - { let FuncType (ins, out) = fst $5 in - FuncType ($3 @ ins, out), - fun c -> anon_locals c (lazy $3); snd $5 c } - | LPAR PARAM bind_var value_type RPAR func_fields_body /* Sugar */ - { let FuncType (ins, out) = fst $6 in - FuncType ($4 :: ins, out), - fun c -> ignore (bind_local c $3); snd $6 c } + | LPAR PARAM val_type_list RPAR func_fields_body + { (fun c -> let FuncT (ts1, ts2) = fst $5 c in + FuncT (snd $3 c @ ts1, ts2)), + (fun c -> anon_locals c (fst $3) $loc($3); snd $5 c) } + | LPAR PARAM bind_var val_type RPAR func_fields_body /* Sugar */ + { (fun c -> let FuncT (ts1, ts2) = fst $6 c in + FuncT ($4 c :: ts1, ts2)), + (fun c -> ignore (bind_local c $3); snd $6 c) } func_result_body : - | func_body { FuncType ([], []), $1 } - | LPAR RESULT list(value_type) RPAR func_result_body - { let FuncType (ins, out) = fst $5 in - FuncType (ins, $3 @ out), snd $5 } + | func_body { (fun c -> FuncT ([], [])), $1 } + | LPAR RESULT val_type_list RPAR func_result_body + { (fun c -> let FuncT (ts1, ts2) = fst $5 c in + FuncT (ts1, snd $3 c @ ts2)), + snd $5 } func_body : | instr_list - { fun c -> let c' = anon_label c in - {ftype = -1l @@ $sloc; locals = []; body = $1 c'} } - | LPAR LOCAL list(value_type) RPAR func_body - { fun c -> anon_locals c (lazy $3); let f = $5 c in - {f with locals = $3 @ f.Ast.locals} } - | LPAR LOCAL bind_var value_type RPAR func_body /* Sugar */ + { fun c -> ignore (anon_label c $sloc); + {ftype = -1l @@ $sloc; locals = []; body = $1 c} } + | LPAR LOCAL local_type_list RPAR func_body + { fun c -> anon_locals c (fst $3) $loc($3); let f = $5 c in + {f with locals = snd $3 c @ f.Ast.locals} } + | LPAR LOCAL bind_var local_type RPAR func_body /* Sugar */ { fun c -> ignore (bind_local c $3); let f = $6 c in - {f with locals = $4 :: f.Ast.locals} } + {f with locals = $4 c :: f.Ast.locals} } + +local_type : + | val_type { fun c -> {ltype = $1 c} @@ $sloc } + +local_type_list : + | list(local_type) + { Lib.List32.length $1, fun c -> List.map (fun f -> f c) $1 } /* Tables, Memories & Globals */ @@ -742,14 +942,14 @@ memory_use : offset : | LPAR OFFSET const_expr RPAR { $3 } - | expr { let at = $sloc in fun c -> $1 c @@ at } /* Sugar */ + | expr { fun c -> $1 c @@ $sloc } /* Sugar */ elem_kind : - | FUNC { FuncRefType } + | FUNC { (NoNull, FuncHT) } elem_expr : | LPAR ITEM const_expr RPAR { $3 } - | expr { let at = $sloc in fun c -> $1 c @@ at } /* Sugar */ + | expr { fun c -> $1 c @@ $sloc } /* Sugar */ elem_expr_list : | /* empty */ { fun c -> [] } @@ -757,132 +957,132 @@ elem_expr_list : elem_var_list : | var_list - { let f = function {at = at'; _} as x -> Source.([ref_func x @@ at'] @@ at') in + { let f = function {at; _} as x -> [ref_func x @@@ at] @@@ at in fun c -> List.map f ($1 c func) } elem_list : | elem_kind elem_var_list - { ($1, fun c -> $2 c) } + { fun c -> $1, $2 c } | ref_type elem_expr_list - { ($1, fun c -> $2 c) } + { fun c -> $1 c, $2 c } elem : | LPAR ELEM bind_var_opt elem_list RPAR - { let at = $sloc in - fun c -> ignore ($3 c anon_elem bind_elem); - fun () -> - { etype = (fst $4); einit = (snd $4) c; emode = Passive @@ at } @@ at } + { fun c -> ignore ($3 c anon_elem bind_elem); + fun () -> let etype, einit = $4 c in + { etype; einit; emode = Passive @@ $sloc } @@ $sloc } | LPAR ELEM bind_var_opt table_use offset elem_list RPAR - { let at = $sloc in - fun c -> ignore ($3 c anon_elem bind_elem); - fun () -> - { etype = (fst $6); einit = (snd $6) c; - emode = Active {index = $4 c table; offset = $5 c} @@ at } @@ at } + { fun c -> ignore ($3 c anon_elem bind_elem); + fun () -> let etype, einit = $6 c in + { etype; einit; + emode = Active {index = $4 c table; offset = $5 c} @@ $sloc } @@ $sloc } | LPAR ELEM bind_var_opt DECLARE elem_list RPAR - { let at = $sloc in - fun c -> ignore ($3 c anon_elem bind_elem); - fun () -> - { etype = (fst $5); einit = (snd $5) c; emode = Declarative @@ at } @@ at } + { fun c -> ignore ($3 c anon_elem bind_elem); + fun () -> let etype, einit = $5 c in + { etype; einit; emode = Declarative @@ $sloc } @@ $sloc } | LPAR ELEM bind_var_opt offset elem_list RPAR /* Sugar */ - { let at = $sloc in - fun c -> ignore ($3 c anon_elem bind_elem); - fun () -> - { etype = (fst $5); einit = (snd $5) c; - emode = Active {index = 0l @@ at; offset = $4 c} @@ at } @@ at } + { fun c -> ignore ($3 c anon_elem bind_elem); + fun () -> let etype, einit = $5 c in + { etype; einit; + emode = Active {index = 0l @@ $sloc; offset = $4 c} @@ $sloc } @@ $sloc } | LPAR ELEM bind_var_opt offset elem_var_list RPAR /* Sugar */ - { let at = $sloc in - fun c -> ignore ($3 c anon_elem bind_elem); + { fun c -> ignore ($3 c anon_elem bind_elem); fun () -> - { etype = FuncRefType; einit = $5 c; - emode = Active {index = 0l @@ at; offset = $4 c} @@ at } @@ at } + { etype = (NoNull, FuncHT); einit = $5 c; + emode = Active {index = 0l @@ $sloc; offset = $4 c} @@ $sloc } @@ $sloc } table : | LPAR TABLE bind_var_opt table_fields RPAR - { let at = $sloc in - fun c -> let x = $3 c anon_table bind_table @@ at in - fun () -> $4 c x at } + { fun c -> let x = $3 c anon_table bind_table @@ $sloc in + fun () -> $4 c x $sloc } table_fields : - | table_type - { fun c x at -> [{ttype = $1} @@ at], [], [], [] } + | table_type const_expr1 + { fun c x loc -> [{ttype = $1 c; tinit = $2 c} @@ loc], [], [], [] } + | table_type /* Sugar */ + { fun c x loc -> let TableT (_, _, (_, ht)) as ttype = $1 c in + [{ttype; tinit = [RefNull ht @@ loc] @@ loc} @@ loc], [], [], [] } | inline_import table_type /* Sugar */ - { fun c x at -> + { fun c x loc -> [], [], [{ module_name = fst $1; item_name = snd $1; - idesc = TableImport $2 @@ at } @@ at], [] } + idesc = TableImport ($2 c) @@ loc } @@ loc], [] } | inline_export table_fields /* Sugar */ - { fun c x at -> let tabs, elems, ims, exs = $2 c x at in + { fun c x loc -> let tabs, elems, ims, exs = $2 c x loc in tabs, elems, ims, $1 (TableExport x) c :: exs } | ref_type LPAR ELEM elem_expr elem_expr_list RPAR /* Sugar */ - { fun c x at -> - let offset = [i32_const (0l @@ at) @@ at] @@ at in + { fun c x loc -> + let offset = [i32_const (0l @@ loc) @@ loc] @@ loc in let einit = $4 c :: $5 c in let size = Lib.List32.length einit in let size64 = Int64.of_int32 size in - let emode = Active {index = x; offset} @@ at in - [{ttype = TableType ({min = size64; max = Some size64}, I32IndexType, $1)} @@ at], - [{etype = $1; einit; emode} @@ at], + let emode = Active {index = x; offset} @@ loc in + let (_, ht) as etype = $1 c in + let tinit = [RefNull ht @@ loc] @@ loc in + [{ttype = TableT ({min = size64; max = Some size64}, I32IndexType, etype); tinit} @@ loc], + [{etype; einit; emode} @@ loc], [], [] } | ref_type LPAR ELEM elem_var_list RPAR /* Sugar */ - { table_data $4 I32IndexType FuncRefType } - | value_type ref_type LPAR ELEM elem_var_list RPAR /* Sugar */ - { table_data $5 (index_type_of_value_type $1 $sloc) FuncRefType } + { fun c x loc -> + let (_, ht) as etype = $1 c in + let tinit = [RefNull ht @@ loc] @@ loc in + table_data tinit $4 I32IndexType etype c x loc } + | val_type ref_type LPAR ELEM elem_var_list RPAR /* Sugar */ + { fun c x loc -> + let (_, ht) as etype = $2 c in + let tinit = [RefNull ht @@ loc] @@ loc in + table_data tinit $5 (index_type_of_value_type ($1 c) loc) etype c x loc } data : | LPAR DATA bind_var_opt string_list RPAR - { let at = $sloc in - fun c -> ignore ($3 c anon_data bind_data); - fun () -> {dinit = $4; dmode = Passive @@ at} @@ at } + { fun c -> ignore ($3 c anon_data bind_data); + fun () -> {dinit = $4; dmode = Passive @@ $sloc} @@ $sloc } | LPAR DATA bind_var_opt memory_use offset string_list RPAR - { let at = $sloc in - fun c -> ignore ($3 c anon_data bind_data); + { fun c -> ignore ($3 c anon_data bind_data); fun () -> - {dinit = $6; dmode = Active {index = $4 c memory; offset = $5 c} @@ at} @@ at } + {dinit = $6; dmode = Active {index = $4 c memory; offset = $5 c} @@ $sloc} @@ $sloc } | LPAR DATA bind_var_opt offset string_list RPAR /* Sugar */ - { let at = $sloc in - fun c -> ignore ($3 c anon_data bind_data); + { fun c -> ignore ($3 c anon_data bind_data); fun () -> - {dinit = $5; dmode = Active {index = 0l @@ at; offset = $4 c} @@ at} @@ at } + {dinit = $5; dmode = Active {index = 0l @@ $sloc; offset = $4 c} @@ $sloc} @@ $sloc } memory : | LPAR MEMORY bind_var_opt memory_fields RPAR - { let at = $sloc in - fun c -> let x = $3 c anon_memory bind_memory @@ at in - fun () -> $4 c x at } + { fun c -> let x = $3 c anon_memory bind_memory @@ $sloc in + fun () -> $4 c x $sloc } memory_fields : | memory_type - { fun c x at -> [{mtype = $1} @@ at], [], [], [] } + { fun c x loc -> [{mtype = $1 c} @@ loc], [], [], [] } | inline_import memory_type /* Sugar */ - { fun c x at -> + { fun c x loc -> [], [], [{ module_name = fst $1; item_name = snd $1; - idesc = MemoryImport $2 @@ at } @@ at], [] } + idesc = MemoryImport ($2 c) @@ loc } @@ loc], [] } | inline_export memory_fields /* Sugar */ - { fun c x at -> let mems, data, ims, exs = $2 c x at in + { fun c x loc -> let mems, data, ims, exs = $2 c x loc in mems, data, ims, $1 (MemoryExport x) c :: exs } | LPAR DATA string_list RPAR /* Sugar */ { memory_data $3 I32IndexType } - | value_type LPAR DATA string_list RPAR /* Sugar */ - { memory_data $4 (index_type_of_value_type $1 $sloc) } + | val_type LPAR DATA string_list RPAR /* Sugar */ + { fun c x loc -> memory_data $4 (index_type_of_value_type ($1 c) $sloc) c x loc } global : | LPAR GLOBAL bind_var_opt global_fields RPAR - { let at = $sloc in - fun c -> let x = $3 c anon_global bind_global @@ at in - fun () -> $4 c x at } + { fun c -> let x = $3 c anon_global bind_global @@ $sloc in + fun () -> $4 c x $sloc } global_fields : | global_type const_expr - { fun c x at -> [{gtype = $1; ginit = $2 c} @@ at], [], [] } + { fun c x loc -> [{gtype = $1 c; ginit = $2 c} @@ loc], [], [] } | inline_import global_type /* Sugar */ - { fun c x at -> + { fun c x loc -> [], [{ module_name = fst $1; item_name = snd $1; - idesc = GlobalImport $2 @@ at } @@ at], [] } + idesc = GlobalImport ($2 c) @@ loc } @@ loc], [] } | inline_export global_fields /* Sugar */ - { fun c x at -> let globs, ims, exs = $2 c x at in + { fun c x loc -> let globs, ims, exs = $2 c x loc in globs, ims, $1 (GlobalExport x) c :: exs } @@ -891,26 +1091,24 @@ global_fields : import_desc : | LPAR FUNC bind_var_opt type_use RPAR { fun c -> ignore ($3 c anon_func bind_func); - fun () -> FuncImport ($4 c type_) } + fun () -> FuncImport ($4 c) } | LPAR FUNC bind_var_opt func_type RPAR /* Sugar */ - { let at4 = $loc($4) in - fun c -> ignore ($3 c anon_func bind_func); - fun () -> FuncImport (inline_type c $4 at4) } + { fun c -> ignore ($3 c anon_func bind_func); + fun () -> FuncImport (inline_func_type c ($4 c) $loc($4)) } | LPAR TABLE bind_var_opt table_type RPAR { fun c -> ignore ($3 c anon_table bind_table); - fun () -> TableImport $4 } + fun () -> TableImport ($4 c) } | LPAR MEMORY bind_var_opt memory_type RPAR { fun c -> ignore ($3 c anon_memory bind_memory); - fun () -> MemoryImport $4 } + fun () -> MemoryImport ($4 c) } | LPAR GLOBAL bind_var_opt global_type RPAR { fun c -> ignore ($3 c anon_global bind_global); - fun () -> GlobalImport $4 } + fun () -> GlobalImport ($4 c) } import : | LPAR IMPORT name name import_desc RPAR - { let at = $sloc and at5 = $loc($5) in - fun c -> let df = $5 c in - fun () -> {module_name = $3; item_name = $4; idesc = df () @@ at5} @@ at } + { fun c -> let df = $5 c in + fun () -> {module_name = $3; item_name = $4; idesc = df () @@ $loc($5)} @@ $sloc } inline_import : | LPAR IMPORT name name RPAR { $3, $4 } @@ -923,85 +1121,114 @@ export_desc : export : | LPAR EXPORT name export_desc RPAR - { let at = $sloc and at4 = $loc($4) in - fun c -> {name = $3; edesc = $4 c @@ at4} @@ at } + { fun c -> {name = $3; edesc = $4 c @@ $loc($4)} @@ $sloc } inline_export : | LPAR EXPORT name RPAR - { let at = $sloc in fun d c -> {name = $3; edesc = d @@ at} @@ at } + { fun d c -> {name = $3; edesc = d @@ $sloc} @@ $sloc } /* Modules */ -type_ : - | def_type { $1 @@ $sloc } - type_def : - | LPAR TYPE type_ RPAR - { fun c -> anon_type c $3 } - | LPAR TYPE bind_var type_ RPAR /* Sugar */ - { fun c -> bind_type c $3 $4 } + | LPAR TYPE sub_type RPAR + { fun c -> let x = anon_type c $sloc in fun () -> $3 c x } + | LPAR TYPE bind_var sub_type RPAR /* Sugar */ + { fun c -> let x = bind_type c $3 in fun () -> $4 c x } + +type_def_list : + | /* empty */ { fun c () -> [] } + | type_def type_def_list + { fun c -> let tf = $1 c in let tsf = $2 c in fun () -> + let st = tf () and sts = tsf () in st::sts } + +rec_type : + | type_def + { fun c -> let tf = $1 c in fun () -> + let st = tf () in + define_def_type c (DefT (RecT [st], 0l)); + RecT [st] } + | LPAR REC type_def_list RPAR + { fun c -> let tf = $3 c in fun () -> + let sts = tf () in + Lib.List32.iteri (fun i _ -> define_def_type c (DefT (RecT sts, i))) sts; + RecT sts } + +type_ : + | rec_type + { fun c -> let tf = $1 c in fun () -> define_type c (tf () @@ $sloc) } start : | LPAR START var RPAR - { let at = $sloc in fun c -> {sfunc = $3 c func} @@ at } + { fun c -> {sfunc = $3 c func} @@ $sloc } module_fields : | /* empty */ - { fun (c : context) () -> {empty_module with types = c.types.list} } + { fun (c : context) () () -> {empty_module with types = c.types.list} } | module_fields1 { $1 } module_fields1 : - | type_def module_fields - { fun c -> ignore ($1 c); $2 c } + | type_ module_fields + { fun c -> let tf = $1 c in let mff = $2 c in + fun () -> tf (); mff () } | global module_fields - { fun c -> let gf = $1 c in let mf = $2 c in + { fun c -> let gf = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let globs, ims, exs = gf () in let m = mf () in if globs <> [] && m.imports <> [] then error (List.hd m.imports).at "import after global definition"; { m with globals = globs @ m.globals; imports = ims @ m.imports; exports = exs @ m.exports } } | table module_fields - { fun c -> let tf = $1 c in let mf = $2 c in + { fun c -> let tf = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let tabs, elems, ims, exs = tf () in let m = mf () in if tabs <> [] && m.imports <> [] then error (List.hd m.imports).at "import after table definition"; { m with tables = tabs @ m.tables; elems = elems @ m.elems; imports = ims @ m.imports; exports = exs @ m.exports } } | memory module_fields - { fun c -> let mmf = $1 c in let mf = $2 c in + { fun c -> let mmf = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let mems, data, ims, exs = mmf () in let m = mf () in if mems <> [] && m.imports <> [] then error (List.hd m.imports).at "import after memory definition"; { m with memories = mems @ m.memories; datas = data @ m.datas; imports = ims @ m.imports; exports = exs @ m.exports } } | func module_fields - { fun c -> let ff = $1 c in let mf = $2 c in + { fun c -> let ff = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let funcs, ims, exs = ff () in let m = mf () in if funcs <> [] && m.imports <> [] then error (List.hd m.imports).at "import after function definition"; { m with funcs = funcs @ m.funcs; imports = ims @ m.imports; exports = exs @ m.exports } } | elem module_fields - { fun c -> let ef = $1 c in let mf = $2 c in + { fun c -> let ef = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let elems = ef () in let m = mf () in {m with elems = elems :: m.Ast.elems} } | data module_fields - { fun c -> let df = $1 c in let mf = $2 c in + { fun c -> let df = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let data = df () in let m = mf () in {m with datas = data :: m.Ast.datas} } | start module_fields - { fun c -> let mf = $2 c in - fun () -> let m = mf () in let x = $1 c in + { fun c -> let mff = $2 c in + fun () -> let mf = mff () in + fun () -> let m = mf () in + let x = $1 c in match m.start with | Some _ -> error x.at "multiple start sections" | None -> {m with start = Some x} } | import module_fields - { fun c -> let imf = $1 c in let mf = $2 c in + { fun c -> let imf = $1 c in let mff = $2 c in + fun () -> let mf = mff () in fun () -> let im = imf () in let m = mf () in {m with imports = im :: m.imports} } | export module_fields - { fun c -> let mf = $2 c in + { fun c -> let mff = $2 c in + fun () -> let mf = mff () in fun () -> let m = mf () in {m with exports = $1 c :: m.exports} } @@ -1010,13 +1237,13 @@ module_var : module_ : | LPAR MODULE option(module_var) module_fields RPAR - { $3, Textual ($4 (empty_context ()) () @@ $sloc) @@ $sloc } + { $3, Textual ($4 (empty_context ()) () () @@ $sloc) @@ $sloc } inline_module : /* Sugar */ - | module_fields { Textual ($1 (empty_context ()) () @@ $sloc) @@ $sloc } + | module_fields { Textual ($1 (empty_context ()) () () @@ $sloc) @@ $sloc } inline_module1 : /* Sugar */ - | module_fields1 { Textual ($1 (empty_context ()) () @@ $sloc) @@ $sloc } + | module_fields1 { Textual ($1 (empty_context ()) () () @@ $sloc) @@ $sloc } /* Scripts */ @@ -1070,13 +1297,14 @@ literal_vec : | LPAR VEC_CONST VEC_SHAPE list(num) RPAR { snd (vec $2 $3 $4 $sloc) } literal_ref : - | LPAR REF_NULL ref_kind RPAR { Values.NullRef $3 } - | LPAR REF_EXTERN NAT RPAR { ExternRef (nat32 $3 $loc($3)) } + | LPAR REF_NULL heap_type RPAR { Value.NullRef ($3 (empty_context ())) } + | LPAR REF_HOST NAT RPAR { Script.HostRef (nat32 $3 $loc($3)) } + | LPAR REF_EXTERN NAT RPAR { Extern.ExternRef (Script.HostRef (nat32 $3 $loc($3))) } literal : - | literal_num { Values.Num $1 @@ $sloc } - | literal_vec { Values.Vec $1 @@ $sloc } - | literal_ref { Values.Ref $1 @@ $sloc } + | literal_num { Value.Num $1 @@ $sloc } + | literal_vec { Value.Vec $1 @@ $sloc } + | literal_ref { Value.Ref $1 @@ $sloc } numpat : | num { fun sh -> vec_lane_lit sh $1.it $1.at } @@ -1086,13 +1314,19 @@ result : | literal_num { NumResult (NumPat ($1 @@ $sloc)) @@ $sloc } | LPAR CONST NAN RPAR { NumResult (NanPat (nanop $2 ($3 @@ $loc($3)))) @@ $sloc } | literal_ref { RefResult (RefPat ($1 @@ $sloc)) @@ $sloc } - | LPAR REF_FUNC RPAR { RefResult (RefTypePat FuncRefType) @@ $sloc } - | LPAR REF_EXTERN RPAR { RefResult (RefTypePat ExternRefType) @@ $sloc } - | LPAR VEC_CONST VEC_SHAPE list(numpat) RPAR { - if V128.num_lanes $3 <> List.length $4 then - error (at $sloc) "wrong number of lane literals"; - VecResult (VecPat (Values.V128 ($3, List.map (fun lit -> lit $3) $4))) @@ $sloc - } + | LPAR REF RPAR { RefResult (RefTypePat AnyHT) @@ $sloc } + | LPAR REF_EQ RPAR { RefResult (RefTypePat EqHT) @@ $sloc } + | LPAR REF_I31 RPAR { RefResult (RefTypePat I31HT) @@ $sloc } + | LPAR REF_STRUCT RPAR { RefResult (RefTypePat StructHT) @@ $sloc } + | LPAR REF_ARRAY RPAR { RefResult (RefTypePat ArrayHT) @@ $sloc } + | LPAR REF_FUNC RPAR { RefResult (RefTypePat FuncHT) @@ $sloc } + | LPAR REF_EXTERN RPAR { RefResult (RefTypePat ExternHT) @@ $sloc } + | LPAR REF_NULL RPAR { RefResult NullPat @@ $sloc } + | LPAR VEC_CONST VEC_SHAPE list(numpat) RPAR + { if V128.num_lanes $3 <> List.length $4 then + error (at $sloc) "wrong number of lane literals"; + VecResult (VecPat + (Value.V128 ($3, List.map (fun lit -> lit $3) $4))) @@ $sloc } script : | list(cmd) EOF { $1 } diff --git a/interpreter/util/lib.ml b/interpreter/util/lib.ml index 051661e862..b10480073a 100644 --- a/interpreter/util/lib.ml +++ b/interpreter/util/lib.ml @@ -3,11 +3,12 @@ type void = | module Fun = struct let id x = x + let flip f x y = f y x let curry f x y = f (x, y) let uncurry f (x, y) = f x y let rec repeat n f x = - if n = 0 then () else (f x; repeat (n - 1) f x) + if n = 0 then x else repeat (n - 1) f (f x) end module Int = @@ -63,10 +64,6 @@ struct and make' n x xs = if n = 0 then xs else make' (n - 1) x (x::xs) - let rec table n f = table' n f [] - and table' n f xs = - if n = 0 then xs else table' (n - 1) f (f (n - 1) :: xs) - let rec take n xs = match n, xs with | 0, _ -> [] @@ -79,6 +76,18 @@ struct | n, _::xs' when n > 0 -> drop (n - 1) xs' | _ -> failwith "drop" + let rec split n xs = split' n [] xs + and split' n xs ys = + match n, ys with + | 0, _ -> List.rev xs, ys + | n, y::ys' when n > 0 -> split' (n - 1) (y::xs) ys' + | _ -> failwith "split" + + let rec lead = function + | x::[] -> [] + | x::xs -> x :: lead xs + | [] -> failwith "last" + let rec last = function | x::[] -> x | _::xs -> last xs @@ -98,17 +107,6 @@ struct let index_of x = index_where ((=) x) - let rec map_filter f = function - | [] -> [] - | x::xs -> - match f x with - | None -> map_filter f xs - | Some y -> y :: map_filter f xs - - let rec concat_map f = function - | [] -> [] - | x::xs -> f x @ concat_map f xs - let rec pairwise f = function | [] -> [] | x1::x2::xs -> f x1 x2 :: pairwise f xs @@ -117,6 +115,10 @@ end module List32 = struct + let rec init n f = init' n f [] + and init' n f xs = + if n = 0l then xs else init' (Int32.sub n 1l) f (f (Int32.sub n 1l) :: xs) + let rec make n x = make' n x [] and make' n x xs = if n = 0l then xs else make' (Int32.sub n 1l) x (x::xs) @@ -134,6 +136,12 @@ struct | n, _::xs' when n > 0l -> nth xs' (Int32.sub n 1l) | _ -> failwith "nth" + let rec replace xs n y = + match n, xs with + | 0l, _::xs' -> y::xs' + | n, x::xs' when n > 0l -> x :: replace xs' (Int32.sub n 1l) y + | _ -> failwith "replace" + let rec take n xs = match n, xs with | 0l, _ -> [] @@ -146,14 +154,32 @@ struct | n, _::xs' when n > 0l -> drop (Int32.sub n 1l) xs' | _ -> failwith "drop" + let rec iteri f xs = iteri' f 0l xs + and iteri' f i = function + | [] -> () + | x::xs -> f i x; iteri' f (Int32.add i 1l) xs + let rec mapi f xs = mapi' f 0l xs and mapi' f i = function | [] -> [] | x::xs -> f i x :: mapi' f (Int32.add i 1l) xs + + let rec index_where p xs = index_where' p xs 0l + and index_where' p xs i = + match xs with + | [] -> None + | x::xs' when p x -> Some i + | x::xs' -> index_where' p xs' (Int32.add i 1l) + + let index_of x = index_where ((=) x) end module List64 = struct + let rec init n f = init' n f [] + and init' n f xs = + if n = 0L then xs else init' (Int64.sub n 1L) f (f (Int64.sub n 1L) :: xs) + let rec make n x = make' n x [] and make' n x xs = if n = 0L then xs else make' (Int64.sub n 1L) x (x::xs) @@ -251,3 +277,15 @@ struct | Some x -> f x | None -> () end + +module Promise = +struct + type 'a t = 'a option ref + + exception Promise + + let make () = ref None + let fulfill p x = if !p = None then p := Some x else raise Promise + let value_opt p = !p + let value p = match !p with Some x -> x | None -> raise Promise +end diff --git a/interpreter/util/lib.mli b/interpreter/util/lib.mli index 9179956f9f..1c29c5d646 100644 --- a/interpreter/util/lib.mli +++ b/interpreter/util/lib.mli @@ -5,41 +5,48 @@ type void = | module Fun : sig val id : 'a -> 'a + val flip : ('a -> 'b -> 'c) -> ('b -> 'a -> 'c) val curry : ('a * 'b -> 'c) -> ('a -> 'b -> 'c) val uncurry : ('a -> 'b -> 'c) -> ('a * 'b -> 'c) - val repeat : int -> ('a -> unit) -> 'a -> unit + val repeat : int -> ('a -> 'a) -> 'a -> 'a end module List : sig val make : int -> 'a -> 'a list - val table : int -> (int -> 'a) -> 'a list val take : int -> 'a list -> 'a list (* raises Failure *) val drop : int -> 'a list -> 'a list (* raises Failure *) + val split : int -> 'a list -> 'a list * 'a list (* raises Failure *) + val lead : 'a list -> 'a list (* raises Failure *) val last : 'a list -> 'a (* raises Failure *) val split_last : 'a list -> 'a list * 'a (* raises Failure *) val index_of : 'a -> 'a list -> int option val index_where : ('a -> bool) -> 'a list -> int option - val map_filter : ('a -> 'b option) -> 'a list -> 'b list - val concat_map : ('a -> 'b list) -> 'a list -> 'b list val pairwise : ('a -> 'a -> 'b) -> 'a list -> 'b list end module List32 : sig + val init : int32 -> (int32 -> 'a) -> 'a list val make : int32 -> 'a -> 'a list val length : 'a list -> int32 val nth : 'a list -> int32 -> 'a (* raises Failure *) + val replace : 'a list -> int32 -> 'a -> 'a list (* raises Failure *) val take : int32 -> 'a list -> 'a list (* raises Failure *) val drop : int32 -> 'a list -> 'a list (* raises Failure *) + val iteri : (int32 -> 'a -> unit) -> 'a list -> unit val mapi : (int32 -> 'a -> 'b) -> 'a list -> 'b list + + val index_of : 'a -> 'a list -> int32 option + val index_where : ('a -> bool) -> 'a list -> int32 option end module List64 : sig + val init : int64 -> (int64 -> 'a) -> 'a list val make : int64 -> 'a -> 'a list val length : 'a list -> int64 val nth : 'a list -> int64 -> 'a (* raises Failure *) @@ -93,3 +100,13 @@ sig val breakup : string -> int -> string list val find_from_opt : (char -> bool) -> string -> int -> int option end + +module Promise : +sig + type 'a t + exception Promise + val make : unit -> 'a t + val fulfill : 'a t -> 'a -> unit + val value : 'a t -> 'a + val value_opt : 'a t -> 'a option +end diff --git a/interpreter/util/source.ml b/interpreter/util/source.ml index f659f6f235..a3dc54c09e 100644 --- a/interpreter/util/source.ml +++ b/interpreter/util/source.ml @@ -3,7 +3,8 @@ type region = {left : pos; right : pos} type 'a phrase = {at : region; it : 'a} let (@@) x region = {it = x; at = region} -let at region x = x @@ region +let it phrase = phrase.it +let at phrase = phrase.at (* Positions and regions *) diff --git a/interpreter/util/source.mli b/interpreter/util/source.mli index 240fe64138..a4bc79bc90 100644 --- a/interpreter/util/source.mli +++ b/interpreter/util/source.mli @@ -9,4 +9,5 @@ val string_of_pos : pos -> string val string_of_region : region -> string val (@@) : 'a -> region -> 'a phrase -val at : region -> 'a -> 'a phrase +val it : 'a phrase -> 'a +val at : 'a phrase -> region diff --git a/interpreter/valid/match.ml b/interpreter/valid/match.ml new file mode 100644 index 0000000000..6eead8dc6a --- /dev/null +++ b/interpreter/valid/match.ml @@ -0,0 +1,170 @@ +open Types + + +(* Context *) + +type context = def_type list + +let lookup c x = Lib.List32.nth c x + + +(* Extremas *) + +let abs_of_str_type _c = function + | DefStructT _ | DefArrayT _ -> StructHT + | DefFuncT _ -> FuncHT + +let rec top_of_str_type c st = + top_of_heap_type c (abs_of_str_type c st) + +and top_of_heap_type c = function + | AnyHT | NoneHT | EqHT | StructHT | ArrayHT | I31HT -> AnyHT + | FuncHT | NoFuncHT -> FuncHT + | ExternHT | NoExternHT -> ExternHT + | DefHT dt -> top_of_str_type c (expand_def_type dt) + | VarHT (StatX x) -> top_of_str_type c (expand_def_type (lookup c x)) + | VarHT (RecX _) | BotHT -> assert false + +let rec bot_of_str_type c st = + bot_of_heap_type c (abs_of_str_type c st) + +and bot_of_heap_type c = function + | AnyHT | NoneHT | EqHT | StructHT | ArrayHT | I31HT -> NoneHT + | FuncHT | NoFuncHT -> NoFuncHT + | ExternHT | NoExternHT -> NoExternHT + | DefHT dt -> bot_of_str_type c (expand_def_type dt) + | VarHT (StatX x) -> bot_of_str_type c (expand_def_type (lookup c x)) + | VarHT (RecX _) | BotHT -> assert false + + +(* Subtyping *) + +let match_null _c nul1 nul2 = + match nul1, nul2 with + | NoNull, Null -> true + | _, _ -> nul1 = nul2 + +let match_limits _c lim1 lim2 = + I64.ge_u lim1.min lim2.min && + match lim1.max, lim2.max with + | _, None -> true + | None, Some _ -> false + | Some i, Some j -> I64.le_u i j + + +let match_num_type _c t1 t2 = + t1 = t2 + +let match_vec_type _c t1 t2 = + t1 = t2 + +let rec match_heap_type c t1 t2 = + match t1, t2 with + | AnyHT, AnyHT -> true + | EqHT, AnyHT -> true + | StructHT, AnyHT -> true + | ArrayHT, AnyHT -> true + | I31HT, AnyHT -> true + | I31HT, EqHT -> true + | StructHT, EqHT -> true + | ArrayHT, EqHT -> true + | ExternHT, ExternHT -> true + | NoneHT, t -> match_heap_type c t AnyHT + | NoFuncHT, t -> match_heap_type c t FuncHT + | NoExternHT, t -> match_heap_type c t ExternHT + | VarHT (StatX x1), _ -> match_heap_type c (DefHT (lookup c x1)) t2 + | _, VarHT (StatX x2) -> match_heap_type c t1 (DefHT (lookup c x2)) + | DefHT dt1, DefHT dt2 -> match_def_type c dt1 dt2 + | DefHT dt, t -> + (match expand_def_type dt, t with + | DefStructT _, AnyHT -> true + | DefStructT _, EqHT -> true + | DefStructT _, StructHT -> true + | DefArrayT _, AnyHT -> true + | DefArrayT _, EqHT -> true + | DefArrayT _, ArrayHT -> true + | DefFuncT _, FuncHT -> true + | _ -> false + ) + | BotHT, _ -> true + | _, _ -> t1 = t2 + +and match_ref_type c t1 t2 = + match t1, t2 with + | (nul1, t1'), (nul2, t2') -> + match_null c nul1 nul2 && match_heap_type c t1' t2' + +and match_val_type c t1 t2 = + match t1, t2 with + | NumT t1', NumT t2' -> match_num_type c t1' t2' + | VecT t1', VecT t2' -> match_vec_type c t1' t2' + | RefT t1', RefT t2' -> match_ref_type c t1' t2' + | BotT, _ -> true + | _, _ -> false + +and match_result_type c ts1 ts2 = + List.length ts1 = List.length ts2 && + List.for_all2 (match_val_type c) ts1 ts2 + + +and match_pack_type _c t1 t2 = + t1 = t2 + +and match_storage_type c st1 st2 = + match st1, st2 with + | ValStorageT t1, ValStorageT t2 -> match_val_type c t1 t2 + | PackStorageT t1, PackStorageT t2 -> match_pack_type c t1 t2 + | _, _ -> false + +and match_field_type c (FieldT (mut1, st1)) (FieldT (mut2, st2)) = + mut1 = mut2 && match_storage_type c st1 st2 && + match mut1 with + | Cons -> true + | Var -> match_storage_type c st2 st1 + + +and match_struct_type c (StructT fts1) (StructT fts2) = + List.length fts1 >= List.length fts2 && + List.for_all2 (match_field_type c) (Lib.List.take (List.length fts2) fts1) fts2 + +and match_array_type c (ArrayT ft1) (ArrayT ft2) = + match_field_type c ft1 ft2 + +and match_func_type c (FuncT (ts11, ts12)) (FuncT (ts21, ts22)) = + match_result_type c ts21 ts11 && match_result_type c ts12 ts22 + + +and match_str_type c dt1 dt2 = + match dt1, dt2 with + | DefStructT st1, DefStructT st2 -> match_struct_type c st1 st2 + | DefArrayT at1, DefArrayT at2 -> match_array_type c at1 at2 + | DefFuncT ft1, DefFuncT ft2 -> match_func_type c ft1 ft2 + | _, _ -> false + +and match_def_type c dt1 dt2 = + dt1 == dt2 || (* optimisation *) + let s = subst_of c in subst_def_type s dt1 = subst_def_type s dt2 || + let SubT (_fin, hts1, _st) = unroll_def_type dt1 in + List.exists (fun ht1 -> match_heap_type c ht1 (DefHT dt2)) hts1 + + +let match_global_type c (GlobalT (mut1, t1)) (GlobalT (mut2, t2)) = + mut1 = mut2 && match_val_type c t1 t2 && + match mut1 with + | Cons -> true + | Var -> match_val_type c t2 t1 + +let match_table_type c (TableT (lim1, it1, t1)) (TableT (lim2, it2, t2)) = + match_limits c lim1 lim2 && it1 = it2 && match_ref_type c t1 t2 && match_ref_type c t2 t1 + +let match_memory_type c (MemoryT (lim1, it1)) (MemoryT (lim2, it2)) = + match_limits c lim1 lim2 && it1 = it2 + + +let match_extern_type c et1 et2 = + match et1, et2 with + | ExternFuncT dt1, ExternFuncT dt2 -> match_def_type c dt1 dt2 + | ExternTableT tt1, ExternTableT tt2 -> match_table_type c tt1 tt2 + | ExternMemoryT mt1, ExternMemoryT mt2 -> match_memory_type c mt1 mt2 + | ExternGlobalT gt1, ExternGlobalT gt2 -> match_global_type c gt1 gt2 + | _, _ -> false diff --git a/interpreter/valid/match.mli b/interpreter/valid/match.mli new file mode 100644 index 0000000000..5f0c96398f --- /dev/null +++ b/interpreter/valid/match.mli @@ -0,0 +1,36 @@ +open Types + + +(* Context *) + +type context = def_type list + + +(* Extremas *) + +val top_of_heap_type : context -> heap_type -> heap_type +val bot_of_heap_type : context -> heap_type -> heap_type +val top_of_str_type : context -> str_type -> heap_type +val bot_of_str_type : context -> str_type -> heap_type + + +(* Subtyping *) + +val match_num_type : context -> num_type -> num_type -> bool +val match_ref_type : context -> ref_type -> ref_type -> bool +val match_val_type : context -> val_type -> val_type -> bool + +val match_result_type : context -> result_type -> result_type -> bool + +val match_storage_type : context -> storage_type -> storage_type -> bool + +val match_str_type : context -> str_type -> str_type -> bool +val match_def_type : context -> def_type -> def_type -> bool + +val match_func_type : context -> func_type -> func_type -> bool + +val match_table_type : context -> table_type -> table_type -> bool +val match_memory_type : context -> memory_type -> memory_type -> bool +val match_global_type : context -> global_type -> global_type -> bool + +val match_extern_type : context -> extern_type -> extern_type -> bool diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index c39e80dbac..90adfd0d0e 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -1,6 +1,7 @@ open Ast open Source open Types +open Match (* Errors *) @@ -16,22 +17,22 @@ let require b at s = if not b then error at s type context = { - types : func_type list; - funcs : func_type list; + types : def_type list; + funcs : def_type list; tables : table_type list; memories : memory_type list; globals : global_type list; elems : ref_type list; datas : unit list; - locals : value_type list; - results : value_type list; + locals : local_type list; + results : val_type list; labels : result_type list; refs : Free.t; } let empty_context = - { types = []; funcs = []; tables = []; memories = []; - globals = []; elems = []; datas = []; + { types = []; funcs = []; globals = []; tables = []; memories = []; + elems = []; datas = []; locals = []; results = []; labels = []; refs = Free.empty } @@ -50,112 +51,271 @@ let data (c : context) x = lookup "data segment" c.datas x let local (c : context) x = lookup "local" c.locals x let label (c : context) x = lookup "label" c.labels x +let replace category list x y = + try Lib.List32.replace list x.it y with Failure _ -> + error x.at ("unknown " ^ category ^ " " ^ I32.to_string_u x.it) + +let init_local (c : context) x = + let LocalT (_init, t) = local c x in + {c with locals = replace "local" c.locals x (LocalT (Set, t))} + +let init_locals (c : context) xs = + List.fold_left init_local c xs + +let func_type (c : context) x = + match expand_def_type (type_ c x) with + | DefFuncT ft -> ft + | _ -> error x.at ("non-function type " ^ I32.to_string_u x.it) + +let struct_type (c : context) x = + match expand_def_type (type_ c x) with + | DefStructT st -> st + | _ -> error x.at ("non-structure type " ^ I32.to_string_u x.it) + +let array_type (c : context) x = + match expand_def_type (type_ c x) with + | DefArrayT at -> at + | _ -> error x.at ("non-array type " ^ I32.to_string_u x.it) + let refer category (s : Free.Set.t) x = if not (Free.Set.mem x.it s) then error x.at - ("undeclared " ^ category ^ " reference " ^ Int32.to_string x.it) + ("undeclared " ^ category ^ " reference " ^ I32.to_string_u x.it) let refer_func (c : context) x = refer "function" c.refs.Free.funcs x +(* Types *) + +let check_limits le_u {min; max} range at msg = + require (le_u min range) at msg; + match max with + | None -> () + | Some max -> + require (le_u max range) at msg; + require (le_u min max) at + "size minimum must not be greater than maximum" + +let check_num_type (c : context) (t : num_type) at = + () + +let check_vec_type (c : context) (t : vec_type) at = + () + +let check_heap_type (c : context) (t : heap_type) at = + match t with + | AnyHT | NoneHT | EqHT | I31HT | StructHT | ArrayHT + | FuncHT | NoFuncHT + | ExternHT | NoExternHT -> () + | VarHT (StatX x) -> let _dt = type_ c (x @@ at) in () + | VarHT (RecX _) | DefHT _ -> assert false + | BotHT -> () + +let check_ref_type (c : context) (t : ref_type) at = + match t with + | (_nul, ht) -> check_heap_type c ht at + +let check_val_type (c : context) (t : val_type) at = + match t with + | NumT t' -> check_num_type c t' at + | VecT t' -> check_vec_type c t' at + | RefT t' -> check_ref_type c t' at + | BotT -> assert false + +let check_result_type (c : context) (ts : result_type) at = + List.iter (fun t -> check_val_type c t at) ts + +let check_storage_type (c : context) (st : storage_type) at = + match st with + | ValStorageT t -> check_val_type c t at + | PackStorageT p -> assert Pack.(p = Pack8 || p = Pack16) + +let check_field_type (c : context) (ft : field_type) at = + match ft with + | FieldT (_mut, st) -> check_storage_type c st at + +let check_struct_type (c : context) (st : struct_type) at = + match st with + | StructT fts -> List.iter (fun ft -> check_field_type c ft at) fts + +let check_array_type (c : context) (rt : array_type) at = + match rt with + | ArrayT ft -> check_field_type c ft at + +let check_func_type (c : context) (ft : func_type) at = + let FuncT (ts1, ts2) = ft in + check_result_type c ts1 at; + check_result_type c ts2 at + +let check_table_type (c : context) (tt : table_type) at = + let TableT (lim, it, t) = tt in + check_ref_type c t at; + match it with + | I64IndexType -> + check_limits I64.le_u lim 0xffff_ffff_ffff_ffffL at + "table size must be at most 2^64-1" + | I32IndexType -> + check_limits I64.le_u lim 0xffff_ffffL at + "table size must be at most 2^32-1" + +let check_memory_type (c : context) (mt : memory_type) at = + let MemoryT (lim, it) = mt in + match it with + | I32IndexType -> + check_limits I64.le_u lim 0x1_0000L at + "memory size must be at most 65536 pages (4GiB)" + | I64IndexType -> + check_limits I64.le_u lim 0x1_0000_0000_0000L at + "memory size must be at most 48 bits of pages" + +let check_global_type (c : context) (gt : global_type) at = + let GlobalT (_mut, t) = gt in + check_val_type c t at + +let check_str_type (c : context) (st : str_type) at = + match st with + | DefStructT st -> check_struct_type c st at + | DefArrayT rt -> check_array_type c rt at + | DefFuncT ft -> check_func_type c ft at + +let check_sub_type (c : context) (sut : sub_type) at = + let SubT (_fin, hts, st) = sut in + List.iter (fun ht -> check_heap_type c ht at) hts; + check_str_type c st at + +let check_sub_type_sub (c : context) (sut : sub_type) x at = + let SubT (_fin, hts, st) = sut in + List.iter (fun hti -> + let xi = match hti with VarHT (StatX xi) -> xi | _ -> assert false in + let SubT (fini, _, sti) = unroll_def_type (type_ c (xi @@ at)) in + require (xi < x) at ("forward use of type " ^ I32.to_string_u xi ^ + " in sub type definition"); + require (fini = NoFinal) at ("sub type " ^ I32.to_string_u x ^ + " has final super type " ^ I32.to_string_u xi); + require (match_str_type c.types st sti) at ("sub type " ^ I32.to_string_u x ^ + " does not match super type " ^ I32.to_string_u xi) + ) hts + +let check_rec_type (c : context) (rt : rec_type) at : context = + let RecT sts = rt in + let x = Lib.List32.length c.types in + let c' = {c with types = c.types @ roll_def_types x rt} in + List.iter (fun st -> check_sub_type c' st at) sts; + Lib.List32.iteri + (fun i st -> check_sub_type_sub c' st (Int32.add x i) at) sts; + c' + +let check_type (c : context) (t : type_) : context = + check_rec_type c t.it t.at + + +let diff_ref_type (nul1, ht1) (nul2, ht2) = + match nul2 with + | Null -> (NoNull, ht1) + | NoNull -> (nul1, ht1) + + (* Stack typing *) (* * Note: The declarative typing rules are non-deterministic, that is, they * have the liberty to locally "guess" the right types implied by the context. - * In the algorithmic formulation required here, stack types are hence modelled - * as lists of _options_ of types, where `None` represents a locally - * unknown type. Furthermore, an ellipses flag represents arbitrary sequences + * In the algorithmic formulation required here, stack types may hence pick + * `BotT` as the principal choice for a locally unknown type. + * Furthermore, an ellipses flag represents arbitrary sequences * of unknown types, in order to handle stack polymorphism algorithmically. *) type ellipses = NoEllipses | Ellipses -type infer_result_type = ellipses * value_type option list -type op_type = {ins : infer_result_type; outs : infer_result_type} - -let known = List.map (fun t -> Some t) -let stack ts = (NoEllipses, known ts) -let (-~>) ts1 ts2 = {ins = NoEllipses, ts1; outs = NoEllipses, ts2} -let (-->) ts1 ts2 = {ins = NoEllipses, known ts1; outs = NoEllipses, known ts2} -let (-~>...) ts1 ts2 = {ins = Ellipses, ts1; outs = Ellipses, ts2} -let (-->...) ts1 ts2 = {ins = Ellipses, known ts1; outs = Ellipses, known ts2} - -let string_of_infer_type t = - Lib.Option.get (Lib.Option.map string_of_value_type t) "_" -let string_of_infer_types ts = - "[" ^ String.concat " " (List.map string_of_infer_type ts) ^ "]" - -let eq_ty t1 t2 = (t1 = t2 || t1 = None || t2 = None) -let check_stack ts1 ts2 at = - require (List.length ts1 = List.length ts2 && List.for_all2 eq_ty ts1 ts2) at - ("type mismatch: instruction requires " ^ string_of_infer_types ts1 ^ - " but stack has " ^ string_of_infer_types ts2) - -let pop (ell1, ts1) (ell2, ts2) at = +type infer_result_type = ellipses * val_type list +type infer_func_type = {ins : infer_result_type; outs : infer_result_type} +type infer_instr_type = infer_func_type * idx list + +let stack ts = (NoEllipses, ts) +let (-->) ts1 ts2 = {ins = NoEllipses, ts1; outs = NoEllipses, ts2} +let (-->...) ts1 ts2 = {ins = Ellipses, ts1; outs = Ellipses, ts2} + +let check_stack (c : context) ts1 ts2 at = + require + ( List.length ts1 = List.length ts2 && + List.for_all2 (match_val_type c.types) ts1 ts2 ) at + ("type mismatch: instruction requires " ^ string_of_result_type ts2 ^ + " but stack has " ^ string_of_result_type ts1) + +let pop c (ell1, ts1) (ell2, ts2) at = let n1 = List.length ts1 in let n2 = List.length ts2 in let n = min n1 n2 in let n3 = if ell2 = Ellipses then (n1 - n) else 0 in - check_stack ts1 (Lib.List.make n3 None @ Lib.List.drop (n2 - n) ts2) at; + check_stack c (Lib.List.make n3 (BotT : val_type) @ Lib.List.drop (n2 - n) ts2) ts1 at; (ell2, if ell1 = Ellipses then [] else Lib.List.take (n2 - n) ts2) -let push (ell1, ts1) (ell2, ts2) = +let push c (ell1, ts1) (ell2, ts2) = assert (ell1 = NoEllipses || ts2 = []); (if ell1 = Ellipses || ell2 = Ellipses then Ellipses else NoEllipses), ts2 @ ts1 -let peek i (ell, ts) = - try List.nth (List.rev ts) i with Failure _ -> None +let peek i (ell, ts) : val_type = + try List.nth (List.rev ts) i with Failure _ -> BotT + +let peek_ref i (ell, ts) at : ref_type = + match peek i (ell, ts) with + | RefT rt -> rt + | BotT -> (NoNull, BotHT) + | t -> + error at + ("type mismatch: instruction requires reference type" ^ + " but stack has " ^ string_of_val_type t) (* Type Synthesis *) -let type_num = Values.type_of_num -let type_vec = Values.type_of_vec +let type_num = Value.type_of_op +let type_vec = Value.type_of_vecop let type_vec_lane = function - | Values.V128 laneop -> V128.type_of_lane laneop + | Value.V128 laneop -> V128.type_of_lane laneop let type_cvtop at = function - | Values.I32 cvtop -> + | Value.I32 cvtop -> let open I32Op in (match cvtop with | ExtendSI32 | ExtendUI32 -> error at "invalid conversion" - | WrapI64 -> I64Type + | WrapI64 -> I64T | TruncSF32 | TruncUF32 | TruncSatSF32 | TruncSatUF32 - | ReinterpretFloat -> F32Type - | TruncSF64 | TruncUF64 | TruncSatSF64 | TruncSatUF64 -> F64Type - ), I32Type - | Values.I64 cvtop -> + | ReinterpretFloat -> F32T + | TruncSF64 | TruncUF64 | TruncSatSF64 | TruncSatUF64 -> F64T + ), I32T + | Value.I64 cvtop -> let open I64Op in (match cvtop with - | ExtendSI32 | ExtendUI32 -> I32Type + | ExtendSI32 | ExtendUI32 -> I32T | WrapI64 -> error at "invalid conversion" - | TruncSF32 | TruncUF32 | TruncSatSF32 | TruncSatUF32 -> F32Type + | TruncSF32 | TruncUF32 | TruncSatSF32 | TruncSatUF32 -> F32T | TruncSF64 | TruncUF64 | TruncSatSF64 | TruncSatUF64 - | ReinterpretFloat -> F64Type - ), I64Type - | Values.F32 cvtop -> + | ReinterpretFloat -> F64T + ), I64T + | Value.F32 cvtop -> let open F32Op in (match cvtop with - | ConvertSI32 | ConvertUI32 | ReinterpretInt -> I32Type - | ConvertSI64 | ConvertUI64 -> I64Type + | ConvertSI32 | ConvertUI32 | ReinterpretInt -> I32T + | ConvertSI64 | ConvertUI64 -> I64T | PromoteF32 -> error at "invalid conversion" - | DemoteF64 -> F64Type - ), F32Type - | Values.F64 cvtop -> + | DemoteF64 -> F64T + ), F32T + | Value.F64 cvtop -> let open F64Op in (match cvtop with - | ConvertSI32 | ConvertUI32 -> I32Type - | ConvertSI64 | ConvertUI64 | ReinterpretInt -> I64Type - | PromoteF32 -> F32Type + | ConvertSI32 | ConvertUI32 -> I32T + | ConvertSI64 | ConvertUI64 | ReinterpretInt -> I64T + | PromoteF32 -> F32T | DemoteF64 -> error at "invalid conversion" - ), F64Type + ), F64T let num_lanes = function - | Values.V128 laneop -> V128.num_lanes laneop + | Value.V128 laneop -> V128.num_lanes laneop let lane_extractop = function - | Values.V128 extractop -> + | Value.V128 extractop -> let open V128 in let open V128Op in match extractop with | I8x16 (Extract (i, _)) | I16x8 (Extract (i, _)) @@ -163,28 +323,33 @@ let lane_extractop = function | F32x4 (Extract (i, _)) | F64x2 (Extract (i, _)) -> i let lane_replaceop = function - | Values.V128 replaceop -> + | Value.V128 replaceop -> let open V128 in let open V128Op in match replaceop with | I8x16 (Replace i) | I16x8 (Replace i) | I32x4 (Replace i) | I64x2 (Replace i) | F32x4 (Replace i) | F64x2 (Replace i) -> i +let type_externop op = + match op with + | Internalize -> ExternHT, AnyHT + | Externalize -> AnyHT, ExternHT + (* Expressions *) let check_pack sz t_sz at = - require (packed_size sz < t_sz) at "invalid sign extension" + require (Pack.packed_size sz < t_sz) at "invalid sign extension" let check_unop unop at = match unop with - | Values.I32 (IntOp.ExtendS sz) | Values.I64 (IntOp.ExtendS sz) -> - check_pack sz (num_size (Values.type_of_num unop)) at + | Value.I32 (IntOp.ExtendS sz) | Value.I64 (IntOp.ExtendS sz) -> + check_pack sz (num_size (Value.type_of_op unop)) at | _ -> () let check_vec_binop binop at = match binop with - | Values.(V128 (V128.I8x16 (V128Op.Shuffle is))) -> + | Value.(V128 (V128.I8x16 (V128Op.Shuffle is))) -> if List.exists ((<=) 32) is then error at "invalid lane index" | _ -> () @@ -195,15 +360,17 @@ let check_memop (c : context) (memop : ('t, 's) memop) ty_size get_sz at = | None -> ty_size memop.ty | Some sz -> check_pack sz (ty_size memop.ty) at; - packed_size sz + Pack.packed_size sz in + require (memop.align < 63) at + "alignment must not be larger than natural"; require (1 lsl memop.align <= size) at "alignment must not be larger than natural"; - let MemoryType (_lim, it) = memory c (0l @@ at) in + let MemoryT (_lim, it) = memory c (0l @@ at) in if it = I32IndexType then require (I64.lt_u memop.offset 0x1_0000_0000L) at "offset out of range"; - it + memop.ty (* @@ -212,7 +379,7 @@ let check_memop (c : context) (memop : ('t, 's) memop) ty_size get_sz at = * e : instr * es : instr list * v : value - * t : value_type var + * t : val_type * ts : result_type * x : variable * @@ -226,377 +393,538 @@ let check_memop (c : context) (memop : ('t, 's) memop) ty_size get_sz at = * declarative typing rules. *) -let check_block_type (c : context) (bt : block_type) : func_type = +let check_block_type (c : context) (bt : block_type) at : instr_type = match bt with - | VarBlockType x -> type_ c x - | ValBlockType None -> FuncType ([], []) - | ValBlockType (Some t) -> FuncType ([], [t]) + | ValBlockType None -> InstrT ([], [], []) + | ValBlockType (Some t) -> check_val_type c t at; InstrT ([], [t], []) + | VarBlockType x -> + let FuncT (ts1, ts2) = func_type c x in InstrT (ts1, ts2, []) -let rec check_instr (c : context) (e : instr) (s : infer_result_type) : op_type = +let rec check_instr (c : context) (e : instr) (s : infer_result_type) : infer_instr_type = match e.it with | Unreachable -> - [] -->... [] + [] -->... [], [] | Nop -> - [] --> [] + [] --> [], [] | Drop -> - [peek 0 s] -~> [] + [peek 0 s] --> [], [] | Select None -> let t = peek 1 s in - require (match t with None -> true | Some t -> is_num_type t || is_vec_type t) e.at + require (is_num_type t || is_vec_type t) e.at ("type mismatch: instruction requires numeric or vector type" ^ - " but stack has " ^ string_of_infer_type t); - [t; t; Some (NumType I32Type)] -~> [t] + " but stack has " ^ string_of_val_type t); + [t; t; NumT I32T] --> [t], [] | Select (Some ts) -> require (List.length ts = 1) e.at "invalid result arity other than 1 is not (yet) allowed"; - (ts @ ts @ [NumType I32Type]) --> ts + check_result_type c ts e.at; + (ts @ ts @ [NumT I32T]) --> ts, [] | Block (bt, es) -> - let FuncType (ts1, ts2) as ft = check_block_type c bt in - check_block {c with labels = ts2 :: c.labels} es ft e.at; - ts1 --> ts2 + let InstrT (ts1, ts2, xs) as it = check_block_type c bt e.at in + check_block {c with labels = ts2 :: c.labels} es it e.at; + ts1 --> ts2, List.map (fun x -> x @@ e.at) xs | Loop (bt, es) -> - let FuncType (ts1, ts2) as ft = check_block_type c bt in - check_block {c with labels = ts1 :: c.labels} es ft e.at; - ts1 --> ts2 + let InstrT (ts1, ts2, xs) as it = check_block_type c bt e.at in + check_block {c with labels = ts1 :: c.labels} es it e.at; + ts1 --> ts2, List.map (fun x -> x @@ e.at) xs | If (bt, es1, es2) -> - let FuncType (ts1, ts2) as ft = check_block_type c bt in - check_block {c with labels = ts2 :: c.labels} es1 ft e.at; - check_block {c with labels = ts2 :: c.labels} es2 ft e.at; - (ts1 @ [NumType I32Type]) --> ts2 + let InstrT (ts1, ts2, xs) as it = check_block_type c bt e.at in + check_block {c with labels = ts2 :: c.labels} es1 it e.at; + check_block {c with labels = ts2 :: c.labels} es2 it e.at; + (ts1 @ [NumT I32T]) --> ts2, List.map (fun x -> x @@ e.at) xs | Br x -> - label c x -->... [] + label c x -->... [], [] | BrIf x -> - (label c x @ [NumType I32Type]) --> label c x + (label c x @ [NumT I32T]) --> label c x, [] | BrTable (xs, x) -> let n = List.length (label c x) in - let ts = Lib.List.table n (fun i -> peek (n - i) s) in - check_stack ts (known (label c x)) x.at; - List.iter (fun x' -> check_stack ts (known (label c x')) x'.at) xs; - (ts @ [Some (NumType I32Type)]) -~>... [] + let ts = List.init n (fun i -> peek (n - i) s) in + check_stack c ts (label c x) x.at; + List.iter (fun x' -> check_stack c ts (label c x') x'.at) xs; + (ts @ [NumT I32T]) -->... [], [] + + | BrOnNull x -> + let (_nul, ht) = peek_ref 0 s e.at in + (label c x @ [RefT (Null, ht)]) --> (label c x @ [RefT (NoNull, ht)]), [] + + | BrOnNonNull x -> + let (_nul, ht) = peek_ref 0 s e.at in + let t' = RefT (NoNull, ht) in + require (label c x <> []) e.at + ("type mismatch: instruction requires type " ^ string_of_val_type t' ^ + " but label has " ^ string_of_result_type (label c x)); + let ts0, t1 = Lib.List.split_last (label c x) in + require (match_val_type c.types t' t1) e.at + ("type mismatch: instruction requires type " ^ string_of_val_type t' ^ + " but label has " ^ string_of_result_type (label c x)); + (ts0 @ [RefT (Null, ht)]) --> ts0, [] + + | BrOnCast (x, rt1, rt2) -> + check_ref_type c rt1 e.at; + check_ref_type c rt2 e.at; + require + (match_ref_type c.types rt2 rt1) e.at + ("type mismatch on cast: type " ^ string_of_ref_type rt2 ^ + " does not match " ^ string_of_ref_type rt1); + require (label c x <> []) e.at + ("type mismatch: instruction requires type " ^ string_of_ref_type rt2 ^ + " but label has " ^ string_of_result_type (label c x)); + let ts0, t1 = Lib.List.split_last (label c x) in + require (match_val_type c.types (RefT rt2) t1) e.at + ("type mismatch: instruction requires type " ^ string_of_ref_type rt2 ^ + " but label has " ^ string_of_result_type (label c x)); + (ts0 @ [RefT rt1]) --> (ts0 @ [RefT (diff_ref_type rt1 rt2)]), [] + + | BrOnCastFail (x, rt1, rt2) -> + check_ref_type c rt1 e.at; + check_ref_type c rt2 e.at; + let rt1' = diff_ref_type rt1 rt2 in + require + (match_ref_type c.types rt2 rt1) e.at + ("type mismatch on cast: type " ^ string_of_ref_type rt2 ^ + " does not match " ^ string_of_ref_type rt1); + require (label c x <> []) e.at + ("type mismatch: instruction requires type " ^ string_of_ref_type rt1' ^ + " but label has " ^ string_of_result_type (label c x)); + let ts0, t1 = Lib.List.split_last (label c x) in + require (match_val_type c.types (RefT rt1') t1) e.at + ("type mismatch: instruction requires type " ^ string_of_ref_type rt1' ^ + " but label has " ^ string_of_result_type (label c x)); + (ts0 @ [RefT rt1]) --> (ts0 @ [RefT rt2]), [] | Return -> - c.results -->... [] + c.results -->... [], [] | Call x -> - let FuncType (ts1, ts2) = func c x in - ts1 --> ts2 + let FuncT (ts1, ts2) = as_func_str_type (expand_def_type (func c x)) in + ts1 --> ts2, [] + + | CallRef x -> + let FuncT (ts1, ts2) = func_type c x in + (ts1 @ [RefT (Null, DefHT (type_ c x))]) --> ts2, [] | CallIndirect (x, y) -> - let TableType (lim, it, t) = table c x in - let FuncType (ts1, ts2) = type_ c y in - require (t = FuncRefType) x.at - ("type mismatch: instruction requires table of functions" ^ - " but table has " ^ string_of_ref_type t); - (ts1 @ [value_type_of_index_type it]) --> ts2 + let TableT (lim, it, t) = table c x in + let FuncT (ts1, ts2) = func_type c y in + require (match_ref_type c.types t (Null, FuncHT)) x.at + ("type mismatch: instruction requires table of function type" ^ + " but table has element type " ^ string_of_ref_type t); + (ts1 @ [value_type_of_index_type it]) --> ts2, [] + + | ReturnCall x -> + let FuncT (ts1, ts2) = as_func_str_type (expand_def_type (func c x)) in + require (match_result_type c.types ts2 c.results) e.at + ("type mismatch: current function requires result type " ^ + string_of_result_type c.results ^ + " but callee returns " ^ string_of_result_type ts2); + ts1 -->... [], [] + + | ReturnCallRef x -> + let FuncT (ts1, ts2) = func_type c x in + require (match_result_type c.types ts2 c.results) e.at + ("type mismatch: current function requires result type " ^ + string_of_result_type c.results ^ + " but callee returns " ^ string_of_result_type ts2); + (ts1 @ [RefT (Null, DefHT (type_ c x))]) -->... [], [] + + | ReturnCallIndirect (x, y) -> + let TableT (_lim, it, t) = table c x in + let FuncT (ts1, ts2) = func_type c y in + require (match_result_type c.types ts2 c.results) e.at + ("type mismatch: current function requires result type " ^ + string_of_result_type c.results ^ + " but callee returns " ^ string_of_result_type ts2); + (ts1 @ [value_type_of_index_type it]) -->... [], [] | LocalGet x -> - [] --> [local c x] + let LocalT (init, t) = local c x in + require (init = Set) x.at "uninitialized local"; + [] --> [t], [] | LocalSet x -> - [local c x] --> [] + let LocalT (_init, t) = local c x in + [t] --> [], [x] | LocalTee x -> - [local c x] --> [local c x] + let LocalT (_init, t) = local c x in + [t] --> [t], [x] | GlobalGet x -> - let GlobalType (t, _mut) = global c x in - [] --> [t] + let GlobalT (_mut, t) = global c x in + [] --> [t], [] | GlobalSet x -> - let GlobalType (t, mut) = global c x in - require (mut = Mutable) x.at "global is immutable"; - [t] --> [] + let GlobalT (mut, t) = global c x in + require (mut = Var) x.at "immutable global"; + [t] --> [], [] | TableGet x -> - let TableType (_lim, it, t) = table c x in - [value_type_of_index_type it] --> [RefType t] + let TableT (_lim, it, rt) = table c x in + [value_type_of_index_type it] --> [RefT rt], [] | TableSet x -> - let TableType (_lim, it, t) = table c x in - [value_type_of_index_type it; RefType t] --> [] + let TableT (_lim, it, rt) = table c x in + [value_type_of_index_type it; RefT rt] --> [], [] | TableSize x -> - let TableType (_lim, it, _t) = table c x in - [] --> [value_type_of_index_type it] + let TableT (_lim, it, _rt) = table c x in + [] --> [value_type_of_index_type it], [] | TableGrow x -> - let TableType (_lim, it, t) = table c x in - [RefType t; value_type_of_index_type it] --> [value_type_of_index_type it] + let TableT (_lim, it, rt) = table c x in + [RefT rt; value_type_of_index_type it] --> [value_type_of_index_type it], [] | TableFill x -> - let TableType (_lim, it, t) = table c x in - [value_type_of_index_type it; RefType t; value_type_of_index_type it] --> [] + let TableT (_lim, it, rt) = table c x in + [value_type_of_index_type it; RefT rt; value_type_of_index_type it] --> [], [] | TableCopy (x, y) -> - let TableType (_lim1, it1, t1) = table c x in - let TableType (_lim2, it2, t2) = table c y in - let it3 = min it1 it2 in - require (t1 = t2) x.at + let TableT (_lim1, it1, t1) = table c x in + let TableT (_lim2, it2, t2) = table c y in + let it_min = min it1 it2 in + require (match_ref_type c.types t2 t1) x.at ("type mismatch: source element type " ^ string_of_ref_type t1 ^ " does not match destination element type " ^ string_of_ref_type t2); - [value_type_of_index_type it1; value_type_of_index_type it2; value_type_of_index_type it3] --> [] + [value_type_of_index_type it1; value_type_of_index_type it2; value_type_of_index_type it_min] --> [], [] | TableInit (x, y) -> - let TableType (_lim, it, t1) = table c x in + let TableT (_lim1, it, t1) = table c x in let t2 = elem c y in - require (t1 = t2) x.at + require (match_ref_type c.types t2 t1) x.at ("type mismatch: element segment's type " ^ string_of_ref_type t1 ^ " does not match table's element type " ^ string_of_ref_type t2); - [value_type_of_index_type it; NumType I32Type; NumType I32Type] --> [] + [value_type_of_index_type it; NumT I32T; NumT I32T] --> [], [] | ElemDrop x -> ignore (elem c x); - [] --> [] - - | Load memop -> - let it = check_memop c memop num_size (Lib.Option.map fst) e.at in - [value_type_of_index_type it] --> [NumType memop.ty] - - | Store memop -> - let it = check_memop c memop num_size (fun sz -> sz) e.at in - [value_type_of_index_type it; NumType memop.ty] --> [] - - | VecLoad memop -> - let it = check_memop c memop vec_size (Lib.Option.map fst) e.at in - [value_type_of_index_type it]--> [VecType memop.ty] - - | VecStore memop -> - let it = check_memop c memop vec_size (fun _ -> None) e.at in - [value_type_of_index_type it; VecType memop.ty] --> [] - - | VecLoadLane (memop, i) -> - let it = check_memop c memop vec_size (fun sz -> Some sz) e.at in - require (i < vec_size memop.ty / packed_size memop.pack) e.at + [] --> [], [] + + | Load (x, memop) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop num_size (Lib.Option.map fst) e.at in + [value_type_of_index_type it] --> [NumT t], [] + + | Store (x, memop) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop num_size (fun sz -> sz) e.at in + [value_type_of_index_type it; NumT t] --> [], [] + + | VecLoad (x, memop) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop vec_size (Lib.Option.map fst) e.at in + [value_type_of_index_type it] --> [VecT t], [] + + | VecStore (x, memop) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop vec_size (fun _ -> None) e.at in + [value_type_of_index_type it; VecT t] --> [], [] + + | VecLoadLane (x, memop, i) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop vec_size (fun sz -> Some sz) e.at in + require (i < vec_size t / Pack.packed_size memop.pack) e.at "invalid lane index"; - [value_type_of_index_type it; VecType memop.ty] --> [VecType memop.ty] + [value_type_of_index_type it; VecT t] --> [VecT t], [] - | VecStoreLane (memop, i) -> - let it = check_memop c memop vec_size (fun sz -> Some sz) e.at in - require (i < vec_size memop.ty / packed_size memop.pack) e.at + | VecStoreLane (x, memop, i) -> + let MemoryT (_lim, it) = memory c x in + let t = check_memop c memop vec_size (fun sz -> Some sz) e.at in + require (i < vec_size t / Pack.packed_size memop.pack) e.at "invalid lane index"; - [value_type_of_index_type it; VecType memop.ty] --> [] + [value_type_of_index_type it; VecT t] --> [], [] - | MemorySize -> - let MemoryType (_, it) = memory c (0l @@ e.at) in - [] --> [value_type_of_index_type it] + | MemorySize x -> + let MemoryT (_lim, it) = memory c x in + [] --> [value_type_of_index_type it], [] - | MemoryGrow -> - let MemoryType (_, it) = memory c (0l @@ e.at) in - [value_type_of_index_type it] --> [value_type_of_index_type it] + | MemoryGrow x -> + let MemoryT (_lim, it) = memory c x in + [value_type_of_index_type it] --> [value_type_of_index_type it], [] - | MemoryFill -> - let MemoryType (_, it) = memory c (0l @@ e.at) in - [value_type_of_index_type it; NumType I32Type; value_type_of_index_type it] --> [] + | MemoryFill x -> + let MemoryT (_lim, it) = memory c x in + [value_type_of_index_type it; NumT I32T; value_type_of_index_type it] --> [], [] - | MemoryCopy -> - let MemoryType (_, it) = memory c (0l @@ e.at) in - [value_type_of_index_type it; value_type_of_index_type it; value_type_of_index_type it] --> [] + | MemoryCopy (x, y)-> + let MemoryT (_lib1, it1) = memory c x in + let MemoryT (_lib2, it2) = memory c y in + let it_min = min it1 it2 in + [value_type_of_index_type it1; value_type_of_index_type it2; value_type_of_index_type it_min] --> [], [] - | MemoryInit x -> - let MemoryType (_, it) = memory c (0l @@ e.at) in - ignore (data c x); - [value_type_of_index_type it; NumType I32Type; NumType I32Type] --> [] + | MemoryInit (x, y) -> + let MemoryT (_lib, it) = memory c x in + let () = data c y in + [value_type_of_index_type it; NumT I32T; NumT I32T] --> [], [] | DataDrop x -> - ignore (data c x); - [] --> [] + let () = data c x in + [] --> [], [] - | RefNull t -> - [] --> [RefType t] - - | RefIsNull -> - let t = peek 0 s in - require (match t with None -> true | Some t -> is_ref_type t) e.at - ("type mismatch: instruction requires reference type" ^ - " but stack has " ^ string_of_infer_type t); - [t] -~> [Some (NumType I32Type)] + | RefNull ht -> + check_heap_type c ht e.at; + [] --> [RefT (Null, ht)], [] | RefFunc x -> - let _ft = func c x in + let dt = func c x in refer_func c x; - [] --> [RefType FuncRefType] + [] --> [RefT (NoNull, DefHT dt)], [] + + | RefIsNull -> + let (_nul, ht) = peek_ref 0 s e.at in + [RefT (Null, ht)] --> [NumT I32T], [] + + | RefAsNonNull -> + let (_nul, ht) = peek_ref 0 s e.at in + [RefT (Null, ht)] --> [RefT (NoNull, ht)], [] + + | RefTest rt -> + let (_nul, ht) = rt in + check_ref_type c rt e.at; + [RefT (Null, top_of_heap_type c.types ht)] --> [NumT I32T], [] + + | RefCast rt -> + let (nul, ht) = rt in + check_ref_type c rt e.at; + [RefT (Null, top_of_heap_type c.types ht)] --> [RefT (nul, ht)], [] + + | RefEq -> + [RefT (Null, EqHT); RefT (Null, EqHT)] --> [NumT I32T], [] + + | RefI31 -> + [NumT I32T] --> [RefT (NoNull, I31HT)], [] + + | I31Get ext -> + [RefT (Null, I31HT)] --> [NumT I32T], [] + + | StructNew (x, initop) -> + let StructT fts = struct_type c x in + require + ( initop = Explicit || List.for_all (fun ft -> + defaultable (unpacked_field_type ft)) fts ) x.at + "field type is not defaultable"; + let ts = if initop = Implicit then [] else List.map unpacked_field_type fts in + ts --> [RefT (NoNull, DefHT (type_ c x))], [] + + | StructGet (x, y, exto) -> + let StructT fts = struct_type c x in + require (y.it < Lib.List32.length fts) y.at + ("unknown field " ^ I32.to_string_u y.it); + let FieldT (_mut, st) = Lib.List32.nth fts y.it in + require ((exto <> None) == is_packed_storage_type st) y.at + ("field is " ^ (if exto = None then "packed" else "unpacked")); + let t = unpacked_storage_type st in + [RefT (Null, DefHT (type_ c x))] --> [t], [] + + | StructSet (x, y) -> + let StructT fts = struct_type c x in + require (y.it < Lib.List32.length fts) y.at + ("unknown field " ^ I32.to_string_u y.it); + let FieldT (mut, st) = Lib.List32.nth fts y.it in + require (mut == Var) y.at "field is immutable"; + let t = unpacked_storage_type st in + [RefT (Null, DefHT (type_ c x)); t] --> [], [] + + | ArrayNew (x, initop) -> + let ArrayT ft = array_type c x in + require + (initop = Explicit || defaultable (unpacked_field_type ft)) x.at + "array type is not defaultable"; + let ts = if initop = Implicit then [] else [unpacked_field_type ft] in + (ts @ [NumT I32T]) --> [RefT (NoNull, DefHT (type_ c x))], [] + + | ArrayNewFixed (x, n) -> + let ArrayT ft = array_type c x in + let ts = Lib.List32.make n (unpacked_field_type ft) in + ts --> [RefT (NoNull, DefHT (type_ c x))], [] + + | ArrayNewElem (x, y) -> + let ArrayT ft = array_type c x in + let rt = elem c y in + require (match_val_type c.types (RefT rt) (unpacked_field_type ft)) x.at + ("type mismatch: element segment's type " ^ string_of_ref_type rt ^ + " does not match array's field type " ^ string_of_field_type ft); + [NumT I32T; NumT I32T] --> [RefT (NoNull, DefHT (type_ c x))], [] + + | ArrayNewData (x, y) -> + let ArrayT ft = array_type c x in + let () = data c y in + let t = unpacked_field_type ft in + require (is_num_type t || is_vec_type t) x.at + "array type is not numeric or vector"; + [NumT I32T; NumT I32T] --> [RefT (NoNull, DefHT (type_ c x))], [] + + | ArrayGet (x, exto) -> + let ArrayT (FieldT (_mut, st)) = array_type c x in + require ((exto <> None) == is_packed_storage_type st) e.at + ("array is " ^ (if exto = None then "packed" else "unpacked")); + let t = unpacked_storage_type st in + [RefT (Null, DefHT (type_ c x)); NumT I32T] --> [t], [] + + | ArraySet x -> + let ArrayT (FieldT (mut, st)) = array_type c x in + require (mut == Var) e.at "array is immutable"; + let t = unpacked_storage_type st in + [RefT (Null, DefHT (type_ c x)); NumT I32T; t] --> [], [] + + | ArrayLen -> + [RefT (Null, ArrayHT)] --> [NumT I32T], [] + + | ArrayCopy (x, y) -> + let ArrayT (FieldT (mutd, std)) = array_type c x in + let ArrayT (FieldT (_muts, sts)) = array_type c y in + require (mutd = Var) e.at "array is immutable"; + require (match_storage_type c.types sts std) e.at "array types do not match"; + [RefT (Null, DefHT (type_ c x)); NumT I32T; RefT (Null, DefHT (type_ c y)); NumT I32T; NumT I32T] --> [], [] + + | ArrayFill x -> + let ArrayT (FieldT (mut, st)) = array_type c x in + require (mut = Var) e.at "array is immutable"; + let t = unpacked_storage_type st in + [RefT (Null, DefHT (type_ c x)); NumT I32T; t; NumT I32T] --> [], [] + + | ArrayInitData (x, y) -> + let ArrayT (FieldT (mut, st)) = array_type c x in + require (mut = Var) e.at "array is immutable"; + let () = data c y in + let t = unpacked_storage_type st in + require (is_num_type t || is_vec_type t) x.at + "array type is not numeric or vector"; + [RefT (Null, DefHT (type_ c x)); NumT I32T; NumT I32T; NumT I32T] --> [], [] + + | ArrayInitElem (x, y) -> + let ArrayT (FieldT (mut, st)) = array_type c x in + require (mut = Var) e.at "array is immutable"; + let rt = elem c y in + require (match_val_type c.types (RefT rt) (unpacked_storage_type st)) x.at + ("type mismatch: element segment's type " ^ string_of_ref_type rt ^ + " does not match array's field type " ^ string_of_field_type (FieldT (mut, st))); + [RefT (Null, DefHT (type_ c x)); NumT I32T; NumT I32T; NumT I32T] --> [], [] + + | ExternConvert op -> + let ht1, ht2 = type_externop op in + let (nul, _ht) = peek_ref 0 s e.at in + [RefT (nul, ht1)] --> [RefT (nul, ht2)], [] | Const v -> - let t = NumType (type_num v.it) in - [] --> [t] + let t = NumT (type_num v.it) in + [] --> [t], [] | Test testop -> - let t = NumType (type_num testop) in - [t] --> [NumType I32Type] + let t = NumT (type_num testop) in + [t] --> [NumT I32T], [] | Compare relop -> - let t = NumType (type_num relop) in - [t; t] --> [NumType I32Type] + let t = NumT (type_num relop) in + [t; t] --> [NumT I32T], [] | Unary unop -> check_unop unop e.at; - let t = NumType (type_num unop) in - [t] --> [t] + let t = NumT (type_num unop) in + [t] --> [t], [] | Binary binop -> - let t = NumType (type_num binop) in - [t; t] --> [t] + let t = NumT (type_num binop) in + [t; t] --> [t], [] | Convert cvtop -> let t1, t2 = type_cvtop e.at cvtop in - [NumType t1] --> [NumType t2] + [NumT t1] --> [NumT t2], [] | VecConst v -> - let t = VecType (type_vec v.it) in - [] --> [t] + let t = VecT (type_vec v.it) in + [] --> [t], [] | VecTest testop -> - let t = VecType (type_vec testop) in - [t] --> [NumType I32Type] + let t = VecT (type_vec testop) in + [t] --> [NumT I32T], [] | VecUnary unop -> - let t = VecType (type_vec unop) in - [t] --> [t] + let t = VecT (type_vec unop) in + [t] --> [t], [] | VecBinary binop -> check_vec_binop binop e.at; - let t = VecType (type_vec binop) in - [t; t] --> [t] + let t = VecT (type_vec binop) in + [t; t] --> [t], [] | VecCompare relop -> - let t = VecType (type_vec relop) in - [t; t] --> [t] + let t = VecT (type_vec relop) in + [t; t] --> [t], [] | VecConvert cvtop -> - let t = VecType (type_vec cvtop) in - [t] --> [t] + let t = VecT (type_vec cvtop) in + [t] --> [t], [] | VecShift shiftop -> - let t = VecType (type_vec shiftop) in - [t; NumType I32Type] --> [VecType V128Type] + let t = VecT (type_vec shiftop) in + [t; NumT I32T] --> [t], [] | VecBitmask bitmaskop -> - let t = VecType (type_vec bitmaskop) in - [t] --> [NumType I32Type] + let t = VecT (type_vec bitmaskop) in + [t] --> [NumT I32T], [] | VecTestBits vtestop -> - let t = VecType (type_vec vtestop) in - [t] --> [NumType I32Type] + let t = VecT (type_vec vtestop) in + [t] --> [NumT I32T], [] | VecUnaryBits vunop -> - let t = VecType (type_vec vunop) in - [t] --> [t] + let t = VecT (type_vec vunop) in + [t] --> [t], [] | VecBinaryBits vbinop -> - let t = VecType (type_vec vbinop) in - [t; t] --> [t] + let t = VecT (type_vec vbinop) in + [t; t] --> [t], [] | VecTernaryBits vternop -> - let t = VecType (type_vec vternop) in - [t; t; t] --> [t] + let t = VecT (type_vec vternop) in + [t; t; t] --> [t], [] | VecSplat splatop -> - let t1 = type_vec_lane splatop in - let t2 = VecType (type_vec splatop) in - [NumType t1] --> [t2] + let t1 = NumT (type_vec_lane splatop) in + let t2 = VecT (type_vec splatop) in + [t1] --> [t2], [] | VecExtract extractop -> - let t = VecType (type_vec extractop) in - let t2 = type_vec_lane extractop in + let t1 = VecT (type_vec extractop) in + let t2 = NumT (type_vec_lane extractop) in require (lane_extractop extractop < num_lanes extractop) e.at "invalid lane index"; - [t] --> [NumType t2] + [t1] --> [t2], [] | VecReplace replaceop -> - let t = VecType (type_vec replaceop) in - let t2 = type_vec_lane replaceop in + let t1 = VecT (type_vec replaceop) in + let t2 = NumT (type_vec_lane replaceop) in require (lane_replaceop replaceop < num_lanes replaceop) e.at "invalid lane index"; - [t; NumType t2] --> [t] + [t1; t2] --> [t1], [] and check_seq (c : context) (s : infer_result_type) (es : instr list) - : infer_result_type = + : infer_result_type * idx list = match es with | [] -> - s - - | _ -> - let es', e = Lib.List.split_last es in - let s' = check_seq c s es' in - let {ins; outs} = check_instr c e s' in - push outs (pop ins s' e.at) - -and check_block (c : context) (es : instr list) (ft : func_type) at = - let FuncType (ts1, ts2) = ft in - let s = check_seq c (stack ts1) es in - let s' = pop (stack ts2) s at in - require (snd s' = []) at - ("type mismatch: block requires " ^ string_of_result_type ts2 ^ - " but stack has " ^ string_of_infer_types (snd s)) - - -(* Types *) - -let check_limits le_u {min; max} range at msg = - require (le_u min range) at msg; - match max with - | None -> () - | Some max -> - require (le_u max range) at msg; - require (le_u min max) at - "size minimum must not be greater than maximum" - -let check_num_type (t : num_type) at = - () - -let check_vec_type (t : vec_type) at = - () - -let check_ref_type (t : ref_type) at = - () - -let check_value_type (t : value_type) at = - match t with - | NumType t' -> check_num_type t' at - | VecType t' -> check_vec_type t' at - | RefType t' -> check_ref_type t' at - -let check_func_type (ft : func_type) at = - let FuncType (ts1, ts2) = ft in - List.iter (fun t -> check_value_type t at) ts1; - List.iter (fun t -> check_value_type t at) ts2 + s, [] -let check_table_type (tt : table_type) at = - let TableType (lim, it, t) = tt in - match it with - | I64IndexType -> - check_limits I64.le_u lim 0xffff_ffff_ffff_ffffL at - "table size must be at most 2^64-1" - | I32IndexType -> - check_limits I64.le_u lim 0xffff_ffffL at - "table size must be at most 2^32-1" + | e::es' -> + let {ins; outs}, xs = check_instr c e s in + check_seq (init_locals c xs) (push c outs (pop c ins s e.at)) es' -let check_memory_type (mt : memory_type) at = - let MemoryType (lim, it) = mt in - match it with - | I32IndexType -> - check_limits I64.le_u lim 0x1_0000L at - "memory size must be at most 65536 pages (4GiB)" - | I64IndexType -> - check_limits I64.le_u lim 0x1_0000_0000_0000L at - "memory size must be at most 48 bits of pages" - -let check_global_type (gt : global_type) at = - let GlobalType (t, mut) = gt in - check_value_type t at - -let check_type (t : type_) = - check_func_type t.it t.at +and check_block (c : context) (es : instr list) (it : instr_type) at = + let InstrT (ts1, ts2, _xs) = it in + let s, xs' = check_seq c (stack ts1) es in + let s' = pop c (stack ts2) s at in + require (snd s' = []) at + ("type mismatch: block requires " ^ string_of_result_type ts2 ^ + " but stack has " ^ string_of_result_type (snd s)) (* Functions & Constants *) @@ -608,98 +936,126 @@ let check_type (t : type_) = * f : func * e : instr * v : value - * t : value_type + * t : val_type * s : func_type * x : variable *) -let check_func (c : context) (f : func) = +let check_local (c : context) (loc : local) : local_type = + check_val_type c loc.it.ltype loc.at; + let init = if defaultable loc.it.ltype then Set else Unset in + LocalT (init, loc.it.ltype) + +let check_func (c : context) (f : func) : context = let {ftype; locals; body} = f.it in - let FuncType (ts1, ts2) = type_ c ftype in - let c' = {c with locals = ts1 @ locals; results = ts2; labels = [ts2]} in - check_block c' body (FuncType ([], ts2)) f.at + let _ft = func_type c ftype in + {c with funcs = c.funcs @ [type_ c ftype]} + +let check_func_body (c : context) (f : func) = + let {ftype; locals; body} = f.it in + let FuncT (ts1, ts2) = func_type c ftype in + let lts = List.map (check_local c) locals in + let c' = + { c with + locals = List.map (fun t -> LocalT (Set, t)) ts1 @ lts; + results = ts2; + labels = [ts2] + } + in check_block c' body (InstrT ([], ts2, [])) f.at let is_const (c : context) (e : instr) = match e.it with - | RefNull _ - | RefFunc _ - | Const _ - | VecConst _ -> true - | GlobalGet x -> let GlobalType (_, mut) = global c x in mut = Immutable + | Const _ | VecConst _ + | Binary (Value.I32 I32Op.(Add | Sub | Mul)) + | Binary (Value.I64 I64Op.(Add | Sub | Mul)) + | RefNull _ | RefFunc _ + | RefI31 | StructNew _ | ArrayNew _ | ArrayNewFixed _ -> true + | GlobalGet x -> let GlobalT (mut, _t) = global c x in mut = Cons | _ -> false -let check_const (c : context) (const : const) (t : value_type) = +let check_const (c : context) (const : const) (t : val_type) = require (List.for_all (is_const c) const.it) const.at "constant expression required"; - check_block c const.it (FuncType ([], [t])) const.at + check_block c const.it (InstrT ([], [t], [])) const.at -(* Tables, Memories, & Globals *) +(* Globals, Tables, Memories *) -let check_table (c : context) (tab : table) = - let {ttype} = tab.it in - check_table_type ttype tab.at - -let check_memory (c : context) (mem : memory) = +let check_global (c : context) (glob : global) : context = + let {gtype; ginit} = glob.it in + let GlobalT (_mut, t) = gtype in + check_global_type c gtype glob.at; + check_const c ginit t; + {c with globals = c.globals @ [gtype]} + +let check_table (c : context) (tab : table) : context = + let {ttype; tinit} = tab.it in + let TableT (_lim, _it, rt) = ttype in + check_table_type c ttype tab.at; + check_const c tinit (RefT rt); + {c with tables = c.tables @ [ttype]} + +let check_memory (c : context) (mem : memory) : context = let {mtype} = mem.it in - check_memory_type mtype mem.at + check_memory_type c mtype mem.at; + {c with memories = c.memories @ [mtype]} let check_elem_mode (c : context) (t : ref_type) (mode : segment_mode) = match mode.it with | Passive -> () | Active {index; offset} -> - let TableType (_, it, et) = table c index in - require (t = et) mode.at + let TableT (_lim, it, et) = table c index in + require (match_ref_type c.types t et) mode.at ("type mismatch: element segment's type " ^ string_of_ref_type t ^ " does not match table's element type " ^ string_of_ref_type et); check_const c offset (value_type_of_index_type it) | Declarative -> () -let check_elem (c : context) (seg : elem_segment) = +let check_elem (c : context) (seg : elem_segment) : context = let {etype; einit; emode} = seg.it in - List.iter (fun const -> check_const c const (RefType etype)) einit; - check_elem_mode c etype emode + check_ref_type c etype seg.at; + List.iter (fun const -> check_const c const (RefT etype)) einit; + check_elem_mode c etype emode; + {c with elems = c.elems @ [etype]} let check_data_mode (c : context) (mode : segment_mode) = match mode.it with | Passive -> () | Active {index; offset} -> - let MemoryType (_, it) = memory c index in + let MemoryT (_, it) = memory c index in check_const c offset (value_type_of_index_type it) | Declarative -> assert false -let check_data (c : context) (seg : data_segment) = +let check_data (c : context) (seg : data_segment) : context = let {dinit; dmode} = seg.it in - check_data_mode c dmode - -let check_global (c : context) (glob : global) = - let {gtype; ginit} = glob.it in - let GlobalType (t, mut) = gtype in - check_const c ginit t + check_data_mode c dmode; + {c with datas = c.datas @ [()]} (* Modules *) let check_start (c : context) (start : start) = let {sfunc} = start.it in - require (func c sfunc = FuncType ([], [])) start.at + let ft = as_func_str_type (expand_def_type (func c sfunc)) in + require (ft = FuncT ([], [])) start.at "start function must not have parameters or results" -let check_import (im : import) (c : context) : context = +let check_import (c : context) (im : import) : context = let {module_name = _; item_name = _; idesc} = im.it in match idesc.it with | FuncImport x -> - {c with funcs = type_ c x :: c.funcs} + let _ = func_type c x in + {c with funcs = c.funcs @ [type_ c x]} + | GlobalImport gt -> + check_global_type c gt idesc.at; + {c with globals = c.globals @ [gt]} | TableImport tt -> - check_table_type tt idesc.at; - {c with tables = tt :: c.tables} + check_table_type c tt idesc.at; + {c with tables = c.tables @ [tt]} | MemoryImport mt -> - check_memory_type mt idesc.at; - {c with memories = mt :: c.memories} - | GlobalImport gt -> - check_global_type gt idesc.at; - {c with globals = gt :: c.globals} + check_memory_type c mt idesc.at; + {c with memories = c.memories @ [mt]} module NameSet = Set.Make(struct type t = Ast.name let compare = compare end) @@ -707,45 +1063,30 @@ let check_export (c : context) (set : NameSet.t) (ex : export) : NameSet.t = let {name; edesc} = ex.it in (match edesc.it with | FuncExport x -> ignore (func c x) + | GlobalExport x -> ignore (global c x) | TableExport x -> ignore (table c x) | MemoryExport x -> ignore (memory c x) - | GlobalExport x -> ignore (global c x) ); require (not (NameSet.mem name set)) ex.at "duplicate export name"; NameSet.add name set + +let check_list f xs (c : context) : context = + List.fold_left f c xs + let check_module (m : module_) = - let - { types; imports; tables; memories; globals; funcs; start; elems; datas; - exports } = m.it - in - let c0 = - List.fold_right check_import imports - { empty_context with - refs = Free.module_ ({m.it with funcs = []; start = None} @@ m.at); - types = List.map (fun ty -> ty.it) types; - } - in - let c1 = - { c0 with - funcs = c0.funcs @ List.map (fun f -> type_ c0 f.it.ftype) funcs; - tables = c0.tables @ List.map (fun tab -> tab.it.ttype) tables; - memories = c0.memories @ List.map (fun mem -> mem.it.mtype) memories; - elems = List.map (fun elem -> elem.it.etype) elems; - datas = List.map (fun _data -> ()) datas; - } - in + let refs = Free.module_ ({m.it with funcs = []; start = None} @@ m.at) in let c = - { c1 with globals = c1.globals @ List.map (fun g -> g.it.gtype) globals } + {empty_context with refs} + |> check_list check_type m.it.types + |> check_list check_import m.it.imports + |> check_list check_func m.it.funcs + |> check_list check_table m.it.tables + |> check_list check_memory m.it.memories + |> check_list check_global m.it.globals + |> check_list check_elem m.it.elems + |> check_list check_data m.it.datas in - List.iter check_type types; - List.iter (check_global c1) globals; - List.iter (check_table c1) tables; - List.iter (check_memory c1) memories; - List.iter (check_elem c1) elems; - List.iter (check_data c1) datas; - List.iter (check_func c) funcs; - Lib.Option.app (check_start c) start; - ignore (List.fold_left (check_export c) NameSet.empty exports); - require (List.length c.memories <= 1) m.at - "multiple memories are not allowed (yet)" + List.iter (check_func_body c) m.it.funcs; + Option.iter (check_start c) m.it.start; + ignore (List.fold_left (check_export c) NameSet.empty m.it.exports) diff --git a/proposals/extended-const/Overview.md b/proposals/extended-const/Overview.md new file mode 100644 index 0000000000..864749a63d --- /dev/null +++ b/proposals/extended-const/Overview.md @@ -0,0 +1,63 @@ +# Extended Constant Expressions + +## Introduction + +This page describes a proposal for extending constant expressions in +WebAssembly. The current [spec][] for constant expressions is fairly limited, +and was always intended to be extended. + +This proposal adds new instructions to the list of *constant instructions* that +can be used in *constant expressions*. + +An overview of this proposal was presented at the 01-19-20 CG meeting along with +a brief [presentation][]. This issue was originally discusses in +https://github.com/WebAssembly/design/issues/1392. + +### Motivation + +The primay/initial motivation comes from LLVM where we could benefit from using +integer addition in both global initializers and in segment initializers. Both +of these use cases stem from dynamic linking, which is currently +[experimental][abi]. + +1. With dynamic linking the data segments are relative to a global import called + `__memory_base` which is supplied by the dynamic linker. We currently have + to have [combine][] all our memory segments into one because there is no way + to do `__memory_base + CONST_OFFSET` in a segment initilizer. + +2. The linker currently has to generate [dynamic relocations][reloc] for certain + WebAssembly globals because its currently not possible to initialize a global + with a value of `__memory_base + CONST_OFFSET`. Specifically, this happens + when the static linker decides that a given symbol is local to the currently + module. In this case, rather than importing a global it creates a new + global which points insides the a data segment (i.e. it's value is an offset + from `__memory_base` or `__table_base` which are themselves imported). + +## New Instructions + +This proposal adds the following new instructions to the list of valid constant +instruction: + + - `i32.add` + - `i32.sub` + - `i32.mul` + - `i64.add` + - `i64.sub` + - `i64.mul` + +## Implementation Status + +- spec interpreter: Done +- Firefox: [Done](https://github.com/WebAssembly/debugging/issues/17#issuecomment-1041130743) +- Chrome/v8: [Done](https://chromium.googlesource.com/v8/v8/+/bf1565d7081cabc510e39c42eaea67ea6e79484e) +- Safari: ? +- wabt: Done +- llvm: Done +- binaryen: Done +- emscripten: Done + +[spec]: https://webassembly.github.io/spec/core/valid/instructions.html#constant-expressions +[presentation]: https://docs.google.com/presentation/d/1sM9mJJ6iM7D8324ipYxot91hSKnWCtB8jX4Kh3bde5E +[abi]: https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md +[combine]: https://github.com/llvm/llvm-project/blob/5f9be2c3e37c0428ba56876dd84af04b8d9d8915/lld/wasm/Writer.cpp#L868 +[reloc]: https://github.com/llvm/llvm-project/blob/5f9be2c3e37c0428ba56876dd84af04b8d9d8915/lld/wasm/SyntheticSections.cpp#L311 diff --git a/proposals/function-references/Overview.md b/proposals/function-references/Overview.md new file mode 100644 index 0000000000..da2b27ab58 --- /dev/null +++ b/proposals/function-references/Overview.md @@ -0,0 +1,567 @@ +# Typed Function References for WebAssembly + +## Introduction + +This proposal adds function references that are typed and can be called directly. Unlike `funcref` and the existing `call_indirect` instruction, typed function references do not need to be stored in a table to be called (though they can). A typed function reference can be formed from any function index. + +The proposal distinguished regular and nullable function reference. The former cannot be null, and a call through them does not require any runtime check. + +The proposal has instructions for producing and consuming (calling) function references. It also includes instruction for testing and converting between regular and nullable references. + +Typed references have no canonical default value, because they cannot be null. To enable storing them in locals, which so far depend on default values for initialization, the proposal also tracks the initialization status of locals during validation. + + +### Motivation + +* Enable efficient indirect function calls without runtime checks + +* Represent first-class function pointers without the need for tables + +* Easier and more efficient exchange of function references between modules and with host environment + +* Separate independently useful features from [GC proposal](https://github.com/WebAssembly/gc/blob/master/proposals/gc/Overview.md) + + +### Summary + +* This proposal is based on the [reference types proposal](https://github.com/WebAssembly/reference-types) + +* Add a new form of *typed reference type* `ref $t` and a nullable variant `(ref null $t)`, where `$t` is a type index; can be used as both a value type or an element type for tables + +* Add an instruction `ref.as_non_null` that converts a nullable reference to a non-nullable one or traps if null + +* Add an instruction `br_on_null` that converts a nullable reference to a non-nullable one or branches if null, as well as the inverted `br_on_non_null` + +* Add an instruction `call_ref` for calling a function through a `ref $t` + +* Refine the instruction `ref.func $f` to return a typed function reference + +* Track initialisation status of locals during validation and only allow `local.get` after a `local.set/tee` in the same or a surrounding block. + +* Add an optional initializer expression to table definitions, for element types that do not have an implicit default value. + + +### Examples + +The function `$hof` takes a function pointer as parameter, and is invoked by `$caller`, passing `$inc` as argument: +```wasm +(type $i32-i32 (func (param i32) (result i32))) + +(func $hof (param $f (ref $i32-i32)) (result i32) + (i32.add (i32.const 10) (call_ref $i32-i32 (i32.const 42) (local.get $f))) +) + +(func $inc (param $i i32) (result i32) + (i32.add (local.get $i) (i32.const 1)) +) + +(func $caller (result i32) + (call $hof (ref.func $inc)) +) +``` + +It is also possible to create a typed function table: +```wasm +(table 1 (ref $i32-i32) (ref.func $inc)) +``` +Such a table can neither contain `null` entries nor functions of another type. Because entries can't be `null`, tables of concrete type are required to be declared with an explicit initialisation value. Any use of `call_indirect` on this table does hence avoid all runtime checks beyond the basic bounds check. By using multiple tables, each one can be given a homogeneous type. The table can be initialized with an initializer or by growing it. + +Typed function references are a subtype of `funcref`, so they can also be used as untyped references. All previous uses of `ref.func` remain valid: +```wasm +(func $f (param i32)) +(func $g) +(func $h (result i64)) + +(table 10 funcref) + +(func $init + (table.set (i32.const 0) (ref.func $f)) + (table.set (i32.const 1) (ref.func $g)) + (table.set (i32.const 2) (ref.func $h)) +) +``` + + +## Language + +Based on [reference types proposal](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), which introduces types `funcref` and `externref`. + + +### Types + +#### Heap Types + +A *heap type* denotes a user-defined or pre-defined data type that is not a primitive scalar: + +* `heaptype ::= | func | extern` + - `$t ok` iff `$t` is defined in the context + - `func ok` and `extern ok`, always + +* In the binary encoding, + - the `` type is encoded as a (positive) signed LEB + - the others use the same (negative) opcodes as the existing `funcref`, `externref`, respectively + + +#### Reference Types + +A *reference type* denotes the type of a reference to some data. It may either include or exclude null: + +* `(ref null? )` is a new form of reference type + - `reftype ::= ref null? ` + - `ref null? ok` iff ` ok` + +* Reference types now *all* take the form `ref null? ` + - `funcref` and `externref` are reinterpreted as abbreviations (in both binary and text format) for `(ref null func)` and `(ref null extern)`, respectively + - Note: this refactoring allows using `func` and `extern` as heap types, which is relevant for future extensions such as [type imports](https://github.com/WebAssembly/proposal-type-imports/proposals/type-imports/Overview.md) + +* In the binary encoding, + - null and non-null variant are distinguished by two new (negative) type opcodes + - the opcodes for `funcref` and `externref` continue to exist as shorthands as described above + + +#### Type Definitions + +* Type definitions are validated in sequence and without allowing recursion + - `functype* ok` + - iff `functype* = epsilon` + - or `functype* = functype'* functype''`and `functype'* ok` and `functype'' ok` using only type indices up to `|functype'*|-1` + + +#### Subtyping + +The following rules, now defined in terms of heap types, replace and extend the rules for [basic reference types](https://github.com/WebAssembly/reference-types/proposals/reference-types/Overview.md#subtyping). + +##### Reference Types + +* Reference types are covariant in the referenced heap type + - `(ref null ) <: (ref null )` + - iff ` <: ` + - `(ref ) <: (ref )` + - iff ` <: ` + +* Non-null types are subtypes of possibly-null types + - `(ref ) <: (ref null )` + - iff ` <: ` + +##### Heap Types + +* Any function type is a subtype of `func` + - `$t <: func` + - iff `$t = ` + +##### Type Indices + +* Type indices are subtypes only if they define equivalent types + - `$t <: $t'` + - iff `$t = ` and `$t' = ` and ` == ` + - Note: Function types are invariant for now. This may be relaxed in future extensions. + + +#### Defaultability + +* Any numeric value type is defaultable (to 0) + +* A reference value type is defaultable (to `null`) iff it is of the form `ref null $t` + +* Function-level locals must have a type that is defaultable. + +* Table definitions with a type that is not defaultable must have an initializer value. (Imports are not affected.) + + +#### Local Types + +* Locals are now recorded in the context with types of the following form: + - `localtype ::= set? ` + - the flag `set` records that the local has been initialized + - all locals with non-defaultable type start out unset + + +#### Instruction Types + +* Instructions and instruction sequences are now typed with types of the following form: + - `instrtype ::= *` + - the local indices record which locals have been set by the instructions + - most typing rules except those for locals and for instruction sequences remain unchanged, since the index list is empty + +* There is a natural notion of subtyping on instruction types: + - `[t1*] -> [t2*] x1* <: [t3*] -> [t4*] x2*` + - iff `t1* = t0* t1'*` + - and `t2* = t0* t2'*` + - and `[t1'*] -> [t2'*] <: [t3*] -> [t4*]*` + - and `{x2*} subset {x1*}` + +* Block types are instruction types with empty index set. + +Note: Extending block types with index sets to allow initialization status to last beyond a block's end is a possible extension. + + +### Instructions + +#### Functions + +* `ref.func` creates a function reference from a function index + - `ref.func $f : [] -> [(ref $t)]` + - iff `$f : $t` + - this is a *constant instruction* + +* `call_ref ` calls a function through a reference + - `call_ref $t : [t1* (ref null $t)] -> [t2*]` + - iff `$t = [t1*] -> [t2*]` + - traps on `null` + +* `return_call_ref` tail-calls a function through a reference + - `return_call_ref $t : [t1* (ref null $t)] -> [t2*]` + - iff `$t = [t1*] -> [t2*]` + - and `t2* <: C.result` + - traps on `null` + + +#### Optional References + +* `ref.null ` is generalised to take a `` immediate + - `ref.null ht: [] -> [(ref null ht)]` + - iff `ht ok` + +* `ref.as_non_null` converts a nullable reference to a non-null one + - `ref.as_non_null : [(ref null ht)] -> [(ref ht)]` + - iff `ht ok` + - traps on `null` + +* `br_on_null ` checks for null and branches if present + - `br_on_null $l : [t* (ref null ht)] -> [t* (ref ht)]` + - iff `$l : [t*]` + - and `ht ok` + - branches to `$l` on `null`, otherwise returns operand as non-null + +* `br_on_non_null ` checks for null and branches if not present + - `br_on_non_null $l : [t* (ref null ht)] -> [t*]` + - iff `$l : [t* (ref ht)]` + - and `ht ok` + - branches to `$l` if operand is not `null`, passing the operand itself under non-null type (along with potential additional operands) + +* Note: `ref.is_null` already exists via the [reference types proposal](https://github.com/WebAssembly/reference-types) + + +#### Locals + +Typing of local instructions is updated to account for the initialization status of locals. + +* `local.get ` + - `local.get $x : [] -> [t]` + - iff `$x : set t` + +* `local.set ` + - `local.set $x : [t] -> [] $x` + - iff `$x : set? t` + +* `local.tee ` + - `local.tee $x : [t] -> [t] $x` + - iff `$x : set? t` + +Note: These typing rules do not try to exclude indices for locals that have already been set, but an implementation could. + + +#### Instruction Sequences + +Typing of instruction sequences is updated to account for initialization of locals. + +* `instr*` + - `instr1 instr* : [t1*] -> [t3*] x1* x2*` + - iff `instr1 : [t1*] -> [t2*] x1*` + - and `instr* : [t2*] -> [t3*] x2*` under a context where `x1*` are changed to `set` + - `epsilon : [] -> [] epsilon` + +Note: These typing rules do not try to eliminate duplicate indices, but an implementation could. + +A subsumption rule allows to go to a supertype for any instruction: + +* `instr` + - `instr : [t1*] -> [t2*] x*` + - iff `instr : [t1'*] -> [t2'*] x'*` + - and `[t1'*] -> [t2'*] x'* <: [t1*] -> [t2*] x*` + + +### Tables + +Table definitions have an initializer value: + +* `(table )` is an extended form of table definition + - `(table ) ok` iff ` ok` and ` : ` + +* `(table )` is shorthand for `(table (ref.null ))`, where `` is the element heap type contained in `` + - note: the typing rule above implies that this only validates if the table's reference type is nullable + + +## Binary Format + +### Types + +#### Reference Types + +| Opcode | Type | Parameters | +| ------ | --------------- | ---------- | +| -0x10 | `funcref` | | +| -0x11 | `externref` | | +| -0x1c | `(ref ht)` | `$t : heaptype` | +| -0x1d | `(ref null ht)` | `$t : heaptype` | + +#### Heap Types + +The opcode for heap types is encoded as an `s33`. + +| Opcode | Type | Parameters | +| ------ | --------------- | ---------- | +| i >= 0 | i | | +| -0x10 | `func` | | +| -0x11 | `extern` | | + +### Instructions + +| Opcode | Instruction | Immediates | +| ------ | ------------------------ | ---------- | +| 0x14 | `call_ref $t` | `$t : u32` | +| 0x15 | `return_call_ref $t` | `$t : u32` | +| 0xd4 | `ref.as_non_null` | | +| 0xd5 | `br_on_null $l` | `$l : u32` | +| 0xd6 | `br_on_non_null $l` | `$l : u32` | + +### Tables + +Entries to the table section are extended as follows: + +| Table Definition | Note | +|------------------|------| +| tabletype | null-initialized table (as before) | +| 0x40 0x00 tabletype constexpr | explicitly initialized table | + +The encoding of a table type starts with the encoding of a reference type, which cannot be 0x40 (since this is a pseudo type code otherwise only used in block types). Consequently, both forms can be distinguished by the first byte. +The second byte is reserved for possible future extensions. + + +## JS API + +The group decided to go with the "no-frills" approach for the JS API for the time being. +In the context of this proposal, that means that conversions (or type reflection) at concrete reference type throws a `TypeError` exception. + + +### Value Conversions + +#### Reference Types + +In addition to the rules for basic reference types: + +* If the target type of a ToWebAssemblyValue is a concrete reference type, then throw `TypeError`. + +* If the source type of a ToJSValue is a concrete reference type, then throw `TypeError`. + + +### Constructors + +#### `Global` + +* `TypeError` is produced if the `Global` constructor is invoked without a value argument but a type that is not defaultable. + +#### `Table` + +* The `Table` constructor gets an additional optional argument `init` that is used to initialize the table slots. It defaults to `null`. A `TypeError` is produced if the argument is omitted and the table's element type is not defaultable. + +* The `Table` method `grow` gets an additional optional argument `init` that is used to initialize the new table slots. It defaults to `null`. A `TypeError` is produced if the argument is omitted and the table's element type is not defaultable. + + +### Type Reflection + +In the presence of the [JS type reflection proposal](https://github.com/WebAssembly/js-types): + +* `Global.type` throws `TypeError` when encountering a concrete reference type. + +* `Table.type` throws `TypeError` when encountering a concrete reference type as element type. + +* `Function.type` throws `TypeError` when encountering a concrete reference type as parameter or result type. + +* `Module.imports` throws `TypeError` when encountering a concrete reference type in any of the global, table or function types of imports. + +* `Module.exports` throws `TypeError` when encountering a concrete reference type in any of the global, table or function types of exports. + +Note: The [GC proposal](https://github.com/WebAssembly/gc) is expected to at least add `anyref` as a recognised `RefType`, but possibly throw on other abstract reference types. + + +### Possible Extension: Full Type Reflection + +Post-MVP, the type reflection abilities of the JS API could be refined based on the [JS type reflection proposal](https://github.com/WebAssembly/js-types). + +### Type Representation + +* A `RefType` can be described by an object of the form `{ref: HeapType, ...}` + - `type RefType = ... | {ref: HeapType, nullable: bool}` + +* A `HeapType` can be described by a suitable union type + - `type HeapType = "func" | "extern" | DefType` + +Note: The [GC proposal](https://github.com/WebAssembly/gc) adds additional heap types. + + +### Value Conversions + +#### Reference Types + +In addition to the rules for basic reference types: + +* Any function that is an instance of `WebAssembly.Function` with type `` is allowed as `ref ` or `ref null `. + +* Any non-null external reference is allowed as `ref extern`. + +* The `null` value is allowed as `ref null ht`. + +Note: The [GC proposal](https://github.com/WebAssembly/gc) is expected to allow additional conversions for `anyref`. + + +## Possible Extension: Function Subtyping + +In the future (especially with the [GC proposal](https://github.com/WebAssembly/gc/blob/master/proposals/gc/Overview.md)) it will be desirable to allow the usual subtyping rules on function types. +This is relevant, for example, to encode [method tables](https://github.com/WebAssembly/gc/blob/master/proposals/gc/Overview.md#objects-and-method-tables). + +While doing so, the semantics of static and dynamic type checks need to remain coherent. +That is, both static and dynamic type checking (as well as link-time checks) must use the same subtype relation. +Otherwise, values of a subtype would not always be substitutable for values of a supertype, thereby breaking a fundemental property of subtyping and various transformations and optimisations relying on substitutability. + +At the same time, care has to be taken to not damage the performance of existing instructions. +In particular, `call_indirect` performs a runtime type check over (structural) function types that is expected to be no more expensive than a single pointer comparison. +That effectively rules out allowing any form of subtyping to kick in for this check. + + +### Exact Types + +This tension with `call_indirect` can be resolved by distinguishing function types that have further subtypes and those that don't. +The latter are sometimes called _exact_ types. + +Exact types might come in handy in a few other circumstances, +so we could distinguish the two forms in a generic manner by enriching heap types with a flag as follows: + +* `heaptype ::= exact? | func | extern` + +Exact types are themselves subtypes of corresponding non-exact types, +but the crucial difference is that they do not have further subtypes themselves. +That is, the following subtype rules would be defined on heap types: + +* `exact? $t <: $t'` + - iff `$t = ` and `$t' = ` + - and ` <: ` + +* `exact $t <: exact $t'` + - iff `$t = ` and `$t' = ` + - and ` == ` + +in combination with the canonical rules for function subtyping itself: + +* `[t1*]->[t2*] <: [t1'*]->[t2'*]` + - iff `(t1' <: t1)*` + - and `(t2 <: t2')*` + +(and appropriate rules for other types that may be added to the type section in the future). + +With this: + +* The type of each function definition is interpreted as its exact type. + +* The type annotation on `call_indirect` is interpreted as an exact type. + (It would also be possible to extend `call_indirect` to allow either choice, leaving it to to the producer to make the trade-off between performance and flexibility.) + +* For imports and references, every module can decide individually where it requires an exact type. + +For any import or reference in a module's interface that flows into a function table on which it invokes `call_indirect`, the module can thereby enforce exact types that are guaranteed to succeed the signature check. + + +### Example + +Consider: +``` +(module $A + (type $proc (func)) + (func (export "f") (result funcref) ...) + (func (export "g") (result (ref $proc)) ...) +) + +(module $B + (type $th (func (result funcref))) + (func $h (import "..." "h") (type exact $th)) + + (table $tab funcref (elem $h)) + + (func $call + ... + (call_indirect $th (i32.const 0)) + ... + ) + + (func $update (param $f (ref exact $th)) + (table.set (local.get $f) (i32.const 0)) + ) +) +``` +In module `$B`, the imported function `$h` requires an exact type match. +That ensures that a client cannot supply a function of a subtype, +such as `A.g`, +which would break the use of `call_indirect` in `$call`. +An attempt to do so will fail at link time. +Passing `A.f` would be accepted, however. + +Similarly, `$update` can only be passed a reference with exact type, +again ensuring that `$call` will not fail unexpectedly subsequently. + +If a function import (or reference) never interferes with `call_indirect`, however, then the import can have a more flexible type: +``` +(module $C + (type $th (func (result funcref))) + (func $h (import "..." "h") (type $th)) + + (func $k (result (ref $th)) + (call $h) + ) +) +``` +Here, the laxer import type is okay: for `$h`, any function that returns a subtype of `funcref` is fine, since all choices will satisfy the result type `(ref $th)` of function `$k` performing the call. + +More generally, the following imports are well-typed: +``` +(module + (type $proc (func)) + (type $tf (func (result funcref))) + (type $tg (func (result (ref $proc)))) + + (func $h1 (import "A" "g") (type $tg)) + (func $h2 (import "A" "g") (type exact $tg)) + (func $h3 (import "A" "g") (type $tf)) + ;; (func $h4 (import "A" "g") (type exact $tf)) ;; fails to link! +) +``` + +Note that the use of exact types for imports or references flowing into a function table is not _enforced_ by the type system. +It is the producer's responsibility to protect its interface with suitably narrow types. + + +### Backwards Compatibility and Textual Syntax + +For exposition, we assumed above that exact types are a new construct, +denoted by an explicit flag. +However, such a design would still change the meaning of existing type expressions (by suddenly allowing subtyping) and would hence not be backwards compatible, for the reasons stated above. + +To avoid breaking existing modules when operating in new environments that support subtyping, we must preserve the current subtype-less, i.e. exact, meaning of the pre-existing function types -- at least in the binary format. +Subtypable function references/imports must be introduced as the new construct. + +It's a slightly different question what to do for the text format, however. +To maintain full backwards compatibility, the flag would likewise need to be inverted: +instead of an optional `exact` flag we'd have an optional `sub` flag with the opposite meaning: + +* `heaptype ::= sub? | func | extern` + +In the binary format it doesn't really matter which way both alternatives are encoded. +But for the text format, this inversion will be rather annoying: +the normal use case is to allow subtyping; +but to express that, every import or reference type will have to explicitly state `sub`. + +One possibility might be to keep the `exact` syntax above for the text format, +and effectively change the interpretation of the existing syntax. +That means that existing text format programs using e.g. function imports would change their meaning, and running wasm/wat converters would produce different results. +In particular, rebuilding a binary from a pre-existing text file may produce a binary with weaker interface requirements. + +Is the risk involved in that worth taking? How much of a problem do we think this would be? diff --git a/proposals/gc/Charter.md b/proposals/gc/Charter.md new file mode 100644 index 0000000000..88a62fde85 --- /dev/null +++ b/proposals/gc/Charter.md @@ -0,0 +1,60 @@ +# WebAssembly Garbage Collection Subgroup Charter + +The Garbage Collection Subgroup is a sub-organization of the +[WebAssembly Community Group](https://www.w3.org/community/webassembly/) +of the W3C. +As such, it is intended that its charter align with that of the CG. In particular, +the sections of the [CG charter](https://webassembly.github.io/cg-charter/) relating to +[Community and Business Group Process](https://webassembly.github.io/cg-charter/#process), +[Contribution Mechanics](https://webassembly.github.io/cg-charter/#contrib), +[Transparency](https://webassembly.github.io/cg-charter/#transparency), +and +[Decision Process](https://webassembly.github.io/cg-charter/#decision) +also apply to the Subgroup. + +## Goals + +The mission of this subgroup is to provide a forum for collaboration on the standardisation of garbage collection support for WebAssembly. + +## Scope + +The Subgroup will consider topics related to garbage collection for Wasm, including: + +- an instruction set for defining and manipulating managed data types, +- type system rules for validating such instructions, +- APIs for accessing managed data types outside Wasm or a Wasm engine, +- tool and language assists for user-space GC in linear memory, +- code generation for compilers targetting Wasm with GC extensions. + +## Deliverables + +### Specifications + +The Subgroup may produce several kinds of specification-related work output: + +- new specifications in standards bodies or working groups + (e.g. W3C WebAssembly WG or Ecma TC39), + +- new specifications outside of standards bodies + (e.g. similar to the LLVM object file format documentation in Wasm tool conventions). + +### Non-normative reports + +The Subgroup may produce non-normative material such as requirements +documents, recommendations, and case studies. + +### Software + +The Subgroup may produce software related to garbage collection in Wasm +(either as standalone libraries, tooling, or integration of interface-related functionality in existing CG software). +These may include + +- extensions to the Wasm reference interpreter, +- extensions to the Wasm test suite, +- compilers and tools for producing code that uses Wasm GC extensions, +- tools for implementing Wasm with GC, +- tools for debugging programs using Wasm GC extensions. + +## Amendments to this Charter and Chair Selection + +This charter may be amended, and Subgroup Chairs may be selected by vote of the full WebAssembly Community Group. diff --git a/proposals/gc/MVP-JS.md b/proposals/gc/MVP-JS.md new file mode 100644 index 0000000000..455465bb55 --- /dev/null +++ b/proposals/gc/MVP-JS.md @@ -0,0 +1,84 @@ +# GC MVP JS API + +This document describes the proposed changes to the +[WebAssembly JS API spec](http://webassembly.github.io/spec/js-api/) that are +associated with the [changes to the WebAssembly core spec](MVP.md). + +## Goals + +The goal of the MVP JS API is to be as simple as possible, providing just enough +expressivity to serve as the foundation for interop between JS and Wasm GC. +Making it easy to access, manipulate, and extend Wasm GC objects from +hand-written JS is left as future work. For now, require using the established +pattern of exporting accessor functions to interact with Wasm data from JS. + +## Explanation + +WebAssembly GC objects are exposed to JS via opaque exotic object wrappers. The +wrapper object internally holds a reference to the underlying WasmGC object to +facilitate identity-preserving round-tripping from WebAssembly to JS and back. +The wrapper object serves only to opaquely reference the underlying WebAssembly +value and does not support inspecting or modifying the value in any way. The +wrapper object also cannot be extended with new JS properties. The wrapper +objects supports the following JS operations: + + - `GetPrototypeOf`: returns `null`. + - `SetPrototypeOf`: returns `false` (and does not set a prototype). + - `IsExtensible`: returns `false`. + - `PreventExtensions`: returns `false`. + - `GetOwnProperty`: returns `undefined`. + - `DefineOwnProperty`: returns `false`. + - `HasProperty`: returns `false`. + - `Get`: returns `undefined`. + - `Set`: throws a `TypeError`. + - `Delete`: throws a `TypeError`. + - `OwnPropertyKeys`: returns an empty list. + +These operations may be extended in the future to support more useful ways of +interacting with Wasm GC objects from JS. + +When a non-null internal reference value (i.e. a value with of any subtype of +`anyref`) is passed out to JS, the `ToJSValue` algorithm will implicitly call +`extern.externalize` on it to get a JS value. Similarly, when a JS value is +passed into WebAssembly in a location that expects an internal reference, the +`ToWebAssemblyValue` algorithm will implicitly call `extern.internalize` on it +to get an internal reference. `ToWebAssemblyValue` will then cast the internal +reference to the specific expected type, and if that fails, it will throw a +`TypeError`. JS `null` converts to and is converted from WebAssembly `null` +values. + +In JS hosts, `extern.internalize` will behave differently depending on the JS +value that is being internalized: + + - If the value is a number and is an integer in signed i31 range, it will be + converted to an i31 reference. + - If the value is a wrapper for an exported GC object, the wrapped internal + reference is returned. + - Otherwise if it is some other JS value, it will be internalized as an opaque + reference that cannot be downcast from anyref. + +Similarly, `extern.externalize` will behave differently depending on the +internal reference value that is being externalized: + + - If the value is an i31 reference, convert it to a JS number. + - If the value is a struct or array reference, create a JS wrapper for it, + reusing any pre-existing wrapper to ensure bidirectional identity + preservation. + - Otherwise if the reference is an internalized host reference, return the + original external host reference. + +_TODO: avoid having to patch the behavior of `extern.internalize` and +`extern.internalize` by converting to/from JS numbers separately._ + +## Implementation-defined Limits + +The following limits will be added to the Implementation-defined Limits +[section](https://webassembly.github.io/spec/js-api/index.html#limits) of the JS +API. + + - The maximum number of recursion groups is 1000000. (The maximum number of + individual types remains unchanged and is also 1000000.) + - The maximum number of struct fields is 10000. + - The maximum number of operands to `array.new_fixed` is 10000. + - The maximum length of a supertype chain is 63. (A type declared with no + supertypes has a supertype chain of length 0) diff --git a/proposals/gc/MVP.md b/proposals/gc/MVP.md new file mode 100644 index 0000000000..cec9ee12b7 --- /dev/null +++ b/proposals/gc/MVP.md @@ -0,0 +1,1069 @@ +# GC v1 Extensions + +See [overview](Overview.md) for background. + +The functionality provided with this first version of GC support for Wasm is intentionally limited in the spirit of a "a minimal viable product" (MVP). +As a rough guideline, it includes only essential functionality and avoids features that may provide better performance in some cases, but whose lack can be worked around in a reasonable manner. + +In particular, it is expected that compiling to this minimal functionality will require a substantial number of runtime casts that may be eliminated by future extensions. +A range of such extensions are discussed in the [Post-MVP](Post-MVP.md) document. +Most of them are expected to be added before GC support can be considered reasonably "complete". + + +## Language + +Based on the following proposals: + +* [reference types](https://github.com/WebAssembly/reference-types), which introduces reference types + +* [typed function references](https://github.com/WebAssembly/function-references), which introduces typed references `(ref null? $t)` etc. + +Both proposals are prerequisites. + + +### Types + +#### Heap Types + +[Heap types](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#types) classify reference types and are extended. There now are 3 disjoint hierarchies of heap types: + +1. _Internal_ (values in Wasm representation) +2. _External_ (values in a host-specific representation) +3. _Functions_ + +Heap types `extern` and `func` already exist via [reference types proposal](https://github.com/WebAssembly/reference-types), and `(ref null? $t)` via [typed references](https://github.com/WebAssembly/function-references); `extern` and `func` are the common supertypes (a.k.a. top) of all external and function types, respectively. + +The following additions are made to the hierarchies of heap types: + +* `any` is a new heap type + - `heaptype ::= ... | any` + - the common supertype (a.k.a. top) of all internal types + +* `none` is a new heap type + - `heaptype ::= ... | none` + - the common subtype (a.k.a. bottom) of all internal types + +* `noextern` is a new heap type + - `heaptype ::= ... | noextern` + - the common subtype (a.k.a. bottom) of all external types + +* `nofunc` is a new heap type + - `heaptype ::= ... | nofunc` + - the common subtype (a.k.a. bottom) of all function types + +* `eq` is a new heap type + - `heaptype ::= ... | eq` + - the common supertype of all referenceable types on which comparison (`ref.eq`) is allowed (this may include host-defined external types) + +* `struct` is a new heap type + - `heaptype ::= ... | struct` + - the common supertype of all struct types + +* `array` is a new heap type + - `heaptype ::= ... | array` + - the common supertype of all array types + +* `i31` is a new heap type + - `heaptype ::= ... | i31` + - the type of unboxed scalars + +We distinguish these *abstract* heap types from *concrete* heap types `$t` that reference actual definitions in the type section. +Most abstract heap types are a supertype of a class of concrete heap types. +Moreover, they form several small [subtype hierarchies](#subtyping) among themselves. + + +#### Reference Types + +New abbreviations are introduced for reference types in binary and text format, corresponding to `funcref` and `externref`: + +* `anyref` is a new reference type + - `anyref == (ref null any)` + +* `nullref` is a new reference type + - `nullref == (ref null none)` + +* `nullexternref` is a new reference type + - `nullexternref == (ref null noextern)` + +* `nullfuncref` is a new reference type + - `nullfuncref == (ref null nofunc)` + +* `eqref` is a new reference type + - `eqref == (ref null eq)` + +* `structref` is a new reference type + - `structref == (ref null struct)` + +* `arrayref` is a new reference type + - `arrayref == (ref null array)` + +* `i31ref` is a new reference type + - `i31ref == (ref null i31)` + + +#### Type Definitions + +* `deftype` is the syntax for an entry in the type section, generalising the existing syntax + - `deftype ::= rec *` + - `module ::= {..., types vec()}` + - a `rec` definition defines a group of mutually recursive types that can refer to each other; it thereby defines several type indices at a time + - a single type definition, as in Wasm before this proposal, is reinterpreted as a short-hand for a recursive group containing just one type + - Note that the number of type section entries is now the number of recursion groups rather than the number of individual types. + +* `subtype` is a new category of type defining a single type, as a subtype of possible other types + - `subtype ::= sub final? * ` + - the preexisting syntax with no `sub` clause is redefined to be a shorthand for a `sub` clause with empty `typeidx` list: ` == sub final () ` + - Note: This allows multiple supertypes. For the MVP, it is restricted to at most one supertype. + +* `comptype` is a new category of types covering the different forms of *composite* types + - `comptype ::= | | ` + +* `structtype` describes a structure with statically indexed fields + - `structtype ::= struct *` + +* `arraytype` describes an array with dynamically indexed fields + - `arraytype ::= array ` + +* `fieldtype` describes a struct or array field and whether it is mutable + - `fieldtype ::= ` + - `storagetype ::= | ` + - `packedtype ::= i8 | i16` + +TODO: Need to be able to use `i31` as a type definition. + + +#### Type Contexts + +Validity of a module is checked under a context storing the definitions for each type. In the case of recursive types, this definition is given by a respective projection from the full type: +``` +ctxtype ::= . +``` + +Both `C.types` and `C.funcs` in typing contexts `C` as defined by the spec now carry `ctxtype`s as opposed to `functype`s like before. +In the case of `C.funcs`, it is an invariant that all types [expand](#auxiliary-definitions) to a function type. + + +#### Auxiliary Definitions + +* Unpacking a storage type yields `i32` for packed types, otherwise the type itself + - `unpacked(t) = t` + - `unpacked(pt) = i32` + +* Unrolling a possibly recursive context type projects the respective item + - `unroll($t) = unroll()` iff `$t = ` + - `unroll((rec *).i) = (*)[i]` + +* Expanding a type definition unrolls it and returns its plain definition + - `expand($t) = expand()` iff `$t = ` + - `expand() = ` + - where `unroll() = sub final? x* ` + +* Finality of a type just checks the flag + - `final($t) = final()` iff `$t = ` + - `final() = final? =/= empty` + - where `unroll() = sub final? x* ` + + +#### External Types + +Unlike in the current spec, external function types need to be represented by a type index/address, in order to preserve the structure and equivalence of iso-recursive types: +``` +externtype ::= func | ... +``` +The type is then looked up and expanded as needed. (This was `func ` before.) + + +#### Type Validity + +Some of the rules define a type as `ok` for a certain index, written `ok(x)`. This controls uses of type indices as supertypes inside a recursive group: the subtype hierarchy must not be cyclic, and hence any type index used for a supertype is required to be smaller than the index `x` of the current type. + +* a sequence of type definitions is valid if each item is valid within the context containing the prior items + - ` * ok` + - iff ` ok` and extends the context accordingly + - and `* ok` under the extended context + +* a group of recursive type definitions is valid if its types are valid under the context containing all of them + - `rec * ok` and extends the context with `*` + - iff `* ok($t)` under the extended context(!) + - where `$t` is the next unused (i.e., current) type index + - and `N = |*|-1` + - and `* = (rec *).0, ..., (rec *).N` + +* a sequence of subtype's is valid if each of them is valid for their respective index + - ` * ok($t)` + - iff ` ok($t)` + - and `* ok($t+1)` + +* an individual subtype is valid if its definition is valid, matches every supertype, and no supertype is final or has an index higher than its own + - `sub final? $t* ok($t')` + - iff ` ok` + - and `( <: expand($t))*` + - and `(not final($t))*` + - and `($t < $t')*` + - Note: the upper bound on the supertype indices ensures that subtyping hierarchies are never circular, because definitions need to be ordered. + +* as [before](https://github.com/WebAssembly/function-references/proposals/function-references/Overview.md#types), a comptype is valid if all the occurring value types are valid + - specifically, a concrete reference type `(ref $t)` is valid when `$t` is defined in the context + +Example: Consider two mutually recursive types: +``` +(rec + (type $t1 (struct (field i32 (ref $t2)))) + (type $t2 (struct (field i64 (ref $t1)))) +) +``` +In the context, these will be recorded as: +``` +$t1 = rect1t2.0 +$t2 = rect1t2.1 + +where + +rect1t2 = (rec + (struct (field i32 (ref $t2))) + (struct (field i64 (ref $t1))) +) +``` +That is, the types are defined as projections from their respective recursion group, using their relative inner indices `0` and `1`. + + +#### Equivalence + +Type equivalence, written `t == t'` here, is essentially defined inductively. All rules are simply the canonical congruences, with the exception of the rule for recursive types. + +For the purpose of defining recursive type equivalence, type indices are extended with a special form that distinguishes regular from recursive type uses. + +* `rec.` is a new form of type index + - `typeidx ::= ... | rec.` + +This form is only used during equivalence checking, to identify and represent "back edges" inside a recursive type. It is merely a technical device for formulating the rules and cannot appear in source code. It is introduced by the following auxiliary meta-function: + +* Rolling a context type produces an _iso-recursive_ representation of its underlying recursion group + - `tie($t) = tie_$t()` iff `$t = ` + - `tie_$t((rec *).i) = (rec *).i[$t':=rec.0, ..., $t'+N:=rec.N]` iff `$t' = $t-i` and `N = |*|-1` + - Note: This definition assumes that all projections of the recursive type are bound to consecutive type indices, so that `$t-i` is the first of them. + - Note: If a type is not recursive, `tie` is just the identity. + +With that: + +* two regular type indices are equivalent if they define equivalent tied context types: + - `$t == $t'` + - iff `tie($t) == tie($t')` + +* two recursive type indices are equivalent if they project the same index + - `rec.i == rec.i'` + - iff `i = i'` + +* two recursive types are equivalent if they are equivalent pointwise + - `(rec *) == (rec *)` + - iff `( == )*` + - Note: This rule is only used on types that have been tied, which prevents looping. + +* notably, two subtypes are equivalent if their structure is equivalent, they have equivalent supertypes, and their finality flag matches + - `(sub final1? $t* ) == (sub final2? $t'* )` + - iff ` == ` + - and `($t == $t')*` + - and `final1? = final2?` + +Example: As explained above, the mutually recursive types +``` +(rec + (type $t1 (struct (field i32 (ref $t2)))) + (type $t2 (struct (field i64 (ref $t1)))) +) +``` +would be recorded in the context as +``` +$t1 = (rec (struct (field i32 (ref $t2))) (struct (field i64 (ref $t1)))).0 +$t2 = (rec (struct (field i32 (ref $t2))) (struct (field i64 (ref $t1)))).1 +``` +Consequently, if there was an equivalent pair of types, +``` +(rec + (type $u1 (struct (field i32 (ref $u2)))) + (type $u2 (struct (field i64 (ref $u1)))) +) +``` +recorded in the context as +``` +$u1 = (rec (struct (field i32 (ref $u2))) (struct (field i64 (ref $u1)))).0 +$u2 = (rec (struct (field i32 (ref $u2))) (struct (field i64 (ref $u1)))).1 +``` +then to check the equivalence `$t1 == $u1`, both types are tied into iso-recursive types first: +``` +tie($t1) = (rec (struct (field i32 (ref rec.1))) (struct (field i64 (ref rec.0)))).0 +tie($u1) = (rec (struct (field i32 (ref rec.1))) (struct (field i64 (ref rec.0)))).0 +``` +In this case, it is immediately apparent that these are equivalent types. + +Note: In type-theoretic terms, these are higher-kinded iso-recursive types: +``` +tie($t1) ~ (mu a. <(struct (field i32 (ref a.1))), (struct i64 (field (ref a.0)))>).0 +tie($t2) ~ (mu a. <(struct (field i32 (ref a.1))), (struct i64 (field (ref a.0)))>).1 +``` +where `<...>` denotes a type tuple. However, in our case, a single syntactic type variable `rec` is enough for all types, because recursive types cannot nest by construction. + +Note 2: This semantics implies that type equivalence checks can be implemented in constant-time by representing all types as trees in tied form and canonicalising them bottom-up in linear time upfront. + +Note 3: It's worth noting that the only observable difference to the rules for a nominal type system is the equivalence rule on (non-recursive) type indices: instead of comparing the definitions of their recursive groups, a nominal system would require `$t = $t'` syntactically (at least as long as we ignore things like checking imports, where type indices become meaningless). +Consequently, using a single big recursion group in this system makes it behave like a nominal system. + + +#### Subtyping + +##### Type Indices + +In the [existing rules](https://github.com/WebAssembly/function-references/proposals/function-references/Overview.md#subtyping), subtyping on type indices required equivalence. Now it can take declared supertypes into account. + +* Type indices are subtypes if they either define [equivalent](#type-equivalence) types or a suitable (direct or indirect) subtype relation has been declared + - `$t <: $t'` + - if `$t = ` and `$t' = ` and ` == ` + - or `unroll($t) = sub final? $t1* $t'' $t2* comptype` and `$t'' <: $t'` + - Note: This rule climbs the supertype hierarchy until an equivalent type has been found. Effectively, this means that subtyping is "nominal" modulo type canonicalisation. + + +##### Heap Types + +In addition to the [existing rules](https://github.com/WebAssembly/function-references/proposals/function-references/Overview.md#subtyping) for heap types, the following are added: + +* every internal type is a subtype of `any` + - `t <: any` + - if `t = any/eq/struct/array/i31` or `t = $t` and `$t = ` or `$t = ` + +* every internal type is a supertype of `none` + - `none <: t` + - if `t <: any` + +* every external type is a subtype of `extern` + - `t <: extern` + - if `t = extern` + - note: there may be other subtypes of `extern` in the future + +* every external type is a supertype of `noextern` + - `noextern <: t` + - if `t <: extern` + +* every function type is a subtype of `func` + - `t <: func` + - if `t = func` or `t = $t` and `$t = ` + +* every function type is a supertype of `nofunc` + - `nofunc <: t` + - if `t <: func` + +* `structref` is a subtype of `eqref` + - `struct <: eq` + - TODO: provide a way to make aggregate types non-eq, especially immutable ones? + +* `arrayref` is a subtype of `eqref` + - `array <: eq` + +* `i31ref` is a subtype of `eqref` + - `i31 <: eq` + +* Any concrete struct type is a subtype of `struct` + - `$t <: struct` + - if `$t = ` + +* Any concrete array type is a subtype of `array` + - `$t <: array` + - if `$t = ` + +* Any concrete function type is a subtype of `func` + - `$t <: func` + - if `$t = ` + +Note: This creates a hierarchy of *abstract* Wasm heap types that looks as follows. +``` + any extern func + | + eq + / | \ +i31 struct array +``` +The hierarchy consists of several disjoint sub hierarchies, each starting from one of the *top* heap types `any`, `extern`, or `func`. + +All *concrete* types (of the form `$t`) are situated below either `struct`, `array`, or `func`. +Not shown in the graph are `none`, `noextern`, and `nofunc`, which are below the other "leaf" types. + +A host environment may introduce additional inhabitants of type `any` +that are are in neither of the above leaf type categories. +The interpretation of such values is defined by the host environment, they are opaque within Wasm code. + +Note: In the future, this hierarchy could be refined, e.g., to distinguish aggregate types that are not subtypes of `eq`. + + +##### Composite Types + +The subtyping rules for composite types are only invoked during validation of a `sub` [type definition](#type-definitions). + +* Function types are covariant on their results and contravariant on their parameters + - `func * -> * <: func * -> *` + - iff `( <: )*` + - and `( <: )*` + +* Structure types support width and depth subtyping + - `struct * * <: struct *` + - iff `( <: )*` + +* Array types support depth subtyping + - `array <: array ` + - iff ` <: ` + +* Field types are covariant if they are immutable, invariant otherwise + - `const <: const ` + - iff ` <: ` + - `var <: var ` + - Note: mutable fields are *not* subtypes of immutable ones, so `const` really means constant, not read-only + +* Storage types inherent subtyping from value types, packed types must be equivalent + - ` <: ` + + +##### Type Definitions + +Subtyping is not defined on type definitions. + + +### Runtime + +#### Runtime Types + +* Runtime types (RTTs) are values representing concrete types at runtime. In the MVP, *canonical* RTTs are implicitly created by all instructions depending on runtime type information. In future versions, RTTs may become explicit values, and non-canonical versions of these instructions will be introduced. + +* An RTT value r1 is *equal* to another RTT value r2 iff they both represent the same static type. + +* An RTT value r1 is a *subtype* of another RTT value r2 iff they represent static types that are in a respective subtype relation. + +Note: RTT values correspond to type descriptors or "shape" objects as they exist in various engines. +RTT equality can be implemented as a single pointer test by memoising RTT values. +More interestingly, runtime casts along the hierarchy encoded in these values can be implemented in an engine efficiently +by using well-known techniques such as including a vector of its (direct and indirect) super-RTTs in each RTT value (with itself as the last entry). +A subtype check between two RTT values can be implemented as follows using such a representation. +Assume RTT value v1 represents static type `$t1` and v2 type `$t2`. +Let `n1` and `n2` be the lengths of the respective supertype vectors. +To check whether v1 denotes a subtype RTT of v2, first verify that `n1 >= n2` -- +if both `n1` and `n2` are known statically, this can be performed at compile time; +if either is not statically known (`$t1` and `n1` are typically unknown during a cast), +it has to be read from the respective RTT value dynamically, and `n1 >= n2` becomes a dynamic check. +Then compare v2 to the n2-th entry in v1's supertype vector. +If they are equal, v1 is a subtype RTT. +In the case of actual casts, the static type of RTT v1 (obtained from the value to cast) is not known at compile time, so `n1` is dynamic as well. +(Note that `$t1` and `$t2` are not relevant for the dynamic semantics, +but merely for validation.) + +Note: This assumes that there is at most one supertype. For hierarchies with multiple supertypes, more complex tests would be necessary. + +Example: Consider three types and corresponding RTTs: +``` +(type $A (sub (struct))) +(type $B (sub $A (struct (field i32)))) +(type $C (sub $B (struct (field i32 i64)))) +``` +Assume the respective RTTs for types `$A`, `$B`, and `$C` are called `$rttA`, `$rttB`, and `$rttC`. +Then, `$rttA` would carry supertype vector `[$rttA]`, `$rttB` has `[$rttA, $rttB]`, and `$rttC` has `[$rttA, $rttB, $rttC]`. + +Now consider a function that casts a `$B` to a `$C`: +``` +(func $castBtoC (param $x (ref $B)) (result (ref $C)) + (ref.cast (ref $C) (local.get $x)) +) +``` +This can compile to machine code that (1) reads the RTT from `$x`, (2) checks that the length of its supertype table is >= 3, and (3) pointer-compares table[2] against `$rttC`. + + +#### Values + +* Reference values of aggregate or function type have an associated runtime type: + - for structures or arrays, it is the RTT value implictly produced upon creation, + - for functions, it is the RTT value for the function's type (which may be recursive). + + +### Instructions + +Note: Instructions not mentioned here remain the same. +In particular, `ref.null` is typed as before, despite the introduction of `none`/`nofunc`/`noextern`. + + +#### Equality + +* `ref.eq` compares two references whose types support equality + - `ref.eq : [eqref eqref] -> [i32]` + + +#### Structures + +* `struct.new ` allocates a structure with canonical [RTT](#values) and initialises its fields with given values + - `struct.new $t : [t'*] -> [(ref $t)]` + - iff `expand($t) = struct (mut t'')*` + - and `(t' = unpacked(t''))*` + - this is a *constant instruction* + +* `struct.new_default ` allocates a structure of type `$t` with canonical [RTT](#values) and initialises its fields with default values + - `struct.new_default $t : [] -> [(ref $t)]` + - iff `expand($t) = struct (mut t')*` + - and all `t'*` are defaultable + - this is a *constant instruction* + +* `struct.get_? ` reads field `i` from a structure + - `struct.get_? $t i : [(ref null $t)] -> [t]` + - iff `expand($t) = struct (mut1 t1)^i (mut ti) (mut2 t2)*` + - and `t = unpacked(ti)` + - and `_` present iff `t =/= ti` + - traps on `null` + +* `struct.set ` writes field `i` of a structure + - `struct.set $t i : [(ref null $t) ti] -> []` + - iff `expand($t) = struct (mut1 t1)^i (var ti) (mut2 t2)*` + - and `t = unpacked(ti)` + - traps on `null` + + +#### Arrays + +* `array.new ` allocates an array with canonical [RTT](#values) + - `array.new $t : [t' i32] -> [(ref $t)]` + - iff `expand($t) = array (mut t'')` + - and `t' = unpacked(t'')` + - this is a *constant instruction* + +* `array.new_default ` allocates an array with canonical [RTT](#values) and initialises its fields with the default value + - `array.new_default $t : [i32] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `t'` is defaultable + - this is a *constant instruction* + +* `array.new_fixed ` allocates an array with canonical [RTT](#values) of fixed size and initialises it from operands + - `array.new_fixed $t N : [t^N] -> [(ref $t)]` + - iff `expand($t) = array (mut t'')` + - and `t' = unpacked(t'')` + - this is a *constant instruction* + +* `array.new_data ` allocates an array with canonical [RTT](#values) and initialises it from a data segment + - `array.new_data $t $d : [i32 i32] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `t'` is numeric, vector, or packed + - and `$d` is a defined data segment + - the 1st operand is the `offset` into the segment + - the 2nd operand is the `size` of the array + - traps if `offset + |t'|*size > len($d)` + - note: for now, this is _not_ a constant instruction, in order to side-step issues of recursion between binary sections; this restriction will be lifted later + +* `array.new_elem ` allocates an array with canonical [RTT](#values) and initialises it from an element segment + - `array.new_elem $t $e : [i32 i32] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `$e : rt` + - and `rt <: t'` + - the 1st operand is the `offset` into the segment + - the 2nd operand is the `size` of the array + - traps if `offset + size > len($e)` + - note: for now, this is _not_ a constant instruction, in order to side-step issues of recursion between binary sections; this restriction will be lifted later + +* `array.get_? ` reads an element from an array + - `array.get_? $t : [(ref null $t) i32] -> [t]` + - iff `expand($t) = array (mut t')` + - and `t = unpacked(t')` + - and `_` present iff `t =/= t'` + - traps on `null` or if the dynamic index is out of bounds + +* `array.set ` writes an element to an array + - `array.set $t : [(ref null $t) i32 t] -> []` + - iff `expand($t) = array (var t')` + - and `t = unpacked(t')` + - traps on `null` or if the dynamic index is out of bounds + +* `array.len` inquires the length of an array + - `array.len : [(ref null array)] -> [i32]` + - traps on `null` + +* `array.fill ` fills a slice of an array with a given value + - `array.fill $t : [(ref null $t) i32 t i32] -> []` + - iff `expand($t) = array (mut t')` + - and `t = unpacked(t')` + - the 1st operand is the `array` to fill + - the 2nd operand is the `offset` into the array at which to begin filling + - the 3rd operand is the `value` with which to fill + - the 4th operand is the `size` of the filled slice + - traps if `array` is null or `offset + size > len(array)` + +* `array.copy ` copies a sequence of elements between two arrays + - `array.copy $t1 $t2 : [(ref null $t1) i32 (ref null $t2) i32 i32] -> []` + - iff `expand($t1) = array (mut t1)` + - and `expand($t2) = array (mut? t2)` + - and `t2 <: t1` + - the 1st operand is the `dest` array that will be copied to + - the 2nd operand is the `dest_offset` at which the copy will begin in `dest` + - the 3rd operand is the `src` array that will be copied from + - the 4th operand is the `src_offset` at which the copy will begin in `src` + - the 5th operand is the `size` of the copy + - traps if `dest` is null or `src` is null + - traps if `dest_offset + size > len(dest)` or `src_offset + size > len(src)` + - note: `dest` and `src` may be the same array and the source and destination + regions may overlap. This must be handled correctly just like it is for + `memory.copy`. + +* `array.init_elem ` copies a sequence of elements from an element segment to an array + - `array.init_elem $t $e : [(ref null $t) i32 i32 i32] -> []` + - iff `expand($t) = array (mut t)` + - and `$e : rt` + - and `rt <: t` + - the 1st operand is the `array` to be initialized + - the 2nd operand is the `dest_offset` at which the copy will begin in `array` + - the 3rd operand is the `src_offset` at which the copy will begin in `$e` + - the 4th operand is the `size` of the copy + - traps if `array` is null + - traps if `dest_offset + size > len(array)` or `src_offset + size > len($e)` + +* `array.init_data ` copies a sequence of values from a data segment to an array + - `array.init_data $t $d : [(ref null $t) i32 i32 i32] -> []` + - iff `expand($t) = array (mut t)` + - and `t` is numeric, vector, or packed + - and `$d` is a defined data segment + - the 1st operand is the `array` to be initialized + - the 2nd operand is the `dest_offset` at which the copy will begin in `array` + - the 3rd operand is the `src_offset` at which the copy will begin in `$d` + - the 4th operand is the `size` of the copy in array slots + - note: The size of the source region is `size * |t|`. If `t` is a packed + type, the source is interpreted as packed in the same way. + - traps if `array` is null + - traps if `dest_offset + size > len(array)` or `src_offset + size * |t| > len($d)` + +#### Unboxed Scalars + +* `ref.i31` creates an `i31ref` from a 32 bit value, truncating high bit + - `ref.i31 : [i32] -> [(ref i31)]` + - this is a *constant instruction* + +* `i31.get_` extracts the value, zero- or sign-extending + - `i31.get_ : [(ref null i31)] -> [i32]` + - traps if the operand is null + + +#### External conversion + +* `any.convert_extern` converts an external value into the internal representation + - `any.convert_extern : [(ref null1? extern)] -> [(ref null2? any)]` + - iff `null1? = null2?` + - this is a *constant instruction* + - note: this succeeds for all values, composing this with `extern.convert_any` (in either order) yields the original value + +* `extern.convert_any` converts an internal value into the external representation + - `extern.convert_any : [(ref null1? any)] -> [(ref null2? extern)]` + - iff `null1? = null2?` + - this is a *constant instruction* + - note: this succeeds for all values; moreover, composing this with `any.convert_extern` (in either order) yields the original value + + +#### Casts + +Casts work for both abstract and concrete types. In the latter case, they test if the operand's RTT is a sub-RTT of the target type. + +* `ref.test ` tests whether a reference has a given type + - `ref.test rt : [rt'] -> [i32]` + - iff `rt <: rt'` + - if `rt` contains `null`, returns 1 for null, otherwise 0 + +* `ref.cast ` tries to convert a reference to a given type + - `ref.cast rt : [rt'] -> [rt]` + - iff `rt <: rt'` + - traps if reference is not of requested type + - if `rt` contains `null`, a null operand is passed through, otherwise traps on null + - equivalent to `(block $l (param trt) (result rt) (br_on_cast $l rt) (unreachable))` + +* `br_on_cast ` branches if a reference has a given type + - `br_on_cast $l rt1 rt2 : [t0* rt1] -> [t0* rt1\rt2]` + - iff `$l : [t0* rt2]` + - and `rt2 <: rt1` + - passes operand along with branch under target type, plus possible extra args + - if `rt2` contains `null`, branches on null, otherwise does not + +* `br_on_cast_fail ` branches if a reference does not have a given type + - `br_on_cast_fail $l rt1 rt2 : [t0* rt1] -> [t0* rt2]` + - iff `$l : [t0* rt1\rt2]` + - and `rt2 <: rt1` + - passes operand along with branch, plus possible extra args + - if `rt2` contains `null`, does not branch on null, otherwise does + +where: + - `(ref null1? ht1)\(ref null ht2) = (ref ht1)` + - `(ref null1? ht1)\(ref ht2) = (ref null1? ht1)` + +Note: The [reference types](https://github.com/WebAssembly/reference-types) and [typed function references](https://github.com/WebAssembly/function-references)already introduce similar `ref.is_null`, `br_on_null`, and `br_on_non_null` instructions. These can now be interpreted as syntactic sugar: + +* `ref.is_null` is equivalent to `ref.test null ht`, where `ht` is the suitable bottom type (`none`, `nofunc`, or `noextern`) + +* `br_on_null` is equivalent to `br_on_cast null ht`, where `ht` is the suitable bottom type, except that it does not forward the null value + +* `br_on_non_null` is equivalent to `(br_on_cast_fail null ht) (drop)`, where `ht` is the suitable bottom type + +* finally, `ref.as_non_null` is equivalent to `ref.cast ht`, where `ht` is the heap type of the operand + + +#### Constant Expressions + +In order to allow RTTs to be initialised as globals, the following extensions are made to the definition of *constant expressions*: + +* `ref.i31` is a constant instruction +* `struct.new` and `struct.new_default` are constant instructions +* `array.new`, `array.new_default`, and `array.new_fixed` are constant instructions + - Note: `array.new_data` and `array.new_elem` are not for the time being, see above +* `any.convert_extern` and `extern.convert_any` are constant instructions +* `global.get` is a constant instruction and can access preceding (immutable) global definitions, not just imports as in the MVP + + +## Binary Format + +### Types + +This extends the [encodings](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#types-1) from the typed function references proposal. + +#### Storage Types + +| Opcode | Type | +| ------ | --------------- | +| -0x08 | `i8` | +| -0x09 | `i16` | + +#### Reference Types + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| -0x0d | `nullfuncref` | | shorthand | +| -0x0e | `nullexternref` | | shorthand | +| -0x0f | `nullref` | | shorthand | +| -0x10 | `funcref` | | shorthand, from reftype proposal | +| -0x11 | `externref` | | shorthand, from reftype proposal | +| -0x12 | `anyref` | | shorthand | +| -0x13 | `eqref` | | shorthand | +| -0x14 | `i31ref` | | shorthand | +| -0x15 | `structref` | | shorthand | +| -0x16 | `arrayref` | | shorthand | +| -0x1c | `(ref ht)` | `ht : heaptype (s33)` | from funcref proposal | +| -0x1d | `(ref null ht)` | `ht : heaptype (s33)` | from funcref proposal | + +#### Heap Types + +The opcode for heap types is encoded as an `s33`. + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| i >= 0 | `(type i)` | | from funcref proposal | +| -0x0d | `nofunc` | | | +| -0x0e | `noextern` | | | +| -0x0f | `none` | | | +| -0x10 | `func` | | from funcref proposal | +| -0x11 | `extern` | | from funcref proposal | +| -0x12 | `any` | | | +| -0x13 | `eq` | | | +| -0x14 | `i31` | | | +| -0x15 | `struct` | | | +| -0x16 | `array` | | | + +#### Composite Types + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| -0x20 | `func t1* t2*` | `t1* : vec(valtype)`, `t2* : vec(valtype)` | from Wasm 1.0 | +| -0x21 | `struct ft*` | `ft* : vec(fieldtype)` | | +| -0x22 | `array ft` | `ft : fieldtype` | | + +#### Subtypes + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| -0x20 | `func t1* t2*` | `t1* : vec(valtype)`, `t2* : vec(valtype)` | shorthand | +| -0x21 | `struct ft*` | `ft* : vec(fieldtype)` | shorthand | +| -0x22 | `array ft` | `ft : fieldtype` | shorthand | +| -0x30 | `sub $t* st` | `$t* : vec(typeidx)`, `st : comptype` | | +| -0x31 | `sub final $t* st` | `$t* : vec(typeidx)`, `st : comptype` | | + +#### Defined Types + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| -0x20 | `func t1* t2*` | `t1* : vec(valtype)`, `t2* : vec(valtype)` | shorthand | +| -0x21 | `struct ft*` | `ft* : vec(fieldtype)` | shorthand | +| -0x22 | `array ft` | `ft : fieldtype` | shorthand | +| -0x30 | `sub $t* st` | `$t* : vec(typeidx)`, `st : comptype` | shorthand | +| -0x31 | `sub final $t* st` | `$t* : vec(typeidx)`, `st : comptype` | shorthand | +| -0x32 | `rec dt*` | `dt* : vec(subtype)` | | + +#### Field Types + +| Type | Parameters | +| --------------- | ---------- | +| `field t mut` | `t : storagetype`, `mut : mutability` | + + +### Instructions + +| Opcode | Type | Parameters | Note | +| ------ | --------------- | ---------- | ---- | +| 0xd0 | `ref.null ht` | `ht : heap_type` | from Wasm 2.0 | +| 0xd1 | `ref.is_null` | | from Wasm 2.0 | +| 0xd2 | `ref.func $f` | `$f : funcidx` | from Wasm 2.0 | +| 0xd3 | `ref.eq` | | +| 0xd4 | `ref.as_non_null` | | from funcref proposal | +| 0xd5 | `br_on_null $l` | `$l : u32` | from funcref proposal | +| 0xd6 | `br_on_non_null $l` | `$l : u32` | from funcref proposal | +| 0xfb00 | `struct.new $t` | `$t : typeidx` | +| 0xfb01 | `struct.new_default $t` | `$t : typeidx` | +| 0xfb02 | `struct.get $t i` | `$t : typeidx`, `i : fieldidx` | +| 0xfb03 | `struct.get_s $t i` | `$t : typeidx`, `i : fieldidx` | +| 0xfb04 | `struct.get_u $t i` | `$t : typeidx`, `i : fieldidx` | +| 0xfb05 | `struct.set $t i` | `$t : typeidx`, `i : fieldidx` | +| 0xfb06 | `array.new $t` | `$t : typeidx` | +| 0xfb07 | `array.new_default $t` | `$t : typeidx` | +| 0xfb08 | `array.new_fixed $t N` | `$t : typeidx`, `N : u32` | +| 0xfb09 | `array.new_data $t $d` | `$t : typeidx`, `$d : dataidx` | +| 0xfb0a | `array.new_elem $t $e` | `$t : typeidx`, `$e : elemidx` | +| 0xfb0b | `array.get $t` | `$t : typeidx` | +| 0xfb0c | `array.get_s $t` | `$t : typeidx` | +| 0xfb0d | `array.get_u $t` | `$t : typeidx` | +| 0xfb0e | `array.set $t` | `$t : typeidx` | +| 0xfb0f | `array.len` | +| 0xfb10 | `array.fill $t` | `$t : typeidx` | +| 0xfb11 | `array.copy $t1 $t2` | `$t1 : typeidx`, `$t2 : typeidx` | +| 0xfb12 | `array.init_data $t $d` | `$t : typeidx`, `$d : dataidx` | +| 0xfb13 | `array.init_elem $t $e` | `$t : typeidx`, `$e : elemidx` | +| 0xfb14 | `ref.test (ref ht)` | `ht : heaptype` | +| 0xfb15 | `ref.test (ref null ht)` | `ht : heaptype` | +| 0xfb16 | `ref.cast (ref ht)` | `ht : heaptype` | +| 0xfb17 | `ref.cast (ref null ht)` | `ht : heaptype` | +| 0xfb18 | `br_on_cast $l (ref null1? ht1) (ref null2? ht2)` | `flags : u8`, `$l : labelidx`, `ht1 : heaptype`, `ht2 : heaptype` | +| 0xfb19 | `br_on_cast_fail $l (ref null1? ht1) (ref null2? ht2)` | `flags : u8`, `$l : labelidx`, `ht1 : heaptype`, `ht2 : heaptype` | +| 0xfb1a | `any.convert_extern` | +| 0xfb1b | `extern.convert_any` | +| 0xfb1c | `ref.i31` | +| 0xfb1d | `i31.get_s` | +| 0xfb1e | `i31.get_u` | + +Flag byte encoding for `br_on_cast(_fail)?`: + +| Bit | Function | +| --- | ------------- | +| 0 | null1 present | +| 1 | null2 present | + + +## JS API + +See [GC JS API document](MVP-JS.md). + +## Questions + +* Enable `i31` as a type definition. + +* Should reference types be generalised to *unions*, e.g., of the form `(ref null? i31? struct? array? func? extern? $t?)`? Perhaps even allowing multiple concrete types? + +* Provide a way to make aggregate types non-eq, especially immutable ones? + + + +## Appendix: Formal Rules for Types + +### Validity + +#### Type Indices (`C |- ok`) + +``` +C(x) = ct +--------- +C |- x ok +``` + +#### Value Types (`C |- ok`) + +``` + +----------- +C |- i32 ok + +C |- x ok +------------- +C |- ref x ok +``` + +...and so on. + + +#### Composite Types (`C |- ok`) +``` +(C |- t1 ok)* +(C |- t2 ok)* +-------------------- +C |- func t1* t2* ok + +(C |- ft ok)* +------------------ +C |- struct ft* ok + +C |- ft ok +---------------- +C |- array ft ok +``` + +#### Sub Types (`C |- * ok(x)`) + +``` +C |- st ok +(C |- st <: expand(C(x)))* +(not final(C(x)))* +(x < x')* +---------------------------- +C |- sub final? x* st ok(x') + +C |- st ok(x) +C |- st'* ok(x+1) +------------------- +C |- st st'* ok(x) +``` + +#### Defined Types (`C |- * -| C'`) + +``` +x = |C| N = |st*|-1 +C' = C,(rec st*).0,...,(rec st*).N +C' |- st* ok(x) +------------------------------------- +C |- rec st* -| C' + +C |- dt -| C' +C' |- dt'* ok +--------------- +C |- dt dt'* ok +``` + +#### Instructions (`C |- : [t1*] -> [t2*]`) + +``` +expand(C(x)) = func t1* t2* +--------------------------------------- +C |- func.call : [t1* (ref x)] -> [t2*] + +expand(C(x)) = struct t1^i t t2* +------------------------------------ +C |- struct.get i : [(ref x)] -> [t] +``` + +...and so on + + +### Type Equivalence + +#### Type Indices (`C |- == `) + +``` +C |- tie(x) == tie(x') +---------------------- +C |- x == x' + + +-------------------- +C |- rec.i == rec.i +``` + +#### Value Types (`C |- == `) + +``` + +--------------- +C |- i32 == i32 + +C |- x == x' +null? = null'? +--------------------------------- +C |- ref null? x == ref null'? x' +``` + +...and so on. + +#### Field Types (`C |- == `) + +``` +C |- t == t' +-------------------- +C |- mut t == mut t' +``` + +#### Composite Types (`C |- == `) + +``` +(C |- t1 == t1')* +(C |- t2 == t2')* +------------------------------------ +C |- func t1* t2* == func t1'* t2'* + +(C |- ft == ft')* +---------------------------- +C |- struct ft* == struct ft'* + +C |- ft == ft' +-------------------------- +C |- array ft == array ft' +``` + +#### Defined Types (`C |- == `) + +``` +(C |- x == x')* +C |- st == st' +final1? = final2? +--------------------------------------------- +C |- sub final1? x* st == sub final2? x'* st' +``` + +### Subtyping + +#### Type Indices (`C |- <: `) + +``` +C |- x == x' +------------ +C |- x <: x' + +unroll(C(x)) = sub final? (x1* x'' x2*) st +C |- x'' <: x' +------------------------------------------ +C |- x <: x' +``` + +#### Value Types (`C |- <: `) + +``` + +--------------- +C |- i32 <: i32 + +C |- x <: x' +null? = epsilon \/ null'? = null +--------------------------------- +C |- ref null? x <: ref null'? x' +``` + +...and so on. + +#### Field Types (`C |- <: `) + +``` +C |- t == t' +-------------------- +C |- mut t <: mut t' +``` + +#### Composite Types (`C |- <: `) + +``` +(C |- t1' <: t1)* +(C |- t2 <: t2')* +----------------------------------- +C |- func t1* t2* <: func t1'* t2'* + +(C |- ft1 <: ft1')* +------------------------------------- +C |- struct ft1* ft2* <: struct ft1'* + +C |- ft <: ft' +-------------------------- +C |- array ft <: array ft' +``` diff --git a/proposals/gc/Overview.md b/proposals/gc/Overview.md new file mode 100644 index 0000000000..210c4e53be --- /dev/null +++ b/proposals/gc/Overview.md @@ -0,0 +1,785 @@ +# GC Extension + +## Introduction + +Note: Basic support for simple [reference types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md), for [typed function references proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md), and for [type imports](https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md) have been carved out into separate proposals which should become the future basis for this proposal. + +See [MVP](MVP.md) for a concrete v1 proposal and [Post-MVP](Post-MVP.md) for possible future features. + +WARNING: Some contents of this document may have gotten out of sync with the [MVP](MVP.md) design, which is more up-to-date. + + +### Motivation + +* Efficient support for high-level languages + - faster execution + - smaller modules + - the vast majority of modern languages need it + +* Provide access to industrial-strength GCs + - at least on the web, VMs already have high performance GCs + +* Non-goal: seamless interoperability between multiple languages + + +### Requirements + +* Allocation of data structures that are garbage collected +* Allocation of byte arrays that are garbage collected +* Allow heap values from the embedder (e.g. JavaScript objects) that are garbage collected +* Unboxing of small scalar values +* Down casts as an escape hatch for the low-level type system +* Explicit low-level control over all runtime behaviour (no implicit allocation, no implicit runtime types) +* Modular (no need for shared type definitions etc.) + + +### Challenges + +* Fast but type-safe +* Lean but sufficiently universal +* Language-independent +* Trade-off triangle between simplicity, expressiveness and performance +* Interaction with threads + + +### Approach + +* Independent from linear memory +* Low-level *data representation types*, not high-level language types or object model +* Basic but general structure: tuples (structs), arrays, unboxed scalars +* Accept minimal amount of dynamic overhead (checked casts) as price for simplicity/universality +* Pay as you go; in particular, no effect on code not using GC, no runtime type information unless requested +* Don't introduce dependencies on GC for other features (e.g., using resources through tables) +* Make runtime type information explicit +* Extend the design iteratively, ship a minimal set of functionality fast + + +### Types + +The sole purpose of the Wasm type system is to describe low-level data layout, in order to aid the engine compiling its access efficiently. It is *not* designed or intended to catch errors in a producer or reflect richer semantic behaviours of a source language's type system, such as distinguishing the types of data structures that have the same layout but are intended to be distinguished in the source language (e.g., different classes). + +This is true for the types in this proposal as well. The introduction of managed data adds new forms of types that describe the layout of memory blocks on the heap, so that the engine knows, for example, the type of a struct being accessed, avoiding any runtime check or dispatch. Likewise, it knows the result type of this access, such that consecutive uses of the result are equally check-free. For that purpose, the type system does little more than describing the *shape* of such data. + + +### Potential Extensions + +* Safe interaction with threads (sharing, atomic access) +* Forming unions of different types, as value types? +* Direct support for strings? +* Defining, allocating, and indexing structures as extensions to imported types? + + +### Efficiency Considerations + +GC support should maintain Wasm's efficiency properties as much as possible, namely: + +* all operations are reliably cheap, ideally constant time +* structures are contiguous, dense chunks of memory +* field accesses are single-indirection loads and stores +* allocation is fast +* no implicit allocation on the heap (e.g. boxing) +* primitive values should not need to be boxed to be stored in managed data structures +* unboxed scalars are interchangeable with references +* allows ahead-of-time compilation and code caching + + +### Evaluation + +Example languages from three categories should be successfully implemented: + +* an object-oriented language with nominal subtyping (e.g., a subset of Java, with classes, inheritance, interfaces) +* a typed functional language (e.g., a subset of ML, with closures, polymorphism, variant types) +* an untyped language (e.g., a subset of Scheme or Python or something else) + + +## Use Cases + +### Structs and Arrays + +* Want to represent first-class tuples/records/structs with static indexing +* Want to represent arrays with dynamic indexing +* Possibly want to create arrays with either fixed or dynamic length + +Examples (fictional language): +``` +type tup = (int, int, bool) +type vec3d = float[3] +type buf = {var pos : int, chars : char[]} + +function f() { + let t : tup = (1, 2, true) + t.1 +} + +function g() { + let v : vec3d = [1, 1, 5] + v[1] +} + +function h() { + let b : nullable buf = {pos = 0, chars = "AAAA"} + b.chars[b.pos] +} +``` + +Needs: + +* user-defined structures and arrays as heap objects +* references to those as first-class values +* locals of these types + +The above could map to +``` +(type $tup (struct i64 i64 i32)) +(type $vec3d (array (mut f64))) +(type $char-array (array (mut i8))) +(type $buf (struct (field $pos (mut i64)) (field $chars (ref $char-array)))) + +(func $f + (local $t (ref $tup)) + (local.set $t + (struct.new $tup (i64.const 1) (i64.const 2) (i64.const 1)) + ) + (struct.get $tup 1 (local.get $t)) + (drop) +) + +(func $g + (local $v (ref $vec3d)) + (local.set $v + (array.new $vec3d (f64.const 1) (i32.const 3)) + ) + (array.set $vec3d (local.get $v) (i32.const 2) (i32.const 5)) + (array.get $vec3d (local.get $v) (i32.const 1)) + (drop) +) + +(func $h + (local $b (ref null $buf)) + (local.set $b + (struct.new $buf + (i64.const 0) + (array.new $char-array (i32.const 0x41) (i32.const 4)) + ) + ) + (array.get $buf + (struct.get $buf $chars (local.get $b)) + (struct.get $buf $pos (local.get $b)) + ) + (drop) +) +``` +These functions `$f` and `$g` code introduces locals which cannot be null, so they must be set before their first get (see the [typed function references proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md)). +In the case of `$b` the local is declared as nullable, however, mapping to an optional reference. +The respective access via `struct.get` may hence trap. + + +### Objects and Method Tables + +* Want to represent objects as structures, whose first field is the method table +* Want to represent method tables themselves as structures, whose fields are function pointers +* Subtyping is relevant, both on instance types and method table types + +Example (Java-ish): +``` +class C { + int a; + void f(int i); + int g(); +} +class D extends C { + double b; + override int g(); + int h(); +} +``` + +``` +(type $f-sig (func (param (ref $C)) (param i32))) ;; first param is `this` +(type $g-sig (func (param (ref $C)) (result i32))) +(type $h-sig (func (param (ref $D)) (result i32))) + +(type $C (struct (ref $C-vt) (mut i32)) +(type $C-vt (struct (ref $f-sig) (ref $g-sig))) ;; all immutable +(type $D (struct (ref $D-vt) (mut i32) (mut f64))) ;; subtype of $C +(type $D-vt (struct (extend $C-vt) (ref $h-sig))) ;; immutable, subtype of $C-vt +``` + +(Note: the use of `extend` in this example and others is assumed to be simple syntactic sugar for expanding the referenced structure type in place; there may be no `extend` construct in the abstract syntax or binary format; subtyping is meant to defined [structurally](#subtyping).) + +Needs: + +* (structural) subtyping +* immutable fields (for sound subtyping) +* universal type of references +* down casts +* dynamic linking might add a whole new dimension + +To emulate the covariance of the `this` parameter, one down cast on `this` is needed in the compilation of each method that overrides a method from a base class. +For example, `D.g`: +``` +(func $D.g (param $Cthis (ref $C)) + (local $this (ref $D)) + (local.set $this + (ref.cast (local.get $Cthis) (rtt.get (ref $D))) + ) + ... +) +``` +The addition of [type fields](Post-MVP.md#type-parameters) may later avoid this cast. + + +### Closures + +* Want to associate a code pointer and its environment in a GC-managed object +* Want to allow compiler of source language to choose appropriate environment representation + +Example: +``` +function outer(x : float) : float -> float { + let a = x + 1.0 + function inner(y : float) { + return y + a + x + } + return inner +} + +function caller() { + return outer(1.0)(2.0) +} +``` + +``` +(type $code-f64-f64 (func (param $env (ref $clos-f64-f64)) (param $y f64) (result f64))) +(type $clos-f64-f64 (struct (field $code (ref $code-f64-f64))) +(type $inner-clos (struct (extend $clos-f64-f64) (field $x f64) (field $a f64)) + +(func $outer (param $x f64) (result (ref $clos-f64-f64)) + (struct.new $inner-clos + (ref.func $inner) ;; code + (local.get $x) ;; x + (f64.add (local.get $x) (f64.const 1)) ;; a + ) ;; (ref $clos-f64-f64) by subtyping +) + +(func $inner (param $clos (ref $clos-f64-f64)) (param $y f64) (result f64) + (local $env (ref $inner-clos)) + (local.set $env + (ref.cast (local.get $clos) (rtt.get (ref $inner-clos))) + ) + (local.get $y) + (struct.get $inner-clos $a (local.get $env)) + (f64.add) + (struct.get $inner-clos $x (local.get $env)) + (f64.add) +) + +(func $caller (result f64) + (local $clos (ref $clos-f64-f64)) + (local.set $clos (call $outer (f64.const 1))) + (call_ref + (local.get $clos) + (f64.const 2) + (struct.get $clos-f64-f64 $code (local.get $clos)) + ) +) +``` + +Needs: +* function pointers +* (mutually) recursive function types +* down casts + +The down cast for the closure environment is necessary to go from the abstract closure type to the concrete. +Statically type checking this would require (first-class) [type fields](Post-MVP.md#type-parameters), a.k.a. existential types. + +Note that this example shows just one way to represent closures (with flattened closure environment). +The proposal provides all necessary primitives allowing high-level language compilers to choose other representations. + +An alternative is to provide [primitive support](#closures) for closures, e.g. a partial application operator. + + +### Parametric Polymorphism + +* Dynamic languages or static languages with sufficiently expressive parametric polymorphism (generics) often require a *uniform representation*, where all its data types are represented in a single word. +* Typically, pointer tagging is used to unbox small scalars. +* Want to be able to represent this with type `anyref`. + +Contrived example (fictional language): +``` +function make_pair(a : A, b : B) : (A, B) { + return (a, b); +} + +class C {...}; +function f() { + ... + make_pair(true, false) + ... + make_pair(new C, new C) + ... +} + +function fst(p : (A, A)) : A { let (a, _) = p; return a } +function snd(p : (A, A)) : A { let (_, a) = p; return a } + +function g(p1 : (Bool, Bool), p2 : (C, C), pick : ((A, A)) -> A) : C { + if (pick(p1)) + return pick(p2); + else + return new C; +} +``` + +Here, `make_pair` as well as `fst` and `snd` need to be able to operate on any type of pair. Furthermore, `fst` and `snd` cannot simply be type-specialised at compile time, because that would be insufficient to compile `g`, which takes a polymorphic function as an argument and instantiates it with multiple different types. Such *first-class* polymorphism is not expressible with compile time techniques such as C++ templates, but common-place in many languages (including OO ones like Java or C#, where it can be emulated via generic methods). Untyped languages like JavaScript or Scheme trivially allow such programs as well. + +The problem is that the compilation of `fst` and `snd` must not depend on the type they are instantiated with because with first-class polymorphism it is not generally possible to tell, at compile time, the set of all such types (static analysis can do that in many cases but not all). +Unless willing to implement runtime code specialisation (like C# / .NET) a type-agnostic compilation scheme is necessary. + +The usual implementation technique is a uniform representation, potentially refined with local unboxing and type specialisation optimisations. + +The MVP proposal does not directly support parametric polymorphism (see the discussion of [type parameters](Post-MVP.md#type-parameters)). +However, compilation with a uniform representation can still be achieved in this proposal by consistently using the type `anyref`, which is the super type of all references, and then down-cast from there: +``` +(type $pair (struct anyref anyref)) + +(func $make_pair (param $a anyref) (param $b anyref) (result (ref $pair)) + (struct.new $pair (local.get $a) (local.get $b)) +) + + +(type $C (struct ...)) + +(func $new_C (result (ref $C)) ...) +(func $f + ... + (call $make_pair (ref.i31 1) (ref.i31 0)) + ... + (call $make_pair (call $new_C) (call $new_C)) + ... +) + +(func $fst (param $p (ref $pair)) (result anyref) + (struct.get $pair 0 (get_local $p)) +) +(func $snd (param $p (ref $pair)) (result anyref) + (struct.get $pair 1 (get_local $p)) +) + +(type $pick (func (param $pair) (result anyref))) +(func $g + (param $p1 (ref $pair)) (param $p2 (ref $pair)) (param $pick (ref $pick)) + (result (ref $C)) + (if (i31.get_u (ref.cast (call_ref $pick (local.get $p1))) (rtt.get i31ref)) + (then (ref.cast (call_ref $pick (local.get $p2)) (rtt.get (ref $C)))) + (else (call $new_C)) + ) +) +``` +Note how type [`i31ref`](#tagged-integers) avoids Boolean values to be heap-allocated. +Also note how a down cast is necessary to recover the original type after a value has been passed through (the compiled form of) a polymorphic function like `g` -- the compiler knows the type but Wasm does not. + +(Future versions of Wasm should support [type parameters](Post-MVP.md#type-parameters) to make such use cases more efficient and avoid the excessive use of runtime types to compile source language polymorphism, but for the GC MVP this provides the necessary expressiveness.) + +Needs: +* `anyref` +* `i31ref` +* down casts + + +## Basic Functionality: Simple Aggregates + +* Extend the Wasm type section with type constructors to express aggregate types +* Extend the value types with new constructors for references (split out into [reference types](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) and [typed function references proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md) proposals) + + +### Structures + +*Structure* types define aggregates with _heterogeneous fields_ that are _statically indexed_: +``` +(type $time (struct (field i32) (field f64))) +(type $point (struct (field $x f64) (field $y f64) (field $z f64))) +``` +Such types can be used by forming [typed reference types](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md), which are a new form of value type. +Fields are *accessed* with generic load/store instructions that take a reference to a structure. +For example: +``` +(func $f (param $p (ref $point)) + (struct.set $point $y (local.get $p) + (struct.get $point $x (local.get $p)) + ) +) +``` +All accesses are type-checked at validation time. +The structure operand of `struct.get/set` may either be a `ref` or a `ref null` for a structure type +In the latter case, the access involves a runtime null check that will trap upon failure. + +Structures are *allocated* with the `struct.new` instruction that accepts initialization values for each field. +The operator yields a reference to the respective type: +``` +(func $g + (call $f (struct.new $point (i32.const 1) (i32.const 2) (i32.const 3))) +) +``` +Structures are *managed* -- i.e., garbage-collected -- so manual deallocation is neither required nor possible. + + +### Arrays + +*Array* types define aggregates with _homogeneous elements_ that are _dynamically indexed_: +``` +(type $vector (array (mut f64))) +(type $matrix (array (mut (ref $vector)))) +``` +Array types are used by forming reference types. +For now, we assume that all array types have a ([flexible](#flexible-aggregates)) length dynamically computed at allocation time. + +Elements are accessed with generic load/store instructions that take a reference to an array: +``` +(func $f (param $v (ref $vector)) + (array.get $vector (local.get $v) (i32.const 1) + (array.set $vector (local.get $v) (i32.const 2)) + ) +) +``` +The element type of every access is checked at validation time. +The array operand of `array.get/set` may either be a `ref` or a `ref null` for an array type +In the latter case, the access involves a runtime null check that will trap upon failure. +The index is checked against the array's length at execution time. +A trap occurs if the index is out of bounds. + +Arrays are *allocated* with the `array.new` instruction that takes an initialization value and a length as operands, yielding a reference: +``` +(func $g + (call $f (array.new $vector (f64.const 3.14) (i32.const 1))) +) +``` + +The *length* of an array, i.e., the number of elements, can be inquired via the `array.len` instruction: +``` +(array.len $vector (local.get $v)) +``` + +Like structures, arrays are garbage-collected. + + +### Packed Fields + +Structure and array fields can have a packed *storage type* `i8` or `i16`: +``` +(type $s (struct (field $a i8) (field $b i16))) +(type $buf (array (mut i8))) +``` +Loads of packed fields require a sign extension mode: +``` +(struct.get_s $s $a (...)) +(struct.get_u $s $a (...)) +(array.get_s $s $a (...)) +(array.get_u $s $a (...)) +``` + + +### Mutability + +Fields can either be immutable or *mutable*: +``` +(type $s (struct (field $a (mut i32)) (field $b i32))) +(type $a (array (mut i32))) +``` +Store operators are only valid when targeting a mutable field or element. +Immutable fields can only be stored to as part of an allocation. + +Immutability needs to be distinguished in order to enable safe and efficient [subtyping](#subtyping), especially as needed for the [objects](#objects-and-method-tables) use case. + + +### Reference Equality + +References can be compared for identity: +``` +(ref.eq (struct.new $point ...) (struct.new $point ...)) ;; false +``` +The `ref.eq` instruction expects two operands of type `eqref`, which is a subtype of `anyref` and the supertype of all reference types that support equality checks. +That includes structure and array references as well as [tagged integers](#tagged-integers), but not function references. + + +### Nullability & Defaultability + +These notions are already introduced by [typed function references](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md) and carry over to the new forms of reference types in this proposal. + +Plain references cannot be null, +avoiding any runtime overhead for null checks when accessing a struct or array. +Nullable references are available as separate types called `ref null`, as per the [typed function references proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md). + +Most value types, including all numeric types and nullable references are *defaultable*, which means that they have 0 or null as a default value. +Other reference types are not defaultable. + +Allocations of aggregates with non-defaultable fields or elements must have initializers. + +Objects whose members all have _mutable_ and _defaultable_ type may be allocated without initializers: +``` +(type $s (struct (field $a (mut i32)) (field (mut (ref $s))))) +(type $a (array (mut f32))) + +(struct.new_default $s) +(array.new_default $a (i32.const 100)) +``` + +TODO (post-MVP): How to create interesting immutable arrays? + + +## Other Reference Types + +### Universal Type + +This type is already introduced by the [reference types proposal](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md). + +The type `anyref` can hold references of any reference type. +It can be formed via [up casts](#casting), +and the original type can be recovered via [down casts](#casting). + + +### Imported Types + +These are available through the [type imports proposal](https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md). + +Types can be exported from and imported into a module: +``` +(type (export "T") (type (struct ...))) +(type (import "env" "T")) +``` + +Imported types are essentially parameters to the module. +If no further constraints are given, they are entirely abstract, as far as compile-time validation is concerned. +The only operations possible with them are those that do not require knowledge of their actual definition or size: primarily, passing and storing references to such types. + +Type imports can also specify constraints that (partially) reveal their definition, such that operations are enables, e.g., field accesses to a struct type. + +Imported types can participate as the source in casts if associated RTTs are imported that enable revealing a subtype. + +Imported types are not per se abstract at runtime. They can participate in casts if associated RTTs are constructed or imported (including implicitly, as in `call_indirect`). + + +### Host Types + +These are enabled by the [type imports proposal](https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md). + +The embedder may define its own set of types (such as DOM objects) or allow the user to create their own types using the embedder API (including a subtype relation between them). +Such *host types* can be [imported](import-and-export) into a module, where they are treated as opaque data types. + +There are no operations to manipulate such types, but a WebAssembly program can receive references to them as parameters or results of imported/exported Wasm functions. Such "foreign" references may point to objects on the _embedder_'s heap. Yet, they can safely be stored in or round-trip through Wasm code. +``` +(type $Foreign (import "env" "Foreign")) +(type $s (struct (field $a i32) (field $x (ref $Foreign))) + +(func (export "f") (param $x (ref $Foreign)) + ... +) +``` + +### Function References + +Function references are already introduced by the [typed function references proposal](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md). + +References can also be formed to function types, thereby introducing the notion of _typed function pointer_. + +Function references can be called through `call_ref` instruction: +``` +(type $t (func (param i32)) + +(func $f (param $x (ref $t)) + (call_ref (i32.const 5) (local.get $x)) +) +``` +Unlike `call_indirect`, this instruction is statically typed and does not involve any runtime check. + +Values of function reference type are formed with the `ref.func` operator: +``` +(func $g (param $x (ref $t)) + (call $f (ref.func $h)) +) + +(func $h (param i32) ...) +``` + +### Unboxed Scalars + +Efficient implementations of untyped languages or languages with [polymorphism](#parametric-polymorphism) often rely on a _uniform representation_, meaning that all values are represented in a single machine word -- usually a pointer. +At the same time, they want to avoid the cost of boxing as much as possible, by passing around small scalar values (such as bools, enums, characters, small integer types) unboxed and using a tagging scheme to distinguish them from pointers in the GC. + +To implement any such language efficiently, Wasm needs to provide such a mechanism. This proposal therefor introduces a built-in reference type `i31ref` that can be implemented in an engine via tagged integers. Producers may use this type to request unboxing for scalars. With this type a producer can convey to an engine that the value range is sufficiently limited that it _only_ needs to handle unboxed values, and no hidden branches or allocations need to be generated to handle overflow into a boxed representation, as would potentially be necessary for larger value ranges. + +There are only three instructions for converting from and to this reference type: +``` +ref.i31 : [i32] -> [i31ref] +i31.get_u : [i31ref] -> [i32] +i31.get_s : [i31ref] -> [i32] +``` +The first is essentially a "tag" instruction, while the other two are two variants of the inverse "untag" operation, either with or without sign extension to 32 bits. + +Being reference types, unboxed scalars can be cast into `anyref`, and can participate in runtime type checks and dispatch with `ref.cast` or `br_on_cast`. + +To avoid portability hazards, the value range of `i31ref` has to be restricted to at most 31 bits, since that is the widest range that can be guaranteed to be efficiently representable on all platforms. + +Note: As a future extension, Wasm could also introduce wider integer references, such as `i32ref`. However, these sometimes will have to be boxed on some platforms, introducing the unpredictable cost of possible "hidden" allocation upon creation or branching upon access. They hence serve a different use case. + + +## Type Structure + +### Type Grammar + +The overall type syntax can be captured in the following grammar: +``` +num_type ::= i32 | i64 | f32 | f64 +ref_type ::= (ref ) +cons_type ::= opt? | i31 | func | eq | any | null +value_type ::= | + +packed_type ::= i8 | i16 +storage_type ::= | +field_type ::= | (mut ) + +data_type ::= (struct *) | (array ) +func_type ::= (func * *) +def_type ::= | +``` +where `value_type` is the type usable for parameters, local variables and the operand stack, and `def_type` describes the types that can be defined in the type section. + +Note that for the MVP, an additional restriction on the above grammar is that array fields must be mutable. + +### Type Recursion + +Through references, aggregate types can be *recursive*: +``` +(type $list (struct (field i32) (field (ref $list)))) +``` +Mutual recursion is possible as well: +``` +(type $tree (struct (field i32) (field (ref $forest)))) +(type $forest (struct (field (ref $tree)) (field (ref $forest)))) +``` + +The [type grammar](#type-grammar) does not make recursion explicit. Semantically, it is assumed that types can be infinite regular trees by expanding all references in the type section, as is standard. +Folding that into a finite representation (such as a graph) is an implementation concern. + + +### Type Equivalence + +In order to avoid type incompatibilities at module boundaries, +all types are structural. +Aggregate types are considered equivalent when the unfoldings of their definitions are (note that field names are not part of the actual types, so are irrelevant): +``` +(type $pt (struct (i32) (i32) (i32))) +(type $vec (struct (i32) (i32) (i32))) ;; vec = pt +``` +This extends to nested and recursive types: +``` +(type $t1 (struct (type $pt) (ptr $t2))) +(type $t2 (struct (type $pt) (ptr $t1))) ;; t2 = t1 +(type $u (struct (type $vec) (ptr $u))) ;; u = t1 = t2 +``` +Note: This is the standard definition of recursive structural equivalence for "equi-recursive" types. +Checking it is computationally equivalent to checking whether two DFAs are equivalent, i.e., it is a non-trivial algorithm (even though most practical cases will be trivial). +This may be a problem, in which case we need to fall back to a more restrictive definition, although it is unclear what exactly that would be. + + +### Subtyping + +Subtyping is designed to be _non-coercive_, i.e., never requires any underlying value conversion. + +The subtyping relation is the reflexive transitive closure of a few basic rules: + +1. The `anyref` type is a supertype of every reference type (top reference type). +2. The `funcref` type is a supertype of every function type. +3. A structure type is a supertype of another structure type if its field list is a prefix of the other (width subtyping). +4. A structure type also is a supertype of another structure type if they have the same fields and for each field type: + - The field is mutable in both types and the storage types are the same. + - The field is immutable in both types and their storage types are in (covariant) subtype relation (depth subtyping). +5. An array type is a supertype of another array type if: + - Both element types are mutable and the storage types are the same. + - Both element types are immutable and their storage types are in +(covariant) subtype relation (depth subtyping). +6. A function type is a supertype of another function type if they have the same number of parameters and results, and: + - For each parameter, the supertype's parameter type is a subtype of the subtype's parameter type (contravariance). + - For each result, the supertype's parameter type is a supertype of the subtype's parameter type (covariance). + +Note: Like [type equivalence](#type-equivalence), (static) subtyping is *structural*. +The above is the standard (co-inductive) definition, which is the most general definition that is sound. +Checking it is computationally equivalent to checking whether one DFA recognises a sublanguage of another DFA, i.e., it is a non-trivial algorithm (even though most practical cases will be trivial). +Like with type equivalence, this may be a problem, in which case a more restrictive definition might be needed. + +Subtyping could be relaxed such that mutable fields and elements could be subtypes of immutable ones. +That would simplify creation of immutable objects, by first creating them as mutable, initialize them, and then cast away their constness. +On the other hand, it means that immutable fields can still change, preventing various access optimizations. +Another alternative would be a three-point mutability lattice with readonly as a top value and mutable and immutable as two incomparable smaller values. + + +### Casting and Runtime Types + +The Wasm type system is intentionally simple. +That implies that it cannot be expressive enough to track all type information that is available in a source program. +To allow producers to work around the inevitable limitations of the type system, down casts have to provided as an "escape hatch". +For example, that allows the use of type `anyref` to represent reference values whose type is not locally known. +When such a value is used in a context where the producer knows its real type, it can use a down cast to recover it. + +For safety, down casts have to be checked at runtime by the engine. Down casts hence need a runtime representation of Wasm types: runtime types (RTT). To avoid hidden cost and make RTTs optional when not needed, all runtime types are explicit operand values (*witnesses*). For example: +``` +(ref.cast () ()) +``` +This instruction checks whether the runtime type stored in `` is a runtime subtype of the runtime type represented by the second operand. + +In order to cast down the type of a struct or array, the aggregate itself must be equipped with a suitable RTT. Attaching runtime type information to aggregates happens at allocation time. +A runtime type is an expression of type `rtt `, which is another form of opaque value type. It represents the static type `` at runtime. +In its plain form, a runtime type is obtained using the instruction `rtt.get` +``` +(rtt.canon ) +``` +For example, this can be used to cast down from `dataref` to a concrete type: +``` +(ref.cast () (rtt.canon )) +``` + +More generally, runtime type checks can verify a subtype relation between runtime types. +In order to make these checks cheap, runtime subtyping follows a *nominal* semantics. To that end, every RTT value may not only represents a given type, it can also record a subtype relation to another (runtime) type (possibly `anyref`) defined when constructing the RTT value: +``` +(rtt.sub ()) +``` +This creates a new witness for `` and defines it to be a subtype of the runtime type expressed by ``. +Validation ensures that `` is a static subtype of the type denoted by ``. Consequently, runtime subtyping is always a subrelation of static subtyping, as required for soundness. + +The above form of cast traps in case of a type mismatch. This form is useful when using casts to work around limitations of the Wasm type system, in cases where the producer knows that it will succeed. + +Another variant of down cast avoids the trap: +``` +(br_on_cast $label () ()) +``` +This branches to `$label` if the check is successful, with the operand as an argument, but using its refined type. Otherwise, the operand remains on the stack. By chaining multiple of these instructions, runtime type analysis ("typecase") can be implemented: +``` +(block $l1 (result (ref $t1)) + (block $l2 (result (ref $t2)) + (block $l3 (result (ref $t3)) + (local.get $operand) ;; has type (ref $t) + (br_on_cast $l1 (ref $t) (ref $t1) (rtt.get (ref $t1))) + (br_on_cast $l2 (ref $t) (ref $t2) (rtt.get (ref $t2))) + (br_on_cast $l3 (ref $t) (ref $t3) (rtt.get (ref $t3))) + ... ;; (ref $t) still on stack here + ) + ... ;; (ref $t3) on stack here + ) + ... ;; (ref $t2) on stack here +) +... ;; (ref $t1) on stack here +``` + +There are a number of reasons to make RTTs explicit: + +* It makes all data and cost (in space and time) involved in casting explicit, which is a desirable property for an "assembly" language. + +* It allows more choice in producers' use of RTT information, including making it optional (post-MVP), in accordance with the pay-as-you-go principle: for example, structs that are not involved in any casts do not need to pay the overhead of carrying runtime type information (depending on specifics of the GC implementation strategy). Some languages may never need to introduce any RTTs at all. + +* Most importantly, making RTTs explicit separates the concerns of casting from Wasm-level polymorphism, i.e., [type parameters](Post-MVP.md#type-parameters). Type parameters can thus be treated as purely a validation artifact with no bearing on runtime. This property, known as parametricity, drastically simplifies the implementation of such type parameterisation and avoids the substantial hidden costs of reified generics that would otherwise hvae to be paid for every single use of type parameters (short of non-trivial cross-procedural dataflow analysis in the engine). + + +## Future Extensions + +In the spirit of an MVP (minimal viable product), the features discussed so far are intentionally limited to a minimum of functionality. +Many [additional extensions](Post-MVP.md) are expected before GC support can be considered "complete". diff --git a/proposals/gc/Post-MVP.md b/proposals/gc/Post-MVP.md new file mode 100644 index 0000000000..6406963493 --- /dev/null +++ b/proposals/gc/Post-MVP.md @@ -0,0 +1,791 @@ +# GC Post-v1 Extensions + +This document discusses various extensions which seem desirable for comprehensive support of GC types, but are intentionally left out of the [MVP](MVP.md), in order to keep its scope manageable. +Over the course of implementing and validating the MVP, it is possible that features in this list may be promoted to the MVP if experience shows that MVP performance would not otherwise be viable. + +See [overview](Overview.md) for addition background. + +* [Bulk operations](#bulk-operations) +* [Arrays with fields](#arrays-with-fields) +* [Readonly field](#readonly-fields) +* [Field references](#field-references) (a.k.a. member pointers) +* [Fixed-size arrays](#fixed-sized-arrays) +* [Nested data structures](#nested-data-structures) (flattening) +* [Explicit RTTs](#explicit-rtts) +* [Type parameters](#type-parameters) (polymorphism, generics) +* [Variants](#variants) (a.k.a. disjoint unions or tagging) +* [Static fields](#static-fields) (meta structures) +* [Closures](#closures) +* [Custom function RTTs](#custom-function-RTTs) +* [Threads and shared references](#threads-and-shared-references) +* [Weak references](#weak-references) +* [Method dispatch](#method-dispatch) +* [Handle nondefaultable fields in `struct.new_default`](#handle-nondefaultable-fields-in-structnew_default) +* [Throwing versions of trapping instructions](#throwing-versions-of-trapping-instructions) + +## Bulk Operations + +In the MVP, aggregate data like structs and arrays can only be accessed one field or element at a time. +More compact code and more efficient code generation might be enabled if they could be copied in one piece. + +To that end, _bulk copying_ instructions could be added, similar to the [bulk instructions for tables and memories](https://github.com/WebAssembly/bulk-memory-operations). + +**Why Post-MVP:** These operators do not provide any additional expressiveness, so are not essential for the MVP. + + +### Sketch + +* An instruction for bulk copying a struct: + - `struct.copy $d : [(ref $d) (ref $s)] -> []` where both `$d` and `$s` are struct types, `$d` has only mutable fields, and `$s <: $d` modulo mutability + +* An instruction for bulk copying an array range: + - `array.copy $d $s : [(ref null $d) i32 (ref null $s) i32 i32] -> []` + - iff `expand($d) = array (var t1)` + - and `expand($s) = array (mut t2)` + - and `t2 <: t1` + - the 1st i32 operand is the `destination` offset in the first array + - the 2nd i32 operand is the `source` offset in the second array + - the 3rd i32 operand is the `length` of the array subrange + - traps if either array is null + - traps if `destination + length > len(array1)` + - traps if `source + length > len(array2)` + +* An instruction for bulk setting an array range: + - `array.fill $t : [(ref null $t) i32 t i32] -> []` + - iff `expand($t) = array (var t')` + - and `t = unpacked(t')` + - the 1st operand is the `offset` in the array + - the 2nd operand is the `length` of the array subrange + - traps if the array is null + - traps if `offset + length > len(array)` + +* An instruction to (re)initialise an array range from a data segment + - `array.init_data $t $d : [(ref null $t) i32 i32 i32] -> []` + - iff `expand($t) = array (var t')` + - and `t'` is numeric or packed numeric + - and `$d` is a defined data segment + - the 1st operand is the `destination` offset in the array + - the 2nd operand is the `source` offset in the segment + - the 3rd operand is the `length` of the array subrange + - traps if the array is null + - traps if `destination + length > len(array)` + - traps if `source + |t'|*length > len($d)` + +* An instruction to (re)initialise an array range from an element segment + - `array.init_elem $t $e : [(ref null $t) i32 i32 i32] -> []` + - iff `expand($t) = array (var t')` + - and `t'` is a reference type + - and `$e` is a defined element segment + - the 1st operand is the `destination` offset in the array + - the 2nd operand is the `source` offset in the segment + - the 3rd operand is the `length` of the array subrange + - traps if the array is null + - traps if `destination + length > len(array)` + - traps if `source + length > len($e)` + + + +## Array with Fields + +One common suggestion is to merge structs and arrays into a single construct with both struct-like fields and a array-like elements. + +However, this is merely a special case of [nested data structures](#nested-data-structures), which some languages will need in a more general form. +So instead of adding an ad-hoc construct for it (which would actually complicate nesting), the idea is to defer to the general mechanism. + +**Why Post-MVP:** For the MVP, all that the lack of arrays with fields entails is the need to represent objects with both fields and elements (e.g., Java arrays) with one extra indirection to the array. That cost seems acceptable for the MVP. + + +## Readonly Fields + +One problem with immutable data structures sometimes is initialisation. +The MVP requires all field values for a struct to be available at allocation time (and lacks a way to construct immutable arrays with individual field values, see [below](#fixed-size-arrays)). + +However, this only allows bottom-up initialisation, which can't handle cases where initialisation is recursive, e.g., because two mutually recursive but immutable structs ought to reference each other. + +In the MVP, such structs need to be defined as mutable and remain so throughout their lifetime. +That prevents depth subtyping to be applied to them (because subtyping mutable fields is unsound). + +In order to prevent mutation after the fact, a third kind of mutability can be added: `readonly`. +Unlike `const`, a `readonly` field or element can still be mutated, but only through another alias where it has `var` type. +At the same time, `readonly` is a supertype of `var` (and also of `const`). + +The upshot is that a struct or array can be allocated with `var` type and be initialised via mutation. +Once done, it can be effectively "frozen" by forgetting the mutable type and upcasting to a reference with a type where fields or elements are `readonly`, preventing any further mutation. + +Note: The notion of `const` in languages like C corresponds to `readonly`, not `const` as currently specified in Wasm. + +**Why Post-MVP:** This is not included in the MVP because it is not entirely clear how important it is in practice. + +### Sketch + +* Introduce a new `` attribute, `readonly`: + - `mutability ::= ... | readonly` + +* Both `var` and `const` are subtypes of `readonly`: + - `var <: readonly` + - `const <: readonly` + +* A `` is a subtype of another `` iff both mutability and storage type are in respective subtype relation: + - ` <: ` + - iff ` <: ` + - and ` <: ` + +### Alternative Design + +Alternatively, one could also extend the MVP with subtyping between `var` and `const` fields, thereby effectively reinterpreting `const` as `readonly`. +True immutability allows more aggressive optimisations, so it might have merits to distinguish it; OTOH it's not clear how much such optimisations matters on the Wasm level, where the producer can apply beforehand in most cases. + + +## Field References + +Some compilation schemes, such as for abstracting over the position or order of fields in an object, require a notion of first-class _field offsets_. +These can be modeled as _field references_ akin to member pointers in C++. + +Such references can also be used to implement *interior pointers* as *fat pointers*. + +**Why Post-MVP:** This is not included in the MVP for the sake of simplicity and because it addresses a more advanced use case. There usually are ways to work around it at some extra cost with extra reference indirections, e.g., two-level object layout. For the MVP, that seems acceptable. + +### Sketch + +* Add a new form of field reference type: + - `reftype ::= ... | fieldref null? ` + - denotes the offset of a field of type `` in the struct defined at `` + - conservatively, a field reference is not a subtype of `anyref` + +* An instruction `ref.field ` that creates a reference to a struct field: + - `ref.field $t i : [] -> [(fieldref $t ft)]` + - iff `$t = (struct ft1^i ft ft2*)` + - this instruction is a constant expression + +* Instructions for accessing fields through field references: + - `struct.get_ref_? : [(ref null? $t) (fieldref null? $t ft)] -> [t]` + - iff `$t = (struct ft1^i ft ft2*)` + - and `ft = (mut? st)` + - and `t = unpacked(st)` + - and `` present iff `st` is a packed type + - `struct.set_ref : [t (ref null? $t) (fieldref null? $t ft)] -> []` + - iff `$t = (struct ft1^i (mut st) ft2*)` + - and `t = unpacked(st)` + - trap if either the value reference or the field reference is null + + +## Fixed-Size Arrays + +The MVP only supports dynamically-sized arrays. +In some scenarios, a static size is sufficient, and may allow for slightly more efficient compilation, e.g., by eliding some bounds checks. + +Fixed-size array types can also be used to support initialisation of immutable arrays: if the size of the array is statically known, it can take the appropriate number of initialisation values from the stack. + +Most importantly, fixed-size array types are a prerequisite for allowing [nested data structures](#nested-data-structures). + +**Why Post-MVP:** This is not included in the MVP for the sake of simplicity and because without [nested data structures](#nested-data-structures), it mostly provides minor performance gains. + + +### Sketch + +* Add a new form of statically-sized array type: + - `arraytype ::= ... | array N ` + +* Such an array type is a subtype of a smaller statically-sized array: + - `array N1 ft1 <: array N2 ft2` + - iff `N1 < N2` + - and `ft1 <: ft2` + +* Such an array type also is a subtype of a dynamically-sized array: + - `array N ft1 <: array ft2` + - iff `ft1 <: ft2` + +* An instruction for allocating a statically-sized array: + - `array.new_static $t : [t^N] -> [(ref $t)]` + - iff `$t = (array N ft)` + - and `ft = (mut? st)` + - and `t = unpacked(st)` + + +## Nested Data Structures + +The MVP only supports "flat" data structures, i.e., structs or arrays whose field types are simple values. +Ultimately, Wasm should support more of the C data model, where structs and arrays can be nested in an unboxed fashion, i.e., flattened into a single heap object. + +With the extension sketched below, it is possible, for example, to represent [arrays with fields](#arrays-with-fields), by nesting a dynamically-sized array at the end of the struct. +For example: +``` +(type $Array (array i32)) +(type $ArrayObject (struct f32 i64 (type $Array)) +``` + +More generally, nested data structures also enables representing "arrays of structs" compactly (and deeper nestings). + +Examples naturally mapping to nested structs are e.g. the value types in C#, where structures can be unboxed members of arrays, or a language like Go. +The sketched data model also has a close correspondance to the [Typed Objects](http://smallcultfollowing.com/babysteps/pubs/2014.04.01-TypedObjects.pdf) that are [proposed](https://github.com/tschneidereit/proposal-typed-objects/blob/master/explainer.md#) for JavaScript. + +Under the sketched extension, such that inner structures can be stored in contiguous heap ranges. That avoids the need to split and _transpose_ representations, i.e., turning an array of structs into a struct of arrays, which would be necessary otherwise. +Such a transformation of the data format destroys composability and memory locality. Access can become much more expensive; for example, copying a struct into or out of an array in this representation is not a single memcpy but requires an arbitrary number of individual reads/writes at distant memory locations. + +For example, consider this source-level pseudo code (C-ish syntax with GC): +``` +struct A { + char x; + int y[30]; + float z; +} + +A aa[20]; + +// Copying inner structs +A aa2[10]; +for (int i = 0..9) { + aa2[i] = aa[i]; // should expect a single bulk copy +} + +// Iterating over an (inner) array +for (int i = 0..19) { + A* a = aa[i]; // should point to a contiguous struct representation + print(a->x); + for (int j = 0..29) { + print(a->y[j]); // should expect contiguous memory access + } +} +``` + +Two main challenges arise: + +* _Interior pointers_ are required to reference inner structures. True inner pointers introduce significant complications to GC that are probably an infeasible requirement to impose on all Wasm engines. This can be avoided by distinguishing interior references from regular ones. That way, engines can choose to represent interior pointers as _fat pointers_ (essentially, a pair of a an object reference and a [field reference](#field-references)) without complicating the GC, and their use is mostly pay-as-you-go. + +* Aggregate objects, especially arrays, can nest arbitrarily. At each nesting level, they may introduce arbitrary mixes of pointer and non-pointer representations that the GC must know about. An efficient solution requires that the GC interprets (an abstraction of) the type structure. More advanced optimisations involve dynamic code generation. + + +**Why Post-MVP:** Due to their obvious added complexity, nested data structures are not included in the MVP. + + +### Sketch + +Nested Types: + +* Aggregate types can be used as field types: + - `fieldtype ::= ... | ` + - valid if the type use denotes a _fixed_ struct or array type, or if it is the last field and denotes a _flexible_ data type (see below) + + For example: + ``` + (type $point (struct (field i32 i32))) + (type $colored-point (struct (field $p (type $point)) (field $col (i16)))) + ``` + Here, `type $point` refers to a previously defined `$point` structure type. + +* A data type is called _flexible_ if it does not have a static size, i.e., either is a dynamically-sized array, or a struct whose last field recursively is a flexible data type. + + Flexible aggregates cannot be used as a (direct flattened) field or element type. + However, it is a common pattern to define structs that end in an array of dynamic length. + To support this, flexible arrays can be allowed for the _last_ field of a structure: + ``` + (type $flex-array (array i32)) + (type $file (struct (field i32) (field (type $flex-array)))) + ``` + This notion of flexibility can be generalized recursively, i.e., the last field of a flexible struct may be a flexible array _or_ a nested flexible struct (this always bottoms out with a flexible array). + + (Note: This notion of "flexible" only considers extension at the end of an object. In principle, it would be possible to introduce a similar notion that allows extension at the beginning of an object, giving rise to a generalised notion of "butterfly object". However, such a generalisation would have substantial repercussions on the implementation strategy of Wasm GC.) + +* A data type is _fixed_ if it is a struct or array that is not flexible. + +* With nesting and flexible aggregates, the type grammar generalizes as follows: + ``` + datatype ::= | + fix_datatype ::= (struct *) | (array N ) + flex_datatype ::= (struct * ) | (array ) + + fix_fieldtype ::= ( ) | + flex_fieldtype ::= + ``` + However, additional constraints apply to (mutually) recursive type definitions in order to ensure well-foundedness of the recursion (a data type cannot contain itself in flat form). + For example, + ``` + (type $t (struct (type $t))) + ``` + is not valid. + For example, well-foundedness can be ensured by requiring that the *nesting depth* of any `datatype`, derivable by the following inductive definition, is finite: + ``` + |( )| = 0 + |(struct *)| = 1 + max{||*} + |(array N? )| = 1 + || + ``` + +Allocation: + +* Allocation instructions like `struct.new` expect initialiser operands for nested aggregates: each individual field for a nested struct, and one initialiser for a nested array (which may itself be a list of initialisers, if the array's element type is again a struct). Details TBD. + +* Like a dynamically-sized array, allocating a flexible struct requires giving a dynamic length operand for its flexible tail array (which is a direct or indirect last field). Details TBD. + + +Interior references: + +* Interior References are another form of reference type that can point to inner aggregates: + - `reftype ::= ... | (inref null? )` + - to allow their implementation as fat pointers, interior references are neither subtypes of regular references nor of `anyref`; however, they can be obtained from regular ones (see below) + + For example: + ``` + (local $ip (inref $point)) + ``` + +* Existing access instructions on structs and arrays are generalied to accept both regular or inner references. For example: + - `struct.set $t i : [([in]ref null? $t) ti] -> []` + - `array.get $t : [([in]ref null? $t) i32] -> [t]` + - etc. + - it is not valid to get or set a struct field or array element that has aggregate type + + +* New instructions to obtain an inner reference: + - `struct.inner $t i : [([in]ref null? $t)] -> [(inref ft)]` + - iff `$t = (struct ft1^i ft ft2*)` + - `array.inner $t : [([in]ref null? $t) i32] -> [(inref ft)]` + - iff `$t = (array N? ft)` + - the source reference may either be a regular or itself interior + - traps if the source reference is null + + For example: + ``` + (struct.get $point $y (struct.inner $colored-point $p ())) + ``` + +* Instructions to obtain an inner reference from a [field reference](#field-references): + - `struct.inner_ref : [([in]ref null? $t) (fieldref null? $t ft)] -> [(inref ft)]` + - iff `$t = (struct ft1^* ft ft2*)` + - and `ft = (type $t')` + - traps if either of the references is null + +* It is not valid to get or set a field or element that has aggregate type. + Writing to a nested structure or array requires combined uses of `struct.inner`/`array.inner` to acquire the interior reference and `struct.set`/`array.set` to its contents: + ``` + (struct.set $color-point $x + (struct.inner $color-point $p (...some $color-point...)) + (f64.const 1.2) + ) + ``` + + An engine should be able to optimise away intermediate interior pointers very easily. + +* TBD: As sketched here, interior references can only point to nested aggregates. Should there also be interior references to plain fields? + + +## Explicit RTTs + +In the MVP, canonical RTTs can be statically created, and they are hence implicit. With extensions like [type parameters](#type-parameters) or type imports, that is no longer the case, and RTTs need to be handled explicitly in order to control when, where, and how they are created and passed. + +* Runtime types are explicit values representing concrete types at runtime; a value of type `rtt ` is a dynamic representative of the static type ``. + +* All RTTs are explicitly created and all operations involving dynamic type information (like casts) operate on explicit RTT operands. + +This will require adding a new form of heap type: + +* `rtt ` is a new heap type that is a runtime representation of the static type `` + - `heaptype ::= ... | rtt ` + - `rtt t ok` iff `t ok` + - the reference type `(rtt $t)` is a shorthand for `(ref (rtt $t))` + +* `rtt $t` is a subtype of `eq` + - `rtt $t <: eq` + - Note: `rtt $t1` is *not* a subtype of `rtt $t2`, unless `$t1` and `$t2` are equivalent; covariant subtyping would be unsound, since RTTs are used in both co- and contravariant roles (e.g., both when constructing and consuming a reference) + +At a baseline, RTT values can be created with a new instruction: +``` +* `rtt.canon ` returns the RTT of the specified type + - `rtt.canon $t : [] -> [(rtt $t)]` + - multiple invocations of this instruction yield the same observable RTTs + - this is a *constant instruction* +``` +With extensions like [type parameters](#type-parameters), this instruction will become more nuanced and will involve additional RTT operands if `$t` has type paramters. + +Correspondingly, all allocation and cast instructions will get counter parts taking an explicit RTT operand. The MVP's canonical versions can be reinterpreted as the combination of the explicit ones whose RTT operand is created with `rtt.canon`: + +* `struct.new ` allocates a structure with RTT information determining its [runtime type](#values) and initialises its fields with given values + - `struct.new $t : [t'* (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = struct (mut t')*` + - this is a *constant instruction* + +* `struct.new_default ` allocates a structure of type `$t` and initialises its fields with default values + - `struct.new_default $t : [(rtt $t)] -> [(ref $t)]` + - iff `expand($t) = struct (mut t')*` + - and all `t'*` are defaultable + - this is a *constant instruction* + +* `array.new ` allocates an array with RTT information determining its [runtime type](#values) + - `array.new $t : [t' i32 (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - this is a *constant instruction* + +* `array.new_default ` allocates an array and initialises its fields with the default value + - `array.new_default $t : [i32 (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `t'` is defaultable + - this is a *constant instruction* + +* `array.new_fixed ` allocates an array of fixed size and initialises it from operands + - `array.new_fixed $t N : [t^N (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - this is a *constant instruction* + +* `array.new_data ` allocates an array and initialises it from a data segment + - `array.new_data $t $d : [i32 i32 (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `t'` is numeric or packed numeric + - and `$d` is a defined data segment + - the 1st operand is the `offset` into the segment + - the 2nd operand is the `size` of the array + - traps if `offset + |t'|*size > len($d)` + +* `array.new_elem ` allocates an array and initialises it from an element segment + - `array.new_elem $t $e : [i32 i32 (rtt $t)] -> [(ref $t)]` + - iff `expand($t) = array (mut t')` + - and `$e : rt` + - and `rt <: t'` + - the 1st operand is the `offset` into the segment + - the 2nd operand is the `size` of the array + - traps if `offset + size > len($e)` + +* `ref.test` tests whether a reference value's [runtime type](#values) is a [runtime subtype](#runtime) of a given RTT + - `ref.test : [t' (rtt $t)] -> [i32]` + - iff `t' <: (ref null data)` or `t' <: (ref null func)` + - returns 1 if the first operand is not null and its runtime type is a sub-RTT of the RTT operand, 0 otherwise + +* `ref.cast` casts a reference value down to a type given by a RTT representation + - `ref.cast : [(ref null1? ht) (rtt $t)] -> [(ref null2? $t)]` + - iff `ht <: data` or `ht <: func` + - and `null1? = null2?` + - returns null if the first operand is null + - traps if the first operand is not null and its runtime type is not a sub-RTT of the RTT operand + +* `br_on_cast ` branches if a value can be cast down to a given reference type + - `br_on_cast $l : [t0* t (rtt $t')] -> [t0* t]` + - iff `$l : [t0* t']` + - and `t <: (ref null data)` or `t <: (ref null func)` + - and `(ref $t') <: t'` + - branches iff the first operand is not null and its runtime type is a sub-RTT of the RTT operand + - passes cast operand along with branch, plus possible extra args + +* `br_on_cast_fail ` branches if a value can not be cast down to a given reference type + - `br_on_cast_fail $l : [t0* t (rtt $t')] -> [t0* (ref $t')]` + - iff `$l : [t0* t']` + - and `t <: (ref null data)` or `t <: (ref null func)` + - and `t <: t'` + - branches iff the first operand is null or its runtime type is not a sub-RTT of the RTT operand + - passes operand along with branch, plus possible extra args + + +## Type Parameters + +The MVP does not support any type parameters for types or functions (also known as _parametric polymorphism_ or _generics_). +The only feasible ways to compile source-level polymorphism to the MVP are: + +1. via type specialisation and code duplication (also known as _monomorphisation_), +2. by using a _uniform representation_ (e.g., `anyref`) for all values passed to polymorphic definitions + +Both methods have severe limitations: + +1. Monomorphisation is only feasible for 2nd-class polymorphism, where use-def relations are statically known. That excludes features like polymorphic methods, polymorphic closures, existential types, GADTs, first-class modules, and other type system features present in many languages. Monomorphisation with such features would require a whole-program analysis and defunctionalisation of polymorphism, which is both complex, more costly at runtime, and giving up on separate compilation and linking. Furthermore, there are features like polymorphic recursion for which even that isn't possible. + + For example, the visitor pattern in object-oriented programming requires an `accept` method in every traversable object. In order to enable traversals with varying result types, a visitor, and hence the corresponding`accept` methods, ought to be generic: + ``` + accept(visitor : Visitor) : T + ``` + In languages limited by monomorphisation, such as C++, this pattern typically cannot be expressed and requires cumbersome workarounds (in C++ terminology, template virtual methods are not allowed). + + To demonstrate polymorphic recursion, here is a (contrived but simple) OCaml example due to @gasche, tweaked to show that such recursion can imply instantiating other functions and concrete data types at a statically unbounded number of types: + ``` + type 'a tree = {lft : 'a; rgt : 'a} + + let sum : 'a . ('a -> int) -> 'a tree -> int = + fun f {lft; rgt} -> f lft + f rgt + + (* a fairly inefficient way to compute 2^n, + by creating a full tree of depth n and counting its leaves *) + let rec loop : 'a . int -> 'a -> ('a -> int) -> int = + fun n v count -> + if n = 0 then count v else + (* call ourselves on values of type ('a tree) *) + loop (n - 1) {lft = v; rgt = v} (fun t -> sum count t) + + let pow2 n = loop n () (fun _ -> 1) + ``` + Due to `tree` being nested to arbitrary depth `n` (which may be an input to the program), it is not possible to monomorphise this code. Instead, a compiler would have to detect this case and fall back to a uniform representation for the different instantiations of the type `tree` in the loop -- and all other code it is passed to, such as `sum` (in the limit, this could be almost all of the program). + +2. A uniform representation cannot be expressed in the MVP without losing all static type information and thereby requiring costly runtime checks at every use site. For example, if `anyref` is used as the uniform type of all values, passing a value through a polymorphic function will require forgetting its static type on the way in (because the function only takes `anyref`) and recovering it with a downcast on the way out (because the function only returns `anyref`). Worse, any composite type, like tuples, arrays, records, or lists, must be implemented with field and element type `anyref` throughout, because their values could not be passed to a function that is polymorphic in their field or element type otherwise. + + For example, consider another piece of code in OCaml, which modifies each element of an array by applying a function `f` to it: + ``` + (* modify : ('a -> 'a) -> 'a array -> unit * + let modify f a = + for i = 0 to Array.length a - 1 do + a.(i) <- f (a.(i)) + done + ``` + Since the function is polymorphic in the element type of the array, the given array has to be represented as an array of universal element type, e.g. `(array (mut anyref))`. + At the same time, it must be compatible with any concrete array type. + For example, passing an array of integers and a suitable function on ints must compose: + ``` + modify ((+) 1) [1; 2; 3] + ``` + The implication is that even the integer array has to be represented using the universal element type, such as `(array (mut anyref))`, and every single read requires a cast. + +To address this shortcoming, it seems necessary to enrich the Wasm type system with a form of type parameters, which allows more accurate tracking of type information for such definitions, avoiding the need to fallback to a universal type. +However, there are a number of challenges: + +1. It is _highly_ desirable to avoid the rabbit hole of _reified generics_, where every type parameter has to be backed by runtime type information. That approach is both highly complex and has a substantial overhead, even where that information isn't needed because the parameterized function does not allocate or cast to the parameter type. Generic containers, for example, would only receive and return references to values of their parameter types and would never have to use those types in casts or allocations. + + Instead, type parameters should adhere to the don't-pay-what-you-don't-use principle, which would be violated if _every_ parameter had to be backed by runtime types. + + That can be achieved by a design where type parameters are a purely static mechanism (a property known as _parametricity_), and all operational behaviour and cost of runtime typing, both in terms of space and time, is made explicit in terms of explicit values and instructions, whose use is optional. + In other words, reification is implemented in user space, by inserting explicit RTT parameters where desired. + The design of RTTs in the MVP has been chosen to make such an approach possible. + +2. Type parameters have to remain compatible with Wasm's existing model for separate, ahead-of-time compilation and linking. Hence it should avoid a dependency on code specialisation. That implies that, like with [type imports](https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md), instantiation has to be restricted (for now) to a set of types that has the same representation in an engine, such as reference types. + + A generalisation to other types would be possible in the future, but would depend on other features like compile-time imports, which are not available yet. + +In general, the semantics and implementation of type parameters should be analogous to that of type imports. Ideally, in the presence of the [module linking proposal](https://github.com/WebAssembly/module-linking), it should even be possible to explain definitions with type parameters as shorthands for nested modules (well, at least for 2nd-class cases). + +### Sketch + +* Allow type parameters on function types: + - `functype ::= (func * * *)` + - `typeparam ::= (typeparam $x ?)` + - like with type imports, the `` describes a bound, contraining instantiation to a subtype; they are likewise instantiated with heap types + +* Allow type parameters on type definitions: + - `typedef ::= (type * )` + - recursive type definitions with parameters must _uniformly recursive_, i.e., not expand to infinitely large types (details TBD) + +* Possibly, also allow type _fields_ in structs, which would technically amount to existential types: + - `datatype ::= ... | (struct * *)` + - `typefield ::= (typefield $x ?)` + - again, the `` describes a bound + - consecutive field types can refer to type fields in the same way as to type parameters + +* Add a way to reference a type parameter as a heap type: + - `heaptype ::= ... | (typeparam $x)` + - a type parameter is a subtype of its bound + +* Add a way to supply type arguments in a type use: + - `typeuse ::= (type $t *)` + - type uses in a heap type must instantiate all parameters + +* Generalise all call instructions (and `func.bind`) with a way to supply type arguments: + - e.g., `call $f *` + +* Generalise the instruction for creating an RTT value with a way to supply type arguments and additional RTT operands for backing them up: + - `rtt.canon (type $t ^n) : [(rtt )^n] -> [(rtt $t ^n)]` + - iff `$t = (type ^n ...)` + - and `^n` matches the bounds of `^n`, respectively + - similarly for `rtt.sub` + + This instruction allows a function with type parameters to construct runtime types that involve those parameters. For example, + ``` + (type $Pair (typeparam $X) + (struct (field (ref (typeparam $X)) (field (ref (typeparam $X))))) + ) + + (func + (typeparam $T) + (param $rttT (rtt (typeparam $T))) + (param $x (ref $T)) + (result (ref $Pair (typeparam $T))) + + ;; allocate a pair with full RTT information + (struct.new_with_rtt + (rtt.canon $Pair (typeparam $T) (local.get $rttT)) + (local.get $x) (local.gt $x) + ) + ) + ``` + + +## Variants + +The MVP supports the most basic form of _pointer tagging_ via the `i31ref` type. +That type allows injecting and distinguishing unboxed integers and pointers in the same type space, such that unboxing can be guaranteed on all platforms. + +However, many language implementations use more elaborate tagging schemes in one form or the other. For example, they want to efficiently distinguish different classes of values without dereferencing, or to store additional information in pointer values without requiring extra space (e.g., Lisp or Prolog often introduce a special tag bit for cons cells). +Other languages provide user-defined tags as an explicit language feature (e.g., _variants_ or _algebraic data types_). + +Unfortunately, hardware differs widely in how many tagging bits a pointer can conveniently support, and different VMs might have additional constraints on how many of these bits they can make available to user code. +In order to provide tagging in a way that is portable but maximally efficient on any given hardware and engine, a somewhat higher level of abstraction is useful. + +Such a higher-level solution would be to support a form of _variant types_ (a.k.a. disjoint unions or sum types) in the type system. +In addition to structs and arrays, a module could define a variant type that is a closed union of multiple different cases. +Dedicated instructions allow allocating and inspecting references of variant type. + +It is left to the engine to pick an efficient representation for the required tags, and depending on the hardware's word size, the number of tags in a defined type, the presence of parameters to a given tag, and other design decisions in the engine, these tags could either be stored as bits in the pointer, in a shared per-type data structure (a.k. hidden class or shape), or in an explicit per-value slot within the heap object. +These decisions can be made by the engine on a per-type basis; validation ensures that all uses are coherent. + +**Why Post-MVP:** Variants allow for more compact representations and potentially more precise types, thereby possibly saving a certain number of runtime checks and downcasts over the use of a common supertype, as necessary in the MVP. However, they are not strictly necessary in the presence of the latter. Nor can they replace it, since they necessarily define a _closed_ set of values, whereas the ability to import an arbitrary number of abstract types requires a way to include an _open_ set of types. To handle that, yet another form of _extensible union types_ (with generative tags) would be required in addition. + + +### Sketch + +* Add a new form of `deftype` for variants: + - `deftype ::= ... | (variant (case $x ?)*)` + - along with [nested-types](#nested-data-structures), the fieldtype can itself be a struct + - references to such a type can be formed as usual + - cases with no `` can typically be represented as an unboxed integer in an engine + +* An instruction for allocating a variant value: + - `variant.new $t i : [t] -> [(ref $t)]` + - iff `$t = (variant ft^i (mut? st) ft*)` + - and `t = unpacked(st)` + +* An instruction for testing a variant value: + - `variant.test $t i : [(ref null? $t)] -> [i32]` + - iff `$t = (variant ft1^i ft ft2*)` + - returns 1 if the value is case `i`, 0 otherwise; traps if the reference is null + +* An instruction for branching on a case: + - `br_on_case $l i : [(ref null? $t)] -> [(ref $t)]` + - iff `$t = (variant ft1^i ft ft2*)` + - and `ft = (mut? st)` and `t = unpacked(st)` and `$l : [t]` + - or `ft = (type $t')` and `$l : [(inref $t')]` + - i.e., if the field type is a scalar, pass its value to the label, otherwise an interior reference + - TBD: the typing rule could synthesise a more precise result type without the tested case; alternatively, this could be replaced with a multi-branch instruction, but that may be cumbersome in many cases + +* TBD: how this integrates with RTTs + + +## Static Fields + +In various object models and value representations, heap values share certain meta information -- for example, the method table in an object, the tag in a union type, or other "static" meta information about a value. + +Since a Wasm engine already has to store its own meta information in heap values, such as GC type descriptor or RTTs, that may double the space usage for meta data in every heap object (e.g., two memory words instead of just one). It would hence be desirable if GC type definitions could piggy-back on the meta object that the engine already has to implement. + +The basic idea would be introducing a notion of _static fields_ in a form of immutable meta object that is shared between multiple instances of the same type. +There are various ways in which this could be modelled, details are TBD. + +If we allow allocating separate instances of the static data for a type, for example by allocating separate RTTs corresponding to the same type with different static data attached to them, then those different RTT values should not be distinguishable via existing cast instructions. In other words, RTTs should continue to precisely represent static types with respect to casting. This will avoid invalidating existing cast optimizations in optimizers and engines. New instructions can be introduced to perform metaobject identity checks with type refinement if necessary. + +**Why Post-MVP:** Such a feature only saves space, so isn't critical for the MVP. Furthermore, there isn't much precedent for exposing such a mechanism to user code in low-level form, so no obvious design philosophy to follow. + + +## Closures + +Function references could be generalised to represent closures by means of an instruction that takes a prefix of the function's arguments and returns a new function reference with those parameters bound. + +* `func.bind` creates or extends a closure by binding several parameters + - `func.bind $t $t' : [t0* (ref null $t)] -> [(ref $t')]` + - iff `unroll($t) = [t0* t1*] -> [t2*]` + - and `unroll($t') = [t1'*] -> [t2'*]` + - and `t1'* <: t1*` + - and `t2* <: t2'*` + - traps on `null` + +With this extension, closures are interchangeable with regular function references. That is, conceptually, all function references would be closures of 0 or more parameters. + +An alternative design would be to distinguish closures from raw functions. In such a design, we would introduce: + +* `closure $t` is a new heap type + - `heaptype ::= ... | closure $t` + - `(closure $t) ok` iff `$t = [t1*] -> [t2*]` + +* `closure $t` also is a new reference type shorthand + - `reftype ::= ... | closure $t` + - shorthand for `(ref (closure $t))` + +There would be two bind instructions, both returning a closure: + +* `closure.new` creates a closure from a function + - `closure.new $t : [(ref null $t)] -> [(ref (closure $t))]` + - iff `unroll($t) = [t1*] -> [t2*]` + - traps on `null` + +* `closure.bind` creates a new closure by binding (additional) parameters of an existing closure + - `closure.bind $t $t' : [t0* (ref null (closure $t))] -> [(ref (closure $t'))]` + - iff `unroll($t) = [t0* t1*] -> [t2*]` + - and `unroll($t') = [t1'*] -> [t2'*]` + - and `t1'* <: t1*` + - and `t2* <: t2'*` + - traps on `null` + +As a variant, `closure.new` could be generalised to `func.bind` like above, but returning a closure. + + +## Custom Function RTTs + +For backwards compatibility, the RTT embedded in a function behaves as if it was created by `rtt.canon`. +It might be useful to customise this semantics and allow programs to pick other RTTs, e.g., ones that have dynamic supertypes. + +To this end, the syntax of function definitions could be extended to include an initialiser expression denoting the desired RTT. +The current form omitting it would be a shorthand for the canonical choice. + + +## Threads and Shared References + +In conjunction with [threads](https://github.com/WebAssembly/threads/blob/master/proposals/threads/Overview.md), GC support ultimately isn't complete until references can also be shared across threads. +For example, this would be necessary to fully implement a JVM with threading using GC types. +In order to support this, the type system must track which references can be *shared* across threads. + +The basic idea for enriching Wasm with shared references has already been laid out in our [OOPSLA'19 paper](https://github.com/WebAssembly/spec/blob/master/papers/oopsla2019.pdf). + +**Why Post-MVP:** Shared references have not been included in the GC MVP, because they will require engines to implement *concurrent garbage collection*. +That requires major changes to most existing Web implementation, that will probably take a long time to implement, let alone optimise. +It seems highly preferable not to gate GC support on that. + +### Sketch + +* Add the *sharability* attribute introduced for memory types by the threads proposal to function, global, and table types. Like with memories, shared definitions are incompatible with non-shared ones. + +* Reference types are extended with a *sharability* attributes as well. That is, the basic form of reference type becomes something like `(ref null? shared? $t)`. + +* Instructions for accessing globals and tables are enriched with sibling *atomic* versions, such as `atomic.global.{get,set}`, `atomic.table.{get,set}`, `atomic.call_indirect`, which have to be used to access shared ones (we may allow non-atomic access as well, but that may be tricky to implement safely on some platforms). + +* Similarly, the accessors in the GC MVP proposal need to be complemented with atomic variants, such as `atomic.struct.{get,set}` etc., and allocation instructions must include shared variants such as `atomic.struct.new`. + +* Validation has to enforce consistency for sharedness, such that only shared definitions and objects must be reachable from shared reference. For example, + + - a value type is *sharable* if it is either numeric or a shared reference; + - a defined type is *sharable* if all its constituent types are sharable; + - a shared global must have a sharable content type; + - a shared table must have a sharable element type; + - a shared function must have sharable parameter and result types; furthermore, it can only access other shared definitions; + - a shared reference can only be formed to a sharable defined type; + - `ref.func` on a shared function produces a shared reference; + - `atomic.struct.new` produces a shared reference, but is only applicable to sharable struct types; + - and so on. + + +## Weak References + +Binding to external libraries sometimes requires the use of *weak references* or *finalizers*. +They also exist in the libraries of various languages in myriads of forms. +Consequently, it would be desirable for Wasm to support them. + +The main challenge is the large variety of different semantics that existing languages provide. +Clearly, Wasm cannot build in all of them, so we need to be looking for a mechanism that can emulate most of them with acceptable performance loss. + +**Why Post-MVP:** Unfortunately, it is not clear at this point what a sufficiently simple and efficient set of primitives for weak references and finalisation could be. This requires more investigation, and should not block basic GC functionality. + + +## Method and Closure Typing + +Right now OO-style method dispatch requires downcasting the receiver parameter from the base class receiver type in the implementation of overriding methods. As of May 2022, unsafely removing this receiver downcast improved performance by 3-4% across a suite of real-world j2wasm workloads. A closely related problem exists for client-side encodings of closures. + +The problem could be addressed by extending the type system with features that allow typing the receiver/environment parameter more precisely, for which a number of solutions are known (e.g., [1](https://dl.acm.org/doi/pdf/10.1145/354222.353192), [2](http://lucacardelli.name/Papers/ObjectEncodings.pdf)). A fallback solution could be the introduction of a primitive method and dispatch mechanism besides functions, e.g., as an extension to the static fields mechanism. + +**Why Post-MVP:** Methods and closures can be easily expressed without being built into WebAssembly, so this would be a fair amount of extra complexity for a modest performance improvement and no additional benefits. Considering this as an optimimzation after shipping the MVP makes the most sense. + +## Handle nondefaultable fields in `struct.new_default` + +As proposed in #174. The idea is that `struct.new_default` would take values for nondefaultable fields in the struct from the stack. This would be a code size optimization over having to specify every single field with `struct.new` when allocating a struct containing nondefaultable fields. + +**Why Post-MVP:** This is a local code size optimization that does not affect expressivity, so getting it into the MVP is not urgent. + + +## Throwing versions of trapping instructions + +Some language implementers have expressed interest in having throwing versions of operations like `struct.get` to simplify the implementation of e.g. Java's `NullPointerException`. It may be worth investigating what the potential code size or performance benefits of these instructions would be. See [#208](https://github.com/WebAssembly/gc/issues/208) for details and previous discussion. + +**Why Post-MVP:** Null checks followed by throws are easily expressible in the MVP, so there is no pressing need to add additional instructions for this pattern. Also, throwing instructions would depend on the exception handling proposal, and we would prefer to avoid that dependency in the MVP. + + +## Equality-comparable funcref + +Being able to compare function references for equality would enable more precise polymorphic devirtualization. On the other hand, some polymorphic devirtualization should already be possible by taking advantage of patterns in vtables and letting functions be equality comparable would inhibit function deduplication optimizations. See [#239](https://github.com/WebAssembly/gc/issues/239) for details and previous discussion. + +**Why Post-MVP:** The benefits of this change are unknown and would likely be small, so it's not urgent that we get this into the MVP. + diff --git a/proposals/multi-memory/Overview.md b/proposals/multi-memory/Overview.md new file mode 100644 index 0000000000..abf7c12bcd --- /dev/null +++ b/proposals/multi-memory/Overview.md @@ -0,0 +1,99 @@ +# Multiple Memories for Wasm + +## Summary + +This proposal adds the ability to use multiple memories within a single Wasm module. +In the current version of Wasm, an application can already create multiple memories, but only by splitting things up into multiple modules. +A single module or function cannot refer to multiple memories at the same time. +Consequently, it is not possible to e.g. efficiently transfer data from one memory to another, since that necessarily involves an individual function call into a different module per value. + +## Motivation + +There are a number of use case scenarios for using multiple memories in a single application: + +* *Security.* A module may want to separate public memory that is shared with the outside to exchange data, from private memory that is kept encapsulated inside the module. + +* *Isolation.* Even internal to a single module it is beneficial to have both and be able separate memory that is shared between multiple threads from memory used in a single-threaded manner. + +* *Persistence.* An application may want to keep some of its memory state persistent between runs, e.g., by storing it in a file. But it may not want to do that for all its memory, so separating lifetimes via multiple memories is a natural setup. + +* *Linking.* There are a number of tools out there that can merge multiple Wasm modules into one, as a form of static linking. This is possible in almost all cases, except when the set of modules defines more than one memory. Allowing multiple memories in a single module closes this unfortunate gap. + +* *Scaling.* As long as Wasm memories are limited to 32 bit address space, there is no way to scale out of 4 GB memory efficiently. Multiple memories at least provide an efficient workaround until 64 bit memories become available (which may still take a while). + +* *Polyfilling.* Some proposals, e.g., [garbage collection](https://github.com/WebAssembly/gc) or [interface types](https://github.com/WebAssembly/interface-types) could be emulated in current Wasm if they had the ability to add an auxiliary memory that is distinct from the module's own address space. + + +## Overview + +The original Wasm design already anticipated the ability to define and reference multiple memories. In particular, there already is the notion of an index space for memories (which currently can contain at most one entry), and most memory constructs already leave space in the binary encoding for it. +This proposal fills in the holes accordingly. + +This generalisation is fully symmetric to the extension to multiple tables in the [reference types](https://github.com/WebAssembly/reference-types) proposal. + +The design of this extension is almost entirely canonical. Concretely: + +* Allow multiple memory imports and definitions in a single module. + +* Add a memory index to all memory-related instructions. + - Loads and stores have the memop field, in which we can allocate a bit indicating a memory index immediate in the binary format. + - All other memory instruction already have a memory index immediate (which currently has to be 0). + +* Data segments and exports already have a memory index as well. + +* Extend the validation and execution semantics in the obvious manner. + + +### Instructions + +Except where noted, the following changes apply to each `load` and `store` instruction as well as to `memory.size` and `memory.grow`. Corresponding changes will be necessary for the new instructions introduced by the bulk memory proposal.instructions. + +Abstract syntax: + +* Add a memory index immediate. + +Validation: + +* Check the memory index immediate instead of index 0. + +Execution: + +* Access the memory according to their index immediate instead of memory 0. + +Binary format: + +* For loads and stores: Reinterpret the alignment value in the `memarg` as a bitfield; if bit 6 (the MSB of the first LEB byte) is set, then an `i32` memory index follows after the alignment bitfield (even with SIMD, alignment must not currently be larger than 4 in the logarithmic encoding, i.e., taking up the lower 3 bits, so this is more than safe). + +* For copy, replace the two hard-coded `0x00` bytes with two `i32` memory indexes, denoting the destination and source memory, respectively. + +* For other memory instructions: Replace the hard-coded `0x00` bytes with an `i32` memory index. + +Text format: + +* Add an optional `memidx` immediate that defaults to 0 if absent (for backwards compatibility). In the case of loads and stores, this index must occur before the optional alignment and offset immediates. + +* In the case of copy, two optional `memidx` immediates are added, denoting the destination and source memory, respectively. These optional indexes must both be present to specify a source and destintaion memory, or both be absent to default to 0. + + +### Modules + +#### Data Segments and Memory Exports + +The abstract syntax for data segments and memory exports already carry a memory index that is checked and used accordingly (even though it currently is known to be 0). Binary and text format also already have the corresponding index parameter (which is optional and defaults to 0 in the case of data segments). + +So technically, no spec change is necessary, though implementations now need to deal with the possibility that the index is not 0. + + +#### Modules + +* Validation for modules no longer checks that there is at most 1 memory defined or imported. + + +## Implementation + +Engines already have to deal with multiple memories, but any given code so far can only address one. +Hence, in current engines, reserving one register for the base address is a common technique. +Multiple memories will typically require an extra indirection (which some engines already have). + +Engines could conservatively continue to optimise access to memory index 0 via a dedicated register. +Presumably, we could also eventually add a future custom section with optimisation hints, which could e.g. mark the index of the "main" memory. diff --git a/proposals/tail-call/Overview.md b/proposals/tail-call/Overview.md new file mode 100644 index 0000000000..b422654f04 --- /dev/null +++ b/proposals/tail-call/Overview.md @@ -0,0 +1,145 @@ +# Tail Call Extension + +## Introduction + +### Motivation + +* Currently, the Wasm design explicitly forbids tail call optimisations + +* Want support to enable + - the correct and efficient implementations of languages that require tail call elimination + - the compilation of control constructs that can be implemented with it (e.g., forms of coroutines, continuations) + - compilation and optimization techniques that require it (e.g., dynamic recompilation, tracing, CPS) + - other sorts of computation being expressed as Wasm functions, e.g., FSMs + + +### Semantics + +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: + +* Caller and callee can differ +* Caller and callee type can differ +* Callee may be dynamic (e.g., `call_indirect`) + + +## Design Space + +### Instructions + +* 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 + - WebAssembly will likely get a few more call instructions in the future, e.g., `call_ref` + - otoh, instruction prefixes as modifiers are not used anywhere else in Wasm + + +### Execution + +* Tail 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 + +* 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: + 1. Distinguish tail-callees by type + 2. Distinguish tail-callers by type + 3. Both + 4. Neither + +* Considerations: + - Option 1 (and 3) allows different calling conventions for non-tail-callable functions, which may reduce constraints on ABIs. + - On the other hand, it creates a bifurcated function space, which can lead to difficulties e.g. when using function tables or other forms of dynamic indirection. + - Benefit of option 2 (and thus 3) unclear. + - Experimental validation revealed that there isn't a notable performance benefit to option 1 either. + +* CG resolution was to go with option 4 as the conceptually simplest. + + +## Examples + +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)) + ) + ) + ) +) + +``` + + +## Spec Changes + +### Structure + +Add two instructions: + +* `return_call `, the tail-call version of `call` +* `return_call_indirect `, the tail-call version of `call_indirect` + +Other language extensions like [typed function references](https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md) that introduce new call instructions will also introduce tail versions of these new instructions. + + +### Validation + +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 + +Execution semantics of the new instructions would + +1. pop the call operands +2. clear and pop the topmost stack frame in the same way `return` does +3. push back the operands +4. delegate to the semantics of the respective plain call instructions + + +### Binary Format + +Use the reserved opcodes after existing call instructions, i.e.: + +* `return_call` is 0x12 +* `return_call_indirect` is 0x13 + + +### Text Format + +The text format is extended with two new instructions in the obvious manner. diff --git a/test/core/align.wast b/test/core/align.wast index 7753df8deb..817a1dfb24 100644 --- a/test/core/align.wast +++ b/test/core/align.wast @@ -903,7 +903,7 @@ "\1a" ;; drop "\0b" ;; end ) - "malformed memop flags" + "malformed memop alignment" ) ;; 32-bit out of range @@ -922,7 +922,7 @@ "\1a" ;; drop "\0b" ;; end ) - "malformed memop flags" + "malformed memop alignment" ) ;; Signed 64-bit overflow @@ -941,11 +941,11 @@ "\1a" ;; drop "\0b" ;; end ) - "malformed memop flags" + "malformed memop alignment" ) ;; Unsigned 64-bit overflow -(assert_malformed +(assert_invalid (module binary "\00asm" "\01\00\00\00" "\01\04\01\60\00\00" ;; Type section: 1 type @@ -956,15 +956,15 @@ ;; function 0 "\08\00" "\41\00" ;; i32.const 0 - "\28\40\00" ;; i32.load offset=0 align=2**64 + "\28\40\00" ;; i32.load offset=0 align=2**64 (parsed as align=0, memidx present) "\1a" ;; drop "\0b" ;; end ) - "malformed memop flags" + "type mismatch" ) ;; 64-bit out of range -(assert_malformed +(assert_invalid (module binary "\00asm" "\01\00\00\00" "\01\04\01\60\00\00" ;; Type section: 1 type @@ -975,9 +975,9 @@ ;; function 0 "\08\00" "\41\00" ;; i32.const 0 - "\28\41\00" ;; i32.load offset=0 align=2**65 + "\28\41\00" ;; i32.load offset=0 align=2**65 (parsed as align=1, memidx present) "\1a" ;; drop "\0b" ;; end ) - "malformed memop flags" + "type mismatch" ) diff --git a/test/core/binary.wast b/test/core/binary.wast index fb2c6c4dcc..e0b8571801 100644 --- a/test/core/binary.wast +++ b/test/core/binary.wast @@ -852,197 +852,6 @@ "integer too large" ) -;; memory.grow reserved byte equal to zero. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\09\01" ;; Code section - - ;; function 0 - "\07\00" - "\41\00" ;; i32.const 0 - "\40" ;; memory.grow - "\01" ;; memory.grow reserved byte is not equal to zero! - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -;; memory.grow reserved byte should not be a "long" LEB128 zero. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0a\01" ;; Code section - - ;; function 0 - "\08\00" - "\41\00" ;; i32.const 0 - "\40" ;; memory.grow - "\80\00" ;; memory.grow reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -;; Same as above for 3, 4, and 5-byte zero encodings. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0b\01" ;; Code section - - ;; function 0 - "\09\00" - "\41\00" ;; i32.const 0 - "\40" ;; memory.grow - "\80\80\00" ;; memory.grow reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0c\01" ;; Code section - - ;; function 0 - "\0a\00" - "\41\00" ;; i32.const 0 - "\40" ;; memory.grow - "\80\80\80\00" ;; memory.grow reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0d\01" ;; Code section - - ;; function 0 - "\0b\00" - "\41\00" ;; i32.const 0 - "\40" ;; memory.grow - "\80\80\80\80\00" ;; memory.grow reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -;; memory.size reserved byte equal to zero. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\07\01" ;; Code section - - ;; function 0 - "\05\00" - "\3f" ;; memory.size - "\01" ;; memory.size reserved byte is not equal to zero! - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -;; memory.size reserved byte should not be a "long" LEB128 zero. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\08\01" ;; Code section - - ;; function 0 - "\06\00" - "\3f" ;; memory.size - "\80\00" ;; memory.size reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -;; Same as above for 3, 4, and 5-byte zero encodings. -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\09\01" ;; Code section - - ;; function 0 - "\07\00" - "\3f" ;; memory.size - "\80\80\00" ;; memory.size reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0a\01" ;; Code section - - ;; function 0 - "\08\00" - "\3f" ;; memory.size - "\80\80\80\00" ;; memory.size reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - -(assert_malformed - (module binary - "\00asm" "\01\00\00\00" - "\01\04\01\60\00\00" ;; Type section - "\03\02\01\00" ;; Function section - "\05\03\01\00\00" ;; Memory section - "\0a\0b\01" ;; Code section - - ;; function 0 - "\09\00" - "\3f" ;; memory.size - "\80\80\80\80\00" ;; memory.size reserved byte - "\1a" ;; drop - "\0b" ;; end - ) - "zero byte expected" -) - ;; Local number is unsigned 32 bit (assert_malformed (module binary @@ -1843,7 +1652,7 @@ "\0b" ;; end, interpreted as type 11 for the block "\0b\0b" ;; end ) - "unexpected end" + "unexpected end of section or function" ) ;; Start section diff --git a/test/core/br_if.wast b/test/core/br_if.wast index fba5deb9b3..9d0cdd81fa 100644 --- a/test/core/br_if.wast +++ b/test/core/br_if.wast @@ -663,3 +663,17 @@ "unknown label" ) +;; https://github.com/WebAssembly/gc/issues/516 +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t)) (result funcref) (local.get 0)) + (func (result funcref) + (ref.null $t) + (i32.const 0) + (br_if 0) ;; only leaves funcref on the stack + (call $f) + ) + ) + "type mismatch" +) diff --git a/test/core/br_on_non_null.wast b/test/core/br_on_non_null.wast new file mode 100644 index 0000000000..43800194fe --- /dev/null +++ b/test/core/br_on_non_null.wast @@ -0,0 +1,90 @@ +(module + (type $t (func (result i32))) + + (func $nn (param $r (ref $t)) (result i32) + (call_ref $t + (block $l (result (ref $t)) + (br_on_non_null $l (local.get $r)) + (return (i32.const -1)) + ) + ) + ) + (func $n (param $r (ref null $t)) (result i32) + (call_ref $t + (block $l (result (ref $t)) + (br_on_non_null $l (local.get $r)) + (return (i32.const -1)) + ) + ) + ) + + (elem func $f) + (func $f (result i32) (i32.const 7)) + + (func (export "nullable-null") (result i32) (call $n (ref.null $t))) + (func (export "nonnullable-f") (result i32) (call $nn (ref.func $f))) + (func (export "nullable-f") (result i32) (call $n (ref.func $f))) + + (func (export "unreachable") (result i32) + (block $l (result (ref $t)) + (br_on_non_null $l (unreachable)) + (return (i32.const -1)) + ) + (call_ref $t) + ) +) + +(assert_trap (invoke "unreachable") "unreachable") + +(assert_return (invoke "nullable-null") (i32.const -1)) +(assert_return (invoke "nonnullable-f") (i32.const 7)) +(assert_return (invoke "nullable-f") (i32.const 7)) + +(module + (type $t (func)) + (func (param $r (ref null $t)) (drop (block (result (ref $t)) (br_on_non_null 0 (local.get $r)) (unreachable)))) + (func (param $r (ref null func)) (drop (block (result (ref func)) (br_on_non_null 0 (local.get $r)) (unreachable)))) + (func (param $r (ref null extern)) (drop (block (result (ref extern)) (br_on_non_null 0 (local.get $r)) (unreachable)))) +) + + +(module + (type $t (func (param i32) (result i32))) + (elem func $f) + (func $f (param i32) (result i32) (i32.mul (local.get 0) (local.get 0))) + + (func $a (param $n i32) (param $r (ref null $t)) (result i32) + (call_ref $t + (block $l (result i32 (ref $t)) + (return (br_on_non_null $l (local.get $n) (local.get $r))) + ) + ) + ) + + (func (export "args-null") (param $n i32) (result i32) + (call $a (local.get $n) (ref.null $t)) + ) + (func (export "args-f") (param $n i32) (result i32) + (call $a (local.get $n) (ref.func $f)) + ) +) + +(assert_return (invoke "args-null" (i32.const 3)) (i32.const 3)) +(assert_return (invoke "args-f" (i32.const 3)) (i32.const 9)) + + +;; https://github.com/WebAssembly/gc/issues/516 +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t)) (result funcref) (local.get 0)) + (func (param funcref) (result funcref funcref) + (ref.null $t) + (local.get 0) + (br_on_non_null 0) ;; only leaves a funcref on the stack + (call $f) + (local.get 0) + ) + ) + "type mismatch" +) diff --git a/test/core/br_on_null.wast b/test/core/br_on_null.wast new file mode 100644 index 0000000000..e47dae50a3 --- /dev/null +++ b/test/core/br_on_null.wast @@ -0,0 +1,94 @@ +(module + (type $t (func (result i32))) + + (func $nn (param $r (ref $t)) (result i32) + (block $l + (return (call_ref $t (br_on_null $l (local.get $r)))) + ) + (i32.const -1) + ) + (func $n (param $r (ref null $t)) (result i32) + (block $l + (return (call_ref $t (br_on_null $l (local.get $r)))) + ) + (i32.const -1) + ) + + (elem func $f) + (func $f (result i32) (i32.const 7)) + + (func (export "nullable-null") (result i32) (call $n (ref.null $t))) + (func (export "nonnullable-f") (result i32) (call $nn (ref.func $f))) + (func (export "nullable-f") (result i32) (call $n (ref.func $f))) + + (func (export "unreachable") (result i32) + (block $l + (return (call_ref $t (br_on_null $l (unreachable)))) + ) + (i32.const -1) + ) +) + +(assert_trap (invoke "unreachable") "unreachable") + +(assert_return (invoke "nullable-null") (i32.const -1)) +(assert_return (invoke "nonnullable-f") (i32.const 7)) +(assert_return (invoke "nullable-f") (i32.const 7)) + +(module + (type $t (func)) + (func (param $r (ref null $t)) (drop (br_on_null 0 (local.get $r)))) + (func (param $r (ref null func)) (drop (br_on_null 0 (local.get $r)))) + (func (param $r (ref null extern)) (drop (br_on_null 0 (local.get $r)))) +) + + +(module + (type $t (func (param i32) (result i32))) + (elem func $f) + (func $f (param i32) (result i32) (i32.mul (local.get 0) (local.get 0))) + + (func $a (param $n i32) (param $r (ref null $t)) (result i32) + (block $l (result i32) + (return (call_ref $t (br_on_null $l (local.get $n) (local.get $r)))) + ) + ) + + (func (export "args-null") (param $n i32) (result i32) + (call $a (local.get $n) (ref.null $t)) + ) + (func (export "args-f") (param $n i32) (result i32) + (call $a (local.get $n) (ref.func $f)) + ) +) + +(assert_return (invoke "args-null" (i32.const 3)) (i32.const 3)) +(assert_return (invoke "args-f" (i32.const 3)) (i32.const 9)) + + +;; https://github.com/WebAssembly/gc/issues/516 +;; Tests that validators are correctly doing +;; +;; pop_operands(label_types) +;; push_operands(label_types) +;; +;; for validating br_on_null, rather than incorrectly doing either +;; +;; push_operands(pop_operands(label_types)) +;; +;; or just inspecting the types on the stack without any pushing or +;; popping, neither of which handle subtyping correctly. +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t)) (result funcref) (local.get 0)) + (func (param funcref) (result funcref) + (ref.null $t) + (local.get 0) + (br_on_null 0) ;; only leaves two funcref's on the stack + (drop) + (call $f) + ) + ) + "type mismatch" +) diff --git a/test/core/br_table.wast b/test/core/br_table.wast index 3fc533d56e..2a5197ce14 100644 --- a/test/core/br_table.wast +++ b/test/core/br_table.wast @@ -1002,6 +1002,67 @@ ) ) + (func (export "meet-bottom") + (block (result f64) + (block (result f32) + (unreachable) + (br_table 0 1 1 (i32.const 1)) + ) + (drop) + (f64.const 0) + ) + (drop) + ) + + (type $t (func)) + (func $tf) + (table $t (ref null $t) (elem $tf)) + (func (export "meet-funcref-1") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (br_table $l1 $l1 $l2 (table.get $t (i32.const 0)) (local.get 0)) + ) + ) + ) + (func (export "meet-funcref-2") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (br_table $l2 $l2 $l1 (table.get $t (i32.const 0)) (local.get 0)) + ) + ) + ) + (func (export "meet-funcref-3") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (br_table $l2 $l1 $l2 (table.get $t (i32.const 0)) (local.get 0)) + ) + ) + ) + (func (export "meet-funcref-4") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (br_table $l1 $l2 $l1 (table.get $t (i32.const 0)) (local.get 0)) + ) + ) + ) + + (func (export "meet-nullref") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (br_table $l1 $l2 $l1 (ref.null $t) (local.get 0)) + ) + ) + ) + + (func (export "meet-multi-ref") (param i32) (result (ref null func)) + (block $l1 (result (ref null func)) + (block $l2 (result (ref null $t)) + (block $l3 (result (ref $t)) + (br_table $l3 $l2 $l1 (ref.func $tf) (local.get 0)) + ) + ) + ) + ) ) (assert_return (invoke "type-i32")) @@ -1189,6 +1250,19 @@ (assert_return (invoke "meet-externref" (i32.const 1) (ref.extern 1)) (ref.extern 1)) (assert_return (invoke "meet-externref" (i32.const 2) (ref.extern 1)) (ref.extern 1)) +(assert_return (invoke "meet-funcref-1" (i32.const 0)) (ref.func)) +(assert_return (invoke "meet-funcref-1" (i32.const 1)) (ref.func)) +(assert_return (invoke "meet-funcref-1" (i32.const 2)) (ref.func)) +(assert_return (invoke "meet-funcref-2" (i32.const 0)) (ref.func)) +(assert_return (invoke "meet-funcref-2" (i32.const 1)) (ref.func)) +(assert_return (invoke "meet-funcref-2" (i32.const 2)) (ref.func)) +(assert_return (invoke "meet-funcref-3" (i32.const 0)) (ref.func)) +(assert_return (invoke "meet-funcref-3" (i32.const 1)) (ref.func)) +(assert_return (invoke "meet-funcref-3" (i32.const 2)) (ref.func)) +(assert_return (invoke "meet-funcref-4" (i32.const 0)) (ref.func)) +(assert_return (invoke "meet-funcref-4" (i32.const 1)) (ref.func)) +(assert_return (invoke "meet-funcref-4" (i32.const 2)) (ref.func)) + (assert_invalid (module (func $type-arg-void-vs-num (result i32) (block (br_table 0 (i32.const 1)) (i32.const 1)) @@ -1389,4 +1463,3 @@ )) "unknown label" ) - diff --git a/test/core/call_ref.wast b/test/core/call_ref.wast new file mode 100644 index 0000000000..da480a7f23 --- /dev/null +++ b/test/core/call_ref.wast @@ -0,0 +1,208 @@ +(module + (type $ii (func (param i32) (result i32))) + + (func $apply (param $f (ref $ii)) (param $x i32) (result i32) + (call_ref $ii (local.get $x) (local.get $f)) + ) + + (func $f (type $ii) (i32.mul (local.get 0) (local.get 0))) + (func $g (type $ii) (i32.sub (i32.const 0) (local.get 0))) + + (elem declare func $f $g) + + (func (export "run") (param $x i32) (result i32) + (local $rf (ref null $ii)) + (local $rg (ref null $ii)) + (local.set $rf (ref.func $f)) + (local.set $rg (ref.func $g)) + (call_ref $ii (call_ref $ii (local.get $x) (local.get $rf)) (local.get $rg)) + ) + + (func (export "null") (result i32) + (call_ref $ii (i32.const 1) (ref.null $ii)) + ) + + ;; Recursion + + (type $ll (func (param i64) (result i64))) + (type $lll (func (param i64 i64) (result i64))) + + (elem declare func $fac) + (global $fac (ref $ll) (ref.func $fac)) + + (func $fac (export "fac") (type $ll) + (if (result i64) (i64.eqz (local.get 0)) + (then (i64.const 1)) + (else + (i64.mul + (local.get 0) + (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $fac)) + ) + ) + ) + ) + + (elem declare func $fac-acc) + (global $fac-acc (ref $lll) (ref.func $fac-acc)) + + (func $fac-acc (export "fac-acc") (type $lll) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 1)) + (else + (call_ref $lll + (i64.sub (local.get 0) (i64.const 1)) + (i64.mul (local.get 0) (local.get 1)) + (global.get $fac-acc) + ) + ) + ) + ) + + (elem declare func $fib) + (global $fib (ref $ll) (ref.func $fib)) + + (func $fib (export "fib") (type $ll) + (if (result i64) (i64.le_u (local.get 0) (i64.const 1)) + (then (i64.const 1)) + (else + (i64.add + (call_ref $ll (i64.sub (local.get 0) (i64.const 2)) (global.get $fib)) + (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $fib)) + ) + ) + ) + ) + + (elem declare func $even $odd) + (global $even (ref $ll) (ref.func $even)) + (global $odd (ref $ll) (ref.func $odd)) + + (func $even (export "even") (type $ll) + (if (result i64) (i64.eqz (local.get 0)) + (then (i64.const 44)) + (else (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $odd))) + ) + ) + (func $odd (export "odd") (type $ll) + (if (result i64) (i64.eqz (local.get 0)) + (then (i64.const 99)) + (else (call_ref $ll (i64.sub (local.get 0) (i64.const 1)) (global.get $even))) + ) + ) +) + +(assert_return (invoke "run" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "run" (i32.const 3)) (i32.const -9)) + +(assert_trap (invoke "null") "null function reference") + +(assert_return (invoke "fac" (i64.const 0)) (i64.const 1)) +(assert_return (invoke "fac" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac" (i64.const 5)) (i64.const 120)) +(assert_return (invoke "fac" (i64.const 25)) (i64.const 7034535277573963776)) +(assert_return (invoke "fac-acc" (i64.const 0) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 1) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 5) (i64.const 1)) (i64.const 120)) +(assert_return + (invoke "fac-acc" (i64.const 25) (i64.const 1)) + (i64.const 7034535277573963776) +) + +(assert_return (invoke "fib" (i64.const 0)) (i64.const 1)) +(assert_return (invoke "fib" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fib" (i64.const 2)) (i64.const 2)) +(assert_return (invoke "fib" (i64.const 5)) (i64.const 8)) +(assert_return (invoke "fib" (i64.const 20)) (i64.const 10946)) + +(assert_return (invoke "even" (i64.const 0)) (i64.const 44)) +(assert_return (invoke "even" (i64.const 1)) (i64.const 99)) +(assert_return (invoke "even" (i64.const 100)) (i64.const 44)) +(assert_return (invoke "even" (i64.const 77)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 0)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 1)) (i64.const 44)) +(assert_return (invoke "odd" (i64.const 200)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 77)) (i64.const 44)) + + +;; Unreachable typing. + +(module + (type $t (func)) + (func (export "unreachable") (result i32) + (unreachable) + (call_ref $t) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (ref.func $f) + (call_ref $t) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (i32.const 0) + (ref.func $f) + (call_ref $t) + (drop) + (i32.const 0) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(assert_invalid + (module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (i64.const 0) + (ref.func $f) + (call_ref $t) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (ref.func $f) + (call_ref $t) + (drop) + (i64.const 0) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (func $f (param $r externref) + (call_ref $t (local.get $r)) + ) + ) + "type mismatch" +) diff --git a/test/core/data.wast b/test/core/data.wast index b1e1239753..5e10a647b3 100644 --- a/test/core/data.wast +++ b/test/core/data.wast @@ -16,6 +16,7 @@ (data (memory $m) (i32.const 1) "a" "" "bcd") (data (memory $m) (offset (i32.const 0))) (data (memory $m) (offset (i32.const 0)) "" "a" "bc" "") + (data $d1 (i32.const 0)) (data $d2 (i32.const 1) "a" "" "bcd") (data $d3 (offset (i32.const 0))) @@ -81,14 +82,8 @@ (data (global.get $g) "a") ) -(assert_invalid - (module (memory 1) (global i32 (i32.const 0)) (data (global.get 0) "a")) - "unknown global" -) -(assert_invalid - (module (memory 1) (global $g i32 (i32.const 0)) (data (global.get $g) "a")) - "unknown global" -) +(module (memory 1) (global i32 (i32.const 0)) (data (global.get 0) "a")) +(module (memory 1) (global $g i32 (i32.const 0)) (data (global.get $g) "a")) ;; Corner cases @@ -174,6 +169,38 @@ (data (i32.const 1) "a") ) +;; Extended contant expressions + +(module + (memory 1) + (data (i32.add (i32.const 0) (i32.const 42))) +) + +(module + (memory 1) + (data (i32.sub (i32.const 42) (i32.const 0))) +) + +(module + (memory 1) + (data (i32.mul (i32.const 1) (i32.const 2))) +) + +;; Combining add, sub, mul and global.get + +(module + (global (import "spectest" "global_i32") i32) + (memory 1) + (data (i32.mul + (i32.const 2) + (i32.add + (i32.sub (global.get 0) (i32.const 1)) + (i32.const 2) + ) + ) + ) +) + ;; Invalid bounds for data (assert_trap diff --git a/test/core/elem.wast b/test/core/elem.wast index 68a244b992..c2a7976ac6 100644 --- a/test/core/elem.wast +++ b/test/core/elem.wast @@ -84,6 +84,14 @@ (table $t funcref (elem (ref.func $f) (ref.null func) (ref.func $g))) ) +(module + (func $f) + (func $g) + + (table $t 10 (ref func) (ref.func $f)) + (elem (i32.const 3) $g) +) + ;; Basic use @@ -167,13 +175,13 @@ (assert_return (invoke "call-7") (i32.const 65)) (assert_return (invoke "call-9") (i32.const 66)) -(assert_invalid - (module (table 1 funcref) (global i32 (i32.const 0)) (elem (global.get 0) $f) (func $f)) - "unknown global" +(module + (global i32 (i32.const 0)) + (table 1 funcref) (elem (global.get 0) $f) (func $f) ) -(assert_invalid - (module (table 1 funcref) (global $g i32 (i32.const 0)) (elem (global.get $g) $f) (func $f)) - "unknown global" +(module + (global $g i32 (i32.const 0)) + (table 1 funcref) (elem (global.get $g) $f) (func $f) ) @@ -233,6 +241,353 @@ (elem (i32.const 1) $f) ) + +;; Binary format variations + +(module + (func) + (table 1 funcref) + (elem (i32.const 0) func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\07\01" ;; Elem section: 1 element segment + "\00\41\00\0b\01\00" ;; Segment 0: (i32.const 0) func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\05\01" ;; Elem section: 1 element segment + "\01\00\01\00" ;; Segment 0: func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem (table 0) (i32.const 0) func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\09\01" ;; Elem section: 1 element segment + "\02\00\41\00\0b\00\01\00" ;; Segment 0: (table 0) (i32.const 0) func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem declare func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\05\01" ;; Elem section: 1 element segment + "\03\00\01\00" ;; Segment 0: declare func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem (i32.const 0) (;;)(ref func) (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\09\01" ;; Elem section: 1 element segment + "\04\41\00\0b\01\d2\00\0b" ;; Segment 0: (i32.const 0) (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) +(module + (func) + (table 1 funcref) + (elem (i32.const 0) funcref (ref.null func)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\09\01" ;; Elem section: 1 element segment + "\04\41\00\0b\01\d0\70\0b" ;; Segment 0: (i32.const 0) (ref.null func) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem (i32.const 0) funcref (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\07\01" ;; Elem section: 1 element segment + "\05\70\01\d2\00\0b" ;; Segment 0: funcref (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) +(module + (func) + (table 1 funcref) + (elem (i32.const 0) funcref (ref.null func)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\07\01" ;; Elem section: 1 element segment + "\05\70\01\d0\70\0b" ;; Segment 0: funcref (ref.null func) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem (table 0) (i32.const 0) funcref (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\0b\01" ;; Elem section: 1 element segment + "\06\00\41\00\0b\70\01\d2\00\0b" ;; Segment 0: (table 0) (i32.const 0) funcref (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) +(module + (func) + (table 1 funcref) + (elem (table 0) (i32.const 0) funcref (ref.null func)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\0b\01" ;; Elem section: 1 element segment + "\06\00\41\00\0b\70\01\d0\70\0b" ;; Segment 0: (table 0) (i32.const 0) funcref (ref.null func) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 funcref) + (elem declare funcref (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\07\01" ;; Elem section: 1 element segment + "\07\70\01\d2\00\0b" ;; Segment 0: declare funcref (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) +(module + (func) + (table 1 funcref) + (elem declare funcref (ref.null func)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\04\01" ;; Table section: 1 table + "\70\00\01" ;; Table 0: [1..] funcref + "\09\07\01" ;; Elem section: 1 element segment + "\07\70\01\d0\70\0b" ;; Segment 0: declare funcref (ref.null func) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem (i32.const 0) func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\07\01" ;; Elem section: 1 element segment + "\00\41\00\0b\01\00" ;; Segment 0: (i32.const 0) func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\05\01" ;; Elem section: 1 element segment + "\01\00\01\00" ;; Segment 0: func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem (table 0) (i32.const 0) func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\09\01" ;; Elem section: 1 element segment + "\02\00\41\00\0b\00\01\00" ;; Segment 0: (table 0) (i32.const 0) func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem declare func 0) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\05\01" ;; Elem section: 1 element segment + "\03\00\01\00" ;; Segment 0: declare func 0 + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(assert_invalid + (module + (func) + (table 1 (ref func) (ref.func 0)) + (elem (i32.const 0) funcref (ref.func 0)) + ) + "type mismatch" +) +(assert_invalid + (module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\09\01" ;; Elem section: 1 element segment + "\04\41\00\0b\01\d2\00\0b" ;; Segment 0: (i32.const 0) (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty + ) + "type mismatch" +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem (ref func) (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\08\01" ;; Elem section: 1 element segment + "\05\64\70\01\d2\00\0b" ;; Segment 0: (ref func) (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem (table 0) (i32.const 0) (ref func) (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\0c\01" ;; Elem section: 1 element segment + "\06\00\41\00\0b\64\70\01\d2\00\0b" ;; Segment 0: (table 0) (i32.const 0) (ref func) (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + +(module + (func) + (table 1 (ref func) (ref.func 0)) + (elem declare (ref func) (ref.func 0)) +) +(module binary + "\00asm" "\01\00\00\00" ;; Magic + "\01\04\01\60\00\00" ;; Type section: 1 type + "\03\02\01\00" ;; Function section: 1 function + "\04\0a\01" ;; Table section: 1 table + "\40\00\64\70\00\01\d2\00\0b" ;; Table 0: [1..] (ref func) (ref.func 0) + "\09\08\01" ;; Elem section: 1 element segment + "\07\64\70\01\d2\00\0b" ;; Segment 0: declare (ref func) (ref.func 0) + "\0a\04\01" ;; Code section: 1 function + "\02\00\0b" ;; Function 0: empty +) + + ;; Invalid bounds for elements (assert_trap @@ -337,6 +692,7 @@ "out of bounds table access" ) + ;; Implicitly dropped elements (module @@ -359,6 +715,7 @@ ) (assert_trap (invoke "init") "out of bounds table access") + ;; Element without table (assert_invalid @@ -369,6 +726,7 @@ "unknown table" ) + ;; Invalid offsets (assert_invalid @@ -489,6 +847,7 @@ "constant expression required" ) + ;; Invalid elements (assert_invalid @@ -532,6 +891,16 @@ "constant expression required" ) +(assert_invalid + (module + (func $f (result i32) (i32.const 9)) + (table 1 funcref) + (elem (i32.const 0) funcref (item (call $f))) + ) + "constant expression required" +) + + ;; Two elements target the same slot (module @@ -560,6 +929,7 @@ ) (assert_return (invoke "call-overwritten-element") (i32.const 66)) + ;; Element sections across multiple modules change the same table (module $module1 @@ -690,3 +1060,60 @@ ) (assert_return (invoke "call_imported_elem") (i32.const 42)) + +;; Extended contant expressions + +(module + (table 10 funcref) + (func (result i32) (i32.const 42)) + (func (export "call_in_table") (param i32) (result i32) + (call_indirect (type 0) (local.get 0))) + (elem (table 0) (offset (i32.add (i32.const 1) (i32.const 2))) funcref (ref.func 0)) +) + +(assert_return (invoke "call_in_table" (i32.const 3)) (i32.const 42)) +(assert_trap (invoke "call_in_table" (i32.const 0)) "uninitialized element") + +(module + (table 10 funcref) + (func (result i32) (i32.const 42)) + (func (export "call_in_table") (param i32) (result i32) + (call_indirect (type 0) (local.get 0))) + (elem (table 0) (offset (i32.sub (i32.const 2) (i32.const 1))) funcref (ref.func 0)) +) + +(assert_return (invoke "call_in_table" (i32.const 1)) (i32.const 42)) +(assert_trap (invoke "call_in_table" (i32.const 0)) "uninitialized element") + +(module + (table 10 funcref) + (func (result i32) (i32.const 42)) + (func (export "call_in_table") (param i32) (result i32) + (call_indirect (type 0) (local.get 0))) + (elem (table 0) (offset (i32.mul (i32.const 2) (i32.const 2))) funcref (ref.func 0)) +) + +(assert_return (invoke "call_in_table" (i32.const 4)) (i32.const 42)) +(assert_trap (invoke "call_in_table" (i32.const 0)) "uninitialized element") + +;; Combining add, sub, mul and global.get + +(module + (global (import "spectest" "global_i32") i32) + (table 10 funcref) + (func (result i32) (i32.const 42)) + (func (export "call_in_table") (param i32) (result i32) + (call_indirect (type 0) (local.get 0))) + (elem (table 0) + (offset + (i32.mul + (i32.const 2) + (i32.add + (i32.sub (global.get 0) (i32.const 665)) + (i32.const 2)))) + funcref + (ref.func 0)) +) + +(assert_return (invoke "call_in_table" (i32.const 6)) (i32.const 42)) +(assert_trap (invoke "call_in_table" (i32.const 0)) "uninitialized element") diff --git a/test/core/func.wast b/test/core/func.wast index 7189257c99..f2d18cc9f1 100644 --- a/test/core/func.wast +++ b/test/core/func.wast @@ -182,9 +182,9 @@ (func (export "break-br_table-num") (param i32) (result i32) (br_table 0 0 (i32.const 50) (local.get 0)) (i32.const 51) ) - (func (export "break-br_table-num-num") (param i32) (result i32 i64) - (br_table 0 0 (i32.const 50) (i64.const 51) (local.get 0)) - (i32.const 51) (i64.const 52) + (func (export "break-br_table-num-num") (param i32) (result f32 i64) + (br_table 0 0 (f32.const 50) (i64.const 51) (local.get 0)) + (f32.const 51) (i64.const 52) ) (func (export "break-br_table-nested-empty") (param i32) (block (br_table 0 1 0 (local.get 0))) @@ -353,16 +353,16 @@ (assert_return (invoke "break-br_table-num" (i32.const 10)) (i32.const 50)) (assert_return (invoke "break-br_table-num" (i32.const -100)) (i32.const 50)) (assert_return (invoke "break-br_table-num-num" (i32.const 0)) - (i32.const 50) (i64.const 51) + (f32.const 50) (i64.const 51) ) (assert_return (invoke "break-br_table-num-num" (i32.const 1)) - (i32.const 50) (i64.const 51) + (f32.const 50) (i64.const 51) ) (assert_return (invoke "break-br_table-num-num" (i32.const 10)) - (i32.const 50) (i64.const 51) + (f32.const 50) (i64.const 51) ) (assert_return (invoke "break-br_table-num-num" (i32.const -100)) - (i32.const 50) (i64.const 51) + (f32.const 50) (i64.const 51) ) (assert_return (invoke "break-br_table-nested-empty" (i32.const 0))) (assert_return (invoke "break-br_table-nested-empty" (i32.const 1))) @@ -627,6 +627,19 @@ "inline function type" ) +(assert_invalid + (module (func $g (type 4))) + "unknown type" +) +(assert_invalid + (module + (func $f (drop (ref.func $g))) + (func $g (type 4)) + (elem declare func $g) + ) + "unknown type" +) + ;; Invalid typing of locals @@ -643,6 +656,14 @@ "type mismatch" ) +(assert_invalid + (module + (type $t (func)) + (func $type-local-uninitialized (local $x (ref $t)) (drop (local.get $x))) + ) + "uninitialized local" +) + ;; Invalid typing of parameters @@ -940,22 +961,28 @@ ;; Duplicate name errors -(assert_malformed (module quote - "(func $foo)" - "(func $foo)") - "duplicate func") -(assert_malformed (module quote - "(import \"\" \"\" (func $foo))" - "(func $foo)") - "duplicate func") -(assert_malformed (module quote - "(import \"\" \"\" (func $foo))" - "(import \"\" \"\" (func $foo))") - "duplicate func") - -(assert_malformed (module quote "(func (param $foo i32) (param $foo i32))") - "duplicate local") -(assert_malformed (module quote "(func (param $foo i32) (local $foo i32))") - "duplicate local") -(assert_malformed (module quote "(func (local $foo i32) (local $foo i32))") - "duplicate local") +(assert_malformed + (module quote "(func $foo)" "(func $foo)") + "duplicate func" +) +(assert_malformed + (module quote "(import \"\" \"\" (func $foo))" "(func $foo)") + "duplicate func" +) +(assert_malformed + (module quote "(import \"\" \"\" (func $foo))" "(import \"\" \"\" (func $foo))") + "duplicate func" +) + +(assert_malformed + (module quote "(func (param $foo i32) (param $foo i32))") + "duplicate local" +) +(assert_malformed + (module quote "(func (param $foo i32) (local $foo i32))") + "duplicate local" +) +(assert_malformed + (module quote "(func (local $foo i32) (local $foo i32))") + "duplicate local" +) diff --git a/test/core/gc/array.wast b/test/core/gc/array.wast new file mode 100644 index 0000000000..6ad95c0873 --- /dev/null +++ b/test/core/gc/array.wast @@ -0,0 +1,315 @@ +;; Type syntax + +(module + (type (array i8)) + (type (array i16)) + (type (array i32)) + (type (array i64)) + (type (array f32)) + (type (array f64)) + (type (array anyref)) + (type (array (ref struct))) + (type (array (ref 0))) + (type (array (ref null 1))) + (type (array (mut i8))) + (type (array (mut i16))) + (type (array (mut i32))) + (type (array (mut i64))) + (type (array (mut i32))) + (type (array (mut i64))) + (type (array (mut anyref))) + (type (array (mut (ref struct)))) + (type (array (mut (ref 0)))) + (type (array (mut (ref null i31)))) +) + + +(assert_invalid + (module + (type (array (mut (ref null 10)))) + ) + "unknown type" +) + + +;; Binding structure + +(module + (rec + (type $s0 (array (ref $s1))) + (type $s1 (array (ref $s0))) + ) + + (func (param (ref $forward))) + + (type $forward (array i32)) +) + +(assert_invalid + (module (type (array (ref 1)))) + "unknown type" +) +(assert_invalid + (module (type (array (mut (ref 1))))) + "unknown type" +) + + +;; Basic instructions + +(module + (type $vec (array f32)) + (type $mvec (array (mut f32))) + + (global (ref $vec) (array.new $vec (f32.const 1) (i32.const 3))) + (global (ref $vec) (array.new_default $vec (i32.const 3))) + + (func $new (export "new") (result (ref $vec)) + (array.new_default $vec (i32.const 3)) + ) + + (func $get (param $i i32) (param $v (ref $vec)) (result f32) + (array.get $vec (local.get $v) (local.get $i)) + ) + (func (export "get") (param $i i32) (result f32) + (call $get (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y f32) (result f32) + (array.set $mvec (local.get $v) (local.get $i) (local.get $y)) + (array.get $mvec (local.get $v) (local.get $i)) + ) + (func (export "set_get") (param $i i32) (param $y f32) (result f32) + (call $set_get (local.get $i) + (array.new_default $mvec (i32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "new") (ref.array)) +(assert_return (invoke "new") (ref.eq)) +(assert_return (invoke "get" (i32.const 0)) (f32.const 0)) +(assert_return (invoke "set_get" (i32.const 1) (f32.const 7)) (f32.const 7)) +(assert_return (invoke "len") (i32.const 3)) + +(assert_trap (invoke "get" (i32.const 10)) "out of bounds array access") +(assert_trap (invoke "set_get" (i32.const 10) (f32.const 7)) "out of bounds array access") + +(module + (type $vec (array f32)) + (type $mvec (array (mut f32))) + + (global (ref $vec) (array.new_fixed $vec 2 (f32.const 1) (f32.const 2))) + + (func $new (export "new") (result (ref $vec)) + (array.new_fixed $vec 2 (f32.const 1) (f32.const 2)) + ) + + (func $get (param $i i32) (param $v (ref $vec)) (result f32) + (array.get $vec (local.get $v) (local.get $i)) + ) + (func (export "get") (param $i i32) (result f32) + (call $get (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y f32) (result f32) + (array.set $mvec (local.get $v) (local.get $i) (local.get $y)) + (array.get $mvec (local.get $v) (local.get $i)) + ) + (func (export "set_get") (param $i i32) (param $y f32) (result f32) + (call $set_get (local.get $i) + (array.new_fixed $mvec 3 (f32.const 1) (f32.const 2) (f32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "new") (ref.array)) +(assert_return (invoke "new") (ref.eq)) +(assert_return (invoke "get" (i32.const 0)) (f32.const 1)) +(assert_return (invoke "set_get" (i32.const 1) (f32.const 7)) (f32.const 7)) +(assert_return (invoke "len") (i32.const 2)) + +(assert_trap (invoke "get" (i32.const 10)) "out of bounds array access") +(assert_trap (invoke "set_get" (i32.const 10) (f32.const 7)) "out of bounds array access") + +(module + (type $vec (array i8)) + (type $mvec (array (mut i8))) + + (data $d "\00\01\02\ff\04") + + (func $new (export "new") (result (ref $vec)) + (array.new_data $vec $d (i32.const 1) (i32.const 3)) + ) + + (func $get_u (param $i i32) (param $v (ref $vec)) (result i32) + (array.get_u $vec (local.get $v) (local.get $i)) + ) + (func (export "get_u") (param $i i32) (result i32) + (call $get_u (local.get $i) (call $new)) + ) + + (func $get_s (param $i i32) (param $v (ref $vec)) (result i32) + (array.get_s $vec (local.get $v) (local.get $i)) + ) + (func (export "get_s") (param $i i32) (result i32) + (call $get_s (local.get $i) (call $new)) + ) + + (func $set_get (param $i i32) (param $v (ref $mvec)) (param $y i32) (result i32) + (array.set $mvec (local.get $v) (local.get $i) (local.get $y)) + (array.get_u $mvec (local.get $v) (local.get $i)) + ) + (func (export "set_get") (param $i i32) (param $y i32) (result i32) + (call $set_get (local.get $i) + (array.new_data $mvec $d (i32.const 1) (i32.const 3)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "new") (ref.array)) +(assert_return (invoke "new") (ref.eq)) +(assert_return (invoke "get_u" (i32.const 2)) (i32.const 0xff)) +(assert_return (invoke "get_s" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "set_get" (i32.const 1) (i32.const 7)) (i32.const 7)) +(assert_return (invoke "len") (i32.const 3)) + +(assert_trap (invoke "get_u" (i32.const 10)) "out of bounds array access") +(assert_trap (invoke "get_s" (i32.const 10)) "out of bounds array access") +(assert_trap (invoke "set_get" (i32.const 10) (i32.const 7)) "out of bounds array access") + +(module + (type $bvec (array i8)) + (type $vec (array (ref $bvec))) + (type $mvec (array (mut (ref $bvec)))) + (type $nvec (array (ref null $bvec))) + (type $avec (array (mut anyref))) + + (elem $e (ref $bvec) + (array.new $bvec (i32.const 7) (i32.const 3)) + (array.new_fixed $bvec 2 (i32.const 1) (i32.const 2)) + ) + + (func $new (export "new") (result (ref $vec)) + (array.new_elem $vec $e (i32.const 0) (i32.const 2)) + ) + + (func $sub1 (result (ref $nvec)) + (array.new_elem $nvec $e (i32.const 0) (i32.const 2)) + ) + (func $sub2 (result (ref $avec)) + (array.new_elem $avec $e (i32.const 0) (i32.const 2)) + ) + + (func $get (param $i i32) (param $j i32) (param $v (ref $vec)) (result i32) + (array.get_u $bvec (array.get $vec (local.get $v) (local.get $i)) (local.get $j)) + ) + (func (export "get") (param $i i32) (param $j i32) (result i32) + (call $get (local.get $i) (local.get $j) (call $new)) + ) + + (func $set_get (param $i i32) (param $j i32) (param $v (ref $mvec)) (param $y i32) (result i32) + (array.set $mvec (local.get $v) (local.get $i) (array.get $mvec (local.get $v) (local.get $y))) + (array.get_u $bvec (array.get $mvec (local.get $v) (local.get $i)) (local.get $j)) + ) + (func (export "set_get") (param $i i32) (param $j i32) (param $y i32) (result i32) + (call $set_get (local.get $i) (local.get $j) + (array.new_elem $mvec $e (i32.const 0) (i32.const 2)) + (local.get $y) + ) + ) + + (func $len (param $v (ref array)) (result i32) + (array.len (local.get $v)) + ) + (func (export "len") (result i32) + (call $len (call $new)) + ) +) + +(assert_return (invoke "new") (ref.array)) +(assert_return (invoke "new") (ref.eq)) +(assert_return (invoke "get" (i32.const 0) (i32.const 0)) (i32.const 7)) +(assert_return (invoke "get" (i32.const 1) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "set_get" (i32.const 0) (i32.const 1) (i32.const 1)) (i32.const 2)) +(assert_return (invoke "len") (i32.const 2)) + +(assert_trap (invoke "get" (i32.const 10) (i32.const 0)) "out of bounds array access") +(assert_trap (invoke "set_get" (i32.const 10) (i32.const 0) (i32.const 0)) "out of bounds array access") + +(assert_invalid + (module + (type $a (array i64)) + (func (export "array.set-immutable") (param $a (ref $a)) + (array.set $a (local.get $a) (i32.const 0) (i64.const 1)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $bvec (array i8)) + + (data $d "\00\01\02\03\04") + + (global (ref $bvec) + (array.new_data $bvec $d (i32.const 1) (i32.const 3)) + ) + ) + "constant expression required" +) + +(assert_invalid + (module + (type $bvec (array i8)) + (type $vvec (array (ref $bvec))) + + (elem $e (ref $bvec) (ref.null $bvec)) + + (global (ref $vvec) + (array.new_elem $vvec $e (i32.const 0) (i32.const 1)) + ) + ) + "constant expression required" +) + + +;; Null dereference + +(module + (type $t (array (mut i32))) + (func (export "array.get-null") + (local (ref null $t)) (drop (array.get $t (local.get 0) (i32.const 0))) + ) + (func (export "array.set-null") + (local (ref null $t)) (array.set $t (local.get 0) (i32.const 0) (i32.const 0)) + ) +) + +(assert_trap (invoke "array.get-null") "null array reference") +(assert_trap (invoke "array.set-null") "null array reference") diff --git a/test/core/gc/array_copy.wast b/test/core/gc/array_copy.wast new file mode 100644 index 0000000000..d1caf12647 --- /dev/null +++ b/test/core/gc/array_copy.wast @@ -0,0 +1,139 @@ +;; Bulk instructions + +;; invalid uses + +(assert_invalid + (module + (type $a (array i8)) + (type $b (array (mut i8))) + + (func (export "array.copy-immutable") (param $1 (ref $a)) (param $2 (ref $b)) + (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $a (array (mut i8))) + (type $b (array i16)) + + (func (export "array.copy-packed-invalid") (param $1 (ref $a)) (param $2 (ref $b)) + (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0)) + ) + ) + "array types do not match" +) + +(assert_invalid + (module + (type $a (array (mut i8))) + (type $b (array (mut (ref $a)))) + + (func (export "array.copy-ref-invalid-1") (param $1 (ref $a)) (param $2 (ref $b)) + (array.copy $a $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0)) + ) + ) + "array types do not match" +) + +(assert_invalid + (module + (type $a (array (mut i8))) + (type $b (array (mut (ref $a)))) + (type $c (array (mut (ref $b)))) + + (func (export "array.copy-ref-invalid-1") (param $1 (ref $b)) (param $2 (ref $c)) + (array.copy $b $c (local.get $1) (i32.const 0) (local.get $2) (i32.const 0) (i32.const 0)) + ) + ) + "array types do not match" +) + +(module + (type $arr8 (array i8)) + (type $arr8_mut (array (mut i8))) + + (global $g_arr8 (ref $arr8) (array.new $arr8 (i32.const 10) (i32.const 12))) + (global $g_arr8_mut (mut (ref $arr8_mut)) (array.new_default $arr8_mut (i32.const 12))) + + (data $d1 "abcdefghijkl") + + (func (export "array_get_nth") (param $1 i32) (result i32) + (array.get_u $arr8_mut (global.get $g_arr8_mut) (local.get $1)) + ) + + (func (export "array_copy-null-left") + (array.copy $arr8_mut $arr8 (ref.null $arr8_mut) (i32.const 0) (global.get $g_arr8) (i32.const 0) (i32.const 0)) + ) + + (func (export "array_copy-null-right") + (array.copy $arr8_mut $arr8 (global.get $g_arr8_mut) (i32.const 0) (ref.null $arr8) (i32.const 0) (i32.const 0)) + ) + + (func (export "array_copy") (param $1 i32) (param $2 i32) (param $3 i32) + (array.copy $arr8_mut $arr8 (global.get $g_arr8_mut) (local.get $1) (global.get $g_arr8) (local.get $2) (local.get $3)) + ) + + (func (export "array_copy_overlap_test-1") + (local $1 (ref $arr8_mut)) + (array.new_data $arr8_mut $d1 (i32.const 0) (i32.const 12)) + (local.set $1) + (array.copy $arr8_mut $arr8_mut (local.get $1) (i32.const 1) (local.get $1) (i32.const 0) (i32.const 11)) + (global.set $g_arr8_mut (local.get $1)) + ) + + (func (export "array_copy_overlap_test-2") + (local $1 (ref $arr8_mut)) + (array.new_data $arr8_mut $d1 (i32.const 0) (i32.const 12)) + (local.set $1) + (array.copy $arr8_mut $arr8_mut (local.get $1) (i32.const 0) (local.get $1) (i32.const 1) (i32.const 11)) + (global.set $g_arr8_mut (local.get $1)) + ) +) + +;; null array argument traps +(assert_trap (invoke "array_copy-null-left") "null array reference") +(assert_trap (invoke "array_copy-null-right") "null array reference") + +;; OOB initial index traps +(assert_trap (invoke "array_copy" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access") +(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds array access") + +;; OOB length traps +(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") +(assert_trap (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") + +;; start index = array size, len = 0 doesn't trap +(assert_return (invoke "array_copy" (i32.const 12) (i32.const 0) (i32.const 0))) +(assert_return (invoke "array_copy" (i32.const 0) (i32.const 12) (i32.const 0))) + +;; check arrays were not modified +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 0)) +(assert_trap (invoke "array_get_nth" (i32.const 12)) "out of bounds array access") + +;; normal case +(assert_return (invoke "array_copy" (i32.const 0) (i32.const 0) (i32.const 2))) +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 10)) +(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 10)) +(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 0)) + +;; test that overlapping array.copy works as if intermediate copy taken +(assert_return (invoke "array_copy_overlap_test-1")) +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 97)) +(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 98)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 101)) +(assert_return (invoke "array_get_nth" (i32.const 10)) (i32.const 106)) +(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 107)) + +(assert_return (invoke "array_copy_overlap_test-2")) +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 98)) +(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 99)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 103)) +(assert_return (invoke "array_get_nth" (i32.const 9)) (i32.const 107)) +(assert_return (invoke "array_get_nth" (i32.const 10)) (i32.const 108)) +(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 108)) diff --git a/test/core/gc/array_fill.wast b/test/core/gc/array_fill.wast new file mode 100644 index 0000000000..0379ad537e --- /dev/null +++ b/test/core/gc/array_fill.wast @@ -0,0 +1,81 @@ +;; Bulk instructions + +;; invalid uses + +(assert_invalid + (module + (type $a (array i8)) + + (func (export "array.fill-immutable") (param $1 (ref $a)) (param $2 i32) + (array.fill $a (local.get $1) (i32.const 0) (local.get $2) (i32.const 0)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $a (array (mut i8))) + + (func (export "array.fill-invalid-1") (param $1 (ref $a)) (param $2 funcref) + (array.fill $a (local.get $1) (i32.const 0) (local.get $2) (i32.const 0)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $b (array (mut funcref))) + + (func (export "array.fill-invalid-1") (param $1 (ref $b)) (param $2 i32) + (array.fill $b (local.get $1) (i32.const 0) (local.get $2) (i32.const 0)) + ) + ) + "type mismatch" +) + +(module + (type $arr8 (array i8)) + (type $arr8_mut (array (mut i8))) + + (global $g_arr8 (ref $arr8) (array.new $arr8 (i32.const 10) (i32.const 12))) + (global $g_arr8_mut (mut (ref $arr8_mut)) (array.new_default $arr8_mut (i32.const 12))) + + (func (export "array_get_nth") (param $1 i32) (result i32) + (array.get_u $arr8_mut (global.get $g_arr8_mut) (local.get $1)) + ) + + (func (export "array_fill-null") + (array.fill $arr8_mut (ref.null $arr8_mut) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + + (func (export "array_fill") (param $1 i32) (param $2 i32) (param $3 i32) + (array.fill $arr8_mut (global.get $g_arr8_mut) (local.get $1) (local.get $2) (local.get $3)) + ) +) + +;; null array argument traps +(assert_trap (invoke "array_fill-null") "null array reference") + +;; OOB initial index traps +(assert_trap (invoke "array_fill" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access") + +;; OOB length traps +(assert_trap (invoke "array_fill" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") + +;; start index = array size, len = 0 doesn't trap +(assert_return (invoke "array_fill" (i32.const 12) (i32.const 0) (i32.const 0))) + +;; check arrays were not modified +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 0)) +(assert_trap (invoke "array_get_nth" (i32.const 12)) "out of bounds array access") + +;; normal case +(assert_return (invoke "array_fill" (i32.const 2) (i32.const 11) (i32.const 2))) +(assert_return (invoke "array_get_nth" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 2)) (i32.const 11)) +(assert_return (invoke "array_get_nth" (i32.const 3)) (i32.const 11)) +(assert_return (invoke "array_get_nth" (i32.const 4)) (i32.const 0)) diff --git a/test/core/gc/array_init_data.wast b/test/core/gc/array_init_data.wast new file mode 100644 index 0000000000..3bee026474 --- /dev/null +++ b/test/core/gc/array_init_data.wast @@ -0,0 +1,110 @@ +;; Bulk instructions + +;; invalid uses + +(assert_invalid + (module + (type $a (array i8)) + + (data $d1 "a") + + (func (export "array.init_data-immutable") (param $1 (ref $a)) + (array.init_data $a $d1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $a (array (mut funcref))) + + (data $d1 "a") + + (func (export "array.init_data-invalid-1") (param $1 (ref $a)) + (array.init_data $a $d1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + ) + "array type is not numeric or vector" +) + +(module + (type $arr8 (array i8)) + (type $arr8_mut (array (mut i8))) + (type $arr16_mut (array (mut i16))) + + (global $g_arr8 (ref $arr8) (array.new $arr8 (i32.const 10) (i32.const 12))) + (global $g_arr8_mut (mut (ref $arr8_mut)) (array.new_default $arr8_mut (i32.const 12))) + (global $g_arr16_mut (ref $arr16_mut) (array.new_default $arr16_mut (i32.const 6))) + + (data $d1 "abcdefghijkl") + + (func (export "array_get_nth") (param $1 i32) (result i32) + (array.get_u $arr8_mut (global.get $g_arr8_mut) (local.get $1)) + ) + + (func (export "array_get_nth_i16") (param $1 i32) (result i32) + (array.get_u $arr16_mut (global.get $g_arr16_mut) (local.get $1)) + ) + + (func (export "array_init_data-null") + (array.init_data $arr8_mut $d1 (ref.null $arr8_mut) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + + (func (export "array_init_data") (param $1 i32) (param $2 i32) (param $3 i32) + (array.init_data $arr8_mut $d1 (global.get $g_arr8_mut) (local.get $1) (local.get $2) (local.get $3)) + ) + + (func (export "array_init_data_i16") (param $1 i32) (param $2 i32) (param $3 i32) + (array.init_data $arr16_mut $d1 (global.get $g_arr16_mut) (local.get $1) (local.get $2) (local.get $3)) + ) + + (func (export "drop_segs") + (data.drop $d1) + ) +) + +;; null array argument traps +(assert_trap (invoke "array_init_data-null") "null array reference") + +;; OOB initial index traps +(assert_trap (invoke "array_init_data" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access") +(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds memory access") + +;; OOB length traps +(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") +(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") +(assert_trap (invoke "array_init_data_i16" (i32.const 0) (i32.const 0) (i32.const 7)) "out of bounds array access") + +;; start index = array size, len = 0 doesn't trap +(assert_return (invoke "array_init_data" (i32.const 12) (i32.const 0) (i32.const 0))) +(assert_return (invoke "array_init_data" (i32.const 0) (i32.const 12) (i32.const 0))) +(assert_return (invoke "array_init_data_i16" (i32.const 0) (i32.const 6) (i32.const 0))) + +;; check arrays were not modified +(assert_return (invoke "array_get_nth" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 11)) (i32.const 0)) +(assert_trap (invoke "array_get_nth" (i32.const 12)) "out of bounds array access") +(assert_return (invoke "array_get_nth_i16" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "array_get_nth_i16" (i32.const 2)) (i32.const 0)) +(assert_return (invoke "array_get_nth_i16" (i32.const 5)) (i32.const 0)) +(assert_trap (invoke "array_get_nth_i16" (i32.const 6)) "out of bounds array access") + +;; normal cases +(assert_return (invoke "array_init_data" (i32.const 4) (i32.const 2) (i32.const 2))) +(assert_return (invoke "array_get_nth" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "array_get_nth" (i32.const 4)) (i32.const 99)) +(assert_return (invoke "array_get_nth" (i32.const 5)) (i32.const 100)) +(assert_return (invoke "array_get_nth" (i32.const 6)) (i32.const 0)) + +(assert_return (invoke "array_init_data_i16" (i32.const 2) (i32.const 5) (i32.const 2))) +(assert_return (invoke "array_get_nth_i16" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "array_get_nth_i16" (i32.const 2)) (i32.const 0x6766)) +(assert_return (invoke "array_get_nth_i16" (i32.const 3)) (i32.const 0x6968)) +(assert_return (invoke "array_get_nth_i16" (i32.const 4)) (i32.const 0)) + +;; init_data/elem with dropped segments traps for non-zero length +(assert_return (invoke "drop_segs")) +(assert_return (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 0))) +(assert_trap (invoke "array_init_data" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds memory access") diff --git a/test/core/gc/array_init_elem.wast b/test/core/gc/array_init_elem.wast new file mode 100644 index 0000000000..34a12599b0 --- /dev/null +++ b/test/core/gc/array_init_elem.wast @@ -0,0 +1,108 @@ +;; Bulk instructions + +;; invalid uses + +(assert_invalid + (module + (type $a (array funcref)) + + (elem $e1 funcref) + + (func (export "array.init_elem-immutable") (param $1 (ref $a)) + (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + ) + "array is immutable" +) + +(assert_invalid + (module + (type $a (array (mut i8))) + + (elem $e1 funcref) + + (func (export "array.init_elem-invalid-1") (param $1 (ref $a)) + (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $a (array (mut funcref))) + + (elem $e1 externref) + + (func (export "array.init_elem-invalid-2") (param $1 (ref $a)) + (array.init_elem $a $e1 (local.get $1) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + ) + "type mismatch" +) + +(module + (type $t_f (func)) + (type $arrref (array (ref $t_f))) + (type $arrref_mut (array (mut funcref))) + + (global $g_arrref (ref $arrref) (array.new $arrref (ref.func $dummy) (i32.const 12))) + (global $g_arrref_mut (ref $arrref_mut) (array.new_default $arrref_mut (i32.const 12))) + + (table $t 1 funcref) + + (elem $e1 func $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy) + + (func $dummy + ) + + (func (export "array_call_nth") (param $1 i32) + (table.set $t (i32.const 0) (array.get $arrref_mut (global.get $g_arrref_mut) (local.get $1))) + (call_indirect $t (i32.const 0)) + ) + + (func (export "array_init_elem-null") + (array.init_elem $arrref_mut $e1 (ref.null $arrref_mut) (i32.const 0) (i32.const 0) (i32.const 0)) + ) + + (func (export "array_init_elem") (param $1 i32) (param $2 i32) (param $3 i32) + (array.init_elem $arrref_mut $e1 (global.get $g_arrref_mut) (local.get $1) (local.get $2) (local.get $3)) + ) + + (func (export "drop_segs") + (elem.drop $e1) + ) +) + +;; null array argument traps +(assert_trap (invoke "array_init_elem-null") "null array reference") + +;; OOB initial index traps +(assert_trap (invoke "array_init_elem" (i32.const 13) (i32.const 0) (i32.const 0)) "out of bounds array access") +(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 13) (i32.const 0)) "out of bounds table access") + +;; OOB length traps +(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") +(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 13)) "out of bounds array access") + +;; start index = array size, len = 0 doesn't trap +(assert_return (invoke "array_init_elem" (i32.const 12) (i32.const 0) (i32.const 0))) +(assert_return (invoke "array_init_elem" (i32.const 0) (i32.const 12) (i32.const 0))) + +;; check arrays were not modified +(assert_trap (invoke "array_call_nth" (i32.const 0)) "uninitialized element") +(assert_trap (invoke "array_call_nth" (i32.const 5)) "uninitialized element") +(assert_trap (invoke "array_call_nth" (i32.const 11)) "uninitialized element") +(assert_trap (invoke "array_call_nth" (i32.const 12)) "out of bounds array access") + +;; normal cases +(assert_return (invoke "array_init_elem" (i32.const 2) (i32.const 3) (i32.const 2))) +(assert_trap (invoke "array_call_nth" (i32.const 1)) "uninitialized element") +(assert_return (invoke "array_call_nth" (i32.const 2))) +(assert_return (invoke "array_call_nth" (i32.const 3))) +(assert_trap (invoke "array_call_nth" (i32.const 4)) "uninitialized element") + +;; init_data/elem with dropped segments traps for non-zero length +(assert_return (invoke "drop_segs")) +(assert_return (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 0))) +(assert_trap (invoke "array_init_elem" (i32.const 0) (i32.const 0) (i32.const 1)) "out of bounds table access") diff --git a/test/core/gc/binary-gc.wast b/test/core/gc/binary-gc.wast new file mode 100644 index 0000000000..589573f2cc --- /dev/null +++ b/test/core/gc/binary-gc.wast @@ -0,0 +1,12 @@ +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01" ;; Type section id + "\04" ;; Type section length + "\01" ;; Types vector length + "\5e" ;; Array type, -0x22 + "\78" ;; Storage type: i8 or -0x08 + "\02" ;; Mutability, should be 0 or 1, but isn't + ) + "malformed mutability" +) diff --git a/test/core/gc/br_on_cast.wast b/test/core/gc/br_on_cast.wast new file mode 100644 index 0000000000..3c895c0710 --- /dev/null +++ b/test/core/gc/br_on_cast.wast @@ -0,0 +1,285 @@ +;; Abstract Types + +(module + (type $ft (func (result i32))) + (type $st (struct (field i16))) + (type $at (array i8)) + + (table 10 anyref) + + (elem declare func $f) + (func $f (result i32) (i32.const 9)) + + (func (export "init") (param $x externref) + (table.set (i32.const 0) (ref.null any)) + (table.set (i32.const 1) (ref.i31 (i32.const 7))) + (table.set (i32.const 2) (struct.new $st (i32.const 6))) + (table.set (i32.const 3) (array.new $at (i32.const 5) (i32.const 3))) + (table.set (i32.const 4) (any.convert_extern (local.get $x))) + ) + + (func (export "br_on_null") (param $i i32) (result i32) + (block $l + (br_on_null $l (table.get (local.get $i))) + (return (i32.const -1)) + ) + (i32.const 0) + ) + (func (export "br_on_i31") (param $i i32) (result i32) + (block $l (result (ref i31)) + (br_on_cast $l anyref (ref i31) (table.get (local.get $i))) + (return (i32.const -1)) + ) + (i31.get_u) + ) + (func (export "br_on_struct") (param $i i32) (result i32) + (block $l (result (ref struct)) + (br_on_cast $l anyref (ref struct) (table.get (local.get $i))) + (return (i32.const -1)) + ) + (block $l2 (param structref) (result (ref $st)) + (block $l3 (param structref) (result (ref $at)) + (br_on_cast $l2 structref (ref $st)) + (br_on_cast $l3 anyref (ref $at)) + (return (i32.const -2)) + ) + (return (array.get_u $at (i32.const 0))) + ) + (struct.get_s $st 0) + ) + (func (export "br_on_array") (param $i i32) (result i32) + (block $l (result (ref array)) + (br_on_cast $l anyref (ref array) (table.get (local.get $i))) + (return (i32.const -1)) + ) + (array.len) + ) + + (func (export "null-diff") (param $i i32) (result i32) + (block $l (result (ref null struct)) + (block (result (ref any)) + (br_on_cast $l (ref null any) (ref null struct) (table.get (local.get $i))) + ) + (return (i32.const 0)) + ) + (return (i32.const 1)) + ) +) + +(invoke "init" (ref.extern 0)) + +(assert_return (invoke "br_on_null" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "br_on_null" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_null" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_null" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_null" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_i31" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_i31" (i32.const 1)) (i32.const 7)) +(assert_return (invoke "br_on_i31" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_i31" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_i31" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_struct" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_struct" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_struct" (i32.const 2)) (i32.const 6)) +(assert_return (invoke "br_on_struct" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_struct" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_array" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_array" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_array" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_array" (i32.const 3)) (i32.const 3)) +(assert_return (invoke "br_on_array" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "null-diff" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "null-diff" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "null-diff" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "null-diff" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "null-diff" (i32.const 4)) (i32.const 0)) + + +;; Concrete Types + +(module + (type $t0 (sub (struct))) + (type $t1 (sub $t0 (struct (field i32)))) + (type $t1' (sub $t0 (struct (field i32)))) + (type $t2 (sub $t1 (struct (field i32 i32)))) + (type $t2' (sub $t1' (struct (field i32 i32)))) + (type $t3 (sub $t0 (struct (field i32 i32)))) + (type $t0' (sub $t0 (struct))) + (type $t4 (sub $t0' (struct (field i32 i32)))) + + (table 20 structref) + + (func $init + (table.set (i32.const 0) (struct.new_default $t0)) + (table.set (i32.const 10) (struct.new_default $t0')) + (table.set (i32.const 1) (struct.new_default $t1)) + (table.set (i32.const 11) (struct.new_default $t1')) + (table.set (i32.const 2) (struct.new_default $t2)) + (table.set (i32.const 12) (struct.new_default $t2')) + (table.set (i32.const 3) (struct.new_default $t3)) + (table.set (i32.const 4) (struct.new_default $t4)) + ) + + (func (export "test-sub") + (call $init) + (block $l (result structref) + ;; must succeed + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (ref.null struct)))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 2))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 3))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 4))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t1) (ref.null struct)))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t1) (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t1) (table.get (i32.const 2))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t2) (ref.null struct)))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t2) (table.get (i32.const 2))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t3) (ref.null struct)))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t3) (table.get (i32.const 3))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t4) (ref.null struct)))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t4) (table.get (i32.const 4))))) + + ;; must not succeed + (br_on_cast $l anyref (ref $t1) (table.get (i32.const 0))) + (br_on_cast $l anyref (ref $t1) (table.get (i32.const 3))) + (br_on_cast $l anyref (ref $t1) (table.get (i32.const 4))) + + (br_on_cast $l anyref (ref $t2) (table.get (i32.const 0))) + (br_on_cast $l anyref (ref $t2) (table.get (i32.const 1))) + (br_on_cast $l anyref (ref $t2) (table.get (i32.const 3))) + (br_on_cast $l anyref (ref $t2) (table.get (i32.const 4))) + + (br_on_cast $l anyref (ref $t3) (table.get (i32.const 0))) + (br_on_cast $l anyref (ref $t3) (table.get (i32.const 1))) + (br_on_cast $l anyref (ref $t3) (table.get (i32.const 2))) + (br_on_cast $l anyref (ref $t3) (table.get (i32.const 4))) + + (br_on_cast $l anyref (ref $t4) (table.get (i32.const 0))) + (br_on_cast $l anyref (ref $t4) (table.get (i32.const 1))) + (br_on_cast $l anyref (ref $t4) (table.get (i32.const 2))) + (br_on_cast $l anyref (ref $t4) (table.get (i32.const 3))) + + (return) + ) + (unreachable) + ) + + (func (export "test-canon") + (call $init) + (block $l + (drop (block (result structref) (br_on_cast 0 structref (ref $t0') (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0') (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0') (table.get (i32.const 2))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0') (table.get (i32.const 3))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0') (table.get (i32.const 4))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 10))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 11))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t0) (table.get (i32.const 12))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t1') (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t1') (table.get (i32.const 2))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t1) (table.get (i32.const 11))))) + (drop (block (result structref) (br_on_cast 0 structref (ref $t1) (table.get (i32.const 12))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t2') (table.get (i32.const 2))))) + + (drop (block (result structref) (br_on_cast 0 structref (ref $t2) (table.get (i32.const 12))))) + + (return) + ) + (unreachable) + ) +) + +(invoke "test-sub") +(invoke "test-canon") + + +;; Cases of nullability + +(module + (type $t (struct)) + + (func (param (ref any)) (result (ref $t)) + (block (result (ref any)) (br_on_cast 1 (ref any) (ref $t) (local.get 0))) (unreachable) + ) + (func (param (ref null any)) (result (ref $t)) + (block (result (ref null any)) (br_on_cast 1 (ref null any) (ref $t) (local.get 0))) (unreachable) + ) + (func (param (ref null any)) (result (ref null $t)) + (block (result (ref null any)) (br_on_cast 1 (ref null any) (ref null $t) (local.get 0))) (unreachable) + ) +) + +(assert_invalid + (module + (type $t (struct)) + (func (param (ref any)) (result (ref $t)) + (block (result (ref any)) (br_on_cast 1 (ref null any) (ref null $t) (local.get 0))) (unreachable) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (struct)) + (func (param (ref any)) (result (ref null $t)) + (block (result (ref any)) (br_on_cast 1 (ref any) (ref null $t) (local.get 0))) (unreachable) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (struct)) + (func (param (ref null any)) (result (ref $t)) + (block (result (ref any)) (br_on_cast 1 (ref null any) (ref $t) (local.get 0))) (unreachable) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (result anyref) + (br_on_cast 0 eqref anyref (unreachable)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (result anyref) + (br_on_cast 0 structref arrayref (unreachable)) + ) + ) + "type mismatch" +) + + +;; https://github.com/WebAssembly/gc/issues/516 +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t)) (result funcref) (local.get 0)) + (func (param funcref) (result funcref funcref) + (ref.null $t) + (local.get 0) + (br_on_cast 0 funcref (ref $t)) ;; only leaves two funcref's on the stack + (drop) + (call $f) + (local.get 0) + ) + ) + "type mismatch" +) diff --git a/test/core/gc/br_on_cast_fail.wast b/test/core/gc/br_on_cast_fail.wast new file mode 100644 index 0000000000..db6db11b05 --- /dev/null +++ b/test/core/gc/br_on_cast_fail.wast @@ -0,0 +1,300 @@ +;; Abstract Types + +(module + (type $ft (func (result i32))) + (type $st (struct (field i16))) + (type $at (array i8)) + + (table 10 anyref) + + (elem declare func $f) + (func $f (result i32) (i32.const 9)) + + (func (export "init") (param $x externref) + (table.set (i32.const 0) (ref.null any)) + (table.set (i32.const 1) (ref.i31 (i32.const 7))) + (table.set (i32.const 2) (struct.new $st (i32.const 6))) + (table.set (i32.const 3) (array.new $at (i32.const 5) (i32.const 3))) + (table.set (i32.const 4) (any.convert_extern (local.get $x))) + ) + + (func (export "br_on_non_null") (param $i i32) (result i32) + (block $l (result (ref any)) + (br_on_non_null $l (table.get (local.get $i))) + (return (i32.const 0)) + ) + (return (i32.const -1)) + ) + (func (export "br_on_non_i31") (param $i i32) (result i32) + (block $l (result anyref) + (br_on_cast_fail $l anyref (ref i31) (table.get (local.get $i))) + (return (i31.get_u)) + ) + (return (i32.const -1)) + ) + (func (export "br_on_non_struct") (param $i i32) (result i32) + (block $l (result anyref) + (br_on_cast_fail $l anyref (ref struct) (table.get (local.get $i))) + (block $l2 (param structref) (result (ref $st)) + (block $l3 (param structref) (result (ref $at)) + (br_on_cast $l2 structref (ref $st)) + (br_on_cast $l3 anyref (ref $at)) + (return (i32.const -2)) + ) + (return (array.get_u $at (i32.const 0))) + ) + (return (struct.get_s $st 0)) + ) + (return (i32.const -1)) + ) + (func (export "br_on_non_array") (param $i i32) (result i32) + (block $l (result anyref) + (br_on_cast_fail $l anyref (ref array) (table.get (local.get $i))) + (return (array.len)) + ) + (return (i32.const -1)) + ) + + (func (export "null-diff") (param $i i32) (result i32) + (block $l (result (ref any)) + (block (result (ref null struct)) + (br_on_cast_fail $l (ref null any) (ref null struct) (table.get (local.get $i))) + ) + (return (i32.const 1)) + ) + (return (i32.const 0)) + ) +) + +(invoke "init" (ref.extern 0)) + +(assert_return (invoke "br_on_non_null" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "br_on_non_null" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_non_null" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_non_null" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_non_null" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_non_i31" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_non_i31" (i32.const 1)) (i32.const 7)) +(assert_return (invoke "br_on_non_i31" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_non_i31" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_non_i31" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_non_struct" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_non_struct" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_non_struct" (i32.const 2)) (i32.const 6)) +(assert_return (invoke "br_on_non_struct" (i32.const 3)) (i32.const -1)) +(assert_return (invoke "br_on_non_struct" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "br_on_non_array" (i32.const 0)) (i32.const -1)) +(assert_return (invoke "br_on_non_array" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "br_on_non_array" (i32.const 2)) (i32.const -1)) +(assert_return (invoke "br_on_non_array" (i32.const 3)) (i32.const 3)) +(assert_return (invoke "br_on_non_array" (i32.const 4)) (i32.const -1)) + +(assert_return (invoke "null-diff" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "null-diff" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "null-diff" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "null-diff" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "null-diff" (i32.const 4)) (i32.const 0)) + + +;; Concrete Types + +(module + (type $t0 (sub (struct))) + (type $t1 (sub $t0 (struct (field i32)))) + (type $t1' (sub $t0 (struct (field i32)))) + (type $t2 (sub $t1 (struct (field i32 i32)))) + (type $t2' (sub $t1' (struct (field i32 i32)))) + (type $t3 (sub $t0 (struct (field i32 i32)))) + (type $t0' (sub $t0 (struct))) + (type $t4 (sub $t0' (struct (field i32 i32)))) + + (table 20 structref) + + (func $init + (table.set (i32.const 0) (struct.new_default $t0)) + (table.set (i32.const 10) (struct.new_default $t0)) + (table.set (i32.const 1) (struct.new_default $t1)) + (table.set (i32.const 11) (struct.new_default $t1')) + (table.set (i32.const 2) (struct.new_default $t2)) + (table.set (i32.const 12) (struct.new_default $t2')) + (table.set (i32.const 3) (struct.new_default $t3 )) + (table.set (i32.const 4) (struct.new_default $t4)) + ) + + (func (export "test-sub") + (call $init) + (block $l (result structref) + ;; must not succeed + (br_on_cast_fail $l structref (ref null $t0) (ref.null struct)) + (br_on_cast_fail $l structref (ref null $t0) (table.get (i32.const 0))) + (br_on_cast_fail $l structref (ref null $t0) (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref null $t0) (table.get (i32.const 2))) + (br_on_cast_fail $l structref (ref null $t0) (table.get (i32.const 3))) + (br_on_cast_fail $l structref (ref null $t0) (table.get (i32.const 4))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 0))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 2))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 3))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 4))) + + (br_on_cast_fail $l structref (ref null $t1) (ref.null struct)) + (br_on_cast_fail $l structref (ref null $t1) (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref null $t1) (table.get (i32.const 2))) + (br_on_cast_fail $l structref (ref $t1) (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref $t1) (table.get (i32.const 2))) + + (br_on_cast_fail $l structref (ref null $t2) (ref.null struct)) + (br_on_cast_fail $l structref (ref null $t2) (table.get (i32.const 2))) + (br_on_cast_fail $l structref (ref $t2) (table.get (i32.const 2))) + + (br_on_cast_fail $l structref (ref null $t3) (ref.null struct)) + (br_on_cast_fail $l structref (ref null $t3) (table.get (i32.const 3))) + (br_on_cast_fail $l structref (ref $t3) (table.get (i32.const 3))) + + (br_on_cast_fail $l structref (ref null $t4) (ref.null struct)) + (br_on_cast_fail $l structref (ref null $t4) (table.get (i32.const 4))) + (br_on_cast_fail $l structref (ref $t4) (table.get (i32.const 4))) + + ;; must succeed + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t0) (ref.null struct)))) + + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t1) (ref.null struct)))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t1) (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t1) (table.get (i32.const 3))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t1) (table.get (i32.const 4))))) + + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t2) (ref.null struct)))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t2) (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t2) (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t2) (table.get (i32.const 3))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t2) (table.get (i32.const 4))))) + + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t3) (ref.null struct)))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t3) (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t3) (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t3) (table.get (i32.const 2))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t3) (table.get (i32.const 4))))) + + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t4) (ref.null struct)))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t4) (table.get (i32.const 0))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t4) (table.get (i32.const 1))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t4) (table.get (i32.const 2))))) + (drop (block (result structref) (br_on_cast_fail 0 structref (ref $t4) (table.get (i32.const 3))))) + + (return) + ) + (unreachable) + ) + + (func (export "test-canon") + (call $init) + (block $l (result structref) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 0))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 2))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 3))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 4))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 10))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 11))) + (br_on_cast_fail $l structref (ref $t0) (table.get (i32.const 12))) + + (br_on_cast_fail $l structref (ref $t1') (table.get (i32.const 1))) + (br_on_cast_fail $l structref (ref $t1') (table.get (i32.const 2))) + + (br_on_cast_fail $l structref (ref $t1) (table.get (i32.const 11))) + (br_on_cast_fail $l structref (ref $t1) (table.get (i32.const 12))) + + (br_on_cast_fail $l structref (ref $t2') (table.get (i32.const 2))) + + (br_on_cast_fail $l structref (ref $t2) (table.get (i32.const 12))) + + (return) + ) + (unreachable) + ) +) + +(invoke "test-sub") +(invoke "test-canon") + + +;; Cases of nullability + +(module + (type $t (struct)) + + (func (param (ref any)) (result (ref any)) + (block (result (ref $t)) (br_on_cast_fail 1 (ref any) (ref $t) (local.get 0))) + ) + (func (param (ref null any)) (result (ref null any)) + (block (result (ref $t)) (br_on_cast_fail 1 (ref null any) (ref $t) (local.get 0))) + ) + (func (param (ref null any)) (result (ref null any)) + (block (result (ref null $t)) (br_on_cast_fail 1 (ref null any) (ref null $t) (local.get 0))) + ) +) + +(assert_invalid + (module + (type $t (struct)) + (func (param (ref any)) (result (ref any)) + (block (result (ref $t)) (br_on_cast_fail 1 (ref null any) (ref null $t) (local.get 0))) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (struct)) + (func (param (ref any)) (result (ref any)) + (block (result (ref null $t)) (br_on_cast_fail 1 (ref any) (ref null $t) (local.get 0))) (ref.as_non_null) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (struct)) + (func (param (ref null any)) (result (ref any)) + (block (result (ref $t)) (br_on_cast_fail 1 (ref null any) (ref $t) (local.get 0))) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (result anyref) + (br_on_cast_fail 0 eqref anyref (unreachable)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (result anyref) + (br_on_cast_fail 0 structref arrayref (unreachable)) + ) + ) + "type mismatch" +) + + +;; https://github.com/WebAssembly/gc/issues/516 +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t)) (result funcref) (local.get 0)) + (func (param funcref) (result funcref funcref) + (ref.null $t) + (local.get 0) + (br_on_cast_fail 0 funcref (ref $t)) ;; only leaves two funcref's on the stack + (drop) + (call $f) + (local.get 0) + ) + ) + "type mismatch" +) diff --git a/test/core/gc/extern.wast b/test/core/gc/extern.wast new file mode 100644 index 0000000000..abf31669eb --- /dev/null +++ b/test/core/gc/extern.wast @@ -0,0 +1,54 @@ +(module + (type $ft (func)) + (type $st (struct)) + (type $at (array i8)) + + (table 10 anyref) + + (elem declare func $f) + (func $f) + + (func (export "init") (param $x externref) + (table.set (i32.const 0) (ref.null any)) + (table.set (i32.const 1) (ref.i31 (i32.const 7))) + (table.set (i32.const 2) (struct.new_default $st)) + (table.set (i32.const 3) (array.new_default $at (i32.const 0))) + (table.set (i32.const 4) (any.convert_extern (local.get $x))) + ) + + (func (export "internalize") (param externref) (result anyref) + (any.convert_extern (local.get 0)) + ) + (func (export "externalize") (param anyref) (result externref) + (extern.convert_any (local.get 0)) + ) + + (func (export "externalize-i") (param i32) (result externref) + (extern.convert_any (table.get (local.get 0))) + ) + (func (export "externalize-ii") (param i32) (result anyref) + (any.convert_extern (extern.convert_any (table.get (local.get 0)))) + ) +) + +(invoke "init" (ref.extern 0)) + +(assert_return (invoke "internalize" (ref.extern 1)) (ref.host 1)) +(assert_return (invoke "internalize" (ref.null extern)) (ref.null any)) + +(assert_return (invoke "externalize" (ref.host 2)) (ref.extern 2)) +(assert_return (invoke "externalize" (ref.null any)) (ref.null extern)) + +(assert_return (invoke "externalize-i" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "externalize-i" (i32.const 1)) (ref.extern)) +(assert_return (invoke "externalize-i" (i32.const 2)) (ref.extern)) +(assert_return (invoke "externalize-i" (i32.const 3)) (ref.extern)) +(assert_return (invoke "externalize-i" (i32.const 4)) (ref.extern)) +(assert_return (invoke "externalize-i" (i32.const 5)) (ref.null extern)) + +(assert_return (invoke "externalize-ii" (i32.const 0)) (ref.null any)) +(assert_return (invoke "externalize-ii" (i32.const 1)) (ref.i31)) +(assert_return (invoke "externalize-ii" (i32.const 2)) (ref.struct)) +(assert_return (invoke "externalize-ii" (i32.const 3)) (ref.array)) +(assert_return (invoke "externalize-ii" (i32.const 4)) (ref.host 0)) +(assert_return (invoke "externalize-ii" (i32.const 5)) (ref.null any)) diff --git a/test/core/gc/i31.wast b/test/core/gc/i31.wast new file mode 100644 index 0000000000..7485650454 --- /dev/null +++ b/test/core/gc/i31.wast @@ -0,0 +1,228 @@ +(module + (func (export "new") (param $i i32) (result (ref i31)) + (ref.i31 (local.get $i)) + ) + + (func (export "get_u") (param $i i32) (result i32) + (i31.get_u (ref.i31 (local.get $i))) + ) + (func (export "get_s") (param $i i32) (result i32) + (i31.get_s (ref.i31 (local.get $i))) + ) + + (func (export "get_u-null") (result i32) + (i31.get_u (ref.null i31)) + ) + (func (export "get_s-null") (result i32) + (i31.get_u (ref.null i31)) + ) + + (global $i (ref i31) (ref.i31 (i32.const 2))) + (global $m (mut (ref i31)) (ref.i31 (i32.const 3))) + + (func (export "get_globals") (result i32 i32) + (i31.get_u (global.get $i)) + (i31.get_u (global.get $m)) + ) + + (func (export "set_global") (param i32) + (global.set $m (ref.i31 (local.get 0))) + ) +) + +(assert_return (invoke "new" (i32.const 1)) (ref.i31)) + +(assert_return (invoke "get_u" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "get_u" (i32.const 100)) (i32.const 100)) +(assert_return (invoke "get_u" (i32.const -1)) (i32.const 0x7fff_ffff)) +(assert_return (invoke "get_u" (i32.const 0x3fff_ffff)) (i32.const 0x3fff_ffff)) +(assert_return (invoke "get_u" (i32.const 0x4000_0000)) (i32.const 0x4000_0000)) +(assert_return (invoke "get_u" (i32.const 0x7fff_ffff)) (i32.const 0x7fff_ffff)) +(assert_return (invoke "get_u" (i32.const 0xaaaa_aaaa)) (i32.const 0x2aaa_aaaa)) +(assert_return (invoke "get_u" (i32.const 0xcaaa_aaaa)) (i32.const 0x4aaa_aaaa)) + +(assert_return (invoke "get_s" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "get_s" (i32.const 100)) (i32.const 100)) +(assert_return (invoke "get_s" (i32.const -1)) (i32.const -1)) +(assert_return (invoke "get_s" (i32.const 0x3fff_ffff)) (i32.const 0x3fff_ffff)) +(assert_return (invoke "get_s" (i32.const 0x4000_0000)) (i32.const -0x4000_0000)) +(assert_return (invoke "get_s" (i32.const 0x7fff_ffff)) (i32.const -1)) +(assert_return (invoke "get_s" (i32.const 0xaaaa_aaaa)) (i32.const 0x2aaa_aaaa)) +(assert_return (invoke "get_s" (i32.const 0xcaaa_aaaa)) (i32.const 0xcaaa_aaaa)) + +(assert_trap (invoke "get_u-null") "null i31 reference") +(assert_trap (invoke "get_s-null") "null i31 reference") + +(assert_return (invoke "get_globals") (i32.const 2) (i32.const 3)) + +(invoke "set_global" (i32.const 1234)) +(assert_return (invoke "get_globals") (i32.const 2) (i32.const 1234)) + +(module $tables_of_i31ref + (table $table 3 10 i31ref) + (elem (table $table) (i32.const 0) i31ref (item (ref.i31 (i32.const 999))) + (item (ref.i31 (i32.const 888))) + (item (ref.i31 (i32.const 777)))) + + (func (export "size") (result i32) + table.size $table + ) + + (func (export "get") (param i32) (result i32) + (i31.get_u (table.get $table (local.get 0))) + ) + + (func (export "grow") (param i32 i32) (result i32) + (table.grow $table (ref.i31 (local.get 1)) (local.get 0)) + ) + + (func (export "fill") (param i32 i32 i32) + (table.fill $table (local.get 0) (ref.i31 (local.get 1)) (local.get 2)) + ) + + (func (export "copy") (param i32 i32 i32) + (table.copy $table $table (local.get 0) (local.get 1) (local.get 2)) + ) + + (elem $elem i31ref (item (ref.i31 (i32.const 123))) + (item (ref.i31 (i32.const 456))) + (item (ref.i31 (i32.const 789)))) + (func (export "init") (param i32 i32 i32) + (table.init $table $elem (local.get 0) (local.get 1) (local.get 2)) + ) +) + +;; Initial state. +(assert_return (invoke "size") (i32.const 3)) +(assert_return (invoke "get" (i32.const 0)) (i32.const 999)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 888)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 777)) + +;; Grow from size 3 to size 5. +(assert_return (invoke "grow" (i32.const 2) (i32.const 333)) (i32.const 3)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 333)) +(assert_return (invoke "get" (i32.const 4)) (i32.const 333)) + +;; Fill table[2..4] = 111. +(invoke "fill" (i32.const 2) (i32.const 111) (i32.const 2)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 111)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 111)) + +;; Copy from table[0..2] to table[3..5]. +(invoke "copy" (i32.const 3) (i32.const 0) (i32.const 2)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 999)) +(assert_return (invoke "get" (i32.const 4)) (i32.const 888)) + +;; Initialize the passive element at table[1..4]. +(invoke "init" (i32.const 1) (i32.const 0) (i32.const 3)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 123)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 456)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 789)) + +(module $env + (global (export "g") i32 (i32.const 42)) +) +(register "env") + +(module $i31ref_of_global_table_initializer + (global $g (import "env" "g") i32) + (table $t 3 3 (ref i31) (ref.i31 (global.get $g))) + (func (export "get") (param i32) (result i32) + (i31.get_u (local.get 0) (table.get $t)) + ) +) + +(assert_return (invoke "get" (i32.const 0)) (i32.const 42)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 42)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 42)) + +(module $i31ref_of_global_global_initializer + (global $g0 (import "env" "g") i32) + (global $g1 i31ref (ref.i31 (global.get $g0))) + (func (export "get") (result i32) + (i31.get_u (global.get $g1)) + ) +) + +(assert_return (invoke "get") (i32.const 42)) + +(module $anyref_global_of_i31ref + (global $c anyref (ref.i31 (i32.const 1234))) + (global $m (mut anyref) (ref.i31 (i32.const 5678))) + + (func (export "get_globals") (result i32 i32) + (i31.get_u (ref.cast i31ref (global.get $c))) + (i31.get_u (ref.cast i31ref (global.get $m))) + ) + + (func (export "set_global") (param i32) + (global.set $m (ref.i31 (local.get 0))) + ) +) + +(assert_return (invoke "get_globals") (i32.const 1234) (i32.const 5678)) +(invoke "set_global" (i32.const 0)) +(assert_return (invoke "get_globals") (i32.const 1234) (i32.const 0)) + +(module $anyref_table_of_i31ref + (table $table 3 10 anyref) + (elem (table $table) (i32.const 0) i31ref (item (ref.i31 (i32.const 999))) + (item (ref.i31 (i32.const 888))) + (item (ref.i31 (i32.const 777)))) + + (func (export "size") (result i32) + table.size $table + ) + + (func (export "get") (param i32) (result i32) + (i31.get_u (ref.cast i31ref (table.get $table (local.get 0)))) + ) + + (func (export "grow") (param i32 i32) (result i32) + (table.grow $table (ref.i31 (local.get 1)) (local.get 0)) + ) + + (func (export "fill") (param i32 i32 i32) + (table.fill $table (local.get 0) (ref.i31 (local.get 1)) (local.get 2)) + ) + + (func (export "copy") (param i32 i32 i32) + (table.copy $table $table (local.get 0) (local.get 1) (local.get 2)) + ) + + (elem $elem i31ref (item (ref.i31 (i32.const 123))) + (item (ref.i31 (i32.const 456))) + (item (ref.i31 (i32.const 789)))) + (func (export "init") (param i32 i32 i32) + (table.init $table $elem (local.get 0) (local.get 1) (local.get 2)) + ) +) + +;; Initial state. +(assert_return (invoke "size") (i32.const 3)) +(assert_return (invoke "get" (i32.const 0)) (i32.const 999)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 888)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 777)) + +;; Grow from size 3 to size 5. +(assert_return (invoke "grow" (i32.const 2) (i32.const 333)) (i32.const 3)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 333)) +(assert_return (invoke "get" (i32.const 4)) (i32.const 333)) + +;; Fill table[2..4] = 111. +(invoke "fill" (i32.const 2) (i32.const 111) (i32.const 2)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 111)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 111)) + +;; Copy from table[0..2] to table[3..5]. +(invoke "copy" (i32.const 3) (i32.const 0) (i32.const 2)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 999)) +(assert_return (invoke "get" (i32.const 4)) (i32.const 888)) + +;; Initialize the passive element at table[1..4]. +(invoke "init" (i32.const 1) (i32.const 0) (i32.const 3)) +(assert_return (invoke "get" (i32.const 1)) (i32.const 123)) +(assert_return (invoke "get" (i32.const 2)) (i32.const 456)) +(assert_return (invoke "get" (i32.const 3)) (i32.const 789)) diff --git a/test/core/gc/ref_cast.wast b/test/core/gc/ref_cast.wast new file mode 100644 index 0000000000..8e35431193 --- /dev/null +++ b/test/core/gc/ref_cast.wast @@ -0,0 +1,186 @@ +;; Abstract Types + +(module + (type $ft (func)) + (type $st (struct)) + (type $at (array i8)) + + (table 10 anyref) + + (elem declare func $f) + (func $f) + + (func (export "init") (param $x externref) + (table.set (i32.const 0) (ref.null any)) + (table.set (i32.const 1) (ref.i31 (i32.const 7))) + (table.set (i32.const 2) (struct.new_default $st)) + (table.set (i32.const 3) (array.new_default $at (i32.const 0))) + (table.set (i32.const 4) (any.convert_extern (local.get $x))) + (table.set (i32.const 5) (ref.null i31)) + (table.set (i32.const 6) (ref.null struct)) + (table.set (i32.const 7) (ref.null none)) + ) + + (func (export "ref_cast_non_null") (param $i i32) + (drop (ref.as_non_null (table.get (local.get $i)))) + (drop (ref.cast (ref null any) (table.get (local.get $i)))) + ) + (func (export "ref_cast_null") (param $i i32) + (drop (ref.cast anyref (table.get (local.get $i)))) + (drop (ref.cast structref (table.get (local.get $i)))) + (drop (ref.cast arrayref (table.get (local.get $i)))) + (drop (ref.cast i31ref (table.get (local.get $i)))) + (drop (ref.cast nullref (table.get (local.get $i)))) + ) + (func (export "ref_cast_i31") (param $i i32) + (drop (ref.cast (ref i31) (table.get (local.get $i)))) + (drop (ref.cast i31ref (table.get (local.get $i)))) + ) + (func (export "ref_cast_struct") (param $i i32) + (drop (ref.cast (ref struct) (table.get (local.get $i)))) + (drop (ref.cast structref (table.get (local.get $i)))) + ) + (func (export "ref_cast_array") (param $i i32) + (drop (ref.cast (ref array) (table.get (local.get $i)))) + (drop (ref.cast arrayref (table.get (local.get $i)))) + ) +) + +(invoke "init" (ref.extern 0)) + +(assert_trap (invoke "ref_cast_non_null" (i32.const 0)) "null reference") +(assert_return (invoke "ref_cast_non_null" (i32.const 1))) +(assert_return (invoke "ref_cast_non_null" (i32.const 2))) +(assert_return (invoke "ref_cast_non_null" (i32.const 3))) +(assert_return (invoke "ref_cast_non_null" (i32.const 4))) +(assert_trap (invoke "ref_cast_non_null" (i32.const 5)) "null reference") +(assert_trap (invoke "ref_cast_non_null" (i32.const 6)) "null reference") +(assert_trap (invoke "ref_cast_non_null" (i32.const 7)) "null reference") + +(assert_return (invoke "ref_cast_null" (i32.const 0))) +(assert_trap (invoke "ref_cast_null" (i32.const 1)) "cast failure") +(assert_trap (invoke "ref_cast_null" (i32.const 2)) "cast failure") +(assert_trap (invoke "ref_cast_null" (i32.const 3)) "cast failure") +(assert_trap (invoke "ref_cast_null" (i32.const 4)) "cast failure") +(assert_return (invoke "ref_cast_null" (i32.const 5))) +(assert_return (invoke "ref_cast_null" (i32.const 6))) +(assert_return (invoke "ref_cast_null" (i32.const 7))) + +(assert_trap (invoke "ref_cast_i31" (i32.const 0)) "cast failure") +(assert_return (invoke "ref_cast_i31" (i32.const 1))) +(assert_trap (invoke "ref_cast_i31" (i32.const 2)) "cast failure") +(assert_trap (invoke "ref_cast_i31" (i32.const 3)) "cast failure") +(assert_trap (invoke "ref_cast_i31" (i32.const 4)) "cast failure") +(assert_trap (invoke "ref_cast_i31" (i32.const 5)) "cast failure") +(assert_trap (invoke "ref_cast_i31" (i32.const 6)) "cast failure") +(assert_trap (invoke "ref_cast_i31" (i32.const 7)) "cast failure") + +(assert_trap (invoke "ref_cast_struct" (i32.const 0)) "cast failure") +(assert_trap (invoke "ref_cast_struct" (i32.const 1)) "cast failure") +(assert_return (invoke "ref_cast_struct" (i32.const 2))) +(assert_trap (invoke "ref_cast_struct" (i32.const 3)) "cast failure") +(assert_trap (invoke "ref_cast_struct" (i32.const 4)) "cast failure") +(assert_trap (invoke "ref_cast_struct" (i32.const 5)) "cast failure") +(assert_trap (invoke "ref_cast_struct" (i32.const 6)) "cast failure") +(assert_trap (invoke "ref_cast_struct" (i32.const 7)) "cast failure") + +(assert_trap (invoke "ref_cast_array" (i32.const 0)) "cast failure") +(assert_trap (invoke "ref_cast_array" (i32.const 1)) "cast failure") +(assert_trap (invoke "ref_cast_array" (i32.const 2)) "cast failure") +(assert_return (invoke "ref_cast_array" (i32.const 3))) +(assert_trap (invoke "ref_cast_array" (i32.const 4)) "cast failure") +(assert_trap (invoke "ref_cast_array" (i32.const 5)) "cast failure") +(assert_trap (invoke "ref_cast_array" (i32.const 6)) "cast failure") +(assert_trap (invoke "ref_cast_array" (i32.const 7)) "cast failure") + + +;; Concrete Types + +(module + (type $t0 (sub (struct))) + (type $t1 (sub $t0 (struct (field i32)))) + (type $t1' (sub $t0 (struct (field i32)))) + (type $t2 (sub $t1 (struct (field i32 i32)))) + (type $t2' (sub $t1' (struct (field i32 i32)))) + (type $t3 (sub $t0 (struct (field i32 i32)))) + (type $t0' (sub $t0 (struct))) + (type $t4 (sub $t0' (struct (field i32 i32)))) + + (table 20 (ref null struct)) + + (func $init + (table.set (i32.const 0) (struct.new_default $t0)) + (table.set (i32.const 10) (struct.new_default $t0)) + (table.set (i32.const 1) (struct.new_default $t1)) + (table.set (i32.const 11) (struct.new_default $t1')) + (table.set (i32.const 2) (struct.new_default $t2)) + (table.set (i32.const 12) (struct.new_default $t2')) + (table.set (i32.const 3) (struct.new_default $t3)) + (table.set (i32.const 4) (struct.new_default $t4)) + ) + + (func (export "test-sub") + (call $init) + + (drop (ref.cast (ref null $t0) (ref.null struct))) + (drop (ref.cast (ref null $t0) (table.get (i32.const 0)))) + (drop (ref.cast (ref null $t0) (table.get (i32.const 1)))) + (drop (ref.cast (ref null $t0) (table.get (i32.const 2)))) + (drop (ref.cast (ref null $t0) (table.get (i32.const 3)))) + (drop (ref.cast (ref null $t0) (table.get (i32.const 4)))) + + (drop (ref.cast (ref null $t0) (ref.null struct))) + (drop (ref.cast (ref null $t1) (table.get (i32.const 1)))) + (drop (ref.cast (ref null $t1) (table.get (i32.const 2)))) + + (drop (ref.cast (ref null $t0) (ref.null struct))) + (drop (ref.cast (ref null $t2) (table.get (i32.const 2)))) + + (drop (ref.cast (ref null $t0) (ref.null struct))) + (drop (ref.cast (ref null $t3) (table.get (i32.const 3)))) + + (drop (ref.cast (ref null $t4) (table.get (i32.const 4)))) + + (drop (ref.cast (ref $t0) (table.get (i32.const 0)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 1)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 2)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 3)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 4)))) + + (drop (ref.cast (ref $t1) (table.get (i32.const 1)))) + (drop (ref.cast (ref $t1) (table.get (i32.const 2)))) + + (drop (ref.cast (ref $t2) (table.get (i32.const 2)))) + + (drop (ref.cast (ref $t3) (table.get (i32.const 3)))) + + (drop (ref.cast (ref $t4) (table.get (i32.const 4)))) + ) + + (func (export "test-canon") + (call $init) + + (drop (ref.cast (ref $t0) (table.get (i32.const 0)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 1)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 2)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 3)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 4)))) + + (drop (ref.cast (ref $t0) (table.get (i32.const 10)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 11)))) + (drop (ref.cast (ref $t0) (table.get (i32.const 12)))) + + (drop (ref.cast (ref $t1') (table.get (i32.const 1)))) + (drop (ref.cast (ref $t1') (table.get (i32.const 2)))) + + (drop (ref.cast (ref $t1) (table.get (i32.const 11)))) + (drop (ref.cast (ref $t1) (table.get (i32.const 12)))) + + (drop (ref.cast (ref $t2') (table.get (i32.const 2)))) + + (drop (ref.cast (ref $t2) (table.get (i32.const 12)))) + ) +) + +(invoke "test-sub") +(invoke "test-canon") diff --git a/test/core/gc/ref_eq.wast b/test/core/gc/ref_eq.wast new file mode 100644 index 0000000000..001efd69f8 --- /dev/null +++ b/test/core/gc/ref_eq.wast @@ -0,0 +1,168 @@ +(module + (type $st (sub (struct))) + (type $st' (sub (struct (field i32)))) + (type $at (array i8)) + (type $st-sub1 (sub $st (struct))) + (type $st-sub2 (sub $st (struct))) + (type $st'-sub1 (sub $st' (struct (field i32)))) + (type $st'-sub2 (sub $st' (struct (field i32)))) + + (table 20 (ref null eq)) + + (func (export "init") + (table.set (i32.const 0) (ref.null eq)) + (table.set (i32.const 1) (ref.null i31)) + (table.set (i32.const 2) (ref.i31 (i32.const 7))) + (table.set (i32.const 3) (ref.i31 (i32.const 7))) + (table.set (i32.const 4) (ref.i31 (i32.const 8))) + (table.set (i32.const 5) (struct.new_default $st)) + (table.set (i32.const 6) (struct.new_default $st)) + (table.set (i32.const 7) (array.new_default $at (i32.const 0))) + (table.set (i32.const 8) (array.new_default $at (i32.const 0))) + ) + + (func (export "eq") (param $i i32) (param $j i32) (result i32) + (ref.eq (table.get (local.get $i)) (table.get (local.get $j))) + ) +) + +(invoke "init") + +(assert_return (invoke "eq" (i32.const 0) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 0) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 1) (i32.const 0)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 1)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 1) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 2) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 3)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 2) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 3) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 2)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 3)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 3) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 4) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 4)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 4) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 5) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 5)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 5) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 6) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 6)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 6) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 7) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 7)) (i32.const 1)) +(assert_return (invoke "eq" (i32.const 7) (i32.const 8)) (i32.const 0)) + +(assert_return (invoke "eq" (i32.const 8) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 2)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 3)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 4)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 5)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 6)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 7)) (i32.const 0)) +(assert_return (invoke "eq" (i32.const 8) (i32.const 8)) (i32.const 1)) + +(assert_invalid + (module + (func (export "eq") (param $r (ref any)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (export "eq") (param $r (ref null any)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (export "eq") (param $r (ref func)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (export "eq") (param $r (ref null func)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (export "eq") (param $r (ref extern)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (func (export "eq") (param $r (ref null extern)) (result i32) + (ref.eq (local.get $r) (local.get $r)) + ) + ) + "type mismatch" +) diff --git a/test/core/gc/ref_test.wast b/test/core/gc/ref_test.wast new file mode 100644 index 0000000000..590b81b8a4 --- /dev/null +++ b/test/core/gc/ref_test.wast @@ -0,0 +1,330 @@ +;; Abstract Types + +(module + (type $ft (func)) + (type $st (struct)) + (type $at (array i8)) + + (table $ta 10 anyref) + (table $tf 10 funcref) + (table $te 10 externref) + + (elem declare func $f) + (func $f) + + (func (export "init") (param $x externref) + (table.set $ta (i32.const 0) (ref.null any)) + (table.set $ta (i32.const 1) (ref.null struct)) + (table.set $ta (i32.const 2) (ref.null none)) + (table.set $ta (i32.const 3) (ref.i31 (i32.const 7))) + (table.set $ta (i32.const 4) (struct.new_default $st)) + (table.set $ta (i32.const 5) (array.new_default $at (i32.const 0))) + (table.set $ta (i32.const 6) (any.convert_extern (local.get $x))) + (table.set $ta (i32.const 7) (any.convert_extern (ref.null extern))) + + (table.set $tf (i32.const 0) (ref.null nofunc)) + (table.set $tf (i32.const 1) (ref.null func)) + (table.set $tf (i32.const 2) (ref.func $f)) + + (table.set $te (i32.const 0) (ref.null noextern)) + (table.set $te (i32.const 1) (ref.null extern)) + (table.set $te (i32.const 2) (local.get $x)) + (table.set $te (i32.const 3) (extern.convert_any (ref.i31 (i32.const 8)))) + (table.set $te (i32.const 4) (extern.convert_any (struct.new_default $st))) + (table.set $te (i32.const 5) (extern.convert_any (ref.null any))) + ) + + (func (export "ref_test_null_data") (param $i i32) (result i32) + (i32.add + (ref.is_null (table.get $ta (local.get $i))) + (ref.test nullref (table.get $ta (local.get $i))) + ) + ) + (func (export "ref_test_any") (param $i i32) (result i32) + (i32.add + (ref.test (ref any) (table.get $ta (local.get $i))) + (ref.test anyref (table.get $ta (local.get $i))) + ) + ) + (func (export "ref_test_eq") (param $i i32) (result i32) + (i32.add + (ref.test (ref eq) (table.get $ta (local.get $i))) + (ref.test eqref (table.get $ta (local.get $i))) + ) + ) + (func (export "ref_test_i31") (param $i i32) (result i32) + (i32.add + (ref.test (ref i31) (table.get $ta (local.get $i))) + (ref.test i31ref (table.get $ta (local.get $i))) + ) + ) + (func (export "ref_test_struct") (param $i i32) (result i32) + (i32.add + (ref.test (ref struct) (table.get $ta (local.get $i))) + (ref.test structref (table.get $ta (local.get $i))) + ) + ) + (func (export "ref_test_array") (param $i i32) (result i32) + (i32.add + (ref.test (ref array) (table.get $ta (local.get $i))) + (ref.test arrayref (table.get $ta (local.get $i))) + ) + ) + + (func (export "ref_test_null_func") (param $i i32) (result i32) + (i32.add + (ref.is_null (table.get $tf (local.get $i))) + (ref.test (ref null nofunc) (table.get $tf (local.get $i))) + ) + ) + (func (export "ref_test_func") (param $i i32) (result i32) + (i32.add + (ref.test (ref func) (table.get $tf (local.get $i))) + (ref.test funcref (table.get $tf (local.get $i))) + ) + ) + + (func (export "ref_test_null_extern") (param $i i32) (result i32) + (i32.add + (ref.is_null (table.get $te (local.get $i))) + (ref.test (ref null noextern) (table.get $te (local.get $i))) + ) + ) + (func (export "ref_test_extern") (param $i i32) (result i32) + (i32.add + (ref.test (ref extern) (table.get $te (local.get $i))) + (ref.test externref (table.get $te (local.get $i))) + ) + ) +) + +(invoke "init" (ref.extern 0)) + +(assert_return (invoke "ref_test_null_data" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "ref_test_null_data" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "ref_test_null_data" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "ref_test_null_data" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "ref_test_null_data" (i32.const 4)) (i32.const 0)) +(assert_return (invoke "ref_test_null_data" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "ref_test_null_data" (i32.const 6)) (i32.const 0)) +(assert_return (invoke "ref_test_null_data" (i32.const 7)) (i32.const 2)) + +(assert_return (invoke "ref_test_any" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_any" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_any" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "ref_test_any" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "ref_test_any" (i32.const 4)) (i32.const 2)) +(assert_return (invoke "ref_test_any" (i32.const 5)) (i32.const 2)) +(assert_return (invoke "ref_test_any" (i32.const 6)) (i32.const 2)) +(assert_return (invoke "ref_test_any" (i32.const 7)) (i32.const 1)) + +(assert_return (invoke "ref_test_eq" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_eq" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_eq" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "ref_test_eq" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "ref_test_eq" (i32.const 4)) (i32.const 2)) +(assert_return (invoke "ref_test_eq" (i32.const 5)) (i32.const 2)) +(assert_return (invoke "ref_test_eq" (i32.const 6)) (i32.const 0)) +(assert_return (invoke "ref_test_eq" (i32.const 7)) (i32.const 1)) + +(assert_return (invoke "ref_test_i31" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_i31" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_i31" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "ref_test_i31" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "ref_test_i31" (i32.const 4)) (i32.const 0)) +(assert_return (invoke "ref_test_i31" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "ref_test_i31" (i32.const 6)) (i32.const 0)) +(assert_return (invoke "ref_test_i31" (i32.const 7)) (i32.const 1)) + +(assert_return (invoke "ref_test_struct" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_struct" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_struct" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "ref_test_struct" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "ref_test_struct" (i32.const 4)) (i32.const 2)) +(assert_return (invoke "ref_test_struct" (i32.const 5)) (i32.const 0)) +(assert_return (invoke "ref_test_struct" (i32.const 6)) (i32.const 0)) +(assert_return (invoke "ref_test_struct" (i32.const 7)) (i32.const 1)) + +(assert_return (invoke "ref_test_array" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_array" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_array" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "ref_test_array" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "ref_test_array" (i32.const 4)) (i32.const 0)) +(assert_return (invoke "ref_test_array" (i32.const 5)) (i32.const 2)) +(assert_return (invoke "ref_test_array" (i32.const 6)) (i32.const 0)) +(assert_return (invoke "ref_test_array" (i32.const 7)) (i32.const 1)) + +(assert_return (invoke "ref_test_null_func" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "ref_test_null_func" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "ref_test_null_func" (i32.const 2)) (i32.const 0)) + +(assert_return (invoke "ref_test_func" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_func" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_func" (i32.const 2)) (i32.const 2)) + +(assert_return (invoke "ref_test_null_extern" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "ref_test_null_extern" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "ref_test_null_extern" (i32.const 2)) (i32.const 0)) +(assert_return (invoke "ref_test_null_extern" (i32.const 3)) (i32.const 0)) +(assert_return (invoke "ref_test_null_extern" (i32.const 4)) (i32.const 0)) +(assert_return (invoke "ref_test_null_extern" (i32.const 5)) (i32.const 2)) + +(assert_return (invoke "ref_test_extern" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref_test_extern" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref_test_extern" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "ref_test_extern" (i32.const 3)) (i32.const 2)) +(assert_return (invoke "ref_test_extern" (i32.const 4)) (i32.const 2)) +(assert_return (invoke "ref_test_extern" (i32.const 5)) (i32.const 1)) + + +;; Concrete Types + +(module + (type $t0 (sub (struct))) + (type $t1 (sub $t0 (struct (field i32)))) + (type $t1' (sub $t0 (struct (field i32)))) + (type $t2 (sub $t1 (struct (field i32 i32)))) + (type $t2' (sub $t1' (struct (field i32 i32)))) + (type $t3 (sub $t0 (struct (field i32 i32)))) + (type $t0' (sub $t0 (struct))) + (type $t4 (sub $t0' (struct (field i32 i32)))) + + (table 20 (ref null struct)) + + (func $init + (table.set (i32.const 0) (struct.new_default $t0)) + (table.set (i32.const 10) (struct.new_default $t0)) + (table.set (i32.const 1) (struct.new_default $t1)) + (table.set (i32.const 11) (struct.new_default $t1')) + (table.set (i32.const 2) (struct.new_default $t2)) + (table.set (i32.const 12) (struct.new_default $t2')) + (table.set (i32.const 3) (struct.new_default $t3)) + (table.set (i32.const 4) (struct.new_default $t4)) + ) + + (func (export "test-sub") + (call $init) + (block $l + ;; must hold + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null struct)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null $t0)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null $t1)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null $t2)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null $t3)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (ref.null $t4)))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (table.get (i32.const 0))))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (table.get (i32.const 2))))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (table.get (i32.const 3))))) + (br_if $l (i32.eqz (ref.test (ref null $t0) (table.get (i32.const 4))))) + + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null struct)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null $t0)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null $t1)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null $t2)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null $t3)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (ref.null $t4)))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref null $t1) (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null struct)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null $t0)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null $t1)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null $t2)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null $t3)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (ref.null $t4)))) + (br_if $l (i32.eqz (ref.test (ref null $t2) (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null struct)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null $t0)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null $t1)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null $t2)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null $t3)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (ref.null $t4)))) + (br_if $l (i32.eqz (ref.test (ref null $t3) (table.get (i32.const 3))))) + + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null struct)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null $t0)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null $t1)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null $t2)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null $t3)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (ref.null $t4)))) + (br_if $l (i32.eqz (ref.test (ref null $t4) (table.get (i32.const 4))))) + + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 0))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 2))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 3))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 4))))) + + (br_if $l (i32.eqz (ref.test (ref $t1) (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref $t1) (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref $t2) (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref $t3) (table.get (i32.const 3))))) + + (br_if $l (i32.eqz (ref.test (ref $t4) (table.get (i32.const 4))))) + + ;; must not hold + (br_if $l (ref.test (ref $t0) (ref.null struct))) + (br_if $l (ref.test (ref $t1) (ref.null struct))) + (br_if $l (ref.test (ref $t2) (ref.null struct))) + (br_if $l (ref.test (ref $t3) (ref.null struct))) + (br_if $l (ref.test (ref $t4) (ref.null struct))) + + (br_if $l (ref.test (ref $t1) (table.get (i32.const 0)))) + (br_if $l (ref.test (ref $t1) (table.get (i32.const 3)))) + (br_if $l (ref.test (ref $t1) (table.get (i32.const 4)))) + + (br_if $l (ref.test (ref $t2) (table.get (i32.const 0)))) + (br_if $l (ref.test (ref $t2) (table.get (i32.const 1)))) + (br_if $l (ref.test (ref $t2) (table.get (i32.const 3)))) + (br_if $l (ref.test (ref $t2) (table.get (i32.const 4)))) + + (br_if $l (ref.test (ref $t3) (table.get (i32.const 0)))) + (br_if $l (ref.test (ref $t3) (table.get (i32.const 1)))) + (br_if $l (ref.test (ref $t3) (table.get (i32.const 2)))) + (br_if $l (ref.test (ref $t3) (table.get (i32.const 4)))) + + (br_if $l (ref.test (ref $t4) (table.get (i32.const 0)))) + (br_if $l (ref.test (ref $t4) (table.get (i32.const 1)))) + (br_if $l (ref.test (ref $t4) (table.get (i32.const 2)))) + (br_if $l (ref.test (ref $t4) (table.get (i32.const 3)))) + + (return) + ) + (unreachable) + ) + + (func (export "test-canon") + (call $init) + (block $l + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 0))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 2))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 3))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 4))))) + + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 10))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 11))))) + (br_if $l (i32.eqz (ref.test (ref $t0) (table.get (i32.const 12))))) + + (br_if $l (i32.eqz (ref.test (ref $t1') (table.get (i32.const 1))))) + (br_if $l (i32.eqz (ref.test (ref $t1') (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref $t1) (table.get (i32.const 11))))) + (br_if $l (i32.eqz (ref.test (ref $t1) (table.get (i32.const 12))))) + + (br_if $l (i32.eqz (ref.test (ref $t2') (table.get (i32.const 2))))) + + (br_if $l (i32.eqz (ref.test (ref $t2) (table.get (i32.const 12))))) + + (return) + ) + (unreachable) + ) +) + +(assert_return (invoke "test-sub")) +(assert_return (invoke "test-canon")) diff --git a/test/core/gc/struct.wast b/test/core/gc/struct.wast new file mode 100644 index 0000000000..6151fe100f --- /dev/null +++ b/test/core/gc/struct.wast @@ -0,0 +1,229 @@ +;; Type syntax + +(module + (type (struct)) + (type (struct (field))) + (type (struct (field i8))) + (type (struct (field i8 i8 i8 i8))) + (type (struct (field $x1 i32) (field $y1 i32))) + (type (struct (field i8 i16 i32 i64 f32 f64 anyref funcref (ref 0) (ref null 1)))) + (type (struct (field i32 i64 i8) (field) (field) (field (ref null i31) anyref))) + (type (struct (field $x2 i32) (field f32 f64) (field $y2 i32))) +) + + +(assert_malformed + (module quote + "(type (struct (field $x i32) (field $x i32)))" + ) + "duplicate field" +) + + +;; Binding structure + +(module + (rec + (type $s0 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + (type $s1 (struct (field (ref 0) (ref 1) (ref $s0) (ref $s1)))) + ) + + (func (param (ref $forward))) + + (type $forward (struct)) +) + +(assert_invalid + (module (type (struct (field (ref 1))))) + "unknown type" +) +(assert_invalid + (module (type (struct (field (mut (ref 1)))))) + "unknown type" +) + + +;; Field names + +(module + (type (struct (field $x i32))) + (type $t1 (struct (field i32) (field $x f32))) + (type $t2 (struct (field i32 i32) (field $x i64))) + + (func (param (ref 0)) (result i32) (struct.get 0 $x (local.get 0))) + (func (param (ref $t1)) (result f32) (struct.get 1 $x (local.get 0))) + (func (param (ref $t2)) (result i64) (struct.get $t2 $x (local.get 0))) +) + +(assert_invalid + (module + (type (struct (field $x i64))) + (type $t (struct (field $x i32))) + (func (param (ref 0)) (result i32) (struct.get 0 $x (local.get 0))) + ) + "type mismatch" +) + + +;; Basic instructions + +(module + (type $vec (struct (field f32) (field $y (mut f32)) (field $z f32))) + + (global (ref $vec) (struct.new $vec (f32.const 1) (f32.const 2) (f32.const 3))) + (global (ref $vec) (struct.new_default $vec)) + + (func (export "new") (result anyref) + (struct.new_default $vec) + ) + + (func $get_0_0 (param $v (ref $vec)) (result f32) + (struct.get 0 0 (local.get $v)) + ) + (func (export "get_0_0") (result f32) + (call $get_0_0 (struct.new_default $vec)) + ) + (func $get_vec_0 (param $v (ref $vec)) (result f32) + (struct.get $vec 0 (local.get $v)) + ) + (func (export "get_vec_0") (result f32) + (call $get_vec_0 (struct.new_default $vec)) + ) + (func $get_0_y (param $v (ref $vec)) (result f32) + (struct.get 0 $y (local.get $v)) + ) + (func (export "get_0_y") (result f32) + (call $get_0_y (struct.new_default $vec)) + ) + (func $get_vec_y (param $v (ref $vec)) (result f32) + (struct.get $vec $y (local.get $v)) + ) + (func (export "get_vec_y") (result f32) + (call $get_vec_y (struct.new_default $vec)) + ) + + (func $set_get_y (param $v (ref $vec)) (param $y f32) (result f32) + (struct.set $vec $y (local.get $v) (local.get $y)) + (struct.get $vec $y (local.get $v)) + ) + (func (export "set_get_y") (param $y f32) (result f32) + (call $set_get_y (struct.new_default $vec) (local.get $y)) + ) + + (func $set_get_1 (param $v (ref $vec)) (param $y f32) (result f32) + (struct.set $vec 1 (local.get $v) (local.get $y)) + (struct.get $vec $y (local.get $v)) + ) + (func (export "set_get_1") (param $y f32) (result f32) + (call $set_get_1 (struct.new_default $vec) (local.get $y)) + ) +) + +(assert_return (invoke "new") (ref.struct)) + +(assert_return (invoke "get_0_0") (f32.const 0)) +(assert_return (invoke "get_vec_0") (f32.const 0)) +(assert_return (invoke "get_0_y") (f32.const 0)) +(assert_return (invoke "get_vec_y") (f32.const 0)) + +(assert_return (invoke "set_get_y" (f32.const 7)) (f32.const 7)) +(assert_return (invoke "set_get_1" (f32.const 7)) (f32.const 7)) + +(assert_invalid + (module + (type $s (struct (field i64))) + (func (export "struct.set-immutable") (param $s (ref $s)) + (struct.set $s 0 (local.get $s) (i64.const 1)) + ) + ) + "field is immutable" +) + + +;; Null dereference + +(module + (type $t (struct (field i32 (mut i32)))) + (func (export "struct.get-null") + (local (ref null $t)) (drop (struct.get $t 1 (local.get 0))) + ) + (func (export "struct.set-null") + (local (ref null $t)) (struct.set $t 1 (local.get 0) (i32.const 0)) + ) +) + +(assert_trap (invoke "struct.get-null") "null structure reference") +(assert_trap (invoke "struct.set-null") "null structure reference") + +;; Packed field instructions + +(module + (type $s (struct (field i8) (field (mut i8)) (field i16) (field (mut i16)))) + + (global (export "g0") (ref $s) (struct.new $s (i32.const 0) (i32.const 1) (i32.const 2) (i32.const 3))) + (global (export "g1") (ref $s) (struct.new $s (i32.const 254) (i32.const 255) (i32.const 65534) (i32.const 65535))) + + (func (export "get_packed_g0_0") (result i32 i32) + (struct.get_s 0 0 (global.get 0)) + (struct.get_u 0 0 (global.get 0)) + ) + + (func (export "get_packed_g1_0") (result i32 i32) + (struct.get_s 0 0 (global.get 1)) + (struct.get_u 0 0 (global.get 1)) + ) + + (func (export "get_packed_g0_1") (result i32 i32) + (struct.get_s 0 1 (global.get 0)) + (struct.get_u 0 1 (global.get 0)) + ) + + (func (export "get_packed_g1_1") (result i32 i32) + (struct.get_s 0 1 (global.get 1)) + (struct.get_u 0 1 (global.get 1)) + ) + + (func (export "get_packed_g0_2") (result i32 i32) + (struct.get_s 0 2 (global.get 0)) + (struct.get_u 0 2 (global.get 0)) + ) + + (func (export "get_packed_g1_2") (result i32 i32) + (struct.get_s 0 2 (global.get 1)) + (struct.get_u 0 2 (global.get 1)) + ) + + (func (export "get_packed_g0_3") (result i32 i32) + (struct.get_s 0 3 (global.get 0)) + (struct.get_u 0 3 (global.get 0)) + ) + + (func (export "get_packed_g1_3") (result i32 i32) + (struct.get_s 0 3 (global.get 1)) + (struct.get_u 0 3 (global.get 1)) + ) + + (func (export "set_get_packed_g0_1") (param i32) (result i32 i32) + (struct.set 0 1 (global.get 0) (local.get 0)) + (struct.get_s 0 1 (global.get 0)) + (struct.get_u 0 1 (global.get 0)) + ) + + (func (export "set_get_packed_g0_3") (param i32) (result i32 i32) + (struct.set 0 3 (global.get 0) (local.get 0)) + (struct.get_s 0 3 (global.get 0)) + (struct.get_u 0 3 (global.get 0)) + ) +) + +(assert_return (invoke "get_packed_g0_0") (i32.const 0) (i32.const 0)) +(assert_return (invoke "get_packed_g1_0") (i32.const -2) (i32.const 254)) +(assert_return (invoke "get_packed_g0_1") (i32.const 1) (i32.const 1)) +(assert_return (invoke "get_packed_g1_1") (i32.const -1) (i32.const 255)) +(assert_return (invoke "get_packed_g0_2") (i32.const 2) (i32.const 2)) +(assert_return (invoke "get_packed_g1_2") (i32.const -2) (i32.const 65534)) +(assert_return (invoke "get_packed_g0_3") (i32.const 3) (i32.const 3)) +(assert_return (invoke "get_packed_g1_3") (i32.const -1) (i32.const 65535)) + +(assert_return (invoke "set_get_packed_g0_1" (i32.const 257)) (i32.const 1) (i32.const 1)) +(assert_return (invoke "set_get_packed_g0_3" (i32.const 257)) (i32.const 257) (i32.const 257)) diff --git a/test/core/gc/type-subtyping.wast b/test/core/gc/type-subtyping.wast new file mode 100644 index 0000000000..f2b33d7c49 --- /dev/null +++ b/test/core/gc/type-subtyping.wast @@ -0,0 +1,833 @@ +;; Definitions + +(module + (type $e0 (sub (array i32))) + (type $e1 (sub $e0 (array i32))) + + (type $e2 (sub (array anyref))) + (type $e3 (sub (array (ref null $e0)))) + (type $e4 (sub (array (ref $e1)))) + + (type $m1 (sub (array (mut i32)))) + (type $m2 (sub $m1 (array (mut i32)))) +) + +(module + (type $e0 (sub (struct))) + (type $e1 (sub $e0 (struct))) + (type $e2 (sub $e1 (struct (field i32)))) + (type $e3 (sub $e2 (struct (field i32 (ref null $e0))))) + (type $e4 (sub $e3 (struct (field i32 (ref $e0) (mut i64))))) + (type $e5 (sub $e4 (struct (field i32 (ref $e1) (mut i64))))) +) + +(module + (type $s (sub (struct))) + (type $s' (sub $s (struct))) + + (type $f1 (sub (func (param (ref $s')) (result anyref)))) + (type $f2 (sub $f1 (func (param (ref $s)) (result (ref any))))) + (type $f3 (sub $f2 (func (param (ref null $s)) (result (ref $s))))) + (type $f4 (sub $f3 (func (param (ref null struct)) (result (ref $s'))))) +) + + +;; Recursive definitions + +(module + (type $t (sub (struct (field anyref)))) + (rec (type $r (sub $t (struct (field (ref $r)))))) + (type $t' (sub $r (struct (field (ref $r) i32)))) +) + +(module + (rec + (type $r1 (sub (struct (field i32 (ref $r1))))) + ) + (rec + (type $r2 (sub $r1 (struct (field i32 (ref $r3))))) + (type $r3 (sub $r1 (struct (field i32 (ref $r2))))) + ) +) + +(module + (rec + (type $a1 (sub (struct (field i32 (ref $a2))))) + (type $a2 (sub (struct (field i64 (ref $a1))))) + ) + (rec + (type $b1 (sub $a2 (struct (field i64 (ref $a1) i32)))) + (type $b2 (sub $a1 (struct (field i32 (ref $a2) i32)))) + (type $b3 (sub $a2 (struct (field i64 (ref $b2) i32)))) + ) +) + + +;; Subsumption + +(module + (rec + (type $t1 (sub (func (param i32 (ref $t3))))) + (type $t2 (sub $t1 (func (param i32 (ref $t2))))) + (type $t3 (sub $t2 (func (param i32 (ref $t1))))) + ) + + (func $f1 (param $r (ref $t1)) + (call $f1 (local.get $r)) + ) + (func $f2 (param $r (ref $t2)) + (call $f1 (local.get $r)) + (call $f2 (local.get $r)) + ) + (func $f3 (param $r (ref $t3)) + (call $f1 (local.get $r)) + (call $f2 (local.get $r)) + (call $f3 (local.get $r)) + ) +) + +(module + (rec + (type $t1 (sub (func (result i32 (ref $u1))))) + (type $u1 (sub (func (result f32 (ref $t1))))) + ) + + (rec + (type $t2 (sub $t1 (func (result i32 (ref $u3))))) + (type $u2 (sub $u1 (func (result f32 (ref $t3))))) + (type $t3 (sub $t1 (func (result i32 (ref $u2))))) + (type $u3 (sub $u1 (func (result f32 (ref $t2))))) + ) + + (func $f1 (param $r (ref $t1)) + (call $f1 (local.get $r)) + ) + (func $f2 (param $r (ref $t2)) + (call $f1 (local.get $r)) + (call $f2 (local.get $r)) + ) + (func $f3 (param $r (ref $t3)) + (call $f1 (local.get $r)) + (call $f3 (local.get $r)) + ) +) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func $g (type $g2)) + (global (ref $g1) (ref.func $g)) +) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (func $g (type $g2)) + (global (ref $g1) (ref.func $g)) +) + +(assert_invalid + (module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func $g (type $g2)) + (global (ref $g1) (ref.func $g)) + ) + "type mismatch" +) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g (sub $f1 (func))) (type (struct))) + (func $g (type $g)) + (global (ref $f1) (ref.func $g)) +) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (rec (type $h (sub $g2 (func))) (type (struct))) + (func $h (type $h)) + (global (ref $f1) (ref.func $h)) + (global (ref $g1) (ref.func $h)) +) + + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (func $f11 (type $f11) (unreachable)) + (func $f12 (type $f12) (unreachable)) + (global (ref $f11) (ref.func $f11)) + (global (ref $f21) (ref.func $f11)) + (global (ref $f12) (ref.func $f12)) + (global (ref $f22) (ref.func $f12)) +) + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (rec (type $g11 (sub $f11 (func (result (ref func))))) (type $g12 (sub $g11 (func (result (ref $g11)))))) + (rec (type $g21 (sub $f21 (func (result (ref func))))) (type $g22 (sub $g21 (func (result (ref $g21)))))) + (func $g11 (type $g11) (unreachable)) + (func $g12 (type $g12) (unreachable)) + (global (ref $f11) (ref.func $g11)) + (global (ref $f21) (ref.func $g11)) + (global (ref $f11) (ref.func $g12)) + (global (ref $f21) (ref.func $g12)) + (global (ref $g11) (ref.func $g11)) + (global (ref $g21) (ref.func $g11)) + (global (ref $g12) (ref.func $g12)) + (global (ref $g22) (ref.func $g12)) +) + +(assert_invalid + (module + (rec (type $f11 (sub (func))) (type $f12 (sub $f11 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func $f (type $f21)) + (global (ref $f11) (ref.func $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (rec (type $f01 (sub (func))) (type $f02 (sub $f01 (func)))) + (rec (type $f11 (sub (func))) (type $f12 (sub $f01 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func $f (type $f21)) + (global (ref $f11) (ref.func $f)) + ) + "type mismatch" +) + + +;; Runtime types + +(module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + + (func $f0 (type $t0) (ref.null func)) + (func $f1 (type $t1) (ref.null $t1)) + (func $f2 (type $t2) (ref.null $t2)) + (table funcref (elem $f0 $f1 $f2)) + + (func (export "run") + (block (result (ref null func)) (call_indirect (type $t0) (i32.const 0))) + (block (result (ref null func)) (call_indirect (type $t0) (i32.const 1))) + (block (result (ref null func)) (call_indirect (type $t0) (i32.const 2))) + (block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 1))) + (block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 2))) + (block (result (ref null $t2)) (call_indirect (type $t2) (i32.const 2))) + + (block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 0)))) + (block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 1)))) + (block (result (ref null $t0)) (ref.cast (ref $t0) (table.get (i32.const 2)))) + (block (result (ref null $t1)) (ref.cast (ref $t1) (table.get (i32.const 1)))) + (block (result (ref null $t1)) (ref.cast (ref $t1) (table.get (i32.const 2)))) + (block (result (ref null $t2)) (ref.cast (ref $t2) (table.get (i32.const 2)))) + (br 0) + ) + + (func (export "fail1") + (block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 0))) + (br 0) + ) + (func (export "fail2") + (block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 0))) + (br 0) + ) + (func (export "fail3") + (block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 1))) + (br 0) + ) + + (func (export "fail4") + (ref.cast (ref $t1) (table.get (i32.const 0))) + (br 0) + ) + (func (export "fail5") + (ref.cast (ref $t2) (table.get (i32.const 0))) + (br 0) + ) + (func (export "fail6") + (ref.cast (ref $t2) (table.get (i32.const 1))) + (br 0) + ) +) +(assert_return (invoke "run")) +(assert_trap (invoke "fail1") "indirect call") +(assert_trap (invoke "fail2") "indirect call") +(assert_trap (invoke "fail3") "indirect call") +(assert_trap (invoke "fail4") "cast") +(assert_trap (invoke "fail5") "cast") +(assert_trap (invoke "fail6") "cast") + +(module + (type $t1 (sub (func))) + (type $t2 (sub final (func))) + + (func $f1 (type $t1)) + (func $f2 (type $t2)) + (table funcref (elem $f1 $f2)) + + (func (export "fail1") + (block (call_indirect (type $t1) (i32.const 1))) + ) + (func (export "fail2") + (block (call_indirect (type $t2) (i32.const 0))) + ) + + (func (export "fail3") + (ref.cast (ref $t1) (table.get (i32.const 1))) + (drop) + ) + (func (export "fail4") + (ref.cast (ref $t2) (table.get (i32.const 0))) + (drop) + ) +) +(assert_trap (invoke "fail1") "indirect call") +(assert_trap (invoke "fail2") "indirect call") +(assert_trap (invoke "fail3") "cast") +(assert_trap (invoke "fail4") "cast") + +(module + (type $t1 (sub (func))) + (type $t2 (sub $t1 (func))) + (type $t3 (sub $t2 (func))) + (type $t4 (sub final (func))) + + (func $f2 (type $t2)) + (func $f3 (type $t3)) + (table (ref null $t2) (elem $f2 $f3)) + + (func (export "run") + (call_indirect (type $t1) (i32.const 0)) + (call_indirect (type $t1) (i32.const 1)) + (call_indirect (type $t2) (i32.const 0)) + (call_indirect (type $t2) (i32.const 1)) + (call_indirect (type $t3) (i32.const 1)) + ) + + (func (export "fail1") + (call_indirect (type $t3) (i32.const 0)) + ) + (func (export "fail2") + (call_indirect (type $t4) (i32.const 0)) + ) +) +(assert_return (invoke "run")) +(assert_trap (invoke "fail1") "indirect call") +(assert_trap (invoke "fail2") "indirect call") + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func $g (type $g2)) (elem declare func $g) + (func (export "run") (result i32) + (ref.test (ref $g1) (ref.func $g)) + ) +) +(assert_return (invoke "run") (i32.const 1)) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (func $g (type $g2)) (elem declare func $g) + (func (export "run") (result i32) + (ref.test (ref $g1) (ref.func $g)) + ) +) +(assert_return (invoke "run") (i32.const 1)) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func $g (type $g2)) (elem declare func $g) + (func (export "run") (result i32) + (ref.test (ref $g1) (ref.func $g)) + ) +) +(assert_return (invoke "run") (i32.const 0)) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g (sub $f1 (func))) (type (struct))) + (func $g (type $g)) (elem declare func $g) + (func (export "run") (result i32) + (ref.test (ref $f1) (ref.func $g)) + ) +) +(assert_return (invoke "run") (i32.const 1)) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (rec (type $h (sub $g2 (func))) (type (struct))) + (func $h (type $h)) (elem declare func $h) + (func (export "run") (result i32 i32) + (ref.test (ref $f1) (ref.func $h)) + (ref.test (ref $g1) (ref.func $h)) + ) +) +(assert_return (invoke "run") (i32.const 1) (i32.const 1)) + + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (func $f11 (type $f11) (unreachable)) (elem declare func $f11) + (func $f12 (type $f12) (unreachable)) (elem declare func $f12) + (func (export "run") (result i32 i32 i32 i32) + (ref.test (ref $f11) (ref.func $f11)) + (ref.test (ref $f21) (ref.func $f11)) + (ref.test (ref $f12) (ref.func $f12)) + (ref.test (ref $f22) (ref.func $f12)) + ) +) +(assert_return (invoke "run") + (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1) +) + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (rec (type $g11 (sub $f11 (func (result (ref func))))) (type $g12 (sub $g11 (func (result (ref $g11)))))) + (rec (type $g21 (sub $f21 (func (result (ref func))))) (type $g22 (sub $g21 (func (result (ref $g21)))))) + (func $g11 (type $g11) (unreachable)) (elem declare func $g11) + (func $g12 (type $g12) (unreachable)) (elem declare func $g12) + (func (export "run") (result i32 i32 i32 i32 i32 i32 i32 i32) + (ref.test (ref $f11) (ref.func $g11)) + (ref.test (ref $f21) (ref.func $g11)) + (ref.test (ref $f11) (ref.func $g12)) + (ref.test (ref $f21) (ref.func $g12)) + (ref.test (ref $g11) (ref.func $g11)) + (ref.test (ref $g21) (ref.func $g11)) + (ref.test (ref $g12) (ref.func $g12)) + (ref.test (ref $g22) (ref.func $g12)) + ) +) +(assert_return (invoke "run") + (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1) + (i32.const 1) (i32.const 1) (i32.const 1) (i32.const 1) +) + +(module + (rec (type $f11 (sub (func))) (type $f12 (sub $f11 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func $f (type $f21)) (elem declare func $f) + (func (export "run") (result i32) + (ref.test (ref $f11) (ref.func $f)) + ) +) +(assert_return (invoke "run") (i32.const 0)) + +(module + (rec (type $f01 (sub (func))) (type $f02 (sub $f01 (func)))) + (rec (type $f11 (sub (func))) (type $f12 (sub $f01 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func $f (type $f21)) (elem declare func $f) + (func (export "run") (result i32) + (ref.test (ref $f11) (ref.func $f)) + ) +) +(assert_return (invoke "run") (i32.const 0)) + + + +;; Linking + +(module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + + (func (export "f0") (type $t0) (ref.null func)) + (func (export "f1") (type $t1) (ref.null $t1)) + (func (export "f2") (type $t2) (ref.null $t2)) +) +(register "M") + +(module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + + (func (import "M" "f0") (type $t0)) + (func (import "M" "f1") (type $t0)) + (func (import "M" "f1") (type $t1)) + (func (import "M" "f2") (type $t0)) + (func (import "M" "f2") (type $t1)) + (func (import "M" "f2") (type $t2)) +) + +(assert_unlinkable + (module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + (func (import "M" "f0") (type $t1)) + ) + "incompatible import type" +) + +(assert_unlinkable + (module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + (func (import "M" "f0") (type $t2)) + ) + "incompatible import type" +) + +(assert_unlinkable + (module + (type $t0 (sub (func (result (ref null func))))) + (rec (type $t1 (sub $t0 (func (result (ref null $t1)))))) + (rec (type $t2 (sub $t1 (func (result (ref null $t2)))))) + (func (import "M" "f1") (type $t2)) + ) + "incompatible import type" +) + +(module + (type $t1 (sub (func))) + (type $t2 (sub final (func))) + (func (export "f1") (type $t1)) + (func (export "f2") (type $t2)) +) +(register "M2") + +(assert_unlinkable + (module + (type $t1 (sub (func))) + (type $t2 (sub final (func))) + (func (import "M2" "f1") (type $t2)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (type $t1 (sub (func))) + (type $t2 (sub final (func))) + (func (import "M2" "f2") (type $t1)) + ) + "incompatible import type" +) + + +(module + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func (export "g") (type $g2)) +) +(register "M3") +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (func (import "M3" "g") (type $g1)) +) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (func (export "g") (type $g2)) +) +(register "M4") +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (func (import "M4" "g") (type $g1)) +) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $g2 (sub $f2 (func))) (type (struct))) + (func (export "g") (type $g2)) +) +(register "M5") +(assert_unlinkable + (module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $g1 (sub $f1 (func))) (type (struct))) + (func (import "M5" "g") (type $g1)) + ) + "incompatible import" +) + +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g (sub $f1 (func))) (type (struct))) + (func (export "g") (type $g)) +) +(register "M6") +(module + (rec (type $f1 (sub (func))) (type (struct (field (ref $f1))))) + (rec (type $f2 (sub (func))) (type (struct (field (ref $f2))))) + (rec (type $g (sub $f1 (func))) (type (struct))) + (func (import "M6" "g") (type $f1)) +) + +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g2 (sub $f2 (func))) + (type (sub $s2 (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2))))) + ) + (rec (type $h (sub $g2 (func))) (type (struct))) + (func (export "h") (type $h)) +) +(register "M7") +(module + (rec (type $f1 (sub (func))) (type $s1 (sub (struct (field (ref $f1)))))) + (rec (type $f2 (sub (func))) (type $s2 (sub (struct (field (ref $f2)))))) + (rec + (type $g1 (sub $f1 (func))) + (type (sub $s1 (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1))))) + ) + (rec (type $h (sub $g1 (func))) (type (struct))) + (func (import "M7" "h") (type $f1)) + (func (import "M7" "h") (type $g1)) +) + + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (func (export "f11") (type $f11) (unreachable)) + (func (export "f12") (type $f12) (unreachable)) +) +(register "M8") +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (func (import "M8" "f11") (type $f11)) + (func (import "M8" "f11") (type $f21)) + (func (import "M8" "f12") (type $f12)) + (func (import "M8" "f12") (type $f22)) +) + +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (rec (type $g11 (sub $f11 (func (result (ref func))))) (type $g12 (sub $g11 (func (result (ref $g11)))))) + (rec (type $g21 (sub $f21 (func (result (ref func))))) (type $g22 (sub $g21 (func (result (ref $g21)))))) + (func (export "g11") (type $g11) (unreachable)) + (func (export "g12") (type $g12) (unreachable)) +) +(register "M9") +(module + (rec (type $f11 (sub (func (result (ref func))))) (type $f12 (sub $f11 (func (result (ref $f11)))))) + (rec (type $f21 (sub (func (result (ref func))))) (type $f22 (sub $f21 (func (result (ref $f21)))))) + (rec (type $g11 (sub $f11 (func (result (ref func))))) (type $g12 (sub $g11 (func (result (ref $g11)))))) + (rec (type $g21 (sub $f21 (func (result (ref func))))) (type $g22 (sub $g21 (func (result (ref $g21)))))) + (func (import "M9" "g11") (type $f11)) + (func (import "M9" "g11") (type $f21)) + (func (import "M9" "g12") (type $f11)) + (func (import "M9" "g12") (type $f21)) + (func (import "M9" "g11") (type $g11)) + (func (import "M9" "g11") (type $g21)) + (func (import "M9" "g12") (type $g12)) + (func (import "M9" "g12") (type $g22)) +) + +(module + (rec (type $f11 (sub (func))) (type $f12 (sub $f11 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func (export "f") (type $f21)) +) +(register "M10") +(assert_unlinkable + (module + (rec (type $f11 (sub (func))) (type $f12 (sub $f11 (func)))) + (func (import "M10" "f") (type $f11)) + ) + "incompatible import" +) + +(module + (rec (type $f01 (sub (func))) (type $f02 (sub $f01 (func)))) + (rec (type $f11 (sub (func))) (type $f12 (sub $f01 (func)))) + (rec (type $f21 (sub (func))) (type $f22 (sub $f11 (func)))) + (func (export "f") (type $f21)) +) +(register "M11") +(assert_unlinkable + (module + (rec (type $f01 (sub (func))) (type $f02 (sub $f01 (func)))) + (rec (type $f11 (sub (func))) (type $f12 (sub $f01 (func)))) + (func (import "M11" "f") (type $f11)) + ) + "incompatible import" +) + + + +;; Finality violation + +(assert_invalid + (module + (type $t (func)) + (type $s (sub $t (func))) + ) + "sub type" +) + +(assert_invalid + (module + (type $t (struct)) + (type $s (sub $t (struct))) + ) + "sub type" +) + +(assert_invalid + (module + (type $t (sub final (func))) + (type $s (sub $t (func))) + ) + "sub type" +) + +(assert_invalid + (module + (type $t (sub (func))) + (type $s (sub final $t (func))) + (type $u (sub $s (func))) + ) + "sub type" +) + + + +;; Invalid subtyping definitions + +(assert_invalid + (module + (type $a0 (sub (array i32))) + (type $s0 (sub $a0 (struct))) + ) + "sub type" +) + +(assert_invalid + (module + (type $f0 (sub (func (param i32) (result i32)))) + (type $s0 (sub $f0 (struct))) + ) + "sub type" +) + +(assert_invalid + (module + (type $s0 (sub (struct))) + (type $a0 (sub $s0 (array i32))) + ) + "sub type" +) + +(assert_invalid + (module + (type $f0 (sub (func (param i32) (result i32)))) + (type $a0 (sub $f0 (array i32))) + ) + "sub type" +) + +(assert_invalid + (module + (type $s0 (sub (struct))) + (type $f0 (sub $s0 (func (param i32) (result i32)))) + ) + "sub type" +) + +(assert_invalid + (module + (type $a0 (sub (array i32))) + (type $f0 (sub $a0 (func (param i32) (result i32)))) + ) + "sub type" +) + +(assert_invalid + (module + (type $a0 (sub (array i32))) + (type $a1 (sub $a0 (array i64))) + ) + "sub type" +) + +(assert_invalid + (module + (type $s0 (sub (struct (field i32)))) + (type $s1 (sub $s0 (struct (field i64)))) + ) + "sub type" +) + +(assert_invalid + (module + (type $f0 (sub (func))) + (type $f1 (sub $f0 (func (param i32)))) + ) + "sub type" +) diff --git a/test/core/global.wast b/test/core/global.wast index e40a305f16..370d755864 100644 --- a/test/core/global.wast +++ b/test/core/global.wast @@ -16,6 +16,10 @@ (global $z1 i32 (global.get 0)) (global $z2 i64 (global.get 1)) + (global $z3 i32 (i32.add (i32.sub (i32.mul (i32.const 20) (i32.const 2)) (i32.const 2)) (i32.const 4))) + (global $z4 i64 (i64.add (i64.sub (i64.mul (i64.const 20) (i64.const 2)) (i64.const 2)) (i64.const 5))) + (global $z5 i32 (i32.add (global.get 0) (i32.const 42))) + (global $z6 i64 (i64.add (global.get 1) (i64.const 42))) (global $r externref (ref.null extern)) (global $mr (mut externref) (ref.null extern)) @@ -29,6 +33,10 @@ (func (export "get-y") (result i64) (global.get $y)) (func (export "get-z1") (result i32) (global.get $z1)) (func (export "get-z2") (result i64) (global.get $z2)) + (func (export "get-z3") (result i32) (global.get $z3)) + (func (export "get-z4") (result i64) (global.get $z4)) + (func (export "get-z5") (result i32) (global.get $z5)) + (func (export "get-z6") (result i64) (global.get $z6)) (func (export "set-x") (param i32) (global.set $x (local.get 0))) (func (export "set-y") (param i64) (global.set $y (local.get 0))) (func (export "set-mr") (param externref) (global.set $mr (local.get 0))) @@ -201,6 +209,10 @@ (assert_return (invoke "get-y") (i64.const -15)) (assert_return (invoke "get-z1") (i32.const 666)) (assert_return (invoke "get-z2") (i64.const 666)) +(assert_return (invoke "get-z3") (i32.const 42)) +(assert_return (invoke "get-z4") (i64.const 43)) +(assert_return (invoke "get-z5") (i32.const 708)) +(assert_return (invoke "get-z6") (i64.const 708)) (assert_return (invoke "get-3") (f32.const -3)) (assert_return (invoke "get-4") (f64.const -4)) @@ -271,12 +283,12 @@ (assert_invalid (module (global f32 (f32.const 0)) (func (global.set 0 (f32.const 1)))) - "global is immutable" + "immutable global" ) (assert_invalid (module (import "spectest" "global_i32" (global i32)) (func (global.set 0 (i32.const 1)))) - "global is immutable" + "immutable global" ) ;; mutable globals can be exported @@ -348,15 +360,6 @@ "unknown global" ) -(assert_invalid - (module (global i32 (i32.const 0)) (global i32 (global.get 0))) - "unknown global" -) -(assert_invalid - (module (global $g i32 (i32.const 0)) (global i32 (global.get $g))) - "unknown global" -) - (assert_invalid (module (global i32 (global.get 1)) (global i32 (i32.const 0))) "unknown global" @@ -367,6 +370,9 @@ "unknown global" ) +(module (global i32 (i32.const 0)) (global i32 (global.get 0))) +(module (global $g i32 (i32.const 0)) (global i32 (global.get $g))) + (assert_invalid (module (global (import "test" "global-mut-i32") (mut i32)) (global i32 (global.get 0))) "constant expression required" @@ -617,17 +623,83 @@ "type mismatch" ) + +;; Definition order + +(module + (global (export "g") i32 (i32.const 4)) +) +(register "G") + +(module + (global $g0 (import "G" "g") i32) + (global $g1 i32 (i32.const 8)) + (global $g2 i32 (global.get $g0)) + (global $g3 i32 (global.get $g1)) + + (global $gf funcref (ref.func $f)) + (func $f) + + (table $t 10 funcref (ref.null func)) + (elem (table $t) (global.get $g2) funcref (ref.func $f)) + (elem (table $t) (global.get $g3) funcref (global.get $gf)) + + (memory $m 1) + (data (global.get $g2) "\44\44\44\44") + (data (global.get $g3) "\88\88\88\88") + + (func (export "get-elem") (param $i i32) (result funcref) + (table.get $t (local.get $i)) + ) + (func (export "get-data") (param $i i32) (result i32) + (i32.load (local.get $i)) + ) +) + +(assert_return (invoke "get-elem" (i32.const 0)) (ref.null)) +(assert_return (invoke "get-elem" (i32.const 4)) (ref.func)) +(assert_return (invoke "get-elem" (i32.const 8)) (ref.func)) + +(assert_return (invoke "get-data" (i32.const 4)) (i32.const 0x44444444)) +(assert_return (invoke "get-data" (i32.const 8)) (i32.const 0x88888888)) + +(assert_invalid + (module + (global $g1 i32 (global.get $g2)) + (global $g2 i32 (i32.const 0)) + ) + "unknown global" +) + +(assert_invalid + (module + (global $g funcref (ref.null func)) + (table $t 10 funcref (global.get $g)) + ) + "unknown global" +) + + ;; Duplicate identifier errors -(assert_malformed (module quote - "(global $foo i32 (i32.const 0))" - "(global $foo i32 (i32.const 0))") - "duplicate global") -(assert_malformed (module quote - "(import \"\" \"\" (global $foo i32))" - "(global $foo i32 (i32.const 0))") - "duplicate global") -(assert_malformed (module quote - "(import \"\" \"\" (global $foo i32))" - "(import \"\" \"\" (global $foo i32))") - "duplicate global") +(assert_malformed + (module quote + "(global $foo i32 (i32.const 0))" + "(global $foo i32 (i32.const 0))" + ) + "duplicate global" +) +(assert_malformed + (module quote + "(import \"\" \"\" (global $foo i32))" + "(global $foo i32 (i32.const 0))" + ) + "duplicate global" +) +(assert_malformed + (module quote + "(import \"\" \"\" (global $foo i32))" + "(import \"\" \"\" (global $foo i32))" + ) + "duplicate global" +) diff --git a/test/core/if.wast b/test/core/if.wast index 2ea45f6fa0..35a4cf6cb9 100644 --- a/test/core/if.wast +++ b/test/core/if.wast @@ -1561,4 +1561,4 @@ (assert_malformed (module quote "(func (if i32.const 0 (then) (else)))") "unexpected token" -) \ No newline at end of file +) diff --git a/test/core/imports.wast b/test/core/imports.wast index 0cc07cb1a9..110df172f9 100644 --- a/test/core/imports.wast +++ b/test/core/imports.wast @@ -485,19 +485,6 @@ (assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000)) (assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access") -(assert_invalid - (module (import "" "" (memory 1)) (import "" "" (memory 1))) - "multiple memories" -) -(assert_invalid - (module (import "" "" (memory 1)) (memory 0)) - "multiple memories" -) -(assert_invalid - (module (memory 0) (memory 0)) - "multiple memories" -) - (module (import "test" "memory-2-inf" (memory 2))) (module (import "test" "memory-2-inf" (memory 1))) (module (import "test" "memory-2-inf" (memory 0))) diff --git a/test/core/linking.wast b/test/core/linking.wast index 994e0f49d0..6a8ba1d0cc 100644 --- a/test/core/linking.wast +++ b/test/core/linking.wast @@ -94,37 +94,172 @@ (module $Mref_ex - (global (export "g-const-func") funcref (ref.null func)) - (global (export "g-var-func") (mut funcref) (ref.null func)) + (type $t (func)) + (func $f) (elem declare func $f) + (global (export "g-const-funcnull") (ref null func) (ref.null func)) + (global (export "g-const-func") (ref func) (ref.func $f)) + (global (export "g-const-refnull") (ref null $t) (ref.null $t)) + (global (export "g-const-ref") (ref $t) (ref.func $f)) (global (export "g-const-extern") externref (ref.null extern)) + (global (export "g-var-funcnull") (mut (ref null func)) (ref.null func)) + (global (export "g-var-func") (mut (ref func)) (ref.func $f)) + (global (export "g-var-refnull") (mut (ref null $t)) (ref.null $t)) + (global (export "g-var-ref") (mut (ref $t)) (ref.func $f)) (global (export "g-var-extern") (mut externref) (ref.null extern)) ) (register "Mref_ex" $Mref_ex) (module $Mref_im - (global (import "Mref_ex" "g-const-func") funcref) + (type $t (func)) + (global (import "Mref_ex" "g-const-funcnull") (ref null func)) + (global (import "Mref_ex" "g-const-func") (ref null func)) + (global (import "Mref_ex" "g-const-refnull") (ref null func)) + (global (import "Mref_ex" "g-const-ref") (ref null func)) + (global (import "Mref_ex" "g-const-func") (ref func)) + (global (import "Mref_ex" "g-const-ref") (ref func)) + (global (import "Mref_ex" "g-const-refnull") (ref null $t)) + (global (import "Mref_ex" "g-const-ref") (ref null $t)) + (global (import "Mref_ex" "g-const-ref") (ref $t)) (global (import "Mref_ex" "g-const-extern") externref) - (global (import "Mref_ex" "g-var-func") (mut funcref)) + (global (import "Mref_ex" "g-var-funcnull") (mut (ref null func))) + (global (import "Mref_ex" "g-var-func") (mut (ref func))) + (global (import "Mref_ex" "g-var-refnull") (mut (ref null $t))) + (global (import "Mref_ex" "g-var-ref") (mut (ref $t))) (global (import "Mref_ex" "g-var-extern") (mut externref)) ) (assert_unlinkable - (module (global (import "Mref_ex" "g-const-extern") funcref)) + (module (global (import "Mref_ex" "g-const-extern") (ref null func))) + "incompatible import type" +) + +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-funcnull") (ref func))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-refnull") (ref func))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-extern") (ref func))) + "incompatible import type" +) + +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-funcnull") (ref null $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-func") (ref null $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-extern") (ref null $t))) + "incompatible import type" +) + +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-funcnull") (ref $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-func") (ref $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-refnull") (ref $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-const-extern") (ref $t))) + "incompatible import type" +) + + +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-func") (mut (ref null func)))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-refnull") (mut (ref null func)))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-ref") (mut (ref null func)))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-extern") (mut (ref null func)))) + "incompatible import type" +) + +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-funcnull") (mut (ref func)))) "incompatible import type" ) (assert_unlinkable - (module (global (import "Mref_ex" "g-const-func") externref)) + (module (global (import "Mref_ex" "g-var-refnull") (mut (ref func)))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-ref") (mut (ref func)))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-extern") (mut (ref func)))) "incompatible import type" ) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-funcnull") (mut (ref null $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-func") (mut (ref null $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-ref") (mut (ref null $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-extern") (mut (ref null $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-funcnull") (mut (ref $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-func") (mut (ref $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-refnull") (mut (ref $t)))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (global (import "Mref_ex" "g-var-extern") (mut (ref $t)))) + "incompatible import type" +) + +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-funcnull") (mut externref))) + "incompatible import type" +) (assert_unlinkable (module (global (import "Mref_ex" "g-var-func") (mut externref))) "incompatible import type" ) (assert_unlinkable - (module (global (import "Mref_ex" "g-var-extern") (mut funcref))) + (module (global (import "Mref_ex" "g-var-refnull") (mut externref))) + "incompatible import type" +) +(assert_unlinkable + (module (global (import "Mref_ex" "g-var-ref") (mut externref))) "incompatible import type" ) @@ -289,22 +424,44 @@ (module $Mtable_ex - (table $t1 (export "t-func") 1 funcref) - (table $t2 (export "t-extern") 1 externref) + (type $t (func)) + (table (export "t-funcnull") 1 (ref null func)) + (table (export "t-refnull") 1 (ref null $t)) + (table (export "t-extern") 1 externref) ) (register "Mtable_ex" $Mtable_ex) (module - (table (import "Mtable_ex" "t-func") 1 funcref) + (type $t (func)) + (table (import "Mtable_ex" "t-funcnull") 1 (ref null func)) + (table (import "Mtable_ex" "t-refnull") 1 (ref null $t)) (table (import "Mtable_ex" "t-extern") 1 externref) ) (assert_unlinkable - (module (table (import "Mtable_ex" "t-func") 1 externref)) + (module (table (import "Mtable_ex" "t-refnull") 1 (ref null func))) + "incompatible import type" +) +(assert_unlinkable + (module (table (import "Mtable_ex" "t-extern") 1 (ref null func))) + "incompatible import type" +) + +(assert_unlinkable + (module (type $t (func)) (table (import "Mtable_ex" "t-funcnull") 1 (ref null $t))) + "incompatible import type" +) +(assert_unlinkable + (module (type $t (func)) (table (import "Mtable_ex" "t-extern") 1 (ref null $t))) + "incompatible import type" +) + +(assert_unlinkable + (module (table (import "Mtable_ex" "t-funcnull") 1 externref)) "incompatible import type" ) (assert_unlinkable - (module (table (import "Mtable_ex" "t-extern") 1 funcref)) + (module (table (import "Mtable_ex" "t-refnull") 1 externref)) "incompatible import type" ) diff --git a/test/core/load.wast b/test/core/load.wast index ef5ec7c429..9fe48e2b41 100644 --- a/test/core/load.wast +++ b/test/core/load.wast @@ -1,3 +1,67 @@ +;; Multiple memories + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (data (memory $mem1) (i32.const 0) "\01") + (data (memory $mem2) (i32.const 0) "\02") +) + +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) + + +(module $M + (memory (export "mem") 2) + + (func (export "read") (param i32) (result i32) + (i32.load8_u (local.get 0)) + ) +) +(register "M") + +(module + (memory $mem1 (import "M" "mem") 2) + (memory $mem2 3) + + (data (memory $mem1) (i32.const 20) "\01\02\03\04\05") + (data (memory $mem2) (i32.const 50) "\0A\0B\0C\0D\0E") + + (func (export "read1") (param i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) + (func (export "read2") (param i32) (result i32) + (i32.load8_u $mem2 (local.get 0)) + ) +) + +(assert_return (invoke $M "read" (i32.const 20)) (i32.const 1)) +(assert_return (invoke $M "read" (i32.const 21)) (i32.const 2)) +(assert_return (invoke $M "read" (i32.const 22)) (i32.const 3)) +(assert_return (invoke $M "read" (i32.const 23)) (i32.const 4)) +(assert_return (invoke $M "read" (i32.const 24)) (i32.const 5)) + +(assert_return (invoke "read1" (i32.const 20)) (i32.const 1)) +(assert_return (invoke "read1" (i32.const 21)) (i32.const 2)) +(assert_return (invoke "read1" (i32.const 22)) (i32.const 3)) +(assert_return (invoke "read1" (i32.const 23)) (i32.const 4)) +(assert_return (invoke "read1" (i32.const 24)) (i32.const 5)) + +(assert_return (invoke "read2" (i32.const 50)) (i32.const 10)) +(assert_return (invoke "read2" (i32.const 51)) (i32.const 11)) +(assert_return (invoke "read2" (i32.const 52)) (i32.const 12)) +(assert_return (invoke "read2" (i32.const 53)) (i32.const 13)) +(assert_return (invoke "read2" (i32.const 54)) (i32.const 14)) + + ;; Load operator as the argument of control constructs and instructions (module diff --git a/test/core/local_get.wast b/test/core/local_get.wast index 6acab3988f..45dbcaeeb4 100644 --- a/test/core/local_get.wast +++ b/test/core/local_get.wast @@ -175,7 +175,7 @@ ) -;; local.set should have retval +;; Invalid result type (assert_invalid (module (func $type-empty-vs-i32 (local i32) (local.get 0))) @@ -223,4 +223,3 @@ (module (func $large-mixed (param i64) (local i32 i64) (local.get 214324343) drop)) "unknown local" ) - diff --git a/test/core/local_init.wast b/test/core/local_init.wast new file mode 100644 index 0000000000..176ccc64bb --- /dev/null +++ b/test/core/local_init.wast @@ -0,0 +1,74 @@ +;; Uninitialized undefaulted locals + +(module + (func (export "get-after-set") (param $p (ref extern)) (result (ref extern)) + (local $x (ref extern)) + (local.set $x (local.get $p)) + (local.get $x) + ) + (func (export "get-after-tee") (param $p (ref extern)) (result (ref extern)) + (local $x (ref extern)) + (drop (local.tee $x (local.get $p))) + (local.get $x) + ) + (func (export "get-in-block-after-set") (param $p (ref extern)) (result (ref extern)) + (local $x (ref extern)) + (local.set $x (local.get $p)) + (block (result (ref extern)) (local.get $x)) + ) +) + +(assert_return (invoke "get-after-set" (ref.extern 1)) (ref.extern 1)) +(assert_return (invoke "get-after-tee" (ref.extern 2)) (ref.extern 2)) +(assert_return (invoke "get-in-block-after-set" (ref.extern 3)) (ref.extern 3)) + +(assert_invalid + (module (func $uninit (local $x (ref extern)) (drop (local.get $x)))) + "uninitialized local" +) +(assert_invalid + (module + (func $uninit-after-end (param $p (ref extern)) + (local $x (ref extern)) + (block (local.set $x (local.get $p)) (drop (local.tee $x (local.get $p)))) + (drop (local.get $x)) + ) + ) + "uninitialized local" +) +(assert_invalid + (module + (func $uninit-in-else (param $p (ref extern)) + (local $x (ref extern)) + (if (i32.const 0) + (then (local.set $x (local.get $p))) + (else (local.get $x)) + ) + ) + ) + "uninitialized local" +) + +(assert_invalid + (module + (func $uninit-from-if (param $p (ref extern)) + (local $x (ref extern)) + (if (i32.const 0) + (then (local.set $x (local.get $p))) + (else (local.set $x (local.get $p))) + ) + (drop (local.get $x)) + ) + ) + "uninitialized local" +) + +(module + (func (export "tee-init") (param $p (ref extern)) (result (ref extern)) + (local $x (ref extern)) + (drop (local.tee $x (local.get $p))) + (local.get $x) + ) +) + +(assert_return (invoke "tee-init" (ref.extern 1)) (ref.extern 1)) diff --git a/test/core/local_tee.wast b/test/core/local_tee.wast index a158f0c7c2..eeb26e6e01 100644 --- a/test/core/local_tee.wast +++ b/test/core/local_tee.wast @@ -608,6 +608,21 @@ "type mismatch" ) +;; https://github.com/WebAssembly/gc/issues/516 +(assert_invalid + (module + (type $t (func)) + (func $f (param (ref null $t))) + (func + (local $x funcref) + (ref.null $t) + (local.tee $x) ;; leaves only a funcref on the stack + (call $f) + ) + ) + "type mismatch" +) + ;; Invalid local index diff --git a/test/core/memory-multi.wast b/test/core/memory-multi.wast new file mode 100644 index 0000000000..b5b71fd9e4 --- /dev/null +++ b/test/core/memory-multi.wast @@ -0,0 +1,42 @@ +;; From wasmtime misc_testsuite/multi-memory/simple.wast + +;; Should be replaced with suitable extensions to ../meta/generate_memory_*.js + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "init1") (result i32) + (memory.init $mem1 $d (i32.const 1) (i32.const 0) (i32.const 4)) + (i32.load $mem1 (i32.const 1)) + ) + + (func (export "init2") (result i32) + (memory.init $mem2 $d (i32.const 1) (i32.const 4) (i32.const 4)) + (i32.load $mem2 (i32.const 1)) + ) + + (data $d "\01\00\00\00" "\02\00\00\00") +) + +(assert_return (invoke "init1") (i32.const 1)) +(assert_return (invoke "init2") (i32.const 2)) + + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "fill1") (result i32) + (memory.fill $mem1 (i32.const 1) (i32.const 0x01) (i32.const 4)) + (i32.load $mem1 (i32.const 1)) + ) + + (func (export "fill2") (result i32) + (memory.fill $mem2 (i32.const 1) (i32.const 0x02) (i32.const 2)) + (i32.load $mem2 (i32.const 1)) + ) +) + +(assert_return (invoke "fill1") (i32.const 0x01010101)) +(assert_return (invoke "fill2") (i32.const 0x0202)) diff --git a/test/core/memory.wast b/test/core/memory.wast index a0e1335506..7ab4bbea2a 100644 --- a/test/core/memory.wast +++ b/test/core/memory.wast @@ -7,9 +7,6 @@ (module (memory 1 256)) (module (memory 0 65536)) -(assert_invalid (module (memory 0) (memory 0)) "multiple memories") -(assert_invalid (module (memory (import "spectest" "memory") 0) (memory 0)) "multiple memories") - (module (memory (data)) (func (export "memsize") (result i32) (memory.size))) (assert_return (invoke "memsize") (i32.const 0)) (module (memory (data "")) (func (export "memsize") (result i32) (memory.size))) diff --git a/test/core/memory64.wast b/test/core/memory64.wast index c324e3b198..41e1bf034b 100644 --- a/test/core/memory64.wast +++ b/test/core/memory64.wast @@ -6,9 +6,6 @@ (module (memory i64 1 256)) (module (memory i64 0 65536)) -(assert_invalid (module (memory i64 0) (memory i64 0)) "multiple memories") -(assert_invalid (module (memory (import "spectest" "memory") i64 0) (memory i64 0)) "multiple memories") - (module (memory i64 (data)) (func (export "memsize") (result i64) (memory.size))) (assert_return (invoke "memsize") (i64.const 0)) (module (memory i64 (data "")) (func (export "memsize") (result i64) (memory.size))) diff --git a/test/core/memory_grow.wast b/test/core/memory_grow.wast index aa56297d25..4b6dbc8386 100644 --- a/test/core/memory_grow.wast +++ b/test/core/memory_grow.wast @@ -1,38 +1,3 @@ -(module - (memory 0) - - (func (export "load_at_zero") (result i32) (i32.load (i32.const 0))) - (func (export "store_at_zero") (i32.store (i32.const 0) (i32.const 2))) - - (func (export "load_at_page_size") (result i32) (i32.load (i32.const 0x10000))) - (func (export "store_at_page_size") (i32.store (i32.const 0x10000) (i32.const 3))) - - (func (export "grow") (param $sz i32) (result i32) (memory.grow (local.get $sz))) - (func (export "size") (result i32) (memory.size)) -) - -(assert_return (invoke "size") (i32.const 0)) -(assert_trap (invoke "store_at_zero") "out of bounds memory access") -(assert_trap (invoke "load_at_zero") "out of bounds memory access") -(assert_trap (invoke "store_at_page_size") "out of bounds memory access") -(assert_trap (invoke "load_at_page_size") "out of bounds memory access") -(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) -(assert_return (invoke "size") (i32.const 1)) -(assert_return (invoke "load_at_zero") (i32.const 0)) -(assert_return (invoke "store_at_zero")) -(assert_return (invoke "load_at_zero") (i32.const 2)) -(assert_trap (invoke "store_at_page_size") "out of bounds memory access") -(assert_trap (invoke "load_at_page_size") "out of bounds memory access") -(assert_return (invoke "grow" (i32.const 4)) (i32.const 1)) -(assert_return (invoke "size") (i32.const 5)) -(assert_return (invoke "load_at_zero") (i32.const 2)) -(assert_return (invoke "store_at_zero")) -(assert_return (invoke "load_at_zero") (i32.const 2)) -(assert_return (invoke "load_at_page_size") (i32.const 0)) -(assert_return (invoke "store_at_page_size")) -(assert_return (invoke "load_at_page_size") (i32.const 3)) - - (module (memory 0) (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) @@ -61,6 +26,7 @@ (assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) (assert_return (invoke "grow" (i32.const 0x10000)) (i32.const -1)) + ;; Test that newly allocated memory (program start and memory.grow) is zeroed (module @@ -96,6 +62,130 @@ (assert_return (invoke "grow" (i32.const 1)) (i32.const 5)) (assert_return (invoke "check-memory-zero" (i32.const 0x50000) (i32.const 0x5_ffff)) (i32.const 0)) + +;; Memory access at boundary + +(module + (memory 0) + + (func (export "load_at_zero") (result i32) (i32.load (i32.const 0))) + (func (export "store_at_zero") (i32.store (i32.const 0) (i32.const 2))) + + (func (export "load_at_page_size") (result i32) + (i32.load (i32.const 0x10000)) + ) + (func (export "store_at_page_size") + (i32.store (i32.const 0x10000) (i32.const 3)) + ) + + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) + (func (export "size") (result i32) (memory.size)) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_trap (invoke "store_at_zero") "out of bounds memory access") +(assert_trap (invoke "load_at_zero") "out of bounds memory access") +(assert_trap (invoke "store_at_page_size") "out of bounds memory access") +(assert_trap (invoke "load_at_page_size") "out of bounds memory access") +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "load_at_zero") (i32.const 0)) +(assert_return (invoke "store_at_zero")) +(assert_return (invoke "load_at_zero") (i32.const 2)) +(assert_trap (invoke "store_at_page_size") "out of bounds memory access") +(assert_trap (invoke "load_at_page_size") "out of bounds memory access") +(assert_return (invoke "grow" (i32.const 4)) (i32.const 1)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "load_at_zero") (i32.const 2)) +(assert_return (invoke "store_at_zero")) +(assert_return (invoke "load_at_zero") (i32.const 2)) +(assert_return (invoke "load_at_page_size") (i32.const 0)) +(assert_return (invoke "store_at_page_size")) +(assert_return (invoke "load_at_page_size") (i32.const 3)) + + +;; Multiple memories + +(module + (memory (export "mem1") 2 5) + (memory (export "mem2") 0) +) +(register "M") + +(module + (memory $mem1 (import "M" "mem1") 1 6) + (memory $mem2 (import "M" "mem2") 0) + (memory $mem3 3) + (memory $mem4 4 5) + + (func (export "size1") (result i32) (memory.size $mem1)) + (func (export "size2") (result i32) (memory.size $mem2)) + (func (export "size3") (result i32) (memory.size $mem3)) + (func (export "size4") (result i32) (memory.size $mem4)) + + (func (export "grow1") (param i32) (result i32) + (memory.grow $mem1 (local.get 0)) + ) + (func (export "grow2") (param i32) (result i32) + (memory.grow $mem2 (local.get 0)) + ) + (func (export "grow3") (param i32) (result i32) + (memory.grow $mem3 (local.get 0)) + ) + (func (export "grow4") (param i32) (result i32) + (memory.grow $mem4 (local.get 0)) + ) +) + +(assert_return (invoke "size1") (i32.const 2)) +(assert_return (invoke "size2") (i32.const 0)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow1" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "size1") (i32.const 3)) +(assert_return (invoke "size2") (i32.const 0)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow1" (i32.const 2)) (i32.const 3)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 0)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow1" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 0)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow2" (i32.const 10)) (i32.const 0)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 10)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow3" (i32.const 0x1000_0000)) (i32.const -1)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 10)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow3" (i32.const 3)) (i32.const 3)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 10)) +(assert_return (invoke "size3") (i32.const 6)) +(assert_return (invoke "size4") (i32.const 4)) + +(assert_return (invoke "grow4" (i32.const 1)) (i32.const 4)) +(assert_return (invoke "grow4" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "size1") (i32.const 5)) +(assert_return (invoke "size2") (i32.const 10)) +(assert_return (invoke "size3") (i32.const 6)) +(assert_return (invoke "size4") (i32.const 5)) + + ;; As the argument of control constructs and instructions (module @@ -309,6 +399,43 @@ (assert_return (invoke "as-memory.grow-size") (i32.const 1)) +;; Multiple memories + +(module + (memory $mem1 1) + (memory $mem2 2) + + (func (export "grow1") (param i32) (result i32) + (memory.grow $mem1 (local.get 0)) + ) + (func (export "grow2") (param i32) (result i32) + (memory.grow $mem2 (local.get 0)) + ) + + (func (export "size1") (result i32) (memory.size $mem1)) + (func (export "size2") (result i32) (memory.size $mem2)) +) + +(assert_return (invoke "size1") (i32.const 1)) +(assert_return (invoke "size2") (i32.const 2)) +(assert_return (invoke "grow1" (i32.const 3)) (i32.const 1)) +(assert_return (invoke "grow1" (i32.const 4)) (i32.const 4)) +(assert_return (invoke "grow1" (i32.const 1)) (i32.const 8)) +(assert_return (invoke "grow2" (i32.const 1)) (i32.const 2)) +(assert_return (invoke "grow2" (i32.const 1)) (i32.const 3)) + + +;; Type mismatches + +(assert_invalid + (module + (memory 1) + (func $type-i32-vs-f32 (result i32) + (memory.grow (f32.const 0)) + ) + ) + "type mismatch" +) (assert_invalid (module (memory 0) @@ -349,6 +476,15 @@ "type mismatch" ) +(assert_invalid + (module + (memory 1) + (func $type-result-i32-vs-empty + (memory.grow (i32.const 1)) + ) + ) + "type mismatch" +) (assert_invalid (module (memory 1) diff --git a/test/core/memory_size.wast b/test/core/memory_size.wast index 239e66d8f2..a1d6ea2dd0 100644 --- a/test/core/memory_size.wast +++ b/test/core/memory_size.wast @@ -63,6 +63,32 @@ (assert_return (invoke "size") (i32.const 8)) +;; Multiple memories + +(module + (memory (export "mem1") 2 4) + (memory (export "mem2") 0) +) +(register "M") + +(module + (memory $mem1 (import "M" "mem1") 1 5) + (memory $mem2 (import "M" "mem2") 0) + (memory $mem3 3) + (memory $mem4 4 5) + + (func (export "size1") (result i32) (memory.size $mem1)) + (func (export "size2") (result i32) (memory.size $mem2)) + (func (export "size3") (result i32) (memory.size $mem3)) + (func (export "size4") (result i32) (memory.size $mem4)) +) + +(assert_return (invoke "size1") (i32.const 2)) +(assert_return (invoke "size2") (i32.const 0)) +(assert_return (invoke "size3") (i32.const 3)) +(assert_return (invoke "size4") (i32.const 4)) + + ;; Type errors (assert_invalid diff --git a/test/core/multi-memory/address0.wast b/test/core/multi-memory/address0.wast new file mode 100644 index 0000000000..a022365206 --- /dev/null +++ b/test/core/multi-memory/address0.wast @@ -0,0 +1,212 @@ +;; Load i32 data with different offset/align arguments + +(module + (memory $mem0 0) + (memory $mem1 1) + (data (memory $mem1) (i32.const 0) "abcdefghijklmnopqrstuvwxyz") + + (func (export "8u_good1") (param $i i32) (result i32) + (i32.load8_u $mem1 offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good2") (param $i i32) (result i32) + (i32.load8_u $mem1 align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good3") (param $i i32) (result i32) + (i32.load8_u $mem1 offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8u_good4") (param $i i32) (result i32) + (i32.load8_u $mem1 offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8u_good5") (param $i i32) (result i32) + (i32.load8_u $mem1 offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "8s_good1") (param $i i32) (result i32) + (i32.load8_s $mem1 offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good2") (param $i i32) (result i32) + (i32.load8_s $mem1 align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good3") (param $i i32) (result i32) + (i32.load8_s $mem1 offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8s_good4") (param $i i32) (result i32) + (i32.load8_s $mem1 offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8s_good5") (param $i i32) (result i32) + (i32.load8_s $mem1 offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "16u_good1") (param $i i32) (result i32) + (i32.load16_u $mem1 offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good2") (param $i i32) (result i32) + (i32.load16_u $mem1 align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good3") (param $i i32) (result i32) + (i32.load16_u $mem1 offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16u_good4") (param $i i32) (result i32) + (i32.load16_u $mem1 offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16u_good5") (param $i i32) (result i32) + (i32.load16_u $mem1 offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "16s_good1") (param $i i32) (result i32) + (i32.load16_s $mem1 offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good2") (param $i i32) (result i32) + (i32.load16_s $mem1 align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good3") (param $i i32) (result i32) + (i32.load16_s $mem1 offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16s_good4") (param $i i32) (result i32) + (i32.load16_s $mem1 offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16s_good5") (param $i i32) (result i32) + (i32.load16_s $mem1 offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "32_good1") (param $i i32) (result i32) + (i32.load $mem1 offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32_good2") (param $i i32) (result i32) + (i32.load $mem1 align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32_good3") (param $i i32) (result i32) + (i32.load $mem1 offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32_good4") (param $i i32) (result i32) + (i32.load $mem1 offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32_good5") (param $i i32) (result i32) + (i32.load $mem1 offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "8u_bad") (param $i i32) + (drop (i32.load8_u $mem1 offset=4294967295 (local.get $i))) + ) + (func (export "8s_bad") (param $i i32) + (drop (i32.load8_s $mem1 offset=4294967295 (local.get $i))) + ) + (func (export "16u_bad") (param $i i32) + (drop (i32.load16_u $mem1 offset=4294967295 (local.get $i))) + ) + (func (export "16s_bad") (param $i i32) + (drop (i32.load16_s $mem1 offset=4294967295 (local.get $i))) + ) + (func (export "32_bad") (param $i i32) + (drop (i32.load $mem1 offset=4294967295 (local.get $i))) + ) +) + +(assert_return (invoke "8u_good1" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8u_good2" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8u_good3" (i32.const 0)) (i32.const 98)) +(assert_return (invoke "8u_good4" (i32.const 0)) (i32.const 99)) +(assert_return (invoke "8u_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "8s_good1" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8s_good2" (i32.const 0)) (i32.const 97)) +(assert_return (invoke "8s_good3" (i32.const 0)) (i32.const 98)) +(assert_return (invoke "8s_good4" (i32.const 0)) (i32.const 99)) +(assert_return (invoke "8s_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "16u_good1" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16u_good2" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16u_good3" (i32.const 0)) (i32.const 25442)) +(assert_return (invoke "16u_good4" (i32.const 0)) (i32.const 25699)) +(assert_return (invoke "16u_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "16s_good1" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16s_good2" (i32.const 0)) (i32.const 25185)) +(assert_return (invoke "16s_good3" (i32.const 0)) (i32.const 25442)) +(assert_return (invoke "16s_good4" (i32.const 0)) (i32.const 25699)) +(assert_return (invoke "16s_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "32_good1" (i32.const 0)) (i32.const 1684234849)) +(assert_return (invoke "32_good2" (i32.const 0)) (i32.const 1684234849)) +(assert_return (invoke "32_good3" (i32.const 0)) (i32.const 1701077858)) +(assert_return (invoke "32_good4" (i32.const 0)) (i32.const 1717920867)) +(assert_return (invoke "32_good5" (i32.const 0)) (i32.const 122)) + +(assert_return (invoke "8u_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "32_good1" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good2" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good3" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good4" (i32.const 65507)) (i32.const 0)) +(assert_return (invoke "32_good5" (i32.const 65507)) (i32.const 0)) + +(assert_return (invoke "8u_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65508)) (i32.const 0)) + +(assert_return (invoke "32_good1" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good2" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good3" (i32.const 65508)) (i32.const 0)) +(assert_return (invoke "32_good4" (i32.const 65508)) (i32.const 0)) +(assert_trap (invoke "32_good5" (i32.const 65508)) "out of bounds memory access") + +(assert_trap (invoke "8u_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "8s_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "16u_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "16s_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "32_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "32_good3" (i32.const -1)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32_bad" (i32.const 0)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "32_bad" (i32.const 1)) "out of bounds memory access") diff --git a/test/core/multi-memory/address1.wast b/test/core/multi-memory/address1.wast new file mode 100644 index 0000000000..d43d6475ca --- /dev/null +++ b/test/core/multi-memory/address1.wast @@ -0,0 +1,295 @@ +;; Load i64 data with different offset/align arguments + +(module + (memory $mem0 0) + (memory $mem1 0) + (memory $mem2 0) + (memory $mem3 0) + (memory $mem4 1) + (data (memory $mem4) (i32.const 0) "abcdefghijklmnopqrstuvwxyz") + + (func (export "8u_good1") (param $i i32) (result i64) + (i64.load8_u $mem4 offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good2") (param $i i32) (result i64) + (i64.load8_u $mem4 align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8u_good3") (param $i i32) (result i64) + (i64.load8_u $mem4 offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8u_good4") (param $i i32) (result i64) + (i64.load8_u $mem4 offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8u_good5") (param $i i32) (result i64) + (i64.load8_u $mem4 offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "8s_good1") (param $i i32) (result i64) + (i64.load8_s $mem4 offset=0 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good2") (param $i i32) (result i64) + (i64.load8_s $mem4 align=1 (local.get $i)) ;; 97 'a' + ) + (func (export "8s_good3") (param $i i32) (result i64) + (i64.load8_s $mem4 offset=1 align=1 (local.get $i)) ;; 98 'b' + ) + (func (export "8s_good4") (param $i i32) (result i64) + (i64.load8_s $mem4 offset=2 align=1 (local.get $i)) ;; 99 'c' + ) + (func (export "8s_good5") (param $i i32) (result i64) + (i64.load8_s $mem4 offset=25 align=1 (local.get $i)) ;; 122 'z' + ) + + (func (export "16u_good1") (param $i i32) (result i64) + (i64.load16_u $mem4 offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good2") (param $i i32) (result i64) + (i64.load16_u $mem4 align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16u_good3") (param $i i32) (result i64) + (i64.load16_u $mem4 offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16u_good4") (param $i i32) (result i64) + (i64.load16_u $mem4 offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16u_good5") (param $i i32) (result i64) + (i64.load16_u $mem4 offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "16s_good1") (param $i i32) (result i64) + (i64.load16_s $mem4 offset=0 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good2") (param $i i32) (result i64) + (i64.load16_s $mem4 align=1 (local.get $i)) ;; 25185 'ab' + ) + (func (export "16s_good3") (param $i i32) (result i64) + (i64.load16_s $mem4 offset=1 align=1 (local.get $i)) ;; 25442 'bc' + ) + (func (export "16s_good4") (param $i i32) (result i64) + (i64.load16_s $mem4 offset=2 align=2 (local.get $i)) ;; 25699 'cd' + ) + (func (export "16s_good5") (param $i i32) (result i64) + (i64.load16_s $mem4 offset=25 align=2 (local.get $i)) ;; 122 'z\0' + ) + + (func (export "32u_good1") (param $i i32) (result i64) + (i64.load32_u $mem4 offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32u_good2") (param $i i32) (result i64) + (i64.load32_u $mem4 align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32u_good3") (param $i i32) (result i64) + (i64.load32_u $mem4 offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32u_good4") (param $i i32) (result i64) + (i64.load32_u $mem4 offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32u_good5") (param $i i32) (result i64) + (i64.load32_u $mem4 offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "32s_good1") (param $i i32) (result i64) + (i64.load32_s $mem4 offset=0 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32s_good2") (param $i i32) (result i64) + (i64.load32_s $mem4 align=1 (local.get $i)) ;; 1684234849 'abcd' + ) + (func (export "32s_good3") (param $i i32) (result i64) + (i64.load32_s $mem4 offset=1 align=1 (local.get $i)) ;; 1701077858 'bcde' + ) + (func (export "32s_good4") (param $i i32) (result i64) + (i64.load32_s $mem4 offset=2 align=2 (local.get $i)) ;; 1717920867 'cdef' + ) + (func (export "32s_good5") (param $i i32) (result i64) + (i64.load32_s $mem4 offset=25 align=4 (local.get $i)) ;; 122 'z\0\0\0' + ) + + (func (export "64_good1") (param $i i32) (result i64) + (i64.load $mem4 offset=0 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + (func (export "64_good2") (param $i i32) (result i64) + (i64.load $mem4 align=1 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + (func (export "64_good3") (param $i i32) (result i64) + (i64.load $mem4 offset=1 align=1 (local.get $i)) ;; 0x6968676665646362 'bcdefghi' + ) + (func (export "64_good4") (param $i i32) (result i64) + (i64.load $mem4 offset=2 align=2 (local.get $i)) ;; 0x6a69686766656463 'cdefghij' + ) + (func (export "64_good5") (param $i i32) (result i64) + (i64.load $mem4 offset=25 align=8 (local.get $i)) ;; 122 'z\0\0\0\0\0\0\0' + ) + + (func (export "8u_bad") (param $i i32) + (drop (i64.load8_u $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "8s_bad") (param $i i32) + (drop (i64.load8_s $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "16u_bad") (param $i i32) + (drop (i64.load16_u $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "16s_bad") (param $i i32) + (drop (i64.load16_s $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "32u_bad") (param $i i32) + (drop (i64.load32_u $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "32s_bad") (param $i i32) + (drop (i64.load32_s $mem4 offset=4294967295 (local.get $i))) + ) + (func (export "64_bad") (param $i i32) + (drop (i64.load $mem4 offset=4294967295 (local.get $i))) + ) +) + +(assert_return (invoke "8u_good1" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8u_good2" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8u_good3" (i32.const 0)) (i64.const 98)) +(assert_return (invoke "8u_good4" (i32.const 0)) (i64.const 99)) +(assert_return (invoke "8u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "8s_good1" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8s_good2" (i32.const 0)) (i64.const 97)) +(assert_return (invoke "8s_good3" (i32.const 0)) (i64.const 98)) +(assert_return (invoke "8s_good4" (i32.const 0)) (i64.const 99)) +(assert_return (invoke "8s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "16u_good1" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16u_good2" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16u_good3" (i32.const 0)) (i64.const 25442)) +(assert_return (invoke "16u_good4" (i32.const 0)) (i64.const 25699)) +(assert_return (invoke "16u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "16s_good1" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16s_good2" (i32.const 0)) (i64.const 25185)) +(assert_return (invoke "16s_good3" (i32.const 0)) (i64.const 25442)) +(assert_return (invoke "16s_good4" (i32.const 0)) (i64.const 25699)) +(assert_return (invoke "16s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "32u_good1" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32u_good2" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32u_good3" (i32.const 0)) (i64.const 1701077858)) +(assert_return (invoke "32u_good4" (i32.const 0)) (i64.const 1717920867)) +(assert_return (invoke "32u_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "32s_good1" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32s_good2" (i32.const 0)) (i64.const 1684234849)) +(assert_return (invoke "32s_good3" (i32.const 0)) (i64.const 1701077858)) +(assert_return (invoke "32s_good4" (i32.const 0)) (i64.const 1717920867)) +(assert_return (invoke "32s_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "64_good1" (i32.const 0)) (i64.const 0x6867666564636261)) +(assert_return (invoke "64_good2" (i32.const 0)) (i64.const 0x6867666564636261)) +(assert_return (invoke "64_good3" (i32.const 0)) (i64.const 0x6968676665646362)) +(assert_return (invoke "64_good4" (i32.const 0)) (i64.const 0x6a69686766656463)) +(assert_return (invoke "64_good5" (i32.const 0)) (i64.const 122)) + +(assert_return (invoke "8u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "32u_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32u_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "32s_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "32s_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "64_good1" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good2" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good3" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good4" (i32.const 65503)) (i64.const 0)) +(assert_return (invoke "64_good5" (i32.const 65503)) (i64.const 0)) + +(assert_return (invoke "8u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "8s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "8s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "16u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "16s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "16s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "32u_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32u_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "32s_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good4" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "32s_good5" (i32.const 65504)) (i64.const 0)) + +(assert_return (invoke "64_good1" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good2" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good3" (i32.const 65504)) (i64.const 0)) +(assert_return (invoke "64_good4" (i32.const 65504)) (i64.const 0)) +(assert_trap (invoke "64_good5" (i32.const 65504)) "out of bounds memory access") + +(assert_trap (invoke "8u_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "8s_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "16u_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "16s_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "32u_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "32s_good3" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "64_good3" (i32.const -1)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "64_bad" (i32.const 0)) "out of bounds memory access") + +(assert_trap (invoke "8u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "8s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16u_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "16s_bad" (i32.const 1)) "out of bounds memory access") +(assert_trap (invoke "32u_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "32s_bad" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "64_bad" (i32.const 1)) "out of bounds memory access") + diff --git a/test/core/multi-memory/align0.wast b/test/core/multi-memory/align0.wast new file mode 100644 index 0000000000..f1507087fa --- /dev/null +++ b/test/core/multi-memory/align0.wast @@ -0,0 +1,43 @@ +;; Test aligned and unaligned read/write + +(module + (memory $mem0 0) + (memory $mem1 1) + (memory $mem2 0) + + ;; $default: natural alignment, $1: align=1, $2: align=2, $4: align=4, $8: align=8 + + (func (export "f32_align_switch") (param i32) (result f32) + (local f32 f32) + (local.set 1 (f32.const 10.0)) + (block $4 + (block $2 + (block $1 + (block $default + (block $0 + (br_table $0 $default $1 $2 $4 (local.get 0)) + ) ;; 0 + (f32.store $mem1 (i32.const 0) (local.get 1)) + (local.set 2 (f32.load $mem1 (i32.const 0))) + (br $4) + ) ;; default + (f32.store $mem1 align=1 (i32.const 0) (local.get 1)) + (local.set 2 (f32.load $mem1 align=1 (i32.const 0))) + (br $4) + ) ;; 1 + (f32.store $mem1 align=2 (i32.const 0) (local.get 1)) + (local.set 2 (f32.load $mem1 align=2 (i32.const 0))) + (br $4) + ) ;; 2 + (f32.store $mem1 align=4 (i32.const 0) (local.get 1)) + (local.set 2 (f32.load $mem1 align=4 (i32.const 0))) + ) ;; 4 + (local.get 2) + ) +) + +(assert_return (invoke "f32_align_switch" (i32.const 0)) (f32.const 10.0)) +(assert_return (invoke "f32_align_switch" (i32.const 1)) (f32.const 10.0)) +(assert_return (invoke "f32_align_switch" (i32.const 2)) (f32.const 10.0)) +(assert_return (invoke "f32_align_switch" (i32.const 3)) (f32.const 10.0)) + diff --git a/test/core/multi-memory/binary0.wast b/test/core/multi-memory/binary0.wast new file mode 100644 index 0000000000..88270ac9e8 --- /dev/null +++ b/test/core/multi-memory/binary0.wast @@ -0,0 +1,67 @@ +;; Unsigned LEB128 can have non-minimal length +(module binary + "\00asm" "\01\00\00\00" + "\05\07\02" ;; Memory section with 2 entries + "\00\82\00" ;; no max, minimum 2 + "\00\82\00" ;; no max, minimum 2 +) +(module binary + "\00asm" "\01\00\00\00" + "\05\13\03" ;; Memory section with 3 entries + "\00\83\80\80\80\00" ;; no max, minimum 3 + "\00\84\80\80\80\00" ;; no max, minimum 4 + "\00\85\80\80\80\00" ;; no max, minimum 5 +) + +(module binary + "\00asm" "\01\00\00\00" + "\05\05\02" ;; Memory section with 2 entries + "\00\00" ;; no max, minimum 0 + "\00\00" ;; no max, minimum 0 + "\0b\06\01" ;; Data section with 1 entry + "\00" ;; Memory index 0 + "\41\00\0b\00" ;; (i32.const 0) with contents "" +) + +(module binary + "\00asm" "\01\00\00\00" + "\05\05\02" ;; Memory section with 2 entries + "\00\00" ;; no max, minimum 0 + "\00\01" ;; no max, minimum 1 + "\0b\07\01" ;; Data section with 1 entry + "\02\01" ;; Memory index 1 + "\41\00\0b\00" ;; (i32.const 0) with contents "" +) + +(module binary + "\00asm" "\01\00\00\00" + "\05\05\02" ;; Memory section with 2 entries + "\00\00" ;; no max, minimum 0 + "\00\01" ;; no max, minimum 1 + "\0b\0a\01" ;; Data section with 1 entry + "\02\81\80\80\00" ;; Memory index 1 + "\41\00\0b\00" ;; (i32.const 0) with contents "" +) + +;; Unsigned LEB128 must not be overlong +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\05\10\02" ;; Memory section with 2 entries + "\00\01" ;; no max, minimum 1 + "\00\82\80\80\80\80\80\80\80\80\80\80\00" ;; no max, minimum 2 with one byte too many + ) + "integer representation too long" +) + +;; 2 memories declared, 1 given +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\05\03\02" ;; memory section with inconsistent count (1 declared, 0 given) + "\00\00" ;; memory 0 (missed) + ;; "\00\00" ;; memory 1 (missing) + ) + "unexpected end of section or function" +) + diff --git a/test/core/multi-memory/data0.wast b/test/core/multi-memory/data0.wast new file mode 100644 index 0000000000..2cb95851ce --- /dev/null +++ b/test/core/multi-memory/data0.wast @@ -0,0 +1,73 @@ +;; Test the data section + +;; Syntax + +(module + (memory $mem0 1) + (memory $mem1 1) + (memory $mem2 1) + + (data (i32.const 0)) + (data (i32.const 1) "a" "" "bcd") + (data (offset (i32.const 0))) + (data (offset (i32.const 0)) "" "a" "bc" "") + (data (memory 0) (i32.const 0)) + (data (memory 0x0) (i32.const 1) "a" "" "bcd") + (data (memory 0x000) (offset (i32.const 0))) + (data (memory 0) (offset (i32.const 0)) "" "a" "bc" "") + (data (memory $mem0) (i32.const 0)) + (data (memory $mem1) (i32.const 1) "a" "" "bcd") + (data (memory $mem2) (offset (i32.const 0))) + (data (memory $mem0) (offset (i32.const 0)) "" "a" "bc" "") + + (data $d1 (i32.const 0)) + (data $d2 (i32.const 1) "a" "" "bcd") + (data $d3 (offset (i32.const 0))) + (data $d4 (offset (i32.const 0)) "" "a" "bc" "") + (data $d5 (memory 0) (i32.const 0)) + (data $d6 (memory 0x0) (i32.const 1) "a" "" "bcd") + (data $d7 (memory 0x000) (offset (i32.const 0))) + (data $d8 (memory 0) (offset (i32.const 0)) "" "a" "bc" "") + (data $d9 (memory $mem0) (i32.const 0)) + (data $d10 (memory $mem1) (i32.const 1) "a" "" "bcd") + (data $d11 (memory $mem2) (offset (i32.const 0))) + (data $d12 (memory $mem0) (offset (i32.const 0)) "" "a" "bc" "") +) + +;; Basic use + +(module + (memory 1) + (data (i32.const 0) "a") +) +(module + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (data (memory 0) (i32.const 0) "a") + (data (memory 1) (i32.const 0) "a") + (data (memory 2) (i32.const 0) "a") +) + +(module + (global (import "spectest" "global_i32") i32) + (memory 1) + (data (global.get 0) "a") +) +(module + (global (import "spectest" "global_i32") i32) + (import "spectest" "memory" (memory 1)) + (data (global.get 0) "a") +) + +(module + (global $g (import "spectest" "global_i32") i32) + (memory 1) + (data (global.get $g) "a") +) +(module + (global $g (import "spectest" "global_i32") i32) + (import "spectest" "memory" (memory 1)) + (data (global.get $g) "a") +) + diff --git a/test/core/multi-memory/data1.wast b/test/core/multi-memory/data1.wast new file mode 100644 index 0000000000..8348d5c58a --- /dev/null +++ b/test/core/multi-memory/data1.wast @@ -0,0 +1,146 @@ +;; Invalid bounds for data + +(assert_trap + (module + (memory 1) + (memory 0) + (memory 2) + (data (memory 1) (i32.const 0) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 1 1) + (memory 1 1) + (memory 0 0) + (data (memory 2) (i32.const 0) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 1 1) + (memory 0 1) + (memory 1 1) + (data (memory 1) (i32.const 0) "a") + ) + "out of bounds memory access" +) +(assert_trap + (module + (memory 1) + (memory 1) + (memory 0) + (data (memory 2) (i32.const 1)) + ) + "out of bounds memory access" +) +(assert_trap + (module + (memory 1 1) + (memory 1 1) + (memory 0 1) + (data (memory 2) (i32.const 1)) + ) + "out of bounds memory access" +) + +;; This seems to cause a time-out on Travis. +(;assert_unlinkable + (module + (memory 0x10000) + (data (i32.const 0xffffffff) "ab") + ) + "" ;; either out of memory or out of bounds +;) + +(assert_trap + (module + (global (import "spectest" "global_i32") i32) + (memory 3) + (memory 0) + (memory 3) + (data (memory 1) (global.get 0) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 2 2) + (memory 1 2) + (memory 2 2) + (data (memory 1) (i32.const 0x1_0000) "a") + ) + "out of bounds memory access" +) +(assert_trap + (module + (import "spectest" "memory" (memory 1)) + (data (i32.const 0x1_0000) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 3) + (memory 3) + (memory 2) + (data (memory 2) (i32.const 0x2_0000) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 3 3) + (memory 2 3) + (memory 3 3) + (data (memory 1) (i32.const 0x2_0000) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 0) + (memory 0) + (memory 1) + (data (memory 2) (i32.const -1) "a") + ) + "out of bounds memory access" +) +(assert_trap + (module + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (data (memory 2) (i32.const -1) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory 2) + (memory 2) + (memory 2) + (data (memory 2) (i32.const -100) "a") + ) + "out of bounds memory access" +) +(assert_trap + (module + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 1)) + (data (memory 3) (i32.const -100) "a") + ) + "out of bounds memory access" +) + diff --git a/test/core/multi-memory/data_drop0.wast b/test/core/multi-memory/data_drop0.wast new file mode 100644 index 0000000000..b617b10eaf --- /dev/null +++ b/test/core/multi-memory/data_drop0.wast @@ -0,0 +1,28 @@ +;; data.drop +(module + (memory $mem0 0) + (memory $mem1 1) + (memory $mem2 0) + (data $p "x") + (data $a (memory 1) (i32.const 0) "x") + + (func (export "drop_passive") (data.drop $p)) + (func (export "init_passive") (param $len i32) + (memory.init $mem1 $p (i32.const 0) (i32.const 0) (local.get $len))) + + (func (export "drop_active") (data.drop $a)) + (func (export "init_active") (param $len i32) + (memory.init $mem1 $a (i32.const 0) (i32.const 0) (local.get $len))) +) + +(invoke "init_passive" (i32.const 1)) +(invoke "drop_passive") +(invoke "drop_passive") +(assert_return (invoke "init_passive" (i32.const 0))) +(assert_trap (invoke "init_passive" (i32.const 1)) "out of bounds memory access") +(invoke "init_passive" (i32.const 0)) +(invoke "drop_active") +(assert_return (invoke "init_active" (i32.const 0))) +(assert_trap (invoke "init_active" (i32.const 1)) "out of bounds memory access") +(invoke "init_active" (i32.const 0)) + diff --git a/test/core/multi-memory/exports0.wast b/test/core/multi-memory/exports0.wast new file mode 100644 index 0000000000..6eb94d13b5 --- /dev/null +++ b/test/core/multi-memory/exports0.wast @@ -0,0 +1,51 @@ +;; Memories + +(module (memory 0) (export "a" (memory 0))) +(module (memory 0) (export "a" (memory 0)) (export "b" (memory 0))) +(module (memory 0) (memory 0) (export "a" (memory 0)) (export "b" (memory 1))) +(module + (memory $mem0 0) + (memory $mem1 0) + (memory $mem2 0) + (memory $mem3 0) + (memory $mem4 0) + (memory $mem5 0) + (memory $mem6 0) + + (export "a" (memory $mem0)) + (export "b" (memory $mem1)) + (export "ac" (memory $mem2)) + (export "bc" (memory $mem3)) + (export "ad" (memory $mem4)) + (export "bd" (memory $mem5)) + (export "be" (memory $mem6)) + + (export "za" (memory $mem0)) + (export "zb" (memory $mem1)) + (export "zac" (memory $mem2)) + (export "zbc" (memory $mem3)) + (export "zad" (memory $mem4)) + (export "zbd" (memory $mem5)) + (export "zbe" (memory $mem6)) +) + +(module + (export "a" (memory 0)) + (memory 6) + + (export "b" (memory 1)) + (memory 3) +) + +(module + (export "a" (memory 0)) + (memory 0 1) + (memory 0 1) + (memory 0 1) + (memory 0 1) + + (export "b" (memory 3)) +) +(module (export "a" (memory $a)) (memory $a 0)) +(module (export "a" (memory $a)) (memory $a 0 1)) + diff --git a/test/core/multi-memory/float_exprs0.wast b/test/core/multi-memory/float_exprs0.wast new file mode 100644 index 0000000000..8b40b349dd --- /dev/null +++ b/test/core/multi-memory/float_exprs0.wast @@ -0,0 +1,38 @@ +(module + (memory 0 0) + (memory $m 1 1) + (memory 0 0) + (func (export "init") (param $i i32) (param $x f64) + (f64.store $m (local.get $i) (local.get $x))) + + (func (export "run") (param $n i32) (param $z f64) + (local $i i32) + (block $exit + (loop $cont + (f64.store $m + (local.get $i) + (f64.div (f64.load $m (local.get $i)) (local.get $z)) + ) + (local.set $i (i32.add (local.get $i) (i32.const 8))) + (br_if $cont (i32.lt_u (local.get $i) (local.get $n))) + ) + ) + ) + + (func (export "check") (param $i i32) (result f64) (f64.load $m (local.get $i))) +) + +(invoke "init" (i32.const 0) (f64.const 15.1)) +(invoke "init" (i32.const 8) (f64.const 15.2)) +(invoke "init" (i32.const 16) (f64.const 15.3)) +(invoke "init" (i32.const 24) (f64.const 15.4)) +(assert_return (invoke "check" (i32.const 0)) (f64.const 15.1)) +(assert_return (invoke "check" (i32.const 8)) (f64.const 15.2)) +(assert_return (invoke "check" (i32.const 16)) (f64.const 15.3)) +(assert_return (invoke "check" (i32.const 24)) (f64.const 15.4)) +(invoke "run" (i32.const 32) (f64.const 3.0)) +(assert_return (invoke "check" (i32.const 0)) (f64.const 0x1.4222222222222p+2)) +(assert_return (invoke "check" (i32.const 8)) (f64.const 0x1.4444444444444p+2)) +(assert_return (invoke "check" (i32.const 16)) (f64.const 0x1.4666666666667p+2)) +(assert_return (invoke "check" (i32.const 24)) (f64.const 0x1.4888888888889p+2)) + diff --git a/test/core/multi-memory/float_exprs1.wast b/test/core/multi-memory/float_exprs1.wast new file mode 100644 index 0000000000..834696acd7 --- /dev/null +++ b/test/core/multi-memory/float_exprs1.wast @@ -0,0 +1,105 @@ +;; Test that plain summation is not reassociated, and that Kahan summation +;; isn't optimized into plain summation. + +(module + (memory 0 0) + (memory 0 0) + (memory 0 0) + (memory 0 0) + (memory 0 0) + (memory $m (data + "\c4\c5\57\24\a5\84\c8\0b\6d\b8\4b\2e\f2\76\17\1c\ca\4a\56\1e\1b\6e\71\22" + "\5d\17\1e\6e\bf\cd\14\5c\c7\21\55\51\39\9c\1f\b2\51\f0\a3\93\d7\c1\2c\ae" + "\7e\a8\28\3a\01\21\f4\0a\58\93\f8\42\77\9f\83\39\6a\5f\ba\f7\0a\d8\51\6a" + "\34\ca\ad\c6\34\0e\d8\26\dc\4c\33\1c\ed\29\90\a8\78\0f\d1\ce\76\31\23\83" + "\b8\35\e8\f2\44\b0\d3\a1\fc\bb\32\e1\b0\ba\69\44\09\d6\d9\7d\ff\2e\c0\5a" + "\36\14\33\14\3e\a9\fa\87\6d\8b\bc\ce\9d\a7\fd\c4\e9\85\3f\dd\d7\e1\18\a6" + "\50\26\72\6e\3f\73\0f\f8\12\93\23\34\61\76\12\48\c0\9b\05\93\eb\ac\86\de" + "\94\3e\55\e8\8c\e8\dd\e4\fc\95\47\be\56\03\21\20\4c\e6\bf\7b\f6\7f\d5\ba" + "\73\1c\c1\14\8f\c4\27\96\b3\bd\33\ff\78\41\5f\c0\5a\ce\f6\67\6e\73\9a\17" + "\66\70\03\f8\ce\27\a3\52\b2\9f\3b\bf\fb\ae\ed\d3\5a\f8\37\57\f0\f5\6e\ef" + "\b1\4d\70\3d\54\a7\01\9a\85\08\48\91\f5\9d\0c\60\87\5b\d9\54\1e\51\6d\88" + "\8e\08\8c\a5\71\3a\56\08\67\46\8f\8f\13\2a\2c\ec\2c\1f\b4\62\2b\6f\41\0a" + "\c4\65\42\a2\31\6b\2c\7d\3e\bb\75\ac\86\97\30\d9\48\cd\9a\1f\56\c4\c6\e4" + "\12\c0\9d\fb\ee\02\8c\ce\1c\f2\1e\a1\78\23\db\c4\1e\49\03\d3\71\cc\08\50" + "\c5\d8\5c\ed\d5\b5\65\ac\b5\c9\21\d2\c9\29\76\de\f0\30\1a\5b\3c\f2\3b\db" + "\3a\39\82\3a\16\08\6f\a8\f1\be\69\69\99\71\a6\05\d3\14\93\2a\16\f2\2f\11" + "\c7\7e\20\bb\91\44\ee\f8\e4\01\53\c0\b9\7f\f0\bf\f0\03\9c\6d\b1\df\a2\44" + "\01\6d\6b\71\2b\5c\b3\21\19\46\5e\8f\db\91\d3\7c\78\6b\b7\12\00\8f\eb\bd" + "\8a\f5\d4\2e\c4\c1\1e\df\73\63\59\47\49\03\0a\b7\cf\24\cf\9c\0e\44\7a\9e" + "\14\fb\42\bf\9d\39\30\9e\a0\ab\2f\d1\ae\9e\6a\83\43\e3\55\7d\85\bf\63\8a" + "\f8\96\10\1f\fe\6d\e7\22\1b\e1\69\46\8a\44\c8\c8\f9\0c\2b\19\07\a5\02\3e" + "\f2\30\10\9a\85\8a\5f\ef\81\45\a0\77\b1\03\10\73\4b\ae\98\9d\47\bf\9a\2d" + "\3a\d5\0f\03\66\e3\3d\53\d9\40\ce\1f\6f\32\2f\21\2b\23\21\6c\62\d4\a7\3e" + "\a8\ce\28\31\2d\00\3d\67\5e\af\a0\cf\2e\d2\b9\6b\84\eb\69\08\3c\62\36\be" + "\12\fd\36\7f\88\3e\ad\bc\0b\c0\41\c4\50\b6\e3\50\31\e8\ce\e2\96\65\55\9c" + "\16\46\e6\b0\2d\3a\e8\81\05\b0\bf\34\f7\bc\10\1c\fb\cc\3c\f1\85\97\42\9f" + "\eb\14\8d\3c\bf\d7\17\88\49\9d\8b\2b\b2\3a\83\d1\4f\04\9e\a1\0f\ad\08\9d" + "\54\af\d1\82\c3\ec\32\2f\02\8f\05\21\2d\a2\b7\e4\f4\6f\2e\81\2b\0b\9c\fc" + "\cb\fe\74\02\f9\db\f4\f3\ea\00\a8\ec\d1\99\74\26\dd\d6\34\d5\25\b1\46\dd" + "\9c\aa\71\f5\60\b0\88\c8\e0\0b\59\5a\25\4f\29\66\f9\e3\2e\fe\e9\da\e5\18" + "\4f\27\62\f4\ce\a4\21\95\74\c7\57\64\27\9a\4c\fd\54\7d\61\ce\c3\ac\87\46" + "\9c\fa\ff\09\ca\79\97\67\24\74\ca\d4\21\83\26\25\19\12\37\64\19\e5\65\e0" + "\74\75\8e\dd\c8\ef\74\c7\d8\21\2b\79\04\51\46\65\60\03\5d\fa\d8\f4\65\a4" + "\9e\5d\23\da\d7\8a\92\80\a4\de\78\3c\f1\57\42\6d\cd\c9\2f\d5\a4\9e\ab\40" + "\f4\cb\1b\d7\a3\ca\fc\eb\a7\01\b2\9a\69\4e\46\9b\18\4e\dd\79\a7\aa\a6\52" + "\39\1e\ef\30\cc\9b\bd\5b\ee\4c\21\6d\30\00\72\b0\46\5f\08\cf\c5\b9\e0\3e" + "\c2\b3\0c\dc\8e\64\de\19\42\79\cf\43\ea\43\5d\8e\88\f7\ab\15\dc\3f\c8\67" + "\20\db\b8\64\b1\47\1f\de\f2\cb\3f\59\9f\d8\46\90\dc\ae\2f\22\f9\e2\31\89" + "\d9\9c\1c\4c\d3\a9\4a\57\84\9c\9f\ea\2c\3c\ae\3c\c3\1e\8b\e5\4e\17\01\25" + "\db\34\46\5f\15\ea\05\0c\7c\d9\45\8c\19\d0\73\8a\96\16\dd\44\f9\05\b7\5b" + "\71\b0\e6\21\36\5f\75\89\91\73\75\ab\7d\ae\d3\73\ec\37\c6\ea\55\75\ef\ea" + "\ab\8b\7b\11\dc\6d\1a\b2\6a\c4\25\cf\aa\e3\9f\49\49\89\cb\37\9b\0a\a7\01" + "\60\70\dc\b7\c8\83\e1\42\f5\be\ad\62\94\ad\8d\a1" + )) + (memory 0 0) + (memory 0 0) + (memory 0 0) + + (func (export "f32.kahan_sum") (param $p i32) (param $n i32) (result f32) + (local $sum f32) + (local $c f32) + (local $t f32) + (block $exit + (loop $top + (local.set $t + (f32.sub + (f32.sub + (local.tee $sum + (f32.add + (local.get $c) + (local.tee $t + (f32.sub (f32.load $m (local.get $p)) (local.get $t)) + ) + ) + ) + (local.get $c) + ) + (local.get $t) + ) + ) + (local.set $p (i32.add (local.get $p) (i32.const 4))) + (local.set $c (local.get $sum)) + (br_if $top (local.tee $n (i32.add (local.get $n) (i32.const -1)))) + ) + ) + (local.get $sum) + ) + + (func (export "f32.plain_sum") (param $p i32) (param $n i32) (result f32) + (local $sum f32) + (block $exit + (loop $top + (local.set $sum (f32.add (local.get $sum) (f32.load $m (local.get $p)))) + (local.set $p (i32.add (local.get $p) (i32.const 4))) + (local.set $n (i32.add (local.get $n) (i32.const -1))) + (br_if $top (local.get $n)) + ) + ) + (local.get $sum) + ) +) + +(assert_return (invoke "f32.kahan_sum" (i32.const 0) (i32.const 256)) (f32.const -0x1.101a1ap+104)) +(assert_return (invoke "f32.plain_sum" (i32.const 0) (i32.const 256)) (f32.const -0x1.a0343ap+103)) + diff --git a/test/core/multi-memory/float_memory0.wast b/test/core/multi-memory/float_memory0.wast new file mode 100644 index 0000000000..00e22dfb0a --- /dev/null +++ b/test/core/multi-memory/float_memory0.wast @@ -0,0 +1,60 @@ +;; Test that floating-point load and store are bit-preserving. + +;; Test that load and store do not canonicalize NaNs as x87 does. + +(module + (memory 0 0) + (memory 0 0) + (memory 0 0) + (memory $m (data "\00\00\a0\7f")) + (memory 0 0) + (memory 0 0) + + (func (export "f32.load") (result f32) (f32.load $m (i32.const 0))) + (func (export "i32.load") (result i32) (i32.load $m (i32.const 0))) + (func (export "f32.store") (f32.store $m (i32.const 0) (f32.const nan:0x200000))) + (func (export "i32.store") (i32.store $m (i32.const 0) (i32.const 0x7fa00000))) + (func (export "reset") (i32.store $m (i32.const 0) (i32.const 0))) +) + +(assert_return (invoke "i32.load") (i32.const 0x7fa00000)) +(assert_return (invoke "f32.load") (f32.const nan:0x200000)) +(invoke "reset") +(assert_return (invoke "i32.load") (i32.const 0x0)) +(assert_return (invoke "f32.load") (f32.const 0.0)) +(invoke "f32.store") +(assert_return (invoke "i32.load") (i32.const 0x7fa00000)) +(assert_return (invoke "f32.load") (f32.const nan:0x200000)) +(invoke "reset") +(assert_return (invoke "i32.load") (i32.const 0x0)) +(assert_return (invoke "f32.load") (f32.const 0.0)) +(invoke "i32.store") +(assert_return (invoke "i32.load") (i32.const 0x7fa00000)) +(assert_return (invoke "f32.load") (f32.const nan:0x200000)) + +(module + (memory 0 0) + (memory $m (data "\00\00\00\00\00\00\f4\7f")) + + (func (export "f64.load") (result f64) (f64.load $m (i32.const 0))) + (func (export "i64.load") (result i64) (i64.load $m (i32.const 0))) + (func (export "f64.store") (f64.store $m (i32.const 0) (f64.const nan:0x4000000000000))) + (func (export "i64.store") (i64.store $m (i32.const 0) (i64.const 0x7ff4000000000000))) + (func (export "reset") (i64.store $m (i32.const 0) (i64.const 0))) +) + +(assert_return (invoke "i64.load") (i64.const 0x7ff4000000000000)) +(assert_return (invoke "f64.load") (f64.const nan:0x4000000000000)) +(invoke "reset") +(assert_return (invoke "i64.load") (i64.const 0x0)) +(assert_return (invoke "f64.load") (f64.const 0.0)) +(invoke "f64.store") +(assert_return (invoke "i64.load") (i64.const 0x7ff4000000000000)) +(assert_return (invoke "f64.load") (f64.const nan:0x4000000000000)) +(invoke "reset") +(assert_return (invoke "i64.load") (i64.const 0x0)) +(assert_return (invoke "f64.load") (f64.const 0.0)) +(invoke "i64.store") +(assert_return (invoke "i64.load") (i64.const 0x7ff4000000000000)) +(assert_return (invoke "f64.load") (f64.const nan:0x4000000000000)) + diff --git a/test/core/multi-memory/imports0.wast b/test/core/multi-memory/imports0.wast new file mode 100644 index 0000000000..ce827a2d2c --- /dev/null +++ b/test/core/multi-memory/imports0.wast @@ -0,0 +1,45 @@ +(module + (func (export "func")) + (func (export "func-i32") (param i32)) + (func (export "func-f32") (param f32)) + (func (export "func->i32") (result i32) (i32.const 22)) + (func (export "func->f32") (result f32) (f32.const 11)) + (func (export "func-i32->i32") (param i32) (result i32) (local.get 0)) + (func (export "func-i64->i64") (param i64) (result i64) (local.get 0)) + (global (export "global-i32") i32 (i32.const 55)) + (global (export "global-f32") f32 (f32.const 44)) + (global (export "global-mut-i64") (mut i64) (i64.const 66)) + (table (export "table-10-inf") 10 funcref) + (table (export "table-10-20") 10 20 funcref) + (memory (export "memory-2-inf") 2) + (memory (export "memory-2-4") 2 4) +) + +(register "test") + +(assert_unlinkable + (module (import "test" "memory-2-inf" (func))) + "incompatible import type" +) +(assert_unlinkable + (module (import "test" "memory-2-4" (func))) + "incompatible import type" +) + +(assert_unlinkable + (module (import "test" "memory-2-inf" (global i32))) + "incompatible import type" +) +(assert_unlinkable + (module (import "test" "memory-2-4" (global i32))) + "incompatible import type" +) + +(assert_unlinkable + (module (import "test" "memory-2-inf" (table 10 funcref))) + "incompatible import type" +) +(assert_unlinkable + (module (import "test" "memory-2-4" (table 10 funcref))) + "incompatible import type" +) diff --git a/test/core/multi-memory/imports1.wast b/test/core/multi-memory/imports1.wast new file mode 100644 index 0000000000..bae0f80087 --- /dev/null +++ b/test/core/multi-memory/imports1.wast @@ -0,0 +1,16 @@ +(module + (import "spectest" "memory" (memory 1 2)) + (import "spectest" "memory" (memory 1 2)) + (memory $m (import "spectest" "memory") 1 2) + (import "spectest" "memory" (memory 1 2)) + + (data (memory 2) (i32.const 10) "\10") + + (func (export "load") (param i32) (result i32) (i32.load $m (local.get 0))) +) + +(assert_return (invoke "load" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "load" (i32.const 10)) (i32.const 16)) +(assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000)) +(assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access") + diff --git a/test/core/multi-memory/imports2.wast b/test/core/multi-memory/imports2.wast new file mode 100644 index 0000000000..314bc131d0 --- /dev/null +++ b/test/core/multi-memory/imports2.wast @@ -0,0 +1,73 @@ +(module + (memory (export "z") 0 0) + (memory (export "memory-2-inf") 2) + (memory (export "memory-2-4") 2 4) +) + +(register "test") + +(module + (import "test" "z" (memory 0)) + (memory $m (import "spectest" "memory") 1 2) + (data (memory 1) (i32.const 10) "\10") + + (func (export "load") (param i32) (result i32) (i32.load $m (local.get 0))) +) + +(assert_return (invoke "load" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "load" (i32.const 10)) (i32.const 16)) +(assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000)) +(assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access") + +(module + (memory (import "spectest" "memory") 1 2) + (data (memory 0) (i32.const 10) "\10") + + (func (export "load") (param i32) (result i32) (i32.load (local.get 0))) +) +(assert_return (invoke "load" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "load" (i32.const 10)) (i32.const 16)) +(assert_return (invoke "load" (i32.const 8)) (i32.const 0x100000)) +(assert_trap (invoke "load" (i32.const 1000000)) "out of bounds memory access") + +(module + (import "test" "memory-2-inf" (memory 2)) + (import "test" "memory-2-inf" (memory 1)) + (import "test" "memory-2-inf" (memory 0)) +) + +(module + (import "spectest" "memory" (memory 1)) + (import "spectest" "memory" (memory 0)) + (import "spectest" "memory" (memory 1 2)) + (import "spectest" "memory" (memory 0 2)) + (import "spectest" "memory" (memory 1 3)) + (import "spectest" "memory" (memory 0 3)) +) + +(assert_unlinkable + (module (import "test" "unknown" (memory 1))) + "unknown import" +) +(assert_unlinkable + (module (import "spectest" "unknown" (memory 1))) + "unknown import" +) + +(assert_unlinkable + (module (import "test" "memory-2-inf" (memory 3))) + "incompatible import type" +) +(assert_unlinkable + (module (import "test" "memory-2-inf" (memory 2 3))) + "incompatible import type" +) +(assert_unlinkable + (module (import "spectest" "memory" (memory 2))) + "incompatible import type" +) +(assert_unlinkable + (module (import "spectest" "memory" (memory 1 1))) + "incompatible import type" +) + diff --git a/test/core/multi-memory/imports3.wast b/test/core/multi-memory/imports3.wast new file mode 100644 index 0000000000..37cc4eb87b --- /dev/null +++ b/test/core/multi-memory/imports3.wast @@ -0,0 +1,75 @@ +(module + (func (export "func")) + (func (export "func-i32") (param i32)) + (func (export "func-f32") (param f32)) + (func (export "func->i32") (result i32) (i32.const 22)) + (func (export "func->f32") (result f32) (f32.const 11)) + (func (export "func-i32->i32") (param i32) (result i32) (local.get 0)) + (func (export "func-i64->i64") (param i64) (result i64) (local.get 0)) + (global (export "global-i32") i32 (i32.const 55)) + (global (export "global-f32") f32 (f32.const 44)) + (global (export "global-mut-i64") (mut i64) (i64.const 66)) + (table (export "table-10-inf") 10 funcref) + (table (export "table-10-20") 10 20 funcref) + (memory (export "memory-2-inf") 2) + (memory (export "memory-2-4") 2 4) +) + +(register "test") +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "test" "func-i32" (memory 1)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "test" "global-i32" (memory 1)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "test" "table-10-inf" (memory 1)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "spectest" "print_i32" (memory 1)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "spectest" "global_i32" (memory 1)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "spectest" "table" (memory 1)) + ) + "incompatible import type" +) + +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "spectest" "memory" (memory 2)) + ) + "incompatible import type" +) +(assert_unlinkable + (module + (import "test" "memory-2-4" (memory 1)) + (import "spectest" "memory" (memory 1 1)) + ) + "incompatible import type" +) diff --git a/test/core/multi-memory/imports4.wast b/test/core/multi-memory/imports4.wast new file mode 100644 index 0000000000..411b1c0f40 --- /dev/null +++ b/test/core/multi-memory/imports4.wast @@ -0,0 +1,47 @@ +(module + (memory (export "memory-2-inf") 2) + (memory (export "memory-2-4") 2 4) +) + +(register "test") + +(module + (import "test" "memory-2-4" (memory 1)) + (memory $m (import "spectest" "memory") 0 3) ;; actual has max size 2 + (func (export "grow") (param i32) (result i32) (memory.grow $m (local.get 0))) +) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 2)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 2)) + +(module $Mgm + (memory 0) + (memory 0) + (memory $m (export "memory") 1) ;; initial size is 1 + (func (export "grow") (result i32) (memory.grow $m (i32.const 1))) +) +(register "grown-memory" $Mgm) +(assert_return (invoke $Mgm "grow") (i32.const 1)) ;; now size is 2 + +(module $Mgim1 + ;; imported memory limits should match, because external memory size is 2 now + (import "test" "memory-2-4" (memory 1)) + (memory $m (export "memory") (import "grown-memory" "memory") 2) + (memory 0) + (memory 0) + (func (export "grow") (result i32) (memory.grow $m (i32.const 1))) +) +(register "grown-imported-memory" $Mgim1) +(assert_return (invoke $Mgim1 "grow") (i32.const 2)) ;; now size is 3 + +(module $Mgim2 + ;; imported memory limits should match, because external memory size is 3 now + (import "test" "memory-2-4" (memory 1)) + (memory $m (import "grown-imported-memory" "memory") 3) + (memory 0) + (memory 0) + (func (export "size") (result i32) (memory.size $m)) +) +(assert_return (invoke $Mgim2 "size") (i32.const 3)) diff --git a/test/core/multi-memory/linking0.wast b/test/core/multi-memory/linking0.wast new file mode 100644 index 0000000000..b09c69f6ab --- /dev/null +++ b/test/core/multi-memory/linking0.wast @@ -0,0 +1,42 @@ +(module $Mt + (type (func (result i32))) + (type (func)) + + (table (export "tab") 10 funcref) + (elem (i32.const 2) $g $g $g $g) + (func $g (result i32) (i32.const 4)) + (func (export "h") (result i32) (i32.const -4)) + + (func (export "call") (param i32) (result i32) + (call_indirect (type 0) (local.get 0)) + ) +) +(register "Mt" $Mt) + +(assert_unlinkable + (module + (table (import "Mt" "tab") 10 funcref) + (memory (import "spectest" "memory") 1) + (memory (import "Mt" "mem") 1) ;; does not exist + (func $f (result i32) (i32.const 0)) + (elem (i32.const 7) $f) + (elem (i32.const 9) $f) + ) + "unknown import" +) +(assert_trap (invoke $Mt "call" (i32.const 7)) "uninitialized element") + + +(assert_trap + (module + (table (import "Mt" "tab") 10 funcref) + (func $f (result i32) (i32.const 0)) + (elem (i32.const 7) $f) + (memory 0) + (memory $m 1) + (memory 0) + (data $m (i32.const 0x10000) "d") ;; out of bounds + ) + "out of bounds memory access" +) +(assert_return (invoke $Mt "call" (i32.const 7)) (i32.const 0)) diff --git a/test/core/multi-memory/linking1.wast b/test/core/multi-memory/linking1.wast new file mode 100644 index 0000000000..39eabb00b0 --- /dev/null +++ b/test/core/multi-memory/linking1.wast @@ -0,0 +1,65 @@ +(module $Mm + (memory $mem0 (export "mem0") 0 0) + (memory $mem1 (export "mem1") 1 5) + (memory $mem2 (export "mem2") 0 0) + + (data (memory 1) (i32.const 10) "\00\01\02\03\04\05\06\07\08\09") + + (func (export "load") (param $a i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) +) +(register "Mm" $Mm) + +(module $Nm + (func $loadM (import "Mm" "load") (param i32) (result i32)) + (memory (import "Mm" "mem0") 0) + + (memory $m 1) + (data (memory 1) (i32.const 10) "\f0\f1\f2\f3\f4\f5") + + (export "Mm.load" (func $loadM)) + (func (export "load") (param $a i32) (result i32) + (i32.load8_u $m (local.get 0)) + ) +) + +(assert_return (invoke $Mm "load" (i32.const 12)) (i32.const 2)) +(assert_return (invoke $Nm "Mm.load" (i32.const 12)) (i32.const 2)) +(assert_return (invoke $Nm "load" (i32.const 12)) (i32.const 0xf2)) + +(module $Om + (memory (import "Mm" "mem1") 1) + (data (i32.const 5) "\a0\a1\a2\a3\a4\a5\a6\a7") + + (func (export "load") (param $a i32) (result i32) + (i32.load8_u (local.get 0)) + ) +) + +(assert_return (invoke $Mm "load" (i32.const 12)) (i32.const 0xa7)) +(assert_return (invoke $Nm "Mm.load" (i32.const 12)) (i32.const 0xa7)) +(assert_return (invoke $Nm "load" (i32.const 12)) (i32.const 0xf2)) +(assert_return (invoke $Om "load" (i32.const 12)) (i32.const 0xa7)) + +(module + (memory (import "Mm" "mem1") 0) + (data (i32.const 0xffff) "a") +) + +(assert_trap + (module + (memory (import "Mm" "mem0") 0) + (data (i32.const 0xffff) "a") + ) + "out of bounds memory access" +) + +(assert_trap + (module + (memory (import "Mm" "mem1") 0) + (data (i32.const 0x10000) "a") + ) + "out of bounds memory access" +) + diff --git a/test/core/multi-memory/linking2.wast b/test/core/multi-memory/linking2.wast new file mode 100644 index 0000000000..26bf3cca2a --- /dev/null +++ b/test/core/multi-memory/linking2.wast @@ -0,0 +1,30 @@ +(module $Mm + (memory $mem0 (export "mem0") 0 0) + (memory $mem1 (export "mem1") 1 5) + (memory $mem2 (export "mem2") 0 0) + + (data (memory 1) (i32.const 10) "\00\01\02\03\04\05\06\07\08\09") + + (func (export "load") (param $a i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) +) +(register "Mm" $Mm) + +(module $Pm + (memory (import "Mm" "mem1") 1 8) + + (func (export "grow") (param $a i32) (result i32) + (memory.grow (local.get 0)) + ) +) + +(assert_return (invoke $Pm "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke $Pm "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke $Pm "grow" (i32.const 0)) (i32.const 3)) +(assert_return (invoke $Pm "grow" (i32.const 1)) (i32.const 3)) +(assert_return (invoke $Pm "grow" (i32.const 1)) (i32.const 4)) +(assert_return (invoke $Pm "grow" (i32.const 0)) (i32.const 5)) +(assert_return (invoke $Pm "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke $Pm "grow" (i32.const 0)) (i32.const 5)) + diff --git a/test/core/multi-memory/linking3.wast b/test/core/multi-memory/linking3.wast new file mode 100644 index 0000000000..e23fbe4e6c --- /dev/null +++ b/test/core/multi-memory/linking3.wast @@ -0,0 +1,83 @@ +(module $Mm + (memory $mem0 (export "mem0") 0 0) + (memory $mem1 (export "mem1") 5 5) + (memory $mem2 (export "mem2") 0 0) + + (data (memory 1) (i32.const 10) "\00\01\02\03\04\05\06\07\08\09") + + (func (export "load") (param $a i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) +) +(register "Mm" $Mm) + +(assert_unlinkable + (module + (func $host (import "spectest" "print")) + (memory (import "Mm" "mem1") 1) + (table (import "Mm" "tab") 0 funcref) ;; does not exist + (data (i32.const 0) "abc") + ) + "unknown import" +) +(assert_return (invoke $Mm "load" (i32.const 0)) (i32.const 0)) + +;; Unlike in v1 spec, active data segments written before an +;; out-of-bounds access persist after the instantiation failure. +(assert_trap + (module + ;; Note: the memory is 5 pages large by the time we get here. + (memory (import "Mm" "mem1") 1) + (data (i32.const 0) "abc") + (data (i32.const 327670) "zzzzzzzzzzzzzzzzzz") ;; (partially) out of bounds + ) + "out of bounds memory access" +) +(assert_return (invoke $Mm "load" (i32.const 0)) (i32.const 97)) +(assert_return (invoke $Mm "load" (i32.const 327670)) (i32.const 0)) + +(assert_trap + (module + (memory (import "Mm" "mem1") 1) + (data (i32.const 0) "abc") + (table 0 funcref) + (func) + (elem (i32.const 0) 0) ;; out of bounds + ) + "out of bounds table access" +) +(assert_return (invoke $Mm "load" (i32.const 0)) (i32.const 97)) + +;; Store is modified if the start function traps. +(module $Ms + (type $t (func (result i32))) + (memory (export "memory") 1) + (table (export "table") 1 funcref) + (func (export "get memory[0]") (type $t) + (i32.load8_u (i32.const 0)) + ) + (func (export "get table[0]") (type $t) + (call_indirect (type $t) (i32.const 0)) + ) +) +(register "Ms" $Ms) + +(assert_trap + (module + (import "Ms" "memory" (memory 1)) + (import "Ms" "table" (table 1 funcref)) + (data (i32.const 0) "hello") + (elem (i32.const 0) $f) + (func $f (result i32) + (i32.const 0xdead) + ) + (func $main + (unreachable) + ) + (start $main) + ) + "unreachable" +) + +(assert_return (invoke $Ms "get memory[0]") (i32.const 104)) ;; 'h' +(assert_return (invoke $Ms "get table[0]") (i32.const 0xdead)) diff --git a/test/core/multi-memory/load0.wast b/test/core/multi-memory/load0.wast new file mode 100644 index 0000000000..2332a20087 --- /dev/null +++ b/test/core/multi-memory/load0.wast @@ -0,0 +1,19 @@ +;; Multiple memories + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (data (memory $mem1) (i32.const 0) "\01") + (data (memory $mem2) (i32.const 0) "\02") +) + +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) diff --git a/test/core/multi-memory/load1.wast b/test/core/multi-memory/load1.wast new file mode 100644 index 0000000000..be309c39cb --- /dev/null +++ b/test/core/multi-memory/load1.wast @@ -0,0 +1,41 @@ +(module $M + (memory (export "mem") 2) + + (func (export "read") (param i32) (result i32) + (i32.load8_u (local.get 0)) + ) +) +(register "M") + +(module + (memory $mem1 (import "M" "mem") 2) + (memory $mem2 3) + + (data (memory $mem1) (i32.const 20) "\01\02\03\04\05") + (data (memory $mem2) (i32.const 50) "\0A\0B\0C\0D\0E") + + (func (export "read1") (param i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) + (func (export "read2") (param i32) (result i32) + (i32.load8_u $mem2 (local.get 0)) + ) +) + +(assert_return (invoke $M "read" (i32.const 20)) (i32.const 1)) +(assert_return (invoke $M "read" (i32.const 21)) (i32.const 2)) +(assert_return (invoke $M "read" (i32.const 22)) (i32.const 3)) +(assert_return (invoke $M "read" (i32.const 23)) (i32.const 4)) +(assert_return (invoke $M "read" (i32.const 24)) (i32.const 5)) + +(assert_return (invoke "read1" (i32.const 20)) (i32.const 1)) +(assert_return (invoke "read1" (i32.const 21)) (i32.const 2)) +(assert_return (invoke "read1" (i32.const 22)) (i32.const 3)) +(assert_return (invoke "read1" (i32.const 23)) (i32.const 4)) +(assert_return (invoke "read1" (i32.const 24)) (i32.const 5)) + +(assert_return (invoke "read2" (i32.const 50)) (i32.const 10)) +(assert_return (invoke "read2" (i32.const 51)) (i32.const 11)) +(assert_return (invoke "read2" (i32.const 52)) (i32.const 12)) +(assert_return (invoke "read2" (i32.const 53)) (i32.const 13)) +(assert_return (invoke "read2" (i32.const 54)) (i32.const 14)) diff --git a/test/core/multi-memory/load2.wast b/test/core/multi-memory/load2.wast new file mode 100644 index 0000000000..dfe6fed763 --- /dev/null +++ b/test/core/multi-memory/load2.wast @@ -0,0 +1,213 @@ +(module + (memory 0) + (memory 0) + (memory 0) + (memory $m 1) + + (func (export "as-br-value") (result i32) + (block (result i32) (br 0 (i32.load $m (i32.const 0)))) + ) + + (func (export "as-br_if-cond") + (block (br_if 0 (i32.load $m (i32.const 0)))) + ) + (func (export "as-br_if-value") (result i32) + (block (result i32) + (drop (br_if 0 (i32.load $m (i32.const 0)) (i32.const 1))) (i32.const 7) + ) + ) + (func (export "as-br_if-value-cond") (result i32) + (block (result i32) + (drop (br_if 0 (i32.const 6) (i32.load $m (i32.const 0)))) (i32.const 7) + ) + ) + + (func (export "as-br_table-index") + (block (br_table 0 0 0 (i32.load $m (i32.const 0)))) + ) + (func (export "as-br_table-value") (result i32) + (block (result i32) + (br_table 0 0 0 (i32.load $m (i32.const 0)) (i32.const 1)) (i32.const 7) + ) + ) + (func (export "as-br_table-value-index") (result i32) + (block (result i32) + (br_table 0 0 (i32.const 6) (i32.load $m (i32.const 0))) (i32.const 7) + ) + ) + + (func (export "as-return-value") (result i32) + (return (i32.load $m (i32.const 0))) + ) + + (func (export "as-if-cond") (result i32) + (if (result i32) (i32.load $m (i32.const 0)) + (then (i32.const 0)) (else (i32.const 1)) + ) + ) + (func (export "as-if-then") (result i32) + (if (result i32) (i32.const 1) + (then (i32.load $m (i32.const 0))) (else (i32.const 0)) + ) + ) + (func (export "as-if-else") (result i32) + (if (result i32) (i32.const 0) + (then (i32.const 0)) (else (i32.load $m (i32.const 0))) + ) + ) + + (func (export "as-select-first") (param i32 i32) (result i32) + (select (i32.load $m (i32.const 0)) (local.get 0) (local.get 1)) + ) + (func (export "as-select-second") (param i32 i32) (result i32) + (select (local.get 0) (i32.load $m (i32.const 0)) (local.get 1)) + ) + (func (export "as-select-cond") (result i32) + (select (i32.const 0) (i32.const 1) (i32.load $m (i32.const 0))) + ) + + (func $f (param i32 i32 i32) (result i32) (i32.const -1)) + (func (export "as-call-first") (result i32) + (call $f (i32.load $m (i32.const 0)) (i32.const 2) (i32.const 3)) + ) + (func (export "as-call-mid") (result i32) + (call $f (i32.const 1) (i32.load $m (i32.const 0)) (i32.const 3)) + ) + (func (export "as-call-last") (result i32) + (call $f (i32.const 1) (i32.const 2) (i32.load $m (i32.const 0))) + ) + + (type $sig (func (param i32 i32 i32) (result i32))) + (table funcref (elem $f)) + (func (export "as-call_indirect-first") (result i32) + (call_indirect (type $sig) + (i32.load $m (i32.const 0)) (i32.const 2) (i32.const 3) (i32.const 0) + ) + ) + (func (export "as-call_indirect-mid") (result i32) + (call_indirect (type $sig) + (i32.const 1) (i32.load $m (i32.const 0)) (i32.const 3) (i32.const 0) + ) + ) + (func (export "as-call_indirect-last") (result i32) + (call_indirect (type $sig) + (i32.const 1) (i32.const 2) (i32.load $m (i32.const 0)) (i32.const 0) + ) + ) + (func (export "as-call_indirect-index") (result i32) + (call_indirect (type $sig) + (i32.const 1) (i32.const 2) (i32.const 3) (i32.load $m (i32.const 0)) + ) + ) + + (func (export "as-local.set-value") (local i32) + (local.set 0 (i32.load $m (i32.const 0))) + ) + (func (export "as-local.tee-value") (result i32) (local i32) + (local.tee 0 (i32.load $m (i32.const 0))) + ) + (global $g (mut i32) (i32.const 0)) + (func (export "as-global.set-value") (local i32) + (global.set $g (i32.load $m (i32.const 0))) + ) + + (func (export "as-load-address") (result i32) + (i32.load $m (i32.load $m (i32.const 0))) + ) + (func (export "as-loadN-address") (result i32) + (i32.load8_s $m (i32.load $m (i32.const 0))) + ) + + (func (export "as-store-address") + (i32.store $m (i32.load $m (i32.const 0)) (i32.const 7)) + ) + (func (export "as-store-value") + (i32.store $m (i32.const 2) (i32.load $m (i32.const 0))) + ) + + (func (export "as-storeN-address") + (i32.store8 $m (i32.load8_s $m (i32.const 0)) (i32.const 7)) + ) + (func (export "as-storeN-value") + (i32.store16 $m (i32.const 2) (i32.load $m (i32.const 0))) + ) + + (func (export "as-unary-operand") (result i32) + (i32.clz (i32.load $m (i32.const 100))) + ) + + (func (export "as-binary-left") (result i32) + (i32.add (i32.load $m (i32.const 100)) (i32.const 10)) + ) + (func (export "as-binary-right") (result i32) + (i32.sub (i32.const 10) (i32.load $m (i32.const 100))) + ) + + (func (export "as-test-operand") (result i32) + (i32.eqz (i32.load $m (i32.const 100))) + ) + + (func (export "as-compare-left") (result i32) + (i32.le_s (i32.load $m (i32.const 100)) (i32.const 10)) + ) + (func (export "as-compare-right") (result i32) + (i32.ne (i32.const 10) (i32.load $m (i32.const 100))) + ) + + (func (export "as-memory.grow-size") (result i32) + (memory.grow $m (i32.load $m (i32.const 100))) + ) +) + +(assert_return (invoke "as-br-value") (i32.const 0)) + +(assert_return (invoke "as-br_if-cond")) +(assert_return (invoke "as-br_if-value") (i32.const 0)) +(assert_return (invoke "as-br_if-value-cond") (i32.const 7)) + +(assert_return (invoke "as-br_table-index")) +(assert_return (invoke "as-br_table-value") (i32.const 0)) +(assert_return (invoke "as-br_table-value-index") (i32.const 6)) + +(assert_return (invoke "as-return-value") (i32.const 0)) + +(assert_return (invoke "as-if-cond") (i32.const 1)) +(assert_return (invoke "as-if-then") (i32.const 0)) +(assert_return (invoke "as-if-else") (i32.const 0)) + +(assert_return (invoke "as-select-first" (i32.const 0) (i32.const 1)) (i32.const 0)) +(assert_return (invoke "as-select-second" (i32.const 0) (i32.const 0)) (i32.const 0)) +(assert_return (invoke "as-select-cond") (i32.const 1)) + +(assert_return (invoke "as-call-first") (i32.const -1)) +(assert_return (invoke "as-call-mid") (i32.const -1)) +(assert_return (invoke "as-call-last") (i32.const -1)) + +(assert_return (invoke "as-call_indirect-first") (i32.const -1)) +(assert_return (invoke "as-call_indirect-mid") (i32.const -1)) +(assert_return (invoke "as-call_indirect-last") (i32.const -1)) +(assert_return (invoke "as-call_indirect-index") (i32.const -1)) + +(assert_return (invoke "as-local.set-value")) +(assert_return (invoke "as-local.tee-value") (i32.const 0)) +(assert_return (invoke "as-global.set-value")) + +(assert_return (invoke "as-load-address") (i32.const 0)) +(assert_return (invoke "as-loadN-address") (i32.const 0)) +(assert_return (invoke "as-store-address")) +(assert_return (invoke "as-store-value")) +(assert_return (invoke "as-storeN-address")) +(assert_return (invoke "as-storeN-value")) + +(assert_return (invoke "as-unary-operand") (i32.const 32)) + +(assert_return (invoke "as-binary-left") (i32.const 10)) +(assert_return (invoke "as-binary-right") (i32.const 10)) + +(assert_return (invoke "as-test-operand") (i32.const 1)) + +(assert_return (invoke "as-compare-left") (i32.const 1)) +(assert_return (invoke "as-compare-right") (i32.const 1)) + +(assert_return (invoke "as-memory.grow-size") (i32.const 1)) + diff --git a/test/core/multi-memory/memory_copy0.wast b/test/core/multi-memory/memory_copy0.wast new file mode 100644 index 0000000000..5a5d910e78 --- /dev/null +++ b/test/core/multi-memory/memory_copy0.wast @@ -0,0 +1,60 @@ +;; memory.copy +(module + (memory $mem0 (data "\ff\11\44\ee")) + (memory $mem1 (data "\ee\22\55\ff")) + (memory $mem2 (data "\dd\33\66\00")) + (memory $mem3 (data "\aa\bb\cc\dd")) + + (func (export "copy") (param i32 i32 i32) + (memory.copy $mem3 $mem3 + (local.get 0) + (local.get 1) + (local.get 2))) + + (func (export "load8_u") (param i32) (result i32) + (i32.load8_u $mem3 (local.get 0))) +) + +;; Non-overlapping copy. +(invoke "copy" (i32.const 10) (i32.const 0) (i32.const 4)) + +(assert_return (invoke "load8_u" (i32.const 9)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0xaa)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xbb)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 13)) (i32.const 0xdd)) +(assert_return (invoke "load8_u" (i32.const 14)) (i32.const 0)) + +;; Overlap, source > dest +(invoke "copy" (i32.const 8) (i32.const 10) (i32.const 4)) +(assert_return (invoke "load8_u" (i32.const 8)) (i32.const 0xaa)) +(assert_return (invoke "load8_u" (i32.const 9)) (i32.const 0xbb)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xdd)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 13)) (i32.const 0xdd)) + +;; Overlap, source < dest +(invoke "copy" (i32.const 10) (i32.const 7) (i32.const 6)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xaa)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xbb)) +(assert_return (invoke "load8_u" (i32.const 13)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 14)) (i32.const 0xdd)) +(assert_return (invoke "load8_u" (i32.const 15)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 16)) (i32.const 0)) + +;; Copy ending at memory limit is ok. +(invoke "copy" (i32.const 0xff00) (i32.const 0) (i32.const 0x100)) +(invoke "copy" (i32.const 0xfe00) (i32.const 0xff00) (i32.const 0x100)) + +;; Succeed when copying 0 bytes at the end of the region. +(invoke "copy" (i32.const 0x10000) (i32.const 0) (i32.const 0)) +(invoke "copy" (i32.const 0) (i32.const 0x10000) (i32.const 0)) + +;; Copying 0 bytes outside the memory traps. +(assert_trap (invoke "copy" (i32.const 0x10001) (i32.const 0) (i32.const 0)) + "out of bounds memory access") +(assert_trap (invoke "copy" (i32.const 0) (i32.const 0x10001) (i32.const 0)) + "out of bounds memory access") + diff --git a/test/core/multi-memory/memory_copy1.wast b/test/core/multi-memory/memory_copy1.wast new file mode 100644 index 0000000000..df9165f42a --- /dev/null +++ b/test/core/multi-memory/memory_copy1.wast @@ -0,0 +1,40 @@ +;; test memory.copy across different memories. +(module + (memory $mem0 (data "\ff\11\44\ee")) + (memory $mem1 (data "\ee\22\55\ff")) + (memory $mem2 (data "\dd\33\66\00")) + (memory $mem3 (data "\aa\bb\cc\dd")) + + (func (export "copy") (param i32 i32 i32) + (memory.copy $mem0 $mem3 + (local.get 0) + (local.get 1) + (local.get 2))) + + (func (export "load8_u") (param i32) (result i32) + (i32.load8_u $mem0 (local.get 0))) +) + +;; Non-overlapping copy. +(invoke "copy" (i32.const 10) (i32.const 0) (i32.const 4)) + +(assert_return (invoke "load8_u" (i32.const 9)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 10)) (i32.const 0xaa)) +(assert_return (invoke "load8_u" (i32.const 11)) (i32.const 0xbb)) +(assert_return (invoke "load8_u" (i32.const 12)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 13)) (i32.const 0xdd)) +(assert_return (invoke "load8_u" (i32.const 14)) (i32.const 0)) + +;; Copy ending at memory limit is ok. +(invoke "copy" (i32.const 0xff00) (i32.const 0) (i32.const 0x100)) +(invoke "copy" (i32.const 0xfe00) (i32.const 0xff00) (i32.const 0x100)) + +;; Succeed when copying 0 bytes at the end of the region. +(invoke "copy" (i32.const 0x10000) (i32.const 0) (i32.const 0)) +(invoke "copy" (i32.const 0) (i32.const 0x10000) (i32.const 0)) + +;; Copying 0 bytes outside the memory traps. +(assert_trap (invoke "copy" (i32.const 0x10001) (i32.const 0) (i32.const 0)) + "out of bounds memory access") +(assert_trap (invoke "copy" (i32.const 0) (i32.const 0x10001) (i32.const 0)) + "out of bounds memory access") diff --git a/test/core/multi-memory/memory_fill0.wast b/test/core/multi-memory/memory_fill0.wast new file mode 100644 index 0000000000..eaf15bf5aa --- /dev/null +++ b/test/core/multi-memory/memory_fill0.wast @@ -0,0 +1,46 @@ +;; memory.fill +(module + (memory $mem0 0) + (memory $mem1 0) + (memory $mem2 1) + + (func (export "fill") (param i32 i32 i32) + (memory.fill $mem2 + (local.get 0) + (local.get 1) + (local.get 2))) + + (func (export "load8_u") (param i32) (result i32) + (i32.load8_u $mem2 (local.get 0))) +) + +;; Basic fill test. +(invoke "fill" (i32.const 1) (i32.const 0xff) (i32.const 3)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xff)) +(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0xff)) +(assert_return (invoke "load8_u" (i32.const 3)) (i32.const 0xff)) +(assert_return (invoke "load8_u" (i32.const 4)) (i32.const 0)) + +;; Fill value is stored as a byte. +(invoke "fill" (i32.const 0) (i32.const 0xbbaa) (i32.const 2)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0xaa)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xaa)) + +;; Fill all of memory +(invoke "fill" (i32.const 0) (i32.const 0) (i32.const 0x10000)) + +;; Out-of-bounds writes trap, and nothing is written +(assert_trap (invoke "fill" (i32.const 0xff00) (i32.const 1) (i32.const 0x101)) + "out of bounds memory access") +(assert_return (invoke "load8_u" (i32.const 0xff00)) (i32.const 0)) +(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0)) + +;; Succeed when writing 0 bytes at the end of the region. +(invoke "fill" (i32.const 0x10000) (i32.const 0) (i32.const 0)) + +;; Writing 0 bytes outside the memory traps. +(assert_trap (invoke "fill" (i32.const 0x10001) (i32.const 0) (i32.const 0)) + "out of bounds memory access") + + diff --git a/test/core/multi-memory/memory_init0.wast b/test/core/multi-memory/memory_init0.wast new file mode 100644 index 0000000000..190da6b9d0 --- /dev/null +++ b/test/core/multi-memory/memory_init0.wast @@ -0,0 +1,42 @@ +;; memory.init +(module + (memory $mem0 0) + (memory $mem1 0) + (memory $mem2 1) + (memory $mem3 0) + (data $mem2 "\aa\bb\cc\dd") + + (func (export "init") (param i32 i32 i32) + (memory.init $mem2 0 + (local.get 0) + (local.get 1) + (local.get 2))) + + (func (export "load8_u") (param i32) (result i32) + (i32.load8_u $mem2 (local.get 0))) +) + +(invoke "init" (i32.const 0) (i32.const 1) (i32.const 2)) +(assert_return (invoke "load8_u" (i32.const 0)) (i32.const 0xbb)) +(assert_return (invoke "load8_u" (i32.const 1)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 2)) (i32.const 0)) + +;; Init ending at memory limit and segment limit is ok. +(invoke "init" (i32.const 0xfffc) (i32.const 0) (i32.const 4)) + +;; Out-of-bounds writes trap, and nothing is written. +(assert_trap (invoke "init" (i32.const 0xfffe) (i32.const 0) (i32.const 3)) + "out of bounds memory access") +(assert_return (invoke "load8_u" (i32.const 0xfffe)) (i32.const 0xcc)) +(assert_return (invoke "load8_u" (i32.const 0xffff)) (i32.const 0xdd)) + +;; Succeed when writing 0 bytes at the end of either region. +(invoke "init" (i32.const 0x10000) (i32.const 0) (i32.const 0)) +(invoke "init" (i32.const 0) (i32.const 4) (i32.const 0)) + +;; Writing 0 bytes outside the memory traps. +(assert_trap (invoke "init" (i32.const 0x10001) (i32.const 0) (i32.const 0)) + "out of bounds memory access") +(assert_trap (invoke "init" (i32.const 0) (i32.const 5) (i32.const 0)) + "out of bounds memory access") + diff --git a/test/core/multi-memory/memory_size0.wast b/test/core/multi-memory/memory_size0.wast new file mode 100644 index 0000000000..47ad9ec4b5 --- /dev/null +++ b/test/core/multi-memory/memory_size0.wast @@ -0,0 +1,19 @@ +(module + (memory 0) + (memory 0) + (memory 0) + (memory 0) + (memory $m 0) + + (func (export "size") (result i32) (memory.size $m)) + (func (export "grow") (param $sz i32) (drop (memory.grow $m (local.get $sz)))) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1))) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "grow" (i32.const 4))) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "grow" (i32.const 0))) +(assert_return (invoke "size") (i32.const 5)) + diff --git a/test/core/multi-memory/memory_size1.wast b/test/core/multi-memory/memory_size1.wast new file mode 100644 index 0000000000..69026a650c --- /dev/null +++ b/test/core/multi-memory/memory_size1.wast @@ -0,0 +1,29 @@ +(module + (memory 0) + (memory 0) + (memory $n 0) + (memory 0) + (memory $m 0) + + (func (export "size") (result i32) (memory.size $m)) + (func (export "grow") (param $sz i32) (drop (memory.grow $m (local.get $sz)))) + + (func (export "sizen") (result i32) (memory.size $n)) + (func (export "grown") (param $sz i32) (drop (memory.grow $n (local.get $sz)))) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1))) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 4))) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0))) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "sizen") (i32.const 0)) + +(assert_return (invoke "grown" (i32.const 1))) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "sizen") (i32.const 1)) diff --git a/test/core/multi-memory/memory_size2.wast b/test/core/multi-memory/memory_size2.wast new file mode 100644 index 0000000000..e59917e371 --- /dev/null +++ b/test/core/multi-memory/memory_size2.wast @@ -0,0 +1,34 @@ +(module + (memory 0 0) + (memory 0 0) + (memory $n 0 0) + (memory $m 0 2) + + (func (export "size") (result i32) (memory.size $m)) + (func (export "grow") (param $sz i32) (drop (memory.grow $m (local.get $sz)))) + + (func (export "sizen") (result i32) (memory.size $n)) + (func (export "grown") (param $sz i32) (drop (memory.grow $n (local.get $sz)))) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 3))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "grow" (i32.const 0))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "grow" (i32.const 4))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "grow" (i32.const 1))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 2)) + +(assert_return (invoke "grown" (i32.const 1))) +(assert_return (invoke "sizen") (i32.const 0)) +(assert_return (invoke "size") (i32.const 2)) diff --git a/test/core/multi-memory/memory_size3.wast b/test/core/multi-memory/memory_size3.wast new file mode 100644 index 0000000000..834aa1c368 --- /dev/null +++ b/test/core/multi-memory/memory_size3.wast @@ -0,0 +1,25 @@ +;; Type errors + +(assert_invalid + (module + (memory 0) + (memory $m 1) + (memory 0) + (func $type-result-i32-vs-empty + (memory.size $m) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (memory 0) + (memory 0) + (memory 0) + (memory $m 1) + (func $type-result-i32-vs-f32 (result f32) + (memory.size $m) + ) + ) + "type mismatch" +) diff --git a/test/core/multi-memory/memory_trap0.wast b/test/core/multi-memory/memory_trap0.wast new file mode 100644 index 0000000000..26ecdf8444 --- /dev/null +++ b/test/core/multi-memory/memory_trap0.wast @@ -0,0 +1,36 @@ +(module + (memory 0) + (memory 0) + (memory $m 1) + + (func $addr_limit (result i32) + (i32.mul (memory.size $m) (i32.const 0x10000)) + ) + + (func (export "store") (param $i i32) (param $v i32) + (i32.store $m (i32.add (call $addr_limit) (local.get $i)) (local.get $v)) + ) + + (func (export "load") (param $i i32) (result i32) + (i32.load $m (i32.add (call $addr_limit) (local.get $i))) + ) + + (func (export "memory.grow") (param i32) (result i32) + (memory.grow $m (local.get 0)) + ) +) + +(assert_return (invoke "store" (i32.const -4) (i32.const 42))) +(assert_return (invoke "load" (i32.const -4)) (i32.const 42)) +(assert_trap (invoke "store" (i32.const -3) (i32.const 0x12345678)) "out of bounds memory access") +(assert_trap (invoke "load" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "store" (i32.const -2) (i32.const 13)) "out of bounds memory access") +(assert_trap (invoke "load" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "store" (i32.const -1) (i32.const 13)) "out of bounds memory access") +(assert_trap (invoke "load" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "store" (i32.const 0) (i32.const 13)) "out of bounds memory access") +(assert_trap (invoke "load" (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "store" (i32.const 0x80000000) (i32.const 13)) "out of bounds memory access") +(assert_trap (invoke "load" (i32.const 0x80000000)) "out of bounds memory access") +(assert_return (invoke "memory.grow" (i32.const 0x10001)) (i32.const -1)) + diff --git a/test/core/multi-memory/memory_trap1.wast b/test/core/multi-memory/memory_trap1.wast new file mode 100644 index 0000000000..bd692d04c5 --- /dev/null +++ b/test/core/multi-memory/memory_trap1.wast @@ -0,0 +1,250 @@ +(module + (memory 0) + (memory 0) + (memory $m 1) + (data (memory 2) (i32.const 0) "abcdefgh") + (data (memory 2) (i32.const 0xfff8) "abcdefgh") + + (func (export "i32.load") (param $a i32) (result i32) + (i32.load $m (local.get $a)) + ) + (func (export "i64.load") (param $a i32) (result i64) + (i64.load $m (local.get $a)) + ) + (func (export "f32.load") (param $a i32) (result f32) + (f32.load $m (local.get $a)) + ) + (func (export "f64.load") (param $a i32) (result f64) + (f64.load $m (local.get $a)) + ) + (func (export "i32.load8_s") (param $a i32) (result i32) + (i32.load8_s $m (local.get $a)) + ) + (func (export "i32.load8_u") (param $a i32) (result i32) + (i32.load8_u $m (local.get $a)) + ) + (func (export "i32.load16_s") (param $a i32) (result i32) + (i32.load16_s $m (local.get $a)) + ) + (func (export "i32.load16_u") (param $a i32) (result i32) + (i32.load16_u $m (local.get $a)) + ) + (func (export "i64.load8_s") (param $a i32) (result i64) + (i64.load8_s $m (local.get $a)) + ) + (func (export "i64.load8_u") (param $a i32) (result i64) + (i64.load8_u $m (local.get $a)) + ) + (func (export "i64.load16_s") (param $a i32) (result i64) + (i64.load16_s $m (local.get $a)) + ) + (func (export "i64.load16_u") (param $a i32) (result i64) + (i64.load16_u $m (local.get $a)) + ) + (func (export "i64.load32_s") (param $a i32) (result i64) + (i64.load32_s $m (local.get $a)) + ) + (func (export "i64.load32_u") (param $a i32) (result i64) + (i64.load32_u $m (local.get $a)) + ) + (func (export "i32.store") (param $a i32) (param $v i32) + (i32.store $m (local.get $a) (local.get $v)) + ) + (func (export "i64.store") (param $a i32) (param $v i64) + (i64.store $m (local.get $a) (local.get $v)) + ) + (func (export "f32.store") (param $a i32) (param $v f32) + (f32.store $m (local.get $a) (local.get $v)) + ) + (func (export "f64.store") (param $a i32) (param $v f64) + (f64.store $m (local.get $a) (local.get $v)) + ) + (func (export "i32.store8") (param $a i32) (param $v i32) + (i32.store8 $m (local.get $a) (local.get $v)) + ) + (func (export "i32.store16") (param $a i32) (param $v i32) + (i32.store16 $m (local.get $a) (local.get $v)) + ) + (func (export "i64.store8") (param $a i32) (param $v i64) + (i64.store8 $m (local.get $a) (local.get $v)) + ) + (func (export "i64.store16") (param $a i32) (param $v i64) + (i64.store16 $m (local.get $a) (local.get $v)) + ) + (func (export "i64.store32") (param $a i32) (param $v i64) + (i64.store32 $m (local.get $a) (local.get $v)) + ) +) + +(assert_trap (invoke "i32.store" (i32.const 0x10000) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const 0xffff) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const 0xfffe) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const 0xfffd) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const -2) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const -3) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store" (i32.const -4) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0x10000) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xffff) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfffe) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfffd) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfffc) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfffb) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfffa) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const 0xfff9) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -1) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -2) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -3) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -4) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -5) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -6) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -7) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store" (i32.const -8) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const 0x10000) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const 0xffff) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const 0xfffe) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const 0xfffd) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const -1) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const -2) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const -3) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f32.store" (i32.const -4) (f32.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0x10000) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xffff) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfffe) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfffd) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfffc) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfffb) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfffa) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const 0xfff9) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -1) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -2) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -3) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -4) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -5) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -6) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -7) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "f64.store" (i32.const -8) (f64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store8" (i32.const 0x10000) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store8" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store16" (i32.const 0x10000) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store16" (i32.const 0xffff) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store16" (i32.const -1) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.store16" (i32.const -2) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store8" (i32.const 0x10000) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store8" (i32.const -1) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store16" (i32.const 0x10000) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store16" (i32.const 0xffff) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store16" (i32.const -1) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store16" (i32.const -2) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const 0x10000) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const 0xffff) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const 0xfffe) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const 0xfffd) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const -1) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const -2) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const -3) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i64.store32" (i32.const -4) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "i32.load" (i32.const -4)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfffc)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfffb)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfffa)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const 0xfff9)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -4)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -5)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -6)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -7)) "out of bounds memory access") +(assert_trap (invoke "i64.load" (i32.const -8)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "f32.load" (i32.const -4)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfffc)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfffb)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfffa)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const 0xfff9)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -4)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -5)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -6)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -7)) "out of bounds memory access") +(assert_trap (invoke "f64.load" (i32.const -8)) "out of bounds memory access") +(assert_trap (invoke "i32.load8_s" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i32.load8_s" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i32.load8_u" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i32.load8_u" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_s" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_s" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_s" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_s" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_u" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_u" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_u" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i32.load16_u" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load8_s" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load8_s" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load8_u" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load8_u" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_s" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_s" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_s" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_s" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_u" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_u" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_u" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load16_u" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_s" (i32.const -4)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const 0x10000)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const 0xffff)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const 0xfffe)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const 0xfffd)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const -1)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const -2)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const -3)) "out of bounds memory access") +(assert_trap (invoke "i64.load32_u" (i32.const -4)) "out of bounds memory access") + +;; No memory was changed +(assert_return (invoke "i64.load" (i32.const 0xfff8)) (i64.const 0x6867666564636261)) +(assert_return (invoke "i64.load" (i32.const 0)) (i64.const 0x6867666564636261)) + +;; Check that out of bounds store do not store partial data. +;; Zero last 8 bytes. +(assert_return (invoke "i64.store" (i32.const 0xfff8) (i64.const 0))) +(assert_trap (invoke "i32.store" (i32.const 0xfffd) (i32.const 0x12345678)) "out of bounds memory access") +(assert_return (invoke "i32.load" (i32.const 0xfffc)) (i32.const 0)) +(assert_trap (invoke "i64.store" (i32.const 0xfff9) (i64.const 0x1234567890abcdef)) "out of bounds memory access") +(assert_return (invoke "i64.load" (i32.const 0xfff8)) (i64.const 0)) +(assert_trap (invoke "f32.store" (i32.const 0xfffd) (f32.const 0x12345678)) "out of bounds memory access") +(assert_return (invoke "f32.load" (i32.const 0xfffc)) (f32.const 0)) +(assert_trap (invoke "f64.store" (i32.const 0xfff9) (f64.const 0x1234567890abcdef)) "out of bounds memory access") +(assert_return (invoke "f64.load" (i32.const 0xfff8)) (f64.const 0)) diff --git a/test/core/multi-memory/start0.wast b/test/core/multi-memory/start0.wast new file mode 100644 index 0000000000..677279ed6c --- /dev/null +++ b/test/core/multi-memory/start0.wast @@ -0,0 +1,42 @@ +(module + (memory 0) + (memory $m (data "A")) + (memory $n 1) + + (func $inc + (i32.store8 $m + (i32.const 0) + (i32.add + (i32.load8_u $m (i32.const 0)) + (i32.const 1) + ) + ) + ) + (func $get (result i32) + (return (i32.load8_u $m (i32.const 0))) + ) + (func $getn (result i32) + (return (i32.load8_u $n (i32.const 0))) + ) + (func $main + (call $inc) + (call $inc) + (call $inc) + ) + + (start $main) + (export "inc" (func $inc)) + (export "get" (func $get)) + (export "getn" (func $getn)) +) +(assert_return (invoke "get") (i32.const 68)) +(assert_return (invoke "getn") (i32.const 0)) + +(invoke "inc") +(assert_return (invoke "get") (i32.const 69)) +(assert_return (invoke "getn") (i32.const 0)) + +(invoke "inc") +(assert_return (invoke "get") (i32.const 70)) +(assert_return (invoke "getn") (i32.const 0)) + diff --git a/test/core/multi-memory/store0.wast b/test/core/multi-memory/store0.wast new file mode 100644 index 0000000000..2f217b1f9d --- /dev/null +++ b/test/core/multi-memory/store0.wast @@ -0,0 +1,25 @@ +;; Multiple memories + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (func (export "store1") (param i32 i64) + (i64.store $mem1 (local.get 0) (local.get 1)) + ) + (func (export "store2") (param i32 i64) + (i64.store $mem2 (local.get 0) (local.get 1)) + ) +) + +(invoke "store1" (i32.const 0) (i64.const 1)) +(invoke "store2" (i32.const 0) (i64.const 2)) +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) diff --git a/test/core/multi-memory/store1.wast b/test/core/multi-memory/store1.wast new file mode 100644 index 0000000000..10cf2c4262 --- /dev/null +++ b/test/core/multi-memory/store1.wast @@ -0,0 +1,52 @@ +(module $M1 + (memory (export "mem") 1) + + (func (export "load") (param i32) (result i64) + (i64.load (local.get 0)) + ) + (func (export "store") (param i32 i64) + (i64.store (local.get 0) (local.get 1)) + ) +) +(register "M1") + +(module $M2 + (memory (export "mem") 1) + + (func (export "load") (param i32) (result i64) + (i64.load (local.get 0)) + ) + (func (export "store") (param i32 i64) + (i64.store (local.get 0) (local.get 1)) + ) +) +(register "M2") + +(invoke $M1 "store" (i32.const 0) (i64.const 1)) +(invoke $M2 "store" (i32.const 0) (i64.const 2)) +(assert_return (invoke $M1 "load" (i32.const 0)) (i64.const 1)) +(assert_return (invoke $M2 "load" (i32.const 0)) (i64.const 2)) + +(module + (memory $mem1 (import "M1" "mem") 1) + (memory $mem2 (import "M2" "mem") 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (func (export "store1") (param i32 i64) + (i64.store $mem1 (local.get 0) (local.get 1)) + ) + (func (export "store2") (param i32 i64) + (i64.store $mem2 (local.get 0) (local.get 1)) + ) +) + +(invoke "store1" (i32.const 0) (i64.const 1)) +(invoke "store2" (i32.const 0) (i64.const 2)) +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) diff --git a/test/core/multi-memory/traps0.wast b/test/core/multi-memory/traps0.wast new file mode 100644 index 0000000000..fd942e4b64 --- /dev/null +++ b/test/core/multi-memory/traps0.wast @@ -0,0 +1,35 @@ +(module + (memory $mem0 1) + (memory $mem1 1) + (memory $mem2 1) + + (func (export "no_dce.i32.load") (param $i i32) (drop (i32.load $mem1 (local.get $i)))) + (func (export "no_dce.i32.load16_s") (param $i i32) (drop (i32.load16_s $mem1 (local.get $i)))) + (func (export "no_dce.i32.load16_u") (param $i i32) (drop (i32.load16_u $mem1 (local.get $i)))) + (func (export "no_dce.i32.load8_s") (param $i i32) (drop (i32.load8_s $mem1 (local.get $i)))) + (func (export "no_dce.i32.load8_u") (param $i i32) (drop (i32.load8_u $mem1 (local.get $i)))) + (func (export "no_dce.i64.load") (param $i i32) (drop (i64.load $mem1 (local.get $i)))) + (func (export "no_dce.i64.load32_s") (param $i i32) (drop (i64.load32_s $mem1 (local.get $i)))) + (func (export "no_dce.i64.load32_u") (param $i i32) (drop (i64.load32_u $mem2 (local.get $i)))) + (func (export "no_dce.i64.load16_s") (param $i i32) (drop (i64.load16_s $mem2 (local.get $i)))) + (func (export "no_dce.i64.load16_u") (param $i i32) (drop (i64.load16_u $mem2 (local.get $i)))) + (func (export "no_dce.i64.load8_s") (param $i i32) (drop (i64.load8_s $mem2 (local.get $i)))) + (func (export "no_dce.i64.load8_u") (param $i i32) (drop (i64.load8_u $mem2 (local.get $i)))) + (func (export "no_dce.f32.load") (param $i i32) (drop (f32.load $mem2 (local.get $i)))) + (func (export "no_dce.f64.load") (param $i i32) (drop (f64.load $mem2 (local.get $i)))) +) + +(assert_trap (invoke "no_dce.i32.load" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i32.load16_s" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i32.load16_u" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i32.load8_s" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i32.load8_u" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load32_s" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load32_u" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load16_s" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load16_u" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load8_s" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.i64.load8_u" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.f32.load" (i32.const 65536)) "out of bounds memory access") +(assert_trap (invoke "no_dce.f64.load" (i32.const 65536)) "out of bounds memory access") diff --git a/test/core/ref.wast b/test/core/ref.wast new file mode 100644 index 0000000000..aef1b392ca --- /dev/null +++ b/test/core/ref.wast @@ -0,0 +1,80 @@ +;; Syntax + +(module + (type $t (func)) + + (func + (param + funcref + externref + (ref func) + (ref extern) + (ref 0) + (ref $t) + (ref 0) + (ref $t) + (ref null func) + (ref null extern) + (ref null 0) + (ref null $t) + ) + ) +) + + +;; Undefined type index. + +(assert_invalid + (module (type $type-func-param-invalid (func (param (ref 1))))) + "unknown type" +) +(assert_invalid + (module (type $type-func-result-invalid (func (result (ref 1))))) + "unknown type" +) + +(assert_invalid + (module (global $global-invalid (ref null 1) (ref.null 1))) + "unknown type" +) + +(assert_invalid + (module (table $table-invalid 10 (ref null 1))) + "unknown type" +) + +(assert_invalid + (module (elem $elem-invalid (ref 1))) + "unknown type" +) + +(assert_invalid + (module (func $func-param-invalid (param (ref 1)))) + "unknown type" +) +(assert_invalid + (module (func $func-result-invalid (result (ref 1)))) + "unknown type" +) +(assert_invalid + (module (func $func-local-invalid (local (ref null 1)))) + "unknown type" +) + +(assert_invalid + (module (func $block-result-invalid (drop (block (result (ref 1)) (unreachable))))) + "unknown type" +) +(assert_invalid + (module (func $loop-result-invalid (drop (loop (result (ref 1)) (unreachable))))) + "unknown type" +) +(assert_invalid + (module (func $if-invalid (drop (if (result (ref 1)) (then) (else))))) + "unknown type" +) + +(assert_invalid + (module (func $select-result-invalid (drop (select (result (ref 1)) (unreachable))))) + "unknown type" +) diff --git a/test/core/ref_as_non_null.wast b/test/core/ref_as_non_null.wast new file mode 100644 index 0000000000..6b3380fc59 --- /dev/null +++ b/test/core/ref_as_non_null.wast @@ -0,0 +1,46 @@ +(module + (type $t (func (result i32))) + + (func $nn (param $r (ref $t)) (result i32) + (call_ref $t (ref.as_non_null (local.get $r))) + ) + (func $n (param $r (ref null $t)) (result i32) + (call_ref $t (ref.as_non_null (local.get $r))) + ) + + (elem func $f) + (func $f (result i32) (i32.const 7)) + + (func (export "nullable-null") (result i32) (call $n (ref.null $t))) + (func (export "nonnullable-f") (result i32) (call $nn (ref.func $f))) + (func (export "nullable-f") (result i32) (call $n (ref.func $f))) + + (func (export "unreachable") (result i32) + (unreachable) + (ref.as_non_null) + (call $nn) + ) +) + +(assert_trap (invoke "unreachable") "unreachable") + +(assert_trap (invoke "nullable-null") "null reference") +(assert_return (invoke "nonnullable-f") (i32.const 7)) +(assert_return (invoke "nullable-f") (i32.const 7)) + +(assert_invalid + (module + (type $t (func (result i32))) + (func $g (param $r (ref $t)) (drop (ref.as_non_null (local.get $r)))) + (func (call $g (ref.null $t))) + ) + "type mismatch" +) + + +(module + (type $t (func)) + (func (param $r (ref $t)) (drop (ref.as_non_null (local.get $r)))) + (func (param $r (ref func)) (drop (ref.as_non_null (local.get $r)))) + (func (param $r (ref extern)) (drop (ref.as_non_null (local.get $r)))) +) diff --git a/test/core/ref_is_null.wast b/test/core/ref_is_null.wast index 8396da4a7e..730170df45 100644 --- a/test/core/ref_is_null.wast +++ b/test/core/ref_is_null.wast @@ -1,15 +1,25 @@ (module + (type $t (func)) + (func $dummy) + (func $f1 (export "funcref") (param $x funcref) (result i32) (ref.is_null (local.get $x)) ) (func $f2 (export "externref") (param $x externref) (result i32) (ref.is_null (local.get $x)) ) + (func $f3 (param $x (ref null $t)) (result i32) + (ref.is_null (local.get $x)) + ) + (func $f3' (export "ref-null") (result i32) + (call $f3 (ref.null $t)) + ) (table $t1 2 funcref) (table $t2 2 externref) + (table $t3 2 (ref null $t)) (elem (table $t1) (i32.const 1) func $dummy) - (func $dummy) + (elem (table $t3) (i32.const 1) (ref $t) (ref.func $dummy)) (func (export "init") (param $r externref) (table.set $t2 (i32.const 1) (local.get $r)) @@ -17,6 +27,7 @@ (func (export "deinit") (table.set $t1 (i32.const 1) (ref.null func)) (table.set $t2 (i32.const 1) (ref.null extern)) + (table.set $t3 (i32.const 1) (ref.null $t)) ) (func (export "funcref-elem") (param $x i32) (result i32) @@ -25,10 +36,14 @@ (func (export "externref-elem") (param $x i32) (result i32) (call $f2 (table.get $t2 (local.get $x))) ) + (func (export "ref-elem") (param $x i32) (result i32) + (call $f3 (table.get $t3 (local.get $x))) + ) ) (assert_return (invoke "funcref" (ref.null func)) (i32.const 1)) (assert_return (invoke "externref" (ref.null extern)) (i32.const 1)) +(assert_return (invoke "ref-null") (i32.const 1)) (assert_return (invoke "externref" (ref.extern 1)) (i32.const 0)) @@ -36,17 +51,29 @@ (assert_return (invoke "funcref-elem" (i32.const 0)) (i32.const 1)) (assert_return (invoke "externref-elem" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref-elem" (i32.const 0)) (i32.const 1)) (assert_return (invoke "funcref-elem" (i32.const 1)) (i32.const 0)) (assert_return (invoke "externref-elem" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "ref-elem" (i32.const 1)) (i32.const 0)) (invoke "deinit") (assert_return (invoke "funcref-elem" (i32.const 0)) (i32.const 1)) (assert_return (invoke "externref-elem" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "ref-elem" (i32.const 0)) (i32.const 1)) (assert_return (invoke "funcref-elem" (i32.const 1)) (i32.const 1)) (assert_return (invoke "externref-elem" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "ref-elem" (i32.const 1)) (i32.const 1)) + + +(module + (type $t (func)) + (func (param $r (ref $t)) (drop (ref.is_null (local.get $r)))) + (func (param $r (ref func)) (drop (ref.is_null (local.get $r)))) + (func (param $r (ref extern)) (drop (ref.is_null (local.get $r)))) +) (assert_invalid (module (func $ref-vs-num (param i32) (ref.is_null (local.get 0)))) diff --git a/test/core/ref_null.wast b/test/core/ref_null.wast index b88b0888fd..1ffd03f8a4 100644 --- a/test/core/ref_null.wast +++ b/test/core/ref_null.wast @@ -1,10 +1,63 @@ (module - (func (export "externref") (result externref) (ref.null extern)) + (type $t (func)) + (func (export "anyref") (result anyref) (ref.null any)) (func (export "funcref") (result funcref) (ref.null func)) + (func (export "ref") (result (ref null $t)) (ref.null $t)) - (global externref (ref.null extern)) + (global anyref (ref.null any)) (global funcref (ref.null func)) + (global (ref null $t) (ref.null $t)) ) -(assert_return (invoke "externref") (ref.null extern)) +(assert_return (invoke "anyref") (ref.null any)) +(assert_return (invoke "funcref") (ref.null func)) +(assert_return (invoke "ref") (ref.null)) + + +(module + (type $t (func)) + (global $null nullref (ref.null none)) + (global $nullfunc nullfuncref (ref.null nofunc)) + (global $nullextern nullexternref (ref.null noextern)) + (func (export "anyref") (result anyref) (global.get $null)) + (func (export "nullref") (result nullref) (global.get $null)) + (func (export "funcref") (result funcref) (global.get $nullfunc)) + (func (export "nullfuncref") (result nullfuncref) (global.get $nullfunc)) + (func (export "externref") (result externref) (global.get $nullextern)) + (func (export "nullexternref") (result nullexternref) (global.get $nullextern)) + (func (export "ref") (result (ref null $t)) (global.get $nullfunc)) + + (global anyref (ref.null any)) + (global anyref (ref.null none)) + (global funcref (ref.null func)) + (global funcref (ref.null nofunc)) + (global externref (ref.null extern)) + (global externref (ref.null noextern)) + (global nullref (ref.null none)) + (global nullfuncref (ref.null nofunc)) + (global nullexternref (ref.null noextern)) + (global (ref null $t) (ref.null $t)) + (global (ref null $t) (ref.null nofunc)) +) + +(assert_return (invoke "anyref") (ref.null any)) +(assert_return (invoke "anyref") (ref.null none)) +(assert_return (invoke "anyref") (ref.null)) +(assert_return (invoke "nullref") (ref.null any)) +(assert_return (invoke "nullref") (ref.null none)) +(assert_return (invoke "nullref") (ref.null)) (assert_return (invoke "funcref") (ref.null func)) +(assert_return (invoke "funcref") (ref.null nofunc)) +(assert_return (invoke "funcref") (ref.null)) +(assert_return (invoke "nullfuncref") (ref.null func)) +(assert_return (invoke "nullfuncref") (ref.null nofunc)) +(assert_return (invoke "nullfuncref") (ref.null)) +(assert_return (invoke "externref") (ref.null extern)) +(assert_return (invoke "externref") (ref.null noextern)) +(assert_return (invoke "externref") (ref.null)) +(assert_return (invoke "nullexternref") (ref.null extern)) +(assert_return (invoke "nullexternref") (ref.null noextern)) +(assert_return (invoke "nullexternref") (ref.null)) +(assert_return (invoke "ref") (ref.null func)) +(assert_return (invoke "ref") (ref.null nofunc)) +(assert_return (invoke "ref") (ref.null)) diff --git a/test/core/return_call.wast b/test/core/return_call.wast new file mode 100644 index 0000000000..2f91f4deac --- /dev/null +++ b/test/core/return_call.wast @@ -0,0 +1,202 @@ +;; Test `return_call` operator + +(module + ;; Auxiliary definitions + (func $const-i32 (result i32) (i32.const 0x132)) + (func $const-i64 (result i64) (i64.const 0x164)) + (func $const-f32 (result f32) (f32.const 0xf32)) + (func $const-f64 (result f64) (f64.const 0xf64)) + + (func $id-i32 (param i32) (result i32) (local.get 0)) + (func $id-i64 (param i64) (result i64) (local.get 0)) + (func $id-f32 (param f32) (result f32) (local.get 0)) + (func $id-f64 (param f64) (result f64) (local.get 0)) + + (func $f32-i32 (param f32 i32) (result i32) (local.get 1)) + (func $i32-i64 (param i32 i64) (result i64) (local.get 1)) + (func $f64-f32 (param f64 f32) (result f32) (local.get 1)) + (func $i64-f64 (param i64 f64) (result f64) (local.get 1)) + + ;; Typing + + (func (export "type-i32") (result i32) (return_call $const-i32)) + (func (export "type-i64") (result i64) (return_call $const-i64)) + (func (export "type-f32") (result f32) (return_call $const-f32)) + (func (export "type-f64") (result f64) (return_call $const-f64)) + + (func (export "type-first-i32") (result i32) (return_call $id-i32 (i32.const 32))) + (func (export "type-first-i64") (result i64) (return_call $id-i64 (i64.const 64))) + (func (export "type-first-f32") (result f32) (return_call $id-f32 (f32.const 1.32))) + (func (export "type-first-f64") (result f64) (return_call $id-f64 (f64.const 1.64))) + + (func (export "type-second-i32") (result i32) + (return_call $f32-i32 (f32.const 32.1) (i32.const 32)) + ) + (func (export "type-second-i64") (result i64) + (return_call $i32-i64 (i32.const 32) (i64.const 64)) + ) + (func (export "type-second-f32") (result f32) + (return_call $f64-f32 (f64.const 64) (f32.const 32)) + ) + (func (export "type-second-f64") (result f64) + (return_call $i64-f64 (i64.const 64) (f64.const 64.1)) + ) + + ;; Recursion + + (func $fac-acc (export "fac-acc") (param i64 i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 1)) + (else + (return_call $fac-acc + (i64.sub (local.get 0) (i64.const 1)) + (i64.mul (local.get 0) (local.get 1)) + ) + ) + ) + ) + + (func $count (export "count") (param i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 0)) + (else (return_call $count (i64.sub (local.get 0) (i64.const 1)))) + ) + ) + + (func $even (export "even") (param i64) (result i32) + (if (result i32) (i64.eqz (local.get 0)) + (then (i32.const 44)) + (else (return_call $odd (i64.sub (local.get 0) (i64.const 1)))) + ) + ) + (func $odd (export "odd") (param i64) (result i32) + (if (result i32) (i64.eqz (local.get 0)) + (then (i32.const 99)) + (else (return_call $even (i64.sub (local.get 0) (i64.const 1)))) + ) + ) +) + +(assert_return (invoke "type-i32") (i32.const 0x132)) +(assert_return (invoke "type-i64") (i64.const 0x164)) +(assert_return (invoke "type-f32") (f32.const 0xf32)) +(assert_return (invoke "type-f64") (f64.const 0xf64)) + +(assert_return (invoke "type-first-i32") (i32.const 32)) +(assert_return (invoke "type-first-i64") (i64.const 64)) +(assert_return (invoke "type-first-f32") (f32.const 1.32)) +(assert_return (invoke "type-first-f64") (f64.const 1.64)) + +(assert_return (invoke "type-second-i32") (i32.const 32)) +(assert_return (invoke "type-second-i64") (i64.const 64)) +(assert_return (invoke "type-second-f32") (f32.const 32)) +(assert_return (invoke "type-second-f64") (f64.const 64.1)) + +(assert_return (invoke "fac-acc" (i64.const 0) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 1) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 5) (i64.const 1)) (i64.const 120)) +(assert_return + (invoke "fac-acc" (i64.const 25) (i64.const 1)) + (i64.const 7034535277573963776) +) + +(assert_return (invoke "count" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "count" (i64.const 1000)) (i64.const 0)) +(assert_return (invoke "count" (i64.const 1_000_000)) (i64.const 0)) + +(assert_return (invoke "even" (i64.const 0)) (i32.const 44)) +(assert_return (invoke "even" (i64.const 1)) (i32.const 99)) +(assert_return (invoke "even" (i64.const 100)) (i32.const 44)) +(assert_return (invoke "even" (i64.const 77)) (i32.const 99)) +(assert_return (invoke "even" (i64.const 1_000_000)) (i32.const 44)) +(assert_return (invoke "even" (i64.const 1_000_001)) (i32.const 99)) +(assert_return (invoke "odd" (i64.const 0)) (i32.const 99)) +(assert_return (invoke "odd" (i64.const 1)) (i32.const 44)) +(assert_return (invoke "odd" (i64.const 200)) (i32.const 99)) +(assert_return (invoke "odd" (i64.const 77)) (i32.const 44)) +(assert_return (invoke "odd" (i64.const 1_000_000)) (i32.const 99)) +(assert_return (invoke "odd" (i64.const 999_999)) (i32.const 44)) + + +;; Invalid typing + +(assert_invalid + (module + (func $type-void-vs-num (result i32) (return_call 1) (i32.const 0)) + (func) + ) + "type mismatch" +) +(assert_invalid + (module + (func $type-num-vs-num (result i32) (return_call 1) (i32.const 0)) + (func (result i64) (i64.const 1)) + ) + "type mismatch" +) + +(assert_invalid + (module + (func $arity-0-vs-1 (return_call 1)) + (func (param i32)) + ) + "type mismatch" +) +(assert_invalid + (module + (func $arity-0-vs-2 (return_call 1)) + (func (param f64 i32)) + ) + "type mismatch" +) + +(module + (func $arity-1-vs-0 (i32.const 1) (return_call 1)) + (func) +) + +(module + (func $arity-2-vs-0 (f64.const 2) (i32.const 1) (return_call 1)) + (func) +) + +(assert_invalid + (module + (func $type-first-void-vs-num (return_call 1 (nop) (i32.const 1))) + (func (param i32 i32)) + ) + "type mismatch" +) +(assert_invalid + (module + (func $type-second-void-vs-num (return_call 1 (i32.const 1) (nop))) + (func (param i32 i32)) + ) + "type mismatch" +) +(assert_invalid + (module + (func $type-first-num-vs-num (return_call 1 (f64.const 1) (i32.const 1))) + (func (param i32 f64)) + ) + "type mismatch" +) +(assert_invalid + (module + (func $type-second-num-vs-num (return_call 1 (i32.const 1) (f64.const 1))) + (func (param f64 i32)) + ) + "type mismatch" +) + + +;; Unbound function + +(assert_invalid + (module (func $unbound-func (return_call 1))) + "unknown function" +) +(assert_invalid + (module (func $large-func (return_call 1012321300))) + "unknown function" +) diff --git a/test/core/return_call_indirect.wast b/test/core/return_call_indirect.wast new file mode 100644 index 0000000000..acf0a72e06 --- /dev/null +++ b/test/core/return_call_indirect.wast @@ -0,0 +1,536 @@ +;; Test `return_call_indirect` operator + +(module + ;; Auxiliary definitions + (type $proc (func)) + (type $out-i32 (func (result i32))) + (type $out-i64 (func (result i64))) + (type $out-f32 (func (result f32))) + (type $out-f64 (func (result f64))) + (type $over-i32 (func (param i32) (result i32))) + (type $over-i64 (func (param i64) (result i64))) + (type $over-f32 (func (param f32) (result f32))) + (type $over-f64 (func (param f64) (result f64))) + (type $f32-i32 (func (param f32 i32) (result i32))) + (type $i32-i64 (func (param i32 i64) (result i64))) + (type $f64-f32 (func (param f64 f32) (result f32))) + (type $i64-f64 (func (param i64 f64) (result f64))) + (type $over-i32-duplicate (func (param i32) (result i32))) + (type $over-i64-duplicate (func (param i64) (result i64))) + (type $over-f32-duplicate (func (param f32) (result f32))) + (type $over-f64-duplicate (func (param f64) (result f64))) + + (func $const-i32 (type $out-i32) (i32.const 0x132)) + (func $const-i64 (type $out-i64) (i64.const 0x164)) + (func $const-f32 (type $out-f32) (f32.const 0xf32)) + (func $const-f64 (type $out-f64) (f64.const 0xf64)) + + (func $id-i32 (type $over-i32) (local.get 0)) + (func $id-i64 (type $over-i64) (local.get 0)) + (func $id-f32 (type $over-f32) (local.get 0)) + (func $id-f64 (type $over-f64) (local.get 0)) + + (func $i32-i64 (type $i32-i64) (local.get 1)) + (func $i64-f64 (type $i64-f64) (local.get 1)) + (func $f32-i32 (type $f32-i32) (local.get 1)) + (func $f64-f32 (type $f64-f32) (local.get 1)) + + (func $over-i32-duplicate (type $over-i32-duplicate) (local.get 0)) + (func $over-i64-duplicate (type $over-i64-duplicate) (local.get 0)) + (func $over-f32-duplicate (type $over-f32-duplicate) (local.get 0)) + (func $over-f64-duplicate (type $over-f64-duplicate) (local.get 0)) + + (table funcref + (elem + $const-i32 $const-i64 $const-f32 $const-f64 + $id-i32 $id-i64 $id-f32 $id-f64 + $f32-i32 $i32-i64 $f64-f32 $i64-f64 + $fac $fac-acc $even $odd + $over-i32-duplicate $over-i64-duplicate + $over-f32-duplicate $over-f64-duplicate + ) + ) + + ;; Syntax + + (func + (return_call_indirect (i32.const 0)) + (return_call_indirect (param i64) (i64.const 0) (i32.const 0)) + (return_call_indirect (param i64) (param) (param f64 i32 i64) + (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (i32.const 0) + ) + (return_call_indirect (result) (i32.const 0)) + ) + + (func (result i32) + (return_call_indirect (result i32) (i32.const 0)) + (return_call_indirect (result i32) (result) (i32.const 0)) + (return_call_indirect (param i64) (result i32) (i64.const 0) (i32.const 0)) + (return_call_indirect + (param) (param i64) (param) (param f64 i32 i64) (param) (param) + (result) (result i32) (result) (result) + (i64.const 0) (f64.const 0) (i32.const 0) (i64.const 0) (i32.const 0) + ) + ) + + (func (result i64) + (return_call_indirect (type $over-i64) (param i64) (result i64) + (i64.const 0) (i32.const 0) + ) + ) + + ;; Typing + + (func (export "type-i32") (result i32) + (return_call_indirect (type $out-i32) (i32.const 0)) + ) + (func (export "type-i64") (result i64) + (return_call_indirect (type $out-i64) (i32.const 1)) + ) + (func (export "type-f32") (result f32) + (return_call_indirect (type $out-f32) (i32.const 2)) + ) + (func (export "type-f64") (result f64) + (return_call_indirect (type $out-f64) (i32.const 3)) + ) + + (func (export "type-index") (result i64) + (return_call_indirect (type $over-i64) (i64.const 100) (i32.const 5)) + ) + + (func (export "type-first-i32") (result i32) + (return_call_indirect (type $over-i32) (i32.const 32) (i32.const 4)) + ) + (func (export "type-first-i64") (result i64) + (return_call_indirect (type $over-i64) (i64.const 64) (i32.const 5)) + ) + (func (export "type-first-f32") (result f32) + (return_call_indirect (type $over-f32) (f32.const 1.32) (i32.const 6)) + ) + (func (export "type-first-f64") (result f64) + (return_call_indirect (type $over-f64) (f64.const 1.64) (i32.const 7)) + ) + + (func (export "type-second-i32") (result i32) + (return_call_indirect (type $f32-i32) + (f32.const 32.1) (i32.const 32) (i32.const 8) + ) + ) + (func (export "type-second-i64") (result i64) + (return_call_indirect (type $i32-i64) + (i32.const 32) (i64.const 64) (i32.const 9) + ) + ) + (func (export "type-second-f32") (result f32) + (return_call_indirect (type $f64-f32) + (f64.const 64) (f32.const 32) (i32.const 10) + ) + ) + (func (export "type-second-f64") (result f64) + (return_call_indirect (type $i64-f64) + (i64.const 64) (f64.const 64.1) (i32.const 11) + ) + ) + + ;; Dispatch + + (func (export "dispatch") (param i32 i64) (result i64) + (return_call_indirect (type $over-i64) (local.get 1) (local.get 0)) + ) + + (func (export "dispatch-structural") (param i32) (result i64) + (return_call_indirect (type $over-i64-duplicate) + (i64.const 9) (local.get 0) + ) + ) + + ;; Multiple tables + + (table $tab2 funcref (elem $tab-f1)) + (table $tab3 funcref (elem $tab-f2)) + + (func $tab-f1 (result i32) (i32.const 0x133)) + (func $tab-f2 (result i32) (i32.const 0x134)) + + (func (export "call-tab") (param $i i32) (result i32) + (if (i32.eq (local.get $i) (i32.const 0)) + (then (return_call_indirect (type $out-i32) (i32.const 0))) + ) + (if (i32.eq (local.get $i) (i32.const 1)) + (then (return_call_indirect 1 (type $out-i32) (i32.const 0))) + ) + (if (i32.eq (local.get $i) (i32.const 2)) + (then (return_call_indirect $tab3 (type $out-i32) (i32.const 0))) + ) + (i32.const 0) + ) + + ;; Recursion + + (func $fac (export "fac") (type $over-i64) + (return_call_indirect (param i64 i64) (result i64) + (local.get 0) (i64.const 1) (i32.const 13) + ) + ) + + (func $fac-acc (param i64 i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 1)) + (else + (return_call_indirect (param i64 i64) (result i64) + (i64.sub (local.get 0) (i64.const 1)) + (i64.mul (local.get 0) (local.get 1)) + (i32.const 13) + ) + ) + ) + ) + + (func $even (export "even") (param i32) (result i32) + (if (result i32) (i32.eqz (local.get 0)) + (then (i32.const 44)) + (else + (return_call_indirect (type $over-i32) + (i32.sub (local.get 0) (i32.const 1)) + (i32.const 15) + ) + ) + ) + ) + (func $odd (export "odd") (param i32) (result i32) + (if (result i32) (i32.eqz (local.get 0)) + (then (i32.const 99)) + (else + (return_call_indirect (type $over-i32) + (i32.sub (local.get 0) (i32.const 1)) + (i32.const 14) + ) + ) + ) + ) +) + +(assert_return (invoke "type-i32") (i32.const 0x132)) +(assert_return (invoke "type-i64") (i64.const 0x164)) +(assert_return (invoke "type-f32") (f32.const 0xf32)) +(assert_return (invoke "type-f64") (f64.const 0xf64)) + +(assert_return (invoke "type-index") (i64.const 100)) + +(assert_return (invoke "type-first-i32") (i32.const 32)) +(assert_return (invoke "type-first-i64") (i64.const 64)) +(assert_return (invoke "type-first-f32") (f32.const 1.32)) +(assert_return (invoke "type-first-f64") (f64.const 1.64)) + +(assert_return (invoke "type-second-i32") (i32.const 32)) +(assert_return (invoke "type-second-i64") (i64.const 64)) +(assert_return (invoke "type-second-f32") (f32.const 32)) +(assert_return (invoke "type-second-f64") (f64.const 64.1)) + +(assert_return (invoke "dispatch" (i32.const 5) (i64.const 2)) (i64.const 2)) +(assert_return (invoke "dispatch" (i32.const 5) (i64.const 5)) (i64.const 5)) +(assert_return (invoke "dispatch" (i32.const 12) (i64.const 5)) (i64.const 120)) +(assert_return (invoke "dispatch" (i32.const 17) (i64.const 2)) (i64.const 2)) +(assert_trap (invoke "dispatch" (i32.const 0) (i64.const 2)) "indirect call type mismatch") +(assert_trap (invoke "dispatch" (i32.const 15) (i64.const 2)) "indirect call type mismatch") +(assert_trap (invoke "dispatch" (i32.const 20) (i64.const 2)) "undefined element") +(assert_trap (invoke "dispatch" (i32.const -1) (i64.const 2)) "undefined element") +(assert_trap (invoke "dispatch" (i32.const 1213432423) (i64.const 2)) "undefined element") + +(assert_return (invoke "dispatch-structural" (i32.const 5)) (i64.const 9)) +(assert_return (invoke "dispatch-structural" (i32.const 5)) (i64.const 9)) +(assert_return (invoke "dispatch-structural" (i32.const 12)) (i64.const 362880)) +(assert_return (invoke "dispatch-structural" (i32.const 17)) (i64.const 9)) +(assert_trap (invoke "dispatch-structural" (i32.const 11)) "indirect call type mismatch") +(assert_trap (invoke "dispatch-structural" (i32.const 16)) "indirect call type mismatch") + +(assert_return (invoke "call-tab" (i32.const 0)) (i32.const 0x132)) +(assert_return (invoke "call-tab" (i32.const 1)) (i32.const 0x133)) +(assert_return (invoke "call-tab" (i32.const 2)) (i32.const 0x134)) + +(assert_return (invoke "fac" (i64.const 0)) (i64.const 1)) +(assert_return (invoke "fac" (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac" (i64.const 5)) (i64.const 120)) +(assert_return (invoke "fac" (i64.const 25)) (i64.const 7034535277573963776)) + +(assert_return (invoke "even" (i32.const 0)) (i32.const 44)) +(assert_return (invoke "even" (i32.const 1)) (i32.const 99)) +(assert_return (invoke "even" (i32.const 100)) (i32.const 44)) +(assert_return (invoke "even" (i32.const 77)) (i32.const 99)) +(assert_return (invoke "even" (i32.const 100_000)) (i32.const 44)) +(assert_return (invoke "even" (i32.const 111_111)) (i32.const 99)) +(assert_return (invoke "odd" (i32.const 0)) (i32.const 99)) +(assert_return (invoke "odd" (i32.const 1)) (i32.const 44)) +(assert_return (invoke "odd" (i32.const 200)) (i32.const 99)) +(assert_return (invoke "odd" (i32.const 77)) (i32.const 44)) +(assert_return (invoke "odd" (i32.const 200_002)) (i32.const 99)) +(assert_return (invoke "odd" (i32.const 300_003)) (i32.const 44)) + + +;; Invalid syntax + +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (type $sig) (result i32) (param i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (param i32) (type $sig) (result i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (param i32) (result i32) (type $sig)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (result i32) (type $sig) (param i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (result i32) (param i32) (type $sig)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (result i32) (param i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "unexpected token" +) + +(assert_malformed + (module quote + "(table 0 funcref)" + "(func (return_call_indirect (param $x i32) (i32.const 0) (i32.const 0)))" + ) + "unexpected token" +) +(assert_malformed + (module quote + "(type $sig (func))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (type $sig) (result i32) (i32.const 0))" + ")" + ) + "inline function type" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (type $sig) (result i32) (i32.const 0))" + ")" + ) + "inline function type" +) +(assert_malformed + (module quote + "(type $sig (func (param i32) (result i32)))" + "(table 0 funcref)" + "(func" + " (return_call_indirect (type $sig) (param i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "inline function type" +) +(assert_malformed + (module quote + "(type $sig (func (param i32 i32) (result i32)))" + "(table 0 funcref)" + "(func (result i32)" + " (return_call_indirect (type $sig) (param i32) (result i32)" + " (i32.const 0) (i32.const 0)" + " )" + ")" + ) + "inline function type" +) + +;; Invalid typing + +(assert_invalid + (module + (type (func)) + (func $no-table (return_call_indirect (type 0) (i32.const 0))) + ) + "unknown table" +) + +(assert_invalid + (module + (type (func)) + (table 0 funcref) + (func $type-void-vs-num (i32.eqz (return_call_indirect (type 0) (i32.const 0)))) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (result i64))) + (table 0 funcref) + (func $type-num-vs-num (i32.eqz (return_call_indirect (type 0) (i32.const 0)))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type (func (param i32))) + (table 0 funcref) + (func $arity-0-vs-1 (return_call_indirect (type 0) (i32.const 0))) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (param f64 i32))) + (table 0 funcref) + (func $arity-0-vs-2 (return_call_indirect (type 0) (i32.const 0))) + ) + "type mismatch" +) + +(module + (type (func)) + (table 0 funcref) + (func $arity-1-vs-0 (return_call_indirect (type 0) (i32.const 1) (i32.const 0))) +) + +(module + (type (func)) + (table 0 funcref) + (func $arity-2-vs-0 + (return_call_indirect (type 0) (f64.const 2) (i32.const 1) (i32.const 0)) + ) +) + +(assert_invalid + (module + (type (func (param i32))) + (table 0 funcref) + (func $type-func-void-vs-i32 (return_call_indirect (type 0) (i32.const 1) (nop))) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (param i32))) + (table 0 funcref) + (func $type-func-num-vs-i32 (return_call_indirect (type 0) (i32.const 0) (i64.const 1))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type (func (param i32 i32))) + (table 0 funcref) + (func $type-first-void-vs-num + (return_call_indirect (type 0) (nop) (i32.const 1) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (param i32 i32))) + (table 0 funcref) + (func $type-second-void-vs-num + (return_call_indirect (type 0) (i32.const 1) (nop) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (param i32 f64))) + (table 0 funcref) + (func $type-first-num-vs-num + (return_call_indirect (type 0) (f64.const 1) (i32.const 1) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type (func (param f64 i32))) + (table 0 funcref) + (func $type-second-num-vs-num + (return_call_indirect (type 0) (i32.const 1) (f64.const 1) (i32.const 0)) + ) + ) + "type mismatch" +) + + +;; Unbound type + +(assert_invalid + (module + (table 0 funcref) + (func $unbound-type (return_call_indirect (type 1) (i32.const 0))) + ) + "unknown type" +) +(assert_invalid + (module + (table 0 funcref) + (func $large-type (return_call_indirect (type 1012321300) (i32.const 0))) + ) + "unknown type" +) + + +;; Unbound function in table + +(assert_invalid + (module (table funcref (elem 0 0))) + "unknown function 0" +) diff --git a/test/core/return_call_ref.wast b/test/core/return_call_ref.wast new file mode 100644 index 0000000000..2b495f46e9 --- /dev/null +++ b/test/core/return_call_ref.wast @@ -0,0 +1,376 @@ +;; Test `return_call_ref` operator + +(module + ;; Auxiliary definitions + (type $proc (func)) + (type $-i32 (func (result i32))) + (type $-i64 (func (result i64))) + (type $-f32 (func (result f32))) + (type $-f64 (func (result f64))) + + (type $i32-i32 (func (param i32) (result i32))) + (type $i64-i64 (func (param i64) (result i64))) + (type $f32-f32 (func (param f32) (result f32))) + (type $f64-f64 (func (param f64) (result f64))) + + (type $f32-i32 (func (param f32 i32) (result i32))) + (type $i32-i64 (func (param i32 i64) (result i64))) + (type $f64-f32 (func (param f64 f32) (result f32))) + (type $i64-f64 (func (param i64 f64) (result f64))) + + (type $i64i64-i64 (func (param i64 i64) (result i64))) + + (func $const-i32 (result i32) (i32.const 0x132)) + (func $const-i64 (result i64) (i64.const 0x164)) + (func $const-f32 (result f32) (f32.const 0xf32)) + (func $const-f64 (result f64) (f64.const 0xf64)) + + (func $id-i32 (param i32) (result i32) (local.get 0)) + (func $id-i64 (param i64) (result i64) (local.get 0)) + (func $id-f32 (param f32) (result f32) (local.get 0)) + (func $id-f64 (param f64) (result f64) (local.get 0)) + + (func $f32-i32 (param f32 i32) (result i32) (local.get 1)) + (func $i32-i64 (param i32 i64) (result i64) (local.get 1)) + (func $f64-f32 (param f64 f32) (result f32) (local.get 1)) + (func $i64-f64 (param i64 f64) (result f64) (local.get 1)) + + (global $const-i32 (ref $-i32) (ref.func $const-i32)) + (global $const-i64 (ref $-i64) (ref.func $const-i64)) + (global $const-f32 (ref $-f32) (ref.func $const-f32)) + (global $const-f64 (ref $-f64) (ref.func $const-f64)) + + (global $id-i32 (ref $i32-i32) (ref.func $id-i32)) + (global $id-i64 (ref $i64-i64) (ref.func $id-i64)) + (global $id-f32 (ref $f32-f32) (ref.func $id-f32)) + (global $id-f64 (ref $f64-f64) (ref.func $id-f64)) + + (global $f32-i32 (ref $f32-i32) (ref.func $f32-i32)) + (global $i32-i64 (ref $i32-i64) (ref.func $i32-i64)) + (global $f64-f32 (ref $f64-f32) (ref.func $f64-f32)) + (global $i64-f64 (ref $i64-f64) (ref.func $i64-f64)) + + (elem declare func + $const-i32 $const-i64 $const-f32 $const-f64 + $id-i32 $id-i64 $id-f32 $id-f64 + $f32-i32 $i32-i64 $f64-f32 $i64-f64 + ) + + ;; Typing + + (func (export "type-i32") (result i32) + (return_call_ref $-i32 (global.get $const-i32)) + ) + (func (export "type-i64") (result i64) + (return_call_ref $-i64 (global.get $const-i64)) + ) + (func (export "type-f32") (result f32) + (return_call_ref $-f32 (global.get $const-f32)) + ) + (func (export "type-f64") (result f64) + (return_call_ref $-f64 (global.get $const-f64)) + ) + + (func (export "type-first-i32") (result i32) + (return_call_ref $i32-i32 (i32.const 32) (global.get $id-i32)) + ) + (func (export "type-first-i64") (result i64) + (return_call_ref $i64-i64 (i64.const 64) (global.get $id-i64)) + ) + (func (export "type-first-f32") (result f32) + (return_call_ref $f32-f32 (f32.const 1.32) (global.get $id-f32)) + ) + (func (export "type-first-f64") (result f64) + (return_call_ref $f64-f64 (f64.const 1.64) (global.get $id-f64)) + ) + + (func (export "type-second-i32") (result i32) + (return_call_ref $f32-i32 (f32.const 32.1) (i32.const 32) (global.get $f32-i32)) + ) + (func (export "type-second-i64") (result i64) + (return_call_ref $i32-i64 (i32.const 32) (i64.const 64) (global.get $i32-i64)) + ) + (func (export "type-second-f32") (result f32) + (return_call_ref $f64-f32 (f64.const 64) (f32.const 32) (global.get $f64-f32)) + ) + (func (export "type-second-f64") (result f64) + (return_call_ref $i64-f64 (i64.const 64) (f64.const 64.1) (global.get $i64-f64)) + ) + + ;; Null + + (func (export "null") + (return_call_ref $proc (ref.null $proc)) + ) + + ;; Recursion + + (global $fac-acc (ref $i64i64-i64) (ref.func $fac-acc)) + + (elem declare func $fac-acc) + (func $fac-acc (export "fac-acc") (param i64 i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 1)) + (else + (return_call_ref $i64i64-i64 + (i64.sub (local.get 0) (i64.const 1)) + (i64.mul (local.get 0) (local.get 1)) + (global.get $fac-acc) + ) + ) + ) + ) + + (global $count (ref $i64-i64) (ref.func $count)) + + (elem declare func $count) + (func $count (export "count") (param i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (local.get 0)) + (else + (return_call_ref $i64-i64 + (i64.sub (local.get 0) (i64.const 1)) + (global.get $count) + ) + ) + ) + ) + + (global $even (ref $i64-i64) (ref.func $even)) + (global $odd (ref $i64-i64) (ref.func $odd)) + + (elem declare func $even) + (func $even (export "even") (param i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (i64.const 44)) + (else + (return_call_ref $i64-i64 + (i64.sub (local.get 0) (i64.const 1)) + (global.get $odd) + ) + ) + ) + ) + (elem declare func $odd) + (func $odd (export "odd") (param i64) (result i64) + (if (result i64) (i64.eqz (local.get 0)) + (then (i64.const 99)) + (else + (return_call_ref $i64-i64 + (i64.sub (local.get 0) (i64.const 1)) + (global.get $even) + ) + ) + ) + ) +) + +(assert_return (invoke "type-i32") (i32.const 0x132)) +(assert_return (invoke "type-i64") (i64.const 0x164)) +(assert_return (invoke "type-f32") (f32.const 0xf32)) +(assert_return (invoke "type-f64") (f64.const 0xf64)) + +(assert_return (invoke "type-first-i32") (i32.const 32)) +(assert_return (invoke "type-first-i64") (i64.const 64)) +(assert_return (invoke "type-first-f32") (f32.const 1.32)) +(assert_return (invoke "type-first-f64") (f64.const 1.64)) + +(assert_return (invoke "type-second-i32") (i32.const 32)) +(assert_return (invoke "type-second-i64") (i64.const 64)) +(assert_return (invoke "type-second-f32") (f32.const 32)) +(assert_return (invoke "type-second-f64") (f64.const 64.1)) + +(assert_trap (invoke "null") "null function reference") + +(assert_return (invoke "fac-acc" (i64.const 0) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 1) (i64.const 1)) (i64.const 1)) +(assert_return (invoke "fac-acc" (i64.const 5) (i64.const 1)) (i64.const 120)) +(assert_return + (invoke "fac-acc" (i64.const 25) (i64.const 1)) + (i64.const 7034535277573963776) +) + +(assert_return (invoke "count" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "count" (i64.const 1000)) (i64.const 0)) +(assert_return (invoke "count" (i64.const 1_000_000)) (i64.const 0)) + +(assert_return (invoke "even" (i64.const 0)) (i64.const 44)) +(assert_return (invoke "even" (i64.const 1)) (i64.const 99)) +(assert_return (invoke "even" (i64.const 100)) (i64.const 44)) +(assert_return (invoke "even" (i64.const 77)) (i64.const 99)) +(assert_return (invoke "even" (i64.const 1_000_000)) (i64.const 44)) +(assert_return (invoke "even" (i64.const 1_000_001)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 0)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 1)) (i64.const 44)) +(assert_return (invoke "odd" (i64.const 200)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 77)) (i64.const 44)) +(assert_return (invoke "odd" (i64.const 1_000_000)) (i64.const 99)) +(assert_return (invoke "odd" (i64.const 999_999)) (i64.const 44)) + + +;; More typing + +(module + (type $t (func)) + (type $t1 (func (result (ref $t)))) + (type $t2 (func (result (ref null $t)))) + (type $t3 (func (result (ref func)))) + (type $t4 (func (result (ref null func)))) + (elem declare func $f11 $f22 $f33 $f44) + (func $f11 (result (ref $t)) (return_call_ref $t1 (ref.func $f11))) + (func $f21 (result (ref null $t)) (return_call_ref $t1 (ref.func $f11))) + (func $f22 (result (ref null $t)) (return_call_ref $t2 (ref.func $f22))) + (func $f31 (result (ref func)) (return_call_ref $t1 (ref.func $f11))) + (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33))) + (func $f41 (result (ref null func)) (return_call_ref $t1 (ref.func $f11))) + (func $f42 (result (ref null func)) (return_call_ref $t2 (ref.func $f22))) + (func $f43 (result (ref null func)) (return_call_ref $t3 (ref.func $f33))) + (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44))) +) + +(assert_invalid + (module + (type $t (func)) + (type $t2 (func (result (ref null $t)))) + (elem declare func $f22) + (func $f12 (result (ref $t)) (return_call_ref $t2 (ref.func $f22))) + (func $f22 (result (ref null $t)) (return_call_ref $t2 (ref.func $f22))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (type $t3 (func (result (ref func)))) + (elem declare func $f33) + (func $f13 (result (ref $t)) (return_call_ref $t3 (ref.func $f33))) + (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (type $t4 (func (result (ref null func)))) + (elem declare func $f44) + (func $f14 (result (ref $t)) (return_call_ref $t4 (ref.func $f44))) + (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (type $t3 (func (result (ref func)))) + (elem declare func $f33) + (func $f23 (result (ref null $t)) (return_call_ref $t3 (ref.func $f33))) + (func $f33 (result (ref func)) (return_call_ref $t3 (ref.func $f33))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (type $t4 (func (result (ref null func)))) + (elem declare func $f44) + (func $f24 (result (ref null $t)) (return_call_ref $t4 (ref.func $f44))) + (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t4 (func (result (ref null func)))) + (elem declare func $f44) + (func $f34 (result (ref func)) (return_call_ref $t4 (ref.func $f44))) + (func $f44 (result (ref null func)) (return_call_ref $t4 (ref.func $f44))) + ) + "type mismatch" +) + + +;; Unreachable typing. + +(module + (type $t (func (result i32))) + (func (export "unreachable") (result i32) + (unreachable) + (return_call_ref $t) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (ref.func $f) + (return_call_ref $t) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (i32.const 0) + (ref.func $f) + (return_call_ref $t) + (i32.const 0) + ) +) +(assert_trap (invoke "unreachable") "unreachable") + +(assert_invalid + (module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (i64.const 0) + (ref.func $f) + (return_call_ref $t) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (elem declare func $f) + (type $t (func (param i32) (result i32))) + (func $f (param i32) (result i32) (local.get 0)) + + (func (export "unreachable") (result i32) + (unreachable) + (ref.func $f) + (return_call_ref $t) + (i64.const 0) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $t (func)) + (func $f (param $r externref) + (return_call_ref $t (local.get $r)) + ) + ) + "type mismatch" +) diff --git a/test/core/run.py b/test/core/run.py index 6500e6a6bb..21036065d6 100755 --- a/test/core/run.py +++ b/test/core/run.py @@ -26,14 +26,17 @@ sys.argv = sys.argv[:1] main_test_files = glob.glob(os.path.join(inputDir, "*.wast")) -# SIMD test files are in a subdirectory. +# Other test files are in subdirectories simd_test_files = glob.glob(os.path.join(inputDir, "simd", "*.wast")) +gc_test_files = glob.glob(os.path.join(inputDir, "gc", "*.wast")) +multi_memory_test_files = glob.glob(os.path.join(inputDir, "multi-memory", "*.wast")) +all_test_files = main_test_files + simd_test_files + gc_test_files + multi_memory_test_files wasmCommand = arguments.wasm jsCommand = arguments.js generateJsOnly = arguments.generate_js_only outputDir = arguments.out -inputFiles = arguments.file if arguments.file else main_test_files + simd_test_files +inputFiles = arguments.file if arguments.file else all_test_files if not os.path.exists(wasmCommand): sys.stderr.write("""\ diff --git a/test/core/select.wast b/test/core/select.wast index 673dcf478d..61e4dc22db 100644 --- a/test/core/select.wast +++ b/test/core/select.wast @@ -36,6 +36,42 @@ (select (result externref) (local.get 0) (local.get 1) (local.get 2)) ) + (type $t (func)) + (func $tf) (elem declare func $tf) + (func (export "join-funcnull") (param i32) (result (ref null func)) + (select (result (ref null func)) + (ref.func $tf) + (ref.null func) + (local.get 0) + ) + ) + + ;; Check that both sides of the select are evaluated + (func (export "select-trap-left") (param $cond i32) (result i32) + (select (unreachable) (i32.const 0) (local.get $cond)) + ) + (func (export "select-trap-right") (param $cond i32) (result i32) + (select (i32.const 0) (unreachable) (local.get $cond)) + ) + + (func (export "select-unreached") + (unreachable) (select) + (unreachable) (i32.const 0) (select) + (unreachable) (i32.const 0) (i32.const 0) (select) + (unreachable) (i32.const 0) (i32.const 0) (i32.const 0) (select) + (unreachable) (f32.const 0) (i32.const 0) (select) + (unreachable) + ) + + (func (export "select_unreached_result_1") (result i32) + (unreachable) (i32.add (select)) + ) + + (func (export "select_unreached_result_2") (result i64) + (unreachable) (i64.add (select (i64.const 0) (i32.const 0))) + ) + + ;; As the argument of control constructs and instructions (func (export "as-select-first") (param i32) (result i32) @@ -240,6 +276,14 @@ (assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan) (i32.const 0)) (f64.const nan)) (assert_return (invoke "select-f64-t" (f64.const 2) (f64.const nan:0x20304) (i32.const 0)) (f64.const nan:0x20304)) +(assert_return (invoke "join-funcnull" (i32.const 1)) (ref.func)) +(assert_return (invoke "join-funcnull" (i32.const 0)) (ref.null)) + +(assert_trap (invoke "select-trap-left" (i32.const 1)) "unreachable") +(assert_trap (invoke "select-trap-left" (i32.const 0)) "unreachable") +(assert_trap (invoke "select-trap-right" (i32.const 1)) "unreachable") +(assert_trap (invoke "select-trap-right" (i32.const 0)) "unreachable") + (assert_return (invoke "as-select-first" (i32.const 0)) (i32.const 1)) (assert_return (invoke "as-select-first" (i32.const 1)) (i32.const 0)) (assert_return (invoke "as-select-mid" (i32.const 0)) (i32.const 2)) @@ -336,12 +380,29 @@ ) +(assert_invalid + (module (type $t (func)) + (func $type-ref-implicit (param $r (ref $t)) + (drop (select (local.get $r) (local.get $r) (i32.const 1))) + ) + ) + "type mismatch" +) +(assert_invalid + (module (func $type-funcref-implicit (param $r funcref) + (drop (select (local.get $r) (local.get $r) (i32.const 1))) + )) + "type mismatch" +) (assert_invalid (module (func $type-externref-implicit (param $r externref) (drop (select (local.get $r) (local.get $r) (i32.const 1))) )) "type mismatch" ) +(module (func $type-unreachable-ref-implicit + (drop (ref.is_null (select (unreachable) (i32.const 1)))) +)) (assert_invalid (module (func $type-num-vs-num diff --git a/test/core/simd/simd_memory-multi.wast b/test/core/simd/simd_memory-multi.wast new file mode 100644 index 0000000000..202f2cd8d0 --- /dev/null +++ b/test/core/simd/simd_memory-multi.wast @@ -0,0 +1,42 @@ +;; From wasmtime misc_testsuite/multi-memory/simple.wast + +;; Test syntax for load/store_lane immediates + +(module + (memory 1) + (memory $m 1) + + (func + (local $v v128) + + (drop (v128.load8_lane 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 offset=0 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 offset=0 align=1 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 align=1 1 (i32.const 0) (local.get $v))) + + (drop (v128.load8_lane $m 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane $m offset=0 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane $m offset=0 align=1 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane $m align=1 1 (i32.const 0) (local.get $v))) + + (drop (v128.load8_lane 1 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 offset=0 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 offset=0 align=1 1 (i32.const 0) (local.get $v))) + (drop (v128.load8_lane 1 align=1 1 (i32.const 0) (local.get $v))) + + (v128.store8_lane 1 (i32.const 0) (local.get $v)) + (v128.store8_lane offset=0 1 (i32.const 0) (local.get $v)) + (v128.store8_lane offset=0 align=1 1 (i32.const 0) (local.get $v)) + (v128.store8_lane align=1 1 (i32.const 0) (local.get $v)) + + (v128.store8_lane $m 1 (i32.const 0) (local.get $v)) + (v128.store8_lane $m offset=0 1 (i32.const 0) (local.get $v)) + (v128.store8_lane $m offset=0 align=1 1 (i32.const 0) (local.get $v)) + (v128.store8_lane $m align=1 1 (i32.const 0) (local.get $v)) + + (v128.store8_lane 1 1 (i32.const 0) (local.get $v)) + (v128.store8_lane 1 offset=0 1 (i32.const 0) (local.get $v)) + (v128.store8_lane 1 offset=0 align=1 1 (i32.const 0) (local.get $v)) + (v128.store8_lane 1 align=1 1 (i32.const 0) (local.get $v)) + ) +) diff --git a/test/core/store.wast b/test/core/store.wast index 01c3a2dd64..86f6263afb 100644 --- a/test/core/store.wast +++ b/test/core/store.wast @@ -1,3 +1,151 @@ +;; Multiple memories + +(module + (memory $mem1 1) + (memory $mem2 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (func (export "store1") (param i32 i64) + (i64.store $mem1 (local.get 0) (local.get 1)) + ) + (func (export "store2") (param i32 i64) + (i64.store $mem2 (local.get 0) (local.get 1)) + ) +) + +(invoke "store1" (i32.const 0) (i64.const 1)) +(invoke "store2" (i32.const 0) (i64.const 2)) +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) + + +(module $M1 + (memory (export "mem") 1) + + (func (export "load") (param i32) (result i64) + (i64.load (local.get 0)) + ) + (func (export "store") (param i32 i64) + (i64.store (local.get 0) (local.get 1)) + ) +) +(register "M1") + +(module $M2 + (memory (export "mem") 1) + + (func (export "load") (param i32) (result i64) + (i64.load (local.get 0)) + ) + (func (export "store") (param i32 i64) + (i64.store (local.get 0) (local.get 1)) + ) +) +(register "M2") + +(invoke $M1 "store" (i32.const 0) (i64.const 1)) +(invoke $M2 "store" (i32.const 0) (i64.const 2)) +(assert_return (invoke $M1 "load" (i32.const 0)) (i64.const 1)) +(assert_return (invoke $M2 "load" (i32.const 0)) (i64.const 2)) + +(module + (memory $mem1 (import "M1" "mem") 1) + (memory $mem2 (import "M2" "mem") 1) + + (func (export "load1") (param i32) (result i64) + (i64.load $mem1 (local.get 0)) + ) + (func (export "load2") (param i32) (result i64) + (i64.load $mem2 (local.get 0)) + ) + + (func (export "store1") (param i32 i64) + (i64.store $mem1 (local.get 0) (local.get 1)) + ) + (func (export "store2") (param i32 i64) + (i64.store $mem2 (local.get 0) (local.get 1)) + ) +) + +(invoke "store1" (i32.const 0) (i64.const 1)) +(invoke "store2" (i32.const 0) (i64.const 2)) +(assert_return (invoke "load1" (i32.const 0)) (i64.const 1)) +(assert_return (invoke "load2" (i32.const 0)) (i64.const 2)) + + +(module + (memory (export "mem") 2) +) +(register "M") + +(module + (memory $mem1 (import "M" "mem") 2) + (memory $mem2 3) + + (data (memory $mem1) (i32.const 20) "\01\02\03\04\05") + (data (memory $mem2) (i32.const 50) "\0A\0B\0C\0D\0E") + + (func (export "read1") (param i32) (result i32) + (i32.load8_u $mem1 (local.get 0)) + ) + (func (export "read2") (param i32) (result i32) + (i32.load8_u $mem2 (local.get 0)) + ) + + (func (export "copy-1-to-2") + (local $i i32) + (local.set $i (i32.const 20)) + (loop $cont + (br_if 1 (i32.eq (local.get $i) (i32.const 23))) + (i32.store8 $mem2 (local.get $i) (i32.load8_u $mem1 (local.get $i))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $cont) + ) + ) + + (func (export "copy-2-to-1") + (local $i i32) + (local.set $i (i32.const 50)) + (loop $cont + (br_if 1 (i32.eq (local.get $i) (i32.const 54))) + (i32.store8 $mem1 (local.get $i) (i32.load8_u $mem2 (local.get $i))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $cont) + ) + ) +) + +(assert_return (invoke "read2" (i32.const 20)) (i32.const 0)) +(assert_return (invoke "read2" (i32.const 21)) (i32.const 0)) +(assert_return (invoke "read2" (i32.const 22)) (i32.const 0)) +(assert_return (invoke "read2" (i32.const 23)) (i32.const 0)) +(assert_return (invoke "read2" (i32.const 24)) (i32.const 0)) +(invoke "copy-1-to-2") +(assert_return (invoke "read2" (i32.const 20)) (i32.const 1)) +(assert_return (invoke "read2" (i32.const 21)) (i32.const 2)) +(assert_return (invoke "read2" (i32.const 22)) (i32.const 3)) +(assert_return (invoke "read2" (i32.const 23)) (i32.const 0)) +(assert_return (invoke "read2" (i32.const 24)) (i32.const 0)) + +(assert_return (invoke "read1" (i32.const 50)) (i32.const 0)) +(assert_return (invoke "read1" (i32.const 51)) (i32.const 0)) +(assert_return (invoke "read1" (i32.const 52)) (i32.const 0)) +(assert_return (invoke "read1" (i32.const 53)) (i32.const 0)) +(assert_return (invoke "read1" (i32.const 54)) (i32.const 0)) +(invoke "copy-2-to-1") +(assert_return (invoke "read1" (i32.const 50)) (i32.const 10)) +(assert_return (invoke "read1" (i32.const 51)) (i32.const 11)) +(assert_return (invoke "read1" (i32.const 52)) (i32.const 12)) +(assert_return (invoke "read1" (i32.const 53)) (i32.const 13)) +(assert_return (invoke "read1" (i32.const 54)) (i32.const 0)) + + ;; Store operator as the argument of control constructs and instructions (module diff --git a/test/core/table-sub.wast b/test/core/table-sub.wast index 08787bddd2..c3a7440cec 100644 --- a/test/core/table-sub.wast +++ b/test/core/table-sub.wast @@ -1,3 +1,14 @@ +(module + (type $t (func)) + (table $t1 10 (ref null func)) + (table $t2 10 (ref null $t)) + (elem $el funcref) + (func $f + (table.init $t1 $el (i32.const 0) (i32.const 1) (i32.const 2)) + (table.copy $t1 $t2 (i32.const 0) (i32.const 1) (i32.const 2)) + ) +) + (assert_invalid (module (table $t1 10 funcref) diff --git a/test/core/table.wast b/test/core/table.wast index d61a7d1b70..1b6afe9b98 100644 --- a/test/core/table.wast +++ b/test/core/table.wast @@ -8,9 +8,20 @@ (module (table 0 65536 funcref)) (module (table 0 0xffff_ffff funcref)) +(module (table 1 (ref null func))) +(module (table 1 (ref null extern))) +(module (table 1 (ref null $t)) (type $t (func))) + (module (table 0 funcref) (table 0 funcref)) (module (table (import "spectest" "table") 0 funcref) (table 0 funcref)) +(module (table 0 funcref (ref.null func))) +(module (table 1 funcref (ref.null func))) +(module (table 1 (ref null func) (ref.null func))) + +(assert_invalid (module (elem (i32.const 0))) "unknown table") +(assert_invalid (module (elem (i32.const 0) $f) (func $f)) "unknown table") + (assert_invalid (module (table 1 0 funcref)) "size minimum must not be greater than maximum" @@ -60,17 +71,116 @@ (assert_invalid (module (elem (i32.const 0))) "unknown table") (assert_invalid (module (elem (i32.const 0) $f) (func $f)) "unknown table") +(assert_invalid + (module (table 1 (ref null func) (i32.const 0))) + "type mismatch" +) +(assert_invalid + (module (table 1 (ref func) (ref.null extern))) + "type mismatch" +) +(assert_invalid + (module (type $t (func)) (table 1 (ref $t) (ref.null func))) + "type mismatch" +) +(assert_invalid + (module (table 1 (ref func) (ref.null func))) + "type mismatch" +) +(assert_invalid + (module (table 0 (ref func))) + "type mismatch" +) +(assert_invalid + (module (table 0 (ref extern))) + "type mismatch" +) +(assert_invalid + (module (type $t (func)) (table 0 (ref $t))) + "type mismatch" +) + + +;; Table initializer + +(module + (global (export "g") (ref $f) (ref.func $f)) + (type $f (func)) + (func $f) +) +(register "M") + +(module + (global $g (import "M" "g") (ref $dummy)) + + (type $dummy (func)) + (func $dummy) + + (table $t1 10 funcref) + (table $t2 10 funcref (ref.func $dummy)) + (table $t3 10 (ref $dummy) (ref.func $dummy)) + (table $t4 10 funcref (global.get $g)) + (table $t5 10 (ref $dummy) (global.get $g)) + + (func (export "get1") (result funcref) (table.get $t1 (i32.const 1))) + (func (export "get2") (result funcref) (table.get $t2 (i32.const 4))) + (func (export "get3") (result funcref) (table.get $t3 (i32.const 7))) + (func (export "get4") (result funcref) (table.get $t4 (i32.const 8))) + (func (export "get5") (result funcref) (table.get $t5 (i32.const 9))) +) + +(assert_return (invoke "get1") (ref.null)) +(assert_return (invoke "get2") (ref.func)) +(assert_return (invoke "get3") (ref.func)) +(assert_return (invoke "get4") (ref.func)) +(assert_return (invoke "get5") (ref.func)) + + +(assert_invalid + (module + (type $f (func)) + (table 10 (ref $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (table 0 (ref $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $f (func)) + (table 0 0 (ref $f)) + ) + "type mismatch" +) + + ;; Duplicate table identifiers -(assert_malformed (module quote - "(table $foo 1 funcref)" - "(table $foo 1 funcref)") - "duplicate table") -(assert_malformed (module quote - "(import \"\" \"\" (table $foo 1 funcref))" - "(table $foo 1 funcref)") - "duplicate table") -(assert_malformed (module quote - "(import \"\" \"\" (table $foo 1 funcref))" - "(import \"\" \"\" (table $foo 1 funcref))") - "duplicate table") +(assert_malformed + (module quote + "(table $foo 1 funcref)" + "(table $foo 1 funcref)" + ) + "duplicate table" +) +(assert_malformed + (module quote + "(import \"\" \"\" (table $foo 1 funcref))" + "(table $foo 1 funcref)" + ) + "duplicate table" +) +(assert_malformed + (module quote + "(import \"\" \"\" (table $foo 1 funcref))" + "(import \"\" \"\" (table $foo 1 funcref))" + ) + "duplicate table" +) diff --git a/test/core/type-canon.wast b/test/core/type-canon.wast new file mode 100644 index 0000000000..2c723aa392 --- /dev/null +++ b/test/core/type-canon.wast @@ -0,0 +1,17 @@ +(module + (rec + (type $t1 (func (param i32 (ref $t3)))) + (type $t2 (func (param i32 (ref $t1)))) + (type $t3 (func (param i32 (ref $t2)))) + ) +) + +(module + (rec + (type $t0 (func (param i32 (ref $t2) (ref $t3)))) + (type $t1 (func (param i32 (ref $t0) i32 (ref $t4)))) + (type $t2 (func (param i32 (ref $t2) (ref $t1)))) + (type $t3 (func (param i32 (ref $t2) i32 (ref $t4)))) + (type $t4 (func (param (ref $t0) (ref $t2)))) + ) +) diff --git a/test/core/type-equivalence.wast b/test/core/type-equivalence.wast new file mode 100644 index 0000000000..b3d1151d22 --- /dev/null +++ b/test/core/type-equivalence.wast @@ -0,0 +1,324 @@ +;; Syntactic types (validation time) + +;; Simple types. + +(module + (type $t1 (func (param f32 f32) (result f32))) + (type $t2 (func (param $x f32) (param $y f32) (result f32))) + + (func $f1 (param $r (ref $t1)) (call $f2 (local.get $r))) + (func $f2 (param $r (ref $t2)) (call $f1 (local.get $r))) +) + + +;; Indirect types. + +(module + (type $s0 (func (param i32) (result f32))) + (type $s1 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $s2 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $t1 (func (param (ref $s1)) (result (ref $s2)))) + (type $t2 (func (param (ref $s2)) (result (ref $s1)))) + + (func $f1 (param $r (ref $t1)) (call $f2 (local.get $r))) + (func $f2 (param $r (ref $t2)) (call $f1 (local.get $r))) +) + + +;; Recursive types. + +(module + (rec (type $t1 (func (param i32 (ref $t1))))) + (rec (type $t2 (func (param i32 (ref $t2))))) + + (func $f1 (param $r (ref $t1)) (call $f2 (local.get $r))) + (func $f2 (param $r (ref $t2)) (call $f1 (local.get $r))) +) + +(module + (type $t1 (func (param i32 (ref $t1)))) + (type $t2 (func (param i32 (ref $t2)))) + + (func $f1 (param $r (ref $t1)) (call $f2 (local.get $r))) + (func $f2 (param $r (ref $t2)) (call $f1 (local.get $r))) +) + + +;; Isomorphic recursive types. + +(module + (rec + (type $t0 (func (param i32 (ref $t1)))) + (type $t1 (func (param i32 (ref $t0)))) + ) + (rec + (type $t2 (func (param i32 (ref $t3)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + + (func $f0 (param $r (ref $t0)) + (call $f2 (local.get $r)) + ) + (func $f1 (param $r (ref $t1)) + (call $f3 (local.get $r)) + ) + (func $f2 (param $r (ref $t2)) + (call $f0 (local.get $r)) + ) + (func $f3 (param $r (ref $t3)) + (call $f1 (local.get $r)) + ) +) + + +;; Invalid recursion. + +(assert_invalid + (module + (type $t1 (func (param (ref $t2)))) + (type $t2 (func (param (ref $t1)))) + ) + "unknown type" +) + + +;; Semantic types (run time) + +;; Simple types. + +(module + (type $t1 (func (param f32 f32))) + (type $t2 (func (param $x f32) (param $y f32))) + + (func $f1 (type $t1)) + (func $f2 (type $t2)) + (table funcref (elem $f1 $f2)) + + (func (export "run") + (call_indirect (type $t1) (f32.const 1) (f32.const 2) (i32.const 1)) + (call_indirect (type $t2) (f32.const 1) (f32.const 2) (i32.const 0)) + ) +) +(assert_return (invoke "run")) + + +;; Indirect types. + +(module + (type $s0 (func (param i32))) + (type $s1 (func (param i32 (ref $s0)))) + (type $s2 (func (param i32 (ref $s0)))) + (type $t1 (func (param (ref $s1)))) + (type $t2 (func (param (ref $s2)))) + + (func $s1 (type $s1)) + (func $s2 (type $s2)) + (func $f1 (type $t1)) + (func $f2 (type $t2)) + (table funcref (elem $f1 $f2 $s1 $s2)) + + (func (export "run") + (call_indirect (type $t1) (ref.func $s1) (i32.const 0)) + (call_indirect (type $t1) (ref.func $s1) (i32.const 1)) + (call_indirect (type $t1) (ref.func $s2) (i32.const 0)) + (call_indirect (type $t1) (ref.func $s2) (i32.const 1)) + (call_indirect (type $t2) (ref.func $s1) (i32.const 0)) + (call_indirect (type $t2) (ref.func $s1) (i32.const 1)) + (call_indirect (type $t2) (ref.func $s2) (i32.const 0)) + (call_indirect (type $t2) (ref.func $s2) (i32.const 1)) + ) +) +(assert_return (invoke "run")) + + +;; Recursive types. + +(module + (rec (type $t1 (func (result (ref null $t1))))) + (rec (type $t2 (func (result (ref null $t2))))) + + (func $f1 (type $t1) (ref.null $t1)) + (func $f2 (type $t2) (ref.null $t2)) + (table funcref (elem $f1 $f2)) + + (func (export "run") + (block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 0))) + (block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 0))) + (block (result (ref null $t2)) (call_indirect (type $t1) (i32.const 0))) + (block (result (ref null $t2)) (call_indirect (type $t2) (i32.const 0))) + (block (result (ref null $t1)) (call_indirect (type $t1) (i32.const 1))) + (block (result (ref null $t1)) (call_indirect (type $t2) (i32.const 1))) + (block (result (ref null $t2)) (call_indirect (type $t1) (i32.const 1))) + (block (result (ref null $t2)) (call_indirect (type $t2) (i32.const 1))) + (br 0) + ) +) +(assert_return (invoke "run")) + + +;; Isomorphic recursive types. + +(module + (rec + (type $t1 (func (param i32 (ref $t1)))) + (type $t2 (func (param i32 (ref $t3)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + + (rec + (type $u1 (func (param i32 (ref $u1)))) + (type $u2 (func (param i32 (ref $u3)))) + (type $u3 (func (param i32 (ref $u2)))) + ) + + (func $f1 (type $t1)) + (func $f2 (type $t2)) + (func $f3 (type $t3)) + (table funcref (elem $f1 $f2 $f3)) + + (func (export "run") + (call_indirect (type $t1) (i32.const 1) (ref.func $f1) (i32.const 0)) + (call_indirect (type $t2) (i32.const 1) (ref.func $f3) (i32.const 1)) + (call_indirect (type $t3) (i32.const 1) (ref.func $f2) (i32.const 2)) + (call_indirect (type $u1) (i32.const 1) (ref.func $f1) (i32.const 0)) + (call_indirect (type $u2) (i32.const 1) (ref.func $f3) (i32.const 1)) + (call_indirect (type $u3) (i32.const 1) (ref.func $f2) (i32.const 2)) + ) +) +(assert_return (invoke "run")) + + +;; Semantic types (link time) + +;; Simple types. + +(module + (type $t1 (func (param f32 f32) (result f32))) + (func (export "f") (param (ref $t1))) +) +(register "M") +(module + (type $t2 (func (param $x f32) (param $y f32) (result f32))) + (func (import "M" "f") (param (ref $t2))) +) + + +;; Indirect types. + +(module + (type $s0 (func (param i32) (result f32))) + (type $s1 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $s2 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $t1 (func (param (ref $s1)) (result (ref $s2)))) + (type $t2 (func (param (ref $s2)) (result (ref $s1)))) + (func (export "f1") (param (ref $t1))) + (func (export "f2") (param (ref $t1))) +) +(register "N") +(module + (type $s0 (func (param i32) (result f32))) + (type $s1 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $s2 (func (param i32 (ref $s0)) (result (ref $s0)))) + (type $t1 (func (param (ref $s1)) (result (ref $s2)))) + (type $t2 (func (param (ref $s2)) (result (ref $s1)))) + (func (import "N" "f1") (param (ref $t1))) + (func (import "N" "f1") (param (ref $t2))) + (func (import "N" "f2") (param (ref $t1))) + (func (import "N" "f2") (param (ref $t1))) +) + + +;; Recursive types. + +(module + (rec (type $t1 (func (param i32 (ref $t1))))) + (func (export "f") (param (ref $t1))) +) +(register "Mr1") +(module + (rec (type $t2 (func (param i32 (ref $t2))))) + (func (import "Mr1" "f") (param (ref $t2))) +) + + +;; Isomorphic recursive types. + +(module + (rec + (type $t1 (func (param i32 (ref $t1)))) + (type $t2 (func (param i32 (ref $t3)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + (func (export "f1") (param (ref $t1))) + (func (export "f2") (param (ref $t2))) + (func (export "f3") (param (ref $t3))) +) +(register "Mr2") +(module + (rec + (type $t1 (func (param i32 (ref $t1)))) + (type $t2 (func (param i32 (ref $t3)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + (func (import "Mr2" "f1") (param (ref $t1))) + (func (import "Mr2" "f2") (param (ref $t2))) + (func (import "Mr2" "f3") (param (ref $t3))) +) + +(module + (rec + (type $t1 (func (param i32 (ref $t3)))) + (type $t2 (func (param i32 (ref $t1)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + (func (export "f1") (param (ref $t1))) + (func (export "f2") (param (ref $t2))) + (func (export "f3") (param (ref $t3))) +) +(register "Mr3") +(module + (rec + (type $t1 (func (param i32 (ref $t3)))) + (type $t2 (func (param i32 (ref $t1)))) + (type $t3 (func (param i32 (ref $t2)))) + ) + (func (import "Mr3" "f1") (param (ref $t1))) + (func (import "Mr3" "f2") (param (ref $t2))) + (func (import "Mr3" "f3") (param (ref $t3))) +) + +(module + (rec + (type $t1 (func (param i32 (ref $u1)))) + (type $u1 (func (param f32 (ref $t1)))) + ) + + (rec + (type $t2 (func (param i32 (ref $u3)))) + (type $u2 (func (param f32 (ref $t3)))) + (type $t3 (func (param i32 (ref $u2)))) + (type $u3 (func (param f32 (ref $t2)))) + ) + + (func (export "f1") (param (ref $t1))) + (func (export "f2") (param (ref $t2))) + (func (export "f3") (param (ref $t3))) +) +(register "Mr4") +(module + (rec + (type $t1 (func (param i32 (ref $u1)))) + (type $u1 (func (param f32 (ref $t1)))) + ) + + (rec + (type $t2 (func (param i32 (ref $u3)))) + (type $u2 (func (param f32 (ref $t3)))) + (type $t3 (func (param i32 (ref $u2)))) + (type $u3 (func (param f32 (ref $t2)))) + ) + + (func (import "Mr4" "f1") (param (ref $t1))) + (func (import "Mr4" "f2") (param (ref $t2))) + (func (import "Mr4" "f3") (param (ref $t3))) +) diff --git a/test/core/type-rec.wast b/test/core/type-rec.wast new file mode 100644 index 0000000000..5c4ba589da --- /dev/null +++ b/test/core/type-rec.wast @@ -0,0 +1,158 @@ +;; Static matching of recursive types + +(module + (rec (type $f1 (func)) (type (struct (field (ref $f1))))) + (rec (type $f2 (func)) (type (struct (field (ref $f2))))) + (func $f (type $f2)) + (global (ref $f1) (ref.func $f)) +) + +(module + (rec (type $f1 (func)) (type (struct (field (ref $f1))))) + (rec (type $f2 (func)) (type (struct (field (ref $f2))))) + (rec + (type $g1 (func)) + (type (struct (field (ref $f1) (ref $f1) (ref $f2) (ref $f2) (ref $g1)))) + ) + (rec + (type $g2 (func)) + (type (struct (field (ref $f1) (ref $f2) (ref $f1) (ref $f2) (ref $g2)))) + ) + (func $g (type $g2)) + (global (ref $g1) (ref.func $g)) +) + +(assert_invalid + (module + (rec (type $f1 (func)) (type (struct (field (ref $f1))))) + (rec (type $f2 (func)) (type (struct (field (ref $f1))))) + (func $f (type $f2)) + (global (ref $f1) (ref.func $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (rec (type $f0 (func)) (type (struct (field (ref $f0))))) + (rec (type $f1 (func)) (type (struct (field (ref $f0))))) + (rec (type $f2 (func)) (type (struct (field (ref $f1))))) + (func $f (type $f2)) + (global (ref $f1) (ref.func $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (rec (type $f1 (func)) (type (struct))) + (rec (type (struct)) (type $f2 (func))) + (global (ref $f1) (ref.func $f)) + (func $f (type $f2)) + ) + "type mismatch" +) + +(assert_invalid + (module + (rec (type $f1 (func)) (type (struct))) + (rec (type $f2 (func)) (type (struct)) (type (func))) + (global (ref $f1) (ref.func $f)) + (func $f (type $f2)) + ) + "type mismatch" +) + + +;; Link-time matching of recursive function types + +(module $M + (rec (type $f1 (func)) (type (struct))) + (func (export "f") (type $f1)) +) +(register "M" $M) + +(module + (rec (type $f2 (func)) (type (struct))) + (func (import "M" "f") (type $f2)) +) + +(assert_unlinkable + (module + (rec (type (struct)) (type $f2 (func))) + (func (import "M" "f") (type $f2)) + ) + "incompatible import type" +) + +(assert_unlinkable + (module + (rec (type $f2 (func))) + (func (import "M" "f") (type $f2)) + ) + "incompatible import type" +) + + +;; Dynamic matching of recursive function types + +(module + (rec (type $f1 (func)) (type (struct))) + (rec (type $f2 (func)) (type (struct))) + (table funcref (elem $f1)) + (func $f1 (type $f1)) + (func (export "run") (call_indirect (type $f2) (i32.const 0))) +) +(assert_return (invoke "run")) + +(module + (rec (type $f1 (func)) (type (struct))) + (rec (type (struct)) (type $f2 (func))) + (table funcref (elem $f1)) + (func $f1 (type $f1)) + (func (export "run") (call_indirect (type $f2) (i32.const 0))) +) +(assert_trap (invoke "run") "indirect call type mismatch") + +(module + (rec (type $f1 (func)) (type (struct))) + (rec (type $f2 (func))) + (table funcref (elem $f1)) + (func $f1 (type $f1)) + (func (export "run") (call_indirect (type $f2) (i32.const 0))) +) +(assert_trap (invoke "run") "indirect call type mismatch") + + +;; Implicit function types never pick up non-singleton recursive types + +(module + (rec (type $s (struct))) + (rec (type $t (func (param (ref $s))))) + (func $f (param (ref $s))) ;; okay, type is equivalent to $t + (global (ref $t) (ref.func $f)) +) + +(assert_invalid + (module + (rec + (type $s (struct)) + (type $t (func (param (ref $s)))) + ) + (func $f (param (ref $s))) ;; type is not equivalent to $t + (global (ref $t) (ref.func $f)) + ) + "type mismatch" +) + +(assert_invalid + (module + (rec + (type (struct)) + (type $t (func)) + ) + (func $f) ;; type is not equivalent to $t + (global (ref $t) (ref.func $f)) + ) + "type mismatch" +) diff --git a/test/core/unreached-invalid.wast b/test/core/unreached-invalid.wast index 5889ba1149..1dfcf91f8c 100644 --- a/test/core/unreached-invalid.wast +++ b/test/core/unreached-invalid.wast @@ -686,14 +686,25 @@ (assert_invalid (module (func $type-br_if-after-unreachable (result i64) - unreachable - br_if 0 - i64.extend_i32_u + (unreachable) + (br_if 0) + (i64.extend_i32_u) ) ) "type mismatch" ) +(assert_invalid + (module + (func $type-after-ref.as_non_null + (unreachable) + (ref.as_non_null) + (f32.abs) + ) + ) + "type mismatch" +) + ;; The first two operands should have the same type as each other (assert_invalid (module (func (unreachable) (select (i32.const 1) (i64.const 1) (i32.const 1)) (drop))) @@ -748,3 +759,24 @@ "type mismatch" ) + +(assert_invalid + (module + (type $t (func (param i32) (result i64))) + (func (result i32) + (unreachable) + (call_ref $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (func (param i32) (result i32 i32))) + (func (result i32) + (unreachable) + (call_ref $t) + ) + ) + "type mismatch" +) diff --git a/test/core/unreached-valid.wast b/test/core/unreached-valid.wast index b7ebabfdb1..f3feb0f35c 100644 --- a/test/core/unreached-valid.wast +++ b/test/core/unreached-valid.wast @@ -17,26 +17,32 @@ (unreachable) ) - (func (export "select_unreached_result_1") (result i32) + (func (export "select-unreached-result1") (result i32) (unreachable) (i32.add (select)) ) - (func (export "select_unreached_result_2") (result i64) + (func (export "select-unreached-result2") (result i64) (unreachable) (i64.add (select (i64.const 0) (i32.const 0))) ) - (func (export "unreachable-num") + (func (export "select-unreached-num") (unreachable) (select) (i32.eqz) (drop) ) - (func (export "unreachable-ref") + (func (export "select-unreached-ref") (unreachable) (select) (ref.is_null) (drop) ) + + (type $t (func (param i32) (result i32))) + (func (export "call_ref-unreached") (result i32) + (unreachable) + (call_ref $t) + ) ) (assert_trap (invoke "select-trap-left" (i32.const 1)) "unreachable") @@ -44,6 +50,14 @@ (assert_trap (invoke "select-trap-right" (i32.const 1)) "unreachable") (assert_trap (invoke "select-trap-right" (i32.const 0)) "unreachable") +(assert_trap (invoke "select-unreached-result1") "unreachable") +(assert_trap (invoke "select-unreached-result2") "unreachable") +(assert_trap (invoke "select-unreached-num") "unreachable") +(assert_trap (invoke "select-unreached-ref") "unreachable") + +(assert_trap (invoke "call_ref-unreached") "unreachable") + + ;; Validation after unreachable (module @@ -61,3 +75,34 @@ ) (assert_trap (invoke "meet-bottom") "unreachable") + + +;; Bottom heap type + +(module + (func (result (ref func)) + (unreachable) + (ref.as_non_null) + ) + (func (result (ref extern)) + (unreachable) + (ref.as_non_null) + ) + + (func (result (ref func)) + (block (result funcref) + (unreachable) + (br_on_null 0) + (return) + ) + (unreachable) + ) + (func (result (ref extern)) + (block (result externref) + (unreachable) + (br_on_null 0) + (return) + ) + (unreachable) + ) +) diff --git a/test/js-api/gc/casts.tentative.any.js b/test/js-api/gc/casts.tentative.any.js new file mode 100644 index 0000000000..cce06224fd --- /dev/null +++ b/test/js-api/gc/casts.tentative.any.js @@ -0,0 +1,332 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structIndex2 = builder.addStruct([makeField(kWasmF32, true)]); + const arrayIndex2 = builder.addArray(kWasmF32, true); + const funcIndex = builder.addType({ params: [], results: [] }); + const funcIndex2 = builder.addType({ params: [], results: [kWasmI32] }); + + const argFunctions = [ + { name: "any", code: kWasmAnyRef }, + { name: "eq", code: kWasmEqRef }, + { name: "struct", code: kWasmStructRef }, + { name: "array", code: kWasmArrayRef }, + { name: "i31", code: kWasmI31Ref }, + { name: "func", code: kWasmFuncRef }, + { name: "extern", code: kWasmExternRef }, + { name: "none", code: kWasmNullRef }, + { name: "nofunc", code: kWasmNullFuncRef }, + { name: "noextern", code: kWasmNullExternRef }, + { name: "concreteStruct", code: structIndex }, + { name: "concreteArray", code: arrayIndex }, + { name: "concreteFunc", code: funcIndex }, + ]; + + for (const desc of argFunctions) { + builder + .addFunction(desc.name + "Arg", makeSig_v_x(wasmRefType(desc.code))) + .addBody([]) + .exportFunc(); + + builder + .addFunction(desc.name + "NullableArg", makeSig_v_x(wasmRefNullType(desc.code))) + .addBody([]) + .exportFunc(); + } + + builder + .addFunction("makeStruct", makeSig_r_v(wasmRefType(structIndex))) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(wasmRefType(arrayIndex))) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + builder + .addFunction("makeStruct2", makeSig_r_v(wasmRefType(structIndex2))) + .addBody([...wasmF32Const(42), + ...GCInstr(kExprStructNew), structIndex2]) + .exportFunc(); + + builder + .addFunction("makeArray2", makeSig_r_v(wasmRefType(arrayIndex2))) + .addBody([...wasmF32Const(42), ...wasmI32Const(5), + ...GCInstr(kExprArrayNew), arrayIndex2]) + .exportFunc(); + + builder + .addFunction("testFunc", funcIndex) + .addBody([]) + .exportFunc(); + + builder + .addFunction("testFunc2", funcIndex2) + .addBody([...wasmI32Const(42)]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + exports.anyArg(exports.makeStruct()); + exports.anyArg(exports.makeArray()); + exports.anyArg(42); + exports.anyArg(42n); + exports.anyArg("foo"); + exports.anyArg({}); + exports.anyArg(() => {}); + exports.anyArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.anyArg(null)); + + exports.anyNullableArg(null); + exports.anyNullableArg(exports.makeStruct()); + exports.anyNullableArg(exports.makeArray()); + exports.anyNullableArg(42); + exports.anyNullableArg(42n); + exports.anyNullableArg("foo"); + exports.anyNullableArg({}); + exports.anyNullableArg(() => {}); + exports.anyNullableArg(exports.testFunc); +}, "anyref casts"); + +test(() => { + exports.eqArg(exports.makeStruct()); + exports.eqArg(exports.makeArray()); + exports.eqArg(42); + assert_throws_js(TypeError, () => exports.eqArg(42n)); + assert_throws_js(TypeError, () => exports.eqArg("foo")); + assert_throws_js(TypeError, () => exports.eqArg({})); + assert_throws_js(TypeError, () => exports.eqArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqArg(() => {})); + assert_throws_js(TypeError, () => exports.eqArg(null)); + + exports.eqNullableArg(null); + exports.eqNullableArg(exports.makeStruct()); + exports.eqNullableArg(exports.makeArray()); + exports.eqNullableArg(42); + assert_throws_js(TypeError, () => exports.eqNullableArg(42n)); + assert_throws_js(TypeError, () => exports.eqNullableArg("foo")); + assert_throws_js(TypeError, () => exports.eqNullableArg({})); + assert_throws_js(TypeError, () => exports.eqNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.eqNullableArg(() => {})); +}, "eqref casts"); + +test(() => { + exports.structArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structArg(42)); + assert_throws_js(TypeError, () => exports.structArg(42n)); + assert_throws_js(TypeError, () => exports.structArg("foo")); + assert_throws_js(TypeError, () => exports.structArg({})); + assert_throws_js(TypeError, () => exports.structArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structArg(() => {})); + assert_throws_js(TypeError, () => exports.structArg(null)); + + exports.structNullableArg(null); + exports.structNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.structNullableArg(42)); + assert_throws_js(TypeError, () => exports.structNullableArg(42n)); + assert_throws_js(TypeError, () => exports.structNullableArg("foo")); + assert_throws_js(TypeError, () => exports.structNullableArg({})); + assert_throws_js(TypeError, () => exports.structNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.structNullableArg(() => {})); +}, "structref casts"); + +test(() => { + exports.arrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayArg(42)); + assert_throws_js(TypeError, () => exports.arrayArg(42n)); + assert_throws_js(TypeError, () => exports.arrayArg("foo")); + assert_throws_js(TypeError, () => exports.arrayArg({})); + assert_throws_js(TypeError, () => exports.arrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayArg(() => {})); + assert_throws_js(TypeError, () => exports.arrayArg(null)); + + exports.arrayNullableArg(null); + exports.arrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.arrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.arrayNullableArg({})); + assert_throws_js(TypeError, () => exports.arrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.arrayNullableArg(() => {})); +}, "arrayref casts"); + +test(() => { + exports.i31Arg(42); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31Arg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31Arg(42n)); + assert_throws_js(TypeError, () => exports.i31Arg("foo")); + assert_throws_js(TypeError, () => exports.i31Arg({})); + assert_throws_js(TypeError, () => exports.i31Arg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31Arg(() => {})); + assert_throws_js(TypeError, () => exports.i31Arg(null)); + + exports.i31NullableArg(null); + exports.i31NullableArg(42); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.i31NullableArg(42n)); + assert_throws_js(TypeError, () => exports.i31NullableArg("foo")); + assert_throws_js(TypeError, () => exports.i31NullableArg({})); + assert_throws_js(TypeError, () => exports.i31NullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.i31NullableArg(() => {})); +}, "i31ref casts"); + +test(() => { + exports.funcArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcArg(42)); + assert_throws_js(TypeError, () => exports.funcArg(42n)); + assert_throws_js(TypeError, () => exports.funcArg("foo")); + assert_throws_js(TypeError, () => exports.funcArg({})); + assert_throws_js(TypeError, () => exports.funcArg(() => {})); + assert_throws_js(TypeError, () => exports.funcArg(null)); + + exports.funcNullableArg(null); + exports.funcNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.funcNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.funcNullableArg(42)); + assert_throws_js(TypeError, () => exports.funcNullableArg(42n)); + assert_throws_js(TypeError, () => exports.funcNullableArg("foo")); + assert_throws_js(TypeError, () => exports.funcNullableArg({})); + assert_throws_js(TypeError, () => exports.funcNullableArg(() => {})); +}, "funcref casts"); + +test(() => { + exports.externArg(exports.makeArray()); + exports.externArg(exports.makeStruct()); + exports.externArg(42); + exports.externArg(42n); + exports.externArg("foo"); + exports.externArg({}); + exports.externArg(exports.testFunc); + exports.externArg(() => {}); + assert_throws_js(TypeError, () => exports.externArg(null)); + + exports.externNullableArg(null); + exports.externNullableArg(exports.makeArray()); + exports.externNullableArg(exports.makeStruct()); + exports.externNullableArg(42); + exports.externNullableArg(42n); + exports.externNullableArg("foo"); + exports.externNullableArg({}); + exports.externNullableArg(exports.testFunc); + exports.externNullableArg(() => {}); +}, "externref casts"); + +test(() => { + for (const nullfunc of [exports.noneArg, exports.nofuncArg, exports.noexternArg]) { + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + assert_throws_js(TypeError, () => nullfunc(null)); + } + + for (const nullfunc of [exports.noneNullableArg, exports.nofuncNullableArg, exports.noexternNullableArg]) { + nullfunc(null); + assert_throws_js(TypeError, () => nullfunc(exports.makeStruct())); + assert_throws_js(TypeError, () => nullfunc(exports.makeArray())); + assert_throws_js(TypeError, () => nullfunc(42)); + assert_throws_js(TypeError, () => nullfunc(42n)); + assert_throws_js(TypeError, () => nullfunc("foo")); + assert_throws_js(TypeError, () => nullfunc({})); + assert_throws_js(TypeError, () => nullfunc(exports.testFunc)); + assert_throws_js(TypeError, () => nullfunc(() => {})); + } +}, "null casts"); + +test(() => { + exports.concreteStructArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructArg({})); + assert_throws_js(TypeError, () => exports.concreteStructArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteStructArg(null)); + + exports.concreteStructNullableArg(null); + exports.concreteStructNullableArg(exports.makeStruct()); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeStruct2())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteStructNullableArg(() => {})); +}, "concrete struct casts"); + +test(() => { + exports.concreteArrayArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteArrayArg(null)); + + exports.concreteArrayNullableArg(null); + exports.concreteArrayNullableArg(exports.makeArray()); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeArray2())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(exports.testFunc)); + assert_throws_js(TypeError, () => exports.concreteArrayNullableArg(() => {})); +}, "concrete array casts"); + +test(() => { + exports.concreteFuncArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(() => {})); + assert_throws_js(TypeError, () => exports.concreteFuncArg(null)); + + exports.concreteFuncNullableArg(null); + exports.concreteFuncNullableArg(exports.testFunc); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.testFunc2)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeArray())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(exports.makeStruct())); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(42n)); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg("foo")); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg({})); + assert_throws_js(TypeError, () => exports.concreteFuncNullableArg(() => {})); +}, "concrete func casts"); diff --git a/test/js-api/gc/default-value.tentative.any.js b/test/js-api/gc/default-value.tentative.any.js new file mode 100644 index 0000000000..d828b4d87f --- /dev/null +++ b/test/js-api/gc/default-value.tentative.any.js @@ -0,0 +1,43 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + builder.addTable(wasmRefType(kWasmAnyRef), 10, 20, [...wasmI32Const(42), ...GCInstr(kExprRefI31)]) + .exportAs("tableAnyNonNullable"); + builder.addTable(wasmRefNullType(kWasmAnyRef), 10, 20) + .exportAs("tableAnyNullable"); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + exports.tableAnyNullable.grow(5); + for (let i = 0; i < 5; i++) + assert_equals(exports.tableAnyNullable.get(10 + i), null); +}, "grow (nullable anyref)"); + +test(() => { + assert_throws_js(TypeError, () => { exports.tableAnyNonNullable.grow(5); }); + exports.tableAnyNonNullable.grow(5, "foo"); + for (let i = 0; i < 5; i++) + assert_equals(exports.tableAnyNonNullable.get(10 + i), "foo"); +}, "grow (non-nullable anyref)"); + +test(() => { + for (let i = 0; i < exports.tableAnyNullable.length; i++) { + exports.tableAnyNullable.set(i); + assert_equals(exports.tableAnyNullable.get(i), null); + } +}, "set (nullable anyref)"); + +test(() => { + for (let i = 0; i < exports.tableAnyNonNullable.length; i++) { + assert_throws_js(TypeError, () => { exports.tableAnyNonNullable.set(i); }); + } +}, "set (non-nullable anyref)"); diff --git a/test/js-api/gc/exported-object.tentative.any.js b/test/js-api/gc/exported-object.tentative.any.js new file mode 100644 index 0000000000..b572f14006 --- /dev/null +++ b/test/js-api/gc/exported-object.tentative.any.js @@ -0,0 +1,190 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let functions = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + + const structIndex = builder.addStruct([makeField(kWasmI32, true)]); + const arrayIndex = builder.addArray(kWasmI32, true); + const structRef = wasmRefType(structIndex); + const arrayRef = wasmRefType(arrayIndex); + + builder + .addFunction("makeStruct", makeSig_r_v(structRef)) + .addBody([...wasmI32Const(42), + ...GCInstr(kExprStructNew), structIndex]) + .exportFunc(); + + builder + .addFunction("makeArray", makeSig_r_v(arrayRef)) + .addBody([...wasmI32Const(5), ...wasmI32Const(42), + ...GCInstr(kExprArrayNew), arrayIndex]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + functions = instance.exports; +}); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(struct.foo, undefined); + assert_equals(struct[0], undefined); + assert_equals(array.foo, undefined); + assert_equals(array[0], undefined); +}, "property access"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => { struct.foo = 5; }); + assert_throws_js(TypeError, () => { array.foo = 5; }); + assert_throws_js(TypeError, () => { struct[0] = 5; }); + assert_throws_js(TypeError, () => { array[0] = 5; }); +}, "property assignment (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getOwnPropertyNames(struct).length, 0); + assert_equals(Object.getOwnPropertyNames(array).length, 0); +}, "ownPropertyNames"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.defineProperty(struct, "foo", { value: 1 })); + assert_throws_js(TypeError, () => Object.defineProperty(array, "foo", { value: 1 })); +}, "defineProperty"); + +test(() => { + "use strict"; + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => delete struct.foo); + assert_throws_js(TypeError, () => delete struct[0]); + assert_throws_js(TypeError, () => delete array.foo); + assert_throws_js(TypeError, () => delete array[0]); +}, "delete (non-strict mode)"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(Object.getPrototypeOf(struct), null); + assert_equals(Object.getPrototypeOf(array), null); +}, "getPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.setPrototypeOf(struct, {})); + assert_throws_js(TypeError, () => Object.setPrototypeOf(array, {})); +}, "setPrototypeOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_false(Object.isExtensible(struct)); + assert_false(Object.isExtensible(array)); +}, "isExtensible"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.preventExtensions(struct)); + assert_throws_js(TypeError, () => Object.preventExtensions(array)); +}, "preventExtensions"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => Object.seal(struct)); + assert_throws_js(TypeError, () => Object.seal(array)); +}, "sealing"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_equals(typeof struct, "object"); + assert_equals(typeof array, "object"); +}, "typeof"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.toString()); + assert_equals(Object.prototype.toString.call(struct), "[object Object]"); + assert_throws_js(TypeError, () => array.toString()); + assert_equals(Object.prototype.toString.call(array), "[object Object]"); +}, "toString"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + assert_throws_js(TypeError, () => struct.valueOf()); + assert_equals(Object.prototype.valueOf.call(struct), struct); + assert_throws_js(TypeError, () => array.valueOf()); + assert_equals(Object.prototype.valueOf.call(array), array); +}, "valueOf"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new Map(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new Set(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as set element"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const map = new WeakMap(); + map.set(struct, "struct"); + map.set(array, "array"); + assert_equals(map.get(struct), "struct"); + assert_equals(map.get(array), "array"); +}, "GC objects as weak map keys"); + +test(() => { + const struct = functions.makeStruct(); + const array = functions.makeArray(); + const set = new WeakSet(); + set.add(struct); + set.add(array); + assert_true(set.has(struct)); + assert_true(set.has(array)); +}, "GC objects as weak set element"); diff --git a/test/js-api/gc/i31.tentative.any.js b/test/js-api/gc/i31.tentative.any.js new file mode 100644 index 0000000000..d8cfe424e8 --- /dev/null +++ b/test/js-api/gc/i31.tentative.any.js @@ -0,0 +1,98 @@ +// META: global=window,dedicatedworker,jsshell +// META: script=/wasm/jsapi/wasm-module-builder.js + +let exports = {}; +setup(() => { + const builder = new WasmModuleBuilder(); + const i31Ref = wasmRefType(kWasmI31Ref); + const i31NullableRef = wasmRefNullType(kWasmI31Ref); + const anyRef = wasmRefType(kWasmAnyRef); + + builder + .addFunction("makeI31", makeSig_r_x(i31Ref, kWasmI32)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprRefI31)]) + .exportFunc(); + + builder + .addFunction("castI31", makeSig_r_x(kWasmI32, anyRef)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprRefCast), kI31RefCode, + ...GCInstr(kExprI31GetU)]) + .exportFunc(); + + builder + .addFunction("getI31", makeSig_r_x(kWasmI32, i31Ref)) + .addBody([kExprLocalGet, 0, + ...GCInstr(kExprI31GetS)]) + .exportFunc(); + + builder + .addFunction("argI31", makeSig_v_x(i31NullableRef)) + .addBody([]) + .exportFunc(); + + builder + .addGlobal(i31NullableRef, true, [...wasmI32Const(0), ...GCInstr(kExprRefI31)]) + builder + .addExportOfKind("i31Global", kExternalGlobal, 0); + + builder + .addTable(i31NullableRef, 10) + builder + .addExportOfKind("i31Table", kExternalTable, 0); + + const buffer = builder.toBuffer(); + const module = new WebAssembly.Module(buffer); + const instance = new WebAssembly.Instance(module, {}); + exports = instance.exports; +}); + +test(() => { + assert_equals(exports.makeI31(42), 42); + assert_equals(exports.makeI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.makeI31(2 ** 30), -(2 ** 30)); + assert_equals(exports.makeI31(-(2 ** 30)), -(2 ** 30)); + assert_equals(exports.makeI31(2 ** 31 - 1), -1); + assert_equals(exports.makeI31(2 ** 31), 0); +}, "i31ref conversion to Number"); + +test(() => { + assert_equals(exports.getI31(exports.makeI31(42)), 42); + assert_equals(exports.getI31(42), 42); + assert_equals(exports.getI31(2.0 ** 30 - 1), 2 ** 30 - 1); + assert_equals(exports.getI31(-(2 ** 30)), -(2 ** 30)); +}, "Number conversion to i31ref"); + +test(() => { + exports.argI31(null); + assert_throws_js(TypeError, () => exports.argI31(2 ** 30)); + assert_throws_js(TypeError, () => exports.argI31(-(2 ** 30) - 1)); + assert_throws_js(TypeError, () => exports.argI31(2n)); + assert_throws_js(TypeError, () => exports.argI31(() => 3)); + assert_throws_js(TypeError, () => exports.argI31(exports.getI31)); +}, "Check i31ref argument type"); + +test(() => { + assert_equals(exports.castI31(42), 42); + assert_equals(exports.castI31(2 ** 30 - 1), 2 ** 30 - 1); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 30); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(-(2 ** 30) - 1); }); + assert_throws_js(WebAssembly.RuntimeError, () => { exports.castI31(2 ** 32); }); +}, "Numbers in i31 range are i31ref, not hostref"); + +test(() => { + assert_equals(exports.i31Global.value, 0); + exports.i31Global.value = 42; + assert_throws_js(TypeError, () => exports.i31Global.value = 2 ** 30); + assert_throws_js(TypeError, () => exports.i31Global.value = -(2 ** 30) - 1); + assert_equals(exports.i31Global.value, 42); +}, "i31ref global"); + +test(() => { + assert_equals(exports.i31Table.get(0), null); + exports.i31Table.set(0, 42); + assert_throws_js(TypeError, () => exports.i31Table.set(0, 2 ** 30)); + assert_throws_js(TypeError, () => exports.i31Table.set(0, -(2 ** 30) - 1)); + assert_equals(exports.i31Table.get(0), 42); +}, "i31ref table"); diff --git a/test/js-api/instanceTestFactory.js b/test/js-api/instanceTestFactory.js index ac468947ec..7936810a52 100644 --- a/test/js-api/instanceTestFactory.js +++ b/test/js-api/instanceTestFactory.js @@ -237,7 +237,7 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer(); @@ -273,10 +273,10 @@ const instanceTestFactory = [ builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(4, 8, true); diff --git a/test/js-api/module/exports.any.js b/test/js-api/module/exports.any.js index 40a3935a4a..0d62725ae4 100644 --- a/test/js-api/module/exports.any.js +++ b/test/js-api/module/exports.any.js @@ -109,10 +109,10 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("global") - .init = 7; + .init = wasmI32Const(7); builder.addGlobal(kWasmF64, true) .exportAs("global2") - .init = 1.2; + .init = wasmF64Const(1.2); builder.addMemory(0, 256, true); @@ -167,7 +167,7 @@ test(() => { builder.addGlobal(kWasmI32, true) .exportAs("") - .init = 7; + .init = wasmI32Const(7); const buffer = builder.toBuffer() const module = new WebAssembly.Module(buffer); diff --git a/test/js-api/wasm-module-builder.js b/test/js-api/wasm-module-builder.js index d0f9e78bcd..104c730c7c 100644 --- a/test/js-api/wasm-module-builder.js +++ b/test/js-api/wasm-module-builder.js @@ -74,6 +74,13 @@ let kLocalNamesCode = 2; let kWasmFunctionTypeForm = 0x60; let kWasmAnyFunctionTypeForm = 0x70; +let kWasmStructTypeForm = 0x5f; +let kWasmArrayTypeForm = 0x5e; +let kWasmSubtypeForm = 0x50; +let kWasmSubtypeFinalForm = 0x4f; +let kWasmRecursiveTypeGroupForm = 0x4e; + +let kNoSuperType = 0xFFFFFFFF; let kHasMaximumFlag = 1; let kSharedHasMaximumFlag = 3; @@ -97,8 +104,43 @@ let kWasmI64 = 0x7e; let kWasmF32 = 0x7d; let kWasmF64 = 0x7c; let kWasmS128 = 0x7b; -let kWasmAnyRef = 0x6f; -let kWasmAnyFunc = 0x70; + +// These are defined as negative integers to distinguish them from positive type +// indices. +let kWasmNullFuncRef = -0x0d; +let kWasmNullExternRef = -0x0e; +let kWasmNullRef = -0x0f; +let kWasmFuncRef = -0x10; +let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec +let kWasmExternRef = -0x11; +let kWasmAnyRef = -0x12; +let kWasmEqRef = -0x13; +let kWasmI31Ref = -0x14; +let kWasmStructRef = -0x15; +let kWasmArrayRef = -0x16; + +// Use the positive-byte versions inside function bodies. +let kLeb128Mask = 0x7f; +let kFuncRefCode = kWasmFuncRef & kLeb128Mask; +let kAnyFuncCode = kFuncRefCode; // Alias named as in the JS API spec +let kExternRefCode = kWasmExternRef & kLeb128Mask; +let kAnyRefCode = kWasmAnyRef & kLeb128Mask; +let kEqRefCode = kWasmEqRef & kLeb128Mask; +let kI31RefCode = kWasmI31Ref & kLeb128Mask; +let kNullExternRefCode = kWasmNullExternRef & kLeb128Mask; +let kNullFuncRefCode = kWasmNullFuncRef & kLeb128Mask; +let kStructRefCode = kWasmStructRef & kLeb128Mask; +let kArrayRefCode = kWasmArrayRef & kLeb128Mask; +let kNullRefCode = kWasmNullRef & kLeb128Mask; + +let kWasmRefNull = 0x63; +let kWasmRef = 0x64; +function wasmRefNullType(heap_type) { + return {opcode: kWasmRefNull, heap_type: heap_type}; +} +function wasmRefType(heap_type) { + return {opcode: kWasmRef, heap_type: heap_type}; +} let kExternalFunction = 0; let kExternalTable = 1; @@ -146,14 +188,14 @@ let kSig_v_f = makeSig([kWasmF32], []); let kSig_f_f = makeSig([kWasmF32], [kWasmF32]); let kSig_f_d = makeSig([kWasmF64], [kWasmF32]); let kSig_d_d = makeSig([kWasmF64], [kWasmF64]); -let kSig_r_r = makeSig([kWasmAnyRef], [kWasmAnyRef]); +let kSig_r_r = makeSig([kWasmExternRef], [kWasmExternRef]); let kSig_a_a = makeSig([kWasmAnyFunc], [kWasmAnyFunc]); -let kSig_i_r = makeSig([kWasmAnyRef], [kWasmI32]); -let kSig_v_r = makeSig([kWasmAnyRef], []); +let kSig_i_r = makeSig([kWasmExternRef], [kWasmI32]); +let kSig_v_r = makeSig([kWasmExternRef], []); let kSig_v_a = makeSig([kWasmAnyFunc], []); -let kSig_v_rr = makeSig([kWasmAnyRef, kWasmAnyRef], []); +let kSig_v_rr = makeSig([kWasmExternRef, kWasmExternRef], []); let kSig_v_aa = makeSig([kWasmAnyFunc, kWasmAnyFunc], []); -let kSig_r_v = makeSig([], [kWasmAnyRef]); +let kSig_r_v = makeSig([], [kWasmExternRef]); let kSig_a_v = makeSig([], [kWasmAnyFunc]); let kSig_a_i = makeSig([kWasmI32], [kWasmAnyFunc]); @@ -374,10 +416,50 @@ let kExprRefIsNull = 0xd1; let kExprRefFunc = 0xd2; // Prefix opcodes +let kGCPrefix = 0xfb; let kNumericPrefix = 0xfc; let kSimdPrefix = 0xfd; let kAtomicPrefix = 0xfe; +// Use these for multi-byte instructions (opcode > 0x7F needing two LEB bytes): +function GCInstr(opcode) { + if (opcode <= 0x7F) return [kGCPrefix, opcode]; + return [kGCPrefix, 0x80 | (opcode & 0x7F), opcode >> 7]; +} + +// GC opcodes +let kExprStructNew = 0x00; +let kExprStructNewDefault = 0x01; +let kExprStructGet = 0x02; +let kExprStructGetS = 0x03; +let kExprStructGetU = 0x04; +let kExprStructSet = 0x05; +let kExprArrayNew = 0x06; +let kExprArrayNewDefault = 0x07; +let kExprArrayNewFixed = 0x08; +let kExprArrayNewData = 0x09; +let kExprArrayNewElem = 0x0a; +let kExprArrayGet = 0x0b; +let kExprArrayGetS = 0x0c; +let kExprArrayGetU = 0x0d; +let kExprArraySet = 0x0e; +let kExprArrayLen = 0x0f; +let kExprArrayFill = 0x10; +let kExprArrayCopy = 0x11; +let kExprArrayInitData = 0x12; +let kExprArrayInitElem = 0x13; +let kExprRefTest = 0x14; +let kExprRefTestNull = 0x15; +let kExprRefCast = 0x16; +let kExprRefCastNull = 0x17; +let kExprBrOnCast = 0x18; +let kExprBrOnCastFail = 0x19; +let kExprExternInternalize = 0x1a; +let kExprExternExternalize = 0x1b; +let kExprRefI31 = 0x1c; +let kExprI31GetS = 0x1d; +let kExprI31GetU = 0x1e; + // Numeric opcodes. let kExprMemoryInit = 0x08; let kExprDataDrop = 0x09; @@ -554,6 +636,25 @@ class Binary { } } + emit_heap_type(heap_type) { + this.emit_bytes(wasmSignedLeb(heap_type, kMaxVarInt32Size)); + } + + emit_type(type) { + if ((typeof type) == 'number') { + this.emit_u8(type >= 0 ? type : type & kLeb128Mask); + } else { + this.emit_u8(type.opcode); + if ('depth' in type) this.emit_u8(type.depth); + this.emit_heap_type(type.heap_type); + } + } + + emit_init_expr(expr) { + this.emit_bytes(expr); + this.emit_u8(kExprEnd); + } + emit_header() { this.emit_bytes([ kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 @@ -644,11 +745,11 @@ class WasmFunctionBuilder { } class WasmGlobalBuilder { - constructor(module, type, mutable) { + constructor(module, type, mutable, init) { this.module = module; this.type = type; this.mutable = mutable; - this.init = 0; + this.init = init; } exportAs(name) { @@ -658,13 +759,24 @@ class WasmGlobalBuilder { } } +function checkExpr(expr) { + for (let b of expr) { + if (typeof b !== 'number' || (b & (~0xFF)) !== 0) { + throw new Error( + 'invalid body (entries must be 8 bit numbers): ' + expr); + } + } +} + class WasmTableBuilder { - constructor(module, type, initial_size, max_size) { + constructor(module, type, initial_size, max_size, init_expr) { this.module = module; this.type = type; this.initial_size = initial_size; this.has_max = max_size != undefined; this.max_size = max_size; + this.init_expr = init_expr; + this.has_init = init_expr !== undefined; } exportAs(name) { @@ -674,6 +786,35 @@ class WasmTableBuilder { } } +function makeField(type, mutability) { + if ((typeof mutability) != 'boolean') { + throw new Error('field mutability must be boolean'); + } + return {type: type, mutability: mutability}; +} + +class WasmStruct { + constructor(fields, is_final, supertype_idx) { + if (!Array.isArray(fields)) { + throw new Error('struct fields must be an array'); + } + this.fields = fields; + this.type_form = kWasmStructTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + +class WasmArray { + constructor(type, mutability, is_final, supertype_idx) { + this.type = type; + this.mutability = mutability; + this.type_form = kWasmArrayTypeForm; + this.is_final = is_final; + this.supertype = supertype_idx; + } +} + class WasmModuleBuilder { constructor() { this.types = []; @@ -686,6 +827,7 @@ class WasmModuleBuilder { this.element_segments = []; this.data_segments = []; this.explicit = []; + this.rec_groups = []; this.num_imported_funcs = 0; this.num_imported_globals = 0; this.num_imported_tables = 0; @@ -728,25 +870,65 @@ class WasmModuleBuilder { this.explicit.push(this.createCustomSection(name, bytes)); } - addType(type) { - this.types.push(type); - var pl = type.params.length; // should have params - var rl = type.results.length; // should have results + // We use {is_final = true} so that the MVP syntax is generated for + // signatures. + addType(type, supertype_idx = kNoSuperType, is_final = true) { + var pl = type.params.length; // should have params + var rl = type.results.length; // should have results + var type_copy = {params: type.params, results: type.results, + is_final: is_final, supertype: supertype_idx}; + this.types.push(type_copy); return this.types.length - 1; } - addGlobal(local_type, mutable) { - let glob = new WasmGlobalBuilder(this, local_type, mutable); + addStruct(fields, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmStruct(fields, is_final, supertype_idx)); + return this.types.length - 1; + } + + addArray(type, mutability, supertype_idx = kNoSuperType, is_final = false) { + this.types.push(new WasmArray(type, mutability, is_final, supertype_idx)); + return this.types.length - 1; + } + + static defaultFor(type) { + switch (type) { + case kWasmI32: + return wasmI32Const(0); + case kWasmI64: + return wasmI64Const(0); + case kWasmF32: + return wasmF32Const(0.0); + case kWasmF64: + return wasmF64Const(0.0); + case kWasmS128: + return [kSimdPrefix, kExprS128Const, ...(new Array(16).fill(0))]; + default: + if ((typeof type) != 'number' && type.opcode != kWasmRefNull) { + throw new Error("Non-defaultable type"); + } + let heap_type = (typeof type) == 'number' ? type : type.heap_type; + return [kExprRefNull, ...wasmSignedLeb(heap_type, kMaxVarInt32Size)]; + } + } + + addGlobal(type, mutable, init) { + if (init === undefined) init = WasmModuleBuilder.defaultFor(type); + checkExpr(init); + let glob = new WasmGlobalBuilder(this, type, mutable, init); glob.index = this.globals.length + this.num_imported_globals; this.globals.push(glob); return glob; } - addTable(type, initial_size, max_size = undefined) { - if (type != kWasmAnyRef && type != kWasmAnyFunc) { - throw new Error('Tables must be of type kWasmAnyRef or kWasmAnyFunc'); + addTable(type, initial_size, max_size = undefined, init_expr = undefined) { + if (type == kWasmI32 || type == kWasmI64 || type == kWasmF32 || + type == kWasmF64 || type == kWasmS128 || type == kWasmStmt) { + throw new Error('Tables must be of a reference type'); } - let table = new WasmTableBuilder(this, type, initial_size, max_size); + if (init_expr != undefined) checkExpr(init_expr); + let table = new WasmTableBuilder( + this, type, initial_size, max_size, init_expr); table.index = this.tables.length + this.num_imported_tables; this.tables.push(table); return table; @@ -877,6 +1059,21 @@ class WasmModuleBuilder { return this; } + startRecGroup() { + this.rec_groups.push({start: this.types.length, size: 0}); + } + + endRecGroup() { + if (this.rec_groups.length == 0) { + throw new Error("Did not start a recursive group before ending one") + } + let last_element = this.rec_groups[this.rec_groups.length - 1] + if (last_element.size != 0) { + throw new Error("Did not start a recursive group before ending one") + } + last_element.size = this.types.length - last_element.start; + } + setName(name) { this.name = name; return this; @@ -891,18 +1088,55 @@ class WasmModuleBuilder { // Add type section if (wasm.types.length > 0) { - if (debug) print("emitting types @ " + binary.length); + if (debug) print('emitting types @ ' + binary.length); binary.emit_section(kTypeSectionCode, section => { - section.emit_u32v(wasm.types.length); - for (let type of wasm.types) { - section.emit_u8(kWasmFunctionTypeForm); - section.emit_u32v(type.params.length); - for (let param of type.params) { - section.emit_u8(param); + let length_with_groups = wasm.types.length; + for (let group of wasm.rec_groups) { + length_with_groups -= group.size - 1; + } + section.emit_u32v(length_with_groups); + + let rec_group_index = 0; + + for (let i = 0; i < wasm.types.length; i++) { + if (rec_group_index < wasm.rec_groups.length && + wasm.rec_groups[rec_group_index].start == i) { + section.emit_u8(kWasmRecursiveTypeGroupForm); + section.emit_u32v(wasm.rec_groups[rec_group_index].size); + rec_group_index++; + } + + let type = wasm.types[i]; + if (type.supertype != kNoSuperType) { + section.emit_u8(type.is_final ? kWasmSubtypeFinalForm + : kWasmSubtypeForm); + section.emit_u8(1); // supertype count + section.emit_u32v(type.supertype); + } else if (!type.is_final) { + section.emit_u8(kWasmSubtypeForm); + section.emit_u8(0); // no supertypes } - section.emit_u32v(type.results.length); - for (let result of type.results) { - section.emit_u8(result); + if (type instanceof WasmStruct) { + section.emit_u8(kWasmStructTypeForm); + section.emit_u32v(type.fields.length); + for (let field of type.fields) { + section.emit_type(field.type); + section.emit_u8(field.mutability ? 1 : 0); + } + } else if (type instanceof WasmArray) { + section.emit_u8(kWasmArrayTypeForm); + section.emit_type(type.type); + section.emit_u8(type.mutability ? 1 : 0); + } else { + section.emit_u8(kWasmFunctionTypeForm); + section.emit_u32v(type.params.length); + for (let param of type.params) { + section.emit_type(param); + } + section.emit_u32v(type.results.length); + for (let result of type.results) { + section.emit_type(result); + } } } }); @@ -918,9 +1152,9 @@ class WasmModuleBuilder { section.emit_string(imp.name || ''); section.emit_u8(imp.kind); if (imp.kind == kExternalFunction) { - section.emit_u32v(imp.type); + section.emit_u32v(imp.type_index); } else if (imp.kind == kExternalGlobal) { - section.emit_u32v(imp.type); + section.emit_type(imp.type); section.emit_u8(imp.mutable); } else if (imp.kind == kExternalMemory) { var has_max = (typeof imp.maximum) != "undefined"; @@ -933,7 +1167,7 @@ class WasmModuleBuilder { section.emit_u32v(imp.initial); // initial if (has_max) section.emit_u32v(imp.maximum); // maximum } else if (imp.kind == kExternalTable) { - section.emit_u8(imp.type); + section.emit_type(imp.type); var has_max = (typeof imp.maximum) != "undefined"; section.emit_u8(has_max ? 1 : 0); // flags section.emit_u32v(imp.initial); // initial @@ -965,10 +1199,15 @@ class WasmModuleBuilder { binary.emit_section(kTableSectionCode, section => { section.emit_u32v(wasm.tables.length); for (let table of wasm.tables) { - section.emit_u8(table.type); + if (table.has_init) { + section.emit_u8(0x40); // "has initializer" + section.emit_u8(0x00); // Reserved byte. + } + section.emit_type(table.type); section.emit_u8(table.has_max); section.emit_u32v(table.initial_size); if (table.has_max) section.emit_u32v(table.max_size); + if (table.has_init) section.emit_init_expr(table.init_expr); } }); } @@ -997,41 +1236,9 @@ class WasmModuleBuilder { binary.emit_section(kGlobalSectionCode, section => { section.emit_u32v(wasm.globals.length); for (let global of wasm.globals) { - section.emit_u8(global.type); + section.emit_type(global.type); section.emit_u8(global.mutable); - if ((typeof global.init_index) == "undefined") { - // Emit a constant initializer. - switch (global.type) { - case kWasmI32: - section.emit_u8(kExprI32Const); - section.emit_u32v(global.init); - break; - case kWasmI64: - section.emit_u8(kExprI64Const); - section.emit_u64v(global.init); - break; - case kWasmF32: - section.emit_bytes(wasmF32Const(global.init)); - break; - case kWasmF64: - section.emit_bytes(wasmF64Const(global.init)); - break; - case kWasmAnyFunc: - case kWasmAnyRef: - if (global.function_index !== undefined) { - section.emit_u8(kExprRefFunc); - section.emit_u32v(global.function_index); - } else { - section.emit_u8(kExprRefNull); - } - break; - } - } else { - // Emit a global-index initializer. - section.emit_u8(kExprGlobalGet); - section.emit_u32v(global.init_index); - } - section.emit_u8(kExprEnd); // end of init expression + section.emit_init_expr(global.init); } }); } @@ -1161,7 +1368,7 @@ class WasmModuleBuilder { local_decls.push({count: l.s128_count, type: kWasmS128}); } if (l.anyref_count > 0) { - local_decls.push({count: l.anyref_count, type: kWasmAnyRef}); + local_decls.push({count: l.anyref_count, type: kWasmExternRef}); } if (l.anyfunc_count > 0) { local_decls.push({count: l.anyfunc_count, type: kWasmAnyFunc}); @@ -1171,7 +1378,7 @@ class WasmModuleBuilder { header.emit_u32v(local_decls.length); for (let decl of local_decls) { header.emit_u32v(decl.count); - header.emit_u8(decl.type); + header.emit_type(decl.type); } section.emit_u32v(header.length + func.body.length);