cwist
Modern C web framework with libttak runtime
Loading...
Searching...
No Matches
alloc.c
Go to the documentation of this file.
3#if !defined(CWIST_PLATFORM_HOSTED)
4/* WASM hosts are single-threaded and lack libttak's mmap/pthread machinery;
5 * allocations go straight to libc. The owner-guard bridge is native-only. */
6#include <cjson/cJSON.h>
7#include <stdbool.h>
8#include <stdint.h>
9#include <stdio.h>
10#include <string.h>
11#include <stdlib.h>
12
13void *cwist_malloc(size_t size) {
14 return calloc(1, size ? size : 1);
15}
16
17void *cwist_alloc(size_t size) {
18 return cwist_malloc(size);
19}
20
21void *cwist_alloc_array(size_t count, size_t elem_size) {
22 if (count == 0 || elem_size == 0) return cwist_malloc(1);
23 if (elem_size > SIZE_MAX / count) return NULL;
24 return cwist_malloc(count * elem_size);
25}
26
27void *cwist_realloc(void *ptr, size_t new_size) {
28 if (!ptr) return cwist_malloc(new_size);
29 return realloc(ptr, new_size ? new_size : 1);
30}
31
32char *cwist_strdup(const char *src) {
33 if (!src) return NULL;
34 size_t len = strlen(src);
35 char *dst = (char *)cwist_malloc(len + 1);
36 if (!dst) return NULL;
37 memcpy(dst, src, len + 1);
38 return dst;
39}
40
41char *cwist_strndup(const char *src, size_t n) {
42 if (!src) return NULL;
43 size_t len = 0;
44 while (len < n && src[len]) len++;
45 char *dst = (char *)cwist_malloc(len + 1);
46 if (!dst) return NULL;
47 memcpy(dst, src, len);
48 dst[len] = '\0';
49 return dst;
50}
51
52void cwist_free(void *ptr) {
53 free(ptr);
54}
55
56static void *cwist_cjson_malloc(size_t size) {
57 return cwist_malloc(size);
58}
59
60static void cwist_cjson_free(void *ptr) {
61 cwist_free(ptr);
62}
63
64__attribute__((constructor)) static void cwist_install_cjson_hooks(void) {
65 cJSON_Hooks hooks = {.malloc_fn = cwist_cjson_malloc, .free_fn = cwist_cjson_free};
66 cJSON_InitHooks(&hooks);
67}
68#else
69#include <cwist/core/mem/gc.h>
70#include "gc_internal.h"
71#include <ttak/mem/mem.h>
72#include <ttak/timing/timing.h>
73#include <ttak/sync/sync.h>
74#include <cjson/cJSON.h>
75#include <stdbool.h>
76#include <stdio.h>
77#include <string.h>
78#include <limits.h>
79#include <stdlib.h>
80#include <stdatomic.h>
81
87#ifndef ATOMIC_VAR_INIT
88#define ATOMIC_VAR_INIT(value) (value)
89#endif
90
91#define CWIST_OWNER_RESOURCE "cwist_mem_resource"
92#define CWIST_OWNER_ALLOC_FUNC "cwist_mem_alloc"
93#define CWIST_OWNER_FREE_FUNC "cwist_mem_free"
94#define CWIST_OWNER_REALLOC_FUNC "cwist_mem_realloc"
95
100static inline uint64_t cwist_mem_now(void) {
101 return ttak_get_tick_count();
102}
103
104typedef struct {
105 ttak_mem_flags_t flags;
106} cwist_owner_policy_t;
107
108typedef struct {
109 size_t size;
110 void *result;
111} cwist_owner_alloc_args_t;
112
113typedef struct {
114 void *ptr;
115} cwist_owner_free_args_t;
116
117typedef struct {
118 void *ptr;
119 size_t size;
120 void *result;
121} cwist_owner_realloc_args_t;
122
123static ttak_mutex_t g_owner_lock;
124#if defined(__TINYC__)
125static atomic_bool g_owner_lock_ready = false;
126#else
127static atomic_bool g_owner_lock_ready = ATOMIC_VAR_INIT(false);
128#endif
129static ttak_owner_t *g_owner = NULL;
130static cwist_owner_policy_t g_owner_policy = {.flags = TTAK_MEM_DEFAULT | TTAK_MEM_STRICT_CHECK};
131static bool g_owner_enabled = false;
132
136static void cwist_owner_lock_init(void) {
137 if (atomic_load_explicit(&g_owner_lock_ready, memory_order_acquire)) {
138 return;
139 }
140 static atomic_flag init_flag = ATOMIC_FLAG_INIT;
141 while (atomic_flag_test_and_set_explicit(&init_flag, memory_order_acquire)) {
142 // Spin until initialization completes; extremely rare path.
143 }
144 if (!atomic_load_explicit(&g_owner_lock_ready, memory_order_relaxed)) {
145 ttak_mutex_init(&g_owner_lock);
146 atomic_store_explicit(&g_owner_lock_ready, true, memory_order_release);
147 }
148 atomic_flag_clear_explicit(&init_flag, memory_order_release);
149}
150
156static void cwist_owner_alloc(void *ctx, void *args) {
157 cwist_owner_policy_t *policy = (cwist_owner_policy_t *)ctx;
158 cwist_owner_alloc_args_t *req = (cwist_owner_alloc_args_t *)args;
159 if (!req) return;
160 size_t actual = req->size ? req->size : 1;
161 ttak_mem_flags_t flags = policy ? policy->flags : TTAK_MEM_DEFAULT;
162 req->result = ttak_mem_alloc_safe(actual, __TTAK_UNSAFE_MEM_FOREVER__, cwist_mem_now(), false,
163 false, true, true, flags);
164}
165
171static void cwist_owner_free(void *ctx, void *args) {
172 (void)ctx;
173 cwist_owner_free_args_t *req = (cwist_owner_free_args_t *)args;
174 if (!req || !req->ptr) return;
175 ttak_mem_free(req->ptr);
176 req->ptr = NULL;
177}
178
184static void cwist_owner_realloc(void *ctx, void *args) {
185 cwist_owner_policy_t *policy = (cwist_owner_policy_t *)ctx;
186 cwist_owner_realloc_args_t *req = (cwist_owner_realloc_args_t *)args;
187 if (!req) return;
188 size_t actual = req->size ? req->size : 1;
189 ttak_mem_flags_t flags = policy ? policy->flags : TTAK_MEM_DEFAULT;
190 req->result = ttak_mem_realloc_safe(req->ptr, actual, __TTAK_UNSAFE_MEM_FOREVER__,
191 cwist_mem_now(), true, flags);
192}
193
198ttak_owner_t *cwist_create_owner(void) {
199 cwist_owner_lock_init();
200 ttak_mutex_lock(&g_owner_lock);
201 if (!g_owner) {
202 ttak_owner_t *owner =
203 ttak_owner_create(TTAK_OWNER_SAFE_DEFAULT | TTAK_OWNER_DENY_DANGEROUS_MEM);
204 if (owner) {
205 bool ok = true;
206 ok &= ttak_owner_register_resource(owner, CWIST_OWNER_RESOURCE, &g_owner_policy);
207 ok &= ttak_owner_register_func(owner, CWIST_OWNER_ALLOC_FUNC, cwist_owner_alloc);
208 ok &= ttak_owner_register_func(owner, CWIST_OWNER_FREE_FUNC, cwist_owner_free);
209 ok &= ttak_owner_register_func(owner, CWIST_OWNER_REALLOC_FUNC, cwist_owner_realloc);
210 if (!ok) {
211 ttak_owner_destroy(owner);
212 owner = NULL;
213 fprintf(stderr, "[CWIST] Failed to register owner guards\n");
214 }
215 } else {
216 fprintf(stderr, "[CWIST] Failed to create owner context\n");
217 }
218 g_owner = owner;
219 }
220 ttak_owner_t *owner = g_owner;
221 ttak_mutex_unlock(&g_owner_lock);
222 return owner;
223}
224
231static bool cwist_owner_call(const char *func_name, void *args) {
232 ttak_owner_t *owner = cwist_create_owner();
233 if (!owner) {
234 fprintf(stderr, "[CWIST] Owner unavailable for %s\n", func_name);
235 return false;
236 }
237 if (!ttak_owner_execute(owner, func_name, CWIST_OWNER_RESOURCE, args)) {
238 fprintf(stderr, "[CWIST] Owner rejected call: %s\n", func_name);
239 return false;
240 }
241 return true;
242}
243
248static void cwist_owner_abort(const char *func_name) {
249 fprintf(stderr, "[CWIST] Unable to honor %s; aborting to prevent unsafe memory access\n",
250 func_name);
251 abort();
252}
253
259void *cwist_malloc(size_t size) {
260 size_t actual = size ? size : 1;
261 if (!g_owner_enabled) {
262 void *ptr = calloc(1, actual);
263 return ptr;
264 }
265 cwist_owner_alloc_args_t args = {.size = actual, .result = NULL};
266 if (!cwist_owner_call(CWIST_OWNER_ALLOC_FUNC, &args)) {
267 return NULL;
268 }
269 return args.result;
270}
271
277void *cwist_alloc(size_t size) {
278 void *ptr = cwist_malloc(size);
279 if (ptr && cwist_full_gc_enabled()) {
281 }
282 return ptr;
283}
284
291void *cwist_alloc_array(size_t count, size_t elem_size) {
292 if (count == 0 || elem_size == 0) {
293 return cwist_malloc(1);
294 }
295 if (elem_size > SIZE_MAX / count) {
296 return NULL;
297 }
298 return cwist_malloc(count * elem_size);
299}
300
307void *cwist_realloc(void *ptr, size_t new_size) {
308 size_t actual = new_size ? new_size : 1;
309 if (!ptr) {
310 void *fresh = cwist_malloc(actual);
311 if (fresh && cwist_full_gc_enabled()) {
313 }
314 return fresh;
315 }
316 /* realloc() (or the owner-guarded equivalent below) is free to move the
317 * block to a new address; if @p ptr was on this thread's full-GC
318 * pending-sweep list under its old address, leaving that stale entry
319 * in place means the thread-exit sweep would later free() an address
320 * that's already back in general circulation -- a real double-free,
321 * not a hypothetical one (reproduced via bench_malloc_intercept_concurrent).
322 * Untrack the old address up front and track whatever address the
323 * (re)allocation actually returns; on failure the original block is
324 * still valid per realloc()'s contract, so restore its tracking. */
325 bool was_tracked = cwist_full_gc_enabled() && cwist_gc_scope_untrack(ptr);
326 if (!g_owner_enabled) {
327 void *res = realloc(ptr, actual);
328 if (!res) {
329 if (was_tracked) cwist_gc_scope_track(ptr);
330 return NULL;
331 }
332 if (was_tracked) cwist_gc_scope_track(res);
333 return res;
334 }
335 cwist_owner_realloc_args_t args = {.ptr = ptr, .size = actual, .result = NULL};
336 if (!cwist_owner_call(CWIST_OWNER_REALLOC_FUNC, &args) || !args.result) {
337 if (was_tracked) cwist_gc_scope_track(ptr);
338 return NULL;
339 }
340 if (was_tracked) cwist_gc_scope_track(args.result);
341 return args.result;
342}
343
349char *cwist_strdup(const char *src) {
350 if (!src) return NULL;
351 size_t len = strlen(src);
352 char *dst = (char *)cwist_malloc(len + 1);
353 if (!dst) return NULL;
354 memcpy(dst, src, len + 1);
355 return dst;
356}
357
364static size_t cwist_strnlen(const char *src, size_t max_len) {
365 size_t len = 0;
366 while (len < max_len && src[len]) {
367 len++;
368 }
369 return len;
370}
371
378char *cwist_strndup(const char *src, size_t n) {
379 if (!src) return NULL;
380 size_t len = cwist_strnlen(src, n);
381 char *dst = (char *)cwist_malloc(len + 1);
382 if (!dst) return NULL;
383 memcpy(dst, src, len);
384 dst[len] = '\0';
385 return dst;
386}
387
392void cwist_free(void *ptr) {
393 if (!ptr) return;
394 /* No-op unless ptr was handed to a GC context via cwist_reg_ptr(): an
395 * explicit free must detach it first, or cwist_gc_shutdown() would
396 * release it a second time. */
398 if (cwist_full_gc_enabled()) {
399 /* Best-effort: absent from the pending list just means ptr came
400 * from cwist_strdup()/cwist_realloc()/etc. rather than cwist_alloc(),
401 * or full-GC was off when it was allocated. Either way we still
402 * free it normally below. */
404 }
405 if (!g_owner_enabled) {
406 free(ptr);
407 return;
408 }
409 cwist_owner_free_args_t args = {.ptr = ptr};
410 if (!cwist_owner_call(CWIST_OWNER_FREE_FUNC, &args)) {
411 cwist_owner_abort(CWIST_OWNER_FREE_FUNC);
412 }
413}
414
423void *cwist_malloc_shim(size_t size) {
424 void *ptr = malloc(size ? size : 1);
425 if (ptr && cwist_full_gc_enabled()) {
427 }
428 return ptr;
429}
430
434void *cwist_calloc_shim(size_t nmemb, size_t size) {
435 void *ptr = calloc(nmemb ? nmemb : 1, size ? size : 1);
436 if (ptr && cwist_full_gc_enabled()) {
438 }
439 return ptr;
440}
441
449void *cwist_realloc_shim(void *ptr, size_t size) {
450 size_t actual = size ? size : 1;
451 if (!ptr) {
452 return cwist_malloc_shim(actual);
453 }
454 bool was_tracked = false;
455 if (cwist_full_gc_enabled()) {
456 was_tracked = cwist_gc_scope_untrack(ptr);
457 }
458 void *new_ptr = realloc(ptr, actual);
459 if (new_ptr && was_tracked && cwist_full_gc_enabled()) {
460 cwist_gc_scope_track(new_ptr);
461 }
462 return new_ptr;
463}
464
470void cwist_free_shim(void *ptr) {
471 if (!ptr) return;
472 if (cwist_full_gc_enabled()) {
474 }
475 free(ptr);
476}
477
483static void *cwist_cjson_malloc(size_t size) {
484 return cwist_malloc(size);
485}
486
491static void cwist_cjson_free(void *ptr) {
492 cwist_free(ptr);
493}
494
498__attribute__((constructor)) static void cwist_install_cjson_hooks(void) {
499 cJSON_Hooks hooks = {.malloc_fn = cwist_cjson_malloc, .free_fn = cwist_cjson_free};
500 cJSON_InitHooks(&hooks);
501}
502
506static void cwist_destroy_owner(void) {
507 if (!atomic_load_explicit(&g_owner_lock_ready, memory_order_acquire)) {
508 return;
509 }
510 ttak_mutex_lock(&g_owner_lock);
511 if (g_owner) {
512 ttak_owner_destroy(g_owner);
513 g_owner = NULL;
514 }
515 ttak_mutex_unlock(&g_owner_lock);
516}
517
521__attribute__((destructor)) static void cwist_owner_cleanup(void) {
522 cwist_destroy_owner();
523}
524#endif /* __EMSCRIPTEN__ / __wasi__ */
__attribute__((constructor))
Definition alloc.c:64
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
Definition alloc.c:52
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
Definition alloc.c:27
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
Definition alloc.c:32
void * cwist_malloc(size_t size)
Allocate zeroed memory via the CWIST owner context.
Definition alloc.c:13
void * cwist_alloc_array(size_t count, size_t elem_size)
Allocate zeroed memory for count elements.
Definition alloc.c:21
char * cwist_strndup(const char *src, size_t n)
Duplicate up to n bytes of a string (libttak-backed).
Definition alloc.c:41
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Definition alloc.c:17
Memory allocation wrappers backed by libttak.
ttak_owner_t * cwist_create_owner(void)
Lazily create (or return) the shared CWIST owner context.
bool cwist_gc_reg_untrack(void *ptr)
Detach ptr from whichever GC context tracks it, without freeing.
Definition gc.c:113
Epoch-based garbage collection wrapper.
bool cwist_full_gc_enabled(void)
Check whether full-GC mode is currently enabled.
Definition gc.c:515
bool cwist_gc_scope_untrack(void *ptr)
Remove a block from the current thread's pending-sweep list.
Definition gc.c:734
void cwist_gc_scope_track(void *ptr)
Register a cwist_alloc() block with the current thread's pending-sweep list (full-GC mode only).
Definition gc.c:707
Internal bridge between the allocator and the epoch GC wrapper.
void * cwist_calloc_shim(size_t nmemb, size_t size)
calloc() replacement: real libc calloc() (zeroed), registered with the full-GC pending-sweep list whe...
void * cwist_realloc_shim(void *ptr, size_t size)
realloc() replacement. Tracking follows the incoming pointer: if ptr was tracked (shimmed-and-full-GC...
void cwist_free_shim(void *ptr)
free() replacement: untracks ptr first if it was tracked (safe no-op if it wasn't - same contract as ...
void * cwist_malloc_shim(size_t size)
malloc() replacement: real libc malloc() (uninitialized memory, exactly like malloc - unlike cwist_al...
size_t count
Definition otel.c:58
Target capability macros shared by all CWIST sources.