From 2a7c22ae49185c5bf248a763c151ee9ea154cacb Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 11 Oct 2026 15:06:29 -0700 Subject: [PATCH] Keep map storage on the runtime allocator Acyclic map headers, entry arrays, bucket tables and number-key indices came from glibc (calloc/malloc/realloc/free) even when small: a map with one entry paid a 160-byte glibc header plus a 96-byte glibc entry block, and every growth step went through glibc realloc. They now use scr_mem_calloc/alloc/realloc/free like arrays, so blocks up to 512 bytes come from the size-class slices (inline free lists, huge pages after the slice prefix) and larger ones still go to the system through scr_mem_realloc. Nothing outside scr_map.c allocates or frees map storage. --- packages/runtime/src/scr_map.c | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/runtime/src/scr_map.c b/packages/runtime/src/scr_map.c index 7bfa805ca..bebcbd9e3 100644 --- a/packages/runtime/src/scr_map.c +++ b/packages/runtime/src/scr_map.c @@ -425,10 +425,10 @@ void *scr_map_get_span_ref(const ScrMap *m, const char *bytes, size_t length) { static void scr_map_rebuild_buckets(ScrMap *m, size_t nbuckets) { bool compact = scr_map_compact_buckets(nbuckets); size_t bytes = nbuckets * (compact ? sizeof(uint32_t) : sizeof(size_t)); - void *buckets = malloc(bytes); + void *buckets = scr_mem_alloc(bytes); if (!buckets) scr_map_oom(); memset(buckets, compact ? 0 : 0xff, bytes); - free(m->buckets); + scr_mem_free(m->buckets); m->buckets = buckets; m->nbuckets = nbuckets; size_t mask = nbuckets - 1; @@ -467,7 +467,7 @@ static size_t scr_map_dense_limit(size_t live) { } static void scr_map_dense_alloc(ScrMap *m, size_t n) { - uint32_t *dense = realloc(m->dense, n * sizeof *dense); + uint32_t *dense = scr_mem_realloc(m->dense, n * sizeof *dense); if (!dense) scr_map_oom(); m->dense = dense; m->ndense = n; @@ -485,7 +485,7 @@ static __attribute__((noinline)) void scr_map_enter_dense(ScrMap *m, double inco if (!scr_map_dense_slot(scr_map_slot_to_f64(m->entries[e].key), limit, &i)) return; if (i > top) top = i; } - free(m->buckets); + scr_mem_free(m->buckets); m->buckets = NULL; m->nbuckets = 0; scr_map_dense_alloc(m, scr_map_dense_size(top)); @@ -505,7 +505,7 @@ static __attribute__((noinline)) bool scr_map_grow_dense(ScrMap *m, double k, si /* Switch to the bucket table; cached entry hashes are already complete. */ static __attribute__((noinline)) void scr_map_leave_dense(ScrMap *m) { - free(m->dense); + scr_mem_free(m->dense); m->dense = NULL; m->ndense = 0; if (m->nentries < SCR_MAP_LINEAR_LIMIT) return; @@ -549,7 +549,7 @@ static void scr_map_compact(ScrMap *m, bool reindex) { if (!dense) return; if (reindex) { if (w == 0) { - free(m->dense); + scr_mem_free(m->dense); m->dense = NULL; m->ndense = 0; return; @@ -571,7 +571,7 @@ static void scr_map_grow_entries(ScrMap *m) { if (cap > SIZE_MAX / 2 / sizeof(ScrMapEntry)) scr_map_oom(); cap *= 2; } - ScrMapEntry *entries = realloc(m->entries, cap * sizeof *entries); + ScrMapEntry *entries = scr_mem_realloc(m->entries, cap * sizeof *entries); if (!entries) scr_map_oom(); m->entries = entries; m->ecap = cap; @@ -651,9 +651,9 @@ static void scr_map_gcfree(void *o) { if (!m->key_trace) scr_map_release_key(m, m->entries[e].key); if (!m->val_trace) scr_map_release_val(m, m->entries[e].val); } - free(m->entries); - free(m->buckets); - free(m->dense); + scr_mem_free(m->entries); + scr_mem_free(m->buckets); + scr_mem_free(m->dense); #ifdef SCR_RC_AUDIT scr_live_maps--; #endif @@ -677,7 +677,7 @@ ScrMap *scr_map_new_typed(ScrMapKeyKind key_kind, ScrMapValKind val_kind, static const ScrCycType cyc_type = { &scr_map_trace, &scr_map_gcfree, SCR_CYC_KIND_MAP }; m = scr_cyc_alloc(sizeof *m, &cyc_type); } else { - m = calloc(1, sizeof *m); + m = scr_mem_calloc(sizeof *m); if (!m) scr_map_oom(); } m->rc = 1; @@ -710,14 +710,14 @@ static void scr_map_destroy(void *object) { scr_map_release_key(m, m->entries[e].key); scr_map_release_val(m, m->entries[e].val); } - free(m->entries); - free(m->buckets); - free(m->dense); + scr_mem_free(m->entries); + scr_mem_free(m->buckets); + scr_mem_free(m->dense); #ifdef SCR_RC_AUDIT scr_live_maps--; #endif if (m->key_trace || m->val_trace) scr_cyc_free(m); - else free(m); + else scr_mem_free(m); } void scr_map_release(ScrMap *m) { @@ -771,7 +771,7 @@ void scr_map_clear(ScrMap *m) { * after the clear append past them and ARE visited (Node-exact). */ m->nentries = 0; if (m->ecap > SCR_MAP_CLEAR_KEEP && m->ecap / 8 > walked) { - free(m->entries); + scr_mem_free(m->entries); m->entries = NULL; m->ecap = 0; } @@ -779,13 +779,13 @@ void scr_map_clear(ScrMap *m) { /* Without either index, a map is a linear scan; dead entries kept for an * active iteration are skipped by every later rebuild. */ if (m->ndense > SCR_MAP_CLEAR_KEEP && m->ndense / 32 > walked) { - free(m->dense); + scr_mem_free(m->dense); m->dense = NULL; m->ndense = 0; } if (m->nbuckets) { if (m->nbuckets > SCR_MAP_CLEAR_KEEP && m->nbuckets / 8 > walked) { - free(m->buckets); + scr_mem_free(m->buckets); m->buckets = NULL; m->nbuckets = 0; } else { @@ -1249,7 +1249,7 @@ ScrMap *scr_map_clone(const ScrMap *source, bool keys_only) { size_t count = source->nlive; if (!count) return out; if (count > SIZE_MAX / sizeof(ScrMapEntry) || count > SIZE_MAX / 2 / sizeof(size_t)) scr_map_oom(); - out->entries = malloc(count * sizeof(ScrMapEntry)); + out->entries = scr_mem_alloc(count * sizeof(ScrMapEntry)); if (!out->entries) scr_map_oom(); out->ecap = count; size_t top = 0;