Skip to content

[analysis] Add a lattice for value types #6064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not i64, f64?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think adding them (or v128, or other reference types) would add much, since their relationships to other types isn't any different than what is already covered here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Comment might be worthwhile as it's an obvious question for a reader of the code to have I think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do 👍

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);
}