1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #ifndef COMPILER_OPTIMIZER_ANALYSIS_TYPED_REF_SET_H
17 #define COMPILER_OPTIMIZER_ANALYSIS_TYPED_REF_SET_H
18
19 #include "libpandabase/utils/bit_vector.h"
20 #include "optimizer/ir/inst.h"
21
22 namespace ark::compiler {
23
24 // Unique id of some object. Objects with different IDs are never equal
25 using Ref = uint32_t;
26 constexpr Ref ALL_REF = std::numeric_limits<Ref>::max();
27
28 ObjectTypeInfo Merge(ObjectTypeInfo lhs, ObjectTypeInfo rhs);
29
30 void TryImproveTypeInfo(ObjectTypeInfo &lhs, ObjectTypeInfo rhs);
31
32 /// @brief Set of unique `Ref`s (RefSet) represented as sorted vector
33 template <typename Allocator = ArenaAllocator>
34 class VectorSet {
35 public:
36 using BitVectorT = BitVector<Allocator>;
37
VectorSet(Allocator * allocator)38 explicit VectorSet(Allocator *allocator) : data_(allocator->Adapter()) {}
39
MinBitsToStore()40 size_t MinBitsToStore() const
41 {
42 return data_.empty() ? 0 : data_.back() + 1;
43 }
44
PopCount()45 size_t PopCount() const
46 {
47 return data_.size();
48 }
49
50 bool operator==(const VectorSet &other) const
51 {
52 return data_ == other.data_;
53 }
54
GetBit(size_t index)55 bool GetBit(size_t index) const
56 {
57 auto it = std::lower_bound(data_.begin(), data_.end(), index);
58 return it != data_.end() && *it == index;
59 }
60
SetBit(size_t index)61 void SetBit(size_t index)
62 {
63 auto it = std::lower_bound(data_.begin(), data_.end(), index);
64 if (it != data_.end() && *it == index) {
65 return;
66 }
67 data_.insert(it, index);
68 }
69
TransitionToBitVector()70 BitVectorT TransitionToBitVector()
71 {
72 auto bitVector = BitVectorT(data_.back(), data_.get_allocator());
73 for (auto ref : data_) {
74 bitVector.SetBit(ref);
75 }
76 return bitVector;
77 }
78
79 VectorSet &operator|=(const VectorSet &other)
80 {
81 auto size = data_.size();
82 data_.insert(data_.end(), other.data_.begin(), other.data_.end());
83 std::inplace_merge(data_.begin(), data_.begin() + size, data_.end());
84 data_.erase(std::unique(data_.begin(), data_.end()), data_.end());
85 ASSERT(std::is_sorted(data_.begin(), data_.end()));
86 return *this;
87 }
88
89 friend BitVectorT operator|=(BitVectorT &to, const VectorSet &from)
90 {
91 // ensure capacity in advance
92 if (auto minBits = from.MinBitsToStore(); minBits > to.size()) {
93 to.resize(minBits);
94 }
95 for (auto ref : from.data_) {
96 to.SetBit(ref);
97 }
98 return to;
99 }
100
GetSingle()101 Ref GetSingle() const
102 {
103 ASSERT(data_.size() == 1);
104 return data_[0];
105 }
106
GetData()107 const auto &GetData() const
108 {
109 return data_;
110 }
111
112 private:
113 std::vector<Ref, typename Allocator::template AdapterType<Ref>> data_;
114 };
115
116 template <typename Allocator, typename Callback>
VisitBitVector(const VectorSet<Allocator> & vectorSet,Callback && cb)117 void VisitBitVector(const VectorSet<Allocator> &vectorSet, Callback &&cb)
118 {
119 for (auto ref : vectorSet.GetData()) {
120 cb(ref);
121 }
122 }
123
124 template <typename Allocator, typename Callback>
VisitBitVector(const BitVector<Allocator> & bitVector,Callback && cb)125 void VisitBitVector(const BitVector<Allocator> &bitVector, Callback &&cb)
126 {
127 auto span = bitVector.GetContainerDataSpan();
128 for (size_t maskIdx = 0; maskIdx < span.size(); ++maskIdx) {
129 auto mask = span[maskIdx];
130 size_t maskOffset = 0;
131 while (mask != 0) {
132 auto offset = static_cast<size_t>(Ctz(mask));
133 mask = (mask >> offset) >> 1U;
134 maskOffset += offset;
135 size_t oneIdx = maskIdx * sizeof(mask) * BITS_PER_BYTE + maskOffset;
136 ++maskOffset; // consume current bit
137 cb(oneIdx);
138 }
139 }
140 }
141
142 class UniverseSet {
143 public:
PopCount()144 size_t PopCount() const
145 {
146 return std::numeric_limits<size_t>::max();
147 }
148
149 bool operator==([[maybe_unused]] const UniverseSet &other) const
150 {
151 return true;
152 }
153
GetBit(size_t index)154 bool GetBit([[maybe_unused]] size_t index) const
155 {
156 return true;
157 }
158
SetBit(size_t index)159 void SetBit([[maybe_unused]] size_t index) {}
160 };
161
162 /**
163 * @brief Set of unique `Ref`s (RefSet)
164 *
165 * Is represented as sorted vector while small;
166 * transitions to `BitVector` when grows larger then `VECTOR_THRESHOLD`;
167 * transitions to `UniverseSet` when grows larger then `BIT_VECTOR_THRESHOLD`;
168 */
169 template <typename Allocator = StdAllocatorStub>
170 class SmallSet {
171 using VectorSetT = VectorSet<Allocator>;
172 using BitVectorT = BitVector<Allocator>;
173
174 public:
SmallSet(Allocator * allocator)175 explicit SmallSet(Allocator *allocator) : data_(VectorSetT(allocator)) {}
176 // CC-OFFNXT(G.CLS.03-CPP) clang-tidy: initializer-list constructor shouldn't be explicit
SmallSet(allocator)177 SmallSet(std::initializer_list<Ref> values, Allocator *allocator = Allocator::GetInstance()) : SmallSet(allocator)
178 {
179 for (auto value : values) {
180 SetBit(value);
181 }
182 }
183 ~SmallSet() = default;
184 DEFAULT_COPY_SEMANTIC(SmallSet);
185 DEFAULT_MOVE_SEMANTIC(SmallSet);
186
187 bool operator==(const SmallSet &other) const
188 {
189 return data_ == other.data_;
190 }
191
PopCount()192 size_t PopCount() const
193 {
194 return std::visit([](const auto &s) { return s.PopCount(); }, data_);
195 }
196
GetBit(Ref index)197 bool GetBit(Ref index) const
198 {
199 if (index == ALL_REF) {
200 return std::holds_alternative<UniverseSet>(data_);
201 }
202 return std::visit([index](const auto &s) { return s.GetBit(index); }, data_);
203 }
204
SetBit(Ref index)205 void SetBit(Ref index)
206 {
207 if (index == ALL_REF) {
208 MakeUniverse();
209 return;
210 }
211 std::visit([index](auto &s) { s.SetBit(index); }, data_);
212 CheckTransition();
213 }
214
Includes(const SmallSet & other)215 bool Includes(const SmallSet &other) const
216 {
217 return std::visit(
218 [](const auto &to, const auto &from) -> bool {
219 using To = std::decay_t<decltype(to)>;
220 using From = std::decay_t<decltype(from)>;
221 if constexpr (std::is_same_v<To, UniverseSet>) {
222 return true;
223 } else if constexpr (std::is_same_v<From, UniverseSet>) {
224 return false;
225 } else {
226 return Includes(to, from);
227 }
228 },
229 data_, other.data_);
230 }
231
232 SmallSet &operator|=(const SmallSet &other)
233 {
234 std::visit(
235 [this](auto &to, const auto &from) {
236 using To = std::decay_t<decltype(to)>;
237 using From = std::decay_t<decltype(from)>;
238 if constexpr (std::is_same_v<To, UniverseSet>) {
239 // do nothing
240 } else if constexpr (std::is_same_v<From, UniverseSet>) {
241 MakeUniverse();
242 } else if constexpr (std::is_same_v<To, VectorSetT>) {
243 if constexpr (std::is_same_v<From, VectorSetT>) {
244 to |= from;
245 } else {
246 auto newData = from;
247 newData |= to;
248 data_ = std::move(newData);
249 }
250 } else {
251 static_assert(std::is_same_v<To, BitVectorT>);
252 to |= from;
253 }
254 },
255 data_, other.data_);
256 CheckTransition();
257 return *this;
258 }
259
GetSingle()260 Ref GetSingle() const
261 {
262 return std::get<VectorSetT>(data_).GetSingle();
263 }
264
265 template <typename Callback>
Visit(Callback && cb)266 void Visit(Callback &&cb) const
267 {
268 std::visit(
269 [&cb](auto &s) {
270 using T = std::decay_t<decltype(s)>;
271 if constexpr (std::is_same_v<T, UniverseSet>) {
272 cb(UINT_MAX);
273 UNREACHABLE();
274 } else {
275 VisitBitVector(s, cb);
276 }
277 },
278 data_);
279 }
280
IsVectorSet()281 bool IsVectorSet() const
282 {
283 return std::holds_alternative<VectorSetT>(data_);
284 }
285
286 private:
CheckTransition()287 void CheckTransition()
288 {
289 if (std::holds_alternative<VectorSetT>(data_)) {
290 if (PopCount() > VECTOR_THRESHOLD) {
291 data_ = std::get<VectorSetT>(data_).TransitionToBitVector();
292 }
293 } else if (std::holds_alternative<BitVectorT>(data_)) {
294 if (std::get<BitVectorT>(data_).capacity() > BIT_VECTOR_THRESHOLD) {
295 MakeUniverse();
296 }
297 } else {
298 ASSERT(std::holds_alternative<UniverseSet>(data_));
299 }
300 }
301
MakeUniverse()302 void MakeUniverse()
303 {
304 data_ = UniverseSet {};
305 }
306
307 template <typename T>
Includes(const T & to,const VectorSetT & from)308 static bool Includes(const T &to, const VectorSetT &from)
309 {
310 for (auto i : from.GetData()) {
311 if (!to.GetBit(i)) {
312 return false;
313 }
314 }
315 return true;
316 }
317
318 template <typename T>
Includes(const T & to,const UniverseSet & from)319 static bool Includes([[maybe_unused]] const T &to, [[maybe_unused]] const UniverseSet &from)
320 {
321 return std::is_same_v<T, UniverseSet>;
322 }
323
Includes(const VectorSetT & to,const BitVectorT & from)324 static bool Includes([[maybe_unused]] const VectorSetT &to, [[maybe_unused]] const BitVectorT &from)
325 {
326 // from is definitely larger
327 return false;
328 }
329
Includes(const BitVectorT & to,const BitVectorT & from)330 static bool Includes(const BitVectorT &to, const BitVectorT &from)
331 {
332 auto toData = to.GetContainerDataSpan();
333 auto fromData = from.GetContainerDataSpan();
334 for (size_t i = 0; i < fromData.size(); i++) {
335 auto toWord = i >= toData.size() ? 0 : toData[i];
336 if ((toWord & fromData[i]) != fromData[i]) {
337 return false;
338 }
339 }
340 return true;
341 }
342
343 private:
344 std::variant<VectorSetT, BitVectorT, UniverseSet> data_;
345 constexpr static size_t VECTOR_THRESHOLD = 5;
346 // NOTE: temp, never trigger
347 constexpr static size_t BIT_VECTOR_THRESHOLD = 200'000 * BITS_PER_UINT32;
348 };
349
350 template <typename Allocator = StdAllocatorStub>
351 class TypedRefSet : private SmallSet<Allocator> {
352 using Base = SmallSet<Allocator>;
353
354 public:
TypedRefSet(const Base & base,ObjectTypeInfo typeInfo)355 TypedRefSet(const Base &base, ObjectTypeInfo typeInfo) : Base(base), typeInfo_(typeInfo) {}
356 TypedRefSet(Allocator *allocator, ObjectTypeInfo typeInfo) : Base(allocator), typeInfo_(typeInfo) {}
357 ~TypedRefSet() = default;
358
359 DEFAULT_COPY_SEMANTIC(TypedRefSet);
360 DEFAULT_MOVE_SEMANTIC(TypedRefSet);
361
362 using Base::GetBit;
363 using Base::GetSingle;
364 using Base::PopCount;
365 using Base::Visit;
366
367 bool operator==(const TypedRefSet &other) const
368 {
369 return Base::operator==(other) && typeInfo_ == other.typeInfo_;
370 }
371
372 bool operator!=(const TypedRefSet &other) const
373 {
374 return !(*this == other);
375 }
376
377 TypedRefSet &operator|=(const TypedRefSet &other)
378 {
379 Base::operator|=(other);
380 typeInfo_ = Merge(typeInfo_, other.typeInfo_);
381 return *this;
382 }
383
384 void SetBit(Ref index, ObjectTypeInfo typeInfo)
385 {
386 Base::SetBit(index);
387 UpdateTypeInfoOr(typeInfo);
388 }
389
390 bool Includes(const TypedRefSet &other) const
391 {
392 if (typeInfo_ != Merge(typeInfo_, other.typeInfo_)) {
393 return false;
394 }
395 return Base::Includes(other);
396 }
397
398 void TryImproveTypeInfo(ObjectTypeInfo otherKnownTypeInfo)
399 {
400 compiler::TryImproveTypeInfo(typeInfo_, otherKnownTypeInfo);
401 }
402
403 void UpdateTypeInfoOr(ObjectTypeInfo newPossibleTypeInfo)
404 {
405 typeInfo_ = Merge(typeInfo_, newPossibleTypeInfo);
406 }
407
408 ObjectTypeInfo GetTypeInfo() const
409 {
410 return typeInfo_;
411 }
412
413 template <typename Alloc>
414 friend std::ostream &operator<<(std::ostream &os, const TypedRefSet<Alloc> &typedRefSet);
415
416 private:
417 ObjectTypeInfo typeInfo_ {ObjectTypeInfo::INVALID};
418 };
419
420 template <typename Allocator>
421 std::ostream &operator<<(std::ostream &os, const SmallSet<Allocator> &smallSet)
422 {
423 os << "{";
424 bool first = true;
425 smallSet.Visit([&os, &first](Ref ref) {
426 if (!first) {
427 os << ", ";
428 }
429 first = false;
430 os << ref;
431 });
432 return os << "}";
433 }
434
435 template <typename Allocator>
436 std::ostream &operator<<(std::ostream &os, const TypedRefSet<Allocator> &typedRefSet)
437 {
438 return os << static_cast<const SmallSet<Allocator> &>(typedRefSet);
439 }
440
441 using ArenaSmallSet = SmallSet<ArenaAllocator>;
442 using ArenaTypedRefSet = TypedRefSet<ArenaAllocator>;
443
444 } // namespace ark::compiler
445
446 #endif // COMPILER_OPTIMIZER_ANALYSIS_TYPED_REF_SET_H