CIRCT 23.0.0git
Loading...
Searching...
No Matches
Values.cpp
Go to the documentation of this file.
1//===- Values.cpp - ESI value system impl ----------------------*- 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#include "esi/Values.h"
10#include <algorithm>
11#include <cstring>
12#include <format>
13
14using namespace esi;
15
16BitVector::BitVector(std::span<const byte> bytes, std::optional<size_t> width,
17 uint8_t bitIndex)
18 : bitWidth(width ? *width : bytes.size() * 8), bitIndex(bitIndex) {
19 data = bytes;
20 if (bitIndex > 7)
21 throw std::invalid_argument("bitIndex must be <= 7");
22 size_t totalBitsAvail = bytes.size() * 8 - bitIndex;
23 if (bitWidth > totalBitsAvail)
24 throw std::invalid_argument(
25 std::format("Width of {} bits exceeds provided storage of {} bits",
26 bitWidth, totalBitsAvail));
27}
28
30 : data(other.data), bitWidth(other.bitWidth), bitIndex(other.bitIndex) {}
31
33 if (this == &other)
34 return *this;
35 data = other.data;
36 bitWidth = other.bitWidth;
37 bitIndex = other.bitIndex;
38 return *this;
39}
40
41bool BitVector::getBit(size_t i) const {
42 if (i >= bitWidth)
43 throw std::out_of_range("Bit index out of range");
44 size_t absoluteBit = bitIndex + i;
45 size_t byteIdx = absoluteBit / 8;
46 uint8_t bitOff = absoluteBit % 8;
47 return (data[byteIdx] >> bitOff) & 0x1;
48}
49
51 // Shifting by more than the current logical width is invalid.
52 if (n > bitWidth)
53 throw std::out_of_range("Right shift exceeds bit width");
54 // Shifting by exactly the width yields an empty (zero-width) vector.
55 if (n == bitWidth) {
57 return empty;
58 }
59
60 // Create a new view with adjusted bitIndex and width
61 BitVector result = *this;
62 result.bitWidth -= n;
63
64 // Compute new bitIndex and byte offset
65 size_t totalBitOffset = bitIndex + n;
66 size_t byteOffset = totalBitOffset / 8;
67 result.bitIndex = static_cast<uint8_t>(totalBitOffset % 8);
68
69 // Skip the appropriate number of bytes
70 if (byteOffset > 0 && byteOffset < data.size())
71 result.data = data.subspan(byteOffset);
72 else if (byteOffset >= data.size())
73 result.data = data.subspan(data.size());
74
75 return result;
76}
77
79 *this = *this >> n;
80 return *this;
81}
82
83BitVector BitVector::slice(size_t offset, size_t sliceWidth) const {
84 if (offset > bitWidth || sliceWidth > bitWidth - offset)
85 throw std::invalid_argument("slice range exceeds current width");
86 // Return a new view with adjusted bitIndex
87 BitVector result;
88 result.bitWidth = sliceWidth;
89 result.bitIndex = static_cast<uint8_t>(bitIndex + (offset % 8));
90 size_t byteOffset = offset / 8;
91 result.data = data.subspan(byteOffset);
92 if (result.bitIndex >= 8) {
93 result.bitIndex -= 8;
94 result.data = result.data.subspan(1);
95 }
96 return result;
97}
98
99MutableBitVector::MutableBitVector(size_t width) : owner((width + 7) / 8, 0) {
100 bitWidth = width;
101 bitIndex = 0;
102 data = std::span<const byte>(owner.data(), owner.size());
103}
104
105MutableBitVector::MutableBitVector(std::vector<byte> &&bytes,
106 std::optional<size_t> width)
107 : owner(std::move(bytes)) {
108 bitWidth = width ? *width : owner.size() * 8;
109 bitIndex = 0;
110 data = std::span<const byte>(owner.data(), owner.size());
111 if (bitWidth > data.size() * 8)
112 throw std::invalid_argument("Width exceeds provided storage");
113}
114
116 : BitVector(), owner(other.owner) {
117 bitWidth = other.bitWidth;
118 bitIndex = 0;
119 data = std::span<const byte>(owner.data(), owner.size());
120}
121
123 : BitVector(), owner(std::vector<byte>((other.width() + 7) / 8, 0)) {
124 bitWidth = other.width();
125 bitIndex = 0;
126 data = std::span<const byte>(owner.data(), owner.size());
127 // Copy bits from source
128 for (size_t i = 0; i < bitWidth; ++i)
129 if (other.getBit(i))
130 setBit(i, true);
131}
132
134 : BitVector(), owner(std::vector<byte>((other.width() + 7) / 8, 0)) {
135 bitWidth = other.width();
136 bitIndex = 0;
137 data = std::span<const byte>(owner.data(), owner.size());
138 // Copy bits from source
139 for (size_t i = 0; i < bitWidth; ++i)
140 if (other.getBit(i))
141 setBit(i, true);
142}
143
145 : BitVector(), owner(std::move(other.owner)) {
146 bitWidth = other.bitWidth;
147 bitIndex = 0;
148 data = std::span<const byte>(owner.data(), owner.size());
149 other.data = {};
150 other.bitWidth = 0;
151 other.bitIndex = 0;
152}
153
155 if (this == &other)
156 return *this;
157 owner = other.owner;
158 bitWidth = other.bitWidth;
159 bitIndex = 0;
160 data = std::span<const byte>(owner.data(), owner.size());
161 return *this;
162}
163
166 if (this == &other)
167 return *this;
168 owner = std::move(other.owner);
169 bitWidth = other.bitWidth;
170 bitIndex = 0;
171 data = std::span<const byte>(owner.data(), owner.size());
172 other.data = {};
173 other.bitWidth = 0;
174 other.bitIndex = 0;
175 return *this;
176}
177
178void MutableBitVector::setBit(size_t i, bool v) {
179 if (i >= bitWidth)
180 throw std::out_of_range("Bit index out of range");
181 size_t byteIdx = i / 8;
182 uint8_t bitOff = i % 8;
183 uint8_t mask = static_cast<uint8_t>(1u << bitOff);
184 byte &target = owner[byteIdx];
185 if (v)
186 target |= mask;
187 else
188 target &= static_cast<uint8_t>(~mask);
189}
190
192 // Shifting by more than the current logical width is invalid.
193 if (n > bitWidth)
194 throw std::out_of_range("Right shift exceeds bit width");
195 // Shifting by exactly the width yields an empty (zero-width) vector.
196 if (n == bitWidth) {
197 bitWidth = 0;
198 return *this;
199 }
200 bitWidth -= n;
201 bitIndex = static_cast<uint8_t>(bitIndex + n);
202 while (bitIndex >= 8) {
203 bitIndex -= 8;
204 if (data.size() == 0)
205 break;
206 data = data.subspan(1);
207 }
208 return *this;
209}
210
212 // Extend width by n bits, shifting existing bits upward by n and inserting
213 // n zero bits at the least-significant positions.
214 if (n == 0)
215 return *this;
216 size_t oldWidth = bitWidth;
217 size_t newWidth = oldWidth + n;
218 // Always produce an owning, tightly packed representation with bitIndex=0.
219 std::vector<byte> newStorage((newWidth + 7) / 8, 0);
220 // Copy old bits to new positions.
221 for (size_t i = 0; i < oldWidth; ++i)
222 if (getBit(i)) {
223 size_t newPos = i + n; // shifted upward
224 newStorage[newPos / 8] |= static_cast<byte>(1u << (newPos % 8));
225 }
226 owner = std::move(newStorage);
227 data = std::span<const byte>(owner.data(), owner.size());
228 bitWidth = newWidth;
229 bitIndex = 0;
230 return *this;
231}
232
234 // Concatenation: append bits from other (must create new owning storage)
235 size_t oldWidth = bitWidth;
236 size_t otherWidth = other.width();
237 size_t newWidth = oldWidth + otherWidth;
238 std::vector<byte> newStorage((newWidth + 7) / 8, 0);
239 // Copy this's bits
240 for (size_t i = 0; i < oldWidth; ++i)
241 if (getBit(i)) {
242 newStorage[i / 8] |= static_cast<byte>(1u << (i % 8));
243 }
244 // Copy other's bits offset by oldWidth
245 for (size_t i = 0; i < otherWidth; ++i)
246 if (other.getBit(i)) {
247 size_t newPos = i + oldWidth;
248 newStorage[newPos / 8] |= static_cast<byte>(1u << (newPos % 8));
249 }
250 owner = std::move(newStorage);
251 data = std::span<const byte>(owner.data(), owner.size());
252 bitWidth = newWidth;
253 bitIndex = 0;
254 return *this;
255}
256
257MutableBitVector esi::operator&(const BitVector &a, const BitVector &b) {
258 if (a.width() != b.width())
259 throw std::invalid_argument("Bitwise & require equal widths");
260 MutableBitVector result(a.width());
261 for (size_t i = 0; i < a.width(); ++i)
262 if (a.getBit(i) && b.getBit(i))
263 result.setBit(i, true);
264 return result;
265}
266
267MutableBitVector esi::operator|(const BitVector &a, const BitVector &b) {
268 if (a.width() != b.width())
269 throw std::invalid_argument("Bitwise ops require equal widths");
270 MutableBitVector result(a.width());
271 for (size_t i = 0; i < a.width(); ++i)
272 if (a.getBit(i) || b.getBit(i))
273 result.setBit(i, true);
274 return result;
275}
276
277MutableBitVector esi::operator^(const BitVector &a, const BitVector &b) {
278 if (a.width() != b.width())
279 throw std::invalid_argument("Bitwise ops require equal widths");
280 MutableBitVector result(a.width());
281 for (size_t i = 0; i < a.width(); ++i)
282 if (a.getBit(i) != b.getBit(i))
283 result.setBit(i, true);
284 return result;
285}
286
287// Helper: convert to hexadecimal / octal (power-of-two bases) without
288// allocating large temporaries. bitsPerDigit must be 1/3/4.
289static std::string toPowerOfTwoString(const BitVector &bv,
290 unsigned bitsPerDigit, bool uppercase) {
291 if (bv.width() == 0)
292 return "0";
293 unsigned groups = (bv.width() + bitsPerDigit - 1) / bitsPerDigit;
294 std::string out;
295 out.reserve(groups);
296 auto hexDigit = [&](unsigned v) -> char {
297 if (v < 10)
298 return static_cast<char>('0' + v);
299 return static_cast<char>((uppercase ? 'A' : 'a') + (v - 10));
300 };
301 for (int g = static_cast<int>(groups) - 1; g >= 0; --g) {
302 unsigned value = 0;
303 for (unsigned j = 0; j < bitsPerDigit; ++j) {
304 unsigned bitIdx = g * bitsPerDigit + j;
305 if (bitIdx < bv.width() && bv.getBit(bitIdx))
306 value |= (1u << j); // LSB-first inside digit
307 }
308 if (out.empty() && value == 0 && g != 0)
309 continue; // skip leading zeros
310 if (bitsPerDigit == 4)
311 out.push_back(hexDigit(value));
312 else // octal (3 bits) or binary (1 bit)
313 out.push_back(static_cast<char>('0' + value));
314 }
315 if (out.empty())
316 return "0"; // all zeros
317 return out;
318}
319
320// Helper: decimal conversion via base 1e9 limbs (little-endian).
321static std::string toDecimalString(const BitVector &bv) {
322 if (bv.width() == 0)
323 return "0";
324 constexpr uint32_t BASE = 1000000000u; // 1e9
325 std::vector<uint32_t> limbs(1, 0);
326 // Iterate MSB->LSB so we can perform: acc = acc*2 + bit
327 for (size_t idx = 0; idx < bv.width(); ++idx) {
328 size_t i = bv.width() - 1 - idx;
329 // acc *= 2
330 uint64_t carry = 0;
331 for (auto &d : limbs) {
332 uint64_t v = static_cast<uint64_t>(d) * 2 + carry;
333 d = static_cast<uint32_t>(v % BASE);
334 carry = v / BASE;
335 }
336 if (carry)
337 limbs.push_back(static_cast<uint32_t>(carry));
338 // acc += bit
339 if (bv.getBit(i)) {
340 uint64_t c = 1;
341 for (auto &d : limbs) {
342 uint64_t v = static_cast<uint64_t>(d) + c;
343 d = static_cast<uint32_t>(v % BASE);
344 c = v / BASE;
345 if (!c)
346 break;
347 }
348 if (c)
349 limbs.push_back(static_cast<uint32_t>(c));
350 }
351 }
352 // Convert limbs to string.
353 std::string out = std::to_string(limbs.back());
354 for (int i = static_cast<int>(limbs.size()) - 2; i >= 0; --i) {
355 std::string chunk = std::to_string(limbs[i]);
356 out.append(9 - chunk.size(), '0');
357 out += chunk;
358 }
359 return out;
360}
361
362std::string BitVector::toString(unsigned base) const {
363 switch (base) {
364 case 2: {
365 // Binary (MSB -> LSB) representation.
366 if (bitWidth == 0)
367 return std::string("0");
368 std::string s;
369 s.reserve(bitWidth);
370 for (size_t i = 0; i < bitWidth; ++i)
371 s.push_back(getBit(bitWidth - 1 - i) ? '1' : '0');
372 return s;
373 }
374 case 8:
375 return toPowerOfTwoString(*this, 3, false);
376 case 16:
377 return toPowerOfTwoString(*this, 4, false);
378 case 10:
379 return toDecimalString(*this);
380 default:
381 throw std::invalid_argument(
382 std::format("Unsupported base '{}' for BitVector::toString", base));
383 }
384}
385
386std::ostream &esi::operator<<(std::ostream &os, const BitVector &bv) {
387 using std::ios_base;
388 ios_base::fmtflags basefmt = os.flags() & ios_base::basefield;
389 bool showBase = (os.flags() & ios_base::showbase) != 0;
390 bool upper = (os.flags() & ios_base::uppercase) != 0;
391 std::string body;
392 switch (basefmt) {
393 case ios_base::hex: {
394 body = toPowerOfTwoString(bv, 4, upper);
395 if (showBase)
396 os << (upper ? "0X" : "0x");
397 os << body;
398 return os;
399 }
400 case ios_base::oct: {
401 body = toPowerOfTwoString(bv, 3, false);
402 if (showBase)
403 os << '0';
404 os << body;
405 return os;
406 }
407 case ios_base::dec: {
408 body = toDecimalString(bv);
409 os << body; // showbase ignored for dec (matches standard)
410 return os;
411 }
412 default: { // Fallback: binary string (no standard manipulator for this)
413 body = bv.toString(2);
414 os << body;
415 return os;
416 }
417 }
418}
419
420bool BitVector::operator==(const BitVector &rhs) const {
421 if (bitWidth != rhs.bitWidth)
422 return false;
423 return std::ranges::equal(*this, rhs);
424}
425
426// Loop-peeled byte-walk over bits [lowExclusive, bitWidth-1]: the first
427// and last bytes may start/end mid-byte and need masked comparisons; every
428// byte in between is a full-byte XOR-with-expected. The masked mismatches
429// are OR-accumulated into a single byte so the only branch is the final
430// throw decision, and the middle-byte loop body is just
431// `diff |= data[b] ^ expected` -- which the compiler can auto-vectorise.
432//
433// In the common emitted-accessor case where `bitIndex == 0` and
434// `lowExclusive` is a multiple of 8 (it always is: 8, 16, 32, or 64 for
435// the narrow-int conversions), `loBit == 0` and the first-byte peel
436// collapses to a full-byte compare too -- the optimiser folds it.
437void BitVector::checkHighBytesEqual(unsigned lowExclusive, uint8_t expectedByte,
438 const char *fmt, unsigned target) const {
439 const size_t absLo = bitIndex + lowExclusive;
440 const size_t absHi = bitIndex + bitWidth - 1;
441 const size_t byteLo = absLo / 8;
442 const size_t byteHi = absHi / 8;
443 const unsigned loBit = absLo & 7u;
444 const unsigned hiBit = absHi & 7u;
445 uint8_t diff;
446 if (byteLo == byteHi) {
447 // Whole range fits in one byte; mask = bits [loBit, hiBit].
448 const uint8_t mask =
449 static_cast<uint8_t>(((1u << (hiBit - loBit + 1u)) - 1u) << loBit);
450 diff = static_cast<uint8_t>(data[byteLo] ^ expectedByte) & mask;
451 } else {
452 // Peel the first byte: mask off bits below loBit.
453 const uint8_t firstMask = static_cast<uint8_t>(0xFFu << loBit);
454 diff = static_cast<uint8_t>(data[byteLo] ^ expectedByte) & firstMask;
455 // Middle bytes: full-byte mask, branchless body.
456 for (size_t b = byteLo + 1; b < byteHi; ++b)
457 diff |= static_cast<uint8_t>(data[b] ^ expectedByte);
458 // Peel the last byte: mask off bits above hiBit.
459 const uint8_t lastMask = static_cast<uint8_t>((1u << (hiBit + 1u)) - 1u);
460 diff |= static_cast<uint8_t>(data[byteHi] ^ expectedByte) & lastMask;
461 }
462 if (diff != 0)
463 throw std::overflow_error(std::vformat(fmt, std::make_format_args(target)));
464}
465
466UInt::UInt(uint64_t v, unsigned width) : MutableBitVector(width) {
467 if (width > 0 && width < 64 && (v >> width) != 0)
468 throw std::overflow_error(
469 std::format("Value {} does not fit in {} bits", v, width));
470 for (size_t i = 0; i < width; ++i)
471 if ((v >> i) & 1)
472 setBit(i, true);
473}
474
475Int::Int(int64_t v, unsigned width) : MutableBitVector(width) {
476 if (width > 0 && width < 64) {
477 int64_t maxVal = (1LL << (width - 1)) - 1;
478 int64_t minVal = -(1LL << (width - 1));
479 if (v < minVal || v > maxVal)
480 throw std::overflow_error(
481 std::format("Value {} does not fit in {} bits", v, width));
482 }
483 for (size_t i = 0; i < width; ++i)
484 if ((v >> i) & 1)
485 setBit(i, true);
486}
487
489 if (bitWidth != other.bitWidth)
490 throw std::invalid_argument("Bitwise &= requires equal widths");
491 for (size_t i = 0; i < bitWidth; ++i)
492 setBit(i, getBit(i) && other.getBit(i));
493 return *this;
494}
495
497 if (bitWidth < other.bitWidth)
498 throw std::invalid_argument("Bitwise |= requires <= widths");
499 for (size_t i = 0; i < other.bitWidth; ++i)
500 setBit(i, getBit(i) || other.getBit(i));
501 return *this;
502}
503
505 if (bitWidth != other.bitWidth)
506 throw std::invalid_argument("Bitwise ^= requires equal widths");
507 for (size_t i = 0; i < bitWidth; ++i)
508 setBit(i, getBit(i) != other.getBit(i));
509 return *this;
510}
513 for (size_t i = 0; i < bitWidth; ++i)
514 res.setBit(i, !getBit(i));
515 return res;
516}
519 MutableBitVector result(static_cast<const BitVector &>(*this));
520 result |= other;
521 return result;
522}
523
526 MutableBitVector result(static_cast<const BitVector &>(*this));
527 result &= other;
528 return result;
529}
530
533 MutableBitVector result(static_cast<const BitVector &>(*this));
534 result ^= other;
535 return result;
536}
537
538int64_t IntView::toI64() const {
539 if (this->bitWidth == 0)
540 return 0;
541 uint64_t u = 0;
542 size_t limit = this->bitWidth < 64 ? this->bitWidth : 64;
543 for (size_t i = 0; i < limit; ++i)
544 if (this->getBit(i))
545 u |= (1ULL << i);
546 if (this->bitWidth < 64) {
547 // Source is narrower than int64_t: sign-extend from the source's top bit.
548 if (this->getBit(this->bitWidth - 1))
549 for (size_t i = this->bitWidth; i < 64; ++i)
550 u |= (1ULL << i);
551 return static_cast<int64_t>(u);
552 }
553 if (this->bitWidth > 64) {
554 // Source is wider than int64_t: bits [64, width-1] must all equal the
555 // destination's sign bit (bit 63 of the low 64 we're about to return),
556 // not bit `width-1` of the source. A wide value like 2^63 has source
557 // top-bit 0 but cannot fit in int64_t -- the low 64 would alias
558 // INT64_MIN if we used the source's top bit as the expected fill.
559 const bool destSignBit = (u >> 63) & 1ULL;
560 checkHighBytesEqual(64, destSignBit ? uint8_t{0xFF} : uint8_t{0x00},
561 "Int does not fit in int{}_t", 64);
562 }
563 return static_cast<int64_t>(u);
564}
565
566uint64_t UIntView::toUI64() const {
567 if (this->bitWidth > 64)
568 checkHighBytesEqual(64, uint8_t{0x00}, "UInt does not fit in uint{}_t", 64);
569 uint64_t u = 0;
570 size_t limit = this->bitWidth < 64 ? this->bitWidth : 64;
571 for (size_t i = 0; i < limit; ++i)
572 if (this->getBit(i))
573 u |= (1ULL << i);
574 return u;
575}
static InstancePath empty
static std::string toPowerOfTwoString(const BitVector &bv, unsigned bitsPerDigit, bool uppercase)
Definition Values.cpp:289
static std::string toDecimalString(const BitVector &bv)
Definition Values.cpp:321
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 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
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
std::string toString(unsigned base=16) const
Definition Values.cpp:362
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
friend MutableBitVector operator|(const BitVector &a, const BitVector &b)
Bitwise OR: creates a new MutableBitVector with the result.
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
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
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
uint64_t toUI64() const
Convert to a uint64_t, throwing if the value does not fit.
Definition Values.cpp:566
UInt()=default
Definition esi.py:1
std::ostream & operator<<(std::ostream &os, const BitVector &bv)
Definition Values.cpp:386