| // Copyright 2019 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Utilities for efficiently reading and writing to/from memory. |
| #ifndef EMBOSS_RUNTIME_CPP_EMBOSS_MEMORY_UTIL_H_ |
| #define EMBOSS_RUNTIME_CPP_EMBOSS_MEMORY_UTIL_H_ |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstring> |
| |
| #include "runtime/cpp/emboss_bit_util.h" |
| #include "runtime/cpp/emboss_cpp_types.h" |
| #include "runtime/cpp/emboss_defines.h" |
| |
| namespace emboss { |
| namespace support { |
| |
| // MemoryAccessor reads and writes big- and little-endian unsigned integers in |
| // and out of memory, using optimized routines where possible. |
| // |
| // The default MemoryAccessor just proxies to the MemoryAccessor with the |
| // next-smallest alignment and equivalent offset: MemoryAccessor<C, 8, 0, 32> |
| // and MemoryAccessor<C, 8, 4, 32> will proxy to MemoryAccessor<C, 4, 0, 32>, |
| // since an 8-byte-aligned pointer is also 4-byte-aligned, as is a pointer that |
| // is 4 bytes away from 8-byte alignment. |
| template <typename CharT, ::std::size_t kAlignment, ::std::size_t kOffset, |
| ::std::size_t kBits> |
| struct MemoryAccessor { |
| static_assert(IsPowerOfTwo(kAlignment), |
| "MemoryAccessor requires power-of-two alignment."); |
| static_assert( |
| kOffset < kAlignment, |
| "MemoryAccessor requires offset to be strictly less than alignment."); |
| |
| using ChainedAccessor = |
| MemoryAccessor<CharT, kAlignment / 2, kOffset % (kAlignment / 2), kBits>; |
| using Unsigned = typename LeastWidthInteger<kBits>::Unsigned; |
| static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { |
| return ChainedAccessor::ReadLittleEndianUInt(bytes); |
| } |
| static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { |
| ChainedAccessor::WriteLittleEndianUInt(bytes, value); |
| } |
| static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { |
| return ChainedAccessor::ReadBigEndianUInt(bytes); |
| } |
| static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { |
| ChainedAccessor::WriteBigEndianUInt(bytes, value); |
| } |
| }; |
| |
| // The least-aligned case for MemoryAccessor is 8-bit alignment, and the default |
| // version of MemoryAccessor will devolve to this one if there is no more |
| // specific override. |
| // |
| // If the system byte order is known, then these routines can use memcpy and |
| // (possibly) a byte swap; otherwise they can read individual bytes and |
| // shift+or them together in the appropriate order. I (bolms@) haven't found a |
| // compiler that will optimize the multiple reads, shifts, and ors into a single |
| // read, so the memcpy version is *much* faster for 32-bit and larger reads. |
| template <typename CharT, ::std::size_t kBits> |
| struct MemoryAccessor<CharT, 1, 0, kBits> { |
| static_assert(kBits % 8 == 0, |
| "MemoryAccessor can only read and write whole-byte values."); |
| static_assert(IsAliasSafe<CharT>::value, |
| "MemoryAccessor can only be used on pointers to char types."); |
| |
| using Unsigned = typename LeastWidthInteger<kBits>::Unsigned; |
| |
| #if defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) |
| static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { |
| Unsigned result = 0; |
| ::std::memcpy(&result, bytes, kBits / 8); |
| return EMBOSS_LITTLE_ENDIAN_TO_NATIVE(result); |
| } |
| #else |
| static inline Unsigned ReadLittleEndianUInt(const CharT *bytes) { |
| Unsigned result = 0; |
| for (decltype(kBits) i = 0; i < kBits / 8; ++i) { |
| result |= |
| static_cast<Unsigned>(static_cast</**/ ::std::uint8_t>(bytes[i])) |
| << i * 8; |
| } |
| return result; |
| } |
| #endif |
| |
| #if defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) |
| static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { |
| value = EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); |
| ::std::memcpy(bytes, &value, kBits / 8); |
| } |
| #else |
| static inline void WriteLittleEndianUInt(CharT *bytes, Unsigned value) { |
| for (decltype(kBits) i = 0; i < kBits / 8; ++i) { |
| bytes[i] = static_cast<CharT>(static_cast</**/ ::std::uint8_t>(value)); |
| if (sizeof value > 1) { |
| // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this |
| // step for uint8_t. |
| value >>= 8; |
| } |
| } |
| } |
| #endif |
| |
| #if defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) |
| static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { |
| Unsigned result = 0; |
| // When a big-endian source integer is smaller than the result, the source |
| // bytes must be copied into the final bytes of the destination. This is |
| // true whether the host is big- or little-endian. |
| // |
| // For a little-endian host: |
| // |
| // source (big-endian value 0x112233): |
| // |
| // byte 0 byte 1 byte 2 |
| // +--------+--------+--------+ |
| // | 0x11 | 0x22 | 0x33 | |
| // +--------+--------+--------+ |
| // |
| // result after memcpy (host-interpreted value 0x33221100): |
| // |
| // byte 0 byte 1 byte 2 byte 3 |
| // +--------+--------+--------+--------+ |
| // | 0x00 | 0x11 | 0x22 | 0x33 | |
| // +--------+--------+--------+--------+ |
| // |
| // result after 32-bit byte swap (host-interpreted value 0x112233): |
| // |
| // byte 0 byte 1 byte 2 byte 3 |
| // +--------+--------+--------+--------+ |
| // | 0x33 | 0x22 | 0x11 | 0x00 | |
| // +--------+--------+--------+--------+ |
| // |
| // For a big-endian host: |
| // |
| // source (value 0x112233): |
| // |
| // byte 0 byte 1 byte 2 |
| // +--------+--------+--------+ |
| // | 0x11 | 0x22 | 0x33 | |
| // +--------+--------+--------+ |
| // |
| // result after memcpy (value 0x112233) -- no byte swap needed: |
| // |
| // byte 0 byte 1 byte 2 byte 3 |
| // +--------+--------+--------+--------+ |
| // | 0x00 | 0x11 | 0x22 | 0x33 | |
| // +--------+--------+--------+--------+ |
| ::std::memcpy(reinterpret_cast<char *>(&result) + sizeof result - kBits / 8, |
| bytes, kBits / 8); |
| result = EMBOSS_BIG_ENDIAN_TO_NATIVE(result); |
| return result; |
| } |
| #else |
| static inline Unsigned ReadBigEndianUInt(const CharT *bytes) { |
| Unsigned result = 0; |
| for (decltype(kBits) i = 0; i < kBits / 8; ++i) { |
| result |= |
| static_cast<Unsigned>(static_cast</**/ ::std::uint8_t>(bytes[i])) |
| << (kBits - 8 - i * 8); |
| } |
| return result; |
| } |
| #endif |
| |
| #if defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) |
| static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { |
| value = EMBOSS_NATIVE_TO_BIG_ENDIAN(value); |
| ::std::memcpy(bytes, |
| reinterpret_cast<char *>(&value) + sizeof value - kBits / 8, |
| kBits / 8); |
| } |
| #else |
| static inline void WriteBigEndianUInt(CharT *bytes, Unsigned value) { |
| for (decltype(kBits) i = 0; i < kBits / 8; ++i) { |
| bytes[kBits / 8 - 1 - i] = |
| static_cast<CharT>(static_cast</**/ ::std::uint8_t>(value)); |
| if (sizeof value > 1) { |
| // Shifting an 8-bit type by 8 bits is undefined behavior, so skip this |
| // step for uint8_t. |
| value >>= 8; |
| } |
| } |
| } |
| #endif |
| }; |
| |
| // Specialization of UIntMemoryAccessor for 16- 32- and 64-bit-aligned reads and |
| // writes, using EMBOSS_ALIAS_SAFE_POINTER_CAST instead of memcpy. |
| #if defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) && \ |
| defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) && \ |
| defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) && \ |
| defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) && \ |
| defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) |
| template <typename CharT> |
| struct MemoryAccessor<CharT, 8, 0, 64> { |
| static inline ::std::uint64_t ReadLittleEndianUInt(const CharT *bytes) { |
| return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes)); |
| } |
| |
| static inline void WriteLittleEndianUInt(CharT *bytes, |
| ::std::uint64_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) = |
| EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); |
| } |
| |
| static inline ::std::uint64_t ReadBigEndianUInt(const CharT *bytes) { |
| return EMBOSS_BIG_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint64_t, bytes)); |
| } |
| |
| static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint64_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint64_t, bytes) = |
| EMBOSS_NATIVE_TO_BIG_ENDIAN(value); |
| } |
| }; |
| |
| template <typename CharT> |
| struct MemoryAccessor<CharT, 4, 0, 32> { |
| static inline ::std::uint32_t ReadLittleEndianUInt(const CharT *bytes) { |
| return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes)); |
| } |
| |
| static inline void WriteLittleEndianUInt(CharT *bytes, |
| ::std::uint32_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) = |
| EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); |
| } |
| |
| static inline ::std::uint32_t ReadBigEndianUInt(const CharT *bytes) { |
| return EMBOSS_BIG_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint32_t, bytes)); |
| } |
| |
| static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint32_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint32_t, bytes) = |
| EMBOSS_NATIVE_TO_BIG_ENDIAN(value); |
| } |
| }; |
| |
| template <typename CharT> |
| struct MemoryAccessor<CharT, 2, 0, 16> { |
| static inline ::std::uint16_t ReadLittleEndianUInt(const CharT *bytes) { |
| return EMBOSS_LITTLE_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes)); |
| } |
| |
| static inline void WriteLittleEndianUInt(CharT *bytes, |
| ::std::uint16_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) = |
| EMBOSS_NATIVE_TO_LITTLE_ENDIAN(value); |
| } |
| |
| static inline ::std::uint16_t ReadBigEndianUInt(const CharT *bytes) { |
| return EMBOSS_BIG_ENDIAN_TO_NATIVE( |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(const ::std::uint16_t, bytes)); |
| } |
| |
| static inline void WriteBigEndianUInt(CharT *bytes, ::std::uint16_t value) { |
| *EMBOSS_ALIAS_SAFE_POINTER_CAST(::std::uint16_t, bytes) = |
| EMBOSS_NATIVE_TO_BIG_ENDIAN(value); |
| } |
| }; |
| #endif // defined(EMBOSS_ALIAS_SAFE_POINTER_CAST) && |
| // defined(EMBOSS_LITTLE_ENDIAN_TO_NATIVE) && |
| // defined(EMBOSS_BIG_ENDIAN_TO_NATIVE) && |
| // defined(EMBOSS_NATIVE_TO_LITTLE_ENDIAN) && |
| // defined(EMBOSS_NATIVE_TO_BIG_ENDIAN) |
| |
| // This is the Euclidean GCD algorithm, in C++11-constexpr-safe form. The |
| // initial is-b-greater-than-a-if-so-swap is omitted, since gcd(b % a, a) is the |
| // same as gcd(b, a) when a > b. |
| inline constexpr ::std::size_t GreatestCommonDivisor(::std::size_t a, |
| ::std::size_t b) { |
| return a == 0 ? b : GreatestCommonDivisor(b % a, a); |
| } |
| |
| // ContiguousBuffer is a direct view of a fixed number of contiguous bytes in |
| // memory. If Byte is a const type, it will be a read-only view; if Byte is |
| // non-const, then writes will be allowed. |
| // |
| // The kAlignment and kOffset parameters are used to optimize certain reads and |
| // writes. static_cast<uintptr_t>(bytes_) % kAlignment must equal kOffset. |
| // |
| // This class is used extensively by generated code, and is not intended to be |
| // heavily used by hand-written code -- some interfaces can be tricky to call |
| // correctly. |
| template <typename Byte, ::std::size_t kAlignment, ::std::size_t kOffset> |
| class ContiguousBuffer final { |
| // There aren't many systems with non-8-bit chars, and a quirk of POSIX |
| // requires that POSIX C systems have CHAR_BIT == 8, but some DSPs use wider |
| // chars. |
| static_assert(CHAR_BIT == 8, "ContiguousBuffer requires 8-bit chars."); |
| |
| // ContiguousBuffer assumes that its backing store is byte-oriented. The |
| // previous check ensures that chars are 8 bits, and this one ensures that the |
| // backing store uses chars. |
| // |
| // Note that this check is explicitly that Byte is one of the three standard |
| // char types, and not that (say) it is a one-byte type with an assignment |
| // operator that can be static_cast<> to and from uint8_t. I (bolms@) have |
| // chosen to lock it down to just char types to avoid running afoul of strict |
| // aliasing rules anywhere. |
| // |
| // Of somewhat academic interest, uint8_t is not required to be a char type |
| // (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66110#c10), though it is |
| // unlikely that any compiler vendor will actually change it, as there is |
| // probably enough real-world code that relies on uint8_t being allowed to |
| // alias. |
| static_assert(IsAliasSafe<Byte>::value, |
| "ContiguousBuffer requires char type."); |
| |
| // Because real-world processors only care about power-of-2 alignments, |
| // ContiguousBuffer only supports power-of-2 alignments. Note that |
| // GetOffsetStorage can handle non-power-of-2 alignments. |
| static_assert(IsPowerOfTwo(kAlignment), |
| "ContiguousBuffer requires power-of-two alignment."); |
| |
| // To avoid template variant explosion, ContiguousBuffer requires kOffset to |
| // be strictly less than kAlignment. Users of ContiguousBuffer are expected |
| // to take the modulus of kOffset by kAlignment before passing it in as a |
| // parameter. |
| static_assert( |
| kOffset < kAlignment, |
| "ContiguousBuffer requires offset to be strictly less than alignment."); |
| |
| public: |
| using ByteType = Byte; |
| // OffsetStorageType<kSubAlignment, kSubOffset> is the return type of |
| // GetOffsetStorage<kSubAlignment, kSubOffset>(...). This is used in a number |
| // of places in generated code to specify deeply-nested template values. |
| // |
| // In theory, anything that cared about this type could use |
| // decltype(declval(ContiguousBuffer<...>).GetOffsetStorage<kSubAlignment, |
| // kSubOffset>(0, 0)) instead, but that is much more cumbersome, and it |
| // appears that at least some versions of GCC do not handle it correctly. |
| template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset> |
| using OffsetStorageType = |
| ContiguousBuffer<Byte, GreatestCommonDivisor(kAlignment, kSubAlignment), |
| (kOffset + kSubOffset) % |
| GreatestCommonDivisor(kAlignment, kSubAlignment)>; |
| |
| // Constructs a default ContiguousBuffer. |
| ContiguousBuffer() : bytes_(nullptr), size_(0) {} |
| |
| // Constructs a ContiguousBuffer from a contiguous container type over some |
| // `char` type, such as std::string, std::vector<signed char>, |
| // std::array<unsigned char, N>, or std::string_view. |
| // |
| // This template is only enabled if: |
| // |
| // 1. bytes->data() returns a pointer to some char type. |
| // 2. Byte is at least as cv-qualified as decltype(*bytes->data()). |
| // |
| // The first requirement means that this constructor won't work on, e.g., |
| // std::vector<int> -- this is mostly a precautionary measure, since |
| // ContiguousBuffer only uses alias-safe operations anyway. |
| // |
| // The second requirement means that const and volatile are respected in the |
| // expected way: a ContiguousBuffer<const unsigned char, ...> may be |
| // initialized from std::vector<char>, but a ContiguousBuffer<unsigned char, |
| // ...> may not be initialized from std::string_view. |
| template < |
| typename T, |
| typename = typename ::std::enable_if< |
| IsAliasSafe<typename ::std::remove_cv< |
| typename ::std::remove_reference<decltype(*( |
| ::std::declval<T>().data()))>::type>::type>::value && ::std:: |
| is_same<typename AddSourceCV< |
| decltype(*::std::declval<T>().data()), Byte>::Type, |
| Byte>::value>::type> |
| explicit ContiguousBuffer(T *bytes) |
| : bytes_{reinterpret_cast<Byte *>(bytes->data())}, size_{bytes->size()} { |
| if (bytes != nullptr) |
| EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset); |
| } |
| |
| // Constructs a ContiguousBuffer from a pointer to a char type and a size. As |
| // with the constructor from a container, above, Byte must be at least as |
| // cv-qualified as T. |
| template <typename T, |
| typename = typename ::std::enable_if< |
| IsAliasSafe<T>::value && ::std::is_same< |
| typename AddSourceCV<T, Byte>::Type, Byte>::value>> |
| explicit ContiguousBuffer(T *bytes, ::std::size_t size) |
| : bytes_{reinterpret_cast<Byte *>(bytes)}, |
| size_{bytes == nullptr ? 0 : size} { |
| if (bytes != nullptr) |
| EMBOSS_DCHECK_POINTER_ALIGNMENT(bytes, kAlignment, kOffset); |
| } |
| |
| // Constructs a ContiguousBuffer from nullptr. Equivalent to |
| // ContiguousBuffer(). |
| // |
| // TODO(bolms): Update callers and remove this constructor. |
| explicit ContiguousBuffer(::std::nullptr_t) : bytes_{nullptr}, size_{0} {} |
| |
| // Implicitly construct or assign a ContiguousBuffer from a ContiguousBuffer. |
| ContiguousBuffer(const ContiguousBuffer &other) = default; |
| ContiguousBuffer& operator=(const ContiguousBuffer& other) = default; |
| |
| // Explicitly construct a ContiguousBuffers from another, compatible |
| // ContiguousBuffer. A compatible ContiguousBuffer has an |
| // equally-or-less-cv-qualified Byte type, an alignment that is an exact |
| // multiple of this ContiguousBuffer's alignment, and an offset that is the |
| // same when reduced to this ContiguousBuffer's alignment. |
| // |
| // The final !::std::is_same<...> clause prevents this constructor from |
| // overlapping with the *implicit* copy constructor. |
| template < |
| typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset, |
| typename = typename ::std::enable_if< |
| kOtherAlignment % kAlignment == 0 && |
| kOtherOffset % kAlignment == |
| kOffset && ::std::is_same< |
| typename AddSourceCV<OtherByte, Byte>::Type, Byte>::value && |
| !::std::is_same<ContiguousBuffer, |
| ContiguousBuffer<OtherByte, kOtherAlignment, |
| kOtherOffset>>::value>::type> |
| explicit ContiguousBuffer( |
| const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other) |
| : bytes_{reinterpret_cast<Byte *>(other.data())}, |
| size_{other.SizeInBytes()} {} |
| |
| // Compare a ContiguousBuffers to another, compatible ContiguousBuffer. |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset, |
| typename = typename ::std::enable_if< |
| kOtherAlignment % kAlignment == 0 && |
| kOtherOffset % kAlignment == |
| kOffset && ::std::is_same< |
| typename AddSourceCV<OtherByte, Byte>::Type, |
| Byte>::value>::type> |
| bool operator==(const ContiguousBuffer<OtherByte, kOtherAlignment, |
| kOtherOffset> &other) const { |
| return bytes_ == reinterpret_cast<Byte *>(other.data()) && |
| size_ == other.SizeInBytes(); |
| } |
| |
| // Compare a ContiguousBuffers to another, compatible ContiguousBuffer. |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset, |
| typename = typename ::std::enable_if< |
| kOtherAlignment % kAlignment == 0 && |
| kOtherOffset % kAlignment == |
| kOffset && ::std::is_same< |
| typename AddSourceCV<OtherByte, Byte>::Type, |
| Byte>::value>::type> |
| bool operator!=(const ContiguousBuffer<OtherByte, kOtherAlignment, |
| kOtherOffset> &other) const { |
| return !(*this == other); |
| } |
| |
| // Assignment from a compatible ContiguousBuffer. |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset, |
| typename = typename ::std::enable_if< |
| kOtherAlignment % kAlignment == 0 && |
| kOtherOffset % kAlignment == |
| kOffset && ::std::is_same< |
| typename AddSourceCV<OtherByte, Byte>::Type, |
| Byte>::value>::type> |
| ContiguousBuffer &operator=( |
| const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other) { |
| bytes_ = reinterpret_cast<Byte *>(other.data()); |
| size_ = other.SizeInBytes(); |
| return *this; |
| } |
| |
| // GetOffsetStorage returns a new ContiguousBuffer that is a subsection of |
| // this ContiguousBuffer, with appropriate alignment assertions. The new |
| // ContiguousBuffer will point to a region `offset` bytes into the original |
| // ContiguousBuffer, with a size of `max(size, original_size - offset)`. |
| // |
| // The kSubAlignment and kSubOffset template parameters act as assertions |
| // about the value of `offset`: `offset % (kSubAlignment / 8) - (kSubOffset / |
| // 8)` must be zero. That is, if `kSubAlignment` is 16 and `kSubOffset` is 8, |
| // then `offset` may be 1, 3, 5, 7, etc. |
| // |
| // As a special case, if `kSubAlignment` is 0, then `offset` must exactly |
| // equal `kSubOffset`. |
| // |
| // This method is used by generated structure views to get backing buffers for |
| // views of their fields; the code generator can determine proper values for |
| // `kSubAlignment` and `kSubOffset`. |
| template </**/ ::std::size_t kSubAlignment, ::std::size_t kSubOffset> |
| OffsetStorageType<kSubAlignment, kSubOffset> GetOffsetStorage( |
| ::std::size_t offset, ::std::size_t size) const { |
| static_assert(kSubAlignment == 0 || kSubAlignment > kSubOffset, |
| "kSubAlignment must be greater than kSubOffset."); |
| // Emboss provides a fast, unchecked path for reads and writes like: |
| // |
| // view.field().subfield().UncheckedWrite(). |
| // |
| // Each of .field() and .subfield() call GetOffsetStorage(), so |
| // GetOffsetStorage() must be small and fast. |
| if (kSubAlignment == 0) { |
| EMBOSS_DCHECK_EQ(offset, kSubOffset); |
| } else { |
| // The weird ?:, below, silences -Werror=div-by-zero on versions of GCC |
| // that aren't smart enough to figure out that kSubAlignment can't be zero |
| // in this branch. |
| EMBOSS_DCHECK_EQ(offset % (kSubAlignment == 0 ? 1 : kSubAlignment), |
| kSubOffset); |
| } |
| using ResultStorageType = OffsetStorageType<kSubAlignment, kSubOffset>; |
| return bytes_ == nullptr |
| ? ResultStorageType{nullptr} |
| : ResultStorageType{ |
| bytes_ + offset, |
| size_ < offset ? 0 : ::std::min(size, size_ - offset)}; |
| } |
| |
| // ReadLittleEndianUInt, ReadBigEndianUInt, and the unchecked versions thereof |
| // provide efficient multibyte read access to the underlying buffer. The |
| // kBits template parameter should always equal the buffer size when these are |
| // called. |
| // |
| // Generally, types other than unsigned integers can be relatively efficiently |
| // converted from unsigned integers, and views should use Read...UInt to read |
| // the raw value, then convert. |
| // |
| // Read...UInt always reads the entire buffer; to read a smaller section, use |
| // GetOffsetStorage first. |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned ReadLittleEndianUInt() const { |
| EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); |
| EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); |
| return UncheckedReadLittleEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned UncheckedReadLittleEndianUInt() |
| const { |
| static_assert(kBits % 8 == 0, |
| "ContiguousBuffer::ReadLittleEndianUInt() can only read " |
| "whole-byte values."); |
| return MemoryAccessor<Byte, kAlignment, kOffset, |
| kBits>::ReadLittleEndianUInt(bytes_); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned ReadBigEndianUInt() const { |
| EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); |
| EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); |
| return UncheckedReadBigEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned UncheckedReadBigEndianUInt() |
| const { |
| static_assert(kBits % 8 == 0, |
| "ContiguousBuffer::ReadBigEndianUInt() can only read " |
| "whole-byte values."); |
| return MemoryAccessor<Byte, kAlignment, kOffset, kBits>::ReadBigEndianUInt( |
| bytes_); |
| } |
| |
| // WriteLittleEndianUInt, WriteBigEndianUInt, and the unchecked versions |
| // thereof provide efficient write access to the buffer. Similar to the Read |
| // methods above, they write the entire buffer from an unsigned integer; |
| // non-unsigned values should be converted to the equivalent bit pattern, then |
| // written, and to write a subsection of the buffer use GetOffsetStorage |
| // first. |
| template </**/ ::std::size_t kBits> |
| void WriteLittleEndianUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); |
| EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); |
| UncheckedWriteLittleEndianUInt<kBits>(value); |
| } |
| template </**/ ::std::size_t kBits> |
| void UncheckedWriteLittleEndianUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| static_assert(kBits % 8 == 0, |
| "ContiguousBuffer::WriteLittleEndianUInt() can only write " |
| "whole-byte values."); |
| MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteLittleEndianUInt( |
| bytes_, value); |
| } |
| template </**/ ::std::size_t kBits> |
| void WriteBigEndianUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| EMBOSS_CHECK_EQ(SizeInBytes() * 8, kBits); |
| EMBOSS_CHECK_POINTER_ALIGNMENT(bytes_, kAlignment, kOffset); |
| return UncheckedWriteBigEndianUInt<kBits>(value); |
| } |
| template </**/ ::std::size_t kBits> |
| void UncheckedWriteBigEndianUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| static_assert(kBits % 8 == 0, |
| "ContiguousBuffer::WriteBigEndianUInt() can only write " |
| "whole-byte values."); |
| MemoryAccessor<Byte, kAlignment, kOffset, kBits>::WriteBigEndianUInt(bytes_, |
| value); |
| } |
| |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset> |
| void UncheckedCopyFrom( |
| const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, |
| ::std::size_t size) const { |
| memmove(data(), other.data(), size); |
| } |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset> |
| void CopyFrom( |
| const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, |
| ::std::size_t size) const { |
| EMBOSS_CHECK(Ok()); |
| EMBOSS_CHECK(other.Ok()); |
| // It is OK if either buffer contains extra bytes that are not being copied. |
| EMBOSS_CHECK_GE(SizeInBytes(), size); |
| EMBOSS_CHECK_GE(other.SizeInBytes(), size); |
| UncheckedCopyFrom(other, size); |
| } |
| template <typename OtherByte, ::std::size_t kOtherAlignment, |
| ::std::size_t kOtherOffset> |
| bool TryToCopyFrom( |
| const ContiguousBuffer<OtherByte, kOtherAlignment, kOtherOffset> &other, |
| ::std::size_t size) const { |
| if (Ok() && other.Ok() && SizeInBytes() >= size && |
| other.SizeInBytes() >= size) { |
| UncheckedCopyFrom(other, size); |
| return true; |
| } |
| return false; |
| } |
| ::std::size_t SizeInBytes() const { return size_; } |
| bool Ok() const { return bytes_ != nullptr; } |
| Byte *data() const { return bytes_; } |
| Byte *begin() const { return bytes_; } |
| Byte *end() const { return bytes_ + size_; } |
| |
| // Constructs a string type from the underlying data; mostly intended to be |
| // called as: |
| // |
| // buffer.ToString<std::string>(); |
| // |
| // or: |
| // |
| // buffer.ToString<std::string_view>(); |
| // |
| // ... but it should also work with any similar-enough classes, such as |
| // std::basic_string_view<unsigned char> or Google's absl::string_view. |
| // |
| // Note that this may or may not make a copy of the underlying data, |
| // depending on the behavior of the given string type. |
| template <typename String> |
| typename ::std::enable_if< |
| IsAliasSafe<typename ::std::remove_reference< |
| decltype(*::std::declval<String>().data())>::type>::value, |
| String>::type |
| ToString() const { |
| return String( |
| reinterpret_cast< |
| const typename ::std::remove_reference<typename ::std::remove_cv< |
| decltype(*::std::declval<String>().data())>::type>::type *>( |
| bytes_), |
| size_); |
| } |
| |
| private: |
| Byte *bytes_ = nullptr; |
| ::std::size_t size_ = 0; |
| }; |
| |
| // TODO(bolms): Remove these aliases. |
| using ReadWriteContiguousBuffer = ContiguousBuffer<unsigned char, 1, 0>; |
| using ReadOnlyContiguousBuffer = ContiguousBuffer<const unsigned char, 1, 0>; |
| |
| // LittleEndianByteOrderer is a pass-through adapter for a byte buffer class. |
| // It is used to implement little-endian bit blocks. |
| // |
| // When used by BitBlock, the resulting bits are numbered as if they are |
| // little-endian: |
| // |
| // bit addresses of each bit in each byte |
| // +----+----+----+----+----+----+----+----+----+----+----+----+---- |
| // bit in 7 | 7 | 15 | 23 | 31 | 39 | 47 | 55 | 63 | 71 | 79 | 87 | 95 | |
| // byte 6 | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 | 70 | 78 | 86 | 94 | |
| // 5 | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 | 69 | 77 | 85 | 93 | |
| // 4 | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 | 68 | 76 | 84 | 92 | |
| // 3 | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 | 67 | 75 | 83 | 91 | ... |
| // 2 | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 | 66 | 74 | 82 | 90 | |
| // 1 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 | 65 | 73 | 81 | 89 | |
| // 0 | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | |
| // +----+----+----+----+----+----+----+----+----+----+----+----+---- |
| // 0 1 2 3 4 5 6 7 8 9 10 11 ... |
| // byte address |
| // |
| // Because endian-specific reads and writes are handled in ContiguousBuffer, |
| // this class exists mostly to translate VerbUInt calls to VerbLittleEndianUInt. |
| template <class BufferT> |
| class LittleEndianByteOrderer final { |
| public: |
| // Type declaration so that BitBlock can use BufferType::BufferType. |
| using BufferType = BufferT; |
| |
| LittleEndianByteOrderer() : buffer_() {} |
| explicit LittleEndianByteOrderer(BufferType buffer) : buffer_{buffer} {} |
| LittleEndianByteOrderer(const LittleEndianByteOrderer &other) = default; |
| LittleEndianByteOrderer(LittleEndianByteOrderer &&other) = default; |
| LittleEndianByteOrderer &operator=(const LittleEndianByteOrderer &other) = |
| default; |
| |
| // LittleEndianByteOrderer just passes straight through to the underlying |
| // buffer. |
| bool Ok() const { return buffer_.Ok(); } |
| ::std::size_t SizeInBytes() const { return buffer_.SizeInBytes(); } |
| |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { |
| return buffer_.template ReadLittleEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { |
| return buffer_.template UncheckedReadLittleEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { |
| buffer_.template WriteLittleEndianUInt<kBits>(value); |
| } |
| template </**/ ::std::size_t kBits> |
| void UncheckedWriteUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| buffer_.template UncheckedWriteLittleEndianUInt<kBits>(value); |
| } |
| |
| private: |
| BufferType buffer_; |
| }; |
| |
| // BigEndianByteOrderer is an adapter for a byte buffer class which reverses |
| // the addresses of the underlying byte buffer. It is used to implement |
| // big-endian bit blocks. |
| // |
| // When used by BitBlock, the resulting bits are numbered with "bit 0" as the |
| // lowest-order bit of the *last* byte in the buffer. For example, for a |
| // 12-byte buffer, the bit ordering looks like: |
| // |
| // bit addresses of each bit in each byte |
| // +----+----+----+----+----+----+----+----+----+----+----+----+ |
| // bit in 7 | 95 | 87 | 79 | 71 | 63 | 55 | 47 | 39 | 31 | 23 | 15 | 7 | |
| // byte 6 | 94 | 86 | 78 | 70 | 62 | 54 | 46 | 38 | 30 | 22 | 14 | 6 | |
| // 5 | 93 | 85 | 77 | 69 | 61 | 53 | 45 | 37 | 29 | 21 | 13 | 5 | |
| // 4 | 92 | 84 | 76 | 68 | 60 | 52 | 44 | 36 | 28 | 20 | 12 | 4 | |
| // 3 | 91 | 83 | 75 | 67 | 59 | 51 | 43 | 35 | 27 | 19 | 11 | 3 | |
| // 2 | 90 | 82 | 74 | 66 | 58 | 50 | 42 | 34 | 26 | 18 | 10 | 2 | |
| // 1 | 89 | 81 | 73 | 65 | 57 | 49 | 41 | 33 | 25 | 17 | 9 | 1 | |
| // 0 | 88 | 80 | 72 | 64 | 56 | 48 | 40 | 32 | 24 | 16 | 8 | 0 | |
| // +----+----+----+----+----+----+----+----+----+----+----+----+ |
| // 0 1 2 3 4 5 6 7 8 9 10 11 |
| // byte address |
| // |
| // Note that some big-endian protocols are documented with "bit 0" being the |
| // *high-order* bit of a number, in which case "bit 0" would be the |
| // highest-order bit of the first byte in the buffer. The "bit 0 is the |
| // high-order bit" style seems to be more common in older documents (e.g., RFCs |
| // 791 and 793, for IP and TCP), while the Emboss-style "bit 0 is in the last |
| // byte" seems to be more common in newer documents (e.g., the hardware user |
| // manuals bolms@ examined). |
| // TODO(bolms): Examine more documents to see if the old vs new pattern holds. |
| // |
| // Because endian-specific reads and writes are handled in ContiguousBuffer, |
| // this class exists mostly to translate VerbUInt calls to VerbBigEndianUInt. |
| template <class BufferT> |
| class BigEndianByteOrderer final { |
| public: |
| // Type declaration so that BitBlock can use BufferType::BufferType. |
| using BufferType = BufferT; |
| |
| BigEndianByteOrderer() : buffer_() {} |
| explicit BigEndianByteOrderer(BufferType buffer) : buffer_{buffer} {} |
| BigEndianByteOrderer(const BigEndianByteOrderer &other) = default; |
| BigEndianByteOrderer(BigEndianByteOrderer &&other) = default; |
| BigEndianByteOrderer &operator=(const BigEndianByteOrderer &other) = default; |
| |
| // Ok() and SizeInBytes() get passed through with no changes. |
| bool Ok() const { return buffer_.Ok(); } |
| ::std::size_t SizeInBytes() const { return buffer_.SizeInBytes(); } |
| |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { |
| return buffer_.template ReadBigEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { |
| return buffer_.template UncheckedReadBigEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { |
| buffer_.template WriteBigEndianUInt<kBits>(value); |
| } |
| template </**/ ::std::size_t kBits> |
| void UncheckedWriteUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| buffer_.template UncheckedWriteBigEndianUInt<kBits>(value); |
| } |
| |
| private: |
| BufferType buffer_; |
| }; |
| |
| // NullByteOrderer is a pass-through adapter for a byte buffer class. It is |
| // used to implement single-byte bit blocks, where byte order does not matter. |
| // |
| // Technically, it should be valid to swap in BigEndianByteOrderer or |
| // LittleEndianByteOrderer anywhere that NullByteOrderer is used, but |
| // NullByteOrderer contains a few extra CHECKs to ensure it is being used |
| // correctly. |
| template <class BufferT> |
| class NullByteOrderer final { |
| public: |
| // Type declaration so that BitBlock can use BufferType::BufferType. |
| using BufferType = BufferT; |
| |
| NullByteOrderer() : buffer_() {} |
| explicit NullByteOrderer(BufferType buffer) : buffer_{buffer} {} |
| NullByteOrderer(const NullByteOrderer &other) = default; |
| NullByteOrderer(NullByteOrderer &&other) = default; |
| NullByteOrderer &operator=(const NullByteOrderer &other) = default; |
| |
| bool Ok() const { return buffer_.Ok(); } |
| ::std::size_t SizeInBytes() const { return Ok() ? 1 : 0; } |
| |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned ReadUInt() const { |
| static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); |
| return buffer_.template ReadLittleEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| typename LeastWidthInteger<kBits>::Unsigned UncheckedReadUInt() const { |
| static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); |
| return buffer_.template UncheckedReadLittleEndianUInt<kBits>(); |
| } |
| template </**/ ::std::size_t kBits> |
| void WriteUInt(typename LeastWidthInteger<kBits>::Unsigned value) const { |
| static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); |
| buffer_.template WriteBigEndianUInt<kBits>(value); |
| } |
| template </**/ ::std::size_t kBits> |
| void UncheckedWriteUInt( |
| typename LeastWidthInteger<kBits>::Unsigned value) const { |
| static_assert(kBits == 8, "NullByteOrderer may only read 8-bit values."); |
| buffer_.template UncheckedWriteBigEndianUInt<kBits>(value); |
| } |
| |
| private: |
| BufferType buffer_; |
| }; |
| |
| // OffsetBitBlock is a filter on another BitBlock class, which adds a fixed |
| // offset to reads from underlying bit block. This is used by Emboss generated |
| // classes to read bitfields: the parent provides an OffsetBitBlock of its |
| // buffer to the child's view. |
| // |
| // OffsetBitBlock is always statically sized, but because |
| // BitBlock::GetOffsetStorage and OffsetBitBlock::GetOffsetStorage must have the |
| // same signature as ContiguousBuffer::GetOffsetStorage, OffsetBitBlock's size |
| // parameter must be a runtime value. |
| // |
| // TODO(bolms): Figure out how to add size as a template parameter to |
| // OffsetBitBlock. |
| template <class UnderlyingBitBlockType> |
| class OffsetBitBlock final { |
| public: |
| using ValueType = typename UnderlyingBitBlockType::ValueType; |
| // Bit blocks do not use alignment information, but generated code expects bit |
| // blocks to have the same methods and types as byte blocks, so even though |
| // kNewAlignment and kNewOffset are unused, they must be present as template |
| // parameters. |
| template </**/ ::std::size_t kNewAlignment, ::std::size_t kNewOffset> |
| using OffsetStorageType = OffsetBitBlock<UnderlyingBitBlockType>; |
| |
| OffsetBitBlock() : bit_block_(), offset_(0), size_(0), ok_(false) {} |
| explicit OffsetBitBlock(UnderlyingBitBlockType bit_block, |
| ::std::size_t offset, ::std::size_t size, bool ok) |
| : bit_block_{bit_block}, |
| offset_{static_cast</**/ ::std::uint8_t>(offset)}, |
| size_{static_cast</**/ ::std::uint8_t>(size)}, |
| ok_{offset == offset_ && size == size_ && ok} {} |
| OffsetBitBlock(const OffsetBitBlock &other) = default; |
| OffsetBitBlock &operator=(const OffsetBitBlock &other) = default; |
| |
| template </**/ ::std::size_t kNewAlignment, ::std::size_t kNewOffset> |
| OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage( |
| ::std::size_t offset, ::std::size_t size) const { |
| return OffsetStorageType<kNewAlignment, kNewOffset>{ |
| bit_block_, offset_ + offset, size, ok_ && offset + size <= size_}; |
| } |
| |
| // ReadUInt reads the entire underlying bit block, then shifts and masks to |
| // the appropriate size. |
| ValueType ReadUInt() const { |
| EMBOSS_CHECK_GE(bit_block_.SizeInBits(), offset_ + size_); |
| EMBOSS_CHECK_GE(bit_block_.SizeInBits(), |
| static_cast</**/ ::std::uint64_t>(offset_ + size_)); |
| EMBOSS_CHECK(Ok()); |
| return MaskToNBits(bit_block_.ReadUInt(), offset_ + size_) >> offset_; |
| } |
| ValueType UncheckedReadUInt() const { |
| return MaskToNBits(bit_block_.UncheckedReadUInt(), offset_ + size_) >> |
| offset_; |
| } |
| |
| // WriteUInt writes the entire underlying bit block; in order to only write |
| // the specific bits that should be changed, the current value is first read, |
| // then masked out and or'ed with the new value, and finally the result is |
| // written back to memory. |
| void WriteUInt(ValueType value) const { |
| EMBOSS_CHECK_EQ(value, MaskToNBits(value, size_)); |
| EMBOSS_CHECK(Ok()); |
| // OffsetBitBlock::WriteUInt *always* does a read-modify-write because it is |
| // assumed that if the user wanted to read or write the entire value they |
| // would just use the underlying BitBlock directly. This is mostly true for |
| // code generated by Emboss, which only uses OffsetBitBlock for subfields of |
| // `bits` types; bit-oriented types such as `UInt` will use BitBlock |
| // directly when they are placed directly in a `struct`. |
| bit_block_.WriteUInt(MaskInValue(bit_block_.ReadUInt(), value)); |
| } |
| void UncheckedWriteUInt(ValueType value) const { |
| bit_block_.UncheckedWriteUInt( |
| MaskInValue(bit_block_.UncheckedReadUInt(), value)); |
| } |
| |
| ::std::size_t SizeInBits() const { return size_; } |
| bool Ok() const { return ok_; } |
| |
| private: |
| ValueType MaskInValue(ValueType original_value, ValueType new_value) const { |
| ValueType original_mask = static_cast<ValueType>(~( |
| MaskToNBits(static_cast<ValueType>(~ValueType{0}), size_) << offset_)); |
| return static_cast<ValueType>((original_value & original_mask) | |
| (new_value << offset_)); |
| } |
| |
| const UnderlyingBitBlockType bit_block_; |
| const ::std::uint8_t offset_; |
| const ::std::uint8_t size_; |
| const ::std::uint8_t ok_; |
| }; |
| |
| // BitBlock is a view of a short, fixed-size sequence of bits somewhere in |
| // memory. Big- and little-endian values are handled by BufferType, which is |
| // typically BigEndianByteOrderer<ContiguousBuffer<...>> or |
| // LittleEndianByteOrderer<ContiguousBuffer<...>>. |
| // |
| // BitBlock is implemented such that it always reads and writes its entire |
| // buffer; unlike ContiguousBuffer for bytes, there is no way to modify part of |
| // the underlying data without doing a read-modify-write of the full value. |
| // This sidesteps a lot of weirdness with converting between bit addresses and |
| // byte addresses for big-endian values, though it does mean that in certain |
| // cases excess bits will be read or written, particularly if care is not taken |
| // in the .emb definition to keep `bits` types to a minimum size. |
| template <class BufferType, ::std::size_t kBufferSizeInBits> |
| class BitBlock final { |
| static_assert(kBufferSizeInBits % 8 == 0, |
| "BitBlock can only operate on byte buffers."); |
| static_assert(kBufferSizeInBits <= 64, |
| "BitBlock can only operate on small buffers."); |
| |
| public: |
| using ValueType = typename LeastWidthInteger<kBufferSizeInBits>::Unsigned; |
| // As with OffsetBitBlock::OffsetStorageType, the kNewAlignment and kNewOffset |
| // values are not used, but they must be template parameters so that generated |
| // code can work with both BitBlock and ContiguousBuffer. |
| template </**/ ::std::size_t kNewAlignment, ::std::size_t kNewOffset> |
| using OffsetStorageType = |
| OffsetBitBlock<BitBlock<BufferType, kBufferSizeInBits>>; |
| |
| explicit BitBlock() : buffer_() {} |
| explicit BitBlock(BufferType buffer) : buffer_{buffer} {} |
| explicit BitBlock(typename BufferType::BufferType buffer) : buffer_{buffer} {} |
| BitBlock(const BitBlock &) = default; |
| BitBlock(BitBlock &&) = default; |
| BitBlock &operator=(const BitBlock &) = default; |
| BitBlock &operator=(BitBlock &&) = default; |
| ~BitBlock() = default; |
| |
| static constexpr ::std::size_t Bits() { return kBufferSizeInBits; } |
| |
| template </**/ ::std::size_t kNewAlignment, ::std::size_t kNewOffset> |
| OffsetStorageType<kNewAlignment, kNewOffset> GetOffsetStorage( |
| ::std::size_t offset, ::std::size_t size) const { |
| return OffsetStorageType<kNewAlignment, kNewOffset>{ |
| *this, offset, size, Ok() && offset + size <= kBufferSizeInBits}; |
| } |
| |
| // BitBlock clients must read or write the entire BitBlock value as an |
| // unsigned integer. OffsetBitBlock can be used to extract a portion of the |
| // value via shift and mask, and individual view types such as IntView or |
| // BcdView are expected to convert ValueType to/from their desired types. |
| ValueType ReadUInt() const { |
| return buffer_.template ReadUInt<kBufferSizeInBits>(); |
| } |
| ValueType UncheckedReadUInt() const { |
| return buffer_.template UncheckedReadUInt<kBufferSizeInBits>(); |
| } |
| void WriteUInt(ValueType value) const { |
| EMBOSS_CHECK_EQ(value, MaskToNBits(value, kBufferSizeInBits)); |
| buffer_.template WriteUInt<kBufferSizeInBits>(value); |
| } |
| void UncheckedWriteUInt(ValueType value) const { |
| buffer_.template UncheckedWriteUInt<kBufferSizeInBits>(value); |
| } |
| |
| ::std::size_t SizeInBits() const { return kBufferSizeInBits; } |
| bool Ok() const { |
| return buffer_.Ok() && buffer_.SizeInBytes() * 8 == kBufferSizeInBits; |
| } |
| |
| private: |
| BufferType buffer_; |
| }; |
| |
| } // namespace support |
| } // namespace emboss |
| |
| #endif // EMBOSS_RUNTIME_CPP_EMBOSS_MEMORY_UTIL_H_ |