diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/lubs.cpp | 10 | ||||
-rw-r--r-- | src/ir/lubs.h | 82 | ||||
-rw-r--r-- | src/passes/DeadArgumentElimination.cpp | 16 | ||||
-rw-r--r-- | src/passes/GlobalRefining.cpp | 7 | ||||
-rw-r--r-- | src/passes/LocalSubtyping.cpp | 7 | ||||
-rw-r--r-- | src/passes/SignatureRefining.cpp | 13 | ||||
-rw-r--r-- | src/passes/TypeRefining.cpp | 16 |
7 files changed, 31 insertions, 120 deletions
diff --git a/src/ir/lubs.cpp b/src/ir/lubs.cpp index 852c27677..ead1299b5 100644 --- a/src/ir/lubs.cpp +++ b/src/ir/lubs.cpp @@ -45,15 +45,15 @@ LUBFinder getResultsLUB(Function* func, Module& wasm) { // ) ReFinalize().walkFunctionInModule(func, &wasm); - lub.noteUpdatableExpression(func->body); - if (lub.getBestPossible() == originalType) { + lub.note(func->body->type); + if (lub.getLUB() == originalType) { return lub; } // Scan the body and look at the returns. First, return expressions. for (auto* ret : FindAll<Return>(func->body).list) { - lub.noteUpdatableExpression(ret->value); - if (lub.getBestPossible() == originalType) { + lub.note(ret->value->type); + if (lub.getLUB() == originalType) { return lub; } } @@ -64,7 +64,7 @@ LUBFinder getResultsLUB(Function* func, Module& wasm) { // 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; + return lub.getLUB() != originalType; }; for (auto* call : FindAll<Call>(func->body).list) { diff --git a/src/ir/lubs.h b/src/ir/lubs.h index 05c6b07fd..afc5c0676 100644 --- a/src/ir/lubs.h +++ b/src/ir/lubs.h @@ -25,16 +25,7 @@ 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. +// early. // struct LUBFinder { LUBFinder() {} @@ -44,61 +35,11 @@ struct LUBFinder { // 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). + // Returns whether we noted any (reachable) value. 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); - } - } - } - } + // Returns the lub. + Type getLUB() { return lub; } // Combines the information in another LUBFinder into this one, and returns // whether we changed anything. @@ -106,26 +47,13 @@ struct LUBFinder { // 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; + return old != lub; } 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 { diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index d319340b8..f739e9263 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -381,8 +381,8 @@ private: auto& lub = lubs[i]; for (auto* call : calls) { auto* operand = call->operands[i]; - lub.noteUpdatableExpression(operand); - if (lub.getBestPossible() == originalType) { + lub.note(operand->type); + if (lub.getLUB() == originalType) { // We failed to refine this parameter to anything more specific. break; } @@ -392,7 +392,7 @@ private: if (!lub.noted()) { return; } - newParamTypes.push_back(lub.getBestPossible()); + newParamTypes.push_back(lub.getLUB()); } // Check if we are able to optimize here before we do the work to scan the @@ -405,12 +405,7 @@ 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. + // Update the function's type. func->setParams(newParams); } @@ -436,9 +431,8 @@ private: if (!lub.noted()) { return false; } - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != func->getResults()) { - lub.updateNulls(); func->setResults(newType); for (auto* call : calls) { if (call->type != Type::unreachable) { diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp index 0576e3285..67a186f92 100644 --- a/src/passes/GlobalRefining.cpp +++ b/src/passes/GlobalRefining.cpp @@ -59,7 +59,7 @@ struct GlobalRefining : public Pass { // Combine all the information we gathered and compute lubs. for (auto& [func, info] : analysis.map) { for (auto* set : info.sets) { - lubs[set->name].noteUpdatableExpression(set->value); + lubs[set->name].note(set->value->type); } } @@ -73,7 +73,7 @@ struct GlobalRefining : public Pass { auto& lub = lubs[global->name]; // Note the initial value. - lub.noteUpdatableExpression(global->init); + lub.note(global->init->type); // The initial value cannot be unreachable, but it might be null, and all // other values might be too. In that case, we've noted nothing useful @@ -83,12 +83,11 @@ struct GlobalRefining : public Pass { } auto oldType = global->type; - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != oldType) { // We found an improvement! assert(Type::isSubType(newType, oldType)); global->type = newType; - lub.updateNulls(); optimized = true; } } diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index cf8570986..0d41434fc 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -134,8 +134,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]) { - lub.noteUpdatableExpression(set->value); - if (lub.getBestPossible() == oldType) { + lub.note(set->value->type); + if (lub.getLUB() == oldType) { break; } } @@ -144,7 +144,7 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { continue; } - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); assert(newType != Type::none); // in valid wasm there must be a LUB // Remove non-nullability if we disallow that in locals. @@ -165,7 +165,6 @@ 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/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index 132079b76..15b89b2a7 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -161,7 +161,7 @@ struct SignatureRefining : public Pass { auto updateLUBs = [&](const ExpressionList& operands) { for (Index i = 0; i < numParams; i++) { - paramLUBs[i].noteUpdatableExpression(operands[i]); + paramLUBs[i].note(operands[i]->type); } }; @@ -178,7 +178,7 @@ struct SignatureRefining : public Pass { if (!lub.noted()) { break; } - newParamsTypes.push_back(lub.getBestPossible()); + newParamsTypes.push_back(lub.getLUB()); } Type newParams; if (newParamsTypes.size() < numParams) { @@ -197,7 +197,7 @@ struct SignatureRefining : public Pass { // value, or it can return a value but traps instead etc.). newResults = func->getResults(); } else { - newResults = resultsLUB.getBestPossible(); + newResults = resultsLUB.getLUB(); } if (newParams == func->getParams() && newResults == func->getResults()) { @@ -207,14 +207,7 @@ struct SignatureRefining : public Pass { // We found an improvement! newSignatures[type] = Signature(newParams, newResults); - // Update nulls as necessary, now that we are changing things. - if (newParams != func->getParams()) { - for (auto& lub : paramLUBs) { - lub.updateNulls(); - } - } if (newResults != func->getResults()) { - resultsLUB.updateNulls(); refinedResults = true; // Update the types of calls using the signature. diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index e9aa07ca6..6e2c96fce 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -53,7 +53,7 @@ struct FieldInfoScanner HeapType type, Index index, FieldInfo& info) { - info.noteUpdatableExpression(expr); + info.note(expr->type); } void @@ -61,7 +61,7 @@ struct FieldInfoScanner // Default values do not affect what the heap type of a field can be turned // into. Note them, however, as they force us to keep the type nullable. if (fieldType.isRef()) { - info.noteNullDefault(); + info.note(Type(fieldType.getHeapType().getBottom(), Nullable)); } } @@ -173,9 +173,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].getBestPossible(); + auto newSuperType = finalInfos[*super][i].getLUB(); auto& info = finalInfos[type][i]; - auto newType = info.getBestPossible(); + auto newType = info.getLUB(); 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. @@ -210,10 +210,9 @@ struct TypeRefining : public Pass { for (Index i = 0; i < fields.size(); i++) { auto oldType = fields[i].type; auto& lub = finalInfos[type][i]; - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != oldType) { canOptimize = true; - lub.updateNulls(); } } @@ -256,8 +255,7 @@ struct TypeRefining : public Pass { } auto oldType = curr->ref->type.getHeapType(); - auto newFieldType = - parent.finalInfos[oldType][curr->index].getBestPossible(); + auto newFieldType = parent.finalInfos[oldType][curr->index].getLUB(); if (!Type::isSubType(newFieldType, curr->type)) { // This instruction is invalid, so it must be the result of the // situation described above: we ignored the read during our @@ -298,7 +296,7 @@ struct TypeRefining : public Pass { if (!oldType.isRef()) { continue; } - auto newType = parent.finalInfos[oldStructType][i].getBestPossible(); + auto newType = parent.finalInfos[oldStructType][i].getLUB(); newFields[i].type = getTempType(newType); } } |