Memory

I’m Back

Last week was my birthday (all week), and I decided to gift myself fewer blog posts.

Now that things are back to normal, however, I’ll be easing my way back into the blogosphere. Today I’m going to be looking briefly at the benefits of code from a recent MR by Erik Faye-Lund which improves memory allocation in ntv to let us better track all the allocations done in the course of doing compiler stuff, with the key benefit being that we’re no longer leaking this entire thing every time we compile a shader (oops).

Mesa internals use an allocator called ralloc, and this was first added to the tree by Kenneth Graunke way back in 7.10.1, or at least that was the earliest reference of it I could find. It’s used everywhere, and it has a number of nice features that justify its use other than simple efficiency.

For this post, I’ll be looking specifically at its memory context capability.

Contextualize

Typically in C, we have our calls to malloc, and we have to track all these allocations, ensuring ownership is preserved, and then later call free at an appropriate time on everything that we’ve allocated.

Not so with the magic of ralloc!

A call to ralloc_context() creates a new memory context. Subsequent allocations for related objects requiring allocation can then pass this context to the related ralloc_size() calls, and this allocation will then be added to the specified context. At a later point, the context can be freed, taking all the allocated memory with it.

This enables code like:

void *ctx = ralloc_context(NULL);
void *some_mem1 = ralloc_size(ctx, some_size1);
void *some_mem2 = ralloc_size(ctx, some_size2);
void *some_mem3 = ralloc_size(ctx, some_size3);
ralloc_free(ctx);

which will free all allocations created using ctx.

But wait, there’s more! Every allocation made using ralloc is implicitly a context like this, which means that any time a struct is created with allocated members, the struct can be passed as a context, meaning that a destructor function for that struct can simply be written as a call to ralloc_free() which passes the struct, and newly-added members don’t need to be deallocated.

So for example:

struct somestruct *s = ralloc_size(NULL, sizeof(struct somestruct));
s->member1 = ralloc_size(s, sizeof(member1_size));
ralloc_free(s);

will free the struct and member, but also adding e.g.,:

s->member2 = ralloc_size(s, sizeof(member2_size));

for a new member at some point requires no changes to the deallocation codepath.

Future Work

At some point, it’s likely we’ll be going through the zink codebase to add more ralloc usage, both for helping to avoid memory leaks and for the other more general benefits that memory allocators bring over our existing usage of raw malloc.

Written on July 20, 2020