/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/tads/tads2/memory_cache.h" #include "glk/tads/tads2/memory_cache_heap.h" #include "glk/tads/tads2/error.h" #include "glk/tads/os_glk.h" namespace Glk { namespace TADS { namespace TADS2 { /* get an unused object cache entry, allocating a new page if needed */ static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *objnum); /* split a (previously free) block into two pieces */ static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz); /* unlink an object from a doubly-linked list */ static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst); /* initialize a cache, return cache context */ /* find free block: find a block from the free pool to satisfy a request */ static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump); /* add page pagenum, initializing entries after firstunu to unused */ static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu); /* link an object into a doubly-linked list at the head of the list */ static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n); /* try to allocate a new chunk from the heap */ static uchar *mcmhalo(mcmcx1def *ctx); /* relocate blocks in a heap */ static uchar *mcmreloc(mcmcx1def *ctx, uchar *start, uchar *end); /* find next free heap block */ static uchar *mcmffh(mcmcx1def *ctx, uchar *p); #ifdef NEVER /* update entry to account for a block relocation */ static void mcmmove(mcmcx1def *ctx, mcmodef *obj, uchar *newaddr); #else /* NEVER */ #define mcmmove(ctx, o, new) ((o)->mcmoptr = (new)) #endif /* NEVER */ /* consolidate two contiguous free blocks into a single block */ static void mcmconsol(mcmcx1def *ctx, uchar *p); /* collect garbage in all heaps */ static void mcmgarb(mcmcx1def *ctx); /* make some room by swapping or discarding objects */ static int mcmswap(mcmcx1def *ctx, ushort siz); /* toss out an object; returns TRUE if successful */ static int mcmtoss(mcmcx1def *ctx, mcmon objnum); /* next heap block, given a heap block (points to header) */ /* uchar *mcmhnxt(mcmcx1def *ctx, uchar *p) */ #define mcmnxh(ctx, p) \ ((p) + osrndsz(sizeof(mcmon)) + mcmgobje(ctx, *(mcmon*)(p))->mcmosiz) #ifdef DEBUG # define MCMCLICTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x02020202) # define MCMGLBCTX(ctx) assert(*(((ulong *)ctx) - 1) == 0x01010101) #else /* DEBUG */ # define MCMCLICTX(ctx) # define MCMGLBCTX(ctx) #endif /* DEBUG */ /* initialize a new client context */ mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages, void (*loadfn)(void *, mclhd, uchar *, ushort), void *loadctx, void (*revertfn)(void *, mcmon), void *revertctx) { mcmcxdef *ret; ushort siz; siz = sizeof(mcmcxdef) + sizeof(mcmon *) * (pages - 1); IF_DEBUG(siz += sizeof(ulong)); ret = (mcmcxdef *)mchalo(globalctx->mcmcxerr, siz, "mcm client context"); IF_DEBUG((*(ulong *)ret = 0x02020202, ret = (mcmcxdef *)((uchar *)ret + sizeof(ulong)))); ret->mcmcxmsz = pages; ret->mcmcxgl = globalctx; ret->mcmcxldf = loadfn; ret->mcmcxldc = loadctx; ret->mcmcxrvf = revertfn; ret->mcmcxrvc = revertctx; ret->mcmcxflg = 0; memset(ret->mcmcxmtb, 0, (size_t)(pages * sizeof(mcmon *))); return(ret); } /* uninitialize a client context */ void mcmcterm(mcmcxdef *ctx) { /* delete the context memory */ mchfre(ctx); } /* initialize a new global context */ mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize, osfildef *swapfp, char *swapfilename, errcxdef *errctx) { mcmcx1def *ctx; /* newly-allocated cache manager context */ uchar *noreg chunk;/* 1st chunk of memory managed by this cache mgr */ mcmodef *obj; /* pointer to a cache object entry */ ushort siz; /* size of current thing being allocated */ ushort rem; /* bytes remaining in chunk */ int err; NOREG((&chunk)) /* make sure 'max' is big enough - must be at least one chunk */ if (max < (ulong)MCMCHUNK) max = (ulong)MCMCHUNK; /* allocate space for control structures from low-level heap */ rem = MCMCHUNK; IF_DEBUG(rem += sizeof(long)); chunk = mchalo(errctx, rem, "mcmini"); IF_DEBUG((*(ulong *)chunk = 0x01010101, chunk += sizeof(ulong), rem -= sizeof(ulong))); ctx = (mcmcx1def *)chunk; /* put context at start of chunk */ /* initialize swapper; clean up if it fails */ ERRBEGIN(errctx) mcsini(&ctx->mcmcxswc, ctx, swapsize, swapfp, swapfilename, errctx); ERRCATCH(errctx, err) mcsclose(&ctx->mcmcxswc); mchfre(chunk); errsig(errctx, err); ERREND(errctx) chunk += sizeof(mcmcx1def); /* rest of chunk is after context */ rem -= sizeof(mcmcx1def); /* remove from remaining size counter */ /* allocate the page table (an array of pointers to pages) */ ctx->mcmcxtab = (mcmodef **)chunk; /* put at bottom of chunk */ siz = pages * sizeof(mcmodef *); /* calcuate size of table */ memset(ctx->mcmcxtab, 0, (size_t)siz); /* clear entire table */ chunk += siz; /* reflect size of table */ rem -= siz; /* take it out of the remaining count */ /* here we begin normal heap marking with object references */ ctx->mcmcxhpch = (mcmhdef *)chunk; /* set start of heap chain */ chunk += sizeof(mcmhdef); rem -= sizeof(mcmhdef); ctx->mcmcxhpch->mcmhnxt = (mcmhdef *)0; /* no next heap in chain yet */ /* allocate the first page */ *(mcmon *)chunk = 0; /* set object number header in chunk */ chunk += osrndsz(sizeof(mcmon)); rem -= osrndsz(sizeof(mcmon)); ctx->mcmcxtab[0] = (mcmodef *)chunk; /* put at bottom of chunk */ memset(ctx->mcmcxtab[0], 0, (size_t)MCMPAGESIZE); chunk += MCMPAGESIZE; /* reflect size of page */ rem -= MCMPAGESIZE; /* take it out of the remainder */ /* set up the first page with an entry for itself */ obj = mcmgobje(ctx, (mcmon)0); /* point to first page entry */ obj->mcmoflg = MCMOFPRES | MCMOFNODISC | MCMOFPAGE | MCMOFNOSWAP; obj->mcmoptr = (uchar *)ctx->mcmcxtab[0]; obj->mcmosiz = MCMPAGESIZE; /* set up the rest of the context */ ctx->mcmcxlru = ctx->mcmcxmru = MCMONINV; /* no mru/lru list yet */ ctx->mcmcxmax = max - (ulong)MCMCHUNK; ctx->mcmcxpage = 1; /* next page slot to be allocated will be #1 */ ctx->mcmcxpgmx = pages; /* max number of pages we can allocate */ ctx->mcmcxerr = errctx; ctx->mcmcxcsw = mcmcswf; /* set up the free list with the remainder of the chunk */ ctx->mcmcxfre = 1; /* we've allocated object 0; obj 1 is free space */ obj = mcmgobje(ctx, ctx->mcmcxfre); /* point to free object entry */ obj->mcmonxt = obj->mcmoprv = MCMONINV; /* end of free list */ obj->mcmoflg = MCMOFFREE; /* mark the free block as such */ *(mcmon *)chunk = ctx->mcmcxfre; /* set free list header */ chunk += osrndsz(sizeof(mcmon)); rem -= osrndsz(sizeof(mcmon)); obj->mcmoptr = chunk; /* rest of chunk */ obj->mcmosiz = rem - osrndsz(sizeof(mcmon)); /* remaining size in chunk */ /* set flag for end of chunk (invalid object header) */ *((mcmon *)(chunk + rem - osrndsz(sizeof(mcmon)))) = MCMONINV; /* set up the unused entry list with the remaining headers in the page */ mcmadpg(ctx, 0, 2); return(ctx); } /* * Uninitialize the cache manager. Frees the memory allocated for the * cache, including the context structure itself. */ void mcmterm(mcmcx1def *ctx) { mcmhdef *cur, *nxt; /* * Free each chunk in the cache block list, *except* the last one. The * last one is special: it's actually the first chunk allocated, since * we build the list in reverse order, and the first chunk pointer * points into the middle of the actual allocation block, since we * sub-allocated the context structure itself and the page table out of * that memory. */ for (cur = ctx->mcmcxhpch ; cur != 0 && cur->mcmhnxt != 0 ; cur = nxt) { /* remember the next chunk, and delete this one */ nxt = cur->mcmhnxt; mchfre(cur); } /* * As described above, the last chunk in the list is the first * allocated, and it points into the middle of the actual allocated * memory block. Luckily, we do have a handy pointer to the start of * the memory block, namely the context pointer - it's the first thing * allocated out of the block, so it's the same as the block pointer. * Freeing the context frees this last/first chunk. */ mchfre(ctx); } /* * Allocate a new object, returning a pointer to its memory. The new * object is locked upon return. The object number for the new object * is returned at *nump. */ static uchar *mcmalo1(mcmcx1def *ctx, ushort siz, mcmon *nump) { mcmon n; mcmodef *o; uchar *chunk; MCMGLBCTX(ctx); /* round size to appropriate multiple */ siz = osrndsz(siz); /* if it's bigger than the chunk size, we can't allocate it */ if (siz > MCMCHUNK) errsig(ctx->mcmcxerr, ERR_BIGOBJ); startover: /* look in the free block chain for a fit to the request */ o = mcmffb(ctx, siz, &n); if (n != MCMONINV) { mcmsplt(ctx, n, siz); /* split the block if necessary */ mcmgobje(ctx, n)->mcmoflg = MCMOFNODISC | MCMOFLOCK | MCMOFPRES; mcmgobje(ctx, n)->mcmolcnt = 1; /* one locker so far */ *nump = n; return(o->mcmoptr); } /* nothing found; we must get space out of the heap if possible */ chunk = mcmhalo(ctx); /* get space from heap */ if (!chunk) goto error; /* can't get any more space from heap */ o = mcmoal(ctx, &n); /* set up cache entry for free space */ if (n == MCMONINV) { mcmhdef *chunk_hdr = ((mcmhdef *)chunk) - 1; ctx->mcmcxhpch = chunk_hdr->mcmhnxt; mchfre(chunk_hdr); goto error; /* any error means we can't allocate the memory */ } *(mcmon *)chunk = n; /* set object header */ chunk += osrndsz(sizeof(mcmon)); o->mcmoptr = chunk; o->mcmosiz = MCMCHUNK - osrndsz(sizeof(mcmon)); o->mcmoflg = MCMOFFREE; mcmlnkhd(ctx, &ctx->mcmcxfre, n); goto startover; /* try again, now that we have some memory */ error: *nump = MCMONINV; return((uchar *)0); } static void mcmcliexp(mcmcxdef *cctx, mcmon clinum) { /* add global number to client mapping table at client number */ if (cctx->mcmcxmtb[clinum >> 8] == (mcmon *)0) { mcmcx1def *ctx = cctx->mcmcxgl; int i; mcmon *p; /* this page is not allocated - allocate it */ p = (mcmon *)mchalo(ctx->mcmcxerr, (256 * sizeof(mcmon)), "client mapping page"); cctx->mcmcxmtb[clinum >> 8] = p; for (i = 0 ; i < 256 ; ++i) *p++ = MCMONINV; } } /* high-level allocate: try, collect garbage, then try again */ uchar *mcmalo0(mcmcxdef *cctx, ushort siz, mcmon *nump, mcmon clinum, int noclitrans) { uchar *ret; mcmcx1def *ctx = cctx->mcmcxgl; /* global context */ mcmon glb; /* global object number allocated */ MCMCLICTX(cctx); MCMGLBCTX(ctx); /* try once */ if ((ret = mcmalo1(ctx, siz, &glb)) != 0) goto done; /* collect some garbage */ mcmgarb(ctx); /* try swapping until we get the memory or have nothing left to swap */ for ( ;; ) { /* try again */ if ((ret = mcmalo1(ctx, siz, &glb)) != 0) goto done; /* nothing left to swap? */ if (!mcmswap(ctx, siz)) break; /* try yet again */ if ((ret = mcmalo1(ctx, siz, &glb)) != 0) goto done; /* collect garbage once again */ mcmgarb(ctx); } /* try again */ if ((ret = mcmalo1(ctx, siz, &glb)) != 0) goto done; /* we have no other way of getting more memory, so signal an error */ errsig(ctx->mcmcxerr, ERR_NOMEM1); NOTREACHEDV(uchar *); done: if (noclitrans) { *nump = glb; return(ret); } /* we have an object - generate client number */ if (clinum == MCMONINV) { /* find a free number */ mcmon **p; uint i; mcmon j = 0; mcmon *q; int found = FALSE; int unused = -1; for (i = 0, p = cctx->mcmcxmtb ; i < cctx->mcmcxmsz ; ++i, ++p) { if (*p) { for (j = 0, q = *p ; j < 256 ; ++j, ++q) { if (*q == MCMONINV) { found = TRUE; break; } } } else if (unused == -1) unused = i; /* note an unused page mapping table */ if (found) break; } if (found) clinum = (i << 8) + j; else if (unused != -1) clinum = (unused << 8); else errsig(ctx->mcmcxerr, ERR_CLIFULL); } /* expand client mapping table if necessary */ mcmcliexp(cctx, clinum); /* make sure the entry isn't already in use */ if (mcmc2g(cctx, clinum) != MCMONINV) errsig(ctx->mcmcxerr, ERR_CLIUSE); cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb; if (nump) *nump = clinum; return(ret); } /* reserve space for an object at a client object number */ void mcmrsrv(mcmcxdef *cctx, ushort siz, mcmon clinum, mclhd loadhd) { mcmcx1def *ctx = cctx->mcmcxgl; /* global context */ mcmon glb; /* global object number allocated */ mcmodef *o; MCMCLICTX(cctx); MCMGLBCTX(ctx); o = mcmoal(ctx, &glb); /* get a new object header */ if (!o) errsig(ctx->mcmcxerr, ERR_NOHDR); /* can't get a new header */ o->mcmoldh = loadhd; o->mcmoflg = 0; o->mcmosiz = siz; mcmcliexp(cctx, clinum); if (mcmc2g(cctx, clinum) != MCMONINV) errsig(ctx->mcmcxerr, ERR_CLIUSE); cctx->mcmcxmtb[clinum >> 8][clinum & 255] = glb; } /* resize an existing object */ uchar *mcmrealo(mcmcxdef *cctx, mcmon cliobj, ushort newsize) { mcmcx1def *ctx = cctx->mcmcxgl; /* global context */ mcmon obj = mcmc2g(cctx, cliobj); mcmodef *o = mcmgobje(ctx, obj); mcmon nxt; mcmodef *nxto; uchar *p; int local_lock; MCMCLICTX(cctx); MCMGLBCTX(ctx); newsize = osrndsz(newsize); /* make sure the object is locked, and note if we locked it */ if ((local_lock = !(o->mcmoflg & MCMOFLOCK)) != 0) (void)mcmlck(cctx, cliobj); ERRBEGIN(ctx->mcmcxerr) if (newsize < o->mcmosiz) mcmsplt(ctx, obj, newsize); /* smaller; just split block */ else { /* see if there's a free block after this block */ p = o->mcmoptr; nxt = *(mcmon *)(p + o->mcmosiz); nxto = (nxt == MCMONINV) ? (mcmodef *)0 : mcmgobje(ctx, nxt); if (nxto && ((nxto->mcmoflg & MCMOFFREE) && nxto->mcmosiz >= newsize - o->mcmosiz)) { /* sanity check - make sure heap and page table agree */ assert(nxto->mcmoptr == p + o->mcmosiz + osrndsz(sizeof(mcmon))); /* annex the free block */ o->mcmosiz += nxto->mcmosiz + osrndsz(sizeof(mcmon)); /* move the free block to the unused list */ mcmunl(ctx, nxt, &ctx->mcmcxfre); nxto->mcmonxt = ctx->mcmcxunu; ctx->mcmcxunu = nxt; nxto->mcmoflg = 0; /* split the newly grown block if necessary */ mcmsplt(ctx, obj, newsize); } else { /* can't annex; allocate new memory and copy */ if (o->mcmolcnt != 1) /* if anyone else has a lock... */ errsig(ctx->mcmcxerr, ERR_REALCK); /* we can't move it */ p = mcmalo0(cctx, newsize, &nxt, MCMONINV, TRUE); if (nxt == MCMONINV) errsig(ctx->mcmcxerr, ERR_NOMEM2); memcpy(p, o->mcmoptr, (size_t)o->mcmosiz); /* adjust the object entries */ nxto = mcmgobje(ctx, nxt); /* get pointer to new entry */ newsize = nxto->mcmosiz; /* get actual size of new block */ nxto->mcmoptr = o->mcmoptr; /* copy current block info to new */ nxto->mcmosiz = o->mcmosiz; o->mcmoptr = p; /* copy new block info to original entry */ o->mcmosiz = newsize; /* now fix up the heap pointers, and free the temp object */ *(mcmon *)(p - osrndsz(sizeof(mcmon))) = obj; *(mcmon *)(nxto->mcmoptr - osrndsz(sizeof(mcmon))) = nxt; mcmgunlck(ctx, nxt); mcmgfre(ctx, nxt); } } ERRCLEAN(ctx->mcmcxerr) /* release our lock, if we had to obtain one */ if (local_lock) mcmunlck(cctx, cliobj); ERRENDCLN(ctx->mcmcxerr) /* return the address of the object */ return(o->mcmoptr); } /* * Free an object by GLOBAL number: move object to free list. */ void mcmgfre(mcmcx1def *ctx, mcmon obj) { mcmodef *o = mcmgobje(ctx, obj); MCMGLBCTX(ctx); /* signal an error if the object is locked */ if (o->mcmolcnt) errsig(ctx->mcmcxerr, ERR_LCKFRE); /* take out of LRU chain if it's in the chain */ if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru); /* put it in the free list */ mcmlnkhd(ctx, &ctx->mcmcxfre, obj); o->mcmoflg = MCMOFFREE; } /* * load and lock an object that has been swapped out or discarded */ uchar *mcmload(mcmcxdef *cctx, mcmon cnum) { mcmcx1def *ctx = cctx->mcmcxgl; mcmodef *o = mcmobje(cctx, cnum); mcmodef *newdef; mcmon newn; mcmon num = mcmc2g(cctx, cnum); MCMCLICTX(cctx); MCMGLBCTX(ctx); /* we first need to obtain some memory for this object */ (void)mcmalo0(cctx, o->mcmosiz, &newn, MCMONINV, TRUE); newdef = mcmgobje(ctx, newn); /* use memory block from our new object */ o->mcmoptr = newdef->mcmoptr; o->mcmosiz = newdef->mcmosiz; /* load or swap the object in */ ERRBEGIN(ctx->mcmcxerr) if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY)) mcsin(&ctx->mcmcxswc, o->mcmoswh, o->mcmoptr, o->mcmosiz); else if (cctx->mcmcxldf) (*cctx->mcmcxldf)(cctx->mcmcxldc, o->mcmoldh, o->mcmoptr, o->mcmosiz); else errsig(ctx->mcmcxerr, ERR_NOLOAD); ERRCLEAN(ctx->mcmcxerr) mcmgunlck(ctx, newn); /* unlock the object */ mcmgfre(ctx, newn); /* don't need new memory after all */ ERRENDCLN(ctx->mcmcxerr) /* unuse the new cache entry we obtained (we just wanted the memory) */ /* @@@ */ *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = num; /* set obj# */ newdef->mcmoflg = 0; /* mark new block as unused */ newdef->mcmonxt = ctx->mcmcxunu; /* link to unused chain */ ctx->mcmcxunu = newn; /* set flags in the newly loaded object and return */ o->mcmoflg |= MCMOFPRES | MCMOFLOCK; /* object is now present in memory */ o->mcmoflg &= ~MCMOFDIRTY; /* not written since last swapped in */ o->mcmoflg |= MCMOFNODISC; /* don't discard once it's been to swap file */ o->mcmolcnt = 1; /* one locker so far */ /* if the object is to be reverted upon loading, revert it now */ if (o->mcmoflg & MCMOFREVRT) { (*cctx->mcmcxrvf)(cctx->mcmcxrvc, cnum); o->mcmoflg &= ~MCMOFREVRT; } return(o->mcmoptr); } /* * Allocate a new object header. This doesn't allocate an object, just * the header for one. */ static mcmodef *mcmoal(mcmcx1def *ctx, mcmon *nump) { mcmodef *ret; uint pagenum; MCMGLBCTX(ctx); /* look first in list of unused headers */ startover: if (ctx->mcmcxunu != MCMONINV) { /* we have something in the unused list; return it */ *nump = ctx->mcmcxunu; ret = mcmgobje(ctx, *nump); ctx->mcmcxunu = ret->mcmonxt; ret->mcmoswh = MCSSEGINV; return(ret); } /* * No unused entries: we must create a new page. To do so, we * simply allocate memory for a new page. Allocate the memory * ourselves, to avoid deadlocking with the allocator (which can * try to get a new entry to satisfy our request for memory). */ if (ctx->mcmcxpage == ctx->mcmcxpgmx) goto error; /* no more pages */ pagenum = ctx->mcmcxpage++; /* get a new page slot */ ctx->mcmcxtab[pagenum] = (mcmodef *)mchalo(ctx->mcmcxerr, MCMPAGESIZE, "mcmoal"); mcmadpg(ctx, pagenum, MCMONINV); goto startover; error: *nump = MCMONINV; return((mcmodef *)0); } /* find free block: find a block from the free pool to satisfy allocation */ static mcmodef *mcmffb(mcmcx1def *ctx, ushort siz, mcmon *nump) { mcmon n; mcmodef *o; mcmon minn; mcmodef *mino; ushort min = 0; MCMGLBCTX(ctx); for (minn = MCMONINV, mino = 0, n = ctx->mcmcxfre ; n != MCMONINV ; n = o->mcmonxt) { o = mcmgobje(ctx, n); if (o->mcmosiz == siz) { /* found exact match - use it immediately */ minn = n; min = siz; mino = o; break; } else if (o->mcmosiz > siz) { /* found something at least as big; is it smallest yet? */ if (minn == MCMONINV || o->mcmosiz < min) { /* yes, best fit so far, use it; but keep looking */ minn = n; mino = o; min = o->mcmosiz; } } } /* if we found something, remove from the free list */ if (minn != MCMONINV) { mcmunl(ctx, minn, &ctx->mcmcxfre); mino->mcmoflg &= ~MCMOFFREE; mino->mcmoswh = MCSSEGINV; } *nump = minn; return mino; } /* * unlink an object header from one of the doubly-linked lists */ static void mcmunl(mcmcx1def *ctx, mcmon n, mcmon *lst) { mcmodef *o = mcmgobje(ctx, n); mcmodef *nxt; mcmodef *prv; MCMGLBCTX(ctx); /* see if this is LRU chain - must deal with MRU pointer if so */ if (lst == &ctx->mcmcxlru) { /* if it's at MRU, set MRU pointer to previous object in list */ if (ctx->mcmcxmru == n) { ctx->mcmcxmru = o->mcmoprv; /* set MRU to previous in chain */ if (ctx->mcmcxmru != MCMONINV) /* set nxt for new MRU */ mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = MCMONINV; else ctx->mcmcxlru = MCMONINV; /* nothing in list; clear LRU */ } o->mcmoflg &= ~MCMOFLRU; } nxt = o->mcmonxt == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmonxt); prv = o->mcmoprv == MCMONINV ? (mcmodef *)0 : mcmgobje(ctx, o->mcmoprv); /* set back link for next object, if there is a next object */ if (nxt) nxt->mcmoprv = o->mcmoprv; /* set forward link for previous object, or head if no previous object */ if (prv) prv->mcmonxt = o->mcmonxt; else *lst = o->mcmonxt; o->mcmonxt = o->mcmoprv = MCMONINV; } /* link an item to the head of a doubly-linked list */ static void mcmlnkhd(mcmcx1def *ctx, mcmon *lst, mcmon n) { MCMGLBCTX(ctx); if (*lst != MCMONINV) mcmgobje(ctx, *lst)->mcmoprv = n; mcmgobje(ctx, n)->mcmonxt = *lst; /* next is previous head of list */ *lst = n; /* make object new head of list */ mcmgobje(ctx, n)->mcmoprv = MCMONINV; /* there is no previous entry */ } /* add page pagenum, initializing entries after firstunu to unused */ static void mcmadpg(mcmcx1def *ctx, uint pagenum, mcmon firstunu) { mcmon unu; mcmodef *obj; mcmon lastunu; MCMGLBCTX(ctx); unu = (firstunu == MCMONINV ? pagenum * MCMPAGECNT : firstunu); ctx->mcmcxunu = unu; lastunu = (pagenum * MCMPAGECNT) + MCMPAGECNT - 1; for (obj = mcmgobje(ctx, unu) ; unu < lastunu ; ++obj) obj->mcmonxt = ++unu; obj->mcmonxt = MCMONINV; } /* * split a previously-free block into two chunks, adding the remainder * back into the free list, if there's enough left over */ static void mcmsplt(mcmcx1def *ctx, mcmon n, ushort siz) { mcmodef *o = mcmgobje(ctx, n); mcmon newn; mcmodef *newp; MCMGLBCTX(ctx); if (o->mcmosiz < siz + MCMSPLIT) return; /* don't split; we're done */ newp = mcmoal(ctx, &newn); if (newn == MCMONINV) return; /* ignore error - just skip split */ /* set up the new entry, and link into free list */ *(mcmon *)(o->mcmoptr + siz) = newn; newp->mcmoptr = o->mcmoptr + siz + osrndsz(sizeof(mcmon)); newp->mcmosiz = o->mcmosiz - siz - osrndsz(sizeof(mcmon)); newp->mcmoflg = MCMOFFREE; mcmlnkhd(ctx, &ctx->mcmcxfre, newn); o->mcmosiz = siz; /* size of new object is now exactly as request */ } /* allocate a new chunk from the heap if possible */ static uchar *mcmhalo(mcmcx1def *ctx) { uchar *chunk; int err; #define size (MCMCHUNK + sizeof(mcmhdef) + 2*osrndsz(sizeof(mcmon))) MCMGLBCTX(ctx); if (ctx->mcmcxmax < MCMCHUNK) return((uchar *)0); ERRBEGIN(ctx->mcmcxerr) chunk = mchalo(ctx->mcmcxerr, size, "mcmhalo"); ERRCATCH(ctx->mcmcxerr, err) ctx->mcmcxmax = 0; /* remember we can't allocate anything more */ return((uchar *)0); /* return no memory */ ERREND(ctx->mcmcxerr) ctx->mcmcxmax -= MCMCHUNK; /* link into heap chain */ ((mcmhdef *)chunk)->mcmhnxt = ctx->mcmcxhpch; ctx->mcmcxhpch = (mcmhdef *)chunk; /*@@@@*/ *(mcmon *)(chunk + osrndsz(sizeof(mcmhdef) + MCMCHUNK)) = MCMONINV; VARUSED(err); return(chunk + sizeof(mcmhdef)); #undef size } /* "use" an object - move to most-recent position in LRU chain */ void mcmuse(mcmcx1def *ctx, mcmon obj) { mcmodef *o = mcmgobje(ctx, obj); MCMGLBCTX(ctx); if (ctx->mcmcxmru == obj) return; /* already MRU; nothing to do */ /* remove from LRU chain if it's in it */ if (o->mcmoflg & MCMOFLRU) mcmunl(ctx, obj, &ctx->mcmcxlru); /* set forward pointer of last block, if there is one */ if (ctx->mcmcxmru != MCMONINV) mcmgobje(ctx, ctx->mcmcxmru)->mcmonxt = obj; o->mcmoprv = ctx->mcmcxmru; /* point back to previous MRU */ o->mcmonxt = MCMONINV; /* nothing in list after this one */ ctx->mcmcxmru = obj; /* point MRU to new block */ /* if there's nothing in the chain at all, set LRU to this block, too */ if (ctx->mcmcxlru == MCMONINV) ctx->mcmcxlru = obj; /* note that object is in LRU chain */ o->mcmoflg |= MCMOFLRU; } /* find next free block in a heap, starting with pointer */ static uchar *mcmffh(mcmcx1def *ctx, uchar *p) { mcmodef *o; MCMGLBCTX(ctx); while (*(mcmon *)p != MCMONINV) { o = mcmgobje(ctx, *(mcmon *)p); assert(o->mcmoptr == p + osrndsz(sizeof(mcmon))); if (o->mcmoflg & MCMOFFREE) return(p); p += osrndsz(sizeof(mcmon)) + o->mcmosiz; /* move on to next chunk */ } return((uchar *)0); /* no more free blocks in heap */ } #ifdef NEVER static void mcmmove(mcmcx1def *ctx, mcmodef *o, uchar *newpage) { mcmodef **page; MCMGLBCTX(ctx); /* see if we need to update page table (we do if moving a page) */ if (o->mcmoflg & MCMOFPAGE) { for (page = ctx->mcmcxtab ; *page ; ++page) { if (*page == (mcmodef *)(o->mcmoptr)) { *page = (mcmodef *)newpag; break; } } if (!*page) printf("\n*** internal error - relocating page\n"); } o->mcmoptr = newpage; } #endif /* NEVER */ /* relocate blocks from p to (but not including) q */ static uchar *mcmreloc(mcmcx1def *ctx, uchar *p, uchar *q) { mcmodef *o; ushort dist; mcmon objnum; MCMGLBCTX(ctx); objnum = *(mcmon *)p; /* get number of free block being bubbled up */ o = mcmgobje(ctx, objnum); /* get pointer to free object */ assert(o->mcmoptr == p + osrndsz(sizeof(mcmon))); dist = osrndsz(sizeof(mcmon)) + o->mcmosiz; /* compute distance to move */ mcmmove(ctx, o, q - dist + osrndsz(sizeof(mcmon))); /* move obj to top */ memmove(p, p+dist, (size_t)(q - p - o->mcmosiz)); /* move memory */ /* update cache entries for the blocks we moved */ while (p != q - dist) { mcmmove(ctx, mcmgobje(ctx, *(mcmon *)p), p + osrndsz(sizeof(mcmon))); p = mcmnxh(ctx, p); } *(mcmon *)(q - dist) = objnum; /* set bubbled num */ return(q - dist); /* return new location of bubbled block */ } /* consolidate the two (free) blocks starting at p into one block */ static void mcmconsol(mcmcx1def *ctx, uchar *p) { uchar *q; mcmodef *obj1, *obj2; MCMGLBCTX(ctx); q = mcmnxh(ctx, p); obj1 = mcmgobje(ctx, *(mcmon *)p); obj2 = mcmgobje(ctx, *(mcmon *)q); assert(obj1->mcmoptr == p + osrndsz(sizeof(mcmon))); assert(obj2->mcmoptr == q + osrndsz(sizeof(mcmon))); obj1->mcmosiz += osrndsz(sizeof(mcmon)) + obj2->mcmosiz; mcmunl(ctx, *(mcmon *)q, &ctx->mcmcxfre); /* add second object entry to unused list */ obj2->mcmonxt = ctx->mcmcxunu; ctx->mcmcxunu = *(mcmon *)q; obj2->mcmoflg = 0; } /* attempt to compact all heaps by consolidating free space */ static void mcmgarb(mcmcx1def *ctx) { mcmhdef *h; uchar *p; uchar *q; uchar *nxt; ushort flags; MCMGLBCTX(ctx); for (h = ctx->mcmcxhpch ; h ; h = h->mcmhnxt) { p = (uchar *)(h+1); /* get pointer to actual heap */ p = mcmffh(ctx, p); /* get first free block in heap */ if (!p) continue; /* can't do anything - no free blocks */ nxt = mcmnxh(ctx, p); /* remember immediate next block */ for (q=p ;; ) { q = mcmnxh(ctx, q); /* find next chunk in heap */ if (*(mcmon *)q == MCMONINV) break; /* reached end of heap */ assert(mcmgobje(ctx, *(mcmon *)q)->mcmoptr == q + osrndsz(sizeof(mcmon))); flags = mcmgobje(ctx, *(mcmon *)q)->mcmoflg; /* get flags */ /* if the block is locked, p can't be relocated */ if (flags & MCMOFLOCK) { p = mcmffh(ctx, q); /* find next free block after p */ q = p; if (p) continue; /* try again; start with next free block */ else break; /* no more free blocks - done with heap */ } /* if the block is free, we can relocate between p and q */ if (flags & MCMOFFREE) { if (q != nxt) p = mcmreloc(ctx, p, q); /* relocate */ mcmconsol(ctx, p); /* consolidate two free blocks */ /* resume looking, starting with consolidated block */ nxt = mcmnxh(ctx, p); q = p; continue; } } } } /* toss out a particular object */ static int mcmtoss(mcmcx1def *ctx, mcmon n) { mcmodef *o = mcmgobje(ctx, n); mcmodef *newp; mcmon newn; MCMGLBCTX(ctx); /* make a new block for the free space */ newp = mcmoal(ctx, &newn); if (newn == MCMONINV) return(FALSE); /* ignore the error, but can't toss it out */ /* write object to swap file if not discardable */ if (o->mcmoflg & (MCMOFNODISC | MCMOFDIRTY)) { mcsseg old_swap_seg; /* * If this object was last loaded out of the load file, rather * than the swap file, don't attempt to find it in the swap file * -- so note by setting the old swap segment parameter to null. */ if (!(o->mcmoflg & MCMOFNODISC)) old_swap_seg = o->mcmoswh; else old_swap_seg = MCSSEGINV; o->mcmoswh = mcsout(&ctx->mcmcxswc, (uint)n, o->mcmoptr, o->mcmosiz, old_swap_seg, o->mcmoflg & MCMOFDIRTY); } /* give the object's space to the newly created block */ newp->mcmoptr = o->mcmoptr; newp->mcmosiz = o->mcmosiz; newp->mcmoflg = MCMOFFREE; /*@@@*/ *(mcmon *)(o->mcmoptr - osrndsz(sizeof(mcmon))) = newn; mcmlnkhd(ctx, &ctx->mcmcxfre, newn); o->mcmoflg &= ~MCMOFPRES; /* object is no longer in memory */ mcmunl(ctx, n, &ctx->mcmcxlru); /* remove from LRU list */ return(TRUE); /* successful, so return TRUE */ } /* swap or discard to make room for siz; return 0 if nothing swapped */ static int mcmswap(mcmcx1def *ctx, ushort siz) { mcmon n; mcmodef *o; mcmon nxt; int pass; /* pass 1: swap one piece big enough */ /* pass 2: swap enough pieces to add up to right size */ ushort tot; MCMGLBCTX(ctx); for (pass = 1, tot = 0 ; pass < 3 && tot < siz ; ++pass) { for (n = ctx->mcmcxlru ; n != MCMONINV && tot < siz ; n = nxt) { o = mcmgobje(ctx, n); nxt = o->mcmonxt; /* get next now, as we may unlink */ if (!(o->mcmoflg & (MCMOFLOCK | MCMOFNOSWAP | MCMOFPAGE)) && (pass == 2 || o->mcmosiz >= siz)) { /* toss out, and add into size if successful */ if (mcmtoss(ctx, n)) tot += o->mcmosiz; } } } /* if we managed to remove anything, return TRUE, otherwise FALSE */ return(tot != 0); } /* compute size of cache */ ulong mcmcsiz(mcmcxdef *cctx) { mcmcx1def *ctx = cctx->mcmcxgl; mcmhdef *p; ulong tot; MCMCLICTX(cctx); MCMGLBCTX(ctx); /* count number of heaps, adding in chunk size for each */ for (tot = 0, p = ctx->mcmcxhpch ; p ; p = p->mcmhnxt) tot += MCMCHUNK; return(tot); } #ifdef MCM_NO_MACRO /* routines that can be either macros or functions */ uchar *mcmlck(mcmcxdef *ctx, mcmon objnum) { mcmodef *o = mcmobje(ctx, objnum); if ((o->mcmoflg & MCMOFFREE) != 0 || mcmc2g(ctx, objnum) == MCMONINV) { errsig(ctx->mcmcxgl->mcmcxerr, ERR_INVOBJ); return 0; } else if (o->mcmoflg & MCMOFPRES) { o->mcmoflg |= MCMOFLOCK; ++(o->mcmolcnt); return(o->mcmoptr); } else return(mcmload(ctx, objnum)); } void mcmunlck(mcmcxdef *ctx, mcmon obj) { mcmodef *o = mcmobje(ctx, obj); if (o->mcmoflg & MCMOFLOCK) { if (!(--(o->mcmolcnt))) { o->mcmoflg &= ~MCMOFLOCK; mcmuse(ctx->mcmcxgl, mcmc2g(ctx, obj)); } } } void mcmgunlck(mcmcx1def *ctx, mcmon obj) { mcmodef *o = mcmgobje(ctx, obj); if (o->mcmoflg & MCMOFLOCK) { if (!(--(o->mcmolcnt))) { o->mcmoflg &= ~MCMOFLOCK; mcmuse(ctx, obj); } } } #endif /* MCM_NO_MACRO */ /* * Change an object's swap file handle. This routine will only be * called for an object that is either present or swapped out (i.e., an * object with a valid mcsseg number in its swap state). */ void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn) { mcmodef *o = mcmgobje(ctx, objn); MCMGLBCTX(ctx); /* * Reset the swap number only if the object is swapped out and its * swap file number matches the old one, or the object is currently * present (in which case the swap file number is irrelevant and can * be replaced). */ if (((o->mcmoflg & (MCMOFDIRTY | MCMOFNODISC)) && o->mcmoswh == oldswapn) || (o->mcmoflg & MCMOFPRES)) o->mcmoswh = swapn; } void mcmfre(mcmcxdef *ctx, mcmon obj) { /* free the actual object */ mcmgfre(ctx->mcmcxgl, mcmc2g(ctx, obj)); /* unmap the client object number */ mcmc2g(ctx, obj) = MCMONINV; } } // End of namespace TADS2 } // End of namespace TADS } // End of namespace Glk