summaryrefslogtreecommitdiff
path: root/src/passes/RemoveUnusedBrs.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/RemoveUnusedBrs.cpp')
-rw-r--r--src/passes/RemoveUnusedBrs.cpp176
1 files changed, 141 insertions, 35 deletions
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index dc7741c0e..4fda7afb2 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -699,8 +699,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
}
struct Optimizer : public PostWalker<Optimizer> {
+ PassOptions& passOptions;
bool worked = false;
+ Optimizer(PassOptions& passOptions) : passOptions(passOptions) {}
+
void visitBrOn(BrOn* curr) {
// Ignore unreachable BrOns which we cannot improve anyhow. Note that
// we must check the ref field manually, as we may be changing types as
@@ -712,56 +715,159 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
return;
}
- // First, check for a possible null which would prevent optimizations on
- // null checks.
- // TODO: Use the fallthrough to determine in more cases that we
- // definitely have a null.
- auto refType = curr->ref->type;
- if (refType.isNullable() &&
- (curr->op == BrOnNull || curr->op == BrOnNonNull)) {
- return;
- }
+ Builder builder(*getModule());
+
+ Type refType =
+ Properties::getFallthroughType(curr->ref, passOptions, *getModule());
+ assert(refType.isRef());
+
+ // When we optimize based on all the fallthrough type information
+ // available, we may need to insert a cast to maintain validity. For
+ // example, in this case we know the cast will succeed, but it would be
+ // invalid to send curr->ref directly:
+ //
+ // (br_on_cast $l anyref i31ref
+ // (block (result anyref)
+ // (i31.new ...)))
+ //
+ // We could just always do the cast and leave removing the casts to
+ // OptimizeInstructions, but it's simple enough to avoid unnecessary
+ // casting here.
+ auto maybeCast = [&](Expression* expr, Type type) -> Expression* {
+ assert(expr->type.isRef() && type.isRef());
+ if (Type::isSubType(expr->type, type)) {
+ return expr;
+ }
+ if (HeapType::isSubType(expr->type.getHeapType(),
+ type.getHeapType())) {
+ return builder.makeRefAs(RefAsNonNull, expr);
+ }
+ return builder.makeRefCast(expr, type);
+ };
if (curr->op == BrOnNull) {
- assert(refType.isNonNullable());
- // This cannot be null, so the br is never taken, and the non-null
- // value flows through.
- replaceCurrent(curr->ref);
- worked = true;
+ if (refType.isNull()) {
+ // The branch will definitely be taken.
+ replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
+ builder.makeBreak(curr->name)));
+ worked = true;
+ return;
+ }
+ if (refType.isNonNullable()) {
+ // The branch will definitely not be taken.
+ replaceCurrent(maybeCast(curr->ref, curr->type));
+ worked = true;
+ return;
+ }
return;
}
+
if (curr->op == BrOnNonNull) {
- assert(refType.isNonNullable());
- // This cannot be null, so the br is always taken.
- replaceCurrent(
- Builder(*getModule()).makeBreak(curr->name, curr->ref));
- worked = true;
+ if (refType.isNull()) {
+ // Definitely not taken.
+ replaceCurrent(builder.makeDrop(curr->ref));
+ worked = true;
+ return;
+ }
+ if (refType.isNonNullable()) {
+ // Definitely taken.
+ replaceCurrent(builder.makeBreak(
+ curr->name, maybeCast(curr->ref, curr->getSentType())));
+ worked = true;
+ return;
+ }
return;
}
- // Check if the type is the kind we are checking for.
+ // Improve the cast target type as much as possible given what we know
+ // about the input. Unlike in BrOn::finalize(), we consider type
+ // information from all the fallthrough values here. We can continue to
+ // further optimizations after this, and those optimizations might even
+ // benefit from this improvement.
+ auto glb = Type::getGreatestLowerBound(curr->castType, refType);
+ if (glb != Type::unreachable && glb != curr->castType) {
+ curr->castType = glb;
+ curr->finalize();
+ worked = true;
+ }
+
+ // Depending on what we know about the cast results, we may be able to
+ // optimize.
auto result = GCTypeUtils::evaluateCastCheck(refType, curr->castType);
if (curr->op == BrOnCastFail) {
result = GCTypeUtils::flipEvaluationResult(result);
}
- if (result == GCTypeUtils::Success) {
- // The cast succeeds, so we can switch from BrOn to a simple br that
- // is always taken.
- replaceCurrent(
- Builder(*getModule()).makeBreak(curr->name, curr->ref));
- worked = true;
- } else if (result == GCTypeUtils::Failure ||
- result == GCTypeUtils::Unreachable) {
- // The cast fails, so the branch is never taken, and the value just
- // flows through. Or, the cast cannot even be reached, so it does not
- // matter what we do, and we can handle it as a failure.
- replaceCurrent(curr->ref);
- worked = true;
+ switch (result) {
+ case GCTypeUtils::Unknown:
+ // Anything could happen, so we cannot optimize.
+ return;
+ case GCTypeUtils::Success: {
+ replaceCurrent(builder.makeBreak(
+ curr->name, maybeCast(curr->ref, curr->getSentType())));
+ worked = true;
+ return;
+ }
+ case GCTypeUtils::Failure: {
+ replaceCurrent(maybeCast(curr->ref, curr->type));
+ worked = true;
+ return;
+ }
+ case GCTypeUtils::SuccessOnlyIfNull: {
+ // TODO: optimize this case using the following replacement, which
+ // avoids using any scratch locals and only does a single null
+ // check, but does require generating a fresh label:
+ //
+ // (br_on_cast $l (ref null $X) (ref null $Y)
+ // (...)
+ // )
+ // =>
+ // (block $l' (result (ref $X))
+ // (br_on_non_null $l' ;; reuses `curr`
+ // (...)
+ // )
+ // (br $l
+ // (ref.null bot<X>)
+ // )
+ // )
+ return;
+ }
+ case GCTypeUtils::SuccessOnlyIfNonNull: {
+ // Perform this replacement:
+ //
+ // (br_on_cast $l (ref null $X') (ref $X))
+ // (...)
+ // )
+ // =>
+ // (block (result (ref bot<X>))
+ // (br_on_non_null $l ;; reuses `curr`
+ // (...)
+ // (ref.null bot<X>)
+ // )
+ curr->ref = maybeCast(
+ curr->ref, Type(curr->getSentType().getHeapType(), Nullable));
+ curr->op = BrOnNonNull;
+ curr->castType = Type::none;
+ curr->type = Type::none;
+
+ assert(curr->ref->type.isRef());
+ auto* refNull = builder.makeRefNull(curr->ref->type.getHeapType());
+ replaceCurrent(builder.makeBlock({curr, refNull}, refNull->type));
+ worked = true;
+ return;
+ }
+ case GCTypeUtils::Unreachable: {
+ // The cast is never executed, possibly because its input type is
+ // uninhabitable. Replace it with unreachable.
+ auto* drop = builder.makeDrop(curr->ref);
+ auto* unreachable = ExpressionManipulator::unreachable(curr);
+ replaceCurrent(builder.makeBlock({drop, unreachable}));
+ worked = true;
+ return;
+ }
}
- // TODO: Handle SuccessOnlyIfNull and SuccessOnlyIfNonNull.
}
- } optimizer;
+ } optimizer(getPassOptions());
optimizer.setModule(getModule());
optimizer.doWalkFunction(func);