quakeforge/libs/util/test/test-cmem.c
Bill Currie 1078bd9efa [util] Implement Sys_Free for windows
And get the tests so they can (sort of) be run.
2021-07-12 18:55:16 +09:00

682 lines
17 KiB
C

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include "QF/cmem.h"
#include "QF/set.h"
#include "QF/sys.h"
#define SUPER_LINES (sizeof (memsuper_t) / MEM_LINE_SIZE)
static int
test_block (memsuper_t *super)
{
size_t size = super->page_size;
void *mem = cmemalloc (super, size);
memblock_t *block;
if (!mem) {
fprintf (stderr, "could not allocate %zd byte block\n",
super->page_size);
return 0;
}
if ((size_t) mem & super->page_mask) {
fprintf (stderr, "mem not page aligned: %p %zd\n",
mem, super->page_size);
return 0;
}
block = super->memblocks;
if ((size_t) mem != (size_t) block + super->page_size) {
fprintf (stderr, "super does not point to mem\n");
return 0;
}
if (block->post_size < size) {
fprintf (stderr, "block post_size too small: %zd < %zd\n",
block->post_size, size);
return 0;
}
if (block->post_size - size >= super->page_size) {
fprintf (stderr, "block post_size too big: %zd < %zd\n",
block->post_size - size, super->page_size);
return 0;
}
memset (mem, 0, size); // valgrind check
cmemfree (super, mem);
if ((size_t) super->memblocks + super->page_size == (size_t) mem) {
fprintf (stderr, "super still points to mem\n");
return 0;
}
return 1;
}
static int
check_block (memblock_t *block, int line_count, int allocated)
{
set_t *visited = set_new ();
size_t free_bytes = 0;
int ret = 1;
int count = 0;
for (memline_t **l = &block->free_lines; *l; l = &(*l)->block_next) {
memline_t *line = *l;
ptrdiff_t ind = (byte *) line - (byte *) block;
ind /= MEM_LINE_SIZE;
if (ind < 1 || (size_t ) (ind - 1) > block->size / MEM_LINE_SIZE) {
fprintf (stderr, "line outside of block: %p %p\n",
line, block);
return 0;
}
if (set_is_member (visited, ind)) {
fprintf (stderr, "loop in block free_lines\n");
return 0;
}
count++;
set_add (visited, ind);
if (!line->size) {
fprintf (stderr, "line with size 0\n");
ret = 0;
}
if (line->block_next && line->block_next < line) {
fprintf (stderr, "line link in wrong direction\n");
ret = 0;
}
if ((size_t) line + line->size == (size_t) line->block_next) {
fprintf (stderr, "adjacant free line blocks\n");
ret = 0;
}
if (line->block_prev != l) {
fprintf (stderr, "line block_prev link incorrect\n");
ret = 0;
}
if (line->size > block->size) {
fprintf (stderr, "line size too large: %zd / %zd\n",
line->size, block->size);
ret = 0;
}
if (line->size % MEM_LINE_SIZE) {
fprintf (stderr, "bad line size: %zd / %zd\n",
line->size, line->size % MEM_LINE_SIZE);
ret = 0;
}
if (ret) {
free_bytes += line->size;
}
}
if (ret) {
if (free_bytes + block->allocated != block->size) {
fprintf (stderr, "block space mismatch: s: %zd a: %zd f: %zd\n",
block->size, block->allocated, free_bytes);
ret = 0;
}
if (line_count >= 0 && line_count != count) {
fprintf (stderr, "incorrect number of lines: e: %d g: %d\n",
line_count, count);
ret = 0;
}
if (allocated >= 0 && (size_t) allocated != block->allocated) {
fprintf (stderr, "allocated wrong size: %zd != %d\n",
block->allocated, allocated);
}
}
set_delete (visited);
return ret;
}
static int __attribute__ ((pure))
check_for_loop (memline_t *line, memline_t **stop)
{
memline_t *next = line->free_next;
for (memline_t **l = line->free_prev; l != stop;
l = line->free_prev) {
line = (memline_t *) l;
if (line == next) {
return 1;
}
}
return 0;
}
static int
check_bins (memsuper_t *super, int mask)
{
int ret = 1;
for (int i = MAX_CACHE_LINES; i-- > 0; ) {
if (mask >= 0) {
if (mask & (1 << i)) {
if (!super->free_lines[i]) {
fprintf (stderr, "super free_lines[%d] is empty\n", i);
ret = 0;
}
} else {
if (super->free_lines[i]) {
fprintf (stderr, "super free_lines[%d] is occupied\n", i);
ret = 0;
}
}
}
for (memline_t **l = &super->free_lines[i]; *l; l = &(*l)->free_next) {
memline_t *line = *l;
if (line->free_prev != l) {
fprintf (stderr, "super free_lines[%d] has bad prev\n", i);
ret = 0;
break;
}
if (check_for_loop (line, &super->free_lines[i])) {
fprintf (stderr, "super free_lines[%d] loop detected\n", i);
ret = 0;
break;
}
if ((MEM_LINE_SIZE << i) > (int) line->size
|| (MEM_LINE_SIZE << (i + 1)) <= (int) line->size) {
fprintf (stderr, "line in wrong i: %d %d %zd\n",
i, MEM_LINE_SIZE << i, line->size);
ret = 0;
}
}
}
return ret;
}
static int
test_line (memsuper_t *super)
{
memline_t *line1 = cmemalloc (super, MEM_LINE_SIZE);
memline_t *line2 = cmemalloc (super, MEM_LINE_SIZE);
memline_t *line3 = cmemalloc (super, MEM_LINE_SIZE);
memblock_t *block = super->memblocks;
if (block->next) {
fprintf (stderr, "too many memblocks\n");
return 0;
}
if (line1 < (memline_t *) (block + 1)
|| line1 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line1 outside block line pool: %p %p\n",
line1, block);
return 0;
}
if (line2 < (memline_t *) (block + 1)
|| line2 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line2 outside block line pool: %p %p\n",
line2, block);
return 0;
}
if (line3 < (memline_t *) (block + 1)
|| line3 >= (memline_t *) ((byte *) block + super->page_size)) {
fprintf (stderr, "line3 outside block line pool: %p %p\n",
line3, block);
return 0;
}
if (!((size_t) line1 & super->page_mask)) {
fprintf (stderr, "line1 is page aligned\n");
return 0;
}
if (!((size_t) line2 & super->page_mask)) {
fprintf (stderr, "line2 is page aligned\n");
return 0;
}
if (!((size_t) line3 & super->page_mask)) {
fprintf (stderr, "line3 is page aligned\n");
return 0;
}
if (line1 + 1 != line2 || line2 + 1 != line3) {
fprintf (stderr, "lines not contiguous\n");
return 0;
}
if (line3 + 1 != block->free_lines) {
fprintf (stderr, "line3 not contiguous with free lines\n");
return 0;
}
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 1 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 1 failed\n");
return 0;
}
cmemfree (super, line2);
if (!check_block (block, 2, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 2 failed\n");
return 0;
}
if (!check_bins (super, 0x21)) {
fprintf (stderr, "bin check 2 failed\n");
return 0;
}
if (block->free_lines != line2) {
fprintf (stderr, "block free_lines not pointing to line2\n");
return 0;
}
if (super->free_lines[0] != line2) {
fprintf (stderr, "super free_lines[0] not pointing to line2\n");
return 0;
}
cmemfree (super, line3);
if (!check_block (block, 1, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 3 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 3 failed\n");
return 0;
}
if (block->free_lines != line2) {
fprintf (stderr, "free lines not pointing to line2 2\n");
return 0;
}
cmemfree (super, line1);
if (!check_block (block, 1, (0 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 4 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 4 failed\n");
return 0;
}
if (super->free_lines[5] != line1) {
fprintf (stderr, "super free_lines[5] not pointing to line1\n");
return 0;
}
line1 = cmemalloc (super, MEM_LINE_SIZE);
line2 = cmemalloc (super, MEM_LINE_SIZE);
line3 = cmemalloc (super, MEM_LINE_SIZE);
block = super->memblocks;
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 4 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 4 failed\n");
return 0;
}
cmemfree (super, line1);
if (!check_block (block, 2, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 5 failed\n");
return 0;
}
if (!check_bins (super, 0x21)) {
fprintf (stderr, "bin check 5 failed\n");
return 0;
}
cmemfree (super, line2);
if (!check_block (block, 2, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 6 failed\n");
return 0;
}
if (!check_bins (super, 0x22)) {
fprintf (stderr, "bin check 6 failed\n");
return 0;
}
cmemfree (super, line3);
line1 = cmemalloc (super, MEM_LINE_SIZE);
line2 = cmemalloc (super, MEM_LINE_SIZE);
line3 = cmemalloc (super, MEM_LINE_SIZE);
block = super->memblocks;
if (!check_block (block, 1, (3 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 7 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 7 failed\n");
return 0;
}
cmemfree (super, line3);
if (!check_block (block, 1, (2 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 8 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 8 failed\n");
return 0;
}
cmemfree (super, line2);
if (!check_block (block, 1, (1 + SUPER_LINES) * MEM_LINE_SIZE)) {
fprintf (stderr, "line block check 9 failed\n");
return 0;
}
if (!check_bins (super, 0x20)) {
fprintf (stderr, "bin check 9 failed\n");
return 0;
}
cmemfree (super, line1);
return 1;
}
typedef struct {
size_t size;
int group_id;
} sline_block_t;
static sline_block_t group_tests[] = {
{ 2, 4},
{ 4, 4},
{ 5, 8},
{ 3, 4},
{ 1, 4},
{ 6, 8},
{ 9, 16},
{ 4, 4},
{ 4, 4},
{ 7, 8},
{ 2, 4},
{ 4, 4},
{ 8, 8},
{13, 16},
{ 3, 4},
{ 1, 4},
{ 8, 8},
{ 8, 8},
{ 4, 4},
{ 4, 4},
{16, 16},
{32, 32},
{32, 33},
};
#define num_group_tests (sizeof (group_tests) / sizeof (group_tests[0]))
#define mask(x) (((size_t) (x)) & ~(MEM_LINE_SIZE - 1))
#define pagemask(x,o,s) (((size_t) (x)) & (o (s)->page_mask))
static int
test_sline (memsuper_t *super)
{
void *mem[num_group_tests];
int ret = 1;
for (size_t i = 0; i < num_group_tests; i++) {
mem[i] = cmemalloc (super, group_tests[i].size);
if (!mem[i]) {
fprintf (stderr, "mem[%zd] is null\n", i);
return 0;
}
if (!((size_t) mem[i] % MEM_LINE_SIZE)) {
fprintf (stderr, "mem[%zd] is aligned with chache line\n", i);
ret = 0;
}
}
for (size_t i = 0; i < num_group_tests; i++) {
for (size_t j = i + 1; j < num_group_tests; j++) {
if (mem[i] == mem[j]) {
fprintf (stderr, "mem[%zd] is dupped with %zd\n", i, j);
ret = 0;
}
if (mask (mem[i]) == mask (mem[j])) {
if (group_tests[i].group_id != group_tests[j].group_id) {
fprintf (stderr, "mem[%zd](%d) is grouped with %zd(%d)\n",
i, group_tests[i].group_id,
j, group_tests[j].group_id);
ret = 0;
}
} else {
if (group_tests[i].group_id == group_tests[j].group_id) {
fprintf (stderr,
"mem[%zd](%d) is not grouped with %zd(%d)\n",
i, group_tests[i].group_id,
j, group_tests[j].group_id);
ret = 0;
}
}
if (pagemask (mem[i], ~, super) != pagemask (mem[j], ~, super)) {
fprintf (stderr,
"mem[%zd](%d) is not block grouped with %zd(%d)\n",
i, group_tests[i].group_id,
j, group_tests[j].group_id);
}
}
}
for (size_t i = 0; i < num_group_tests; i++) {
cmemfree (super, mem[i]);
void *newmem = cmemalloc (super, group_tests[i].size);
if (newmem != mem[i]) {
fprintf (stderr,
"%2zd bytes not recycled (%2zd,%2d) (%06zx %06zx)\n",
group_tests[i].size, i, group_tests[i].group_id,
pagemask (mem[i], ~~, super),
pagemask (newmem, ~~, super));
ret = 0;
}
if (!((size_t) newmem % MEM_LINE_SIZE)) {
fprintf (stderr, "newmem is aligned with chache line %p\n",
newmem);
ret = 0;
}
}
return ret;
}
static int
test_block_line (memsuper_t *super)
{
void *mem = cmemalloc (super, 2 * super->page_size);
void *line;
memblock_t *block = super->memblocks;
if ((size_t) block + super->page_size != (size_t) mem) {
fprintf (stderr, "super memblocks do not point to mem\n");
return 0;
}
if (block->size < MEM_LINE_SIZE) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "can't allocate line from block\n");
return 0;
}
if (block->next != (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "excess blocks in super\n");
return 0;
}
line = cmemalloc (super, MEM_LINE_SIZE);
if (!((size_t) line & super->page_mask)) {
fprintf (stderr, "line is page aligned\n");
return 0;
}
if (0 && super->memblocks->next) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "mem and line not in same block\n");
return 0;
}
cmemfree (super, mem);
if (!super->memblocks) {
fprintf (stderr, "shared block freed\n");
return 0;
}
if (cmemalloc (super, super->page_size) != mem) {
fprintf (stderr, "block not reused for mem\n");
return 0;
}
//if (super->memblocks != block || super->memblocks->next) {
if (super->memblocks != block || !super->memblocks->next
|| super->memblocks->next->next) {
// need to figure out a way to guarantee a shared block
fprintf (stderr, "blocks corrupt\n");
return 0;
}
cmemfree (super, line);
if (!super->memblocks) {
fprintf (stderr, "shared block freed 2\n");
return 0;
}
cmemfree (super, mem);
if (0 && super->memblocks) {
fprintf (stderr, "shared block not freed\n");
return 0;
}
return 1;
}
int
main (void)
{
memsuper_t *super = new_memsuper ();
int i;
#if __WORDSIZE == 32
if (sizeof (memsuper_t) != 1 * MEM_LINE_SIZE) {
fprintf (stderr, "memsuper_t not 2 * cache size: %zd\n",
sizeof (memsuper_t));
return 1;
}
#else
if (sizeof (memsuper_t) != 2 * MEM_LINE_SIZE) {
fprintf (stderr, "memsuper_t not 2 * cache size: %zd\n",
sizeof (memsuper_t));
return 1;
}
#endif
if (sizeof (memline_t) != MEM_LINE_SIZE) {
fprintf (stderr, "memline_t not cache size: %zd\n",
sizeof (memline_t));
return 1;
}
if (sizeof (memsline_t) != 2 * sizeof (void *)) {
fprintf (stderr, "memsline_t not two pointers: %zd\n",
sizeof (memsline_t));
return 1;
}
if (sizeof (memblock_t) != MEM_LINE_SIZE) {
fprintf (stderr, "memblock_t not cache size: %zd\n",
sizeof (memblock_t));
return 1;
}
if ((size_t) super & (MEM_LINE_SIZE - 1)) {
fprintf (stderr, "super block not cache aligned: %p\n", super);
return 1;
}
if (super->page_size != Sys_PageSize ()) {
fprintf (stderr, "page size not equal to system page size: %zd, %zd\n",
super->page_size, Sys_PageSize ());
return 1;
}
if (!super->page_size || (super->page_size & (super->page_size - 1))) {
fprintf (stderr, "page size not power of two: %zd\n",
super->page_size);
return 1;
}
if (super->page_mask + 1 != super->page_size) {
fprintf (stderr, "page mask not page size - 1: %zx %zx\n",
super->page_mask, super->page_size);
return 1;
}
if (!super->page_mask || (super->page_mask & (super->page_mask + 1))) {
fprintf (stderr, "page mask not all 1s: %zx\n",
super->page_mask);
return 1;
}
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block a: %p %p\n", super->memblocks, super);
return 1;
}
for (i = 4; i-- > 0; ) {
if (super->last_freed[i]) {
break;
}
}
if (i >= 0) {
fprintf (stderr, "super last_freed not all null\n");
return 1;
}
#if 0 // no longer valid
for (i = MAX_CACHE_LINES; i-- > 0; ) {
if (super->free_lines[i]) {
break;
}
}
if (i >= 0) {
fprintf (stderr, "super free_lines not all null\n");
return 1;
}
#endif
if (!test_block (super)) {
fprintf (stderr, "block tests failed\n");
return 1;
}
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block b: %p %p\n", super->memblocks, super);
return 1;
}
for (i = 4; i-- > 0; ) {
if (super->last_freed[i]) {
break;
}
}
if (i >= 0) {
fprintf (stderr, "super last_freed not all null 2\n");
return 1;
}
#if 0 // no longer valid
for (i = MAX_CACHE_LINES; i-- > 0; ) {
if (super->free_lines[i]) {
break;
}
}
if (i >= 0) {
fprintf (stderr, "super free_lines not all null 2\n");
return 1;
}
#endif
if (!test_line (super)) {
fprintf (stderr, "line tests failed\n");
return 1;
}
if (super->memblocks
!= (memblock_t *) ((size_t) super & ~super->page_mask)) {
fprintf (stderr, "superblock not in block c: %p %p\n", super->memblocks, super);
return 1;
}
if (!test_block_line (super)) {
fprintf (stderr, "block-line tests failed\n");
return 1;
}
for (size_t i = 0; i < 2 * super->page_size / MEM_LINE_SIZE; i++) {
void *line = cmemalloc (super, MEM_LINE_SIZE);
if (!line) {
fprintf (stderr, "could not allocate %d byte line\n",
MEM_LINE_SIZE);
return 1;
}
if ((size_t) line % MEM_LINE_SIZE) {
fprintf (stderr, "line not cache-line aligned: %p %d\n",
line, MEM_LINE_SIZE);
return 1;
}
if (!((size_t) line & super->page_mask)) {
fprintf (stderr, "line is page aligned: %p %zd\n",
line, super->page_size);
return 1;
}
}
if (!test_sline (super)) {
fprintf (stderr, "sub-line tests failed\n");
return 1;
}
delete_memsuper (super);
return 0;
}