diff options
author | Alon Zakai <azakai@google.com> | 2022-11-11 10:16:32 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-11 10:16:32 -0800 |
commit | 3928189214e03430bbc9f2b51c6af3887b465160 (patch) | |
tree | 306cfddc749859a597ff1ca11bea52c24b836ebb /test | |
parent | b7697808988037469d352b044bd6b2469f99da55 (diff) | |
download | binaryen-3928189214e03430bbc9f2b51c6af3887b465160.tar.gz binaryen-3928189214e03430bbc9f2b51c6af3887b465160.tar.bz2 binaryen-3928189214e03430bbc9f2b51c6af3887b465160.zip |
[Wasm GC] Add Monomorphize pass (#5238)
Monomorphization finds cases where we send more refined types to a function
than it declares. In such cases we can copy the function and refine the parameters:
// B is a subtype of A
foo(new B());
function foo(x : A) { ..}
=>
foo_B(new B()); // call redirected to refined copy
function foo(x : A) { ..} // unchanged
function foo_B(x : B) { ..} // refined copy
This increases code size so it may not be worth it in all cases. This initial PR is
hopefully enough to start experimenting with this on performance, and so it does
not enable the pass by default.
This adds two variations of monomorphization, one that always does it, and the
default which is "careful": it sees whether monomorphizing lets the refined function
actually be better than the original (say, by removing a cast). If there is no
improvement then we do not make any changes. This saves a significant amount
of code size - on j2wasm the careful version increases by 13% instead of 20% -
but it does run more slowly obviously.
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/help/wasm-opt.test | 6 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 6 | ||||
-rw-r--r-- | test/lit/passes/monomorphize.wast | 590 |
3 files changed, 602 insertions, 0 deletions
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 281411cca..4fd50abcc 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -273,6 +273,12 @@ ;; CHECK-NEXT: --mod-asyncify-never-unwind apply the assumption that ;; CHECK-NEXT: asyncify never unwinds ;; CHECK-NEXT: +;; CHECK-NEXT: --monomorphize creates specialized versions of +;; CHECK-NEXT: functions +;; CHECK-NEXT: +;; CHECK-NEXT: --monomorphize-always creates specialized versions of +;; CHECK-NEXT: functions (even if unhelpful) +;; CHECK-NEXT: ;; CHECK-NEXT: --multi-memory-lowering combines multiple memories into ;; CHECK-NEXT: a single memory ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index f190c8fed..7a782c872 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -232,6 +232,12 @@ ;; CHECK-NEXT: --mod-asyncify-never-unwind apply the assumption that ;; CHECK-NEXT: asyncify never unwinds ;; CHECK-NEXT: +;; CHECK-NEXT: --monomorphize creates specialized versions of +;; CHECK-NEXT: functions +;; CHECK-NEXT: +;; CHECK-NEXT: --monomorphize-always creates specialized versions of +;; CHECK-NEXT: functions (even if unhelpful) +;; CHECK-NEXT: ;; CHECK-NEXT: --multi-memory-lowering combines multiple memories into ;; CHECK-NEXT: a single memory ;; CHECK-NEXT: diff --git a/test/lit/passes/monomorphize.wast b/test/lit/passes/monomorphize.wast new file mode 100644 index 000000000..1cd219d08 --- /dev/null +++ b/test/lit/passes/monomorphize.wast @@ -0,0 +1,590 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; Test in both "always" mode, which always monomorphizes, and in "careful" +;; mode which does it only when it appears to actually help. + +;; RUN: foreach %s %t wasm-opt --nominal --monomorphize-always -all -S -o - | filecheck %s --check-prefix ALWAYS +;; RUN: foreach %s %t wasm-opt --nominal --monomorphize -all -S -o - | filecheck %s --check-prefix CAREFUL + +(module + ;; ALWAYS: (type $A (struct_subtype data)) + ;; CAREFUL: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + ;; ALWAYS: (type $B (struct_subtype $A)) + ;; CAREFUL: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + ;; ALWAYS: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; ALWAYS: (type $none_=>_none (func_subtype func)) + + ;; ALWAYS: (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func)) + + ;; ALWAYS: (import "a" "b" (func $import (param (ref $A)))) + ;; CAREFUL: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; CAREFUL: (type $none_=>_none (func_subtype func)) + + ;; CAREFUL: (import "a" "b" (func $import (param (ref $A)))) + (import "a" "b" (func $import (param (ref $A)))) + + ;; ALWAYS: (func $calls (type $none_=>_none) + ;; ALWAYS-NEXT: (call $refinable + ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable + ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $calls (type $none_=>_none) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls + ;; Two calls with $A, two with $B. The calls to $B should both go to the + ;; same new function which has a refined parameter of $B. + ;; + ;; However, in CAREFUL mode we won't do that, as there is no helpful + ;; improvement in the target functions even with the refined types. + (call $refinable + (struct.new $A) + ) + (call $refinable + (struct.new $A) + ) + (call $refinable + (struct.new $B) + ) + (call $refinable + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $call-import (type $none_=>_none) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $call-import (type $none_=>_none) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $call-import + ;; Calls to imports are left as they are. + (call $import + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A)) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $refinable (param $ref (ref $A)) + ;; Helper function for the above. Use the parameter to see we update types + ;; etc when we make a refined version of the function (if we didn't, + ;; validation would error). + ;; + ;; In CAREFUL mode we optimize to check if refined types help, which has the + ;; side effect of optimizing the body of this function into a nop. + (drop + (local.get $ref) + ) + ) +) + + +;; ALWAYS: (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B)) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +(module + ;; As above, but now the refinable function uses the local in a way that + ;; requires a fixup. + + ;; ALWAYS: (type $A (struct_subtype data)) + ;; CAREFUL: (type $none_=>_none (func_subtype func)) + + ;; CAREFUL: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + ;; ALWAYS: (type $B (struct_subtype $A)) + ;; CAREFUL: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + + + ;; ALWAYS: (type $none_=>_none (func_subtype func)) + + ;; ALWAYS: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; ALWAYS: (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func)) + + ;; ALWAYS: (func $calls (type $none_=>_none) + ;; ALWAYS-NEXT: (call $refinable_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; CAREFUL: (func $calls (type $none_=>_none) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls + (call $refinable + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (local $unref (ref $A)) + ;; ALWAYS-NEXT: (local.set $unref + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.set $ref + ;; ALWAYS-NEXT: (local.get $unref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A)) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $refinable (param $ref (ref $A)) + (local $unref (ref $A)) + (local.set $unref + (local.get $ref) + ) + ;; If we refine $ref then this set will be invalid - we'd be setting a less- + ;; refined type into a local/param that is more refined. We should fix this + ;; up by using a temp local. + (local.set $ref + (local.get $unref) + ) + ) +) + + +;; ALWAYS: (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B)) +;; ALWAYS-NEXT: (local $unref (ref $A)) +;; ALWAYS-NEXT: (local $2 (ref $A)) +;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (local.set $unref +;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (local.get $unref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +(module + ;; Multiple refinings of the same function, and of different functions. + + ;; ALWAYS: (type $A (struct_subtype data)) + ;; CAREFUL: (type $none_=>_none (func_subtype func)) + + ;; CAREFUL: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + ;; ALWAYS: (type $B (struct_subtype $A)) + ;; CAREFUL: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + ;; ALWAYS: (type $none_=>_none (func_subtype func)) + + ;; ALWAYS: (type $C (struct_subtype $B)) + ;; CAREFUL: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; CAREFUL: (type $C (struct_subtype $B)) + (type $C (struct_subtype $B)) + + ;; ALWAYS: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; ALWAYS: (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func)) + + ;; ALWAYS: (type $ref|$C|_=>_none (func_subtype (param (ref $C)) func)) + + ;; ALWAYS: (func $calls1 (type $none_=>_none) + ;; ALWAYS-NEXT: (call $refinable1 + ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable1_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $calls1 (type $none_=>_none) + ;; CAREFUL-NEXT: (call $refinable1 + ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable1 + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls1 + (call $refinable1 + (struct.new $A) + ) + (call $refinable1 + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $calls2 (type $none_=>_none) + ;; ALWAYS-NEXT: (call $refinable1_1 + ;; ALWAYS-NEXT: (struct.new_default $C) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable2_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $calls2 (type $none_=>_none) + ;; CAREFUL-NEXT: (call $refinable1 + ;; CAREFUL-NEXT: (struct.new_default $C) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable2 + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls2 + (call $refinable1 + (struct.new $C) + ) + (call $refinable2 + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $refinable1 (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $refinable1 (type $ref|$A|_=>_none) (param $0 (ref $A)) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $refinable1 (param $ref (ref $A)) + (drop + (local.get $ref) + ) + ) + + ;; ALWAYS: (func $refinable2 (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $refinable2 (type $ref|$A|_=>_none) (param $0 (ref $A)) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $refinable2 (param $ref (ref $A)) + (drop + (local.get $ref) + ) + ) +) + +;; ALWAYS: (func $refinable1_0 (type $ref|$B|_=>_none) (param $ref (ref $B)) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $refinable1_1 (type $ref|$C|_=>_none) (param $ref (ref $C)) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $refinable2_0 (type $ref|$B|_=>_none) (param $ref (ref $B)) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +(module + ;; A case where even CAREFUL mode will monomorphize, as it helps the target + ;; function get optimized better. + + ;; ALWAYS: (type $A (struct_subtype data)) + ;; CAREFUL: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + + ;; ALWAYS: (type $B (struct_subtype $A)) + ;; CAREFUL: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + ;; ALWAYS: (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func)) + + ;; ALWAYS: (type $none_=>_none (func_subtype func)) + + ;; ALWAYS: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; ALWAYS: (import "a" "b" (func $import (param (ref $B)))) + + ;; ALWAYS: (global $global (mut i32) (i32.const 1)) + ;; CAREFUL: (type $ref|$B|_=>_none (func_subtype (param (ref $B)) func)) + + ;; CAREFUL: (type $none_=>_none (func_subtype func)) + + ;; CAREFUL: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; CAREFUL: (import "a" "b" (func $import (param (ref $B)))) + + ;; CAREFUL: (global $global (mut i32) (i32.const 1)) + (global $global (mut i32) (i32.const 1)) + + (import "a" "b" (func $import (param (ref $B)))) + + ;; ALWAYS: (func $calls (type $none_=>_none) + ;; ALWAYS-NEXT: (call $refinable + ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable + ;; ALWAYS-NEXT: (struct.new_default $A) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $refinable_0 + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $calls (type $none_=>_none) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable + ;; CAREFUL-NEXT: (struct.new_default $A) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable_0 + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $refinable_0 + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls + ;; The calls sending $B will switch to calling a refined version, as the + ;; refined version is better, even in CAREFUL mode. + (call $refinable + (struct.new $A) + ) + (call $refinable + (struct.new $A) + ) + (call $refinable + (struct.new $B) + ) + (call $refinable + (struct.new $B) + ) + ) + + ;; ALWAYS: (func $refinable (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (local $x (ref $A)) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (ref.cast_static $B + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (local.set $x + ;; ALWAYS-NEXT: (select (result (ref $A)) + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: (global.get $global) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (ref.cast_static $B + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (ref.cast_static $B + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (ref.cast_static $B + ;; ALWAYS-NEXT: (local.get $ref) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $refinable (type $ref|$A|_=>_none) (param $0 (ref $A)) + ;; CAREFUL-NEXT: (local $1 (ref $A)) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (ref.cast_static $B + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (ref.cast_static $B + ;; CAREFUL-NEXT: (local.tee $1 + ;; CAREFUL-NEXT: (select (result (ref $A)) + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: (global.get $global) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (ref.cast_static $B + ;; CAREFUL-NEXT: (local.get $1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (ref.cast_static $B + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $refinable (param $ref (ref $A)) + (local $x (ref $A)) + ;; The refined version of this function will not have the cast, since + ;; optimizations manage to remove it using the more refined type. + ;; + ;; (That is the case in CAREFUL mode, which optimizes; in ALWAYS mode the + ;; cast will remain since we monomorphize without bothering to optimize and + ;; see if there is any benefit.) + (call $import + (ref.cast_static $B + (local.get $ref) + ) + ) + ;; Also copy the param into a local. The local should get refined to $B in + ;; the refined function in CAREFUL mode. + (local.set $x + ;; Use a select here so optimizations don't just merge $x and $ref. + (select (result (ref $A)) + (local.get $ref) + (struct.new $B) + (global.get $global) + ) + ) + (call $import + (ref.cast_static $B + (local.get $x) + ) + ) + (call $import + (ref.cast_static $B + (local.get $x) + ) + ) + ;; Another use of $ref, also to avoid opts merging $x and $ref. + (call $import + (ref.cast_static $B + (local.get $ref) + ) + ) + ) +) + +;; ALWAYS: (func $refinable_0 (type $ref|$B|_=>_none) (param $ref (ref $B)) +;; ALWAYS-NEXT: (local $x (ref $A)) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast_static $B +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (select (result (ref $B)) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (struct.new_default $B) +;; ALWAYS-NEXT: (global.get $global) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast_static $B +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast_static $B +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast_static $B +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $refinable_0 (type $ref|$B|_=>_none) (param $0 (ref $B)) +;; CAREFUL-NEXT: (local $1 (ref $B)) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (local.get $0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (local.tee $1 +;; CAREFUL-NEXT: (select (result (ref $B)) +;; CAREFUL-NEXT: (local.get $0) +;; CAREFUL-NEXT: (struct.new_default $B) +;; CAREFUL-NEXT: (global.get $global) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (local.get $1) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (local.get $0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) +(module + ;; Test that we avoid recursive calls. + + ;; ALWAYS: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; ALWAYS: (type $A (struct_subtype data)) + ;; CAREFUL: (type $ref|$A|_=>_none (func_subtype (param (ref $A)) func)) + + ;; CAREFUL: (type $A (struct_subtype data)) + (type $A (struct_subtype data)) + ;; ALWAYS: (type $B (struct_subtype $A)) + ;; CAREFUL: (type $B (struct_subtype $A)) + (type $B (struct_subtype $A)) + + + ;; ALWAYS: (func $calls (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; ALWAYS-NEXT: (call $calls + ;; ALWAYS-NEXT: (struct.new_default $B) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $calls (type $ref|$A|_=>_none) (param $ref (ref $A)) + ;; CAREFUL-NEXT: (call $calls + ;; CAREFUL-NEXT: (struct.new_default $B) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls (param $ref (ref $A)) + ;; We should change nothing in this recursive call, even though we are + ;; sending a more refined type, so we could try to monomorphize in theory. + (call $calls + (struct.new $B) + ) + ) +) |