Repository navigation
Conversation
When ConstantFieldPropagation or GlobalStructInference optimizes a struct.get or ref.get_desc, it either wraps the reference operand in a Block or places it into the condition (third child) of a Select. Any pop instruction inside the reference from a legacy exception-handling catch body then becomes either nested inside a block or displaced out of the catch body's first-descendant chain. Update EHUtils::handleBlockNestedPop to also find and lift pops that have been moved out of the first-descendant chain, and call EHUtils::handleBlockNestedPops in both passes when reads are optimized.
| // The pop may have been moved out of the first-descendant line, e.g. | ||
| // into the condition of a select. Find it and fix it up just like a | ||
| // block-nested pop. |
There was a problem hiding this comment.
What do we think about this solution? The alternative would be to inhibit these optimizations in the presence of pops.
There was a problem hiding this comment.
If we are gonna ignore getFirstPop's "no", we probably should remove it and replace it with this? There's no point in calling and keeping getFirstPop at this point.
There was a problem hiding this comment.
Code Review
This pull request introduces a helper function findPopPtr in eh-utils.cpp to locate nested Pop expressions and fix them up when they are moved out of the first-descendant line (e.g., into select conditions or blocks). This fix-up logic is integrated into the ConstantFieldPropagation and GlobalStructInference passes, with accompanying tests. A critical issue was identified in the new findPopPtr function where curr can be nullptr for optional AST children, which would lead to a segmentation fault when calling curr->is<Pop>(). A null check should be added to prevent this.
| pop = *popPtr; | ||
| isPopNested = true; | ||
| } | ||
| } |
There was a problem hiding this comment.
Can we separate this fix from adding calls to the two passes?
There was a problem hiding this comment.
The reason this fix goes along with these two passes is that these passes are the only ones (that we know about) that can move the pop out of that first-child position. We could of course split it up in principle, but I think it makes sense to keep them together.
There was a problem hiding this comment.
Ok, then let me try to understand this change more. How does getFirstPop differ from findPopPtr? The new function isn't documented and I'm not sure exactly what it is doing yet.
There was a problem hiding this comment.
getFirstPop only looks at the leftmost leaf in the expression tree, i.e. where pop is supposed to be found. findPopPtr looks for a pop in the entire expression tree, except inside nested trys.
There was a problem hiding this comment.
Thanks, though now I'm a little fuzzy on why we need both - the motivation/background is not clear to me?
There was a problem hiding this comment.
I think the reason I only allowed pops in first-descendant lines in #4348 was because otherwise the code was invalid, and all optimizations that needed the fixup so far were only nesting pops into a block and nothing more. (@kripken Is this right? You are who added almost all fixup code in various passes. Does this reflect your experience, or you saw cases where pops were displaced out of first-descendant lines but you just gave up optimizing them?)
Anyway, if we are to allow pops to be hoisted from anywhere within a catch block, I think we should just replace it with the existing first-descendant searching (because we are ignoring its result anyway). I'm also wondering, should we go into loops, where pops can run multiple times, and if's true/false bodies, where pops run conditionally? Or should we just avoid going into nested catches?
| // The pop may have been moved out of the first-descendant line, e.g. | ||
| // into the condition of a select. Find it and fix it up just like a | ||
| // block-nested pop. |
There was a problem hiding this comment.
If we are gonna ignore getFirstPop's "no", we probably should remove it and replace it with this? There's no point in calling and keeping getFirstPop at this point.
I think it would be harmless to search in ifs and loops. It's hard to imagine a case where an optimization would be able to move a pop into an if or a loop, and maybe that never makes sense. But if it did make sense, then we would definitely need to fix it up. I don't think there's much downside to searching in ifs or loops, either. I doubt it makes much of a performance difference. |
Yes, that is all I have seen. Actually I was just thinking that since that is the common case, maybe there is a simpler solution to all this. Yesterday I started Gemini on an attempt to allow multiple unnamed blocks in validation - will report back when it finishes. |
|
Ok, #9292 is up for feedback. If that direction looks good maybe we don't need these CFP/GSI changes? |
|
We still need these changes because pops can only ever validate in the leftmost leaf position, and the problem here is that CFP and GSI do not preserve this. |
kripken
left a comment
There was a problem hiding this comment.
Makes sense, happy to review this.
| return pop != nullptr && !isPopNested; | ||
| } | ||
|
|
||
| static Expression** findPopPtr(Expression*& expr) { |
There was a problem hiding this comment.
Maybe add a comment on this function?
|
Ok, I've pushed a new implementation that 1) replaces |
|
|
||
| // If we changed anything, we need to update parent types as types may have | ||
| // changed. | ||
| // changed, and fix up any pops that we enclosed in a block. |
| // to synchronize with in the first place. | ||
| ref = builder.makeSequence( | ||
| builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref)), get); | ||
| needEHFixups = true; |
| replaceCurrent(builder.makeSequence( | ||
| builder.makeDrop(builder.makeRefAs(RefAsNonNull, ref)), | ||
| getReadValue(values[0], fieldIndex, field, curr))); | ||
| needEHFixups = true; |
| ;; Optimizing this struct.get wraps its reference in a block, requiring the | ||
| ;; enclosed pop to be fixed up. |
There was a problem hiding this comment.
Unnecessary after #9292. We can probably just remove the test, or alternatively keep it as a test showing that we need no fixup.
| ;; Optimizing this struct.get wraps its reference in a block, requiring the | ||
| ;; enclosed pop to be fixed up. |
When ConstantFieldPropagation or GlobalStructInference optimizes a struct.get or ref.get_desc, it either wraps the reference operand in a Block or places it into the condition (third child) of a Select. Any pop instruction inside the reference from a legacy exception-handling catch body then becomes either nested inside a block or displaced out of the catch body's first-descendant chain. Update EHUtils::handleBlockNestedPop to also find and lift pops that have been moved out of the first-descendant chain, and call EHUtils::handleBlockNestedPops in both passes when reads are optimized.