-
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
Conversation
That can be a little less readable, I agree. The idea though behind this convention is that it's very common to do It might be worth measuring this more in detail and possibly refactoring it, though. |
Looking into both those changes later sounds fine to me. |
Oh, btw, as more background, this is common in game engines where such operations on vectors and matrices etc. must be very optimized. It's possible it matters less here... |
I would be very interested to see how much of a difference it makes. |
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.
Sorry for the delayed reply. Haven't read everything yet, but wonder what would be an example of the case when we know a precise depth for a cone type and what benefit it can bring.
src/ir/possible-contents.h
Outdated
@@ -133,6 +145,11 @@ class PossibleContents { | |||
// contents here will then include whatever content was possible in |other|. | |||
void combine(const PossibleContents& other); | |||
|
|||
// Removes anything not in |other| from this object, so that it ends up with | |||
// only their intersection. At this only handles an intersection with a full |
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.
Not sure what does "At this" mean here... Is that a typo?
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.
Sorry, that should be "Atm" for "at the moment"... will fix.
I should have added an example. Something like this: struct A { field : i32 };
struct B : A;
struct C : B;
a = new A{.field = 42};
b = new B{.field = 42};
c = new C{.field = 1337};
foo((x ? a : b).f); // => foo(42)
// since Cone(A, 1)
// includes only A, B The input to |
FYI I think this will have a merge conflict with #5115 since the handling of nulls in possible-contents needed to change. I don't have a preference for what order they land in, though. |
I don't have a preference either. As your PR is much larger, maybe best to let yours land first. |
|
||
// Returns whether the relevant cone for this, as computed by getCone(), is of | ||
// full size, that is, includes all subtypes. | ||
bool hasFullCone() const { return getCone().depth == FullDepth; } |
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.
FullCone
sounds good to me.
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; |
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.
src/ir/possible-contents.cpp
Outdated
// 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? |
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.
Why do we need a loop?
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.
getDepth()
does a loop, basically it goes up the chain of supertypes until the end. So each getDepth()
here is a loop, sadly.
src/ir/possible-contents.cpp
Outdated
Index depthUnderLub = depthFromRoot - lubDepthFromRoot + depth; | ||
Index otherDepthUnderLub = | ||
otherDepthFromRoot - lubDepthFromRoot + otherDepth; |
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.
Does a heap type have a unique depth?
It's been a while since I last looked at wasm-type.cpp
, so I may lack the context.
binaryen/src/wasm/wasm-type.cpp
Lines 1389 to 1404 in e8884de
size_t HeapType::getDepth() const { | |
size_t depth = 0; | |
std::optional<HeapType> super; | |
for (auto curr = *this; (super = curr.getSuperType()); curr = *super) { | |
++depth; | |
} | |
// In addition to the explicit supertypes we just traversed over, there is | |
// implicit supertyping wrt basic types. A signature type always has one more | |
// super, HeapType::func, etc. | |
if (!isBasic()) { | |
if (isFunction()) { | |
depth++; | |
} else if (isData()) { | |
// specific struct types <: data <: eq <: any | |
depth += 3; | |
} |
Here we follow supertypes using a loop and then add +1 if it is a function and +3 if it is a data. But is it possible for one of the supertypes traversed by the for loop is function
or data
? In that case, is the depth unique?
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.
getSuperType
only returns user types atm. So it never returns a basic type like data
. (This might be worth improving, but I looked into it and it wasn't trivial.)
The depth should be unique, and is defined to include basic supertypes in the count.
src/ir/possible-contents.cpp
Outdated
auto isSubType = HeapType::isSubType(heapType, otherHeapType); | ||
auto otherIsSubType = HeapType::isSubType(otherHeapType, heapType); | ||
if (!isSubType && !otherIsSubType) { | ||
setNoneOrNull(); |
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.
Can this be null, given that there's no bottom type (yet)?
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.
Yes, looks like this needs updating as well...
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.
Done.
src/ir/possible-contents.cpp
Outdated
// An interesting non-empty intersection that is a new cone which differs from | ||
// both the original ones. (This must be an intersection of cones, since by | ||
// assumption |other| is a cone, and another cone is the only shape that can | ||
// have a non-empty intersection with it that differs from them both.) |
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.
Why should this differ from them both? We handled the case of isSubContents(other, *this)
above, but can't this be isSubContents(*this, other)
, so the intersection is just *this
? (The code seems to handle this case though)
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.
Correct, thanks. This comment was stale.
|
||
auto type = getType(); | ||
auto otherType = other.getType(); | ||
auto heapType = type.getHeapType(); |
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.
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?
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.
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.
// returns 1 if the input is of a subtype of the intended type, that is, | ||
// we are looking for a type in that cone of types. | ||
auto intendedContents = | ||
PossibleContents::fullConeType(Type(curr->intendedType, NonNullable)); |
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.
Would be helpful to have a short comment on why this is NonNullable
(i.e., otherwise it can trap)
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.
Added.
assertCombination(exactFuncref, exactAnyref, many); | ||
assertCombination(exactFuncref, anyGlobal, many); | ||
assertCombination(exactFuncref, nonNullFunc, many); | ||
assertCombination(exactFuncref, nonNullFunc, coneFuncref1); |
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.
Why is nonNullFunc
a subtype depth 1 of funcref
? It looks we treat all signatures in the same way. Is that gonna be true after implementing the real subtyping for functions?
binaryen/src/wasm/wasm-type.cpp
Lines 1731 to 1736 in e8884de
bool SubTyper::isSubType(const Signature& a, const Signature& b) { | |
// TODO: Implement proper signature subtyping, covariant in results and | |
// contravariant in params, once V8 implements it. | |
// return isSubType(b.params, a.params) && isSubType(a.results, b.results); | |
return a == b; | |
} |
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.
I think that's right, this is the current temporary situation. @tlively would know for sure.
Co-authored-by: Heejin Ahn <[email protected]>
#if BINARYEN_TEST_DEBUG | ||
if (!PossibleContents::haveIntersection(a, b) || | ||
!PossibleContents::haveIntersection(b, a)) { | ||
std::cout << "\nFailure: no intersection:\n" << a << '\n' << b << '\n'; | ||
abort(); | ||
} | ||
#endif |
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.
Why do we need this? If this is the case, doesn't this crash in EXPECT_TRUE
above anyway?
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.
EXPECT_TRUE
doesn't print out the values being operated on, so we just get "expected true on line 286" and that's it. We don't even know which call to this function caused the problem. The extra logging makes it easy to debug these.
A cone type is a PossibleContents that has a base type and a depth, and it
contains all subtypes up to that depth. So depth 0 is an exact type from
before, etc.
This only adds cone type computations when combining types, that is, when we
combine two exact types we might get a cone, etc. This does not yet use the
cone info in all places (like struct gets and sets), and it does not yet define roots
of cone types, all of which is left for later. IOW this is the MVP of cone types that
is just enough to add them + pass tests + test the new functionality.
This improves the j2wasm binary, but only very slightly.