Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/passes/OptimizeInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 will not be 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());
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();

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 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)
)

OPERANDS is moved past the STUFFs.

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.

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.

But ChildLocalizer sees all the children of the resume, including the one that has STUFF A and STUFF B here, so I think this is safe. See the $test-eval-order test, for example.

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... all the last child (with STUFF) is, is just another child for ChildLocalizer. Nice it works out so well!

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:
Expand Down
Loading
Loading