From 1ed9e8eeab100f1c60332ed1242a0697484e6b37 Mon Sep 17 00:00:00 2001 From: Zak Yani Star Fenton Date: Wed, 4 Jun 2025 01:22:53 +1000 Subject: [PATCH] Added libc code --- README.md | 8 +- ctype.c | 113 +++++ elf.h | 370 ++++++++++++++++ elmalloc.h | 1001 +++++++++++++++++++++++++++++++++++++++++++ include.mk | 13 + include/assert.h | 17 + include/ctype.h | 27 ++ include/dirent.h | 9 + include/errno.h | 30 ++ include/fcntl.h | 9 + include/float.h | 17 + include/limits.h | 18 + include/math.h | 21 + include/memory.h | 15 + include/pwd.h | 9 + include/setjmp.h | 28 ++ include/signal.h | 9 + include/stdarg.h | 45 ++ include/stdbool.h | 20 + include/stddef.h | 23 + include/stdint.h | 36 ++ include/stdio.h | 120 ++++++ include/stdlib.h | 50 +++ include/string.h | 33 ++ include/sys/event.h | 48 +++ include/syscalls.h | 23 + include/time.h | 40 ++ include/unistd.h | 18 + include/utime.h | 9 + internal.c | 65 +++ internal.h | 34 ++ link.ld | 25 ++ math.c | 216 ++++++++++ memory.c | 86 ++++ misc.c | 22 + simgc.h | 438 +++++++++++++++++++ stdio.c | 591 +++++++++++++++++++++++++ stdlib.c | 195 +++++++++ string.c | 399 +++++++++++++++++ 39 files changed, 4245 insertions(+), 5 deletions(-) create mode 100644 ctype.c create mode 100644 elf.h create mode 100644 elmalloc.h create mode 100644 include.mk create mode 100644 include/assert.h create mode 100644 include/ctype.h create mode 100644 include/dirent.h create mode 100644 include/errno.h create mode 100644 include/fcntl.h create mode 100644 include/float.h create mode 100644 include/limits.h create mode 100644 include/math.h create mode 100644 include/memory.h create mode 100644 include/pwd.h create mode 100644 include/setjmp.h create mode 100644 include/signal.h create mode 100644 include/stdarg.h create mode 100644 include/stdbool.h create mode 100644 include/stddef.h create mode 100644 include/stdint.h create mode 100644 include/stdio.h create mode 100644 include/stdlib.h create mode 100644 include/string.h create mode 100644 include/sys/event.h create mode 100644 include/syscalls.h create mode 100644 include/time.h create mode 100644 include/unistd.h create mode 100644 include/utime.h create mode 100644 internal.c create mode 100644 internal.h create mode 100644 link.ld create mode 100644 math.c create mode 100644 memory.c create mode 100644 misc.c create mode 100644 simgc.h create mode 100644 stdio.c create mode 100644 stdlib.c create mode 100644 string.c 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; +}