/* tsc.c coded by isaku@pb4.so-net.ne.jp
タイムスタンプカウンターの比較
Pentium 以降のCPUに対応 
gcc(32/64)、Visual C++(32/64)、Borland C++(32)で動作する
2.6 より前の linux には対応していない
*/

#if defined(__GNUC__)
 #define _GNU_SOURCE 1
 #include <sched.h>
 typedef long long __int64;
#else
 #include <windows.h>
#endif

#include <stdio.h>

#define TSC_HISTGRAM_SIZE 100

#if defined(_MSC_VER)
 #pragma comment(linker,"/section:.rdata,ER")
 #pragma const_seg(".rdata")
#endif

const unsigned char RdtscText32[]={
    0x0F,0x31, /* rdtsc */
    0xC3,      /* ret   */
};

const unsigned char RdtscText64[]={
    0x0F,0x31,           /* rdtsc      */
    0x48,0xC1,0xE2,0x20, /* shl rdx,32 */
    0x48,0x09,0xD0,      /* or rax,rdx */
    0xC3,                /* ret        */
};

#define rdtsc32() (((__int64(*)(void))(size_t)RdtscText32)())
#define rdtsc64() (((__int64(*)(void))(size_t)RdtscText64)())
#define rdtsc()   (sizeof(size_t)==8?rdtsc64():rdtsc32())

typedef struct tsc_t { 
    __int64 tick;   /* クロック数計測開始値   */
    double  median; /* クロック数の中央値     */
    int     size;   /* ヒストグラムの大きさ   */
    int     num;    /* ヒストグラムの登録総数 */
    int     cnt[TSC_HISTGRAM_SIZE]; /* ヒストグラムのカウンタ */
    __int64 key[TSC_HISTGRAM_SIZE]; /* ヒストグラムの値       */
} tsc_t;

void AddHistgramTsc(tsc_t*t,__int64 key) {
    int i,j;
    t->num++; key-=t->tick;
    for (i=0;i<t->size;i++) {
        if (key==t->key[i]) { t->cnt[i]++; return; }
        if (key<t->key[i]) break;
    }
    if (t->size>=TSC_HISTGRAM_SIZE) {
        if (i==0) { t->cnt[0]++; t->key[0]=key; }
        else if (i>=TSC_HISTGRAM_SIZE)
        { t->cnt[TSC_HISTGRAM_SIZE-1]++; t->key[TSC_HISTGRAM_SIZE-1]=key; }
        else if (t->key[i]-key<key-t->key[i-1]) t->cnt[i]++;
        else t->cnt[i-1]++;
        return;
    }
    for (j=t->size++;j>i;j--)
    { t->cnt[j]=t->cnt[j-1]; t->key[j]=t->key[j-1]; }
    t->key[i]=key; t->cnt[i]=1;
}

void InitializeTsc(tsc_t*t) {
    static int initialized;
    if (initialized==0) {
#if defined(__GNUC__)
        cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(0,&mask);
        sched_setaffinity(0,sizeof mask,&mask);
#else
        SetThreadAffinityMask(GetCurrentThread(),1);
#endif
        initialized=1;
    }
    t->size=t->num=0;
}

#define BeginTsc(T) ((T)->tick=rdtsc())
#define EndTsc(T)   AddHistgramTsc(T,rdtsc())

void ReportTsc(const char*msg,tsc_t*t,double result) {
    int i,sum;
    if (t->size<=0) { printf("%s is empty\n",msg); return; }
    for (i=0,sum=t->cnt[0];sum<(t->num+1)/2;i++) sum+=t->cnt[i+1];
    if (sum>t->num/2) t->median=(double)t->key[i];
    else t->median=(double)((t->key[i]+t->key[i+1])/2);
    printf("clock=%.0f %s result=%g\n",t->median,msg,result);
}

enum { A=0x76543210, SAMPLE=100, LOOP=100 };
unsigned mag[2]={0,A};

#define TEST(X) {                    \
    unsigned sum,i,y; tsc_t tsc[1];  \
    InitializeTsc(tsc);              \
    for (sum=i=0;i<SAMPLE;i++) {     \
        BeginTsc(tsc);               \
        for (y=0;y<LOOP;y++) sum+=X; \
        EndTsc(tsc);                 \
    }                                \
    ReportTsc(#X,tsc,sum);           \
}

int main(void) {
    TEST(mag[y&1]);
    TEST((y&1)*A);
    TEST(((y&1)?A:0));
    TEST((-(int)(y&1))&A);
    TEST((((int)(y<<31))>>31)&A);
    return 0;
}