malloc: make join elements function public
[dpdk.git] / lib / librte_eal / common / malloc_elem.c
index 42568e1..2291ee1 100644 (file)
@@ -1,35 +1,7 @@
-/*-
- *   BSD LICENSE
- *
- *   Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
- *   All rights reserved.
- *
- *   Redistribution and use in source and binary forms, with or without
- *   modification, are permitted provided that the following conditions
- *   are met:
- *
- *     * Redistributions of source code must retain the above copyright
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright
- *       notice, this list of conditions and the following disclaimer in
- *       the documentation and/or other materials provided with the
- *       distribution.
- *     * Neither the name of Intel Corporation nor the names of its
- *       contributors may be used to endorse or promote products derived
- *       from this software without specific prior written permission.
- *
- *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2010-2014 Intel Corporation
  */
+#include <inttypes.h>
 #include <stdint.h>
 #include <stddef.h>
 #include <stdio.h>
@@ -51,7 +23,7 @@
 #define MIN_DATA_SIZE (RTE_CACHE_LINE_SIZE)
 
 /*
- * initialise a general malloc_elem header structure
+ * Initialize a general malloc_elem header structure
  */
 void
 malloc_elem_init(struct malloc_elem *elem,
@@ -60,6 +32,7 @@ malloc_elem_init(struct malloc_elem *elem,
        elem->heap = heap;
        elem->ms = ms;
        elem->prev = NULL;
+       elem->next = NULL;
        memset(&elem->free_list, 0, sizeof(elem->free_list));
        elem->state = ELEM_FREE;
        elem->size = size;
@@ -68,15 +41,56 @@ malloc_elem_init(struct malloc_elem *elem,
        set_trailer(elem);
 }
 
-/*
- * initialise a dummy malloc_elem header for the end-of-memseg marker
- */
 void
-malloc_elem_mkend(struct malloc_elem *elem, struct malloc_elem *prev)
+malloc_elem_insert(struct malloc_elem *elem)
 {
-       malloc_elem_init(elem, prev->heap, prev->ms, 0);
-       elem->prev = prev;
-       elem->state = ELEM_BUSY; /* mark busy so its never merged */
+       struct malloc_elem *prev_elem, *next_elem;
+       struct malloc_heap *heap = elem->heap;
+
+       if (heap->first == NULL && heap->last == NULL) {
+               /* if empty heap */
+               heap->first = elem;
+               heap->last = elem;
+               prev_elem = NULL;
+               next_elem = NULL;
+       } else if (elem < heap->first) {
+               /* if lower than start */
+               prev_elem = NULL;
+               next_elem = heap->first;
+               heap->first = elem;
+       } else if (elem > heap->last) {
+               /* if higher than end */
+               prev_elem = heap->last;
+               next_elem = NULL;
+               heap->last = elem;
+       } else {
+               /* the new memory is somewhere inbetween start and end */
+               uint64_t dist_from_start, dist_from_end;
+
+               dist_from_end = RTE_PTR_DIFF(heap->last, elem);
+               dist_from_start = RTE_PTR_DIFF(elem, heap->first);
+
+               /* check which is closer, and find closest list entries */
+               if (dist_from_start < dist_from_end) {
+                       prev_elem = heap->first;
+                       while (prev_elem->next < elem)
+                               prev_elem = prev_elem->next;
+                       next_elem = prev_elem->next;
+               } else {
+                       next_elem = heap->last;
+                       while (next_elem->prev > elem)
+                               next_elem = next_elem->prev;
+                       prev_elem = next_elem->prev;
+               }
+       }
+
+       /* insert new element */
+       elem->prev = prev_elem;
+       elem->next = next_elem;
+       if (prev_elem)
+               prev_elem->next = elem;
+       if (next_elem)
+               next_elem->prev = elem;
 }
 
 /*
@@ -98,6 +112,7 @@ elem_start_pt(struct malloc_elem *elem, size_t size, unsigned align,
        if ((new_data_start & bmask) != ((end_pt - 1) & bmask)) {
                end_pt = RTE_ALIGN_FLOOR(end_pt, bound);
                new_data_start = RTE_ALIGN_FLOOR((end_pt - size), align);
+               end_pt = new_data_start + size;
                if (((end_pt - 1) & bmask) != (new_data_start & bmask))
                        return NULL;
        }
@@ -126,17 +141,57 @@ malloc_elem_can_hold(struct malloc_elem *elem, size_t size,       unsigned align,
 static void
 split_elem(struct malloc_elem *elem, struct malloc_elem *split_pt)
 {
-       struct malloc_elem *next_elem = RTE_PTR_ADD(elem, elem->size);
+       struct malloc_elem *next_elem = elem->next;
        const size_t old_elem_size = (uintptr_t)split_pt - (uintptr_t)elem;
        const size_t new_elem_size = elem->size - old_elem_size;
 
        malloc_elem_init(split_pt, elem->heap, elem->ms, new_elem_size);
        split_pt->prev = elem;
-       next_elem->prev = split_pt;
+       split_pt->next = next_elem;
+       if (next_elem)
+               next_elem->prev = split_pt;
+       else
+               elem->heap->last = split_pt;
+       elem->next = split_pt;
        elem->size = old_elem_size;
        set_trailer(elem);
 }
 
+/*
+ * our malloc heap is a doubly linked list, so doubly remove our element.
+ */
+static void __rte_unused
+remove_elem(struct malloc_elem *elem)
+{
+       struct malloc_elem *next, *prev;
+       next = elem->next;
+       prev = elem->prev;
+
+       if (next)
+               next->prev = prev;
+       else
+               elem->heap->last = prev;
+       if (prev)
+               prev->next = next;
+       else
+               elem->heap->first = next;
+
+       elem->prev = NULL;
+       elem->next = NULL;
+}
+
+static int
+next_elem_is_adjacent(struct malloc_elem *elem)
+{
+       return elem->next == RTE_PTR_ADD(elem, elem->size);
+}
+
+static int
+prev_elem_is_adjacent(struct malloc_elem *elem)
+{
+       return elem == RTE_PTR_ADD(elem->prev, elem->prev->size);
+}
+
 /*
  * Given an element size, compute its freelist index.
  * We free an element into the freelist containing similarly-sized elements.
@@ -220,6 +275,9 @@ malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
 
                split_elem(elem, new_free_elem);
                malloc_elem_free_list_insert(new_free_elem);
+
+               if (elem == elem->heap->last)
+                       elem->heap->last = new_free_elem;
        }
 
        if (old_elem_size < MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
@@ -228,7 +286,7 @@ malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
                elem->pad = old_elem_size;
 
                /* put a dummy header in padding, to point to real element header */
-               if (elem->pad > 0){ /* pad will be at least 64-bytes, as everything
+               if (elem->pad > 0) { /* pad will be at least 64-bytes, as everything
                                     * is cache-line aligned */
                        new_elem->pad = elem->pad;
                        new_elem->state = ELEM_PAD;
@@ -252,15 +310,68 @@ malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
 }
 
 /*
- * joing two struct malloc_elem together. elem1 and elem2 must
+ * join two struct malloc_elem together. elem1 and elem2 must
  * be contiguous in memory.
  */
 static inline void
 join_elem(struct malloc_elem *elem1, struct malloc_elem *elem2)
 {
-       struct malloc_elem *next = RTE_PTR_ADD(elem2, elem2->size);
+       struct malloc_elem *next = elem2->next;
        elem1->size += elem2->size;
-       next->prev = elem1;
+       if (next)
+               next->prev = elem1;
+       else
+               elem1->heap->last = elem1;
+       elem1->next = next;
+}
+
+struct malloc_elem *
+malloc_elem_join_adjacent_free(struct malloc_elem *elem)
+{
+       /*
+        * check if next element exists, is adjacent and is free, if so join
+        * with it, need to remove from free list.
+        */
+       if (elem->next != NULL && elem->next->state == ELEM_FREE &&
+                       next_elem_is_adjacent(elem)) {
+               void *erase;
+
+               /* we will want to erase the trailer and header */
+               erase = RTE_PTR_SUB(elem->next, MALLOC_ELEM_TRAILER_LEN);
+
+               /* remove from free list, join to this one */
+               elem_free_list_remove(elem->next);
+               join_elem(elem, elem->next);
+
+               /* erase header and trailer */
+               memset(erase, 0, MALLOC_ELEM_OVERHEAD);
+       }
+
+       /*
+        * check if prev element exists, is adjacent and is free, if so join
+        * with it, need to remove from free list.
+        */
+       if (elem->prev != NULL && elem->prev->state == ELEM_FREE &&
+                       prev_elem_is_adjacent(elem)) {
+               struct malloc_elem *new_elem;
+               void *erase;
+
+               /* we will want to erase trailer and header */
+               erase = RTE_PTR_SUB(elem, MALLOC_ELEM_TRAILER_LEN);
+
+               /* remove from free list, join to this one */
+               elem_free_list_remove(elem->prev);
+
+               new_elem = elem->prev;
+               join_elem(new_elem, elem);
+
+               /* erase header and trailer */
+               memset(erase, 0, MALLOC_ELEM_OVERHEAD);
+
+               elem = new_elem;
+       }
+
+       return elem;
 }
 
 /*
@@ -271,38 +382,20 @@ join_elem(struct malloc_elem *elem1, struct malloc_elem *elem2)
 int
 malloc_elem_free(struct malloc_elem *elem)
 {
-       if (!malloc_elem_cookies_ok(elem) || elem->state != ELEM_BUSY)
-               return -1;
+       void *ptr;
+       size_t data_len;
 
-       rte_spinlock_lock(&(elem->heap->lock));
-       size_t sz = elem->size - sizeof(*elem);
-       uint8_t *ptr = (uint8_t *)&elem[1];
-       struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size);
-       if (next->state == ELEM_FREE){
-               /* remove from free list, join to this one */
-               elem_free_list_remove(next);
-               join_elem(elem, next);
-               sz += sizeof(*elem);
-       }
+       ptr = RTE_PTR_ADD(elem, sizeof(*elem));
+       data_len = elem->size - MALLOC_ELEM_OVERHEAD;
+
+       elem = malloc_elem_join_adjacent_free(elem);
 
-       /* check if previous element is free, if so join with it and return,
-        * need to re-insert in free list, as that element's size is changing
-        */
-       if (elem->prev != NULL && elem->prev->state == ELEM_FREE) {
-               elem_free_list_remove(elem->prev);
-               join_elem(elem->prev, elem);
-               sz += sizeof(*elem);
-               ptr -= sizeof(*elem);
-               elem = elem->prev;
-       }
        malloc_elem_free_list_insert(elem);
 
        /* decrease heap's count of allocated elements */
        elem->heap->alloc_count--;
 
-       memset(ptr, 0, sz);
-
-       rte_spinlock_unlock(&(elem->heap->lock));
+       memset(ptr, 0, data_len);
 
        return 0;
 }
@@ -314,36 +407,54 @@ malloc_elem_free(struct malloc_elem *elem)
 int
 malloc_elem_resize(struct malloc_elem *elem, size_t size)
 {
-       const size_t new_size = size + MALLOC_ELEM_OVERHEAD;
+       const size_t new_size = size + elem->pad + MALLOC_ELEM_OVERHEAD;
+
        /* if we request a smaller size, then always return ok */
-       const size_t current_size = elem->size - elem->pad;
-       if (current_size >= new_size)
+       if (elem->size >= new_size)
                return 0;
 
-       struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size);
-       rte_spinlock_lock(&elem->heap->lock);
-       if (next ->state != ELEM_FREE)
-               goto err_return;
-       if (current_size + next->size < new_size)
-               goto err_return;
+       /* check if there is a next element, it's free and adjacent */
+       if (!elem->next || elem->next->state != ELEM_FREE ||
+                       !next_elem_is_adjacent(elem))
+               return -1;
+       if (elem->size + elem->next->size < new_size)
+               return -1;
 
        /* we now know the element fits, so remove from free list,
         * join the two
         */
-       elem_free_list_remove(next);
-       join_elem(elem, next);
+       elem_free_list_remove(elem->next);
+       join_elem(elem, elem->next);
 
-       if (elem->size - new_size >= MIN_DATA_SIZE + MALLOC_ELEM_OVERHEAD){
+       if (elem->size - new_size >= MIN_DATA_SIZE + MALLOC_ELEM_OVERHEAD) {
                /* now we have a big block together. Lets cut it down a bit, by splitting */
                struct malloc_elem *split_pt = RTE_PTR_ADD(elem, new_size);
                split_pt = RTE_PTR_ALIGN_CEIL(split_pt, RTE_CACHE_LINE_SIZE);
                split_elem(elem, split_pt);
                malloc_elem_free_list_insert(split_pt);
        }
-       rte_spinlock_unlock(&elem->heap->lock);
        return 0;
+}
 
-err_return:
-       rte_spinlock_unlock(&elem->heap->lock);
-       return -1;
+static inline const char *
+elem_state_to_str(enum elem_state state)
+{
+       switch (state) {
+       case ELEM_PAD:
+               return "PAD";
+       case ELEM_BUSY:
+               return "BUSY";
+       case ELEM_FREE:
+               return "FREE";
+       }
+       return "ERROR";
+}
+
+void
+malloc_elem_dump(const struct malloc_elem *elem, FILE *f)
+{
+       fprintf(f, "Malloc element at %p (%s)\n", elem,
+                       elem_state_to_str(elem->state));
+       fprintf(f, "  len: 0x%zx pad: 0x%" PRIx32 "\n", elem->size, elem->pad);
+       fprintf(f, "  prev: %p next: %p\n", elem->prev, elem->next);
 }