summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/lit/help/optimization-opts.test3
-rw-r--r--test/lit/passes/once-reduction.wast1429
2 files changed, 1432 insertions, 0 deletions
diff --git a/test/lit/help/optimization-opts.test b/test/lit/help/optimization-opts.test
index 52695077a..18f8970c2 100644
--- a/test/lit/help/optimization-opts.test
+++ b/test/lit/help/optimization-opts.test
@@ -348,6 +348,9 @@
;; CHECK-NEXT: is valid if the C runtime will
;; CHECK-NEXT: never be exited
;; CHECK-NEXT:
+;; CHECK-NEXT: --once-reduction reduces calls to code that only
+;; CHECK-NEXT: runs once
+;; CHECK-NEXT:
;; CHECK-NEXT: --optimize-added-constants optimizes added constants into
;; CHECK-NEXT: load/store offsets
;; CHECK-NEXT:
diff --git a/test/lit/passes/once-reduction.wast b/test/lit/passes/once-reduction.wast
new file mode 100644
index 000000000..6cd0c9d64
--- /dev/null
+++ b/test/lit/passes/once-reduction.wast
@@ -0,0 +1,1429 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --once-reduction -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ ;; A minimal "once" function.
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller
+ ;; Call a once function more than once, in a way that we can optimize: the
+ ;; first dominates the second. The second call will become a nop.
+ (call $once)
+ (call $once)
+ )
+)
+
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 100)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ ;; Add some more content in the function.
+ (drop (i32.const 100))
+ )
+
+ ;; CHECK: (func $caller-if-1
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-if-1
+ ;; Add more calls, and ones that are conditional.
+ (if
+ (i32.const 1)
+ (block
+ (call $once)
+ (call $once)
+ (call $once)
+ (call $once)
+ )
+ )
+ (call $once)
+ (call $once)
+ )
+
+ ;; CHECK: (func $caller-if-2
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-if-2
+ ;; Call in both arms. As we only handle dominance, and not merges, the first
+ ;; call after the if is *not* optimized.
+ (if
+ (i32.const 1)
+ (call $once)
+ (block
+ (call $once)
+ (call $once)
+ )
+ )
+ (call $once)
+ (call $once)
+ )
+
+ ;; CHECK: (func $caller-loop-1
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (br_if $loop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-loop-1
+ ;; Add calls in a loop.
+ (loop $loop
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (call $once)
+ (br_if $loop (i32.const 1))
+ )
+ (call $once)
+ (call $once)
+ )
+
+ ;; CHECK: (func $caller-loop-2
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br_if $loop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-loop-2
+ ;; Add a single conditional call in a loop.
+ (loop $loop
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (br_if $loop (i32.const 1))
+ )
+ (call $once)
+ (call $once)
+ )
+
+ ;; CHECK: (func $caller-single
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller-single
+ ;; A short function with a single call.
+ (call $once)
+ )
+
+ ;; CHECK: (func $caller-empty
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-empty
+ ;; A tiny function with nothing at all, just to verify we do not crash on
+ ;; such things.
+ )
+)
+
+;; Corner case: Initial value is not zero. We can still optimize this here,
+;; though in fact the function will never execute the payload call of foo(),
+;; which in theory we could further optimize.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "foo" (func $foo))
+ (import "env" "foo" (func $foo))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 42))
+ (global $once (mut i32) (i32.const 42))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ (call $foo)
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: function is not quite once, there is code before the if, so no
+;; optimization will happen.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "foo" (func $foo))
+ (import "env" "foo" (func $foo))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 42))
+ (global $once (mut i32) (i32.const 42))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $once
+ (nop)
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ (call $foo)
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: a nop after the if.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "foo" (func $foo))
+ (import "env" "foo" (func $foo))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 42))
+ (global $once (mut i32) (i32.const 42))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (nop)
+ (global.set $once (i32.const 1))
+ (call $foo)
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: The if has an else.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "foo" (func $foo))
+ (import "env" "foo" (func $foo))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 42))
+ (global $once (mut i32) (i32.const 42))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo)
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ (call $foo)
+ )
+ (global.set $once (i32.const 1))
+ (call $foo)
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: different global names in the get and set
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once1 (mut i32) (i32.const 0))
+ (global $once1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $once2 (mut i32) (i32.const 0))
+ (global $once2 (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once1)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once2
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once1)
+ (return)
+ )
+ (global.set $once2 (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: The global is written a zero.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 0))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: The global is written a zero elsewhere.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ (global.set $once (i32.const 0))
+ )
+)
+
+;; Corner case: The global is written a non-zero value elsewhere. This is ok to
+;; optimize, and in fact we can write a value different than 1 both there and
+;; in the "once" function, and we can still optimize.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 42))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ (global.set $once (i32.const 1337))
+ )
+
+ ;; CHECK: (func $caller-2
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller-2
+ ;; Reverse order of the above.
+ (global.set $once (i32.const 1337))
+ (call $once)
+ (call $once)
+ )
+)
+
+;; It is ok to call the "once" function inside itself - as that call appears
+;; behind a set of the global, the call is redundant and we optimize it away.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ (call $once)
+ )
+)
+
+;; Corner case: Non-integer global, which we ignore.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut f64) (f64.const 0))
+ (global $once (mut f64) (f64.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.trunc_f64_s
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (f64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ ;; We must cast this to an integer for the wasm to validate.
+ (i32.trunc_f64_s
+ (global.get $once)
+ )
+ (return)
+ )
+ (global.set $once (f64.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Non-constant initial value. This is fine, as the initial value
+;; does not matter (if it is zero, then this is a "classic" "once" global; if
+;; not then it will never be written to, and the "once" function will never run
+;; at all, which is fine too)
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "glob" (global $import i32))
+ (import "env" "glob" (global $import i32))
+
+ ;; CHECK: (global $once (mut i32) (global.get $import))
+ (global $once (mut i32) (global.get $import))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Non-constant later value.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.eqz
+ ;; CHECK-NEXT: (i32.eqz
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.eqz (i32.eqz (i32.const 1))))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: "Once" function has a param.
+(module
+ ;; CHECK: (type $i32_=>_none (func (param i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once (param $x i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once (param $x i32)
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once (i32.const 1))
+ (call $once (i32.const 1))
+ )
+)
+
+;; Corner case: "Once" function has a result.
+(module
+ ;; CHECK: (type $none_=>_i32 (func (result i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ (func $once (result i32)
+ (if
+ (global.get $once)
+ (return (i32.const 2))
+ )
+ (global.set $once (i32.const 1))
+ (i32.const 3)
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (drop (call $once))
+ (drop (call $once))
+ )
+)
+
+;; Corner case: "Once" function body is not a block.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (loop $loop
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (loop $loop
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Once body is too short.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Additional reads of the global.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ (drop (global.get $once))
+ )
+)
+
+;; Corner case: Additional reads of the global in the "once" func.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ (drop (global.get $once))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Optimization opportunties in unreachable code (which we can
+;; ignore, but should not error on).
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ (unreachable)
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Add a very long chain of control flow.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (nop)
+ (nop)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (nop)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (nop)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ (call $once)
+ (call $once)
+ )
+)
+
+;; A test with a try-catch. This verifies that we emit their contents properly
+;; in reverse postorder and do not hit any assertions.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (type $i32_=>_none (func (param i32)))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (tag $tag (param i32))
+ (tag $tag (param i32))
+
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $try-catch
+ ;; CHECK-NEXT: (try $label$5
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $try-catch
+ (try $label$5
+ (do
+ (if
+ (i32.const 1)
+ (call $once)
+ )
+ )
+ (catch $tag
+ (drop
+ (pop i32)
+ )
+ )
+ )
+ )
+)
+
+(module
+ ;; Test a module with more than one global that we can optimize, and more than
+ ;; one that we cannot.
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once1 (mut i32) (i32.const 0))
+ (global $once1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $many1 (mut i32) (i32.const 0))
+ (global $many1 (mut i32) (i32.const 0))
+ ;; CHECK: (global $once2 (mut i32) (i32.const 0))
+ (global $once2 (mut i32) (i32.const 0))
+ ;; CHECK: (global $many2 (mut i32) (i32.const 0))
+ (global $many2 (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once1
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once1)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once1
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (call $once2)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: )
+ (func $once1
+ (if
+ (global.get $once1)
+ (return)
+ )
+ (global.set $once1 (i32.const 1))
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ )
+
+ ;; CHECK: (func $many1
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $many1)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $many1
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (call $once1)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (call $once2)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $many1
+ (if
+ (global.get $many1)
+ (return)
+ )
+ (global.set $many1 (i32.const 0)) ;; prevent this global being "once"
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ )
+
+ ;; CHECK: (func $once2
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once2)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once2
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (call $once1)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: )
+ (func $once2
+ (if
+ (global.get $once2)
+ (return)
+ )
+ (global.set $once2 (i32.const 2))
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ )
+
+ ;; CHECK: (func $many2
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $many2)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $many1
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (call $once2)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (call $once1)
+ ;; CHECK-NEXT: (call $many1)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (call $many2)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $many2
+ (if
+ (global.get $many2)
+ (return)
+ )
+ (global.set $many1 (i32.const 0))
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ (call $many1)
+ (call $once2)
+ (call $many2)
+ (call $once1)
+ )
+)
+
+;; Test for propagation of information about called functions: if A->B->C->D
+;; and D calls some "once" functions, then A can infer that it's call to B does
+;; so.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $A
+ ;; CHECK-NEXT: (call $B)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $A
+ ;; We can infer that calling B calls C and then D, and D calls this "once"
+ ;; function, so we can remove the call after it
+ (call $B)
+ (call $once)
+ )
+
+ ;; CHECK: (func $B
+ ;; CHECK-NEXT: (call $C)
+ ;; CHECK-NEXT: )
+ (func $B
+ (call $C)
+ )
+
+ ;; CHECK: (func $C
+ ;; CHECK-NEXT: (call $D)
+ ;; CHECK-NEXT: )
+ (func $C
+ (call $D)
+ )
+
+ ;; CHECK: (func $D
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $D
+ (call $once)
+ (call $once)
+ )
+
+ ;; CHECK: (func $bad-A
+ ;; CHECK-NEXT: (call $bad-B)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $bad-A
+ ;; Call a function that does *not* do anything useful. We should not remove
+ ;; the second call here.
+ (call $bad-B)
+ (call $once)
+ )
+
+ ;; CHECK: (func $bad-B
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $bad-B
+ )
+)
+
+;; Corner case: Imported mutable global. We cannot optimize it, since the
+;; outside may read and write it.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "glob" (global $once (mut i32)))
+ (import "env" "glob" (global $once (mut i32)))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)
+
+;; Corner case: Exported mutable global. We cannot optimize it, since the
+;; outside may read and write it.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $once (mut i32) (i32.const 0))
+ (global $once (mut i32) (i32.const 0))
+
+ ;; CHECK: (export "once-global" (global $once))
+ (export "once-global" (global $once))
+
+ ;; CHECK: (func $once
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (global.get $once)
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $once
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $once
+ (if
+ (global.get $once)
+ (return)
+ )
+ (global.set $once (i32.const 1))
+ )
+
+ ;; CHECK: (func $caller
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: (call $once)
+ ;; CHECK-NEXT: )
+ (func $caller
+ (call $once)
+ (call $once)
+ )
+)