Compare commits

..

14 Commits

Author SHA1 Message Date
7afc3b0637 allocator prototypes 2026-01-15 17:26:18 -05:00
cbc8d0a7c5 add rules to build allocator lib 2026-01-15 17:23:15 -05:00
3e346312e2 add standard header 2026-01-15 17:22:38 -05:00
1b980b2720 I don't feel like implementing a brk()/sbrk() vmm, vm_posix.c will be mmap based 2026-01-15 16:44:33 -05:00
2c51ab5d78 malloc shim 2026-01-15 16:43:51 -05:00
cbd01e7a47 xalloc impl...maybe. needs testing. 2026-01-15 13:36:56 -05:00
30273486f1 clean up readme 2026-01-15 12:51:44 -05:00
eab45f3f58 x-alloc class function prototypes 2026-01-15 12:51:29 -05:00
507c274887 added prototype for basic oops handler function 2026-01-15 12:51:08 -05:00
b0db2effc7 library is now C++ compatible :^) 2026-01-15 10:22:01 -05:00
11f1a7e7c3 add header guards 2026-01-15 10:14:32 -05:00
d078546eab prototypes for malloc impl 2026-01-15 10:14:05 -05:00
e6e4cdd961 type stuff 2026-01-14 21:27:12 -05:00
4db7dfeb5d ensure links only belong to 1 list
also some small documentation changes.
2026-01-14 10:41:54 -05:00
31 changed files with 768 additions and 65 deletions

View File

@@ -1,4 +1,6 @@
### Core “make C nicer” utilities # Nickel lib overview/roadmap
## Core “make C nicer” utilities
1. **`xmalloc/xcalloc/xrealloc/xstrdup` (header: `xalloc.h`)** 1. **`xmalloc/xcalloc/xrealloc/xstrdup` (header: `xalloc.h`)**
@@ -28,89 +30,93 @@
* `bitset_set/clear/test`, `bitset_find_first_zero` * `bitset_set/clear/test`, `bitset_find_first_zero`
* Teaches: word operations, popcount/ctz if you want. * Teaches: word operations, popcount/ctz if you want.
### C11 correctness features 7. **Intrusive double linked list (header: `linkedlist.h`)**
7. **Compile-time assertions & type checks (header: `ctassert.h`)** * An implementation of a double linked list using a sentinel pattern
## C11 correctness features
1. **Compile-time assertions & type checks (header: `ctassert.h`)**
* Wrap `_Static_assert`, plus some `_Generic` helpers. * Wrap `_Static_assert`, plus some `_Generic` helpers.
* Teaches: compile-time constraints, “making invalid states unrepresentable”. * Teaches: compile-time constraints, “making invalid states unrepresentable”.
8. **`_Generic` logging macros (header: `log.h`)** 2. **`_Generic` logging macros (header: `log.h`)**
* `LOG_INFO("x=%d", x)` with file/line, optional type-based formatting helpers. * `LOG_INFO("x=%d", x)` with file/line, optional type-based formatting helpers.
* Teaches: variadic macros, `_Generic`, ergonomics. * Teaches: variadic macros, `_Generic`, ergonomics.
9. **Defer/cleanup pattern (header: `defer.h`)** 3. **Defer/cleanup pattern (header: `defer.h`)**
* Macro that runs cleanup at scope end (via `goto` pattern). * Macro that runs cleanup at scope end (via `goto` pattern).
* Teaches: structured cleanup in C, error paths. * Teaches: structured cleanup in C, error paths.
10. **Optional result type (header: `result.h`)** 4. **Optional result type (header: `result.h`)**
* `typedef struct { int ok; int err; T value; } result_T;` pattern. * `typedef struct { int ok; int err; T value; } result_T;` pattern.
* Teaches: explicit error handling, no hidden global state. * Teaches: explicit error handling, no hidden global state.
### Concurrency & atomics (C11s “real” new power) ## Concurrency & atomics
11. **Atomic reference counting (header: `refcnt.h`)** 1. **Atomic reference counting (header: `refcnt.h`)**
* `ref_inc/ref_dec` with destructor callback. * `ref_inc/ref_dec` with destructor callback.
* Teaches: `stdatomic.h`, memory ordering (start with seq_cst, then learn relax/acq_rel). * Teaches: `stdatomic.h`, memory ordering (start with seq_cst, then learn relax/acq_rel).
12. **Lock-free SPSC ring buffer (header: `ring_spsc.h`)** 2. **Lock-free SPSC ring buffer (header: `ring_spsc.h`)**
* Single-producer/single-consumer queue. * Single-producer/single-consumer queue.
* Teaches: atomics, cache friendliness, correctness reasoning. * Teaches: atomics, cache friendliness, correctness reasoning.
13. **Thread pool (header: `threadpool.h`)** using `threads.h` 3. **Thread pool (header: `threadpool.h`)** using `threads.h`
* `tp_init`, `tp_submit`, `tp_join`, `tp_destroy` * `tp_init`, `tp_submit`, `tp_join`, `tp_destroy`
* Teaches: `thrd_t`, `mtx_t`, `cnd_t`, work queues. * Teaches: `thrd_t`, `mtx_t`, `cnd_t`, work queues.
14. **Once-init & singletons (header: `once.h`)** 4. **Once-init & singletons (header: `once.h`)**
* Use `once_flag` / `call_once` (or implement if platform lacks). * Use `once_flag` / `call_once` (or implement if platform lacks).
* Teaches: init races, safe global setup. * Teaches: init races, safe global setup.
### Parsing / CLI tools (youll actually use these) ## Parsing / CLI tools
15. **Argument parser (header: `argparse.h`)** 1. **Argument parser (header: `argparse.h`)**
* `--long`, `-s`, combined short flags, `--key=value` * `--long`, `-s`, combined short flags, `--key=value`
* Teaches: string parsing, API design, test cases. * Teaches: string parsing, API design, test cases.
16. **INI parser (header: `ini.h`)** 2. **INI parser (header: `ini.h`)**
* Minimal: sections, key=value, comments. * Minimal: sections, key=value, comments.
* Teaches: parsing state machines, callbacks. * Teaches: parsing state machines, callbacks.
17. **CSV reader/writer (header: `csv.h`)** 3. **CSV reader/writer (header: `csv.h`)**
* Correct quoting/escaping. * Correct quoting/escaping.
* Teaches: edge cases, streaming parsing. * Teaches: edge cases, streaming parsing.
18. **Path utilities (header: `path.h`)** 4. **Path utilities (header: `path.h`)**
* `path_join`, `path_dirname`, `path_basename`, normalize `..` and `.` * `path_join`, `path_dirname`, `path_basename`, normalize `..` and `.`
* Teaches: portability pitfalls, careful string ops. * Teaches: portability pitfalls, careful string ops.
### Systems-ish building blocks ## Systems-ish building blocks
19. **File mapping / buffered reader (header: `io.h`)** 1. **File mapping / buffered reader (header: `io.h`)**
* Portable-ish wrapper for “read whole file”, “iter lines”, “atomic write via temp+rename”. * Portable-ish wrapper for “read whole file”, “iter lines”, “atomic write via temp+rename”.
* Teaches: robust file I/O patterns, error handling. * Teaches: robust file I/O patterns, error handling.
20. **Timing + profiling helpers (header: `timeutil.h`)** 2. **Timing + profiling helpers (header: `timeutil.h`)**
* `now_ns()`, `stopwatch`, `scope_timer` macro * `now_ns()`, `stopwatch`, `scope_timer` macro
* Teaches: `timespec_get`, measurement pitfalls, microbenchmark hygiene. * Teaches: `timespec_get`, measurement pitfalls, microbenchmark hygiene.
--- ---
## A suggested “crash course” order (so it builds) ## suggested order
13 (xalloc/arena/buf) → 710 (static assert, generic, cleanup, results) → 15 (argparse) → 19 (io) → 1618 (parsers) → 1114 (atomics/threads) → 56 (hashmap/bitset) → 20 (timing) → 12/13 (ring + threadpool, as capstone) xalloc/arena/buf -> static assert, generic, cleanup, results -> argparse -> io -> parsers -> atomics/threads -> hashmap/bitset-> timing -> ring + threadpool, as capstone
--- ---

135
include/ni_alloc/alloc.h Normal file
View File

@@ -0,0 +1,135 @@
#ifndef __NICKEL_ALLOC_H
#define __NICKEL_ALLOC_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Nickel Allocator API (explicit; does NOT replace libc malloc unless you opt-in).
Design goal: easy to use in tools, easy to test, portable across Linux/macOS.
*/
//#define ENABLE_NICKEL_ALLOC_SHIM
typedef struct ni_alloc_stats {
/* current usage */
size_t bytes_in_use; /* user-visible allocated bytes (approx if desired) */
size_t bytes_reserved; /* backing store reserved from VM */
size_t bytes_peak; /* peak in-use */
size_t bytes_reserved_peak;
/* counters */
uint64_t alloc_calls;
uint64_t free_calls;
uint64_t realloc_calls;
uint64_t calloc_calls;
uint64_t memalign_calls;
/* failures */
uint64_t oom_count;
} ni_alloc_stats;
/* Allocator configuration knobs (keep minimal at first; extend later). */
typedef struct ni_alloc_config {
/* If true, ni_free(NULL) is allowed and a no-op (recommended true). */
bool free_null_is_noop;
/* If true, ni_realloc(p,0) behaves like free(p) and returns NULL. */
bool realloc_zero_frees;
/* If true, scribble freed blocks with a pattern (debug build feature). */
bool scribble_free;
/* If true, scribble newly allocated blocks with a pattern (debug). */
bool scribble_alloc;
/* Optional alignment for default allocations (usually alignof(max_align_t)). */
size_t default_alignment;
/* Optional: maximum small-bin size, arena chunk size, etc. */
size_t arena_chunk_bytes;
} ni_alloc_config;
/* Opaque allocator instance.
You can use the global allocator, or create a private allocator instance
(handy for tests / subsystems).
*/
typedef struct ni_allocator ni_allocator;
/* -------- Global allocator API -------- */
/* Initialize/shutdown global allocator (optional; can be lazy).
Returns false if initialization fails.
*/
bool ni_alloc_global_init(const ni_alloc_config* cfg);
void ni_alloc_global_shutdown(void);
/* Allocation functions using the global allocator. */
void* ni_malloc(size_t size);
void ni_free(void* ptr);
void* ni_realloc(void* ptr, size_t new_size);
void* ni_calloc(size_t count, size_t elem_size);
/* Alignment:
- alignment must be power of two and >= sizeof(void*)
- returns NULL on failure
*/
void* ni_memalign(size_t alignment, size_t size);
/* Equivalent of C11 aligned_alloc semantics:
- alignment must be power of two
- size must be a multiple of alignment
*/
void* ni_aligned_alloc(size_t alignment, size_t size);
/* Query/reset stats for global allocator. */
void ni_alloc_get_stats(ni_alloc_stats* out_stats);
void ni_alloc_reset_stats(void);
/* -------- Instance allocator API -------- */
ni_allocator* ni_allocator_create(const ni_alloc_config* cfg);
void ni_allocator_destroy(ni_allocator* a);
void* ni_allocator_malloc(ni_allocator* a, size_t size);
void ni_allocator_free(ni_allocator* a, void* ptr);
void* ni_allocator_realloc(ni_allocator* a, void* ptr, size_t new_size);
void* ni_allocator_calloc(ni_allocator* a, size_t count, size_t elem_size);
void* ni_allocator_memalign(ni_allocator* a, size_t alignment, size_t size);
void ni_allocator_get_stats(ni_allocator* a, ni_alloc_stats* out_stats);
void ni_allocator_reset_stats(ni_allocator* a);
/* -------- Utilities / validation -------- */
/* Returns true if n is a power of two (and nonzero). */
bool ni_is_pow2_size(size_t n);
/* Round up x to next multiple of align (align must be power of two). */
size_t ni_align_up(size_t x, size_t align);
/* Optional: verify internal invariants in debug builds.
- returns false if corruption detected
*/
bool ni_alloc_validate(void);
#ifdef ENABLE_NICKEL_ALLOC_SHIM
/* Allocation functions using the global allocator.
define symbols pointing at our versions
*/
void* malloc(size_t size) { ni_malloc(size); }
void free(void* ptr) { ni_free(ptr); }
void* realloc(void* ptr, size_t new_size) { ni_realloc(ptr, new_size); }
void* calloc(size_t count, size_t elem_size) { ni_calloc(count, elem_size); }
#endif
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_ALLOC_H */

59
include/ni_alloc/vm.h Normal file
View File

@@ -0,0 +1,59 @@
#ifndef __NICKEL_VM_H
#define __NICKEL_VM_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* VM layer: portable-ish page allocation based on mmap/munmap (Linux + macOS). */
typedef struct ni_vm_stats {
size_t page_size;
/* cumulative counters */
uint64_t reserves; /* number of reserve calls */
uint64_t releases; /* number of release calls */
uint64_t commits; /* if supported by impl (optional), else 0 */
uint64_t decommits; /* if supported by impl (optional), else 0 */
size_t bytes_reserved; /* current bytes reserved */
size_t bytes_peak; /* peak reserved */
} ni_vm_stats;
/* Returns OS page size (usually sysconf(_SC_PAGESIZE)). Never returns 0. */
size_t ni_vm_page_size(void);
/* Reserve anonymous virtual memory.
- bytes will be rounded up to a page multiple.
- returns NULL on failure.
*/
void* ni_vm_reserve(size_t bytes);
/* Release a region previously returned by ni_vm_reserve (page-multiple size). */
bool ni_vm_release(void* addr, size_t bytes);
/* Optional: “commit/decommit” for platforms where its meaningful.
On many Unix systems, reserve implies commit; implementations may no-op.
*/
bool ni_vm_commit(void* addr, size_t bytes);
bool ni_vm_decommit(void* addr, size_t bytes);
/* Optional: set guard pages (mprotect) for debug.
Returns false if unsupported or fails.
*/
bool ni_vm_protect_none(void* addr, size_t bytes);
bool ni_vm_protect_rw(void* addr, size_t bytes);
/* Stats are optional but very useful for tests/tools. */
void ni_vm_get_stats(ni_vm_stats* out_stats);
void ni_vm_reset_stats(void);
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_VM_H */

29
include/nickel.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef __NICKEL_H_
#define __NICKEL_H_
/* common includes across the project */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
/* includes from internal to the project */
// ...
#ifdef __cplusplus
extern "C" {
#endif
// any thing we need in the top level here
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_ARENA_H_
#define __NICKEL_ARENA_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_ARENA_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_ARGPARSE_H_
#define __NICKEL_ARGPARSE_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_ARGPARSE_H_ */

14
include/nickel/bitman.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_BITMAN_H_
#define __NICKEL_BITMAN_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_BITMAN_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_BUF_H_
#define __NICKEL_BUF_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_BUF_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_CTASSERT_H_
#define __NICKEL_CTASSERT_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_CTASSERT_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_DEFER_H_
#define __NICKEL_DEFER_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_DEFER_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_HASHMAP_H_
#define __NICKEL_HASHMAP_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_HASHMAP_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_INI_H_
#define __NICKEL_INI_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_INI_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_IO_H_
#define __NICKEL_IO_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_IO_H_ */

View File

@@ -1,10 +1,11 @@
#ifndef __NICKEL_LINKEDLIST_H_ #ifndef __NICKEL_LINKEDLIST_H_
#define __NICKEL_LINKEDLIST_H_ #define __NICKEL_LINKEDLIST_H_
#include <stdlib.h> #include "nickel.h"
#include <stddef.h>
#include <stdint.h> #ifdef __cplusplus
#include <stdbool.h> extern "C" {
#endif
typedef struct ni_list_node { typedef struct ni_list_node {
struct ni_list_node* next; struct ni_list_node* next;
@@ -47,9 +48,13 @@ static inline ni_list_node* ni_list__get_back(ni_list* l) {
/* Iter Helpers */ /* Iter Helpers */
// helper macro for element traversal, ordering of operations may cause seg faults
// generally just don't remove 'it', this causes it->next on the next loop to segfault
#define NI_LIST__FOREACH(lptr) \ #define NI_LIST__FOREACH(lptr) \
for (ni_list_node* it = (lptr)->head.next; it != &(lptr)->head; it = it->next) for (ni_list_node* it = (lptr)->head.next; it != &(lptr)->head; it = it->next)
// helper macro for element traversal, that also makes a copy of it in tmp
// useful for removing/moving the current node (it)
#define NI_LIST_FOREACH_SAFE(tmp, lptr) \ #define NI_LIST_FOREACH_SAFE(tmp, lptr) \
for (ni_list_node* it = (lptr)->head.next, *tmp = it->next; \ for (ni_list_node* it = (lptr)->head.next, *tmp = it->next; \
it != &(lptr)->head; \ it != &(lptr)->head; \
@@ -61,4 +66,8 @@ static inline ni_list_node* ni_list__get_back(ni_list* l) {
#define NI_LIST_CONTAINER_OF(ptr, type, member) \ #define NI_LIST_CONTAINER_OF(ptr, type, member) \
((type*)((uint8_t*)(ptr) - offsetof(type, member))) ((type*)((uint8_t*)(ptr) - offsetof(type, member)))
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_LINKEDLIST_H_ */ #endif /* __NICKEL_LINKEDLIST_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_LOG_H_
#define __NICKEL_LOG_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_LOG_H_ */

31
include/nickel/oops.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef __NICKEL_OOPS_H
#define __NICKEL_OOPS_H
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
// TODO: C23 adds [[noreturn]] (& [[]] function attribute tags)
// which are standardized, the below depends on compiler support.
#if defined(__GNUC__) || defined(__CLANG__)
#define NORETURN __attribute__ ((__noreturn__))
#else
#define NORETURN
#endif /* __GNUC__ || __CLANG__*/
void nickel_oops_handler(const char *format, ...) NORETURN;
#ifdef __cplusplus
}
#endif
// clean up our noreturn define
#undef NORETURN
#endif

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_REFCNT_H_
#define __NICKEL_REFCNT_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_REFCNT_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_RESULT_H_
#define __NICKEL_RESULT_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_RESULT_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_RINGSPSC_H_
#define __NICKEL_RINGSPSC_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_RINGSPSC_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_SPAN_H_
#define __NICKEL_SPAN_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_SPAN_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_THREADPOOL_H_
#define __NICKEL_THREADPOOL_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_THREADPOOL_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_TIMEUTIL_H_
#define __NICKEL_TIMEUTIL_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_TIMEUTIL_H_ */

View File

@@ -0,0 +1,14 @@
#ifndef __NICKEL_VERSION_H_
#define __NICKEL_VERSION_H_
#ifdef __cplusplus
extern "C" {
#endif
/* header contents */
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_VERSION_H_ */

View File

@@ -0,0 +1,22 @@
#ifndef __NICKEL_XALLOC_H_
#define __NICKEL_XALLOC_H_
#include "nickel.h"
#ifdef __cplusplus
extern "C" {
#endif
void* ni_xmalloc(size_t size);
void* ni_xcalloc(size_t nmemb,size_t size);
void* ni_xrealloc(void* ptr, size_t size);
char* ni_xstrdup(const char *s);
char* ni_xstrndup(const char s[], size_t n);
#ifdef __cplusplus
}
#endif
#endif /* __NICKEL_XALLOC_H_ */

View File

@@ -1,9 +1,13 @@
ALLOC_LIB := $(BUILD_DIR)/lib/libnia.a
LIB := $(BUILD_DIR)/lib/libnickel.a LIB := $(BUILD_DIR)/lib/libnickel.a
BIN_DIR := $(BUILD_DIR)/bin BIN_DIR := $(BUILD_DIR)/bin
OBJ_DIR := $(BUILD_DIR)/obj OBJ_DIR := $(BUILD_DIR)/obj
NI_ALLOC_SRCS := $(wildcard src/nickel/ni_alloc/*.c)
NI_ALLOC_OBJS := $(patsubst src/ni_alloc/%.c,$(OBJ_DIR)/ni_alloc/%.o,$(NI_ALLOC_SRCS))
NICKEL_SRCS := $(wildcard src/nickel/*.c) NICKEL_SRCS := $(wildcard src/nickel/*.c)
NICKEL_OBJS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(NICKEL_SRCS)) NICKEL_OBJS := $(patsubst src/nickel/%.c,$(OBJ_DIR)/nickel/%.o,$(NICKEL_SRCS))
TEST_SRCS := $(wildcard tests/*.c) TEST_SRCS := $(wildcard tests/*.c)
TEST_BIN := $(BIN_DIR)/tests TEST_BIN := $(BIN_DIR)/tests
@@ -14,7 +18,12 @@ TOOL_BINS := \
.PHONY: all clean test tools examples .PHONY: all clean test tools examples
all: $(LIB) tools $(TEST_BIN) all: $(ALLOC_LIB) $(LIB) tools $(TEST_BIN)
$(ALLOC_LIB): $(NI_ALLOC_OBJS)
@mkdir -p $(dir $@)
$(AR) rcs $@ $^
$(RANLIB) $@
$(LIB): $(NICKEL_OBJS) $(LIB): $(NICKEL_OBJS)
@mkdir -p $(dir $@) @mkdir -p $(dir $@)
@@ -26,9 +35,9 @@ $(OBJ_DIR)/%.o: src/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(CC) $(CFLAGS) -c $< -o $@
# Tests link against libnickel.a # Tests link against libnickel.a
$(TEST_BIN): $(TEST_SRCS) $(LIB) $(TEST_BIN): $(TEST_SRCS) $(ALLOC_LIB) $(LIB)
@mkdir -p $(BIN_DIR) @mkdir -p $(BIN_DIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TEST_SRCS) $(LIB) $(LDLIBS) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(TEST_SRCS) $(ALLOC_LIB) $(LIB) $(LDLIBS)
# Tools (one main.c each; expand as needed) # Tools (one main.c each; expand as needed)
#$(BIN_DIR)/logscan: tools/logscan/main.c $(LIB) #$(BIN_DIR)/logscan: tools/logscan/main.c $(LIB)
@@ -39,10 +48,10 @@ $(TEST_BIN): $(TEST_SRCS) $(LIB)
# @mkdir -p $(BIN_DIR) # @mkdir -p $(BIN_DIR)
# $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS) # $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS)
tools: $(TOOL_BINS)
test: $(TEST_BIN) test: $(TEST_BIN)
$(TEST_BIN) $(TEST_BIN)
tools: $(TOOL_BINS)
clean: clean:
rm -rf $(BUILD_DIR) rm -rf $(BUILD_DIR)

58
src/ni_alloc/alloc.c Normal file
View File

@@ -0,0 +1,58 @@
#include "ni_alloc/alloc.h"
/* -------- Global allocator API -------- */
/* Initialize/shutdown global allocator (optional; can be lazy).
Returns false if initialization fails.
*/
bool ni_alloc_global_init(const ni_alloc_config* cfg);
void ni_alloc_global_shutdown(void);
/* Allocation functions using the global allocator. */
void* ni_malloc(size_t size);
void ni_free(void* ptr);
void* ni_realloc(void* ptr, size_t new_size);
void* ni_calloc(size_t count, size_t elem_size);
/* Alignment:
- alignment must be power of two and >= sizeof(void*)
- returns NULL on failure
*/
void* ni_memalign(size_t alignment, size_t size);
/* Equivalent of C11 aligned_alloc semantics:
- alignment must be power of two
- size must be a multiple of alignment
*/
void* ni_aligned_alloc(size_t alignment, size_t size);
/* Query/reset stats for global allocator. */
void ni_alloc_get_stats(ni_alloc_stats* out_stats);
void ni_alloc_reset_stats(void);
/* -------- Instance allocator API -------- */
ni_allocator* ni_allocator_create(const ni_alloc_config* cfg);
void ni_allocator_destroy(ni_allocator* a);
void* ni_allocator_malloc(ni_allocator* a, size_t size);
void ni_allocator_free(ni_allocator* a, void* ptr);
void* ni_allocator_realloc(ni_allocator* a, void* ptr, size_t new_size);
void* ni_allocator_calloc(ni_allocator* a, size_t count, size_t elem_size);
void* ni_allocator_memalign(ni_allocator* a, size_t alignment, size_t size);
void ni_allocator_get_stats(ni_allocator* a, ni_alloc_stats* out_stats);
void ni_allocator_reset_stats(ni_allocator* a);
/* -------- Utilities / validation -------- */
/* Returns true if n is a power of two (and nonzero). */
bool ni_is_pow2_size(size_t n);
/* Round up x to next multiple of align (align must be power of two). */
size_t ni_align_up(size_t x, size_t align);
/* Optional: verify internal invariants in debug builds.
- returns false if corruption detected
*/
bool ni_alloc_validate(void);

29
src/ni_alloc/vm_posix.c Normal file
View File

@@ -0,0 +1,29 @@
#include "ni_alloc/vm.h"
/* Returns OS page size (usually sysconf(_SC_PAGESIZE)). Never returns 0. */
size_t ni_vm_page_size(void);
/* Reserve anonymous virtual memory.
- bytes will be rounded up to a page multiple.
- returns NULL on failure.
*/
void* ni_vm_reserve(size_t bytes);
/* Release a region previously returned by ni_vm_reserve (page-multiple size). */
bool ni_vm_release(void* addr, size_t bytes);
/* Optional: “commit/decommit” for platforms where its meaningful.
On many Unix systems, reserve implies commit; implementations may no-op.
*/
bool ni_vm_commit(void* addr, size_t bytes);
bool ni_vm_decommit(void* addr, size_t bytes);
/* Optional: set guard pages (mprotect) for debug.
Returns false if unsupported or fails.
*/
bool ni_vm_protect_none(void* addr, size_t bytes);
bool ni_vm_protect_rw(void* addr, size_t bytes);
/* Stats are optional but very useful for tests/tools. */
void ni_vm_get_stats(ni_vm_stats* out_stats);
void ni_vm_reset_stats(void);

View File

@@ -2,6 +2,7 @@
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h>
#include "nickel/linkedlist.h" #include "nickel/linkedlist.h"
@@ -19,6 +20,10 @@ bool ni_list__is_empty(ni_list* l) {
} }
void ni_list__insert_after(ni_list* l, ni_list_node* entry, ni_list_node* n) { void ni_list__insert_after(ni_list* l, ni_list_node* entry, ni_list_node* n) {
// n must not be in a list already,
// if you need an object in multiple lists, use multiple links.
assert(n->next == NULL && n->prev == NULL);
// setup our incomming node as the next node after the insert point // setup our incomming node as the next node after the insert point
n->next = entry->next; n->next = entry->next;
n->prev = entry; n->prev = entry;
@@ -32,6 +37,10 @@ void ni_list__insert_after(ni_list* l, ni_list_node* entry, ni_list_node* n) {
} }
void ni_list__insert_before(ni_list* l, ni_list_node* entry, ni_list_node* n) { void ni_list__insert_before(ni_list* l, ni_list_node* entry, ni_list_node* n) {
// n must not be in a list already,
// if you need an object in multiple lists, use multiple links.
assert(n->next == NULL && n->prev == NULL);
// setup our incomming node as the next node before the insert point // setup our incomming node as the next node before the insert point
n->prev = entry->prev; n->prev = entry->prev;
n->next = entry; n->next = entry;

64
src/nickel/xalloc.c Normal file
View File

@@ -0,0 +1,64 @@
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include "nickel/xalloc.h"
void* ni_xmalloc(size_t size) {
void* retptr = malloc(size);
if (retptr != NULL) {
return retptr;
} else {
perror("xmalloc failed");
exit(EXIT_FAILURE);
}
}
void* ni_xcalloc(size_t nmemb,size_t size) {
void* retptr = calloc(nmemb, size);
if (retptr != NULL) {
return retptr;
} else {
perror("xcalloc failed");
exit(EXIT_FAILURE);
}
}
void* ni_xrealloc(void* ptr, size_t size) {
void* retptr = realloc(ptr, size);
if (retptr != NULL) {
return retptr;
} else {
perror("xrealloc failed");
exit(EXIT_FAILURE);
}
}
char* ni_xstrdup(const char *s) {
void* dupstr = strdup(s);
if (dupstr != NULL) {
return dupstr;
} else {
perror("xdupstr failed");
exit(EXIT_FAILURE);
}
}
char* ni_xstrndup(const char s[], size_t n) {
void* dupstr = strndup(s, n);
if (dupstr != NULL) {
return dupstr;
} else {
perror("xdupstr failed");
exit(EXIT_FAILURE);
}
}

View File

@@ -30,9 +30,9 @@ char* get_bytes_f(long bytes, char* str) {
} }
typedef struct test_data { typedef struct test_data {
uint64_t data1; uint8_t data1;
uint64_t data2; uint16_t data2;
uint64_t data3; uint32_t data3;
uint64_t data4; uint64_t data4;
ni_list_node link; ni_list_node link;
@@ -50,31 +50,36 @@ int main (void)
size_t test_size = 10; size_t test_size = 10;
for(size_t i = 0; i < test_size; i++) { for(size_t i = 0; i < test_size; i++) {
test_data* t = malloc(sizeof(test_data)); test_data* t = malloc(sizeof(test_data));
printf("n = %d allocated data at %p...test_data.link at %p...\n", i, &t, &t->link); printf("n = %ld allocated data at %p...test_data.link at %p...\n", i, (void*)t, (void*)&t->link);
t->data1 = rand() % UINT64_MAX; t->data1 = (uint8_t)(rand() % UINT8_MAX);
t->data2 = rand() % UINT64_MAX; t->data2 = (uint16_t)(rand() % UINT16_MAX);
t->data3 = rand() % UINT64_MAX; t->data3 = (uint32_t)(rand() % UINT32_MAX);
t->data4 = rand() % UINT64_MAX; t->data4 = (((uint64_t)(rand() % UINT32_MAX)) << 32 | (rand() %UINT32_MAX));
// ensure our link points at nothing.
t->link.next = NULL;
t->link.prev = NULL;
ni_list__push_back(&data_instances, &(t->link)); ni_list__push_back(&data_instances, &(t->link));
printf("write: n = %d: %ld %ld %ld %ld \n\tnext: %p\n\tprev: %p\n", printf("write: n = %ld: %d %d %d %ld \n\tnext: %p\n\tprev: %p\n",
i, t->data1, t->data2, t->data3, t->data4, t->link.next, t->link.prev); i, t->data1, t->data2, t->data3, t->data4, (void*)t->link.next, (void*)t->link.prev);
} }
printf("Inserted %d count entries into test list...\n", data_instances.count); printf("Inserted %ld count entries into test list...\n", data_instances.count);
{ // scope for access to iter and list { // scope for access to iter and list
size_t i = 0; size_t i = 0;
NI_LIST__FOREACH(&data_instances) { NI_LIST__FOREACH(&data_instances) {
test_data* t = NI_LIST_CONTAINER_OF(it, test_data, link); test_data* t = NI_LIST_CONTAINER_OF(it, test_data, link);
printf("read: %d: %ld %ld %ld %ld \n\tnext: %p\n\tprev: %p\n", printf("read: %ld: %d %d %d %ld \n\tnext: %p\n\tprev: %p\n",
i, t->data1, t->data2, t->data3, t->data4, (void*)it->next, (void*)it->prev); i, t->data1, t->data2, t->data3, t->data4, (void*)it->next, (void*)it->prev);
i++; i++;
} }
} }
// drain from back of list and remove then free entries
while (!ni_list__is_empty(&data_instances)) { while (!ni_list__is_empty(&data_instances)) {
ni_list_node* tail = ni_list__get_back(&data_instances); ni_list_node* tail = ni_list__get_back(&data_instances);
ni_list__remove(&data_instances, tail); ni_list__remove(&data_instances, tail);