summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ir/gc-type-utils.h38
-rw-r--r--src/passes/OptimizeInstructions.cpp93
2 files changed, 130 insertions, 1 deletions
diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h
index 168c9529e..d59193193 100644
--- a/src/ir/gc-type-utils.h
+++ b/src/ir/gc-type-utils.h
@@ -42,7 +42,7 @@ enum EvaluationResult {
// (like br_on_func checks if it is a function), see if type info lets us
// determine that at compile time.
// This ignores nullability - it just checks the kind.
-EvaluationResult evaluateKindCheck(Expression* curr) {
+inline EvaluationResult evaluateKindCheck(Expression* curr) {
Kind expected;
Expression* child;
@@ -66,6 +66,42 @@ EvaluationResult evaluateKindCheck(Expression* curr) {
WASM_UNREACHABLE("unhandled BrOn");
}
child = br->ref;
+ } else if (auto* is = curr->dynCast<RefIs>()) {
+ switch (is->op) {
+ // We don't check nullability here.
+ case RefIsNull:
+ return Unknown;
+ case RefIsFunc:
+ expected = Func;
+ break;
+ case RefIsData:
+ expected = Data;
+ break;
+ case RefIsI31:
+ expected = I31;
+ break;
+ default:
+ WASM_UNREACHABLE("unhandled BrOn");
+ }
+ child = is->value;
+ } else if (auto* as = curr->dynCast<RefAs>()) {
+ switch (as->op) {
+ // We don't check nullability here.
+ case RefAsNonNull:
+ return Unknown;
+ case RefAsFunc:
+ expected = Func;
+ break;
+ case RefAsData:
+ expected = Data;
+ break;
+ case RefAsI31:
+ expected = I31;
+ break;
+ default:
+ WASM_UNREACHABLE("unhandled BrOn");
+ }
+ child = as->value;
} else {
WASM_UNREACHABLE("invalid input to evaluateKindCheck");
}
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index be89257a1..972ffd794 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -26,6 +26,7 @@
#include <ir/bits.h>
#include <ir/cost.h>
#include <ir/effects.h>
+#include <ir/gc-type-utils.h>
#include <ir/literal-utils.h>
#include <ir/load-utils.h>
#include <ir/manipulation.h>
@@ -1005,6 +1006,98 @@ struct OptimizeInstructions
}
}
+ void visitRefIs(RefIs* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+
+ // Optimizating RefIs is not that obvious, since even if we know the result
+ // evaluates to 0 or 1 then the replacement may not actually save code size,
+ // since RefIsNull is a single byte (the others are 2), while adding a Const
+ // of 0 would be two bytes. Other factors are that we can remove the input
+ // and the added drop on it if it has no side effects, and that replacing
+ // with a constant may allow further optimizations later. For now, replace
+ // with a constant, but this warrants more investigation. TODO
+
+ Builder builder(*getModule());
+
+ auto nonNull = !curr->value->type.isNullable();
+
+ if (curr->op == RefIsNull) {
+ if (nonNull) {
+ replaceCurrent(builder.makeSequence(
+ builder.makeDrop(curr->value),
+ builder.makeConst(Literal::makeZero(Type::i32))));
+ }
+ return;
+ }
+
+ // Check if the type is the kind we are checking for.
+ auto result = GCTypeUtils::evaluateKindCheck(curr);
+
+ if (result != GCTypeUtils::Unknown) {
+ // We know the kind. Now we must also take into account nullability.
+ if (nonNull) {
+ // We know the entire result.
+ replaceCurrent(
+ builder.makeSequence(builder.makeDrop(curr->value),
+ builder.makeConst(Literal::makeFromInt32(
+ result == GCTypeUtils::Success, Type::i32))));
+ } else {
+ // The value may be null. Leave only a check for that.
+ curr->op = RefIsNull;
+ if (result == GCTypeUtils::Success) {
+ // The input is of the right kind. If it is not null then the result
+ // is 1, and otherwise it is 0, so we need to flip the result of
+ // RefIsNull.
+ // Note that even after adding an eqz here we do not regress code size
+ // as RefIsNull is a single byte while the others are two. So we keep
+ // code size identical. However, in theory this may be more work, if
+ // a VM considers ref.is_X to be as fast as ref.is_null, and if eqz is
+ // not free, so this is worth more investigation. TODO
+ replaceCurrent(builder.makeUnary(EqZInt32, curr));
+ } else {
+ // The input is of the wrong kind. In this case if it is null we
+ // return zero because of that, and if it is not then we return zero
+ // because of the kind, so the result is always the same.
+ assert(result == GCTypeUtils::Failure);
+ replaceCurrent(builder.makeSequence(
+ builder.makeDrop(curr->value),
+ builder.makeConst(Literal::makeZero(Type::i32))));
+ }
+ }
+ }
+ }
+
+ void visitRefAs(RefAs* curr) {
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+
+ // Check if the type is the kind we are checking for.
+ auto result = GCTypeUtils::evaluateKindCheck(curr);
+
+ if (result == GCTypeUtils::Success) {
+ // We know the kind is correct, so all that is left is a check for
+ // non-nullability, which we do lower down.
+ curr->op = RefAsNonNull;
+ } else if (result == GCTypeUtils::Failure) {
+ // This is the wrong kind, so it will trap. The binaryen optimizer does
+ // not differentiate traps, so we can perform a replacement here. We
+ // replace 2 bytes of ref.as_* with one byte of unreachable and one of a
+ // drop, which is no worse, and the value and the drop can be optimized
+ // out later if the value has no side effects.
+ Builder builder(*getModule());
+ replaceCurrent(builder.makeSequence(builder.makeDrop(curr->value),
+ builder.makeUnreachable()));
+ return;
+ }
+
+ if (curr->op == RefAsNonNull && !curr->value->type.isNullable()) {
+ replaceCurrent(curr->value);
+ }
+ }
+
Index getMaxBitsForLocal(LocalGet* get) {
// check what we know about the local
return localInfo[get->index].maxBits;