libc: fix declaration: use size_t instead of vm_size_t
[dragonfly.git] / lib / libc / stdlib / nmalloc.c
index 8392614..739d738 100644 (file)
@@ -177,10 +177,13 @@ typedef struct slglobaldata {
  * The WEIRD_ADDR is used as known text to copy into free objects to
  * try to create deterministic failure cases if the data is accessed after
  * free.
+ *
+ * WARNING: A limited number of spinlocks are available, BIGXSIZE should
+ *         not be larger then 64.
  */
 #define WEIRD_ADDR      0xdeadc0de
 #define MAX_COPY        sizeof(weirdary)
-#define ZERO_LENGTH_PTR        ((void *)-8)
+#define ZERO_LENGTH_PTR        ((void *)&malloc_dummy_pointer)
 
 #define BIGHSHIFT      10                      /* bigalloc hash table */
 #define BIGHSIZE       (1 << BIGHSHIFT)
@@ -216,6 +219,7 @@ static struct slglobaldata  SLGlobalData[SLGD_MAX];
 static bigalloc_t bigalloc_array[BIGHSIZE];
 static spinlock_t bigspin_array[BIGXSIZE];
 static int malloc_panic;
+static int malloc_dummy_pointer;
 
 static const int32_t weirdary[16] = {
        WEIRD_ADDR, WEIRD_ADDR, WEIRD_ADDR, WEIRD_ADDR,
@@ -281,7 +285,8 @@ _bigalloc_hash(void *ptr)
 {
        int hv;
 
-       hv = ((int)ptr >> PAGE_SHIFT) ^ ((int)ptr >> (PAGE_SHIFT + BIGHSHIFT));
+       hv = ((int)(intptr_t)ptr >> PAGE_SHIFT) ^
+             ((int)(intptr_t)ptr >> (PAGE_SHIFT + BIGHSHIFT));
 
        return(hv);
 }
@@ -402,7 +407,12 @@ zoneindex(size_t *bytes, size_t *chunking)
 void *
 malloc(size_t size)
 {
-       return(_slaballoc(size, 0));
+       void *ptr;
+
+       ptr = _slaballoc(size, 0);
+       if (ptr == NULL)
+               errno = ENOMEM;
+       return(ptr);
 }
 
 /*
@@ -411,7 +421,12 @@ malloc(size_t size)
 void *
 calloc(size_t number, size_t size)
 {
-       return(_slaballoc(number * size, SAFLAG_ZERO));
+       void *ptr;
+
+       ptr = _slaballoc(number * size, SAFLAG_ZERO);
+       if (ptr == NULL)
+               errno = ENOMEM;
+       return(ptr);
 }
 
 /*
@@ -424,9 +439,123 @@ calloc(size_t number, size_t size)
 void *
 realloc(void *ptr, size_t size)
 {
-       return(_slabrealloc(ptr, size));
+       ptr = _slabrealloc(ptr, size);
+       if (ptr == NULL)
+               errno = ENOMEM;
+       return(ptr);
 }
 
+/*
+ * posix_memalign()
+ *
+ * Allocate (size) bytes with a alignment of (alignment), where (alignment)
+ * is a power of 2 >= sizeof(void *).
+ *
+ * The slab allocator will allocate on power-of-2 boundaries up to
+ * at least PAGE_SIZE.  We use the zoneindex mechanic to find a
+ * zone matching the requirements, and _vmem_alloc() otherwise.
+ */
+int
+posix_memalign(void **memptr, size_t alignment, size_t size)
+{
+       bigalloc_t *bigp;
+       bigalloc_t big;
+       size_t chunking;
+       int zi;
+
+       /*
+        * OpenGroup spec issue 6 checks
+        */
+       if ((alignment | (alignment - 1)) + 1 != (alignment << 1)) {
+               *memptr = NULL;
+               return(EINVAL);
+       }
+       if (alignment < sizeof(void *)) {
+               *memptr = NULL;
+               return(EINVAL);
+       }
+
+       /*
+        * Our zone mechanism guarantees same-sized alignment for any
+        * power-of-2 allocation.  If size is a power-of-2 and reasonable
+        * we can just call _slaballoc() and be done.  We round size up
+        * to the nearest alignment boundary to improve our odds of
+        * it becoming a power-of-2 if it wasn't before.
+        */
+       if (size <= alignment)
+               size = alignment;
+       else
+               size = (size + alignment - 1) & ~(size_t)(alignment - 1);
+       if (size < PAGE_SIZE && (size | (size - 1)) + 1 == (size << 1)) {
+               *memptr = _slaballoc(size, 0);
+               return(*memptr ? 0 : ENOMEM);
+       }
+
+       /*
+        * Otherwise locate a zone with a chunking that matches
+        * the requested alignment, within reason.   Consider two cases:
+        *
+        * (1) A 1K allocation on a 32-byte alignment.  The first zoneindex
+        *     we find will be the best fit because the chunking will be
+        *     greater or equal to the alignment.
+        *
+        * (2) A 513 allocation on a 256-byte alignment.  In this case
+        *     the first zoneindex we find will be for 576 byte allocations
+        *     with a chunking of 64, which is not sufficient.  To fix this
+        *     we simply find the nearest power-of-2 >= size and use the
+        *     same side-effect of _slaballoc() which guarantees
+        *     same-alignment on a power-of-2 allocation.
+        */
+       if (size < PAGE_SIZE) {
+               zi = zoneindex(&size, &chunking);
+               if (chunking >= alignment) {
+                       *memptr = _slaballoc(size, 0);
+                       return(*memptr ? 0 : ENOMEM);
+               }
+               if (size >= 1024)
+                       alignment = 1024;
+               if (size >= 16384)
+                       alignment = 16384;
+               while (alignment < size)
+                       alignment <<= 1;
+               *memptr = _slaballoc(alignment, 0);
+               return(*memptr ? 0 : ENOMEM);
+       }
+
+       /*
+        * If the slab allocator cannot handle it use vmem_alloc().
+        *
+        * Alignment must be adjusted up to at least PAGE_SIZE in this case.
+        */
+       if (alignment < PAGE_SIZE)
+               alignment = PAGE_SIZE;
+       if (size < alignment)
+               size = alignment;
+       size = (size + PAGE_MASK) & ~(size_t)PAGE_MASK;
+       *memptr = _vmem_alloc(size, alignment, 0);
+       if (*memptr == NULL)
+               return(ENOMEM);
+
+       big = _slaballoc(sizeof(struct bigalloc), 0);
+       if (big == NULL) {
+               _vmem_free(*memptr, size);
+               *memptr = NULL;
+               return(ENOMEM);
+       }
+       bigp = bigalloc_lock(*memptr);
+       big->base = *memptr;
+       big->bytes = size;
+       big->unused01 = 0;
+       big->next = *bigp;
+       *bigp = big;
+       bigalloc_unlock(*memptr);
+
+       return(0);
+}
+
+/*
+ * free() (SLAB ALLOCATOR) - do the obvious
+ */
 void
 free(void *ptr)
 {
@@ -446,7 +575,7 @@ _slaballoc(size_t size, int flags)
        slzone_t z;
        slchunk_t chunk;
        slglobaldata_t slgd;
-       int chunking;
+       size_t chunking;
        int zi;
 #ifdef INVARIANTS
        int i;
@@ -482,6 +611,10 @@ _slaballoc(size_t size, int flags)
                        return(NULL);
 
                big = _slaballoc(sizeof(struct bigalloc), 0);
+               if (big == NULL) {
+                       _vmem_free(chunk, size);
+                       return(NULL);
+               }
                bigp = bigalloc_lock(chunk);
                big->base = chunk;
                big->bytes = size;
@@ -955,6 +1088,8 @@ chunk_mark_free(slzone_t z, void *chunk)
  *     alignment.
  *
  *     Alignment must be a multiple of PAGE_SIZE.
+ *
+ *     Size must be >= alignment.
  */
 static void *
 _vmem_alloc(size_t size, size_t align, int flags)
@@ -968,22 +1103,28 @@ _vmem_alloc(size_t size, size_t align, int flags)
         */
        addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
                    MAP_PRIVATE|MAP_ANON, -1, 0);
-       if (addr == MAP_FAILED) {
-               errno = ENOMEM;
+       if (addr == MAP_FAILED)
                return(NULL);
-       }
 
        /*
         * Check alignment.  The misaligned offset is also the excess
         * amount.  If misaligned unmap the excess so we have a chance of
         * mapping at the next alignment point and recursively try again.
+        *
+        * BBBBBBBBBBB BBBBBBBBBBB BBBBBBBBBBB  block alignment
+        *   aaaaaaaaa aaaaaaaaaaa aa           mis-aligned allocation
+        *   xxxxxxxxx                          final excess calculation
+        *   ^ returned address
         */
        excess = (uintptr_t)addr & (align - 1);
+
        if (excess) {
+               excess = align - excess;
                save = addr;
-               munmap(save + align - excess, excess);
+
+               munmap(save + excess, size - excess);
                addr = _vmem_alloc(size, align, flags);
-               munmap(save, align - excess);
+               munmap(save, excess);
        }
        return((void *)addr);
 }
@@ -994,7 +1135,7 @@ _vmem_alloc(size_t size, size_t align, int flags)
  *     Free a chunk of memory allocated with _vmem_alloc()
  */
 static void
-_vmem_free(void *ptr, vm_size_t size)
+_vmem_free(void *ptr, size_t size)
 {
        munmap(ptr, size);
 }