Skip to content

Commit

Permalink
[analysis] Add a lattice for value types (WebAssembly#6064)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tlively committed Nov 1, 2023
1 parent c82627e commit 74237bf
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 6 deletions.
82 changes: 82 additions & 0 deletions src/analysis/lattices/valtype.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2023 WebAssembly Community Group participants
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef wasm_analysis_lattices_valtype_h
#define wasm_analysis_lattices_valtype_h

#include "../lattice.h"
#include "wasm-type.h"

namespace wasm::analysis {

// 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.
struct ValType {
using Element = Type;

Element getBottom() const noexcept { return Type::unreachable; }

Element getTop() const noexcept { return Type::none; }

LatticeComparison compare(Element a, Element b) const noexcept {
if (a == b) {
return EQUAL;
}
if (b == Type::none || Type::isSubType(a, b)) {
return LESS;
}
if (a == Type::none || Type::isSubType(b, a)) {
return GREATER;
}
return NO_RELATION;
}

bool join(Element& joinee, Element joiner) const noexcept {
// `getLeastUpperBound` already treats `Type::none` as top.
auto lub = Type::getLeastUpperBound(joinee, joiner);
if (lub != joinee) {
joinee = lub;
return true;
}
return false;
}

bool meet(Element& meetee, Element meeter) const noexcept {
if (meetee == meeter || meeter == Type::none) {
return false;
}
if (meetee == Type::none) {
meetee = meeter;
return true;
}
auto glb = Type::getGreatestLowerBound(meetee, meeter);
if (glb != meetee) {
meetee = glb;
return true;
}
return false;
}
};

#if __cplusplus >= 202002L
static_assert(FullLattice<ValType>);
#endif

} // namespace wasm::analysis

#endif // wasm_analysis_lattices_valtype_h
59 changes: 53 additions & 6 deletions src/tools/wasm-fuzz-lattices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "analysis/lattices/lift.h"
#include "analysis/lattices/stack.h"
#include "analysis/lattices/tuple.h"
#include "analysis/lattices/valtype.h"
#include "analysis/lattices/vector.h"
#include "analysis/liveness-transfer-function.h"
#include "analysis/reaching-definitions-transfer-function.h"
Expand Down Expand Up @@ -158,6 +159,7 @@ using TupleLattice = analysis::Tuple<RandomLattice, RandomLattice>;

struct RandomFullLattice::L : std::variant<Bool,
UInt32,
ValType,
Inverted<RandomFullLattice>,
ArrayFullLattice,
Vector<RandomFullLattice>,
Expand All @@ -166,6 +168,7 @@ struct RandomFullLattice::L : std::variant<Bool,
struct RandomFullLattice::ElementImpl
: std::variant<typename Bool::Element,
typename UInt32::Element,
typename ValType::Element,
typename Inverted<RandomFullLattice>::Element,
typename ArrayFullLattice::Element,
typename Vector<RandomFullLattice>::Element,
Expand All @@ -186,7 +189,7 @@ struct RandomLattice::ElementImpl
typename Vector<RandomLattice>::Element,
typename TupleLattice::Element> {};

constexpr int FullLatticePicks = 6;
constexpr int FullLatticePicks = 7;

RandomFullLattice::RandomFullLattice(Random& rand,
size_t depth,
Expand All @@ -202,18 +205,21 @@ RandomFullLattice::RandomFullLattice(Random& rand,
lattice = std::make_unique<L>(L{UInt32{}});
return;
case 2:
lattice = std::make_unique<L>(L{ValType{}});
return;
case 3:
lattice =
std::make_unique<L>(L{Inverted{RandomFullLattice{rand, depth + 1}}});
return;
case 3:
case 4:
lattice = std::make_unique<L>(
L{ArrayFullLattice{RandomFullLattice{rand, depth + 1}}});
return;
case 4:
case 5:
lattice = std::make_unique<L>(
L{Vector{RandomFullLattice{rand, depth + 1}, rand.upTo(4)}});
return;
case 5:
case 6:
lattice = std::make_unique<L>(
L{TupleFullLattice{RandomFullLattice{rand, depth + 1},
RandomFullLattice{rand, depth + 1}}});
Expand Down Expand Up @@ -261,6 +267,38 @@ RandomFullLattice::Element RandomFullLattice::makeElement() const noexcept {
if (std::get_if<UInt32>(lattice.get())) {
return ElementImpl{rand.upToSquared(33)};
}
if (std::get_if<ValType>(lattice.get())) {
Type type;
// Choose a random type. No need to make all possible types available as
// long as we cover all the kinds of relationships between types.
switch (rand.upTo(8)) {
case 0:
type = Type::unreachable;
break;
case 1:
type = Type::none;
break;
case 2:
type = Type::i32;
break;
case 3:
type = Type::f32;
break;
case 4:
type = Type(HeapType::any, rand.oneIn(2) ? Nullable : NonNullable);
break;
case 5:
type = Type(HeapType::none, rand.oneIn(2) ? Nullable : NonNullable);
break;
case 6:
type = Type(HeapType::struct_, rand.oneIn(2) ? Nullable : NonNullable);
break;
case 7:
type = Type(HeapType::array, rand.oneIn(2) ? Nullable : NonNullable);
break;
}
return ElementImpl{type};
}
if (const auto* l = std::get_if<Inverted<RandomFullLattice>>(lattice.get())) {
return ElementImpl{l->lattice.makeElement()};
}
Expand Down Expand Up @@ -338,6 +376,8 @@ void printFullElement(std::ostream& os,
os << (*e ? "true" : "false") << "\n";
} else if (const auto* e = std::get_if<typename UInt32::Element>(&*elem)) {
os << *e << "\n";
} else if (const auto* e = std::get_if<typename ValType::Element>(&*elem)) {
os << *e << "\n";
} else if (const auto* e =
std::get_if<typename Inverted<RandomFullLattice>::Element>(
&*elem)) {
Expand Down Expand Up @@ -439,13 +479,20 @@ std::ostream& operator<<(std::ostream& os,

// Check that random lattices have the correct mathematical properties by
// checking the relationships between random elements.
void checkLatticeProperties(Random& rand) {
void checkLatticeProperties(Random& rand, bool verbose) {
RandomLattice lattice(rand);

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

if (verbose) {
std::cout << "Random lattice elements:\n"
<< elems[0] << "\n"
<< elems[1] << "\n"
<< elems[2];
}

// Calculate the relations between the generated elements.
LatticeComparison relation[3][3];
for (int i = 0; i < 3; ++i) {
Expand Down Expand Up @@ -920,7 +967,7 @@ struct Fuzzer {
}

Random rand(std::move(funcBytes));
checkLatticeProperties(rand);
checkLatticeProperties(rand, verbose);

CFG cfg = CFG::fromFunction(func);

Expand Down
112 changes: 112 additions & 0 deletions test/gtest/lattices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "analysis/lattices/inverted.h"
#include "analysis/lattices/lift.h"
#include "analysis/lattices/tuple.h"
#include "analysis/lattices/valtype.h"
#include "analysis/lattices/vector.h"
#include "gtest/gtest.h"

Expand Down Expand Up @@ -709,3 +710,114 @@ TEST(TupleLattice, Meet) {
test(tt, tf, true, tf);
test(tt, tt, false, tt);
}

TEST(ValTypeLattice, GetBottom) {
analysis::ValType valtype;
EXPECT_EQ(valtype.getBottom(), Type::unreachable);
}

TEST(ValTypeLattice, GetTop) {
analysis::ValType valtype;
EXPECT_EQ(valtype.getTop(), Type::none);
}

TEST(ValTypeLattice, Compare) {
analysis::ValType valtype;

Type ff = Type::unreachable;
Type ft = Type::i32;
Type tf = Type::f32;
Type tt = Type::none;

EXPECT_EQ(valtype.compare(ff, ff), analysis::EQUAL);
EXPECT_EQ(valtype.compare(ff, ft), analysis::LESS);
EXPECT_EQ(valtype.compare(ff, tf), analysis::LESS);
EXPECT_EQ(valtype.compare(ff, tt), analysis::LESS);

EXPECT_EQ(valtype.compare(ft, ff), analysis::GREATER);
EXPECT_EQ(valtype.compare(ft, ft), analysis::EQUAL);
EXPECT_EQ(valtype.compare(ft, tf), analysis::NO_RELATION);
EXPECT_EQ(valtype.compare(ft, tt), analysis::LESS);

EXPECT_EQ(valtype.compare(tf, ff), analysis::GREATER);
EXPECT_EQ(valtype.compare(tf, ft), analysis::NO_RELATION);
EXPECT_EQ(valtype.compare(tf, tf), analysis::EQUAL);
EXPECT_EQ(valtype.compare(tf, tt), analysis::LESS);

EXPECT_EQ(valtype.compare(tt, ff), analysis::GREATER);
EXPECT_EQ(valtype.compare(tt, ft), analysis::GREATER);
EXPECT_EQ(valtype.compare(tt, tf), analysis::GREATER);
EXPECT_EQ(valtype.compare(tt, tt), analysis::EQUAL);
}

TEST(ValTypeLattice, Join) {
analysis::ValType valtype;

auto ff = []() -> Type { return Type::unreachable; };
auto ft = []() -> Type { return Type::i32; };
auto tf = []() -> Type { return Type::f32; };
auto tt = []() -> Type { return Type::none; };

auto test =
[&](auto& makeJoinee, auto& makeJoiner, bool modified, auto& makeExpected) {
auto joinee = makeJoinee();
EXPECT_EQ(valtype.join(joinee, makeJoiner()), modified);
EXPECT_EQ(joinee, makeExpected());
};

test(ff, ff, false, ff);
test(ff, ft, true, ft);
test(ff, tf, true, tf);
test(ff, tt, true, tt);

test(ft, ff, false, ft);
test(ft, ft, false, ft);
test(ft, tf, true, tt);
test(ft, tt, true, tt);

test(tf, ff, false, tf);
test(tf, ft, true, tt);
test(tf, tf, false, tf);
test(tf, tt, true, tt);

test(tt, ff, false, tt);
test(tt, ft, false, tt);
test(tt, tf, false, tt);
test(tt, tt, false, tt);
}

TEST(ValTypeLattice, Meet) {
analysis::ValType valtype;

auto ff = []() -> Type { return Type::unreachable; };
auto ft = []() -> Type { return Type::i32; };
auto tf = []() -> Type { return Type::f32; };
auto tt = []() -> Type { return Type::none; };

auto test =
[&](auto& makeMeetee, auto& makeMeeter, bool modified, auto& makeExpected) {
auto meetee = makeMeetee();
EXPECT_EQ(valtype.meet(meetee, makeMeeter()), modified);
EXPECT_EQ(meetee, makeExpected());
};

test(ff, ff, false, ff);
test(ff, ft, false, ff);
test(ff, tf, false, ff);
test(ff, tt, false, ff);

test(ft, ff, true, ff);
test(ft, ft, false, ft);
test(ft, tf, true, ff);
test(ft, tt, false, ft);

test(tf, ff, true, ff);
test(tf, ft, true, ff);
test(tf, tf, false, tf);
test(tf, tt, false, tf);

test(tt, ff, true, ff);
test(tt, ft, true, ft);
test(tt, tf, true, tf);
test(tt, tt, false, tt);
}

0 comments on commit 74237bf

Please sign in to comment.