/* Copyright (c) Microsoft Corporation. Licensed under the MIT License. */ /*************************************************************************** Author: ShonK Project: Kauai Reviewed: Copyright (c) Microsoft Corporation Macintosh memory management. ***************************************************************************/ #include "util.h" ASSERTNAME // REVIEW shonk: Mac: implement HQ statistics // HQ header - actually goes at the end of the hq. struct HQH { #ifdef DEBUG short swMagic; // to detect memory trashing short cactRef; // for marking memory schar *pszsFile; // source file that allocation request is coming from long lwLine; // line in file that allocation request is coming from HQ hqPrev; // previous hq in doubly linked list HQ hqNext; // next hq in doubly linked list #endif //DEBUG byte cactLock; // lock count byte cbExtra; // count of extra bytes }; #ifdef DEBUG HQ _hqFirst; // head of the doubly linked list long vcactSuspendCheckPointers = 0; #endif //DEBUG inline void *_QvFromHq(HQ hq) { return vadst.PvStrip(*(void **)hq); } inline HQH *_QhqhFromHq(HQ hq) { return (HQH *)PvAddBv(vadst.PvStrip(*(void **)hq), GetHandleSize((HN)hq) - size(HQH)); } inline HQH *_QhqhFromHqBv(HQ hq, long bv) { return (HQH *)PvAddBv(vadst.PvStrip(*(void **)hq), bv); } long __pascal _CbFreeStuff(long cb); ADST vadst; /*************************************************************************** This is the GrowZone proc (a call back from the Mac OS memory manager). If _fInAlloc is true, we don't do anything, cuz the code that's doing the allocation will free stuff and try again. If _fInAlloc is false, it probably means that the Toolbox is allocating something, in which case, the priority is critical. REVIEW shonk: should we just free an emergency buffer and return? ***************************************************************************/ long __pascal _CbFreeStuff(long cb) { if (_fInAlloc || pvNil == vpfnlib) return 0; _fInAlloc = fTrue; cb = (*vpfnlib)(cb, mprCritical); _fInAlloc = fFalse; return cb; } /*************************************************************************** Constructor for the address stripper - overloaded to also set the grow-zone proc. ***************************************************************************/ ADST::ADST(void) { _lwMaskAddress = (long)StripAddress((void *)0xFFFFFFFFL); SetGrowZone(&_CbFreeStuff); } /*************************************************************************** Allocates a new moveable block. ***************************************************************************/ #ifdef DEBUG bool FAllocHqDebug(HQ *phq, long cb, ulong grfmem, long mpr, schar *pszsFile, long lwLine) #else //!DEBUG bool FAllocHq(HQ *phq, long cb, ulong grfmem, long mpr) #endif //!DEBUG { AssertVarMem(phq); AssertIn(cb, 0, kcbMax); HQH hqh; if (_fInAlloc) { Bug("recursing in FAllocHq"); goto LFail; } #ifdef DEBUG if (_hqFirst != hqNil) AssertHq(_hqFirst); #endif //DEBUG if (cb > kcbMax) { BugVar("who's allocating a humongous block?", &cb); goto LFail; } hqh.cbExtra = (-cb) & 3; Assert((cb + hqh.cbExtra) % 4 == 0, 0); // assert we don't overflow (the limit of kcbMax should ensure this) Assert(cb + size(HQH) + hqh.cbExtra > cb, 0); cb += hqh.cbExtra + size(HQH); _fInAlloc = fTrue; do { *phq = (grfmem & fmemClear) ? NewHandleClear(cb) : NewHandle(cb); } while (hqNil == *phq && vpfnlib != pvNil && (*vpfnlib)(cb, mpr) > 0); _fInAlloc = fFalse; if (hqNil == *phq) { LFail: *phq = hqNil; PushErc(ercOomHq); return fFalse; } #ifdef DEBUG if (!(grfmem & fmemClear)) FillPb(_QvFromHq(*phq), cb, kbGarbage); hqh.swMagic = kswMagicMem; hqh.pszsFile = pszsFile; hqh.lwLine = lwLine; hqh.hqPrev = hqNil; hqh.hqNext = _hqFirst; if (_hqFirst != hqNil) { Assert(_QhqhFromHq(_hqFirst)->hqPrev == hqNil, "_hqFirst's prev is not nil"); _QhqhFromHq(_hqFirst)->hqPrev = *phq; } _hqFirst = *phq; #endif //DEBUG hqh.cactLock = 0; *_QhqhFromHqBv(*phq, cb - size(HQH)) = hqh; AssertHq(*phq); return fTrue; } /*************************************************************************** Resizes the given hq. *phq may change (on Windows). If fhqClear, clears any newly added space. ***************************************************************************/ bool FResizePhq(HQ *phq, long cb, ulong grfmem, long mpr) { AssertVarMem(phq); AssertHq(*phq); AssertIn(cb, 0, kcbMax); HQH hqh; long cbOld; bool fInAllocSave; short err; if (cb > kcbMax) { BugVar("who's resizing a humongous block?", &cb); goto LFail; } cbOld = GetHandleSize((HN)*phq) - size(HQH); if (_fInAlloc && cb > cbOld) { Bug("recursing in FResizePhq"); goto LFail; } hqh = *_QhqhFromHqBv(*phq, cbOld); if (hqh.cactLock != 0) { Bug("Resizing locked HQ"); goto LFail; } #ifdef DEBUG // trash the old stuff if (cbOld > cb) FillPb(PvAddBv(_QvFromHq(*phq), cb), cbOld + size(HQH) - cb, kbGarbage); #endif cbOld -= hqh.cbExtra; hqh.cbExtra = (-cb) & 3; Assert((cb + hqh.cbExtra) % 4 == 0, 0); // assert we don't overflow (the limit of kcbMax should ensure this) Assert(cb + size(HQH) + hqh.cbExtra > cb, 0); fInAllocSave = _fInAlloc; _fInAlloc = fTrue; do { err = ErrSetHandleSize((HN)*phq, cb + hqh.cbExtra + size(HQH)); } while (err != noErr && cb > cbOld && vpfnlib != pvNil && (*vpfnlib)(cb - cbOld, mpr) > 0); _fInAlloc = fInAllocSave; if (err != noErr) { LFail: AssertHq(*phq); PushErc(ercOomHq); return fFalse; } if (cbOld < cb) { if (grfmem & fmemClear) ClearPb(PvAddBv(_QvFromHq(*phq), cbOld), cb - cbOld); #ifdef DEBUG else // trash the new stuff FillPb(PvAddBv(_QvFromHq(*phq), cbOld), cb - cbOld, kbGarbage); // trash the rest of the block FillPb(PvAddBv(_QvFromHq(*phq), cb), hqh.cbExtra + size(HQH), kbGarbage); #endif //DEBUG } // put the HQH where it belongs *_QhqhFromHqBv(*phq, cb + hqh.cbExtra) = hqh; AssertHq(*phq); return fTrue; } /*************************************************************************** If hq is not nil, frees it. ***************************************************************************/ void FreePhq(HQ *phq) { AssertVarMem(phq); if (*phq == hqNil) return; #ifdef DEBUG AssertHq(*phq); HQH hqh; hqh = *_QhqhFromHq(*phq); Assert(hqh.cactLock == 0, "Freeing locked HQ"); // update prev's next pointer if (hqh.hqPrev == hqNil) { Assert(_hqFirst == *phq, "prev is wrongly nil"); _hqFirst = hqh.hqNext; } else { AssertHq(hqh.hqPrev); Assert(_hqFirst != *phq, "prev should be nil"); _QhqhFromHq(hqh.hqPrev)->hqNext = hqh.hqNext; } // update next's prev pointer if (hqh.hqNext != hqNil) { AssertHq(hqh.hqNext); _QhqhFromHq(hqh.hqNext)->hqPrev = hqh.hqPrev; } // fill the block with garbage FillPb(_QvFromHq(*phq), GetHandleSize((HN)*phq), kbGarbage); #endif //DEBUG DisposHandle((HN)*phq); *phq = hqNil; } /*************************************************************************** Return the size of the hq (the client area of the block). ***************************************************************************/ long CbOfHq(HQ hq) { AssertHq(hq); long cbRaw; cbRaw = GetHandleSize((HN)hq) - size(HQH); return cbRaw - _QhqhFromHqBv(hq, cbRaw)->cbExtra; } /*************************************************************************** Copy an hq to a new block. ***************************************************************************/ bool FCopyHq(HQ hqSrc, HQ *phqDst, long mpr) { AssertHq(hqSrc); AssertVarMem(phqDst); HQH *qhqh; short err; if (_fInAlloc) { Bug("recursing in FCopyHq"); goto LFail; } *phqDst = hqSrc; _fInAlloc = fTrue; do { err = HandToHand((HN *)phqDst); } while (err != noErr && vpfnlib != pvNil && (*vpfnlib)(CbOfHq(hqSrc), mpr) > 0); _fInAlloc = fFalse; if (err != noErr) { LFail: *phqDst = hqNil; PushErc(ercOomHq); return fFalse; } qhqh = _QhqhFromHq(*phqDst); #ifdef DEBUG qhqh->swMagic = kswMagicMem; qhqh->pszsFile = __szsFile; qhqh->lwLine = __LINE__; qhqh->hqPrev = hqNil; qhqh->hqNext = _hqFirst; if (_hqFirst != hqNil) { Assert(_QhqhFromHq(_hqFirst)->hqPrev == hqNil, "_hqFirst's prev is not nil"); _QhqhFromHq(_hqFirst)->hqPrev = *phqDst; } _hqFirst = *phqDst; #endif //DEBUG qhqh->cactLock = 0; AssertHq(*phqDst); return fTrue; } #ifdef DEBUG /*************************************************************************** Returns a volatile pointer from an hq. ***************************************************************************/ void *QvFromHq(HQ hq) { AssertHq(hq); return _QvFromHq(hq); } #endif //DEBUG /*************************************************************************** Lock the hq and return a pointer to the data. ***************************************************************************/ void *PvLockHq(HQ hq) { AssertHq(hq); HQH *qhqh; qhqh = _QhqhFromHq(hq); if (qhqh->cactLock++ == 0) HLock((HN)hq); Assert(_QhqhFromHq(hq)->cactLock > 0, "overflow in cactLock"); return _QvFromHq(hq); } /*************************************************************************** Unlock the hq. Asserts and does nothing if the lock count is zero. ***************************************************************************/ void UnlockHq(HQ hq) { AssertHq(hq); HQH *qhqh; qhqh = _QhqhFromHq(hq); Assert(qhqh->cactLock > 0, "hq not locked"); if (qhqh->cactLock > 0) { if (--qhqh->cactLock == 0) HUnlock((HN)hq); } } #ifdef DEBUG /*************************************************************************** Assert that a given hq is valid. ***************************************************************************/ void AssertHq(HQ hq) { static void *_pvMinZone; void *pvLimZone; void *qv; HQH hqh; long cb; short sw; // make sure hq isn't nil or odd if (hq == hqNil || (long(hq) & 1) != 0) { BugVar("hq is nil or odd", &hq); return; } // make sure *hq is not nil or odd if ((qv = _QvFromHq(hq)) == pvNil || (long(qv) & 1) != 0) { BugVar("*hq is nil or odd", &qv); return; } // get the heap limits and make sure the hq is in it if (pvNil == _pvMinZone) _pvMinZone = ApplicZone(); pvLimZone = (void *)LMGetHeapEnd(); if (hq <= _pvMinZone || hq >= pvLimZone) { BugVar("hq not in application heap", &hq); return; } if ((qv = _QvFromHq(hq)) <= _pvMinZone || qv >= pvLimZone) { BugVar("*hq not in the appication heap", &qv); return; } cb = GetHandleSize((HN)hq); AssertVar(cb >= size(HQH), "hq block is too small", &cb); AssertVar(cb <= BvSubPvs(pvLimZone, _QvFromHq(hq)), "hq block runs past end of heap", &cb); // verify the HQH hqh = *_QhqhFromHqBv(hq, cb - size(HQH)); if (hqh.swMagic != kswMagicMem) { BugVar("end of hq block is trashed", &hqh); return; } AssertVar(cb >= size(HQH) + hqh.cbExtra, "cbExtra or cb is wrong", &cb); sw = HGetState((HN)hq); Assert((hqh.cactLock == 0) == !(sw & 0x0080), "lock count is wrong"); Assert((hqh.hqPrev == hqNil) == (hq == _hqFirst), "hqPrev is wrong"); // verify the links if (hqh.hqPrev != hqNil) Assert(_QhqhFromHq(hqh.hqPrev)->hqNext == hq, "hqNext in prev is wrong"); if (hqh.hqNext != hqNil) Assert(_QhqhFromHq(hqh.hqNext)->hqPrev == hq, "hqPrev in next is wrong"); } /*************************************************************************** Increment the ref count on an hq. ***************************************************************************/ void MarkHq(HQ hq) { if (hq != hqNil) { AssertHq(hq); _QhqhFromHq(hq)->cactRef++; } } /*************************************************************************** Asserts on all unmarked HQs. ***************************************************************************/ void _AssertUnmarkedHqs(void) { HQ hq; for (hq = _hqFirst; hq != hqNil; hq = _QhqhFromHq(hq)->hqNext) { AssertHq(hq); if (_QhqhFromHq(hq)->cactRef == 0) { HQH hqh; long cb; achar stz[kcbMaxStz]; cb = CbOfHq(hq); hqh = *_QhqhFromHq(hq); FFormatStzSz(stz, "Lost hq of size %d allocated at line %d of file '%z'", cb, hqh.lwLine, hqh.pszsFile); Bug(PszStz(stz)); } } } /*************************************************************************** Clears all reference counts. ***************************************************************************/ void _UnmarkAllHqs(void) { HQ hq; for (hq = _hqFirst; hq != hqNil; hq = _QhqhFromHq(hq)->hqNext) { AssertHq(hq); _QhqhFromHq(hq)->cactRef = 0; } } /*************************************************************************** Assert on obviously bogus pointers. Assert that [pv, pv+cb) resides in either the app zone or the system zone. If cb is zero, pv can be anything (including nil). ***************************************************************************/ void AssertPvCb(void *pv, long cb) { static long _lwStrip; static void *_pvMinZone, *_pvMinSysZone; void *pvLimZone, *pvLimSysZone; void *pvClean, *pvLim; if (vcactSuspendCheckPointers != 0 || cb == 0) return; if (_pvMinZone == 0) { _pvMinZone = ApplicZone(); _pvMinSysZone = SystemZone(); _lwStrip = (long)StripAddress((void *)-1L); } pvLimZone = (void *)LMGetCurrentA5(); pvLimSysZone = (void *)(*(long *)_pvMinSysZone & _lwStrip); pvClean = (void *)((long)pv & _lwStrip); pvLim = PvAddBv(pvClean, cb); if (pvClean < _pvMinZone || pvLim > pvLimZone) { AssertVar(pvClean >= _pvMinSysZone && pvLim <= pvLimSysZone, "(pv,cb) not in app or sys zone", &pv); } } #endif //DEBUG