summaryrefslogtreecommitdiff
path: root/test/lit/passes
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-11-18 11:12:02 -0800
committerGitHub <noreply@github.com>2021-11-18 11:12:02 -0800
commitcba41cc227346c8a8357aa06bb1d916663c29dfe (patch)
tree9252d215750283e12ee6cf51ead7f55fde600dda /test/lit/passes
parent03dca9aa1a5a266c53a474aeb3c10a3f0584b25b (diff)
downloadbinaryen-cba41cc227346c8a8357aa06bb1d916663c29dfe.tar.gz
binaryen-cba41cc227346c8a8357aa06bb1d916663c29dfe.tar.bz2
binaryen-cba41cc227346c8a8357aa06bb1d916663c29dfe.zip
[Wasm GC] Update nulls to allow finding better LUBs (#4340)
It is common in GC code to have stuff like this: x = null; .. x = Data(); Nulls in wasm have a type, and if that initial null has say anyref then before this PR we would keep the type of x as anyref. However, while nulls have types, all null values are identical, and so we can in fact change x's type to a nullable reference of Data, by also changing the null's type to something more specific. LUBFinder now has an API that can return the best possible LUB so far, and that can be told to update nulls if we decide that the new LUB is worth using. This updates the passes using LUBFinder to use the new API. Note how TypeRefining becomes simpler because the special logic it had in a subclass of LUBFinder is now part of the main class (it used to remember if there was a null default; LUBFinder now handles both a null default as well as other nulls). This requires some changes to existing tests to avoid them from optimizing using nulls in ways that ends up not testing the original intent. Specifically the dae-gc-refine-params.wast now has calls to get a null of a type, instead of just having a ref.null of that type (which could be optimized now). And dae-gc-refine-return uses locals instead of ref.nulls.
Diffstat (limited to 'test/lit/passes')
-rw-r--r--test/lit/passes/dae-gc-refine-params.wast166
-rw-r--r--test/lit/passes/dae-gc-refine-return.wast283
-rw-r--r--test/lit/passes/local-subtyping.wast49
-rw-r--r--test/lit/passes/type-refining.wast217
4 files changed, 607 insertions, 108 deletions
diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast
index 818c943b2..e7059b0e0 100644
--- a/test/lit/passes/dae-gc-refine-params.wast
+++ b/test/lit/passes/dae-gc-refine-params.wast
@@ -15,12 +15,14 @@
;; NOMNL: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
(type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
+ ;; CHECK: (type ${i32_f32} (struct (field i32) (field f32)))
+
;; CHECK: (type ${f64} (struct (field f64)))
+ ;; NOMNL: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
+
;; NOMNL: (type ${f64} (struct_subtype (field f64) ${}))
(type ${f64} (struct_subtype (field f64) ${}))
- ;; CHECK: (type ${i32_f32} (struct (field i32) (field f32)))
- ;; NOMNL: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
(type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
;; CHECK: (func $call-various-params-no
@@ -81,26 +83,26 @@
;; CHECK: (func $call-various-params-yes
;; CHECK-NEXT: (call $various-params-yes
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $various-params-yes
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (call $get_null_{i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $call-various-params-yes (type $none_=>_none)
;; NOMNL-NEXT: (call $various-params-yes
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: (i32.const 0)
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (call $various-params-yes
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: (i32.const 1)
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (call $get_null_{i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $call-various-params-yes
@@ -108,14 +110,14 @@
;; both of those pairs can be optimized to {i32}.
;; There is also an i32 in the middle, which should not confuse us.
(call $various-params-yes
- (ref.null ${i32})
+ (call $get_null_{i32})
(i32.const 0)
- (ref.null ${i32})
+ (call $get_null_{i32})
)
(call $various-params-yes
- (ref.null ${i32})
+ (call $get_null_{i32})
(i32.const 1)
- (ref.null ${i32_i64})
+ (call $get_null_{i32_i64})
)
)
;; This function is called in ways that *do* allow us to alter the types of
@@ -151,34 +153,34 @@
;; CHECK: (func $call-various-params-set
;; CHECK-NEXT: (call $various-params-set
- ;; CHECK-NEXT: (ref.null ${i32})
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $various-params-set
- ;; CHECK-NEXT: (ref.null ${i32})
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (call $get_null_{i32})
+ ;; CHECK-NEXT: (call $get_null_{i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $call-various-params-set (type $none_=>_none)
;; NOMNL-NEXT: (call $various-params-set
- ;; NOMNL-NEXT: (ref.null ${i32})
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (call $various-params-set
- ;; NOMNL-NEXT: (ref.null ${i32})
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $call-various-params-set
;; The first argument gets {i32} and {i32}; the second {i32} and {i32_i64;
;; both of those pairs can be optimized to {i32}
(call $various-params-set
- (ref.null ${i32})
- (ref.null ${i32})
+ (call $get_null_{i32})
+ (call $get_null_{i32})
)
(call $various-params-set
- (ref.null ${i32})
- (ref.null ${i32_i64})
+ (call $get_null_{i32})
+ (call $get_null_{i32_i64})
)
)
;; This function is called in ways that *do* allow us to alter the types of
@@ -203,7 +205,7 @@
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $y
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (call $get_null_{i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $y)
@@ -229,7 +231,7 @@
;; NOMNL-NEXT: (local.get $2)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (local.set $y
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (call $get_null_{i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (local.get $y)
@@ -250,7 +252,7 @@
)
;; Write to $y in a way that does not cause any issue, and we should not do
;; any fixup while we refine the type.
- (local.set $y (ref.null ${i32_i64}))
+ (local.set $y (call $get_null_{i32_i64}))
(drop
(local.get $y)
)
@@ -258,18 +260,18 @@
;; CHECK: (func $call-various-params-tee
;; CHECK-NEXT: (call $various-params-tee
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $call-various-params-tee (type $none_=>_none)
;; NOMNL-NEXT: (call $various-params-tee
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $call-various-params-tee
;; The argument gets {i32}, which allows us to refine.
(call $various-params-tee
- (ref.null ${i32})
+ (call $get_null_{i32})
)
)
;; CHECK: (func $various-params-tee (param $x (ref null ${i32}))
@@ -279,7 +281,7 @@
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $block (result (ref null ${i32}))
;; CHECK-NEXT: (local.tee $x
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (call $get_null_{i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
@@ -291,7 +293,7 @@
;; NOMNL-NEXT: (drop
;; NOMNL-NEXT: (block $block (result (ref null ${i32}))
;; NOMNL-NEXT: (local.tee $x
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (call $get_null_{i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
@@ -304,7 +306,7 @@
;; would occur), and that will also cause the block's type to update as well.
(drop
(block (result (ref null ${}))
- (local.tee $x (ref.null ${i32_i64}))
+ (local.tee $x (call $get_null_{i32_i64}))
)
)
)
@@ -314,7 +316,7 @@
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null ${i32})
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: (call $get_null_{i32})
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $various-params-null
;; CHECK-NEXT: (ref.as_non_null
@@ -330,7 +332,7 @@
;; NOMNL-NEXT: (ref.as_non_null
;; NOMNL-NEXT: (ref.null ${i32})
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: (call $get_null_{i32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (call $various-params-null
;; NOMNL-NEXT: (ref.as_non_null
@@ -346,7 +348,7 @@
;; second gets only one.
(call $various-params-null
(ref.as_non_null (ref.null ${i32}))
- (ref.null ${i32})
+ (call $get_null_{i32})
)
(call $various-params-null
(ref.as_non_null (ref.null ${i32}))
@@ -393,28 +395,28 @@
;; CHECK: (func $call-various-params-middle
;; CHECK-NEXT: (call $various-params-middle
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (call $get_null_{i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $various-params-middle
- ;; CHECK-NEXT: (ref.null ${i32_f32})
+ ;; CHECK-NEXT: (call $get_null_{i32_f32})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $call-various-params-middle (type $none_=>_none)
;; NOMNL-NEXT: (call $various-params-middle
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (call $get_null_{i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (call $various-params-middle
- ;; NOMNL-NEXT: (ref.null ${i32_f32})
+ ;; NOMNL-NEXT: (call $get_null_{i32_f32})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $call-various-params-middle
;; The argument gets {i32_i64} and {i32_f32}. This allows us to refine from
;; {} to {i32}, a type "in the middle".
(call $various-params-middle
- (ref.null ${i32_i64})
+ (call $get_null_{i32_i64})
)
(call $various-params-middle
- (ref.null ${i32_f32})
+ (call $get_null_{i32_f32})
)
)
;; CHECK: (func $various-params-middle (param $x (ref null ${i32}))
@@ -510,4 +512,80 @@
(struct.new_default ${})
)
)
+
+ ;; CHECK: (func $call-update-null
+ ;; CHECK-NEXT: (call $update-null
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $update-null
+ ;; CHECK-NEXT: (struct.new_default ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $call-update-null (type $none_=>_none)
+ ;; NOMNL-NEXT: (call $update-null
+ ;; NOMNL-NEXT: (ref.null ${})
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $update-null
+ ;; NOMNL-NEXT: (struct.new_default ${})
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $call-update-null
+ ;; Call a function with one of the parameters a null of a type that we can
+ ;; update in order to get a better LUB.
+ (call $update-null
+ (ref.null any)
+ )
+ (call $update-null
+ (struct.new_default ${})
+ )
+ )
+
+ ;; CHECK: (func $update-null (param $x (ref null ${}))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $update-null (type $ref?|${}|_=>_none) (param $x (ref null ${}))
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $update-null (param $x (ref null any))
+ ;; "Use" the param to avoid other optimizations kicking in. We should only
+ ;; see the type of the param refined to a null ${} after updating the null
+ ;; in the caller.
+ (drop (local.get $x))
+ )
+
+ ;; CHECK: (func $get_null_{i32} (result (ref null ${i32}))
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_null_{i32} (type $none_=>_ref?|${i32}|) (result (ref null ${i32}))
+ ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: )
+ (func $get_null_{i32} (result (ref null ${i32}))
+ ;; Helper function that returns a null value of ${i32}. We use this instead of
+ ;; a direct ref.null because those can be rewritten by LUBFinder.
+ (ref.null ${i32})
+ )
+
+ ;; CHECK: (func $get_null_{i32_i64} (result (ref null ${i32_i64}))
+ ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_null_{i32_i64} (type $none_=>_ref?|${i32_i64}|) (result (ref null ${i32_i64}))
+ ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: )
+ (func $get_null_{i32_i64} (result (ref null ${i32_i64}))
+ (ref.null ${i32_i64})
+ )
+
+ ;; CHECK: (func $get_null_{i32_f32} (result (ref null ${i32_f32}))
+ ;; CHECK-NEXT: (ref.null ${i32_f32})
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $get_null_{i32_f32} (type $none_=>_ref?|${i32_f32}|) (result (ref null ${i32_f32}))
+ ;; NOMNL-NEXT: (ref.null ${i32_f32})
+ ;; NOMNL-NEXT: )
+ (func $get_null_{i32_f32} (result (ref null ${i32_f32}))
+ (ref.null ${i32_f32})
+ )
)
diff --git a/test/lit/passes/dae-gc-refine-return.wast b/test/lit/passes/dae-gc-refine-return.wast
index c5ea326d9..15a40652d 100644
--- a/test/lit/passes/dae-gc-refine-return.wast
+++ b/test/lit/passes/dae-gc-refine-return.wast
@@ -7,16 +7,20 @@
;; NOMNL: (type $return_{} (func_subtype (result (ref ${})) func))
(type $return_{} (func (result (ref ${}))))
+ ;; CHECK: (type ${i32} (struct (field i32)))
+
+ ;; CHECK: (type ${i32_i64} (struct (field i32) (field i64)))
+
;; CHECK: (type ${i32_f32} (struct (field i32) (field f32)))
+ ;; NOMNL: (type ${i32} (struct_subtype (field i32) ${}))
+
+ ;; NOMNL: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
+
;; NOMNL: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
(type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32}))
- ;; CHECK: (type ${i32_i64} (struct (field i32) (field i64)))
- ;; NOMNL: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
(type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32}))
- ;; CHECK: (type ${i32} (struct (field i32)))
- ;; NOMNL: (type ${i32} (struct_subtype (field i32) ${}))
(type ${i32} (struct_subtype (field i32) ${}))
;; CHECK: (type ${} (struct ))
@@ -53,45 +57,53 @@
;; We cannot refine the return type if it is already the best it can be.
;; CHECK: (func $refine-return-no-refining (result anyref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-no-refining)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-no-refining (type $none_=>_anyref) (result anyref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $any anyref)
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-no-refining)
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null any)
+ ;; NOMNL-NEXT: (local.get $any)
;; NOMNL-NEXT: )
(func $refine-return-no-refining (result anyref)
(local $temp anyref)
+ (local $any anyref)
+
(local.set $temp (call $refine-return-no-refining))
- (ref.null any)
+ (local.get $any)
)
;; Refine the return type based on the value flowing out.
;; CHECK: (func $refine-return-flow (result funcref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-flow)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-flow (type $none_=>_funcref) (result funcref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-flow)
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
(func $refine-return-flow (result anyref)
(local $temp anyref)
+ (local $func funcref)
+
(local.set $temp (call $refine-return-flow))
- (ref.null func)
+ (local.get $func)
)
;; CHECK: (func $call-refine-return-flow (result funcref)
;; CHECK-NEXT: (local $temp anyref)
@@ -132,257 +144,294 @@
;; Refine the return type based on a return.
;; CHECK: (func $refine-return-return (result funcref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-return)
;; CHECK-NEXT: )
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-return (type $none_=>_funcref) (result funcref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-return)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $refine-return-return (result anyref)
(local $temp anyref)
+ (local $func funcref)
+
(local.set $temp (call $refine-return-return))
- (return (ref.null func))
+ (return (local.get $func))
)
;; Refine the return type based on multiple values.
;; CHECK: (func $refine-return-many (result funcref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-many)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-many (type $none_=>_funcref) (result funcref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-many)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 2)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
(func $refine-return-many (result anyref)
(local $temp anyref)
+ (local $func funcref)
+
(local.set $temp (call $refine-return-many))
(if
(i32.const 1)
- (return (ref.null func))
+ (return (local.get $func))
)
(if
(i32.const 2)
- (return (ref.null func))
+ (return (local.get $func))
)
- (ref.null func)
+ (local.get $func)
)
;; CHECK: (func $refine-return-many-blocked (result anyref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
+ ;; CHECK-NEXT: (local $data (ref null data))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-many-blocked)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null data)
+ ;; CHECK-NEXT: (local.get $data)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-many-blocked (type $none_=>_anyref) (result anyref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
+ ;; NOMNL-NEXT: (local $data (ref null data))
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-many-blocked)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 2)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null data)
+ ;; NOMNL-NEXT: (local.get $data)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
(func $refine-return-many-blocked (result anyref)
(local $temp anyref)
+ (local $func funcref)
+ (local $data (ref null data))
+
(local.set $temp (call $refine-return-many-blocked))
(if
(i32.const 1)
- (return (ref.null func))
+ (return (local.get $func))
)
(if
(i32.const 2)
;; The refined return value is blocked by this return.
- (return (ref.null data))
+ (return (local.get $data))
)
- (ref.null func)
+ (local.get $func)
)
;; CHECK: (func $refine-return-many-blocked-2 (result anyref)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
+ ;; CHECK-NEXT: (local $data (ref null data))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-many-blocked-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null data)
+ ;; CHECK-NEXT: (local.get $data)
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-many-blocked-2 (type $none_=>_anyref) (result anyref)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
+ ;; NOMNL-NEXT: (local $data (ref null data))
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-many-blocked-2)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 2)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null data)
+ ;; NOMNL-NEXT: (local.get $data)
;; NOMNL-NEXT: )
(func $refine-return-many-blocked-2 (result anyref)
(local $temp anyref)
+ (local $func funcref)
+ (local $data (ref null data))
+
(local.set $temp (call $refine-return-many-blocked-2))
(if
(i32.const 1)
- (return (ref.null func))
+ (return (local.get $func))
)
(if
(i32.const 2)
- (return (ref.null func))
+ (return (local.get $func))
)
;; The refined return value is blocked by this value.
- (ref.null data)
+ (local.get $data)
)
;; CHECK: (func $refine-return-many-middle (result (ref null ${i32}))
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local ${i32_i64} (ref null ${i32_i64}))
+ ;; CHECK-NEXT: (local ${i32_f32} (ref null ${i32_f32}))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (call $refine-return-many-middle)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null ${i32_i64})
+ ;; CHECK-NEXT: (local.get ${i32_i64})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (ref.null ${i32_f32})
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (local.get ${i32_f32})
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-many-middle (type $none_=>_ref?|${i32}|) (result (ref null ${i32}))
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local ${i32_i64} (ref null ${i32_i64}))
+ ;; NOMNL-NEXT: (local ${i32_f32} (ref null ${i32_f32}))
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (call $refine-return-many-middle)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null ${i32_i64})
+ ;; NOMNL-NEXT: (local.get ${i32_i64})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
- ;; NOMNL-NEXT: (ref.null ${i32_f32})
+ ;; NOMNL-NEXT: (return
+ ;; NOMNL-NEXT: (local.get ${i32_f32})
+ ;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $refine-return-many-middle (result anyref)
(local $temp anyref)
+ (local ${i32_i64} (ref null ${i32_i64}))
+ (local ${i32_f32} (ref null ${i32_f32}))
+
(local.set $temp (call $refine-return-many-middle))
;; Return two different struct types, with an LUB that is not equal to either
;; of them.
(if
(i32.const 1)
- (return (ref.null ${i32_i64}))
+ (return (local.get ${i32_i64}))
)
- (ref.null ${i32_f32})
+ (return (local.get ${i32_f32}))
)
;; We can refine the return types of tuples.
;; CHECK: (func $refine-return-tuple (result funcref i32)
;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (local $func funcref)
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (tuple.extract 0
;; CHECK-NEXT: (call $refine-return-tuple)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (tuple.make
- ;; CHECK-NEXT: (ref.null func)
+ ;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $refine-return-tuple (type $none_=>_funcref_i32) (result funcref i32)
;; NOMNL-NEXT: (local $temp anyref)
+ ;; NOMNL-NEXT: (local $func funcref)
;; NOMNL-NEXT: (local.set $temp
;; NOMNL-NEXT: (tuple.extract 0
;; NOMNL-NEXT: (call $refine-return-tuple)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (tuple.make
- ;; NOMNL-NEXT: (ref.null func)
+ ;; NOMNL-NEXT: (local.get $func)
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $refine-return-tuple (result anyref i32)
(local $temp anyref)
+ (local $func funcref)
+
(local.set $temp
(tuple.extract 0
(call $refine-return-tuple)
@@ -390,7 +439,7 @@
)
(tuple.make
- (ref.null func)
+ (local.get $func)
(i32.const 1)
)
)
@@ -441,28 +490,32 @@
(return_call $tail-callee)
)
;; CHECK: (func $tail-caller-no (result anyref)
+ ;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return_call $tail-callee)
;; CHECK-NEXT: )
;; NOMNL: (func $tail-caller-no (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT: (local $any anyref)
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null any)
+ ;; NOMNL-NEXT: (local.get $any)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (return_call $tail-callee)
;; NOMNL-NEXT: )
(func $tail-caller-no (result anyref)
+ (local $any anyref)
+
;; This function's return type cannot be refined because of another return
;; whose type prevents it.
(if (i32.const 1)
- (return (ref.null any))
+ (return (local.get $any))
)
(return_call $tail-callee)
)
@@ -516,10 +569,11 @@
(return_call_indirect (type $return_{}) (i32.const 0))
)
;; CHECK: (func $tail-caller-indirect-no (result anyref)
+ ;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return_call_indirect $0 (type $return_{})
@@ -527,10 +581,11 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $tail-caller-indirect-no (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT: (local $any anyref)
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null any)
+ ;; NOMNL-NEXT: (local.get $any)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (return_call_indirect $0 (type $return_{})
@@ -538,8 +593,10 @@
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $tail-caller-indirect-no (result anyref)
+ (local $any anyref)
+
(if (i32.const 1)
- (return (ref.null any))
+ (return (local.get $any))
)
(return_call_indirect (type $return_{}) (i32.const 0))
)
@@ -579,45 +636,56 @@
(unreachable)
)
;; CHECK: (func $tail-caller-call_ref-yes (result (ref ${}))
+ ;; CHECK-NEXT: (local $return_{} (ref null $return_{}))
;; CHECK-NEXT: (return_call_ref
- ;; CHECK-NEXT: (ref.null $return_{})
+ ;; CHECK-NEXT: (local.get $return_{})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $tail-caller-call_ref-yes (type $return_{}) (result (ref ${}))
+ ;; NOMNL-NEXT: (local $return_{} (ref null $return_{}))
;; NOMNL-NEXT: (return_call_ref
- ;; NOMNL-NEXT: (ref.null $return_{})
+ ;; NOMNL-NEXT: (local.get $return_{})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $tail-caller-call_ref-yes (result anyref)
- (return_call_ref (ref.null $return_{}))
+ (local $return_{} (ref null $return_{}))
+
+ (return_call_ref (local.get $return_{}))
)
;; CHECK: (func $tail-caller-call_ref-no (result anyref)
+ ;; CHECK-NEXT: (local $any anyref)
+ ;; CHECK-NEXT: (local $return_{} (ref null $return_{}))
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (return
- ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (return_call_ref
- ;; CHECK-NEXT: (ref.null $return_{})
+ ;; CHECK-NEXT: (local.get $return_{})
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; NOMNL: (func $tail-caller-call_ref-no (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT: (local $any anyref)
+ ;; NOMNL-NEXT: (local $return_{} (ref null $return_{}))
;; NOMNL-NEXT: (if
;; NOMNL-NEXT: (i32.const 1)
;; NOMNL-NEXT: (return
- ;; NOMNL-NEXT: (ref.null any)
+ ;; NOMNL-NEXT: (local.get $any)
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
;; NOMNL-NEXT: (return_call_ref
- ;; NOMNL-NEXT: (ref.null $return_{})
+ ;; NOMNL-NEXT: (local.get $return_{})
;; NOMNL-NEXT: )
;; NOMNL-NEXT: )
(func $tail-caller-call_ref-no (result anyref)
+ (local $any anyref)
+ (local $return_{} (ref null $return_{}))
+
(if (i32.const 1)
- (return (ref.null any))
+ (return (local.get $any))
)
- (return_call_ref (ref.null $return_{}))
+ (return_call_ref (local.get $return_{}))
)
;; CHECK: (func $tail-caller-call_ref-unreachable
;; CHECK-NEXT: (unreachable)
@@ -659,4 +727,91 @@
(call $tail-caller-call_ref-unreachable)
)
)
+
+ ;; CHECK: (func $update-null (param $x i32) (param $y i32) (result (ref null ${i32}))
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (struct.new_default ${i32_f32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (struct.new_default ${i32_i64})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $update-null (type $i32_i32_=>_ref?|${i32}|) (param $x i32) (param $y i32) (result (ref null ${i32}))
+ ;; NOMNL-NEXT: (if
+ ;; NOMNL-NEXT: (local.get $x)
+ ;; NOMNL-NEXT: (if
+ ;; NOMNL-NEXT: (local.get $y)
+ ;; NOMNL-NEXT: (return
+ ;; NOMNL-NEXT: (struct.new_default ${i32_f32})
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (return
+ ;; NOMNL-NEXT: (ref.null ${i32})
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (return
+ ;; NOMNL-NEXT: (struct.new_default ${i32_i64})
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $update-null (param $x i32) (param $y i32) (result anyref)
+ ;; Of the three returns here, the null can be updated, and the LUB is
+ ;; determined by the other two, and is their shared parent ${}.
+ (if
+ (local.get $x)
+ (if
+ (local.get $y)
+ (return (struct.new ${i32_f32}))
+ (return (ref.null any))
+ )
+ (return (struct.new ${i32_i64}))
+ )
+ )
+
+ ;; CHECK: (func $call-update-null (result anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $update-null
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $update-null
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $call-update-null (type $none_=>_anyref) (result anyref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (call $update-null
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (call $update-null
+ ;; NOMNL-NEXT: (i32.const 1)
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $call-update-null (result anyref)
+ ;; Call $update-null so it gets optimized. (Call it with various values so
+ ;; that other opts do not inline the constants.)
+ (drop
+ ($call $update-null
+ (i32.const 0)
+ (i32.const 1)
+ )
+ )
+ ($call $update-null
+ (i32.const 1)
+ (i32.const 0)
+ )
+ )
)
diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast
index f655eba1e..99a59d005 100644
--- a/test/lit/passes/local-subtyping.wast
+++ b/test/lit/passes/local-subtyping.wast
@@ -3,6 +3,12 @@
;; RUN: | filecheck %s
(module
+ ;; CHECK: (type ${} (struct ))
+ (type ${} (struct_subtype data))
+
+ ;; CHECK: (type ${i32} (struct (field i32)))
+ (type ${i32} (struct_subtype (field i32) data))
+
;; CHECK: (import "out" "i32" (func $i32 (result i32)))
(import "out" "i32" (func $i32 (result i32)))
;; CHECK: (import "out" "i64" (func $i64 (result i64)))
@@ -333,4 +339,47 @@
)
(unreachable)
)
+
+ ;; CHECK: (func $update-nulls
+ ;; CHECK-NEXT: (local $x (ref null ${}))
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (struct.new_default ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $x
+ ;; CHECK-NEXT: (ref.null ${i32})
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update-nulls
+ (local $x anyref)
+ (local.set $x (ref.null any))
+ (local.set $x (ref.null eq))
+ ;; All the nulls can be changed into other nulls here, for the new LUB,
+ ;; which will be ${}
+ (local.set $x (struct.new ${}))
+ (local.set $x (ref.null data))
+ ;; Note that this func null is even of a type that is incompatible with the
+ ;; new lub (func vs data). Still, we can just update it along with the
+ ;; others.
+ (local.set $x (ref.null func))
+ ;; This null is equal to the LUB we'll find, and will not change.
+ (local.set $x (ref.null ${}))
+ ;; This null is more specific than the LUB we'll find, and will not change,
+ ;; as there is no point to making something less specific in type.
+ (local.set $x (ref.null ${i32}))
+ )
)
diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast
index 38a6319f3..eb33f058a 100644
--- a/test/lit/passes/type-refining.wast
+++ b/test/lit/passes/type-refining.wast
@@ -551,3 +551,220 @@
)
)
)
+
+(module
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+
+ ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
+ ;; CHECK: (func $update-null (type $ref|$struct|_=>_none) (param $struct (ref $struct))
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update-null (param $struct (ref $struct))
+ (struct.set $struct 0
+ (local.get $struct)
+ ;; Write a $struct to the field.
+ (local.get $struct)
+ )
+ (struct.set $struct 0
+ (local.get $struct)
+ ;; This null can be updated, allowing us to refine the type of the field
+ ;; to a null of $struct.
+ (ref.null data)
+ )
+ )
+)
+
+(module
+ ;; As above, but now the null is in a child. The result should be the same:
+ ;; refine the field to nullable $struct.
+
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+ ;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) $struct))
+ (type $child (struct_subtype (field (mut (ref null data))) $struct))
+
+ ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func))
+
+ ;; CHECK: (func $update-null (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child))
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $child 0
+ ;; CHECK-NEXT: (local.get $child)
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
+ (struct.set $struct 0
+ (local.get $struct)
+ (local.get $struct)
+ )
+ (struct.set $child 0
+ (local.get $child)
+ (ref.null data)
+ )
+ )
+)
+
+(module
+ ;; As above, but now the null is in a parent. The result should be the same.
+
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+ ;; CHECK: (type $child (struct_subtype (field (mut (ref null $struct))) $struct))
+ (type $child (struct_subtype (field (mut (ref null data))) $struct))
+
+ ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func))
+
+ ;; CHECK: (func $update-null (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child))
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $child 0
+ ;; CHECK-NEXT: (local.get $child)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
+ (struct.set $struct 0
+ (local.get $struct)
+ (ref.null data)
+ )
+ (struct.set $child 0
+ (local.get $child)
+ (local.get $struct)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null data))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+
+ ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
+ ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new_default $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $struct (ref $struct))
+ ;; The only write to this struct is of a null default value. There is
+ ;; nothing to optimize here.
+ (drop
+ (struct.new_default $struct)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+
+ ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
+ ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new_default $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $struct (ref $struct))
+ (drop
+ (struct.new_default $struct)
+ )
+ ;; Also write a $struct. The null default should not prevent us from
+ ;; refining the field's type to $struct (but nullable).
+ (struct.set $struct 0
+ (local.get $struct)
+ (local.get $struct)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) data))
+
+ ;; CHECK: (type $ref|$struct|_=>_none (func_subtype (param (ref $struct)) func))
+
+ ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $work (param $struct (ref $struct))
+ ;; As before, but instead of new_default, new, and use a null in the given
+ ;; value, which should be updated.
+ (drop
+ (struct.new $struct
+ (ref.null data)
+ )
+ )
+ (struct.set $struct 0
+ (local.get $struct)
+ (local.get $struct)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $struct (struct_subtype (field (mut (ref null $child))) (field (mut (ref null $struct))) data))
+ (type $struct (struct_subtype (field (mut (ref null data))) (field (mut (ref null data))) data))
+
+ ;; CHECK: (type $child (struct_subtype (field (mut (ref null $child))) (field (mut (ref null $struct))) $struct))
+ (type $child (struct_subtype (field (mut (ref null data))) (field (mut (ref null data))) $struct))
+
+ ;; CHECK: (type $ref|$struct|_ref|$child|_=>_none (func_subtype (param (ref $struct) (ref $child)) func))
+
+ ;; CHECK: (func $update-null (type $ref|$struct|_ref|$child|_=>_none) (param $struct (ref $struct)) (param $child (ref $child))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (local.get $child)
+ ;; CHECK-NEXT: (ref.null $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (ref.null $child)
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $update-null (param $struct (ref $struct)) (param $child (ref $child))
+ ;; Update nulls in two fields that are separately optimized to separate
+ ;; values.
+ (drop
+ (struct.new $struct
+ (local.get $child)
+ (ref.null data)
+ )
+ )
+ (drop
+ (struct.new $struct
+ (ref.null data)
+ (local.get $struct)
+ )
+ )
+ )
+)