Skip to content

[analysis][NFC] Rename makeLeastUpperBound to join and move it to lattice #6035

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 2 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 7 additions & 6 deletions src/analysis/lattice.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,17 @@ concept Lattice = requires(const L& lattice,
// Lattices must have elements.
typename L::Element;
requires std::copyable<typename L::Element>;
// We need to be able to get the bottom element.
// Get the bottom element of this lattice.
{ lattice.getBottom() } noexcept -> std::same_as<typename L::Element>;
// Elements should be comparable. TODO: use <=> and std::three_way_comparable
// once we support C++20 everywhere.
// Compare two elements of this lattice. TODO: use <=> and
// std::three_way_comparable once we support C++20 everywhere.
{
lattice.compare(constElem, constElem)
} noexcept -> std::same_as<LatticeComparison>;
// We need to be able to get the least upper bound of two elements and know
// whether any change was made.
{ elem.makeLeastUpperBound(constElem) } noexcept -> std::same_as<bool>;
// Modify `elem` in-place to be the join (aka least upper bound) of `elem` and
// `constElem`, returning true iff `elem` was modified, i.e. if it was not
// already an upper bound of `constElem`.
{ lattice.join(elem, constElem) } noexcept -> std::same_as<bool>;
};

#else // __cplusplus >= 202002L
Expand Down
13 changes: 7 additions & 6 deletions src/analysis/lattices/powerset-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,18 @@ inline size_t FiniteIntPowersetLattice::Element::count() const {
// Least upper bound is implemented as a logical OR between the bitvectors on
// both sides. We return true if a bit is flipped in-place on the left so the
// worklist algorithm will know if when to enqueue more work.
inline bool FiniteIntPowersetLattice::Element::makeLeastUpperBound(
const FiniteIntPowersetLattice::Element& other) noexcept {
inline bool
FiniteIntPowersetLattice::join(Element& self,
const Element& other) const noexcept {
// Both must be from powerset lattice of the same set.
assert(other.bitvector.size() == bitvector.size());
assert(other.bitvector.size() == self.bitvector.size());

bool modified = false;
for (size_t i = 0; i < bitvector.size(); ++i) {
for (size_t i = 0; i < self.bitvector.size(); ++i) {
// Bit is flipped on self only if self is false and other is true when self
// and other are OR'ed together.
modified |= (!bitvector[i] && other.bitvector[i]);
bitvector[i] = bitvector[i] || other.bitvector[i];
modified |= (!self.bitvector[i] && other.bitvector[i]);
self.bitvector[i] = self.bitvector[i] || other.bitvector[i];
}

return modified;
Expand Down
14 changes: 9 additions & 5 deletions src/analysis/lattices/powerset.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,6 @@ class FiniteIntPowersetLattice {
bool isTop() const { return count() == bitvector.size(); }
bool isBottom() const { return count() == 0; }

// Calculates the LUB of this element with some other element and sets
// this element to the LUB in place. Returns true if this element before
// this method call was different than the LUB.
bool makeLeastUpperBound(const Element& other) noexcept;

// Prints out the bits in the bitvector for a lattice element.
void print(std::ostream& os);

Expand All @@ -89,6 +84,11 @@ class FiniteIntPowersetLattice {

// Returns an instance of the bottom lattice element.
Element getBottom() const noexcept;

// Modifies `self` to be the join (aka least upper bound) of `self` and
// `other`. Returns true if `self` was modified, i.e. if it was not already an
// upper bound of `other`.
bool join(Element& self, const Element& other) const noexcept;
};

// A layer of abstraction over FiniteIntPowersetLattice which maps
Expand Down Expand Up @@ -149,6 +149,10 @@ template<typename T> class FinitePowersetLattice {
}

Element getBottom() const noexcept { return intLattice.getBottom(); }

bool join(Element& self, const Element& other) const noexcept {
return intLattice.join(self, other);
}
};

} // namespace wasm::analysis
Expand Down
92 changes: 46 additions & 46 deletions src/analysis/lattices/stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,51 +127,6 @@ template<Lattice L> class StackLattice {
return value;
}

// When taking the LUB, we take the LUBs of the elements of each stack
// starting from the top of the stack. So, LUB([b, a], [b', a']) is
// [LUB(b, b'), LUB(a, a')]. If one stack is higher than the other,
// the bottom of the higher stack will be kept, while the LUB of the
// corresponding tops of each stack will be taken. For instance,
// LUB([d, c, b, a], [b', a']) is [d, c, LUB(b, b'), LUB(a, a')].
//
// We start at the top because it makes taking the LUB of stacks with
// different scope easier, as mentioned at the top of the file. It also
// fits with the conception of the stack starting at the top and having
// an infinite bottom, which allows stacks of different height and scope
// to be easily joined.
bool makeLeastUpperBound(const Element& other) noexcept {
// Top element cases, since top elements don't actually have the stack
// value.
if (isTop()) {
return false;
} else if (other.isTop()) {
stackValue.reset();
return true;
}

bool modified = false;

// Merge the shorter height stack with the top of the longer height
// stack. We do this by taking the LUB of each pair of matching elements
// from both stacks.
auto otherIt = other.stackValue->crbegin();
auto thisIt = stackValue->rbegin();
for (;
thisIt != stackValue->rend() && otherIt != other.stackValue->crend();
++thisIt, ++otherIt) {
modified |= thisIt->makeLeastUpperBound(*otherIt);
}

// If the other stack is higher, append the bottom of it to our current
// stack.
for (; otherIt != other.stackValue->crend(); ++otherIt) {
stackValue->push_front(*otherIt);
modified = true;
}

return modified;
}

void print(std::ostream& os) {
if (isTop()) {
os << "TOP STACK" << std::endl;
Expand All @@ -188,6 +143,8 @@ template<Lattice L> class StackLattice {
friend StackLattice;
};

Element getBottom() const noexcept { return Element{}; }

// Like in computing the LUB, we compare the tops of the two stacks, as it
// handles the case of stacks of different scopes. Comparisons are done by
// element starting from the top.
Expand Down Expand Up @@ -258,7 +215,50 @@ template<Lattice L> class StackLattice {
}
}

Element getBottom() const noexcept { return Element{}; }
// When taking the LUB, we take the LUBs of the elements of each stack
// starting from the top of the stack. So, LUB([b, a], [b', a']) is
// [LUB(b, b'), LUB(a, a')]. If one stack is higher than the other,
// the bottom of the higher stack will be kept, while the LUB of the
// corresponding tops of each stack will be taken. For instance,
// LUB([d, c, b, a], [b', a']) is [d, c, LUB(b, b'), LUB(a, a')].
//
// We start at the top because it makes taking the LUB of stacks with
// different scope easier, as mentioned at the top of the file. It also
// fits with the conception of the stack starting at the top and having
// an infinite bottom, which allows stacks of different height and scope
// to be easily joined.
bool join(Element& self, const Element& other) const noexcept {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Seems a bit weird to call the first element "self" as it implies the instance of the StackLattice class. Wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair enough. Not sure what better to call them than a and b or something similarly unimaginative, though. Do you have ideas?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The Obj-C dev in me quite liked the previous naming of makeLeastUpperBound because it encoded mutability at the function's call site.

Since the first argument always has to match the LUB, maybe we can call it 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.

Hmm, the first argument is the LUB at the end of the method, but not necessarily at the beginning. It would be nice to have names that make sense both before and after.

Maybe joinee and joiner?

Copy link
Member Author

Choose a reason for hiding this comment

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

Will do this globally as a follow-up.

// Top element cases, since top elements don't actually have the stack
// value.
if (self.isTop()) {
return false;
} else if (other.isTop()) {
self.stackValue.reset();
return true;
}

bool modified = false;

// Merge the shorter height stack with the top of the longer height
// stack. We do this by taking the LUB of each pair of matching elements
// from both stacks.
auto selfIt = self.stackValue->rbegin();
auto otherIt = other.stackValue->crbegin();
for (; selfIt != self.stackValue->rend() &&
otherIt != other.stackValue->crend();
++selfIt, ++otherIt) {
modified |= lattice.join(*selfIt, *otherIt);
}

// If the other stack is higher, append the bottom of it to our current
// stack.
for (; otherIt != other.stackValue->crend(); ++otherIt) {
self.stackValue->push_front(*otherIt);
modified = true;
}

return modified;
}
};

} // namespace wasm::analysis
Expand Down
2 changes: 1 addition & 1 deletion src/analysis/monotone-analyzer-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ inline void MonotoneCFGAnalyzer<L, TxFn>::evaluate() {
for (auto& dep : txfn.getDependents(cfg[i])) {
// If the input state for the dependent block changes, we need to
// re-analyze it.
if (states[dep.getIndex()].makeLeastUpperBound(state)) {
if (lattice.join(states[dep.getIndex()], state)) {
worklist.push(dep.getIndex());
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/gtest/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ TEST_F(CFGTest, FinitePowersetLatticeFunctioning) {
element2.print(ss);
EXPECT_EQ(ss.str(), "100101");
ss.str(std::string());
element2.makeLeastUpperBound(element1);
lattice.join(element2, element1);
element2.print(ss);
EXPECT_EQ(ss.str(), "101101");
}
Expand Down Expand Up @@ -496,7 +496,7 @@ TEST_F(CFGTest, StackLatticeFunctioning) {
EXPECT_EQ(stackLattice.compare(thirdStack, expectedStack),
LatticeComparison::EQUAL);

EXPECT_EQ(thirdStack.makeLeastUpperBound(secondStack), true);
EXPECT_TRUE(stackLattice.join(thirdStack, secondStack));

{
expectedStack.stackTop().set(0, true);
Expand Down