1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
|
/* 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.
*
*/
/*
* Memory cache manager
*/
#ifndef GLK_TADS_TADS2_MEMORY_CACHE
#define GLK_TADS_TADS2_MEMORY_CACHE
#include "glk/tads/tads2/lib.h"
#include "glk/tads/tads2/memory_cache_loader.h"
#include "glk/tads/tads2/memory_cache_swap.h"
#include "glk/tads/os_frob_tads.h"
namespace Glk {
namespace TADS {
namespace TADS2 {
/* if the os layer doesn't want long mcm macros, we don't either */
#ifdef OS_MCM_NO_MACRO
# define MCM_NO_MACRO
#endif
/*
* mcmon - cache object number. Each object in the cache is referenced
* by an object number, which is a number of this type.
*/
typedef ushort mcmon;
/* invalid object number - used to indicate N/A or no object */
#define MCMONINV ((mcmon)~0)
/* invalid page number */
#define MCMPINV ((uint)~0)
union mcmodefloc {
mcsseg mcmolocs; /* swap segment handle */
mclhd mcmolocl; /* load file object handle */
};
/*
* mcmodef - cache object definition. Each cache object is managed by
* this structure. Pointers for a doubly-linked list are provided;
* these are used to maintain a recent-use list for objects in memory,
* and to maintain a free list for cache object entries not in use. A
* set of flag bits is provided, as is the size of the object and its
* location (which is a memory pointer for objects in memory, a swap
* file handle for swapped-out objects, or a load-file handle for
* objects that are unloaded).
*/
struct mcmodef {
uchar *mcmoptr; /* memory pointer (if object is present) */
mcmodefloc mcmoloc; /* object's external location */
#define mcmoswh mcmoloc.mcmolocs
#define mcmoldh mcmoloc.mcmolocl
mcmon mcmonxt; /* next object in this list */
mcmon mcmoprv; /* previous object in this list */
ushort mcmoflg; /* flags for this cache object */
#define MCMOFDIRTY 0x01 /* object has been written */
#define MCMOFNODISC 0x02 /* not in load file (can't be discarded) */
#define MCMOFLOCK 0x04 /* object is locked */
#define MCMOFPRES 0x08 /* object is present in memory */
#define MCMOFLRU 0x10 /* object is in LRU chain */
#define MCMOFPAGE 0x20 /* object is a cache manager page */
#define MCMOFNOSWAP 0x40 /* object cannot be swapped out */
#define MCMOFFREE 0x80 /* entry refers to a free block of memory */
#define MCMOFREVRT 0x100 /* call revert callback upon loading */
uchar mcmolcnt; /* lock count */
ushort mcmosiz; /* size of the object */
};
/* heap header - allocate one of these in each heap */
struct mcmhdef {
mcmhdef *mcmhnxt; /* next heap in this chain */
};
/* GLOBAL cache manager context: tracks cache manager state */
struct mcmcx1def {
mcmodef **mcmcxtab; /* page table for the cache */
errcxdef *mcmcxerr; /* error handling context */
mcmhdef *mcmcxhpch; /* heap chain pointer */
mcscxdef mcmcxswc; /* swap manager context */
mclcxdef mcmcxldc; /* loader context */
ulong mcmcxmax; /* maximum amount of actual heap we can ever alloc */
mcmon mcmcxlru; /* least recently used object still in memory */
mcmon mcmcxmru; /* most recently used object */
mcmon mcmcxfre; /* head of free list */
mcmon mcmcxunu; /* head of unused list */
ushort mcmcxpage; /* last page table slot used */
ushort mcmcxpgmx; /* maximum number of pages we can allocate */
void (*mcmcxcsw)(mcmcx1def *, mcmon, mcsseg, mcsseg);
/* change swap handle in object to new swap handle */
};
/* CLIENT cache manager context: used by client to request mcm services */
struct mcmcxdef {
mcmcx1def *mcmcxgl; /* global cache manager context */
uint mcmcxflg; /* flags */
uint mcmcxmsz; /* maximum size of mapping table */
void (*mcmcxldf)(void *ctx, mclhd handle, uchar *ptr,
ushort siz); /* callback to load objects */
void *mcmcxldc; /* context for load callback */
void (*mcmcxrvf)(void *ctx, mcmon objn); /* revert object */
void *mcmcxrvc; /* context for revert callback */
mcmon *mcmcxmtb[1]; /* mapping table */
};
/* context flags */
#define MCMCXF_NO_PRP_DEL 0x0001 /* PRPFDEL is invalid in this game file */
/* convert from a client object number to a global object number */
/* mcmon mcmc2g(mcmcxdef *ctx, mcmon objn); */
#define mcmc2g(ctx, objn) ((ctx)->mcmcxmtb[(objn)>>8][(objn)&255])
/*
* FREE LIST: this is a list, headed by context->mcmcxfre and chained
* forward and back by mcmonxt and mcmoprv, consisting of free memory
* blocks. These refer to blocks in the heap that are not used by any
* client objects.
*/
/*
* UNUSED LIST: this is a list, headed by context->mcmcxunu and chained
* forward by mcmonxt (not back, because it's never necessary to take
* anything out of the list except at the head, nor to search the list
* backwards), of unused cache object entries. These entries are not
* associated with any client object or with any heap memory. This list
* is used to get a new cache object header, and deleted cache objects
* are placed onto this list.
*/
/*
* LRU LIST: this is a list of in-memory blocks in ascending order of
* recency of use by the client. Each time a client unlocks a block,
* the block is moved to the most recent position in the list (the end
* of the list). To make it fast to add a new object, we keep a pointer
* to the end of the list as well as to the beginning. The start of the
* list is at context->mcmcxlru, and is the least recently unlocked
* block still in memory. The end of the list is at context->mcmcxmru,
* and is the most recently unlocked block.
*/
/*
* initialize the cache manager, returning a context for cache manager
* operations; a null pointer is returned if insufficient heap memory is
* available for initialization. The 'max' argument specifies the
* maximum amount of actual low-level heap memory that the cache manager
* can ever allocate on behalf of this context (of course, it can
* overcommit the heap through swapping). If 'max' is less than the
* size of a single heap allocation, it is adjusted upwards to that
* minimum.
*/
mcmcx1def *mcmini(ulong max, uint pages, ulong swapsize,
osfildef *swapfp, char *swapfilename, errcxdef *errctx);
/* terminate the cache manager - frees the structure and all cache memory */
void mcmterm(mcmcx1def *ctx);
/* allocate a client context */
mcmcxdef *mcmcini(mcmcx1def *globalctx, uint pages,
void (*loadfn)(void *, mclhd, uchar *, ushort),
void *loadctx,
void (*revertfn)(void *, mcmon), void *revertctx);
/* terminate a client context - frees the structure memory */
void mcmcterm(mcmcxdef *ctx);
/*
* Lock a cache object, bringing it into memory if necessary. Returns
* a pointer to the memory containing the object. A null pointer is
* returned in case of error. The object remains fixed in memory at the
* returned location until unlocked. Locks are stacked; that is, if
* an object is locked twice in succession, it needs to be unlocked
* twice in succession before it is actually unlocked.
*/
#ifdef MCM_NO_MACRO
uchar *mcmlck(mcmcxdef *ctx, mcmon objnum);
#else /* MCM_NO_MACRO */
/* uchar *mcmlck(mcmcxdef *ctx, mcmon objnum); */
#define mcmlck(ctx,num) \
((mcmobje(ctx,num)->mcmoflg & MCMOFPRES ) ? \
((mcmobje(ctx,num)->mcmoflg|=MCMOFLOCK), \
++(mcmobje(ctx,num)->mcmolcnt), mcmobje(ctx,num)->mcmoptr) \
: mcmload(ctx,num))
#endif /* MCM_NO_MACRO */
/*
* Unlock a cache object, allowing it to be moved and swapped.
* Unlocking an object moves it to the end (i.e., most recently used)
* position on the LRU chain, making it the least favorable to swap out
* or discard. This happens at unlock time (rather than lock time)
* because presumably the client has been using the object the entire
* time it was locked. For this reason, and to keep memory unfragmented
* as much as possible, objects should not be kept locked except when
* actually in use. Note that locks nest; if an object is locked three
* times without an intervening unlock, it must be unlocked three times
* in a row. An object can be unlocked even if it's not locked; doing
* so has no effect.
*/
#ifdef MCM_NO_MACRO
void mcmunlck(mcmcxdef *ctx, mcmon objnum);
#else /* MCM_NO_MACRO */
/* void mcmunlck(mcmcxdef *ctx, mcmon objnum); */
#define mcmunlck(ctx,obj) \
((mcmobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
(--(mcmobje(ctx,obj)->mcmolcnt) ? (void)0 : \
((mcmobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), \
mcmuse((ctx)->mcmcxgl,mcmc2g(ctx,obj)))) : (void)0)
#endif /* MCM_NO_MACRO */
/*
* Allocate a new cache object. The new object is locked upon return.
* A pointer to the memory for the new object is returned, and
* the object number is returned at *nump. A null pointer is returned
* if the object cannot be allocated.
*/
/* uchar *mcmalo(mcmcxdef *ctx, ushort siz, mcmon *nump); */
#define mcmalo(ctx, siz, nump) mcmalo0(ctx, siz, nump, MCMONINV, FALSE)
/*
* Reserve space for an object, giving it a particular client object
* number. This doesn't actually allocate any space for the object, but
* just sets it up so that it can be loaded by the client when it's
* needed.
*/
void mcmrsrv(mcmcxdef *ctx, ushort siz, mcmon clinum, mclhd loadhd);
/*
* Allocate a new cache object, and associate it with a particular
* client object number. An error is signalled if the client object
* number is already in use.
*/
/* uchar *mcmalonum(mcmcxdef *ctx, ushort siz, mcmon num); */
#define mcmalonum(ctx, siz, num) mcmalo0(ctx, siz, (mcmon *)0, num, FALSE)
/*
* Reallocate an existing object. The object's size is increased
* or reduced according to newsize. The object is locked if it is
* not already, and the address of the object's memory is returned.
* Note that the object can move when reallocated, even if it was
* locked before the call.
*/
uchar *mcmrealo(mcmcxdef *ctx, mcmon objnum, ushort newsize);
/*
* Touch a cache object (rendering it dirty). When an object is
* written, the client must touch it to ensure that the version in
* memory is not discarded. The cache manager attempts to optimize
* activity by not writing objects that can be reconstructed from the
* load or swap file. Touching the object informs the cache manager
* that the object is different from any version it has in a swap or
* load file.
*/
/* void mcmtch(mcmcxdef *ctx, mcmon objnum); */
#define mcmtch(ctx,obj) \
(mcmobje(ctx,obj)->mcmoflg |= MCMOFDIRTY)
/* was: (mcmobje(ctx,obj)->mcmoflg |= (MCMOFDIRTY | MCMOFNODISC)) */
/* get size of a cache manager object - object need not be locked */
/* ushort mcmobjsiz(mcmcxdef *ctx, mcmon objn); */
#define mcmobjsiz(ctx, objn) (mcmobje(ctx, objn)->mcmosiz)
/* determine if object has ever been touched */
/* int mcmobjdirty(mcmcxdef *ctx, mcmon objn); */
#define mcmobjdirty(ctx, objn) \
(mcmobje(ctx, objn)->mcmoflg & (MCMOFDIRTY | MCMOFNODISC))
/* get object's memory pointer - object must be locked for valid result */
/* uchar *mcmobjptr(mcmcxdef *ctx, mcmon objn); */
#define mcmobjptr(ctx, objn) (mcmobje(ctx, objn)->mcmoptr)
/*
* Free an object. The memory occupied by the object is discarded, and
* the object may no longer be referenced.
*/
void mcmfre(mcmcxdef *ctx, mcmon obj);
/*
* "Revert" an object - convert it back to original state. This
* routine just invokes a client callback to do the actual reversion
* work. The callback is called immediately if the object is already
* present in memory, but is deferred until the object is loaded/swapped
* in if the object is not in memory.
*/
/* void mcmrevert(mcmcxdef *ctx, mcmon objn); */
#define mcmrevert(ctx, objn) \
((mcmobje(ctx, objn)->mcmoflg & MCMOFPRES) ? \
((*(ctx)->mcmcxrvf)((ctx)->mcmcxrvc, objn), DISCARD 0) \
: DISCARD (mcmobje(ctx, objn)->mcmoflg |= MCMOFREVRT))
/* get current size of object cache */
ulong mcmcsiz(mcmcxdef *ctx);
/* change an object's swap handle (used by swapper) */
/* void mcmcsw(mcmcx1def *ctx, ushort objn, mcsseg swapn, mcsseg oldswn); */
#define mcmcsw(ctx, objn, swapn, oldswapn) \
((*(ctx)->mcmcxcsw)(ctx, objn, swapn, oldswapn))
/* ------------------------------- PRIVATE ------------------------------- */
/* Unlock an object by its global handle */
#ifdef MCM_NO_MACRO
void mcmgunlck(mcmcx1def *ctx, mcmon objnum);
#else /* MCM_NO_MACRO */
/* void mcmgunlck(mcmcx1def *ctx, mcmon objnum); */
#define mcmgunlck(ctx,obj) \
((mcmgobje(ctx,obj)->mcmoflg & MCMOFLOCK) ? \
(--(mcmgobje(ctx,obj)->mcmolcnt) ? (void)0 : \
((mcmgobje(ctx,obj)->mcmoflg&=(~MCMOFLOCK)), mcmuse(ctx,obj))) : \
(void)0)
#endif /* MCM_NO_MACRO */
/* real memory allocator; clients use cover macros */
uchar *mcmalo0(mcmcxdef *ctx, ushort siz, mcmon *nump, mcmon clinum,
int noclitrans);
/* free an object by global object number */
void mcmgfre(mcmcx1def *ctx, mcmon obj);
/* "use" an object (move to most-recent position in LRU chain) */
void mcmuse(mcmcx1def *ctx, mcmon n);
/*
* Load or swap in a cache object which is currently unloaded, locking
* it before returning. Returns a pointer to the memory containing
* the object, or a null pointer in case of error. The object
* remains fixed in memory at the returned location until unlocked.
*/
uchar *mcmload(mcmcxdef *ctx, mcmon objnum);
/*
* Size of each chunk of memory we'll request from the heap manager.
* To cut down on wasted memory from the heap manager, we'll always make
* our requests in this large size, then sub-allocate out of these
* chunks.
*/
#define MCMCHUNK 32768
/*
* number of cache entries in a page - make this a power of 2 to keep
* the arithmetic to find a cache object entry simple
*/
#define MCMPAGECNT 256
/*
* size of a page, in bytes
*/
#define MCMPAGESIZE (MCMPAGECNT * sizeof(mcmodef))
/*
* When allocating memory, and we find a free block satisfying the
* request, we will split the free block if doing so would result in
* enough space in the second block. MCMSPLIT specifies the minimum
* size left over that will allow a split to occur.
*/
#define MCMSPLIT 64
/* get an object cache entyr given a GLOBAL object number */
#define mcmgobje(ctx,num) (&((ctx)->mcmcxtab[(num)>>8][(num)&255]))
/* get an object cache entry given a CLIENT object number */
/* mcmodef *mcmobje(mcmcxdef *ctx, mcmon objnum) */
#define mcmobje(ctx,num) mcmgobje((ctx)->mcmcxgl,mcmc2g(ctx,num))
/* allocate a block that will be locked for its entire lifetime */
void *mcmptralo(mcmcxdef *ctx, ushort siz);
/* free a block allocated with mcmptralo */
void mcmptrfre(mcmcxdef *ctx, void *block);
/* change an object's swap handle */
void mcmcswf(mcmcx1def *ctx, mcmon objn, mcsseg swapn, mcsseg oldswapn);
} // End of namespace TADS2
} // End of namespace TADS
} // End of namespace Glk
#endif
|