Coverage Report

Created: 2026-07-15 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libredwg/src/hash.c
Line
Count
Source
1
/*****************************************************************************/
2
/*  LibreDWG - free implementation of the DWG file format                    */
3
/*                                                                           */
4
/*  Copyright (C) 2018-2019,2023 Free Software Foundation, Inc.              */
5
/*                                                                           */
6
/*  This library is free software, licensed under the terms of the GNU       */
7
/*  General Public License as published by the Free Software Foundation,     */
8
/*  either version 3 of the License, or (at your option) any later version.  */
9
/*  You should have received a copy of the GNU General Public License        */
10
/*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
11
/*****************************************************************************/
12
13
/*
14
 * hash.c: int hashmap for the object_ref map.
15
 *         uses linear probing for best cache usage.
16
 *         values are inlined into the array. The 0 key is disallowed.
17
 * written by Reini Urban
18
 */
19
20
#include "hash.h"
21
#include <stdlib.h>
22
#include <stdio.h>
23
#include <string.h>
24
#include "logging.h"
25
26
dwg_inthash *
27
hash_new (uint64_t size)
28
16.5k
{
29
16.5k
  dwg_inthash *hash = (dwg_inthash *)malloc (sizeof (dwg_inthash));
30
16.5k
  uint64_t cap;
31
16.5k
  if (!hash)
32
0
    return NULL;
33
  // multiply with load factor,
34
  // and round size to next power of 2 (fast) or prime (secure),
35
16.5k
  if (size < 15)
36
14.5k
    size = 15;
37
16.5k
  cap = (uint64_t)(size * 100.0 / HASH_LOAD);
38
  // this is slow, but only done once. clz would be much faster
39
33.0k
  while (size <= cap)
40
16.5k
    size <<= 1U;
41
16.5k
  hash->array = (struct _hashbucket *)calloc (
42
16.5k
      size, sizeof (struct _hashbucket)); // key+value pairs
43
16.5k
  hash->elems = 0;
44
16.5k
  hash->size = size;
45
16.5k
  return hash;
46
16.5k
}
47
48
// if exceeds load factor
49
static inline int
50
hash_need_resize (dwg_inthash *hash)
51
3.09k
{
52
3.09k
  return (uint64_t)(hash->elems * 100.0 / HASH_LOAD) > hash->size;
53
3.09k
}
54
55
static void
56
hash_resize (dwg_inthash *hash)
57
3.09k
{
58
3.09k
  dwg_inthash oldhash = *hash;
59
3.09k
  uint64_t size = hash->size * 2;
60
3.09k
  uint64_t i;
61
62
  // allocate key+value pairs afresh
63
3.09k
  hash->array
64
3.09k
      = (struct _hashbucket *)calloc (size, sizeof (struct _hashbucket));
65
3.09k
  if (!hash->array)
66
0
    {
67
0
      *hash = oldhash;
68
0
      return;
69
0
    }
70
3.09k
  hash->elems = 0;
71
3.09k
  hash->size = size;
72
3.09k
  memset (hash->array, 0, size * sizeof (struct _hashbucket));
73
  // spread out the old elements in double space, less collisions
74
421k
  for (i = 0; i < oldhash.size; i++)
75
418k
    {
76
418k
      if (oldhash.array[i].key)
77
418k
        hash_set (hash, oldhash.array[i].key, oldhash.array[i].value);
78
418k
    }
79
3.09k
  free (oldhash.array);
80
3.09k
  return;
81
3.09k
}
82
83
// found this gem by Thomas Mueller at stackoverflow. triviality threshold.
84
// it's like a normal murmur or jenkins finalizer,
85
// just statistically tested to be optimal.
86
// 2023: changed to 64bit, checked at https://nullprogram.com/blog/2018/07/31/
87
// Note that this is entirely "insecure", the inverse func is trivial.
88
// We don't care as we deal with DWG and had linear search before.
89
static inline uint64_t
90
hash_func (uint64_t key)
91
3.71M
{
92
3.71M
  key = ((key >> 32) ^ key) * UINT64_C (0xd6e8feb86659fd93);
93
3.71M
  key = ((key >> 32) ^ key) * UINT64_C (0xd6e8feb86659fd93);
94
3.71M
  key = (key >> 32) ^ key;
95
3.71M
  return key;
96
3.71M
}
97
98
// 0 is disallowed as key, even if there's no deletion.
99
uint64_t
100
hash_get (dwg_inthash *hash, uint64_t key)
101
2.71M
{
102
2.71M
  uint64_t i = hash_func (key) % hash->size;
103
2.71M
  uint64_t j = i;
104
19.2M
  while (hash->array[i].key && hash->array[i].key != key)
105
16.5M
    {
106
      // HANDLER (OUTPUT, "get collision at %d\n", i);
107
16.5M
      i++; // linear probing with wrap around
108
16.5M
      if (i == hash->size)
109
206k
        i = 0;
110
16.5M
      if (i == j) // not found
111
3.26k
        return HASH_NOT_FOUND;
112
16.5M
    }
113
2.71M
  if (hash->array[i].key)
114
289k
    return hash->array[i].value;
115
2.42M
  else
116
2.42M
    return HASH_NOT_FOUND;
117
2.71M
}
118
119
// search or insert. key 0 is forbidden.
120
void
121
hash_set (dwg_inthash *hash, uint64_t key, uint64_t value)
122
994k
{
123
994k
  uint64_t i = hash_func (key) % hash->size;
124
994k
  uint64_t j = i;
125
994k
  if (key == 0)
126
164
    {
127
164
      HANDLER (OUTPUT, "forbidden 0 key\n");
128
164
      return;
129
164
    }
130
  // empty slot
131
994k
  if (!hash->array[i].key)
132
487k
    {
133
487k
      hash->array[i].key = key;
134
487k
      hash->array[i].value = value;
135
487k
      hash->elems++;
136
487k
      return;
137
487k
    }
138
6.99M
  while (hash->array[i].key)
139
6.65M
    {
140
6.65M
      if (hash->array[i].key == key)
141
163k
        { // found
142
163k
          hash->array[i].value = value;
143
163k
          return;
144
163k
        }
145
      // HANDLER (OUTPUT, "set collision at %d\n", i);
146
6.49M
      i++; // linear probing with wrap around
147
6.49M
      if (i == hash->size)
148
17.2k
        i = 0;
149
6.49M
      if (i == j) // not found
150
3.09k
        {
151
          // HANDLER (OUTPUT, "set not found at %d\n", i);
152
          // if does not exist, add at i+1
153
3.09k
          if (hash_need_resize (hash))
154
3.09k
            {
155
              // HANDLER (OUTPUT, "resize at %d\n", hash->size);
156
3.09k
              hash_resize (hash);
157
3.09k
              hash_set (hash, key, value);
158
3.09k
              return;
159
3.09k
            }
160
0
          while (hash->array[i].key) // find next empty slot
161
0
            {
162
              // up to here we have no coverage!
163
              // HANDLER (OUTPUT, "set 2nd collision at %d\n", i);
164
0
              i++; // again linear probing with wrap around
165
0
              if (i == hash->size)
166
0
                i = 0;
167
0
              if (i == j) // not found
168
0
                {
169
                  // HANDLER (OUTPUT, "not found resize at %d\n", hash->size);
170
0
                  hash_resize (hash); // guarantees new empty slots
171
0
                  hash_set (hash, key, value);
172
0
                  return;
173
0
                }
174
0
              else
175
0
                { // insert at empty slot
176
0
                  hash->array[i].key = key;
177
0
                  hash->array[i].value = value;
178
0
                  hash->elems++;
179
0
                  return;
180
0
                }
181
0
            }
182
0
        }
183
6.49M
    }
184
  // empty slot
185
339k
  hash->array[i].key = key;
186
339k
  hash->array[i].value = value;
187
339k
  hash->elems++;
188
339k
  return;
189
506k
}
190
191
void
192
hash_free (dwg_inthash *hash)
193
16.5k
{
194
16.5k
  free (hash->array);
195
  hash->array = NULL;
196
16.5k
  hash->size = 0;
197
16.5k
  hash->elems = 0;
198
16.5k
  free (hash);
199
16.5k
}