Skip to content

Commit 33139e2

Browse files
committed
[analysis] Add a lattice for value types
Add a lattice that is a thin wrapper around `wasm::Type` giving it the interface of a lattice. As usual, `Type::unreachable` is the bottom element, but unlike in the underlying API, we uniformly treat `Type::none` as the top type so that we have a proper lattice.
1 parent fad0698 commit 33139e2

File tree

3 files changed

+245
-6
lines changed

3 files changed

+245
-6
lines changed

src/analysis/lattices/valtype.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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_valtype_h
18+
#define wasm_analysis_lattices_valtype_h
19+
20+
#include "../lattice.h"
21+
#include "wasm-type.h"
22+
23+
namespace wasm::analysis {
24+
25+
// Thin wrapper around `wasm::Type` giving it the interface of a lattice. As
26+
// usual, `Type::unreachable` is the bottom element, but unlike in the
27+
// underlying API, we uniformly treat `Type::none` as the top type so that we
28+
// have a proper lattice.
29+
struct ValType {
30+
using Element = Type;
31+
32+
Element getBottom() const noexcept { return Type::unreachable; }
33+
34+
Element getTop() const noexcept { return Type::none; }
35+
36+
LatticeComparison compare(Element a, Element b) const noexcept {
37+
if (a == b) {
38+
return EQUAL;
39+
}
40+
if (b == Type::none || Type::isSubType(a, b)) {
41+
return LESS;
42+
}
43+
if (a == Type::none || Type::isSubType(b, a)) {
44+
return GREATER;
45+
}
46+
return NO_RELATION;
47+
}
48+
49+
bool join(Element& joinee, Element joiner) const noexcept {
50+
// `getLeastUpperBound` already treats `Type::none` as top.
51+
auto lub = Type::getLeastUpperBound(joinee, joiner);
52+
if (lub != joinee) {
53+
joinee = lub;
54+
return true;
55+
}
56+
return false;
57+
}
58+
59+
bool meet(Element& meetee, Element meeter) const noexcept {
60+
if (meetee == meeter || meeter == Type::none) {
61+
return false;
62+
}
63+
if (meetee == Type::none) {
64+
meetee = meeter;
65+
return true;
66+
}
67+
auto glb = Type::getGreatestLowerBound(meetee, meeter);
68+
if (glb != meetee) {
69+
meetee = glb;
70+
return true;
71+
}
72+
return false;
73+
}
74+
};
75+
76+
#if __cplusplus >= 202002L
77+
static_assert(FullLattice<ValType>);
78+
#endif
79+
80+
} // namespace wasm::analysis
81+
82+
#endif // wasm_analysis_lattices_valtype_h

src/tools/wasm-fuzz-lattices.cpp

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "analysis/lattices/lift.h"
3030
#include "analysis/lattices/stack.h"
3131
#include "analysis/lattices/tuple.h"
32+
#include "analysis/lattices/valtype.h"
3233
#include "analysis/lattices/vector.h"
3334
#include "analysis/liveness-transfer-function.h"
3435
#include "analysis/reaching-definitions-transfer-function.h"
@@ -158,6 +159,7 @@ using TupleLattice = analysis::Tuple<RandomLattice, RandomLattice>;
158159

159160
struct RandomFullLattice::L : std::variant<Bool,
160161
UInt32,
162+
ValType,
161163
Inverted<RandomFullLattice>,
162164
ArrayFullLattice,
163165
Vector<RandomFullLattice>,
@@ -166,6 +168,7 @@ struct RandomFullLattice::L : std::variant<Bool,
166168
struct RandomFullLattice::ElementImpl
167169
: std::variant<typename Bool::Element,
168170
typename UInt32::Element,
171+
typename ValType::Element,
169172
typename Inverted<RandomFullLattice>::Element,
170173
typename ArrayFullLattice::Element,
171174
typename Vector<RandomFullLattice>::Element,
@@ -186,7 +189,7 @@ struct RandomLattice::ElementImpl
186189
typename Vector<RandomLattice>::Element,
187190
typename TupleLattice::Element> {};
188191

189-
constexpr int FullLatticePicks = 6;
192+
constexpr int FullLatticePicks = 7;
190193

191194
RandomFullLattice::RandomFullLattice(Random& rand,
192195
size_t depth,
@@ -202,18 +205,21 @@ RandomFullLattice::RandomFullLattice(Random& rand,
202205
lattice = std::make_unique<L>(L{UInt32{}});
203206
return;
204207
case 2:
208+
lattice = std::make_unique<L>(L{ValType{}});
209+
return;
210+
case 3:
205211
lattice =
206212
std::make_unique<L>(L{Inverted{RandomFullLattice{rand, depth + 1}}});
207213
return;
208-
case 3:
214+
case 4:
209215
lattice = std::make_unique<L>(
210216
L{ArrayFullLattice{RandomFullLattice{rand, depth + 1}}});
211217
return;
212-
case 4:
218+
case 5:
213219
lattice = std::make_unique<L>(
214220
L{Vector{RandomFullLattice{rand, depth + 1}, rand.upTo(4)}});
215221
return;
216-
case 5:
222+
case 6:
217223
lattice = std::make_unique<L>(
218224
L{TupleFullLattice{RandomFullLattice{rand, depth + 1},
219225
RandomFullLattice{rand, depth + 1}}});
@@ -261,6 +267,36 @@ RandomFullLattice::Element RandomFullLattice::makeElement() const noexcept {
261267
if (std::get_if<UInt32>(lattice.get())) {
262268
return ElementImpl{rand.upToSquared(33)};
263269
}
270+
if (std::get_if<ValType>(lattice.get())) {
271+
Type type;
272+
switch (rand.upTo(8)) {
273+
case 0:
274+
type = Type::unreachable;
275+
break;
276+
case 1:
277+
type = Type::none;
278+
break;
279+
case 2:
280+
type = Type::i32;
281+
break;
282+
case 3:
283+
type = Type::f32;
284+
break;
285+
case 4:
286+
type = Type(HeapType::any, rand.oneIn(2) ? Nullable : NonNullable);
287+
break;
288+
case 5:
289+
type = Type(HeapType::none, rand.oneIn(2) ? Nullable : NonNullable);
290+
break;
291+
case 6:
292+
type = Type(HeapType::struct_, rand.oneIn(2) ? Nullable : NonNullable);
293+
break;
294+
case 7:
295+
type = Type(HeapType::array, rand.oneIn(2) ? Nullable : NonNullable);
296+
break;
297+
}
298+
return ElementImpl{type};
299+
}
264300
if (const auto* l = std::get_if<Inverted<RandomFullLattice>>(lattice.get())) {
265301
return ElementImpl{l->lattice.makeElement()};
266302
}
@@ -338,6 +374,8 @@ void printFullElement(std::ostream& os,
338374
os << (*e ? "true" : "false") << "\n";
339375
} else if (const auto* e = std::get_if<typename UInt32::Element>(&*elem)) {
340376
os << *e << "\n";
377+
} else if (const auto* e = std::get_if<typename ValType::Element>(&*elem)) {
378+
os << *e << "\n";
341379
} else if (const auto* e =
342380
std::get_if<typename Inverted<RandomFullLattice>::Element>(
343381
&*elem)) {
@@ -439,13 +477,20 @@ std::ostream& operator<<(std::ostream& os,
439477

440478
// Check that random lattices have the correct mathematical properties by
441479
// checking the relationships between random elements.
442-
void checkLatticeProperties(Random& rand) {
480+
void checkLatticeProperties(Random& rand, bool verbose) {
443481
RandomLattice lattice(rand);
444482

445483
// Generate the lattice elements we will perform checks on.
446484
typename RandomLattice::Element elems[3] = {
447485
lattice.makeElement(), lattice.makeElement(), lattice.makeElement()};
448486

487+
if (verbose) {
488+
std::cout << "Random lattice elements:\n"
489+
<< elems[0] << "\n"
490+
<< elems[1] << "\n"
491+
<< elems[2];
492+
}
493+
449494
// Calculate the relations between the generated elements.
450495
LatticeComparison relation[3][3];
451496
for (int i = 0; i < 3; ++i) {
@@ -920,7 +965,7 @@ struct Fuzzer {
920965
}
921966

922967
Random rand(std::move(funcBytes));
923-
checkLatticeProperties(rand);
968+
checkLatticeProperties(rand, verbose);
924969

925970
CFG cfg = CFG::fromFunction(func);
926971

test/gtest/lattices.cpp

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "analysis/lattices/inverted.h"
2222
#include "analysis/lattices/lift.h"
2323
#include "analysis/lattices/tuple.h"
24+
#include "analysis/lattices/valtype.h"
2425
#include "analysis/lattices/vector.h"
2526
#include "gtest/gtest.h"
2627

@@ -709,3 +710,114 @@ TEST(TupleLattice, Meet) {
709710
test(tt, tf, true, tf);
710711
test(tt, tt, false, tt);
711712
}
713+
714+
TEST(ValTypeLattice, GetBottom) {
715+
analysis::ValType valtype;
716+
EXPECT_EQ(valtype.getBottom(), Type::unreachable);
717+
}
718+
719+
TEST(ValTypeLattice, GetTop) {
720+
analysis::ValType valtype;
721+
EXPECT_EQ(valtype.getTop(), Type::none);
722+
}
723+
724+
TEST(ValTypeLattice, Compare) {
725+
analysis::ValType valtype;
726+
727+
Type ff = Type::unreachable;
728+
Type ft = Type::i32;
729+
Type tf = Type::f32;
730+
Type tt = Type::none;
731+
732+
EXPECT_EQ(valtype.compare(ff, ff), analysis::EQUAL);
733+
EXPECT_EQ(valtype.compare(ff, ft), analysis::LESS);
734+
EXPECT_EQ(valtype.compare(ff, tf), analysis::LESS);
735+
EXPECT_EQ(valtype.compare(ff, tt), analysis::LESS);
736+
737+
EXPECT_EQ(valtype.compare(ft, ff), analysis::GREATER);
738+
EXPECT_EQ(valtype.compare(ft, ft), analysis::EQUAL);
739+
EXPECT_EQ(valtype.compare(ft, tf), analysis::NO_RELATION);
740+
EXPECT_EQ(valtype.compare(ft, tt), analysis::LESS);
741+
742+
EXPECT_EQ(valtype.compare(tf, ff), analysis::GREATER);
743+
EXPECT_EQ(valtype.compare(tf, ft), analysis::NO_RELATION);
744+
EXPECT_EQ(valtype.compare(tf, tf), analysis::EQUAL);
745+
EXPECT_EQ(valtype.compare(tf, tt), analysis::LESS);
746+
747+
EXPECT_EQ(valtype.compare(tt, ff), analysis::GREATER);
748+
EXPECT_EQ(valtype.compare(tt, ft), analysis::GREATER);
749+
EXPECT_EQ(valtype.compare(tt, tf), analysis::GREATER);
750+
EXPECT_EQ(valtype.compare(tt, tt), analysis::EQUAL);
751+
}
752+
753+
TEST(ValTypeLattice, Join) {
754+
analysis::ValType valtype;
755+
756+
auto ff = []() -> Type { return Type::unreachable; };
757+
auto ft = []() -> Type { return Type::i32; };
758+
auto tf = []() -> Type { return Type::f32; };
759+
auto tt = []() -> Type { return Type::none; };
760+
761+
auto test =
762+
[&](auto& makeJoinee, auto& makeJoiner, bool modified, auto& makeExpected) {
763+
auto joinee = makeJoinee();
764+
EXPECT_EQ(valtype.join(joinee, makeJoiner()), modified);
765+
EXPECT_EQ(joinee, makeExpected());
766+
};
767+
768+
test(ff, ff, false, ff);
769+
test(ff, ft, true, ft);
770+
test(ff, tf, true, tf);
771+
test(ff, tt, true, tt);
772+
773+
test(ft, ff, false, ft);
774+
test(ft, ft, false, ft);
775+
test(ft, tf, true, tt);
776+
test(ft, tt, true, tt);
777+
778+
test(tf, ff, false, tf);
779+
test(tf, ft, true, tt);
780+
test(tf, tf, false, tf);
781+
test(tf, tt, true, tt);
782+
783+
test(tt, ff, false, tt);
784+
test(tt, ft, false, tt);
785+
test(tt, tf, false, tt);
786+
test(tt, tt, false, tt);
787+
}
788+
789+
TEST(ValTypeLattice, Meet) {
790+
analysis::ValType valtype;
791+
792+
auto ff = []() -> Type { return Type::unreachable; };
793+
auto ft = []() -> Type { return Type::i32; };
794+
auto tf = []() -> Type { return Type::f32; };
795+
auto tt = []() -> Type { return Type::none; };
796+
797+
auto test =
798+
[&](auto& makeMeetee, auto& makeMeeter, bool modified, auto& makeExpected) {
799+
auto meetee = makeMeetee();
800+
EXPECT_EQ(valtype.meet(meetee, makeMeeter()), modified);
801+
EXPECT_EQ(meetee, makeExpected());
802+
};
803+
804+
test(ff, ff, false, ff);
805+
test(ff, ft, false, ff);
806+
test(ff, tf, false, ff);
807+
test(ff, tt, false, ff);
808+
809+
test(ft, ff, true, ff);
810+
test(ft, ft, false, ft);
811+
test(ft, tf, true, ff);
812+
test(ft, tt, false, ft);
813+
814+
test(tf, ff, true, ff);
815+
test(tf, ft, true, ff);
816+
test(tf, tf, false, tf);
817+
test(tf, tt, false, tf);
818+
819+
test(tt, ff, true, ff);
820+
test(tt, ft, true, ft);
821+
test(tt, tf, true, tf);
822+
test(tt, tt, false, tt);
823+
}

0 commit comments

Comments
 (0)