Stacky Heap

A new memory management approach

by Marcel Garus · 2026-02-17 · available at www.marcelgarus.dev/stacky-heap

I recently implemented garbage collection for Orchard, a self-hosted programming system with immutable values.

A straightforward solution would have been reference counting: You store a counter at every object of how many references point to it and you free the memory when that counter reaches zero. Reference counting is a good fit for languages with immutable values and eager evaluation; because values can only refer to previously created values, there are no cycles, avoiding the major weakness of reference counting. Even better, some languages like Koka use the reference count to opportunistically mutate values while keeping immutable semantics if the count is 1.

However, reference counting has downsides: The operations for adjusting the counter can be expensive. And exception handling becomes complicated because if you only executed part of some function, the reference counters might be in an inconsistent state.

I want to support exceptions in Orchard, so I took another approach: A stacky heap.

In the beginning, I allocate a big area of memory. Whenever you need memory for a new value, I give out some of that memory, from left to right. This way of giving out memory is well-established and usually called "bump allocation" or "arena allocation".

Now comes the cool part: Because the immutable objects can only refer to previously created objects, pointers inside the heap only ever point to the left. This enables partial garbage collection! When you call a function that does allocation-heavy work, but produces a small value, you can garbage-collect only the values allocated by that function.

The way this is surfaced in the language is through a built-in collect_garbage function. It's a higher-order function that takes a lambda without arguments. It just runs the lambda and returns its value, but it frees all intermediary objects that are not referenced by the return value.

input = ...
output = (collect_garbage (\ () (expensive_operation input)))

Partial garbage collection

Let's say some values are allocated on the heap:

a b c d

Let's say we want to call a function with a and c and garbage collect all its intermediary allocations. To do that, we remember the current size of the heap. Then, we just call the function, which may allocate lots of objects:

a b c d e f g h i j k

Let's say k is the return value. We can do a standard mark-and-sweep garbage collection on everything after the call boundary: Starting at k, we do a depth-first traversal of all dependencies, marking them as needed. If we cross the call boundary (the blue line), we stop marking.

a b c d e f g h i j k

Then, starting at the call boundary, we go over the objects. We keep only marked objects, compacting them together and overwriting the other ones.

a b c d e h k

What even is this?

In Borrow checking, RC, GC, and the Eleven (!) Other Memory Safety Approaches, Evan Ovadia describes how several memory safety approaches can be blended together. My stacky heap combines aspects of several techniques:

So, there you have it: I created a new memory management approach that combines aspects from arena allocation, tracing garbage collection, and manual memory management. I'm curious how this adventure continues.