CIRCT 23.0.0git
Loading...
Searching...
No Matches
BitAccess.h
Go to the documentation of this file.
1//===- BitAccess.h - Bit-level helpers for generated types ----------------===//
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// DO NOT EDIT!
10// This file is distributed as part of an ESI package. The source for this file
11// should always be modified within CIRCT.
12//
13// Helpers used by `esiaccel.codegen`-generated headers to read and write
14// arbitrary-width integer fields out of a contiguous wire-byte buffer. The
15// runtime emits each struct/union as a `std::array<uint8_t, N>` matching the
16// on-wire bit layout exactly, and these helpers handle the shift/mask work
17// (including signed sign-extension) without depending on C++ bit-fields,
18// which have implementation-defined layout that differs between the Itanium
19// ABI (GCC/Clang) and MSVC.
20//
21// `BitOffset` and `Width` are non-type template parameters so the per-field
22// constants from the generated code (a) participate in `static_assert`
23// checks against `Storage`, and (b) let the optimiser fully unroll the
24// per-bit loop.
25//
26//===----------------------------------------------------------------------===//
27
28// NOLINTNEXTLINE(llvm-header-guard)
29#ifndef ESI_BITACCESS_H
30#define ESI_BITACCESS_H
31
32#include <cstddef>
33#include <cstdint>
34#include <type_traits>
35
36namespace esi {
37namespace detail {
38
39/// Read `Width` bits starting at `BitOffset` (LSB-first within `bytes`) and
40/// return them zero-extended in `Storage`. `Storage` must be an unsigned
41/// integer type wide enough to hold `Width` bits.
42template <typename Storage, std::size_t BitOffset, std::size_t Width>
43inline constexpr Storage readUnsignedBits(const uint8_t *bytes) {
44 static_assert(std::is_unsigned<Storage>::value,
45 "readUnsignedBits Storage must be unsigned");
46 static_assert(Width != 0, "readUnsignedBits Width must be non-zero");
47 static_assert(sizeof(Storage) * 8 >= Width,
48 "readUnsignedBits Storage is narrower than Width");
49 Storage result = 0;
50 for (std::size_t i = 0; i < Width; ++i) {
51 std::size_t src = BitOffset + i;
52 Storage bit = static_cast<Storage>((bytes[src >> 3] >> (src & 7)) & 1u);
53 result |= static_cast<Storage>(bit << i);
54 }
55 return result;
56}
57
58/// Read `Width` bits starting at `BitOffset` (LSB-first) and sign-extend
59/// into the signed integer type `Signed`. `Signed` must be at least `Width`
60/// bits wide.
61template <typename Signed, std::size_t BitOffset, std::size_t Width>
62inline constexpr Signed readSignedBits(const uint8_t *bytes) {
63 static_assert(std::is_signed<Signed>::value,
64 "readSignedBits Signed must be signed");
65 static_assert(Width != 0, "readSignedBits Width must be non-zero");
66 static_assert(sizeof(Signed) * 8 >= Width,
67 "readSignedBits Signed is narrower than Width");
68 using Unsigned = typename std::make_unsigned<Signed>::type;
69 Unsigned u = readUnsignedBits<Unsigned, BitOffset, Width>(bytes);
70 if constexpr (Width < sizeof(Unsigned) * 8) {
71 // Branch-free sign extend: `(u ^ signBit) - signBit` flips the sign
72 // bit and subtracts it back, leaving the high bits all-ones when the
73 // sign bit was set and unchanged otherwise. Avoids relying on the
74 // arithmetic-shift behaviour of signed right-shift.
75 constexpr Unsigned signBit = static_cast<Unsigned>(1) << (Width - 1);
76 u = static_cast<Unsigned>((u ^ signBit) - signBit);
77 }
78 return static_cast<Signed>(u);
79}
80
81/// Write the low `Width` bits of `value` into `bytes` starting at
82/// `BitOffset` (LSB-first). Other bits in the affected bytes are preserved.
83template <typename Storage, std::size_t BitOffset, std::size_t Width>
84inline constexpr void writeUnsignedBits(uint8_t *bytes, Storage value) {
85 static_assert(std::is_unsigned<Storage>::value,
86 "writeUnsignedBits Storage must be unsigned");
87 static_assert(Width != 0, "writeUnsignedBits Width must be non-zero");
88 static_assert(sizeof(Storage) * 8 >= Width,
89 "writeUnsignedBits Storage is narrower than Width");
90 for (std::size_t i = 0; i < Width; ++i) {
91 std::size_t dst = BitOffset + i;
92 std::size_t shift = dst & 7;
93 uint8_t mask = static_cast<uint8_t>(1u << shift);
94 uint8_t bit = static_cast<uint8_t>((value >> i) & static_cast<Storage>(1));
95 bytes[dst >> 3] =
96 static_cast<uint8_t>((bytes[dst >> 3] & static_cast<uint8_t>(~mask)) |
97 static_cast<uint8_t>(bit << shift));
98 }
99}
100
101/// Convenience overload for signed values. Reinterprets the value as
102/// unsigned (two's complement) and writes the low `Width` bits.
103template <typename Signed, std::size_t BitOffset, std::size_t Width>
104inline constexpr void writeSignedBits(uint8_t *bytes, Signed value) {
105 static_assert(std::is_signed<Signed>::value,
106 "writeSignedBits Signed must be signed");
107 static_assert(Width != 0, "writeSignedBits Width must be non-zero");
108 static_assert(sizeof(Signed) * 8 >= Width,
109 "writeSignedBits Signed is narrower than Width");
110 using Unsigned = typename std::make_unsigned<Signed>::type;
111 writeUnsignedBits<Unsigned, BitOffset, Width>(bytes,
112 static_cast<Unsigned>(value));
113}
114
115/// Copy `Width` bits out of `src` starting at `BitOffset` into `dst`
116/// packed LSB-first from bit 0. Used to embed an aggregate (struct, union,
117/// or array) at an arbitrary bit position inside a parent wire buffer:
118/// the inner type already stores its bits LSB-first in `dst`, so the
119/// destination bits land at the same positions they'd occupy if the inner
120/// was read out at byte alignment. `dst` must be zero-initialised on entry
121/// (the helper only sets `1` bits) and must hold at least
122/// `ceil(Width / 8)` bytes.
123template <std::size_t BitOffset, std::size_t Width>
124inline constexpr void copyBitsIn(const uint8_t *src, uint8_t *dst) {
125 static_assert(Width != 0, "copyBitsIn Width must be non-zero");
126 for (std::size_t i = 0; i < Width; ++i) {
127 std::size_t s = BitOffset + i;
128 uint8_t bit = static_cast<uint8_t>((src[s >> 3] >> (s & 7)) & 1u);
129 dst[i >> 3] = static_cast<uint8_t>(dst[i >> 3] | (bit << (i & 7)));
130 }
131}
132
133/// Inverse of `copyBitsIn`: take `Width` bits packed LSB-first from bit 0
134/// of `src` and write them into `dst` starting at `BitOffset`. Other bits
135/// in the affected bytes of `dst` are preserved.
136template <std::size_t BitOffset, std::size_t Width>
137inline constexpr void copyBitsOut(uint8_t *dst, const uint8_t *src) {
138 static_assert(Width != 0, "copyBitsOut Width must be non-zero");
139 for (std::size_t i = 0; i < Width; ++i) {
140 std::size_t d = BitOffset + i;
141 std::size_t shift = d & 7;
142 uint8_t mask = static_cast<uint8_t>(1u << shift);
143 uint8_t bit = static_cast<uint8_t>((src[i >> 3] >> (i & 7)) & 1u);
144 dst[d >> 3] =
145 static_cast<uint8_t>((dst[d >> 3] & static_cast<uint8_t>(~mask)) |
146 static_cast<uint8_t>(bit << shift));
147 }
148}
149
150// ---------------------------------------------------------------------------
151// Runtime-offset variants.
152//
153// The compile-time templates above are used for struct/union fields, whose
154// bit offset and width are known constants. Array elements, however, live at
155// `baseOffset + index * elementWidth`, where `index` is only known at run
156// time, so the generator emits per-element loops that call these `*Dyn`
157// overloads instead. They are otherwise bit-for-bit identical to their
158// templated counterparts; `bitOffset` / `width` simply move from template
159// parameters to ordinary arguments (so the per-bit loop is a real runtime
160// loop rather than a fully-unrolled one). Callers must ensure `width` is
161// non-zero and that `Storage` is wide enough to hold it.
162// ---------------------------------------------------------------------------
163
164/// Runtime-offset form of `readUnsignedBits`.
165template <typename Storage>
166inline Storage readUnsignedBitsDyn(const uint8_t *bytes, std::size_t bitOffset,
167 std::size_t width) {
168 static_assert(std::is_unsigned<Storage>::value,
169 "readUnsignedBitsDyn Storage must be unsigned");
170 Storage result = 0;
171 for (std::size_t i = 0; i < width; ++i) {
172 std::size_t src = bitOffset + i;
173 Storage bit = static_cast<Storage>((bytes[src >> 3] >> (src & 7)) & 1u);
174 result |= static_cast<Storage>(bit << i);
175 }
176 return result;
177}
178
179/// Runtime-offset form of `readSignedBits`.
180template <typename Signed>
181inline Signed readSignedBitsDyn(const uint8_t *bytes, std::size_t bitOffset,
182 std::size_t width) {
183 static_assert(std::is_signed<Signed>::value,
184 "readSignedBitsDyn Signed must be signed");
185 using Unsigned = typename std::make_unsigned<Signed>::type;
186 Unsigned u = readUnsignedBitsDyn<Unsigned>(bytes, bitOffset, width);
187 if (width < sizeof(Unsigned) * 8) {
188 Unsigned signBit =
189 static_cast<Unsigned>(static_cast<Unsigned>(1) << (width - 1));
190 u = static_cast<Unsigned>((u ^ signBit) - signBit);
191 }
192 return static_cast<Signed>(u);
193}
194
195/// Runtime-offset form of `writeUnsignedBits`.
196template <typename Storage>
197inline void writeUnsignedBitsDyn(uint8_t *bytes, Storage value,
198 std::size_t bitOffset, std::size_t width) {
199 static_assert(std::is_unsigned<Storage>::value,
200 "writeUnsignedBitsDyn Storage must be unsigned");
201 for (std::size_t i = 0; i < width; ++i) {
202 std::size_t dst = bitOffset + i;
203 std::size_t shift = dst & 7;
204 uint8_t mask = static_cast<uint8_t>(1u << shift);
205 uint8_t bit = static_cast<uint8_t>((value >> i) & static_cast<Storage>(1));
206 bytes[dst >> 3] =
207 static_cast<uint8_t>((bytes[dst >> 3] & static_cast<uint8_t>(~mask)) |
208 static_cast<uint8_t>(bit << shift));
209 }
210}
211
212/// Runtime-offset form of `writeSignedBits`.
213template <typename Signed>
214inline void writeSignedBitsDyn(uint8_t *bytes, Signed value,
215 std::size_t bitOffset, std::size_t width) {
216 static_assert(std::is_signed<Signed>::value,
217 "writeSignedBitsDyn Signed must be signed");
218 using Unsigned = typename std::make_unsigned<Signed>::type;
219 writeUnsignedBitsDyn<Unsigned>(bytes, static_cast<Unsigned>(value), bitOffset,
220 width);
221}
222
223/// Runtime-offset form of `copyBitsIn`. `dst` must be zero-initialised on
224/// entry (only `1` bits are set) and hold at least `ceil(width / 8)` bytes.
225inline void copyBitsInDyn(const uint8_t *src, std::size_t srcBitOffset,
226 uint8_t *dst, std::size_t width) {
227 for (std::size_t i = 0; i < width; ++i) {
228 std::size_t s = srcBitOffset + i;
229 uint8_t bit = static_cast<uint8_t>((src[s >> 3] >> (s & 7)) & 1u);
230 dst[i >> 3] = static_cast<uint8_t>(dst[i >> 3] | (bit << (i & 7)));
231 }
232}
233
234/// Runtime-offset form of `copyBitsOut`. Bits outside the written range in
235/// the affected bytes of `dst` are preserved.
236inline void copyBitsOutDyn(uint8_t *dst, std::size_t dstBitOffset,
237 const uint8_t *src, std::size_t width) {
238 for (std::size_t i = 0; i < width; ++i) {
239 std::size_t d = dstBitOffset + i;
240 std::size_t shift = d & 7;
241 uint8_t mask = static_cast<uint8_t>(1u << shift);
242 uint8_t bit = static_cast<uint8_t>((src[i >> 3] >> (i & 7)) & 1u);
243 dst[d >> 3] =
244 static_cast<uint8_t>((dst[d >> 3] & static_cast<uint8_t>(~mask)) |
245 static_cast<uint8_t>(bit << shift));
246 }
247}
248
249} // namespace detail
250} // namespace esi
251
252#endif // ESI_BITACCESS_H
constexpr void copyBitsOut(uint8_t *dst, const uint8_t *src)
Inverse of copyBitsIn: take Width bits packed LSB-first from bit 0 of src and write them into dst sta...
Definition BitAccess.h:137
Signed readSignedBitsDyn(const uint8_t *bytes, std::size_t bitOffset, std::size_t width)
Runtime-offset form of readSignedBits.
Definition BitAccess.h:181
constexpr Signed readSignedBits(const uint8_t *bytes)
Read Width bits starting at BitOffset (LSB-first) and sign-extend into the signed integer type Signed...
Definition BitAccess.h:62
void copyBitsInDyn(const uint8_t *src, std::size_t srcBitOffset, uint8_t *dst, std::size_t width)
Runtime-offset form of copyBitsIn.
Definition BitAccess.h:225
void copyBitsOutDyn(uint8_t *dst, std::size_t dstBitOffset, const uint8_t *src, std::size_t width)
Runtime-offset form of copyBitsOut.
Definition BitAccess.h:236
constexpr void copyBitsIn(const uint8_t *src, uint8_t *dst)
Copy Width bits out of src starting at BitOffset into dst packed LSB-first from bit 0.
Definition BitAccess.h:124
constexpr void writeSignedBits(uint8_t *bytes, Signed value)
Convenience overload for signed values.
Definition BitAccess.h:104
void writeSignedBitsDyn(uint8_t *bytes, Signed value, std::size_t bitOffset, std::size_t width)
Runtime-offset form of writeSignedBits.
Definition BitAccess.h:214
Storage readUnsignedBitsDyn(const uint8_t *bytes, std::size_t bitOffset, std::size_t width)
Runtime-offset form of readUnsignedBits.
Definition BitAccess.h:166
constexpr void writeUnsignedBits(uint8_t *bytes, Storage value)
Write the low Width bits of value into bytes starting at BitOffset (LSB-first).
Definition BitAccess.h:84
void writeUnsignedBitsDyn(uint8_t *bytes, Storage value, std::size_t bitOffset, std::size_t width)
Runtime-offset form of writeUnsignedBits.
Definition BitAccess.h:197
constexpr Storage readUnsignedBits(const uint8_t *bytes)
Read Width bits starting at BitOffset (LSB-first within bytes) and return them zero-extended in Stora...
Definition BitAccess.h:43
Definition esi.py:1