Skip to content

Commit 8a7936d

Browse files
committed
[analysis] Allow joining a single vector element efficiently
Previously, modifying a single vector element of a `Shared<Vector>` element required materializing a full vector to do the join. When there is just a single element to update, materializing all the other elements with bottom value is useless work. Add a `Vector<L>::SingletonElement` utility that represents but does not materialize a vector with a single non-bottom element and allow it to be passed to `Vector<L>::join`. Also update `Shared` and `Inverted` so that `SingletonElement` joins still work on vectors wrapped in those other lattices.
1 parent 350ba10 commit 8a7936d

File tree

4 files changed

+111
-40
lines changed

4 files changed

+111
-40
lines changed

src/analysis/lattices/inverted.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,14 @@ template<FullLattice L> struct Inverted {
3737
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
3838
return lattice.compare(b, a);
3939
}
40-
bool join(Element& joinee, Element joiner) const noexcept {
40+
41+
template<typename Elem>
42+
bool join(Element& joinee, const Elem& joiner) const noexcept {
4143
return lattice.meet(joinee, joiner);
4244
}
43-
bool meet(Element& meetee, Element meeter) const noexcept {
45+
46+
template<typename Elem>
47+
bool meet(Element& meetee, const Elem& meeter) const noexcept {
4448
return lattice.join(meetee, meeter);
4549
}
4650
};

src/analysis/lattices/shared.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ template<Lattice L> struct Shared {
106106
return false;
107107
}
108108

109-
bool join(Element& joinee, const typename L::Element& joiner) const noexcept {
109+
template<typename Elem>
110+
bool join(Element& joinee, const Elem& joiner) const noexcept {
110111
if (lattice.join(val, joiner)) {
111112
// We have moved to the next value in our ascending chain. Assign it a new
112113
// sequence number and update joinee with that sequence number.

src/analysis/lattices/vector.h

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ namespace wasm::analysis {
3030
template<Lattice L> struct Vector {
3131
using Element = std::vector<typename L::Element>;
3232

33+
// Represent a vector in which all but one of the elements are bottom without
34+
// materializing the full vector.
35+
struct SingletonElement : std::pair<size_t, typename L::Element> {
36+
SingletonElement(size_t i, typename L::Element&& elem)
37+
: std::pair<size_t, typename L::Element>{i, std::move(elem)} {}
38+
};
39+
3340
L lattice;
3441
const size_t size;
3542

@@ -39,13 +46,7 @@ template<Lattice L> struct Vector {
3946
return Element(size, lattice.getBottom());
4047
}
4148

42-
Element getTop() const noexcept
43-
#if __cplusplus >= 202002L
44-
requires FullLattice<L>
45-
#endif
46-
{
47-
return Element(size, lattice.getTop());
48-
}
49+
Element getTop() const noexcept { return Element(size, lattice.getTop()); }
4950

5051
// `a` <= `b` if their elements are pairwise <=, etc. Unless we determine
5152
// that there is no relation, we must check all the elements.
@@ -84,48 +85,70 @@ template<Lattice L> struct Vector {
8485
assert(joiner.size() == size);
8586
bool result = false;
8687
for (size_t i = 0; i < size; ++i) {
87-
if constexpr (std::is_same_v<typename L::Element, bool>) {
88-
// The vector<bool> specialization does not expose references to the
89-
// individual bools because they might be in a bitmap, so we need a
90-
// workaround.
91-
bool e = joinee[i];
92-
if (lattice.join(e, joiner[i])) {
93-
joinee[i] = e;
94-
result = true;
95-
}
96-
} else {
97-
result |= lattice.join(joinee[i], joiner[i]);
98-
}
88+
result |= joinAtIndex(joinee, i, joiner[i]);
9989
}
100-
10190
return result;
10291
}
10392

93+
bool join(Element& joinee, const SingletonElement& joiner) const noexcept {
94+
const auto& [index, elem] = joiner;
95+
assert(index < joinee.size());
96+
return joinAtIndex(joinee, index, elem);
97+
}
98+
10499
// Pairwise meet on the elements.
105-
bool meet(Element& meetee, const Element& meeter) const noexcept
106-
#if __cplusplus >= 202002L
107-
requires FullLattice<L>
108-
#endif
109-
{
100+
bool meet(Element& meetee, const Element& meeter) const noexcept {
110101
assert(meetee.size() == size);
111102
assert(meeter.size() == size);
112103
bool result = false;
113104
for (size_t i = 0; i < size; ++i) {
114-
if constexpr (std::is_same_v<typename L::Element, bool>) {
115-
// The vector<bool> specialization does not expose references to the
116-
// individual bools because they might be in a bitmap, so we need a
117-
// workaround.
118-
bool e = meetee[i];
119-
if (lattice.meet(e, meeter[i])) {
120-
meetee[i] = e;
121-
result = true;
122-
}
123-
} else {
124-
result |= lattice.meet(meetee[i], meeter[i]);
125-
}
105+
result |= meetAtIndex(meetee, i, meeter[i]);
126106
}
127107
return result;
128108
}
109+
110+
bool meet(Element& meetee, const SingletonElement& meeter) const noexcept {
111+
const auto& [index, elem] = meeter;
112+
assert(index < meetee.size());
113+
return meetAtIndex(meetee, index, elem);
114+
}
115+
116+
private:
117+
bool joinAtIndex(Element& joinee,
118+
size_t i,
119+
const typename L::Element& elem) const noexcept {
120+
if constexpr (std::is_same_v<typename L::Element, bool>) {
121+
// The vector<bool> specialization does not expose references to the
122+
// individual bools because they might be in a bitmap, so we need a
123+
// workaround.
124+
bool e = joinee[i];
125+
if (lattice.join(e, elem)) {
126+
joinee[i] = e;
127+
return true;
128+
}
129+
return false;
130+
} else {
131+
return lattice.join(joinee[i], elem);
132+
}
133+
}
134+
135+
bool meetAtIndex(Element& meetee,
136+
size_t i,
137+
const typename L::Element& elem) const noexcept {
138+
if constexpr (std::is_same_v<typename L::Element, bool>) {
139+
// The vector<bool> specialization does not expose references to the
140+
// individual bools because they might be in a bitmap, so we need a
141+
// workaround.
142+
bool e = meetee[i];
143+
if (lattice.meet(e, elem)) {
144+
meetee[i] = e;
145+
return true;
146+
}
147+
return false;
148+
} else {
149+
return lattice.meet(meetee[i], elem);
150+
}
151+
}
129152
};
130153

131154
#if __cplusplus >= 202002L

test/gtest/lattices.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,30 @@ TEST(VectorLattice, Meet) {
478478
vector, {false, false}, {false, true}, {true, false}, {true, true});
479479
}
480480

481+
TEST(VectorLattice, JoinSingleton) {
482+
using Vec = analysis::Vector<analysis::Bool>;
483+
Vec vector{analysis::Bool{}, 2};
484+
auto elem = vector.getBottom();
485+
486+
EXPECT_FALSE(vector.join(elem, Vec::SingletonElement(0, false)));
487+
EXPECT_EQ(elem, (std::vector{false, false}));
488+
489+
EXPECT_TRUE(vector.join(elem, Vec::SingletonElement(1, true)));
490+
EXPECT_EQ(elem, (std::vector{false, true}));
491+
}
492+
493+
TEST(VectorLattice, MeetSingleton) {
494+
using Vec = analysis::Vector<analysis::Bool>;
495+
Vec vector{analysis::Bool{}, 2};
496+
auto elem = vector.getTop();
497+
498+
EXPECT_FALSE(vector.meet(elem, Vec::SingletonElement(1, true)));
499+
EXPECT_EQ(elem, (std::vector{true, true}));
500+
501+
EXPECT_TRUE(vector.meet(elem, Vec::SingletonElement(0, false)));
502+
EXPECT_EQ(elem, (std::vector{false, true}));
503+
}
504+
481505
TEST(TupleLattice, GetBottom) {
482506
analysis::Tuple<analysis::Bool, analysis::UInt32> tuple{analysis::Bool{},
483507
analysis::UInt32{}};
@@ -656,6 +680,25 @@ TEST(SharedLattice, Join) {
656680
}
657681
}
658682

683+
TEST(SharedLattice, JoinVecSingleton) {
684+
using Vec = analysis::Vector<analysis::Bool>;
685+
analysis::Shared<Vec> shared{analysis::Vector{analysis::Bool{}, 2}};
686+
687+
auto elem = shared.getBottom();
688+
EXPECT_TRUE(shared.join(elem, Vec::SingletonElement(1, true)));
689+
EXPECT_EQ(*elem, (std::vector{false, true}));
690+
}
691+
692+
TEST(SharedLattice, JoinInvertedVecSingleton) {
693+
using Vec = analysis::Vector<analysis::Bool>;
694+
analysis::Shared<analysis::Inverted<Vec>> shared{
695+
analysis::Inverted{analysis::Vector{analysis::Bool{}, 2}}};
696+
697+
auto elem = shared.getBottom();
698+
EXPECT_TRUE(shared.join(elem, Vec::SingletonElement(1, false)));
699+
EXPECT_EQ(*elem, (std::vector{true, false}));
700+
}
701+
659702
TEST(StackLattice, GetBottom) {
660703
analysis::Stack stack{analysis::Flat<uint32_t>{}};
661704
EXPECT_EQ(stack.getBottom().size(), 0u);

0 commit comments

Comments
 (0)