/* fbmtest.c: FREE BLOCK MANAGEMENT TEST * * $Id$ * Copyright (c) 2001-2014 Ravenbrook Limited. See end of file for license. * * The MPS contains two free block management modules: * * 1. the CBS (Coalescing Block Structure) module maintains free * blocks in a splay tree for fast access with a cost in storage; * * 2. the Freelist module maintains free blocks in an address-ordered * singly linked list for zero storage overhead with a cost in * performance. * * The two modules present identical interfaces, so we apply the same * test cases to both. */ #include "cbs.h" #include "freelist.h" #include "mpm.h" #include "mps.h" #include "mpsavm.h" #include "testlib.h" #include /* printf */ SRCID(fbmtest, "$Id$"); #define ArraySize ((Size)123456) /* CBS is much faster than Freelist, so we apply more operations to * the former. */ #define nCBSOperations ((Size)125000) #define nFLOperations ((Size)12500) static Count NAllocateTried, NAllocateSucceeded, NDeallocateTried, NDeallocateSucceeded; static Bool verbose = FALSE; typedef unsigned FBMType; enum { FBMTypeCBS = 1, FBMTypeFreelist, FBMTypeLimit }; typedef struct FBMStateStruct { FBMType type; Align align; BT allocTable; Addr block; union { CBS cbs; Freelist fl; } the; } FBMStateStruct, *FBMState; typedef struct CheckFBMClosureStruct { FBMState state; Addr limit; Addr oldLimit; } CheckFBMClosureStruct, *CheckFBMClosure; static Addr (addrOfIndex)(FBMState state, Index i) { return AddrAdd(state->block, (i * state->align)); } static Index (indexOfAddr)(FBMState state, Addr a) { return (Index)(AddrOffset(state->block, a) / state->align); } static void describe(FBMState state) { switch (state->type) { case FBMTypeCBS: die(CBSDescribe(state->the.cbs, mps_lib_get_stdout(), 0), "CBSDescribe"); break; case FBMTypeFreelist: die(FreelistDescribe(state->the.fl, mps_lib_get_stdout(), 0), "FreelistDescribe"); break; default: cdie(0, "invalid state->type"); break; } } static Bool checkCallback(Range range, void *closure) { Addr base, limit; CheckFBMClosure cl = (CheckFBMClosure)closure; Insist(cl != NULL); base = RangeBase(range); limit = RangeLimit(range); if (base > cl->oldLimit) { Insist(BTIsSetRange(cl->state->allocTable, indexOfAddr(cl->state, cl->oldLimit), indexOfAddr(cl->state, base))); } else { /* must be at start of table */ Insist(base == cl->oldLimit); Insist(cl->oldLimit == cl->state->block); } Insist(BTIsResRange(cl->state->allocTable, indexOfAddr(cl->state, base), indexOfAddr(cl->state, limit))); cl->oldLimit = limit; return TRUE; } static Bool checkCBSCallback(CBS cbs, Range range, void *closure) { UNUSED(cbs); return checkCallback(range, closure); } static Bool checkFLCallback(Bool *deleteReturn, Range range, void *closure) { *deleteReturn = FALSE; return checkCallback(range, closure); } static void check(FBMState state) { CheckFBMClosureStruct closure; closure.state = state; closure.limit = addrOfIndex(state, ArraySize); closure.oldLimit = state->block; switch (state->type) { case FBMTypeCBS: CBSIterate(state->the.cbs, checkCBSCallback, &closure); break; case FBMTypeFreelist: FreelistIterate(state->the.fl, checkFLCallback, &closure); break; default: cdie(0, "invalid state->type"); return; } if (closure.oldLimit == state->block) Insist(BTIsSetRange(state->allocTable, 0, indexOfAddr(state, closure.limit))); else if (closure.limit > closure.oldLimit) Insist(BTIsSetRange(state->allocTable, indexOfAddr(state, closure.oldLimit), indexOfAddr(state, closure.limit))); else Insist(closure.oldLimit == closure.limit); } static Word fbmRnd(Word limit) { /* Not very uniform, but never mind. */ return (Word)rnd() % limit; } /* nextEdge -- Finds the next transition in the bit table * * Returns the index greater than such that the * range [, ) has the same value in the bit table, * and has a different value or does not exist. */ static Index nextEdge(BT bt, Size size, Index base) { Index end; Bool baseValue; Insist(bt != NULL); Insist(base < size); baseValue = BTGet(bt, base); for(end = base + 1; end < size && BTGet(bt, end) == baseValue; end++) NOOP; return end; } /* lastEdge -- Finds the previous transition in the bit table * * Returns the index less than such that the range * [, ] has the same value in the bit table, * and -1 has a different value or does not exist. */ static Index lastEdge(BT bt, Size size, Index base) { Index end; Bool baseValue; Insist(bt != NULL); Insist(base < size); baseValue = BTGet(bt, base); for(end = base; end > (Index)0 && BTGet(bt, end - 1) == baseValue; end--) NOOP; return end; } /* randomRange -- picks random range within table * * The function first picks a uniformly distributed within the table. * * It then scans forward a binary exponentially distributed * number of "edges" in the table (that is, transitions between set and * reset) to get . Note that there is a 50% chance that will * be the next edge, a 25% chance it will be the edge after, etc., until * the end of the table. * * Finally it picks a uniformly distributed in the range * [base+1, limit]. * * Hence there is a somewhat better than 50% chance that the range will be * all either set or reset. */ static void randomRange(Addr *baseReturn, Addr *limitReturn, FBMState state) { Index base; /* the start of our range */ Index end; /* an edge (i.e. different from its predecessor) */ /* after base */ Index limit; /* a randomly chosen value in (base, limit]. */ base = fbmRnd(ArraySize); do { end = nextEdge(state->allocTable, ArraySize, base); } while(end < ArraySize && fbmRnd(2) == 0); /* p=0.5 exponential */ Insist(end > base); limit = base + 1 + fbmRnd(end - base); *baseReturn = addrOfIndex(state, base); *limitReturn = addrOfIndex(state, limit); } static void allocate(FBMState state, Addr base, Addr limit) { Res res; Index ib, il; /* Indexed for base and limit */ Bool isFree; RangeStruct range, oldRange; Addr outerBase, outerLimit; /* interval containing [ib, il) */ ib = indexOfAddr(state, base); il = indexOfAddr(state, limit); isFree = BTIsResRange(state->allocTable, ib, il); NAllocateTried++; if (isFree) { Size left, right, total; /* Sizes of block and two fragments */ outerBase = addrOfIndex(state, lastEdge(state->allocTable, ArraySize, ib)); outerLimit = addrOfIndex(state, nextEdge(state->allocTable, ArraySize, il - 1)); left = AddrOffset(outerBase, base); right = AddrOffset(limit, outerLimit); total = AddrOffset(outerBase, outerLimit); /* TODO: check these values */ UNUSED(left); UNUSED(right); UNUSED(total); } else { outerBase = outerLimit = NULL; } RangeInit(&range, base, limit); switch (state->type) { case FBMTypeCBS: res = CBSDelete(&oldRange, state->the.cbs, &range); break; case FBMTypeFreelist: res = FreelistDelete(&oldRange, state->the.fl, &range); break; default: cdie(0, "invalid state->type"); return; } if (verbose) { printf("allocate: [%p,%p) -- %s\n", (void *)base, (void *)limit, isFree ? "succeed" : "fail"); describe(state); } if (!isFree) { die_expect((mps_res_t)res, MPS_RES_FAIL, "Succeeded in deleting allocated block"); } else { /* isFree */ die_expect((mps_res_t)res, MPS_RES_OK, "failed to delete free block"); Insist(RangeBase(&oldRange) == outerBase); Insist(RangeLimit(&oldRange) == outerLimit); NAllocateSucceeded++; BTSetRange(state->allocTable, ib, il); } } static void deallocate(FBMState state, Addr base, Addr limit) { Res res; Index ib, il; Bool isAllocated; Addr outerBase = base, outerLimit = limit; /* interval containing [ib, il) */ RangeStruct range, freeRange; /* interval returned by the manager */ ib = indexOfAddr(state, base); il = indexOfAddr(state, limit); isAllocated = BTIsSetRange(state->allocTable, ib, il); NDeallocateTried++; if (isAllocated) { Size left, right, total; /* Sizes of block and two fragments */ /* Find the free blocks adjacent to the allocated block */ if (ib > 0 && !BTGet(state->allocTable, ib - 1)) { outerBase = addrOfIndex(state, lastEdge(state->allocTable, ArraySize, ib - 1)); } else { outerBase = base; } if (il < ArraySize && !BTGet(state->allocTable, il)) { outerLimit = addrOfIndex(state, nextEdge(state->allocTable, ArraySize, il)); } else { outerLimit = limit; } left = AddrOffset(outerBase, base); right = AddrOffset(limit, outerLimit); total = AddrOffset(outerBase, outerLimit); /* TODO: check these values */ UNUSED(left); UNUSED(right); UNUSED(total); } RangeInit(&range, base, limit); switch (state->type) { case FBMTypeCBS: res = CBSInsert(&freeRange, state->the.cbs, &range); break; case FBMTypeFreelist: res = FreelistInsert(&freeRange, state->the.fl, &range); break; default: cdie(0, "invalid state->type"); return; } if (verbose) { printf("deallocate: [%p,%p) -- %s\n", (void *)base, (void *)limit, isAllocated ? "succeed" : "fail"); describe(state); } if (!isAllocated) { die_expect((mps_res_t)res, MPS_RES_FAIL, "succeeded in inserting non-allocated block"); } else { /* isAllocated */ die_expect((mps_res_t)res, MPS_RES_OK, "failed to insert allocated block"); NDeallocateSucceeded++; BTResRange(state->allocTable, ib, il); Insist(RangeBase(&freeRange) == outerBase); Insist(RangeLimit(&freeRange) == outerLimit); } } static void find(FBMState state, Size size, Bool high, FindDelete findDelete) { Bool expected, found; Index expectedBase, expectedLimit; RangeStruct foundRange, oldRange; Addr remainderBase, remainderLimit; Addr origBase, origLimit; Size oldSize, newSize; origBase = origLimit = NULL; expected = (high ? BTFindLongResRangeHigh : BTFindLongResRange) (&expectedBase, &expectedLimit, state->allocTable, (Index)0, (Index)ArraySize, (Count)size); if (expected) { oldSize = (expectedLimit - expectedBase) * state->align; remainderBase = origBase = addrOfIndex(state, expectedBase); remainderLimit = origLimit = addrOfIndex(state, expectedLimit); switch(findDelete) { case FindDeleteNONE: /* do nothing */ break; case FindDeleteENTIRE: remainderBase = remainderLimit; break; case FindDeleteLOW: expectedLimit = expectedBase + size; remainderBase = addrOfIndex(state, expectedLimit); break; case FindDeleteHIGH: expectedBase = expectedLimit - size; remainderLimit = addrOfIndex(state, expectedBase); break; default: cdie(0, "invalid findDelete"); break; } if (findDelete != FindDeleteNONE) { newSize = AddrOffset(remainderBase, remainderLimit); } /* TODO: check these values */ UNUSED(oldSize); UNUSED(newSize); } switch (state->type) { case FBMTypeCBS: found = (high ? CBSFindLast : CBSFindFirst) (&foundRange, &oldRange, state->the.cbs, size * state->align, findDelete); break; case FBMTypeFreelist: found = (high ? FreelistFindLast : FreelistFindFirst) (&foundRange, &oldRange, state->the.fl, size * state->align, findDelete); break; default: cdie(0, "invalid state->type"); return; } if (verbose) { printf("find %s %lu: ", high ? "last" : "first", (unsigned long)(size * state->align)); if (expected) { printf("expecting [%p,%p)\n", (void *)addrOfIndex(state, expectedBase), (void *)addrOfIndex(state, expectedLimit)); } else { printf("expecting this not to be found\n"); } if (found) { printf(" found [%p,%p)\n", (void *)RangeBase(&foundRange), (void *)RangeLimit(&foundRange)); } else { printf(" not found\n"); } } Insist(found == expected); if (found) { Insist(expectedBase == indexOfAddr(state, RangeBase(&foundRange))); Insist(expectedLimit == indexOfAddr(state, RangeLimit(&foundRange))); if (findDelete != FindDeleteNONE) { Insist(RangeBase(&oldRange) == origBase); Insist(RangeLimit(&oldRange) == origLimit); BTSetRange(state->allocTable, expectedBase, expectedLimit); } } return; } static void test(FBMState state, unsigned n) { Addr base, limit; unsigned i; Size size; Bool high; FindDelete findDelete = FindDeleteNONE; BTSetRange(state->allocTable, 0, ArraySize); /* Initially all allocated */ check(state); for(i = 0; i < n; i++) { switch(fbmRnd(3)) { case 0: randomRange(&base, &limit, state); allocate(state, base, limit); break; case 1: randomRange(&base, &limit, state); deallocate(state, base, limit); break; case 2: size = fbmRnd(ArraySize / 10) + 1; high = fbmRnd(2) ? TRUE : FALSE; switch(fbmRnd(6)) { default: findDelete = FindDeleteNONE; break; case 3: findDelete = FindDeleteLOW; break; case 4: findDelete = FindDeleteHIGH; break; case 5: findDelete = FindDeleteENTIRE; break; } find(state, size, high, findDelete); break; default: cdie(0, "invalid state->type"); return; } if ((i + 1) % 1000 == 0) check(state); if (i == 100) describe(state); } } #define testArenaSIZE (((size_t)4)<<20) extern int main(int argc, char *argv[]) { mps_arena_t mpsArena; Arena arena; /* the ANSI arena which we use to allocate the BT */ FBMStateStruct state; void *p; Addr dummyBlock; BT allocTable; FreelistStruct flStruct; CBSStruct cbsStruct; Align align; testlib_init(argc, argv); align = sizeof(void *) << (rnd() % 4); NAllocateTried = NAllocateSucceeded = NDeallocateTried = NDeallocateSucceeded = 0; die(mps_arena_create(&mpsArena, mps_arena_class_vm(), testArenaSIZE), "mps_arena_create"); arena = (Arena)mpsArena; /* avoid pun */ die((mps_res_t)BTCreate(&allocTable, arena, ArraySize), "failed to create alloc table"); /* We're not going to use this block, but I feel unhappy just */ /* inventing addresses. */ die((mps_res_t)ControlAlloc(&p, arena, ArraySize * align); "failed to allocate block"); dummyBlock = p; /* avoid pun */ if (verbose) { printf("Allocated block [%p,%p)\n", (void*)dummyBlock, (char *)dummyBlock + ArraySize); } die((mps_res_t)CBSInit(&cbsStruct, arena, arena, align, /* fastFind */ TRUE, /* zoned */ FALSE, mps_args_none), "failed to initialise CBS"); state.type = FBMTypeCBS; state.align = align; state.block = dummyBlock; state.allocTable = allocTable; state.the.cbs = &cbsStruct; test(&state, nCBSOperations); CBSFinish(&cbsStruct); die((mps_res_t)FreelistInit(&flStruct, align), "failed to initialise Freelist"); state.type = FBMTypeFreelist; state.the.fl = &flStruct; test(&state, nFLOperations); FreelistFinish(&flStruct); mps_arena_destroy(arena); printf("\nNumber of allocations attempted: %"PRIuLONGEST"\n", (ulongest_t)NAllocateTried); printf("Number of allocations succeeded: %"PRIuLONGEST"\n", (ulongest_t)NAllocateSucceeded); printf("Number of deallocations attempted: %"PRIuLONGEST"\n", (ulongest_t)NDeallocateTried); printf("Number of deallocations succeeded: %"PRIuLONGEST"\n", (ulongest_t)NDeallocateSucceeded); printf("%s: Conclusion: Failed to find any defects.\n", argv[0]); return 0; } /* C. COPYRIGHT AND LICENSE * * Copyright (c) 2001-2014 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. */