Skip to content

[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

Merged
merged 146 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
146 commits
Select commit Hold shift + click to select a range
51cb0b9
start
kripken Sep 26, 2022
6dc7292
format
kripken Sep 26, 2022
ec0b3d6
builds
kripken Sep 26, 2022
617690a
src/
kripken Sep 26, 2022
f750d67
fix
kripken Sep 26, 2022
a34e470
typo
kripken Sep 26, 2022
afe8b5d
clarify comment
kripken Sep 27, 2022
3af1107
works
kripken Sep 27, 2022
c7c6b93
rename
kripken Sep 27, 2022
fa7a116
moar + fix one
kripken Sep 27, 2022
7927fbe
fix
kripken Sep 27, 2022
e504b0b
fix
kripken Sep 27, 2022
9a057dc
progress
kripken Sep 27, 2022
932da84
comment
kripken Sep 28, 2022
e164411
Merge remote-tracking branch 'origin/main' into cone
kripken Sep 28, 2022
f50a8d4
start
kripken Sep 28, 2022
973586d
format
kripken Sep 28, 2022
8261dc0
test
kripken Sep 28, 2022
650ce4e
hash index too
kripken Sep 28, 2022
c0aa7e4
considered best
kripken Sep 28, 2022
3a1dbfd
Merge remote-tracking branch 'origin/gufa.has' into cone
kripken Sep 28, 2022
85f31a5
test
kripken Sep 28, 2022
81bcf3e
Merge remote-tracking branch 'origin/main' into cone
kripken Sep 29, 2022
43da163
test
kripken Sep 29, 2022
dee3da0
test
kripken Sep 29, 2022
c6bc347
test
kripken Sep 29, 2022
47fffc4
test
kripken Sep 29, 2022
1773856
test
kripken Sep 29, 2022
af84e99
test
kripken Sep 29, 2022
36ebd4c
test
kripken Sep 29, 2022
2b1bfc7
test
kripken Sep 29, 2022
8da6585
test
kripken Sep 29, 2022
fbf99a9
test
kripken Sep 29, 2022
5b577e6
test
kripken Sep 29, 2022
5b9eaba
test
kripken Sep 29, 2022
ad3aefa
test
kripken Sep 29, 2022
126fd22
test
kripken Sep 29, 2022
c435d5a
test
kripken Sep 29, 2022
6624aa3
test
kripken Sep 29, 2022
ec5ccc2
test
kripken Sep 29, 2022
35b43af
test
kripken Sep 29, 2022
a78b734
test
kripken Sep 29, 2022
eda95c9
test
kripken Sep 29, 2022
cfd79c6
test
kripken Sep 29, 2022
6769447
test
kripken Sep 29, 2022
2443410
test
kripken Sep 29, 2022
932b1e7
format
kripken Sep 29, 2022
1765c4f
format
kripken Sep 29, 2022
bbeb87d
Merge remote-tracking branch 'origin/main' into cone
kripken Sep 29, 2022
e855951
progress
kripken Sep 29, 2022
48d99a0
test
kripken Sep 29, 2022
9e742f1
test
kripken Sep 29, 2022
5df1d38
test
kripken Sep 29, 2022
a61239d
progress
kripken Sep 29, 2022
ab75451
test
kripken Sep 29, 2022
7e7d36b
test
kripken Sep 29, 2022
4f2ad9e
test
kripken Sep 29, 2022
416b586
test
kripken Sep 29, 2022
1bcab96
test
kripken Sep 29, 2022
433f4be
test
kripken Sep 29, 2022
680041c
test
kripken Sep 29, 2022
5af82bb
test
kripken Sep 29, 2022
e511255
test
kripken Sep 29, 2022
fdc9930
test
kripken Sep 29, 2022
c9383c7
test
kripken Sep 29, 2022
4680ecd
test
kripken Sep 29, 2022
b1f5fb9
test
kripken Sep 29, 2022
a68dbfe
test
kripken Sep 29, 2022
3cff9cb
format
kripken Sep 29, 2022
fd277a3
test
kripken Sep 29, 2022
9b8e7b6
test
kripken Sep 29, 2022
4662f54
test
kripken Sep 29, 2022
4b50ecc
test
kripken Sep 29, 2022
d4f0e7f
test
kripken Sep 29, 2022
883cad8
test
kripken Sep 29, 2022
953548e
test
kripken Sep 29, 2022
282b678
format
kripken Sep 29, 2022
9a77fed
work
kripken Sep 29, 2022
1217eeb
todo
kripken Sep 30, 2022
aa74ce9
work
kripken Sep 30, 2022
aea36da
fix
kripken Sep 30, 2022
ad825a7
format
kripken Sep 30, 2022
9af078c
test
kripken Sep 30, 2022
bfd3509
test
kripken Sep 30, 2022
8ca04e4
test
kripken Sep 30, 2022
ce84508
test
kripken Sep 30, 2022
44baef0
test
kripken Sep 30, 2022
cb68fe1
test
kripken Sep 30, 2022
0e3c2b2
test
kripken Sep 30, 2022
0fc4e13
test
kripken Sep 30, 2022
54fa5cc
test
kripken Sep 30, 2022
446c2de
work
kripken Sep 30, 2022
c27187e
format
kripken Sep 30, 2022
e830086
format
kripken Sep 30, 2022
6a3a208
builds
kripken Sep 30, 2022
d7152dd
fix
kripken Sep 30, 2022
dc5e63a
format
kripken Sep 30, 2022
643b539
work
kripken Sep 30, 2022
3d4b099
work
kripken Sep 30, 2022
5fc0d71
work
kripken Sep 30, 2022
f5454be
work
kripken Sep 30, 2022
64b7136
test
kripken Sep 30, 2022
3ef1a32
test
kripken Sep 30, 2022
07762db
work
kripken Sep 30, 2022
6fd8c96
work
kripken Sep 30, 2022
f6806ad
test
kripken Sep 30, 2022
18c7348
test
kripken Sep 30, 2022
2bc7826
test
kripken Sep 30, 2022
0a20071
test
kripken Sep 30, 2022
03eb8c5
test
kripken Sep 30, 2022
e46a096
fix
kripken Sep 30, 2022
0dc1cb1
work
kripken Sep 30, 2022
552e2e8
work
kripken Sep 30, 2022
97cd211
work
kripken Sep 30, 2022
3b611f9
format
kripken Sep 30, 2022
d792619
test
kripken Sep 30, 2022
f3bcdc7
fix
kripken Sep 30, 2022
ef917a2
comment
kripken Sep 30, 2022
cbfffd0
Merge remote-tracking branch 'origin/fix' into cone
kripken Sep 30, 2022
781f043
Merge remote-tracking branch 'origin/main' into cone
kripken Oct 3, 2022
3cf100d
move
kripken Oct 3, 2022
5808d74
comment
kripken Oct 3, 2022
6102dca
simpler
kripken Oct 3, 2022
8618c0a
comments
kripken Oct 3, 2022
a2269de
fix
kripken Oct 3, 2022
824c864
todo
kripken Oct 3, 2022
2290fd2
test
kripken Oct 3, 2022
c898267
typo
kripken Oct 3, 2022
d24fc0b
fix
kripken Oct 3, 2022
9999985
Revert "fix":wq
kripken Oct 3, 2022
9737075
rename
kripken Oct 5, 2022
1f272ab
cleanup
kripken Oct 5, 2022
b280cc1
fix typo
kripken Oct 6, 2022
6f182f8
Merge remote-tracking branch 'origin/main' into cone
kripken Oct 7, 2022
85e2ac3
null lub handling after recent null changes
kripken Oct 7, 2022
415a8ea
test update
kripken Oct 7, 2022
05ea12a
fix
kripken Oct 7, 2022
d87fc2b
fix
kripken Oct 7, 2022
78002dd
clenup
kripken Oct 7, 2022
fd520f8
clenup
kripken Oct 7, 2022
2e0e687
fix comment
kripken Oct 7, 2022
6926cb1
Update src/ir/possible-contents.cpp
kripken Oct 7, 2022
9cfdc3f
Merge remote-tracking branch 'origin/cone' into cone
kripken Oct 7, 2022
9117223
comment
kripken Oct 7, 2022
83d720a
comment
kripken Oct 7, 2022
a1245ad
Merge remote-tracking branch 'origin/main' into cone
kripken Oct 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 234 additions & 69 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +113 to 118
Copy link
Member

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?

Copy link
Member Author

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.

}
}

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The case of None and Many are handled above, but can this be a Literal or Global? In that case, what it doesn't have a heap type?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about other.isLiteral() || other.isGlobal()? I guess those don't matter because we only call this with cone types? It would be good to add a TODO here. Alternatively, we could give this whole function a more specific name like intersectWithCone.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, atm we just have other being a cone type. I can rename the method that way. If we expand it later we can always rename it back.

Copy link
Member

Choose a reason for hiding this comment

The 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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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().
Expand Down Expand Up @@ -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};
Expand All @@ -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";
Expand Down
Loading