runners: Added stack/heap measurement functions

These have been battle-tested in external benchmarks, and have proven
useful for finding a runtime estimate on stack+heap usage.

Of course, to be realistic they need to be cross-compiled and run under
QEMU (which does work!), but even on x86_64 they provide a nice insight
into RAM usage. In practice the only real difference is pointer width
anyways.

---

Enabled by default for the bench runner, these are available if
TEST/BENCH_YES_HEAP and/or TEST/BENCH_YES_STACK are defined.

(This default is provided by the Makefile. At least heap measurements
rely on linker flags, so it probably doesn't make sense to default
enable in the bench runner itself.)

Stack vs heap rely on slightly different mechanisms:

- Stack: Uses GCC's __builtin_frame_address(0) to measure the current
  stack usage on entry to every bd operation.

- Heap: Relies on GCC's -Wl,--wrap flags to intercept every malloc/free
  call, to track the current heap usage.

These are available via bench/test macros:

- BENCH_STACK()         - Maximum stack usage of the current run
- BENCH_STACK_CURRENT() - Current stack usage
- BENCH_HEAP()          - Maximum heap usage of the current run
- BENCH_HEAP_CURRENT()  - Current heap usage

Note BENCH_STACK_CURRENT() can be useful for separating out the bench's
ctx from total stack usage, similarly to our static analysis.

---

One surprising outcome is that these heap hooks trivially implement a
memory leak detector. Maybe that could be useful in the test_runner as a
cheaper alternative to Valgrind?
This commit is contained in:
Christopher Haster
2026-01-26 03:54:03 -06:00
parent 23ac67bf34
commit 356d7065be
5 changed files with 866 additions and 43 deletions
+28
View File
@@ -124,6 +124,34 @@ void bench_permutation(size_t i, uint32_t *buffer, size_t size);
#define BENCH_FACTORIAL(x) bench_factorial(x)
#define BENCH_PERMUTATION(i, buffer, size) bench_permutation(i, buffer, size)
#ifdef BENCH_YES_STACK
// get the maximum/current stack usage for this run
size_t bench_stack(void);
__attribute__((noinline))
size_t bench_stack_current(void);
__attribute__((noinline))
void bench_stack_pause(void);
void bench_stack_resume(void);
#define BENCH_STACK() bench_stack()
#define BENCH_STACK_CURRENT() bench_stack_current()
#define BENCH_STACK_PAUSE() bench_stack_pause()
#define BENCH_STACK_RESUME() bench_stack_resume()
#endif
#ifdef BENCH_YES_HEAP
// get the maximum/current heap usage for this run
size_t bench_heap(void);
size_t bench_heap_current(void);
void bench_heap_pause(void);
void bench_heap_resume(void);
#define BENCH_HEAP() bench_heap()
#define BENCH_HEAP_CURRENT() bench_heap_current()
#define BENCH_HEAP_PAUSE() bench_heap_pause()
#define BENCH_HEAP_RESUME() bench_heap_resume()
#endif
// declare implicit defines as global intmax_ts
#define BENCH_DEFINE(k, v) \