| /* |
| * Copyright 2021 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #ifndef wasm_ir_lubs_h |
| #define wasm_ir_lubs_h |
| |
| #include "ir/module-utils.h" |
| #include "wasm.h" |
| |
| 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. 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 { |
| LUBFinder() {} |
| |
| LUBFinder(Type initialType) { note(initialType); } |
| |
| // Note another type to take into account in the lub. |
| void note(Type type) { lub = Type::getLeastUpperBound(lub, 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); |
| } |
| } |
| |
| 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 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); |
| 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 LUB { |
| |
| // Given a function, computes a LUB for its results. The caller can then decide |
| // to apply a refined type if we found one. |
| // |
| // This modifies the called function even if it fails to find a refined type as |
| // it does a refinalize in order to be able to compute the new types. We could |
| // roll back that change, but it's not harmful and can help, so we keep it |
| // regardless. |
| LUBFinder getResultsLUB(Function* func, Module& wasm); |
| |
| } // namespace LUB |
| |
| } // namespace wasm |
| |
| #endif // wasm_ir_lubs_h |