diff options
author | Alon Zakai <azakai@google.com> | 2022-10-19 11:02:33 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-19 11:02:33 -0700 |
commit | 18273489e9433e399cc4f088e9352c08aec13369 (patch) | |
tree | a3f8274aee790918dfb26326dcc5ec2e33c1d5ec /src | |
parent | 3b1df7e5c329d9600cfadd513b557af618c007aa (diff) | |
download | binaryen-18273489e9433e399cc4f088e9352c08aec13369.tar.gz binaryen-18273489e9433e399cc4f088e9352c08aec13369.tar.bz2 binaryen-18273489e9433e399cc4f088e9352c08aec13369.zip |
[Wasm GC] Use Cones in GUFA data reads and writes (#5157)
When we read from a struct/array using a cone type, read from the types in the cone
and nothing else. Previously we used the declared type in the wasm, which might be
larger (both in the base type and the depth). Likewise, in a write.
To do this, this extends ConeReadLocation with a depth (previously the depth there
was assumed to be infinite, and now it is to a potentially limited depth).
After this we are fully utilizing cone types in GUFA, as the test changes show (or at
least I can't think of any other uses of cones).
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/possible-contents.cpp | 149 | ||||
-rw-r--r-- | src/ir/possible-contents.h | 17 |
2 files changed, 88 insertions, 78 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 5a2482413..3b48a5122 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -1374,7 +1374,7 @@ private: // in the reference the read receives, and the read instruction itself. We // compute where we need to read from based on the type and the ref contents // and get that data, adding new links in the graph as needed. - void readFromData(HeapType declaredHeapType, + void readFromData(Type declaredType, Index fieldIndex, const PossibleContents& refContents, Expression* read); @@ -1753,14 +1753,14 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) { if (auto* get = parent->dynCast<StructGet>()) { // |child| is the reference child of a struct.get. assert(get->ref == child); - readFromData(get->ref->type.getHeapType(), get->index, contents, get); + readFromData(get->ref->type, get->index, contents, get); } else if (auto* set = parent->dynCast<StructSet>()) { // |child| is either the reference or the value child of a struct.set. assert(set->ref == child || set->value == child); writeToData(set->ref, set->value, set->index); } else if (auto* get = parent->dynCast<ArrayGet>()) { assert(get->ref == child); - readFromData(get->ref->type.getHeapType(), 0, contents, get); + readFromData(get->ref->type, 0, contents, get); } else if (auto* set = parent->dynCast<ArraySet>()) { assert(set->ref == child || set->value == child); writeToData(set->ref, set->value, 0); @@ -1913,10 +1913,17 @@ void Flower::filterGlobalContents(PossibleContents& contents, } } -void Flower::readFromData(HeapType declaredHeapType, +void Flower::readFromData(Type declaredType, Index fieldIndex, const PossibleContents& refContents, Expression* read) { +#ifndef NDEBUG + // We must not have anything in the reference that is invalid for the wasm + // type there. + auto maximalContents = PossibleContents::fullConeType(declaredType); + assert(PossibleContents::isSubContents(refContents, maximalContents)); +#endif + // The data that a struct.get reads depends on two things: the reference that // we read from, and the relevant DataLocations. The reference determines // which DataLocations are relevant: if it is an exact type then we have a @@ -1944,16 +1951,10 @@ void Flower::readFromData(HeapType declaredHeapType, // future. if (refContents.isNull() || refContents.isNone()) { - // Nothing is read here. - return; - } - - if (refContents.isLiteral()) { - // The only reference literals we have are nulls (handled above) and - // ref.func. ref.func will trap in struct|array.get, so nothing will be read - // here (when we finish optimizing all instructions like BrOn then - // ref.funcs should get filtered out before arriving here TODO). - assert(refContents.getType().isFunction()); + // Nothing is read here as this is either a null or unreachable code. (Note + // that the contents must be a subtype of the wasm type, which rules out + // other possibilities like a non-null literal such as an integer or a + // function reference.) return; } @@ -1961,50 +1962,47 @@ void Flower::readFromData(HeapType declaredHeapType, std::cout << " add special reads\n"; #endif - if (refContents.hasExactType()) { - // Add a single link to the exact location the reference points to. - connectDuringFlow( - DataLocation{refContents.getType().getHeapType(), fieldIndex}, - ExpressionLocation{read, 0}); - } else { - // Otherwise, this is a true cone (i.e., it has a depth > 0): the declared - // type of the reference or some of its subtypes. - // TODO: The Global case may have a different cone type than the heapType, - // which we could use here. - // TODO: A Global may refer to an immutable global, which we can read the - // field from potentially (reading it from the struct.new/array.new - // in the definition of it, if it is not imported; or, we could track - // the contents of immutable fields of allocated objects, and not just - // represent them as an exact type). - // See the test TODO with text "We optimize some of this, but stop at - // reading from the immutable global" - assert(refContents.isMany() || refContents.isGlobal() || - refContents.isConeType()); - - // TODO: Use the cone depth here for ConeType. Right now we do the - // pessimistic thing and assume a full cone of all subtypes. - - // We create a ConeReadLocation for the canonical cone of this type, to - // avoid bloating the graph, see comment on ConeReadLocation(). - // TODO: A cone with no subtypes needs no canonical location, just - // add one direct link here. - auto coneReadLocation = ConeReadLocation{declaredHeapType, fieldIndex}; - if (!hasIndex(coneReadLocation)) { - // This is the first time we use this location, so create the links for it - // in the graph. - for (auto type : subTypes->getAllSubTypes(declaredHeapType)) { + // The only possibilities left are a cone type (the worst case is where the + // cone matches the wasm type), or a global. + // + // TODO: The Global case may have a different cone type than the heapType, + // which we could use here. + // TODO: A Global may refer to an immutable global, which we can read the + // field from potentially (reading it from the struct.new/array.new + // in the definition of it, if it is not imported; or, we could track + // the contents of immutable fields of allocated objects, and not just + // represent them as an exact type). + // See the test TODO with text "We optimize some of this, but stop at + // reading from the immutable global" + assert(refContents.isGlobal() || refContents.isConeType()); + + // Just look at the cone here, discarding information about this being a + // global, if it was one. All that matters from now is the cone. We also + // normalize the cone to avoid wasted work later. + auto cone = refContents.getCone(); + auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth); + + // We create a ConeReadLocation for the canonical cone of this type, to + // avoid bloating the graph, see comment on ConeReadLocation(). + auto coneReadLocation = + ConeReadLocation{cone.type.getHeapType(), normalizedDepth, fieldIndex}; + if (!hasIndex(coneReadLocation)) { + // This is the first time we use this location, so create the links for it + // in the graph. + subTypes->iterSubTypes( + cone.type.getHeapType(), + normalizedDepth, + [&](HeapType type, Index depth) { connectDuringFlow(DataLocation{type, fieldIndex}, coneReadLocation); - } - - // TODO: if the old contents here were an exact type then we have an old - // link here that we could remove as it is redundant (the cone - // contains the exact type among the others). But removing links - // is not efficient, so maybe not worth it. - } + }); - // Link to the canonical location. - connectDuringFlow(coneReadLocation, ExpressionLocation{read, 0}); + // TODO: we can end up with redundant links here if we see one cone first + // and then a larger one later. But removing links is not efficient, + // so for now just leave that. } + + // Link to the canonical location. + connectDuringFlow(coneReadLocation, ExpressionLocation{read, 0}); } void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { @@ -2012,6 +2010,15 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { std::cout << " add special writes\n"; #endif + auto refContents = getContents(getIndex(ExpressionLocation{ref, 0})); + +#ifndef NDEBUG + // We must not have anything in the reference that is invalid for the wasm + // type there. + auto maximalContents = PossibleContents::fullConeType(ref->type); + assert(PossibleContents::isSubContents(refContents, maximalContents)); +#endif + // We could set up links here as we do for reads, but as we get to this code // in any case, we can just flow the values forward directly. This avoids // adding any links (edges) to the graph (and edges are what we want to avoid @@ -2033,29 +2040,25 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { // we've sent information based on the final and correct information about our // reference and value.) - auto refContents = getContents(getIndex(ExpressionLocation{ref, 0})); auto valueContents = getContents(getIndex(ExpressionLocation{value, 0})); + + // See the related comment in readFromData() as to why these are the only + // things we need to check, and why the assertion afterwards contains the only + // things possible. if (refContents.isNone() || refContents.isNull()) { return; } - if (refContents.hasExactType()) { - // Update the one possible type here. - auto heapLoc = - DataLocation{refContents.getType().getHeapType(), fieldIndex}; - updateContents(heapLoc, valueContents); - } else { - assert(refContents.isMany() || refContents.isGlobal() || - refContents.isConeType()); - - // Update all possible subtypes here. - // TODO: Use the cone depth here for ConeType. Right now we do the - // pessimistic thing and assume a full cone of all subtypes. - auto type = ref->type.getHeapType(); - for (auto subType : subTypes->getAllSubTypes(type)) { - auto heapLoc = DataLocation{subType, fieldIndex}; + assert(refContents.isGlobal() || refContents.isConeType()); + + // As in readFromData, normalize to the proper cone. + auto cone = refContents.getCone(); + auto normalizedDepth = getNormalizedConeDepth(cone.type, cone.depth); + + subTypes->iterSubTypes( + cone.type.getHeapType(), normalizedDepth, [&](HeapType type, Index depth) { + auto heapLoc = DataLocation{type, fieldIndex}; updateContents(heapLoc, valueContents); - } - } + }); } void Flower::flowRefCast(const PossibleContents& contents, RefCast* cast) { diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 14ed36b4d..8a8d9b635 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -454,8 +454,10 @@ struct NullLocation { // A special type of location that does not refer to something concrete in the // wasm, but is used to optimize the graph. A "cone read" is a struct.get or -// array.get of a type that is not exact, so it can read the "cone" of all the -// subtypes. In general a read of a cone type (as opposed to an exact type) will +// array.get of a type that is not exact, so it can read from either that type +// of some of the subtypes (up to a particular subtype depth). +// +// In general a read of a cone type + depth (as opposed to an exact type) will // require N incoming links, from each of the N subtypes - and we need that // for each struct.get of a cone. If there are M such gets then we have N * M // edges for this. Instead, we make a single canonical "cone read" location, and @@ -464,11 +466,15 @@ struct NullLocation { // data to flow along). struct ConeReadLocation { HeapType type; + // As in PossibleContents, this represents the how deep we go with subtypes. + // 0 means an exact type, 1 means immediate subtypes, etc. (Note that 0 is not + // needed since that is what DataLocation already is.) + Index depth; // The index of the field in a struct, or 0 for an array (where we do not // attempt to differentiate by index). Index index; bool operator==(const ConeReadLocation& other) const { - return type == other.type && index == other.index; + return type == other.type && depth == other.depth && index == other.index; } }; @@ -572,8 +578,9 @@ template<> struct hash<wasm::NullLocation> { template<> struct hash<wasm::ConeReadLocation> { size_t operator()(const wasm::ConeReadLocation& loc) const { - return std::hash<std::pair<wasm::HeapType, wasm::Index>>{}( - {loc.type, loc.index}); + return std::hash< + std::pair<wasm::HeapType, std::pair<wasm::Index, wasm::Index>>>{}( + {loc.type, {loc.depth, loc.index}}); } }; |