/* segsmss.c: Segment splitting and merging stress test * * $Id$ * Copyright (c) 2001 Ravenbrook Limited. See end of file for license. * Portions copyright (c) 2002 Global Graphics Software. * * .design: Adapted from amsss.c (because AMS already supports * a protocol for subclassing AMS segments). Defines a new pool * class, AMST. Segments are split and merged during BufferFill * operations. Buffered segments are also split and merged between * allocation requests. */ #include "mpm.h" #include "poolams.h" #include "fmtdy.h" #include "fmtdytst.h" #include "testlib.h" #include "chain.h" #include "mpscams.h" #include "mpsavm.h" #include "mpstd.h" #ifdef MPS_OS_W3 #include "mpsw3.h" #endif #include "mps.h" #include #include #include #include /* Forward declarations */ static SegClass AMSTSegClassGet(void); static PoolClass AMSTPoolClassGet(void); /* Start by defining the AMST pool (AMS Test pool) */ #define AMSTSig ((Sig)0x519A3529) /* SIGnature AMST */ /* AMSTStruct -- AMST pool instance structure */ typedef struct AMSTStruct { AMSStruct amsStruct; /* generic AMS structure */ Chain chain; /* chain to use */ Bool failSegs; /* fail seg splits & merges when true */ Count splits; /* count of successful segment splits */ Count merges; /* count of successful segment merges */ Count badSplits; /* count of unsuccessful segment splits */ Count badMerges; /* count of unsuccessful segment merges */ Count bsplits; /* count of buffered segment splits */ Count bmerges; /* count of buffered segment merges */ Sig sig; /* */ } AMSTStruct; typedef struct AMSTStruct *AMST; #define Pool2AMST(pool) PARENT(AMSTStruct, amsStruct.poolStruct, (pool)) #define AMST2AMS(amst) (&(amst)->amsStruct) /* AMSTCheck -- the check method for an AMST */ static Bool AMSTCheck(AMST amst) { CHECKS(AMST, amst); CHECKL(AMSCheck(AMST2AMS(amst))); return TRUE; } /* AMSTFailOperation -- should a split/merge operation fail? * * returns TRUE if so. */ static Bool AMSTFailOperation(AMST amst) { if (amst->failSegs) { return rnd() % 2; } else { return FALSE; } } /* AMSTSegStruct: AMST segment instances */ #define AMSTSegSig ((Sig)0x519A3525) /* SIGnature AMST Seg */ typedef struct AMSTSegStruct *AMSTSeg; typedef struct AMSTSegStruct { AMSSegStruct amsSegStruct; /* superclass fields must come first */ AMSTSeg next; /* mergeable next segment, or NULL */ AMSTSeg prev; /* mergeable prev segment, or NULL */ Sig sig; /* */ } AMSTSegStruct; /* AMSTSegCheck -- check the AMST segment */ static Bool AMSTSegCheck(AMSTSeg amstseg) { CHECKS(AMSTSeg, amstseg); CHECKL(AMSSegCheck(&amstseg->amsSegStruct)); /* don't bother to do other checks - this is a stress test */ return TRUE; } #define Seg2AMSTSeg(seg) ((AMSTSeg)(seg)) #define AMSTSeg2Seg(amstseg) ((Seg)(amstseg)) /* amstSegInit -- initialise an amst segment */ static Res amstSegInit(Seg seg, Pool pool, Addr base, Size size, Bool reservoirPermit, va_list args) { SegClass super; AMSTSeg amstseg; AMST amst; Res res; AVERT(Seg, seg); amstseg = Seg2AMSTSeg(seg); AVERT(Pool, pool); amst = Pool2AMST(pool); AVERT(AMST, amst); /* no useful checks for base and size */ AVER(BoolCheck(reservoirPermit)); /* Initialize the superclass fields first via next-method call */ super = SEG_SUPERCLASS(AMSTSegClass); res = super->init(seg, pool, base, size, reservoirPermit, args); if (res != ResOK) return res; amstseg->next = NULL; amstseg->prev = NULL; amstseg->sig = AMSTSegSig; AVERT(AMSTSeg, amstseg); return ResOK; } /* amstSegFinish -- Finish method for AMST segments */ static void amstSegFinish(Seg seg) { SegClass super; AMSTSeg amstseg; AVERT(Seg, seg); amstseg = Seg2AMSTSeg(seg); AVERT(AMSTSeg, amstseg); if (amstseg->next != NULL) amstseg->next->prev = NULL; if (amstseg->prev != NULL) amstseg->prev->next = NULL; amstseg->sig = SigInvalid; /* finish the superclass fields last */ super = SEG_SUPERCLASS(AMSTSegClass); super->finish(seg); } /* amstSegMerge -- AMSTSeg merge method * * .fail: Test proper handling of the most complex failure cases * by deliberately detecting failure sometimes after calling the * next method. We handle the error by calling the anti-method. * This isn't strictly safe (see ). * But we assume here that we won't run out of memory when calling the * anti-method. */ static Res amstSegMerge(Seg seg, Seg segHi, Addr base, Addr mid, Addr limit, Bool withReservoirPermit, va_list args) { SegClass super; AMST amst; AMSTSeg amstseg, amstsegHi; Res res; AVERT(Seg, seg); AVERT(Seg, segHi); amstseg = Seg2AMSTSeg(seg); amstsegHi = Seg2AMSTSeg(segHi); AVERT(AMSTSeg, amstseg); AVERT(AMSTSeg, amstsegHi); amst = Pool2AMST(SegPool(seg)); /* Merge the superclass fields via direct next-method call */ super = SEG_SUPERCLASS(AMSTSegClass); res = super->merge(seg, segHi, base, mid, limit, withReservoirPermit, args); if (res != ResOK) goto failSuper; if (AMSTFailOperation(amst)) { amst->badMerges++; printf("D"); goto failDeliberate; } amstseg->next = amstsegHi->next; amstsegHi->sig = SigInvalid; AVERT(AMSTSeg, amstseg); amst->merges++; printf("M"); return ResOK; failDeliberate: /* Call the anti-method (see .fail) */ res = super->split(seg, segHi, base, mid, limit, withReservoirPermit, args); AVER(res == ResOK); res = ResFAIL; failSuper: AVERT(AMSTSeg, amstseg); AVERT(AMSTSeg, amstsegHi); return res; } /* amstSegSplit -- AMSTSeg split method */ static Res amstSegSplit(Seg seg, Seg segHi, Addr base, Addr mid, Addr limit, Bool withReservoirPermit, va_list args) { SegClass super; AMST amst; AMSTSeg amstseg, amstsegHi; Res res; AVERT(Seg, seg); AVER(segHi != NULL); /* can't check fully, it's not initialized */ amstseg = Seg2AMSTSeg(seg); amstsegHi = Seg2AMSTSeg(segHi); AVERT(AMSTSeg, amstseg); amst = Pool2AMST(SegPool(seg)); /* Split the superclass fields via direct next-method call */ super = SEG_SUPERCLASS(AMSTSegClass); res = super->split(seg, segHi, base, mid, limit, withReservoirPermit, args); if (res != ResOK) goto failSuper; if (AMSTFailOperation(amst)) { amst->badSplits++; printf("B"); goto failDeliberate; } /* Full initialization for segHi. */ amstsegHi->next = amstseg->next; amstsegHi->prev = amstseg; amstsegHi->sig = AMSTSegSig; amstseg->next = amstsegHi; AVERT(AMSTSeg, amstseg); AVERT(AMSTSeg, amstsegHi); amst->splits++; printf("S"); return ResOK; failDeliberate: /* Call the anti-method. (see .fail) */ res = super->merge(seg, segHi, base, mid, limit, withReservoirPermit, args); AVER(res == ResOK); res = ResFAIL; failSuper: AVERT(AMSTSeg, amstseg); return res; } /* AMSTSegClass -- Class definition for AMST segments */ DEFINE_SEG_CLASS(AMSTSegClass, class) { INHERIT_CLASS(class, AMSSegClass); class->name = "AMSTSEG"; class->size = sizeof(AMSTSegStruct); class->init = amstSegInit; class->finish = amstSegFinish; class->split = amstSegSplit; class->merge = amstSegMerge; } /* AMSTSegSizePolicy * * Picks double the default segment size. */ static Res AMSTSegSizePolicy(Size *sizeReturn, Pool pool, Size size, RankSet rankSet) { Arena arena; Size basic, want; AVER(sizeReturn != NULL); AVERT(Pool, pool); AVER(size > 0); AVER(RankSetCheck(rankSet)); arena = PoolArena(pool); basic = SizeAlignUp(size, ArenaAlign(arena)); if (basic == 0) { /* overflow */ return ResMEMORY; } want = basic + basic; if (want <= basic) { /* overflow */ return ResMEMORY; } *sizeReturn = want; return ResOK; } /* AMSTInit -- the pool class initialization method */ static Res AMSTInit(Pool pool, va_list args) { AMST amst; AMS ams; Format format; Chain chain; Res res; static GenParamStruct genParam = { 1024, 0.2 }; AVERT(Pool, pool); format = va_arg(args, Format); res = ChainCreate(&chain, pool->arena, 1, &genParam); if (res != ResOK) return res; res = AMSInitInternal(Pool2AMS(pool), format, chain, FALSE); if (res != ResOK) return res; amst = Pool2AMST(pool); ams = Pool2AMS(pool); ams->segSize = AMSTSegSizePolicy; ams->segClass = AMSTSegClassGet; amst->chain = chain; amst->failSegs = TRUE; amst->splits = 0; amst->merges = 0; amst->badSplits = 0; amst->badMerges = 0; amst->bsplits = 0; amst->bmerges = 0; amst->sig = AMSTSig; AVERT(AMST, amst); return ResOK; } /* AMSTFinish -- the pool class finish method */ static void AMSTFinish(Pool pool) { AMST amst; AVERT(Pool, pool); amst = Pool2AMST(pool); AVERT(AMST, amst); printf("\nDestroying pool, having performed:\n"); printf(" %lu splits (S)\n", (unsigned long)amst->splits); printf(" %lu merges (M)\n", (unsigned long)amst->merges); printf(" %lu aborted splits (B)\n", (unsigned long)amst->badSplits); printf(" %lu aborted merges (D)\n", (unsigned long)amst->badMerges); printf(" which included:\n"); printf(" %lu buffered splits (C)\n", (unsigned long)amst->bsplits); printf(" %lu buffered merges (J)\n", (unsigned long)amst->bmerges); AMSFinish(pool); amst->sig = SigInvalid; ChainDestroy(amst->chain); } /* AMSSegIsFree -- return TRUE if a seg is all unallocated */ static Bool AMSSegIsFree(Seg seg) { AMSSeg amsseg; AVERT(Seg, seg); amsseg = Seg2AMSSeg(seg); return(amsseg->free == amsseg->grains); } /* AMSSegRegionIsFree -- return TRUE if a region is all unallocated */ static Bool AMSSegRegionIsFree(Seg seg, Addr base, Addr limit) { AMSSeg amsseg; AMS ams; Count bgrain, lgrain; Addr sbase; AVERT(Seg, seg); amsseg = Seg2AMSSeg(seg); sbase = SegBase(seg); ams = Pool2AMS(SegPool(seg)); bgrain = AMSGrains(ams, AddrOffset(sbase, base)); lgrain = AMSGrains(ams, AddrOffset(sbase, limit)); if (amsseg->allocTableInUse) { return BTIsResRange(amsseg->allocTable, bgrain, lgrain); } else { return amsseg->firstFree <= bgrain; } } /* AMSUnallocateRange -- set a range to be unallocated * * Used as a means of overriding the behaviour of AMSBufferFill. * The code is similar to AMSBufferEmpty. */ static void AMSUnallocateRange(Seg seg, Addr base, Addr limit) { Pool pool; AMS ams; AMSSeg amsseg; Index baseIndex, limitIndex; /* parameters checked by caller */ pool = SegPool(seg); ams = Pool2AMS(pool); amsseg = Seg2AMSSeg(seg); baseIndex = AMS_ADDR_INDEX(seg, base); limitIndex = AMS_ADDR_INDEX(seg, limit); if (amsseg->allocTableInUse) { /* check that it's allocated */ AVER(BTIsSetRange(amsseg->allocTable, baseIndex, limitIndex)); BTResRange(amsseg->allocTable, baseIndex, limitIndex); } else { /* check that it's allocated */ AVER(limitIndex <= amsseg->firstFree); if (limitIndex == amsseg->firstFree) /* is it at the end? */ { amsseg->firstFree = baseIndex; } else { /* start using allocTable */ amsseg->allocTableInUse = TRUE; BTSetRange(amsseg->allocTable, 0, amsseg->firstFree); if (amsseg->firstFree < amsseg->grains) BTResRange(amsseg->allocTable, amsseg->firstFree, amsseg->grains); BTResRange(amsseg->allocTable, baseIndex, limitIndex); } } amsseg->free += limitIndex - baseIndex; amsseg->newAlloc -= limitIndex - baseIndex; } /* AMSAllocateRange -- set a range to be allocated * * Used as a means of overriding the behaviour of AMSBufferFill. * The code is similar to AMSUnallocateRange. */ static void AMSAllocateRange(Seg seg, Addr base, Addr limit) { Pool pool; AMS ams; AMSSeg amsseg; Index baseIndex, limitIndex; /* parameters checked by caller */ pool = SegPool(seg); ams = Pool2AMS(pool); amsseg = Seg2AMSSeg(seg); baseIndex = AMS_ADDR_INDEX(seg, base); limitIndex = AMS_ADDR_INDEX(seg, limit); if (amsseg->allocTableInUse) { /* check that it's not allocated */ AVER(BTIsResRange(amsseg->allocTable, baseIndex, limitIndex)); BTSetRange(amsseg->allocTable, baseIndex, limitIndex); } else { /* check that it's not allocated */ AVER(baseIndex >= amsseg->firstFree); if (baseIndex == amsseg->firstFree) /* is it at the end? */ { amsseg->firstFree = limitIndex; } else { /* start using allocTable */ amsseg->allocTableInUse = TRUE; BTSetRange(amsseg->allocTable, 0, amsseg->firstFree); if (amsseg->firstFree < amsseg->grains) BTResRange(amsseg->allocTable, amsseg->firstFree, amsseg->grains); BTSetRange(amsseg->allocTable, baseIndex, limitIndex); } } AVER(amsseg->free >= limitIndex - baseIndex); amsseg->free -= limitIndex - baseIndex; amsseg->newAlloc += limitIndex - baseIndex; } /* AMSTBufferFill -- the pool class buffer fill method * * Calls next method - but possibly splits or merges the chosen * segment. * * .merge: A merge is performed when the next method returns * the entire segment, this segment had previously been split * from the segment below, and the segment below is appropriately * similar (i.e. not already attached to a buffer and similarly grey) * * .split: If we're not merging, a split is performed if the next method * returns the entire segment, and yet lower half of the segment would * meet the request. */ static Res AMSTBufferFill(Addr *baseReturn, Addr *limitReturn, Pool pool, Buffer buffer, Size size, Bool withReservoirPermit) { PoolClass super; Addr base, limit; Arena arena; AMST amst; Bool b; Seg seg; AMSTSeg amstseg; Res res; AVERT(Pool, pool); AVER(baseReturn != NULL); AVER(limitReturn != NULL); /* other parameters are checked by next method */ arena = PoolArena(pool); amst = Pool2AMST(pool); /* call next method */ super = POOL_SUPERCLASS(AMSTPoolClass); res = super->bufferFill(&base, &limit, pool, buffer, size, withReservoirPermit); if (res != ResOK) return res; b = SegOfAddr(&seg, arena, base); AVER(b); amstseg = Seg2AMSTSeg(seg); if (SegLimit(seg) == limit && SegBase(seg) == base) { if (amstseg->prev != NULL) { Seg segLo = AMSTSeg2Seg(amstseg->prev); if (SegBuffer(segLo) == NULL && SegGrey(segLo) == SegGrey(seg)) { /* .merge */ Seg mergedSeg; Res mres; AMSUnallocateRange(seg, base, limit); mres = SegMerge(&mergedSeg, segLo, seg, withReservoirPermit); if (ResOK == mres) { /* successful merge */ AMSAllocateRange(mergedSeg, base, limit); /* leave range as-is */ } else { /* failed to merge */ AVER(amst->failSegs); /* deliberate fails only */ AMSAllocateRange(seg, base, limit); } } } else { Size half = SegSize(seg) / 2; if (half >= size && SizeIsAligned(half, ArenaAlign(arena))) { /* .split */ Addr mid = AddrAdd(base, half); Seg segLo, segHi; Res sres; AMSUnallocateRange(seg, mid, limit); sres = SegSplit(&segLo, &segHi, seg, mid, withReservoirPermit); if (ResOK == sres) { /* successful split */ limit = mid; /* range is lower segment */ } else { /* failed to split */ AVER(amst->failSegs); /* deliberate fails only */ AMSAllocateRange(seg, mid, limit); } } } } *baseReturn = base; *limitReturn = limit; return ResOK; } /* AMSTStressBufferedSeg -- Stress test for a buffered seg * * Test splitting or merging a buffered seg. * * .bmerge: A merge is performed when the segment had previously * been split and the segment above meets the constraints (i.e. empty, * not already attached to a buffer and similar colour) * * .bsplit: Whether or not a merge happpened, a split is performed if * the limit of the buffered region is arena aligned, and yet does not * correspond to the segment limit, provided that the part of the segment * above the buffer is all free. */ static void AMSTStressBufferedSeg(Seg seg, Buffer buffer) { AMSTSeg amstseg; AMST amst; Arena arena; Addr limit; AVERT(Seg, seg); AVERT(Buffer, buffer); AVER(SegBuffer(seg) == buffer); amstseg = Seg2AMSTSeg(seg); AVERT(AMSTSeg, amstseg); limit = BufferLimit(buffer); arena = PoolArena(SegPool(seg)); amst = Pool2AMST(SegPool(seg)); AVERT(AMST, amst); if (amstseg->next != NULL) { Seg segHi = AMSTSeg2Seg(amstseg->next); if (AMSSegIsFree(segHi) && SegGrey(segHi) == SegGrey(seg)) { /* .bmerge */ Seg mergedSeg; Res res; res = SegMerge(&mergedSeg, seg, segHi, FALSE); if (ResOK == res) { amst->bmerges++; printf("J"); } else { /* deliberate fails only */ AVER(amst->failSegs); } } } if (SegLimit(seg) != limit && AddrIsAligned(limit, ArenaAlign(arena)) && AMSSegRegionIsFree(seg, limit, SegLimit(seg))) { /* .bsplit */ Seg segLo, segHi; Res res; res = SegSplit(&segLo, &segHi, seg, limit, FALSE); if (ResOK == res) { amst->bsplits++; printf("C"); } else { /* deliberate fails only */ AVER(amst->failSegs); } } } /* AMSTPoolClass -- the pool class definition */ DEFINE_POOL_CLASS(AMSTPoolClass, this) { INHERIT_CLASS(this, AMSPoolClass); this->name = "AMST"; this->size = sizeof(AMSTStruct); this->offset = offsetof(AMSTStruct, amsStruct.poolStruct); this->init = AMSTInit; this->finish = AMSTFinish; this->bufferFill = AMSTBufferFill; } /* mps_amst_ap_stress -- stress an active buffer * * Attempt to either split or merge a segment attached to an AP */ static void mps_amst_ap_stress(mps_ap_t ap) { Buffer buffer; Seg seg; buffer = BufferOfAP((AP)ap); AVERT(Buffer, buffer); seg = BufferSeg(buffer); AMSTStressBufferedSeg(seg, buffer); } /* mps_class_amst -- return the pool class descriptor to the client */ static mps_class_t mps_class_amst(void) { return (mps_class_t)AMSTPoolClassGet(); } /* AMS collection parameters */ #define exactRootsCOUNT 50 #define ambigRootsCOUNT 100 #define sizeScale 4 /* This is enough for five GCs. */ #define totalSizeMAX sizeScale * 800 * (size_t)1024 #define totalSizeSTEP 200 * (size_t)1024 /* objNULL needs to be odd so that it's ignored in exactRoots. */ #define objNULL ((mps_addr_t)0xDECEA5ED) #define testArenaSIZE ((size_t)16<<20) #define initTestFREQ 6000 #define stressTestFREQ 40 /* static variables for the test */ static mps_pool_t pool; static mps_ap_t ap; static mps_addr_t exactRoots[exactRootsCOUNT]; static mps_addr_t ambigRoots[ambigRootsCOUNT]; static size_t totalSize = 0; /* make -- object allocation and init */ static mps_addr_t make(void) { size_t length = rnd() % 20, size = (length+2) * sizeof(mps_word_t); mps_addr_t p; mps_res_t res; do { MPS_RESERVE_BLOCK(res, p, ap, size); if (res) die(res, "MPS_RESERVE_BLOCK"); res = dylan_init(p, size, exactRoots, exactRootsCOUNT); if (res) die(res, "dylan_init"); } while(!mps_commit(ap, p, size)); totalSize += size; return p; } /* test -- the actual stress test */ static void *test(void *arg, size_t s) { mps_arena_t arena; mps_fmt_t format; mps_root_t exactRoot, ambigRoot; size_t lastStep = 0, i, r; unsigned long objs; mps_ap_t busy_ap; mps_addr_t busy_init; char *indent = " "; arena = (mps_arena_t)arg; (void)s; /* unused */ die(mps_fmt_create_A(&format, arena, dylan_fmt_A()), "fmt_create"); die(mps_pool_create(&pool, arena, mps_class_amst(), format), "pool_create(amst)"); die(mps_ap_create(&ap, pool, MPS_RANK_EXACT), "BufferCreate"); die(mps_ap_create(&busy_ap, pool, MPS_RANK_EXACT), "BufferCreate 2"); for(i = 0; i < exactRootsCOUNT; ++i) exactRoots[i] = objNULL; for(i = 0; i < ambigRootsCOUNT; ++i) ambigRoots[i] = (mps_addr_t)rnd(); die(mps_root_create_table_masked(&exactRoot, arena, MPS_RANK_EXACT, (mps_rm_t)0, &exactRoots[0], exactRootsCOUNT, (mps_word_t)1), "root_create_table(exact)"); die(mps_root_create_table(&ambigRoot, arena, MPS_RANK_AMBIG, (mps_rm_t)0, &ambigRoots[0], ambigRootsCOUNT), "root_create_table(ambig)"); printf(indent); /* create an ap, and leave it busy */ die(mps_reserve(&busy_init, busy_ap, 64), "mps_reserve busy"); objs = 0; while(totalSize < totalSizeMAX) { if (totalSize > lastStep + totalSizeSTEP) { lastStep = totalSize; printf("\nSize %lu bytes, %lu objects.\n", (unsigned long)totalSize, objs); printf(indent); fflush(stdout); for(i = 0; i < exactRootsCOUNT; ++i) cdie(exactRoots[i] == objNULL || dylan_check(exactRoots[i]), "all roots check"); } r = (size_t)rnd(); if (r & 1) { i = (r >> 1) % exactRootsCOUNT; if (exactRoots[i] != objNULL) cdie(dylan_check(exactRoots[i]), "dying root check"); exactRoots[i] = make(); if (exactRoots[(exactRootsCOUNT-1) - i] != objNULL) dylan_write(exactRoots[(exactRootsCOUNT-1) - i], exactRoots, exactRootsCOUNT); } else { i = (r >> 1) % ambigRootsCOUNT; ambigRoots[(ambigRootsCOUNT-1) - i] = make(); /* Create random interior pointers */ ambigRoots[i] = (mps_addr_t)((char *)(ambigRoots[i/2]) + 1); } if (rnd() % stressTestFREQ == 0) mps_amst_ap_stress(ap); /* stress active buffer */ if (rnd() % initTestFREQ == 0) *(int*)busy_init = -1; /* check that the buffer is still there */ ++objs; if (objs % 256 == 0) { printf("."); fflush(stdout); } } (void)mps_commit(busy_ap, busy_init, 64); mps_ap_destroy(busy_ap); mps_ap_destroy(ap); mps_root_destroy(exactRoot); mps_root_destroy(ambigRoot); mps_pool_destroy(pool); mps_fmt_destroy(format); return NULL; } int main(int argc, char **argv) { mps_arena_t arena; mps_thr_t thread; void *r; randomize(argc, argv); die(mps_arena_create(&arena, mps_arena_class_vm(), testArenaSIZE), "arena_create"); die(mps_thread_reg(&thread, arena), "thread_reg"); mps_tramp(&r, test, arena, 0); mps_thread_dereg(thread); mps_arena_destroy(arena); fflush(stdout); /* synchronize */ fprintf(stderr, "\nConclusion: Failed to find any defects.\n"); return 0; } /* 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. */