Commit 9e8e6004 authored by Bradley C. Kuszmaul's avatar Bradley C. Kuszmaul Committed by Yoni Fogel

Block allocator works and is tested. Addresses #1080, #1031, #1000.

git-svn-id: file:///svn/tokudb.1131b+1080a@6076 c7de825b-a66e-492c-adef-691d508d4ae1
parent fcd8cb08
...@@ -56,6 +56,7 @@ build default: bins libs tdb-recover tdb_logprint $(TEST_OFILES) ...@@ -56,6 +56,7 @@ build default: bins libs tdb-recover tdb_logprint $(TEST_OFILES)
cd tests;$(MAKE) build cd tests;$(MAKE) build
BRT_SOURCES = \ BRT_SOURCES = \
block_allocator \
bread \ bread \
brt-serialize \ brt-serialize \
brt-verify \ brt-verify \
......
/* -*- mode: C; c-basic-offset: 4 -*- */
#include "block_allocator.h"
#include "memory.h"
#include "toku_assert.h"
#include <errno.h>
#include <string.h>
// Here's a very simple implementation.
// It's not very fast at allocating or freeing.
struct blockpair {
u_int64_t offset;
u_int64_t size;
};
struct block_allocator {
u_int64_t reserve_at_beginning; // How much to reserve at the beginning
u_int64_t n_blocks; // How many blocks
u_int64_t blocks_array_size; // How big is the blocks_array. Must be >= n_blocks.
struct blockpair *blocks_array; // These blocks are sorted by address.
u_int64_t next_fit_counter; // Used for the next_fit algorithm.
};
void
block_allocator_validate (BLOCK_ALLOCATOR ba) {
u_int64_t i;
for (i=0; i<ba->n_blocks; i++) {
if (i>0) {
assert(ba->blocks_array[i].offset > ba->blocks_array[i-1].offset);
assert(ba->blocks_array[i].offset >= ba->blocks_array[i-1].offset + ba->blocks_array[i-1].size );
}
}
}
#if 0
void
block_allocator_print (BLOCK_ALLOCATOR ba) {
u_int64_t i;
for (i=0; i<ba->n_blocks; i++) {
printf("%" PRId64 ":%" PRId64 " ", ba->blocks_array[i].offset, ba->blocks_array[i].size);
}
printf("\n");
block_allocator_validate(ba);
}
#endif
void
create_block_allocator (BLOCK_ALLOCATOR *ba, u_int64_t reserve_at_beginning) {
BLOCK_ALLOCATOR XMALLOC(result);
result->reserve_at_beginning = reserve_at_beginning;
result->n_blocks = 0;
result->blocks_array_size = 1;
XMALLOC_N(result->blocks_array_size, result->blocks_array);
result->next_fit_counter = 0;
*ba = result;
}
void
destroy_block_allocator (BLOCK_ALLOCATOR *bap) {
BLOCK_ALLOCATOR ba = *bap;
*bap = 0;
toku_free(ba->blocks_array);
toku_free(ba);
}
static void
grow_blocks_array (BLOCK_ALLOCATOR ba) {
if (ba->n_blocks >= ba->blocks_array_size) {
ba->blocks_array_size *= 2;
XREALLOC_N(ba->blocks_array_size, ba->blocks_array);
}
}
void
block_allocator_alloc_block_at (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t offset) {
u_int64_t i;
grow_blocks_array(ba);
// Just do a linear search for the block
for (i=0; i<ba->n_blocks; i++) {
if (ba->blocks_array[i].offset > offset) {
// allocate it in that slot
// Don't do error checking, since we require that the blocks don't overlap.
// Slide everything over
memmove(ba->blocks_array+i, ba->blocks_array+i-1, (ba->n_blocks - i)*sizeof(struct blockpair));
ba->blocks_array[i].offset = offset;
ba->blocks_array[i].size = size;
ba->n_blocks++;
return;
}
}
// Goes at the end
ba->blocks_array[ba->n_blocks].offset = offset;
ba->blocks_array[ba->n_blocks].size = size;
ba->n_blocks++;
}
void
block_allocator_alloc_block (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t *offset) {
grow_blocks_array(ba);
if (ba->n_blocks==0) {
ba->blocks_array[0].offset = ba->reserve_at_beginning;
ba->blocks_array[0].size = size;
*offset = ba->reserve_at_beginning;
ba->n_blocks++;
return;
}
u_int64_t i;
u_int64_t blocknum = ba->next_fit_counter;
// Implement next fit.
for (i=0; i<ba->n_blocks; i++, blocknum++) {
if (blocknum>=ba->n_blocks) blocknum=0;
// Consider the space after blocknum
if (blocknum+1 ==ba->n_blocks) continue; // Can't use the space after the last block, since that would be new space.
struct blockpair *bp = &ba->blocks_array[blocknum];
u_int64_t this_offset = bp[0].offset;
u_int64_t this_size = bp[0].size;
u_int64_t answer_offset = this_offset + this_size;
if (answer_offset + size > bp[1].offset) continue; // The block we want doesn't fit after this block.
// It fits, so allocate it here.
memmove(bp+2, bp+1, (ba->n_blocks - blocknum -1)*sizeof(struct blockpair));
bp[1].offset = answer_offset;
bp[1].size = size;
ba->n_blocks++;
ba->next_fit_counter = blocknum;
*offset = answer_offset;
return;
}
// It didn't fit anywhere, so fit it on the end.
struct blockpair *bp = &ba->blocks_array[ba->n_blocks];
u_int64_t answer_offset = bp[-1].offset+bp[-1].size;
bp->offset = answer_offset;
bp->size = size;
ba->n_blocks++;
*offset = answer_offset;
}
static int64_t
find_block (BLOCK_ALLOCATOR ba, u_int64_t offset)
// Find the index in the blocks array that has a particular offset. Requires that the block exist.
// Use binary search so it runs fast.
{
if (ba->n_blocks==1) {
assert(ba->blocks_array[0].offset == offset);
return 0;
}
u_int64_t lo = 0;
u_int64_t hi = ba->n_blocks;
while (1) {
assert(lo<hi); // otherwise no such block exists.
u_int64_t mid = (lo+hi)/2;
u_int64_t thisoff = ba->blocks_array[mid].offset;
//printf("lo=%" PRId64 " hi=%" PRId64 " mid=%" PRId64 " thisoff=%" PRId64 " offset=%" PRId64 "\n", lo, hi, mid, thisoff, offset);
if (thisoff < offset) {
lo = mid+1;
} else if (thisoff > offset) {
hi = mid;
} else {
return mid;
}
}
}
void
block_allocator_free_block (BLOCK_ALLOCATOR ba, u_int64_t offset) {
int64_t bn = find_block(ba, offset);
assert(bn>=0); // we require that there is a block with that offset. Might as well abort if no such block exists.
memmove(&ba->blocks_array[bn], &ba->blocks_array[bn+1], (ba->n_blocks-bn-1) * sizeof(struct blockpair));
ba->n_blocks--;
}
u_int64_t
block_allocator_block_size (BLOCK_ALLOCATOR ba, u_int64_t offset) {
int64_t bn = find_block(ba, offset);
assert(bn>=0); // we require that there is a block with that offset. Might as well abort if no such block exists.
return ba->blocks_array[bn].size;
}
/* -*- mode: C; c-basic-offset: 4 -*- */
#ifndef BLOCK_ALLOCATOR_H
#define BLOCK_ALLOCATOR_H
#ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved."
#include "brttypes.h"
// A block allocator manages the allocation of variable-sized blocks.
// The translation of block numbers to addresses is handled elsewhere.
// The allocation of block numbers is handled elsewhere.
// We can create a block allocator.
// When creating a block allocator we also specify a certain-sized
// block at the beginning that is preallocated (and cannot be allocated
// or freed)
// We can allocate blocks of a particular size at a particular location.
// We can allocate blocks of a particular size at a location chosen by the allocator.
// We can free blocks.
// We can determine the size of a block.
typedef struct block_allocator *BLOCK_ALLOCATOR;
void
create_block_allocator (BLOCK_ALLOCATOR * ba, u_int64_t reserve_at_beginning);
// Effect: Create a block allocator, in which the first RESERVE_AT_BEGINNING bytes are not put into a block.
// Aborts if we run out of memory.
// Parameters
// ba (OUT): Result stored here.
// reserve_at_beginning (IN) Size of reserved block at beginning.
void
destroy_block_allocator (BLOCK_ALLOCATOR *ba);
// Effect: Destroy a block allocator at *ba.
// Also, set *ba=NULL.
// Rationale: If there was only one copy of the pointer, this kills that copy too.
// Paramaters:
// ba (IN/OUT):
void
block_allocator_alloc_block_at (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t offset);
// Effect: Allocate a block of the specified size at a particular offset.
// Aborts if anything goes wrong.
// Requires: The resulting block may not overlap any other allocated block.
// Parameters:
// ba (IN/OUT): The block allocator. (Modifies ba.)
// size (IN): The size of the block.
// offset (IN): The location of the block.
//
void
block_allocator_alloc_block (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t *offset);
// Effect: Allocate a block of the specified size at an address chosen by the allocator.
// Aborts if anything goes wrong.
// Parameters:
// ba (IN/OUT): The block allocator. (Modifies ba.)
// size (IN): The size of the block.
// offset (OUT): The location of the block.
void
block_allocator_free_block (BLOCK_ALLOCATOR ba, u_int64_t offset);
// Effect: Free the block at offset.
// Requires: There must be a block currently allocated at that offset.
// Parameters:
// ba (IN/OUT): The block allocator. (Modifies ba.)
// offset (IN): The offset of the block.
u_int64_t
block_allocator_block_size (BLOCK_ALLOCATOR ba, u_int64_t offset);
// Effect: Return the size of the block that starts at offset.
// Requires: There must be a block currently allocated at that offset.
// Parameters:
// ba (IN/OUT): The block allocator. (Modifies ba.)
// offset (IN): The offset of the block.
void
block_allocator_validate (BLOCK_ALLOCATOR ba);
// Effect: Check to see if the block allocator is OK. This may take a long time.
// Usage Hints: Probably only use this for unit tests.
void
block_allocator_print (BLOCK_ALLOCATOR ba);
#endif
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved." #ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved."
#include "toku_assert.h" #include "toku_assert.h"
#include "block_allocator.h"
#include "cachetable.h" #include "cachetable.h"
#include "fifo.h" #include "fifo.h"
#include "brt.h" #include "brt.h"
...@@ -131,6 +132,14 @@ struct brt_header { ...@@ -131,6 +132,14 @@ struct brt_header {
//struct block_descriptor *blocks; //struct block_descriptor *blocks;
BLOCKNUM free_blocks; // free list for blocks. Use -1 to indicate that there are no free blocks BLOCKNUM free_blocks; // free list for blocks. Use -1 to indicate that there are no free blocks
BLOCKNUM unused_blocks; // first unused block BLOCKNUM unused_blocks; // first unused block
// Where and how big is the block allocation vector?
// The block allocation vector translates block numbers to offsets (DISKOFF) in the file.
// We use a DISKOFF for the vector location, because we cannot use block numbers to bootstrap the indirection table.
BLOCKNUM block_allocation_vector_length; // It's a blocknum because the block allocation vector is indexed by blocknums.
DISKOFF block_allocation_vector_location; // Remember this so we can free the block before writing a new one.
// The in-memory data structure for block allocation
BLOCK_ALLOCATOR block_allocator;
}; };
struct brt { struct brt {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
#ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved." #ident "Copyright (c) 2007, 2008 Tokutek Inc. All rights reserved."
#include "toku_assert.h" #include "toku_assert.h"
#include "block_allocator.h"
#include "brt-internal.h" #include "brt-internal.h"
#include "key.h" #include "key.h"
#include "rbuf.h" #include "rbuf.h"
...@@ -15,6 +16,16 @@ ...@@ -15,6 +16,16 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <zlib.h> #include <zlib.h>
static u_int64_t ntohll(u_int64_t v) {
union u {
u_int32_t l[2];
u_int64_t ll;
} uv;
uv.ll = v;
return (((u_int64_t)uv.l[0])<<32) + uv.l[1];
}
// Don't include the compressed data size or the uncompressed data size. // Don't include the compressed data size or the uncompressed data size.
static const int brtnode_header_overhead = (8+ // magic "tokunode" or "tokuleaf" static const int brtnode_header_overhead = (8+ // magic "tokunode" or "tokuleaf"
...@@ -575,6 +586,18 @@ int toku_serialize_brt_header_to_wbuf (struct wbuf *wbuf, struct brt_header *h) ...@@ -575,6 +586,18 @@ int toku_serialize_brt_header_to_wbuf (struct wbuf *wbuf, struct brt_header *h)
wbuf_BLOCKNUM(wbuf, h->free_blocks); wbuf_BLOCKNUM(wbuf, h->free_blocks);
wbuf_BLOCKNUM(wbuf, h->unused_blocks); wbuf_BLOCKNUM(wbuf, h->unused_blocks);
wbuf_int (wbuf, h->n_named_roots); wbuf_int (wbuf, h->n_named_roots);
if (h->block_allocation_vector_length.b != 0) {
block_allocator_free_block(h->block_allocator, h->block_allocation_vector_location);
}
#if 0
h->block_allocation_vector_length.b = block_allocator_get_vector_length(h->block_allocator);
{
int r = block_allocator_alloc_block(h->block_allocator, 0, h->block_allocation_vector_length.b, (u_int64_t*)&h->block_allocation_vector_location);
assert(r==0);
}
#endif
wbuf_BLOCKNUM(wbuf, h->block_allocation_vector_length);
wbuf_DISKOFF(wbuf, h->block_allocation_vector_location);
if (h->n_named_roots>=0) { if (h->n_named_roots>=0) {
int i; int i;
for (i=0; i<h->n_named_roots; i++) { for (i=0; i<h->n_named_roots; i++) {
...@@ -632,6 +655,29 @@ int deserialize_brtheader (u_int32_t size, int fd, DISKOFF off, struct brt_heade ...@@ -632,6 +655,29 @@ int deserialize_brtheader (u_int32_t size, int fd, DISKOFF off, struct brt_heade
h->free_blocks = rbuf_blocknum(&rc); h->free_blocks = rbuf_blocknum(&rc);
h->unused_blocks = rbuf_blocknum(&rc); h->unused_blocks = rbuf_blocknum(&rc);
h->n_named_roots = rbuf_int(&rc); h->n_named_roots = rbuf_int(&rc);
h->block_allocation_vector_length = rbuf_blocknum(&rc);
h->block_allocation_vector_location = rbuf_diskoff(&rc);
{
u_int64_t *block_locations_and_lengths = 0;
if (h->block_allocation_vector_length.b > 0) {
MALLOC_N(h->block_allocation_vector_length.b * 2, block_locations_and_lengths);
assert(block_locations_and_lengths);
u_int64_t len = h->block_allocation_vector_length.b * 16 + 4;
ssize_t r = pread(fd, block_locations_and_lengths, len, h->block_allocation_vector_location);
assert(r==(ssize_t)len);
u_int32_t x1764 = x1764_memory(block_locations_and_lengths, len-4); // compute a checksum
assert(x1764 == *(u_int32_t*)(block_locations_and_lengths+h->block_allocation_vector_length.b*2));
int i;
for (i=0; i<h->block_allocation_vector_length.b * 2; i++) {
block_locations_and_lengths[i] = ntohll(block_locations_and_lengths[i]);
}
}
#if 0
int r = create_block_allocator(&h->block_allocator, h->block_allocation_vector_length.b, block_locations_and_lengths, h->nodesize);
assert(r==0);
#endif
if (block_locations_and_lengths) free(block_locations_and_lengths);
}
if (h->n_named_roots>=0) { if (h->n_named_roots>=0) {
int i; int i;
int n_to_malloc = (h->n_named_roots == 0) ? 1 : h->n_named_roots; int n_to_malloc = (h->n_named_roots == 0) ? 1 : h->n_named_roots;
......
...@@ -20,7 +20,7 @@ typedef unsigned int ITEMLEN; ...@@ -20,7 +20,7 @@ typedef unsigned int ITEMLEN;
typedef const void *bytevec; typedef const void *bytevec;
//typedef const void *bytevec; //typedef const void *bytevec;
typedef long long DISKOFF; /* Offset in a disk. -1 is the NULL pointer. */ typedef int64_t DISKOFF; /* Offset in a disk. -1 is the NULL pointer. */
typedef u_int64_t TXNID; typedef u_int64_t TXNID;
typedef struct s_blocknum { int64_t b; } BLOCKNUM; // make a struct so that we will notice type problems. typedef struct s_blocknum { int64_t b; } BLOCKNUM; // make a struct so that we will notice type problems.
......
...@@ -741,7 +741,7 @@ int toku_logprint_DISKOFF (FILE *outf, FILE *inf, const char *fieldname, struct ...@@ -741,7 +741,7 @@ int toku_logprint_DISKOFF (FILE *outf, FILE *inf, const char *fieldname, struct
DISKOFF v; DISKOFF v;
int r = toku_fread_DISKOFF(inf, &v, checksum, len); int r = toku_fread_DISKOFF(inf, &v, checksum, len);
if (r!=0) return r; if (r!=0) return r;
fprintf(outf, " %s=%lld", fieldname, v); fprintf(outf, " %s=%" PRId64, fieldname, v);
return 0; return 0;
} }
int toku_logprint_BLOCKNUM (FILE *outf, FILE *inf, const char *fieldname, struct x1764 *checksum, u_int32_t *len, const char *format __attribute__((__unused__))) { int toku_logprint_BLOCKNUM (FILE *outf, FILE *inf, const char *fieldname, struct x1764 *checksum, u_int32_t *len, const char *format __attribute__((__unused__))) {
......
...@@ -18,6 +18,12 @@ void *toku_malloc(size_t size) { ...@@ -18,6 +18,12 @@ void *toku_malloc(size_t size) {
return malloc(size); return malloc(size);
} }
void *toku_xmalloc(size_t size) {
void *r = malloc(size);
if (r==0) abort();
return r;
}
void *toku_tagmalloc(size_t size, enum typ_tag typtag) { void *toku_tagmalloc(size_t size, enum typ_tag typtag) {
//printf("%s:%d tagmalloc\n", __FILE__, __LINE__); //printf("%s:%d tagmalloc\n", __FILE__, __LINE__);
void *r = toku_malloc(size); void *r = toku_malloc(size);
......
...@@ -22,6 +22,10 @@ enum typ_tag { TYP_BRTNODE = 0xdead0001, ...@@ -22,6 +22,10 @@ enum typ_tag { TYP_BRTNODE = 0xdead0001,
/* Everything should call toku_malloc() instead of malloc(), and toku_calloc() instead of calloc() */ /* Everything should call toku_malloc() instead of malloc(), and toku_calloc() instead of calloc() */
void *toku_calloc(size_t nmemb, size_t size); void *toku_calloc(size_t nmemb, size_t size);
void *toku_malloc(size_t size); void *toku_malloc(size_t size);
// xmalloc aborts instead of return NULL if we run out of memory
void *toku_xmalloc(size_t size);
/* toku_tagmalloc() performs a malloc(size), but fills in the first 4 bytes with typ. /* toku_tagmalloc() performs a malloc(size), but fills in the first 4 bytes with typ.
* This "tag" is useful if you are debugging and run across a void* that is * This "tag" is useful if you are debugging and run across a void* that is
* really a (struct foo *), and you want to figure out what it is. * really a (struct foo *), and you want to figure out what it is.
...@@ -53,6 +57,11 @@ void *toku_realloc(void *, size_t size); ...@@ -53,6 +57,11 @@ void *toku_realloc(void *, size_t size);
#define REALLOC_N(n,v) v = toku_realloc(v, (n)*sizeof(*v)) #define REALLOC_N(n,v) v = toku_realloc(v, (n)*sizeof(*v))
// XMALLOC macros are like MALLOC except they abort if the operatoin fails
#define XMALLOC(v) v = toku_xmalloc(sizeof(*v))
#define XMALLOC_N(n,v) v = toku_malloc((n)*sizeof(*v))
#define XREALLOC_N(n,v) v = toku_realloc(v, (n)*sizeof(*v))
/* If you have a type such as /* If you have a type such as
* struct pma *PMA; * struct pma *PMA;
* and you define a corresponding int constant, such as * and you define a corresponding int constant, such as
......
...@@ -49,6 +49,7 @@ endif ...@@ -49,6 +49,7 @@ endif
# Put these one-per-line so that if we insert a new one the svn diff can understand it better. # Put these one-per-line so that if we insert a new one the svn diff can understand it better.
# Also keep them sorted. # Also keep them sorted.
REGRESSION_TESTS = \ REGRESSION_TESTS = \
block_allocator_test \
bread-test \ bread-test \
brt-serialize-test \ brt-serialize-test \
brt-test \ brt-test \
......
/* -*- mode: C; c-basic-offset: 4 -*- */
#include "block_allocator.h"
#include "toku_assert.h"
#include <stdlib.h>
static void ba_alloc_at (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t offset) {
block_allocator_validate(ba);
block_allocator_alloc_block_at(ba, size, offset);
block_allocator_validate(ba);
}
static void ba_alloc (BLOCK_ALLOCATOR ba, u_int64_t size, u_int64_t *answer) {
block_allocator_validate(ba);
block_allocator_alloc_block(ba, size, answer);
block_allocator_validate(ba);
}
static void ba_free (BLOCK_ALLOCATOR ba, u_int64_t offset) {
block_allocator_validate(ba);
block_allocator_free_block(ba, offset);
block_allocator_validate(ba);
}
// Simple block allocator test
static void
test_ba0 (void) {
BLOCK_ALLOCATOR ba;
u_int64_t b0, b1;
create_block_allocator(&ba, 100);
ba_alloc_at(ba, 50, 100);
ba_alloc_at(ba, 25, 150);
ba_alloc (ba, 10, &b0);
assert(b0==175);
ba_free(ba, 150);
ba_alloc_at(ba, 10, 150);
ba_alloc(ba, 10, &b0);
assert(b0==160);
ba_alloc(ba, 10, &b0);
ba_alloc(ba, 113, &b1);
assert(113==block_allocator_block_size(ba, b1));
assert(10==block_allocator_block_size(ba, b0));
assert(50==block_allocator_block_size(ba, 100));
u_int64_t b2, b3, b4, b5, b6, b7;
ba_alloc(ba, 100, &b2);
ba_alloc(ba, 100, &b3);
ba_alloc(ba, 100, &b4);
ba_alloc(ba, 100, &b5);
ba_alloc(ba, 100, &b6);
ba_alloc(ba, 100, &b7);
ba_free(ba, b2);
ba_alloc(ba, 100, &b2);
ba_free(ba, b4);
ba_free(ba, b6);
u_int64_t b8, b9;
ba_alloc(ba, 100, &b4);
ba_free(ba, b2);
ba_alloc(ba, 100, &b6);
ba_alloc(ba, 100, &b8);
ba_alloc(ba, 100, &b9);
ba_free(ba, b6);
ba_free(ba, b7);
ba_free(ba, b8);
ba_alloc(ba, 100, &b6);
ba_alloc(ba, 100, &b7);
ba_free(ba, b4);
ba_alloc(ba, 100, &b4);
destroy_block_allocator(&ba);
assert(ba==0);
}
// Manually to get coverage of all the code in the block allocator.
static void
test_ba1 (int n_initial) {
BLOCK_ALLOCATOR ba;
create_block_allocator(&ba, 0);
int i;
int n_blocks=0;
u_int64_t blocks[1000];
for (i=0; i<1000; i++) {
if (i<n_initial || random()%2 == 0) {
if (n_blocks<1000) {
ba_alloc(ba, 1, &blocks[n_blocks]);
//printf("A[%d]=%ld\n", n_blocks, blocks[n_blocks]);
n_blocks++;
}
} else {
if (n_blocks>0) {
int blocknum = random()%n_blocks;
//printf("F[%d]%ld\n", blocknum, blocks[blocknum]);
ba_free(ba, blocks[blocknum]);
blocks[blocknum]=blocks[n_blocks-1];
n_blocks--;
}
}
}
destroy_block_allocator(&ba);
assert(ba==0);
}
int
main (int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__))) {
test_ba0();
test_ba1(0);
test_ba1(10);
test_ba1(20);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment