TiedFields.h (7692B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #ifndef DOM_CANVAS_TIED_FIELDS_H 6 #define DOM_CANVAS_TIED_FIELDS_H 7 8 #include <array> 9 10 #include "TupleUtils.h" 11 12 namespace mozilla { 13 14 // - 15 16 /** 17 * TiedFields(T&) -> std::tuple<Fields&...> 18 * TiedFields(const T&) -> std::tuple<const Fields&...> 19 * 20 * You can also overload TiedFields without adding T::MutTiedFields: 21 * template<> 22 * inline auto TiedFields<gfx::IntSize>(gfx::IntSize& a) { 23 * return std::tie(a.width, a.height); 24 * } 25 */ 26 template <class T> 27 constexpr auto TiedFields(T& t) { 28 const auto fields = t.MutTiedFields(); 29 return fields; 30 } 31 template <class T, class... Args, class Tup = std::tuple<Args&...>> 32 constexpr auto TiedFields(const T& t) { 33 // Uncast const to get mutable-fields tuple, but reapply const to tuple args. 34 // We should do better than this when C++ gets a solution other than macros. 35 const auto mutFields = TiedFields(const_cast<T&>(t)); 36 return ToTupleOfConstRefs(mutFields); 37 } 38 39 /** 40 * Returns true if all bytes in T are accounted for via size of all tied fields. 41 * Returns false if there's bytes unaccounted for, which might indicate either 42 * unaccounted-for padding or missing fields. 43 * The goal is to check that TiedFields returns every field in T, and this 44 * returns false if it suspects there are bytes that are not accounted for by 45 * TiedFields. 46 * 47 * `constexpr` effectively cannot do math on pointers, so it's not possible to 48 * figure out via `constexpr` whether fields are consecutive or dense. 49 * However, we can at least compare `sizeof(T)` to the sum of `sizeof(Args...)` 50 * for `TiedFields(T) -> std::tuple<Args...>`. 51 * 52 * See TiedFieldsExamples. 53 */ 54 template <class T> 55 constexpr bool AreAllBytesTiedFields() { 56 using fieldsT = decltype(TiedFields(std::declval<T>())); 57 const auto fields_size_sum = SizeofTupleArgs<fieldsT>::value; 58 const auto t_size = sizeof(T); 59 return fields_size_sum == t_size; 60 } 61 62 // It's also possible to determine AreAllBytesRecursiveTiedFields: 63 // https://hackmd.io/@jgilbert/B16qa0Fa9 64 65 // - 66 67 template <class StructT, size_t FieldId, size_t PrevFieldBeginOffset, 68 class PrevFieldT, size_t PrevFieldEndOffset, class FieldT, 69 size_t FieldAlignment = alignof(FieldT)> 70 struct FieldDebugInfoT { 71 static constexpr bool IsTightlyPacked() { 72 return PrevFieldEndOffset % FieldAlignment == 0; 73 } 74 }; 75 76 template <class StructT, class TupleOfFields, size_t FieldId> 77 struct TightlyPackedFieldEndOffsetT { 78 template <size_t I> 79 using FieldTAt = std::remove_reference_t< 80 typename std::tuple_element<I, TupleOfFields>::type>; 81 82 static constexpr size_t Fn() { 83 constexpr auto num_fields = std::tuple_size_v<TupleOfFields>; 84 static_assert(FieldId < num_fields); 85 86 using PrevFieldT = FieldTAt<FieldId - 1>; 87 using FieldT = FieldTAt<FieldId>; 88 constexpr auto prev_field_end_offset = 89 TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, FieldId - 1>::Fn(); 90 constexpr auto prev_field_begin_offset = 91 prev_field_end_offset - sizeof(PrevFieldT); 92 93 using FieldDebugInfoT = 94 FieldDebugInfoT<StructT, FieldId, prev_field_begin_offset, PrevFieldT, 95 prev_field_end_offset, FieldT>; 96 static_assert(FieldDebugInfoT::IsTightlyPacked(), 97 "This field was not tightly packed. Is there padding between " 98 "it and its predecessor?"); 99 100 return prev_field_end_offset + sizeof(FieldT); 101 } 102 }; 103 104 template <class StructT, class TupleOfFields> 105 struct TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, 0> { 106 static constexpr size_t Fn() { 107 using FieldT = typename std::tuple_element<0, TupleOfFields>::type; 108 return sizeof(FieldT); 109 } 110 }; 111 template <class StructT, class TupleOfFields> 112 struct TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, size_t(-1)> { 113 static constexpr size_t Fn() { 114 // -1 means tuple_size_v<TupleOfFields> -> 0. 115 static_assert(sizeof(StructT) == 0); 116 return 0; 117 } 118 }; 119 120 template <class StructT> 121 constexpr bool AssertTiedFieldsAreExhaustive() { 122 static_assert(AreAllBytesTiedFields<StructT>()); 123 124 using TupleOfFields = decltype(TiedFields(std::declval<StructT&>())); 125 constexpr auto num_fields = std::tuple_size_v<TupleOfFields>; 126 constexpr auto end_offset_of_last_field = 127 TightlyPackedFieldEndOffsetT<StructT, TupleOfFields, 128 num_fields - 1>::Fn(); 129 static_assert( 130 end_offset_of_last_field == sizeof(StructT), 131 "Incorrect field list in MutTiedFields()? (or not tightly-packed?)"); 132 return true; // Support `static_assert(AssertTiedFieldsAreExhaustive())`. 133 } 134 135 // - 136 137 /** 138 * PaddingField<T,N=1> can be used to pad out a struct so that it's not 139 * implicitly padded by struct rules, but also can't be accidentally initialized 140 * via Aggregate Initialization. (TiedFields serialization checks rely on object 141 * fields leaving no implicit padding bytes, but explicit padding fields are 142 * fine) While you can use e.g. `uint8_t _padding[3];`, consider instead 143 * `PaddingField<uint8_t,3> _padding;` for clarity and to move the `3` nearer 144 * to the `uint8_t`. 145 */ 146 template <class T, size_t N = 1> 147 struct PaddingField { 148 static_assert(!std::is_array_v<T>, "Use PaddingField<T,N> not <T[N]>."); 149 150 std::array<T, N> ignored = {}; 151 152 PaddingField() {} 153 154 friend constexpr bool operator==(const PaddingField&, const PaddingField&) { 155 return true; 156 } 157 friend constexpr bool operator<(const PaddingField&, const PaddingField&) { 158 return false; 159 } 160 161 auto MutTiedFields() { return std::tie(ignored); } 162 }; 163 static_assert(sizeof(PaddingField<bool>) == 1); 164 static_assert(sizeof(PaddingField<bool, 2>) == 2); 165 static_assert(sizeof(PaddingField<int>) == 4); 166 167 // - 168 169 namespace TiedFieldsExamples { 170 171 struct Cat { 172 int i; 173 bool b; 174 175 constexpr auto MutTiedFields() { return std::tie(i, b); } 176 }; 177 static_assert(sizeof(Cat) == 8); 178 static_assert(!AreAllBytesTiedFields<Cat>()); 179 180 struct Dog { 181 bool b; 182 int i; 183 184 constexpr auto MutTiedFields() { return std::tie(i, b); } 185 }; 186 static_assert(sizeof(Dog) == 8); 187 static_assert(!AreAllBytesTiedFields<Dog>()); 188 189 struct Fish { 190 bool b; 191 bool padding[3]; 192 int i; 193 194 constexpr auto MutTiedFields() { return std::tie(i, b, padding); } 195 }; 196 static_assert(sizeof(Fish) == 8); 197 static_assert(AreAllBytesTiedFields<Fish>()); 198 199 struct Eel { // Like a Fish, but you can skip serializing the padding. 200 bool b; 201 PaddingField<bool, 3> padding; 202 int i; 203 204 constexpr auto MutTiedFields() { return std::tie(i, b, padding); } 205 }; 206 static_assert(sizeof(Eel) == 8); 207 static_assert(AreAllBytesTiedFields<Eel>()); 208 209 // - 210 211 // #define LETS_USE_BIT_FIELDS 212 #ifdef LETS_USE_BIT_FIELDS 213 # undef LETS_USE_BIT_FIELDS 214 215 struct Platypus { 216 short s : 1; 217 short s2 : 1; 218 int i; 219 220 constexpr auto MutTiedFields() { 221 return std::tie(s, s2, i); // Error: Can't take reference to bit-field. 222 } 223 }; 224 225 #endif 226 227 // - 228 229 struct FishTank { 230 Fish f; 231 int i2; 232 233 constexpr auto MutTiedFields() { return std::tie(f, i2); } 234 }; 235 static_assert(sizeof(FishTank) == 12); 236 static_assert(AreAllBytesTiedFields<FishTank>()); 237 238 struct CatCarrier { 239 Cat c; 240 int i2; 241 242 constexpr auto MutTiedFields() { return std::tie(c, i2); } 243 }; 244 static_assert(sizeof(CatCarrier) == 12); 245 static_assert(AreAllBytesTiedFields<CatCarrier>()); 246 static_assert( 247 !AreAllBytesTiedFields<decltype(CatCarrier::c)>()); // BUT BEWARE THIS! 248 // For example, if we had AreAllBytesRecursiveTiedFields: 249 // static_assert(!AreAllBytesRecursiveTiedFields<CatCarrier>()); 250 251 } // namespace TiedFieldsExamples 252 } // namespace mozilla 253 254 #endif // DOM_CANVAS_TIED_FIELDS_H