diff --git a/README.md b/README.md index 3e0a9f6..79413e9 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,14 @@ SecureLang Library for C -THIS IS A TEST EDIT. - ## Features -* A subset of traditional libc features (e.g. `printf`) +* A subset of traditional libc features (e.g. provides `printf`) * Some support for high level language features like garbage collection -* Simple licensing, with only small exceptions the code was written by me +* Simple copyright, with only small exceptions the code was written by me ## Limitations -* Doesn't support all legacy functions +* Doesn't support all legacy functions (e.g. doesn't provide `scanf`) * Incomplete support for error handling, multithreading, signals etc. * Currently used for testing in-house systems so not readily buildable/testable on commodity platforms diff --git a/ctype.c b/ctype.c new file mode 100644 index 0000000..4825925 --- /dev/null +++ b/ctype.c @@ -0,0 +1,113 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include + +int isspace(int ch) { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); +} + +int isdigit(int ch) { + return (ch >= '0') && (ch <= '9'); +} + +int isxdigit(int ch) { + return isdigit(ch) || ((ch >= 'a') && (ch <= 'f')) || ((ch >= 'A') && (ch <= 'F')); +} + +int isalpha(int ch) { + return ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')); +} + +int isalnum(int ch) { + return isalpha(ch) || isdigit(ch); +} + +int isprint(int ch) { + return isalnum(ch) || isspace(ch) || ispunct(ch); +} + +int ispunct(int ch) { + switch (ch) { + case ',': + case '<': + case '.': + case '>': + case '/': + case '?': + case ';': + case ':': + case '\'': + case '\"': + case '[': + case ']': + case '{': + case '}': + case '`': + case '~': + case '@': + case '#': + case '$': + case '%': + case '^': + case '&': + case '*': + case '(': + case ')': + case '-': + case '_': + case '=': + case '+': + return 1; + default: + return 0; + } +} + +int isupper(int ch) { + if (ch >= 'A' && ch <= 'Z') { + return 1; + } else { + return 0; + } +} + +int islower(int ch) { + if (ch >= 'a' && ch <= 'a') { + return 1; + } else { + return 0; + } +} + +int toupper(int ch) { + if (ch >= 'a' && ch <= 'z') { + return ((int)'A') + (ch - ((int)'a')); + } else { + return ch; + } +} + +int tolower(int ch) { + if (ch >= 'A' && ch <= 'Z') { + return ((int)'a') + (ch - ((int)'A')); + } else { + return ch; + } +} + +int isascii(int ch) { + int ascii = ch & 0x7F; + if (ch == ascii) { + return 1; + } else { + return 0; + } +} diff --git a/elf.h b/elf.h new file mode 100644 index 0000000..3a067e3 --- /dev/null +++ b/elf.h @@ -0,0 +1,370 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. +#ifndef _LIBC_ELF +#define _LIBC_ELF + +// For the purposes of the audit program this is NEW CODE (but was written by me a while ago) + +/* NOTE: The specification used as a reference is the draft at https://refspecs.linuxfoundation.org/elf/gabi4+/contents.html + * This header includes both 32-bit and 64-bit types. + * For x86-64 specific information I used as a reference https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf + */ + +/* +#include + +typedef uint32_t Elf32_Addr; +typedef uint32_t Elf32_Off; +typedef uint32_t Elf32_Word; +typedef int32_t Elf32_Sword; +typedef uint16_t Elf32_Half; + +typedef uint64_t Elf64_Addr; +typedef uint64_t Elf64_Off; +typedef uint32_t Elf64_Word; +typedef int32_t Elf64_Sword; +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; +typedef uint16_t Elf64_Half; +*/ +typedef unsigned int Elf32_Addr; +typedef unsigned int Elf32_Off; +typedef unsigned int Elf32_Word; +typedef int Elf32_Sword; +typedef unsigned short Elf32_Half; + +typedef unsigned long long Elf64_Addr; +typedef unsigned long long Elf64_Off; +typedef unsigned int Elf64_Word; +typedef int Elf64_Sword; +typedef unsigned long long Elf64_Xword; +typedef long long Elf64_Sxword; +typedef unsigned short Elf64_Half; + +#define EV_NONE 0 +#define EV_CURRENT 1 + +#define EI_MAG0 0 +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 +#define EI_ABIVERSION 8 +#define EI_PAD 9 +#define EI_NIDENT 16 + +#define ELFMAG0 0x7F +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' + +#define ELFCLASSNONE 0 +#define ELFCLASS32 1 +#define ELFCLASS64 2 + +#define ELFDATANONE 0 +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + + +#define ET_NONE 0 +#define ET_REL 1 +#define ET_EXEC 2 +#define ET_DYN 3 +#define ET_CORE 4 +#define ET_LOOS 0xFE00 +#define ET_HIOS 0xFEFF +#define ET_LOPROC 0xFF00 +#define ET_HIPROC 0xFFFF + +#define EM_X86_64 62 + +#define ELFOSABI_NONE 0 +/* NOTE: SYSV is apparently the same as "NONE". */ +#define ELFOSABI_SYSV 0 +#define ELFOSABI_NETBSD 2 +#define ELFOSABI_LINUX 3 +#define ELFOSABI_SOLARIS 6 +#define ELFOSABI_FREEBSD 9 +#define ELFOSABI_OPENBSD 12 + +#define SHT_NULL 0 +#define SHT_PROGBITS 1 +#define SHT_SYMTAB 2 +#define SHT_STRTAB 3 +#define SHT_RELA 4 +#define SHT_HASH 5 +#define SHT_DYNAMIC 6 +#define SHT_NOTE 7 +#define SHT_NOBITS 8 +#define SHT_REL 9 +#define SHT_SHLIB 10 +#define SHT_DYNSYM 11 +#define SHT_INIT_ARRAY 14 +#define SHT_FINI_ARRAY 15 +#define SHT_PREINIT_ARRAY 16 +#define SHT_GROUP 17 +#define SHT_SYMTAB_SHNDX 18 +#define SHT_LOOS 0x60000000 +#define SHT_HIOS 0x6fffffff +#define SHT_LOPROC 0x70000000 +#define SHT_HIPROC 0x7fffffff +#define SHT_LOUSER 0x80000000 +#define SHT_HIUSER 0xffffffff + +#define SHN_UNDEF 0 +#define SHN_LORESERVE 0xff00 +#define SHN_LOPROC 0xff00 +#define SHN_HIPROC 0xff1f +#define SHN_LOOS 0xff20 +#define SHN_HIOS 0xff3f +#define SHN_ABS 0xfff1 +#define SHN_COMMON 0xfff2 +#define SHN_XINDEX 0xffff +#define SHN_HIRESERVE 0xffff + +#define SHF_WRITE 0x1 +#define SHF_ALLOC 0x2 +#define SHF_EXECINSTR 0x4 +#define SHF_MERGE 0x10 +#define SHF_STRINGS 0x20 +#define SHF_INFO_LINK 0x40 +#define SHF_LINK_ORDER 0x80 +#define SHF_OS_NONCONFORMING 0x100 +#define SHF_GROUP 0x200 +#define SHF_TLS 0x400 +#define SHF_MASKOS 0x0ff00000 +#define SHF_MASKPROC 0xf0000000 + + +#define STN_UNDEF 0 + +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_SHLIB 5 +#define PT_PHDR 6 +#define PT_TLS 7 +#define PT_LOOS 0x60000000 +#define PT_HIOS 0x6fffffff +#define PT_LOPROC 0x70000000 +#define PT_HIPROC 0x7fffffff + +#define STT_NOTYPE 0 +#define STT_OBJECT 1 +#define STT_FUNC 2 +#define STT_SECTION 3 +#define STT_FILE 4 +#define STT_COMMON 5 +#define STT_TLS 6 +#define STT_LOOS 10 +#define STT_HIOS 12 +#define STT_LOPROC 13 +#define STT_HIPROC 15 + +#define PF_X 0x1 +#define PF_W 0x2 +#define PF_R 0x4 +#define PF_MASKOS 0x0ff00000 +#define PF_MASKPROC 0xf0000000 + + +/* These macros are defined in the draft specification: */ +/* NOTE: I think at least my _INFO is broken here. */ +#define ELF32_R_SYM(i) ((i)>>8) +#define ELF32_R_TYPE(i) ((unsigned char)(i)) +#define ELF32_R_INFO(s,t) (((s)<<8)+(unsigned char)(t)) + +#define ELF64_R_SYM(i) ((i)>>32) +#define ELF64_R_TYPE(i) ((i)&0xffffffffL) +#define ELF64_R_INFO(s,t) (((s)<<32)+((t)&0xffffffffL)) + +#define ELF32_ST_BIND(i) ((i)>>4) +#define ELF32_ST_TYPE(i) ((i)&0xf) +#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf)) + +#define ELF64_ST_BIND(i) ((i)>>4) +#define ELF64_ST_TYPE(i) ((i)&0xf) +#define ELF64_ST_INFO(b,t) (((b)<<4)+((t)&0xf)) + +#define ELF32_ST_VISIBILITY(o) ((o)&0x3) +#define ELF64_ST_VISIBILITY(o) ((o)&0x3) + +/* These (relocation types?) are specific to x86-64. I still find it weird that ELF works that way (why not just encode the expressions so it'd be portable?) */ +#define R_X86_64_NONE 0 //none none +#define R_X86_64_64 1 //word64 S + A +#define R_X86_64_PC32 2 //word32 S + A - P +#define R_X86_64_GOT32 3 //word32 G + A +#define R_X86_64_PLT32 4 //word32 L + A - P +#define R_X86_64_COPY 5 //none none +#define R_X86_64_GLOB_DAT 6 //word64 S +#define R_X86_64_JUMP_SLOT 7 //word64 S +#define R_X86_64_RELATIVE 8 //word64 B + A +#define R_X86_64_GOTPCREL 9 //word32 G + GOT + A - P +#define R_X86_64_32 10 //word32 S + A +#define R_X86_64_32S 11 //word32 S + A +#define R_X86_64_16 12 //word16 S + A +#define R_X86_64_PC16 13 //word16 S + A - P +#define R_X86_64_8 14 //word8 S + A +#define R_X86_64_PC8 15 //word8 S + A - P +#define R_X86_64_DPTMOD64 16 //word64 +#define R_X86_64_DTPOFF64 17 //word64 +#define R_X86_64_TPOFF64 18 //word64 +#define R_X86_64_TLSGD 19 //word32 +#define R_X86_64_TLSLD 20 //word32 +#define R_X86_64_DTPOFF32 21 //word32 +#define R_X86_64_GOTTPOFF 22 //word32 +#define R_X86_64_TPOFF32 23 //word32 +#define R_X86_64_PC64 24 //word64 S + A - P +#define R_X86_64_GOTOFF64 25 //word64 S + A - GOT +#define R_X86_64_GOTPC32 26 //word32 GOT + A - P + +typedef struct Elf32_Ehdr_struct Elf32_Ehdr; +typedef struct Elf64_Ehdr_struct Elf64_Ehdr; +typedef struct Elf32_Shdr_struct Elf32_Shdr; +typedef struct Elf64_Shdr_struct Elf64_Shdr; +typedef struct Elf32_Sym_struct Elf32_Sym; +typedef struct Elf64_Sym_struct Elf64_Sym; +typedef struct Elf32_Rel_struct Elf32_Rel; +typedef struct Elf64_Rel_struct Elf64_Rel; +typedef struct Elf32_Rela_struct Elf32_Rela; +typedef struct Elf64_Rela_struct Elf64_Rela; +typedef struct Elf32_Phdr_struct Elf32_Phdr; +typedef struct Elf64_Phdr_struct Elf64_Phdr; + +struct Elf32_Ehdr_struct { + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +}; + +struct Elf64_Ehdr_struct { + unsigned char e_ident[EI_NIDENT]; + Elf64_Half e_type; + Elf64_Half e_machine; + Elf64_Word e_version; + Elf64_Addr e_entry; + Elf64_Off e_phoff; + Elf64_Off e_shoff; + Elf64_Word e_flags; + Elf64_Half e_ehsize; + Elf64_Half e_phentsize; + Elf64_Half e_phnum; + Elf64_Half e_shentsize; + Elf64_Half e_shnum; + Elf64_Half e_shstrndx; +}; + +struct Elf32_Shdr_struct { + Elf32_Word sh_name; + Elf32_Word sh_type; + Elf32_Word sh_flags; + Elf32_Addr sh_addr; + Elf32_Off sh_offset; + Elf32_Word sh_size; + Elf32_Word sh_link; + Elf32_Word sh_info; + Elf32_Word sh_addralign; + Elf32_Word sh_entsize; +}; + +struct Elf64_Shdr_struct { + Elf64_Word sh_name; + Elf64_Word sh_type; + Elf64_Xword sh_flags; + Elf64_Addr sh_addr; + Elf64_Off sh_offset; + Elf64_Xword sh_size; + Elf64_Word sh_link; + Elf64_Word sh_info; + Elf64_Xword sh_addralign; + Elf64_Xword sh_entsize; +}; + +struct Elf32_Sym_struct { + Elf32_Word st_name; + Elf32_Addr st_value; + Elf32_Word st_size; + unsigned char st_info; + unsigned char st_other; + Elf32_Half st_shndx; +}; + +struct Elf64_Sym_struct { + Elf64_Word st_name; + unsigned char st_info; + unsigned char st_other; + Elf64_Half st_shndx; + Elf64_Addr st_value; + Elf64_Xword st_size; +}; + +struct Elf32_Rel_struct { + Elf32_Addr r_offset; + Elf32_Word r_info; +}; + +struct Elf32_Rela_struct { + Elf32_Addr r_offset; + Elf32_Word r_info; + Elf32_Sword r_addend; +}; + +struct Elf64_Rel_struct { + Elf64_Addr r_offset; + Elf64_Xword r_info; +}; + +struct Elf64_Rela_struct { + Elf64_Addr r_offset; + Elf64_Xword r_info; + Elf64_Sxword r_addend; +}; + +struct Elf32_Phdr_struct { + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +}; + +struct Elf64_Phdr_struct { + Elf64_Word p_type; + Elf64_Word p_flags; + Elf64_Off p_offset; + Elf64_Addr p_vaddr; + Elf64_Addr p_paddr; + Elf64_Xword p_filesz; + Elf64_Xword p_memsz; + Elf64_Xword p_align; +}; + +/* From ifndef at top of file: */ +#endif diff --git a/elmalloc.h b/elmalloc.h new file mode 100644 index 0000000..6f00c13 --- /dev/null +++ b/elmalloc.h @@ -0,0 +1,1001 @@ +/* A Simple memory manager. PUBLIC DOMAIN, NO COPYRIGHT, NO WARRANTY. + * Peace, love & Buddhism for all. -Zak Fenton, MMXX. + * (2025 update) This is now a real malloc, you may also use the license in my libc. -Zak. + * FEATURES: + * Minimal, portable and embeddable implementations of "malloc", "free", "calloc" and "realloc" functions. + * Should be threadsafe if a locking function is provided before initialisation (the init function MUST be called + * before attempting to allocate any memory! But only a "bigchunk" function is required for the initialisation to + * work.) Fine-grained locking might be added in the future after trying some multithreaded tests. + * Almost completely standalone, no standard library functions are required and the only headers required are "stdint" and + * "stdbool" (which are both very easy to implement in the rare cases where standard versions don't work). + * Compaction is provided and works separately to the regular malloc/free control flows. The intention is for malloc/free + * to work fairly quickly and not bother sorting or merging lists, then an idle process or similar can regularly scan and + * compact the heap. (Returning unused memory to the environment hasn't been implemented yet, but should basically consist + * of a quick check-and-trim process after running the compaction function.) + * Provides a fairly robust way of getting free/used statistics (this is designed for future extension so only minimal + * changes are required to support new or system-specific metrics). + * Works in both 32-bit and 64-bit builds (should also adapt to smaller or larger word sizes, should use appropriate types + * and sizeof instead of making assumptions but rounding values etc. may need to be adapted for more obscure cases. + * Will attempt to grow the heap if it runs out of memory (and should fail gracefully if it can't). + * Will attempt to release whatever memory it can spare if you call elmm_friendlycompact. + * NOT YET IMPLEMENTED: + * Only tested under very simple and fake conditions (see testmain.c), TODO: Proper stress tests (might be easy to hook up + * something like Lua which can take a memory manager callback and then just run some large Lua program, otherwise some + * kind of benchmark program might be a good basis for a test). + * Some debugging code has been left in for now (search "printf" to remove the debug lines, but some may be useful to + * indicate TODOs or errors etc. A separate "warning" callback might be added in the future for debugging memory issues.) + * Not very fast and may not have enough safeguards for some use cases, but hopefully will be enough for a starting point. + * Locking should probably just be determined with #defines, the function pointer style MAY be helpful for debugging though. + */ +#ifndef ELMM_H +#define ELMM_H + +#include +#include + +#define ELMM_VERSION 0x0100 +/**/ +#define ELMM_STATIC static +#define ELMM_INLINE static inline + +/* Internally, a smallchunk header is used for each allocated and unallocated piece (besides the header). */ +#define ELMM_SMALLCHUNK_FREE 1234 +#define ELMM_SMALLCHUNK_ALLOCATED 4321 +typedef struct elmm_smallchunk elmm_smallchunk_t; +struct elmm_smallchunk { + uintptr_t check1; /* Either ELMM_SMALLCHUNK_FREE or ELMM_SMALLCHUNK_ALLOCATED.*/ + uintptr_t size; + elmm_smallchunk_t* next; + uintptr_t check2; /* Either ELMM_SMALLCHUNK_FREE or ELMM_SMALLCHUNK_ALLOCATED.*/ +}; + +/* Internal elements of a bigchunk*/ +typedef struct elmm_bigchunk_internals elmm_bigchunk_internals_t; +typedef struct elmm_bigchunk elmm_bigchunk_t; +struct elmm_bigchunk_internals { + uintptr_t allocatedTop; + elmm_bigchunk_t* nextChunk; + elmm_bigchunk_t* prevChunk; + elmm_smallchunk_t* firstFree; + elmm_smallchunk_t* firstAllocated; +}; + +struct elmm_bigchunk { + void* startAddress; + uintptr_t headerSize; + uintptr_t totalSize; + uintptr_t maximumSize; + const char* errorString; + elmm_bigchunk_internals_t internals; +}; + +/* The bigchunk allocator takes a size (or zero) and an "old" bigchunk address (or NULL). + * If the oldchunk is NULL and a chunk with the given size can be allocated, the function should + * allocate a chunk (of at least that size) and fill in a elmm_bigchunk_t structure at the start + * of the chunk (the structure's address must match the start address). In this case, a NULL + * should be returned if the chunk can't be allocated. + * + * Unused fields of the bigchunk structure + * should be cleared to zero, but the rest of the memory doesn't necessarily need to be cleared + * (it probably should be anyway for security reasons, but the allocator will clear it itself + * anyway). The total size of a bigchunk is expected to always include the header (the header is + * part of the chunk, and only needs to be separated by it's own headerSize field). + * + * If the oldchunk is non-null and the size is zero, the function should deallocate that chunk + * entirely (or set it to it's minimum size, depending on implementation), and return NULL (if + * there is an error, it should return the structure and set it's error string). + * + * Before returning a bigchunk, the function can set the maximumSize field to a value other + * than zero, in which case the higher-level allocater may call the function again with + * the same chunk and a different size value to attempt to resize the chunk. The allocator + * will generally try to use memory from an existing bigchunk before attempting to get a new + * one, which means an implementation of the bigchunk function can just work with a single + * resizable bigchunk (as in the sbrk implementation). + * + * Between calls to the bigchunk function, the allocator may change, resize or reconstruct the + * bigchunk header, but subsequent calls will always use the same header address and sensible values + * (it must always be located at the very start of the chunk and the size fields should be unchanged + * between calls, except for the headerSize which may be increased by the caller to account for it's own + * chunk headers). + */ +typedef elmm_bigchunk_t* (*elmm_bigchunk_function_t)(uintptr_t size, elmm_bigchunk_t* oldchunk, void* udata); + + +/* Internally, the memory manager works by allocating "big chunks" from somewhere else and then allocating the + * "small chunks" requested by the user within that memory. On Unix-like systems and on embedded systems using + * only one heap at a time, it may be more convenient to allocate from a single memory bank which is just + * increased or decreased in size as required. The system call usually used to achieve this is called "sbrk", + * so a wrapper around our usual "big chunk" allocation scheme which can be plugged directly into sbrk (by + * ignoring the udata argument). + */ + +/* The error code returned by sbrk should be "NULL minus one", but some casts may be required to get the value + * right without compiler errors. + */ +#define ELMM_SBRK_ERROR ((void*)(((char*)(((void*)NULL))) - 1)) +typedef struct elmm_sbrk_data elmm_sbrk_data_t; +typedef void* (*elmm_sbrk_function_t)(intptr_t incr, void* udata); + +struct elmm_sbrk_data { + elmm_sbrk_function_t func; + void* udata; + /* This can either be set to the maximum heap size, or to zero (which will imply a default of 1GB). + * This should generally be set as high as possible, but can be used as an easy way to set an effective + * limit on memory usage. + */ + uintptr_t max; + /* This should initially be set to NULL, but is otherwise used to store the single resizable chunk + * which represents the resizable heap. + */ + elmm_bigchunk_t* onlyChunk; +}; + +/* This function can be used as the bigchunk function if you need it to work on top of a "sbrk"-like API. + * A elmm_sbrk_data_t should be given as the userdata, and it holds a pointer to an sbrk-like function. + */ +ELMM_STATIC elmm_bigchunk_t* elmm_bigchunk_sbrk(uintptr_t size, elmm_bigchunk_t* oldchunk, void* udata) { + elmm_sbrk_data_t* data = (elmm_sbrk_data_t*)udata; + elmm_sbrk_function_t sbrkf = data->func; + //printf("elmm_bigchunk_sbrk(%d,%d,%d)\n", size, oldchunk, udata); + if (size > 0 && oldchunk == NULL) { + if (data->onlyChunk != NULL) { + return NULL; + } + data->onlyChunk = (elmm_bigchunk_t*)sbrkf(size, data->udata); + if (data->onlyChunk == ELMM_SBRK_ERROR) { + return NULL; + } + data->onlyChunk->startAddress = (void*)(data->onlyChunk); // Header MUST be at the start address. + data->onlyChunk->headerSize = sizeof(elmm_bigchunk_t); + data->onlyChunk->totalSize = size; + data->onlyChunk->maximumSize = (data->max == 0) ? 1024 * 1024 * 1024 : data->max; + return data->onlyChunk; + } else if (size > 0 && oldchunk != NULL) { + //printf("Attempting to resize...\n"); + if (data->onlyChunk != oldchunk) { + return NULL; + } + intptr_t diff = size - data->onlyChunk->totalSize; + void* sbrkresult = sbrkf(diff, data->udata); + // TODO: Should probably check that the sbrk function returned a pointer exactly where we expected + if (sbrkresult == ELMM_SBRK_ERROR) { + return NULL; + } + data->onlyChunk->totalSize = size; + return data->onlyChunk; + } else if (size == 0 && oldchunk != NULL) { + //printf("Attempting to deallocate...\n"); + if (data->onlyChunk != oldchunk) { + return NULL; + } + //TODO... + return NULL; + } else { + //printf("TODO!!!\n"); + return NULL; + } +} + +/* A locking function can be provided to the memory manager so that it can be used in multithreaded + * environments. If provided, the locking function should return true on success or false on error, + * and needs to obey a few pretty universal commands that should be easy to implement for any + * multithreaded environment. Use of a lock function ensures that calls to the bigchunk function + * would not be made simultaneously on different threads, but other locks may be used internally + * to maintain structures. + * + * The locking function is expected to either be very straightforward (using the variable address + * to do some platform-specific atomic check-and-set operation) or to allocate/delete it's own + * lock structures (storing the address of the corresponding structure in each lockVariable). + */ +#define ELMM_LOCK_NEW 1 +#define ELMM_LOCK_DELETE 2 +#define ELMM_LOCK_TRYLOCK 3 +#define ELMM_LOCK_WAITLOCK 4 +#define ELMM_LOCK_UNLOCK 5 +#define ELMM_LOCK_GETTHREAD 6 + +typedef bool (*elmm_lock_function_t)(int command, void** lockVarible, void* udata); + +/* This function is used internally as the lock function if no other one is provided. + * (The current implementation doesn't perform any checks, it just sets the lock to + * NULL and reports success. Future versions might at least attempt to make sure + * the commands are issued in the correct order.) + */ +ELMM_STATIC bool elmm_nolock(int command, void** lockVariable, void* udata) { + *lockVariable = NULL; + return true; +} + +typedef struct elmm elmm_t; +struct elmm { + /* Some configuration options come first. These can be set to zero before initialisation, + * but shouldn't be modified by the caller after initialising the memory mananger. + */ + uintptr_t bigchunkMinimum; + uintptr_t bigchunkGranularity; + + /* Callback functions for the heap management and locking come next. A bigchunk function + * is mandatory, but the lock function is optional (if it's NULL at initialisation, + * it will be set to a locking function which has no effect or only works in single-threaded + * implementations). + */ + elmm_bigchunk_function_t bigchunkFunction; + void* bigchunkData; + elmm_lock_function_t lockFunction; + void* lockData; + + /* Internal pointers are stored last. + */ + void* mainLock; + elmm_bigchunk_t* firstChunk; + + bool initialised; +}; + +#define ELMM_STAT_VERSION 0 +#define ELMM_STAT_ALLOCATED 1 +#define ELMM_STAT_FREE 2 +#define ELMM_STAT_TOTAL 3 +#define ELMM_STAT_OVERHEADS 4 +#define ELMM_STATTOP 5 + +/* Called internally by elmm_innerstat to add up the "size" elements of each smallchunk in the given list. */ +ELMM_INLINE uintptr_t elmm_innerstatpart(elmm_t* mm, elmm_smallchunk_t* listHead) { + uintptr_t result = 0; + + while (listHead != NULL) { + result += listHead->size; + listHead = listHead->next; + } + + return result; +} + +/* This function is only useful for getting statistics WHILE THE MEMORY MANAGER IS LOCKED OR NOT BEING USED! + * The elmm_stat function is designed for more regular use (it will lock the memory manager and collect + * all of the statistics consistently). + */ +ELMM_STATIC uintptr_t elmm_innerstat(elmm_t* mm, uintptr_t statnum) { + elmm_bigchunk_t* chunk = mm->firstChunk; + uintptr_t result = 0; + switch (statnum) { + case ELMM_STAT_VERSION: + return ELMM_VERSION; + case ELMM_STAT_ALLOCATED: + while (chunk != NULL) { + result += elmm_innerstatpart(mm, chunk->internals.firstAllocated); + chunk = chunk->internals.nextChunk; + } + break; + case ELMM_STAT_FREE: + while (chunk != NULL) { + result += elmm_innerstatpart(mm, chunk->internals.firstFree); + chunk = chunk->internals.nextChunk; + } + break; + case ELMM_STAT_TOTAL: + while (chunk != NULL) { + result += chunk->totalSize; + chunk = chunk->internals.nextChunk; + } + break; + case ELMM_STAT_OVERHEADS: + return elmm_innerstat(mm, ELMM_STAT_TOTAL) - (elmm_innerstat(mm, ELMM_STAT_ALLOCATED) + elmm_innerstat(mm, ELMM_STAT_FREE)); + default: + return 0 - 1; + } + return result; +} + +ELMM_INLINE elmm_smallchunk_t* elmm_getheader(elmm_t* mm, void* allocatedMemory) { + if (allocatedMemory == NULL) { + return NULL; + } else { + return ((elmm_smallchunk_t*)allocatedMemory) - 1; + } +} + +ELMM_INLINE uintptr_t elmm_sizeof(elmm_t* mm, void* allocatedMemory) { + if (allocatedMemory == NULL) { + return 0; + } else { + return elmm_getheader(mm, allocatedMemory)->size; + } +} + +ELMM_STATIC elmm_bigchunk_t* elmm_allocinner(elmm_t* mm, uintptr_t minsize) { + /* Header size plus the size of some number of smallchunk structures is added to the minimum size + * to account for any overheads which might be required to allocate (at least) a structure of + * the given size size. + */ + minsize += sizeof(elmm_bigchunk_t) + (sizeof(elmm_smallchunk_t) * 10); // This should more than cover the minimum overheads + if (minsize < mm->bigchunkMinimum) { + minsize = mm->bigchunkMinimum; + } + while ((minsize % mm->bigchunkGranularity) != 0) { + minsize++; + } + elmm_bigchunk_function_t bigchf = mm->bigchunkFunction; + elmm_bigchunk_t* result = bigchf(minsize, NULL, mm->bigchunkData); + if (result == NULL) { + printf("BIGCHUNK FUNCTION GAVE US NULL!\n"); + return NULL; + } + if (result->headerSize > sizeof(elmm_bigchunk_t)) { + return NULL; + } + if (result->startAddress != (void*)result) { + return NULL; + } + result->internals.nextChunk = NULL; + result->internals.prevChunk = NULL; + /* When allocated by the bigchunk function, the header size only needs to represent the size of the header + * known to (and cleared or filled in by) the bigchunk function. It's extended here to ensure that it fits + * all of our interna fields (which could be ignored by the bigchunk implementation) and also aligns to a + * reasonable boundary for the purposes of allocating within it. + */ + result->headerSize = sizeof(elmm_bigchunk_t); + while ((result->headerSize % 16) != 0) { + result->headerSize++; + } + uint8_t* bytes = (uint8_t*)(result->startAddress); + result->internals.firstAllocated = NULL; + result->internals.firstFree = (elmm_smallchunk_t*)(bytes + result->headerSize); + result->internals.firstFree->check1 = ELMM_SMALLCHUNK_FREE; + result->internals.firstFree->size = result->totalSize - (result->headerSize + sizeof(elmm_smallchunk_t)); + result->internals.firstFree->next = NULL; + result->internals.firstFree->check2 = ELMM_SMALLCHUNK_FREE; + + return result; +} + +bool elmm_dolock(elmm_t* mm, int command, void** lockVariable, void* udata) { + elmm_lock_function_t lockf = mm->lockFunction; + return lockf(command, lockVariable, udata); +} + +/* Must be called before any other functions to initialise a memory manager. + * The memory manager structure itself is provided by the caller, and at a minimum it should have it's + * bigchunkFunction set to an appropriate value (all unused fields should be cleared to zero/NULL before + * initialisation). + * + * NOTE: The reason the init function needs to be called explicitly (rather than automatically e.g. in the + * first call to "elmm_malloc") is just because of edge-cases involving multithreaded programs: If the + * memory manager isn't used before creating a second thread, then calling elmm_malloc at the same time from any + * two threads could lead to both threads trying to initialise the structure at the same time (which is + * critical because the initialisation function needs to initialise any locks which would normally safely + * synchronise multithreaded access). This shouldn't be an issue in many cases, so in a wrapper function + * you could just check the "initialised" field and initialise whenever necessary (but there would probably + * be a better place to put the initialisation call in most cases anyway). + */ +ELMM_STATIC bool elmm_init(elmm_t* mm) { + if (mm == NULL) { + return false; + } + + if (mm->initialised) { + return false; + } + + if (mm->bigchunkFunction == NULL) { + return false; + } + + if (mm->bigchunkMinimum == 0) { + mm->bigchunkMinimum = 1024; + } + + if (mm->bigchunkGranularity == 0) { + mm->bigchunkGranularity = 1024; + } + + if (mm->lockFunction == NULL) { + mm->lockFunction = &elmm_nolock; + mm->lockData = NULL; + } + + if (!elmm_dolock(mm, ELMM_LOCK_NEW, &mm->mainLock, mm->lockData)) { + return false; + } + + /* The lock is then obtained to ensure that the structure is locked until + * AFTER it's set to initialised. + */ + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return false; + } + + mm->firstChunk = elmm_allocinner(mm, mm->bigchunkMinimum); + if (mm->firstChunk == NULL) { + //printf("ALLOC FAILED\n"); + return false; + } + + mm->initialised = true; + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return false; + } + + return true; +} + +ELMM_INLINE bool elmm_checkinit(elmm_t* mm) { + if (mm != NULL && mm->initialised) { + return true; + } else { + return false; + /* Initially the plan was to automatically call elmm_init(mm), but then I realised this wouldn't be threadsafe. + * So now each memory manager structure needs to be initialised explicitly before using it (and functions should + * just fail otherwise). The initialisation function (and, if called, the cleanup function) should only be invoked + * from a single thread (but if a lock function is provided, normal memory management functions can be called from + * any thread once it's initialised). + */ + } +} + +/* The inverse of elmm_init. Should be called when the heap is completely finished to deallocate any remaining chunks. In + * practice, if only one heap is used for the entire duration of a program then a program doesn't really need to clean it up + * (all of the program's memory would normally be reclaimed when the program ends anyway), but if multiple heaps are used + * it may become necessary to deallocate some of them individually. + */ +ELMM_INLINE bool elmm_cleanup(elmm_t* mm) { + if (mm == NULL || mm->bigchunkFunction == NULL || mm->lockFunction == NULL) { + return false; + } + + elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData); + + elmm_bigchunk_t* chunk = mm->firstChunk; + mm->firstChunk = NULL; + elmm_bigchunk_function_t bigchf = mm->bigchunkFunction; + while (chunk != NULL) { + elmm_bigchunk_t* deadChunk = chunk; + chunk = deadChunk->internals.nextChunk; + + bigchf(0, deadChunk, mm->bigchunkData); + } + + elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData); + + /* Finally, delete the lock.*/ + elmm_dolock(mm, ELMM_LOCK_DELETE, &mm->mainLock, mm->lockData); + + return true; +} + +/* This function is called by elmm_growheap in order to resize a chunk. One important responsibility is to create a new + * smallchunk representing the allocated space. + */ +ELMM_INLINE bool elmm_growinner(elmm_t* mm, elmm_bigchunk_t* bigchunk, uintptr_t increment) { + increment += sizeof(elmm_bigchunk_t) + (sizeof(elmm_smallchunk_t) * 10); // This should more than cover the minimum overheads + if (increment < mm->bigchunkMinimum) { + increment = mm->bigchunkMinimum; + } + while ((increment % mm->bigchunkGranularity) != 0) { + increment++; + } + uintptr_t oldSize = bigchunk->totalSize; + uintptr_t newSize = oldSize + increment; + elmm_bigchunk_function_t bigchf = mm->bigchunkFunction; + if (bigchf(newSize, bigchunk, mm->bigchunkData) != bigchunk) { + //printf("The bigchunk function failed!\n"); + return false; + } + + /* It might be higher than our requested size, but we should indicate failure if it's lower. */ + if (bigchunk->totalSize < newSize) { + //printf("The bigchunk function reported success but didn't allocate enough space\n"); + return false; + } + + /* Now we just need to calculate the address and size of the newChunk, initialise it's fields + * and add it to the free list. + */ + elmm_smallchunk_t* newChunk = (elmm_smallchunk_t*)(((uint8_t*)(bigchunk->startAddress)) + oldSize); + //printf("The new chunk starts at %d\n", newChunk); + newChunk->check1 = ELMM_SMALLCHUNK_FREE; + newChunk->next = bigchunk->internals.firstFree; + bigchunk->internals.firstFree = newChunk; + newChunk->size = (bigchunk->totalSize - oldSize) - sizeof(elmm_smallchunk_t); + newChunk->check2 = ELMM_SMALLCHUNK_FREE; + + //printf("Smallchunk size: %d\n", newChunk->size); + + return true; +} + +/* If not enough FREE smallchunks exist within the heap, this function will be + * called to attempt to allocate more. It should return true if successful and + * false otherwise. + */ +ELMM_STATIC bool elmm_growheap(elmm_t* mm, uintptr_t increment) { + //printf("elmm_growheap(%d, %d)\n", mm, increment); + if (!elmm_checkinit(mm)) { + return false; + } + if (increment > 1024 * 1024 * 1024) { + return false; + // TODO: Check against some better maximum incr? + } + while ((increment % mm->bigchunkGranularity) != 0) { + increment++; + } + /* First we need to check existing chunks, if any can be resized to fit the + * new data we should attempt that. + */ + elmm_bigchunk_t* chunk = mm->firstChunk; + elmm_bigchunk_t* lastValidChunk = chunk; + while (chunk != NULL) { + if (chunk->maximumSize >= (chunk->totalSize + increment)) { + //printf("Looks like we can resize this chunk!\n"); + if (elmm_growinner(mm, chunk, increment)) { // Only if it works should we return now! + return true; + } + /* Even if the maximum size indicates otherwise, there might be some reason the chunk + * can't be resized, so if growinner failed we need to continue and try to allocate + * somewhere else. + */ + } + lastValidChunk = chunk; + chunk = chunk->internals.nextChunk; + } + //printf("Attempting to allocate a new chunk...\n"); + elmm_bigchunk_t* newChunk = elmm_allocinner(mm, increment); + if (newChunk == NULL) { + printf("Internal allocation failed\n"); + return false; + } + + if (mm->firstChunk == NULL) { // Wouldn't normally happen, but maybe if initialisation changes + mm->firstChunk = newChunk; + } else { + lastValidChunk->internals.nextChunk = newChunk; + newChunk->internals.prevChunk = lastValidChunk; + } + return true; +} + +/* Locks the memory manager while collecting statistics into the given array (up to at most arrayLength elements). + * The array indices will match the ELMM_STAT_ values and the number of elements filled will be returned (zero upon + * complete failure). + */ +ELMM_STATIC uintptr_t elmm_stat(elmm_t* mm, uintptr_t* statArray, uintptr_t arrayLength) { + if (!elmm_checkinit(mm)) { + return 0; + } + + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return 0; + } + void* result = NULL; + + uintptr_t i; + for (i = 0; i < arrayLength && i < ELMM_STATTOP; i++) { + statArray[i] = elmm_innerstat(mm, i); + } + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return 0; + } + + return i; +} + +/* The chunkymalloc function attempts to allocate a smaller chunk from the free list within a given bigchunk. */ +ELMM_INLINE void* elmm_chunkymalloc(elmm_t* mm, elmm_bigchunk_t* bigchunk, uintptr_t size) { + while ((size % sizeof(elmm_smallchunk_t)) != 0) { + size++; + } + elmm_smallchunk_t** chunkptr = &bigchunk->internals.firstFree; + elmm_smallchunk_t* chunk = bigchunk->internals.firstFree; + while (chunk != NULL) { + if (chunk->size >= size) { + //printf("Can fit\n"); + if (chunk->size >= size + (2 * sizeof(elmm_smallchunk_t))) { + //printf("Will split\n"); + uint8_t* rawbytes = (uint8_t*) chunk; + elmm_smallchunk_t* upperchunk = (elmm_smallchunk_t*)(rawbytes + sizeof(elmm_smallchunk_t) + size); + while ((((uintptr_t)upperchunk) % sizeof(elmm_smallchunk_t)) != 0) { + upperchunk = (elmm_smallchunk_t*) (((uintptr_t)upperchunk) + 1); + } + upperchunk->check1 = ELMM_SMALLCHUNK_FREE; + uintptr_t oldsize = chunk->size; + chunk->size = (((uintptr_t)upperchunk) - ((uintptr_t)chunk)) - sizeof(elmm_smallchunk_t); + upperchunk->size = oldsize - (((uintptr_t)upperchunk) - ((uintptr_t)chunk)); + upperchunk->next = chunk->next; + upperchunk->check2 = ELMM_SMALLCHUNK_FREE; + chunk->next = upperchunk; + //printf("Splitted one chunk of %d into two chunks of %d and %d\n", oldsize, chunk->size, upperchunk->size); + } + *chunkptr = chunk->next; + chunk->check1 = ELMM_SMALLCHUNK_ALLOCATED; + chunk->next = bigchunk->internals.firstAllocated; + bigchunk->internals.firstAllocated = chunk; + chunk->check2 = ELMM_SMALLCHUNK_ALLOCATED; + return (void*)(chunk + 1); + } + chunkptr = &chunk->next; + chunk = chunk->next; + } + + //printf("No fit in chunk %d\n", bigchunk); + + /* If no chunk was found, just return NULL. */ + return NULL; +} + +/* The inner malloc function attempts to allocate from any free chunks. This performs + * most of the job of malloc, but leaves the edge cases (i.e. when we need to obtain more memory) + * as well as locking for the outer elmm_malloc function (and leaves the hard bits to elmm_chunkymalloc). + */ +ELMM_INLINE void* elmm_innermalloc(elmm_t* mm, uintptr_t size) { + elmm_bigchunk_t* chunk = mm->firstChunk; + while (chunk != NULL) { + void* result = elmm_chunkymalloc(mm, chunk, size); + if (result != NULL) { + return result; // Success! + } + chunk = chunk->internals.nextChunk; + } + /* If we got to the end without any being able to allocate, then we didn't find + * any free memory in the heap. + */ + return NULL; +} + +/* The main allocation function, equivalent to malloc. */ +ELMM_INLINE void* elmm_malloc(elmm_t* mm, uintptr_t size) { + if (!elmm_checkinit(mm)) { + return NULL; + } + + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return NULL; + } + void* result = NULL; + + /* First try innermalloc once, for the usual case where memory is already available. */ + result = elmm_innermalloc(mm, size); + + /* Only if the first allocation failed do we need to expand the heap and try again. */ + if (result == NULL) { + //printf("innermalloc failed, trying growheap...\n"); + if (elmm_growheap(mm, size)) { /* And only if the heap has actually be grown should we try again. */ + //printf("growheap worked, trying malloc again...\n"); + result = elmm_innermalloc(mm, size); + if (result == NULL) { + //printf("That failed :(\n"); + } else { + //printf("That worked :D\n"); + } + } else { + //printf("growheap failed\n"); + } + } + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return NULL; + } + + return result; +} + +/* Only used internally by elmm_free to find the bigchunk which the data at the given pointer would reside in. */ +ELMM_INLINE elmm_bigchunk_t* elmm_innerchunk(elmm_t* mm, void* pointer) { + elmm_bigchunk_t* chunk = mm->firstChunk; + while (chunk != NULL) { + uintptr_t iptr = (uintptr_t)pointer; + uintptr_t cptr = (uintptr_t)(chunk->startAddress); + if (iptr >= cptr && iptr < cptr + chunk->totalSize) { + return chunk; + } + chunk = chunk->internals.nextChunk; + } + return NULL; +} + +/* Only used internally by elmm_free to unlink a smallchunk element from a list. */ +ELMM_INLINE bool elmm_innerunlink(elmm_t* mm, elmm_smallchunk_t** listVariable, elmm_smallchunk_t* element) { + if (element == *listVariable) { + *listVariable = element->next; + element->next = NULL; + return true; + } + elmm_smallchunk_t* prevChunk = *listVariable; + elmm_smallchunk_t* chunk = prevChunk->next; + while (chunk != NULL) { + if (chunk == element) { + prevChunk->next = element->next; + element->next = NULL; + return true; + } + prevChunk = chunk; + chunk = chunk->next; + } + return false; +} + +/* The main deallocation function, equivalent to free (except it should always return true, otherwise + * it has e.g. been fed a NULL value or worse). + */ +ELMM_INLINE bool elmm_free(elmm_t* mm, void* pointer) { + if (!elmm_checkinit(mm)) { + return false; + } + + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return false; + } + bool success = false; + + elmm_smallchunk_t* header = elmm_getheader(mm, pointer); + + if (header != NULL) { + if (header->check1 == ELMM_SMALLCHUNK_ALLOCATED && header->check1 == header->check2) { + elmm_bigchunk_t* bigchunk = elmm_innerchunk(mm, pointer); + if (bigchunk != NULL) { + if (elmm_innerunlink(mm, &bigchunk->internals.firstAllocated, header)) { + header->check1 = ELMM_SMALLCHUNK_FREE; + header->next = bigchunk->internals.firstFree; + header->check2 = ELMM_SMALLCHUNK_FREE; + bigchunk->internals.firstFree = header; + success = true; + } + } + } + } + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return false; + } + return success; +} + +/* The equivalent of the calloc function, which is built on top of the malloc implementation. */ +ELMM_INLINE void* elmm_calloc(elmm_t* mm, uintptr_t count, uintptr_t elementSize) { + if (elementSize == 3) { + elementSize = 4; + } else if (elementSize > 4 && elementSize < 8) { + elementSize = 8; + } else if (elementSize > 8 && elementSize < 16) { + elementSize = 16; + } else if (elementSize > 16) { + while ((elementSize % 16) != 0) { + elementSize++; + } + } + uintptr_t size = count * elementSize; + void* result = elmm_malloc(mm, size); + + if (result == NULL) { + return NULL; + } else { + uint8_t* bytes = (uint8_t*)result; + uintptr_t i; + for (i = 0; i < size; i++) { + bytes[i] = 0; + } + return result; + } +} + +/* The equivalent of the realloc function, which is built on top of the calloc and free implementations but also + * uses an internal API to get the original pointer's size. */ +ELMM_INLINE void* elmm_realloc(elmm_t* mm, void* pointer, uintptr_t newSize) { + if (pointer == NULL) { + if (newSize == 0) { + return NULL; + } else { + return elmm_calloc(mm, newSize, 1); + } + } else if (newSize == 0) { + elmm_free(mm, pointer); + return NULL; + } else { + uintptr_t oldSize = elmm_sizeof(mm, pointer); + void* newPointer = elmm_calloc(mm, newSize, 1); + if (newPointer == NULL) { + return NULL; + } + uint8_t* oldBytes = (uint8_t*)pointer; + uint8_t* newBytes = (uint8_t*)newPointer; + uintptr_t commonSize = (oldSize < newSize) ? oldSize : newSize; + uintptr_t i; + for (i = 0; i < commonSize; i++) { + newBytes[i] = oldBytes[i]; + } + elmm_free(mm, pointer); + return newPointer; + } +} + +ELMM_INLINE intptr_t elmm_innercompact(elmm_t* mm, elmm_bigchunk_t* bigchunk) { + intptr_t result = 0; + + elmm_smallchunk_t** chunkvar = &bigchunk->internals.firstFree; + elmm_smallchunk_t* chunk = *chunkvar; + while (chunk != NULL) { + uintptr_t addr1 = (uintptr_t)chunk; + uintptr_t addr2 = (uintptr_t)(chunk->next); + if (chunk->next != NULL && addr1 > addr2) { /* Sorting is required. */ + //printf("I'm going to sort chunks %d and %d into correct order...\n", addr1, addr2); + *chunkvar = chunk->next; + chunk->next = (*chunkvar)->next; + (*chunkvar)->next = chunk; + result++; + } else if (addr2 == (addr1 + sizeof(elmm_smallchunk_t) + chunk->size)) { + //printf("I'm going to compact chunks %d and %d into one chunk...\n", addr1, addr2); + chunk->size += sizeof(elmm_smallchunk_t) + chunk->next->size; + chunk->next = chunk->next->next; + result++; + } + chunkvar = &chunk->next; + chunk = chunk->next; + } + + return result; +} + +/* Performs one-or-more cycles of compaction, returning the total number of sorting or compaction operations performed, and stopping + * early if it stops finding sortable or compactable entries. Returns -1 on error. + */ +ELMM_INLINE intptr_t elmm_compact(elmm_t* mm/*, uintptr_t maxCycles*/) { + if (!elmm_checkinit(mm)) { + return -1; + } + + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return -1; + } + intptr_t result = 0; + + elmm_bigchunk_t* chunk = mm->firstChunk; + while (chunk != NULL) { + intptr_t nchanges = elmm_innercompact(mm, chunk); + result += nchanges; + chunk = chunk->internals.nextChunk; + } + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return -1; + } + + return result; +} + +/* Just calls elmm_compact until either there's nothing more to compact or an error occurs. + * NOTE: This should be threadsafe, in that the internal cycles perform locking, but if you + * continue allocating the whole time it's running it'll just keep compacting forever. This + * might be the desired behaviour in some scenarios (particularly if you have a dedicated + * compaction thread - you'd just call this and then run friendlycompact and sleep for a + * while if/when it returns - or if you need to perform compaction only at specific times + * this function might also do the trick) but for more general usage you'd probably just want + * to call elmm_friendlycompact regularly. + */ +ELMM_INLINE intptr_t elmm_fullcompact(elmm_t* mm) { + intptr_t result = 0; + + intptr_t tmpresult; + while ((tmpresult = elmm_compact(mm)) > 0) { + result += tmpresult; + } + + if (tmpresult < 0) { + return tmpresult; + } else { + return result; + } +} + +ELMM_INLINE intptr_t elmm_innertrim(elmm_t* mm, elmm_bigchunk_t* bigchunk) { + uintptr_t nalloc = elmm_innerstatpart(mm, bigchunk->internals.firstAllocated); + uintptr_t nfree = elmm_innerstatpart(mm, bigchunk->internals.firstFree); + + /* This is the minimum size of a smallchunk worth returning to the system, equal to + * whatever's set as the bigchunk granularity except taking it's smallchunk header size + * into account. + */ + uintptr_t mindealloc = mm->bigchunkGranularity - sizeof(elmm_smallchunk_t); + + elmm_bigchunk_function_t bigchf = mm->bigchunkFunction; + if (nalloc == 0) { + //printf("That's it, I'm deleting the whole chunk at %d\n", bigchunk); + if (bigchunk == mm->firstChunk) { + mm->firstChunk = bigchunk->internals.nextChunk; + } + if (bigchunk->internals.nextChunk != NULL) { + bigchunk->internals.prevChunk = bigchunk->internals.prevChunk; + } + if (bigchf(0, bigchunk, mm->bigchunkData) != NULL) { + return -1; + } + } else if (bigchunk->maximumSize > 0 && nfree >= mindealloc) { + /* If the chunk is resizable and there's enough free memory that it'd be worth deallocating, then + * try to find a large chunk at the end of this bigchunk and resize. If the free space is too fragmented + * or not at the end of the chunk then we'll just have to leave it for later. + */ + //printf("I'm looking for a chunk to deallocate inside %d\n", bigchunk); + elmm_smallchunk_t** smallchunkVar = &bigchunk->internals.firstFree; + elmm_smallchunk_t* smallchunk = *smallchunkVar; + while (smallchunk != NULL) { + if (smallchunk->size >= mindealloc) { + uintptr_t startaddr = (uintptr_t)(bigchunk->startAddress); + uintptr_t addr = (uintptr_t)smallchunk; + /* If this smallchunk ends right at the end of the bigchunk or close enough that another chunk header wouldn't fit, then + * let's trim it! + */ + if (addr + sizeof(elmm_smallchunk_t) + smallchunk->size >= startaddr + bigchunk->totalSize - sizeof(elmm_bigchunk_t)) { + //printf("Okay I'm gonna trim some out of the smallchunk at %d with size %d\n", addr, smallchunk->size); + } + intptr_t actualDealloc = mm->bigchunkGranularity; + intptr_t newsz = smallchunk->size - actualDealloc; + if (newsz < 0) { + /* Set the pointer to this chunk to a pointer to the next chunk (or NULL) instead. We don't have to worry about the + * next iteration either, since we can return as soon as we yield some memory. + */ + *smallchunkVar = smallchunk->next; + //printf("I'm deleting that smallchunk entirely.\n"); + } else { + while (newsz >= mm->bigchunkGranularity && (bigchunk->totalSize - actualDealloc >= mm->bigchunkMinimum)) { + newsz -= mm->bigchunkGranularity; + actualDealloc += mm->bigchunkGranularity; + } + /* If we're only trimming part of this smallchunk we can leave the rest in place and just modify it's size. */ + smallchunk->size = newsz; + //printf("I left it with a size of %d\n", smallchunk->size); + } + uintptr_t oldtotal = bigchunk->totalSize; + if (bigchf(bigchunk->totalSize - actualDealloc, bigchunk, mm->bigchunkData) != bigchunk) { + //printf("The bigchunk function failed.\n"); + return -1; + } + + /* I guess it worked! But we'll return the actual size calculation in case it ended up zero or erroneous. */ + return oldtotal - bigchunk->totalSize; + } + smallchunkVar = &smallchunk->next; + smallchunk = *smallchunkVar; + } + } else { + return 0; + } +} + +/* Attempts partial compaction and then tries to yield any spare memory to the operating system (or at least + * back to the bigchunk allocator, whatever it wants to do with it). Returns the amount of memory yielded + * (zero if none can be yielded) or a negative value on error. Note that this doesn't attempt full compaction + * (to avoid locking the memory manager for too long - and also since it wouldn't be required if enough memory + * is free), it just runs one compaction cycle and then tries to trim the heap opportunistically. + */ +ELMM_INLINE intptr_t elmm_friendlycompact(elmm_t* mm) { + if (elmm_compact(mm) < 0) { + return -1; + } + + if (!elmm_dolock(mm, ELMM_LOCK_WAITLOCK, &mm->mainLock, mm->lockData)) { + return -1; + } + intptr_t result = 0; + + elmm_bigchunk_t* chunk = mm->firstChunk; + while (chunk != NULL) { + /* Get the pointer to the next chunk BEFORE trimming, in case this chunk gets yielded entirely. */ + elmm_bigchunk_t* next = chunk->internals.nextChunk; + intptr_t nreleased = elmm_innertrim(mm, chunk); + if (nreleased < 0) { + return -1; + } + result += nreleased; + chunk = next; + } + + if (!elmm_dolock(mm, ELMM_LOCK_UNLOCK, &mm->mainLock, mm->lockData)) { + return -1; + } + + return result; +} + +/* From ifndef at top of file: */ +#endif diff --git a/include.mk b/include.mk new file mode 100644 index 0000000..05f3c2a --- /dev/null +++ b/include.mk @@ -0,0 +1,13 @@ +L=libc +LIBC = $L/_libc_internal.o $L/_libc_stdio.o $L/_libc_memory.o $L/_libc_string.o $L/_libc_stdlib.o $L/_libc_ctype.o $L/_libc_misc.o $L/_libc_math.o $U/usys.o +LIBCH = $L/include/ + +# Note: Add -DLIBC_OOP for object-oriented FILE + +_libc_%.o: %.c $(LIBCH) + $(CC) $(CFLAGS_MANDATORY) -I$L/include -c -o $@ $< + +_libc_%: _libc_%.o $(LIBC) + $(LD) $(LDFLAGS) -T $L/link.ld -o $@ $^ + $(OBJDUMP) -S $@ > $*.asm + $(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym diff --git a/include/assert.h b/include/assert.h new file mode 100644 index 0000000..0b10b2b --- /dev/null +++ b/include/assert.h @@ -0,0 +1,17 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_ASSERT_H +#define _LIBC_ASSERT_H + +#define assert(...) do {} while(0) + +/* From ifndef at top of file: */ +#endif diff --git a/include/ctype.h b/include/ctype.h new file mode 100644 index 0000000..dcc4df1 --- /dev/null +++ b/include/ctype.h @@ -0,0 +1,27 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_CTYPE_H +#define _LIBC_CTYPE_H + +int isspace(int ch); +int isdigit(int ch); +int isxdigit(int ch); +int isalpha(int ch); +int isalnum(int ch); +int isprint(int ch); +int ispunct(int ch); +int isupper(int ch); +int islower(int ch); +int toupper(int ch); +int tolower(int ch); + +/* From ifndef at top of file: */ +#endif diff --git a/include/dirent.h b/include/dirent.h new file mode 100644 index 0000000..035fc01 --- /dev/null +++ b/include/dirent.h @@ -0,0 +1,9 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. diff --git a/include/errno.h b/include/errno.h new file mode 100644 index 0000000..b44d264 --- /dev/null +++ b/include/errno.h @@ -0,0 +1,30 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_ERRNO_H +#define _LIBC_ERRNO_H + +//extern int errno; + +/* On modern Linux platforms the errno is simulated. This is presumably so that each + * thread can have it's own errno without the ABI becoming a huge mess. + */ +#ifdef __MAC +int errno; +#else +int* __errno_location(); + +#define errno __errno_location()[0] +#endif + +#define ENOENT 2 + +/* From ifndef at top of file: */ +#endif diff --git a/include/fcntl.h b/include/fcntl.h new file mode 100644 index 0000000..035fc01 --- /dev/null +++ b/include/fcntl.h @@ -0,0 +1,9 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. diff --git a/include/float.h b/include/float.h new file mode 100644 index 0000000..09feeca --- /dev/null +++ b/include/float.h @@ -0,0 +1,17 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_FLOAT_H +#define _LIBC_FLOAT_H + +#define DBL_MAX_EXP 1024 +#define DBL_MANT_DIG 53 + +#endif diff --git a/include/limits.h b/include/limits.h new file mode 100644 index 0000000..26c2152 --- /dev/null +++ b/include/limits.h @@ -0,0 +1,18 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_LIMITS_H +#define _LIBC_LIMITS_H + +#define CHAR_BIT 8 +#define UINT_MAX 0xFFFFFFFFU + +/* From ifndef at top of file: */ +#endif diff --git a/include/math.h b/include/math.h new file mode 100644 index 0000000..371d3bf --- /dev/null +++ b/include/math.h @@ -0,0 +1,21 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_MATH_H +#define _LIBC_MATH_H + +double fabs(double n); +double pow(double x, double p); +double exp(double x); + +double ldexp(double x, int exp); + +// From ifndef at top of file: +#endif diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..ffaed6c --- /dev/null +++ b/include/memory.h @@ -0,0 +1,15 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_MEMORY_H +#define _LIBC_MEMORY_H + +/* From ifndef at top of file: */ +#endif diff --git a/include/pwd.h b/include/pwd.h new file mode 100644 index 0000000..035fc01 --- /dev/null +++ b/include/pwd.h @@ -0,0 +1,9 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. diff --git a/include/setjmp.h b/include/setjmp.h new file mode 100644 index 0000000..3da9161 --- /dev/null +++ b/include/setjmp.h @@ -0,0 +1,28 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_SETJMP_H +#define _LIBC_SETJMP_H + +/*struct jmp_buf_struct { + int todo; +}; + +typedef struct jmp_buf_struct jmp_buf;*/ +typedef int jmp_buf; + +#define setjmp(x) \ + 0 +// (printf("WARNING: Unimplemented: setjmp\n") && 0) +#define longjmp(x,y) \ + printf("WARNING: Unimplemented: longjmp\n") + +/* From ifndef at top of file: */ +#endif diff --git a/include/signal.h b/include/signal.h new file mode 100644 index 0000000..035fc01 --- /dev/null +++ b/include/signal.h @@ -0,0 +1,9 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. diff --git a/include/stdarg.h b/include/stdarg.h new file mode 100644 index 0000000..f89da61 --- /dev/null +++ b/include/stdarg.h @@ -0,0 +1,45 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDARG_H +#define _LIBC_STDARG_H + +#ifdef _ZCC + +typedef long long** va_list; + +#define _VA_CHECK() \ + if (__builtin_func_callconv != 101) {\ + printf("ERROR: Unpacking varargs currently only works with __classic_call (#101). Function %s uses convention %d instead.\n", __func__, __builtin_func_callconv);\ + } + +#define va_start(list,lastarg) \ + do {\ + _VA_CHECK();\ + list = &lastarg;\ + list++;\ + } while(0) + +#define va_arg(list,T) \ + (*((T*)(list++))) + +#define va_end(list) \ + do {list = (void*)0;} while(0) + +/* Not new C compiler, use GCC ABI */ +#else +#define va_list __builtin_va_list +#define va_start __builtin_va_start +#define va_end __builtin_va_end +#define va_arg __builtin_va_arg +#endif + +/* From ifndef at top of file: */ +#endif diff --git a/include/stdbool.h b/include/stdbool.h new file mode 100644 index 0000000..7995bab --- /dev/null +++ b/include/stdbool.h @@ -0,0 +1,20 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDBOOL_H +#define _LIBC_STDBOOL_H + +typedef int bool; + +#define true 1 +#define false 0 + +/* From ifndef at top of file: */ +#endif diff --git a/include/stddef.h b/include/stddef.h new file mode 100644 index 0000000..063e5e8 --- /dev/null +++ b/include/stddef.h @@ -0,0 +1,23 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDDEF_H +#define _LIBC_STDDEF_H + +//#ifndef _LIBC_STDLIB_H +typedef long size_t; +//#endif + +#ifndef NULL +#define NULL ((void*) 0) +#endif + +/* From ifndef at top of file: */ +#endif diff --git a/include/stdint.h b/include/stdint.h new file mode 100644 index 0000000..b2728bf --- /dev/null +++ b/include/stdint.h @@ -0,0 +1,36 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDINT_H +#define _LIBC_STDINT_H + +typedef char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +typedef int64_t intptr_t; +typedef uint64_t uintptr_t; + +typedef uint32_t uint_fast8_t; +typedef int32_t int_fast8_t; +typedef uint32_t uint_fast16_t; +typedef int32_t int_fast16_t; +typedef uint32_t uint_fast32_t; +typedef int32_t int_fast32_t; +typedef uint64_t uint_fast64_t; +typedef int64_t int_fast64_t; + +/* From ifndef at top of file: */ +#endif diff --git a/include/stdio.h b/include/stdio.h new file mode 100644 index 0000000..32d8b1f --- /dev/null +++ b/include/stdio.h @@ -0,0 +1,120 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDIO_H +#define _LIBC_STDIO_H + +#include +#include + +#ifdef LIBC_OOP +// Theoretical OOP version of FILE, this would be ideal for C+OOP apps +// This is split into FILE (base class for interoperable stream objects) +// and _libc_FILE (the default FILE implementation using system file +// descriptors) + +// Class hierarchy doesn't exist yet... +//#import +@interface Resource { +} +@end +/* The extensible FILE base type. */ +@interface FILE : Resource { + FILE* backend; +} +-(id) init; +@end + +/* The subclass of FILE which works over an OS file descriptor. */ +@interface _libc_FILE : FILE { + int fd; + int ungot; // Character pushed back or -1 + int eof; +} +@end + +#else +// Simplified data structure, only intended for bootstrapping (does not +// provide complex buffering or overloading) + +struct _libc_FILE_internals { + int fd; + int ungot; // Character pushed back or -1 + int eof; +}; + +typedef struct _libc_FILE_internals FILE; +#endif + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +extern FILE* stderr; +extern FILE* stdin; +extern FILE* stdout; + +#define EOF ((int)-1) + +#ifdef _ZCC +#define _LIBC_PRINTF_CALLCONV __classic_call +#else +#define _LIBC_PRINTF_CALLCONV +#endif + +int _LIBC_PRINTF_CALLCONV printf(const char* fmt, ...); +int _LIBC_PRINTF_CALLCONV sprintf(char *buf, const char* fmt, ...); +int _LIBC_PRINTF_CALLCONV snprintf(char *buf, size_t n, const char* fmt, ...); +int _LIBC_PRINTF_CALLCONV dprintf(int fd, const char* fmt, ...); +int _LIBC_PRINTF_CALLCONV fprintf(FILE* f, const char* fmt, ...); + +int _LIBC_PRINTF_CALLCONV vfprintf(FILE* f, const char* fmt, va_list list); +int _LIBC_PRINTF_CALLCONV vsnprintf(char* str, size_t n, const char* fmt, va_list list); + +// These are non-standard, but the other *printf functions need to be implemented somehow, +// so fnprintf/vfnprintf just use a callback function for output of n bytes of string output. +typedef int(*_libc_fnprintf_fn_t)(const char* str, int n, void* udata); +int _LIBC_PRINTF_CALLCONV _libc_fnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, ...); +int _LIBC_PRINTF_CALLCONV _libc_vfnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, va_list list); + +FILE* fopen(const char* name, const char* mode); +FILE* freopen(const char* name, const char* mode, FILE* f); +int fclose(FILE* f); +int fflush(FILE* f); + +long fread(void* buffer, long size, long count, FILE* f); +long fwrite(void* buffer, long size, long count, FILE* f); + +char* fgets(char*, int, FILE*); + +int fputs(const char*, FILE*); +int fputc(int, FILE*); + +int feof(FILE* f); + +void perror(const char*); + +int putc(int c, FILE* f); + +int putchar(int c); + +int getc(FILE* f); +int ungetc(int c, FILE* f); + +int fseek(FILE* f, long offset, int wh); +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +long ftell(FILE* f); + +long getline(char** linevar, long *nvar, FILE* f); + +/* From ifndef at top of file: */ +#endif diff --git a/include/stdlib.h b/include/stdlib.h new file mode 100644 index 0000000..460cfca --- /dev/null +++ b/include/stdlib.h @@ -0,0 +1,50 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STDLIB_H +#define _LIBC_STDLIB_H + +#include + +// By default, this libc includes both traditional and garbage collected +// memory managers. _libc_gcalloc is therefore preconfigured as a malloc +// alternative with garbage collection, but internally +// malloc/calloc/realloc/free are also just wrappers over a flexible +// memory manager. + +void* malloc(size_t sz); +void* calloc(size_t n, size_t sz); +void* realloc(void* mem, size_t sz); +void free(void* mem); +void* _libc_gcalloc(size_t sz); +char* getenv(const char* key); +int setenv(const char* key, const char* value, int overwrite); +int unsetenv(const char* key); +void exit(int x); +void abort(); + +long strtol(const char* str, char**endvar, int base); +long long strtoll(const char* str, char**endvar, int base); +unsigned long strtoul(const char* str, char**endvar, int base); +unsigned long long strtoull(const char* str, char**endvar, int base); + +float strtof(const char* str, char**endvar); +double strtod(const char* str, char**endvar); +long double strtold(const char* str, char**endvar); + +double atof(const char* str); +int atoi(const char* str); + +int rand(); +void srand(unsigned int r); +#define RAND_MAX 999999999 + +/* From ifndef at top of file: */ +#endif diff --git a/include/string.h b/include/string.h new file mode 100644 index 0000000..0df5f0c --- /dev/null +++ b/include/string.h @@ -0,0 +1,33 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_STRING_H +#define _LIBC_STRING_H + +#include + +size_t strlen(const char* foo); + +char *strchr(const char* str, int chr); +char *strrchr(const char* str, int chr); +char* strcat(char* str, const char* cat); +char* strcpy(char* buffer, const char* str); +char* strncpy(char* buffer, const char* str, size_t n); +const char* strpbrk(const char* str, const char* search); +int strcmp(const char* a, const char* b); +char* strdup(const char* str); +char* strndup(const char* str, size_t n); + +void* memcpy(void* dst, const void* src, size_t nbytes); + +void* memset(void* mem, int byt, size_t nbytes); + +/* From ifndef at top of file: */ +#endif diff --git a/include/sys/event.h b/include/sys/event.h new file mode 100644 index 0000000..1a497e9 --- /dev/null +++ b/include/sys/event.h @@ -0,0 +1,48 @@ +// Zak's kqueue implementation +#ifndef SYS_EVENT_H +#define SYS_EVENT_H + +#include + +// The kevent structure is used for waiting/receiving notifications. +struct kevent { + uintptr_t ident; + int16_t filter; + uint16_t flags; + uint32_t fflags; + int64_t data; + void* udata; + uint64_t ext[4]; +}; + +// kqueue1 is the kqueue system call with a flags argument, +// as defined on NetBSD & OpenBSD. +int kqueue1(int flags); + +// kqueue() without flags is equivalent to kqueue1(0) +#define kqueue() kqueue1(0) + +// On FreeBSD, kqueue1 is also known as kqueuex +#define kqueuex(flags) kqueue1(flags) + +// After creating a kqueue, kevent is the main syscall used. +// This accepts a list of changes and can receive multiple events. +int kevent(int queue, const struct kevent* changes, int nch, struct kevent* events, int nev, void* todo_timeout); + +// The EV_SET() macro is for initialising a struct kevent* +#define EV_SET(ev,idn,flt,flg,ffl,dat,udt) \ + do { \ + (ev)->ident = idn; \ + (ev)->filter = flt; \ + (ev)->flags = flg; \ + (ev)->fflags = ffl; \ + (ev)->data = dat; \ + (ev)->udata = udt; \ + (ev)->ext[0] = 0; \ + (ev)->ext[1] = 0; \ + (ev)->ext[2] = 0; \ + (ev)->ext[3] = 0; \ + } while(0) + +// From ifndef at top of file: +#endif diff --git a/include/syscalls.h b/include/syscalls.h new file mode 100644 index 0000000..f339db2 --- /dev/null +++ b/include/syscalls.h @@ -0,0 +1,23 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. +#ifndef _LIBC_SYSCALLS_H +#define _LIBC_SYSCALLS_H +#include + +// NOTE: This is NOT the definitive list of system calls, this is just any +// syscall definitions which didn't fit anywhere else! + +// TODO: Add some platform-checking here + +void* sbrk(int incr); + +/* From ifndef at top of file: */ +#endif + diff --git a/include/time.h b/include/time.h new file mode 100644 index 0000000..76f1845 --- /dev/null +++ b/include/time.h @@ -0,0 +1,40 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#ifndef _LIBC_TIME_H +#define _LIBC_TIME_H + +typedef long time_t; + +char* ctime(const time_t* timevar); +time_t time(time_t* timevar); + +/* TODO: This is not implemented on the current OS. */ +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +struct timespec { + int tv_sec; + long tv_nsec; +}; + +struct tm* localtime(const time_t* timep); + +/* From ifndef at top of file: */ +#endif diff --git a/include/unistd.h b/include/unistd.h new file mode 100644 index 0000000..a5a250d --- /dev/null +++ b/include/unistd.h @@ -0,0 +1,18 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. +#ifndef _LIBC_UNISTD_H +#define _LIBC_UNISTD_H + +void exit(int status); +typedef int pid_t; +pid_t getpid(); + +/* From ifndef at top of file: */ +#endif diff --git a/include/utime.h b/include/utime.h new file mode 100644 index 0000000..035fc01 --- /dev/null +++ b/include/utime.h @@ -0,0 +1,9 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. diff --git a/internal.c b/internal.c new file mode 100644 index 0000000..c47e235 --- /dev/null +++ b/internal.c @@ -0,0 +1,65 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include "internal.h" +#include "simgc.h" + +int main(int argc, char** argv, char** envv); // Note, the third argument is usually not implemented by the main function. + +void _libc_malloc_init(void* stackptr); +void _libc_env_init(const char** envv); +void _libc_malloc_finish(); +void _libc_stdio_init(); + +void _libc_start(int argc, char** argv, char** envv) { + _libc_env_init(envv); + _libc_malloc_init(&argc); + _libc_stdio_init(); + /* + for (int i = 0; i < argc; i++) { + write(1, "ARG: ", 5); + write(1, argv[i], strlen(argv[i])); + write(1, "\n", 1); + } + for (int i = 0; envv[i]; i++) { + write(1, "ENV: ", 5); + write(1, envv[i], strlen(envv[i])); + write(1, "\n", 1); + } + */ + int result = main(argc, argv, envv); + _libc_malloc_finish(); + exit(result); + FATAL("Faulty 'exit' syscall"); +} + +/* This is used to report missing functions or other critical errors, so + * it shouldn't use any additional library functions it should just + * print the error and exit using the simplest possible code path. + */ +void _libc_fatal(const char* msg, const char* fnc) { + const char* l1 = "FATAL ERROR:\n"; + const char* l3 = "FUNCTION:\n"; + write(2, l1, strlen(l1)); + write(2, "\t", 1); + write(2, msg, strlen(msg)); + write(2, "\n", 1); + write(2, l3, strlen(l3)); + write(2, "\t", 1); + write(2, fnc, strlen(fnc)); + write(2, "\n", 1); + // TODO: Trigger debugger here? + exit(-1); + l1 = "FAULTY EXIT? HOT LOOP FOREVER"; + write(2, l1, strlen(l1)); + while(1) { + // Hot loop forever + } +} diff --git a/internal.h b/internal.h new file mode 100644 index 0000000..8704cb0 --- /dev/null +++ b/internal.h @@ -0,0 +1,34 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. +#ifndef _LIBC_INTERNAL_H +#define _LIBC_INTERNAL_H + +/* This should be called when a function isn't yet implemented or some similar + * irreconcilable error exists. + */ +void _libc_fatal(const char* errorstr, const char* function); +/* The _libc_fatalf function is like _libc_fatal except prints a formatted + * string ("printf style"). + */ +/*void +#ifdef _ZCC +__classic_call +#endif +_libc_fatalf(const char* fmt, ...);*/ + +#define FATAL(msg) \ + _libc_fatal(msg, __func__); + +#define UNIMPLEMENTED() \ + _libc_fatal("Unimplemented libc function", __func__) + +/* From ifndef at top of file: */ +#endif + diff --git a/link.ld b/link.ld new file mode 100644 index 0000000..e8ea62e --- /dev/null +++ b/link.ld @@ -0,0 +1,25 @@ +OUTPUT_ARCH("riscv") +ENTRY(_libc_start) + +SECTIONS { + . = 0; + .text : { + . = ALIGN(4); + *(.text) + } + .rodata : { + . = ALIGN(16); + *(.rodata) + } + . = ALIGN(4096); + .data : { + . = ALIGN(16); + *(.data) + } + .bss : { + . = ALIGN(16); + *(.bss) + } + + PROVIDE(_libc_programend = .); +} diff --git a/math.c b/math.c new file mode 100644 index 0000000..634c863 --- /dev/null +++ b/math.c @@ -0,0 +1,216 @@ +#include +#include +#include +#include "internal.h" + +unsigned int _libc_rand_seed = 12345; + +int rand() { + _libc_rand_seed ^= _libc_rand_seed << 13; + _libc_rand_seed ^= _libc_rand_seed >> 17; + _libc_rand_seed ^= _libc_rand_seed << 5; + return _libc_rand_seed % (RAND_MAX+1); +} + +void srand(unsigned int r) { + if (r == 0) { + _libc_rand_seed = 1; + } else { + _libc_rand_seed = r; + } +} + +double fabs(double n) { + if (n < 0) { + return 0.0 - n; + } else { + return n; + } +} + +double ldexp(double x, int exp) { + UNIMPLEMENTED(); +} + +double frexp(double x, int* expptr) { + // TODO: Check special cases? + uint64_t bits = *((uint64_t*)((void*)&x)); + uint64_t exp = (bits>>52)&0x7FF; + *expptr = (int) exp - 1023; + bits &= 0x800FFFFFFFFFFFFFULL; + bits |= 1023ULL << 52; + return *((double*)((void*)&bits)); +} + +/* +double _libc_factorial(int n) { + double x = 1.0; + for (int i = 1; i <= n; i++) { + x *= i; + } + return x; +} + +// This implementation is very very dumb. +double pow(double x, double p) { + double result = x; + for (int i = 2; i < p; i++) { + result *= x; + } + return result; +} + +#define EXP_EPSILON 0.0000000001 + +// NOTE: This function was written with the help of Qwen AI +double exp(double x) { + double r = 1.0; + double t = 1.0; + int n = 1; + + while (fabs(t) < EXP_EPSILON) { + t = pow(x, n) / _libc_factorial(n); + r += t; + n++; + } + + return r; +} +*/ + +//////////////////////////////////////////////////////////////////// +// NOTE: OLD CODE taken from PDPC400's math.c ("public domain") +//////////////////////////////////////////////////////////////////// + +#define HUGE_VAL 9.999999999999999999999E72 + +/* + + Some constants to make life easier elsewhere + (These should I guess be in math.h) + +*/ +double _libc_pi = 3.1415926535897932384626433832795; +double _libc_ln10 = 2.3025850929940456840179914546844; +double _libc_ln2 = 0.69314718055994530941723212145818; + +/* + +exp(x) = 1 + x + x2/2 + x3/6 + x4/24 + x5/120 + ... + xn/n! + ... + +*/ +double exp (double x) +{ + int i; + double term,answer,work; + + i=2; + term=x; + answer=x; + + while (1) + { + work = i; + term = (term * x)/work; + if ( answer == (answer + term) )break; + answer = answer + (term); + i++; + } + + answer=answer+1.0; + return(answer); +} + +/* + + Calculate LOG using Taylor series. + + log(1+ x) = x - x**2 + x**3 - x**4 + x**5 + ==== ==== ==== ==== ......... + 2 3 4 8 + + Note this only works for small x so we scale.... + +*/ +double log (double x) +{ + int i,scale; + double term,answer,work,xs; + + if (x <= 0 ) + { + /* need to set signal */ + // TODO: Proper errno errno=EDOM; + return (HUGE_VAL); + } + if( x == 1.0)return(0.0); + +/* + Scale arguments to be in range 1 < x <= 10 +*/ + +/* + scale = 0; + xs = x; + while ( xs > 10.0 ) { scale ++; xs=xs/10.0;} + while ( xs < 1.0 ) { scale --; xs=xs*10.0;} +*/ + xs = frexp(x,&scale); + xs = (1.0 * xs) - 1.0; + scale = scale - 0; + + i=2; + term=answer=xs; + + while (1) + { + work = i; + term = - (term * xs); + if ( answer == (answer + (term/work)) )break; + answer = answer + (term/work); + i++; + } + + answer = answer + (double)scale * _libc_ln2; + return(answer); +} + + +double log10(double x) +{ + return ( log(x) / _libc_ln10 ); +} + + +/* + + This code uses log and exp to calculate x to the power y. + If + +*/ + +double pow(double x,double y) +{ + int j,neg; + double yy,xx; + neg=0; + j=y; + yy=j; + if( yy == y) { + xx = x; + if ( y < 0 ){neg = 1; j = -j;} + if ( y == 0) return (1.0); + --j; + while(j>0){ xx=xx * x; j--;} + if(neg)xx=1.0/xx; + return (xx); + } + if (x < 0.0) + { + // TODO: Proper errno errno=EDOM; + return(0.0); + } + if (y == 0.0) return (1.0); + + return (exp(y*log(x))); +} diff --git a/memory.c b/memory.c new file mode 100644 index 0000000..9f7af96 --- /dev/null +++ b/memory.c @@ -0,0 +1,86 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include +#include +#include +#include +#include "elmalloc.h" +#include "internal.h" + +elmm_t _libc_mm; +elmm_sbrk_data_t _libc_sbrkdata; + +#define SIMGC_SYSMALLOC(x) elmm_malloc(&_libc_mm, x) +#define SIMGC_FREE(x) elmm_free(&_libc_mm, x) +#define SIMGC_IMPLEMENTATION +#include "simgc.h" + +simgc_t _libc_gc; +simgc_thread_t* _libc_gcmainthread; + +void _libc_malloc_init(void* stackptr) { + // Initialising the memory manager is currently a bit of work, but in + // theory should pay off as the memory manager code is easy to fine + // tune and adapt for more specialised allocators. + _libc_sbrkdata.func = (void*)(&sbrk); // Hooking it straight to the syscall should work, the userdata argument will be ignored + _libc_sbrkdata.max = 1024 * 1024 * 8; + _libc_sbrkdata.onlyChunk = NULL; + _libc_sbrkdata.udata = NULL; + _libc_mm.bigchunkMinimum = 1024 * 1024; + _libc_mm.bigchunkGranularity = 1024; + _libc_mm.initialised = false; + _libc_mm.lockFunction = NULL; + _libc_mm.bigchunkData = &_libc_sbrkdata; + _libc_mm.bigchunkFunction = &elmm_bigchunk_sbrk; + if (!elmm_init(&_libc_mm)) { + const char* err = "ERROR: Failed to initialise memory manager!\n"; + write(2, err, strlen(err)); + exit(-1); + } + if (!elmm_growheap(&_libc_mm, 1024*1024*4)) { + printf("Growheap failed.\n"); + } else { + printf("Growheap success!\n"); + } + simgc_init(&_libc_gc); + _libc_gcmainthread = simgc_begin_inner(&_libc_gc, stackptr); +} + +void _libc_malloc_finish() { + simgc_end_inner(_libc_gcmainthread); + simgc_reclaim(&_libc_gc); +} + +void* _libc_gcalloc(size_t sz) { + return simgc_alloc(&_libc_gc, sz); +} + +void* malloc(size_t sz) { + return elmm_malloc(&_libc_mm, sz); +} + +void free(void* mem) { + return elmm_free(&_libc_mm, mem); +} + +void* calloc(size_t n, size_t sz) { + /* TODO: check for overflow of n * sz? Probably not a problem IRL (may + * have been more of a thing back in the 16-bit era but I guess math + * overflow is not an important issue on 64-bit or even 32-bit memory + * allocator calls) + */ + return elmm_calloc(&_libc_mm, n, sz); +} + +void* realloc(void* mem, size_t sz) { + return elmm_realloc(&_libc_mm, mem, sz); +} + diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..70d892f --- /dev/null +++ b/misc.c @@ -0,0 +1,22 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include +#include +#include +#include "internal.h" + +char* ctime(const time_t* timevar) { + UNIMPLEMENTED(); +} + +time_t time(time_t* timevar) { + UNIMPLEMENTED(); +} diff --git a/simgc.h b/simgc.h new file mode 100644 index 0000000..3c2a75e --- /dev/null +++ b/simgc.h @@ -0,0 +1,438 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +// NOTE: This is entirely my code, except for the hashmap code being +// based on ChatGPT output. Don't worry, I'm using better AIs now, and +// will try to mark anywhere with AI generated code in case IP issues +// arise from them (or in case clients want pure AI-free code for policy +// reasons). Btw the AIs are pretty good at that kind of code +#ifndef SIMGC_H +#define SIMGC_H + +#include +#include +#include + +#ifndef SIMGC_SYSMALLOC +#include +#define SIMGC_SYSMALLOC malloc +#endif +#ifndef SIMGC_SYSFREE +#include +#define SIMGC_SYSFREE free +#endif +#ifndef SIMGC_LOCK_TYPE +#define SIMGC_LOCK_TYPE int +#define SIMGC_CREATELOCK(l) l = 1 +#define SIMGC_DELETELOCK(l) l = 0 +#define SIMGC_LOCK(l) l = 2 +#define SIMGC_UNLOCK(l) l = 1 +#endif + +#ifndef SIMGC_GRANULARITY +#define SIMGC_GRANULARITY 8 +#endif + +typedef struct simgc simgc_t; +typedef struct simgc_ptrset simgc_ptrset_t; +typedef struct simgc_ptrset_node simgc_ptrset_node_t; +typedef struct simgc_allocation simgc_allocation_t; +typedef struct simgc_thread simgc_thread_t; + +struct simgc { + SIMGC_LOCK_TYPE alloclock; // Locking is not used properly yet but partly implemented as an example + void* sdata; // Any data which may be needed by any complex SIMGC_SYSMALLOC/SIMGC_SYSFREE macros + void* udata; // Any data which may be associated with the heap by program or libraries + simgc_ptrset_t* pointers; + simgc_allocation_t* allocations; + simgc_thread_t* threads; +}; + +struct simgc_ptrset_node { + void* key; + simgc_ptrset_node_t* next; +}; + +struct simgc_ptrset { + simgc_t* gc; + simgc_ptrset_node_t** buckets; + size_t capacity; + size_t size; +}; + +struct simgc_allocation { + simgc_allocation_t* next; + size_t sizeflg; // Size/flags + // NOTE: The data will start immediately after this struct +}; + +struct simgc_thread { + void* sdata; // The system thread pointer, if applicable + void* udata; // Userdata, pointer to info assigned by the program or libraries + void* stackbase; // Base of stack (NOTE: may be numerically higher or lower than "top") + void* stacktop; // Top of stack (NOTE: may be numerically higher or lower than "top") + simgc_thread_t* next; // Always NULL for now (until I add locking etc.) + simgc_t* gc; + int iomode; +}; + +int simgc_init(simgc_t* gc); + +// simgc_beginio should be called before entering a blocking I/O or pause operation +void simgc_beginio(simgc_t* gc); +// simgc_endio should be called after exiting a blocking I/O or pause operation +void simgc_endio(simgc_t* gc); + +simgc_thread_t* simgc_begin_inner(simgc_t* gc, void* stackbase); +void simgc_end_inner(simgc_thread_t* ctx); +#define SIMGC_RUN(c,f) \ + do { \ + intptr_t dummyval = 123; \ + simgc_thread_t* ctx = simgc_begin_inner(c, &dummyval); \ + f; \ + simgc_end_inner(ctx); \ + } while(0) + +void* simgc_alloc(simgc_t* gc, size_t sz); + +// From ifndef SIMGC_H +#endif + +#ifdef SIMGC_IMPLEMENTATION +#ifndef SIMGC_IMPLEMENTATION_ALREADY +#define SIMGC_IMPLEMENTATION_ALREADY + +// stdio is only used for debugging +#include + +#define SIMGC_PTRSET_INITIAL_CAPACITY 16 + + +static uint32_t simgc_ptrset_hash(void* ptr) { + //return (uint32_t)(uintptr_t)ptr; + uintptr_t val = (uintptr_t)ptr; + return ((uint32_t)(val ^ (val >> 32))) & 0x7FFFFFFF; +} + +simgc_ptrset_t* simgc_ptrset_create(simgc_t* gc) { + simgc_ptrset_t* set = (simgc_ptrset_t*)SIMGC_SYSMALLOC(sizeof(simgc_ptrset_t)); + if (!set) { + return NULL; + } + + set->gc = gc; + set->capacity = SIMGC_PTRSET_INITIAL_CAPACITY; + set->size = 0; // TODO: No calloc! + set->buckets = (simgc_ptrset_node_t**)calloc(set->capacity, sizeof(simgc_ptrset_node_t*)); + if (!set->buckets) { + SIMGC_SYSFREE(set); + return NULL; + } + + return set; +} + +void simgc_ptrset_free(simgc_ptrset_t* set) { + simgc_t* gc = set->gc; + for (size_t i = 0; i < set->capacity; ++i) { + simgc_ptrset_node_t* node = set->buckets[i]; + while (node) { + simgc_ptrset_node_t* next = node->next; + SIMGC_SYSFREE(node); + node = next; + } + } + SIMGC_SYSFREE(set->buckets); + SIMGC_SYSFREE(set); +} + +static bool simgc_ptrset_resize(simgc_ptrset_t* set, size_t new_capacity) { + simgc_ptrset_node_t** new_buckets = (simgc_ptrset_node_t**)calloc(new_capacity, sizeof(simgc_ptrset_node_t*)); + if (!new_buckets) { + return false; + } + + for (size_t i = 0; i < set->capacity; ++i) { + simgc_ptrset_node_t* node = set->buckets[i]; + while (node) { + simgc_ptrset_node_t* next = node->next; + uint32_t hash = simgc_ptrset_hash(node->key); + size_t index = hash % new_capacity; + + node->next = new_buckets[index]; + new_buckets[index] = node; + node = next; + } + } + + SIMGC_SYSFREE(set->buckets); + set->buckets = new_buckets; + set->capacity = new_capacity; + + return true; +} + +bool simgc_ptrset_add(simgc_ptrset_t* set, void* ptr) { + if ((set->size + 1) * 2 > set->capacity) { + if (!simgc_ptrset_resize(set, set->capacity * 2)) { + return false; + } + } + + uint32_t hash = simgc_ptrset_hash(ptr); + size_t index = ((uint64_t)hash) % ((uint64_t)(set->capacity)); + simgc_ptrset_node_t* node = set->buckets[index]; + + while (node) { + if (node->key == ptr) { + return true; // Already in the set + } + node = node->next; + } + + simgc_ptrset_node_t* new_node = (simgc_ptrset_node_t*)SIMGC_SYSMALLOC(sizeof(simgc_ptrset_node_t)); + if (!new_node) { + return false; + } + + new_node->key = ptr; + new_node->next = set->buckets[index]; + set->buckets[index] = new_node; + set->size++; + + return true; +} + +bool simgc_ptrset_remove(simgc_ptrset_t* set, void* ptr) { + uint32_t hash = simgc_ptrset_hash(ptr); + size_t index = hash % set->capacity; + simgc_ptrset_node_t* node = set->buckets[index]; + simgc_ptrset_node_t* prev = NULL; + + while (node) { + if (node->key == ptr) { + if (prev) { + prev->next = node->next; + } else { + set->buckets[index] = node->next; + } + SIMGC_SYSFREE(node); + set->size--; + return true; + } + prev = node; + node = node->next; + } + + return false; +} + +bool simgc_ptrset_has(simgc_ptrset_t* set, void* ptr) { + //fprintf(stderr, "LOOKING FOR %p\n", ptr);fflush(stderr); + uint32_t hash = simgc_ptrset_hash(ptr); + //fprintf(stderr, "HASH %x CAPACITY %llu\n", hash, set->capacity);fflush(stderr); + size_t index = hash % set->capacity; + //fprintf(stderr, "INDEX %p\n", index);fflush(stderr); + simgc_ptrset_node_t* node = set->buckets[index]; + + while (node) { + //fprintf(stderr, "CHECKING %p\n", node);fflush(stderr); + if (node->key == ptr) { + //fprintf(stderr, "FOUND %p\n", ptr);fflush(stderr); + return true; + } + node = node->next; + } + + fprintf(stderr, "NOT FOUND %p\n", ptr);fflush(stderr); + return false; +} + +int simgc_init(simgc_t* gc) { + SIMGC_CREATELOCK(gc->alloclock); + gc->sdata = NULL; + gc->udata = NULL; + //gc->allocations = NULL; + gc->pointers = simgc_ptrset_create(gc); + gc->threads = NULL; +} + +simgc_thread_t* simgc_begin_inner(simgc_t* gc, void* stackbase) { + simgc_thread_t* t = SIMGC_SYSMALLOC(sizeof(simgc_thread_t)); + if (t == NULL) { + return NULL; + } + t->sdata = NULL; + t->udata = NULL; + t->stackbase = stackbase; + t->stacktop = NULL; + t->gc = gc; + t->iomode = 0; + + t->next = gc->threads; + gc->threads = t; + + return t; +} + +void simgc_end_inner(simgc_thread_t* ctx) { + if (ctx == NULL) { + return; + } + simgc_t* gc = ctx->gc; + simgc_thread_t* prev = NULL; + simgc_thread_t* thr = gc->threads; + while (thr != NULL) { + if (thr == ctx) { + if (prev == NULL) { + gc->threads = ctx->next; + } else { + prev->next = ctx->next; + } + SIMGC_SYSFREE(ctx); + return; + } + thr = thr->next; + } +} + +void* simgc_alloc(simgc_t* gc, size_t sz) { + if (gc == NULL) { + return NULL; + } + while ((sz & SIMGC_GRANULARITY) != 0) sz++; + simgc_allocation_t* alloc = SIMGC_SYSMALLOC(sizeof(simgc_allocation_t) + sz); + if (alloc == NULL) { + return NULL; + } + void* ptr = alloc+1; + alloc->sizeflg = sz; + alloc->next = gc->allocations; + gc->allocations = alloc; + + simgc_ptrset_add(gc->pointers, ptr); + return ptr; +} + +simgc_allocation_t* simgc_lookup(simgc_t* gc, void* ptr) { + if ((((uintptr_t)ptr) > 1000) && simgc_ptrset_has(gc->pointers, ptr)) { + return ptr-sizeof(simgc_allocation_t); + } else { + return NULL; + } +} + +int simgc_recmark(simgc_t* gc, void* ptr); +int simgc_recmark(simgc_t* gc, void* ptr) { + fprintf(stderr, "MARK %p\n", ptr);fflush(stderr); + int n = 0; + simgc_allocation_t* alloc = simgc_lookup(gc, ptr); + if (alloc != NULL) { + fprintf(stderr, "SPECULATIVE HIT %p\n", ptr);fflush(stderr); + } + if (alloc == NULL || alloc->sizeflg & 1) { + fprintf(stderr, "MISS %p\n", ptr);fflush(stderr); + return 0; + } + fprintf(stderr, "HIT %p\n", ptr);fflush(stderr); + n = 1; + alloc->sizeflg = alloc->sizeflg | 1; + size_t sz = (alloc->sizeflg >> 3) << 3; + size_t i; + for (i = 0; i < sz; i += sizeof(void*)) { + n += simgc_recmark(gc, *((void**)(ptr + i))); + } + return n; +} + +int simgc_markstack(simgc_t* gc, void* stackbase, void* stacktop) { + int n = 0; + // The stack generally grows top-down, but here we simplify it + if (((uintptr_t)stackbase) > ((uintptr_t)stacktop)) { + void* x = stacktop; + stacktop = stackbase; + stackbase = x; + } + while ((((uintptr_t)stackbase)%SIMGC_GRANULARITY) != 0) stackbase++; + while ((((uintptr_t)stacktop)%SIMGC_GRANULARITY) != 0) stacktop--; + void* p = stackbase; + while (((uintptr_t)p) < ((uintptr_t)stacktop)) { + n += simgc_recmark(gc, *((void**)p)); + p += sizeof(void*); + } + return n; +} + +int simgc_quicksweep(simgc_t* gc) { + int n = 0; + simgc_allocation_t* prev = NULL; + simgc_allocation_t* alloc = gc->allocations; + while (alloc != NULL) { + if (alloc->sizeflg & 1) { + alloc->sizeflg = (alloc->sizeflg >> 1) << 1; // Clear the "marked" bit + prev = alloc; + alloc = alloc->next; + } else { + simgc_allocation_t* next = alloc->next; + if (prev == NULL) { + gc->allocations = alloc->next; + } else { + prev->next = alloc->next; + } + simgc_ptrset_remove(gc->pointers, alloc); + SIMGC_SYSFREE(alloc); + alloc = next; + n++; + } + } + return n; +} + +simgc_thread_t* simgc_threadbystack(simgc_t* gc, void* stacktop) { + return gc->threads; +} + +void simgc_beginio(simgc_t* gc) { + simgc_thread_t* t; + t = simgc_threadbystack(gc, &t); + if (t == NULL) { + return; + } + t->iomode = 1; +} + +void simgc_endio(simgc_t* gc) { + simgc_thread_t* t; + t = simgc_threadbystack(gc, &t); + if (t == NULL) { + return; + } + t->iomode = 0; +} + +int simgc_reclaim(simgc_t* gc) { + intptr_t dummyval = 123; + void* stacktop = &dummyval; + simgc_thread_t* t = simgc_threadbystack(gc, stacktop); + if (t == NULL) { + return -1; + } + t->stacktop = stacktop; + int n = simgc_markstack(gc, t->stackbase, t->stacktop); + fprintf(stderr, "Marked %d\n", n); + int o = simgc_quicksweep(gc); + fprintf(stderr, "Swept %d\n", o); + return o; +} + +// From ifndef SIMGC_IMPLEMENTATION_ALREADY: +#endif +// From ifdef SIMGC_IMPLEMENTATION +#endif diff --git a/stdio.c b/stdio.c new file mode 100644 index 0000000..b0ddfa6 --- /dev/null +++ b/stdio.c @@ -0,0 +1,591 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +#include +#include +#include +#include +#include +#include "../kernel/fcntl.h" +#include "internal.h" + +FILE* stdin; +FILE* stdout; +FILE* stderr; + +// In the object-oriented version, we can just malloc from the class +// like it's a struct, as long as the class pointer is filled in! +#ifdef LIBC_OOP +#define FILE_internals _libc_FILE +#else +#define FILE_internals FILE +#endif + +FILE* _libc_openfd(int fd) { + FILE_internals* result = malloc(sizeof(FILE_internals)); + if (result) { +#ifdef LIBC_OOP + // Need to set the special class field as well as regular fields + #warning TODO class field +#else + // Nothing special to set, just the regular fields +#endif + result->fd = fd; + result->ungot = -1; + } + return (void*) result; +} + +void _libc_stdio_init() { + stdin = _libc_openfd(0); + stdout = _libc_openfd(1); + stderr = _libc_openfd(2); +} + +FILE* fopen(const char* name, const char* mode) { + int has_r = 0; + int has_w = 0; + int has_a = 0; + int has_b = 0; + int has_plus = 0; + + printf("open mode: \"%s\"\n", mode); + + for (char* m = mode; *m; m++) { + switch (*m) { + case 'r': + if (has_r || has_w || has_a) { + return NULL; // Invalid mode + } + has_r = 1; + break; + case 'w': + if (has_r || has_w || has_a) { + return NULL; // Invalid mode + } + has_w = 1; + break; + case 'a': + if (has_r || has_w || has_a) { + return NULL; // Invalid mode + } + has_a = 1; + break; + case 'b': + if (has_b) { + return NULL; // Invalid mode + } + has_b = 1; + break; + case '+': + if (has_plus) { + return NULL; // Invalid mode + } + has_plus = 1; + break; + default: + return NULL; // Invalid mode + } + } + + int o_mode = 0; + if (has_r) { + if (has_plus) { + o_mode |= O_RDWR; + } else { + o_mode = O_RDONLY; + } + } else if (has_w) { + o_mode = O_WRONLY; + o_mode |= O_CREATE; + if (has_plus) { + o_mode |= O_TRUNC; + } + } else if (has_a) { + o_mode = O_WRONLY; + o_mode = O_APPEND; + if (has_plus) { + o_mode |= O_CREATE; + } + } else { + FATAL("This should be unreachable."); + } + + FILE_internals* result = malloc(sizeof(FILE_internals)); + if (!result) { + //printf("Malloc failed\n"); + return NULL; + } + result->fd = open(name, o_mode); + if (result->fd <= 0) { + //printf("Open failed, used mode %d got %d\n", o_mode, result->fd); + free(result); + return NULL; + } + result->ungot = -1; + result->eof = 0; + + return (FILE*)result; +} + +FILE* freopen(const char* name, const char* mode, FILE* f) { + UNIMPLEMENTED(); +} + +int fclose(FILE* f) { + if (f == NULL) { + return -1; + } + close(((FILE_internals*)f)->fd); + free(f); + return 0; // TODO: Return -1 if it fails +} + +int fflush(FILE* f) { + return 0; // TODO: This function needs to be implemented for buffered streams +} + +int feof(FILE* f) { + if (f == NULL) { + return 1; + } + return f->eof; +} + +size_t fread(void* buffer, size_t size, size_t nmemb, FILE* f) { + // TODO: See note in calloc() about possibly checking size calculation + char* charbuff = buffer; + size_t realsize = size*nmemb; + if (realsize > 0 && ((FILE_internals*)f)->ungot != -1) { + *charbuff++ = (char) ((FILE_internals*)f)->ungot; + ((FILE_internals*)f)->ungot = -1; + realsize--; + } + size_t realresult = read(((FILE_internals*)f)->fd, charbuff, realsize); + if (realresult == realsize) { + return nmemb; + } else { + ((FILE_internals*)f)->eof = 1; + return realresult / size; + } +} + +size_t fwrite(void* buffer, size_t size, size_t nmemb, FILE* f) { + // TODO: See note in calloc() about possibly checking size calculation + size_t realsize = size*nmemb; + size_t realresult = write(((FILE_internals*)f)->fd, buffer, realsize); + if (realresult == realsize) { + return nmemb; + } else { + return realresult / size; + } +} + +int getc(FILE* f) { + unsigned char foo; + if (fread(&foo, 1, 1, f) == 1) { + return (int) foo; + } else { + return EOF; + } +} + +int fgetc(FILE* f) { + return getc(f); +} + +int ungetc(int c, FILE* f) { + if (((FILE_internals*)f)->ungot == -1) { + return EOF; + } else { + ((FILE_internals*)f)->ungot = (int)((unsigned char)c); + } +} + +int putc(int c, FILE* f) { + unsigned char chbuf = (unsigned char) c; + if (fwrite(&chbuf, 1, 1, f) != 1) { + return EOF; + } + return chbuf; +} + +int putchar(int c) { + return putc(c, stdout); +} + +int fputc(int c, FILE* f) { + return putc(c, f); +} + +char* fgets(char* str, int size, FILE* f) { + int i = 0; + int ch; + while (i < size-1) { + ch = getc(f); + //printf("Got character '%c'\n", ch); + if (ch == EOF) { + if (i == 0) { + return NULL; // This is important for a program to be able to detect EOF! + } + goto fgets_finished; + } + str[i++] = (char) ch; + if (ch == '\n') { + goto fgets_finished; + } + } + fgets_finished: // Write terminating zero at i and return + str[i] = 0; + return str; +} + +int fputs(const char* str, FILE* f) { + int n = strlen(str); + if (fwrite(str, 1, n, f) != n) { + return EOF; + } else { + return n; + } +} + +int _libc_dprintf_backend(const char* str, int n, void* udata) { + int fd = (int)((intptr_t)udata); + return write(fd, str, n); +} + +int _libc_fprintf_backend(const char* str, int n, void* udata) { + FILE* f = udata; + return fwrite(str, 1, n, f); +} + +int _libc_sprintf_backend(const char* str, int n, void* udata) { + char** strvar = udata; + char* s = *strvar; + for (int i = 0; i < n; i++) { + *s++ = str[i]; + } + *s = 0; + *strvar = s; + return n; +} + +struct _libc_snprintf_args { + char* str; + size_t i; + size_t sz; +}; + +int _libc_snprintf_backend(const char* str, int n, void* udata) { + struct _libc_snprintf_args* args = udata; + char* s = args->str; + + for (int i = 0; i < n; i++) { + if (args->i >= args->sz-1) { + return i; + } + args->str[args->i] = str[i]; + args->i++; + } + args->str[args->i+1] = 0; + return n; +} + +int _LIBC_PRINTF_CALLCONV printf(const char* fmt, ...) { + void* udata = (void*)((uintptr_t)1); // stdout + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(&_libc_dprintf_backend, udata, fmt, varargs); + va_end(varargs); + return result; +} +int _LIBC_PRINTF_CALLCONV sprintf(char *buf, const char* fmt, ...) { + void* udata = &buf; + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(&_libc_sprintf_backend, udata, fmt, varargs); + va_end(varargs); + return result; +} +int _LIBC_PRINTF_CALLCONV snprintf(char *buf, size_t n, const char* fmt, ...) { + struct _libc_snprintf_args args; + args.str = buf; + args.sz = n; + args.i = 0; + void* udata = &args; + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(&_libc_snprintf_backend, udata, fmt, varargs); + va_end(varargs); + return result; +} +int _LIBC_PRINTF_CALLCONV dprintf(int fd, const char* fmt, ...) { + void* udata = (void*)((uintptr_t)fd); + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(&_libc_dprintf_backend, udata, fmt, varargs); + va_end(varargs); + return result; +} + +int _LIBC_PRINTF_CALLCONV fprintf(FILE* f, const char* fmt, ...) { + void* udata = f; + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(&_libc_fprintf_backend, udata, fmt, varargs); + va_end(varargs); + return result; +} + +int _LIBC_PRINTF_CALLCONV vfprintf(FILE* f, const char* fmt, va_list list) { + void* udata = f; + uintptr_t result = _libc_vfnprintf(&_libc_fprintf_backend, udata, fmt, list); + return result; +} +int _LIBC_PRINTF_CALLCONV vsnprintf(char* buf, size_t n, const char* fmt, va_list list) { + struct _libc_snprintf_args args; + args.str = buf; + args.sz = n; + args.i = 0; + void* udata = &args; + uintptr_t result = _libc_vfnprintf(&_libc_snprintf_backend, udata, fmt, list); + return result; +} + +// These are non-standard, but the other *printf functions need to be implemented somehow, +// so fnprintf/vfnprintf just use a callback function for output of n bytes of string output. +int _LIBC_PRINTF_CALLCONV _libc_fnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, ...) { + va_list varargs; + va_start(varargs, fmt); + uintptr_t result = _libc_vfnprintf(fn, udata, fmt, varargs); + va_end(varargs); + return result; +} + +#define DUMPTOFN() \ + if (dayswithoutincident) { \ + if (fn(f-dayswithoutincident, dayswithoutincident, udata) != dayswithoutincident) { \ + return EOF; \ + } \ + written += dayswithoutincident; \ + dayswithoutincident = 0; \ + } + +int _LIBC_PRINTF_CALLCONV _libc_vfnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, va_list list) { + int dayswithoutincident = 0; + int written = 0; + const char* f = fmt; + int c; + while ((c = *f)) { + if (c == '%') { + DUMPTOFN(); + int has_decimals = 0; + int decimals = 0; + nextctrl: + int tc = *(f+1); + int longcount = 0; // for counting %ld/%lld + switch (tc) { + case '%': { + f++; + dayswithoutincident++; // Just let the second '%' be printed with the next string part + } break; + case '.': { + has_decimals = 1; + f++; + } goto nextctrl; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + decimals = decimals * 10 + (tc - '0'); + f++; + } goto nextctrl; + case 'f': { + char buf[20]; + int i = 20; + double fval = va_arg(list, double); + //fval = -543.210987654321; + long ival = (long) fval; + int isneg = ival < 0; + unsigned long uval = isneg ? -ival : ival; + do { + buf[--i] = (char)('0' + uval % 10); + uval /= 10; + } while (uval > 0); + if (ival < 0) { + buf[--i] = '-'; + } + int nwr = fn(buf+i, 20-i, udata); + if (nwr != 20-i) { + return EOF; + } + written += nwr; + if (!has_decimals) { + decimals = 6; + } + double x = fabs(fval - (double) ival); + if (fn(".", 1, udata) != 1) { + return EOF; + } + written++; + while (decimals > 0) { + x *= 10; + decimals--; + int ix = (int) x; + char xch = (char) ('0'+(ix%10)); + if (fn(&xch, 1, udata) != 1) { + return EOF; + } + written++; + } + f++; + } break; + case 'S': { // Formatted substring, I thought there was a standard % for it + char* s = va_arg(list, char*); + if (!s) { + s = "(null)"; + } + int len = strlen(s); + int nwr = _libc_vfnprintf(fn, udata, s, list); + if (nwr == EOF) { + return EOF; + } + written += nwr; + f++; + } break; + + case 's': { + char* s = va_arg(list, char*); + if (!s) { + s = "(null)"; + } + int len = strlen(s); + int nwr = fn(s, len, udata); + if (nwr != len) { + return EOF; + } + written += nwr; + f++; + } break; + + case 'c': { + int chr = va_arg(list, int); + char chrc = (char) chr; + if (fn(&chrc, 1, udata) != 1) { + return EOF; + } + written++; + f++; + } break; + + case 'l': // %ld/%lx is just handled as a special case of %d/%x + longcount++; + f++; + goto nextctrl; + + case 'u': // Unsigned is just handled as a special case of %d + case 'd': { + // TODO: Testing/edge case for negative maximum? + char buf[20]; + int i = 20; + if (longcount) { + long val = va_arg(list, long); + int isneg = ((tc == 'd') && (val < 0)); + unsigned long uval = isneg ? -val : val; + // Not actually needed, unless you want a full string: buf[i--] = '0'; + do { + buf[--i] = (char)('0' + uval % 10); + uval /= 10; + } while (uval > 0); + if (isneg) { + buf[--i] = '-'; + } + } else { + int val = va_arg(list, int); + int isneg = ((tc == 'd') && (val < 0)); + unsigned int uval = isneg ? -val : val; + // Not actually needed, unless you want a full string: buf[i--] = '0'; + do { + buf[--i] = (char)('0' + uval % 10); + uval /= 10; + } while (uval > 0); + if (isneg) { + buf[--i] = '-'; + } + } + int nwr = fn(buf+i, 20-i, udata); + if (nwr != 20-i) { + return EOF; + } + written += nwr; + f++; + } break; + + case 'p': // Pointer is treated as %lx + longcount++; + case 'x': + case 'X': { // Hex is handled a separate case to %d because the loop is slightly slower and sign is never used + const char* digits = (tc == 'x') ? "0123456789abcdef" : "0123456789ABCDEF"; + char buf[16]; // Size is easier to predict for hex, two characters of digits per byte + int i = 16; + if (longcount) { + unsigned long uval = va_arg(list, unsigned long); + // Not actually needed, unless you want a full string: buf[i--] = '0'; + do { + buf[--i] = digits[uval % 16]; + uval /= 16; + } while (uval > 0); + } else { + unsigned int uval = va_arg(list, unsigned int); + // Not actually needed, unless you want a full string: buf[i--] = '0'; + do { + buf[--i] = digits[uval % 16]; + uval /= 16; + } while (uval > 0); + } + int nwr = fn(buf+i, 16-i, udata); + if (nwr != 16-i) { + return EOF; + } + written += nwr; + f++; + } break; + + default: { // For now just print %x??? when 'x' is unknown, but this is probably unsafe and an error should be reported properly instead (TODO) + // Also skip the argument (assume all arguments are word-sized for now) + void* _ignored = va_arg(list, void*); + if (fn(f, 2, udata) != 2) { + return EOF; + } + if (fn("???", 3, udata) != 3) { + return EOF; + } + written+=5; + f++; + } + } + } else { + dayswithoutincident++; + } + f++; + } + DUMPTOFN(); + return written; +} + +void perror(const char* errormsg) { + UNIMPLEMENTED(); +} diff --git a/stdlib.c b/stdlib.c new file mode 100644 index 0000000..7411a9b --- /dev/null +++ b/stdlib.c @@ -0,0 +1,195 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. +#include +#include +#include "internal.h" + +/* DESIGN NOTE: At program entry the environment variables are just an + * array of "key=value" strings allocated on/near the stack by the + * kernel. When actively modifying environment variables the structure + * needs to be reallocated, so the system initially just uses the + * preallocated copy but keeps a _libc_env_reallocated flag to tell it + * if it's using a malloc'd copy so it can be freed properly. This means + * the system is optimised for programs that don't use environment + * variables much so it doesn't rely on memory allocation in those + * cases but may be slightly inefficient in cases where you do make + * heavy use of them (copying the whole table on every change instead of + * just using an optimised structure from the start). + */ +const char** _libc_env_variable; +int _libc_env_count; +int _libc_env_reallocated; + +void _libc_env_init(const char** envv) { + _libc_env_variable = envv; + _libc_env_count = 0; + _libc_env_reallocated = 0; + while (envv != NULL && envv[_libc_env_count] != NULL) { + _libc_env_count++; + } +} + +const char* _libc_env_match(const char* env, const char* key) { + int i; + for (i = 0; key[i] != 0; i++) { + if (env[i] != key[i]) { + return 0; // false, env doesn't match given key + } + } + if (env[i] != '=') { + return 0; // false, env key is longer than given key + } + return env+i+1; // true, env string starts with key followed by "=" +} + +char* getenv(const char* key) { + for (int i = 0; i < _libc_env_count; i++) { + const char* tmp; + if ((tmp = _libc_env_match(_libc_env_variable[i], key)) != NULL) { + return tmp; + } + } + return NULL; +} + +int setenv(const char* key, const char* value, int overwrite) { + int exists = -1; + int i; + for (i = 0; i < _libc_env_count; i++) { + const char* tmp; + if ((tmp = _libc_env_match(_libc_env_variable[i], key)) != NULL) { + if (overwrite) { + exists = i; + break; + } else { + return 0; + } + } + } + int newsize; + newsize = _libc_env_count + (exists >= 0 ? 1 : 0); + char* newentry = malloc(strlen(key)+strlen(value)+2); + sprintf(newentry, "%s=%s", key, value); + const char** oldptr = _libc_env_variable; + const char** newptr = malloc(newsize * sizeof(char*)); + for (i = 0; i < _libc_env_count; i++) { + const char* tmp; + if (i == exists) { + newptr[i] = newentry; + } else { + newptr[i] = strdup(oldptr[i]); + } + if (_libc_env_reallocated) { + free(oldptr[i]); + } + } + if (i < newsize) { + newptr[i] = newentry; + } + _libc_env_variable = newptr; + if (_libc_env_reallocated) { + free(oldptr); + } + _libc_env_reallocated = 1; + return 0; +} + +int unsetenv(const char* key) { + int exists = -1; + int i; + for (i = 0; i < _libc_env_count; i++) { + const char* tmp; + if ((tmp = _libc_env_match(_libc_env_variable[i], key)) != NULL) { + exists = i; + break; + } + } + if (exists < 0) { + return 0; // Nothing to do, env var already doesn't exist + } + int newsize = _libc_env_count - 1; + const char** oldptr = _libc_env_variable; + const char** newptr = malloc(newsize * sizeof(char*)); + for (i = 0; i < _libc_env_count; i++) { + const char* tmp; + if (i >= exists) { + newptr[i] = strdup(oldptr[i-1]); + } else { + newptr[i] = strdup(oldptr[i]); + } + if (_libc_env_reallocated) { + free(oldptr[i]); + } + } + _libc_env_variable = newptr; + if (_libc_env_reallocated) { + free(oldptr); + } + _libc_env_reallocated = 1; + return 0; +} + +int system(const char* cmd) { + UNIMPLEMENTED(); +} + +void abort() { + UNIMPLEMENTED(); +} + +double atof(const char* str) { + UNIMPLEMENTED(); +} + +int _libc_charnum(char c, int base) { + int n; + if (c >= '0' && c <= '9') { + n = c - '0'; + } else if (c >= 'A' && c <= 'F') { + n = 10 + c - 'A'; + } else if (c >= 'a' && c <= 'f') { + n = 10 + c - 'a'; + } else { + return -1; + } + if (n < base) { + return n; + } else { + return -1; + } +} + +long strtol(const char* str, char**endvar, int base) { + UNIMPLEMENTED(); +} + +long long strtoll(const char* str, char**endvar, int base) { + UNIMPLEMENTED(); +} + +unsigned long strtoul(const char* str, char**endvar, int base) { + return strtoull(str, endvar, base); +} + +unsigned long long strtoull(const char* str, char**endvar, int base) { + UNIMPLEMENTED(); +} + +float strtof(const char* str, char**endvar) { + UNIMPLEMENTED(); +} + +double strtod(const char* str, char**endvar) { + UNIMPLEMENTED(); +} + +long double strtold(const char* str, char**endvar) { + UNIMPLEMENTED(); +} diff --git a/string.c b/string.c new file mode 100644 index 0000000..67ed295 --- /dev/null +++ b/string.c @@ -0,0 +1,399 @@ +// Copyright (c) 2025, Zak Fenton +// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this +// software according to the terms and conditions of the Mulan PSL v2. +// You may obtain a copy of Mulan PSL v2 at: +// http://license.coscl.org.cn/MulanPSL2 +// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of +// any kind, either express or implied, including but not limited to +// non-infringement, merchantability or fit for a particular purpose. +// See the Mulan PSL v2 for more details. + +// For auditing purposes, this is NEW CODE + +#ifdef KERNEL_MODE +#define size_t long +#define NULL ((void*)0ULL) +#else +#include +#include "internal.h" +#endif + + +//ifdef _ZCC +#ifdef NOT_USED_YET +/* Works like memset except assumes n is a multiple of 8 and src>=dst+8. + * Doesn't return a useful value. + */ +void __naked memset_quick64(void* dst, uint64 v, uint64 n) __asm { + add a3, a0, a2 + .setloop: + sd a1, 0(a0) + addi a0, a0, 8 + bne a0, a3, .setloop + ret +} +void __naked memset_quick(void* dst, uint64 v, uint64 n) __asm { + add a3, a0, a2 + .setloopa: + sb a1, 0(a0) + addi a0, a0, 1 + bne a0, a3, .setloopa + ret +} +void* __naked memset_opt(void* dst, uint64 v, uint64 n) __asm { + add a3, a0, a2 + add a4, a0, zero + beq a2, zero, .endopt + addi a6, zero, 16 + blt a2, a6, .setloopoptshort + addi a6, zero, 128 + blt a6, a2, .setloopoptlong + .setloopopt: + sb a1, 0(a4) + addi a4, a4, 1 + andi a5, a4, 7 + beq a5, zero, .setloopopt64 + .setloopstartopt: + bne a4, a3, .setloopopt + .endopt: + ret + .setloopoptshort: + sb a1, 0(a4) + addi a4, a4, 1 + bne a4, a3, .setloopoptshort + ret + .setloopopt64adjust: + addi a3, a3, -1 + sb a1, 0(a3) + .setloopopt64: + bne a1, zero, .setloopstartopt + andi a6, a3, 7 + bne a6, zero, .setloopopt64adjust + //addi a6, zero, 8 + .setloopopt64b: + //sub a5, a3, a4 + //blt a5, a6, .setloopstartopt + sd a1, 0(a4) + addi a4, a4, 8 + blt a4, a3, .setloopopt64b + //j .setloopstartopt + ret + .setloopoptlong: + bne a1, zero, .setloopstartopt + andi a6, a2, 127 + bne a6, zero, .setloopstartopt + .setloopoptlongb: + sd zero, 0(a4) + sd zero, 8(a4) + sd zero, 16(a4) + sd zero, 24(a4) + sd zero, 32(a4) + sd zero, 40(a4) + sd zero, 48(a4) + sd zero, 56(a4) + sd zero, 64(a4) + sd zero, 72(a4) + sd zero, 80(a4) + sd zero, 88(a4) + sd zero, 96(a4) + sd zero, 104(a4) + sd zero, 112(a4) + sd zero, 120(a4) + addi a4, a4, 128 + bne a4, a3, .setloopoptlongb + ret +} +void* __naked memcpy_opt(void* dst, void* src, uint64 n) __asm { + add a3, a0, a2 + add a4, a0, zero + beq a2, zero, .endcpy + addi a6, zero, 16 + blt a2, a6, .cpyloopoptshort + addi a6, zero, 128 + blt a6, a2, .cpyloopoptlong + .cpyloopopt: + lb a6, 0(a1) + sb a6, 0(a4) + addi a4, a4, 1 + addi a1, a1, 1 + andi a5, a4, 7 + beq a5, zero, .cpyloopopt64 + .cpyloopstartopt: + bne a4, a3, .cpyloopopt + .endcpy: + ret + .cpyloopoptshort: + lb a6, 0(a1) + sb a6, 0(a4) + addi a4, a4, 1 + addi a1, a1, 1 + bne a4, a3, .cpyloopoptshort + ret + .cpyloopopt64adjust: + addi a3, a3, -1 + sub a5, a3, a4 + add a5, a5, a1 + lb a6, 0(a5) + sb a6, 0(a3) + .cpyloopopt64: + andi a6, a3, 7 + bne a6, zero, .cpyloopopt64adjust + //addi a6, zero, 8 + .cpyloopopt64b: + //sub a5, a3, a4 + //blt a5, a6, .setloopstartopt + ld a6, 0(a1) + sd a6, 0(a4) + addi a4, a4, 8 + addi a1, a1, 8 + blt a4, a3, .cpyloopopt64b + //j .setloopstartopt + ret + .cpyloopoptlong: + andi a6, a2, 127 + bne a6, zero, .cpyloopstartopt + .cpyloopoptlongb: + ld a6, 0(a1) + sd a6, 0(a4) + ld a6, 8(a1) + sd a6, 8(a4) + ld a6, 16(a1) + sd a6, 16(a4) + ld a6, 24(a1) + sd a6, 24(a4) + ld a6, 32(a1) + sd a6, 32(a4) + ld a6, 40(a1) + sd a6, 40(a4) + ld a6, 48(a1) + sd a6, 48(a4) + ld a6, 56(a1) + sd a6, 56(a4) + ld a6, 64(a1) + sd a6, 64(a4) + ld a6, 72(a1) + sd a6, 72(a4) + ld a6, 80(a1) + sd a6, 80(a4) + ld a6, 88(a1) + sd a6, 88(a4) + ld a6, 96(a1) + sd a6, 96(a4) + ld a6, 104(a1) + sd a6, 104(a4) + ld a6, 112(a1) + sd a6, 112(a4) + ld a6, 120(a1) + sd a6, 120(a4) + addi a4, a4, 128 + addi a1, a1, 128 + bne a4, a3, .cpyloopoptlongb + ret +} +// Works like memcpy except assumes n is a multiple of 8 and src>=dst+8. +// Doesn't return a useful value. +void __naked memcpy_quick64(void* dst, void* src, uint64 n) __asm { + add a3, a0, a2 + .cpyloop: + ld a4, 0(a1) + addi a1, a1, 8 + sd a4, 0(a0) + addi a0, a0, 8 + bne a0, a3, .cpyloop + ret +} +// Expands a byte to a word. +uint64 __naked expandbyte_quick64(int b) __asm { + andi a1, a0, 255 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + slli a0, a0, 8 + or a0, a0, a1 + ret +} +#endif + +// Copied from my preexisting number parser in numbered.c +unsigned long long _libc_scandec(const char** strvar) { + unsigned long long val = 0; + const char* s = *strvar; + char c; + while ((c = *s) >= '0' && c <= '9') { + unsigned long long oldval = val; + val = val * 10 + c - '0'; + if (val / 10 != oldval) { + return 0; + } + s++; + } + + *strvar = s; + return val; +} + +long long atoll(const char* str) { + if (*str == '-') { + str++; + long long x = (long long) _libc_scandec(&str); + return -x; + } else if (*str == '+') { + str++; + return (long long) _libc_scandec(&str); + } else { + return (long long) _libc_scandec(&str); + } +} + +long atol(const char* str) { + return (long) atoll(str); +} + +int atoi(const char* str) { + return (int) atoll(str); +} + +size_t strlen(const char* foo) { + if (foo == NULL) { + return 0; + } else { + size_t i = 0; + while (foo[i] != 0) i++; + return i; + } +} + +void* memcpy(void* dst, const void* src, size_t nbytes) { + char* cdst = (char*) dst; + const char* csrc = (char*) src; + size_t i = 0; + for (i = 0; i < nbytes; i++) { + cdst[i] = csrc[i]; + } + return dst; +} + +void* memset(void* mem, int byt, size_t nbytes) { + char* tmp = mem; + for (size_t i = 0; i < nbytes; i++) { + tmp[i] = (char) byt; + } + return mem; +} + +int strcmp(const char* a, const char* b) { + while (*a == *b) { + if (*a == 0) { + return 0; // Got to the end, strings are equal + } + a++; + b++; + } + return *a - *b; +} + +char* strcat(char* dst, const char* src) { + char* end = dst; + while (*end) { // Find the terminating zero of the dst string + end++; + } + while (*src) { // Overwrite the end of dst from src until it's terminating zero + *end++ = *src++; + } + *end = 0; // Add terminating zero back to dst + return dst; // Return the start of the dst string +} + +char* strcpy(char *dst, const char* src) { + char *d = dst; + while (*src) { + *d++ = *src++; + } + *d = 0; + return dst; +} + +char* strncpy(char* dst, const char* src, size_t n) { + size_t i = 0; + char *d = dst; + while (*src) { + *d++ = *src++; + i++; + } + while (i < n) { // TODO: Is this right?? + dst[i] = 0; + i++; + } + return dst; +} + +#ifndef KERNEL_MODE +char* strdup(const char* src) { + char* result = malloc(strlen(src) + 1); + if (result) { + strcpy(result, src); + } + return result; +} + +char* strndup(const char* str, size_t n) { + size_t resultlen = strlen(str); + if (resultlen > n) { + resultlen = n; + } + char* result = malloc(resultlen + 1); + if (result) { + for (size_t i = 0; i < resultlen; i++) { + result[i] = str[i]; + } + str[resultlen] = 0; + } + return result; +} +#endif + +const char* strpbrk(const char* str, const char* search) { + char c; + while ((c = *str)) { + const char* s = search; + while (*s++) { + if (c == *s) { + return str; + } + } + str++; + } + return NULL; +} + +char *strchr(const char* str, int chr) { + int c; + while ((c = *str)) { + if (c == chr) { + return (char*) str; + } + str++; + } + return NULL; +} + +char *strrchr(const char* str, int chr) { + int c; + char* result = NULL; + while ((c = *str)) { + if (c == chr) { + result = (char*) str; + } + str++; + } + return result; +}