//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/***
*shadow.cpp - RTC support
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*
*Revision History:
*       07-28-98  JWM   Module incorporated into CRTs (from KFrei)
*       08-13-98  KBF   Changed address cache to be 'invalid' address cache
*       08-13-98  KBF   Turned on optimization, modified address calc functions
*       10-13-98  KBF   Added Shadow-Death notification capabilities
*       11-03-98  KBF   added pragma intrinsic to eliminate CRT code dependence
*       11-03-98  KBF   Also fixed bug with allocating blocks of 4K multiples
*       12-01-98  KBF   Fixed a bug in RTC_MSFreeShadow - MC 11029
*       12-02-98  KBF   Fixed _RTC_MSR0AssignPtr
*       12-03-98  KBF   Added CheckMem_API and APISet functions
*       05-11-99  KBF   Error if RTC support define not enabled
*       05-14-99  KBF   Requires _RTC_ADVMEM (it's been cut for 7.0)
*
****/

#ifndef _RTC
#error  RunTime Check support not enabled!
#endif

#include "rtcpriv.h"

#ifdef _RTC_ADVMEM

static const unsigned MEM_SIZE = 0x40000000;
// This tag is valid?
#define MEM_ISVALID(tag) (tag)
// Both tags in this short are valid
#define MEM_SHORTVALID(tag) (((tag) & 0xFF) && ((tag) & 0xFF00))
// All 4 tags in this int are valid
#define MEM_INTVALID(tag) (((tag) & 0xFF) && ((tag) & 0xFF00) && ((tag) & 0xFF0000) && ((tag) & 0xFF000000))
// Given an address, get a shadow memory index
#define MEM_FIXADDR(addr) ((addr) & (MEM_SIZE - 1))
// An int's worth of unused values
static const unsigned int MEM_EMPTYINT = 0;
// An unused value
static const shadowtag MEM_EMPTY = 0;
// An untracked value
static const shadowtag MEM_UNKNOWN = 0xFF;
static const unsigned int MEM_UNKNOWNINT = 0xFFFFFFFF;

#define MEM_NEXT_ID(a) ((shadowtag)((a) % 253 + 1))

static const unsigned int PAGE_SIZE = 4096;

/* Page Index Macros */
static const unsigned int PAGES_PER_ELEM = 1;
static const unsigned int MEM_PER_IDX = PAGE_SIZE; // 4K
static const unsigned int IDX_SIZE = ((MEM_SIZE / MEM_PER_IDX) * sizeof(index_elem)) / PAGES_PER_ELEM;
static const unsigned int IDX_STATE_TRACKED        = 0x2; // bitmask

#define SET_IDX_STATE(idx, st) (_RTC_pageidx[idx]=st)
#define GET_IDX_STATE(idx) (_RTC_pageidx[idx])

// Get the index number for a given address
#define IDX_NUM(addr) (MEM_FIXADDR(addr) / MEM_PER_IDX)

// Index align this address
#define IDX_ALIGN(addr) ((addr) & ~(MEM_PER_IDX - 1))

#ifdef _RTC_DEBUG
// Debugging helper functions
#define show(a, b) unsigned b(unsigned c) { return a(c); }
show(GET_IDX_STATE, get_idx_state)
show(IDX_NUM, idx_num)
show(IDX_ALIGN, idx_align)
#undef show
#endif

// This is the pseudo-address used for REG0 in the cache
#define REG0 ((memref)1)

static shadowtag blockID = 0;

static void KillShadow();

#ifdef __MSVC_RUNTIME_CHECKS
#error Hey dufus, don't compile this file with runtime checks turned on
#endif

#pragma intrinsic(memset)

struct cacheLine
{
    memref pointer;
    memptr value;
    memptr base;
    void *assignment;
};

// Actual Cache Size is 2^CacheSize
// Cache is 8x3 - 24 elements total
#define CACHESIZE     3
#define CACHELINESIZE 3

static cacheLine cache[1<<CACHESIZE][CACHELINESIZE];
static long readlocks[1<<CACHESIZE];
static long writelocks[1<<CACHESIZE];
static long cachePos[1<<CACHESIZE];

#define READ_LOCK(line) \
{\
    while(InterlockedIncrement(&readlocks[line]) <= 0)\
    {\
        InterlockedDecrement(&readlocks[line]);\
        Sleep(0);\
    }\
}
#define READ_UNLOCK(line) InterlockedDecrement(&readlocks[line])

static void WRITE_LOCK(int line) 
{
    while (InterlockedExchange(&writelocks[line], 1))
        Sleep(0);
    long users = InterlockedExchange(&readlocks[line], -2000);
    while (readlocks[line] != -2000-users)
        Sleep(0);
}

#define WRITE_UNLOCK(line) {readlocks[line] = 0; writelocks[line] = 0;}

#define CacheHash(value) (((1 << CACHESIZE) - 1) & (value >> 3))

static void
ClearCacheRange(memptr lo, memptr hi)
{
    // Remove all pointers stored between lo and hi
    // We don't need to use locks, because this stuff is only
    // used for the stack, and if you're running on the same stack
    // you're in serious trouble...
    unsigned size = hi - lo;
    for (int i = 0; i < (1 << CACHESIZE); i++)
    {
        for (int j = 0; j < CACHELINESIZE; j++)
        {
            if (cache[i][j].pointer && 
                (unsigned)cache[i][j].pointer - (unsigned)lo < size)
                cache[i][j].pointer = 0;
        }
    }
}

static void
AddCacheLine(void *retaddr, memref ptr, memptr base, memptr value)
{
    if (!value)
        return;
    
    int loc = CacheHash((int)ptr);

    WRITE_LOCK(loc);
    int prefpos = 0;
    
    for (int i = 0; i < CACHELINESIZE; i++)
    {
        if (cache[loc][i].pointer == ptr)
        {
            prefpos = i+1;
            break;
        } else if (!prefpos && !cache[loc][i].pointer)
            prefpos = i+1;
    }

    if (!prefpos)
        prefpos = cachePos[loc];
    else
        prefpos--;
    
    cache[loc][prefpos].pointer = ptr;
    cache[loc][prefpos].value = value;
    cache[loc][prefpos].base = base;
    cache[loc][prefpos].assignment = retaddr;
    
    if (++prefpos == CACHELINESIZE)
        cachePos[loc] = 0;
    else
        cachePos[loc] = prefpos;
    
    WRITE_UNLOCK(loc);
}

static void
ClearCacheLine(memref ptr)
{
    int loc = CacheHash((int)ptr);

    READ_LOCK(loc);
    for (int i = 0; i < CACHELINESIZE; i++)
    {
        if (cache[loc][i].pointer == ptr)
        {
            READ_UNLOCK(loc);
            WRITE_LOCK(loc);
            cache[loc][i].pointer = 0;
            cachePos[loc] = i;
            WRITE_UNLOCK(loc);
            return;
        }
    }
    READ_UNLOCK(loc);
}

#define GetCacheLine(ptr, dst) {\
    int loc = CacheHash((int)ptr);\
    dst.pointer = 0;\
    READ_LOCK(loc);\
    for (int i = 0; i < CACHELINESIZE; i++)\
    {\
        if (cache[loc][i].pointer == ptr)\
        {\
            dst = cache[loc][i];\
            break;\
        }\
    }\
    READ_UNLOCK(loc);\
}

static void
ClearCache()
{
    for (int loc = 0; loc < 1 << CACHESIZE; loc++)
    {
        for (int i = 0; i < CACHELINESIZE; i++)
            cache[loc][i].pointer = 0;
        readlocks[loc] = writelocks[loc] = 0;
        cachePos[loc] = 0;
    }
}


// This is called before every function to allocate 
// locals in the shadow memory
void __fastcall
_RTC_MSAllocateFrame(memptr frame, _RTC_framedesc *v)
{
    if (!_RTC_shadow)
        return;

    int i;
    int memsize = -v->variables[v->varCount-1].addr + sizeof(int);

    // Next, commit all required pages, initializing all unallocated portions 
    // of newly committed memory to the proper state
    _RTC_MSCommitRange(frame - memsize, memsize, IDX_STATE_PARTIALLY_KNOWN);
    if (!_RTC_shadow)
        return;

    // Now step thru each variable, and allocate it within the shadow memory
    // While allocating, mark the buffer sections as invalid
    for (i = 0; i < v->varCount; i++)
    {
        *(unsigned*)(&_RTC_shadow[MEM_FIXADDR(frame + v->variables[i].addr - sizeof(int))]) = MEM_EMPTYINT;
        *(unsigned*)(&_RTC_shadow[MEM_FIXADDR(frame + v->variables[i].addr + v->variables[i].size)]) = MEM_EMPTYINT;
        blockID = MEM_NEXT_ID(blockID);
        memset(&_RTC_shadow[MEM_FIXADDR(frame + v->variables[i].addr)], blockID, v->variables[i].size);
    }
}

// Free the stack frame from shadow memory
void __fastcall
_RTC_MSFreeFrame(memptr frame, _RTC_framedesc *v)
{
    // I'm not bothering to attempt to free any shadow memory pages
    // This might cause problems for certain really poorly written programs...
    if (_RTC_shadow)
    {
        int size = (sizeof(int) + sizeof(int) - 1 - v->variables[v->varCount - 1].addr);
        memset(&_RTC_shadow[MEM_FIXADDR(frame-size)], MEM_UNKNOWN, size);

        // Temporary hack until we handle parameters
        ClearCacheRange(frame - size, frame);
    }
    // Cheap Bounds Check, to be sure that no external functions trash the stack
    _RTC_CheckStackVars((void*)frame, v);
}


// The list of global variable descriptors is constructed by dummy
// start and end descriptor here, in sections .rtc$MEA and .rtc$MEZ.
// The compiler emits .rtc$MEB entries for each global, under -RTCm.
// The linker sorts these together into the .rtc section. Note that the
// linker, under /DEBUG, inserts zero padding into the section for
// incremental compilation. We force the alignment of these descriptors,
// and thus the sections, to be the size of the structure, so no odd padding
// is inserted.
//
// The following is how the code *should* look:
//
// __declspec(align(8)) struct global_descriptor {
//     memptr addr;
//     unsigned size;
// };
// 
// #pragma section(".rtc$MEA", read)
// #pragma section(".rtc$MEZ", read)
// 
// __declspec(allocate(".rtc$MEA")) global_descriptor glob_desc_start = {0};
// __declspec(allocate(".rtc$MEZ")) global_descriptor glob_desc_end = {0};
//
// However, __declspec(align()), #pragma section, and __declspec(allocate())
// are all VC 6.1 features. It is a CRT requirement to compile the 6.1 CRT
// using only 6.0 language features (because NT 5 only uses the 6.0 compiler,
// I think). So here's how we do it:

struct global_descriptor
{
    union {
        double ___unused; // only here to force 8-byte alignment
        struct {
            memptr addr;
            unsigned size;
        };
    };
};

#pragma const_seg(".rtc$MEA")
const global_descriptor glob_desc_start = {0};
#pragma const_seg(".rtc$MEZ")
const global_descriptor glob_desc_end = {0};
#pragma const_seg()

// We must start our loop at &glob_desc_start, not &glob_desc_start + 1,
// because the pre-VC 6.1 compiler (specifically, the global optimizer)
// treats &glob_desc_start + 1 as distinct (unaliased) from &glob_desc_end,
// in the loop below. Thus, it gets rid of the loop test at the loop top.
// This is a problem for cases where there are no globals. This is done
// because it is expected that the 6.1 CRT will be compiled by pre-6.1
// compilers.

// Allocate the list of globals in shadow memory
void __cdecl
_RTC_MSAllocateGlobals(void)
{
    if (!_RTC_shadow)
        return;

    // Just step thru every item, and call _RTC_MSAllocShadow
    const global_descriptor *glob = &glob_desc_start;
    for (; glob != &glob_desc_end; glob++)
        _RTC_MSAllocShadow(glob->addr, glob->size, IDX_STATE_PARTIALLY_KNOWN);
}


// This should initialize the shadow memory as appropriate,
// committing all necessary pages
// partial implies that the page is only partially known
// so we need to be sure that all unallocated values on the
// page are set as a single valid block
short
_RTC_MSAllocShadow(memptr real_addr, unsigned real_size, unsigned state)
{
    // Ignore bogus zero address or size, possibly from globals linker padding
    if (!_RTC_shadow || !real_addr || !real_size)
        return 0;

    // Now allocate the shadow memory, if necessary
    if (state & IDX_STATE_TRACKED)
    {
        // Commit the shadow memory, 
        // marking newly committed, but unallocated memory as appropriate
        _RTC_MSCommitRange(real_addr, real_size, state);
        if (!_RTC_shadow)
            return blockID;

        // Now initialize the shadow memory
        blockID = MEM_NEXT_ID(blockID);

        memset(&_RTC_shadow[MEM_FIXADDR(real_addr)], blockID, real_size);
    } else if (state == IDX_STATE_ILLEGAL)
    {
        // Initialize the page index stuff to the correct state
        // ASSERT(state == IDX_STATE_ILLEGAL)

        unsigned idx_start = IDX_NUM(real_addr);
        unsigned idx_end = IDX_NUM(real_addr + real_size - 1);

        for (unsigned i = idx_start; i <= idx_end; i++)
            SET_IDX_STATE(i, state);
    }

    return blockID;
}

// This sets the value of the shadow memory to be the value passed in
void 
_RTC_MSRestoreShadow(memptr addr, unsigned size, short id)
{
    if (!_RTC_shadow)
        return;
    memset(&_RTC_shadow[MEM_FIXADDR(addr)], id, size);
}

// This assigns a new blockID to the shadow memory
// It will NOT be equal to the id passed in
short
_RTC_MSRenumberShadow(memptr addr, unsigned size, short notID)
{
    if (!_RTC_shadow)
        return 0;

    blockID = MEM_NEXT_ID(blockID);
    
    if (blockID == notID)
        blockID = MEM_NEXT_ID(blockID);
    
    memset(&_RTC_shadow[MEM_FIXADDR(addr)], blockID, size);
    return blockID;
}
    

// This should de-initialize shadow memory
// and decommit any unneeded pages
void _RTC_MSFreeShadow(memptr addr, unsigned size)
{
    if (!_RTC_shadow)
        return;

    // Low & Hi are the bounds of the freed memory region
    memptr low = MEM_FIXADDR(addr);
    memptr hi  = (low + size) & ~(sizeof(unsigned)-1);

    // start and end are the page-aligned bounds;
    memptr start = IDX_ALIGN(low);
    memptr end = IDX_ALIGN(low + size + MEM_PER_IDX - 1);

    memptr tmp;

    int used;

    // First, clear the shadow memory that contained this stuff
    memset(&_RTC_shadow[low], 
           (GET_IDX_STATE(IDX_NUM(low)) == IDX_STATE_PARTIALLY_KNOWN) 
            ? MEM_UNKNOWN 
            : MEM_EMPTY, 
           size);

    // Now go thru and release the pages that have
    // been completely eliminated from use
    for (tmp = start, used = 0; !used && tmp < low; tmp += sizeof(unsigned))
    {
        unsigned val = *(unsigned *)&_RTC_shadow[tmp];
        used = val != MEM_EMPTYINT && val != MEM_UNKNOWNINT;
    }

    if (used)
        start += MEM_PER_IDX;

    for (tmp = hi, used = 0; !used && tmp < end; tmp += sizeof(unsigned))
    {
        unsigned val = *(unsigned *)&_RTC_shadow[tmp];
        used = val != MEM_EMPTYINT && val != MEM_UNKNOWNINT;
    }

    if (used)
        end -= MEM_PER_IDX;

    if (start < end)
        // Free the page in memory
        _RTC_MSDecommitRange(start, end-start);

}

void _RTC_MSCommitRange(memptr addr, unsigned size, unsigned state)
{
    // Commit the page range
    if (!VirtualAlloc(&_RTC_shadow[MEM_FIXADDR(addr)], size, MEM_COMMIT, PAGE_READWRITE))
        KillShadow();
    else {
        // Now mark the range as committed in the page tables
        size += (addr - IDX_ALIGN(addr));
        int val = (state == IDX_STATE_PARTIALLY_KNOWN) ? MEM_UNKNOWNINT : MEM_EMPTYINT;
        while (size && !(size & 0x80000000)) 
        {
            // If this is a newly committed page, initialize it to the proper value
            if (GET_IDX_STATE(IDX_NUM(addr)) != state)
            {
                SET_IDX_STATE(IDX_NUM(addr), state);
                int *pg = (int*)&_RTC_shadow[MEM_FIXADDR(IDX_ALIGN(addr))];
                for (int i = 0; i < MEM_PER_IDX / sizeof(int); i++)
                    pg[i] = val;
            }
            addr += MEM_PER_IDX;
            size -= MEM_PER_IDX;
        }
    }
}

void _RTC_MSDecommitRange(memptr addr, unsigned size)
{
    // Decommit the page range
    VirtualFree(&_RTC_shadow[MEM_FIXADDR(addr)], size, MEM_DECOMMIT);

    // Now mark the range as decommited in the page tables
    size += (addr - IDX_ALIGN(addr));
    while (size && !(size & 0x80000000))
    {
        SET_IDX_STATE(IDX_NUM(addr), IDX_STATE_UNKNOWN);
        addr += MEM_PER_IDX;
        size -= MEM_PER_IDX;
    }
}

static shadowtag
GetAddrTag(memptr addr)
{
    shadowtag *loc = &_RTC_shadow[MEM_FIXADDR(addr)];
    if ((memptr)loc == addr)
        return MEM_EMPTY;
    
    if (addr & 0x80000000) 
        return MEM_UNKNOWN;
    switch (GET_IDX_STATE(IDX_NUM(addr)))
    {
    case IDX_STATE_UNKNOWN:
        return MEM_UNKNOWN;
    case IDX_STATE_ILLEGAL:
        return MEM_EMPTY;
    case IDX_STATE_PARTIALLY_KNOWN:
    case IDX_STATE_FULLY_KNOWN:
        return *loc;
    default:
        __assume(0);
    }
}

static void 
MemCheckAdd(void *retaddr, memptr base, int offset, unsigned size)
{
    // If base isn't really the base, don't assume offset is, 
    // just be sure the memory is valid
    shadowtag baseTag;
    if (base < offset) 
        baseTag = GetAddrTag(base + offset);
    else
        baseTag = GetAddrTag(base);
    
    // Step thru ever byte of the memory and verify that they're all the same
    for (unsigned i = 0; i < size; i++)
    {
        shadowtag newTag = GetAddrTag(base + offset + i);
        if (newTag != baseTag || newTag == MEM_EMPTY)
        {
            _RTC_Failure(retaddr, (newTag == MEM_EMPTY) 
                                  ? _RTC_INVALID_MEM 
                                  : _RTC_DIFF_MEM_BLOCK);
            return;
        }
    }
}


static void 
PtrMemCheckAdd(void *retaddr, memref base, int offset, unsigned size)
{
    if (*base < offset)
    {
        // if *base isn't really the base, just do a MemCheckAdd
        MemCheckAdd(retaddr, *base, offset, size);
        return;
    }
    
    shadowtag baseTag;
    cacheLine cl;
    GetCacheLine(base, cl);

    if (cl.pointer && cl.value == *base)
    {
        baseTag = GetAddrTag(cl.base);
    } else
        baseTag = GetAddrTag(*base);

    for (unsigned i = 0; i < size; i++)
    {
        shadowtag newTag = GetAddrTag(*base + offset + i);
        if (newTag != baseTag || newTag == MEM_EMPTY)
        {
            if (cl.pointer && cl.value == *base && cl.base)
                _RTC_MemFailure(retaddr, 
                                (newTag == MEM_EMPTY) ? _RTC_INVALID_MEM : _RTC_DIFF_MEM_BLOCK,
                                cl.assignment);
            else
                _RTC_Failure(retaddr, 
                             (newTag == MEM_EMPTY) ? _RTC_INVALID_MEM : _RTC_DIFF_MEM_BLOCK);
            return;
        }
    }
}

static void 
PtrMemCheck(void *retaddr, memref base, unsigned size)
{
    shadowtag baseTag = GetAddrTag(*base);
    cacheLine cl;
    GetCacheLine(base, cl);
    if (cl.pointer && cl.value == *base)
        _RTC_MemFailure(retaddr, 
                        (baseTag == MEM_EMPTY) ? _RTC_INVALID_MEM : _RTC_DIFF_MEM_BLOCK,
                        cl.assignment);
    
    else for (unsigned i = 1; i < size; i++)
    {
        shadowtag newTag = GetAddrTag(*base + i);
        if (newTag != baseTag)
        {
            _RTC_Failure(retaddr, (newTag == MEM_EMPTY) ? _RTC_INVALID_MEM : _RTC_DIFF_MEM_BLOCK);
            return;
        }
    }
}

memptr __fastcall 
_RTC_MSPtrPushAdd(memref dstoffset, memref base, int offset)
{
    if (_RTC_shadow)
    {
        memptr src = *base;
        memref dst = dstoffset - 4;

        shadowtag dstTag = GetAddrTag(src + offset);
        shadowtag srcTag = GetAddrTag(src);

        cacheLine cl;
        GetCacheLine(base, cl);
        memptr origBase = src;
    
        if (cl.pointer)
        {
            if (cl.value == src)
            {
                srcTag = GetAddrTag(cl.base);
                origBase = cl.base;
            } else
                ClearCacheLine(base);
        }
        
        if (srcTag != MEM_EMPTY)
        {
            if (dstTag != srcTag)
                AddCacheLine(_ReturnAddress(), dst, origBase, src + offset);
            else
                ClearCacheLine(dst);
        }
    }
    return *base + offset;
}

void __fastcall 
_RTC_MSPtrAssignAdd(memref dst, memref base, int offset)
{
    memptr src = *base;
    *dst = src + offset;
    if (!_RTC_shadow)
        return;

    // First, verify that the address is not in shadow memory
    shadowtag dstTag = GetAddrTag(*dst);
    shadowtag srcTag = GetAddrTag(src);

    cacheLine cl;
    GetCacheLine(base, cl);
    memptr origBase = src;
    
    if (cl.pointer)
    {
        if (cl.value == src)
        {
            srcTag = GetAddrTag(cl.base);
            origBase = cl.base;
        } else
            ClearCacheLine(base);
    }
        
    if (srcTag == MEM_EMPTY)
        return;

    if (dstTag != srcTag)
        AddCacheLine(_ReturnAddress(), dst, origBase, *dst);
    else
        ClearCacheLine(dst);
}

memptr __fastcall 
_RTC_MSPtrAssignR0(memref src)
{
    if (_RTC_shadow)
    {
        cacheLine cl;
        GetCacheLine(src, cl);
        if (cl.pointer)
        {
            if (cl.value == *src)
                AddCacheLine(_ReturnAddress(), REG0, cl.base, *src);
            else
                ClearCacheLine(src);
        }
    }
    return *src;
}

memptr __fastcall 
_RTC_MSPtrAssignR0Add(memref src, int offset)
{
    memptr dst = *src + offset;
    if (_RTC_shadow)
    {
        // First, verify that the address is tolerable 
        shadowtag dstTag = GetAddrTag(dst);
        shadowtag srcTag = GetAddrTag(*src);

        cacheLine cl;
        GetCacheLine(src, cl);
        memptr origBase = *src;

        if (cl.pointer)
        {
            if (cl.value == *src)
            {
                srcTag = GetAddrTag(cl.base);
                origBase = cl.base;
            } else
                ClearCacheLine(src);
        }
        
        if (srcTag != MEM_EMPTY)
        {
            if (dstTag != srcTag)
                AddCacheLine(_ReturnAddress(), REG0, origBase, dst);
            else
                ClearCacheLine(REG0);
        }
    }
    return *src + offset;
}

void __fastcall 
_RTC_MSR0AssignPtr(memref dst, memptr src)
{
    *dst = src;
    if (_RTC_shadow)
    {
        cacheLine cl;
        GetCacheLine(REG0, cl);
        if (cl.pointer)
        {
            if (cl.value == src)
                AddCacheLine(_ReturnAddress(), dst, cl.base, src);
            else
                ClearCacheLine(REG0);
        }
    }
}

void __fastcall 
_RTC_MSR0AssignPtrAdd(memref dst, memptr src, int offset)
{
    *dst = src + offset;
    if (_RTC_shadow)
    {
        shadowtag dstTag = GetAddrTag(*dst);
        shadowtag srcTag = GetAddrTag(src);

        cacheLine cl;
        GetCacheLine(REG0, cl);
        memptr origBase = src;

        if (cl.pointer)
        {
            if (cl.value == src)
            {
                srcTag = GetAddrTag(cl.base);
                origBase = cl.base;
            } else
                ClearCacheLine(REG0);
        }
        
        if (srcTag == MEM_EMPTY)
            return;
        
        if (dstTag != srcTag)
            AddCacheLine(_ReturnAddress(), dst, origBase, *dst);
        else
            ClearCacheLine(dst);
    }
}
    
memptr __fastcall 
_RTC_MSAddrPushAdd(memref dstoffset, memptr base, int offset)
{
    if (_RTC_shadow)
    {
        memref dst = dstoffset - 4;
        // First, verify that the address is not in shadow memory
        shadowtag dstTag = GetAddrTag(base + offset);
        shadowtag srcTag = GetAddrTag(base);

        if (dstTag == MEM_UNKNOWN && 
            (srcTag == MEM_EMPTY || srcTag == MEM_UNKNOWN))
            ClearCacheLine(dst);

        else if (dstTag == MEM_EMPTY ||
                (dstTag == MEM_UNKNOWN && srcTag != MEM_UNKNOWN) ||
                (srcTag == MEM_UNKNOWN && dstTag != MEM_UNKNOWN))
            AddCacheLine(_ReturnAddress(), dst, base, base + offset);
    
        else if (srcTag != MEM_EMPTY)
        {
            if (srcTag != dstTag)
                AddCacheLine(_ReturnAddress(), dst, base, base + offset);
        
            else
                ClearCacheLine(dst);
        }
    }
    return base + offset;
}

void __fastcall 
_RTC_MSAddrAssignAdd(memref dst, memptr base, int offset)
{
    *dst = base + offset;
    if (!_RTC_shadow)
        return;

    // First, verify that the address is not in shadow memory
    shadowtag dstTag = GetAddrTag(*dst);
    shadowtag srcTag = GetAddrTag(base);

    if (dstTag == MEM_UNKNOWN && 
        (srcTag == MEM_EMPTY || srcTag == MEM_UNKNOWN))
        ClearCacheLine(dst);

    else if (dstTag == MEM_EMPTY ||
            (dstTag == MEM_UNKNOWN && srcTag != MEM_UNKNOWN) ||
            (srcTag == MEM_UNKNOWN && dstTag != MEM_UNKNOWN))
        AddCacheLine(_ReturnAddress(), dst, base, *dst);
    
    else if (srcTag == MEM_EMPTY)
        return;

    else if (srcTag != dstTag)
        AddCacheLine(_ReturnAddress(), dst, base, *dst);

    else
        ClearCacheLine(dst);
}

void __fastcall 
_RTC_MSPtrAssign(memref dst, memref src)
{
    *dst = *src;
    if (!_RTC_shadow)
        return;
    cacheLine cl;
    GetCacheLine(src, cl);
    if (cl.pointer)
    {
        if (cl.value == *src)
            AddCacheLine(_ReturnAddress(), dst, cl.base, *src);
        else
            ClearCacheLine(src);
    }
}

memptr __fastcall 
_RTC_MSPtrPush(memref dstoffset, memref src)
{
    if (_RTC_shadow)
    {
        cacheLine cl;
        GetCacheLine(src, cl);
        if (cl.pointer)
        {
            if (cl.value == *src)
                AddCacheLine(_ReturnAddress(), dstoffset - 4, cl.base, *src);
            else
                ClearCacheLine(src);
        }
    }
    return *src;
}

memval1 __fastcall 
_RTC_MSPtrMemReadAdd1(memref base, int offset)
{
    memval1 res;
    __try
    {
        res = *(memval1*)(*base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 1);
    return res;
}

memval2 __fastcall 
_RTC_MSPtrMemReadAdd2(memref base, int offset)
{
    memval2 res;
    __try
    {
        res = *(memval2*)(*base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 2);
    return res;
}

memval4 __fastcall 
_RTC_MSPtrMemReadAdd4(memref base, int offset)
{
    memval4 res;
    __try
    {
        res = *(memval4*)(*base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 4);
    return res;
}

memval8 __fastcall 
_RTC_MSPtrMemReadAdd8(memref base, int offset)
{
    memval8 res;
    __try
    {
        res = *(memval8*)(*base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 8);
    return res;
}

memval1 __fastcall 
_RTC_MSMemReadAdd1(memptr base, int offset)
{
    memval1 res;
    __try
    {
        res = *(memval1*)(base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 1);
    return res;
}

memval2 __fastcall 
_RTC_MSMemReadAdd2(memptr base, int offset)
{
    memval2 res;
    __try
    {
        res = *(memval2*)(base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 2);
    return res;
}

memval4 __fastcall 
_RTC_MSMemReadAdd4(memptr base, int offset)
{
    memval4 res;
    __try
    {
        res = *(memval4*)(base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 4);
    return res;
}

memval8 __fastcall 
_RTC_MSMemReadAdd8(memptr base, int offset)
{
    memval8 res;
    __try
    {
        res = *(memval8*)(base + offset);
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 8);
    return res;
}

memval1 __fastcall 
_RTC_MSPtrMemRead1(memref base)
{
    memval1 res;
    __try
    {
        res = *(memval1*)*base;
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 1);
    return res;
}

memval2 __fastcall 
_RTC_MSPtrMemRead2(memref base)
{
    memval2 res;
    __try
    {
        res = *(memval2*)*base;
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 2);
    return res;
}

memval4 __fastcall 
_RTC_MSPtrMemRead4(memref base)
{
    memval4 res;
    __try
    {
        res = *(memval4*)*base;
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 4);
    return res;
}

memval8 __fastcall 
_RTC_MSPtrMemRead8(memref base)
{
    memval8 res;
    __try
    {
        res = *(memval8*)*base;
    }
    __except(1)
    {
        _RTC_Failure(_ReturnAddress(), _RTC_INVALID_MEM);
    }
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 8);
    return res;
}

memptr  __fastcall 
_RTC_MSPtrMemCheckAdd1(memref base, int offset)
{
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 1);
    return *base + offset;
}

memptr  __fastcall 
_RTC_MSPtrMemCheckAdd2(memref base, int offset)
{
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 2);
    return *base + offset;
}

memptr  __fastcall 
_RTC_MSPtrMemCheckAdd4(memref base, int offset)
{
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 4);
    return *base + offset;
}

memptr  __fastcall 
_RTC_MSPtrMemCheckAdd8(memref base, int offset)
{
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, 8);
    return *base + offset;
}

memptr  __fastcall 
_RTC_MSPtrMemCheckAddN(memref base, int offset, unsigned size)
{
    if (_RTC_shadow)
        PtrMemCheckAdd(_ReturnAddress(), base, offset, size);
    return *base + offset;
}

memptr  __fastcall 
_RTC_MSMemCheckAdd1(memptr base, int offset)
{
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 1);
    return base + offset;
}

memptr  __fastcall 
_RTC_MSMemCheckAdd2(memptr base, int offset)
{
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 2);
    return base + offset;
}

memptr  __fastcall 
_RTC_MSMemCheckAdd4(memptr base, int offset)
{
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 4);
    return base + offset;
}

memptr  __fastcall 
_RTC_MSMemCheckAdd8(memptr base, int offset)
{
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, 8);
    return base + offset;
}

memptr  __fastcall 
_RTC_MSMemCheckAddN(memptr base, int offset, unsigned size)
{
    if (_RTC_shadow)
        MemCheckAdd(_ReturnAddress(), base, offset, size);
    return base + offset;
}

memptr __fastcall
_RTC_MSPtrMemCheck1(memref base)
{
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 1);
    return *base;
}

memptr __fastcall
_RTC_MSPtrMemCheck2(memref base)
{
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 2);
    return *base;
}

memptr __fastcall
_RTC_MSPtrMemCheck4(memref base)
{
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 4);
    return *base;
}

memptr __fastcall
_RTC_MSPtrMemCheck8(memref base)
{
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, 8);
    return *base;
}

memptr __fastcall
_RTC_MSPtrMemCheckN(memref base, unsigned size)
{
    if (_RTC_shadow)
        PtrMemCheck(_ReturnAddress(), base, size);
    return *base;
}

static long enabled = 1;

void __fastcall 
_RTC_CheckMem_API(memref addr, unsigned size)
{
    if (enabled)
        _RTC_MSPtrMemCheckN(addr, size);
}

void __fastcall 
_RTC_APISet(int on_off)
{
    if (on_off)
        InterlockedIncrement(&enabled);
    else
        InterlockedDecrement(&enabled);
}

void _RTC_MS_Init()
{
    _RTC_shadow = (shadowtag *)VirtualAlloc(NULL, MEM_SIZE, MEM_RESERVE, PAGE_READWRITE);
    _RTC_pageidx = (index_elem*)VirtualAlloc(NULL, IDX_SIZE, MEM_COMMIT, PAGE_READWRITE);
    _RTC_MSAllocShadow((memptr)_RTC_pageidx, IDX_SIZE, IDX_STATE_ILLEGAL);
    ClearCache();
}


static void
KillShadow()
{
    // This is called if a call to VirtualAlloc failed - we need to turn off shadow memory
    bool didit = false;
    _RTC_Lock();

    if (_RTC_shadow) 
    {
        VirtualFree(_RTC_shadow, 0, MEM_RELEASE);
        VirtualFree(_RTC_pageidx, 0, MEM_RELEASE);
        _RTC_shadow = 0;
        _RTC_pageidx = 0;

        MEMORY_BASIC_INFORMATION mbi;
        if (VirtualQuery(&_RTC_SetErrorFunc, &mbi, sizeof(mbi)))
            _RTC_NotifyOthersOfChange((void*)mbi.AllocationBase);
        didit = true;
    }
    
    if (didit)
    {
        bool notify = true;
        for (_RTC_Funcs *f = _RTC_globptr->callbacks; f; f = f->next)
            if (f->shadowoff)
                notify = notify && (f->shadowoff()!=0);

        if (notify)
        {
            HMODULE user32 = LoadLibraryExW(L"USER32.DLL", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
#if !defined(_M_ARM)
            if (!user32 && GetLastError() == ERROR_INVALID_PARAMETER)
            {
                // LOAD_LIBRARY_SEARCH_SYSTEM32 is not supported on this platfrom,
                // try one more time using default options
                user32 = LoadLibraryW(L"USER32.DLL");
            }
#endif
            if (!user32)
                return;
            typedef int (*pMsgBoxProc)(HWND,LPCWSTR,LPCWSTR,UINT);
            pMsgBoxProc pMsgBox = (pMsgBoxProc)GetProcAddress(user32, "MessageBoxW");
            if (!pMsgBox)
                return;
            pMsgBox(NULL, L"The Advanced Memory Checking subsystem has run out of virtual memory,"
                          L" and is now disabled. The checks will no longer occur for this process. "
                          L"Try freeing up hard drive space for your swap file.",
                    L"RTC Subsystem Failure",
                    MB_OK | MB_ICONWARNING | MB_DEFBUTTON1 | MB_SETFOREGROUND | MB_TOPMOST);
        }
    }
    _RTC_Unlock();
}

#endif // _RTC_ADVMEM
