Skip to content

Bug in maybe_repurpose_single_chunk_large_objects_head #8

@Maato

Description

@Maato

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;
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions