diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/dae-gc-refine-params.wast | 166 | ||||
-rw-r--r-- | test/lit/passes/dae-gc-refine-return.wast | 283 | ||||
-rw-r--r-- | test/lit/passes/local-subtyping.wast | 49 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 217 |
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) + ) + ) + ) +) |