commit f82f8288cf369a4f833d06216cf9ccbf7d9b996e Author: SoraKatadzuma Date: Fri Aug 7 18:33:50 2026 -0500 Initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2903287 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.chinook +.environ +.vscode diff --git a/chinookfile b/chinookfile new file mode 100644 index 0000000..51f01ee --- /dev/null +++ b/chinookfile @@ -0,0 +1,38 @@ +name: allocators +gpid: malunal +semv: 1.0.0 + +requires: +- remote: git@git.erasit.com:malunal/assert + branch: v1.1.0 +- remote: git@git.erasit.com:malunal/config + branch: v1.0.2 +- remote: git@git.erasit.com:malunal/types + branch: v1.0.0 + +targets: +- name: malunal.allocators + type: archive + deps: + - malunal.assert + - malunal.config + - malunal.types + srcs: + - ./sources/allocator.c + - ./sources/arena.c + +tests: +- name: allocator_tests + type: program + defs: + - MALUNAL_ENABLE_ASSERTIONS + deps: + - malunal.allocators + srcs: + - ./tests/libc_allocator.c + - ./tests/platform_allocator.c + - ./tests/arena_allocator.c + - ./tests/allocator.c + +exports: +- malunal.allocators diff --git a/include/malunal/allocators.h b/include/malunal/allocators.h new file mode 100644 index 0000000..a04f934 --- /dev/null +++ b/include/malunal/allocators.h @@ -0,0 +1,552 @@ +/** + * @file allocators.h + * @brief This header contains the definition of the memory functions and basic + * allocator object. + * @author John Christman (sorakatadzuma@gmail.com) + * @copyright Malunal Studios, LLC. + */ +#include "malunal/config.h" +#include "malunal/types.h" + +#ifndef MALUNAL_ALLOCATORS_HEADER +#define MALUNAL_ALLOCATORS_HEADER + +// Assuring that NULL_ADDRESS is correct. +#ifdef NULL_ADDRESS +#undef NULL_ADDRESS +#endif /* NULL_ADDRESS */ + +/** + * @def NULL_ADDRESS + * @brief Defines a null address. + * @details This is needed for functions to indicate that an address could not + * acquired. + */ +#define NULL_ADDRESS ((void*)0) + +/** + * @brief Defines the basis of all allocators. + * @details You can create new allocators simply by providing your own virtual + * function table, an optional upstream allocator, and the allocator + * context. + */ +typedef struct Allocator allocator_t; + +/** + * @brief A pointer to a mutable allocator. + * @details This is provided to simplify type declarations for functions + * requiring allocators that are meant to be mutable. + */ +typedef allocator_t* allocator_mptr_t; + +/** + * @brief A pointer to a immutable allocator view. + * @details This is provided to simplify type declarations for functions + * requiring allocators that are meant to be immutable. + */ +typedef const allocator_t* allocator_iptr_t; + + +/** + * @brief Defines a set of exception that an allocator may throw. + * @details These are extremely useful for debugging an allocator or catching + * runtime issues that can be fixed. + */ +typedef enum AllocatorException { + /** + * @brief No exception was thrown. + * @details Some allocator functions only return an exception. This indicates + * for those functions that the operation was successful. + */ + ALLOCATOR_EXCEPTION_SUCCESS, + + /** + * @brief The generic allocator exception. + * @details If the allocator does not know what to throw, or in test cases, + * this may be thrown. + */ + ALLOCATOR_EXCEPTION_FAILURE, + + /** + * @brief The allocator instance was null. + * @details Allocator functions take it by reference because it is a rather + * large object. So, when the reference is expected to be non-null, + * which is always, this will be thrown. + */ + ALLOCATOR_EXCEPTION_NULL_ALLOCATOR, + + /** + * @brief The allocator upstream was null. + * @details Allocator implementations may expect for the upstream parameter + * to pointer to a valid allocator. For those allocators that expect + * an upstream, and do not receive it, this will be thrown. + */ + ALLOCATOR_EXCEPTION_NULL_UPSTREAM, + + /** + * @brief The allocator context was null. + * @details Allocator implementations often expect for the context parameter + * to point to a valid address. For those allocators that expect a + * context, and do not receive it, this will be thrown. + */ + ALLOCATOR_EXCEPTION_NULL_CONTEXT, + + /** + * @brief The allocator ran out of memory. + * @details By nature of memory being a limited resource, if the system or the + * allocator run out of memory this will be thrown. + */ + ALLOCATOR_EXCEPTION_OUT_OF_MEMORY, + + /** + * @brief The address being released does not belong to the allocator. + * @details Allocators expect any given address to have been allocated by + * itself. Most allocators can check if it was allocated from its + * memory pool, and if it wasn't this will be thrown. + */ + ALLOCATOR_EXCEPTION_NOT_MY_ADDRESS, + + /** + * @brief The allocator has determined that memory is being leaked. + * @details Given the diagnostics within an allocator, the allocator may find + * some memory is still in use upon release or finalization. If this + * happens, the allocator may throw this exception. + */ + ALLOCATOR_EXCEPTION_LEAKY_MEMORY, +} allocator_exception_t; + + +/** + * @brief Packs together the information needed to initialize an allocator. + * @details By using a struct like this, we can easily indicate what parameters + * are being passed into the function. + */ +typedef struct AllocatorInitializeRequest { + /** + * @brief The allocator to initialize. + * @details There is a sort of double dispatch methodology with allocators. + * The primary @c allocator_initialize function will simply call into + * the provided allocators virtual function table, passing this data + * to it. This makes it possible for the virtual function to interact + * with the entirety of the allocator as if inherited. + */ + allocator_mptr_t allocator; + + /** + * @brief The upstream allocator for allocator being initialized. + * @details This allows the allocator to get large memory allocations from an + * upstream allocator, such that it can partition those larger memory + * blocks. + */ + allocator_mptr_t upstream; + + /** + * @brief The size to initialize the allocator to. + * @details It's useful for an allocator to be initialized to some initial + * size so that it can immediately begin to hand out allocated + * addresses. + */ + malunal_size_t size; +} allocator_initialize_req_t; + +/** + * @brief A type definition for a pointer to a function that can initialize + * some allocator instance. + * @param request Contains the information necessary to fulfill the request. + * @returns @c ALLOCATOR_EXCEPTION_SUCCESS if the request could be fulfilled; + * otherwise, any other exception value. + */ +typedef allocator_exception_t +(*allocator_initialize_pfn_t)( + allocator_initialize_req_t request +); + +/** + * @brief A type definition for a pointer to a function that can finalize some + * allocator instance. + * @param allocator The allocator to finalize. + * @returns @c ALLOCATOR_EXCEPTION_SUCCESS if the allocator could be finalized; + * otherwise, any other exception value. + */ +typedef allocator_exception_t +(*allocator_finalize_pfn_t)( + allocator_mptr_t allocator +); + + +/** + * @brief Packs together the information needed to acquire some memory. + * @details By using a struct like this, we can easily indicate what parameters + * are being passed into the function. + */ +typedef struct AllocatorAcquireRequest { + /** + * @brief The allocator to use to acquire the memory. + * @details There is a sort of double dispatch methodology with allocators. + * The primary @c allocator_acquire function will simply call into + * the provided allocators virtual function table, passing this data + * to it. This makes it possible for the virtual function to interact + * with the entirety of the allocator as if inherited. + */ + allocator_mptr_t allocator; + + /** + * @brief The size of the allocation to acquire. + * @details Allocators need this to obtain a memory block. Allocators may also + * use it to perform bounds checking, update internals, or otherwise. + */ + malunal_size_t size; +} allocator_acquire_req_t; + +/** + * @brief Represents the result of an acquisition request. + * @details This has two parts to it: a fulfilled acquisition and an possible + * exception. Which part is put into the result is indicated by the + * @c threw tag. + */ +typedef struct AllocatorAcquireResult { + /** + * @brief Indicates whether the result contains an allocation or exception. + * @details Allocators will set this to true when an exception has occurred, + * such as when there is no more memory to allocate from. + */ + malunal_bool_t threw; + + union { + /** + * @brief The success of fulfilling the acquisition request. + * @details If the allocator sucessfully fulfilled the request, this will + * contain the pointer of the allocation. + */ + malunal_mptr_t address; + + /** + * @brief The failure of fulfilling the acquisition request. + * @details If the allocator unsuccessfully fulfilled the request, this will + * contain the reason for failure. + */ + allocator_exception_t exception; + }; +} allocator_acquire_res_t; + +/** + * @brief A type definition of a pointer to a function that can acquire some + * newly allocated memory address. + * @param request Contains the information necessary to fulfill the request. + * @returns The result of acquisition which indicates if the function threw or + * if the request was fulfilled along with the exception or allocation. + */ +typedef allocator_acquire_res_t +(*allocator_acquire_pfn_t)( + allocator_acquire_req_t request +); + + +/** + * @brief Packs together the information needed to reacquire some memory. + * @details By using a struct like this, we can easily indicate what parameters + * are being passed into the function. + */ +typedef struct AllocatorReacquireRequest { + /** + * @brief The allocator to use to reacquire the memory. + * @details There is a sort of double dispatch methodology with allocators. + * The primary @c allocator_acquire function will simply call into + * the provided allocators virtual function table, passing this data + * to it. This makes it possible for the virtual function to interact + * with the entirety of the allocator as if inherited. + */ + allocator_mptr_t allocator; + + /** + * @brief The address of the previous allocation. + * @details The memory at this address will typically be released after its + * contents are copied to the new address, but some allocators will + * not fully release it, only soft release it. + */ + malunal_mptr_t prev; + + /** + * @brief The size of the allocation to release. + * @details Allocators need this to release a memory block. Allocators may + * also use it to perform bounds checking, update internals, or + * otherwise. + */ + malunal_size_t oldsz; + + /** + * @brief The size of the allocation to reacquire. + * @details Allocators need this to obtain a memory block. Allocators may also + * use it to perform bounds checking, update internals, or otherwise. + */ + malunal_size_t newsz; +} allocator_reacquire_req_t; + +/** + * @brief A type definition of a pointer to a function that can reacquire some + * newly allocated memory address. + * @param request Contains the information necessary to fulfill the request. + * @returns The result of reacquisition which indicates if the function threw or + * if the request was fulfilled along with the exception or allocation. + */ +typedef allocator_acquire_res_t +(*allocator_reacquire_pfn_t)( + allocator_reacquire_req_t request +); + + +/** + * @brief Packs together the information needed to release some memory. + * @details By using a struct like this, we can easily indicate what parameters + * are being passed into the function. + */ +typedef struct AllocatorReleaseRequest { + /** + * @brief The allocator to use to release the memory. + * @details There is a sort of double dispatch methodology with allocators. + * The primary @c allocator_release function will simply call into + * the provided allocators virtual function table, passing this data + * to it. This makes it possible for the virtual function to interact + * with the entirety of the allocator as if inherited. + */ + allocator_mptr_t allocator; + + /** + * @brief The address of the allocation to release. + * @details The address can be null, as most allocators will simply ignore it. + * However, when the address is not null, it should be aligned unless + * an allocator explicitly requires an address to be unaligned. + */ + malunal_mptr_t address; + + /** + * @brief The size of the allocation to release. + * @details Allocators need this to release a memory block. Allocators may + * also use it to perform bounds checking, update internals, or + * otherwise. + */ + malunal_size_t size; +} allocator_release_req_t; + +/** + * @brief A type definition of a pointer to a function that can release some + * previously allocated memory address. + * @param request + */ +typedef allocator_exception_t +(*allocator_release_pfn_t)( + allocator_release_req_t request +); + + +/** + * @brief Defines the basis of all allocators. + * @details You can create new allocators simply by providing your own virtual + * function table, an optional upstream allocator, and the allocator + * context. + */ +struct Allocator { + /** + * @brief The virtual function table for the allocator. + * @details Contains the pointers to the functions that make this allocator + * struct effectively an interface. + */ + struct { + /** + * @brief The initialization funciton for a given allocator. + * @details This is responsible for initializing an allocator, which may + * include acquiring some memory from an upstream allocator or + * otherwise. + */ + const allocator_initialize_pfn_t initialize; + + /** + * @brief The finalization function for a given allocator. + * @details This is responsible for finalizing an allocator, which may + * include releasing some memory to an upstream allocator or + * otherwise. + */ + const allocator_finalize_pfn_t finalize; + + /** + * @brief The acquisition function for a given allocator. + * @details This is responsible for acquiring a new valid memory address for + * the calling code to use. + */ + const allocator_acquire_pfn_t acquire; + + /** + * @brief The reacquisition function for a given allocator. + * @details This is responsible for acquiring a new valid memory address for + * the calling code, copying the old contents over to the new address + * automatically for the caller. + */ + const allocator_reacquire_pfn_t reacquire; + + /** + * @brief The release function for a given allocator. + * @details This is responsible for releasing a previously allocated memory + * address. The caller is expected to destroy anything within the + * valid address. + */ + const allocator_release_pfn_t release; + } const vtable; + + /** + * @brief The upstream allocator to this allocator. + * @details The purpose of an upstream allocator is to provide this allocator + * the ability to obtain other memory pages or blocks that it plans + * to further partition. + */ + allocator_mptr_t upstream; + + /** + * @brief The context data for the allocator. + * @details This is passed to the allocator implementation through the vtable + * functions for the ability of the implementation to access its own + * data in the virtual functions. + */ + malunal_mptr_t context; + + /** + * @brief How much of the reserved memory has been allocated. + * @details This is a piece of diagnostics that might be useful to know how + * much memory is being allocated by this allocator. + */ + malunal_size_t allocated; + + /** + * @brief How much memory was reserved by this allocator. + * @details This is a piece of diagnostics that might be useful to know how + * much memory is being reserved by this allocator. + */ + malunal_size_t reserved; + + /** + * @brief How many times this allocator was acquired from. + * @details This is a piece of diagnostics that might be useful to know how + * often this allocator is being used. + */ + malunal_size_t acquires; + + /** + * @brief How many times this allocator was released from. + * @details This is a piece of diagnostics that might be useful to know how + * often this allocator is being used. + */ + malunal_size_t releases; +}; + + +/** + * @brief Executes an acquisition request on an allocator. + * @details This simplifies the execution of an allocator's virtual function + * table entries, specifically @c acquire. + * @param request The acquisition request being made. + * @returns The result of the acquisition request to the allocator. + */ +allocator_acquire_res_t +allocator_acquire(allocator_acquire_req_t request); + +/** + * @brief Executes a reacquisition request on an allocator. + * @details This simplifies the execution of an allocator's virtual function + * table entries, specifically @c reacquire. + * @param request The reacquisition request being made. + * @returns The result of the reacquisition request to the allocator. + */ +allocator_acquire_res_t +allocator_reacquire(allocator_reacquire_req_t request); + +/** + * @brief Executes a release request on an allocator. + * @details This simplifies the execution of an allocator's virtual function + * table entries, specifically @c release. + * @param request The release request being made. + * @returns The result of the release request to the allocator. + */ +allocator_exception_t +allocator_release(allocator_release_req_t request); + +/** + * @brief Rounds the given size up to the nearest multiple of @c alignment. + * @param size The current size to round up to the nearest multiple of + * the provided @c alignment. + * @param alignment The alignment to round the size to. + * @returns The rounded size. + */ +malunal_size_t +align_to( + malunal_size_t size, + malunal_size_t alignment +); + +/** + * @brief Rounds the given size up to the nearest page. + * @details This will give you a size that is rounded up to the next multiple of + * the platform page size. For example, if you provide 4000 and the + * platform page size is 4096, then you will get 4096. Likewise, if you + * provided 5000 and the platform page size is 4096, then you will get + * 8192. + * @param size The current size that will be rounded up to the nearest page + * multiple for the platform. + * @returns The rounded page size. + */ +malunal_size_t +align_to_page(malunal_size_t size); + +/** + * @brief Provides the platform page size. + * @details This can be really useful when you plan to allocate a lot of memory. + * Platforms typically round memory requests up to the nearest page and + * give the base address of the virtually allocated block back, but + * placing a guard on the portion of the memory that will not be used. + * By querying the page size for the platform and rounding a memory + * acquisition request up to the nearest page, you can get the full + * block the platform intends to hand back which, significantly helps + * in proper memory tracking. + * @returns The memory page size for the platform. + */ +malunal_size_t +platform_page_size(); + +/** + * @brief Provides the general libc allocator. + * @details This will provide a general purpose allocator backed by the standard + * library. The functions provided are equivalent to @c malloc, + * @c realloc, and @c free. + * @returns An allocator that interfaces with libc. + */ +allocator_t +libc_allocator(); + +/** + * @brief Provides the platform allocator. + * @details This will provide the appropriate platform allocator depending on + * what operating system this library was compiled for. + * @returns One of: @c linux_allocator or @c win32_allocator. + */ +allocator_t +platform_allocator(); + +#if MALUNAL_PLATFORM_LINUX +/** + * @brief Provides a linux specific allocator. + * @details This is the linux specific allocator. It uses @c mmap and @c munmap + * to acquire, reacquire, and release memory. + * @returns An allocator that works specifically with linux. + */ +allocator_t +linux_allocator(); +#elif MALUNAL_PLATFORM_WIN32 +/** + * @brief Provides a windows specific allocator. + * @details Thisis the windows specific allocator. It uses @c VirtualAlloc and + * @c VirtualFree to acquire, reacquire, and release memory. + * @returns An allocator that works specifically with windows. + */ +allocator_t +win32_allocator(); +#endif /* Platform specific allocators */ + +#endif /* MALUNAL_ALLOCATORS_HEADER */ diff --git a/include/malunal/allocators/arena.h b/include/malunal/allocators/arena.h new file mode 100644 index 0000000..981822a --- /dev/null +++ b/include/malunal/allocators/arena.h @@ -0,0 +1,150 @@ +/** + * @file arena.h + * @brief Contains the definition of an arena allocator and all the functions + * needed to utilize it. + * @author John Christman (sorakatadzuma@gmail.com) + * @copyright Malunal Studios, LLC. + */ +#include "../allocators.h" + +#ifndef MALUNAL_ALLOCATORS_ARENA_HEADER +#define MALUNAL_ALLOCATORS_ARENA_HEADER + +/** + * @def ARENA_DEFAULT_PAGE_SIZE + * @brief Defines the default page size for the arena in terms of bytes. + * @details This should be, but is not required to be, a multiple of 4KiB. This + * is because, on most systems the memory page size will be 4KiB. + */ +#ifndef ARENA_DEFAULT_PAGE_SIZE +#define ARENA_DEFAULT_PAGE_SIZE 8192 +#endif /* ARENA_DEFAULT_PAGE_SIZE */ + + +/** + * @brief Defines an arena allocator page. + * @details Pages are large blocks of memory typically acquired from the + * operating system, but may be acquired from any upstream allocator. + * These pages may be subdivided by another allocator if the arena is + * used as an upstream itself. + */ +typedef struct ArenaAllocatorPage arena_page_t; + +/** + * @brief A pointer to a mutable arena page. + * @details This is provided to simplify type declarations for functions + * requiring arena pages that are meant to be mutable. + */ +typedef arena_page_t* arena_page_mptr_t; + +/** + * @brief A pointer to a immutable arena page. + * @details This is provided to simplify type declarations for functions + * requiring arena pages that are meant to be immutable. + */ +typedef const arena_page_t* arena_page_iptr_t; + + +struct ArenaAllocatorPage { + /** + * @brief A pointer to the next page. + * @details This is a linked list of pages making it possible for the arena + * to easily add new pages and find available memory blocks. + */ + arena_page_mptr_t next; + + /** + * @brief How much of this page is already been used. + * @details This helps the arena know if it can fit the allocation request it + * has into this page, acquiring a new page if necessary. + */ + malunal_uint32_t used; + + /** + * @brief The full size of this page. + * @details This helps the arena know if it can fit the allocation request it + * has into this page, acquiring a new page if necessary. + */ + malunal_uint32_t size; + + /** + * @brief The acquired page data. + * @details This is where the arena allocator will retrieve a memory block + * address from when fulfilling an acquisition request. + */ + malunal_int8_t data[]; +}; + +/** + * @brief Initializes the given arena. + * @details The arena is initialized by using the upstream allocator to obtain + * pages to fill the arena with. + * @param arena The arena to initialize. + * @param upstream The allocator to use to acquire arena pages. + * @param size The initial amount of memory to acquire for the arena. + */ +allocator_t +arena_allocator( + allocator_mptr_t upstream, + malunal_size_t size +); + +/** + * @brief Arena implementation for memory acquisition. + * @details This will find the next available memory location within the pages + * of the arena and reserve the required memory for the acquisition + * request. + * @param request The request to an arena allocator to acquire the specified + * amount of memory listed in the request. + * @returns An object containing either the address acquired or an exception if + * the allocator threw. + */ +allocator_acquire_res_t +arena_acquire(allocator_acquire_req_t request); + +/** + * @brief Arena implementation for memory re-acquisition. + * @details This works exactly the same as @c arena_acquire but will copy the + * contents of the previous block to the new block. + * @param request The request to an arena allocator to reacquire the specified + * amount of memory listed in the request, releasing the old memory. + * @returns An object containing either the address acquired or an exception if + * the allocator threw. + */ +allocator_acquire_res_t +arena_reacquire(allocator_reacquire_req_t request); + +/** + * @brief Arena implementation for memory release. + * @details For an arena, this will not do anything internally. This is because + * arenas are like push allocators. They are there so a bunch of data + * can be dumped in it and released all at once. + * @param request The request to an arena allocator to release the specified + * amount of memory listed in the request. + * @returns An exception if the memory could not be released. + */ +allocator_exception_t +arena_release(allocator_release_req_t request); + +/** + * @brief Resets the entire arena without freeing its pages. + * @details This will iterate through each of the pages acquired for the arena + * setting the usage of each page to zero. This will allow the arena to + * overwrite the data within the pages. + * @param arena The arena to reset. + * @returns An exception if the arena could not be reset. + */ +allocator_exception_t +arena_reset(allocator_mptr_t arena); + +/** + * @brief Frees the entire arena. + * @details This will iterate through each of the pages acquired for the arena, + * releasing them back to the upstream. + * @param arena The arena to free. + * @returns An exception if the arena could not be freed. + */ +allocator_exception_t +arena_free(allocator_mptr_t arena); + +#endif /* MALUNAL_ALLOCATORS_ARENA_HEADER */ diff --git a/sources/allocator.c b/sources/allocator.c new file mode 100644 index 0000000..6ffaa37 --- /dev/null +++ b/sources/allocator.c @@ -0,0 +1,359 @@ +#include +#include +#include "malunal/allocators.h" +#include "malunal/assert.h" + +#if MALUNAL_PLATFORM_LINUX +# include +# include +#elif MALUNAL_PLATFORM_WIN32 +# include +#endif /* Platform headers */ + + +allocator_acquire_res_t +allocator_acquire(allocator_acquire_req_t request) { + if (request.allocator == NULL_ADDRESS) + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_NULL_ALLOCATOR + }; + + return request.allocator->vtable.acquire != NULL_ADDRESS + ? request.allocator->vtable.acquire(request) + : (allocator_acquire_res_t) { + .threw = 0, + .address = NULL_ADDRESS + }; +} + +allocator_acquire_res_t +allocator_reacquire(allocator_reacquire_req_t request) { + if (request.allocator == NULL_ADDRESS) + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_NULL_ALLOCATOR + }; + + return request.allocator->vtable.reacquire != NULL_ADDRESS + ? request.allocator->vtable.reacquire(request) + : (allocator_acquire_res_t) { + .threw = 0, + .address = NULL_ADDRESS + }; +} + +allocator_exception_t +allocator_release(allocator_release_req_t request) { + if (request.allocator == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_ALLOCATOR; + + return request.allocator->vtable.release != NULL_ADDRESS + ? request.allocator->vtable.release(request) + : ALLOCATOR_EXCEPTION_SUCCESS; +} + +malunal_size_t +align_to( + malunal_size_t size, + malunal_size_t alignment +) { + return (size + alignment - 1) & ~(alignment - 1); +} + +malunal_size_t +align_to_page(malunal_size_t size) { + return align_to(size, platform_page_size()); +} + +static +malunal_size_t +g_platform_page_size = 0; + +malunal_size_t +platform_page_size() { + if (g_platform_page_size != 0) + return g_platform_page_size; + +#if MALUNAL_PLATFORM_LINUX + g_platform_page_size = sysconf(_SC_PAGESIZE); +#elif MALUNAL_PLATFORM_WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + g_platform_page_size = si.dwPageSize; +#endif + + return g_platform_page_size; +} + + +static +allocator_acquire_res_t +libc_acquire(allocator_acquire_req_t request); + +static +allocator_acquire_res_t +libc_reacquire(allocator_reacquire_req_t request); + +static +allocator_exception_t +libc_release(allocator_release_req_t request); + + +static +allocator_acquire_res_t +libc_acquire(allocator_acquire_req_t request) { + allocator_acquire_res_t result; + + malunal_mptr_t addr = malloc(request.size); + if (addr == NULL_ADDRESS) { + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_OUT_OF_MEMORY + }; + } + + request.allocator->allocated += request.size; + request.allocator->reserved += request.size; + request.allocator->acquires += 1; + return (allocator_acquire_res_t) { + .threw = 0, + .address = addr + }; +} + +static +allocator_acquire_res_t +libc_reacquire(allocator_reacquire_req_t request) { + allocator_acquire_res_t result = + libc_acquire((allocator_acquire_req_t) { + .allocator = request.allocator, + .size = request.newsz + }); + + if (result.threw) + return result; + + result.address = memcpy( + result.address, + request.prev, + request.oldsz + ); + + libc_release((allocator_release_req_t) { + .allocator = request.allocator, + .address = request.prev, + .size = request.oldsz + }); + + return result; +} + +static +allocator_exception_t +libc_release(allocator_release_req_t request) { + free(request.address); + request.allocator->allocated -= request.size; + request.allocator->reserved -= request.size; + request.allocator->releases += 1; + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +allocator_t +libc_allocator() { + return (allocator_t) { + .vtable = { + .acquire = &libc_acquire, + .reacquire = &libc_reacquire, + .release = &libc_release + }, + .context = NULL_ADDRESS + }; +} + +allocator_t +platform_allocator() { +#if MALUNAL_PLATFORM_LINUX + return linux_allocator(); +#elif MALUNAL_PLATFORM_WIN32 + return win32_allocator(); +#endif /* Platform specific allocators */ +} + +#if MALUNAL_PLATFORM_LINUX +static +allocator_acquire_res_t +linux_acquire(allocator_acquire_req_t request); + +static +allocator_acquire_res_t +linux_reacquire(allocator_reacquire_req_t request); + +static +allocator_exception_t +linux_release(allocator_release_req_t request); + + +static +allocator_acquire_res_t +linux_acquire(allocator_acquire_req_t request) { + const malunal_int32_t memops = PROT_READ | PROT_WRITE; + const malunal_int32_t memprms = MAP_PRIVATE | MAP_ANONYMOUS; + + allocator_acquire_res_t result; + + // TODO: + malunal_size_t size = request.size; + malunal_mptr_t ptr = mmap(0, size, memops, memprms, -1, 0); + if (ptr == MAP_FAILED || ptr == NULL_ADDRESS) { + result = (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_OUT_OF_MEMORY + }; + } else { + result = (allocator_acquire_res_t) { + .threw = 0, + .address = ptr + }; + } + + return result; +} + +static +allocator_acquire_res_t +linux_reacquire(allocator_reacquire_req_t request) { + allocator_acquire_res_t result = + linux_acquire((allocator_acquire_req_t) { + .allocator = request.allocator, + .size = request.newsz + }); + + if (result.threw) + return result; + + result.address = memcpy( + result.address, + request.prev, + request.oldsz + ); + + linux_release((allocator_release_req_t) { + .allocator = request.allocator, + .address = request.prev, + .size = request.oldsz + }); + + return result; +} + +static +allocator_exception_t +linux_release(allocator_release_req_t request) { + munmap(request.address, request.size); + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +allocator_t +linux_allocator() { + return (allocator_t) { + .vtable = { + .acquire = &linux_acquire, + .reacquire = &linux_reacquire, + .release = &linux_release + }, + .context = NULL_ADDRESS + }; +} +#elif MALUNAL_PLATFORM_WIN32 +static +allocator_acquire_res_t +win32_acquire(allocator_acquire_req_t request); + +static +allocator_acquire_res_t +win32_reacquire(allocator_reacquire_req_t request); + +static +allocator_exception_t +win32_release(allocator_release_req_t request); + +static +allocator_acquire_res_t +win32_acquire(allocator_acquire_req_t request) { + const malunal_int32_t memops = MEM_COMMIT | MEM_RESERVE; + const malunal_int32_t pageops = PAGE_READWRITE; + + malunal_mptr_t addr = VirtualAlloc( + NULL_ADDRESS, + request.size, + memops, + pageops + ); + + if (addr == NULL_ADDRESS) { + result = (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_OUT_OF_MEMORY + }; + } else { + result = (allocator_acquire_res_t) { + .threw = 0, + .address = addr + }; + } + + return result; +} + +static +allocator_acquire_res_t +win32_reacquire(allocator_reacquire_req_t request) { + allocator_acquire_res_t result = + win32_acquire((allocator_acquire_req_t) { + .allocator = request.allocator, + .size = request.newsz + }); + + if (result.threw) + return result; + + result.address = memcpy( + result.address, + request.prev, + request.oldsz + ); + + win32_release((allocator_release_req_t) { + .allocator = request.allocator, + .address = request.prev, + .size = request.oldsz + }); + + return result; +} + +static +allocator_exception_t +win32_release(allocator_release_req_t request) { + VirtualFree( + request.address, + request.size, + MEM_RELEASE + ); + + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +allocator_t +win32_allocator() { + return (allocator_t) { + .vtable = { + .acquire = &win32_acquire, + .reacquire = &win32_reacquire, + .release = &win32_release + }, + .context = NULL_ADDRESS + }; +} +#endif /* Platform specific allocators */ diff --git a/sources/arena.c b/sources/arena.c new file mode 100644 index 0000000..5598b79 --- /dev/null +++ b/sources/arena.c @@ -0,0 +1,180 @@ +#include +#include "malunal/allocators/arena.h" +#include "malunal/assert.h" + + +allocator_acquire_res_t +create_page(allocator_acquire_req_t request) { + if (request.allocator == NULL_ADDRESS) { + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_NULL_UPSTREAM + }; + } + + allocator_acquire_res_t result = + allocator_acquire(request); + if (result.threw) + return result; + + arena_page_mptr_t page = result.address; + page->size = request.size; + page->used = sizeof(arena_page_t); + page->next = NULL_ADDRESS; + return result; +} + +allocator_exception_t +delete_page( + allocator_mptr_t allocator, + arena_page_mptr_t page +) { + if (allocator == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_UPSTREAM; + + if (page == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_FAILURE; + + delete_page(allocator, page->next); + return allocator_release((allocator_release_req_t) { + .allocator = allocator, + .address = page, + .size = page->size + }); +} + +allocator_acquire_res_t +page_acquire(allocator_acquire_req_t request) { + arena_page_mptr_t page = request.allocator->context; + if (page == NULL_ADDRESS) { + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_NULL_CONTEXT + }; + } + + malunal_uint32_t offset = page->used - sizeof(arena_page_t); + malunal_mptr_t result = page->data + offset; + page->used += request.size; + return (allocator_acquire_res_t) { + .threw = 0, + .address = result + }; +} + +allocator_t +arena_allocator( + allocator_mptr_t upstream, + malunal_size_t size +) { + allocator_acquire_req_t request = + (allocator_acquire_req_t) { + .allocator = upstream, + .size = platform_page_size() + }; + + allocator_acquire_res_t result = create_page(request); + arena_page_mptr_t currpage = + !result.threw ? result.address : NULL_ADDRESS; + + malunal_size_t reserved = 0; + if (currpage != NULL_ADDRESS) { + reserved += currpage->size; + while (reserved > size) { + result = create_page(request); + if (result.threw) + break; + currpage = currpage->next = result.address; + reserved += currpage->size; + } + } + + return (allocator_t) { + .vtable = { + .acquire = (allocator_acquire_pfn_t)&arena_acquire, + .reacquire = (allocator_reacquire_pfn_t)&arena_reacquire, + .release = (allocator_release_pfn_t)&arena_release + }, + .upstream = upstream, + .context = currpage + }; +} + +allocator_acquire_res_t +arena_acquire(allocator_acquire_req_t request) { + if (request.allocator == NULL_ADDRESS) { + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_NULL_ALLOCATOR + }; + } + + if (request.size > platform_page_size()) { + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_OUT_OF_MEMORY + }; + } + + arena_page_mptr_t page = request.allocator->context; + while (page != NULL_ADDRESS) { + malunal_uint32_t remaining = page->size - page->used; + if (request.size <= remaining) + return page_acquire(request); + page = page->next; + continue; + } + + allocator_acquire_res_t result = + create_page((allocator_acquire_req_t) { + .allocator = request.allocator->upstream, + .size = platform_page_size() + }); + + page = page->next = !result.threw + ? result.address + : NULL_ADDRESS; + + return page != NULL_ADDRESS + ? page_acquire(request) + : (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_OUT_OF_MEMORY + }; +} + +allocator_acquire_res_t +arena_reacquire(allocator_reacquire_req_t request) { + return arena_acquire((allocator_acquire_req_t) { + .allocator = request.allocator, + .size = request.newsz + }); +} + +allocator_exception_t +arena_release(allocator_release_req_t request) { + if (request.allocator == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_ALLOCATOR; + + if (request.allocator->context == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_CONTEXT; +} + +allocator_exception_t +arena_reset(allocator_mptr_t arena) { + if (arena == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_ALLOCATOR; + + arena_page_mptr_t page = arena->context; + while (page != NULL_ADDRESS) { + page->used = 0; + page = page->next; + } +} + +allocator_exception_t +arena_free(allocator_mptr_t arena) { + if (arena == NULL_ADDRESS) + return ALLOCATOR_EXCEPTION_NULL_ALLOCATOR; + delete_page(arena->upstream, arena->context); +} diff --git a/tests/allocator.c b/tests/allocator.c new file mode 100644 index 0000000..9a08438 --- /dev/null +++ b/tests/allocator.c @@ -0,0 +1,11 @@ +extern int test_libc_allocator(); +extern int test_platform_allocator(); +extern int test_arena_allocator(); + +int main(void) { + return !( + test_libc_allocator() && + test_platform_allocator() && + test_arena_allocator() + ); +} diff --git a/tests/arena_allocator.c b/tests/arena_allocator.c new file mode 100644 index 0000000..15fe08d --- /dev/null +++ b/tests/arena_allocator.c @@ -0,0 +1,419 @@ +#include +#include "malunal/allocators/arena.h" + +const malunal_size_t page_bytes = 4096; +const malunal_size_t init_bytes = sizeof(malunal_int32_t) * 4; +const malunal_size_t final_bytes = sizeof(malunal_int32_t) * 8; + +static +allocator_acquire_res_t +test_arena_acquires(allocator_mptr_t arena) { + allocator_acquire_res_t result = + allocator_acquire((allocator_acquire_req_t) { + .allocator = arena, + .size = init_bytes + }); + + if (result.threw) + return result; + + // Upstream checks. + if (arena->upstream->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->reserved); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->upstream->allocated != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->allocated != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->allocated); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->upstream->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->acquires); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->upstream->releases != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->releases != 0, actual == %d"; + fprintf(stderr, format, arena->upstream->releases); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + // Arena checks. + if (arena->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->reserved); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->allocated != init_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->allocated != %d, actual == %d"; + fprintf(stderr, format, init_bytes, arena->allocated); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->acquires); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + if (arena->releases != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->releases != 0, actual == %d"; + fprintf(stderr, format, arena->releases); + return (allocator_acquire_res_t) { + .threw = 1, + .exception = ALLOCATOR_EXCEPTION_FAILURE + }; + } + + return result; +} + +static int +test_arena_reacquire( + allocator_mptr_t arena, + malunal_mptr_t address +) { + malunal_int32_t index; + malunal_int32_t* vector; + + vector = address; + for (index = 0; index < 4; index++) + vector[index] = index + 1; + + allocator_acquire_res_t result = + allocator_reacquire((allocator_reacquire_req_t) { + .allocator = arena, + .prev = result.address, + .oldsz = init_bytes, + .newsz = final_bytes + }); + + if (result.threw) + return result.exception; + + // Upstream checks. + if (arena->upstream->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->reserved); + return 1; + } + + if (arena->upstream->allocated != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->allocated != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->allocated); + return 1; + } + + if (arena->upstream->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->acquires); + return 1; + } + + if (arena->upstream->releases != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->releases != 0, actual == %d"; + fprintf(stderr, format, arena->upstream->releases); + return 1; + } + + // Arena checks. + if (arena->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->reserved); + return 1; + } + + malunal_int32_t all_bytes = init_bytes + final_bytes; + if (arena->allocated != all_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->allocated != %d, actual == %d"; + fprintf(stderr, format, all_bytes, arena->allocated); + return 1; + } + + if (arena->acquires != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->acquires != 2, actual == %d"; + fprintf(stderr, format, arena->acquires); + return 1; + } + + if (arena->releases != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->releases != 1, actual == %d"; + fprintf(stderr, format, arena->releases); + return 1; + } + + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +static int +test_arena_release( + allocator_mptr_t arena, + malunal_mptr_t address +) { + malunal_int32_t index; + malunal_int32_t* vector; + + vector = address; + for (index = 0; index < 4; index++) + if (vector[index] != index + 1) + return 1; + + allocator_exception_t relexc = + allocator_release((allocator_release_req_t) { + .allocator = arena, + .address = address, + .size = final_bytes + }); + + if (relexc != ALLOCATOR_EXCEPTION_SUCCESS) + return relexc; + + // Upstream checks. + if (arena->upstream->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->reserved); + return 1; + } + + if (arena->upstream->allocated != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->allocated != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->allocated); + return 1; + } + + if (arena->upstream->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->acquires); + return 1; + } + + if (arena->upstream->releases != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->releases != 0, actual == %d"; + fprintf(stderr, format, arena->upstream->releases); + return 1; + } + + // Arena checks. + if (arena->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->reserved); + return 1; + } + + malunal_int32_t all_bytes = init_bytes + final_bytes; + if (arena->allocated != all_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->allocated != %d, actual == %d"; + fprintf(stderr, format, all_bytes, arena->allocated); + return 1; + } + + if (arena->acquires != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->acquires != 2, actual == %d"; + fprintf(stderr, format, arena->acquires); + return 1; + } + + if (arena->releases != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->releases != 2, actual == %d"; + fprintf(stderr, format, arena->releases); + return 1; + } + + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +static int +test_arena_reset(allocator_mptr_t arena) { + arena_reset(arena); + + // Upstream checks. + if (arena->upstream->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->reserved); + return 1; + } + + if (arena->upstream->allocated != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->allocated != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->upstream->allocated); + return 1; + } + + if (arena->upstream->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->acquires); + return 1; + } + + if (arena->upstream->releases != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->releases != 0, actual == %d"; + fprintf(stderr, format, arena->upstream->releases); + return 1; + } + + // Arena checks. + if (arena->reserved != page_bytes) { + const malunal_cstr_t format = + "[arena_allocator]: arena->reserved != %d, actual == %d"; + fprintf(stderr, format, page_bytes, arena->reserved); + return 1; + } + + if (arena->allocated != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->allocated != 0, actual == %d"; + fprintf(stderr, format, arena->allocated); + return 1; + } + + if (arena->acquires != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->acquires != 2, actual == %d"; + fprintf(stderr, format, arena->acquires); + return 1; + } + + if (arena->releases != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->releases != 2, actual == %d"; + fprintf(stderr, format, arena->releases); + return 1; + } + + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +static int +test_arena_free(allocator_mptr_t arena) { + arena_free(arena); + + // Upstream checks. + if (arena->upstream->reserved != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->reserved != 0, actual == %d"; + fprintf(stderr, format, arena->upstream->reserved); + return 1; + } + + if (arena->upstream->allocated != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->allocated != %d, actual == %d"; + fprintf(stderr, format, arena->upstream->allocated); + return 1; + } + + if (arena->upstream->acquires != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->acquires != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->acquires); + return 1; + } + + if (arena->upstream->releases != 1) { + const malunal_cstr_t format = + "[arena_allocator]: arena->upstream->releases != 1, actual == %d"; + fprintf(stderr, format, arena->upstream->releases); + return 1; + } + + // Arena checks. + if (arena->reserved != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->reserved != %d, actual == %d"; + fprintf(stderr, format, arena->reserved); + return 1; + } + + if (arena->allocated != 0) { + const malunal_cstr_t format = + "[arena_allocator]: arena->allocated != 0, actual == %d"; + fprintf(stderr, format, arena->allocated); + return 1; + } + + if (arena->acquires != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->acquires != 2, actual == %d"; + fprintf(stderr, format, arena->acquires); + return 1; + } + + if (arena->releases != 2) { + const malunal_cstr_t format = + "[arena_allocator]: arena->releases != 2, actual == %d"; + fprintf(stderr, format, arena->releases); + return 1; + } + + return ALLOCATOR_EXCEPTION_SUCCESS; +} + +int test_arena_allocator() { + allocator_t upstream = platform_allocator(); + allocator_t arena = arena_allocator(&upstream, page_bytes); + + allocator_acquire_res_t result = + test_arena_acquires(&arena); + + return !( + !result.threw && + test_arena_reacquire(result.address, &arena) && + test_arena_release(result.address, &arena) && + test_arena_reset(&arena) && + test_arena_free(&arena) + ); +} diff --git a/tests/libc_allocator.c b/tests/libc_allocator.c new file mode 100644 index 0000000..4293fd1 --- /dev/null +++ b/tests/libc_allocator.c @@ -0,0 +1,134 @@ +#include +#include "malunal/allocators.h" + + +int test_libc_allocator() { + const malunal_size_t init_bytes = sizeof(malunal_int32_t) * 4; + const malunal_size_t final_bytes = sizeof(malunal_int32_t) * 8; + + allocator_acquire_res_t result; + allocator_t allocator = libc_allocator(); + + result = allocator_acquire((allocator_acquire_req_t) { + .allocator = &allocator, + .size = init_bytes + }); + + if (result.threw) + return result.exception; + + if (allocator.reserved != init_bytes) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.reserved != %d, actual == %d"; + fprintf(stderr, format, init_bytes, allocator.reserved); + return 1; + } + + if (allocator.allocated != init_bytes) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.allocated != %d, actual == %d"; + fprintf(stderr, format, init_bytes, allocator.allocated); + return 1; + } + + if (allocator.acquires != 1) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.acquires != 1, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 0) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.releases != 0, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + malunal_int32_t index; + malunal_int32_t* vector; + + vector = result.address; + for (index = 0; index < 4; index++) + vector[index] = index + 1; + + result = allocator_reacquire((allocator_reacquire_req_t) { + .allocator = &allocator, + .prev = result.address, + .oldsz = init_bytes, + .newsz = final_bytes + }); + + if (result.threw) + return result.exception; + + if (allocator.reserved != final_bytes) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.reserved != %d, actual == %d"; + fprintf(stderr, format, final_bytes, allocator.reserved); + return 1; + } + + if (allocator.allocated != final_bytes) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.allocator != %d, actual == %d"; + fprintf(stderr, format, final_bytes, allocator.reserved); + return 1; + } + + if (allocator.acquires != 2) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.acquires != 2, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 1) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.releases != 1, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + vector = result.address; + for (index = 0; index < 4; index++) + if (vector[index] != index + 1) + return 1; + + allocator_exception_t relexc = + allocator_release((allocator_release_req_t) { + .allocator = &allocator, + .address = result.address, + .size = final_bytes + }); + + if (allocator.reserved != 0) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.reserved != 0, actual == %d"; + fprintf(stderr, format, allocator.reserved); + return 1; + } + + if (allocator.allocated != 0) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.allocator != 0, actual == %d"; + fprintf(stderr, format, allocator.reserved); + return 1; + } + + if (allocator.acquires != 2) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.acquires != 2, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 2) { + const malunal_cstr_t format = + "[libc_allocator]: allocator.releases != 2, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + return relexc == ALLOCATOR_EXCEPTION_SUCCESS; +} diff --git a/tests/platform_allocator.c b/tests/platform_allocator.c new file mode 100644 index 0000000..8d1a041 --- /dev/null +++ b/tests/platform_allocator.c @@ -0,0 +1,134 @@ +#include +#include "malunal/allocators.h" + + +int test_platform_allocator() { + const malunal_size_t init_bytes = sizeof(malunal_int32_t) * 4; + const malunal_size_t final_bytes = sizeof(malunal_int32_t) * 8; + + allocator_acquire_res_t result; + allocator_t allocator = platform_allocator(); + + result = allocator_acquire((allocator_acquire_req_t) { + .allocator = &allocator, + .size = init_bytes + }); + + if (result.threw) + return result.exception; + + if (allocator.reserved != init_bytes) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.reserved != %d, actual == %d"; + fprintf(stderr, format, init_bytes, allocator.reserved); + return 1; + } + + if (allocator.allocated != init_bytes) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.allocated != %d, actual == %d"; + fprintf(stderr, format, init_bytes, allocator.allocated); + return 1; + } + + if (allocator.acquires != 1) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.acquires != 1, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 0) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.releases != 0, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + malunal_int32_t index; + malunal_int32_t* vector; + + vector = result.address; + for (index = 0; index < 4; index++) + vector[index] = index + 1; + + result = allocator_reacquire((allocator_reacquire_req_t) { + .allocator = &allocator, + .prev = result.address, + .oldsz = init_bytes, + .newsz = final_bytes + }); + + if (result.threw) + return result.exception; + + if (allocator.reserved != final_bytes) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.reserved != %d, actual == %d"; + fprintf(stderr, format, final_bytes, allocator.reserved); + return 1; + } + + if (allocator.allocated != final_bytes) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.allocator != %d, actual == %d"; + fprintf(stderr, format, final_bytes, allocator.reserved); + return 1; + } + + if (allocator.acquires != 2) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.acquires != 2, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 1) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.releases != 1, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + vector = result.address; + for (index = 0; index < 4; index++) + if (vector[index] != index + 1) + return 1; + + allocator_exception_t relexc = + allocator_release((allocator_release_req_t) { + .allocator = &allocator, + .address = result.address, + .size = final_bytes + }); + + if (allocator.reserved != 0) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.reserved != 0, actual == %d"; + fprintf(stderr, format, allocator.reserved); + return 1; + } + + if (allocator.allocated != 0) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.allocator != 0, actual == %d"; + fprintf(stderr, format, allocator.reserved); + return 1; + } + + if (allocator.acquires != 2) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.acquires != 2, actual == %d"; + fprintf(stderr, format, allocator.acquires); + return 1; + } + + if (allocator.releases != 2) { + const malunal_cstr_t format = + "[platform_allocator]: allocator.releases != 2, actual == %d"; + fprintf(stderr, format, allocator.releases); + return 1; + } + + return relexc == ALLOCATOR_EXCEPTION_SUCCESS; +}