diff options
-rw-r--r-- | src/passes/Heap2Local.cpp | 54 | ||||
-rw-r--r-- | test/lit/passes/heap2local.wast | 116 |
2 files changed, 149 insertions, 21 deletions
diff --git a/src/passes/Heap2Local.cpp b/src/passes/Heap2Local.cpp index f0f691299..0f569374b 100644 --- a/src/passes/Heap2Local.cpp +++ b/src/passes/Heap2Local.cpp @@ -541,6 +541,19 @@ struct Heap2LocalOptimizer { auto* child = flow.first; auto* parent = flow.second; + auto interaction = getParentChildInteraction(allocation, parent, child); + if (interaction == ParentChildInteraction::Escapes || + interaction == ParentChildInteraction::Mixes) { + // If the parent may let us escape, or the parent mixes other values + // up with us, give up. + return; + } + + // The parent either fully consumes us, or flows us onwards; either way, + // we can proceed here, hopefully. + assert(interaction == ParentChildInteraction::FullyConsumes || + interaction == ParentChildInteraction::Flows); + // If we've already seen an expression, stop since we cannot optimize // things that overlap in any way (see the notes on exclusivity, above). // Note that we use a nonrepeating queue here, so we already do not visit @@ -548,31 +561,30 @@ struct Heap2LocalOptimizer { // look at something that another allocation reached, which would be in a // different call to this function and use a different queue (any overlap // between calls would prove non-exclusivity). + // + // Note that we do this after the check for Escapes/Mixes above: it is + // possible for a parent to receive two children and handle them + // differently: + // + // (struct.set + // (local.get $ref) + // (local.get $value) + // ) + // + // The value escapes, but the ref does not, and might be optimized. If we + // added the parent to |seen| for both children, the reference would get + // blocked from being optimized. if (!seen.emplace(parent).second) { return; } - switch (getParentChildInteraction(allocation, parent, child)) { - case ParentChildInteraction::Escapes: { - // If the parent may let us escape then we are done. - return; - } - case ParentChildInteraction::FullyConsumes: { - // If the parent consumes us without letting us escape then all is - // well (and there is nothing flowing from the parent to check). - break; - } - case ParentChildInteraction::Flows: { - // The value flows through the parent; we need to look further at the - // grandparent. - flows.push({parent, parents.getParent(parent)}); - break; - } - case ParentChildInteraction::Mixes: { - // Our allocation is not used exclusively via the parent, as other - // values are mixed with it. Give up. - return; - } + // We can proceed, as the parent interacts with us properly, and we are + // the only allocation to get here. + + if (interaction == ParentChildInteraction::Flows) { + // The value flows through the parent; we need to look further at the + // grandparent. + flows.push({parent, parents.getParent(parent)}); } if (auto* set = parent->dynCast<LocalSet>()) { diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 04c0936c6..37a9e7919 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -2078,3 +2078,119 @@ ) ) ) + +(module + ;; CHECK: (type $struct (struct (field (mut anyref)))) + (type $struct (struct (field (mut anyref)))) + + ;; CHECK: (func $multiple-interactions (type $1) + ;; CHECK-NEXT: (local $temp (ref $struct)) + ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multiple-interactions + (local $temp (ref $struct)) + (local.set $temp + (struct.new_default $struct) + ) + ;; This expression interacts with its children in two different ways: the + ;; reference does not escape from the function, so we can optimize it into + ;; locals, while the value read from the local is written to the heap, so it + ;; does escape. However, we can optimize it after we optimize the first + ;; allocation away, which would happen if we ran another pass of heap2local + ;; (but we do not here). + (struct.set $struct 0 + (struct.new_default $struct) + (local.get $temp) + ) + ) + + ;; CHECK: (func $multiple-interactions-both-locals (type $1) + ;; CHECK-NEXT: (local $temp (ref $struct)) + ;; CHECK-NEXT: (local $temp2 (ref $struct)) + ;; CHECK-NEXT: (local $2 anyref) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $multiple-interactions-both-locals + (local $temp (ref $struct)) + (local $temp2 (ref $struct)) + (local.set $temp + (struct.new_default $struct) + ) + ;; Now both allocations are written to locals. We can still optimize the + ;; second. + (local.set $temp2 + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $temp2) + (local.get $temp) + ) + ) + + ;; CHECK: (func $multiple-interactions-escapes (type $2) (result anyref) + ;; CHECK-NEXT: (local $temp (ref $struct)) + ;; CHECK-NEXT: (local $temp2 (ref $struct)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp2 + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $temp2) + ;; CHECK-NEXT: (local.get $temp) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $temp2) + ;; CHECK-NEXT: ) + (func $multiple-interactions-escapes (result anyref) + (local $temp (ref $struct)) + (local $temp2 (ref $struct)) + (local.set $temp + (struct.new_default $struct) + ) + (local.set $temp2 + (struct.new_default $struct) + ) + (struct.set $struct 0 + (local.get $temp2) + (local.get $temp) + ) + ;; As above, but now the second allocation escapes, so nothing is + ;; optimized. + (local.get $temp2) + ) +) |