Microsoft-3D-Movie-Maker/kauai/SRC/UTILMEM.CPP

594 lines
14 KiB
C++

/* Copyright (c) Microsoft Corporation.
Licensed under the MIT License. */
/***************************************************************************
Author: ShonK
Project: Kauai
Reviewed:
Copyright (c) Microsoft Corporation
Shared (between Mac and Win) memory allocation routines.
The APIs in this module implement fixed block (non-moveable,
non-resizeable) memory management. On win, we use GlobalAlloc; on Mac
we use ::operator new. The win hq is based on FAllocPv. The mac
hq is a Mac handle. _FResizePpv is win only (needed for
resizing HQs) and considered private to the memory management code
(if the implementation of Win HQs changes, it will go away).
***************************************************************************/
#include "util.h"
ASSERTNAME
#ifdef DEBUG
const long kclwStackMbh = 5;
//memory block header
struct MBH
{
long cb; // size of block, including header and footer
PSZS pszsFile; // source file that allocation request is coming from
long lwLine; // line in file that allocation request is coming from
long lwThread; // thread id
MBH *pmbhPrev; // previous allocated block (in doubly linked list)
MBH *pmbhNext; // next allocated block
long rglwStack[kclwStackMbh]; // the EBP/A6 chain
short cactRef; // for marking memory
short swMagic; // magic number, to detect memory trashing
};
//memory block footer
struct MBF
{
short swMagic; // magic number, to detect memory trashing
};
MBH *_pmbhFirst; // head of the doubly linked list
priv void _LinkMbh(MBH *pmbh);
priv void _UnlinkMbh(MBH *pmbh, MBH *pmbhOld);
priv void _AssertMbh(MBH *pmbh);
#endif //DEBUG
#ifdef MAC
#define malloc(cb) ::operator new(cb)
#define free(pv) delete (pv)
#endif //MAC
#ifdef WIN
#define malloc(cb) (void *)GlobalAlloc(GMEM_FIXED, cb)
#define free(pv) GlobalFree((HGLOBAL)pv)
#define _msize(pv) GlobalSize((HGLOBAL)pv)
#define realloc(pv, cb) (void *)GlobalReAlloc((HGLOBAL)pv, cb, GMEM_MOVEABLE)
#endif //WIN
PFNLIB vpfnlib = pvNil;
bool _fInLiberator = fFalse;
#ifdef DEBUG
/***************************************************************************
Do simulated failure testing.
***************************************************************************/
bool DMAGL::FFail(void)
{
bool fRet = fFalse;
vmutxMem.Enter();
if (cactFail > 0)
{
if (cactDo <= 0)
{
cactFail--;
fRet = fTrue;
}
else
cactDo--;
}
vmutxMem.Leave();
return fRet;
}
/***************************************************************************
Update values after an allocation
***************************************************************************/
void DMAGL::Allocate(long cbT)
{
vmutxMem.Enter();
if (cvRun < ++cv)
cvRun = cv;
cvTot++;
if (cbRun < (cb += cbT))
cbRun = cb;
vmutxMem.Leave();
}
/***************************************************************************
Update values after a resize
***************************************************************************/
void DMAGL::Resize(long dcb)
{
vmutxMem.Enter();
if (cbRun < (cb += dcb))
cbRun = cb;
vmutxMem.Leave();
}
/***************************************************************************
Update values after a block is freed
***************************************************************************/
void DMAGL::Free(long cbT)
{
--cv;
cb -= cbT;
}
#endif //DEBUG
/***************************************************************************
Allocates a fixed block.
***************************************************************************/
#ifdef DEBUG
bool FAllocPvDebug(void **ppv, long cb, ulong grfmem, long mpr,
PSZS pszsFile, long lwLine, DMAGL *pdmagl)
#else //!DEBUG
bool FAllocPv(void **ppv, long cb, ulong grfmem, long mpr)
#endif //!DEBUG
{
AssertVarMem(ppv);
AssertIn(cb, 0, kcbMax);
long cbFree;
if (cb > kcbMax)
{
BugVar("who's allocating a humongous block?", &cb);
goto LFail;
}
#ifdef DEBUG
// do simulated failure
if (pdmagl->FFail())
goto LFail;
vmutxMem.Enter();
if (pvNil != _pmbhFirst)
_AssertMbh(_pmbhFirst);
vmutxMem.Leave();
Assert(cb + size(MBH) + size(MBF) > cb, 0);
cb += size(MBH) + size(MBF);
#endif //DEBUG
for (;;)
{
*ppv = malloc(cb);
if (pvNil != *ppv || pvNil == vpfnlib)
break;
vmutxMem.Enter();
if (_fInLiberator)
cbFree = 0;
else
{
_fInLiberator = fTrue;
vmutxMem.Leave();
cbFree = (*vpfnlib)(cb, mpr);
vmutxMem.Enter();
_fInLiberator = fFalse;
}
vmutxMem.Leave();
if (cbFree <= 0)
break;
}
if (pvNil == *ppv)
{
LFail:
*ppv = pvNil;
PushErc(ercOomPv);
return fFalse;
}
if (grfmem & fmemClear)
ClearPb(*ppv, cb);
#ifdef DEBUG
else
FillPb(*ppv, cb, kbGarbage);
// fill in the header
MBH *pmbh = (MBH *)*ppv;
*ppv = pmbh + 1;
pmbh->cb = cb;
pmbh->swMagic = kswMagicMem;
pmbh->pszsFile = pszsFile;
pmbh->lwLine = lwLine;
pmbh->lwThread = LwThreadCur();
#ifdef WIN
// follow the EBP chain....
long *plw;
long ilw;
__asm { mov plw,ebp }
for (ilw = 0; ilw < kclwStackMbh; ilw++)
{
if (pvNil == plw || IsBadReadPtr(plw, 2 * size(long)) ||
*plw <= (long)plw)
{
pmbh->rglwStack[ilw] = 0;
plw = pvNil;
}
else
{
pmbh->rglwStack[ilw] = plw[1];
plw = (long *)*plw;
}
}
#endif //WIN
// write the footer
MBF mbf;
mbf.swMagic = kswMagicMem;
CopyPb(&mbf, PvAddBv(pmbh, cb - size(MBF)), size(MBF));
// link the block
_LinkMbh(pmbh);
// update statistics
pdmagl->Allocate(cb - size(MBF) - size(MBH));
AssertPvAlloced(*ppv, cb - size(MBF) - size(MBH));
#endif //DEBUG
return fTrue;
}
#ifdef WIN
/***************************************************************************
Resizes the given block. *ppv may change. If fmemClear, clears any
newly added space.
***************************************************************************/
#ifdef DEBUG
bool _FResizePpvDebug(void **ppv, long cbNew, long cbOld,
ulong grfmem, long mpr, DMAGL *pdmagl)
#else //!DEBUG
bool _FResizePpv(void **ppv, long cbNew, long cbOld, ulong grfmem, long mpr)
#endif //!DEBUG
{
AssertVarMem(ppv);
AssertIn(cbNew, 0, kcbMax);
AssertIn(cbOld, 0, kcbMax);
AssertPvAlloced(*ppv, cbOld);
long cbFree;
void *pvNew, *pvOld;
#ifdef DEBUG
MBH *pmbh = (MBH *)PvSubBv(*ppv, size(MBH));
_AssertMbh(pmbh);
#endif //DEBUG
if (cbNew > kcbMax)
{
BugVar("who's resizing a humongous block?", &cbNew);
goto LFail;
}
pvOld = *ppv;
#ifdef DEBUG
// do simulated failure - we can only fail if the block is growing
if (cbNew > cbOld && pdmagl->FFail())
goto LFail;
// assert we don't overflow (the limit of kcbMax should ensure this)
Assert(cbOld + size(MBH) + size(MBF) > cbOld, 0);
Assert(cbNew + size(MBH) + size(MBF) > cbNew, 0);
cbOld += size(MBH) + size(MBF);
cbNew += size(MBH) + size(MBF);
AssertVar(pmbh->cb == cbOld, "bad cbOld value passed to _FResizePpv", &cbOld);
// trash the old stuff
if (cbOld > cbNew)
FillPb(PvAddBv(pmbh, cbNew), cbOld - cbNew, kbGarbage);
pvOld = pmbh;
#endif //DEBUG
for (;;)
{
pvNew = realloc(pvOld, cbNew);
if (pvNil != pvNew || pvNil == vpfnlib)
break;
vmutxMem.Enter();
if (_fInLiberator)
cbFree = 0;
else
{
_fInLiberator = fTrue;
vmutxMem.Leave();
cbFree = (*vpfnlib)(cbNew - cbOld, mpr);
vmutxMem.Enter();
_fInLiberator = fFalse;
}
vmutxMem.Leave();
if (cbFree <= 0)
break;
}
if (pvNil == pvNew)
{
Assert(cbOld < cbNew, "why did shrinking fail?");
LFail:
AssertPvAlloced(*ppv, cbOld - size(MBH) - size(MBF));
PushErc(ercOomPv);
return fFalse;
}
*ppv = pvNew;
if ((grfmem & fmemClear) && cbOld < cbNew)
{
// Clear the new stuff
ClearPb(PvAddBv(pvNew, cbOld), cbNew - cbOld);
}
#ifdef DEBUG
if ((grfmem & fmemClear) && cbOld < cbNew)
ClearPb(PvAddBv(pvNew, cbOld - size(MBF)), size(MBF));
else if (cbOld < cbNew)
{
// fill the new stuff with garbage
FillPb(PvAddBv(pvNew, cbOld - size(MBF)), cbNew - cbOld + size(MBF),
kbGarbage);
}
// update the header
if (pvNew != pmbh)
{
_UnlinkMbh((MBH *)pvNew, pmbh);
pmbh = (MBH *)pvNew;
_LinkMbh(pmbh);
}
*ppv = pmbh + 1;
pmbh->cb = cbNew;
// write the footer
MBF mbf;
mbf.swMagic = kswMagicMem;
CopyPb(&mbf, PvAddBv(pmbh, cbNew - size(MBF)), size(MBF));
AssertPvAlloced(*ppv, cbNew - size(MBF) - size(MBH));
// update statistics
pdmagl->Resize(cbNew - cbOld);
#endif //DEBUG
return fTrue;
}
#endif //WIN
/***************************************************************************
If *ppv is not nil, frees it and sets *ppv to nil.
***************************************************************************/
#ifdef DEBUG
void FreePpvDebug(void **ppv, DMAGL *pdmagl)
#else //!DEBUG
void FreePpv(void **ppv)
#endif //!DEBUG
{
AssertVarMem(ppv);
if (*ppv == pvNil)
return;
#ifdef DEBUG
MBH *pmbh = (MBH *)PvSubBv(*ppv, size(MBH));
_AssertMbh(pmbh);
_UnlinkMbh(pmbh, pmbh);
// update statistics
pdmagl->Free(pmbh->cb - size(MBF) - size(MBH));
// fill the block with garbage before freeing it
FillPb(pmbh, pmbh->cb, kbGarbage);
*ppv = pmbh;
#endif //DEBUG
free(*ppv);
*ppv = pvNil;
}
#ifdef DEBUG
/***************************************************************************
Link the Mbh into the debug-only doubly linked list.
***************************************************************************/
priv void _LinkMbh(MBH *pmbh)
{
AssertVarMem(pmbh);
vmutxMem.Enter();
pmbh->pmbhPrev = pvNil;
pmbh->pmbhNext = _pmbhFirst;
if (_pmbhFirst != pvNil)
{
Assert(_pmbhFirst->pmbhPrev == pvNil, "_pmbhFirst's prev is not nil");
_pmbhFirst->pmbhPrev = pmbh;
}
_pmbhFirst = pmbh;
vmutxMem.Leave();
}
/***************************************************************************
Unlink the MBH from the debug-only doubly linked list. pmbhOld is the
previous value of the linked block. pmbhOld may not be a valid pointer
now (when mem is resized).
***************************************************************************/
priv void _UnlinkMbh(MBH *pmbh, MBH *pmbhOld)
{
AssertVarMem(pmbh);
Assert(pmbhOld != pvNil, 0);
vmutxMem.Enter();
// update prev's next pointer
if (pvNil == pmbh->pmbhPrev)
{
Assert(_pmbhFirst == pmbhOld, "prev is wrongly nil");
_pmbhFirst = pmbh->pmbhNext;
}
else
{
Assert(_pmbhFirst != pmbhOld, "prev should be nil");
Assert(pmbh->pmbhPrev->pmbhNext == pmbhOld, "prev's next wrong");
pmbh->pmbhPrev->pmbhNext = pmbh->pmbhNext;
}
// update next's prev pointer
if (pvNil != pmbh->pmbhNext)
{
Assert(pmbh->pmbhNext->pmbhPrev == pmbhOld, "next's prev wrong");
pmbh->pmbhNext->pmbhPrev = pmbh->pmbhPrev;
}
vmutxMem.Leave();
}
/***************************************************************************
Validate the MBH and the block that it is the head of.
***************************************************************************/
void _AssertMbh(MBH *pmbh)
{
short sw;
if (vcactSuspendCheckPointers != 0)
return;
vmutxMem.Enter();
AssertVarMem(pmbh);
Assert(pmbh->swMagic == kswMagicMem, "bad magic number");
AssertIn(pmbh->cb - size(MBH) - size(MBF), 0, kcbMax);
Win( Assert(pmbh->cb <= (long)_msize(pmbh), "bigger than malloced block"); )
AssertPvCb(pmbh, pmbh->cb);
if (pmbh->pmbhPrev != pvNil)
{
AssertVarMem(pmbh->pmbhPrev);
Assert(pmbh->pmbhPrev->pmbhNext == pmbh, "wrong next in prev");
Assert(pmbh != _pmbhFirst, "first has prev!");
}
if (pmbh->pmbhNext != pvNil)
{
AssertVarMem(pmbh->pmbhNext);
Assert(pmbh->pmbhNext->pmbhPrev == pmbh, "wrong prev in next");
}
((byte *)&sw)[0] = *(byte *)PvAddBv(pmbh, pmbh->cb - 2);
((byte *)&sw)[1] = *(byte *)PvAddBv(pmbh, pmbh->cb - 1);
Assert(sw == kswMagicMem, "bad tail magic number");
vmutxMem.Leave();
}
/***************************************************************************
Assert the validity of an allocated fixed block. Cb is the size.
If cb is unknown, pass cvNil.
***************************************************************************/
void AssertPvAlloced(void *pv, long cb)
{
if (vcactSuspendCheckPointers != 0)
return;
Assert(pv != pvNil, "nil pv");
MBH *pmbh = (MBH *)PvSubBv(pv, size(MBH));
_AssertMbh(pmbh);
if (cb != cvNil)
Assert(pmbh->cb == cb + size(MBH) + size(MBF), "wrong cb");
}
/***************************************************************************
Asserts on unmarked blocks.
***************************************************************************/
void AssertUnmarkedMem(void)
{
MBH *pmbh;
long lwThread = LwThreadCur();
// enter the critical section
vmutxMem.Enter();
for (pmbh = _pmbhFirst; pmbh != pvNil; pmbh = pmbh->pmbhNext)
{
_AssertMbh(pmbh);
if (pmbh->cactRef == 0 && pmbh->lwThread == lwThread)
{
STN stn;
SZS szs;
stn.FFormatSz(
PszLit("\nLost block: size=%d, StackTrace=(use map file)"),
pmbh->cb);
stn.GetSzs(szs);
if (FAssertProc(pmbh->pszsFile, pmbh->lwLine, szs,
pmbh->rglwStack, kclwStackMbh * size(long)))
{
Debugger();
}
}
}
Mac( _AssertUnmarkedHqs(); )
// leave the critical section
vmutxMem.Leave();
}
/***************************************************************************
Clears all marks on memory blocks.
***************************************************************************/
void UnmarkAllMem(void)
{
MBH *pmbh;
long lwThread = LwThreadCur();
// enter the critical section
vmutxMem.Enter();
for (pmbh = _pmbhFirst; pmbh != pvNil; pmbh = pmbh->pmbhNext)
{
_AssertMbh(pmbh);
if (pmbh->lwThread == lwThread)
pmbh->cactRef = 0;
}
Mac( _UnmarkAllHqs(); )
// leave the critical section
vmutxMem.Leave();
}
/***************************************************************************
Increment the ref count on an allocated pv.
***************************************************************************/
void MarkPv(void *pv)
{
if (pvNil != pv)
{
AssertPvAlloced(pv, cvNil);
MBH *pmbh = (MBH *)PvSubBv(pv, size(MBH));
pmbh->cactRef++;
}
}
#endif //DEBUG