// 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. // This header contains implementations of the types in the Emboss Prelude // (UInt, Int, Flag, etc.) #ifndef EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_ #define EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_ #include #include #include #include #include #include #include #include "runtime/cpp/emboss_cpp_util.h" // Forward declarations for optional text processing helpers. namespace emboss { class TextOutputOptions; namespace support { template bool ReadBooleanFromTextStream(View *view, Stream *stream); template void WriteBooleanViewToTextStream(View *view, Stream *stream, const TextOutputOptions &); template bool ReadIntegerFromTextStream(View *view, Stream *stream); template void WriteIntegerViewToTextStream(View *view, Stream *stream, const TextOutputOptions &options); template bool ReadFloatFromTextStream(View *view, Stream *stream); template void WriteFloatToTextStream(Float n, Stream *stream, const TextOutputOptions &options); } // namespace support } // namespace emboss // This namespace must match the [(cpp) namespace] in the Emboss prelude. namespace emboss { namespace prelude { // FlagView is the C++ implementation of the Emboss "Flag" type, which is a // 1-bit value. template class FlagView final { public: static_assert(Parameters::kBits == 1, "FlagView must be 1 bit."); explicit FlagView(BitBlock bits) : bit_block_{bits} {} FlagView() : bit_block_() {} FlagView(const FlagView &) = default; FlagView(FlagView &&) = default; FlagView &operator=(const FlagView &) = default; FlagView &operator=(FlagView &&) = default; ~FlagView() = default; bool Read() const { bool result = bit_block_.ReadUInt(); EMBOSS_CHECK(Parameters::ValueIsOk(result)); return result; } bool UncheckedRead() const { return bit_block_.UncheckedReadUInt(); } void Write(bool value) const { const bool result = TryToWrite(value); (void)result; EMBOSS_CHECK(result); } bool TryToWrite(bool value) const { if (!CouldWriteValue(value)) return false; if (!IsComplete()) return false; bit_block_.WriteUInt(value); return true; } static constexpr bool CouldWriteValue(bool value) { return Parameters::ValueIsOk(value); } void UncheckedWrite(bool value) const { bit_block_.UncheckedWriteUInt(value); } template void CopyFrom(const OtherView &other) const { Write(other.Read()); } template void UncheckedCopyFrom(const OtherView &other) const { UncheckedWrite(other.UncheckedRead()); } template bool TryToCopyFrom(const OtherView &other) const { return TryToWrite(other.Read()); } bool Ok() const { return IsComplete() && Parameters::ValueIsOk(UncheckedRead()); } template bool Equals(const FlagView &other) const { return Read() == other.Read(); } template bool UncheckedEquals(const FlagView &other) const { return UncheckedRead() == other.UncheckedRead(); } bool IsComplete() const { return bit_block_.Ok() && bit_block_.SizeInBits() > 0; } template bool UpdateFromTextStream(Stream *stream) const { return ::emboss::support::ReadBooleanFromTextStream(this, stream); } template void WriteToTextStream(Stream *stream, const ::emboss::TextOutputOptions &options) const { ::emboss::support::WriteBooleanViewToTextStream(this, stream, options); } static constexpr bool IsAggregate() { return false; } private: BitBlock bit_block_; }; // UIntView is a view for UInts inside of bitfields. template class UIntView final { public: using ValueType = typename ::emboss::support::LeastWidthInteger< Parameters::kBits>::Unsigned; static_assert( Parameters::kBits <= sizeof(ValueType) * 8, "UIntView requires sizeof(ValueType) * 8 >= Parameters::kBits."); template explicit UIntView(Args &&...args) : buffer_{::std::forward(args)...} {} UIntView() : buffer_() {} UIntView(const UIntView &) = default; UIntView(UIntView &&) = default; UIntView &operator=(const UIntView &) = default; UIntView &operator=(UIntView &&) = default; ~UIntView() = default; ValueType Read() const { ValueType result = static_cast(buffer_.ReadUInt()); EMBOSS_CHECK(Parameters::ValueIsOk(result)); return result; } ValueType UncheckedRead() const { return buffer_.UncheckedReadUInt(); } // The Write, TryToWrite, and CouldWriteValue methods are templated in order // to avoid surprises due to implicit narrowing. // // In C++, you can pass (say) an `int` to a function expecting `uint8_t`, and // the compiler will silently cast the `int` to `uint8_t`, which can change // the value. Even with fairly aggressive warnings, something like this will // silently compile, and print `256 is not >= 128!`: // // bool is_big_uint8(uint8_t value) { return value >= 128; } // bool is_big(uint32_t value) { return is_big_uint8(value); } // int main() { // assert(!is_big(256)); // big is truncated to 0. // std::cout << 256 << " is not >= 128!\n"; // return 0; // } // // (Most compilers will give a warning when directly passing a *constant* that // gets truncated; for example, GCC will throw -Woverflow on // `is_big_uint8(256U)`.) template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> void Write(IntT value) const { const bool result = TryToWrite(value); (void)result; EMBOSS_CHECK(result); } template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> bool TryToWrite(IntT value) const { if (!CouldWriteValue(value)) return false; if (!IsComplete()) return false; buffer_.WriteUInt(static_cast(value)); return true; } template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> static constexpr bool CouldWriteValue(IntT value) { // Implicit conversions are doing some work here, but the upshot is that the // value must be at least 0, and at most (2**kBits)-1. The clause to // compute (2**kBits)-1 should not be "simplified" further. // // Because of C++ implicit integer promotions, the (2**kBits)-1 computation // works differently when `ValueType` is smaller than `unsigned int` than it // does when `ValueType` is at least as big as `unsigned int`. // // For example, when `ValueType` is `uint8_t` and `kBits` is 8: // // 1. `static_cast(1)` becomes `uint8_t(1)`. // 2. `uint8_t(1) << (kBits - 1)` is `uint8_t(1) << 7`. // 3. The shift operator `<<` promotes its left operand to `unsigned`, // giving `unsigned(1) << 7`. // 4. `unsigned(1) << 7` becomes `unsigned(0x80)`. // 5. `unsigned(0x80) << 1` becomes `unsigned(0x100)`. // 6. Finally, `unsigned(0x100) - 1` is `unsigned(0xff)`. // // (Note that the cases where `kBits` is less than `sizeof(ValueType) * 8` // are very similar.) // // When `ValueType` is `uint32_t`, `unsigned` is 32 bits, and `kBits` is 32: // // 1. `static_cast(1)` becomes `uint32_t(1)`. // 2. `uint32_t(1) << (kBits - 1)` is `uint32_t(1) << 31`. // 3. The shift operator `<<` does *not* further promote `uint32_t`. // 4. `uint32_t(1) << 31` becomes `uint32_t(0x80000000)`. Note that // `uint32_t(1) << 32` would be undefined behavior (shift of >= the // size of the left operand type), which is why the shift is broken // into two parts. // 5. `uint32_t(0x80000000) << 1` overflows, leaving `uint32_t(0)`. // 6. `uint32_t(0) - 1` underflows, leaving `uint32_t(0xffffffff)`. // // Because unsigned overflow and underflow are defined to be modulo 2**N, // where N is the number of bits in the type, this is entirely // standards-compliant. return value >= 0 && static_cast(value) <= ((static_cast(1) << (Parameters::kBits - 1)) << 1) - 1 && Parameters::ValueIsOk(value); } void UncheckedWrite(ValueType value) const { buffer_.UncheckedWriteUInt(value); } template void CopyFrom(const OtherView &other) const { Write(other.Read()); } template void UncheckedCopyFrom(const OtherView &other) const { UncheckedWrite(other.UncheckedRead()); } template bool TryToCopyFrom(const OtherView &other) const { return other.Ok() && TryToWrite(other.Read()); } // All bit patterns in the underlying buffer are valid, so Ok() is always // true if IsComplete() is true. bool Ok() const { return IsComplete() && Parameters::ValueIsOk(UncheckedRead()); } template bool Equals(const UIntView &other) const { return Read() == other.Read(); } template bool UncheckedEquals( const UIntView &other) const { return UncheckedRead() == other.UncheckedRead(); } bool IsComplete() const { return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits; } template bool UpdateFromTextStream(Stream *stream) const { return support::ReadIntegerFromTextStream(this, stream); } template void WriteToTextStream(Stream *stream, ::emboss::TextOutputOptions &options) const { support::WriteIntegerViewToTextStream(this, stream, options); } static constexpr bool IsAggregate() { return false; } static constexpr int SizeInBits() { return Parameters::kBits; } private: BitViewType buffer_; }; // IntView is a view for Ints inside of bitfields. template class IntView final { public: using ValueType = typename ::emboss::support::LeastWidthInteger::Signed; static_assert(Parameters::kBits <= sizeof(ValueType) * 8, "IntView requires sizeof(ValueType) * 8 >= Parameters::kBits."); template explicit IntView(Args &&...args) : buffer_{::std::forward(args)...} {} IntView() : buffer_() {} IntView(const IntView &) = default; IntView(IntView &&) = default; IntView &operator=(const IntView &) = default; IntView &operator=(IntView &&) = default; ~IntView() = default; ValueType Read() const { ValueType value = ConvertToSigned(buffer_.ReadUInt()); EMBOSS_CHECK(Parameters::ValueIsOk(value)); return value; } ValueType UncheckedRead() const { return ConvertToSigned(buffer_.UncheckedReadUInt()); } // As with UIntView, above, Write, TryToWrite, and CouldWriteValue need to be // templated in order to avoid surprises due to implicit narrowing // conversions. template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> void Write(IntT value) const { const bool result = TryToWrite(value); (void)result; EMBOSS_CHECK(result); } template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> bool TryToWrite(IntT value) const { if (!CouldWriteValue(value)) return false; if (!IsComplete()) return false; buffer_.WriteUInt(::emboss::support::MaskToNBits( static_cast(value), Parameters::kBits)); return true; } template ::type>::type>:: is_integer && !::std::is_same::type>::type>::value) || ::std::is_enum::value>::type> static constexpr bool CouldWriteValue(IntT value) { // This effectively checks that value >= -(2**(kBits-1) and value <= // (2**(kBits-1))-1. // // This has to be done somewhat piecemeal, in order to avoid various bits of // undefined and implementation-defined behavior. // // First, if IntT is an unsigned type, the check that value >= // -(2**(kBits-1)) is skipped, in order to avoid any signed <-> unsigned // conversions. // // Second, if kBits is 1, then the limits -1 and 0 are explicit, so that // there is never a shift by -1 (which is undefined behavior). // // Third, the shifts are by (kBits - 2), so that they do not alter sign // bits. To get the final bounds, we use a bit of addition and // multiplication. For example, for 8 bits, the lower bound is (1 << 6) * // -2, which is 64 * -2, which is -128. The corresponding upper bound is // ((1 << 6) - 1) * 2 + 1, which is (64 - 1) * 2 + 1, which is 63 * 2 + 1, // which is 126 + 1, which is 127. The upper bound must be computed in // multiple steps like this in order to avoid overflow. return (!::std::is_signed::type>::type>::value || static_cast(value) >= (Parameters::kBits == 1 ? -1 : (static_cast(1) << (Parameters::kBits - 2)) * -2)) && value <= (Parameters::kBits == 1 ? 0 : ((static_cast(1) << (Parameters::kBits - 2)) - 1) * 2 + 1) && Parameters::ValueIsOk(value); } void UncheckedWrite(ValueType value) const { buffer_.UncheckedWriteUInt(::emboss::support::MaskToNBits( static_cast(value), Parameters::kBits)); } template void CopyFrom(const OtherView &other) const { Write(other.Read()); } template void UncheckedCopyFrom(const IntView &other) const { UncheckedWrite(other.UncheckedRead()); } template bool TryToCopyFrom(const OtherView &other) const { return other.Ok() && TryToWrite(other.Read()); } // All bit patterns in the underlying buffer are valid, so Ok() is always // true if IsComplete() is true. bool Ok() const { return IsComplete() && Parameters::ValueIsOk(UncheckedRead()); } template bool Equals(const IntView &other) const { return Read() == other.Read(); } template bool UncheckedEquals( const IntView &other) const { return UncheckedRead() == other.UncheckedRead(); } bool IsComplete() const { return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits; } template bool UpdateFromTextStream(Stream *stream) const { return support::ReadIntegerFromTextStream(this, stream); } template void WriteToTextStream(Stream *stream, ::emboss::TextOutputOptions &options) const { support::WriteIntegerViewToTextStream(this, stream, options); } static constexpr bool IsAggregate() { return false; } static constexpr int SizeInBits() { return Parameters::kBits; } private: static ValueType ConvertToSigned(typename BitViewType::ValueType data) { static_assert(sizeof(ValueType) <= sizeof(typename BitViewType::ValueType), "Integer types wider than BitViewType::ValueType are not " "supported."); #if EMBOSS_SYSTEM_IS_TWOS_COMPLEMENT // static_cast from unsigned to signed is implementation-defined when the // value does not fit in the signed type (in this case, when the final value // should be negative). Most implementations use a reasonable definition, // so on most systems we can just cast. // // If the integer does not take up the full width of ValueType, it needs to // be sign-extended until it does. The easiest way to do this is to shift // until the sign bit is in the topmost position, then cast to signed, then // shift back. The shift back will copy the sign bit. return static_cast( data << (sizeof(ValueType) * 8 - Parameters::kBits)) >> (sizeof(ValueType) * 8 - Parameters::kBits); #else // Otherwise, in order to convert without running into // implementation-defined behavior, first mask out the sign bit. This // results in (final result MOD 2 ** (width of int in bits - 1)). That // value can be safely converted to the signed ValueType. // // Finally, if the sign bit was set, subtract (2 ** (width of int in bits - // 2)) twice. // // The 1-bit signed integer case must be handled separately, but it is // (fortunately) quite easy to enumerate both possible values. if (Parameters::kBits == 1) { if (data == 0) { return 0; } else if (data == 1) { return -1; } else { EMBOSS_CHECK(false); return -1; // Return value if EMBOSS_CHECK is disabled. } } else { typename BitViewType::ValueType sign_bit = static_cast(1) << (Parameters::kBits - 1); typename BitViewType::ValueType mask = sign_bit - 1; typename BitViewType::ValueType data_mod2_to_n = mask & data; ValueType result_sign_bit = static_cast((data & sign_bit) >> 1); return data_mod2_to_n - result_sign_bit - result_sign_bit; } #endif } BitViewType buffer_; }; // The maximum Binary-Coded Decimal (BCD) value that fits in a particular number // of bits. template constexpr inline ValueType MaxBcd(int bits) { return bits < 4 ? (1 << bits) - 1 : 10 * (MaxBcd(bits - 4) + 1) - 1; } template inline bool IsBcd(ValueType x) { // Adapted from: // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord // // This determines if any nibble has a value greater than 9. It does // this by treating operations on the n-bit value as parallel operations // on n/4 4-bit values. // // The result is computed in the high bit of each nibble: if any of those // bits is set in the end, then at least one nibble had a value in the // range 10-15. // // The first check is subtle: ~x is equivalent to (nibble = 15 - nibble). // Then, 6 is subtracted from each nibble. This operation will underflow // if the original value was more than 9, leaving the high bit of the // nibble set. It will also leave the high bit of the nibble set // (without underflow) if the original value was 0 or 1. // // The second check is just x: the high bit of each nibble in x is set if // that nibble's value is 8-15. // // Thus, the first check leaves the high bit set in any nibble with the // value 0, 1, or 10-15, and the second check leaves the high bit set in // any nibble with the value 8-15. Bitwise-anding these results, high // bits are only set if the original value was 10-15. // // The underflow condition in the first check can screw up the condition // for nibbles in higher positions than the underflowing nibble. This // cannot affect the overall boolean result, because the underflow // condition only happens if a nibble was greater than 9, and therefore // *that* nibble's final value will be nonzero, and therefore the whole // result will be nonzero, no matter what happens in the higher-order // nibbles. // // A couple of examples in 16 bit: // // x = 0x09a8 // (~0x09a8 - 0x6666) & 0x09a8 & 0x8888 // ( 0xf657 - 0x6666) & 0x09a8 & 0x8888 // 0x8ff1 & 0x09a8 & 0x8888 // 0x09a0 & 0x8888 // 0x0880 Note the underflow into nibble 2 // // x = 0x1289 // (~0x1289 - 0x6666) & 0x1289 & 0x8888 // ( 0xed76 - 0x6666) & 0x1289 & 0x8888 // 0x8710 & 0x1289 & 0x8888 // 0x0200 & 0x8888 // 0x0000 static_assert(!::std::is_signed::value, "IsBcd only works on unsigned values."); if (sizeof(ValueType) < sizeof(unsigned)) { // For types with lower integer conversion rank than unsigned int, integer // promotion rules cause many implicit conversions to signed int in the math // below, which makes the math go wrong. Rather than add a dozen explicit // casts back to ValueType, just do the math as 'unsigned'. return IsBcd(x); } else { return ((~x - (~ValueType{0} / 0xf * 0x6 /* 0x6666...6666 */)) & x & (~ValueType{0} / 0xf * 0x8 /* 0x8888...8888 */)) == 0; } } // Base template for Binary-Coded Decimal (BCD) unsigned integer readers. template class BcdView final { public: using ValueType = typename ::emboss::support::LeastWidthInteger< Parameters::kBits>::Unsigned; static_assert(Parameters::kBits <= sizeof(ValueType) * 8, "BcdView requires sizeof(ValueType) * 8 >= Parameters::kBits."); template explicit BcdView(Args &&...args) : buffer_{::std::forward(args)...} {} BcdView() : buffer_() {} BcdView(const BcdView &) = default; BcdView(BcdView &&) = default; BcdView &operator=(const BcdView &) = default; BcdView &operator=(BcdView &&) = default; ~BcdView() = default; ValueType Read() const { EMBOSS_CHECK(Ok()); return ConvertToBinary(buffer_.ReadUInt()); } ValueType UncheckedRead() const { return ConvertToBinary(buffer_.UncheckedReadUInt()); } void Write(ValueType value) const { const bool result = TryToWrite(value); (void)result; EMBOSS_CHECK(result); } bool TryToWrite(ValueType value) const { if (!CouldWriteValue(value)) return false; if (!IsComplete()) return false; buffer_.WriteUInt(ConvertToBcd(value)); return true; } static constexpr bool CouldWriteValue(ValueType value) { return value <= MaxValue() && Parameters::ValueIsOk(value); } void UncheckedWrite(ValueType value) const { buffer_.UncheckedWriteUInt(ConvertToBcd(value)); } template bool UpdateFromTextStream(Stream *stream) const { return support::ReadIntegerFromTextStream(this, stream); } template void WriteToTextStream(Stream *stream, ::emboss::TextOutputOptions &options) const { // TODO(bolms): This shares the numeric_base() option with IntView and // UIntView (and EnumView, for unknown enum values). It seems like an end // user might prefer to see BCD values in decimal, even if they want to see // values of other numeric types in hex or binary. It seems like there // could be some fancy C++ trickery to allow separate options for separate // view types. support::WriteIntegerViewToTextStream(this, stream, options); } static constexpr bool IsAggregate() { return false; } template void CopyFrom(const OtherView &other) const { Write(other.Read()); } template void UncheckedCopyFrom(const OtherView &other) const { UncheckedWrite(other.UncheckedRead()); } template bool TryToCopyFrom(const OtherView &other) const { return other.Ok() && TryToWrite(other.Read()); } bool Ok() const { if (!IsComplete()) return false; if (!IsBcd(buffer_.ReadUInt())) return false; if (!Parameters::ValueIsOk(UncheckedRead())) return false; return true; } template bool Equals(const BcdView &other) const { return Read() == other.Read(); } template bool UncheckedEquals( const BcdView &other) const { return UncheckedRead() == other.UncheckedRead(); } bool IsComplete() const { return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits; } static constexpr int SizeInBits() { return Parameters::kBits; } private: static ValueType ConvertToBinary(ValueType bcd_value) { ValueType result = 0; ValueType multiplier = 1; for (int shift = 0; shift < Parameters::kBits; shift += 4) { result += ((bcd_value >> shift) & 0xf) * multiplier; multiplier *= 10; } return result; } static ValueType ConvertToBcd(ValueType value) { ValueType bcd_value = 0; for (int shift = 0; shift < Parameters::kBits; shift += 4) { bcd_value |= (value % 10) << shift; value /= 10; } return bcd_value; } static constexpr ValueType MaxValue() { return MaxBcd(Parameters::kBits); } BitViewType buffer_; }; // FloatView is the view for the Emboss Float type. template class FloatView final { static_assert(Parameters::kBits == 32 || Parameters::kBits == 64, "Only 32- and 64-bit floats are currently supported."); public: using ValueType = typename support::FloatType::Type; template explicit FloatView(Args &&...args) : buffer_{::std::forward(args)...} {} FloatView() : buffer_() {} FloatView(const FloatView &) = default; FloatView(FloatView &&) = default; FloatView &operator=(const FloatView &) = default; FloatView &operator=(FloatView &&) = default; ~FloatView() = default; ValueType Read() const { return ConvertToFloat(buffer_.ReadUInt()); } ValueType UncheckedRead() const { return ConvertToFloat(buffer_.UncheckedReadUInt()); } void Write(ValueType value) const { const bool result = TryToWrite(value); (void)result; EMBOSS_CHECK(result); } bool TryToWrite(ValueType value) const { if (!CouldWriteValue(value)) return false; if (!IsComplete()) return false; buffer_.WriteUInt(ConvertToUInt(value)); return true; } static constexpr bool CouldWriteValue(ValueType value) { // Avoid unused parameters error: static_cast(value); return true; } void UncheckedWrite(ValueType value) const { buffer_.UncheckedWriteUInt(ConvertToUInt(value)); } template void CopyFrom(const OtherView &other) const { Write(other.Read()); } template void UncheckedCopyFrom(const OtherView &other) const { UncheckedWrite(other.UncheckedRead()); } template bool TryToCopyFrom(const OtherView &other) const { return other.Ok() && TryToWrite(other.Read()); } // All bit patterns in the underlying buffer are valid, so Ok() is always // true if IsComplete() is true. bool Ok() const { return IsComplete(); } template bool Equals(const FloatView &other) const { return Read() == other.Read(); } template bool UncheckedEquals( const FloatView &other) const { return UncheckedRead() == other.UncheckedRead(); } bool IsComplete() const { return buffer_.Ok() && buffer_.SizeInBits() >= Parameters::kBits; } template bool UpdateFromTextStream(Stream *stream) const { return support::ReadFloatFromTextStream(this, stream); } template void WriteToTextStream(Stream *stream, ::emboss::TextOutputOptions &options) const { support::WriteFloatToTextStream(Read(), stream, options); } static constexpr bool IsAggregate() { return false; } static constexpr int SizeInBits() { return Parameters::kBits; } private: using UIntType = typename support::FloatType::UIntType; static ValueType ConvertToFloat(UIntType bits) { // TODO(bolms): This method assumes a few things that are not always // strictly true; e.g., that uint32_t and float have the same endianness. ValueType result; memcpy(static_cast(&result), static_cast(&bits), sizeof result); return result; } static UIntType ConvertToUInt(ValueType value) { // TODO(bolms): This method assumes a few things that are not always // strictly true; e.g., that uint32_t and float have the same endianness. UIntType bits; memcpy(static_cast(&bits), static_cast(&value), sizeof bits); return bits; } BitViewType buffer_; }; } // namespace prelude } // namespace emboss #endif // EMBOSS_RUNTIME_CPP_EMBOSS_PRELUDE_H_