blob: 006cd17030f2e61e822935f51257d0617f866b97 [file] [log] [blame] [edit]
;; 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 --monomorphize-always -all -S -o - | filecheck %s --check-prefix ALWAYS
;; RUN: foreach %s %t wasm-opt --monomorphize -all -S -o - | filecheck %s --check-prefix CAREFUL
(module
;; ALWAYS: (type $A (struct ))
;; CAREFUL: (type $A (struct ))
(type $A (struct_subtype data))
;; ALWAYS: (type $B (sub $A (struct )))
;; CAREFUL: (type $B (sub $A (struct )))
(type $B (struct_subtype $A))
;; ALWAYS: (type $ref|$A|_=>_none (func (param (ref $A))))
;; ALWAYS: (type $none_=>_none (func))
;; ALWAYS: (type $ref|$B|_=>_none (func (param (ref $B))))
;; ALWAYS: (import "a" "b" (func $import (type $ref|$A|_=>_none) (param (ref $A))))
;; CAREFUL: (type $ref|$A|_=>_none (func (param (ref $A))))
;; CAREFUL: (type $none_=>_none (func))
;; CAREFUL: (import "a" "b" (func $import (type $ref|$A|_=>_none) (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_4
;; ALWAYS-NEXT: (struct.new_default $B)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $refinable_4
;; 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_4 (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 ))
;; CAREFUL: (type $none_=>_none (func))
;; CAREFUL: (type $A (struct ))
(type $A (struct_subtype data))
;; ALWAYS: (type $B (sub $A (struct )))
;; CAREFUL: (type $B (sub $A (struct )))
(type $B (struct_subtype $A))
;; ALWAYS: (type $none_=>_none (func))
;; ALWAYS: (type $ref|$A|_=>_none (func (param (ref $A))))
;; ALWAYS: (type $ref|$B|_=>_none (func (param (ref $B))))
;; ALWAYS: (func $calls (type $none_=>_none)
;; ALWAYS-NEXT: (call $refinable_2
;; ALWAYS-NEXT: (struct.new_default $B)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; CAREFUL: (type $ref|$A|_=>_none (func (param (ref $A))))
;; 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_2 (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 ))
;; CAREFUL: (type $none_=>_none (func))
;; CAREFUL: (type $A (struct ))
(type $A (struct_subtype data))
;; ALWAYS: (type $B (sub $A (struct )))
;; CAREFUL: (type $B (sub $A (struct )))
(type $B (struct_subtype $A))
;; ALWAYS: (type $none_=>_none (func))
;; ALWAYS: (type $C (sub $B (struct )))
;; CAREFUL: (type $ref|$A|_=>_none (func (param (ref $A))))
;; CAREFUL: (type $C (sub $B (struct )))
(type $C (struct_subtype $B))
;; ALWAYS: (type $ref|$A|_=>_none (func (param (ref $A))))
;; ALWAYS: (type $ref|$B|_=>_none (func (param (ref $B))))
;; ALWAYS: (type $ref|$C|_=>_none (func (param (ref $C))))
;; ALWAYS: (func $calls1 (type $none_=>_none)
;; ALWAYS-NEXT: (call $refinable1
;; ALWAYS-NEXT: (struct.new_default $A)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $refinable1_4
;; 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_5
;; ALWAYS-NEXT: (struct.new_default $C)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $refinable2_6
;; 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_4 (type $ref|$B|_=>_none) (param $ref (ref $B))
;; ALWAYS-NEXT: (drop
;; ALWAYS-NEXT: (local.get $ref)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS: (func $refinable1_5 (type $ref|$C|_=>_none) (param $ref (ref $C))
;; ALWAYS-NEXT: (drop
;; ALWAYS-NEXT: (local.get $ref)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS: (func $refinable2_6 (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 ))
;; CAREFUL: (type $A (struct ))
(type $A (struct_subtype data))
;; ALWAYS: (type $B (sub $A (struct )))
;; CAREFUL: (type $B (sub $A (struct )))
(type $B (struct_subtype $A))
;; ALWAYS: (type $ref|$B|_=>_none (func (param (ref $B))))
;; ALWAYS: (type $none_=>_none (func))
;; ALWAYS: (type $ref|$A|_=>_none (func (param (ref $A))))
;; ALWAYS: (import "a" "b" (func $import (type $ref|$B|_=>_none) (param (ref $B))))
;; ALWAYS: (global $global (mut i32) (i32.const 1))
;; CAREFUL: (type $ref|$B|_=>_none (func (param (ref $B))))
;; CAREFUL: (type $none_=>_none (func))
;; CAREFUL: (type $ref|$A|_=>_none (func (param (ref $A))))
;; CAREFUL: (import "a" "b" (func $import (type $ref|$B|_=>_none) (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_3
;; ALWAYS-NEXT: (struct.new_default $B)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $refinable_3
;; 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_3
;; CAREFUL-NEXT: (struct.new_default $B)
;; CAREFUL-NEXT: )
;; CAREFUL-NEXT: (call $refinable_3
;; 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 $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 $B
;; ALWAYS-NEXT: (local.get $x)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $import
;; ALWAYS-NEXT: (ref.cast $B
;; ALWAYS-NEXT: (local.get $x)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $import
;; ALWAYS-NEXT: (ref.cast $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 $B
;; CAREFUL-NEXT: (local.get $0)
;; CAREFUL-NEXT: )
;; CAREFUL-NEXT: )
;; CAREFUL-NEXT: (call $import
;; CAREFUL-NEXT: (ref.cast $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 $B
;; CAREFUL-NEXT: (local.get $1)
;; CAREFUL-NEXT: )
;; CAREFUL-NEXT: )
;; CAREFUL-NEXT: (call $import
;; CAREFUL-NEXT: (ref.cast $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 $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 $B
(local.get $x)
)
)
(call $import
(ref.cast $B
(local.get $x)
)
)
;; Another use of $ref, also to avoid opts merging $x and $ref.
(call $import
(ref.cast $B
(local.get $ref)
)
)
)
)
;; ALWAYS: (func $refinable_3 (type $ref|$B|_=>_none) (param $ref (ref $B))
;; ALWAYS-NEXT: (local $x (ref $A))
;; ALWAYS-NEXT: (call $import
;; ALWAYS-NEXT: (ref.cast $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 $B
;; ALWAYS-NEXT: (local.get $x)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $import
;; ALWAYS-NEXT: (ref.cast $B
;; ALWAYS-NEXT: (local.get $x)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: (call $import
;; ALWAYS-NEXT: (ref.cast $B
;; ALWAYS-NEXT: (local.get $ref)
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; ALWAYS-NEXT: )
;; CAREFUL: (func $refinable_3 (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 $A (struct ))
;; CAREFUL: (type $A (struct ))
(type $A (struct_subtype data))
;; ALWAYS: (type $ref|$A|_=>_none (func (param (ref $A))))
;; ALWAYS: (type $B (sub $A (struct )))
;; CAREFUL: (type $ref|$A|_=>_none (func (param (ref $A))))
;; CAREFUL: (type $B (sub $A (struct )))
(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)
)
)
)