#include <stdint.h>
#include <stdio.h>

#define RDTSC_START(cycles)                                             \
    do {                                                                \
        uint32_t cyc_high, cyc_low;                                     \
        __asm volatile("cpuid\n"                                        \
                       "rdtsc\n"                                        \
                       "mov %%edx, %0\n"                                \
                       "mov %%eax, %1" :                                \
                       "=r" (cyc_high),                                 \
                       "=r"(cyc_low) :                                  \
                       : /* no read only */                             \
                       "%rax", "%rbx", "%rcx", "%rdx" /* clobbers */    \
                       );                                               \
        (cycles) = ((uint64_t)cyc_high << 32) | cyc_low;                \
    } while (0)

#define RDTSC_STOP(cycles)                                              \
    do {                                                                \
        uint32_t cyc_high, cyc_low;                                     \
        __asm volatile("rdtscp\n"                                       \
                       "mov %%edx, %0\n"                                \
                       "mov %%eax, %1\n"                                \
                       "cpuid" :                                        \
                       "=r"(cyc_high),                                  \
                       "=r"(cyc_low) :                                  \
                       /* no read only registers */ :                   \
                       "%rax", "%rbx", "%rcx", "%rdx" /* clobbers */    \
                       );                                               \
        (cycles) = ((uint64_t)cyc_high << 32) | cyc_low;                \
    } while (0)

static __attribute__ ((noinline))
uint64_t rdtsc_overhead_func(uint64_t dummy) {
    return dummy;
}

uint64_t global_rdtsc_overhead = (uint64_t) UINT64_MAX;

#define RDTSC_SET_OVERHEAD(test, repeat)			      \
  do {								      \
    uint64_t cycles_start, cycles_final, cycles_diff;		      \
    uint64_t min_diff = UINT64_MAX;				      \
    for (int i = 0; i < repeat; i++) {			      \
      __asm volatile("" ::: /* pretend to clobber */ "memory");	      \
      RDTSC_START(cycles_start);				      \
      test;							      \
      RDTSC_STOP(cycles_final);                                       \
      cycles_diff = (cycles_final - cycles_start);		      \
      if (cycles_diff < min_diff) min_diff = cycles_diff;	      \
    }								      \
    global_rdtsc_overhead = min_diff;				      \
    printf("rdtsc_overhead set to %d\n", (int)global_rdtsc_overhead);     \
  } while (0)							      \


/*
 * Prints the best number of operations per cycle where
 * test is the function call, answer is the expected answer generated by
 * test, repeat is the number of times we should repeat and size is the
 * number of operations represented by test.
 */
#define BEST_TIME(test, expected, pre, repeat, size, verbose)                         \
        do {                                                              \
            if (global_rdtsc_overhead == UINT64_MAX) {                    \
               RDTSC_SET_OVERHEAD(rdtsc_overhead_func(1), repeat);        \
            }                                                             \
            if(verbose) printf("%-60s\t: ", #test);                                        \
            fflush(NULL);                                                 \
            uint64_t cycles_start, cycles_final, cycles_diff;             \
            uint64_t min_diff = (uint64_t)-1;                             \
            uint64_t sum_diff = 0;                                        \
            for (int i = 0; i < repeat; i++) {                            \
                pre;                                                      \
                __asm volatile("" ::: /* pretend to clobber */ "memory"); \
                RDTSC_START(cycles_start);                                \
                if(test != expected) {printf("not expected (%d , %d )",(int)test,(int)expected);break;}                     \
                RDTSC_STOP(cycles_final);                                \
                cycles_diff = (cycles_final - cycles_start - global_rdtsc_overhead);           \
                if (cycles_diff < min_diff) min_diff = cycles_diff;       \
                sum_diff += cycles_diff;                                  \
            }                                                             \
            uint64_t S = size;                                            \
            float cycle_per_op = (min_diff) / (double)S;                  \
            float avg_cycle_per_op = (sum_diff) / ((double)S * repeat);   \
            if(verbose) printf(" %.3f cycles per operation (best) ", cycle_per_op);   \
            if(verbose) printf("\t%.3f cycles per operation (avg) ", avg_cycle_per_op);   \
            if(verbose) printf("\n");                                                 \
            if(!verbose) printf(" %.3f ",cycle_per_op);                   \
            fflush(NULL);                                                 \
 } while (0)

 __attribute__ ((noinline))
uint32_t encode_hundreds(uint32_t hi, uint32_t lo) {
        uint32_t merged = hi | (lo << 16);
        uint32_t tens = (merged * 103UL) >> 10; // they discard lsb
        tens &= (0xFUL << 16) | 0xFUL;
        return tens + ((merged - 10UL * tens) << 8);
}

 __attribute__ ((noinline))
uint32_t alt_encode_hundreds(uint32_t hi, uint32_t lo) {
        uint32_t merged = hi | (lo << 16);
        uint32_t multiply = merged * 103UL;
        uint32_t multiply_lsb = multiply & 0x03ff03ff;
        uint32_t multiply_msb = multiply & 0x3c003c00;
        uint32_t tens = multiply_msb >> 10;
        uint32_t units = ((multiply_lsb * 10) >> 2)& 0x0F000F00;
        return tens + units;// could be addition
}

uint32_t sumall() {
  uint32_t x = 0;
  for(int k = 0; k < 10000; k++) {
    x += encode_hundreds(k/100,k%100);
  }
  return x;
}
uint32_t altsumall() {
  uint32_t x = 0;
  for(int k = 0; k < 10000; k++) {
    x += alt_encode_hundreds(k/100,k%100);
  }
  return x;
}

int main() {
  for(int k = 0; k < 10000; k++) {
    uint32_t e1 = encode_hundreds(k/100,k%100);
    uint32_t e2 = alt_encode_hundreds(k/100,k%100);
    printf("%08X %08X \n", e1, e2);
    if(e1!=e2) {printf("bug\n"); break;}
  }
  uint32_t expected = sumall();
  int repeat = 5;
  int size = 10000;
  int verbose = 1;

  BEST_TIME(sumall(), expected, , repeat, size, verbose);
  BEST_TIME(altsumall(), expected, , repeat, size, verbose);
  BEST_TIME(sumall(), expected, , repeat, size, verbose);
  BEST_TIME(altsumall(), expected, , repeat, size, verbose);

}

