diff --git a/document/core/appendix/embedding.rst b/document/core/appendix/embedding.rst index 48b601b2..f3fa84a3 100644 --- a/document/core/appendix/embedding.rst +++ b/document/core/appendix/embedding.rst @@ -294,8 +294,8 @@ Tables .. _embed-alloc-table: -:math:`\F{alloc\_table}(\store, \tabletype) : (\store, \tableaddr)` -................................................................... +:math:`\F{alloc\_table}(\store, \tabletype) : (\store, \tableaddr, \val)` +......................................................................... 1. Let :math:`\tableaddr` be the result of :ref:`allocating a table ` in :math:`\store` with :ref:`table type ` :math:`\tabletype`. @@ -303,7 +303,7 @@ Tables .. math:: \begin{array}{lclll} - \F{alloc\_table}(S, \X{tt}) &=& (S', \X{a}) && (\iff \alloctable(S, \X{tt}) = S', \X{a}) \\ + \F{alloc\_table}(S, \X{tt}, v) &=& (S', \X{a}) && (\iff \alloctable(S, \X{tt}, v) = S', \X{a}) \\ \end{array} @@ -390,8 +390,8 @@ Tables .. _embed-grow-table: -:math:`\F{grow\_table}(\store, \tableaddr, n) : \store ~|~ \error` -.................................................................. +:math:`\F{grow\_table}(\store, \tableaddr, n, \val) : \store ~|~ \error` +........................................................................ 1. Assert: :math:`\store.\STABLES[\tableaddr]` exists. @@ -406,9 +406,9 @@ Tables .. math:: ~ \\ \begin{array}{lclll} - \F{grow\_table}(S, a, n) &=& S' && - (\iff S' = S \with \STABLES[a] = \growtable(S.\STABLES[a], n)) \\ - \F{grow\_table}(S, a, n) &=& \ERROR && (\otherwise) \\ + \F{grow\_table}(S, a, n, v) &=& S' && + (\iff S' = S \with \STABLES[a] = \growtable(S.\STABLES[a], n, v)) \\ + \F{grow\_table}(S, a, n, v) &=& \ERROR && (\otherwise) \\ \end{array} diff --git a/document/core/binary/instructions.rst b/document/core/binary/instructions.rst index 7b41829c..e1ad15ce 100644 --- a/document/core/binary/instructions.rst +++ b/document/core/binary/instructions.rst @@ -138,21 +138,24 @@ Variable Instructions Table Instructions ~~~~~~~~~~~~~~~~~~ -:ref:`Table instructions ` are represented by single byte codes. +:ref:`Table instructions ` are represented by either single byte or two byte codes. .. _binary-table.get: .. _binary-table.set: +.. _binary-table.size: +.. _binary-table.grow: +.. _binary-table.fill: .. math:: \begin{array}{llclll} \production{instruction} & \Binstr &::=& \dots \\ &&|& \hex{25}~~x{:}\Btableidx &\Rightarrow& \TABLEGET~x \\ &&|& - \hex{26}~~x{:}\Btableidx &\Rightarrow& \TABLESET~x \\ + \hex{26}~~x{:}\Btableidx &\Rightarrow& \TABLESET~x \\ &&|& + \hex{FC}~\hex{0F}~~x{:}\Btableidx &\Rightarrow& \TABLEGROW~x \\ &&|& + \hex{FC}~\hex{10}~~x{:}\Btableidx &\Rightarrow& \TABLESIZE~x \\ &&|& + \hex{FC}~\hex{11}~~x{:}\Btableidx &\Rightarrow& \TABLEFILL~x \\ \end{array} -.. note:: - These opcode assignments are preliminary. - .. index:: memory instruction, memory index pair: binary format; instruction diff --git a/document/core/exec/instructions.rst b/document/core/exec/instructions.rst index 2673db49..91452e8c 100644 --- a/document/core/exec/instructions.rst +++ b/document/core/exec/instructions.rst @@ -522,6 +522,162 @@ Table Instructions \end{array} +.. _exec-table.size: + +:math:`\TABLESIZE~x` +.................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITABLES[x]` exists. + +3. Let :math:`a` be the :ref:`table address ` :math:`F.\AMODULE.\MITABLES[x]`. + +4. Assert: due to :ref:`validation `, :math:`S.\STABLES[a]` exists. + +5. Let :math:`\X{tab}` be the :ref:`table instance ` :math:`S.\STABLES[a]`. + +6. Let :math:`\X{sz}` be the length of :math:`\X{tab}.\TIELEM`. + +7. Push the value :math:`\I32.\CONST~\X{sz}` to the stack. + +.. math:: + \begin{array}{l} + \begin{array}{lcl@{\qquad}l} + S; F; \TABLESIZE~x &\stepto& S; F; (\I32.\CONST~\X{sz}) + \end{array} + \\ \qquad + (\iff |S.\STABLES[F.\AMODULE.\MITABLES[x]].\TIELEM| = \X{sz}) \\ + \end{array} + + +.. _exec-table.grow: + +:math:`\TABLEGROW~x` +.................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITABLES[x]` exists. + +3. Let :math:`a` be the :ref:`table address ` :math:`F.\AMODULE.\MITABLES[x]`. + +4. Assert: due to :ref:`validation `, :math:`S.\STABLES[a]` exists. + +5. Let :math:`\X{tab}` be the :ref:`table instance ` :math:`S.\STABLES[a]`. + +6. Let :math:`\X{sz}` be the length of :math:`S.\STABLES[a]`. + +7. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. + +8. Pop the value :math:`\I32.\CONST~n` from the stack. + +9. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +10. Pop the value :math:`\val` from the stack. + +11. Either, try :ref:`growing ` :math:`\X{table}` by :math:`n` entries with initialization value :math:`\val`: + + a. If it succeeds, push the value :math:`\I32.\CONST~\X{sz}` to the stack. + + b. Else, push the value :math:`\I32.\CONST~(-1)` to the stack. + +12. Or, push the value :math:`\I32.\CONST~(-1)` to the stack. + +.. math:: + ~\\[-1ex] + \begin{array}{l} + \begin{array}{lcl@{\qquad}l} + S; F; \val~(\I32.\CONST~n)~\TABLEGROW~x &\stepto& S'; F; (\I32.\CONST~\X{sz}) + \end{array} + \\ \qquad + \begin{array}[t]{@{}r@{~}l@{}} + (\iff & F.\AMODULE.\MITABLES[x] = a \\ + \wedge & \X{sz} = |S.\STABLES[a].\TIELEM| \\ + \wedge & S' = S \with \STABLES[a] = \growtable(S.\STABLES[a], n, \val)) \\ + \end{array} + \\[1ex] + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~n)~\TABLEGROW~x &\stepto& S; F; (\I32.\CONST~{-1}) + \end{array} + \end{array} + +.. note:: + The |TABLEGROW| instruction is non-deterministic. + It may either succeed, returning the old table size :math:`\X{sz}`, + or fail, returning :math:`{-1}`. + Failure *must* occur if the referenced table instance has a maximum size defined that would be exceeded. + However, failure *can* occur in other cases as well. + In practice, the choice depends on the :ref:`resources ` available to the :ref:`embedder `. + + +.. _exec-table.fill: + +:math:`\TABLEFILL~x` +.................... + +1. Let :math:`F` be the :ref:`current ` :ref:`frame `. + +2. Assert: due to :ref:`validation `, :math:`F.\AMODULE.\MITABLES[x]` exists. + +3. Let :math:`a` be the :ref:`table address ` :math:`F.\AMODULE.\MITABLES[x]`. + +4. Assert: due to :ref:`validation `, :math:`S.\STABLES[a]` exists. + +5. Let :math:`\X{tab}` be the :ref:`table instance ` :math:`S.\STABLES[a]`. + +6. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. + +7. Pop the value :math:`\I32.\CONST~n` from the stack. + +8. Assert: due to :ref:`validation `, a :ref:`reference value ` is on the top of the stack. + +9. Pop the value :math:`\val` from the stack. + +10. Assert: due to :ref:`validation `, a value of :ref:`value type ` |I32| is on the top of the stack. + +11. Pop the value :math:`\I32.\CONST~i` from the stack. + +12. If :math:`n` is :math:`0`, then: + + a. If :math:`i` is larger than the length of :math:`\X{tab}.\TIELEM`, then: + + i. Trap. + +12. Else: + + a. Push the value :math:`\I32.CONST~i` to the stack. + + b. Push the value :math:`\val` to the stack. + + c. Execute the instruction :math:`\TABLESET~x`. + + d. Push the value :math:`\I32.CONST~(i+1)` to the stack. + + e. Push the value :math:`\val` to the stack. + + f. Push the value :math:`\I32.CONST~(n-1)` to the stack. + + c. Execute the instruction :math:`\TABLEFILL~x`. + +.. math:: + \begin{array}{l} + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~i)~\val~(\I32.\CONST~(n+1))~(\TABLEFILL~x) &\stepto& S'; F; (\I32.\CONST~i)~\val~(\TABLESET~x)~(\I32.\CONST~(i+1))~\val~(\I32.\CONST~n)~(\TABLEFILL~x) + \end{array} \\ + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~i)~\val~(\I32.\CONST~0)~(\TABLEFILL~x) &\stepto& S'; F; \epsilon + \end{array} + \\ \qquad + (\iff i \leq |\STABLES[F.\AMODULE.\MITABLES[x]]|) \\ + \begin{array}{lcl@{\qquad}l} + S; F; (\I32.\CONST~i)~\val~(\I32.\CONST~0)~(\TABLEFILL~x) &\stepto& S; F; \TRAP + \end{array} + \\ \qquad + (\otherwise) \\ + \end{array} + + .. index:: memory instruction, memory index, store, frame, address, memory address, memory instance, value, integer, limits, value type, bit width pair: execution; instruction single: abstract syntax; instruction diff --git a/document/core/exec/modules.rst b/document/core/exec/modules.rst index 3078d944..4aad62cb 100644 --- a/document/core/exec/modules.rst +++ b/document/core/exec/modules.rst @@ -298,7 +298,7 @@ New instances of :ref:`functions `, :ref:`tables ` ................................ -1. Let :math:`\tabletype` be the :ref:`table type ` to allocate. +1. Let :math:`\tabletype` be the :ref:`table type ` to allocate and :math:`\val` the initialization value. 2. Let :math:`(\{\LMIN~n, \LMAX~m^?\}~\reftype)` be the structure of :ref:`table type ` :math:`\tabletype`. @@ -312,11 +312,11 @@ New instances of :ref:`functions `, :ref:`tables `, :ref:`tables ` ........................................ -1. Let :math:`\tableinst` be the :ref:`table instance ` to grow and :math:`n` the number of elements by which to grow it. +1. Let :math:`\tableinst` be the :ref:`table instance ` to grow, :math:`n` the number of elements by which to grow it, and :math:`\val` the initialization value. 2. Let :math:`\X{len}` be :math:`n` added to the length of :math:`\tableinst.\TIELEM`. @@ -397,7 +397,7 @@ Growing :ref:`tables ` .. math:: \begin{array}{rllll} - \growtable(\tableinst, n) &=& \tableinst \with \TIELEM = \tableinst.\TIELEM~\REFNULL^n \\ + \growtable(\tableinst, n, \val) &=& \tableinst \with \TIELEM = \tableinst.\TIELEM~\val^n \\ && ( \begin{array}[t]{@{}r@{~}l@{}} \iff & \X{len} = n + |\tableinst.\TIELEM| \\ @@ -519,7 +519,7 @@ where: \MIEXPORTS~\exportinst^\ast ~\} \end{array} \\[1ex] S_1, \funcaddr^\ast &=& \allocfunc^\ast(S, \module.\MFUNCS, \moduleinst) \\ - S_2, \tableaddr^\ast &=& \alloctable^\ast(S_1, (\table.\TTYPE)^\ast) + S_2, \tableaddr^\ast &=& \alloctable^\ast(S_1, (\table.\TTYPE)^\ast, \REFNULL) \qquad\qquad\qquad~ (\where \table^\ast = \module.\MTABLES) \\ S_3, \memaddr^\ast &=& \allocmem^\ast(S_2, (\mem.\MTYPE)^\ast) \qquad\qquad\qquad~ (\where \mem^\ast = \module.\MMEMS) \\ diff --git a/document/core/syntax/instructions.rst b/document/core/syntax/instructions.rst index 2fa21f1c..c4806d5c 100644 --- a/document/core/syntax/instructions.rst +++ b/document/core/syntax/instructions.rst @@ -241,6 +241,9 @@ The |LOCALTEE| instruction is like |LOCALSET| but also returns its argument. pair: abstract syntax; instruction .. _syntax-table.get: .. _syntax-table.set: +.. _syntax-table.size: +.. _syntax-table.grow: +.. _syntax-table.fill: .. _syntax-instr-table: Table Instructions @@ -253,10 +256,19 @@ Instructions in this group are concerned with accessing :ref:`tables ` |CALLINDIRECT|. diff --git a/document/core/text/instructions.rst b/document/core/text/instructions.rst index 002866e1..f42cc0f6 100644 --- a/document/core/text/instructions.rst +++ b/document/core/text/instructions.rst @@ -200,12 +200,18 @@ Table Instructions .. _text-table.get: .. _text-table.set: +.. _text-table.size: +.. _text-table.grow: +.. _text-table.fill: .. math:: \begin{array}{llclll} \production{instruction} & \Tplaininstr_I &::=& \dots \\ &&|& \text{table.get}~~x{:}\Ttableidx_I &\Rightarrow& \TABLEGET~x \\ &&|& - \text{table.set}~~x{:}\Ttableidx_I &\Rightarrow& \TABLESET~x \\ + \text{table.set}~~x{:}\Ttableidx_I &\Rightarrow& \TABLESET~x \\ &&|& + \text{table.size}~~x{:}\Ttableidx_I &\Rightarrow& \TABLESIZE~x \\ &&|& + \text{table.grow}~~x{:}\Ttableidx_I &\Rightarrow& \TABLEGROW~x \\ &&|& + \text{table.fill}~~x{:}\Ttableidx_I &\Rightarrow& \TABLEFILL~x \\ \end{array} diff --git a/document/core/util/macros.def b/document/core/util/macros.def index 3fe9dfd7..54be7bda 100644 --- a/document/core/util/macros.def +++ b/document/core/util/macros.def @@ -328,6 +328,9 @@ .. |TABLEGET| mathdef:: \xref{syntax/instructions}{syntax-instr-table}{\K{table.get}} .. |TABLESET| mathdef:: \xref{syntax/instructions}{syntax-instr-table}{\K{table.set}} +.. |TABLESIZE| mathdef:: \xref{syntax/instructions}{syntax-instr-table}{\K{table.size}} +.. |TABLEGROW| mathdef:: \xref{syntax/instructions}{syntax-instr-table}{\K{table.grow}} +.. |TABLEFILL| mathdef:: \xref{syntax/instructions}{syntax-instr-table}{\K{table.fill}} .. |LOAD| mathdef:: \xref{syntax/instructions}{syntax-instr-memory}{\K{load}} .. |STORE| mathdef:: \xref{syntax/instructions}{syntax-instr-memory}{\K{store}} diff --git a/document/core/valid/instructions.rst b/document/core/valid/instructions.rst index 72415623..16a432c1 100644 --- a/document/core/valid/instructions.rst +++ b/document/core/valid/instructions.rst @@ -398,6 +398,61 @@ Table Instructions } +.. _valid-table.size: + +:math:`\TABLESIZE~x` +.................... + +* The table :math:`C.\CTABLES[x]` must be defined in the context. + +* Then the instruction is valid with type :math:`[] \to [\I32]`. + +.. math:: + \frac{ + C.\CTABLES[x] = t + }{ + C \vdashinstr \TABLESIZE~x : [] \to [\I32] + } + + +.. _valid-table.grow: + +:math:`\TABLEGROW~x` +.................... + +* The table :math:`C.\CTABLES[x]` must be defined in the context. + +* Let :math:`\limits~t` be the :ref:`table type ` :math:`C.\CTABLES[x]`. + +* Then the instruction is valid with type :math:`[t~\I32] \to [\I32]`. + +.. math:: + \frac{ + C.\CTABLES[x] = t + }{ + C \vdashinstr \TABLEGROW~x : [t~\I32] \to [\I32] + } + + +.. _valid-table.fill: + +:math:`\TABLEFILL~x` +.................... + +* The table :math:`C.\CTABLES[x]` must be defined in the context. + +* Let :math:`\limits~t` be the :ref:`table type ` :math:`C.\CTABLES[x]`. + +* Then the instruction is valid with type :math:`[\I32~t~\I32] \to []`. + +.. math:: + \frac{ + C.\CTABLES[x] = t + }{ + C \vdashinstr \TABLEFILL~x : [\I32~t~\I32] \to [] + } + + .. index:: memory instruction, memory index, context pair: validation; instruction single: abstract syntax; instruction diff --git a/document/js-api/index.bs b/document/js-api/index.bs index c2eb6ec5..75463497 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -716,7 +716,7 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. 1. Let |type| be the [=table type=] {[=table type|𝗆𝗂𝗇=] n, [=table type|𝗆𝖺𝗑=] |maximum|} [=table type|funcref=]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let (|store|, |tableaddr|) be [=alloc_table=](|store|, |type|). + 1. Let (|store|, |tableaddr|) be [=alloc_table=](|store|, |type|, [=ref.null=]). 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. 1. [=Create a table object=] from the table address |tableaddr| and return the result. @@ -726,7 +726,7 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each 1. Let |tableaddr| be the Table instance's \[[Table]] internal slot. 1. Let |initialSize| be the length of the Table instance's \[[Values]] internal slot. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let |result| be [=grow_table=](|store|, |tableaddr|, |delta|). + 1. Let |result| be [=grow_table=](|store|, |tableaddr|, |delta|, [=ref.null=]). 1. If |result| is [=error=], throw a {{RangeError}} exception. Note: The above exception may happen due to either insufficient memory or an invalid size parameter. @@ -752,12 +752,12 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each The set(|index|, |value|) method, when invoked, performs the following steps: 1. Let |tableaddr| be the Table instance's \[[Table]] internal slot. 1. Let |values| be the Table instance's \[[Values]] internal slot. - 1. If |value| is null, let |funcaddr| be an empty [=function element=]. + 1. If |value| is null, let |ref| be [=ref.null=]. 1. Otherwise, 1. If |value| does not have a \[[FunctionAddress]] internal slot, throw a {{TypeError}} exception. - 1. Let |funcaddr| be |value|.\[[FunctionAddress]]. + 1. Let |ref| be [=ref.func=] |value|.\[[FunctionAddress]]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let |store| be [=write_table=](|store|, |tableaddr|, |index|, |funcaddr|). + 1. Let |store| be [=write_table=](|store|, |tableaddr|, |index|, |ref|). 1. If |store| is [=error=], throw a {{RangeError}} exception. 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. 1. Set |values|[|index|] to |value|. diff --git a/interpreter/README.md b/interpreter/README.md index 0ef90f16..75512b55 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -226,6 +226,9 @@ op: global.set table.get table.set + table.size + table.grow + table.fill .load((8|16|32)_)? ? ? .store(8|16|32)? ? ? memory.size diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index e6bb53eb..08e41d36 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -47,6 +47,8 @@ let skip n = guard (skip n) let expect b s msg = require (guard get s = b) s (pos s - 1) msg let illegal s pos b = error s pos ("illegal opcode " ^ string_of_byte b) +let illegal2 s pos b1 b2 = + error s pos ("illegal opcode " ^ string_of_byte b1 ^ " " ^ string_of_byte b2) let at f s = let left = pos s in @@ -448,6 +450,15 @@ let rec instr s = | 0xd1 -> ref_is_null | 0xd2 -> ref_func (at var s) + | 0xfc as b1 -> + (match op s with + | 0x0f -> table_grow (at var s) + | 0x10 -> table_size (at var s) + | 0x11 -> table_fill (at var s) + + | b2 -> illegal2 s pos b1 b2 + ) + | b -> illegal s pos b and instr_block s = List.rev (instr_block' s []) @@ -610,7 +621,14 @@ let code_section s = (* Element section *) let segment dat s = - let index = at var s in + let pos = pos s in + let x = at vu32 s in + let index = + match x.Source.it with + | 0l -> x + | 2l -> at var s + | _ -> error s pos "invalid segment kind" + in let offset = const s in let init = dat s in {index; offset; init} diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index e4d25b26..7519bc2e 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -173,8 +173,12 @@ let encode m = | LocalTee x -> op 0x22; var x | GlobalGet x -> op 0x23; var x | GlobalSet x -> op 0x24; var x + | TableGet x -> op 0x25; var x | TableSet x -> op 0x26; var x + | TableSize x -> op 0xfc; op 0x10; var x + | TableGrow x -> op 0xfc; op 0x0f; var x + | TableFill x -> op 0xfc; op 0x11; var x | Load ({ty = I32Type; sz = None; _} as mo) -> op 0x28; memop mo | Load ({ty = I64Type; sz = None; _} as mo) -> op 0x29; memop mo @@ -485,7 +489,12 @@ let encode m = (* Element section *) let segment dat seg = let {index; offset; init} = seg.it in - var index; const offset; dat init + if index.it = 0l then + u8 0x00 + else begin + u8 0x02; var index + end; + const offset; dat init let table_segment seg = segment (vec var) seg diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 46c3b0d0..84eb2f4e 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -205,6 +205,29 @@ let rec step (c : config) : config = (try Table.store (table frame.inst x) i r; vs', [] with exn -> vs', [Trapping (table_error e.at exn) @@ e.at]) + | TableSize x, vs -> + Num (I32 (Table.size (table frame.inst x))) :: vs, [] + + | TableGrow x, Num (I32 delta) :: Ref r :: vs' -> + let tab = table frame.inst x in + let old_size = Table.size tab in + let result = + try Table.grow tab delta r; old_size + with Table.SizeOverflow | Table.SizeLimit | Table.OutOfMemory -> -1l + in Num (I32 result) :: vs', [] + + | TableFill x, Num (I32 0l) :: Ref r :: Num (I32 i) :: vs' -> + if I32.gt_u i (Table.size (table frame.inst x)) then + vs', [Trapping (table_error e.at Table.Bounds) @@ e.at] + else + vs', [] + + | TableFill x, Num (I32 n) :: Ref r :: Num (I32 i) :: vs' -> + assert (I32.lt_u i 0xffff_ffffl); + Ref r :: Num (I32 i) :: + Num (I32 (I32.sub n 1l)) :: Ref r :: Num (I32 (I32.add i 1l)) :: vs', + [Plain (TableSet x) @@ e.at; Plain (TableFill x) @@ e.at] + | Load {offset; ty; sz; _}, Num (I32 i) :: vs' -> let mem = memory frame.inst (0l @@ e.at) in let addr = I64_convert.extend_i32_u i in @@ -388,7 +411,7 @@ let create_func (inst : module_inst) (f : func) : func_inst = let create_table (inst : module_inst) (tab : table) : table_inst = let {ttype} = tab.it in - Table.alloc ttype + Table.alloc ttype NullRef let create_memory (inst : module_inst) (mem : memory) : memory_inst = let {mtype} = mem.it in diff --git a/interpreter/host/spectest.ml b/interpreter/host/spectest.ml index 6946f3cf..c343e1bb 100644 --- a/interpreter/host/spectest.ml +++ b/interpreter/host/spectest.ml @@ -17,7 +17,8 @@ let global (GlobalType (t, _) as gt) = | RefType _ -> Ref NullRef in Global.alloc gt v -let table = Table.alloc (TableType ({min = 10l; max = Some 20l}, FuncRefType)) +let table = + Table.alloc (TableType ({min = 10l; max = Some 20l}, FuncRefType)) NullRef let memory = Memory.alloc (MemoryType {min = 1l; max = Some 2l}) let func f t = Func.alloc_host t (f t) diff --git a/interpreter/runtime/table.ml b/interpreter/runtime/table.ml index 9958b7f7..9a5c173e 100644 --- a/interpreter/runtime/table.ml +++ b/interpreter/runtime/table.ml @@ -13,18 +13,19 @@ exception Type exception Bounds exception SizeOverflow exception SizeLimit +exception OutOfMemory let within_limits size = function | None -> true | Some max -> I32.le_u size max -let create size = - try Lib.Array32.make size NullRef - with Invalid_argument _ -> raise Out_of_memory +let create size r = + try Lib.Array32.make size r + with Out_of_memory | Invalid_argument _ -> raise OutOfMemory -let alloc (TableType ({min; max}, elem_type)) = +let alloc (TableType ({min; max}, elem_type)) r = assert (within_limits min max); - {content = create min; max; elem_type} + {content = create min r; max; elem_type} let size tab = Lib.Array32.length tab.content @@ -32,12 +33,12 @@ let size tab = let type_of tab = TableType ({min = size tab; max = tab.max}, tab.elem_type) -let grow tab delta = +let grow tab delta r = let old_size = size tab in let new_size = Int32.add old_size delta in if I32.gt_u old_size new_size then raise SizeOverflow else if not (within_limits new_size tab.max) then raise SizeLimit else - let after = create new_size in + let after = create new_size r in Array.blit tab.content 0 after 0 (Array.length tab.content); tab.content <- after diff --git a/interpreter/runtime/table.mli b/interpreter/runtime/table.mli index ae5fe691..b07b3980 100644 --- a/interpreter/runtime/table.mli +++ b/interpreter/runtime/table.mli @@ -11,11 +11,13 @@ exception Type exception Bounds exception SizeOverflow exception SizeLimit +exception OutOfMemory -val alloc : table_type -> table +val alloc : table_type -> ref_ -> table (* raises OutOfMemory *) val type_of : table -> table_type val size : table -> size -val grow : table -> size -> unit (* raises SizeOverflow, SizeLimit *) +val grow : table -> size -> ref_ -> unit + (* raises SizeOverflow, SizeLimit, OutOfMemory *) val load : table -> index -> ref_ (* raises Bounds *) val store : table -> index -> ref_ -> unit (* raises Type, Bounds *) diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index da0563cd..0f57ba31 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -89,6 +89,9 @@ and instr' = | GlobalSet of var (* write global variable *) | TableGet of var (* read table element *) | TableSet of var (* write table element *) + | TableSize of var (* size of table *) + | TableGrow of var (* grow table *) + | TableFill of var (* fill table with unique value *) | Load of loadop (* read memory at address *) | Store of storeop (* write memory at address *) | MemorySize (* size of linear memory *) diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index c77d1e42..3fb6a4b4 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -10,8 +10,8 @@ let i64_const n = Const (I64 n.it @@ n.at) let f32_const n = Const (F32 n.it @@ n.at) let f64_const n = Const (F64 n.it @@ n.at) let ref_null = RefNull -let ref_is_null = RefIsNull let ref_func x = RefFunc x +let ref_is_null = RefIsNull let unreachable = Unreachable let nop = Nop @@ -33,8 +33,12 @@ let local_set x = LocalSet x let local_tee x = LocalTee x let global_get x = GlobalGet x let global_set x = GlobalSet x + let table_get x = TableGet x let table_set x = TableSet x +let table_size x = TableSize x +let table_grow x = TableGrow x +let table_fill x = TableFill x let i32_load align offset = Load {ty = I32Type; align; offset; sz = None} let i64_load align offset = Load {ty = I64Type; align; offset; sz = None} diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index 2d97083a..e2b7adae 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -245,6 +245,9 @@ let rec instr e = | GlobalSet x -> "global.set " ^ var x, [] | TableGet x -> "table.get " ^ var x, [] | TableSet x -> "table.set " ^ var x, [] + | TableSize x -> "table.size " ^ var x, [] + | TableGrow x -> "table.grow " ^ var x, [] + | TableFill x -> "table.fill " ^ var x, [] | Load op -> loadop op, [] | Store op -> storeop op, [] | MemorySize -> "memory.size", [] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index b9f253b5..3b7ec2ca 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -204,8 +204,12 @@ rule token = parse | "local.tee" { LOCAL_TEE } | "global.get" { GLOBAL_GET } | "global.set" { GLOBAL_SET } + | "table.get" { TABLE_GET } | "table.set" { TABLE_SET } + | "table.size" { TABLE_SIZE } + | "table.grow" { TABLE_GROW } + | "table.fill" { TABLE_FILL } | (nxx as t)".load" { LOAD (fun a o -> diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index df2c3f85..576e0dd1 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -148,13 +148,15 @@ let inline_type_explicit (c : context) x ft at = %token LPAR RPAR %token NAT INT FLOAT STRING VAR %token ANYREF FUNCREF NUM_TYPE MUT -%token NOP DROP BLOCK END IF THEN ELSE SELECT LOOP BR BR_IF BR_TABLE +%token UNREACHABLE NOP DROP SELECT +%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE %token CALL CALL_INDIRECT RETURN -%token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET TABLE_GET TABLE_SET +%token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET +%token TABLE_GET TABLE_SET TABLE_SIZE TABLE_GROW TABLE_FILL +%token MEMORY_SIZE MEMORY_GROW %token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT -%token REF_NULL REF_FUNC REF_HOST REF_IS_NULL %token CONST UNARY BINARY TEST COMPARE CONVERT -%token UNREACHABLE MEMORY_SIZE MEMORY_GROW +%token REF_NULL REF_FUNC REF_HOST REF_IS_NULL %token FUNC START TYPE PARAM RESULT LOCAL GLOBAL %token TABLE ELEM MEMORY DATA OFFSET IMPORT EXPORT TABLE %token MODULE BIN QUOTE @@ -326,6 +328,9 @@ plain_instr : | GLOBAL_SET var { fun c -> global_set ($2 c global) } | TABLE_GET var { fun c -> table_get ($2 c table) } | TABLE_SET var { fun c -> table_set ($2 c table) } + | TABLE_SIZE var { fun c -> table_size ($2 c table) } + | TABLE_GROW var { fun c -> table_grow ($2 c table) } + | TABLE_FILL var { fun c -> table_fill ($2 c table) } | LOAD offset_opt align_opt { fun c -> $1 $3 $2 } | STORE offset_opt align_opt { fun c -> $1 $3 $2 } | MEMORY_SIZE { fun c -> memory_size } diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index 22dcc63f..5946f689 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -270,7 +270,7 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = [local c x] --> [local c x] | GlobalGet x -> - let GlobalType (t, mut) = global c x in + let GlobalType (t, _mut) = global c x in [] --> [t] | GlobalSet x -> @@ -279,13 +279,25 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = [t] --> [] | TableGet x -> - let TableType (lim, t) = table c x in + let TableType (_lim, t) = table c x in [NumType I32Type] --> [RefType t] | TableSet x -> - let TableType (lim, t) = table c x in + let TableType (_lim, t) = table c x in [NumType I32Type; RefType t] --> [] + | TableSize x -> + let _tt = table c x in + [] --> [NumType I32Type] + + | TableGrow x -> + let TableType (_lim, t) = table c x in + [RefType t; NumType I32Type] --> [NumType I32Type] + + | TableFill x -> + let TableType (_lim, t) = table c x in + [NumType I32Type; RefType t; NumType I32Type] --> [] + | Load memop -> check_memop c memop (Lib.Option.map fst) e.at; [NumType I32Type] --> [NumType memop.ty] diff --git a/proposals/reference-types/Overview.md b/proposals/reference-types/Overview.md index a1257160..44b94ae6 100644 --- a/proposals/reference-types/Overview.md +++ b/proposals/reference-types/Overview.md @@ -14,6 +14,7 @@ Motivation: - allow representing data structures containing references by repurposing tables as a general memory for opaque data types - allow manipulating function tables from within Wasm. + - add instructions missing from [bulk operations proposal](https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md) * Set the stage for later additions: @@ -31,6 +32,8 @@ Summary: * Introduce instructions to get and set table slots. +* Add missing table size, grow, fill instructions. + * Allow multiple tables. Notes: @@ -59,9 +62,9 @@ Typing extensions: * Introduce a simple subtype relation between reference types. - reflexive transitive closure of the following rules - - `t < anyref` for all reftypes `t` - - `nullref < anyref` and `nullref < funcref` - - Note: No rule `nullref < t` for all reftypes `t` -- while that is derivable from the above given the current set of types it might not hold for future reference types which don't allow null. + - `t <: anyref` for all reftypes `t` + - `nullref <: anyref` and `nullref <: funcref` + - Note: No rule `nullref <: t` for all reftypes `t` -- while that is derivable from the above given the current set of types it might not hold for future reference types which don't allow null. New/extended instructions: @@ -74,17 +77,51 @@ New/extended instructions: - `ref.is_null : [anyref] -> [i32]` * The new instruction `ref.func` creates a reference to a given function. - - `ref.func $x : [] -> [funcref]` iff `$x` is a function + - `ref.func $x : [] -> [funcref]` + - iff `$x : func $t` + - allowed in constant expressions + - Note: the result type of this instruction may be refined by future proposals (e.g., to `[(ref $t)]`) * The new instructions `table.get` and `table.set` access tables. - - `table.get $x : [i32] -> [t]` iff `t` is the element type of table `$x` - - `table.set $x : [i32 t] -> []` iff `t` is the element type of table `$x` - - `table.fill $x : [i32 i32 t] -> []` iff `t` is the element type of table `$x` + - `table.get $x : [i32] -> [t]` + - iff `$x : table t` + - `table.set $x : [i32 t] -> []` + - iff `$x : table t` + +* The new instructions `table.size`and `table.grow` manipulate the size of a table. + - `table.size $x : [] -> [i32]` + - iff `$x : table t` + - `table.grow $x : [t i32] -> [i32]` + - iff `$x : table t` + - the first operand of `table.grow` is an initialisation value (for compatibility with future extensions to the type system, such as non-nullable references) + +* 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 `table.init` instruction takes an additional table index as immediate. + - `table.init $x $y : [i32 i32 i32] -> []` + - iff `$x : table t` + - and `$y : elem t'` + - and `t' <: t` + +* The `table.copy` instruction takes two additional table indices as immediate. + - `table.copy $x $y : [i32 i32 i32] -> []` + - iff `$x : table t` + - and `$y : table t'` + - and `t' <: t` + +* The `call_indirect` instruction takes a table index as immediate. + - `call_indirect (type $t) $x : [t1* i32] -> [t2*]` + - iff `$t = [t1*] -> [t2*]` + - and `$x : table t'` + - and `t' <: funcref` -* The `call_indirect` instruction takes a table index as immediate that identifies the table it calls through. - - `call_indirect (type $t) $x : [t1* i32] -> [t2*]` iff `$t` denotes the function type `[t1*] -> [t2*]` and the element type of table `$x` is a subtype of `funcref`. - - In the binary format, space for the index is already reserved. - - For backwards compatibility, the index may be omitted in the text format, in which case it defaults to 0. +Note: +- In the binary format, space for the additional table indices is already reserved. +- For backwards compatibility, all table indices may be omitted in the text format, in which case they default to 0 (for `table.copy`, both indices must be either present or absent). Table extensions: diff --git a/test/core/memory_grow.wast b/test/core/memory_grow.wast index 8a87c77c..aa56297d 100644 --- a/test/core/memory_grow.wast +++ b/test/core/memory_grow.wast @@ -312,8 +312,8 @@ (assert_invalid (module (memory 0) - (func $type-size-empty - (memory.grow) (drop) + (func $type-size-empty-vs-i32 (result i32) + (memory.grow) ) ) "type mismatch" @@ -321,9 +321,9 @@ (assert_invalid (module (memory 0) - (func $type-size-empty-in-block + (func $type-size-empty-vs-i32-in-block (result i32) (i32.const 0) - (block (memory.grow) (drop)) + (block (result i32) (memory.grow)) ) ) "type mismatch" @@ -331,9 +331,9 @@ (assert_invalid (module (memory 0) - (func $type-size-empty-in-loop + (func $type-size-empty-vs-i32-in-loop (result i32) (i32.const 0) - (loop (memory.grow) (drop)) + (loop (result i32) (memory.grow)) ) ) "type mismatch" @@ -341,15 +341,39 @@ (assert_invalid (module (memory 0) - (func $type-size-empty-in-then + (func $type-size-empty-vs-i32-in-then (result i32) (i32.const 0) (i32.const 0) - (if (then (memory.grow) (drop))) + (if (result i32) (then (memory.grow))) ) ) "type mismatch" ) +(assert_invalid + (module + (memory 1) + (func $type-size-f32-vs-i32 (result i32) + (memory.grow (f32.const 0)) + ) + ) + "type mismatch" +) -;; Type check - -(assert_invalid (module (memory 1) (func (result i32) (memory.grow (f32.const 0)))) "type mismatch") +(assert_invalid + (module + (memory 1) + (func $type-result-i32-vs-empty + (memory.grow (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (memory 1) + (func $type-result-i32-vs-f32 (result f32) + (memory.grow (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/test/core/table_fill.wast b/test/core/table_fill.wast new file mode 100644 index 00000000..8ec66b69 --- /dev/null +++ b/test/core/table_fill.wast @@ -0,0 +1,153 @@ +(module + (table $t 10 anyref) + + (func (export "fill") (param $i i32) (param $r anyref) (param $n i32) + (table.fill $t (local.get $i) (local.get $r) (local.get $n)) + ) + + (func (export "get") (param $i i32) (result anyref) + (table.get $t (local.get $i)) + ) +) + +(assert_return (invoke "get" (i32.const 1)) (ref.null)) +(assert_return (invoke "get" (i32.const 2)) (ref.null)) +(assert_return (invoke "get" (i32.const 3)) (ref.null)) +(assert_return (invoke "get" (i32.const 4)) (ref.null)) +(assert_return (invoke "get" (i32.const 5)) (ref.null)) + +(assert_return (invoke "fill" (i32.const 2) (ref.host 1) (i32.const 3))) +(assert_return (invoke "get" (i32.const 1)) (ref.null)) +(assert_return (invoke "get" (i32.const 2)) (ref.host 1)) +(assert_return (invoke "get" (i32.const 3)) (ref.host 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.host 1)) +(assert_return (invoke "get" (i32.const 5)) (ref.null)) + +(assert_return (invoke "fill" (i32.const 4) (ref.host 2) (i32.const 2))) +(assert_return (invoke "get" (i32.const 3)) (ref.host 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.host 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.host 2)) +(assert_return (invoke "get" (i32.const 6)) (ref.null)) + +(assert_return (invoke "fill" (i32.const 4) (ref.host 3) (i32.const 0))) +(assert_return (invoke "get" (i32.const 3)) (ref.host 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.host 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.host 2)) + +(assert_return (invoke "fill" (i32.const 8) (ref.host 4) (i32.const 2))) +(assert_return (invoke "get" (i32.const 7)) (ref.null)) +(assert_return (invoke "get" (i32.const 8)) (ref.host 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.host 4)) + +(assert_return (invoke "fill" (i32.const 9) (ref.null) (i32.const 1))) +(assert_return (invoke "get" (i32.const 8)) (ref.host 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.null)) + +(assert_return (invoke "fill" (i32.const 10) (ref.host 5) (i32.const 0))) +(assert_return (invoke "get" (i32.const 9)) (ref.null)) + +(assert_trap + (invoke "fill" (i32.const 8) (ref.host 6) (i32.const 3)) + "out of bounds" +) +(assert_return (invoke "get" (i32.const 7)) (ref.null)) +(assert_return (invoke "get" (i32.const 8)) (ref.host 6)) +(assert_return (invoke "get" (i32.const 9)) (ref.host 6)) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null) (i32.const 0)) + "out of bounds" +) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null) (i32.const 10)) + "out of bounds" +) + + +;; Type errors + +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-value-length-empty-vs-i32-i32 + (table.fill $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-empty-vs-i32 + (table.fill $t (ref.null) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-value-empty-vs + (table.fill $t (i32.const 1) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-length-empty-vs-i32 + (table.fill $t (i32.const 1) (ref.null)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 anyref) + (func $type-index-f32-vs-i32 + (table.fill $t (f32.const 1) (ref.null) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-value-vs-funcref (param $r anyref) + (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 anyref) + (func $type-length-f32-vs-i32 + (table.fill $t (i32.const 1) (ref.null) (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 anyref) + (table $t2 1 funcref) + (func $type-value-anyref-vs-funcref-multi (param $r anyref) + (table.fill $t2 (i32.const 0) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 anyref) + (func $type-result-empty-vs-num (result i32) + (table.fill $t (i32.const 0) (ref.null) (i32.const 1)) + ) + ) + "type mismatch" +) diff --git a/test/core/table_get.wast b/test/core/table_get.wast index bd0c30c9..02855a83 100644 --- a/test/core/table_get.wast +++ b/test/core/table_get.wast @@ -33,3 +33,55 @@ (assert_trap (invoke "get-funcref" (i32.const 3)) "out of bounds") (assert_trap (invoke "get-anyref" (i32.const -1)) "out of bounds") (assert_trap (invoke "get-funcref" (i32.const -1)) "out of bounds") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-empty-vs-i32 (result anyref) + (table.get $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-f32-vs-i32 (result anyref) + (table.get $t (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 anyref) + (func $type-result-anyref-vs-empty + (table.get $t (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-result-anyref-vs-funcref (result funcref) + (table.get $t (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 funcref) + (table $t2 1 anyref) + (func $type-result-anyref-vs-funcref-multi (result funcref) + (table.get $t2 (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/test/core/table_grow.wast b/test/core/table_grow.wast new file mode 100644 index 00000000..bd9c395a --- /dev/null +++ b/test/core/table_grow.wast @@ -0,0 +1,160 @@ +(module + (table $t 0 anyref) + + (func (export "get") (param $i i32) (result anyref) (table.get $t (local.get $i))) + (func (export "set") (param $i i32) (param $r anyref) (table.set $t (local.get $i) (local.get $r))) + + (func (export "grow") (param $sz i32) (param $init anyref) (result i32) + (table.grow $t (local.get $init) (local.get $sz)) + ) + (func (export "size") (result i32) (table.size $t)) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_trap (invoke "set" (i32.const 0) (ref.host 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 0)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 1) (ref.null)) (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "get" (i32.const 0)) (ref.null)) +(assert_return (invoke "set" (i32.const 0) (ref.host 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.host 2)) +(assert_trap (invoke "set" (i32.const 1) (ref.host 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 1)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 4) (ref.host 3)) (i32.const 1)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "get" (i32.const 0)) (ref.host 2)) +(assert_return (invoke "set" (i32.const 0) (ref.host 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.host 2)) +(assert_return (invoke "get" (i32.const 1)) (ref.host 3)) +(assert_return (invoke "get" (i32.const 4)) (ref.host 3)) +(assert_return (invoke "set" (i32.const 4) (ref.host 4))) +(assert_return (invoke "get" (i32.const 4)) (ref.host 4)) +(assert_trap (invoke "set" (i32.const 5) (ref.host 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 5)) "out of bounds table access") + + +(module + (table $t 0 anyref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) + + +(module + (table $t 0 10 anyref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "grow" (i32.const 6)) (i32.const 4)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 10)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "grow" (i32.const 0x10000)) (i32.const -1)) + + +(module + (table $t 10 anyref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null) (local.get 0)) + ) + (func (export "check-table-null") (param i32 i32) (result anyref) + (local anyref) + (local.set 2 (ref.func 1)) + (block + (loop + (local.set 2 (table.get $t (local.get 0))) + (br_if 1 (i32.eqz (ref.is_null (local.get 2)))) + (br_if 1 (i32.ge_u (local.get 0) (local.get 1))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if 0 (i32.le_u (local.get 0) (local.get 1))) + ) + ) + (local.get 2) + ) +) + +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 9)) (ref.null)) +(assert_return (invoke "grow" (i32.const 10)) (i32.const 10)) +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 19)) (ref.null)) + + +;; Type errors + +(assert_invalid + (module + (table $t 0 anyref) + (func $type-init-size-empty-vs-i32-anyref (result i32) + (table.grow $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 anyref) + (func $type-size-empty-vs-i32 (result i32) + (table.grow $t (ref.null)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 anyref) + (func $type-init-empty-vs-anyref (result i32) + (table.grow $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 anyref) + (func $type-size-f32-vs-i32 (result i32) + (table.grow $t (ref.null) (f32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-init-anyref-vs-funcref (param $r anyref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 anyref) + (func $type-result-i32-vs-empty + (table.grow $t (ref.null) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 1 anyref) + (func $type-result-i32-vs-f32 (result f32) + (table.grow $t (ref.null) (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/test/core/table_set.wast b/test/core/table_set.wast index 9cb231b5..5d2a6a26 100644 --- a/test/core/table_set.wast +++ b/test/core/table_set.wast @@ -46,3 +46,73 @@ (assert_trap (invoke "set-funcref-from" (i32.const 3) (i32.const 1)) "out of bounds") (assert_trap (invoke "set-anyref" (i32.const -1) (ref.host 0)) "out of bounds") (assert_trap (invoke "set-funcref-from" (i32.const -1) (i32.const 1)) "out of bounds") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-value-empty-vs-i32-anyref + (table.set $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-index-empty-vs-i32 + (table.set $t (ref.null)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-value-empty-vs-anyref + (table.set $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 anyref) + (func $type-size-f32-vs-i32 + (table.set $t (f32.const 1) (ref.null)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 funcref) + (func $type-value-anyref-vs-funcref (param $r anyref) + (table.set $t (i32.const 1) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 anyref) + (table $t2 1 funcref) + (func $type-value-anyref-vs-funcref-multi (param $r anyref) + (table.set $t2 (i32.const 0) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 anyref) + (func $type-result-empty-vs-num (result i32) + (table.set $t (i32.const 0) (ref.null)) + ) + ) + "type mismatch" +) diff --git a/test/core/table_size.wast b/test/core/table_size.wast new file mode 100644 index 00000000..5817104a --- /dev/null +++ b/test/core/table_size.wast @@ -0,0 +1,86 @@ +(module + (table $t0 0 anyref) + (table $t1 1 anyref) + (table $t2 0 2 anyref) + (table $t3 3 8 anyref) + + (func (export "size-t0") (result i32) (table.size $t0)) + (func (export "size-t1") (result i32) (table.size $t1)) + (func (export "size-t2") (result i32) (table.size $t2)) + (func (export "size-t3") (result i32) (table.size $t3)) + + (func (export "grow-t0") (param $sz i32) + (drop (table.grow $t0 (ref.null) (local.get $sz))) + ) + (func (export "grow-t1") (param $sz i32) + (drop (table.grow $t1 (ref.null) (local.get $sz))) + ) + (func (export "grow-t2") (param $sz i32) + (drop (table.grow $t2 (ref.null) (local.get $sz))) + ) + (func (export "grow-t3") (param $sz i32) + (drop (table.grow $t3 (ref.null) (local.get $sz))) + ) +) + +(assert_return (invoke "size-t0") (i32.const 0)) +(assert_return (invoke "grow-t0" (i32.const 1))) +(assert_return (invoke "size-t0") (i32.const 1)) +(assert_return (invoke "grow-t0" (i32.const 4))) +(assert_return (invoke "size-t0") (i32.const 5)) +(assert_return (invoke "grow-t0" (i32.const 0))) +(assert_return (invoke "size-t0") (i32.const 5)) + +(assert_return (invoke "size-t1") (i32.const 1)) +(assert_return (invoke "grow-t1" (i32.const 1))) +(assert_return (invoke "size-t1") (i32.const 2)) +(assert_return (invoke "grow-t1" (i32.const 4))) +(assert_return (invoke "size-t1") (i32.const 6)) +(assert_return (invoke "grow-t1" (i32.const 0))) +(assert_return (invoke "size-t1") (i32.const 6)) + +(assert_return (invoke "size-t2") (i32.const 0)) +(assert_return (invoke "grow-t2" (i32.const 3))) +(assert_return (invoke "size-t2") (i32.const 0)) +(assert_return (invoke "grow-t2" (i32.const 1))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 0))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 4))) +(assert_return (invoke "size-t2") (i32.const 1)) +(assert_return (invoke "grow-t2" (i32.const 1))) +(assert_return (invoke "size-t2") (i32.const 2)) + +(assert_return (invoke "size-t3") (i32.const 3)) +(assert_return (invoke "grow-t3" (i32.const 1))) +(assert_return (invoke "size-t3") (i32.const 4)) +(assert_return (invoke "grow-t3" (i32.const 3))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 0))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 2))) +(assert_return (invoke "size-t3") (i32.const 7)) +(assert_return (invoke "grow-t3" (i32.const 1))) +(assert_return (invoke "size-t3") (i32.const 8)) + + +;; Type errors + +(assert_invalid + (module + (table $t 1 anyref) + (func $type-result-i32-vs-empty + (table.size $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 1 anyref) + (func $type-result-i32-vs-f32 (result f32) + (table.size $t) + ) + ) + "type mismatch" +)