-
Notifications
You must be signed in to change notification settings - Fork 787
[Wasm GC] [GUFA] Add initial ConeType support #5116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
51cb0b9
6dc7292
ec0b3d6
617690a
f750d67
a34e470
afe8b5d
3af1107
c7c6b93
fa7a116
7927fbe
e504b0b
9a057dc
932da84
e164411
f50a8d4
973586d
8261dc0
650ce4e
c0aa7e4
3a1dbfd
85f31a5
81bcf3e
43da163
dee3da0
c6bc347
47fffc4
1773856
af84e99
36ebd4c
2b1bfc7
8da6585
fbf99a9
5b577e6
5b9eaba
ad3aefa
126fd22
c435d5a
6624aa3
ec5ccc2
35b43af
a78b734
eda95c9
cfd79c6
6769447
2443410
932b1e7
1765c4f
bbeb87d
e855951
48d99a0
9e742f1
5df1d38
a61239d
ab75451
7e7d36b
4f2ad9e
416b586
1bcab96
433f4be
680041c
5af82bb
e511255
fdc9930
c9383c7
4680ecd
b1f5fb9
a68dbfe
3cff9cb
fd277a3
9b8e7b6
4662f54
4b50ecc
d4f0e7f
883cad8
953548e
282b678
9a77fed
1217eeb
aa74ce9
aea36da
ad825a7
9af078c
bfd3509
8ca04e4
ce84508
44baef0
cb68fe1
0e3c2b2
0fc4e13
54fa5cc
446c2de
c27187e
e830086
6a3a208
d7152dd
dc5e63a
643b539
3d4b099
5fc0d71
f5454be
64b7136
3ef1a32
07762db
6fd8c96
f6806ad
18c7348
2bc7826
0a20071
03eb8c5
e46a096
0dc1cb1
552e2e8
97cd211
3b611f9
d792619
f3bcdc7
ef917a2
cbfffd0
781f043
3cf100d
5808d74
6102dca
8618c0a
a2269de
824c864
2290fd2
c898267
d24fc0b
9999985
9737075
1f272ab
b280cc1
6f182f8
85e2ac3
415a8ea
05ea12a
d87fc2b
78002dd
fd520f8
2e0e687
6926cb1
9cfdc3f
9117223
83d720a
a1245ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,39 +89,184 @@ void PossibleContents::combine(const PossibleContents& other) { | |
return; | ||
} | ||
|
||
auto lub = Type::getLeastUpperBound(type, otherType); | ||
if (lub == Type::none) { | ||
// The types are not in the same hierarchy. | ||
value = Many(); | ||
return; | ||
} | ||
|
||
// From here we can assume there is a useful LUB. | ||
|
||
// Nulls can be combined in by just adding nullability to a type. | ||
if (isNull() || other.isNull()) { | ||
// Only one of them can be null here, since we already handled the case | ||
// where they were both null. | ||
assert(!isNull() || !other.isNull()); | ||
// If only one is a null, but the other's type is known exactly, then the | ||
// combination is to add nullability (if the type is *not* known exactly, | ||
// like for a global, then we cannot do anything useful here). | ||
if (!isNull() && hasExactType()) { | ||
value = ExactType(Type(type.getHeapType(), Nullable)); | ||
// If only one is a null then we can use the type info from the other, and | ||
// just add in nullability. For example, a literal of type T and a null | ||
// becomes an exact type of T that allows nulls, and so forth. | ||
auto mixInNull = [](ConeType cone) { | ||
cone.type = Type(cone.type.getHeapType(), Nullable); | ||
return cone; | ||
}; | ||
if (!isNull()) { | ||
value = mixInNull(getCone()); | ||
return; | ||
} else if (!other.isNull() && other.hasExactType()) { | ||
value = ExactType(Type(otherType.getHeapType(), Nullable)); | ||
} else if (!other.isNull()) { | ||
value = mixInNull(other.getCone()); | ||
return; | ||
} | ||
} | ||
|
||
if (hasExactType() && other.hasExactType() && | ||
type.getHeapType() == otherType.getHeapType()) { | ||
// We know the types here exactly, and even the heap types match, but | ||
// there is some other difference that prevents them from being 100% | ||
// identical (for example, one might be an ExactType and the other a | ||
// Literal; or both might be ExactTypes and only one might be nullable). | ||
// In these cases we can emit a proper ExactType here, adding nullability | ||
// if we need to. | ||
value = ExactType(Type( | ||
type.getHeapType(), | ||
type.isNullable() || otherType.isNullable() ? Nullable : NonNullable)); | ||
// Find a ConeType that describes both inputs, using the shared ancestor which | ||
// is the LUB. We need to find how big a cone we need: the cone must be big | ||
// enough to contain both the inputs. | ||
auto depth = getCone().depth; | ||
auto otherDepth = other.getCone().depth; | ||
Index newDepth; | ||
if (depth == FullDepth || otherDepth == FullDepth) { | ||
// At least one has full (infinite) depth, so we know the new depth must | ||
// be the same. | ||
newDepth = FullDepth; | ||
} else { | ||
// The depth we need under the lub is how far from the lub we are, plus | ||
// the depth of our cone. | ||
// TODO: we could make a single loop that also does the LUB, at the same | ||
// time, and also avoids calling getDepth() which loops once more? | ||
auto depthFromRoot = type.getHeapType().getDepth(); | ||
auto otherDepthFromRoot = otherType.getHeapType().getDepth(); | ||
auto lubDepthFromRoot = lub.getHeapType().getDepth(); | ||
assert(lubDepthFromRoot <= depthFromRoot); | ||
assert(lubDepthFromRoot <= otherDepthFromRoot); | ||
Index depthUnderLub = depthFromRoot - lubDepthFromRoot + depth; | ||
Index otherDepthUnderLub = | ||
otherDepthFromRoot - lubDepthFromRoot + otherDepth; | ||
|
||
// The total cone must be big enough to contain all the above. | ||
newDepth = std::max(depthUnderLub, otherDepthUnderLub); | ||
} | ||
|
||
value = ConeType{lub, newDepth}; | ||
} | ||
|
||
void PossibleContents::intersectWithFullCone(const PossibleContents& other) { | ||
assert(other.isFullConeType()); | ||
|
||
if (isSubContents(other, *this)) { | ||
// The intersection is just |other|. | ||
// Note that this code path handles |this| being Many. | ||
value = other.value; | ||
return; | ||
} | ||
|
||
if (!haveIntersection(*this, other)) { | ||
// There is no intersection at all. | ||
// Note that this code path handles |this| being None. | ||
value = None(); | ||
return; | ||
} | ||
|
||
// There is an intersection here. Note that this implies |this| is a reference | ||
// type, as it has an intersection with |other| which is a full cone type | ||
// (which must be a reference type). | ||
auto type = getType(); | ||
auto otherType = other.getType(); | ||
auto heapType = type.getHeapType(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The case of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does look unclear, I'll add a comment. The reason is the intersection with another reference type is not empty, so this must be a reference type itself. |
||
auto otherHeapType = otherType.getHeapType(); | ||
|
||
// If both inputs are nullable then the intersection is nullable as well. | ||
auto nullability = | ||
type.isNullable() && otherType.isNullable() ? Nullable : NonNullable; | ||
|
||
auto setNoneOrNull = [&]() { | ||
if (nullability == Nullable) { | ||
value = Literal::makeNull(otherHeapType); | ||
} else { | ||
value = None(); | ||
} | ||
}; | ||
|
||
if (isNull()) { | ||
// The intersection is either this null itself, or nothing if a null is not | ||
// allowed. | ||
setNoneOrNull(); | ||
return; | ||
} | ||
|
||
// If the heap types are not compatible then they are in separate hierarchies | ||
// and there is no intersection. | ||
auto isSubType = HeapType::isSubType(heapType, otherHeapType); | ||
auto otherIsSubType = HeapType::isSubType(otherHeapType, heapType); | ||
if (!isSubType && !otherIsSubType) { | ||
value = None(); | ||
return; | ||
} | ||
|
||
// Nothing else possible combines in an interesting way; emit a Many. | ||
value = Many(); | ||
if (isLiteral() || isGlobal()) { | ||
// The information about the value being identical to a particular literal | ||
// or immutable global is not removed by intersection, if the type is in the | ||
// cone we are intersecting with. | ||
if (isSubType) { | ||
return; | ||
} | ||
|
||
// The type must change, so continue down to the generic code path. | ||
// TODO: for globals we could perhaps refine the type here, but then the | ||
// type on GlobalInfo would not match the module, so that needs some | ||
// refactoring. | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, atm we just have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be helpful, thanks! |
||
|
||
// Intersect the cones, as there is no more specific information we can use. | ||
auto depthFromRoot = heapType.getDepth(); | ||
auto otherDepthFromRoot = otherHeapType.getDepth(); | ||
|
||
// To compute the new cone, find the new heap type for it, and to compute its | ||
// depth, consider the adjustments to the existing depths that stem from the | ||
// choice of new heap type. | ||
HeapType newHeapType; | ||
|
||
if (depthFromRoot < otherDepthFromRoot) { | ||
newHeapType = otherHeapType; | ||
} else { | ||
newHeapType = heapType; | ||
} | ||
|
||
auto newType = Type(newHeapType, nullability); | ||
|
||
// By assumption |other| has full depth. Consider the other cone in |this|. | ||
if (hasFullCone()) { | ||
// Both are full cones, so the result is as well. | ||
value = FullConeType(newType); | ||
} else { | ||
// The result is a partial cone. If the cone starts in |otherHeapType| then | ||
// we need to adjust the depth down, since it will be smaller than the | ||
// original cone: | ||
/* | ||
// .. | ||
// / | ||
// otherHeapType | ||
// / \ | ||
// heapType .. | ||
// \ | ||
*/ | ||
// E.g. if |this| is a cone of depth 10, and |otherHeapType| is an immediate | ||
// subtype of |this|, then the new cone must be of depth 9. | ||
auto newDepth = getCone().depth; | ||
if (newHeapType == otherHeapType) { | ||
assert(depthFromRoot <= otherDepthFromRoot); | ||
auto reduction = otherDepthFromRoot - depthFromRoot; | ||
if (reduction > newDepth) { | ||
// The cone on heapType does not even reach the cone on otherHeapType, | ||
// so the result is not a cone. | ||
setNoneOrNull(); | ||
return; | ||
} | ||
newDepth -= reduction; | ||
} | ||
|
||
value = ConeType{newType, newDepth}; | ||
} | ||
} | ||
|
||
bool PossibleContents::haveIntersection(const PossibleContents& a, | ||
|
@@ -148,43 +293,77 @@ bool PossibleContents::haveIntersection(const PossibleContents& a, | |
|
||
// From here on we focus on references. | ||
|
||
if (aType.isNullable() && bType.isNullable()) { | ||
// Null is possible on both sides. Assume that an intersection can exist, | ||
// but we could be more precise here and check if the types belong to | ||
// different hierarchies, in which case the nulls would differ TODO. For | ||
// now we only use this API from the RefEq logic, so this is fully precise. | ||
auto aHeapType = aType.getHeapType(); | ||
auto bHeapType = bType.getHeapType(); | ||
|
||
if (aType.isNullable() && bType.isNullable() && | ||
aHeapType.getBottom() == bHeapType.getBottom()) { | ||
// A compatible null is possible on both sides. | ||
return true; | ||
} | ||
|
||
// We ruled out a null on both sides, so at least one is non-nullable. If the | ||
// other is a null then no chance for an intersection remains. | ||
// We ruled out having a compatible null on both sides. If one is simply a | ||
// null then no chance for an intersection remains. | ||
if (a.isNull() || b.isNull()) { | ||
return false; | ||
} | ||
|
||
auto aSubB = HeapType::isSubType(aHeapType, bHeapType); | ||
auto bSubA = HeapType::isSubType(bHeapType, aHeapType); | ||
if (!aSubB && !bSubA) { | ||
// No type can appear in both a and b, so the types differ, so the values | ||
// do not overlap. | ||
return false; | ||
} | ||
|
||
// From here on we focus on references and can ignore the case of null - any | ||
// intersection must be of a non-null value, so we can focus on the heap | ||
// types. | ||
auto aHeapType = aType.getHeapType(); | ||
auto bHeapType = bType.getHeapType(); | ||
|
||
if (a.hasExactType() && b.hasExactType() && aHeapType != bHeapType) { | ||
// The values must be different since their types are different. | ||
return false; | ||
} | ||
|
||
if (!HeapType::isSubType(aHeapType, bHeapType) && | ||
!HeapType::isSubType(bHeapType, aHeapType)) { | ||
// No type can appear in both a and b, so the types differ, so the values | ||
// differ. | ||
return false; | ||
auto aDepthFromRoot = aHeapType.getDepth(); | ||
auto bDepthFromRoot = bHeapType.getDepth(); | ||
|
||
if (aSubB) { | ||
// A is a subtype of B. For there to be an intersection we need their cones | ||
// to intersect, that is, to rule out the case where the cone from B is not | ||
// deep enough to reach A. | ||
assert(aDepthFromRoot >= bDepthFromRoot); | ||
return aDepthFromRoot - bDepthFromRoot <= b.getCone().depth; | ||
} else if (bSubA) { | ||
assert(bDepthFromRoot >= aDepthFromRoot); | ||
return bDepthFromRoot - aDepthFromRoot <= a.getCone().depth; | ||
} else { | ||
WASM_UNREACHABLE("we ruled out no subtyping before"); | ||
} | ||
|
||
// TODO: we can also optimize things like different Literals, but existing | ||
// passes do such things already so it is low priority. | ||
} | ||
|
||
bool PossibleContents::isSubContents(const PossibleContents& a, | ||
const PossibleContents& b) { | ||
// TODO: Everything else. For now we only call this when |a| or |b| is a full | ||
// cone type. | ||
if (b.isFullConeType()) { | ||
if (a.isNone()) { | ||
return true; | ||
} | ||
if (a.isMany()) { | ||
return false; | ||
} | ||
if (a.isNull()) { | ||
return b.getType().isNullable(); | ||
} | ||
return Type::isSubType(a.getType(), b.getType()); | ||
} | ||
|
||
if (a.isFullConeType()) { | ||
// We've already ruled out b being a full cone type before, so the only way | ||
// |a| can be contained in |b| is if |b| is everything. | ||
return b.isMany(); | ||
} | ||
|
||
// It appears they can intersect. | ||
return true; | ||
WASM_UNREACHABLE("a or b must be a full cone"); | ||
} | ||
|
||
namespace { | ||
|
@@ -1018,6 +1197,7 @@ struct InfoCollector | |
// verbose code). | ||
void addRoot(Expression* curr, | ||
PossibleContents contents = PossibleContents::many()) { | ||
// TODO Use a cone type here when relevant | ||
if (isRelevant(curr)) { | ||
addRoot(ExpressionLocation{curr, 0}, contents); | ||
} | ||
|
@@ -1675,7 +1855,11 @@ void Flower::readFromData(HeapType declaredHeapType, | |
// 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()); | ||
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(). | ||
|
@@ -1737,9 +1921,12 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { | |
DataLocation{refContents.getType().getHeapType(), fieldIndex}; | ||
updateContents(heapLoc, valueContents); | ||
} else { | ||
assert(refContents.isMany() || refContents.isGlobal()); | ||
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}; | ||
|
@@ -1751,32 +1938,10 @@ void Flower::writeToData(Expression* ref, Expression* value, Index fieldIndex) { | |
void Flower::flowRefCast(const PossibleContents& contents, RefCast* cast) { | ||
// RefCast only allows valid values to go through: nulls and things of the | ||
// cast type. Filter anything else out. | ||
PossibleContents filtered; | ||
if (contents.isMany()) { | ||
// Just pass the Many through. | ||
// TODO: we could emit a cone type here when we get one, instead of | ||
// emitting a Many in any of these code paths | ||
filtered = contents; | ||
} else { | ||
bool isSubType = | ||
HeapType::isSubType(contents.getType().getHeapType(), cast->intendedType); | ||
if (isSubType) { | ||
// The contents are not Many, but their heap type is a subtype of the | ||
// intended type, so we'll pass that through. Note that we pass the entire | ||
// contents here, which includes nullability, but that is fine, it would | ||
// just overlap with the code below that handles nulls (that is, the code | ||
// below only makes a difference when the heap type is *not* a subtype but | ||
// the type is nullable). | ||
// TODO: When we get cone types, we could filter the cone here. | ||
filtered.combine(contents); | ||
} | ||
bool mayBeNull = contents.getType().isNullable(); | ||
if (mayBeNull) { | ||
// A null is possible, so pass that along. | ||
filtered.combine( | ||
PossibleContents::literal(Literal::makeNull(cast->intendedType))); | ||
} | ||
} | ||
auto intendedCone = | ||
PossibleContents::fullConeType(Type(cast->intendedType, Nullable)); | ||
PossibleContents filtered = contents; | ||
filtered.intersectWithFullCone(intendedCone); | ||
if (!filtered.isNone()) { | ||
#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2 | ||
std::cout << " ref.cast passing through\n"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the heap type of the two types don't have a lub?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, yeah, looks like this "raced" with the null type changes that just landed. Fixed.