summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-03-18 14:18:09 -0700
committerGitHub <noreply@github.com>2024-03-18 14:18:09 -0700
commitd8086c63a9e3e6bbd1bdc5d7e0843af8433cc4c8 (patch)
treeb46989cd5f1e397b6eda83ca7741a1a5d2716728 /test
parentc166ca015860b337e9ce07a5e02cb707964056ba (diff)
downloadbinaryen-d8086c63a9e3e6bbd1bdc5d7e0843af8433cc4c8.tar.gz
binaryen-d8086c63a9e3e6bbd1bdc5d7e0843af8433cc4c8.tar.bz2
binaryen-d8086c63a9e3e6bbd1bdc5d7e0843af8433cc4c8.zip
DeadArgumentElimination/SignaturePruning: Prune params even if called with effects (#6395)
Before this PR, when we saw a param was unused we sometimes could not remove it. For example, if there was one call like this: (call $target (call $other) ) That nested call has effects, so we can't just remove it from the outer call - we'd need to move it first. That motion was hard to integrate which was why it was left out, but it turns out that is sometimes very important. E.g. in Java it is common to have such calls that send the this parameter as the result of another call; not being able to remove such params meant we kept those nested calls alive, creating empty structs just to have something to send there. To fix this, this builds on top of #6394 which makes it easier to move all children out of a parent, leaving only nested things that can be easily moved around and removed. In more detail, DeadArgumentElimination/SignaturePruning track whether we run into effects that prevent removing a field. If we do, then we queue an operation to move the children out, which we do using a new utility ParamUtils::localizeCallsTo. The pass then does another iteration after that operation. Alternatively we could try to move things around immediately, but that is quite hard: those passes already track a lot of state. It is simpler to do the fixup in an entirely separate utility. That does come at the cost of the utility doing another pass on the module and the pass itself running another iteration, but this situation is not the most common.
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/dae_all-features.wast255
-rw-r--r--test/lit/passes/dae_tnh.wast52
-rw-r--r--test/lit/passes/signature-pruning.wast301
3 files changed, 545 insertions, 63 deletions
diff --git a/test/lit/passes/dae_all-features.wast b/test/lit/passes/dae_all-features.wast
index d4390351f..17ea77942 100644
--- a/test/lit/passes/dae_all-features.wast
+++ b/test/lit/passes/dae_all-features.wast
@@ -109,25 +109,31 @@
(func $b33
(call $a3 (i32.const 4))
)
- ;; CHECK: (func $a4 (type $1) (param $x i32)
+ ;; CHECK: (func $a4 (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
- (func $a4 (param $x i32) ;; diff value, but with effects
+ (func $a4 (param $x i32)
+ ;; This function is called with one constant and one unreachable. We can
+ ;; remove the param despite the unreachable's effects.
)
;; CHECK: (func $b4 (type $0)
- ;; CHECK-NEXT: (call $a4
- ;; CHECK-NEXT: (unreachable)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $b4
+ ;; This call will vanish entirely, because the unreachable child executes
+ ;; first (so we cannot see here that we removed the parameter from $a4, but
+ ;; that can be confirmed in $a4 itself).
(call $a4 (unreachable))
)
;; CHECK: (func $b43 (type $0)
- ;; CHECK-NEXT: (call $a4
- ;; CHECK-NEXT: (i32.const 4)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $a4)
;; CHECK-NEXT: )
(func $b43
+ ;; We will remove the parameter here.
(call $a4 (i32.const 4))
)
;; CHECK: (func $a5 (type $0)
@@ -659,27 +665,33 @@
)
(module
- ;; CHECK: (type $0 (func (param i32)))
+ ;; CHECK: (type $0 (func))
- ;; CHECK: (func $0 (type $0) (param $0 i32)
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (call $0
+ ;; CHECK: (func $0 (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
- ;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
(func $0 (param $0 i32) (result i32)
- ;; The result of this function can be removed, which makes us modify the
- ;; returns (we should not return a value any more) and also the calls (the
- ;; calls must be dropped). The returns here are nested in each other, and one
- ;; is a recursive call to this function itself, which makes this a corner case
- ;; we might emit invalid code for.
+ ;; The returns here are nested in each other, and one is a recursive call to
+ ;; this function itself, which makes this a corner case we might emit invalid
+ ;; code for. We end up removing the parameter, and then the call vanishes as
+ ;; it was unreachable; we also remove the return as well as it is dropped in
+ ;; the other caller, below.
(return
(drop
(call $0
@@ -690,6 +702,17 @@
)
)
)
+
+ ;; CHECK: (func $other-call (type $0)
+ ;; CHECK-NEXT: (call $0)
+ ;; CHECK-NEXT: )
+ (func $other-call
+ (drop
+ (call $0
+ (i32.const 1)
+ )
+ )
+ )
)
(module
@@ -727,3 +750,193 @@
)
)
)
+
+(module
+ ;; CHECK: (type $0 (func (param f64) (result i32)))
+
+ ;; CHECK: (func $target (type $0) (param $0 f64) (result i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (local $3 i32)
+ ;; CHECK-NEXT: (local $4 i32)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (f64.const 1.1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (f64.const 4.4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $a i32) (param $b f64) (param $c i32) (result i32)
+ ;; Test removing a parameter despite calls having interesting non-unreachable
+ ;; effects. This also tests recursion of such calls. We can remove all the i32
+ ;; parameters here.
+ (call $target
+ (call $target
+ (i32.const 0)
+ (f64.const 1.1)
+ (i32.const 2)
+ )
+ (local.get $b)
+ (call $target
+ (i32.const 3)
+ (f64.const 4.4)
+ (i32.const 5)
+ )
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func))
+
+ ;; CHECK: (type $v128 (func (result v128)))
+ (type $v128 (func (result v128)))
+
+ ;; CHECK: (type $2 (func (result f32)))
+
+ ;; CHECK: (table $0 10 funcref)
+ (table $0 10 funcref)
+
+ ;; CHECK: (func $caller-effects (type $0)
+ ;; CHECK-NEXT: (local $0 v128)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result f32)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (call_indirect $0 (type $v128)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller-effects
+ (drop
+ (call $target
+ (i64.const 0)
+ ;; We'd like to remove this unused parameter, but it has effects, so we'll
+ ;; move it to a local first.
+ (call_indirect $0 (type $v128)
+ (i32.const 0)
+ )
+ (i64.const 0)
+ )
+ )
+ )
+
+ ;; CHECK: (func $target (type $2) (result f32)
+ ;; CHECK-NEXT: (local $0 i64)
+ ;; CHECK-NEXT: (local $1 i64)
+ ;; CHECK-NEXT: (local $2 v128)
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $0 i64) (param $1 v128) (param $2 i64) (result f32)
+ ;; All parameters here should vanish.
+ (unreachable)
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32 i64)))
+
+ ;; CHECK: (type $1 (func (param i64 i64)))
+
+ ;; CHECK: (func $caller-later-br (type $0) (param $x i32) (param $y i64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (then
+ ;; CHECK-NEXT: (return)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (br $block)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller-later-br (param $x i32) (param $y i64)
+ (block $block
+ (drop
+ (call $target
+ (i64.const 0)
+ ;; We'd like to remove this unused parameter, and we can do so by moving
+ ;; it to a local, but we need to be careful: the br right after us must be
+ ;; kept around, as it is the only thing that makes the outer block have
+ ;; type none and not unreachable.
+ (block (result i32)
+ (if
+ (local.get $x)
+ (then
+ (return)
+ )
+ )
+ (i32.const 42)
+ )
+ ;; We'll move this around, but won't remove it, as explained above.
+ (br $block)
+ )
+ )
+ )
+ ;; Another call, to show the effect of removing the i32 parameter (also, if
+ ;; no calls remain after removing the unreachable one before us, then the pass
+ ;; would stop before removing parameters in $target - we don't remove params
+ ;; from functions that look dead).
+ (drop
+ (call $target
+ (local.get $y)
+ (local.get $x)
+ (local.get $y)
+ )
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i64) (param $1 i64)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result f32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $0 i64) (param $1 i32) (param $2 i64) (result f32)
+ ;; The i32 parameter should vanish.
+ (drop
+ (local.get $0)
+ )
+ (drop
+ (local.get $2)
+ )
+ (unreachable)
+ )
+)
diff --git a/test/lit/passes/dae_tnh.wast b/test/lit/passes/dae_tnh.wast
index 33e6eb09b..6d75aed30 100644
--- a/test/lit/passes/dae_tnh.wast
+++ b/test/lit/passes/dae_tnh.wast
@@ -39,14 +39,18 @@
;; CHECK: (type $1 (func (param i32)))
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (call $target
- ;; CHECK-NEXT: (unreachable)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $caller
- ;; Removing this parameter would require the type of the call to change from
- ;; unreachable to none. We don't handle such complexity and ignore such
- ;; cases.
+ ;; Removing this parameter would make the type of the call change from
+ ;; unreachable to none. But the call itself is in unreachable code, so we
+ ;; will replace it with an unreachable (and then, once the call is gone, the
+ ;; target can be better optimized; however, no other calls remain here, so
+ ;; the pass does nothing as it considers it dead at that point).
+ ;;
+ ;; This test verifies we do the proper thing even in TNH mode, as in TNH
+ ;; mode |unreachable| seems to have no effects, but for validation reasons
+ ;; we must still replace the call here.
(call $target
(unreachable)
)
@@ -59,13 +63,40 @@
)
)
-;; As above, but use a return_call. We can optimize that, since return_calls
-;; have type unreachable anyhow, and the optimization would not change the type.
+;; As above but the called target has a result.
+(module
+ ;; CHECK: (type $0 (func (result i32)))
+
+ ;; CHECK: (type $1 (func (param i32) (result i32)))
+
+ ;; CHECK: (func $caller (type $0) (result i32)
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ (func $caller (result i32)
+ ;; Again, the call is replaced by an unreachable.
+ (call $target
+ (unreachable)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32) (result i32)
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ (func $target (param i32) (result i32)
+ (i32.const 42)
+ )
+)
+
+;; As above, but use a return_call. We can optimize that too (return_calls have
+;; type unreachable anyhow, and the optimization would not change the type, so
+;; it is even simpler).
(module
;; CHECK: (type $0 (func))
+ ;; CHECK: (type $1 (func (param i32)))
+
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (return_call $target)
+ ;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $caller
(return_call $target
@@ -73,8 +104,7 @@
)
)
- ;; CHECK: (func $target (type $0)
- ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK: (func $target (type $1) (param $0 i32)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $target (param i32)
diff --git a/test/lit/passes/signature-pruning.wast b/test/lit/passes/signature-pruning.wast
index 4d22ef619..cad9af82b 100644
--- a/test/lit/passes/signature-pruning.wast
+++ b/test/lit/passes/signature-pruning.wast
@@ -136,7 +136,7 @@
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func))
- ;; CHECK: (type $sig (sub (func (param i32 i64 f32))))
+ ;; CHECK: (type $sig (sub (func (param i64 f32))))
(type $sig (sub (func (param i32) (param i64) (param f32) (param f64))))
(memory 1 1)
@@ -145,19 +145,20 @@
;; CHECK: (elem declare func $foo)
- ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32)
- ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
;; CHECK-NEXT: (i64.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
- ;; Use the middle two parameters.
+ ;; Use the middle two parameters. The other two vanish.
(i64.store
(i32.const 0)
(local.get $i64)
@@ -169,25 +170,29 @@
)
;; CHECK: (func $caller (type $0)
- ;; CHECK-NEXT: (call $foo
- ;; CHECK-NEXT: (block (result i32)
- ;; CHECK-NEXT: (call $caller)
- ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $foo
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (i64.const 1)
- ;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $sig
- ;; CHECK-NEXT: (i32.const 4)
;; CHECK-NEXT: (i64.const 5)
;; CHECK-NEXT: (f32.const 6)
;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
- ;; As above, but now one of the unused parameters has a side effect which
- ;; prevents us from removing it (flattening the IR first would avoid this
- ;; limitation). We only end up removing a single unused param, the last.
+ ;; As above, but now one of the unused parameters has a side effect. We
+ ;; move it to a local, which allows us to remove it (and also the last,
+ ;; which is trivial).
(call $foo
(block (result i32)
(call $caller)
@@ -207,13 +212,13 @@
)
)
-;; As above, but with the effects on a call_ref. Once more, we can only optimize
-;; away the very last param.
+;; As above, but with the effects on a call_ref. Once more, we can optimize
+;; even with effects on a param, using locals.
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func))
- ;; CHECK: (type $sig (sub (func (param i32 i64 f32))))
+ ;; CHECK: (type $sig (sub (func (param i64 f32))))
(type $sig (sub (func (param i32) (param i64) (param f32) (param f64))))
(memory 1 1)
@@ -222,15 +227,16 @@
;; CHECK: (elem declare func $foo)
- ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32)
- ;; CHECK-NEXT: (local $3 f64)
+ ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32)
+ ;; CHECK-NEXT: (local $2 f64)
+ ;; CHECK-NEXT: (local $3 i32)
;; CHECK-NEXT: (i64.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.store
;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64)
@@ -245,19 +251,23 @@
)
;; CHECK: (func $caller (type $0)
+ ;; CHECK-NEXT: (local $0 i32)
;; CHECK-NEXT: (call $foo
- ;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i64.const 1)
;; CHECK-NEXT: (f32.const 2)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (call_ref $sig
- ;; CHECK-NEXT: (block (result i32)
- ;; CHECK-NEXT: (call $caller)
- ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (call $caller)
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $sig
+ ;; CHECK-NEXT: (i64.const 5)
+ ;; CHECK-NEXT: (f32.const 6)
+ ;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
- ;; CHECK-NEXT: (i64.const 5)
- ;; CHECK-NEXT: (f32.const 6)
- ;; CHECK-NEXT: (ref.func $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
@@ -912,3 +922,232 @@
(unreachable)
)
)
+
+;; Test corner cases with var updating. To remove the parameter of $func we
+;; must move the parameter to a local first. We must then adjust local types
+;; properly while adjusting the signature (when the signature loses a parameter,
+;; local indexes change, which is a delicate dance handled by
+;; GlobalTypeRewriter::updateSignatures and ParamUtils::removeParameters;
+;; moving the parameter to a local first should not get in the way there).
+(module
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $struct (sub (struct (field v128))))
+ (type $struct (sub (struct (field v128))))
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (type $func (func))
+ (type $func (func (param v128)))
+
+ ;; CHECK: (elem declare func $func)
+
+ ;; CHECK: (func $func (type $func)
+ ;; CHECK-NEXT: (local $0 v128)
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $func (type $func) (param $0 v128)
+ ;; The parameter will be removed.
+ (nop)
+ )
+
+ ;; CHECK: (func $caller (type $1)
+ ;; CHECK-NEXT: (local $0 (ref $struct))
+ ;; CHECK-NEXT: (local $1 externref)
+ ;; CHECK-NEXT: (local $2 v128)
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.tee $0
+ ;; CHECK-NEXT: (struct.new_default $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call_ref $func
+ ;; CHECK-NEXT: (ref.func $func)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $caller (param $param externref)
+ (local $var (ref $struct))
+ ;; The parameter of this call_ref will be removed.
+ (call_ref $func
+ ;; Use a struct.get, which would error if the type the nested tee were
+ ;; incorrect (it asserts on it being a struct type).
+ (struct.get $struct 0
+ ;; Use a tee to test the updating of tee'd vars, as mentioned above.
+ (local.tee $var
+ (struct.new_default $struct)
+ )
+ )
+ (ref.func $func)
+ )
+ )
+)
+
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (func (result i32)))
+
+ ;; CHECK: (type $3 (func (param i32)))
+
+ ;; CHECK: (tag $tag (param i32))
+ (tag $tag (param i32))
+
+ ;; CHECK: (func $catch-pop (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (br_if $block
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catch-pop (result i32)
+ (block $block (result i32)
+ (try $try
+ (do
+ (nop)
+ )
+ (catch $tag
+ (call $target
+ (pop i32)
+ ;; We can remove this parameter by moving it to a local first, which
+ ;; also moves the pop, which then needs to be fixed up.
+ (br_if $block
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ ;; This nop causes the call to be in a block. When we add another
+ ;; block to hold the code that we move, we'd get an error if we don't
+ ;; apply fixups.
+ (nop)
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $x i32) (param $y i32)
+ ;; Use only the first param. The second will be removed.
+ (drop
+ (local.get $x)
+ )
+ )
+)
+
+;; As above, but remove the other parameter (the pop).
+(module
+ ;; CHECK: (type $0 (func (param i32)))
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $1 (func (param i32)))
+
+ ;; CHECK: (type $2 (func (result i32)))
+
+ ;; CHECK: (type $3 (func (param i32)))
+
+ ;; CHECK: (tag $tag (param i32))
+ (tag $tag (param i32))
+
+ ;; CHECK: (func $catch-pop (type $2) (result i32)
+ ;; CHECK-NEXT: (local $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (local $2 i32)
+ ;; CHECK-NEXT: (block $block (result i32)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch $tag
+ ;; CHECK-NEXT: (local.set $2
+ ;; CHECK-NEXT: (pop i32)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (local.set $0
+ ;; CHECK-NEXT: (local.get $2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (br_if $block
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (call $target
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $catch-pop (result i32)
+ (block $block (result i32)
+ (try $try
+ (do
+ (nop)
+ )
+ (catch $tag
+ (call $target
+ (pop i32)
+ (br_if $block
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ (nop)
+ )
+ )
+ (i32.const 3)
+ )
+ )
+
+ ;; CHECK: (func $target (type $1) (param $0 i32)
+ ;; CHECK-NEXT: (local $1 i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $target (param $x i32) (param $y i32)
+ (drop
+ (local.get $y) ;; this changed from $x to $y
+ )
+ )
+)