From 456699bed2d2d9fd6b62ba8e1c5bab63c0830d43 Mon Sep 17 00:00:00 2001 From: zhangjipeng Date: Wed, 19 Feb 2025 17:23:42 +0800 Subject: [PATCH] add linear allocator --- ext/common/common.cmake | 18 +++++ ext/common/psx_linear_allocator.c | 129 ++++++++++++++++++++++++++++++ ext/common/psx_linear_allocator.h | 61 ++++++++++++++ ext/ext.cmake | 1 + unit_tests/ext_array.cpp | 2 +- unit_tests/ext_linear_alloc.cpp | 121 ++++++++++++++++++++++++++++ unit_tests/unit_tests.cmake | 2 +- 7 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 ext/common/common.cmake create mode 100644 ext/common/psx_linear_allocator.c create mode 100644 ext/common/psx_linear_allocator.h create mode 100644 unit_tests/ext_linear_alloc.cpp diff --git a/ext/common/common.cmake b/ext/common/common.cmake new file mode 100644 index 0000000..ce3b3b7 --- /dev/null +++ b/ext/common/common.cmake @@ -0,0 +1,18 @@ +# Picasso - a vector graphics library +# +# Copyright (C) 2024 Zhang Ji Peng +# Contact: onecoolx@gmail.com + +set(PXCOMMON_DIR ${PROJECT_ROOT}/ext/common) + +set(PXCOMMON_SOURCES + ${PXCOMMON_DIR}/psx_linear_allocator.h + ${PXCOMMON_DIR}/psx_linear_allocator.c +) + +set(LIBX_COMMON psx_common) + +add_library(${LIBX_COMMON} STATIC ${PXCOMMON_SOURCES}) +install(TARGETS ${LIBX_COMMON} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) +include_directories(${PXCOMMON_DIR} ${PROJECT_ROOT}/ext/common) + diff --git a/ext/common/psx_linear_allocator.c b/ext/common/psx_linear_allocator.c new file mode 100644 index 0000000..7dcd0a6 --- /dev/null +++ b/ext/common/psx_linear_allocator.c @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2025, Zhang Ji Peng + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "psx_linear_allocator.h" + +#define LINEAR_DEFAULT_BLOCK_SIZE (1024) // 1kb +#define MAX_BLOCK_SIZE ((size_t)65536) // 64kb + +#define MEM_ALIGN(x, shift) \ + (((x) + ((shift) - 1)) & ~(shift - 1)) + +typedef struct _mem_block { + struct _mem_block* next; + uint32_t block_size; + uint8_t data[1]; +} mem_block_t; + +typedef struct { + psx_linear_allocator base; + mem_block_t* blocks; + mem_block_t* cur_block; + void* next_ptr; + psx_memory_align_t align; +} linear_allocator_impl; + +static INLINE bool fits_block(linear_allocator_impl* mem, size_t size) +{ + return mem->next_ptr && + (((uint8_t*)mem->next_ptr) + size) <= (((uint8_t*)mem->cur_block) + mem->cur_block->block_size); +} + +static INLINE void ensure_next(linear_allocator_impl* mem, size_t size) +{ + if (fits_block(mem, size)) { + return; + } + + size_t block_size = MEM_ALIGN(LINEAR_DEFAULT_BLOCK_SIZE, PSX_LINEAR_ALIGN_DEFAULT); + size_t block_hdr_size = sizeof(mem_block_t*) + sizeof(uint32_t); + + while ((block_size - block_hdr_size) < size) { + block_size *= 2; + } + + mem->base.total_memory += block_size; + + mem_block_t* block = malloc(block_size); + block->next = NULL; + block->block_size = (uint32_t)block_size; + + if (mem->cur_block) { + mem->cur_block->next = block; + } + + mem->cur_block = block; + + if (!mem->blocks) { + mem->blocks = mem->cur_block; + } + + mem->next_ptr = (void*)(&block->data); + mem->next_ptr = (void*)MEM_ALIGN((uintptr_t)mem->next_ptr, (uintptr_t)mem->align); +} + +static INLINE void* _linear_alloc(struct _linear_allocator* mem, size_t size) +{ + if (size > MAX_BLOCK_SIZE) { + fprintf(stderr, "Allocating more than 64kb of memory using the linear allocator is not allowed!\n"); + return NULL; + } + + linear_allocator_impl* p = (linear_allocator_impl*)mem; + size = MEM_ALIGN(size, p->align); + ensure_next(p, size); + void* ptr = p->next_ptr; + p->next_ptr = ((uint8_t*)p->next_ptr) + size; + return ptr; +} + +psx_linear_allocator* psx_linear_allocator_create(psx_memory_align_t align) +{ + linear_allocator_impl* mem = (linear_allocator_impl*)malloc(sizeof(linear_allocator_impl)); + memset(mem, 0, sizeof(linear_allocator_impl)); + + mem->base.alloc = _linear_alloc; + mem->base.total_memory = 0; + mem->align = align; + + return (psx_linear_allocator*)mem; +} + +void psx_linear_allocator_destroy(psx_linear_allocator* mem) +{ + mem_block_t* b = ((linear_allocator_impl*)mem)->blocks; + + while (b) { + mem_block_t* next = b->next; + free(b); + b = next; + } + free(mem); +} diff --git a/ext/common/psx_linear_allocator.h b/ext/common/psx_linear_allocator.h new file mode 100644 index 0000000..3410eba --- /dev/null +++ b/ext/common/psx_linear_allocator.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Zhang Ji Peng + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PSX_LINEAR_ALLOCATOR_H_ +#define _PSX_LINEAR_ALLOCATOR_H_ + +#include "psx_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + PSX_LINEAR_ALIGN_1 = 1, + PSX_LINEAR_ALIGN_2 = (1 << 1), + PSX_LINEAR_ALIGN_4 = (1 << 2), + PSX_LINEAR_ALIGN_8 = (1 << 3), + PSX_LINEAR_ALIGN_16 = (1 << 4), + PSX_LINEAR_ALIGN_32 = (1 << 5), + PSX_LINEAR_ALIGN_64 = (1 << 6), +} psx_memory_align_t; + +#define PSX_LINEAR_ALIGN_DEFAULT PSX_LINEAR_ALIGN_4 + +typedef struct _linear_allocator { + size_t total_memory; + void* (* alloc)(struct _linear_allocator* allocator, size_t size); +} psx_linear_allocator; + +psx_linear_allocator* psx_linear_allocator_create(psx_memory_align_t align); + +void psx_linear_allocator_destroy(psx_linear_allocator* allocator); + +#ifdef __cplusplus +} +#endif + +#endif /* _PSX_LINEAR_ALLOCATOR_H_ */ diff --git a/ext/ext.cmake b/ext/ext.cmake index b74d316..042836b 100644 --- a/ext/ext.cmake +++ b/ext/ext.cmake @@ -3,5 +3,6 @@ # Copyright (C) 2024 Zhang Ji Peng # Contact: onecoolx@gmail.com +include (${CMAKE_CURRENT_LIST_DIR}/common/common.cmake) include (${CMAKE_CURRENT_LIST_DIR}/image_coders/image.cmake) include (${CMAKE_CURRENT_LIST_DIR}/svg/svg.cmake) diff --git a/unit_tests/ext_array.cpp b/unit_tests/ext_array.cpp index e1ba09e..10e9051 100644 --- a/unit_tests/ext_array.cpp +++ b/unit_tests/ext_array.cpp @@ -142,4 +142,4 @@ TEST_F(PsxArrayTest, RemoveLastTest) psx_array_remove_last(&array); EXPECT_EQ(array.size, 1u); EXPECT_EQ(*(int*)psx_array_at(&array, 0), value1); -} \ No newline at end of file +} diff --git a/unit_tests/ext_linear_alloc.cpp b/unit_tests/ext_linear_alloc.cpp new file mode 100644 index 0000000..72266b0 --- /dev/null +++ b/unit_tests/ext_linear_alloc.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2025, Zhang Ji Peng + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test.h" +#include "timeuse.h" + +#include "psx_linear_allocator.h" + +TEST(LinearAllocatorTest, TestDefaultAlignment) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_DEFAULT); + void* ptr = allocator->alloc(allocator, 100); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_DEFAULT, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAllocationSizes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_DEFAULT); + void* ptr1 = allocator->alloc(allocator, 1); + ASSERT_NE(ptr1, nullptr); + void* ptr4 = allocator->alloc(allocator, 4); + ASSERT_NE(ptr4, nullptr); + void* ptr10 = allocator->alloc(allocator, 10); + ASSERT_NE(ptr10, nullptr); + void* ptr38 = allocator->alloc(allocator, 38); + ASSERT_NE(ptr38, nullptr); + void* ptr122 = allocator->alloc(allocator, 122); + ASSERT_NE(ptr122, nullptr); + void* ptr2345 = allocator->alloc(allocator, 2345); + ASSERT_NE(ptr2345, nullptr); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAllocationOver64KB) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_DEFAULT); + void* ptr = allocator->alloc(allocator, 65540); // > 64kb + ASSERT_EQ(ptr, nullptr); // fail to allocate + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment1Byte) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_1); + void* ptr = allocator->alloc(allocator, 1); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_1, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment4Bytes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_4); + void* ptr = allocator->alloc(allocator, 4); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_4, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment10Bytes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_1); + void* ptr = allocator->alloc(allocator, 10); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_1, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment38Bytes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_2); + void* ptr = allocator->alloc(allocator, 38); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_2, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment122Bytes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_8); + void* ptr = allocator->alloc(allocator, 122); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_8, 0); + psx_linear_allocator_destroy(allocator); +} + +TEST(LinearAllocatorTest, TestAlignment2345Bytes) +{ + psx_linear_allocator* allocator; + allocator = psx_linear_allocator_create(PSX_LINEAR_ALIGN_64); + void* ptr = allocator->alloc(allocator, 2345); + ASSERT_EQ(reinterpret_cast(ptr) % PSX_LINEAR_ALIGN_64, 0); + psx_linear_allocator_destroy(allocator); +} diff --git a/unit_tests/unit_tests.cmake b/unit_tests/unit_tests.cmake index 7d952e5..e3c079d 100644 --- a/unit_tests/unit_tests.cmake +++ b/unit_tests/unit_tests.cmake @@ -21,7 +21,7 @@ set(UNIT_TESTS unit_tests) include_directories(${PROJECT_ROOT}) add_executable(${UNIT_TESTS} ${UNIT_SOURCES}) -target_link_libraries(${UNIT_TESTS} PRIVATE GTest::GTest ${LIB_NAME} ${LIBX_IMAGE} ${LIBX_SVG}) +target_link_libraries(${UNIT_TESTS} PRIVATE GTest::GTest ${LIB_NAME} ${LIBX_COMMON} ${LIBX_IMAGE} ${LIBX_SVG}) if (WIN32) add_custom_command(