1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-06 03:40:56 -08:00
emacs/mps/code/poollo.c
Peter Jackson 1d00afac81 Publish licence updates in code directory.
Publish minor changes to readme and configure files in main directory.
Main directory and code directory licence texts are now fully updated.

Copied from Perforce
 Change: 196994
2020-06-11 17:05:40 +01:00

788 lines
22 KiB
C

/* poollo.c: LEAF POOL CLASS
*
* $Id$
* Copyright (c) 2001-2020 Ravenbrook Limited. See end of file for license.
*
* DESIGN
*
* .design: <design/poollo>. This is a leaf pool class.
*/
#include "mpsclo.h"
#include "mpm.h"
#include "mps.h"
SRCID(poollo, "$Id$");
/* LOStruct -- leaf object pool instance structure */
#define LOSig ((Sig)0x51970B07) /* SIGnature LO POoL */
typedef struct LOStruct *LO;
typedef struct LOStruct {
PoolStruct poolStruct; /* generic pool structure */
PoolGenStruct pgenStruct; /* generation representing the pool */
PoolGen pgen; /* NULL or pointer to pgenStruct */
Sig sig; /* <code/misc.h#sig> */
} LOStruct;
typedef LO LOPool;
#define LOPoolCheck LOCheck
DECLARE_CLASS(Pool, LOPool, AbstractCollectPool);
DECLARE_CLASS(Seg, LOSeg, MutatorSeg);
/* forward declaration */
static Bool LOCheck(LO lo);
/* LOGSegStruct -- LO segment structure
*
* Colour is represented as follows:
* Black: +alloc +mark
* White: +alloc -mark
* Grey: objects have no references so can't be grey
* Free: -alloc ?mark
*/
typedef struct LOSegStruct *LOSeg;
#define LOSegSig ((Sig)0x519705E9) /* SIGnature LO SEG */
typedef struct LOSegStruct {
GCSegStruct gcSegStruct; /* superclass fields must come first */
BT mark; /* mark bit table */
BT alloc; /* alloc bit table */
Count freeGrains; /* free grains */
Count bufferedGrains; /* grains in buffers */
Count newGrains; /* grains allocated since last collection */
Count oldGrains; /* grains allocated prior to last collection */
Sig sig; /* <code/misc.h#sig> */
} LOSegStruct;
/* forward decls */
static Res loSegInit(Seg seg, Pool pool, Addr base, Size size, ArgList args);
static void loSegFinish(Inst inst);
static Count loSegGrains(LOSeg loseg);
static Bool loSegBufferFill(Addr *baseReturn, Addr *limitReturn,
Seg seg, Size size, RankSet rankSet);
static void loSegBufferEmpty(Seg seg, Buffer buffer);
static Res loSegWhiten(Seg seg, Trace trace);
static Res loSegFix(Seg seg, ScanState ss, Ref *refIO);
static void loSegReclaim(Seg seg, Trace trace);
static void loSegWalk(Seg seg, Format format, FormattedObjectsVisitor f,
void *p, size_t s);
/* LOSegClass -- Class definition for LO segments */
DEFINE_CLASS(Seg, LOSeg, klass)
{
INHERIT_CLASS(klass, LOSeg, MutatorSeg);
SegClassMixInNoSplitMerge(klass);
klass->instClassStruct.finish = loSegFinish;
klass->size = sizeof(LOSegStruct);
klass->init = loSegInit;
klass->bufferFill = loSegBufferFill;
klass->bufferEmpty = loSegBufferEmpty;
klass->whiten = loSegWhiten;
klass->fix = loSegFix;
klass->fixEmergency = loSegFix;
klass->reclaim = loSegReclaim;
klass->walk = loSegWalk;
AVERT(SegClass, klass);
}
/* LOSegCheck -- check an LO segment */
ATTRIBUTE_UNUSED
static Bool LOSegCheck(LOSeg loseg)
{
Seg seg = MustBeA(Seg, loseg);
Pool pool = SegPool(seg);
CHECKS(LOSeg, loseg);
CHECKD(GCSeg, &loseg->gcSegStruct);
CHECKL(loseg->mark != NULL);
CHECKL(loseg->alloc != NULL);
/* Could check exactly how many bits are set in the alloc table. */
CHECKL(loseg->freeGrains + loseg->bufferedGrains + loseg->newGrains
+ loseg->oldGrains
== PoolSizeGrains(pool, SegSize(seg)));
return TRUE;
}
/* loSegInit -- Init method for LO segments */
static Res loSegInit(Seg seg, Pool pool, Addr base, Size size, ArgList args)
{
LOSeg loseg;
Res res;
Size tableSize; /* # bytes in each control array */
Arena arena = PoolArena(pool);
/* number of bits needed in each control array */
Count grains;
void *p;
/* Initialize the superclass fields first via next-method call */
res = NextMethod(Seg, LOSeg, init)(seg, pool, base, size, args);
if(res != ResOK)
goto failSuperInit;
loseg = CouldBeA(LOSeg, seg);
AVER(SegWhite(seg) == TraceSetEMPTY);
grains = PoolSizeGrains(pool, size);
tableSize = BTSize(grains);
res = ControlAlloc(&p, arena, 2 * tableSize);
if(res != ResOK)
goto failControlAlloc;
loseg->mark = p;
loseg->alloc = PointerAdd(p, tableSize);
BTResRange(loseg->alloc, 0, grains);
loseg->freeGrains = grains;
loseg->bufferedGrains = (Count)0;
loseg->newGrains = (Count)0;
loseg->oldGrains = (Count)0;
SetClassOfPoly(seg, CLASS(LOSeg));
loseg->sig = LOSegSig;
AVERC(LOSeg, loseg);
return ResOK;
failControlAlloc:
NextMethod(Inst, LOSeg, finish)(MustBeA(Inst, seg));
failSuperInit:
AVER(res != ResOK);
return res;
}
/* loSegFinish -- Finish method for LO segments */
static void loSegFinish(Inst inst)
{
Seg seg = MustBeA(Seg, inst);
LOSeg loseg = MustBeA(LOSeg, seg);
Pool pool = SegPool(seg);
Arena arena = PoolArena(pool);
Size tablesize;
Count grains;
loseg->sig = SigInvalid;
grains = loSegGrains(loseg);
tablesize = BTSize(grains);
ControlFree(arena, loseg->mark, 2 * tablesize);
NextMethod(Inst, LOSeg, finish)(inst);
}
ATTRIBUTE_UNUSED
static Count loSegGrains(LOSeg loseg)
{
Seg seg = MustBeA(Seg, loseg);
Pool pool = SegPool(seg);
Size size = SegSize(seg);
return PoolSizeGrains(pool, size);
}
/* loSegBufferFill -- try filling buffer from segment */
static Bool loSegBufferFill(Addr *baseReturn, Addr *limitReturn,
Seg seg, Size size, RankSet rankSet)
{
Index baseIndex, limitIndex;
LOSeg loseg = MustBeA_CRITICAL(LOSeg, seg);
Pool pool = SegPool(seg);
Count requestedGrains, segGrains, allocatedGrains;
Addr segBase, base, limit;
AVER_CRITICAL(baseReturn != NULL);
AVER_CRITICAL(limitReturn != NULL);
AVER_CRITICAL(SizeIsAligned(size, PoolAlignment(pool)));
AVER_CRITICAL(size > 0);
AVER_CRITICAL(rankSet == RankSetEMPTY);
requestedGrains = PoolSizeGrains(pool, size);
if (loseg->freeGrains < requestedGrains)
/* Not enough space to satisfy the request. */
return FALSE;
if (SegHasBuffer(seg))
/* Don't bother trying to allocate from a buffered segment */
return FALSE;
segGrains = PoolSizeGrains(pool, SegSize(seg));
if (loseg->freeGrains == segGrains) {
/* Whole segment is free: no need for a search. */
baseIndex = 0;
limitIndex = segGrains;
goto found;
}
if (!BTFindLongResRange(&baseIndex, &limitIndex, loseg->alloc,
0, segGrains, requestedGrains))
return FALSE;
found:
AVER(baseIndex < limitIndex);
allocatedGrains = limitIndex - baseIndex;
AVER(requestedGrains <= allocatedGrains);
AVER(BTIsResRange(loseg->alloc, baseIndex, limitIndex));
/* Objects are allocated black. */
/* TODO: This should depend on trace phase. */
BTSetRange(loseg->alloc, baseIndex, limitIndex);
BTSetRange(loseg->mark, baseIndex, limitIndex);
AVER(loseg->freeGrains >= allocatedGrains);
loseg->freeGrains -= allocatedGrains;
loseg->bufferedGrains += allocatedGrains;
segBase = SegBase(seg);
base = PoolAddrOfIndex(segBase, pool, baseIndex);
limit = PoolAddrOfIndex(segBase, pool, limitIndex);
PoolGenAccountForFill(PoolSegPoolGen(pool, seg), AddrOffset(base, limit));
*baseReturn = base;
*limitReturn = limit;
return TRUE;
}
/* loSegBufferEmpty -- empty buffer to segment */
static void loSegBufferEmpty(Seg seg, Buffer buffer)
{
LOSeg loseg = MustBeA(LOSeg, seg);
Pool pool = SegPool(seg);
Addr segBase, bufferBase, init, limit;
Index initIndex, limitIndex;
Count unusedGrains, usedGrains;
AVERT(Seg, seg);
AVERT(Buffer, buffer);
segBase = SegBase(seg);
bufferBase = BufferBase(buffer);
init = BufferGetInit(buffer);
limit = BufferLimit(buffer);
AVER(segBase <= bufferBase);
AVER(bufferBase <= init);
AVER(init <= limit);
AVER(limit <= SegLimit(seg));
initIndex = PoolIndexOfAddr(segBase, pool, init);
limitIndex = PoolIndexOfAddr(segBase, pool, limit);
if (initIndex < limitIndex)
BTResRange(loseg->alloc, initIndex, limitIndex);
unusedGrains = limitIndex - initIndex;
AVER(unusedGrains <= loseg->bufferedGrains);
usedGrains = loseg->bufferedGrains - unusedGrains;
loseg->freeGrains += unusedGrains;
loseg->bufferedGrains = 0;
loseg->newGrains += usedGrains;
PoolGenAccountForEmpty(PoolSegPoolGen(pool, seg),
PoolGrainsSize(pool, usedGrains),
PoolGrainsSize(pool, unusedGrains), FALSE);
}
/* loSegReclaim -- reclaim white objects in an LO segment
*
* Could consider implementing this using Walk.
*/
static void loSegReclaim(Seg seg, Trace trace)
{
LOSeg loseg = MustBeA(LOSeg, seg);
Pool pool = SegPool(seg);
PoolGen pgen = PoolSegPoolGen(pool, seg);
Addr p, base, limit;
Buffer buffer;
Bool hasBuffer = SegBuffer(&buffer, seg);
Count reclaimedGrains = (Count)0;
Format format = NULL; /* supress "may be used uninitialized" warning */
Count preservedInPlaceCount = (Count)0;
Size preservedInPlaceSize = (Size)0;
Bool b;
AVERT(Trace, trace);
base = SegBase(seg);
limit = SegLimit(seg);
b = PoolFormat(&format, pool);
AVER(b);
/* i is the index of the current pointer,
* p is the actual address that is being considered.
* j and q act similarly for a pointer which is used to
* point at the end of the current object.
*/
p = base;
while(p < limit) {
Addr q;
Index i;
if (hasBuffer) {
if (p == BufferScanLimit(buffer)
&& BufferScanLimit(buffer) != BufferLimit(buffer)) {
/* skip over buffered area */
p = BufferLimit(buffer);
continue;
}
/* since we skip over the buffered area we are always */
/* either before the buffer, or after it, never in it */
AVER(p < BufferGetInit(buffer) || BufferLimit(buffer) <= p);
}
i = PoolIndexOfAddr(base, pool, p);
if(!BTGet(loseg->alloc, i)) {
/* This grain is free */
p = AddrAdd(p, pool->alignment);
continue;
}
q = (*format->skip)(AddrAdd(p, format->headerSize));
q = AddrSub(q, format->headerSize);
if(BTGet(loseg->mark, i)) {
++preservedInPlaceCount;
preservedInPlaceSize += AddrOffset(p, q);
} else {
Index j = PoolIndexOfAddr(base, pool, q);
/* This object is not marked, so free it */
BTResRange(loseg->alloc, i, j);
reclaimedGrains += j - i;
}
p = q;
}
AVER(p == limit);
AVER(reclaimedGrains <= loSegGrains(loseg));
AVER(loseg->oldGrains >= reclaimedGrains);
loseg->oldGrains -= reclaimedGrains;
loseg->freeGrains += reclaimedGrains;
PoolGenAccountForReclaim(pgen, PoolGrainsSize(pool, reclaimedGrains), FALSE);
STATISTIC(trace->reclaimSize += PoolGrainsSize(pool, reclaimedGrains));
STATISTIC(trace->preservedInPlaceCount += preservedInPlaceCount);
GenDescSurvived(pgen->gen, trace, 0, preservedInPlaceSize);
SegSetWhite(seg, TraceSetDel(SegWhite(seg), trace));
if (loseg->freeGrains == PoolSizeGrains(pool, SegSize(seg)) && !hasBuffer) {
AVER(loseg->bufferedGrains == 0);
PoolGenFree(pgen, seg,
PoolGrainsSize(pool, loseg->freeGrains),
PoolGrainsSize(pool, loseg->oldGrains),
PoolGrainsSize(pool, loseg->newGrains),
FALSE);
}
}
/* Walks over _all_ objects in the segnent: whether they are black or
* white, they are still validly formatted as this is a leaf pool, so
* there can't be any dangling references.
*/
static void loSegWalk(Seg seg, Format format, FormattedObjectsVisitor f,
void *p, size_t s)
{
Addr base;
LOSeg loseg = MustBeA(LOSeg, seg);
Pool pool = SegPool(seg);
Index i, grains;
AVERT(Format, format);
AVER(FUNCHECK(f));
/* p and s are arbitrary closures and can't be checked */
base = SegBase(seg);
grains = loSegGrains(loseg);
i = 0;
while(i < grains) {
/* object is a slight misnomer because it might point to a */
/* free grain */
Addr object = PoolAddrOfIndex(base, pool, i);
Addr next;
Index j;
Buffer buffer;
if (SegBuffer(&buffer, seg)) {
if(object == BufferScanLimit(buffer) &&
BufferScanLimit(buffer) != BufferLimit(buffer)) {
/* skip over buffered area */
object = BufferLimit(buffer);
i = PoolIndexOfAddr(base, pool, object);
continue;
}
/* since we skip over the buffered area we are always */
/* either before the buffer, or after it, never in it */
AVER(object < BufferGetInit(buffer) || BufferLimit(buffer) <= object);
}
if(!BTGet(loseg->alloc, i)) {
/* This grain is free */
++i;
continue;
}
object = AddrAdd(object, format->headerSize);
next = (*format->skip)(object);
next = AddrSub(next, format->headerSize);
j = PoolIndexOfAddr(base, pool, next);
AVER(i < j);
(*f)(object, pool->format, pool, p, s);
i = j;
}
}
/* LOVarargs -- decode obsolete varargs */
static void LOVarargs(ArgStruct args[MPS_ARGS_MAX], va_list varargs)
{
args[0].key = MPS_KEY_FORMAT;
args[0].val.format = va_arg(varargs, Format);
args[1].key = MPS_KEY_ARGS_END;
AVERT(ArgList, args);
}
/* LOInit -- initialize an LO pool */
static Res LOInit(Pool pool, Arena arena, PoolClass klass, ArgList args)
{
LO lo;
Res res;
ArgStruct arg;
Chain chain;
unsigned gen = LO_GEN_DEFAULT;
AVER(pool != NULL);
AVERT(Arena, arena);
AVERT(ArgList, args);
UNUSED(klass); /* used for debug pools only */
res = NextMethod(Pool, LOPool, init)(pool, arena, klass, args);
if (res != ResOK)
goto failNextInit;
lo = CouldBeA(LOPool, pool);
/* Ensure a format was supplied in the argument list. */
AVER(pool->format != NULL);
if (ArgPick(&arg, args, MPS_KEY_CHAIN))
chain = arg.val.chain;
else {
chain = ArenaGlobals(arena)->defaultChain;
gen = 1; /* avoid the nursery of the default chain by default */
}
if (ArgPick(&arg, args, MPS_KEY_GEN))
gen = arg.val.u;
AVERT(Format, pool->format);
AVER(FormatArena(pool->format) == arena);
AVERT(Chain, chain);
AVER(gen <= ChainGens(chain));
AVER(chain->arena == arena);
pool->alignment = pool->format->alignment;
pool->alignShift = SizeLog2(pool->alignment);
lo->pgen = NULL;
SetClassOfPoly(pool, CLASS(LOPool));
lo->sig = LOSig;
AVERC(LOPool, lo);
res = PoolGenInit(&lo->pgenStruct, ChainGen(chain, gen), pool);
if (res != ResOK)
goto failGenInit;
lo->pgen = &lo->pgenStruct;
EVENT2(PoolInitLO, pool, pool->format);
return ResOK;
failGenInit:
NextMethod(Inst, LOPool, finish)(MustBeA(Inst, pool));
failNextInit:
AVER(res != ResOK);
return res;
}
/* LOFinish -- finish an LO pool */
static void LOFinish(Inst inst)
{
Pool pool = MustBeA(AbstractPool, inst);
LO lo = MustBeA(LOPool, pool);
Ring node, nextNode;
RING_FOR(node, &pool->segRing, nextNode) {
Seg seg = SegOfPoolRing(node);
LOSeg loseg = MustBeA(LOSeg, seg);
AVER(!SegHasBuffer(seg));
AVERT(LOSeg, loseg);
AVER(loseg->bufferedGrains == 0);
PoolGenFree(lo->pgen, seg,
PoolGrainsSize(pool, loseg->freeGrains),
PoolGrainsSize(pool, loseg->oldGrains),
PoolGrainsSize(pool, loseg->newGrains),
FALSE);
}
PoolGenFinish(lo->pgen);
lo->sig = SigInvalid;
NextMethod(Inst, LOPool, finish)(inst);
}
static Res LOBufferFill(Addr *baseReturn, Addr *limitReturn,
Pool pool, Buffer buffer,
Size size)
{
LO lo = MustBeA(LOPool, pool);
Res res;
Ring node, nextNode;
RankSet rankSet;
Seg seg;
Bool b;
AVER(baseReturn != NULL);
AVER(limitReturn != NULL);
AVERC(Buffer, buffer);
AVER(BufferIsReset(buffer));
AVER(BufferRankSet(buffer) == RankSetEMPTY);
AVER(size > 0);
AVER(SizeIsAligned(size, PoolAlignment(pool)));
/* Try to find a segment with enough space already. */
rankSet = BufferRankSet(buffer);
RING_FOR(node, PoolSegRing(pool), nextNode) {
seg = SegOfPoolRing(node);
if (SegBufferFill(baseReturn, limitReturn, seg, size, rankSet))
return ResOK;
}
/* No segment had enough space, so make a new one. */
res = PoolGenAlloc(&seg, lo->pgen, CLASS(LOSeg),
SizeArenaGrains(size, PoolArena(pool)),
argsNone);
if (res != ResOK)
return res;
b = SegBufferFill(baseReturn, limitReturn, seg, size, rankSet);
AVER(b);
return ResOK;
}
/* Synchronise the buffer with the alloc Bit Table in the segment. */
/* loSegPoolGen -- get pool generation for an LO segment */
static PoolGen loSegPoolGen(Pool pool, Seg seg)
{
LO lo = MustBeA(LOPool, pool);
AVERT(Seg, seg);
return lo->pgen;
}
/* loSegWhiten -- whiten a segment */
static Res loSegWhiten(Seg seg, Trace trace)
{
LOSeg loseg = MustBeA(LOSeg, seg);
Pool pool = SegPool(seg);
PoolGen pgen = PoolSegPoolGen(pool, seg);
Buffer buffer;
Count grains, agedGrains, uncondemnedGrains;
AVERT(Trace, trace);
AVER(SegWhite(seg) == TraceSetEMPTY);
grains = loSegGrains(loseg);
/* Whiten allocated objects; leave free areas black. */
if (SegBuffer(&buffer, seg)) {
Addr base = SegBase(seg);
Index scanLimitIndex = PoolIndexOfAddr(base, pool, BufferScanLimit(buffer));
Index limitIndex = PoolIndexOfAddr(base, pool, BufferLimit(buffer));
uncondemnedGrains = limitIndex - scanLimitIndex;
if (0 < scanLimitIndex)
BTCopyInvertRange(loseg->alloc, loseg->mark, 0, scanLimitIndex);
if (limitIndex < grains)
BTCopyInvertRange(loseg->alloc, loseg->mark, limitIndex, grains);
} else {
uncondemnedGrains = (Count)0;
BTCopyInvertRange(loseg->alloc, loseg->mark, 0, grains);
}
/* The unused part of the buffer remains buffered: the rest becomes old. */
AVER(loseg->bufferedGrains >= uncondemnedGrains);
agedGrains = loseg->bufferedGrains - uncondemnedGrains;
PoolGenAccountForAge(pgen, PoolGrainsSize(pool, agedGrains),
PoolGrainsSize(pool, loseg->newGrains), FALSE);
loseg->oldGrains += agedGrains + loseg->newGrains;
loseg->bufferedGrains = uncondemnedGrains;
loseg->newGrains = 0;
if (loseg->oldGrains > 0) {
GenDescCondemned(pgen->gen, trace,
PoolGrainsSize(pool, loseg->oldGrains));
SegSetWhite(seg, TraceSetAdd(SegWhite(seg), trace));
}
return ResOK;
}
static Res loSegFix(Seg seg, ScanState ss, Ref *refIO)
{
LOSeg loseg = MustBeA_CRITICAL(LOSeg, seg);
Pool pool = SegPool(seg);
Ref clientRef;
Addr base;
Index i;
AVERT_CRITICAL(ScanState, ss);
AVER_CRITICAL(TraceSetInter(SegWhite(seg), ss->traces) != TraceSetEMPTY);
AVER_CRITICAL(refIO != NULL);
clientRef = *refIO;
base = AddrSub((Addr)clientRef, pool->format->headerSize);
/* Not a real reference if out of bounds. This can happen if an
ambiguous reference is closer to the base of the segment than the
header size. */
if (base < SegBase(seg)) {
AVER(ss->rank == RankAMBIG);
return ResOK;
}
/* Not a real reference if unaligned. */
if (!AddrIsAligned(base, PoolAlignment(pool))) {
AVER(ss->rank == RankAMBIG);
return ResOK;
}
i = PoolIndexOfAddr(SegBase(seg), pool, base);
/* Not a real reference if unallocated. */
if (!BTGet(loseg->alloc, i)) {
AVER(ss->rank == RankAMBIG);
return ResOK;
}
if(!BTGet(loseg->mark, i)) {
ss->wasMarked = FALSE; /* <design/fix#.was-marked.not> */
if(ss->rank == RankWEAK) {
*refIO = (Addr)0;
} else {
BTSet(loseg->mark, i);
}
}
return ResOK;
}
/* LOTotalSize -- total memory allocated from the arena */
/* TODO: This code is repeated in AMS */
static Size LOTotalSize(Pool pool)
{
LO lo = MustBeA(LOPool, pool);
return lo->pgen->totalSize;
}
/* LOFreeSize -- free memory (unused by client program) */
/* TODO: This code is repeated in AMS */
static Size LOFreeSize(Pool pool)
{
LO lo = MustBeA(LOPool, pool);
return lo->pgen->freeSize;
}
/* LOPoolClass -- the class definition */
DEFINE_CLASS(Pool, LOPool, klass)
{
INHERIT_CLASS(klass, LOPool, AbstractCollectPool);
klass->instClassStruct.finish = LOFinish;
klass->size = sizeof(LOStruct);
klass->varargs = LOVarargs;
klass->init = LOInit;
klass->bufferFill = LOBufferFill;
klass->segPoolGen = loSegPoolGen;
klass->totalSize = LOTotalSize;
klass->freeSize = LOFreeSize;
AVERT(PoolClass, klass);
}
/* mps_class_lo -- the external interface to get the LO pool class */
mps_pool_class_t mps_class_lo(void)
{
return (mps_pool_class_t)CLASS(LOPool);
}
/* LOCheck -- check an LO pool */
ATTRIBUTE_UNUSED
static Bool LOCheck(LO lo)
{
CHECKS(LO, lo);
CHECKC(LOPool, lo);
CHECKD(Pool, &lo->poolStruct);
CHECKC(LOPool, lo);
if (lo->pgen != NULL) {
CHECKL(lo->pgen == &lo->pgenStruct);
CHECKD(PoolGen, lo->pgen);
}
return TRUE;
}
/* C. COPYRIGHT AND LICENSE
*
* Copyright (C) 2001-2020 Ravenbrook Limited <http://www.ravenbrook.com/>.
*
* 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.
*
* 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 AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR 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.
*/