/* ThreadSanitizer
 * Copyright (c) 2011, Google Inc. All rights reserved.
 * Author: Dmitry Vyukov (dvyukov)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "ts_util.h"
#include "ts_atomic_int.h"


char const* tsan_atomic_to_str(tsan_memory_order mo) {
  switch (mo) {
    case tsan_memory_order_invalid: return "invalid";
    case tsan_memory_order_natomic: return "natomic";
    case tsan_memory_order_relaxed: return "relaxed";
    case tsan_memory_order_consume: return "consume";
    case tsan_memory_order_acquire: return "acquire";
    case tsan_memory_order_release: return "release";
    case tsan_memory_order_acq_rel: return "acq_rel";
    case tsan_memory_order_seq_cst: return "seq_cst";
    default: return "-------";
  }
}


char const* tsan_atomic_to_str(tsan_atomic_op op) {
  switch (op) {
    case tsan_atomic_op_invalid: return "invalid";
    case tsan_atomic_op_fence: return "fence";
    case tsan_atomic_op_load: return "load";
    case tsan_atomic_op_store: return "store";
    case tsan_atomic_op_exchange: return "exchange";
    case tsan_atomic_op_fetch_add: return "fetch_add";
    case tsan_atomic_op_fetch_sub: return "fetch_sub";
    case tsan_atomic_op_fetch_and: return "fetch_and";
    case tsan_atomic_op_fetch_xor: return "fetch_xor";
    case tsan_atomic_op_fetch_or: return "fetch_or";
    case tsan_atomic_op_compare_exchange_weak: return "compare_exchange_weak";
    case tsan_atomic_op_compare_exchange_strong:
      return "compare_exchange_strong";
    default: return "---";
  }
}


bool tsan_atomic_is_acquire(tsan_memory_order mo) {
  return !!(mo & (tsan_memory_order_consume
      | tsan_memory_order_acquire
      | tsan_memory_order_acq_rel
      | tsan_memory_order_seq_cst));
}


bool tsan_atomic_is_release(tsan_memory_order mo) {
  return !!(mo & (tsan_memory_order_release
      | tsan_memory_order_acq_rel
      | tsan_memory_order_seq_cst));
}


bool tsan_atomic_is_rmw(tsan_atomic_op op) {
  return !!(op & (tsan_atomic_op_exchange
      | tsan_atomic_op_fetch_add
      | tsan_atomic_op_fetch_sub
      | tsan_atomic_op_fetch_and
      | tsan_atomic_op_fetch_xor
      | tsan_atomic_op_fetch_or
      | tsan_atomic_op_compare_exchange_weak
      | tsan_atomic_op_compare_exchange_strong));
}


void tsan_atomic_verify(tsan_atomic_op op,
                        tsan_memory_order mo,
                        tsan_memory_order fail_mo,
                        size_t size,
                        void volatile* a) {
  CHECK(size == 1 || size == 2 || size == 4 || size == 8);
  CHECK((((uintptr_t)a) % size) == 0);

  if (op == tsan_atomic_op_load) {
    CHECK(mo & (tsan_memory_order_natomic
        | tsan_memory_order_relaxed
        | tsan_memory_order_consume
        | tsan_memory_order_acquire
        | tsan_memory_order_seq_cst));
  } else if (op == tsan_atomic_op_store) {
    CHECK(mo & (tsan_memory_order_natomic
        | tsan_memory_order_relaxed
        | tsan_memory_order_release
        | tsan_memory_order_seq_cst));
  } else if (op == tsan_atomic_op_fence) {
    CHECK(mo & (tsan_memory_order_consume
        | tsan_memory_order_acquire
        | tsan_memory_order_release
        | tsan_memory_order_acq_rel
        | tsan_memory_order_seq_cst));
  } else if (op & (tsan_atomic_op_exchange
        | tsan_atomic_op_fetch_add
        | tsan_atomic_op_fetch_sub
        | tsan_atomic_op_fetch_and
        | tsan_atomic_op_fetch_xor
        | tsan_atomic_op_fetch_or
        | tsan_atomic_op_compare_exchange_weak
        | tsan_atomic_op_compare_exchange_strong)) {
    CHECK(mo & (tsan_memory_order_relaxed
        | tsan_memory_order_consume
        | tsan_memory_order_acquire
        | tsan_memory_order_release
        | tsan_memory_order_acq_rel
        | tsan_memory_order_seq_cst));
  } else {
    CHECK("unknown tsan_atomic_op" == 0);
  }
}


#if defined(__i386__)
# define __x86__
#elif defined(__x86_64__)
# define __x86__
#endif

#if defined(__GNUC__) && defined(__x86_64__)
uint64_t tsan_atomic_do_op(tsan_atomic_op op,
                           tsan_memory_order mo,
                           tsan_memory_order fail_mo,
                           size_t size,
                           void volatile* a,
                           uint64_t v,
                           uint64_t cmp,
                           uint64_t* newv,
                           uint64_t* prev) {
  *newv = v;
  if (op != tsan_atomic_op_fence) {
    if (size == 1) {
      *prev = *(uint8_t volatile*)a;
    } else if (size == 2) {
      *prev =  *(uint16_t volatile*)a;
    } else if (size == 4) {
      *prev =  *(uint32_t volatile*)a;
    } else if (size == 8) {
      *prev =  *(uint64_t volatile*)a;
    }
  }

  if (op == tsan_atomic_op_load) {
    return *prev;

  } else if (op == tsan_atomic_op_store) {
    if (mo == tsan_memory_order_seq_cst) {
      if (size == 1) {
        uint8_t vv = (uint8_t)v;
        __asm__ __volatile__ ("xchgb %1, %0"
            : "=r" (vv) : "m" (*(uint8_t volatile*)a), "0" (vv));
        *prev = vv;
      } else if (size == 2) {
        uint16_t vv = (uint16_t)v;
        __asm__ __volatile__ ("xchgw %1, %0"
            : "=r" (vv) : "m" (*(uint16_t volatile*)a), "0" (vv));
        *prev = vv;
      } else if (size == 4) {
        uint32_t vv = (uint32_t)v;
        __asm__ __volatile__ ("xchgl %1, %0"
            : "=r" (vv) : "m" (*(uint32_t volatile*)a), "0" (vv));
        *prev = vv;
      } else if (size == 8) {
#ifdef __x86_64__
        uint64_t vv = (uint64_t)v;
        __asm__ __volatile__ ("xchgq %1, %0"
            : "=r" (vv) : "m" (*(uint64_t volatile*)a), "0" (vv));
        *prev = vv;
#else
#error "IMPLEMENT ME, PLZ"
        //uint64_t cmp = *a;
        //!!!while (!tsan_atomic64_compare_exchange_strong(a, &cmp, v, mo, mo))
        //!!! {}
#endif
      }
    } else {
      if (size == 1) {
        *(uint8_t volatile*)a = v;
      } else if (size == 2) {
        *(uint16_t volatile*)a = v;
      } else if (size == 4) {
        *(uint32_t volatile*)a = v;
      } else if (size == 8) {
        *(uint64_t volatile*)a = v;
      }
    }
    return 0;

  } else if (op == tsan_atomic_op_exchange) {
    if (size == 1) {
      uint8_t vv = (uint8_t)v;
      __asm__ __volatile__ ("xchgb %1, %0"
          : "=r" (vv) : "m" (*(uint8_t volatile*)a), "0" (vv));
      *prev = vv;
      return vv;
    } else if (size == 2) {
      uint16_t vv = (uint16_t)v;
      __asm__ __volatile__ ("xchgw %1, %0"
          : "=r" (vv) : "m" (*(uint16_t volatile*)a), "0" (vv));
      *prev = vv;
      return vv;
    } else if (size == 4) {
      uint32_t vv = (uint32_t)v;
      __asm__ __volatile__ ("xchgl %1, %0"
          : "=r" (vv) : "m" (*(uint32_t volatile*)a), "0" (vv));
      *prev = vv;
      return vv;
    } else if (size == 8) {
# ifdef __x86_64__
      uint64_t vv = (uint64_t)v;
      __asm__ __volatile__ ("xchgq %1, %0"
          : "=r" (vv) : "m" (*(uint64_t volatile*)a), "0" (vv));
      *prev = vv;
      return vv;
#else
#error "IMPLEMENT ME, PLZ"
      //uint64_t cmp = *a;
      //while (!tsan_atomic64_compare_exchange_strong(a, &cmp, v, mo, mo))
      // {}
      //return cmp;
#endif
    }

  } else if (op == tsan_atomic_op_fetch_add) {
    if (size == 1) {
      uint8_t prevv = __sync_fetch_and_add((uint8_t volatile*)a, (uint8_t)v);
      *prev = prevv;
      *newv = prevv + (uint8_t)v;
      return prevv;
    } else if (size == 2) {
      uint16_t prevv = __sync_fetch_and_add(
          (uint16_t volatile*)a, (uint16_t)v);
      *prev = prevv;
      *newv = prevv + (uint16_t)v;
      return prevv;
    } else if (size == 4) {
      uint32_t prevv = __sync_fetch_and_add(
          (uint32_t volatile*)a, (uint32_t)v);
      *prev = prevv;
      *newv = prevv + (uint32_t)v;
      return prevv;
    } else if (size == 8) {
      uint64_t prevv = __sync_fetch_and_add(
          (uint64_t volatile*)a, (uint64_t)v);
      *prev = prevv;
      *newv = prevv + v;
      return prevv;
    }

  } else if (op == tsan_atomic_op_fetch_sub) {
    if (size == 1) {
      uint8_t prevv = __sync_fetch_and_sub(
          (uint8_t volatile*)a, (uint8_t)v);
      *prev = prevv;
      *newv = prevv - (uint8_t)v;
      return prevv;
    } else if (size == 2) {
      uint16_t prevv = __sync_fetch_and_sub(
          (uint16_t volatile*)a, (uint16_t)v);
      *prev = prevv;
      *newv = prevv - (uint16_t)v;
      return prevv;
    } else if (size == 4) {
      uint32_t prevv = __sync_fetch_and_sub(
          (uint32_t volatile*)a, (uint32_t)v);
      *prev = prevv;
      *newv = prevv - (uint32_t)v;
      return prevv;
    } else if (size == 8) {
      uint64_t prevv = __sync_fetch_and_sub(
          (uint64_t volatile*)a, (uint64_t)v);
      *prev = prevv;
      *newv = prevv - v;
      return prevv;
    }

  } else if (op == tsan_atomic_op_fetch_and) {
    if (size == 1) {
      uint8_t prevv = __sync_fetch_and_and(
          (uint8_t volatile*)a, (uint8_t)v);
      *prev = prevv;
      *newv = prevv & (uint8_t)v;
      return prevv;
    } else if (size == 2) {
      uint16_t prevv = __sync_fetch_and_and(
          (uint16_t volatile*)a, (uint16_t)v);
      *prev = prevv;
      *newv = prevv & (uint16_t)v;
      return prevv;
    } else if (size == 4) {
      uint32_t prevv = __sync_fetch_and_and(
          (uint32_t volatile*)a, (uint32_t)v);
      *prev = prevv;
      *newv = prevv & (uint32_t)v;
      return prevv;
    } else if (size == 8) {
      uint64_t prevv = __sync_fetch_and_and(
          (uint64_t volatile*)a, (uint64_t)v);
      *prev = prevv;
      *newv = prevv & v;
      return prevv;
    }

  } else if (op == tsan_atomic_op_fetch_xor) {
    if (size == 1) {
      uint8_t prevv = __sync_fetch_and_xor(
          (uint8_t volatile*)a, (uint8_t)v);
      *prev = prevv;
      *newv = prevv ^ (uint8_t)v;
      return prevv;
    } else if (size == 2) {
      uint16_t prevv = __sync_fetch_and_xor(
          (uint16_t volatile*)a, (uint16_t)v);
      *prev = prevv;
      *newv = prevv ^ (uint16_t)v;
      return prevv;
    } else if (size == 4) {
      uint32_t prevv = __sync_fetch_and_xor(
          (uint32_t volatile*)a, (uint32_t)v);
      *prev = prevv;
      *newv = prevv ^ (uint32_t)v;
      return prevv;
    } else if (size == 8) {
      uint64_t prevv = __sync_fetch_and_xor(
          (uint64_t volatile*)a, (uint64_t)v);
      *prev = prevv;
      *newv = prevv ^ v;
      return prevv;
    }

  } else if (op == tsan_atomic_op_fetch_or) {
    if (size == 1) {
      uint8_t prevv = __sync_fetch_and_or(
          (uint8_t volatile*)a, (uint8_t)v);
      *prev = prevv;
      *newv = prevv | (uint8_t)v;
      return prevv;
    } else if (size == 2) {
      uint16_t prevv = __sync_fetch_and_or(
          (uint16_t volatile*)a, (uint16_t)v);
      *prev = prevv;
      *newv = prevv | (uint16_t)v;
      return prevv;
    } else if (size == 4) {
      uint32_t prevv = __sync_fetch_and_or(
          (uint32_t volatile*)a, (uint32_t)v);
      *prev = prevv;
      *newv = prevv | (uint32_t)v;
      return prevv;
    } else if (size == 8) {
      uint64_t prevv = __sync_fetch_and_or(
          (uint64_t volatile*)a, (uint64_t)v);
      *prev = prevv;
      *newv = prevv | v;
      return prevv;
    }

  } else if (op == tsan_atomic_op_compare_exchange_strong
          || op == tsan_atomic_op_compare_exchange_weak) {
    uint64_t prevv = 0;
    if (size == 1) {
      prevv = __sync_val_compare_and_swap((uint8_t volatile*)a, cmp, v);
    } else if (size == 2) {
      prevv = __sync_val_compare_and_swap((uint16_t volatile*)a, cmp, v);
    } else if (size == 4) {
      prevv = __sync_val_compare_and_swap((uint32_t volatile*)a, cmp, v);
    } else if (size == 8) {
      prevv = __sync_val_compare_and_swap((uint64_t volatile*)a, cmp, v);
    }
    *prev = prevv;
    return prevv;

  } else if (op == tsan_atomic_op_fence) {
    if (mo == tsan_memory_order_seq_cst)
      __sync_synchronize();
    return 0;
  }

  CHECK("unknown atomic operation" == 0);
  return 0;
}

#else

uint64_t tsan_atomic_do_op(tsan_atomic_op op,
                           tsan_memory_order mo,
                           tsan_memory_order fail_mo,
                           size_t size,
                           void volatile* a,
                           uint64_t v,
                           uint64_t cmp,
                           uint64_t* newv,
                           uint64_t* prev) {
  CHECK(!"IMPLEMENTED" == 0);
  return 0;
}

#endif