1#ifndef _GNU_SOURCE
  2#   define _GNU_SOURCE
  3#endif
  4
  5#include <stdint.h>
  6#include <stdio.h>
  7#include <stdlib.h>
  8#include <string.h>
  9#include <sys/mman.h>
 10
 11#include "alignment.h"
 12#include "mmarray.h"
 13
 14typedef struct {
 15    unsigned capacity;
 16    unsigned max_capacity;
 17    unsigned length;
 18    unsigned item_size;
 19} ArrayHeader;
 20
 21
 22[[nodiscard]] static size_t calc_memsize(unsigned capacity, unsigned item_size)
 23{
 24    return align_unsigned_to_page(sizeof(ArrayHeader) + ((size_t) capacity) * item_size);
 25}
 26
 27[[nodiscard]] static inline ArrayHeader* get_array_header(void* array)
 28{
 29    return (ArrayHeader*) (
 30        ((ptrdiff_t) array) & ~(ptrdiff_t) (sys_page_size() - 1)  // header starts on page boundary
 31    );
 32}
 33
 34[[nodiscard]] void* mmarray_allocate(unsigned max_capacity, unsigned length, unsigned item_size)
 35{
 36    ArrayHeader* a;
 37    size_t memsize;
 38
 39    if (max_capacity) {
 40        // capped array
 41        // reserve address space
 42        memsize = calc_memsize(max_capacity, item_size);
 43        a = mmap(NULL, memsize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 44        if (a == MAP_FAILED) {
 45            perror("mmap");
 46            abort();
 47        }
 48        // commit pages for `length` items
 49        memsize = calc_memsize(length, item_size);
 50        if (mprotect(a, memsize, PROT_READ | PROT_WRITE)) {
 51            perror("mprotect");
 52            abort();
 53        }
 54    } else {
 55        // relocatable array
 56        // allocate pages
 57        memsize = calc_memsize(length, item_size);
 58        a = mmap(NULL, memsize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
 59        if (a == MAP_FAILED) {
 60            perror("mmap");
 61            abort();
 62        }
 63    }
 64    a->capacity = (memsize - sizeof(ArrayHeader)) / item_size;
 65    if (a->capacity == 0) {
 66        fprintf(stderr, "mmarray_allocate: item size %u too big\n", item_size);
 67        abort();
 68    }
 69    a->max_capacity = max_capacity;
 70    a->length       = length;
 71    a->item_size    = item_size;
 72
 73    // return pointer to the array data
 74    return a + 1;
 75}
 76
 77[[nodiscard]] void* mmarray_grow(void* array, unsigned increment)
 78{
 79    ArrayHeader* a = get_array_header(array);
 80
 81    if (a->length + increment > a->capacity) {
 82
 83        size_t old_memsize = calc_memsize(a->capacity, a->item_size);
 84        size_t new_memsize = calc_memsize(a->length + increment, a->item_size);
 85
 86        if (a->max_capacity) {
 87            if (a->length + increment > a->max_capacity) {
 88                fprintf(stderr, "mmarray_grow: reached maximum capacity of %u items\n", a->max_capacity);
 89                abort();
 90            }
 91            if (mprotect(((uint8_t*) a) + old_memsize, new_memsize - old_memsize, PROT_READ | PROT_WRITE)) {
 92                perror("mprotect");
 93                abort();
 94            }
 95        } else {
 96            ArrayHeader* new_array = mremap(a, old_memsize, new_memsize, MREMAP_MAYMOVE);
 97            if (new_array == MAP_FAILED) {
 98                perror("mremap");
 99                abort();
100            }
101            a = new_array;
102            a->capacity = (new_memsize - sizeof(ArrayHeader)) / a->item_size;
103        }
104    }
105    a->length += increment;
106
107    // return pointer to the array data
108    return a + 1;
109}
110
111void mmarray_reset(void* array, unsigned new_item_size)
112{
113    ArrayHeader* a = get_array_header(array);
114
115    size_t memsize = calc_memsize(a->capacity, a->item_size);
116    a->capacity = (memsize - sizeof(ArrayHeader)) / new_item_size;
117    if (a->capacity == 0) {
118        fprintf(stderr, "mmarray_reset: new item size %u too big\n", new_item_size);
119        abort();
120    }
121    a->length = 0;
122    a->item_size = new_item_size;
123}
124
125void mmarray_free(void* array)
126{
127    ArrayHeader* a = get_array_header(array);
128    size_t memsize;
129
130    if (a->max_capacity) {
131        memsize = calc_memsize(a->max_capacity, a->item_size);
132    } else {
133        memsize = calc_memsize(a->capacity, a->item_size);
134    }
135    if (munmap(a, memsize) == -1) {
136        perror("munmap");
137    }
138}
139
140[[nodiscard]] void* mmarray_append_item(void* array, void* item)
141{
142    unsigned index = mmarray_length(array);
143
144    array = mmarray_grow(array, 1);
145    ArrayHeader* a = get_array_header(array);
146
147    memcpy(((char*) array) + (index * a->item_size), item, a->item_size);
148
149    // return pointer to the array data
150    return a + 1;
151}
152
153[[nodiscard]] unsigned mmarray_length(void* array)
154{
155    return get_array_header(array)->length;
156}
157
158[[nodiscard]] unsigned mmarray_capacity(void* array)
159{
160    return get_array_header(array)->capacity;
161}