Skip to content

Commit 82ce454

Browse files
committed
[analysis] Add an abstraction lattice
The abstraction lattice is composed of increasingly abstract sub-lattices. Elements of the abstraction lattice are variants that may hold the elements of any of its constituent lattices. When elements from different sub-lattices are compared or joined, the more concrete element is first abstracted into the lattice of the other element. Unrelated elements in the same lattice may be abstracted before they are joined. This choice and the abstraction operation itself are provided by CRTP subclasses of Abstraction. This is an important building block for eventually reconstructing PossibleContents on top of the lattice framework.
1 parent f0660c0 commit 82ce454

File tree

2 files changed

+326
-0
lines changed

2 files changed

+326
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright 2025 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+
#include <array>
18+
#include <tuple>
19+
#include <utility>
20+
#include <variant>
21+
22+
#include "../lattice.h"
23+
#include "support/utilities.h"
24+
25+
#if __cplusplus >= 202002L
26+
#include "analysis/lattices/bool.h"
27+
#endif
28+
29+
#ifndef wasm_analysis_lattices_abstraction_h
30+
#define wasm_analysis_lattices_abstraction_h
31+
32+
namespace wasm::analysis {
33+
34+
// CRTP lattice composed of increasingly abstract sub-lattices. The subclass is
35+
// responsible for providing two method templates. The first abstracts an
36+
// element of one sub-lattice into an element of the next sub-lattice:
37+
//
38+
// template<size_t I, typename E1, typename E2>
39+
// E2 abstract(const E1&) const
40+
//
41+
// The template method should be specialized for each sub-lattice index I, its
42+
// element type E1, and the next element type E2.
43+
//
44+
// The `abstract` method is used to abstract elements of the more specific
45+
// lattice whenever elements from different lattices are compared or joined. It
46+
// may also be used to abstract two joined elements from the same lattice when
47+
// those elements are unrelated and the second method returns true:
48+
//
49+
// template<size_t I, typename E>
50+
// bool shouldAbstract(const E&. const E&) const
51+
//
52+
// shouldAbstract is only queries for unrelated elements. Related elements of
53+
// the same sub-lattice are always joined as normal.
54+
template<typename Self, typename... Ls> struct Abstraction {
55+
using Element = std::variant<typename Ls::Element...>;
56+
57+
std::tuple<Ls...> lattices;
58+
59+
Abstraction(Ls&&... lattices) : lattices({std::move(lattices)...}) {}
60+
61+
Element getBottom() const noexcept {
62+
return std::get<0>(lattices).getBottom();
63+
}
64+
65+
LatticeComparison compare(const Element& a, const Element& b) const noexcept {
66+
if (a.index() < b.index()) {
67+
auto abstractedA = a;
68+
abstractToIndex(abstractedA, b.index());
69+
switch (compares()[b.index()](lattices, abstractedA, b)) {
70+
case EQUAL:
71+
case LESS:
72+
return LESS;
73+
case NO_RELATION:
74+
case GREATER:
75+
return NO_RELATION;
76+
}
77+
WASM_UNREACHABLE("unexpected comparison");
78+
}
79+
if (a.index() > b.index()) {
80+
auto abstractedB = b;
81+
abstractToIndex(abstractedB, a.index());
82+
switch (compares()[a.index()](lattices, a, abstractedB)) {
83+
case EQUAL:
84+
case GREATER:
85+
return GREATER;
86+
case NO_RELATION:
87+
case LESS:
88+
return NO_RELATION;
89+
}
90+
WASM_UNREACHABLE("unexpected comparison");
91+
}
92+
return compares()[a.index()](lattices, a, b);
93+
}
94+
95+
bool join(Element& joinee, const Element& _joiner) const noexcept {
96+
Element joiner = _joiner;
97+
bool changed = false;
98+
if (joinee.index() < joiner.index()) {
99+
abstractToIndex(joinee, joiner.index());
100+
changed = true;
101+
} else if (joinee.index() > joiner.index()) {
102+
abstractToIndex(joiner, joinee.index());
103+
}
104+
while (true) {
105+
assert(joinee.index() == joiner.index());
106+
if (joiner.index() == sizeof...(Ls) - 1) {
107+
// Cannot abstract further, so we must join no matter what.
108+
break;
109+
}
110+
switch (compares()[joiner.index()](lattices, joinee, joiner)) {
111+
case NO_RELATION:
112+
if (shouldAbstracts()[joiner.index()](self(), joinee, joiner)) {
113+
// Try abstracting further.
114+
joinee = abstracts()[joinee.index()](self(), joinee);
115+
joiner = abstracts()[joiner.index()](self(), joiner);
116+
changed = true;
117+
continue;
118+
}
119+
break;
120+
case EQUAL:
121+
case LESS:
122+
case GREATER:
123+
break;
124+
}
125+
break;
126+
}
127+
return joins()[joiner.index()](lattices, joinee, joiner) || changed;
128+
}
129+
130+
private:
131+
const Self& self() const noexcept { return *static_cast<const Self*>(this); }
132+
133+
// TODO: Use C++26 pack indexing.
134+
template<std::size_t I, typename... Ts> struct Indexed;
135+
template<typename T, typename... Ts> struct Indexed<0, T, Ts...> {
136+
using type = T;
137+
};
138+
template<std::size_t I, typename T, typename... Ts>
139+
struct Indexed<I, T, Ts...> {
140+
using type = typename Indexed<I - 1, Ts...>::type;
141+
};
142+
template<std::size_t I> using L = typename Indexed<I, Ls...>::type;
143+
144+
// Compute tables of functions that forward operations to the CRTP subtype or
145+
// the lattices. These tables map the dynamic variant indices to compile-time
146+
// lattice indices.
147+
148+
template<std::size_t... I>
149+
static constexpr auto makeAbstracts(std::index_sequence<I...>) noexcept {
150+
using F = Element (*)(const Self&, const Element& elem);
151+
return std::array<F, sizeof...(I)>{
152+
[](const Self& self, const Element& elem) -> Element {
153+
if constexpr (I < sizeof...(Ls) - 1) {
154+
using E1 = typename L<I>::Element;
155+
using E2 = typename L<I + 1>::Element;
156+
return Element(std::in_place_index_t<I + 1>{},
157+
self.template abstract<I, E1, E2>(std::get<I>(elem)));
158+
} else {
159+
WASM_UNREACHABLE("unexpected abstraction");
160+
}
161+
}...};
162+
}
163+
static constexpr auto abstracts() noexcept {
164+
return makeAbstracts(std::make_index_sequence<sizeof...(Ls)>{});
165+
}
166+
167+
void abstractToIndex(Element& elem, std::size_t index) const noexcept {
168+
while (elem.index() < index) {
169+
elem = abstracts()[elem.index()](self(), elem);
170+
}
171+
}
172+
173+
template<std::size_t... I>
174+
static constexpr auto
175+
makeShouldAbstracts(std::index_sequence<I...>) noexcept {
176+
using F = bool (*)(const Self&, const Element&, const Element&);
177+
return std::array<F, sizeof...(I)>{
178+
[](const Self& self, const Element& a, const Element& b) -> bool {
179+
if constexpr (I == sizeof...(Ls) - 1) {
180+
WASM_UNREACHABLE("unexpected abstraction check");
181+
} else {
182+
return self.template shouldAbstract<I>(std::get<I>(a),
183+
std::get<I>(b));
184+
}
185+
}...};
186+
}
187+
static constexpr auto shouldAbstracts() noexcept {
188+
return makeShouldAbstracts(std::make_index_sequence<sizeof...(Ls)>{});
189+
}
190+
191+
template<std::size_t... I>
192+
static constexpr auto makeCompares(std::index_sequence<I...>) noexcept {
193+
using F = LatticeComparison (*)(
194+
const std::tuple<Ls...>&, const Element&, const Element&);
195+
return std::array<F, sizeof...(I)>{
196+
[](const std::tuple<Ls...>& lattices,
197+
const Element& a,
198+
const Element& b) -> LatticeComparison {
199+
return std::get<I>(lattices).compare(std::get<I>(a), std::get<I>(b));
200+
}...};
201+
}
202+
static constexpr auto compares() noexcept {
203+
return makeCompares(std::make_index_sequence<sizeof...(Ls)>{});
204+
}
205+
206+
template<std::size_t... I>
207+
static constexpr auto makeJoins(std::index_sequence<I...>) noexcept {
208+
using F = bool (*)(const std::tuple<Ls...>&, Element&, const Element&);
209+
return std::array<F, sizeof...(I)>{[](const std::tuple<Ls...>& lattices,
210+
Element& joinee,
211+
const Element& joiner) {
212+
return std::get<I>(lattices).join(std::get<I>(joinee),
213+
std::get<I>(joiner));
214+
}...};
215+
}
216+
static constexpr auto joins() noexcept {
217+
return makeJoins(std::make_index_sequence<sizeof...(Ls)>{});
218+
}
219+
};
220+
221+
#if __cplusplus >= 202002L
222+
static_assert(Lattice<Abstraction<Bool, Bool, Bool>>);
223+
#endif
224+
225+
} // namespace wasm::analysis
226+
227+
#endif // wasm_analysis_lattices_abstraction_h

test/gtest/lattices.cpp

Lines changed: 99 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/abstraction.h"
1718
#include "analysis/lattices/array.h"
1819
#include "analysis/lattices/bool.h"
1920
#include "analysis/lattices/flat.h"
@@ -723,3 +724,101 @@ TEST(StackLattice, Join) {
723724
{flat.get(0), flat.get(1)},
724725
{flat.get(0), flat.getTop()});
725726
}
727+
728+
using OddEvenInt = analysis::Flat<uint32_t>;
729+
using OddEvenBool = analysis::Flat<bool>;
730+
struct OddEvenAbstraction
731+
: analysis::Abstraction<OddEvenAbstraction, OddEvenInt, OddEvenBool> {
732+
OddEvenAbstraction()
733+
: analysis::Abstraction<OddEvenAbstraction, OddEvenInt, OddEvenBool>(
734+
OddEvenInt{}, OddEvenBool{}) {}
735+
736+
template<size_t I, typename E1, typename E2> E2 abstract(const E1&) const;
737+
738+
template<>
739+
OddEvenBool::Element abstract<0>(const OddEvenInt::Element& elem) const {
740+
if (elem.isTop()) {
741+
return OddEvenBool{}.getTop();
742+
}
743+
if (elem.isBottom()) {
744+
return OddEvenBool{}.getBottom();
745+
}
746+
return OddEvenBool{}.get((*elem.getVal() & 1) == 0);
747+
}
748+
749+
template<std::size_t I, typename E>
750+
bool shouldAbstract(const E&, const E&) const;
751+
752+
template<>
753+
bool shouldAbstract<0>(const OddEvenInt::Element&,
754+
const OddEvenInt::Element&) const {
755+
// Since the elements are not related, they must be different integers.
756+
// Always abstract them.
757+
return true;
758+
}
759+
};
760+
761+
TEST(AbstractionLattice, GetBottom) {
762+
OddEvenAbstraction abstraction;
763+
auto expected = OddEvenAbstraction::Element(OddEvenInt{}.getBottom());
764+
EXPECT_EQ(abstraction.getBottom(), expected);
765+
}
766+
767+
TEST(AbstractionLattice, Join) {
768+
OddEvenAbstraction abstraction;
769+
770+
auto expectJoin = [&](const char* file,
771+
int line,
772+
const auto& joinee,
773+
const auto& joiner,
774+
const auto& expected) {
775+
testing::ScopedTrace trace(file, line, "");
776+
auto copy = joinee;
777+
EXPECT_EQ(abstraction.join(copy, joiner), joinee != expected);
778+
EXPECT_EQ(copy, expected);
779+
780+
auto copy2 = joiner;
781+
EXPECT_EQ(abstraction.join(copy2, joinee), joiner != expected);
782+
EXPECT_EQ(copy2, expected);
783+
};
784+
785+
#define JOIN(a, b, c) expectJoin(__FILE__, __LINE__, a, b, c)
786+
787+
auto bot = abstraction.getBottom();
788+
auto one = OddEvenAbstraction::Element(OddEvenInt{}.get(1));
789+
auto two = OddEvenAbstraction::Element(OddEvenInt{}.get(2));
790+
auto three = OddEvenAbstraction::Element(OddEvenInt{}.get(3));
791+
auto four = OddEvenAbstraction::Element(OddEvenInt{}.get(4));
792+
auto even = OddEvenAbstraction::Element(OddEvenBool{}.get(true));
793+
auto odd = OddEvenAbstraction::Element(OddEvenBool{}.get(false));
794+
auto top = OddEvenAbstraction::Element(OddEvenBool{}.getTop());
795+
796+
JOIN(bot, bot, bot);
797+
JOIN(bot, one, one);
798+
JOIN(bot, two, two);
799+
JOIN(bot, even, even);
800+
JOIN(bot, odd, odd);
801+
JOIN(bot, top, top);
802+
803+
JOIN(one, one, one);
804+
JOIN(one, two, top);
805+
JOIN(one, three, odd);
806+
JOIN(one, even, top);
807+
JOIN(one, odd, odd);
808+
809+
JOIN(two, two, two);
810+
JOIN(two, three, top);
811+
JOIN(two, four, even);
812+
JOIN(two, even, even);
813+
JOIN(two, odd, top);
814+
JOIN(two, top, top);
815+
816+
JOIN(even, even, even);
817+
JOIN(even, odd, top);
818+
JOIN(even, top, top);
819+
820+
JOIN(odd, odd, odd);
821+
JOIN(odd, top, top);
822+
823+
#undef JOIN
824+
}

0 commit comments

Comments
 (0)