|
| 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