Compare commits

..

1 Commits

Author SHA1 Message Date
bigmerge a8908908df runtime: count container allocations too, not just strings
El SDK CI - dev / build-and-test (pull_request) Failing after 12m11s
el #131 instrumented the four string allocators, which meant list- and map-heavy
code reported ZERO allocations — a benchmark over lists would have been fitted
against a flat line and passed anything. Caught during framework work: a
"linear" specimen read 0 allocs until it was rewritten to allocate strings.

A gate is only as good as its blind spots are small, and a signal that silently
reads zero is worse than no signal: it produces a confident pass.

Now counted at every container allocation — ElList and ElMap bodies, their
backing arrays, the copy-on-write clones, and the realloc growth path.

Verified on an append loop (n = 100..800):
    allocs  7, 8, 9, 10          +1 per doubling = O(log n) reallocations
    bytes   2048, 4096, 8192, 16384   exactly 2x per doubling = O(n)

Both curves are what correct amortized growth should look like, and both read
zero before this change.

Known remaining scope, stated rather than left implicit: these counters cover
the runtime's own allocations. They do not see malloc inside engram_*.c or
libcurl, which is correct — the gate is for El-level complexity, not for
third-party memory behaviour.
2026-08-15 21:36:52 -05:00
+9
View File
@@ -476,12 +476,14 @@ typedef struct {
static ElList* list_alloc(int64_t cap) { static ElList* list_alloc(int64_t cap) {
if (cap < 4) cap = 4; if (cap < 4) cap = 4;
ElList* lst = malloc(sizeof(ElList)); ElList* lst = malloc(sizeof(ElList));
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!lst) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
lst->hdr.magic = EL_MAGIC_LIST; lst->hdr.magic = EL_MAGIC_LIST;
lst->hdr.refcount = 1; lst->hdr.refcount = 1;
lst->length = 0; lst->length = 0;
lst->capacity = cap; lst->capacity = cap;
lst->elems = malloc((size_t)cap * sizeof(el_val_t)); lst->elems = malloc((size_t)cap * sizeof(el_val_t));
_el_alloc_count++; _el_alloc_bytes += (size_t)cap * sizeof(el_val_t);
if (!lst->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!lst->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
return lst; return lst;
} }
@@ -531,6 +533,7 @@ el_val_t el_list_append(el_val_t listv, el_val_t elem) {
if (old->length >= old->capacity) { if (old->length >= old->capacity) {
int64_t new_cap = old->capacity > 0 ? old->capacity * 2 : 4; int64_t new_cap = old->capacity > 0 ? old->capacity * 2 : 4;
el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t)); el_val_t* grown = realloc(old->elems, (size_t)new_cap * sizeof(el_val_t));
_el_alloc_count++; _el_alloc_bytes += (size_t)new_cap * sizeof(el_val_t);
if (!grown) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!grown) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
old->elems = grown; old->elems = grown;
old->capacity = new_cap; old->capacity = new_cap;
@@ -543,12 +546,14 @@ el_val_t el_list_append(el_val_t listv, el_val_t elem) {
int64_t new_cap = old->length + 1; int64_t new_cap = old->length + 1;
if (new_cap < 4) new_cap = 4; if (new_cap < 4) new_cap = 4;
ElList* fresh = malloc(sizeof(ElList)); ElList* fresh = malloc(sizeof(ElList));
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
fresh->hdr.magic = EL_MAGIC_LIST; fresh->hdr.magic = EL_MAGIC_LIST;
fresh->hdr.refcount = 1; fresh->hdr.refcount = 1;
fresh->length = old->length + 1; fresh->length = old->length + 1;
fresh->capacity = new_cap; fresh->capacity = new_cap;
fresh->elems = malloc((size_t)new_cap * sizeof(el_val_t)); fresh->elems = malloc((size_t)new_cap * sizeof(el_val_t));
_el_alloc_count++; _el_alloc_bytes += (size_t)new_cap * sizeof(el_val_t);
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
if (old->length > 0) { if (old->length > 0) {
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t)); memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
@@ -570,12 +575,14 @@ el_val_t el_list_clone(el_val_t listv) {
if (cap < old->length) cap = old->length; if (cap < old->length) cap = old->length;
if (cap < 4) cap = 4; if (cap < 4) cap = 4;
ElList* fresh = malloc(sizeof(ElList)); ElList* fresh = malloc(sizeof(ElList));
_el_alloc_count++; _el_alloc_bytes += sizeof(ElList);
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
fresh->hdr.magic = EL_MAGIC_LIST; fresh->hdr.magic = EL_MAGIC_LIST;
fresh->hdr.refcount = 1; fresh->hdr.refcount = 1;
fresh->length = old->length; fresh->length = old->length;
fresh->capacity = cap; fresh->capacity = cap;
fresh->elems = malloc((size_t)cap * sizeof(el_val_t)); fresh->elems = malloc((size_t)cap * sizeof(el_val_t));
_el_alloc_count++; _el_alloc_bytes += (size_t)cap * sizeof(el_val_t);
if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!fresh->elems) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
if (old->length > 0) { if (old->length > 0) {
memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t)); memcpy(fresh->elems, old->elems, (size_t)old->length * sizeof(el_val_t));
@@ -596,6 +603,7 @@ typedef struct {
static ElMap* map_alloc(int64_t cap) { static ElMap* map_alloc(int64_t cap) {
if (cap < 4) cap = 4; if (cap < 4) cap = 4;
ElMap* m = malloc(sizeof(ElMap)); ElMap* m = malloc(sizeof(ElMap));
_el_alloc_count++; _el_alloc_bytes += sizeof(ElMap);
if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!m) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
m->hdr.magic = EL_MAGIC_MAP; m->hdr.magic = EL_MAGIC_MAP;
m->hdr.refcount = 1; m->hdr.refcount = 1;
@@ -671,6 +679,7 @@ el_val_t el_map_set(el_val_t mapv, el_val_t keyv, el_val_t value) {
int64_t new_cap = m->count + 1; int64_t new_cap = m->count + 1;
if (new_cap < 4) new_cap = 4; if (new_cap < 4) new_cap = 4;
ElMap* fresh = malloc(sizeof(ElMap)); ElMap* fresh = malloc(sizeof(ElMap));
_el_alloc_count++; _el_alloc_bytes += sizeof(ElMap);
if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); } if (!fresh) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
fresh->hdr.magic = EL_MAGIC_MAP; fresh->hdr.magic = EL_MAGIC_MAP;
fresh->hdr.refcount = 1; fresh->hdr.refcount = 1;