#include "Rts.h"

#if darwin_HOST_OS

#include <mach/mach_time.h>

StgWord64 criterion_rdtsc(void)
{
  return mach_absolute_time();
}

#elif aarch64_HOST_ARCH

StgWord64 criterion_rdtsc(void)
{
  StgWord64 ret;
  __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r"(ret));
  return ret;
}

#elif powerpc64le_HOST_ARCH || powerpc64_HOST_ARCH

StgWord64 criterion_rdtsc(void)
{
  // Read the PowerPC Time Base: a monotonically-increasing 64-bit counter
  // that is the architectural analogue of x86's TSC. SPR 268 returns the
  // full 64-bit Time Base on 64-bit PowerPC (this is what the deprecated
  // `mftb` mnemonic expanded to).
  StgWord64 ret;
  __asm__ __volatile__ ("mfspr %0, 268" : "=r"(ret));
  return ret;
}

#elif x86_64_HOST_ARCH || i386_HOST_ARCH

StgWord64 criterion_rdtsc(void)
{
  StgWord32 hi, lo;
  __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
  return ((StgWord64) lo) | (((StgWord64) hi)<<32);
}

#elif linux_HOST_OS

/*
 * This should work on all Linux.
 *
 * Technique by Austin Seipp found here:
 *
 * http://neocontra.blogspot.com/2013/05/user-mode-performance-counters-for.html
 */

#include <unistd.h>
#include <asm-generic/unistd.h>
#include <linux/perf_event.h>

static int fddev = -1;
__attribute__((constructor))
static void
init(void)
{
  static struct perf_event_attr attr;
  attr.type = PERF_TYPE_HARDWARE;
  attr.config = PERF_COUNT_HW_CPU_CYCLES;
  fddev = syscall (__NR_perf_event_open, &attr, 0, -1, -1, 0);
}

__attribute__((destructor))
static void
fini(void)
{
  close(fddev);
}

StgWord64
criterion_rdtsc (void)
{
  StgWord64 result = 0;
  if (read (fddev, &result, sizeof(result)) < sizeof(result))
    return 0;
  return result;
}

#elif wasm32_HOST_ARCH

#include <time.h>

StgWord64 criterion_rdtsc(void)
{
  struct timespec ts;
  StgWord64 result_stg = 0;
  clock_gettime(CLOCK_REALTIME, &ts);
  result_stg = ts.tv_sec * 1000000000LL + ts.tv_nsec;
  return result_stg;
}
#else

#error Unsupported OS/architecture/compiler!

#endif
