diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 26 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-tnh.wast | 150 |
2 files changed, 149 insertions, 27 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 6251348af..acb2c8d66 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1615,9 +1615,27 @@ struct OptimizeInstructions // TODO Worth thinking about an 'assume' instrinsic of some form that // annotates knowledge about a value, or another mechanism to allow // that information to be passed around. + + // Note that we must check that the null is actually flowed out, that is, + // that control flow is not transferred before: + // + // (if + // (1) + // (block (result null) + // (return) + // ) + // (other)) + // + // The true arm has a bottom type, but in fact it just returns out of the + // function and the null does not actually flow out. We can only optimize + // here if a null definitely flows out (as only that would cause a trap). + auto flowsOutNull = [&](Expression* child) { + return child->type.isNull() && !effects(child).transfersControlFlow(); + }; + if (auto* iff = ref->dynCast<If>()) { if (iff->ifFalse) { - if (iff->ifTrue->type.isNull()) { + if (flowsOutNull(iff->ifTrue)) { if (ref->type != iff->ifFalse->type) { refinalize = true; } @@ -1625,7 +1643,7 @@ struct OptimizeInstructions iff->ifFalse); return false; } - if (iff->ifFalse->type.isNull()) { + if (flowsOutNull(iff->ifFalse)) { if (ref->type != iff->ifTrue->type) { refinalize = true; } @@ -1641,7 +1659,7 @@ struct OptimizeInstructions // refinalize only happens at the end. That is, the select may stil be // reachable after we turned one child into an unreachable, and we are // calling getResultOfFirst which will error on unreachability. - if (select->ifTrue->type.isNull() && + if (flowsOutNull(select->ifTrue) && select->ifFalse->type != Type::unreachable) { ref = builder.makeSequence( builder.makeDrop(select->ifTrue), @@ -1649,7 +1667,7 @@ struct OptimizeInstructions builder.makeDrop(select->condition))); return false; } - if (select->ifFalse->type.isNull() && + if (flowsOutNull(select->ifFalse) && select->ifTrue->type != Type::unreachable) { ref = getResultOfFirst( select->ifTrue, diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index 11627d90c..34071f2af 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -319,77 +319,134 @@ ) ;; TNH: (func $select.arm.null.effects (type $void) - ;; TNH-NEXT: (local $0 (ref $struct)) + ;; TNH-NEXT: (local $temp i32) ;; TNH-NEXT: (local $1 (ref $struct)) + ;; TNH-NEXT: (local $2 (ref $struct)) ;; TNH-NEXT: (struct.set $struct 0 ;; TNH-NEXT: (block (result (ref $struct)) - ;; TNH-NEXT: (local.set $0 - ;; TNH-NEXT: (call $get-ref) + ;; TNH-NEXT: (local.set $1 + ;; TNH-NEXT: (struct.new $struct + ;; TNH-NEXT: (local.tee $temp + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (call $get-null) + ;; TNH-NEXT: (block (result nullref) + ;; TNH-NEXT: (local.set $temp + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (ref.null none) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (call $get-i32) + ;; TNH-NEXT: (local.get $temp) ;; TNH-NEXT: ) ;; TNH-NEXT: ) - ;; TNH-NEXT: (local.get $0) + ;; TNH-NEXT: (local.get $1) ;; TNH-NEXT: ) ;; TNH-NEXT: (i32.const 1) ;; TNH-NEXT: ) ;; TNH-NEXT: (struct.set $struct 0 ;; TNH-NEXT: (block (result (ref $struct)) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (call $get-null) + ;; TNH-NEXT: (block (result nullref) + ;; TNH-NEXT: (local.set $temp + ;; TNH-NEXT: (i32.const 2) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (ref.null none) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (block (result (ref $struct)) - ;; TNH-NEXT: (local.set $1 - ;; TNH-NEXT: (call $get-ref) + ;; TNH-NEXT: (local.set $2 + ;; TNH-NEXT: (struct.new $struct + ;; TNH-NEXT: (local.tee $temp + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (drop - ;; TNH-NEXT: (call $get-i32) + ;; TNH-NEXT: (local.get $temp) ;; TNH-NEXT: ) - ;; TNH-NEXT: (local.get $1) + ;; TNH-NEXT: (local.get $2) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; TNH-NEXT: (i32.const 2) ;; TNH-NEXT: ) ;; TNH-NEXT: ) ;; NO_TNH: (func $select.arm.null.effects (type $void) + ;; NO_TNH-NEXT: (local $temp i32) ;; NO_TNH-NEXT: (struct.set $struct 0 ;; NO_TNH-NEXT: (select (result (ref null $struct)) - ;; NO_TNH-NEXT: (call $get-ref) - ;; NO_TNH-NEXT: (call $get-null) - ;; NO_TNH-NEXT: (call $get-i32) + ;; NO_TNH-NEXT: (struct.new $struct + ;; NO_TNH-NEXT: (local.tee $temp + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (local.set $temp + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (ref.null none) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.get $temp) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (i32.const 1) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (struct.set $struct 0 ;; NO_TNH-NEXT: (select (result (ref null $struct)) - ;; NO_TNH-NEXT: (call $get-null) - ;; NO_TNH-NEXT: (call $get-ref) - ;; NO_TNH-NEXT: (call $get-i32) + ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (local.set $temp + ;; NO_TNH-NEXT: (i32.const 2) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (ref.null none) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (struct.new $struct + ;; NO_TNH-NEXT: (local.tee $temp + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (local.get $temp) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (i32.const 2) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) (func $select.arm.null.effects + (local $temp i32) ;; As above but there are conflicting effects and we must add a local when ;; we optimize. (struct.set $struct 0 (select (result (ref null $struct)) - (call $get-ref) - (call $get-null) - (call $get-i32) + (struct.new $struct + (local.tee $temp + (i32.const 1) + ) + ) + (block (result (ref null none)) + (local.set $temp + (i32.const 2) + ) + (ref.null none) + ) + (local.get $temp) ) (i32.const 1) ) (struct.set $struct 0 (select (result (ref null $struct)) - (call $get-null) - (call $get-ref) - (call $get-i32) + (block (result (ref null none)) + (local.set $temp + (i32.const 2) + ) + (ref.null none) + ) + (struct.new $struct + (local.tee $temp + (i32.const 1) + ) + ) + (local.get $temp) ) (i32.const 2) ) @@ -845,6 +902,53 @@ ) ) + ;; TNH: (func $if.null.child.but.no.flow (type $void) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (block (result (ref func)) + ;; TNH-NEXT: (drop + ;; TNH-NEXT: (if (result (ref nofunc)) + ;; TNH-NEXT: (i32.const 1) + ;; TNH-NEXT: (return) + ;; TNH-NEXT: (unreachable) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: (unreachable) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; TNH-NEXT: ) + ;; NO_TNH: (func $if.null.child.but.no.flow (type $void) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (block (result (ref func)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (if (result (ref nofunc)) + ;; NO_TNH-NEXT: (i32.const 1) + ;; NO_TNH-NEXT: (return) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + (func $if.null.child.but.no.flow + ;; The if's true arm has a bottom type, which the cast would trap on. But we + ;; cannot optimize using that fact, as the null does not actually flow out - + ;; we return from the function before. So we should not replace the if with + ;; the false arm (that would trap, and change the behavior; tnh can remove + ;; traps, not add them). + (drop + (ref.cast func + (if (result (ref nofunc)) + (i32.const 1) + (block (result (ref nofunc)) + (return) + ) + (unreachable) + ) + ) + ) + ) + ;; Helper functions. ;; TNH: (func $get-i32 (type $none_=>_i32) (result i32) |