/* eventrep.c: Allocation replayer routines * Copyright (c) 2001 Ravenbrook Limited. See end of file for license. * * $Id$ */ #include "config.h" /* override variety setting for EVENT */ #define EVENT #include "eventcom.h" #include "eventrep.h" #include "eventpro.h" #include "mpmtypes.h" #include "mps.h" #include "mpsavm.h" #include "mpsacl.h" #include "mpscmv.h" #include "mpscmvff.h" #include "mpscepvm.h" #include "fmtpstst.h" #include "mpscepdl.h" #include "table.h" #include /* for size_t */ #include /* for va_list */ #include /* for EXIT_FAILURE */ #include /* for printf */ #include "mpstd.h" #if defined(MPS_OS_W3) && defined(MPS_ARCH_I6) #define PRIuLONGEST "llu" #define PRIXPTR "016llX" typedef unsigned long long ulongest_t; #else #define PRIuLONGEST "lu" #define PRIXPTR "08lX" typedef unsigned long ulongest_t; #endif typedef unsigned long ulong; /* Globals */ static ulong totalEvents; /* count of events */ static ulong discardedEvents; /* count of ignored events */ static ulong unknownEvents; /* count of unknown events */ static Word eventTime; /* Dictionaries for translating from log to replay values */ static Table arenaTable; /* dictionary of arenas */ static Table poolTable; /* dictionary of poolReps */ static Table apTable; /* dictionary of apReps */ /* poolSupport -- describes pool support for explicit deallocation */ enum {supportTruncate = 1, supportFree, supportNothing}; typedef int poolSupport; /* objectTable -- object address mapping structure * * .obj-mapping.truncate: Pools that support truncate need to keep track * of object end points as well. .obj-mapping.partial-free: Arbitrary * partial free is not supported. */ typedef struct objectTableStruct { Table startTable; Table endTable; } objectTableStruct; typedef struct objectTableStruct *objectTable; /* poolRep -- pool tracking structure * * .pool.object-addr: Pools that support explicit free (or truncate) * need to maintain a mapping from the addresses in the log to those in * the replay. * * .bufclass: In order to create APs with the correct arguments, the * replayer has to pick the right BufferInit event to use, as there's * one for each superclass. The pool determines the buffer class, so * we store its subclass level in the pool representation. */ typedef struct poolRepStruct { mps_pool_t pool; /* the replay pool */ objectTable objects; int bufferClassLevel; /* subclass level of the buffer class */ } poolRepStruct; typedef struct poolRepStruct *poolRep; /* apRep -- ap tracking structure */ typedef struct apRepStruct { mps_ap_t ap; /* the replay ap */ objectTable objects; /* object mapping for the pool of this ap */ } apRepStruct; typedef struct apRepStruct *apRep; /* PointerAdd -- add offset to pointer */ #define PointerAdd(p, s) ((void *)((char *)(p) + (s))) #define PointerSub(p, s) ((void *)((char *)(p) - (s))) /* error -- error signalling */ ATTRIBUTE_FORMAT((printf, 1, 2)) static void error(const char *format, ...) { va_list args; fflush(stdout); /* sync */ fprintf(stderr, "Failed @%"PRIuLONGEST" ", (ulongest_t)eventTime); va_start(args, format); vfprintf(stderr, format, args); fprintf(stderr, "\n"); va_end(args); exit(EXIT_FAILURE); } /* verify, verifyMPS -- check return values * * We don't use assert for this, because we want it in release as well. */ #define verifyMPS(res) \ MPS_BEGIN if ((res) != MPS_RES_OK) error("line %d MPS", __LINE__); MPS_END #define verify(cond) \ MPS_BEGIN if (!(cond)) error("line %d " #cond, __LINE__); MPS_END #ifdef MPS_PROD_EPCORE /* ensurePSFormat -- return the PS format, creating it, if necessary */ static mps_fmt_t psFormat = NULL; static void ensurePSFormat(mps_fmt_t *fmtOut, mps_arena_t arena) { mps_res_t eres; if (psFormat == NULL) { eres = mps_fmt_create_A(&psFormat, arena, ps_fmt_A()); verifyMPS(eres); } *fmtOut = psFormat; } /* finishPSFormat -- finish the PS format, if necessary */ static void finishPSFormat(void) { if (psFormat != NULL) mps_fmt_destroy(psFormat); } #endif /* objectTableCreate -- create an objectTable */ static objectTable objectTableCreate(poolSupport support) { if (support != supportNothing) { Res ires; objectTable table; table = malloc(sizeof(objectTableStruct)); verify(table != NULL); ires = TableCreate(&table->startTable, (size_t)1<<12); verify(ires == ResOK); if (support == supportTruncate) { ires = TableCreate(&table->endTable, (size_t)1<<12); verify(ires == ResOK); } else { table->endTable = NULL; } return table; } else { return NULL; } } /* objectTableDestroy -- destroy an objectTable */ static void objectTableDestroy(objectTable table) { if (table != NULL) { TableDestroy(table->startTable); if (table->endTable != NULL) TableDestroy(table->endTable); free(table); } } /* objDefine -- add a new mapping to an objectTable */ static void objDefine(objectTable table, void *logObj, void *obj, size_t size) { if (table != NULL) { Res ires; ires = TableDefine(table->startTable, (Word)logObj, obj); verify(ires == ResOK); if (table->endTable != NULL) { ires = TableDefine(table->endTable, (Word)PointerAdd(logObj, size), PointerAdd(obj, size)); verify(ires == ResOK); } } } /* objRemove -- look up and remove a mapping in an objectTable */ static void objRemove(void **objReturn, objectTable table, void *logObj, size_t size) { Bool found; Res ires; void *obj; void *end; void *logEnd; found = TableLookup(&obj, table->startTable, (Word)logObj); if (found) { ires = TableRemove(table->startTable, (Word)logObj); verify(ires == ResOK); if (table->endTable != NULL) { ires = TableRemove(table->endTable, (Word)PointerAdd(logObj, size)); verify(ires == ResOK); } *objReturn = obj; return; } /* Must be a truncation. */ verify(table->endTable != NULL); logEnd = PointerAdd(logObj, size); found = TableLookup(&end, table->endTable, (Word)logEnd); verify(found); obj = PointerSub(end, size); /* Remove the old end and insert the new one. */ ires = TableRemove(table->endTable, (Word)logEnd); verify(ires == ResOK); ires = TableDefine(table->endTable, (Word)logObj, obj); verify(ires == ResOK); *objReturn = obj; return; } /* poolRecreate -- create and record a pool */ static void poolRecreate(void *logPool, void *logArena, mps_class_t class, poolSupport support, int bufferClassLevel, ...) { va_list args; mps_pool_t pool; mps_res_t eres; poolRep rep; Res ires; void *entry; Bool found; found = TableLookup(&entry, arenaTable, (Word)logArena); verify(found); va_start(args, bufferClassLevel); eres = mps_pool_create_v(&pool, (mps_arena_t)entry, class, args); verifyMPS(eres); va_end(args); rep = malloc(sizeof(poolRepStruct)); verify(rep != NULL); rep->pool = pool; rep->objects = objectTableCreate(support); rep->bufferClassLevel = bufferClassLevel; ires = TableDefine(poolTable, (Word)logPool, (void *)rep); verify(ires == ResOK); } /* poolRedestroy -- destroy and derecord a pool */ static void poolRedestroy(void *logPool) { Res ires; void *entry; Bool found; poolRep rep; found = TableLookup(&entry, poolTable, (Word)logPool); verify(found); rep = (poolRep)entry; mps_pool_destroy(rep->pool); ires = TableRemove(poolTable, (Word)logPool); verify(ires == ResOK); objectTableDestroy(rep->objects); free(rep); } /* apRecreate -- create and record an ap */ static void apRecreate(void *logAp, void *logPool, ...) { va_list args; mps_ap_t ap; poolRep pRep; apRep aRep; mps_res_t eres; Res ires; void *entry; Bool found; found = TableLookup(&entry, poolTable, (Word)logPool); verify(found); pRep = (poolRep)entry; va_start(args, logPool); eres = mps_ap_create_v(&ap, pRep->pool, args); verifyMPS(eres); va_end(args); aRep = malloc(sizeof(apRepStruct)); verify(aRep != NULL); aRep->ap = ap; aRep->objects = pRep->objects; ires = TableDefine(apTable, (Word)logAp, (void *)aRep); verify(ires == ResOK); } /* apRedestroy -- destroy and derecord an ap */ static void apRedestroy(void *logAp) { Res ires; void *entry; Bool found; apRep rep; found = TableLookup(&entry, apTable, (Word)logAp); verify(found); rep = (apRep)entry; mps_ap_destroy(rep->ap); ires = TableRemove(apTable, (Word)logAp); verify(ires == ResOK); free(rep); } /* EventReplay -- replay event */ static arenaJustCreated = FALSE; void EventReplay(Event event, Word etime) { mps_res_t eres; Res ires; Bool found; void *entry; ++totalEvents; eventTime = etime; switch(event->any.code) { case EventArenaCreateVM: { /* arena, userSize, chunkSize */ mps_arena_t arena; eres = mps_arena_create(&arena, mps_arena_class_vm(), event->pww.w1); verifyMPS(eres); ires = TableDefine(arenaTable, (Word)event->pww.p0, (void *)arena); verify(ires == ResOK); arenaJustCreated = TRUE; } break; case EventArenaCreateVMNZ: { /* arena, userSize, chunkSize */ mps_arena_t arena; eres = mps_arena_create(&arena, mps_arena_class_vmnz(), event->pww.w1); verifyMPS(eres); ires = TableDefine(arenaTable, (Word)event->pww.p0, (void *)arena); verify(ires == ResOK); arenaJustCreated = TRUE; } break; case EventArenaCreateCL: { /* arena, size, base */ mps_arena_t arena; void *base; base = malloc((size_t)event->pwa.w1); verify(base != NULL); eres = mps_arena_create(&arena, mps_arena_class_cl(), (Size)event->pwa.w1, base); verifyMPS(eres); ires = TableDefine(arenaTable, (Word)event->pw.p0, (void *)arena); verify(ires == ResOK); arenaJustCreated = TRUE; } break; case EventArenaDestroy: { /* arena */ found = TableLookup(&entry, arenaTable, (Word)event->p.p0); verify(found); #ifdef MPS_PROD_EPCORE /* @@@@ assuming there's only one arena at a time */ finishPSFormat(); #endif mps_arena_destroy((mps_arena_t)entry); ires = TableRemove(arenaTable, (Word)event->pw.p0); verify(ires == ResOK); } break; case EventPoolInitMVFF: { /* pool, arena, extendBy, avgSize, align, slotHigh, arenaHigh, firstFit */ poolRecreate(event->ppwwwuuu.p0, event->ppwwwuuu.p1, mps_class_mvff(), supportFree, 0, (size_t)event->ppwwwuuu.w2, (size_t)event->ppwwwuuu.w3, (size_t)event->ppwwwuuu.w4, (mps_bool_t)event->ppwwwuuu.u5, (mps_bool_t)event->ppwwwuuu.u6, (mps_bool_t)event->ppwwwuuu.u7); } break; case EventPoolInitMV: { /* pool, arena, extendBy, avgSize, maxSize */ /* .pool.control: The control pool will get created just after */ /* its arena; ignore it. */ if (!arenaJustCreated) { poolRecreate(event->ppwww.p0, event->ppwww.p1, mps_class_mv(), supportFree, 0, (size_t)event->ppwww.w2, (size_t)event->ppwww.w3, (size_t)event->ppwww.w4); } else { arenaJustCreated = FALSE; } } break; case EventPoolInitMFS: { /* pool, arena, extendBy, unitSize */ /* internal only */ ++discardedEvents; } break; case EventPoolInit: { /* pool, arena, class */ /* all internal only */ ++discardedEvents; } break; #ifdef MPS_PROD_EPCORE case EventPoolInitEPVM: { /* pool, arena, format, maxSaveLevel, saveLevel */ mps_arena_t arena; mps_fmt_t format; found = TableLookup(&entry, arenaTable, (Word)event->pppuu.p1); verify(found); arena = (mps_arena_t)entry; ensurePSFormat(&format, arena); /* We know what the format is. */ poolRecreate(event->pppuu.p0, event->pppuu.p1, mps_class_epvm(), supportNothing, 2, format, (mps_epvm_save_level_t)event->pppuu.u3, (mps_epvm_save_level_t)event->pppuu.u4); } break; case EventPoolInitEPDL: { /* pool, arena, isEPDL, extendBy, avgSize, align */ poolRecreate(event->ppuwww.p0, event->ppuwww.p1, event->ppuwww.u2 ? mps_class_epdl() : mps_class_epdr(), event->ppuwww.u2 ? supportTruncate : supportFree, 0, (size_t)event->ppuwww.w3, (size_t)event->ppuwww.w4, (size_t)event->ppuwww.w5); } break; #endif case EventPoolFinish: { /* pool */ found = TableLookup(&entry, poolTable, (Word)event->p.p0); if (found) { poolRedestroy(event->p.p0); } else { ++discardedEvents; } } break; case EventBufferInit: { /* buffer, pool, isMutator */ if ((Bool)event->ppu.u2) { found = TableLookup(&entry, poolTable, (Word)event->ppu.p1); if (found) { poolRep rep = (poolRep)entry; if(rep->bufferClassLevel == 0) { /* see .bufclass */ apRecreate(event->ppu.p0, event->ppu.p1); } else { ++discardedEvents; } } else { ++discardedEvents; } } else { ++discardedEvents; } } break; case EventBufferInitSeg: { /* buffer, pool, isMutator */ if ((Bool)event->ppu.u2) { found = TableLookup(&entry, poolTable, (Word)event->ppu.p1); if (found) { poolRep rep = (poolRep)entry; if(rep->bufferClassLevel == 1) { /* see .bufclass */ apRecreate(event->ppu.p0, event->ppu.p1); } else { ++discardedEvents; } } else { ++discardedEvents; } } else { ++discardedEvents; } } break; case EventBufferInitRank: { /* buffer, pool, isMutator, rank */ if ((Bool)event->ppuu.u2) { found = TableLookup(&entry, poolTable, (Word)event->ppuu.p1); if (found) { poolRep rep = (poolRep)entry; if(rep->bufferClassLevel == 2) { /* see .bufclass */ apRecreate(event->ppuu.p0, event->ppuu.p1, event->ppuu.u3); } else { ++discardedEvents; } } else { ++discardedEvents; } } else { ++discardedEvents; } } break; #ifdef MPS_PROD_EPCORE case EventBufferInitEPVM: { /* buffer, pool, isObj */ found = TableLookup(&entry, poolTable, (Word)event->ppu.p1); if (found) { poolRep rep = (poolRep)entry; if(rep->bufferClassLevel == 2) { /* see .bufclass */ apRecreate(event->ppu.p0, event->ppu.p1, (mps_bool_t)event->ppu.u2); } else { ++discardedEvents; } } else { ++discardedEvents; } } break; #endif case EventBufferFinish: { /* buffer */ found = TableLookup(&entry, apTable, (Word)event->p.p0); if (found) { apRedestroy(event->p.p0); } else { ++discardedEvents; } } break; case EventBufferReserve: { /* buffer, init, size */ found = TableLookup(&entry, apTable, (Word)event->paw.p0); if (found) { apRep rep = (apRep)entry; mps_addr_t p; eres = mps_reserve(&p, rep->ap, (size_t)event->paw.w2); verifyMPS(eres); } else { ++discardedEvents; } } break; case EventBufferCommit: { /* buffer, p, size, clientClass */ found = TableLookup(&entry, apTable, (Word)event->pawa.p0); if (found) { apRep rep = (apRep)entry; mps_addr_t obj = rep->ap->init; mps_bool_t committed; size_t size = (size_t)event->pawa.w2; committed = mps_commit(rep->ap, obj, size); verifyMPS(committed ? MPS_RES_OK : MPS_RES_FAIL); objDefine(rep->objects, event->pawa.a1, obj, size); } else { ++discardedEvents; } } break; case EventPoolAlloc: { /* pool, obj, size */ found = TableLookup(&entry, poolTable, (Word)event->paw.p0); if (found) { poolRep rep = (poolRep)entry; void *obj; size_t size = (size_t)event->paw.w2; eres = mps_alloc(&obj, rep->pool, size); verifyMPS(eres); objDefine(rep->objects, event->paw.a1, obj, size); } else { ++discardedEvents; } } break; case EventPoolFree: { /* pool, obj, size */ found = TableLookup(&entry, poolTable, (Word)event->paw.p0); if (found) { poolRep rep = (poolRep)entry; void *obj; size_t size = (size_t)event->paw.w2; objRemove(&obj, rep->objects, event->paw.a1, size); mps_free(rep->pool, obj, size); } else { ++discardedEvents; } } break; #ifdef MPS_PROD_EPCORE case EventPoolPush: { /* pool */ found = TableLookup(&entry, poolTable, (Word)event->p.p0); if (found) { poolRep rep = (poolRep)entry; /* It must be EPVM. */ mps_epvm_save(rep->pool); } } break; case EventPoolPop: { /* pool, level */ found = TableLookup(&entry, poolTable, (Word)event->pu.p0); if (found) { poolRep rep = (poolRep)entry; /* It must be EPVM. */ mps_epvm_restore(rep->pool, (mps_epvm_save_level_t)event->pu.u1); } } break; #endif case EventCommitLimitSet: { /* arena, limit, succeeded */ found = TableLookup(&entry, arenaTable, (Word)event->pwu.p0); verify(found); eres = mps_arena_commit_limit_set((mps_arena_t)entry, (size_t)event->pwu.w1); verifyMPS(((Bool)event->pwu.u2 == (eres == MPS_RES_OK)) ? MPS_RES_OK : MPS_RES_FAIL); } break; case EventSpareCommitLimitSet: { /* arena, limit */ found = TableLookup(&entry, arenaTable, (Word)event->pw.p0); verify(found); (void)mps_arena_spare_commit_limit_set((mps_arena_t)entry, (size_t)event->pw.w1); } break; case EventReservoirLimitSet: { /* arena, limit */ found = TableLookup(&entry, arenaTable, (Word)event->pw.p0); verify(found); mps_reservoir_limit_set((mps_arena_t)entry, (size_t)event->pw.w1); } break; case EventVMMap: case EventVMUnmap: case EventVMCreate: case EventVMDestroy: case EventArenaWriteFaults: case EventArenaAlloc: case EventArenaAllocFail: case EventArenaFree: case EventSegAlloc: case EventSegAllocFail: case EventSegFree: case EventSegMerge: case EventSegSplit: case EventBufferFill: case EventBufferEmpty: case EventCBSInit: case EventMeterInit: case EventMeterValues: case EventIntern: case EventLabel: { ++discardedEvents; } break; default: { ++unknownEvents; if (unknownEvents < 12) /* don't output too much */ printf("Unknown event @%ld: %s.\n", etime, EventCode2Name(EventGetCode(event))); } break; } } /* Checking macros, copied from check.h */ #define COMPATLVALUE(lv1, lv2) \ ((void)sizeof((lv1) = (lv2)), (void)sizeof((lv2) = (lv1)), TRUE) #define COMPATTYPE(t1, t2) \ (sizeof(t1) == sizeof(t2) && \ COMPATLVALUE(*((t1 *)0), *((t2 *)0))) /* CHECKCONV -- check t2 can be cast to t1 without loss */ #define CHECKCONV(t1, t2) \ (sizeof(t1) >= sizeof(t2)) /* EventRepInit -- initialize the module */ Res EventRepInit(void) { Res res; /* Check using pointers as keys in the tables. */ verify(CHECKCONV(Word, void *)); /* Check storage of MPS opaque handles in the tables. */ verify(COMPATTYPE(mps_arena_t, void *)); verify(COMPATTYPE(mps_ap_t, void *)); /* .event-conv: Conversion of event fields into the types required */ /* by the MPS functions is justified by the reverse conversion */ /* being acceptable (which is upto the event log generator). */ totalEvents = 0; discardedEvents = 0; unknownEvents = 0; res = TableCreate(&arenaTable, (size_t)1); if (res != ResOK) goto failArena; res = TableCreate(&poolTable, (size_t)1<<4); if (res != ResOK) goto failPool; res = TableCreate(&apTable, (size_t)1<<6); if (res != ResOK) goto failAp; return ResOK; failAp: TableDestroy(poolTable); failPool: TableDestroy(arenaTable); failArena: return res; } /* EventRepFinish -- finish the module */ void EventRepFinish(void) { /* @@@@ add listing of remaining objects? */ /* No point in cleaning up the tables, since we're quitting. */ printf("Replayed %lu and discarded %lu events (%lu unknown).\n", totalEvents - discardedEvents - unknownEvents, discardedEvents + unknownEvents, unknownEvents); } /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2001-2002 Ravenbrook Limited . * All rights reserved. This is an open source license. Contact * Ravenbrook for commercial licensing options. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * 3. Redistributions in any form must be accompanied by information on how * to obtain complete source code for this software and any accompanying * software that uses this software. The source code must either be * included in the distribution or be available for no more than the cost * of distribution plus a nominal fee, and must be freely redistributable * under reasonable conditions. For an executable file, complete source * code means the source code for all modules it contains. It does not * include source code for modules or files that typically accompany the * major components of the operating system on which the executable file * runs. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */