diff options
Diffstat (limited to 'src/ir/possible-contents.cpp')
-rw-r--r-- | src/ir/possible-contents.cpp | 149 |
1 files changed, 132 insertions, 17 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 2efd61391..209028f25 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1359,8 +1359,13 @@ private: // exists it is not added. void connectDuringFlow(Location from, Location to); - // Contents sent to a global location can be filtered in a special way during - // the flow, which is handled in this helper. + // Contents sent to certain locations can be filtered in a special way during + // the flow, which is handled in these helpers. These may update + // |worthSendingMore| which is whether it is worth sending any more content to + // this location in the future. + void filterExpressionContents(PossibleContents& contents, + const ExpressionLocation& exprLoc, + bool& worthSendingMore); void filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc); @@ -1384,6 +1389,30 @@ private: // We will need subtypes during the flow, so compute them once ahead of time. std::unique_ptr<SubTypes> subTypes; + // The depth of children for each type. This is 0 if the type has no + // subtypes, 1 if it has subtypes but none of those have subtypes themselves, + // and so forth. + std::unordered_map<HeapType, Index> maxDepths; + + // Given a ConeType, return the normalized depth, that is, the canonical depth + // given the actual children it has. If this is a full cone, then we can + // always pick the actual maximal depth and use that instead of FullDepth==-1. + // For a non-full cone, we also reduce the depth as much as possible, so it is + // equal to the maximum depth of an existing subtype. + Index getNormalizedConeDepth(Type type, Index depth) { + return std::min(depth, maxDepths[type.getHeapType()]); + } + + void normalizeConeType(PossibleContents& cone) { + assert(cone.isConeType()); + auto type = cone.getType(); + auto before = cone.getCone().depth; + auto normalized = getNormalizedConeDepth(type, before); + if (normalized != before) { + cone = PossibleContents::coneType(type, normalized); + } + } + #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 // Dump out a location for debug purposes. void dump(Location location); @@ -1530,6 +1559,7 @@ Flower::Flower(Module& wasm) : wasm(wasm) { if (getTypeSystem() == TypeSystem::Nominal || getTypeSystem() == TypeSystem::Isorecursive) { subTypes = std::make_unique<SubTypes>(wasm); + maxDepths = subTypes->getMaxDepths(); } #ifdef POSSIBLE_CONTENTS_DEBUG @@ -1616,14 +1646,23 @@ bool Flower::updateContents(LocationIndex locationIndex, // It is not worth sending any more to this location if we are now in the // worst possible case, as no future value could cause any change. - // - // Many is always the worst possible case. A cone type of a non-reference is - // also the worst case, since subtyping is not relevant there, and so if we - // only know something about the type then we already know nothing beyond what - // the type in the wasm tells us (and from there we can only go to Many). - bool worthSendingMore = !contents.isMany(); - if (!contents.getType().isRef() && contents.isConeType()) { - worthSendingMore = false; + bool worthSendingMore = true; + if (contents.isConeType()) { + if (!contents.getType().isRef()) { + // A cone type of a non-reference is the worst case, since subtyping is + // not relevant there, and so if we only know something about the type + // then we already know nothing beyond what the type in the wasm tells us + // (and from there we can only go to Many). + worthSendingMore = false; + } else { + // Normalize all reference cones. There is never a point to flow around + // anything non-normalized, which might lead to extra work. For example, + // if A has no subtypes, then a full cone for A is really the same as one + // with depth 0 (an exact type). And we don't want to see the full cone + // arrive and think it was an improvement over the one with depth 0 and do + // more flowing based on that. + normalizeConeType(contents); + } } // Check if anything changed. Note that we check not just the content but @@ -1639,16 +1678,30 @@ bool Flower::updateContents(LocationIndex locationIndex, // Handle special cases: Some locations can only contain certain contents, so // filter accordingly. - if (auto* globalLoc = - std::get_if<GlobalLocation>(&getLocation(locationIndex))) { + auto location = getLocation(locationIndex); + bool filtered = false; + if (auto* exprLoc = std::get_if<ExpressionLocation>(&location)) { + // TODO: Replace this with specific filterFoo or flowBar methods like we + // have for flowRefCast and filterGlobalContents. That could save a + // little wasted work here. Might be best to do that after the spec is + // fully stable. + filterExpressionContents(contents, *exprLoc, worthSendingMore); + filtered = true; + } else if (auto* globalLoc = std::get_if<GlobalLocation>(&location)) { filterGlobalContents(contents, *globalLoc); - if (contents == oldContents && - contents.getType() == oldContents.getType()) { - // Nothing actually changed after filtering, so just return. - return worthSendingMore; - } + filtered = true; } + if (filtered && contents == oldContents && + contents.getType() == oldContents.getType()) { + // Nothing actually changed after filtering, so just return. + return worthSendingMore; + } + + // After filtering we should always have more precise information than "many" + // - in the worst case, we can have the type declared in the wasm. + assert(!contents.isMany()); + #if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 std::cout << " updateContents has something new\n"; contents.dump(std::cout, &wasm); @@ -1776,6 +1829,68 @@ void Flower::connectDuringFlow(Location from, Location to) { } } +void Flower::filterExpressionContents(PossibleContents& contents, + const ExpressionLocation& exprLoc, + bool& worthSendingMore) { + auto type = exprLoc.expr->type; + if (!type.isRef()) { + return; + } + + // The caller cannot know of a situation where it might not be worth sending + // more to a reference - all that logic is in here. That is, the rest of this + // function is the only place we can mark |worthSendingMore| as false for a + // reference. + assert(worthSendingMore); + + // The maximal contents here are the declared type and all subtypes. Nothing + // else can pass through, so filter such things out. + auto maximalContents = PossibleContents::fullConeType(type); + contents.intersectWithFullCone(maximalContents); + if (contents.isNone()) { + // Nothing was left here at all. + return; + } + + // Normalize the intersection. We want to check later if any more content can + // arrive here, and also we want to avoid flowing around anything non- + // normalized, as explained earlier. + // + // Note that this normalization is necessary even though |contents| was + // normalized before the intersection, e.g.: + /* + // A + // / \ + // B C + // | + // D + */ + // Consider the case where |maximalContents| is Cone(B, Infinity) and the + // original |contents| was Cone(A, 2) (which is normalized). The naive + // intersection is Cone(B, 1), since the core intersection logic makes no + // assumptions about the rest of the types. That is then normalized to + // Cone(B, 0) since there happens to be no subtypes for B. + // + // Note that the intersection may also not be a cone type, if it is a global + // or literal. In that case we don't have anything more to do here. + if (!contents.isConeType()) { + return; + } + + normalizeConeType(contents); + + // There is a chance that the intersection is equal to the maximal contents, + // which would mean nothing more can arrive here. (Note that we can't + // normalize |maximalContents| before the intersection as + // intersectWithFullCone assumes a full/infinite cone.) + normalizeConeType(maximalContents); + + if (contents == maximalContents) { + // We already contain everything possible, so this is the worst case. + worthSendingMore = false; + } +} + void Flower::filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc) { auto* global = wasm.getGlobal(globalLoc.name); |