#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#endif

typedef void *(*cpy_t)(void *, const void *, size_t);
typedef void *(*set_t)(void *, int, size_t);
typedef int (*cmp_t)(const void *, const void *, size_t);
typedef void *(*chr_t)(const void *, int, size_t);
typedef size_t (*len_t)(const char *);
typedef size_t (*nlen_t)(const char *, size_t);
typedef char *(*schr_t)(const char *, int);
typedef char *(*sstr_t)(const char *, const char *);
typedef int (*scmp_t)(const char *, const char *);
typedef int (*sncmp_t)(const char *, const char *, size_t);
typedef size_t (*wlen_t)(const wchar_t *);

enum kind { CPY, SET, CMP, CHR, LEN, NLEN, SCHR, SRCHR, SSTR, SCMP, SNCMP, WLEN };
struct fn { const char *name; enum kind kind; };
static const struct fn fns[] = {
  {"memcpy", CPY}, {"memmove", CPY}, {"memset", SET}, {"memcmp", CMP}, {"memchr", CHR},
  {"strlen", LEN}, {"strnlen", NLEN}, {"strchr", SCHR}, {"strrchr", SRCHR}, {"strstr", SSTR},
  {"strcmp", SCMP}, {"strncmp", SNCMP}, {"wcslen", WLEN},
};
#define NFNS (sizeof(fns) / sizeof(fns[0]))

static double now(void) {
#ifdef _WIN32
  LARGE_INTEGER f, t;
  QueryPerformanceFrequency(&f);
  QueryPerformanceCounter(&t);
  return (double)t.QuadPart / f.QuadPart;
#else
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return ts.tv_sec + ts.tv_nsec / 1e9;
#endif
}

#define BARRIER(p) __asm__ volatile("" : : "r"(p) : "memory")

static unsigned char *A, *B;
static wchar_t *W;

/* returns GB/s, or -1 on a wrong result */
static double bench(enum kind k, void *f, size_t n) {
  long iters = (long)((256ULL << 20) / n);
  size_t bad = 0;
  /* setup: n bytes of content; strings end with NUL at n-1 */
  memset(A, 'a', n);
  memset(B, 'a', n);
  A[n - 2] = '\n';
  A[n - 1] = 0;
  B[n - 2] = '\n';
  B[n - 1] = 0;
  if (k == SRCHR) A[0] = 'z';
  if (k == SSTR) { A[n - 4] = 'x'; A[n - 3] = 'y'; A[n - 2] = 'z'; }
  if (k == CHR) { A[n - 1] = '\n'; A[n - 2] = 'a'; }
  for (size_t i = 0; i < n / sizeof(wchar_t); i++) W[i] = L'a';
  W[n / sizeof(wchar_t) - 1] = 0;
  double t0 = now();
  for (long i = 0; i < iters; i++) {
    BARRIER(A);
    BARRIER(B);
    switch (k) {
    case CPY: bad += ((cpy_t)f)(B, A, n) != B; break;
    case SET: bad += ((set_t)f)(B, 'x', n) != B; break;
    case CMP: bad += ((cmp_t)f)(A, B, n) != 0; break;
    case CHR: bad += (unsigned char *)((chr_t)f)(A, '\n', n) != A + n - 1; break;
    case LEN: bad += ((len_t)f)((char *)A) != n - 1; break;
    case NLEN: bad += ((nlen_t)f)((char *)A, n) != n - 1; break;
    case SCHR: bad += ((schr_t)f)((char *)A, '\n') != (char *)A + n - 2; break;
    case SRCHR: bad += ((schr_t)f)((char *)A, 'z') != (char *)A; break;
    case SSTR: bad += ((sstr_t)f)((char *)A, "xyz") != (char *)A + n - 4; break;
    case SCMP: bad += ((scmp_t)f)((char *)A, (char *)B) != 0; break;
    case SNCMP: bad += ((sncmp_t)f)((char *)A, (char *)B, n) != 0; break;
    case WLEN: bad += ((wlen_t)f)(W) != n / sizeof(wchar_t) - 1; break;
    }
  }
  double t = now() - t0;
  if (bad) return -1;
  return (double)n * iters / t / 1e9;
}

int main(void) {
  size_t sizes[] = {64, 1040, 65536, 1 << 20};
  A = malloc(1 << 20);
  B = malloc(1 << 20);
  W = malloc(1 << 20);
#ifdef _WIN32
  const char *libs[] = {"ucrtbase.dll", "msvcrt.dll", "ntdll.dll", "vcruntime140.dll"};
  int nlibs = 4;
#else
  const char *libs[] = {"glibc"};
  int nlibs = 1;
  void *glibc[] = {(void *)memcpy, (void *)memmove, (void *)memset, (void *)memcmp, (void *)memchr,
                   (void *)strlen, (void *)strnlen, (void *)strchr, (void *)strrchr, (void *)strstr,
                   (void *)strcmp, (void *)strncmp, (void *)wcslen};
#endif
  printf("%-9s %-17s %10s %10s %10s %10s   (GB/s)\n", "func", "library", "64 B", "1040 B", "64 KB", "1 MB");
  for (size_t i = 0; i < NFNS; i++) {
    for (int l = 0; l < nlibs; l++) {
#ifdef _WIN32
      HMODULE h = LoadLibraryA(libs[l]);
      void *f = h ? (void *)GetProcAddress(h, fns[i].name) : NULL;
#else
      void *f = glibc[i];
#endif
      printf("%-9s %-17s", fns[i].name, libs[l]);
      if (!f) { printf(" (not exported)\n"); continue; }
      for (int s = 0; s < 4; s++) {
        double g = bench(fns[i].kind, f, sizes[s]);
        if (g < 0) printf(" %10s", "WRONG");
        else printf(" %10.1f", g);
      }
      printf("\n");
    }
  }
  return 0;
}
