diff --git a/programs/develop/ktcc/trunk/libc.obj/.gitignore b/programs/develop/ktcc/trunk/libc.obj/.gitignore new file mode 100644 index 000000000..0b0ee0ab8 --- /dev/null +++ b/programs/develop/ktcc/trunk/libc.obj/.gitignore @@ -0,0 +1,4 @@ +.tup +*.o +*.obj +*.kex diff --git a/programs/develop/ktcc/trunk/libc.obj/samples/Makefile b/programs/develop/ktcc/trunk/libc.obj/samples/Makefile index 49725be3d..453b00026 100644 --- a/programs/develop/ktcc/trunk/libc.obj/samples/Makefile +++ b/programs/develop/ktcc/trunk/libc.obj/samples/Makefile @@ -27,7 +27,8 @@ BIN = \ libc_test.kex \ pipe.kex \ defgen.kex \ - futex.kex + futex.kek \ + malloc_test.kex all: $(BIN) diff --git a/programs/develop/ktcc/trunk/libc.obj/samples/build_all.sh b/programs/develop/ktcc/trunk/libc.obj/samples/build_all.sh index fb6e57cf7..ff7f151cb 100644 --- a/programs/develop/ktcc/trunk/libc.obj/samples/build_all.sh +++ b/programs/develop/ktcc/trunk/libc.obj/samples/build_all.sh @@ -23,5 +23,6 @@ cp clayer/logo.png /tmp0/1/tcc_samples/logo.png ../tcc defgen.c -o /tmp0/1/tcc_samples/defgen ../tcc pipe.c -o /tmp0/1/tcc_samples/pipe ../tcc futex.c -o /tmp0/1/tcc_samples/futex +../tcc malloc_test.c -o /tmp0/1/tcc_samples/malloc_test "/sys/File managers/Eolite" /tmp0/1/tcc_samples exit diff --git a/programs/develop/ktcc/trunk/libc.obj/samples/malloc_test.c b/programs/develop/ktcc/trunk/libc.obj/samples/malloc_test.c new file mode 100644 index 000000000..dfa013590 --- /dev/null +++ b/programs/develop/ktcc/trunk/libc.obj/samples/malloc_test.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include "../source/stdlib/_mem.h" + +#define RUN_TEST(func) \ + printf("---\tRUN TEST: %s\t---\n", #func); \ + if (func()) { \ + printf("[SUCCESS]\tTest %s is ok.\n\n", #func); \ + } else { \ + printf("[FAIL]\tTest %s failed.\n\n", #func); \ + exit(EXIT_FAILURE); \ + } + +// c between a and b +#define IN_RANGE(a, b, c, len) ((a > c && c > b) || ((a > c + len && c + len > b))) + +bool test_malloc_basic_allocation() +{ + void* ptr = malloc(sizeof(int)); + + if (ptr) + free(ptr); + + return ptr; +} +bool test_malloc_zero_bytes() +{ + return malloc(0) == NULL; +} + +bool test_malloc_multiple_allocations() +{ + void* ptr[512]; + + for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) { + ptr[i] = malloc(i); + if (ptr[i] == NULL) { + return false; + } + } + for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) { + for (int j = 1; j < sizeof(ptr) / sizeof(*ptr); j++) { + if (i != j) { + if (ptr[i] == ptr[j]) { + printf("ptrs[%d] == ptrs[%d].\n", i, j); + return false; + } else if (IN_RANGE( + (char*)GET_MEM_NODE_HEADER(ptr[i]) + GET_MEM_NODE_HEADER(ptr[i])->size, + (char*)GET_MEM_NODE_HEADER(ptr[i]), + (char*)GET_MEM_NODE_HEADER(ptr[j]), + GET_MEM_NODE_HEADER(ptr[j])->size)) { + printf("node %p in node %p", GET_MEM_NODE_HEADER(ptr[i]), GET_MEM_NODE_HEADER(ptr[j])); + // additional info, may help with debug + printf("node %p\n size:%p\n free:%p\n next: %p\n last: %p\n", GET_MEM_NODE_HEADER(ptr[i]), GET_MEM_NODE_HEADER(ptr[i])->size, GET_MEM_NODE_HEADER(ptr[i])->free, GET_MEM_NODE_HEADER(ptr[i])->next, GET_MEM_NODE_HEADER(ptr[i])->last); + printf("node %p\n size:%p\n free:%p\n next: %p\n last: %p\n", GET_MEM_NODE_HEADER(ptr[j]), GET_MEM_NODE_HEADER(ptr[j])->size, GET_MEM_NODE_HEADER(ptr[j])->free, GET_MEM_NODE_HEADER(ptr[j])->next, GET_MEM_NODE_HEADER(ptr[j])->last); + exit(EXIT_FAILURE); + } + } + } + } + + for (int i = 1; i < sizeof(ptr) / sizeof(*ptr); i++) { + free(ptr[i]); + } + + return true; +} +bool test_malloc_data_integrity() +{ + const char* As = "AAA"; + const char* Cs = "CCC"; + + char* A = (char*)malloc(10); + char* B = (char*)malloc(10); + char* C = (char*)malloc(10); + + if (!A || !B || !C) { + printf("can't alloc\n"); + free(A); + free(B); + free(C); + return false; + } + + strcpy(A, As); + strcpy(C, Cs); + + free(B); + + if (strcmp(A, As) != 0) { + printf("A data is broken after free(B). A = '%s'\n", A); + free(A); + free(C); + return false; + } + if (strcmp(C, Cs) != 0) { + printf("C data is broken after free(B). C = '%s'\n", C); + free(A); + free(C); + return false; + } + + free(A); + free(C); + return true; +} +bool test_malloc_large_allocation() +{ + void* ptr = malloc(1024 * 1024 * 16); // alloc 16mb + + if (ptr) + free(ptr); + + return ptr; +} +bool test_malloc_allocation_and_free() +{ + free(malloc(sizeof(int))); + return true; +} + +void fill_buffer(void* ptr, size_t size, unsigned char pattern) +{ + if (ptr) { + memset(ptr, pattern, size); + } +} + +bool check_buffer(void* ptr, size_t size, unsigned char pattern) +{ + if (!ptr) { + return false; // Нельзя проверить NULL + } + unsigned char* byte_ptr = (unsigned char*)ptr; + for (size_t i = 0; i < size; ++i) { + if (byte_ptr[i] != pattern) { + fprintf(stderr, "Ошибка: Байт %zu не соответствует паттерну. Ожидалось %02X, получено %02X\n", + i, pattern, byte_ptr[i]); + return false; + } + } + return true; +} + +bool test_realloc_basic_grow() +{ + size_t old_size = 10; + size_t new_size = 20; + int* ptr = (int*)malloc(old_size * sizeof(int)); + + if (ptr == NULL) { + return false; + } + fill_buffer(ptr, old_size * sizeof(int), 0xAA); + + int* new_ptr = (int*)realloc(ptr, new_size * sizeof(int)); + + if (new_ptr == NULL) { + free(ptr); + return false; + } + + if (!check_buffer(new_ptr, old_size * sizeof(int), 0xAA)) { + free(new_ptr); + return false; + } + + fill_buffer(new_ptr + old_size, (new_size - old_size) * sizeof(int), 0xBB); + if (!check_buffer(new_ptr + old_size, (new_size - old_size) * sizeof(int), 0xBB)) { + free(new_ptr); + return false; + } + + free(new_ptr); + return true; +} + +bool test_realloc_basic_shrink() +{ + size_t old_size = 20; + size_t new_size = 10; + int* ptr = (int*)malloc(old_size * sizeof(int)); + + if (ptr == NULL) { + return false; + } + fill_buffer(ptr, old_size * sizeof(int), 0xCC); + + int* new_ptr = (int*)realloc(ptr, new_size * sizeof(int)); + + if (new_ptr == NULL) { + free(ptr); + return false; + } + + if (!check_buffer(new_ptr, new_size * sizeof(int), 0xCC)) { + free(new_ptr); + return false; + } + + free(new_ptr); + return true; +} + +bool test_realloc_same_size() +{ + size_t size = 15; + int* ptr = (int*)malloc(size * sizeof(int)); + + if (ptr == NULL) { + return false; + } + fill_buffer(ptr, size * sizeof(int), 0xDD); + + int* new_ptr = (int*)realloc(ptr, size * sizeof(int)); + + if (new_ptr == NULL) { + free(ptr); + return false; + } + + if (!check_buffer(new_ptr, size * sizeof(int), 0xDD)) { + free(new_ptr); + return false; + } + + free(new_ptr); + + return true; +} + +bool test_realloc_null_ptr() +{ + size_t size = 25; + void* ptr = realloc(NULL, size); + + if (ptr == NULL) { + return false; + } + + fill_buffer(ptr, size, 0xEE); + if (!check_buffer(ptr, size, 0xEE)) { + free(ptr); + return false; + } + + free(ptr); + + return true; +} + +bool test_realloc_to_zero_size() +{ + size_t old_size = 30; + void* ptr = malloc(old_size); + + if (ptr == NULL) { + return false; + } + fill_buffer(ptr, old_size, 0xFF); + + void* new_ptr = realloc(ptr, 0); + + if (new_ptr == NULL) { + printf("realloc(ptr, 0) return NULL.\n"); + free(ptr); + } else { + printf("realloc(ptr, 0) return: %p.\n", new_ptr); + free(new_ptr); + } + return true; +} + +int main() +{ + RUN_TEST(test_malloc_basic_allocation); + RUN_TEST(test_malloc_zero_bytes); + RUN_TEST(test_malloc_multiple_allocations); + RUN_TEST(test_malloc_data_integrity); + RUN_TEST(test_malloc_large_allocation); + RUN_TEST(test_malloc_basic_allocation); + RUN_TEST(test_malloc_allocation_and_free); + RUN_TEST(test_realloc_basic_grow); + RUN_TEST(test_realloc_basic_shrink); + RUN_TEST(test_realloc_same_size); + RUN_TEST(test_realloc_null_ptr); + RUN_TEST(test_realloc_to_zero_size); + + return 0; +} \ No newline at end of file diff --git a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/_mem.h b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/_mem.h new file mode 100644 index 000000000..c91b96f23 --- /dev/null +++ b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/_mem.h @@ -0,0 +1,81 @@ +#ifndef _LIBC_STDLIB__MEM_ +#define _LIBC_STDLIB__MEM_ + +#include + +struct mem_node { + size_t free; // Amount of free space in this node. When equal to size, the entire node is free. + + size_t size; // Total size of this memory node. + + struct mem_node* last; // Pointer to the previous memory node in the linked list. + struct mem_node* next; // Pointer to the next memory node in the linked list. +}; + +struct mem_block { + size_t size; // Size of the allocated memory block. + + size_t a; // align to 8bytes +}; + +// Size of the blocks allocated by `_ksys_alloc` +#define ALLOC_BLOCK_SIZE 4096 + +// Macro to get a pointer to the user data area from a mem_node pointer. +// This is done by adding the size of the mem_node structure to the mem_node pointer. +#define GET_MEM_NODE_PTR(node) (char*)((char*)(node) + sizeof(struct mem_node)) + +// Macro to check if a memory node is completely free. +#define MEM_NODE_IS_FREE(node) (node->free == node->size) + +// Macro to get the amount of used memory in a memory node. +#define GET_MEM_NODE_USED_MEM(node) (node->size - node->free) + +// Macro to get a pointer to the mem_node structure from a user data pointer. +// This is done by subtracting the size of the mem_node structure from the user data pointer. +#define GET_MEM_NODE_HEADER(ptr) ((struct mem_node*)(((char*)ptr) - sizeof(struct mem_node))) + +// Macro to check if two adjacent memory nodes are in the same block. +// Checks if the end of the left node's allocated space is the start of the right node. +#define MEM_NODES_ARE_IN_ONE_BLOCK(left, right) (GET_MEM_NODE_PTR(left) + ((struct mem_node*)left)->size == (char*)right) + +// Macro to merge two adjacent memory nodes. +#define CHECK_SIDE_IN_OTHER_BLOCK(node, side) (side == NULL || ((side != NULL) && !MEM_NODES_ARE_IN_ONE_BLOCK(node, side))) + +// align a value to a specified alignment. +// Ensures that the allocated memory is aligned to a certain boundary +inline size_t __mem_align(size_t value, size_t align) +{ + return ((value + align - 1) & ~(align - 1)); +} + +#define __mem_default_align(value) __mem_align(value, 8) + +inline struct mem_node* __mem_MERGE_MEM_NODES(struct mem_node* base, struct mem_node* addition) +{ + // addition is free && nodes base and addition both in one block, else merge is impossible + if (MEM_NODE_IS_FREE(addition) && MEM_NODES_ARE_IN_ONE_BLOCK(base, addition)) { + // just change size + const size_t s = addition->size + sizeof(struct mem_node); + base->size += s; + base->free += s; + + // and delete addition from list + if (addition->next != NULL) { + addition->next->last = base; + base->next = addition->next; + } else { + base->next = NULL; + } + return base; + } + return NULL; +} + +// Static pointer to the first memory node in the linked list. +// This acts as the head of the memory pool. +static struct mem_node* __mem_node = NULL; + +static struct mem_node* __last_biggest_mem_node = NULL; + +#endif // _LIBC_STDLIB_MEM_ diff --git a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/calloc.c b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/calloc.c index 48f20eec1..f5698e2d2 100644 --- a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/calloc.c +++ b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/calloc.c @@ -1,14 +1,10 @@ -#include #include -#include void* calloc(size_t num, size_t size) { - void* ptr = _ksys_alloc(num * size); - if (!ptr) { - __errno = ENOMEM; - return NULL; + void* ptr = malloc(num * size); + if (ptr) { + memset(ptr, 0, num * size); } - memset(ptr, 0, num * size); return ptr; } diff --git a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/free.c b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/free.c index afce966cc..996929a26 100644 --- a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/free.c +++ b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/free.c @@ -1,7 +1,97 @@ #include +#include #include +#include "_mem.h" void free(void* ptr) { - _ksys_free(ptr); -} \ No newline at end of file + // Handle NULL pointer. + if (ptr == NULL) + return; + + // Get a pointer to the mem_node header from the user data pointer. + struct mem_node* node = GET_MEM_NODE_HEADER(ptr); + + // Mark the memory node as free. + node->free = node->size; + + if (__last_biggest_mem_node == node) { + if (node->last) { + __last_biggest_mem_node = node->last; // anyway node will be merged with next + // and last and last will have size = last + node + next(if its free too) + } + } + + // Merge with the next node if possible. + if (node->next != NULL) + __mem_MERGE_MEM_NODES(node, node->next); + + // Merge with the previous node if possible. + if (node->last != NULL) + node = __mem_MERGE_MEM_NODES(node->last, node); + + if (node) { + + // If the current node is not adjacent to either the next or previous node, + // it might be a separate block that can be freed. + if (MEM_NODE_IS_FREE(node) // check it because node maybe was merged with last + && (node->last == NULL || !MEM_NODES_ARE_IN_ONE_BLOCK(node, node->next)) + && (node->next == NULL || !MEM_NODES_ARE_IN_ONE_BLOCK(node->last, node))) { + + // Get a pointer to the mem_block header from the mem_node header. + struct mem_block* block = (struct mem_block*)(((char*)node) - sizeof(struct mem_block)); + + // Check if the block size matches the node size. + if (block->size == node->size + sizeof(struct mem_block) + sizeof(struct mem_node)) { + + // Update the linked list pointers to remove the current node. + if (node->last != NULL) + node->last->next = node->next; + + if (node->next != NULL) + node->next->last = node->last; + + // Update the head of the linked list if necessary. + if (__mem_node == node) { + if (node->last != NULL) { + __mem_node = node->last; + } else if (node->next != NULL) { + __mem_node = node->next; + } else { + __mem_node = NULL; + } + } + + struct mem_node* a = node->next; + struct mem_node* b = node->last; + + if (!a && !b) { + __last_biggest_mem_node = NULL; + } else if (a && !b) { + __last_biggest_mem_node = a; + } else if (!a && b) { + __last_biggest_mem_node = b; + } else if (a && b) { + __last_biggest_mem_node = (a->free > b->free) ? a : b; + } + + if (__last_biggest_mem_node == node) { + if (node->next && !(node->last)) { + __last_biggest_mem_node = node->next; + } else if (node->last && !(node->next)) { + __last_biggest_mem_node = node->last; + } else if (node->next && node->last) { + if (node->last->free > node->next->free) { + __last_biggest_mem_node = node->last; + } else { + __last_biggest_mem_node = node->next; + } + } + } + + // Free the memory block using the ksys_free function. + _ksys_free(block); + } + } + } +} diff --git a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/malloc.c b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/malloc.c index c96d5a521..baf7daf2a 100644 --- a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/malloc.c +++ b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/malloc.c @@ -1,7 +1,128 @@ +#include #include +#include #include +#include +#include "_mem.h" + +static struct mem_node* __new_mem_node_from_exist(struct mem_node* current_node, size_t size, bool* from_empty_node) +{ + struct mem_node* new_node = NULL; + // Check if the current node has enough free space for the requested size. + if (size + sizeof(struct mem_node) <= current_node->free) { + + *from_empty_node = MEM_NODE_IS_FREE(current_node); + if (*from_empty_node) { + new_node = current_node; + } else { + // Calculate the used memory in current node + const size_t s = GET_MEM_NODE_USED_MEM(current_node); + + // Create a new memory node after the current node's used space. + new_node = (struct mem_node*)(GET_MEM_NODE_PTR(current_node) + s); + + // Update current node's size + current_node->size = s; + + // Set the size of the new node. + // for new node give all free space in current node + new_node->size = current_node->free - sizeof(struct mem_node); + + // Mark current node as used. + current_node->free = 0; + } + } + return new_node; +} void* malloc(size_t size) { - return _ksys_alloc(size); -} \ No newline at end of file + char b[32]; + + // Handle zero-size allocation. + if (size == 0) { + return NULL; + } + + // Align the size to 8 bytes. + size = __mem_default_align(size); + + struct mem_node* current_node = __mem_node; + struct mem_node* new_node = NULL; // Pointer to the new node that will be created. + bool from_empty_node = false; + + if (__last_biggest_mem_node != NULL) + new_node = __new_mem_node_from_exist(__last_biggest_mem_node, size, &from_empty_node); // try find free space in last created node + + // if cant find in __last_biggest_mem_node + if (new_node == NULL) { + // Iterate through the linked list of memory nodes. + while (current_node != NULL) { + new_node = __new_mem_node_from_exist(current_node, size, &from_empty_node); + if (new_node) + break; // Found a suitable node, exit the loop. + + current_node = current_node->next; // Move to the next node in the list. + } + } + + // If no suitable node was found in the existing list: + if (new_node == NULL) { + // Calculate the size of the new block, including the mem_block header, mem_node header and alignment. + const size_t s = __mem_align(size + sizeof(struct mem_block) + sizeof(struct mem_node), ALLOC_BLOCK_SIZE); + + // Allocate a new block of memory using the ksys_alloc function (presumably a kernel-level allocation function). + struct mem_block* block = (struct mem_block*)_ksys_alloc(s); + + // Check if the allocation was successful. + if (block == NULL) { + __errno = ENOMEM; // Set the error number to indicate memory allocation failure. + return NULL; // Return NULL to indicate allocation failure. + } + + block->size = s; + + // Create a new memory node after the mem_block header. + new_node = (struct mem_node*)(((char*)block) + sizeof(struct mem_block)); + + // Set the size of the new node. + new_node->size = s - sizeof(struct mem_block) - sizeof(struct mem_node); + } + + // Set the free space in the new node. + new_node->free = new_node->size - size; + + if (!from_empty_node) { + // Set the last pointer of the new node to the current node. + new_node->last = current_node; + + // Link the new node into the linked list. + if (current_node != NULL) { + // Set the next pointer of the current node to the new node. + new_node->next = current_node->next; + + // Update the last pointer of the next node, if it exists. + if (current_node->next != NULL) { + current_node->next->last = new_node; + } + current_node->next = new_node; + } else { + // If the current node is NULL, the new node is the first node in the list. + new_node->next = NULL; + } + } + + // If the linked list was empty, set the head to the new node. + if (__mem_node == NULL) { + __mem_node = new_node; + } + + if (__last_biggest_mem_node == NULL || new_node->free > __last_biggest_mem_node->free) { + __last_biggest_mem_node = new_node; + } + + // Return a pointer to the user data area of the new node. + return GET_MEM_NODE_PTR(new_node); +} + +#undef __mem_align diff --git a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/realloc.c b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/realloc.c index 6278a9199..18c747e22 100644 --- a/programs/develop/ktcc/trunk/libc.obj/source/stdlib/realloc.c +++ b/programs/develop/ktcc/trunk/libc.obj/source/stdlib/realloc.c @@ -1,7 +1,76 @@ #include +#include #include +#include "_mem.h" + +// realloc mem. using if other ways not working +static void* fail_realloc(void* ptr, size_t newsize) +{ + // Allocate a new block of memory with the new size. + void* new_ptr = malloc(newsize); + + // If both the old pointer and the new pointer are not NULL: + if (ptr != NULL && new_ptr != NULL) { + // Copy the data from the old block to the new block. + memcpy(new_ptr, ptr, min(newsize, GET_MEM_NODE_USED_MEM(GET_MEM_NODE_HEADER(ptr)))); + } + + if (ptr) { + free(ptr); // Free the old block. + } + + return new_ptr; +} void* realloc(void* ptr, size_t newsize) { - return _ksys_realloc(ptr, newsize); -} \ No newline at end of file + + void* new_ptr = NULL; + struct mem_node* node = NULL; + + if (ptr && newsize) { + + // Get a pointer to the mem_node header from the user data pointer. + node = GET_MEM_NODE_HEADER(ptr); + + newsize = __mem_default_align(newsize); + + if (node->size >= newsize) { // current node have enough mem + // it work always if newsize is smaller + // Update the free space in the current node. + node->free = node->size - newsize; + // Return the original pointer. + new_ptr = ptr; + } else if (node->last && MEM_NODE_IS_FREE(node->last) && node->size + node->last->size >= newsize) { + // So what happens here is that the node merges with the last node if their volume is sufficient. + // And a reallock is called in the hopes that the first condition will be met. + // if merge failed realloc anyway return pointer, but it will got from `fail_realloc` + struct mem_node* l = node->last; + + l = __mem_MERGE_MEM_NODES(l, node); + if (l) { + memmove(GET_MEM_NODE_PTR(l), ptr, GET_MEM_NODE_USED_MEM(node)); + new_ptr = realloc(GET_MEM_NODE_PTR(l), newsize); + } + } + } + + if (new_ptr == NULL) { + + // Allocate a new block of memory with the new size. + new_ptr = malloc(newsize); + + // If both the old pointer and the new pointer are not NULL: + if (ptr != NULL && new_ptr != NULL) { + // Copy the data from the old block to the new block. + memcpy(new_ptr, ptr, min(newsize, GET_MEM_NODE_USED_MEM(GET_MEM_NODE_HEADER(ptr)))); + } + + if (ptr) { + free(ptr); // Free the old block. + } + } + + // Return the new pointer. + return new_ptr; +}