Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Parser] Fix bug in unreachable fallback logic #6676

Merged
merged 2 commits into from
Jun 19, 2024

Conversation

tlively
Copy link
Member

@tlively tlively commented Jun 18, 2024

When popping past an unreachable instruction would lead to popping from an empty
stack or popping an incorrect type, we need to avoid popping and produce new
Unreachable instructions instead to ensure we parse valid IR. The logic for
this was flawed and made the synthetic Unreachable come before the popped
unreachable child, which was not correct in the case that that popped
unreachable was a branch or other non-trapping instruction. Fix and simplify the
logic and re-enable the spec test that uncovered the bug.

@tlively tlively requested a review from kripken June 18, 2024 01:20
Comment on lines 413 to 414
// not reaching past an unreachable, the error will be caught we
// when pop.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// not reaching past an unreachable, the error will be caught we
// when pop.
// not reaching past an unreachable, the error will be caught when
// we pop.

// the input unreachable instruction is executed first. If we are
// not reaching past an unreachable, the error will be caught we
// when pop.
needUnreachableFallback = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to change the meaning of needUnreachableFallback slightly - worth updating the comment on line 379 perhaps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That comment is actually already correct because it mentions "or try to pop from an empty stack." This fixes the code to agree with the comment.

// The next item on the stack is the unreachable instruction we must
// not pop past. We cannot insert unreachables in front of it because
// it might be a branch we actually have to execute, so this next item
// must be child 0. But we are not ready to pop child 0 yet, so
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having a hard time following this. "The next item is the unreachable we must not pop past" fits scope.exprStack.size() == *unreachableIndex + 1. Then "this next item be child 0" seems to refer to the same "next item", which suggests *unreachableIndex == 0. Is that right? If so then I can't see why.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, *unreachableIndex is an index in the stack, not in the children we are popping. i is the index of the child we want to pop, so that's why we check i > 0 here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That helps, thanks. But if i is the index of the child we want to pop, and we check i > 0 before getting here, how can so this next item must be child 0 be true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because inside this condition we are not popping a value from the stack, we are synthesizing an unreachable instead. We're deferring popping that value until i == 0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Ok, I think I understand the surrounding code a bit better now. But I am still quite confused 😄 Specifically, about this sentence:

We cannot insert unreachables in front of it because it might be a branch we actually have to execute, so this next item must be child 0.

I can't follow the inference of "so". I can try to paraphrase the key part of it as "We can't insert unreachables, as it might be a branch, and therefore it must be child 0". Neither "we can't insert unreachables" nor "it might be a branch" suggest to me any reason why the child must be 0 and not any other index.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we put the unreachable instruction from the stack in the child 1 slot, then there would be nothing valid to do for the child 0 slot. Popping off the stack would be popping past the unreachable, which we determined we cannot do. Synthesizing an Unreachable for child 0 would insert that Unreachable before the unreachable instruction from the stack in the execution order and change the program semantics if the instruction from the stack was a branch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is that not a problem for the child 2 slot as well? If we put the unreachable instruction from the stack in the child 2 slot then there would be nothing to do for the child 1 or child 0 slots, I think, for the exact reasons you said?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, it's a problem for any slot except for child 0. That's why the child 0 slot is special.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now, thanks... sorry for being dense!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha no worries! It's certainly a dense piece of code.

Base automatically changed from malformed-custom-section-id to main June 18, 2024 18:13
When popping past an unreachable instruction would lead to popping from an empty
stack or popping an incorrect type, we need to avoid popping and produce new
`Unreachable` instructions instead to ensure we parse valid IR. The logic for
this was flawed and made the synthetic `Unreachable` come before the popped
unreachable child, which was not correct in the case that that popped
unreachable was a branch or other non-trapping instruction. Fix and simplify the
logic and re-enable the spec test that uncovered the bug.
@tlively tlively force-pushed the parser-unreachable-fallback-fix branch from 1d5e9aa to cebdb16 Compare June 18, 2024 19:56
@tlively tlively merged commit 829e228 into main Jun 19, 2024
13 checks passed
@tlively tlively deleted the parser-unreachable-fallback-fix branch June 19, 2024 00:14
@gkdn gkdn mentioned this pull request Aug 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants