blob: 09be2f105e1e7f57b1e80137a5d841ed3c3b5fe4 [file] [log] [blame]
// 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_