/* 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/. */

#include "mozilla/Assertions.h"
#include "mozilla/AtomicBitfields.h"

// This is a big macro mess, so let's summarize what's in here right up front:
//
// |TestDocumentationExample| is intended to be a copy-paste of the example
// in the macro's documentation, to make sure it's correct.
//
//
// |TestJammedWithFlags| tests using every bit of the type for bool flags.
// 64-bit isn't tested due to macro limitations.
//
//
// |TestLopsided| tests an instance with the following configuration:
//
// * a 1-bit boolean
// * an (N-1)-bit uintN_t
//
// It tests both orderings of these fields.
//
// Hopefully these are enough to cover all the nasty boundary conditions
// (that still compile).

// ==================== TestDocumentationExample ========================

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
  }
};

void TestDocumentationExample() {
  MyType val(3);

  if (!val.LoadIsDownloaded()) {
    val.StoreOtherData(2);
    val.StoreIsDownloaded(true);
  }
}

// ====================== TestJammedWithFlags =========================

#define TIMES_8(aFunc, aSeparator, aArgs) \
  MOZ_FOR_EACH_SEPARATED(aFunc, aSeparator, aArgs, (1, 2, 3, 4, 5, 6, 7, 8))
#define TIMES_16(aFunc, aSeparator, aArgs) \
  MOZ_FOR_EACH_SEPARATED(                  \
      aFunc, aSeparator, aArgs,            \
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16))
#define TIMES_32(aFunc, aSeparator, aArgs)                                    \
  MOZ_FOR_EACH_SEPARATED(                                                     \
      aFunc, aSeparator, aArgs,                                               \
      (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, \
       21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32))

#define CHECK_BOOL(aIndex)                             \
  MOZ_RELEASE_ASSERT(val.LoadFlag##aIndex() == false); \
  val.StoreFlag##aIndex(true);                         \
  MOZ_RELEASE_ASSERT(val.LoadFlag##aIndex() == true);  \
  val.StoreFlag##aIndex(false);                        \
  MOZ_RELEASE_ASSERT(val.LoadFlag##aIndex() == false);

#define GENERATE_TEST_JAMMED_WITH_FLAGS(aSize) \
  void TestJammedWithFlags##aSize() {          \
    JammedWithFlags##aSize val;                \
    TIMES_##aSize(CHECK_BOOL, (;), ());        \
  }

#define TEST_JAMMED_WITH_FLAGS(aSize) TestJammedWithFlags##aSize();

// ========================= TestLopsided ===========================

#define GENERATE_TEST_LOPSIDED_FUNC(aSide, aSize)              \
  void TestLopsided##aSide##aSize() {                          \
    Lopsided##aSide##aSize val;                                \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == false);     \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == 0);       \
    val.StoreHappyLittleBit(true);                             \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == true);      \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == 0);       \
    val.StoreLargeAndInCharge(1);                              \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == true);      \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == 1);       \
    val.StoreLargeAndInCharge(0);                              \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == true);      \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == 0);       \
    uint##aSize##_t size = aSize;                              \
    uint##aSize##_t int_max = (~(1ull << (size - 1))) - 1;     \
    val.StoreLargeAndInCharge(int_max);                        \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == true);      \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == int_max); \
    val.StoreHappyLittleBit(false);                            \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == false);     \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == int_max); \
    val.StoreLargeAndInCharge(int_max);                        \
    MOZ_RELEASE_ASSERT(val.LoadHappyLittleBit() == false);     \
    MOZ_RELEASE_ASSERT(val.LoadLargeAndInCharge() == int_max); \
  }

#define GENERATE_TEST_LOPSIDED(aSize)                                          \
  struct LopsidedA##aSize {                                                    \
    MOZ_ATOMIC_BITFIELDS(mAtomicFields, aSize,                                 \
                         ((bool, HappyLittleBit, 1),                           \
                          (uint##aSize##_t, LargeAndInCharge, ((aSize) - 1)))) \
  };                                                                           \
  struct LopsidedB##aSize {                                                    \
    MOZ_ATOMIC_BITFIELDS(mAtomicFields, aSize,                                 \
                         ((uint##aSize##_t, LargeAndInCharge, ((aSize) - 1)),  \
                          (bool, HappyLittleBit, 1)))                          \
  };                                                                           \
  GENERATE_TEST_LOPSIDED_FUNC(A, aSize);                                       \
  GENERATE_TEST_LOPSIDED_FUNC(B, aSize);

#define TEST_LOPSIDED(aSize) \
  TestLopsidedA##aSize();    \
  TestLopsidedB##aSize();

// ==================== generate and run the tests ======================

// There's an unknown bug in clang-cl-9 (used for win64-ccov) that makes
// generating these with the TIMES_N macro not work. So these are written out
// explicitly to unbork CI.
struct JammedWithFlags8 {
  MOZ_ATOMIC_BITFIELDS(mAtomicFields, 8,
                       ((bool, Flag1, 1), (bool, Flag2, 1), (bool, Flag3, 1),
                        (bool, Flag4, 1), (bool, Flag5, 1), (bool, Flag6, 1),
                        (bool, Flag7, 1), (bool, Flag8, 1)))
};

struct JammedWithFlags16 {
  MOZ_ATOMIC_BITFIELDS(mAtomicFields, 16,
                       ((bool, Flag1, 1), (bool, Flag2, 1), (bool, Flag3, 1),
                        (bool, Flag4, 1), (bool, Flag5, 1), (bool, Flag6, 1),
                        (bool, Flag7, 1), (bool, Flag8, 1), (bool, Flag9, 1),
                        (bool, Flag10, 1), (bool, Flag11, 1), (bool, Flag12, 1),
                        (bool, Flag13, 1), (bool, Flag14, 1), (bool, Flag15, 1),
                        (bool, Flag16, 1)))
};

struct JammedWithFlags32 {
  MOZ_ATOMIC_BITFIELDS(mAtomicFields, 32,
                       ((bool, Flag1, 1), (bool, Flag2, 1), (bool, Flag3, 1),
                        (bool, Flag4, 1), (bool, Flag5, 1), (bool, Flag6, 1),
                        (bool, Flag7, 1), (bool, Flag8, 1), (bool, Flag9, 1),
                        (bool, Flag10, 1), (bool, Flag11, 1), (bool, Flag12, 1),
                        (bool, Flag13, 1), (bool, Flag14, 1), (bool, Flag15, 1),
                        (bool, Flag16, 1), (bool, Flag17, 1), (bool, Flag18, 1),
                        (bool, Flag19, 1), (bool, Flag20, 1), (bool, Flag21, 1),
                        (bool, Flag22, 1), (bool, Flag23, 1), (bool, Flag24, 1),
                        (bool, Flag25, 1), (bool, Flag26, 1), (bool, Flag27, 1),
                        (bool, Flag28, 1), (bool, Flag29, 1), (bool, Flag30, 1),
                        (bool, Flag31, 1), (bool, Flag32, 1)))
};

GENERATE_TEST_JAMMED_WITH_FLAGS(8)
GENERATE_TEST_JAMMED_WITH_FLAGS(16)
GENERATE_TEST_JAMMED_WITH_FLAGS(32)
// MOZ_FOR_EACH_64 doesn't exist :(

GENERATE_TEST_LOPSIDED(8)
GENERATE_TEST_LOPSIDED(16)
GENERATE_TEST_LOPSIDED(32)
GENERATE_TEST_LOPSIDED(64)

int main() {
  TestDocumentationExample();

  TEST_JAMMED_WITH_FLAGS(8);
  TEST_JAMMED_WITH_FLAGS(16);
  TEST_JAMMED_WITH_FLAGS(32);

  TEST_LOPSIDED(8);
  TEST_LOPSIDED(16);
  TEST_LOPSIDED(32);
  TEST_LOPSIDED(64);
  return 0;
}
