summaryrefslogtreecommitdiffstats
path: root/mfbt/AtomicBitfields.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /mfbt/AtomicBitfields.h
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mfbt/AtomicBitfields.h')
-rw-r--r--mfbt/AtomicBitfields.h468
1 files changed, 468 insertions, 0 deletions
diff --git a/mfbt/AtomicBitfields.h b/mfbt/AtomicBitfields.h
new file mode 100644
index 0000000000..c61dc4df46
--- /dev/null
+++ b/mfbt/AtomicBitfields.h
@@ -0,0 +1,468 @@
+/* -*- 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 mozilla_AtomicBitfields_h
+#define mozilla_AtomicBitfields_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/MacroArgs.h"
+#include "mozilla/MacroForEach.h"
+
+#include <limits>
+#include <stdint.h>
+#include <type_traits>
+
+#ifdef __wasi__
+# include "mozilla/WasiAtomic.h"
+#else
+# include <atomic>
+#endif // __wasi__
+
+namespace mozilla {
+
+// Creates a series of atomic bitfields.
+//
+// |aBitfields| is the name of the underlying storage for the bitfields.
+// |aBitFieldsSize| is the size of the underlying storage (8, 16, 32, or 64).
+//
+// Bitfields are specified as a triplet of (type, name, size), which mirrors
+// the way you declare native C++ bitfields (bool mMyField1: 1). Trailing
+// commas are not supported in the list of bitfields.
+//
+// Signed integer types are not supported by this Macro to avoid dealing with
+// packing/unpacking the sign bit and C++'s general messiness around signed
+// integer representations not being fully defined.
+//
+// You cannot request a single field that's the
+// size of the the entire bitfield storage. Just use a normal atomic integer!
+//
+//
+// ========================== SEMANTICS AND SAFETY ============================
+//
+// All fields are default-initialized to 0.
+//
+// In debug builds, storing a value to a bitfield that's larger than its bits
+// can fit will trigger an assertion. In release builds, the value will just be
+// masked off.
+//
+// If you request anything unsupported by this macro it should result in
+// a compile-time error (either a static assert or just weird macro errors).
+// For instance, this macro will statically prevent using more bits than
+// |aBitFieldsSize|, so specifying the size is just to prevent accidentally
+// making the storage bigger.
+//
+// Each field will get a Load$NAME and Store$Name method which will atomically
+// load and store the requested value with a Sequentially Consistent memory
+// order (to be on the safe side). Storing a field requires a compare-exchange,
+// so a thread may get stalled if there's a lot of contention on the bitfields.
+//
+//
+// ============================== MOTIVATION ==================================
+//
+// You might be wondering: why would I need atomic bitfields? Well as it turns
+// out, bitfields and concurrency mess a lot of people up!
+//
+// CPUs don't have operations to write to a handful of bits -- they generally
+// only have the precision of a byte. So when you use C++'s native bitfields,
+// the compiler generates code to mask and shift the values in for you. This
+// means writing to a single field will actually overwrite all the other
+// bitfields that are packed in with it!
+//
+// In single-threaded code this is fine; the old values are loaded and written
+// back by the compiler's generated code. But in concurrent code, it means
+// that accessing two different fields can be an unexpected Data Race (which is
+// Undefined Behavior!).
+//
+// By using MOZ_ATOMIC_BITFIELDS, you protect yourself from these Data Races,
+// and don't have to worry about writes getting lost.
+//
+//
+// ================================ EXAMPLE ===================================
+//
+// #include "mozilla/AtomicBitfields.h"
+// #include <stdint.h>
+//
+//
+// struct MyType {
+// MOZ_ATOMIC_BITFIELDS(mAtomicFields, 8, (
+// (bool, IsDownloaded, 1),
+// (uint32_t, SomeData, 2),
+// (uint8_t, OtherData, 5)
+// ))
+//
+// int32_t aNormalInteger;
+//
+// explicit MyType(uint32_t aSomeData): aNormalInteger(7) {
+// StoreSomeData(aSomeData);
+// // Other bitfields were already default initialized to 0/false
+// }
+// };
+//
+//
+// int main() {
+// MyType val(3);
+//
+// if (!val.LoadIsDownloaded()) {
+// val.StoreOtherData(2);
+// val.StoreIsDownloaded(true);
+// }
+// }
+//
+//
+// ============================== GENERATED ===================================
+//
+// This macro is a real mess to read because, well, it's a macro. So for the
+// sake of anyone who has to review or modify its internals, here's a rough
+// sketch of what the above example would expand to:
+//
+// struct MyType {
+// // The actual storage of the bitfields, initialized to 0.
+// std::atomic_uint8_t mAtomicFields{0};
+//
+// // How many bits were actually used (in this case, all of them).
+// static const size_t mAtomicFields_USED_BITS = 8;
+//
+// // The offset values for each field.
+// static const size_t mAtomicFieldsIsDownloaded = 0;
+// static const size_t mAtomicFieldsSomeData = 1;
+// static const size_t mAtomicFieldsOtherData = 3;
+//
+// // Quick safety guard to prevent capacity overflow.
+// static_assert(mAtomicFields_USED_BITS <= 8);
+//
+// // Asserts that fields are reasonable.
+// static_assert(8>1, "mAtomicFields: MOZ_ATOMIC_BITFIELDS field too big");
+// static_assert(std::is_unsigned<bool>(), "mAtomicFields:
+// MOZ_ATOMIC_BITFIELDS doesn't support signed payloads");
+// // ...and so on
+//
+// // Load/Store methods for all the fields.
+//
+// bool LoadIsDownloaded() { ... }
+// void StoreIsDownloaded(bool aValue) { ... }
+//
+// uint32_t LoadSomeData() { ... }
+// void StoreSomeData(uint32_t aValue) { ... }
+//
+// uint8_t LoadOtherData() { ... }
+// void StoreOtherData(uint8_t aValue) { ... }
+//
+//
+// // Remainder of the struct body continues normally.
+// int32_t aNormalInteger;
+// explicit MyType(uint32_t aSomeData): aNormalInteger(7) {
+// StoreSomeData(aSomeData);
+// // Other bitfields were already default initialized to 0/false.
+// }
+// }
+//
+// Also if you're wondering why there's so many MOZ_CONCAT's -- it's because
+// the preprocessor sometimes gets confused if we use ## on certain arguments.
+// MOZ_CONCAT reliably kept the preprocessor happy, sorry it's so ugly!
+//
+//
+// ==================== FIXMES / FUTURE WORK ==================================
+//
+// * It would be nice if LoadField could be IsField for booleans.
+//
+// * For the case of setting something to all 1's or 0's, we can use
+// |fetch_or| or |fetch_and| instead of |compare_exchange_weak|. Is this
+// worth providing? (Possibly for 1-bit boolean fields?)
+//
+// * Try harder to hide the atomic/enum/array internals from
+// the outer struct?
+//
+#define MOZ_ATOMIC_BITFIELDS(aBitfields, aBitfieldsSize, aFields) \
+ std::atomic_uint##aBitfieldsSize##_t aBitfields{0}; \
+ \
+ static const size_t MOZ_CONCAT(aBitfields, _USED_BITS) = \
+ MOZ_FOR_EACH_SEPARATED(MOZ_ATOMIC_BITFIELDS_FIELD_SIZE, (+), (), \
+ aFields); \
+ \
+ MOZ_ROLL_EACH(MOZ_ATOMIC_BITFIELDS_OFFSET_HELPER1, (aBitfields, ), aFields) \
+ \
+ static_assert(MOZ_CONCAT(aBitfields, _USED_BITS) <= aBitfieldsSize, \
+ #aBitfields ": Maximum bits (" #aBitfieldsSize \
+ ") exceeded for MOZ_ATOMIC_BITFIELDS instance"); \
+ \
+ MOZ_FOR_EACH(MOZ_ATOMIC_BITFIELDS_FIELD_HELPER, \
+ (aBitfields, aBitfieldsSize, ), aFields)
+
+// Just a helper to unpack the head of the list.
+#define MOZ_ATOMIC_BITFIELDS_OFFSET_HELPER1(aBitfields, aFields) \
+ MOZ_ATOMIC_BITFIELDS_OFFSET_HELPER2(aBitfields, MOZ_ARG_1 aFields, aFields);
+
+// Just a helper to unpack the name and call the real function.
+#define MOZ_ATOMIC_BITFIELDS_OFFSET_HELPER2(aBitfields, aField, aFields) \
+ MOZ_ATOMIC_BITFIELDS_OFFSET(aBitfields, MOZ_ARG_2 aField, aFields)
+
+// To compute the offset of a field, why sum up all the offsets after it
+// (inclusive) and subtract that from the total sum itself. We do this to swap
+// the rolling sum that |MOZ_ROLL_EACH| gets us from descending to ascending.
+#define MOZ_ATOMIC_BITFIELDS_OFFSET(aBitfields, aFieldName, aFields) \
+ static const size_t MOZ_CONCAT(aBitfields, aFieldName) = \
+ MOZ_CONCAT(aBitfields, _USED_BITS) - \
+ (MOZ_FOR_EACH_SEPARATED(MOZ_ATOMIC_BITFIELDS_FIELD_SIZE, (+), (), \
+ aFields));
+
+// Just a more clearly named way of unpacking the size.
+#define MOZ_ATOMIC_BITFIELDS_FIELD_SIZE(aArgs) MOZ_ARG_3 aArgs
+
+// Just a helper to unpack the tuple and call the real function.
+#define MOZ_ATOMIC_BITFIELDS_FIELD_HELPER(aBitfields, aBitfieldsSize, aArgs) \
+ MOZ_ATOMIC_BITFIELDS_FIELD(aBitfields, aBitfieldsSize, MOZ_ARG_1 aArgs, \
+ MOZ_ARG_2 aArgs, MOZ_ARG_3 aArgs)
+
+// We need to disable this with coverity because it doesn't like checking that
+// booleans are < 2 (because they always are).
+#ifdef __COVERITY__
+# define MOZ_ATOMIC_BITFIELDS_STORE_GUARD(aValue, aFieldSize)
+#else
+# define MOZ_ATOMIC_BITFIELDS_STORE_GUARD(aValue, aFieldSize) \
+ MOZ_ASSERT(((uint64_t)aValue) < (1ull << aFieldSize), \
+ "Stored value exceeded capacity of bitfield!")
+#endif
+
+// Generates the Load and Store methods for each field.
+//
+// Some comments here because inline macro comments are a pain in the neck:
+//
+// Most of the locals are forward declared to minimize messy macroified
+// type declaration. Also a lot of locals are used to try to make things
+// a little more clear, while also avoiding integer promotion issues.
+// This is why some locals are literally just copying a value we already have:
+// to force it to the right size.
+//
+// There's an annoying overflow case where a bitfields instance has a field
+// that is the same size as the bitfields. Rather than trying to handle that,
+// we just static_assert against it.
+//
+//
+// BITMATH EXPLAINED:
+//
+// For |Load$Name|:
+//
+// mask = ((1 << fieldSize) - 1) << offset
+//
+// If you subtract 1 from a value with 1 bit set you get all 1's below that bit.
+// This is perfect for ANDing out |fieldSize| bits. We shift by |offset| to get
+// it in the right place.
+//
+// value = (aBitfields.load() & mask) >> offset
+//
+// This sets every bit we're not interested in to 0. Shifting the result by
+// |offset| converts the value back to its native format, ready to be cast
+// up to an integer type.
+//
+//
+// For |Store$Name|:
+//
+// packedValue = (resizedValue << offset) & mask
+//
+// This converts a native value to the packed format. If the value is in bounds,
+// the AND will do nothing. If it's out of bounds (not checked in release),
+// then it will cause the value to wrap around by modulo 2^aFieldSize, just like
+// a normal uint.
+//
+// clearedValue = oldValue & ~mask;
+//
+// This clears the bits where our field is stored on our bitfield storage by
+// ANDing it with an inverted (NOTed) mask.
+//
+// newValue = clearedValue | packedValue;
+//
+// Once we have |packedValue| and |clearedValue| they just need to be ORed
+// together to merge the new field value with the old values of all the other
+// fields.
+//
+// This last step is done in a while loop because someone else can modify
+// the bits before we have a chance to. If we didn't guard against this,
+// our write would undo the write the other thread did. |compare_exchange_weak|
+// is specifically designed to handle this. We give it what we expect the
+// current value to be, and what we want it to be. If someone else modifies
+// the bitfields before us, then we will reload the value and try again.
+//
+// Note that |compare_exchange_weak| writes back the actual value to the
+// "expected" argument (it's passed by-reference), so we don't need to do
+// another load in the body of the loop when we fail to write our result.
+#define MOZ_ATOMIC_BITFIELDS_FIELD(aBitfields, aBitfieldsSize, aFieldType, \
+ aFieldName, aFieldSize) \
+ static_assert(aBitfieldsSize > aFieldSize, \
+ #aBitfields ": MOZ_ATOMIC_BITFIELDS field too big"); \
+ static_assert(std::is_unsigned<aFieldType>(), #aBitfields \
+ ": MOZ_ATOMIC_BITFIELDS doesn't support signed payloads"); \
+ \
+ aFieldType MOZ_CONCAT(Load, aFieldName)() const { \
+ uint##aBitfieldsSize##_t fieldSize, mask, masked, value; \
+ size_t offset = MOZ_CONCAT(aBitfields, aFieldName); \
+ fieldSize = aFieldSize; \
+ mask = ((1ull << fieldSize) - 1ull) << offset; \
+ masked = aBitfields.load() & mask; \
+ value = (masked >> offset); \
+ return value; \
+ } \
+ \
+ void MOZ_CONCAT(Store, aFieldName)(aFieldType aValue) { \
+ MOZ_ATOMIC_BITFIELDS_STORE_GUARD(aValue, aFieldSize); \
+ uint##aBitfieldsSize##_t fieldSize, mask, resizedValue, packedValue, \
+ oldValue, clearedValue, newValue; \
+ size_t offset = MOZ_CONCAT(aBitfields, aFieldName); \
+ fieldSize = aFieldSize; \
+ mask = ((1ull << fieldSize) - 1ull) << offset; \
+ resizedValue = aValue; \
+ packedValue = (resizedValue << offset) & mask; \
+ oldValue = aBitfields.load(); \
+ do { \
+ clearedValue = oldValue & ~mask; \
+ newValue = clearedValue | packedValue; \
+ } while (!aBitfields.compare_exchange_weak(oldValue, newValue)); \
+ }
+
+// OK SO THIS IS A GROSS HACK. GCC 10.2 (and below) has a bug[1] where it
+// doesn't allow a static array to reference itself in its initializer, so we
+// need to create a hacky way to produce a rolling sum of all the offsets.
+//
+// To do this, we make a tweaked version of |MOZ_FOR_EACH| which instead of
+// passing just one argument to |aMacro| it passes the remaining values of
+// |aArgs|.
+//
+// This allows us to expand an input (a, b, c, d) quadratically to:
+//
+// int sum1 = a + b + c + d;
+// int sum2 = b + c + d;
+// int sum3 = c + d;
+// int sum4 = d;
+//
+// So all of this is a copy-paste of |MOZ_FOR_EACH| except the definition
+// of |MOZ_FOR_EACH_HELPER| no longer extracts an argument with |MOZ_ARG_1|.
+// Also this is restricted to 32 arguments just to reduce footprint a little.
+//
+// If the GCC bug is ever fixed, then this hack can be removed, and we can
+// use the non-quadratic version that was originally written[2]. In case
+// that link dies, a brief summary of that implementation:
+//
+// * Associate each field with an index by creating an `enum class` with
+// entries for each field (an existing gecko patten).
+//
+// * Calculate offsets with a constexpr static array whose initializer
+// self-referentially adds the contents of the previous index to the
+// compute the current one.
+//
+// * Index into this array with the enum.
+//
+// [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97234
+// [2]: https://phabricator.services.mozilla.com/D91622?id=346499
+#define MOZ_ROLL_EACH_EXPAND_HELPER(...) __VA_ARGS__
+#define MOZ_ROLL_EACH_GLUE(a, b) a b
+#define MOZ_ROLL_EACH_SEPARATED(aMacro, aSeparator, aFixedArgs, aArgs) \
+ MOZ_ROLL_EACH_GLUE(MOZ_PASTE_PREFIX_AND_ARG_COUNT( \
+ MOZ_ROLL_EACH_, MOZ_ROLL_EACH_EXPAND_HELPER aArgs), \
+ (aMacro, aSeparator, aFixedArgs, aArgs))
+#define MOZ_ROLL_EACH(aMacro, aFixedArgs, aArgs) \
+ MOZ_ROLL_EACH_SEPARATED(aMacro, (), aFixedArgs, aArgs)
+
+#define MOZ_ROLL_EACH_HELPER_GLUE(a, b) a b
+#define MOZ_ROLL_EACH_HELPER(aMacro, aFixedArgs, aArgs) \
+ MOZ_ROLL_EACH_HELPER_GLUE(aMacro, \
+ (MOZ_ROLL_EACH_EXPAND_HELPER aFixedArgs aArgs))
+
+#define MOZ_ROLL_EACH_0(m, s, fa, a)
+#define MOZ_ROLL_EACH_1(m, s, fa, a) MOZ_ROLL_EACH_HELPER(m, fa, a)
+#define MOZ_ROLL_EACH_2(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_1(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_3(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_2(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_4(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_3(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_5(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_4(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_6(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_5(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_7(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_6(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_8(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_7(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_9(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_8(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_10(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_9(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_11(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_10(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_12(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_11(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_13(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_12(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_14(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_13(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_15(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_14(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_16(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_15(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_17(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_16(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_18(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_17(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_19(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_18(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_20(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_19(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_21(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_20(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_22(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_21(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_23(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_22(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_24(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_23(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_25(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_24(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_26(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_25(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_27(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_26(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_28(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_27(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_29(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_28(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_30(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_29(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_31(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_30(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_ROLL_EACH_32(m, s, fa, a) \
+ MOZ_ROLL_EACH_HELPER(m, fa, a) \
+ MOZ_ROLL_EACH_EXPAND_HELPER s MOZ_ROLL_EACH_31(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+} // namespace mozilla
+#endif /* mozilla_AtomicBitfields_h */