-
Notifications
You must be signed in to change notification settings - Fork 882
Optimize resume of non-suspending continuations #9071
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
base: main
Are you sure you want to change the base?
Changes from all commits
9a36dad
f3f4698
64465af
75fa6e3
cb44856
d1dedd4
de31b49
114fa8e
e64929f
7daad92
a3ffe83
c0f49ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1461,6 +1461,65 @@ struct OptimizeInstructions | |
| } | ||
| } | ||
|
|
||
| void visitResume(Resume* curr) { | ||
| skipNonNullCast(curr->cont, curr); | ||
| if (trapOnNull(curr, curr->cont)) { | ||
| return; | ||
| } | ||
| if (curr->type == Type::unreachable) { | ||
| return; | ||
| } | ||
|
|
||
| // If this resume operates on a freshly-created continuation of an exact | ||
| // function that is known never to suspend, we can turn the resumption into | ||
| // a direct call, avoiding the continuation allocation and handler overhead. | ||
|
|
||
| // Continuations are single-shot, so resuming a continuation that has | ||
| // already been consumed will trap. If traps are assumed never to happen, we | ||
| // can assume this continuation was not consumed on another path and look | ||
| // through tees and conditional branches. Otherwise, avoid looking through | ||
| // them to ensure the continuation cannot be consumed elsewhere. | ||
| auto behavior = getPassOptions().trapsNeverHappen | ||
| ? Properties::FallthroughBehavior::AllowTeeBrIf | ||
| : Properties::FallthroughBehavior::NoTeeBrIf; | ||
|
|
||
| auto* contExpr = Properties::getFallthrough( | ||
| curr->cont, getPassOptions(), *getModule(), behavior); | ||
| auto* contNew = contExpr->dynCast<ContNew>(); | ||
| if (!contNew) { | ||
| return; | ||
| } | ||
| if (contNew->func->type == Type::unreachable) { | ||
| return; | ||
| } | ||
|
|
||
| auto* funcExpr = Properties::getFallthrough( | ||
| contNew->func, getPassOptions(), *getModule(), behavior); | ||
| auto* refFunc = funcExpr->dynCast<RefFunc>(); | ||
| if (!refFunc) { | ||
| return; | ||
| } | ||
|
|
||
| auto* target = getModule()->getFunctionOrNull(refFunc->func); | ||
| if (!target || target->imported()) { | ||
| return; | ||
| } | ||
|
|
||
| if (!target->effects || target->effects->suspends) { | ||
| return; | ||
| } | ||
|
|
||
| auto* block = | ||
| ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) | ||
| .getChildrenReplacement(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realize that this is almost but not quite right. ChildLocalizer considers child effects wrt each other. After the operation, it is safe to reorder and remove them. But moving them across other effects might not be safe. Unless we know no other effects can be in the middle, here? But it seems like there can be: (resume
(OPERANDS)
(block
(STUFF A)
(cont.new
(block
(STUFF B)
(ref.func)
)
)
)
)
=>
(STUFF A)
(STUFF B)
(call
(OPERANDS)
)
This is simple to handle, though: add a flag to ChildLocalizer to control this behavior. Where it computes // Use a local if we need to. That is the case either if this has side
// effects we can't remove, or if it interacts with other children.then, we can have a mode where it uses a local for any child with effects, even removable ones. |
||
| Builder builder(*getModule()); | ||
| Type results = target->getResults(); | ||
| block->list.push_back( | ||
| builder.makeCall(target->name, curr->operands, results)); | ||
| block->type = results; | ||
| replaceCurrent(block); | ||
| } | ||
|
|
||
| // Note on removing casts (which the following utilities, skipNonNullCast and | ||
| // skipCast do): removing a cast is potentially dangerous, as it removes | ||
| // information from the IR. For example: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallthrough of the function doesn't need to care about other uses and traps and such.