;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once ;; A minimal "once" function. It is so trivial we can remove its body. (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) ;; Add some more content in the function. (drop (i32.const 100)) ) ;; CHECK: (func $caller-if-1 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; 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) (then (block (call $once) (call $once) (call $once) (call $once) ) ) ) (call $once) (call $once) ) ;; CHECK: (func $caller-if-2 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; 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) (then (call $once) ) (else (block (call $once) (call $once) ) ) ) (call $once) (call $once) ) ;; CHECK: (func $caller-loop-1 (type $0) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; 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) (then (call $once) ) ) (call $once) (call $once) (br_if $loop (i32.const 1)) ) (call $once) (call $once) ) ;; CHECK: (func $caller-loop-2 (type $0) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; 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) (then (call $once) ) ) (br_if $loop (i32.const 1)) ) (call $once) (call $once) ) ;; CHECK: (func $caller-single (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $caller-single ;; A short function with a single call. (call $once) ) ;; CHECK: (func $caller-empty (type $0) ;; 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 $0 (func)) ;; CHECK: (import "env" "foo" (func $foo (type $0))) (import "env" "foo" (func $foo)) ;; CHECK: (global $once (mut i32) (i32.const 42)) (global $once (mut i32) (i32.const 42)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) (call $foo) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (import "env" "foo" (func $foo (type $0))) (import "env" "foo" (func $foo)) ;; CHECK: (global $once (mut i32) (i32.const 42)) (global $once (mut i32) (i32.const 42)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) (call $foo) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (import "env" "foo" (func $foo (type $0))) (import "env" "foo" (func $foo)) ;; CHECK: (global $once (mut i32) (i32.const 42)) (global $once (mut i32) (i32.const 42)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (nop) (global.set $once (i32.const 1)) (call $foo) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (import "env" "foo" (func $foo (type $0))) (import "env" "foo" (func $foo)) ;; CHECK: (global $once (mut i32) (i32.const 42)) (global $once (mut i32) (i32.const 42)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (call $foo) ;; CHECK-NEXT: ) ;; 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) (then (return) ) (else (call $foo) ) ) (global.set $once (i32.const 1)) (call $foo) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once2 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once1) (then (return) ) ) (global.set $once2 (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 0)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 42)) ) ;; CHECK: (func $caller (type $0) ;; 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 (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) (call $once) ) ) ;; Corner case: Non-integer global, which we ignore. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut f64) (f64.const 0)) (global $once (mut f64) (f64.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.trunc_f64_s ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) ) (then (return) ) ) (global.set $once (f64.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (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 (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller (call $once) (call $once) ) ) ;; Corner case: Non-constant later value. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.eqz (i32.eqz (i32.const 1)))) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func (param i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) (param $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $1) ;; 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 $0 (func (result i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) (result i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; 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) (then (return (i32.const 2)) ) ) (global.set $once (i32.const 1)) (i32.const 3) ) ;; CHECK: (func $caller (type $1) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (loop $loop ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) ) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) (drop (global.get $once)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller (if (i32.const 1) (then (call $once) ) ) (if (i32.const 1) (then (call $once) ) ) (if (i32.const 1) (then (call $once) ) ) (call $once) (if (i32.const 1) (then (call $once) ) ) (call $once) (if (i32.const 1) (then (nop) ) (else (nop) ) ) (call $once) (if (i32.const 1) (then (nop) ) (else (call $once) ) ) (call $once) (if (i32.const 1) (then (call $once) ) ) (call $once) (if (i32.const 1) (then (nop) ) (else (call $once) ) ) (call $once) (if (i32.const 1) (then (call $once) ) ) (call $once) (if (i32.const 1) (then (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 $0 (func)) ;; CHECK: (type $1 (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 (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $try-catch (type $0) ;; CHECK-NEXT: (try $label$5 ;; CHECK-NEXT: (do ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) ;; 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) (then (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 $0 (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $many1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once2) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $many2) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (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 $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $A (type $0) ;; 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 (type $0) ;; CHECK-NEXT: (call $C) ;; CHECK-NEXT: ) (func $B (call $C) ) ;; CHECK: (func $C (type $0) ;; CHECK-NEXT: (call $D) ;; CHECK-NEXT: ) (func $C (call $D) ) ;; CHECK: (func $D (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $D (call $once) (call $once) ) ;; CHECK: (func $bad-A (type $0) ;; 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 (type $0) ;; 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 $0 (func)) ;; CHECK: (import "env" "glob" (global $once (mut i32))) (import "env" "glob" (global $once (mut i32))) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; 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 $0 (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 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $caller (call $once) (call $once) ) ) ;; Test calls of other "once" functions in "once" functions. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (global $once.1 (mut i32) (i32.const 0)) (global $once.1 (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: ) (func $once ;; A minimal "once" function, which calls another. We can remove the first ;; two lines here (the early-exit logic). (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) (call $once.1) ) ;; CHECK: (func $once.1 (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once.1 ;; Another minimal "once" function. It has no payload and we can empty it ;; out. (if (global.get $once.1) (then (return) ) ) (global.set $once.1 (i32.const 1)) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller ;; Call a once function more than once. The second call will become a nop. (call $once) (call $once) ) ;; CHECK: (func $caller$1 (type $0) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller$1 ;; Two calls to the second function. Again, the second becomes a nop. (call $once.1) (call $once.1) ) ;; CHECK: (func $caller$2 (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: ) (func $caller$2 ;; A mix of calls. We can remove the second in principle, because we know ;; the first will call it, but we leave that for later optimizations ;; ($once turns into a call to $once.1 that inlining will remove, and then ;; we'll have two identical calls here). (call $once) (call $once.1) ) ;; CHECK: (func $caller$3 (type $0) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $caller$3 ;; Reverse of the above; again we cannot optimize (as $once.1 does not call ;; $once). (call $once.1) (call $once) ) ;; CHECK: (func $caller$4 (type $0) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (call $caller$4) ;; CHECK-NEXT: ) (func $caller$4 ;; Here we cannot optimize, since the second function is not a "once" ;; function. (call $once.1) (call $caller$4) ) ) ;; Test loops between "once" functions. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (global $once.1 (mut i32) (i32.const 0)) (global $once.1 (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: ) (func $once ;; This "once" function calls another, and so we can remove the early-exit ;; logic. (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) (call $once.1) ) ;; CHECK: (func $once.1 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once.1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once.1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $once.1 ;; This early-exit logic looks removable, since we call another "once" ;; function. However, we remove that function's early-exit logic, so we ;; cannot do so here (it would risk an infinite loop). (if (global.get $once.1) (then (return) ) ) (global.set $once.1 (i32.const 1)) (call $once) ;; This call was added. ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller ;; The second call will become a nop. (call $once) (call $once) ) ;; CHECK: (func $caller$1 (type $0) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller$1 ;; Again, the second becomes a nop. (call $once.1) (call $once.1) ) ;; CHECK: (func $caller$2 (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: ) (func $caller$2 ;; The second could become a nop, but we leave that for later optimizations ;; (after inlining the call to $once becomes a call to $once.1). (call $once) (call $once.1) ) ;; CHECK: (func $caller$3 (type $0) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $caller$3 ;; Reverse order, same result (no optimization). (call $once.1) (call $once) ) ) ;; Test a dangerous triple loop. (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $1 (func (param i32))) ;; CHECK: (import "env" "foo" (func $import (type $1) (param i32))) (import "env" "foo" (func $import (param i32))) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (global $once.1 (mut i32) (i32.const 0)) (global $once.1 (mut i32) (i32.const 0)) ;; CHECK: (global $once.2 (mut i32) (i32.const 0)) (global $once.2 (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (call $once.2) ;; CHECK-NEXT: (call $import ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) (call $once.1) ;; We cannot remove this second call. While $once.1 calls $once.2, we may ;; be in this situation: a call started at $once.1, which calls $once ;; (here) which then calls $once.1 which immediately exits (as the global ;; has been set for it), and then we call $once.2 from here, which calls ;; the other two that immediately exit as well, and then we call the import ;; from there with value 2. Then we call it from here with value 0, and ;; then we return to the caller, $once.1, which calls with 1, so we have ;; 2, 0, 1. If we remove the call here to $once.2 then the order would be ;; 0, 2, 1. ;; ;; The problem is that the setting of the global happens at the very start ;; of the once function, but that does not mean we have executed the body ;; yet, and without executing it, we cannot infer anything about other ;; globals. (call $once.2) (call $import (i32.const 0) ) ) ;; CHECK: (func $once.1 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once.1) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once.1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $once.2) ;; CHECK-NEXT: (call $import ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once.1 (if (global.get $once.1) (then (return) ) ) (global.set $once.1 (i32.const 1)) (call $once) ;; As above, by symmetry, we cannot remove this second call. (call $once.2) (call $import (i32.const 1) ) ) ;; CHECK: (func $once.2 (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once.2) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once.2 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $once.1) ;; CHECK-NEXT: (call $import ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $once.2 (if (global.get $once.2) (then (return) ) ) (global.set $once.2 (i32.const 1)) (call $once) ;; As above, by symmetry, we cannot remove this second call. (call $once.1) (call $import (i32.const 2) ) ) ) ;; Test a self-loop. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; 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) (then (return) ) ) (global.set $once (i32.const 1)) ;; A recursive call. This of course does not recurse infinitely since the ;; next call early exits, as the global is set, and for that reason we can ;; optimize this to a nop. (call $once) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller ;; The second call will become a nop. (call $once) (call $once) ) ) ;; Calls from non-"once" functions to "once" functions. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $once ;; A minimal "once" function. (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ) ;; CHECK: (func $do-once (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $do-once ;; Call the once function. (call $once) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $do-once) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $caller ;; The first proves the second is not needed, and can be nopped. (call $do-once) (call $once) ) ;; CHECK: (func $caller2 (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $do-once) ;; CHECK-NEXT: ) (func $caller2 ;; Reverse order of the above. We cannot optimize here: $do-once does ;; nothing aside from call $once, but all we know is that it is not a "once" ;; function itself, and we only remove calls to "once" functions. (call $once) (call $do-once) ) ) ;; Calls from "once" functions to non-"once" functions. (module ;; CHECK: (type $0 (func)) ;; CHECK: (global $once (mut i32) (i32.const 0)) (global $once (mut i32) (i32.const 0)) ;; CHECK: (func $once (type $0) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (global.get $once) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $once ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $other) ;; CHECK-NEXT: ) (func $once ;; We should not remove this early-exit logic. (if (global.get $once) (then (return) ) ) (global.set $once (i32.const 1)) ;; A call to a non-"once" function. (call $other) ) ;; CHECK: (func $other (type $0) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) (func $other ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $other) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: ) (func $caller ;; There is nothing to optimize here. (call $other) (call $once) ) ;; CHECK: (func $caller2 (type $0) ;; CHECK-NEXT: (call $once) ;; CHECK-NEXT: (call $other) ;; CHECK-NEXT: ) (func $caller2 ;; Reverse order of the above. Also nothing to do. (call $once) (call $other) ) )