/* locus.c: LOCUS MANAGER * * $Id$ * Copyright (c) 2001-2016 Ravenbrook Limited. See end of file for license. * * DESIGN * * See and for basic locus stuff. * See for chains. See for the * collection strategy. */ #include "locus.h" #include "ring.h" #include "mpm.h" #include "mpstd.h" #include /* for DBL_MAX */ SRCID(locus, "$Id$"); /* LocusPrefCheck -- check the consistency of a locus preference */ Bool LocusPrefCheck(LocusPref pref) { CHECKS(LocusPref, pref); CHECKL(BoolCheck(pref->high)); /* zones can't be checked because it's arbitrary. */ /* avoid can't be checked because it's arbitrary. */ return TRUE; } /* LocusPrefDefault -- return a locus preference representing the defaults */ static LocusPrefStruct locusPrefDefault = LocusPrefDEFAULT; LocusPref LocusPrefDefault(void) { return &locusPrefDefault; } /* LocusPrefInit -- initialise a locus preference to the defaults */ void LocusPrefInit(LocusPref pref) { (void)mps_lib_memcpy(pref, &locusPrefDefault, sizeof(LocusPrefStruct)); } /* LocusPrefExpress -- express a locus preference */ void LocusPrefExpress(LocusPref pref, LocusPrefKind kind, void *p) { AVERT(LocusPref, pref); AVER(pref != &locusPrefDefault); switch(kind) { case LocusPrefHIGH: AVER(p == NULL); pref->high = TRUE; break; case LocusPrefLOW: AVER(p == NULL); pref->high = FALSE; break; case LocusPrefZONESET: AVER(p != NULL); pref->zones = *(ZoneSet *)p; break; default: /* Unknown kinds are ignored for binary compatibility. */ break; } } /* LocusPrefDescribe -- describe a locus preference */ Res LocusPrefDescribe(LocusPref pref, mps_lib_FILE *stream, Count depth) { Res res; if (!TESTT(LocusPref, pref)) return ResFAIL; if (stream == NULL) return ResFAIL; res = WriteF(stream, depth, "LocusPref $P {\n", (WriteFP)pref, " high $S\n", WriteFYesNo(pref->high), " zones $B\n", (WriteFB)pref->zones, " avoid $B\n", (WriteFB)pref->avoid, "} LocusPref $P\n", (WriteFP)pref, NULL); return res; } /* GenDescCheck -- check a GenDesc */ ATTRIBUTE_UNUSED Bool GenDescCheck(GenDesc gen) { CHECKS(GenDesc, gen); /* nothing to check for zones */ /* nothing to check for capacity */ CHECKL(gen->mortality >= 0.0); CHECKL(gen->mortality <= 1.0); CHECKD_NOSIG(Ring, &gen->locusRing); CHECKD_NOSIG(Ring, &gen->segRing); return TRUE; } /* GenParamCheck -- check consistency of generation parameters */ ATTRIBUTE_UNUSED static Bool GenParamCheck(GenParamStruct *params) { CHECKL(params != NULL); CHECKL(params->capacity > 0); CHECKL(params->mortality >= 0.0); CHECKL(params->mortality <= 1.0); return TRUE; } /* GenDescInit -- initialize a generation in a chain */ static void GenDescInit(GenDesc gen, GenParamStruct *params) { AVER(gen != NULL); AVER(GenParamCheck(params)); gen->zones = ZoneSetEMPTY; gen->capacity = params->capacity; gen->mortality = params->mortality; RingInit(&gen->locusRing); RingInit(&gen->segRing); gen->sig = GenDescSig; AVERT(GenDesc, gen); } /* GenDescFinish -- finish a generation in a chain */ static void GenDescFinish(GenDesc gen) { AVERT(GenDesc, gen); RingFinish(&gen->locusRing); RingFinish(&gen->segRing); gen->sig = SigInvalid; } /* GenDescNewSize -- return effective size of generation */ Size GenDescNewSize(GenDesc gen) { Size size = 0; Ring node, nextNode; AVERT(GenDesc, gen); RING_FOR(node, &gen->locusRing, nextNode) { PoolGen pgen = RING_ELT(PoolGen, genRing, node); AVERT(PoolGen, pgen); size += pgen->newSize; } return size; } /* genDescTraceStart -- notify generation of start of a trace */ static void genDescStartTrace(GenDesc gen, Trace trace) { GenTraceStats stats; AVERT(GenDesc, gen); AVERT(Trace, trace); stats = &gen->trace[trace->ti]; stats->condemned = 0; stats->forwarded = 0; stats->preservedInPlace = 0; } /* genDescEndTrace -- notify generation of end of a trace */ static void genDescEndTrace(GenDesc gen, Trace trace) { GenTraceStats stats; Size survived; AVERT(GenDesc, gen); AVERT(Trace, trace); stats = &gen->trace[trace->ti]; survived = stats->forwarded + stats->preservedInPlace; AVER(survived <= stats->condemned); if (stats->condemned > 0) { double mortality = 1.0 - survived / (double)stats->condemned; double alpha = LocusMortalityALPHA; gen->mortality = gen->mortality * (1 - alpha) + mortality * alpha; EVENT6(TraceEndGen, trace, gen, stats->condemned, stats->forwarded, stats->preservedInPlace, gen->mortality); } } /* GenDescCondemned -- memory in a generation was condemned for a trace */ void GenDescCondemned(GenDesc gen, Trace trace, Size size) { GenTraceStats stats; AVERT(GenDesc, gen); AVERT(Trace, trace); stats = &gen->trace[trace->ti]; stats->condemned += size; trace->condemned += size; } /* GenDescSurvived -- memory in a generation survived a trace */ void GenDescSurvived(GenDesc gen, Trace trace, Size forwarded, Size preservedInPlace) { GenTraceStats stats; AVERT(GenDesc, gen); AVERT(Trace, trace); stats = &gen->trace[trace->ti]; stats->forwarded += forwarded; stats->preservedInPlace += preservedInPlace; trace->forwardedSize += forwarded; trace->preservedInPlaceSize += preservedInPlace; } /* GenDescTotalSize -- return total size of generation */ Size GenDescTotalSize(GenDesc gen) { Size size = 0; Ring node, nextNode; AVERT(GenDesc, gen); RING_FOR(node, &gen->locusRing, nextNode) { PoolGen pgen = RING_ELT(PoolGen, genRing, node); AVERT(PoolGen, pgen); size += pgen->totalSize; } return size; } /* GenDescDescribe -- describe a generation in a chain */ Res GenDescDescribe(GenDesc gen, mps_lib_FILE *stream, Count depth) { Index i; Res res; Ring node, nextNode; if (!TESTT(GenDesc, gen)) return ResFAIL; if (stream == NULL) return ResFAIL; res = WriteF(stream, depth, "GenDesc $P {\n", (WriteFP)gen, " zones $B\n", (WriteFB)gen->zones, " capacity $W\n", (WriteFW)gen->capacity, " mortality $D\n", (WriteFD)gen->mortality, NULL); if (res != ResOK) return res; for (i = 0; i < NELEMS(gen->trace); ++i) { GenTraceStats stats = &gen->trace[i]; res = WriteF(stream, depth + 2, "trace $W {\n", (WriteFW)i, " condemned $W\n", (WriteFW)stats->condemned, " forwarded $W\n", (WriteFW)stats->forwarded, " preservedInPlace $W\n", (WriteFW)stats->preservedInPlace, "}\n", NULL); if (res != ResOK) return res; } RING_FOR(node, &gen->locusRing, nextNode) { PoolGen pgen = RING_ELT(PoolGen, genRing, node); res = PoolGenDescribe(pgen, stream, depth + 2); if (res != ResOK) return res; } res = WriteF(stream, depth, "} GenDesc $P\n", (WriteFP)gen, NULL); return res; } /* ChainInit -- initialize a generation chain */ static void ChainInit(ChainStruct *chain, Arena arena, GenDescStruct *gens, Count genCount) { AVER(chain != NULL); AVERT(Arena, arena); AVER(gens != NULL); AVER(genCount > 0); chain->arena = arena; RingInit(&chain->chainRing); chain->activeTraces = TraceSetEMPTY; chain->genCount = genCount; chain->gens = gens; chain->sig = ChainSig; AVERT(Chain, chain); RingAppend(&arena->chainRing, &chain->chainRing); } /* ChainCreate -- create a generation chain */ Res ChainCreate(Chain *chainReturn, Arena arena, size_t genCount, GenParamStruct *params) { size_t i; Chain chain; GenDescStruct *gens; Res res; void *p; AVER(chainReturn != NULL); AVERT(Arena, arena); AVER(genCount > 0); AVER(params != NULL); res = ControlAlloc(&p, arena, genCount * sizeof(GenDescStruct)); if (res != ResOK) return res; gens = (GenDescStruct *)p; for (i = 0; i < genCount; ++i) GenDescInit(&gens[i], ¶ms[i]); res = ControlAlloc(&p, arena, sizeof(ChainStruct)); if (res != ResOK) goto failChainAlloc; chain = (Chain)p; ChainInit(chain, arena, gens, genCount); *chainReturn = chain; return ResOK; failChainAlloc: ControlFree(arena, gens, genCount * sizeof(GenDescStruct)); return res; } /* ChainCheck -- check a chain */ Bool ChainCheck(Chain chain) { size_t i; CHECKS(Chain, chain); CHECKU(Arena, chain->arena); CHECKD_NOSIG(Ring, &chain->chainRing); CHECKL(TraceSetCheck(chain->activeTraces)); CHECKL(chain->genCount > 0); for (i = 0; i < chain->genCount; ++i) { CHECKD(GenDesc, &chain->gens[i]); } return TRUE; } /* ChainDestroy -- destroy a chain */ void ChainDestroy(Chain chain) { Arena arena; size_t genCount; size_t i; AVERT(Chain, chain); AVER(chain->activeTraces == TraceSetEMPTY); /* */ arena = chain->arena; genCount = chain->genCount; RingRemove(&chain->chainRing); chain->sig = SigInvalid; for (i = 0; i < genCount; ++i) GenDescFinish(&chain->gens[i]); RingFinish(&chain->chainRing); ControlFree(arena, chain->gens, genCount * sizeof(GenDescStruct)); ControlFree(arena, chain, sizeof(ChainStruct)); } /* ChainGens -- return the number of generation in chain */ size_t ChainGens(Chain chain) { AVERT(Chain, chain); return chain->genCount; } /* ChainGen -- return a generation in a chain, or the arena top generation */ GenDesc ChainGen(Chain chain, Index gen) { AVERT(Chain, chain); AVER(gen <= chain->genCount); if (gen < chain->genCount) return &chain->gens[gen]; else return &chain->arena->topGen; } /* ChainDeferral -- time until next ephemeral GC for this chain */ double ChainDeferral(Chain chain) { double time = DBL_MAX; size_t i; AVERT(Chain, chain); if (chain->activeTraces == TraceSetEMPTY) { for (i = 0; i < chain->genCount; ++i) { double genTime = chain->gens[i].capacity * 1024.0 - (double)GenDescNewSize(&chain->gens[i]); if (genTime < time) time = genTime; } } return time; } /* ChainStartTrace -- called to notify start of GC for this chain */ void ChainStartTrace(Chain chain, Trace trace) { Index i; AVERT(Chain, chain); AVERT(Trace, trace); chain->activeTraces = TraceSetAdd(chain->activeTraces, trace); for (i = 0; i < chain->genCount; ++i) genDescStartTrace(&chain->gens[i], trace); } /* ChainEndTrace -- called to notify end of GC for this chain */ void ChainEndTrace(Chain chain, Trace trace) { Index i; AVERT(Chain, chain); AVERT(Trace, trace); chain->activeTraces = TraceSetDel(chain->activeTraces, trace); for (i = 0; i < chain->genCount; ++i) genDescEndTrace(&chain->gens[i], trace); } /* ChainDescribe -- describe a chain */ Res ChainDescribe(Chain chain, mps_lib_FILE *stream, Count depth) { Res res; size_t i; if (!TESTT(Chain, chain)) return ResFAIL; if (stream == NULL) return ResFAIL; res = WriteF(stream, depth, "Chain $P {\n", (WriteFP)chain, " arena $P\n", (WriteFP)chain->arena, " activeTraces $B\n", (WriteFB)chain->activeTraces, NULL); if (res != ResOK) return res; for (i = 0; i < chain->genCount; ++i) { res = GenDescDescribe(&chain->gens[i], stream, depth + 2); if (res != ResOK) return res; } res = WriteF(stream, depth, "} Chain $P\n", (WriteFP)chain, NULL); return res; } /* PoolGenInit -- initialize a PoolGen */ Res PoolGenInit(PoolGen pgen, GenDesc gen, Pool pool) { /* Can't check pgen, because it's not been initialized. */ AVER(pgen != NULL); AVERT(GenDesc, gen); AVERT(Pool, pool); AVER(PoolHasAttr(pool, AttrGC)); pgen->pool = pool; pgen->gen = gen; RingInit(&pgen->genRing); pgen->segs = 0; pgen->totalSize = 0; pgen->freeSize = 0; pgen->bufferedSize = 0; pgen->newSize = 0; pgen->oldSize = 0; pgen->newDeferredSize = 0; pgen->oldDeferredSize = 0; pgen->sig = PoolGenSig; AVERT(PoolGen, pgen); RingAppend(&gen->locusRing, &pgen->genRing); return ResOK; } /* PoolGenFinish -- finish a PoolGen */ void PoolGenFinish(PoolGen pgen) { AVERT(PoolGen, pgen); AVER(pgen->segs == 0); AVER(pgen->totalSize == 0); AVER(pgen->freeSize == 0); AVER(pgen->bufferedSize == 0); AVER(pgen->newSize == 0); AVER(pgen->newDeferredSize == 0); AVER(pgen->oldSize == 0); AVER(pgen->oldDeferredSize == 0); pgen->sig = SigInvalid; RingRemove(&pgen->genRing); } /* PoolGenCheck -- check a PoolGen */ Bool PoolGenCheck(PoolGen pgen) { CHECKS(PoolGen, pgen); /* nothing to check about serial */ CHECKU(Pool, pgen->pool); CHECKU(GenDesc, pgen->gen); CHECKD_NOSIG(Ring, &pgen->genRing); CHECKL((pgen->totalSize == 0) == (pgen->segs == 0)); CHECKL(pgen->totalSize >= pgen->segs * ArenaGrainSize(PoolArena(pgen->pool))); CHECKL(pgen->totalSize == pgen->freeSize + pgen->bufferedSize + pgen->newSize + pgen->oldSize + pgen->newDeferredSize + pgen->oldDeferredSize); return TRUE; } /* PoolGenAccountForAlloc -- accounting for allocation of a segment */ static void PoolGenAccountForAlloc(PoolGen pgen, Size size) { pgen->totalSize += size; ++ pgen->segs; pgen->freeSize += size; } /* PoolGenAlloc -- allocate a segment in a pool generation * * Allocate a GCSeg, attach it to the generation, and update the * accounting. */ Res PoolGenAlloc(Seg *segReturn, PoolGen pgen, SegClass class, Size size, ArgList args) { LocusPrefStruct pref; Res res; Seg seg; ZoneSet zones, moreZones; Arena arena; GenDesc gen; AVER(segReturn != NULL); AVERT(PoolGen, pgen); AVERT(SegClass, class); AVER(size > 0); AVERT(ArgList, args); arena = PoolArena(pgen->pool); gen = pgen->gen; zones = gen->zones; LocusPrefInit(&pref); pref.high = FALSE; pref.zones = zones; pref.avoid = ZoneSetBlacklist(arena); res = SegAlloc(&seg, class, &pref, size, pgen->pool, args); if (res != ResOK) return res; RingAppend(&gen->segRing, &SegGCSeg(seg)->genRing); moreZones = ZoneSetUnion(zones, ZoneSetOfSeg(arena, seg)); gen->zones = moreZones; if (!ZoneSetSuper(zones, moreZones)) { /* Tracking the whole zoneset for each generation gives more * understandable telemetry than just reporting the added * zones. */ EVENT3(ArenaGenZoneAdd, arena, gen, moreZones); } PoolGenAccountForAlloc(pgen, SegSize(seg)); *segReturn = seg; return ResOK; } /* PoolGenAccountForFill -- accounting for allocation within a segment * * Call this when the pool allocates memory to the client program via * BufferFill. * * See */ void PoolGenAccountForFill(PoolGen pgen, Size size) { AVERT(PoolGen, pgen); AVER(pgen->freeSize >= size); pgen->freeSize -= size; pgen->bufferedSize += size; } /* PoolGenAccountForEmpty -- accounting for emptying a buffer * * Call this when the client program returns memory to the pool via * BufferEmpty. The deferred flag indicates whether the accounting of * the used memory (for the purpose of scheduling collections) should * be deferred until later. * * See */ void PoolGenAccountForEmpty(PoolGen pgen, Size used, Size unused, Bool deferred) { AVERT(PoolGen, pgen); AVERT(Bool, deferred); AVER(pgen->bufferedSize >= used + unused); pgen->bufferedSize -= used + unused; if (deferred) { pgen->newDeferredSize += used; } else { pgen->newSize += used; } pgen->freeSize += unused; } /* PoolGenAccountForAge -- accounting for condemning * * Call this when memory is condemned via PoolWhiten, or when * artificially ageing memory in PoolGenFree. The size parameter * should be the amount of memory that is being condemned for the * first time. The deferred flag is as for PoolGenAccountForEmpty. * * See */ void PoolGenAccountForAge(PoolGen pgen, Size wasBuffered, Size wasNew, Bool deferred) { AVERT(PoolGen, pgen); AVERT(Bool, deferred); AVER(pgen->bufferedSize >= wasBuffered); pgen->bufferedSize -= wasBuffered; if (deferred) { AVER(pgen->newDeferredSize >= wasNew); pgen->newDeferredSize -= wasNew; pgen->oldDeferredSize += wasBuffered + wasNew; } else { AVER(pgen->newSize >= wasNew); pgen->newSize -= wasNew; pgen->oldSize += wasBuffered + wasNew; } } /* PoolGenAccountForReclaim -- accounting for reclaiming * * Call this when reclaiming memory, passing the amount of memory that * was reclaimed. The deferred flag is as for PoolGenAccountForEmpty. * * See */ void PoolGenAccountForReclaim(PoolGen pgen, Size reclaimed, Bool deferred) { AVERT(PoolGen, pgen); AVERT(Bool, deferred); if (deferred) { AVER(pgen->oldDeferredSize >= reclaimed); pgen->oldDeferredSize -= reclaimed; } else { AVER(pgen->oldSize >= reclaimed); pgen->oldSize -= reclaimed; } pgen->freeSize += reclaimed; } /* PoolGenUndefer -- finish deferring accounting * * Call this when exiting ramp mode, passing the amount of old * (condemned at least once) and new (never condemned) memory whose * accounting was deferred (for example, during a ramp). * * See */ void PoolGenUndefer(PoolGen pgen, Size oldSize, Size newSize) { AVERT(PoolGen, pgen); AVER(pgen->oldDeferredSize >= oldSize); pgen->oldDeferredSize -= oldSize; pgen->oldSize += oldSize; AVER(pgen->newDeferredSize >= newSize); pgen->newDeferredSize -= newSize; pgen->newSize += newSize; } /* PoolGenAccountForSegSplit -- accounting for splitting a segment */ void PoolGenAccountForSegSplit(PoolGen pgen) { AVERT(PoolGen, pgen); AVER(pgen->segs >= 1); /* must be at least one segment to split */ ++ pgen->segs; } /* PoolGenAccountForSegMerge -- accounting for merging a segment */ void PoolGenAccountForSegMerge(PoolGen pgen) { AVERT(PoolGen, pgen); AVER(pgen->segs >= 2); /* must be at least two segments to merge */ -- pgen->segs; } /* PoolGenAccountForFree -- accounting for the freeing of a segment */ static void PoolGenAccountForFree(PoolGen pgen, Size size, Size oldSize, Size newSize, Bool deferred) { /* Pretend to age and reclaim the contents of the segment to ensure * that the entire segment is accounted as free. */ PoolGenAccountForAge(pgen, 0, newSize, deferred); PoolGenAccountForReclaim(pgen, oldSize + newSize, deferred); AVER(pgen->totalSize >= size); pgen->totalSize -= size; AVER(pgen->segs > 0); -- pgen->segs; AVER(pgen->freeSize >= size); pgen->freeSize -= size; } /* PoolGenFree -- free a segment and update accounting * * Pass the amount of memory in the segment that is accounted as free, * old, or new, respectively. The deferred flag is as for * PoolGenAccountForEmpty. * * See */ void PoolGenFree(PoolGen pgen, Seg seg, Size freeSize, Size oldSize, Size newSize, Bool deferred) { Size size; AVERT(PoolGen, pgen); AVERT(Seg, seg); size = SegSize(seg); AVER(freeSize + oldSize + newSize == size); PoolGenAccountForFree(pgen, size, oldSize, newSize, deferred); RingRemove(&SegGCSeg(seg)->genRing); SegFree(seg); } /* PoolGenDescribe -- describe a PoolGen */ Res PoolGenDescribe(PoolGen pgen, mps_lib_FILE *stream, Count depth) { Res res; PoolClass poolClass; if (!TESTT(PoolGen, pgen)) return ResPARAM; if (stream == NULL) return ResPARAM; poolClass = ClassOfPoly(Pool, pgen->pool); res = WriteF(stream, depth, "PoolGen $P {\n", (WriteFP)pgen, " pool $P ($U) \"$S\"\n", (WriteFP)pgen->pool, (WriteFU)pgen->pool->serial, (WriteFS)ClassName(poolClass), " segs $U\n", (WriteFU)pgen->segs, " totalSize $U\n", (WriteFU)pgen->totalSize, " freeSize $U\n", (WriteFU)pgen->freeSize, " bufferedSize $U\n", (WriteFU)pgen->bufferedSize, " oldSize $U\n", (WriteFU)pgen->oldSize, " oldDeferredSize $U\n", (WriteFU)pgen->oldDeferredSize, " newSize $U\n", (WriteFU)pgen->newSize, " newDeferredSize $U\n", (WriteFU)pgen->newDeferredSize, "} PoolGen $P\n", (WriteFP)pgen, NULL); return res; } /* LocusInit -- initialize the locus module */ void LocusInit(Arena arena) { GenDesc gen = &arena->topGen; /* Can't check arena, because it's not been inited. */ gen->zones = ZoneSetEMPTY; gen->capacity = 0; /* unused */ gen->mortality = 0.5; RingInit(&gen->locusRing); RingInit(&gen->segRing); gen->sig = GenDescSig; AVERT(GenDesc, gen); } /* LocusFinish -- finish the locus module */ void LocusFinish(Arena arena) { GenDesc gen = &arena->topGen; /* Can't check arena, because it's being finished. */ gen->sig = SigInvalid; RingFinish(&gen->locusRing); } /* LocusCheck -- check the locus module */ Bool LocusCheck(Arena arena) { /* Can't check arena, because this is part of ArenaCheck. */ CHECKD(GenDesc, &arena->topGen); return TRUE; } /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2001-2016 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. */