dev-docs/RFCs/v4.0.0/overrideable-allocators-rfc.md
This is a proposal for adding a mechanism for users of the H3 library to provide heap allocator instead of the default malloc implementation.
This will address the following use cases:
Most H3 functions accept memory from the caller in order to avoid this problem. This will still be the preferred way
to handle memory management in H3. Stack allocation is avoided because H3 cannot know whether there is sufficient stack
memory available. (Note that _kRingInternal/kRingDistances implicitly uses stack allocation because it implements DFS
recursively.)
A few functions in H3 do heap allocate memory because it is not feasible to do otherwise, or as a convenience. The functions that heap allocate are:
| Function | Reason |
|---|---|
kRing | Convenience wrapper around kRingDistances |
polyfill | Convenience (could be passed in, requires internal knowledge) |
compact | Convenience (could be passed in, requires internal knowledge) |
h3SetToLinkedGeo | Requires knowledge of how to initialize the internal struct |
destroyLinkedPolygon | Required for h3SetToLinkedGeo |
Reading materials to reference:
vector (via templates)SDL_SetMemoryFunctions)palloc)All approaches assume the user has defined the following functions:
void* my_malloc(size_t size);
void* my_calloc(size_t count, size_t size);
void my_free(void* pointer);
// TODO: Do we want my_realloc?
In this approach, H3 stores the allocation functions in a set of static variables.
h3SetAllocator(&my_alloc, &my_calloc, &my_free);
// call into H3 as before
polyfill(geoPolygon, res, out);
Pro:
Con:
This approach is similar to how C++ handles allocator replacement in its standard library, by accepting the allocator as a template argument. However, H3 is written in C and must implement templates using macros.
POLYFILL_WITH_ALLLOCATORS(my_polyfill, my_malloc, my_calloc, my_free);
// Call the function created by the template
my_polyfill(geoPolygon, res, out);
Pro:
Con:
In this approach, every function call includes allocators.
H3MemoryManager allocFunctions = {
.malloc = &my_malloc,
.calloc = &my_calloc,
.free = &my_free
};
polyfill(geoPolygon, res, out, &allocFunctions);
Pro:
Con:
#define approachIn this approach, the allocators are specified at build time.
# In build process:
cmake -DH3_ALLOC_PREFIX=my_ ...
// in source file, functions are used as before.
Alternately, instead of setting a prefix, the build could accept individual options
for functions, such as -DH3_MALLOC=my_malloc -DH3_CALLOC=my_calloc. (Although this
could allow a user to accidentally override malloc but not free, which is generally
very bad.)
Pro:
Con:
#define based allocator replacement seems like the clearest and lowest overhead to implement, while still supporting
the full range of use cases. A user could optionally implement a more complicated replacement inside their custom
allocator functions.