diff --git a/src/analysis/lattices/valtype.h b/src/analysis/lattices/valtype.h new file mode 100644 index 00000000000..d63432ac668 --- /dev/null +++ b/src/analysis/lattices/valtype.h @@ -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); +#endif + +} // namespace wasm::analysis + +#endif // wasm_analysis_lattices_valtype_h diff --git a/src/tools/wasm-fuzz-lattices.cpp b/src/tools/wasm-fuzz-lattices.cpp index 7b757379814..f380f354168 100644 --- a/src/tools/wasm-fuzz-lattices.cpp +++ b/src/tools/wasm-fuzz-lattices.cpp @@ -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" @@ -158,6 +159,7 @@ using TupleLattice = analysis::Tuple; struct RandomFullLattice::L : std::variant, ArrayFullLattice, Vector, @@ -166,6 +168,7 @@ struct RandomFullLattice::L : std::variant::Element, typename ArrayFullLattice::Element, typename Vector::Element, @@ -186,7 +189,7 @@ struct RandomLattice::ElementImpl typename Vector::Element, typename TupleLattice::Element> {}; -constexpr int FullLatticePicks = 6; +constexpr int FullLatticePicks = 7; RandomFullLattice::RandomFullLattice(Random& rand, size_t depth, @@ -202,18 +205,21 @@ RandomFullLattice::RandomFullLattice(Random& rand, lattice = std::make_unique(L{UInt32{}}); return; case 2: + lattice = std::make_unique(L{ValType{}}); + return; + case 3: lattice = std::make_unique(L{Inverted{RandomFullLattice{rand, depth + 1}}}); return; - case 3: + case 4: lattice = std::make_unique( L{ArrayFullLattice{RandomFullLattice{rand, depth + 1}}}); return; - case 4: + case 5: lattice = std::make_unique( L{Vector{RandomFullLattice{rand, depth + 1}, rand.upTo(4)}}); return; - case 5: + case 6: lattice = std::make_unique( L{TupleFullLattice{RandomFullLattice{rand, depth + 1}, RandomFullLattice{rand, depth + 1}}}); @@ -261,6 +267,38 @@ RandomFullLattice::Element RandomFullLattice::makeElement() const noexcept { if (std::get_if(lattice.get())) { return ElementImpl{rand.upToSquared(33)}; } + if (std::get_if(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>(lattice.get())) { return ElementImpl{l->lattice.makeElement()}; } @@ -338,6 +376,8 @@ void printFullElement(std::ostream& os, os << (*e ? "true" : "false") << "\n"; } else if (const auto* e = std::get_if(&*elem)) { os << *e << "\n"; + } else if (const auto* e = std::get_if(&*elem)) { + os << *e << "\n"; } else if (const auto* e = std::get_if::Element>( &*elem)) { @@ -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) { @@ -920,7 +967,7 @@ struct Fuzzer { } Random rand(std::move(funcBytes)); - checkLatticeProperties(rand); + checkLatticeProperties(rand, verbose); CFG cfg = CFG::fromFunction(func); diff --git a/test/gtest/lattices.cpp b/test/gtest/lattices.cpp index e293348cb84..0adc09992cb 100644 --- a/test/gtest/lattices.cpp +++ b/test/gtest/lattices.cpp @@ -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" @@ -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); +}