diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/lubs.h | 99 | ||||
-rw-r--r-- | src/passes/DeadArgumentElimination.cpp | 47 | ||||
-rw-r--r-- | src/passes/LocalSubtyping.cpp | 6 | ||||
-rw-r--r-- | src/passes/TypeRefining.cpp | 65 |
4 files changed, 142 insertions, 75 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 diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index e997c591f..b7ce5ef5f 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -553,6 +553,7 @@ private: auto numParams = func->getNumParams(); std::vector<Type> newParamTypes; newParamTypes.reserve(numParams); + std::vector<LUBFinder> lubs(numParams); for (Index i = 0; i < numParams; i++) { auto originalType = func->getLocalType(i); // If the parameter type is not a reference, there is nothing to refine. @@ -565,10 +566,11 @@ private: newParamTypes.push_back(originalType); continue; } - LUBFinder lub; + auto& lub = lubs[i]; for (auto* call : calls) { auto* operand = call->operands[i]; - if (lub.note(operand) == originalType) { + lub.noteUpdatableExpression(operand); + if (lub.getBestPossible() == originalType) { // We failed to refine this parameter to anything more specific. break; } @@ -578,7 +580,7 @@ private: if (!lub.noted()) { return; } - newParamTypes.push_back(lub.get()); + newParamTypes.push_back(lub.getBestPossible()); } // Check if we are able to optimize here before we do the work to scan the @@ -591,6 +593,11 @@ private: // We can do this! TypeUpdating::updateParamTypes(func, newParamTypes, *module); + // Update anything the lubs need to update. + for (auto& lub : lubs) { + lub.updateNulls(); + } + // Also update the function's type. func->setParams(newParams); } @@ -633,21 +640,28 @@ private: ReFinalize().walkFunctionInModule(func, module); LUBFinder lub; - if (lub.note(func->body) == originalType) { + lub.noteUpdatableExpression(func->body); + if (lub.getBestPossible() == originalType) { return false; } - // Scan the body and look at the returns. - auto processReturnType = [&](Type type) { - // Return whether we still look ok to do the optimization. If this is - // false then we can stop here. - return lub.note(type) != originalType; - }; + // Scan the body and look at the returns. First, return expressions. for (auto* ret : FindAll<Return>(func->body).list) { - if (!processReturnType(ret->value->type)) { + lub.noteUpdatableExpression(ret->value); + if (lub.getBestPossible() == originalType) { return false; } } + + // Process return_calls and call_refs. Unlike return expressions which we + // just handled, these only get a type to update, not a value. + auto processReturnType = [&](Type type) { + // Return whether we still look ok to do the optimization. If this is + // false then we can stop here. + lub.note(type); + return lub.getBestPossible() != originalType; + }; + for (auto* call : FindAll<Call>(func->body).list) { if (call->isReturn && !processReturnType(module->getFunction(call->target)->getResults())) { @@ -671,7 +685,6 @@ private: } } } - assert(lub.get() != originalType); // If the refined type is unreachable then nothing actually returns from // this function. @@ -680,13 +693,19 @@ private: return false; } + auto newType = lub.getBestPossible(); + if (newType == originalType) { + return false; + } + // Success. Update the type, and the calls. - func->setResults(lub.get()); + func->setResults(newType); for (auto* call : calls) { if (call->type != Type::unreachable) { - call->type = lub.get(); + call->type = newType; } } + lub.updateNulls(); return true; } }; diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index 57119a253..a8174aa2d 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -124,7 +124,8 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { // Find all the types assigned to the var, and compute the optimal LUB. LUBFinder lub; for (auto* set : setsForLocal[i]) { - if (lub.note(set->value) == oldType) { + lub.noteUpdatableExpression(set->value); + if (lub.getBestPossible() == oldType) { break; } } @@ -133,7 +134,7 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { continue; } - auto newType = lub.get(); + auto newType = lub.getBestPossible(); assert(newType != Type::none); // in valid wasm there must be a LUB // Remove non-nullability if we disallow that in locals. @@ -157,6 +158,7 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { func->vars[i - varBase] = newType; more = true; optimized = true; + lub.updateNulls(); // Update gets and tees. for (auto* get : getsForLocal[i]) { diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 33e382bc5..9480322bb 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -36,38 +36,7 @@ namespace { // possible LUB so far. The only extra functionality we need here is whether // there is a default null value (which would force us to keep the type // nullable). -struct FieldInfo : public LUBFinder { - bool nullDefault = false; - - void noteNullDefault() { nullDefault = true; } - - bool hasNullDefault() { return nullDefault; } - - bool noted() { return LUBFinder::noted() || nullDefault; } - - Type get() { - auto ret = LUBFinder::get(); - if (nullDefault && !ret.isNullable()) { - ret = Type(ret.getHeapType(), Nullable); - } - return ret; - } - - bool combine(const FieldInfo& other) { - auto old = nullDefault; - if (other.nullDefault) { - noteNullDefault(); - } - return LUBFinder::combine(other) || nullDefault != old; - } - - // Force the lub to a particular type. - void set(Type type) { lub = type; } - - void dump(std::ostream& o) { - std::cout << "FieldInfo(" << lub << ", " << nullDefault << ")"; - } -}; +using FieldInfo = LUBFinder; struct FieldInfoScanner : public StructUtils::StructScanner<FieldInfo, FieldInfoScanner> { @@ -85,7 +54,7 @@ struct FieldInfoScanner HeapType type, Index index, FieldInfo& info) { - info.note(expr); + info.noteUpdatableExpression(expr); } void @@ -171,9 +140,8 @@ struct TypeRefining : public Pass { for (Index i = 0; i < fields.size(); i++) { auto oldType = fields[i].type; auto& info = finalInfos[type][i]; - auto newType = info.get(); - if (newType == Type::unreachable) { - info.set(oldType); + if (!info.noted()) { + info = LUBFinder(oldType); } } @@ -181,9 +149,9 @@ struct TypeRefining : public Pass { if (auto super = type.getSuperType()) { auto& superFields = super->getStruct().fields; for (Index i = 0; i < superFields.size(); i++) { - auto newSuperType = finalInfos[*super][i].get(); + auto newSuperType = finalInfos[*super][i].getBestPossible(); auto& info = finalInfos[type][i]; - auto newType = info.get(); + auto newType = info.getBestPossible(); if (!Type::isSubType(newType, newSuperType)) { // To ensure we are a subtype of the super's field, simply copy that // value, which is more specific than us. @@ -201,7 +169,7 @@ struct TypeRefining : public Pass { // to something more specific than $C's old type, we end up with the // problem that this code path fixes: we just need to get $C's type // to be identical to its super so that validation works. - info.set(newSuperType); + info = LUBFinder(newSuperType); } else if (fields[i].mutable_ == Mutable) { // Mutable fields must have identical types, so we cannot // specialize. @@ -209,20 +177,19 @@ struct TypeRefining : public Pass { // here? This entire analysis might be done on fields, and not // types, which would also handle more things added to fields // in the future. - info.set(newSuperType); + info = LUBFinder(newSuperType); } } } // After all those decisions, see if we found anything to optimize. - if (!canOptimize) { - for (Index i = 0; i < fields.size(); i++) { - auto oldType = fields[i].type; - auto newType = finalInfos[type][i].get(); - if (newType != oldType) { - canOptimize = true; - break; - } + for (Index i = 0; i < fields.size(); i++) { + auto oldType = fields[i].type; + auto& lub = finalInfos[type][i]; + auto newType = lub.getBestPossible(); + if (newType != oldType) { + canOptimize = true; + lub.updateNulls(); } } @@ -253,7 +220,7 @@ struct TypeRefining : public Pass { if (!oldType.isRef()) { continue; } - auto newType = parent.finalInfos[oldStructType][i].get(); + auto newType = parent.finalInfos[oldStructType][i].getBestPossible(); newFields[i].type = getTempType(newType); } } |