diff --git a/document/core/appendix/changes.rst b/document/core/appendix/changes.rst index 7e0e6459fb..1221518f77 100644 --- a/document/core/appendix/changes.rst +++ b/document/core/appendix/changes.rst @@ -462,6 +462,8 @@ Added more precise types for references. [#proposal-typedref]_ * Refined typing of :ref:`local instructions ` and :ref:`instruction sequences ` to track the :ref:`initialization status ` of :ref:`locals ` with non-:ref:`defaultable ` type +* Refined decoding of :ref:`active ` :ref:`element segments ` with implicit element type and plain function indices (opcode :math:`0`) to produce :ref:`non-nullable ` :ref:`reference type `. + * Extended :ref:`table definitions ` with optional initializer expression diff --git a/document/core/appendix/index-instructions.py b/document/core/appendix/index-instructions.py index c39fc4a901..3a0a0e434a 100755 --- a/document/core/appendix/index-instructions.py +++ b/document/core/appendix/index-instructions.py @@ -334,7 +334,7 @@ def Instruction(name, opcode, type=None, validation=None, execution=None, operat Instruction(r'\STRUCTGETS~x~y', r'\hex{FB}~\hex{03}', r'[(\REF~\NULL~x)] \to [\I32]', r'valid-struct.get', r'exec-struct.get'), Instruction(r'\STRUCTGETU~x~y', r'\hex{FB}~\hex{04}', r'[(\REF~\NULL~x)] \to [\I32]', r'valid-struct.get', r'exec-struct.get'), Instruction(r'\STRUCTSET~x~y', r'\hex{FB}~\hex{05}', r'[(\REF~\NULL~x)~t] \to []', r'valid-struct.set', r'exec-struct.set'), - Instruction(r'\ARRAYNEW~x', r'\hex{FB}~\hex{06}', r'[t] \to [(\REF~x)]', r'valid-array.new', r'exec-array.new'), + Instruction(r'\ARRAYNEW~x', r'\hex{FB}~\hex{06}', r'[t~\I32] \to [(\REF~x)]', r'valid-array.new', r'exec-array.new'), Instruction(r'\ARRAYNEWDEFAULT~x', r'\hex{FB}~\hex{07}', r'[\I32] \to [(\REF~x)]', r'valid-array.new', r'exec-array.new'), Instruction(r'\ARRAYNEWFIXED~x~n', r'\hex{FB}~\hex{08}', r'[t^n] \to [(\REF~x)]', r'valid-array.new_fixed', r'exec-array.new_fixed'), Instruction(r'\ARRAYNEWDATA~x~y', r'\hex{FB}~\hex{09}', r'[\I32~\I32] \to [(\REF~x)]', r'valid-array.new_data', r'exec-array.new_data'), diff --git a/document/core/exec/instructions.rst b/document/core/exec/instructions.rst index 25eb52ab95..9019bef4bc 100644 --- a/document/core/exec/instructions.rst +++ b/document/core/exec/instructions.rst @@ -4753,7 +4753,7 @@ Control Instructions \begin{array}[t]{@{}r@{~}l@{}} (\iff & S.\STABLES[F.\AMODULE.\MITABLES[x]].\TIELEM[i] = \REFFUNCADDR~a \\ \wedge & S.\SFUNCS[a] = f \\ - \wedge & S \vdashdeftypematch F.\AMODULE.\MITYPES[y] \matchesdeftype f.\FITYPE) + \wedge & S \vdashdeftypematch f.\FITYPE \matchesdeftype F.\AMODULE.\MITYPES[y]) \end{array} \\[1ex] \begin{array}{lcl@{\qquad}l} diff --git a/test/core/gc/array_new_data.wast b/test/core/gc/array_new_data.wast new file mode 100644 index 0000000000..2af894c169 --- /dev/null +++ b/test/core/gc/array_new_data.wast @@ -0,0 +1,68 @@ +(module + (type $arr (array (mut i8))) + + (data $d "abcd") + + (func (export "array-new-data") (param i32 i32) (result (ref $arr)) + (array.new_data $arr $d (local.get 0) (local.get 1)) + ) +) + +;; In-bounds data segment accesses. +(assert_return (invoke "array-new-data" (i32.const 0) (i32.const 0)) (ref.array)) +(assert_return (invoke "array-new-data" (i32.const 0) (i32.const 4)) (ref.array)) +(assert_return (invoke "array-new-data" (i32.const 1) (i32.const 2)) (ref.array)) +(assert_return (invoke "array-new-data" (i32.const 4) (i32.const 0)) (ref.array)) + +;; Out-of-bounds data segment accesses. +(assert_trap (invoke "array-new-data" (i32.const 0) (i32.const 5)) "out of bounds memory access") +(assert_trap (invoke "array-new-data" (i32.const 5) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "array-new-data" (i32.const 1) (i32.const 4)) "out of bounds memory access") +(assert_trap (invoke "array-new-data" (i32.const 4) (i32.const 1)) "out of bounds memory access") + + +(module + (type $arr (array (mut i8))) + + (data $d "\aa\bb\cc\dd") + + (func (export "array-new-data-contents") (result i32 i32) + (local (ref $arr)) + (local.set 0 (array.new_data $arr $d (i32.const 1) (i32.const 2))) + (array.get_u $arr (local.get 0) (i32.const 0)) + (array.get_u $arr (local.get 0) (i32.const 1)) + ) +) + +;; Array is initialized with the correct contents. +(assert_return (invoke "array-new-data-contents") (i32.const 0xbb) (i32.const 0xcc)) + +(module + (type $arr (array (mut i32))) + + (data $d "\aa\bb\cc\dd") + + (func (export "array-new-data-little-endian") (result i32) + (array.get $arr + (array.new_data $arr $d (i32.const 0) (i32.const 1)) + (i32.const 0)) + ) +) + +;; Data segments are interpreted as little-endian. +(assert_return (invoke "array-new-data-little-endian") (i32.const 0xddccbbaa)) + +(module + (type $arr (array (mut i16))) + + (data $d "\00\11\22") + + (func (export "array-new-data-unaligned") (result i32) + (array.get_u $arr + (array.new_data $arr $d (i32.const 1) (i32.const 1)) + (i32.const 0)) + ) +) + +;; Data inside the segment doesn't need to be aligned to the element size. +(assert_return (invoke "array-new-data-unaligned") (i32.const 0x2211)) diff --git a/test/core/gc/array_new_elem.wast b/test/core/gc/array_new_elem.wast new file mode 100644 index 0000000000..d71d981d63 --- /dev/null +++ b/test/core/gc/array_new_elem.wast @@ -0,0 +1,103 @@ +;;;; Expression-style element segments. + +(module + (type $arr (array i31ref)) + + (elem $e i31ref + (ref.i31 (i32.const 0xaa)) + (ref.i31 (i32.const 0xbb)) + (ref.i31 (i32.const 0xcc)) + (ref.i31 (i32.const 0xdd))) + + (func (export "array-new-elem") (param i32 i32) (result (ref $arr)) + (array.new_elem $arr $e (local.get 0) (local.get 1)) + ) +) + +;; In-bounds element segment accesses. +(assert_return (invoke "array-new-elem" (i32.const 0) (i32.const 0)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 0) (i32.const 4)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 1) (i32.const 2)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 4) (i32.const 0)) (ref.array)) + +;; Out-of-bounds element segment accesses. +(assert_trap (invoke "array-new-elem" (i32.const 0) (i32.const 5)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 5) (i32.const 0)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 1) (i32.const 4)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 4) (i32.const 1)) "out of bounds table access") + +(module + (type $arr (array i31ref)) + + (elem $e i31ref + (ref.i31 (i32.const 0xaa)) + (ref.i31 (i32.const 0xbb)) + (ref.i31 (i32.const 0xcc)) + (ref.i31 (i32.const 0xdd))) + + (func (export "array-new-elem-contents") (result i32 i32) + (local (ref $arr)) + (local.set 0 (array.new_elem $arr $e (i32.const 1) (i32.const 2))) + (i31.get_u (array.get $arr (local.get 0) (i32.const 0))) + (i31.get_u (array.get $arr (local.get 0) (i32.const 1))) + ) +) + +;; Array is initialized with the correct contents. +(assert_return (invoke "array-new-elem-contents") (i32.const 0xbb) (i32.const 0xcc)) + +;;;; MVP-style function-index segments. + +(module + (type $arr (array funcref)) + + (elem $e func $aa $bb $cc $dd) + (func $aa (result i32) (i32.const 0xaa)) + (func $bb (result i32) (i32.const 0xbb)) + (func $cc (result i32) (i32.const 0xcc)) + (func $dd (result i32) (i32.const 0xdd)) + + (func (export "array-new-elem") (param i32 i32) (result (ref $arr)) + (array.new_elem $arr $e (local.get 0) (local.get 1)) + ) +) + +;; In-bounds element segment accesses. +(assert_return (invoke "array-new-elem" (i32.const 0) (i32.const 0)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 0) (i32.const 4)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 1) (i32.const 2)) (ref.array)) +(assert_return (invoke "array-new-elem" (i32.const 4) (i32.const 0)) (ref.array)) + +;; Out-of-bounds element segment accesses. +(assert_trap (invoke "array-new-elem" (i32.const 0) (i32.const 5)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 5) (i32.const 0)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 1) (i32.const 4)) "out of bounds table access") +(assert_trap (invoke "array-new-elem" (i32.const 4) (i32.const 1)) "out of bounds table access") + +(module + (type $f (func (result i32))) + (type $arr (array funcref)) + + (elem $e func $aa $bb $cc $dd) + (func $aa (result i32) (i32.const 0xaa)) + (func $bb (result i32) (i32.const 0xbb)) + (func $cc (result i32) (i32.const 0xcc)) + (func $dd (result i32) (i32.const 0xdd)) + + (table $t 2 2 funcref) + + (func (export "array-new-elem-contents") (result i32 i32) + (local (ref $arr)) + (local.set 0 (array.new_elem $arr $e (i32.const 1) (i32.const 2))) + + (table.set $t (i32.const 0) (array.get $arr (local.get 0) (i32.const 0))) + (table.set $t (i32.const 1) (array.get $arr (local.get 0) (i32.const 1))) + + (call_indirect (type $f) (i32.const 0)) + (call_indirect (type $f) (i32.const 1)) + + ) +) + +;; Array is initialized with the correct contents. +(assert_return (invoke "array-new-elem-contents") (i32.const 0xbb) (i32.const 0xcc)) diff --git a/test/core/gc/i31.wast b/test/core/gc/i31.wast index 7485650454..6309e72b65 100644 --- a/test/core/gc/i31.wast +++ b/test/core/gc/i31.wast @@ -14,7 +14,7 @@ (i31.get_u (ref.null i31)) ) (func (export "get_s-null") (result i32) - (i31.get_u (ref.null i31)) + (i31.get_s (ref.null i31)) ) (global $i (ref i31) (ref.i31 (i32.const 2))) diff --git a/test/core/gc/type-subtyping.wast b/test/core/gc/type-subtyping.wast index f2b33d7c49..891aa31ad0 100644 --- a/test/core/gc/type-subtyping.wast +++ b/test/core/gc/type-subtyping.wast @@ -824,6 +824,70 @@ "sub type" ) +(assert_invalid + (module + (type $a (sub (array (ref none)))) + (type $b (sub $a (array (ref any)))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (array (mut (ref any))))) + (type $b (sub $a (array (mut (ref none))))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (array (mut (ref any))))) + (type $b (sub $a (array (ref any)))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (array (ref any)))) + (type $b (sub $a (array (mut (ref any))))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (struct (field (ref none))))) + (type $b (sub $a (struct (field (ref any))))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (struct (field (mut (ref any)))))) + (type $b (sub $a (struct (field (mut (ref none)))))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (struct (field (mut (ref any)))))) + (type $b (sub $a (struct (field (ref any))))) + ) + "sub type 1 does not match super type" +) + +(assert_invalid + (module + (type $a (sub (struct (field (ref any))))) + (type $b (sub $a (struct (field (mut (ref any)))))) + ) + "sub type 1 does not match super type" +) + (assert_invalid (module (type $f0 (sub (func)))