| ;; This file tests validation only, focusing on GC types and subtyping. |
| |
| ;;;; |
| ;;;;; WasmFX types |
| ;;;; |
| |
| |
| (module |
| (type $f (func)) |
| |
| (type $ft1 (sub (func (param (ref $f)) (result (ref func))))) |
| (type $ct1 (sub (cont $ft1))) |
| |
| (type $ft2 (func (param (ref any)) (result (ref func)))) |
| (type $ct2 (cont $ft2)) |
| |
| (type $ft3 (sub $ft1 (func (param (ref func)) (result (ref $f))))) |
| (type $ct3 (cont $ft3)) |
| |
| ;; Okay: Continuation types are covariant, have declared $ft3 <: $ft1 |
| (type $ct_sub (sub $ct1 (cont $ft3))) |
| |
| (func $test |
| (param $p1 (ref $ct1)) |
| (param $p2 (ref $ct_sub)) |
| |
| ;; Okay: (ref $ct_sub) <: (ref $ct1) |
| (local.set $p1 (local.get $p2)) |
| |
| ) |
| |
| ) |
| |
| (assert_invalid |
| (module |
| (type $f (func)) |
| |
| (type $ft1 (sub (func (param (ref $f)) (result (ref func))))) |
| (type $ct1 (sub (cont $ft1))) |
| |
| (type $ft2 (func (param (ref func)) (result (ref $f)))) |
| |
| ;; Error: $ft2 and ft1 have compatible types, but have not declared $ft2 <: ft1 |
| (type $error (sub $ct1 (cont $ft2))) |
| |
| ) |
| "sub type 4 does not match super type 2" |
| ) |
| |
| (assert_invalid |
| (module |
| (type $f (func)) |
| |
| (type $ft1 (sub (func (param (ref $f)) (result (ref func))))) |
| (type $ct1 (sub (cont $ft1))) |
| |
| (type $ft2 (func (param (ref func)) (result (ref $f)))) |
| (type $ct2 (cont $ft2)) |
| |
| (func $test |
| (param $p1 (ref $ct1)) |
| (param $p2 (ref $ct2)) |
| |
| ;; Error $ct2 and $ct1 have generally compatible types, |
| ;; but have not declared $ft2 <: ft1 or $ct2 <: $ct1 |
| ;; Thus, $ct2 </: $ct1. |
| (local.set $p1 (local.get $p2)) |
| |
| ) |
| |
| ) |
| "type mismatch" |
| ) |
| |
| (assert_invalid |
| (module |
| (type $f (func)) |
| |
| (type $ft1 (sub (func (param (ref $f)) (result (ref func))))) |
| (type $ct1 (sub (cont $ft1))) |
| (type $ft3 (sub $ft1 (func (param (ref func)) (result (ref $f))))) |
| (type $ct3 (cont $ft3)) |
| |
| (func $error |
| (param $p1 (ref $ct1)) |
| ;;(param $p2 (ref $ct2)) |
| (param $p3 (ref $ct3)) |
| |
| ;; Error $ct3 and $ct1 have generally compatible types, |
| ;; (in particular: declared $ft3 <: ft1, |
| ;; but have not declared $ct3 <: $ct1 |
| ;; $ct3 </: $ct1 |
| (local.set $p1 (local.get $p3)) |
| |
| ) |
| |
| ) |
| "type mismatch" |
| ) |
| |
| (assert_invalid |
| (module |
| |
| (func $error |
| (param $p_any (ref any)) |
| (param $p_cont (ref cont)) |
| |
| ;; Error: cont </: any |
| (local.set $p_any (local.get $p_cont)) |
| ) |
| ) |
| |
| "type mismatch" |
| ) |
| |
| ;;;; |
| ;;;; cont_bind instructions |
| ;;;; |
| |
| (module $non_final |
| |
| (type $ft1 (func (param i32) (result (ref func)))) |
| (type $ct1 (sub (cont $ft1))) |
| |
| (type $ft0 (func (result (ref func)))) |
| (type $ct0 (sub (cont $ft0))) |
| |
| (func $test1 (param $x (ref $ct1)) |
| (i32.const 123) |
| (local.get $x) |
| ;; Smoke test: using non-final types here |
| (cont.bind $ct1 $ct0) |
| (drop) |
| ) |
| ) |
| |
| |
| (module $subtyping0 |
| |
| (type $f (func)) |
| |
| (type $ft0_sup (func (result (ref func)))) |
| (type $ct0_sup (cont $ft0_sup)) |
| |
| (type $ft1 (func (param i32) (result (ref $f)))) |
| (type $ct1 (cont $ft1)) |
| |
| (type $ft0 (func (result (ref $f)))) |
| (type $ct0 (cont $ft0)) |
| |
| |
| (func $test2 (param $x (ref $ct1)) |
| (i32.const 123) |
| (local.get $x) |
| ;; Okay: The most natural second continuation type would be $ct0. |
| ;; But we have (func (result (ref $f))) <: (func (result (ref $func))) |
| ;; This holds without us needing to declare any subtyping relations at all. |
| (cont.bind $ct1 $ct0_sup) |
| (drop) |
| ) |
| |
| ) |
| |
| (module $recursive |
| |
| (rec |
| (type $ft0 (func (result (ref $ct_rec)))) |
| (type $ft1 (func (param i32) (result (ref $ct_rec)))) |
| (type $ct_rec (cont $ft1)) |
| ) |
| (type $ct0 (cont $ft0)) |
| |
| (rec |
| (type $ft0' (func (result (ref $ct_rec')))) |
| (type $ft1' (func (param i32) (result (ref $ct_rec')))) |
| (type $ct_rec' (cont $ft1')) |
| ) |
| (type $ct1 (cont $ft1')) |
| |
| |
| ;; Okay: Some simple test where the types involved in cont.bind |
| ;; are part of a recursion group |
| ;; (more concretely: two equivalent recursion groups) |
| (func $test (param $x (ref $ct1)) |
| (i32.const 123) |
| (local.get $x) |
| (cont.bind $ct1 $ct0) |
| (drop) |
| ) |
| |
| ) |
| |
| (module $recursive_subtyping |
| |
| ;; We define types such that $ft0 <: $ft0_sup and $ct_rec <: $ct_rec_sup |
| (rec |
| (type $ft0_sup (sub (func (result (ref $ct_rec_sup))))) |
| (type $ft0 (sub $ft0_sup (func (result (ref $ct_rec))))) |
| (type $ft1 (sub (func (param i32) (result (ref $ct_rec))))) |
| |
| (type $ct_rec_sup (sub (cont $ft0_sup))) |
| (type $ct_rec (sub $ct_rec_sup (cont $ft0))) |
| ) |
| |
| (type $ct0_sup (cont $ft0_sup)) |
| (type $ct0 (cont $ft0)) |
| (type $ct1 (cont $ft1)) |
| |
| |
| (func $test (param $x (ref $ct1)) |
| (i32.const 123) |
| (local.get $x) |
| (cont.bind $ct1 $ct0) |
| (drop) |
| |
| (i32.const 123) |
| (local.get $x) |
| ;; Okay: We have (func (result (ref $ct_rec))) <: (func (result (ref |
| ;; $ct_rec_sup))) |
| (cont.bind $ct1 $ct0_sup) |
| (drop) |
| ) |
| |
| ) |
| |
| ;;;; |
| ;;;; resume instructions |
| ;;;; |
| |
| |
| (module $subtyping_resume0 |
| |
| (type $ft0 (func)) |
| (type $ct0 (cont $ft0)) |
| |
| (type $ft_sup (func (param (ref $ft0)))) |
| (type $ct_sup (cont $ft_sup)) |
| |
| (type $ft_sub (func (param (ref func)))) |
| (type $ct_sub (cont $ft_sub)) ;; unused |
| |
| (tag $t (result (ref func))) |
| |
| (func $test0 |
| (param $p (ref $ct0)) |
| |
| ;; Here we test subtyping with respect to the continuations received by handlers. |
| ;; |
| ;; The most "straightforward" type of the continuation in $handler would be (ref |
| ;; $ct_sub). Instead, we use $ct_sup. According to the spec, we neither need |
| ;; to declare $ft_sub <: $ft_sup or $ct_sub <: $ct_sup for this to work. We |
| ;; have (func (param (ref func))) <: (func (param (ref $ft0))), which is |
| ;; sufficient |
| (block $handler (result (ref $ct_sup)) |
| (local.get $p) |
| (resume $ct0 (on $t $handler)) |
| (return) |
| ) |
| (unreachable) |
| ) |
| ) |
| |
| (module $subtyping_resume1 |
| |
| (type $ft0 (func)) |
| (type $ct0 (cont $ft0)) |
| |
| (tag $t (param (ref $ft0))) |
| |
| (func $test0 |
| (param $p (ref $ct0)) |
| |
| ;; Here we test subtyping with respect to the payloads received by handlers. |
| ;; Instead of just (ref $ft0), then handler takes func. |
| (block $handler (result (ref func) (ref $ct0)) |
| (local.get $p) |
| (resume $ct0 (on $t $handler)) |
| (return) |
| ) |
| (unreachable) |
| ) |
| |
| ) |
| |
| (assert_invalid |
| (module |
| |
| (type $ft0 (func)) |
| (type $ct0 (cont $ft0)) |
| |
| (type $ft_sup (sub (func (param (ref $ft0))))) |
| (type $ct_sup (sub (cont $ft_sup))) |
| |
| (type $ft_sub (sub $ft_sup (func (param (ref func))))) |
| (type $ct_sub (cont $ft_sub)) |
| |
| (tag $t (param (ref $ct_sub))) |
| |
| (func $test0 |
| (param $p (ref $ct0)) |
| |
| ;; This is similar to $subtyping1, but this time we use a continuation as payload. |
| ;; But we did not actually declare $ct_sub <: $ct_sub. |
| ;; |
| ;; This is mostly just to check the following: |
| ;; For the continuation received by every handler, we see through the |
| ;; continuation type and do structural subtyping on the underlying |
| ;; function type. |
| ;; However, for continuations that are just payloads ($ct_sup here), we do |
| ;; ordinary nominal subtyping. |
| (block $handler (result (ref $ct_sup) (ref $ct0)) |
| (local.get $p) |
| (resume $ct0 (on $t $handler)) |
| (return) |
| ) |
| (unreachable) |
| ) |
| ) |
| "type mismatch" |
| ) |