/* 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 DOM_CANVAS_TIED_FIELDS_H #define DOM_CANVAS_TIED_FIELDS_H #include "TupleUtils.h" namespace mozilla { // - /** * TiedFields(T&) -> std::tuple * TiedFields(const T&) -> std::tuple * * You can also overload TiedFields without adding T::MutTiedFields: * template<> * inline auto TiedFields(gfx::IntSize& a) { * return std::tie(a.width, a.height); * } */ template constexpr auto TiedFields(T& t) { const auto fields = t.MutTiedFields(); return fields; } template > constexpr auto TiedFields(const T& t) { // Uncast const to get mutable-fields tuple, but reapply const to tuple args. // We should do better than this when C++ gets a solution other than macros. const auto mutFields = TiedFields(const_cast(t)); return ToTupleOfConstRefs(mutFields); } /** * Returns true if all bytes in T are accounted for via size of all tied fields. * Returns false if there's bytes unaccounted for, which might indicate either * unaccounted-for padding or missing fields. * The goal is to check that TiedFields returns every field in T, and this * returns false if it suspects there are bytes that are not accounted for by * TiedFields. * * `constexpr` effectively cannot do math on pointers, so it's not possible to * figure out via `constexpr` whether fields are consecutive or dense. * However, we can at least compare `sizeof(T)` to the sum of `sizeof(Args...)` * for `TiedFields(T) -> std::tuple`. * * See TiedFieldsExamples. */ template constexpr bool AreAllBytesTiedFields() { using fieldsT = decltype(TiedFields(std::declval())); const auto fields_size_sum = SizeofTupleArgs::value; const auto t_size = sizeof(T); return fields_size_sum == t_size; } // It's also possible to determine AreAllBytesRecursiveTiedFields: // https://hackmd.io/@jgilbert/B16qa0Fa9 // - template struct FieldDebugInfoT { static constexpr bool IsTightlyPacked() { return PrevFieldEndOffset % FieldAlignment == 0; } }; template struct TightlyPackedFieldEndOffsetT { template using FieldTAt = std::remove_reference_t< typename std::tuple_element::type>; static constexpr size_t Fn() { constexpr auto num_fields = std::tuple_size_v; static_assert(FieldId < num_fields); using PrevFieldT = FieldTAt; using FieldT = FieldTAt; constexpr auto prev_field_end_offset = TightlyPackedFieldEndOffsetT::Fn(); constexpr auto prev_field_begin_offset = prev_field_end_offset - sizeof(PrevFieldT); using FieldDebugInfoT = FieldDebugInfoT; static_assert(FieldDebugInfoT::IsTightlyPacked(), "This field was not tightly packed. Is there padding between " "it and its predecessor?"); return prev_field_end_offset + sizeof(FieldT); } }; template struct TightlyPackedFieldEndOffsetT { static constexpr size_t Fn() { using FieldT = typename std::tuple_element<0, TupleOfFields>::type; return sizeof(FieldT); } }; template struct TightlyPackedFieldEndOffsetT { static constexpr size_t Fn() { // -1 means tuple_size_v -> 0. static_assert(sizeof(StructT) == 0); return 0; } }; template constexpr bool AssertTiedFieldsAreExhaustive() { using TupleOfFields = decltype(std::declval().MutTiedFields()); constexpr auto num_fields = std::tuple_size_v; constexpr auto end_offset_of_last_field = TightlyPackedFieldEndOffsetT::Fn(); static_assert( end_offset_of_last_field == sizeof(StructT), "Incorrect field list in MutTiedFields()? (or not tightly-packed?)"); return true; // Support `static_assert(AssertTiedFieldsAreExhaustive())`. } // - /** * PaddingField can be used to pad out a struct so that it's not * implicitly padded by struct rules, but also can't be accidentally initialized * via Aggregate Initialization. (TiedFields serialization checks rely on object * fields leaving no implicit padding bytes, but explicit padding fields are * fine) While you can use e.g. `uint8_t _padding[3];`, consider instead * `PaddingField _padding;` for clarity and to move the `3` nearer * to the `uint8_t`. */ template struct PaddingField { static_assert(!std::is_array_v, "Use PaddingField not ."); std::array ignored = {}; PaddingField() {} friend constexpr bool operator==(const PaddingField&, const PaddingField&) { return true; } friend constexpr bool operator<(const PaddingField&, const PaddingField&) { return false; } auto MutTiedFields() { return std::tie(ignored); } }; static_assert(sizeof(PaddingField) == 1); static_assert(sizeof(PaddingField) == 2); static_assert(sizeof(PaddingField) == 4); // - namespace TiedFieldsExamples { struct Cat { int i; bool b; constexpr auto MutTiedFields() { return std::tie(i, b); } }; static_assert(sizeof(Cat) == 8); static_assert(!AreAllBytesTiedFields()); struct Dog { bool b; int i; constexpr auto MutTiedFields() { return std::tie(i, b); } }; static_assert(sizeof(Dog) == 8); static_assert(!AreAllBytesTiedFields()); struct Fish { bool b; bool padding[3]; int i; constexpr auto MutTiedFields() { return std::tie(i, b, padding); } }; static_assert(sizeof(Fish) == 8); static_assert(AreAllBytesTiedFields()); struct Eel { // Like a Fish, but you can skip serializing the padding. bool b; PaddingField padding; int i; constexpr auto MutTiedFields() { return std::tie(i, b, padding); } }; static_assert(sizeof(Eel) == 8); static_assert(AreAllBytesTiedFields()); // - // #define LETS_USE_BIT_FIELDS #ifdef LETS_USE_BIT_FIELDS # undef LETS_USE_BIT_FIELDS struct Platypus { short s : 1; short s2 : 1; int i; constexpr auto MutTiedFields() { return std::tie(s, s2, i); // Error: Can't take reference to bit-field. } }; #endif // - struct FishTank { Fish f; int i2; constexpr auto MutTiedFields() { return std::tie(f, i2); } }; static_assert(sizeof(FishTank) == 12); static_assert(AreAllBytesTiedFields()); struct CatCarrier { Cat c; int i2; constexpr auto MutTiedFields() { return std::tie(c, i2); } }; static_assert(sizeof(CatCarrier) == 12); static_assert(AreAllBytesTiedFields()); static_assert( !AreAllBytesTiedFields()); // BUT BEWARE THIS! // For example, if we had AreAllBytesRecursiveTiedFields: // static_assert(!AreAllBytesRecursiveTiedFields()); } // namespace TiedFieldsExamples } // namespace mozilla #endif // DOM_CANVAS_TIED_FIELDS_H