Skip to content

Commit

Permalink
add linear allocator
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangjipeng committed Feb 19, 2025
1 parent 2904d88 commit 456699b
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 2 deletions.
18 changes: 18 additions & 0 deletions ext/common/common.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Picasso - a vector graphics library
#
# Copyright (C) 2024 Zhang Ji Peng
# Contact: [email protected]

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)

129 changes: 129 additions & 0 deletions ext/common/psx_linear_allocator.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>
#include <string.h>
#include <stdint.h>

#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);
}
61 changes: 61 additions & 0 deletions ext/common/psx_linear_allocator.h
Original file line number Diff line number Diff line change
@@ -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_ */
1 change: 1 addition & 0 deletions ext/ext.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
# Copyright (C) 2024 Zhang Ji Peng
# Contact: [email protected]

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)
2 changes: 1 addition & 1 deletion unit_tests/ext_array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
121 changes: 121 additions & 0 deletions unit_tests/ext_linear_alloc.cpp
Original file line number Diff line number Diff line change
@@ -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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(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<uintptr_t>(ptr) % PSX_LINEAR_ALIGN_64, 0);
psx_linear_allocator_destroy(allocator);
}
2 changes: 1 addition & 1 deletion unit_tests/unit_tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 456699b

Please sign in to comment.