Skip to content

Commit c5a6478

Browse files
committed
[analysis] Implement a vector lattice
The vector lattice is nearly identical to the array lattice, except that the size of the elements is specified at runtime when the lattice object is created rather than at compile time. The code and tests are largely copy-pasted and fixed up from the array implementation, but there are a couple differences. First, initializing vector elements does not need the template magic used to initialize array elements. Second, the obvious implementations of join and meet do not work for vectors of bools because they might be specialized to be bit vectors, so we need workarounds for that particular case.
1 parent f62c7aa commit c5a6478

File tree

4 files changed

+310
-11
lines changed

4 files changed

+310
-11
lines changed

src/analysis/lattices/array.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
namespace wasm::analysis {
2828

2929
// A lattice whose elements are N-tuples of elements of L. Also written as L^N.
30+
// N is supplied at compile time rather than run time like it is for Vector.
3031
template<Lattice L, size_t N> struct Array {
3132
using Element = std::array<typename L::Element, N>;
3233

src/analysis/lattices/vector.h

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright 2023 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_analysis_lattices_vector_h
18+
#define wasm_analysis_lattices_vector_h
19+
20+
#include <vector>
21+
22+
#include "../lattice.h"
23+
#include "bool.h"
24+
#include "flat.h"
25+
26+
namespace wasm::analysis {
27+
28+
// A lattice whose elements are N-tuples of elements of L. Also written as L^N.
29+
// N is supplied at run time rather than compile time like it is for Array.
30+
template<Lattice L> struct Vector {
31+
using Element = std::vector<typename L::Element>;
32+
33+
L lattice;
34+
const size_t size;
35+
36+
Vector(L&& lattice, size_t size) : lattice(std::move(lattice)), size(size) {}
37+
38+
Element getBottom() const noexcept {
39+
return Element(size, lattice.getBottom());
40+
}
41+
42+
Element getTop() const noexcept
43+
#if __cplusplus >= 202002L
44+
requires FullLattice<L>
45+
#endif
46+
{
47+
return Element(size, lattice.getTop());
48+
}
49+
50+
// `a` <= `b` if their elements are pairwise <=, etc.
51+
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
52+
assert(a.size() == size);
53+
assert(b.size() == size);
54+
auto result = EQUAL;
55+
for (size_t i = 0; i < size; ++i) {
56+
switch (lattice.compare(a[i], b[i])) {
57+
case NO_RELATION:
58+
return NO_RELATION;
59+
case EQUAL:
60+
continue;
61+
case LESS:
62+
if (result == GREATER) {
63+
// Cannot be both less and greater.
64+
return NO_RELATION;
65+
}
66+
result = LESS;
67+
continue;
68+
case GREATER:
69+
if (result == LESS) {
70+
// Cannot be both greater and less.
71+
return NO_RELATION;
72+
}
73+
result = GREATER;
74+
continue;
75+
}
76+
}
77+
return result;
78+
}
79+
80+
// Pairwise join on the elements.
81+
bool join(Element& joinee, const Element& joiner) const noexcept {
82+
assert(joinee.size() == size);
83+
assert(joiner.size() == size);
84+
bool result = false;
85+
for (size_t i = 0; i < size; ++i) {
86+
if constexpr (std::is_same_v<typename L::Element, bool>) {
87+
// The vector<bool> specialization does not expose references to the
88+
// individual bools because they might be in a bitmap, so we need a
89+
// workaround.
90+
bool e = joinee[i];
91+
if (lattice.join(e, joiner[i])) {
92+
joinee[i] = e;
93+
result = true;
94+
}
95+
} else {
96+
result |= lattice.join(joinee[i], joiner[i]);
97+
}
98+
}
99+
100+
return result;
101+
}
102+
103+
// Pairwise meet on the elements.
104+
bool meet(Element& meetee, const Element& meeter) const noexcept
105+
#if __cplusplus >= 202002L
106+
requires FullLattice<L>
107+
#endif
108+
{
109+
assert(meetee.size() == size);
110+
assert(meeter.size() == size);
111+
bool result = false;
112+
for (size_t i = 0; i < size; ++i) {
113+
if constexpr (std::is_same_v<typename L::Element, bool>) {
114+
// The vector<bool> specialization does not expose references to the
115+
// individual bools because they might be in a bitmap, so we need a
116+
// workaround.
117+
bool e = meetee[i];
118+
if (lattice.meet(e, meeter[i])) {
119+
meetee[i] = e;
120+
result = true;
121+
}
122+
} else {
123+
result |= lattice.meet(meetee[i], meeter[i]);
124+
}
125+
}
126+
return result;
127+
}
128+
};
129+
130+
#if __cplusplus >= 202002L
131+
static_assert(FullLattice<Vector<Bool>>);
132+
static_assert(Lattice<Vector<Flat<bool>>>);
133+
#endif
134+
135+
} // namespace wasm::analysis
136+
137+
#endif // wasm_analysis_lattices_vector_h

src/tools/wasm-fuzz-lattices.cpp

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "analysis/lattices/inverted.h"
2929
#include "analysis/lattices/lift.h"
3030
#include "analysis/lattices/stack.h"
31+
#include "analysis/lattices/vector.h"
3132
#include "analysis/liveness-transfer-function.h"
3233
#include "analysis/reaching-definitions-transfer-function.h"
3334
#include "analysis/transfer-function.h"
@@ -151,33 +152,38 @@ static_assert(Lattice<RandomLattice>);
151152
using ArrayFullLattice = analysis::Array<RandomFullLattice, 2>;
152153
using ArrayLattice = analysis::Array<RandomLattice, 2>;
153154

154-
struct RandomFullLattice::L
155-
: std::variant<Bool, UInt32, Inverted<RandomFullLattice>, ArrayFullLattice> {
156-
};
155+
struct RandomFullLattice::L : std::variant<Bool,
156+
UInt32,
157+
Inverted<RandomFullLattice>,
158+
ArrayFullLattice,
159+
Vector<RandomFullLattice>> {};
157160

158161
struct RandomFullLattice::ElementImpl
159162
: std::variant<typename Bool::Element,
160163
typename UInt32::Element,
161164
typename Inverted<RandomFullLattice>::Element,
162-
typename ArrayFullLattice::Element> {};
165+
typename ArrayFullLattice::Element,
166+
typename Vector<RandomFullLattice>::Element> {};
163167

164168
struct RandomLattice::L : std::variant<RandomFullLattice,
165169
Flat<uint32_t>,
166170
Lift<RandomLattice>,
167-
ArrayLattice> {};
171+
ArrayLattice,
172+
Vector<RandomLattice>> {};
168173

169174
struct RandomLattice::ElementImpl
170175
: std::variant<typename RandomFullLattice::Element,
171176
typename Flat<uint32_t>::Element,
172177
typename Lift<RandomLattice>::Element,
173-
typename ArrayLattice::Element> {};
178+
typename ArrayLattice::Element,
179+
typename Vector<RandomLattice>::Element> {};
174180

175181
RandomFullLattice::RandomFullLattice(Random& rand,
176182
size_t depth,
177183
std::optional<uint32_t> maybePick)
178184
: rand(rand) {
179185
// TODO: Limit the depth once we get lattices with more fan-out.
180-
uint32_t pick = maybePick ? *maybePick : rand.upTo(4);
186+
uint32_t pick = maybePick ? *maybePick : rand.upTo(5);
181187
switch (pick) {
182188
case 0:
183189
lattice = std::make_unique<L>(L{Bool{}});
@@ -193,30 +199,39 @@ RandomFullLattice::RandomFullLattice(Random& rand,
193199
lattice = std::make_unique<L>(
194200
L{ArrayFullLattice{RandomFullLattice{rand, depth + 1}}});
195201
return;
202+
case 4:
203+
lattice = std::make_unique<L>(
204+
L{Vector{RandomFullLattice{rand, depth + 1}, rand.upTo(4)}});
205+
return;
196206
}
197207
WASM_UNREACHABLE("unexpected pick");
198208
}
199209

200210
RandomLattice::RandomLattice(Random& rand, size_t depth) : rand(rand) {
201211
// TODO: Limit the depth once we get lattices with more fan-out.
202-
uint32_t pick = rand.upTo(7);
212+
uint32_t pick = rand.upTo(9);
203213
switch (pick) {
204214
case 0:
205215
case 1:
206216
case 2:
207217
case 3:
218+
case 4:
208219
lattice = std::make_unique<L>(L{RandomFullLattice{rand, depth, pick}});
209220
return;
210-
case 4:
221+
case 5:
211222
lattice = std::make_unique<L>(L{Flat<uint32_t>{}});
212223
return;
213-
case 5:
224+
case 6:
214225
lattice = std::make_unique<L>(L{Lift{RandomLattice{rand, depth + 1}}});
215226
return;
216-
case 6:
227+
case 7:
217228
lattice =
218229
std::make_unique<L>(L{ArrayLattice{RandomLattice{rand, depth + 1}}});
219230
return;
231+
case 8:
232+
lattice = std::make_unique<L>(
233+
L{Vector{RandomLattice{rand, depth + 1}, rand.upTo(4)}});
234+
return;
220235
}
221236
WASM_UNREACHABLE("unexpected pick");
222237
}
@@ -235,6 +250,14 @@ RandomFullLattice::Element RandomFullLattice::makeElement() const noexcept {
235250
return ElementImpl{typename ArrayFullLattice::Element{
236251
l->lattice.makeElement(), l->lattice.makeElement()}};
237252
}
253+
if (const auto* l = std::get_if<Vector<RandomFullLattice>>(lattice.get())) {
254+
std::vector<typename RandomFullLattice::Element> elem;
255+
elem.reserve(l->size);
256+
for (size_t i = 0; i < l->size; ++i) {
257+
elem.push_back(l->lattice.makeElement());
258+
}
259+
return ElementImpl{std::move(elem)};
260+
}
238261
WASM_UNREACHABLE("unexpected lattice");
239262
}
240263

@@ -261,6 +284,14 @@ RandomLattice::Element RandomLattice::makeElement() const noexcept {
261284
return ElementImpl{typename ArrayLattice::Element{
262285
l->lattice.makeElement(), l->lattice.makeElement()}};
263286
}
287+
if (const auto* l = std::get_if<Vector<RandomLattice>>(lattice.get())) {
288+
std::vector<typename RandomLattice::Element> elem;
289+
elem.reserve(l->size);
290+
for (size_t i = 0; i < l->size; ++i) {
291+
elem.push_back(l->lattice.makeElement());
292+
}
293+
return ElementImpl{std::move(elem)};
294+
}
264295
WASM_UNREACHABLE("unexpected lattice");
265296
}
266297

@@ -293,6 +324,17 @@ void printFullElement(std::ostream& os,
293324
printFullElement(os, e->back(), depth + 1);
294325
indent(os, depth);
295326
os << "]\n";
327+
} else if (const auto* vec =
328+
std::get_if<typename Vector<RandomFullLattice>::Element>(
329+
&*elem)) {
330+
os << "Vector[\n";
331+
for (const auto& e : *vec) {
332+
printFullElement(os, e, depth + 1);
333+
}
334+
indent(os, depth);
335+
os << "]\n";
336+
} else {
337+
WASM_UNREACHABLE("unexpected element");
296338
}
297339
}
298340

@@ -332,6 +374,16 @@ void printElement(std::ostream& os,
332374
printElement(os, e->back(), depth + 1);
333375
indent(os, depth);
334376
os << ")\n";
377+
} else if (const auto* vec =
378+
std::get_if<typename Vector<RandomLattice>::Element>(&*elem)) {
379+
os << "Vector[\n";
380+
for (const auto& e : *vec) {
381+
printElement(os, e, depth + 1);
382+
}
383+
indent(os, depth);
384+
os << "]\n";
385+
} else {
386+
WASM_UNREACHABLE("unexpected element");
335387
}
336388
}
337389

0 commit comments

Comments
 (0)