├── .gitignore ├── LICENSE ├── README.txt ├── reloadhost.c ├── reloadhost.h └── util └── map.h /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*/ 3 | !*.* 4 | !Makefile 5 | Session.vim 6 | *.glsl.h 7 | 8 | imgui.ini 9 | 10 | bin/ 11 | .idea/ 12 | 13 | *.tmp 14 | 15 | # shader output 16 | *.bin 17 | 18 | .cache/ 19 | .vimrc 20 | 21 | *.icloud 22 | .DS_Store 23 | .vscode 24 | .ccls* 25 | compile_flags.txt 26 | compile_commands.json 27 | 28 | # Prerequisites 29 | *.d 30 | 31 | # Object files 32 | *.o 33 | *.ko 34 | *.obj 35 | *.elf 36 | 37 | # Linker output 38 | *.ilk 39 | *.map 40 | *.exp 41 | 42 | # Precompiled Headers 43 | *.gch 44 | *.pch 45 | 46 | # Libraries 47 | *.lib 48 | *.a 49 | *.la 50 | *.lo 51 | 52 | # Shared objects (inc. Windows DLLs) 53 | *.dll 54 | *.so 55 | *.so.* 56 | *.dylib 57 | 58 | # Executables 59 | *.exe 60 | *.out 61 | *.app 62 | *.i*86 63 | *.x86_64 64 | *.hex 65 | 66 | # Debug files 67 | *.dSYM/ 68 | *.su 69 | *.idb 70 | *.pdb 71 | 72 | # Kernel Module Compile Results 73 | *.mod* 74 | *.cmd 75 | .tmp_versions/ 76 | modules.order 77 | Module.symvers 78 | Mkfile.old 79 | dkms.conf 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 jdh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | simple POSIX platform live code reloading 2 | 3 | === USAGE === 4 | $ reloadhost [argv...] 5 | 6 | === CLIENT === 7 | Your target application will need to support the reloadhost. A simple example: 8 | 9 | #include "reloadhost.h" 10 | 11 | // all of your heap allocated application state 12 | state_t *state; 13 | 14 | // called on RH_RELOAD 15 | static void reload() { 16 | // ... 17 | } 18 | 19 | int RH_ENTRY_NAME( 20 | int argc, 21 | char *argv[], 22 | reload_host_op op, 23 | reload_host_t *reload_host); 24 | 25 | // used when built with RELOADABLE but linked into executable instead of shared 26 | int main(int argc, char *argv[]) { 27 | int res; 28 | if ((res = RH_ENTRY_NAME(argc, argv, RH_INIT, NULL))) { return res; } 29 | while (true) { 30 | if ((res = RH_ENTRY_NAME(argc, argv, RH_STEP, NULL))) { 31 | if (res == RH_CLOSE_REQUESTED) { 32 | break; 33 | } else { 34 | return res; 35 | } 36 | } 37 | } 38 | return RH_ENTRY_NAME(argc, argv, RH_DEINIT, NULL); 39 | } 40 | 41 | // reload_host entry point 42 | int RH_ENTRY_NAME( 43 | int argc, 44 | char *argv[], 45 | reload_host_op op, 46 | reload_host_t *reload_host) { 47 | if (!state) { 48 | state = calloc(1, sizeof(*state)); 49 | } 50 | 51 | switch (op) { 52 | case RH_INIT: 53 | state = calloc(1, sizeof(*state)); 54 | if (reload_host) { 55 | reload_host->userdata = state; 56 | state->reload_host = reload_host; 57 | } 58 | init(); 59 | return 0; 60 | case RH_DEINIT: 61 | deinit(); 62 | return 0; 63 | case RH_RELOAD: 64 | assert(reload_host); 65 | state = reload_host->userdata; 66 | reload(); 67 | return 0; 68 | case RH_STEP: 69 | if (reload_host) { 70 | state = reload_host->userdata; 71 | } 72 | if (state->quit) { return RH_CLOSE_REQUESTED; } 73 | } 74 | 75 | if (reload_host) { 76 | state->reload_host = reload_host; 77 | } 78 | 79 | // whatever your loop function is 80 | loop(); 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /reloadhost.c: -------------------------------------------------------------------------------- 1 | #ifndef UTIL_IMPL 2 | #define UTIL_IMPL 3 | #include 4 | #endif // ifndef UTIL_IMPL 5 | 6 | #include "util/map.h" 7 | #include "reloadhost.h" 8 | 9 | #define _POSIX_C_SOURCE 200809L 10 | #include 11 | #undef _POSIX_C_SOURCE 12 | 13 | #define _GNU_SOURCE 14 | #include 15 | #include 16 | #include 17 | 18 | #define _STRINGIFY_IMPL(x) #x 19 | #define STRINGIFY(x) _STRINGIFY_IMPL(x) 20 | 21 | typedef struct { 22 | char *name; 23 | void *storage; 24 | } func_storage_t; 25 | 26 | // persistent data exposed to client 27 | static reload_host_t rh; 28 | 29 | // currently loaded module 30 | static void *module = NULL; 31 | 32 | // map of storage address -> char* function name 33 | static map_t funcs; 34 | 35 | // see reload_host::reg_fn 36 | static void reg_fn(void **p) { 37 | Dl_info info; 38 | dladdr(*p, &info); 39 | map_insert(&funcs, p, strdup(info.dli_sname)); 40 | } 41 | 42 | // see reload_host::del_fn 43 | static void del_fn(void **p) { 44 | map_remove(&funcs, p); 45 | } 46 | 47 | int main(int argc, char *argv[]) { 48 | if (argc < 2) { 49 | printf("%s", "usage: reloadhost [args...]"); 50 | } 51 | 52 | rh = (reload_host_t) { 53 | .reg_fn = reg_fn, 54 | .del_fn = del_fn, 55 | .userdata = NULL 56 | }; 57 | 58 | map_init( 59 | &funcs, 60 | map_hash_id, 61 | NULL, 62 | NULL, 63 | NULL, 64 | map_cmp_id, 65 | NULL, 66 | map_default_free, 67 | NULL); 68 | 69 | struct timespec mod_time; 70 | rh_entry_f func = NULL; 71 | reload_host_op op = RH_INIT; 72 | 73 | while (true) { 74 | struct stat st; 75 | assert(stat(argv[1], &st) >= 0); 76 | 77 | struct timespec ts; 78 | timespec_get(&ts, TIME_UTC); 79 | 80 | // reload 81 | if (op == RH_INIT 82 | || (st.st_mtimespec.tv_sec > mod_time.tv_sec 83 | && ts.tv_sec > st.st_mtimespec.tv_sec + 1)) { 84 | if (module) { 85 | assert(!dlclose(module)); 86 | module = NULL; 87 | } 88 | 89 | dlerror(); 90 | module = dlopen(argv[1], RTLD_LOCAL | RTLD_LAZY); 91 | assert(module); 92 | 93 | func = (rh_entry_f) dlsym(module, STRINGIFY(RH_ENTRY_NAME)); 94 | assert(func); 95 | mod_time = st.st_mtimespec; 96 | 97 | if (op != RH_INIT) { 98 | op = RH_RELOAD; 99 | } 100 | } 101 | 102 | if (op == RH_RELOAD) { 103 | // reload function pointers 104 | map_each(void**, char*, &funcs, it) { 105 | void *sym = dlsym(module, it.value); 106 | assert(sym); 107 | memcpy(it.key, &sym, sizeof(void*)); 108 | } 109 | } 110 | 111 | const int res = func(argc - 1, &argv[1], op, &rh); 112 | if (res) { 113 | if (res == RH_CLOSE_REQUESTED) { 114 | printf("%s", "client requested close, exiting"); 115 | return func(argc - 1, &argv[1], RH_DEINIT, &rh); 116 | } else { 117 | printf("client exited with code %d", res); 118 | return res; 119 | } 120 | } 121 | 122 | op = RH_STEP; 123 | } 124 | 125 | map_destroy(&funcs); 126 | 127 | return 0; 128 | } 129 | -------------------------------------------------------------------------------- /reloadhost.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // operations for f_rh_entry 4 | // RH_INIT: client should initialize 5 | // RH_DEINIT: client should close 6 | // RH_STEP: client step (loop operation) 7 | // RH_RELOAD: client has been reloaded 8 | typedef enum { 9 | RH_INIT, 10 | RH_DEINIT, 11 | RH_STEP, 12 | RH_RELOAD 13 | } reload_host_op; 14 | 15 | // f_rh_entry returns this to request that it be called with RH_DEINIT 16 | #define RH_CLOSE_REQUESTED INT_MAX 17 | 18 | // name of f_rh_entry function in client 19 | #define RH_ENTRY_NAME rh_entry 20 | 21 | typedef struct reload_host reload_host_t; 22 | 23 | // see reload_host::regfunc 24 | typedef void (*rh_regfunc_f)(void **p); 25 | 26 | // see reload_host::delfunc 27 | typedef void (*rh_delfunc_f)(void **p); 28 | 29 | // type of entry function in client 30 | typedef int (*rh_entry_f)(int, char*[], reload_host_op, reload_host_t*); 31 | 32 | typedef struct reload_host { 33 | // pointer to function pointer registry function 34 | // 35 | // usage: 36 | // 37 | // struct foo { int (*funcptr)(); } 38 | // int myfunc() { ... } 39 | // struct foo f = malloc(sizeof(struct foo)); 40 | // f->funcptr = myfunc; 41 | // reload_host->regfunc(&f->funcptr); 42 | // 43 | // now f->funcptr is properly changed on code reload 44 | rh_regfunc_f reg_fn; 45 | 46 | // see regfunc 47 | // when heap storage for function pointer is free'd, call delfunc() with the 48 | // same address to free the function pointer in the reload_host 49 | rh_delfunc_f del_fn; 50 | 51 | // userdata pointer, can be used by client for arbitrary storage on reload 52 | // host 53 | void *userdata; 54 | } reload_host_t; 55 | 56 | -------------------------------------------------------------------------------- /util/map.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef uint64_t hash_t; 9 | 10 | typedef hash_t (*f_map_hash)(void*, void*); 11 | typedef void *(*f_map_dup)(void*, void*); 12 | typedef void (*f_map_free)(void*, void*); 13 | typedef int (*f_map_cmp)(void*, void*, void*); 14 | typedef void *(*f_map_alloc)(size_t, void*, void*); 15 | 16 | typedef struct map_entry_t { 17 | void *key, *value; 18 | } map_entry_t; 19 | 20 | typedef struct map_internal_entry_t { 21 | map_entry_t entry; 22 | uint16_t dist; 23 | hash_t hash : 15; 24 | int used : 1; 25 | } map_internal_entry_t; 26 | 27 | typedef struct map_t { 28 | f_map_hash f_hash; 29 | f_map_dup f_keydup; 30 | f_map_cmp f_keycmp; 31 | f_map_free f_keyfree, f_valfree; 32 | 33 | // internal allocation functions, default to malloc/free, can be replaced 34 | f_map_alloc f_alloc; 35 | f_map_free f_free; 36 | 37 | size_t used, capacity, prime; 38 | map_internal_entry_t *entries; 39 | 40 | void *userdata; 41 | } map_t; 42 | 43 | enum { 44 | MAP_INSERT_ENTRY_IS_NEW = 1 << 0 45 | }; 46 | 47 | // hash functions 48 | hash_t map_hash_id(void *p, void*); 49 | hash_t map_hash_str(void *p, void*); 50 | 51 | // compare functions 52 | int map_cmp_id(void *p, void *q, void*); 53 | int map_cmp_str(void *p, void *q, void*); 54 | 55 | // duplication functions 56 | void *map_dup_str(void *s, void*); 57 | 58 | // default map_alloc_fn implemented with stdlib's "realloc()" 59 | void *map_default_alloc(size_t n, void *p, void*); 60 | 61 | // default map_free_fn implemented with stdlib's "free()" 62 | void map_default_free(void *p, void*); 63 | 64 | // internal usage only 65 | size_t _map_insert( 66 | map_t *self, 67 | hash_t hash, 68 | void *key, 69 | void *value, 70 | int flags, 71 | int *rehash_out); 72 | 73 | // internal usage only 74 | size_t _map_find(const map_t *self, hash_t hash, void *key); 75 | 76 | // internal usage only 77 | void _map_remove_at(map_t *self, size_t pos); 78 | 79 | // internal usage only 80 | void _map_remove(map_t *self, hash_t hash, void *key); 81 | 82 | // create new map 83 | void map_init( 84 | map_t *self, 85 | f_map_hash f_hash, 86 | f_map_alloc f_alloc, 87 | f_map_free f_free, 88 | f_map_dup f_keydup, 89 | f_map_cmp f_keycmp, 90 | f_map_free f_keyfree, 91 | f_map_free f_valfree, 92 | void *userdata); 93 | 94 | // destroy map 95 | void map_destroy(map_t *self); 96 | 97 | // clear map of all keys and values 98 | void map_clear(map_t *self); 99 | 100 | // insert (k, v) into map, replacing value if it is already present, returns 101 | // pointer to value 102 | #define _map_insert3(_m, _k, _v) ({ \ 103 | map_t *__m = (_m); \ 104 | void *__k = (void*)(uintptr_t)(_k), *__v = (void*)(uintptr_t)(_v); \ 105 | size_t __n = \ 106 | _map_insert( \ 107 | __m, \ 108 | __m->f_hash(__k, __m->userdata), \ 109 | __k, \ 110 | __v, \ 111 | MAP_INSERT_ENTRY_IS_NEW, \ 112 | NULL); \ 113 | &__m->entries[__n].entry.value; \ 114 | }) 115 | 116 | // insert (k, v) into map, replacing value if it is already present, returns 117 | // pointer T* to value 118 | #define _map_insert4(T, _m, _k, _v) ({ \ 119 | map_t *__m = (_m); \ 120 | void *__k = (void*)(uintptr_t)(_k), *__v = (void*)(uintptr_t)(_v); \ 121 | size_t __n = \ 122 | _map_insert( \ 123 | __m, \ 124 | __m->f_hash(__k, __m->userdata), \ 125 | __k, \ 126 | __v, \ 127 | MAP_INSERT_ENTRY_IS_NEW); \ 128 | (T*) (uintptr_t) &__m->entries[__n].entry.value; \ 129 | }) 130 | 131 | // insert (k, v) into map, replacing value if it is already present, returns 132 | // pointer to value 133 | #define map_insert(...) VFUNC(_map_insert, __VA_ARGS__) 134 | 135 | // returns true if map contians key 136 | #define map_contains(_m, _k) ({ \ 137 | const map_t *__m = (_m); \ 138 | void *__k = (void*)(uintptr_t)(_k); \ 139 | _map_find(__m, __m->f_hash(__k, __m->userdata), __k) != SIZE_MAX; \ 140 | }) 141 | 142 | // returns _T *value, NULL if not present 143 | #define _map_find3(_T, _m, _k) ({ \ 144 | const map_t *__m = (_m); \ 145 | void *__k = (void*)(uintptr_t)(_k); \ 146 | size_t _i = _map_find(__m, __m->f_hash(__k, __m->userdata), __k); \ 147 | _i == SIZE_MAX ? NULL : (_T*)(&__m->entries[_i].entry.value); \ 148 | }) 149 | 150 | // returns void **value, NULL if not present 151 | #define _map_find2(_m, _k) ({ \ 152 | const map_t *__m = (_m); \ 153 | void *__k = (void*)(uintptr_t)(_k); \ 154 | size_t _i = _map_find(__m, __m->f_hash(__k, __m->userdata), __k); \ 155 | _i == SIZE_MAX ? NULL : &__m->entries[_i].entry.value; \ 156 | }) 157 | 158 | // map_find(map, key) or map_find(TYPE, map, key) -> ptr to value, NULL if not 159 | // found 160 | #define map_find(...) VFUNC(_map_find, __VA_ARGS__) 161 | 162 | // returns _T value, crashes if not present 163 | #define _map_get3(_T, _m, _k) ({ \ 164 | map_t *__m = (_m); \ 165 | void *__k = (void*)(uintptr_t)(_k); \ 166 | size_t _i = _map_find(__m, __m->f_hash(__k, __m->userdata), __k); \ 167 | assert(_i != SIZE_MAX, "key not found"); \ 168 | *(_T*)(&__m->entries[_i].entry.value); \ 169 | }) 170 | 171 | // returns void *value, crashes if not present 172 | #define _map_get2(_m, _k) ({ \ 173 | map_t *__m = (_m); \ 174 | void *__k = (void*)(uintptr_t)(_k); \ 175 | size_t _i = _map_find(__m, __m->f_hash(__k, __m->userdata), __k); \ 176 | assert(_i != SIZE_MAX, "key not found"); \ 177 | __m->entries[_i].entry.value; \ 178 | }) 179 | 180 | // map_get(map, key) or map_get(TYPE, map, key) 181 | #define map_get(...) VFUNC(_map_get, __VA_ARGS__) 182 | 183 | // remove key k from map 184 | #define map_remove(_m, _k) ({ \ 185 | map_t *__m = (_m); \ 186 | void *__k = (void*)(uintptr_t)(_k); \ 187 | _map_remove(__m, __m->f_hash(__k, __m->userdata), __k); \ 188 | }) 189 | 190 | // get next used entry from map index _i. returns _m->capacity at map end 191 | #define map_next(_m, _i, _first) ({ \ 192 | TYPEOF(_m) __m = (_m); \ 193 | size_t _j = _i; \ 194 | if (!(_first)) { _j++; } \ 195 | while (_j < __m->capacity && !__m->entries[_j].used) { _j++; } \ 196 | assert(_j >= __m->capacity || __m->entries[_j].used); \ 197 | _j; \ 198 | }) 199 | 200 | #define _map_each_impl(_KT, _VT, _m, _it, _itname) \ 201 | typedef struct { \ 202 | TYPEOF(_m) __m; size_t __i; _KT key; _VT value; } _itname; \ 203 | for (_itname _it = { \ 204 | .__m = (_m), \ 205 | .__i = map_next(_it.__m, 0, true), \ 206 | .key = (_KT)(_it.__i < _it.__m->capacity ? \ 207 | (uintptr_t)(_it.__m->entries[_it.__i].entry.key) \ 208 | : 0), \ 209 | .value = (_VT)(_it.__i < _it.__m->capacity ? \ 210 | (uintptr_t)(_it.__m->entries[_it.__i].entry.value) \ 211 | : 0) \ 212 | }; _it.__i < _it.__m->capacity; \ 213 | _it.__i = map_next(_it.__m, _it.__i, false), \ 214 | _it.key = (_KT)(_it.__i < _it.__m->capacity ? \ 215 | (uintptr_t)(_it.__m->entries[_it.__i].entry.key) \ 216 | : 0), \ 217 | _it.value = (_VT)(_it.__i < _it.__m->capacity ? \ 218 | (uintptr_t)(_it.__m->entries[_it.__i].entry.value) \ 219 | : 0)) 220 | 221 | // map_each(KEY_TYPE, VALUE_TYPE, *map, it_name) 222 | #define map_each(_KT, _VT, _m, _it) \ 223 | _map_each_impl( \ 224 | _KT, \ 225 | _VT, \ 226 | _m, \ 227 | _it, \ 228 | CONCAT(_mei, __COUNTER__)) 229 | 230 | // true if map is empty 231 | #define map_empty(_m) ((_m)->used == 0) 232 | 233 | // number of elements in map 234 | #define map_size(_m) ((_m)->used) 235 | 236 | #ifdef UTIL_IMPL 237 | 238 | #define _POSIX_C_SOURCE 200809L 239 | #include 240 | 241 | #define ARRLEN(_arr) ((sizeof((_arr))) / ((sizeof((_arr)[0])))) 242 | 243 | #define TYPEOF(_t) __typeof__((_t)) 244 | #define CONCAT_INNER(a, b) a ## b 245 | #define CONCAT(a, b) CONCAT_INNER(a, b) 246 | 247 | // see stackoverflow.com/questions/11761703 248 | // get number of arguments with _NARG_ 249 | #define NARG(...) _NARG_I_(__VA_ARGS__,_RSEQ_N()) 250 | #define _NARG_I_(...) _ARG_N(__VA_ARGS__) 251 | #define _ARG_N( \ 252 | _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ 253 | _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ 254 | _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ 255 | _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ 256 | _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ 257 | _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ 258 | _61,_62,_63,N,...) N 259 | #define _RSEQ_N() \ 260 | 63,62,61,60, \ 261 | 59,58,57,56,55,54,53,52,51,50, \ 262 | 49,48,47,46,45,44,43,42,41,40, \ 263 | 39,38,37,36,35,34,33,32,31,30, \ 264 | 29,28,27,26,25,24,23,22,21,20, \ 265 | 19,18,17,16,15,14,13,12,11,10, \ 266 | 9,8,7,6,5,4,3,2,1,0 267 | 268 | // general definition for any function name 269 | #define _VFUNC(name, n) CONCAT(name, n) 270 | #define VFUNC(func, ...) _VFUNC(func, NARG(__VA_ARGS__))(__VA_ARGS__) 271 | 272 | #define _NTH_ARG0(A0, ...) A0 273 | #define _NTH_ARG1(A0, A1, ...) A1 274 | #define _NTH_ARG2(A0, A1, A2, ...) A2 275 | #define _NTH_ARG3(A0, A1, A2, A3, ...) A3 276 | #define _NTH_ARG4(A0, A1, A2, A3, A4, ...) A4 277 | #define _NTH_ARG5(A0, A1, A2, A3, A4, A5, ...) A5 278 | #define _NTH_ARG6(A0, A1, A2, A3, A4, A5, A6, ...) A6 279 | #define _NTH_ARG7(A0, A1, A2, A3, A4, A5, A6, A7, ...) A7 280 | #define _NTH_ARG8(A0, A1, A2, A3, A4, A5, A6, A7, A8, ...) A8 281 | 282 | // get NTH_ARG 283 | #define _NTH_ARG(N) CONCAT(_NTH_ARG, N) 284 | #define NTH_ARG(N, ...) _NTH_ARG(NARG(__VA_ARGS__))(__VA_ARGS__) 285 | 286 | // planetmath.org/goodhashtableprimes 287 | static const uint32_t PRIMES[] = { 288 | 11, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 289 | 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 290 | 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741 291 | }; 292 | 293 | // load factors at which rehashing happens, expressed as percentages 294 | #define MAP_LOAD_HIGH 80 295 | #define MAP_LOAD_LOW 10 296 | 297 | // distance is stored as a 16-bit unsigned integer 298 | #define MAP_MAX_DISTANCE UINT16_MAX 299 | 300 | // mask for map_internal_entry_t::hash 301 | #define MAP_STORED_HASH_MASK ((1 << 15) - 1) 302 | 303 | hash_t map_hash_id(void *p, void*) { 304 | return (hash_t) (p); 305 | } 306 | 307 | // fnv1a hash 308 | hash_t map_hash_str(void *p, void*) { 309 | char *s = (char*) p; 310 | 311 | hash_t h = 0; 312 | while (*s != '\0') { 313 | h = (h ^ ((char) (*s++))) * 1099511628211u; 314 | } 315 | 316 | return h; 317 | } 318 | 319 | int map_cmp_id(void *p, void *q, void*) { 320 | return (int) (((char*) p) - ((char*) (q))); 321 | } 322 | 323 | int map_cmp_str(void *p, void *q, void*) { 324 | return strcmp((char*) p, (char*) q); 325 | } 326 | 327 | void *map_dup_str(void *s, void*) { 328 | return strdup((char*) s); 329 | } 330 | 331 | void *map_default_alloc(size_t n, void *p, void*) { 332 | return realloc(p, n); 333 | } 334 | 335 | void map_default_free(void *p, void*) { 336 | free(p); 337 | } 338 | 339 | void map_init( 340 | map_t *map, 341 | f_map_hash f_hash, 342 | f_map_alloc f_alloc, 343 | f_map_free f_free, 344 | f_map_dup f_keydup, 345 | f_map_cmp f_keycmp, 346 | f_map_free f_keyfree, 347 | f_map_free f_valfree, 348 | void *userdata) { 349 | map->f_hash = f_hash; 350 | map->f_alloc = f_alloc ? f_alloc : map_default_alloc; 351 | map->f_free = f_free ? f_free : map_default_free; 352 | map->f_keydup = f_keydup; 353 | map->f_keycmp = f_keycmp; 354 | map->f_keyfree = f_keyfree; 355 | map->f_valfree = f_valfree; 356 | map->userdata = userdata; 357 | map->used = 0; 358 | map->capacity = 0; 359 | map->prime = 0; 360 | map->entries = NULL; 361 | } 362 | 363 | void map_destroy(map_t *map) { 364 | if (!map->entries) { 365 | return; 366 | } 367 | 368 | for (size_t i = 0; i < map->capacity; i++) 369 | if (map->entries[i].used) { 370 | if (map->f_keyfree) { 371 | map->f_keyfree(map->entries[i].entry.key, map->userdata); 372 | } 373 | 374 | if (map->f_valfree) { 375 | map->f_valfree(map->entries[i].entry.value, map->userdata); 376 | } 377 | } 378 | 379 | map->f_free(map->entries, map->userdata); 380 | 381 | map->used = 0; 382 | map->capacity = 0; 383 | map->prime = 0; 384 | map->entries = NULL; 385 | } 386 | 387 | // returns true on rehash 388 | static int map_rehash(map_t *map, int force) { 389 | if (!map->entries) { 390 | return false; 391 | } 392 | 393 | size_t load = (map->used * 100) / (map->capacity); 394 | int 395 | low = load < MAP_LOAD_LOW, 396 | high = load > MAP_LOAD_HIGH; 397 | 398 | if (!force && ((!low && !high) || 399 | (low && map->prime == 0) || 400 | (high && map->prime == ARRLEN(PRIMES) - 1))) { 401 | return false; 402 | } 403 | 404 | size_t old_capacity = map->capacity; 405 | 406 | assert( 407 | force || 408 | (high && map->prime != ARRLEN(PRIMES) - 1) || 409 | (low && map->prime != 0)); 410 | 411 | if (low && map->prime != 0) { 412 | map->prime--; 413 | } else if (force || (high && map->prime != (ARRLEN(PRIMES) - 1))) { 414 | map->prime++; 415 | } 416 | 417 | assert(map->prime < ARRLEN(PRIMES)); 418 | map->capacity = PRIMES[map->prime]; 419 | 420 | map_internal_entry_t *old = map->entries; 421 | map->entries = 422 | (map_internal_entry_t*) 423 | map->f_alloc( 424 | map->capacity * sizeof(map_internal_entry_t), 425 | NULL, 426 | map->userdata); 427 | memset(map->entries, 0, map->capacity * sizeof(map_internal_entry_t)); 428 | 429 | // re-insert all entries 430 | for (size_t i = 0; i < old_capacity; i++) { 431 | if (old[i].used) { 432 | _map_insert( 433 | map, 434 | map->f_hash(old[i].entry.key, map->userdata), 435 | old[i].entry.key, 436 | old[i].entry.value, 437 | false, 438 | NULL); 439 | } 440 | } 441 | 442 | map->f_free(old, map->userdata); 443 | return true; 444 | } 445 | 446 | size_t _map_insert( 447 | map_t *map, 448 | hash_t hash, 449 | void *key, 450 | void *value, 451 | int flags, 452 | int *rehash_out) { 453 | if (!map->entries) { 454 | assert(map->used == 0); 455 | assert(map->capacity == 0); 456 | assert(map->prime == 0); 457 | map->capacity = PRIMES[map->prime]; 458 | map->entries = 459 | (map_internal_entry_t*) 460 | map->f_alloc( 461 | map->capacity * sizeof(map_internal_entry_t), 462 | NULL, 463 | map->userdata); 464 | memset( 465 | map->entries, 0, map->capacity * sizeof(map_internal_entry_t)); 466 | } 467 | 468 | int did_rehash = false; 469 | size_t pos = hash % map->capacity, dist = 0; 470 | hash_t hashbits = hash & MAP_STORED_HASH_MASK; 471 | 472 | while (true) { 473 | map_internal_entry_t *entry = &map->entries[pos]; 474 | if (!entry->used || entry->dist < dist) { 475 | map_internal_entry_t new_entry = { 476 | .entry = (map_entry_t) { .key = key, .value = value }, 477 | .dist = (uint16_t) dist, 478 | .hash = hashbits, 479 | .used = true, 480 | }; 481 | 482 | if (flags & MAP_INSERT_ENTRY_IS_NEW) { 483 | map->used++; 484 | } 485 | 486 | if ((flags & MAP_INSERT_ENTRY_IS_NEW) && map->f_keydup) { 487 | new_entry.entry.key = map->f_keydup(key, map->userdata); 488 | } 489 | 490 | if (!entry->used) { 491 | *entry = new_entry; 492 | } else { 493 | // distance is less than the one that we're trying to insert, 494 | // displace the element to reduce lookup times 495 | map_internal_entry_t e = *entry; 496 | *entry = new_entry; 497 | _map_insert( 498 | map, 499 | map->f_hash(e.entry.key, map->userdata), 500 | e.entry.key, 501 | e.entry.value, 502 | flags & ~MAP_INSERT_ENTRY_IS_NEW, 503 | &did_rehash); 504 | } 505 | 506 | break; 507 | } else if ( 508 | entry->dist == dist && 509 | entry->hash == hashbits && 510 | !map->f_keycmp(entry->entry.key, key, map->userdata)) { 511 | // entry already exists, replace value 512 | if (map->f_valfree) { 513 | map->f_valfree(entry->entry.value, map->userdata); 514 | } 515 | 516 | entry->entry.value = value; 517 | break; 518 | } 519 | 520 | // rehash if we have a greater distance than storage allows 521 | dist++; 522 | if (dist >= MAP_MAX_DISTANCE) { 523 | did_rehash = true; 524 | if (rehash_out) { *rehash_out = true; } 525 | map_rehash(map, true); 526 | return _map_insert(map, hash, key, value, flags, NULL); 527 | } 528 | 529 | // check next location 530 | pos = (pos + 1) % map->capacity; 531 | } 532 | 533 | // rehash if necessary 534 | if (map_rehash(map, false) || did_rehash) { 535 | // recompute pos as it may have moved on rehash 536 | if (rehash_out) { *rehash_out = true; } 537 | pos = _map_find(map, hash, key); 538 | } 539 | 540 | return pos; 541 | } 542 | 543 | size_t _map_find(const map_t *map, hash_t hash, void *key) { 544 | if (!map->entries) { return SIZE_MAX; } 545 | 546 | size_t pos = hash % map->capacity, res = SIZE_MAX; 547 | hash_t hashbits = hash & MAP_STORED_HASH_MASK; 548 | 549 | while (true) { 550 | if (!map->entries[pos].used) { 551 | break; 552 | } 553 | 554 | if (map->entries[pos].hash == hashbits 555 | && !map->f_keycmp(map->entries[pos].entry.key, key, map->userdata)) { 556 | res = pos; 557 | break; 558 | } 559 | 560 | pos = (pos + 1) % map->capacity; 561 | } 562 | 563 | return res; 564 | } 565 | 566 | void _map_remove_at(map_t *map, size_t pos) { 567 | assert(map->entries); 568 | 569 | size_t next_pos = 0; 570 | assert(pos < map->capacity); 571 | 572 | map_internal_entry_t *entry = &map->entries[pos]; 573 | if (entry->entry.key && map->f_keyfree) { 574 | map->f_keyfree(entry->entry.key, map->userdata); 575 | } 576 | 577 | if (entry->entry.value && map->f_valfree) { 578 | map->f_valfree(entry->entry.value, map->userdata); 579 | } 580 | 581 | map->used--; 582 | 583 | // mark empty 584 | entry->used = false; 585 | 586 | // shift further entries back by one 587 | while (true) { 588 | next_pos = (pos + 1) % map->capacity; 589 | 590 | // stop when unused entry is found or dist is 0 591 | if (!map->entries[next_pos].used 592 | || map->entries[next_pos].dist == 0) { 593 | break; 594 | } 595 | 596 | // copy next pos into current 597 | map->entries[pos] = map->entries[next_pos]; 598 | map->entries[pos].dist--; 599 | 600 | // mark next entry as empty 601 | map->entries[next_pos].used = false; 602 | 603 | pos = next_pos; 604 | } 605 | 606 | map_rehash(map, false); 607 | } 608 | 609 | void _map_remove(map_t *map, hash_t hash, void *key) { 610 | const size_t pos = _map_find(map, hash, key); 611 | assert(pos != SIZE_MAX); 612 | _map_remove_at(map, pos); 613 | assert(!map_find(map, key)); 614 | } 615 | 616 | void map_clear(map_t *map) { 617 | map_destroy(map); 618 | } 619 | #endif // ifdef UTIL_IMPL 620 | --------------------------------------------------------------------------------