/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2004-2011. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sys.h"
#include "error.h"
#include "global.h"
#include "bif.h"
#include "big.h"
#include "erl_binary.h"
#include "hipe_perfctr.h"
#include "libperfctr.h"

static struct vperfctr *vperfctr;
static unsigned int have_rdtsc;
static double tsc_to_ms;
static unsigned int tsc_on; /* control calls must set tsc_on if have_rdtsc is true */
static unsigned int nractrs;
static unsigned int users;
#define USER_BIFS	(1<<0)
#define USER_HRVTIME	(1<<1)

static int hipe_perfctr_open(unsigned int user)
{
    struct perfctr_info info;

    if (!vperfctr) {
	vperfctr = vperfctr_open();
	if (!vperfctr)
	    return -1;
	if (vperfctr_info(vperfctr, &info) >= 0) {
	    tsc_to_ms = (double)(info.tsc_to_cpu_mult ? : 1) / (double)info.cpu_khz;
	    have_rdtsc = (info.cpu_features & PERFCTR_FEATURE_RDTSC) ? 1 : 0;
	}
	tsc_on = 0;
	nractrs = 0;
    }
    users |= user;
    return 0;
}

static void hipe_perfctr_reset(void)
{
    struct vperfctr_control control;

    memset(&control, 0, sizeof control);
    if (have_rdtsc)
	control.cpu_control.tsc_on = 1;
    nractrs = 0;
    if (vperfctr_control(vperfctr, &control) >= 0)
	tsc_on = 1;
}

static void hipe_perfctr_close(unsigned int user)
{
    if (!vperfctr)
	return;
    users &= ~user;
    switch (users) {
      case 0:
	vperfctr_unlink(vperfctr);
	vperfctr_close(vperfctr);
	vperfctr = NULL;
	tsc_on = 0;
	nractrs = 0;
	break;
      case USER_HRVTIME:
	hipe_perfctr_reset();
    }
}

/*
 * Interface for HiPE's hrvtime code.
 */

int hipe_perfctr_hrvtime_open(void)
{
    if (hipe_perfctr_open(USER_HRVTIME) < 0)
	return -1;
    if (have_rdtsc) {
	if (!tsc_on)
	    hipe_perfctr_reset(); /* note: updates tsc_on */
	if (tsc_on)
	    return 0;
    }
    hipe_perfctr_hrvtime_close();
    return -1;
}

void hipe_perfctr_hrvtime_close(void)
{
    hipe_perfctr_close(USER_HRVTIME);
}

double hipe_perfctr_hrvtime_get(void)
{
    return (double)vperfctr_read_tsc(vperfctr) * tsc_to_ms;
}

/*
 * BIF interface for user-programmable performance counters.
 */

BIF_RETTYPE hipe_bifs_vperfctr_open_0(BIF_ALIST_0)
{
    if (hipe_perfctr_open(USER_BIFS) < 0)
	BIF_RET(am_false); /* arity 0 BIFs can't fail :-( */
    BIF_RET(am_true);
}

BIF_RETTYPE hipe_bifs_vperfctr_close_0(BIF_ALIST_0)
{
    hipe_perfctr_close(USER_BIFS);
    BIF_RET(NIL);
}

static Eterm ull_to_integer(unsigned long long x, Process *p)
{
    unsigned long long tmpx;
    unsigned int ds, i;
    size_t sz;
    Eterm *hp;
    ErtsDigit *xp;

    if (x <= (unsigned long long)MAX_SMALL)
	return make_small(x);

    /* Calculate number of digits. */
    ds = 0;
    tmpx = x;
    do {
	++ds;
	tmpx = (tmpx >> (D_EXP / 2)) >> (D_EXP / 2);
    } while (tmpx != 0);

    sz = BIG_NEED_SIZE(ds);	/* number of words including arity */
    hp = HAlloc(p, sz);
    *hp = make_pos_bignum_header(sz-1);

    xp = (ErtsDigit*)(hp+1);
    i = 0;
    do {
	xp[i++] = (ErtsDigit)x;
	x = (x >> (D_EXP / 2)) >> (D_EXP / 2);
    } while (i < ds);
    while (i & (BIG_DIGITS_PER_WORD-1))
	xp[i++] = 0;

    return make_big(hp);
}

BIF_RETTYPE hipe_bifs_vperfctr_info_0(BIF_ALIST_0)
{
    struct perfctr_info info;

    if (!vperfctr || vperfctr_info(vperfctr, &info) < 0)
	BIF_RET(am_false); /* arity 0 BIFs can't fail :-( */
    BIF_RET(new_binary(BIF_P, (void*)&info, sizeof info));
}

BIF_RETTYPE hipe_bifs_vperfctr_read_tsc_0(BIF_ALIST_0)
{
    unsigned long long val;

    if (!vperfctr || !tsc_on)
	BIF_RET(am_false); /* arity 0 BIFs can't fail :-( */
    val = vperfctr_read_tsc(vperfctr);
    BIF_RET(ull_to_integer(val, BIF_P));
}

BIF_RETTYPE hipe_bifs_vperfctr_read_pmc_1(BIF_ALIST_1)
{
    Uint pmc;
    unsigned long long val;

    if (!vperfctr ||
	is_not_small(BIF_ARG_1) ||
	(pmc = unsigned_val(BIF_ARG_1), pmc >= nractrs))
	BIF_RET(am_false); /* for consistency with the arity 0 BIFs */
    val = vperfctr_read_pmc(vperfctr, pmc);
    BIF_RET(ull_to_integer(val, BIF_P));
}

BIF_RETTYPE hipe_bifs_vperfctr_control_1(BIF_ALIST_1)
{
    void *bytes;
    struct vperfctr_control control;
    Uint bitoffs;
    Uint bitsize;

    if (!vperfctr)
	BIF_ERROR(BIF_P, BADARG);
    if (is_not_binary(BIF_ARG_1))
	BIF_ERROR(BIF_P, BADARG);
    if (binary_size(BIF_ARG_1) != sizeof control)
	BIF_ERROR(BIF_P, BADARG);
    ERTS_GET_BINARY_BYTES(BIF_ARG_1, bytes, bitoffs, bitsize);
    ASSERT(bitoffs == 0);
    ASSERT(bitsize == 0);
    memcpy(&control, bytes, sizeof control);
    if (have_rdtsc)
	control.cpu_control.tsc_on = 1;
    if (vperfctr_control(vperfctr, &control) < 0) {
	hipe_perfctr_reset();
	BIF_ERROR(BIF_P, BADARG);
    }
    tsc_on = control.cpu_control.tsc_on;
    nractrs = control.cpu_control.nractrs;
    BIF_RET(NIL);
}