summaryrefslogtreecommitdiffstats
path: root/dom/canvas/TiedFields.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/TiedFields.h')
-rw-r--r--dom/canvas/TiedFields.h190
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