summaryrefslogtreecommitdiff
path: root/src/ir/lubs.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/ir/lubs.h')
-rw-r--r--src/ir/lubs.h99
1 files changed, 89 insertions, 10 deletions
diff --git a/src/ir/lubs.h b/src/ir/lubs.h
index a45b62c52..e03e693e0 100644
--- a/src/ir/lubs.h
+++ b/src/ir/lubs.h
@@ -22,31 +22,110 @@
namespace wasm {
+//
// Helper to find a LUB of a series of expressions. This works incrementally so
// that if we see we are not improving on an existing type then we can stop
-// early.
+// early. It also notes null expressions that can be updated later, and if
+// updating them would allow a better LUB it can do so. That is, given this:
+//
+// (ref.null any) ;; an expression that we can update
+// (.. something of type data ..)
+//
+// We can update that null to type (ref null data) which would allow setting
+// that as the LUB. This is important in cases where there is a null initial
+// value in a field, for example: we should not let the type there prevent us
+// from optimizing - after all, all nulls compare equally anyhow.
+//
struct LUBFinder {
- // The least upper bound we found so far.
- Type lub = Type::unreachable;
+ LUBFinder() {}
+
+ LUBFinder(Type initialType) { note(initialType); }
- // Note another type to take into account in the lub. Returns the new lub.
- Type note(Type type) { return lub = Type::getLeastUpperBound(lub, type); }
+ // Note another type to take into account in the lub.
+ void note(Type type) { lub = Type::getLeastUpperBound(lub, type); }
- Type note(Expression* curr) { return note(curr->type); }
+ // Note an expression that can be updated, that is, that we can modify in
+ // safe ways if doing so would allow us to get a better lub. The specific
+ // optimization possible here involves nulls, see the top comment.
+ void noteUpdatableExpression(Expression* curr) {
+ if (auto* null = curr->dynCast<RefNull>()) {
+ nulls.insert(null);
+ } else {
+ note(curr->type);
+ }
+ }
- // Returns whether we noted any (reachable) value.
+ void noteNullDefault() {
+ // A default value is indicated by a null pointer.
+ nulls.insert(nullptr);
+ }
+
+ // Returns whether we noted any (reachable) value. This ignores nulls, as they
+ // do not contribute type information - we do not try to find a lub based on
+ // them (rather we update them to the LUB).
bool noted() { return lub != Type::unreachable; }
- // Returns the lub that we found.
- Type get() { return lub; }
+ // Returns the best possible lub. This ignores updatable nulls for the reasons
+ // mentioned above, since they will not limit us, aside from making the type
+ // nullable if nulls exist. This does not update the nulls.
+ Type getBestPossible() {
+ if (lub == Type::unreachable) {
+ // Perhaps if we have seen nulls we could compute a lub on them, but it's
+ // not clear that is helpful.
+ return lub;
+ }
+
+ // We have a lub. Make it nullable if we need to.
+ if (!lub.isNullable() && !nulls.empty()) {
+ return Type(lub.getHeapType(), Nullable);
+ } else {
+ return lub;
+ }
+ }
+
+ // Update the nulls for the best possible LUB, if we found one.
+ void updateNulls() {
+ auto newType = getBestPossible();
+ if (newType != Type::unreachable) {
+ for (auto* null : nulls) {
+ // Default null values (represented as nullptr here) do not need to be
+ // updated. Aside from that, if this null is already of a more specific
+ // type, also do not update it - it's never worth making a type less
+ // specific. What we care about here is making sure the nulls are all
+ // specific enough given the LUB that is being applied.
+ if (null && !Type::isSubType(null->type, newType)) {
+ null->finalize(newType);
+ }
+ }
+ }
+ }
// Combines the information in another LUBFinder into this one, and returns
// whether we changed anything.
bool combine(const LUBFinder& other) {
+ // Check if the lub was changed.
auto old = lub;
note(other.lub);
- return lub != old;
+ bool changed = old != lub;
+
+ // Check if we added new updatable nulls.
+ for (auto* null : other.nulls) {
+ if (nulls.insert(null).second) {
+ changed = true;
+ }
+ }
+
+ return changed;
}
+
+private:
+ // The least upper bound. As we go this always contains the latest value based
+ // on everything we've seen so far, except for nulls.
+ Type lub = Type::unreachable;
+
+ // Nulls that we can update. A nullptr here indicates an "implicit" null, that
+ // is, a null default value.
+ std::unordered_set<RefNull*> nulls;
};
} // namespace wasm