| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| ;; RUN: foreach %s %t wasm-opt -all --gufa -tnh -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (type $0 (func (param funcref funcref funcref funcref))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (import "a" "b" (global $unknown-i32 i32)) |
| (import "a" "b" (global $unknown-i32 i32)) |
| |
| ;; CHECK: (import "a" "b" (global $unknown-funcref1 funcref)) |
| (import "a" "b" (global $unknown-funcref1 funcref)) |
| |
| ;; CHECK: (import "a" "b" (global $unknown-funcref2 funcref)) |
| (import "a" "b" (global $unknown-funcref2 funcref)) |
| |
| ;; CHECK: (import "a" "b" (global $unknown-nn-func1 (ref func))) |
| (import "a" "b" (global $unknown-nn-func1 (ref func))) |
| |
| ;; CHECK: (import "a" "b" (global $unknown-nn-func2 (ref func))) |
| (import "a" "b" (global $unknown-nn-func2 (ref func))) |
| |
| ;; CHECK: (func $called (type $0) (param $x funcref) (param $no-cast funcref) (param $y funcref) (param $z funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x funcref) (param $no-cast funcref) (param $y funcref) (param $z funcref) |
| ;; All but the second parameter are cast here, which allows some |
| ;; optimization in the caller. Nothing significant changes here in this |
| ;; function. |
| (drop |
| (ref.cast (ref func) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.cast (ref func) |
| (local.get $y) |
| ) |
| ) |
| (drop |
| (ref.cast (ref func) |
| (local.get $z) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $1) |
| ;; CHECK-NEXT: (local $f funcref) |
| ;; CHECK-NEXT: (local.set $f |
| ;; CHECK-NEXT: (select (result funcref) |
| ;; CHECK-NEXT: (global.get $unknown-funcref1) |
| ;; CHECK-NEXT: (global.get $unknown-funcref2) |
| ;; CHECK-NEXT: (global.get $unknown-i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast funcref |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref func) |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (local $f funcref) |
| |
| ;; Fill the local with an unknown value (so that trivial inference doesn't |
| ;; optimize away the thing we care about below). |
| (local.set $f |
| (select |
| (global.get $unknown-funcref1) |
| (global.get $unknown-funcref2) |
| (global.get $unknown-i32) |
| ) |
| ) |
| |
| ;; All but the third parameter are cast here. The cast has no effect by |
| ;; itself as the type is funcref, but GUFA will refine casts when it can |
| ;; (but not add a new cast, which might not be worth it). |
| ;; |
| ;; Specifically here, the first and last cast can be refined, since those |
| ;; are cast both here and in the called function. Those casts will lose the |
| ;; "null" and become non-nullable. |
| (call $called |
| (ref.cast funcref |
| (local.get $f) |
| ) |
| (ref.cast funcref |
| (local.get $f) |
| ) |
| (local.get $f) |
| (ref.cast funcref |
| (local.get $f) |
| ) |
| ) |
| |
| ;; Another call, but with different casts. |
| (call $called |
| (ref.cast funcref ;; this is now non-nullable, and will not change |
| (local.get $f) |
| ) |
| (local.get $f) ;; this is not cast, and will not change. |
| (ref.cast funcref |
| (local.get $f) ;; this is now cast, and will be optimized. |
| ) |
| (ref.cast funcref ;; this is the same as before, and will be optimized. |
| (local.get $f) |
| ) |
| ) |
| |
| ;; Test that we do not error in unreachable code. |
| (call $called |
| (unreachable) |
| (unreachable) |
| (unreachable) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $maker (type $2) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $A |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $B |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $maker |
| ;; A always contains 10, and B always contains 20. |
| (drop |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| (drop |
| (struct.new $B |
| (i32.const 20) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $called (type $3) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| ;; Cast the input to a $B, which will help the caller. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $any anyref) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $A 0 |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (local $x (ref null $A)) |
| ;; The called function casts to $B. This lets us infer the value of the |
| ;; fallthrough ref.cast, which will turn into $B. Furthermore, that then |
| ;; tells us what is written into the local $x, and the forward flow |
| ;; analysis will use that fact in the local.get $x below. |
| (call $called |
| (local.tee $x |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ;; We can't infer anything here at the moment, but a more sophisticated |
| ;; analysis could. (Other passes can help here, however, by using $x where |
| ;; $any appears.) |
| (drop |
| (struct.get $A 0 |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| (drop |
| (ref.test (ref $B) |
| (local.get $any) |
| ) |
| ) |
| ;; We know that $x must contain $B, so this can be inferred to be 20, and |
| ;; the ref.is to 1. |
| (drop |
| (struct.get $A 0 |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.test (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A local.tee by itself, without a cast. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $3 (func)) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $maker (type $3) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $A |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $B |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $maker |
| ;; A always contains 10, and B always contains 20. |
| (drop |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| (drop |
| (struct.new $B |
| (i32.const 20) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| ;; Cast the input to a $B, which will help the caller. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $2) (param $a (ref null $A)) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $a (ref null $A)) |
| (local $x (ref null $A)) |
| ;; The change compared to before is that we only have a local.tee here, and |
| ;; no ref.cast. We can still infer the type of the tee's value, and |
| ;; therefore the type of $x when it is read below, and optimize there. |
| (call $called |
| (local.tee $x |
| (local.get $a) |
| ) |
| ) |
| ;; This can be inferred to be 20. |
| (drop |
| (struct.get $A 0 |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but add a local.tee etc. in the called function. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $3 (func (param anyref))) |
| |
| ;; CHECK: (global $global (mut i32) (i32.const 0)) |
| (global $global (mut i32) (i32.const 0)) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (local $local (ref null $A)) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (global.set $global |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.tee $local |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (local $local (ref null $A)) |
| ;; Some nops and such do not bother us. Even a side effect like setting a |
| ;; global does not. |
| (nop) |
| (drop |
| (i32.const 42) |
| ) |
| (global.set $global |
| (i32.const 1337) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| ;; This local.tee should not stop us from optimizing. |
| (local.tee $local |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (local $x (ref null $A)) |
| (call $called |
| (local.tee $x |
| (ref.cast (ref $A) ;; this cast will be refined |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but now add some control flow before the cast in the function. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $1 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $3 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $1) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (local $local (ref null $A)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (local $local (ref null $A)) |
| ;; Control flow before the cast *does* stop us from optimizing. |
| (if |
| (i32.const 0) |
| (then |
| (return) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (local $x (ref null $A)) |
| (call $called |
| (local.tee $x |
| (ref.cast (ref $A) ;; this cast will *not* be refined |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but make the cast uninteresting so we do not optimize. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $1 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $2 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $1) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.cast (ref $A) ;; This cast only removes nullability. |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $2) (param $any anyref) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (local $x (ref null $A)) |
| (call $called |
| (local.tee $x |
| (ref.cast (ref $A) |
| ;; This cast will *not* be refined, as it is already non- |
| ;; nullable here, and the other cast did not improve the |
| ;; heap type. |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but two casts in the called function. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) |
| (type $C (sub $B (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| ;; Two casts. We keep the first, which is simple to do, and good enough in |
| ;; the general case as other optimizations will leave the most-refined one. |
| ;; (But in this test, it is less optimal actually.) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $C) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $any anyref) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (local $x (ref null $A)) |
| (call $called |
| (local.tee $x |
| (ref.cast (ref $A) ;; this cast will be refined to $B. |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Multiple parameters with control flow between them. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A) (ref null $A) (ref null $A)))) |
| |
| ;; CHECK: (type $3 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; All parameters are cast. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $y) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $z) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (block (result (ref $A)) |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref $B)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref $B)) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| ;; All three params get similar blocks+casts, but the middle one might |
| ;; transfer control flow, so we cannot optimize the first parameter (we |
| ;; might not reach the call, so we can't assume the call's cast suceeds). |
| ;; As a result, only the last two casts are refined to $B, but not the |
| ;; first. |
| (call $called |
| (block (result (ref $A)) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| (block (result (ref $A)) |
| (if |
| (i32.const 0) |
| (then |
| (return) |
| ) |
| ) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| (block (result (ref $A)) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but without the cast in the middle of the called function. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A) (ref null $A) (ref null $A)))) |
| |
| ;; CHECK: (type $3 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| ;; This changed to not have a cast. |
| (local.get $y) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $z) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (block (result (ref $A)) |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref $A)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref $B)) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| ;; We can't refine the first cast because of control flow, like before, and |
| ;; we can't refine the middle because the called function has no cast, but |
| ;; we can still refine the last one. |
| (call $called |
| (block (result (ref $A)) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| (block (result (ref $A)) |
| (if |
| (i32.const 0) |
| (then |
| (return) |
| ) |
| ) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| (block (result (ref $A)) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but with a different control flow transfer in the caller, a call. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (result anyref))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (import "a" "b" (func $get-any (type $2) (result anyref))) |
| (import "a" "b" (func $get-any (result anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; All parameters are cast. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $y) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $z) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (call $get-any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (call $called |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| (ref.cast (ref $A) |
| ;; This call might transfer control flow (if it throws), so we |
| ;; can't optimize before it, but the last two casts will become $B. |
| (call $get-any) |
| ) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but with yet another control flow transfer, using an if. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (result anyref))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (import "a" "b" (func $get-any (type $2) (result anyref))) |
| (import "a" "b" (func $get-any (result anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) |
| ;; All parameters are cast. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $y) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $z) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result (ref $A)) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (call $called |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ;; One if arm transfers control flow, so while we have a fallthrough |
| ;; value we cannot optimize it (in fact, it might never be reached, at |
| ;; least if the constant were not 0). As a result we'll optimize only the |
| ;; very last cast. |
| (if (result (ref $A)) |
| (i32.const 0) |
| (then |
| (return) |
| ) |
| (else |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A cast that will fail. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $2 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $3 (func)) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $2) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref (exact $B)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $3) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (struct.new $B |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") |
| (call $called |
| ;; The called function will cast to $B, but this is an $A, so the cast |
| ;; will fail. We can infer that the code here is unreachable. Note that no |
| ;; ref.cast appears here - we infer this even without seeing a cast. |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| ;; Another call that will not be modified. |
| (call $called |
| (struct.new $B |
| (i32.const 20) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Test that we refine using ref.as_non_null and not just ref.cast. |
| (module |
| ;; CHECK: (type $A (struct (field (mut i32)))) |
| (type $A (struct (field (mut i32)))) |
| |
| ;; CHECK: (type $1 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $2 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $1) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $2) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| (call $called |
| ;; This cast can become non-nullable. |
| (ref.cast (ref null $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Verify we do not propagate *less*-refined information. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) |
| (type $C (sub $B (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $3 (func (param anyref))) |
| |
| ;; CHECK: (type $4 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $5 (func)) |
| |
| ;; CHECK: (export "caller-C" (func $caller-C)) |
| |
| ;; CHECK: (export "caller-B" (func $caller-B)) |
| |
| ;; CHECK: (export "caller-A" (func $caller-A)) |
| |
| ;; CHECK: (func $called (type $4) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| ;; This function casts the A to a B. |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $maker (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $A |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $B |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $C |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $maker |
| ;; A always contains 10, and B 20, and C 30. |
| (drop |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| (drop |
| (struct.new $B |
| (i32.const 20) |
| ) |
| ) |
| (drop |
| (struct.new $C |
| (i32.const 30) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller-C (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (local $temp-C (ref $C)) |
| ;; CHECK-NEXT: (local $temp-any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $temp-C |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.tee $temp-any |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $B 0 |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $temp-any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $A 0 |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller-C (export "caller-C") (param $any anyref) |
| (local $temp-C (ref $C)) |
| (local $temp-any anyref) |
| (call $called |
| (local.tee $temp-C |
| (ref.cast (ref $C) |
| ;; This cast is already more refined than even the called |
| ;; function casts to. It should stay as it is. |
| (local.tee $temp-any |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $A 0 ;; the reference contains a C, so this value is 30. |
| (ref.cast (ref $A) |
| (local.get $temp-C) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $A 0 ;; We infer that $temp-any is $B from the cast in the |
| ;; call, so the cast here can be improved, but not the |
| ;; value (which can be 20 or 30). |
| ;; TODO: We can infer from the ref.cast $C in this |
| ;; function backwards into the tee and its value. |
| (ref.cast (ref $A) |
| (local.get $temp-any) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $A 0 ;; We have not inferred anything about the param, so this |
| ;; is not optimized yet, but it could be. TODO |
| (ref.cast (ref $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller-B (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (local $temp (ref $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $temp |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $A 0 |
| ;; CHECK-NEXT: (local.get $temp) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller-B (export "caller-B") (param $any anyref) |
| (local $temp (ref $A)) |
| (call $called |
| (local.tee $temp |
| (ref.cast (ref $B) ;; This cast is equal to the called cast. It should remain. |
| (local.get $any) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $A 0 ;; Nothing can be inferred here. |
| (local.get $temp) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller-A (type $3) (param $any anyref) |
| ;; CHECK-NEXT: (local $temp (ref $A)) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (local.tee $temp |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get $A 0 |
| ;; CHECK-NEXT: (local.get $temp) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller-A (export "caller-A") (param $any anyref) |
| (local $temp (ref $A)) |
| (call $called |
| (local.tee $temp |
| (ref.cast (ref $A) ;; This cast is less refined, and can be improved to B. |
| (local.get $any) |
| ) |
| ) |
| ) |
| (drop |
| (struct.get $A 0 ;; Nothing can be inferred here. |
| (local.get $temp) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Refine a type to unreachable. B1 and B2 are sibling subtypes of A, and the |
| ;; caller passes in a B1 that is cast in the function to B2. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $B1 (sub $A (struct (field (mut i32))))) |
| (type $B1 (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B2 (sub $A (struct (field (mut i32))))) |
| (type $B2 (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $C1 (sub $B1 (struct (field (mut i32))))) |
| (type $C1 (sub $B1 (struct (field (mut i32))))) |
| ) |
| |
| ;; CHECK: (type $4 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $5 (func (param anyref))) |
| |
| ;; CHECK: (export "caller" (func $caller)) |
| |
| ;; CHECK: (func $called (type $4) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B1) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.cast (ref $B1) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $5) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (struct.new $B1 |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (struct.new $C1 |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "caller") (param $any anyref) |
| ;; The cast of this A to B1 will fail, so it is unreachable. |
| (call $called |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| ;; This cast of B1 to itself will succeed, so nothing changes. |
| (call $called |
| (struct.new $B1 |
| (i32.const 20) |
| ) |
| ) |
| ;; The cast of this C1 to its supertype B1 will succeed. |
| (call $called |
| (struct.new $C1 |
| (i32.const 30) |
| ) |
| ) |
| ;; Casting B2 to B1 will fail as they are sibling types. |
| (call $called |
| (struct.new $B2 |
| (i32.const 40) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Check we ignore casts of non-param locals. |
| (module |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $0) |
| ;; CHECK-NEXT: (local $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called |
| (local $x (ref null $A)) |
| ;; This casts a local in the entry block, but it is not a parameter, so we |
| ;; should ignore it and not error. |
| (drop |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $called) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") |
| (call $called) |
| ) |
| ) |
| |
| ;; Check all combinations of types passed to a nullable cast. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) |
| (type $C (sub $B (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $x anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref null $C) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $x anyref) |
| ;; A non-nullable A passed into a cast of B means this value must be a non- |
| ;; nullable B. |
| (call $called |
| (ref.cast (ref $A) |
| (local.get $x) |
| ) |
| ) |
| ;; This will be refined to (nullable) B. |
| (call $called |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ;; Casts of B remain the same. |
| (call $called |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (call $called |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ;; Casts of C remain the same. |
| (call $called |
| (ref.cast (ref $C) |
| (local.get $x) |
| ) |
| ) |
| (call $called |
| (ref.cast (ref null $C) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Check all combinations of types passed to a *non*-nullable cast. |
| (module |
| ;; CHECK: (type $A (sub (struct (field (mut i32))))) |
| (type $A (sub (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) |
| (type $B (sub $A (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) |
| (type $C (sub $B (struct (field (mut i32))))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $x (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x (ref null $A)) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $x anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $x anyref) |
| ;; Casts of A are refined. |
| (call $called |
| (ref.cast (ref $A) |
| (local.get $x) |
| ) |
| ) |
| ;; This will be refined to B. |
| (call $called |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ;; Casts of B turn or stay as non-nullable B. |
| (call $called |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| (call $called |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ;; This cast of C remains the same. |
| (call $called |
| (ref.cast (ref $C) |
| (local.get $x) |
| ) |
| ) |
| ;; A nullable C passed into a cast of non-null B means this value must be a |
| ;; non-nullable C. |
| (call $called |
| (ref.cast (ref null $C) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A cast of an array. |
| (module |
| ;; CHECK: (type $0 (func (param anyref))) |
| |
| ;; CHECK: (type $A (array (mut i32))) |
| (type $A (array (mut i32))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $0) (param $x anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $x anyref) |
| (drop |
| (ref.cast (ref $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) (param $x anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $x anyref) |
| (call $called |
| ;; This will be refined to a non-nullable cast. |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Test array and struct operations. |
| (module |
| ;; CHECK: (type $B (array (mut i32))) |
| |
| ;; CHECK: (type $A (struct (field (mut i32)))) |
| (type $A (struct (field (mut i32)))) |
| |
| (type $B (array (mut i32))) |
| |
| ;; CHECK: (type $C (array (mut funcref))) |
| (type $C (array (mut funcref))) |
| |
| |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $C) (ref null $A)))) |
| |
| ;; CHECK: (type $4 (func (param anyref))) |
| |
| ;; CHECK: (data $d "a") |
| (data $d "a") |
| |
| ;; CHECK: (elem $e func) |
| (elem $e funcref) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $struct.get (ref null $A)) (param $struct.set (ref null $A)) (param $array.get (ref null $B)) (param $array.set (ref null $B)) (param $array.len (ref null $B)) (param $array.copy.src (ref null $B)) (param $array.copy.dest (ref null $B)) (param $array.fill (ref null $B)) (param $array.init_data (ref null $B)) (param $array.init_elem (ref null $C)) (param $ref.test (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $A 0 |
| ;; CHECK-NEXT: (local.get $struct.set) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.get $B |
| ;; CHECK-NEXT: (local.get $array.get) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.set $B |
| ;; CHECK-NEXT: (local.get $array.set) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.len |
| ;; CHECK-NEXT: (local.get $array.len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.copy $B $B |
| ;; CHECK-NEXT: (local.get $array.copy.src) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: (local.get $array.copy.dest) |
| ;; CHECK-NEXT: (i32.const 5) |
| ;; CHECK-NEXT: (i32.const 6) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.fill $B |
| ;; CHECK-NEXT: (local.get $array.fill) |
| ;; CHECK-NEXT: (i32.const 7) |
| ;; CHECK-NEXT: (i32.const 8) |
| ;; CHECK-NEXT: (i32.const 9) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.init_data $B $d |
| ;; CHECK-NEXT: (local.get $array.init_data) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 11) |
| ;; CHECK-NEXT: (i32.const 12) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.init_elem $C $e |
| ;; CHECK-NEXT: (local.get $array.init_elem) |
| ;; CHECK-NEXT: (i32.const 13) |
| ;; CHECK-NEXT: (i32.const 14) |
| ;; CHECK-NEXT: (i32.const 15) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $A) |
| ;; CHECK-NEXT: (local.get $ref.test) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called |
| (param $struct.get (ref null $A)) |
| (param $struct.set (ref null $A)) |
| (param $array.get (ref null $B)) |
| (param $array.set (ref null $B)) |
| (param $array.len (ref null $B)) |
| (param $array.copy.src (ref null $B)) |
| (param $array.copy.dest (ref null $B)) |
| (param $array.fill (ref null $B)) |
| (param $array.init_data (ref null $B)) |
| (param $array.init_elem (ref null $C)) |
| (param $ref.test (ref null $A)) |
| |
| ;; All the operations trap on null, aside from ref.test. |
| (drop |
| (struct.get $A 0 |
| (local.get $struct.get) |
| ) |
| ) |
| (struct.set $A 0 |
| (local.get $struct.set) |
| (i32.const 0) |
| ) |
| (drop |
| (array.get $B |
| (local.get $array.get) |
| (i32.const 1) |
| ) |
| ) |
| (array.set $B |
| (local.get $array.set) |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| (drop |
| (array.len |
| (local.get $array.len) |
| ) |
| ) |
| (array.copy $B $B |
| (local.get $array.copy.src) |
| (i32.const 4) |
| (local.get $array.copy.dest) |
| (i32.const 5) |
| (i32.const 6) |
| ) |
| (array.fill $B |
| (local.get $array.fill) |
| (i32.const 7) |
| (i32.const 8) |
| (i32.const 9) |
| ) |
| (array.init_data $B $d |
| (local.get $array.init_data) |
| (i32.const 10) |
| (i32.const 11) |
| (i32.const 12) |
| ) |
| (array.init_elem $C $e |
| (local.get $array.init_elem) |
| (i32.const 13) |
| (i32.const 14) |
| (i32.const 15) |
| ) |
| (drop |
| (ref.test (ref $A) |
| (local.get $ref.test) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $4) (param $any anyref) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $C) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref null $A) |
| ;; CHECK-NEXT: (local.get $any) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $any anyref) |
| ;; All these casts will be refined to non-nullable, aside from the last |
| ;; param which is but a ref.test. |
| (call $called |
| (ref.cast (ref null $A) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $A) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $C) |
| (local.get $any) |
| ) |
| (ref.cast (ref null $A) |
| (local.get $any) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; A CallRef with no non-trapping targets must be unreachable if traps never |
| ;; happen. However, this requires closed world, so we do nothing here. (This |
| ;; test is mirrored in gufa-tnh-closed, where closed world *is* enabled.) |
| (module |
| ;; CHECK: (type $A (func)) |
| (type $A (func)) |
| |
| ;; CHECK: (type $1 (func (param funcref))) |
| |
| ;; CHECK: (export "out" (func $caller)) |
| |
| ;; CHECK: (func $impossible (type $A) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $impossible (type $A) |
| ;; This cannot be called if traps never happen. |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $caller (type $1) (param $x funcref) |
| ;; CHECK-NEXT: (call_ref $A |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "out") (param $x funcref) |
| ;; In closed world this call must trap, as the only function of the right |
| ;; type will trap. But we are in open world so we do not optimize here. |
| ;; TODO: We could analyze publicly visible tables, exports, etc. to see |
| ;; whether any more functions could be possible even in an open world. |
| (call_ref $A |
| (ref.cast (ref $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Control flow around calls. |
| (module |
| ;; CHECK: (type $A (sub (struct))) |
| (type $A (sub (struct))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (type $B (sub $A (struct))) |
| (type $B (sub $A (struct))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $A)))) |
| |
| ;; CHECK: (import "a" "b" (func $import-throw (type $1))) |
| (import "a" "b" (func $import-throw)) |
| |
| ;; CHECK: (export "a" (func $caller)) |
| |
| ;; CHECK: (func $called (type $3) (param $0 (ref null $A)) |
| ;; CHECK-NEXT: (call $import-throw) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $called (param $0 (ref null $A)) |
| ;; This function calls an import, and later casts. We cannot use those casts |
| ;; to infer anything in the callers, since the import might throw (in which |
| ;; case we'd never reach the cast). |
| (call $import-throw) |
| (drop |
| (ref.cast (ref $B) |
| (local.get $0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $1) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (struct.new_default $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $called |
| ;; CHECK-NEXT: (struct.new_default $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (export "a") |
| ;; This call sends a $B which will be cast to $B (assuming the import does |
| ;; not trap), so nothing should happen here. |
| (call $called |
| (struct.new $B) |
| ) |
| ;; This call sends an $A, which would fail the cast if it were reached. But |
| ;; it might not, so we do nothing here. |
| (call $called |
| (struct.new $A) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $0 (func (param i32) (result (ref null (shared any))))) |
| |
| ;; CHECK: (table $0 3 3 (ref null (shared any))) |
| (table $0 3 3 (ref null (shared any))) |
| ;; CHECK: (elem $0 (table $0) (i32.const 0) (ref null (shared i31)) (item (ref.i31_shared |
| ;; CHECK-NEXT: (i32.const 999) |
| ;; CHECK-NEXT: ))) |
| (elem $0 (table $0) (i32.const 0) (ref null (shared i31)) (item (ref.i31_shared (i32.const 999)))) |
| ;; CHECK: (export "get" (func $0)) |
| |
| ;; CHECK: (func $0 (type $0) (param $0 i32) (result (ref null (shared any))) |
| ;; CHECK-NEXT: (ref.cast (ref null (shared i31)) |
| ;; CHECK-NEXT: (table.get $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $0 (export "get") (param $0 i32) (result (ref null (shared any))) |
| ;; Regression test for a bug where subtypes.h did not handle shared types |
| ;; correctly, causing this example to be misoptimized to return null. |
| (ref.cast (ref null (shared i31)) |
| (table.get $0 |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Test writes to locals that interfere with inferences about casts. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $top (sub (struct))) |
| (type $top (sub (struct))) |
| ;; CHECK: (type $bot (sub $top (struct))) |
| (type $bot (sub $top (struct))) |
| ) |
| |
| ;; CHECK: (type $2 (func (param (ref $top)))) |
| |
| ;; CHECK: (type $3 (func (param (ref extern)))) |
| |
| ;; CHECK: (export "$invokeMain" (func $invokeMain)) |
| |
| ;; CHECK: (func $main-set (type $2) (param $0 (ref $top)) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (struct.new_default $bot) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref (exact $bot)) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $main-set (param (ref $top)) |
| ;; We receive a top as input, but write a bot to it, trampling the ignored |
| ;; parameter. Thanks to the trampling, the cast below will succeed, and so we |
| ;; should not make anything unreachable in the caller - nothing traps here. |
| (local.set 0 |
| (struct.new $bot) |
| ) |
| (drop |
| (ref.cast (ref $bot) |
| (local.get 0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $main-noset (type $2) (param $0 (ref $top)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $main-noset (param (ref $top)) |
| ;; As above, but without the local.set. Here we will trap, so the caller can |
| ;; optimize to unreachable. |
| (drop |
| (ref.cast (ref $bot) |
| (local.get 0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $invokeMain (type $3) (param $0 (ref extern)) |
| ;; CHECK-NEXT: (call $main-set |
| ;; CHECK-NEXT: (struct.new_default $top) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $main-noset |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $invokeMain (export "$invokeMain") (param (ref extern)) |
| (call $main-set |
| (struct.new $top) |
| ) |
| (call $main-noset |
| (struct.new $top) |
| ) |
| ) |
| ) |