mirror of
git://git.sv.gnu.org/emacs.git
synced 2025-12-15 10:30:25 -08:00
Eliminating complex zonedcbs adt and reintroducing a cbs with zone summaries. removing the default “high” segment preference, that caused the cbs trees to unbalance, and isn’t required outside of ep. considerable reduction in code and speed up, now at parity with master on test xc/release/gcbench -x 1234 amc
Copied from Perforce Change: 184587 ServerID: perforce.ravenbrook.com
This commit is contained in:
parent
7a33207595
commit
ddfec2c708
11 changed files with 113 additions and 563 deletions
|
|
@ -9,7 +9,6 @@
|
|||
#include "poolmv.h"
|
||||
#include "mpm.h"
|
||||
#include "cbs.h"
|
||||
#include "zonedcbs.h"
|
||||
#include "bt.h"
|
||||
#include "poolmfs.h"
|
||||
#include "mpscmfs.h"
|
||||
|
|
@ -155,7 +154,7 @@ Bool ArenaCheck(Arena arena)
|
|||
|
||||
CHECKL(BoolCheck(arena->hasFreeCBS));
|
||||
if (arena->hasFreeCBS)
|
||||
ZonedCBSCheck(ArenaZonedCBS(arena));
|
||||
CBSCheck(ArenaZonedCBS(arena));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -221,11 +220,18 @@ Res ArenaInit(Arena arena, ArenaClass class, Align alignment)
|
|||
if (res != ResOK)
|
||||
goto failMFSInit;
|
||||
|
||||
res = ZonedCBSInit(ArenaZonedCBS(arena), arena, ArenaCBSBlockPool(arena),
|
||||
ArenaAlign(arena));
|
||||
/* Initialise the freeCBS. */
|
||||
MPS_ARGS_BEGIN(cbsiArgs) {
|
||||
MPS_ARGS_ADD(cbsiArgs, CBSBlockPool, ArenaCBSBlockPool(arena));
|
||||
MPS_ARGS_DONE(cbsiArgs);
|
||||
res = CBSInit(arena, ArenaZonedCBS(arena), arena, alignment, TRUE, TRUE, cbsiArgs);
|
||||
} MPS_ARGS_END(cbsiArgs);
|
||||
AVER(res == ResOK); /* no allocation, no failure expected */
|
||||
if (res != ResOK)
|
||||
goto failCBSInit;
|
||||
|
||||
/* Note that although freeCBS is initialised, it doesn't have any memory
|
||||
for its blocks, so hasFreeCBS remains FALSE until later. */
|
||||
|
||||
/* initialize the reservoir, <design/reservoir/> */
|
||||
res = ReservoirInit(&arena->reservoirStruct, arena);
|
||||
if (res != ResOK)
|
||||
|
|
@ -235,7 +241,7 @@ Res ArenaInit(Arena arena, ArenaClass class, Align alignment)
|
|||
return ResOK;
|
||||
|
||||
failReservoirInit:
|
||||
ZonedCBSFinish(ArenaZonedCBS(arena));
|
||||
CBSFinish(ArenaZonedCBS(arena));
|
||||
failCBSInit:
|
||||
PoolFinish(ArenaCBSBlockPool(arena));
|
||||
failMFSInit:
|
||||
|
|
@ -364,7 +370,7 @@ void ArenaDestroy(Arena arena)
|
|||
containing CBS blocks might be allocated in those chunks. */
|
||||
AVER(arena->hasFreeCBS);
|
||||
arena->hasFreeCBS = FALSE;
|
||||
ZonedCBSFinish(ArenaZonedCBS(arena));
|
||||
CBSFinish(ArenaZonedCBS(arena));
|
||||
|
||||
/* The CBS block pool can't free its own memory via ArenaFree because
|
||||
that would use the ZonedCBS. */
|
||||
|
|
@ -675,7 +681,7 @@ static void arenaExcludePage(Arena arena, Range pageRange)
|
|||
RangeStruct oldRange;
|
||||
Res res;
|
||||
|
||||
res = ZonedCBSDelete(&oldRange, ArenaZonedCBS(arena), pageRange);
|
||||
res = CBSDelete(&oldRange, ArenaZonedCBS(arena), pageRange);
|
||||
AVER(res == ResOK); /* we just gave memory to the CBSs */
|
||||
}
|
||||
|
||||
|
|
@ -696,7 +702,7 @@ static Res arenaCBSInsert(Range rangeReturn, Arena arena, Range range)
|
|||
AVERT(Arena, arena);
|
||||
AVERT(Range, range);
|
||||
|
||||
res = ZonedCBSInsert(rangeReturn, ArenaZonedCBS(arena), range);
|
||||
res = CBSInsert(rangeReturn, ArenaZonedCBS(arena), range);
|
||||
|
||||
if (res == ResLIMIT) { /* freeCBS MFS pool ran out of blocks */
|
||||
RangeStruct pageRange;
|
||||
|
|
@ -704,8 +710,8 @@ static Res arenaCBSInsert(Range rangeReturn, Arena arena, Range range)
|
|||
if (res != ResOK)
|
||||
return res;
|
||||
/* Must insert before exclude so that we can bootstrap when the
|
||||
ZonedCBS is empty. */
|
||||
res = ZonedCBSInsert(rangeReturn, ArenaZonedCBS(arena), range);
|
||||
zoned CBS is empty. */
|
||||
res = CBSInsert(rangeReturn, ArenaZonedCBS(arena), range);
|
||||
AVER(res == ResOK); /* we just gave memory to the CBSs */
|
||||
arenaExcludePage(arena, &pageRange);
|
||||
}
|
||||
|
|
@ -752,7 +758,7 @@ static void arenaCBSInsertSteal(Range rangeReturn, Arena arena, Range rangeIO)
|
|||
MFSExtend(ArenaCBSBlockPool(arena), pageBase, ArenaAlign(arena));
|
||||
|
||||
/* Try again. */
|
||||
res = ZonedCBSInsert(rangeReturn, ArenaZonedCBS(arena), rangeIO);
|
||||
res = CBSInsert(rangeReturn, ArenaZonedCBS(arena), rangeIO);
|
||||
AVER(res == ResOK); /* we just gave memory to the CBS */
|
||||
}
|
||||
|
||||
|
|
@ -803,7 +809,7 @@ void ArenaFreeCBSDelete(Arena arena, Addr base, Addr limit)
|
|||
Res res;
|
||||
|
||||
RangeInit(&range, base, limit);
|
||||
res = ZonedCBSDelete(&oldRange, ArenaZonedCBS(arena), &range);
|
||||
res = CBSDelete(&oldRange, ArenaZonedCBS(arena), &range);
|
||||
|
||||
/* Shouldn't be any other kind of failure because we were only deleting
|
||||
a non-coalesced block. See .chunk.no-coalesce. */
|
||||
|
|
@ -823,6 +829,7 @@ static Res arenaAllocFromCBS(Tract *tractReturn, ZoneSet zones, Bool high,
|
|||
Count pages;
|
||||
Res res;
|
||||
FindDelete fd;
|
||||
CBSFindInZonesMethod find;
|
||||
|
||||
AVER(tractReturn != NULL);
|
||||
/* ZoneSet is arbitrary */
|
||||
|
|
@ -834,8 +841,8 @@ static Res arenaAllocFromCBS(Tract *tractReturn, ZoneSet zones, Bool high,
|
|||
/* Step 1. Find a range of address space. */
|
||||
|
||||
fd = high ? FindDeleteHIGH : FindDeleteLOW;
|
||||
res = ZonedCBSFind(&range, &oldRange, ArenaZonedCBS(arena),
|
||||
zones, size, fd, high);
|
||||
find = high ? CBSFindLastInZones : CBSFindFirstInZones;
|
||||
res = find(&range, &oldRange, ArenaZonedCBS(arena), size, arena, zones);
|
||||
|
||||
if (res == ResLIMIT) { /* found block, but couldn't store info */
|
||||
RangeStruct pageRange;
|
||||
|
|
@ -843,8 +850,7 @@ static Res arenaAllocFromCBS(Tract *tractReturn, ZoneSet zones, Bool high,
|
|||
if (res != ResOK) /* disasterously short on memory */
|
||||
return res;
|
||||
arenaExcludePage(arena, &pageRange);
|
||||
res = ZonedCBSFind(&range, &oldRange, ArenaZonedCBS(arena),
|
||||
zones, size, fd, high);
|
||||
res = find(&range, &oldRange, ArenaZonedCBS(arena), size, arena, zones);
|
||||
AVER(res != ResLIMIT);
|
||||
}
|
||||
|
||||
|
|
@ -876,7 +882,7 @@ static Res arenaAllocFromCBS(Tract *tractReturn, ZoneSet zones, Bool high,
|
|||
failMark:
|
||||
NOTREACHED; /* FIXME */
|
||||
{
|
||||
Res insertRes = ZonedCBSInsert(&oldRange, ArenaZonedCBS(arena), &range);
|
||||
Res insertRes = CBSInsert(&oldRange, ArenaZonedCBS(arena), &range);
|
||||
AVER(insertRes == ResOK); /* We only just deleted it. */
|
||||
/* If the insert does fail, we lose some address space permanently. */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ Bool CBSCheck(CBS cbs)
|
|||
CHECKL(SplayTreeCheck(splayTreeOfCBS(cbs)));
|
||||
/* nothing to check about splayTreeSize */
|
||||
CHECKD(Pool, cbs->blockPool);
|
||||
CHECKU(Arena, cbs->arena);
|
||||
CHECKL(BoolCheck(cbs->fastFind));
|
||||
CHECKL(BoolCheck(cbs->inCBS));
|
||||
CHECKL(BoolCheck(cbs->ownPool));
|
||||
|
|
@ -198,6 +199,48 @@ static void cbsUpdateNode(SplayTree tree, SplayNode node,
|
|||
}
|
||||
|
||||
|
||||
/* cbsUpdateZonedNode -- update size and zone info after restructuring */
|
||||
|
||||
static void cbsUpdateZonedNode(SplayTree tree, SplayNode node,
|
||||
SplayNode leftChild, SplayNode rightChild)
|
||||
{
|
||||
Size maxSize;
|
||||
ZoneSet zones;
|
||||
CBSBlock block;
|
||||
Arena arena;
|
||||
|
||||
AVERT(SplayTree, tree);
|
||||
AVERT(SplayNode, node);
|
||||
if (leftChild != NULL)
|
||||
AVERT(SplayNode, leftChild);
|
||||
if (rightChild != NULL)
|
||||
AVERT(SplayNode, rightChild);
|
||||
AVER(cbsOfSplayTree(tree)->fastFind);
|
||||
|
||||
block = cbsBlockOfSplayNode(node);
|
||||
maxSize = CBSBlockSize(block);
|
||||
arena = cbsOfSplayTree(tree)->arena;
|
||||
zones = ZoneSetOfRange(arena, CBSBlockBase(block), CBSBlockLimit(block));
|
||||
|
||||
if (leftChild != NULL) {
|
||||
Size size = cbsBlockOfSplayNode(leftChild)->maxSize;
|
||||
if (size > maxSize)
|
||||
maxSize = size;
|
||||
zones = ZoneSetUnion(zones, cbsBlockOfSplayNode(leftChild)->zones);
|
||||
}
|
||||
|
||||
if (rightChild != NULL) {
|
||||
Size size = cbsBlockOfSplayNode(rightChild)->maxSize;
|
||||
if (size > maxSize)
|
||||
maxSize = size;
|
||||
zones = ZoneSetUnion(zones, cbsBlockOfSplayNode(rightChild)->zones);
|
||||
}
|
||||
|
||||
block->maxSize = maxSize;
|
||||
block->zones = zones;
|
||||
}
|
||||
|
||||
|
||||
/* CBSInit -- Initialise a CBS structure
|
||||
*
|
||||
* See <design/cbs/#function.cbs.init>.
|
||||
|
|
@ -207,15 +250,21 @@ ARG_DEFINE_KEY(cbs_extend_by, Size);
|
|||
ARG_DEFINE_KEY(cbs_block_pool, Pool);
|
||||
|
||||
Res CBSInit(Arena arena, CBS cbs, void *owner, Align alignment,
|
||||
Bool fastFind, ArgList args)
|
||||
Bool fastFind, Bool zoned, ArgList args)
|
||||
{
|
||||
Size extendBy = CBS_EXTEND_BY_DEFAULT;
|
||||
Bool extendSelf = TRUE;
|
||||
ArgStruct arg;
|
||||
Res res;
|
||||
Pool blockPool = NULL;
|
||||
SplayUpdateNodeMethod update;
|
||||
|
||||
AVERT(Arena, arena);
|
||||
AVER(cbs != NULL);
|
||||
AVER(AlignCheck(alignment));
|
||||
AVER(BoolCheck(fastFind));
|
||||
AVER(BoolCheck(zoned));
|
||||
|
||||
|
||||
if (ArgPick(&arg, args, CBSBlockPool))
|
||||
blockPool = arg.val.pool;
|
||||
|
|
@ -224,8 +273,15 @@ Res CBSInit(Arena arena, CBS cbs, void *owner, Align alignment,
|
|||
if (ArgPick(&arg, args, MFSExtendSelf))
|
||||
extendSelf = arg.val.b;
|
||||
|
||||
SplayTreeInit(splayTreeOfCBS(cbs), &cbsSplayCompare,
|
||||
fastFind ? &cbsUpdateNode : NULL);
|
||||
update = NULL;
|
||||
if (fastFind)
|
||||
update = cbsUpdateNode;
|
||||
if (zoned) {
|
||||
AVER(fastFind);
|
||||
update = cbsUpdateZonedNode;
|
||||
}
|
||||
|
||||
SplayTreeInit(splayTreeOfCBS(cbs), cbsSplayCompare, update);
|
||||
|
||||
if (blockPool != NULL) {
|
||||
cbs->blockPool = blockPool;
|
||||
|
|
@ -245,7 +301,9 @@ Res CBSInit(Arena arena, CBS cbs, void *owner, Align alignment,
|
|||
|
||||
cbs->splayTreeSize = 0;
|
||||
|
||||
cbs->arena = arena;
|
||||
cbs->fastFind = fastFind;
|
||||
cbs->zoned = zoned;
|
||||
cbs->alignment = alignment;
|
||||
cbs->inCBS = TRUE;
|
||||
|
||||
|
|
@ -812,6 +870,20 @@ static Bool cbsTestNodeInZones(SplayTree tree, SplayNode node,
|
|||
closure->arena, closure->zoneSet, closure->size);
|
||||
}
|
||||
|
||||
static Bool cbsTestTreeInZones(SplayTree tree, SplayNode node,
|
||||
void *closureP, Size closureSize)
|
||||
{
|
||||
CBSBlock block = cbsBlockOfSplayNode(node);
|
||||
cbsTestNodeInZonesClosure closure = closureP;
|
||||
|
||||
UNUSED(tree);
|
||||
AVER(closureSize == sizeof(cbsTestNodeInZonesClosureStruct));
|
||||
UNUSED(closureSize);
|
||||
|
||||
return block->maxSize >= closure->size &&
|
||||
ZoneSetInter(block->zones, closure->zoneSet) != ZoneSetEMPTY;
|
||||
}
|
||||
|
||||
static Res CBSFindInZones(Range rangeReturn, Range oldRangeReturn,
|
||||
CBS cbs, Size size,
|
||||
Arena arena, ZoneSet zoneSet,
|
||||
|
|
@ -855,7 +927,7 @@ static Res CBSFindInZones(Range rangeReturn, Range oldRangeReturn,
|
|||
closure.size = size;
|
||||
if (splayFind(&node, splayTreeOfCBS(cbs),
|
||||
&cbsTestNodeInZones,
|
||||
&cbsTestTree,
|
||||
&cbsTestTreeInZones,
|
||||
&closure, sizeof(closure))) {
|
||||
CBSBlock block = cbsBlockOfSplayNode(node);
|
||||
RangeStruct rangeStruct, oldRangeStruct;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@
|
|||
#include "splay.h"
|
||||
|
||||
|
||||
/* TODO: There ought to be different levels of CBS block with inheritance
|
||||
so that CBSs without fastFind don't allocate the maxSize and zones fields,
|
||||
and CBSs without zoned don't allocate the zones field. */
|
||||
|
||||
typedef struct CBSBlockStruct *CBSBlock;
|
||||
typedef struct CBSBlockStruct {
|
||||
SplayNodeStruct splayNode;
|
||||
|
|
@ -36,8 +40,8 @@ extern const struct mps_key_s _mps_key_cbs_block_pool;
|
|||
#define CBSBlockPool (&_mps_key_cbs_block_pool)
|
||||
#define CBSBlockPool_FIELD pool
|
||||
|
||||
extern Res CBSInit(Arena arena, CBS cbs, void *owner,
|
||||
Align alignment, Bool fastFind, ArgList args);
|
||||
extern Res CBSInit(Arena arena, CBS cbs, void *owner, Align alignment,
|
||||
Bool fastFind, Bool zoned, ArgList args);
|
||||
extern void CBSFinish(CBS cbs);
|
||||
|
||||
extern Res CBSInsert(Range rangeReturn, CBS cbs, Range range);
|
||||
|
|
|
|||
|
|
@ -307,12 +307,9 @@
|
|||
/* @@@@ knows the implementation of ZoneSets */
|
||||
/* FIXME: There's no particular reason to think this will avoid GC segments. */
|
||||
|
||||
/* .segpref.default: For EPcore, non-DL segments should be placed high */
|
||||
/* to reduce fragmentation of DL pools (see request.epcore.170193_). */
|
||||
/* .. _request.epcore.170193: https://info.ravenbrook.com/project/mps/import/2001-11-05/mmprevol/request/epcore/170193 */
|
||||
#define SegPrefDEFAULT { \
|
||||
SegPrefSig, /* sig */ \
|
||||
TRUE, /* high */ \
|
||||
FALSE, /* high */ \
|
||||
ArenaDefaultZONESET, /* zoneSet */ \
|
||||
ZoneSetEMPTY, /* avoid */ \
|
||||
}
|
||||
|
|
|
|||
|
|
@ -614,9 +614,11 @@ typedef struct GlobalsStruct {
|
|||
typedef struct CBSStruct {
|
||||
SplayTreeStruct splayTree;
|
||||
STATISTIC_DECL(Count splayTreeSize);
|
||||
Arena arena;
|
||||
Pool blockPool;
|
||||
Align alignment;
|
||||
Bool fastFind;
|
||||
Bool fastFind; /* maintain and use size property? */
|
||||
Bool zoned; /* maintain and use zone property? */
|
||||
Bool inCBS; /* prevent reentrance */
|
||||
Bool ownPool; /* did we create blockPool? */
|
||||
/* meters for sizes of search structures at each op */
|
||||
|
|
@ -677,7 +679,7 @@ typedef struct mps_arena_s {
|
|||
|
||||
Bool hasFreeCBS; /* Is freeCBS available? */
|
||||
MFSStruct zonedCBSBlockPoolStruct;
|
||||
ZonedCBSStruct zonedCBSStruct;
|
||||
CBSStruct zonedCBSStruct;
|
||||
ZoneSet freeZones; /* zones not yet allocated */
|
||||
|
||||
/* locus fields (<code/locus.c>) */
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@
|
|||
#include "range.c"
|
||||
#include "freelist.c"
|
||||
#include "sa.c"
|
||||
#include "zonedcbs.c"
|
||||
|
||||
/* Additional pool classes */
|
||||
|
||||
|
|
|
|||
|
|
@ -1250,8 +1250,6 @@
|
|||
3114A6BA156E9768001E0AA3 /* walkt0.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = walkt0.c; sourceTree = "<group>"; };
|
||||
3114A6C6156E9815001E0AA3 /* mpseventcnv */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mpseventcnv; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3114A6D0156E9829001E0AA3 /* eventcnv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = eventcnv.c; sourceTree = "<group>"; };
|
||||
3115E70118BED7E000385449 /* zonedcbs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zonedcbs.h; sourceTree = "<group>"; };
|
||||
3115E70218BEDA1100385449 /* zonedcbs.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zonedcbs.c; sourceTree = "<group>"; };
|
||||
31160D921899540D0071EB17 /* abq.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = abq.txt; path = ../design/abq.txt; sourceTree = "<group>"; };
|
||||
31160D931899540D0071EB17 /* alloc-frame.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = "alloc-frame.txt"; path = "../design/alloc-frame.txt"; sourceTree = "<group>"; };
|
||||
31160D941899540D0071EB17 /* arena.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = arena.txt; path = ../design/arena.txt; sourceTree = "<group>"; };
|
||||
|
|
@ -2137,8 +2135,6 @@
|
|||
31EEAC0E156AB27B00714D05 /* walk.c */,
|
||||
3112ED3A18ABC57F00CC531A /* sa.h */,
|
||||
3112ED3B18ABC75200CC531A /* sa.c */,
|
||||
3115E70118BED7E000385449 /* zonedcbs.h */,
|
||||
3115E70218BEDA1100385449 /* zonedcbs.c */,
|
||||
);
|
||||
name = "MPM Core";
|
||||
sourceTree = "<group>";
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ static Res MVTInit(Pool pool, ArgList args)
|
|||
if (abqDepth < 3)
|
||||
abqDepth = 3;
|
||||
|
||||
res = CBSInit(arena, MVTCBS(mvt), (void *)mvt, align, FALSE, args);
|
||||
res = CBSInit(arena, MVTCBS(mvt), (void *)mvt, align, FALSE, FALSE, args);
|
||||
if (res != ResOK)
|
||||
goto failCBS;
|
||||
|
||||
|
|
|
|||
|
|
@ -602,7 +602,7 @@ static Res MVFFInit(Pool pool, ArgList args)
|
|||
if (res != ResOK)
|
||||
goto failInit;
|
||||
|
||||
res = CBSInit(arena, CBSOfMVFF(mvff), (void *)mvff, align, TRUE, args);
|
||||
res = CBSInit(arena, CBSOfMVFF(mvff), (void *)mvff, align, TRUE, FALSE, args);
|
||||
if (res != ResOK)
|
||||
goto failInit;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,433 +0,0 @@
|
|||
/* zonedcbs.c: ZONED COALESCING BLOCK STRUCTURE IMPLEMENTATION
|
||||
*
|
||||
* $Id$
|
||||
* Copyright (c) 2014 Ravenbrook Limited. See end of file for license.
|
||||
*
|
||||
* A Zone CBS is like a CBS but allows for efficient allocation in zones.
|
||||
* Allocation in zones gives control over some parts of an object's address,
|
||||
* so that we can later apply fast filters on the critical path.
|
||||
* The Zone CBS is mainly used by the arena to allocate blocks to pools.
|
||||
*
|
||||
* Can be stressed by setting a small arena size.
|
||||
*
|
||||
* This code is essentially an optimisation of CBSFindFirstInZones, but
|
||||
* doesn't have quite the same effect. FIXME: Be clearer.
|
||||
*/
|
||||
|
||||
#include "zonedcbs.h"
|
||||
#include "mpm.h"
|
||||
#include "cbs.h"
|
||||
|
||||
|
||||
#define ZonedCBSBlockPool(zcbs) RVALUE((zcbs)->blockPool)
|
||||
#define ZonedCBSFreeCBS(zcbs) (&(zcbs)->freeStruct)
|
||||
#define ZonedCBSZoneCBS(zcbs, z) (&(zcbs)->zoneStruct[z])
|
||||
#define ZonedCBSNZones(zcbs) NELEMS((zcbs)->zoneStruct)
|
||||
|
||||
|
||||
/* ZonedCBSCheck -- check consistency of zoned CBS structure */
|
||||
|
||||
Bool ZonedCBSCheck(ZonedCBS zcbs)
|
||||
{
|
||||
Index i;
|
||||
|
||||
CHECKS(ZonedCBS, zcbs);
|
||||
CHECKU(Arena, zcbs->arena);
|
||||
CHECKD(Pool, ZonedCBSBlockPool(zcbs));
|
||||
CHECKD(CBS, ZonedCBSFreeCBS(zcbs));
|
||||
CHECKL(ZonedCBSNZones(zcbs) == sizeof(ZoneSet) * CHAR_BIT);
|
||||
CHECKL(ZonedCBSNZones(zcbs) == NELEMS(zcbs->zoneStruct));
|
||||
for (i = 0; i < ZonedCBSNZones(zcbs); ++i)
|
||||
CHECKD(CBS, ZonedCBSZoneCBS(zcbs, i));
|
||||
|
||||
/* TODO: Thorough check summing CBSs against totals. The sum of the
|
||||
sizes of the contents of the zone CBSs and the freeCBS should equal
|
||||
the total allocatable free space in the chunks. The CBS ADT
|
||||
probably ought to maintain totals like this too. */
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* ZonedCBSInit -- initialise a Zoned CBS */
|
||||
|
||||
Res ZonedCBSInit(ZonedCBS zcbs, Arena arena, Pool blockPool, Align alignment)
|
||||
{
|
||||
Index i;
|
||||
Res res;
|
||||
|
||||
AVER(zcbs != NULL);
|
||||
AVERT(Pool, blockPool);
|
||||
|
||||
zcbs->arena = arena;
|
||||
zcbs->blockPool = blockPool;
|
||||
|
||||
/* Initialise the freeCBS. */
|
||||
MPS_ARGS_BEGIN(cbsiArgs) {
|
||||
MPS_ARGS_ADD(cbsiArgs, CBSBlockPool, blockPool);
|
||||
MPS_ARGS_DONE(cbsiArgs);
|
||||
res = CBSInit(arena, ZonedCBSFreeCBS(zcbs), zcbs, alignment, TRUE, cbsiArgs);
|
||||
} MPS_ARGS_END(cbsiArgs);
|
||||
AVER(res == ResOK); /* no allocation, no failure expected */
|
||||
if (res != ResOK)
|
||||
goto failCBSInit;
|
||||
/* Note that although freeCBS is initialised, it doesn't have any memory
|
||||
for its blocks, so hasFreeCBS remains FALSE until later. */
|
||||
|
||||
/* Initialise the zoneCBSs. */
|
||||
for (i = 0; i < NELEMS(zcbs->zoneStruct); ++i) {
|
||||
MPS_ARGS_BEGIN(cbsiArgs) {
|
||||
MPS_ARGS_ADD(cbsiArgs, CBSBlockPool, blockPool);
|
||||
MPS_ARGS_DONE(cbsiArgs);
|
||||
res = CBSInit(arena, ZonedCBSZoneCBS(zcbs, i), arena, alignment, TRUE, cbsiArgs);
|
||||
} MPS_ARGS_END(cbsiArgs);
|
||||
AVER(res == ResOK); /* no allocation, no failure expected */
|
||||
if (res != ResOK)
|
||||
goto failZoneCBSInit;
|
||||
}
|
||||
|
||||
zcbs->sig = ZonedCBSSig;
|
||||
AVERT(ZonedCBS, zcbs);
|
||||
|
||||
return ResOK;
|
||||
|
||||
failZoneCBSInit:
|
||||
while (i > 0) {
|
||||
--i;
|
||||
CBSFinish(ZonedCBSZoneCBS(zcbs, i));
|
||||
}
|
||||
CBSFinish(ZonedCBSFreeCBS(zcbs));
|
||||
failCBSInit:
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/* ZonedCBSFinish -- finish the zoned CBS */
|
||||
|
||||
void ZonedCBSFinish(ZonedCBS zcbs)
|
||||
{
|
||||
Index i;
|
||||
|
||||
AVERT(ZonedCBS, zcbs);
|
||||
|
||||
zcbs->sig = SigInvalid;
|
||||
|
||||
/* FIXME: Should be asserting that CBSs are empty? Could iterate over
|
||||
chunks and remove their ranges to make sure things are consistent. */
|
||||
|
||||
for (i = 0; i < ZonedCBSNZones(zcbs); ++i)
|
||||
CBSFinish(ZonedCBSZoneCBS(zcbs, i));
|
||||
CBSFinish(ZonedCBSFreeCBS(zcbs));
|
||||
}
|
||||
|
||||
|
||||
/* ZonedCBSInsert -- insert a range into the zoned CBS
|
||||
*
|
||||
* We just insert it into the free CBS. It will get split up and cached
|
||||
* in the zoned CBSs by ZonedCBSFindFirst and ZonedCBSFindLast.
|
||||
*/
|
||||
|
||||
Res ZonedCBSInsert(Range rangeReturn, ZonedCBS zcbs, Range range)
|
||||
{
|
||||
ZoneSet zs;
|
||||
|
||||
AVERT(ZonedCBS, zcbs);
|
||||
|
||||
/* TODO: Consider moving empty zone stripes back to freeCBS. At the
|
||||
moment we do not do this, partly for simplicity, and partly to
|
||||
keep large objects apart from smaller ones, at the possible cost
|
||||
of address space fragmentation. We probably do not want to do it
|
||||
eagerly in any case, but lazily if we're unable to find address
|
||||
space, even though that reduces first-fit. */
|
||||
|
||||
zs = ZoneSetOfRange(zcbs->arena, RangeBase(range), RangeLimit(range));
|
||||
if (ZoneSetIsSingle(zs)) {
|
||||
Index zone = AddrZone(zcbs->arena, RangeBase(range));
|
||||
CBS zoneCBS = ZonedCBSZoneCBS(zcbs, zone);
|
||||
return CBSInsert(rangeReturn, zoneCBS, range);
|
||||
}
|
||||
|
||||
return CBSInsert(rangeReturn, ZonedCBSFreeCBS(zcbs), range);
|
||||
}
|
||||
|
||||
|
||||
/* ZonedCBSDelete -- delete a range from the zoned CBS
|
||||
*
|
||||
* The range may be split between the zone CBSs and the free CBS on zone
|
||||
* stripe boundaries, in which case we have to iterate.
|
||||
*
|
||||
* .delete.exists: The range must exist in the zoned CBS. To make the
|
||||
* zoned CBS more general we'd need to unwind the error case where we
|
||||
* found a hole, putting the range we'd already found back. This isn't
|
||||
* required by the arena.
|
||||
*
|
||||
* FIXME: Document guarantees about res.
|
||||
*/
|
||||
|
||||
Res ZonedCBSDelete(Range oldRange, ZonedCBS zcbs, Range range)
|
||||
{
|
||||
Res res;
|
||||
ZoneSet zs;
|
||||
Addr base, limit;
|
||||
|
||||
AVER(oldRange != NULL);
|
||||
AVERT(ZonedCBS, zcbs);
|
||||
AVERT(Range, range);
|
||||
|
||||
zs = ZoneSetOfRange(zcbs->arena, RangeBase(range), RangeLimit(range));
|
||||
if (ZoneSetIsSingle(zs)) {
|
||||
Index zone = AddrZone(zcbs->arena, RangeBase(range));
|
||||
CBS zoneCBS = ZonedCBSZoneCBS(zcbs, zone);
|
||||
res = CBSDelete(oldRange, zoneCBS, range);
|
||||
if (res != ResFAIL) /* block was in zone CBS */
|
||||
return res;
|
||||
}
|
||||
|
||||
res = CBSDelete(oldRange, ZonedCBSFreeCBS(zcbs), range);
|
||||
if (res != ResFAIL) /* block was in free CBS */
|
||||
return res;
|
||||
|
||||
/* The range may be divided between the free CBS and zone CBSs. */
|
||||
base = RangeBase(range);
|
||||
limit = RangeLimit(range);
|
||||
while (base < limit) {
|
||||
Addr stripeLimit;
|
||||
CBS zoneCBS;
|
||||
RangeStruct stripe, oldStripe;
|
||||
Index zone;
|
||||
|
||||
stripeLimit = AddrAlignUp(AddrAdd(base, 1), ArenaStripeSize(zcbs->arena));
|
||||
if (stripeLimit > limit)
|
||||
stripeLimit = limit;
|
||||
|
||||
zone = AddrZone(zcbs->arena, base);
|
||||
AVER(AddrZone(zcbs->arena, AddrSub(stripeLimit, 1)) == zone);
|
||||
zoneCBS = ZonedCBSZoneCBS(zcbs, zone);
|
||||
|
||||
RangeInit(&stripe, base, stripeLimit);
|
||||
res = CBSDelete(&oldStripe, ZonedCBSFreeCBS(zcbs), &stripe);
|
||||
|
||||
/* Optimisation: delete and skip over the rest of the block we
|
||||
found in the free CBS, up to the next block that's in a zone CBS
|
||||
(or the end). */
|
||||
if (res == ResOK && !RangesEqual(&oldStripe, &stripe)) {
|
||||
Addr skipLimit = RangeLimit(&oldStripe);
|
||||
if (skipLimit > limit)
|
||||
skipLimit = limit;
|
||||
if (stripeLimit < skipLimit) {
|
||||
RangeStruct restOfBlock;
|
||||
RangeInit(&restOfBlock, stripeLimit, skipLimit);
|
||||
res = CBSDelete(&oldStripe, ZonedCBSFreeCBS(zcbs), &restOfBlock);
|
||||
AVER(res == ResOK); /* FIXME: is this right? */
|
||||
AVER(RangesEqual(&oldStripe, &restOfBlock));
|
||||
base = skipLimit;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (res == ResFAIL) {
|
||||
res = CBSDelete(&oldStripe, zoneCBS, &stripe);
|
||||
AVER(res != ResFAIL); /* .delete.exists */
|
||||
AVER(RangesEqual(&oldStripe, &stripe));
|
||||
}
|
||||
|
||||
AVER(res == ResOK); /* FIXME: end of range, shouldn't fail? */
|
||||
|
||||
base = stripeLimit;
|
||||
}
|
||||
|
||||
/* Shouldn't be any other kind of failure. */
|
||||
AVER(res == ResOK);
|
||||
return ResOK;
|
||||
}
|
||||
|
||||
|
||||
/* ZonedCBSFindFirst, ZonedCBSFindLast -- find a range in the zoned CBS */
|
||||
|
||||
|
||||
static Res ZonedCBSFindInZones(Range rangeReturn,
|
||||
Range oldRangeReturn,
|
||||
ZonedCBS zcbs,
|
||||
ZoneSet zones,
|
||||
Size size,
|
||||
FindDelete findDelete,
|
||||
Bool high)
|
||||
{
|
||||
Index i;
|
||||
CBSFindMethod find = high ? CBSFindLast : CBSFindFirst;
|
||||
|
||||
/* We could check zoneCBS if size > stripeSize to avoid looking in the
|
||||
zoneCBSs, but this probably isn't a win for arena allocation. */
|
||||
|
||||
/* TODO: Does "high" really make sense for zone stripes? */
|
||||
/* TODO: How do we disable zones anyway? Just make zoneShift = WORD_WIDTH?
|
||||
DL points out that if we had two zones, they'd both be blacklisted. */
|
||||
|
||||
/* Even though we have no guarantee that zone zero is at the bottom end
|
||||
of anything, it makes sense to reverse the search when "high" is set,
|
||||
because the point of it (presumably) is to separate memory usage into
|
||||
two sets (high and low) that avoid interference. */
|
||||
|
||||
/* TODO: Consider masking zones against a ZoneSet of non-empty zone CBSs */
|
||||
|
||||
/* TODO: ZONESET_FOR using __builtin_ffsl or similar. */
|
||||
|
||||
for (i = 0; i < ZonedCBSNZones(zcbs); ++i) {
|
||||
Index zone = high ? ZonedCBSNZones(zcbs) - i - 1 : i;
|
||||
if (ZoneSetIsMember(zones, zone) &&
|
||||
find(rangeReturn, oldRangeReturn, ZonedCBSZoneCBS(zcbs, zone),
|
||||
size, findDelete))
|
||||
return ResOK;
|
||||
}
|
||||
|
||||
return ResRESOURCE;
|
||||
}
|
||||
|
||||
static Res ZonedCBSFindInFree(Range rangeReturn,
|
||||
Range oldRangeReturn,
|
||||
ZonedCBS zcbs,
|
||||
ZoneSet zones,
|
||||
Size size,
|
||||
FindDelete findDelete,
|
||||
Bool high)
|
||||
{
|
||||
CBSFindInZonesMethod find = high ? CBSFindLastInZones : CBSFindFirstInZones;
|
||||
|
||||
UNUSED(findDelete); /* FIXME: why is this so? */
|
||||
|
||||
return find(rangeReturn, oldRangeReturn, ZonedCBSFreeCBS(zcbs), size,
|
||||
zcbs->arena, zones);
|
||||
}
|
||||
|
||||
Res ZonedCBSFind(Range rangeReturn,
|
||||
Range oldRangeReturn,
|
||||
ZonedCBS zcbs,
|
||||
ZoneSet zones,
|
||||
Size size,
|
||||
FindDelete findDelete,
|
||||
Bool high)
|
||||
{
|
||||
RangeStruct restRange;
|
||||
Addr allocLimit, stripeLimit, oldLimit, limit;
|
||||
Res res;
|
||||
|
||||
AVER(rangeReturn != NULL);
|
||||
AVER(oldRangeReturn != NULL);
|
||||
AVERT(ZonedCBS, zcbs);
|
||||
AVER(size > 0);
|
||||
/* ZoneSet arbitrary. TODO: Make a ZoneSetCheck anyway. */
|
||||
AVER(BoolCheck(high));
|
||||
|
||||
/* TODO: Consider the other FindDelete cases. */
|
||||
AVER(findDelete == FindDeleteLOW || findDelete == FindDeleteHIGH);
|
||||
AVER((findDelete == FindDeleteHIGH) == high);
|
||||
|
||||
res = ZonedCBSFindInZones(rangeReturn, oldRangeReturn,
|
||||
zcbs, zones, size,
|
||||
findDelete, high);
|
||||
if (res != ResRESOURCE)
|
||||
return res;
|
||||
|
||||
res = ZonedCBSFindInFree(rangeReturn, oldRangeReturn,
|
||||
zcbs, zones, size,
|
||||
findDelete, high);
|
||||
if (res != ResOK)
|
||||
return res;
|
||||
|
||||
/* TODO: We may have failed to find because the address space is
|
||||
fragmented between the zone CBSs and the free CBS. This isn't
|
||||
a very important case for the arena, but it does make the Zone CBS
|
||||
technically incorrect as a representation of a set of ranges.
|
||||
Flush the zone CBSs back to the free CBS? */
|
||||
|
||||
/* Add the rest of the zone stripe to the zoneCBS so that subsequent
|
||||
allocations in the zone are fast. This is what the ZonedCBS is
|
||||
all about! */
|
||||
|
||||
allocLimit = RangeLimit(rangeReturn);
|
||||
stripeLimit = AddrAlignUp(allocLimit, ArenaStripeSize(zcbs->arena));
|
||||
oldLimit = RangeLimit(oldRangeReturn);
|
||||
limit = oldLimit < stripeLimit ? oldLimit : stripeLimit;
|
||||
RangeInit(&restRange, allocLimit, limit);
|
||||
AVER(RangesNest(oldRangeReturn, &restRange));
|
||||
|
||||
if (allocLimit < limit) {
|
||||
Index zone;
|
||||
CBS zoneCBS;
|
||||
RangeStruct oldRange;
|
||||
|
||||
res = CBSDelete(&oldRange, ZonedCBSFreeCBS(zcbs), &restRange);
|
||||
AVER(res == ResOK); /* we should just be bumping up a base */
|
||||
zone = AddrZone(zcbs->arena, RangeBase(&restRange));
|
||||
zoneCBS = ZonedCBSZoneCBS(zcbs, zone);
|
||||
|
||||
res = CBSInsert(&oldRange, zoneCBS, &restRange);
|
||||
AVER(res != ResOK || RangesEqual(&oldRange, &restRange)); /* shouldn't coalesce */
|
||||
if (res != ResOK) { /* disasterously short on memory, so put it back */
|
||||
res = CBSInsert(&oldRange, ZonedCBSFreeCBS(zcbs), &restRange);
|
||||
AVER(res == ResOK); /* should just be lowering a base */
|
||||
}
|
||||
}
|
||||
|
||||
return ResOK;
|
||||
}
|
||||
|
||||
/*
|
||||
Res ZonedCBSFindFirst(Range rangeReturn, Range oldRangeReturn,
|
||||
ZonedCBS zcbs, ZoneSet zones,
|
||||
Size size, FindDelete findDelete)
|
||||
{
|
||||
return ZonedCBSFind(rangeReturn, oldRangeReturn, zcbs, zones, size,
|
||||
findDelete, FALSE);
|
||||
}
|
||||
|
||||
Bool ZonedCBSFindLast(Range rangeReturn, Range oldRangeReturn,
|
||||
ZonedCBS zcbs, ZoneSet zones,
|
||||
Size size, FindDelete findDelete)
|
||||
{
|
||||
return ZonedCBSFind(rangeReturn, oldRangeReturn, zcbs, zones, size,
|
||||
findDelete, TRUE);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/* C. COPYRIGHT AND LICENSE
|
||||
*
|
||||
* Copyright (C) 2014 Ravenbrook Limited <http://www.ravenbrook.com/>.
|
||||
* 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.
|
||||
*/
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
/* zonedcbs.h: ZONE COALESCING BLOCK STRUCTURE INTERFACE
|
||||
*
|
||||
* $Id$
|
||||
* Copyright (c) 2014 Ravenbrook Limited. See end of file for license.
|
||||
*
|
||||
* A Zone CBS is like a CBS but allows for efficient allocation in zones.
|
||||
* Allocation in zones gives control over some parts of an object's address,
|
||||
* so that we can later apply fast filters on the critical path.
|
||||
* The Zone CBS is mainly used by the arena to allocate blocks to pools.
|
||||
*/
|
||||
|
||||
#ifndef zonedcbs_h
|
||||
#define zonedcbs_h
|
||||
|
||||
#include "mpm.h"
|
||||
#include "range.h"
|
||||
|
||||
|
||||
typedef struct ZonedCBSStruct *ZonedCBS;
|
||||
|
||||
/* ZoneCBSStruct is in mpmst.h because it is inlined in the ArenaStruct. */
|
||||
|
||||
|
||||
/* Basically the same interface as for CBS. See <code/cbs.h>. */
|
||||
|
||||
extern Res ZonedCBSInit(ZonedCBS zcbs, Arena arena, Pool blockPool,
|
||||
Align alignment);
|
||||
extern void ZonedCBSFinish(ZonedCBS zcbs);
|
||||
extern Bool ZonedCBSCheck(ZonedCBS zcbs);
|
||||
|
||||
extern Res ZonedCBSInsert(Range rangeReturn, ZonedCBS zcbs, Range range);
|
||||
extern Res ZonedCBSDelete(Range oldRange, ZonedCBS zcbs, Range range);
|
||||
|
||||
extern Res ZonedCBSFind(Range rangeReturn,
|
||||
Range oldRangeReturn,
|
||||
ZonedCBS zcbs,
|
||||
ZoneSet zones,
|
||||
Size size,
|
||||
FindDelete findDelete,
|
||||
Bool high);
|
||||
|
||||
/*
|
||||
extern Bool ZonedCBSFindFirst(Range rangeReturn, Range oldRangeReturn,
|
||||
ZonedCBS zcbs, ZoneSet zones,
|
||||
Size size, FindDelete findDelete);
|
||||
extern Bool ZonedCBSFindLast(Range rangeReturn, Range oldRangeReturn,
|
||||
ZonedCBS zcbs, ZoneSet zones,
|
||||
Size size, FindDelete findDelete);
|
||||
*/
|
||||
|
||||
|
||||
#endif /* zonedcbs_h */
|
||||
|
||||
/* C. COPYRIGHT AND LICENSE
|
||||
*
|
||||
* Copyright (C) 2014 Ravenbrook Limited <http://www.ravenbrook.com/>.
|
||||
* 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.
|
||||
*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue