diff options
Diffstat (limited to 'src/ir/lubs.h')
-rw-r--r-- | src/ir/lubs.h | 99 |
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 |