diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 31e5fb1179d..412c53e40a7 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -978,7 +978,19 @@ struct FunctionSplitter { if (finalItem && getItem(body, numIfs + 1)) { return InliningMode::Uninlineable; } - // This has the general shape we seek. Check each if. + // This has the general shape we seek. Check each if: it must be in the + // form mentioned above (simple condition, no returns in body). We must also + // have no sets of locals that the final item notices, as then we could + // have this: + // + // if (A) { + // x = 10; + // } + // return x; + // + // We cannot split out the if in such a case because of the local + // dependency. + std::unordered_set writtenLocals; for (Index i = 0; i < numIfs; i++) { auto* iff = getIf(body, i); // The if must have a simple condition and no else arm. @@ -995,7 +1007,21 @@ struct FunctionSplitter { // unreachable, and we ruled out none before. assert(iff->ifTrue->type == Type::unreachable); } + if (finalItem) { + for (auto* set : FindAll(iff).list) { + writtenLocals.insert(set->index); + } + } } + // Finish the locals check mentioned above. + if (finalItem) { + for (auto* get : FindAll(finalItem).list) { + if (writtenLocals.count(get->index)) { + return InliningMode::Uninlineable; + } + } + } + // Success, this matches the pattern. // If the outlined function will be worth inlining normally, skip the diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast index f3d7f44a4d2..e583e2e4021 100644 --- a/test/lit/passes/inlining_splitting.wast +++ b/test/lit/passes/inlining_splitting.wast @@ -2271,3 +2271,489 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) +(module + ;; Pattern B situations with local value dependencies. + + ;; An import, whose calls are work that we want to split out. + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32 i32) (result i32))) + + ;; CHECK: (type $2 (func (param i32 i32))) + + ;; CHECK: (import "out" "func" (func $import (type $0))) + (import "out" "func" (func $import)) + + ;; CHECK: (func $bad (type $1) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + (func $bad (param $x i32) (param $y i32) (result i32) + ;; Check $x, and possibly set $y. $y is read below, so we cannot split out + ;; the set. + (if + (local.get $x) + (then + (block + (local.set $y + (i32.const 42) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-bad (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-bad + ;; These should not be inlined/split. + ;; Call twice to avoid single-call inlining. + (drop + (call $bad (i32.const 1) (i32.const 2)) + ) + (drop + (call $bad (i32.const 1) (i32.const 2)) + ) + ) + + (func $good (param $x i32) (param $y i32) (result i32) + ;; As above, but set $x in the if body. No problem occurs after the if, so + ;; we can split here. + (if + (local.get $x) + (then + (block + (local.set $x ;; this changed + (i32.const 1337) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-good (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$good (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$good$1 (result i32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-good + ;; These should be inlined/split. + (drop + (call $good (i32.const 2) (i32.const 3)) + ) + (drop + (call $good (i32.const 2) (i32.const 3)) + ) + ) + + ;; CHECK: (func $bad-2 (type $1) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + (func $bad-2 (param $x i32) (param $y i32) (result i32) + ;; Two ifs, with the problem in the first. + (if + (local.get $x) + (then + (block + (local.set $y ;; this is bad + (i32.const 42) + ) + (call $import) + ) + ) + ) + (if + (local.get $y) + (then + (block + (local.set $x + (i32.const 42) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-bad-2 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-bad-2 + ;; These should not be inlined/split. + (drop + (call $bad-2 (i32.const 1) (i32.const 2)) + ) + (drop + (call $bad-2 (i32.const 1) (i32.const 2)) + ) + ) + + ;; CHECK: (func $bad-3 (type $1) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + (func $bad-3 (param $x i32) (param $y i32) (result i32) + ;; Two ifs, with the problem in the second. + (if + (local.get $x) + (then + (block + (local.set $x ;; this changed + (i32.const 42) + ) + (call $import) + ) + ) + ) + (if + (local.get $y) + (then + (block + (local.set $y ;; this changed + (i32.const 42) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-bad-3 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-3 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-bad-3 + ;; These should not be inlined/split. + (drop + (call $bad-3 (i32.const 1) (i32.const 2)) + ) + (drop + (call $bad-3 (i32.const 1) (i32.const 2)) + ) + ) + + ;; CHECK: (func $bad-4 (type $1) (param $x i32) (param $y i32) (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + (func $bad-4 (param $x i32) (param $y i32) (result i32) + ;; Two ifs, with a problem in both. + (if + (local.get $x) + (then + (block + (local.set $y ;; this changed + (i32.const 42) + ) + (call $import) + ) + ) + ) + (if + (local.get $y) + (then + (block + (local.set $y + (i32.const 42) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-bad-4 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $bad-4 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-bad-4 + ;; These should not be inlined/split. + (drop + (call $bad-4 (i32.const 1) (i32.const 2)) + ) + (drop + (call $bad-4 (i32.const 1) (i32.const 2)) + ) + ) + + (func $good-5 (param $x i32) (param $y i32) (result i32) + ;; Two ifs, with no problem in either. + (if + (local.get $x) + (then + (block + (local.set $x ;; this changed + (i32.const 42) + ) + (call $import) + ) + ) + ) + (if + (local.get $y) + (then + (block + (local.set $x ;; this changed + (i32.const 42) + ) + (call $import) + ) + ) + ) + (local.get $y) + ) + + ;; CHECK: (func $calls-good-5 (type $0) + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$good-5$2 (result i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good-5 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good-5_17 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$byn-split-inlineable-B$good-5$3 (result i32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good-5 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $byn-split-outlined-B$good-5_17 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $calls-good-5 + ;; These should be inlined/split. + (drop + (call $good-5 (i32.const 1) (i32.const 2)) + ) + (drop + (call $good-5 (i32.const 1) (i32.const 2)) + ) + ) +) + +;; CHECK: (func $byn-split-outlined-B$good (type $2) (param $x i32) (param $y i32) +;; CHECK-NEXT: (local.set $x +;; CHECK-NEXT: (i32.const 1337) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call $import) +;; CHECK-NEXT: ) + +;; CHECK: (func $byn-split-outlined-B$good-5 (type $2) (param $x i32) (param $y i32) +;; CHECK-NEXT: (local.set $x +;; CHECK-NEXT: (i32.const 42) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call $import) +;; CHECK-NEXT: ) + +;; CHECK: (func $byn-split-outlined-B$good-5_17 (type $2) (param $x i32) (param $y i32) +;; CHECK-NEXT: (local.set $x +;; CHECK-NEXT: (i32.const 42) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call $import) +;; CHECK-NEXT: )