CIRCT 23.0.0git
Loading...
Searching...
No Matches
codegen_harness.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2//
3// Round-trip harness driven by `tests/unit/test_codegen.py`. The Python
4// fixture builds a small manifest, runs `esiaccel.codegen` on it, and
5// compiles this file against the resulting `types.h`. The harness asserts
6// the generated accessors round-trip user-visible values *and* that the
7// underlying wire bytes match the manifest's bit-packed layout exactly.
8//
9// Reviewing this file is the easiest way to see what the C++ codegen
10// promises end-to-end; if anything here changes the runtime semantics
11// (rather than just the textual emission), this harness is what will
12// catch it.
13
14#include "codegen_harness/types.h"
15
16#include <array>
17#include <cassert>
18#ifdef _MSC_VER
19#include <crtdbg.h>
20#include <cstdlib>
21#endif
22#include <cstdint>
23#include <cstdio>
24#include <cstring>
25#include <ranges>
26#include <type_traits>
27#include <vector>
28
29#include "esi/Values.h"
30
31using namespace esi_system;
32
33namespace {
34
35// Reinterpret `value` as a `std::array<uint8_t, sizeof(T)>` view of its
36// wire bytes. Used to assert the underlying byte layout of generated
37// types — the only safe API for that is `reinterpret_cast<uint8_t *>`,
38// which the runtime already uses internally via `MessageData::from()`.
39template <typename T>
40std::array<uint8_t, sizeof(T)> wireBytes(const T &value) {
41 std::array<uint8_t, sizeof(T)> out{};
42 const auto *src = reinterpret_cast<const uint8_t *>(&value);
43 for (std::size_t i = 0; i < sizeof(T); ++i)
44 out[i] = src[i];
45 return out;
46}
47
48template <typename T, std::size_t N>
49void expectBytes(const T &value, const std::array<uint8_t, N> &expected,
50 const char *label) {
51 static_assert(sizeof(T) == N, "wire-byte assertion size mismatch");
52 auto got = wireBytes(value);
53 for (std::size_t i = 0; i < N; ++i) {
54 if (got[i] != expected[i]) {
55 std::fprintf(stderr,
56 "%s: byte %zu mismatch: got 0x%02x expected 0x%02x\n", label,
57 i, got[i], expected[i]);
58 std::fprintf(stderr, " got :");
59 for (auto b : got)
60 std::fprintf(stderr, " %02x", b);
61 std::fprintf(stderr, "\n expected:");
62 for (auto b : expected)
63 std::fprintf(stderr, " %02x", b);
64 std::fprintf(stderr, "\n");
65 std::abort();
66 }
67 }
68}
69
70// ---------------------------------------------------------------------------
71// Path A — 8/16/32/64-bit byte-aligned integers
72// ---------------------------------------------------------------------------
73
74void testStandardWidthUnsigned() {
75 StdU s;
76 s.u8(0xAB).u16(0xCAFE).u32(0xDEADBEEFu).u64(0x0123456789ABCDEFull);
77 assert(s.u8() == 0xAB);
78 assert(s.u16() == 0xCAFE);
79 assert(s.u32() == 0xDEADBEEFu);
80 assert(s.u64() == 0x0123456789ABCDEFull);
81
82 // Wire order is reverse of declaration: u64 at byte 0, u32 at byte 8,
83 // u16 at byte 12, u8 at byte 14. Each value is little-endian within
84 // its own bytes.
85 expectBytes(s,
86 std::array<uint8_t, 15>{
87 // u64 = 0x0123456789ABCDEF, little-endian:
88 0xEF,
89 0xCD,
90 0xAB,
91 0x89,
92 0x67,
93 0x45,
94 0x23,
95 0x01,
96 // u32 = 0xDEADBEEF, little-endian:
97 0xEF,
98 0xBE,
99 0xAD,
100 0xDE,
101 // u16 = 0xCAFE, little-endian:
102 0xFE,
103 0xCA,
104 // u8 = 0xAB:
105 0xAB,
106 },
107 "StdU");
108
109 // Default construction zero-initialises the wire bytes.
110 StdU empty;
111 expectBytes(
112 empty,
113 std::array<uint8_t, 15>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
114 "StdU empty");
115
116 // Logical-order ctor matches manifest field order.
117 StdU cons(0xAB, 0xCAFE, 0xDEADBEEFu, 0x0123456789ABCDEFull);
118 assert(wireBytes(cons) == wireBytes(s));
119}
120
121void testStandardWidthSigned() {
122 StdS s;
123 s.s8(-7).s16(-1000).s32(-1234567).s64(INT64_MIN);
124 assert(s.s8() == -7);
125 assert(s.s16() == -1000);
126 assert(s.s32() == -1234567);
127 assert(s.s64() == INT64_MIN);
128
129 // Default-construct a fresh value to confirm round-trip without the
130 // chained setter; check INT64_MAX as well to verify both ends of the
131 // range.
132 StdS pos(127, 32767, 2147483647, INT64_MAX);
133 assert(pos.s8() == 127);
134 assert(pos.s64() == INT64_MAX);
135}
136
137// ---------------------------------------------------------------------------
138// Path B — byte-aligned non-standard width (e.g. i24, i48)
139// ---------------------------------------------------------------------------
140
141void testByteAlignedOddWidthUnsigned() {
142 OddU s;
143 s.u24(0xABCDEFu);
144 assert(s.u24() == 0xABCDEFu);
145 // ui24 is 3 bytes little-endian on the wire.
146 expectBytes(s, std::array<uint8_t, 3>{0xEF, 0xCD, 0xAB}, "OddU");
147}
148
149void testByteAlignedOddWidthSigned() {
150 OddS s;
151 s.s24(-1);
152 assert(s.s24() == -1);
153 expectBytes(s, std::array<uint8_t, 3>{0xFF, 0xFF, 0xFF}, "OddS -1");
154
155 s.s24(-8388608); // si24 min
156 assert(s.s24() == -8388608);
157 expectBytes(s, std::array<uint8_t, 3>{0x00, 0x00, 0x80}, "OddS min");
158
159 s.s24(8388607); // si24 max
160 assert(s.s24() == 8388607);
161 expectBytes(s, std::array<uint8_t, 3>{0xFF, 0xFF, 0x7F}, "OddS max");
162
163 // Negative value with all three bytes non-zero. Round-trip + bit pattern.
164 s.s24(-1234567);
165 assert(s.s24() == -1234567);
166}
167
168// ---------------------------------------------------------------------------
169// Path C — sub-byte alignment (uses the BitAccess helpers)
170// ---------------------------------------------------------------------------
171
172void testSubByteUnsigned() {
173 SubU s;
174 s.u3(0b101).u12(0xABC);
175 assert(s.u3() == 0b101);
176 assert(s.u12() == 0xABC);
177
178 // Wire layout: u12 (low 12 bits) then u3 (next 3 bits), tightly packed.
179 // bit 0..11 = 0xABC -> bytes 0=0xBC, byte 1 low nibble = 0xA, high
180 // nibble starts u3
181 // bit 12..14 = 0b101 -> byte 1 high 3 bits = 0b101 << 4 = 0x50
182 // total 15 bits in 2 bytes: byte 0 = 0xBC, byte 1 = 0x5A.
183 expectBytes(s, std::array<uint8_t, 2>{0xBC, 0x5A}, "SubU");
184}
185
186void testSubByteSigned() {
187 SubS s;
188 s.s5(-3).s7(-50);
189 assert(s.s5() == -3);
190 assert(s.s7() == -50);
191
192 // s5 = -3 (5-bit two's complement = 0x1D); s7 = -50 (7-bit = 0x4E).
193 // Wire (reversed): s7 in bit 0..6, s5 in bit 7..11.
194 // bit 0..6 = 0b1001110 (0x4E)
195 // bit 7 = s5 bit 0 (LSB of -3 = 1)
196 // bit 8..11 = s5 bit 1..4
197 // s5 = 0x1D = 0b11101 -> bits 7..11 = 11101
198 // byte 0 = (0x4E) | (1 << 7) = 0xCE
199 // byte 1 = 0b00001110 = 0x0E
200 expectBytes(s, std::array<uint8_t, 2>{0xCE, 0x0E}, "SubS");
201}
202
203void testBoolField() {
204 BoolField f;
205 f.flag(true).pad(0x5A);
206 assert(f.flag() == true);
207 assert(f.pad() == 0x5A);
208 // flag is at bit 7 (after the ui7 pad, which is wire-reversed first).
209 // byte 0 = 0x80 | 0x5A = 0xDA
210 expectBytes(f, std::array<uint8_t, 1>{0xDA}, "BoolField true");
211
212 f.flag(false);
213 expectBytes(f, std::array<uint8_t, 1>{0x5A}, "BoolField false");
214}
215
216// ---------------------------------------------------------------------------
217// Nested struct fields (aggregate accessor)
218// ---------------------------------------------------------------------------
219
220void testNestedStruct() {
221 Inner in;
222 in.x(0x12).y(0x34);
223
224 Outer o;
225 o.label(0xAB).inner(in);
226 assert(o.label() == 0xAB);
227 assert(o.inner().x() == 0x12);
228 assert(o.inner().y() == 0x34);
229
230 // Wire order: inner first (in declaration-reversed order), then label.
231 // Inner has y at byte 0, x at byte 1 (reversed inside inner too).
232 expectBytes(o, std::array<uint8_t, 3>{0x34, 0x12, 0xAB}, "Outer");
233
234 // Modifying a returned inner does NOT alias back into the outer
235 // (accessor returns a value, not a reference) — the caller has to
236 // hand the modified inner back via `outer.inner(modified)`.
237 Outer o2;
238 o2.inner(in);
239 auto copy = o2.inner();
240 copy.x(0xFF);
241 assert(o2.inner().x() == 0x12); // unchanged
242 o2.inner(copy);
243 assert(o2.inner().x() == 0xFF);
244}
245
246void testNestedStructMisaligned() {
247 // `Misaligned` wire layout (LSB-first):
248 // bits 0..2 = tag (ui3) = 0b101 (= 5)
249 // bits 3..10 = inner.y (ui8 LSB at outer bit 3) = 0x34
250 // bits 11..18 = inner.x (ui8) = 0x12
251 // bits 19..23 = padding (0)
252 // Inner is byte-aligned internally (x at inner bit 8, y at inner bit 0
253 // with the standard reverse), but the inner aggregate as a whole lands
254 // at outer bit 3 — exercising the `copyBitsIn`/`copyBitsOut` path
255 // rather than the byte-aligned fast copy.
256 MisInner mi;
257 mi.x(0x12).y(0x34);
258
259 Misaligned m;
260 m.tag(0b101).inner(mi);
261 assert(m.tag() == 0b101);
262 assert(m.inner().x() == 0x12);
263 assert(m.inner().y() == 0x34);
264
265 // Hand-derived bytes from the bit layout above.
266 expectBytes(m, std::array<uint8_t, 3>{0xA5, 0x91, 0x00},
267 "Misaligned all-set");
268
269 // Setting tag again after writing inner must not disturb inner's bits
270 // (the writer is supposed to mask only the tag's bit range).
271 m.tag(0b000);
272 assert(m.inner().x() == 0x12);
273 assert(m.inner().y() == 0x34);
274 expectBytes(m, std::array<uint8_t, 3>{0xA0, 0x91, 0x00},
275 "Misaligned tag cleared");
276
277 // Setting inner again after writing tag must not disturb tag.
278 m.tag(0b101);
279 MisInner mi2;
280 mi2.x(0xFF).y(0x00);
281 m.inner(mi2);
282 assert(m.tag() == 0b101);
283 assert(m.inner().x() == 0xFF);
284 assert(m.inner().y() == 0x00);
285 // bits 0..2 = 0b101 = 5
286 // bits 3..10 = 0x00 = 0
287 // bits 11..18 = 0xFF = all 1s spanning byte 1 high and
288 // byte 2 low bits
289 // byte 0: tag(3) | y[0..4]<<3 = 0b00000101 = 0x05
290 // byte 1: y[5..7] | x[0..4]<<3 = 0 | 0b11111<<3 = 0b11111000 = 0xF8
291 // byte 2: x[5..7] | pad = 0b00000111 = 0x07
292 expectBytes(m, std::array<uint8_t, 3>{0x05, 0xF8, 0x07},
293 "Misaligned inner all-ones");
294}
295
296// ---------------------------------------------------------------------------
297// Array fields: whole-array and indexed accessors
298// ---------------------------------------------------------------------------
299
300void testArrayOfIntegers() {
301 Arr4 a;
302 a.r({0x10, 0x20, 0x30, 0x40});
303 auto r = a.r();
304 assert(r[0] == 0x10 && r[1] == 0x20 && r[2] == 0x30 && r[3] == 0x40);
305 // Indexed read/write convenience overloads.
306 assert(a.r(2) == 0x30);
307 a.r(2, 0x99);
308 assert(a.r(2) == 0x99);
309
310 // Arrays are laid out element-0-first on the wire (matching the
311 // existing `std::array` layout), not reversed.
312 expectBytes(a, std::array<uint8_t, 4>{0x10, 0x20, 0x99, 0x40}, "Arr4");
313}
314
315// ---------------------------------------------------------------------------
316// Packed arrays: element storage size != on-wire element width
317// ---------------------------------------------------------------------------
318//
319// These exercise the per-element bit-unpacking path. Unlike `Arr4` (whose
320// `ui8` elements are whole bytes), the C++ `std::array` here is wider in
321// memory than the bit-packed wire layout, so a flat byte copy would both
322// truncate the field and misplace every element after the first.
323
324void testSubByteUnsignedArray() {
325 // `8 x ui3` => 24 wire bits (3 bytes) packed into a `std::array<uint8_t, 8>`
326 // (8 bytes in memory). Each element occupies its own 3-bit window.
327 U3Arr a;
328 a.vals({1, 2, 3, 4, 5, 6, 7, 0});
329 const std::array<uint8_t, 8> expected = {1, 2, 3, 4, 5, 6, 7, 0};
330 auto whole = a.vals();
331 for (std::size_t i = 0; i < 8; ++i) {
332 assert(whole[i] == expected[i]);
333 assert(a.vals(i) == expected[i]);
334 }
335 // bit-packed LSB-first: elem i at bits [3i, 3i+2].
336 expectBytes(a, std::array<uint8_t, 3>{0xD1, 0x58, 0x1F}, "U3Arr packed");
337
338 // A single element straddling a byte boundary (elem 2 spans bits 6..8).
339 U3Arr b;
340 b.vals(2, 7);
341 assert(b.vals(2) == 7);
342 for (std::size_t i = 0; i < 8; ++i)
343 if (i != 2)
344 assert(b.vals(i) == 0);
345 expectBytes(b, std::array<uint8_t, 3>{0xC0, 0x01, 0x00}, "U3Arr cross-byte");
346}
347
348void testSubByteBoolArray() {
349 // `8 x ui1` => 8 wire bits (1 byte) packed into a `std::array<bool, 8>`
350 // (8 bytes in memory). Each element is a single bit; reads must yield a
351 // valid `bool` (0/1) rather than dumping a raw byte into bool storage.
352 Bits1Arr a;
353 const std::array<bool, 8> flags = {true, true, false, false,
354 true, false, true, true};
355 a.flags(flags);
356 auto whole = a.flags();
357 for (std::size_t i = 0; i < 8; ++i) {
358 assert(whole[i] == flags[i]);
359 assert(a.flags(i) == flags[i]);
360 }
361 expectBytes(a, std::array<uint8_t, 1>{0xD3}, "Bits1Arr");
362}
363
364void testSubByteSignedArray() {
365 // `4 x si5` => 20 wire bits (3 bytes) packed into a `std::array<int8_t, 4>`
366 // (4 bytes in memory). Reads must sign-extend the 5-bit value.
367 S5Arr a;
368 const std::array<int8_t, 4> vals = {-1, -16, 15, 0};
369 a.vals(vals);
370 auto whole = a.vals();
371 for (std::size_t i = 0; i < 4; ++i) {
372 assert(whole[i] == vals[i]);
373 assert(a.vals(i) == vals[i]);
374 }
375 expectBytes(a, std::array<uint8_t, 3>{0x1F, 0x3E, 0x00}, "S5Arr");
376}
377
378void testOddWidthArray() {
379 // `2 x ui24` => 48 wire bits (6 bytes) packed into a `std::array<uint32_t,
380 // 2>` (8 bytes in memory). The element wire stride (3 bytes) differs from
381 // the storage stride (4 bytes), so a flat copy would corrupt elem 1.
382 U24Arr a;
383 const std::array<uint32_t, 2> vals = {0xABCDEFu, 0x123456u};
384 a.vals(vals);
385 auto whole = a.vals();
386 for (std::size_t i = 0; i < 2; ++i) {
387 assert(whole[i] == vals[i]);
388 assert(a.vals(i) == vals[i]);
389 }
390 expectBytes(a, std::array<uint8_t, 6>{0xEF, 0xCD, 0xAB, 0x56, 0x34, 0x12},
391 "U24Arr");
392}
393
394void testSubByteStructArray() {
395 // `4 x {ui3 hi, ui2 lo}`: each element is 5 wire bits (20 bits = 3 bytes)
396 // but a padded 1-byte `SbCell` in memory, so the whole-array accessor must
397 // unpack each element from its own 5-bit window. Within a cell `lo` lands
398 // at bits 0..1 and `hi` at bits 2..4 (wire order is reversed manifest
399 // order).
400 SbCellArr a;
401 std::array<SbCell, 4> cells;
402 cells[0] = SbCell(1, 0);
403 cells[1] = SbCell(2, 1);
404 cells[2] = SbCell(3, 2);
405 cells[3] = SbCell(7, 3);
406 a.cells(cells);
407
408 auto whole = a.cells();
409 for (std::size_t i = 0; i < 4; ++i) {
410 assert(whole[i].hi() == cells[i].hi());
411 assert(whole[i].lo() == cells[i].lo());
412 assert(a.cells(i).hi() == cells[i].hi());
413 assert(a.cells(i).lo() == cells[i].lo());
414 }
415 // cell i value = (hi << 2) | lo, placed at bits [5i, 5i+4].
416 expectBytes(a, std::array<uint8_t, 3>{0x24, 0xB9, 0x0F}, "SbCellArr");
417
418 // Indexed setter on a single element straddling a byte boundary (cell 1
419 // spans bits 5..9). Other cells stay zero.
420 SbCellArr b;
421 b.cells(1, SbCell(7, 3));
422 assert(b.cells(1).hi() == 7);
423 assert(b.cells(1).lo() == 3);
424 for (std::size_t i = 0; i < 4; ++i)
425 if (i != 1) {
426 assert(b.cells(i).hi() == 0);
427 assert(b.cells(i).lo() == 0);
428 }
429 expectBytes(b, std::array<uint8_t, 3>{0xE0, 0x03, 0x00},
430 "SbCellArr cross-byte");
431}
432
433// ---------------------------------------------------------------------------
434// Unions
435// ---------------------------------------------------------------------------
436
437void testUnion() {
438 UnionTwo u;
439 u.big(0xCAFE);
440 assert(u.big() == 0xCAFE);
441 // Narrow variant at MSB end: small = high byte of big (little-endian
442 // layout means high byte is at byte_offset = union_bytes - 1 = 1).
443 assert(u.small() == 0xCA);
444 expectBytes(u, std::array<uint8_t, 2>{0xFE, 0xCA}, "Union big");
445
446 u.small(0xA5);
447 assert(u.small() == 0xA5);
448 // Writing the narrow variant overwrites only its byte slot at the
449 // MSB end; the LSB byte retains its previous content.
450 expectBytes(u, std::array<uint8_t, 2>{0xFE, 0xA5}, "Union small");
451}
452
453// ---------------------------------------------------------------------------
454// Window helpers (header / data frame round-trip)
455// ---------------------------------------------------------------------------
456
457void testWindowList() {
458 std::vector<uint32_t> elements = {0xAAAA0001u, 0xBBBB0002u, 0xCCCC0003u};
459 ListWindow win(0xDEAD, elements);
460
461 // Header-field accessor reads the static `tag`. Count comes from the
462 // `<list>_count()` accessor and matches the number of data frames.
463 assert(win.tag() == 0xDEAD);
464 assert(win.items_count() == elements.size());
465
466 // Vector helper materialises the data into a flat std::vector.
467 auto got = win.items_vector();
468 assert(got.size() == elements.size());
469 for (std::size_t i = 0; i < elements.size(); ++i)
470 assert(got[i] == elements[i]);
471
472 // Range accessor produces the same values lazily.
473 std::size_t i = 0;
474 for (uint32_t v : win.items())
475 assert(v == elements[i++]);
476
477 // The window splits into three wire segments: header, data, footer.
478 assert(win.numSegments() == 3);
479 auto header_seg = win.segment(0);
480 auto data_seg = win.segment(1);
481 auto footer_seg = win.segment(2);
482 assert(data_seg.size == elements.size() * sizeof(uint32_t));
483 // Header carries `tag` at the MSB end and the count at the LSB end.
484 // Layout: count (ui16, 2 bytes) then tag (ui16, 2 bytes) in reversed
485 // declaration order; header bytes = [count_lo, count_hi, tag_lo, tag_hi].
486 assert(header_seg.size == 4);
487 assert(header_seg.data[0] == (static_cast<uint8_t>(elements.size()) & 0xFF));
488 assert(header_seg.data[1] == 0); // count high byte (size < 256)
489 assert(header_seg.data[2] == 0xAD);
490 assert(header_seg.data[3] == 0xDE);
491 // Footer is a zero-count header with the same layout.
492 assert(footer_seg.size == 4);
493 assert(footer_seg.data[0] == 0);
494 assert(footer_seg.data[1] == 0);
495}
496
497// ---------------------------------------------------------------------------
498// Path D — value-class fields (esi::MutableBitVector, esi::Int, esi::UInt)
499// ---------------------------------------------------------------------------
500
501// Build a wide MutableBitVector from an arbitrary-length byte buffer
502// (LSB-first wire order). Useful for hand-constructing > 64-bit values in
503// the harness.
504esi::MutableBitVector bvFromBytes(std::initializer_list<uint8_t> bytes,
505 std::size_t width) {
506 std::vector<uint8_t> storage(bytes.begin(), bytes.end());
507 // The MutableBitVector(vector<byte>&&, width) ctor requires the storage
508 // size to cover `width` bits. Pad with zeros if the caller supplied
509 // fewer bytes than that.
510 std::size_t need = (width + 7) / 8;
511 if (storage.size() < need)
512 storage.resize(need, 0);
513 return esi::MutableBitVector(std::move(storage), width);
514}
515
516void testWideUnsigned() {
517 // WideU has two byte-aligned wide-int fields: ui128 at the LSB end
518 // (bits 0..127), ui96 at bits 128..223. Total 224 bits = 28 bytes.
519 WideU s;
520 // Construct a 96-bit value: low half = 0x0123456789ABCDEF,
521 // high half = 0xCAFEBABEu (32 bits worth of upper bytes).
522 auto u96 = bvFromBytes(
523 {
524 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, // low 64 bits
525 0xBE, 0xBA, 0xFE, 0xCA, // bits 64..95
526 },
527 96);
528 auto u128 = bvFromBytes(
529 {
530 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, // low 64 bits
531 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, // high 64 bits
532 },
533 128);
534
535 s.u96(u96).u128(u128);
536
537 // Round-trip: read the field back out and compare byte-for-byte.
538 auto got96 = s.u96();
539 assert(got96.width() == 96);
540 for (std::size_t i = 0; i < 96; ++i)
541 assert(got96.getBit(i) == u96.getBit(i));
542 auto got128 = s.u128();
543 assert(got128.width() == 128);
544 for (std::size_t i = 0; i < 128; ++i)
545 assert(got128.getBit(i) == u128.getBit(i));
546
547 // Wire layout: ui128 at byte 0..15 (LSB), then ui96 at byte 16..27.
548 std::array<uint8_t, 28> expected{
549 // u128 first (LSB):
550 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, //
551 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, //
552 // u96 next:
553 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01, //
554 0xBE, 0xBA, 0xFE, 0xCA, //
555 };
556 expectBytes(s, expected, "WideU");
557
558 // Constructor takes the same value-class params, in manifest order.
559 WideU cons(u96, u128);
560 assert(wireBytes(cons) == wireBytes(s));
561}
562
563void testWideSigned() {
564 // si128 max = all ones except the sign bit = 0x7FFF...FF.
565 // si128 -1 = all ones.
566 WideS s;
567 auto s96_zero = bvFromBytes({}, 96);
568 auto s128_neg_one = bvFromBytes(
569 {
570 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //
571 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, //
572 },
573 128);
574 s.s96(s96_zero).s128(s128_neg_one);
575
576 auto got = s.s128();
577 for (std::size_t i = 0; i < 128; ++i)
578 assert(got.getBit(i)); // -1 has every bit set
579 auto got96 = s.s96();
580 for (std::size_t i = 0; i < 96; ++i)
581 assert(!got96.getBit(i));
582
583 // Wire bytes: low 16 bytes all-ones (s128), then 12 zero bytes (s96).
584 std::array<uint8_t, 28> expected{};
585 for (std::size_t i = 0; i < 16; ++i)
586 expected[i] = 0xFF;
587 expectBytes(s, expected, "WideS s128=-1, s96=0");
588}
589
590void testBitsField() {
591 // BitsType > 64 routes through Path D (non-owning `esi::BitVector`
592 // view); narrower Bits stay on the native int paths and are covered
593 // by the other tests above.
594 BitsField f;
595 auto wide = bvFromBytes(
596 {
597 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, //
598 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, //
599 },
600 128);
601 f.wide(wide);
602
603 auto got_wide = f.wide();
604 assert(got_wide.width() == 128);
605 for (std::size_t i = 0; i < 128; ++i)
606 assert(got_wide.getBit(i) == wide.getBit(i));
607}
608
609void testWideMisaligned() {
610 // WideMisaligned: tag (ui3) at bits 0..2, payload (ui128) at bits
611 // 3..130 — i.e. a wide value at a non-byte-aligned offset. Hits the
612 // `copyBitsIn` / `copyBitsOut` arm of Path D.
613 WideMisaligned m;
614 auto payload = bvFromBytes(
615 {
616 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, //
617 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, //
618 },
619 128);
620 m.tag(0b101).payload(payload);
621 assert(m.tag() == 0b101);
622
623 auto got = m.payload();
624 assert(got.width() == 128);
625 for (std::size_t i = 0; i < 128; ++i)
626 assert(got.getBit(i) == payload.getBit(i));
627
628 // Re-setting `tag` must not disturb `payload`, and vice versa.
629 m.tag(0b010);
630 auto got2 = m.payload();
631 for (std::size_t i = 0; i < 128; ++i)
632 assert(got2.getBit(i) == payload.getBit(i));
633 assert(m.tag() == 0b010);
634
635 // A narrower input value must be zero-extended; a wider input is
636 // truncated. Verify the former here (the latter is exercised
637 // implicitly because the Path D setter clamps to bit_width).
638 auto narrow = bvFromBytes({0xAA}, 8); // only 8 bits of input
639 m.payload(narrow);
640 auto got3 = m.payload();
641 // Low 8 bits == 0xAA, upper 120 bits == 0.
642 for (std::size_t i = 0; i < 8; ++i)
643 assert(got3.getBit(i) == ((0xAA >> i) & 1));
644 for (std::size_t i = 8; i < 128; ++i)
645 assert(!got3.getBit(i));
646 assert(m.tag() == 0b010); // still preserved
647}
648
649// ---------------------------------------------------------------------------
650// Array of view-class elements: indexed + lazy whole-array accessors
651// ---------------------------------------------------------------------------
652
653// Round-trip a 3-element array of `ui128` through both the per-element
654// indexed accessors (`items(i)` / `items(i, v)`) and the lazy
655// whole-array accessor (`items()` returning a
656// `std::views::iota | std::views::transform` range of per-element
657// views, plus the `std::array<view, N>` whole-array setter and matching
658// ctor). The byte-copy whole-array path used for native-int arrays
659// doesn't apply here because the view's storage layout differs from
660// the wire layout.
661void testArrayOfViewsAligned() {
662 ArrViews a;
663 std::array<esi::MutableBitVector, 3> source;
664 for (std::size_t i = 0; i < 3; ++i) {
665 source[i] = bvFromBytes(
666 {
667 static_cast<uint8_t>(0x10 + i),
668 static_cast<uint8_t>(0x20 + i),
669 static_cast<uint8_t>(0x30 + i),
670 static_cast<uint8_t>(0x40 + i),
671 static_cast<uint8_t>(0x50 + i),
672 static_cast<uint8_t>(0x60 + i),
673 static_cast<uint8_t>(0x70 + i),
674 static_cast<uint8_t>(0x80 + i),
675 static_cast<uint8_t>(0x91 + i),
676 static_cast<uint8_t>(0xA2 + i),
677 static_cast<uint8_t>(0xB3 + i),
678 static_cast<uint8_t>(0xC4 + i),
679 static_cast<uint8_t>(0xD5 + i),
680 static_cast<uint8_t>(0xE6 + i),
681 static_cast<uint8_t>(0xF7 + i),
682 static_cast<uint8_t>(0x08 + i),
683 },
684 128);
685 a.items(i, source[i]);
686 }
687 for (std::size_t i = 0; i < 3; ++i) {
688 auto got = a.items(i);
689 assert(got.width() == 128);
690 for (std::size_t b = 0; b < 128; ++b)
691 assert(got.getBit(b) == source[i].getBit(b));
692 }
693
694 // Independence: writing element 1 must not disturb elements 0 or 2.
695 auto fresh = bvFromBytes({0xAA}, 128);
696 a.items(1, fresh);
697 for (std::size_t b = 0; b < 128; ++b) {
698 assert(a.items(0).getBit(b) == source[0].getBit(b));
699 assert(a.items(2).getBit(b) == source[2].getBit(b));
700 }
701
702 // Lazy whole-array accessor (std::views::iota | std::views::transform):
703 // yields per-element views on demand. Random-access, so size() and
704 // r[i] both work, and we can range-for it.
705 auto range = a.items();
706 assert(std::ranges::size(range) == 3);
707 std::size_t i = 0;
708 for (auto view : range) {
709 assert(view.width() == 128);
710 for (std::size_t b = 0; b < 128; ++b)
711 assert(view.getBit(b) == a.items(i).getBit(b));
712 ++i;
713 }
714 assert(i == 3);
715
716 // Whole-array setter taking `std::array<view, N>` and the matching
717 // ctor: build a fresh struct from the array argument and confirm the
718 // round-trip matches.
719 std::array<esi::UIntView, 3> bulk = {
720 esi::UIntView(source[0]),
721 esi::UIntView(source[1]),
722 esi::UIntView(source[2]),
723 };
724 ArrViews ctor_built(bulk);
725 for (std::size_t k = 0; k < 3; ++k) {
726 auto got = ctor_built.items(k);
727 for (std::size_t b = 0; b < 128; ++b)
728 assert(got.getBit(b) == source[k].getBit(b));
729 }
730 // Whole-array setter on an existing instance.
731 ArrViews bulk_set;
732 bulk_set.items(bulk);
733 for (std::size_t k = 0; k < 3; ++k) {
734 auto got = bulk_set.items(k);
735 for (std::size_t b = 0; b < 128; ++b)
736 assert(got.getBit(b) == source[k].getBit(b));
737 }
738}
739
740void testArrayOfViewsMisaligned() {
741 // `items` is the FIRST manifest field, so on the wire it lands at the
742 // top of the buffer with `tag` (ui3) at bits 0..2 and the 3 ui128
743 // elements at bits 3..130, 131..258, 259..386. Every element is
744 // bit-misaligned -- exercises the runtime per-bit setter loop and the
745 // sub-byte BitVector view constructor.
746 ArrViewsMis m;
747 m.tag(0b101);
748 std::array<esi::MutableBitVector, 3> source;
749 for (std::size_t i = 0; i < 3; ++i) {
750 source[i] = bvFromBytes(
751 {
752 static_cast<uint8_t>(0xC0 + i),
753 static_cast<uint8_t>(0xC1 + i),
754 static_cast<uint8_t>(0xC2 + i),
755 static_cast<uint8_t>(0xC3 + i),
756 static_cast<uint8_t>(0xC4 + i),
757 static_cast<uint8_t>(0xC5 + i),
758 static_cast<uint8_t>(0xC6 + i),
759 static_cast<uint8_t>(0xC7 + i),
760 static_cast<uint8_t>(0xC8 + i),
761 static_cast<uint8_t>(0xC9 + i),
762 static_cast<uint8_t>(0xCA + i),
763 static_cast<uint8_t>(0xCB + i),
764 static_cast<uint8_t>(0xCC + i),
765 static_cast<uint8_t>(0xCD + i),
766 static_cast<uint8_t>(0xCE + i),
767 static_cast<uint8_t>(0xCF + i),
768 },
769 128);
770 m.items(i, source[i]);
771 }
772 assert(m.tag() == 0b101);
773 for (std::size_t i = 0; i < 3; ++i) {
774 auto got = m.items(i);
775 assert(got.width() == 128);
776 for (std::size_t b = 0; b < 128; ++b)
777 assert(got.getBit(b) == source[i].getBit(b));
778 }
779}
780
781} // namespace
782
783int main() {
784#if defined(_MSC_VER) && defined(_DEBUG)
785 _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
786 _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
787 _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
788 _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);
789 _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
790 _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR);
791 _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
792#endif
793
794 testStandardWidthUnsigned();
795 testStandardWidthSigned();
796 testByteAlignedOddWidthUnsigned();
797 testByteAlignedOddWidthSigned();
798 testSubByteUnsigned();
799 testSubByteSigned();
800 testBoolField();
801 testNestedStruct();
802 testNestedStructMisaligned();
803 testArrayOfIntegers();
804 testSubByteUnsignedArray();
805 testSubByteBoolArray();
806 testSubByteSignedArray();
807 testOddWidthArray();
808 testSubByteStructArray();
809 testUnion();
810 testWindowList();
811 testWideUnsigned();
812 testWideSigned();
813 testBitsField();
814 testWideMisaligned();
815 testArrayOfViewsAligned();
816 testArrayOfViewsMisaligned();
817 std::printf("OK\n");
818 return 0;
819}
assert(baseType &&"element must be base type")
static InstancePath empty
A mutable bit vector that owns its underlying storage.
Definition Values.h:278
Non-owning view of an unsigned bit vector with toUI64() and implicit conversions to unsigned scalar t...
Definition Values.h:205
int main()