summaryrefslogtreecommitdiffstats
path: root/ssl/priority_queue.c
blob: 5393c532a7addb6d85014015a48e4c170af8ae9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
/*
 * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <openssl/crypto.h>
#include <openssl/err.h>
#include <assert.h>
#include "internal/priority_queue.h"
#include "internal/safe_math.h"
#include "internal/numbers.h"

OSSL_SAFE_MATH_UNSIGNED(size_t, size_t)

/*
 * Fundamental operations:
 *                        Binary Heap   Fibonacci Heap
 *  Get smallest            O(1)          O(1)
 *  Delete any              O(log n)      O(log n) average but worst O(n)
 *  Insert                  O(log n)      O(1)
 *
 * Not supported:
 *  Merge two structures    O(log n)      O(1)
 *  Decrease key            O(log n)      O(1)
 *  Increase key            O(log n)      ?
 *
 * The Fibonacci heap is quite a bit more complicated to implement and has
 * larger overhead in practice.  We favour the binary heap here.  A multi-way
 * (ternary or quaternary) heap might elicit a performance advantage via better
 * cache access patterns.
 */

struct pq_heap_st {
    void *data;     /* User supplied data pointer */
    size_t index;   /* Constant index in elements[] */
};

struct pq_elem_st {
    size_t posn;    /* Current index in heap[] or link in free list */
#ifndef NDEBUG
    int used;       /* Debug flag indicating that this is in use */
#endif
};

struct ossl_pqueue_st
{
    struct pq_heap_st *heap;
    struct pq_elem_st *elements;
    int (*compare)(const void *, const void *);
    size_t htop;        /* Highest used heap element */
    size_t hmax;        /* Allocated heap & element space */
    size_t freelist;    /* Index into elements[], start of free element list */
};

/*
 * The initial and maximum number of elements in the heap.
 */
static const size_t min_nodes = 8;
static const size_t max_nodes =
        SIZE_MAX / (sizeof(struct pq_heap_st) > sizeof(struct pq_elem_st)
                    ? sizeof(struct pq_heap_st) : sizeof(struct pq_elem_st));

#ifndef NDEBUG
/* Some basic sanity checking of the data structure */
# define ASSERT_USED(pq, idx)                                               \
    assert(pq->elements[pq->heap[idx].index].used);                         \
    assert(pq->elements[pq->heap[idx].index].posn == idx)
# define ASSERT_ELEM_USED(pq, elem)                                         \
    assert(pq->elements[elem].used)
#else
# define ASSERT_USED(pq, idx)
# define ASSERT_ELEM_USED(pq, elem)
#endif

/*
 * Calculate the array growth based on the target size.
 *
 * The growth factor is a rational number and is defined by a numerator
 * and a denominator.  According to Andrew Koenig in his paper "Why Are
 * Vectors Efficient?" from JOOP 11(5) 1998, this factor should be less
 * than the golden ratio (1.618...).
 *
 * We use an expansion factor of 8 / 5 = 1.6
 */
static ossl_inline size_t compute_pqueue_growth(size_t target, size_t current)
{
    int err = 0;

    while (current < target) {
        if (current >= max_nodes)
            return 0;

        current = safe_muldiv_size_t(current, 8, 5, &err);
        if (err)
            return 0;
        if (current >= max_nodes)
            current = max_nodes;
    }
    return current;
}

static ossl_inline void pqueue_swap_elem(OSSL_PQUEUE *pq, size_t i, size_t j)
{
    struct pq_heap_st *h = pq->heap, t_h;
    struct pq_elem_st *e = pq->elements;

    ASSERT_USED(pq, i);
    ASSERT_USED(pq, j);

    t_h = h[i];
    h[i] = h[j];
    h[j] = t_h;

    e[h[i].index].posn = i;
    e[h[j].index].posn = j;
}

static ossl_inline void pqueue_move_elem(OSSL_PQUEUE *pq, size_t from, size_t to)
{
    struct pq_heap_st *h = pq->heap;
    struct pq_elem_st *e = pq->elements;

    ASSERT_USED(pq, from);

    h[to] = h[from];
    e[h[to].index].posn = to;
}

/*
 * Force the specified element to the front of the heap.  This breaks
 * the heap partial ordering pre-condition.
 */
static ossl_inline void pqueue_force_bottom(OSSL_PQUEUE *pq, size_t n)
{
    ASSERT_USED(pq, n);
    while (n > 0) {
        const size_t p = (n - 1) / 2;

        ASSERT_USED(pq, p);
        pqueue_swap_elem(pq, n, p);
        n = p;
    }
}

/*
 * Move an element down to its correct position to restore the partial
 * order pre-condition.
 */
static ossl_inline void pqueue_move_down(OSSL_PQUEUE *pq, size_t n)
{
    struct pq_heap_st *h = pq->heap;

    ASSERT_USED(pq, n);
    while (n > 0) {
        const size_t p = (n - 1) / 2;

        ASSERT_USED(pq, p);
        if (pq->compare(h[n].data, h[p].data) >= 0)
            break;
        pqueue_swap_elem(pq, n, p);
        n = p;
    }
}

/*
 * Move an element up to its correct position to restore the partial
 * order pre-condition.
 */
static ossl_inline void pqueue_move_up(OSSL_PQUEUE *pq, size_t n)
{
    struct pq_heap_st *h = pq->heap;
    size_t p = n * 2 + 1;

    ASSERT_USED(pq, n);
    if (pq->htop > p + 1) {
        ASSERT_USED(pq, p);
        ASSERT_USED(pq, p + 1);
        if (pq->compare(h[p].data, h[p + 1].data) > 0)
            p++;
    }
    while (pq->htop > p && pq->compare(h[p].data, h[n].data) < 0) {
        ASSERT_USED(pq, p);
        pqueue_swap_elem(pq, n, p);
        n = p;
        p = n * 2 + 1;
        if (pq->htop > p + 1) {
            ASSERT_USED(pq, p + 1);
            if (pq->compare(h[p].data, h[p + 1].data) > 0)
                p++;
        }
    }
}

int ossl_pqueue_push(OSSL_PQUEUE *pq, void *data, size_t *elem)
{
    size_t n, m;

    if (!ossl_pqueue_reserve(pq, 1))
        return 0;

    n = pq->htop++;
    m = pq->freelist;
    pq->freelist = pq->elements[m].posn;

    pq->heap[n].data = data;
    pq->heap[n].index = m;

    pq->elements[m].posn = n;
#ifndef NDEBUG
    pq->elements[m].used = 1;
#endif
    pqueue_move_down(pq, n);
    if (elem != NULL)
        *elem = m;
    return 1;
}

void *ossl_pqueue_peek(const OSSL_PQUEUE *pq)
{
    if (pq->htop > 0) {
        ASSERT_USED(pq, 0);
        return pq->heap->data;
    }
    return NULL;
}

void *ossl_pqueue_pop(OSSL_PQUEUE *pq)
{
    void *res;
    size_t elem;

    if (pq == NULL || pq->htop == 0)
        return NULL;

    ASSERT_USED(pq, 0);
    res = pq->heap->data;
    elem = pq->heap->index;

    if (--pq->htop != 0) {
        pqueue_move_elem(pq, pq->htop, 0);
        pqueue_move_up(pq, 0);
    }

    pq->elements[elem].posn = pq->freelist;
    pq->freelist = elem;
#ifndef NDEBUG
    pq->elements[elem].used = 0;
#endif
    return res;
}

void *ossl_pqueue_remove(OSSL_PQUEUE *pq, size_t elem)
{
    size_t n;

    if (pq == NULL || elem >= pq->hmax || pq->htop == 0)
        return 0;

    ASSERT_ELEM_USED(pq, elem);
    n = pq->elements[elem].posn;

    ASSERT_USED(pq, n);

    if (n == pq->htop - 1) {