diff options
author | Alon Zakai <azakai@google.com> | 2024-07-11 10:31:31 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-11 10:31:31 -0700 |
commit | 5a1daf7727e768acebedd57464d2e0788afc85c5 (patch) | |
tree | 685da7a53cafb6d9a6b9afc3213b386425cf8489 /test | |
parent | e05f7629a27220c9951acc511d6f68a49e7b25d9 (diff) | |
download | binaryen-5a1daf7727e768acebedd57464d2e0788afc85c5.tar.gz binaryen-5a1daf7727e768acebedd57464d2e0788afc85c5.tar.bz2 binaryen-5a1daf7727e768acebedd57464d2e0788afc85c5.zip |
Monomorphization: Optimize constants (#6711)
Previously the pass would monomorphize a call when we were sending more
refined types than the target expects. This generalizes the pass to also consider
the case where we send a constant in a parameter.
To achieve that, this refactors the pass to explicitly define the "call context",
which is the code around the call (inputs and outputs) that may end up leading
to optimization opportunities when combined with the target function. Also
add comments about the overall design + roadmap.
The existing test is mostly unmodified, and the diff there is smaller when
ignoring whitespace. We do "regress" those tests by adding more local.set
operations, as in the refactoring that makes things a lot simpler, that is, to
handle the general case of an operand having either a refined type or be a
constant, we copy it inside the function, which works either way. This
"regression" is only in the testing version of the pass (the normal version
runs optimizations, which would remove that extra code).
This also enables the pass when GC is disabled. Previously we only handled
refined types, so only GC could benefit. Add a test for MVP content
specifically to show we operate there as well.
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/monomorphize-consts.wast | 635 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-mvp.wast | 94 | ||||
-rw-r--r-- | test/lit/passes/monomorphize-types.wast (renamed from test/lit/passes/monomorphize.wast) | 87 | ||||
-rw-r--r-- | test/lit/passes/no-inline-monomorphize-inlining.wast | 37 |
4 files changed, 812 insertions, 41 deletions
diff --git a/test/lit/passes/monomorphize-consts.wast b/test/lit/passes/monomorphize-consts.wast new file mode 100644 index 000000000..1dbdf1592 --- /dev/null +++ b/test/lit/passes/monomorphize-consts.wast @@ -0,0 +1,635 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; As in monomorphize-types.wast, test in both "always" mode, which always +;; monomorphizes, and in "careful" mode which does it only when it appears to +;; actually help. + +;; RUN: foreach %s %t wasm-opt --monomorphize-always -all -S -o - | filecheck %s --check-prefix ALWAYS +;; RUN: foreach %s %t wasm-opt --monomorphize -all -S -o - | filecheck %s --check-prefix CAREFUL + +(module + ;; Test that constants are monomorphized. + + ;; ALWAYS: (type $0 (func (param i32))) + + ;; ALWAYS: (type $1 (func)) + + ;; ALWAYS: (type $2 (func (param i32 i32 funcref stringref))) + + ;; ALWAYS: (type $3 (func (param i32) (result i32))) + + ;; ALWAYS: (type $4 (func (result i32))) + + ;; ALWAYS: (import "a" "b" (func $import (type $0) (param i32))) + ;; CAREFUL: (type $0 (func)) + + ;; CAREFUL: (type $1 (func (param i32 i32 funcref stringref))) + + ;; CAREFUL: (type $2 (func (param i32))) + + ;; CAREFUL: (type $3 (func (param i32) (result i32))) + + ;; CAREFUL: (import "a" "b" (func $import (type $2) (param i32))) + (import "a" "b" (func $import (param i32))) + + ;; ALWAYS: (elem declare func $calls) + + ;; ALWAYS: (func $calls (type $1) + ;; ALWAYS-NEXT: (call $target_9 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $target_9 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 3) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $target_10 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (elem declare func $calls) + + ;; CAREFUL: (func $calls (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 3) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.const 3) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $calls + ;; All but the eqz parameter are constants that can be handled. In ALWAYS + ;; mode we optimize and remove all but that one. In CAREFUL mode we end up + ;; not doing anything, as the target function's body optimizes out anyhow + ;; (so there is no benefit from monomorphization, after opts). + (call $target + (i32.const 1) + (i32.eqz + (i32.const 2) + ) + (ref.func $calls) + (string.const "foo") + ) + + ;; This has the same effective call context, as the constants are identical, + ;; and the non-constant is different, which we keep as a variable anyhow. + ;; This will call the same refined function as the previous call. + (call $target + (i32.const 1) + (i32.eqz + (i32.const 3) ;; this changed + ) + (ref.func $calls) + (string.const "foo") + ) + + ;; This has a different call context: one constant is different, so we'll + ;; call a different refined function. + (call $target + (i32.const 3) ;; this changed + (i32.eqz + (i32.const 2) + ) + (ref.func $calls) + (string.const "foo") + ) + ) + + ;; ALWAYS: (func $more-calls (type $1) + ;; ALWAYS-NEXT: (call $target_9 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 999) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $other-target_11 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 999) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $work_12 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 999) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $more-calls (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 999) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $other-target + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 999) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $work_9 + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 999) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $more-calls + ;; Identical to the first call in the previous function (except for the non- + ;; constant second param, which is ok to be different). We should call the + ;; same refined function before, even though we are in a different + ;; function here. + (call $target + (i32.const 1) + (i32.eqz + (i32.const 999) + ) + (ref.func $calls) + (string.const "foo") + ) + + ;; Call a different function but with the exact same params. This tests that + ;; we handle identical contexts but with different functions. This will call + ;; a different refined function than before + (call $other-target + (i32.const 1) + (i32.eqz + (i32.const 999) + ) + (ref.func $calls) + (string.const "foo") + ) + + ;; Call yet another different function with the same context, this time the + ;; function is worth optimizing even in CAREFUL mode, as the constants + ;; unlock actual work. + (call $work + (i32.const 3) + (i32.eqz + (i32.const 999) + ) + (ref.func $calls) + (string.const "foo") + ) + ) + + ;; ALWAYS: (func $fail (type $1) + ;; ALWAYS-NEXT: (call $target + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 1) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 999) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result funcref) + ;; ALWAYS-NEXT: (ref.func $calls) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (block (result stringref) + ;; ALWAYS-NEXT: (string.const "foo") + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $fail (type $0) + ;; CAREFUL-NEXT: (call $target + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 999) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result funcref) + ;; CAREFUL-NEXT: (ref.func $calls) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (block (result stringref) + ;; CAREFUL-NEXT: (string.const "foo") + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $fail + ;; No operand is a constant here, so we do nothing. + (call $target + (i32.eqz + (i32.const 1) + ) + (i32.eqz + (i32.const 999) + ) + (block (result funcref) + (ref.func $calls) + ) + (block (result stringref) + (string.const "foo") + ) + ) + ) + + ;; ALWAYS: (func $mutual-recursion-a (type $3) (param $x i32) (result i32) + ;; ALWAYS-NEXT: (if (result i32) + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (then + ;; ALWAYS-NEXT: (i32.add + ;; ALWAYS-NEXT: (call $mutual-recursion-b + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $mutual-recursion-b_13) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (else + ;; ALWAYS-NEXT: (i32.const 42) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $mutual-recursion-a (type $3) (param $0 i32) (result i32) + ;; CAREFUL-NEXT: (if (result i32) + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (then + ;; CAREFUL-NEXT: (i32.add + ;; CAREFUL-NEXT: (call $mutual-recursion-b + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $mutual-recursion-b + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (else + ;; CAREFUL-NEXT: (i32.const 42) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $mutual-recursion-a (param $x i32) (result i32) + ;; We ignore direct recursion (see test in other monomorphize-types) but we + ;; do handle mutual recursion normally. This also tests another function + ;; that can be optimized, with a different signature than before. + (if (result i32) + (local.get $x) + (then + (i32.add + ;; This call cannot be monomorphized. + (call $mutual-recursion-b + (local.get $x) + ) + ;; The constant here allows us to monomorphize (in ALWAYS; to see the + ;; benefit in CAREFUL, we need additional cycles, which we do not do + ;; yet). + (call $mutual-recursion-b + (i32.const 0) + ) + ) + ) + (else + (i32.const 42) + ) + ) + ) + + ;; ALWAYS: (func $mutual-recursion-b (type $3) (param $x i32) (result i32) + ;; ALWAYS-NEXT: (i32.add + ;; ALWAYS-NEXT: (call $mutual-recursion-a_14) + ;; ALWAYS-NEXT: (i32.const 1337) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $mutual-recursion-b (type $3) (param $0 i32) (result i32) + ;; CAREFUL-NEXT: (i32.add + ;; CAREFUL-NEXT: (call $mutual-recursion-a + ;; CAREFUL-NEXT: (i32.const 0) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (i32.const 1337) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $mutual-recursion-b (param $x i32) (result i32) + (i32.add + ;; This can be optimized (in ALWAYS; to see the benefit in CAREFUL, we + ;; need additional cycles, which we do not do yet). + (call $mutual-recursion-a + (i32.const 0) + ) + (i32.const 1337) + ) + ) + + ;; ALWAYS: (func $target (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $y) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $func) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $str) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (type $1) (param $0 i32) (param $1 i32) (param $2 funcref) (param $3 stringref) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $target (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + (drop + (local.get $func) + ) + (drop + (local.get $str) + ) + ) + + ;; ALWAYS: (func $other-target (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $func) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $str) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (drop + ;; ALWAYS-NEXT: (local.get $y) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $other-target (type $1) (param $0 i32) (param $1 i32) (param $2 funcref) (param $3 stringref) + ;; CAREFUL-NEXT: (nop) + ;; CAREFUL-NEXT: ) + (func $other-target (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; Similar to $target, but the inside is a little reordered. + (drop + (local.get $func) + ) + (drop + (local.get $str) + ) + (drop + (local.get $x) + ) + (drop + (local.get $y) + ) + ) + + ;; ALWAYS: (func $work (type $2) (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (i32.add + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (i32.add + ;; ALWAYS-NEXT: (ref.is_null + ;; ALWAYS-NEXT: (local.get $func) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (ref.is_null + ;; ALWAYS-NEXT: (local.get $str) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: (call $import + ;; ALWAYS-NEXT: (local.get $y) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $work (type $1) (param $0 i32) (param $1 i32) (param $2 funcref) (param $3 stringref) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (i32.add + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (i32.add + ;; CAREFUL-NEXT: (ref.is_null + ;; CAREFUL-NEXT: (local.get $2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (ref.is_null + ;; CAREFUL-NEXT: (local.get $3) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: (call $import + ;; CAREFUL-NEXT: (local.get $1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $work (param $x i32) (param $y i32) (param $func funcref) (param $str stringref) + ;; Similar to $target, but the inside has actual work that can be optimized + ;; away if we have constants here. Specifically the refs are not null and + ;; $x is 3, so we sent 3 to the import here. + (call $import + (i32.add + (local.get $x) + (i32.add + (ref.is_null + (local.get $func) + ) + (ref.is_null + (local.get $str) + ) + ) + ) + ) + ;; This parameter is unknown, so we can't do any optimization in this part. + (call $import + (local.get $y) + ) + ) +) +;; ALWAYS: (func $target_9 (type $0) (param $0 i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local $y i32) +;; ALWAYS-NEXT: (local $func funcref) +;; ALWAYS-NEXT: (local $str stringref) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $y +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $func +;; ALWAYS-NEXT: (ref.func $calls) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $str +;; ALWAYS-NEXT: (string.const "foo") +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $y) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $func) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $str) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $target_10 (type $0) (param $0 i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local $y i32) +;; ALWAYS-NEXT: (local $func funcref) +;; ALWAYS-NEXT: (local $str stringref) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $y +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $func +;; ALWAYS-NEXT: (ref.func $calls) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $str +;; ALWAYS-NEXT: (string.const "foo") +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $y) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $func) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $str) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $other-target_11 (type $0) (param $0 i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local $y i32) +;; ALWAYS-NEXT: (local $func funcref) +;; ALWAYS-NEXT: (local $str stringref) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $y +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $func +;; ALWAYS-NEXT: (ref.func $calls) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $str +;; ALWAYS-NEXT: (string.const "foo") +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $func) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $str) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (drop +;; ALWAYS-NEXT: (local.get $y) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $work_12 (type $0) (param $0 i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local $y i32) +;; ALWAYS-NEXT: (local $func funcref) +;; ALWAYS-NEXT: (local $str stringref) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 3) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $y +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $func +;; ALWAYS-NEXT: (ref.func $calls) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $str +;; ALWAYS-NEXT: (string.const "foo") +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (i32.add +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: (i32.add +;; ALWAYS-NEXT: (ref.is_null +;; ALWAYS-NEXT: (local.get $func) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (ref.is_null +;; ALWAYS-NEXT: (local.get $str) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (local.get $y) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $mutual-recursion-b_13 (type $4) (result i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (i32.add +;; ALWAYS-NEXT: (call $mutual-recursion-a +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (i32.const 1337) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; ALWAYS: (func $mutual-recursion-a_14 (type $4) (result i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (i32.const 0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (if (result i32) +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: (then +;; ALWAYS-NEXT: (i32.add +;; ALWAYS-NEXT: (call $mutual-recursion-b +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $mutual-recursion-b_13) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (else +;; ALWAYS-NEXT: (i32.const 42) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $work_9 (type $2) (param $0 i32) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (i32.const 3) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: (call $import +;; CAREFUL-NEXT: (local.get $0) +;; CAREFUL-NEXT: ) +;; CAREFUL-NEXT: ) diff --git a/test/lit/passes/monomorphize-mvp.wast b/test/lit/passes/monomorphize-mvp.wast new file mode 100644 index 000000000..567a4c2ce --- /dev/null +++ b/test/lit/passes/monomorphize-mvp.wast @@ -0,0 +1,94 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; As in monomorphize-types.wast, test in both "always" mode, which always +;; monomorphizes, and in "careful" mode which does it only when it appears to +;; actually help. + +;; This file specifically tests that we optimize constants in MVP mode (most +;; of the pass benefits from other features, but we should still do work in +;; MVP). + +;; RUN: foreach %s %t wasm-opt --monomorphize-always -S -o - | filecheck %s --check-prefix ALWAYS +;; RUN: foreach %s %t wasm-opt --monomorphize -S -o - | filecheck %s --check-prefix CAREFUL + +(module + ;; ALWAYS: (type $0 (func (result i32))) + + ;; ALWAYS: (type $1 (func (param i32 i32) (result i32))) + + ;; ALWAYS: (type $2 (func (param i32) (result i32))) + + ;; ALWAYS: (func $call (result i32) + ;; ALWAYS-NEXT: (call $target_2 + ;; ALWAYS-NEXT: (i32.eqz + ;; ALWAYS-NEXT: (i32.const 2) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (type $0 (func (result i32))) + + ;; CAREFUL: (type $1 (func (param i32 i32) (result i32))) + + ;; CAREFUL: (type $2 (func (param i32) (result i32))) + + ;; CAREFUL: (func $call (result i32) + ;; CAREFUL-NEXT: (call $target_2 + ;; CAREFUL-NEXT: (i32.eqz + ;; CAREFUL-NEXT: (i32.const 2) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $call (result i32) + ;; The second parameter can be monomorphized. + (call $target + (i32.eqz + (i32.const 2) + ) + (i32.const 1) + ) + ) + + ;; ALWAYS: (func $target (param $x i32) (param $y i32) (result i32) + ;; ALWAYS-NEXT: (select + ;; ALWAYS-NEXT: (local.get $x) + ;; ALWAYS-NEXT: (i32.const 42) + ;; ALWAYS-NEXT: (local.get $y) + ;; ALWAYS-NEXT: ) + ;; ALWAYS-NEXT: ) + ;; CAREFUL: (func $target (param $0 i32) (param $1 i32) (result i32) + ;; CAREFUL-NEXT: (select + ;; CAREFUL-NEXT: (local.get $0) + ;; CAREFUL-NEXT: (i32.const 42) + ;; CAREFUL-NEXT: (local.get $1) + ;; CAREFUL-NEXT: ) + ;; CAREFUL-NEXT: ) + (func $target (param $x i32) (param $y i32) (result i32) + ;; The monomorphized copies of this function will be able to remove the + ;; select, in CAREFUL (which optimizes). + (select + (local.get $x) + (i32.const 42) + (local.get $y) + ) + ) +) + +;; ALWAYS: (func $target_2 (param $0 i32) (result i32) +;; ALWAYS-NEXT: (local $x i32) +;; ALWAYS-NEXT: (local $y i32) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local.set $y +;; ALWAYS-NEXT: (i32.const 1) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (select +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: (i32.const 42) +;; ALWAYS-NEXT: (local.get $y) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) + +;; CAREFUL: (func $target_2 (param $0 i32) (result i32) +;; CAREFUL-NEXT: (local.get $0) +;; CAREFUL-NEXT: ) diff --git a/test/lit/passes/monomorphize.wast b/test/lit/passes/monomorphize-types.wast index 43208a97d..3133d88c5 100644 --- a/test/lit/passes/monomorphize.wast +++ b/test/lit/passes/monomorphize-types.wast @@ -115,7 +115,11 @@ ) -;; ALWAYS: (func $refinable_4 (type $4) (param $ref (ref $B)) +;; ALWAYS: (func $refinable_4 (type $4) (param $0 (ref $B)) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) @@ -186,17 +190,17 @@ ) -;; ALWAYS: (func $refinable_2 (type $4) (param $ref (ref $B)) +;; ALWAYS: (func $refinable_2 (type $4) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $unref (ref $A)) -;; ALWAYS-NEXT: (local $2 (ref $A)) -;; ALWAYS-NEXT: (local.set $2 -;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (block ;; ALWAYS-NEXT: (local.set $unref -;; ALWAYS-NEXT: (local.get $2) +;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (local.set $2 +;; ALWAYS-NEXT: (local.set $ref ;; ALWAYS-NEXT: (local.get $unref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -306,19 +310,31 @@ ) ) -;; ALWAYS: (func $refinable1_4 (type $5) (param $ref (ref $B)) +;; ALWAYS: (func $refinable1_4 (type $5) (param $0 (ref $B)) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $refinable1_5 (type $6) (param $ref (ref $C)) +;; ALWAYS: (func $refinable1_5 (type $6) (param $0 (ref $C)) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS: (func $refinable2_6 (type $5) (param $ref (ref $B)) +;; ALWAYS: (func $refinable2_6 (type $5) (param $0 (ref $B)) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (drop ;; ALWAYS-NEXT: (local.get $ref) ;; ALWAYS-NEXT: ) @@ -501,33 +517,39 @@ ) ) -;; ALWAYS: (func $refinable_3 (type $2) (param $ref (ref $B)) +;; ALWAYS: (func $refinable_3 (type $2) (param $0 (ref $B)) ;; ALWAYS-NEXT: (local $x (ref $A)) -;; ALWAYS-NEXT: (call $import -;; ALWAYS-NEXT: (ref.cast (ref $B) -;; ALWAYS-NEXT: (local.get $ref) -;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (local $ref (ref $A)) +;; ALWAYS-NEXT: (local.set $ref +;; ALWAYS-NEXT: (local.get $0) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (local.set $x -;; ALWAYS-NEXT: (select (result (ref $B)) -;; ALWAYS-NEXT: (local.get $ref) -;; ALWAYS-NEXT: (struct.new_default $B) -;; ALWAYS-NEXT: (global.get $global) +;; ALWAYS-NEXT: (block +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast (ref $B) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (call $import -;; ALWAYS-NEXT: (ref.cast (ref $B) -;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: (local.set $x +;; ALWAYS-NEXT: (select (result (ref $A)) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (struct.new_default $B) +;; ALWAYS-NEXT: (global.get $global) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (call $import -;; ALWAYS-NEXT: (ref.cast (ref $B) -;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast (ref $B) +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: ) -;; ALWAYS-NEXT: (call $import -;; ALWAYS-NEXT: (ref.cast (ref $B) -;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast (ref $B) +;; ALWAYS-NEXT: (local.get $x) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: ) +;; ALWAYS-NEXT: (call $import +;; ALWAYS-NEXT: (ref.cast (ref $B) +;; ALWAYS-NEXT: (local.get $ref) +;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -586,3 +608,4 @@ ) ) ) + diff --git a/test/lit/passes/no-inline-monomorphize-inlining.wast b/test/lit/passes/no-inline-monomorphize-inlining.wast index be3b5759d..716d0beda 100644 --- a/test/lit/passes/no-inline-monomorphize-inlining.wast +++ b/test/lit/passes/no-inline-monomorphize-inlining.wast @@ -3,9 +3,12 @@ ;; Monomorphization creates a new function, which we can then inline. When we ;; mark the original as no-inline, we should not inline the copy, as the copy ;; inherits the metadata. +;; +;; Use --optimize-level=3 to ensure inlining works at the maximum (to avoid it +;; not happening because of size limits etc.). -;; RUN: foreach %s %t wasm-opt --no-inline=*noinline* --monomorphize-always --inlining -all -S -o - | filecheck %s --check-prefix NO_INLINE -;; RUN: foreach %s %t wasm-opt --monomorphize-always --inlining -all -S -o - | filecheck %s --check-prefix YESINLINE +;; RUN: foreach %s %t wasm-opt --no-inline=*noinline* --monomorphize-always --inlining --optimize-level=3 -all -S -o - | filecheck %s --check-prefix NO_INLINE +;; RUN: foreach %s %t wasm-opt --monomorphize-always --inlining --optimize-level=3 -all -S -o - | filecheck %s --check-prefix YESINLINE (module ;; NO_INLINE: (type $A (sub (struct ))) @@ -42,7 +45,9 @@ ;; YESINLINE-NEXT: (local $0 (ref $A)) ;; YESINLINE-NEXT: (local $1 (ref $A)) ;; YESINLINE-NEXT: (local $2 (ref $B)) - ;; YESINLINE-NEXT: (local $3 (ref $B)) + ;; YESINLINE-NEXT: (local $3 (ref $A)) + ;; YESINLINE-NEXT: (local $4 (ref $B)) + ;; YESINLINE-NEXT: (local $5 (ref $A)) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline ;; YESINLINE-NEXT: (local.set $0 @@ -68,18 +73,28 @@ ;; YESINLINE-NEXT: (local.set $2 ;; YESINLINE-NEXT: (struct.new_default $B) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $2) + ;; YESINLINE-NEXT: (block + ;; YESINLINE-NEXT: (local.set $3 + ;; YESINLINE-NEXT: (local.get $2) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $3) + ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: (block ;; YESINLINE-NEXT: (block $__inlined_func$refinable_noinline_2$3 - ;; YESINLINE-NEXT: (local.set $3 + ;; YESINLINE-NEXT: (local.set $4 ;; YESINLINE-NEXT: (struct.new_default $B) ;; YESINLINE-NEXT: ) - ;; YESINLINE-NEXT: (drop - ;; YESINLINE-NEXT: (local.get $3) + ;; YESINLINE-NEXT: (block + ;; YESINLINE-NEXT: (local.set $5 + ;; YESINLINE-NEXT: (local.get $4) + ;; YESINLINE-NEXT: ) + ;; YESINLINE-NEXT: (drop + ;; YESINLINE-NEXT: (local.get $5) + ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) ;; YESINLINE-NEXT: ) @@ -118,7 +133,11 @@ ) ) ) -;; NO_INLINE: (func $refinable_noinline_2 (type $4) (param $ref (ref $B)) +;; NO_INLINE: (func $refinable_noinline_2 (type $4) (param $0 (ref $B)) +;; NO_INLINE-NEXT: (local $ref (ref $A)) +;; NO_INLINE-NEXT: (local.set $ref +;; NO_INLINE-NEXT: (local.get $0) +;; NO_INLINE-NEXT: ) ;; NO_INLINE-NEXT: (drop ;; NO_INLINE-NEXT: (local.get $ref) ;; NO_INLINE-NEXT: ) |