CIRCT 23.0.0git
Loading...
Searching...
No Matches
Values.h
Go to the documentation of this file.
1//===- values.h - ESI value system -------------------------------* 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// ESI arbitrary width bitvector and integer types.
10// These types are not meant to be highly optimized. Rather, its a simple
11// implementation to support arbitrary bit widths for ESI runtime values.
12//
13//===----------------------------------------------------------------------===//
14// DO NOT EDIT!
15// This file is distributed as part of an ESI package. The source for this file
16// should always be modified within CIRCT.
17//
18//===----------------------------------------------------------------------===//
19
20// NOLINTNEXTLINE(llvm-header-guard)
21#ifndef ESI_VALUES_H
22#define ESI_VALUES_H
23
24#include <cstdint>
25#include <format>
26#include <memory> // (may be removable later)
27#include <optional>
28#include <ostream>
29#include <span>
30#include <stdexcept>
31#include <string>
32#include <vector>
33
34namespace esi {
35
36class MutableBitVector;
37
38/// A lightweight, non-owning bit vector view backed by a byte array span.
39/// BitVector is immutable wrt. modifying the underlying bits, and provides
40/// read-only access to bits. It supports bit-level access and returns new views
41/// for operations.
42///
43/// Lifetime: `BitVector`, `IntView`, and `UIntView` (defined just below)
44/// are non-owning views, in the same family as `std::span` /
45/// `std::string_view`. They store only a span pointer + width + bitIndex
46/// and do not extend the lifetime of the underlying buffer. A view
47/// returned from an accessor of a temporary dangles once the temporary
48/// is destroyed; bind the parent to a named local, or construct an
49/// owning `Int` / `UInt` / `MutableBitVector` from the view if the value
50/// needs to outlive its source.
51class BitVector {
52public:
53 using byte = uint8_t;
54
55 /// Construct from an existing span. Width defaults to the number of bits in
56 /// the span (size * 8). The BitVector does not take ownership.
57 BitVector(std::span<const byte> bytes,
58 std::optional<size_t> width = std::nullopt, uint8_t bitIndex = 0);
59 BitVector() = default;
60 BitVector(const BitVector &other);
61 BitVector &operator=(const BitVector &other);
62
63 size_t width() const { return bitWidth; }
64 size_t size() const { return width(); }
65
66 /// Return the i-th bit (0 = LSB) as boolean.
67 bool getBit(size_t i) const;
68
69 /// Return a handle to the underlying span. Throws if the current bit index
70 /// is not 0 (since a non-zero bit offset breaks raw byte alignment).
71 std::span<const byte> getSpan() const {
72 if (bitIndex != 0)
73 throw std::runtime_error("Cannot get data span with non-zero bit index");
74 return data;
75 }
76
77 /// Logical right shift that drops the least-significant n bits by advancing
78 /// the byte/bit index and reducing width. Returns a new immutable
79 /// view. Does not modify the underlying storage contents.
80 BitVector operator>>(size_t n) const;
81 BitVector &operator>>=(size_t n);
82
83 /// Create a new immutable view of a contiguous bit slice [offset,
84 /// offset+sliceWidth). The returned BitVector is a view (not an owning copy)
85 /// into the same underlying span. Throws if the requested slice exceeds the
86 /// current width.
87 BitVector slice(size_t offset, size_t sliceWidth) const;
88
89 /// Return a view of the N least-significant bits.
90 BitVector lsb(size_t n) const { return slice(0, n); }
91
92 /// Return a view of the N most-significant bits.
93 BitVector msb(size_t n) const {
94 if (n > bitWidth)
95 throw std::invalid_argument("msb width exceeds bit width");
96 return slice(bitWidth - n, n);
97 }
98
99 std::string toString(unsigned base = 16) const;
100
101 bool operator==(const BitVector &rhs) const;
102 bool operator!=(const BitVector &rhs) const { return !(*this == rhs); }
103
104 /// Bitwise AND: creates a new MutableBitVector with the result.
105 friend MutableBitVector operator&(const BitVector &a, const BitVector &b);
106
107 /// Bitwise OR: creates a new MutableBitVector with the result.
108 friend MutableBitVector operator|(const BitVector &a, const BitVector &b);
109
110 /// Bitwise XOR: creates a new MutableBitVector with the result.
111 friend MutableBitVector operator^(const BitVector &a, const BitVector &b);
112
113 /// Forward iterator for iterating over bits from LSB (index 0) to MSB.
115 public:
116 using difference_type = std::ptrdiff_t;
117 using value_type = bool;
118 using pointer = const bool *;
119 using reference = bool;
120 using iterator_category = std::forward_iterator_tag;
121
122 /// Default constructor.
123 bit_iterator() = default;
124
125 /// Construct an iterator at the given bit position.
126 bit_iterator(const BitVector *bv, size_t pos = 0)
127 : bitVector(bv), position(pos) {}
128
129 /// Dereference: returns the bit value at the current position.
130 bool operator*() const {
131 if (bitVector == nullptr || position >= bitVector->bitWidth)
132 throw std::out_of_range("bit_iterator dereference out of range");
133 return bitVector->getBit(position);
134 }
135
136 /// Pre-increment: move to next bit.
138 ++position;
139 return *this;
140 }
141
142 /// Post-increment: move to next bit.
144 bit_iterator tmp = *this;
145 ++position;
146 return tmp;
147 }
148
149 /// Equality comparison.
150 bool operator==(const bit_iterator &other) const {
151 return bitVector == other.bitVector && position == other.position;
152 }
153
154 /// Inequality comparison.
155 bool operator!=(const bit_iterator &other) const {
156 return !(*this == other);
157 }
158
159 /// Less-than comparison (for ranges support).
160 bool operator<(const bit_iterator &other) const {
161 return bitVector == other.bitVector && position < other.position;
162 }
163
164 /// Sentinel-compatible equality (for ranges support).
165 bool operator==(std::default_sentinel_t) const {
166 return bitVector == nullptr || position >= bitVector->bitWidth;
167 }
168
169 /// Sentinel-compatible inequality.
170 bool operator!=(std::default_sentinel_t sent) const {
171 return !(*this == sent);
172 }
173
174 private:
175 const BitVector *bitVector = nullptr;
176 size_t position = 0;
177 };
178
179 /// Return an iterator to the first bit (LSB).
180 bit_iterator begin() const { return bit_iterator(this, 0); }
181
182 /// Return an iterator past the last bit.
183 bit_iterator end() const { return bit_iterator(this, bitWidth); }
184
185protected:
186 // Underlying storage view. const, to allow for non-owning immutable views.
187 std::span<const byte> data{};
188 size_t bitWidth = 0; // Number of valid bits.
189 uint8_t bitIndex = 0; // Starting bit offset in first byte.
190
191 // Scan bits [lowExclusive, width()-1] in byte-sized chunks and throw
192 // `std::overflow_error(std::vformat(fmt, std::make_format_args(target)))`
193 // if any of them differ from `expectedByte` (0x00 to check
194 // "all-zero" for the unsigned narrow-conversion case; 0xFF to check
195 // "all-one" for the signed narrow-conversion case with the sign bit
196 // set).
197 void checkHighBytesEqual(unsigned lowExclusive, uint8_t expectedByte,
198 const char *fmt, unsigned target) const;
199};
200
201/// Non-owning view of an unsigned bit vector with `toUI64()` and implicit
202/// conversions to unsigned scalar types. Adds only static-type tagging and
203/// conversion methods on top of `BitVector`. See the lifetime note on
204/// `BitVector`.
205class UIntView : public BitVector {
206public:
208 UIntView() = default;
209 /// Adopt an existing view as unsigned. Cheap (no copy of bytes); the
210 /// resulting UIntView aliases the same buffer.
211 UIntView(const BitVector &v) : BitVector(v) {}
212
213 /// Convert to a `uint64_t`, throwing if the value does not fit.
214 uint64_t toUI64() const;
215 operator uint64_t() const { return toUI64(); }
216 operator uint32_t() const { return toUInt<uint32_t>(); }
217 operator uint16_t() const { return toUInt<uint16_t>(); }
218 operator uint8_t() const { return toUInt<uint8_t>(); }
219
220private:
221 template <typename T>
222 T toUInt() const {
223 static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,
224 "T must be an unsigned integral type");
225 constexpr unsigned N = sizeof(T) * 8;
226 // Pre-conversion range check: bits [N, width-1] must all be zero
227 // for the value to fit in unsigned `T`. We have to do this
228 // *before* `toUI64()` because `toUI64()` would itself throw
229 // "does not fit in uint64_t" for any wide value where bits 64+
230 // are set -- even if the low N bits would have fit in `T`.
231 if (this->width() > N)
232 this->checkHighBytesEqual(N, /*expectedByte=*/uint8_t{0},
233 "UInt does not fit in uint{}_t", N);
234 return static_cast<T>(toUI64());
235 }
236};
237
238/// Non-owning view of a signed bit vector with `toI64()` and implicit
239/// conversions to signed scalar types. See the lifetime note on `BitVector`.
240class IntView : public BitVector {
241public:
243 IntView() = default;
244 /// Adopt an existing view as signed. Cheap (no copy of bytes); the
245 /// resulting IntView aliases the same buffer.
246 IntView(const BitVector &v) : BitVector(v) {}
247
248 /// Convert to an `int64_t`, sign-extending from the high bit and throwing
249 /// if the value does not fit.
250 int64_t toI64() const;
251 operator int64_t() const { return toI64(); }
252 operator int32_t() const { return toInt<int32_t>(); }
253 operator int16_t() const { return toInt<int16_t>(); }
254 operator int8_t() const { return toInt<int8_t>(); }
255
256private:
257 template <typename T>
258 T toInt() const {
259 static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
260 "T must be a signed integral type");
261 constexpr unsigned N = sizeof(T) * 8;
262 // Pre-conversion range check: bits [N, width-1] must all equal
263 // the sign bit (bit N-1) for the value to fit in signed `T`. We
264 // have to do this *before* `toI64()` because `toI64()` would
265 // itself throw "does not fit in int64_t" for any wide value
266 // whose bits 64+ don't sign-extend bit 63 -- even if the actual
267 // problem is that the value doesn't fit in `T`.
268 if (this->width() > N) {
269 const uint8_t expected = this->getBit(N - 1) ? uint8_t{0xFF} : uint8_t{0};
270 this->checkHighBytesEqual(N, expected, "Int does not fit in int{}_t", N);
271 }
272 return static_cast<T>(toI64());
273 }
274};
275
276/// A mutable bit vector that owns its underlying storage.
277/// It supports in-place modifications and mutable operations.
279public:
280 /// Owning, zero-initialized constructor of a given width.
281 explicit MutableBitVector(size_t width);
282
283 /// Owning constructor from an rvalue vector (must move in).
284 MutableBitVector(std::vector<byte> &&bytes,
285 std::optional<size_t> width = std::nullopt);
286
287 MutableBitVector() = default;
288
289 // Copy constructor: duplicate storage.
291
292 // Copy constructor from immutable BitVector: creates owning copy.
293 MutableBitVector(const BitVector &other);
294
295 // Move constructor: transfer ownership.
296 MutableBitVector(MutableBitVector &&other) noexcept;
297
298 // Move constructor from immutable BitVector: creates owning copy.
300
302
304
305 /// Set the i-th bit.
306 void setBit(size_t i, bool v);
307
308 /// Return a handle to the underlying span (always aligned since bitIndex=0).
309 std::span<const byte> getSpan() const { return data; }
310
311 /// Return and transfer ownership of the underlying storage.
312 std::vector<uint8_t> takeStorage() { return std::move(owner); }
313
314 /// In-place logical right shift that drops the least-significant n bits.
315 /// Reduces width and updates internal state. Does not modify underlying
316 /// storage.
317 MutableBitVector &operator>>=(size_t n);
318
319 /// In-place logical left shift shifts in n zero bits at LSB, shifting
320 /// existing bits upward.
321 MutableBitVector &operator<<=(size_t n);
322
323 /// In-place concatenate: appends bits from other to this.
325
330 MutableBitVector operator|(const MutableBitVector &other) const;
331 MutableBitVector operator&(const MutableBitVector &other) const;
332 MutableBitVector operator^(const MutableBitVector &other) const;
333
334private:
335 // Storage owned by this MutableBitVector.
336 std::vector<byte> owner;
337};
338
339std::ostream &operator<<(std::ostream &os, const BitVector &bv);
340
341// Arbitrary width signed integer type built on MutableBitVector. The
342// scalar-conversion operators all delegate to `IntView` so the
343// canonical width-check + i64 conversion lives in one place.
344class Int : public MutableBitVector {
345public:
347 Int() = default;
348 Int(int64_t v, unsigned width = 64);
349 operator int64_t() const { return IntView(*this); }
350 operator int32_t() const { return IntView(*this); }
351 operator int16_t() const { return IntView(*this); }
352 operator int8_t() const { return IntView(*this); }
353};
354
355// Arbitrary width unsigned integer type built on MutableBitVector. The
356// scalar-conversion operators all delegate to `UIntView`.
357class UInt : public MutableBitVector {
358public:
360 UInt() = default;
361 UInt(uint64_t v, unsigned width = 64);
362 operator uint64_t() const { return UIntView(*this); }
363 operator uint32_t() const { return UIntView(*this); }
364 operator uint16_t() const { return UIntView(*this); }
365 operator uint8_t() const { return UIntView(*this); }
366};
367
368} // namespace esi
369
370// Enable BitVector and MutableBitVector to work with std::ranges algorithms
371template <>
372inline constexpr bool std::ranges::enable_borrowed_range<esi::BitVector> = true;
373
374template <>
375inline constexpr bool
376 std::ranges::enable_borrowed_range<esi::MutableBitVector> = true;
377
378#endif // ESI_VALUES_H
Forward iterator for iterating over bits from LSB (index 0) to MSB.
Definition Values.h:114
std::forward_iterator_tag iterator_category
Definition Values.h:120
bit_iterator & operator++()
Pre-increment: move to next bit.
Definition Values.h:137
std::ptrdiff_t difference_type
Definition Values.h:116
bool operator!=(std::default_sentinel_t sent) const
Sentinel-compatible inequality.
Definition Values.h:170
bool operator<(const bit_iterator &other) const
Less-than comparison (for ranges support).
Definition Values.h:160
bool operator==(const bit_iterator &other) const
Equality comparison.
Definition Values.h:150
bit_iterator()=default
Default constructor.
const BitVector * bitVector
Definition Values.h:175
bool operator==(std::default_sentinel_t) const
Sentinel-compatible equality (for ranges support).
Definition Values.h:165
bit_iterator(const BitVector *bv, size_t pos=0)
Construct an iterator at the given bit position.
Definition Values.h:126
bit_iterator operator++(int)
Post-increment: move to next bit.
Definition Values.h:143
bool operator!=(const bit_iterator &other) const
Inequality comparison.
Definition Values.h:155
bool operator*() const
Dereference: returns the bit value at the current position.
Definition Values.h:130
A lightweight, non-owning bit vector view backed by a byte array span.
Definition Values.h:51
size_t width() const
Definition Values.h:63
friend MutableBitVector operator^(const BitVector &a, const BitVector &b)
Bitwise XOR: creates a new MutableBitVector with the result.
void checkHighBytesEqual(unsigned lowExclusive, uint8_t expectedByte, const char *fmt, unsigned target) const
Definition Values.cpp:437
size_t size() const
Definition Values.h:64
size_t bitWidth
Definition Values.h:188
friend MutableBitVector operator&(const BitVector &a, const BitVector &b)
Bitwise AND: creates a new MutableBitVector with the result.
BitVector()=default
BitVector msb(size_t n) const
Return a view of the N most-significant bits.
Definition Values.h:93
uint8_t byte
Definition Values.h:53
BitVector slice(size_t offset, size_t sliceWidth) const
Create a new immutable view of a contiguous bit slice [offset, offset+sliceWidth).
Definition Values.cpp:83
BitVector & operator=(const BitVector &other)
Definition Values.cpp:32
bool getBit(size_t i) const
Return the i-th bit (0 = LSB) as boolean.
Definition Values.cpp:41
BitVector lsb(size_t n) const
Return a view of the N least-significant bits.
Definition Values.h:90
std::string toString(unsigned base=16) const
Definition Values.cpp:362
bit_iterator begin() const
Return an iterator to the first bit (LSB).
Definition Values.h:180
bool operator!=(const BitVector &rhs) const
Definition Values.h:102
std::span< const byte > data
Definition Values.h:187
BitVector & operator>>=(size_t n)
Definition Values.cpp:78
BitVector operator>>(size_t n) const
Logical right shift that drops the least-significant n bits by advancing the byte/bit index and reduc...
Definition Values.cpp:50
uint8_t bitIndex
Definition Values.h:189
bool operator==(const BitVector &rhs) const
Definition Values.cpp:420
bit_iterator end() const
Return an iterator past the last bit.
Definition Values.h:183
std::span< const byte > getSpan() const
Return a handle to the underlying span.
Definition Values.h:71
friend MutableBitVector operator|(const BitVector &a, const BitVector &b)
Bitwise OR: creates a new MutableBitVector with the result.
Non-owning view of a signed bit vector with toI64() and implicit conversions to signed scalar types.
Definition Values.h:240
T toInt() const
Definition Values.h:258
IntView()=default
IntView(const BitVector &v)
Adopt an existing view as signed.
Definition Values.h:246
int64_t toI64() const
Convert to an int64_t, sign-extending from the high bit and throwing if the value does not fit.
Definition Values.cpp:538
Int()=default
A mutable bit vector that owns its underlying storage.
Definition Values.h:278
MutableBitVector & operator|=(const MutableBitVector &other)
Definition Values.cpp:496
MutableBitVector & operator>>=(size_t n)
In-place logical right shift that drops the least-significant n bits.
Definition Values.cpp:191
std::vector< uint8_t > takeStorage()
Return and transfer ownership of the underlying storage.
Definition Values.h:312
MutableBitVector & operator<<=(size_t n)
In-place logical left shift shifts in n zero bits at LSB, shifting existing bits upward.
Definition Values.cpp:211
MutableBitVector operator~() const
Definition Values.cpp:511
std::span< const byte > getSpan() const
Return a handle to the underlying span (always aligned since bitIndex=0).
Definition Values.h:309
MutableBitVector & operator&=(const MutableBitVector &other)
Definition Values.cpp:488
MutableBitVector()=default
std::vector< byte > owner
Definition Values.h:336
void setBit(size_t i, bool v)
Set the i-th bit.
Definition Values.cpp:178
MutableBitVector & operator=(const MutableBitVector &other)
Definition Values.cpp:154
MutableBitVector & operator^=(const MutableBitVector &other)
Definition Values.cpp:504
Non-owning view of an unsigned bit vector with toUI64() and implicit conversions to unsigned scalar t...
Definition Values.h:205
T toUInt() const
Definition Values.h:222
uint64_t toUI64() const
Convert to a uint64_t, throwing if the value does not fit.
Definition Values.cpp:566
UIntView()=default
UIntView(const BitVector &v)
Adopt an existing view as unsigned.
Definition Values.h:211
UInt()=default
Definition esi.py:1
std::ostream & operator<<(std::ostream &os, const BitVector &bv)
Definition Values.cpp:386