Skip to content

Add a MarkNoReturn pass - #9229

Merged
kripken merged 11 commits into
WebAssembly:mainfrom
kripken:mark.onreturn
Oct 8, 2026
Merged

kripken merged 11 commits into
WebAssembly:mainfrom
kripken:mark.onreturn

Conversation

@kripken

@kripken kripken commented Oct 7, 2026

Copy link
Copy Markdown
Member

This adds the @binaryen.noreturn annotation where we can infer the
function does not return. Other passes can then use that information.

This does not run by default yet. Design-wise, we don't want this to
run and add annotations, as then they need to be stripped by the user
(that is, just adding these annotations now will add to binary size, for
users that don't run --strip-toolchain-annotations). We can either

  1. Never run this by default (but we miss out on some optimizations).
  2. Run this by default and document the issue, expecting users
    (and toolchains like emscripten, kotlin, dart, etc.) to strip the
    annotation.
  3. Run this by default and automatically clean up the annotation. E.g.
    -O3 could push a stack of things to undo, and pop it and undo them
    at the end end, something like that.

Thoughts on these options?

@kripken
kripken requested a review from tlively October 7, 2026 18:12
@kripken
kripken requested a review from a team as a code owner October 7, 2026 18:12
@kripken

kripken commented Oct 7, 2026 •

Copy link
Copy Markdown
Member Author

If we add some "auto-cleanup" mechanism in the pass runner, it might also be useful for doing strings and other things in a nicer/more automatic way..?

@tlively

tlively commented Oct 7, 2026

Copy link
Copy Markdown
Member

Another option would be to strip toolchain annotations by default at the end of the pass pipeline unless users explicitly disable that stripping.

@kripken

kripken commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Interesting, yeah, that would be safer for code size. Though people that run multiple wasm-opt invocations would need to know to disable this (i.e. to keep the annotations) or else miss out on some opts. Not sure what's best here.

Comment thread src/ir/lubs.h
// roll back that change, but it's not harmful and can help, so we keep it
// regardless.
LUBFinder getResultsLUB(Function* func, Module& wasm);
enum Mode {

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.

I guess this is a performance optimization? How much does it matter? It would be nice to unify the handling if possible.

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.

It is significant, I think. Without this flag, we scan the entire module unnecessarily, rather than just functions returning a GC type.

Comment thread src/ir/lubs.cpp
}
void visitCall(Call* curr) {
if (curr->isReturn) {
lub.note(wasm.getFunction(curr->target)->getResults());

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.

This isn't going to handle cases where the tail-called function itself does not return or has refinable results.

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.

Makes sense, I'll add a TODO for propagating noreturn

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.

It might be better to do this as a part of the existing passes that refine result types, since they can find the fixed point in the case of cycles.

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.

Heh, that was my first idea, which you can see here:

https://github.com/WebAssembly/binaryen/compare/main...kripken:binaryen:dae.noreturn?expand=1

Unfortunately it ends up adding a bunch of work inside each pass. It seems cleaner and simpler to just mark first, independently.

But I do agree cycles etc. are worth handling, I intend to add that part in those passes later. That is, after this pass marks functions, specific passes can use that information to handle cycles.

@tlively

tlively commented Oct 7, 2026

Copy link
Copy Markdown
Member

Though people that run multiple wasm-opt invocations would need to know to disable this (i.e. to keep the annotations) or else miss out on some opts.

I assume these people are generally toolchain developers, who we can perhaps expect to pay more attention to things like this.

@kripken

kripken commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

I assume these people are generally toolchain developers, who we can perhaps expect to pay more attention to things like this.

Yes, fair point.

Though I can see an argument the other way too, as silent optimization misses are easy to miss. Not sure what is best here.

@kripken

kripken commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

@tlively what do you think about landing this PR? (this is needed regardless of how we decide to handle the larger picture)

@tlively tlively left a comment

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.

LGTM to land while we experiment with potentially more powerful solutions.

@kripken
kripken merged commit 57bc6ba into WebAssembly:main Oct 8, 2026
16 checks passed
@kripken
kripken deleted the mark.onreturn branch October 8, 2026 17:51
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