@@ -170,8 +170,16 @@ namespace {
170170// Core analysis that provides an escapes() method to check if an allocation
171171// escapes in a way that prevents optimizing it away as described above. It also
172172// stashes information about the relevant expressions as it goes, which helps
173- // optimization later (|reached|).
173+ // optimization later (|seen| and | reached|).
174174struct EscapeAnalyzer {
175+ // All the expressions that have already been seen by the optimizer, see the
176+ // comment above on exclusivity: once we have seen something when analyzing
177+ // one allocation, if we reach it again then we can exit early since seeing it
178+ // a second time proves we lost exclusivity. We must track this across
179+ // multiple instances of EscapeAnalyzer as each handles a particular
180+ // allocation.
181+ std::unordered_set<Expression*>& seen;
182+
175183 // To find what escapes, we need to follow where values flow, both up to
176184 // parents, and via branches, and through locals.
177185 // TODO: for efficiency, only scan reference types in LocalGraph
@@ -182,12 +190,13 @@ struct EscapeAnalyzer {
182190 const PassOptions& passOptions;
183191 Module& wasm;
184192
185- EscapeAnalyzer (const LocalGraph& localGraph,
193+ EscapeAnalyzer (std::unordered_set<Expression*>& seen,
194+ const LocalGraph& localGraph,
186195 const Parents& parents,
187196 const BranchUtils::BranchTargets& branchTargets,
188197 const PassOptions& passOptions,
189198 Module& wasm)
190- : localGraph(localGraph), parents(parents),
199+ : seen(seen), localGraph(localGraph), parents(parents),
191200 branchTargets (branchTargets), passOptions(passOptions), wasm(wasm) {}
192201
193202 // We must track all the local.sets that write the allocation, to verify
@@ -252,6 +261,28 @@ struct EscapeAnalyzer {
252261 assert (interaction == ParentChildInteraction::FullyConsumes ||
253262 interaction == ParentChildInteraction::Flows);
254263
264+ // If we've already seen an expression, stop since we cannot optimize
265+ // things that overlap in any way (see the notes on exclusivity, above).
266+ // Note that we use a nonrepeating queue here, so we already do not visit
267+ // the same thing more than once; what this check does is verify we don't
268+ // look at something that another allocation reached, which would be in a
269+ // different call to this function and use a different queue (any overlap
270+ // between calls would prove non-exclusivity).
271+ //
272+ // It is ok, however, to see a parent more than once, if the allocation
273+ // flows to it from two children, as is the case for ref.eq. Likewise, for
274+ // struct.set, where we also have different considerations for the two
275+ // children (the reference does not escape, but the value does), and in
276+ // that case there may be different allocations for the children.
277+ if (interaction == ParentChildInteraction::Flows) {
278+ auto seenBefore = !seen.emplace (parent).second ;
279+ auto reachedInThisAllocation = reached.count (parent) > 0 ;
280+ if (seenBefore && !reachedInThisAllocation) {
281+ // XXX struct.set can be from different allocations!
282+ return true ;
283+ }
284+ }
285+
255286 // We can proceed, as the parent interacts with us properly, and we are
256287 // the only allocation to get here.
257288
@@ -1031,6 +1062,10 @@ struct Heap2Local {
10311062 // flow to.
10321063 localGraph.computeSetInfluences ();
10331064
1065+ // All the expressions we have already looked at. We use this to avoid
1066+ // repeated work, see above.
1067+ std::unordered_set<Expression*> seen;
1068+
10341069 // Find all the relevant allocations in the function: StructNew, ArrayNew,
10351070 // ArrayNewFixed.
10361071 struct AllocationFinder : public PostWalker <AllocationFinder> {
@@ -1090,7 +1125,7 @@ struct Heap2Local {
10901125 }
10911126
10921127 EscapeAnalyzer analyzer (
1093- localGraph, parents, branchTargets, passOptions, wasm);
1128+ seen, localGraph, parents, branchTargets, passOptions, wasm);
10941129 if (!analyzer.escapes (allocation)) {
10951130 // Convert the allocation and all its uses into a struct. Then convert
10961131 // the struct into locals.
@@ -1110,7 +1145,7 @@ struct Heap2Local {
11101145 // Check for escaping, noting relevant information as we go. If this does
11111146 // not escape, optimize it into locals.
11121147 EscapeAnalyzer analyzer (
1113- localGraph, parents, branchTargets, passOptions, wasm);
1148+ seen, localGraph, parents, branchTargets, passOptions, wasm);
11141149 if (!analyzer.escapes (allocation)) {
11151150 Struct2Local (allocation, analyzer, func, wasm);
11161151 }
0 commit comments