Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37bcf7eb74 | |||
| 2240d26c32 | |||
| f39ae40047 | |||
| 7a479111ac | |||
| c21074b547 | |||
| 7557ea6e19 | |||
| d545b69614 |
@@ -2766,6 +2766,9 @@ fn builtin_arity(name: String) -> Int {
|
|||||||
if str_eq(name, "__engram_scan_nodes_json") { return 2 }
|
if str_eq(name, "__engram_scan_nodes_json") { return 2 }
|
||||||
if str_eq(name, "__engram_edges_json") { return 2 }
|
if str_eq(name, "__engram_edges_json") { return 2 }
|
||||||
if str_eq(name, "__engram_pool_stats_json") { return 0 }
|
if str_eq(name, "__engram_pool_stats_json") { return 0 }
|
||||||
|
if str_eq(name, "__el_alloc_count") { return 0 }
|
||||||
|
if str_eq(name, "__el_alloc_bytes") { return 0 }
|
||||||
|
if str_eq(name, "__el_peak_rss") { return 0 }
|
||||||
if str_eq(name, "__generate") { return 1 }
|
if str_eq(name, "__generate") { return 1 }
|
||||||
// Filesystem
|
// Filesystem
|
||||||
if str_eq(name, "fs_read") { return 1 }
|
if str_eq(name, "fs_read") { return 1 }
|
||||||
@@ -2866,6 +2869,9 @@ fn builtin_arity(name: String) -> Int {
|
|||||||
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
if str_eq(name, "engram_scan_nodes_json") { return 2 }
|
||||||
if str_eq(name, "engram_edges_json") { return 2 }
|
if str_eq(name, "engram_edges_json") { return 2 }
|
||||||
if str_eq(name, "engram_pool_stats_json") { return 0 }
|
if str_eq(name, "engram_pool_stats_json") { return 0 }
|
||||||
|
if str_eq(name, "el_alloc_count") { return 0 }
|
||||||
|
if str_eq(name, "el_alloc_bytes") { return 0 }
|
||||||
|
if str_eq(name, "el_peak_rss") { return 0 }
|
||||||
if str_eq(name, "engram_neighbors_json") { return 3 }
|
if str_eq(name, "engram_neighbors_json") { return 3 }
|
||||||
if str_eq(name, "engram_activate_json") { return 2 }
|
if str_eq(name, "engram_activate_json") { return 2 }
|
||||||
if str_eq(name, "engram_stats_json") { return 0 }
|
if str_eq(name, "engram_stats_json") { return 0 }
|
||||||
|
|||||||
@@ -155,21 +155,55 @@ el_val_t el_arena_pop(el_val_t mark) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Allocation accounting ───────────────────────────────────────────────────
|
||||||
|
*
|
||||||
|
* Every string allocation in the runtime funnels through the four functions
|
||||||
|
* below, so counting here counts everything the language does.
|
||||||
|
*
|
||||||
|
* WHY THIS EXISTS: a growth-curve gate needs a signal that is DETERMINISTIC.
|
||||||
|
* Wall-clock needs statistics, warmup, and a quiet machine; it is noisy on
|
||||||
|
* shared CI and unusable as a hard build gate. Allocation COUNT has none of
|
||||||
|
* those problems — the same input allocates the same number of times on every
|
||||||
|
* machine, every run. Fit allocations against input size and a complexity
|
||||||
|
* regression becomes a build failure with zero flake.
|
||||||
|
*
|
||||||
|
* This is not hypothetical. elc's known defect is quadratic ALLOCATION VOLUME.
|
||||||
|
* The old shipped binary paid it in RSS (27 GB, OOM); the rebuilt one pays the
|
||||||
|
* same quadratic in malloc/free churn (42s on a 1.4 MB input). The allocation
|
||||||
|
* count was the invariant across both — RSS and wall-clock were just the two
|
||||||
|
* ways it surfaced. An `expect allocs O(n)` assertion on the compile path
|
||||||
|
* would have failed the build the day it was introduced.
|
||||||
|
*
|
||||||
|
* Peak RSS is exported too but is explicitly NOT the gating signal: it is
|
||||||
|
* perturbed by allocator behaviour, page cache, and the OS. Gate on counts,
|
||||||
|
* report RSS as context.
|
||||||
|
*
|
||||||
|
* Counters are plain unsigned longs, incremented on the allocating thread with
|
||||||
|
* no synchronisation: this is measurement, and a lock here would change the
|
||||||
|
* thing being measured. Under threads the count is approximate; for the
|
||||||
|
* single-threaded compile path it is exact.
|
||||||
|
* ──────────────────────────────────────────────────────────────────────────── */
|
||||||
|
static unsigned long _el_alloc_count = 0;
|
||||||
|
static unsigned long _el_alloc_bytes = 0;
|
||||||
|
|
||||||
/* Persistent allocation — bypasses the arena (state_set, engram internals). */
|
/* Persistent allocation — bypasses the arena (state_set, engram internals). */
|
||||||
static char* el_strdup_persist(const char* s) {
|
static char* el_strdup_persist(const char* s) {
|
||||||
if (!s) return strdup("");
|
if (!s) { _el_alloc_count++; _el_alloc_bytes += 1; return strdup(""); }
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += strlen(s) + 1;
|
||||||
return strdup(s);
|
return strdup(s);
|
||||||
}
|
}
|
||||||
static char* el_strbuf_persist(size_t n) {
|
static char* el_strbuf_persist(size_t n) {
|
||||||
char* p = malloc(n + 1);
|
char* p = malloc(n + 1);
|
||||||
if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
p[0] = '\0';
|
p[0] = '\0';
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += n + 1;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* el_strdup(const char* s) {
|
static char* el_strdup(const char* s) {
|
||||||
if (!s) { char* p = strdup(""); el_arena_track(p); return p; }
|
if (!s) { char* p = strdup(""); _el_alloc_count++; _el_alloc_bytes += 1; el_arena_track(p); return p; }
|
||||||
char* p = strdup(s);
|
char* p = strdup(s);
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += strlen(s) + 1;
|
||||||
el_arena_track(p);
|
el_arena_track(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -178,6 +212,7 @@ static char* el_strbuf(size_t n) {
|
|||||||
char* p = malloc(n + 1);
|
char* p = malloc(n + 1);
|
||||||
if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
if (!p) { fputs("el_runtime: out of memory\n", stderr); exit(1); }
|
||||||
p[0] = '\0';
|
p[0] = '\0';
|
||||||
|
_el_alloc_count++; _el_alloc_bytes += n + 1;
|
||||||
el_arena_track(p);
|
el_arena_track(p);
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
@@ -18411,3 +18446,34 @@ el_val_t engram_pool_stats_json(void) {
|
|||||||
(unsigned)STORE_PAGE_SIZE);
|
(unsigned)STORE_PAGE_SIZE);
|
||||||
return el_wrap_str(el_strdup(b));
|
return el_wrap_str(el_strdup(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Allocation/RSS introspection (test-framework complexity gate, §6.5) ─────
|
||||||
|
*
|
||||||
|
* el_alloc_count() — total runtime string allocations since process start.
|
||||||
|
* THE gating signal. Deterministic: same input => same count, every machine,
|
||||||
|
* every run. A benchmark harness samples it before and after an operation at
|
||||||
|
* several input sizes and fits the deltas against n; a curve worse than the
|
||||||
|
* declared one fails the build. No warmup, no statistics, no baseline file,
|
||||||
|
* no flake — none of which is true of wall-clock.
|
||||||
|
*
|
||||||
|
* el_alloc_bytes() — total bytes requested. Same determinism; catches the case
|
||||||
|
* where allocation COUNT stays linear but per-allocation SIZE grows, which is
|
||||||
|
* the classic accidental-quadratic shape (rebuilding a whole buffer per
|
||||||
|
* append). Count alone would miss it.
|
||||||
|
*
|
||||||
|
* el_peak_rss() — peak resident set in bytes. Context, NOT a gate: perturbed by
|
||||||
|
* allocator internals, the page cache, and the OS. Reported so a human can
|
||||||
|
* see the physical consequence; never fitted.
|
||||||
|
*/
|
||||||
|
el_val_t el_alloc_count(void) { return (el_val_t)(int64_t)_el_alloc_count; }
|
||||||
|
el_val_t el_alloc_bytes(void) { return (el_val_t)(int64_t)_el_alloc_bytes; }
|
||||||
|
|
||||||
|
el_val_t el_peak_rss(void) {
|
||||||
|
struct rusage ru;
|
||||||
|
if (getrusage(RUSAGE_SELF, &ru) != 0) return (el_val_t)0;
|
||||||
|
#if defined(__APPLE__) || defined(__MACH__)
|
||||||
|
return (el_val_t)(int64_t)ru.ru_maxrss; /* macOS: bytes */
|
||||||
|
#else
|
||||||
|
return (el_val_t)(int64_t)(ru.ru_maxrss * 1024L); /* Linux: KB -> bytes */
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|||||||
@@ -1017,6 +1017,12 @@ el_val_t stdout_to_file(el_val_t path);
|
|||||||
el_val_t stdout_restore(void);
|
el_val_t stdout_restore(void);
|
||||||
el_val_t el_mem_check(void);
|
el_val_t el_mem_check(void);
|
||||||
|
|
||||||
|
/* Allocation accounting — the deterministic signal behind complexity gating.
|
||||||
|
* Gate on counts/bytes; peak RSS is context only. */
|
||||||
|
el_val_t el_alloc_count(void);
|
||||||
|
el_val_t el_alloc_bytes(void);
|
||||||
|
el_val_t el_peak_rss(void);
|
||||||
|
|
||||||
/* Semantic retrieval surface. NOT interchangeable with engram_search_json,
|
/* Semantic retrieval surface. NOT interchangeable with engram_search_json,
|
||||||
* which is lexical by design — see the note at the definition. */
|
* which is lexical by design — see the note at the definition. */
|
||||||
el_val_t engram_recall_json(el_val_t query, el_val_t limit);
|
el_val_t engram_recall_json(el_val_t query, el_val_t limit);
|
||||||
|
|||||||
@@ -1379,6 +1379,13 @@ el_val_t __engram_edges_json(el_val_t limit, el_val_t offset) {
|
|||||||
el_val_t engram_pool_stats_json(void);
|
el_val_t engram_pool_stats_json(void);
|
||||||
el_val_t __engram_pool_stats_json(void) { return engram_pool_stats_json(); }
|
el_val_t __engram_pool_stats_json(void) { return engram_pool_stats_json(); }
|
||||||
|
|
||||||
|
el_val_t el_alloc_count(void);
|
||||||
|
el_val_t el_alloc_bytes(void);
|
||||||
|
el_val_t el_peak_rss(void);
|
||||||
|
el_val_t __el_alloc_count(void) { return el_alloc_count(); }
|
||||||
|
el_val_t __el_alloc_bytes(void) { return el_alloc_bytes(); }
|
||||||
|
el_val_t __el_peak_rss(void) { return el_peak_rss(); }
|
||||||
|
|
||||||
el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset) {
|
el_val_t __engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset) {
|
||||||
return engram_scan_nodes_by_type_json(node_type, limit, offset);
|
return engram_scan_nodes_by_type_json(node_type, limit, offset);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user