OFFSETOF_VAR macro to simplify hashmap iterators

While we cannot rely on a `__typeof__' operator being portable to use with `offsetof'; we can calculate the pointer offset using an existing pointer and the address of a member using pointer arithmetic for compilers without `__typeof__'. This allows us to simplify usage of hashmap iterator macros by not having to specify a type when a pointer of that type is already given. In the future, list iterator macros (e.g. list_for_each_entry) may also be implemented using OFFSETOF_VAR to save hackers the trouble of using container_of/list_entry macros and without relying on non-portable `__typeof__'. v3: use `__typeof__' to avoid clang warnings Signed-off-by: Eric Wong <e@80x24.org> Reviewed-by: Derrick Stolee <stolee@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Eric Wong committed Oct 6, 2019 at 23:30 UTC 23dee69f53cf5024ca79e0b707dcb03c63f33bef
15 files changed +56 -45
attr.c
-1
@@ -168,7 +168,6 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
168 check->all_attrs_nr = size;
169
170 hashmap_for_each_entry(&map->map, &iter, e,
171 - struct attr_hash_entry,
171 ent /* member name */) {
172 const struct git_attr *a = e->value;
173 check->all_attrs[a->attr_nr].attr = a;
blame.c
-2
@@ -451,7 +451,6 @@ static int fingerprint_similarity(struct fingerprint *a, struct fingerprint *b)
451 const struct fingerprint_entry *entry_a, *entry_b;
452
453 hashmap_for_each_entry(&b->map, &iter, entry_b,
454 - const struct fingerprint_entry,
454 entry /* member name */) {
455 entry_a = hashmap_get_entry(&a->map, entry_b, NULL,
456 struct fingerprint_entry, entry);
@@ -474,7 +473,6 @@ static void fingerprint_subtract(struct fingerprint *a, struct fingerprint *b)
473 hashmap_iter_init(&b->map, &iter);
474
475 hashmap_for_each_entry(&b->map, &iter, entry_b,
477 - const struct fingerprint_entry,
476 entry /* member name */) {
477 entry_a = hashmap_get_entry(&a->map, entry_b, NULL,
478 struct fingerprint_entry, entry);
builtin/describe.c
+1 -1
@@ -333,7 +333,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
333 struct commit_name *n;
334
335 init_commit_names(&commit_names);
336 - hashmap_for_each_entry(&names, &iter, n, struct commit_name,
336 + hashmap_for_each_entry(&names, &iter, n,
337 entry /* member name */) {
338 c = lookup_commit_reference_gently(the_repository,
339 &n->peeled, 1);
builtin/difftool.c
+2 -2
@@ -539,7 +539,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
539 * change in the recorded SHA1 for the submodule.
540 */
541 hashmap_for_each_entry(&submodules, &iter, entry,
542 - struct pair_entry, entry /* member name */) {
542 + entry /* member name */) {
543 if (*entry->left) {
544 add_path(&ldir, ldir_len, entry->path);
545 ensure_leading_directories(ldir.buf);
@@ -558,7 +558,7 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
558 * This loop replicates that behavior.
559 */
560 hashmap_for_each_entry(&symlinks2, &iter, entry,
561 - struct pair_entry, entry /* member name */) {
561 + entry /* member name */) {
562 if (*entry->left) {
563 add_path(&ldir, ldir_len, entry->path);
564 ensure_leading_directories(ldir.buf);
config.c
-1
@@ -1943,7 +1943,6 @@ void git_configset_clear(struct config_set *cs)
1943 return;
1944
1945 hashmap_for_each_entry(&cs->config_hash, &iter, entry,
1946 - struct config_set_element,
1946 ent /* member name */) {
1947 free(entry->key);
1948 string_list_clear(&entry->value_list, 1);
diff.c
+2 -3
@@ -1038,7 +1038,7 @@ static void pmb_advance_or_null_multi_match(struct diff_options *o,
1038 int i;
1039 char *got_match = xcalloc(1, pmb_nr);
1040
1041 - hashmap_for_each_entry_from(hm, match, struct moved_entry, ent) {
1041 + hashmap_for_each_entry_from(hm, match, ent) {
1042 for (i = 0; i < pmb_nr; i++) {
1043 struct moved_entry *prev = pmb[i].match;
1044 struct moved_entry *cur = (prev && prev->next_line) ?
@@ -1193,8 +1193,7 @@ static void mark_color_as_moved(struct diff_options *o,
1193 * The current line is the start of a new block.
1194 * Setup the set of potential blocks.
1195 */
1196 - hashmap_for_each_entry_from(hm, match,
1197 - struct moved_entry, ent) {
1196 + hashmap_for_each_entry_from(hm, match, ent) {
1197 ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
1198 if (o->color_moved_ws_handling &
1199 COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
diffcore-rename.c
+1 -1
@@ -284,7 +284,7 @@ static int find_identical_files(struct hashmap *srcs,
284 */
285 p = hashmap_get_entry_from_hash(srcs, hash, NULL,
286 struct file_similarity, entry);
287 - hashmap_for_each_entry_from(srcs, p, struct file_similarity, entry) {
287 + hashmap_for_each_entry_from(srcs, p, entry) {
288 int score;
289 struct diff_filespec *source = p->filespec;
290
git-compat-util.h
+13
@@ -1337,4 +1337,17 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset)
1337 #define container_of_or_null(ptr, type, member) \
1338 (type *)container_of_or_null_offset(ptr, offsetof(type, member))
1339
1340 +/*
1341 + * like offsetof(), but takes a pointer to a a variable of type which
1342 + * contains @member, instead of a specified type.
1343 + * @ptr is subject to multiple evaluation since we can't rely on __typeof__
1344 + * everywhere.
1345 + */
1346 +#if defined(__GNUC__) /* clang sets this, too */
1347 +#define OFFSETOF_VAR(ptr, member) offsetof(__typeof__(*ptr), member)
1348 +#else /* !__GNUC__ */
1349 +#define OFFSETOF_VAR(ptr, member) \
1350 + ((uintptr_t)&(ptr)->member - (uintptr_t)(ptr))
1351 +#endif /* !__GNUC__ */
1352 +
1353 #endif
hashmap.h
+30 -14
@@ -408,16 +408,32 @@ static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,
408 return hashmap_iter_next(iter);
409 }
410
411 -#define hashmap_iter_next_entry(iter, type, member) \
412 - container_of_or_null(hashmap_iter_next(iter), type, member)
413 -
411 +/*
412 + * returns the first entry in @map using @iter, where the entry is of
413 + * @type (e.g. "struct foo") and @member is the name of the
414 + * "struct hashmap_entry" in @type
415 + */
416 #define hashmap_iter_first_entry(map, iter, type, member) \
417 container_of_or_null(hashmap_iter_first(map, iter), type, member)
418
417 -#define hashmap_for_each_entry(map, iter, var, type, member) \
418 - for (var = hashmap_iter_first_entry(map, iter, type, member); \
419 +/* internal macro for hashmap_for_each_entry */
420 +#define hashmap_iter_next_entry_offset(iter, offset) \
421 + container_of_or_null_offset(hashmap_iter_next(iter), offset)
422 +
423 +/* internal macro for hashmap_for_each_entry */
424 +#define hashmap_iter_first_entry_offset(map, iter, offset) \
425 + container_of_or_null_offset(hashmap_iter_first(map, iter), offset)
426 +
427 +/*
428 + * iterate through @map using @iter, @var is a pointer to a type
429 + * containing a @member which is a "struct hashmap_entry"
430 + */
431 +#define hashmap_for_each_entry(map, iter, var, member) \
432 + for (var = hashmap_iter_first_entry_offset(map, iter, \
433 + OFFSETOF_VAR(var, member)); \
434 var; \
420 - var = hashmap_iter_next_entry(iter, type, member))
435 + var = hashmap_iter_next_entry_offset(iter, \
436 + OFFSETOF_VAR(var, member)))
437
438 /*
439 * returns a @pointer of @type matching @keyvar, or NULL if nothing found.
@@ -432,22 +448,22 @@ static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,
448 container_of_or_null(hashmap_get_from_hash(map, hash, keydata), \
449 type, member)
450 /*
435 - * returns the next equal @type pointer to @var, or NULL if not found.
436 - * @var is a pointer of @type
437 - * @member is the name of the "struct hashmap_entry" field in @type
451 + * returns the next equal pointer to @var, or NULL if not found.
452 + * @var is a pointer of any type containing "struct hashmap_entry"
453 + * @member is the name of the "struct hashmap_entry" field
454 */
439 -#define hashmap_get_next_entry(map, var, type, member) \
440 - container_of_or_null(hashmap_get_next(map, &(var)->member), \
441 - type, member)
455 +#define hashmap_get_next_entry(map, var, member) \
456 + container_of_or_null_offset(hashmap_get_next(map, &(var)->member), \
457 + OFFSETOF_VAR(var, member))
458
459 /*
460 * iterate @map starting from @var, where @var is a pointer of @type
461 * and @member is the name of the "struct hashmap_entry" field in @type
462 */
447 -#define hashmap_for_each_entry_from(map, var, type, member) \
463 +#define hashmap_for_each_entry_from(map, var, member) \
464 for (; \
465 var; \
450 - var = hashmap_get_next_entry(map, var, type, member))
466 + var = hashmap_get_next_entry(map, var, member))
467
468 /*
469 * Disable item counting and automatic rehashing when adding/removing items.
merge-recursive.c
-5
@@ -2136,7 +2136,6 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
2136 struct string_list remove_from_merge = STRING_LIST_INIT_NODUP;
2137
2138 hashmap_for_each_entry(dir_re_head, &iter, head_ent,
2139 - struct dir_rename_entry,
2139 ent /* member name */) {
2140 merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir);
2141 if (merge_ent &&
@@ -2162,7 +2161,6 @@ static void handle_directory_level_conflicts(struct merge_options *opt,
2161 remove_hashmap_entries(dir_re_merge, &remove_from_merge);
2162
2163 hashmap_for_each_entry(dir_re_merge, &iter, merge_ent,
2165 - struct dir_rename_entry,
2164 ent /* member name */) {
2165 head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir);
2166 if (tree_has_path(opt->repo, merge, merge_ent->dir)) {
@@ -2268,7 +2266,6 @@ static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs)
2266 * that there is no winner), we no longer need possible_new_dirs.
2267 */
2268 hashmap_for_each_entry(dir_renames, &iter, entry,
2271 - struct dir_rename_entry,
2269 ent /* member name */) {
2270 int max = 0;
2271 int bad_max = 0;
@@ -2628,7 +2625,6 @@ static struct string_list *get_renames(struct merge_options *opt,
2625 }
2626
2627 hashmap_for_each_entry(&collisions, &iter, e,
2631 - struct collision_entry,
2628 ent /* member name */) {
2629 free(e->target_file);
2630 string_list_clear(&e->source_files, 0);
@@ -2847,7 +2843,6 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,
2843 struct dir_rename_entry *e;
2844
2845 hashmap_for_each_entry(dir_renames, &iter, e,
2850 - struct dir_rename_entry,
2846 ent /* member name */) {
2847 free(e->dir);
2848 strbuf_release(&e->new_dir);
name-hash.c
+1 -2
@@ -714,8 +714,7 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
714
715 ce = hashmap_get_entry_from_hash(&istate->name_hash, hash, NULL,
716 struct cache_entry, ent);
717 - hashmap_for_each_entry_from(&istate->name_hash, ce,
718 - struct cache_entry, ent) {
717 + hashmap_for_each_entry_from(&istate->name_hash, ce, ent) {
718 if (same_name(ce, name, namelen, icase))
719 return ce;
720 }
revision.c
+2 -6
@@ -129,9 +129,7 @@ static void paths_and_oids_clear(struct hashmap *map)
129 struct hashmap_iter iter;
130 struct path_and_oids_entry *entry;
131
132 - hashmap_for_each_entry(map, &iter, entry,
133 - struct path_and_oids_entry,
134 - ent /* member name */) {
132 + hashmap_for_each_entry(map, &iter, entry, ent /* member name */) {
133 oidset_clear(&entry->trees);
134 free(entry->path);
135 }
@@ -243,9 +241,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,
241 add_children_by_path(r, tree, &map);
242 }
243
246 - hashmap_for_each_entry(&map, &map_iter, entry,
247 - struct path_and_oids_entry,
248 - ent /* member name */)
244 + hashmap_for_each_entry(&map, &map_iter, entry, ent /* member name */)
245 mark_trees_uninteresting_sparse(r, &entry->trees);
246
247 paths_and_oids_clear(&map);
submodule-config.c
+1 -1
@@ -100,7 +100,7 @@ static void submodule_cache_clear(struct submodule_cache *cache)
100 * their .gitmodules blob sha1 and submodule name.
101 */
102 hashmap_for_each_entry(&cache->for_name, &iter, entry,
103 - struct submodule_entry, ent /* member name */)
103 + ent /* member name */)
104 free_one_config(entry);
105
106 hashmap_free_entries(&cache->for_path, struct submodule_entry, ent);
t/helper/test-hashmap.c
+1 -4
@@ -205,10 +205,8 @@ int cmd__hashmap(int argc, const char **argv)
205 /* print result */
206 if (!entry)
207 puts("NULL");
208 - hashmap_for_each_entry_from(&map, entry,
209 - struct test_entry, ent) {
208 + hashmap_for_each_entry_from(&map, entry, ent)
209 puts(get_value(entry));
211 - }
210
211 } else if (!strcmp("remove", cmd) && p1) {
212
@@ -230,7 +228,6 @@ int cmd__hashmap(int argc, const char **argv)
228 struct hashmap_iter iter;
229
230 hashmap_for_each_entry(&map, &iter, entry,
233 - struct test_entry,
231 ent /* member name */)
232 printf("%s %s\n", entry->key, get_value(entry));
233
t/helper/test-lazy-init-name-hash.c
+2 -2
@@ -42,11 +42,11 @@ static void dump_run(void)
42 }
43
44 hashmap_for_each_entry(&the_index.dir_hash, &iter_dir, dir,
45 - struct dir_entry, ent /* member name */)
45 + ent /* member name */)
46 printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name);
47
48 hashmap_for_each_entry(&the_index.name_hash, &iter_cache, ce,
49 - struct cache_entry, ent /* member name */)
49 + ent /* member name */)
50 printf("name %08x %s\n", ce->ent.hash, ce->name);
51
52 discard_cache();