diff options
-rw-r--r-- | src/passes/Inlining.cpp | 41 | ||||
-rw-r--r-- | test/lit/passes/inlining-unreachable.wast | 2 | ||||
-rw-r--r-- | test/lit/passes/inlining_all-features.wast | 2 | ||||
-rw-r--r-- | test/lit/passes/inlining_enable-tail-call.wast | 3 | ||||
-rw-r--r-- | test/lit/passes/inlining_optimize-level=3.wast | 68 | ||||
-rw-r--r-- | test/lit/passes/inlining_splitting.wast | 1 |
6 files changed, 101 insertions, 16 deletions
diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 7df5c8f29..1732c1a2c 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -404,14 +404,39 @@ static Expression* doInlining(Module* module, updater.walk(contents); block->list.push_back(contents); block->type = retType; - // If the function returned a value, we just set the block containing the - // inlined code to have that type. or, if the function was void and - // contained void, that is fine too. a bad case is a void function in which - // we have unreachable code, so we would be replacing a void call with an - // unreachable. - if (contents->type == Type::unreachable && block->type == Type::none) { - // Make the block reachable by adding a break to it - block->list.push_back(builder.makeBreak(block->name)); + // The ReFinalize below will handle propagating unreachability if we need to + // do so, that is, if the call was reachable but now the inlined content we + // replaced it with was unreachable. The opposite case requires special + // handling: ReFinalize works under the assumption that code can become + // unreachable, but it does not go back from that state. But inlining can + // cause that: + // + // (call $A ;; an unreachable call + // (unreachable) + // ) + // => + // (block $__inlined_A_body (result i32) ;; reachable code after inlining + // (unreachable) + // ) + // + // That is, if the called function wraps the input parameter in a block with a + // declared type, then the block is not unreachable. And then we might error + // if the outside expects the code to be unreachable - perhaps it only + // validates that way. To fix this, if the call was unreachable then we make + // the inlined code unreachable as well. That also maximizes DCE + // opportunities by propagating unreachability as much as possible. + // + // (Note that we don't need to do this for a return_call, which is always + // unreachable anyhow.) + if (call->type == Type::unreachable && !call->isReturn) { + // Make the replacement code unreachable. Note that we can't just add an + // unreachable at the end, as the block might have breaks to it (returns are + // transformed into those). + Expression* old = block; + if (old->type.isConcrete()) { + old = builder.makeDrop(old); + } + *action.callSite = builder.makeSequence(old, builder.makeUnreachable()); } // Anything we inlined into may now have non-unique label names, fix it up. // Note that we must do this before refinalization, as otherwise duplicate diff --git a/test/lit/passes/inlining-unreachable.wast b/test/lit/passes/inlining-unreachable.wast index e8797d5b9..66ed2097c 100644 --- a/test/lit/passes/inlining-unreachable.wast +++ b/test/lit/passes/inlining-unreachable.wast @@ -16,7 +16,6 @@ ;; CHECK: (func $call-trap (type $none_=>_none) ;; CHECK-NEXT: (block $__inlined_func$trap ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (br $__inlined_func$trap) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call-trap @@ -59,7 +58,6 @@ ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$contents-then-trap) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call-contents-then-trap diff --git a/test/lit/passes/inlining_all-features.wast b/test/lit/passes/inlining_all-features.wast index d26a2aa9f..eee8fbdc9 100644 --- a/test/lit/passes/inlining_all-features.wast +++ b/test/lit/passes/inlining_all-features.wast @@ -142,13 +142,11 @@ ;; CHECK: (func $1 (type $none_=>_none) ;; CHECK-NEXT: (block $__inlined_func$0 ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (br $__inlined_func$0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $1 (type $none_=>_none) ;; NOMNL-NEXT: (block $__inlined_func$0 ;; NOMNL-NEXT: (unreachable) - ;; NOMNL-NEXT: (br $__inlined_func$0) ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $1 diff --git a/test/lit/passes/inlining_enable-tail-call.wast b/test/lit/passes/inlining_enable-tail-call.wast index ed642594c..34b230371 100644 --- a/test/lit/passes/inlining_enable-tail-call.wast +++ b/test/lit/passes/inlining_enable-tail-call.wast @@ -561,7 +561,6 @@ ;; CHECK-NEXT: (br $__inlined_func$1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $0 @@ -629,7 +628,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (br $__inlined_func$1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $0 @@ -694,7 +692,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$13) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/inlining_optimize-level=3.wast b/test/lit/passes/inlining_optimize-level=3.wast index e5a0bc7e1..9e20e27dd 100644 --- a/test/lit/passes/inlining_optimize-level=3.wast +++ b/test/lit/passes/inlining_optimize-level=3.wast @@ -495,3 +495,71 @@ (unreachable) ) ) + +;; We inline multiple times here, and in the sequence of those inlinings we +;; turn the code in $B unreachable (when we inline $D), and no later inlining +;; (of $C or $A, or even $C's inlining in $A) should turn it into anything else +;; than an unreachable - once it is unreachable, we should keep it that way. +;; (That avoids possible validation problems, and maximizes DCE.) To keep it +;; unreachable we'll add an unreachable instruction after the inlined code. +(module + ;; CHECK: (type $f32_=>_none (func (param f32))) + + ;; CHECK: (type $none_=>_none (func)) + + ;; CHECK: (func $A (param $0 f32) + ;; CHECK-NEXT: (local $1 f32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result f32) + ;; CHECK-NEXT: (block $__inlined_func$C (result f32) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $A (param $0 f32) + (drop + (call $C + (local.get $0) + ) + ) + ) + ;; CHECK: (func $B + ;; CHECK-NEXT: (local $0 f32) + ;; CHECK-NEXT: (call $A + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $__inlined_func$C (result f32) + ;; CHECK-NEXT: (local.tee $0 + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block $__inlined_func$D + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $B + (call $A + (call $C + (call $D) + ) + ) + ) + (func $C (param $0 f32) (result f32) + (local.get $0) + ) + (func $D (result f32) + (unreachable) + ) +) diff --git a/test/lit/passes/inlining_splitting.wast b/test/lit/passes/inlining_splitting.wast index 2f840a494..9764aa235 100644 --- a/test/lit/passes/inlining_splitting.wast +++ b/test/lit/passes/inlining_splitting.wast @@ -231,7 +231,6 @@ ;; CHECK-NEXT: (br $l) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (br $__inlined_func$nondefaultable-param) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $call-nondefaultable-param |