1 | //===- TypeSize.h - Wrapper around type sizes -------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file provides a struct that can be used to query the size of IR types |
10 | // which may be scalable vectors. It provides convenience operators so that |
11 | // it can be used in much the same way as a single scalar value. |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #ifndef LLVM_SUPPORT_TYPESIZE_H |
16 | #define LLVM_SUPPORT_TYPESIZE_H |
17 | |
18 | #include "llvm/ADT/ArrayRef.h" |
19 | #include "llvm/Support/MathExtras.h" |
20 | #include "llvm/Support/WithColor.h" |
21 | |
22 | #include <algorithm> |
23 | #include <array> |
24 | #include <cassert> |
25 | #include <cstdint> |
26 | #include <type_traits> |
27 | |
28 | namespace llvm { |
29 | |
30 | /// Reports a diagnostic message to indicate an invalid size request has been |
31 | /// done on a scalable vector. This function may not return. |
32 | void reportInvalidSizeRequest(const char *Msg); |
33 | |
34 | template <typename LeafTy> struct LinearPolyBaseTypeTraits {}; |
35 | |
36 | //===----------------------------------------------------------------------===// |
37 | // LinearPolyBase - a base class for linear polynomials with multiple |
38 | // dimensions. This can e.g. be used to describe offsets that are have both a |
39 | // fixed and scalable component. |
40 | //===----------------------------------------------------------------------===// |
41 | |
42 | /// LinearPolyBase describes a linear polynomial: |
43 | /// c0 * scale0 + c1 * scale1 + ... + cK * scaleK |
44 | /// where the scale is implicit, so only the coefficients are encoded. |
45 | template <typename LeafTy> |
46 | class LinearPolyBase { |
47 | public: |
48 | using ScalarTy = typename LinearPolyBaseTypeTraits<LeafTy>::ScalarTy; |
49 | static constexpr auto Dimensions = LinearPolyBaseTypeTraits<LeafTy>::Dimensions; |
50 | static_assert(Dimensions != std::numeric_limits<unsigned>::max(), |
51 | "Dimensions out of range" ); |
52 | |
53 | private: |
54 | std::array<ScalarTy, Dimensions> Coefficients; |
55 | |
56 | protected: |
57 | LinearPolyBase(ArrayRef<ScalarTy> Values) { |
58 | std::copy(Values.begin(), Values.end(), Coefficients.begin()); |
59 | } |
60 | |
61 | public: |
62 | friend LeafTy &operator+=(LeafTy &LHS, const LeafTy &RHS) { |
63 | for (unsigned I=0; I<Dimensions; ++I) |
64 | LHS.Coefficients[I] += RHS.Coefficients[I]; |
65 | return LHS; |
66 | } |
67 | |
68 | friend LeafTy &operator-=(LeafTy &LHS, const LeafTy &RHS) { |
69 | for (unsigned I=0; I<Dimensions; ++I) |
70 | LHS.Coefficients[I] -= RHS.Coefficients[I]; |
71 | return LHS; |
72 | } |
73 | |
74 | friend LeafTy &operator*=(LeafTy &LHS, ScalarTy RHS) { |
75 | for (auto &C : LHS.Coefficients) |
76 | C *= RHS; |
77 | return LHS; |
78 | } |
79 | |
80 | friend LeafTy operator+(const LeafTy &LHS, const LeafTy &RHS) { |
81 | LeafTy Copy = LHS; |
82 | return Copy += RHS; |
83 | } |
84 | |
85 | friend LeafTy operator-(const LeafTy &LHS, const LeafTy &RHS) { |
86 | LeafTy Copy = LHS; |
87 | return Copy -= RHS; |
88 | } |
89 | |
90 | friend LeafTy operator*(const LeafTy &LHS, ScalarTy RHS) { |
91 | LeafTy Copy = LHS; |
92 | return Copy *= RHS; |
93 | } |
94 | |
95 | template <typename U = ScalarTy> |
96 | friend typename std::enable_if_t<std::is_signed<U>::value, LeafTy> |
97 | operator-(const LeafTy &LHS) { |
98 | LeafTy Copy = LHS; |
99 | return Copy *= -1; |
100 | } |
101 | |
102 | bool operator==(const LinearPolyBase &RHS) const { |
103 | return std::equal(Coefficients.begin(), Coefficients.end(), |
104 | RHS.Coefficients.begin()); |
105 | } |
106 | |
107 | bool operator!=(const LinearPolyBase &RHS) const { |
108 | return !(*this == RHS); |
109 | } |
110 | |
111 | bool isZero() const { |
112 | return all_of(Coefficients, [](const ScalarTy &C) { return C == 0; }); |
113 | } |
114 | bool isNonZero() const { return !isZero(); } |
115 | explicit operator bool() const { return isNonZero(); } |
116 | |
117 | ScalarTy getValue(unsigned Dim) const { return Coefficients[Dim]; } |
118 | }; |
119 | |
120 | //===----------------------------------------------------------------------===// |
121 | // StackOffset - Represent an offset with named fixed and scalable components. |
122 | //===----------------------------------------------------------------------===// |
123 | |
124 | class StackOffset; |
125 | template <> struct LinearPolyBaseTypeTraits<StackOffset> { |
126 | using ScalarTy = int64_t; |
127 | static constexpr unsigned Dimensions = 2; |
128 | }; |
129 | |
130 | /// StackOffset is a class to represent an offset with 2 dimensions, |
131 | /// named fixed and scalable, respectively. This class allows a value for both |
132 | /// dimensions to depict e.g. "8 bytes and 16 scalable bytes", which is needed |
133 | /// to represent stack offsets. |
134 | class StackOffset : public LinearPolyBase<StackOffset> { |
135 | protected: |
136 | StackOffset(ScalarTy Fixed, ScalarTy Scalable) |
137 | : LinearPolyBase<StackOffset>({Fixed, Scalable}) {} |
138 | |
139 | public: |
140 | StackOffset() : StackOffset({0, 0}) {} |
141 | StackOffset(const LinearPolyBase<StackOffset> &Other) |
142 | : LinearPolyBase<StackOffset>(Other) {} |
143 | static StackOffset getFixed(ScalarTy Fixed) { return {Fixed, 0}; } |
144 | static StackOffset getScalable(ScalarTy Scalable) { return {0, Scalable}; } |
145 | static StackOffset get(ScalarTy Fixed, ScalarTy Scalable) { |
146 | return {Fixed, Scalable}; |
147 | } |
148 | |
149 | ScalarTy getFixed() const { return this->getValue(0); } |
150 | ScalarTy getScalable() const { return this->getValue(1); } |
151 | }; |
152 | |
153 | //===----------------------------------------------------------------------===// |
154 | // UnivariateLinearPolyBase - a base class for linear polynomials with multiple |
155 | // dimensions, but where only one dimension can be set at any time. |
156 | // This can e.g. be used to describe sizes that are either fixed or scalable. |
157 | //===----------------------------------------------------------------------===// |
158 | |
159 | /// UnivariateLinearPolyBase is a base class for ElementCount and TypeSize. |
160 | /// Like LinearPolyBase it tries to represent a linear polynomial |
161 | /// where only one dimension can be set at any time, e.g. |
162 | /// 0 * scale0 + 0 * scale1 + ... + cJ * scaleJ + ... + 0 * scaleK |
163 | /// The dimension that is set is the univariate dimension. |
164 | template <typename LeafTy> |
165 | class UnivariateLinearPolyBase { |
166 | public: |
167 | using ScalarTy = typename LinearPolyBaseTypeTraits<LeafTy>::ScalarTy; |
168 | static constexpr auto Dimensions = LinearPolyBaseTypeTraits<LeafTy>::Dimensions; |
169 | static_assert(Dimensions != std::numeric_limits<unsigned>::max(), |
170 | "Dimensions out of range" ); |
171 | |
172 | protected: |
173 | ScalarTy Value; // The value at the univeriate dimension. |
174 | unsigned UnivariateDim; // The univeriate dimension. |
175 | |
176 | UnivariateLinearPolyBase(ScalarTy Val, unsigned UnivariateDim) |
177 | : Value(Val), UnivariateDim(UnivariateDim) { |
178 | assert(UnivariateDim < Dimensions && "Dimension out of range" ); |
179 | } |
180 | |
181 | friend LeafTy &operator+=(LeafTy &LHS, const LeafTy &RHS) { |
182 | assert(LHS.UnivariateDim == RHS.UnivariateDim && "Invalid dimensions" ); |
183 | LHS.Value += RHS.Value; |
184 | return LHS; |
185 | } |
186 | |
187 | friend LeafTy &operator-=(LeafTy &LHS, const LeafTy &RHS) { |
188 | assert(LHS.UnivariateDim == RHS.UnivariateDim && "Invalid dimensions" ); |
189 | LHS.Value -= RHS.Value; |
190 | return LHS; |
191 | } |
192 | |
193 | friend LeafTy &operator*=(LeafTy &LHS, ScalarTy RHS) { |
194 | LHS.Value *= RHS; |
195 | return LHS; |
196 | } |
197 | |
198 | friend LeafTy operator+(const LeafTy &LHS, const LeafTy &RHS) { |
199 | LeafTy Copy = LHS; |
200 | return Copy += RHS; |
201 | } |
202 | |
203 | friend LeafTy operator-(const LeafTy &LHS, const LeafTy &RHS) { |
204 | LeafTy Copy = LHS; |
205 | return Copy -= RHS; |
206 | } |
207 | |
208 | friend LeafTy operator*(const LeafTy &LHS, ScalarTy RHS) { |
209 | LeafTy Copy = LHS; |
210 | return Copy *= RHS; |
211 | } |
212 | |
213 | template <typename U = ScalarTy> |
214 | friend typename std::enable_if<std::is_signed<U>::value, LeafTy>::type |
215 | operator-(const LeafTy &LHS) { |
216 | LeafTy Copy = LHS; |
217 | return Copy *= -1; |
218 | } |
219 | |
220 | public: |
221 | bool operator==(const UnivariateLinearPolyBase &RHS) const { |
222 | return Value == RHS.Value && UnivariateDim == RHS.UnivariateDim; |
223 | } |
224 | |
225 | bool operator!=(const UnivariateLinearPolyBase &RHS) const { |
226 | return !(*this == RHS); |
227 | } |
228 | |
229 | bool isZero() const { return !Value; } |
230 | bool isNonZero() const { return !isZero(); } |
231 | explicit operator bool() const { return isNonZero(); } |
232 | ScalarTy getValue() const { return Value; } |
233 | ScalarTy getValue(unsigned Dim) const { |
234 | return Dim == UnivariateDim ? Value : 0; |
235 | } |
236 | |
237 | /// Add \p RHS to the value at the univariate dimension. |
238 | LeafTy getWithIncrement(ScalarTy RHS) const { |
239 | return static_cast<LeafTy>( |
240 | UnivariateLinearPolyBase(Value + RHS, UnivariateDim)); |
241 | } |
242 | |
243 | /// Subtract \p RHS from the value at the univariate dimension. |
244 | LeafTy getWithDecrement(ScalarTy RHS) const { |
245 | return static_cast<LeafTy>( |
246 | UnivariateLinearPolyBase(Value - RHS, UnivariateDim)); |
247 | } |
248 | }; |
249 | |
250 | |
251 | //===----------------------------------------------------------------------===// |
252 | // LinearPolySize - base class for fixed- or scalable sizes. |
253 | // ^ ^ |
254 | // | | |
255 | // | +----- ElementCount - Leaf class to represent an element count |
256 | // | (vscale x unsigned) |
257 | // | |
258 | // +-------- TypeSize - Leaf class to represent a type size |
259 | // (vscale x uint64_t) |
260 | //===----------------------------------------------------------------------===// |
261 | |
262 | /// LinearPolySize is a base class to represent sizes. It is either |
263 | /// fixed-sized or it is scalable-sized, but it cannot be both. |
264 | template <typename LeafTy> |
265 | class LinearPolySize : public UnivariateLinearPolyBase<LeafTy> { |
266 | // Make the parent class a friend, so that it can access the protected |
267 | // conversion/copy-constructor for UnivariatePolyBase<LeafTy> -> |
268 | // LinearPolySize<LeafTy>. |
269 | friend class UnivariateLinearPolyBase<LeafTy>; |
270 | |
271 | public: |
272 | using ScalarTy = typename UnivariateLinearPolyBase<LeafTy>::ScalarTy; |
273 | enum Dims : unsigned { FixedDim = 0, ScalableDim = 1 }; |
274 | |
275 | protected: |
276 | LinearPolySize(ScalarTy MinVal, Dims D) |
277 | : UnivariateLinearPolyBase<LeafTy>(MinVal, D) {} |
278 | |
279 | LinearPolySize(const UnivariateLinearPolyBase<LeafTy> &V) |
280 | : UnivariateLinearPolyBase<LeafTy>(V) {} |
281 | |
282 | public: |
283 | |
284 | static LeafTy getFixed(ScalarTy MinVal) { |
285 | return static_cast<LeafTy>(LinearPolySize(MinVal, FixedDim)); |
286 | } |
287 | static LeafTy getScalable(ScalarTy MinVal) { |
288 | return static_cast<LeafTy>(LinearPolySize(MinVal, ScalableDim)); |
289 | } |
290 | static LeafTy get(ScalarTy MinVal, bool Scalable) { |
291 | return static_cast<LeafTy>( |
292 | LinearPolySize(MinVal, Scalable ? ScalableDim : FixedDim)); |
293 | } |
294 | static LeafTy getNull() { return get(0, false); } |
295 | |
296 | /// Returns the minimum value this size can represent. |
297 | ScalarTy getKnownMinValue() const { return this->getValue(); } |
298 | /// Returns whether the size is scaled by a runtime quantity (vscale). |
299 | bool isScalable() const { return this->UnivariateDim == ScalableDim; } |
300 | /// A return value of true indicates we know at compile time that the number |
301 | /// of elements (vscale * Min) is definitely even. However, returning false |
302 | /// does not guarantee that the total number of elements is odd. |
303 | bool isKnownEven() const { return (getKnownMinValue() & 0x1) == 0; } |
304 | /// This function tells the caller whether the element count is known at |
305 | /// compile time to be a multiple of the scalar value RHS. |
306 | bool isKnownMultipleOf(ScalarTy RHS) const { |
307 | return getKnownMinValue() % RHS == 0; |
308 | } |
309 | |
310 | // Return the minimum value with the assumption that the count is exact. |
311 | // Use in places where a scalable count doesn't make sense (e.g. non-vector |
312 | // types, or vectors in backends which don't support scalable vectors). |
313 | ScalarTy getFixedValue() const { |
314 | assert(!isScalable() && |
315 | "Request for a fixed element count on a scalable object" ); |
316 | return getKnownMinValue(); |
317 | } |
318 | |
319 | // For some cases, size ordering between scalable and fixed size types cannot |
320 | // be determined at compile time, so such comparisons aren't allowed. |
321 | // |
322 | // e.g. <vscale x 2 x i16> could be bigger than <4 x i32> with a runtime |
323 | // vscale >= 5, equal sized with a vscale of 4, and smaller with |
324 | // a vscale <= 3. |
325 | // |
326 | // All the functions below make use of the fact vscale is always >= 1, which |
327 | // means that <vscale x 4 x i32> is guaranteed to be >= <4 x i32>, etc. |
328 | |
329 | static bool isKnownLT(const LinearPolySize &LHS, const LinearPolySize &RHS) { |
330 | if (!LHS.isScalable() || RHS.isScalable()) |
331 | return LHS.getKnownMinValue() < RHS.getKnownMinValue(); |
332 | return false; |
333 | } |
334 | |
335 | static bool isKnownGT(const LinearPolySize &LHS, const LinearPolySize &RHS) { |
336 | if (LHS.isScalable() || !RHS.isScalable()) |
337 | return LHS.getKnownMinValue() > RHS.getKnownMinValue(); |
338 | return false; |
339 | } |
340 | |
341 | static bool isKnownLE(const LinearPolySize &LHS, const LinearPolySize &RHS) { |
342 | if (!LHS.isScalable() || RHS.isScalable()) |
343 | return LHS.getKnownMinValue() <= RHS.getKnownMinValue(); |
344 | return false; |
345 | } |
346 | |
347 | static bool isKnownGE(const LinearPolySize &LHS, const LinearPolySize &RHS) { |
348 | if (LHS.isScalable() || !RHS.isScalable()) |
349 | return LHS.getKnownMinValue() >= RHS.getKnownMinValue(); |
350 | return false; |
351 | } |
352 | |
353 | /// We do not provide the '/' operator here because division for polynomial |
354 | /// types does not work in the same way as for normal integer types. We can |
355 | /// only divide the minimum value (or coefficient) by RHS, which is not the |
356 | /// same as |
357 | /// (Min * Vscale) / RHS |
358 | /// The caller is recommended to use this function in combination with |
359 | /// isKnownMultipleOf(RHS), which lets the caller know if it's possible to |
360 | /// perform a lossless divide by RHS. |
361 | LeafTy divideCoefficientBy(ScalarTy RHS) const { |
362 | return static_cast<LeafTy>( |
363 | LinearPolySize::get(getKnownMinValue() / RHS, isScalable())); |
364 | } |
365 | |
366 | LeafTy coefficientNextPowerOf2() const { |
367 | return static_cast<LeafTy>(LinearPolySize::get( |
368 | static_cast<ScalarTy>(llvm::NextPowerOf2(getKnownMinValue())), |
369 | isScalable())); |
370 | } |
371 | |
372 | /// Printing function. |
373 | void print(raw_ostream &OS) const { |
374 | if (isScalable()) |
375 | OS << "vscale x " ; |
376 | OS << getKnownMinValue(); |
377 | } |
378 | }; |
379 | |
380 | class ElementCount; |
381 | template <> struct LinearPolyBaseTypeTraits<ElementCount> { |
382 | using ScalarTy = unsigned; |
383 | static constexpr unsigned Dimensions = 2; |
384 | }; |
385 | |
386 | class ElementCount : public LinearPolySize<ElementCount> { |
387 | public: |
388 | ElementCount() : LinearPolySize(LinearPolySize::getNull()) {} |
389 | |
390 | ElementCount(const LinearPolySize<ElementCount> &V) : LinearPolySize(V) {} |
391 | |
392 | /// Counting predicates. |
393 | /// |
394 | ///@{ Number of elements.. |
395 | /// Exactly one element. |
396 | bool isScalar() const { return !isScalable() && getKnownMinValue() == 1; } |
397 | /// One or more elements. |
398 | bool isVector() const { |
399 | return (isScalable() && getKnownMinValue() != 0) || getKnownMinValue() > 1; |
400 | } |
401 | ///@} |
402 | }; |
403 | |
404 | // This class is used to represent the size of types. If the type is of fixed |
405 | class TypeSize; |
406 | template <> struct LinearPolyBaseTypeTraits<TypeSize> { |
407 | using ScalarTy = uint64_t; |
408 | static constexpr unsigned Dimensions = 2; |
409 | }; |
410 | |
411 | // TODO: Most functionality in this class will gradually be phased out |
412 | // so it will resemble LinearPolySize as much as possible. |
413 | // |
414 | // TypeSize is used to represent the size of types. If the type is of fixed |
415 | // size, it will represent the exact size. If the type is a scalable vector, |
416 | // it will represent the known minimum size. |
417 | class TypeSize : public LinearPolySize<TypeSize> { |
418 | public: |
419 | TypeSize(const LinearPolySize<TypeSize> &V) : LinearPolySize(V) {} |
420 | TypeSize(ScalarTy MinVal, bool IsScalable) |
421 | : LinearPolySize(LinearPolySize::get(MinVal, IsScalable)) {} |
422 | |
423 | static TypeSize Fixed(ScalarTy MinVal) { return TypeSize(MinVal, false); } |
424 | static TypeSize Scalable(ScalarTy MinVal) { return TypeSize(MinVal, true); } |
425 | |
426 | ScalarTy getFixedSize() const { return getFixedValue(); } |
427 | ScalarTy getKnownMinSize() const { return getKnownMinValue(); } |
428 | |
429 | // All code for this class below this point is needed because of the |
430 | // temporary implicit conversion to uint64_t. The operator overloads are |
431 | // needed because otherwise the conversion of the parent class |
432 | // UnivariateLinearPolyBase -> TypeSize is ambiguous. |
433 | // TODO: Remove the implicit conversion. |
434 | |
435 | // Casts to a uint64_t if this is a fixed-width size. |
436 | // |
437 | // This interface is deprecated and will be removed in a future version |
438 | // of LLVM in favour of upgrading uses that rely on this implicit conversion |
439 | // to uint64_t. Calls to functions that return a TypeSize should use the |
440 | // proper interfaces to TypeSize. |
441 | // In practice this is mostly calls to MVT/EVT::getSizeInBits(). |
442 | // |
443 | // To determine how to upgrade the code: |
444 | // |
445 | // if (<algorithm works for both scalable and fixed-width vectors>) |
446 | // use getKnownMinValue() |
447 | // else if (<algorithm works only for fixed-width vectors>) { |
448 | // if <algorithm can be adapted for both scalable and fixed-width vectors> |
449 | // update the algorithm and use getKnownMinValue() |
450 | // else |
451 | // bail out early for scalable vectors and use getFixedValue() |
452 | // } |
453 | operator ScalarTy() const; |
454 | |
455 | // Additional operators needed to avoid ambiguous parses |
456 | // because of the implicit conversion hack. |
457 | friend TypeSize operator*(const TypeSize &LHS, const int RHS) { |
458 | return LHS * (ScalarTy)RHS; |
459 | } |
460 | friend TypeSize operator*(const TypeSize &LHS, const unsigned RHS) { |
461 | return LHS * (ScalarTy)RHS; |
462 | } |
463 | friend TypeSize operator*(const TypeSize &LHS, const int64_t RHS) { |
464 | return LHS * (ScalarTy)RHS; |
465 | } |
466 | friend TypeSize operator*(const int LHS, const TypeSize &RHS) { |
467 | return RHS * LHS; |
468 | } |
469 | friend TypeSize operator*(const unsigned LHS, const TypeSize &RHS) { |
470 | return RHS * LHS; |
471 | } |
472 | friend TypeSize operator*(const int64_t LHS, const TypeSize &RHS) { |
473 | return RHS * LHS; |
474 | } |
475 | friend TypeSize operator*(const uint64_t LHS, const TypeSize &RHS) { |
476 | return RHS * LHS; |
477 | } |
478 | }; |
479 | |
480 | //===----------------------------------------------------------------------===// |
481 | // Utilities |
482 | //===----------------------------------------------------------------------===// |
483 | |
484 | /// Returns a TypeSize with a known minimum size that is the next integer |
485 | /// (mod 2**64) that is greater than or equal to \p Value and is a multiple |
486 | /// of \p Align. \p Align must be non-zero. |
487 | /// |
488 | /// Similar to the alignTo functions in MathExtras.h |
489 | inline TypeSize alignTo(TypeSize Size, uint64_t Align) { |
490 | assert(Align != 0u && "Align must be non-zero" ); |
491 | return {(Size.getKnownMinValue() + Align - 1) / Align * Align, |
492 | Size.isScalable()}; |
493 | } |
494 | |
495 | /// Stream operator function for `LinearPolySize`. |
496 | template <typename LeafTy> |
497 | inline raw_ostream &operator<<(raw_ostream &OS, |
498 | const LinearPolySize<LeafTy> &PS) { |
499 | PS.print(OS); |
500 | return OS; |
501 | } |
502 | |
503 | template <typename T> struct DenseMapInfo; |
504 | template <> struct DenseMapInfo<ElementCount> { |
505 | static inline ElementCount getEmptyKey() { |
506 | return ElementCount::getScalable(~0U); |
507 | } |
508 | static inline ElementCount getTombstoneKey() { |
509 | return ElementCount::getFixed(~0U - 1); |
510 | } |
511 | static unsigned getHashValue(const ElementCount &EltCnt) { |
512 | unsigned HashVal = EltCnt.getKnownMinValue() * 37U; |
513 | if (EltCnt.isScalable()) |
514 | return (HashVal - 1U); |
515 | |
516 | return HashVal; |
517 | } |
518 | |
519 | static bool isEqual(const ElementCount &LHS, const ElementCount &RHS) { |
520 | return LHS == RHS; |
521 | } |
522 | }; |
523 | |
524 | } // end namespace llvm |
525 | |
526 | #endif // LLVM_SUPPORT_TYPESIZE_H |
527 | |