/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef builtin_intl_Packed_h #define builtin_intl_Packed_h #include "mozilla/Assertions.h" #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include #include #include #include #include #include #include "js/Value.h" namespace js::intl::packed { namespace detail { // js::Bit extended to uint64_t. constexpr uint64_t Bit(uint64_t n) { return uint64_t(1) << n; } constexpr uint64_t BitMask(uint64_t n) { return Bit(n) - 1; } // Concept declaring the required static members for |Field| types. template concept Field = std::unsigned_integral && std::unsigned_integral && std::unsigned_integral; template concept FieldOrUnsigned = Field || std::unsigned_integral; template constexpr uint32_t ShiftAmount() { if constexpr (Field) { return T::Shift + T::Bits; } else { return 0; } } template struct RepresentationSelector; template struct RepresentationSelector { using Type = typename T::Representation; }; template struct RepresentationSelector { using Type = T; }; template consteval auto LargestBitRepresentation() { using R = typename F::Representation; // Largest bit representation of the field with all trailing bits set to ones. constexpr auto shift = F::Bits + F::Shift; return shift == std::numeric_limits::digits ? R(-1) : R{(R(1) << shift) - 1}; } /** * Return true if the bit representation fits into JS::PrivateUint32Value. */ template consteval auto CanRepresentAsPrivateUint32Value() { return LargestBitRepresentation() <= UINT32_MAX; } /** * Return true if the bit representation fits into JS::DoubleValue. */ template consteval auto CanRepresentAsDoubleValue() { constexpr auto largest = LargestBitRepresentation(); // If the largest representation is smaller than the bit representation of // positive infinity, than this field (and any preceding fields) definitely // fit into DoubleValue. return largest < mozilla::InfinityBits::value; } } // namespace detail /** * Helper struct to provide conversion methods from and to JS::Value. */ template struct PackedValue; template struct PackedValue>> { static_assert(detail::CanRepresentAsPrivateUint32Value(), "packed representation fits into PrivateUint32Value"); static uint32_t fromValue(JS::Value value) { uint32_t rawValue = value.toPrivateUint32(); MOZ_ASSERT(rawValue <= detail::LargestBitRepresentation()); return rawValue; } static JS::Value toValue(uint32_t rawValue) { MOZ_ASSERT(rawValue <= detail::LargestBitRepresentation()); return JS::PrivateUint32Value(rawValue); } }; template struct PackedValue>> { static_assert(detail::CanRepresentAsDoubleValue(), "packed representation fits into DoubleValue"); // Assert we don't use a too large raw value representation. static_assert(!detail::CanRepresentAsPrivateUint32Value(), "packed representation could fit into PrivateUint32Value"); static uint64_t fromValue(JS::Value value) { uint64_t rawValue = mozilla::BitwiseCast(value.toDouble()); MOZ_ASSERT(rawValue <= detail::LargestBitRepresentation()); return rawValue; } static JS::Value toValue(uint64_t rawValue) { MOZ_ASSERT(rawValue <= detail::LargestBitRepresentation()); return JS::DoubleValue(mozilla::BitwiseCast(rawValue)); } }; /** * Packed field for enums. * * Template parameters: * 1. Previous field or representation. * 2. First enum value. * 3. Last enum value. * * Usage: * ``` * enum class TestEnum { * First, Second, Third * }; * * using TestEnumField = EnumField< * FieldOrRepresentation, * TestEnum::First, * TestEnum::Third * >; * ``` */ template struct EnumField { using Representation = detail::RepresentationSelector::Type; static_assert(std::is_same_v, "First and Last have the same type"); static_assert(First <= Last, "First and Last are ordered"); using Enum = decltype(First); static_assert(std::is_enum_v); using EnumType = std::underlying_type_t; static constexpr auto FirstValue = static_cast(First); static constexpr auto LastValue = static_cast(Last); static constexpr auto ValueRange = LastValue - FirstValue; static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(std::make_unsigned_t(ValueRange)); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(Enum e) { MOZ_ASSERT(First <= e && e <= Last); auto t = static_cast(e) - FirstValue; return static_cast(t) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr Enum unpack(Representation v) { auto w = (v >> Shift) & detail::BitMask(Bits); MOZ_ASSERT(w <= ValueRange); auto t = static_cast(w + FirstValue); return Enum{t}; } static_assert(unpack(pack(First)) == First, "First value can be unpacked"); static_assert(unpack(pack(Last)) == Last, "Last value can be unpacked"); }; /** * Packed field for optional enums. * * Template parameters: * 1. Previous field or representation. * 2. First enum value. * 3. Last enum value. * * Usage: * ``` * enum class TestEnum { * First, Second, Third * }; * * using TestEnumField = OptionalEnumField< * FieldOrRepresentation, * TestEnum::First, * TestEnum::Third * >; * ``` */ template struct OptionalEnumField { using Representation = detail::RepresentationSelector::Type; static_assert(std::is_same_v, "First and Last have the same type"); static_assert(First <= Last, "First and Last are ordered"); using Enum = decltype(First); static_assert(std::is_enum_v); using EnumType = std::underlying_type_t; static constexpr auto FirstValue = static_cast(First); static constexpr auto LastValue = static_cast(Last); static constexpr auto NoneValue = LastValue + 1; static constexpr auto ValueRange = (NoneValue - FirstValue); static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(std::make_unsigned_t(ValueRange)); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(mozilla::Maybe e) { MOZ_ASSERT_IF(e.isSome(), First <= *e && *e <= Last); auto t = (e.isSome() ? static_cast(e.value()) : NoneValue) - FirstValue; return static_cast(t) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr mozilla::Maybe unpack(Representation v) { auto w = ((v >> Shift) & detail::BitMask(Bits)); MOZ_ASSERT(w <= ValueRange); auto t = static_cast(w + FirstValue); if (t == NoneValue) { return mozilla::Nothing(); } return mozilla::Some(Enum{t}); } static_assert(unpack(pack(mozilla::Some(First))) == mozilla::Some(First), "First value can be unpacked"); static_assert(unpack(pack(mozilla::Some(Last))) == mozilla::Some(Last), "Last value can be unpacked"); static_assert(unpack(pack(mozilla::Nothing())) == mozilla::Nothing(), "Nothing value can be unpacked"); }; /** * Packed field for boolean values. * * Template parameters: * 1. Previous field or representation. * * Usage: * ``` * using TestBooleanField = BooleanField; * ``` */ template struct BooleanField { using Representation = detail::RepresentationSelector::Type; static constexpr auto FirstValue = uint32_t(false); static constexpr auto LastValue = uint32_t(true); static constexpr auto ValueRange = (LastValue - FirstValue); static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(std::make_unsigned_t(ValueRange)); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(bool e) { auto t = uint32_t(e) - FirstValue; return static_cast(t) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr bool unpack(Representation v) { auto w = ((v >> Shift) & detail::BitMask(Bits)); MOZ_ASSERT(w <= ValueRange); auto t = uint32_t(w + FirstValue); return bool(t); } static_assert(unpack(pack(false)) == false, "False value can be unpacked"); static_assert(unpack(pack(true)) == true, "True value can be unpacked"); }; /** * Packed field for optional boolean values. * * Template parameters: * 1. Previous field or representation. * * Usage: * ``` * using TestBooleanField = OptionalBooleanField; * ``` */ template struct OptionalBooleanField { using Representation = detail::RepresentationSelector::Type; static constexpr auto FirstValue = uint32_t(false); static constexpr auto LastValue = uint32_t(true); static constexpr auto NoneValue = LastValue + 1; static constexpr auto ValueRange = (NoneValue - FirstValue); static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(std::make_unsigned_t(ValueRange)); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(mozilla::Maybe e) { auto t = (e.isSome() ? uint32_t(e.value()) : NoneValue) - FirstValue; return static_cast(t) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr mozilla::Maybe unpack(Representation v) { auto w = ((v >> Shift) & detail::BitMask(Bits)); MOZ_ASSERT(w <= ValueRange); auto t = uint32_t(w + FirstValue); if (t == NoneValue) { return mozilla::Nothing(); } return mozilla::Some(bool(t)); } static_assert(unpack(pack(mozilla::Some(false))) == mozilla::Some(false), "True value can be unpacked"); static_assert(unpack(pack(mozilla::Some(true))) == mozilla::Some(true), "False value can be unpacked"); static_assert(unpack(pack(mozilla::Nothing())) == mozilla::Nothing(), "Nothing value can be unpacked"); }; /** * Packed field for a range of values. * * Template parameters: * 1. Previous field or representation. * 2. Range value type. * 3. First range value. * 4. Last range value. * * Usage: * ``` * using TestRangeField = RangeField; * ``` */ template struct RangeField { using Representation = detail::RepresentationSelector::Type; static_assert(std::is_same_v); static_assert(First < Last); using Type = RangeType; static constexpr auto FirstValue = First; static constexpr auto LastValue = Last; static constexpr auto ValueRange = (LastValue - FirstValue); static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(std::make_unsigned_t(ValueRange)); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(Type e) { MOZ_ASSERT(e >= FirstValue); MOZ_ASSERT(e <= LastValue); return static_cast(e - FirstValue) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr Type unpack(Representation v) { auto w = (v >> Shift) & detail::BitMask(Bits); MOZ_ASSERT(w <= ValueRange); return static_cast(w + FirstValue); } static_assert(unpack(pack(First)) == First, "First value can be unpacked"); static_assert(unpack(pack(Last)) == Last, "Last value can be unpacked"); }; /** * Packed field for a list of values. * * Template parameters: * 1. Previous field or representation. * 2. List of valid values. * * Usage: * ``` * using TestListField = ListField< * FieldOrRepresentation, * std::to_array({1, 2, 5, 10}) * >; * ``` */ template struct ListField { using Representation = detail::RepresentationSelector::Type; using Type = decltype(List)::value_type; static constexpr auto ValueRange = std::size(List); static constexpr uint32_t Shift = detail::ShiftAmount(); static constexpr uint32_t Bits = std::bit_width(ValueRange); static_assert(Bits <= std::numeric_limits::digits - Shift, "too few available bits"); /** * Return the packed representation of |e|. */ static constexpr Representation pack(Type e) { auto it = std::find(std::begin(List), std::end(List), e); MOZ_ASSERT(it != std::end(List)); auto index = std::distance(std::begin(List), it); return static_cast(index) << Shift; } /** * Unpack the packed representation value |v|. */ static constexpr Type unpack(Representation v) { size_t index = (v >> Shift) & detail::BitMask(Bits); MOZ_ASSERT(index < std::size(List)); return static_cast(*std::next(std::begin(List), index)); } static_assert(std::empty(List) || unpack(pack(*std::begin(List))) == *std::begin(List), "First value can be unpacked"); static_assert(std::empty(List) || unpack(pack(*std::rbegin(List))) == *std::rbegin(List), "Last value can be unpacked"); }; } // namespace js::intl::packed #endif // builtin_intl_Packed_h