freebsd-skq/sys/netpfil/ipfw/dn_heap.c
Pedro F. Giffuni 454529cd0b netpfil/ipfw: Make some use of mallocarray(9).
Reviewed by:	kp, ae
Differential Revision: https://reviews.freebsd.org/D13834
2018-01-11 15:29:29 +00:00

557 lines
14 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 1998-2002,2010 Luigi Rizzo, Universita` di Pisa
* All rights reserved
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*/
/*
* Binary heap and hash tables, used in dummynet
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#ifdef _KERNEL
__FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <netpfil/ipfw/dn_heap.h>
#ifndef log
#define log(x, arg...)
#endif
#else /* !_KERNEL */
#include <stdio.h>
#include <dn_test.h>
#include <strings.h>
#include <stdlib.h>
#include "dn_heap.h"
#define log(x, arg...) fprintf(stderr, ## arg)
#define panic(x...) fprintf(stderr, ## x), exit(1)
#define MALLOC_DEFINE(a, b, c) volatile int __dummy__ ## a __attribute__((__unused__))
static void *my_malloc(int s) { return malloc(s); }
static void my_free(void *p) { free(p); }
#define malloc(s, t, w) my_malloc(s)
#define free(p, t) my_free(p)
#endif /* !_KERNEL */
static MALLOC_DEFINE(M_DN_HEAP, "dummynet", "dummynet heap");
/*
* Heap management functions.
*
* In the heap, first node is element 0. Children of i are 2i+1 and 2i+2.
* Some macros help finding parent/children so we can optimize them.
*
* heap_init() is called to expand the heap when needed.
* Increment size in blocks of 16 entries.
* Returns 1 on error, 0 on success
*/
#define HEAP_FATHER(x) ( ( (x) - 1 ) / 2 )
#define HEAP_LEFT(x) ( (x)+(x) + 1 )
#define HEAP_SWAP(a, b, buffer) { buffer = a ; a = b ; b = buffer ; }
#define HEAP_INCREMENT 15
static int
heap_resize(struct dn_heap *h, unsigned int new_size)
{
struct dn_heap_entry *p;
if ((unsigned int)h->size >= new_size ) /* have enough room */
return 0;
#if 1 /* round to the next power of 2 */
new_size |= new_size >> 1;
new_size |= new_size >> 2;
new_size |= new_size >> 4;
new_size |= new_size >> 8;
new_size |= new_size >> 16;
#else
new_size = (new_size + HEAP_INCREMENT ) & ~HEAP_INCREMENT;
#endif
p = mallocarray(new_size, sizeof(*p), M_DN_HEAP, M_NOWAIT);
if (p == NULL) {
printf("--- %s, resize %d failed\n", __func__, new_size );
return 1; /* error */
}
if (h->size > 0) {
bcopy(h->p, p, h->size * sizeof(*p) );
free(h->p, M_DN_HEAP);
}
h->p = p;
h->size = new_size;
return 0;
}
int
heap_init(struct dn_heap *h, int size, int ofs)
{
if (heap_resize(h, size))
return 1;
h->elements = 0;
h->ofs = ofs;
return 0;
}
/*
* Insert element in heap. Normally, p != NULL, we insert p in
* a new position and bubble up. If p == NULL, then the element is
* already in place, and key is the position where to start the
* bubble-up.
* Returns 1 on failure (cannot allocate new heap entry)
*
* If ofs > 0 the position (index, int) of the element in the heap is
* also stored in the element itself at the given offset in bytes.
*/
#define SET_OFFSET(h, i) do { \
if (h->ofs > 0) \
*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = i; \
} while (0)
/*
* RESET_OFFSET is used for sanity checks. It sets ofs
* to an invalid value.
*/
#define RESET_OFFSET(h, i) do { \
if (h->ofs > 0) \
*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = -16; \
} while (0)
int
heap_insert(struct dn_heap *h, uint64_t key1, void *p)
{
int son = h->elements;
//log("%s key %llu p %p\n", __FUNCTION__, key1, p);
if (p == NULL) { /* data already there, set starting point */
son = key1;
} else { /* insert new element at the end, possibly resize */
son = h->elements;
if (son == h->size) /* need resize... */
// XXX expand by 16 or so
if (heap_resize(h, h->elements+16) )
return 1; /* failure... */
h->p[son].object = p;
h->p[son].key = key1;
h->elements++;
}
/* make sure that son >= father along the path */
while (son > 0) {
int father = HEAP_FATHER(son);
struct dn_heap_entry tmp;
if (DN_KEY_LT( h->p[father].key, h->p[son].key ) )
break; /* found right position */
/* son smaller than father, swap and repeat */
HEAP_SWAP(h->p[son], h->p[father], tmp);
SET_OFFSET(h, son);
son = father;
}
SET_OFFSET(h, son);
return 0;
}
/*
* remove top element from heap, or obj if obj != NULL
*/
void
heap_extract(struct dn_heap *h, void *obj)
{
int child, father, max = h->elements - 1;
if (max < 0) {
printf("--- %s: empty heap 0x%p\n", __FUNCTION__, h);
return;
}
if (obj == NULL)
father = 0; /* default: move up smallest child */
else { /* extract specific element, index is at offset */
if (h->ofs <= 0)
panic("%s: extract from middle not set on %p\n",
__FUNCTION__, h);
father = *((int *)((char *)obj + h->ofs));
if (father < 0 || father >= h->elements) {
panic("%s: father %d out of bound 0..%d\n",
__FUNCTION__, father, h->elements);
}
}
/*
* below, father is the index of the empty element, which
* we replace at each step with the smallest child until we
* reach the bottom level.
*/
// XXX why removing RESET_OFFSET increases runtime by 10% ?
RESET_OFFSET(h, father);
while ( (child = HEAP_LEFT(father)) <= max ) {
if (child != max &&
DN_KEY_LT(h->p[child+1].key, h->p[child].key) )
child++; /* take right child, otherwise left */
h->p[father] = h->p[child];
SET_OFFSET(h, father);
father = child;
}
h->elements--;
if (father != max) {
/*
* Fill hole with last entry and bubble up,
* reusing the insert code
*/
h->p[father] = h->p[max];
heap_insert(h, father, NULL);
}
}
#if 0
/*
* change object position and update references
* XXX this one is never used!
*/
static void
heap_move(struct dn_heap *h, uint64_t new_key, void *object)
{
int temp, i, max = h->elements-1;
struct dn_heap_entry *p, buf;
if (h->ofs <= 0)
panic("cannot move items on this heap");
p = h->p; /* shortcut */
i = *((int *)((char *)object + h->ofs));
if (DN_KEY_LT(new_key, p[i].key) ) { /* must move up */
p[i].key = new_key;
for (; i>0 &&
DN_KEY_LT(new_key, p[(temp = HEAP_FATHER(i))].key);
i = temp ) { /* bubble up */
HEAP_SWAP(p[i], p[temp], buf);
SET_OFFSET(h, i);
}
} else { /* must move down */
p[i].key = new_key;
while ( (temp = HEAP_LEFT(i)) <= max ) {
/* found left child */
if (temp != max &&
DN_KEY_LT(p[temp+1].key, p[temp].key))
temp++; /* select child with min key */
if (DN_KEY_LT(>p[temp].key, new_key)) {
/* go down */
HEAP_SWAP(p[i], p[temp], buf);
SET_OFFSET(h, i);
} else
break;
i = temp;
}
}
SET_OFFSET(h, i);
}
#endif /* heap_move, unused */
/*
* heapify() will reorganize data inside an array to maintain the
* heap property. It is needed when we delete a bunch of entries.
*/
static void
heapify(struct dn_heap *h)
{
int i;
for (i = 0; i < h->elements; i++ )
heap_insert(h, i , NULL);
}
int
heap_scan(struct dn_heap *h, int (*fn)(void *, uintptr_t),
uintptr_t arg)
{
int i, ret, found;
for (i = found = 0 ; i < h->elements ;) {
ret = fn(h->p[i].object, arg);
if (ret & HEAP_SCAN_DEL) {
h->elements-- ;
h->p[i] = h->p[h->elements] ;
found++ ;
} else
i++ ;
if (ret & HEAP_SCAN_END)
break;
}
if (found)
heapify(h);
return found;
}
/*
* cleanup the heap and free data structure
*/
void
heap_free(struct dn_heap *h)
{
if (h->size >0 )
free(h->p, M_DN_HEAP);
bzero(h, sizeof(*h) );
}
/*
* hash table support.
*/
struct dn_ht {
int buckets; /* how many buckets, really buckets - 1*/
int entries; /* how many entries */
int ofs; /* offset of link field */
uint32_t (*hash)(uintptr_t, int, void *arg);
int (*match)(void *_el, uintptr_t key, int, void *);
void *(*newh)(uintptr_t, int, void *);
void **ht; /* bucket heads */
};
/*
* Initialize, allocating bucket pointers inline.
* Recycle previous record if possible.
* If the 'newh' function is not supplied, we assume that the
* key passed to ht_find is the same object to be stored in.
*/
struct dn_ht *
dn_ht_init(struct dn_ht *ht, int buckets, int ofs,
uint32_t (*h)(uintptr_t, int, void *),
int (*match)(void *, uintptr_t, int, void *),
void *(*newh)(uintptr_t, int, void *))
{
int l;
/*
* Notes about rounding bucket size to a power of two.
* Given the original bucket size, we compute the nearest lower and
* higher power of two, minus 1 (respectively b_min and b_max) because
* this value will be used to do an AND with the index returned
* by hash function.
* To choice between these two values, the original bucket size is
* compared with b_min. If the original size is greater than 4/3 b_min,
* we round the bucket size to b_max, else to b_min.
* This ratio try to round to the nearest power of two, advantaging
* the greater size if the different between two power is relatively
* big.
* Rounding the bucket size to a power of two avoid the use of
* module when calculating the correct bucket.
* The ht->buckets variable store the bucket size - 1 to simply
* do an AND between the index returned by hash function and ht->bucket
* instead of a module.
*/
int b_min; /* min buckets */
int b_max; /* max buckets */
int b_ori; /* original buckets */
if (h == NULL || match == NULL) {
printf("--- missing hash or match function");
return NULL;
}
if (buckets < 1 || buckets > 65536)
return NULL;
b_ori = buckets;
/* calculate next power of 2, - 1*/
buckets |= buckets >> 1;
buckets |= buckets >> 2;
buckets |= buckets >> 4;
buckets |= buckets >> 8;
buckets |= buckets >> 16;
b_max = buckets; /* Next power */
b_min = buckets >> 1; /* Previous power */
/* Calculate the 'nearest' bucket size */
if (b_min * 4000 / 3000 < b_ori)
buckets = b_max;
else
buckets = b_min;
if (ht) { /* see if we can reuse */
if (buckets <= ht->buckets) {
ht->buckets = buckets;
} else {
/* free pointers if not allocated inline */
if (ht->ht != (void *)(ht + 1))
free(ht->ht, M_DN_HEAP);
free(ht, M_DN_HEAP);
ht = NULL;
}
}
if (ht == NULL) {
/* Allocate buckets + 1 entries because buckets is use to
* do the AND with the index returned by hash function
*/
l = sizeof(*ht) + (buckets + 1) * sizeof(void **);
ht = malloc(l, M_DN_HEAP, M_NOWAIT | M_ZERO);
}
if (ht) {
ht->ht = (void **)(ht + 1);
ht->buckets = buckets;
ht->ofs = ofs;
ht->hash = h;
ht->match = match;
ht->newh = newh;
}
return ht;
}
/* dummy callback for dn_ht_free to unlink all */
static int
do_del(void *obj, void *arg)
{
(void)obj;
(void)arg;
return DNHT_SCAN_DEL;
}
void
dn_ht_free(struct dn_ht *ht, int flags)
{
if (ht == NULL)
return;
if (flags & DNHT_REMOVE) {
(void)dn_ht_scan(ht, do_del, NULL);
} else {
if (ht->ht && ht->ht != (void *)(ht + 1))
free(ht->ht, M_DN_HEAP);
free(ht, M_DN_HEAP);
}
}
int
dn_ht_entries(struct dn_ht *ht)
{
return ht ? ht->entries : 0;
}
/* lookup and optionally create or delete element */
void *
dn_ht_find(struct dn_ht *ht, uintptr_t key, int flags, void *arg)
{
int i;
void **pp, *p;
if (ht == NULL) /* easy on an empty hash */
return NULL;
i = (ht->buckets == 1) ? 0 :
(ht->hash(key, flags, arg) & ht->buckets);
for (pp = &ht->ht[i]; (p = *pp); pp = (void **)((char *)p + ht->ofs)) {
if (flags & DNHT_MATCH_PTR) {
if (key == (uintptr_t)p)
break;
} else if (ht->match(p, key, flags, arg)) /* found match */
break;
}
if (p) {
if (flags & DNHT_REMOVE) {
/* link in the next element */
*pp = *(void **)((char *)p + ht->ofs);
*(void **)((char *)p + ht->ofs) = NULL;
ht->entries--;
}
} else if (flags & DNHT_INSERT) {
// printf("%s before calling new, bucket %d ofs %d\n",
// __FUNCTION__, i, ht->ofs);
p = ht->newh ? ht->newh(key, flags, arg) : (void *)key;
// printf("%s newh returns %p\n", __FUNCTION__, p);
if (p) {
ht->entries++;
*(void **)((char *)p + ht->ofs) = ht->ht[i];
ht->ht[i] = p;
}
}
return p;
}
/*
* do a scan with the option to delete the object. Extract next before
* running the callback because the element may be destroyed there.
*/
int
dn_ht_scan(struct dn_ht *ht, int (*fn)(void *, void *), void *arg)
{
int i, ret, found = 0;
void **curp, *cur, *next;
if (ht == NULL || fn == NULL)
return 0;
for (i = 0; i <= ht->buckets; i++) {
curp = &ht->ht[i];
while ( (cur = *curp) != NULL) {
next = *(void **)((char *)cur + ht->ofs);
ret = fn(cur, arg);
if (ret & DNHT_SCAN_DEL) {
found++;
ht->entries--;
*curp = next;
} else {
curp = (void **)((char *)cur + ht->ofs);
}
if (ret & DNHT_SCAN_END)
return found;
}
}
return found;
}
/*
* Similar to dn_ht_scan(), except that the scan is performed only
* in the bucket 'bucket'. The function returns a correct bucket number if
* the original is invalid.
* If the callback returns DNHT_SCAN_END, the function move the ht->ht[i]
* pointer to the last entry processed. Moreover, the bucket number passed
* by caller is decremented, because usually the caller increment it.
*/
int
dn_ht_scan_bucket(struct dn_ht *ht, int *bucket, int (*fn)(void *, void *),
void *arg)
{
int i, ret, found = 0;
void **curp, *cur, *next;
if (ht == NULL || fn == NULL)
return 0;
if (*bucket > ht->buckets)
*bucket = 0;
i = *bucket;
curp = &ht->ht[i];
while ( (cur = *curp) != NULL) {
next = *(void **)((char *)cur + ht->ofs);
ret = fn(cur, arg);
if (ret & DNHT_SCAN_DEL) {
found++;
ht->entries--;
*curp = next;
} else {
curp = (void **)((char *)cur + ht->ofs);
}
if (ret & DNHT_SCAN_END)
return found;
}
return found;
}