tsan_clock_test.cc   [plain text]


//===-- tsan_clock_test.cc ------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file is a part of ThreadSanitizer (TSan), a race detector.
//
//===----------------------------------------------------------------------===//
#include "tsan_clock.h"
#include "tsan_rtl.h"
#include "gtest/gtest.h"
#include <time.h>

namespace __tsan {

ClockCache cache;

TEST(Clock, VectorBasic) {
  ThreadClock clk(0);
  ASSERT_EQ(clk.size(), 1U);
  clk.tick();
  ASSERT_EQ(clk.size(), 1U);
  ASSERT_EQ(clk.get(0), 1U);
  clk.set(3, clk.get(3) + 1);
  ASSERT_EQ(clk.size(), 4U);
  ASSERT_EQ(clk.get(0), 1U);
  ASSERT_EQ(clk.get(1), 0U);
  ASSERT_EQ(clk.get(2), 0U);
  ASSERT_EQ(clk.get(3), 1U);
  clk.set(3, clk.get(3) + 1);
  ASSERT_EQ(clk.get(3), 2U);
}

TEST(Clock, ChunkedBasic) {
  ThreadClock vector(0);
  SyncClock chunked;
  ASSERT_EQ(vector.size(), 1U);
  ASSERT_EQ(chunked.size(), 0U);
  vector.acquire(&cache, &chunked);
  ASSERT_EQ(vector.size(), 1U);
  ASSERT_EQ(chunked.size(), 0U);
  vector.release(&cache, &chunked);
  ASSERT_EQ(vector.size(), 1U);
  ASSERT_EQ(chunked.size(), 1U);
  vector.acq_rel(&cache, &chunked);
  ASSERT_EQ(vector.size(), 1U);
  ASSERT_EQ(chunked.size(), 1U);
  chunked.Reset(&cache);
}

TEST(Clock, AcquireRelease) {
  ThreadClock vector1(100);
  vector1.tick();
  SyncClock chunked;
  vector1.release(&cache, &chunked);
  ASSERT_EQ(chunked.size(), 101U);
  ThreadClock vector2(0);
  vector2.acquire(&cache, &chunked);
  ASSERT_EQ(vector2.size(), 101U);
  ASSERT_EQ(vector2.get(0), 0U);
  ASSERT_EQ(vector2.get(1), 0U);
  ASSERT_EQ(vector2.get(99), 0U);
  ASSERT_EQ(vector2.get(100), 1U);
  chunked.Reset(&cache);
}

TEST(Clock, RepeatedAcquire) {
  ThreadClock thr1(1);
  thr1.tick();
  ThreadClock thr2(2);
  thr2.tick();

  SyncClock sync;
  thr1.ReleaseStore(&cache, &sync);

  thr2.acquire(&cache, &sync);
  thr2.acquire(&cache, &sync);

  sync.Reset(&cache);
}

TEST(Clock, ManyThreads) {
  SyncClock chunked;
  for (unsigned i = 0; i < 100; i++) {
    ThreadClock vector(0);
    vector.tick();
    vector.set(i, 1);
    vector.release(&cache, &chunked);
    ASSERT_EQ(i + 1, chunked.size());
    vector.acquire(&cache, &chunked);
    ASSERT_EQ(i + 1, vector.size());
  }

  for (unsigned i = 0; i < 100; i++)
    ASSERT_EQ(1U, chunked.get(i));

  ThreadClock vector(1);
  vector.acquire(&cache, &chunked);
  ASSERT_EQ(100U, vector.size());
  for (unsigned i = 0; i < 100; i++)
    ASSERT_EQ(1U, vector.get(i));

  chunked.Reset(&cache);
}

TEST(Clock, DifferentSizes) {
  {
    ThreadClock vector1(10);
    vector1.tick();
    ThreadClock vector2(20);
    vector2.tick();
    {
      SyncClock chunked;
      vector1.release(&cache, &chunked);
      ASSERT_EQ(chunked.size(), 11U);
      vector2.release(&cache, &chunked);
      ASSERT_EQ(chunked.size(), 21U);
      chunked.Reset(&cache);
    }
    {
      SyncClock chunked;
      vector2.release(&cache, &chunked);
      ASSERT_EQ(chunked.size(), 21U);
      vector1.release(&cache, &chunked);
      ASSERT_EQ(chunked.size(), 21U);
      chunked.Reset(&cache);
    }
    {
      SyncClock chunked;
      vector1.release(&cache, &chunked);
      vector2.acquire(&cache, &chunked);
      ASSERT_EQ(vector2.size(), 21U);
      chunked.Reset(&cache);
    }
    {
      SyncClock chunked;
      vector2.release(&cache, &chunked);
      vector1.acquire(&cache, &chunked);
      ASSERT_EQ(vector1.size(), 21U);
      chunked.Reset(&cache);
    }
  }
}

TEST(Clock, Growth) {
  {
    ThreadClock vector(10);
    vector.tick();
    vector.set(5, 42);
    SyncClock sync;
    vector.release(&cache, &sync);
    ASSERT_EQ(sync.size(), 11U);
    ASSERT_EQ(sync.get(0), 0ULL);
    ASSERT_EQ(sync.get(1), 0ULL);
    ASSERT_EQ(sync.get(5), 42ULL);
    ASSERT_EQ(sync.get(9), 0ULL);
    ASSERT_EQ(sync.get(10), 1ULL);
    sync.Reset(&cache);
  }
  {
    ThreadClock vector1(10);
    vector1.tick();
    ThreadClock vector2(20);
    vector2.tick();
    SyncClock sync;
    vector1.release(&cache, &sync);
    vector2.release(&cache, &sync);
    ASSERT_EQ(sync.size(), 21U);
    ASSERT_EQ(sync.get(0), 0ULL);
    ASSERT_EQ(sync.get(10), 1ULL);
    ASSERT_EQ(sync.get(19), 0ULL);
    ASSERT_EQ(sync.get(20), 1ULL);
    sync.Reset(&cache);
  }
  {
    ThreadClock vector(100);
    vector.tick();
    vector.set(5, 42);
    vector.set(90, 84);
    SyncClock sync;
    vector.release(&cache, &sync);
    ASSERT_EQ(sync.size(), 101U);
    ASSERT_EQ(sync.get(0), 0ULL);
    ASSERT_EQ(sync.get(1), 0ULL);
    ASSERT_EQ(sync.get(5), 42ULL);
    ASSERT_EQ(sync.get(60), 0ULL);
    ASSERT_EQ(sync.get(70), 0ULL);
    ASSERT_EQ(sync.get(90), 84ULL);
    ASSERT_EQ(sync.get(99), 0ULL);
    ASSERT_EQ(sync.get(100), 1ULL);
    sync.Reset(&cache);
  }
  {
    ThreadClock vector1(10);
    vector1.tick();
    ThreadClock vector2(100);
    vector2.tick();
    SyncClock sync;
    vector1.release(&cache, &sync);
    vector2.release(&cache, &sync);
    ASSERT_EQ(sync.size(), 101U);
    ASSERT_EQ(sync.get(0), 0ULL);
    ASSERT_EQ(sync.get(10), 1ULL);
    ASSERT_EQ(sync.get(99), 0ULL);
    ASSERT_EQ(sync.get(100), 1ULL);
    sync.Reset(&cache);
  }
}

const int kThreads = 4;
const int kClocks = 4;

// SimpleSyncClock and SimpleThreadClock implement the same thing as
// SyncClock and ThreadClock, but in a very simple way.
struct SimpleSyncClock {
  u64 clock[kThreads];
  uptr size;

  SimpleSyncClock() {
    Reset();
  }

  void Reset() {
    size = 0;
    for (uptr i = 0; i < kThreads; i++)
      clock[i] = 0;
  }

  bool verify(const SyncClock *other) const {
    for (uptr i = 0; i < min(size, other->size()); i++) {
      if (clock[i] != other->get(i))
        return false;
    }
    for (uptr i = min(size, other->size()); i < max(size, other->size()); i++) {
      if (i < size && clock[i] != 0)
        return false;
      if (i < other->size() && other->get(i) != 0)
        return false;
    }
    return true;
  }
};

struct SimpleThreadClock {
  u64 clock[kThreads];
  uptr size;
  unsigned tid;

  explicit SimpleThreadClock(unsigned tid) {
    this->tid = tid;
    size = tid + 1;
    for (uptr i = 0; i < kThreads; i++)
      clock[i] = 0;
  }

  void tick() {
    clock[tid]++;
  }

  void acquire(const SimpleSyncClock *src) {
    if (size < src->size)
      size = src->size;
    for (uptr i = 0; i < kThreads; i++)
      clock[i] = max(clock[i], src->clock[i]);
  }

  void release(SimpleSyncClock *dst) const {
    if (dst->size < size)
      dst->size = size;
    for (uptr i = 0; i < kThreads; i++)
      dst->clock[i] = max(dst->clock[i], clock[i]);
  }

  void acq_rel(SimpleSyncClock *dst) {
    acquire(dst);
    release(dst);
  }

  void ReleaseStore(SimpleSyncClock *dst) const {
    if (dst->size < size)
      dst->size = size;
    for (uptr i = 0; i < kThreads; i++)
      dst->clock[i] = clock[i];
  }

  bool verify(const ThreadClock *other) const {
    for (uptr i = 0; i < min(size, other->size()); i++) {
      if (clock[i] != other->get(i))
        return false;
    }
    for (uptr i = min(size, other->size()); i < max(size, other->size()); i++) {
      if (i < size && clock[i] != 0)
        return false;
      if (i < other->size() && other->get(i) != 0)
        return false;
    }
    return true;
  }
};

static bool ClockFuzzer(bool printing) {
  // Create kThreads thread clocks.
  SimpleThreadClock *thr0[kThreads];
  ThreadClock *thr1[kThreads];
  unsigned reused[kThreads];
  for (unsigned i = 0; i < kThreads; i++) {
    reused[i] = 0;
    thr0[i] = new SimpleThreadClock(i);
    thr1[i] = new ThreadClock(i, reused[i]);
  }

  // Create kClocks sync clocks.
  SimpleSyncClock *sync0[kClocks];
  SyncClock *sync1[kClocks];
  for (unsigned i = 0; i < kClocks; i++) {
    sync0[i] = new SimpleSyncClock();
    sync1[i] = new SyncClock();
  }

  // Do N random operations (acquire, release, etc) and compare results
  // for SimpleThread/SyncClock and real Thread/SyncClock.
  for (int i = 0; i < 10000; i++) {
    unsigned tid = rand() % kThreads;
    unsigned cid = rand() % kClocks;
    thr0[tid]->tick();
    thr1[tid]->tick();

    switch (rand() % 6) {
    case 0:
      if (printing)
        printf("acquire thr%d <- clk%d\n", tid, cid);
      thr0[tid]->acquire(sync0[cid]);
      thr1[tid]->acquire(&cache, sync1[cid]);
      break;
    case 1:
      if (printing)
        printf("release thr%d -> clk%d\n", tid, cid);
      thr0[tid]->release(sync0[cid]);
      thr1[tid]->release(&cache, sync1[cid]);
      break;
    case 2:
      if (printing)
        printf("acq_rel thr%d <> clk%d\n", tid, cid);
      thr0[tid]->acq_rel(sync0[cid]);
      thr1[tid]->acq_rel(&cache, sync1[cid]);
      break;
    case 3:
      if (printing)
        printf("rel_str thr%d >> clk%d\n", tid, cid);
      thr0[tid]->ReleaseStore(sync0[cid]);
      thr1[tid]->ReleaseStore(&cache, sync1[cid]);
      break;
    case 4:
      if (printing)
        printf("reset clk%d\n", cid);
      sync0[cid]->Reset();
      sync1[cid]->Reset(&cache);
      break;
    case 5:
      if (printing)
        printf("reset thr%d\n", tid);
      u64 epoch = thr0[tid]->clock[tid] + 1;
      reused[tid]++;
      delete thr0[tid];
      thr0[tid] = new SimpleThreadClock(tid);
      thr0[tid]->clock[tid] = epoch;
      delete thr1[tid];
      thr1[tid] = new ThreadClock(tid, reused[tid]);
      thr1[tid]->set(epoch);
      break;
    }

    if (printing) {
      for (unsigned i = 0; i < kThreads; i++) {
        printf("thr%d: ", i);
        thr1[i]->DebugDump(printf);
        printf("\n");
      }
      for (unsigned i = 0; i < kClocks; i++) {
        printf("clk%d: ", i);
        sync1[i]->DebugDump(printf);
        printf("\n");
      }

      printf("\n");
    }

    if (!thr0[tid]->verify(thr1[tid]) || !sync0[cid]->verify(sync1[cid])) {
      if (!printing)
        return false;
      printf("differs with model:\n");
      for (unsigned i = 0; i < kThreads; i++) {
        printf("thr%d: clock=[", i);
        for (uptr j = 0; j < thr0[i]->size; j++)
          printf("%s%llu", j == 0 ? "" : ",", thr0[i]->clock[j]);
        printf("]\n");
      }
      for (unsigned i = 0; i < kClocks; i++) {
        printf("clk%d: clock=[", i);
        for (uptr j = 0; j < sync0[i]->size; j++)
          printf("%s%llu", j == 0 ? "" : ",", sync0[i]->clock[j]);
        printf("]\n");
      }
      return false;
    }
  }

  for (unsigned i = 0; i < kClocks; i++) {
    sync1[i]->Reset(&cache);
  }
  return true;
}

TEST(Clock, Fuzzer) {
  timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  int seed = ts.tv_sec + ts.tv_nsec;
  printf("seed=%d\n", seed);
  srand(seed);
  if (!ClockFuzzer(false)) {
    // Redo the test with the same seed, but logging operations.
    srand(seed);
    ClockFuzzer(true);
    ASSERT_TRUE(false);
  }
}

}  // namespace __tsan