diff options
Diffstat (limited to 'dom/canvas/TiedFields.h')
-rw-r--r-- | dom/canvas/TiedFields.h | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/dom/canvas/TiedFields.h b/dom/canvas/TiedFields.h new file mode 100644 index 0000000000..4b06300568 --- /dev/null +++ b/dom/canvas/TiedFields.h @@ -0,0 +1,190 @@ +/* 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<Fields&...> + * TiedFields(const T&) -> std::tuple<const Fields&...> + * + * You can also overload TiedFields without adding T::MutTiedFields: + * template<> + * inline auto TiedFields<gfx::IntSize>(gfx::IntSize& a) { + * return std::tie(a.width, a.height); + * } + */ +template <class T> +constexpr auto TiedFields(T& t) { + const auto fields = t.MutTiedFields(); + return fields; +} +template <class T, class... Args, class Tup = std::tuple<Args&...>> +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&>(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<Args...>`. + * + * See TiedFieldsExamples. + */ +template <class T> +constexpr bool AreAllBytesTiedFields() { + using fieldsT = decltype(TiedFields(std::declval<T>())); + const auto fields_size_sum = SizeofTupleArgs<fieldsT>::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 + +// - + +/** + * Padding<T> can be used to pad out a struct so that it's not implicitly + * padded by struct rules. + * You can also just add your padding to TiedFields, but by explicitly typing + * padding like this, serialization can make a choice whether to copy Padding, + * or instead to omit the copy. + * + * Omitting the copy isn't always faster. + * struct Entry { + * uint16_t key; + * Padding<uint16_t> padding; + * uint32_t val; + * auto MutTiedFields() { return std::tie(key, padding, val); } + * }; + * If you serialize Padding, the serialized size is 8, and the compiler will + * optimize serialization to a single 8-byte memcpy. + * If your serialization omits Padding, the serialized size of Entry shrinks + * by 25%. If you have a big list of Entrys, maybe this is a big savings! + * However, by optimizing for size here you sacrifice speed, because this splits + * the single memcpy into two: a 2-byte memcpy and a 4-byte memcpy. + * + * Explicitly marking padding gives callers the option of choosing. + */ +template <class T> +struct Padding { + T ignored; + + friend constexpr bool operator==(const Padding&, const Padding&) { + return true; + } + friend constexpr bool operator<(const Padding&, const Padding&) { + return false; + } +}; +static_assert(sizeof(Padding<bool>) == 1); +static_assert(sizeof(Padding<bool[2]>) == 2); +static_assert(sizeof(Padding<int>) == 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<Cat>()); + +struct Dog { + bool b; + int i; + + constexpr auto MutTiedFields() { return std::tie(i, b); } +}; +static_assert(sizeof(Dog) == 8); +static_assert(!AreAllBytesTiedFields<Dog>()); + +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<Fish>()); + +struct Eel { // Like a Fish, but you can skip serializing the padding. + bool b; + Padding<bool> padding[3]; + int i; + + constexpr auto MutTiedFields() { return std::tie(i, b, padding); } +}; +static_assert(sizeof(Eel) == 8); +static_assert(AreAllBytesTiedFields<Eel>()); + +// - + +// #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<FishTank>()); + +struct CatCarrier { + Cat c; + int i2; + + constexpr auto MutTiedFields() { return std::tie(c, i2); } +}; +static_assert(sizeof(CatCarrier) == 12); +static_assert(AreAllBytesTiedFields<CatCarrier>()); +static_assert( + !AreAllBytesTiedFields<decltype(CatCarrier::c)>()); // BUT BEWARE THIS! +// For example, if we had AreAllBytesRecursiveTiedFields: +// static_assert(!AreAllBytesRecursiveTiedFields<CatCarrier>()); + +} // namespace TiedFieldsExamples +} // namespace mozilla + +#endif // DOM_CANVAS_TIED_FIELDS_H |