Skip to content

Commit a49d33c

Browse files
tlivelyradekdoulik
authored andcommitted
[analysis] Implement an array lattice (WebAssembly#6057)
The elements of `Array<L, N>` lattice are arrays of length `N` of elements of `L`, compared pairwise with each other. This lattice is a concrete implementation of what would be written L^N with pen and paper.
1 parent 9432d40 commit a49d33c

File tree

3 files changed

+280
-9
lines changed

3 files changed

+280
-9
lines changed

src/analysis/lattices/array.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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_array_h
18+
#define wasm_analysis_lattices_array_h
19+
20+
#include <array>
21+
#include <utility>
22+
23+
#include "../lattice.h"
24+
#include "bool.h"
25+
#include "flat.h"
26+
27+
namespace wasm::analysis {
28+
29+
// A lattice whose elements are N-tuples of elements of L. Also written as L^N.
30+
template<Lattice L, size_t N> struct Array {
31+
using Element = std::array<typename L::Element, N>;
32+
33+
L lattice;
34+
35+
Array(L&& lattice) : lattice(std::move(lattice)) {}
36+
37+
private:
38+
// Use a template parameter pack to generate N copies of
39+
// `lattice.getBottom()`. TODO: Use C++20 lambda template parameters instead
40+
// of a separate helper function.
41+
template<size_t... I>
42+
Element getBottomImpl(std::index_sequence<I...>) const noexcept {
43+
return {((void)I, lattice.getBottom())...};
44+
}
45+
template<size_t... I>
46+
Element getTopImpl(std::index_sequence<I...>) const noexcept {
47+
return {((void)I, lattice.getTop())...};
48+
}
49+
50+
public:
51+
Element getBottom() const noexcept {
52+
return getBottomImpl(std::make_index_sequence<N>());
53+
}
54+
55+
Element getTop() const noexcept
56+
#if __cplusplus >= 202002L
57+
requires FullLattice<L>
58+
#endif
59+
{
60+
return getTopImpl(std::make_index_sequence<N>());
61+
}
62+
63+
// `a` <= `b` if all their elements are pairwise <=, etc. Unless we determine
64+
// that there is no relation, we must check all the elements.
65+
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
66+
auto result = EQUAL;
67+
for (size_t i = 0; i < N; ++i) {
68+
switch (lattice.compare(a[i], b[i])) {
69+
case NO_RELATION:
70+
return NO_RELATION;
71+
case EQUAL:
72+
continue;
73+
case LESS:
74+
if (result == GREATER) {
75+
// Cannot be both less and greater.
76+
return NO_RELATION;
77+
}
78+
result = LESS;
79+
continue;
80+
case GREATER:
81+
if (result == LESS) {
82+
// Cannot be both greater and less.
83+
return NO_RELATION;
84+
}
85+
result = GREATER;
86+
continue;
87+
}
88+
}
89+
return result;
90+
}
91+
92+
// Pairwise join on the elements.
93+
bool join(Element& joinee, const Element& joiner) const noexcept {
94+
bool result = false;
95+
for (size_t i = 0; i < N; ++i) {
96+
result |= lattice.join(joinee[i], joiner[i]);
97+
}
98+
return result;
99+
}
100+
101+
// Pairwise meet on the elements.
102+
bool meet(Element& meetee, const Element& meeter) const noexcept
103+
#if __cplusplus >= 202002L
104+
requires FullLattice<L>
105+
#endif
106+
{
107+
bool result = false;
108+
for (size_t i = 0; i < N; ++i) {
109+
result |= lattice.meet(meetee[i], meeter[i]);
110+
}
111+
return result;
112+
}
113+
};
114+
115+
#if __cplusplus >= 202002L
116+
static_assert(FullLattice<Array<Bool, 1>>);
117+
static_assert(Lattice<Array<Flat<bool>, 1>>);
118+
#endif
119+
120+
} // namespace wasm::analysis
121+
122+
#endif // wasm_analysis_lattices_array_h

src/tools/wasm-fuzz-lattices.cpp

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <variant>
2222

2323
#include "analysis/lattice.h"
24+
#include "analysis/lattices/array.h"
2425
#include "analysis/lattices/bool.h"
2526
#include "analysis/lattices/flat.h"
2627
#include "analysis/lattices/int.h"
@@ -147,28 +148,36 @@ static_assert(FullLattice<RandomFullLattice>);
147148
static_assert(Lattice<RandomLattice>);
148149
#endif
149150

151+
using ArrayFullLattice = analysis::Array<RandomFullLattice, 2>;
152+
using ArrayLattice = analysis::Array<RandomLattice, 2>;
153+
150154
struct RandomFullLattice::L
151-
: std::variant<Bool, UInt32, Inverted<RandomFullLattice>> {};
155+
: std::variant<Bool, UInt32, Inverted<RandomFullLattice>, ArrayFullLattice> {
156+
};
152157

153158
struct RandomFullLattice::ElementImpl
154159
: std::variant<typename Bool::Element,
155160
typename UInt32::Element,
156-
typename Inverted<RandomFullLattice>::Element> {};
161+
typename Inverted<RandomFullLattice>::Element,
162+
typename ArrayFullLattice::Element> {};
157163

158-
struct RandomLattice::L
159-
: std::variant<RandomFullLattice, Flat<uint32_t>, Lift<RandomLattice>> {};
164+
struct RandomLattice::L : std::variant<RandomFullLattice,
165+
Flat<uint32_t>,
166+
Lift<RandomLattice>,
167+
ArrayLattice> {};
160168

161169
struct RandomLattice::ElementImpl
162170
: std::variant<typename RandomFullLattice::Element,
163171
typename Flat<uint32_t>::Element,
164-
typename Lift<RandomLattice>::Element> {};
172+
typename Lift<RandomLattice>::Element,
173+
typename ArrayLattice::Element> {};
165174

166175
RandomFullLattice::RandomFullLattice(Random& rand,
167176
size_t depth,
168177
std::optional<uint32_t> maybePick)
169178
: rand(rand) {
170179
// TODO: Limit the depth once we get lattices with more fan-out.
171-
uint32_t pick = maybePick ? *maybePick : rand.upTo(3);
180+
uint32_t pick = maybePick ? *maybePick : rand.upTo(4);
172181
switch (pick) {
173182
case 0:
174183
lattice = std::make_unique<L>(L{Bool{}});
@@ -180,25 +189,34 @@ RandomFullLattice::RandomFullLattice(Random& rand,
180189
lattice =
181190
std::make_unique<L>(L{Inverted{RandomFullLattice{rand, depth + 1}}});
182191
return;
192+
case 3:
193+
lattice = std::make_unique<L>(
194+
L{ArrayFullLattice{RandomFullLattice{rand, depth + 1}}});
195+
return;
183196
}
184197
WASM_UNREACHABLE("unexpected pick");
185198
}
186199

187200
RandomLattice::RandomLattice(Random& rand, size_t depth) : rand(rand) {
188201
// TODO: Limit the depth once we get lattices with more fan-out.
189-
uint32_t pick = rand.upTo(5);
202+
uint32_t pick = rand.upTo(7);
190203
switch (pick) {
191204
case 0:
192205
case 1:
193206
case 2:
207+
case 3:
194208
lattice = std::make_unique<L>(L{RandomFullLattice{rand, depth, pick}});
195209
return;
196-
case 3:
210+
case 4:
197211
lattice = std::make_unique<L>(L{Flat<uint32_t>{}});
198212
return;
199-
case 4:
213+
case 5:
200214
lattice = std::make_unique<L>(L{Lift{RandomLattice{rand, depth + 1}}});
201215
return;
216+
case 6:
217+
lattice =
218+
std::make_unique<L>(L{ArrayLattice{RandomLattice{rand, depth + 1}}});
219+
return;
202220
}
203221
WASM_UNREACHABLE("unexpected pick");
204222
}
@@ -213,6 +231,10 @@ RandomFullLattice::Element RandomFullLattice::makeElement() const noexcept {
213231
if (const auto* l = std::get_if<Inverted<RandomFullLattice>>(lattice.get())) {
214232
return ElementImpl{l->lattice.makeElement()};
215233
}
234+
if (const auto* l = std::get_if<ArrayFullLattice>(lattice.get())) {
235+
return ElementImpl{typename ArrayFullLattice::Element{
236+
l->lattice.makeElement(), l->lattice.makeElement()}};
237+
}
216238
WASM_UNREACHABLE("unexpected lattice");
217239
}
218240

@@ -235,6 +257,10 @@ RandomLattice::Element RandomLattice::makeElement() const noexcept {
235257
return ElementImpl{rand.oneIn(4) ? l->getBottom()
236258
: l->get(l->lattice.makeElement())};
237259
}
260+
if (const auto* l = std::get_if<ArrayLattice>(lattice.get())) {
261+
return ElementImpl{typename ArrayLattice::Element{
262+
l->lattice.makeElement(), l->lattice.makeElement()}};
263+
}
238264
WASM_UNREACHABLE("unexpected lattice");
239265
}
240266

@@ -260,6 +286,13 @@ void printFullElement(std::ostream& os,
260286
printFullElement(os, *e, depth + 1);
261287
indent(os, depth);
262288
os << ")\n";
289+
} else if (const auto* e =
290+
std::get_if<typename ArrayFullLattice::Element>(&*elem)) {
291+
os << "Array[\n";
292+
printFullElement(os, e->front(), depth + 1);
293+
printFullElement(os, e->back(), depth + 1);
294+
indent(os, depth);
295+
os << "]\n";
263296
}
264297
}
265298

@@ -292,6 +325,13 @@ void printElement(std::ostream& os,
292325
indent(os, depth);
293326
os << ")\n";
294327
}
328+
} else if (const auto* e =
329+
std::get_if<typename ArrayLattice::Element>(&*elem)) {
330+
os << "Array[\n";
331+
printElement(os, e->front(), depth + 1);
332+
printElement(os, e->back(), depth + 1);
333+
indent(os, depth);
334+
os << ")\n";
295335
}
296336
}
297337

test/gtest/lattices.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include "analysis/lattices/array.h"
1718
#include "analysis/lattices/bool.h"
1819
#include "analysis/lattices/flat.h"
1920
#include "analysis/lattices/int.h"
@@ -374,3 +375,111 @@ TEST(LiftLattice, Join) {
374375
EXPECT_FALSE(lift.join(elem, bot));
375376
EXPECT_EQ(elem, top);
376377
}
378+
379+
TEST(ArrayLattice, GetBottom) {
380+
analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
381+
EXPECT_EQ(array.getBottom(), (std::array<bool, 2>{false, false}));
382+
}
383+
384+
TEST(ArrayLattice, GetTop) {
385+
analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
386+
EXPECT_EQ(array.getTop(), (std::array<bool, 2>{true, true}));
387+
}
388+
389+
TEST(ArrayLattice, Compare) {
390+
analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
391+
std::array<bool, 2> ff{false, false};
392+
std::array<bool, 2> ft{false, true};
393+
std::array<bool, 2> tf{true, false};
394+
std::array<bool, 2> tt{true, true};
395+
396+
EXPECT_EQ(array.compare(ff, ff), analysis::EQUAL);
397+
EXPECT_EQ(array.compare(ff, ft), analysis::LESS);
398+
EXPECT_EQ(array.compare(ff, tf), analysis::LESS);
399+
EXPECT_EQ(array.compare(ff, tt), analysis::LESS);
400+
401+
EXPECT_EQ(array.compare(ft, ff), analysis::GREATER);
402+
EXPECT_EQ(array.compare(ft, ft), analysis::EQUAL);
403+
EXPECT_EQ(array.compare(ft, tf), analysis::NO_RELATION);
404+
EXPECT_EQ(array.compare(ft, tt), analysis::LESS);
405+
406+
EXPECT_EQ(array.compare(tf, ff), analysis::GREATER);
407+
EXPECT_EQ(array.compare(tf, ft), analysis::NO_RELATION);
408+
EXPECT_EQ(array.compare(tf, tf), analysis::EQUAL);
409+
EXPECT_EQ(array.compare(tf, tt), analysis::LESS);
410+
411+
EXPECT_EQ(array.compare(tt, ff), analysis::GREATER);
412+
EXPECT_EQ(array.compare(tt, ft), analysis::GREATER);
413+
EXPECT_EQ(array.compare(tt, tf), analysis::GREATER);
414+
EXPECT_EQ(array.compare(tt, tt), analysis::EQUAL);
415+
}
416+
417+
TEST(ArrayLattice, Join) {
418+
analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
419+
auto ff = []() { return std::array<bool, 2>{false, false}; };
420+
auto ft = []() { return std::array<bool, 2>{false, true}; };
421+
auto tf = []() { return std::array<bool, 2>{true, false}; };
422+
auto tt = []() { return std::array<bool, 2>{true, true}; };
423+
424+
auto test =
425+
[&](auto& makeJoinee, auto& makeJoiner, bool modified, auto& makeExpected) {
426+
auto joinee = makeJoinee();
427+
EXPECT_EQ(array.join(joinee, makeJoiner()), modified);
428+
EXPECT_EQ(joinee, makeExpected());
429+
};
430+
431+
test(ff, ff, false, ff);
432+
test(ff, ft, true, ft);
433+
test(ff, tf, true, tf);
434+
test(ff, tt, true, tt);
435+
436+
test(ft, ff, false, ft);
437+
test(ft, ft, false, ft);
438+
test(ft, tf, true, tt);
439+
test(ft, tt, true, tt);
440+
441+
test(tf, ff, false, tf);
442+
test(tf, ft, true, tt);
443+
test(tf, tf, false, tf);
444+
test(tf, tt, true, tt);
445+
446+
test(tt, ff, false, tt);
447+
test(tt, ft, false, tt);
448+
test(tt, tf, false, tt);
449+
test(tt, tt, false, tt);
450+
}
451+
452+
TEST(ArrayLattice, Meet) {
453+
analysis::Array<analysis::Bool, 2> array{analysis::Bool{}};
454+
auto ff = []() { return std::array<bool, 2>{false, false}; };
455+
auto ft = []() { return std::array<bool, 2>{false, true}; };
456+
auto tf = []() { return std::array<bool, 2>{true, false}; };
457+
auto tt = []() { return std::array<bool, 2>{true, true}; };
458+
459+
auto test =
460+
[&](auto& makeMeetee, auto& makeMeeter, bool modified, auto& makeExpected) {
461+
auto meetee = makeMeetee();
462+
EXPECT_EQ(array.meet(meetee, makeMeeter()), modified);
463+
EXPECT_EQ(meetee, makeExpected());
464+
};
465+
466+
test(ff, ff, false, ff);
467+
test(ff, ft, false, ff);
468+
test(ff, tf, false, ff);
469+
test(ff, tt, false, ff);
470+
471+
test(ft, ff, true, ff);
472+
test(ft, ft, false, ft);
473+
test(ft, tf, true, ff);
474+
test(ft, tt, false, ft);
475+
476+
test(tf, ff, true, ff);
477+
test(tf, ft, true, ff);
478+
test(tf, tf, false, tf);
479+
test(tf, tt, false, tf);
480+
481+
test(tt, ff, true, ff);
482+
test(tt, ft, true, ft);
483+
test(tt, tf, true, tf);
484+
test(tt, tt, false, tt);
485+
}

0 commit comments

Comments
 (0)