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
0 commit comments