Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sys/netpfil/ipfw/dn_heap.c
39478 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* Copyright (c) 1998-2002,2010 Luigi Rizzo, Universita` di Pisa
5
* All rights reserved
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
*
16
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
* SUCH DAMAGE.
27
*/
28
29
/*
30
* Binary heap and hash tables, used in dummynet
31
*/
32
33
#include <sys/param.h>
34
#ifdef _KERNEL
35
#include <sys/systm.h>
36
#include <sys/malloc.h>
37
#include <sys/kernel.h>
38
#include <netpfil/ipfw/dn_heap.h>
39
#ifndef log
40
#define log(x, arg...)
41
#endif
42
43
#else /* !_KERNEL */
44
45
#include <stdio.h>
46
#include <dn_test.h>
47
#include <strings.h>
48
#include <stdlib.h>
49
50
#include "dn_heap.h"
51
#define log(x, arg...) fprintf(stderr, ## arg)
52
#define panic(x...) fprintf(stderr, ## x), exit(1)
53
#define MALLOC_DEFINE(a, b, c) volatile int __dummy__ ## a __attribute__((__unused__))
54
static void *my_malloc(int s) { return malloc(s); }
55
static void my_free(void *p) { free(p); }
56
#define malloc(s, t, w) my_malloc(s)
57
#define free(p, t) my_free(p)
58
#endif /* !_KERNEL */
59
60
static MALLOC_DEFINE(M_DN_HEAP, "dummynet", "dummynet heap");
61
62
/*
63
* Heap management functions.
64
*
65
* In the heap, first node is element 0. Children of i are 2i+1 and 2i+2.
66
* Some macros help finding parent/children so we can optimize them.
67
*
68
* heap_init() is called to expand the heap when needed.
69
* Increment size in blocks of 16 entries.
70
* Returns 1 on error, 0 on success
71
*/
72
#define HEAP_FATHER(x) ( ( (x) - 1 ) / 2 )
73
#define HEAP_LEFT(x) ( (x)+(x) + 1 )
74
#define HEAP_SWAP(a, b, buffer) { buffer = a ; a = b ; b = buffer ; }
75
#define HEAP_INCREMENT 15
76
77
static int
78
heap_resize(struct dn_heap *h, unsigned int new_size)
79
{
80
struct dn_heap_entry *p;
81
82
if ((unsigned int)h->size >= new_size ) /* have enough room */
83
return 0;
84
#if 1 /* round to the next power of 2 */
85
new_size |= new_size >> 1;
86
new_size |= new_size >> 2;
87
new_size |= new_size >> 4;
88
new_size |= new_size >> 8;
89
new_size |= new_size >> 16;
90
#else
91
new_size = (new_size + HEAP_INCREMENT ) & ~HEAP_INCREMENT;
92
#endif
93
p = mallocarray(new_size, sizeof(*p), M_DN_HEAP, M_NOWAIT);
94
if (p == NULL) {
95
printf("--- %s, resize %d failed\n", __func__, new_size );
96
return 1; /* error */
97
}
98
if (h->size > 0) {
99
bcopy(h->p, p, h->size * sizeof(*p) );
100
free(h->p, M_DN_HEAP);
101
}
102
h->p = p;
103
h->size = new_size;
104
return 0;
105
}
106
107
int
108
heap_init(struct dn_heap *h, int size, int ofs)
109
{
110
if (heap_resize(h, size))
111
return 1;
112
h->elements = 0;
113
h->ofs = ofs;
114
return 0;
115
}
116
117
/*
118
* Insert element in heap. Normally, p != NULL, we insert p in
119
* a new position and bubble up. If p == NULL, then the element is
120
* already in place, and key is the position where to start the
121
* bubble-up.
122
* Returns 1 on failure (cannot allocate new heap entry)
123
*
124
* If ofs > 0 the position (index, int) of the element in the heap is
125
* also stored in the element itself at the given offset in bytes.
126
*/
127
#define SET_OFFSET(h, i) do { \
128
if (h->ofs > 0) \
129
*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = i; \
130
} while (0)
131
/*
132
* RESET_OFFSET is used for sanity checks. It sets ofs
133
* to an invalid value.
134
*/
135
#define RESET_OFFSET(h, i) do { \
136
if (h->ofs > 0) \
137
*((int32_t *)((char *)(h->p[i].object) + h->ofs)) = -16; \
138
} while (0)
139
140
int
141
heap_insert(struct dn_heap *h, uint64_t key1, void *p)
142
{
143
int son = h->elements;
144
145
//log("%s key %llu p %p\n", __FUNCTION__, key1, p);
146
if (p == NULL) { /* data already there, set starting point */
147
son = key1;
148
} else { /* insert new element at the end, possibly resize */
149
son = h->elements;
150
if (son == h->size) /* need resize... */
151
// XXX expand by 16 or so
152
if (heap_resize(h, h->elements+16) )
153
return 1; /* failure... */
154
h->p[son].object = p;
155
h->p[son].key = key1;
156
h->elements++;
157
}
158
/* make sure that son >= father along the path */
159
while (son > 0) {
160
int father = HEAP_FATHER(son);
161
struct dn_heap_entry tmp;
162
163
if (DN_KEY_LT( h->p[father].key, h->p[son].key ) )
164
break; /* found right position */
165
/* son smaller than father, swap and repeat */
166
HEAP_SWAP(h->p[son], h->p[father], tmp);
167
SET_OFFSET(h, son);
168
son = father;
169
}
170
SET_OFFSET(h, son);
171
return 0;
172
}
173
174
/*
175
* remove top element from heap, or obj if obj != NULL
176
*/
177
bool
178
heap_extract(struct dn_heap *h, void *obj)
179
{
180
int child, father, max = h->elements - 1;
181
182
if (max < 0) {
183
return false;
184
}
185
if (obj == NULL)
186
father = 0; /* default: move up smallest child */
187
else { /* extract specific element, index is at offset */
188
if (h->ofs <= 0)
189
panic("%s: extract from middle not set on %p\n",
190
__FUNCTION__, h);
191
father = *((int *)((char *)obj + h->ofs));
192
if (father < 0 || father >= h->elements)
193
return false;
194
}
195
/* We should make sure that the object we're trying to remove is
196
* actually in this heap. */
197
if (obj != NULL && h->p[father].object != obj)
198
return false;
199
200
/*
201
* below, father is the index of the empty element, which
202
* we replace at each step with the smallest child until we
203
* reach the bottom level.
204
*/
205
// XXX why removing RESET_OFFSET increases runtime by 10% ?
206
RESET_OFFSET(h, father);
207
while ( (child = HEAP_LEFT(father)) <= max ) {
208
if (child != max &&
209
DN_KEY_LT(h->p[child+1].key, h->p[child].key) )
210
child++; /* take right child, otherwise left */
211
h->p[father] = h->p[child];
212
SET_OFFSET(h, father);
213
father = child;
214
}
215
h->elements--;
216
if (father != max) {
217
/*
218
* Fill hole with last entry and bubble up,
219
* reusing the insert code
220
*/
221
h->p[father] = h->p[max];
222
heap_insert(h, father, NULL);
223
}
224
225
return true;
226
}
227
228
#if 0
229
/*
230
* change object position and update references
231
* XXX this one is never used!
232
*/
233
static void
234
heap_move(struct dn_heap *h, uint64_t new_key, void *object)
235
{
236
int temp, i, max = h->elements-1;
237
struct dn_heap_entry *p, buf;
238
239
if (h->ofs <= 0)
240
panic("cannot move items on this heap");
241
p = h->p; /* shortcut */
242
243
i = *((int *)((char *)object + h->ofs));
244
if (DN_KEY_LT(new_key, p[i].key) ) { /* must move up */
245
p[i].key = new_key;
246
for (; i>0 &&
247
DN_KEY_LT(new_key, p[(temp = HEAP_FATHER(i))].key);
248
i = temp ) { /* bubble up */
249
HEAP_SWAP(p[i], p[temp], buf);
250
SET_OFFSET(h, i);
251
}
252
} else { /* must move down */
253
p[i].key = new_key;
254
while ( (temp = HEAP_LEFT(i)) <= max ) {
255
/* found left child */
256
if (temp != max &&
257
DN_KEY_LT(p[temp+1].key, p[temp].key))
258
temp++; /* select child with min key */
259
if (DN_KEY_LT(>p[temp].key, new_key)) {
260
/* go down */
261
HEAP_SWAP(p[i], p[temp], buf);
262
SET_OFFSET(h, i);
263
} else
264
break;
265
i = temp;
266
}
267
}
268
SET_OFFSET(h, i);
269
}
270
#endif /* heap_move, unused */
271
272
/*
273
* heapify() will reorganize data inside an array to maintain the
274
* heap property. It is needed when we delete a bunch of entries.
275
*/
276
static void
277
heapify(struct dn_heap *h)
278
{
279
int i;
280
281
for (i = 0; i < h->elements; i++ )
282
heap_insert(h, i , NULL);
283
}
284
285
int
286
heap_scan(struct dn_heap *h, int (*fn)(void *, uintptr_t),
287
uintptr_t arg)
288
{
289
int i, ret, found;
290
291
for (i = found = 0 ; i < h->elements ;) {
292
ret = fn(h->p[i].object, arg);
293
if (ret & HEAP_SCAN_DEL) {
294
h->elements-- ;
295
h->p[i] = h->p[h->elements] ;
296
found++ ;
297
} else
298
i++ ;
299
if (ret & HEAP_SCAN_END)
300
break;
301
}
302
if (found)
303
heapify(h);
304
return found;
305
}
306
307
/*
308
* cleanup the heap and free data structure
309
*/
310
void
311
heap_free(struct dn_heap *h)
312
{
313
if (h->size >0 )
314
free(h->p, M_DN_HEAP);
315
bzero(h, sizeof(*h) );
316
}
317
318
/*
319
* hash table support.
320
*/
321
322
struct dn_ht {
323
int buckets; /* how many buckets, really buckets - 1*/
324
int entries; /* how many entries */
325
int ofs; /* offset of link field */
326
uint32_t (*hash)(uintptr_t, int, void *arg);
327
int (*match)(void *_el, uintptr_t key, int, void *);
328
void *(*newh)(uintptr_t, int, void *);
329
void **ht; /* bucket heads */
330
};
331
/*
332
* Initialize, allocating bucket pointers inline.
333
* Recycle previous record if possible.
334
* If the 'newh' function is not supplied, we assume that the
335
* key passed to ht_find is the same object to be stored in.
336
*/
337
struct dn_ht *
338
dn_ht_init(struct dn_ht *ht, int buckets, int ofs,
339
uint32_t (*h)(uintptr_t, int, void *),
340
int (*match)(void *, uintptr_t, int, void *),
341
void *(*newh)(uintptr_t, int, void *))
342
{
343
int l;
344
345
/*
346
* Notes about rounding bucket size to a power of two.
347
* Given the original bucket size, we compute the nearest lower and
348
* higher power of two, minus 1 (respectively b_min and b_max) because
349
* this value will be used to do an AND with the index returned
350
* by hash function.
351
* To choice between these two values, the original bucket size is
352
* compared with b_min. If the original size is greater than 4/3 b_min,
353
* we round the bucket size to b_max, else to b_min.
354
* This ratio try to round to the nearest power of two, advantaging
355
* the greater size if the different between two power is relatively
356
* big.
357
* Rounding the bucket size to a power of two avoid the use of
358
* module when calculating the correct bucket.
359
* The ht->buckets variable store the bucket size - 1 to simply
360
* do an AND between the index returned by hash function and ht->bucket
361
* instead of a module.
362
*/
363
int b_min; /* min buckets */
364
int b_max; /* max buckets */
365
int b_ori; /* original buckets */
366
367
if (h == NULL || match == NULL) {
368
printf("--- missing hash or match function");
369
return NULL;
370
}
371
if (buckets < 1 || buckets > 65536)
372
return NULL;
373
374
b_ori = buckets;
375
/* calculate next power of 2, - 1*/
376
buckets |= buckets >> 1;
377
buckets |= buckets >> 2;
378
buckets |= buckets >> 4;
379
buckets |= buckets >> 8;
380
buckets |= buckets >> 16;
381
382
b_max = buckets; /* Next power */
383
b_min = buckets >> 1; /* Previous power */
384
385
/* Calculate the 'nearest' bucket size */
386
if (b_min * 4000 / 3000 < b_ori)
387
buckets = b_max;
388
else
389
buckets = b_min;
390
391
if (ht) { /* see if we can reuse */
392
if (buckets <= ht->buckets) {
393
ht->buckets = buckets;
394
} else {
395
/* free pointers if not allocated inline */
396
if (ht->ht != (void *)(ht + 1))
397
free(ht->ht, M_DN_HEAP);
398
free(ht, M_DN_HEAP);
399
ht = NULL;
400
}
401
}
402
if (ht == NULL) {
403
/* Allocate buckets + 1 entries because buckets is use to
404
* do the AND with the index returned by hash function
405
*/
406
l = sizeof(*ht) + (buckets + 1) * sizeof(void **);
407
ht = malloc(l, M_DN_HEAP, M_NOWAIT | M_ZERO);
408
}
409
if (ht) {
410
ht->ht = (void **)(ht + 1);
411
ht->buckets = buckets;
412
ht->ofs = ofs;
413
ht->hash = h;
414
ht->match = match;
415
ht->newh = newh;
416
}
417
return ht;
418
}
419
420
/* dummy callback for dn_ht_free to unlink all */
421
static int
422
do_del(void *obj, void *arg)
423
{
424
(void)obj;
425
(void)arg;
426
return DNHT_SCAN_DEL;
427
}
428
429
void
430
dn_ht_free(struct dn_ht *ht, int flags)
431
{
432
if (ht == NULL)
433
return;
434
if (flags & DNHT_REMOVE) {
435
(void)dn_ht_scan(ht, do_del, NULL);
436
} else {
437
if (ht->ht && ht->ht != (void *)(ht + 1))
438
free(ht->ht, M_DN_HEAP);
439
free(ht, M_DN_HEAP);
440
}
441
}
442
443
int
444
dn_ht_entries(struct dn_ht *ht)
445
{
446
return ht ? ht->entries : 0;
447
}
448
449
/* lookup and optionally create or delete element */
450
void *
451
dn_ht_find(struct dn_ht *ht, uintptr_t key, int flags, void *arg)
452
{
453
int i;
454
void **pp, *p;
455
456
if (ht == NULL) /* easy on an empty hash */
457
return NULL;
458
i = (ht->buckets == 1) ? 0 :
459
(ht->hash(key, flags, arg) & ht->buckets);
460
461
for (pp = &ht->ht[i]; (p = *pp); pp = (void **)((char *)p + ht->ofs)) {
462
if (flags & DNHT_MATCH_PTR) {
463
if (key == (uintptr_t)p)
464
break;
465
} else if (ht->match(p, key, flags, arg)) /* found match */
466
break;
467
}
468
if (p) {
469
if (flags & DNHT_REMOVE) {
470
/* link in the next element */
471
*pp = *(void **)((char *)p + ht->ofs);
472
*(void **)((char *)p + ht->ofs) = NULL;
473
ht->entries--;
474
}
475
} else if (flags & DNHT_INSERT) {
476
// printf("%s before calling new, bucket %d ofs %d\n",
477
// __FUNCTION__, i, ht->ofs);
478
p = ht->newh ? ht->newh(key, flags, arg) : (void *)key;
479
// printf("%s newh returns %p\n", __FUNCTION__, p);
480
if (p) {
481
ht->entries++;
482
*(void **)((char *)p + ht->ofs) = ht->ht[i];
483
ht->ht[i] = p;
484
}
485
}
486
return p;
487
}
488
489
/*
490
* do a scan with the option to delete the object. Extract next before
491
* running the callback because the element may be destroyed there.
492
*/
493
int
494
dn_ht_scan(struct dn_ht *ht, int (*fn)(void *, void *), void *arg)
495
{
496
int i, ret, found = 0;
497
void **curp, *cur, *next;
498
499
if (ht == NULL || fn == NULL)
500
return 0;
501
for (i = 0; i <= ht->buckets; i++) {
502
curp = &ht->ht[i];
503
while ( (cur = *curp) != NULL) {
504
next = *(void **)((char *)cur + ht->ofs);
505
ret = fn(cur, arg);
506
if (ret & DNHT_SCAN_DEL) {
507
found++;
508
ht->entries--;
509
*curp = next;
510
} else {
511
curp = (void **)((char *)cur + ht->ofs);
512
}
513
if (ret & DNHT_SCAN_END)
514
return found;
515
}
516
}
517
return found;
518
}
519
520
/*
521
* Similar to dn_ht_scan(), except that the scan is performed only
522
* in the bucket 'bucket'. The function returns a correct bucket number if
523
* the original is invalid.
524
* If the callback returns DNHT_SCAN_END, the function move the ht->ht[i]
525
* pointer to the last entry processed. Moreover, the bucket number passed
526
* by caller is decremented, because usually the caller increment it.
527
*/
528
int
529
dn_ht_scan_bucket(struct dn_ht *ht, int *bucket, int (*fn)(void *, void *),
530
void *arg)
531
{
532
int i, ret, found = 0;
533
void **curp, *cur, *next;
534
535
if (ht == NULL || fn == NULL)
536
return 0;
537
if (*bucket > ht->buckets)
538
*bucket = 0;
539
i = *bucket;
540
541
curp = &ht->ht[i];
542
while ( (cur = *curp) != NULL) {
543
next = *(void **)((char *)cur + ht->ofs);
544
ret = fn(cur, arg);
545
if (ret & DNHT_SCAN_DEL) {
546
found++;
547
ht->entries--;
548
*curp = next;
549
} else {
550
curp = (void **)((char *)cur + ht->ofs);
551
}
552
if (ret & DNHT_SCAN_END)
553
return found;
554
}
555
return found;
556
}
557
558