diff --git a/.github/workflows/ci-spec.yml b/.github/workflows/ci-spec.yml index a0aae6a047..75a88f58cd 100644 --- a/.github/workflows/ci-spec.yml +++ b/.github/workflows/ci-spec.yml @@ -40,8 +40,39 @@ jobs: name: core-rendered path: document/core/_build/html - build-legacy-exceptions-spec: + build-js-api-spec: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Setup Bikeshed + run: pip install bikeshed && bikeshed update + - name: Run Bikeshed + run: bikeshed spec "document/js-api/index.bs" "document/js-api/index.html" + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: js-api-rendered + path: document/js-api/index.html + + build-web-api-spec: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Setup Bikeshed + run: pip install bikeshed && bikeshed update + - name: Run Bikeshed + run: bikeshed spec "document/web-api/index.bs" "document/web-api/index.html" + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: web-api-rendered + path: document/web-api/index.html + + build-code-metadata-spec: runs-on: ubuntu-latest + needs: [build-core-spec] steps: - name: Checkout repo uses: actions/checkout@v2 @@ -52,29 +83,33 @@ jobs: - name: Setup Sphinx run: pip install six && pip install sphinx==5.1.0 - name: Build main spec - run: cd document/legacy/exceptions && make main + run: cd document/metadata/code && make main - name: Upload artifact uses: actions/upload-artifact@v2 with: - name: legacy-exceptions-rendered - path: document/legacy/exceptions/_build/html + name: code-metadata-rendered + path: document/metadata/code/_build/html - build-js-api-spec: + build-legacy-exceptions-core-spec: runs-on: ubuntu-latest steps: - name: Checkout repo uses: actions/checkout@v2 - - name: Setup Bikeshed - run: pip install bikeshed && bikeshed update - - name: Run Bikeshed - run: bikeshed spec "document/js-api/index.bs" "document/js-api/index.html" + with: + submodules: "recursive" + - name: Setup TexLive + run: sudo apt-get update -y && sudo apt-get install -y latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended + - name: Setup Sphinx + run: pip install six && pip install sphinx==5.1.0 + - name: Build main spec + run: cd document/legacy/exceptions/core && make main - name: Upload artifact uses: actions/upload-artifact@v2 with: - name: js-api-rendered - path: document/js-api/index.html + name: legacy-exceptions-core-rendered + path: document/legacy/exceptions/core/_build/html - build-web-api-spec: + build-legacy-exceptions-js-api-spec: runs-on: ubuntu-latest steps: - name: Checkout repo @@ -82,16 +117,16 @@ jobs: - name: Setup Bikeshed run: pip install bikeshed && bikeshed update - name: Run Bikeshed - run: bikeshed spec "document/web-api/index.bs" "document/web-api/index.html" + run: bikeshed spec "document/legacy/exceptions/js-api/index.bs" "document/legacy/exceptions/js-api/index.html" - name: Upload artifact uses: actions/upload-artifact@v2 with: - name: web-api-rendered - path: document/web-api/index.html + name: legacy-exceptions-js-api-rendered + path: document/legacy/exceptions/js-api/index.html publish-spec: runs-on: ubuntu-latest - needs: [build-core-spec, build-legacy-exceptions-spec, build-js-api-spec, build-web-api-spec] + needs: [build-core-spec, build-js-api-spec, build-web-api-spec, build-code-metadata-spec, build-legacy-exceptions-core-spec, build-legacy-exceptions-js-api-spec] steps: - name: Checkout repo uses: actions/checkout@v2 @@ -102,11 +137,6 @@ jobs: with: name: core-rendered path: _output/core - - name: Download legacy exceptions spec artifact - uses: actions/download-artifact@v2 - with: - name: legacy-exceptions-rendered - path: _output/legacy/exceptions - name: Download JS API spec artifact uses: actions/download-artifact@v2 with: @@ -117,6 +147,21 @@ jobs: with: name: web-api-rendered path: _output/web-api + - name: Download code metadata spec artifact + uses: actions/download-artifact@v2 + with: + name: code-metadata-rendered + path: _output/metadata/code + - name: Download legacy exceptions core spec artifact + uses: actions/download-artifact@v2 + with: + name: legacy-exceptions-core-rendered + path: _output/legacy/exceptions/core + - name: Download legacy exceptions JS API spec artifact + uses: actions/download-artifact@v2 + with: + name: legacy-exceptions-js-api-rendered + path: _output/legacy/exceptions/js-api - name: Publish to GitHub Pages if: github.ref == 'refs/heads/main' uses: peaceiris/actions-gh-pages@v3 diff --git a/README.md b/README.md index 1c529cb0c6..86ff7e93b8 100644 --- a/README.md +++ b/README.md @@ -21,18 +21,11 @@ Original `README` from upstream repository follows. # spec -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. +This repository holds the sources for the WebAssembly specification, +a reference implementation, and the official test suite. -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". +A formatted version of the spec is available here: +[webassembly.github.io/spec](https://webassembly.github.io/spec/), 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 deleted file mode 100644 index f4e5b00055..0000000000 --- a/deploy_key +++ /dev/null @@ -1,49 +0,0 @@ ------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 deleted file mode 100644 index 313f1bd8e1..0000000000 --- a/deploy_key.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCf0VStDNZyAQ4ewMn5/svSDvEhit0FvcrV1CoVYWJ1lwvwpgwxHT8IRhFd9KfcRy+lidGgiNZ8ZCCo6kD3QUxoDCp2grvYHJburYLjKaNfnFHxcXIblglW+Nox5JNdmXKdBI4f2VdTlGih4TBFq2BCLO7AH8GpqGvL/wU58OFWU07bohsTIk93bep7IB37jYI5MTmhYcQndMpJZJ/BMCF+siUiD51aJLH8hRwtz7syzkSlCHd5tfj2hpbTRJZEOfKqgz3mYP5RzcO5MUnIp0eUui4RJ02sT0t4Gazk39sdKB51XJnl8v1IcLo/UeaAM8WymY7rPWzXn9rnLmF/3MdWiPWFrngtJo+UTn8vmGljo8Ift96lFeHDzhUhBXvgiwmjDVdRuSZ8qSNbc94GoIXlxMoXYsrrlKtrER80wtTROE4foHrLN4+rskX7TlUYyHYbZaTDLXWl7N6pbm09TUzarBJE3yMAUojC4LsHvd2vPzIyEXwx9NbiIYzhGqFBEIrbbGZW3PBgXBEpQz76naI6CAaLjaOdWyS/v/JDpdxoSb7YnHcLbPL/k50uqX3PyCYqwfiOlBQAOZp7woF8kKodmu2+FFOof30Te2GkgpynNj6fm6E0LKtj6FN8Hjj8Qie1aRcNMvBVVGaY7IdK8QDv8PPW+0DuBj027bnFj9JdKw== diff --git a/document/Makefile b/document/Makefile index e19d7e7de5..77a79ee70f 100644 --- a/document/Makefile +++ b/document/Makefile @@ -1,4 +1,4 @@ -DIRS = core js-api web-api legacy/exceptions +DIRS = core js-api web-api metadata/code legacy/exceptions FILES = index.html BUILDDIR = _build TAR = tar diff --git a/document/core/appendix/changes.rst b/document/core/appendix/changes.rst index 3db3891a1e..7c23860819 100644 --- a/document/core/appendix/changes.rst +++ b/document/core/appendix/changes.rst @@ -12,7 +12,7 @@ Release 2.0 .. index:: instruction, integer -Sign extension instructions +Sign Extension Instructions ........................... Added new numeric instructions for performing sign extension within integer representations. [#proposal-signext]_ @@ -22,7 +22,7 @@ Added new numeric instructions for performing sign extension within integer repr .. index:: instruction, trap, floating-point, integer -Non-trapping float-to-int conversions +Non-trapping Float-to-Int Conversions ..................................... Added new conversion instructions that avoid trapping when converting a floating-point number to an integer. [#proposal-cvtsat]_ @@ -32,7 +32,7 @@ Added new conversion instructions that avoid trapping when converting a floating .. index:: block, function, value type, result type -Multiple values +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]_ @@ -44,7 +44,7 @@ Generalized the result type of blocks and functions to allow for multiple values .. index:: value type, reference, reference type, instruction, element segment -Reference types +Reference Types ............... Added |FUNCREF| and |EXTERNREF| as new value types and respective instructions. [#proposal-reftype]_ @@ -60,7 +60,7 @@ Added |FUNCREF| and |EXTERNREF| as new value types and respective instructions. .. index:: reference, instruction, table, table type -Table instructions +Table Instructions .................. Added instructions to directly access and modify tables. [#proposal-reftype]_ @@ -72,7 +72,7 @@ Added instructions to directly access and modify tables. [#proposal-reftype]_ .. index:: table, instruction, table index, element segment, import, export -Multiple tables +Multiple Tables ............... Added the ability to use multiple tables per module. [#proposal-reftype]_ @@ -86,7 +86,7 @@ Added the ability to use multiple tables per module. [#proposal-reftype]_ .. index:: instruction, table, memory, data segment, element segment -Bulk memory and table instructions +Bulk Memory and Table Instructions .................................. Added instructions that modify ranges of memory or table entries. [#proposal-reftype]_ [#proposal-bulk]_ @@ -106,7 +106,7 @@ Added instructions that modify ranges of memory or table entries. [#proposal-ref .. index:: instructions, SIMD, value type, vector type -Vector instructions +Vector Instructions ................... Added vector type and instructions that manipulate multiple numeric values in parallel (also known as *SIMD*, single instruction multiple data) [#proposal-vectype]_ @@ -160,90 +160,89 @@ Added vector type and instructions that manipulate multiple numeric values in pa Release 3.0 ~~~~~~~~~~~ -.. index: instruction, function, call +.. index:: instruction, expression, constant -Tail Calls -.......... +Extended Constant Expressions +............................. -Added instructions to perform tail calls [#proposal-tailcall]_. +Allowed basic numeric computations in constant expressions. [#proposal-extconst]_ -* New :ref:`control instructions `: :math:`RETURNCALL` and :math:`RETURNCALLINDIRECT` +* 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:: reference, reference type, heap type, value type, local, local type, instruction, instruction type, table, function, function type, matching, subtyping -Typeful References -.................. +.. index:: instruction, function, call -Added more precise types for references [#proposal-typedref]_. +Tail Calls +.......... -* New generalised form of :ref:`reference types `: :math:`(\REF~\NULL^?~\heaptype)` +Added instructions to perform tail calls. [#proposal-tailcall]_ -* New class of :ref:`heap types `: |FUNC|, |EXTERN|, :math:`\typeidx` +* New :ref:`control instructions `: |RETURNCALL| and |RETURNCALLINDIRECT| -* Basic :ref:`subtyping ` on :ref:`reference ` and :ref:`value ` types -* New :ref:`reference instructions `: |REFASNONNULL|, |BRONNULL|, |BRONNONNULL| - -* New :ref:`control instruction `: |CALLREF| +.. index:: instruction, exception, reference type, tag type, tag, handler -* Refined typing of :ref:`reference instruction ` |REFFUNC| with more precise result type +Exception Handling +.................. -* Refined typing of :ref:`local instructions ` and :ref:`instruction sequences ` to track the :ref:`initialization status ` of :ref:`locals ` with non-:ref:`defaultable ` type +Added tag definitions, imports, and exports, and instructions to throw and catch exceptions [#proposal-exn]_ -* Extended :ref:`table definitions ` with optional initializer expression +* Modules may :ref:`define `, :ref:`import `, and :ref:`export ` tags. +* New :ref:`heap types `: |EXN|, |NOEXN| -.. index:: reference, reference type, heap type, field type, storage type, structure type, array type, composite type, sub type, recursive type +* New :ref:`reference type ` short-hands: |EXNREF|, |NULLEXNREF| -Garbage Collection -.................. +* New :ref:`control instructions `: |THROW|, |THROWREF|, and |TRYTABLE|. -Added managed reference types [#proposal-gc]_. +* New :ref:`tag section ` in binary format. -* 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| +.. index:: instruction, memory, memory index, data segment, import, export -* New forms of type definitions: :ref:`structure ` and :ref:`array types `, :ref:`sub types `, and :ref:`recursive types ` +Multiple Memories +................. -* Enriched :ref:`subtyping ` based on explicitly declared :ref:`sub types ` and the new heap types +Added the ability to use multiple memories per module. [#proposal-multimem]_ -* New generic :ref:`reference instructions `: |REFEQ|, |REFTEST|, |REFCAST|, |BRONCAST|, |BRONCASTFAIL| +* :ref:`Modules ` may :ref:`define `, :ref:`import `, and :ref:`export ` multiple memories -* New :ref:`reference instructions ` for :ref:`unboxed scalars `: |REFI31|, :math:`\I31GET\K{\_}\sx` +* :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}` -* New :ref:`reference instructions ` for :ref:`structure types `: |STRUCTNEW|, |STRUCTNEWDEFAULT|, :math:`\STRUCTGET\K{\_}\sx^?`, |STRUCTSET| +* :ref:`Data segments ` take a :ref:`memory index ` -* 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| +.. index:: reference, reference type, heap type, value type, local, local type, instruction, instruction type, table, function, function type, matching, subtyping -* Extended set of :ref:`constant instructions ` with |REFI31|, |STRUCTNEW|, |STRUCTNEWDEFAULT|, |ARRAYNEW|, |ARRAYNEWDEFAULT|, |ARRAYNEWFIXED|, |ANYCONVERTEXTERN|, |EXTERNCONVERTANY|, and |GLOBALGET| for any previously declared immutable :ref:`global ` +Typeful References +.................. +Added more precise types for references. [#proposal-typedref]_ -.. index:: instruction, exception, reference type, tag type, tag, handler +* New generalised form of :ref:`reference types `: :math:`(\REF~\NULL^?~\heaptype)` -Exception Handling -.................. +* New class of :ref:`heap types `: |FUNC|, |EXTERN|, :math:`\typeidx` -Added tag definitions, imports, and exports, and instructions to throw and catch exceptions [#proposal-exn]_ +* Basic :ref:`subtyping ` on :ref:`reference ` and :ref:`value ` types -* Modules may :ref:`define `, :ref:`import `, and :ref:`export ` tags. +* New :ref:`reference instructions `: |REFASNONNULL|, |BRONNULL|, |BRONNONNULL| -* New :ref:`heap types `: |EXN|, |NOEXN| +* New :ref:`control instruction `: |CALLREF| -* New :ref:`reference type ` short-hands: |EXNREF|, |NULLEXNREF| +* Refined typing of :ref:`reference instruction ` |REFFUNC| with more precise result type -* New :ref:`control instructions `: |THROW|, |THROWREF|, and |TRYTABLE|. +* Refined typing of :ref:`local instructions ` and :ref:`instruction sequences ` to track the :ref:`initialization status ` of :ref:`locals ` with non-:ref:`defaultable ` type -* New :ref:`tag section ` in binary format. +* 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 +Garbage Collection .................. Added managed reference types. [#proposal-gc]_ @@ -269,20 +268,40 @@ Added managed reference types. [#proposal-gc]_ * Extended set of :ref:`constant instructions ` with |REFI31|, |STRUCTNEW|, |STRUCTNEWDEFAULT|, |ARRAYNEW|, |ARRAYNEWDEFAULT|, |ARRAYNEWFIXED|, |ANYCONVERTEXTERN|, |EXTERNCONVERTANY| +.. index:: text format, annotation, custom section, identifier, module, type, function, local, structure field + +Custom Annotations +.................. + +Added generic syntax for custom annotations in the text format, +mirroring the role of custom sections in the binary format. [#proposal-annot]_ + +* :ref:`Annotations ` of the form :math:`\text{(@id~\dots)}` are allowed anywhere in the :ref:`text format ` + +* :ref:`Identifiers ` can be escaped as :math:`\text{@"\dots"}` with arbitrary :ref:`names ` + +* Defined :ref:`name annotations ` :math:`\text{(@name~"\dots")}` for :ref:`module names `, :ref:`type names `, :ref:`function names `, :ref:`local names `, and :ref:`field names ` + +* Defined :ref:`custom annotation ` :math:`\text{(@custom~"\dots")}` to represent arbitrary :ref:`custom sections ` in the text format + + .. [#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-exn] + https://github.com/WebAssembly/spec/tree/main/proposals/exception-handling/ + .. [#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-exn] - https://github.com/WebAssembly/spec/tree/main/proposals/exception-handling/ - .. [#proposal-gc] https://github.com/WebAssembly/spec/tree/main/proposals/gc/ + +.. [#proposal-annot] + https://github.com/WebAssembly/annotations/tree/main/proposals/annotations/ diff --git a/document/core/appendix/custom.rst b/document/core/appendix/custom.rst index dcafe0079c..ba5aa9b618 100644 --- a/document/core/appendix/custom.rst +++ b/document/core/appendix/custom.rst @@ -1,13 +1,12 @@ -.. index:: custom section, section, binary format +.. index:: custom section, section, binary format, annotation, text format -Custom Sections ---------------- +Custom Sections and Annotations +------------------------------- -This appendix defines dedicated :ref:`custom sections ` for WebAssembly's :ref:`binary format `. -Such sections do not contribute to, or otherwise affect, the WebAssembly semantics, and like any custom section they may be ignored by an implementation. +This appendix defines dedicated :ref:`custom sections ` for WebAssembly's :ref:`binary format ` and :ref:`annotations ` for the text format. +Such sections or annotations do not contribute to, or otherwise affect, the WebAssembly semantics, and may be ignored by an implementation. However, they provide useful meta data that implementations can make use of to improve user experience or take compilation hints. -Currently, only one dedicated custom section is defined, the :ref:`name section`. .. index:: ! name section, name, Unicode UTF-8 @@ -153,7 +152,7 @@ It consists of an :ref:`indirect name map ` assigning lo .. _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 `. @@ -172,7 +171,7 @@ 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 `. +It consists of an :ref:`indirect name map ` assigning field names to :ref:`field indices ` grouped by the :ref:`type indices ` of their respective :ref:`structure types `. .. math:: \begin{array}{llclll} @@ -195,3 +194,249 @@ It consists of a :ref:`name map ` assigning tag names to :ref:`t \production{tag name subsection} & \Btagnamesubsec &::=& \Bnamesubsection_1(\Bnamemap) \\ \end{array} + + +.. index:: ! name annotation, name, Unicode UTF-8 +.. _text-nameannot: + +Name Annotations +~~~~~~~~~~~~~~~~ + +*Name annotations* are the textual analogue to the :ref:`name section ` and provide a textual representation for it. +Consequently, their id is :math:`\T{@name}`. + +Analogous to the name section, name annotations are allowed on :ref:`modules `, :ref:`functions `, and :ref:`locals ` (including :ref:`parameters `). +They can be placed where the text format allows binding occurrences of respective :ref:`identifiers `. +If both an identifier and a name annotation are given, the annotation is expected *after* the identifier. +In that case, the annotation takes precedence over the identifier as a textual representation of the binding's name. +At most one name annotation may be given per binding. + +All name annotations have the following format: + +.. math:: + \begin{array}{llclll} + \production{name annotation} & \Tnameannot &::=& + \text{(@name}~\Tstring~\text{)} \\ + \end{array} + + +.. note:: + All name annotations can be arbitrary UTF-8 :ref:`strings `. + Names need not be unique. + + +.. index:: module +.. _text-modulenameannot: + +Module Names +............ + +A *module name annotation* must be placed on a :ref:`module ` definition, +directly after the :math:`\text{module}` keyword, or if present, after the following module :ref:`identifier `. + +.. math:: + \begin{array}{llclll} + \production{module name annotation} & \Tmodulenameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: function +.. _text-funcnameannot: + +Function Names +.............. + +A *function name annotation* must be placed on a :ref:`function ` definition or function :ref:`import `, +directly after the :math:`\text{func}` keyword, or if present, after the following function :ref:`identifier ` or. + +.. math:: + \begin{array}{llclll} + \production{function name annotation} & \Tfuncnameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: function, parameter +.. _text-paramnameannot: + +Parameter Names +............... + +A *parameter name annotation* must be placed on a :ref:`parameter ` declaration, +directly after the :math:`\text{param}` keyword, or if present, after the following parameter :ref:`identifier `. +It may only be placed on a declaration that declares exactly one parameter. + +.. math:: + \begin{array}{llclll} + \production{parameter name annotation} & \Tparamnameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: function, local +.. _text-localnameannot: + +Local Names +........... + +A *local name annotation* must be placed on a :ref:`local ` declaration, +directly after the :math:`\text{local}` keyword, or if present, after the following local :ref:`identifier `. +It may only be placed on a declaration that declares exactly one local. + +.. math:: + \begin{array}{llclll} + \production{local name annotation} & \Tlocalnameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: type +.. _text-typenameannot: + +Type Names +.......... + +A *type name annotation* must be placed on a :ref:`type ` declaration, +directly after the :math:`\text{type}` keyword, or if present, after the following type :ref:`identifier `. + +.. math:: + \begin{array}{llclll} + \production{type name annotation} & \Ttypenameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: type, structure type, field +.. _text-fieldnameannot: + +Field Names +........... + +A *field name annotation* must be placed on the field of a :ref:`structure type `, +directly after the :math:`\text{field}` keyword, or if present, after the following field :ref:`identifier `. +It may only be placed on a declaration that declares exactly one field. + +.. math:: + \begin{array}{llclll} + \production{field name annotation} & \Tfieldnameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: tag +.. _text-tagnameannot: + +Tag Names +......... + +A *tag name annotation* must be placed on a :ref:`tag declaration ` or tag :ref:`import `, +directly after the :math:`\text{tag}` keyword, or if present, after the following tag :ref:`identifier `. + +.. math:: + \begin{array}{llclll} + \production{tag name annotation} & \Ttagnameannot &::=& + \Tnameannot \\ + \end{array} + + +.. index:: ! custom annotation, custom section +.. _text-customannot: + +Custom Annotations +~~~~~~~~~~~~~~~~~~ + +*Custom annotations* are a generic textual representation for any :ref:`custom section `. +Their id is :math:`\T{@custom}`. +By generating custom annotations, tools converting between :ref:`binary format ` and :ref:`text format ` can maintain and round-trip the content of custom sections even when they do not recognize them. + +Custom annotations must be placed inside a :ref:`module ` definition. +They must occur anywhere after the :math:`\text{module}` keyword, or if present, after the following module :ref:`identifier `. +They must not be nested into other constructs. + +.. math:: + \begin{array}{llclll} + \production{custom annotation} & \Tcustomannot &::=& + \text{(@custom}~~\Tstring~~\Tcustomplace^?~~\Tdatastring~~\text{)} \\ + \production{custom placement} & \Tcustomplace &::=& + \text{(}~\text{before}~~\text{first}~\text{)} \\ &&|& + \text{(}~\text{before}~~\Tsec~\text{)} \\ &&|& + \text{(}~\text{after}~~\Tsec~\text{)} \\ &&|& + \text{(}~\text{after}~~\text{last}~\text{)} \\ + \production{section} & \Tsec &::=& + \text{type} \\ &&|& + \text{import} \\ &&|& + \text{func} \\ &&|& + \text{table} \\ &&|& + \text{memory} \\ &&|& + \text{global} \\ &&|& + \text{export} \\ &&|& + \text{start} \\ &&|& + \text{elem} \\ &&|& + \text{code} \\ &&|& + \text{data} \\ &&|& + \text{datacount} \\ + \end{array} + +The first :ref:`string ` in a custom annotation denotes the name of the custom section it represents. +The remaining strings collectively represent the section's payload data, written as a :ref:`data string `, which can be split up into a possibly empty sequence of individual string literals (similar to :ref:`data segments `). + +An arbitrary number of custom annotations (even of the same name) may occur in a module, +each defining a separate custom section when converting to :ref:`binary format `. +Placement of the sections in the binary can be customized via explicit *placement* directives, that position them either directly before or directly after a known section. +That section must exist and be non-empty in the binary encoding of the annotated module. +The placements :math:`\T{(before~first)}` and :math:`\T{(after~last)}` denote virtual sections before the first and after the last known section, respectively. +When the placement directive is omitted, it defaults to :math:`\T{(after~last)}`. + +If multiple placement directives appear for the same position, then the sections are all placed there, in order of their appearance in the text. +For this purpose, the position :math:`\T{after}` a section is considered different from the position :math:`\T{before}` the consecutive section, and the former occurs before the latter. + +.. note:: + Future versions of WebAssembly may introduce additional sections between others or at the beginning or end of a module. + Using :math:`\T{first}` and :math:`\T{last}` guarantees that placement will still go before or after any future section, respectively. + +If a custom section with a specific section id is given as well as annotations representing the same custom section (e.g., :math:`\T{@name}` :ref:`annotations ` as well as a :math:`\T{@custom}` annotation for a :math:`\T{name}` :ref:`section `), then two sections are assumed to be created. +Their relative placement will depend on the placement directive given for the :math:`\T{@custom}` annotation as well as the implicit placement requirements of the custom section, which are applied to the other annotation. + +.. note:: + + For example, the following module, + + .. code-block:: none + + (module + (@custom "A" "aaa") + (type $t (func)) + (@custom "B" (after func) "bbb") + (@custom "C" (before func) "ccc") + (@custom "D" (after last) "ddd") + (table 10 funcref) + (func (type $t)) + (@custom "E" (after import) "eee") + (@custom "F" (before type) "fff") + (@custom "G" (after data) "ggg") + (@custom "H" (after code) "hhh") + (@custom "I" (after func) "iii") + (@custom "J" (before func) "jjj") + (@custom "K" (before first) "kkk") + ) + + will result in the following section ordering: + + .. code-block:: none + + custom section "K" + custom section "F" + type section + custom section "E" + custom section "C" + custom section "J" + function section + custom section "B" + custom section "I" + table section + code section + custom section "H" + custom section "G" + custom section "A" + custom section "D" diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index e58f8966cd..737230ef85 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -39,10 +39,19 @@ Interface operation that are predicates return Boolean values: .. _embed-error: -Errors -~~~~~~ +Exceptions and Errors +~~~~~~~~~~~~~~~~~~~~~ + +Invoking an exported function may throw or propagate exceptions, expressed by an auxiliary syntactic class: + +.. math:: + \begin{array}{llll} + \production{exception} & \exception &::=& \ETHROW ~ \exnaddr \\ + \end{array} -Failure of an interface operation is indicated by an auxiliary syntactic class: +The exception address :math:`exnaddr` identifies the exception thrown. + +Failure of an interface operation is also indicated by an auxiliary syntactic class: .. math:: \begin{array}{llll} @@ -56,6 +65,8 @@ In addition to the error conditions specified explicitly in this section, such a Implementations can refine it to carry suitable classifications and diagnostic messages. + + Pre- and Post-Conditions ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -307,14 +318,16 @@ Functions .. index:: invocation, value, result .. _embed-func-invoke: -:math:`\F{func\_invoke}(\store, \funcaddr, \val^\ast) : (\store, \val^\ast ~|~ \error)` -........................................................................................ +:math:`\F{func\_invoke}(\store, \funcaddr, \val^\ast) : (\store, \val^\ast ~|~ \exception ~|~ \error)` +...................................................................................................... 1. Try :ref:`invoking ` the function :math:`\funcaddr` in :math:`\store` with :ref:`values ` :math:`\val^\ast` as arguments: a. If it succeeds with :ref:`values ` :math:`{\val'}^\ast` as results, then let :math:`\X{result}` be :math:`{\val'}^\ast`. - b. Else it has trapped, hence let :math:`\X{result}` be :math:`\ERROR`. + b. Else if the outcome is an exception with a thrown :ref:`exception ` :math:`\REFEXNADDR~\exnaddr` as the result, then let :math:`\X{result}` be :math:`\ETHROW~\exnaddr` + + c. Else it has trapped, hence let :math:`\X{result}` be :math:`\ERROR`. 2. Return the new store paired with :math:`\X{result}`. @@ -322,7 +335,8 @@ Functions ~ \\ \begin{array}{lclll} \F{func\_invoke}(S, a, v^\ast) &=& (S', {v'}^\ast) && (\iff \invoke(S, a, v^\ast) \stepto^\ast S'; F; {v'}^\ast) \\ - \F{func\_invoke}(S, a, v^\ast) &=& (S', \ERROR) && (\iff \invoke(S, a, v^\ast) \stepto^\ast S'; F; \result) \\ + \F{func\_invoke}(S, a, v^\ast) &=& (S', \ETHROW~a') && (\iff \invoke(S, a, v^\ast) \stepto^\ast S'; F; \XT[(\REFEXNADDR~a')~\THROWREF] \\ + \F{func\_invoke}(S, a, v^\ast) &=& (S', \ERROR) && (\iff \invoke(S, a, v^\ast) \stepto^\ast S'; F; \TRAP) \\ \end{array} .. note:: @@ -576,6 +590,77 @@ Tags \end{array} +.. _embed-tag-type: + +:math:`\F{tag\_type}(\store, \tagaddr) : \tagtype` +.................................................. + +1. Return :math:`S.\STAGS[a].\TAGITYPE`. + +2. Post-condition: the returned :ref:`tag type ` is :ref:`valid `. + +.. math:: + \begin{array}{lclll} + \F{tag\_type}(S, a) &=& S.\STAGS[a].\TAGITYPE \\ + \end{array} + + +.. index:: exception, exception address, store, exception instance, exception type +.. _embed-exception: + +Exceptions +~~~~~~~~~~ + +.. _embed-exn-alloc: + +:math:`\F{exn\_alloc}(\store, \tagaddr, \val^\ast) : (\store, \exnaddr)` +........................................................................ + +1. Pre-condition: :math:`\tagaddr` is an allocated :ref:`tag address `. + +2. Let :math:`\exnaddr` be the result of :ref:`allocating an exception instance ` in :math:`\store` with :ref:`tag address ` :math:`\tagaddr` and initialization values :math:`\val^\ast`. + +3. Return the new store paired with :math:`\exnaddr`. + +.. math:: + \begin{array}{lcll} + \F{exn\_alloc}(S, \tagaddr, \val^\ast) &=& (S \compose \{\SEXNS~\exninst\}, |S.\SEXNS|) & + (\iff \exninst = \{\EITAG~\tagaddr, \EIFIELDS~\val^\ast\} \\ + \end{array} + + +.. _embed-exn-tag: + +:math:`\F{exn\_tag}(\store, \exnaddr) : \tagaddr` +................................................. + +1. Let :math:`\exninst` be the :ref:`exception instance ` :math:`\store.\SEXNS[\exnaddr]`. + +2. Return the :ref:`tag address ` :math:`\exninst.\EITAG`. + +.. math:: + \begin{array}{lcll} + \F{exn\_tag}(S, a) &=& \exninst.\EITAG & + (\iff \exninst = S.\SEXNS[a]) \\ + \end{array} + + +.. _embed-exn-read: + +:math:`\F{exn\_read}(\store, \exnaddr) : \val^\ast` +................................................... + +1. Let :math:`\exninst` be the :ref:`exception instance ` :math:`\store.\SEXNS[\exnaddr]`. + +2. Return the :ref:`values ` :math:`\exninst.\EIFIELDS`. + +.. math:: + \begin{array}{lcll} + \F{exn\_read}(S, a) &=& \exninst.\EIFIELDS & + (\iff \exninst = S.\SEXNS[a]) \\ + \end{array} + + .. index:: global, global address, store, global instance, global type, value .. _embed-global: diff --git a/document/core/appendix/index-instructions.py b/document/core/appendix/index-instructions.py index 267bf433af..52ffaaf465 100755 --- a/document/core/appendix/index-instructions.py +++ b/document/core/appendix/index-instructions.py @@ -349,7 +349,7 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat 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'\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'), diff --git a/document/core/binary/modules.rst b/document/core/binary/modules.rst index 85a32180a9..be424e86d7 100644 --- a/document/core/binary/modules.rst +++ b/document/core/binary/modules.rst @@ -181,7 +181,7 @@ It decodes into a vector of :ref:`imports ` that represent the |M \hex{01}~~\X{tt}{:}\Btabletype &\Rightarrow& \IDTABLE~\X{tt} \\ &&|& \hex{02}~~\X{mt}{:}\Bmemtype &\Rightarrow& \IDMEM~\X{mt} \\ &&|& \hex{03}~~\X{gt}{:}\Bglobaltype &\Rightarrow& \IDGLOBAL~\X{gt} \\ &&|& - \hex{04}~~\X{tt}{:}\Btag &\Rightarrow& \IDTAG~\X{tt} \\ + \hex{04}~~x{:}\Btagtype &\Rightarrow& \IDTAG~x \\ \end{array} diff --git a/document/core/binary/types.rst b/document/core/binary/types.rst index 525cc33739..c6212ed643 100644 --- a/document/core/binary/types.rst +++ b/document/core/binary/types.rst @@ -321,17 +321,14 @@ Global Types Tag Types ~~~~~~~~~ -:ref:`Tag types ` are encoded by their function type. +:ref:`Tag types ` are encoded by a :ref:`type index ` denoting a :ref:`function type `. .. math:: \begin{array}{llclll} \production{tag type} & \Btagtype &::=& - \hex{00}~~ft{:}\Bfunctype &\Rightarrow& ft \\ + \hex{00}~~x{:}\Btypeidx &\Rightarrow& x \\ \end{array} -The |Bfunctype| of a tag is used to characterise exceptions. -The :math:`\hex{00}` bit signifies an exception and is currently the only allowed value. - .. note:: In future versions of WebAssembly, the preceding zero byte may encode additional flags. diff --git a/document/core/exec/instructions.rst b/document/core/exec/instructions.rst index 13cb52da75..3370104907 100644 --- a/document/core/exec/instructions.rst +++ b/document/core/exec/instructions.rst @@ -803,11 +803,11 @@ Reference Instructions 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`: +18. For each of the :math:`n` consecutive subsequences :math:`{b'}^z` 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`. + b. Let :math:`c_i` be the constant for which :math:`\bytes_{\X{ft}}(c_i)` is :math:`{b'}^z`. c. Push the value :math:`t.\CONST~c_i` to the stack. @@ -1217,13 +1217,13 @@ Reference Instructions 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}`. + b. Assert: due to the earlier check against the array 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}`. + e. Assert: due to the earlier check against the array size, :math:`s+n-1 < 2^{32}`. f. Push the value :math:`\I32.\CONST~(s+n-1)` to the stack. @@ -3833,7 +3833,7 @@ Memory Instructions 20. Push the value :math:`\I32.\CONST~b` to the stack. -21. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~\{ \OFFSET~0, \ALIGN~0 \}`. +21. Execute the instruction :math:`\I32\K{.}\STORE\K{8}~x~\{ \OFFSET~0, \ALIGN~0 \}`. 22. Assert: due to the earlier check against the memory size, :math:`d+1 < 2^{32}`. @@ -4030,11 +4030,11 @@ Control Instructions 2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITAGS[x]` exists. -3. Let :math:`a` be the :ref:`tag address ` :math:`F.\AMODULE.\MITAGS[x]`. +3. Let :math:`ta` be the :ref:`tag address ` :math:`F.\AMODULE.\MITAGS[x]`. -4. Assert: due to :ref:`validation `, :math:`S.\STAGS[a]` exists. +4. Assert: due to :ref:`validation `, :math:`S.\STAGS[ta]` exists. -5. Let :math:`\X{ti}` be the :ref:`tag instance ` :math:`S.\STAGS[a]`. +5. Let :math:`\X{ti}` be the :ref:`tag instance ` :math:`S.\STAGS[ta]`. 6. Let :math:`[t^n] \toF [{t'}^\ast]` be the :ref:`tag type ` :math:`\X{ti}.\TAGITYPE`. @@ -4042,15 +4042,13 @@ Control Instructions 8. Pop the :math:`n` values :math:`\val^n` from the stack. -9. Let :math:`\X{exn}` be the :ref:`exception instance ` :math:`\{ \EITAG~a, \EIFIELDS~\val^n \}`. +9. Let :math:`\X{ea}` be the :ref:`exception address ` resulting from :ref:`allocating ` an exception instance with tag address :math:`ta` and initializer values :math:`\val^n`. -10. Let :math:`\X{ea}` be the length of :math:`S.\SEXNS`. +10. Let :math:`\X{exn}` be :math:`S.\SEXNS[ea]` -11. Append :math:`\X{exn}` to :math:`S.\SEXNS`. +11. Push the value :math:`\REFEXNADDR~\X{ea}` to the stack. -12. Push the value :math:`\REFEXNADDR~\X{ea}` to the stack. - -13. Execute the instruction |THROWREF|. +12. Execute the instruction |THROWREF|. .. math:: ~\\[-1ex] @@ -4114,13 +4112,13 @@ Control Instructions a. Let :math:`\catch_1` be the first :ref:`catch clause ` in :math:`\catch^\ast` and :math:`{\catch'}^\ast` the remaining clauses. - b. If :math:`\catch_1` is of the form :math:`\CATCH~x~l` and the :ref:`exception address ` :math:`a` equals :math:`F.\AMODULE.\MITAGS[x]`, then: + b. If :math:`\catch_1` is of the form :math:`\CATCH~x~l` and the :ref:`tag address ` :math:`a` equals :math:`F.\AMODULE.\MITAGS[x]`, then: i. Push the values :math:`\X{exn}.\EIFIELDS` to the stack. ii. Execute the instruction :math:`\BR~l`. - c. Else if :math:`\catch_1` is of the form :math:`\CATCHREF~x~l` and the :ref:`exception address ` :math:`a` equals :math:`F.\AMODULE.\MITAGS[x]`, then: + c. Else if :math:`\catch_1` is of the form :math:`\CATCHREF~x~l` and the :ref:`tag address ` :math:`a` equals :math:`F.\AMODULE.\MITAGS[x]`, then: i. Push the values :math:`\X{exn}.\EIFIELDS` to the stack. diff --git a/document/core/exec/modules.rst b/document/core/exec/modules.rst index 47c3f223d1..eef15836d0 100644 --- a/document/core/exec/modules.rst +++ b/document/core/exec/modules.rst @@ -170,6 +170,32 @@ are *allocated* in a :ref:`store ` :math:`S`, as defined by the fo \end{array} +.. index:: exception, exception instance, exception address, tag address +.. _alloc-exception: + +:ref:`Exceptions ` +.................................. + +1. Let :math:`ta` be the :ref:`tag address ` associated with the exception to allocate and :math:`\EIFIELDS~\val^\ast` be the values to initialize the exception with. + +2. Let :math:`a` be the first free :ref:`exception address ` in :math:`S`. + +3. Let :math:`\exninst` be the :ref:`exception instance ` :math:`\{ \EITAG~ta, \EIFIELDS~\val^\ast \}`. + +4. Append :math:`\exninst` to the |SEXNS| of :math:`S`. + +5. Return :math:`a`. + +.. math:: + \begin{array}{rlll} + \allocexn(S, \tagaddr, \val^\ast) &=& S', \exnaddr \\[1ex] + \mbox{where:} \hfill \\ + \exnaddr &=& |S.\SEXNS| \\ + \exninst &=& \{ \EITAG~\tagaddr, \EIFIELDS~\val^\ast \} \\ + S' &=& S \compose \{\SEXNS~\exninst\} \\ + \end{array} + + .. index:: global, global instance, global address, global type, value type, mutability, value .. _alloc-global: @@ -633,19 +659,17 @@ It is up to the :ref:`embedder ` to define how such conditions are rep 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`. - - b. Let :math:`n` be the length of the vector :math:`\data_i.\DINIT`. + a. Let :math:`n` be the length of the vector :math:`\data_i.\DINIT`. - c. :ref:`Execute ` the instruction sequence :math:`\X{dinstr}^\ast_i`. + b. :ref:`Execute ` the instruction sequence :math:`\X{dinstr}^\ast_i`. - d. :ref:`Execute ` the instruction :math:`\I32.\CONST~0`. + c. :ref:`Execute ` the instruction :math:`\I32.\CONST~0`. - e. :ref:`Execute ` the instruction :math:`\I32.\CONST~n`. + d. :ref:`Execute ` the instruction :math:`\I32.\CONST~n`. - f. :ref:`Execute ` the instruction :math:`\MEMORYINIT~i`. + e. :ref:`Execute ` the instruction :math:`\MEMORYINIT~i`. - g. :ref:`Execute ` the instruction :math:`\DATADROP~i`. + f. :ref:`Execute ` the instruction :math:`\DATADROP~i`. 15. If the :ref:`start function ` :math:`\module.\MSTART` is not empty, then: @@ -695,8 +719,8 @@ where: \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=\\ \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) \\ + \F{rundata}_i(\{\DINIT~b^n, \DMODE~\DACTIVE \{\DMEM~x, \DOFFSET~\instr^\ast~\END\}\}) \quad=\\ \qquad + \instr^\ast~(\I32.\CONST~0)~(\I32.\CONST~n)~(\MEMORYINIT~x~i)~(\DATADROP~i) \\ \end{array} .. note:: diff --git a/document/core/syntax/instructions.rst b/document/core/syntax/instructions.rst index bc718b4d08..295d5ad299 100644 --- a/document/core/syntax/instructions.rst +++ b/document/core/syntax/instructions.rst @@ -763,7 +763,7 @@ Instructions in this group affect the flow of control. \CALLREF~\typeidx \\&&|& \CALLINDIRECT~\tableidx~\typeidx \\&&|& \RETURNCALL~\funcidx \\&&|& - \RETURNCALLREF~\funcidx \\&&|& + \RETURNCALLREF~\typeidx \\&&|& \RETURNCALLINDIRECT~\tableidx~\typeidx \\&&|& \THROW~\tagidx \\&&|& \THROWREF \\ &&|& diff --git a/document/core/syntax/modules.rst b/document/core/syntax/modules.rst index 8ffebb9ded..a0ea7fe750 100644 --- a/document/core/syntax/modules.rst +++ b/document/core/syntax/modules.rst @@ -445,7 +445,7 @@ The |MIMPORTS| component of a module defines a set of *imports* that are require \IDTABLE~\tabletype \\&&|& \IDMEM~\memtype \\&&|& \IDGLOBAL~\globaltype \\&&|& - \IDTAG~\tagtype \\ + \IDTAG~\typeidx \\ \end{array} Each import is labeled by a two-level :ref:`name ` space, consisting of a |IMODULE| name and a |INAME| for an entity within that module. diff --git a/document/core/text/conventions.rst b/document/core/text/conventions.rst index dd172ca0df..ff3720638f 100644 --- a/document/core/text/conventions.rst +++ b/document/core/text/conventions.rst @@ -120,22 +120,23 @@ It is convenient to define identifier contexts as :ref:`records ` assigned to the defined indices. +For each index space, such a context contains the list of :ref:`names ` assigned to the defined indices, +which were denoted by the corresponding :ref:`identifiers `. 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. diff --git a/document/core/text/instructions.rst b/document/core/text/instructions.rst index d0f911f24d..d3f7935c6b 100644 --- a/document/core/text/instructions.rst +++ b/document/core/text/instructions.rst @@ -32,11 +32,11 @@ The following grammar handles the corresponding update to the :ref:`identifier c .. math:: \begin{array}{llcllll} \production{label} & \Tlabel_I &::=& - v{:}\Tid &\Rightarrow& \{\ILABELS~v\} \compose I + v{:}\Tid &\Rightarrow& v, \{\ILABELS~v\} \compose I & (\iff v \notin I.\ILABELS) \\ &&|& - v{:}\Tid &\Rightarrow& \{\ILABELS~v\} \compose (I \with \ILABELS[i] = \epsilon) + v{:}\Tid &\Rightarrow& v, \{\ILABELS~v\} \compose (I \with \ILABELS[i] = \epsilon) & (\iff I.\ILABELS[i] = v) \\ &&|& - \epsilon &\Rightarrow& \{\ILABELS~(\epsilon)\} \compose I \\ + \epsilon &\Rightarrow& \epsilon, \{\ILABELS~(\epsilon)\} \compose I \\ \end{array} .. note:: @@ -81,16 +81,16 @@ However, the special case of a type use that is syntactically empty or consists x,I'{:}\Ttypeuse_I &\Rightarrow& x & (\iff I' = \{\ILOCALS~(\epsilon)^\ast\}) \\ \end{array} \\ \production{block instruction} & \Tblockinstr_I &::=& - \text{block}~~I'{:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}{:}\Tinstr_{I'})^\ast~~\text{end}~~\Tid^? + \text{block}~~(v^?,I'){:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}{:}\Tinstr_{I'})^\ast~~\text{end}~~{v'}^?{:}\Tid^? \\ &&&\qquad \Rightarrow\quad \BLOCK~\X{bt}~\X{in}^\ast~\END - \qquad\quad~~ (\iff \Tid^? = \epsilon \vee \Tid^? = \Tlabel) \\ &&|& - \text{loop}~~I'{:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}{:}\Tinstr_{I'})^\ast~~\text{end}~~\Tid^? + \qquad\quad~~ (\iff {v'}^? = \epsilon \vee {v'}^? = v^?) \\ &&|& + \text{loop}~~(v^?,I'){:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}{:}\Tinstr_{I'})^\ast~~\text{end}~~{v'}^?{:}\Tid^? \\ &&&\qquad \Rightarrow\quad \LOOP~\X{bt}~\X{in}^\ast~\END - \qquad\qquad (\iff \Tid^? = \epsilon \vee \Tid^? = \Tlabel) \\ &&|& - \text{if}~~I'{:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}_1{:}\Tinstr_{I'})^\ast~~ - \text{else}~~\Tid_1^?~~(\X{in}_2{:}\Tinstr_{I'})^\ast~~\text{end}~~\Tid_2^? + \qquad\qquad (\iff {v'}^? = \epsilon \vee {v'}^? = v^?) \\ &&|& + \text{if}~~(v^?,I'){:}\Tlabel_I~~\X{bt}{:}\Tblocktype_I~~(\X{in}_1{:}\Tinstr_{I'})^\ast~~ + \text{else}~~v_1^?{:}\Tid_1^?~~(\X{in}_2{:}\Tinstr_{I'})^\ast~~\text{end}~~v_2^?{:}\Tid_2^? \\ &&&\qquad \Rightarrow\quad \IF~\X{bt}~\X{in}_1^\ast~\ELSE~\X{in}_2^\ast~\END - \qquad (\iff \Tid_1^? = \epsilon \vee \Tid_1^? = \Tlabel, \Tid_2^? = \epsilon \vee \Tid_2^? = \Tlabel) \\ &&|& + \qquad (\iff v_1^? = \epsilon \vee v_1^? = v^?, v_2^? = \epsilon \vee v_2^? = v^?) \\ &&|& \text{try\_table}~~I'{:}\Tlabel_I~~\X{bt}{:}\Tblocktype~~(c{:}\Tcatch_I)^\ast~~(\X{in}{:}\Tinstr_{I'})^\ast~~\text{end}~~\Tid^? \\ &&&\qquad \Rightarrow\quad \TRYTABLE~\X{bt}~c^\ast~\X{in}^\ast~~\END \qquad\qquad (\iff \Tid^? = \epsilon \vee \Tid^? = \Tlabel) \\ diff --git a/document/core/text/lexical.rst b/document/core/text/lexical.rst index 684688d49a..16e83dcd05 100644 --- a/document/core/text/lexical.rst +++ b/document/core/text/lexical.rst @@ -50,7 +50,7 @@ The character stream in the source text is divided, from left to right, into a s (\text{a} ~|~ \dots ~|~ \text{z})~\Tidchar^\ast \qquad (\iff~\mbox{occurring as a literal terminal in the grammar}) \\ \production{reserved} & \Treserved &::=& - (\Tidchar ~|~ \Tstring)^+ \\ + (\Tidchar ~|~ \Tstring ~|~ \text{,} ~|~ \text{;} ~|~ \text{[} ~|~ \text{]} ~|~ \text{\{} ~|~ \text{\}})^+ \\ \end{array} Tokens are formed from the input character stream according to the *longest match* rule. @@ -78,7 +78,7 @@ Any token that does not fall into any of the other categories is considered *res White Space ~~~~~~~~~~~ -*White space* is any sequence of literal space characters, formatting characters, or :ref:`comments `. +*White space* is any sequence of literal space characters, formatting characters, :ref:`comments `, or :ref:`annotations `. The allowed formatting characters correspond to a subset of the |ASCII|_ *format effectors*, namely, *horizontal tabulation* (:math:`\unicode{09}`), *line feed* (:math:`\unicode{0A}`), and *carriage return* (:math:`\unicode{0D}`). .. math:: @@ -127,3 +127,35 @@ The *look-ahead* restrictions on the productions for |Tblockchar| disambiguate t .. note:: Any formatting and control characters are allowed inside comments. + + +.. index:: ! annotation + single: text format; annotation +.. _text-annot: + +Annotations +~~~~~~~~~~~ + +An *annotation* is a bracketed token sequence headed by an *annotation id* of the form :math:`\text{@id}` or :math:`\text{@"..."}`. +No :ref:`space ` is allowed between the opening parenthesis and this id. +Annotations are intended to be used for third-party extensions; +they can appear anywhere in a program but are ignored by the WebAssembly semantics itself, which treats them as :ref:`white space `. + +Annotations can contain other parenthesized token sequences (including nested annotations), as long as they are well-nested. +:ref:`String literals ` and :ref:`comments ` occurring in an annotation must also be properly nested and closed. + +.. math:: + \begin{array}{llclll@{\qquad\qquad}l} + \production{annotation} & \Tannot &::=& + \text{(@}~\Tannotid ~(\Tspace ~|~ \Ttoken)^\ast~\text{)} \\ + \production{annotation identifier} & \Tannotid &::=& + \Tidchar^+ ~|~ \Tname \\ + \end{array} + +.. note:: + The annotation id is meant to be an identifier categorising the extension, and plays a role similar to the name of a :ref:`custom section `. + By convention, annotations corresponding to a custom section should use the custom section's name as an id. + + Implementations are expected to ignore annotations with ids that they do not recognize. + On the other hand, they may impose restrictions on annotations that they do recognize, e.g., requiring a specific structure by superimposing a more concrete grammar. + It is up to an implementation how it deals with errors in such annotations. diff --git a/document/core/text/modules.rst b/document/core/text/modules.rst index 1f35b89dc5..34e4e924f4 100644 --- a/document/core/text/modules.rst +++ b/document/core/text/modules.rst @@ -759,34 +759,33 @@ The definition of the initial :ref:`identifier context ` :math:`I` \begin{array}{@{}lcl@{\qquad\qquad}l} \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{)}) &=& - \{\ITABLES~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{memory}~\Tid^?~\dots~\text{)}) &=& - \{\IMEMS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{global}~\Tid^?~\dots~\text{)}) &=& - \{\IGLOBALS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{tag}~\Tid^?~\dots~\text{)}) &=& - \{\ITAGS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{elem}~\Tid^?~\dots~\text{)}) &=& - \{\IELEM~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{data}~\Tid^?~\dots~\text{)}) &=& - \{\IDATA~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{func}~\Tid^?~\dots~\text{)}~\text{)}) &=& - \{\IFUNCS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{table}~\Tid^?~\dots~\text{)}~\text{)}) &=& - \{\ITABLES~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{memory}~\Tid^?~\dots~\text{)}~\text{)}) &=& - \{\IMEMS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{global}~\Tid^?~\dots~\text{)}~\text{)}) &=& - \{\IGLOBALS~(\Tid^?)\} \\ - \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{tag}~\Tid^?~\dots~\text{)}~\text{)}) &=& - \{\ITAGS~(\Tid^?)\} \\ + \F{idc}(\text{(}~\text{type}~v^?{:}\Tid^?~\Tsubtype~\text{)}) &=& + \{\ITYPES~(v^?), \IFIELDS~\F{idf}(\Tsubtype), \ITYPEDEFS~\X{st}\} \\ + \F{idc}(\text{(}~\text{func}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\IFUNCS~(v^?)\} \\ + \F{idc}(\text{(}~\text{table}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\ITABLES~(v^?)\} \\ + \F{idc}(\text{(}~\text{memory}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\IMEMS~(v^?)\} \\ + \F{idc}(\text{(}~\text{global}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\IGLOBALS~(v^?)\} \\ + \F{idc}(\text{(}~\text{tag}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\ITAGS~(v^?)\} \\ + \F{idc}(\text{(}~\text{elem}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\IELEM~(v^?)\} \\ + \F{idc}(\text{(}~\text{data}~v^?{:}\Tid^?~\dots~\text{)}) &=& + \{\IDATA~(v^?)\} \\ + \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{func}~v^?{:}\Tid^?~\dots~\text{)}~\text{)}) &=& + \{\IFUNCS~(v^?)\} \\ + \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{table}~v^?{:}\Tid^?~\dots~\text{)}~\text{)}) &=& + \{\ITABLES~(v^?)\} \\ + \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{memory}~v^?{:}\Tid^?~\dots~\text{)}~\text{)}) &=& + \{\IMEMS~(v^?)\} \\ + \F{idc}(\text{(}~\text{import}~\dots~\text{(}~\text{global}~v^?{:}\Tid^?~\dots~\text{)}~\text{)}) &=& + \{\IGLOBALS~(v^?)\} \\ \F{idc}(\text{(}~\dots~\text{)}) &=& - \{\} \\[2ex] + \{\} + \\[2ex] \F{idf}(\text{(}~\text{sub}~\dots~\Tcomptype~\text{)}) &=& \F{idf}(\Tcomptype) \\ \F{idf}(\text{(}~\text{struct}~\X{Tfield}^\ast~\text{)}) &=& @@ -795,8 +794,8 @@ The definition of the initial :ref:`identifier context ` :math:`I` \epsilon \\ \F{idf}(\text{(}~\text{func}~\dots~\text{)}) &=& \epsilon \\ - \F{idf}(\text{(}~\text{field}~\Tid^?~\dots~\text{)}) &=& - \Tid^? \\ + \F{idf}(\text{(}~\text{field}~v^?{:}\Tid^?~\dots~\text{)}) &=& + v^? \\ \end{array} diff --git a/document/core/text/values.rst b/document/core/text/values.rst index 2b869d251a..80a142fa15 100644 --- a/document/core/text/values.rst +++ b/document/core/text/values.rst @@ -211,12 +211,13 @@ Identifiers ~~~~~~~~~~~ :ref:`Indices ` can be given in both numeric and symbolic form. -Symbolic *identifiers* that stand in lieu of indices start with :math:`\text{\$}`, followed by any sequence of printable |ASCII|_ characters that does not contain a space, quotation mark, comma, semicolon, or bracket. +Symbolic *identifiers* that stand in lieu of indices start with :math:`\text{\$}`, followed by eiter a sequence of printable |ASCII|_ characters that does not contain a space, quotation mark, comma, semicolon, or bracket, or by a quoted :ref:`name `. .. math:: \begin{array}{llclll@{\qquad}l} \production{identifier} & \Tid &::=& - \text{\$}~\Tidchar^+ \\ + \text{\$}~c^\ast{:}\Tidchar^+ &\Rightarrow& c^\ast \\ &&|& + \text{\$}~c^\ast{:}\Tname &\Rightarrow& c^\ast & (\iff |c^\ast| > 0) \\ \production{identifier character} & \Tidchar &::=& \text{0} ~~|~~ \dots ~~|~~ \text{9} \\ &&|& \text{A} ~~|~~ \dots ~~|~~ \text{Z} \\ &&|& @@ -246,6 +247,9 @@ Symbolic *identifiers* that stand in lieu of indices start with :math:`\text{\$} \text{\tilde{~~}} \\ \end{array} +.. note:: + The value of an identifier character is the Unicode codepoint denoting it. + .. _text-id-fresh: Conventions diff --git a/document/core/util/macros.def b/document/core/util/macros.def index a256d0b7aa..f2c6961250 100644 --- a/document/core/util/macros.def +++ b/document/core/util/macros.def @@ -871,6 +871,10 @@ .. |Tlinechar| mathdef:: \xref{text/lexical}{text-comment}{\T{linechar}} .. |Tblockchar| mathdef:: \xref{text/lexical}{text-comment}{\T{blockchar}} +.. |Tannot| mathdef:: \xref{text/lexical}{text-annot}{\T{annot}} +.. |Tannotid| mathdef:: \xref{text/lexical}{text-annot}{\T{annotid}} +.. |Tannottoken| mathdef:: \xref{text/lexical}{text-annot}{\T{annottoken}} + .. Values, non-terminals @@ -1198,6 +1202,8 @@ .. |allocdata| mathdef:: \xref{exec/modules}{alloc-data}{\F{allocdata}} .. |allocmodule| mathdef:: \xref{exec/modules}{alloc-module}{\F{allocmodule}} +.. |allocexn| mathdef:: \xref{exec/modules}{alloc-exception}{\F{allocexn}} + .. |growtable| mathdef:: \xref{exec/modules}{grow-table}{\F{growtable}} .. |growmem| mathdef:: \xref{exec/modules}{grow-mem}{\F{growmem}} @@ -1592,6 +1598,28 @@ .. |Bfieldnamesubsec| mathdef:: \xref{appendix/custom}{binary-fieldnamesec}{\B{fieldnamesubsec}} +.. Annotations +.. ----------- + +.. Custom annotations, non-terminals + +.. |Tcustomannot| mathdef:: \xref{appendix/custom}{text-customannot}{\T{customannot}} +.. |Tcustomplace| mathdef:: \xref{appendix/custom}{text-customannot}{\T{customplace}} +.. |Tsec| mathdef:: \xref{appendix/custom}{text-customannot}{\T{sec}} + + +.. Name annotations, non-terminals + +.. |Tnameannot| mathdef:: \xref{appendix/custom}{text-nameannot}{\T{nameannot}} +.. |Tmodulenameannot| mathdef:: \xref{appendix/custom}{text-modulenameannot}{\T{modulenameannot}} +.. |Tfuncnameannot| mathdef:: \xref{appendix/custom}{text-funcnameannot}{\T{funcnameannot}} +.. |Tparamnameannot| mathdef:: \xref{appendix/custom}{text-paramnameannot}{\T{paramnameannot}} +.. |Tlocalnameannot| mathdef:: \xref{appendix/custom}{text-localnameannot}{\T{localnameannot}} +.. |Ttypenameannot| mathdef:: \xref{appendix/custom}{text-typenameannot}{\T{typenameannot}} +.. |Tfieldnameannot| mathdef:: \xref{appendix/custom}{text-fieldnameannot}{\T{fieldnameannot}} +.. |Ttagnameannot| mathdef:: \xref{appendix/custom}{text-tagnameannot}{\T{tagnameannot}} + + .. Embedding .. --------- @@ -1601,3 +1629,5 @@ .. |error| mathdef:: \xref{appendix/embedding}{embed-error}{\X{error}} .. |ERROR| mathdef:: \xref{appendix/embedding}{embed-error}{\K{error}} +.. |exception| mathdef:: \xref{appendix/embedding}{embed-error}{\X{exception}} +.. |ETHROW| mathdef:: \xref{appendix/embedding}{embed-error}{\K{THROW}} diff --git a/document/core/valid/conventions.rst b/document/core/valid/conventions.rst index 6dc6950147..a2a555c904 100644 --- a/document/core/valid/conventions.rst +++ b/document/core/valid/conventions.rst @@ -308,7 +308,7 @@ In addition to field access written :math:`C.\K{field}` the following notation i 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. +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 types in :math:`C.\CTYPES` themselves. .. math:: \begin{array}{@{}lcll@{}} diff --git a/document/core/valid/instructions.rst b/document/core/valid/instructions.rst index 8088049e4f..2fa7982605 100644 --- a/document/core/valid/instructions.rst +++ b/document/core/valid/instructions.rst @@ -1660,7 +1660,7 @@ Memory Instructions \frac{ C.\CMEMS[x] = \memtype \qquad - 2^{\memarg.\ALIGN} < N/8 + 2^{\memarg.\ALIGN} \leq N/8 \qquad \laneidx < 128/N }{ @@ -1685,7 +1685,7 @@ Memory Instructions \frac{ C.\CMEMS[x] = \memtype \qquad - 2^{\memarg.\ALIGN} < N/8 + 2^{\memarg.\ALIGN} \leq N/8 \qquad \laneidx < 128/N }{ diff --git a/document/core/valid/matching.rst b/document/core/valid/matching.rst index e5575b5ad1..af9c161edf 100644 --- a/document/core/valid/matching.rst +++ b/document/core/valid/matching.rst @@ -456,7 +456,7 @@ A :ref:`defined type ` :math:`\deftype_1` matches a type :math:` .. 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. + as used in the premise of the former rule above. .. index:: limits diff --git a/document/core/valid/types.rst b/document/core/valid/types.rst index e6ab987b3c..d4324f72bb 100644 --- a/document/core/valid/types.rst +++ b/document/core/valid/types.rst @@ -51,7 +51,8 @@ Vector Types Heap Types ~~~~~~~~~~ -Concrete :ref:`Heap types ` are only valid when the :ref:`type index ` is. +Concrete :ref:`heap types ` are only valid when the :ref:`type index ` is, +while abstract ones are vacuously valid. :math:`\absheaptype` .................... diff --git a/document/index.html b/document/index.html index a5766fac4b..f7661d8bec 100644 --- a/document/index.html +++ b/document/index.html @@ -62,6 +62,19 @@

Embedder specifications

+

Metadata specifications

+ +

Define the format and semantics of extra information attached to a WebAssembly module

+ + +

Legacy Extensions

Define extensions that are deprecated, but may still be in use.

@@ -69,15 +82,23 @@

Legacy Extensions

-

- Source for these documents is available + Source for all documents is available here.

diff --git a/document/js-api/index.bs b/document/js-api/index.bs index fb60d30b2a..b32f5b0297 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -62,8 +62,6 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT 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 @@ -89,6 +87,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: ref.func text: ref.host text: ref.extern + text: ref.exn text: function index; url: syntax/modules.html#syntax-funcidx text: function instance; url: exec/runtime.html#function-instances text: store_init; url: appendix/embedding.html#embed-store-init @@ -128,11 +127,18 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: memory address; url: exec/runtime.html#syntax-memaddr text: global address; url: exec/runtime.html#syntax-globaladdr text: tag address; url: exec/runtime.html#syntax-tagaddr + text: tag type; url: syntax/types.html#syntax-tagtype text: struct address; url: exec/runtime.html#syntax-structaddr text: array address; url: exec/runtime.html#syntax-arrayaddr + text: exception address; url: exec/runtime.html#syntax-exnaddr 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 + text: tag_alloc; url: appendix/embedding.html#embed-tag-alloc + text: tag_type; url: appendix/embedding.html#embed-tag-type + text: exn_alloc; url: appendix/embedding.html#embed-exn-alloc + text: exn_tag; url: appendix/embedding.html#embed-exn-tag + text: exn_read; url: appendix/embedding.html#embed-exn-read url: syntax/types.html#syntax-numtype text: i32 text: i64 @@ -184,6 +190,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df 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: throw_ref; url: exec/instructions.html#exec-throw-ref text: current frame; url: exec/conventions.html#exec-notation-textual text: module; for: frame; url: exec/runtime.html#syntax-frame text: memaddrs; for: moduleinst; url: exec/runtime.html#syntax-moduleinst @@ -283,8 +290,10 @@ Each [=agent=] is associated with the following [=ordered map=]s: * 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 Tag object cache, mapping [=tag addresses=] to {{Tag}} objects. + * The Exception object cache, mapping [=exception address=]es to {{Exception}} objects. * The Host value cache, mapping [=host address=]es to values. +

The WebAssembly Namespace

@@ -303,6 +312,8 @@ namespace WebAssembly {
 
     Promise<Instance> instantiate(
         Module moduleObject, optional object importObject);
+
+    readonly attribute Tag JSTag;
 };
 
@@ -382,7 +393,7 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje 1. Let |index| be the number of external functions in |imports|. This value |index| is known as the index of the host function |funcaddr|. 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 |externtype| is of the form [=external-type/global=] mut |valtype|, 1. If |v| [=implements=] {{Global}}, 1. Let |globaladdr| be |v|.\[[Global]]. 1. Otherwise, @@ -398,16 +409,16 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. 1. Let |externglobal| be [=external value|global=] |globaladdr|. 1. [=list/Append=] |externglobal| to |imports|. - 1. If |externtype| is of the form [=mem=] memtype, + 1. If |externtype| is of the form [=external-type/mem=] memtype, 1. If |v| does not [=implement=] {{Memory}}, throw a {{LinkError}} exception. 1. Let |externmem| be the [=external value=] [=external value|mem=] |v|.\[[Memory]]. 1. [=list/Append=] |externmem| to |imports|. - 1. If |externtype| is of the form [=table=] tabletype, + 1. If |externtype| is of the form [=external-type/table=] tabletype, 1. If |v| does not [=implement=] {{Table}}, throw a {{LinkError}} exception. 1. Let |tableaddr| be |v|.\[[Table]]. 1. Let |externtable| be the [=external value=] [=external value|table=] |tableaddr|. 1. [=list/Append=] |externtable| to |imports|. - 1. If |externtype| is of the form [=externtype/tag=] |attribute| functype, + 1. If |externtype| is of the form [=external-type/tag=] |attribute| functype, 1. Assert: |attribute| is [=tagtype/attribute/exception=]. 1. If |v| does not [=implement=] {{Tag}}, throw a {{LinkError}} exception. 1. Let |tagaddr| be |v|.\[[Address]]. @@ -431,22 +442,22 @@ The verification of WebAssembly type requirements is deferred to the 1. Let [=external value|func=] |funcaddr| be |externval|. 1. Let |func| be the result of creating [=a new Exported Function=] from |funcaddr|. 1. Let |value| be |func|. - 1. If |externtype| is of the form [=global=] mut globaltype, + 1. If |externtype| is of the form [=external-type/global=] mut globaltype, 1. Assert: |externval| is of the form [=external value|global=] |globaladdr|. 1. Let [=external value|global=] |globaladdr| be |externval|. 1. Let |global| be [=create a global object|a new Global object=] created from |globaladdr|. 1. Let |value| be |global|. - 1. If |externtype| is of the form [=mem=] memtype, + 1. If |externtype| is of the form [=external-type/mem=] memtype, 1. Assert: |externval| is of the form [=external value|mem=] |memaddr|. 1. Let [=external value|mem=] |memaddr| be |externval|. 1. Let |memory| be [=create a memory object|a new Memory object=] created from |memaddr|. 1. Let |value| be |memory|. - 1. If |externtype| is of the form [=table=] tabletype, + 1. If |externtype| is of the form [=external-type/table=] tabletype, 1. Assert: |externval| is of the form [=external value|table=] |tableaddr|. 1. Let [=external value|table=] |tableaddr| be |externval|. 1. Let |table| be [=create a Table object|a new Table object=] created from |tableaddr|. 1. Let |value| be |table|. - 1. If |externtype| is of the form [=externtype/tag=] |attribute| functype, + 1. If |externtype| is of the form [=external-type/tag=] |attribute| functype, 1. Assert: |attribute| is [=tagtype/attribute/exception=]. 1. Assert: |externval| is of the form [=external value/tag=] |tagaddr|. 1. Let [=external value/tag=] |tagaddr| be |externval|. @@ -529,6 +540,11 @@ The verification of WebAssembly type requirements is deferred to the Note: A follow-on streaming API is documented in the WebAssembly Web API. +The getter of the JSTag attribute of the {{WebAssembly}} Namespace, when invoked, performs the following steps: + 1. Let |JSTagAddr| be the result of [=get the JavaScript exception tag|getting the JavaScript exception tag=]. + 1. Let |JSTagObject| be the result of [=create a Tag object|creating a Tag object=] from |JSTagAddr|. + 1. Return |JSTagObject|. +

Modules

@@ -564,10 +580,10 @@ interface Module {
 
The string value of the extern type |type| is * "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 - * "tag" if |type| is of the form [=externtype/tag=] tag + * "table" if |type| is of the form [=external-type/table=] tabletype + * "memory" if |type| is of the form [=external-type/mem=] memtype + * "global" if |type| is of the form [=external-type/global=] globaltype + * "tag" if |type| is of the form [=external-type/tag=] tag
@@ -800,7 +816,8 @@ Immediately after a WebAssembly [=memory.grow=] instruction executes, perform th {{ArrayBuffer}} objects returned by a {{Memory}} object must have a size that is a multiple of a WebAssembly [=page size=] (the constant 65536). For this reason [=HostResizeArrayBuffer=] is redefined as follows.
- The abstract operation [=HostResizeArrayBuffer=] takes arguments |buffer| (an {{ArrayBuffer}}) and |newLength|. It performs the following steps when called. + + The abstract operation [=HostResizeArrayBuffer=] takes arguments |buffer| (an {{ArrayBuffer}}) and |newLength|. It performs the following steps when called. 1. If |buffer|.\[[ArrayBufferDetachKey]] is "WebAssembly.Memory", 1. Let |map| be the [=surrounding agent=]'s associated [=Memory object cache=]. @@ -873,7 +890,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address The Table(|descriptor|, |value|) constructor, when invoked, performs the following steps: 1. Let |elementType| be [=ToValueType=](|descriptor|["element"]). 1. If |elementType| is not a [=reftype=], - 1. [=Throw=] a {{TypeError}} exception. + 1. Throw a {{TypeError}} exception. 1. Let |initial| be |descriptor|["initial"]. 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be |descriptor|["maximum"]; otherwise, let |maximum| be empty. 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. @@ -922,7 +939,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (limits, |elementType|) be [=table_type=](|store|, |tableaddr|). 1. If |elementType| is [=exnref=], - 1. [=Throw=] a {{TypeError}} exception. + 1. Throw a {{TypeError}} exception. 1. Let |result| be [=table_read=](|store|, |tableaddr|, |index|). 1. If |result| is [=error=], throw a {{RangeError}} exception. 1. Return [=ToJSValue=](|result|). @@ -934,7 +951,7 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (limits, |elementType|) be [=table_type=](|store|, |tableaddr|). 1. If |elementType| is [=exnref=], - 1. [=Throw=] a {{TypeError}} exception. + 1. Throw a {{TypeError}} exception. 1. If |value| is missing, 1. Let |ref| be [=DefaultValue=](|elementType|). 1. If |ref| is [=error=], throw a {{TypeError}} exception. @@ -1124,15 +1141,17 @@ This slot holds a [=function address=] relative to the [=surrounding agent=]'s [ 1. [=list/Append=] [=ToWebAssemblyValue=](|arg|, |t|) to |args|. 1. Set |i| to |i| + 1. 1. Let (|store|, |ret|) be the result of [=func_invoke=](|store|, |funcaddr|, |args|). - 1. Note: The expectation is that [=func_invoke=] will be updated to return (|store|, val* | [=error=] | (exception |exntag| |payload| |opaqueData|)). 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. 1. If |ret| is [=error=], throw an exception. This exception should be a WebAssembly {{RuntimeError}} exception, unless otherwise indicated by the WebAssembly error mapping. - 1. If |ret| is exception |exntag| |payload| |opaqueData|, then - 1. If |opaqueData| is not [=ref.null=] [=externref=], - 1. Let « [=ref.extern=] |externaddr| » be |opaqueData|. - 1. Throw the result of [=retrieving an extern value=] from |externaddr|. - 1. Let |exception| be [=create an Exception object|a new Exception=] for |exntag| and |payload|. - 1. Throw |exception|. + 1. If |ret| is [=THROW=] [=ref.exn=] |exnaddr|, then + 1. Let |tagaddr| be [=exn_tag=](|store|, |exnaddr|). + 1. Let |payload| be [=exn_read=](|store|, |exnaddr|). + 1. Let |jsTagAddr| be the result of [=get the JavaScript exception tag |getting the JavaScript exception tag=]. + 1. If |tagaddr| is equal to |jsTagAddr|, + 1. Throw the result of [=retrieving a host value=] from |payload|[0]. + 1. Otherwise, + 1. Let |exception| be [=create an Exception object|a new Exception=] created from |exnaddr|. + 1. Throw |exception|. 1. Let |outArity| be the [=list/size=] of |ret|. 1. If |outArity| is 0, return undefined. 1. Otherwise, if |outArity| is 1, return [=ToJSValue=](|ret|[0]). @@ -1160,7 +1179,7 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not 1. If |resultsSize| is 0, return « ». 1. Otherwise, if |resultsSize| is 1, return « [=?=] [=ToWebAssemblyValue=](|ret|, |results|[0]) ». 1. Otherwise, - 1. Let |method| be [=?=] [$GetMethod$](|ret|, {{@@iterator}}). + 1. Let |method| be [=?=] [$GetMethod$](|ret|, {{%Symbol.iterator%}}). 1. If |method| is undefined, [=throw=] a {{TypeError}}. 1. Let |values| be [=?=] [$IteratorToList$]([=?=] [$GetIteratorFromMethod$](|ret|, |method|)). 1. Let |wasmValues| be a new, empty [=list=]. @@ -1184,18 +1203,18 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not 1. [=Clean up after running a callback=] with |stored settings|. 1. [=Clean up after running script=] with |relevant settings|. 1. Assert: |result|.\[[Type]] is throw or normal. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. If |result|.\[[Type]] is throw, then: 1. Let |v| be |result|.\[[Value]]. 1. If |v| [=implements=] {{Exception}}, - 1. Let |type| be |v|.\[[Type]]. - 1. Let |payload| be |v|.\[[Payload]]. + 1. Let |address| be |v|.\[[Address]]. 1. Otherwise, - 1. Let |type| be the [=JavaScript exception tag=]. - 1. Let |payload| be « ». - 1. Let |opaqueData| be [=ToWebAssemblyValue=](|v|, [=externref=]) - 1. [=WebAssembly/Throw=] with |type|, |payload| and |opaqueData|. + 1. Let |type| be the result of [=get the JavaScript exception tag |getting the JavaScript exception tag=]. + 1. Let |payload| be [=!=] [=ToWebAssemblyValue=](|v|, [=externref=]). + 1. Let (|store|, |address|) be [=exn_alloc=](|store|, |type|, « |payload| »). + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. Execute the WebAssembly instructions ([=ref.exn=] |address|) ([=throw_ref=]). 1. Otherwise, return |result|.\[[Value]]. - 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (|store|, |funcaddr|) be [=func_alloc=](|store|, |functype|, |hostfunc|). 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. 1. Return |funcaddr|. @@ -1209,7 +1228,7 @@ The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a Jav 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. If |w| is of the form [=i32.const=] |i32|, 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|, @@ -1220,7 +1239,7 @@ 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|). @@ -1228,7 +1247,7 @@ The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a Jav 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.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|). +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. @@ -1309,6 +1328,66 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va
+ +

Tags

+ +The tag_alloc(|store|, |parameters|) algorithm creates a new [=tag address=] for |parameters| in |store| and returns the updated store and the [=tag address=]. + +

Tag types

+ +
+dictionary TagType {
+  required sequence<ValueType> parameters;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
+interface Tag {
+  constructor(TagType type);
+};
+
+ +A {{Tag}} value represents an exception tag. + +
+ +To initialize a Tag object |tag| from a [=tag address=] |tagAddress|, perform the following steps: + +1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. +1. Assert: |map|[|tagAddress|] doesn't [=map/exist=]. +1. Set |tag|.\[[Address]] to |tagAddress|. +1. [=map/Set=] |map|[|tagAddress|] to |tag|. + +
+ +
+ +To create a Tag object from a [=tag address=] |tagAddress|, perform the following steps: + +1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. +1. If |map|[|tagAddress|] [=map/exists=], + 1. Return |map|[|tagAddress|]. +1. Let |tag| be a [=new=] {{Tag}}. +1. [=initialize a Tag object|Initialize=] |tag| from |tagAddress|. +1. Return |tag|. + +
+ +
+ +The new Tag(|type|) constructor steps are: + +1. Let |parameters| be |type|["parameters"]. +1. Let |wasmParameters| be «». +1. [=list/iterate|For each=] |type| of |parameters|, + 1. [=list/Append=] [=ToValueType=](|type|) to |wasmParameters|. +1. Let |store| be the current agent's [=associated store=]. +1. Let (|store|, |tagAddress|) be [=tag_alloc=](|store|, |wasmParameters|). +1. Set the current agent's [=associated store=] to |store|. +1. [=initialize a Tag object|Initialize=] **this** from |tagAddress|. + +
+ +

Garbage Collected Objects

A WebAssembly struct or array is made available in JavaScript as an Exported GC Object. @@ -1412,86 +1491,9 @@ The internal methods of an [=Exported GC Object=] use the following implementati 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|. - - - -

Tags

- -The tag_alloc(|store|, |parameters|) algorithm creates a new [=tag address=] for |parameters| in |store| and returns the updated store and the [=tag address=]. - -The tag_parameters(|store|, |tagAddress|) algorithm returns the [=list=] of types for |tagAddress| in |store|. - -

Exception types

- -
-dictionary TagType {
-  required sequence<ValueType> parameters;
-};
-
-[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
-interface Tag {
-  constructor(TagType type);
-  TagType type();
-};
-
- -A {{Tag}} value represents a type of exception. - -
- -To initialize a Tag object |tag| from a [=tag address=] |tagAddress|, perform the following steps: - -1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. -1. Assert: |map|[|tagAddress|] doesn't [=map/exist=]. -1. Set |tag|.\[[Address]] to |tagAddress|. -1. [=map/Set=] |map|[|tagAddress|] to |tag|. - -
- -
- -To create a Tag object from a [=tag address=] |tagAddress|, perform the following steps: - -1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. -1. If |map|[|tagAddress|] [=map/exists=], - 1. Return |map|[|tagAddress|]. -1. Let |tag| be a [=new=] {{Tag}}. -1. [=initialize a Tag object|Initialize=] |tag| from |tagAddress|. -1. Return |tag|. - -
- -
- -The new Tag(|type|) constructor steps are: - -1. Let |parameters| be |type|["parameters"]. -1. Let |wasmParameters| be «». -1. [=list/iterate|For each=] |type| of |parameters|, - 1. [=list/Append=] [=ToValueType=](|type|) to |wasmParameters|. -1. Let |store| be the current agent's [=associated store=]. -1. Let (|store|, |tagAddress|) be [=tag_alloc=](|store|, |wasmParameters|). -1. Set the current agent's [=associated store=] to |store|. -1. [=initialize a Tag object|Initialize=] **this** from |tagAddress|. - -
- -
- -The type() method steps are: - -1. Let |store| be the [=surrounding agent=]'s [=associated store=]. -1. Let |parameters| be [=tag_parameters=](|store|, **this**.\[[Address]]). -1. Let |idlParameters| be «». -1. [=list/iterate|For each=] |type| of |parameters|, - 1. [=list/Append=] [$FromValueType$](|type|) to |idlParameters|. -1. Return «[ "{{TagType/parameters}}" → |idlParameters| ]». - -Advisement: This method is only expected to be implemented or shipped when both this proposal and the Type Reflection proposal are implemented or shipped (respectively). -
-

Runtime exceptions

+

Exceptions

 dictionary ExceptionOptions {
@@ -1501,7 +1503,7 @@ dictionary ExceptionOptions {
 [LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
 interface Exception {
   constructor(Tag exceptionTag, sequence<any> payload, optional ExceptionOptions options = {});
-  any getArg(Tag exceptionTag, [EnforceRange] unsigned long index);
+  any getArg([EnforceRange] unsigned long index);
   boolean is(Tag exceptionTag);
   readonly attribute (DOMString or undefined) stack;
 };
@@ -1511,19 +1513,32 @@ An {{Exception}} value represents an exception.
 
 
-To create an Exception object from a [=tag address=] |tagAddress| and a [=list=] of -WebAssembly values |payload|, perform the following steps: +To initialize an Exception object |exn| from an [=Exception address=] |exnAddress|, perform the following steps: +1. Let |map| be the [=surrounding agent=]'s associated [=Exception object cache=]. +1. Assert: |map|[|exnAddress|] doesn't [=map/exist=]. +1. Set |exn|.\[[Address]] to |exnAddress|. +1. [=map/Set=] |map|[|exnAddress|] to |exn|. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. -1. Let |types| be [=tag_parameters=](|store|, |tagAddress|). -1. Assert: |types|'s [=list/size=] is |payload|'s [=list/size=]. -1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly, - 1. Assert: |value|'s type matches |resultType|. -1. Let |exception| be a [=new=] {{Exception}}. -1. Set |exception|.\[[Type]] to |tagAddress|. -1. Set |exception|.\[[Payload]] to |payload|. -1. Set |exception|.\[[Stack]] to undefined. -1. Return |exception|. +1. Let |tagaddr| be [=exn_tag=](|store|, |exnAddress|). +1. Let |payload| be [=exn_read=](|store|, |exnAddress|). +1. Set |exn|.\[[Type]] to |tagaddr|. +1. Set |exn|.\[[Payload]] to |payload|. +1. Set |exn|.\[[Stack]] to undefined. + +
+ +
+ +To create an Exception object from a [=exception address=] |exnAddress|, perform the following steps: + +1. Let |map| be the [=surrounding agent=]'s associated [=Exception object cache=]. +1. If |map|[|exnAddress|] [=map/exists=], + 1. Return |map|[|exnAddress|]. +1. Let |exn| be a [=new=] {{Exception}}. +1. [=initialize an Exception object|Initialize=] |exn| from |exnAddress|. +1. Return |exn|. +
@@ -1533,31 +1548,40 @@ The new Exception(|exceptionTag|, |payload|, |options|) constructor steps are: +1. Let |JSTagAddr| be the result of [=get the JavaScript exception tag|getting the JavaScript exception tag=]. +1. If |exceptionTag|.\[[Address]] is equal to |JSTagAddr|, + 1. Throw a {{TypeError}}. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. -1. Let |types| be [=tag_parameters=](|store|, |exceptionTag|.\[[Address]]). +1. Let [|types|] → [] be [=tag_type=](|store|, |exceptionTag|.\[[Address]]). 1. If |types|'s [=list/size=] is not |payload|'s [=list/size=], 1. Throw a {{TypeError}}. 1. Let |wasmPayload| be « ». 1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly, - 1. [=list/Append=] ? [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmPayload|. -1. Set **this**.\[[Type]] to |exceptionTag|.\[[Address]]. -1. Set **this**.\[[Payload]] to |wasmPayload|. + 1. If |resultType| is [=v128=] or [=exnref=], + 1. Throw a {{TypeError}}. + 1. [=list/Append=] [=?=] [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmPayload|. +1. Let (|store|, |exceptionAddr|) be [=exn_alloc=](|store|, |exceptionTag|.\[[Address]], |wasmPayload|) +1. Set the [=surrounding agent=]'s [=associated store=] to |store|. +1. [=initialize an Exception object|Initialize=] **this** from |exceptionAddr|. 1. If |options|["traceStack"] is true, 1. Set **this**.\[[Stack]] to either a {{DOMString}} representation of the current call stack or undefined. -1. Otherwise, - 1. Set **this**.\[[Stack]] to undefined. +
-The getArg(|exceptionTag|, |index|) method steps are: +The getArg(|index|) method steps are: -1. If **this**.\[[Type]] is not equal to |exceptionTag|.\[[Address]], - 1. Throw a {{TypeError}}. -1. Let |payload| be **this**.\[[Payload]]. +1. Let |store| be the [=surrounding agent=]'s [=associated store=]. +1. Let |tagaddr| be [=exn_tag=](|store|, **this**.\[[Address]]). +1. Let |payload| be [=exn_read=](|store|, **this**.\[[Address]]). +1. Assert: |tagaddr| is equal to **this**.\[[Type]]. 1. If |index| ≥ |payload|'s [=list/size=], 1. Throw a {{RangeError}}. +1. Let [|types|] → [] be [=tag_type=](|store|, |tagaddr|). +1. If |types|[|index|] is [=v128=] or [=exnref=], + 1. Throw a {{TypeError}}. 1. Return [=ToJSValue=](|payload|[|index|]).
@@ -1582,20 +1606,22 @@ The stack getter steps are:

JavaScript exceptions

-The JavaScript exception tag is a [=tag address=] reserved by this -specification to distinguish exceptions originating from JavaScript. +The JavaScript exception tag is a [=tag address=] associated with +the surrounding agent. It is allocated in the agent's [=associated store=] on +first use and cached. It always has the [=tag type=] « [=externref=] » → « ». -For any [=associated store=] |store|, the result of -[=tag_parameters=](|store|, [=JavaScript exception tag=]) must be « ».
-To throw with a [=tag address=] |type|, a matching [=list=] of WebAssembly values |payload|, and an [=externref=] |opaqueData|, perform the following steps: - -1. Unwind the stack until reaching the *catching try block* given |type|. -1. Invoke the catch block with |payload| and |opaqueData|. +To get the JavaScript exception tag, perform the following steps: -Note: This algorithm is expected to be moved into the core specification. + 1. If the [=surrounding agent=]'s associated [=JavaScript exception tag=] has been initialized, + 1. return the [=surrounding agent=]'s associated [=JavaScript exception tag=] + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let (|store|, |tagAddress|) be [=tag_alloc=](|store|, « [=externref=] » → « »). + 1. Set the current agent's [=associated store=] to |store|. + 1. Set the current agent's associated [=JavaScript exception tag=] to |tagAddress|. + 1. return |tagAddress|.
@@ -1610,7 +1636,7 @@ When the [=namespace object=] for the {{WebAssembly}} namespace is [=create a na 1. Let |namespaceObject| be the [=namespace object=]. 1. [=list/iterate|For each=] |error| of « "CompileError", "LinkError", "RuntimeError" », 1. Let |constructor| be a new object, implementing the [=NativeError Object Structure=], with NativeError set to |error|. - 1. [=!=] [$CreateMethodProperty$](|namespaceObject|, |error|, |constructor|). + 1. [=!=] [$DefineMethodProperty$](|namespaceObject|, |error|, |constructor|, false). diff --git a/document/legacy/exceptions/LICENSE b/document/legacy/exceptions/core/LICENSE similarity index 100% rename from document/legacy/exceptions/LICENSE rename to document/legacy/exceptions/core/LICENSE diff --git a/document/legacy/exceptions/Makefile b/document/legacy/exceptions/core/Makefile similarity index 100% rename from document/legacy/exceptions/Makefile rename to document/legacy/exceptions/core/Makefile diff --git a/document/legacy/exceptions/README.md b/document/legacy/exceptions/core/README.md similarity index 100% rename from document/legacy/exceptions/README.md rename to document/legacy/exceptions/core/README.md diff --git a/document/legacy/exceptions/appendix/index-instructions.py b/document/legacy/exceptions/core/appendix/index-instructions.py similarity index 100% rename from document/legacy/exceptions/appendix/index-instructions.py rename to document/legacy/exceptions/core/appendix/index-instructions.py diff --git a/document/legacy/exceptions/binary.rst b/document/legacy/exceptions/core/binary.rst similarity index 100% rename from document/legacy/exceptions/binary.rst rename to document/legacy/exceptions/core/binary.rst diff --git a/document/legacy/exceptions/conf.py b/document/legacy/exceptions/core/conf.py similarity index 100% rename from document/legacy/exceptions/conf.py rename to document/legacy/exceptions/core/conf.py diff --git a/document/legacy/exceptions/exec.rst b/document/legacy/exceptions/core/exec.rst similarity index 100% rename from document/legacy/exceptions/exec.rst rename to document/legacy/exceptions/core/exec.rst diff --git a/document/legacy/exceptions/index.rst b/document/legacy/exceptions/core/index.rst similarity index 100% rename from document/legacy/exceptions/index.rst rename to document/legacy/exceptions/core/index.rst diff --git a/document/legacy/exceptions/intro.rst b/document/legacy/exceptions/core/intro.rst similarity index 100% rename from document/legacy/exceptions/intro.rst rename to document/legacy/exceptions/core/intro.rst diff --git a/document/legacy/exceptions/static/custom.css b/document/legacy/exceptions/core/static/custom.css similarity index 100% rename from document/legacy/exceptions/static/custom.css rename to document/legacy/exceptions/core/static/custom.css diff --git a/document/legacy/exceptions/static/webassembly.png b/document/legacy/exceptions/core/static/webassembly.png similarity index 100% rename from document/legacy/exceptions/static/webassembly.png rename to document/legacy/exceptions/core/static/webassembly.png diff --git a/document/legacy/exceptions/syntax.rst b/document/legacy/exceptions/core/syntax.rst similarity index 100% rename from document/legacy/exceptions/syntax.rst rename to document/legacy/exceptions/core/syntax.rst diff --git a/document/legacy/exceptions/text.rst b/document/legacy/exceptions/core/text.rst similarity index 100% rename from document/legacy/exceptions/text.rst rename to document/legacy/exceptions/core/text.rst diff --git a/document/legacy/exceptions/util/macros.def b/document/legacy/exceptions/core/util/macros.def similarity index 100% rename from document/legacy/exceptions/util/macros.def rename to document/legacy/exceptions/core/util/macros.def diff --git a/document/legacy/exceptions/util/mathdef.py b/document/legacy/exceptions/core/util/mathdef.py similarity index 100% rename from document/legacy/exceptions/util/mathdef.py rename to document/legacy/exceptions/core/util/mathdef.py diff --git a/document/legacy/exceptions/util/pseudo-lexer.py b/document/legacy/exceptions/core/util/pseudo-lexer.py similarity index 100% rename from document/legacy/exceptions/util/pseudo-lexer.py rename to document/legacy/exceptions/core/util/pseudo-lexer.py diff --git a/document/legacy/exceptions/valid.rst b/document/legacy/exceptions/core/valid.rst similarity index 100% rename from document/legacy/exceptions/valid.rst rename to document/legacy/exceptions/core/valid.rst diff --git a/document/legacy/exceptions/js-api/Makefile b/document/legacy/exceptions/js-api/Makefile new file mode 100644 index 0000000000..84ba5d3abd --- /dev/null +++ b/document/legacy/exceptions/js-api/Makefile @@ -0,0 +1,44 @@ +BUILDDIR = _build +STATICDIR = _static +DOWNLOADDIR = _download +NAME = WebAssembly + +.PHONY: all +all: + mkdir -p $(BUILDDIR)/html + bikeshed spec index.bs $(BUILDDIR)/html/index.html + @echo "Build finished. The HTML pages are in `pwd`/$(BUILDDIR)/html." + +.PHONY: publish +publish: + (cd ..; make publish-js-api) + +.PHONY: clean +clean: + rm -rf $(BUILDDIR) + rm -rf $(STATICDIR) + +.PHONY: diff +diff: all + @echo "Downloading the old single-file html spec..." + curl `grep "^TR" index.bs | cut -d' ' -f2` -o $(BUILDDIR)/html/old.html + @echo "Done." + @echo "Diffing new against old..." + perl ../util/htmldiff.pl $(BUILDDIR)/html/old.html $(BUILDDIR)/html/index.html $(BUILDDIR)/html/diff.html + @echo "Done. The diff is at $(BUILDDIR)/html/diff.html" + +.PHONY: WD-tar +WD-tar: + bikeshed echidna --just-tar index.bs $(BUILDDIR)/html/index.html + mv test.tar $(BUILDDIR)/WD.tar + @echo "Built $(BUILDDIR)/WD.tar." + +.PHONY: WD-echidna +WD-echidna: + @if [ -z $(W3C_USERNAME) ] || \ + [ -z $(W3C_PASSWORD) ] || \ + [ -z $(DECISION_URL) ] ; then \ + echo "Must provide W3C_USERNAME, W3C_PASSWORD, and DECISION_URL environment variables"; \ + exit 1; \ + fi + bikeshed echidna index.bs --u $(W3C_USERNAME) --p $(W3C_PASSWORD) --d $(DECISION_URL) diff --git a/document/legacy/exceptions/js-api/index.bs b/document/legacy/exceptions/js-api/index.bs new file mode 100644 index 0000000000..e7db140740 --- /dev/null +++ b/document/legacy/exceptions/js-api/index.bs @@ -0,0 +1,1450 @@ + + +
+{
+  "WEBASSEMBLY": {
+    "href": "https://webassembly.github.io/spec/core/",
+    "title": "WebAssembly Core Specification",
+    "publisher": "W3C WebAssembly Community Group",
+    "status": "Draft"
+  }
+}
+
+ +
+urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT
+    type: interface; for: ECMAScript
+        text: ArrayBuffer; url: sec-arraybuffer-objects
+    type: exception; for: ECMAScript
+        text: Error; url: sec-error-objects
+        text: NativeError; url: sec-nativeerror-constructors
+        text: TypeError; url: sec-native-error-types-used-in-this-standard-typeerror
+        text: RangeError; url: sec-native-error-types-used-in-this-standard-rangeerror
+    type: dfn
+        url: sec-returnifabrupt-shorthands
+            text: !
+            text: ?
+        text: Type; url: sec-ecmascript-data-types-and-values
+        text: current Realm; url: current-realm
+        text: Built-in Function Objects; url: sec-built-in-function-objects
+        text: NativeError Object Structure; url: sec-nativeerror-object-structure
+        text: 𝔽; url: #𝔽
+        text: ℤ; url: #ℤ
+urlPrefix: https://webassembly.github.io/exception-handling/core/; spec: WebAssembly; type: dfn
+    url: valid/modules.html#valid-module
+        text: valid
+        text: WebAssembly module validation
+    text: module grammar; url: binary/modules.html#binary-module
+    text: custom section; url: binary/modules.html#custom-section
+    text: customsec; url: binary/modules.html#binary-customsec
+    text: memory instance; url: exec/runtime.html#memory-instances
+    text: table instance; url: exec/runtime.html#table-instances
+    text: global instance; url: exec/runtime.html#global-instances
+    text: trap; url: exec/runtime.html#syntax-trap
+    url: exec/runtime.html#values
+        text: WebAssembly value
+        text: i64.const
+        text: i32.const
+        text: f32.const
+        text: f64.const
+        text: v128.const
+        text: ref.null
+        text: ref.func
+        text: ref.extern
+    text: function index; url: syntax/modules.html#syntax-funcidx
+    text: function instance; url: exec/runtime.html#function-instances
+    text: store_init; url: appendix/embedding.html#embed-store-init
+    text: module_decode; url: appendix/embedding.html#embed-module-decode
+    text: module_validate; url: appendix/embedding.html#embed-module-validate
+    text: module_instantiate; url: appendix/embedding.html#embed-module-instantiate
+    text: module_imports; url: appendix/embedding.html#embed-module-imports
+    text: module_exports; url: appendix/embedding.html#embed-module-exports
+    text: instance_export; url: appendix/embedding.html#embed-instance-export
+    text: func_alloc; url: appendix/embedding.html#embed-func-alloc
+    text: func_type; url: appendix/embedding.html#embed-func-type
+    text: func_invoke; url: appendix/embedding.html#embed-func-invoke
+    text: table_alloc; url: appendix/embedding.html#embed-table-alloc
+    text: table_type; url: appendix/embedding.html#embed-table-type
+    text: table_read; url: appendix/embedding.html#embed-table-read
+    text: table_write; url: appendix/embedding.html#embed-table-write
+    text: table_size; url: appendix/embedding.html#embed-table-size
+    text: table_grow; url: appendix/embedding.html#embed-table-grow
+    text: mem_alloc; url: appendix/embedding.html#embed-mem-alloc
+    text: mem_type; url: appendix/embedding.html#embed-mem-type
+    text: mem_read; url: appendix/embedding.html#embed-mem-read
+    text: mem_write; url: appendix/embedding.html#embed-mem-write
+    text: mem_size; url: appendix/embedding.html#embed-mem-size
+    text: mem_grow; url: appendix/embedding.html#embed-mem-grow
+    text: global_alloc; url: appendix/embedding.html#embed-global-alloc
+    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: error; url: appendix/embedding.html#embed-error
+    text: store; url: exec/runtime.html#syntax-store
+    text: table type; url: syntax/types.html#syntax-tabletype
+    text: table address; url: exec/runtime.html#syntax-tableaddr
+    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: extern address; url: exec/runtime.html#syntax-externaddr
+    text: tag address; url: exec/runtime.html#syntax-tagaddr
+    url: syntax/types.html#syntax-numtype
+        text: i32
+        text: i64
+        text: f32
+        text: f64
+    url: syntax/types.html#vector-types
+        text: v128
+    url: syntax/types.html#syntax-reftype
+        text: reftype
+        text: funcref
+        text: externref
+    url: syntax/values.html#syntax-float
+        text: +∞
+        text: −∞
+        text: nan
+        text: canon
+        text: signif
+    text: function element; url: exec/runtime.html#syntax-funcelem
+    text: import component; url: syntax/modules.html#imports
+    url: exec/runtime.html#syntax-externval
+        text: external value
+        for: external value
+            text: tag
+    text: host function; url: exec/runtime.html#syntax-hostfunc
+    text: the instantiation algorithm; url: exec/modules.html#instantiation
+    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
+        text: external type
+        text: func
+        text: table
+        text: mem
+        text: global
+        for: externtype
+            text: tag
+    text: global type; url: syntax/types.html#syntax-globaltype
+    url: syntax/types.html#syntax-mut
+        text: var
+        text: const
+    text: address; url: exec/runtime.html#addresses
+    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
+    text: module; for: frame; url: exec/runtime.html#syntax-frame
+    text: memaddrs; for: moduleinst; url: exec/runtime.html#syntax-moduleinst
+    text: signed_64; url: exec/numerics.html#aux-signed
+    text: sequence; url: syntax/conventions.html#grammar-notation
+    text: exception; for: tagtype/attribute; url: syntax/types.html#syntax-tagtype
+urlPrefix: https://heycam.github.io/webidl/; spec: WebIDL
+    type: dfn
+        text: create a namespace object; url: create-a-namespace-object
+urlPrefix: https://webassembly.github.io/js-types/js-api/; spec: WebAssembly JS API (JS Type Reflection)
+    type: abstract-op; text: FromValueType; url: abstract-opdef-fromvaluetype
+
+ + + + + +This API provides a way to access WebAssembly [[WEBASSEMBLY]] through a bridge to explicitly construct modules from JavaScript [[ECMASCRIPT]]. + +

Sample API Usage

+ +

This section is non-normative.

+ +Given `demo.wat` (encoded to `demo.wasm`): + +```lisp +(module + (import "js" "import1" (func $i1)) + (import "js" "import2" (func $i2)) + (func $main (call $i1)) + (start $main) + (func (export "f") (call $i2)) +) +``` + +and the following JavaScript, run in a browser: + +```javascript +var importObj = {js: { + import1: () => console.log("hello,"), + import2: () => console.log("world!") +}}; +fetch('demo.wasm').then(response => + response.arrayBuffer() +).then(buffer => + WebAssembly.instantiate(buffer, importObj) +).then(({module, instance}) => + instance.exports.f() +); +``` + +

Notation

+ +This specification depends on the Infra Standard. [[INFRA]] + +The WebAssembly [=sequence=] type is equivalent to the [=list=] type defined there; values of one +are treated as values of the other transparently. + +

Internal storage

+ +

Interaction of the WebAssembly Store with JavaScript

+ +Note: WebAssembly semantics are defined in terms of an abstract [=store=], representing the state of the WebAssembly abstract machine. WebAssembly operations take a store and return an updated store. + +Each [=agent=] has an associated store. When a new agent is created, its associated store is set to the result of [=store_init=](). + +Note: In this specification, no WebAssembly-related objects, memory or addresses can be shared among agents in an [=agent cluster=]. In a future version of WebAssembly, this may change. + +Elements of the WebAssembly store may be identified with JavaScript values. In particular, each WebAssembly [=memory instance=] with a corresponding {{Memory}} object is identified with a JavaScript [=Data Block=]; modifications to this Data Block are identified to updating the agent's store to a store which reflects those changes, and vice versa. + +

WebAssembly JS Object Caches

+ +Note: There are several WebAssembly objects that may have a corresponding JavaScript object. The correspondence is stored in a per-agent mapping from WebAssembly [=address=]es to JavaScript objects. +This mapping is used to ensure that, for a given [=agent=], there exists at most one JavaScript object for a particular WebAssembly address. However, this property does not hold for shared objects. + +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 Global object cache, mapping [=global address=]es to {{Global}} objects. + * The Extern value cache, mapping [=extern address=]es to values. + * The Tag object cache, mapping [=tag addresses=] to {{Tag}} objects. + +

The WebAssembly Namespace

+ +
+dictionary WebAssemblyInstantiatedSource {
+    required Module module;
+    required Instance instance;
+};
+
+[Exposed=*]
+namespace WebAssembly {
+    boolean validate(BufferSource bytes);
+    Promise<Module> compile(BufferSource bytes);
+
+    Promise<WebAssemblyInstantiatedSource> instantiate(
+        BufferSource bytes, optional object importObject);
+
+    Promise<Instance> instantiate(
+        Module moduleObject, optional object importObject);
+};
+
+ + + +
+ To compile a WebAssembly module from source bytes |bytes|, perform the following steps: + 1. Let |module| be [=module_decode=](|bytes|). If |module| is [=error=], return [=error=]. + 1. If [=module_validate=](|module|) is [=error=], return [=error=]. + 1. Return |module|. +
+ +
+ The validate(|bytes|) method, when invoked, performs the following steps: + 1. Let |stableBytes| be a [=get a copy of the buffer source|copy of the bytes held by the buffer=] |bytes|. + 1. [=Compile a WebAssembly module|Compile=] |stableBytes| as a WebAssembly module and store the results as |module|. + 1. If |module| is [=error=], return false. + 1. Return true. +
+ +A {{Module}} object represents a single WebAssembly module. Each {{Module}} object has the following internal slots: + + * \[[Module]] : a WebAssembly [=/module=] + * \[[Bytes]] : the source bytes of \[[Module]]. + +
+ To construct a WebAssembly module object from a module |module| and source bytes |bytes|, perform the following steps: + + 1. Let |moduleObject| be a new {{Module}} object. + 1. Set |moduleObject|.\[[Module]] to |module|. + 1. Set |moduleObject|.\[[Bytes]] to |bytes|. + 1. Return |moduleObject|. +
+ +
+ To asynchronously compile a WebAssembly module from source bytes |bytes|, using optional [=task source=] |taskSource|, perform the following steps: + + 1. Let |promise| be [=a new promise=]. + 1. Run the following steps [=in parallel=]: + 1. [=compile a WebAssembly module|Compile the WebAssembly module=] |bytes| and store the result as |module|. + 1. [=Queue a task=] to perform the following steps. If |taskSource| was provided, queue the task on that task source. + 1. If |module| is [=error=], reject |promise| with a {{CompileError}} exception. + 1. Otherwise, + 1. [=Construct a WebAssembly module object=] from |module| and |bytes|, and let |moduleObject| be the result. + 1. [=Resolve=] |promise| with |moduleObject|. + 1. Return |promise|. +
+ +
+ The compile(|bytes|) method, when invoked, performs the following steps: + 1. Let |stableBytes| be a [=get a copy of the buffer source|copy of the bytes held by the buffer=] |bytes|. + 1. [=Asynchronously compile a WebAssembly module=] from |stableBytes| and return the result. +
+ +
+ To read the imports from a WebAssembly module |module| from imports object |importObject|, perform the following steps: + 1. If |module|.[=imports=] [=list/is empty|is not empty=], and |importObject| is undefined, throw a {{TypeError}} exception. + 1. Let |imports| be « ». + 1. [=list/iterate|For each=] (|moduleName|, |componentName|, |externtype|) of [=module_imports=](|module|), + 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 [$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. + 1. Otherwise, + 1. [=Create a host function=] from |v| and |functype|, and let |funcaddr| be the result. + 1. Let |index| be the number of external functions in |imports|. This value |index| is known as the index of the host function |funcaddr|. + 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. Throw a {{LinkError}} exception. + 1. If |valtype| is not [=i64=] and [=Type=](|v|) is BigInt, + 1. Throw a {{LinkError}} exception. + 1. If |valtype| is [=v128=], + 1. Throw a {{LinkError}} exception. + 1. Let |value| be [=ToWebAssemblyValue=](|v|, |valtype|). + 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, + 1. If |v| does not [=implement=] {{Memory}}, throw a {{LinkError}} exception. + 1. Let |externmem| be the [=external value=] [=external value|mem=] |v|.\[[Memory]]. + 1. [=list/Append=] |externmem| to |imports|. + 1. If |externtype| is of the form [=table=] tabletype, + 1. If |v| does not [=implement=] {{Table}}, throw a {{LinkError}} exception. + 1. Let |tableaddr| be |v|.\[[Table]]. + 1. Let |externtable| be the [=external value=] [=external value|table=] |tableaddr|. + 1. [=list/Append=] |externtable| to |imports|. + 1. If |externtype| is of the form [=externtype/tag=] |attribute| functype, + 1. Assert: |attribute| is [=tagtype/attribute/exception=]. + 1. If |v| does not [=implement=] {{Tag}}, throw a {{LinkError}} exception. + 1. Let |tagaddr| be |v|.\[[Address]]. + 1. Let |externtag| be the [=external value=] [=external value/tag=] |tagaddr|. + 1. [=list/Append=] |externtag| to |imports|. + 1. Return |imports|. + +Note: This algorithm only verifies the right kind of JavaScript values are passed. +The verification of WebAssembly type requirements is deferred to the +"[=instantiate the core of a WebAssembly module=]" algorithm. +
+ +
+ To create an exports object from a WebAssembly module |module| and instance |instance|, perform the following steps: + 1. Let |exportsObject| be [=!=] [$OrdinaryObjectCreate$](null). + 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. 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|. + 1. Let |value| be |func|. + 1. If |externtype| is of the form [=global=] mut globaltype, + 1. Assert: |externval| is of the form [=external value|global=] |globaladdr|. + 1. Let [=external value|global=] |globaladdr| be |externval|. + 1. Let |global| be [=create a global object|a new Global object=] created from |globaladdr|. + 1. Let |value| be |global|. + 1. If |externtype| is of the form [=mem=] memtype, + 1. Assert: |externval| is of the form [=external value|mem=] |memaddr|. + 1. Let [=external value|mem=] |memaddr| be |externval|. + 1. Let |memory| be [=create a memory object|a new Memory object=] created from |memaddr|. + 1. Let |value| be |memory|. + 1. If |externtype| is of the form [=table=] tabletype, + 1. Assert: |externval| is of the form [=external value|table=] |tableaddr|. + 1. Let [=external value|table=] |tableaddr| be |externval|. + 1. Let |table| be [=create a Table object|a new Table object=] created from |tableaddr|. + 1. Let |value| be |table|. + 1. If |externtype| is of the form [=externtype/tag=] |attribute| functype, + 1. Assert: |attribute| is [=tagtype/attribute/exception=]. + 1. Assert: |externval| is of the form [=external value/tag=] |tagaddr|. + 1. Let [=external value/tag=] |tagaddr| be |externval|. + 1. Let |tag| be [=create a Tag object|a new Tag object=] created from |tagaddr|. + 1. Let |value| be |tag|. + 1. Let |status| be [=!=] [$CreateDataProperty$](|exportsObject|, |name|, |value|). + 1. Assert: |status| is true. + + Note: the validity and uniqueness checks performed during [=WebAssembly module validation=] ensure that each property name is valid and no properties are defined twice. + 1. Perform [=!=] [$SetIntegrityLevel$](|exportsObject|, `"frozen"`). + 1. Return |exportsObject|. +
+ +
+ To initialize an instance object |instanceObject| from a WebAssembly module |module| and instance |instance|, perform the following steps: + + 1. [=Create an exports object=] from |module| and |instance| and let |exportsObject| be the result. + 1. Set |instanceObject|.\[[Instance]] to |instance|. + 1. Set |instanceObject|.\[[Exports]] to |exportsObject|. +
+ +
+ To instantiate the core of a WebAssembly module from a module |module| and imports |imports|, perform the following steps: + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |result| be [=module_instantiate=](|store|, |module|, |imports|). + 1. If |result| is [=error=], throw an appropriate exception type: + * A {{LinkError}} exception for most cases which occur during linking. + * If the error came when running the start function, throw a {{RuntimeError}} for most errors which occur from WebAssembly, or the error object propagated from inner ECMAScript code. + * Another error type if appropriate, for example an out-of-memory exception, as documented in the WebAssembly error mapping. + 1. Let (|store|, |instance|) be |result|. + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. Return |instance|. +
+ +
+ To asynchronously instantiate a WebAssembly module from a {{Module}} |moduleObject| and imports |importObject|, perform the following steps: + 1. Let |promise| be [=a new promise=]. + 1. Let |module| be |moduleObject|.\[[Module]]. + 1. [=Read the imports=] of |module| with imports |importObject|, and let |imports| be the result. + If this operation throws an exception, catch it, [=reject=] |promise| with the exception, and return |promise|. + 1. Run the following steps [=in parallel=]: + 1. [=Queue a task=] to perform the following steps: + Note: Implementation-specific work may be performed here. + 1. [=Instantiate the core of a WebAssembly module=] |module| with |imports|, and let |instance| be the result. + If this throws an exception, catch it, [=reject=] |promise| with the exception, and terminate these substeps. + 1. Let |instanceObject| be a [=/new=] {{Instance}}. + 1. [=initialize an instance object|Initialize=] |instanceObject| from |module| and |instance|. + If this throws an exception, catch it, [=reject=] |promise| with the exception, and terminate these substeps. + 1. [=Resolve=] |promise| with |instanceObject|. + 1. Return |promise|. +
+ +
+ To synchronously instantiate a WebAssembly module from a {{Module}} |moduleObject| and imports |importObject|, perform the following steps: + 1. Let |module| be |moduleObject|.\[[Module]]. + 1. [=Read the imports=] of |module| with imports |importObject|, and let |imports| be the result. + 1. [=Instantiate the core of a WebAssembly module=] |module| with |imports|, and let |instance| be the result. + 1. Let |instanceObject| be a [=/new=] {{Instance}}. + 1. [=initialize an instance object|Initialize=] |instanceObject| from |module| and |instance|. + 1. Return |instanceObject|. +
+ +
+ To instantiate a promise of a module |promiseOfModule| with imports |importObject|, perform the following steps: + + 1. Let |promise| be [=a new promise=]. + 1. [=Upon fulfillment=] of |promiseOfModule| with value |module|: + 1. [=asynchronously instantiate a WebAssembly module|Instantiate the WebAssembly module=] |module| importing |importObject|, and let |innerPromise| be the result. + 1. [=Upon fulfillment=] of |innerPromise| with value |instance|. + 1. Let |result| be the {{WebAssemblyInstantiatedSource}} value «[ "{{WebAssemblyInstantiatedSource/module}}" → |module|, "{{WebAssemblyInstantiatedSource/instance}}" → |instance| ]». + 1. [=Resolve=] |promise| with |result|. + 1. [=Upon rejection=] of |innerPromise| with reason |reason|: + 1. [=Reject=] |promise| with |reason|. + 1. [=Upon rejection=] of |promiseOfModule| with reason |reason|: + 1. [=Reject=] |promise| with |reason|. + 1. Return |promise|. +
+ +
+ The instantiate(|bytes|, |importObject|) method, when invoked, performs the following steps: + 1. Let |stableBytes| be a [=get a copy of the buffer source|copy of the bytes held by the buffer=] |bytes|. + 1. [=Asynchronously compile a WebAssembly module=] from |stableBytes| and let |promiseOfModule| be the result. + 1. [=Instantiate a promise of a module|Instantiate=] |promiseOfModule| with imports |importObject| and return the result. +
+ +
+ The instantiate(|moduleObject|, |importObject|) method, when invoked, performs the following steps: + 1. [=asynchronously instantiate a WebAssembly module|Asynchronously instantiate the WebAssembly module=] |moduleObject| importing |importObject|, and return the result. +
+ +Note: A follow-on streaming API is documented in the WebAssembly Web API. + +

Modules

+ +
+enum ImportExportKind {
+  "function",
+  "table",
+  "memory",
+  "global",
+  "tag"
+};
+
+dictionary ModuleExportDescriptor {
+  required USVString name;
+  required ImportExportKind kind;
+  // Note: Other fields such as signature may be added in the future.
+};
+
+dictionary ModuleImportDescriptor {
+  required USVString module;
+  required USVString name;
+  required ImportExportKind kind;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=*]
+interface Module {
+  constructor(BufferSource bytes);
+  static sequence<ModuleExportDescriptor> exports(Module moduleObject);
+  static sequence<ModuleImportDescriptor> imports(Module moduleObject);
+  static sequence<ArrayBuffer> customSections(Module moduleObject, DOMString sectionName);
+};
+
+ +
+ The string value of the extern type |type| is + * "function" if |type| is of the form [=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 + * "tag" if |type| is of the form [=externtype/tag=] tag +
+ +
+ The exports(|moduleObject|) method, when invoked, performs the following steps: + 1. Let |module| be |moduleObject|.\[[Module]]. + 1. Let |exports| be « ». + 1. [=list/iterate|For each=] (|name|, |type|) of [=module_exports=](|module|), + 1. Let |kind| be the [=string value of the extern type=] |type|. + 1. Let |obj| be «[ "{{ModuleExportDescriptor/name}}" → |name|, "{{ModuleExportDescriptor/kind}}" → |kind| ]». + 1. [=list/Append=] |obj| to |exports|. + 1. Return |exports|. +
+ +
+ The imports(|moduleObject|) method, when invoked, performs the following steps: + 1. Let |module| be |moduleObject|.\[[Module]]. + 1. Let |imports| be « ». + 1. [=list/iterate|For each=] (|moduleName|, |name|, |type|) of [=module_imports=](|module|), + 1. Let |kind| be the [=string value of the extern type=] |type|. + 1. Let |obj| be «[ "{{ModuleImportDescriptor/module}}" → |moduleName|, "{{ModuleImportDescriptor/name}}" → |name|, "{{ModuleImportDescriptor/kind}}" → |kind| ]». + 1. [=list/Append=] |obj| to |imports|. + 1. Return |imports|. +
+ +
+ The customSections(|moduleObject|, |sectionName|) method, when invoked, performs the following steps: + 1. Let |bytes| be |moduleObject|.\[[Bytes]]. + 1. Let |customSections| be « ». + 1. [=list/iterate|For each=] [=custom section=] |customSection| of |bytes|, interpreted according to the [=module grammar=], + 1. Let |name| be the name of |customSection|, [=UTF-8 decode without BOM or fail|decoded as UTF-8=]. + 1. Assert: |name| is not failure (|moduleObject|.\[[Module]] is [=valid=]). + 1. If |name| equals |sectionName| as string values, + 1. [=list/Append=] a new {{ArrayBuffer}} containing a copy of the bytes in |bytes| for the range matched by this [=customsec=] production to |customSections|. + 1. Return |customSections|. +
+ +
+ The Module(|bytes|) constructor, when invoked, performs the following steps: + + 1. Let |stableBytes| be a [=get a copy of the buffer source|copy of the bytes held by the buffer=] |bytes|. + 1. [=Compile a WebAssembly module|Compile the WebAssembly module=] |stableBytes| and store the result as |module|. + 1. If |module| is [=error=], throw a {{CompileError}} exception. + 1. Set **this**.\[[Module]] to |module|. + 1. Set **this**.\[[Bytes]] to |stableBytes|. + +Note: Some implementations enforce a size limitation on |bytes|. Use of this API is discouraged, in favor of asynchronous APIs. +
+ +

Instances

+ +
+[LegacyNamespace=WebAssembly, Exposed=*]
+interface Instance {
+  constructor(Module module, optional object importObject);
+  readonly attribute object exports;
+};
+
+ +
+ The Instance(|module|, |importObject|) constructor, when invoked, runs the following steps: + 1. Let |module| be |module|.\[[Module]]. + 1. [=Read the imports=] of |module| with imports |importObject|, and let |imports| be the result. + 1. [=Instantiate the core of a WebAssembly module=] |module| with |imports|, and let |instance| be the result. + 1. [=initialize an instance object|Initialize=] **this** from |module| and |instance|. + +Note: The use of this synchronous API is discouraged, as some implementations sometimes do long-running compilation work when instantiating. +
+ +
+ The getter of the exports attribute of {{Instance}} returns **this**.\[[Exports]]. +
+ +

Memories

+ +
+dictionary MemoryDescriptor {
+  required [EnforceRange] unsigned long initial;
+  [EnforceRange] unsigned long maximum;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=*]
+interface Memory {
+  constructor(MemoryDescriptor descriptor);
+  unsigned long grow([EnforceRange] unsigned long delta);
+  readonly attribute ArrayBuffer buffer;
+};
+
+ +A {{Memory}} object represents a single [=memory instance=] +which can be simultaneously referenced by multiple {{Instance}} objects. Each +{{Memory}} object has the following internal slots: + + * \[[Memory]] : a [=memory address=] + * \[[BufferObject]] : an {{ArrayBuffer}} whose [=Data Block=] is [=identified with=] the above memory address + +
+ To create a memory buffer from a [=memory address=] |memaddr|, perform the following steps: + + 1. Let |block| be a [=Data Block=] which is [=identified with=] the underlying memory of |memaddr|. + 1. Let |buffer| be a new {{ArrayBuffer}} whose \[[ArrayBufferData]] is |block| and \[[ArrayBufferByteLength]] is set to the length of |block|. + 1. Set |buffer|.\[[ArrayBufferDetachKey]] to "WebAssembly.Memory". + 1. Return |buffer|. +
+ +
+ To initialize a memory object |memory| from a [=memory address=] |memaddr|, perform the following steps: + 1. Let |map| be the [=surrounding agent=]'s associated [=Memory object cache=]. + 1. Assert: |map|[|memaddr|] doesn't [=map/exist=]. + 1. Let |buffer| be the result of [=create a memory buffer|creating a memory buffer=] from |memaddr|. + 1. Set |memory|.\[[Memory]] to |memaddr|. + 1. Set |memory|.\[[BufferObject]] to |buffer|. + 1. [=map/Set=] |map|[|memaddr|] to |memory|. +
+ +
+ To create a memory object from a [=memory address=] |memaddr|, perform the following steps: + + 1. Let |map| be the [=surrounding agent=]'s associated [=Memory object cache=]. + 1. If |map|[|memaddr|] [=map/exists=], + 1. Return |map|[|memaddr|]. + 1. Let |memory| be a [=/new=] {{Memory}}. + 1. [=initialize a memory object|Initialize=] |memory| from |memaddr|. + 1. Return |memory|. +
+ +
+ The Memory(|descriptor|) constructor, when invoked, performs the following steps: + 1. Let |initial| be |descriptor|["initial"]. + 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be |descriptor|["maximum"]; otherwise, let |maximum| be empty. + 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. + 1. Let |memtype| be { min |initial|, max |maximum| }. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let (|store|, |memaddr|) be [=mem_alloc=](|store|, |memtype|). If allocation fails, throw a {{RangeError}} exception. + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. [=initialize a memory object|Initialize=] **this** from |memaddr|. +
+ +
+ To reset the Memory buffer of |memaddr|, perform the following steps: + + 1. Let |map| be the [=surrounding agent=]'s associated [=Memory object cache=]. + 1. Assert: |map|[|memaddr|] [=map/exists=]. + 1. Let |memory| be |map|[|memaddr|]. + 1. Perform [=!=] [$DetachArrayBuffer$](|memory|.\[[BufferObject]], "WebAssembly.Memory"). + 1. Let |buffer| be the result of [=create a memory buffer|creating a memory buffer=] from |memaddr|. + 1. Set |memory|.\[[BufferObject]] to |buffer|. +
+ +
+ The grow(|delta|) method, when invoked, performs the following steps: + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |memaddr| be **this**.\[[Memory]]. + 1. Let |ret| be the [=mem_size=](|store|, |memaddr|). + 1. Let |store| be [=mem_grow=](|store|, |memaddr|, |delta|). + 1. If |store| is [=error=], throw a {{RangeError}} exception. + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. [=Reset the memory buffer=] of |memaddr|. + 1. Return |ret|. +
+ +Immediately after a WebAssembly [=memory.grow=] instruction executes, perform the following steps: + +
+ 1. If the top of the stack is not [=i32.const=] (−1), + 1. Let |frame| be the [=current frame=]. + 1. Assert: due to validation, |frame|.[=frame/module=].[=moduleinst/memaddrs=][0] exists. + 1. Let |memaddr| be the memory address |frame|.[=frame/module=].[=moduleinst/memaddrs=][0]. + 1. [=Reset the memory buffer=] of |memaddr|. +
+ +
+ The getter of the buffer attribute of {{Memory}} returns **this**.\[[BufferObject]]. +
+ +

Tables

+ +
+enum TableKind {
+  "externref",
+  "anyfunc",
+  // Note: More values may be added in future iterations,
+  // e.g., typed function references, typed GC references
+};
+
+dictionary TableDescriptor {
+  required TableKind element;
+  required [EnforceRange] unsigned long initial;
+  [EnforceRange] unsigned long maximum;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=*]
+interface Table {
+  constructor(TableDescriptor descriptor, optional any value);
+  unsigned long grow([EnforceRange] unsigned long delta, optional any value);
+  any get([EnforceRange] unsigned long index);
+  undefined set([EnforceRange] unsigned long index, optional any value);
+  readonly attribute unsigned long length;
+};
+
+ +A {{Table}} object represents a single [=table instance=] which can be simultaneously referenced by +multiple {{Instance}} objects. +Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address=]. + +
+ To initialize a table object |table| from a [=table address=] |tableaddr|, perform the following steps: + 1. Let |map| be the [=surrounding agent=]'s associated [=Table object cache=]. + 1. Assert: |map|[|tableaddr|] doesn't [=map/exist=]. + 1. Set |table|.\[[Table]] to |tableaddr|. + 1. [=map/Set=] |map|[|tableaddr|] to |table|. +
+ +
+ To create a table object from a [=table address=] |tableaddr|, perform the following steps: + 1. Let |map| be the [=surrounding agent=]'s associated [=Table object cache=]. + 1. If |map|[|tableaddr|] [=map/exists=], + 1. Return |map|[|tableaddr|]. + 1. Let |table| be a [=/new=] {{Table}}. + 1. [=initialize a table object|Initialize=] |table| from |tableaddr|. + 1. Return |table|. +
+ +
+ The Table(|descriptor|, |value|) constructor, when invoked, performs the following steps: + 1. Let |elementType| be [=ToValueType=](|descriptor|["element"]). + 1. If |elementType| is not a [=reftype=], + 1. [=Throw=] a {{TypeError}} exception. + 1. Let |initial| be |descriptor|["initial"]. + 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be |descriptor|["maximum"]; otherwise, let |maximum| be empty. + 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. Otherwise, + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). + 1. Let |type| be the [=table type=] {[=table type|min=] |initial|, [=table type|max=] |maximum|} |elementType|. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let (|store|, |tableaddr|) be [=table_alloc=](|store|, |type|, |ref|). + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. [=initialize a table object|Initialize=] **this** from |tableaddr|. +
+ +
+ The grow(|delta|, |value|) method, when invoked, performs the following steps: + 1. Let |tableaddr| be **this**.\[[Table]]. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |initialSize| be [=table_size=](|store|, |tableaddr|). + 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). + 1. If |value| is missing, + 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Otherwise, + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). + 1. Let |result| be [=table_grow=](|store|, |tableaddr|, |delta|, |ref|). + 1. If |result| is [=error=], throw a {{RangeError}} exception. + + Note: The above exception can happen due to either insufficient memory or an invalid size parameter. + + 1. Set the [=surrounding agent=]'s [=associated store=] to |result|. + 1. Return |initialSize|. +
+ +
+ The getter of the length attribute of {{Table}}, when invoked, performs the following steps: + 1. Let |tableaddr| be **this**.\[[Table]]. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Return [=table_size=](|store|, |tableaddr|). +
+ +
+ The get(|index|) method, when invoked, performs the following steps: + 1. Let |tableaddr| be **this**.\[[Table]]. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |result| be [=table_read=](|store|, |tableaddr|, |index|). + 1. If |result| is [=error=], throw a {{RangeError}} exception. + 1. Return [=ToJSValue=](|result|). +
+ +
+ The set(|index|, |value|) method, when invoked, performs the following steps: + 1. Let |tableaddr| be **this**.\[[Table]]. + 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). + 1. If |value| is missing, + 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Otherwise, + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |store| be [=table_write=](|store|, |tableaddr|, |index|, |ref|). + 1. If |store| is [=error=], throw a {{RangeError}} exception. + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. +
+ +

Globals

+ +
+enum ValueType {
+  "i32",
+  "i64",
+  "f32",
+  "f64",
+  "v128",
+  "externref",
+  "anyfunc",
+};
+
+ +Note: this type may be extended with additional cases in future versions of WebAssembly. + +
+dictionary GlobalDescriptor {
+  required ValueType value;
+  boolean mutable = false;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=*]
+interface Global {
+  constructor(GlobalDescriptor descriptor, optional any v);
+  any valueOf();
+  attribute any value;
+};
+
+ +A {{Global}} object represents a single [=global instance=] +which can be simultaneously referenced by multiple {{Instance}} objects. Each +{{Global}} object has one internal slot: + + * \[[Global]] : a [=global address=] + +
+ To initialize a global object |global| from a [=global address=] |globaladdr|, perform the following steps: + 1. Let |map| be the [=surrounding agent=]'s associated [=Global object cache=]. + 1. Assert: |map|[|globaladdr|] doesn't [=map/exist=]. + 1. Set |global|.\[[Global]] to |globaladdr|. + 1. [=map/Set=] |map|[|globaladdr|] to |global|. +
+ +
+ To create a global object from a [=global address=] |globaladdr|, perform the following steps: + 1. Let |map| be the current [=agent=]'s associated [=Global object cache=]. + 1. If |map|[|globaladdr|] [=map/exists=], + 1. Return |map|[|globaladdr|]. + 1. Let |global| be a [=/new=] {{Global}}. + 1. [=initialize a global object|Initialize=] |global| from |globaladdr|. + 1. Return |global|. +
+ +
+ The algorithm ToValueType(|s|) performs the following steps: + 1. If |s| equals "i32", return [=i32=]. + 1. If |s| equals "i64", return [=i64=]. + 1. If |s| equals "f32", return [=f32=]. + 1. If |s| equals "f64", return [=f64=]. + 1. If |s| equals "v128", return [=v128=]. + 1. If |s| equals "anyfunc", return [=funcref=]. + 1. If |s| equals "externref", return [=externref=]. + 1. Assert: This step is not reached. +
+ +
+ 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. +
+ +
+ The Global(|descriptor|, |v|) constructor, when invoked, performs the following steps: + 1. Let |mutable| be |descriptor|["mutable"]. + 1. Let |valuetype| be [=ToValueType=](|descriptor|["value"]). + 1. If |valuetype| is [=v128=], + 1. Throw a {{TypeError}} exception. + 1. If |v| is missing, + 1. Let |value| be [=DefaultValue=](|valuetype|). + 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|. + 1. Let |store| be the current agent's [=associated store=]. + 1. Let (|store|, |globaladdr|) be [=global_alloc=](|store|, |globaltype|, |value|). + 1. Set the current agent's [=associated store=] to |store|. + 1. [=initialize a global object|Initialize=] **this** from |globaladdr|. +
+ +
+ The algorithm GetGlobalValue({{Global}} |global|) performs the following steps: + 1. Let |store| be the current agent's [=associated store=]. + 1. Let |globaladdr| be |global|.\[[Global]]. + 1. Let |globaltype| be [=global_type=](|store|, |globaladdr|). + 1. If |globaltype| is of the form mut [=v128=], throw a {{TypeError}}. + 1. Let |value| be [=global_read=](|store|, |globaladdr|). + 1. Return [=ToJSValue=](|value|). +
+ +
+ The getter of the value attribute of {{Global}}, when invoked, performs the following steps: + 1. Return [=GetGlobalValue=](**this**). + + The setter of the value attribute of {{Global}}, when invoked, performs the following steps: + 1. Let |store| be the current agent's [=associated store=]. + 1. Let |globaladdr| be **this**.\[[Global]]. + 1. Let |mut| |valuetype| be [=global_type=](|store|, |globaladdr|). + 1. If |valuetype| is [=v128=], throw a {{TypeError}}. + 1. If |mut| is [=const=], throw a {{TypeError}}. + 1. Let |value| be [=ToWebAssemblyValue=](**the given value**, |valuetype|). + 1. Let |store| be [=global_write=](|store|, |globaladdr|, |value|). + 1. If |store| is [=error=], throw a {{RangeError}} exception. + 1. Set the current agent's [=associated store=] to |store|. +
+ +
+ The valueOf() method, when invoked, performs the following steps: + 1. Return [=GetGlobalValue=](**this**). +
+ +

Exported Functions

+ +A WebAssembly function is made available in JavaScript as an Exported Function. +Exported Functions are [=Built-in Function Objects=] which are not constructors, and which have a \[[FunctionAddress]] internal slot. +This slot holds a [=function address=] relative to the [=surrounding agent=]'s [=associated store=]. + +
+ The name of the WebAssembly function |funcaddr| is found by performing the following steps: + + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |funcinst| be |store|.funcs[|funcaddr|]. + 1. If |funcinst| is of the form {type functype, hostcode |hostfunc|}, + 1. Assert: |hostfunc| is a JavaScript object and [$IsCallable$](|hostfunc|) is true. + 1. Let |index| be the [=index of the host function=] |funcaddr|. + 1. Otherwise, + 1. Let |moduleinst| be |funcinst|.module. + 1. Assert: |funcaddr| is contained in |moduleinst|.funcaddrs. + 1. Let |index| be the index of |moduleinst|.funcaddrs where |funcaddr| is found. + 1. Return [=!=] [$ToString$](|index|). +
+ +
+ To create a new Exported Function from a WebAssembly [=function address=] |funcaddr|, perform the following steps: + + 1. Let |map| be the [=surrounding agent=]'s associated [=Exported Function cache=]. + 1. If |map|[|funcaddr|] [=map/exists=], + 1. Return |map|[|funcaddr|]. + 1. Let |steps| be "[=call an Exported Function|call the Exported Function=] |funcaddr| with arguments." + 1. Let |realm| be the [=current Realm=]. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |functype| be [=func_type=](|store|, |funcaddr|). + 1. Let [|paramTypes|] → [resultTypes] be |functype|. + 1. Let |arity| be |paramTypes|'s [=list/size=]. + 1. Let |name| be the [=name of the WebAssembly function=] |funcaddr|. + 1. Let |function| be [=!=] [$CreateBuiltinFunction$](|steps|, |arity|, |name|, « \[[FunctionAddress]] », |realm|). + 1. Set |function|.\[[FunctionAddress]] to |funcaddr|. + 1. [=map/Set=] |map|[|funcaddr|] to |function|. + 1. Return |function|. +
+ +
+ To call an Exported Function with [=function address=] |funcaddr| and a [=list=] of JavaScript arguments |argValues|, perform the following steps: + + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |functype| be [=func_type=](|store|, |funcaddr|). + 1. Let [|parameters|] → [|results|] be |functype|. + 1. If |parameters| or |results| contain [=v128=], throw a {{TypeError}}. + + Note: the above error is thrown each time the \[[Call]] method is invoked. + 1. Let |args| be « ». + 1. Let |i| be 0. + 1. [=list/iterate|For each=] |t| of |parameters|, + 1. If |argValues|'s [=list/size=] > |i|, let |arg| be |argValues|[|i|]. + 1. Otherwise, let |arg| be undefined. + 1. [=list/Append=] [=ToWebAssemblyValue=](|arg|, |t|) to |args|. + 1. Set |i| to |i| + 1. + 1. Let (|store|, |ret|) be the result of [=func_invoke=](|store|, |funcaddr|, |args|). + 1. Note: The expectation is that [=func_invoke=] will be updated to return (|store|, val* | [=error=] | (exception |exntag| |payload| |opaqueData|)). + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. If |ret| is [=error=], throw an exception. This exception should be a WebAssembly {{RuntimeError}} exception, unless otherwise indicated by the WebAssembly error mapping. + 1. If |ret| is exception |exntag| |payload| |opaqueData|, then + 1. If |opaqueData| is not [=ref.null=] [=externref=], + 1. Let « [=ref.extern=] |externaddr| » be |opaqueData|. + 1. Throw the result of [=retrieving an extern value=] from |externaddr|. + 1. Let |exception| be [=create an Exception object|a new Exception=] for |exntag| and |payload|. + 1. Throw |exception|. + 1. Let |outArity| be the [=list/size=] of |ret|. + 1. If |outArity| is 0, return undefined. + 1. Otherwise, if |outArity| is 1, return [=ToJSValue=](|ret|[0]). + 1. Otherwise, + 1. Let |values| be « ». + 1. [=list/iterate|For each=] |r| of |ret|, + 1. [=list/Append=] [=ToJSValue=](|r|) to |values|. + 1. Return [$CreateArrayFromList$](|values|). +
+ +Note: [=call an Exported Function|Calling an Exported Function=] executes in the \[[Realm]] of the callee Exported Function, as per the definition of [=built-in function objects=]. + +Note: Exported Functions do not have a \[[Construct]] method and thus it is not possible to call one with the `new` operator. + +
+ To run a host function from the JavaScript object |func|, type |functype|, and [=list=] of [=WebAssembly values=] |arguments|, perform the following steps: + + 1. Let [|parameters|] → [|results|] be |functype|. + 1. If |parameters| or |results| contain [=v128=], throw a {{TypeError}}. + 1. Let |jsArguments| be « ». + 1. [=list/iterate|For each=] |arg| of |arguments|, + 1. [=list/Append=] [=!=] [=ToJSValue=](|arg|) to |jsArguments|. + 1. Let |ret| be [=?=] [$Call$](|func|, undefined, |jsArguments|). + 1. Let |resultsSize| be |results|'s [=list/size=]. + 1. If |resultsSize| is 0, return « ». + 1. Otherwise, if |resultsSize| is 1, return « [=?=] [=ToWebAssemblyValue=](|ret|, |results|[0]) ». + 1. Otherwise, + 1. Let |method| be [=?=] [$GetMethod$](|ret|, {{@@iterator}}). + 1. If |method| is undefined, [=throw=] a {{TypeError}}. + 1. Let |values| be [=?=] [$IterableToList$](|ret|, |method|). + 1. Let |wasmValues| be a new, empty [=list=]. + 1. If |values|'s [=list/size=] is not |resultsSize|, throw a {{TypeError}} exception. + 1. For each |value| and |resultType| in |values| and |results|, paired linearly, + 1. [=list/Append=] [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmValues|. + 1. Return |wasmValues|. +
+ +
+ To create a host function from the JavaScript object |func| and type |functype|, perform the following steps: + + 1. Assert: [$IsCallable$](|func|). + 1. Let |stored settings| be the incumbent settings object. + 1. Let |hostfunc| be a [=host function=] which performs the following steps when called with arguments |arguments|: + 1. Let |realm| be |func|'s [=associated Realm=]. + 1. Let |relevant settings| be |realm|'s [=realm/settings object=]. + 1. [=Prepare to run script=] with |relevant settings|. + 1. [=Prepare to run a callback=] with |stored settings|. + 1. Let |result| be the result of [=run a host function|running a host function=] from |func|, |functype|, and |arguments|. + 1. [=Clean up after running a callback=] with |stored settings|. + 1. [=Clean up after running script=] with |relevant settings|. + 1. Assert: |result|.\[[Type]] is throw or normal. + 1. If |result|.\[[Type]] is throw, then: + 1. Let |v| be |result|.\[[Value]]. + 1. If |v| [=implements=] {{Exception}}, + 1. Let |type| be |v|.\[[Type]]. + 1. Let |payload| be |v|.\[[Payload]]. + 1. Otherwise, + 1. Let |type| be the [=JavaScript exception tag=]. + 1. Let |payload| be « ». + 1. Let |opaqueData| be [=ToWebAssemblyValue=](|v|, [=externref=]) + 1. [=WebAssembly/Throw=] with |type|, |payload| and |opaqueData|. + 1. Otherwise, return |result|.\[[Value]]. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let (|store|, |funcaddr|) be [=func_alloc=](|store|, |functype|, |hostfunc|). + 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. + 1. Return |funcaddr|. +
+ +
+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 [=f32.const=] |f32|, + 1. If |f32| is [=+∞=] or [=−∞=], return **+∞**𝔽 or **-∞**𝔽, respectively. + 1. If |f32| is [=nan=], return **NaN**. + 1. Return [=𝔽=](|f32| interpreted as a mathematical value). +1. If |w| is of the form [=f64.const=] |f64|, + 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.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|. + +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: + +1. Let |map| be the [=surrounding agent=]'s associated [=extern value cache=]. +1. Assert: |map|[|externaddr|] [=map/exists=]. +1. Return |map|[|externaddr|]. + +
+ +
+The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript value to a [=WebAssembly value=] by performing the following steps: + +1. Assert: |type| is not [=v128=]. +1. If |type| is [=i64=], + 1. Let |i64| be [=?=] [$ToBigInt64$](|v|). + 1. Return [=i64.const=] |i64|. +1. If |type| is [=i32=], + 1. Let |i32| be [=?=] [$ToInt32$](|v|). + 1. Return [=i32.const=] |i32|. +1. If |type| is [=f32=], + 1. Let |number| be [=?=] [$ToNumber$](|v|). + 1. If |number| is **NaN**, + 1. Let |n| be an implementation-defined integer such that [=canon=]32 ≤ |n| < 2[=signif=](32). + 1. Let |f32| be [=nan=](n). + 1. Otherwise, + 1. Let |f32| be |number| rounded to the nearest representable value using IEEE 754-2008 round to nearest, ties to even mode. [[IEEE-754]] + 1. Return [=f32.const=] |f32|. +1. If |type| is [=f64=], + 1. Let |number| be [=?=] [$ToNumber$](|v|). + 1. If |number| is **NaN**, + 1. Let |n| be an implementation-defined integer such that [=canon=]64 ≤ |n| < 2[=signif=](64). + 1. Let |f64| be [=nan=](n). + 1. Otherwise, + 1. Let |f64| be |number|. + 1. Return [=f64.const=] |f64|. +1. If |type| is [=funcref=], + 1. If |v| is null, + 1. Return [=ref.null=] [=funcref=]. + 1. If |v| is an [=Exported Function=], + 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. Assert: This step is not reached. + +
+ +

Tags

+ +The tag_alloc(|store|, |parameters|) algorithm creates a new [=tag address=] for |parameters| in |store| and returns the updated store and the [=tag address=]. + +The tag_parameters(|store|, |tagAddress|) algorithm returns the [=list=] of types for |tagAddress| in |store|. + +

Exception types

+ +
+dictionary TagType {
+  required sequence<ValueType> parameters;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
+interface Tag {
+  constructor(TagType type);
+  TagType type();
+};
+
+ +A {{Tag}} value represents a type of exception. + +
+ +To initialize a Tag object |tag| from a [=tag address=] |tagAddress|, perform the following steps: + +1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. +1. Assert: |map|[|tagAddress|] doesn't [=map/exist=]. +1. Set |tag|.\[[Address]] to |tagAddress|. +1. [=map/Set=] |map|[|tagAddress|] to |tag|. + +
+ +
+ +To create a Tag object from a [=tag address=] |tagAddress|, perform the following steps: + +1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=]. +1. If |map|[|tagAddress|] [=map/exists=], + 1. Return |map|[|tagAddress|]. +1. Let |tag| be a [=new=] {{Tag}}. +1. [=initialize a Tag object|Initialize=] |tag| from |tagAddress|. +1. Return |tag|. + +
+ +
+ +The new Tag(|type|) constructor steps are: + +1. Let |parameters| be |type|["parameters"]. +1. Let |wasmParameters| be «». +1. [=list/iterate|For each=] |type| of |parameters|, + 1. [=list/Append=] [=ToValueType=](|type|) to |wasmParameters|. +1. Let |store| be the current agent's [=associated store=]. +1. Let (|store|, |tagAddress|) be [=tag_alloc=](|store|, |wasmParameters|). +1. Set the current agent's [=associated store=] to |store|. +1. [=initialize a Tag object|Initialize=] **this** from |tagAddress|. + +
+ +
+ +The type() method steps are: + +1. Let |store| be the [=surrounding agent=]'s [=associated store=]. +1. Let |parameters| be [=tag_parameters=](|store|, **this**.\[[Address]]). +1. Let |idlParameters| be «». +1. [=list/iterate|For each=] |type| of |parameters|, + 1. [=list/Append=] [$FromValueType$](|type|) to |idlParameters|. +1. Return «[ "{{TagType/parameters}}" → |idlParameters| ]». + +Advisement: This method is only expected to be implemented or shipped when both this proposal and the Type Reflection proposal are implemented or shipped (respectively). + +
+ +

Runtime exceptions

+ +
+dictionary ExceptionOptions {
+  boolean traceStack = false;
+};
+
+[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
+interface Exception {
+  constructor(Tag exceptionTag, sequence<any> payload, optional ExceptionOptions options = {});
+  any getArg(Tag exceptionTag, [EnforceRange] unsigned long index);
+  boolean is(Tag exceptionTag);
+  readonly attribute (DOMString or undefined) stack;
+};
+
+ +An {{Exception}} value represents an exception. + +
+ +To create an Exception object from a [=tag address=] |tagAddress| and a [=list=] of +WebAssembly values |payload|, perform the following steps: + +1. Let |store| be the [=surrounding agent=]'s [=associated store=]. +1. Let |types| be [=tag_parameters=](|store|, |tagAddress|). +1. Assert: |types|'s [=list/size=] is |payload|'s [=list/size=]. +1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly, + 1. Assert: |value|'s type matches |resultType|. +1. Let |exception| be a [=new=] {{Exception}}. +1. Set |exception|.\[[Type]] to |tagAddress|. +1. Set |exception|.\[[Payload]] to |payload|. +1. Set |exception|.\[[Stack]] to undefined. +1. Return |exception|. + +
+ +
+ +The new Exception(|exceptionTag|, |payload|, |options|) +constructor steps are: + +1. Let |store| be the [=surrounding agent=]'s [=associated store=]. +1. Let |types| be [=tag_parameters=](|store|, |exceptionTag|.\[[Address]]). +1. If |types|'s [=list/size=] is not |payload|'s [=list/size=], + 1. Throw a {{TypeError}}. +1. Let |wasmPayload| be « ». +1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly, + 1. [=list/Append=] ? [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmPayload|. +1. Set **this**.\[[Type]] to |exceptionTag|.\[[Address]]. +1. Set **this**.\[[Payload]] to |wasmPayload|. +1. If |options|["traceStack"] is true, + 1. Set **this**.\[[Stack]] to either a {{DOMString}} representation of the current call stack or undefined. +1. Otherwise, + 1. Set **this**.\[[Stack]] to undefined. + +
+ +
+ +The getArg(|exceptionTag|, |index|) method steps are: + +1. If **this**.\[[Type]] is not equal to |exceptionTag|.\[[Address]], + 1. Throw a {{TypeError}}. +1. Let |payload| be **this**.\[[Payload]]. +1. If |index| ≥ |payload|'s [=list/size=], + 1. Throw a {{RangeError}}. +1. Return [=ToJSValue=](|payload|[|index|]). + +
+ +
+ +The is(|exceptionTag|) method steps are: + +1. If **this**.\[[Type]] is not equal to |exceptionTag|.\[[Address]], + 1. Return false. +1. Return true. + +
+ +
+ +The stack getter steps are: + +1. Return **this**.\[[Stack]]. + +
+ +

JavaScript exceptions

+ +The JavaScript exception tag is a [=tag address=] reserved by this +specification to distinguish exceptions originating from JavaScript. + +For any [=associated store=] |store|, the result of +[=tag_parameters=](|store|, [=JavaScript exception tag=]) must be « ». + +
+ +To throw with a [=tag address=] |type|, a matching [=list=] of WebAssembly values |payload|, and an [=externref=] |opaqueData|, perform the following steps: + +1. Unwind the stack until reaching the *catching try block* given |type|. +1. Invoke the catch block with |payload| and |opaqueData|. + +Note: This algorithm is expected to be moved into the core specification. + +
+ +

Error Objects

+ +WebAssembly defines the following Error classes: CompileError, LinkError, and RuntimeError. + +
+When the [=namespace object=] for the {{WebAssembly}} namespace is [=create a namespace object|created=], the following steps must be run: + +1. Let |namespaceObject| be the [=namespace object=]. +1. [=list/iterate|For each=] |error| of « "CompileError", "LinkError", "RuntimeError" », + 1. Let |constructor| be a new object, implementing the [=NativeError Object Structure=], with NativeError set to |error|. + 1. [=!=] [$CreateMethodProperty$](|namespaceObject|, |error|, |constructor|). + +
+ +Note: This defines {{CompileError}}, {{LinkError}}, and {{RuntimeError}} classes on the {{WebAssembly}} namespace, which are produced by the APIs defined in this specification. +They expose the same interface as native JavaScript errors like {{TypeError}} and {{RangeError}}. + +Note: It is not currently possible to define this behavior using Web IDL. + + +

Error Condition Mappings to JavaScript

+ +Running WebAssembly programs encounter certain events which halt execution of the WebAssembly code. +WebAssembly code (currently) +has no way to catch these conditions and thus an exception will necessarily +propagate to the enclosing non-WebAssembly caller (whether it is a browser, +JavaScript or another runtime system) where it is handled like a normal JavaScript exception. + +If WebAssembly calls JavaScript via import and the JavaScript throws an +exception, the exception is propagated through the WebAssembly activation to the +enclosing caller. + +Because JavaScript exceptions can be handled, and JavaScript can continue to +call WebAssembly exports after a trap has been handled, traps do not, in +general, prevent future execution. + +

Stack Overflow

+ +Whenever a stack overflow occurs in +WebAssembly code, the same class of exception is thrown as for a stack overflow in +JavaScript. The particular exception here is implementation-defined in both cases. + +Note: ECMAScript doesn't specify any sort of behavior on stack overflow; implementations have been observed to throw {{RangeError}}, InternalError or Error. Any is valid here. + +

Out of Memory

+ +Whenever validation, compilation or instantiation run out of memory, the +same class of exception is thrown as for out of memory conditions in JavaScript. +The particular exception here is implementation-defined in both cases. + +Note: ECMAScript doesn't specify any sort of behavior on out-of-memory conditions; implementations have been observed to throw OOMError and to crash. Either is valid here. + +
+ A failed allocation of a large table or memory may either result in + - a {{RangeError}}, as specified in the {{Memory}} {{Memory/grow()}} and {{Table}} {{Table/grow()}} operations + - returning -1 as the [=memory.grow=] instruction + - UA-specific OOM behavior as described in this section. + In a future revision, we may reconsider more reliable and recoverable errors for allocations of large amounts of memory. + + 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}}: +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 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.
  • +
  • The maximum number of globals defined in a module is 1,000,000.
  • +
  • The maximum number of tags defined in a module is 1,000,000.
  • +
  • The maximum number of data segments defined in a module is 100,000.
  • + +
  • 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 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.
  • +
+ +An implementation must throw a {{RuntimeError}} if one of the following limits is exceeded during runtime: +In practice, an implementation may run out of resources for valid modules below these limits. + +
    +
  • The maximum size of a table is 10,000,000.
  • +
  • The maximum number of pages of a memory is 65,536.
  • +
+ +

Security and Privacy Considerations

+ +

This section is non-normative.

+ +This document defines a host environment for WebAssembly. It enables a WebAssembly instance to [=import=] JavaScript objects and functions from an [=read the imports|import object=], but otherwise provides no access to the embedding environment. Thus a WebAssembly instance is bound to the same constraints as JavaScript. diff --git a/document/metadata/code/.gitignore b/document/metadata/code/.gitignore new file mode 100644 index 0000000000..b932ec283e --- /dev/null +++ b/document/metadata/code/.gitignore @@ -0,0 +1,3 @@ +_build +_static +document/*.pyc diff --git a/document/metadata/code/LICENSE b/document/metadata/code/LICENSE new file mode 100644 index 0000000000..795b406e4e --- /dev/null +++ b/document/metadata/code/LICENSE @@ -0,0 +1,50 @@ +W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE + +This work is being provided by the copyright holders under the following +license. + + +LICENSE + +By obtaining and/or copying this work, you (the licensee) agree that you have +read, understood, and will comply with the following terms and conditions. + +Permission to copy, modify, and distribute this work, with or without +modification, for any purpose and without fee or royalty is hereby granted, +provided that you include the following on ALL copies of the work or portions +thereof, including modifications: + +* The full text of this NOTICE in a location viewable to users of the + redistributed or derivative work. + +* Any pre-existing intellectual property disclaimers, notices, or terms and + conditions. If none exist, the W3C Software and Document Short Notice + (https://www.w3.org/Consortium/Legal/copyright-software-short-notice) should + be included. + +* Notice of any changes or modifications, through a copyright statement on the + new code or document such as "This software or document includes material + copied from or derived from [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." + + +DISCLAIMERS + +THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS +OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF +MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE +SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, +TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. + +The name and trademarks of copyright holders may NOT be used in advertising or +publicity pertaining to the work without specific, written prior permission. +Title to copyright in this work will at all times remain with copyright +holders. + + +NOTES + +This version: +http://www.w3.org/Consortium/Legal/2015/copyright-software-and-document diff --git a/document/metadata/code/Makefile b/document/metadata/code/Makefile new file mode 100644 index 0000000000..e8b975c727 --- /dev/null +++ b/document/metadata/code/Makefile @@ -0,0 +1,361 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = a4 +BUILDDIR = _build +STATICDIR = _static +DOWNLOADDIR = _download +NAME = WebAssembly-Metadata-Code + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: usage +usage: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pdf to make standalone PDF file" + @echo " bikeshed to make a bikeshed wrapped single large HTML file" + @echo " diff to make a diff of the bikeshed HTML file with the latest TR" + @echo " WD-tar generate tar file for updating the Working Draft" + @echo " WD-echidna publish the Working Draft tar file via Echidna" + @echo " all to make all 3" + @echo " publish to make all and push to gh-pages" + @echo " help to see more options" + +.PHONY: help +help: + @echo "Usage: \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " pdf to make standalone PDF file" + @echo " bikeshed to make a bikeshed wrapped single large HTML file" + @echo " all to make all 3" + @echo " publish to make all and push to gh-pages" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " epub3 to make an epub3" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + @echo " dummy to check syntax errors of document sources" + +.PHONY: deploy +deploy: + (cd ../..; make dir-core deploy-core) + +.PHONY: publish +publish: clean all deploy + +.PHONY: publish-main +publish-main: clean main deploy + +.PHONY: all +all: pdf html + +.PHONY: main +main: pdf html + +# Dirty hack to avoid rebuilding the Bikeshed version for every push. +.PHONY: bikeshed-keep +bikeshed-keep: + test -e $(BUILDDIR)/html/bikeshed || \ + wget -r -nH --cut-dirs=2 -P $(BUILDDIR)/html --no-check-certificate \ + https://webassembly.github.io/spec/core/bikeshed || \ + echo Downloaded Bikeshed. + + +%.rst: %.py + (cd `dirname $@`; ./`basename $^`) + +.PHONY: pdf +pdf: latexpdf + mkdir -p $(BUILDDIR)/html/$(DOWNLOADDIR) + ln -f $(BUILDDIR)/latex/$(NAME).pdf $(BUILDDIR)/html/$(DOWNLOADDIR)/$(NAME).pdf + + +.PHONY: clean +clean: + rm -rf $(BUILDDIR) + rm -rf $(STATICDIR) + +.PHONY: html +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + for file in `ls $(BUILDDIR)/html/*.html`; \ + do \ + sed s:BASEDIR:.:g <$$file >$$file.out; \ + mv -f $$file.out $$file; \ + done + for file in `ls $(BUILDDIR)/html/*/*.html`; \ + do \ + sed s:BASEDIR:..:g <$$file >$$file.out; \ + mv -f $$file.out $$file; \ + done + @echo + @echo "Build finished. The HTML pages are in `pwd`/$(BUILDDIR)/html/." + +.PHONY: dirhtml +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +.PHONY: singlehtml +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +.PHONY: bikeshed +bikeshed: + $(SPHINXBUILD) -b singlehtml -c util/bikeshed \ + $(ALLSPHINXOPTS) $(BUILDDIR)/bikeshed_singlehtml + python3 util/bikeshed_fixup.py $(BUILDDIR)/bikeshed_singlehtml/index.html \ + >$(BUILDDIR)/bikeshed_singlehtml/index_fixed.html + @echo ==== Showing contents of _build/bikeshed_singlehtml/index_fixed.html ==== + @head -n10 _build/bikeshed_singlehtml/index_fixed.html + @echo ... skipping $$(expr `cat _build/bikeshed_singlehtml/index_fixed.html | wc -l` - 20) lines ... + @tail -n10 _build/bikeshed_singlehtml/index_fixed.html + @echo + @echo ========================================================================= + mkdir -p $(BUILDDIR)/bikeshed_mathjax/ + bikeshed spec index.bs $(BUILDDIR)/bikeshed_mathjax/index.html + mkdir -p $(BUILDDIR)/html/bikeshed/ + (cd util/katex/ && yarn && yarn build && npm install --only=prod) + python3 util/mathjax2katex.py $(BUILDDIR)/bikeshed_mathjax/index.html \ + >$(BUILDDIR)/html/bikeshed/index.html + mkdir -p $(BUILDDIR)/html/bikeshed/katex/dist/ + cp -r util/katex/dist/* $(BUILDDIR)/html/bikeshed/katex/dist/ + patch -p0 $(BUILDDIR)/html/bikeshed/katex/dist/katex.css \ + < util/katex_fix.patch + cp $(BUILDDIR)/bikeshed_singlehtml/_static/pygments.css \ + $(BUILDDIR)/html/bikeshed/ + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/html/bikeshed/." + +.PHONY: WD-tar +WD-tar: bikeshed + @echo "Building tar file..." + tar cvf \ + $(BUILDDIR)/WD.tar \ + --transform='s|$(BUILDDIR)/html/bikeshed/||' \ + --transform='s|index.html|Overview.html|' \ + $(BUILDDIR)/html/bikeshed/index.html \ + $(BUILDDIR)/html/bikeshed/pygments.css \ + $(BUILDDIR)/html/bikeshed/katex/dist/katex.css \ + $(BUILDDIR)/html/bikeshed/katex/dist/fonts + @echo "Built $(BUILDDIR)/WD.tar." + +.PHONY: WD-echidna +WD-echidna: WD-tar + @if [ -z $(W3C_USERNAME) ] || \ + [ -z $(W3C_PASSWORD) ] || \ + [ -z $(DECISION_URL) ] ; then \ + echo "Must provide W3C_USERNAME, W3C_PASSWORD, and DECISION_URL environment variables"; \ + exit 1; \ + fi + curl 'https://labs.w3.org/echidna/api/request' \ + --user '$(W3C_USERNAME):$(W3C_PASSWORD)' \ + -F "tar=@$(BUILDDIR)/WD.tar" \ + -F "decision=$(DECISION_URL)" | tee $(BUILDDIR)/WD-echidna-id.txt + @echo + @echo "Published working draft. Check its status at https://labs.w3.org/echidna/api/status?id=`cat $(BUILDDIR)/WD-echidna-id.txt`" + +.PHONY: diff +diff: bikeshed + @echo "Downloading the old single-file html spec..." + curl `grep "^TR" index.bs | cut -d' ' -f2` -o $(BUILDDIR)/html/bikeshed/old.html + @echo "Done." + @echo "Diffing new against old (go get a coffee)..." + perl ../util/htmldiff.pl $(BUILDDIR)/html/bikeshed/old.html $(BUILDDIR)/html/bikeshed/index.html $(BUILDDIR)/html/bikeshed/diff.html + @echo "Done. The diff is at $(BUILDDIR)/html/bikeshed/diff.html" + +.PHONY: pickle +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +.PHONY: json +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +.PHONY: htmlhelp +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +.PHONY: qthelp +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/WebAssembly.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/WebAssembly.qhc" + +.PHONY: applehelp +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +.PHONY: devhelp +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/WebAssembly" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/WebAssembly" + @echo "# devhelp" + +.PHONY: epub +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +.PHONY: epub3 +epub3: + $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 + @echo + @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." + + +.PHONY: latex-core-aux +latex-core-aux: + (cd ../../core; make BUILDDIR=$(BUILDDIR) latexpdf) + mkdir -p $(BUILDDIR)/latex + cp ../../core/$(BUILDDIR)/latex/WebAssembly.aux $(BUILDDIR)/latex/ + +.PHONY: latex +latex: latex-core-aux + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +.PHONY: latexpdf +latexpdf: latex-core-aux + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex LATEXMKOPTS=" $(BUILDDIR)/latex/LOG 2>&1 || cat $(BUILDDIR)/latex/LOG + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: latexpdfja +latexpdfja: latex-core-aux + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +.PHONY: text +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +.PHONY: man +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +.PHONY: texinfo +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +.PHONY: info +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +.PHONY: gettext +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +.PHONY: changes +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +.PHONY: linkcheck +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +.PHONY: doctest +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +.PHONY: coverage +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +.PHONY: xml +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +.PHONY: pseudoxml +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." + +.PHONY: dummy +dummy: + $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy + @echo + @echo "Build finished. Dummy builder generates no files." diff --git a/document/metadata/code/README.md b/document/metadata/code/README.md new file mode 100644 index 0000000000..d5fe68ce8d --- /dev/null +++ b/document/metadata/code/README.md @@ -0,0 +1,25 @@ +# WebAssembly Code Metadata Specification + +This is the official WebAssembly "language" specification. + +It uses [Sphinx](http://www.sphinx-doc.org/). To install that: +``` +pip install sphinx +``` +To make HTML (result in `_build/html`): +``` +make html +``` +To make PDF (result in `_build/latex`, requires LaTeX): +``` +make pdf +``` +To make all: +``` +make all +``` +Finally, to make all and update webassembly.github.io/spec with it: +``` +make publish +``` +Please make sure to only use that once a change has approval. diff --git a/document/metadata/code/binary.rst b/document/metadata/code/binary.rst new file mode 100644 index 0000000000..1b17265513 --- /dev/null +++ b/document/metadata/code/binary.rst @@ -0,0 +1,69 @@ +.. _binary: + +Binary Format +============= + +.. _binary-codemetadata: + +Code Metadata +------------- + +A Code Metadata item is a piece of information logically attached to an instruction. + +An item is associated with a format, which defines the item's payload. + +All code metadata items of a format named *T* are grouped under a custom section +named *'metadata.code.T'*. +The following parametrized grammar rules define the generic structure of a code metadata +section of format *T*. + +.. math:: + \begin{array}{llcll} + \production{code metadata section} & \Bcodemetadatasec(\B{T}) &::=& + \Bsection_0(\Bcodemetadata(\B{T})) \\ + \production{code metadata} & \Bcodemetadata(\B{T}) &::=& + n{:}\Bname & (\iff n = \text{metadata.code.T}) \\ &&& + \Bvec(\Bcodemetadatafunc(\B{T})) \\ + \production{code metadata function} & \Bcodemetadatafunc(\B{T}) &::=& + x{:}\Bfuncidx~\Bvec(\Bcodemetadataitem(\B{T})) \\ + \production{code metadata item} & \Bcodemetadataitem(\B{T}) &::=& + \X{off}{:}\Bu32 ~~ \X{size}{:}\Bu32 & (\iff \X{size} = ||\B{T}||) \\ &&& + \X{data}{:}\B{T} \\ + \end{array} +.. index:: ! code metadata section + +Where :math:`\X{off}` is the byte offset of the attached instruction, relative to the beginning of the |Bfunc| declaration, and :math:`\X{data}` is a further payload, whose content depends on the format :math:`T`. + +|Bcodemetadatafunc| entries must appear in order of increasing :math:`x`, and duplicate id values are not allowed. |Bcodemetadata| entries must appear in order of increasing :math:`\X{off}`, and duplicate offset values are not allowed. + +.. _binary-branchhints: + +Branch Hints +~~~~~~~~~~~~ + +A Branch Hint is a code metadata item with format *branch_hint*. + +It can only be attached to |BRIF| and |IF| instructions. + +Its payload indicates whether the branch is likely or unlikely to be taken. + +All branch hints for a module are contained in a single code metadata section +with name *'metadata.code.branch_hint'*. + +.. math:: + \begin{array}{llcll} + \production{branch hint section} & \Bbranchhintsec &::=& + \Bcodemetadatasec(\Bbranchhint) \\ + \production{branch hint} & \Bbranchhint &::=& + \Bunlikely \\ &&|& + \Blikely \\ + \production{unlikely} & \Bunlikely &::=& + \hex{00} \\ + \production{likely} & \Blikely &::=& + \hex{01} \\ + \end{array} +.. index:: ! branch hint section + +A value of |Blikely| means that the branch is likely to be taken, while a +value of |Bunlikely| means the opposite. A branch with no hints is considered +equally likely to be taken or not. diff --git a/document/metadata/code/conf.py b/document/metadata/code/conf.py new file mode 100644 index 0000000000..bff6550b48 --- /dev/null +++ b/document/metadata/code/conf.py @@ -0,0 +1,505 @@ +# -*- coding: utf-8 -*- +# +# WebAssembly documentation build configuration file, created by +# sphinx-quickstart on Mon Nov 21 11:32:49 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +import os +import sys +from datetime import date + +core_dir = os.path.abspath('../../core') +sys.path.insert(0, core_dir) +pwd = os.path.abspath('.') +sys.path.insert(0, pwd) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +needs_sphinx = '2.3' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.ifconfig', + 'sphinx.ext.githubpages', + 'util.mathdef', + 'util.pseudo-lexer' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = ['.rst'] + +# The encoding of source files. +# +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +name = 'WebAssembly-Metadata-Code' +project = u'WebAssembly-Metadata-Code' +title = u'WebAssembly Code Metadata Specification' +copyright = u'2024, WebAssembly Community Group' +author = u'WebAssembly Community Group' +editor = u'Yuri Iozzelli (editor)' +logo = 'static/webassembly.png' + +# The name of the GitHub repository this resides in +repo = 'spec' + +# The draft version string (clear out for release cuts) +draft = ' (Draft ' + date.today().strftime("%Y-%m-%d") + ')' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = version + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# +# today = '' +# +# Else, today_fmt is used as the format for a strftime call. +# +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +# +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +# keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + 'logo': logo, + 'logo_name': 'WebAssembly', + 'description': 'WebAssembly Specification', + 'fixed_sidebar': True, + 'sidebar_width': '260px', + 'sidebar_collapse': True, + 'show_powered_by': False, + 'extra_nav_links': { + 'Download as PDF': 'BASEDIR/_download/' + name + '.pdf' + }, +} + +html_sidebars = { + '**': [ + # 'about.html', + 'navigation.html', + # 'relations.html', + 'searchbox.html', + ] +} + + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. +# " v documentation" by default. +# +html_title = u'WebAssembly Custom Sections' + +# A shorter title for the navigation bar. Default is the same as html_title. +# +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# +html_logo = logo + +# The name of an image file (relative to this directory) to use as a favicon of +# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static/custom.css'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +# +# html_extra_path = [] + +# If not None, a 'Last updated on:' timestamp is inserted at every page +# bottom, using the given strftime format. +# The empty string is equivalent to '%b %d, %Y'. +# +# html_last_updated_fmt = None + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# +# html_use_smartypants = True + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# +# html_additional_pages = {} + +# If false, no module index is generated. +# +html_domain_indices = False + +# If false, no index is generated. +# +html_use_index = False + +# If true, the index is split into individual pages for each letter. +# +html_split_index = False + +# If true, the reST sources are included in the HTML build as _sources/name. The default is True. +# +html_copy_source = False + +# If true, links to the reST sources are added to the pages. +# +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# +html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# +html_show_copyright = True + +# If this is not None, a ‘Last updated on:’ timestamp is inserted at every +# page bottom, using the given strftime() format. +# +html_last_updated_fmt = '%Y-%m-%d' + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# +# html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' +# +# html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# 'ja' uses this config value. +# 'zh' user can custom change `jieba` dictionary path. +# +# html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +# +# html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +# +htmlhelp_basename = 'WebAssemblydoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('a4paper' or 'letterpaper'). + 'papersize': 'a4paper', + + # The font size ('10pt', '11pt' or '12pt'). + 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # Don't type-set cross references with emphasis. + 'preamble': '\\renewcommand\\sphinxcrossref[1]{#1}\n\\externaldocument[Core-]{WebAssembly}[https://webassembly.github.io//'+repo+'/core/_download/WebAssembly.pdf]\n', + + # Latex figure (float) alignment + 'figure_align': 'htbp', + + # Fancy chapters [Bjarne, Sonny, Lenny, Glenn, Conny, Rejne] + 'fncychap': '\\usepackage[Sonny]{fncychap}', + + # Allow chapters to start on even pages + 'extraclassoptions': 'openany', + + 'extrapackages': '\\usepackage{xr-hyper}', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( master_doc, + name + '.tex', + title, + author + '\\\\ \\hfill\\large ' + editor, + 'howto' + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# +latex_logo = logo + +# For "manual" documents [part, chapter, or section]. +# +latex_toplevel_sectioning = 'section' + +# If true, show page references after internal links. +# +latex_show_pagerefs = False + +# How to show URL addresses after external links [no, footnote, inline]. +# +latex_show_urls = 'footnote' + +# Documents to append as an appendix to all manuals. +# +# latex_appendices = [] + +# It false, will not define \strong, \code, \titleref, \crossref ... but only +# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added +# packages. +# +# latex_keep_old_macro_names = True + +# If false, no module index is generated. +# +latex_domain_indices = False + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( master_doc, + name, + title, + [author], + 1 + ) +] + +# If true, show URL addresses after external links. +# +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( master_doc, + name, + title, + author, + name, + 'A portable low-level execution format.', + 'Virtual Machine' + ), +] + +# Documents to append as an appendix to all manuals. +# +# texinfo_appendices = [] + +# If false, no module index is generated. +# +texinfo_domain_indices = False + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +# +# texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project +epub_author = author +epub_publisher = author +epub_copyright = copyright + +# The basename for the epub file. It defaults to the project name. +# epub_basename = project + +# The HTML theme for the epub output. Since the default themes are not +# optimized for small screen space, using the same theme for HTML and epub +# output is usually not wise. This defaults to 'epub', a theme designed to save +# visual space. +# +# epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or 'en' if the language is not set. +# +# epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +# epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +# +# epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +# +# epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# +# epub_pre_files = [] + +# HTML files that should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +# +# epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +# +# epub_tocdepth = 3 + +# Allow duplicate toc entries. +# +# epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +# +# epub_tocscope = 'default' + +# Fix unsupported image types using the Pillow. +# +# epub_fix_images = False + +# Scale large images. +# +# epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# +# epub_show_urls = 'inline' + +# If false, no index is generated. +# +# epub_use_index = True + +# Macros +rst_prolog = """ +.. |issuelink| replace:: https://github.com/webassembly/""" + repo + """/issues/ +.. |pagelink| replace:: https://webassembly.github.io/""" + repo + """/core/ +.. include:: /""" + core_dir + """/util/macros.def +.. include:: /""" + pwd + """/util/macros.def +""" + +# https://www.sphinx-doc.org/en/master/usage/extensions/math.html#confval-mathjax3_config +# https://docs.mathjax.org/en/latest/web/configuration.html#configuration +# https://docs.mathjax.org/en/latest/options/input/tex.html#tex-maxbuffer +mathjax3_config = { + 'tex': { 'maxBuffer': 30*1024 }, +} diff --git a/document/metadata/code/index.rst b/document/metadata/code/index.rst new file mode 100644 index 0000000000..7485244829 --- /dev/null +++ b/document/metadata/code/index.rst @@ -0,0 +1,15 @@ +WebAssembly Code Metadata +========================= + +.. only:: html + + | Editor: Yuri Iozzelli + + | Issue Tracker: |WasmIssues| + +.. toctree:: + :maxdepth: 1 + + intro + binary + text diff --git a/document/metadata/code/intro.rst b/document/metadata/code/intro.rst new file mode 100644 index 0000000000..59503b413e --- /dev/null +++ b/document/metadata/code/intro.rst @@ -0,0 +1,18 @@ +.. _intro: + +Introduction +============ + +This document defines a generic mechanism for attaching arbitrary metadata to WebAssembly instructions. +Additionally, it defines specific metadata formats using this mechanism. + +Such metadata does not contribute to, or otherwise affect, the WebAssembly semantics, and may be ignored by an implementation. + +However, it can provides useful information that implementations can make use of to improve user experience or take compilation hints. + +Dependencies +~~~~~~~~~~~~ + +This document is based on the WebAssembly core specification (|WasmDraft|), and makes use of +terms and definitions described there. These uses always link to the corresponding definition +in the core specification. diff --git a/document/metadata/code/static/custom.css b/document/metadata/code/static/custom.css new file mode 100644 index 0000000000..33bb863d42 --- /dev/null +++ b/document/metadata/code/static/custom.css @@ -0,0 +1,78 @@ +a { + color: #004BAB; + text-decoration: none; +} + +a.reference { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px dotted #004BAB; +} + +body { + font-size: 15px; +} + +div.document { width: 1000px; } +div.bodywrapper { margin: 0 0 0 200px; } +div.body { padding: 0 10px 0 10px; } +div.footer { width: 1000px; } + +div.body h1 { font-size: 200%; } +div.body h2 { font-size: 150%; } +div.body h3 { font-size: 120%; } +div.body h4 { font-size: 110%; } + +div.note { + border: 0px; + font-size: 90%; + background-color: #F6F8FF; +} + +div.admonition { + padding: 10px; +} + +div.admonition p.admonition-title { + margin: 0px 0px 0px 0px; + font-size: 100%; + font-weight: bold; +} + +div.math { + background-color: #F0F0F0; + padding: 3px 0 3px 0; + overflow-x: auto; + overflow-y: hidden; +} + +div.relations { + display: block; +} + +div.sphinxsidebar { + z-index: 1; + background: #FFF; + margin-top: -30px; + font-size: 13px; + width: 200px; + height: 100%; +} + +div.sphinxsidebarwrapper p.logo { + padding: 30px 40px 10px 0px; +} + +div.sphinxsidebar h3 { + font-size: 0px; +} + +div.sphinxsidebar a { + border-bottom: 0px; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px dotted; +} diff --git a/document/metadata/code/static/webassembly.png b/document/metadata/code/static/webassembly.png new file mode 100644 index 0000000000..f9edc61098 Binary files /dev/null and b/document/metadata/code/static/webassembly.png differ diff --git a/document/metadata/code/text.rst b/document/metadata/code/text.rst new file mode 100644 index 0000000000..c5ff1fbd04 --- /dev/null +++ b/document/metadata/code/text.rst @@ -0,0 +1,24 @@ +.. _text: + +Text Format +=========== + +.. _text-codemetadata: + +Code Metadata +------------- + +Code Metadata items appear in the text format as custom annotations, and are considered +attached to the first instruction that follows them. + + +.. math:: + \begin{array}{llclll} + \production{code metadata annotation} & \Tcodemetadataannot(\B{T}) &::=& + \text{(@metadata.code.T}~\X{data}{:}\B{T}~\text{)} \\ + \end{array} +.. index:: ! code metadata annotation + +Where `T` is the format name of the item, and `data` is a byte string containing the same +payload as in the binary format. + diff --git a/document/metadata/code/util/macros.def b/document/metadata/code/util/macros.def new file mode 100644 index 0000000000..fbdd841402 --- /dev/null +++ b/document/metadata/code/util/macros.def @@ -0,0 +1,44 @@ +.. LINK MACROS + +.. External Standards +.. ------------------ + +.. External Definitions +.. -------------------- + + +.. MATH MACROS + +.. Abstract Syntax +.. --------------- + +.. Values, non-terminals + + +.. Binary Format +.. ------------- + +.. Code Metadata, non-terminals + +.. |Bcodemetadatasec| mathdef:: \xref{binary}{binary-codemetadata}{\B{codemetadatasec}} +.. |Bcodemetadata| mathdef:: \xref{binary}{binary-codemetadata}{\B{codemetadata}} +.. |Bcodemetadatafunc| mathdef:: \xref{binary}{binary-codemetadata}{\B{codemetadatafunc}} +.. |Bcodemetadataitem| mathdef:: \xref{binary}{binary-codemetadata}{\B{codemetadataitem}} + +.. Branch Hints, non-terminals + +.. |Bbranchhintsec| mathdef:: \xref{binary}{binary-branchhints}{\B{branchhintsec}} +.. |Bbranchhint| mathdef:: \xref{binary}{binary-branchhints}{\B{branchhint}} +.. |Bunlikely| mathdef:: \xref{binary}{binary-branchhints}{\B{unlikely}} +.. |Blikely| mathdef:: \xref{binary}{binary-branchhints}{\B{likely}} + + +.. Text Format +.. ----------- + + \production{code metadata annotation} & \Tcodemetadataannot(\B{T}) &::=& + +.. Branch Hints, non-terminals + +.. |Tcodemetadataannot| mathdef:: \xref{text}{text-codemetadata}{\T{codemetadataannot}} + diff --git a/document/metadata/code/util/mathdef.py b/document/metadata/code/util/mathdef.py new file mode 100644 index 0000000000..57a6cf043d --- /dev/null +++ b/document/metadata/code/util/mathdef.py @@ -0,0 +1,128 @@ +from sphinx.directives.patches import MathDirective +from sphinx.util.texescape import tex_replace_map +from sphinx.writers.html5 import HTML5Translator +from sphinx.writers.latex import LaTeXTranslator +from docutils import nodes +from docutils.nodes import math +from docutils.parsers.rst.directives.misc import Replace +from six import text_type +import re + + +# Transform \xref in math nodes + +xref_re = re.compile('\\\\xref\{([^}]*)\}\{([^}]*)\}', re.M) + +def html_hyperlink(file, id): + if "/" in file: + return '\\href{../../core/%s.html#%s}' % (file, id.replace('_', '-')) + else: + return '\\href{%s.html#%s}' % (file, id.replace('_', '-')) + +def html_transform_math_xref(node): + new_text = xref_re.sub(lambda m: html_hyperlink(m.group(1), m.group(2)), node.astext()) + node.children[0] = nodes.Text(new_text) + +# Mirrors sphinx/writers/latex +def latex_hyperlink(file, id): + id = text_type(id).translate(tex_replace_map).\ + encode('ascii', 'backslashreplace').decode('ascii').\ + replace('_', '-').replace('\\', '_') + if "/" in file: + return '\\hyperref[Core-%s:%s]' % (file, id) + else: + return '\\hyperref[%s:%s]' % (file, id) + +def latex_transform_math_xref(node): + new_text = xref_re.sub(lambda m: latex_hyperlink(m.group(1), m.group(2)), node.astext()) + node.children[0] = nodes.Text(new_text) + +# Expand mathdef names in math roles and directives + +def_re = re.compile('\\\\[A-Za-z][0-9A-Za-z]*', re.M) + +auxcounter = 0 + +def lookup_mathdef(defs, name): + if name in defs: + [arity, s] = defs[name] + if arity > 0: + global auxcounter + auxcounter = auxcounter + 1 + name = "\\mathdef%d" % auxcounter + s = "\\def%s#%d{%s}%s" % (name, arity, s, name) + return s + return name + +def replace_mathdefs(doc, s): + if not hasattr(doc, 'mathdefs'): + return s + return def_re.sub(lambda m: lookup_mathdef(doc.mathdefs, m.group(0)), s) + +def ext_math_role(role, raw, text, line, inliner, options = {}, content = []): + text = replace_mathdefs(inliner.document, raw.split('`')[1]) + return [math(raw, text)], [] + +class ExtMathDirective(MathDirective): + def run(self): + doc = self.state.document + for i, s in enumerate(self.content): + self.content[i] = replace_mathdefs(doc, s) + for i, s in enumerate(self.arguments): + self.arguments[i] = replace_mathdefs(doc, s) + return super().run() + +class MathdefDirective(Replace): + def run(self): + name = '\\' + self.state.parent.rawsource.split('|')[1] + name = name.split('#') + if len(name) > 1: + arity = int(name[1]) + else: + arity = 0 + name = name[0] + doc = self.state.document + if not hasattr(doc, 'mathdefs'): + doc.mathdefs = {} + # TODO: we don't ever hit the case where len(self.content) > 1 + for i, s in enumerate(self.content): + self.content[i] = replace_mathdefs(doc, s) + doc.mathdefs[name] = [arity, ''.join(self.content)] + self.content[0] = ':math:`' + self.content[0] + self.content[-1] = self.content[-1] + '`' + return super().run() + +class WebAssemblyHTML5Translator(HTML5Translator): + """ + Customize HTML5Translator. + Convert xref in math and math block nodes to hrefs. + """ + def visit_math(self, node, math_env = ''): + html_transform_math_xref(node) + super().visit_math(node, math_env) + + def visit_math_block(self, node, math_env = ''): + html_transform_math_xref(node) + super().visit_math_block(node, math_env) + +class WebAssemblyLaTeXTranslator(LaTeXTranslator): + """ + Customize LaTeXTranslator. + Convert xref in math and math block nodes to hyperrefs. + """ + def visit_math(self, node): + latex_transform_math_xref(node) + super().visit_math(node) + + def visit_math_block(self, node): + latex_transform_math_xref(node) + super().visit_math_block(node) + +# Setup + +def setup(app): + app.set_translator('html', WebAssemblyHTML5Translator) + app.set_translator('latex', WebAssemblyLaTeXTranslator) + app.add_role('math', ext_math_role) + app.add_directive('math', ExtMathDirective, override = True) + app.add_directive('mathdef', MathdefDirective) diff --git a/document/util/htmldiff.pl b/document/util/htmldiff.pl index 4c2780c83f..31ecafef8d 100755 --- a/document/util/htmldiff.pl +++ b/document/util/htmldiff.pl @@ -48,7 +48,7 @@ # also requires the perl modules Getopt::Std # # NOTE: The markup created by htmldiff may not validate against the HTML 4.0 -# DTD. This is because the algorithm is realtively simple, and there are +# DTD. This is because the algorithm is relatively simple, and there are # places in the markup content model where the span element is not allowed. # Htmldiff is NOT aware of these places. # diff --git a/interpreter/Makefile b/interpreter/Makefile index dfd9064bbf..30c7989362 100644 --- a/interpreter/Makefile +++ b/interpreter/Makefile @@ -24,7 +24,8 @@ JS = # set to JS shell command to run JS tests, empty to skip .PHONY: default all ci jslib zip default: $(NAME) -all: default partest +all: default alltest +alltest: unittest partest custompartest ci: all jslib zip jslib: $(JSLIB) @@ -59,9 +60,10 @@ unittest: $(UNITTESTS:%=unittest/%) unittest/%: dune build $(@F).exe dune exec ./$(@F).exe + @echo All unit tests passed. -# Test suite +# Core test suite TESTDIR = ../test/core TESTFILES = $(shell cd $(TESTDIR) > /dev/null; ls *.wast; ls [a-z]*/*.wast) @@ -69,7 +71,7 @@ TESTS = $(TESTFILES:%.wast=%) .PHONY: test partest quiettest -test: $(NAME) unittest +test: $(NAME) $(TESTDIR)/run.py --wasm `pwd`/$(NAME) $(if $(JS),--js '$(JS)',) test/%: $(NAME) @@ -78,7 +80,7 @@ test/%: $(NAME) run/%: $(NAME) ./$(NAME) $(TESTDIR)/$*.wast -partest: $(NAME) unittest +partest: $(NAME) make -j10 quiettest quiettest: $(TESTS:%=quiettest/%) @@ -92,6 +94,36 @@ quiettest/%: $(NAME) (cat $(@F).out && rm $(@F).out && exit 1) +# Custom test suite + +CUSTOMTESTDIR = ../test/custom +CUSTOMTESTDIRS = $(shell cd $(CUSTOMTESTDIR); ls -d [a-z]*) +CUSTOMTESTFILES = $(shell cd $(CUSTOMTESTDIR); ls [a-z]*/*.wast) +CUSTOMTESTS = $(CUSTOMTESTFILES:%.wast=%) +CUSTOMOPTS = -c custom $(CUSTOMTESTDIRS:%=-c %) + +.PHONY: customtest custompartest customquiettest + +customtest: $(NAME) + $(TESTDIR)/run.py --wasm `pwd`/$(NAME) --opts '$(CUSTOMOPTS)' $(if $(JS),--js '$(JS)',) $(CUSTOMTESTFILES:%=$(CUSTOMTESTDIR)/%) + +customtest/%: $(NAME) + $(TESTDIR)/run.py --wasm `pwd`/$(NAME) --opts '$(CUSTOMOPTS) ' $(if $(JS),--js '$(JS)',) $(CUSTOMTESTDIR)/$*.wast + +customrun/%: $(NAME) + ./$(NAME) $(CUSTOMOPTS) $(CUSTOMTESTDIR)/$*.wast + +custompartest: $(CUSTOMTESTS:%=customquiettest/%) + @echo All custom tests passed. + +customquiettest/%: $(NAME) + @ ( \ + $(TESTDIR)/run.py 2>$(@F).out --wasm `pwd`/$(NAME) --opts '$(CUSTOMOPTS)' $(if $(JS),--js '$(JS)',) $(CUSTOMTESTDIR)/$*.wast && \ + rm $(@F).out \ + ) || \ + cat $(@F).out || rm $(@F).out || exit 1 + + # Packaging .PHONY: install diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 1dc343df6c..a22b09c500 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -303,8 +303,7 @@ let memory_type s = let tag_type s = zero s; - let et = heap_type s in - TagT et + at var s let global_type s = let t = val_type s in @@ -1043,20 +1042,20 @@ let id s = let bo = peek s in Lib.Option.map (function - | 0 -> `CustomSection - | 1 -> `TypeSection - | 2 -> `ImportSection - | 3 -> `FuncSection - | 4 -> `TableSection - | 5 -> `MemorySection - | 6 -> `GlobalSection - | 7 -> `ExportSection - | 8 -> `StartSection - | 9 -> `ElemSection - | 10 -> `CodeSection - | 11 -> `DataSection - | 12 -> `DataCountSection - | 13 -> `TagSection + | 0 -> Custom.Custom + | 1 -> Custom.Type + | 2 -> Custom.Import + | 3 -> Custom.Func + | 4 -> Custom.Table + | 5 -> Custom.Memory + | 6 -> Custom.Global + | 7 -> Custom.Export + | 8 -> Custom.Start + | 9 -> Custom.Elem + | 10 -> Custom.Code + | 11 -> Custom.Data + | 12 -> Custom.DataCount + | 13 -> Custom.Tag | _ -> error s (pos s) "malformed section id" ) bo @@ -1074,7 +1073,7 @@ let section tag f default s = let type_ s = at rec_type s let type_section s = - section `TypeSection (vec type_) [] s + section Custom.Type (vec type_) [] s (* Import section *) @@ -1095,13 +1094,13 @@ let import s = {module_name; item_name; idesc} let import_section s = - section `ImportSection (vec (at import)) [] s + section Custom.Import (vec (at import)) [] s (* Function section *) let func_section s = - section `FuncSection (vec (at var)) [] s + section Custom.Func (vec (at var)) [] s (* Table section *) @@ -1123,7 +1122,7 @@ let table s = ] s let table_section s = - section `TableSection (vec (at table)) [] s + section Custom.Table (vec (at table)) [] s (* Memory section *) @@ -1133,16 +1132,18 @@ let memory s = {mtype} let memory_section s = - section `MemorySection (vec (at memory)) [] s + section Custom.Memory (vec (at memory)) [] s + (* Tag section *) let tag s = - let tagtype = tag_type s in - {tagtype} + let tgtype = tag_type s in + {tgtype} let tag_section s = - section `TagSection (vec (at tag)) [] s + section Custom.Tag (vec (at tag)) [] s + (* Global section *) @@ -1153,7 +1154,7 @@ let global s = {gtype; ginit} let global_section s = - section `GlobalSection (vec (at global)) [] s + section Custom.Global (vec (at global)) [] s (* Export section *) @@ -1173,7 +1174,7 @@ let export s = {name; edesc} let export_section s = - section `ExportSection (vec (at export)) [] s + section Custom.Export (vec (at export)) [] s (* Start section *) @@ -1183,7 +1184,7 @@ let start s = {sfunc} let start_section s = - section `StartSection (opt (at start) true) None s + section Custom.Start (opt (at start) true) None s (* Code section *) @@ -1195,7 +1196,7 @@ let code _ s = {locals; body; ftype = -1l @@ no_region} let code_section s = - section `CodeSection (vec (at (sized code))) [] s + section Custom.Code (vec (at (sized code))) [] s (* Element section *) @@ -1268,7 +1269,7 @@ let elem s = | _ -> error s (pos s - 1) "malformed elements segment kind" let elem_section s = - section `ElemSection (vec (at elem)) [] s + section Custom.Elem (vec (at elem)) [] s (* Data section *) @@ -1290,7 +1291,7 @@ let data s = | _ -> error s (pos s - 1) "malformed data segment kind" let data_section s = - section `DataSection (vec (at data)) [] s + section Custom.Data (vec (at data)) [] s (* DataCount section *) @@ -1299,64 +1300,66 @@ let data_count s = Some (u32 s) let data_count_section s = - section `DataCountSection data_count None s + section Custom.DataCount data_count None s (* Custom section *) -let custom size s = +let custom place size s = let start = pos s in - let id = name s in - let bs = get_string (size - (pos s - start)) s in - Some (id, bs) + let name = name s in + let content = get_string (size - (pos s - start)) s in + Custom.{name; content; place} -let custom_section s = - section_with_size `CustomSection custom None s +let some_custom place size s = + Some (at (custom place size) s) -let non_custom_section s = - match id s with - | None | Some `CustomSection -> None - | _ -> skip 1 s; sized skip s; Some () +let custom_section place s = + section_with_size Custom.Custom (some_custom place) None s (* Modules *) -let rec iterate f s = if f s <> None then iterate f s +let rec iterate f s = + match f s with + | None -> [] + | Some x -> x :: iterate f s let magic = 0x6d736100l let module_ s = + let open Custom in let header = word32 s in require (header = magic) s 0 "magic header not detected"; let version = word32 s in require (version = Encode.version) s 4 "unknown binary version"; - iterate custom_section s; + let customs = iterate (custom_section (Before Type)) s in let types = type_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Type)) s in let imports = import_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Import)) s in let func_types = func_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Func)) s in let tables = table_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Table)) s in let memories = memory_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Memory)) s in let tags = tag_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Tag)) s in let globals = global_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Global)) s in let exports = export_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Export)) s in let start = start_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Start)) s in let elems = elem_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Elem)) s in let data_count = data_count_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After DataCount)) s in let func_bodies = code_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Code)) s in let datas = data_section s in - iterate custom_section s; + let customs = customs @ iterate (custom_section (After Data)) s in require (pos s = len s) s (len s) "unexpected content after last section"; require (List.length func_types = List.length func_bodies) s (len s) "function and code section have inconsistent lengths"; @@ -1366,23 +1369,37 @@ let module_ s = List.for_all Free.(fun f -> (func f).datas = Set.empty) func_bodies) s (len s) "data count section required"; let funcs = - List.map2 (fun t f -> {f.it with ftype = t} @@ f.at) func_types func_bodies - in { types; tables; memories; globals; tags; funcs; - imports; exports; elems; datas; start } - -let decode name bs = at module_ (stream name bs) - -let all_custom tag s = - let header = word32 s in - require (header = magic) s 0 "magic header not detected"; - let version = word32 s in - require (version = Encode.version) s 4 "unknown binary version"; - let rec collect () = - iterate non_custom_section s; - match custom_section s with - | None -> [] - | Some (n, s) when n = tag -> s :: collect () - | Some _ -> collect () - in collect () - -let decode_custom tag name bs = all_custom tag (stream name bs) + List.map2 Source.(fun t f -> {f.it with ftype = t} @@ f.at) + func_types func_bodies + in + { types; tables; memories; tags; globals; funcs; + imports; exports; elems; datas; start }, + customs + +let decode_custom m bs custom = + let open Source in + let Custom.{name; content; place} = custom.it in + match Custom.handler name, Custom.handler (Utf8.decode "custom") with + | Some (module Handler), _ -> + let fmt = Handler.decode m bs custom in + let module S = struct module Handler = Handler let it = fmt end in + [(module S : Custom.Section)] + | None, Some (module Handler') -> + let fmt = Handler'.decode m bs custom in + let module S = struct module Handler = Handler' let it = fmt end in + [(module S : Custom.Section)] + | None, None -> + if !Flags.custom_reject then + raise (Custom.Code (custom.at, + "unknown custom section \"" ^ Utf8.encode name ^ "\"")) + else + [] + +let decode_with_custom name bs = + let m_cs = at module_ (stream name bs) in + let open Source in + let m', cs = m_cs.it in + let m = m' @@ m_cs.at in + m, List.flatten (List.map (decode_custom m bs) cs) + +let decode name bs = fst (decode_with_custom name bs) diff --git a/interpreter/binary/decode.mli b/interpreter/binary/decode.mli index 4460023d25..b02b332364 100644 --- a/interpreter/binary/decode.mli +++ b/interpreter/binary/decode.mli @@ -1,5 +1,4 @@ exception Code of Source.region * string val decode : string -> string -> Ast.module_ (* raises Code *) - -val decode_custom : Ast.name -> string -> string -> string list (* raises Code *) +val decode_with_custom : string -> string -> Ast.module_ * Custom.section list (* raises Code *) diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index 3392165fd9..acbe716e2b 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -98,6 +98,8 @@ struct open Types open Source + let var x = u32 x.it + let mutability = function | Cons -> byte 0 | Var -> byte 1 @@ -215,8 +217,7 @@ struct let global_type = function | GlobalT (mut, t) -> val_type t; mutability mut - let tag_type = function - | TagT ht -> byte 0x00; heap_type ht + let tag_type x = u32 0x00l; var x (* Expressions *) @@ -993,7 +994,7 @@ struct (* Tag section *) let tag tag = - tag_type tag.it.tagtype + tag_type tag.it.tgtype let tag_section ts = section 13 (vec tag) ts (ts <> []) @@ -1114,40 +1115,69 @@ struct (* Custom section *) - - let custom (n, bs) = + let custom c = + let Custom.{name = n; content; _} = c.it in name n; - put_string s bs + put_string s content - let custom_section n bs = - section 0 custom (n, bs) true + let custom_section place c = + let here = Custom.(compare_place c.it.place place) <= 0 in + if here then section 0 custom c true; + here (* Module *) + let rec iterate f xs = + match xs with + | [] -> [] + | x::xs' -> if f x then iterate f xs' else xs - let module_ m = + let module_ m cs = + let open Custom in word32 0x6d736100l; word32 version; + let cs = iterate (custom_section (Before Type)) cs in type_section m.it.types; + let cs = iterate (custom_section (Before Import)) cs in import_section m.it.imports; + let cs = iterate (custom_section (Before Func)) cs in func_section m.it.funcs; + let cs = iterate (custom_section (Before Table)) cs in table_section m.it.tables; + let cs = iterate (custom_section (Before Memory)) cs in memory_section m.it.memories; + let cs = iterate (custom_section (Before Tag)) cs in tag_section m.it.tags; + let cs = iterate (custom_section (Before Global)) cs in global_section m.it.globals; + let cs = iterate (custom_section (Before Export)) cs in export_section m.it.exports; + let cs = iterate (custom_section (Before Start)) cs in start_section m.it.start; + let cs = iterate (custom_section (Before Elem)) cs in elem_section m.it.elems; + let cs = iterate (custom_section (Before DataCount)) cs in data_count_section m.it.datas m; + let cs = iterate (custom_section (Before Code)) cs in code_section m.it.funcs; - data_section m.it.datas + let cs = iterate (custom_section (Before Data)) cs in + data_section m.it.datas; + let cs = iterate (custom_section (After Data)) cs in + assert (cs = []) end +let encode_custom m bs (module S : Custom.Section) = + let open Source in + let c = S.Handler.encode m bs S.it in + Custom.{c.it with place = S.Handler.place S.it} @@ c.at + let encode m = let module E = E (struct let stream = stream () end) in - E.module_ m; to_string E.s + E.module_ m []; to_string E.s -let encode_custom name content = +let encode_with_custom (m, secs) = + let bs = encode m in let module E = E (struct let stream = stream () end) in - E.custom_section name content; to_string E.s + let cs = List.map (encode_custom m bs) secs in + E.module_ m cs; to_string E.s diff --git a/interpreter/binary/encode.mli b/interpreter/binary/encode.mli index 00fcd31640..05d1f2a677 100644 --- a/interpreter/binary/encode.mli +++ b/interpreter/binary/encode.mli @@ -2,4 +2,4 @@ exception Code of Source.region * string val version : int32 val encode : Ast.module_ -> string -val encode_custom : Ast.name -> string -> string +val encode_with_custom : Ast.module_ * Custom.section list -> string diff --git a/interpreter/custom/custom.ml b/interpreter/custom/custom.ml new file mode 100644 index 0000000000..0b6a058297 --- /dev/null +++ b/interpreter/custom/custom.ml @@ -0,0 +1,86 @@ +(* Raw custom section *) + +type section_kind = + | Custom + | Type + | Import + | Func + | Table + | Memory + | Global + | Export + | Start + | Elem + | DataCount + | Code + | Data + | Tag + +type place = + | Before of section_kind + | After of section_kind + +type custom = custom' Source.phrase +and custom' = +{ + name : Ast.name; + content : string; + place : place; +} + + +let first = Type +let last = Data + +let compare_place pl1 pl2 = + match pl1, pl2 with + | Before s1, Before s2 + | After s1, After s2 -> compare s1 s2 + | Before s1, After s2 -> if s1 = s2 then -1 else compare s1 s2 + | After s1, Before s2 -> if s1 = s2 then +1 else compare s1 s2 + + +(* Handlers *) + +exception Code of Source.region * string +exception Syntax of Source.region * string +exception Invalid of Source.region * string + +module type Handler = +sig + type format' + type format = format' Source.phrase + val name : Ast.name + val place : format -> place + val decode : Ast.module_ -> string -> custom -> format (* raise Code *) + val encode : Ast.module_ -> string -> format -> custom + val parse : Ast.module_ -> string -> Annot.annot list -> format list (* raise Syntax *) + val arrange : Ast.module_ -> Sexpr.sexpr -> format -> Sexpr.sexpr + val check : Ast.module_ -> format -> unit (* raise Invalid *) +end + +module type Section = +sig + module Handler : Handler + val it : Handler.format +end + +type section = (module Section) + +let compare_section (module S1 : Section) (module S2 : Section) = + match compare_place (S1.Handler.place S1.it) (S2.Handler.place S2.it) with + | 0 -> compare S1.it.Source.at S2.it.Source.at + | n -> n + + +(* Handler registry *) + +module Registry = Map.Make(struct type t = Ast.name let compare = compare end) + +let registry = ref Registry.empty + +let register (module H : Handler) = + registry := Registry.add H.name (module H : Handler) !registry + +let handler (name : Ast.name) : (module Handler) option = + Registry.find_opt name !registry diff --git a/interpreter/custom/handler_branch_hint.ml b/interpreter/custom/handler_branch_hint.ml new file mode 100644 index 0000000000..1f46d5de11 --- /dev/null +++ b/interpreter/custom/handler_branch_hint.ml @@ -0,0 +1,456 @@ +(* Handler for "metadata.code.branch_hint" section and @metadata.code.branch_hint annotations *) + +open Custom +open Annot +open Source +open Ast +module IdxMap = Map.Make (Int32) + +type kind = Likely | Unlikely + +type hint = hint' Source.phrase +and hint' = kind * int + +type hints = hint list +type func_hints = hints IdxMap.t + +type format = format' Source.phrase +and format' = { func_hints : func_hints } + +let empty = { func_hints = IdxMap.empty } +let name = Utf8.decode "metadata.code.branch_hint" +let place _fmt = Before Code +let is_contained r1 r2 = r1.left >= r2.left && r1.right <= r2.right + +let is_left r1 r2 = + r1.right.line < r2.left.line + || (r1.right.line == r2.left.line && r1.right.column <= r2.left.column) + +let starts_before r1 r2 = + r1.left.line < r2.left.line + || (r1.left.line == r2.left.line && r1.left.column < r2.left.column) + +let get_func m fidx = + let nimp = List.length m.it.imports in + let fn = Int32.to_int fidx - nimp in + List.nth m.it.funcs fn + +let flatten_instr_locs is = + let rec flatten is = + match is with + | [] -> [] + | i :: rest -> + let group = + match i.it with + | Block (_, inner) -> [ i ] @ flatten inner + | Loop (_, inner) -> [ i ] @ flatten inner + | If (_, inner1, inner2) -> [ i ] @ flatten inner1 @ flatten inner2 + | _ -> [ i ] + in + group @ flatten rest + in + let flat = flatten is in + let indexed = List.mapi (fun idx i -> (idx, i)) flat in + let sorter (_, i1) (_, i2) = if starts_before i1.at i2.at then -1 else 1 in + let sorted = List.sort sorter indexed in + sorted + +let get_inst_idx locs at = + let finder (i, l) = is_left at l.at in + let o = List.find_opt finder locs in + match o with Some (i, _) -> Some i | None -> None + +let get_nth_inst locs idx = + match List.find_opt (fun (i_idx, i) -> i_idx == idx) locs with + | Some (_, i) -> Some i + | None -> None + +(* Decoding *) + +(* TODO: make Decode module reusable instead of duplicating code *) + +type stream = { bytes : string; pos : int ref } + +exception EOS + +let stream bs = { bytes = bs; pos = ref 0 } +let len s = String.length s.bytes +let pos s = !(s.pos) +let check n s = if pos s + n > len s then raise EOS + +let skip n s = + if n < 0 then raise EOS else check n s; + s.pos := !(s.pos) + n + +let read s = Char.code s.bytes.[!(s.pos)] + +let get s = + check 1 s; + let b = read s in + skip 1 s; + b + +let position file pos = Source.{ file; line = -1; column = pos } + +let region file left right = + Source.{ left = position file left; right = position file right } + +let decode_error pos msg = + raise (Custom.Code (region "@metadata.code.branch_hint section" pos pos, msg)) + +let require b pos msg = if not b then decode_error pos msg +let decode_byte s = get s + +let rec decode_uN n s = + require (n > 0) (pos s) "integer representation too long"; + let b = decode_byte s in + require (n >= 7 || b land 0x7f < 1 lsl n) (pos s - 1) "integer too large"; + let x = Int32.of_int (b land 0x7f) in + if b land 0x80 = 0 then x + else Int32.(logor x (shift_left (decode_uN (n - 7) s) 7)) + +let decode_u32 = decode_uN 32 + +let decode_vec f s = + let n = decode_u32 s in + let n = Int32.to_int n in + let rec it i s = if i = 0 then [] else [ f s ] @ it (i - 1) s in + it n s + +let decode_hint locs foff s = + let off = decode_u32 s in + let one = decode_u32 s in + require (one = 1l) (pos s) + "@metadata.code.branch_hint section: missing reserved byte"; + let k = decode_byte s in + let hint = + match k with + | 0x00 -> Unlikely + | 0x01 -> Likely + | _ -> + decode_error (pos s) + "@metadata.code.branch_hint section: invalid hint value" + in + let abs_off = Int32.to_int (Int32.add off foff) in + let at = region "" abs_off abs_off in + let idx = + match get_inst_idx locs at with + | Some i -> i + | None -> + decode_error (pos s) + "@metadata.code.branch_hint section: invalid offset" + in + (hint, idx) @@ at + +let decode_func_hints locs foff = decode_vec (decode_hint locs foff) + +let decode_func m s = + let fidx = decode_u32 s in + let f = get_func m fidx in + let foff = Int32.of_int f.at.left.column in + let locs = flatten_instr_locs f.it.body in + let hs = decode_func_hints locs foff s in + (fidx, List.rev hs) + +let decode_funcs m s = + let fs = decode_vec (decode_func m) s in + IdxMap.add_seq (List.to_seq fs) IdxMap.empty + +let decode m _ custom = + let s = stream custom.it.content in + try { func_hints = decode_funcs m s } @@ custom.at + with EOS -> decode_error (pos s) "unexpected end of name section" + +(* Encoding *) + +(* TODO: make Encode module reusable *) + +let encode_byte buf b = Buffer.add_char buf (Char.chr b) + +let rec encode_u32 buf i = + let b = Int32.(to_int (logand i 0x7fl)) in + if 0l <= i && i < 128l then encode_byte buf b + else ( + encode_byte buf (b lor 0x80); + encode_u32 buf (Int32.shift_right_logical i 7)) + +let encode_size buf n = encode_u32 buf (Int32.of_int n) + +let encode_vec buf f v = + encode_size buf (List.length v); + let rec it v = + match v with + | [] -> () + | e :: es -> + f buf e; + it es + in + it v + +let encode_hint locs foff buf h = + let kind, idx = h.it in + let i = + match get_nth_inst locs idx with Some i -> i | None -> assert false + in + let off = i.at.left.column - foff in + encode_size buf off; + encode_u32 buf 1l; + let b = match kind with Unlikely -> 0l | Likely -> 1l in + encode_u32 buf b + +let encode_func_hints buf locs foff = encode_vec buf (encode_hint locs foff) + +let encode_func m buf t = + let fidx, hs = t in + encode_u32 buf fidx; + let f = get_func m fidx in + let foff = f.at.left.column in + let locs = flatten_instr_locs f.it.body in + encode_func_hints buf locs foff hs + +let encode_funcs buf m fhs = + encode_vec buf (encode_func m) (List.of_seq (IdxMap.to_seq fhs)) + +let encode m bs sec = + let { func_hints } = sec.it in + let m2 = Decode.decode "" bs in + let buf = Buffer.create 200 in + encode_funcs buf m2 func_hints; + let content = Buffer.contents buf in + { + name = Utf8.decode "metadata.code.branch_hint"; + content; + place = Before Code; + } + @@ sec.at + +(* Parsing *) + +open Ast + +let parse_error at msg = raise (Custom.Syntax (at, msg)) + +let merge_func_hints = + IdxMap.merge (fun key x y -> + match (x, y) with + | Some a, None -> Some a + | None, Some b -> Some b + | Some a, Some [ ({ at = _; it = _, idx } as b) ] -> + let _, last_idx = (List.hd (List.rev a)).it in + if last_idx >= idx then + parse_error b.at + "@metadata.code.branch_hint annotation: duplicate annotation" + else Some (a @ [ b ]) + | Some _, Some _ -> assert false + | None, None -> None) + +let merge s1 s2 = + { func_hints = merge_func_hints s1.it.func_hints s2.it.func_hints } + @@ { left = s1.at.left; right = s2.at.right } + +let find_func_idx m annot = + let idx = + Lib.List.index_where (fun f -> is_contained annot.at f.at) m.it.funcs + in + match idx with + | Some i -> Int32.of_int (i + List.length m.it.imports) + | None -> + parse_error annot.at + "@metadata.code.branch_hint annotation: not in a function" + +let rec parse m _bs annots = + let annots' = List.rev annots in + let ms = List.map (parse_annot m) annots' in + match ms with + | [] -> [] + | m :: ms' -> [ List.fold_left merge (empty @@ m.at) ms ] + +and parse_annot m annot = + let { name = n; items } = annot.it in + assert (n = name); + let payload a = + match a.it with + | String s -> s + | _ -> + parse_error a.at + "@metadata.code.branch_hint annotation: unexpected token" + in + let fold_payload bs a = bs ^ payload a in + let p = List.fold_left fold_payload "" items in + let at_last = (List.hd (List.rev items)).at in + let fidx = find_func_idx m annot in + let f = get_func m fidx in + let locs = flatten_instr_locs f.it.body in + let hint = + match p with + | "\x00" -> Unlikely + | "\x01" -> Likely + | _ -> + parse_error annot.at + "@metadata.code.branch_hint annotation: invalid hint value" + in + let at = Source.{ left = annot.at.left; right = at_last.right } in + let hidx = + match get_inst_idx locs at with + | Some i -> i + | None -> + parse_error annot.at + "@metadata.code.branch_hint annotation: invalid placement" + in + let e = + { func_hints = IdxMap.add fidx [ (hint, hidx) @@ at ] IdxMap.empty } + in + e @@ at + +(* Arranging *) + +let hint_to_string = function Likely -> "\"\\01\"" | Unlikely -> "\"\\00\"" + +let collect_one f hat = + let h, hidx = hat.it in + ( Int32.to_int f, + hidx, + Sexpr.Node ("@metadata.code.branch_hint ", [ Sexpr.Atom (hint_to_string h) ]) + ) + +let collect_func (f, hs) = List.map (collect_one f) hs +let collect_funcs fhs = List.concat (List.map collect_func fhs) + +let string_starts_with s1 s2 = + let s2l = String.length s2 in + let s1l = String.length s1 in + let len = Stdlib.min s2l s1l in + let s1sub = String.sub s1 0 len in + String.equal s1sub s2 + +let rec get_instrs n1 n2 = + match n2 with + | [] -> (n1, []) + | Sexpr.Atom s :: rest -> (n1 @ [ Sexpr.Atom s ], rest) + | Sexpr.Node (h, els) :: rest -> + if + string_starts_with h "type " + || String.equal h "local" + || string_starts_with h "result" + then get_instrs (n1 @ [ Sexpr.Node (h, els) ]) rest + else (n1, n2) + +let get_annot annots fidx idx h = + match !annots with + | [] -> [] + | (a_fidx, a_hidx, a_node) :: rest -> + if a_fidx = fidx && a_hidx = idx then ( + annots := rest; + [ a_node ]) + else [] + +let rec apply_instrs annots fidx curi is = + match is with + | [] -> [] + | i :: rest -> + let idx = !curi in + curi := idx + 1; + let newn = + match i with + | Sexpr.Node (h, ns) -> + let annot = get_annot annots fidx idx h in + if string_starts_with h "block" || string_starts_with h "loop" then + let pre, inner = get_instrs [] ns in + annot + @ [ Sexpr.Node (h, pre @ apply_instrs annots fidx curi inner) ] + else if string_starts_with h "if" then + match ns with + | [ Sexpr.Node (hif, nif); Sexpr.Node (helse, nelse) ] -> + let newif = apply_instrs annots fidx curi nif in + let newelse = apply_instrs annots fidx curi nelse in + annot + @ [ + Sexpr.Node + ( h, + [ + Sexpr.Node (hif, newif); Sexpr.Node (helse, newelse); + ] ); + ] + | [ + Sexpr.Node ("result", res); + Sexpr.Node (hif, nif); + Sexpr.Node (helse, nelse); + ] -> + let newif = apply_instrs annots fidx curi nif in + let newelse = apply_instrs annots fidx curi nelse in + annot + @ [ + Sexpr.Node + ( h, + [ + Sexpr.Node ("result", res); + Sexpr.Node (hif, newif); + Sexpr.Node (helse, newelse); + ] ); + ] + | _ -> assert false + else annot @ [ Sexpr.Node (h, ns) ] + | Sexpr.Atom s -> [ Sexpr.Atom s ] + in + newn @ apply_instrs annots fidx curi rest + +let apply_func nodes annots fidx = + let curi = ref 0 in + let pre, instrs = get_instrs [] nodes in + let new_instrs = apply_instrs annots fidx curi instrs in + pre @ new_instrs + +let apply_secs annots curf node = + match node with + | Sexpr.Atom a -> Sexpr.Atom a + | Sexpr.Node (head, rest) -> + if string_starts_with head "func" then ( + let ret = apply_func rest annots !curf in + curf := !curf + 1; + Sexpr.Node (head, ret)) + else Sexpr.Node (head, rest) + +let apply mnode annots curf = + match mnode with + | Sexpr.Atom a -> Sexpr.Atom a + | Sexpr.Node (h, secs) -> + Sexpr.Node (h, List.map (apply_secs annots curf) secs) + +let arrange m mnode fmt = + let annots = + ref (collect_funcs (List.of_seq (IdxMap.to_seq fmt.it.func_hints))) + in + let curf = ref 0 in + let ret = apply mnode annots curf in + ret + +(* Checking *) + +let check_error at msg = raise (Custom.Invalid (at, msg)) + +let check_one locs prev_hidx h = + let kind, idx = h.it in + match get_nth_inst locs idx with + | None -> assert false + | Some i -> ( + match i.it with + | If _ | BrIf _ -> + if !prev_hidx >= idx then + check_error h.at + "@metadata.code.branch_hint annotation: invalid order" + else ( + prev_hidx := idx; + ()) + | _ -> + check_error h.at + "@metadata.code.branch_hint annotation: invalid target") + +let check_fun m fidx hs = + let f = get_func m fidx in + let locs = flatten_instr_locs f.it.body in + let prev_hidx = ref 0 in + List.iter (check_one locs prev_hidx) hs + +let check (m : module_) (fmt : format) = + IdxMap.iter (check_fun m) fmt.it.func_hints; + () diff --git a/interpreter/custom/handler_custom.ml b/interpreter/custom/handler_custom.ml new file mode 100644 index 0000000000..bcc6457ffa --- /dev/null +++ b/interpreter/custom/handler_custom.ml @@ -0,0 +1,171 @@ +(* Handler for @custom annotations *) + +open Custom +open Annot +open Source + +type format' = Custom.custom' +type format = Custom.custom + +let name = Utf8.decode "custom" + +let place fmt = fmt.it.place + + +(* Decoding & encoding *) + +let decode_content m custom = + let Custom.{name; content; place} = custom.it in + match Custom.handler name with + | Some (module Handler) -> + let module S = + struct + module Handler = Handler + let it = Handler.decode m "" custom + end + in Some (module S : Custom.Section) + | None -> + if !Flags.custom_reject then + raise (Custom.Code (custom.at, + "unknown custom section \"" ^ Utf8.encode name ^ "\"")) + else + None + +let decode m _bs custom = + ignore (decode_content m custom); + custom + +let encode _m _bs custom = custom + + +(* Parsing *) + +let parse_error at msg = raise (Custom.Syntax (at, msg)) + +let rec parse m _bs annots = List.map (parse_annot m) annots + +and parse_annot m annot = + let {name = n; items} = annot.it in + assert (n = name); + let cname, items' = parse_name annot.at items in + let place, items'' = parse_place_opt items' in + let content, items''' = parse_content items'' in + parse_end items'''; + let Ast.{types; tags; globals; tables; memories; funcs; start; + elems; datas; imports; exports} = m.it in + let outside x = + if annot.at.left >= x.at.left && annot.at.right <= x.at.right then + parse_error annot.at "misplaced @custom annotation" + in + List.iter outside types; + List.iter outside tags; + List.iter outside globals; + List.iter outside tables; + List.iter outside memories; + List.iter outside funcs; + List.iter outside (Option.to_list start); + List.iter outside elems; + List.iter outside datas; + List.iter outside imports; + List.iter outside exports; + let custom = {name = cname; content; place} @@ annot.at in + ignore (decode_content m custom); + custom + +and parse_name at = function + | {it = String s; at} :: items -> + (try Utf8.decode s, items with Utf8.Utf8 -> + parse_error at "@custom annotation: malformed UTF-8 encoding" + ) + | _ -> + parse_error at "@custom annotation: missing section name" + +and parse_place_opt = function + | {it = Parens items'; at} :: items -> + let dir, items'' = parse_direction at items' in + let sec, items''' = parse_section at items'' in + parse_end items'''; + dir sec, items + | items -> + After last, items + +and parse_direction at = function + | {it = Atom "before"; _} :: items -> (fun sec -> Before sec), items + | {it = Atom "after"; _} :: items -> (fun sec -> After sec), items + | _ -> + parse_error at "@custom annotation: malformed placement" + +and parse_section at = function + | {it = Atom "type"; _} :: items -> Type, items + | {it = Atom "tag"; _} :: items -> Tag, items + | {it = Atom "import"; _} :: items -> Import, items + | {it = Atom "func"; _} :: items -> Func, items + | {it = Atom "table"; _} :: items -> Table, items + | {it = Atom "memory"; _} :: items -> Memory, items + | {it = Atom "global"; _} :: items -> Global, items + | {it = Atom "export"; _} :: items -> Export, items + | {it = Atom "start"; _} :: items -> Start, items + | {it = Atom "elem"; _} :: items -> Elem, items + | {it = Atom "code"; _} :: items -> Code, items + | {it = Atom "data"; _} :: items -> Data, items + | {it = Atom "datacount"; _} :: items -> DataCount, items + | {it = Atom "first"; _} :: items -> first, items + | {it = Atom "last"; _} :: items -> last, items + | _ -> + parse_error at "@custom annotation: malformed section kind" + +and parse_content = function + | {it = String bs; _} :: items -> + let bs', items' = parse_content items in + bs ^ bs', items' + | items -> "", items + +and parse_end = function + | [] -> () + | item :: _ -> + parse_error item.at "@custom annotation: unexpected token" + + +(* Printing *) + +open Sexpr + +let rec arrange _m mnode custom = + let {name; content; place} = custom.it in + let node = Node ("@custom " ^ Arrange.name name, + arrange_place place :: Arrange.break_bytes content + ) in + match mnode with + | Sexpr.Atom _ -> assert false + | Node (name, secs) -> Node (name, secs @ [node]) + +and arrange_place = function + | Before sec -> Node ("before", [Atom (arrange_sec sec)]) + | After sec -> Node ("after", [Atom (arrange_sec sec)]) + +and arrange_sec = function + | Custom -> assert false + | Type -> "type" + | Tag -> "tag" + | Import -> "import" + | Func -> "func" + | Table -> "table" + | Memory -> "memory" + | Global -> "global" + | Export -> "export" + | Start -> "start" + | Elem -> "elem" + | Code -> "code" + | Data -> "data" + | DataCount -> "datacount" + + +(* Checking *) + +let check m custom = + let {place; _} = custom.it in + assert (compare_place place (After Custom) > 0); + match decode_content m custom with + | None -> () + | Some (module S : Custom.Section) -> + S.Handler.check m S.it diff --git a/interpreter/custom/handler_custom.mli b/interpreter/custom/handler_custom.mli new file mode 100644 index 0000000000..618ad1e4bd --- /dev/null +++ b/interpreter/custom/handler_custom.mli @@ -0,0 +1 @@ +include Custom.Handler with type format' = Custom.custom' diff --git a/interpreter/custom/handler_name.ml b/interpreter/custom/handler_name.ml new file mode 100644 index 0000000000..dde5e28d79 --- /dev/null +++ b/interpreter/custom/handler_name.ml @@ -0,0 +1,454 @@ +(* Handler for "name" section and @name annotations *) + +open Custom +open Annot +open Source + +module IdxMap = Map.Make(Int32) + +type name = Ast.name Source.phrase +type name_map = name IdxMap.t +type indirect_name_map = name_map Source.phrase IdxMap.t + +type format = format' Source.phrase +and format' = +{ + module_ : name option; + funcs : name_map; + locals : indirect_name_map; + types : name_map; + fields : indirect_name_map; + tags : name_map; +} + + +let empty = +{ + module_ = None; + funcs = IdxMap.empty; + locals = IdxMap.empty; + types = IdxMap.empty; + fields = IdxMap.empty; + tags = IdxMap.empty; +} + +let name = Utf8.decode "name" + +let place _fmt = After last + + +(* Decoding *) + +(* TODO: make Decode module reusable instead of duplicating code *) + +type stream = {bytes : string; pos : int ref} + +exception EOS + +let stream bs = {bytes = bs; pos = ref 0} + +let len s = String.length s.bytes +let pos s = !(s.pos) +let eos s = (pos s = len s) + +let check n s = if pos s + n > len s then raise EOS +let skip n s = if n < 0 then raise EOS else check n s; s.pos := !(s.pos) + n + +let read s = Char.code (s.bytes.[!(s.pos)]) +let peek s = if eos s then None else Some (read s) +let get s = check 1 s; let b = read s in skip 1 s; b +let get_string n s = let i = pos s in skip n s; String.sub s.bytes i n + +let position pos = Source.{file = "@name section"; line = -1; column = pos} +let region left right = Source.{left = position left; right = position right} + +let at f s = + let left = pos s in + let x = f s in + let right = pos s in + Source.(x @@ region left right) + +let decode_error pos msg = raise (Custom.Code (region pos pos, msg)) +let require b pos msg = if not b then decode_error pos msg + +let decode_byte s = + get s + +let rec decode_uN n s = + require (n > 0) (pos s) "integer representation too long"; + let b = decode_byte s in + require (n >= 7 || b land 0x7f < 1 lsl n) (pos s - 1) "integer too large"; + let x = Int32.of_int (b land 0x7f) in + if b land 0x80 = 0 then x else + Int32.(logor x (shift_left (decode_uN (n - 7) s) 7)) + +let decode_u32 = decode_uN 32 + +let decode_size s = + Int32.to_int (decode_u32 s) + +let decode_name s = + let n = decode_size s in + let pos = pos s in + try Utf8.decode (get_string n s) with Utf8.Utf8 -> + decode_error pos "malformed UTF-8 encoding" + +let decode_name_assoc s = + let x = decode_u32 s in + let n = decode_name s in + (x, n) + +let decode_name_map s = + let n = decode_size s in + let m = ref IdxMap.empty in + for _ = 1 to n do + let {it = (x, name); at} = at decode_name_assoc s in + if IdxMap.mem x !m then + decode_error at.left.column "custom @name: multiple function or local names"; + m := IdxMap.add x (name @@ at) !m + done; + !m + +let decode_indirect_name_assoc s = + let x = decode_u32 s in + let m = at decode_name_map s in + (x, m) + +let decode_indirect_name_map s = + let n = decode_size s in + let m = ref IdxMap.empty in + for _ = 1 to n do + let {it = (x, m'); at} = at decode_indirect_name_assoc s in + if IdxMap.mem x !m then + decode_error at.left.column "custom @name: multiple function names"; + m := IdxMap.add x m' !m + done; + !m + +let decode_module s = Some (at decode_name s) +let decode_funcs s = decode_name_map s +let decode_locals s = decode_indirect_name_map s +let decode_types s = decode_name_map s +let decode_fields s = decode_indirect_name_map s +let decode_tags s = decode_name_map s + +let decode_subsec id f default s = + match peek s with + | None -> default + | Some id' when id' <> id -> default + | _ -> + let _id = decode_byte s in + let n = decode_size s in + let pos' = pos s in + let ss = f s in + require (pos s = pos' + n) (pos s) "name subsection size mismatch"; + ss + +let decode _m _bs custom = + let s = stream custom.it.content in + try + let module_ = decode_subsec 0 decode_module None s in + let funcs = decode_subsec 1 decode_funcs IdxMap.empty s in + let locals = decode_subsec 2 decode_locals IdxMap.empty s in + let types = decode_subsec 4 decode_types IdxMap.empty s in + let fields = decode_subsec 10 decode_fields IdxMap.empty s in + let tags = decode_subsec 11 decode_tags IdxMap.empty s in + require (eos s) (pos s) "invalid name subsection id"; + {module_; funcs; locals; types; fields; tags} @@ custom.at + with EOS -> decode_error (pos s) "unexpected end of name section" + + +(* Encoding *) + +(* TODO: make Encode module reusable *) + +let encode_byte buf b = + Buffer.add_char buf (Char.chr b) + +let rec encode_u32 buf i = + let b = Int32.(to_int (logand i 0x7fl)) in + if 0l <= i && i < 128l then encode_byte buf b + else ( + encode_byte buf (b lor 0x80); + encode_u32 buf (Int32.shift_right_logical i 7) + ) + +let encode_size buf n = + encode_u32 buf (Int32.of_int n) + +let encode_name buf n = + let s = Utf8.encode n in + encode_size buf (String.length s); + Buffer.add_string buf s + +let encode_name_assoc buf x n = + encode_u32 buf x; + encode_name buf n.it + +let encode_name_map buf m = + encode_size buf (IdxMap.cardinal m); + IdxMap.iter (encode_name_assoc buf) m + +let encode_indirect_name_assoc buf x m = + encode_u32 buf x; + encode_name_map buf m.it + +let encode_indirect_name_map buf m = + encode_size buf (IdxMap.cardinal m); + IdxMap.iter (encode_indirect_name_assoc buf) m + +let encode_subsec_begin buf id = + encode_byte buf id; + let pre = Buffer.contents buf in + Buffer.clear buf; + pre + +let encode_subsec_end buf pre = + let contents = Buffer.contents buf in + Buffer.clear buf; + Buffer.add_string buf pre; + encode_size buf (String.length contents); + Buffer.add_string buf contents + +let encode_module buf name_opt = + match name_opt with + | None -> () + | Some name -> + let subsec = encode_subsec_begin buf 0 in + encode_name buf name.it; + encode_subsec_end buf subsec + +let encode_funcs buf name_map = + if not (IdxMap.is_empty name_map) then begin + let subsec = encode_subsec_begin buf 1 in + encode_name_map buf name_map; + encode_subsec_end buf subsec + end + +let encode_locals buf name_map_map = + if not (IdxMap.is_empty name_map_map) then begin + let subsec = encode_subsec_begin buf 2 in + encode_indirect_name_map buf name_map_map; + encode_subsec_end buf subsec + end + +let encode_types buf name_map = + if not (IdxMap.is_empty name_map) then begin + let subsec = encode_subsec_begin buf 4 in + encode_name_map buf name_map; + encode_subsec_end buf subsec + end + +let encode_fields buf name_map_map = + if not (IdxMap.is_empty name_map_map) then begin + let subsec = encode_subsec_begin buf 10 in + encode_indirect_name_map buf name_map_map; + encode_subsec_end buf subsec + end + +let encode_tags buf name_map = + if not (IdxMap.is_empty name_map) then begin + let subsec = encode_subsec_begin buf 11 in + encode_name_map buf name_map; + encode_subsec_end buf subsec + end + +let encode _m _bs sec = + let {module_; funcs; locals; types; fields; tags} = sec.it in + let buf = Buffer.create 200 in + encode_module buf module_; + encode_funcs buf funcs; + encode_locals buf locals; + encode_types buf types; + encode_fields buf fields; + encode_tags buf tags; + let content = Buffer.contents buf in + {name = Utf8.decode "name"; content; place = After last} @@ sec.at + + +(* Parsing *) + +open Ast +open Types + +let parse_error at msg = raise (Custom.Syntax (at, msg)) + +let merge_name_opt n1 n2 = + match n1, n2 with + | None, None -> None + | None, some + | some, None -> some + | Some _, Some n2 -> + parse_error n2.at "@name annotation: multiple module names" + +let merge_name_map m1 m2 = + IdxMap.union (fun x _ n2 -> + parse_error n2.at "@name annotation: multiple function names" + ) m1 m2 + +let merge_indirect_name_map m1 m2 = + IdxMap.union (fun x m1' m2' -> + Some ( + IdxMap.union (fun x _ n2 -> + parse_error n2.at "@name annotation: multiple local names" + ) m1'.it m2'.it @@ {left = m1'.at.left; right = m2'.at.right} + ) + ) m1 m2 + +let merge s1 s2 = + { + module_ = merge_name_opt s1.it.module_ s2.it.module_; + funcs = merge_name_map s1.it.funcs s2.it.funcs; + locals = merge_indirect_name_map s1.it.locals s2.it.locals; + types = merge_name_map s1.it.types s2.it.types; + fields = merge_indirect_name_map s1.it.fields s2.it.fields; + tags = merge_name_map s1.it.tags s2.it.tags; + } @@ {left = s1.at.left; right = s2.at.right} + + +let is_contained r1 r2 = r1.left >= r2.left && r1.right <= r2.right +let is_left r1 r2 = r1.right <= r2.left + +let locate_func bs x name at (f : func) = + if is_left at f.it.ftype.at then + {empty with funcs = IdxMap.singleton x name} + else if f.it.body = [] || is_left at (List.hd f.it.body).at then + (* TODO re-parse the function params and locals from bs *) + parse_error at "@name annotation: local names not yet supported" + else + parse_error at "@name annotation: misplaced annotation" + +let locate_tag bs x name at (tag : tag) = + if is_left at tag.it.tgtype.at then + {empty with tags = IdxMap.singleton x name} + else + parse_error at "@name annotation: misplaced annotation" + +let locate_type bs x name at (ty : type_) = + (* TODO re-parse types from bs *) + parse_error at "@name annotation: type and field names not yet supported" + +let locate_module bs name at (m : module_) = + if not (is_contained at m.at) then + parse_error at "misplaced @name annotation"; + let {types; tags; globals; tables; memories; funcs; start; + elems; datas; imports; exports} = m.it in + let ats = + List.map (fun p -> p.at) types @ + List.map (fun p -> p.at) tags @ + List.map (fun p -> p.at) globals @ + List.map (fun p -> p.at) tables @ + List.map (fun p -> p.at) memories @ + List.map (fun p -> p.at) funcs @ + List.map (fun p -> p.at) (Option.to_list start) @ + List.map (fun p -> p.at) elems @ + List.map (fun p -> p.at) datas @ + List.map (fun p -> p.at) imports @ + List.map (fun p -> p.at) exports |> List.sort compare + in + match ats with + | [] -> {empty with module_ = Some name} + | at1::_ when is_left at at1 -> {empty with module_ = Some name} + | _ -> + match Lib.List.index_where (fun t -> is_contained at t.at) types with + | Some x -> locate_type bs (Int32.of_int x) name at (List.nth types x) + | None -> + match Lib.List.index_where (fun t -> is_contained at t.at) tags with + | Some x -> locate_tag bs (Int32.of_int x) name at (List.nth tags x) + | None -> + match Lib.List.index_where (fun f -> is_contained at f.at) funcs with + | Some x -> locate_func bs (Int32.of_int x) name at (List.nth funcs x) + | None -> parse_error at "misplaced @name annotation" + + +let rec parse m bs annots = + let ms = List.map (parse_annot m bs) annots in + match ms with + | [] -> [] + | m::ms' -> [List.fold_left merge (empty @@ m.at) ms] + +and parse_annot m bs annot = + let {name = n; items} = annot.it in + assert (n = name); + let name, items' = parse_name annot.at items in + parse_end items'; + locate_module bs name annot.at m @@ annot.at + +and parse_name at = function + | {it = String s; at} :: items -> + (try Utf8.decode s @@ at, items with Utf8.Utf8 -> + parse_error at "malformed UTF-8 encoding" + ) + | _ -> + parse_error at "@name annotation: string expected" + +and parse_end = function + | [] -> () + | item :: _ -> + parse_error item.at "@name annotation: unexpected token" + + +(* Printing *) + +let arrange m bs fmt = + (* Print as generic custom section *) + Handler_custom.arrange m bs (encode m "" fmt) + + +(* Checking *) + +let check_error at msg = raise (Custom.Invalid (at, msg)) + +let check (m : module_) (fmt : format) = + let subtypes = + List.concat (List.map (fun {it = RecT ss; _} -> ss) m.it.types) in + let comptypes = List.map (fun (SubT (_, _, ct)) -> ct) subtypes in + IdxMap.iter (fun x name -> + if I32.ge_u x (Lib.List32.length m.it.funcs) then + check_error name.at ("custom @name: invalid function index " ^ + I32.to_string_u x) + ) fmt.it.funcs; + IdxMap.iter (fun x map -> + if I32.ge_u x (Lib.List32.length m.it.funcs) then + check_error map.at ("custom @name: invalid function index " ^ + I32.to_string_u x); + let f = Lib.List32.nth m.it.funcs x in + if I32.ge_u f.it.ftype.it (Lib.List32.length comptypes) then + check_error map.at ("custom @name: invalid type index " ^ + I32.to_string_u f.it.ftype.it ^ " for function " ^ I32.to_string_u x); + let ts = + match Lib.List32.nth comptypes f.it.ftype.it with + | DefFuncT (FuncT (ts, _)) -> ts + | _ -> + check_error map.at ("custom @name: non-function type " ^ + I32.to_string_u f.it.ftype.it ^ " for function " ^ I32.to_string_u x) + in + let n = I32.add (Lib.List32.length ts) (Lib.List32.length f.it.locals) in + IdxMap.iter (fun y name -> + if I32.ge_u y n then + check_error name.at ("custom @name: invalid local index " ^ + I32.to_string_u y ^ " for function " ^ I32.to_string_u x) + ) map.it; + ) fmt.it.locals; + IdxMap.iter (fun x name -> + if I32.ge_u x (Lib.List32.length comptypes) then + check_error name.at ("custom @name: invalid type index " ^ + I32.to_string_u x) + ) fmt.it.types; + IdxMap.iter (fun x map -> + if I32.ge_u x (Lib.List32.length comptypes) then + check_error map.at ("custom @name: invalid type index " ^ + I32.to_string_u x); + let n = + match Lib.List32.nth comptypes x with + | DefStructT (StructT fs) -> Lib.List32.length fs + | _ -> + check_error map.at ("custom @name: non-struct type " ^ + I32.to_string_u x) + in + IdxMap.iter (fun y name -> + if I32.ge_u y n then + check_error name.at ("custom @name: invalid field index " ^ + I32.to_string_u y ^ " for type " ^ I32.to_string_u x) + ) map.it; + ) fmt.it.fields diff --git a/interpreter/custom/handler_name.mli b/interpreter/custom/handler_name.mli new file mode 100644 index 0000000000..caccb08bc1 --- /dev/null +++ b/interpreter/custom/handler_name.mli @@ -0,0 +1 @@ +include Custom.Handler diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index c2b36563cb..ea708d5004 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -188,8 +188,8 @@ let str_type_of_heap_type (inst : module_inst) ht : str_type = let func_type_of_cont_type (inst : module_inst) (ContT ht) : func_type = as_func_str_type (str_type_of_heap_type inst ht) -let func_type_of_tag_type (inst : module_inst) (TagT ht) : func_type = - as_func_str_type (str_type_of_heap_type inst ht) +let func_type_of_tag_type (_inst : module_inst) (TagT dt) : func_type = + as_func_str_type (expand_def_type dt) (* Evaluation *) @@ -420,7 +420,7 @@ let rec step (c : config) : config = | ThrowRef, Ref (NullRef _) :: vs -> vs, [Trapping "null exception reference" @@ e.at] - | ThrowRef, Ref (ExnRef (t, args)) :: vs -> + | ThrowRef, Ref (Exn.(ExnRef (Exn (t, args)))) :: vs -> vs, [Throwing (t, args) @@ e.at] | TryTable (bt, cs, es'), vs -> @@ -428,7 +428,7 @@ let rec step (c : config) : config = let n1 = List.length ts1 in let n2 = List.length ts2 in let args, vs' = split n1 vs e.at in - vs', [Handler (n2, cs, (args, [Label (n2, [], ([], List.map plain es')) @@ e.at])) @@ e.at] + vs', [Handler (n2, cs, ([], [Label (n2, [], (args, List.map plain es')) @@ e.at])) @@ e.at] | Drop, v :: vs' -> vs', [] @@ -1184,7 +1184,7 @@ let rec step (c : config) : config = | Handler (n, {it = CatchRef (x1, x2); _} :: cs, (vs', {it = Throwing (a, vs0); at} :: es')), vs -> if a == tag c.frame.inst x1 then - Ref (ExnRef (a, vs0)) :: vs0 @ vs, [Plain (Br x2) @@ e.at] + Ref Exn.(ExnRef (Exn (a, vs0))) :: vs0 @ vs, [Plain (Br x2) @@ e.at] else vs, [Handler (n, cs, (vs', {it = Throwing (a, vs0); at} :: es')) @@ e.at] @@ -1192,7 +1192,7 @@ let rec step (c : config) : config = vs, [Plain (Br x) @@ e.at] | Handler (n, {it = CatchAllRef x; _} :: cs, (vs', {it = Throwing (a, vs0); at} :: es')), vs -> - Ref (ExnRef (a, vs0)) :: vs, [Plain (Br x) @@ e.at] + Ref Exn.(ExnRef (Exn (a, vs0))) :: vs, [Plain (Br x) @@ e.at] | Handler (n, [], (vs', {it = Throwing (a, vs0); at} :: es')), vs -> vs, [Throwing (a, vs0) @@ at] @@ -1317,7 +1317,7 @@ let init_import (inst : module_inst) (ex : extern) (im : import) : module_inst = | TableImport tt -> ExternTableT tt | MemoryImport mt -> ExternMemoryT mt | GlobalImport gt -> ExternGlobalT gt - | TagImport x -> ExternTagT (TagT (VarHT (StatX x.it))) + | TagImport x -> ExternTagT (TagT (type_ inst x)) in let et = subst_extern_type (subst_of inst) it in let et' = extern_type_of inst.types ex in @@ -1367,9 +1367,8 @@ let init_elem (inst : module_inst) (seg : elem_segment) : module_inst = let elem = Elem.alloc (List.map (fun c -> as_ref (eval_const inst c)) einit) in {inst with elems = inst.elems @ [elem]} -let init_tag (inst : module_inst) (tag : tag) : module_inst = - let {tagtype} = tag.it in - let tag = Tag.alloc (subst_tag_type (subst_of inst) tagtype) in +let init_tag (inst : module_inst) (t : tag) : module_inst = + let tag = Tag.alloc (TagT (type_ inst t.it.tgtype)) in {inst with tags = inst.tags @ [tag]} let init_data (inst : module_inst) (seg : data_segment) : module_inst = diff --git a/interpreter/jslib/wast.ml b/interpreter/jslib/wast.ml index bc7edef074..266eab1000 100644 --- a/interpreter/jslib/wast.ml +++ b/interpreter/jslib/wast.ml @@ -12,8 +12,8 @@ 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.Encoded (_, bs) -> bs + | Script.Textual (m, cs) -> Encode.encode_with_custom (m, cs) + | Script.Encoded (_, bs) -> bs.Source.it | Script.Quoted (_, _) -> failwith "Unsupported" in let buf = new%js Typed_array.arrayBuffer (String.length bs) in let u8arr = new%js Typed_array.uint8Array_fromBuffer buf in diff --git a/interpreter/main/flags.ml b/interpreter/main/flags.ml index b92378aa2f..14140ff358 100644 --- a/interpreter/main/flags.ml +++ b/interpreter/main/flags.ml @@ -5,4 +5,5 @@ let print_sig = ref false let dry = ref false let width = ref 80 let harness = ref true +let custom_reject = ref false let budget = ref 256 diff --git a/interpreter/main/main.ml b/interpreter/main/main.ml index 2377a6f9aa..fc8a43f059 100644 --- a/interpreter/main/main.ml +++ b/interpreter/main/main.ml @@ -1,9 +1,16 @@ let name = "wasm" let version = "3.0.0" -let configure () = +let all_handlers = [ + (module Handler_custom : Custom.Handler); + (module Handler_name : Custom.Handler); + (module Handler_branch_hint : Custom.Handler); +] + +let configure custom_handlers = Import.register (Utf8.decode "spectest") Spectest.lookup; - Import.register (Utf8.decode "env") Env.lookup + Import.register (Utf8.decode "env") Env.lookup; + List.iter Custom.register custom_handlers let banner () = print_endline (name ^ " " ^ version ^ " reference interpreter") @@ -13,6 +20,15 @@ let usage = "Usage: " ^ name ^ " [option] [file ...]" let args = ref [] let add_arg source = args := !args @ [source] +let customs = ref [] +let add_custom name = + let n = Utf8.decode name in + match List.find_opt (fun (module H : Custom.Handler) -> n = H.name) all_handlers with + | Some h -> customs := !customs @ [h] + | None -> + prerr_endline ("option -c: unknown custom section \"" ^ name ^ "\""); + exit 1 + let quote s = "\"" ^ String.escaped s ^ "\"" let argspec = Arg.align @@ -28,6 +44,12 @@ let argspec = Arg.align " configure call depth budget (default is " ^ string_of_int !Flags.budget ^ ")"; "-w", Arg.Int (fun n -> Flags.width := n), " configure output width (default is " ^ string_of_int !Flags.width ^ ")"; + "-c", Arg.String add_custom, + " recognize custom section"; + "-ca", Arg.Unit (fun () -> customs := all_handlers), + " recognize all known custom section"; + "-cr", Arg.Set Flags.custom_reject, + " reject unrecognized custom sections"; "-s", Arg.Set Flags.print_sig, " show module signatures"; "-u", Arg.Set Flags.unchecked, " unchecked, do not perform validation"; "-j", Arg.Clear Flags.harness, " exclude harness for JS conversion"; @@ -39,9 +61,9 @@ let argspec = Arg.align let () = Printexc.record_backtrace true; try - configure (); Arg.parse argspec (fun file -> add_arg ("(input " ^ quote file ^ ")")) usage; + configure !customs; List.iter (fun arg -> if not (Run.run_string arg) then exit 1) !args; if !args = [] then Flags.interactive := true; if !Flags.interactive then begin diff --git a/interpreter/runtime/exn.ml b/interpreter/runtime/exn.ml new file mode 100644 index 0000000000..d4009372e2 --- /dev/null +++ b/interpreter/runtime/exn.ml @@ -0,0 +1,38 @@ +open Types +open Value + +type exn_ = Exn of Tag.t * value list + +type ref_ += ExnRef of exn_ + +let alloc_exn tag vs = + let TagT dt = Tag.type_of tag in + assert Free.((def_type dt).types = Set.empty); + let FuncT (ts1, ts2) = as_func_str_type (expand_def_type dt) in + assert (List.length vs = List.length ts1); + assert (ts2 = []); + Exn (tag, vs) + +let type_of (Exn (tag, _)) = + let TagT dt = Tag.type_of tag in + dt + +let () = + let eq_ref' = !Value.eq_ref' in + Value.eq_ref' := fun r1 r2 -> + match r1, r2 with + | ExnRef _, ExnRef _ -> failwith "eq_ref" + | _, _ -> eq_ref' r1 r2 + +let () = + let type_of_ref' = !Value.type_of_ref' in + Value.type_of_ref' := function + | ExnRef e -> DefHT (type_of e) + | r -> type_of_ref' r + +let () = + let string_of_ref' = !Value.string_of_ref' in + Value.string_of_ref' := function + | ExnRef (Exn (_tag, vs)) -> + "(tag " ^ String.concat " " (List.map string_of_value vs) ^ ")" + | r -> string_of_ref' r diff --git a/interpreter/runtime/exn.mli b/interpreter/runtime/exn.mli new file mode 100644 index 0000000000..13b0340267 --- /dev/null +++ b/interpreter/runtime/exn.mli @@ -0,0 +1,10 @@ +open Types +open Value + +type exn_ = Exn of Tag.t * value list + +type ref_ += ExnRef of exn_ + +val alloc_exn : Tag.t -> value list -> exn_ + +val type_of : exn_ -> def_type diff --git a/interpreter/runtime/instance.ml b/interpreter/runtime/instance.ml index cdfa0074dd..4b211c939f 100644 --- a/interpreter/runtime/instance.ml +++ b/interpreter/runtime/instance.ml @@ -54,13 +54,6 @@ let () = | FuncRef _ -> "func" | r -> string_of_ref' r -let () = - 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 *) diff --git a/interpreter/runtime/tag.mli b/interpreter/runtime/tag.mli index f8636674b8..c4730e2993 100644 --- a/interpreter/runtime/tag.mli +++ b/interpreter/runtime/tag.mli @@ -1,6 +1,6 @@ open Types -type tag = {ty : tag_type} +type tag type t = tag val alloc : tag_type -> tag diff --git a/interpreter/runtime/value.ml b/interpreter/runtime/value.ml index dec2597229..a24fbaf281 100644 --- a/interpreter/runtime/value.ml +++ b/interpreter/runtime/value.ml @@ -18,7 +18,6 @@ type value = Num of num | Vec of vec | Ref of ref_ type t = value type ref_ += NullRef of heap_type -type ref_ += ExnRef of Tag.t * value list (* Injection & projection *) @@ -109,7 +108,6 @@ 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) - | ExnRef _ -> (NoNull, ExnHT) | r -> (NoNull, !type_of_ref' r) let type_of_value = function @@ -304,7 +302,6 @@ let hex_string_of_vec = function let string_of_ref' = ref (function _ -> "ref") let string_of_ref = function | NullRef _ -> "null" - | ExnRef _ -> "exn" | r -> !string_of_ref' r let string_of_value = function diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index 6a029f9817..16dcddc743 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -96,6 +96,10 @@ function assert_malformed(bytes) { throw new Error("Wasm decoding failure expected"); } +function assert_malformed_custom(bytes) { + return; +} + function assert_invalid(bytes) { try { module(bytes, false) } catch (e) { if (e instanceof WebAssembly.CompileError) return; @@ -103,6 +107,10 @@ function assert_invalid(bytes) { throw new Error("Wasm validation failure expected"); } +function assert_invalid_custom(bytes) { + return; +} + function assert_unlinkable(bytes) { let mod = module(bytes); try { new WebAssembly.Instance(mod, registry) } catch (e) { @@ -567,12 +575,11 @@ let of_result res = let rec of_definition def = match def.it with - | Textual m -> of_bytes (Encode.encode m) - | Encoded (_, bs) -> of_bytes bs + | Textual (m, _) -> of_bytes (Encode.encode m) + | Encoded (_, bs) -> of_bytes bs.it | Quoted (_, s) -> - try of_definition (snd (Parse.Module.parse_string s)) - with Parse.Syntax _ -> - of_bytes "" + try of_definition (snd (Parse.Module.parse_string ~offset:s.at s.it)) + with Parse.Syntax _ | Custom.Syntax _ -> of_bytes "" let of_wrapper mods x_opt name wrap_action wrap_assertion at = let x = of_var_opt mods x_opt in @@ -619,8 +626,12 @@ let of_assertion mods ass = match ass.it with | AssertMalformed (def, _) -> "assert_malformed(" ^ of_definition def ^ ");" + | AssertMalformedCustom (def, _) -> + "assert_malformed_custom(" ^ of_definition def ^ ");" | AssertInvalid (def, _) -> "assert_invalid(" ^ of_definition def ^ ");" + | AssertInvalidCustom (def, _) -> + "assert_invalid_custom(" ^ of_definition def ^ ");" | AssertUnlinkable (def, _) -> "assert_unlinkable(" ^ of_definition def ^ ");" | AssertUninstantiable (def, _) -> @@ -644,9 +655,10 @@ let of_command mods cmd = | Module (x_opt, def) -> let rec unquote def = match def.it with - | Textual m -> m - | Encoded (_, bs) -> Decode.decode "binary" bs - | Quoted (_, s) -> unquote (snd (Parse.Module.parse_string s)) + | Textual (m, _) -> m + | Encoded (name, bs) -> Decode.decode name bs.it + | Quoted (_, s) -> + unquote (snd (Parse.Module.parse_string ~offset:s.at s.it)) in bind mods x_opt (unquote def); "let " ^ current_var mods ^ " = instance(" ^ of_definition def ^ ");\n" ^ (if x_opt = None then "" else diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index ac42defc6f..8a59c94ae0 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -43,7 +43,7 @@ let dispatch_file_ext on_binary on_sexpr on_script_binary on_script on_js file = let create_binary_file file _ get_module = trace ("Encoding (" ^ file ^ ")..."); - let s = Encode.encode (get_module ()) in + let s = Encode.encode_with_custom (get_module ()) in let oc = open_out_bin file in try trace "Writing..."; @@ -55,7 +55,7 @@ let create_sexpr_file file _ get_module = trace ("Writing (" ^ file ^ ")..."); let oc = open_out file in try - Print.module_ oc !Flags.width (get_module ()); + Print.module_with_custom oc !Flags.width (get_module ()); close_out oc with exn -> close_out oc; raise exn @@ -87,7 +87,7 @@ let output_file = let output_stdout get_module = trace "Printing..."; - Print.module_ stdout !Flags.width (get_module ()) + Print.module_with_custom stdout !Flags.width (get_module ()) (* Input *) @@ -106,7 +106,10 @@ let input_from get_script run = with | Decode.Code (at, msg) -> error at "decoding error" msg | Parse.Syntax (at, msg) -> error at "syntax error" msg - | Valid.Invalid (at, msg) -> error at "invalid module" msg + | Valid.Invalid (at, msg) -> error at "validation error" msg + | Custom.Code (at, msg) -> error at "custom section decoding error" msg + | Custom.Syntax (at, msg) -> error at "custom annotation syntax error" msg + | Custom.Invalid (at, msg) -> error at "custom validation error" msg | Import.Unknown (at, msg) -> error at "link failure" msg | Eval.Link (at, msg) -> error at "link failure" msg | Eval.Trap (at, msg) -> error at "runtime trap" msg @@ -133,7 +136,8 @@ let input_sexpr name lexbuf run = let input_binary name buf run = let open Source in input_from (fun () -> - [Module (None, Encoded (name, buf) @@ no_region) @@ no_region]) run + [Module (None, Encoded (name, buf @@ no_region) @@ no_region) @@ no_region] + ) run let input_sexpr_file input file run = trace ("Loading (" ^ file ^ ")..."); @@ -281,15 +285,18 @@ module Map = Map.Make(String) let quote : script ref = ref [] let scripts : script Map.t ref = ref Map.empty -let modules : Ast.module_ Map.t ref = ref Map.empty +let modules : (Ast.module_ * Custom.section list) Map.t ref = ref Map.empty let instances : Instance.module_inst Map.t ref = ref Map.empty let registry : Instance.module_inst Map.t ref = ref Map.empty -let bind map x_opt y = +let bind category map x_opt y = let map' = match x_opt with | None -> !map - | Some x -> Map.add x.it y !map + | Some x -> + if Map.mem x.it !map then + IO.error x.at (category ^ " " ^ x.it ^ " already defined"); + Map.add x.it y !map in map := Map.add "" y map' let lookup category map x_opt at = @@ -311,15 +318,15 @@ let lookup_registry module_name item_name _t = (* Running *) -let rec run_definition def : Ast.module_ = +let rec run_definition def : Ast.module_ * Custom.section list = match def.it with - | Textual m -> m + | Textual (m, cs) -> m, cs | Encoded (name, bs) -> trace "Decoding..."; - Decode.decode name bs + Decode.decode_with_custom name bs.it | Quoted (_, s) -> trace "Parsing quote..."; - let _, def' = Parse.Module.parse_string s in + let _, def' = Parse.Module.parse_string ~offset:s.at s.it in run_definition def' let run_action act : Value.t list = @@ -394,7 +401,7 @@ let assert_ref_pat r p = | RefTypePat Types.StructHT, Aggr.StructRef _ | RefTypePat Types.ArrayHT, Aggr.ArrayRef _ -> true | RefTypePat Types.FuncHT, Instance.FuncRef _ - | RefTypePat Types.ExnHT, Value.ExnRef _ + | RefTypePat Types.ExnHT, Exn.ExnRef _ | RefTypePat Types.ExternHT, _ -> true | NullPat, Value.NullRef _ -> true | _ -> false @@ -437,21 +444,40 @@ let run_assertion ass = | _ -> Assert.error ass.at "expected decoding/parsing error" ) + | AssertMalformedCustom (def, re) -> + trace "Asserting malformed custom..."; + (match ignore (run_definition def) with + | exception Custom.Syntax (_, msg) -> + assert_message ass.at "annotation parsing" msg re + | _ -> Assert.error ass.at "expected custom decoding/parsing error" + ) + | AssertInvalid (def, re) -> trace "Asserting invalid..."; (match - let m = run_definition def in - Valid.check_module m + let m, cs = run_definition def in + Valid.check_module_with_custom (m, cs) with | exception Valid.Invalid (_, msg) -> assert_message ass.at "validation" msg re | _ -> Assert.error ass.at "expected validation error" ) + | AssertInvalidCustom (def, re) -> + trace "Asserting invalid custom..."; + (match + let m, cs = run_definition def in + Valid.check_module_with_custom (m, cs) + with + | exception Custom.Invalid (_, msg) -> + assert_message ass.at "custom validation" msg re + | _ -> Assert.error ass.at "expected custom validation error" + ) + | AssertUnlinkable (def, re) -> trace "Asserting unlinkable..."; - let m = run_definition def in - if not !Flags.unchecked then Valid.check_module m; + let m, cs = run_definition def in + if not !Flags.unchecked then Valid.check_module_with_custom (m, cs); (match let imports = Import.link m in ignore (Eval.init m imports) @@ -463,8 +489,8 @@ let run_assertion ass = | AssertUninstantiable (def, re) -> trace "Asserting trap..."; - let m = run_definition def in - if not !Flags.unchecked then Valid.check_module m; + let m, cs = run_definition def in + if not !Flags.unchecked then Valid.check_module_with_custom (m, cs); (match let imports = Import.link m in ignore (Eval.init m imports) @@ -513,22 +539,22 @@ let rec run_command cmd = match cmd.it with | Module (x_opt, def) -> quote := cmd :: !quote; - let m = run_definition def in + let m, cs = run_definition def in if not !Flags.unchecked then begin trace "Checking..."; - Valid.check_module m; + Valid.check_module_with_custom (m, cs); if !Flags.print_sig then begin trace "Signature:"; print_module x_opt m end end; - bind scripts x_opt [cmd]; - bind modules x_opt m; + bind "module" modules x_opt (m, cs); + bind "script" scripts x_opt [cmd]; if not !Flags.dry then begin trace "Initializing..."; let imports = Import.link m in let inst = Eval.init m imports in - bind instances x_opt inst + bind "instance" instances x_opt inst end | Register (name, x_opt) -> @@ -560,17 +586,17 @@ and run_meta cmd = match cmd.it with | Script (x_opt, script) -> run_quote_script script; - bind scripts x_opt (lookup_script None cmd.at) + bind "script" scripts x_opt (lookup_script None cmd.at) | Input (x_opt, file) -> (try if not (input_file file run_quote_script) then Abort.error cmd.at "aborting" with Sys_error msg -> IO.error cmd.at msg); - bind scripts x_opt (lookup_script None cmd.at); + bind "script" scripts x_opt (lookup_script None cmd.at); if x_opt <> None then begin - bind modules x_opt (lookup_module None cmd.at); + bind "module" modules x_opt (lookup_module None cmd.at); if not !Flags.dry then begin - bind instances x_opt (lookup_instance None cmd.at) + bind "instance" instances x_opt (lookup_instance None cmd.at) end end @@ -592,7 +618,7 @@ and run_quote_script script = let save_quote = !quote in quote := []; (try run_script script with exn -> quote := save_quote; raise exn); - bind scripts None (List.rev !quote); + bind "script" scripts None (List.rev !quote); quote := !quote @ save_quote let run_file file = input_file file run_script diff --git a/interpreter/script/script.ml b/interpreter/script/script.ml index 510bd852c0..d303ada832 100644 --- a/interpreter/script/script.ml +++ b/interpreter/script/script.ml @@ -7,9 +7,9 @@ type literal = Value.t Source.phrase type definition = definition' Source.phrase and definition' = - | Textual of Ast.module_ - | Encoded of string * string - | Quoted of string * string + | Textual of Ast.module_ * Custom.section list + | Encoded of string * string Source.phrase + | Quoted of string * string Source.phrase type action = action' Source.phrase and action' = @@ -41,7 +41,9 @@ and result' = type assertion = assertion' Source.phrase and assertion' = | AssertMalformed of definition * string + | AssertMalformedCustom of definition * string | AssertInvalid of definition * string + | AssertInvalidCustom of definition * string | AssertUnlinkable of definition * string | AssertUninstantiable of definition * string | AssertReturn of action * result list diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index 310ed49f58..a9c0287bd6 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -282,7 +282,7 @@ and func' = type tag = tag' Source.phrase and tag' = { - tagtype : tag_type; + tgtype : idx; } @@ -419,7 +419,7 @@ let import_type_of (m : module_) (im : import) : import_type = | TableImport tt -> ExternTableT tt | MemoryImport mt -> ExternMemoryT mt | GlobalImport gt -> ExternGlobalT gt - | TagImport et -> ExternTagT (TagT (ht m et)) + | TagImport x -> ExternTagT (TagT (Lib.List32.nth dts x.it)) in ImportT (subst_extern_type (subst_of dts) et, module_name, item_name) let export_type_of (m : module_) (ex : export) : export_type = @@ -443,8 +443,9 @@ let export_type_of (m : module_) (ex : export) : export_type = let gts = globals ets @ List.map (fun g -> g.it.gtype) m.it.globals in ExternGlobalT (Lib.List32.nth gts x.it) | TagExport x -> - let tagts = tags ets @ List.map (fun t -> t.it.tagtype) m.it.tags in - ExternTagT (Lib.List32.nth tagts x.it) + let tts = tags ets @ List.map (fun t -> + TagT (Lib.List32.nth dts t.it.tgtype.it)) m.it.tags in + ExternTagT (Lib.List32.nth tts x.it) in ExportT (subst_extern_type (subst_of dts) et, name) let module_type_of (m : module_) : module_type = diff --git a/interpreter/syntax/free.ml b/interpreter/syntax/free.ml index 1a88e69de0..792c032341 100644 --- a/interpreter/syntax/free.ml +++ b/interpreter/syntax/free.ml @@ -128,7 +128,7 @@ let def_type = function let global_type (GlobalT (_mut, t)) = val_type t let table_type (TableT (_lim, t)) = ref_type t let memory_type (MemoryT (_lim)) = empty -let tag_type (TagT ht) = heap_type ht +let tag_type (TagT dt) = def_type dt let extern_type = function | ExternFuncT dt -> def_type dt @@ -218,7 +218,7 @@ 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 tag (e : tag) = tag_type e.it.tagtype +let tag (e : tag) = empty let segment_mode f (m : segment_mode) = match m.it with diff --git a/interpreter/syntax/free.mli b/interpreter/syntax/free.mli index cb76c0fb55..3c18c665c2 100644 --- a/interpreter/syntax/free.mli +++ b/interpreter/syntax/free.mli @@ -26,6 +26,7 @@ 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 tag_type : Types.tag_type -> t val extern_type : Types.extern_type -> t val str_type : Types.str_type -> t diff --git a/interpreter/syntax/types.ml b/interpreter/syntax/types.ml index a361917880..115e61d81e 100644 --- a/interpreter/syntax/types.ml +++ b/interpreter/syntax/types.ml @@ -51,7 +51,7 @@ type table_type = TableT of Int32.t limits * ref_type type memory_type = MemoryT of Int32.t limits type global_type = GlobalT of mut * val_type type local_type = LocalT of init * val_type -type tag_type = TagT of heap_type +type tag_type = TagT of def_type type extern_type = | ExternFuncT of def_type | ExternTableT of table_type @@ -213,7 +213,7 @@ let subst_global_type s = function | GlobalT (mut, t) -> GlobalT (mut, subst_val_type s t) let subst_tag_type s = function - | TagT ht -> TagT (subst_heap_type s ht) + | TagT dt -> TagT (subst_def_type s dt) let subst_extern_type s = function | ExternFuncT dt -> ExternFuncT (subst_def_type s dt) @@ -408,7 +408,7 @@ and string_of_str_type = function and string_of_tag_type = function - | TagT ht -> string_of_heap_type ht + | TagT dt -> string_of_def_type dt and string_of_sub_type = function | SubT (Final, [], st) -> string_of_str_type st diff --git a/interpreter/text/annot.ml b/interpreter/text/annot.ml new file mode 100644 index 0000000000..e0fa73dfa2 --- /dev/null +++ b/interpreter/text/annot.ml @@ -0,0 +1,56 @@ +open Source + +type annot = annot' Source.phrase +and annot' = {name : Ast.name; items : item list} + +and item = item' Source.phrase +and item' = + | Atom of string + | Var of string + | String of string + | Nat of string + | Int of string + | Float of string + | Parens of item list + | Annot of annot + + +(* Stateful recorder for annotations *) +(* I wish this could be encapsulated in the parser somehow *) + +module NameMap = Map.Make(struct type t = Ast.name let compare = compare end) +type map = annot list NameMap.t + +let current : map ref = ref NameMap.empty +let current_source : Buffer.t = Buffer.create 512 + +let reset () = + current := NameMap.empty; + Buffer.clear current_source + +let get_source () = + Buffer.contents current_source + +let extend_source s = + Buffer.add_string current_source s + +let record annot = + let old = Option.value (NameMap.find_opt annot.it.name !current) ~default:[] in + current := NameMap.add annot.it.name (annot::old) !current + +let is_contained r1 r2 = r1.left >= r2.left && r1.right <= r2.right + +let get_all () = + let all = !current in + current := NameMap.empty; + all + +let filter f map = + NameMap.filter (fun _ annots -> annots <> []) + (NameMap.map (List.filter f) map) + +let get r = + let sub = filter (fun annot -> is_contained annot.at r) !current in + let map' = filter (fun annot -> not (is_contained annot.at r)) !current in + current := map'; + sub diff --git a/interpreter/text/annot.mli b/interpreter/text/annot.mli new file mode 100644 index 0000000000..9cdfa02032 --- /dev/null +++ b/interpreter/text/annot.mli @@ -0,0 +1,25 @@ +type annot = annot' Source.phrase +and annot' = {name : Ast.name; items : item list} + +and item = item' Source.phrase +and item' = + | Atom of string + | Var of string + | String of string + | Nat of string + | Int of string + | Float of string + | Parens of item list + | Annot of annot + +module NameMap : Map.S with type key = Ast.name +type map = annot list NameMap.t + +val reset : unit -> unit +val record : annot -> unit + +val get : Source.region -> map +val get_all : unit -> map + +val get_source : unit -> string +val extend_source : string -> unit diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index bb07b64a6d..396dc91d09 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -637,7 +637,12 @@ let func f = func_with_name "" f -(* Tables & memories *) +(* Tags, tables, memories *) + +let tag off i tag = + Node ("tag $" ^ nat (off + i), + [Node ("type " ^ var (tag.it.tgtype), [])] + ) let table off i tab = let {ttype = TableT (lim, t); tinit} = tab.it in @@ -649,12 +654,6 @@ let memory off i mem = let {mtype = MemoryT lim} = mem.it in Node ("memory $" ^ nat (off + i) ^ " " ^ limits nat32 lim, []) -let tag off i tag = - let {tagtype = TagT et} = tag.it in - Node ("tag $" ^ nat (off + i), - [Node ("type", [atom heap_type et])] - ) - let is_elem_kind = function | (NoNull, FuncHT) -> true | _ -> false @@ -743,33 +742,39 @@ let global off i g = let start s = Node ("start " ^ var s.it.sfunc, []) +let custom m mnode (module S : Custom.Section) = + S.Handler.arrange m mnode S.it -(* Modules *) let var_opt = function | None -> "" - | Some x -> " " ^ x.it + | Some x when + String.for_all (fun c -> Lib.Char.is_alphanum_ascii c || c = '_') x.it -> + " $" ^ x.it + | Some x -> " $" ^ name (Utf8.decode x.it) -let module_with_var_opt x_opt m = +let module_with_var_opt x_opt (m, cs) = let fx = ref 0 in let tx = ref 0 in let mx = ref 0 in let tgx = ref 0 in let gx = ref 0 in let imports = list (import fx tx mx tgx gx) m.it.imports in - Node ("module" ^ var_opt x_opt, + let ret = Node ("module" ^ var_opt x_opt, List.rev (fst (List.fold_left type_ ([], 0) m.it.types)) @ imports @ listi (table !tx) m.it.tables @ listi (memory !mx) m.it.memories @ listi (tag !tgx) m.it.tags @ listi (global !gx) m.it.globals @ - listi (func_with_index !fx) m.it.funcs @ list export m.it.exports @ opt start m.it.start @ listi elem m.it.elems @ + listi (func_with_index !fx) m.it.funcs @ listi data m.it.datas - ) + ) in + List.fold_left (custom m) ret cs + let binary_module_with_var_opt x_opt bs = Node ("module" ^ var_opt x_opt ^ " binary", break_bytes bs) @@ -777,7 +782,8 @@ let binary_module_with_var_opt x_opt bs = let quoted_module_with_var_opt x_opt s = Node ("module" ^ var_opt x_opt ^ " quote", break_string s) -let module_ = module_with_var_opt None +let module_with_custom = module_with_var_opt None +let module_ m = module_with_custom (m, []) (* Scripts *) @@ -803,22 +809,25 @@ let definition mode x_opt def = | `Textual -> let rec unquote def = match def.it with - | Textual m -> m - | Encoded (_, bs) -> Decode.decode "" bs - | Quoted (_, s) -> unquote (snd (Parse.Module.parse_string s)) + | Textual (m, cs) -> m, cs + | Encoded (name, bs) -> Decode.decode_with_custom name bs.it + | Quoted (_, s) -> + unquote (snd (Parse.Module.parse_string ~offset:s.at s.it)) in module_with_var_opt x_opt (unquote def) | `Binary -> let rec unquote def = match def.it with - | Textual m -> Encode.encode m - | Encoded (_, bs) -> Encode.encode (Decode.decode "" bs) - | Quoted (_, s) -> unquote (snd (Parse.Module.parse_string s)) + | Textual (m, cs) -> Encode.encode_with_custom (m, cs) + | Encoded (name, bs) -> + Encode.encode_with_custom (Decode.decode_with_custom name bs.it) + | Quoted (_, s) -> + unquote (snd (Parse.Module.parse_string ~offset:s.at s.it)) in binary_module_with_var_opt x_opt (unquote def) | `Original -> match def.it with - | Textual m -> module_with_var_opt x_opt m - | Encoded (_, bs) -> binary_module_with_var_opt x_opt bs - | Quoted (_, s) -> quoted_module_with_var_opt x_opt s + | Textual (m, cs) -> module_with_var_opt x_opt (m, cs) + | Encoded (_, bs) -> binary_module_with_var_opt x_opt bs.it + | Quoted (_, s) -> quoted_module_with_var_opt x_opt s.it with Parse.Syntax _ -> quoted_module_with_var_opt x_opt "" @@ -879,8 +888,16 @@ let assertion mode ass = | _ -> [Node ("assert_malformed", [definition `Original None def; Atom (string re)])] ) + | AssertMalformedCustom (def, re) -> + (match mode, def.it with + | `Binary, Quoted _ -> [] + | _ -> + [Node ("assert_malformed_custom", [definition `Original None def; Atom (string re)])] + ) | AssertInvalid (def, re) -> [Node ("assert_invalid", [definition mode None def; Atom (string re)])] + | AssertInvalidCustom (def, re) -> + [Node ("assert_invalid_custom", [definition mode None def; Atom (string re)])] | AssertUnlinkable (def, re) -> [Node ("assert_unlinkable", [definition mode None def; Atom (string re)])] | AssertUninstantiable (def, re) -> diff --git a/interpreter/text/arrange.mli b/interpreter/text/arrange.mli index 051686a443..a0fddd5d43 100644 --- a/interpreter/text/arrange.mli +++ b/interpreter/text/arrange.mli @@ -1,6 +1,14 @@ open Sexpr +val bytes : string -> string +val string : string -> string +val name : Ast.name -> string + +val break_bytes : string -> sexpr list +val break_string : string -> sexpr list + val instr : Ast.instr -> sexpr val func : Ast.func -> sexpr val module_ : Ast.module_ -> sexpr +val module_with_custom : Ast.module_ * Custom.section list -> sexpr val script : [`Textual | `Binary] -> Script.script -> sexpr list diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 3b16de0362..182b5c3fbb 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -48,6 +48,11 @@ let string s = done; Buffer.contents b +let annot_id lexbuf s = + let s' = string s in + if s' = "" then error lexbuf "empty annotation id"; + try Utf8.decode s' with Utf8.Utf8 -> error lexbuf "malformed UTF-8 encoding" + let opt = Lib.Option.get } @@ -102,8 +107,7 @@ let float = let string = '"' character* '"' let idchar = letter | digit | '_' | symbol -let name = idchar+ -let id = '$' name +let id = idchar+ let keyword = ['a'-'z'] (letter | digit | '_' | '.' | ':')+ let reserved = (idchar | string)+ | ',' | ';' | '[' | ']' | '{' | '}' @@ -779,6 +783,8 @@ rule token = parse | "get" -> GET | "assert_malformed" -> ASSERT_MALFORMED | "assert_invalid" -> ASSERT_INVALID + | "assert_malformed_custom" -> ASSERT_MALFORMED_CUSTOM + | "assert_invalid_custom" -> ASSERT_INVALID_CUSTOM | "assert_unlinkable" -> ASSERT_UNLINKABLE | "assert_return" -> ASSERT_RETURN | "assert_trap" -> ASSERT_TRAP @@ -796,7 +802,21 @@ rule token = parse | "offset="(nat as s) { OFFSET_EQ_NAT s } | "align="(nat as s) { ALIGN_EQ_NAT s } - | id as s { VAR s } + | '$'(id as s) { VAR s } + | '$'(string as s) + { let s' = string s in + if s' = "" then error lexbuf "empty identifier"; VAR s' } + | '$' { error lexbuf "empty identifier" } + + | "(@"(id as n) + { let r = region lexbuf in + let items = annot (Lexing.lexeme_start_p lexbuf) lexbuf in + Annot.record (Annot.{name = Utf8.decode n; items} @@ r); token lexbuf } + | "(@"(string as s) + { let r = region lexbuf in + let items = annot (Lexing.lexeme_start_p lexbuf) lexbuf in + Annot.record (Annot.{name = annot_id lexbuf s; items} @@ r); token lexbuf } + | "(@" { error lexbuf "empty annotation id" } | ";;"utf8_no_nl*eof { EOF } | ";;"utf8_no_nl*newline { Lexing.new_line lexbuf; token lexbuf } @@ -811,6 +831,64 @@ rule token = parse | utf8enc { error lexbuf "misplaced unicode character" } | _ { error lexbuf "malformed UTF-8 encoding" } +and annot start = parse + | ")" { [] } + | "(" + { let r = region lexbuf in + let items = annot (Lexing.lexeme_start_p lexbuf) lexbuf in + (Annot.Parens items @@ r) :: annot start lexbuf } + | "(@"(id as n) + { let r = region lexbuf in + let items = annot (Lexing.lexeme_start_p lexbuf) lexbuf in + let ann = Annot.{name = Utf8.decode n; items} @@ r in + (Annot.Annot ann @@ r) :: annot start lexbuf } + | "(@"(string as s) + { let r = region lexbuf in + let items = annot (Lexing.lexeme_start_p lexbuf) lexbuf in + let ann = Annot.{name = annot_id lexbuf s; items} @@ r in + (Annot.Annot ann @@ r) :: annot start lexbuf } + + | nat as s + { let r = region lexbuf in + (Annot.Nat s @@ r) :: annot start lexbuf } + | int as s + { let r = region lexbuf in + (Annot.Int s @@ r) :: annot start lexbuf } + | float as s + { let r = region lexbuf in + (Annot.Float s @@ r) :: annot start lexbuf } + | '$'(id as s) + { let r = region lexbuf in + (Annot.Var s @@ r) :: annot start lexbuf } + | '$'(string as s) + { let r = region lexbuf in + let s' = string s in + if s' = "" then error lexbuf "empty identifier"; + (Annot.Var s' @@ r) :: annot start lexbuf } + | '$' { error lexbuf "empty identifier" } + | string as s + { let r = region lexbuf in + (Annot.String (string s) @@ r) :: annot start lexbuf } + | reserved as s + { let r = region lexbuf in + (Annot.Atom s @@ r) :: annot start lexbuf } + | '"'character*('\n'|eof) + { error lexbuf "unclosed string literal" } + | '"'character*['\x00'-'\x09''\x0b'-'\x1f''\x7f'] + { error lexbuf "illegal control character in string literal" } + | '"'character*'\\'_ + { error_nest (Lexing.lexeme_end_p lexbuf) lexbuf "illegal escape" } + + | (";;"utf8_no_nl*)? eof { error_nest start lexbuf "unclosed annotation" } + | ";;"utf8_no_nl*'\n' { Lexing.new_line lexbuf; annot start lexbuf } + | ";;"utf8_no_nl* { annot start lexbuf (* error on following position *) } + | "(;" { comment (Lexing.lexeme_start_p lexbuf) lexbuf; annot start lexbuf } + | space#'\n' { annot start lexbuf } + | '\n' { Lexing.new_line lexbuf; annot start lexbuf } + | eof { error_nest start lexbuf "unclosed annotation" } + | utf8 { error lexbuf "illegal character" } + | _ { error lexbuf "malformed UTF-8 encoding" } + and comment start = parse | ";)" { () } | "(;" { comment (Lexing.lexeme_start_p lexbuf) lexbuf; comment start lexbuf } diff --git a/interpreter/text/parse.ml b/interpreter/text/parse.ml index 94e5d55495..986f44dddb 100644 --- a/interpreter/text/parse.ml +++ b/interpreter/text/parse.ml @@ -5,10 +5,25 @@ sig type t val parse : string -> Lexing.lexbuf -> t val parse_file : string -> t - val parse_string : string -> t + val parse_string : ?offset:Source.region -> string -> t val parse_channel : in_channel -> t end +let wrap_lexbuf lexbuf = + let open Lexing in + let inner_refill = lexbuf.refill_buff in + let refill_buff lexbuf = + let oldlen = lexbuf.lex_buffer_len - lexbuf.lex_start_pos in + inner_refill lexbuf; + let newlen = lexbuf.lex_buffer_len - lexbuf.lex_start_pos in + let start = lexbuf.lex_start_pos + oldlen in + let n = newlen - oldlen in + Annot.extend_source (Bytes.sub_string lexbuf.lex_buffer start n) + in + let n = lexbuf.lex_buffer_len - lexbuf.lex_start_pos in + Annot.extend_source (Bytes.sub_string lexbuf.lex_buffer lexbuf.lex_start_pos n); + {lexbuf with refill_buff} + let convert_pos lexbuf = { Source.left = Lexer.convert_pos lexbuf.Lexing.lex_start_p; Source.right = Lexer.convert_pos lexbuf.Lexing.lex_curr_p @@ -19,12 +34,33 @@ let make (type a) (start : _ -> _ -> a) : (module S with type t = a) = type t = a let parse name lexbuf = + Annot.reset (); Lexing.set_filename lexbuf name; - try start Lexer.token lexbuf with Parser.Error -> - raise (Syntax (convert_pos lexbuf, "unexpected token")) + let lexbuf = wrap_lexbuf lexbuf in + let result = + try start Lexer.token lexbuf with Parser.Error -> + raise (Syntax (convert_pos lexbuf, "unexpected token")) + in + let annots = Annot.get_all () in + if not (Annot.NameMap.is_empty annots) then + let annot = List.hd (snd (Annot.NameMap.choose annots)) in + raise (Custom.Syntax (annot.Source.at, "misplaced annotation")) + else + result - let parse_string s = - parse "string" (Lexing.from_string ~with_positions:true s) + let parse_string ?offset s = + let open Source in + let name, s' = + match offset with + | None -> "string", s + | Some at -> + (* Note: this is a hack that only works for singular string literals + * with no escapes in them. + * TODO: Figure out why we need to add 2 instead of 1 to column. *) + at.left.file, + String.make (max 0 (at.left.line - 1)) '\n' ^ + String.make (at.left.column + 2) ' ' ^ s + in parse name (Lexing.from_string ~with_positions:true s') let parse_channel oc = parse "channel" (Lexing.from_channel ~with_positions:true oc) diff --git a/interpreter/text/parse.mli b/interpreter/text/parse.mli index 3a318683b6..f5a4116949 100644 --- a/interpreter/text/parse.mli +++ b/interpreter/text/parse.mli @@ -5,7 +5,7 @@ sig type 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_string : ?offset:Source.region -> string -> t (* raises Syntax *) val parse_channel : in_channel -> t (* raises Syntax *) end diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 93142b24c8..24d0baec85 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -87,6 +87,11 @@ let nat32 s loc = let name s loc = try Utf8.decode s with Utf8.Utf8 -> error (at loc) "malformed UTF-8 encoding" +let var s loc = + let r = at loc in + try ignore (Utf8.decode s); Source.(s @@ r) + with Utf8.Utf8 -> error r "malformed UTF-8 encoding" + (* Symbolic variables *) @@ -144,9 +149,24 @@ let force_locals (c : context) = c.deferred_locals := [] +let print_char = function + | 0x09 -> "\\t" + | 0x0a -> "\\n" + | 0x22 -> "\\\"" + | 0x5c -> "\\\\" + | c when 0x20 <= c && c < 0x7f -> String.make 1 (Char.chr c) + | c -> Printf.sprintf "\\u{%02x}" c + +let print x = + "$" ^ + if String.for_all (fun c -> Lib.Char.is_alphanum_ascii c || c = '_') x.it + then x.it + else "\"" ^ String.concat "" (List.map print_char (Utf8.decode x.it)) ^ "\"" + + let lookup category space x = try VarMap.find x.it space.map - with Not_found -> error x.at ("unknown " ^ category ^ " " ^ x.it) + with Not_found -> error x.at ("unknown " ^ category ^ " " ^ print x) let type_ (c : context) x = lookup "type" c.types.space x let func (c : context) x = lookup "function" c.funcs x @@ -169,7 +189,7 @@ let func_type (c : context) x = let bind_abs category space x = if VarMap.mem x.it space.map then - error x.at ("duplicate " ^ category ^ " " ^ x.it); + error x.at ("duplicate " ^ category ^ " " ^ print x); let i = bind category space 1l x.at in space.map <- VarMap.add x.it i space.map; i @@ -247,11 +267,28 @@ let inline_func_type_explicit (c : context) x ft loc = error (at loc) "inline function type does not match explicit type"; x -let inline_tag_type (c : context) (TagT ht) at = - match ht with - | VarHT (StatX x) -> x @@ at - | DefHT dt -> find_type_index c (expand_def_type dt) at - | _ -> assert false +(* Custom annotations *) + +let parse_annots (m : module_) : Custom.section list = + let bs = Annot.get_source () in + let annots = Annot.get m.at in + let secs = + Annot.NameMap.fold (fun name anns secs -> + match Custom.handler name with + | Some (module Handler) -> + let secs' = Handler.parse m bs anns in + List.map (fun fmt -> + let module S = struct module Handler = Handler let it = fmt end in + (module S : Custom.Section) + ) secs' @ secs + | None -> + if !Flags.custom_reject then + raise (Custom.Syntax ((List.hd anns).at, + "unknown annotation @" ^ Utf8.encode name)) + else [] + ) annots [] + in + List.stable_sort Custom.compare_section secs %} @@ -306,6 +343,7 @@ let inline_tag_type (c : context) (TagT ht) at = %token SCRIPT REGISTER INVOKE GET %token ASSERT_MALFORMED ASSERT_INVALID ASSERT_UNLINKABLE %token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION ASSERT_SUSPENSION +%token ASSERT_MALFORMED_CUSTOM ASSERT_INVALID_CUSTOM %token NAN %token INPUT OUTPUT %token EOF @@ -450,12 +488,6 @@ func_type_result : | LPAR RESULT val_type_list RPAR func_type_result { fun c -> snd $3 c @ $5 c } -tag_type : - | type_use - { fun c -> TagT (VarHT (StatX ($1 c).it)) } - | func_type - { let at1 = $sloc in fun c -> TagT (VarHT (StatX (inline_func_type c ($1 c) at1).it)) } - 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) } @@ -497,7 +529,7 @@ num : var : | NAT { fun c lookup -> nat32 $1 $sloc @@ $sloc } - | VAR { fun c lookup -> lookup c ($1 @@ $sloc) @@ $sloc } + | VAR { fun c lookup -> lookup c (var $1 $sloc) @@ $sloc } var_opt : | /* empty */ { fun c lookup at -> 0l @@ at } @@ -516,7 +548,7 @@ bind_var_opt : | bind_var { fun c anon bind -> bind c $1 } /* Sugar */ bind_var : - | VAR { $1 @@ $sloc } + | VAR { var $1 $sloc } labeling_opt : | /* empty */ @@ -1015,7 +1047,7 @@ func_fields : | type_use func_fields_body { 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 + let y = inline_func_type_explicit c' ($1 c') (fst $2 c') $loc($1) in [{(snd $2 c') with ftype = y} @@ loc], [], [] } | func_fields_body /* Sugar */ { fun c x loc -> @@ -1024,7 +1056,7 @@ func_fields : [{(snd $1 c') with ftype = y} @@ loc], [], [] } | inline_import type_use func_fields_import /* Sugar */ { fun c x loc -> - let y = inline_func_type_explicit c ($2 c) ($3 c) loc in + let y = inline_func_type_explicit c ($2 c) ($3 c) $loc($2) in [], [{ module_name = fst $1; item_name = snd $1; idesc = FuncImport y @@ loc } @@ loc ], [] } @@ -1226,6 +1258,36 @@ memory_fields : [{dinit = $3; dmode = Active {index = x; offset} @@ loc} @@ loc], [], [] } +tag : + | LPAR TAG bind_var_opt tag_fields RPAR + { fun c -> let x = $3 c anon_tag bind_tag @@ $sloc in fun () -> $4 c x $sloc } + +tag_fields : + | type_use func_type + { fun c x loc -> + let tgtype = inline_func_type_explicit c ($1 c) ($2 c) $loc($1) in + [{tgtype} @@ loc], [], [] } + | func_type /* Sugar */ + { fun c x loc -> + let tgtype = inline_func_type c ($1 c) $sloc in + [{tgtype} @@ loc], [], [] } + | inline_import type_use func_type /* Sugar */ + { fun c x loc -> + let y = inline_func_type_explicit c ($2 c) ($3 c) $loc($2) in + [], + [{ module_name = fst $1; item_name = snd $1; + idesc = TagImport y @@ loc } @@ loc ], [] } + | inline_import func_type /* Sugar */ + { fun c x loc -> + let y = inline_func_type c ($2 c) $loc($2) in + [], + [{ module_name = fst $1; item_name = snd $1; + idesc = TagImport y @@ loc } @@ loc ], [] } + | inline_export tag_fields /* Sugar */ + { fun c x loc -> + let tgs, ims, exs = $2 c x loc in tgs, ims, $1 (TagExport x) c :: exs } + + global : | LPAR GLOBAL bind_var_opt global_fields RPAR { fun c -> let x = $3 c anon_global bind_global @@ $sloc in @@ -1243,24 +1305,6 @@ global_fields : { fun c x loc -> let globs, ims, exs = $2 c x loc in globs, ims, $1 (GlobalExport x) c :: exs } -tag : - | LPAR TAG bind_var_opt tag_fields RPAR - { let loc = $sloc in - fun c -> let x = $3 c anon_tag bind_tag @@ loc in - fun () -> $4 c x loc } - -tag_fields : - | tag_type - { fun c x at -> [{tagtype = $1 c} @@ at], [], [] } - | inline_import tag_type /* Sugar */ - { fun c x at -> - [], - [{ module_name = fst $1; item_name = snd $1; - idesc = TagImport (inline_tag_type c ($2 c) at) @@ at } @@ at], [] } - | inline_export tag_fields /* Sugar */ - { fun c x at -> let evts, ims, exs = $2 c x at in - evts, ims, $1 (TagExport x) c :: exs } - /* Imports & Exports */ import_desc : @@ -1279,13 +1323,12 @@ import_desc : | LPAR GLOBAL bind_var_opt global_type RPAR { fun c -> ignore ($3 c anon_global bind_global); fun () -> GlobalImport ($4 c) } - | LPAR TAG bind_var_opt tag_type RPAR - { let at4 = $loc($4) in - fun c -> ignore ($3 c anon_tag bind_tag); - fun () -> TagImport (inline_tag_type c ($4 c) at4) } - /* | LPAR TAG bind_var_opt type_use RPAR */ - /* { fun c -> ignore ($3 c anon_tag bind_tag); */ - /* fun () -> TagImport ($4 c) } */ + | LPAR TAG bind_var_opt type_use RPAR + { fun c -> ignore ($3 c anon_tag bind_tag); + fun () -> TagImport ($4 c) } + | LPAR TAG bind_var_opt func_type RPAR /* Sugar */ + { fun c -> ignore ($3 c anon_tag bind_tag); + fun () -> TagImport (inline_func_type c ($4 c) $loc($4)) } import : | LPAR IMPORT name name import_desc RPAR @@ -1424,30 +1467,41 @@ module_fields1 : {m with exports = $1 c :: m.exports} } module_var : - | VAR { $1 @@ $sloc } /* Sugar */ + | VAR { var $1 $sloc } /* Sugar */ module_ : | LPAR MODULE option(module_var) module_fields RPAR - { $3, Textual ($4 (empty_context ()) () () @@ $sloc) @@ $sloc } + { let m = $4 (empty_context ()) () () @@ $sloc in + $3, Textual (m, parse_annots m) @@ $sloc } inline_module : /* Sugar */ - | module_fields { Textual ($1 (empty_context ()) () () @@ $sloc) @@ $sloc } + | module_fields + { let m = $1 (empty_context ()) () () @@ $sloc in + (* Hack to handle annotations before first and after last token *) + let all = all_region (at $sloc).left.file in + Textual (m, parse_annots Source.(m.it @@ all)) @@ $sloc } inline_module1 : /* Sugar */ - | module_fields1 { Textual ($1 (empty_context ()) () () @@ $sloc) @@ $sloc } + | module_fields1 + { let m = $1 (empty_context ()) () () @@ $sloc in + (* Hack to handle annotations before first and after last token *) + let all = all_region (at $sloc).left.file in + Textual (m, parse_annots Source.(m.it @@ all)) @@ $sloc } /* Scripts */ script_var : - | VAR { $1 @@ $sloc } /* Sugar */ + | VAR { var $1 $sloc } /* Sugar */ script_module : | module_ { $1 } | LPAR MODULE option(module_var) BIN string_list RPAR - { $3, Encoded ("binary:" ^ string_of_pos (at $sloc).left, $5) @@ $sloc } + { let s = $5 @@ $loc($5) in + $3, Encoded ("binary:" ^ string_of_pos (at $sloc).left, s) @@ $sloc } | LPAR MODULE option(module_var) QUOTE string_list RPAR - { $3, Quoted ("quote:" ^ string_of_pos (at $sloc).left, $5) @@ $sloc } + { let s = $5 @@ $loc($5) in + $3, Quoted ("quote:" ^ string_of_pos (at $sloc).left, s) @@ $sloc } action : | LPAR INVOKE option(module_var) name list(literal) RPAR @@ -1461,6 +1515,10 @@ assertion : { AssertMalformed (snd $3, $4) @@ $sloc } | LPAR ASSERT_INVALID script_module STRING RPAR { AssertInvalid (snd $3, $4) @@ $sloc } + | LPAR ASSERT_MALFORMED_CUSTOM script_module STRING RPAR + { AssertMalformedCustom (snd $3, $4) @@ $sloc } + | LPAR ASSERT_INVALID_CUSTOM script_module STRING RPAR + { AssertInvalidCustom (snd $3, $4) @@ $sloc } | LPAR ASSERT_UNLINKABLE script_module STRING RPAR { AssertUnlinkable (snd $3, $4) @@ $sloc } | LPAR ASSERT_TRAP script_module STRING RPAR diff --git a/interpreter/text/print.ml b/interpreter/text/print.ml index 9496182147..44bf4e5756 100644 --- a/interpreter/text/print.ml +++ b/interpreter/text/print.ml @@ -1,5 +1,6 @@ let instr oc width e = Sexpr.output oc width (Arrange.instr e) let func oc width f = Sexpr.output oc width (Arrange.func f) let module_ oc width m = Sexpr.output oc width (Arrange.module_ m) +let module_with_custom oc width m_cs = Sexpr.output oc width (Arrange.module_with_custom m_cs) let script oc width mode s = List.iter (Sexpr.output oc width) (Arrange.script mode s) diff --git a/interpreter/text/print.mli b/interpreter/text/print.mli index 861ae40d97..c97dc9f618 100644 --- a/interpreter/text/print.mli +++ b/interpreter/text/print.mli @@ -1,4 +1,5 @@ val instr : out_channel -> int -> Ast.instr -> unit val func : out_channel -> int -> Ast.func -> unit val module_ : out_channel -> int -> Ast.module_ -> unit +val module_with_custom : out_channel -> int -> Ast.module_ * Custom.section list -> unit val script : out_channel -> int -> [`Textual | `Binary] -> Script.script -> unit diff --git a/interpreter/util/lib.ml b/interpreter/util/lib.ml index 1cc4e40d2b..258ccc8e96 100644 --- a/interpreter/util/lib.ml +++ b/interpreter/util/lib.ml @@ -23,6 +23,15 @@ struct n <> 0 && n land (n - 1) = 0 end +module Char = +struct + let is_digit_ascii c = '0' <= c && c <= '9' + let is_uppercase_ascii c = 'A' <= c && c <= 'Z' + let is_lowercase_ascii c = 'a' <= c && c <= 'z' + let is_letter_ascii c = is_uppercase_ascii c || is_lowercase_ascii c + let is_alphanum_ascii c = is_digit_ascii c || is_letter_ascii c +end + module String = struct let implode cs = diff --git a/interpreter/util/lib.mli b/interpreter/util/lib.mli index 56923cabc7..dc0d7d2bda 100644 --- a/interpreter/util/lib.mli +++ b/interpreter/util/lib.mli @@ -83,6 +83,15 @@ sig val is_power_of_two : int -> bool end +module Char : +sig + val is_digit_ascii : char -> bool + val is_uppercase_ascii : char -> bool + val is_lowercase_ascii : char -> bool + val is_letter_ascii : char -> bool + val is_alphanum_ascii : char -> bool +end + module String : sig val implode : char list -> string diff --git a/interpreter/util/source.ml b/interpreter/util/source.ml index a3dc54c09e..cb4b2e0373 100644 --- a/interpreter/util/source.ml +++ b/interpreter/util/source.ml @@ -11,6 +11,10 @@ let at phrase = phrase.at let no_pos = {file = ""; line = 0; column = 0} let no_region = {left = no_pos; right = no_pos} +let all_region file = + { left = {file; line = 0; column = 0}; + right = {file; line = Int.max_int; column = Int.max_int} + } let string_of_pos pos = if pos.line = -1 then diff --git a/interpreter/util/source.mli b/interpreter/util/source.mli index a4bc79bc90..5d1734ea31 100644 --- a/interpreter/util/source.mli +++ b/interpreter/util/source.mli @@ -4,6 +4,7 @@ type 'a phrase = {at : region; it : 'a} val no_pos : pos val no_region : region +val all_region : string -> region val string_of_pos : pos -> string val string_of_region : region -> string diff --git a/interpreter/valid/match.ml b/interpreter/valid/match.ml index 0f21d62214..5ec7069a30 100644 --- a/interpreter/valid/match.ml +++ b/interpreter/valid/match.ml @@ -162,8 +162,8 @@ let match_global_type c (GlobalT (mut1, t1)) (GlobalT (mut2, t2)) = | Cons -> true | Var -> match_val_type c t2 t1 -let match_tag_type c (TagT ht1) (TagT ht2) = - match_heap_type c ht1 ht2 +let match_tag_type c (TagT dt1) (TagT dt2) = + match_def_type c dt1 dt2 let match_table_type c (TableT (lim1, t1)) (TableT (lim2, t2)) = match_limits c lim1 lim2 && match_ref_type c t1 t2 && match_ref_type c t2 t1 diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index dfc38b0274..4741c9cc16 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -109,8 +109,10 @@ let func_type_of_heap_type (c : context) (ht : heap_type) at : func_type = let func_type_of_cont_type (c : context) (ContT ht) at : func_type = func_type_of_heap_type c ht at -let func_type_of_tag_type (c : context) (TagT ht) at : func_type = - func_type_of_heap_type c ht at +let func_type_of_tag_type (c : context) (TagT dt) at : func_type = + match expand_def_type dt with + | DefFuncT ft -> ft + | _ -> error at "non-function type" let heap_type_of_str_type (_c : context) (st : str_type) : heap_type = DefHT (DefT (RecT [SubT (Final, [], st)], Int32.of_int 0)) @@ -198,7 +200,7 @@ let check_memory_type (c : context) (mt : memory_type) at = let check_tag_type (c : context) (et : tag_type) at = match et with - | TagT ht -> check_heap_type c ht at + | TagT dt -> check_func_type c (as_func_str_type (expand_def_type dt)) at let check_global_type (c : context) (gt : global_type) at = let GlobalT (_mut, t) = gt in @@ -1039,12 +1041,13 @@ and check_catch (c : context) (cc : catch) (ts : val_type list) at = let FuncT (ts1, ts2) = func_type_of_tag_type c (tag c x1) x1.at in match_target c ts1 (label c x2) cc.at | CatchRef (x1, x2) -> - let FuncT (ts1, ts2) = func_type_of_tag_type c (tag c x1) x1.at in - match_target c (ts1 @ [RefT (Null, ExnHT)]) (label c x2) cc.at + let TagT dt = tag c x1 in + let FuncT (ts1, ts2) = as_func_str_type (expand_def_type dt) in + match_target c (ts1 @ [RefT (NoNull, ExnHT)]) (label c x2) cc.at | CatchAll x -> match_target c [] (label c x) cc.at | CatchAllRef x -> - match_target c [RefT (Null, ExnHT)] (label c x) cc.at + match_target c [RefT (NoNull, ExnHT)] (label c x) cc.at (* Functions & Constants *) @@ -1152,9 +1155,8 @@ let check_data (c : context) (seg : data_segment) : context = {c with datas = c.datas @ [()]} let check_tag (c : context) (tag : tag) : context = - let {tagtype} = tag.it in - check_tag_type c tagtype tag.at; - {c with tags = c.tags @ [tagtype]} + check_tag_type c (TagT (type_ c tag.it.tgtype)) tag.at; + {c with tags = c.tags @ [TagT (type_ c tag.it.tgtype)]} @@ -1182,9 +1184,8 @@ let check_import (c : context) (im : import) : context = check_memory_type c mt idesc.at; {c with memories = c.memories @ [mt]} | TagImport x -> - let tag = (TagT (VarHT (StatX x.it))) in - check_tag_type c tag idesc.at; - {c with tags = c.tags @ [tag]} + let _ = func_type c x in + {c with tags = c.tags @ [TagT (type_ c x)]} module NameSet = Set.Make(struct type t = Ast.name let compare = compare end) @@ -1222,3 +1223,7 @@ let check_module (m : module_) = 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) + +let check_module_with_custom ((m : module_), (cs : Custom.section list)) = + check_module m; + List.iter (fun (module S : Custom.Section) -> S.Handler.check m S.it) cs diff --git a/interpreter/valid/valid.mli b/interpreter/valid/valid.mli index 5827ae56e9..ef89313312 100644 --- a/interpreter/valid/valid.mli +++ b/interpreter/valid/valid.mli @@ -1,3 +1,4 @@ exception Invalid of Source.region * string val check_module : Ast.module_ -> unit (* raises Invalid *) +val check_module_with_custom : Ast.module_ * Custom.section list -> unit (* raises Invalid, Custom.Check *) diff --git a/proposals/annotations/Overview.md b/proposals/annotations/Overview.md new file mode 100644 index 0000000000..dbd1842062 --- /dev/null +++ b/proposals/annotations/Overview.md @@ -0,0 +1,107 @@ +# Custom Annotation Syntax for the Wasm Text Format + +## Motivation + +Problem + +* The Wasm binary format supports custom sections to enable associating arbitrary meta data with a Wasm module. + +* No equivalent exists for the text format. In particular, there is no way to + - represent custom sections themselves in the text format, cf. WebAssembly/design#1153 and https://gist.github.com/binji/d1cfff7faaebb2aa4f8b1c995234e5a0 + - reflect arbitrary names in the text format, cf. WebAssembly/spec#617 + - express information like for Web IDL bindings, cf. https://github.com/WebAssembly/webidl-bindings/blob/master/proposals/webidl-bindings/Explainer.md + +Solution + +* This proposal adds the ability to decorate a module in textual notarion with arbitrary annotations of the form `(@id ...)`. + +* Neither the syntactic shape nor the semantics is prescribed by the Wasm specification, though the Appendix might include a description of optional support for name section annotations and generic custom sections. + +* As an aside, the syntax of symbolic identifiers is extended to allow arbitrary strings in the form `$"..."`. + +* This proposal only affects the text format, nothing else. + + +## Details + +Extend the Text Format as follows: + +* Anywhere where white space is allowed, allow *annotations* of the following form: + ``` + annot ::= "(@"annotid annotelem* ")" + annotid ::= idchar+ | name + annotelem ::= keyword | reserved | uN | sN | fN | string | id | "(" annotelem* ")" + ``` + In other words, an annotation can contain any sequence of tokens, as long as it is well-bracketed. + No white space is allowed as part of the initial `(@annotid` delimiter. + +* The initial `annotid` is meant to be an identifier categorising the extension, and plays a role similar to the name of a custom section. + By convention, annotations corresponding to a custom section should use the same id. + +* Extend the grammar of identifiers as follows: + ``` + id ::= "$"idchar+ | "$"name + ``` + +* Elaborate identifiers to their denotation as a name, treating the unquoted form as a shorthand for the name `"idchar+"`. In all places where identifiers are compared, compare the denotated names instead. In particular, change the identifier environment `I` to record names instead of identifiers. + +Extend the Appendix on the Custom Sections: + +* Define annotations reflecting the Name section, which take the form of annotations `(@name "name")`. + They may be placed after the binder for any construct that can be named by the name section. + +* Define annotation syntax expressing arbitrary custom sections; cf. https://gist.github.com/binji/d1cfff7faaebb2aa4f8b1c995234e5a0 + + With that, a custom section annotation is specified as follows: + ``` + custom ::= '(' '@custom' string place? datastring ')' + place ::= + | '(' 'before' section ')' + | '(' 'after' section ')' + | '(' 'before' 'first' ')' + | '(' 'after' 'last' ')' + section ::= + | 'type' + | 'import' + | 'func' + | 'table' + | 'memory' + | 'global' + | 'export' + | 'start' + | 'elem' + | 'code' + | 'data' + | 'datacount' + ``` + If placement relative to an explicit section is used, then that section must exist in the encoding of the annotated module. + + Custom section annotations that appear within module fields rather than as siblings of module fields may be ignored. + + As with any matter concerning annotations, it is up to implementations how they handle the case where an explicit custom section overlaps with individual annotations that are associated with the same custom section. + + +## Examples + +Expressing generic custom sections (cf. https://gist.github.com/binji/d1cfff7faaebb2aa4f8b1c995234e5a0) +```wasm +(module + (@custom "my-fancy-section" (after func) "contents-bytes") +) +``` + +Expressing names +```wasm +(module (@name "Gümüsü") + (func $lambda (@name "λ") (param $x (@name "α βγ δ") i32) (result i32) (get_local $x)) +) +``` + +Web IDL bindings (cf. https://github.com/WebAssembly/webidl-bindings/blob/master/proposals/webidl-bindings/Explainer.md) +```wasm +(module + (func (export "f") (param i32 (@js unsigned)) ...) ;; argument converted as unsigned + (func (export "method") (param $x anyref (@js this)) (param $y i32) ...) ;; maps this to first arg + (func (import "m" "constructor") (@js new) (param i32) (result anyref) ;; is called as a constructor +) +``` diff --git a/proposals/branch-hinting/Motivation.md b/proposals/branch-hinting/Motivation.md new file mode 100644 index 0000000000..a8e30d734b --- /dev/null +++ b/proposals/branch-hinting/Motivation.md @@ -0,0 +1,393 @@ +# Proposal motivation + +This document has the purpose of explaining better the underlying issues that the proposal +aim to solve, and why they can't be solved in other ways (for example with better tooling). + +# Example code + +We will refer to the following example code, which is purposefully simple to make +the analysis easier. The analysis generalizes for more complex code. + +``` +void foo(); +int run(int i) { + int ret = 0; + int b = 0; + while(i) { + if(__builtin_expect(b==i-1, 0)) { + foo(); + } + b++; + ret += b; + if(b>=i) + break; + } + return ret; +} +``` + +`__builtin_expect` is a gcc and clang specific builtin that hints the compiler that +we know that `b==i-1` is very likely to be false. + +The compiler can use this information to make better decisions regarding register allocation, +inlining, code layout, etc... + +For example here the compiler may decide that it is not worth it to inline the call to `foo()`, +and that in the resulting machine code the call should be placed far from the rest of the code. + +# Native x86-64 code generation + +We can see the effect of `__builtin_expect` when compiling native code by compiling +first with the expected value `1` and then `0`: + +``` +// expected 1 +0000000000000000 : + 0: 55 push rbp + 1: 41 57 push r15 + 3: 41 56 push r14 + 5: 53 push rbx + 6: 50 push rax + 7: 41 89 ff mov r15d,edi + a: 85 ff test edi,edi + c: bb 01 00 00 00 mov ebx,0x1 + 11: 41 be 01 00 00 00 mov r14d,0x1 + 17: 44 0f 4f f7 cmovg r14d,edi + 1b: 41 f7 de neg r14d + 1e: 31 ed xor ebp,ebp + 20: /----> 45 85 ff test r15d,r15d + 23: /--|----- 74 21 je 46 + 25: | | 41 39 df cmp r15d,ebx + 28: | | /-- 75 05 jne 2f + 2a: | | | e8 00 00 00 00 call 2f foo + 2f: | | \-> 01 dd add ebp,ebx + 31: | | 41 8d 04 1e lea eax,[r14+rbx*1] + 35: | | 83 c0 01 add eax,0x1 + 38: | | 89 d9 mov ecx,ebx + 3a: | | 83 c1 01 add ecx,0x1 + 3d: | | 89 cb mov ebx,ecx + 3f: | | 83 f8 01 cmp eax,0x1 + 42: | \----- 75 dc jne 20 + 44: | /-- eb 02 jmp 48 + 46: \-----|-> 31 ed xor ebp,ebp + 48: \-> 89 e8 mov eax,ebp + 4a: 48 83 c4 08 add rsp,0x8 + 4e: 5b pop rbx + 4f: 41 5e pop r14 + 51: 41 5f pop r15 + 53: 5d pop rbp + 54: c3 ret +``` + +``` +// expected 0 +0000000000000000 : + 0: 55 push rbp + 1: 41 57 push r15 + 3: 41 56 push r14 + 5: 53 push rbx + 6: 50 push rax + 7: 41 89 ff mov r15d,edi + a: 85 ff test edi,edi + c: bb 01 00 00 00 mov ebx,0x1 + 11: 41 be 01 00 00 00 mov r14d,0x1 + 17: 44 0f 4f f7 cmovg r14d,edi + 1b: 41 f7 de neg r14d + 1e: 31 ed xor ebp,ebp + 20: /-> 45 85 ff test r15d,r15d + 23: /-----------|-- 74 23 je 48 + 25: | | 41 39 df cmp r15d,ebx + 28: | /-----|-- 74 17 je 41 + 2a: | /--|-----|-> 01 dd add ebp,ebx + 2c: | | | | 41 8d 04 1e lea eax,[r14+rbx*1] + 30: | | | | 83 c0 01 add eax,0x1 + 33: | | | | 89 d9 mov ecx,ebx + 35: | | | | 83 c1 01 add ecx,0x1 + 38: | | | | 89 cb mov ebx,ecx + 3a: | | | | 83 f8 01 cmp eax,0x1 + 3d: | | | \-- 75 e1 jne 20 + 3f: | | | /----- eb 09 jmp 4a + 41: | | \--|----> e8 00 00 00 00 call 46 foo + 46: | \-----|----- eb e2 jmp 2a + 48: \--------|----> 31 ed xor ebp,ebp + 4a: \----> 89 e8 mov eax,ebp + 4c: 48 83 c4 08 add rsp,0x8 + 50: 5b pop rbx + 51: 41 5e pop r14 + 53: 41 5f pop r15 + 55: 5d pop rbp + 56: c3 ret +``` + +You can see that if the compiler expects the branch to be taken, it puts the call +with the rest of the loop code, while if it expects it to be not take, it puts it after. + +This is to maximise the likelyhood that the most often executed code is in the instruction cache. + +# Wasm code generation + +When compiling into Wasm, the compiler can still use `__builtin_expect` to make better decisions: + +For example it can still decide that inlining the call to `foo()` is not worth it. + +But a key difference is that in the case of Wasm, the Wasm code is not the actual machine +code that the cpu will execute. +Moreover, Wasm's structured control flow has no 1 to 1 relationship with the final machine code +that a Wasm engine will generate in tems of code layout. + +This means that there is no way that a Wasm compiler can tell a Wasm engine how it would +like the machine code to be laid out. + +The Wasm generated from the above code is the following (regardless of the presence of `__builtin_expect`): + +``` +func[2] : + local[0..3] type=i32 + local.get 0 + i32.const 4294967295 + i32.add + local.set 1 + local.get 0 + i32.const 0 + i32.ne + local.set 2 + i32.const 0 + local.tee 3 + local.tee 4 + loop + local.get 2 + if + local.get 4 + local.get 1 + i32.eq + if + call 0 + end + local.get 4 + i32.const 1 + i32.add + local.tee 4 + local.get 3 + i32.add + local.set 3 + local.get 4 + local.get 0 + i32.lt_s + br_if 1 + local.get 3 + return + end + end + end +``` + +And the machine code generated by V8's TurboFan is: + +``` +0x3ed39fc0d640 0 55 push rbp +0x3ed39fc0d641 1 4889e5 REX.W movq rbp,rsp +0x3ed39fc0d644 4 6a0a push 0xa +0x3ed39fc0d646 6 56 push rsi +0x3ed39fc0d647 7 4883ec20 REX.W subq rsp,0x20 +0x3ed39fc0d64b b 488b5e23 REX.W movq rbx,[rsi+0x23] +0x3ed39fc0d64f f 488945e8 REX.W movq [rbp-0x18],rax +0x3ed39fc0d653 13 483b23 REX.W cmpq rsp,[rbx] +0x3ed39fc0d656 16 0f869b000000 jna 0x3ed39fc0d6f7 <+0xb7> +0x3ed39fc0d65c 1c 83f800 cmpl rax,0x0 +0x3ed39fc0d65f 1f 0f95c3 setnzl bl +0x3ed39fc0d662 22 0fb6db movzxbl rbx,rbx +0x3ed39fc0d665 25 33c9 xorl rcx,rcx +0x3ed39fc0d667 27 48895dd0 REX.W movq [rbp-0x30],rbx +0x3ed39fc0d66b 2b 488bd1 REX.W movq rdx,rcx +0x3ed39fc0d66e 2e 488bf9 REX.W movq rdi,rcx +0x3ed39fc0d671 31 660f1f840000000000 nop +0x3ed39fc0d67a 3a 660f1f440000 nop +0x3ed39fc0d680 40 4c8b4623 REX.W movq r8,[rsi+0x23] +0x3ed39fc0d684 44 493b20 REX.W cmpq rsp,[r8] +0x3ed39fc0d687 47 0f867c000000 jna 0x3ed39fc0d709 <+0xc9> +0x3ed39fc0d68d 4d 837dd000 cmpl [rbp-0x30],0x0 +0x3ed39fc0d691 51 0f8454000000 jz 0x3ed39fc0d6eb <+0xab> +0x3ed39fc0d697 57 448d40ff leal r8,[rax-0x1] +0x3ed39fc0d69b 5b 443bc7 cmpl r8,rdi +0x3ed39fc0d69e 5e 0f8539000000 jnz 0x3ed39fc0d6dd <+0x9d> +0x3ed39fc0d6a4 64 448b462b movl r8,[rsi+0x2b] +0x3ed39fc0d6a8 68 4d03c5 REX.W addq r8,r13 +0x3ed39fc0d6ab 6b 458b4007 movl r8,[r8+0x7] +0x3ed39fc0d6af 6f 4d03c5 REX.W addq r8,r13 +0x3ed39fc0d6b2 72 4c8b4e2f REX.W movq r9,[rsi+0x2f] +0x3ed39fc0d6b6 76 4d8b09 REX.W movq r9,[r9] +0x3ed39fc0d6b9 79 48894de0 REX.W movq [rbp-0x20],rcx +0x3ed39fc0d6bd 7d 48897dd8 REX.W movq [rbp-0x28],rdi +0x3ed39fc0d6c1 81 4c8bd6 REX.W movq r10,rsi +0x3ed39fc0d6c4 84 498bf0 REX.W movq rsi,r8 +0x3ed39fc0d6c7 87 4d8bc2 REX.W movq r8,r10 +0x3ed39fc0d6ca 8a 41ffd1 call r9 +0x3ed39fc0d6cd 8d 488b45e8 REX.W movq rax,[rbp-0x18] +0x3ed39fc0d6d1 91 488b4de0 REX.W movq rcx,[rbp-0x20] +0x3ed39fc0d6d5 95 488b7dd8 REX.W movq rdi,[rbp-0x28] +0x3ed39fc0d6d9 99 488b75f0 REX.W movq rsi,[rbp-0x10] +0x3ed39fc0d6dd 9d 83c701 addl rdi,0x1 +0x3ed39fc0d6e0 a0 03cf addl rcx,rdi +0x3ed39fc0d6e2 a2 3bf8 cmpl rdi,rax +0x3ed39fc0d6e4 a4 7c9a jl 0x3ed39fc0d680 <+0x40> +0x3ed39fc0d6e6 a6 e907000000 jmp 0x3ed39fc0d6f2 <+0xb2> +0x3ed39fc0d6eb ab 33c0 xorl rax,rax +0x3ed39fc0d6ed ad 488be5 REX.W movq rsp,rbp +0x3ed39fc0d6f0 b0 5d pop rbp +0x3ed39fc0d6f1 b1 c3 retl +0x3ed39fc0d6f2 b2 488bc1 REX.W movq rax,rcx +0x3ed39fc0d6f5 b5 ebf6 jmp 0x3ed39fc0d6ed <+0xad> +0x3ed39fc0d6f7 b7 e884fbffff call 0x3ed39fc0d280 ;; wasm stub: WasmStackGuard +0x3ed39fc0d6fc bc 488b45e8 REX.W movq rax,[rbp-0x18] +0x3ed39fc0d700 c0 488b75f0 REX.W movq rsi,[rbp-0x10] +0x3ed39fc0d704 c4 e953ffffff jmp 0x3ed39fc0d65c <+0x1c> +0x3ed39fc0d709 c9 48894de0 REX.W movq [rbp-0x20],rcx +0x3ed39fc0d70d cd 48897dd8 REX.W movq [rbp-0x28],rdi +0x3ed39fc0d711 d1 e86afbffff call 0x3ed39fc0d280 ;; wasm stub: WasmStackGuard +0x3ed39fc0d716 d6 488b45e8 REX.W movq rax,[rbp-0x18] +0x3ed39fc0d71a da 488b4de0 REX.W movq rcx,[rbp-0x20] +0x3ed39fc0d71e de 488b7dd8 REX.W movq rdi,[rbp-0x28] +0x3ed39fc0d722 e2 488b75f0 REX.W movq rsi,[rbp-0x10] +0x3ed39fc0d726 e6 e962ffffff jmp 0x3ed39fc0d68d <+0x4d> +0x3ed39fc0d72b eb 90 nop +``` + +As you can see the call to `foo()` (`call r9`) is in the middle of the loop code. + +Is there a way to produce different Wasm code which will result in the call being after +the rest of the loop code? + +No, because: + +- the call is part of the loop +- Wasm loops can only have one entry point + +Therefore it is impossible in Wasm to have some code part of a loop moved after it, +because it would cause irreducible control flow. + +Of course we could introduce irreducible control flow and then turn it into reducible control flow +again (Wasm compilers already must be able to do this), but there are only two way of +fixing the irreducible control flow: duplicating the code or introducing a new variable +to direct the control flow: + +- The first alternative is not feasible in the general case because the code duplication +can incur in exponential blowup + +- The second is feasible but introduces so much overhead that it defeats the purpose + +# Branch Hinting proposal + +The underlying problem is that there is a gap of information between the high level source code +and the final machine code that the engine runs. + +The Branch Hinting proposal aims to close the gap by explicitly passing on the missing information +to the engine. + +As a matter of fact, V8 already has the concept of "branch hinting": It is used internally +to produce better code in the presence of unlikely conditions. + +It is also currently used to implement the experimental `br_on_null` instruction (with the assumption +that it is unlikely to be null). + +These of course are implementation details, but they hint at the fact that this functionality +is valuable. + +Without this proposal, performance sesitive applications that benefits from branch hinting +may have to rely on internal engine behavior to achieve maximum performance. + +For example the CheerpX virtual machine is currently experimenting with using br_on_null to take advantage of internal branch hinting in V8. +Even though this solution is inefficient the performance gains are nonetheless significant. +Proper support for branch hinting would improve the performance even more. + +# Wasm code generation with branch hinting + +The V8 POC with branch hinting support produces the following machine code for our example code: + +``` +0xfe77b01d640 0 55 push rbp +0xfe77b01d641 1 4889e5 REX.W movq rbp,rsp +0xfe77b01d644 4 6a0a push 0xa +0xfe77b01d646 6 56 push rsi +0xfe77b01d647 7 4883ec20 REX.W subq rsp,0x20 +0xfe77b01d64b b 488b5e23 REX.W movq rbx,[rsi+0x23] +0xfe77b01d64f f 483b23 REX.W cmpq rsp,[rbx] +0xfe77b01d652 12 0f8656000000 jna 0xfe77b01d6ae <+0x6e> +0xfe77b01d658 18 83f800 cmpl rax,0x0 +0xfe77b01d65b 1b 0f95c3 setnzl bl +0xfe77b01d65e 1e 0fb6db movzxbl rbx,rbx +0xfe77b01d661 21 33c9 xorl rcx,rcx +0xfe77b01d663 23 488bd1 REX.W movq rdx,rcx +0xfe77b01d666 26 488bf9 REX.W movq rdi,rcx +0xfe77b01d669 29 0f1f8000000000 nop +0xfe77b01d670 30 4c8b4623 REX.W movq r8,[rsi+0x23] +0xfe77b01d674 34 493b20 REX.W cmpq rsp,[r8] +0xfe77b01d677 37 0f8644000000 jna 0xfe77b01d6c1 <+0x81> +0xfe77b01d67d 3d 83fb00 cmpl rbx,0x0 +0xfe77b01d680 40 0f841b000000 jz 0xfe77b01d6a1 <+0x61> +0xfe77b01d686 46 448d40ff leal r8,[rax-0x1] +0xfe77b01d68a 4a 443bc7 cmpl r8,rdi +0xfe77b01d68d 4d 0f845b000000 jz 0xfe77b01d6ee <+0xae> +0xfe77b01d693 53 83c701 addl rdi,0x1 +0xfe77b01d696 56 03cf addl rcx,rdi +0xfe77b01d698 58 3bf8 cmpl rdi,rax +0xfe77b01d69a 5a 7cd4 jl 0xfe77b01d670 <+0x30> +0xfe77b01d69c 5c e908000000 jmp 0xfe77b01d6a9 <+0x69> +0xfe77b01d6a1 61 488bc2 REX.W movq rax,rdx +0xfe77b01d6a4 64 488be5 REX.W movq rsp,rbp +0xfe77b01d6a7 67 5d pop rbp +0xfe77b01d6a8 68 c3 retl +0xfe77b01d6a9 69 488bc1 REX.W movq rax,rcx +0xfe77b01d6ac 6c ebf6 jmp 0xfe77b01d6a4 <+0x64> +0xfe77b01d6ae 6e 488945e8 REX.W movq [rbp-0x18],rax +0xfe77b01d6b2 72 e8c9fbffff call 0xfe77b01d280 ;; wasm stub: WasmStackGuard +0xfe77b01d6b7 77 488b45e8 REX.W movq rax,[rbp-0x18] +0xfe77b01d6bb 7b 488b75f0 REX.W movq rsi,[rbp-0x10] +0xfe77b01d6bf 7f eb97 jmp 0xfe77b01d658 <+0x18> +0xfe77b01d6c1 81 488945e8 REX.W movq [rbp-0x18],rax +0xfe77b01d6c5 85 48894de0 REX.W movq [rbp-0x20],rcx +0xfe77b01d6c9 89 48897dd8 REX.W movq [rbp-0x28],rdi +0xfe77b01d6cd 8d 48895dd0 REX.W movq [rbp-0x30],rbx +0xfe77b01d6d1 91 e8aafbffff call 0xfe77b01d280 ;; wasm stub: WasmStackGuard +0xfe77b01d6d6 96 33d2 xorl rdx,rdx +0xfe77b01d6d8 98 488b45e8 REX.W movq rax,[rbp-0x18] +0xfe77b01d6dc 9c 488b4de0 REX.W movq rcx,[rbp-0x20] +0xfe77b01d6e0 a0 488b7dd8 REX.W movq rdi,[rbp-0x28] +0xfe77b01d6e4 a4 488b75f0 REX.W movq rsi,[rbp-0x10] +0xfe77b01d6e8 a8 488b5dd0 REX.W movq rbx,[rbp-0x30] +0xfe77b01d6ec ac eb8f jmp 0xfe77b01d67d <+0x3d> +0xfe77b01d6ee ae 448b462b movl r8,[rsi+0x2b] +0xfe77b01d6f2 b2 4d03c5 REX.W addq r8,r13 +0xfe77b01d6f5 b5 458b4007 movl r8,[r8+0x7] +0xfe77b01d6f9 b9 4d03c5 REX.W addq r8,r13 +0xfe77b01d6fc bc 4c8b4e2f REX.W movq r9,[rsi+0x2f] +0xfe77b01d700 c0 4d8b09 REX.W movq r9,[r9] +0xfe77b01d703 c3 488945e8 REX.W movq [rbp-0x18],rax +0xfe77b01d707 c7 48894de0 REX.W movq [rbp-0x20],rcx +0xfe77b01d70b cb 48897dd8 REX.W movq [rbp-0x28],rdi +0xfe77b01d70f cf 48895dd0 REX.W movq [rbp-0x30],rbx +0xfe77b01d713 d3 4c8bd6 REX.W movq r10,rsi +0xfe77b01d716 d6 498bf0 REX.W movq rsi,r8 +0xfe77b01d719 d9 4d8bc2 REX.W movq r8,r10 +0xfe77b01d71c dc 41ffd1 call r9 +0xfe77b01d71f df 33d2 xorl rdx,rdx +0xfe77b01d721 e1 488b45e8 REX.W movq rax,[rbp-0x18] +0xfe77b01d725 e5 488b4de0 REX.W movq rcx,[rbp-0x20] +0xfe77b01d729 e9 488b7dd8 REX.W movq rdi,[rbp-0x28] +0xfe77b01d72d ed 488b75f0 REX.W movq rsi,[rbp-0x10] +0xfe77b01d731 f1 488b5dd0 REX.W movq rbx,[rbp-0x30] +0xfe77b01d735 f5 e959ffffff jmp 0xfe77b01d693 <+0x53> +0xfe77b01d73a fa 90 nop +0xfe77b01d73b fb 90 nop +``` + +As you can see the call to `foo()` (`call r9`) is laid out after the rest of the loop code. + +As a result, the hinted version runs ~25% faster than the non-hinted one. + +Of course real world code is more complex and probably doing some real computation +instead of just adding numbers in a tight loop. The actual performance gain will +depend heavily on the specific code, and how good the assumptions about the likeness +of a branch are. + +Techniques like Performance Guided Optimization can also make use of this feature +and provide more precise hints. diff --git a/proposals/branch-hinting/Overview.md b/proposals/branch-hinting/Overview.md new file mode 100644 index 0000000000..dedd4b4c8c --- /dev/null +++ b/proposals/branch-hinting/Overview.md @@ -0,0 +1,130 @@ +# Branch hinting + +## Introduction + +### Motivation + +The motivation for this proposal is to improve the performance of the compiled wasm +code, by informing the engine that a particular branch instruction is very likely to take +a specific path. + +This allows the engine to make better decisions for code layout (improving instruction cache hits) +and register allocation. + +See [Motivation](/proposals/branch-hinting/Motivation.md) for more information. + + +### Background + +This proposal has first been presented here: + +https://github.com/WebAssembly/design/issues/1363 + +This topic was preliminarily discussed at the CG-09-29 meeting: + +https://github.com/WebAssembly/meetings/blob/master/main/2020/CG-09-29.md + +The consensus seemed to be to proceed with the idea, but to present some benchmarks +to prove the need for the proposal. + +A [preliminary benchmark](/benchmarks) showed that the proposal is worth investigating + +The proposal was accepted for phase 1 at the CG-11-10 meeting: + +https://github.com/WebAssembly/meetings/blob/master/main/2020/CG-11-10.md + +Discussion about how to implement the proposal happened in this issue: + +https://github.com/WebAssembly/branch-hinting/issues/1 + +And some more discussion and feedback on the custom section format was done in the CG-01-05: + +https://github.com/WebAssembly/meetings/blob/master/main/2021/CG-01-05.md + +After that, a tentative spec was written and there was further discussion in CG-02-16: + +https://github.com/WebAssembly/meetings/blob/master/main/2021/CG-02-16.md + +And in CG-03-02: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-03-02.md + +Finally after working on the feedback received, a poll for phase 2 was made, and passed, in CG-03-16: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-03-16.md + +The feature landed in V8 92 under an experimental flag, and the CheerpX virtual machine started using it, +with measurable performance improvements. The results were shown in CG-06-08, plus the start of +a discussion about testing requirements for phase 3: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-06-08.md + +After the Instrument and Tracing Proposal passed to phase 2 in CG-07-20, the CG felt the need to +have a framework for standardizing proposals that use custom section as a way to hint the code +without altering the semantics. Ongoing discussion is in a design issue: + +https://github.com/WebAssembly/design/issues/1424 + +The result of that discussion prompted the design of Code Annotations, presented in CG-08-17: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-08-17.md + +and in CG-09-28: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-09-28.md + +Branch hinting adpoted the Code Annotation format and an update about the new format +and the current status of the proposal, as well as a poll for phase 3 (which passed) + was held on CG-11-09: + +https://github.com/WebAssembly/meetings/blob/main/main/2021/CG-11-09.md + + + +### Design + +The *branch hint section* is a **custom section** whose name string is `metadata.code.branch_hint`. +The branch hints section should appear only once in a module, and only before the code section. + +The purpose of this section is to aid the compilation of conditional branch instructions, by providing a hint that a branch is very likely (or unlikely) to be taken. + +The section contains a vector of *function branch hints* each representing the branch hints for a single function. + +Each *function branch hints* structure consists of + +* the function index of the function the hints are referring to, +* a vector of *branch hint* for the function. + +Elements of the vector of *function branch hints* must appear in increasing function index order, +and a function index can appear at most once. + +Each *branch hint* structure consists of + +* the |U32| byte offset of the hinted instruction, relative to the beginning of the function locals declaration, +* A |U32| with value `1`, +* a |U32| indicating the meaning of the hint: + +| value | meaning | +|-------|-------------------| +| 0 | likely not taken | +| 1 | likely taken | + +Elements of the vector of *branch hint* must appear in increasing byte offset order, +and a byte offset can appear at most once. A |BRIF| or |IF| instruction must be present +in the code section at the specified offset. + +#### Annotations + +*Branch Hint annotations* are the textual analogue to the branch hint section and provide a textual representation for it. +Consequently, their id is :math:`@metadata.code.branch_hint`. + +Branch hint annotations are allowed only on |BRIF| and |IF| instructions, +and at most one branch hint annotation may be given per instruction. + +Branch hint annotations have the following format: + +``` +(@metadata.code.branch_hint "\00" | "\01") +``` + +See the [the spec](/document/core/appendix/custom.rst) for the formal notation. diff --git a/proposals/bulk-memory-operations/Overview.md b/proposals/bulk-memory-operations/Overview.md index 0dfc532642..c93eaa3a0d 100644 --- a/proposals/bulk-memory-operations/Overview.md +++ b/proposals/bulk-memory-operations/Overview.md @@ -228,7 +228,7 @@ The meaning of the bits of the flag field (a `varuint32`) for element segments i | 0 | 0=is active, 1=is passive | | 1 | if bit 0 clear: 0=table 0, 1=has table index | | | if bit 0 set: 0=active, 1=declared | -| 2 | 0=carries indicies; 1=carries elemexprs | +| 2 | 0=carries indices; 1=carries elemexprs | which yields this view, with the fields carried by each flag value: @@ -356,7 +356,7 @@ The bounds check is performed before any data are written. ### `table.init`, `elem.drop`, and `table.copy` instructions -The `table.*` instructions behave similary to the `memory.*` instructions, with +The `table.*` instructions behave similarly to the `memory.*` instructions, with the difference that they operate on element segments and tables, instead of data segments and memories. The offset and length operands of `table.init` and `table.copy` have element units instead of bytes as well. diff --git a/proposals/exception-handling/Exceptions.md b/proposals/exception-handling/Exceptions.md index 9a2ddfb8de..d982592ced 100644 --- a/proposals/exception-handling/Exceptions.md +++ b/proposals/exception-handling/Exceptions.md @@ -1,8 +1,8 @@ # Exception handling This explainer reflects the up-to-date version of the exception handling -proposal agreed on [the CG meeting on -09-15-2020](https://github.com/WebAssembly/meetings/blob/master/main/2020/CG-09-15.md). +proposal agreed on [Oct 2023 CG +meeting](https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md#exception-handling-vote-on-proposal-to-re-introduce-exnref). --- @@ -36,8 +36,8 @@ succeeding instructions to process the data. A WebAssembly exception is created when you throw it with the `throw` instruction. Thrown exceptions are handled as follows: -1. They can be caught by one of the *catch clauses* in an enclosing try - block of a function body. +1. They can be caught by one of the *catch clauses* in an enclosing try block of + a function body. 1. Throws not caught within a function body continue up the call stack, popping call frames, until an enclosing try block is found. @@ -86,21 +86,22 @@ Exception tag indices are used by: 1. The `throw` instruction which creates a WebAssembly exception with the corresponding exception tag, and then throws it. -2. Catch clauses use a tag to identify the thrown exception it - can catch. If it matches, it pushes the corresponding argument values of the - exception onto the stack. +2. Catch clauses use a tag to identify the thrown exception it can catch. If it + matches, it pushes the corresponding argument values of the exception onto + the stack. ### Exception references -When caught, an exception is reified into an _exception reference_, a value of the new type `exnref`. -Exception references can be used to rethrow the caught exception. +When caught, an exception is reified into an _exception reference_, a value of +the new type `exnref`. Exception references can be used to rethrow the caught +exception. ### Try blocks A _try block_ defines a list of instructions that may need to process exceptions and/or clean up state when an exception is thrown. Like other higher-level -constructs, a try block begins with a `try_table` instruction, and ends with an `end` -instruction. That is, a try block is sequence of instructions having the +constructs, a try block begins with a `try_table` instruction, and ends with an +`end` instruction. That is, a try block is sequence of instructions having the following form: ``` @@ -109,10 +110,11 @@ try_table blocktype catch* end ``` -A try block contains zero or more _catch clauses_. If there are no catch clauses, then the try block does not catch any exceptions. +A try block contains zero or more _catch clauses_. If there are no catch +clauses, then the try block does not catch any exceptions. -The _body_ of the try block is the list of instructions after the last -catch clause, if any. +The _body_ of the try block is the list of instructions after the last catch +clause, if any. Each `catch` clause can be in one of 4 forms: ``` @@ -121,16 +123,16 @@ catch_ref tag label catch_all label catch_all_ref label ``` -All forms have a label which is branched to when an exception is cought (see below). -The former two forms have an exception tag associated with it that -identifies what exceptions it will catch. -The latter two forms catch any exception, so that they can be used to define a _default_ handler. +All forms have a label which is branched to when an exception is caught (see +below). The former two forms have an exception tag associated with it that +identifies what exceptions it will catch. The latter two forms catch any +exception, so that they can be used to define a _default_ handler. Try blocks, like control-flow blocks, have a _block type_. The block type of a try block defines the values yielded by evaluating the try block when either no -exception is thrown, or the exception is successfully caught by the catch clause. -Because `try_table` defines a control-flow block, it can be -targets for branches (`br` and `br_if`) as well. +exception is thrown, or the exception is successfully caught by the catch +clause. Because `try_table` defines a control-flow block, it can be targets for +branches (`br` and `br_if`) as well. ### Throwing an exception @@ -160,37 +162,38 @@ Once a catching try block is found for the thrown exception, the operand stack is popped back to the size the operand stack had when the try block was entered after possible block parameters were popped. -Then catch clauses are tried in the order -they appear in the catching try block, until one matches. If a matching catch clause is found, control is transferred to the label of that catch clause. -In case of `catch` or `catch_ref`, -the arguments of the exception are pushed back onto the stack. -For `catch_ref` and `catch_all_ref`, an exception reference is then pushed to the stack, which represents the caught exception. +Then catch clauses are tried in the order they appear in the catching try block, +until one matches. If a matching catch clause is found, control is transferred +to the label of that catch clause. In case of `catch` or `catch_ref`, the +arguments of the exception are pushed back onto the stack. For `catch_ref` and +`catch_all_ref`, an exception reference is then pushed to the stack, which +represents the caught exception. If no catch clauses were matched, the exception is implicitly rethrown. -Note that a caught exception can be rethrown explicitly using the `exnref` and the `throw_ref` instruction. +Note that a caught exception can be rethrown explicitly using the `exnref` and +the `throw_ref` instruction. ### Rethrowing an exception -The `throw_ref` takes an operand of type `exnref` and re-throws the corresponding caught exception. -If the operand is null, a trap occurs. +The `throw_ref` takes an operand of type `exnref` and re-throws the +corresponding caught exception. If the operand is null, a trap occurs. ### JS API #### Traps -Catch clauses handle exceptions generated by the `throw` -instruction, but do not catch traps. The rationale for this is that in general -traps are not locally recoverable and are not needed to be handled in local -scopes like try blocks. +Catch clauses handle exceptions generated by the `throw` instruction, but do not +catch traps. The rationale for this is that in general traps are not locally +recoverable and are not needed to be handled in local scopes like try blocks. The `try_table` instruction catches foreign exceptions generated from calls to function imports as well, including JavaScript exceptions, with a few exceptions: 1. In order to be consistent before and after a trap reaches a JavaScript frame, the `try_table` instruction does not catch exceptions generated from traps. -1. The `try_table` instruction does not catch JavaScript exceptions generated from - stack overflow and out of memory. +1. The `try_table` instruction does not catch JavaScript exceptions generated + from stack overflow and out of memory. Filtering these exceptions should be based on a predicate that is not observable by JavaScript. Traps currently generate instances of @@ -232,22 +235,22 @@ check ensures that without access to a WebAssembly module's exported exception tag, the associated data fields cannot be read. The `Exception` constructor can take an optional `ExceptionOptions` argument, -which can optionally contain `traceStack` entry. When `traceStack` is -`true`, JavaScript VMs are permitted to attach a stack trace string to -`Exception.stack` field, as in JavaScript's `Error` class. `traceStack` -serves as a request to the WebAssembly engine to attach a stack trace; it -is not necessary to honour if `true`, but `trace` may not be populated if -`traceStack` is `false`. While `Exception` is not a subclass of JavaScript's -`Error` and it can be used to represent normal control flow constructs, -`traceStack` field can be set when we use it to represent errors. The -format of stack trace strings conform to the [WebAssembly stack trace +which can optionally contain `traceStack` entry. When `traceStack` is `true`, +JavaScript VMs are permitted to attach a stack trace string to `Exception.stack` +field, as in JavaScript's `Error` class. `traceStack` serves as a request to the +WebAssembly engine to attach a stack trace; it is not necessary to honour if +`true`, but `trace` may not be populated if `traceStack` is `false`. While +`Exception` is not a subclass of JavaScript's `Error` and it can be used to +represent normal control flow constructs, `traceStack` field can be set when we +use it to represent errors. The format of stack trace strings conform to the +[WebAssembly stack trace conventions](https://webassembly.github.io/spec/web-api/index.html#conventions). When `ExceptionOption` is not provided or it does not contain `traceStack` entry, `traceStack` is considered `false` by default. To preserve stack trace info when crossing the JS to Wasm boundary, exceptions -can internally contain a stack trace, which is propagated when caught by a `catch[_all]_ref` clause -and rethrown by `throw_ref`. +can internally contain a stack trace, which is propagated when caught by a +`catch[_all]_ref` clause and rethrown by `throw_ref`. More formally, the added interfaces look like the following: @@ -331,16 +334,17 @@ document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md). The type `exnref` is represented by the type opcode `-0x17`. -When combined with the [GC proposal](https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md), -there also is a value type `nullexnref` with opcode `-0x0c`. -Furthermore, these opcodes also function as heap type, -i.e., `exn` is a new heap type with opcode `-0x17`, -and `noexn` is a new heap type with opcode `-0x0c`; -`exnref` and `nullexnref` are shorthands for `(ref null exn)` and `(ref null noexn)`, respectively. +When combined with the [GC +proposal](https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md), +there also is a value type `nullexnref` with opcode `-0x0c`. Furthermore, these +opcodes also function as heap type, i.e., `exn` is a new heap type with opcode +`-0x17`, and `noexn` is a new heap type with opcode `-0x0c`; `exnref` and +`nullexnref` are shorthands for `(ref null exn)` and `(ref null noexn)`, +respectively. -The heap type `noexn` is a subtype of `exn`. -They are not in a subtype relation with any other type (except bottom), -such that they form a new disjoint hierarchy of heap types. +The heap type `noexn` is a subtype of `exn`. They are not in a subtype relation +with any other type (except bottom), such that they form a new disjoint +hierarchy of heap types. ##### tag_type @@ -439,13 +443,13 @@ follows: ###### Tag names -The tag names subsection is a `name_map` which assigns names to a subset of -the tag indices (Used for both imports and module-defined). +The tag names subsection is a `name_map` which assigns names to a subset of the +tag indices (Used for both imports and module-defined). ### Control flow instructions -The control flow instructions are extended to define try blocks and -throws as follows: +The control flow instructions are extended to define try blocks and throws as +follows: | Name | Opcode | Immediates | Description | | ---- | ---- | ---- | ---- | diff --git a/proposals/exception-handling/Exceptions-formal-examples.md b/proposals/exception-handling/legacy/Exceptions-formal-examples.md similarity index 100% rename from proposals/exception-handling/Exceptions-formal-examples.md rename to proposals/exception-handling/legacy/Exceptions-formal-examples.md diff --git a/proposals/exception-handling/Exceptions-formal-overview.md b/proposals/exception-handling/legacy/Exceptions-formal-overview.md similarity index 100% rename from proposals/exception-handling/Exceptions-formal-overview.md rename to proposals/exception-handling/legacy/Exceptions-formal-overview.md diff --git a/proposals/exception-handling/legacy/Exceptions.md b/proposals/exception-handling/legacy/Exceptions.md new file mode 100644 index 0000000000..74270f88c0 --- /dev/null +++ b/proposals/exception-handling/legacy/Exceptions.md @@ -0,0 +1,711 @@ +# Exception handling + +This explainer reflects the third version of the proposal adopted in the [CG +meeting on +09-15-2020](https://github.com/WebAssembly/meetings/blob/main/main/2020/CG-09-15.md#proposal-for-changes-in-eh-proposal-for-two-phase-unwinding-support-part-2--discussions-heejin-ahn-30-min), +which removed `exnref`. The rational then was the old proposal having a +first-class exception reference type was not easily extensible to a future +proposal that supports two-phase unwinding. + +This proposal was superseded by the [current +proposal](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/Exceptions.md) +in [Oct 2023 CG +meeting](https://github.com/WebAssembly/meetings/blob/main/main/2023/CG-10.md#exception-handling-vote-on-proposal-to-re-introduce-exnref), +recognizing that after all `exnref` provided important functionalities for the +spec and engine/toolchain implementations. + +This proposal is currently also known as the "legacy proposal" and still +supported in the web, but can be deprecated in future and the use of this +proposal is discouraged. + +--- + +## Overview + +Exception handling allows code to break control flow when an exception is +thrown. The exception can be any exception known by the WebAssembly module, or +it may an unknown exception that was thrown by a called imported function. + +One of the problems with exception handling is that both WebAssembly and an +embedder have different notions of what exceptions are, but both must be aware +of the other. + +It is difficult to define exceptions in WebAssembly because (in general) it +doesn't have knowledge of any embedder. Further, adding such knowledge to +WebAssembly would limit the ability for other embedders to support WebAssembly +exceptions. + +One issue is that both sides need to know if an exception was thrown by the +other, because cleanup may need to be performed. + +Another problem is that WebAssembly doesn't have direct access to an embedder's +memory. As a result, WebAssembly defers the handling of exceptions to the host +VM. + +To access exceptions, WebAssembly provides instructions to check if the +exception is one that WebAssembly understands. If so, the data of the +WebAssembly exception is extracted and copied onto the stack, allowing +succeeding instructions to process the data. + +A WebAssembly exception is created when you throw it with the `throw` +instruction. Thrown exceptions are handled as follows: + +1. They can be caught by one of `catch`/`catch_all` blocks in an enclosing try + block of a function body. + +1. Throws not caught within a function body continue up the call stack, popping + call frames, until an enclosing try block is found. + +1. If the call stack is exhausted without any enclosing try blocks, the embedder + defines how to handle the uncaught exception. + +### Exception handling + +This proposal adds exception handling to WebAssembly. Part of this proposal is +to define a new section to declare exceptions. However, rather than limiting +this new section to just defining exceptions, it defines a more general format +`tag` that allows the declaration of other forms of typed tags in future. + +WebAssembly tags are defined in a new `tag` section of a WebAssembly module. The +tag section is a list of declared tags that are created fresh each time the +module is instantiated. + +Each tag has an `attribute` and a `type`. Currently, the attribute can only +specify that the tag is for an exception. In the future, additional attribute +values may be added when other kinds of tags are added to WebAssembly. + +To allow for such a future extension possibility, we reserve a byte in the +binary format of an exception definition, set to 0 to denote an exception +attribute. + +### Exceptions + +An `exception tag` is a value to distinguish different exceptions, while an +`exception tag index` is a numeric name to refer to an (imported or defined) +exception tag within a module (see [tag index space](#tag-index-space) for +details). Exception tags are declared in the tag and import sections of a +module. + +An `exception` is an internal construct in WebAssembly that represents a runtime +object that can be thrown. A WebAssembly exception consists of an exception tag +and its runtime arguments. + +The type of an exception tag is denoted by an index to a function signature +defined in the `type` section. The parameters of the function signature define +the list of argument values associated with the tag. The result type must be +empty. + +Exception tag indices are used by: + +1. The `throw` instruction which creates a WebAssembly exception with the + corresponding exception tag, and then throws it. + +2. The `catch` clause uses the tag to identify if the thrown exception is one it + can catch. If true it pushes the corresponding argument values of the + exception onto the stack. + +### Try-catch blocks + +A _try block_ defines a list of instructions that may need to process exceptions +and/or clean up state when an exception is thrown. Like other higher-level +constructs, a try block begins with a `try` instruction, and ends with an `end` +instruction. That is, a try-catch block is sequence of instructions having the +following form: + +``` +try blocktype + instruction* +catch i + instruction* +catch j + instruction* +... +catch_all + instruction* +end +``` + +A try-catch block contains zero or more `catch` blocks and zero or one +`catch_all` block. All `catch` blocks must precede the `catch_all` block, if +any. The `catch`/`catch_all` instructions (within the try construct) are called +the _catching_ instructions. There may not be any `catch` or `catch_all` blocks +after a `try`, in which case the `try` block does not catch any exceptions. + +The _body_ of the try block is the list of instructions before the first +catching instruction. The _body_ of each catch block is the sequence of +instructions following the corresponding catching instruction before the next +catching instruction (or the `end` instruction if it is the last catching +block). + +The `catch` instruction has an exception tag associated with it. The tag +identifies what exceptions it can catch. That is, any exception created with the +corresponding exception tag. Catch blocks that begin with a `catch` instruction +are considered _tagged_ catch blocks. + +The last catching instruction of a try-catch block can be the `catch_all` +instruction. If it begins with the `catch_all` instruction, it defines the +_default_ catch block. The default catch block has no tag index, and is used to +catch all exceptions not caught by any of the tagged catch blocks. The term +'catch block' refers to both `catch` and `catch_all` blocks. + +When the program runs `br` within `catch` or `catch_all` blocks, the rest of +the catching blocks will not run and the program control will branch to the +destination, as in normal blocks. + +Try blocks, like control-flow blocks, have a _block type_. The block type of a +try block defines the values yielded by evaluating the try block when either no +exception is thrown, or the exception is successfully caught by the catch block. +Because `try` and `end` instructions define a control-flow block, they can be +targets for branches (`br` and `br_if`) as well. + +### Throwing an exception + +The `throw` instruction takes an exception tag index as an immediate argument. +That index is used to identify the exception tag to use to create and throw the +corresponding exception. + +The values on top of the stack must correspond to the type associated with the +exception tag. These values are popped off the stack and are used (along with +the corresponding exception tag) to create the corresponding exception. That +exception is then thrown. + +When an exception is thrown, the embedder searches for the nearest enclosing try +block body that execution is in. That try block is called the _catching_ try +block. + +If the throw appears within the body of a try block, it is the catching try +block. + +If a throw occurs within a function body, and it doesn't appear inside the body +of a try block, the throw continues up the call stack until it is in the body of +an an enclosing try block, or the call stack is flushed. If the call stack is +flushed, the embedder defines how to handle uncaught exceptions. Otherwise, the +found enclosing try block is the catching try block. + +A throw inside the body of a catch block is never caught by the corresponding +try block of the catch block, since instructions in the body of the catch block +are not in the body of the try block. + +Once a catching try block is found for the thrown exception, the operand stack +is popped back to the size the operand stack had when the try block was entered +after possible block parameters were popped. + +Then in case of a try-catch block, tagged catch blocks are tried in the order +they appear in the catching try block, until one matches. If a matched tagged +catch block is found, control is transferred to the body of the catch block, and +the arguments of the exception are pushed back onto the stack. + +Otherwise, control is transferred to the body of the `catch_all` block, if any. +However, unlike tagged catch blocks, the constructor arguments are not copied +back onto the operand stack. + +If no tagged catch blocks were matched, and the catching try block doesn't have +a `catch_all` block, the exception is rethrown. + +If control is transferred to the body of a catch block, and the last instruction +in the body is executed, control then exits the try block. + +If the selected catch block does not throw an exception, it must yield the +value(s) specified by the type annotation on the corresponding catching try +block. + +Note that a caught exception can be rethrown using the `rethrow` instruction. + +### Rethrowing an exception + +The `rethrow` instruction can only appear in the body of a catch/catch_all +block. It always re-throws the exception caught by an enclosing catch block. + +Associated with the `rethrow` instruction is a _label_. The label is used to +disambiguate which exception is to be rethrown, when inside nested catch blocks. +The label is the relative block depth to the corresponding try block for which +the catching block appears. + +For example consider the following: + +``` +try $l1 + ... +catch ;; $l1 + ... + block + ... + try $l2 + ... + catch ;; $l2 + ... + try $l3 + ... + catch ;; $l3 + ... + rethrow N ;; (or label) + end + end + end + ... +end +``` + +In this example, `N` is used to disambiguate which caught exception is being +rethrown. It could rethrow any of the three caught expceptions. Hence, `rethrow +0` corresponds to the exception caught by `catch 3`, `rethrow 1` corresponds to +the exception caught by `catch 2`, and `rethrow 3` corresponds to the exception +caught by `catch 1`. In wat format, the argument for the `rethrow` instructions +can also be written as a label, like branches. So `rethrow 0` in the example +above can also be written as `rethrow $l3`. + +Note that `rethrow 2` is not allowed because it does not refer to a `try` label +from within its catch block. Rather, it references a `block` instruction, so it +will result in a validation failure. + +Note that the example below is a validation failure: +``` +try $l1 + try $l2 + rethrow $l2 ;; (= rethrow 0) + catch + end +catch +end +``` +The `rethrow` here references `try $l2`, but the `rethrow` is not within its +`catch` block. + +The example below includes all of the cases explained above. The numbers +within `()` after `rethrow`s are the label operands in immediate values. +``` +(func $test + try $lA + ... + catch ($lA) + ... + block $lB + ... + try $lC + ... + catch ($lC) + ... + try $lD + ... + rethrow $lD (0) ;; refers to 'catch ($lD)', but it is not within 'catch ($lD)', so validation failure + rethrow $lC (1) ;; rethrows the exception caught by catch ($lC) + rethrow $lB (2) ;; refers to 'block $lB', so validation failure + rethrow $lA (3) ;; rethrows the exception caught by catch ($lA) + rethrow 4 ;; validation failure + catch ($lD) + ... + rethrow $lD (0) ;; rethrows the exception caught by catch ($lD) + rethrow $lC (1) ;; rethrows the exception caught by catch ($lC) + rethrow $lB (2) ;; refers to 'block $lB', so validation failure + rethrow $lA (3) ;; rethrows the exception caught by catch ($lA) + rethrow 4 ;; validation failure + end + end + end + ... + end +``` + +### Try-delegate blocks + +Try blocks can also be used with the `delegate` instruction. A try-delegate +block contains a `delegate` instruction with the following form: + +``` +try blocktype + instruction* +delegate label +``` + +The `delegate` clause does not have an associated body, so try-delegate blocks +don't have an `end` instruction at the end. The `delegate` instruction takes a +try label and delegates exception handling to a `catch`/`catch_all`/`delegate` +specified by the `try` label. For example, consider this code: + +``` +try $l0 + try + call $foo + delegate $l0 ;; (= delegate 0) +catch + ... +catch_all + ... +end +``` + +If `call $foo` throws, searching for a catching block first finds `delegate`, +and because it delegates exception handling to catching instructions associated +with `$l1`, it will be next checked by the outer `catch` and then `catch_all` +instructions. + +`delegate` can also target `catch`-less `try`s or non-`try` block constructs +like `block`s or `loop`s, in which case the delegated exception is assumed to +propagate to the outer scope and will be caught by the next matching +try-catches, or rethrown to the caller if there is no outer try block. In the +examples, catches are annotated with `($label_name)` to clarify which `try` it +belongs to for clarification; it is not the official syntax. +``` +try $l0 + block $l1 + try + call $foo + delegate $l1 ;; delegates to 'catch ($l0)' + end +catch ($l0) +end +``` + +Like branches, `delegate` can only target outer blocks, and effectively +rethrows the exception in that block. Consequently, delegating to a specific +`catch` or `catch_all` handler requires targeting the respective label from +within the associated `try` block. Delegating to a label from within a `catch` +block does delegate the exception to the next enclosing handler -- analogous to +performing a `throw` within a `catch` block, that handler is no longer active +at that point. Here is another example: + +``` +try $l0 + try $l1 + catch ($l1) + try + call $foo + delegate $l1 ;; delegates to 'catch ($l0)' + catch_all + ... + end +catch ($l0) +``` + +Here the `delegate` is targeting `catch ($l1)`, which exists before the +`delegate`. So in case an exception occurs, it propagates out and ends up +targetting `catch ($l0)`, if the catch has a matching tag. If not, it will +propagate further out. Even if the `catch_all` is below the `delegate`, +`delegate` targets catches of a `try` as a whole and does not target an +individual `catch`/`catch_all`, so it doesn't apply. + +If `delegate` targets the implicit function body block, then in effect it +delegates the exception to the caller of the current function. For example: +``` +(func $test + try + try + call $foo + delegate 1 ;; delegates to the caller + catch + ... + catch_all + ... + end +) +``` +In case `foo` throws, `delegate 1` here delegates the exception handling to the +caller, i.e., the exception escapes the current function. If the immediate is +greater than or equal to the number of block nesting including the implicit +function-level block, it is a validation failure. In this example, any number +equal to or greater than 2 is not allowed. + +The below is an example that includes all the cases explained. The numbers +within `()` after `delegate`s are the label operands in immediate values. +``` +(func $test + try $lA + block $lB + try $lC + try + delegate $lC (0) ;; delegates to 'catch ($lC)' + try + delegate $lB (1) ;; $lB is a block, so delegates to 'catch ($lA)' + try + delegate $lA (2) ;; delegates to 'catch ($lA)' + try + delegate 3 ;; propagates to the caller + try + delegate 4 ;; validation failure + catch ($lC) + try + delegate $lC (0) ;; 'catch ($lC)' is above this instruction, + ;; so delegates to 'catch ($lA)' + try + delegate $lB (1) ;; $lB is a block, so delegates to 'catch ($lA)' + try + delegate $lA (2) ;; delegates to 'catch ($lA)' + try + delegate 3 ;; propagates to the caller + try + delegate 4 ;; validation failure + end ;; try $lC + end ;; block $lB + catch ($lA) + end ;; try $lA +) +``` + +### JS API + +#### Traps + +The `catch`/`catch_all` instruction catches exceptions generated by the `throw` +instruction, but does not catch traps. The rationale for this is that in general +traps are not locally recoverable and are not needed to be handled in local +scopes like try-catch. + +The `catch` instruction catches foreign exceptions generated from calls to +function imports as well, including JavaScript exceptions, with a few +exceptions: +1. In order to be consistent before and after a trap reaches a JavaScript frame, + the `catch` instruction does not catch exceptions generated from traps. +1. The `catch` instruction does not catch JavaScript exceptions generated from + stack overflow and out of memory. + +Filtering these exceptions should be based on a predicate that is not observable +by JavaScript. Traps currently generate instances of +[`WebAssembly.RuntimeError`](https://webassembly.github.io/reference-types/js-api/#exceptiondef-runtimeerror), +but this detail is not used to decide type. Implementations are supposed to +specially mark non-catchable exceptions. +([`instanceof`](https://tc39.es/ecma262/#sec-instanceofoperator) predicate can +be intercepted in JS, and types of exceptions generated from stack overflow and +out of memory are implementation-defined.) + +#### API additions + +The following additional classes are added to the JS API in order to allow +JavaScript to interact with WebAssembly exceptions: + + * `WebAssembly.Tag` + * `WebAssembly.Exception` + +The `WebAssembly.Tag` class represents a typed tag defined in the tag section +and exported from a WebAssembly module. It allows querying the type of a tag +following the [JS type reflection +proposal](https://github.com/WebAssembly/js-types/blob/master/proposals/js-types/Overview.md). +Constructing an instance of `Tag` creates a fresh tag, and the new tag can be +passed to a WebAssembly module as a tag import. + +In the future, `WebAssembly.Tag` may be used for other proposals that require a +typed tag and its constructor may be extended to accept other types and/or a tag +attribute to differentiate them from tags used for exceptions. + +The `WebAssembly.Exception` class represents an exception thrown from +WebAssembly, or an exception that is constructed in JavaScript and is to be +thrown to a WebAssembly exception handler. The `Exception` constructor accepts a +`Tag` argument and a sequence of arguments for the exception's data fields. The +`Tag` argument determines the exception tag to use. The data field arguments +must match the types specified by the `Tag`'s type. The `is` method can be used +to query if the `Exception` matches a given tag. The `getArg` method allows +access to the data fields of a `Exception` if a matching tag is given. This last +check ensures that without access to a WebAssembly module's exported exception +tag, the associated data fields cannot be read. + +The `Exception` constructor can take an optional `ExceptionOptions` argument, +which can optionally contain `traceStack` entry. When `traceStack` is +`true`, JavaScript VMs are permitted to attach a stack trace string to +`Exception.stack` field, as in JavaScript's `Error` class. `traceStack` +serves as a request to the WebAssembly engine to attach a stack trace; it +is not necessary to honour if `true`, but `trace` may not be populated if +`traceStack` is `false`. While `Exception` is not a subclass of JavaScript's +`Error` and it can be used to represent normal control flow constructs, +`traceStack` field can be set when we use it to represent errors. The +format of stack trace strings conform to the [WebAssembly stack trace +conventions](https://webassembly.github.io/spec/web-api/index.html#conventions). +When `ExceptionOption` is not provided or it does not contain `traceStack` +entry, `traceStack` is considered `false` by default. + +To preserve stack trace info when crossing the JS to Wasm boundary, exceptions +can internally contain a stack trace, which is propagated when caught by `catch` +and rethrown by `rethrow`. + +More formally, the added interfaces look like the following: + +```WebIDL +dictionary TagType { + required sequence parameters; +}; + +[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)] +interface Tag { + constructor(TagType type); + TagType type(); +}; + +dictionary ExceptionOptions { + boolean traceStack = false; +}; + +[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)] +interface Exception { + constructor(Tag tag, sequence payload, optional ExceptionOptions options); + any getArg(Tag tag, unsigned long index); + boolean is(Tag tag); + readonly attribute (DOMString or undefined) stack; +}; +``` + +`TagType` corresponds to a `FunctionType` in [the type reflection +proposal](https://github.com/WebAssembly/js-types/blob/main/proposals/js-types/Overview.md), +without a `results` property). `TagType` could be extended in the future for +other proposals that require a richer type specification. + +## Changes to the text format + +This section describes change in the [instruction syntax +document](https://github.com/WebAssembly/spec/blob/master/document/core/text/instructions.rst). + +### New instructions + +The following rules are added to *instructions*: + +``` + try blocktype instruction* (catch tag_index instruction*)* (catch_all instruction*)? end | + try blocktype instruction* delegate label | + throw tag_index argument* | + rethrow label | +``` + +Like the `block`, `loop`, and `if` instructions, the `try` instruction is +*structured* control flow instruction, and can be labeled. This allows branch +instructions to exit try blocks. + +The `tag_index` of the `throw` and `catch` instructions denotes the exception +tag to use when creating/extract from an exception. See [tag index +space](#tag-index-space) for further clarification of exception tags. + +## Changes to Modules document + +This section describes change in the [Modules +document](https://github.com/WebAssembly/design/blob/master/Modules.md). + +### Tag index space + +The `tag index space` indexes all imported and internally-defined exception +tags, assigning monotonically-increasing indices based on the order defined in +the import and tag sections. Thus, the index space starts at zero with imported +tags, followed by internally-defined tags in the [tag section](#tag-section). + +For tag indices that are not imported/exported, the corresponding exception tag +is guaranteed to be unique over all loaded modules. Exceptions that are imported +or exported alias the respective exceptions defined elsewhere, and use the same +tag. + +## Changes to the binary model + +This section describes changes in the [binary encoding design +document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md). + +#### Other Types + +##### tag_type + +We reserve a bit to denote the exception attribute: + +| Name | Value | +|-----------|-------| +| Exception | 0 | + +Each tag type has the fields: + +| Field | Type | Description | +|-------|------|-------------| +| `attribute` | `uint8` | The attribute of a tag. | +| `type` | `varuint32` | The type index for its corresponding type signature | + +##### external_kind + +A single-byte unsigned integer indicating the kind of definition being imported +or defined: + +* `4` indicating a `Tag` +[import](https://github.com/WebAssembly/design/blob/main/BinaryEncoding.md#import-section) or +[definition](#tag-section) + +### Module structure + +#### High-level structure + +A new `tag` section is introduced. + +##### Tag section + +The `tag` section comes after the [memory +section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#memory-section) +and before the [global +section](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#global-section). +So the list of all sections will be: + +| Section Name | Code | Description | +| ------------ | ---- | ----------- | +| Type | `1` | Function signature declarations | +| Import | `2` | Import declarations | +| Function | `3` | Function declarations | +| Table | `4` | Indirect function table and other tables | +| Memory | `5` | Memory attributes | +| Tag | `13` | Tag declarations | +| Global | `6` | Global declarations | +| Export | `7` | Exports | +| Start | `8` | Start function declaration | +| Element | `9` | Elements section | +| Data count | `12` | Data count section | +| Code | `10` | Function bodies (code) | +| Data | `11` | Data segments | + +The tag section declares a list of tag types as follows: + +| Field | Type | Description | +|-------|------|-------------| +| count | `varuint32` | count of the number of tags to follow | +| type | `tag_type*` | The definitions of the tag types | + +##### Import section + +The import section is extended to include tag definitions by extending an +`import_entry` as follows: + +If the `kind` is `Tag`: + +| Field | Type | Description | +|-------|------|-------------| +| `type` | `tag_type` | the tag being imported | + +##### Export section + +The export section is extended to reference tag types by extending an +`export_entry` as follows: + +If the `kind` is `Tag`: + +| Field | Type | Description | +|-------|------|-------------| +| `index` | `varuint32` | the index into the corresponding tag index space | + +##### Name section + +The set of known values for `name_type` of a name section is extended as +follows: + +| Name Type | Code | Description | +| --------- | ---- | ----------- | +| [Function](#function-names) | `1` | Assigns names to functions | +| [Local](#local-names) | `2` | Assigns names to locals in functions | +| [Tag](#tag-names) | `11` | Assigns names to tags | + +###### Tag names + +The tag names subsection is a `name_map` which assigns names to a subset of +the tag indices (Used for both imports and module-defined). + +### Control flow operators + +The control flow operators are extended to define try blocks, catch blocks, +throws, and rethrows as follows: + +| Name | Opcode | Immediates | Description | +| ---- | ---- | ---- | ---- | +| `try` | `0x06` | sig : `blocktype` | begins a block which can handle thrown exceptions | +| `catch` | `0x07` | index : `varint32` | begins the catch block of the try block | +| `catch_all` | `0x19` | | begins the catch_all block of the try block | +| `delegate` | `0x18` | relative_depth : `varuint32` | begins the delegate block of the try block | +| `throw` | `0x08` | index : `varint32` | Creates an exception defined by the tag and then throws it | +| `rethrow` | `0x09` | relative_depth : `varuint32` | Pops the `exnref` on top of the stack and throws it | + +The *sig* fields of `block`, `if`, and `try` operators are block signatures +which describe their use of the operand stack. diff --git a/proposals/exception-handling/old/Exceptions-v1.md b/proposals/exception-handling/pre-legacy/Exceptions-v1.md similarity index 98% rename from proposals/exception-handling/old/Exceptions-v1.md rename to proposals/exception-handling/pre-legacy/Exceptions-v1.md index a5ca40df86..c48fa64ed5 100644 --- a/proposals/exception-handling/old/Exceptions-v1.md +++ b/proposals/exception-handling/pre-legacy/Exceptions-v1.md @@ -1,10 +1,8 @@ -THIS DOCUMENT IS OBSOLETE! +This is the first version of the exception handling proposal, active from 2017 +to 2018 and superseded by [V2 proposal](Exceptions-v2.md) in [Oct 2018 CG +meeting](https://github.com/WebAssembly/meetings/blob/main/main/2018/TPAC.md#exception-handling-ben-titzer). -Please see The new [Level 1 MVP Proposal] instead. - -[Level 1 MVP Proposal]: Exceptions-v2-Level-1.md - -The original proposal is preserved here for reference. +--- # Exception handling diff --git a/proposals/exception-handling/old/Exceptions-v2-Level-1+N.md b/proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1+N.md similarity index 97% rename from proposals/exception-handling/old/Exceptions-v2-Level-1+N.md rename to proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1+N.md index 7372ef029a..08a1a5e0e3 100644 --- a/proposals/exception-handling/old/Exceptions-v2-Level-1+N.md +++ b/proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1+N.md @@ -1,3 +1,8 @@ +This was written along with [Level 1 Proposal](Exceptions-v2-Level-1.md) in the +beginning of 2018 for collecting ideas for future work. + +--- + # Exception Handling Level 1+N Extensions While the Level 1 Proposal establishes the minimum viable product (MVP) for diff --git a/proposals/exception-handling/old/Exceptions-v2-Level-1.md b/proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1.md similarity index 97% rename from proposals/exception-handling/old/Exceptions-v2-Level-1.md rename to proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1.md index 8c9ce8eda0..32400e9a91 100644 --- a/proposals/exception-handling/old/Exceptions-v2-Level-1.md +++ b/proposals/exception-handling/pre-legacy/Exceptions-v2-Level-1.md @@ -1,3 +1,11 @@ +This is written as an alternative to [V1 Proposal](Exceptions-v1.md) in the +beginning of 2018 as the need for the first-class exception reference type was +suggested. This was later slightly modified and became the basis of [V2 +Proposal](Exceptions-v2.md), which replaced the V1 Proposal in [Oct 2018 CG +meeting](https://github.com/WebAssembly/meetings/blob/main/main/2018/TPAC.md#exception-handling-ben-titzer). + +--- + # Level 1 exception handling Level 1 of exception handling is the MVP (minimal viable proposal) for @@ -10,10 +18,6 @@ levels, and either: 2. Allow performance improvements in the VM. 3. Introduce additional new functionality not available in Level 1. -This document supersedes the original [Exceptions Proposal]. - -[Exceptions Proposal]: Exceptions-v1.md - ## Overview Exception handling allows code to break control flow when an exception is diff --git a/proposals/exception-handling/old/Exceptions-v2.md b/proposals/exception-handling/pre-legacy/Exceptions-v2.md similarity index 96% rename from proposals/exception-handling/old/Exceptions-v2.md rename to proposals/exception-handling/pre-legacy/Exceptions-v2.md index 233f28529e..4ab9eb5f66 100644 --- a/proposals/exception-handling/old/Exceptions-v2.md +++ b/proposals/exception-handling/pre-legacy/Exceptions-v2.md @@ -1,11 +1,16 @@ -# Exception handling +This V2 proposal was developed from [Level 1 Proposal](Exceptions-v2-Level-1.md) +and superseded [V1 proposal](Exceptions-v1.md). We decided to adopt this +proposal in [Oct 2018 CG +meeting](https://github.com/WebAssembly/meetings/blob/main/main/2018/TPAC.md#exception-handling-ben-titzer), +recognizing the need for a first-class exception type, based on the reasoning +that it is more expressive and also more extendible to other kinds of events. + +This proposal was active from Oct 2018 to Sep 2020 and superseded by [V3 +proposal](https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md). -There were two alternative proposals ([1st](Exceptions-v1.md) and -[2nd](Exceptions-v2-Level-1.md)) for the design of exception handling and we -[decided](https://github.com/WebAssembly/meetings/blob/master/2018/TPAC.md#exception-handling-ben-titzer) -on the second proposal, which uses first-class exception types, mainly based on -the reasoning that it is more expressive and also more extendible to other kinds -of events. +--- + +# Exception handling This proposal requires the following proposals as prerequisites. diff --git a/proposals/function-references/Overview.md b/proposals/function-references/Overview.md index da2b27ab58..0a58dd2c19 100644 --- a/proposals/function-references/Overview.md +++ b/proposals/function-references/Overview.md @@ -46,6 +46,7 @@ Typed references have no canonical default value, because they cannot be null. T 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))) +(elem declare funcref (ref.func $inc)) (func $hof (param $f (ref $i32-i32)) (result i32) (i32.add (i32.const 10) (call_ref $i32-i32 (i32.const 42) (local.get $f))) diff --git a/proposals/gc/Overview.md b/proposals/gc/Overview.md index 210c4e53be..c4982342b8 100644 --- a/proposals/gc/Overview.md +++ b/proposals/gc/Overview.md @@ -437,8 +437,8 @@ For now, we assume that all array types have a ([flexible](#flexible-aggregates) 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)) + (array.set $vector (local.get $v) (i32.const 1) + (array.get $vector (local.get $v) (i32.const 2)) ) ) ``` @@ -717,7 +717,7 @@ Another alternative would be a three-point mutability lattice with readonly as a 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". +To allow producers to work around the inevitable limitations of the type system, down casts have to be 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. @@ -729,7 +729,7 @@ This instruction checks whether the runtime type stored in `` is a runt 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` +In its plain form, a runtime type is obtained using the instruction `rtt.canon` ``` (rtt.canon ) ``` @@ -776,7 +776,7 @@ There are a number of reasons to make RTTs explicit: * 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). +* 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 have to be paid for every single use of type parameters (short of non-trivial cross-procedural dataflow analysis in the engine). ## Future Extensions diff --git a/proposals/multi-value/Overview.md b/proposals/multi-value/Overview.md index 42952f0e40..c32af77101 100644 --- a/proposals/multi-value/Overview.md +++ b/proposals/multi-value/Overview.md @@ -9,7 +9,7 @@ - instructions: `value* -> value?` - blocks: `[] -> value?` -* In a stack machine, these asymmetries are artifical restrictions +* In a stack machine, these asymmetries are artificial restrictions - were imposed to simplify the initial WebAssembly release (multiple results deferred to post-MVP) - can easily be lifted by generalising to value* -> value* @@ -32,7 +32,7 @@ - loop labels can have arguments - can represent phis on backward edges - enable future `pick` operator to cross block boundary - - macro definability of instructons with inputs + - macro definability of instructions with inputs * `i32.select3` = `dup if ... else ... end` diff --git a/proposals/reference-types/Overview.md b/proposals/reference-types/Overview.md index 670658f2b7..5a183b64eb 100644 --- a/proposals/reference-types/Overview.md +++ b/proposals/reference-types/Overview.md @@ -95,8 +95,8 @@ New/extended instructions: * The new instruction `table.fill` fills a range in a table with a value. - `table.fill $x : [i32 t i32] -> []` - iff `$x : table t` - - the first operand is the start index of the range, the third operand its length (analoguous to `memory.fill`) - - traps when range+length > size of the table, but only after filling range up to size (analoguous to `memory.fill`) + - the first operand is the start index of the range, the third operand its length (analogous to `memory.fill`) + - traps when range+length > size of the table, but only after filling range up to size (analogous to `memory.fill`) * The `table.init` instruction takes an additional table index as immediate. - `table.init $x $y : [i32 i32 i32] -> []` diff --git a/proposals/simd/SIMD.md b/proposals/simd/SIMD.md index 9f2c802a4a..8e1d2aaaf1 100644 --- a/proposals/simd/SIMD.md +++ b/proposals/simd/SIMD.md @@ -18,7 +18,7 @@ packed data in one instruction. These are commonly used to improve performance for multimedia applications. The set of SIMD instructions in hardware is large, and varies across different versions of hardware. This proposal is comprised of a portable subset of operations that in most cases map to commonly used -instructions in mordern hardware. +instructions in modern hardware. # Types @@ -136,14 +136,14 @@ Accessing WebAssembly module imports or exports containing SIMD Type from JavaSc ### Module Function Imports -Calling an imported function from JavaScript when the function arguments or result is of type v128 will cause the host function to immidiately throw a [`TypeError`](https://tc39.github.io/ecma262/#sec-native-error-types-used-in-this-standard-typeerror). +Calling an imported function from JavaScript when the function arguments or result is of type v128 will cause the host function to immediately throw a [`TypeError`](https://tc39.github.io/ecma262/#sec-native-error-types-used-in-this-standard-typeerror). ### Exported Function Exotic Objects -Invoking the [[Call]] method of an Exported Function Exotic Object when the function type of its [[Closure]] has an argument or result of type v128 will cause the host function to immidiately throw a [`TypeError`](https://tc39.github.io/ecma262/#sec-native-error-types-used-in-this-standard-typeerror). +Invoking the [[Call]] method of an Exported Function Exotic Object when the function type of its [[Closure]] has an argument or result of type v128 will cause the host function to immediately throw a [`TypeError`](https://tc39.github.io/ecma262/#sec-native-error-types-used-in-this-standard-typeerror). -## WebAssembly Module Instatiation +## WebAssembly Module Instantiation Instantiating a WebAssembly Module from a Module moduleObject will throw a LinkError exception, when the global's valtype is v128 and the imported objects type is not WebAssembly.Global. diff --git a/test/core/annotations.wast b/test/core/annotations.wast new file mode 100644 index 0000000000..912d10f38a --- /dev/null +++ b/test/core/annotations.wast @@ -0,0 +1,207 @@ +(module + (@a) + (@0) + (@aas-3!@$d-@#4) + (@@) (@$) (@+) (@0) (@.) (@!$@#$23414@#$) + (@"a") + (@" @ asd\2a 045 \" fdaf \t \u{45}") + + (@a x y z) + (@a x-y $yz "aa" -2 0.3 0x3) + (@a x-y$yz"aa"-2) + (@a block func module i32.add) + (@a 0x 8q 0xfa #4g0-.@f#^&@#$*0sf -- @#) + (@a , ; ] [ }} }x{ ({) ,{{};}] ;) + (@a (bla) () (5-g) ("aa" a) ($x) (bla bla) (x (y)) ")" "(" x")"y) + (@a @ @x (@x) (@x y) (@) (@ x) (@(@(@(@))))) + (@a (;bla;) (; ) ;) + ;; bla) + ;; bla (@x + ) +) + +(assert_malformed (module quote "(@a \00)") "illegal character") +(assert_malformed (module quote "(@a \01)") "illegal character") +(assert_malformed (module quote "(@a \02)") "illegal character") +(assert_malformed (module quote "(@a \03)") "illegal character") +(assert_malformed (module quote "(@a \04)") "illegal character") +(assert_malformed (module quote "(@a \05)") "illegal character") +(assert_malformed (module quote "(@a \06)") "illegal character") +(assert_malformed (module quote "(@a \07)") "illegal character") +(assert_malformed (module quote "(@a \08)") "illegal character") +(module quote "(@a \09)") ;; \t +(module quote "(@a \0a)") ;; \n +(assert_malformed (module quote "(@a \0b)") "illegal character") +(assert_malformed (module quote "(@a \0c)") "illegal character") +(module quote "(@a \0d)") ;; \r +(assert_malformed (module quote "(@a \0e)") "illegal character") +(assert_malformed (module quote "(@a \0f)") "illegal character") +(assert_malformed (module quote "(@a \10)") "illegal character") +(assert_malformed (module quote "(@a \11)") "illegal character") +(assert_malformed (module quote "(@a \12)") "illegal character") +(assert_malformed (module quote "(@a \13)") "illegal character") +(assert_malformed (module quote "(@a \14)") "illegal character") +(assert_malformed (module quote "(@a \15)") "illegal character") +(assert_malformed (module quote "(@a \16)") "illegal character") +(assert_malformed (module quote "(@a \17)") "illegal character") +(assert_malformed (module quote "(@a \18)") "illegal character") +(assert_malformed (module quote "(@a \19)") "illegal character") +(assert_malformed (module quote "(@a \1a)") "illegal character") +(assert_malformed (module quote "(@a \1b)") "illegal character") +(assert_malformed (module quote "(@a \1c)") "illegal character") +(assert_malformed (module quote "(@a \1d)") "illegal character") +(assert_malformed (module quote "(@a \1e)") "illegal character") +(assert_malformed (module quote "(@a \1f)") "illegal character") +(module quote "(@a \20)") ;; space +(assert_malformed (module quote "(@a \7f)") "illegal character") +(assert_malformed (module quote "(@a \80)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \81)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \90)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \a0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \b0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \c0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \d0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \e0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \f0)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a \ff)") "malformed UTF-8 encoding") +(assert_malformed (module quote "(@a Heiße Würstchen)") "illegal character") +(assert_malformed (module quote "(@a )") "illegal character") + +(assert_malformed (module quote "( @a)") "unknown operator") + +(assert_malformed (module quote "(@)") "empty annotation id") +(assert_malformed (module quote "(@ )") "empty annotation id") +(assert_malformed (module quote "(@ x)") "empty annotation id") +(assert_malformed (module quote "(@(@a)x)") "empty annotation id") +(assert_malformed (module quote "(@\"\")") "empty annotation id") +(assert_malformed (module quote "(@ \"a\")") "empty annotation id") +(assert_malformed (module quote "(@\"\n\")") "empty annotation id") +(assert_malformed (module quote "(@\"\\ef\")") "malformed UTF-8") + +(assert_malformed (module quote "(@x ") "unclosed annotation") +(assert_malformed (module quote "(@x ()") "unclosed annotation") +(assert_malformed (module quote "(@x (y (z))") "unclosed annotation") +(assert_malformed (module quote "(@x (@y )") "unclosed annotation") + +(assert_malformed (module quote "(@x))") "unexpected token") +(assert_malformed (module quote "(@x ()))") "unexpected token") +(assert_malformed (module quote "(@x (y (z))))") "unexpected token") +(assert_malformed (module quote "(@x (@y )))") "unexpected token") + +(assert_malformed (module quote "(@x \"") "unclosed string") +(assert_malformed (module quote "(@x \")") "unclosed string") + +(assert_malformed (module quote "((@a)@b)") "unknown operator") +(assert_malformed (module quote "(func $(@a))") "empty identifier") +(assert_malformed (module quote "(func $(@a)f)") "empty identifier") + +((@a) module (@a) $m (@a) (@a) + ((@a) import (@a) "spectest" (@a) "global_i32" (@a) + ((@a) global (@a) $g (@a) i32 (@a)) (@a) + ) (@a) + ((@a) import (@a) "spectest" (@a) "table" (@a) + ((@a) table (@a) $t (@a) 10 (@a) 20 (@a) funcref (@a)) (@a) + ) (@a) + ((@a) import (@a) "spectest" (@a) "memory" (@a) + ((@a) memory (@a) $m (@a) 1 (@a) 2 (@a)) (@a) + ) (@a) + ((@a) import (@a) "spectest" (@a) "print_i32_f32" (@a) + ((@a) func (@a) $f (@a) + ((@a) param (@a) i32 (@a) f32 (@a)) (@a) + ((@a) result (@a)) (@a) + ) (@a) + ) (@a) + + ((@a) export (@a) "g" (@a) + ((@a) global (@a) $g (@a)) (@a) + ) (@a) + ((@a) export (@a) "t" (@a) + ((@a) table (@a) $t (@a)) (@a) + ) (@a) + ((@a) export (@a) "m" (@a) + ((@a) memory (@a) $m (@a)) (@a) + ) (@a) + ((@a) export (@a) "f" (@a) + ((@a) func (@a) $f (@a)) (@a) + ) (@a) +) + +((@a) module (@a) $m1 (@a) (@a) + ((@a) global (@a) $g (@a) + ((@a) export (@a) "g" (@a)) (@a) + ((@a) import (@a) "spectest" (@a) "global_i32" (@a)) (@a) + i32 (@a) + ) (@a) + ((@a) table (@a) $t (@a) + ((@a) export (@a) "t" (@a)) (@a) + ((@a) import (@a) "spectest" (@a) "table" (@a)) (@a) + 10 (@a) 20 (@a) + funcref (@a) + ) (@a) + ((@a) memory (@a) $m (@a) + ((@a) export (@a) "m" (@a)) (@a) + ((@a) import (@a) "spectest" (@a) "memory" (@a)) (@a) + 1 (@a) 2 (@a) + ) (@a) + ((@a) func (@a) $f (@a) + ((@a) export (@a) "f" (@a)) (@a) + ((@a) import (@a) "spectest" (@a) "print_i32_f32" (@a)) (@a) + ((@a) param (@a) i32 (@a) f32 (@a)) (@a) + ((@a) result (@a)) (@a) + ) (@a) +) + +((@a) module (@a) $m2 (@a) (@a) + ((@a) type (@a) $T (@a) + ((@a) func (@a) + ((@a) param (@a) i32 (@a) i64 (@a)) (@a) + ((@a) param (@a) $x (@a) i32 (@a)) (@a) + ((@a) result (@a) i32 (@a)) (@a) + ) (@a) + ) (@a) + + ((@a) global (@a) $g (@a) + ((@a) export (@a) "g" (@a)) (@a) + i32 (@a) + ((@a) i32.const (@a) 42 (@a)) (@a) + ) (@a) + ((@a) table (@a) $t (@a) + ((@a) export (@a) "t" (@a)) (@a) + 10 (@a) 20 (@a) + funcref (@a) + ) (@a) + ((@a) memory (@a) $m (@a) + ((@a) export (@a) "m" (@a)) (@a) + 1 (@a) 2 (@a) + ) (@a) + ((@a) func (@a) $f (@a) + ((@a) export (@a) "f" (@a)) (@a) + ((@a) param (@a) i32 (@a) i64 (@a)) (@a) + ((@a) param (@a) $x (@a) i32 (@a)) (@a) + ((@a) result (@a) i32 (@a)) (@a) + ((@a) local (@a) i32 (@a) i32 (@a)) (@a) + ((@a) local (@a) $y (@a) i32 (@a)) (@a) + ((@a) block (@a) + ((@a) result (@a) i32 (@a)) (@a) + ((@a) i32.add (@a) + ((@a) local.get (@a) $x (@a)) (@a) + ((@a) local.get (@a) 0 (@a)) (@a) + ) + ) + ) (@a) + + ((@a) elem (@a) + ((@a) offset (@a) ((@a) i32.const (@a) 0 (@a)) (@a)) (@a) + $f (@a) $f (@a) (@a) $f (@a) + ) (@a) + ((@a) data (@a) + ((@a) offset (@a) ((@a) i32.const (@a) 0 (@a)) (@a)) (@a) + "bla" (@a) "\43" (@a) (@a) "" (@a) + ) (@a) + + (func $s) + ((@a) start (@a) $s (@a)) (@a) +) + +(module quote "(@a) (func)") +(module quote "(func) (@a)") diff --git a/test/core/call_ref.wast b/test/core/call_ref.wast index da480a7f23..aa9ac7b873 100644 --- a/test/core/call_ref.wast +++ b/test/core/call_ref.wast @@ -206,3 +206,13 @@ ) "type mismatch" ) + +(assert_invalid + (module + (type $t (func)) + (func $f (param $r funcref) + (call_ref $t (local.get $r)) + ) + ) + "type mismatch" +) diff --git a/test/core/float_exprs.wast b/test/core/float_exprs.wast index 1f88299212..e6d4f10e91 100644 --- a/test/core/float_exprs.wast +++ b/test/core/float_exprs.wast @@ -1986,7 +1986,7 @@ (assert_return (invoke "f64.xkcd_better_sqrt_5" (f64.const 13.0) (f64.const 4.0) (f64.const 0x1.921fb54442d18p+1) (f64.const 24.0)) (f64.const 0x1.1e3778509a5a3p+1)) ;; Compute the floating-point radix. -;; M. A. Malcom. Algorithms to reveal properties of floating-point arithmetic. +;; M. A. Malcolm. Algorithms to reveal properties of floating-point arithmetic. ;; Communications of the ACM, 15(11):949-951, November 1972. (module (func (export "f32.compute_radix") (param $0 f32) (param $1 f32) (result f32) diff --git a/test/core/id.wast b/test/core/id.wast new file mode 100644 index 0000000000..dcf151f22c --- /dev/null +++ b/test/core/id.wast @@ -0,0 +1,31 @@ +(module + (func $fg) (func (call $fg)) + (func $03) (func (call $03)) + (func $!?@#a$%^&*b-+_.:9'`|/\<=>~) (func (call $!?@#a$%^&*b-+_.:9'`|/\<=>~)) + (func $" random \t \n stuff ") (func (call $" random \t \n stuff ")) + (func $" ") (func (call $" ")) + + (func $fh) (func (call $"fh")) + (func $"fi") (func (call $fi)) + (func $!?@#a$%^&*-+_.:9'`|/\<=>~) (func (call $"!?@#a$%^&*-+_.:9'`|/\\<=>~")) + + (func $"\41B") (func (call $"AB") (call $"A\42") (call $"\41\42") (call $"\u{41}\u{42}")) + (func $"\t") (func (call $"\09") (call $"\u{09}")) + (func $"") (func (call $"\ef\98\9a\ef\92\a9") (call $"\u{f61a}\u{f4a9}")) + + (func + block $l1 (br $"l1") end $"l1" + block $007 (br $"007") end $"007" + block $!?@#a$%^&*-+_.:9'`|/\<=>~ end $"!?@#a$%^&*-+_.:9'`|/\\<=>~" + (i32.const 0) if $"\41B" (br $AB) else $"A\42" end $"\u{41}\u{42}" + (i32.const 0) if $"\t" else $"\09" end $"\u{09}" + (i32.const 0) if $" " else $"\ef\98\9a\ef\92\a9 " end $"\u{f61a}\u{f4a9} " + ) +) + +(assert_malformed (module quote "(func $)") "empty identifier") +(assert_malformed (module quote "(func $\"\")") "empty identifier") +(assert_malformed (module quote "(func $ \"a\")") "empty identifier") +(assert_malformed (module quote "(func $\"a\nb\")") "empty identifier") +(assert_malformed (module quote "(func $\"a\tb\")") "empty identifier") +(assert_malformed (module quote "(func $\"\\ef\")") "malformed UTF-8") diff --git a/test/core/return_call.wast b/test/core/return_call.wast index 2f91f4deac..b9e8f8f010 100644 --- a/test/core/return_call.wast +++ b/test/core/return_call.wast @@ -188,7 +188,15 @@ ) "type mismatch" ) - +(assert_invalid + (module + (func $f (result i32 i32) unreachable) + (func (result i32) + return_call $f + ) + ) + "type mismatch" +) ;; Unbound function diff --git a/test/core/return_call_indirect.wast b/test/core/return_call_indirect.wast index acf0a72e06..aa158be256 100644 --- a/test/core/return_call_indirect.wast +++ b/test/core/return_call_indirect.wast @@ -508,7 +508,17 @@ ) "type mismatch" ) - +(assert_invalid + (module + (type $ty (func (result i32 i32))) + (import "env" "table" (table $table 0 funcref)) + (func (param i32) (result i32) + local.get 0 + return_call_indirect $table (type $ty) + ) + ) + "type mismatch" +) ;; Unbound type diff --git a/test/core/return_call_ref.wast b/test/core/return_call_ref.wast index 2b495f46e9..5f5a7cba72 100644 --- a/test/core/return_call_ref.wast +++ b/test/core/return_call_ref.wast @@ -374,3 +374,24 @@ ) "type mismatch" ) + +(assert_invalid + (module + (type $t (func)) + (func $f (param $r funcref) + (return_call_ref $t (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (type $ty (func (result i32 i32))) + (func (param (ref $ty)) (result i32) + local.get 0 + return_call_ref $ty + ) + ) + "type mismatch" +) diff --git a/test/core/run.py b/test/core/run.py index 21036065d6..8437837c8a 100755 --- a/test/core/run.py +++ b/test/core/run.py @@ -14,6 +14,10 @@ inputDir = ownDir interpDir = os.path.join(os.path.dirname(os.path.dirname(ownDir)), 'interpreter') outputDir = os.path.join(inputDir, "_output") +opts = "" + +mainTestFiles = glob.glob(os.path.join(inputDir, "*.wast")) +otherTestFiles = glob.glob(os.path.join(inputDir, "[a-z]*/*.wast")) parser = argparse.ArgumentParser() parser.add_argument("--wasm", metavar="", default=os.path.join(interpDir, "wasm")) @@ -21,6 +25,7 @@ parser.add_argument("--generate-js-only", action='store_true') parser.add_argument("--failfast", action='store_true') parser.add_argument("--out", metavar="", default=outputDir) +parser.add_argument("--opts", metavar="", default=opts) parser.add_argument("file", nargs='*') arguments = parser.parse_args() sys.argv = sys.argv[:1] @@ -32,13 +37,14 @@ 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 +wasmExec = arguments.wasm +wasmCommand = wasmExec + " " + arguments.opts jsCommand = arguments.js generateJsOnly = arguments.generate_js_only outputDir = arguments.out inputFiles = arguments.file if arguments.file else all_test_files -if not os.path.exists(wasmCommand): +if not os.path.exists(wasmExec): sys.stderr.write("""\ Error: The executable '%s' does not exist. Provide the correct path with the '--wasm' flag. @@ -52,7 +58,10 @@ class RunTests(unittest.TestCase): def _runCommand(self, command, logPath, expectedExitCode = 0): with open(logPath, 'w+') as out: exitCode = subprocess.call(command, shell=True, stdout=out, stderr=subprocess.STDOUT) - self.assertEqual(expectedExitCode, exitCode, "failed with exit code %i (expected %i) for %s" % (exitCode, expectedExitCode, command)) + with open(logPath) as out: + log = out.read() + msg = "failed with exit code %i (expected %i)\nCommand:\n %s\nLog:\n%s" + self.assertEqual(expectedExitCode, exitCode, msg % (exitCode, expectedExitCode, command, log)) def _auxFile(self, path): if os.path.exists(path): @@ -65,7 +74,7 @@ def _compareFile(self, expectFile, actualFile): with open(actualFile) as actual: expectText = expect.read() actualText = actual.read() - self.assertEqual(expectText, actualText) + self.assertEqual(expectText, actualText) def _runTestFile(self, inputPath): dir, inputFile = os.path.split(inputPath) diff --git a/test/core/simd/simd_lane.wast b/test/core/simd/simd_lane.wast index 9b66f53b1a..3dbd0c9f13 100644 --- a/test/core/simd/simd_lane.wast +++ b/test/core/simd/simd_lane.wast @@ -602,23 +602,23 @@ "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "invalid lane length") (assert_malformed (module quote "(func (result v128) " - "(i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15.0) " + "(i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15.0 " "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "malformed lane index") (assert_malformed (module quote "(func (result v128) " - "(i8x16.shuffle 0.5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) " + "(i8x16.shuffle 0.5 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "malformed lane index") (assert_malformed (module quote "(func (result v128) " - "(i8x16.shuffle -inf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) " + "(i8x16.shuffle -inf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "malformed lane index") (assert_malformed (module quote "(func (result v128) " - "(i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 inf) " + "(i8x16.shuffle 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 inf " "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "malformed lane index") (assert_malformed (module quote "(func (result v128) " - "(i8x16.shuffle nan 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15) " + "(i8x16.shuffle nan 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " "(v128.const i8x16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0) " "(v128.const i8x16 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))") "malformed lane index") diff --git a/test/core/simd/simd_load.wast b/test/core/simd/simd_load.wast index 4b2edc160b..2fddd3019e 100644 --- a/test/core/simd/simd_load.wast +++ b/test/core/simd/simd_load.wast @@ -1,4 +1,4 @@ -;; v128.load operater with normal argument (e.g. (i8x16, i16x8 i32x4)) +;; v128.load operator with normal argument (e.g. (i8x16, i16x8 i32x4)) (module (memory 1) @@ -13,7 +13,7 @@ (assert_return (invoke "v128.load") (v128.const i32x4 0x03020100 0x07060504 0x0b0a0908 0x0f0e0d0c)) -;; v128.load operater as the argument of other SIMD instructions +;; v128.load operator as the argument of other SIMD instructions (module (memory 1) (data (i32.const 0) "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\00\01\02\03") @@ -185,4 +185,4 @@ (assert_invalid (module (memory 1) (func (drop (v128.load)))) "type mismatch" -) \ No newline at end of file +) diff --git a/test/core/simd/simd_store.wast b/test/core/simd/simd_store.wast index 50349c41bd..7bba053d8e 100644 --- a/test/core/simd/simd_store.wast +++ b/test/core/simd/simd_store.wast @@ -1,4 +1,4 @@ -;; v128.store operater with normal argument (e.g. (i8x16, i16x8, i32x4, f32x4)) +;; v128.store operator with normal argument (e.g. (i8x16, i16x8, i32x4, f32x4)) (module (memory 1) diff --git a/test/core/tag.wast b/test/core/tag.wast index 29fd404f96..e970c1f402 100644 --- a/test/core/tag.wast +++ b/test/core/tag.wast @@ -16,8 +16,47 @@ ) ;; NOTE(dhil): This test is invalid as our proposal allows non-empty -;; tag result types ;; (assert_invalid ;; (module (tag (result i32))) ;; "non-empty tag result type" ;; ) + + +;; Link-time typing + +(module + (rec + (type $t1 (func)) + (type $t2 (func)) + ) + (tag (export "tag") (type $t1)) +) + +(register "M") + +(module + (rec + (type $t1 (func)) + (type $t2 (func)) + ) + (tag (import "M" "tag") (type $t1)) +) + +(assert_unlinkable + (module + (rec + (type $t1 (func)) + (type $t2 (func)) + ) + (tag (import "M" "tag") (type $t2)) + ) + "incompatible import" +) + +(assert_unlinkable + (module + (type $t (func)) + (tag (import "M" "tag") (type $t)) + ) + "incompatible import" +) diff --git a/test/core/token.wast b/test/core/token.wast index 624bdca6d8..44e41f22bc 100644 --- a/test/core/token.wast +++ b/test/core/token.wast @@ -88,6 +88,12 @@ ) "unknown operator" ) +(assert_malformed + (module quote + "(func (block $l (i32.const 0) (br_table 0$\"l\")))" + ) + "unknown operator" +) (module (func (block $l (i32.const 0) (br_table $l 0))) @@ -98,6 +104,12 @@ ) "unknown label" ) +(assert_malformed + (module quote + "(func (block $l (i32.const 0) (br_table $\"l\"0)))" + ) + "unknown operator" +) (module (func (block $l (i32.const 0) (br_table $l $l))) @@ -108,6 +120,12 @@ ) "unknown label" ) +(assert_malformed + (module quote + "(func (block $l (i32.const 0) (br_table $\"l\"$l)))" + ) + "unknown operator" +) (module (func (block $l0 (i32.const 0) (br_table $l0))) diff --git a/test/core/try_table.wast b/test/core/try_table.wast index e64b6c189f..ed3b88cbc5 100644 --- a/test/core/try_table.wast +++ b/test/core/try_table.wast @@ -238,6 +238,10 @@ ) ) ) + + (func (export "try-with-param") + (i32.const 0) (try_table (param i32) (drop)) + ) ) (assert_return (invoke "simple-throw-catch" (i32.const 0)) (i32.const 23)) @@ -294,6 +298,8 @@ (assert_exception (invoke "return-call-in-try-catch")) (assert_exception (invoke "return-call-indirect-in-try-catch")) +(assert_return (invoke "try-with-param")) + (module (func $imported-throw (import "test" "throw")) (tag $e0) @@ -370,3 +376,81 @@ ) "type mismatch" ) + + +(module + (type $t (func)) + (func $dummy) + (elem declare func $dummy) + + (tag $e (param (ref $t))) + (func $throw (throw $e (ref.func $dummy))) + + (func (export "catch") (result (ref null $t)) + (block $l (result (ref null $t)) + (try_table (catch $e $l) (call $throw)) + (unreachable) + ) + ) + (func (export "catch_ref1") (result (ref null $t)) + (block $l (result (ref null $t) (ref exn)) + (try_table (catch_ref $e $l) (call $throw)) + (unreachable) + ) + (drop) + ) + (func (export "catch_ref2") (result (ref null $t)) + (block $l (result (ref null $t) (ref null exn)) + (try_table (catch_ref $e $l) (call $throw)) + (unreachable) + ) + (drop) + ) + (func (export "catch_all_ref1") + (block $l (result (ref exn)) + (try_table (catch_all_ref $l) (call $throw)) + (unreachable) + ) + (drop) + ) + (func (export "catch_all_ref2") + (block $l (result (ref null exn)) + (try_table (catch_all_ref $l) (call $throw)) + (unreachable) + ) + (drop) + ) +) + +(assert_return (invoke "catch") (ref.func)) +(assert_return (invoke "catch_ref1") (ref.func)) +(assert_return (invoke "catch_ref2") (ref.func)) +(assert_return (invoke "catch_all_ref1")) +(assert_return (invoke "catch_all_ref2")) + +(assert_invalid + (module + (type $t (func)) + (tag $e (param (ref null $t))) + (func (export "catch") (result (ref $t)) + (block $l (result (ref $t)) + (try_table (catch $e $l)) + (unreachable) + ) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $t (func)) + (tag $e (param (ref null $t))) + (func (export "catch_ref") (result (ref $t)) + (block $l (result (ref $t) (ref exn)) + (try_table (catch $e $l)) + (unreachable) + ) + ) + ) + "type mismatch" +) diff --git a/test/core/utf8-custom-section-id.wast b/test/core/utf8-custom-section-id.wast index ee5fd7ccb7..c3765d9da8 100644 --- a/test/core/utf8-custom-section-id.wast +++ b/test/core/utf8-custom-section-id.wast @@ -136,7 +136,7 @@ "malformed UTF-8 encoding" ) -;; byte after (first) 2-byte prefix not a contination byte +;; byte after (first) 2-byte prefix not a continuation byte (assert_malformed (module binary "\00asm" "\01\00\00\00" diff --git a/test/core/utf8-import-field.wast b/test/core/utf8-import-field.wast index 0d030dabb1..330ecf61c8 100644 --- a/test/core/utf8-import-field.wast +++ b/test/core/utf8-import-field.wast @@ -201,7 +201,7 @@ "malformed UTF-8 encoding" ) -;; byte after (first) 2-byte prefix not a contination byte +;; byte after (first) 2-byte prefix not a continuation byte (assert_malformed (module binary "\00asm" "\01\00\00\00" diff --git a/test/core/utf8-import-module.wast b/test/core/utf8-import-module.wast index 5a092da08a..8f509159d7 100644 --- a/test/core/utf8-import-module.wast +++ b/test/core/utf8-import-module.wast @@ -201,7 +201,7 @@ "malformed UTF-8 encoding" ) -;; byte after (first) 2-byte prefix not a contination byte +;; byte after (first) 2-byte prefix not a continuation byte (assert_malformed (module binary "\00asm" "\01\00\00\00" diff --git a/test/custom/custom/custom_annot.wast b/test/custom/custom/custom_annot.wast new file mode 100644 index 0000000000..011a5776fc --- /dev/null +++ b/test/custom/custom/custom_annot.wast @@ -0,0 +1,99 @@ +(module + (type $t (func)) + (@custom "my-section1" "contents-bytes1") + (@custom "my-section2" "more-contents-bytes0") + (@custom "my-section1" "contents-bytes2") + (@custom "my-section2" (before global) "more-contents-bytes1") + (@custom "my-section2" (after func) "more-contents-bytes2") + (@custom "my-section2" (after func) "more-contents-bytes3") + (@custom "my-section2" (before global) "more-contents-bytes4") + (func) + (@custom "my-section2" "more-contents-bytes5") + + (global $g i32 (i32.const 0)) + (@custom "my-section3") + (@custom "my-section4" "" "1" "" "2" "3" "") + (@custom "") +) + +(module quote "(@custom \"bla\")") +(module quote "(module (@custom \"bla\"))") + + +;; Malformed name + +(assert_malformed_custom + (module quote "(@custom)") + "@custom annotation: missing section name" +) + +(assert_malformed_custom + (module quote "(@custom 4)") + "@custom annotation: missing section name" +) + +(assert_malformed_custom + (module quote "(@custom bla)") + "@custom annotation: missing section name" +) + +(assert_malformed_custom + (module quote "(@custom \"\\df\")") + "@custom annotation: malformed UTF-8 encoding" +) + + +;; Malformed placement + +(assert_malformed_custom + (module quote "(@custom \"bla\" here)") + "@custom annotation: unexpected token" +) + +(assert_malformed_custom + (module quote "(@custom \"bla\" after)") + "@custom annotation: unexpected token" +) + +(assert_malformed_custom + (module quote "(@custom \"bla\" (after))") + "@custom annotation: malformed section kind" +) + +(assert_malformed_custom + (module quote "(@custom \"bla\" (type))") + "@custom annotation: malformed placement" +) + +(assert_malformed_custom + (module quote "(@custom \"bla\" (aft type))") + "@custom annotation: malformed placement" +) + +(assert_malformed_custom + (module quote "(@custom \"bla\" (before types))") + "@custom annotation: malformed section kind" +) + + +;; Misplaced + +(assert_malformed_custom + (module quote "(type (@custom \"bla\") $t (func))") + "misplaced @custom annotation" +) + +(assert_malformed_custom + (module quote "(func (@custom \"bla\"))") + "misplaced @custom annotation" +) + +(assert_malformed_custom + (module quote "(func (block (@custom \"bla\")))") + "misplaced @custom annotation" +) + +(assert_malformed_custom + (module quote "(func (nop (@custom \"bla\")))") + "misplaced @custom annotation" +) diff --git a/test/custom/metadata.code.branch_hint/branch_hint.wast b/test/custom/metadata.code.branch_hint/branch_hint.wast new file mode 100644 index 0000000000..f3c63b32b0 --- /dev/null +++ b/test/custom/metadata.code.branch_hint/branch_hint.wast @@ -0,0 +1,99 @@ +(module + (type (;0;) (func (param i32))) + (memory (;0;) 1 1) + (func $dummy) + (func $test1 (type 0) + (local i32) + local.get 1 + local.get 0 + i32.eq + (@metadata.code.branch_hint "\00" ) if + return + end + return + ) + (func $test2 (type 0) + (local i32) + local.get 1 + local.get 0 + i32.eq + (@metadata.code.branch_hint "\01" ) if + return + end + return + ) + (func (export "nested") (param i32 i32) (result i32) + (@metadata.code.branch_hint "\00") + (if (result i32) (local.get 0) + (then + (if (local.get 1) (then (call $dummy) (block) (nop))) + (if (local.get 1) (then) (else (call $dummy) (block) (nop))) + (@metadata.code.branch_hint "\01") + (if (result i32) (local.get 1) + (then (call $dummy) (i32.const 9)) + (else (call $dummy) (i32.const 10)) + ) + ) + (else + (if (local.get 1) (then (call $dummy) (block) (nop))) + (@metadata.code.branch_hint "\00") + (if (local.get 1) (then) (else (call $dummy) (block) (nop))) + (if (result i32) (local.get 1) + (then (call $dummy) (i32.const 10)) + (else (call $dummy) (i32.const 11)) + ) + ) + ) + ) +) + +(assert_malformed_custom + (module quote + "(func $test2 (type 0)" + " (local i32)" + " local.get 1" + " local.get 0" + " i32.eq" + " (@metadata.code.branch_hint \"\\01\" )" + " (@metadata.code.branch_hint \"\\01\" )" + " if" + " return" + " end" + " return" + ")" + ) + "@metadata.code.branch_hint annotation: duplicate annotation" +) +(assert_malformed_custom + (module quote + "(module" + " (@metadata.code.branch_hint \"\\01\" )" + " (type (;0;) (func (param i32)))" + " (memory (;0;) 1 1)" + " (func $test (type 0)" + " (local i32)" + " local.get 1" + " local.get 0" + " i32.eq" + " return" + " )" + ")" + ) + "@metadata.code.branch_hint annotation: not in a function" +) + +(assert_invalid_custom + (module + (type (;0;) (func (param i32))) + (memory (;0;) 1 1) + (func $test (type 0) + (local i32) + local.get 1 + local.get 0 + (@metadata.code.branch_hint "\01" ) + i32.eq + return + ) + ) + "@metadata.code.branch_hint annotation: invalid target" +) diff --git a/test/custom/name/name_annot.wast b/test/custom/name/name_annot.wast new file mode 100644 index 0000000000..bf34fd67a1 --- /dev/null +++ b/test/custom/name/name_annot.wast @@ -0,0 +1,38 @@ +;; Module names + +(module (@name "Modül")) + +(module $moduel (@name "Modül")) + +(assert_malformed_custom + (module quote "(module (@name \"M1\") (@name \"M2\"))") + "@name annotation: multiple module" +) + +(assert_malformed_custom + (module quote "(module (func) (@name \"M\"))") + "misplaced @name annotation" +) + +(assert_malformed_custom + (module quote "(module (start $f (@name \"M\")) (func $f))") + "misplaced @name annotation" +) + + +;; Function names + +(module + (type $t (func)) + (func (@name "λ") (type $t)) + (func $lambda (@name "λ") (type $t)) +) + + +;; Tag names + +(module + (type $t (func)) + (tag (@name "θ") (type $t)) + (tag $theta (@name "θ") (type $t)) +) diff --git a/test/harness/async_index.js b/test/harness/async_index.js index bd21c3a5a6..a0ed5f97db 100644 --- a/test/harness/async_index.js +++ b/test/harness/async_index.js @@ -176,6 +176,12 @@ function assert_invalid(bytes) { const assert_malformed = assert_invalid; +function assert_invalid_custom(bytes) { + module(bytes); +} + +const assert_malformed_custom = assert_invalid_custom; + function instance(bytes, imports, valid = true) { const test = valid ? "Test that WebAssembly instantiation succeeds" diff --git a/test/harness/sync_index.js b/test/harness/sync_index.js index 475d3f8698..2597032081 100644 --- a/test/harness/sync_index.js +++ b/test/harness/sync_index.js @@ -191,6 +191,18 @@ function assert_invalid(bytes) { const assert_malformed = assert_invalid; +function assert_invalid_custom(bytes) { + uniqueTest(() => { + try { + module(bytes, /* valid */ true); + } catch(e) { + throw new Error('failed on custom section error'); + } + }, "A wast module that should have an invalid or malformed custom section."); +} + +const assert_malformed_custom = assert_invalid_custom; + function instance(bytes, imports = registry, valid = true) { if (imports instanceof Result) { if (imports.isError()) diff --git a/test/js-api/exception/basic.tentative.any.js b/test/js-api/exception/basic.tentative.any.js index acf644f904..09f8479aa8 100644 --- a/test/js-api/exception/basic.tentative.any.js +++ b/test/js-api/exception/basic.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,worker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/wasm-module-builder.js function assert_throws_wasm(fn, message) { @@ -11,14 +11,13 @@ function assert_throws_wasm(fn, message) { } promise_test(async () => { - const kWasmAnyRef = 0x6f; - const kSig_v_r = makeSig([kWasmAnyRef], []); + const kSig_v_r = makeSig([kWasmExternRef], []); const builder = new WasmModuleBuilder(); - const tagIndex = builder.addTag(kSig_v_r); + const tagIndexExternref = builder.addTag(kSig_v_r); builder.addFunction("throw_param", kSig_v_r) .addBody([ kExprLocalGet, 0, - kExprThrow, tagIndex, + kExprThrow, tagIndexExternref, ]) .exportFunc(); const buffer = builder.toBuffer(); @@ -45,11 +44,11 @@ promise_test(async () => { promise_test(async () => { const builder = new WasmModuleBuilder(); - const tagIndex = builder.addTag(kSig_v_a); + const tagIndexAnyref = builder.addTag(kSig_v_a); builder.addFunction("throw_null", kSig_v_v) .addBody([ - kExprRefNull, kWasmAnyFunc, - kExprThrow, tagIndex, + kExprRefNull, kAnyFuncCode, + kExprThrow, tagIndexAnyref, ]) .exportFunc(); const buffer = builder.toBuffer(); @@ -59,11 +58,11 @@ promise_test(async () => { promise_test(async () => { const builder = new WasmModuleBuilder(); - const tagIndex = builder.addTag(kSig_v_i); + const tagIndexI32 = builder.addTag(kSig_v_i); builder.addFunction("throw_int", kSig_v_v) .addBody([ ...wasmI32Const(7), - kExprThrow, tagIndex, + kExprThrow, tagIndexI32, ]) .exportFunc(); const buffer = builder.toBuffer(); @@ -74,15 +73,21 @@ promise_test(async () => { promise_test(async () => { const builder = new WasmModuleBuilder(); const fnIndex = builder.addImport("module", "fn", kSig_v_v); - const tagIndex= builder.addTag(kSig_v_r); + const tagIndexExternref = builder.addTag(kSig_v_r); + builder.addFunction("catch_exception", kSig_r_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, fnIndex, - kExprCatch, tagIndex, + kExprBlock, kWasmVoid, + kExprBlock, kExternRefCode, + kExprTryTable, kWasmVoid, 1, + kCatchNoRef, tagIndexExternref, 0, + kExprCallFunction, fnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, kExprReturn, kExprEnd, - kExprRefNull, kWasmAnyRef, + kExprRefNull, kExternRefCode, ]) .exportFunc(); @@ -101,12 +106,17 @@ promise_test(async () => { const fnIndex = builder.addImport("module", "fn", kSig_v_v); builder.addFunction("catch_and_rethrow", kSig_r_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, fnIndex, - kExprCatchAll, - kExprRethrow, 0x00, + kExprBlock, kWasmVoid, + kExprBlock, kExnRefCode, + kExprTryTable, kWasmVoid, 1, + kCatchAllRef, 0, + kExprCallFunction, fnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, + kExprThrowRef, kExprEnd, - kExprRefNull, kWasmAnyRef, + kExprRefNull, kExternRefCode, ]) .exportFunc(); @@ -119,3 +129,54 @@ promise_test(async () => { }); assert_throws_exactly(error, () => instance.exports.catch_and_rethrow()); }, "Imported JS function throws, Wasm catches and rethrows"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + const tagI32 = new WebAssembly.Tag({ parameters: ["i32"] }); + const tagIndexI32 = builder.addImportedTag("module", "tagI32", kSig_v_i); + const exn = new WebAssembly.Exception(tagI32, [42]); + const kSig_ie_v = makeSig([], [kWasmI32, kExnRefCode]); + const sig_ie_v = builder.addType(kSig_ie_v); + + builder.addFunction("all_catch_clauses", kSig_i_v) + .addBody([ + kExprBlock, kWasmVoid, + kExprBlock, kExnRefCode, + kExprBlock, sig_ie_v, + kExprBlock, kWasmVoid, + kExprBlock, kWasmI32, + kExprTryTable, kWasmVoid, 4, + kCatchNoRef, tagIndexI32, 0, + kCatchAllNoRef, 1, + kCatchRef, tagIndexI32, 2, + kCatchAllRef, 3, + kExprCallFunction, fnIndex, + kExprEnd, + kExprBr, 4, + kExprEnd, + kExprReturn, + kExprEnd, + kExprBr, 2, + kExprEnd, + kExprDrop, + kExprDrop, + kExprBr, 1, + kExprEnd, + kExprDrop, + kExprEnd, + kExprI32Const, 0, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const fn = () => { + throw exn; + }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn, tagI32: tagI32 } + }); + const result = instance.exports.all_catch_clauses(); + assert_equals(result, 42); +}, "try-table uses all four kinds of catch clauses, one of which catches an exception"); diff --git a/test/js-api/exception/constructor.tentative.any.js b/test/js-api/exception/constructor.tentative.any.js index 7ad08e1883..a46d1816c3 100644 --- a/test/js-api/exception/constructor.tentative.any.js +++ b/test/js-api/exception/constructor.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js test(() => { diff --git a/test/js-api/exception/getArg.tentative.any.js b/test/js-api/exception/getArg.tentative.any.js index 4b72c61f47..6d56ac8c6a 100644 --- a/test/js-api/exception/getArg.tentative.any.js +++ b/test/js-api/exception/getArg.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/memory/assertions.js test(() => { diff --git a/test/js-api/exception/identity.tentative.any.js b/test/js-api/exception/identity.tentative.any.js index e431197d1a..802e7053ac 100644 --- a/test/js-api/exception/identity.tentative.any.js +++ b/test/js-api/exception/identity.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js // META: script=/wasm/jsapi/wasm-module-builder.js @@ -22,6 +22,9 @@ test(() => { let wasmTagExnSamePayload = null; let wasmTagExnDiffPayload = null; + const kSig_ie_v = makeSig([], [kWasmI32, kExnRefCode]); + const sig_ie_v = builder.addType(kSig_ie_v); + const imports = { module: { throwJSTagExn: function() { throw jsTagExn; }, @@ -31,55 +34,73 @@ test(() => { }; // Call a JS function that throws an exception using a JS-defined tag, catches - // it with a 'catch' instruction, and rethrows it. + // it with a 'catch_ref' instruction, and rethrows it. builder - .addFunction("catch_js_tag_rethrow", kSig_v_v) + .addFunction("catch_ref_js_tag_throw_ref", kSig_v_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, throwJSTagExnIndex, - kExprCatch, jsTagIndex, - kExprDrop, - kExprRethrow, 0x00, + kExprBlock, kWasmVoid, + kExprBlock, sig_ie_v, + kExprTryTable, kWasmVoid, 1, + kCatchRef, jsTagIndex, 0, + kExprCallFunction, throwJSTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, + kExprThrowRef, kExprEnd ]) .exportFunc(); // Call a JS function that throws an exception using a Wasm-defined tag, - // catches it with a 'catch' instruction, and rethrows it. + // catches it with a 'catch_ref' instruction, and rethrows it. builder - .addFunction("catch_wasm_tag_rethrow", kSig_v_v) + .addFunction("catch_ref_wasm_tag_throw_ref", kSig_v_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, throwWasmTagExnIndex, - kExprCatch, wasmTagIndex, - kExprDrop, - kExprRethrow, 0x00, + kExprBlock, kWasmVoid, + kExprBlock, sig_ie_v, + kExprTryTable, kWasmVoid, 1, + kCatchRef, wasmTagIndex, 0, + kExprCallFunction, throwWasmTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, + kExprThrowRef, kExprEnd ]) .exportFunc(); // Call a JS function that throws an exception using a JS-defined tag, catches - // it with a 'catch_all' instruction, and rethrows it. + // it with a 'catch_all_ref' instruction, and rethrows it. builder - .addFunction("catch_all_js_tag_rethrow", kSig_v_v) + .addFunction("catch_all_ref_js_tag_throw_ref", kSig_v_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, throwJSTagExnIndex, - kExprCatchAll, - kExprRethrow, 0x00, + kExprBlock, kWasmVoid, + kExprBlock, kExnRefCode, + kExprTryTable, kWasmVoid, 1, + kCatchAllRef, 0, + kExprCallFunction, throwJSTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, + kExprThrowRef, kExprEnd ]) .exportFunc(); // Call a JS function that throws an exception using a Wasm-defined tag, - // catches it with a 'catch_all' instruction, and rethrows it. + // catches it with a 'catch_all_ref' instruction, and rethrows it. builder - .addFunction("catch_all_wasm_tag_rethrow", kSig_v_v) + .addFunction("catch_all_ref_wasm_tag_throw_ref", kSig_v_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, throwWasmTagExnIndex, - kExprCatchAll, - kExprRethrow, 0x00, + kExprBlock, kWasmVoid, + kExprBlock, kExnRefCode, + kExprTryTable, kWasmVoid, 1, + kCatchAllRef, 0, + kExprCallFunction, throwWasmTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, + kExprThrowRef, kExprEnd ]) .exportFunc(); @@ -89,12 +110,17 @@ test(() => { builder .addFunction("catch_js_tag_return_payload", kSig_i_v) .addBody([ - kExprTry, kWasmI32, - kExprCallFunction, throwJSTagExnIndex, - kExprI32Const, 0x00, - kExprCatch, jsTagIndex, + kExprBlock, kWasmVoid, + kExprBlock, kWasmI32, + kExprTryTable, kWasmVoid, 1, + kCatchNoRef, jsTagIndex, 0, + kExprCallFunction, throwJSTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, kExprReturn, - kExprEnd + kExprEnd, + kExprI32Const, 0 ]) .exportFunc(); @@ -103,9 +129,14 @@ test(() => { builder .addFunction("catch_js_tag_throw_payload", kSig_v_v) .addBody([ - kExprTry, kWasmStmt, - kExprCallFunction, throwJSTagExnIndex, - kExprCatch, jsTagIndex, + kExprBlock, kWasmVoid, + kExprBlock, kWasmI32, + kExprTryTable, kWasmVoid, 1, + kCatchNoRef, jsTagIndex, 0, + kExprCallFunction, throwJSTagExnIndex, + kExprEnd, + kExprBr, 1, + kExprEnd, kExprThrow, jsTagIndex, kExprEnd ]) @@ -117,7 +148,7 @@ test(() => { // The exception object's identity should be preserved across 'rethrow's in // Wasm code. Do tests with a tag defined in JS. try { - result.instance.exports.catch_js_tag_rethrow(); + result.instance.exports.catch_ref_js_tag_throw_ref(); } catch (e) { assert_equals(e, jsTagExn); // Even if they have the same payload, they are different objects, so they @@ -126,7 +157,7 @@ test(() => { assert_not_equals(e, jsTagExnDiffPayload); } try { - result.instance.exports.catch_all_js_tag_rethrow(); + result.instance.exports.catch_all_ref_js_tag_throw_ref(); } catch (e) { assert_equals(e, jsTagExn); assert_not_equals(e, jsTagExnSamePayload); @@ -139,14 +170,14 @@ test(() => { wasmTagExnSamePayload = new WebAssembly.Exception(wasmTag, [42]); wasmTagExnDiffPayload = new WebAssembly.Exception(wasmTag, [53]); try { - result.instance.exports.catch_wasm_tag_rethrow(); + result.instance.exports.catch_ref_wasm_tag_throw_ref(); } catch (e) { assert_equals(e, wasmTagExn); assert_not_equals(e, wasmTagExnSamePayload); assert_not_equals(e, wasmTagExnDiffPayload); } try { - result.instance.exports.catch_all_wasm_tag_rethrow(); + result.instance.exports.catch_all_ref_wasm_tag_throw_ref(); } catch (e) { assert_equals(e, wasmTagExn); assert_not_equals(e, wasmTagExnSamePayload); diff --git a/test/js-api/exception/is.tentative.any.js b/test/js-api/exception/is.tentative.any.js index e28a88a3c5..840d00bf0d 100644 --- a/test/js-api/exception/is.tentative.any.js +++ b/test/js-api/exception/is.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/memory/assertions.js test(() => { diff --git a/test/js-api/exception/jsTag.tentative.any.js b/test/js-api/exception/jsTag.tentative.any.js new file mode 100644 index 0000000000..c5db553701 --- /dev/null +++ b/test/js-api/exception/jsTag.tentative.any.js @@ -0,0 +1,121 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + assert_throws_js(TypeError, () => new WebAssembly.Exception(WebAssembly.JSTag, [{}])) +}, "Creating a WebAssembly.Exception with JSTag explicitly is not allowed"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const jsTag = builder.addImportedTag("module", "JSTag", kSig_v_r); + + const throwRefFn = builder.addImport("module", "throw_ref", kSig_r_r); + const sig_r_v = builder.addType(kSig_r_v); + const kSig_re_v = makeSig([], [kExternRefCode, kExnRefCode]); + const sig_re_v = builder.addType(kSig_re_v); + + // Calls throw_ref, catches an exception with 'try_table - catch JSTag', and + // returns it + builder.addFunction("catch_js_tag_and_return", kSig_r_r) + .addBody([ + kExprBlock, sig_r_v, + kExprTryTable, sig_r_v, 1, + kCatchNoRef, jsTag, 0, + kExprLocalGet, 0, + kExprCallFunction, throwRefFn, + kExprEnd, + kExprEnd, + ]) + .exportFunc(); + + // Calls throw_ref, catches an exception with 'try_table - catch_ref JSTag', + // and returns it + builder.addFunction("catch_ref_js_tag_and_return", kSig_r_r) + .addBody([ + kExprBlock, sig_re_v, + kExprTryTable, sig_r_v, 1, + kCatchRef, jsTag, 0, + kExprLocalGet, 0, + kExprCallFunction, throwRefFn, + kExprEnd, + kExprReturn, + kExprEnd, + kExprDrop, + ]) + .exportFunc(); + + // Calls throw_ref, catches an exception with 'try_table - catch_ref JSTag', + // and rethrows it (with throw_ref) + builder.addFunction("catch_ref_js_tag_and_throw_ref", kSig_r_r) + .addBody([ + kExprBlock, sig_re_v, + kExprTryTable, sig_r_v, 1, + kCatchRef, jsTag, 0, + kExprLocalGet, 0, + kExprCallFunction, throwRefFn, + kExprEnd, + kExprReturn, + kExprEnd, + kExprThrowRef, + ]) + .exportFunc(); + + function throw_ref(x) { + throw x; + } + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, { + module: { throw_ref, JSTag: WebAssembly.JSTag } + }); + + const obj = {}; + const wasmTag = new WebAssembly.Tag({parameters:['externref']}); + const exn = new WebAssembly.Exception(wasmTag, [obj]); + + // Test catch w/ return: + // This throws obj as a JS exception so it should be caught by the program and + // be returned as the original obj. + assert_equals(obj, instance.exports.catch_js_tag_and_return(obj)); + // This is a WebAssembly.Exception, so the exception should just pass through + // the program without being caught. + assert_throws_exactly(exn, () => instance.exports.catch_js_tag_and_return(exn)); + + // Test catch_ref w/ return: + // This throws obj as a JS exception so it should be caught by the program and + // be returned as the original obj. + assert_equals(obj, instance.exports.catch_ref_js_tag_and_return(obj)); + // This is a WebAssembly.Exception, so the exception should just pass through + // the program without being caught. + assert_throws_exactly(exn, () => instance.exports.catch_ref_js_tag_and_return(exn)); + + // Test catch_ref w/ throw_ref: + // This throws obj as a JS exception so it should be caught by the program and + // be rethrown. + assert_throws_exactly(obj, () => instance.exports.catch_ref_js_tag_and_throw_ref(obj)); + // This is a WebAssembly.Exception, so the exception should just pass through + // the program without being caught. + assert_throws_exactly(exn, () => instance.exports.catch_ref_js_tag_and_throw_ref(exn)); +}, "JS tag catching tests"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const jsTag = builder.addImportedTag("module", "JSTag", kSig_v_r); + + // Throw a JS object with WebAssembly.JSTag and check that we can catch it + // as-is from JavaScript. + builder.addFunction("throw_js_tag", kSig_v_r) + .addBody([ + kExprLocalGet, 0, + kExprThrow, jsTag, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, { + module: { JSTag: WebAssembly.JSTag } + }); + + const obj = {}; + assert_throws_exactly(obj, () => instance.exports.throw_js_tag(obj)); +}, "JS tag throwing test"); diff --git a/test/js-api/exception/toString.tentative.any.js b/test/js-api/exception/toString.tentative.any.js index 00e801a6fc..6885cf0deb 100644 --- a/test/js-api/exception/toString.tentative.any.js +++ b/test/js-api/exception/toString.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { parameters: [] }; diff --git a/test/js-api/tag/constructor.tentative.any.js b/test/js-api/tag/constructor.tentative.any.js index de63e7bf46..54edf8c8f5 100644 --- a/test/js-api/tag/constructor.tentative.any.js +++ b/test/js-api/tag/constructor.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm // META: script=/wasm/jsapi/assertions.js test(() => { diff --git a/test/js-api/tag/toString.tentative.any.js b/test/js-api/tag/toString.tentative.any.js index ad9a4ba152..76fff0feef 100644 --- a/test/js-api/tag/toString.tentative.any.js +++ b/test/js-api/tag/toString.tentative.any.js @@ -1,4 +1,4 @@ -// META: global=window,dedicatedworker,jsshell +// META: global=window,dedicatedworker,jsshell,shadowrealm test(() => { const argument = { parameters: [] }; diff --git a/test/js-api/tag/type.tentative.any.js b/test/js-api/tag/type.tentative.any.js deleted file mode 100644 index 9d2f0de1a0..0000000000 --- a/test/js-api/tag/type.tentative.any.js +++ /dev/null @@ -1,21 +0,0 @@ -// META: global=window,dedicatedworker,jsshell -// META: script=/wasm/jsapi/assertions.js - -function assert_type(argument) { - const tag = new WebAssembly.Tag(argument); - const tagtype = tag.type(); - - assert_array_equals(tagtype.parameters, argument.parameters); -} - -test(() => { - assert_type({ parameters: [] }); -}, "[]"); - -test(() => { - assert_type({ parameters: ["i32", "i64"] }); -}, "[i32 i64]"); - -test(() => { - assert_type({ parameters: ["i32", "i64", "f32", "f64"] }); -}, "[i32 i64 f32 f64]"); diff --git a/test/js-api/wasm-module-builder.js b/test/js-api/wasm-module-builder.js index 104c730c7c..0df0e5a086 100644 --- a/test/js-api/wasm-module-builder.js +++ b/test/js-api/wasm-module-builder.js @@ -97,8 +97,8 @@ let kDeclFunctionImport = 0x02; let kDeclFunctionLocals = 0x04; let kDeclFunctionExport = 0x08; -// Local types -let kWasmStmt = 0x40; +// Value types and related +let kWasmVoid = 0x40; let kWasmI32 = 0x7f; let kWasmI64 = 0x7e; let kWasmF32 = 0x7d; @@ -114,10 +114,15 @@ let kWasmFuncRef = -0x10; let kWasmAnyFunc = kWasmFuncRef; // Alias named as in the JS API spec let kWasmExternRef = -0x11; let kWasmAnyRef = -0x12; +<<<<<<< HEAD let kWasmEqRef = -0x13; let kWasmI31Ref = -0x14; let kWasmStructRef = -0x15; let kWasmArrayRef = -0x16; +======= +let kWasmExnRef = -0x17; +let kWasmNullExnRef = -0x0c; +>>>>>>> exn/main // Use the positive-byte versions inside function bodies. let kLeb128Mask = 0x7f; @@ -125,21 +130,36 @@ let kFuncRefCode = kWasmFuncRef & kLeb128Mask; let kAnyFuncCode = kFuncRefCode; // Alias named as in the JS API spec let kExternRefCode = kWasmExternRef & kLeb128Mask; let kAnyRefCode = kWasmAnyRef & kLeb128Mask; +<<<<<<< HEAD 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 kNullExternRefCode = kWasmNullExternRef & kLeb128Mask; +let kNullFuncRefCode = kWasmNullFuncRef & kLeb128Mask; +let kExnRefCode = kWasmExnRef & kLeb128Mask; +let kNullExnRefCode = kWasmNullExnRef & kLeb128Mask; +>>>>>>> exn/main let kNullRefCode = kWasmNullRef & kLeb128Mask; let kWasmRefNull = 0x63; let kWasmRef = 0x64; +<<<<<<< HEAD function wasmRefNullType(heap_type) { return {opcode: kWasmRefNull, heap_type: heap_type}; } function wasmRefType(heap_type) { return {opcode: kWasmRef, heap_type: heap_type}; +======= +function wasmRefNullType(heap_type, is_shared = false) { + return {opcode: kWasmRefNull, heap_type: heap_type, is_shared: is_shared}; +} +function wasmRefType(heap_type, is_shared = false) { + return {opcode: kWasmRef, heap_type: heap_type, is_shared: is_shared}; +>>>>>>> exn/main } let kExternalFunction = 0; @@ -152,7 +172,7 @@ let kTableZero = 0; let kMemoryZero = 0; let kSegmentZero = 0; -let kTagAttribute = 0; +let kExceptionAttribute = 0; // Useful signatures let kSig_i_i = makeSig([kWasmI32], [kWasmI32]); @@ -232,10 +252,9 @@ let kExprIf = 0x04; let kExprElse = 0x05; let kExprTry = 0x06; let kExprCatch = 0x07; -let kExprCatchAll = 0x19; let kExprThrow = 0x08; let kExprRethrow = 0x09; -let kExprBrOnExn = 0x0a; +let kExprThrowRef = 0x0a; let kExprEnd = 0x0b; let kExprBr = 0x0c; let kExprBrIf = 0x0d; @@ -245,8 +264,10 @@ let kExprCallFunction = 0x10; let kExprCallIndirect = 0x11; let kExprReturnCall = 0x12; let kExprReturnCallIndirect = 0x13; +let kExprCatchAll = 0x19; let kExprDrop = 0x1a; let kExprSelect = 0x1b; +let kExprTryTable = 0x1f; let kExprLocalGet = 0x20; let kExprLocalSet = 0x21; let kExprLocalTee = 0x22; @@ -549,6 +570,12 @@ let kExprI32x4Eq = 0x2c; let kExprS1x4AllTrue = 0x75; let kExprF32x4Min = 0x9e; +// Exception handling with exnref. +let kCatchNoRef = 0x0; +let kCatchRef = 0x1; +let kCatchAllNoRef = 0x2; +let kCatchAllRef = 0x3; + class Binary { constructor() { this.length = 0; @@ -636,10 +663,13 @@ class Binary { } } +<<<<<<< HEAD emit_heap_type(heap_type) { this.emit_bytes(wasmSignedLeb(heap_type, kMaxVarInt32Size)); } +======= +>>>>>>> exn/main emit_type(type) { if ((typeof type) == 'number') { this.emit_u8(type >= 0 ? type : type & kLeb128Mask); @@ -650,11 +680,14 @@ class Binary { } } +<<<<<<< HEAD emit_init_expr(expr) { this.emit_bytes(expr); this.emit_u8(kExprEnd); } +======= +>>>>>>> exn/main emit_header() { this.emit_bytes([ kWasmH0, kWasmH1, kWasmH2, kWasmH3, kWasmV0, kWasmV1, kWasmV2, kWasmV3 @@ -1090,6 +1123,7 @@ class WasmModuleBuilder { if (wasm.types.length > 0) { if (debug) print('emitting types @ ' + binary.length); binary.emit_section(kTypeSectionCode, section => { +<<<<<<< HEAD let length_with_groups = wasm.types.length; for (let group of wasm.rec_groups) { length_with_groups -= group.size - 1; @@ -1137,6 +1171,18 @@ class WasmModuleBuilder { for (let result of type.results) { section.emit_type(result); } +======= + 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_type(param); + } + section.emit_u32v(type.results.length); + for (let result of type.results) { + section.emit_type(result); +>>>>>>> exn/main } } }); @@ -1173,7 +1219,7 @@ class WasmModuleBuilder { section.emit_u32v(imp.initial); // initial if (has_max) section.emit_u32v(imp.maximum); // maximum } else if (imp.kind == kExternalTag) { - section.emit_u32v(kTagAttribute); + section.emit_u32v(kExceptionAttribute); section.emit_u32v(imp.type); } else { throw new Error("unknown/unsupported import kind " + imp.kind); @@ -1249,7 +1295,7 @@ class WasmModuleBuilder { binary.emit_section(kTagSectionCode, section => { section.emit_u32v(wasm.tags.length); for (let type of wasm.tags) { - section.emit_u32v(kTagAttribute); + section.emit_u32v(kExceptionAttribute); section.emit_u32v(type); } }); diff --git a/test/legacy/exceptions/rethrow.wast b/test/legacy/exceptions/core/rethrow.wast similarity index 100% rename from test/legacy/exceptions/rethrow.wast rename to test/legacy/exceptions/core/rethrow.wast diff --git a/test/legacy/run.py b/test/legacy/exceptions/core/run.py similarity index 98% rename from test/legacy/run.py rename to test/legacy/exceptions/core/run.py index f727aeef37..b2f6c314b3 100755 --- a/test/legacy/run.py +++ b/test/legacy/exceptions/core/run.py @@ -23,7 +23,7 @@ arguments = parser.parse_args() sys.argv = sys.argv[:1] -exceptions_test_files = glob.glob(os.path.join(inputDir, "exceptions", "*.wast")) +exceptions_test_files = glob.glob(os.path.join(inputDir, "*.wast")) wasmCommand = arguments.wasm jsCommand = arguments.js diff --git a/test/legacy/exceptions/throw.wast b/test/legacy/exceptions/core/throw.wast similarity index 100% rename from test/legacy/exceptions/throw.wast rename to test/legacy/exceptions/core/throw.wast diff --git a/test/legacy/exceptions/try_catch.wast b/test/legacy/exceptions/core/try_catch.wast similarity index 96% rename from test/legacy/exceptions/try_catch.wast rename to test/legacy/exceptions/core/try_catch.wast index 2a0e9ff620..07399f3a51 100644 --- a/test/legacy/exceptions/try_catch.wast +++ b/test/legacy/exceptions/core/try_catch.wast @@ -167,6 +167,14 @@ (catch $e0) ) ) + + (func (export "break-try-catch") + (try (do (br 0)) (catch $e0)) + ) + + (func (export "break-try-catch_all") + (try (do (br 0)) (catch_all)) + ) ) (assert_return (invoke "empty-catch")) @@ -211,6 +219,9 @@ (assert_exception (invoke "return-call-in-try-catch")) (assert_exception (invoke "return-call-indirect-in-try-catch")) +(assert_return (invoke "break-try-catch")) +(assert_return (invoke "break-try-catch_all")) + (module (func $imported-throw (import "test" "throw")) (tag $e0) diff --git a/test/legacy/exceptions/try_delegate.wast b/test/legacy/exceptions/core/try_delegate.wast similarity index 83% rename from test/legacy/exceptions/try_delegate.wast rename to test/legacy/exceptions/core/try_delegate.wast index aae3a301b9..39ee09a2b1 100644 --- a/test/legacy/exceptions/try_delegate.wast +++ b/test/legacy/exceptions/core/try_delegate.wast @@ -145,6 +145,46 @@ (catch $e0) ) ) + + (func (export "break-try-delegate") + (try (do (br 0)) (delegate 0)) + ) + + (func (export "break-and-call-throw") (result i32) + (try $outer (result i32) + (do + (try (result i32) + (do + (block $a + (try (do (br $a)) (delegate $outer)) + ) + (call $throw-void) + (i32.const 0) + ) + (catch $e0 (i32.const 1)) + ) + ) + (catch $e0 (i32.const 2)) + ) + ) + + (func (export "break-and-throw") (result i32) + (try $outer (result i32) + (do + (try (result i32) + (do + (block $a + (try (do (br $a)) (delegate $outer)) + ) + (throw $e0) + (i32.const 0) + ) + (catch $e0 (i32.const 1)) + ) + ) + (catch $e0 (i32.const 2)) + ) + ) ) (assert_return (invoke "delegate-no-throw") (i32.const 1)) @@ -173,6 +213,11 @@ (assert_exception (invoke "return-call-in-try-delegate")) (assert_exception (invoke "return-call-indirect-in-try-delegate")) +(assert_return (invoke "break-try-delegate")) + +(assert_return (invoke "break-and-call-throw") (i32.const 1)) +(assert_return (invoke "break-and-throw") (i32.const 1)) + (assert_malformed (module quote "(module (func (delegate 0)))") "unexpected token" diff --git a/test/legacy/exceptions/js-api/basic.tentative.any.js b/test/legacy/exceptions/js-api/basic.tentative.any.js new file mode 100644 index 0000000000..81478529a4 --- /dev/null +++ b/test/legacy/exceptions/js-api/basic.tentative.any.js @@ -0,0 +1,120 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/wasm-module-builder.js + +function assert_throws_wasm(fn, message) { + try { + fn(); + assert_not_reached(`expected to throw with ${message}`); + } catch (e) { + assert_true(e instanceof WebAssembly.Exception, `Error should be a WebAssembly.Exception with ${message}`); + } +} + +promise_test(async () => { + const kSig_v_r = makeSig([kWasmExternRef], []); + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_r); + builder.addFunction("throw_param", kSig_v_r) + .addBody([ + kExprLocalGet, 0, + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + const values = [ + undefined, + null, + true, + false, + "test", + Symbol(), + 0, + 1, + 4.2, + NaN, + Infinity, + {}, + () => {}, + ]; + for (const v of values) { + assert_throws_wasm(() => instance.exports.throw_param(v), String(v)); + } +}, "Wasm function throws argument"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_a); + builder.addFunction("throw_null", kSig_v_v) + .addBody([ + kExprRefNull, kAnyFuncCode, + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_null()); +}, "Wasm function throws null"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const tagIndex = builder.addTag(kSig_v_i); + builder.addFunction("throw_int", kSig_v_v) + .addBody([ + ...wasmI32Const(7), + kExprThrow, tagIndex, + ]) + .exportFunc(); + const buffer = builder.toBuffer(); + const {instance} = await WebAssembly.instantiate(buffer, {}); + assert_throws_wasm(() => instance.exports.throw_int()); +}, "Wasm function throws integer"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + const tagIndex = builder.addTag(kSig_v_r); + builder.addFunction("catch_exception", kSig_r_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, fnIndex, + kExprCatch, tagIndex, + kExprReturn, + kExprEnd, + kExprRefNull, kExternRefCode, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_exception()); +}, "Imported JS function throws"); + +promise_test(async () => { + const builder = new WasmModuleBuilder(); + const fnIndex = builder.addImport("module", "fn", kSig_v_v); + builder.addFunction("catch_and_rethrow", kSig_r_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, fnIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd, + kExprRefNull, kExternRefCode, + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + const error = new Error(); + const fn = () => { throw error }; + const {instance} = await WebAssembly.instantiate(buffer, { + module: { fn } + }); + assert_throws_exactly(error, () => instance.exports.catch_and_rethrow()); +}, "Imported JS function throws, Wasm catches and rethrows"); diff --git a/test/legacy/exceptions/js-api/identity.tentative.any.js b/test/legacy/exceptions/js-api/identity.tentative.any.js new file mode 100644 index 0000000000..36651c7d5b --- /dev/null +++ b/test/legacy/exceptions/js-api/identity.tentative.any.js @@ -0,0 +1,170 @@ +// META: global=window,dedicatedworker,jsshell,shadowrealm +// META: script=/wasm/jsapi/assertions.js +// META: script=/wasm/jsapi/wasm-module-builder.js + +test(() => { + const builder = new WasmModuleBuilder(); + + // Tag defined in JavaScript and imported into Wasm + const jsTag = new WebAssembly.Tag({ parameters: ["i32"] }); + const jsTagIndex = builder.addImportedTag("module", "jsTag", kSig_v_i); + const jsTagExn = new WebAssembly.Exception(jsTag, [42]); + const jsTagExnSamePayload = new WebAssembly.Exception(jsTag, [42]); + const jsTagExnDiffPayload = new WebAssembly.Exception(jsTag, [53]); + const throwJSTagExnIndex = builder.addImport("module", "throwJSTagExn", kSig_v_v); + + // Tag defined in Wasm and exported to JS + const wasmTagIndex = builder.addTag(kSig_v_i); + builder.addExportOfKind("wasmTag", kExternalTag, wasmTagIndex); + const throwWasmTagExnIndex = builder.addImport("module", "throwWasmTagExn", kSig_v_v); + // Will be assigned after an instance is created + let wasmTagExn = null; + let wasmTagExnSamePayload = null; + let wasmTagExnDiffPayload = null; + + const imports = { + module: { + throwJSTagExn: function() { throw jsTagExn; }, + throwWasmTagExn: function() { throw wasmTagExn; }, + jsTag: jsTag + } + }; + + // Call a JS function that throws an exception using a JS-defined tag, catches + // it with a 'catch' instruction, and rethrows it. + builder + .addFunction("catch_js_tag_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, throwJSTagExnIndex, + kExprCatch, jsTagIndex, + kExprDrop, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + // Call a JS function that throws an exception using a Wasm-defined tag, + // catches it with a 'catch' instruction, and rethrows it. + builder + .addFunction("catch_wasm_tag_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, throwWasmTagExnIndex, + kExprCatch, wasmTagIndex, + kExprDrop, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + // Call a JS function that throws an exception using a JS-defined tag, catches + // it with a 'catch_all' instruction, and rethrows it. + builder + .addFunction("catch_all_js_tag_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, throwJSTagExnIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + // Call a JS function that throws an exception using a Wasm-defined tag, + // catches it with a 'catch_all' instruction, and rethrows it. + builder + .addFunction("catch_all_wasm_tag_rethrow", kSig_v_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, throwWasmTagExnIndex, + kExprCatchAll, + kExprRethrow, 0x00, + kExprEnd + ]) + .exportFunc(); + + // Call a JS function that throws an exception, catches it with a 'catch' + // instruction, and returns its i32 payload. + builder + .addFunction("catch_js_tag_return_payload", kSig_i_v) + .addBody([ + kExprTry, kWasmI32, + kExprCallFunction, throwJSTagExnIndex, + kExprI32Const, 0x00, + kExprCatch, jsTagIndex, + kExprReturn, + kExprEnd + ]) + .exportFunc(); + + // Call a JS function that throws an exception, catches it with a 'catch' + // instruction, and throws a new exception using that payload. + builder + .addFunction("catch_js_tag_throw_payload", kSig_v_v) + .addBody([ + kExprTry, kWasmVoid, + kExprCallFunction, throwJSTagExnIndex, + kExprCatch, jsTagIndex, + kExprThrow, jsTagIndex, + kExprEnd + ]) + .exportFunc(); + + const buffer = builder.toBuffer(); + + WebAssembly.instantiate(buffer, imports).then(result => { + // The exception object's identity should be preserved across 'rethrow's in + // Wasm code. Do tests with a tag defined in JS. + try { + result.instance.exports.catch_js_tag_rethrow(); + } catch (e) { + assert_equals(e, jsTagExn); + // Even if they have the same payload, they are different objects, so they + // shouldn't compare equal. + assert_not_equals(e, jsTagExnSamePayload); + assert_not_equals(e, jsTagExnDiffPayload); + } + try { + result.instance.exports.catch_all_js_tag_rethrow(); + } catch (e) { + assert_equals(e, jsTagExn); + assert_not_equals(e, jsTagExnSamePayload); + assert_not_equals(e, jsTagExnDiffPayload); + } + + // Do the same tests with a tag defined in Wasm. + const wasmTag = result.instance.exports.wasmTag; + wasmTagExn = new WebAssembly.Exception(wasmTag, [42]); + wasmTagExnSamePayload = new WebAssembly.Exception(wasmTag, [42]); + wasmTagExnDiffPayload = new WebAssembly.Exception(wasmTag, [53]); + try { + result.instance.exports.catch_wasm_tag_rethrow(); + } catch (e) { + assert_equals(e, wasmTagExn); + assert_not_equals(e, wasmTagExnSamePayload); + assert_not_equals(e, wasmTagExnDiffPayload); + } + try { + result.instance.exports.catch_all_wasm_tag_rethrow(); + } catch (e) { + assert_equals(e, wasmTagExn); + assert_not_equals(e, wasmTagExnSamePayload); + assert_not_equals(e, wasmTagExnDiffPayload); + } + + // This function catches the exception and returns its i32 payload, which + // should match the original payload. + assert_equals(result.instance.exports.catch_js_tag_return_payload(), 42); + + // This function catches the exception and throws a new exception using the + // its payload. Even if the payload is reused, the exception objects should + // not compare equal. + try { + result.instance.exports.catch_js_tag_throw_payload(); + } catch (e) { + assert_equals(e.getArg(jsTag, 0), 42); + assert_not_equals(e, jsTagExn); + } + }); +}, "Identity check");