Skip to content

Conversation

@tlively
Copy link
Member

@tlively tlively commented Nov 14, 2025

ConeType is an important part of PossibleContents. Implement it as a
standalone lattice that can be combined into a larger lattice if we
rewrite PossibleContents in terms of the lattice framework.

ConeType is an important part of PossibleContents. Implement it as a
standalone lattice that can be combined into a larger lattice if we
rewrite PossibleContents in terms of the lattice framework.
@tlively tlively requested a review from kripken November 14, 2025 19:56

namespace wasm::analysis {

struct ConeType {
Copy link
Member

Choose a reason for hiding this comment

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

A comment describing this could be good.

if (auto it = typeDepths.find(type.getHeapType()); it != typeDepths.end()) {
return Element{type, it->second};
}
return Element{type, 0};
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be an encoding of infinity?

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 should only be reached in practice for bottom types. I can add an assertion.

Copy link
Member

Choose a reason for hiding this comment

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

In that case, can check type.isBottom on line 44?

bool isTop() const { return type == Type::none; }
};

std::unordered_map<HeapType, Index> typeDepths;
Copy link
Member

Choose a reason for hiding this comment

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

Is this a cache (it isn't const which suggests that)? What is the meaning of a missing entry?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, it only used to create new elements in get. I can make it const.

CHECK_MEET(a, b, meet);
switch (lattice.compare(a, b)) {
case analysis::NO_RELATION:
CHECK_UNRELATED(a, b);
Copy link
Member

Choose a reason for hiding this comment

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

This is identical to what the switch is branching on, i.e., this adds nothing? It reads as

if (x == 1) {
  assert(x == 1);
} else ..

Copy link
Member Author

Choose a reason for hiding this comment

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

The check also tests the opposite direction.

Element top;
Element i32;
Element i64;
Element eqNull3;
Copy link
Member

Choose a reason for hiding this comment

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

what is "3" here?

Copy link
Member Author

Choose a reason for hiding this comment

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

A note to the reader about the depth.

return changed;
}
Index joineeToLub = 0, joinerToLub = 0;
if (!joinee.isBottom() && !joinee.type.getHeapType().isBottom()) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this distinction necessary? That is, do we need to differentiate the case of A being bottom from A's type being bottom?

It seems the difference only arises for non-refs, and the bottom has no more depth below it, so it seems this only applies to the top?

(In PossibleContents, we didn't have cones of the top type - the top type always means "anything" anyhow, so I hope we don't need it here either?)

Copy link
Member Author

Choose a reason for hiding this comment

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

The lattice's bottom is {unreachable, 0}, but we also allow elements like {(ref none), 0}. Right now we don't do any kind of canonicalization, so these are considered separate elements. (Similarly, {(ref foo), 0} and {(ref (exact foo)), 0} are currently considered separate elements.)

We could add canonicalization to avoid having multiple elements that are "morally" equivalent like this, but I'm not sure it's necessary, and it makes the lattice properties harder to reason about.

Copy link
Member

Choose a reason for hiding this comment

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

But do we allow {(ref none), 1)? If not, then why is this line not condensable to a single check?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, we have an invariant that bottom heap types always have a depth of 0. This can't be a single check because joinee.isBottom() is checking the unreachable case and joinee.type.isNull() (previously joinee.getHeapType().isBottom()) is checking the none case. There's not a good way to check both at once.

return changed;
}
Index joineeToLub = 0, joinerToLub = 0;
if (!joinee.isBottom() && !joinee.type.isNull()) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm still fuzzy on why nulls are special here. Perhaps add a comment, as it seems nonobvious?

Copy link
Member Author

Choose a reason for hiding this comment

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

The top comment already mentions the invariant that bottom heap types always have a depth of zero.

Copy link
Member Author

Choose a reason for hiding this comment

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

As for why this is: If we did support arbitrary depths on bottom types, we would have no way to compare the depths with the depths on concrete types. What would the join of {(ref null none), 5} and {(ref null $sub1), 2} be? {(ref null $sub1), 2}? {(ref null $sub1), 1000}? Without an encoding for infinite depth, there's no way to reason about the gap between bottom types and defined types. Since we don't need an encoding of infinite depth for anything else, it's easier to just define the problem away.

top = lattice.getTop();
i32 = lattice.get(Type::i32);
i64 = lattice.get(Type::i64);
eqNull3 = lattice.get(Type(HeapType::eq, Nullable));
Copy link
Member

Choose a reason for hiding this comment

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

What makes this have a depth of 3?

Copy link
Member Author

Choose a reason for hiding this comment

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

eq -> struct -> $super -> $sub1/2

Copy link
Member

Choose a reason for hiding this comment

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

I see, thanks. Then this differs from PossibleContents, where depth is only for user types, not basic ones.

Copy link
Member

Choose a reason for hiding this comment

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

This makes it depend on the set of types given to the lattice to begin with, which PC does not do. I'm not sure which is better.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, interesting. This just uses the depths provided by SubTypes, so I'm surprised PossibleContents doesn't do the same. I doubt this difference will cause problems, though.

@tlively tlively merged commit 0efd70e into main Nov 17, 2025
16 checks passed
@tlively tlively deleted the lattice-conetype branch November 17, 2025 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants