FFmpeg
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
hashtable.c
Go to the documentation of this file.
1 /*
2  * Generic hashtable
3  * Copyright (C) 2025 Emma Worley <emma@emma.gg>
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with FFmpeg; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 #include <stdint.h>
23 #include <string.h>
24 
25 #include "libavutil/attributes.h"
26 #include "libavutil/crc.h"
27 #include "libavutil/error.h"
28 #include "libavutil/macros.h"
29 #include "libavutil/mem.h"
30 #include "hashtable.h"
31 
32 #define ALIGN _Alignof(size_t)
33 
35  size_t key_size;
36  size_t val_size;
37  size_t entry_size;
38  size_t max_entries;
39  size_t nb_entries;
40  const AVCRC *crc;
41  uint8_t *table;
42  uint8_t swapbuf[];
43 };
44 
45 /*
46  * Hash table entries are comprised of a probe sequence length (PSL), key, and
47  * value. When the PSL of an entry is zero, it means it is not occupied by a
48  * key/value pair. When the PSL is non-zero, it represents the "distance" of
49  * the entry from its "home" location plus one, where the "home" location is
50  * hash(key) % max_entries.
51  */
52 
53 #define ENTRY_PSL_VAL(entry) (*(size_t*)(entry))
54 #define ENTRY_KEY_PTR(entry) ((entry) + sizeof(size_t))
55 #define ENTRY_VAL_PTR(entry) (ENTRY_KEY_PTR(entry) + ctx->key_size)
56 
57 #define KEYS_EQUAL(k1, k2) (!memcmp((k1), (k2), ctx->key_size))
58 
60  size_t val_size, size_t max_entries)
61 {
62  const size_t keyval_size = key_size + val_size;
63 
64  if (keyval_size < key_size || // did (unsigned,defined) wraparound happen?
65  keyval_size > FFMIN(SIZE_MAX - sizeof(size_t) - (ALIGN - 1),
66  (SIZE_MAX - sizeof(FFHashtableContext)) / 2))
67  return AVERROR(ERANGE);
68 
69  FFHashtableContext *res = av_mallocz(sizeof(*res) + 2 * keyval_size);
70  if (!res)
71  return AVERROR(ENOMEM);
72  res->key_size = key_size;
73  res->val_size = val_size;
74  res->entry_size = FFALIGN(sizeof(size_t) + keyval_size, ALIGN);
75  res->max_entries = max_entries;
76  res->nb_entries = 0;
78  if (!res->crc) {
79  ff_hashtable_freep(&res);
80  return AVERROR_BUG;
81  }
82  res->table = av_calloc(res->max_entries, res->entry_size);
83  if (!res->table) {
84  ff_hashtable_freep(&res);
85  return AVERROR(ENOMEM);
86  }
87 
88  *ctx = res;
89  return 0;
90 }
91 
92 static size_t hash_key(const struct FFHashtableContext *ctx, const void *key)
93 {
94  return av_crc(ctx->crc, 0, key, ctx->key_size) % ctx->max_entries;
95 }
96 
97 int ff_hashtable_get(const struct FFHashtableContext *ctx, const void *key, void *val)
98 {
99  if (!ctx->nb_entries)
100  return 0;
101 
102  size_t hash = hash_key(ctx, key);
103 
104  for (size_t psl = 1; psl <= ctx->max_entries; psl++) {
105  size_t wrapped_index = (hash + psl) % ctx->max_entries;
106  uint8_t *entry = ctx->table + wrapped_index * ctx->entry_size;
107  if (ENTRY_PSL_VAL(entry) < psl)
108  // When PSL stops increasing it means there are no further entries
109  // with the same key hash.
110  return 0;
112  memcpy(val, ENTRY_VAL_PTR(entry), ctx->val_size);
113  return 1;
114  }
115  }
116  return 0;
117 }
118 
119 int ff_hashtable_set(struct FFHashtableContext *ctx, const void *key, const void *val)
120 {
121  int swapping = 0;
122  size_t psl = 1;
123  size_t hash = hash_key(ctx, key);
124  size_t wrapped_index = hash % ctx->max_entries;
125  uint8_t *set = ctx->swapbuf;
126  uint8_t *tmp = ctx->swapbuf + ctx->key_size + ctx->val_size;
127 
128  memcpy(set, key, ctx->key_size);
129  memcpy(set + ctx->key_size, val, ctx->val_size);
130 
131  for (size_t i = 0; i < ctx->max_entries; i++) {
132  if (++wrapped_index == ctx->max_entries)
133  wrapped_index = 0;
134  uint8_t *entry = ctx->table + wrapped_index * ctx->entry_size;
135  if (!ENTRY_PSL_VAL(entry) || (!swapping && KEYS_EQUAL(ENTRY_KEY_PTR(entry), set))) {
136  if (!ENTRY_PSL_VAL(entry))
137  ctx->nb_entries++;
138  ENTRY_PSL_VAL(entry) = psl;
139  memcpy(ENTRY_KEY_PTR(entry), set, ctx->key_size + ctx->val_size);
140  return 1;
141  }
142  if (ENTRY_PSL_VAL(entry) < psl) {
143  // When PSL stops increasing it means there are no further entries
144  // with the same key hash. We can only hope to find an unoccupied
145  // entry.
146  if (ctx->nb_entries == ctx->max_entries)
147  // The table is full so inserts are impossible.
148  return 0;
149  // Robin Hood hash tables "steal from the rich" by minimizing the
150  // PSL of the inserted entry.
151  swapping = 1;
152  // set needs to swap with entry
153  memcpy(tmp, ENTRY_KEY_PTR(entry), ctx->key_size + ctx->val_size);
154  memcpy(ENTRY_KEY_PTR(entry), set, ctx->key_size + ctx->val_size);
155  FFSWAP(uint8_t*, set, tmp);
156  FFSWAP(size_t, psl, ENTRY_PSL_VAL(entry));
157  }
158  psl++;
159  }
160  return 0;
161 }
162 
164 {
165  if (!ctx->nb_entries)
166  return 0;
167 
168  uint8_t *next_entry;
169  size_t hash = hash_key(ctx, key);
170  size_t wrapped_index = hash % ctx->max_entries;
171 
172  for (size_t psl = 1; psl <= ctx->max_entries; psl++) {
173  if (++wrapped_index == ctx->max_entries)
174  wrapped_index = 0;
175  uint8_t *entry = ctx->table + wrapped_index * ctx->entry_size;
176  if (ENTRY_PSL_VAL(entry) < psl)
177  // When PSL stops increasing it means there are no further entries
178  // with the same key hash.
179  return 0;
181  ENTRY_PSL_VAL(entry) = 0;
182  // Shift each following entry that will benefit from a reduced PSL.
183  for (psl++; psl <= ctx->max_entries; psl++) {
184  if (++wrapped_index == ctx->max_entries)
185  wrapped_index = 0;
186  next_entry = ctx->table + wrapped_index * ctx->entry_size;
187  if (ENTRY_PSL_VAL(next_entry) <= 1) {
188  ctx->nb_entries--;
189  return 1;
190  }
191  memcpy(entry, next_entry, ctx->entry_size);
192  ENTRY_PSL_VAL(entry)--;
193  ENTRY_PSL_VAL(next_entry) = 0;
194  entry = next_entry;
195  }
196  }
197  }
198  return 0;
199 }
200 
202 {
203  memset(ctx->table, 0, ctx->entry_size * ctx->max_entries);
204 }
205 
207 {
208  if (*ctx) {
209  av_freep(&(*ctx)->table);
210  av_freep(ctx);
211  }
212 }
ENTRY_PSL_VAL
#define ENTRY_PSL_VAL(entry)
Definition: hashtable.c:53
hash_key
static size_t hash_key(const struct FFHashtableContext *ctx, const void *key)
Definition: hashtable.c:92
entry
#define entry
Definition: aom_film_grain_template.c:66
ff_hashtable_delete
int ff_hashtable_delete(struct FFHashtableContext *ctx, const void *key)
Delete a value from a hash table given a key.
Definition: hashtable.c:163
ALIGN
#define ALIGN
Definition: hashtable.c:32
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
FFHashtableContext::crc
const AVCRC * crc
Definition: hashtable.c:40
AVCRC
uint32_t AVCRC
Definition: crc.h:46
ff_hashtable_freep
av_cold void ff_hashtable_freep(FFHashtableContext **ctx)
Free a hash table.
Definition: hashtable.c:206
hash
uint8_t hash[HASH_SIZE]
Definition: movenc.c:58
FFHashtableContext::nb_entries
size_t nb_entries
Definition: hashtable.c:39
crc.h
macros.h
val
static double val(void *priv, double ch)
Definition: aeval.c:77
KEYS_EQUAL
#define KEYS_EQUAL(k1, k2)
Definition: hashtable.c:57
av_cold
#define av_cold
Definition: attributes.h:90
set
static void set(uint8_t *a[], int ch, int index, int ch_count, enum AVSampleFormat f, double v)
Definition: swresample.c:59
FFHashtableContext
Definition: hashtable.c:34
FFHashtableContext::max_entries
size_t max_entries
Definition: hashtable.c:38
FFHashtableContext::key_size
size_t key_size
Definition: hashtable.c:35
ctx
AVFormatContext * ctx
Definition: movenc.c:49
FFHashtableContext::table
uint8_t * table
Definition: hashtable.c:41
key
const char * key
Definition: hwcontext_opencl.c:189
if
if(ret)
Definition: filter_design.txt:179
FFHashtableContext::val_size
size_t val_size
Definition: hashtable.c:36
tmp
static uint8_t tmp[20]
Definition: aes_ctr.c:47
FFHashtableContext::entry_size
size_t entry_size
Definition: hashtable.c:37
error.h
ff_hashtable_clear
void ff_hashtable_clear(struct FFHashtableContext *ctx)
Delete all values from a hash table.
Definition: hashtable.c:201
hashtable.h
ENTRY_KEY_PTR
#define ENTRY_KEY_PTR(entry)
Definition: hashtable.c:54
av_crc_get_table
const AVCRC * av_crc_get_table(AVCRCId crc_id)
Get an initialized standard CRC table.
Definition: crc.c:374
attributes.h
ENTRY_VAL_PTR
#define ENTRY_VAL_PTR(entry)
Definition: hashtable.c:55
i
#define i(width, name, range_min, range_max)
Definition: cbs_h2645.c:256
ff_hashtable_set
int ff_hashtable_set(struct FFHashtableContext *ctx, const void *key, const void *val)
Store a value in a hash table given a key.
Definition: hashtable.c:119
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
av_mallocz
void * av_mallocz(size_t size)
Allocate a memory block with alignment suitable for all memory accesses (including vectors if availab...
Definition: mem.c:256
AV_CRC_32_IEEE
@ AV_CRC_32_IEEE
Definition: crc.h:52
av_calloc
void * av_calloc(size_t nmemb, size_t size)
Definition: mem.c:264
FFSWAP
#define FFSWAP(type, a, b)
Definition: macros.h:52
av_crc
uint32_t av_crc(const AVCRC *ctx, uint32_t crc, const uint8_t *buffer, size_t length)
Calculate the CRC of a block.
Definition: crc.c:392
mem.h
FFALIGN
#define FFALIGN(x, a)
Definition: macros.h:78
FFHashtableContext::swapbuf
uint8_t swapbuf[]
Definition: hashtable.c:42
av_freep
#define av_freep(p)
Definition: tableprint_vlc.h:35
ff_hashtable_get
int ff_hashtable_get(const struct FFHashtableContext *ctx, const void *key, void *val)
Look up a value from a hash table given a key.
Definition: hashtable.c:97
AVERROR_BUG
#define AVERROR_BUG
Internal bug, also see AVERROR_BUG2.
Definition: error.h:52
ff_hashtable_alloc
av_cold int ff_hashtable_alloc(FFHashtableContext **ctx, size_t key_size, size_t val_size, size_t max_entries)
Create a fixed-sized Robin Hood hash table.
Definition: hashtable.c:59