Commit 7a8b1f16 authored by Jens Korinth's avatar Jens Korinth
Browse files

Implement ultra-primitive first-fit memory allocator

* does not actually manage memory, only addresses
* for use in pe local memories
parent 1b5c8286
Pipeline #319 passed with stage
in 3 minutes and 36 seconds
//
// Copyright (C) 2018 Jens Korinth, TU Darmstadt
//
// This file is part of Tapasco (TPC).
//
// Tapasco is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tapasco is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Tapasco. If not, see <http://www.gnu.org/licenses/>.
//
//! @file gen_mem.h
//! @brief Generic, header-only memory management library. Can manage
//! address spaces with arbitrary size and base. Extremely light-
//! weight and simplistic, should not be used for applications with
//! frequent and short-lived allocations.
//! @authors J. Korinth, TU Darmstadt (jk@esa.cs.tu-darmstadt.de)
//!
#ifndef GEN_MEM_H__
#define GEN_MEM_H__
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#ifdef GEN_MEM_DEBUG
#define LOG(...) printf(__VA_ARGS__)
#else
#define LOG(...)
#endif
typedef uint32_t addr_t;
#define INVALID_ADDRESS ((addr_t)(-1))
typedef struct block {
addr_t base;
size_t range;
struct block *next;
} block_t;
block_t *gen_mem_create(addr_t const base, size_t const range)
{
assert(base % sizeof(addr_t) == 0 || "base address in gen_mem_create must be aligned with word size");
assert(range % sizeof(addr_t) == 0 || "range in gen_mem_create must be aligned with word size");
block_t *b = (block_t *)malloc(sizeof(*b));
assert(b || "gen_mem_create ran out of memory!");
if (! b) return b;
b->base = base;
b->range = range;
b->next = NULL;
return b;
}
addr_t gen_mem_malloc(block_t **root, size_t const length)
{
assert(root || "argument to gen_mem_malloc may not be NULL");
assert(length > 0 || "length must be > 0");
block_t *prv = *root, *nxt = *root;
while (nxt != NULL && nxt->range < length) {
prv = nxt;
nxt = nxt->next;
}
if (! nxt) return INVALID_ADDRESS;
addr_t const base = nxt->base;
nxt->base += length;
nxt->range -= length;
LOG("alloc'ed 0x%08lx - 0x%08lx\n", (unsigned long)base,
base + length);
if (nxt->range == 0 && nxt != *root) {
prv->next = nxt->next;
free(nxt);
}
return base;
}
void gen_mem_free(block_t **root, addr_t const p, size_t const length)
{
assert(root || "argument to gen_mem_free may not be NULL");
LOG("freeing 0x%08lx - 0x%08lx\n", (unsigned long)p, p + length);
block_t *prv = *root, *nxt = *root;
while (nxt != NULL && nxt->base + nxt->range <= p) {
prv = nxt;
nxt = nxt->next;
}
LOG("prv: 0x%08lx - 0x%08lx\n", (unsigned long)prv->base, prv->base + prv->range);
LOG("nxt: 0x%08lx - 0x%08lx\n", (unsigned long)nxt->base, nxt->base + nxt->range);
if (prv->base + prv->range == p) {
prv->range += length;
if (prv->next && prv->base + prv->range == prv->next->base) {
block_t *del = prv->next;
prv->range += del->range;
prv->next = del->next;
free(del);
}
LOG("merging prv\n");
return;
}
if (nxt != NULL && p + length == nxt->base) {
nxt->base -= length;
nxt->range += length;
LOG("merging nxt\n");
return;
}
LOG("inserting new\n");
block_t *nb = (block_t*)malloc(sizeof(*nb));
assert(nb || "gen_mem_create ran out of memory!");
nb->base = p;
nb->range = length;
if (p + length < prv->base) {
LOG("inserting before\n");
nb->next = prv;
*root = *root == prv ? nb : *root;
} else {
LOG("inserting after\n");
nb->next = prv->next;
prv->next = nb;
}
}
#endif /* GEN_MEM_H__ */
LLVM?=clang
LLVM_OPT+=-fsanitize=address -O3 -g -Wall -Werror -I$(TAPASCO_HOME)/common/include
ifndef TAPASCO_HOME
$(error "TAPASCO_HOME is not set.")
endif
.PHONY: clean
gen_mem_test: gen_mem_test.c $(TAPASCO_HOME)/common/include/gen_mem.h
$(LLVM) $(LLVM_OPT) $< -o $@
clean:
@rm gen_mem_test
/**
* @file test.c
* @brief
* @author J. Korinth, TU Darmstadt (jk@esa.cs.tu-darmstadt.de)
**/
#include "gen_mem.h"
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#define MAX_ALLOCS 100000
#define MAX_SIZE (1024 * 1024)
void print_block(block_t *b) {
if (b) {
printf("block @ 0x%08lx - 0x%08lx\n", (unsigned long)b->base,
b->base + b->range);
print_block(b->next);
} else printf("\n");
}
inline static size_t allocate(block_t **mem, block_t allocs[MAX_ALLOCS])
{
size_t idx = rand() % MAX_ALLOCS;
while (allocs[idx].range != 0) idx = (idx + 1) % MAX_ALLOCS;
allocs[idx].range = rand() % MAX_SIZE;
allocs[idx].base = gen_mem_malloc(mem, allocs[idx].range);
return idx;
}
inline static size_t deallocate(block_t **mem, block_t allocs[MAX_ALLOCS])
{
size_t idx = rand() % MAX_ALLOCS;
while (allocs[idx].range == 0) idx = (idx + 1) % MAX_ALLOCS;
gen_mem_free(mem, allocs[idx].base, allocs[idx].range);
allocs[idx].range = 0;
allocs[idx].base = 0;
return idx;
}
inline static void clean(block_t **mem, block_t allocs[MAX_ALLOCS])
{
for (size_t idx = 0; idx < MAX_ALLOCS; ++idx) {
if (allocs[idx].range != 0) {
gen_mem_free(mem, allocs[idx].base, allocs[idx].range);
}
}
}
void merge_nxt() {
srand(time(NULL));
printf("merge_nxt:\n");
block_t *mem = gen_mem_create(0, 0x1000);
addr_t a = gen_mem_malloc(&mem, 16);
addr_t b = gen_mem_malloc(&mem, 16);
addr_t c = gen_mem_malloc(&mem, 16);
addr_t d = gen_mem_malloc(&mem, 16);
printf("freeing b\n");
gen_mem_free(&mem, b, 16);
print_block(mem);
printf("freeing d\n");
gen_mem_free(&mem, d, 16);
print_block(mem);
printf("freeing a\n");
gen_mem_free(&mem, a, 16);
print_block(mem);
printf("freeing c\n");
gen_mem_free(&mem, c, 16);
print_block(mem);
assert(mem->next == NULL || "expected single block after all frees");
free(mem);
}
void merge_prv() {
srand(time(NULL));
printf("merge_prv:\n");
block_t *mem = gen_mem_create(0, 0x1000);
addr_t a = gen_mem_malloc(&mem, 16);
addr_t b = gen_mem_malloc(&mem, 16);
addr_t c = gen_mem_malloc(&mem, 16);
addr_t d = gen_mem_malloc(&mem, 16);
print_block(mem);
printf("freeing c\n");
gen_mem_free(&mem, c, 16);
print_block(mem);
printf("freeing a\n");
gen_mem_free(&mem, a, 16);
print_block(mem);
printf("freeing d\n");
gen_mem_free(&mem, d, 16);
print_block(mem);
printf("freeing b\n");
gen_mem_free(&mem, b, 16);
print_block(mem);
assert(mem->next == NULL || "expected single block after all frees");
free(mem);
}
void malloc_corners()
{
srand(time(NULL));
printf("malloc_corners:\n");
block_t *mem = gen_mem_create(0, 32);
addr_t a = gen_mem_malloc(&mem, 16);
addr_t b = gen_mem_malloc(&mem, 16);
assert(a != INVALID_ADDRESS || "a must not be invalid");
assert(b != INVALID_ADDRESS || "b must not be invalid");
print_block(mem);
printf("freeing b\n");
gen_mem_free(&mem, b, 16);
print_block(mem);
b = gen_mem_malloc(&mem, 16);
printf("freeing b, a\n");
gen_mem_free(&mem, b, 16);
gen_mem_free(&mem, a, 16);
print_block(mem);
assert(mem->next == NULL || "expected single block after all frees");
free(mem);
}
void check_blocks(block_t *mem)
{
assert(mem || "check_blocks argument must not be NULL");
block_t *prv = mem, *nxt = mem->next;
while (nxt && prv->base + prv->range < nxt->base) {
prv = nxt;
nxt = nxt->next;
}
if (nxt) {
fprintf(stderr, "ERROR: block list is invalid!\n");
print_block(mem);
exit(1);
}
}
void stress_test() {
srand(time(NULL));
block_t *mem = gen_mem_create(0, 1 << 31);
struct timeval tv_start, tv_now;
gettimeofday(&tv_start, NULL);
block_t allocs[MAX_ALLOCS];
memset(allocs, 0, sizeof(allocs));
size_t total_alloc = 0;
size_t curr_alloc = 0;
size_t curr_sz = 0;
do {
if (!curr_alloc || (curr_alloc < MAX_ALLOCS && rand() % 2)) {
//printf("allocating (%zd / %zd)\n", curr_alloc, total_alloc);
curr_sz += allocs[allocate(&mem, allocs)].range;
++curr_alloc; ++total_alloc;
} else {
//printf("deallocating (%zd / %zd)\n", curr_alloc, total_alloc);
curr_sz -= allocs[deallocate(&mem, allocs)].range;
--curr_alloc;
}
check_blocks(mem);
gettimeofday(&tv_now, NULL);
} while (tv_now.tv_sec - tv_start.tv_sec < 30);
clean(&mem, allocs);
printf("finished after %zd allocations.\n", total_alloc);
print_block(mem);
free(mem);
}
int main() {
malloc_corners();
merge_prv();
merge_nxt();
printf("now performing stress test for 30secs ... ");
fflush(stdout);
stress_test();
printf(" done.\n");
}
/* vim: set foldmarker=@{,@} foldlevel=0 foldmethod=marker : */
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