mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Count program-lifetime objects in memoryUsage and fix the runtime tests
- process.memoryUsage(): heapTotal counts the region's chunks that threads hold, minus the chunks discarded workers returned, and heapUsed counts them less the tails threads left unused when they moved to a new chunk. - The region builds only where it can be used: Linux runtimes with the size-class allocator, which excludes the sanitizer runtimes (scriptc keeps 8-byte references there). The standalone allocator, cycle and young-nursery tests link again: the allocator test defines the scr_trap its region needs, and checks the region (below 32 GiB, aligned, zeroed, chunks larger than one, heap statistics, threads). - scr_plr_chunk has an initializer, like the other runtime globals.
This commit is contained in:
@@ -1930,6 +1930,10 @@ void scr_mem_order_for_free(void **ptrs, size_t n, size_t offset) {
|
||||
* mallinfo2, the Darwin malloc zones, the Windows CRT heap, or the
|
||||
* sanitizer's allocator in sanitized builds). Walks the free lists, so
|
||||
* the cost grows with the number of free blocks. */
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
static void scr_plr_heap_stats(size_t *held, size_t *used);
|
||||
#endif
|
||||
|
||||
void scr_heap_stats(double *total, double *used) {
|
||||
size_t held = 0, idle = 0;
|
||||
#if SCR_SMALL_ALLOC
|
||||
@@ -1979,6 +1983,13 @@ void scr_heap_stats(double *total, double *used) {
|
||||
sys_held = s.committed;
|
||||
sys_used = s.allocated;
|
||||
}
|
||||
#endif
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
/* Compact class instances (the program-lifetime region below). */
|
||||
size_t region_held, region_used;
|
||||
scr_plr_heap_stats(®ion_held, ®ion_used);
|
||||
sys_held += region_held;
|
||||
sys_used += region_used;
|
||||
#endif
|
||||
*total = (double)(held + sys_held);
|
||||
*used = (double)(held - (idle < held ? idle : held) + sys_used);
|
||||
@@ -1986,7 +1997,8 @@ void scr_heap_stats(double *total, double *used) {
|
||||
|
||||
/* ── program-lifetime region ────────────────────────────────────────────
|
||||
* Instances of compact classes (llvm/classes.ts LlClassMeta.compact) in
|
||||
* programs built for Linux without sanitizers live in one address range
|
||||
* programs built for Linux without sanitizers (runtimes with the size-class
|
||||
* allocator, SCR_SMALL_ALLOC) live in one address range
|
||||
* reserved for the whole process below 32 GiB, so a field that holds one
|
||||
* can store its address over 8 in 32 bits (0 stays NULL). The instances
|
||||
* are never freed. Each thread carves them from its own chunks of
|
||||
@@ -1994,7 +2006,7 @@ void scr_heap_stats(double *total, double *used) {
|
||||
* Pages are committed by first touch and start zeroed. Its strings are
|
||||
* arrays rather than literals: programs without compact classes then link
|
||||
* none of this, where literals would stay in the unit's merged strings. */
|
||||
#if defined(__linux__) && SIZE_MAX > UINT32_MAX
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define SCR_PLR_CHUNK ((size_t)1 << 20)
|
||||
@@ -2012,10 +2024,13 @@ void scr_heap_stats(double *total, double *used) {
|
||||
static char *scr_plr_base; /* the range's start, once reserved */
|
||||
static size_t scr_plr_size;
|
||||
static size_t scr_plr_top; /* atomic */
|
||||
/* Bytes of the chunks threads hold, and of the tails they left unused when
|
||||
* they moved to a new chunk (scr_heap_stats); atomic. */
|
||||
static size_t scr_plr_held, scr_plr_waste;
|
||||
#if defined(SCR_WORKERS) || (defined(SCR_LIB) && defined(SCR_THREAD_INSTANCES))
|
||||
_Thread_local ScrPlrChunk scr_plr_chunk;
|
||||
_Thread_local ScrPlrChunk scr_plr_chunk = {NULL, NULL};
|
||||
#else
|
||||
ScrPlrChunk scr_plr_chunk;
|
||||
ScrPlrChunk scr_plr_chunk = {NULL, NULL};
|
||||
#endif
|
||||
|
||||
/* Reserves `size` bytes at `at` exactly; NULL if refused. */
|
||||
@@ -2072,6 +2087,7 @@ static __attribute__((noinline)) void scr_plr_reserve(void) {
|
||||
* chunk a thread takes registers scr_plr_thread_end. */
|
||||
#define SCR_PLR_LINK 16
|
||||
static _Thread_local char *scr_plr_mine;
|
||||
static _Thread_local size_t scr_plr_mine_waste; /* this thread's part of scr_plr_waste */
|
||||
static pthread_key_t scr_plr_end_key;
|
||||
static pthread_once_t scr_plr_end_once = PTHREAD_ONCE_INIT;
|
||||
static pthread_mutex_t scr_plr_free_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
@@ -2112,6 +2128,9 @@ static void scr_plr_thread_end(void *unused) {
|
||||
c = next;
|
||||
}
|
||||
static const char stats_line[] = "scr_sa: thread returned %zu MB of program-lifetime objects\n";
|
||||
__atomic_fetch_sub(&scr_plr_held, returned, __ATOMIC_RELAXED);
|
||||
__atomic_fetch_sub(&scr_plr_waste, scr_plr_mine_waste, __ATOMIC_RELAXED);
|
||||
scr_plr_mine_waste = 0;
|
||||
const char *stats = getenv("SCR_SA_STATS");
|
||||
if (stats && stats[0] == '1') fprintf(stderr, stats_line, returned >> 20);
|
||||
}
|
||||
@@ -2162,6 +2181,14 @@ static __attribute__((noinline)) void *scr_plr_refill(size_t n) {
|
||||
#ifdef SCR_SA_TRIM
|
||||
scr_plr_own(p, chunk);
|
||||
#endif
|
||||
if (scr_plr_chunk.next) {
|
||||
size_t left = (size_t)(scr_plr_chunk.limit - scr_plr_chunk.next);
|
||||
__atomic_fetch_add(&scr_plr_waste, left, __ATOMIC_RELAXED);
|
||||
#ifdef SCR_SA_TRIM
|
||||
scr_plr_mine_waste += left;
|
||||
#endif
|
||||
}
|
||||
__atomic_fetch_add(&scr_plr_held, chunk, __ATOMIC_RELAXED);
|
||||
scr_plr_chunk.next = p + SCR_PLR_LINK + n;
|
||||
scr_plr_chunk.limit = p + chunk;
|
||||
return p + SCR_PLR_LINK;
|
||||
@@ -2173,6 +2200,13 @@ void scr_plr_init(void) {
|
||||
if (!__atomic_load_n(&scr_plr_base, __ATOMIC_ACQUIRE)) scr_plr_reserve();
|
||||
}
|
||||
|
||||
static void scr_plr_heap_stats(size_t *held, size_t *used) {
|
||||
size_t h = __atomic_load_n(&scr_plr_held, __ATOMIC_RELAXED);
|
||||
size_t w = __atomic_load_n(&scr_plr_waste, __ATOMIC_RELAXED);
|
||||
*held = h;
|
||||
*used = w < h ? h - w : 0;
|
||||
}
|
||||
|
||||
void *scr_plr_alloc(size_t n) {
|
||||
n = (n + 7) & ~(size_t)7;
|
||||
char *p = scr_plr_chunk.next;
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
static bool owned(const void *p) { return (uintptr_t)p - scr_sa.base < scr_sa.span; }
|
||||
#endif
|
||||
|
||||
/* The allocator unit needs only scr_trap from the rest of the runtime: the
|
||||
* program-lifetime region stops the program through it. */
|
||||
_Noreturn void scr_trap(const char *msg) {
|
||||
fputs(msg, stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
static void fill(unsigned char *p, size_t n, unsigned char seed) {
|
||||
for (size_t i = 0; i < n; i++) p[i] = (unsigned char)(seed + i * 7);
|
||||
}
|
||||
@@ -962,6 +969,60 @@ static void check_discard(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
/* The program-lifetime region hands out zeroed, 8-byte aligned blocks below
|
||||
* 32 GiB, in order from the calling thread's chunk; a block larger than a
|
||||
* chunk gets chunks of its own. Heap statistics count the chunks. Builds
|
||||
* with AddressSanitizer have no region (its shadow memory takes the range),
|
||||
* and scriptc builds with sanitizers keep pointers instead. */
|
||||
static void check_program_lifetime_region(void) {
|
||||
double total0, used0, total1, used1;
|
||||
scr_heap_stats(&total0, &used0);
|
||||
scr_plr_init();
|
||||
unsigned char *a = scr_plr_alloc(20), *b = scr_plr_alloc(40);
|
||||
assert(((uintptr_t)a & 7) == 0 && b == a + 24);
|
||||
assert((uintptr_t)a >= ((uintptr_t)4 << 30) && (uintptr_t)b + 40 <= ((uintptr_t)32 << 30));
|
||||
for (size_t i = 0; i < 40; i++) assert(b[i] == 0);
|
||||
size_t n = (size_t)3 << 20;
|
||||
unsigned char *big = scr_plr_alloc(n);
|
||||
assert(((uintptr_t)big & 7) == 0 && (uintptr_t)big + n <= ((uintptr_t)32 << 30));
|
||||
for (size_t i = 0; i < n; i += 4096) assert(big[i] == 0);
|
||||
fill(big, n, 3);
|
||||
verify(big, n, 3);
|
||||
scr_heap_stats(&total1, &used1);
|
||||
assert(total1 - total0 >= (double)n && used1 - used0 >= (double)n && used1 <= total1);
|
||||
}
|
||||
|
||||
#ifdef SCR_WORKERS
|
||||
/* Threads carve the region concurrently: every block is zeroed and keeps
|
||||
* what its thread wrote. */
|
||||
#define REGION_THREADS 4u
|
||||
#define REGION_BLOCKS 20000u
|
||||
static unsigned char *region_blocks[REGION_THREADS][REGION_BLOCKS];
|
||||
|
||||
static void *region_alloc(void *arg) {
|
||||
unsigned t = (unsigned)(uintptr_t)arg;
|
||||
for (unsigned i = 0; i < REGION_BLOCKS; i++) {
|
||||
unsigned char *p = scr_plr_alloc(24);
|
||||
for (size_t k = 0; k < 24; k++) assert(p[k] == 0);
|
||||
fill(p, 24, (unsigned char)(t * 31 + i));
|
||||
region_blocks[t][i] = p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void check_program_lifetime_region_threads(void) {
|
||||
pthread_t threads[REGION_THREADS];
|
||||
for (unsigned t = 0; t < REGION_THREADS; t++)
|
||||
assert(pthread_create(&threads[t], NULL, region_alloc, (void *)(uintptr_t)t) == 0);
|
||||
for (unsigned t = 0; t < REGION_THREADS; t++) assert(pthread_join(threads[t], NULL) == 0);
|
||||
for (unsigned t = 0; t < REGION_THREADS; t++)
|
||||
for (unsigned i = 0; i < REGION_BLOCKS; i++)
|
||||
verify(region_blocks[t][i], 24, (unsigned char)(t * 31 + i));
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
int main(void) {
|
||||
#if SCR_SMALL_ALLOC
|
||||
check_slice_growth(); /* first: its class must be untouched */
|
||||
@@ -975,6 +1036,12 @@ int main(void) {
|
||||
#if SCR_SMALL_ALLOC && defined(__linux__) && defined(__GLIBC__)
|
||||
check_heap_huge_pages();
|
||||
#endif
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
check_program_lifetime_region();
|
||||
#ifdef SCR_WORKERS
|
||||
check_program_lifetime_region_threads();
|
||||
#endif
|
||||
#endif
|
||||
#if defined(SCR_WORKERS) && SCR_SMALL_ALLOC
|
||||
/* A short discard delay, which stays at its start until check_discard_delay_grows. */
|
||||
setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1);
|
||||
|
||||
@@ -308,7 +308,7 @@ void scr_cyc_free(void *obj); /* frees the block, header included */
|
||||
|
||||
void *scr_mem_realloc(void *p, size_t n);
|
||||
void *scr_rt_calloc(size_t n); /* emitted-code entry points */
|
||||
#if defined(__linux__) && SIZE_MAX > UINT32_MAX
|
||||
#if defined(__linux__) && SCR_SMALL_ALLOC
|
||||
/* The program-lifetime region (scr_alloc.c): compact class instances of
|
||||
* programs built for Linux without sanitizers (llvm/emitter.ts
|
||||
* lowRegionTarget), which are never freed, at 8-byte aligned addresses below
|
||||
|
||||
Reference in New Issue
Block a user