/*++

Copyright (c) Microsoft Corporation. All rights reserved.

Module Name:

    xbbt.cpp

Abstract:

    BBT instrumented binary runtime support functions

--*/

#ifdef _XBOX_ENABLE_PROFILING

#include "ntos.h"
#include "xprofp.h"
#include "xbbt.h"

#ifdef _XBOX_ENABLE_BBT

IRTCLIENTINFO* IrtClientInfo;   // Client-init information
LONG IrtSweepingFlag;           // Whether we're currently sweep data to disk
KTIMER IrtTimer;                // Background timer
KDPC IrtTimerDpc;               //  and associated DPC

#define IsIrtClientInited() (IrtClientInfo != NULL)
#define IrtAssert(cond) do { if (!(cond)) KeBugCheckEx(0, __LINE__, 'IRTS', 0, 0); } while(0)


//
// Allocate space for an additional TLE structure
//
TLE* PtleCreate(IDFHDR* pidfhdr, TLH* ptlh, DWORD iInterval)
{
    DWORD ibtleNew = InterlockedExchangeAdd( (PLONG)&pidfhdr->cb, sizeof(TLE) );
   
    IrtAssert(ibtleNew + sizeof(TLE) <= pidfhdr->cbMax);

    // Fill in the TLE
    TLE* ptle = (TLE*) ((BYTE*) pidfhdr + ibtleNew);
    ptle->ibtleNext = ptlh->ibtleFirst;
    ptle->tob.iInterval = (iInterval & 0xFFFFFF80);

    // Link it into the list
    ptlh->ibtleFirst = ibtleNew;

    return ptle;
}


//
// Sweep TOB data
//
#define PtlhFromIfun(pidfhdr, ifun) \
        ((TLH*) ((BYTE*) pidfhdr + pidfhdr->ibrgtlhCode) + ifun)

#define COPYTOBBITS() { \
        ptlh = PtlhFromIfun(pidfhdr, i); \
        ptlh->qwTOBFanin++; \
        ibtle = ptlh->ibtleFirst; \
        if (ibtle != 0) { \
            ptle = (TLE*) ((BYTE*) pidfhdr + ibtle); \
            dwTimeSlot = iIntervalCur - ptle->tob.iInterval; \
            if (dwTimeSlot < cdwTob * cbitDw) { \
                ptle->tob.rgdwBit[dwTimeSlot>>5] |= 1 << (dwTimeSlot & 0x1F); \
                continue; \
            } \
        } \
        ptle = PtleCreate(pidfhdr, ptlh, iIntervalCur); \
        dwTimeSlot = iIntervalCur - ptle->tob.iInterval; \
        ptle->tob.rgdwBit[dwTimeSlot>>5] |= 1 << (dwTimeSlot & 0x1F); \
        }

VOID
IrtCopyCurrentTOBBits(IRTCLIENTINFO* pci)
{
    // Figure out the current interval index
    IDFHDR* pidfhdr = pci->pidfhdr;
    const IRTP* pirtp = pci->pirtp;
    DWORD iIntervalCur = pci->tobSweepTime / pirtp->cmsTimer;
    pci->tobSweepTime %= pirtp->cmsTimer;

    pci->intervalCount += iIntervalCur;
    pidfhdr->iIntervalCur = iIntervalCur = pci->intervalCount - 1;

    BYTE* rgTobBytes = pci->rgTobCounts;
    DWORD* rgCounts = pci->rgCounts;
    DWORDLONG* pqwDest = (DWORDLONG*) ((BYTE*) pidfhdr + pidfhdr->ibrgqwCount);
    DWORD i, n, ibtle, dwTimeSlot;
    TLH* ptlh;
    TLE* ptle;
    
    n = pidfhdr->ctlhCode;
    if (pidfhdr->tov == tovCTO) {
        // Make tobs from the counts

        // for each TOB
        for (i=0; i < n; i++) {
            // tob bytes are intialized to 1 and set to 0 when hit.
            if (rgCounts[i]) {
                // this byte was hit in the last interval - reset it for the next interval
                // Save counts too!
                pqwDest[i] += rgCounts[i];
                rgCounts[i] = 0;

                COPYTOBBITS();
            }
        }
    } else {
        IrtAssert((pidfhdr->tov & tov4Mask) == 0);

        // for each TOB
        for (i=0; i < n; i++) {
            // tob bytes are intialized to 1 and set to 0 when hit.
            if (rgTobBytes[i] == 0) {
                //this byte was hit in the last interval - reset it for the next interval
                rgTobBytes[i] = 1;

                COPYTOBBITS();
            }
        }
    }
}


//
// Sweep static edge counts
//
VOID IrtCopyCounts(IRTCLIENTINFO* pci)
{
    DWORD* srcCounts = pci->rgCounts;
    IDFHDR* pidfhdr = pci->pidfhdr;
    DWORDLONG* dstCounts = (DWORDLONG*) ((BYTE*) pidfhdr + pidfhdr->ibrgqwCount);
    DWORD i, n;

    n = pidfhdr->cqwCount;
    for (i=0; i < n; i++) {
        *dstCounts++ += *srcCounts;
        *srcCounts++ = 0;
    }

    if (pci->rgSeqNums) {
        n = pidfhdr->ctlhCode;
        for (i=0; i < n; i++) {
            TLH* ptlh = PtlhFromIfun(pidfhdr, i);
            ptlh->iSequenceInit = pci->rgSeqNums[i+1];
        }

        pidfhdr->iSequence = pci->rgSeqNums[0];
    }

    pci->copyCountsTime = 0;
}


VOID
IrtTimerProc(
    PKDPC dpc,
    VOID* context,
    VOID* param1,
    VOID* param2
    )

/*++

Routine Description:

    IRT runtime background sweeping timer procedure

Arguments:

    See DDK documentation

Return Value:

    NONE

Notes:

    This function runs at dispatch level.

--*/

{
    IRTCLIENTINFO* pci = IrtClientInfo;
    IrtAssert(IsIrtClientInited());

    // Check if we need to do a sweep yet. Note that if we're currently
    // in the process of writing IDF data to disk, then skip this round.
    pci->currentTime += IRT_SWEEP_INTERVAL;
    pci->tobSweepTime += IRT_SWEEP_INTERVAL;
    pci->copyCountsTime += IRT_SWEEP_INTERVAL;

    if (IrtSweepingFlag) return;

    if (pci->tobSweepTime >= pci->pirtp->cmsTimer) {
        IrtCopyCurrentTOBBits(pci);
    }

    // Call IrtCopyCounts once every 4 seconds
    if (pci->copyCountsTime >= 4000) {
        IrtCopyCounts(pci);
    }
}


//
// Initialize IDF header information
//
VOID IrtInitializeIdfHeader(IRTCLIENTINFO* pci)
{
    DWORD cb = CbRoundToPage(sizeof(*pci));
    IDFHDR* pidfhdr = (IDFHDR*) ((BYTE*) pci + cb);
    pci->pidfhdr = pidfhdr;

    DWORD cbMax = IRT_BUFFER_SIZE - cb;
    memset(pidfhdr, 0, cbMax);

    const IRTP* pirtp = pci->pirtp;
    pidfhdr->dwSignature = dwIdfSignature;
    pidfhdr->dwVersion = dwIdfVerCurrent;
    pidfhdr->dwId = pirtp->dwId;
    pidfhdr->dwGeneration = pirtp->dwGeneration;
    pidfhdr->tov = pirtp->tov;
    pidfhdr->fSweepable = pirtp->fSweepable ? 1 : 0;

    // Calculate the minimum size for the shared memory region

    cb = CbRound(sizeof(IDFHDR), sizeof(DWORDLONG));
    if (pirtp->szIdfKeyPath) {
        pidfhdr->ibszIdfKeyPath = cb;
        cb += strlen(pirtp->szIdfKeyPath) + 1;
    }
    cb = CbRound(cb, sizeof(DWORDLONG));

    pidfhdr->cqwCount = pirtp->cbrc;
    pidfhdr->ibrgqwCount = cb;
    cb += pidfhdr->cqwCount * sizeof(DWORDLONG);

    // Save space for indirect branches
    pidfhdr->ciblh = pirtp->cibd ? pirtp->cibd : pirtp->cibs;

    pidfhdr->ibrgiblh = cb;
    cb += pidfhdr->ciblh * sizeof(IBLH);
    cb = CbRound(cb, sizeof(DWORDLONG));

    IrtAssert(pirtp->cmsTimer != 0);

    if (pirtp->cfun != 0) {
        pidfhdr->ctlhCode = pirtp->cfun;
        pidfhdr->ibrgtlhCode = cb;
        cb += pidfhdr->ctlhCode * sizeof(TLH);
        cb = CbRound(cb, sizeof(DWORDLONG));
    }

    if (pirtp->crsc != 0) {
        pidfhdr->ctlhResource = pirtp->crsc;
        pidfhdr->ibrgtlhResource = cb;
        cb += pidfhdr->ctlhResource * sizeof(TLH);
        cb = CbRound(cb, sizeof(DWORDLONG));
    }

    pidfhdr->cb = cb;

    // Calculate the minimum size for the shared memory region

    DWORD cbMin = cb + sizeof(THD);
    cbMin += pidfhdr->ciblh * sizeof(IBLE);

    if (pirtp->cfun != 0)
        cbMin += pidfhdr->ctlhCode * sizeof(TLE);

    if (pirtp->crsc != 0)
        cbMin += pidfhdr->ctlhResource * sizeof(TLE);

    IrtAssert(cbMin <= cbMax);

    pidfhdr->cbMax = cbMax;
    pidfhdr->cbAllocated = cbMax;
    pidfhdr->cmsTimer = pirtp->cmsTimer;
    pidfhdr->cpsActive = 1;
    pidfhdr->ifunEndBoot = pirtp->ifunEndBoot;
    pidfhdr->iSequenceEndBoot = (DWORD) -1;
    pidfhdr->iIntervalEndBoot = (DWORD) -1;
    pidfhdr->fFastRT = (pci->rgCounts != NULL);
    pidfhdr->iIntervalStart = pci->intervalCount;
    pidfhdr->cmsStartTime = pci->currentTime;
    KeQuerySystemTime((LARGE_INTEGER*) &pidfhdr->qwStartTime);

    if (pirtp->szIdfKeyPath) {
        strcpy((char*) pidfhdr + pidfhdr->ibszIdfKeyPath, pirtp->szIdfKeyPath);
    }
}


DWORD IrtClientNewIndFast(const IRTP* pirtp, DWORD rvaDest, long iIndSrc)
{
   if (IrtSweepingFlag) return 0;

   IRTCLIENTINFO* pci = IrtClientInfo;
   IDFHDR* pidfhdr = pci->pidfhdr;
   IBLH* rgiblh = (IBLH*) ((BYTE*) pidfhdr + pidfhdr->ibrgiblh);
   IBLH* piblh = rgiblh + iIndSrc;
   DWORD* pibible = &piblh->ibibleFirst;

   // Allocate a new ible
   DWORD ibible = InterlockedExchangeAdd((PLONG)&pidfhdr->cb, sizeof(IBLE));
   IrtAssert(ibible + sizeof(IBLE) <= pidfhdr->cbMax);

   IBLE *pible = (IBLE *) ((BYTE *) pidfhdr + ibible);

   // Fill in the IBLE

   pible->blkidSrc = rvaDest;
   pible->qwCount = 0;

   // Atomically add to the front of the list
   DWORD ibibleFirst;
   do
   {
      ibibleFirst = piblh->ibibleFirst;
      pible->ibibleNext = ibibleFirst;
   }
   while (ibibleFirst != InterlockedCompareExchange((PLONG)&piblh->ibibleFirst, ibible, ibibleFirst ));
   
   InterlockedIncrement((PLONG)&pidfhdr->cible);

   return ibible;
}


//
// This function is called when a BBT instrumented binary is run.
// We assume this function is only called once.
//
extern "C" VOID
IrtClientInitFast(
    const IRTP* pirtp,
    DWORD* rgCounts,
    BYTE* rgTobCounts,
    FASTRTPARAM* pfrtp,
    DWORD* rgSeqNums,
    FASTINDDATA* pfid,
    VOID* unused
    )
{
    if (IsIrtClientInited()) return;

    // Allocate the necessary memory buffers
    IrtSweepingFlag = 0;

    IrtClientInfo = (IRTCLIENTINFO*) MmAllocateSystemMemory(IRT_BUFFER_SIZE, PAGE_READWRITE);
    IrtAssert(IrtClientInfo != NULL);

    // Remember the client init parameters
    memset(IrtClientInfo, 0, sizeof(IRTCLIENTINFO));
    IrtClientInfo->pirtp = pirtp;
    IrtClientInfo->rgCounts = rgCounts;
    IrtClientInfo->rgTobCounts = rgTobCounts;
    IrtClientInfo->rgSeqNums = rgSeqNums;

    // Initialize IDF header information
    IrtInitializeIdfHeader(IrtClientInfo);

    if (pfid) {
       IBLH* rgiblh = (IBLH*) ((BYTE*) IrtClientInfo->pidfhdr + IrtClientInfo->pidfhdr->ibrgiblh);
       pfid->rgiblh = (DWORD_PTR) rgiblh;
       pfid->negaddrbase = -(LONGLONG) pirtp->pvImageBase;
       pfid->pidfhdr = (DWORD_PTR) IrtClientInfo->pidfhdr;
       pfid->pfnInd = (DWORD_PTR) IrtClientNewIndFast;
    }

    // Start the background timer
    LARGE_INTEGER dueTime;
    dueTime.QuadPart = -10000*IRT_SWEEP_INTERVAL;

    KeInitializeDpc(&IrtTimerDpc, IrtTimerProc, NULL);
    KeInitializeTimer(&IrtTimer);
    KeSetTimerEx(&IrtTimer, dueTime, IRT_SWEEP_INTERVAL, &IrtTimerDpc);
}


//
// Sweep the BBT data into a disk file
//
NTSTATUS IrtSweep(HANDLE file)
{
    NTSTATUS status = STATUS_INVALID_PARAMETER;

    //
    // Make sure the client has been initialized
    // and we're not sweeping at the moment
    //
    if (!IsIrtClientInited() ||
        InterlockedCompareExchange(&IrtSweepingFlag, 1, 0) != 0) {
        status = STATUS_INVALID_PARAMETER;
    } else {
        // Sweep static edge counts
        IrtCopyCounts(IrtClientInfo);

        // Write the in-memory IDF data out to the disk

        IO_STATUS_BLOCK iostatusBlock;
        FILE_END_OF_FILE_INFORMATION endOfFile;
        FILE_ALLOCATION_INFORMATION allocation;
        IDFHDR* pidfhdr = IrtClientInfo->pidfhdr;
        DWORD count = pidfhdr->cb;

        status = NtWriteFile(
                   file,
                   NULL,
                   NULL,
                   NULL,
                   &iostatusBlock,
                   pidfhdr,
                   (count + 511) & ~511,
                   NULL);

        // Set file size

        if (NT_SUCCESS(status)) {
            endOfFile.EndOfFile.QuadPart = count;
            status = NtSetInformationFile(
                        file,
                        &iostatusBlock,
                        &endOfFile,
                        sizeof(endOfFile),
                        FileEndOfFileInformation);
        }

        if (NT_SUCCESS(status)) {
            allocation.AllocationSize.QuadPart = count;
            status = NtSetInformationFile(
                        file,
                        &iostatusBlock,
                        &allocation,
                        sizeof(allocation),
                        FileAllocationInformation);
        }

        // Clean up the in-memory data
        IrtInitializeIdfHeader(IrtClientInfo);

        IrtSweepingFlag = 0;
    }

    return status;
}

#else // !_XBOX_ENABLE_BBT

//
// When BBTBUILD is not enabled, just stub out these two functions
//
extern "C" VOID
IrtClientInitFast(
    const IRTP* pirtp,
    DWORD* rgCounts,
    BYTE* rgTobCounts,
    FASTRTPARAM* pfrtp,
    DWORD* rgSeqNums,
    VOID* unused
    ) {}

NTSTATUS IrtSweep(HANDLE file) { return STATUS_NOT_IMPLEMENTED; }

#endif // !_XBOX_ENABLE_BBT

#endif // _XBOX_ENABLE_PROFILING

