Skip to content

Fix and improve unreachable parsing#8617

Merged
tlively merged 2 commits intomainfrom
greedy-fix
Apr 20, 2026
Merged

Fix and improve unreachable parsing#8617
tlively merged 2 commits intomainfrom
greedy-fix

Conversation

@tlively
Copy link
Copy Markdown
Member

@tlively tlively commented Apr 17, 2026

The recent change (#8608) that reduced the number of scratch locals introduced by IRBuilder also inadvertently introduced a bug. That commit changed the conditions under which we package multiple expressions into a block to be popped together. We previously did this whenever there was a get of a scratch local to hoist a value to the top of the stack. After #8608, we started using blocks whenever there were multiple expressions hoisted. These conditions are usually identical, but we missed the case where there are multiple expressions and also no get because the value is unreachable. The introduction of a block where there was none before invalidated our logic for determining whether or not to pop back past an unreachable expression based on whether the expressions underneath it would satisfy the type constraints of the parent.

Simplify that logic by calculating the stack size at which we must avoid popping past an unreachable rather than calculating the index of the unreachable itself. This makes the logic work correctly whether or not the unreachable will be popped as part of a block. That in turn lets us remove an extra unreachable we were conservatively pushing onto the stack when "hoisting" unreachable values.

Add tests for the cases where we can and cannot pop past an unreachable that is followed by a none-typed expression. The former case previously parsed incorrectly and the latter case is now parsed without introducing extra unreachable instructions.

tlively added 2 commits April 16, 2026 21:37
When an instruction has a type immediate that we cannot print because the expression it is supposed to come from has unreachable or null type, we instead print an unreachable block with a comment saying what instruction we failed to print. We previously handled this via different code paths for type immediates that come from child expressions that type immediates that come from the printed expression's own return type. Unify these code paths in the printer by improving `Properties::hasUnwritableTypeImmediate` to handle both cases. Also fix the printing of `ShallowExpression` to check for unwritable type immediates first to avoid assertion failures.
The recent change (#8608) that reduced the number of scratch locals introduced by IRBuilder also inadvertently introduced a bug. That commit changed the conditions under which we package multiple expressions into a block to be popped together. We previously did this whenever there was a get of a scratch local to hoist a value to the top of the stack. After #8608, we started using blocks whenever there were multiple expressions hoisted. These conditions are usually identical, but we missed the case where there are multiple expressions and also no `get` because the value is unreachable. The introduction of a block where there was none before invalidated our logic for determining whether or not to pop back past an unreachable expression based on whether the expressions underneath it would satisfy the type constraints of the parent.

Simplify that logic by calculating the stack size at which we must avoid popping past an unreachable rather than calculating the index of the unreachable itself. This makes the logic work correctly whether or not the unreachable will be popped as part of a block. That in turn lets us remove an extra unreachable we were conservatively pushing onto the stack when "hoisting" unreachable values.

Add tests for the cases where we can and cannot pop past an unreachable that is followed by a none-typed expression. The former case previously parsed incorrectly and the latter case is now parsed without introducing extra unreachable instructions.
@tlively tlively requested a review from a team as a code owner April 17, 2026 05:08
@tlively tlively requested review from stevenfontanella and removed request for a team April 17, 2026 05:08
Base automatically changed from simplify-unreachable-print to main April 20, 2026 14:28
@tlively tlively requested a review from kripken April 20, 2026 14:35
Comment thread src/ir/properties.h
if (!cast->desc->type.isRef()) {
return true;
}
if (!cast->desc->type.getHeapType().getDescribedType()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wouldn't this fail validation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah, this is from an outdated version of #8616. Will update this branch.

But to answer the question, yes, this would fail validation, but this code is used from the Printer, so needs to work correctly for invalid IR as well.

i32.const 0
unreachable
nop
struct.set $pair 0
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we not stop parsing entirely at the unreachable? We can skip unreachable code since we don't try to precisely roundtrip anyhow. In fact I believe we used to do that in the past?

Copy link
Copy Markdown
Member Author

@tlively tlively Apr 20, 2026

Choose a reason for hiding this comment

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

We can't skip parsing instructions after unreachables if we want to be able to write tests containing expressions with unreachable children (which we definitely do). We might have skipped parsing such instructions in the binary parser in the past, but now both text and binary parsers use IRBuilder.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, right - makes sense.

@tlively tlively merged commit 0f4d388 into main Apr 20, 2026
16 checks passed
@tlively tlively deleted the greedy-fix branch April 20, 2026 16:23
@tlively
Copy link
Copy Markdown
Member Author

tlively commented Apr 20, 2026

🤦 this didn't include the commit the remove the stale code from #8616...

tlively added a commit that referenced this pull request Apr 20, 2026
When #8617 landed, it introduced this code from an out-of-date version
of #8616. The code had been moved in such a way that the merge kept both
old and new versions instead of creating a merge conflict. Remove the
outdated code.
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