Bug in maybe_repurpose_single_chunk_large_objects_head
Since clang 20.1.0 maybe_repurpose_single_chunk_large_objects_head gets compiled in a way that surfaces what I assume is a pointer aliasing issue.
I'll try to explain what I think is happening by commenting the function.
static void maybe_repurpose_single_chunk_large_objects_head(void) {
if (large_objects->size < CHUNK_SIZE) {
unsigned idx = get_chunk_index(large_objects);
char *ptr = allocate_chunk(get_page(large_objects), idx, GRANULES_32);
// 'ptr', 'large_objects', and 'head' can point to the same memory location
// However the compiler seems to think that they cannot alias
// As such it thinks it can delay the following assignment until later in the function
large_objects = large_objects->next;
struct freelist* head = (struct freelist *)ptr;
head->next = small_object_freelists[GRANULES_32];
small_object_freelists[GRANULES_32] = head;
}
}
So instead the code gets compiled as if it looked like this:
static void maybe_repurpose_single_chunk_large_objects_head(void) {
if (large_objects->size < CHUNK_SIZE) {
unsigned idx = get_chunk_index(large_objects);
char *ptr = allocate_chunk(get_page(large_objects), idx, GRANULES_32);
struct freelist* head = (struct freelist *)ptr;
head->next = small_object_freelists[GRANULES_32]; // This writes to large_objects->next through pointer aliasing
large_objects = large_objects->next; // This assignment now happens after the previous assignment so
// the wrong value gets assigned to 'large_objects'
small_object_freelists[GRANULES_32] = head;
}
}
I've extracted the function into compiler explorer with both clang 19.1.0 and clang 20.1.0 as compilers here: https://godbolt.org/z/oajj7Tohj There you can observe that the assignment to large_objects happens later in the function.
i32.load large_objects
i32.load 0
i32.store large_objects
For my own purposes I rewrote the function as follows, which seems to compile correctly for now, but I don't know if it actually solves the issue, it might just be another compiler revision away before the issue pops up again. It would take some extra work to zero in on the root cause and to figure out what a proper fix actually looks like.
static void maybe_repurpose_single_chunk_large_objects_head(void) {
struct large_object *large_head = large_objects;
if (large_head->size < CHUNK_SIZE) {
large_objects = large_head->next;
unsigned idx = get_chunk_index(large_head);
void *ptr = allocate_chunk(get_page(large_head), idx, GRANULES_32);
struct freelist* head = (struct freelist *)ptr;
head->next = small_object_freelists[GRANULES_32];
small_object_freelists[GRANULES_32] = head;
}
}