{"thread":{"id":"54087","subject":"[PATCH 0/5] Add struct strmap and associated utility functions","startedAt":"2020-08-21T18:52:43Z","lastAt":"2020-11-11T20:35:15Z","messageCount":144,"participants":["Elijah Newren via GitGitGadget","Jeff King","Elijah Newren","Junio C Hamano","Phillip Wood","Chris Torek"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"404183","messageId":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":null,"subject":"[PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:24Z","receivedAt":"2020-08-21T18:52:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce a new strmap type, which my new merge backed, merge-ort,\nuses heavily. (I also made significant use of it in my changes to\ndiffcore-rename). This strmap type was based on Peff's proposal from a\ncouple years ago[1], but has additions that I made as I used it. I also\nstart the series off with a quick documentation improvement to hashmap.c to\ndifferentiate between hashmap_free() and hashmap_free_entries(), since I\npersonally had difficulty understanding them and it affects how\nstrmap_clear()/strmap_free() are written.\n\nThe biggest issue I know about currently concerns the convenience functions\nfor a string->integer mapping. I wanted such a mapping that didn't need to\nallocate an extra int but instead works by just type-casting the void*\npointer to an int instead. That all seems to work, but I needed a separate\nname for that type, and the problem is that I couldn't come up with a good\none as you'll see in the last patch. Suggestions for better naming are very\nmuch welcome. As are, of course, suggestions for other API or implementation\nimprovements.\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nElijah Newren (5):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: add strdup_strings option\n  strmap: add functions facilitating use as a string->int map\n\n Makefile  |   1 +\n hashmap.h |  27 +++++++++++-\n strmap.c  | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h  | 123 ++++++++++++++++++++++++++++++++++++++++++++++++++++\n 4 files changed, 275 insertions(+), 2 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: 675a4aaf3b226c0089108221b96559e0baae5de9\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v1\nPull-Request: https://github.com/git/git/pull/835\n-- \ngitgitgadget\n"},{"id":"404184","messageId":"5bda171d0c1562573800d4bcbfac4799f01d9c20.1598035949.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH 3/5] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:27Z","receivedAt":"2020-08-21T18:52:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_empty()\n  * strmap_get_size()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_free()\n  * strmap_get_item()\n\nI suspect the first four are self-explanatory.\n\nstrmap_free() differs from strmap_clear() in that the data structure is\nnot reusable after it is called; strmap_clear() is not sufficient for\nthe API because without strmap_free() we will leak memory.\n\nstrmap_get_item() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstring_list_item that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_item()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via item->util\n  * directly update item->util\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 35 ++++++++++++++++++++++++++++++-----\n strmap.h | 43 +++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 73 insertions(+), 5 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 1c9fdb3b1e..a4bfffcd8b 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -27,7 +27,7 @@ void strmap_init(struct strmap *map)\n \thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n }\n \n-void strmap_clear(struct strmap *map, int free_util)\n+void strmap_free(struct strmap *map, int free_util)\n {\n \tstruct hashmap_iter iter;\n \tstruct str_entry *e;\n@@ -35,12 +35,19 @@ void strmap_clear(struct strmap *map, int free_util)\n \tif (!map)\n \t\treturn;\n \n-\thashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {\n-\t\tfree(e->item.string);\n-\t\tif (free_util)\n-\t\t\tfree(e->item.util);\n+\tif (free_util) {\n+\t\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\t\tfree(e->item.string);\n+\t\t\tif (free_util)\n+\t\t\t\tfree(e->item.util);\n+\t\t}\n \t}\n \thashmap_free_entries(&map->map, struct str_entry, ent);\n+}\n+\n+void strmap_clear(struct strmap *map, int free_util)\n+{\n+\tstrmap_free(map, free_util);\n \tstrmap_init(map);\n }\n \n@@ -69,6 +76,13 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct string_list_item *strmap_get_item(struct strmap *map,\n+\t\t\t\t\t const char *str)\n+{\n+\tstruct str_entry *entry = find_str_entry(map, str);\n+\treturn entry ? &entry->item : NULL;\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct str_entry *entry = find_str_entry(map, str);\n@@ -79,3 +93,14 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_str_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_util)\n+{\n+\tstruct str_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.item.string = (char *)str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (ret && free_util)\n+\t\tfree(ret->item.util);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex eb5807f6fa..45d0a4f714 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -21,6 +21,11 @@ void strmap_init(struct strmap *map);\n /*\n  * Remove all entries from the map, releasing any allocated resources.\n  */\n+void strmap_free(struct strmap *map, int free_values);\n+\n+/*\n+ * Same as calling strmap_free() followed by strmap_init().\n+ */\n void strmap_clear(struct strmap *map, int free_values);\n \n /*\n@@ -32,6 +37,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the string_list_item mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct string_list_item *strmap_get_item(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -44,4 +55,36 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map) == 0;\n+}\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type str_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n+\t\t\t\t\t\t   OFFSETOF_VAR(var, ent)); \\\n+\t\tvar; \\\n+\t\tvar = hashmap_iter_next_entry_offset(iter, \\\n+\t\t\t\t\t\tOFFSETOF_VAR(var, ent)))\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"404185","messageId":"b295e9393a8998b3b9263ab7cd195907d4002e36.1598035949.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH 1/5] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:25Z","receivedAt":"2020-08-21T18:52:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  I had to consult other source code\nexamples and the implementation.  Add a brief note to clarify,\nespecially since hashmap_clear*() variants may be added in the future.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 27 +++++++++++++++++++++++++--\n 1 file changed, 25 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex ef220de4c6..a2f4adc1b3 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,36 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ * instead of\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"404186","messageId":"418975b46039f63476852a868ca6221244b5d88e.1598035949.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH 5/5] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:29Z","receivedAt":"2020-08-21T18:52:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nA note on the name: strintmap looks and sounds pretty lame to me, but\nafter trying to come up with something better and having no luck, I\nfigured I'd just go with it for a while and then at some point some\nbetter and obvious name would strike me and I could replace it.  Several\nmonths later, I still don't have a better name.  Hopefully someone else\nhas one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 +++++++++++\n strmap.h | 32 ++++++++++++++++++++++++++++++++\n 2 files changed, 43 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 03eb6af45d..cbb99f4030 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -113,3 +113,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_util)\n \t\tfree(ret->item.util);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct str_entry *entry = find_str_entry(map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->item.util;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 28a98c5a4b..5d9dd3ef58 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -88,4 +88,36 @@ static inline unsigned int strmap_get_size(struct strmap *map)\n \t\tvar = hashmap_iter_next_entry_offset(iter, \\\n \t\t\t\t\t\tOFFSETOF_VAR(var, ent)))\n \n+/*\n+ * Helper functions for using strmap as map of string -> int, using the void*\n+ * field to store the int instead of allocating an int and having the void*\n+ * member point to the allocated int.\n+ */\n+\n+static inline int strintmap_get(struct strmap *map, const char *str,\n+\t\t\t\tint default_value)\n+{\n+\tstruct string_list_item *result = strmap_get_item(map, str);\n+\tif (!result)\n+\t\treturn default_value;\n+\treturn (intptr_t)result->util;\n+}\n+\n+static inline void strintmap_set(struct strmap *map, const char *str, intptr_t v)\n+{\n+\tstrmap_put(map, str, (void *)v);\n+}\n+\n+void strintmap_incr(struct strmap *map, const char *str, intptr_t amt);\n+\n+static inline void strintmap_clear(struct strmap *map)\n+{\n+\tstrmap_clear(map, 0);\n+}\n+\n+static inline void strintmap_free(struct strmap *map)\n+{\n+\tstrmap_free(map, 0);\n+}\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n"},{"id":"404187","messageId":"a86fd5fdcc47baf85046eb07257f4db9f9498084.1598035949.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH 2/5] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:26Z","receivedAt":"2020-08-21T18:52:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nPeff only included the header, not the implementation, so it isn't clear what\nthe structure was he was going to use for the hash entries.  Instead of having\nmy str_entry struct have three subfields (the hashmap_entry, the string, and\nthe void* value), I made it only have two -- the hashmap_entry and a\nstring_list_item, for two reasons:\n\n  1) a strmap is often the data structure we want where string_list has\n     been used in the past.  Using the same building block for\n     individual entries in both makes it easier to adopt and reuse\n     parts of the string_list API in strmap.\n\n  2) In some cases, after doing lots of other work, I want to be able\n     to iterate over the items in my strmap in sorted order.  hashmap\n     obviously doesn't support that, but I wanted to be able to export\n     the strmap to a string_list easily and then use its functions.\n     (Note: I do not need the data structure to both be sorted and have\n     efficient lookup at all times.  If I did, I might use a B-tree\n     instead, as suggested by brian in response to Peff in the thread\n     noted above.  In my case, most strmaps will never need sorting, but\n     in one special case at the very end of a bunch of other work I want\n     to iterate over the items in sorted order without doing any more\n     lookups afterward.)\n\nAlso, I removed the STRMAP_INIT macro, since it cannot be used to\ncorrectly initialize a strmap; the underlying hashmap needs a call to\nhashmap_init() to allocate the hash table first.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |  1 +\n strmap.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h | 47 ++++++++++++++++++++++++++++++++\n 3 files changed, 129 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 65f8cfb236..0da15a9ee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -988,6 +988,7 @@ LIB_OBJS += strbuf.o\n LIB_OBJS += strvec.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\n LIB_OBJS += submodule.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..1c9fdb3b1e\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,81 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+static int cmp_str_entry(const void *hashmap_cmp_fn_data,\n+\t\t\t const struct hashmap_entry *entry1,\n+\t\t\t const struct hashmap_entry *entry2,\n+\t\t\t const void *keydata)\n+{\n+\tconst struct str_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct str_entry, ent);\n+\te2 = container_of(entry2, const struct str_entry, ent);\n+\treturn strcmp(e1->item.string, e2->item.string);\n+}\n+\n+static struct str_entry *find_str_entry(struct strmap *map,\n+\t\t\t\t\tconst char *str)\n+{\n+\tstruct str_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.item.string = (char *)str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n+}\n+\n+void strmap_clear(struct strmap *map, int free_util)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct str_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\thashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {\n+\t\tfree(e->item.string);\n+\t\tif (free_util)\n+\t\t\tfree(e->item.util);\n+\t}\n+\thashmap_free_entries(&map->map, struct str_entry, ent);\n+\tstrmap_init(map);\n+}\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n+ * it does not need to persist after the this function is called.\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct str_entry *entry = find_str_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->item.util;\n+\t\tentry->item.util = data;\n+\t} else {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\t\tentry->item.string = strdup(str);\n+\t\tentry->item.util = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct str_entry *entry = find_str_entry(map, str);\n+\treturn entry ? entry->item.util : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_str_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..eb5807f6fa\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,47 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+#include \"string-list.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+};\n+\n+struct str_entry {\n+\tstruct hashmap_entry ent;\n+\tstruct string_list_item item;\n+};\n+\n+/*\n+ * Initialize an empty strmap\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n+ * it does not need to persist after the this function is called.\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"404188","messageId":"b3095d97d8ee9d6576292731cc100492e7c64f13.1598035949.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH 4/5] strmap: add strdup_strings option","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-08-21T18:52:28Z","receivedAt":"2020-08-21T18:53:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nJust as it is sometimes useful for string_list to duplicate and take\nownership of memory management of the strings it contains, the same is\nsometimes true for strmaps as well.  Add the same flag from string_list\nto strmap.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 23 ++++++++++++++++-------\n strmap.h |  9 +++++----\n 2 files changed, 21 insertions(+), 11 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex a4bfffcd8b..03eb6af45d 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -22,9 +22,10 @@ static struct str_entry *find_str_entry(struct strmap *map,\n \treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n }\n \n-void strmap_init(struct strmap *map)\n+void strmap_init(struct strmap *map, int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n }\n \n void strmap_free(struct strmap *map, int free_util)\n@@ -35,9 +36,10 @@ void strmap_free(struct strmap *map, int free_util)\n \tif (!map)\n \t\treturn;\n \n-\tif (free_util) {\n+\tif (map->strdup_strings || free_util) {\n \t\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n-\t\t\tfree(e->item.string);\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree(e->item.string);\n \t\t\tif (free_util)\n \t\t\t\tfree(e->item.util);\n \t\t}\n@@ -48,12 +50,11 @@ void strmap_free(struct strmap *map, int free_util)\n void strmap_clear(struct strmap *map, int free_util)\n {\n \tstrmap_free(map, free_util);\n-\tstrmap_init(map);\n+\tstrmap_init(map, map->strdup_strings);\n }\n \n /*\n- * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n- * it does not need to persist after the this function is called.\n+ * Insert \"str\" into the map, pointing to \"data\".\n  *\n  * If an entry for \"str\" already exists, its data pointer is overwritten, and\n  * the original data pointer returned. Otherwise, returns NULL.\n@@ -69,7 +70,13 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \t} else {\n \t\tentry = xmalloc(sizeof(*entry));\n \t\thashmap_entry_init(&entry->ent, strhash(str));\n-\t\tentry->item.string = strdup(str);\n+\t\t/*\n+\t\t * We won't modify entry->item.string so it really should be\n+\t\t * const, but changing string_list_item to use a const char *\n+\t\t * is a bit too big of a change at this point.\n+\t\t */\n+\t\tentry->item.string =\n+\t\t\tmap->strdup_strings ? xstrdup(str) : (char *)str;\n \t\tentry->item.util = data;\n \t\thashmap_add(&map->map, &entry->ent);\n \t}\n@@ -100,6 +107,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_util)\n \thashmap_entry_init(&entry.ent, strhash(str));\n \tentry.item.string = (char *)str;\n \tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (map->strdup_strings)\n+\t\tfree(ret->item.string);\n \tif (ret && free_util)\n \t\tfree(ret->item.util);\n \tfree(ret);\ndiff --git a/strmap.h b/strmap.h\nindex 45d0a4f714..28a98c5a4b 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -6,6 +6,7 @@\n \n struct strmap {\n \tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n };\n \n struct str_entry {\n@@ -14,9 +15,10 @@ struct str_entry {\n };\n \n /*\n- * Initialize an empty strmap\n+ * Initialize the members of the strmap, set `strdup_strings`\n+ * member according to the value of the second parameter.\n  */\n-void strmap_init(struct strmap *map);\n+void strmap_init(struct strmap *map, int strdup_strings);\n \n /*\n  * Remove all entries from the map, releasing any allocated resources.\n@@ -29,8 +31,7 @@ void strmap_free(struct strmap *map, int free_values);\n void strmap_clear(struct strmap *map, int free_values);\n \n /*\n- * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n- * it does not need to persist after the this function is called.\n+ * Insert \"str\" into the map, pointing to \"data\".\n  *\n  * If an entry for \"str\" already exists, its data pointer is overwritten, and\n  * the original data pointer returned. Otherwise, returns NULL.\n-- \ngitgitgadget\n\n"},{"id":"404195","messageId":"20200821192224.GC1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"b295e9393a8998b3b9263ab7cd195907d4002e36.1598035949.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/5] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T19:22:24Z","receivedAt":"2020-08-21T19:22:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:25PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> The existence of hashmap_free() and hashmap_free_entries() confused me,\n> and the docs weren't clear enough.  I had to consult other source code\n> examples and the implementation.  Add a brief note to clarify,\n> especially since hashmap_clear*() variants may be added in the future.\n\nThanks, I think this is worth doing and the text looks clear and correct\nto me.\n\n>  /*\n> - * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n> + * Frees a hashmap structure and allocated memory for the table, but does not\n> + * free the entries nor anything they point to.\n> + *\n> + * Usage note:\n> + *\n> + * Many callers will need to iterate over all entries and free the data each\n> + * entry points to; in such a case, they can free the entry itself while at it.\n> + * Thus, you might see:\n> + *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n> + *      free(e->somefield);\n> + *      free(e);\n> + *    }\n> + *    hashmap_free(map);\n> + * instead of\n> + *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n> + *      free(e->somefield);\n> + *    }\n> + *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n> + * to avoid the implicit extra loop over the entries.  However, if there are\n> + * no special fields in your entry that need to be freed beyond the entry\n> + * itself, it is probably simpler to avoid the explicit loop and just call\n> + * hashmap_free_entries().\n\nA minor nit, but a blank line between the code snippets and the text\nmight make it a little more readable.\n\n-Peff\n"},{"id":"404196","messageId":"20200821194857.GD1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"a86fd5fdcc47baf85046eb07257f4db9f9498084.1598035949.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/5] strmap: new utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T19:48:57Z","receivedAt":"2020-08-21T19:49:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:26PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Add strmap as a new struct and associated utility functions,\n> specifically for hashmaps that map strings to some value.  The API is\n> taken directly from Peff's proposal at\n> https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nUh oh. You are encouraging me in the belief that I can send half-baked\nideas to the list and somebody will come along and implement them for\nme. ;)\n\n> Peff only included the header, not the implementation, so it isn't clear what\n> the structure was he was going to use for the hash entries.  Instead of having\n> my str_entry struct have three subfields (the hashmap_entry, the string, and\n> the void* value), I made it only have two -- the hashmap_entry and a\n> string_list_item, for two reasons:\n\nI'd probably have done:\n\n  struct strmap_entry {\n\tstruct hashmap_entry ent;\n\tvoid *value;\n\tchar key[FLEX_ALLOC];\n  };\n\nThat saves 8 bytes (plus malloc overhead)per item, plus avoids an extra\npointer-chase for each item we consider when looking up.\n\n>   1) a strmap is often the data structure we want where string_list has\n>      been used in the past.  Using the same building block for\n>      individual entries in both makes it easier to adopt and reuse\n>      parts of the string_list API in strmap.\n\nI can see that there might be some value in being able to interchange\nthe items for code that's expecting a string_list_item. But I have to\nwonder if the potential for confusion is worth it. I.e., should that\ncode really be expecting a raw string pointer (possibly with a separate\nvoid pointer, or even better an actual typed pointer).\n\nI'll keep an eye out as I read the rest of the series for code which\nuses this.\n\n>   2) In some cases, after doing lots of other work, I want to be able\n>      to iterate over the items in my strmap in sorted order.  hashmap\n>      obviously doesn't support that, but I wanted to be able to export\n>      the strmap to a string_list easily and then use its functions.\n>      (Note: I do not need the data structure to both be sorted and have\n>      efficient lookup at all times.  If I did, I might use a B-tree\n>      instead, as suggested by brian in response to Peff in the thread\n>      noted above.  In my case, most strmaps will never need sorting, but\n>      in one special case at the very end of a bunch of other work I want\n>      to iterate over the items in sorted order without doing any more\n>      lookups afterward.)\n\nHmm. Likewise, I'll keep an eye open for how this works in practice. I\ndo suspect that a B-tree might be a better solution here, but\nimplementing it is non-trivial, and most callers don't care about this\nproperty.\n\nIf the interim solution is to just dump it to a string_list and sort\nthat, that's really not that bad, assuming it just happens once after\nwe've added everything. I'm not sure there's that big a benefit to using\nstring_list_item internally, since presumably that conversion needs to\nwrite a whole new array of string_list_items anyway.\n\n> Also, I removed the STRMAP_INIT macro, since it cannot be used to\n> correctly initialize a strmap; the underlying hashmap needs a call to\n> hashmap_init() to allocate the hash table first.\n\nSince access to the underlying hashmap happens through strmap functions,\nthey could lazily initialize it. That's how oidmap works.\n\n> diff --git a/strmap.c b/strmap.c\n> new file mode 100644\n> index 0000000000..1c9fdb3b1e\n> --- /dev/null\n> +++ b/strmap.c\n> @@ -0,0 +1,81 @@\n> +#include \"git-compat-util.h\"\n> +#include \"strmap.h\"\n> +\n> +static int cmp_str_entry(const void *hashmap_cmp_fn_data,\n> +\t\t\t const struct hashmap_entry *entry1,\n> +\t\t\t const struct hashmap_entry *entry2,\n> +\t\t\t const void *keydata)\n> +{\n> +\tconst struct str_entry *e1, *e2;\n> +\n> +\te1 = container_of(entry1, const struct str_entry, ent);\n> +\te2 = container_of(entry2, const struct str_entry, ent);\n> +\treturn strcmp(e1->item.string, e2->item.string);\n> +}\n\nIf you do go the FLEX_ALLOC route, obviously lookups won't want to\nallocate a str_entry struct for the lookup key. You'd use keydata there\n(and prefer it over looking at entry2 at all). See remotes_hash_cmp()\nfor an example.\n\n> +static struct str_entry *find_str_entry(struct strmap *map,\n> +\t\t\t\t\tconst char *str)\n> +{\n> +\tstruct str_entry entry;\n> +\thashmap_entry_init(&entry.ent, strhash(str));\n> +\tentry.item.string = (char *)str;\n> +\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n> +}\n\nCasting away constness here is awkward. It could likewise benefit from\nusing keydata, so you wouldn't need to create a temporary\nstring_list_item (which is where the non-constness comes from).\n\n> +void strmap_clear(struct strmap *map, int free_util)\n> +{\n> +\tstruct hashmap_iter iter;\n> +\tstruct str_entry *e;\n> +\n> +\tif (!map)\n> +\t\treturn;\n\nIn a lazy-init world, this becomes:\n\n  if (!map || !map->map.table)\n\nOf course it would be better still if the hashmap code learned to do the\nlazy-init stuff itself.\n\n> +\thashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {\n> +\t\tfree(e->item.string);\n> +\t\tif (free_util)\n> +\t\t\tfree(e->item.util);\n> +\t}\n> +\thashmap_free_entries(&map->map, struct str_entry, ent);\n\nWith a flex-alloc struct, you can avoid the extra string free. But I\nguess you still wouldn't avoid the loop if you want to support\nfree_entries().\n\nI wonder if it would make the API simpler if the struct knew whether it\nowned the void pointer values or not. Then you'd do:\n\n  struct strmap foo = { .free_values = 1 };\n  ...\n  strmap_put(&foo, \"key\", value);\n  ...\n  strmap_clear(&foo);\n\nand wouldn't have to remember to do the right thing at clear-time. It is\na little less flexible (e.g., if you transfer ownership after a certain\npoint in the code), but I wonder if any callers actually need that (and\nthey could always set the free_values flag then).\n\n> +/*\n> + * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n> + * it does not need to persist after the this function is called.\n> + *\n> + * If an entry for \"str\" already exists, its data pointer is overwritten, and\n> + * the original data pointer returned. Otherwise, returns NULL.\n> + */\n> +void *strmap_put(struct strmap *map, const char *str, void *data)\n\nMinor, but IMHO we should avoid copying the docstrings to the\nimplementation, since it gives two places that people have to remember\nto update if the API changes.\n\n> +void *strmap_put(struct strmap *map, const char *str, void *data)\n> +{\n> +\tstruct str_entry *entry = find_str_entry(map, str);\n> +\tvoid *old = NULL;\n\nIn a lazy-init world, this is:\n\n  if (!map->map.table) {\n\tstrmap_init(map);\n\tentry = NULL;\n  } else {\n        entry = find_str_entry(map, str);\n  }\n\n(or just call find_str_entry() in both cases and let it realize there's\nnothing to find).\n\n> +\tif (entry) {\n> +\t\told = entry->item.util;\n> +\t\tentry->item.util = data;\n> +\t} else {\n> +\t\tentry = xmalloc(sizeof(*entry));\n> +\t\thashmap_entry_init(&entry->ent, strhash(str));\n> +\t\tentry->item.string = strdup(str);\n> +\t\tentry->item.util = data;\n> +\t\thashmap_add(&map->map, &entry->ent);\n> +\t}\n\nAnd in a flex-alloc world, this second block is:\n\n  FLEX_ALLOC_STR(entry, key, str);\n  hashmap_entry_init(&entry->ent, strhash(str));\n  entry->value = data;\n  hashmap_add(&map->map, &entry->ent);\n\n> +void *strmap_get(struct strmap *map, const char *str)\n> +{\n> +\tstruct str_entry *entry = find_str_entry(map, str);\n> +\treturn entry ? entry->item.util : NULL;\n> +}\n\nIn a lazy world, this is:\n\n  if (!map->map.table)\n          return NULL;\n\n> +int strmap_contains(struct strmap *map, const char *str)\n> +{\n> +\treturn find_str_entry(map, str) != NULL;\n> +}\n\nAnd likewise:\n\n  if (!map->map.table)\n          return NULL;\n\nIt might actually be easier to stick that in find_str_entry().\n\nThe rest of it all looked good to me.\n\n-Peff\n"},{"id":"404199","messageId":"20200821195822.GE1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"5bda171d0c1562573800d4bcbfac4799f01d9c20.1598035949.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/5] strmap: add more utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T19:58:22Z","receivedAt":"2020-08-21T19:58:37Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:27PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> This adds a number of additional convienence functions I want/need:\n>   * strmap_empty()\n>   * strmap_get_size()\n>   * strmap_remove()\n>   * strmap_for_each_entry()\n>   * strmap_free()\n>   * strmap_get_item()\n> \n> I suspect the first four are self-explanatory.\n\nYup, all make sense. We might also want real iterators rather than\nstrmap_for_each_entry(), which can be a bit more convenient given the\nlack of lambdas in C. But I'd be happy to wait until a caller arises.\n\n> strmap_free() differs from strmap_clear() in that the data structure is\n> not reusable after it is called; strmap_clear() is not sufficient for\n> the API because without strmap_free() we will leak memory.\n\nHmm, I missed in the previous function that strmap_clear() is actually\nleaving allocated memory. I think this is bad, because it's unlike most\nof our other data structure clear() functions.\n\nWe could work around it with the lazy-init stuff I mentioned in my last\nemail (i.e., _don't_ strmap_init() at the end of strmap_clear(), and\njust let strmap_put() take care of initializing if somebody actually\nadds something again).\n\nBut IMHO this is a sign that we should be fixing hashmap() to work like\nthat, too.\n\n> strmap_get_item() is similar to strmap_get() except that instead of just\n> returning the void* value that the string maps to, it returns the\n> string_list_item that contains both the string and the void* value (or\n> NULL if the string isn't in the map).  This is helpful because it avoids\n> multiple lookups, e.g. in some cases a caller would need to call:\n>   * strmap_contains() to check that the map has an entry for the string\n>   * strmap_get() to get the void* value\n>   * <do some work to update the value>\n>   * strmap_put() to update/overwrite the value\n\nThat makes sense. If you follow my suggestion to drop string_list_item,\nthen it would be OK to return the whole str_entry. (I forgot to mention\nin the last patch, but perhaps strmap_entry would be a more distinctive\nname).\n\n> diff --git a/strmap.h b/strmap.h\n> index eb5807f6fa..45d0a4f714 100644\n> --- a/strmap.h\n> +++ b/strmap.h\n> @@ -21,6 +21,11 @@ void strmap_init(struct strmap *map);\n>  /*\n>   * Remove all entries from the map, releasing any allocated resources.\n>   */\n> +void strmap_free(struct strmap *map, int free_values);\n> +\n> +/*\n> + * Same as calling strmap_free() followed by strmap_init().\n> + */\n>  void strmap_clear(struct strmap *map, int free_values);\n\nI guess the docstring was a bit inaccurate in the previous patch, then. :)\n\n-Peff\n"},{"id":"404200","messageId":"20200821200121.GF1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"b3095d97d8ee9d6576292731cc100492e7c64f13.1598035949.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T20:01:21Z","receivedAt":"2020-08-21T20:01:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:28PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Just as it is sometimes useful for string_list to duplicate and take\n> ownership of memory management of the strings it contains, the same is\n> sometimes true for strmaps as well.  Add the same flag from string_list\n> to strmap.\n\nThis is actually one of the ugliest parts of string_list, IMHO, and I'd\nprefer if we can avoid duplicating it. Yes, sometimes we can manage to\navoid an extra copy of a string. But the resulting ownership and\nlifetime questions are often very error-prone. In other data structures\nwe've moved towards just having the structure own its data (e.g.,\nstrvec does so, and things like oidmap store their own oids). I've been\nhappy with the simplicity of it.\n\nIt also works if you use a flex-array for the key storage in the\nstrmap_entry. :)\n\n-Peff\n"},{"id":"404201","messageId":"20200821201034.GG1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"418975b46039f63476852a868ca6221244b5d88e.1598035949.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 5/5] strmap: add functions facilitating use as a string->int map","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T20:10:34Z","receivedAt":"2020-08-21T20:10:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:29PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Although strmap could be used as a string->int map, one either had to\n> allocate an int for every entry and then deallocate later, or one had to\n> do a bunch of casting between (void*) and (intptr_t).\n> \n> Add some special functions that do the casting.  Also, rename put->set\n> for such wrapper functions since 'put' implied there may be some\n> deallocation needed if the string was already found in the map, which\n> isn't the case when we're storing an int value directly in the void*\n> slot instead of using the void* slot as a pointer to data.\n\nI think wrapping this kind of hackery is worth doing.\n\nYou'd be able to use put() as usual, wouldn't you? It never deallocates\nthe util field, but just returns the old one. And the caller knows that\nit's really an int, and shouldn't be deallocated.\n\n> A note on the name: strintmap looks and sounds pretty lame to me, but\n> after trying to come up with something better and having no luck, I\n> figured I'd just go with it for a while and then at some point some\n> better and obvious name would strike me and I could replace it.  Several\n> months later, I still don't have a better name.  Hopefully someone else\n> has one.\n\nstrnummap? That's pretty bad, too.\n\nSince the functions all take a raw strmap, you _could_ just do\n\"strmap_getint()\", etc. But I think you could actually get some\nadditional safety by defining a wrapper type:\n\n  struct strintmap {\n          struct strmap strmap;\n  };\n\nIt's a bit annoying because you a bunch of pass-through boilerplate for\nstuff like:\n\n  static inline int strintmap_empty(struct strintmap *map)\n  {\n          return strmap_empty(&map->map);\n  }\n\nbut it would prevent mistakes like:\n\n  strintmap_incr(&map, \"foo\", 10);\n  strmap_clear(&map, 1);\n\nwhich would try to free (void *)10.  I'm not sure if that's worth it or\nnot. You'd almost have to be trying to fail to pass \"1\" for free_util\nthere. But I've seen stranger things. :)\n\n-Peff\n"},{"id":"404202","messageId":"20200821201648.GH1165@coredump.intra.peff.net","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T20:16:48Z","receivedAt":"2020-08-21T20:16:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 06:52:24PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Here I introduce a new strmap type, which my new merge backed, merge-ort,\n> uses heavily. (I also made significant use of it in my changes to\n> diffcore-rename). This strmap type was based on Peff's proposal from a\n> couple years ago[1], but has additions that I made as I used it. I also\n> start the series off with a quick documentation improvement to hashmap.c to\n> differentiate between hashmap_free() and hashmap_free_entries(), since I\n> personally had difficulty understanding them and it affects how\n> strmap_clear()/strmap_free() are written.\n\nI like the direction overall (unsurprisingly), but left a bunch of\ncomments. I do think if we're going to do this that it may be worth\ncleaning up hashmap a bit first, especially around its clear/free\nsemantics, and its ability to lazy-allocate the table.\n\nI'm happy to work on that, but don't want to step on your toes.\n\nI also wonder if you looked at the khash stuff at all. Especially for\nstoring integers, it makes things much more natural. You'd do something\nlike:\n\n  /* you might even be able to just write !strcmp in the macro below */\n  static inline int streq(const char *a, const char *b)\n  {\n          return !strcmp(a, b);\n  }\n\n  KHASH_INIT(strint_map, char *, int, 1, strhash, streq);\n\nand then you'd probably want a \"put\" wrapper that makes a copy of the\nstring. khash has its own charming awkwardness, but I'm just curious if you\nlooked at it and found it more awkward than hashmap.c, or if you just\ndidn't look at it.\n\n-Peff\n"},{"id":"404204","messageId":"CABPp-BGJK4C7U+-MB-+TUgFAuuFJKE-E7y17FFJDRTyFydVX2A@mail.gmail.com","threadId":"54087","inReplyTo":"20200821200121.GF1165@coredump.intra.peff.net","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-21T20:41:44Z","receivedAt":"2020-08-21T20:42:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 21, 2020 at 1:01 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 06:52:28PM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Just as it is sometimes useful for string_list to duplicate and take\n> > ownership of memory management of the strings it contains, the same is\n> > sometimes true for strmaps as well.  Add the same flag from string_list\n> > to strmap.\n>\n> This is actually one of the ugliest parts of string_list, IMHO, and I'd\n> prefer if we can avoid duplicating it. Yes, sometimes we can manage to\n> avoid an extra copy of a string. But the resulting ownership and\n> lifetime questions are often very error-prone. In other data structures\n> we've moved towards just having the structure own its data (e.g.,\n> strvec does so, and things like oidmap store their own oids). I've been\n> happy with the simplicity of it.\n>\n> It also works if you use a flex-array for the key storage in the\n> strmap_entry. :)\n\nI can see how it's easier, but that worries me about the number of\nextra copies for my usecase.  In order to minimize actual computation,\nI track an awful lot of auxiliary data in merge-ort so that I know\nwhen I can safely perform many different case-specific optimizations.\nAmong other things, this means 15 strmaps.  1 of those stores a\nmapping from all paths that traverse_trees() walks over (file or\ndirectory) to metadata about the content on the three different sides.\n9 of the remaining 14 simply share the strings in the main strmap,\nbecause I don't need extra copies of the paths in the repository.  I\ncould (and maybe should) extend that to 11 of the 14.  Only 3 actually\ndo need to store a copy of the paths (because they store data used\nbeyond the end of an inner recursive merge or can be used to\naccelerate subsequent commits in a rebase or cherry-pick sequence).\n\nSo, in most my cases, I don't want to duplicate strings.  I actually\nstarted my implementation using FLEX_ALLOC_STR(), as you suggested\nearlier in this thread, but tossed it because of this same desire to\nnot duplicate strings but just share them between the strmaps.\n\nGranted, I made that decision before I had a complete implementation,\nso I didn't measure the actual costs.  It's possible that was a\npremature optimization.\n"},{"id":"404205","messageId":"CABPp-BFi7JSkkVSLkS9Kb5fMJ3xM3CjM7_ZsvJZMv=XgQgb6Lw@mail.gmail.com","threadId":"54087","inReplyTo":"20200821201034.GG1165@coredump.intra.peff.net","subject":"Re: [PATCH 5/5] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-21T20:51:57Z","receivedAt":"2020-08-21T20:52:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 21, 2020 at 1:10 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 06:52:29PM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Although strmap could be used as a string->int map, one either had to\n> > allocate an int for every entry and then deallocate later, or one had to\n> > do a bunch of casting between (void*) and (intptr_t).\n> >\n> > Add some special functions that do the casting.  Also, rename put->set\n> > for such wrapper functions since 'put' implied there may be some\n> > deallocation needed if the string was already found in the map, which\n> > isn't the case when we're storing an int value directly in the void*\n> > slot instead of using the void* slot as a pointer to data.\n>\n> I think wrapping this kind of hackery is worth doing.\n>\n> You'd be able to use put() as usual, wouldn't you? It never deallocates\n> the util field, but just returns the old one. And the caller knows that\n> it's really an int, and shouldn't be deallocated.\n\nYou can use put() as normal, if you don't mind the need to explicitly\nthrow in a typecast when you use it.  In fact, strintmap_set() does no\nmore than typecasting the int to void* and otherwise calling\nstrmap_put().\n\nI initially called that strintmap_put(), but got confused once or\ntwice and looked up the function definition to make sure there wasn't\nsome deallocation I needed to handle.  After that, I decided to just\nrename to _set() because I thought it'd reduce the chance of myself or\nothers wondering about that in the future.\n\n>\n> > A note on the name: strintmap looks and sounds pretty lame to me, but\n> > after trying to come up with something better and having no luck, I\n> > figured I'd just go with it for a while and then at some point some\n> > better and obvious name would strike me and I could replace it.  Several\n> > months later, I still don't have a better name.  Hopefully someone else\n> > has one.\n>\n> strnummap? That's pretty bad, too.\n>\n> Since the functions all take a raw strmap, you _could_ just do\n> \"strmap_getint()\", etc. But I think you could actually get some\n> additional safety by defining a wrapper type:\n>\n>   struct strintmap {\n>           struct strmap strmap;\n>   };\n>\n> It's a bit annoying because you a bunch of pass-through boilerplate for\n> stuff like:\n>\n>   static inline int strintmap_empty(struct strintmap *map)\n>   {\n>           return strmap_empty(&map->map);\n>   }\n>\n> but it would prevent mistakes like:\n>\n>   strintmap_incr(&map, \"foo\", 10);\n>   strmap_clear(&map, 1);\n>\n> which would try to free (void *)10.  I'm not sure if that's worth it or\n> not. You'd almost have to be trying to fail to pass \"1\" for free_util\n> there. But I've seen stranger things. :)\n\nI like this idea and the extra safety it provides.  Most of strintmap\nis static inline functions anyway, adding a few more wouldn't hurt.\n"},{"id":"404207","messageId":"20200821210301.GA11806@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BGJK4C7U+-MB-+TUgFAuuFJKE-E7y17FFJDRTyFydVX2A@mail.gmail.com","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T21:03:01Z","receivedAt":"2020-08-21T21:03:06Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 01:41:44PM -0700, Elijah Newren wrote:\n\n> > This is actually one of the ugliest parts of string_list, IMHO, and I'd\n> > prefer if we can avoid duplicating it. Yes, sometimes we can manage to\n> > avoid an extra copy of a string. But the resulting ownership and\n> > lifetime questions are often very error-prone. In other data structures\n> > we've moved towards just having the structure own its data (e.g.,\n> > strvec does so, and things like oidmap store their own oids). I've been\n> > happy with the simplicity of it.\n> >\n> > It also works if you use a flex-array for the key storage in the\n> > strmap_entry. :)\n> \n> I can see how it's easier, but that worries me about the number of\n> extra copies for my usecase.  In order to minimize actual computation,\n> I track an awful lot of auxiliary data in merge-ort so that I know\n> when I can safely perform many different case-specific optimizations.\n> Among other things, this means 15 strmaps.  1 of those stores a\n> mapping from all paths that traverse_trees() walks over (file or\n> directory) to metadata about the content on the three different sides.\n> 9 of the remaining 14 simply share the strings in the main strmap,\n> because I don't need extra copies of the paths in the repository.  I\n> could (and maybe should) extend that to 11 of the 14.  Only 3 actually\n> do need to store a copy of the paths (because they store data used\n> beyond the end of an inner recursive merge or can be used to\n> accelerate subsequent commits in a rebase or cherry-pick sequence).\n\nI'd have to see the code, of course, but:\n\n  - keep in mind you're allocating 8 bytes for a pointer (plus 24 for\n    the rest of the strmap entry). If you use a flex-array you get those\n    8 bytes back. Full paths do tend to be longer than that, so it's\n    probably net worse than a pointer to an existing string. But how\n    much worse, and does it matter?\n\n  - That sounds like a lot of maps. :) I guess you've looked at\n    compacting some of them into a single map-to-struct?\n\n> So, in most my cases, I don't want to duplicate strings.  I actually\n> started my implementation using FLEX_ALLOC_STR(), as you suggested\n> earlier in this thread, but tossed it because of this same desire to\n> not duplicate strings but just share them between the strmaps.\n> \n> Granted, I made that decision before I had a complete implementation,\n> so I didn't measure the actual costs.  It's possible that was a\n> premature optimization.\n\nI'm just really concerned that it poisons the data structure with\ncomplexity that many of the other callers will have to deal with. We've\nhad several \"oops, strdup_strings wasn't what I expected it to be\" bugs\nwith string-list (in both directions: leaks and use-after-free). It\nwould be nice to have actual numbers and see if it's worth the cost.\n\n-Peff\n"},{"id":"404208","messageId":"20200821210505.GB11806@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BFi7JSkkVSLkS9Kb5fMJ3xM3CjM7_ZsvJZMv=XgQgb6Lw@mail.gmail.com","subject":"Re: [PATCH 5/5] strmap: add functions facilitating use as a string->int map","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-21T21:05:05Z","receivedAt":"2020-08-21T21:05:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 01:51:57PM -0700, Elijah Newren wrote:\n\n> > I think wrapping this kind of hackery is worth doing.\n> >\n> > You'd be able to use put() as usual, wouldn't you? It never deallocates\n> > the util field, but just returns the old one. And the caller knows that\n> > it's really an int, and shouldn't be deallocated.\n> \n> You can use put() as normal, if you don't mind the need to explicitly\n> throw in a typecast when you use it.  In fact, strintmap_set() does no\n> more than typecasting the int to void* and otherwise calling\n> strmap_put().\n\nYeah, I think hiding the type-casting is worth it alone. I was just\nconfused by your remark.\n\n> I initially called that strintmap_put(), but got confused once or\n> twice and looked up the function definition to make sure there wasn't\n> some deallocation I needed to handle.  After that, I decided to just\n> rename to _set() because I thought it'd reduce the chance of myself or\n> others wondering about that in the future.\n\nYeah, I'd agree that is a much better name. Since there's an \"incr\",\nhaving a specific \"set\" makes it clear that we're overwriting.\n\n> >   struct strintmap {\n> >           struct strmap strmap;\n> >   };\n> [...]\n> I like this idea and the extra safety it provides.  Most of strintmap\n> is static inline functions anyway, adding a few more wouldn't hurt.\n\nOK. Then I guess we can't cheat our way out of picking a name with\nstrmap_getint(). :)\n\n-Peff\n"},{"id":"404219","messageId":"CABPp-BGYiphp-93Bf=2z-ZLd-Y=buTA0BCp6zuTJF39n1x3Rfw@mail.gmail.com","threadId":"54087","inReplyTo":"20200821201648.GH1165@coredump.intra.peff.net","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-21T21:33:54Z","receivedAt":"2020-08-21T21:34:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 21, 2020 at 1:16 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 06:52:24PM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > Here I introduce a new strmap type, which my new merge backed, merge-ort,\n> > uses heavily. (I also made significant use of it in my changes to\n> > diffcore-rename). This strmap type was based on Peff's proposal from a\n> > couple years ago[1], but has additions that I made as I used it. I also\n> > start the series off with a quick documentation improvement to hashmap.c to\n> > differentiate between hashmap_free() and hashmap_free_entries(), since I\n> > personally had difficulty understanding them and it affects how\n> > strmap_clear()/strmap_free() are written.\n>\n> I like the direction overall (unsurprisingly), but left a bunch of\n> comments. I do think if we're going to do this that it may be worth\n> cleaning up hashmap a bit first, especially around its clear/free\n> semantics, and its ability to lazy-allocate the table.\n>\n> I'm happy to work on that, but don't want to step on your toes.\n\nI have patches which introduce hashmap_clear() and\nhashmap_clear_entries() to hashmap.[ch], which allowed me to simplify\nstrmap_clear(); instead of needing to call both\nhashmap_free[_entries]() && strmap_init(), I could just call\nhashmap_clear[_entries]().  Doing that surprised me with a significant\nperformance impact (in a good direction), at which point I started\nadding mem-pool integration into hashmap for storing the entries that\nhashmap.c allocates and got further good speedups.\n\nI thought those were better explained when I got to the performance\nstuff, so I had held off on those patches.  I could pull them out and\nsubmit them first.\n\nHowever, there's an important difference here between what I've done\nand what you've suggested for hashmap: my method did not deallocate\nhashmap->table in hashmap_clear() and then use lazy initialization.\nIn fact, I think not deallocating the table was part of the charm --\nthe table had already naturally grown to the right size, and because\nthe repository has approximately the same number of paths in various\ncommits, this provided me a way of getting a table preallocated to a\nreasonable size for all merges after the first (and there are multiple\nmerges either when recursiveness is needed due to multiple merge\nbases, OR when rebasing or cherry-picking a sequence of commits).\nThis prevented, as hashmap.h puts it, \"expensive resizing\".\n\nSo, once again, my performance ideas might be clashing with some of\nyour desires for the API.  Any clever ideas for resolving that?\n\nAlso, since you want to see hashmap cleanup first, should I submit\njust the hashmap_clear[_entries()] stuff, or should I also submit the\nAPI additions to allow mem-pool integration in hashmap (it's pretty\nsmall and self-contained, but it'll be a while before I submit the\npatches that use it...)?\n\n> I also wonder if you looked at the khash stuff at all. Especially for\n> storing integers, it makes things much more natural. You'd do something\n> like:\n>\n>   /* you might even be able to just write !strcmp in the macro below */\n>   static inline int streq(const char *a, const char *b)\n>   {\n>           return !strcmp(a, b);\n>   }\n>\n>   KHASH_INIT(strint_map, char *, int, 1, strhash, streq);\n>\n> and then you'd probably want a \"put\" wrapper that makes a copy of the\n> string. khash has its own charming awkwardness, but I'm just curious if you\n> looked at it and found it more awkward than hashmap.c, or if you just\n> didn't look at it.\n\nI did look at it, but only briefly.  I had a further investigation on\nmy TODO list for months, along with several other improvement ideas.\nBut it seemed like my TODO list was really long, and my new merge\nbackend hasn't benefited anyone yet.  At some point, I decided to punt\non it and other ideas and start cleaning up my code and submitting.  I\nbelieve merge-ort is more accurate than merge-recursive (it fixes\nseveral test_expect_failures) and is a lot faster as well for the\ncases I'm looking at.  So, for now, I've pulled it off my radar.\n\nBut I'd be really happy if someone else wanted to jump in and try\nswitching out hashmap for khash in the strmap API and see if it helps\nmerge-ort performance.  :-)\n"},{"id":"404241","messageId":"CABPp-BE8tdpjx2RBGyZOYV4hsfjm5HF_dmehvX792x7TtWkLcA@mail.gmail.com","threadId":"54087","inReplyTo":"20200821210301.GA11806@coredump.intra.peff.net","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-21T22:25:44Z","receivedAt":"2020-08-21T22:26:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 21, 2020 at 2:03 PM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 01:41:44PM -0700, Elijah Newren wrote:\n>\n> > > This is actually one of the ugliest parts of string_list, IMHO, and I'd\n> > > prefer if we can avoid duplicating it. Yes, sometimes we can manage to\n> > > avoid an extra copy of a string. But the resulting ownership and\n> > > lifetime questions are often very error-prone. In other data structures\n> > > we've moved towards just having the structure own its data (e.g.,\n> > > strvec does so, and things like oidmap store their own oids). I've been\n> > > happy with the simplicity of it.\n> > >\n> > > It also works if you use a flex-array for the key storage in the\n> > > strmap_entry. :)\n> >\n> > I can see how it's easier, but that worries me about the number of\n> > extra copies for my usecase.  In order to minimize actual computation,\n> > I track an awful lot of auxiliary data in merge-ort so that I know\n> > when I can safely perform many different case-specific optimizations.\n> > Among other things, this means 15 strmaps.  1 of those stores a\n> > mapping from all paths that traverse_trees() walks over (file or\n> > directory) to metadata about the content on the three different sides.\n> > 9 of the remaining 14 simply share the strings in the main strmap,\n> > because I don't need extra copies of the paths in the repository.  I\n> > could (and maybe should) extend that to 11 of the 14.  Only 3 actually\n> > do need to store a copy of the paths (because they store data used\n> > beyond the end of an inner recursive merge or can be used to\n> > accelerate subsequent commits in a rebase or cherry-pick sequence).\n>\n> I'd have to see the code, of course, but:\n\n>   - keep in mind you're allocating 8 bytes for a pointer (plus 24 for\n>     the rest of the strmap entry). If you use a flex-array you get those\n>     8 bytes back. Full paths do tend to be longer than that, so it's\n>     probably net worse than a pointer to an existing string. But how\n>     much worse, and does it matter?\n\nI'll investigate; it may take a while...\n\n>   - That sounds like a lot of maps. :) I guess you've looked at\n>     compacting some of them into a single map-to-struct?\n\nOh, map-to-struct is the primary use.  But compacting them won't work,\nbecause the reason for the additional maps is that they have different\nsets of keys (this set of paths meet a certain condition...).  Only\none map contains all the paths involved in the merge.\n\nAlso, several of those maps don't even store a value; and are really\njust a set implemented via strmap (thus meaning the only bit of data I\nneed for some conditions is whether any given path meets it).  It\nseems slightly ugly to have to call strmap_put(map, string, NULL) for\nthose.  I wonder if I should have another strset type much like your\nsuggesting for strintmap.  Hmm...\n\nAlso, one thing that inflates the number of strmaps I use is that\nseveral of those conditions are specific to a certain side of the\nmerge, thus requiring two strmaps for each of those special\nconditions.\n\n> > So, in most my cases, I don't want to duplicate strings.  I actually\n> > started my implementation using FLEX_ALLOC_STR(), as you suggested\n> > earlier in this thread, but tossed it because of this same desire to\n> > not duplicate strings but just share them between the strmaps.\n> >\n> > Granted, I made that decision before I had a complete implementation,\n> > so I didn't measure the actual costs.  It's possible that was a\n> > premature optimization.\n>\n> I'm just really concerned that it poisons the data structure with\n> complexity that many of the other callers will have to deal with. We've\n> had several \"oops, strdup_strings wasn't what I expected it to be\" bugs\n> with string-list (in both directions: leaks and use-after-free). It\n> would be nice to have actual numbers and see if it's worth the cost.\n\nI'll go get some and find out what the impact is.\n"},{"id":"404242","messageId":"CABPp-BFFOKcBsPEu+zueCKwPGMh9RgOE8QJonKxkNNE3-Ym7Gw@mail.gmail.com","threadId":"54087","inReplyTo":"CABPp-BGYiphp-93Bf=2z-ZLd-Y=buTA0BCp6zuTJF39n1x3Rfw@mail.gmail.com","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-21T22:28:07Z","receivedAt":"2020-08-21T22:28:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 21, 2020 at 2:33 PM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Fri, Aug 21, 2020 at 1:16 PM Jeff King <peff@peff.net> wrote:\n> >\n> > On Fri, Aug 21, 2020 at 06:52:24PM +0000, Elijah Newren via GitGitGadget wrote:\n> >\n> > > Here I introduce a new strmap type, which my new merge backed, merge-ort,\n> > > uses heavily. (I also made significant use of it in my changes to\n> > > diffcore-rename). This strmap type was based on Peff's proposal from a\n> > > couple years ago[1], but has additions that I made as I used it. I also\n> > > start the series off with a quick documentation improvement to hashmap.c to\n> > > differentiate between hashmap_free() and hashmap_free_entries(), since I\n> > > personally had difficulty understanding them and it affects how\n> > > strmap_clear()/strmap_free() are written.\n> >\n> > I like the direction overall (unsurprisingly), but left a bunch of\n> > comments. I do think if we're going to do this that it may be worth\n> > cleaning up hashmap a bit first, especially around its clear/free\n> > semantics, and its ability to lazy-allocate the table.\n> >\n> > I'm happy to work on that, but don't want to step on your toes.\n>\n> I have patches which introduce hashmap_clear() and\n> hashmap_clear_entries() to hashmap.[ch], which allowed me to simplify\n> strmap_clear(); instead of needing to call both\n> hashmap_free[_entries]() && strmap_init(), I could just call\n> hashmap_clear[_entries]().  Doing that surprised me with a significant\n> performance impact (in a good direction), at which point I started\n> adding mem-pool integration into hashmap for storing the entries that\n> hashmap.c allocates and got further good speedups.\n>\n> I thought those were better explained when I got to the performance\n> stuff, so I had held off on those patches.  I could pull them out and\n> submit them first.\n>\n> However, there's an important difference here between what I've done\n> and what you've suggested for hashmap: my method did not deallocate\n> hashmap->table in hashmap_clear() and then use lazy initialization.\n> In fact, I think not deallocating the table was part of the charm --\n> the table had already naturally grown to the right size, and because\n> the repository has approximately the same number of paths in various\n> commits, this provided me a way of getting a table preallocated to a\n> reasonable size for all merges after the first (and there are multiple\n> merges either when recursiveness is needed due to multiple merge\n> bases, OR when rebasing or cherry-picking a sequence of commits).\n> This prevented, as hashmap.h puts it, \"expensive resizing\".\n>\n> So, once again, my performance ideas might be clashing with some of\n> your desires for the API.  Any clever ideas for resolving that?\n>\n> Also, since you want to see hashmap cleanup first, should I submit\n> just the hashmap_clear[_entries()] stuff, or should I also submit the\n> API additions to allow mem-pool integration in hashmap (it's pretty\n> small and self-contained, but it'll be a while before I submit the\n> patches that use it...)?\n\nNevermind, I mis-remembered.  The mempool integration was added\nspecifically to strmap, not to hashmap, because strmap_put() does the\nallocation of the str_entry.  So I'll just pull out the\nhashmap_clear[_entries]() stuff and send it up.\n\n>\n> > I also wonder if you looked at the khash stuff at all. Especially for\n> > storing integers, it makes things much more natural. You'd do something\n> > like:\n> >\n> >   /* you might even be able to just write !strcmp in the macro below */\n> >   static inline int streq(const char *a, const char *b)\n> >   {\n> >           return !strcmp(a, b);\n> >   }\n> >\n> >   KHASH_INIT(strint_map, char *, int, 1, strhash, streq);\n> >\n> > and then you'd probably want a \"put\" wrapper that makes a copy of the\n> > string. khash has its own charming awkwardness, but I'm just curious if you\n> > looked at it and found it more awkward than hashmap.c, or if you just\n> > didn't look at it.\n>\n> I did look at it, but only briefly.  I had a further investigation on\n> my TODO list for months, along with several other improvement ideas.\n> But it seemed like my TODO list was really long, and my new merge\n> backend hasn't benefited anyone yet.  At some point, I decided to punt\n> on it and other ideas and start cleaning up my code and submitting.  I\n> believe merge-ort is more accurate than merge-recursive (it fixes\n> several test_expect_failures) and is a lot faster as well for the\n> cases I'm looking at.  So, for now, I've pulled it off my radar.\n>\n> But I'd be really happy if someone else wanted to jump in and try\n> switching out hashmap for khash in the strmap API and see if it helps\n> merge-ort performance.  :-)\n"},{"id":"404676","messageId":"20200828070335.GB2105050@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BGYiphp-93Bf=2z-ZLd-Y=buTA0BCp6zuTJF39n1x3Rfw@mail.gmail.com","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-28T07:03:35Z","receivedAt":"2020-08-28T07:03:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 02:33:54PM -0700, Elijah Newren wrote:\n\n> However, there's an important difference here between what I've done\n> and what you've suggested for hashmap: my method did not deallocate\n> hashmap->table in hashmap_clear() and then use lazy initialization.\n> In fact, I think not deallocating the table was part of the charm --\n> the table had already naturally grown to the right size, and because\n> the repository has approximately the same number of paths in various\n> commits, this provided me a way of getting a table preallocated to a\n> reasonable size for all merges after the first (and there are multiple\n> merges either when recursiveness is needed due to multiple merge\n> bases, OR when rebasing or cherry-picking a sequence of commits).\n> This prevented, as hashmap.h puts it, \"expensive resizing\".\n> \n> So, once again, my performance ideas might be clashing with some of\n> your desires for the API.  Any clever ideas for resolving that?\n\nIf the magic is in pre-sizing the hash, then it seems like the callers\nought to be feeding the size hint. That does make a little more work for\nthem, but I think there's real value in having consistent semantics for\n\"clear\" across our data structures.\n\nHowever, one cheat would be to free the memory but retain the size hint\nafter a clear. And then if we lazy-init, grow immediately to the hint\nsize. That's more expensive than a true reuse, because we do reallocate\nthe memory. But it avoids the repeated re-allocation during growth.\n\nIt may also be a sign that we should be growing the hash more\naggressively in the first place. Of course all of this is predicated\nhaving some benchmarks. It would be useful to know which part actually\nprovided the speedup.\n\n-Peff\n"},{"id":"404677","messageId":"20200828070802.GC2105050@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BE8tdpjx2RBGyZOYV4hsfjm5HF_dmehvX792x7TtWkLcA@mail.gmail.com","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-08-28T07:08:02Z","receivedAt":"2020-08-28T07:08:05Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 21, 2020 at 03:25:44PM -0700, Elijah Newren wrote:\n\n> >   - That sounds like a lot of maps. :) I guess you've looked at\n> >     compacting some of them into a single map-to-struct?\n> \n> Oh, map-to-struct is the primary use.  But compacting them won't work,\n> because the reason for the additional maps is that they have different\n> sets of keys (this set of paths meet a certain condition...).  Only\n> one map contains all the paths involved in the merge.\n\nOK, I guess I'm not surprised that you would not have missed such an\nobvious optimization. :)\n\n> Also, several of those maps don't even store a value; and are really\n> just a set implemented via strmap (thus meaning the only bit of data I\n> need for some conditions is whether any given path meets it).  It\n> seems slightly ugly to have to call strmap_put(map, string, NULL) for\n> those.  I wonder if I should have another strset type much like your\n> suggesting for strintmap.  Hmm...\n\nFWIW, khash does have a \"set\" mode where it avoids allocating the value\narray at all.\n\nWhat's the easiest way to benchmark merge-ort? I suspect I could swap\nout hashmap for khash (messily) in an hour or less.\n\n-Peff\n"},{"id":"404701","messageId":"CABPp-BGQB8rD8KyzccTQQ_X3Puyy3g5OOSEQES8Lv8Qtb-zRPg@mail.gmail.com","threadId":"54087","inReplyTo":"20200828070335.GB2105050@coredump.intra.peff.net","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-28T15:29:44Z","receivedAt":"2020-08-28T15:30:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 28, 2020 at 12:03 AM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 02:33:54PM -0700, Elijah Newren wrote:\n>\n> > However, there's an important difference here between what I've done\n> > and what you've suggested for hashmap: my method did not deallocate\n> > hashmap->table in hashmap_clear() and then use lazy initialization.\n> > In fact, I think not deallocating the table was part of the charm --\n> > the table had already naturally grown to the right size, and because\n> > the repository has approximately the same number of paths in various\n> > commits, this provided me a way of getting a table preallocated to a\n> > reasonable size for all merges after the first (and there are multiple\n> > merges either when recursiveness is needed due to multiple merge\n> > bases, OR when rebasing or cherry-picking a sequence of commits).\n> > This prevented, as hashmap.h puts it, \"expensive resizing\".\n> >\n> > So, once again, my performance ideas might be clashing with some of\n> > your desires for the API.  Any clever ideas for resolving that?\n>\n> If the magic is in pre-sizing the hash, then it seems like the callers\n> ought to be feeding the size hint. That does make a little more work for\n> them, but I think there's real value in having consistent semantics for\n> \"clear\" across our data structures.\n\nI thought about adding a size hint from the callers, but the thing is\nI don't know how to get a good one short of running a merge and\nquerying how big things were sized in that merge.  (In some common\ncases I can get an upper bound, but I can't get it in all cases and\nthat upper bound might be a couple orders of magnitude too big.)\nThus, it's really a case where I just punt on pre-sizing for the first\nmerge, and use the size from the previous merge for subsequent ones.\nIf you have a non-recursive merge or are cherry-picking only a single\ncommit, then no sizing hint is used.\n\n> However, one cheat would be to free the memory but retain the size hint\n> after a clear. And then if we lazy-init, grow immediately to the hint\n> size. That's more expensive than a true reuse, because we do reallocate\n> the memory. But it avoids the repeated re-allocation during growth.\n>\n> It may also be a sign that we should be growing the hash more\n> aggressively in the first place. Of course all of this is predicated\n> having some benchmarks. It would be useful to know which part actually\n> provided the speedup.\n\nYour thoughts here are great; I also had another one this past week --\nI could introduce a hashmap_partial_clear() (in addition to\nhashmap_clear()) for the special usecase I have of leaving the table\nallocated and pre-sized.  It'd prevent people from accidentally using\nit and forgetting to free stuff, while still allowing me to take\nadvantage.  But, as you say, more benchmarks would be useful to find\nwhich parts provided the speedup before taking any of these steps.\n"},{"id":"404713","messageId":"CABPp-BFL_f4ee=NrfKwVEcLzuCZy7pfXOTLimp5Qn3P89RYfpA@mail.gmail.com","threadId":"54087","inReplyTo":"20200828070802.GC2105050@coredump.intra.peff.net","subject":"Re: [PATCH 4/5] strmap: add strdup_strings option","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-08-28T17:20:40Z","receivedAt":"2020-08-28T17:21:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 28, 2020 at 12:08 AM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Aug 21, 2020 at 03:25:44PM -0700, Elijah Newren wrote:\n>\n> > >   - That sounds like a lot of maps. :) I guess you've looked at\n> > >     compacting some of them into a single map-to-struct?\n> >\n> > Oh, map-to-struct is the primary use.  But compacting them won't work,\n> > because the reason for the additional maps is that they have different\n> > sets of keys (this set of paths meet a certain condition...).  Only\n> > one map contains all the paths involved in the merge.\n>\n> OK, I guess I'm not surprised that you would not have missed such an\n> obvious optimization. :)\n>\n> > Also, several of those maps don't even store a value; and are really\n> > just a set implemented via strmap (thus meaning the only bit of data I\n> > need for some conditions is whether any given path meets it).  It\n> > seems slightly ugly to have to call strmap_put(map, string, NULL) for\n> > those.  I wonder if I should have another strset type much like your\n> > suggesting for strintmap.  Hmm...\n>\n> FWIW, khash does have a \"set\" mode where it avoids allocating the value\n> array at all.\n\nCool.\n\n> What's the easiest way to benchmark merge-ort?\n\nNote that I discovered another optimization that I'm working on\nimplementing; when finished, it should cut down a little more on the\ntime spent on inexact rename detection.  That should have the side\neffect of having the time spent on strmaps stick out some more in the\noverall timings (as a percentage of overall time anyway).  So, I'm\nfocused on that before I do other benchmarking work (which is part of\nthe reason I mentioned my strmap/hashmap benchmarking last week might\ntake a while).\n\nAnyway, on to your question:\n\n=== If you just want to be able to run the ort merge algorithm ===\n\nClone git@github.com:newren/git and checkout the 'ort' branch and\nbuild it.  It currently changes the default merge algorithm to 'ort'\nand even ignores '-s recursive' by remapping it to '-s ort' (because I\nwanted to see how regression tests fared with ort as a replacement for\nrecrusive).  It should pass the regression tests if you want to run\nthose first.  But note that if you want to compare 'ort' to\n'recursive', then currently you need to have two different git builds,\none of my branch and one with a different checkout of something else\n(e.g. 2.28.0 or 'master' or whatever).\n\n=== Decide the granularity of your timing ===\n\nI suspect you know more than me here, but maybe my pointers are useful anyway...\n\nDecide if you want to measure overall program runtime, or dive into\ndetails.  I used both a simple 'time' and the better 'hyperfine' for\nthe former, and used both 'perf' and GIT_TRACE2_PERF for the latter.\nOne nice thing about GIT_TRACE2_PERF was I wrote a simple program to\naggregate the times per region and provide percentages, in a script at\nthe toplevel named 'summarize-perf' that I can use to prefix commands.\nThus, I could for example run from my linux clone:\n    $ ../git/summarize-perf git fast-rebase --onto HEAD base hwmon-updates\nand I'd get output that looks something like (note that this is a\nsubset of the real output):\n    1.400 : 35 : label:inmemory_nonrecursive\n       0.827 : 41 : ..label:renames\n          0.019 : <unmeasured> ( 2.2%)\n          0.803 : 37 : ....label:regular renames\n          0.004 : 31 : ....label:directory renames\n          0.001 : 31 : ....label:process renames\n       0.513 : 41 : ..label:collect_merge_info\n       0.048 : 35 : ..label:process_entries\n    0.117 : 1 : label:checkout\n    0.000 : 1 : label:record_unmerged\nand where those fields are <time> : <count> : <region label>.\n\n=== If you want to time the testcases I used heavily while developing ===\n\nThe rebase-testcase/redo-timings script (in the ort branch) has\ndetails on what I actually ran, though it has some paranoia around\nattempting to make my laptop run semi-quietly and try to avoid all the\nvariance that I wished I could control a bit better.  And it assumes\nyou are running in a linux clone with a few branches set up a certain\nway.  Let me explain those tests without using that script, as simply\nas I can:\n\nThe setup for the particular cases I was testing is as follows:\n  * Clone the linux kernel, and run the following:\n  $ git branch hwmon-updates fd8bdb23b91876ac1e624337bb88dc1dcc21d67e\n  $ git branch hwmon-just-one fd8bdb23b91876ac1e624337bb88dc1dcc21d67e~34\n  $ git branch base 4703d9119972bf586d2cca76ec6438f819ffa30e\n  $ git switch -c 5.4-renames v5.4\n  $ git mv drivers pilots\n  $ git commit -m \"Rename drivers/ to pilots/\"\n\nAnd from there, there were three primary tests I was comparing:\n\n  * Rename testcase, 35 patches:\n  $ git checkout 5.4-renames^0\n  $ git fast-rebase --onto HEAD base hwmon-updates\n\n  * Rename testcase, just 1 patch:\n  $ git switch 5.4-renames^0\n  $ git fast-rebase --onto HEAD base hwmon-just-one\n\n  * No renames (or at least very few renames) testcase, 35 patches:\n  $ git checkout v5.4^0\n  $ git branch -f hwmon-updates\nfd8bdb23b91876ac1e624337bb88dc1dcc21d67e # Need to reset\nhwmon-updates, due to fast-rebase done above\n  $ git fast-rebase --onto HEAD base hwmon-updates\n\n(If you want to compare with 'recursive' from a different build of\ngit, just replace 'fast-rebase' with 'rebase'.  You can also use\n'rebase' instead of 'fast-rebase' on the ort branch and it'll use the\nort merge algorithm, but you get all the annoying\nworking-tree-updates-while-rebasing rather than just having the\nworking tree updated at the end of the rebase.  You also get all the\nannoying forks of 'git checkout' and 'git commit' that sequencer is\nguilty of spawning.  But it certainly supports a lot more options and\ncan save state to allow resuming after conflicts, unlike\n'fast-rebase'.)\n\n> I suspect I could swap out hashmap for khash (messily) in an hour or less.\n\nWell, you might be assuming I used sane strmaps, with each strmap\nhaving a fixed type for the stored value.  That's mostly true, but\nthere were two counterexamples I can think of: \"paths\" (the biggest\nstrmap) is a map of string -> {merged_info OR conflict_info}, because\nmerged_info is a smaller subset of conflict_info and saves space for\neach path that can be trivially merged.  Also, in diffcore-rename,\n\"dir_rename\" starts life as a map of string -> strmap, but later\ntransitions to string -> string, because I'm evil and didn't set up a\ntemporary strmap like I probably should have.\n\nAlso, the code is littered with FIXME comments, unnecessary #ifdefs,\nand is generally in need of lots of cleanup.  Sorry.\n"},{"id":"404839","messageId":"20200901092731.GA1268661@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BGQB8rD8KyzccTQQ_X3Puyy3g5OOSEQES8Lv8Qtb-zRPg@mail.gmail.com","subject":"Re: [PATCH 0/5] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-09-01T09:27:31Z","receivedAt":"2020-09-01T09:27:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Aug 28, 2020 at 08:29:44AM -0700, Elijah Newren wrote:\n\n> > It may also be a sign that we should be growing the hash more\n> > aggressively in the first place. Of course all of this is predicated\n> > having some benchmarks. It would be useful to know which part actually\n> > provided the speedup.\n> \n> Your thoughts here are great; I also had another one this past week --\n> I could introduce a hashmap_partial_clear() (in addition to\n> hashmap_clear()) for the special usecase I have of leaving the table\n> allocated and pre-sized.  It'd prevent people from accidentally using\n> it and forgetting to free stuff, while still allowing me to take\n> advantage.  But, as you say, more benchmarks would be useful to find\n> which parts provided the speedup before taking any of these steps.\n\nYeah, having a separate function to explicitly do \"remove all elements\nbut keep the table allocated\" would be fine with me. My big desire is\nthat clear() should do the safe, non-leaking thing by default.\n\n-Peff\n"},{"id":"407409","messageId":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 01/10] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:41Z","receivedAt":"2020-10-13T02:42:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  We are dealing with a map table,\nentries in that table, and possibly also things each of those entries\npoint to.  I had to consult other source code examples and the\nimplementation.  Add a brief note to clarify the differences.  This will\nbecome even more important once we introduce a new\nhashmap_partial_clear() function which will add the question of whether\nthe table itself has been freed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 31 +++++++++++++++++++++++++++++--\n 1 file changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex b011b394fe..2994dc7a9c 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ *\n+ * instead of\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"407410","messageId":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.git.git.1598035949.gitgitgadget@gmail.com","subject":"[PATCH v2 00/10] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:40Z","receivedAt":"2020-10-13T02:42:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce a new strmap type, which my new merge backed, merge-ort,\nuses heavily. (I also made significant use of it in my changes to\ndiffcore-rename). This strmap type was based on Peff's proposal from a\ncouple years ago[1], but has additions that I made as I used it. I also\nstart the series off with some changes to hashmap, based on Peff's feedback\non v1.\n\nChanges since v1:\n\n * Rebased on newer origin/master (to resolve a conflict in Makefile)\n * First fixed hashmap to allow it to continue to be used after\n   hashmap_free() is called, as requested by Peff.\n * Renamed my hashmap_clear() to hashmap_partial_clear() to avoid mis-use\n   and make it clear it doesn't free everything.\n * Distanced the API from string-list, as per feedback from Peff. In\n   particular, removed the use of string_list_item, and made strdup_strings\n   the default -- strmap_init() does not accept a strdup_strings parameter\n   it just defaults to on; one has to call strmap_ocd_init() instead if one\n   wants to control the memory closely.\n * Added strintmap and strset types, for string->int mappings, and cases\n   where we just want a set of keys rather than having each key map to some\n   value.\n * Also included a patch enabling strmaps to make use of mem_pools (also\n   only accessible via the strmap_ocd_init() constructor ); I previously\n   thought this only made sense to include after the relevant point in\n   merge-ort, but I figured since it slightly affects the API (it was part\n   of what led me to the strmap_ocd_init() name), I decided to include it\n   for now.\n * The hashmap_partial_clear() and strmap_partial_clear() additions in\n   patches 4 and 7 could also potentially be deferred much like mem_pool\n   additions, but again, I decided to include them because they give a\n   better picture of the overall usage I have and usecases I'm trying to\n   design the API for.\n\nThings Peff mentioned that are NOT included in this v2:\n\n * Peff brought up the idea of having a free_values member instead of having\n   a free_util parameter to strmap_clear(). I think that would just mean\n   moving the parameter from strmap_clear() to strmap_init().\n * Peff wanted the strmap_entry type to have a char key[FLEX_ALLOC] instead\n   of having a (const) char *key.\n\nExplanations/excuses for the above: \n\n * For the free_values member, it sounded like Peff didn't have a strong\n   opinion. I don't either, so I'm happy to switch it if someone feels\n   strongly. But since it sounded like a thinking-out-loud comment and I\n   couldn't see an advantage one way or the other, I left things as-is.\n * For the FLEX_ALLOC implementation, Peff did have a clear strong\n   preference. I put a day or so of time into trying to get an alternate\n   implementation working (and I at least ripped out the string_list_item\n   sub-type, made the key be const, and fixed the ugly casts), but didn't\n   quite get the code to a working state. (Not only does it change the\n   memory management quite a bit in ways that I need to run under valgrind\n   both with and without mem_pools, but there's an extra wrinkle as well:\n   merge-ort differs from merge-recursive in that all filepaths come from a\n   single tree traversal instead of several different ones, which means that\n   I had the ability to compare path strings for equality via pointer\n   comparisons instead of using strcmp. Copying strings for each of the\n   different strmaps breaks that.) If the changes I've made aren't\n   sufficient and folks still want to see the performance of a FLEX_ALLOC\n   implementation, it's probably possible to get it working, just\n   surprisingly non-trivial. For now, I at least wanted to get feedback on\n   my other changes, and probe for whether folks do want me to put another\n   day or two into this.\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nElijah Newren (10):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  hashmap: adjust spacing to fix argument alignment\n  hashmap: allow re-use after hashmap_free()\n  hashmap: introduce a new hashmap_partial_clear()\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: enable faster clearing and reusing of strmaps\n  strmap: add functions facilitating use as a string->int map\n  strmap: add a strset sub-type\n  strmap: enable allocations to come from a mem_pool\n\n Makefile  |   1 +\n hashmap.c |  72 +++++++++++-----\n hashmap.h |  44 +++++++++-\n strmap.c  | 152 +++++++++++++++++++++++++++++++++\n strmap.h  | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 494 insertions(+), 25 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: d4a392452e292ff924e79ec8458611c0f679d6d4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v2\nPull-Request: https://github.com/git/git/pull/835\n\nRange-diff vs v1:\n\n  1:  b295e9393a !  1:  af6b6fcb46 hashmap: add usage documentation explaining hashmap_free[_entries]()\n     @@ Commit message\n          hashmap: add usage documentation explaining hashmap_free[_entries]()\n      \n          The existence of hashmap_free() and hashmap_free_entries() confused me,\n     -    and the docs weren't clear enough.  I had to consult other source code\n     -    examples and the implementation.  Add a brief note to clarify,\n     -    especially since hashmap_clear*() variants may be added in the future.\n     +    and the docs weren't clear enough.  We are dealing with a map table,\n     +    entries in that table, and possibly also things each of those entries\n     +    point to.  I had to consult other source code examples and the\n     +    implementation.  Add a brief note to clarify the differences.  This will\n     +    become even more important once we introduce a new\n     +    hashmap_partial_clear() function which will add the question of whether\n     +    the table itself has been freed.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ hashmap.h: void hashmap_init(struct hashmap *map,\n      + * Many callers will need to iterate over all entries and free the data each\n      + * entry points to; in such a case, they can free the entry itself while at it.\n      + * Thus, you might see:\n     ++ *\n      + *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n      + *      free(e->somefield);\n      + *      free(e);\n      + *    }\n      + *    hashmap_free(map);\n     ++ *\n      + * instead of\n     ++ *\n      + *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n      + *      free(e->somefield);\n      + *    }\n      + *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n     ++ *\n      + * to avoid the implicit extra loop over the entries.  However, if there are\n      + * no special fields in your entry that need to be freed beyond the entry\n      + * itself, it is probably simpler to avoid the explicit loop and just call\n  -:  ---------- >  2:  75f17619e9 hashmap: adjust spacing to fix argument alignment\n  -:  ---------- >  3:  a686d0758a hashmap: allow re-use after hashmap_free()\n  -:  ---------- >  4:  061ab45a9b hashmap: introduce a new hashmap_partial_clear()\n  2:  a86fd5fdcc !  5:  5c7507f55b strmap: new utility functions\n     @@ Commit message\n          taken directly from Peff's proposal at\n          https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n      \n     -    Peff only included the header, not the implementation, so it isn't clear what\n     -    the structure was he was going to use for the hash entries.  Instead of having\n     -    my str_entry struct have three subfields (the hashmap_entry, the string, and\n     -    the void* value), I made it only have two -- the hashmap_entry and a\n     -    string_list_item, for two reasons:\n     +    A couple of items of note:\n      \n     -      1) a strmap is often the data structure we want where string_list has\n     -         been used in the past.  Using the same building block for\n     -         individual entries in both makes it easier to adopt and reuse\n     -         parts of the string_list API in strmap.\n     +      * Similar to string-list, I have a strdup_strings setting.  However,\n     +        unlike string-list, strmap_init() does not take a parameter for this\n     +        setting and instead automatically sets it to 1; callers who want to\n     +        control this detail need to instead call strmap_ocd_init().\n      \n     -      2) In some cases, after doing lots of other work, I want to be able\n     -         to iterate over the items in my strmap in sorted order.  hashmap\n     -         obviously doesn't support that, but I wanted to be able to export\n     -         the strmap to a string_list easily and then use its functions.\n     -         (Note: I do not need the data structure to both be sorted and have\n     -         efficient lookup at all times.  If I did, I might use a B-tree\n     -         instead, as suggested by brian in response to Peff in the thread\n     -         noted above.  In my case, most strmaps will never need sorting, but\n     -         in one special case at the very end of a bunch of other work I want\n     -         to iterate over the items in sorted order without doing any more\n     -         lookups afterward.)\n     -\n     -    Also, I removed the STRMAP_INIT macro, since it cannot be used to\n     -    correctly initialize a strmap; the underlying hashmap needs a call to\n     -    hashmap_init() to allocate the hash table first.\n     +      * I do not have a STRMAP_INIT macro.  I could possibly add one, but\n     +          #define STRMAP_INIT { { NULL, cmp_str_entry, NULL, 0, 0, 0, 0, 0 }, 1 }\n     +        feels a bit unwieldy and possibly error-prone in terms of future\n     +        expansion of the hashmap struct.  The fact that cmp_str_entry needs to\n     +        be in there prevents us from passing all zeros for the hashmap, and makes\n     +        me worry that STRMAP_INIT would just be more trouble than it is worth.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## Makefile ##\n     -@@ Makefile: LIB_OBJS += strbuf.o\n     - LIB_OBJS += strvec.o\n     +@@ Makefile: LIB_OBJS += stable-qsort.o\n     + LIB_OBJS += strbuf.o\n       LIB_OBJS += streaming.o\n       LIB_OBJS += string-list.o\n      +LIB_OBJS += strmap.o\n     + LIB_OBJS += strvec.o\n       LIB_OBJS += sub-process.o\n       LIB_OBJS += submodule-config.o\n     - LIB_OBJS += submodule.o\n      \n       ## strmap.c (new) ##\n      @@\n      +#include \"git-compat-util.h\"\n      +#include \"strmap.h\"\n      +\n     -+static int cmp_str_entry(const void *hashmap_cmp_fn_data,\n     -+\t\t\t const struct hashmap_entry *entry1,\n     -+\t\t\t const struct hashmap_entry *entry2,\n     -+\t\t\t const void *keydata)\n     ++static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     ++\t\t\t    const struct hashmap_entry *entry1,\n     ++\t\t\t    const struct hashmap_entry *entry2,\n     ++\t\t\t    const void *keydata)\n      +{\n     -+\tconst struct str_entry *e1, *e2;\n     ++\tconst struct strmap_entry *e1, *e2;\n      +\n     -+\te1 = container_of(entry1, const struct str_entry, ent);\n     -+\te2 = container_of(entry2, const struct str_entry, ent);\n     -+\treturn strcmp(e1->item.string, e2->item.string);\n     ++\te1 = container_of(entry1, const struct strmap_entry, ent);\n     ++\te2 = container_of(entry2, const struct strmap_entry, ent);\n     ++\treturn strcmp(e1->key, e2->key);\n      +}\n      +\n     -+static struct str_entry *find_str_entry(struct strmap *map,\n     -+\t\t\t\t\tconst char *str)\n     ++static struct strmap_entry *find_strmap_entry(struct strmap *map,\n     ++\t\t\t\t\t      const char *str)\n      +{\n     -+\tstruct str_entry entry;\n     ++\tstruct strmap_entry entry;\n      +\thashmap_entry_init(&entry.ent, strhash(str));\n     -+\tentry.item.string = (char *)str;\n     ++\tentry.key = str;\n      +\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n      +}\n      +\n      +void strmap_init(struct strmap *map)\n      +{\n     -+\thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n     ++\tstrmap_ocd_init(map, 1);\n      +}\n      +\n     -+void strmap_clear(struct strmap *map, int free_util)\n     ++void strmap_ocd_init(struct strmap *map,\n     ++\t\t     int strdup_strings)\n     ++{\n     ++\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n     ++\tmap->strdup_strings = strdup_strings;\n     ++}\n     ++\n     ++static void strmap_free_entries_(struct strmap *map, int free_util)\n      +{\n      +\tstruct hashmap_iter iter;\n     -+\tstruct str_entry *e;\n     ++\tstruct strmap_entry *e;\n      +\n      +\tif (!map)\n      +\t\treturn;\n      +\n     -+\thashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {\n     -+\t\tfree(e->item.string);\n     ++\t/*\n     ++\t * We need to iterate over the hashmap entries and free\n     ++\t * e->key and e->value ourselves; hashmap has no API to\n     ++\t * take care of that for us.  Since we're already iterating over\n     ++\t * the hashmap, though, might as well free e too and avoid the need\n     ++\t * to make some call into the hashmap API to do that.\n     ++\t */\n     ++\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n      +\t\tif (free_util)\n     -+\t\t\tfree(e->item.util);\n     ++\t\t\tfree(e->value);\n     ++\t\tif (map->strdup_strings)\n     ++\t\t\tfree((char*)e->key);\n     ++\t\tfree(e);\n      +\t}\n     -+\thashmap_free_entries(&map->map, struct str_entry, ent);\n     -+\tstrmap_init(map);\n      +}\n      +\n     -+/*\n     -+ * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n     -+ * it does not need to persist after the this function is called.\n     -+ *\n     -+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n     -+ * the original data pointer returned. Otherwise, returns NULL.\n     -+ */\n     ++void strmap_clear(struct strmap *map, int free_util)\n     ++{\n     ++\tstrmap_free_entries_(map, free_util);\n     ++\thashmap_free(&map->map);\n     ++}\n     ++\n      +void *strmap_put(struct strmap *map, const char *str, void *data)\n      +{\n     -+\tstruct str_entry *entry = find_str_entry(map, str);\n     ++\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n      +\tvoid *old = NULL;\n      +\n      +\tif (entry) {\n     -+\t\told = entry->item.util;\n     -+\t\tentry->item.util = data;\n     ++\t\told = entry->value;\n     ++\t\tentry->value = data;\n      +\t} else {\n     ++\t\t/*\n     ++\t\t * We won't modify entry->key so it really should be const.\n     ++\t\t */\n     ++\t\tconst char *key = str;\n     ++\n      +\t\tentry = xmalloc(sizeof(*entry));\n      +\t\thashmap_entry_init(&entry->ent, strhash(str));\n     -+\t\tentry->item.string = strdup(str);\n     -+\t\tentry->item.util = data;\n     ++\n     ++\t\tif (map->strdup_strings)\n     ++\t\t\tkey = xstrdup(str);\n     ++\t\tentry->key = key;\n     ++\t\tentry->value = data;\n      +\t\thashmap_add(&map->map, &entry->ent);\n      +\t}\n      +\treturn old;\n     @@ strmap.c (new)\n      +\n      +void *strmap_get(struct strmap *map, const char *str)\n      +{\n     -+\tstruct str_entry *entry = find_str_entry(map, str);\n     -+\treturn entry ? entry->item.util : NULL;\n     ++\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n     ++\treturn entry ? entry->value : NULL;\n      +}\n      +\n      +int strmap_contains(struct strmap *map, const char *str)\n      +{\n     -+\treturn find_str_entry(map, str) != NULL;\n     ++\treturn find_strmap_entry(map, str) != NULL;\n      +}\n      \n       ## strmap.h (new) ##\n     @@ strmap.h (new)\n      +#define STRMAP_H\n      +\n      +#include \"hashmap.h\"\n     -+#include \"string-list.h\"\n      +\n      +struct strmap {\n      +\tstruct hashmap map;\n     ++\tunsigned int strdup_strings:1;\n      +};\n      +\n     -+struct str_entry {\n     ++struct strmap_entry {\n      +\tstruct hashmap_entry ent;\n     -+\tstruct string_list_item item;\n     ++\tconst char *key;\n     ++\tvoid *value;\n      +};\n      +\n      +/*\n     -+ * Initialize an empty strmap\n     ++ * Initialize the members of the strmap.  Any keys added to the strmap will\n     ++ * be strdup'ed with their memory managed by the strmap.\n      + */\n      +void strmap_init(struct strmap *map);\n      +\n      +/*\n     ++ * Same as strmap_init, but for those who want to control the memory management\n     ++ * carefully instead of using the default of strdup_strings=1.\n     ++ * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n     ++ * are obsessing over minor details.)\n     ++ */\n     ++void strmap_ocd_init(struct strmap *map,\n     ++\t\t     int strdup_strings);\n     ++\n     ++/*\n      + * Remove all entries from the map, releasing any allocated resources.\n      + */\n      +void strmap_clear(struct strmap *map, int free_values);\n      +\n      +/*\n     -+ * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n     -+ * it does not need to persist after the this function is called.\n     ++ * Insert \"str\" into the map, pointing to \"data\".\n      + *\n      + * If an entry for \"str\" already exists, its data pointer is overwritten, and\n      + * the original data pointer returned. Otherwise, returns NULL.\n  3:  5bda171d0c !  6:  61b5bf1110 strmap: add more utility functions\n     @@ Commit message\n            * strmap_get_size()\n            * strmap_remove()\n            * strmap_for_each_entry()\n     -      * strmap_free()\n     -      * strmap_get_item()\n     +      * strmap_get_entry()\n      \n          I suspect the first four are self-explanatory.\n      \n     -    strmap_free() differs from strmap_clear() in that the data structure is\n     -    not reusable after it is called; strmap_clear() is not sufficient for\n     -    the API because without strmap_free() we will leak memory.\n     -\n     -    strmap_get_item() is similar to strmap_get() except that instead of just\n     +    strmap_get_entry() is similar to strmap_get() except that instead of just\n          returning the void* value that the string maps to, it returns the\n     -    string_list_item that contains both the string and the void* value (or\n     +    strmap_entry that contains both the string and the void* value (or\n          NULL if the string isn't in the map).  This is helpful because it avoids\n          multiple lookups, e.g. in some cases a caller would need to call:\n            * strmap_contains() to check that the map has an entry for the string\n     @@ Commit message\n            * strmap_put() to update/overwrite the value\n          If the void* pointer returned really is a pointer, then the last step is\n          unnecessary, but if the void* pointer is just cast to an integer then\n     -    strmap_put() will be needed.  In contrast, one can call strmap_get_item()\n     +    strmap_put() will be needed.  In contrast, one can call strmap_get_entry()\n          and then:\n            * check if the string was in the map by whether the pointer is NULL\n     -      * access the value via item->util\n     -      * directly update item->util\n     +      * access the value via entry->value\n     +      * directly update entry->value\n          meaning that we can replace two or three hash table lookups with one.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n     -@@ strmap.c: void strmap_init(struct strmap *map)\n     - \thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n     - }\n     - \n     --void strmap_clear(struct strmap *map, int free_util)\n     -+void strmap_free(struct strmap *map, int free_util)\n     - {\n     - \tstruct hashmap_iter iter;\n     - \tstruct str_entry *e;\n     -@@ strmap.c: void strmap_clear(struct strmap *map, int free_util)\n     - \tif (!map)\n     - \t\treturn;\n     - \n     --\thashmap_for_each_entry(&map->map, &iter, e, ent /* member name */) {\n     --\t\tfree(e->item.string);\n     --\t\tif (free_util)\n     --\t\t\tfree(e->item.util);\n     -+\tif (free_util) {\n     -+\t\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n     -+\t\t\tfree(e->item.string);\n     -+\t\t\tif (free_util)\n     -+\t\t\t\tfree(e->item.util);\n     -+\t\t}\n     - \t}\n     - \thashmap_free_entries(&map->map, struct str_entry, ent);\n     -+}\n     -+\n     -+void strmap_clear(struct strmap *map, int free_util)\n     -+{\n     -+\tstrmap_free(map, free_util);\n     - \tstrmap_init(map);\n     - }\n     - \n      @@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n       \treturn old;\n       }\n       \n     -+struct string_list_item *strmap_get_item(struct strmap *map,\n     -+\t\t\t\t\t const char *str)\n     ++struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n      +{\n     -+\tstruct str_entry *entry = find_str_entry(map, str);\n     -+\treturn entry ? &entry->item : NULL;\n     ++\treturn find_strmap_entry(map, str);\n      +}\n      +\n       void *strmap_get(struct strmap *map, const char *str)\n       {\n     - \tstruct str_entry *entry = find_str_entry(map, str);\n     + \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n      @@ strmap.c: int strmap_contains(struct strmap *map, const char *str)\n       {\n     - \treturn find_str_entry(map, str) != NULL;\n     + \treturn find_strmap_entry(map, str) != NULL;\n       }\n      +\n      +void strmap_remove(struct strmap *map, const char *str, int free_util)\n      +{\n     -+\tstruct str_entry entry, *ret;\n     ++\tstruct strmap_entry entry, *ret;\n      +\thashmap_entry_init(&entry.ent, strhash(str));\n     -+\tentry.item.string = (char *)str;\n     ++\tentry.key = str;\n      +\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n     -+\tif (ret && free_util)\n     -+\t\tfree(ret->item.util);\n     ++\tif (!ret)\n     ++\t\treturn;\n     ++\tif (free_util)\n     ++\t\tfree(ret->value);\n     ++\tif (map->strdup_strings)\n     ++\t\tfree((char*)ret->key);\n      +\tfree(ret);\n      +}\n      \n       ## strmap.h ##\n     -@@ strmap.h: void strmap_init(struct strmap *map);\n     - /*\n     -  * Remove all entries from the map, releasing any allocated resources.\n     -  */\n     -+void strmap_free(struct strmap *map, int free_values);\n     -+\n     -+/*\n     -+ * Same as calling strmap_free() followed by strmap_init().\n     -+ */\n     - void strmap_clear(struct strmap *map, int free_values);\n     - \n     - /*\n      @@ strmap.h: void strmap_clear(struct strmap *map, int free_values);\n        */\n       void *strmap_put(struct strmap *map, const char *str, void *data);\n       \n      +/*\n     -+ * Return the string_list_item mapped by \"str\", or NULL if there is not such\n     ++ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n      + * an item in map.\n      + */\n     -+struct string_list_item *strmap_get_item(struct strmap *map, const char *str);\n     ++struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n      +\n       /*\n        * Return the data pointer mapped by \"str\", or NULL if the entry does not\n     @@ strmap.h: void *strmap_get(struct strmap *map, const char *str);\n      +}\n      +\n      +/*\n     -+ * iterate through @map using @iter, @var is a pointer to a type str_entry\n     ++ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n      + */\n      +#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n      +\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n      +\t\t\t\t\t\t   OFFSETOF_VAR(var, ent)); \\\n      +\t\tvar; \\\n      +\t\tvar = hashmap_iter_next_entry_offset(iter, \\\n     -+\t\t\t\t\t\tOFFSETOF_VAR(var, ent)))\n     ++\t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n      +\n       #endif /* STRMAP_H */\n  -:  ---------- >  7:  2ebce0c5d8 strmap: enable faster clearing and reusing of strmaps\n  5:  418975b460 !  8:  cc8d702f98 strmap: add functions facilitating use as a string->int map\n     @@ Commit message\n          isn't the case when we're storing an int value directly in the void*\n          slot instead of using the void* slot as a pointer to data.\n      \n     -    A note on the name: strintmap looks and sounds pretty lame to me, but\n     -    after trying to come up with something better and having no luck, I\n     -    figured I'd just go with it for a while and then at some point some\n     -    better and obvious name would strike me and I could replace it.  Several\n     -    months later, I still don't have a better name.  Hopefully someone else\n     -    has one.\n     +    A note on the name: if anyone has a better name suggestion than\n     +    strintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\n     +    not been able to come up with a better name.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n      @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n     - \t\tfree(ret->item.util);\n     + \t\tfree((char*)ret->key);\n       \tfree(ret);\n       }\n      +\n     -+void strintmap_incr(struct strmap *map, const char *str, intptr_t amt)\n     ++void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n      +{\n     -+\tstruct str_entry *entry = find_str_entry(map, str);\n     ++\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n      +\tif (entry) {\n     -+\t\tintptr_t *whence = (intptr_t*)&entry->item.util;\n     ++\t\tintptr_t *whence = (intptr_t*)&entry->value;\n      +\t\t*whence += amt;\n      +\t}\n      +\telse\n     @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n       ## strmap.h ##\n      @@ strmap.h: static inline unsigned int strmap_get_size(struct strmap *map)\n       \t\tvar = hashmap_iter_next_entry_offset(iter, \\\n     - \t\t\t\t\t\tOFFSETOF_VAR(var, ent)))\n     + \t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n       \n     ++\n      +/*\n     -+ * Helper functions for using strmap as map of string -> int, using the void*\n     -+ * field to store the int instead of allocating an int and having the void*\n     -+ * member point to the allocated int.\n     ++ * strintmap:\n     ++ *    A map of string -> int, typecasting the void* of strmap to an int.\n     ++ *\n     ++ * Primary differences:\n     ++ *    1) Since the void* value is just an int in disguise, there is no value\n     ++ *       to free.  (Thus one fewer argument to strintmap_clear)\n     ++ *    2) strintmap_get() returns an int; it also requires an extra parameter to\n     ++ *       be specified so it knows what value to return if the underlying strmap\n     ++ *       has not key matching the given string.\n     ++ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n     ++ *       instead.\n      + */\n      +\n     -+static inline int strintmap_get(struct strmap *map, const char *str,\n     -+\t\t\t\tint default_value)\n     ++struct strintmap {\n     ++\tstruct strmap map;\n     ++};\n     ++\n     ++#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n     ++\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n     ++\n     ++static inline void strintmap_init(struct strintmap *map)\n      +{\n     -+\tstruct string_list_item *result = strmap_get_item(map, str);\n     -+\tif (!result)\n     -+\t\treturn default_value;\n     -+\treturn (intptr_t)result->util;\n     ++\tstrmap_init(&map->map);\n     ++}\n     ++\n     ++static inline void strintmap_ocd_init(struct strintmap *map,\n     ++\t\t\t\t      int strdup_strings)\n     ++{\n     ++\tstrmap_ocd_init(&map->map, strdup_strings);\n      +}\n      +\n     -+static inline void strintmap_set(struct strmap *map, const char *str, intptr_t v)\n     ++static inline void strintmap_clear(struct strintmap *map)\n      +{\n     -+\tstrmap_put(map, str, (void *)v);\n     ++\tstrmap_clear(&map->map, 0);\n      +}\n      +\n     -+void strintmap_incr(struct strmap *map, const char *str, intptr_t amt);\n     ++static inline void strintmap_partial_clear(struct strintmap *map)\n     ++{\n     ++\tstrmap_partial_clear(&map->map, 0);\n     ++}\n     ++\n     ++static inline int strintmap_contains(struct strintmap *map, const char *str)\n     ++{\n     ++\treturn strmap_contains(&map->map, str);\n     ++}\n      +\n     -+static inline void strintmap_clear(struct strmap *map)\n     ++static inline void strintmap_remove(struct strintmap *map, const char *str)\n      +{\n     -+\tstrmap_clear(map, 0);\n     ++\treturn strmap_remove(&map->map, str, 0);\n      +}\n      +\n     -+static inline void strintmap_free(struct strmap *map)\n     ++static inline int strintmap_empty(struct strintmap *map)\n      +{\n     -+\tstrmap_free(map, 0);\n     ++\treturn strmap_empty(&map->map);\n      +}\n     ++\n     ++static inline unsigned int strintmap_get_size(struct strintmap *map)\n     ++{\n     ++\treturn strmap_get_size(&map->map);\n     ++}\n     ++\n     ++static inline int strintmap_get(struct strintmap *map, const char *str,\n     ++\t\t\t\tint default_value)\n     ++{\n     ++\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n     ++\tif (!result)\n     ++\t\treturn default_value;\n     ++\treturn (intptr_t)result->value;\n     ++}\n     ++\n     ++static inline void strintmap_set(struct strintmap *map, const char *str,\n     ++\t\t\t\t intptr_t v)\n     ++{\n     ++\tstrmap_put(&map->map, str, (void *)v);\n     ++}\n     ++\n     ++void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n      +\n       #endif /* STRMAP_H */\n  -:  ---------- >  9:  490d3a42ad strmap: add a strset sub-type\n  4:  b3095d97d8 ! 10:  eca4f1ddba strmap: add strdup_strings option\n     @@ Metadata\n      Author: Elijah Newren <newren@gmail.com>\n      \n       ## Commit message ##\n     -    strmap: add strdup_strings option\n     +    strmap: enable allocations to come from a mem_pool\n      \n     -    Just as it is sometimes useful for string_list to duplicate and take\n     -    ownership of memory management of the strings it contains, the same is\n     -    sometimes true for strmaps as well.  Add the same flag from string_list\n     -    to strmap.\n     +    For heavy users of strmaps, allowing the keys and entries to be\n     +    allocated from a memory pool can provide significant overhead savings.\n     +    Add an option to strmap_ocd_init() to specify a memory pool.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n     -@@ strmap.c: static struct str_entry *find_str_entry(struct strmap *map,\n     - \treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n     +@@\n     + #include \"git-compat-util.h\"\n     + #include \"strmap.h\"\n     ++#include \"mem-pool.h\"\n     + \n     + static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     + \t\t\t    const struct hashmap_entry *entry1,\n     +@@ strmap.c: static struct strmap_entry *find_strmap_entry(struct strmap *map,\n     + \n     + void strmap_init(struct strmap *map)\n     + {\n     +-\tstrmap_ocd_init(map, 1);\n     ++\tstrmap_ocd_init(map, NULL, 1);\n       }\n       \n     --void strmap_init(struct strmap *map)\n     -+void strmap_init(struct strmap *map, int strdup_strings)\n     + void strmap_ocd_init(struct strmap *map,\n     ++\t\t     struct mem_pool *pool,\n     + \t\t     int strdup_strings)\n       {\n     - \thashmap_init(&map->map, cmp_str_entry, NULL, 0);\n     -+\tmap->strdup_strings = strdup_strings;\n     + \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n     ++\tmap->pool = pool;\n     + \tmap->strdup_strings = strdup_strings;\n       }\n       \n     - void strmap_free(struct strmap *map, int free_util)\n     -@@ strmap.c: void strmap_free(struct strmap *map, int free_util)\n     +@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_util)\n       \tif (!map)\n       \t\treturn;\n       \n     --\tif (free_util) {\n     -+\tif (map->strdup_strings || free_util) {\n     - \t\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n     --\t\t\tfree(e->item.string);\n     ++\tif (!free_util && map->pool)\n     ++\t\t/* Memory other than util is owned by and freed with the pool */\n     ++\t\treturn;\n     ++\n     + \t/*\n     + \t * We need to iterate over the hashmap entries and free\n     + \t * e->key and e->value ourselves; hashmap has no API to\n     +@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_util)\n     + \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n     + \t\tif (free_util)\n     + \t\t\tfree(e->value);\n     +-\t\tif (map->strdup_strings)\n     +-\t\t\tfree((char*)e->key);\n     +-\t\tfree(e);\n     ++\t\tif (!map->pool) {\n      +\t\t\tif (map->strdup_strings)\n     -+\t\t\t\tfree(e->item.string);\n     - \t\t\tif (free_util)\n     - \t\t\t\tfree(e->item.util);\n     - \t\t}\n     -@@ strmap.c: void strmap_free(struct strmap *map, int free_util)\n     - void strmap_clear(struct strmap *map, int free_util)\n     - {\n     - \tstrmap_free(map, free_util);\n     --\tstrmap_init(map);\n     -+\tstrmap_init(map, map->strdup_strings);\n     ++\t\t\t\tfree((char*)e->key);\n     ++\t\t\tfree(e);\n     ++\t\t}\n     + \t}\n       }\n       \n     - /*\n     -- * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n     -- * it does not need to persist after the this function is called.\n     -+ * Insert \"str\" into the map, pointing to \"data\".\n     -  *\n     -  * If an entry for \"str\" already exists, its data pointer is overwritten, and\n     -  * the original data pointer returned. Otherwise, returns NULL.\n      @@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n     - \t} else {\n     - \t\tentry = xmalloc(sizeof(*entry));\n     + \t\t */\n     + \t\tconst char *key = str;\n     + \n     +-\t\tentry = xmalloc(sizeof(*entry));\n     ++\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n     ++\t\t\t\t  : xmalloc(sizeof(*entry));\n       \t\thashmap_entry_init(&entry->ent, strhash(str));\n     --\t\tentry->item.string = strdup(str);\n     -+\t\t/*\n     -+\t\t * We won't modify entry->item.string so it really should be\n     -+\t\t * const, but changing string_list_item to use a const char *\n     -+\t\t * is a bit too big of a change at this point.\n     -+\t\t */\n     -+\t\tentry->item.string =\n     -+\t\t\tmap->strdup_strings ? xstrdup(str) : (char *)str;\n     - \t\tentry->item.util = data;\n     + \n     + \t\tif (map->strdup_strings)\n     +-\t\t\tkey = xstrdup(str);\n     ++\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n     ++\t\t\t\t\t: xstrdup(str);\n     + \t\tentry->key = key;\n     + \t\tentry->value = data;\n       \t\thashmap_add(&map->map, &entry->ent);\n     - \t}\n      @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n     - \thashmap_entry_init(&entry.ent, strhash(str));\n     - \tentry.item.string = (char *)str;\n     - \tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n     -+\tif (map->strdup_strings)\n     -+\t\tfree(ret->item.string);\n     - \tif (ret && free_util)\n     - \t\tfree(ret->item.util);\n     - \tfree(ret);\n     + \t\treturn;\n     + \tif (free_util)\n     + \t\tfree(ret->value);\n     +-\tif (map->strdup_strings)\n     +-\t\tfree((char*)ret->key);\n     +-\tfree(ret);\n     ++\tif (!map->pool) {\n     ++\t\tif (map->strdup_strings)\n     ++\t\t\tfree((char*)ret->key);\n     ++\t\tfree(ret);\n     ++\t}\n     + }\n     + \n     + void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n      \n       ## strmap.h ##\n      @@\n       \n     + #include \"hashmap.h\"\n     + \n     ++struct mempool;\n       struct strmap {\n       \tstruct hashmap map;\n     -+\tunsigned int strdup_strings:1;\n     ++\tstruct mem_pool *pool;\n     + \tunsigned int strdup_strings:1;\n       };\n       \n     - struct str_entry {\n     -@@ strmap.h: struct str_entry {\n     - };\n     +@@ strmap.h: void strmap_init(struct strmap *map);\n       \n       /*\n     -- * Initialize an empty strmap\n     -+ * Initialize the members of the strmap, set `strdup_strings`\n     -+ * member according to the value of the second parameter.\n     +  * Same as strmap_init, but for those who want to control the memory management\n     +- * carefully instead of using the default of strdup_strings=1.\n     ++ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n     +  * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n     +  * are obsessing over minor details.)\n        */\n     --void strmap_init(struct strmap *map);\n     -+void strmap_init(struct strmap *map, int strdup_strings);\n     + void strmap_ocd_init(struct strmap *map,\n     ++\t\t     struct mem_pool *pool,\n     + \t\t     int strdup_strings);\n       \n       /*\n     -  * Remove all entries from the map, releasing any allocated resources.\n     -@@ strmap.h: void strmap_free(struct strmap *map, int free_values);\n     - void strmap_clear(struct strmap *map, int free_values);\n     +@@ strmap.h: static inline void strintmap_init(struct strintmap *map)\n     + }\n       \n     - /*\n     -- * Insert \"str\" into the map, pointing to \"data\". A copy of \"str\" is made, so\n     -- * it does not need to persist after the this function is called.\n     -+ * Insert \"str\" into the map, pointing to \"data\".\n     -  *\n     -  * If an entry for \"str\" already exists, its data pointer is overwritten, and\n     -  * the original data pointer returned. Otherwise, returns NULL.\n     + static inline void strintmap_ocd_init(struct strintmap *map,\n     ++\t\t\t\t      struct mem_pool *pool,\n     + \t\t\t\t      int strdup_strings)\n     + {\n     +-\tstrmap_ocd_init(&map->map, strdup_strings);\n     ++\tstrmap_ocd_init(&map->map, pool, strdup_strings);\n     + }\n     + \n     + static inline void strintmap_clear(struct strintmap *map)\n     +@@ strmap.h: static inline void strset_init(struct strset *set)\n     + }\n     + \n     + static inline void strset_ocd_init(struct strset *set,\n     ++\t\t\t\t   struct mem_pool *pool,\n     + \t\t\t\t   int strdup_strings)\n     + {\n     +-\tstrmap_ocd_init(&set->map, strdup_strings);\n     ++\tstrmap_ocd_init(&set->map, pool, strdup_strings);\n     + }\n     + \n     + static inline void strset_clear(struct strset *set)\n\n-- \ngitgitgadget\n"},{"id":"407411","messageId":"5c7507f55b09e24c0bfe87cc3df06213cfd1235b.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 05/10] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:45Z","receivedAt":"2020-10-13T02:42:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nA couple of items of note:\n\n  * Similar to string-list, I have a strdup_strings setting.  However,\n    unlike string-list, strmap_init() does not take a parameter for this\n    setting and instead automatically sets it to 1; callers who want to\n    control this detail need to instead call strmap_ocd_init().\n\n  * I do not have a STRMAP_INIT macro.  I could possibly add one, but\n      #define STRMAP_INIT { { NULL, cmp_str_entry, NULL, 0, 0, 0, 0, 0 }, 1 }\n    feels a bit unwieldy and possibly error-prone in terms of future\n    expansion of the hashmap struct.  The fact that cmp_str_entry needs to\n    be in there prevents us from passing all zeros for the hashmap, and makes\n    me worry that STRMAP_INIT would just be more trouble than it is worth.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |   1 +\n strmap.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h |  57 +++++++++++++++++++++++++++++++\n 3 files changed, 160 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..777a34c01c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += strvec.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..4b48d64274\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,102 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t\t    const struct hashmap_entry *entry1,\n+\t\t\t    const struct hashmap_entry *entry2,\n+\t\t\t    const void *keydata)\n+{\n+\tconst struct strmap_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct strmap_entry, ent);\n+\te2 = container_of(entry2, const struct strmap_entry, ent);\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static struct strmap_entry *find_strmap_entry(struct strmap *map,\n+\t\t\t\t\t      const char *str)\n+{\n+\tstruct strmap_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\tstrmap_ocd_init(map, 1);\n+}\n+\n+void strmap_ocd_init(struct strmap *map,\n+\t\t     int strdup_strings)\n+{\n+\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n+}\n+\n+static void strmap_free_entries_(struct strmap *map, int free_util)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\t/*\n+\t * We need to iterate over the hashmap entries and free\n+\t * e->key and e->value ourselves; hashmap has no API to\n+\t * take care of that for us.  Since we're already iterating over\n+\t * the hashmap, though, might as well free e too and avoid the need\n+\t * to make some call into the hashmap API to do that.\n+\t */\n+\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\tif (free_util)\n+\t\t\tfree(e->value);\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)e->key);\n+\t\tfree(e);\n+\t}\n+}\n+\n+void strmap_clear(struct strmap *map, int free_util)\n+{\n+\tstrmap_free_entries_(map, free_util);\n+\thashmap_free(&map->map);\n+}\n+\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->value;\n+\t\tentry->value = data;\n+\t} else {\n+\t\t/*\n+\t\t * We won't modify entry->key so it really should be const.\n+\t\t */\n+\t\tconst char *key = str;\n+\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\t\tif (map->strdup_strings)\n+\t\t\tkey = xstrdup(str);\n+\t\tentry->key = key;\n+\t\tentry->value = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\treturn entry ? entry->value : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..493d19cbc0\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,57 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n+};\n+\n+struct strmap_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *key;\n+\tvoid *value;\n+};\n+\n+/*\n+ * Initialize the members of the strmap.  Any keys added to the strmap will\n+ * be strdup'ed with their memory managed by the strmap.\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Same as strmap_init, but for those who want to control the memory management\n+ * carefully instead of using the default of strdup_strings=1.\n+ * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n+ * are obsessing over minor details.)\n+ */\n+void strmap_ocd_init(struct strmap *map,\n+\t\t     int strdup_strings);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\".\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"407412","messageId":"061ab45a9bdae5352f62fa6e81bb21ae5c94b8df.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 04/10] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:44Z","receivedAt":"2020-10-13T02:42:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\nreset_maps() in merge-ort was taking about 12% of overall runtime in my\ntestcase involving rebasing 35 patches of linux.git across a big rename.\nreset_maps() was calling hashmap_free() followed by hashmap_init(),\nmeaning that not only was it freeing all the memory associated with each\nof the strmaps just to immediately allocate a new array again, it was\nallocating a new array that wasy likely smaller than needed (thus\nresulting in later need to rehash things).  The ending size of the map\ntable on the previous commit was likely almost perfectly sized for the\nnext commit we wanted to pick, and not dropping and reallocating the\ntable immediately is a win.\n\nAdd some new API to hashmap to clear a hashmap of entries without\nfreeing map->table (and instead only zeroing it out like alloc_table()\nwould do, along with zeroing the count of items in the table and the\nshrink_at field).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 39 +++++++++++++++++++++++++++------------\n hashmap.h | 13 ++++++++++++-\n 2 files changed, 39 insertions(+), 13 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex bb7c9979b8..922ed07954 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n \tmap->do_count_items = 1;\n }\n \n+static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct hashmap_entry *e;\n+\n+\thashmap_iter_init(map, &iter);\n+\twhile ((e = hashmap_iter_next(&iter)))\n+\t\t/*\n+\t\t * like container_of, but using caller-calculated\n+\t\t * offset (caller being hashmap_free_entries)\n+\t\t */\n+\t\tfree((char *)e - entry_offset);\n+}\n+\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tif (!map || !map->table)\n+\t\treturn;\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n+\tmemset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));\n+\tmap->shrink_at = 0;\n+\tmap->private_size = 0;\n+}\n+\n void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0) { /* called by hashmap_free_entries */\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct hashmap_entry *e;\n-\n-\t\thashmap_iter_init(map, &iter);\n-\t\twhile ((e = hashmap_iter_next(&iter)))\n-\t\t\t/*\n-\t\t\t * like container_of, but using caller-calculated\n-\t\t\t * offset (caller being hashmap_free_entries)\n-\t\t\t */\n-\t\t\tfree((char *)e - entry_offset);\n-\t}\n+\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\n }\ndiff --git a/hashmap.h b/hashmap.h\nindex 2994dc7a9c..056a8cda32 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -232,7 +232,8 @@ void hashmap_init(struct hashmap *map,\n \t\t\t const void *equals_function_data,\n \t\t\t size_t initial_size);\n \n-/* internal function for freeing hashmap */\n+/* internal functions for clearing or freeing hashmap */\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n@@ -265,6 +266,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n+/*\n+ * Basically the same as calling hashmap_free() followed by hashmap_init(),\n+ * but doesn't incur the overhead of deallocating and reallocating\n+ * map->table; it leaves map->table allocated and the same size but zeroes\n+ * it out so it's ready for use again as an empty map.  As with\n+ * hashmap_free(), you may need to free the entries yourself before calling\n+ * this function.\n+ */\n+#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n+\n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n  * where @member is the hashmap_entry struct used to associate with @map.\n-- \ngitgitgadget\n\n"},{"id":"407413","messageId":"a686d0758a2652c91363dcf68da7726093d00c23.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 03/10] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:43Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nPreviously, once map->table had been freed, any calls to hashmap_put(),\nhashmap_get(), or hashmap_remove() would cause a NULL pointer\ndereference (since hashmap_free_() also zeros the memory; without that\nzeroing, calling these functions would cause a use-after-free problem).\n\nModify these functions to check for a NULL table and automatically\nallocate as needed.\n\nI also thought about creating a HASHMAP_INIT macro to allow initializing\nhashmaps on the stack without calling hashmap_init(), but virtually all\nuses of hashmap specify a usecase-specific equals_function which defeats\nthe utility of such a macro.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 16 ++++++++++++++--\n 1 file changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex e44d8a3e85..bb7c9979b8 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash)\n \n static void rehash(struct hashmap *map, unsigned int newsize)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tunsigned int i, oldsize = map->tablesize;\n \tstruct hashmap_entry **oldtable = map->table;\n \n@@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize)\n static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,\n \t\tconst struct hashmap_entry *key, const void *keydata)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tstruct hashmap_entry **e = &map->table[bucket(map, key)];\n \twhile (*e && !entry_equals(map, *e, key, keydata))\n \t\te = &(*e)->next;\n@@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n \t\t\t\tconst struct hashmap_entry *key,\n \t\t\t\tconst void *keydata)\n {\n+\tif (!map->table)\n+\t\treturn NULL;\n \treturn *find_entry_ptr(map, key, keydata);\n }\n \n@@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n \n void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n {\n-\tunsigned int b = bucket(map, entry);\n+\tunsigned int b;\n+\n+\tif (!map->table)\n+\t\talloc_table(map, HASHMAP_INITIAL_SIZE);\n \n+\tb = bucket(map, entry);\n \t/* add entry */\n \tentry->next = map->table[b];\n \tmap->table[b] = entry;\n@@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n \t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n-\tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n+\tstruct hashmap_entry **e;\n+\n+\tif (!map->table)\n+\t\treturn NULL;\n+\te = find_entry_ptr(map, key, keydata);\n \tif (!*e)\n \t\treturn NULL;\n \n-- \ngitgitgadget\n\n"},{"id":"407414","messageId":"75f17619e964a8a1ab2ecd8267c25c10e5d68bef.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 02/10] hashmap: adjust spacing to fix argument alignment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:42Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo actual code changes; just whitespace adjustments.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 17 +++++++++--------\n 1 file changed, 9 insertions(+), 8 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex 09813e1a46..e44d8a3e85 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size)\n }\n \n static inline int entry_equals(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *e1, const struct hashmap_entry *e2,\n-\t\tconst void *keydata)\n+\t\t\t       const struct hashmap_entry *e1,\n+\t\t\t       const struct hashmap_entry *e2,\n+\t\t\t       const void *keydata)\n {\n \treturn (e1 == e2) ||\n \t       (e1->hash == e2->hash &&\n@@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map,\n }\n \n static inline unsigned int bucket(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *key)\n+\t\t\t\t  const struct hashmap_entry *key)\n {\n \treturn key->hash & (map->tablesize - 1);\n }\n@@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data,\n }\n \n void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n-\t\tconst void *cmpfn_data, size_t initial_size)\n+\t\t  const void *cmpfn_data, size_t initial_size)\n {\n \tunsigned int size = HASHMAP_INITIAL_SIZE;\n \n@@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry)\n+\t\t\t\t       const struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *e = entry->next;\n \tfor (; e; e = e->next)\n@@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n }\n \n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata)\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n \tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n@@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry)\n+\t\t\t\t  struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n \thashmap_add(map, entry);\n-- \ngitgitgadget\n\n"},{"id":"407415","messageId":"2ebce0c5d82b87fa9c9ef5dcefc0ac2701654f3b.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 07/10] strmap: enable faster clearing and reusing of strmaps","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:47Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen strmaps are used heavily, such as is done by my new merge-ort\nalgorithm, and strmaps need to be cleared but then re-used (because of\ne.g. picking multiple commits to cherry-pick, or due to a recursive\nmerge having several different merges while recursing), free-ing and\nreallocating map->table repeatedly can add up in time, especially since\nit will likely be reallocated to a much smaller size but the previous\nmerge provides a good guide to the right size to use for the next merge.\n\nIntroduce strmap_partial_clear() to take advantage of this type of\nsituation; it will act similar to strmap_clear() except that\nmap->table's entries are zeroed instead of map->table being free'd.\nMaking use of this function reduced the cost of reset_maps() by about\n20% in mert-ort, and dropped the overall runtime of my rebase testcase\nby just under 2%.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 6 ++++++\n strmap.h | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 909b9fbedf..47cbf11ec7 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_util)\n \thashmap_free(&map->map);\n }\n \n+void strmap_partial_clear(struct strmap *map, int free_util)\n+{\n+\tstrmap_free_entries_(map, free_util);\n+\thashmap_partial_clear(&map->map);\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\ndiff --git a/strmap.h b/strmap.h\nindex e49d020970..5bb7650d65 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -34,6 +34,12 @@ void strmap_ocd_init(struct strmap *map,\n  */\n void strmap_clear(struct strmap *map, int free_values);\n \n+/*\n+ * Similar to strmap_clear() but leaves map->map->table allocated and\n+ * pre-sized so that subsequent uses won't need as many rehashings.\n+ */\n+void strmap_partial_clear(struct strmap *map, int free_values);\n+\n /*\n  * Insert \"str\" into the map, pointing to \"data\".\n  *\n-- \ngitgitgadget\n\n"},{"id":"407416","messageId":"eca4f1ddbaa1cd8dc3fc64dbe9206af538cb317d.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 10/10] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:50Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor heavy users of strmaps, allowing the keys and entries to be\nallocated from a memory pool can provide significant overhead savings.\nAdd an option to strmap_ocd_init() to specify a memory pool.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 31 ++++++++++++++++++++++---------\n strmap.h | 11 ++++++++---\n 2 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex d5003a79e3..83b9de961c 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"strmap.h\"\n+#include \"mem-pool.h\"\n \n static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t    const struct hashmap_entry *entry1,\n@@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map,\n \n void strmap_init(struct strmap *map)\n {\n-\tstrmap_ocd_init(map, 1);\n+\tstrmap_ocd_init(map, NULL, 1);\n }\n \n void strmap_ocd_init(struct strmap *map,\n+\t\t     struct mem_pool *pool,\n \t\t     int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->pool = pool;\n \tmap->strdup_strings = strdup_strings;\n }\n \n@@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_util)\n \tif (!map)\n \t\treturn;\n \n+\tif (!free_util && map->pool)\n+\t\t/* Memory other than util is owned by and freed with the pool */\n+\t\treturn;\n+\n \t/*\n \t * We need to iterate over the hashmap entries and free\n \t * e->key and e->value ourselves; hashmap has no API to\n@@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_util)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_util)\n \t\t\tfree(e->value);\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)e->key);\n-\t\tfree(e);\n+\t\tif (!map->pool) {\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree((char*)e->key);\n+\t\t\tfree(e);\n+\t\t}\n \t}\n }\n \n@@ -84,11 +93,13 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \t\t */\n \t\tconst char *key = str;\n \n-\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n+\t\t\t\t  : xmalloc(sizeof(*entry));\n \t\thashmap_entry_init(&entry->ent, strhash(str));\n \n \t\tif (map->strdup_strings)\n-\t\t\tkey = xstrdup(str);\n+\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n+\t\t\t\t\t: xstrdup(str);\n \t\tentry->key = key;\n \t\tentry->value = data;\n \t\thashmap_add(&map->map, &entry->ent);\n@@ -122,9 +133,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_util)\n \t\treturn;\n \tif (free_util)\n \t\tfree(ret->value);\n-\tif (map->strdup_strings)\n-\t\tfree((char*)ret->key);\n-\tfree(ret);\n+\tif (!map->pool) {\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)ret->key);\n+\t\tfree(ret);\n+\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex 2ad6696950..b93b7c9fd6 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -3,8 +3,10 @@\n \n #include \"hashmap.h\"\n \n+struct mempool;\n struct strmap {\n \tstruct hashmap map;\n+\tstruct mem_pool *pool;\n \tunsigned int strdup_strings:1;\n };\n \n@@ -22,11 +24,12 @@ void strmap_init(struct strmap *map);\n \n /*\n  * Same as strmap_init, but for those who want to control the memory management\n- * carefully instead of using the default of strdup_strings=1.\n+ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n  * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n  * are obsessing over minor details.)\n  */\n void strmap_ocd_init(struct strmap *map,\n+\t\t     struct mem_pool *pool,\n \t\t     int strdup_strings);\n \n /*\n@@ -126,9 +129,10 @@ static inline void strintmap_init(struct strintmap *map)\n }\n \n static inline void strintmap_ocd_init(struct strintmap *map,\n+\t\t\t\t      struct mem_pool *pool,\n \t\t\t\t      int strdup_strings)\n {\n-\tstrmap_ocd_init(&map->map, strdup_strings);\n+\tstrmap_ocd_init(&map->map, pool, strdup_strings);\n }\n \n static inline void strintmap_clear(struct strintmap *map)\n@@ -202,9 +206,10 @@ static inline void strset_init(struct strset *set)\n }\n \n static inline void strset_ocd_init(struct strset *set,\n+\t\t\t\t   struct mem_pool *pool,\n \t\t\t\t   int strdup_strings)\n {\n-\tstrmap_ocd_init(&set->map, strdup_strings);\n+\tstrmap_ocd_init(&set->map, pool, strdup_strings);\n }\n \n static inline void strset_clear(struct strset *set)\n-- \ngitgitgadget\n"},{"id":"407417","messageId":"61b5bf11103a7bd12de8fd066e128c469da3a0a4.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 06/10] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:46Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_empty()\n  * strmap_get_size()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_get_entry()\n\nI suspect the first four are self-explanatory.\n\nstrmap_get_entry() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstrmap_entry that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_entry()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via entry->value\n  * directly update entry->value\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 20 ++++++++++++++++++++\n strmap.h | 38 ++++++++++++++++++++++++++++++++++++++\n 2 files changed, 58 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 4b48d64274..909b9fbedf 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -90,6 +90,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str);\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n@@ -100,3 +105,18 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_strmap_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_util)\n+{\n+\tstruct strmap_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (!ret)\n+\t\treturn;\n+\tif (free_util)\n+\t\tfree(ret->value);\n+\tif (map->strdup_strings)\n+\t\tfree((char*)ret->key);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 493d19cbc0..e49d020970 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -42,6 +42,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -54,4 +60,36 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map) == 0;\n+}\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n+\t\t\t\t\t\t   OFFSETOF_VAR(var, ent)); \\\n+\t\tvar; \\\n+\t\tvar = hashmap_iter_next_entry_offset(iter, \\\n+\t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"407418","messageId":"cc8d702f98f94fd9202d227d19be43a912467e84.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 08/10] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:48Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nA note on the name: if anyone has a better name suggestion than\nstrintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\nnot been able to come up with a better name.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 ++++++++\n strmap.h | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 91 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 47cbf11ec7..d5003a79e3 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -126,3 +126,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_util)\n \t\tfree((char*)ret->key);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->value;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 5bb7650d65..fe15e74b78 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -98,4 +98,84 @@ static inline unsigned int strmap_get_size(struct strmap *map)\n \t\tvar = hashmap_iter_next_entry_offset(iter, \\\n \t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n \n+\n+/*\n+ * strintmap:\n+ *    A map of string -> int, typecasting the void* of strmap to an int.\n+ *\n+ * Primary differences:\n+ *    1) Since the void* value is just an int in disguise, there is no value\n+ *       to free.  (Thus one fewer argument to strintmap_clear)\n+ *    2) strintmap_get() returns an int; it also requires an extra parameter to\n+ *       be specified so it knows what value to return if the underlying strmap\n+ *       has not key matching the given string.\n+ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n+ *       instead.\n+ */\n+\n+struct strintmap {\n+\tstruct strmap map;\n+};\n+\n+#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n+\n+static inline void strintmap_init(struct strintmap *map)\n+{\n+\tstrmap_init(&map->map);\n+}\n+\n+static inline void strintmap_ocd_init(struct strintmap *map,\n+\t\t\t\t      int strdup_strings)\n+{\n+\tstrmap_ocd_init(&map->map, strdup_strings);\n+}\n+\n+static inline void strintmap_clear(struct strintmap *map)\n+{\n+\tstrmap_clear(&map->map, 0);\n+}\n+\n+static inline void strintmap_partial_clear(struct strintmap *map)\n+{\n+\tstrmap_partial_clear(&map->map, 0);\n+}\n+\n+static inline int strintmap_contains(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_contains(&map->map, str);\n+}\n+\n+static inline void strintmap_remove(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_remove(&map->map, str, 0);\n+}\n+\n+static inline int strintmap_empty(struct strintmap *map)\n+{\n+\treturn strmap_empty(&map->map);\n+}\n+\n+static inline unsigned int strintmap_get_size(struct strintmap *map)\n+{\n+\treturn strmap_get_size(&map->map);\n+}\n+\n+static inline int strintmap_get(struct strintmap *map, const char *str,\n+\t\t\t\tint default_value)\n+{\n+\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n+\tif (!result)\n+\t\treturn default_value;\n+\treturn (intptr_t)result->value;\n+}\n+\n+static inline void strintmap_set(struct strintmap *map, const char *str,\n+\t\t\t\t intptr_t v)\n+{\n+\tstrmap_put(&map->map, str, (void *)v);\n+}\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"407419","messageId":"490d3a42add2cc5f0d30db8f2351614294e00121.1602549650.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v2 09/10] strmap: add a strset sub-type","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-10-13T00:40:49Z","receivedAt":"2020-10-13T02:42:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSimilar to adding strintmap for special-casing a string -> int mapping,\nadd a strset type for cases where we really are only interested in using\nstrmap for storing a set rather than a mapping.  In this case, we'll\nalways just store NULL for the value but the different struct type makes\nit clearer than code comments how a variable is intended to be used.\n\nThe difference in usage also results in some differences in API: a few\nthings that aren't necessary or meaningful are dropped (namely, the\nfree_util argument to *_clear(), and the *_get() function), and\nstrset_add() is chosen as the API instead of strset_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.h | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 64 insertions(+)\n\ndiff --git a/strmap.h b/strmap.h\nindex fe15e74b78..2ad6696950 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -178,4 +178,68 @@ static inline void strintmap_set(struct strintmap *map, const char *str,\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n \n+/*\n+ * strset:\n+ *    A set of strings.\n+ *\n+ * Primary differences with strmap:\n+ *    1) The value is always NULL, and ignored.  As there is no value to free,\n+ *       there is one fewer argument to strset_clear\n+ *    2) No strset_get() because there is no value.\n+ *    3) No strset_put(); use strset_add() instead.\n+ */\n+\n+struct strset {\n+\tstruct strmap map;\n+};\n+\n+#define strset_for_each_entry(mystrset, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrset)->map, iter, var)\n+\n+static inline void strset_init(struct strset *set)\n+{\n+\tstrmap_init(&set->map);\n+}\n+\n+static inline void strset_ocd_init(struct strset *set,\n+\t\t\t\t   int strdup_strings)\n+{\n+\tstrmap_ocd_init(&set->map, strdup_strings);\n+}\n+\n+static inline void strset_clear(struct strset *set)\n+{\n+\tstrmap_clear(&set->map, 0);\n+}\n+\n+static inline void strset_partial_clear(struct strset *set)\n+{\n+\tstrmap_partial_clear(&set->map, 0);\n+}\n+\n+static inline int strset_contains(struct strset *set, const char *str)\n+{\n+\treturn strmap_contains(&set->map, str);\n+}\n+\n+static inline void strset_remove(struct strset *set, const char *str)\n+{\n+\treturn strmap_remove(&set->map, str, 0);\n+}\n+\n+static inline int strset_empty(struct strset *set)\n+{\n+\treturn strmap_empty(&set->map);\n+}\n+\n+static inline unsigned int strset_get_size(struct strset *set)\n+{\n+\treturn strmap_get_size(&set->map);\n+}\n+\n+static inline void strset_add(struct strset *set, const char *str)\n+{\n+\tstrmap_put(&set->map, str, NULL);\n+}\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"408736","messageId":"20201030125059.GA3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 01/10] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T12:50:59Z","receivedAt":"2020-10-30T12:51:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:41AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> The existence of hashmap_free() and hashmap_free_entries() confused me,\n> and the docs weren't clear enough.  We are dealing with a map table,\n> entries in that table, and possibly also things each of those entries\n> point to.  I had to consult other source code examples and the\n> implementation.  Add a brief note to clarify the differences.  This will\n> become even more important once we introduce a new\n> hashmap_partial_clear() function which will add the question of whether\n> the table itself has been freed.\n\nThis is a definite improvement, and I don't see any inaccuracies in the\ndescriptions. I do think some re-naming would help in the long run,\nthough. E.g.:\n\n  - hashmap_clear() - remove all entries and de-allocate any\n    hashmap-specific data, but be ready for reuse\n\n  - hashmap_clear_and_free() - ditto, but free the entries themselves\n\n  - hashmap_partial_clear() - remove all entries but don't deallocate\n    table\n\n  - hashmap_partial_clear_and_free() - ditto, but free the entries\n\nSo always call it \"clear\", but allow options in two dimensions (partial\nor not, free entries or not).\n\nThose could be parameters to a single function, but I think it gets a\nlittle ugly because \"and_free\" requires passing in the type of the\nentries in order to find the pointers.\n\nThe \"not\" cases are implied in the names, but hashmap_clear_full() would\nbe OK with me, too.\n\nBut I think in the current scheme that \"free\" is somewhat overloaded,\nand if we end with a \"clear\" and a \"free\" that seems confusing to me.\n\n-Peff\n"},{"id":"408737","messageId":"20201030125120.GB3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"75f17619e964a8a1ab2ecd8267c25c10e5d68bef.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 02/10] hashmap: adjust spacing to fix argument alignment","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T12:51:20Z","receivedAt":"2020-10-30T12:51:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:42AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> No actual code changes; just whitespace adjustments.\n\nObviously good. Thanks for splitting this into its own patch.\n\n-Peff\n"},{"id":"408739","messageId":"20201030133518.GC3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"a686d0758a2652c91363dcf68da7726093d00c23.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/10] hashmap: allow re-use after hashmap_free()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T13:35:18Z","receivedAt":"2020-10-30T13:35:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:43AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Previously, once map->table had been freed, any calls to hashmap_put(),\n> hashmap_get(), or hashmap_remove() would cause a NULL pointer\n> dereference (since hashmap_free_() also zeros the memory; without that\n> zeroing, calling these functions would cause a use-after-free problem).\n> \n> Modify these functions to check for a NULL table and automatically\n> allocate as needed.\n\nUnsurprisingly, I like this direction. The code looks correct to me,\nthough I think you could reduce duplication slightly by checking\nmap->table in find_entry_ptr(). That covers both hashmap_get() and\nhashmap_remove(). But I'm happy either way.\n\n> I also thought about creating a HASHMAP_INIT macro to allow initializing\n> hashmaps on the stack without calling hashmap_init(), but virtually all\n> uses of hashmap specify a usecase-specific equals_function which defeats\n> the utility of such a macro.\n\nThis part I disagree with. If we did:\n\n  #define HASHMAP_INIT(fn, data) = { .cmpfn = cmpfn, cmpfn_data = data }\n\nthen many callers could avoid handling the lazy-init themselves. E.g.:\n\n attr.c | 16 +++-------------\n 1 file changed, 3 insertions(+), 13 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a826b2ef1f..55a2783f1b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -57,7 +57,9 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n  * is a singleton object which is shared between threads.\n  * Access to this dictionary must be surrounded with a mutex.\n  */\n-static struct attr_hashmap g_attr_hashmap;\n+static struct attr_hashmap g_attr_hashmap = {\n+\tHASHMAP_INIT(attr_hash_entry_cmp, NULL)\n+};\n \n /* The container for objects stored in \"struct attr_hashmap\" */\n struct attr_hash_entry {\n@@ -80,12 +82,6 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n \treturn (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n }\n \n-/* Initialize an 'attr_hashmap' object */\n-static void attr_hashmap_init(struct attr_hashmap *map)\n-{\n-\thashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n-}\n-\n /*\n  * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n  * If there is no matching entry, return NULL.\n@@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n \tstruct attr_hash_entry k;\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \thashmap_entry_init(&k.ent, memhash(key, keylen));\n \tk.key = key;\n \tk.keylen = keylen;\n@@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n {\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \te = xmalloc(sizeof(struct attr_hash_entry));\n \thashmap_entry_init(&e->ent, memhash(key, keylen));\n \te->key = key;\n"},{"id":"408740","messageId":"20201030134131.GD3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"061ab45a9bdae5352f62fa6e81bb21ae5c94b8df.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 04/10] hashmap: introduce a new hashmap_partial_clear()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T13:41:31Z","receivedAt":"2020-10-30T13:41:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:44AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> merge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\n> reset_maps() in merge-ort was taking about 12% of overall runtime in my\n> testcase involving rebasing 35 patches of linux.git across a big rename.\n> reset_maps() was calling hashmap_free() followed by hashmap_init(),\n> meaning that not only was it freeing all the memory associated with each\n> of the strmaps just to immediately allocate a new array again, it was\n> allocating a new array that wasy likely smaller than needed (thus\n\ns/wasy/was/\n\n> resulting in later need to rehash things).  The ending size of the map\n> table on the previous commit was likely almost perfectly sized for the\n> next commit we wanted to pick, and not dropping and reallocating the\n> table immediately is a win.\n> \n> Add some new API to hashmap to clear a hashmap of entries without\n> freeing map->table (and instead only zeroing it out like alloc_table()\n> would do, along with zeroing the count of items in the table and the\n> shrink_at field).\n\nThis seems like a reasonable optimization to make, and doesn't make the\nAPI significantly more complicated. I'd expect the allocation of actual\nentry objects to dwarf the table allocation, but I guess:\n\n  - you'll deal with the individual entries later using a mempool\n\n  - it's not just the allocation, but the re-insertion of the entries as\n    we grow\n\nIt would be nice if we had some actual perf numbers to report here, so\nwe could know exactly how much it was buying us. But I guess things are\na bit out-of-order there. You want to do this series first and then\nbuild merge-ort on top as a user. We could introduce the basic data\nstructure first, then merge-ort, and then start applying optimizations\nwith real-world measurements. But I'm not sure it's worth the amount of\ntime you'd have to spend to reorganize in that way.\n\n>  hashmap.c | 39 +++++++++++++++++++++++++++------------\n>  hashmap.h | 13 ++++++++++++-\n\nThe implementation itself looks correct to me. I already mentioned my\nthoughts on naming in patch 1.\n\n-Peff\n"},{"id":"408743","messageId":"20201030141233.GE3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"5c7507f55b09e24c0bfe87cc3df06213cfd1235b.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 05/10] strmap: new utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:12:33Z","receivedAt":"2020-10-30T14:12:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:45AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Add strmap as a new struct and associated utility functions,\n> specifically for hashmaps that map strings to some value.  The API is\n> taken directly from Peff's proposal at\n> https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nThis looks overall sane to me. I mentioned elsewhere that we could be\nusing FLEXPTR_ALLOC to save an extra allocation. I think it's easy and\nworth doing here, as the logic would be completely contained within\nstrmap_put():\n\n  if (strdup_strings)\n\tFLEXPTR_ALLOC_STR(entry, key, str);\n  else {\n\tentry = xmalloc(sizeof(*entry));\n\tentry->key = str;\n  }\n\nAnd free_entries() then doesn't even have to care about strdup_strings.\n\n> A couple of items of note:\n> \n>   * Similar to string-list, I have a strdup_strings setting.  However,\n>     unlike string-list, strmap_init() does not take a parameter for this\n>     setting and instead automatically sets it to 1; callers who want to\n>     control this detail need to instead call strmap_ocd_init().\n\nThat seems reasonable. It could just be a parameter, but I like that you\npush people in the direction of doing the simple and safe thing, rather\nthan having them wonder whether they ought to set strdup_strings or not.\n\n>   * I do not have a STRMAP_INIT macro.  I could possibly add one, but\n>       #define STRMAP_INIT { { NULL, cmp_str_entry, NULL, 0, 0, 0, 0, 0 }, 1 }\n>     feels a bit unwieldy and possibly error-prone in terms of future\n>     expansion of the hashmap struct.  The fact that cmp_str_entry needs to\n>     be in there prevents us from passing all zeros for the hashmap, and makes\n>     me worry that STRMAP_INIT would just be more trouble than it is worth.\n\nYou can actually omit everything after cmp_str_entry, and those fields\nwould all get zero-initialized. But we also allow C99 designed\ninitializers these days. Coupled with the HASHMAP_INIT() I mentioned in\nthe earlier email, you'd have:\n\n  #define STRMAP_INIT { \\\n\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL), \\\n\t\t.strdup_strings = 1, \\\n\t  }\n\nwhich seems pretty maintainable.\n\n> +static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n> +\t\t\t    const struct hashmap_entry *entry1,\n> +\t\t\t    const struct hashmap_entry *entry2,\n> +\t\t\t    const void *keydata)\n> +{\n> +\tconst struct strmap_entry *e1, *e2;\n> +\n> +\te1 = container_of(entry1, const struct strmap_entry, ent);\n> +\te2 = container_of(entry2, const struct strmap_entry, ent);\n> +\treturn strcmp(e1->key, e2->key);\n> +}\n\nI expected to use keydata here, but it's pretty easy to make a fake\nstrmap_entry because of the use of the \"key\" pointer. So that makes\nsense.\n\n> +static void strmap_free_entries_(struct strmap *map, int free_util)\n\nYou use the term \"value\" for the mapped-to value in this iteration. So\nperhaps free_values here (and in other functions) would be a better\nname?\n\n> +\t/*\n> +\t * We need to iterate over the hashmap entries and free\n> +\t * e->key and e->value ourselves; hashmap has no API to\n> +\t * take care of that for us.  Since we're already iterating over\n> +\t * the hashmap, though, might as well free e too and avoid the need\n> +\t * to make some call into the hashmap API to do that.\n> +\t */\n> +\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n> +\t\tif (free_util)\n> +\t\t\tfree(e->value);\n> +\t\tif (map->strdup_strings)\n> +\t\t\tfree((char*)e->key);\n> +\t\tfree(e);\n> +\t}\n> +}\n\nYep, makes sense.\n\n> +void strmap_clear(struct strmap *map, int free_util)\n> +{\n> +\tstrmap_free_entries_(map, free_util);\n> +\thashmap_free(&map->map);\n> +}\n\nThis made me wonder about a partial_clear(), but it looks like that\ncomes later.\n\n> +void *strmap_put(struct strmap *map, const char *str, void *data)\n> +{\n> +\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n> +\tvoid *old = NULL;\n> +\n> +\tif (entry) {\n> +\t\told = entry->value;\n> +\t\tentry->value = data;\n\nHere's a weird hypothetical. If strdup_strings is not set and I do:\n\n  const char *one = xstrdup(\"foo\");\n  const char *two = xstrdup(\"foo\");\n\n  hashmap_put(map, one, x);\n  hashmap_put(map, two, y);\n\nit's clear that the value should be pointing to \"y\" afterwards (and you\nreturn \"x\" so the caller can free it or whatever, good).\n\nBut which key should the entry be pointing to? The old one or the new\none? I'm trying and failing to think of a case where it would matter.\nCertainly I could add a free() to the toy above where it would, but it\nfeels like a real caller would have to have pretty convoluted memory\nlifetime semantics for it to make a difference.\n\nSo I'm not really asking for a particular behavior, but just bringing it\nup in case you can think of something relevant.\n\n> +\t} else {\n> +\t\t/*\n> +\t\t * We won't modify entry->key so it really should be const.\n> +\t\t */\n> +\t\tconst char *key = str;\n\nThe \"should be\" here confused me. It _is_ const. I'd probably just\ndelete the comment entirely, but perhaps:\n\n  /*\n   * We'll store a const pointer. For non-duplicated strings, they belong\n   * to the caller and we received them as const in the first place. For\n   * our duplicated ones, they do point to memory we own, but they're\n   * still conceptually constant within the lifetime of an entry.\n   */\n\nThough it might make more sense in the struct definition, not here.\n\n> +void *strmap_get(struct strmap *map, const char *str)\n> +{\n> +\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n> +\treturn entry ? entry->value : NULL;\n> +}\n\nJust noting that the caller can't tell the difference between \"no such\nentry\" and \"the entry is storing NULL\". I think the simplicity offered\nby this interface makes it worth having (and being the primary one). If\nsome caller really needs to tell the difference between the two, we can\nadd another function later.\n\nObviously they could use strmap_contains(), but that would mean two hash\nlookups.\n\n> +/*\n> + * Same as strmap_init, but for those who want to control the memory management\n> + * carefully instead of using the default of strdup_strings=1.\n> + * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n> + * are obsessing over minor details.)\n> + */\n> +void strmap_ocd_init(struct strmap *map,\n> +\t\t     int strdup_strings);\n\nI'm not personally bothered by this name, but I wonder if some people\nmay be (because they have or know somebody who actually has OCD).\n\nPerhaps strmap_init_with_options() would be a better name? It likewise\nwould extend well if we want to add other non-default options later.\n\n-Peff\n"},{"id":"408744","messageId":"20201030142350.GF3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"61b5bf11103a7bd12de8fd066e128c469da3a0a4.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/10] strmap: add more utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:23:50Z","receivedAt":"2020-10-30T14:23:53Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:46AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> strmap_get_entry() is similar to strmap_get() except that instead of just\n> returning the void* value that the string maps to, it returns the\n> strmap_entry that contains both the string and the void* value (or\n> NULL if the string isn't in the map).  This is helpful because it avoids\n> multiple lookups, e.g. in some cases a caller would need to call:\n>   * strmap_contains() to check that the map has an entry for the string\n>   * strmap_get() to get the void* value\n>   * <do some work to update the value>\n>   * strmap_put() to update/overwrite the value\n\nOh, I guess I should have read ahead when responding to the last patch. :)\n\nYes, this function makes perfect sense to have (along with the simpler\nalternatives for the callers that don't need this complexity).\n\n>  strmap.c | 20 ++++++++++++++++++++\n>  strmap.h | 38 ++++++++++++++++++++++++++++++++++++++\n\nThe implementation all looks pretty straight-forward.\n\n> +void strmap_remove(struct strmap *map, const char *str, int free_util)\n> +{\n> +\tstruct strmap_entry entry, *ret;\n> +\thashmap_entry_init(&entry.ent, strhash(str));\n> +\tentry.key = str;\n> +\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n> +\tif (!ret)\n> +\t\treturn;\n> +\tif (free_util)\n> +\t\tfree(ret->value);\n> +\tif (map->strdup_strings)\n> +\t\tfree((char*)ret->key);\n> +\tfree(ret);\n> +}\n\nAnother spot that would be simplified by using FLEXPTRs. :)\n\n> +/*\n> + * Return whether the strmap is empty.\n> + */\n> +static inline int strmap_empty(struct strmap *map)\n> +{\n> +\treturn hashmap_get_size(&map->map) == 0;\n> +}\n\nMaybe:\n\n  return strmap_get_size(&map) == 0;\n\nwould be slightly simpler (and more importantly, show callers the\nequivalence between the two).\n\n> +/*\n> + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n> + */\n> +#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n> +\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n> +\t\t\t\t\t\t   OFFSETOF_VAR(var, ent)); \\\n> +\t\tvar; \\\n> +\t\tvar = hashmap_iter_next_entry_offset(iter, \\\n> +\t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n\nMakes sense. This is like hashmap_for_each_entry, but we don't need\nanyone to tell us the offset of \"ent\" within the struct.\n\nI suspect we need the same \"var = NULL\" that hashmap recently got in\n0ad621f61e (hashmap_for_each_entry(): workaround MSVC's runtime check\nfailure #3, 2020-09-30). Alternatively, I think you could drop\nOFFSETOF_VAR completely in favor offsetof(struct strmap_entry, ent).\n\nIn fact, since we know the correct type for \"var\", we _could_ declare it\nourselves in a new block enclosing the loop. But that is probably making\nthe code too magic; people reading the code would say \"huh? where is\nentry declared?\".\n\n-Peff\n"},{"id":"408745","messageId":"20201030142707.GG3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"2ebce0c5d82b87fa9c9ef5dcefc0ac2701654f3b.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/10] strmap: enable faster clearing and reusing of strmaps","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:27:07Z","receivedAt":"2020-10-30T14:27:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:47AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> When strmaps are used heavily, such as is done by my new merge-ort\n> algorithm, and strmaps need to be cleared but then re-used (because of\n> e.g. picking multiple commits to cherry-pick, or due to a recursive\n> merge having several different merges while recursing), free-ing and\n> reallocating map->table repeatedly can add up in time, especially since\n> it will likely be reallocated to a much smaller size but the previous\n> merge provides a good guide to the right size to use for the next merge.\n> \n> Introduce strmap_partial_clear() to take advantage of this type of\n> situation; it will act similar to strmap_clear() except that\n> map->table's entries are zeroed instead of map->table being free'd.\n> Making use of this function reduced the cost of reset_maps() by about\n> 20% in mert-ort, and dropped the overall runtime of my rebase testcase\n> by just under 2%.\n\nOh, these were the real numbers I was looking for earlier. :)\n\nOf course it's a little confusing because reset_maps() doesn't exist yet\nin the code base this is being applied on, but I can live with that.\n\n> +/*\n> + * Similar to strmap_clear() but leaves map->map->table allocated and\n> + * pre-sized so that subsequent uses won't need as many rehashings.\n> + */\n> +void strmap_partial_clear(struct strmap *map, int free_values);\n\nOh good, you anticipated my free_values suggestion from earlier. But...\n\n> +void strmap_partial_clear(struct strmap *map, int free_util)\n> +{\n> +\tstrmap_free_entries_(map, free_util);\n> +\thashmap_partial_clear(&map->map);\n> +}\n\n...the implementation didn't catch up.\n\nOther than that the patch looks obviously correct.\n\n-Peff\n"},{"id":"408746","messageId":"20201030143906.GH3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"cc8d702f98f94fd9202d227d19be43a912467e84.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 08/10] strmap: add functions facilitating use as a string->int map","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:39:06Z","receivedAt":"2020-10-30T14:39:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:48AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Although strmap could be used as a string->int map, one either had to\n> allocate an int for every entry and then deallocate later, or one had to\n> do a bunch of casting between (void*) and (intptr_t).\n> \n> Add some special functions that do the casting.  Also, rename put->set\n> for such wrapper functions since 'put' implied there may be some\n> deallocation needed if the string was already found in the map, which\n> isn't the case when we're storing an int value directly in the void*\n> slot instead of using the void* slot as a pointer to data.\n\nI think this is worth doing. That kind of casting is an implementation\ndetail, and it's nice for callers not to have to see it.\n\nYou might want to mention that this _could_ be done as just accessors to\nstrmap, but using a separate struct provides type safety against\nmisusing pointers as integers or vice versa.\n\n> A note on the name: if anyone has a better name suggestion than\n> strintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\n> not been able to come up with a better name.\n\nI still don't have a better suggestion on the name. Another convention\ncould be to name map types as \"map_from_to\". So \"struct map_str_int\".\nBut it's pretty ugly, and strmap would become \"map_str_ptr\" or\nsomething. As ugly as \"strintmap\" is, I like it better.\n\n> +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n> +{\n> +\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n> +\tif (entry) {\n> +\t\tintptr_t *whence = (intptr_t*)&entry->value;\n> +\t\t*whence += amt;\n> +\t}\n> +\telse\n> +\t\tstrintmap_set(map, str, amt);\n> +}\n\nIncrementing a missing entry auto-vivifies it at 0.  That makes perfect\nsense, but might be worth noting above the function in the header file.\n\nThough maybe it's a little weird since strintmap_get() takes a default\nvalue. Why don't we use that here? I'd have to see how its used, but\nwould it make sense to set a default value when initializing the map,\nrather than providing it on each call?\n\n> +/*\n> + * strintmap:\n> + *    A map of string -> int, typecasting the void* of strmap to an int.\n\nAre the size and signedness of an int flexible enough for all uses?\n\nI doubt the standard makes any promises about the relationship between\nintptr_t and int, but I'd be surprised if any modern platform has an\nintptr_t that isn't at least as big as an int (on most 32-bit platforms\nthey'll be the same, and on 64-bit ones intptr_t is strictly bigger).\n\nWould any callers care about using the full 32-bits, though? I.e., would\nthey prefer casting through uintptr_t to an \"unsigned int\"?\n\n-Peff\n"},{"id":"408747","messageId":"20201030144418.GI3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"490d3a42add2cc5f0d30db8f2351614294e00121.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/10] strmap: add a strset sub-type","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:44:18Z","receivedAt":"2020-10-30T14:44:21Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:49AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Similar to adding strintmap for special-casing a string -> int mapping,\n> add a strset type for cases where we really are only interested in using\n> strmap for storing a set rather than a mapping.  In this case, we'll\n> always just store NULL for the value but the different struct type makes\n> it clearer than code comments how a variable is intended to be used.\n> \n> The difference in usage also results in some differences in API: a few\n> things that aren't necessary or meaningful are dropped (namely, the\n> free_util argument to *_clear(), and the *_get() function), and\n> strset_add() is chosen as the API instead of strset_put().\n\nThat all makes sense.\n\nWe're wasting 8 bytes of NULL pointer for each entry, but it's unlikely\nto be all that important. If we later find a case where we think it\nmatters, we can always refactor the type not to depend on strmap.\n\nI'd want a strset_check_and_add() to match what I used recently in\nshortlog.h. Maybe strset_contains_and_add() would be a better name to\nmatch the individual functions here. I dunno (it actually seems\nclunkier).\n\n-Peff\n"},{"id":"408748","messageId":"20201030145604.GJ3277724@coredump.intra.peff.net","threadId":"54087","inReplyTo":"eca4f1ddbaa1cd8dc3fc64dbe9206af538cb317d.1602549650.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/10] strmap: enable allocations to come from a mem_pool","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-10-30T14:56:04Z","receivedAt":"2020-10-30T14:56:08Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Oct 13, 2020 at 12:40:50AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> For heavy users of strmaps, allowing the keys and entries to be\n> allocated from a memory pool can provide significant overhead savings.\n> Add an option to strmap_ocd_init() to specify a memory pool.\n\nSo this one interacts badly with my FLEXPTR suggestion.\n\nI guess it provides most of the benefit that FLEXPTR would, because\nwe're getting both the entries and the strings from the mempool. Which\nreally ends up being an almost identical memory layout, since the\nmempool presumably just gives you the N bytes for the string right after\nthe last thing you allocated, which would be the struct.\n\nThe only downside is that if you don't want to use the mempool (e.g.,\nbecause you might actually strmap_remove() things), you don't get the\nadvantage.\n\nI think we could fall back to a FLEXPTR when there's no mempool (or even\nwhen there is, though you'd be on your own to reimplement the\ncomputation parts of FLEXPTR_ALLOC). I'm not sure how ugly it would end\nup.\n\nI haven't used our mem_pool before, but the code all looks quite\nstraightforward to me. I guess the caller is responsible for\nde-allocating the mempool, which makes sense. It would be nice to see\nreal numbers on how much this helps, but again, you might not have the\ncommits in the right order to easily find out.\n\n-Peff\n"},{"id":"408754","messageId":"CABPp-BHRSottxaYO+Ot5tPpNCf4scxmtwBLZicMJ4ChNEhmGHA@mail.gmail.com","threadId":"54087","inReplyTo":"20201030133518.GC3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 03/10] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T15:37:42Z","receivedAt":"2020-10-30T15:37:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 6:35 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:43AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > Previously, once map->table had been freed, any calls to hashmap_put(),\n> > hashmap_get(), or hashmap_remove() would cause a NULL pointer\n> > dereference (since hashmap_free_() also zeros the memory; without that\n> > zeroing, calling these functions would cause a use-after-free problem).\n> >\n> > Modify these functions to check for a NULL table and automatically\n> > allocate as needed.\n>\n> Unsurprisingly, I like this direction. The code looks correct to me,\n> though I think you could reduce duplication slightly by checking\n> map->table in find_entry_ptr(). That covers both hashmap_get() and\n> hashmap_remove(). But I'm happy either way.\n>\n> > I also thought about creating a HASHMAP_INIT macro to allow initializing\n> > hashmaps on the stack without calling hashmap_init(), but virtually all\n> > uses of hashmap specify a usecase-specific equals_function which defeats\n> > the utility of such a macro.\n>\n> This part I disagree with. If we did:\n>\n>   #define HASHMAP_INIT(fn, data) = { .cmpfn = cmpfn, cmpfn_data = data }\n>\n> then many callers could avoid handling the lazy-init themselves. E.g.:\n\nAh, gotcha.  That makes sense to me.  Given that 43 out of 47 callers\nof hashmap_init use cmpfn_data = NULL, should I shorten it to just one\nparameter for the macro, and let the four special cases keep calling\nhashmap_init() to specify a non-NULL cmpfn_data?\n\n>\n>  attr.c | 16 +++-------------\n>  1 file changed, 3 insertions(+), 13 deletions(-)\n>\n> diff --git a/attr.c b/attr.c\n> index a826b2ef1f..55a2783f1b 100644\n> --- a/attr.c\n> +++ b/attr.c\n> @@ -57,7 +57,9 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n>   * is a singleton object which is shared between threads.\n>   * Access to this dictionary must be surrounded with a mutex.\n>   */\n> -static struct attr_hashmap g_attr_hashmap;\n> +static struct attr_hashmap g_attr_hashmap = {\n> +       HASHMAP_INIT(attr_hash_entry_cmp, NULL)\n> +};\n>\n>  /* The container for objects stored in \"struct attr_hashmap\" */\n>  struct attr_hash_entry {\n> @@ -80,12 +82,6 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n>         return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n>  }\n>\n> -/* Initialize an 'attr_hashmap' object */\n> -static void attr_hashmap_init(struct attr_hashmap *map)\n> -{\n> -       hashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n> -}\n> -\n>  /*\n>   * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n>   * If there is no matching entry, return NULL.\n> @@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n>         struct attr_hash_entry k;\n>         struct attr_hash_entry *e;\n>\n> -       if (!map->map.tablesize)\n> -               attr_hashmap_init(map);\n> -\n>         hashmap_entry_init(&k.ent, memhash(key, keylen));\n>         k.key = key;\n>         k.keylen = keylen;\n> @@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n>  {\n>         struct attr_hash_entry *e;\n>\n> -       if (!map->map.tablesize)\n> -               attr_hashmap_init(map);\n> -\n>         e = xmalloc(sizeof(struct attr_hash_entry));\n>         hashmap_entry_init(&e->ent, memhash(key, keylen));\n>         e->key = key;\n"},{"id":"408756","messageId":"CABPp-BEV6ZNsA_qqV67P=W4gASLGoggoR4wP2R6kf0eNfcsDCw@mail.gmail.com","threadId":"54087","inReplyTo":"20201030134131.GD3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 04/10] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T16:03:38Z","receivedAt":"2020-10-30T16:03:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 6:41 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:44AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > merge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\n> > reset_maps() in merge-ort was taking about 12% of overall runtime in my\n> > testcase involving rebasing 35 patches of linux.git across a big rename.\n> > reset_maps() was calling hashmap_free() followed by hashmap_init(),\n> > meaning that not only was it freeing all the memory associated with each\n> > of the strmaps just to immediately allocate a new array again, it was\n> > allocating a new array that wasy likely smaller than needed (thus\n>\n> s/wasy/was/\n\nThanks; will fix.\n\n> > resulting in later need to rehash things).  The ending size of the map\n> > table on the previous commit was likely almost perfectly sized for the\n> > next commit we wanted to pick, and not dropping and reallocating the\n> > table immediately is a win.\n> >\n> > Add some new API to hashmap to clear a hashmap of entries without\n> > freeing map->table (and instead only zeroing it out like alloc_table()\n> > would do, along with zeroing the count of items in the table and the\n> > shrink_at field).\n>\n> This seems like a reasonable optimization to make, and doesn't make the\n> API significantly more complicated. I'd expect the allocation of actual\n> entry objects to dwarf the table allocation, but I guess:\n>\n>   - you'll deal with the individual entries later using a mempool\n>\n>   - it's not just the allocation, but the re-insertion of the entries as\n>     we grow\n>\n> It would be nice if we had some actual perf numbers to report here, so\n> we could know exactly how much it was buying us. But I guess things are\n> a bit out-of-order there. You want to do this series first and then\n> build merge-ort on top as a user. We could introduce the basic data\n> structure first, then merge-ort, and then start applying optimizations\n> with real-world measurements. But I'm not sure it's worth the amount of\n> time you'd have to spend to reorganize in that way.\n\nYeah, the perf benefits didn't really come until I added a\nstrmap_clear() based on this, so as you discovered I put perf numbers\nin patch 7 of this series.  Should I add a mention of the later commit\nmessage at this point in the series?\n\n> >  hashmap.c | 39 +++++++++++++++++++++++++++------------\n> >  hashmap.h | 13 ++++++++++++-\n>\n> The implementation itself looks correct to me. I already mentioned my\n> thoughts on naming in patch 1.\n\nI'll circle back to that when I comment on patch 1...\n"},{"id":"408758","messageId":"CABPp-BGTjUdgmiEneVNsLWyODZtH-d+x6uTFfwbOtyba3K76HQ@mail.gmail.com","threadId":"54087","inReplyTo":"20201030141233.GE3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 05/10] strmap: new utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T16:26:02Z","receivedAt":"2020-10-30T16:26:16Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 7:12 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:45AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > Add strmap as a new struct and associated utility functions,\n> > specifically for hashmaps that map strings to some value.  The API is\n> > taken directly from Peff's proposal at\n> > https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n>\n> This looks overall sane to me. I mentioned elsewhere that we could be\n> using FLEXPTR_ALLOC to save an extra allocation. I think it's easy and\n> worth doing here, as the logic would be completely contained within\n> strmap_put():\n>\n>   if (strdup_strings)\n>         FLEXPTR_ALLOC_STR(entry, key, str);\n>   else {\n>         entry = xmalloc(sizeof(*entry));\n>         entry->key = str;\n>   }\n>\n> And free_entries() then doesn't even have to care about strdup_strings.\n\nYeah, as you noted in your review of 10/10 this idea wouldn't play\nwell with the later mem_pool changes.\n\n> > A couple of items of note:\n> >\n> >   * Similar to string-list, I have a strdup_strings setting.  However,\n> >     unlike string-list, strmap_init() does not take a parameter for this\n> >     setting and instead automatically sets it to 1; callers who want to\n> >     control this detail need to instead call strmap_ocd_init().\n>\n> That seems reasonable. It could just be a parameter, but I like that you\n> push people in the direction of doing the simple and safe thing, rather\n> than having them wonder whether they ought to set strdup_strings or not.\n\nWell, in my first round of the series where I did make it a parameter\nyou balked pretty loudly.  ;-)\n\n> >   * I do not have a STRMAP_INIT macro.  I could possibly add one, but\n> >       #define STRMAP_INIT { { NULL, cmp_str_entry, NULL, 0, 0, 0, 0, 0 }, 1 }\n> >     feels a bit unwieldy and possibly error-prone in terms of future\n> >     expansion of the hashmap struct.  The fact that cmp_str_entry needs to\n> >     be in there prevents us from passing all zeros for the hashmap, and makes\n> >     me worry that STRMAP_INIT would just be more trouble than it is worth.\n>\n> You can actually omit everything after cmp_str_entry, and those fields\n> would all get zero-initialized. But we also allow C99 designed\n> initializers these days. Coupled with the HASHMAP_INIT() I mentioned in\n> the earlier email, you'd have:\n>\n>   #define STRMAP_INIT { \\\n>                 .map = HASHMAP_INIT(cmp_strmap_entry, NULL), \\\n>                 .strdup_strings = 1, \\\n>           }\n>\n> which seems pretty maintainable.\n\nMakes sense; will add.\n\n> > +static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n> > +                         const struct hashmap_entry *entry1,\n> > +                         const struct hashmap_entry *entry2,\n> > +                         const void *keydata)\n> > +{\n> > +     const struct strmap_entry *e1, *e2;\n> > +\n> > +     e1 = container_of(entry1, const struct strmap_entry, ent);\n> > +     e2 = container_of(entry2, const struct strmap_entry, ent);\n> > +     return strcmp(e1->key, e2->key);\n> > +}\n>\n> I expected to use keydata here, but it's pretty easy to make a fake\n> strmap_entry because of the use of the \"key\" pointer. So that makes\n> sense.\n>\n> > +static void strmap_free_entries_(struct strmap *map, int free_util)\n>\n> You use the term \"value\" for the mapped-to value in this iteration. So\n> perhaps free_values here (and in other functions) would be a better\n> name?\n\nOops, yes, definitely.\n\n> > +     /*\n> > +      * We need to iterate over the hashmap entries and free\n> > +      * e->key and e->value ourselves; hashmap has no API to\n> > +      * take care of that for us.  Since we're already iterating over\n> > +      * the hashmap, though, might as well free e too and avoid the need\n> > +      * to make some call into the hashmap API to do that.\n> > +      */\n> > +     hashmap_for_each_entry(&map->map, &iter, e, ent) {\n> > +             if (free_util)\n> > +                     free(e->value);\n> > +             if (map->strdup_strings)\n> > +                     free((char*)e->key);\n> > +             free(e);\n> > +     }\n> > +}\n>\n> Yep, makes sense.\n>\n> > +void strmap_clear(struct strmap *map, int free_util)\n> > +{\n> > +     strmap_free_entries_(map, free_util);\n> > +     hashmap_free(&map->map);\n> > +}\n>\n> This made me wonder about a partial_clear(), but it looks like that\n> comes later.\n>\n> > +void *strmap_put(struct strmap *map, const char *str, void *data)\n> > +{\n> > +     struct strmap_entry *entry = find_strmap_entry(map, str);\n> > +     void *old = NULL;\n> > +\n> > +     if (entry) {\n> > +             old = entry->value;\n> > +             entry->value = data;\n>\n> Here's a weird hypothetical. If strdup_strings is not set and I do:\n>\n>   const char *one = xstrdup(\"foo\");\n>   const char *two = xstrdup(\"foo\");\n>\n>   hashmap_put(map, one, x);\n>   hashmap_put(map, two, y);\n>\n> it's clear that the value should be pointing to \"y\" afterwards (and you\n> return \"x\" so the caller can free it or whatever, good).\n>\n> But which key should the entry be pointing to? The old one or the new\n> one? I'm trying and failing to think of a case where it would matter.\n> Certainly I could add a free() to the toy above where it would, but it\n> feels like a real caller would have to have pretty convoluted memory\n> lifetime semantics for it to make a difference.\n>\n> So I'm not really asking for a particular behavior, but just bringing it\n> up in case you can think of something relevant.\n\nI'll keep mulling it over, but I likewise can't currently think of a\ncase where it'd matter.\n\n>\n> > +     } else {\n> > +             /*\n> > +              * We won't modify entry->key so it really should be const.\n> > +              */\n> > +             const char *key = str;\n>\n> The \"should be\" here confused me. It _is_ const. I'd probably just\n> delete the comment entirely, but perhaps:\n>\n>   /*\n>    * We'll store a const pointer. For non-duplicated strings, they belong\n>    * to the caller and we received them as const in the first place. For\n>    * our duplicated ones, they do point to memory we own, but they're\n>    * still conceptually constant within the lifetime of an entry.\n>    */\n>\n> Though it might make more sense in the struct definition, not here.\n\nEither I was (mistakenly) worried about \"I'm going to allocate and\ncopy, but during the copy it isn't actually const\", or this is a\nleftover artifact from some of the other iterations I tried.  Anyway,\nI think this comment isn't useful; I'll just strike it.\n\n> > +void *strmap_get(struct strmap *map, const char *str)\n> > +{\n> > +     struct strmap_entry *entry = find_strmap_entry(map, str);\n> > +     return entry ? entry->value : NULL;\n> > +}\n>\n> Just noting that the caller can't tell the difference between \"no such\n> entry\" and \"the entry is storing NULL\". I think the simplicity offered\n> by this interface makes it worth having (and being the primary one). If\n> some caller really needs to tell the difference between the two, we can\n> add another function later.\n>\n> Obviously they could use strmap_contains(), but that would mean two hash\n> lookups.\n\nYep, addressed later by strmap_get_entry() in another patch, as you\nnoticed in your later review.\n\n> > +/*\n> > + * Same as strmap_init, but for those who want to control the memory management\n> > + * carefully instead of using the default of strdup_strings=1.\n> > + * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n> > + * are obsessing over minor details.)\n> > + */\n> > +void strmap_ocd_init(struct strmap *map,\n> > +                  int strdup_strings);\n>\n> I'm not personally bothered by this name, but I wonder if some people\n> may be (because they have or know somebody who actually has OCD).\n>\n> Perhaps strmap_init_with_options() would be a better name? It likewise\n> would extend well if we want to add other non-default options later.\n\nDoh!  That's going to push a bunch of lines past 80 characters.  Sigh...\n\nIt's probably a better name though; I'll change it.\n"},{"id":"408759","messageId":"CABPp-BEBQ0kAQVRuXRoZnRAm_BmAXUKv9XtC+R4UH0VDZBw6Sg@mail.gmail.com","threadId":"54087","inReplyTo":"20201030142350.GF3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 06/10] strmap: add more utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T16:43:33Z","receivedAt":"2020-10-30T16:43:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 7:23 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:46AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > strmap_get_entry() is similar to strmap_get() except that instead of just\n> > returning the void* value that the string maps to, it returns the\n> > strmap_entry that contains both the string and the void* value (or\n> > NULL if the string isn't in the map).  This is helpful because it avoids\n> > multiple lookups, e.g. in some cases a caller would need to call:\n> >   * strmap_contains() to check that the map has an entry for the string\n> >   * strmap_get() to get the void* value\n> >   * <do some work to update the value>\n> >   * strmap_put() to update/overwrite the value\n>\n> Oh, I guess I should have read ahead when responding to the last patch. :)\n>\n> Yes, this function makes perfect sense to have (along with the simpler\n> alternatives for the callers that don't need this complexity).\n>\n> >  strmap.c | 20 ++++++++++++++++++++\n> >  strmap.h | 38 ++++++++++++++++++++++++++++++++++++++\n>\n> The implementation all looks pretty straight-forward.\n>\n> > +void strmap_remove(struct strmap *map, const char *str, int free_util)\n> > +{\n> > +     struct strmap_entry entry, *ret;\n> > +     hashmap_entry_init(&entry.ent, strhash(str));\n> > +     entry.key = str;\n> > +     ret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n> > +     if (!ret)\n> > +             return;\n> > +     if (free_util)\n> > +             free(ret->value);\n> > +     if (map->strdup_strings)\n> > +             free((char*)ret->key);\n> > +     free(ret);\n> > +}\n>\n> Another spot that would be simplified by using FLEXPTRs. :)\n>\n> > +/*\n> > + * Return whether the strmap is empty.\n> > + */\n> > +static inline int strmap_empty(struct strmap *map)\n> > +{\n> > +     return hashmap_get_size(&map->map) == 0;\n> > +}\n>\n> Maybe:\n>\n>   return strmap_get_size(&map) == 0;\n>\n> would be slightly simpler (and more importantly, show callers the\n> equivalence between the two).\n\nMakes sense; will change it.\n\n> > +/*\n> > + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n> > + */\n> > +#define strmap_for_each_entry(mystrmap, iter, var)   \\\n> > +     for (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n> > +                                                OFFSETOF_VAR(var, ent)); \\\n> > +             var; \\\n> > +             var = hashmap_iter_next_entry_offset(iter, \\\n> > +                                                  OFFSETOF_VAR(var, ent)))\n>\n> Makes sense. This is like hashmap_for_each_entry, but we don't need\n> anyone to tell us the offset of \"ent\" within the struct.\n>\n> I suspect we need the same \"var = NULL\" that hashmap recently got in\n> 0ad621f61e (hashmap_for_each_entry(): workaround MSVC's runtime check\n> failure #3, 2020-09-30). Alternatively, I think you could drop\n> OFFSETOF_VAR completely in favor offsetof(struct strmap_entry, ent).\n>\n> In fact, since we know the correct type for \"var\", we _could_ declare it\n> ourselves in a new block enclosing the loop. But that is probably making\n> the code too magic; people reading the code would say \"huh? where is\n> entry declared?\".\n\nActually, since we know ent is the first entry in strmap, the offset\nis always 0.  So can't we just avoid OFFSETOF_VAR() and offsetof()\nentirely, by just using hashmap_iter_first() and hashmap_iter_next()?\nI'm going to try that.\n"},{"id":"408763","messageId":"CABPp-BFyqTthyBmp5yt+iUniwTi+=y2QcBcmNnnCy=zvyi3Rbw@mail.gmail.com","threadId":"54087","inReplyTo":"20201030143906.GH3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 08/10] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T17:28:51Z","receivedAt":"2020-10-30T17:29:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 7:39 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:48AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > Although strmap could be used as a string->int map, one either had to\n> > allocate an int for every entry and then deallocate later, or one had to\n> > do a bunch of casting between (void*) and (intptr_t).\n> >\n> > Add some special functions that do the casting.  Also, rename put->set\n> > for such wrapper functions since 'put' implied there may be some\n> > deallocation needed if the string was already found in the map, which\n> > isn't the case when we're storing an int value directly in the void*\n> > slot instead of using the void* slot as a pointer to data.\n>\n> I think this is worth doing. That kind of casting is an implementation\n> detail, and it's nice for callers not to have to see it.\n>\n> You might want to mention that this _could_ be done as just accessors to\n> strmap, but using a separate struct provides type safety against\n> misusing pointers as integers or vice versa.\n\nIf I just did it as accessors, it makes it harder for myself and\nothers to remember what my huge piles of strmaps in merge-ort do; I\nfound that it became easier to follow the code and remember what\nthings were doing when some were marked as strmap, some as strintmap,\nand some as strset.\n\n> > A note on the name: if anyone has a better name suggestion than\n> > strintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\n> > not been able to come up with a better name.\n>\n> I still don't have a better suggestion on the name. Another convention\n> could be to name map types as \"map_from_to\". So \"struct map_str_int\".\n> But it's pretty ugly, and strmap would become \"map_str_ptr\" or\n> something. As ugly as \"strintmap\" is, I like it better.\n>\n> > +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n> > +{\n> > +     struct strmap_entry *entry = find_strmap_entry(&map->map, str);\n> > +     if (entry) {\n> > +             intptr_t *whence = (intptr_t*)&entry->value;\n> > +             *whence += amt;\n> > +     }\n> > +     else\n> > +             strintmap_set(map, str, amt);\n> > +}\n>\n> Incrementing a missing entry auto-vivifies it at 0.  That makes perfect\n> sense, but might be worth noting above the function in the header file.\n>\n> Though maybe it's a little weird since strintmap_get() takes a default\n> value. Why don't we use that here? I'd have to see how its used, but\n> would it make sense to set a default value when initializing the map,\n> rather than providing it on each call?\n\nThat probably makes sense.  It turns out there is one strintmap for\nwhich I call strintmap_get() in two different places with different\ndefault values, but I think I can fix that up (one of them really\nneeded -1 as the default, while the other callsite just needed the\ndefault to not accidentally match a specific enum value and 0 was\nconvenient).\n\n>\n> > +/*\n> > + * strintmap:\n> > + *    A map of string -> int, typecasting the void* of strmap to an int.\n>\n> Are the size and signedness of an int flexible enough for all uses?\n\nIf some users want signed values and others want unsigned, I'm not\nsure how we can satisfy both.  Maybe make a struintmap?\n\nPerhaps that could be added later if uses come up for it?  Some of my\nuses need int, the rest of them wouldn't care about int vs unsigned.\n\n> I doubt the standard makes any promises about the relationship between\n> intptr_t and int, but I'd be surprised if any modern platform has an\n> intptr_t that isn't at least as big as an int (on most 32-bit platforms\n> they'll be the same, and on 64-bit ones intptr_t is strictly bigger).\n>\n> Would any callers care about using the full 32-bits, though? I.e., would\n> they prefer casting through uintptr_t to an \"unsigned int\"?\n\nI don't care about the full 32 bits (I'll probably use less than 16),\nbut I absolutely wanted it signed for my uses.  I think it makes sense\nto be signed when using it for an index within an array (-1 for \"not\nfound\" makes sense; using arbitrary large numbers seems really ugly\n(and perhaps buggy) to me).  It also makes sense to me to use -1 as an\ninvalid enum value, though I guess I could technically specify an\nadditional \"INVALID_VALUE\" within the enum and use it as the default.\n\nIf someone does care about the full range of bits up to 64 on relevant\nplatforms, I guess I should make it strintptr_t_map.  But besides the\negregiously ugly name, one advantage of int over intptr_t (or unsigned\nover uintptr_t) is that you can use it in a printf easily:\n   printf(\"Size: %d\\n\", strintmap_get(&foo, 0));\nwhereas if it strintmap_get() returns an intptr_t, then it's a royal\nmess to attempt to portably use it without adding additional manual\ncasts.  Maybe I was just missing something obvious, but I couldn't\nfigure out the %d, %ld, %lld, PRIdMAX, etc. choices and get the\nstatement to compile on all platforms, so I'd always just cast to int\nor unsigned at the time of calling printf.\n"},{"id":"408765","messageId":"CABPp-BH63BtybTqT43PXf4=ruyRwP2SBeVw3632zMfQfizeujw@mail.gmail.com","threadId":"54087","inReplyTo":"20201030144418.GI3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 09/10] strmap: add a strset sub-type","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T18:02:49Z","receivedAt":"2020-10-30T18:03:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 7:44 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:49AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Similar to adding strintmap for special-casing a string -> int mapping,\n> > add a strset type for cases where we really are only interested in using\n> > strmap for storing a set rather than a mapping.  In this case, we'll\n> > always just store NULL for the value but the different struct type makes\n> > it clearer than code comments how a variable is intended to be used.\n> >\n> > The difference in usage also results in some differences in API: a few\n> > things that aren't necessary or meaningful are dropped (namely, the\n> > free_util argument to *_clear(), and the *_get() function), and\n> > strset_add() is chosen as the API instead of strset_put().\n>\n> That all makes sense.\n>\n> We're wasting 8 bytes of NULL pointer for each entry, but it's unlikely\n> to be all that important. If we later find a case where we think it\n> matters, we can always refactor the type not to depend on strmap.\n>\n> I'd want a strset_check_and_add() to match what I used recently in\n> shortlog.h. Maybe strset_contains_and_add() would be a better name to\n> match the individual functions here. I dunno (it actually seems\n> clunkier).\n\nYeah, I'll just go with strset_check_and_add().  :-)\n"},{"id":"408768","messageId":"CABPp-BEY5R79WGsMB1y1MEbcvw8EF5-ADxvDPzL+Hn-PiyobsA@mail.gmail.com","threadId":"54087","inReplyTo":"20201030145604.GJ3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 10/10] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T19:31:13Z","receivedAt":"2020-10-30T19:42:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 7:56 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:50AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > For heavy users of strmaps, allowing the keys and entries to be\n> > allocated from a memory pool can provide significant overhead savings.\n> > Add an option to strmap_ocd_init() to specify a memory pool.\n>\n> So this one interacts badly with my FLEXPTR suggestion.\n>\n> I guess it provides most of the benefit that FLEXPTR would, because\n> we're getting both the entries and the strings from the mempool. Which\n> really ends up being an almost identical memory layout, since the\n> mempool presumably just gives you the N bytes for the string right after\n> the last thing you allocated, which would be the struct.\n>\n> The only downside is that if you don't want to use the mempool (e.g.,\n> because you might actually strmap_remove() things), you don't get the\n> advantage.\n>\n> I think we could fall back to a FLEXPTR when there's no mempool (or even\n> when there is, though you'd be on your own to reimplement the\n> computation parts of FLEXPTR_ALLOC). I'm not sure how ugly it would end\n> up.\n\nYeah, we'd need a mempool-specific reimplementation of FLEXPTR_ALLOC\nwith the mempool, and just avoid using it at all whenever\nstrdup_strings was 0.  Seems slightly ugly, but maybe it wouldn't be\ntoo bad.  I could look into it.\n\n> I haven't used our mem_pool before, but the code all looks quite\n> straightforward to me. I guess the caller is responsible for\n> de-allocating the mempool, which makes sense. It would be nice to see\n> real numbers on how much this helps, but again, you might not have the\n> commits in the right order to easily find out.\n\nAt the time I implemented it, I did grab some numbers.  It varied\nquite a bit between different cases, since a lot of my strmaps are for\ntracking when special cases arise and we can implement various\noptimizations.  Naturally, a usecase which involves heavier use of\nstrmaps will mean greater benefits from using a mempool.  Also, if I\nhad implemented it later, after one rename-related optimization I\nhadn't yet discovered at the time, then it would have shown a larger\nrelative reduction in overall execution time.  Anyway, at the time I\nput the mempool into strmaps and made use of it in relevant places,\none of my rebase testcases saw an almost 5% reduction in overall\nexecution time.  I'm sure it would have been over 5% if I had\nreordered it to come after my final rename optimization.\n"},{"id":"408770","messageId":"CABPp-BGdM0tkQgqRQzzpTOyKPoQVLsppK_EUcthAO8LbtzcTNA@mail.gmail.com","threadId":"54087","inReplyTo":"20201030125059.GA3277724@coredump.intra.peff.net","subject":"Re: [PATCH v2 01/10] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-10-30T19:55:51Z","receivedAt":"2020-10-30T19:56:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Oct 30, 2020 at 5:51 AM Jeff King <peff@peff.net> wrote:\n>\n> On Tue, Oct 13, 2020 at 12:40:41AM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > The existence of hashmap_free() and hashmap_free_entries() confused me,\n> > and the docs weren't clear enough.  We are dealing with a map table,\n> > entries in that table, and possibly also things each of those entries\n> > point to.  I had to consult other source code examples and the\n> > implementation.  Add a brief note to clarify the differences.  This will\n> > become even more important once we introduce a new\n> > hashmap_partial_clear() function which will add the question of whether\n> > the table itself has been freed.\n>\n> This is a definite improvement, and I don't see any inaccuracies in the\n> descriptions. I do think some re-naming would help in the long run,\n> though. E.g.:\n>\n>   - hashmap_clear() - remove all entries and de-allocate any\n>     hashmap-specific data, but be ready for reuse\n>\n>   - hashmap_clear_and_free() - ditto, but free the entries themselves\n>\n>   - hashmap_partial_clear() - remove all entries but don't deallocate\n>     table\n>\n>   - hashmap_partial_clear_and_free() - ditto, but free the entries\n>\n> So always call it \"clear\", but allow options in two dimensions (partial\n> or not, free entries or not).\n>\n> Those could be parameters to a single function, but I think it gets a\n> little ugly because \"and_free\" requires passing in the type of the\n> entries in order to find the pointers.\n>\n> The \"not\" cases are implied in the names, but hashmap_clear_full() would\n> be OK with me, too.\n>\n> But I think in the current scheme that \"free\" is somewhat overloaded,\n> and if we end with a \"clear\" and a \"free\" that seems confusing to me.\n\nHmm...there are quite a few calls to hashmap_free() and\nhashmap_free_entries() throughout the codebase.  I'm wondering if I\nshould make switching these over to your new naming suggestions a\nseparate follow-on series from this one, so that if there are any\nconflicts with other series it doesn't need to hold these first 10\npatches up.\n\nIf I do that, I could also add a patch to convert several callers of\nhashmap_init() to use the new HASHMAP_INIT() macro, and another patch\nto convert shortlog to using my strset instead of its own.\n"},{"id":"408879","messageId":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 01/13] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:01Z","receivedAt":"2020-11-02T18:55:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  We are dealing with a map table,\nentries in that table, and possibly also things each of those entries\npoint to.  I had to consult other source code examples and the\nimplementation.  Add a brief note to clarify the differences.  This will\nbecome even more important once we introduce a new\nhashmap_partial_clear() function which will add the question of whether\nthe table itself has been freed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 31 +++++++++++++++++++++++++++++--\n 1 file changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex b011b394fe..2994dc7a9c 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ *\n+ * instead of\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"408880","messageId":"591161fd781b7666ddaa45694eb20610cc359741.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 02/13] hashmap: adjust spacing to fix argument alignment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:02Z","receivedAt":"2020-11-02T18:55:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo actual code changes; just whitespace adjustments.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 17 +++++++++--------\n hashmap.h | 22 +++++++++++-----------\n 2 files changed, 20 insertions(+), 19 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex 09813e1a46..e44d8a3e85 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size)\n }\n \n static inline int entry_equals(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *e1, const struct hashmap_entry *e2,\n-\t\tconst void *keydata)\n+\t\t\t       const struct hashmap_entry *e1,\n+\t\t\t       const struct hashmap_entry *e2,\n+\t\t\t       const void *keydata)\n {\n \treturn (e1 == e2) ||\n \t       (e1->hash == e2->hash &&\n@@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map,\n }\n \n static inline unsigned int bucket(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *key)\n+\t\t\t\t  const struct hashmap_entry *key)\n {\n \treturn key->hash & (map->tablesize - 1);\n }\n@@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data,\n }\n \n void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n-\t\tconst void *cmpfn_data, size_t initial_size)\n+\t\t  const void *cmpfn_data, size_t initial_size)\n {\n \tunsigned int size = HASHMAP_INITIAL_SIZE;\n \n@@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry)\n+\t\t\t\t       const struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *e = entry->next;\n \tfor (; e; e = e->next)\n@@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n }\n \n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata)\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n \tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n@@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry)\n+\t\t\t\t  struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n \thashmap_add(map, entry);\ndiff --git a/hashmap.h b/hashmap.h\nindex 2994dc7a9c..904f61d6e1 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -228,9 +228,9 @@ struct hashmap {\n  * prevent expensive resizing. If 0, the table is dynamically resized.\n  */\n void hashmap_init(struct hashmap *map,\n-\t\t\t hashmap_cmp_fn equals_function,\n-\t\t\t const void *equals_function_data,\n-\t\t\t size_t initial_size);\n+\t\t  hashmap_cmp_fn equals_function,\n+\t\t  const void *equals_function_data,\n+\t\t  size_t initial_size);\n \n /* internal function for freeing hashmap */\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n@@ -288,7 +288,7 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  * and if it is on stack, you can just let it go out of scope).\n  */\n static inline void hashmap_entry_init(struct hashmap_entry *e,\n-\t\t\t\t\tunsigned int hash)\n+\t\t\t\t      unsigned int hash)\n {\n \te->hash = hash;\n \te->next = NULL;\n@@ -330,8 +330,8 @@ static inline unsigned int hashmap_get_size(struct hashmap *map)\n  * to `hashmap_cmp_fn` to decide whether the entry matches the key.\n  */\n struct hashmap_entry *hashmap_get(const struct hashmap *map,\n-\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\tconst void *keydata);\n+\t\t\t\t  const struct hashmap_entry *key,\n+\t\t\t\t  const void *keydata);\n \n /*\n  * Returns the hashmap entry for the specified hash code and key data,\n@@ -364,7 +364,7 @@ static inline struct hashmap_entry *hashmap_get_from_hash(\n  * call to `hashmap_get` or `hashmap_get_next`.\n  */\n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry);\n+\t\t\t\t       const struct hashmap_entry *entry);\n \n /*\n  * Adds a hashmap entry. This allows to add duplicate entries (i.e.\n@@ -384,7 +384,7 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);\n  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).\n  */\n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry);\n+\t\t\t\t  struct hashmap_entry *entry);\n \n /*\n  * Adds or replaces a hashmap entry contained within @keyvar,\n@@ -406,8 +406,8 @@ struct hashmap_entry *hashmap_put(struct hashmap *map,\n  * Argument explanation is the same as in `hashmap_get`.\n  */\n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata);\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata);\n \n /*\n  * Removes a hashmap entry contained within @keyvar,\n@@ -449,7 +449,7 @@ struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);\n \n /* Initializes the iterator and returns the first entry, if any. */\n static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,\n-\t\tstruct hashmap_iter *iter)\n+\t\t\t\t\t\t       struct hashmap_iter *iter)\n {\n \thashmap_iter_init(map, iter);\n \treturn hashmap_iter_next(iter);\n-- \ngitgitgadget\n\n"},{"id":"408881","messageId":"61f1da3c51b521035ba728c025cd6a28397f8051.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 04/13] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:04Z","receivedAt":"2020-11-02T18:55:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\nclear_or_reinit_internal_opts() in merge-ort was taking about 12% of\noverall runtime in my testcase involving rebasing 35 patches of\nlinux.git across a big rename.  clear_or_reinit_internal_opts() was\ncalling hashmap_free() followed by hashmap_init(), meaning that not only\nwas it freeing all the memory associated with each of the strmaps just\nto immediately allocate a new array again, it was allocating a new array\nthat was likely smaller than needed (thus resulting in later need to\nrehash things).  The ending size of the map table on the previous commit\nwas likely almost perfectly sized for the next commit we wanted to pick,\nand not dropping and reallocating the table immediately is a win.\n\nAdd some new API to hashmap to clear a hashmap of entries without\nfreeing map->table (and instead only zeroing it out like alloc_table()\nwould do, along with zeroing the count of items in the table and the\nshrink_at field).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 39 +++++++++++++++++++++++++++------------\n hashmap.h | 13 ++++++++++++-\n 2 files changed, 39 insertions(+), 13 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex bb7c9979b8..922ed07954 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n \tmap->do_count_items = 1;\n }\n \n+static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct hashmap_entry *e;\n+\n+\thashmap_iter_init(map, &iter);\n+\twhile ((e = hashmap_iter_next(&iter)))\n+\t\t/*\n+\t\t * like container_of, but using caller-calculated\n+\t\t * offset (caller being hashmap_free_entries)\n+\t\t */\n+\t\tfree((char *)e - entry_offset);\n+}\n+\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tif (!map || !map->table)\n+\t\treturn;\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n+\tmemset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));\n+\tmap->shrink_at = 0;\n+\tmap->private_size = 0;\n+}\n+\n void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0) { /* called by hashmap_free_entries */\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct hashmap_entry *e;\n-\n-\t\thashmap_iter_init(map, &iter);\n-\t\twhile ((e = hashmap_iter_next(&iter)))\n-\t\t\t/*\n-\t\t\t * like container_of, but using caller-calculated\n-\t\t\t * offset (caller being hashmap_free_entries)\n-\t\t\t */\n-\t\t\tfree((char *)e - entry_offset);\n-\t}\n+\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\n }\ndiff --git a/hashmap.h b/hashmap.h\nindex 3b0f2bcade..e9430d582a 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -235,7 +235,8 @@ void hashmap_init(struct hashmap *map,\n \t\t  const void *equals_function_data,\n \t\t  size_t initial_size);\n \n-/* internal function for freeing hashmap */\n+/* internal functions for clearing or freeing hashmap */\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n@@ -268,6 +269,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n+/*\n+ * Basically the same as calling hashmap_free() followed by hashmap_init(),\n+ * but doesn't incur the overhead of deallocating and reallocating\n+ * map->table; it leaves map->table allocated and the same size but zeroes\n+ * it out so it's ready for use again as an empty map.  As with\n+ * hashmap_free(), you may need to free the entries yourself before calling\n+ * this function.\n+ */\n+#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n+\n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n  * where @member is the hashmap_entry struct used to associate with @map.\n-- \ngitgitgadget\n\n"},{"id":"408882","messageId":"f2718d036dc5d08c2ed416a5ef98c5730bc38cfc.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 03/13] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:03Z","receivedAt":"2020-11-02T18:55:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nPreviously, once map->table had been freed, any calls to hashmap_put(),\nhashmap_get(), or hashmap_remove() would cause a NULL pointer\ndereference (since hashmap_free_() also zeros the memory; without that\nzeroing, calling these functions would cause a use-after-free problem).\n\nModify these functions to check for a NULL table and automatically\nallocate as needed.\n\nAlso add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the\nstack without calling hashmap_init().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 16 ++++++++++++++--\n hashmap.h |  3 +++\n 2 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex e44d8a3e85..bb7c9979b8 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash)\n \n static void rehash(struct hashmap *map, unsigned int newsize)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tunsigned int i, oldsize = map->tablesize;\n \tstruct hashmap_entry **oldtable = map->table;\n \n@@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize)\n static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,\n \t\tconst struct hashmap_entry *key, const void *keydata)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tstruct hashmap_entry **e = &map->table[bucket(map, key)];\n \twhile (*e && !entry_equals(map, *e, key, keydata))\n \t\te = &(*e)->next;\n@@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n \t\t\t\tconst struct hashmap_entry *key,\n \t\t\t\tconst void *keydata)\n {\n+\tif (!map->table)\n+\t\treturn NULL;\n \treturn *find_entry_ptr(map, key, keydata);\n }\n \n@@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n \n void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n {\n-\tunsigned int b = bucket(map, entry);\n+\tunsigned int b;\n+\n+\tif (!map->table)\n+\t\talloc_table(map, HASHMAP_INITIAL_SIZE);\n \n+\tb = bucket(map, entry);\n \t/* add entry */\n \tentry->next = map->table[b];\n \tmap->table[b] = entry;\n@@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n \t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n-\tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n+\tstruct hashmap_entry **e;\n+\n+\tif (!map->table)\n+\t\treturn NULL;\n+\te = find_entry_ptr(map, key, keydata);\n \tif (!*e)\n \t\treturn NULL;\n \ndiff --git a/hashmap.h b/hashmap.h\nindex 904f61d6e1..3b0f2bcade 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -210,6 +210,9 @@ struct hashmap {\n \n /* hashmap functions */\n \n+#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \\\n+\t\t\t\t .do_count_items = 1 }\n+\n /*\n  * Initializes a hashmap structure.\n  *\n-- \ngitgitgadget\n\n"},{"id":"408884","messageId":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v2.git.git.1602549650.gitgitgadget@gmail.com","subject":"[PATCH v3 00/13] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:00Z","receivedAt":"2020-11-02T18:55:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce a new strmap type (and strintmap and strset), which my new\nmerge backed, merge-ort, uses heavily. (I also made significant use of it in\nmy changes to diffcore-rename). This strmap type was based on Peff's\nproposal from a couple years ago[1], but has additions that I made as I used\nit, and a number of additions/changes suggested by Peff in his reviews. I\nalso start the series off with some changes to hashmap, based on Peff's\nfeedback on v1 & v2.\n\nNOTE: My \"fundamentals of merge-ort implementation\"[2] that depends on this\nseries needs to be updated due to these changes. I'll send a reroll for it\nsoon.\n\nChanges since v2 (almost all of which were suggestions from Peff):\n\n * Added HASHMAP_INIT() and STR*_INIT() macros (and added a patch converting\n   some existing callsites to use them)\n * Introduced the improved hashmap deallocation function names that Peff\n   suggested and updated the codebase (currently merges cleanly with seen,\n   though there's always a risk someone else is introducing a new one, but\n   it's at least clean right now)\n * Renamed free_util -> free_values, everywhere this time\n * Renamed strmap_ocd_init() -> strmap_init_with_options(). Similar for the\n   other str* subtypes.\n * Implemented strmap_empty() on top of strmap_get_size() instead of\n   hashmap_get_size()\n * Avoided the OFFSETOF_VAR initialization-on-windows concerns by just not\n   using the macro; for strmap_entry, the offset is always 0\n * Stored the default_value for a strintmap in the strintmap rather than\n   requiring it at every call to strintmap_get(). Updated strintmap_incr()\n   to make use of it as well.\n * Add a strset_check_and_add() function to the API\n * Added an extra patch at the end to take advantage of FLEXPTR_ALLOC_STR in\n   the default case to avoid an extra allocation and free.\n * Tweaked some commit messages, fixed a few more argument-alignment issues,\n   removed a bad comment\n\nThings that I'm still unsure about:\n\n * strintmap_init() takes a default_value parameter, as suggested by Peff.\n   But this makes the function name strintmap_init_with_options() weird,\n   because strintmap_init() already takes one option, so it seems like the\n   name needs to replace \"options\" with \"more_options\". But that's kinda\n   ugly too. I'm guessing strintmap_init_with_options() is fine as-is, but\n   I'm wondering if anyone else thinks it looks weird and if so if there is\n   anything I should do about it.\n\nThings Peff mentioned on v2 that I did NOT do:\n\n * Peff brought up some questions about mapping strintmap to an int rather\n   than an unsigned or intptr_t. I discussed my rationale in the thread\n\nThings Peff mentioned on v1 that are still not included and which Peff\ndidn't comment on for v2, but which may still be worth mentioning again:\n\n * Peff brought up the idea of having a free_values member instead of having\n   a free_values parameter to strmap_clear(). That'd just mean moving the\n   parameter from strmap_clear() to strmap_init() and would be easy to do,\n   but he sounded like he was just throwing it out as an idea and I didn't\n   have a strong opinion, so I left it as-is. If others have\n   opinions/preferences, changing it is easy right now.\n * Peff early on wanted the strmap_entry type to have a char key[FLEX_ALLOC]\n   instead of having a (const) char *key. I spent a couple more days on this\n   despite him not mentioning it while reviewing v2, and finally got it\n   working this time and running valgrind-free. Note that such a change\n   means always copying the key instead of allowing it as an option. After\n   implementing it, I timed it and it slowed down my important testcase by\n   just over 6%. So I chucked it. I think the FLEXPTR_ALLOC_STR usage in\n   combination with defaulting to strdup_strings=1 gives us most the\n   benefits Peff wanted, while still allowing merge-ort to reuse strings\n   when it's important.\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/[2] \nhttps://lore.kernel.org/git/CABPp-BHKGkx04neULtYUyfiU+z-X7_rxQqriSEjxZjU1oXokOA@mail.gmail.com/T/#t\n[3] \nhttps://lore.kernel.org/git/CABPp-BFyqTthyBmp5yt+iUniwTi+=y2QcBcmNnnCy=zvyi3Rbw@mail.gmail.com/\n\nElijah Newren (13):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  hashmap: adjust spacing to fix argument alignment\n  hashmap: allow re-use after hashmap_free()\n  hashmap: introduce a new hashmap_partial_clear()\n  hashmap: provide deallocation function names\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: enable faster clearing and reusing of strmaps\n  strmap: add functions facilitating use as a string->int map\n  strmap: add a strset sub-type\n  strmap: enable allocations to come from a mem_pool\n  strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n  Use new HASHMAP_INIT macro to simplify hashmap initialization\n\n Makefile                |   1 +\n add-interactive.c       |   2 +-\n attr.c                  |  26 ++--\n blame.c                 |   2 +-\n bloom.c                 |   5 +-\n builtin/difftool.c      |   9 +-\n builtin/fetch.c         |   6 +-\n builtin/shortlog.c      |   2 +-\n config.c                |   2 +-\n diff.c                  |   4 +-\n diffcore-rename.c       |   2 +-\n dir.c                   |   8 +-\n hashmap.c               |  74 +++++++----\n hashmap.h               |  91 ++++++++++---\n merge-recursive.c       |   6 +-\n name-hash.c             |   4 +-\n object.c                |   2 +-\n oidmap.c                |   2 +-\n patch-ids.c             |   2 +-\n range-diff.c            |   6 +-\n ref-filter.c            |   2 +-\n revision.c              |  11 +-\n sequencer.c             |   4 +-\n strmap.c                | 158 +++++++++++++++++++++++\n strmap.h                | 280 ++++++++++++++++++++++++++++++++++++++++\n submodule-config.c      |   4 +-\n t/helper/test-hashmap.c |   9 +-\n 27 files changed, 610 insertions(+), 114 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: d4a392452e292ff924e79ec8458611c0f679d6d4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v3\nPull-Request: https://github.com/git/git/pull/835\n\nRange-diff vs v2:\n\n  1:  af6b6fcb46 =  1:  af6b6fcb46 hashmap: add usage documentation explaining hashmap_free[_entries]()\n  2:  75f17619e9 !  2:  591161fd78 hashmap: adjust spacing to fix argument alignment\n     @@ hashmap.c: struct hashmap_entry *hashmap_remove(struct hashmap *map,\n       {\n       \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n       \thashmap_add(map, entry);\n     +\n     + ## hashmap.h ##\n     +@@ hashmap.h: struct hashmap {\n     +  * prevent expensive resizing. If 0, the table is dynamically resized.\n     +  */\n     + void hashmap_init(struct hashmap *map,\n     +-\t\t\t hashmap_cmp_fn equals_function,\n     +-\t\t\t const void *equals_function_data,\n     +-\t\t\t size_t initial_size);\n     ++\t\t  hashmap_cmp_fn equals_function,\n     ++\t\t  const void *equals_function_data,\n     ++\t\t  size_t initial_size);\n     + \n     + /* internal function for freeing hashmap */\n     + void hashmap_free_(struct hashmap *map, ssize_t offset);\n     +@@ hashmap.h: void hashmap_free_(struct hashmap *map, ssize_t offset);\n     +  * and if it is on stack, you can just let it go out of scope).\n     +  */\n     + static inline void hashmap_entry_init(struct hashmap_entry *e,\n     +-\t\t\t\t\tunsigned int hash)\n     ++\t\t\t\t      unsigned int hash)\n     + {\n     + \te->hash = hash;\n     + \te->next = NULL;\n     +@@ hashmap.h: static inline unsigned int hashmap_get_size(struct hashmap *map)\n     +  * to `hashmap_cmp_fn` to decide whether the entry matches the key.\n     +  */\n     + struct hashmap_entry *hashmap_get(const struct hashmap *map,\n     +-\t\t\t\tconst struct hashmap_entry *key,\n     +-\t\t\t\tconst void *keydata);\n     ++\t\t\t\t  const struct hashmap_entry *key,\n     ++\t\t\t\t  const void *keydata);\n     + \n     + /*\n     +  * Returns the hashmap entry for the specified hash code and key data,\n     +@@ hashmap.h: static inline struct hashmap_entry *hashmap_get_from_hash(\n     +  * call to `hashmap_get` or `hashmap_get_next`.\n     +  */\n     + struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n     +-\t\t\tconst struct hashmap_entry *entry);\n     ++\t\t\t\t       const struct hashmap_entry *entry);\n     + \n     + /*\n     +  * Adds a hashmap entry. This allows to add duplicate entries (i.e.\n     +@@ hashmap.h: void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);\n     +  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).\n     +  */\n     + struct hashmap_entry *hashmap_put(struct hashmap *map,\n     +-\t\t\t\tstruct hashmap_entry *entry);\n     ++\t\t\t\t  struct hashmap_entry *entry);\n     + \n     + /*\n     +  * Adds or replaces a hashmap entry contained within @keyvar,\n     +@@ hashmap.h: struct hashmap_entry *hashmap_put(struct hashmap *map,\n     +  * Argument explanation is the same as in `hashmap_get`.\n     +  */\n     + struct hashmap_entry *hashmap_remove(struct hashmap *map,\n     +-\t\t\t\t\tconst struct hashmap_entry *key,\n     +-\t\t\t\t\tconst void *keydata);\n     ++\t\t\t\t     const struct hashmap_entry *key,\n     ++\t\t\t\t     const void *keydata);\n     + \n     + /*\n     +  * Removes a hashmap entry contained within @keyvar,\n     +@@ hashmap.h: struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);\n     + \n     + /* Initializes the iterator and returns the first entry, if any. */\n     + static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,\n     +-\t\tstruct hashmap_iter *iter)\n     ++\t\t\t\t\t\t       struct hashmap_iter *iter)\n     + {\n     + \thashmap_iter_init(map, iter);\n     + \treturn hashmap_iter_next(iter);\n  3:  a686d0758a !  3:  f2718d036d hashmap: allow re-use after hashmap_free()\n     @@ Commit message\n          Modify these functions to check for a NULL table and automatically\n          allocate as needed.\n      \n     -    I also thought about creating a HASHMAP_INIT macro to allow initializing\n     -    hashmaps on the stack without calling hashmap_init(), but virtually all\n     -    uses of hashmap specify a usecase-specific equals_function which defeats\n     -    the utility of such a macro.\n     +    Also add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the\n     +    stack without calling hashmap_init().\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ hashmap.c: struct hashmap_entry *hashmap_remove(struct hashmap *map,\n       \tif (!*e)\n       \t\treturn NULL;\n       \n     +\n     + ## hashmap.h ##\n     +@@ hashmap.h: struct hashmap {\n     + \n     + /* hashmap functions */\n     + \n     ++#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \\\n     ++\t\t\t\t .do_count_items = 1 }\n     ++\n     + /*\n     +  * Initializes a hashmap structure.\n     +  *\n  4:  061ab45a9b !  4:  61f1da3c51 hashmap: introduce a new hashmap_partial_clear()\n     @@ Commit message\n          hashmap: introduce a new hashmap_partial_clear()\n      \n          merge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\n     -    reset_maps() in merge-ort was taking about 12% of overall runtime in my\n     -    testcase involving rebasing 35 patches of linux.git across a big rename.\n     -    reset_maps() was calling hashmap_free() followed by hashmap_init(),\n     -    meaning that not only was it freeing all the memory associated with each\n     -    of the strmaps just to immediately allocate a new array again, it was\n     -    allocating a new array that wasy likely smaller than needed (thus\n     -    resulting in later need to rehash things).  The ending size of the map\n     -    table on the previous commit was likely almost perfectly sized for the\n     -    next commit we wanted to pick, and not dropping and reallocating the\n     -    table immediately is a win.\n     +    clear_or_reinit_internal_opts() in merge-ort was taking about 12% of\n     +    overall runtime in my testcase involving rebasing 35 patches of\n     +    linux.git across a big rename.  clear_or_reinit_internal_opts() was\n     +    calling hashmap_free() followed by hashmap_init(), meaning that not only\n     +    was it freeing all the memory associated with each of the strmaps just\n     +    to immediately allocate a new array again, it was allocating a new array\n     +    that was likely smaller than needed (thus resulting in later need to\n     +    rehash things).  The ending size of the map table on the previous commit\n     +    was likely almost perfectly sized for the next commit we wanted to pick,\n     +    and not dropping and reallocating the table immediately is a win.\n      \n          Add some new API to hashmap to clear a hashmap of entries without\n          freeing map->table (and instead only zeroing it out like alloc_table()\n     @@ hashmap.c: void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function\n      \n       ## hashmap.h ##\n      @@ hashmap.h: void hashmap_init(struct hashmap *map,\n     - \t\t\t const void *equals_function_data,\n     - \t\t\t size_t initial_size);\n     + \t\t  const void *equals_function_data,\n     + \t\t  size_t initial_size);\n       \n      -/* internal function for freeing hashmap */\n      +/* internal functions for clearing or freeing hashmap */\n  -:  ---------- >  5:  861e8d65ae hashmap: provide deallocation function names\n  5:  5c7507f55b !  6:  448d3b219f strmap: new utility functions\n     @@ Commit message\n          taken directly from Peff's proposal at\n          https://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n      \n     -    A couple of items of note:\n     -\n     -      * Similar to string-list, I have a strdup_strings setting.  However,\n     -        unlike string-list, strmap_init() does not take a parameter for this\n     -        setting and instead automatically sets it to 1; callers who want to\n     -        control this detail need to instead call strmap_ocd_init().\n     -\n     -      * I do not have a STRMAP_INIT macro.  I could possibly add one, but\n     -          #define STRMAP_INIT { { NULL, cmp_str_entry, NULL, 0, 0, 0, 0, 0 }, 1 }\n     -        feels a bit unwieldy and possibly error-prone in terms of future\n     -        expansion of the hashmap struct.  The fact that cmp_str_entry needs to\n     -        be in there prevents us from passing all zeros for the hashmap, and makes\n     -        me worry that STRMAP_INIT would just be more trouble than it is worth.\n     +    Note that similar string-list, I have a strdup_strings setting.\n     +    However, unlike string-list, strmap_init() does not take a parameter for\n     +    this setting and instead automatically sets it to 1; callers who want to\n     +    control this detail need to instead call strmap_init_with_options().\n     +    (Future patches will add additional parameters to\n     +    strmap_init_with_options()).\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ strmap.c (new)\n      +#include \"git-compat-util.h\"\n      +#include \"strmap.h\"\n      +\n     -+static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     -+\t\t\t    const struct hashmap_entry *entry1,\n     -+\t\t\t    const struct hashmap_entry *entry2,\n     -+\t\t\t    const void *keydata)\n     ++int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     ++\t\t     const struct hashmap_entry *entry1,\n     ++\t\t     const struct hashmap_entry *entry2,\n     ++\t\t     const void *keydata)\n      +{\n      +\tconst struct strmap_entry *e1, *e2;\n      +\n     @@ strmap.c (new)\n      +\n      +void strmap_init(struct strmap *map)\n      +{\n     -+\tstrmap_ocd_init(map, 1);\n     ++\tstrmap_init_with_options(map, 1);\n      +}\n      +\n     -+void strmap_ocd_init(struct strmap *map,\n     -+\t\t     int strdup_strings)\n     ++void strmap_init_with_options(struct strmap *map,\n     ++\t\t\t      int strdup_strings)\n      +{\n      +\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n      +\tmap->strdup_strings = strdup_strings;\n      +}\n      +\n     -+static void strmap_free_entries_(struct strmap *map, int free_util)\n     ++static void strmap_free_entries_(struct strmap *map, int free_values)\n      +{\n      +\tstruct hashmap_iter iter;\n      +\tstruct strmap_entry *e;\n     @@ strmap.c (new)\n      +\t * to make some call into the hashmap API to do that.\n      +\t */\n      +\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n     -+\t\tif (free_util)\n     ++\t\tif (free_values)\n      +\t\t\tfree(e->value);\n      +\t\tif (map->strdup_strings)\n      +\t\t\tfree((char*)e->key);\n     @@ strmap.c (new)\n      +\t}\n      +}\n      +\n     -+void strmap_clear(struct strmap *map, int free_util)\n     ++void strmap_clear(struct strmap *map, int free_values)\n      +{\n     -+\tstrmap_free_entries_(map, free_util);\n     -+\thashmap_free(&map->map);\n     ++\tstrmap_free_entries_(map, free_values);\n     ++\thashmap_clear(&map->map);\n      +}\n      +\n      +void *strmap_put(struct strmap *map, const char *str, void *data)\n     @@ strmap.c (new)\n      +\t\told = entry->value;\n      +\t\tentry->value = data;\n      +\t} else {\n     -+\t\t/*\n     -+\t\t * We won't modify entry->key so it really should be const.\n     -+\t\t */\n      +\t\tconst char *key = str;\n      +\n      +\t\tentry = xmalloc(sizeof(*entry));\n     @@ strmap.h (new)\n      +\tvoid *value;\n      +};\n      +\n     ++int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     ++\t\t     const struct hashmap_entry *entry1,\n     ++\t\t     const struct hashmap_entry *entry2,\n     ++\t\t     const void *keydata);\n     ++\n     ++#define STRMAP_INIT { \\\n     ++\t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     ++\t\t\t.strdup_strings = 1,                          \\\n     ++\t\t    }\n     ++\n      +/*\n      + * Initialize the members of the strmap.  Any keys added to the strmap will\n      + * be strdup'ed with their memory managed by the strmap.\n     @@ strmap.h (new)\n      +/*\n      + * Same as strmap_init, but for those who want to control the memory management\n      + * carefully instead of using the default of strdup_strings=1.\n     -+ * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n     -+ * are obsessing over minor details.)\n      + */\n     -+void strmap_ocd_init(struct strmap *map,\n     -+\t\t     int strdup_strings);\n     ++void strmap_init_with_options(struct strmap *map,\n     ++\t\t\t      int strdup_strings);\n      +\n      +/*\n      + * Remove all entries from the map, releasing any allocated resources.\n  6:  61b5bf1110 !  7:  42633b8d03 strmap: add more utility functions\n     @@ Commit message\n          strmap: add more utility functions\n      \n          This adds a number of additional convienence functions I want/need:\n     -      * strmap_empty()\n            * strmap_get_size()\n     +      * strmap_empty()\n            * strmap_remove()\n            * strmap_for_each_entry()\n            * strmap_get_entry()\n     @@ strmap.c: int strmap_contains(struct strmap *map, const char *str)\n       \treturn find_strmap_entry(map, str) != NULL;\n       }\n      +\n     -+void strmap_remove(struct strmap *map, const char *str, int free_util)\n     ++void strmap_remove(struct strmap *map, const char *str, int free_value)\n      +{\n      +\tstruct strmap_entry entry, *ret;\n      +\thashmap_entry_init(&entry.ent, strhash(str));\n     @@ strmap.c: int strmap_contains(struct strmap *map, const char *str)\n      +\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n      +\tif (!ret)\n      +\t\treturn;\n     -+\tif (free_util)\n     ++\tif (free_value)\n      +\t\tfree(ret->value);\n      +\tif (map->strdup_strings)\n      +\t\tfree((char*)ret->key);\n     @@ strmap.h: void *strmap_get(struct strmap *map, const char *str);\n      +void strmap_remove(struct strmap *map, const char *str, int free_value);\n      +\n      +/*\n     -+ * Return whether the strmap is empty.\n     ++ * Return how many entries the strmap has.\n      + */\n     -+static inline int strmap_empty(struct strmap *map)\n     ++static inline unsigned int strmap_get_size(struct strmap *map)\n      +{\n     -+\treturn hashmap_get_size(&map->map) == 0;\n     ++\treturn hashmap_get_size(&map->map);\n      +}\n      +\n      +/*\n     -+ * Return how many entries the strmap has.\n     ++ * Return whether the strmap is empty.\n      + */\n     -+static inline unsigned int strmap_get_size(struct strmap *map)\n     ++static inline int strmap_empty(struct strmap *map)\n      +{\n     -+\treturn hashmap_get_size(&map->map);\n     ++\treturn strmap_get_size(map) == 0;\n      +}\n      +\n      +/*\n      + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n      + */\n      +#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n     -+\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, \\\n     -+\t\t\t\t\t\t   OFFSETOF_VAR(var, ent)); \\\n     ++\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, 0); \\\n      +\t\tvar; \\\n     -+\t\tvar = hashmap_iter_next_entry_offset(iter, \\\n     -+\t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n     ++\t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n      +\n       #endif /* STRMAP_H */\n  7:  2ebce0c5d8 !  8:  ea942eb803 strmap: enable faster clearing and reusing of strmaps\n     @@ Commit message\n          Introduce strmap_partial_clear() to take advantage of this type of\n          situation; it will act similar to strmap_clear() except that\n          map->table's entries are zeroed instead of map->table being free'd.\n     -    Making use of this function reduced the cost of reset_maps() by about\n     -    20% in mert-ort, and dropped the overall runtime of my rebase testcase\n     -    by just under 2%.\n     +    Making use of this function reduced the cost of\n     +    clear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped\n     +    the overall runtime of my rebase testcase by just under 2%.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n     -@@ strmap.c: void strmap_clear(struct strmap *map, int free_util)\n     - \thashmap_free(&map->map);\n     +@@ strmap.c: void strmap_clear(struct strmap *map, int free_values)\n     + \thashmap_clear(&map->map);\n       }\n       \n     -+void strmap_partial_clear(struct strmap *map, int free_util)\n     ++void strmap_partial_clear(struct strmap *map, int free_values)\n      +{\n     -+\tstrmap_free_entries_(map, free_util);\n     ++\tstrmap_free_entries_(map, free_values);\n      +\thashmap_partial_clear(&map->map);\n      +}\n      +\n     @@ strmap.c: void strmap_clear(struct strmap *map, int free_util)\n       \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n      \n       ## strmap.h ##\n     -@@ strmap.h: void strmap_ocd_init(struct strmap *map,\n     +@@ strmap.h: void strmap_init_with_options(struct strmap *map,\n        */\n       void strmap_clear(struct strmap *map, int free_values);\n       \n  8:  cc8d702f98 !  9:  c1d2172171 strmap: add functions facilitating use as a string->int map\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n     -@@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n     +@@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_value)\n       \t\tfree((char*)ret->key);\n       \tfree(ret);\n       }\n     @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n      +\t\t*whence += amt;\n      +\t}\n      +\telse\n     -+\t\tstrintmap_set(map, str, amt);\n     ++\t\tstrintmap_set(map, str, map->default_value + amt);\n      +}\n      \n       ## strmap.h ##\n     -@@ strmap.h: static inline unsigned int strmap_get_size(struct strmap *map)\n     - \t\tvar = hashmap_iter_next_entry_offset(iter, \\\n     - \t\t\t\t\t\t     OFFSETOF_VAR(var, ent)))\n     +@@ strmap.h: int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     + \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     + \t\t\t.strdup_strings = 1,                          \\\n     + \t\t    }\n     ++#define STRINTMAP_INIT { \\\n     ++\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     ++\t\t\t.map.strdup_strings = 1,                          \\\n     ++\t\t\t.default_value = 0,                               \\\n     ++\t\t    }\n     + \n     + /*\n     +  * Initialize the members of the strmap.  Any keys added to the strmap will\n     +@@ strmap.h: static inline int strmap_empty(struct strmap *map)\n     + \t\tvar; \\\n     + \t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n       \n      +\n      +/*\n     @@ strmap.h: static inline unsigned int strmap_get_size(struct strmap *map)\n      +\n      +struct strintmap {\n      +\tstruct strmap map;\n     ++\tint default_value;\n      +};\n      +\n      +#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n      +\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n      +\n     -+static inline void strintmap_init(struct strintmap *map)\n     ++static inline void strintmap_init(struct strintmap *map, int default_value)\n      +{\n      +\tstrmap_init(&map->map);\n     ++\tmap->default_value = default_value;\n      +}\n      +\n     -+static inline void strintmap_ocd_init(struct strintmap *map,\n     -+\t\t\t\t      int strdup_strings)\n     ++static inline void strintmap_init_with_options(struct strintmap *map,\n     ++\t\t\t\t\t       int default_value,\n     ++\t\t\t\t\t       int strdup_strings)\n      +{\n     -+\tstrmap_ocd_init(&map->map, strdup_strings);\n     ++\tstrmap_init_with_options(&map->map, strdup_strings);\n     ++\tmap->default_value = default_value;\n      +}\n      +\n      +static inline void strintmap_clear(struct strintmap *map)\n     @@ strmap.h: static inline unsigned int strmap_get_size(struct strmap *map)\n      +\treturn strmap_get_size(&map->map);\n      +}\n      +\n     -+static inline int strintmap_get(struct strintmap *map, const char *str,\n     -+\t\t\t\tint default_value)\n     ++/*\n     ++ * Returns the value for str in the map.  If str isn't found in the map,\n     ++ * the map's default_value is returned.\n     ++ */\n     ++static inline int strintmap_get(struct strintmap *map, const char *str)\n      +{\n      +\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n      +\tif (!result)\n     -+\t\treturn default_value;\n     ++\t\treturn map->default_value;\n      +\treturn (intptr_t)result->value;\n      +}\n      +\n     @@ strmap.h: static inline unsigned int strmap_get_size(struct strmap *map)\n      +\tstrmap_put(&map->map, str, (void *)v);\n      +}\n      +\n     ++/*\n     ++ * Increment the value for str by amt.  If str isn't in the map, add it and\n     ++ * set its value to default_value + amt.\n     ++ */\n      +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n      +\n       #endif /* STRMAP_H */\n  9:  490d3a42ad ! 10:  0f57735f5e strmap: add a strset sub-type\n     @@ Commit message\n      \n          The difference in usage also results in some differences in API: a few\n          things that aren't necessary or meaningful are dropped (namely, the\n     -    free_util argument to *_clear(), and the *_get() function), and\n     +    free_values argument to *_clear(), and the *_get() function), and\n          strset_add() is chosen as the API instead of strset_put().\n      \n     +    Finally, shortlog already had a more minimal strset API; so this adds a\n     +    strset_check_and_add() function for its benefit to allow it to switch\n     +    over to this strset implementation.\n     +\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     + ## strmap.c ##\n     +@@ strmap.c: void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n     + \telse\n     + \t\tstrintmap_set(map, str, map->default_value + amt);\n     + }\n     ++\n     ++int strset_check_and_add(struct strset *set, const char *str)\n     ++{\n     ++\tif (strset_contains(set, str))\n     ++\t\treturn 1;\n     ++\tstrset_add(set, str);\n     ++\treturn 0;\n     ++}\n     +\n       ## strmap.h ##\n     -@@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *str,\n     +@@ strmap.h: int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     + \t\t\t.map.strdup_strings = 1,                          \\\n     + \t\t\t.default_value = 0,                               \\\n     + \t\t    }\n     ++#define STRSET_INIT { \\\n     ++\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     ++\t\t\t.map.strdup_strings = 1,                          \\\n     ++\t\t    }\n       \n     + /*\n     +  * Initialize the members of the strmap.  Any keys added to the strmap will\n     +@@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *str,\n     +  */\n       void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n       \n      +/*\n     @@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *st\n      +\tstrmap_init(&set->map);\n      +}\n      +\n     -+static inline void strset_ocd_init(struct strset *set,\n     -+\t\t\t\t   int strdup_strings)\n     ++static inline void strset_init_with_options(struct strset *set,\n     ++\t\t\t\t\t    int strdup_strings)\n      +{\n     -+\tstrmap_ocd_init(&set->map, strdup_strings);\n     ++\tstrmap_init_with_options(&set->map, strdup_strings);\n      +}\n      +\n      +static inline void strset_clear(struct strset *set)\n     @@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *st\n      +{\n      +\tstrmap_put(&set->map, str, NULL);\n      +}\n     ++\n     ++/* Returns 1 if str already in set.  Otherwise adds str to set and returns 0 */\n     ++int strset_check_and_add(struct strset *set, const char *str);\n      +\n       #endif /* STRMAP_H */\n 10:  eca4f1ddba ! 11:  980537e877 strmap: enable allocations to come from a mem_pool\n     @@ Commit message\n      \n          For heavy users of strmaps, allowing the keys and entries to be\n          allocated from a memory pool can provide significant overhead savings.\n     -    Add an option to strmap_ocd_init() to specify a memory pool.\n     +    Add an option to strmap_init_with_options() to specify a memory pool.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ strmap.c\n       #include \"strmap.h\"\n      +#include \"mem-pool.h\"\n       \n     - static int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     - \t\t\t    const struct hashmap_entry *entry1,\n     + int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     + \t\t     const struct hashmap_entry *entry1,\n      @@ strmap.c: static struct strmap_entry *find_strmap_entry(struct strmap *map,\n       \n       void strmap_init(struct strmap *map)\n       {\n     --\tstrmap_ocd_init(map, 1);\n     -+\tstrmap_ocd_init(map, NULL, 1);\n     +-\tstrmap_init_with_options(map, 1);\n     ++\tstrmap_init_with_options(map, NULL, 1);\n       }\n       \n     - void strmap_ocd_init(struct strmap *map,\n     -+\t\t     struct mem_pool *pool,\n     - \t\t     int strdup_strings)\n     + void strmap_init_with_options(struct strmap *map,\n     ++\t\t\t      struct mem_pool *pool,\n     + \t\t\t      int strdup_strings)\n       {\n       \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n      +\tmap->pool = pool;\n       \tmap->strdup_strings = strdup_strings;\n       }\n       \n     -@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_util)\n     +@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_values)\n       \tif (!map)\n       \t\treturn;\n       \n     -+\tif (!free_util && map->pool)\n     ++\tif (!free_values && map->pool)\n      +\t\t/* Memory other than util is owned by and freed with the pool */\n      +\t\treturn;\n      +\n       \t/*\n       \t * We need to iterate over the hashmap entries and free\n       \t * e->key and e->value ourselves; hashmap has no API to\n     -@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_util)\n     +@@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_values)\n       \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n     - \t\tif (free_util)\n     + \t\tif (free_values)\n       \t\t\tfree(e->value);\n      -\t\tif (map->strdup_strings)\n      -\t\t\tfree((char*)e->key);\n     @@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_util)\n       }\n       \n      @@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n     - \t\t */\n     + \t} else {\n       \t\tconst char *key = str;\n       \n      -\t\tentry = xmalloc(sizeof(*entry));\n     @@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n       \t\tentry->key = key;\n       \t\tentry->value = data;\n       \t\thashmap_add(&map->map, &entry->ent);\n     -@@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_util)\n     +@@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_value)\n       \t\treturn;\n     - \tif (free_util)\n     + \tif (free_value)\n       \t\tfree(ret->value);\n      -\tif (map->strdup_strings)\n      -\t\tfree((char*)ret->key);\n     @@ strmap.h: void strmap_init(struct strmap *map);\n        * Same as strmap_init, but for those who want to control the memory management\n      - * carefully instead of using the default of strdup_strings=1.\n      + * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n     -  * (OCD = Obsessive Compulsive Disorder, a joke that those who use this function\n     -  * are obsessing over minor details.)\n        */\n     - void strmap_ocd_init(struct strmap *map,\n     -+\t\t     struct mem_pool *pool,\n     - \t\t     int strdup_strings);\n     + void strmap_init_with_options(struct strmap *map,\n     ++\t\t\t      struct mem_pool *pool,\n     + \t\t\t      int strdup_strings);\n       \n       /*\n     -@@ strmap.h: static inline void strintmap_init(struct strintmap *map)\n     - }\n     +@@ strmap.h: static inline void strintmap_init(struct strintmap *map, int default_value)\n       \n     - static inline void strintmap_ocd_init(struct strintmap *map,\n     -+\t\t\t\t      struct mem_pool *pool,\n     - \t\t\t\t      int strdup_strings)\n     + static inline void strintmap_init_with_options(struct strintmap *map,\n     + \t\t\t\t\t       int default_value,\n     ++\t\t\t\t\t       struct mem_pool *pool,\n     + \t\t\t\t\t       int strdup_strings)\n       {\n     --\tstrmap_ocd_init(&map->map, strdup_strings);\n     -+\tstrmap_ocd_init(&map->map, pool, strdup_strings);\n     +-\tstrmap_init_with_options(&map->map, strdup_strings);\n     ++\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n     + \tmap->default_value = default_value;\n       }\n       \n     - static inline void strintmap_clear(struct strintmap *map)\n      @@ strmap.h: static inline void strset_init(struct strset *set)\n       }\n       \n     - static inline void strset_ocd_init(struct strset *set,\n     -+\t\t\t\t   struct mem_pool *pool,\n     - \t\t\t\t   int strdup_strings)\n     + static inline void strset_init_with_options(struct strset *set,\n     ++\t\t\t\t\t    struct mem_pool *pool,\n     + \t\t\t\t\t    int strdup_strings)\n       {\n     --\tstrmap_ocd_init(&set->map, strdup_strings);\n     -+\tstrmap_ocd_init(&set->map, pool, strdup_strings);\n     +-\tstrmap_init_with_options(&set->map, strdup_strings);\n     ++\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n       }\n       \n       static inline void strset_clear(struct strset *set)\n  -:  ---------- > 12:  7f93cbb525 strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n  -:  ---------- > 13:  5f41fc63e5 Use new HASHMAP_INIT macro to simplify hashmap initialization\n\n-- \ngitgitgadget\n"},{"id":"408885","messageId":"861e8d65ae8065595d9d4ccff5f70155fec408c9.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 05/13] hashmap: provide deallocation function names","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:05Z","receivedAt":"2020-11-02T18:55:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nhashmap_free(), hashmap_free_entries(), and hashmap_free_() have existed\nfor a while, but aren't necessarily the clearest names, especially with\nhashmap_partial_clear() being added to the mix and lazy-initialization\nnow being supported.  Peff suggested we adopt the following names[1]:\n\n  - hashmap_clear() - remove all entries and de-allocate any\n    hashmap-specific data, but be ready for reuse\n\n  - hashmap_clear_and_free() - ditto, but free the entries themselves\n\n  - hashmap_partial_clear() - remove all entries but don't deallocate\n    table\n\n  - hashmap_partial_clear_and_free() - ditto, but free the entries\n\nThis patch provides the new names and converts all existing callers over\nto the new naming scheme.\n\n[1] https://lore.kernel.org/git/20201030125059.GA3277724@coredump.intra.peff.net/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n add-interactive.c       |  2 +-\n blame.c                 |  2 +-\n bloom.c                 |  2 +-\n builtin/fetch.c         |  6 +++---\n builtin/shortlog.c      |  2 +-\n config.c                |  2 +-\n diff.c                  |  4 ++--\n diffcore-rename.c       |  2 +-\n dir.c                   |  8 ++++----\n hashmap.c               |  6 +++---\n hashmap.h               | 44 +++++++++++++++++++++++++----------------\n merge-recursive.c       |  6 +++---\n name-hash.c             |  4 ++--\n object.c                |  2 +-\n oidmap.c                |  2 +-\n patch-ids.c             |  2 +-\n range-diff.c            |  2 +-\n ref-filter.c            |  2 +-\n revision.c              |  2 +-\n sequencer.c             |  4 ++--\n submodule-config.c      |  4 ++--\n t/helper/test-hashmap.c |  6 +++---\n 22 files changed, 63 insertions(+), 53 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 555c4abf32..a14c0feaa2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -557,7 +557,7 @@ static int get_modified_files(struct repository *r,\n \t\tif (ps)\n \t\t\tclear_pathspec(&rev.prune_data);\n \t}\n-\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\thashmap_clear_and_free(&s.file_map, struct pathname_entry, ent);\n \tif (unmerged_count)\n \t\t*unmerged_count = s.unmerged_count;\n \tif (binary_count)\ndiff --git a/blame.c b/blame.c\nindex 686845b2b4..229beb6452 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -435,7 +435,7 @@ static void get_fingerprint(struct fingerprint *result,\n \n static void free_fingerprint(struct fingerprint *f)\n {\n-\thashmap_free(&f->map);\n+\thashmap_clear(&f->map);\n \tfree(f->entries);\n }\n \ndiff --git a/bloom.c b/bloom.c\nindex 68c73200a5..719c313a1c 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -287,7 +287,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \t\t}\n \n \tcleanup:\n-\t\thashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);\n+\t\thashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry);\n \t} else {\n \t\tfor (i = 0; i < diff_queued_diff.nr; i++)\n \t\t\tdiff_free_filepair(diff_queued_diff.queue[i]);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex f9c3c49f14..ecf8537605 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -393,7 +393,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\titem = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);\n \t\tstring_list_insert(&remote_refs_list, ref->name);\n \t}\n-\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \t/*\n \t * We may have a final lightweight tag that needs to be\n@@ -428,7 +428,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\t**tail = rm;\n \t\t*tail = &rm->next;\n \t}\n-\thashmap_free_entries(&remote_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent);\n \tstring_list_clear(&remote_refs_list, 0);\n \toidset_clear(&fetch_oids);\n }\n@@ -573,7 +573,7 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \tif (existing_refs_populated)\n-\t\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\t\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \treturn ref_map;\n }\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 0a5c4968f6..83f0a739b4 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -220,7 +220,7 @@ static void strset_clear(struct strset *ss)\n {\n \tif (!ss->map.table)\n \t\treturn;\n-\thashmap_free_entries(&ss->map, struct strset_item, ent);\n+\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n }\n \n static void insert_records_from_trailers(struct shortlog *log,\ndiff --git a/config.c b/config.c\nindex 2bdff4457b..8f324ed3a6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1963,7 +1963,7 @@ void git_configset_clear(struct config_set *cs)\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_free_entries(&cs->config_hash, struct config_set_element, ent);\n+\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n \tcs->hash_initialized = 0;\n \tfree(cs->list.items);\n \tcs->list.nr = 0;\ndiff --git a/diff.c b/diff.c\nindex 2bb2f8f57e..8e0e59f5cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6289,9 +6289,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \t\t\tif (o->color_moved == COLOR_MOVED_ZEBRA_DIM)\n \t\t\t\tdim_moved_lines(o);\n \n-\t\t\thashmap_free_entries(&add_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&add_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n-\t\t\thashmap_free_entries(&del_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&del_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n \t\t}\n \ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 99e63e90f8..d367a6d244 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -407,7 +407,7 @@ static int find_exact_renames(struct diff_options *options)\n \t\trenames += find_identical_files(&file_table, i, options);\n \n \t/* Free the hash data structure and entries */\n-\thashmap_free_entries(&file_table, struct file_similarity, entry);\n+\thashmap_clear_and_free(&file_table, struct file_similarity, entry);\n \n \treturn renames;\n }\ndiff --git a/dir.c b/dir.c\nindex 78387110e6..161dce121e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -817,8 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern\n \n clear_hashmaps:\n \twarning(_(\"disabling cone pattern matching\"));\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n \tpl->use_cone_patterns = 0;\n }\n \n@@ -921,8 +921,8 @@ void clear_pattern_list(struct pattern_list *pl)\n \t\tfree(pl->patterns[i]);\n \tfree(pl->patterns);\n \tfree(pl->filebuf);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n \n \tmemset(pl, 0, sizeof(*pl));\n }\ndiff --git a/hashmap.c b/hashmap.c\nindex 922ed07954..5009471800 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -183,7 +183,7 @@ static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n \twhile ((e = hashmap_iter_next(&iter)))\n \t\t/*\n \t\t * like container_of, but using caller-calculated\n-\t\t * offset (caller being hashmap_free_entries)\n+\t\t * offset (caller being hashmap_clear_and_free)\n \t\t */\n \t\tfree((char *)e - entry_offset);\n }\n@@ -199,11 +199,11 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n \tmap->private_size = 0;\n }\n \n-void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n+void hashmap_clear_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_and_free */\n \t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\ndiff --git a/hashmap.h b/hashmap.h\nindex e9430d582a..7251687d73 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -96,7 +96,7 @@\n  *         }\n  *\n  *         if (!strcmp(\"end\", action)) {\n- *             hashmap_free_entries(&map, struct long2string, ent);\n+ *             hashmap_clear_and_free(&map, struct long2string, ent);\n  *             break;\n  *         }\n  *     }\n@@ -237,7 +237,7 @@ void hashmap_init(struct hashmap *map,\n \n /* internal functions for clearing or freeing hashmap */\n void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n-void hashmap_free_(struct hashmap *map, ssize_t offset);\n+void hashmap_clear_(struct hashmap *map, ssize_t offset);\n \n /*\n  * Frees a hashmap structure and allocated memory for the table, but does not\n@@ -253,40 +253,50 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  *      free(e->somefield);\n  *      free(e);\n  *    }\n- *    hashmap_free(map);\n+ *    hashmap_clear(map);\n  *\n  * instead of\n  *\n  *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n  *      free(e->somefield);\n  *    }\n- *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *    hashmap_clear_and_free(map, struct my_entry_struct, hashmap_entry_name);\n  *\n  * to avoid the implicit extra loop over the entries.  However, if there are\n  * no special fields in your entry that need to be freed beyond the entry\n  * itself, it is probably simpler to avoid the explicit loop and just call\n- * hashmap_free_entries().\n+ * hashmap_clear_and_free().\n  */\n-#define hashmap_free(map) hashmap_free_(map, -1)\n+#define hashmap_clear(map) hashmap_clear_(map, -1)\n \n /*\n- * Basically the same as calling hashmap_free() followed by hashmap_init(),\n- * but doesn't incur the overhead of deallocating and reallocating\n- * map->table; it leaves map->table allocated and the same size but zeroes\n- * it out so it's ready for use again as an empty map.  As with\n- * hashmap_free(), you may need to free the entries yourself before calling\n- * this function.\n+ * Similar to hashmap_clear(), except that the table is no deallocated; it\n+ * is merely zeroed out but left the same size as before.  If the hashmap\n+ * will be reused, this avoids the overhead of deallocating and\n+ * reallocating map->table.  As with hashmap_clear(), you may need to free\n+ * the entries yourself before calling this function.\n  */\n #define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n \n /*\n- * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map.\n+ * Similar to hashmap_clear() but also frees all entries.  @type is the\n+ * struct type of the entry where @member is the hashmap_entry struct used\n+ * to associate with @map.\n  *\n- * See usage note above hashmap_free().\n+ * See usage note above hashmap_clear().\n  */\n-#define hashmap_free_entries(map, type, member) \\\n-\thashmap_free_(map, offsetof(type, member));\n+#define hashmap_clear_and_free(map, type, member) \\\n+\thashmap_clear_(map, offsetof(type, member))\n+\n+/*\n+ * Similar to hashmap_partial_clear() but also frees all entries.  @type is\n+ * the struct type of the entry where @member is the hashmap_entry struct\n+ * used to associate with @map.\n+ *\n+ * See usage note above hashmap_clear().\n+ */\n+#define hashmap_partial_clear_and_free(map, type, member) \\\n+\thashmap_partial_clear_(map, offsetof(type, member))\n \n /* hashmap_entry functions */\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d0214335a7..f736a0f632 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2651,7 +2651,7 @@ static struct string_list *get_renames(struct merge_options *opt,\n \t\tfree(e->target_file);\n \t\tstring_list_clear(&e->source_files, 0);\n \t}\n-\thashmap_free_entries(&collisions, struct collision_entry, ent);\n+\thashmap_clear_and_free(&collisions, struct collision_entry, ent);\n \treturn renames;\n }\n \n@@ -2870,7 +2870,7 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,\n \t\tstrbuf_release(&e->new_dir);\n \t\t/* possible_new_dirs already cleared in get_directory_renames */\n \t}\n-\thashmap_free_entries(dir_renames, struct dir_rename_entry, ent);\n+\thashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent);\n \tfree(dir_renames);\n \n \tfree(pairs->queue);\n@@ -3497,7 +3497,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free_entries(&opt->priv->current_file_dir_set,\n+\t\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n \t\t\t\t\tstruct path_hashmap_entry, e);\n \n \t\tif (clean < 0) {\ndiff --git a/name-hash.c b/name-hash.c\nindex fb526a3775..5d3c7b12c1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -726,6 +726,6 @@ void free_name_hash(struct index_state *istate)\n \t\treturn;\n \tistate->name_hash_initialized = 0;\n \n-\thashmap_free(&istate->name_hash);\n-\thashmap_free_entries(&istate->dir_hash, struct dir_entry, ent);\n+\thashmap_clear(&istate->name_hash);\n+\thashmap_clear_and_free(&istate->dir_hash, struct dir_entry, ent);\n }\ndiff --git a/object.c b/object.c\nindex 3257518656..b8406409d5 100644\n--- a/object.c\n+++ b/object.c\n@@ -532,7 +532,7 @@ void raw_object_store_clear(struct raw_object_store *o)\n \tclose_object_store(o);\n \to->packed_git = NULL;\n \n-\thashmap_free(&o->pack_map);\n+\thashmap_clear(&o->pack_map);\n }\n \n void parsed_object_pool_clear(struct parsed_object_pool *o)\ndiff --git a/oidmap.c b/oidmap.c\nindex 423aa014a3..286a04a53c 100644\n--- a/oidmap.c\n+++ b/oidmap.c\n@@ -27,7 +27,7 @@ void oidmap_free(struct oidmap *map, int free_entries)\n \t\treturn;\n \n \t/* TODO: make oidmap itself not depend on struct layouts */\n-\thashmap_free_(&map->map, free_entries ? 0 : -1);\n+\thashmap_clear_(&map->map, free_entries ? 0 : -1);\n }\n \n void *oidmap_get(const struct oidmap *map, const struct object_id *key)\ndiff --git a/patch-ids.c b/patch-ids.c\nindex 12aa6d494b..21973e4933 100644\n--- a/patch-ids.c\n+++ b/patch-ids.c\n@@ -71,7 +71,7 @@ int init_patch_ids(struct repository *r, struct patch_ids *ids)\n \n int free_patch_ids(struct patch_ids *ids)\n {\n-\thashmap_free_entries(&ids->patches, struct patch_id, ent);\n+\thashmap_clear_and_free(&ids->patches, struct patch_id, ent);\n \treturn 0;\n }\n \ndiff --git a/range-diff.c b/range-diff.c\nindex 24dc435e48..befeecae44 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -266,7 +266,7 @@ static void find_exact_matches(struct string_list *a, struct string_list *b)\n \t\t}\n \t}\n \n-\thashmap_free(&map);\n+\thashmap_clear(&map);\n }\n \n static void diffsize_consume(void *data, char *line, unsigned long len)\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c62f6b4822..5e66b8cd76 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2222,7 +2222,7 @@ void ref_array_clear(struct ref_array *array)\n \tused_atom_cnt = 0;\n \n \tif (ref_to_worktree_map.worktrees) {\n-\t\thashmap_free_entries(&(ref_to_worktree_map.map),\n+\t\thashmap_clear_and_free(&(ref_to_worktree_map.map),\n \t\t\t\t\tstruct ref_to_worktree_entry, ent);\n \t\tfree_worktrees(ref_to_worktree_map.worktrees);\n \t\tref_to_worktree_map.worktrees = NULL;\ndiff --git a/revision.c b/revision.c\nindex aa62212040..f27649d45d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -139,7 +139,7 @@ static void paths_and_oids_clear(struct hashmap *map)\n \t\tfree(entry->path);\n \t}\n \n-\thashmap_free_entries(map, struct path_and_oids_entry, ent);\n+\thashmap_clear_and_free(map, struct path_and_oids_entry, ent);\n }\n \n static void paths_and_oids_insert(struct hashmap *map,\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..23a09c3e7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5058,7 +5058,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n-\thashmap_free_entries(&state.labels, struct labels_entry, entry);\n+\thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\n \tstrbuf_release(&state.buf);\n \n \treturn 0;\n@@ -5577,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)\n \tfor (i = 0; i < todo_list->nr; i++)\n \t\tfree(subjects[i]);\n \tfree(subjects);\n-\thashmap_free_entries(&subject2item, struct subject2item_entry, entry);\n+\thashmap_clear_and_free(&subject2item, struct subject2item_entry, entry);\n \n \tclear_commit_todo_item(&commit_todo);\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex c569e22aa3..f502505566 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -103,8 +103,8 @@ static void submodule_cache_clear(struct submodule_cache *cache)\n \t\t\t\tent /* member name */)\n \t\tfree_one_config(entry);\n \n-\thashmap_free_entries(&cache->for_path, struct submodule_entry, ent);\n-\thashmap_free_entries(&cache->for_name, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_path, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_name, struct submodule_entry, ent);\n \tcache->initialized = 0;\n \tcache->gitmodules_read = 0;\n }\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex f38706216f..2475663b49 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -110,7 +110,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t\thashmap_add(&map, &entries[i]->ent);\n \t\t\t}\n \n-\t\t\thashmap_free(&map);\n+\t\t\thashmap_clear(&map);\n \t\t}\n \t} else {\n \t\t/* test map lookups */\n@@ -130,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t}\n \t\t}\n \n-\t\thashmap_free(&map);\n+\t\thashmap_clear(&map);\n \t}\n }\n \n@@ -262,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv)\n \t}\n \n \tstrbuf_release(&line);\n-\thashmap_free_entries(&map, struct test_entry, ent);\n+\thashmap_clear_and_free(&map, struct test_entry, ent);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"408886","messageId":"c1d217217180cff0b048d5dbac57b8e363db7848.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 09/13] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:09Z","receivedAt":"2020-11-02T18:55:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nA note on the name: if anyone has a better name suggestion than\nstrintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\nnot been able to come up with a better name.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 +++++++\n strmap.h | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 107 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex c410c5241a..0d10a884b5 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -123,3 +123,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\tfree((char*)ret->key);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->value;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, map->default_value + amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 10b4642860..31474f781e 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -23,6 +23,11 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n \t\t\t.strdup_strings = 1,                          \\\n \t\t    }\n+#define STRINTMAP_INIT { \\\n+\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.map.strdup_strings = 1,                          \\\n+\t\t\t.default_value = 0,                               \\\n+\t\t    }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -104,4 +109,95 @@ static inline int strmap_empty(struct strmap *map)\n \t\tvar; \\\n \t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n \n+\n+/*\n+ * strintmap:\n+ *    A map of string -> int, typecasting the void* of strmap to an int.\n+ *\n+ * Primary differences:\n+ *    1) Since the void* value is just an int in disguise, there is no value\n+ *       to free.  (Thus one fewer argument to strintmap_clear)\n+ *    2) strintmap_get() returns an int; it also requires an extra parameter to\n+ *       be specified so it knows what value to return if the underlying strmap\n+ *       has not key matching the given string.\n+ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n+ *       instead.\n+ */\n+\n+struct strintmap {\n+\tstruct strmap map;\n+\tint default_value;\n+};\n+\n+#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n+\n+static inline void strintmap_init(struct strintmap *map, int default_value)\n+{\n+\tstrmap_init(&map->map);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_init_with_options(struct strintmap *map,\n+\t\t\t\t\t       int default_value,\n+\t\t\t\t\t       int strdup_strings)\n+{\n+\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_clear(struct strintmap *map)\n+{\n+\tstrmap_clear(&map->map, 0);\n+}\n+\n+static inline void strintmap_partial_clear(struct strintmap *map)\n+{\n+\tstrmap_partial_clear(&map->map, 0);\n+}\n+\n+static inline int strintmap_contains(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_contains(&map->map, str);\n+}\n+\n+static inline void strintmap_remove(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_remove(&map->map, str, 0);\n+}\n+\n+static inline int strintmap_empty(struct strintmap *map)\n+{\n+\treturn strmap_empty(&map->map);\n+}\n+\n+static inline unsigned int strintmap_get_size(struct strintmap *map)\n+{\n+\treturn strmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Returns the value for str in the map.  If str isn't found in the map,\n+ * the map's default_value is returned.\n+ */\n+static inline int strintmap_get(struct strintmap *map, const char *str)\n+{\n+\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n+\tif (!result)\n+\t\treturn map->default_value;\n+\treturn (intptr_t)result->value;\n+}\n+\n+static inline void strintmap_set(struct strintmap *map, const char *str,\n+\t\t\t\t intptr_t v)\n+{\n+\tstrmap_put(&map->map, str, (void *)v);\n+}\n+\n+/*\n+ * Increment the value for str by amt.  If str isn't in the map, add it and\n+ * set its value to default_value + amt.\n+ */\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"408887","messageId":"980537e877a3e690d451a574593b26f827b0d33d.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 11/13] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:11Z","receivedAt":"2020-11-02T18:55:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor heavy users of strmaps, allowing the keys and entries to be\nallocated from a memory pool can provide significant overhead savings.\nAdd an option to strmap_init_with_options() to specify a memory pool.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 31 ++++++++++++++++++++++---------\n strmap.h | 11 ++++++++---\n 2 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 2aff985f40..34bca92522 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"strmap.h\"\n+#include \"mem-pool.h\"\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t     const struct hashmap_entry *entry1,\n@@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map,\n \n void strmap_init(struct strmap *map)\n {\n-\tstrmap_init_with_options(map, 1);\n+\tstrmap_init_with_options(map, NULL, 1);\n }\n \n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->pool = pool;\n \tmap->strdup_strings = strdup_strings;\n }\n \n@@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \tif (!map)\n \t\treturn;\n \n+\tif (!free_values && map->pool)\n+\t\t/* Memory other than util is owned by and freed with the pool */\n+\t\treturn;\n+\n \t/*\n \t * We need to iterate over the hashmap entries and free\n \t * e->key and e->value ourselves; hashmap has no API to\n@@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)e->key);\n-\t\tfree(e);\n+\t\tif (!map->pool) {\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree((char*)e->key);\n+\t\t\tfree(e);\n+\t\t}\n \t}\n }\n \n@@ -81,11 +90,13 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \t} else {\n \t\tconst char *key = str;\n \n-\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n+\t\t\t\t  : xmalloc(sizeof(*entry));\n \t\thashmap_entry_init(&entry->ent, strhash(str));\n \n \t\tif (map->strdup_strings)\n-\t\t\tkey = xstrdup(str);\n+\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n+\t\t\t\t\t: xstrdup(str);\n \t\tentry->key = key;\n \t\tentry->value = data;\n \t\thashmap_add(&map->map, &entry->ent);\n@@ -119,9 +130,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (map->strdup_strings)\n-\t\tfree((char*)ret->key);\n-\tfree(ret);\n+\tif (!map->pool) {\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)ret->key);\n+\t\tfree(ret);\n+\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex fca1e9f639..6ffa6afb6a 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -3,8 +3,10 @@\n \n #include \"hashmap.h\"\n \n+struct mempool;\n struct strmap {\n \tstruct hashmap map;\n+\tstruct mem_pool *pool;\n \tunsigned int strdup_strings:1;\n };\n \n@@ -41,9 +43,10 @@ void strmap_init(struct strmap *map);\n \n /*\n  * Same as strmap_init, but for those who want to control the memory management\n- * carefully instead of using the default of strdup_strings=1.\n+ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n  */\n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings);\n \n /*\n@@ -144,9 +147,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n \n static inline void strintmap_init_with_options(struct strintmap *map,\n \t\t\t\t\t       int default_value,\n+\t\t\t\t\t       struct mem_pool *pool,\n \t\t\t\t\t       int strdup_strings)\n {\n-\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n \tmap->default_value = default_value;\n }\n \n@@ -228,9 +232,10 @@ static inline void strset_init(struct strset *set)\n }\n \n static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    struct mem_pool *pool,\n \t\t\t\t\t    int strdup_strings)\n {\n-\tstrmap_init_with_options(&set->map, strdup_strings);\n+\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n }\n \n static inline void strset_clear(struct strset *set)\n-- \ngitgitgadget\n\n"},{"id":"408888","messageId":"0f57735f5e30ad61a2e6fdb118067afbcea69660.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 10/13] strmap: add a strset sub-type","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:10Z","receivedAt":"2020-11-02T18:55:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSimilar to adding strintmap for special-casing a string -> int mapping,\nadd a strset type for cases where we really are only interested in using\nstrmap for storing a set rather than a mapping.  In this case, we'll\nalways just store NULL for the value but the different struct type makes\nit clearer than code comments how a variable is intended to be used.\n\nThe difference in usage also results in some differences in API: a few\nthings that aren't necessary or meaningful are dropped (namely, the\nfree_values argument to *_clear(), and the *_get() function), and\nstrset_add() is chosen as the API instead of strset_put().\n\nFinally, shortlog already had a more minimal strset API; so this adds a\nstrset_check_and_add() function for its benefit to allow it to switch\nover to this strset implementation.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c |  8 +++++++\n strmap.h | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 79 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 0d10a884b5..2aff985f40 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -134,3 +134,11 @@ void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n \telse\n \t\tstrintmap_set(map, str, map->default_value + amt);\n }\n+\n+int strset_check_and_add(struct strset *set, const char *str)\n+{\n+\tif (strset_contains(set, str))\n+\t\treturn 1;\n+\tstrset_add(set, str);\n+\treturn 0;\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 31474f781e..fca1e9f639 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -28,6 +28,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map.strdup_strings = 1,                          \\\n \t\t\t.default_value = 0,                               \\\n \t\t    }\n+#define STRSET_INIT { \\\n+\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.map.strdup_strings = 1,                          \\\n+\t\t    }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -200,4 +204,71 @@ static inline void strintmap_set(struct strintmap *map, const char *str,\n  */\n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n \n+/*\n+ * strset:\n+ *    A set of strings.\n+ *\n+ * Primary differences with strmap:\n+ *    1) The value is always NULL, and ignored.  As there is no value to free,\n+ *       there is one fewer argument to strset_clear\n+ *    2) No strset_get() because there is no value.\n+ *    3) No strset_put(); use strset_add() instead.\n+ */\n+\n+struct strset {\n+\tstruct strmap map;\n+};\n+\n+#define strset_for_each_entry(mystrset, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrset)->map, iter, var)\n+\n+static inline void strset_init(struct strset *set)\n+{\n+\tstrmap_init(&set->map);\n+}\n+\n+static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    int strdup_strings)\n+{\n+\tstrmap_init_with_options(&set->map, strdup_strings);\n+}\n+\n+static inline void strset_clear(struct strset *set)\n+{\n+\tstrmap_clear(&set->map, 0);\n+}\n+\n+static inline void strset_partial_clear(struct strset *set)\n+{\n+\tstrmap_partial_clear(&set->map, 0);\n+}\n+\n+static inline int strset_contains(struct strset *set, const char *str)\n+{\n+\treturn strmap_contains(&set->map, str);\n+}\n+\n+static inline void strset_remove(struct strset *set, const char *str)\n+{\n+\treturn strmap_remove(&set->map, str, 0);\n+}\n+\n+static inline int strset_empty(struct strset *set)\n+{\n+\treturn strmap_empty(&set->map);\n+}\n+\n+static inline unsigned int strset_get_size(struct strset *set)\n+{\n+\treturn strmap_get_size(&set->map);\n+}\n+\n+static inline void strset_add(struct strset *set, const char *str)\n+{\n+\tstrmap_put(&set->map, str, NULL);\n+}\n+\n+/* Returns 1 if str already in set.  Otherwise adds str to set and returns 0 */\n+int strset_check_and_add(struct strset *set, const char *str);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"408891","messageId":"5f41fc63e5355121cf882f63e1b38b8df1948df8.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 13/13] Use new HASHMAP_INIT macro to simplify hashmap initialization","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:13Z","receivedAt":"2020-11-02T18:55:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNow that hashamp has lazy initialization and a HASHMAP_INIT macro,\nhashmaps allocated on the stack can be initialized without a call to\nhashmap_init() and in some cases makes the code a bit shorter.  Convert\nsome callsites over to take advantage of this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n attr.c                  | 26 ++++++++------------------\n bloom.c                 |  3 +--\n builtin/difftool.c      |  9 ++++-----\n range-diff.c            |  4 +---\n revision.c              |  9 +--------\n t/helper/test-hashmap.c |  3 +--\n 6 files changed, 16 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a826b2ef1f..4ef85d668b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -52,13 +52,6 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n \tpthread_mutex_unlock(&map->mutex);\n }\n \n-/*\n- * The global dictionary of all interned attributes.  This\n- * is a singleton object which is shared between threads.\n- * Access to this dictionary must be surrounded with a mutex.\n- */\n-static struct attr_hashmap g_attr_hashmap;\n-\n /* The container for objects stored in \"struct attr_hashmap\" */\n struct attr_hash_entry {\n \tstruct hashmap_entry ent;\n@@ -80,11 +73,14 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n \treturn (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n }\n \n-/* Initialize an 'attr_hashmap' object */\n-static void attr_hashmap_init(struct attr_hashmap *map)\n-{\n-\thashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n-}\n+/*\n+ * The global dictionary of all interned attributes.  This\n+ * is a singleton object which is shared between threads.\n+ * Access to this dictionary must be surrounded with a mutex.\n+ */\n+static struct attr_hashmap g_attr_hashmap = {\n+\tHASHMAP_INIT(attr_hash_entry_cmp, NULL)\n+};\n \n /*\n  * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n@@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n \tstruct attr_hash_entry k;\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \thashmap_entry_init(&k.ent, memhash(key, keylen));\n \tk.key = key;\n \tk.keylen = keylen;\n@@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n {\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \te = xmalloc(sizeof(struct attr_hash_entry));\n \thashmap_entry_init(&e->ent, memhash(key, keylen));\n \te->key = key;\ndiff --git a/bloom.c b/bloom.c\nindex 719c313a1c..b176f28f53 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -229,10 +229,9 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \tdiffcore_std(&diffopt);\n \n \tif (diff_queued_diff.nr <= settings->max_changed_paths) {\n-\t\tstruct hashmap pathmap;\n+\t\tstruct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL);\n \t\tstruct pathmap_hash_entry *e;\n \t\tstruct hashmap_iter iter;\n-\t\thashmap_init(&pathmap, pathmap_cmp, NULL, 0);\n \n \t\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n \t\t\tconst char *path = diff_queued_diff.queue[i]->two->path;\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..6e18e623fd 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \tconst char *workdir, *tmp;\n \tint ret = 0, i;\n \tFILE *fp;\n-\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,\n+\t\t\t\t\t\t\tNULL);\n+\tstruct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);\n+\tstruct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);\n \tstruct hashmap_iter iter;\n \tstruct pair_entry *entry;\n \tstruct index_state wtindex;\n@@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \trdir_len = rdir.len;\n \twtdir_len = wtdir.len;\n \n-\thashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);\n-\thashmap_init(&submodules, pair_cmp, NULL, 0);\n-\thashmap_init(&symlinks2, pair_cmp, NULL, 0);\n-\n \tchild.no_stdin = 1;\n \tchild.git_cmd = 1;\n \tchild.use_shell = 0;\ndiff --git a/range-diff.c b/range-diff.c\nindex befeecae44..b9950f10c8 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n \n static void find_exact_matches(struct string_list *a, struct string_list *b)\n {\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);\n \tint i;\n \n-\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n-\n \t/* First, add the patches of a to a hash map */\n \tfor (i = 0; i < a->nr; i++) {\n \t\tstruct patch_util *util = a->items[i].util;\ndiff --git a/revision.c b/revision.c\nindex f27649d45d..c6e169e3eb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -124,11 +124,6 @@ static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,\n \treturn strcmp(e1->path, e2->path);\n }\n \n-static void paths_and_oids_init(struct hashmap *map)\n-{\n-\thashmap_init(map, path_and_oids_cmp, NULL, 0);\n-}\n-\n static void paths_and_oids_clear(struct hashmap *map)\n {\n \tstruct hashmap_iter iter;\n@@ -213,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \t\t\t\t     struct oidset *trees)\n {\n \tunsigned has_interesting = 0, has_uninteresting = 0;\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL);\n \tstruct hashmap_iter map_iter;\n \tstruct path_and_oids_entry *entry;\n \tstruct object_id *oid;\n@@ -237,8 +232,6 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \tif (!has_uninteresting || !has_interesting)\n \t\treturn;\n \n-\tpaths_and_oids_init(&map);\n-\n \toidset_iter_init(trees, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n \t\tstruct tree *tree = lookup_tree(r, oid);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 2475663b49..36ff07bd4b 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -151,12 +151,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n int cmd__hashmap(int argc, const char **argv)\n {\n \tstruct strbuf line = STRBUF_INIT;\n-\tstruct hashmap map;\n \tint icase;\n+\tstruct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);\n \n \t/* init hash map */\n \ticase = argc > 1 && !strcmp(\"ignorecase\", argv[1]);\n-\thashmap_init(&map, test_entry_cmp, &icase, 0);\n \n \t/* process commands from stdin */\n \twhile (strbuf_getline(&line, stdin) != EOF) {\n-- \ngitgitgadget\n"},{"id":"408889","messageId":"42633b8d03008a159bc42bde319f50e87ddb00f6.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 07/13] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:07Z","receivedAt":"2020-11-02T18:55:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_get_size()\n  * strmap_empty()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_get_entry()\n\nI suspect the first four are self-explanatory.\n\nstrmap_get_entry() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstrmap_entry that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_entry()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via entry->value\n  * directly update entry->value\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 20 ++++++++++++++++++++\n strmap.h | 36 ++++++++++++++++++++++++++++++++++++\n 2 files changed, 56 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 53f284eb20..829f1bc095 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -87,6 +87,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str);\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n@@ -97,3 +102,18 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_strmap_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_value)\n+{\n+\tstruct strmap_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (!ret)\n+\t\treturn;\n+\tif (free_value)\n+\t\tfree(ret->value);\n+\tif (map->strdup_strings)\n+\t\tfree((char*)ret->key);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 96888c23ad..ee4307cca5 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -50,6 +50,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -62,4 +68,34 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn strmap_get_size(map) == 0;\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, 0); \\\n+\t\tvar; \\\n+\t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"408890","messageId":"ea942eb8035686af4b284e66affa3a0a1d2a5b0c.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 08/13] strmap: enable faster clearing and reusing of strmaps","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:08Z","receivedAt":"2020-11-02T18:55:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen strmaps are used heavily, such as is done by my new merge-ort\nalgorithm, and strmaps need to be cleared but then re-used (because of\ne.g. picking multiple commits to cherry-pick, or due to a recursive\nmerge having several different merges while recursing), free-ing and\nreallocating map->table repeatedly can add up in time, especially since\nit will likely be reallocated to a much smaller size but the previous\nmerge provides a good guide to the right size to use for the next merge.\n\nIntroduce strmap_partial_clear() to take advantage of this type of\nsituation; it will act similar to strmap_clear() except that\nmap->table's entries are zeroed instead of map->table being free'd.\nMaking use of this function reduced the cost of\nclear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped\nthe overall runtime of my rebase testcase by just under 2%.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 6 ++++++\n strmap.h | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 829f1bc095..c410c5241a 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_values)\n \thashmap_clear(&map->map);\n }\n \n+void strmap_partial_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_partial_clear(&map->map);\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\ndiff --git a/strmap.h b/strmap.h\nindex ee4307cca5..10b4642860 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -42,6 +42,12 @@ void strmap_init_with_options(struct strmap *map,\n  */\n void strmap_clear(struct strmap *map, int free_values);\n \n+/*\n+ * Similar to strmap_clear() but leaves map->map->table allocated and\n+ * pre-sized so that subsequent uses won't need as many rehashings.\n+ */\n+void strmap_partial_clear(struct strmap *map, int free_values);\n+\n /*\n  * Insert \"str\" into the map, pointing to \"data\".\n  *\n-- \ngitgitgadget\n\n"},{"id":"408893","messageId":"448d3b219ffebbc0daa4ef033d78fd45693c5ccd.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 06/13] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:06Z","receivedAt":"2020-11-02T18:55:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nNote that similar string-list, I have a strdup_strings setting.\nHowever, unlike string-list, strmap_init() does not take a parameter for\nthis setting and instead automatically sets it to 1; callers who want to\ncontrol this detail need to instead call strmap_init_with_options().\n(Future patches will add additional parameters to\nstrmap_init_with_options()).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |  1 +\n strmap.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h | 65 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 165 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..777a34c01c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += strvec.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..53f284eb20\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata)\n+{\n+\tconst struct strmap_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct strmap_entry, ent);\n+\te2 = container_of(entry2, const struct strmap_entry, ent);\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static struct strmap_entry *find_strmap_entry(struct strmap *map,\n+\t\t\t\t\t      const char *str)\n+{\n+\tstruct strmap_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\tstrmap_init_with_options(map, 1);\n+}\n+\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings)\n+{\n+\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n+}\n+\n+static void strmap_free_entries_(struct strmap *map, int free_values)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\t/*\n+\t * We need to iterate over the hashmap entries and free\n+\t * e->key and e->value ourselves; hashmap has no API to\n+\t * take care of that for us.  Since we're already iterating over\n+\t * the hashmap, though, might as well free e too and avoid the need\n+\t * to make some call into the hashmap API to do that.\n+\t */\n+\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\tif (free_values)\n+\t\t\tfree(e->value);\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)e->key);\n+\t\tfree(e);\n+\t}\n+}\n+\n+void strmap_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_clear(&map->map);\n+}\n+\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->value;\n+\t\tentry->value = data;\n+\t} else {\n+\t\tconst char *key = str;\n+\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\t\tif (map->strdup_strings)\n+\t\t\tkey = xstrdup(str);\n+\t\tentry->key = key;\n+\t\tentry->value = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\treturn entry ? entry->value : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..96888c23ad\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,65 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n+};\n+\n+struct strmap_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *key;\n+\tvoid *value;\n+};\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata);\n+\n+#define STRMAP_INIT { \\\n+\t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.strdup_strings = 1,                          \\\n+\t\t    }\n+\n+/*\n+ * Initialize the members of the strmap.  Any keys added to the strmap will\n+ * be strdup'ed with their memory managed by the strmap.\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Same as strmap_init, but for those who want to control the memory management\n+ * carefully instead of using the default of strdup_strings=1.\n+ */\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\".\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"408892","messageId":"7f93cbb525704c0bd254181082e3ed1a2782a2d2.1604343314.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v3 12/13] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-02T18:55:12Z","receivedAt":"2020-11-02T18:55:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nBy default, we do not use a mempool and strdup_strings is true; in this\ncase, we can avoid both an extra allocation and an extra free by just\nover-allocating for the strmap_entry leaving enough space at the end to\ncopy the key.  FLEXPTR_ALLOC_STR exists for exactly this purpose, so\nmake use of it.\n\nAlso, adjust the case when we are using a memory pool and strdup_strings\nis true to just do one allocation from the memory pool instead of two so\nthat the strmap_clear() and strmap_remove() code can just avoid freeing\nthe key in all cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 35 ++++++++++++++++++-----------------\n strmap.h |  1 +\n 2 files changed, 19 insertions(+), 17 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 34bca92522..9abd47fd4b 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -59,11 +59,8 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (!map->pool) {\n-\t\t\tif (map->strdup_strings)\n-\t\t\t\tfree((char*)e->key);\n+\t\tif (!map->pool)\n \t\t\tfree(e);\n-\t\t}\n \t}\n }\n \n@@ -88,16 +85,23 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \t\told = entry->value;\n \t\tentry->value = data;\n \t} else {\n-\t\tconst char *key = str;\n-\n-\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n-\t\t\t\t  : xmalloc(sizeof(*entry));\n+\t\tif (map->strdup_strings) {\n+\t\t\tif (!map->pool) {\n+\t\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n+\t\t\t} else {\n+\t\t\t\t/* Remember +1 for nul byte twice below */\n+\t\t\t\tsize_t len = strlen(str);\n+\t\t\t\tentry = mem_pool_alloc(map->pool,\n+\t\t\t\t\t       st_add3(sizeof(*entry), len, 1));\n+\t\t\t\tmemcpy(entry->keydata, str, len+1);\n+\t\t\t}\n+\t\t} else if (!map->pool) {\n+\t\t\tentry = xmalloc(sizeof(*entry));\n+\t\t} else {\n+\t\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n+\t\t}\n \t\thashmap_entry_init(&entry->ent, strhash(str));\n-\n-\t\tif (map->strdup_strings)\n-\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n-\t\t\t\t\t: xstrdup(str);\n-\t\tentry->key = key;\n+\t\tentry->key = map->strdup_strings ? entry->keydata : str;\n \t\tentry->value = data;\n \t\thashmap_add(&map->map, &entry->ent);\n \t}\n@@ -130,11 +134,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (!map->pool) {\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)ret->key);\n+\tif (!map->pool)\n \t\tfree(ret);\n-\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex 6ffa6afb6a..0dd80b276e 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -14,6 +14,7 @@ struct strmap_entry {\n \tstruct hashmap_entry ent;\n \tconst char *key;\n \tvoid *value;\n+\tchar keydata[FLEX_ARRAY]; /* if strdup_strings=1, key == &keydata[0] */\n };\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n-- \ngitgitgadget\n\n"},{"id":"409005","messageId":"20201103160818.GB444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BHRSottxaYO+Ot5tPpNCf4scxmtwBLZicMJ4ChNEhmGHA@mail.gmail.com","subject":"Re: [PATCH v2 03/10] hashmap: allow re-use after hashmap_free()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:08:18Z","receivedAt":"2020-11-03T16:08:22Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 08:37:42AM -0700, Elijah Newren wrote:\n\n> > This part I disagree with. If we did:\n> >\n> >   #define HASHMAP_INIT(fn, data) = { .cmpfn = cmpfn, cmpfn_data = data }\n> >\n> > then many callers could avoid handling the lazy-init themselves. E.g.:\n> \n> Ah, gotcha.  That makes sense to me.  Given that 43 out of 47 callers\n> of hashmap_init use cmpfn_data = NULL, should I shorten it to just one\n> parameter for the macro, and let the four special cases keep calling\n> hashmap_init() to specify a non-NULL cmpfn_data?\n\nI'd be fine with it either way. I actually wrote it without the data\nparameter at first, then changed my mine and added it in. ;)\n\nYou could also do:\n\n  #define HASHMAP_INIT_DATA(fn, data) { .cmpfn = cmpfn, cmpfn_data = data }\n  #define HASHMAP_INIT(fn) HASHMAP_INIT_DATA(fn, NULL)\n\nif you want to keep most callers simple.\n\n-Peff\n"},{"id":"409006","messageId":"20201103161000.GC444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BEV6ZNsA_qqV67P=W4gASLGoggoR4wP2R6kf0eNfcsDCw@mail.gmail.com","subject":"Re: [PATCH v2 04/10] hashmap: introduce a new hashmap_partial_clear()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:10:00Z","receivedAt":"2020-11-03T16:10:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 09:03:38AM -0700, Elijah Newren wrote:\n\n> > It would be nice if we had some actual perf numbers to report here, so\n> > we could know exactly how much it was buying us. But I guess things are\n> > a bit out-of-order there. You want to do this series first and then\n> > build merge-ort on top as a user. We could introduce the basic data\n> > structure first, then merge-ort, and then start applying optimizations\n> > with real-world measurements. But I'm not sure it's worth the amount of\n> > time you'd have to spend to reorganize in that way.\n> \n> Yeah, the perf benefits didn't really come until I added a\n> strmap_clear() based on this, so as you discovered I put perf numbers\n> in patch 7 of this series.  Should I add a mention of the later commit\n> message at this point in the series?\n\nNah, I think it's OK as it is. That kind of thing matters more for\nreviewing than when you find the commit later on. And we're already\ndiscussing it during the review.\n\n-Peff\n"},{"id":"409008","messageId":"20201103161239.GD444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BEBQ0kAQVRuXRoZnRAm_BmAXUKv9XtC+R4UH0VDZBw6Sg@mail.gmail.com","subject":"Re: [PATCH v2 06/10] strmap: add more utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:12:39Z","receivedAt":"2020-11-03T16:12:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 09:43:33AM -0700, Elijah Newren wrote:\n\n> > I suspect we need the same \"var = NULL\" that hashmap recently got in\n> > 0ad621f61e (hashmap_for_each_entry(): workaround MSVC's runtime check\n> > failure #3, 2020-09-30). Alternatively, I think you could drop\n> > OFFSETOF_VAR completely in favor offsetof(struct strmap_entry, ent).\n> >\n> > In fact, since we know the correct type for \"var\", we _could_ declare it\n> > ourselves in a new block enclosing the loop. But that is probably making\n> > the code too magic; people reading the code would say \"huh? where is\n> > entry declared?\".\n> \n> Actually, since we know ent is the first entry in strmap, the offset\n> is always 0.  So can't we just avoid OFFSETOF_VAR() and offsetof()\n> entirely, by just using hashmap_iter_first() and hashmap_iter_next()?\n> I'm going to try that.\n\nYes, I think that would work fine. You may want to add a comment to the\nstruct indicating that it's important for the hashmap_entry to be at the\nfront of the struct. Using offsetof() means that it's impossible to get\nit wrong, though.\n\n-Peff\n"},{"id":"409009","messageId":"CABPp-BH7A7jXWm28+gpFHacMnteNN7gRLpWT6YaVqqkAffn8Mg@mail.gmail.com","threadId":"54087","inReplyTo":"20201103160818.GB444466@coredump.intra.peff.net","subject":"Re: [PATCH v2 03/10] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-03T16:16:08Z","receivedAt":"2020-11-03T16:16:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Nov 3, 2020 at 8:08 AM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Oct 30, 2020 at 08:37:42AM -0700, Elijah Newren wrote:\n>\n> > > This part I disagree with. If we did:\n> > >\n> > >   #define HASHMAP_INIT(fn, data) = { .cmpfn = cmpfn, cmpfn_data = data }\n> > >\n> > > then many callers could avoid handling the lazy-init themselves. E.g.:\n> >\n> > Ah, gotcha.  That makes sense to me.  Given that 43 out of 47 callers\n> > of hashmap_init use cmpfn_data = NULL, should I shorten it to just one\n> > parameter for the macro, and let the four special cases keep calling\n> > hashmap_init() to specify a non-NULL cmpfn_data?\n>\n> I'd be fine with it either way. I actually wrote it without the data\n> parameter at first, then changed my mine and added it in. ;)\n>\n> You could also do:\n>\n>   #define HASHMAP_INIT_DATA(fn, data) { .cmpfn = cmpfn, cmpfn_data = data }\n>   #define HASHMAP_INIT(fn) HASHMAP_INIT_DATA(fn, NULL)\n>\n> if you want to keep most callers simple.\n\nI ended up going with your HASHMAP_INIT(fn, data) in v3 that I\nsubmitted yesterday (except that you have a stray '=', are missing a\n'.' in front of cmpfn_data, and you'll trigger BUG()s if you don't\nalso add .do_count_items = 1, but those are all minor fixups).  In the\nfuture, if we determine we want/need the extra simplicity then we can\nalways convert to this newer suggestion.  I don't think it's that big\na deal either way.\n"},{"id":"409012","messageId":"20201103162013.GE444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BFyqTthyBmp5yt+iUniwTi+=y2QcBcmNnnCy=zvyi3Rbw@mail.gmail.com","subject":"Re: [PATCH v2 08/10] strmap: add functions facilitating use as a string->int map","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:20:13Z","receivedAt":"2020-11-03T16:20:15Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 10:28:51AM -0700, Elijah Newren wrote:\n\n> > You might want to mention that this _could_ be done as just accessors to\n> > strmap, but using a separate struct provides type safety against\n> > misusing pointers as integers or vice versa.\n> \n> If I just did it as accessors, it makes it harder for myself and\n> others to remember what my huge piles of strmaps in merge-ort do; I\n> found that it became easier to follow the code and remember what\n> things were doing when some were marked as strmap, some as strintmap,\n> and some as strset.\n\nOh, I'm definitely on board with that argument. I was just suggesting\nyou might want to put it in the commit message for posterity.\n\n> > > +/*\n> > > + * strintmap:\n> > > + *    A map of string -> int, typecasting the void* of strmap to an int.\n> >\n> > Are the size and signedness of an int flexible enough for all uses?\n> \n> If some users want signed values and others want unsigned, I'm not\n> sure how we can satisfy both.  Maybe make a struintmap?\n\nRight, that was sort of my question: do your users actually want it\nsigned or not. Sounds like they do want it signed, and don't mind the\nloss of range.\n\n> Perhaps that could be added later if uses come up for it?  Some of my\n> uses need int, the rest of them wouldn't care about int vs unsigned.\n\nYeah, if you don't have any callers which care, I'd definitely punt on\nit for now.\n\n> If someone does care about the full range of bits up to 64 on relevant\n> platforms, I guess I should make it strintptr_t_map.\n\nYeah, that's what I was wondering. I suspect the use case for that is\npretty narrow, though. If you really care about having a 64-bit value\nfor some data, then you probably want it _everywhere_, not just on\n64-bit platforms. I guess the exception would be if you're mapping into\nsize_t's or something.\n\nI think my question was as much \"did you think about range issues for\nyour intended users\" as \"should we provide more range in this map type\".\nAnd it sounds like you have thought about that, so I'm happy proceeding.\n\n> But besides the\n> egregiously ugly name, one advantage of int over intptr_t (or unsigned\n> over uintptr_t) is that you can use it in a printf easily:\n>    printf(\"Size: %d\\n\", strintmap_get(&foo, 0));\n> whereas if it strintmap_get() returns an intptr_t, then it's a royal\n> mess to attempt to portably use it without adding additional manual\n> casts.  Maybe I was just missing something obvious, but I couldn't\n> figure out the %d, %ld, %lld, PRIdMAX, etc. choices and get the\n> statement to compile on all platforms, so I'd always just cast to int\n> or unsigned at the time of calling printf.\n\nThe right way is:\n\n  printf(\"Size: %\"PRIdMAX\", (intmax_t) your_intptr_t);\n\nwhich will always do the right thing no matter the size (at the minor\ncost of passing a larger-than-necessary parameter, but if you're\nmicro-optimizing then calling printf at all is probably already a\nproblem).\n\nBut yeah, in general using a real \"int\" is much more convenient and if\nthere's no reason to avoid it for range problems, I think it's\npreferable.\n\n-Peff\n"},{"id":"409013","messageId":"20201103162430.GF444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BEY5R79WGsMB1y1MEbcvw8EF5-ADxvDPzL+Hn-PiyobsA@mail.gmail.com","subject":"Re: [PATCH v2 10/10] strmap: enable allocations to come from a mem_pool","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:24:30Z","receivedAt":"2020-11-03T16:24:34Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 12:31:13PM -0700, Elijah Newren wrote:\n\n> > I think we could fall back to a FLEXPTR when there's no mempool (or even\n> > when there is, though you'd be on your own to reimplement the\n> > computation parts of FLEXPTR_ALLOC). I'm not sure how ugly it would end\n> > up.\n> \n> Yeah, we'd need a mempool-specific reimplementation of FLEXPTR_ALLOC\n> with the mempool, and just avoid using it at all whenever\n> strdup_strings was 0.  Seems slightly ugly, but maybe it wouldn't be\n> too bad.  I could look into it.\n\nIt looks like you went this route (fall back to FLEXPTR) in the re-roll\nyou posted. I haven't looked at it carefully yet, but I suspect it will\nbe just fine to me (I probably would have accepted \"no, it makes the\ncode too ugly; if you want efficiency use a mempool\" as well, but I'll\nsee how ugly it turned out. ;) ).\n\n> Anyway, at the time I\n> put the mempool into strmaps and made use of it in relevant places,\n> one of my rebase testcases saw an almost 5% reduction in overall\n> execution time.  I'm sure it would have been over 5% if I had\n> reordered it to come after my final rename optimization.\n\nThanks, it's nice to have a ballpark like that. It might be worth\nputting it into the commit message, even if it's hand-wavy:\n\n  This seemed to provide about 5% speedup for some rebase test cases I\n  ran. Unfortunately you can't just time this commit and its parent,\n  since we aren't yet actually using strmap in the code yet.\n\nBut again, I think the main value of that is during review, so if it\ndoesn't make it into the commit message, I'm OK.\n\n-Peff\n"},{"id":"409014","messageId":"20201103162607.GG444466@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BGdM0tkQgqRQzzpTOyKPoQVLsppK_EUcthAO8LbtzcTNA@mail.gmail.com","subject":"Re: [PATCH v2 01/10] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-03T16:26:07Z","receivedAt":"2020-11-03T16:26:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Oct 30, 2020 at 12:55:51PM -0700, Elijah Newren wrote:\n\n> > But I think in the current scheme that \"free\" is somewhat overloaded,\n> > and if we end with a \"clear\" and a \"free\" that seems confusing to me.\n> \n> Hmm...there are quite a few calls to hashmap_free() and\n> hashmap_free_entries() throughout the codebase.  I'm wondering if I\n> should make switching these over to your new naming suggestions a\n> separate follow-on series from this one, so that if there are any\n> conflicts with other series it doesn't need to hold these first 10\n> patches up.\n\nYeah, it will definitely need a lot of mechanical fix-up. Those kinds of\nconflicts aren't usually a big deal. Junio will have to resolve them,\nbut if the resolution is easy and mechanical, then it's not likely to\nhold up either topic.\n\n> If I do that, I could also add a patch to convert several callers of\n> hashmap_init() to use the new HASHMAP_INIT() macro, and another patch\n> to convert shortlog to using my strset instead of its own.\n\nYeah, both would be nice. I'm happy if it comes as part of the series,\nor separately on top.\n\n-Peff\n"},{"id":"409016","messageId":"CABPp-BGnjxk--2oAiKPmCdEiwYL4zE338dfpE+tz9SEk5j42Bw@mail.gmail.com","threadId":"54087","inReplyTo":"20201103162013.GE444466@coredump.intra.peff.net","subject":"Re: [PATCH v2 08/10] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-03T16:46:16Z","receivedAt":"2020-11-03T16:46:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Nov 3, 2020 at 8:20 AM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Oct 30, 2020 at 10:28:51AM -0700, Elijah Newren wrote:\n>\n> > > You might want to mention that this _could_ be done as just accessors to\n> > > strmap, but using a separate struct provides type safety against\n> > > misusing pointers as integers or vice versa.\n> >\n> > If I just did it as accessors, it makes it harder for myself and\n> > others to remember what my huge piles of strmaps in merge-ort do; I\n> > found that it became easier to follow the code and remember what\n> > things were doing when some were marked as strmap, some as strintmap,\n> > and some as strset.\n>\n> Oh, I'm definitely on board with that argument. I was just suggesting\n> you might want to put it in the commit message for posterity.\n>\n> > > > +/*\n> > > > + * strintmap:\n> > > > + *    A map of string -> int, typecasting the void* of strmap to an int.\n> > >\n> > > Are the size and signedness of an int flexible enough for all uses?\n> >\n> > If some users want signed values and others want unsigned, I'm not\n> > sure how we can satisfy both.  Maybe make a struintmap?\n>\n> Right, that was sort of my question: do your users actually want it\n> signed or not. Sounds like they do want it signed, and don't mind the\n> loss of range.\n>\n> > Perhaps that could be added later if uses come up for it?  Some of my\n> > uses need int, the rest of them wouldn't care about int vs unsigned.\n>\n> Yeah, if you don't have any callers which care, I'd definitely punt on\n> it for now.\n>\n> > If someone does care about the full range of bits up to 64 on relevant\n> > platforms, I guess I should make it strintptr_t_map.\n>\n> Yeah, that's what I was wondering. I suspect the use case for that is\n> pretty narrow, though. If you really care about having a 64-bit value\n> for some data, then you probably want it _everywhere_, not just on\n> 64-bit platforms. I guess the exception would be if you're mapping into\n> size_t's or something.\n>\n> I think my question was as much \"did you think about range issues for\n> your intended users\" as \"should we provide more range in this map type\".\n> And it sounds like you have thought about that, so I'm happy proceeding.\n>\n> > But besides the\n> > egregiously ugly name, one advantage of int over intptr_t (or unsigned\n> > over uintptr_t) is that you can use it in a printf easily:\n> >    printf(\"Size: %d\\n\", strintmap_get(&foo, 0));\n> > whereas if it strintmap_get() returns an intptr_t, then it's a royal\n> > mess to attempt to portably use it without adding additional manual\n> > casts.  Maybe I was just missing something obvious, but I couldn't\n> > figure out the %d, %ld, %lld, PRIdMAX, etc. choices and get the\n> > statement to compile on all platforms, so I'd always just cast to int\n> > or unsigned at the time of calling printf.\n>\n> The right way is:\n>\n>   printf(\"Size: %\"PRIdMAX\", (intmax_t) your_intptr_t);\n\nAh, intmax_t; that's what I was missing.\n\n> which will always do the right thing no matter the size (at the minor\n> cost of passing a larger-than-necessary parameter, but if you're\n> micro-optimizing then calling printf at all is probably already a\n> problem).\n>\n> But yeah, in general using a real \"int\" is much more convenient and if\n> there's no reason to avoid it for range problems, I think it's\n> preferable.\n\nYep, I like the simplicity of \"int\", the signedness of \"int\" and it\nhas far more than enough range on all platforms (most my strintmaps\nactually map to enum values, but my largest int usage is for counting\nup to at most how many files are involved in rename detection.  Even\nmicrosoft repos only have a number of files present in the repository\nthat registers in the low millions, and I'm only dealing with the\nsubset of those files involved in rename detection, which should be\nmuch smaller).\n"},{"id":"409017","messageId":"CABPp-BHvD+JkZ60i=KQ=sxQYZcmu0OrfXUKq_2vmUkT1jEmiRg@mail.gmail.com","threadId":"54087","inReplyTo":"20201103162607.GG444466@coredump.intra.peff.net","subject":"Re: [PATCH v2 01/10] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-03T16:48:37Z","receivedAt":"2020-11-03T16:48:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Nov 3, 2020 at 8:26 AM Jeff King <peff@peff.net> wrote:\n>\n> On Fri, Oct 30, 2020 at 12:55:51PM -0700, Elijah Newren wrote:\n>\n> > > But I think in the current scheme that \"free\" is somewhat overloaded,\n> > > and if we end with a \"clear\" and a \"free\" that seems confusing to me.\n> >\n> > Hmm...there are quite a few calls to hashmap_free() and\n> > hashmap_free_entries() throughout the codebase.  I'm wondering if I\n> > should make switching these over to your new naming suggestions a\n> > separate follow-on series from this one, so that if there are any\n> > conflicts with other series it doesn't need to hold these first 10\n> > patches up.\n>\n> Yeah, it will definitely need a lot of mechanical fix-up. Those kinds of\n> conflicts aren't usually a big deal. Junio will have to resolve them,\n> but if the resolution is easy and mechanical, then it's not likely to\n> hold up either topic.\n>\n> > If I do that, I could also add a patch to convert several callers of\n> > hashmap_init() to use the new HASHMAP_INIT() macro, and another patch\n> > to convert shortlog to using my strset instead of its own.\n>\n> Yeah, both would be nice. I'm happy if it comes as part of the series,\n> or separately on top.\n\nAfter sending the email, I ended up deciding to convert the callers\njust to sanity check the HASHMAP_INIT macro and discovered that the\ncode will BUG() if you don't also include .do_count_items = 1.  So, I\njust decided to include that in the v3 of the series after all.\n"},{"id":"409136","messageId":"20201104201327.GA3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"42633b8d03008a159bc42bde319f50e87ddb00f6.1604343314.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/13] strmap: add more utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:13:27Z","receivedAt":"2020-11-04T20:13:30Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:07PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> +/*\n> + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n> + */\n> +#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n> +\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, 0); \\\n> +\t\tvar; \\\n> +\t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n> +\n\nI think this resolves my offset question from the last round. But I\nwonder if you tried:\n\n  #define strmap_for_each_entry(mystrmap, iter, var) \\\n\thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n\nwhich is a bit more abstract and should function the same (I think; I\ndidn't try it).\n\n-Peff\n"},{"id":"409139","messageId":"20201104202152.GB3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"c1d217217180cff0b048d5dbac57b8e363db7848.1604343314.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 09/13] strmap: add functions facilitating use as a string->int map","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:21:52Z","receivedAt":"2020-11-04T20:21:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:09PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Although strmap could be used as a string->int map, one either had to\n> allocate an int for every entry and then deallocate later, or one had to\n> do a bunch of casting between (void*) and (intptr_t).\n> \n> Add some special functions that do the casting.  Also, rename put->set\n> for such wrapper functions since 'put' implied there may be some\n> deallocation needed if the string was already found in the map, which\n> isn't the case when we're storing an int value directly in the void*\n> slot instead of using the void* slot as a pointer to data.\n> \n> A note on the name: if anyone has a better name suggestion than\n> strintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\n> not been able to come up with a better name.\n\nYou can probably drop this last paragraph. It's good for review, but\nprobably not in the commit message. :)\n\n> +void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n> +{\n> +\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n> +\tif (entry) {\n> +\t\tintptr_t *whence = (intptr_t*)&entry->value;\n> +\t\t*whence += amt;\n> +\t}\n> +\telse\n> +\t\tstrintmap_set(map, str, map->default_value + amt);\n> +}\n\nHere we use the new default_value. Neat.\n\n> diff --git a/strmap.h b/strmap.h\n> index 10b4642860..31474f781e 100644\n> --- a/strmap.h\n> +++ b/strmap.h\n> @@ -23,6 +23,11 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n>  \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n>  \t\t\t.strdup_strings = 1,                          \\\n>  \t\t    }\n> +#define STRINTMAP_INIT { \\\n> +\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n> +\t\t\t.map.strdup_strings = 1,                          \\\n> +\t\t\t.default_value = 0,                               \\\n> +\t\t    }\n\nRe-using STRMAP_INIT would shorten this (and avoid repeating internal\ndetails of how strmap works). Like:\n\n  #define STRINTMAP_INIT { \\\n\t.map = STRMAP_INIT, \\\n\t.default_value = 0, \\\n  }\n\nYou can also omit default_value, as the value of any un-mentioned\nelements will get the usual C zero-initialization. So:\n\n  #define STRINTMAP_INIT { .map = STRMAP_INIT }\n\nwould be sufficient (though I don't mind making the .default_value part\nexplicit). It could also be a parameter to the macro, but I suspect it\nwould be rarely used. I don't mind leaving it as something that advanced\ncallers can get from using strintmap_init().\n\n> +/*\n> + * strintmap:\n> + *    A map of string -> int, typecasting the void* of strmap to an int.\n> + *\n> + * Primary differences:\n> + *    1) Since the void* value is just an int in disguise, there is no value\n> + *       to free.  (Thus one fewer argument to strintmap_clear)\n> + *    2) strintmap_get() returns an int; it also requires an extra parameter to\n> + *       be specified so it knows what value to return if the underlying strmap\n> + *       has not key matching the given string.\n> + *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n> + *       instead.\n> + */\n\nI think (2) here is out-of-date, as we now use map->default_value.\n\n> +/*\n> + * Returns the value for str in the map.  If str isn't found in the map,\n> + * the map's default_value is returned.\n> + */\n> +static inline int strintmap_get(struct strintmap *map, const char *str)\n> +{\n> +\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n> +\tif (!result)\n> +\t\treturn map->default_value;\n> +\treturn (intptr_t)result->value;\n> +}\n\nAnd we get to reuse default_value here again. Nice.\n\n-Peff\n"},{"id":"409140","messageId":"CABPp-BF7kEaX=d8Wm=BQKqGBS1+4AexMJCPtzFtpaDtOjm-g1w@mail.gmail.com","threadId":"54087","inReplyTo":"20201104201327.GA3629238@coredump.intra.peff.net","subject":"Re: [PATCH v3 07/13] strmap: add more utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-04T20:24:08Z","receivedAt":"2020-11-04T20:24:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Nov 4, 2020 at 12:13 PM Jeff King <peff@peff.net> wrote:\n>\n> On Mon, Nov 02, 2020 at 06:55:07PM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > +/*\n> > + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n> > + */\n> > +#define strmap_for_each_entry(mystrmap, iter, var)   \\\n> > +     for (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, 0); \\\n> > +             var; \\\n> > +             var = hashmap_iter_next_entry_offset(iter, 0))\n> > +\n>\n> I think this resolves my offset question from the last round. But I\n> wonder if you tried:\n>\n>   #define strmap_for_each_entry(mystrmap, iter, var) \\\n>         hashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n>\n> which is a bit more abstract and should function the same (I think; I\n> didn't try it).\n\nI tried another variant or two besides what I used here, but not the\none you suggest.  Your suggestion seems obvious and nicer now that you\npoint it out.\n"},{"id":"409141","messageId":"20201104203117.GC3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"0f57735f5e30ad61a2e6fdb118067afbcea69660.1604343314.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 10/13] strmap: add a strset sub-type","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:31:17Z","receivedAt":"2020-11-04T20:31:19Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:10PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> +int strset_check_and_add(struct strset *set, const char *str)\n> +{\n> +\tif (strset_contains(set, str))\n> +\t\treturn 1;\n> +\tstrset_add(set, str);\n> +\treturn 0;\n> +}\n\nWith this implementation, I wonder if it is worth having such a\nspecialized function. The value of an atomic check-and-add operation is\nthat it can reuse the effort to hash the string for both operations (it\ncould also reuse any open-table probing effort, but for a chained hash\nlike our implementation, it's cheap to add a new entry to the front of\nthe list).\n\nI doubt it matters all that much for the use case in shortlog. Perhaps\nwe should just open-code it there for now, and we can revisit it if\nanother user comes up.\n\n> --- a/strmap.h\n> +++ b/strmap.h\n> @@ -28,6 +28,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n>  \t\t\t.map.strdup_strings = 1,                          \\\n>  \t\t\t.default_value = 0,                               \\\n>  \t\t    }\n> +#define STRSET_INIT { \\\n> +\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n> +\t\t\t.map.strdup_strings = 1,                          \\\n> +\t\t    }\n\nAs with strint, this could be:\n\n  #define STRSET_INIT { .map = STRMAP_INIT }\n\n-Peff\n"},{"id":"409163","messageId":"20201104204349.GD3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"7f93cbb525704c0bd254181082e3ed1a2782a2d2.1604343314.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 12/13] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:43:49Z","receivedAt":"2020-11-04T20:43:52Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:12PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> By default, we do not use a mempool and strdup_strings is true; in this\n> case, we can avoid both an extra allocation and an extra free by just\n> over-allocating for the strmap_entry leaving enough space at the end to\n> copy the key.  FLEXPTR_ALLOC_STR exists for exactly this purpose, so\n> make use of it.\n> \n> Also, adjust the case when we are using a memory pool and strdup_strings\n> is true to just do one allocation from the memory pool instead of two so\n> that the strmap_clear() and strmap_remove() code can just avoid freeing\n> the key in all cases.\n\nThis turned out to be much less painful than I feared, and I think is\nworth doing. Thanks for digging on it.\n\n> +\t\tif (map->strdup_strings) {\n> +\t\t\tif (!map->pool) {\n> +\t\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n> +\t\t\t} else {\n> +\t\t\t\t/* Remember +1 for nul byte twice below */\n> +\t\t\t\tsize_t len = strlen(str);\n> +\t\t\t\tentry = mem_pool_alloc(map->pool,\n> +\t\t\t\t\t       st_add3(sizeof(*entry), len, 1));\n> +\t\t\t\tmemcpy(entry->keydata, str, len+1);\n> +\t\t\t}\n\nPerhaps:\n\n  size_t len = st_add(strlen(str), 1); /* include NUL */\n  entry = mem_pool_alloc(map->pool, st_add(sizeof(*entry), len));\n  memcpy(entry->keydata, str, len);\n\nwould be more obvious than the \"remember to do it twice\" comment?\n\nWith a FLEXPTR, I don't think you need keydata at all (since we would\nnever use that name; note that we don't even pass it in at all to\nFLEXPTR_ALLOC_STR). Without that, I think your memcpy becomes:\n\n  memcpy(entry + 1, str, len);\n\nRemember that \"entry\" is a typed pointer, so \"1\" is really moving\nsizeof(*entry) bytes.\n\n> +\t\t} else if (!map->pool) {\n> +\t\t\tentry = xmalloc(sizeof(*entry));\n> +\t\t} else {\n> +\t\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n> +\t\t}\n\nOK, so if we're not strdup-ing then we either get a mempool or a fresh\nentry. Makes sense.\n\n>  \t\thashmap_entry_init(&entry->ent, strhash(str));\n> -\n> -\t\tif (map->strdup_strings)\n> -\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n> -\t\t\t\t\t: xstrdup(str);\n> -\t\tentry->key = key;\n> +\t\tentry->key = map->strdup_strings ? entry->keydata : str;\n\nI think this is subtly wrong in the FLEXPTR case. The data isn't in\nkeydata; it's directly after the struct. That's _usually_ the same\nthing, but:\n\n  - the compiler can put struct padding at the end if it wants\n\n  - FLEX_ARRAY is usually zero, but for compatibility on some platforms\n    it must be 1\n\nThe call to FLEXPTR_ALLOC_STR() will have already set it up properly\n(and this is at best writing the same value, and at worst messing it\nup).\n\nI think you probably want to leave the FLEXPTR_ALLOC_STR() part alone,\nput a:\n\n  entry->key = (void *)(entry + 1);\n\nline in the mem_pool code path, and then here do:\n\n  if (!strdup_strings)\n\tentry->key = str;\n\n-Peff\n"},{"id":"409164","messageId":"20201104204809.GE3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"5f41fc63e5355121cf882f63e1b38b8df1948df8.1604343314.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 13/13] Use new HASHMAP_INIT macro to simplify hashmap initialization","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:48:09Z","receivedAt":"2020-11-04T20:48:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:13PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> From: Elijah Newren <newren@gmail.com>\n> \n> Now that hashamp has lazy initialization and a HASHMAP_INIT macro,\n> hashmaps allocated on the stack can be initialized without a call to\n> hashmap_init() and in some cases makes the code a bit shorter.  Convert\n> some callsites over to take advantage of this.\n\nThese all look obviously correct. I suspect there are more, but there's\nno need for us to be thorough at this point.\n\n> --- a/range-diff.c\n> +++ b/range-diff.c\n> @@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n>  \n>  static void find_exact_matches(struct string_list *a, struct string_list *b)\n>  {\n> -\tstruct hashmap map;\n> +\tstruct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);\n\nNot related to your patch, but we should in general try to define these\ncomparison functions with the correct signature, rather than cast them.\n\nI think I've already inflated your series enough, so let's leave it for\nnow, but just a mental note that it might be useful to circle back and\nfix these en masse (I know we cleaned up several a while back, so I'm\nactually surprised to see one still here).\n\n-Peff\n"},{"id":"409165","messageId":"20201104205250.GF3629238@coredump.intra.peff.net","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/13] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-04T20:52:50Z","receivedAt":"2020-11-04T20:52:53Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Nov 02, 2020 at 06:55:00PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Changes since v2 (almost all of which were suggestions from Peff):\n\nThanks again for your work on this series, and your willingness to\nlisten to my various suggestions. ;)\n\nThis mostly looks good to me. I pointed out a few minor nits in reply to\nindividual patches, but there's at least one correctness problem, so\nwe'll need a v4.\n\n> Things that I'm still unsure about:\n> \n>  * strintmap_init() takes a default_value parameter, as suggested by Peff.\n>    But this makes the function name strintmap_init_with_options() weird,\n>    because strintmap_init() already takes one option, so it seems like the\n>    name needs to replace \"options\" with \"more_options\". But that's kinda\n>    ugly too. I'm guessing strintmap_init_with_options() is fine as-is, but\n>    I'm wondering if anyone else thinks it looks weird and if so if there is\n>    anything I should do about it.\n\nYou could drop default_value from strintmap_init_with_options(). I'd\n_guess_ most callers would be happy with 0, but you'd know much better\nthan I what your first crop of callers will want.\n\nI'm happy with it either way.\n\n> Things Peff mentioned on v2 that I did NOT do:\n> \n>  * Peff brought up some questions about mapping strintmap to an int rather\n>    than an unsigned or intptr_t. I discussed my rationale in the thread\n\nYeah, I'm well convinced that what you have here is fine.\n\n> Things Peff mentioned on v1 that are still not included and which Peff\n> didn't comment on for v2, but which may still be worth mentioning again:\n> \n>  * Peff brought up the idea of having a free_values member instead of having\n>    a free_values parameter to strmap_clear(). That'd just mean moving the\n>    parameter from strmap_clear() to strmap_init() and would be easy to do,\n>    but he sounded like he was just throwing it out as an idea and I didn't\n>    have a strong opinion, so I left it as-is. If others have\n>    opinions/preferences, changing it is easy right now.\n\nYeah, I was mostly thinking out loud. What you have here looks fine to\nme.\n\n>  * Peff early on wanted the strmap_entry type to have a char key[FLEX_ALLOC]\n>    instead of having a (const) char *key. I spent a couple more days on this\n>    despite him not mentioning it while reviewing v2, and finally got it\n>    working this time and running valgrind-free. Note that such a change\n>    means always copying the key instead of allowing it as an option. After\n>    implementing it, I timed it and it slowed down my important testcase by\n>    just over 6%. So I chucked it. I think the FLEXPTR_ALLOC_STR usage in\n>    combination with defaulting to strdup_strings=1 gives us most the\n>    benefits Peff wanted, while still allowing merge-ort to reuse strings\n>    when it's important.\n\nYes, I'd agree that FLEXPTR is a good middle ground. If I really manage\nto find a caller later where I think the complexity might be worth\nsaving a few bytes, perhaps I'll try it then and get some real\nmeasurements. My guess is that won't ever actually happen. :)\n\n-Peff\n"},{"id":"409174","messageId":"CABPp-BGSgv07wuLQKTvApzmBEEbd7rnzH_xUSJ6gZGtHO6PXPA@mail.gmail.com","threadId":"54087","inReplyTo":"20201104205250.GF3629238@coredump.intra.peff.net","subject":"Re: [PATCH v3 00/13] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-04T22:20:35Z","receivedAt":"2020-11-04T22:21:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Nov 4, 2020 at 12:52 PM Jeff King <peff@peff.net> wrote:\n>\n> On Mon, Nov 02, 2020 at 06:55:00PM +0000, Elijah Newren via GitGitGadget wrote:\n>\n> > Changes since v2 (almost all of which were suggestions from Peff):\n>\n> Thanks again for your work on this series, and your willingness to\n> listen to my various suggestions. ;)\n>\n> This mostly looks good to me. I pointed out a few minor nits in reply to\n> individual patches, but there's at least one correctness problem, so\n> we'll need a v4.\n\nCool, thanks for the careful reviews; I'll fix it up and send a v4 out.\n\n> > Things that I'm still unsure about:\n> >\n> >  * strintmap_init() takes a default_value parameter, as suggested by Peff.\n> >    But this makes the function name strintmap_init_with_options() weird,\n> >    because strintmap_init() already takes one option, so it seems like the\n> >    name needs to replace \"options\" with \"more_options\". But that's kinda\n> >    ugly too. I'm guessing strintmap_init_with_options() is fine as-is, but\n> >    I'm wondering if anyone else thinks it looks weird and if so if there is\n> >    anything I should do about it.\n>\n> You could drop default_value from strintmap_init_with_options(). I'd\n> _guess_ most callers would be happy with 0, but you'd know much better\n> than I what your first crop of callers will want.\n>\n> I'm happy with it either way.\n>\n> > Things Peff mentioned on v2 that I did NOT do:\n> >\n> >  * Peff brought up some questions about mapping strintmap to an int rather\n> >    than an unsigned or intptr_t. I discussed my rationale in the thread\n>\n> Yeah, I'm well convinced that what you have here is fine.\n>\n> > Things Peff mentioned on v1 that are still not included and which Peff\n> > didn't comment on for v2, but which may still be worth mentioning again:\n> >\n> >  * Peff brought up the idea of having a free_values member instead of having\n> >    a free_values parameter to strmap_clear(). That'd just mean moving the\n> >    parameter from strmap_clear() to strmap_init() and would be easy to do,\n> >    but he sounded like he was just throwing it out as an idea and I didn't\n> >    have a strong opinion, so I left it as-is. If others have\n> >    opinions/preferences, changing it is easy right now.\n>\n> Yeah, I was mostly thinking out loud. What you have here looks fine to\n> me.\n>\n> >  * Peff early on wanted the strmap_entry type to have a char key[FLEX_ALLOC]\n> >    instead of having a (const) char *key. I spent a couple more days on this\n> >    despite him not mentioning it while reviewing v2, and finally got it\n> >    working this time and running valgrind-free. Note that such a change\n> >    means always copying the key instead of allowing it as an option. After\n> >    implementing it, I timed it and it slowed down my important testcase by\n> >    just over 6%. So I chucked it. I think the FLEXPTR_ALLOC_STR usage in\n> >    combination with defaulting to strdup_strings=1 gives us most the\n> >    benefits Peff wanted, while still allowing merge-ort to reuse strings\n> >    when it's important.\n>\n> Yes, I'd agree that FLEXPTR is a good middle ground. If I really manage\n> to find a caller later where I think the complexity might be worth\n> saving a few bytes, perhaps I'll try it then and get some real\n> measurements. My guess is that won't ever actually happen. :)\n>\n> -Peff\n"},{"id":"409179","messageId":"617926540b837576a07f8e93272ea729f37077d6.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 13/13] Use new HASHMAP_INIT macro to simplify hashmap initialization","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:45Z","receivedAt":"2020-11-05T00:24:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNow that hashamp has lazy initialization and a HASHMAP_INIT macro,\nhashmaps allocated on the stack can be initialized without a call to\nhashmap_init() and in some cases makes the code a bit shorter.  Convert\nsome callsites over to take advantage of this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n attr.c                  | 26 ++++++++------------------\n bloom.c                 |  3 +--\n builtin/difftool.c      |  9 ++++-----\n range-diff.c            |  4 +---\n revision.c              |  9 +--------\n t/helper/test-hashmap.c |  3 +--\n 6 files changed, 16 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a826b2ef1f..4ef85d668b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -52,13 +52,6 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n \tpthread_mutex_unlock(&map->mutex);\n }\n \n-/*\n- * The global dictionary of all interned attributes.  This\n- * is a singleton object which is shared between threads.\n- * Access to this dictionary must be surrounded with a mutex.\n- */\n-static struct attr_hashmap g_attr_hashmap;\n-\n /* The container for objects stored in \"struct attr_hashmap\" */\n struct attr_hash_entry {\n \tstruct hashmap_entry ent;\n@@ -80,11 +73,14 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n \treturn (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n }\n \n-/* Initialize an 'attr_hashmap' object */\n-static void attr_hashmap_init(struct attr_hashmap *map)\n-{\n-\thashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n-}\n+/*\n+ * The global dictionary of all interned attributes.  This\n+ * is a singleton object which is shared between threads.\n+ * Access to this dictionary must be surrounded with a mutex.\n+ */\n+static struct attr_hashmap g_attr_hashmap = {\n+\tHASHMAP_INIT(attr_hash_entry_cmp, NULL)\n+};\n \n /*\n  * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n@@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n \tstruct attr_hash_entry k;\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \thashmap_entry_init(&k.ent, memhash(key, keylen));\n \tk.key = key;\n \tk.keylen = keylen;\n@@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n {\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \te = xmalloc(sizeof(struct attr_hash_entry));\n \thashmap_entry_init(&e->ent, memhash(key, keylen));\n \te->key = key;\ndiff --git a/bloom.c b/bloom.c\nindex 719c313a1c..b176f28f53 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -229,10 +229,9 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \tdiffcore_std(&diffopt);\n \n \tif (diff_queued_diff.nr <= settings->max_changed_paths) {\n-\t\tstruct hashmap pathmap;\n+\t\tstruct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL);\n \t\tstruct pathmap_hash_entry *e;\n \t\tstruct hashmap_iter iter;\n-\t\thashmap_init(&pathmap, pathmap_cmp, NULL, 0);\n \n \t\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n \t\t\tconst char *path = diff_queued_diff.queue[i]->two->path;\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..6e18e623fd 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \tconst char *workdir, *tmp;\n \tint ret = 0, i;\n \tFILE *fp;\n-\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,\n+\t\t\t\t\t\t\tNULL);\n+\tstruct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);\n+\tstruct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);\n \tstruct hashmap_iter iter;\n \tstruct pair_entry *entry;\n \tstruct index_state wtindex;\n@@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \trdir_len = rdir.len;\n \twtdir_len = wtdir.len;\n \n-\thashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);\n-\thashmap_init(&submodules, pair_cmp, NULL, 0);\n-\thashmap_init(&symlinks2, pair_cmp, NULL, 0);\n-\n \tchild.no_stdin = 1;\n \tchild.git_cmd = 1;\n \tchild.use_shell = 0;\ndiff --git a/range-diff.c b/range-diff.c\nindex befeecae44..b9950f10c8 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n \n static void find_exact_matches(struct string_list *a, struct string_list *b)\n {\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);\n \tint i;\n \n-\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n-\n \t/* First, add the patches of a to a hash map */\n \tfor (i = 0; i < a->nr; i++) {\n \t\tstruct patch_util *util = a->items[i].util;\ndiff --git a/revision.c b/revision.c\nindex f27649d45d..c6e169e3eb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -124,11 +124,6 @@ static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,\n \treturn strcmp(e1->path, e2->path);\n }\n \n-static void paths_and_oids_init(struct hashmap *map)\n-{\n-\thashmap_init(map, path_and_oids_cmp, NULL, 0);\n-}\n-\n static void paths_and_oids_clear(struct hashmap *map)\n {\n \tstruct hashmap_iter iter;\n@@ -213,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \t\t\t\t     struct oidset *trees)\n {\n \tunsigned has_interesting = 0, has_uninteresting = 0;\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL);\n \tstruct hashmap_iter map_iter;\n \tstruct path_and_oids_entry *entry;\n \tstruct object_id *oid;\n@@ -237,8 +232,6 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \tif (!has_uninteresting || !has_interesting)\n \t\treturn;\n \n-\tpaths_and_oids_init(&map);\n-\n \toidset_iter_init(trees, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n \t\tstruct tree *tree = lookup_tree(r, oid);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 2475663b49..36ff07bd4b 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -151,12 +151,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n int cmd__hashmap(int argc, const char **argv)\n {\n \tstruct strbuf line = STRBUF_INIT;\n-\tstruct hashmap map;\n \tint icase;\n+\tstruct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);\n \n \t/* init hash map */\n \ticase = argc > 1 && !strcmp(\"ignorecase\", argv[1]);\n-\thashmap_init(&map, test_entry_cmp, &icase, 0);\n \n \t/* process commands from stdin */\n \twhile (strbuf_getline(&line, stdin) != EOF) {\n-- \ngitgitgadget\n"},{"id":"409180","messageId":"73a57045c37f50fee3a1d3912f5ed7a217142029.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 11/13] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:43Z","receivedAt":"2020-11-05T00:24:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor heavy users of strmaps, allowing the keys and entries to be\nallocated from a memory pool can provide significant overhead savings.\nAdd an option to strmap_init_with_options() to specify a memory pool.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 31 ++++++++++++++++++++++---------\n strmap.h | 11 ++++++++---\n 2 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 0d10a884b5..f5904138e1 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"strmap.h\"\n+#include \"mem-pool.h\"\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t     const struct hashmap_entry *entry1,\n@@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map,\n \n void strmap_init(struct strmap *map)\n {\n-\tstrmap_init_with_options(map, 1);\n+\tstrmap_init_with_options(map, NULL, 1);\n }\n \n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->pool = pool;\n \tmap->strdup_strings = strdup_strings;\n }\n \n@@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \tif (!map)\n \t\treturn;\n \n+\tif (!free_values && map->pool)\n+\t\t/* Memory other than util is owned by and freed with the pool */\n+\t\treturn;\n+\n \t/*\n \t * We need to iterate over the hashmap entries and free\n \t * e->key and e->value ourselves; hashmap has no API to\n@@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)e->key);\n-\t\tfree(e);\n+\t\tif (!map->pool) {\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree((char*)e->key);\n+\t\t\tfree(e);\n+\t\t}\n \t}\n }\n \n@@ -81,11 +90,13 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \t} else {\n \t\tconst char *key = str;\n \n-\t\tentry = xmalloc(sizeof(*entry));\n+\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n+\t\t\t\t  : xmalloc(sizeof(*entry));\n \t\thashmap_entry_init(&entry->ent, strhash(str));\n \n \t\tif (map->strdup_strings)\n-\t\t\tkey = xstrdup(str);\n+\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n+\t\t\t\t\t: xstrdup(str);\n \t\tentry->key = key;\n \t\tentry->value = data;\n \t\thashmap_add(&map->map, &entry->ent);\n@@ -119,9 +130,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (map->strdup_strings)\n-\t\tfree((char*)ret->key);\n-\tfree(ret);\n+\tif (!map->pool) {\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)ret->key);\n+\t\tfree(ret);\n+\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex 22df987644..b9d882a2d0 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -3,8 +3,10 @@\n \n #include \"hashmap.h\"\n \n+struct mempool;\n struct strmap {\n \tstruct hashmap map;\n+\tstruct mem_pool *pool;\n \tunsigned int strdup_strings:1;\n };\n \n@@ -37,9 +39,10 @@ void strmap_init(struct strmap *map);\n \n /*\n  * Same as strmap_init, but for those who want to control the memory management\n- * carefully instead of using the default of strdup_strings=1.\n+ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n  */\n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings);\n \n /*\n@@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n \n static inline void strintmap_init_with_options(struct strintmap *map,\n \t\t\t\t\t       int default_value,\n+\t\t\t\t\t       struct mem_pool *pool,\n \t\t\t\t\t       int strdup_strings)\n {\n-\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n \tmap->default_value = default_value;\n }\n \n@@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set)\n }\n \n static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    struct mem_pool *pool,\n \t\t\t\t\t    int strdup_strings)\n {\n-\tstrmap_init_with_options(&set->map, strdup_strings);\n+\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n }\n \n static inline void strset_clear(struct strset *set)\n-- \ngitgitgadget\n\n"},{"id":"409181","messageId":"ee1ec55f1b44156c119fd03381af1d308284ca72.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 10/13] strmap: add a strset sub-type","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:42Z","receivedAt":"2020-11-05T00:24:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSimilar to adding strintmap for special-casing a string -> int mapping,\nadd a strset type for cases where we really are only interested in using\nstrmap for storing a set rather than a mapping.  In this case, we'll\nalways just store NULL for the value but the different struct type makes\nit clearer than code comments how a variable is intended to be used.\n\nThe difference in usage also results in some differences in API: a few\nthings that aren't necessary or meaningful are dropped (namely, the\nfree_values argument to *_clear(), and the *_get() function), and\nstrset_add() is chosen as the API instead of strset_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.h | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 65 insertions(+)\n\ndiff --git a/strmap.h b/strmap.h\nindex 56a5cdb864..22df987644 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -27,6 +27,7 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = STRMAP_INIT,   \\\n \t\t\t.default_value = 0,   \\\n \t\t       }\n+#define STRSET_INIT { .map = STRMAP_INIT }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -196,4 +197,68 @@ static inline void strintmap_set(struct strintmap *map, const char *str,\n  */\n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n \n+/*\n+ * strset:\n+ *    A set of strings.\n+ *\n+ * Primary differences with strmap:\n+ *    1) The value is always NULL, and ignored.  As there is no value to free,\n+ *       there is one fewer argument to strset_clear\n+ *    2) No strset_get() because there is no value.\n+ *    3) No strset_put(); use strset_add() instead.\n+ */\n+\n+struct strset {\n+\tstruct strmap map;\n+};\n+\n+#define strset_for_each_entry(mystrset, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrset)->map, iter, var)\n+\n+static inline void strset_init(struct strset *set)\n+{\n+\tstrmap_init(&set->map);\n+}\n+\n+static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    int strdup_strings)\n+{\n+\tstrmap_init_with_options(&set->map, strdup_strings);\n+}\n+\n+static inline void strset_clear(struct strset *set)\n+{\n+\tstrmap_clear(&set->map, 0);\n+}\n+\n+static inline void strset_partial_clear(struct strset *set)\n+{\n+\tstrmap_partial_clear(&set->map, 0);\n+}\n+\n+static inline int strset_contains(struct strset *set, const char *str)\n+{\n+\treturn strmap_contains(&set->map, str);\n+}\n+\n+static inline void strset_remove(struct strset *set, const char *str)\n+{\n+\treturn strmap_remove(&set->map, str, 0);\n+}\n+\n+static inline int strset_empty(struct strset *set)\n+{\n+\treturn strmap_empty(&set->map);\n+}\n+\n+static inline unsigned int strset_get_size(struct strset *set)\n+{\n+\treturn strmap_get_size(&set->map);\n+}\n+\n+static inline void strset_add(struct strset *set, const char *str)\n+{\n+\tstrmap_put(&set->map, str, NULL);\n+}\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409182","messageId":"0352260de479e2c61d9971e6072435476ad3d620.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 12/13] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:44Z","receivedAt":"2020-11-05T00:24:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nBy default, we do not use a mempool and strdup_strings is true; in this\ncase, we can avoid both an extra allocation and an extra free by just\nover-allocating for the strmap_entry leaving enough space at the end to\ncopy the key.  FLEXPTR_ALLOC_STR exists for exactly this purpose, so\nmake use of it.\n\nAlso, adjust the case when we are using a memory pool and strdup_strings\nis true to just do one allocation from the memory pool instead of two so\nthat the strmap_clear() and strmap_remove() code can just avoid freeing\nthe key in all cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 48 +++++++++++++++++++++++++-----------------------\n strmap.h |  1 +\n 2 files changed, 26 insertions(+), 23 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex f5904138e1..98513f7d58 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -59,11 +59,8 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (!map->pool) {\n-\t\t\tif (map->strdup_strings)\n-\t\t\t\tfree((char*)e->key);\n+\t\tif (!map->pool)\n \t\t\tfree(e);\n-\t\t}\n \t}\n }\n \n@@ -82,26 +79,34 @@ void strmap_partial_clear(struct strmap *map, int free_values)\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n-\tvoid *old = NULL;\n \n \tif (entry) {\n-\t\told = entry->value;\n+\t\tvoid *old = entry->value;\n \t\tentry->value = data;\n-\t} else {\n-\t\tconst char *key = str;\n-\n-\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n-\t\t\t\t  : xmalloc(sizeof(*entry));\n-\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\t\treturn old;\n+\t}\n \n-\t\tif (map->strdup_strings)\n-\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n-\t\t\t\t\t: xstrdup(str);\n-\t\tentry->key = key;\n-\t\tentry->value = data;\n-\t\thashmap_add(&map->map, &entry->ent);\n+\tif (map->strdup_strings) {\n+\t\tif (!map->pool) {\n+\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n+\t\t} else {\n+\t\t\tsize_t len = st_add(strlen(str), 1); /* include NUL */\n+\t\t\tentry = mem_pool_alloc(map->pool,\n+\t\t\t\t\t       st_add(sizeof(*entry), len));\n+\t\t\tmemcpy(entry + 1, str, len);\n+\t\t\tentry->key = (void *)(entry + 1);\n+\t\t}\n+\t} else if (!map->pool) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t} else {\n+\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n \t}\n-\treturn old;\n+\thashmap_entry_init(&entry->ent, strhash(str));\n+\tif (!map->strdup_strings)\n+\t\tentry->key = str;\n+\tentry->value = data;\n+\thashmap_add(&map->map, &entry->ent);\n+\treturn NULL;\n }\n \n struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n@@ -130,11 +135,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (!map->pool) {\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)ret->key);\n+\tif (!map->pool)\n \t\tfree(ret);\n-\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex b9d882a2d0..d210da5904 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -14,6 +14,7 @@ struct strmap_entry {\n \tstruct hashmap_entry ent;\n \tconst char *key;\n \tvoid *value;\n+\t/* strmap_entry may be allocated extra space to store the key at end */\n };\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n-- \ngitgitgadget\n\n"},{"id":"409183","messageId":"5e8004c728580589cdaf46d755f2c977feaffb34.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 07/13] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:39Z","receivedAt":"2020-11-05T00:24:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_get_size()\n  * strmap_empty()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_get_entry()\n\nI suspect the first four are self-explanatory.\n\nstrmap_get_entry() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstrmap_entry that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_entry()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via entry->value\n  * directly update entry->value\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 20 ++++++++++++++++++++\n strmap.h | 34 ++++++++++++++++++++++++++++++++++\n 2 files changed, 54 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 53f284eb20..829f1bc095 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -87,6 +87,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str);\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n@@ -97,3 +102,18 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_strmap_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_value)\n+{\n+\tstruct strmap_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (!ret)\n+\t\treturn;\n+\tif (free_value)\n+\t\tfree(ret->value);\n+\tif (map->strdup_strings)\n+\t\tfree((char*)ret->key);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 96888c23ad..f74bc582e4 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -50,6 +50,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -62,4 +68,32 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn strmap_get_size(map) == 0;\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409184","messageId":"448d3b219ffebbc0daa4ef033d78fd45693c5ccd.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 06/13] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:38Z","receivedAt":"2020-11-05T00:24:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nNote that similar string-list, I have a strdup_strings setting.\nHowever, unlike string-list, strmap_init() does not take a parameter for\nthis setting and instead automatically sets it to 1; callers who want to\ncontrol this detail need to instead call strmap_init_with_options().\n(Future patches will add additional parameters to\nstrmap_init_with_options()).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |  1 +\n strmap.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h | 65 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 165 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..777a34c01c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += strvec.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..53f284eb20\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata)\n+{\n+\tconst struct strmap_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct strmap_entry, ent);\n+\te2 = container_of(entry2, const struct strmap_entry, ent);\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static struct strmap_entry *find_strmap_entry(struct strmap *map,\n+\t\t\t\t\t      const char *str)\n+{\n+\tstruct strmap_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\tstrmap_init_with_options(map, 1);\n+}\n+\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings)\n+{\n+\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n+}\n+\n+static void strmap_free_entries_(struct strmap *map, int free_values)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\t/*\n+\t * We need to iterate over the hashmap entries and free\n+\t * e->key and e->value ourselves; hashmap has no API to\n+\t * take care of that for us.  Since we're already iterating over\n+\t * the hashmap, though, might as well free e too and avoid the need\n+\t * to make some call into the hashmap API to do that.\n+\t */\n+\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\tif (free_values)\n+\t\t\tfree(e->value);\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)e->key);\n+\t\tfree(e);\n+\t}\n+}\n+\n+void strmap_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_clear(&map->map);\n+}\n+\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->value;\n+\t\tentry->value = data;\n+\t} else {\n+\t\tconst char *key = str;\n+\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\t\tif (map->strdup_strings)\n+\t\t\tkey = xstrdup(str);\n+\t\tentry->key = key;\n+\t\tentry->value = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\treturn entry ? entry->value : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..96888c23ad\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,65 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n+};\n+\n+struct strmap_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *key;\n+\tvoid *value;\n+};\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata);\n+\n+#define STRMAP_INIT { \\\n+\t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.strdup_strings = 1,                          \\\n+\t\t    }\n+\n+/*\n+ * Initialize the members of the strmap.  Any keys added to the strmap will\n+ * be strdup'ed with their memory managed by the strmap.\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Same as strmap_init, but for those who want to control the memory management\n+ * carefully instead of using the default of strdup_strings=1.\n+ */\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\".\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409185","messageId":"f499934f54a2d1d9ac6ffeafcd6388d183d26fa3.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 09/13] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:41Z","receivedAt":"2020-11-05T00:24:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 +++++++\n strmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 105 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex c410c5241a..0d10a884b5 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -123,3 +123,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\tfree((char*)ret->key);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->value;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, map->default_value + amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex c14fcee148..56a5cdb864 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -23,6 +23,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n \t\t\t.strdup_strings = 1,                          \\\n \t\t    }\n+#define STRINTMAP_INIT { \\\n+\t\t\t.map = STRMAP_INIT,   \\\n+\t\t\t.default_value = 0,   \\\n+\t\t       }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -102,4 +106,94 @@ static inline int strmap_empty(struct strmap *map)\n #define strmap_for_each_entry(mystrmap, iter, var)\t\\\n \thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n \n+\n+/*\n+ * strintmap:\n+ *    A map of string -> int, typecasting the void* of strmap to an int.\n+ *\n+ * Primary differences:\n+ *    1) Since the void* value is just an int in disguise, there is no value\n+ *       to free.  (Thus one fewer argument to strintmap_clear)\n+ *    2) strintmap_get() returns an int, or returns the default_value if the\n+ *       key is not found in the strintmap.\n+ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n+ *       instead.\n+ */\n+\n+struct strintmap {\n+\tstruct strmap map;\n+\tint default_value;\n+};\n+\n+#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n+\n+static inline void strintmap_init(struct strintmap *map, int default_value)\n+{\n+\tstrmap_init(&map->map);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_init_with_options(struct strintmap *map,\n+\t\t\t\t\t       int default_value,\n+\t\t\t\t\t       int strdup_strings)\n+{\n+\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_clear(struct strintmap *map)\n+{\n+\tstrmap_clear(&map->map, 0);\n+}\n+\n+static inline void strintmap_partial_clear(struct strintmap *map)\n+{\n+\tstrmap_partial_clear(&map->map, 0);\n+}\n+\n+static inline int strintmap_contains(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_contains(&map->map, str);\n+}\n+\n+static inline void strintmap_remove(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_remove(&map->map, str, 0);\n+}\n+\n+static inline int strintmap_empty(struct strintmap *map)\n+{\n+\treturn strmap_empty(&map->map);\n+}\n+\n+static inline unsigned int strintmap_get_size(struct strintmap *map)\n+{\n+\treturn strmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Returns the value for str in the map.  If str isn't found in the map,\n+ * the map's default_value is returned.\n+ */\n+static inline int strintmap_get(struct strintmap *map, const char *str)\n+{\n+\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n+\tif (!result)\n+\t\treturn map->default_value;\n+\treturn (intptr_t)result->value;\n+}\n+\n+static inline void strintmap_set(struct strintmap *map, const char *str,\n+\t\t\t\t intptr_t v)\n+{\n+\tstrmap_put(&map->map, str, (void *)v);\n+}\n+\n+/*\n+ * Increment the value for str by amt.  If str isn't in the map, add it and\n+ * set its value to default_value + amt.\n+ */\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409186","messageId":"fd96e9fc8df23851022edf3799012441cd4c8728.1604535766.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 08/13] strmap: enable faster clearing and reusing of strmaps","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:40Z","receivedAt":"2020-11-05T00:24:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen strmaps are used heavily, such as is done by my new merge-ort\nalgorithm, and strmaps need to be cleared but then re-used (because of\ne.g. picking multiple commits to cherry-pick, or due to a recursive\nmerge having several different merges while recursing), free-ing and\nreallocating map->table repeatedly can add up in time, especially since\nit will likely be reallocated to a much smaller size but the previous\nmerge provides a good guide to the right size to use for the next merge.\n\nIntroduce strmap_partial_clear() to take advantage of this type of\nsituation; it will act similar to strmap_clear() except that\nmap->table's entries are zeroed instead of map->table being free'd.\nMaking use of this function reduced the cost of\nclear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped\nthe overall runtime of my rebase testcase by just under 2%.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 6 ++++++\n strmap.h | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 829f1bc095..c410c5241a 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_values)\n \thashmap_clear(&map->map);\n }\n \n+void strmap_partial_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_partial_clear(&map->map);\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\ndiff --git a/strmap.h b/strmap.h\nindex f74bc582e4..c14fcee148 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -42,6 +42,12 @@ void strmap_init_with_options(struct strmap *map,\n  */\n void strmap_clear(struct strmap *map, int free_values);\n \n+/*\n+ * Similar to strmap_clear() but leaves map->map->table allocated and\n+ * pre-sized so that subsequent uses won't need as many rehashings.\n+ */\n+void strmap_partial_clear(struct strmap *map, int free_values);\n+\n /*\n  * Insert \"str\" into the map, pointing to \"data\".\n  *\n-- \ngitgitgadget\n\n"},{"id":"409187","messageId":"61f1da3c51b521035ba728c025cd6a28397f8051.1604535765.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 04/13] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:36Z","receivedAt":"2020-11-05T00:24:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\nclear_or_reinit_internal_opts() in merge-ort was taking about 12% of\noverall runtime in my testcase involving rebasing 35 patches of\nlinux.git across a big rename.  clear_or_reinit_internal_opts() was\ncalling hashmap_free() followed by hashmap_init(), meaning that not only\nwas it freeing all the memory associated with each of the strmaps just\nto immediately allocate a new array again, it was allocating a new array\nthat was likely smaller than needed (thus resulting in later need to\nrehash things).  The ending size of the map table on the previous commit\nwas likely almost perfectly sized for the next commit we wanted to pick,\nand not dropping and reallocating the table immediately is a win.\n\nAdd some new API to hashmap to clear a hashmap of entries without\nfreeing map->table (and instead only zeroing it out like alloc_table()\nwould do, along with zeroing the count of items in the table and the\nshrink_at field).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 39 +++++++++++++++++++++++++++------------\n hashmap.h | 13 ++++++++++++-\n 2 files changed, 39 insertions(+), 13 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex bb7c9979b8..922ed07954 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n \tmap->do_count_items = 1;\n }\n \n+static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct hashmap_entry *e;\n+\n+\thashmap_iter_init(map, &iter);\n+\twhile ((e = hashmap_iter_next(&iter)))\n+\t\t/*\n+\t\t * like container_of, but using caller-calculated\n+\t\t * offset (caller being hashmap_free_entries)\n+\t\t */\n+\t\tfree((char *)e - entry_offset);\n+}\n+\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tif (!map || !map->table)\n+\t\treturn;\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n+\tmemset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));\n+\tmap->shrink_at = 0;\n+\tmap->private_size = 0;\n+}\n+\n void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0) { /* called by hashmap_free_entries */\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct hashmap_entry *e;\n-\n-\t\thashmap_iter_init(map, &iter);\n-\t\twhile ((e = hashmap_iter_next(&iter)))\n-\t\t\t/*\n-\t\t\t * like container_of, but using caller-calculated\n-\t\t\t * offset (caller being hashmap_free_entries)\n-\t\t\t */\n-\t\t\tfree((char *)e - entry_offset);\n-\t}\n+\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\n }\ndiff --git a/hashmap.h b/hashmap.h\nindex 3b0f2bcade..e9430d582a 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -235,7 +235,8 @@ void hashmap_init(struct hashmap *map,\n \t\t  const void *equals_function_data,\n \t\t  size_t initial_size);\n \n-/* internal function for freeing hashmap */\n+/* internal functions for clearing or freeing hashmap */\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n@@ -268,6 +269,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n+/*\n+ * Basically the same as calling hashmap_free() followed by hashmap_init(),\n+ * but doesn't incur the overhead of deallocating and reallocating\n+ * map->table; it leaves map->table allocated and the same size but zeroes\n+ * it out so it's ready for use again as an empty map.  As with\n+ * hashmap_free(), you may need to free the entries yourself before calling\n+ * this function.\n+ */\n+#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n+\n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n  * where @member is the hashmap_entry struct used to associate with @map.\n-- \ngitgitgadget\n\n"},{"id":"409188","messageId":"861e8d65ae8065595d9d4ccff5f70155fec408c9.1604535765.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 05/13] hashmap: provide deallocation function names","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:37Z","receivedAt":"2020-11-05T00:24:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nhashmap_free(), hashmap_free_entries(), and hashmap_free_() have existed\nfor a while, but aren't necessarily the clearest names, especially with\nhashmap_partial_clear() being added to the mix and lazy-initialization\nnow being supported.  Peff suggested we adopt the following names[1]:\n\n  - hashmap_clear() - remove all entries and de-allocate any\n    hashmap-specific data, but be ready for reuse\n\n  - hashmap_clear_and_free() - ditto, but free the entries themselves\n\n  - hashmap_partial_clear() - remove all entries but don't deallocate\n    table\n\n  - hashmap_partial_clear_and_free() - ditto, but free the entries\n\nThis patch provides the new names and converts all existing callers over\nto the new naming scheme.\n\n[1] https://lore.kernel.org/git/20201030125059.GA3277724@coredump.intra.peff.net/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n add-interactive.c       |  2 +-\n blame.c                 |  2 +-\n bloom.c                 |  2 +-\n builtin/fetch.c         |  6 +++---\n builtin/shortlog.c      |  2 +-\n config.c                |  2 +-\n diff.c                  |  4 ++--\n diffcore-rename.c       |  2 +-\n dir.c                   |  8 ++++----\n hashmap.c               |  6 +++---\n hashmap.h               | 44 +++++++++++++++++++++++++----------------\n merge-recursive.c       |  6 +++---\n name-hash.c             |  4 ++--\n object.c                |  2 +-\n oidmap.c                |  2 +-\n patch-ids.c             |  2 +-\n range-diff.c            |  2 +-\n ref-filter.c            |  2 +-\n revision.c              |  2 +-\n sequencer.c             |  4 ++--\n submodule-config.c      |  4 ++--\n t/helper/test-hashmap.c |  6 +++---\n 22 files changed, 63 insertions(+), 53 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 555c4abf32..a14c0feaa2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -557,7 +557,7 @@ static int get_modified_files(struct repository *r,\n \t\tif (ps)\n \t\t\tclear_pathspec(&rev.prune_data);\n \t}\n-\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\thashmap_clear_and_free(&s.file_map, struct pathname_entry, ent);\n \tif (unmerged_count)\n \t\t*unmerged_count = s.unmerged_count;\n \tif (binary_count)\ndiff --git a/blame.c b/blame.c\nindex 686845b2b4..229beb6452 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -435,7 +435,7 @@ static void get_fingerprint(struct fingerprint *result,\n \n static void free_fingerprint(struct fingerprint *f)\n {\n-\thashmap_free(&f->map);\n+\thashmap_clear(&f->map);\n \tfree(f->entries);\n }\n \ndiff --git a/bloom.c b/bloom.c\nindex 68c73200a5..719c313a1c 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -287,7 +287,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \t\t}\n \n \tcleanup:\n-\t\thashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);\n+\t\thashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry);\n \t} else {\n \t\tfor (i = 0; i < diff_queued_diff.nr; i++)\n \t\t\tdiff_free_filepair(diff_queued_diff.queue[i]);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex f9c3c49f14..ecf8537605 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -393,7 +393,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\titem = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);\n \t\tstring_list_insert(&remote_refs_list, ref->name);\n \t}\n-\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \t/*\n \t * We may have a final lightweight tag that needs to be\n@@ -428,7 +428,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\t**tail = rm;\n \t\t*tail = &rm->next;\n \t}\n-\thashmap_free_entries(&remote_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent);\n \tstring_list_clear(&remote_refs_list, 0);\n \toidset_clear(&fetch_oids);\n }\n@@ -573,7 +573,7 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \tif (existing_refs_populated)\n-\t\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\t\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \treturn ref_map;\n }\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 0a5c4968f6..83f0a739b4 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -220,7 +220,7 @@ static void strset_clear(struct strset *ss)\n {\n \tif (!ss->map.table)\n \t\treturn;\n-\thashmap_free_entries(&ss->map, struct strset_item, ent);\n+\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n }\n \n static void insert_records_from_trailers(struct shortlog *log,\ndiff --git a/config.c b/config.c\nindex 2bdff4457b..8f324ed3a6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1963,7 +1963,7 @@ void git_configset_clear(struct config_set *cs)\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_free_entries(&cs->config_hash, struct config_set_element, ent);\n+\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n \tcs->hash_initialized = 0;\n \tfree(cs->list.items);\n \tcs->list.nr = 0;\ndiff --git a/diff.c b/diff.c\nindex 2bb2f8f57e..8e0e59f5cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6289,9 +6289,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \t\t\tif (o->color_moved == COLOR_MOVED_ZEBRA_DIM)\n \t\t\t\tdim_moved_lines(o);\n \n-\t\t\thashmap_free_entries(&add_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&add_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n-\t\t\thashmap_free_entries(&del_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&del_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n \t\t}\n \ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 99e63e90f8..d367a6d244 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -407,7 +407,7 @@ static int find_exact_renames(struct diff_options *options)\n \t\trenames += find_identical_files(&file_table, i, options);\n \n \t/* Free the hash data structure and entries */\n-\thashmap_free_entries(&file_table, struct file_similarity, entry);\n+\thashmap_clear_and_free(&file_table, struct file_similarity, entry);\n \n \treturn renames;\n }\ndiff --git a/dir.c b/dir.c\nindex 78387110e6..161dce121e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -817,8 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern\n \n clear_hashmaps:\n \twarning(_(\"disabling cone pattern matching\"));\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n \tpl->use_cone_patterns = 0;\n }\n \n@@ -921,8 +921,8 @@ void clear_pattern_list(struct pattern_list *pl)\n \t\tfree(pl->patterns[i]);\n \tfree(pl->patterns);\n \tfree(pl->filebuf);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n \n \tmemset(pl, 0, sizeof(*pl));\n }\ndiff --git a/hashmap.c b/hashmap.c\nindex 922ed07954..5009471800 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -183,7 +183,7 @@ static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n \twhile ((e = hashmap_iter_next(&iter)))\n \t\t/*\n \t\t * like container_of, but using caller-calculated\n-\t\t * offset (caller being hashmap_free_entries)\n+\t\t * offset (caller being hashmap_clear_and_free)\n \t\t */\n \t\tfree((char *)e - entry_offset);\n }\n@@ -199,11 +199,11 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n \tmap->private_size = 0;\n }\n \n-void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n+void hashmap_clear_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_and_free */\n \t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\ndiff --git a/hashmap.h b/hashmap.h\nindex e9430d582a..7251687d73 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -96,7 +96,7 @@\n  *         }\n  *\n  *         if (!strcmp(\"end\", action)) {\n- *             hashmap_free_entries(&map, struct long2string, ent);\n+ *             hashmap_clear_and_free(&map, struct long2string, ent);\n  *             break;\n  *         }\n  *     }\n@@ -237,7 +237,7 @@ void hashmap_init(struct hashmap *map,\n \n /* internal functions for clearing or freeing hashmap */\n void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n-void hashmap_free_(struct hashmap *map, ssize_t offset);\n+void hashmap_clear_(struct hashmap *map, ssize_t offset);\n \n /*\n  * Frees a hashmap structure and allocated memory for the table, but does not\n@@ -253,40 +253,50 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  *      free(e->somefield);\n  *      free(e);\n  *    }\n- *    hashmap_free(map);\n+ *    hashmap_clear(map);\n  *\n  * instead of\n  *\n  *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n  *      free(e->somefield);\n  *    }\n- *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *    hashmap_clear_and_free(map, struct my_entry_struct, hashmap_entry_name);\n  *\n  * to avoid the implicit extra loop over the entries.  However, if there are\n  * no special fields in your entry that need to be freed beyond the entry\n  * itself, it is probably simpler to avoid the explicit loop and just call\n- * hashmap_free_entries().\n+ * hashmap_clear_and_free().\n  */\n-#define hashmap_free(map) hashmap_free_(map, -1)\n+#define hashmap_clear(map) hashmap_clear_(map, -1)\n \n /*\n- * Basically the same as calling hashmap_free() followed by hashmap_init(),\n- * but doesn't incur the overhead of deallocating and reallocating\n- * map->table; it leaves map->table allocated and the same size but zeroes\n- * it out so it's ready for use again as an empty map.  As with\n- * hashmap_free(), you may need to free the entries yourself before calling\n- * this function.\n+ * Similar to hashmap_clear(), except that the table is no deallocated; it\n+ * is merely zeroed out but left the same size as before.  If the hashmap\n+ * will be reused, this avoids the overhead of deallocating and\n+ * reallocating map->table.  As with hashmap_clear(), you may need to free\n+ * the entries yourself before calling this function.\n  */\n #define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n \n /*\n- * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map.\n+ * Similar to hashmap_clear() but also frees all entries.  @type is the\n+ * struct type of the entry where @member is the hashmap_entry struct used\n+ * to associate with @map.\n  *\n- * See usage note above hashmap_free().\n+ * See usage note above hashmap_clear().\n  */\n-#define hashmap_free_entries(map, type, member) \\\n-\thashmap_free_(map, offsetof(type, member));\n+#define hashmap_clear_and_free(map, type, member) \\\n+\thashmap_clear_(map, offsetof(type, member))\n+\n+/*\n+ * Similar to hashmap_partial_clear() but also frees all entries.  @type is\n+ * the struct type of the entry where @member is the hashmap_entry struct\n+ * used to associate with @map.\n+ *\n+ * See usage note above hashmap_clear().\n+ */\n+#define hashmap_partial_clear_and_free(map, type, member) \\\n+\thashmap_partial_clear_(map, offsetof(type, member))\n \n /* hashmap_entry functions */\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d0214335a7..f736a0f632 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2651,7 +2651,7 @@ static struct string_list *get_renames(struct merge_options *opt,\n \t\tfree(e->target_file);\n \t\tstring_list_clear(&e->source_files, 0);\n \t}\n-\thashmap_free_entries(&collisions, struct collision_entry, ent);\n+\thashmap_clear_and_free(&collisions, struct collision_entry, ent);\n \treturn renames;\n }\n \n@@ -2870,7 +2870,7 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,\n \t\tstrbuf_release(&e->new_dir);\n \t\t/* possible_new_dirs already cleared in get_directory_renames */\n \t}\n-\thashmap_free_entries(dir_renames, struct dir_rename_entry, ent);\n+\thashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent);\n \tfree(dir_renames);\n \n \tfree(pairs->queue);\n@@ -3497,7 +3497,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free_entries(&opt->priv->current_file_dir_set,\n+\t\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n \t\t\t\t\tstruct path_hashmap_entry, e);\n \n \t\tif (clean < 0) {\ndiff --git a/name-hash.c b/name-hash.c\nindex fb526a3775..5d3c7b12c1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -726,6 +726,6 @@ void free_name_hash(struct index_state *istate)\n \t\treturn;\n \tistate->name_hash_initialized = 0;\n \n-\thashmap_free(&istate->name_hash);\n-\thashmap_free_entries(&istate->dir_hash, struct dir_entry, ent);\n+\thashmap_clear(&istate->name_hash);\n+\thashmap_clear_and_free(&istate->dir_hash, struct dir_entry, ent);\n }\ndiff --git a/object.c b/object.c\nindex 3257518656..b8406409d5 100644\n--- a/object.c\n+++ b/object.c\n@@ -532,7 +532,7 @@ void raw_object_store_clear(struct raw_object_store *o)\n \tclose_object_store(o);\n \to->packed_git = NULL;\n \n-\thashmap_free(&o->pack_map);\n+\thashmap_clear(&o->pack_map);\n }\n \n void parsed_object_pool_clear(struct parsed_object_pool *o)\ndiff --git a/oidmap.c b/oidmap.c\nindex 423aa014a3..286a04a53c 100644\n--- a/oidmap.c\n+++ b/oidmap.c\n@@ -27,7 +27,7 @@ void oidmap_free(struct oidmap *map, int free_entries)\n \t\treturn;\n \n \t/* TODO: make oidmap itself not depend on struct layouts */\n-\thashmap_free_(&map->map, free_entries ? 0 : -1);\n+\thashmap_clear_(&map->map, free_entries ? 0 : -1);\n }\n \n void *oidmap_get(const struct oidmap *map, const struct object_id *key)\ndiff --git a/patch-ids.c b/patch-ids.c\nindex 12aa6d494b..21973e4933 100644\n--- a/patch-ids.c\n+++ b/patch-ids.c\n@@ -71,7 +71,7 @@ int init_patch_ids(struct repository *r, struct patch_ids *ids)\n \n int free_patch_ids(struct patch_ids *ids)\n {\n-\thashmap_free_entries(&ids->patches, struct patch_id, ent);\n+\thashmap_clear_and_free(&ids->patches, struct patch_id, ent);\n \treturn 0;\n }\n \ndiff --git a/range-diff.c b/range-diff.c\nindex 24dc435e48..befeecae44 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -266,7 +266,7 @@ static void find_exact_matches(struct string_list *a, struct string_list *b)\n \t\t}\n \t}\n \n-\thashmap_free(&map);\n+\thashmap_clear(&map);\n }\n \n static void diffsize_consume(void *data, char *line, unsigned long len)\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c62f6b4822..5e66b8cd76 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2222,7 +2222,7 @@ void ref_array_clear(struct ref_array *array)\n \tused_atom_cnt = 0;\n \n \tif (ref_to_worktree_map.worktrees) {\n-\t\thashmap_free_entries(&(ref_to_worktree_map.map),\n+\t\thashmap_clear_and_free(&(ref_to_worktree_map.map),\n \t\t\t\t\tstruct ref_to_worktree_entry, ent);\n \t\tfree_worktrees(ref_to_worktree_map.worktrees);\n \t\tref_to_worktree_map.worktrees = NULL;\ndiff --git a/revision.c b/revision.c\nindex aa62212040..f27649d45d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -139,7 +139,7 @@ static void paths_and_oids_clear(struct hashmap *map)\n \t\tfree(entry->path);\n \t}\n \n-\thashmap_free_entries(map, struct path_and_oids_entry, ent);\n+\thashmap_clear_and_free(map, struct path_and_oids_entry, ent);\n }\n \n static void paths_and_oids_insert(struct hashmap *map,\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..23a09c3e7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5058,7 +5058,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n-\thashmap_free_entries(&state.labels, struct labels_entry, entry);\n+\thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\n \tstrbuf_release(&state.buf);\n \n \treturn 0;\n@@ -5577,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)\n \tfor (i = 0; i < todo_list->nr; i++)\n \t\tfree(subjects[i]);\n \tfree(subjects);\n-\thashmap_free_entries(&subject2item, struct subject2item_entry, entry);\n+\thashmap_clear_and_free(&subject2item, struct subject2item_entry, entry);\n \n \tclear_commit_todo_item(&commit_todo);\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex c569e22aa3..f502505566 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -103,8 +103,8 @@ static void submodule_cache_clear(struct submodule_cache *cache)\n \t\t\t\tent /* member name */)\n \t\tfree_one_config(entry);\n \n-\thashmap_free_entries(&cache->for_path, struct submodule_entry, ent);\n-\thashmap_free_entries(&cache->for_name, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_path, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_name, struct submodule_entry, ent);\n \tcache->initialized = 0;\n \tcache->gitmodules_read = 0;\n }\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex f38706216f..2475663b49 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -110,7 +110,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t\thashmap_add(&map, &entries[i]->ent);\n \t\t\t}\n \n-\t\t\thashmap_free(&map);\n+\t\t\thashmap_clear(&map);\n \t\t}\n \t} else {\n \t\t/* test map lookups */\n@@ -130,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t}\n \t\t}\n \n-\t\thashmap_free(&map);\n+\t\thashmap_clear(&map);\n \t}\n }\n \n@@ -262,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv)\n \t}\n \n \tstrbuf_release(&line);\n-\thashmap_free_entries(&map, struct test_entry, ent);\n+\thashmap_clear_and_free(&map, struct test_entry, ent);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"409189","messageId":"591161fd781b7666ddaa45694eb20610cc359741.1604535765.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 02/13] hashmap: adjust spacing to fix argument alignment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:34Z","receivedAt":"2020-11-05T00:24:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo actual code changes; just whitespace adjustments.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 17 +++++++++--------\n hashmap.h | 22 +++++++++++-----------\n 2 files changed, 20 insertions(+), 19 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex 09813e1a46..e44d8a3e85 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size)\n }\n \n static inline int entry_equals(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *e1, const struct hashmap_entry *e2,\n-\t\tconst void *keydata)\n+\t\t\t       const struct hashmap_entry *e1,\n+\t\t\t       const struct hashmap_entry *e2,\n+\t\t\t       const void *keydata)\n {\n \treturn (e1 == e2) ||\n \t       (e1->hash == e2->hash &&\n@@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map,\n }\n \n static inline unsigned int bucket(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *key)\n+\t\t\t\t  const struct hashmap_entry *key)\n {\n \treturn key->hash & (map->tablesize - 1);\n }\n@@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data,\n }\n \n void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n-\t\tconst void *cmpfn_data, size_t initial_size)\n+\t\t  const void *cmpfn_data, size_t initial_size)\n {\n \tunsigned int size = HASHMAP_INITIAL_SIZE;\n \n@@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry)\n+\t\t\t\t       const struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *e = entry->next;\n \tfor (; e; e = e->next)\n@@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n }\n \n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata)\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n \tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n@@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry)\n+\t\t\t\t  struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n \thashmap_add(map, entry);\ndiff --git a/hashmap.h b/hashmap.h\nindex 2994dc7a9c..904f61d6e1 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -228,9 +228,9 @@ struct hashmap {\n  * prevent expensive resizing. If 0, the table is dynamically resized.\n  */\n void hashmap_init(struct hashmap *map,\n-\t\t\t hashmap_cmp_fn equals_function,\n-\t\t\t const void *equals_function_data,\n-\t\t\t size_t initial_size);\n+\t\t  hashmap_cmp_fn equals_function,\n+\t\t  const void *equals_function_data,\n+\t\t  size_t initial_size);\n \n /* internal function for freeing hashmap */\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n@@ -288,7 +288,7 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  * and if it is on stack, you can just let it go out of scope).\n  */\n static inline void hashmap_entry_init(struct hashmap_entry *e,\n-\t\t\t\t\tunsigned int hash)\n+\t\t\t\t      unsigned int hash)\n {\n \te->hash = hash;\n \te->next = NULL;\n@@ -330,8 +330,8 @@ static inline unsigned int hashmap_get_size(struct hashmap *map)\n  * to `hashmap_cmp_fn` to decide whether the entry matches the key.\n  */\n struct hashmap_entry *hashmap_get(const struct hashmap *map,\n-\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\tconst void *keydata);\n+\t\t\t\t  const struct hashmap_entry *key,\n+\t\t\t\t  const void *keydata);\n \n /*\n  * Returns the hashmap entry for the specified hash code and key data,\n@@ -364,7 +364,7 @@ static inline struct hashmap_entry *hashmap_get_from_hash(\n  * call to `hashmap_get` or `hashmap_get_next`.\n  */\n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry);\n+\t\t\t\t       const struct hashmap_entry *entry);\n \n /*\n  * Adds a hashmap entry. This allows to add duplicate entries (i.e.\n@@ -384,7 +384,7 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);\n  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).\n  */\n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry);\n+\t\t\t\t  struct hashmap_entry *entry);\n \n /*\n  * Adds or replaces a hashmap entry contained within @keyvar,\n@@ -406,8 +406,8 @@ struct hashmap_entry *hashmap_put(struct hashmap *map,\n  * Argument explanation is the same as in `hashmap_get`.\n  */\n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata);\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata);\n \n /*\n  * Removes a hashmap entry contained within @keyvar,\n@@ -449,7 +449,7 @@ struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);\n \n /* Initializes the iterator and returns the first entry, if any. */\n static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,\n-\t\tstruct hashmap_iter *iter)\n+\t\t\t\t\t\t       struct hashmap_iter *iter)\n {\n \thashmap_iter_init(map, iter);\n \treturn hashmap_iter_next(iter);\n-- \ngitgitgadget\n\n"},{"id":"409190","messageId":"f2718d036dc5d08c2ed416a5ef98c5730bc38cfc.1604535765.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 03/13] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:35Z","receivedAt":"2020-11-05T00:24:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nPreviously, once map->table had been freed, any calls to hashmap_put(),\nhashmap_get(), or hashmap_remove() would cause a NULL pointer\ndereference (since hashmap_free_() also zeros the memory; without that\nzeroing, calling these functions would cause a use-after-free problem).\n\nModify these functions to check for a NULL table and automatically\nallocate as needed.\n\nAlso add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the\nstack without calling hashmap_init().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 16 ++++++++++++++--\n hashmap.h |  3 +++\n 2 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex e44d8a3e85..bb7c9979b8 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash)\n \n static void rehash(struct hashmap *map, unsigned int newsize)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tunsigned int i, oldsize = map->tablesize;\n \tstruct hashmap_entry **oldtable = map->table;\n \n@@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize)\n static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,\n \t\tconst struct hashmap_entry *key, const void *keydata)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tstruct hashmap_entry **e = &map->table[bucket(map, key)];\n \twhile (*e && !entry_equals(map, *e, key, keydata))\n \t\te = &(*e)->next;\n@@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n \t\t\t\tconst struct hashmap_entry *key,\n \t\t\t\tconst void *keydata)\n {\n+\tif (!map->table)\n+\t\treturn NULL;\n \treturn *find_entry_ptr(map, key, keydata);\n }\n \n@@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n \n void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n {\n-\tunsigned int b = bucket(map, entry);\n+\tunsigned int b;\n+\n+\tif (!map->table)\n+\t\talloc_table(map, HASHMAP_INITIAL_SIZE);\n \n+\tb = bucket(map, entry);\n \t/* add entry */\n \tentry->next = map->table[b];\n \tmap->table[b] = entry;\n@@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n \t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n-\tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n+\tstruct hashmap_entry **e;\n+\n+\tif (!map->table)\n+\t\treturn NULL;\n+\te = find_entry_ptr(map, key, keydata);\n \tif (!*e)\n \t\treturn NULL;\n \ndiff --git a/hashmap.h b/hashmap.h\nindex 904f61d6e1..3b0f2bcade 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -210,6 +210,9 @@ struct hashmap {\n \n /* hashmap functions */\n \n+#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \\\n+\t\t\t\t .do_count_items = 1 }\n+\n /*\n  * Initializes a hashmap structure.\n  *\n-- \ngitgitgadget\n\n"},{"id":"409191","messageId":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1604535765.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v4 01/13] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:33Z","receivedAt":"2020-11-05T00:24:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  We are dealing with a map table,\nentries in that table, and possibly also things each of those entries\npoint to.  I had to consult other source code examples and the\nimplementation.  Add a brief note to clarify the differences.  This will\nbecome even more important once we introduce a new\nhashmap_partial_clear() function which will add the question of whether\nthe table itself has been freed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 31 +++++++++++++++++++++++++++++--\n 1 file changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex b011b394fe..2994dc7a9c 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ *\n+ * instead of\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"409192","messageId":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v3.git.git.1604343313.gitgitgadget@gmail.com","subject":"[PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-05T00:22:32Z","receivedAt":"2020-11-05T00:24:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce a new strmap type (and strintmap and strset), which my new\nmerge backed, merge-ort, uses heavily. (I also made significant use of it in\nmy changes to diffcore-rename). This strmap type was based on Peff's\nproposal from a couple years ago[1], but has additions that I made as I used\nit, and a number of additions/changes suggested by Peff in his reviews. I\nalso start the series off with some changes to hashmap, based on Peff's\nfeedback on v1 & v2.\n\nNOTE: While en/merge-ort-impl depends on this series, there are no changes\nin v4 that affect it so en/merge-ort-impl does not need a reroll.\n\nChanges since v3 (almost all of which were suggestions from Peff):\n\n * Fix pointer math due to platform differences in FLEX_ALLOC definition,\n   and a few other FLEXPTR_ALLOC_STR cleanups\n * Define strmap_for_each_entry in terms of hashmap_for_each_entry instead\n   of lower level functions\n * Use simpler _INIT macros\n * Remove strset_check_and_add() from API as per Peff's suggestion\n   (merge-ort doesn't need it; we can add it later)\n * Update comments and commit messages to update now obsolete statements due\n   to changes from earlier reviews\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nElijah Newren (13):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  hashmap: adjust spacing to fix argument alignment\n  hashmap: allow re-use after hashmap_free()\n  hashmap: introduce a new hashmap_partial_clear()\n  hashmap: provide deallocation function names\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: enable faster clearing and reusing of strmaps\n  strmap: add functions facilitating use as a string->int map\n  strmap: add a strset sub-type\n  strmap: enable allocations to come from a mem_pool\n  strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n  Use new HASHMAP_INIT macro to simplify hashmap initialization\n\n Makefile                |   1 +\n add-interactive.c       |   2 +-\n attr.c                  |  26 ++--\n blame.c                 |   2 +-\n bloom.c                 |   5 +-\n builtin/difftool.c      |   9 +-\n builtin/fetch.c         |   6 +-\n builtin/shortlog.c      |   2 +-\n config.c                |   2 +-\n diff.c                  |   4 +-\n diffcore-rename.c       |   2 +-\n dir.c                   |   8 +-\n hashmap.c               |  74 +++++++----\n hashmap.h               |  91 +++++++++++---\n merge-recursive.c       |   6 +-\n name-hash.c             |   4 +-\n object.c                |   2 +-\n oidmap.c                |   2 +-\n patch-ids.c             |   2 +-\n range-diff.c            |   6 +-\n ref-filter.c            |   2 +-\n revision.c              |  11 +-\n sequencer.c             |   4 +-\n strmap.c                | 151 ++++++++++++++++++++++\n strmap.h                | 270 ++++++++++++++++++++++++++++++++++++++++\n submodule-config.c      |   4 +-\n t/helper/test-hashmap.c |   9 +-\n 27 files changed, 593 insertions(+), 114 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: d4a392452e292ff924e79ec8458611c0f679d6d4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v4\nPull-Request: https://github.com/git/git/pull/835\n\nRange-diff vs v3:\n\n  1:  af6b6fcb46 =  1:  af6b6fcb46 hashmap: add usage documentation explaining hashmap_free[_entries]()\n  2:  591161fd78 =  2:  591161fd78 hashmap: adjust spacing to fix argument alignment\n  3:  f2718d036d =  3:  f2718d036d hashmap: allow re-use after hashmap_free()\n  4:  61f1da3c51 =  4:  61f1da3c51 hashmap: introduce a new hashmap_partial_clear()\n  5:  861e8d65ae =  5:  861e8d65ae hashmap: provide deallocation function names\n  6:  448d3b219f =  6:  448d3b219f strmap: new utility functions\n  7:  42633b8d03 !  7:  5e8004c728 strmap: add more utility functions\n     @@ strmap.h: void *strmap_get(struct strmap *map, const char *str);\n      + * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n      + */\n      +#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n     -+\tfor (var = hashmap_iter_first_entry_offset(&(mystrmap)->map, iter, 0); \\\n     -+\t\tvar; \\\n     -+\t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n     ++\thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n      +\n       #endif /* STRMAP_H */\n  8:  ea942eb803 =  8:  fd96e9fc8d strmap: enable faster clearing and reusing of strmaps\n  9:  c1d2172171 !  9:  f499934f54 strmap: add functions facilitating use as a string->int map\n     @@ Commit message\n          isn't the case when we're storing an int value directly in the void*\n          slot instead of using the void* slot as a pointer to data.\n      \n     -    A note on the name: if anyone has a better name suggestion than\n     -    strintmap, I'm happy to take it.  It seems slightly unwieldy, but I have\n     -    not been able to come up with a better name.\n     -\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## strmap.c ##\n     @@ strmap.h: int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n       \t\t\t.strdup_strings = 1,                          \\\n       \t\t    }\n      +#define STRINTMAP_INIT { \\\n     -+\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     -+\t\t\t.map.strdup_strings = 1,                          \\\n     -+\t\t\t.default_value = 0,                               \\\n     -+\t\t    }\n     ++\t\t\t.map = STRMAP_INIT,   \\\n     ++\t\t\t.default_value = 0,   \\\n     ++\t\t       }\n       \n       /*\n        * Initialize the members of the strmap.  Any keys added to the strmap will\n      @@ strmap.h: static inline int strmap_empty(struct strmap *map)\n     - \t\tvar; \\\n     - \t\tvar = hashmap_iter_next_entry_offset(iter, 0))\n     + #define strmap_for_each_entry(mystrmap, iter, var)\t\\\n     + \thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n       \n      +\n      +/*\n     @@ strmap.h: static inline int strmap_empty(struct strmap *map)\n      + * Primary differences:\n      + *    1) Since the void* value is just an int in disguise, there is no value\n      + *       to free.  (Thus one fewer argument to strintmap_clear)\n     -+ *    2) strintmap_get() returns an int; it also requires an extra parameter to\n     -+ *       be specified so it knows what value to return if the underlying strmap\n     -+ *       has not key matching the given string.\n     ++ *    2) strintmap_get() returns an int, or returns the default_value if the\n     ++ *       key is not found in the strintmap.\n      + *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n      + *       instead.\n      + */\n 10:  0f57735f5e ! 10:  ee1ec55f1b strmap: add a strset sub-type\n     @@ Commit message\n          free_values argument to *_clear(), and the *_get() function), and\n          strset_add() is chosen as the API instead of strset_put().\n      \n     -    Finally, shortlog already had a more minimal strset API; so this adds a\n     -    strset_check_and_add() function for its benefit to allow it to switch\n     -    over to this strset implementation.\n     -\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     - ## strmap.c ##\n     -@@ strmap.c: void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n     - \telse\n     - \t\tstrintmap_set(map, str, map->default_value + amt);\n     - }\n     -+\n     -+int strset_check_and_add(struct strset *set, const char *str)\n     -+{\n     -+\tif (strset_contains(set, str))\n     -+\t\treturn 1;\n     -+\tstrset_add(set, str);\n     -+\treturn 0;\n     -+}\n     -\n       ## strmap.h ##\n      @@ strmap.h: int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n     - \t\t\t.map.strdup_strings = 1,                          \\\n     - \t\t\t.default_value = 0,                               \\\n     - \t\t    }\n     -+#define STRSET_INIT { \\\n     -+\t\t\t.map.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n     -+\t\t\t.map.strdup_strings = 1,                          \\\n     -+\t\t    }\n     + \t\t\t.map = STRMAP_INIT,   \\\n     + \t\t\t.default_value = 0,   \\\n     + \t\t       }\n     ++#define STRSET_INIT { .map = STRMAP_INIT }\n       \n       /*\n        * Initialize the members of the strmap.  Any keys added to the strmap will\n     @@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *st\n      +{\n      +\tstrmap_put(&set->map, str, NULL);\n      +}\n     -+\n     -+/* Returns 1 if str already in set.  Otherwise adds str to set and returns 0 */\n     -+int strset_check_and_add(struct strset *set, const char *str);\n      +\n       #endif /* STRMAP_H */\n 11:  980537e877 = 11:  73a57045c3 strmap: enable allocations to come from a mem_pool\n 12:  7f93cbb525 ! 12:  0352260de4 strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n     @@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_values)\n       \t}\n       }\n       \n     -@@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n     - \t\told = entry->value;\n     +@@ strmap.c: void strmap_partial_clear(struct strmap *map, int free_values)\n     + void *strmap_put(struct strmap *map, const char *str, void *data)\n     + {\n     + \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n     +-\tvoid *old = NULL;\n     + \n     + \tif (entry) {\n     +-\t\told = entry->value;\n     ++\t\tvoid *old = entry->value;\n       \t\tentry->value = data;\n     - \t} else {\n     +-\t} else {\n      -\t\tconst char *key = str;\n      -\n      -\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n      -\t\t\t\t  : xmalloc(sizeof(*entry));\n     -+\t\tif (map->strdup_strings) {\n     -+\t\t\tif (!map->pool) {\n     -+\t\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n     -+\t\t\t} else {\n     -+\t\t\t\t/* Remember +1 for nul byte twice below */\n     -+\t\t\t\tsize_t len = strlen(str);\n     -+\t\t\t\tentry = mem_pool_alloc(map->pool,\n     -+\t\t\t\t\t       st_add3(sizeof(*entry), len, 1));\n     -+\t\t\t\tmemcpy(entry->keydata, str, len+1);\n     -+\t\t\t}\n     -+\t\t} else if (!map->pool) {\n     -+\t\t\tentry = xmalloc(sizeof(*entry));\n     -+\t\t} else {\n     -+\t\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n     -+\t\t}\n     - \t\thashmap_entry_init(&entry->ent, strhash(str));\n     --\n     +-\t\thashmap_entry_init(&entry->ent, strhash(str));\n     ++\t\treturn old;\n     ++\t}\n     + \n      -\t\tif (map->strdup_strings)\n      -\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n      -\t\t\t\t\t: xstrdup(str);\n      -\t\tentry->key = key;\n     -+\t\tentry->key = map->strdup_strings ? entry->keydata : str;\n     - \t\tentry->value = data;\n     - \t\thashmap_add(&map->map, &entry->ent);\n     +-\t\tentry->value = data;\n     +-\t\thashmap_add(&map->map, &entry->ent);\n     ++\tif (map->strdup_strings) {\n     ++\t\tif (!map->pool) {\n     ++\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n     ++\t\t} else {\n     ++\t\t\tsize_t len = st_add(strlen(str), 1); /* include NUL */\n     ++\t\t\tentry = mem_pool_alloc(map->pool,\n     ++\t\t\t\t\t       st_add(sizeof(*entry), len));\n     ++\t\t\tmemcpy(entry + 1, str, len);\n     ++\t\t\tentry->key = (void *)(entry + 1);\n     ++\t\t}\n     ++\t} else if (!map->pool) {\n     ++\t\tentry = xmalloc(sizeof(*entry));\n     ++\t} else {\n     ++\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n       \t}\n     +-\treturn old;\n     ++\thashmap_entry_init(&entry->ent, strhash(str));\n     ++\tif (!map->strdup_strings)\n     ++\t\tentry->key = str;\n     ++\tentry->value = data;\n     ++\thashmap_add(&map->map, &entry->ent);\n     ++\treturn NULL;\n     + }\n     + \n     + struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n      @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_value)\n       \t\treturn;\n       \tif (free_value)\n     @@ strmap.h: struct strmap_entry {\n       \tstruct hashmap_entry ent;\n       \tconst char *key;\n       \tvoid *value;\n     -+\tchar keydata[FLEX_ARRAY]; /* if strdup_strings=1, key == &keydata[0] */\n     ++\t/* strmap_entry may be allocated extra space to store the key at end */\n       };\n       \n       int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n 13:  5f41fc63e5 = 13:  617926540b Use new HASHMAP_INIT macro to simplify hashmap initialization\n\n-- \ngitgitgadget\n"},{"id":"409199","messageId":"20201105132909.GB91972@coredump.intra.peff.net","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-05T13:29:09Z","receivedAt":"2020-11-05T13:29:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 05, 2020 at 12:22:32AM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Changes since v3 (almost all of which were suggestions from Peff):\n> \n>  * Fix pointer math due to platform differences in FLEX_ALLOC definition,\n>    and a few other FLEXPTR_ALLOC_STR cleanups\n>  * Define strmap_for_each_entry in terms of hashmap_for_each_entry instead\n>    of lower level functions\n>  * Use simpler _INIT macros\n>  * Remove strset_check_and_add() from API as per Peff's suggestion\n>    (merge-ort doesn't need it; we can add it later)\n>  * Update comments and commit messages to update now obsolete statements due\n>    to changes from earlier reviews\n\nThanks, this version looks good to me.\n\nI think we might as well do this on top now:\n\n-- >8 --\nSubject: [PATCH] shortlog: drop custom strset implementation\n\nWe can use the strset recently added in strmap.h instead. Note that this\ndoesn't have a \"check_and_add\" function. We can easily write the same\nthing using separate \"contains\" and \"adds\" calls. This is slightly less\nefficient, in that it hashes the string twice, but for our use here it\nshouldn't be a big deal either way.\n\nI did leave it as a separate helper function, though, since we use it in\nthree separate spots (some of which are in the middle of a conditional).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n builtin/shortlog.c | 59 ++++++----------------------------------------\n 1 file changed, 7 insertions(+), 52 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 83f0a739b4..2d036ceec2 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -10,6 +10,7 @@\n #include \"shortlog.h\"\n #include \"parse-options.h\"\n #include \"trailer.h\"\n+#include \"strmap.h\"\n \n static char const * const shortlog_usage[] = {\n \tN_(\"git shortlog [<options>] [<revision-range>] [[--] <path>...]\"),\n@@ -169,60 +170,14 @@ static void read_from_stdin(struct shortlog *log)\n \tstrbuf_release(&oneline);\n }\n \n-struct strset_item {\n-\tstruct hashmap_entry ent;\n-\tchar value[FLEX_ARRAY];\n-};\n-\n-struct strset {\n-\tstruct hashmap map;\n-};\n-\n-#define STRSET_INIT { { NULL } }\n-\n-static int strset_item_hashcmp(const void *hash_data,\n-\t\t\t       const struct hashmap_entry *entry,\n-\t\t\t       const struct hashmap_entry *entry_or_key,\n-\t\t\t       const void *keydata)\n+static int check_dup(struct strset *dups, const char *str)\n {\n-\tconst struct strset_item *a, *b;\n-\n-\ta = container_of(entry, const struct strset_item, ent);\n-\tif (keydata)\n-\t\treturn strcmp(a->value, keydata);\n-\n-\tb = container_of(entry_or_key, const struct strset_item, ent);\n-\treturn strcmp(a->value, b->value);\n-}\n-\n-/*\n- * Adds \"str\" to the set if it was not already present; returns true if it was\n- * already there.\n- */\n-static int strset_check_and_add(struct strset *ss, const char *str)\n-{\n-\tunsigned int hash = strhash(str);\n-\tstruct strset_item *item;\n-\n-\tif (!ss->map.table)\n-\t\thashmap_init(&ss->map, strset_item_hashcmp, NULL, 0);\n-\n-\tif (hashmap_get_from_hash(&ss->map, hash, str))\n+\tif (strset_contains(dups, str))\n \t\treturn 1;\n-\n-\tFLEX_ALLOC_STR(item, value, str);\n-\thashmap_entry_init(&item->ent, hash);\n-\thashmap_add(&ss->map, &item->ent);\n+\tstrset_add(dups, str);\n \treturn 0;\n }\n \n-static void strset_clear(struct strset *ss)\n-{\n-\tif (!ss->map.table)\n-\t\treturn;\n-\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n-}\n-\n static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t struct strset *dups,\n \t\t\t\t\t struct commit *commit,\n@@ -253,7 +208,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\tif (!parse_ident(log, &ident, value))\n \t\t\tvalue = ident.buf;\n \n-\t\tif (strset_check_and_add(dups, value))\n+\t\tif (check_dup(dups, value))\n \t\t\tcontinue;\n \t\tinsert_one_record(log, value, oneline);\n \t}\n@@ -291,7 +246,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%aN <%aE>\" : \"%aN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    !check_dup(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_COMMITTER) {\n@@ -300,7 +255,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%cN <%cE>\" : \"%cN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    !check_dup(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_TRAILER) {\n-- \n2.29.2.575.gb51baa09ad\n\n"},{"id":"409215","messageId":"xmqq8sbfk0ut.fsf@gitster.c.googlers.com","threadId":"54087","inReplyTo":"20201105132909.GB91972@coredump.intra.peff.net","subject":"Re: [PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-05T20:25:14Z","receivedAt":"2020-11-05T20:25:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Subject: [PATCH] shortlog: drop custom strset implementation\n>\n> We can use the strset recently added in strmap.h instead. Note that this\n> doesn't have a \"check_and_add\" function. We can easily write the same\n> thing using separate \"contains\" and \"adds\" calls. This is slightly less\n> efficient, in that it hashes the string twice, but for our use here it\n> shouldn't be a big deal either way.\n>\n> I did leave it as a separate helper function, though, since we use it in\n> three separate spots (some of which are in the middle of a conditional).\n\nIt makes sense, but check_dup() sounds like its use pattern would be\n\n\tif (check_dup(it) == NO_DUP)\n\t\tadd(it);\n\nwhere it is more like \"add it but just once\".\n\nBy the way, is a strset a set or a bag?  If it makes the second add\nan no-op, perhaps your check_dup() is what strset_add() should do\nitself?  What builtin/shortlog.c::check_dup() does smells like it is\na workaround for the lack of a naturally-expected feature.\n\n"},{"id":"409231","messageId":"20201105211736.GA134274@coredump.intra.peff.net","threadId":"54087","inReplyTo":"xmqq8sbfk0ut.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-05T21:17:36Z","receivedAt":"2020-11-05T21:17:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 05, 2020 at 12:25:14PM -0800, Junio C Hamano wrote:\n\n> Jeff King <peff@peff.net> writes:\n> \n> > Subject: [PATCH] shortlog: drop custom strset implementation\n> >\n> > We can use the strset recently added in strmap.h instead. Note that this\n> > doesn't have a \"check_and_add\" function. We can easily write the same\n> > thing using separate \"contains\" and \"adds\" calls. This is slightly less\n> > efficient, in that it hashes the string twice, but for our use here it\n> > shouldn't be a big deal either way.\n> >\n> > I did leave it as a separate helper function, though, since we use it in\n> > three separate spots (some of which are in the middle of a conditional).\n> \n> It makes sense, but check_dup() sounds like its use pattern would be\n> \n> \tif (check_dup(it) == NO_DUP)\n> \t\tadd(it);\n> \n> where it is more like \"add it but just once\".\n\nHmph. I picked that name (versus just \"contains\") hoping it be general\nenough to cover the dual operation.  Better name suggestions are\nwelcome. Though...\n\n> By the way, is a strset a set or a bag?  If it makes the second add\n> an no-op, perhaps your check_dup() is what strset_add() should do\n> itself?  What builtin/shortlog.c::check_dup() does smells like it is\n> a workaround for the lack of a naturally-expected feature.\n\nYes, if strset_add() returned an integer telling us whether the item was\nalready in the set, then we could use it directly. It's slightly\nnon-trivial to do, though, as it's built around strmap_put(), which\nreturns a pointer to the old value. But since we're a set and not a map,\nthat value is always NULL; we can't tell the difference between \"I was\nstoring an old value which was NULL\" and \"I was not storing any value\".\n\nIf strset were built around strintmap it could store \"1\" for \"present in\nthe set\". It somehow feels hacky, though, to induce extra value writes\njust for the sake of working around the API.\n\nSince strset is defined within strmap.c, perhaps it wouldn't be too bad\nfor it to be more intimate with the details here. I.e., to use\nfind_strmap_entry() directly, and if the value is not present, to create\na new hashmap entry. That would require hacking up strmap_put() into a\nfew helpers, but it's probably not too bad.\n\n-Peff\n"},{"id":"409232","messageId":"CABPp-BGFa_e-nncnCH5GSjhn47Sxf7Tpn7prsJCWiJo-SWoYNA@mail.gmail.com","threadId":"54087","inReplyTo":"xmqq8sbfk0ut.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-05T21:22:14Z","receivedAt":"2020-11-05T21:22:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Nov 5, 2020 at 12:25 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Jeff King <peff@peff.net> writes:\n>\n> > Subject: [PATCH] shortlog: drop custom strset implementation\n> >\n> > We can use the strset recently added in strmap.h instead. Note that this\n> > doesn't have a \"check_and_add\" function. We can easily write the same\n> > thing using separate \"contains\" and \"adds\" calls. This is slightly less\n> > efficient, in that it hashes the string twice, but for our use here it\n> > shouldn't be a big deal either way.\n> >\n> > I did leave it as a separate helper function, though, since we use it in\n> > three separate spots (some of which are in the middle of a conditional).\n>\n> It makes sense, but check_dup() sounds like its use pattern would be\n>\n>         if (check_dup(it) == NO_DUP)\n>                 add(it);\n>\n> where it is more like \"add it but just once\".\n>\n> By the way, is a strset a set or a bag?  If it makes the second add\n\nstrset is a set; there is no way to get duplicate entries.\n\n> an no-op, perhaps your check_dup() is what strset_add() should do\n> itself?  What builtin/shortlog.c::check_dup() does smells like it is\n> a workaround for the lack of a naturally-expected feature.\n\nIs the expectation that strset_add() would return a boolean for\nwhether a new entry was added?\n"},{"id":"409239","messageId":"xmqqimajh2lw.fsf@gitster.c.googlers.com","threadId":"54087","inReplyTo":"CABPp-BGFa_e-nncnCH5GSjhn47Sxf7Tpn7prsJCWiJo-SWoYNA@mail.gmail.com","subject":"Re: [PATCH v4 00/13] Add struct strmap and associated utility functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-05T22:15:39Z","receivedAt":"2020-11-05T22:15:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> It makes sense, but check_dup() sounds like its use pattern would be\n>>\n>>         if (check_dup(it) == NO_DUP)\n>>                 add(it);\n>>\n>> where it is more like \"add it but just once\".\n>>\n>> By the way, is a strset a set or a bag?  If it makes the second add\n>\n> strset is a set; there is no way to get duplicate entries.\n>\n>> an no-op, perhaps your check_dup() is what strset_add() should do\n>> itself?  What builtin/shortlog.c::check_dup() does smells like it is\n>> a workaround for the lack of a naturally-expected feature.\n>\n> Is the expectation that strset_add() would return a boolean for\n> whether a new entry was added?\n\nIt seems to be a reasonable expectation that the caller can tell if\nthe add was \"already there and was a no-op\", judging from what we\nsaw in the shortlog code, which was the first audience the API was\nintroduced to support.  It seems to benefit from it if it were\navailable, and has to work around the lack of it with check_dup()\nwrapper.\n\n\n"},{"id":"409241","messageId":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1604622298.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 01/15] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:44Z","receivedAt":"2020-11-06T00:25:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  We are dealing with a map table,\nentries in that table, and possibly also things each of those entries\npoint to.  I had to consult other source code examples and the\nimplementation.  Add a brief note to clarify the differences.  This will\nbecome even more important once we introduce a new\nhashmap_partial_clear() function which will add the question of whether\nthe table itself has been freed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 31 +++++++++++++++++++++++++++++--\n 1 file changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex b011b394fe..2994dc7a9c 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ *\n+ * instead of\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"409242","messageId":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v4.git.git.1604535765.gitgitgadget@gmail.com","subject":"[PATCH v5 00/15] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:43Z","receivedAt":"2020-11-06T00:25:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce new strmap, strintmap, and strset types. This strmap type\nwas based on Peff's proposal from a couple years ago[1], but has additions\nthat I made as I used it, and a number of additions/changes suggested by\nPeff in his reviews (and Junio in his). I also start the series off with\nsome changes to hashmap, based on Peff's feedback on v1 & v2.\n\nNOTE: While en/merge-ort-impl depends on this series, there are no changes\nin v5 that affect it so en/merge-ort-impl does not need a reroll.\n\nChanges since v4:\n\n * Make strset_add() return a boolean -- 1 if it added the value to the set,\n   0 if the value was already in the set.\n * Add a preparatory patch to the above which adds a create_entry() helper\n   function so that strset_add() can bypass strmap_put().\n * Add a patch which updates shortlog to use the new strset API.\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nElijah Newren (15):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  hashmap: adjust spacing to fix argument alignment\n  hashmap: allow re-use after hashmap_free()\n  hashmap: introduce a new hashmap_partial_clear()\n  hashmap: provide deallocation function names\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: enable faster clearing and reusing of strmaps\n  strmap: add functions facilitating use as a string->int map\n  strmap: split create_entry() out of strmap_put()\n  strmap: add a strset sub-type\n  strmap: enable allocations to come from a mem_pool\n  strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n  Use new HASHMAP_INIT macro to simplify hashmap initialization\n  shortlog: use strset from strmap.h\n\n Makefile                |   1 +\n add-interactive.c       |   2 +-\n attr.c                  |  26 ++--\n blame.c                 |   2 +-\n bloom.c                 |   5 +-\n builtin/difftool.c      |   9 +-\n builtin/fetch.c         |   6 +-\n builtin/shortlog.c      |  61 +--------\n config.c                |   2 +-\n diff.c                  |   4 +-\n diffcore-rename.c       |   2 +-\n dir.c                   |   8 +-\n hashmap.c               |  74 +++++++----\n hashmap.h               |  91 +++++++++++---\n merge-recursive.c       |   6 +-\n name-hash.c             |   4 +-\n object.c                |   2 +-\n oidmap.c                |   2 +-\n patch-ids.c             |   2 +-\n range-diff.c            |   6 +-\n ref-filter.c            |   2 +-\n revision.c              |  11 +-\n sequencer.c             |   4 +-\n strmap.c                | 178 ++++++++++++++++++++++++++\n strmap.h                | 268 ++++++++++++++++++++++++++++++++++++++++\n submodule-config.c      |   4 +-\n t/helper/test-hashmap.c |   9 +-\n 27 files changed, 621 insertions(+), 170 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: d4a392452e292ff924e79ec8458611c0f679d6d4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v5\nPull-Request: https://github.com/git/git/pull/835\n\nRange-diff vs v4:\n\n  1:  af6b6fcb46 =  1:  af6b6fcb46 hashmap: add usage documentation explaining hashmap_free[_entries]()\n  2:  591161fd78 =  2:  591161fd78 hashmap: adjust spacing to fix argument alignment\n  3:  f2718d036d =  3:  f2718d036d hashmap: allow re-use after hashmap_free()\n  4:  61f1da3c51 =  4:  61f1da3c51 hashmap: introduce a new hashmap_partial_clear()\n  5:  861e8d65ae =  5:  861e8d65ae hashmap: provide deallocation function names\n  6:  448d3b219f =  6:  448d3b219f strmap: new utility functions\n  7:  5e8004c728 =  7:  5e8004c728 strmap: add more utility functions\n  8:  fd96e9fc8d =  8:  fd96e9fc8d strmap: enable faster clearing and reusing of strmaps\n  9:  f499934f54 =  9:  f499934f54 strmap: add functions facilitating use as a string->int map\n  -:  ---------- > 10:  3bcceb8cdb strmap: split create_entry() out of strmap_put()\n 10:  ee1ec55f1b ! 11:  e128a71fec strmap: add a strset sub-type\n     @@ Commit message\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     + ## strmap.c ##\n     +@@ strmap.c: void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n     + \telse\n     + \t\tstrintmap_set(map, str, map->default_value + amt);\n     + }\n     ++\n     ++int strset_add(struct strset *set, const char *str)\n     ++{\n     ++\t/*\n     ++\t * Cannot use strmap_put() because it'll return NULL in both cases:\n     ++\t *   - cannot find str: NULL means \"not found\"\n     ++\t *   - does find str: NULL is the value associated with str\n     ++\t */\n     ++\tstruct strmap_entry *entry = find_strmap_entry(&set->map, str);\n     ++\n     ++\tif (entry)\n     ++\t\treturn 0;\n     ++\n     ++\tentry = create_entry(&set->map, str, NULL);\n     ++\thashmap_add(&set->map.map, &entry->ent);\n     ++\treturn 1;\n     ++}\n     +\n       ## strmap.h ##\n      @@ strmap.h: int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n       \t\t\t.map = STRMAP_INIT,   \\\n     @@ strmap.h: static inline void strintmap_set(struct strintmap *map, const char *st\n      +\treturn strmap_get_size(&set->map);\n      +}\n      +\n     -+static inline void strset_add(struct strset *set, const char *str)\n     -+{\n     -+\tstrmap_put(&set->map, str, NULL);\n     -+}\n     ++/* Returns 1 if str is added to the set; returns 0 if str was already in set */\n     ++int strset_add(struct strset *set, const char *str);\n      +\n       #endif /* STRMAP_H */\n 11:  73a57045c3 ! 12:  34f542d9dd strmap: enable allocations to come from a mem_pool\n     @@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_values)\n       \t}\n       }\n       \n     -@@ strmap.c: void *strmap_put(struct strmap *map, const char *str, void *data)\n     - \t} else {\n     - \t\tconst char *key = str;\n     - \n     --\t\tentry = xmalloc(sizeof(*entry));\n     -+\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n     -+\t\t\t\t  : xmalloc(sizeof(*entry));\n     - \t\thashmap_entry_init(&entry->ent, strhash(str));\n     - \n     - \t\tif (map->strdup_strings)\n     --\t\t\tkey = xstrdup(str);\n     -+\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n     -+\t\t\t\t\t: xstrdup(str);\n     - \t\tentry->key = key;\n     - \t\tentry->value = data;\n     - \t\thashmap_add(&map->map, &entry->ent);\n     +@@ strmap.c: static struct strmap_entry *create_entry(struct strmap *map,\n     + \tstruct strmap_entry *entry;\n     + \tconst char *key = str;\n     + \n     +-\tentry = xmalloc(sizeof(*entry));\n     ++\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n     ++\t\t\t  : xmalloc(sizeof(*entry));\n     + \thashmap_entry_init(&entry->ent, strhash(str));\n     + \n     + \tif (map->strdup_strings)\n     +-\t\tkey = xstrdup(str);\n     ++\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n     ++\t\t\t\t: xstrdup(str);\n     + \tentry->key = key;\n     + \tentry->value = data;\n     + \treturn entry;\n      @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_value)\n       \t\treturn;\n       \tif (free_value)\n 12:  0352260de4 ! 13:  39ec2fa411 strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n     @@ strmap.c: static void strmap_free_entries_(struct strmap *map, int free_values)\n       \t}\n       }\n       \n     -@@ strmap.c: void strmap_partial_clear(struct strmap *map, int free_values)\n     - void *strmap_put(struct strmap *map, const char *str, void *data)\n     +@@ strmap.c: static struct strmap_entry *create_entry(struct strmap *map,\n     + \t\t\t\t\t void *data)\n       {\n     - \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n     --\tvoid *old = NULL;\n     + \tstruct strmap_entry *entry;\n     +-\tconst char *key = str;\n       \n     - \tif (entry) {\n     --\t\told = entry->value;\n     -+\t\tvoid *old = entry->value;\n     - \t\tentry->value = data;\n     --\t} else {\n     --\t\tconst char *key = str;\n     --\n     --\t\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n     --\t\t\t\t  : xmalloc(sizeof(*entry));\n     --\t\thashmap_entry_init(&entry->ent, strhash(str));\n     -+\t\treturn old;\n     -+\t}\n     - \n     --\t\tif (map->strdup_strings)\n     --\t\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n     --\t\t\t\t\t: xstrdup(str);\n     --\t\tentry->key = key;\n     --\t\tentry->value = data;\n     --\t\thashmap_add(&map->map, &entry->ent);\n     +-\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n     +-\t\t\t  : xmalloc(sizeof(*entry));\n      +\tif (map->strdup_strings) {\n      +\t\tif (!map->pool) {\n      +\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n     @@ strmap.c: void strmap_partial_clear(struct strmap *map, int free_values)\n      +\t\tentry = xmalloc(sizeof(*entry));\n      +\t} else {\n      +\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n     - \t}\n     --\treturn old;\n     -+\thashmap_entry_init(&entry->ent, strhash(str));\n     ++\t}\n     + \thashmap_entry_init(&entry->ent, strhash(str));\n     +-\n     +-\tif (map->strdup_strings)\n     +-\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n     +-\t\t\t\t: xstrdup(str);\n     +-\tentry->key = key;\n      +\tif (!map->strdup_strings)\n      +\t\tentry->key = str;\n     -+\tentry->value = data;\n     -+\thashmap_add(&map->map, &entry->ent);\n     -+\treturn NULL;\n     + \tentry->value = data;\n     + \treturn entry;\n       }\n     - \n     - struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n      @@ strmap.c: void strmap_remove(struct strmap *map, const char *str, int free_value)\n       \t\treturn;\n       \tif (free_value)\n 13:  617926540b = 14:  d3713d88f2 Use new HASHMAP_INIT macro to simplify hashmap initialization\n  -:  ---------- > 15:  24e5ce60f5 shortlog: use strset from strmap.h\n\n-- \ngitgitgadget\n"},{"id":"409243","messageId":"591161fd781b7666ddaa45694eb20610cc359741.1604622298.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 02/15] hashmap: adjust spacing to fix argument alignment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:45Z","receivedAt":"2020-11-06T00:25:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo actual code changes; just whitespace adjustments.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 17 +++++++++--------\n hashmap.h | 22 +++++++++++-----------\n 2 files changed, 20 insertions(+), 19 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex 09813e1a46..e44d8a3e85 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size)\n }\n \n static inline int entry_equals(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *e1, const struct hashmap_entry *e2,\n-\t\tconst void *keydata)\n+\t\t\t       const struct hashmap_entry *e1,\n+\t\t\t       const struct hashmap_entry *e2,\n+\t\t\t       const void *keydata)\n {\n \treturn (e1 == e2) ||\n \t       (e1->hash == e2->hash &&\n@@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map,\n }\n \n static inline unsigned int bucket(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *key)\n+\t\t\t\t  const struct hashmap_entry *key)\n {\n \treturn key->hash & (map->tablesize - 1);\n }\n@@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data,\n }\n \n void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n-\t\tconst void *cmpfn_data, size_t initial_size)\n+\t\t  const void *cmpfn_data, size_t initial_size)\n {\n \tunsigned int size = HASHMAP_INITIAL_SIZE;\n \n@@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry)\n+\t\t\t\t       const struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *e = entry->next;\n \tfor (; e; e = e->next)\n@@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n }\n \n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata)\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n \tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n@@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry)\n+\t\t\t\t  struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n \thashmap_add(map, entry);\ndiff --git a/hashmap.h b/hashmap.h\nindex 2994dc7a9c..904f61d6e1 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -228,9 +228,9 @@ struct hashmap {\n  * prevent expensive resizing. If 0, the table is dynamically resized.\n  */\n void hashmap_init(struct hashmap *map,\n-\t\t\t hashmap_cmp_fn equals_function,\n-\t\t\t const void *equals_function_data,\n-\t\t\t size_t initial_size);\n+\t\t  hashmap_cmp_fn equals_function,\n+\t\t  const void *equals_function_data,\n+\t\t  size_t initial_size);\n \n /* internal function for freeing hashmap */\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n@@ -288,7 +288,7 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  * and if it is on stack, you can just let it go out of scope).\n  */\n static inline void hashmap_entry_init(struct hashmap_entry *e,\n-\t\t\t\t\tunsigned int hash)\n+\t\t\t\t      unsigned int hash)\n {\n \te->hash = hash;\n \te->next = NULL;\n@@ -330,8 +330,8 @@ static inline unsigned int hashmap_get_size(struct hashmap *map)\n  * to `hashmap_cmp_fn` to decide whether the entry matches the key.\n  */\n struct hashmap_entry *hashmap_get(const struct hashmap *map,\n-\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\tconst void *keydata);\n+\t\t\t\t  const struct hashmap_entry *key,\n+\t\t\t\t  const void *keydata);\n \n /*\n  * Returns the hashmap entry for the specified hash code and key data,\n@@ -364,7 +364,7 @@ static inline struct hashmap_entry *hashmap_get_from_hash(\n  * call to `hashmap_get` or `hashmap_get_next`.\n  */\n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry);\n+\t\t\t\t       const struct hashmap_entry *entry);\n \n /*\n  * Adds a hashmap entry. This allows to add duplicate entries (i.e.\n@@ -384,7 +384,7 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);\n  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).\n  */\n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry);\n+\t\t\t\t  struct hashmap_entry *entry);\n \n /*\n  * Adds or replaces a hashmap entry contained within @keyvar,\n@@ -406,8 +406,8 @@ struct hashmap_entry *hashmap_put(struct hashmap *map,\n  * Argument explanation is the same as in `hashmap_get`.\n  */\n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata);\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata);\n \n /*\n  * Removes a hashmap entry contained within @keyvar,\n@@ -449,7 +449,7 @@ struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);\n \n /* Initializes the iterator and returns the first entry, if any. */\n static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,\n-\t\tstruct hashmap_iter *iter)\n+\t\t\t\t\t\t       struct hashmap_iter *iter)\n {\n \thashmap_iter_init(map, iter);\n \treturn hashmap_iter_next(iter);\n-- \ngitgitgadget\n\n"},{"id":"409244","messageId":"61f1da3c51b521035ba728c025cd6a28397f8051.1604622298.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 04/15] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:47Z","receivedAt":"2020-11-06T00:25:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\nclear_or_reinit_internal_opts() in merge-ort was taking about 12% of\noverall runtime in my testcase involving rebasing 35 patches of\nlinux.git across a big rename.  clear_or_reinit_internal_opts() was\ncalling hashmap_free() followed by hashmap_init(), meaning that not only\nwas it freeing all the memory associated with each of the strmaps just\nto immediately allocate a new array again, it was allocating a new array\nthat was likely smaller than needed (thus resulting in later need to\nrehash things).  The ending size of the map table on the previous commit\nwas likely almost perfectly sized for the next commit we wanted to pick,\nand not dropping and reallocating the table immediately is a win.\n\nAdd some new API to hashmap to clear a hashmap of entries without\nfreeing map->table (and instead only zeroing it out like alloc_table()\nwould do, along with zeroing the count of items in the table and the\nshrink_at field).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 39 +++++++++++++++++++++++++++------------\n hashmap.h | 13 ++++++++++++-\n 2 files changed, 39 insertions(+), 13 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex bb7c9979b8..922ed07954 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n \tmap->do_count_items = 1;\n }\n \n+static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct hashmap_entry *e;\n+\n+\thashmap_iter_init(map, &iter);\n+\twhile ((e = hashmap_iter_next(&iter)))\n+\t\t/*\n+\t\t * like container_of, but using caller-calculated\n+\t\t * offset (caller being hashmap_free_entries)\n+\t\t */\n+\t\tfree((char *)e - entry_offset);\n+}\n+\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tif (!map || !map->table)\n+\t\treturn;\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n+\tmemset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));\n+\tmap->shrink_at = 0;\n+\tmap->private_size = 0;\n+}\n+\n void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0) { /* called by hashmap_free_entries */\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct hashmap_entry *e;\n-\n-\t\thashmap_iter_init(map, &iter);\n-\t\twhile ((e = hashmap_iter_next(&iter)))\n-\t\t\t/*\n-\t\t\t * like container_of, but using caller-calculated\n-\t\t\t * offset (caller being hashmap_free_entries)\n-\t\t\t */\n-\t\t\tfree((char *)e - entry_offset);\n-\t}\n+\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\n }\ndiff --git a/hashmap.h b/hashmap.h\nindex 3b0f2bcade..e9430d582a 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -235,7 +235,8 @@ void hashmap_init(struct hashmap *map,\n \t\t  const void *equals_function_data,\n \t\t  size_t initial_size);\n \n-/* internal function for freeing hashmap */\n+/* internal functions for clearing or freeing hashmap */\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n@@ -268,6 +269,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n+/*\n+ * Basically the same as calling hashmap_free() followed by hashmap_init(),\n+ * but doesn't incur the overhead of deallocating and reallocating\n+ * map->table; it leaves map->table allocated and the same size but zeroes\n+ * it out so it's ready for use again as an empty map.  As with\n+ * hashmap_free(), you may need to free the entries yourself before calling\n+ * this function.\n+ */\n+#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n+\n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n  * where @member is the hashmap_entry struct used to associate with @map.\n-- \ngitgitgadget\n\n"},{"id":"409245","messageId":"f2718d036dc5d08c2ed416a5ef98c5730bc38cfc.1604622298.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 03/15] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:46Z","receivedAt":"2020-11-06T00:25:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nPreviously, once map->table had been freed, any calls to hashmap_put(),\nhashmap_get(), or hashmap_remove() would cause a NULL pointer\ndereference (since hashmap_free_() also zeros the memory; without that\nzeroing, calling these functions would cause a use-after-free problem).\n\nModify these functions to check for a NULL table and automatically\nallocate as needed.\n\nAlso add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the\nstack without calling hashmap_init().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 16 ++++++++++++++--\n hashmap.h |  3 +++\n 2 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex e44d8a3e85..bb7c9979b8 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash)\n \n static void rehash(struct hashmap *map, unsigned int newsize)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tunsigned int i, oldsize = map->tablesize;\n \tstruct hashmap_entry **oldtable = map->table;\n \n@@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize)\n static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,\n \t\tconst struct hashmap_entry *key, const void *keydata)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tstruct hashmap_entry **e = &map->table[bucket(map, key)];\n \twhile (*e && !entry_equals(map, *e, key, keydata))\n \t\te = &(*e)->next;\n@@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n \t\t\t\tconst struct hashmap_entry *key,\n \t\t\t\tconst void *keydata)\n {\n+\tif (!map->table)\n+\t\treturn NULL;\n \treturn *find_entry_ptr(map, key, keydata);\n }\n \n@@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n \n void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n {\n-\tunsigned int b = bucket(map, entry);\n+\tunsigned int b;\n+\n+\tif (!map->table)\n+\t\talloc_table(map, HASHMAP_INITIAL_SIZE);\n \n+\tb = bucket(map, entry);\n \t/* add entry */\n \tentry->next = map->table[b];\n \tmap->table[b] = entry;\n@@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n \t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n-\tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n+\tstruct hashmap_entry **e;\n+\n+\tif (!map->table)\n+\t\treturn NULL;\n+\te = find_entry_ptr(map, key, keydata);\n \tif (!*e)\n \t\treturn NULL;\n \ndiff --git a/hashmap.h b/hashmap.h\nindex 904f61d6e1..3b0f2bcade 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -210,6 +210,9 @@ struct hashmap {\n \n /* hashmap functions */\n \n+#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \\\n+\t\t\t\t .do_count_items = 1 }\n+\n /*\n  * Initializes a hashmap structure.\n  *\n-- \ngitgitgadget\n\n"},{"id":"409246","messageId":"3bcceb8cdb1dfd571ac5e0b45f20b3aef8bf553b.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 10/15] strmap: split create_entry() out of strmap_put()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:53Z","receivedAt":"2020-11-06T00:25:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis will facilitate adding entries to a strmap subtype in ways that\ndiffer slightly from that of strmap_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 37 +++++++++++++++++++++++--------------\n 1 file changed, 23 insertions(+), 14 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 0d10a884b5..dc84c57c07 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -70,27 +70,36 @@ void strmap_partial_clear(struct strmap *map, int free_values)\n \thashmap_partial_clear(&map->map);\n }\n \n+static struct strmap_entry *create_entry(struct strmap *map,\n+\t\t\t\t\t const char *str,\n+\t\t\t\t\t void *data)\n+{\n+\tstruct strmap_entry *entry;\n+\tconst char *key = str;\n+\n+\tentry = xmalloc(sizeof(*entry));\n+\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\tif (map->strdup_strings)\n+\t\tkey = xstrdup(str);\n+\tentry->key = key;\n+\tentry->value = data;\n+\treturn entry;\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n-\tvoid *old = NULL;\n \n \tif (entry) {\n-\t\told = entry->value;\n+\t\tvoid *old = entry->value;\n \t\tentry->value = data;\n-\t} else {\n-\t\tconst char *key = str;\n-\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\thashmap_entry_init(&entry->ent, strhash(str));\n-\n-\t\tif (map->strdup_strings)\n-\t\t\tkey = xstrdup(str);\n-\t\tentry->key = key;\n-\t\tentry->value = data;\n-\t\thashmap_add(&map->map, &entry->ent);\n+\t\treturn old;\n \t}\n-\treturn old;\n+\n+\tentry = create_entry(map, str, data);\n+\thashmap_add(&map->map, &entry->ent);\n+\treturn NULL;\n }\n \n struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n-- \ngitgitgadget\n\n"},{"id":"409247","messageId":"e128a71fec415e92cfcb9c63987ee8264235bb6f.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 11/15] strmap: add a strset sub-type","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:54Z","receivedAt":"2020-11-06T00:25:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSimilar to adding strintmap for special-casing a string -> int mapping,\nadd a strset type for cases where we really are only interested in using\nstrmap for storing a set rather than a mapping.  In this case, we'll\nalways just store NULL for the value but the different struct type makes\nit clearer than code comments how a variable is intended to be used.\n\nThe difference in usage also results in some differences in API: a few\nthings that aren't necessary or meaningful are dropped (namely, the\nfree_values argument to *_clear(), and the *_get() function), and\nstrset_add() is chosen as the API instead of strset_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 17 +++++++++++++++\n strmap.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 80 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex dc84c57c07..3784865745 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -143,3 +143,20 @@ void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n \telse\n \t\tstrintmap_set(map, str, map->default_value + amt);\n }\n+\n+int strset_add(struct strset *set, const char *str)\n+{\n+\t/*\n+\t * Cannot use strmap_put() because it'll return NULL in both cases:\n+\t *   - cannot find str: NULL means \"not found\"\n+\t *   - does find str: NULL is the value associated with str\n+\t */\n+\tstruct strmap_entry *entry = find_strmap_entry(&set->map, str);\n+\n+\tif (entry)\n+\t\treturn 0;\n+\n+\tentry = create_entry(&set->map, str, NULL);\n+\thashmap_add(&set->map.map, &entry->ent);\n+\treturn 1;\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 56a5cdb864..c8c4d7c932 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -27,6 +27,7 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = STRMAP_INIT,   \\\n \t\t\t.default_value = 0,   \\\n \t\t       }\n+#define STRSET_INIT { .map = STRMAP_INIT }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -196,4 +197,66 @@ static inline void strintmap_set(struct strintmap *map, const char *str,\n  */\n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n \n+/*\n+ * strset:\n+ *    A set of strings.\n+ *\n+ * Primary differences with strmap:\n+ *    1) The value is always NULL, and ignored.  As there is no value to free,\n+ *       there is one fewer argument to strset_clear\n+ *    2) No strset_get() because there is no value.\n+ *    3) No strset_put(); use strset_add() instead.\n+ */\n+\n+struct strset {\n+\tstruct strmap map;\n+};\n+\n+#define strset_for_each_entry(mystrset, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrset)->map, iter, var)\n+\n+static inline void strset_init(struct strset *set)\n+{\n+\tstrmap_init(&set->map);\n+}\n+\n+static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    int strdup_strings)\n+{\n+\tstrmap_init_with_options(&set->map, strdup_strings);\n+}\n+\n+static inline void strset_clear(struct strset *set)\n+{\n+\tstrmap_clear(&set->map, 0);\n+}\n+\n+static inline void strset_partial_clear(struct strset *set)\n+{\n+\tstrmap_partial_clear(&set->map, 0);\n+}\n+\n+static inline int strset_contains(struct strset *set, const char *str)\n+{\n+\treturn strmap_contains(&set->map, str);\n+}\n+\n+static inline void strset_remove(struct strset *set, const char *str)\n+{\n+\treturn strmap_remove(&set->map, str, 0);\n+}\n+\n+static inline int strset_empty(struct strset *set)\n+{\n+\treturn strmap_empty(&set->map);\n+}\n+\n+static inline unsigned int strset_get_size(struct strset *set)\n+{\n+\treturn strmap_get_size(&set->map);\n+}\n+\n+/* Returns 1 if str is added to the set; returns 0 if str was already in set */\n+int strset_add(struct strset *set, const char *str);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409248","messageId":"861e8d65ae8065595d9d4ccff5f70155fec408c9.1604622298.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 05/15] hashmap: provide deallocation function names","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:48Z","receivedAt":"2020-11-06T00:25:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nhashmap_free(), hashmap_free_entries(), and hashmap_free_() have existed\nfor a while, but aren't necessarily the clearest names, especially with\nhashmap_partial_clear() being added to the mix and lazy-initialization\nnow being supported.  Peff suggested we adopt the following names[1]:\n\n  - hashmap_clear() - remove all entries and de-allocate any\n    hashmap-specific data, but be ready for reuse\n\n  - hashmap_clear_and_free() - ditto, but free the entries themselves\n\n  - hashmap_partial_clear() - remove all entries but don't deallocate\n    table\n\n  - hashmap_partial_clear_and_free() - ditto, but free the entries\n\nThis patch provides the new names and converts all existing callers over\nto the new naming scheme.\n\n[1] https://lore.kernel.org/git/20201030125059.GA3277724@coredump.intra.peff.net/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n add-interactive.c       |  2 +-\n blame.c                 |  2 +-\n bloom.c                 |  2 +-\n builtin/fetch.c         |  6 +++---\n builtin/shortlog.c      |  2 +-\n config.c                |  2 +-\n diff.c                  |  4 ++--\n diffcore-rename.c       |  2 +-\n dir.c                   |  8 ++++----\n hashmap.c               |  6 +++---\n hashmap.h               | 44 +++++++++++++++++++++++++----------------\n merge-recursive.c       |  6 +++---\n name-hash.c             |  4 ++--\n object.c                |  2 +-\n oidmap.c                |  2 +-\n patch-ids.c             |  2 +-\n range-diff.c            |  2 +-\n ref-filter.c            |  2 +-\n revision.c              |  2 +-\n sequencer.c             |  4 ++--\n submodule-config.c      |  4 ++--\n t/helper/test-hashmap.c |  6 +++---\n 22 files changed, 63 insertions(+), 53 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 555c4abf32..a14c0feaa2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -557,7 +557,7 @@ static int get_modified_files(struct repository *r,\n \t\tif (ps)\n \t\t\tclear_pathspec(&rev.prune_data);\n \t}\n-\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\thashmap_clear_and_free(&s.file_map, struct pathname_entry, ent);\n \tif (unmerged_count)\n \t\t*unmerged_count = s.unmerged_count;\n \tif (binary_count)\ndiff --git a/blame.c b/blame.c\nindex 686845b2b4..229beb6452 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -435,7 +435,7 @@ static void get_fingerprint(struct fingerprint *result,\n \n static void free_fingerprint(struct fingerprint *f)\n {\n-\thashmap_free(&f->map);\n+\thashmap_clear(&f->map);\n \tfree(f->entries);\n }\n \ndiff --git a/bloom.c b/bloom.c\nindex 68c73200a5..719c313a1c 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -287,7 +287,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \t\t}\n \n \tcleanup:\n-\t\thashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);\n+\t\thashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry);\n \t} else {\n \t\tfor (i = 0; i < diff_queued_diff.nr; i++)\n \t\t\tdiff_free_filepair(diff_queued_diff.queue[i]);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex f9c3c49f14..ecf8537605 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -393,7 +393,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\titem = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);\n \t\tstring_list_insert(&remote_refs_list, ref->name);\n \t}\n-\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \t/*\n \t * We may have a final lightweight tag that needs to be\n@@ -428,7 +428,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\t**tail = rm;\n \t\t*tail = &rm->next;\n \t}\n-\thashmap_free_entries(&remote_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent);\n \tstring_list_clear(&remote_refs_list, 0);\n \toidset_clear(&fetch_oids);\n }\n@@ -573,7 +573,7 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \tif (existing_refs_populated)\n-\t\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\t\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \treturn ref_map;\n }\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 0a5c4968f6..83f0a739b4 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -220,7 +220,7 @@ static void strset_clear(struct strset *ss)\n {\n \tif (!ss->map.table)\n \t\treturn;\n-\thashmap_free_entries(&ss->map, struct strset_item, ent);\n+\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n }\n \n static void insert_records_from_trailers(struct shortlog *log,\ndiff --git a/config.c b/config.c\nindex 2bdff4457b..8f324ed3a6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1963,7 +1963,7 @@ void git_configset_clear(struct config_set *cs)\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_free_entries(&cs->config_hash, struct config_set_element, ent);\n+\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n \tcs->hash_initialized = 0;\n \tfree(cs->list.items);\n \tcs->list.nr = 0;\ndiff --git a/diff.c b/diff.c\nindex 2bb2f8f57e..8e0e59f5cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6289,9 +6289,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \t\t\tif (o->color_moved == COLOR_MOVED_ZEBRA_DIM)\n \t\t\t\tdim_moved_lines(o);\n \n-\t\t\thashmap_free_entries(&add_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&add_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n-\t\t\thashmap_free_entries(&del_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&del_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n \t\t}\n \ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 99e63e90f8..d367a6d244 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -407,7 +407,7 @@ static int find_exact_renames(struct diff_options *options)\n \t\trenames += find_identical_files(&file_table, i, options);\n \n \t/* Free the hash data structure and entries */\n-\thashmap_free_entries(&file_table, struct file_similarity, entry);\n+\thashmap_clear_and_free(&file_table, struct file_similarity, entry);\n \n \treturn renames;\n }\ndiff --git a/dir.c b/dir.c\nindex 78387110e6..161dce121e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -817,8 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern\n \n clear_hashmaps:\n \twarning(_(\"disabling cone pattern matching\"));\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n \tpl->use_cone_patterns = 0;\n }\n \n@@ -921,8 +921,8 @@ void clear_pattern_list(struct pattern_list *pl)\n \t\tfree(pl->patterns[i]);\n \tfree(pl->patterns);\n \tfree(pl->filebuf);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n \n \tmemset(pl, 0, sizeof(*pl));\n }\ndiff --git a/hashmap.c b/hashmap.c\nindex 922ed07954..5009471800 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -183,7 +183,7 @@ static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n \twhile ((e = hashmap_iter_next(&iter)))\n \t\t/*\n \t\t * like container_of, but using caller-calculated\n-\t\t * offset (caller being hashmap_free_entries)\n+\t\t * offset (caller being hashmap_clear_and_free)\n \t\t */\n \t\tfree((char *)e - entry_offset);\n }\n@@ -199,11 +199,11 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n \tmap->private_size = 0;\n }\n \n-void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n+void hashmap_clear_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_and_free */\n \t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\ndiff --git a/hashmap.h b/hashmap.h\nindex e9430d582a..7251687d73 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -96,7 +96,7 @@\n  *         }\n  *\n  *         if (!strcmp(\"end\", action)) {\n- *             hashmap_free_entries(&map, struct long2string, ent);\n+ *             hashmap_clear_and_free(&map, struct long2string, ent);\n  *             break;\n  *         }\n  *     }\n@@ -237,7 +237,7 @@ void hashmap_init(struct hashmap *map,\n \n /* internal functions for clearing or freeing hashmap */\n void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n-void hashmap_free_(struct hashmap *map, ssize_t offset);\n+void hashmap_clear_(struct hashmap *map, ssize_t offset);\n \n /*\n  * Frees a hashmap structure and allocated memory for the table, but does not\n@@ -253,40 +253,50 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  *      free(e->somefield);\n  *      free(e);\n  *    }\n- *    hashmap_free(map);\n+ *    hashmap_clear(map);\n  *\n  * instead of\n  *\n  *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n  *      free(e->somefield);\n  *    }\n- *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *    hashmap_clear_and_free(map, struct my_entry_struct, hashmap_entry_name);\n  *\n  * to avoid the implicit extra loop over the entries.  However, if there are\n  * no special fields in your entry that need to be freed beyond the entry\n  * itself, it is probably simpler to avoid the explicit loop and just call\n- * hashmap_free_entries().\n+ * hashmap_clear_and_free().\n  */\n-#define hashmap_free(map) hashmap_free_(map, -1)\n+#define hashmap_clear(map) hashmap_clear_(map, -1)\n \n /*\n- * Basically the same as calling hashmap_free() followed by hashmap_init(),\n- * but doesn't incur the overhead of deallocating and reallocating\n- * map->table; it leaves map->table allocated and the same size but zeroes\n- * it out so it's ready for use again as an empty map.  As with\n- * hashmap_free(), you may need to free the entries yourself before calling\n- * this function.\n+ * Similar to hashmap_clear(), except that the table is no deallocated; it\n+ * is merely zeroed out but left the same size as before.  If the hashmap\n+ * will be reused, this avoids the overhead of deallocating and\n+ * reallocating map->table.  As with hashmap_clear(), you may need to free\n+ * the entries yourself before calling this function.\n  */\n #define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n \n /*\n- * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map.\n+ * Similar to hashmap_clear() but also frees all entries.  @type is the\n+ * struct type of the entry where @member is the hashmap_entry struct used\n+ * to associate with @map.\n  *\n- * See usage note above hashmap_free().\n+ * See usage note above hashmap_clear().\n  */\n-#define hashmap_free_entries(map, type, member) \\\n-\thashmap_free_(map, offsetof(type, member));\n+#define hashmap_clear_and_free(map, type, member) \\\n+\thashmap_clear_(map, offsetof(type, member))\n+\n+/*\n+ * Similar to hashmap_partial_clear() but also frees all entries.  @type is\n+ * the struct type of the entry where @member is the hashmap_entry struct\n+ * used to associate with @map.\n+ *\n+ * See usage note above hashmap_clear().\n+ */\n+#define hashmap_partial_clear_and_free(map, type, member) \\\n+\thashmap_partial_clear_(map, offsetof(type, member))\n \n /* hashmap_entry functions */\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d0214335a7..f736a0f632 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2651,7 +2651,7 @@ static struct string_list *get_renames(struct merge_options *opt,\n \t\tfree(e->target_file);\n \t\tstring_list_clear(&e->source_files, 0);\n \t}\n-\thashmap_free_entries(&collisions, struct collision_entry, ent);\n+\thashmap_clear_and_free(&collisions, struct collision_entry, ent);\n \treturn renames;\n }\n \n@@ -2870,7 +2870,7 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,\n \t\tstrbuf_release(&e->new_dir);\n \t\t/* possible_new_dirs already cleared in get_directory_renames */\n \t}\n-\thashmap_free_entries(dir_renames, struct dir_rename_entry, ent);\n+\thashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent);\n \tfree(dir_renames);\n \n \tfree(pairs->queue);\n@@ -3497,7 +3497,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free_entries(&opt->priv->current_file_dir_set,\n+\t\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n \t\t\t\t\tstruct path_hashmap_entry, e);\n \n \t\tif (clean < 0) {\ndiff --git a/name-hash.c b/name-hash.c\nindex fb526a3775..5d3c7b12c1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -726,6 +726,6 @@ void free_name_hash(struct index_state *istate)\n \t\treturn;\n \tistate->name_hash_initialized = 0;\n \n-\thashmap_free(&istate->name_hash);\n-\thashmap_free_entries(&istate->dir_hash, struct dir_entry, ent);\n+\thashmap_clear(&istate->name_hash);\n+\thashmap_clear_and_free(&istate->dir_hash, struct dir_entry, ent);\n }\ndiff --git a/object.c b/object.c\nindex 3257518656..b8406409d5 100644\n--- a/object.c\n+++ b/object.c\n@@ -532,7 +532,7 @@ void raw_object_store_clear(struct raw_object_store *o)\n \tclose_object_store(o);\n \to->packed_git = NULL;\n \n-\thashmap_free(&o->pack_map);\n+\thashmap_clear(&o->pack_map);\n }\n \n void parsed_object_pool_clear(struct parsed_object_pool *o)\ndiff --git a/oidmap.c b/oidmap.c\nindex 423aa014a3..286a04a53c 100644\n--- a/oidmap.c\n+++ b/oidmap.c\n@@ -27,7 +27,7 @@ void oidmap_free(struct oidmap *map, int free_entries)\n \t\treturn;\n \n \t/* TODO: make oidmap itself not depend on struct layouts */\n-\thashmap_free_(&map->map, free_entries ? 0 : -1);\n+\thashmap_clear_(&map->map, free_entries ? 0 : -1);\n }\n \n void *oidmap_get(const struct oidmap *map, const struct object_id *key)\ndiff --git a/patch-ids.c b/patch-ids.c\nindex 12aa6d494b..21973e4933 100644\n--- a/patch-ids.c\n+++ b/patch-ids.c\n@@ -71,7 +71,7 @@ int init_patch_ids(struct repository *r, struct patch_ids *ids)\n \n int free_patch_ids(struct patch_ids *ids)\n {\n-\thashmap_free_entries(&ids->patches, struct patch_id, ent);\n+\thashmap_clear_and_free(&ids->patches, struct patch_id, ent);\n \treturn 0;\n }\n \ndiff --git a/range-diff.c b/range-diff.c\nindex 24dc435e48..befeecae44 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -266,7 +266,7 @@ static void find_exact_matches(struct string_list *a, struct string_list *b)\n \t\t}\n \t}\n \n-\thashmap_free(&map);\n+\thashmap_clear(&map);\n }\n \n static void diffsize_consume(void *data, char *line, unsigned long len)\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c62f6b4822..5e66b8cd76 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2222,7 +2222,7 @@ void ref_array_clear(struct ref_array *array)\n \tused_atom_cnt = 0;\n \n \tif (ref_to_worktree_map.worktrees) {\n-\t\thashmap_free_entries(&(ref_to_worktree_map.map),\n+\t\thashmap_clear_and_free(&(ref_to_worktree_map.map),\n \t\t\t\t\tstruct ref_to_worktree_entry, ent);\n \t\tfree_worktrees(ref_to_worktree_map.worktrees);\n \t\tref_to_worktree_map.worktrees = NULL;\ndiff --git a/revision.c b/revision.c\nindex aa62212040..f27649d45d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -139,7 +139,7 @@ static void paths_and_oids_clear(struct hashmap *map)\n \t\tfree(entry->path);\n \t}\n \n-\thashmap_free_entries(map, struct path_and_oids_entry, ent);\n+\thashmap_clear_and_free(map, struct path_and_oids_entry, ent);\n }\n \n static void paths_and_oids_insert(struct hashmap *map,\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..23a09c3e7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5058,7 +5058,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n-\thashmap_free_entries(&state.labels, struct labels_entry, entry);\n+\thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\n \tstrbuf_release(&state.buf);\n \n \treturn 0;\n@@ -5577,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)\n \tfor (i = 0; i < todo_list->nr; i++)\n \t\tfree(subjects[i]);\n \tfree(subjects);\n-\thashmap_free_entries(&subject2item, struct subject2item_entry, entry);\n+\thashmap_clear_and_free(&subject2item, struct subject2item_entry, entry);\n \n \tclear_commit_todo_item(&commit_todo);\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex c569e22aa3..f502505566 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -103,8 +103,8 @@ static void submodule_cache_clear(struct submodule_cache *cache)\n \t\t\t\tent /* member name */)\n \t\tfree_one_config(entry);\n \n-\thashmap_free_entries(&cache->for_path, struct submodule_entry, ent);\n-\thashmap_free_entries(&cache->for_name, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_path, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_name, struct submodule_entry, ent);\n \tcache->initialized = 0;\n \tcache->gitmodules_read = 0;\n }\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex f38706216f..2475663b49 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -110,7 +110,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t\thashmap_add(&map, &entries[i]->ent);\n \t\t\t}\n \n-\t\t\thashmap_free(&map);\n+\t\t\thashmap_clear(&map);\n \t\t}\n \t} else {\n \t\t/* test map lookups */\n@@ -130,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t}\n \t\t}\n \n-\t\thashmap_free(&map);\n+\t\thashmap_clear(&map);\n \t}\n }\n \n@@ -262,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv)\n \t}\n \n \tstrbuf_release(&line);\n-\thashmap_free_entries(&map, struct test_entry, ent);\n+\thashmap_clear_and_free(&map, struct test_entry, ent);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"409249","messageId":"39ec2fa4118636f4c994735ab11c1d312eb9771d.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 13/15] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:56Z","receivedAt":"2020-11-06T00:25:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nBy default, we do not use a mempool and strdup_strings is true; in this\ncase, we can avoid both an extra allocation and an extra free by just\nover-allocating for the strmap_entry leaving enough space at the end to\ncopy the key.  FLEXPTR_ALLOC_STR exists for exactly this purpose, so\nmake use of it.\n\nAlso, adjust the case when we are using a memory pool and strdup_strings\nis true to just do one allocation from the memory pool instead of two so\nthat the strmap_clear() and strmap_remove() code can just avoid freeing\nthe key in all cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 35 +++++++++++++++++++----------------\n strmap.h |  1 +\n 2 files changed, 20 insertions(+), 16 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 139afb9d4b..4fb9f6100e 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -59,11 +59,8 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (!map->pool) {\n-\t\t\tif (map->strdup_strings)\n-\t\t\t\tfree((char*)e->key);\n+\t\tif (!map->pool)\n \t\t\tfree(e);\n-\t\t}\n \t}\n }\n \n@@ -84,16 +81,25 @@ static struct strmap_entry *create_entry(struct strmap *map,\n \t\t\t\t\t void *data)\n {\n \tstruct strmap_entry *entry;\n-\tconst char *key = str;\n \n-\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n-\t\t\t  : xmalloc(sizeof(*entry));\n+\tif (map->strdup_strings) {\n+\t\tif (!map->pool) {\n+\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n+\t\t} else {\n+\t\t\tsize_t len = st_add(strlen(str), 1); /* include NUL */\n+\t\t\tentry = mem_pool_alloc(map->pool,\n+\t\t\t\t\t       st_add(sizeof(*entry), len));\n+\t\t\tmemcpy(entry + 1, str, len);\n+\t\t\tentry->key = (void *)(entry + 1);\n+\t\t}\n+\t} else if (!map->pool) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t} else {\n+\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n+\t}\n \thashmap_entry_init(&entry->ent, strhash(str));\n-\n-\tif (map->strdup_strings)\n-\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n-\t\t\t\t: xstrdup(str);\n-\tentry->key = key;\n+\tif (!map->strdup_strings)\n+\t\tentry->key = str;\n \tentry->value = data;\n \treturn entry;\n }\n@@ -139,11 +145,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (!map->pool) {\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)ret->key);\n+\tif (!map->pool)\n \t\tfree(ret);\n-\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex dda928703d..a99011df25 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -14,6 +14,7 @@ struct strmap_entry {\n \tstruct hashmap_entry ent;\n \tconst char *key;\n \tvoid *value;\n+\t/* strmap_entry may be allocated extra space to store the key at end */\n };\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n-- \ngitgitgadget\n\n"},{"id":"409250","messageId":"448d3b219ffebbc0daa4ef033d78fd45693c5ccd.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 06/15] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:49Z","receivedAt":"2020-11-06T00:25:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nNote that similar string-list, I have a strdup_strings setting.\nHowever, unlike string-list, strmap_init() does not take a parameter for\nthis setting and instead automatically sets it to 1; callers who want to\ncontrol this detail need to instead call strmap_init_with_options().\n(Future patches will add additional parameters to\nstrmap_init_with_options()).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |  1 +\n strmap.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h | 65 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 165 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..777a34c01c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += strvec.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..53f284eb20\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata)\n+{\n+\tconst struct strmap_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct strmap_entry, ent);\n+\te2 = container_of(entry2, const struct strmap_entry, ent);\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static struct strmap_entry *find_strmap_entry(struct strmap *map,\n+\t\t\t\t\t      const char *str)\n+{\n+\tstruct strmap_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\tstrmap_init_with_options(map, 1);\n+}\n+\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings)\n+{\n+\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n+}\n+\n+static void strmap_free_entries_(struct strmap *map, int free_values)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\t/*\n+\t * We need to iterate over the hashmap entries and free\n+\t * e->key and e->value ourselves; hashmap has no API to\n+\t * take care of that for us.  Since we're already iterating over\n+\t * the hashmap, though, might as well free e too and avoid the need\n+\t * to make some call into the hashmap API to do that.\n+\t */\n+\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\tif (free_values)\n+\t\t\tfree(e->value);\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)e->key);\n+\t\tfree(e);\n+\t}\n+}\n+\n+void strmap_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_clear(&map->map);\n+}\n+\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->value;\n+\t\tentry->value = data;\n+\t} else {\n+\t\tconst char *key = str;\n+\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\t\tif (map->strdup_strings)\n+\t\t\tkey = xstrdup(str);\n+\t\tentry->key = key;\n+\t\tentry->value = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\treturn entry ? entry->value : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..96888c23ad\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,65 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n+};\n+\n+struct strmap_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *key;\n+\tvoid *value;\n+};\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata);\n+\n+#define STRMAP_INIT { \\\n+\t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.strdup_strings = 1,                          \\\n+\t\t    }\n+\n+/*\n+ * Initialize the members of the strmap.  Any keys added to the strmap will\n+ * be strdup'ed with their memory managed by the strmap.\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Same as strmap_init, but for those who want to control the memory management\n+ * carefully instead of using the default of strdup_strings=1.\n+ */\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\".\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409252","messageId":"f499934f54a2d1d9ac6ffeafcd6388d183d26fa3.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 09/15] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:52Z","receivedAt":"2020-11-06T00:25:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 +++++++\n strmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 105 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex c410c5241a..0d10a884b5 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -123,3 +123,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\tfree((char*)ret->key);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->value;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, map->default_value + amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex c14fcee148..56a5cdb864 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -23,6 +23,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n \t\t\t.strdup_strings = 1,                          \\\n \t\t    }\n+#define STRINTMAP_INIT { \\\n+\t\t\t.map = STRMAP_INIT,   \\\n+\t\t\t.default_value = 0,   \\\n+\t\t       }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -102,4 +106,94 @@ static inline int strmap_empty(struct strmap *map)\n #define strmap_for_each_entry(mystrmap, iter, var)\t\\\n \thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n \n+\n+/*\n+ * strintmap:\n+ *    A map of string -> int, typecasting the void* of strmap to an int.\n+ *\n+ * Primary differences:\n+ *    1) Since the void* value is just an int in disguise, there is no value\n+ *       to free.  (Thus one fewer argument to strintmap_clear)\n+ *    2) strintmap_get() returns an int, or returns the default_value if the\n+ *       key is not found in the strintmap.\n+ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n+ *       instead.\n+ */\n+\n+struct strintmap {\n+\tstruct strmap map;\n+\tint default_value;\n+};\n+\n+#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n+\n+static inline void strintmap_init(struct strintmap *map, int default_value)\n+{\n+\tstrmap_init(&map->map);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_init_with_options(struct strintmap *map,\n+\t\t\t\t\t       int default_value,\n+\t\t\t\t\t       int strdup_strings)\n+{\n+\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_clear(struct strintmap *map)\n+{\n+\tstrmap_clear(&map->map, 0);\n+}\n+\n+static inline void strintmap_partial_clear(struct strintmap *map)\n+{\n+\tstrmap_partial_clear(&map->map, 0);\n+}\n+\n+static inline int strintmap_contains(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_contains(&map->map, str);\n+}\n+\n+static inline void strintmap_remove(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_remove(&map->map, str, 0);\n+}\n+\n+static inline int strintmap_empty(struct strintmap *map)\n+{\n+\treturn strmap_empty(&map->map);\n+}\n+\n+static inline unsigned int strintmap_get_size(struct strintmap *map)\n+{\n+\treturn strmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Returns the value for str in the map.  If str isn't found in the map,\n+ * the map's default_value is returned.\n+ */\n+static inline int strintmap_get(struct strintmap *map, const char *str)\n+{\n+\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n+\tif (!result)\n+\t\treturn map->default_value;\n+\treturn (intptr_t)result->value;\n+}\n+\n+static inline void strintmap_set(struct strintmap *map, const char *str,\n+\t\t\t\t intptr_t v)\n+{\n+\tstrmap_put(&map->map, str, (void *)v);\n+}\n+\n+/*\n+ * Increment the value for str by amt.  If str isn't in the map, add it and\n+ * set its value to default_value + amt.\n+ */\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409251","messageId":"fd96e9fc8df23851022edf3799012441cd4c8728.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 08/15] strmap: enable faster clearing and reusing of strmaps","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:51Z","receivedAt":"2020-11-06T00:25:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen strmaps are used heavily, such as is done by my new merge-ort\nalgorithm, and strmaps need to be cleared but then re-used (because of\ne.g. picking multiple commits to cherry-pick, or due to a recursive\nmerge having several different merges while recursing), free-ing and\nreallocating map->table repeatedly can add up in time, especially since\nit will likely be reallocated to a much smaller size but the previous\nmerge provides a good guide to the right size to use for the next merge.\n\nIntroduce strmap_partial_clear() to take advantage of this type of\nsituation; it will act similar to strmap_clear() except that\nmap->table's entries are zeroed instead of map->table being free'd.\nMaking use of this function reduced the cost of\nclear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped\nthe overall runtime of my rebase testcase by just under 2%.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 6 ++++++\n strmap.h | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 829f1bc095..c410c5241a 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_values)\n \thashmap_clear(&map->map);\n }\n \n+void strmap_partial_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_partial_clear(&map->map);\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\ndiff --git a/strmap.h b/strmap.h\nindex f74bc582e4..c14fcee148 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -42,6 +42,12 @@ void strmap_init_with_options(struct strmap *map,\n  */\n void strmap_clear(struct strmap *map, int free_values);\n \n+/*\n+ * Similar to strmap_clear() but leaves map->map->table allocated and\n+ * pre-sized so that subsequent uses won't need as many rehashings.\n+ */\n+void strmap_partial_clear(struct strmap *map, int free_values);\n+\n /*\n  * Insert \"str\" into the map, pointing to \"data\".\n  *\n-- \ngitgitgadget\n\n"},{"id":"409253","messageId":"34f542d9dd846da5fd81274966ee2ebe0660dcef.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:55Z","receivedAt":"2020-11-06T00:25:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor heavy users of strmaps, allowing the keys and entries to be\nallocated from a memory pool can provide significant overhead savings.\nAdd an option to strmap_init_with_options() to specify a memory pool.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 31 ++++++++++++++++++++++---------\n strmap.h | 11 ++++++++---\n 2 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 3784865745..139afb9d4b 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"strmap.h\"\n+#include \"mem-pool.h\"\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t     const struct hashmap_entry *entry1,\n@@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map,\n \n void strmap_init(struct strmap *map)\n {\n-\tstrmap_init_with_options(map, 1);\n+\tstrmap_init_with_options(map, NULL, 1);\n }\n \n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->pool = pool;\n \tmap->strdup_strings = strdup_strings;\n }\n \n@@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \tif (!map)\n \t\treturn;\n \n+\tif (!free_values && map->pool)\n+\t\t/* Memory other than util is owned by and freed with the pool */\n+\t\treturn;\n+\n \t/*\n \t * We need to iterate over the hashmap entries and free\n \t * e->key and e->value ourselves; hashmap has no API to\n@@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)e->key);\n-\t\tfree(e);\n+\t\tif (!map->pool) {\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree((char*)e->key);\n+\t\t\tfree(e);\n+\t\t}\n \t}\n }\n \n@@ -77,11 +86,13 @@ static struct strmap_entry *create_entry(struct strmap *map,\n \tstruct strmap_entry *entry;\n \tconst char *key = str;\n \n-\tentry = xmalloc(sizeof(*entry));\n+\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n+\t\t\t  : xmalloc(sizeof(*entry));\n \thashmap_entry_init(&entry->ent, strhash(str));\n \n \tif (map->strdup_strings)\n-\t\tkey = xstrdup(str);\n+\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n+\t\t\t\t: xstrdup(str);\n \tentry->key = key;\n \tentry->value = data;\n \treturn entry;\n@@ -128,9 +139,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (map->strdup_strings)\n-\t\tfree((char*)ret->key);\n-\tfree(ret);\n+\tif (!map->pool) {\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)ret->key);\n+\t\tfree(ret);\n+\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex c8c4d7c932..dda928703d 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -3,8 +3,10 @@\n \n #include \"hashmap.h\"\n \n+struct mempool;\n struct strmap {\n \tstruct hashmap map;\n+\tstruct mem_pool *pool;\n \tunsigned int strdup_strings:1;\n };\n \n@@ -37,9 +39,10 @@ void strmap_init(struct strmap *map);\n \n /*\n  * Same as strmap_init, but for those who want to control the memory management\n- * carefully instead of using the default of strdup_strings=1.\n+ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n  */\n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings);\n \n /*\n@@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n \n static inline void strintmap_init_with_options(struct strintmap *map,\n \t\t\t\t\t       int default_value,\n+\t\t\t\t\t       struct mem_pool *pool,\n \t\t\t\t\t       int strdup_strings)\n {\n-\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n \tmap->default_value = default_value;\n }\n \n@@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set)\n }\n \n static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    struct mem_pool *pool,\n \t\t\t\t\t    int strdup_strings)\n {\n-\tstrmap_init_with_options(&set->map, strdup_strings);\n+\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n }\n \n static inline void strset_clear(struct strset *set)\n-- \ngitgitgadget\n\n"},{"id":"409254","messageId":"5e8004c728580589cdaf46d755f2c977feaffb34.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 07/15] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:50Z","receivedAt":"2020-11-06T00:25:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_get_size()\n  * strmap_empty()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_get_entry()\n\nI suspect the first four are self-explanatory.\n\nstrmap_get_entry() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstrmap_entry that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_entry()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via entry->value\n  * directly update entry->value\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 20 ++++++++++++++++++++\n strmap.h | 34 ++++++++++++++++++++++++++++++++++\n 2 files changed, 54 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 53f284eb20..829f1bc095 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -87,6 +87,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str);\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n@@ -97,3 +102,18 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_strmap_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_value)\n+{\n+\tstruct strmap_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (!ret)\n+\t\treturn;\n+\tif (free_value)\n+\t\tfree(ret->value);\n+\tif (map->strdup_strings)\n+\t\tfree((char*)ret->key);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 96888c23ad..f74bc582e4 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -50,6 +50,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -62,4 +68,32 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn strmap_get_size(map) == 0;\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409255","messageId":"24e5ce60f5e945239ba23b823c8e69640265e531.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 15/15] shortlog: use strset from strmap.h","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:58Z","receivedAt":"2020-11-06T00:25:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/shortlog.c | 61 +++-------------------------------------------\n 1 file changed, 4 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 83f0a739b4..c52e4ccd19 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -10,6 +10,7 @@\n #include \"shortlog.h\"\n #include \"parse-options.h\"\n #include \"trailer.h\"\n+#include \"strmap.h\"\n \n static char const * const shortlog_usage[] = {\n \tN_(\"git shortlog [<options>] [<revision-range>] [[--] <path>...]\"),\n@@ -169,60 +170,6 @@ static void read_from_stdin(struct shortlog *log)\n \tstrbuf_release(&oneline);\n }\n \n-struct strset_item {\n-\tstruct hashmap_entry ent;\n-\tchar value[FLEX_ARRAY];\n-};\n-\n-struct strset {\n-\tstruct hashmap map;\n-};\n-\n-#define STRSET_INIT { { NULL } }\n-\n-static int strset_item_hashcmp(const void *hash_data,\n-\t\t\t       const struct hashmap_entry *entry,\n-\t\t\t       const struct hashmap_entry *entry_or_key,\n-\t\t\t       const void *keydata)\n-{\n-\tconst struct strset_item *a, *b;\n-\n-\ta = container_of(entry, const struct strset_item, ent);\n-\tif (keydata)\n-\t\treturn strcmp(a->value, keydata);\n-\n-\tb = container_of(entry_or_key, const struct strset_item, ent);\n-\treturn strcmp(a->value, b->value);\n-}\n-\n-/*\n- * Adds \"str\" to the set if it was not already present; returns true if it was\n- * already there.\n- */\n-static int strset_check_and_add(struct strset *ss, const char *str)\n-{\n-\tunsigned int hash = strhash(str);\n-\tstruct strset_item *item;\n-\n-\tif (!ss->map.table)\n-\t\thashmap_init(&ss->map, strset_item_hashcmp, NULL, 0);\n-\n-\tif (hashmap_get_from_hash(&ss->map, hash, str))\n-\t\treturn 1;\n-\n-\tFLEX_ALLOC_STR(item, value, str);\n-\thashmap_entry_init(&item->ent, hash);\n-\thashmap_add(&ss->map, &item->ent);\n-\treturn 0;\n-}\n-\n-static void strset_clear(struct strset *ss)\n-{\n-\tif (!ss->map.table)\n-\t\treturn;\n-\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n-}\n-\n static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t struct strset *dups,\n \t\t\t\t\t struct commit *commit,\n@@ -253,7 +200,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\tif (!parse_ident(log, &ident, value))\n \t\t\tvalue = ident.buf;\n \n-\t\tif (strset_check_and_add(dups, value))\n+\t\tif (!strset_add(dups, value))\n \t\t\tcontinue;\n \t\tinsert_one_record(log, value, oneline);\n \t}\n@@ -291,7 +238,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%aN <%aE>\" : \"%aN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    strset_add(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_COMMITTER) {\n@@ -300,7 +247,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%cN <%cE>\" : \"%cN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    strset_add(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_TRAILER) {\n-- \ngitgitgadget\n"},{"id":"409256","messageId":"d3713d88f2bca9eb97eee987e835fb7b6caeedcb.1604622299.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v5 14/15] Use new HASHMAP_INIT macro to simplify hashmap initialization","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-06T00:24:57Z","receivedAt":"2020-11-06T00:25:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNow that hashamp has lazy initialization and a HASHMAP_INIT macro,\nhashmaps allocated on the stack can be initialized without a call to\nhashmap_init() and in some cases makes the code a bit shorter.  Convert\nsome callsites over to take advantage of this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n attr.c                  | 26 ++++++++------------------\n bloom.c                 |  3 +--\n builtin/difftool.c      |  9 ++++-----\n range-diff.c            |  4 +---\n revision.c              |  9 +--------\n t/helper/test-hashmap.c |  3 +--\n 6 files changed, 16 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a826b2ef1f..4ef85d668b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -52,13 +52,6 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n \tpthread_mutex_unlock(&map->mutex);\n }\n \n-/*\n- * The global dictionary of all interned attributes.  This\n- * is a singleton object which is shared between threads.\n- * Access to this dictionary must be surrounded with a mutex.\n- */\n-static struct attr_hashmap g_attr_hashmap;\n-\n /* The container for objects stored in \"struct attr_hashmap\" */\n struct attr_hash_entry {\n \tstruct hashmap_entry ent;\n@@ -80,11 +73,14 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n \treturn (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n }\n \n-/* Initialize an 'attr_hashmap' object */\n-static void attr_hashmap_init(struct attr_hashmap *map)\n-{\n-\thashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n-}\n+/*\n+ * The global dictionary of all interned attributes.  This\n+ * is a singleton object which is shared between threads.\n+ * Access to this dictionary must be surrounded with a mutex.\n+ */\n+static struct attr_hashmap g_attr_hashmap = {\n+\tHASHMAP_INIT(attr_hash_entry_cmp, NULL)\n+};\n \n /*\n  * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n@@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n \tstruct attr_hash_entry k;\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \thashmap_entry_init(&k.ent, memhash(key, keylen));\n \tk.key = key;\n \tk.keylen = keylen;\n@@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n {\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \te = xmalloc(sizeof(struct attr_hash_entry));\n \thashmap_entry_init(&e->ent, memhash(key, keylen));\n \te->key = key;\ndiff --git a/bloom.c b/bloom.c\nindex 719c313a1c..b176f28f53 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -229,10 +229,9 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \tdiffcore_std(&diffopt);\n \n \tif (diff_queued_diff.nr <= settings->max_changed_paths) {\n-\t\tstruct hashmap pathmap;\n+\t\tstruct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL);\n \t\tstruct pathmap_hash_entry *e;\n \t\tstruct hashmap_iter iter;\n-\t\thashmap_init(&pathmap, pathmap_cmp, NULL, 0);\n \n \t\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n \t\t\tconst char *path = diff_queued_diff.queue[i]->two->path;\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..6e18e623fd 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \tconst char *workdir, *tmp;\n \tint ret = 0, i;\n \tFILE *fp;\n-\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,\n+\t\t\t\t\t\t\tNULL);\n+\tstruct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);\n+\tstruct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);\n \tstruct hashmap_iter iter;\n \tstruct pair_entry *entry;\n \tstruct index_state wtindex;\n@@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \trdir_len = rdir.len;\n \twtdir_len = wtdir.len;\n \n-\thashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);\n-\thashmap_init(&submodules, pair_cmp, NULL, 0);\n-\thashmap_init(&symlinks2, pair_cmp, NULL, 0);\n-\n \tchild.no_stdin = 1;\n \tchild.git_cmd = 1;\n \tchild.use_shell = 0;\ndiff --git a/range-diff.c b/range-diff.c\nindex befeecae44..b9950f10c8 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n \n static void find_exact_matches(struct string_list *a, struct string_list *b)\n {\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);\n \tint i;\n \n-\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n-\n \t/* First, add the patches of a to a hash map */\n \tfor (i = 0; i < a->nr; i++) {\n \t\tstruct patch_util *util = a->items[i].util;\ndiff --git a/revision.c b/revision.c\nindex f27649d45d..c6e169e3eb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -124,11 +124,6 @@ static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,\n \treturn strcmp(e1->path, e2->path);\n }\n \n-static void paths_and_oids_init(struct hashmap *map)\n-{\n-\thashmap_init(map, path_and_oids_cmp, NULL, 0);\n-}\n-\n static void paths_and_oids_clear(struct hashmap *map)\n {\n \tstruct hashmap_iter iter;\n@@ -213,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \t\t\t\t     struct oidset *trees)\n {\n \tunsigned has_interesting = 0, has_uninteresting = 0;\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL);\n \tstruct hashmap_iter map_iter;\n \tstruct path_and_oids_entry *entry;\n \tstruct object_id *oid;\n@@ -237,8 +232,6 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \tif (!has_uninteresting || !has_interesting)\n \t\treturn;\n \n-\tpaths_and_oids_init(&map);\n-\n \toidset_iter_init(trees, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n \t\tstruct tree *tree = lookup_tree(r, oid);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 2475663b49..36ff07bd4b 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -151,12 +151,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n int cmd__hashmap(int argc, const char **argv)\n {\n \tstruct strbuf line = STRBUF_INIT;\n-\tstruct hashmap map;\n \tint icase;\n+\tstruct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);\n \n \t/* init hash map */\n \ticase = argc > 1 && !strcmp(\"ignorecase\", argv[1]);\n-\thashmap_init(&map, test_entry_cmp, &icase, 0);\n \n \t/* process commands from stdin */\n \twhile (strbuf_getline(&line, stdin) != EOF) {\n-- \ngitgitgadget\n\n"},{"id":"409264","messageId":"xmqq5z6jgs6f.fsf@gitster.c.googlers.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/15] Add struct strmap and associated utility functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-06T02:00:56Z","receivedAt":"2020-11-06T02:01:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Changes since v4:\n> ...\n>  * Add a patch which updates shortlog to use the new strset API.\n\nThis makes my life so much simpler ;-)\n\nWould the implementation be very different from Peff's that you can\ntake the authorship?  Thanks.\n"},{"id":"409266","messageId":"CABPp-BHoaqGRbfM=5SZ4+s1hQa9eRQsi4kMAb3cZFrP+dqHM0A@mail.gmail.com","threadId":"54087","inReplyTo":"xmqq5z6jgs6f.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 00/15] Add struct strmap and associated utility functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-06T02:42:38Z","receivedAt":"2020-11-06T02:42:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Nov 5, 2020 at 6:00 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > Changes since v4:\n> > ...\n> >  * Add a patch which updates shortlog to use the new strset API.\n>\n> This makes my life so much simpler ;-)\n>\n> Would the implementation be very different from Peff's that you can\n> take the authorship?  Thanks.\n\nYes; I didn't use his patch, I simply implemented what was needed from\nscratch.  I'm not attached to being author of this though; the changes\nwere trivial.  Feel free to change as you see fit.\n\n\nIf more detail is needed...\n\nThere's only two things in my patch: (1) deleting a bunch of code, (2)\nsearch and replace strset_check_and_add() with !strset_add().\n\nHis patch has three things: (1) deleting a bunch of code, (2)\nintroducing strset_dup() [which may have been a copy of my\nimplementation of strset_check_and_add() from an earlier round of the\nseries; the code is identical to my implementation, but it's only a\nfew lines so he might have just reimplemented it identically], (3)\nsearch and replace strset_check_and_add() with strset_dup().\n\nIf I were to modify his patch into mine (which I didn't do), it'd\nrequire two things: deleting the strdup() definition and still doing a\nsearch and replace.  In other words, it'd be approximately equivalent\nwork to just doing the patch from scratch.\n\nFurther, I wrote a patch that was nearly the same as my current\nsubmission a few days ago, but it used my old strset_check_and_add().\nIt triggered some weird windows bug that I think was an infrastructure\nflake, but I was worried at the time that it'd require familiarity\nwith shortlog and its tests to address.  Since I didn't think my\nseries really depended on that change (shortlog could change to take\nadvantage of the new strset later), I just dropped it.  Then after\nfurther reviews, the series changed a bit more, and Peff at the end\nadded a patch to reintroduce strset_check_and_add() with a different\nname and use it, then you suggested to modify strset_add() so it can\njust be used directly.\n\nSo, at the end, taking my existing patch that pre-dated his submission\nand tweaking it was the easiest route for me.  I didn't actually look\nat his latest patch until after you asked if it was okay for me to\ntake the authorship.  I see it as two similar from-scratch\nimplementations that were nearly trivial in either event.\n"},{"id":"409267","messageId":"20201106024835.GA153035@coredump.intra.peff.net","threadId":"54087","inReplyTo":"CABPp-BHoaqGRbfM=5SZ4+s1hQa9eRQsi4kMAb3cZFrP+dqHM0A@mail.gmail.com","subject":"Re: [PATCH v5 00/15] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-06T02:48:35Z","receivedAt":"2020-11-06T02:48:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 05, 2020 at 06:42:38PM -0800, Elijah Newren wrote:\n\n> > > Changes since v4:\n> > > ...\n> > >  * Add a patch which updates shortlog to use the new strset API.\n> >\n> > This makes my life so much simpler ;-)\n> >\n> > Would the implementation be very different from Peff's that you can\n> > take the authorship?  Thanks.\n> \n> Yes; I didn't use his patch, I simply implemented what was needed from\n> scratch.  I'm not attached to being author of this though; the changes\n> were trivial.  Feel free to change as you see fit.\n\nYeah, I am fine either way with the authorship here. The patch is\ntrivial, and I was pretty sure you had written the same or similar\nalready. My main point in posting it was to push it over the finish line\nso we didn't forget. ;)\n\n-Peff\n"},{"id":"409277","messageId":"xmqq1rh6gzmm.fsf@gitster.c.googlers.com","threadId":"54087","inReplyTo":"20201106024835.GA153035@coredump.intra.peff.net","subject":"Re: [PATCH v5 00/15] Add struct strmap and associated utility functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-06T17:32:17Z","receivedAt":"2020-11-06T17:32:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Thu, Nov 05, 2020 at 06:42:38PM -0800, Elijah Newren wrote:\n>\n>> > > Changes since v4:\n>> > > ...\n>> > >  * Add a patch which updates shortlog to use the new strset API.\n>> >\n>> > This makes my life so much simpler ;-)\n>> >\n>> > Would the implementation be very different from Peff's that you can\n>> > take the authorship?  Thanks.\n>> \n>> Yes; I didn't use his patch, I simply implemented what was needed from\n>> scratch.  I'm not attached to being author of this though; the changes\n>> were trivial.  Feel free to change as you see fit.\n>\n> Yeah, I am fine either way with the authorship here. The patch is\n> trivial, and I was pretty sure you had written the same or similar\n> already. My main point in posting it was to push it over the finish line\n> so we didn't forget. ;)\n\nYes.  I just was double-checking in case Elijah forgot, as I\ncouldn't tell if it was deliberate.  The way you two agree on\nis the best for me, too.\n\nThanks.\n"},{"id":"409656","messageId":"fbaa60c4-ee6b-02b7-68b5-e5873f8ec713@gmail.com","threadId":"54087","inReplyTo":"34f542d9dd846da5fd81274966ee2ebe0660dcef.1604622299.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-11-11T17:33:47Z","receivedAt":"2020-11-11T17:33:53Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Elijah\n\nOn 06/11/2020 00:24, Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> For heavy users of strmaps, allowing the keys and entries to be\n> allocated from a memory pool can provide significant overhead savings.\n> Add an option to strmap_init_with_options() to specify a memory pool.\n> [...]\n> diff --git a/strmap.h b/strmap.h\n> index c8c4d7c932..dda928703d 100644\n> --- a/strmap.h\n> +++ b/strmap.h\n> @@ -3,8 +3,10 @@\n>   \n>   #include \"hashmap.h\"\n>   \n> +struct mempool;\n\nI think this is a typo - I assume you wanted to declare `struct \nmem_pool` but it's not strictly necessary as you're only adding a \npointer to the struct below.\n\nBest Wishes\n\nPhillip\n\n>   struct strmap {\n>   \tstruct hashmap map;\n> +\tstruct mem_pool *pool;\n>   \tunsigned int strdup_strings:1;\n>   };\n>   \n> @@ -37,9 +39,10 @@ void strmap_init(struct strmap *map);\n>   \n>   /*\n>    * Same as strmap_init, but for those who want to control the memory management\n> - * carefully instead of using the default of strdup_strings=1.\n> + * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n>    */\n>   void strmap_init_with_options(struct strmap *map,\n> +\t\t\t      struct mem_pool *pool,\n>   \t\t\t      int strdup_strings);\n>   \n>   /*\n> @@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n>   \n>   static inline void strintmap_init_with_options(struct strintmap *map,\n>   \t\t\t\t\t       int default_value,\n> +\t\t\t\t\t       struct mem_pool *pool,\n>   \t\t\t\t\t       int strdup_strings)\n>   {\n> -\tstrmap_init_with_options(&map->map, strdup_strings);\n> +\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n>   \tmap->default_value = default_value;\n>   }\n>   \n> @@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set)\n>   }\n>   \n>   static inline void strset_init_with_options(struct strset *set,\n> +\t\t\t\t\t    struct mem_pool *pool,\n>   \t\t\t\t\t    int strdup_strings)\n>   {\n> -\tstrmap_init_with_options(&set->map, strdup_strings);\n> +\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n>   }\n>   \n>   static inline void strset_clear(struct strset *set)\n> \n"},{"id":"409663","messageId":"CABPp-BGTS+qe1V7=ie7PjbRmKwwF=c54uOm43R1dnv7MgBJfyA@mail.gmail.com","threadId":"54087","inReplyTo":"fbaa60c4-ee6b-02b7-68b5-e5873f8ec713@gmail.com","subject":"Re: [PATCH v5 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2020-11-11T18:49:46Z","receivedAt":"2020-11-11T18:50:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Nov 11, 2020 at 9:33 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> Hi Elijah\n>\n> On 06/11/2020 00:24, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > For heavy users of strmaps, allowing the keys and entries to be\n> > allocated from a memory pool can provide significant overhead savings.\n> > Add an option to strmap_init_with_options() to specify a memory pool.\n> > [...]\n> > diff --git a/strmap.h b/strmap.h\n> > index c8c4d7c932..dda928703d 100644\n> > --- a/strmap.h\n> > +++ b/strmap.h\n> > @@ -3,8 +3,10 @@\n> >\n> >   #include \"hashmap.h\"\n> >\n> > +struct mempool;\n>\n> I think this is a typo - I assume you wanted to declare `struct\n> mem_pool` but it's not strictly necessary as you're only adding a\n> pointer to the struct below.\n>\n> Best Wishes\n>\n> Phillip\n\nIndeed, thanks.\n\n>\n> >   struct strmap {\n> >       struct hashmap map;\n> > +     struct mem_pool *pool;\n> >       unsigned int strdup_strings:1;\n> >   };\n> >\n> > @@ -37,9 +39,10 @@ void strmap_init(struct strmap *map);\n> >\n> >   /*\n> >    * Same as strmap_init, but for those who want to control the memory management\n> > - * carefully instead of using the default of strdup_strings=1.\n> > + * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n> >    */\n> >   void strmap_init_with_options(struct strmap *map,\n> > +                           struct mem_pool *pool,\n> >                             int strdup_strings);\n> >\n> >   /*\n> > @@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n> >\n> >   static inline void strintmap_init_with_options(struct strintmap *map,\n> >                                              int default_value,\n> > +                                            struct mem_pool *pool,\n> >                                              int strdup_strings)\n> >   {\n> > -     strmap_init_with_options(&map->map, strdup_strings);\n> > +     strmap_init_with_options(&map->map, pool, strdup_strings);\n> >       map->default_value = default_value;\n> >   }\n> >\n> > @@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set)\n> >   }\n> >\n> >   static inline void strset_init_with_options(struct strset *set,\n> > +                                         struct mem_pool *pool,\n> >                                           int strdup_strings)\n> >   {\n> > -     strmap_init_with_options(&set->map, strdup_strings);\n> > +     strmap_init_with_options(&set->map, pool, strdup_strings);\n> >   }\n> >\n> >   static inline void strset_clear(struct strset *set)\n> >\n"},{"id":"409665","messageId":"20201111190121.GE9902@coredump.intra.peff.net","threadId":"54087","inReplyTo":"fbaa60c4-ee6b-02b7-68b5-e5873f8ec713@gmail.com","subject":"Re: [PATCH v5 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-11T19:01:21Z","receivedAt":"2020-11-11T19:01:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 11, 2020 at 05:33:47PM +0000, Phillip Wood wrote:\n\n> On 06/11/2020 00:24, Elijah Newren via GitGitGadget wrote:\n> > From: Elijah Newren <newren@gmail.com>\n> > \n> > For heavy users of strmaps, allowing the keys and entries to be\n> > allocated from a memory pool can provide significant overhead savings.\n> > Add an option to strmap_init_with_options() to specify a memory pool.\n> > [...]\n> > diff --git a/strmap.h b/strmap.h\n> > index c8c4d7c932..dda928703d 100644\n> > --- a/strmap.h\n> > +++ b/strmap.h\n> > @@ -3,8 +3,10 @@\n> >   #include \"hashmap.h\"\n> > +struct mempool;\n> \n> I think this is a typo - I assume you wanted to declare `struct mem_pool`\n> but it's not strictly necessary as you're only adding a pointer to the\n> struct below.\n\nGood catch.\n\nEven if we're only using a pointer to it, we still need a valid forward\ndeclaration (using the pointer only saves us from needing the full\ndefinition). Or so I thought.\n\nIt looks like the compiler will treat the use inside the struct:\n\n> >   struct strmap {\n> >   \tstruct hashmap map;\n> > +\tstruct mem_pool *pool;\n> >   \tunsigned int strdup_strings:1;\n> >   };\n\nas an implicit forward declaration, but not the ones inside the function\ndeclarations:\n\n> >   void strmap_init_with_options(struct strmap *map,\n> > +\t\t\t      struct mem_pool *pool,\n> >   \t\t\t      int strdup_strings);\n\nIf you replace the pointer in the struct definition with \"struct foo\",\nthen \"make hdr-check\" will complain about mem_pool in the function. And\nlikewise if you replace the ones in the function with \"struct foo\", then\nwe'll complain about those.\n\nI'm not sure whether this is a seldom-seen corner of the C standard, or\na compiler-specific thing (though both clang and gcc seem to allow it).\nAt any rate, I think it is worth fixing the typo'd forward declaration\n(rather than deleting it) to make the intention clear.\n\n-Peff\n"},{"id":"409696","messageId":"591161fd781b7666ddaa45694eb20610cc359741.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 02/15] hashmap: adjust spacing to fix argument alignment","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:08Z","receivedAt":"2020-11-11T20:02:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNo actual code changes; just whitespace adjustments.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 17 +++++++++--------\n hashmap.h | 22 +++++++++++-----------\n 2 files changed, 20 insertions(+), 19 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex 09813e1a46..e44d8a3e85 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -92,8 +92,9 @@ static void alloc_table(struct hashmap *map, unsigned int size)\n }\n \n static inline int entry_equals(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *e1, const struct hashmap_entry *e2,\n-\t\tconst void *keydata)\n+\t\t\t       const struct hashmap_entry *e1,\n+\t\t\t       const struct hashmap_entry *e2,\n+\t\t\t       const void *keydata)\n {\n \treturn (e1 == e2) ||\n \t       (e1->hash == e2->hash &&\n@@ -101,7 +102,7 @@ static inline int entry_equals(const struct hashmap *map,\n }\n \n static inline unsigned int bucket(const struct hashmap *map,\n-\t\tconst struct hashmap_entry *key)\n+\t\t\t\t  const struct hashmap_entry *key)\n {\n \treturn key->hash & (map->tablesize - 1);\n }\n@@ -148,7 +149,7 @@ static int always_equal(const void *unused_cmp_data,\n }\n \n void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n-\t\tconst void *cmpfn_data, size_t initial_size)\n+\t\t  const void *cmpfn_data, size_t initial_size)\n {\n \tunsigned int size = HASHMAP_INITIAL_SIZE;\n \n@@ -199,7 +200,7 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry)\n+\t\t\t\t       const struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *e = entry->next;\n \tfor (; e; e = e->next)\n@@ -225,8 +226,8 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n }\n \n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata)\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n \tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n@@ -249,7 +250,7 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n }\n \n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry)\n+\t\t\t\t  struct hashmap_entry *entry)\n {\n \tstruct hashmap_entry *old = hashmap_remove(map, entry, NULL);\n \thashmap_add(map, entry);\ndiff --git a/hashmap.h b/hashmap.h\nindex 2994dc7a9c..904f61d6e1 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -228,9 +228,9 @@ struct hashmap {\n  * prevent expensive resizing. If 0, the table is dynamically resized.\n  */\n void hashmap_init(struct hashmap *map,\n-\t\t\t hashmap_cmp_fn equals_function,\n-\t\t\t const void *equals_function_data,\n-\t\t\t size_t initial_size);\n+\t\t  hashmap_cmp_fn equals_function,\n+\t\t  const void *equals_function_data,\n+\t\t  size_t initial_size);\n \n /* internal function for freeing hashmap */\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n@@ -288,7 +288,7 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  * and if it is on stack, you can just let it go out of scope).\n  */\n static inline void hashmap_entry_init(struct hashmap_entry *e,\n-\t\t\t\t\tunsigned int hash)\n+\t\t\t\t      unsigned int hash)\n {\n \te->hash = hash;\n \te->next = NULL;\n@@ -330,8 +330,8 @@ static inline unsigned int hashmap_get_size(struct hashmap *map)\n  * to `hashmap_cmp_fn` to decide whether the entry matches the key.\n  */\n struct hashmap_entry *hashmap_get(const struct hashmap *map,\n-\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\tconst void *keydata);\n+\t\t\t\t  const struct hashmap_entry *key,\n+\t\t\t\t  const void *keydata);\n \n /*\n  * Returns the hashmap entry for the specified hash code and key data,\n@@ -364,7 +364,7 @@ static inline struct hashmap_entry *hashmap_get_from_hash(\n  * call to `hashmap_get` or `hashmap_get_next`.\n  */\n struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n-\t\t\tconst struct hashmap_entry *entry);\n+\t\t\t\t       const struct hashmap_entry *entry);\n \n /*\n  * Adds a hashmap entry. This allows to add duplicate entries (i.e.\n@@ -384,7 +384,7 @@ void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);\n  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).\n  */\n struct hashmap_entry *hashmap_put(struct hashmap *map,\n-\t\t\t\tstruct hashmap_entry *entry);\n+\t\t\t\t  struct hashmap_entry *entry);\n \n /*\n  * Adds or replaces a hashmap entry contained within @keyvar,\n@@ -406,8 +406,8 @@ struct hashmap_entry *hashmap_put(struct hashmap *map,\n  * Argument explanation is the same as in `hashmap_get`.\n  */\n struct hashmap_entry *hashmap_remove(struct hashmap *map,\n-\t\t\t\t\tconst struct hashmap_entry *key,\n-\t\t\t\t\tconst void *keydata);\n+\t\t\t\t     const struct hashmap_entry *key,\n+\t\t\t\t     const void *keydata);\n \n /*\n  * Removes a hashmap entry contained within @keyvar,\n@@ -449,7 +449,7 @@ struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);\n \n /* Initializes the iterator and returns the first entry, if any. */\n static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,\n-\t\tstruct hashmap_iter *iter)\n+\t\t\t\t\t\t       struct hashmap_iter *iter)\n {\n \thashmap_iter_init(map, iter);\n \treturn hashmap_iter_next(iter);\n-- \ngitgitgadget\n\n"},{"id":"409697","messageId":"af6b6fcb460be900d3fffeb743a42f3f87ce6b7f.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 01/15] hashmap: add usage documentation explaining hashmap_free[_entries]()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:07Z","receivedAt":"2020-11-11T20:02:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe existence of hashmap_free() and hashmap_free_entries() confused me,\nand the docs weren't clear enough.  We are dealing with a map table,\nentries in that table, and possibly also things each of those entries\npoint to.  I had to consult other source code examples and the\nimplementation.  Add a brief note to clarify the differences.  This will\nbecome even more important once we introduce a new\nhashmap_partial_clear() function which will add the question of whether\nthe table itself has been freed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.h | 31 +++++++++++++++++++++++++++++--\n 1 file changed, 29 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.h b/hashmap.h\nindex b011b394fe..2994dc7a9c 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -236,13 +236,40 @@ void hashmap_init(struct hashmap *map,\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n- * Frees a hashmap structure and allocated memory, leaves entries undisturbed\n+ * Frees a hashmap structure and allocated memory for the table, but does not\n+ * free the entries nor anything they point to.\n+ *\n+ * Usage note:\n+ *\n+ * Many callers will need to iterate over all entries and free the data each\n+ * entry points to; in such a case, they can free the entry itself while at it.\n+ * Thus, you might see:\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *      free(e);\n+ *    }\n+ *    hashmap_free(map);\n+ *\n+ * instead of\n+ *\n+ *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n+ *      free(e->somefield);\n+ *    }\n+ *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *\n+ * to avoid the implicit extra loop over the entries.  However, if there are\n+ * no special fields in your entry that need to be freed beyond the entry\n+ * itself, it is probably simpler to avoid the explicit loop and just call\n+ * hashmap_free_entries().\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map\n+ * where @member is the hashmap_entry struct used to associate with @map.\n+ *\n+ * See usage note above hashmap_free().\n  */\n #define hashmap_free_entries(map, type, member) \\\n \thashmap_free_(map, offsetof(type, member));\n-- \ngitgitgadget\n\n"},{"id":"409698","messageId":"f2718d036dc5d08c2ed416a5ef98c5730bc38cfc.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 03/15] hashmap: allow re-use after hashmap_free()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:09Z","receivedAt":"2020-11-11T20:02:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nPreviously, once map->table had been freed, any calls to hashmap_put(),\nhashmap_get(), or hashmap_remove() would cause a NULL pointer\ndereference (since hashmap_free_() also zeros the memory; without that\nzeroing, calling these functions would cause a use-after-free problem).\n\nModify these functions to check for a NULL table and automatically\nallocate as needed.\n\nAlso add a HASHMAP_INIT(fn, data) macro for initializing hashmaps on the\nstack without calling hashmap_init().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 16 ++++++++++++++--\n hashmap.h |  3 +++\n 2 files changed, 17 insertions(+), 2 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex e44d8a3e85..bb7c9979b8 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -114,6 +114,7 @@ int hashmap_bucket(const struct hashmap *map, unsigned int hash)\n \n static void rehash(struct hashmap *map, unsigned int newsize)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tunsigned int i, oldsize = map->tablesize;\n \tstruct hashmap_entry **oldtable = map->table;\n \n@@ -134,6 +135,7 @@ static void rehash(struct hashmap *map, unsigned int newsize)\n static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,\n \t\tconst struct hashmap_entry *key, const void *keydata)\n {\n+\t/* map->table MUST NOT be NULL when this function is called */\n \tstruct hashmap_entry **e = &map->table[bucket(map, key)];\n \twhile (*e && !entry_equals(map, *e, key, keydata))\n \t\te = &(*e)->next;\n@@ -196,6 +198,8 @@ struct hashmap_entry *hashmap_get(const struct hashmap *map,\n \t\t\t\tconst struct hashmap_entry *key,\n \t\t\t\tconst void *keydata)\n {\n+\tif (!map->table)\n+\t\treturn NULL;\n \treturn *find_entry_ptr(map, key, keydata);\n }\n \n@@ -211,8 +215,12 @@ struct hashmap_entry *hashmap_get_next(const struct hashmap *map,\n \n void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)\n {\n-\tunsigned int b = bucket(map, entry);\n+\tunsigned int b;\n+\n+\tif (!map->table)\n+\t\talloc_table(map, HASHMAP_INITIAL_SIZE);\n \n+\tb = bucket(map, entry);\n \t/* add entry */\n \tentry->next = map->table[b];\n \tmap->table[b] = entry;\n@@ -230,7 +238,11 @@ struct hashmap_entry *hashmap_remove(struct hashmap *map,\n \t\t\t\t     const void *keydata)\n {\n \tstruct hashmap_entry *old;\n-\tstruct hashmap_entry **e = find_entry_ptr(map, key, keydata);\n+\tstruct hashmap_entry **e;\n+\n+\tif (!map->table)\n+\t\treturn NULL;\n+\te = find_entry_ptr(map, key, keydata);\n \tif (!*e)\n \t\treturn NULL;\n \ndiff --git a/hashmap.h b/hashmap.h\nindex 904f61d6e1..3b0f2bcade 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -210,6 +210,9 @@ struct hashmap {\n \n /* hashmap functions */\n \n+#define HASHMAP_INIT(fn, data) { .cmpfn = fn, .cmpfn_data = data, \\\n+\t\t\t\t .do_count_items = 1 }\n+\n /*\n  * Initializes a hashmap structure.\n  *\n-- \ngitgitgadget\n\n"},{"id":"409699","messageId":"448d3b219ffebbc0daa4ef033d78fd45693c5ccd.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 06/15] strmap: new utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:12Z","receivedAt":"2020-11-11T20:02:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAdd strmap as a new struct and associated utility functions,\nspecifically for hashmaps that map strings to some value.  The API is\ntaken directly from Peff's proposal at\nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nNote that similar string-list, I have a strdup_strings setting.\nHowever, unlike string-list, strmap_init() does not take a parameter for\nthis setting and instead automatically sets it to 1; callers who want to\ncontrol this detail need to instead call strmap_init_with_options().\n(Future patches will add additional parameters to\nstrmap_init_with_options()).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Makefile |  1 +\n strmap.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n strmap.h | 65 +++++++++++++++++++++++++++++++++++++\n 3 files changed, 165 insertions(+)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\ndiff --git a/Makefile b/Makefile\nindex 95571ee3fc..777a34c01c 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1000,6 +1000,7 @@ LIB_OBJS += stable-qsort.o\n LIB_OBJS += strbuf.o\n LIB_OBJS += streaming.o\n LIB_OBJS += string-list.o\n+LIB_OBJS += strmap.o\n LIB_OBJS += strvec.o\n LIB_OBJS += sub-process.o\n LIB_OBJS += submodule-config.o\ndiff --git a/strmap.c b/strmap.c\nnew file mode 100644\nindex 0000000000..53f284eb20\n--- /dev/null\n+++ b/strmap.c\n@@ -0,0 +1,99 @@\n+#include \"git-compat-util.h\"\n+#include \"strmap.h\"\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata)\n+{\n+\tconst struct strmap_entry *e1, *e2;\n+\n+\te1 = container_of(entry1, const struct strmap_entry, ent);\n+\te2 = container_of(entry2, const struct strmap_entry, ent);\n+\treturn strcmp(e1->key, e2->key);\n+}\n+\n+static struct strmap_entry *find_strmap_entry(struct strmap *map,\n+\t\t\t\t\t      const char *str)\n+{\n+\tstruct strmap_entry entry;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\treturn hashmap_get_entry(&map->map, &entry, ent, NULL);\n+}\n+\n+void strmap_init(struct strmap *map)\n+{\n+\tstrmap_init_with_options(map, 1);\n+}\n+\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings)\n+{\n+\thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->strdup_strings = strdup_strings;\n+}\n+\n+static void strmap_free_entries_(struct strmap *map, int free_values)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct strmap_entry *e;\n+\n+\tif (!map)\n+\t\treturn;\n+\n+\t/*\n+\t * We need to iterate over the hashmap entries and free\n+\t * e->key and e->value ourselves; hashmap has no API to\n+\t * take care of that for us.  Since we're already iterating over\n+\t * the hashmap, though, might as well free e too and avoid the need\n+\t * to make some call into the hashmap API to do that.\n+\t */\n+\thashmap_for_each_entry(&map->map, &iter, e, ent) {\n+\t\tif (free_values)\n+\t\t\tfree(e->value);\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)e->key);\n+\t\tfree(e);\n+\t}\n+}\n+\n+void strmap_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_clear(&map->map);\n+}\n+\n+void *strmap_put(struct strmap *map, const char *str, void *data)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\tvoid *old = NULL;\n+\n+\tif (entry) {\n+\t\told = entry->value;\n+\t\tentry->value = data;\n+\t} else {\n+\t\tconst char *key = str;\n+\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\t\tif (map->strdup_strings)\n+\t\t\tkey = xstrdup(str);\n+\t\tentry->key = key;\n+\t\tentry->value = data;\n+\t\thashmap_add(&map->map, &entry->ent);\n+\t}\n+\treturn old;\n+}\n+\n+void *strmap_get(struct strmap *map, const char *str)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(map, str);\n+\treturn entry ? entry->value : NULL;\n+}\n+\n+int strmap_contains(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str) != NULL;\n+}\ndiff --git a/strmap.h b/strmap.h\nnew file mode 100644\nindex 0000000000..96888c23ad\n--- /dev/null\n+++ b/strmap.h\n@@ -0,0 +1,65 @@\n+#ifndef STRMAP_H\n+#define STRMAP_H\n+\n+#include \"hashmap.h\"\n+\n+struct strmap {\n+\tstruct hashmap map;\n+\tunsigned int strdup_strings:1;\n+};\n+\n+struct strmap_entry {\n+\tstruct hashmap_entry ent;\n+\tconst char *key;\n+\tvoid *value;\n+};\n+\n+int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n+\t\t     const struct hashmap_entry *entry1,\n+\t\t     const struct hashmap_entry *entry2,\n+\t\t     const void *keydata);\n+\n+#define STRMAP_INIT { \\\n+\t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n+\t\t\t.strdup_strings = 1,                          \\\n+\t\t    }\n+\n+/*\n+ * Initialize the members of the strmap.  Any keys added to the strmap will\n+ * be strdup'ed with their memory managed by the strmap.\n+ */\n+void strmap_init(struct strmap *map);\n+\n+/*\n+ * Same as strmap_init, but for those who want to control the memory management\n+ * carefully instead of using the default of strdup_strings=1.\n+ */\n+void strmap_init_with_options(struct strmap *map,\n+\t\t\t      int strdup_strings);\n+\n+/*\n+ * Remove all entries from the map, releasing any allocated resources.\n+ */\n+void strmap_clear(struct strmap *map, int free_values);\n+\n+/*\n+ * Insert \"str\" into the map, pointing to \"data\".\n+ *\n+ * If an entry for \"str\" already exists, its data pointer is overwritten, and\n+ * the original data pointer returned. Otherwise, returns NULL.\n+ */\n+void *strmap_put(struct strmap *map, const char *str, void *data);\n+\n+/*\n+ * Return the data pointer mapped by \"str\", or NULL if the entry does not\n+ * exist.\n+ */\n+void *strmap_get(struct strmap *map, const char *str);\n+\n+/*\n+ * Return non-zero iff \"str\" is present in the map. This differs from\n+ * strmap_get() in that it can distinguish entries with a NULL data pointer.\n+ */\n+int strmap_contains(struct strmap *map, const char *str);\n+\n+#endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409700","messageId":"861e8d65ae8065595d9d4ccff5f70155fec408c9.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 05/15] hashmap: provide deallocation function names","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:11Z","receivedAt":"2020-11-11T20:02:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nhashmap_free(), hashmap_free_entries(), and hashmap_free_() have existed\nfor a while, but aren't necessarily the clearest names, especially with\nhashmap_partial_clear() being added to the mix and lazy-initialization\nnow being supported.  Peff suggested we adopt the following names[1]:\n\n  - hashmap_clear() - remove all entries and de-allocate any\n    hashmap-specific data, but be ready for reuse\n\n  - hashmap_clear_and_free() - ditto, but free the entries themselves\n\n  - hashmap_partial_clear() - remove all entries but don't deallocate\n    table\n\n  - hashmap_partial_clear_and_free() - ditto, but free the entries\n\nThis patch provides the new names and converts all existing callers over\nto the new naming scheme.\n\n[1] https://lore.kernel.org/git/20201030125059.GA3277724@coredump.intra.peff.net/\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n add-interactive.c       |  2 +-\n blame.c                 |  2 +-\n bloom.c                 |  2 +-\n builtin/fetch.c         |  6 +++---\n builtin/shortlog.c      |  2 +-\n config.c                |  2 +-\n diff.c                  |  4 ++--\n diffcore-rename.c       |  2 +-\n dir.c                   |  8 ++++----\n hashmap.c               |  6 +++---\n hashmap.h               | 44 +++++++++++++++++++++++++----------------\n merge-recursive.c       |  6 +++---\n name-hash.c             |  4 ++--\n object.c                |  2 +-\n oidmap.c                |  2 +-\n patch-ids.c             |  2 +-\n range-diff.c            |  2 +-\n ref-filter.c            |  2 +-\n revision.c              |  2 +-\n sequencer.c             |  4 ++--\n submodule-config.c      |  4 ++--\n t/helper/test-hashmap.c |  6 +++---\n 22 files changed, 63 insertions(+), 53 deletions(-)\n\ndiff --git a/add-interactive.c b/add-interactive.c\nindex 555c4abf32..a14c0feaa2 100644\n--- a/add-interactive.c\n+++ b/add-interactive.c\n@@ -557,7 +557,7 @@ static int get_modified_files(struct repository *r,\n \t\tif (ps)\n \t\t\tclear_pathspec(&rev.prune_data);\n \t}\n-\thashmap_free_entries(&s.file_map, struct pathname_entry, ent);\n+\thashmap_clear_and_free(&s.file_map, struct pathname_entry, ent);\n \tif (unmerged_count)\n \t\t*unmerged_count = s.unmerged_count;\n \tif (binary_count)\ndiff --git a/blame.c b/blame.c\nindex 686845b2b4..229beb6452 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -435,7 +435,7 @@ static void get_fingerprint(struct fingerprint *result,\n \n static void free_fingerprint(struct fingerprint *f)\n {\n-\thashmap_free(&f->map);\n+\thashmap_clear(&f->map);\n \tfree(f->entries);\n }\n \ndiff --git a/bloom.c b/bloom.c\nindex 68c73200a5..719c313a1c 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -287,7 +287,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \t\t}\n \n \tcleanup:\n-\t\thashmap_free_entries(&pathmap, struct pathmap_hash_entry, entry);\n+\t\thashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry);\n \t} else {\n \t\tfor (i = 0; i < diff_queued_diff.nr; i++)\n \t\t\tdiff_free_filepair(diff_queued_diff.queue[i]);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex f9c3c49f14..ecf8537605 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -393,7 +393,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\titem = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);\n \t\tstring_list_insert(&remote_refs_list, ref->name);\n \t}\n-\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \t/*\n \t * We may have a final lightweight tag that needs to be\n@@ -428,7 +428,7 @@ static void find_non_local_tags(const struct ref *refs,\n \t\t**tail = rm;\n \t\t*tail = &rm->next;\n \t}\n-\thashmap_free_entries(&remote_refs, struct refname_hash_entry, ent);\n+\thashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent);\n \tstring_list_clear(&remote_refs_list, 0);\n \toidset_clear(&fetch_oids);\n }\n@@ -573,7 +573,7 @@ static struct ref *get_ref_map(struct remote *remote,\n \t\t}\n \t}\n \tif (existing_refs_populated)\n-\t\thashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);\n+\t\thashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);\n \n \treturn ref_map;\n }\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 0a5c4968f6..83f0a739b4 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -220,7 +220,7 @@ static void strset_clear(struct strset *ss)\n {\n \tif (!ss->map.table)\n \t\treturn;\n-\thashmap_free_entries(&ss->map, struct strset_item, ent);\n+\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n }\n \n static void insert_records_from_trailers(struct shortlog *log,\ndiff --git a/config.c b/config.c\nindex 2bdff4457b..8f324ed3a6 100644\n--- a/config.c\n+++ b/config.c\n@@ -1963,7 +1963,7 @@ void git_configset_clear(struct config_set *cs)\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_free_entries(&cs->config_hash, struct config_set_element, ent);\n+\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n \tcs->hash_initialized = 0;\n \tfree(cs->list.items);\n \tcs->list.nr = 0;\ndiff --git a/diff.c b/diff.c\nindex 2bb2f8f57e..8e0e59f5cf 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6289,9 +6289,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \t\t\tif (o->color_moved == COLOR_MOVED_ZEBRA_DIM)\n \t\t\t\tdim_moved_lines(o);\n \n-\t\t\thashmap_free_entries(&add_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&add_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n-\t\t\thashmap_free_entries(&del_lines, struct moved_entry,\n+\t\t\thashmap_clear_and_free(&del_lines, struct moved_entry,\n \t\t\t\t\t\tent);\n \t\t}\n \ndiff --git a/diffcore-rename.c b/diffcore-rename.c\nindex 99e63e90f8..d367a6d244 100644\n--- a/diffcore-rename.c\n+++ b/diffcore-rename.c\n@@ -407,7 +407,7 @@ static int find_exact_renames(struct diff_options *options)\n \t\trenames += find_identical_files(&file_table, i, options);\n \n \t/* Free the hash data structure and entries */\n-\thashmap_free_entries(&file_table, struct file_similarity, entry);\n+\thashmap_clear_and_free(&file_table, struct file_similarity, entry);\n \n \treturn renames;\n }\ndiff --git a/dir.c b/dir.c\nindex 78387110e6..161dce121e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -817,8 +817,8 @@ static void add_pattern_to_hashsets(struct pattern_list *pl, struct path_pattern\n \n clear_hashmaps:\n \twarning(_(\"disabling cone pattern matching\"));\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n \tpl->use_cone_patterns = 0;\n }\n \n@@ -921,8 +921,8 @@ void clear_pattern_list(struct pattern_list *pl)\n \t\tfree(pl->patterns[i]);\n \tfree(pl->patterns);\n \tfree(pl->filebuf);\n-\thashmap_free_entries(&pl->recursive_hashmap, struct pattern_entry, ent);\n-\thashmap_free_entries(&pl->parent_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->recursive_hashmap, struct pattern_entry, ent);\n+\thashmap_clear_and_free(&pl->parent_hashmap, struct pattern_entry, ent);\n \n \tmemset(pl, 0, sizeof(*pl));\n }\ndiff --git a/hashmap.c b/hashmap.c\nindex 922ed07954..5009471800 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -183,7 +183,7 @@ static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n \twhile ((e = hashmap_iter_next(&iter)))\n \t\t/*\n \t\t * like container_of, but using caller-calculated\n-\t\t * offset (caller being hashmap_free_entries)\n+\t\t * offset (caller being hashmap_clear_and_free)\n \t\t */\n \t\tfree((char *)e - entry_offset);\n }\n@@ -199,11 +199,11 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n \tmap->private_size = 0;\n }\n \n-void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n+void hashmap_clear_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_and_free */\n \t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\ndiff --git a/hashmap.h b/hashmap.h\nindex e9430d582a..7251687d73 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -96,7 +96,7 @@\n  *         }\n  *\n  *         if (!strcmp(\"end\", action)) {\n- *             hashmap_free_entries(&map, struct long2string, ent);\n+ *             hashmap_clear_and_free(&map, struct long2string, ent);\n  *             break;\n  *         }\n  *     }\n@@ -237,7 +237,7 @@ void hashmap_init(struct hashmap *map,\n \n /* internal functions for clearing or freeing hashmap */\n void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n-void hashmap_free_(struct hashmap *map, ssize_t offset);\n+void hashmap_clear_(struct hashmap *map, ssize_t offset);\n \n /*\n  * Frees a hashmap structure and allocated memory for the table, but does not\n@@ -253,40 +253,50 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  *      free(e->somefield);\n  *      free(e);\n  *    }\n- *    hashmap_free(map);\n+ *    hashmap_clear(map);\n  *\n  * instead of\n  *\n  *    hashmap_for_each_entry(map, hashmap_iter, e, hashmap_entry_name) {\n  *      free(e->somefield);\n  *    }\n- *    hashmap_free_entries(map, struct my_entry_struct, hashmap_entry_name);\n+ *    hashmap_clear_and_free(map, struct my_entry_struct, hashmap_entry_name);\n  *\n  * to avoid the implicit extra loop over the entries.  However, if there are\n  * no special fields in your entry that need to be freed beyond the entry\n  * itself, it is probably simpler to avoid the explicit loop and just call\n- * hashmap_free_entries().\n+ * hashmap_clear_and_free().\n  */\n-#define hashmap_free(map) hashmap_free_(map, -1)\n+#define hashmap_clear(map) hashmap_clear_(map, -1)\n \n /*\n- * Basically the same as calling hashmap_free() followed by hashmap_init(),\n- * but doesn't incur the overhead of deallocating and reallocating\n- * map->table; it leaves map->table allocated and the same size but zeroes\n- * it out so it's ready for use again as an empty map.  As with\n- * hashmap_free(), you may need to free the entries yourself before calling\n- * this function.\n+ * Similar to hashmap_clear(), except that the table is no deallocated; it\n+ * is merely zeroed out but left the same size as before.  If the hashmap\n+ * will be reused, this avoids the overhead of deallocating and\n+ * reallocating map->table.  As with hashmap_clear(), you may need to free\n+ * the entries yourself before calling this function.\n  */\n #define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n \n /*\n- * Frees @map and all entries.  @type is the struct type of the entry\n- * where @member is the hashmap_entry struct used to associate with @map.\n+ * Similar to hashmap_clear() but also frees all entries.  @type is the\n+ * struct type of the entry where @member is the hashmap_entry struct used\n+ * to associate with @map.\n  *\n- * See usage note above hashmap_free().\n+ * See usage note above hashmap_clear().\n  */\n-#define hashmap_free_entries(map, type, member) \\\n-\thashmap_free_(map, offsetof(type, member));\n+#define hashmap_clear_and_free(map, type, member) \\\n+\thashmap_clear_(map, offsetof(type, member))\n+\n+/*\n+ * Similar to hashmap_partial_clear() but also frees all entries.  @type is\n+ * the struct type of the entry where @member is the hashmap_entry struct\n+ * used to associate with @map.\n+ *\n+ * See usage note above hashmap_clear().\n+ */\n+#define hashmap_partial_clear_and_free(map, type, member) \\\n+\thashmap_partial_clear_(map, offsetof(type, member))\n \n /* hashmap_entry functions */\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d0214335a7..f736a0f632 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -2651,7 +2651,7 @@ static struct string_list *get_renames(struct merge_options *opt,\n \t\tfree(e->target_file);\n \t\tstring_list_clear(&e->source_files, 0);\n \t}\n-\thashmap_free_entries(&collisions, struct collision_entry, ent);\n+\thashmap_clear_and_free(&collisions, struct collision_entry, ent);\n \treturn renames;\n }\n \n@@ -2870,7 +2870,7 @@ static void initial_cleanup_rename(struct diff_queue_struct *pairs,\n \t\tstrbuf_release(&e->new_dir);\n \t\t/* possible_new_dirs already cleared in get_directory_renames */\n \t}\n-\thashmap_free_entries(dir_renames, struct dir_rename_entry, ent);\n+\thashmap_clear_and_free(dir_renames, struct dir_rename_entry, ent);\n \tfree(dir_renames);\n \n \tfree(pairs->queue);\n@@ -3497,7 +3497,7 @@ static int merge_trees_internal(struct merge_options *opt,\n \t\tstring_list_clear(entries, 1);\n \t\tfree(entries);\n \n-\t\thashmap_free_entries(&opt->priv->current_file_dir_set,\n+\t\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n \t\t\t\t\tstruct path_hashmap_entry, e);\n \n \t\tif (clean < 0) {\ndiff --git a/name-hash.c b/name-hash.c\nindex fb526a3775..5d3c7b12c1 100644\n--- a/name-hash.c\n+++ b/name-hash.c\n@@ -726,6 +726,6 @@ void free_name_hash(struct index_state *istate)\n \t\treturn;\n \tistate->name_hash_initialized = 0;\n \n-\thashmap_free(&istate->name_hash);\n-\thashmap_free_entries(&istate->dir_hash, struct dir_entry, ent);\n+\thashmap_clear(&istate->name_hash);\n+\thashmap_clear_and_free(&istate->dir_hash, struct dir_entry, ent);\n }\ndiff --git a/object.c b/object.c\nindex 3257518656..b8406409d5 100644\n--- a/object.c\n+++ b/object.c\n@@ -532,7 +532,7 @@ void raw_object_store_clear(struct raw_object_store *o)\n \tclose_object_store(o);\n \to->packed_git = NULL;\n \n-\thashmap_free(&o->pack_map);\n+\thashmap_clear(&o->pack_map);\n }\n \n void parsed_object_pool_clear(struct parsed_object_pool *o)\ndiff --git a/oidmap.c b/oidmap.c\nindex 423aa014a3..286a04a53c 100644\n--- a/oidmap.c\n+++ b/oidmap.c\n@@ -27,7 +27,7 @@ void oidmap_free(struct oidmap *map, int free_entries)\n \t\treturn;\n \n \t/* TODO: make oidmap itself not depend on struct layouts */\n-\thashmap_free_(&map->map, free_entries ? 0 : -1);\n+\thashmap_clear_(&map->map, free_entries ? 0 : -1);\n }\n \n void *oidmap_get(const struct oidmap *map, const struct object_id *key)\ndiff --git a/patch-ids.c b/patch-ids.c\nindex 12aa6d494b..21973e4933 100644\n--- a/patch-ids.c\n+++ b/patch-ids.c\n@@ -71,7 +71,7 @@ int init_patch_ids(struct repository *r, struct patch_ids *ids)\n \n int free_patch_ids(struct patch_ids *ids)\n {\n-\thashmap_free_entries(&ids->patches, struct patch_id, ent);\n+\thashmap_clear_and_free(&ids->patches, struct patch_id, ent);\n \treturn 0;\n }\n \ndiff --git a/range-diff.c b/range-diff.c\nindex 24dc435e48..befeecae44 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -266,7 +266,7 @@ static void find_exact_matches(struct string_list *a, struct string_list *b)\n \t\t}\n \t}\n \n-\thashmap_free(&map);\n+\thashmap_clear(&map);\n }\n \n static void diffsize_consume(void *data, char *line, unsigned long len)\ndiff --git a/ref-filter.c b/ref-filter.c\nindex c62f6b4822..5e66b8cd76 100644\n--- a/ref-filter.c\n+++ b/ref-filter.c\n@@ -2222,7 +2222,7 @@ void ref_array_clear(struct ref_array *array)\n \tused_atom_cnt = 0;\n \n \tif (ref_to_worktree_map.worktrees) {\n-\t\thashmap_free_entries(&(ref_to_worktree_map.map),\n+\t\thashmap_clear_and_free(&(ref_to_worktree_map.map),\n \t\t\t\t\tstruct ref_to_worktree_entry, ent);\n \t\tfree_worktrees(ref_to_worktree_map.worktrees);\n \t\tref_to_worktree_map.worktrees = NULL;\ndiff --git a/revision.c b/revision.c\nindex aa62212040..f27649d45d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -139,7 +139,7 @@ static void paths_and_oids_clear(struct hashmap *map)\n \t\tfree(entry->path);\n \t}\n \n-\thashmap_free_entries(map, struct path_and_oids_entry, ent);\n+\thashmap_clear_and_free(map, struct path_and_oids_entry, ent);\n }\n \n static void paths_and_oids_insert(struct hashmap *map,\ndiff --git a/sequencer.c b/sequencer.c\nindex 00acb12496..23a09c3e7a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5058,7 +5058,7 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n-\thashmap_free_entries(&state.labels, struct labels_entry, entry);\n+\thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\n \tstrbuf_release(&state.buf);\n \n \treturn 0;\n@@ -5577,7 +5577,7 @@ int todo_list_rearrange_squash(struct todo_list *todo_list)\n \tfor (i = 0; i < todo_list->nr; i++)\n \t\tfree(subjects[i]);\n \tfree(subjects);\n-\thashmap_free_entries(&subject2item, struct subject2item_entry, entry);\n+\thashmap_clear_and_free(&subject2item, struct subject2item_entry, entry);\n \n \tclear_commit_todo_item(&commit_todo);\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex c569e22aa3..f502505566 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -103,8 +103,8 @@ static void submodule_cache_clear(struct submodule_cache *cache)\n \t\t\t\tent /* member name */)\n \t\tfree_one_config(entry);\n \n-\thashmap_free_entries(&cache->for_path, struct submodule_entry, ent);\n-\thashmap_free_entries(&cache->for_name, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_path, struct submodule_entry, ent);\n+\thashmap_clear_and_free(&cache->for_name, struct submodule_entry, ent);\n \tcache->initialized = 0;\n \tcache->gitmodules_read = 0;\n }\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex f38706216f..2475663b49 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -110,7 +110,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t\thashmap_add(&map, &entries[i]->ent);\n \t\t\t}\n \n-\t\t\thashmap_free(&map);\n+\t\t\thashmap_clear(&map);\n \t\t}\n \t} else {\n \t\t/* test map lookups */\n@@ -130,7 +130,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n \t\t\t}\n \t\t}\n \n-\t\thashmap_free(&map);\n+\t\thashmap_clear(&map);\n \t}\n }\n \n@@ -262,6 +262,6 @@ int cmd__hashmap(int argc, const char **argv)\n \t}\n \n \tstrbuf_release(&line);\n-\thashmap_free_entries(&map, struct test_entry, ent);\n+\thashmap_clear_and_free(&map, struct test_entry, ent);\n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"409701","messageId":"f499934f54a2d1d9ac6ffeafcd6388d183d26fa3.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 09/15] strmap: add functions facilitating use as a string->int map","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:15Z","receivedAt":"2020-11-11T20:02:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nAlthough strmap could be used as a string->int map, one either had to\nallocate an int for every entry and then deallocate later, or one had to\ndo a bunch of casting between (void*) and (intptr_t).\n\nAdd some special functions that do the casting.  Also, rename put->set\nfor such wrapper functions since 'put' implied there may be some\ndeallocation needed if the string was already found in the map, which\nisn't the case when we're storing an int value directly in the void*\nslot instead of using the void* slot as a pointer to data.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 11 +++++++\n strmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 105 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex c410c5241a..0d10a884b5 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -123,3 +123,14 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\tfree((char*)ret->key);\n \tfree(ret);\n }\n+\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n+{\n+\tstruct strmap_entry *entry = find_strmap_entry(&map->map, str);\n+\tif (entry) {\n+\t\tintptr_t *whence = (intptr_t*)&entry->value;\n+\t\t*whence += amt;\n+\t}\n+\telse\n+\t\tstrintmap_set(map, str, map->default_value + amt);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex c14fcee148..56a5cdb864 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -23,6 +23,10 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = HASHMAP_INIT(cmp_strmap_entry, NULL),  \\\n \t\t\t.strdup_strings = 1,                          \\\n \t\t    }\n+#define STRINTMAP_INIT { \\\n+\t\t\t.map = STRMAP_INIT,   \\\n+\t\t\t.default_value = 0,   \\\n+\t\t       }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -102,4 +106,94 @@ static inline int strmap_empty(struct strmap *map)\n #define strmap_for_each_entry(mystrmap, iter, var)\t\\\n \thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n \n+\n+/*\n+ * strintmap:\n+ *    A map of string -> int, typecasting the void* of strmap to an int.\n+ *\n+ * Primary differences:\n+ *    1) Since the void* value is just an int in disguise, there is no value\n+ *       to free.  (Thus one fewer argument to strintmap_clear)\n+ *    2) strintmap_get() returns an int, or returns the default_value if the\n+ *       key is not found in the strintmap.\n+ *    3) No strmap_put() equivalent; strintmap_set() and strintmap_incr()\n+ *       instead.\n+ */\n+\n+struct strintmap {\n+\tstruct strmap map;\n+\tint default_value;\n+};\n+\n+#define strintmap_for_each_entry(mystrmap, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrmap)->map, iter, var)\n+\n+static inline void strintmap_init(struct strintmap *map, int default_value)\n+{\n+\tstrmap_init(&map->map);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_init_with_options(struct strintmap *map,\n+\t\t\t\t\t       int default_value,\n+\t\t\t\t\t       int strdup_strings)\n+{\n+\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tmap->default_value = default_value;\n+}\n+\n+static inline void strintmap_clear(struct strintmap *map)\n+{\n+\tstrmap_clear(&map->map, 0);\n+}\n+\n+static inline void strintmap_partial_clear(struct strintmap *map)\n+{\n+\tstrmap_partial_clear(&map->map, 0);\n+}\n+\n+static inline int strintmap_contains(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_contains(&map->map, str);\n+}\n+\n+static inline void strintmap_remove(struct strintmap *map, const char *str)\n+{\n+\treturn strmap_remove(&map->map, str, 0);\n+}\n+\n+static inline int strintmap_empty(struct strintmap *map)\n+{\n+\treturn strmap_empty(&map->map);\n+}\n+\n+static inline unsigned int strintmap_get_size(struct strintmap *map)\n+{\n+\treturn strmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Returns the value for str in the map.  If str isn't found in the map,\n+ * the map's default_value is returned.\n+ */\n+static inline int strintmap_get(struct strintmap *map, const char *str)\n+{\n+\tstruct strmap_entry *result = strmap_get_entry(&map->map, str);\n+\tif (!result)\n+\t\treturn map->default_value;\n+\treturn (intptr_t)result->value;\n+}\n+\n+static inline void strintmap_set(struct strintmap *map, const char *str,\n+\t\t\t\t intptr_t v)\n+{\n+\tstrmap_put(&map->map, str, (void *)v);\n+}\n+\n+/*\n+ * Increment the value for str by amt.  If str isn't in the map, add it and\n+ * set its value to default_value + amt.\n+ */\n+void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409702","messageId":"fd96e9fc8df23851022edf3799012441cd4c8728.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 08/15] strmap: enable faster clearing and reusing of strmaps","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:14Z","receivedAt":"2020-11-11T20:02:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWhen strmaps are used heavily, such as is done by my new merge-ort\nalgorithm, and strmaps need to be cleared but then re-used (because of\ne.g. picking multiple commits to cherry-pick, or due to a recursive\nmerge having several different merges while recursing), free-ing and\nreallocating map->table repeatedly can add up in time, especially since\nit will likely be reallocated to a much smaller size but the previous\nmerge provides a good guide to the right size to use for the next merge.\n\nIntroduce strmap_partial_clear() to take advantage of this type of\nsituation; it will act similar to strmap_clear() except that\nmap->table's entries are zeroed instead of map->table being free'd.\nMaking use of this function reduced the cost of\nclear_or_reinit_internal_opts() by about 20% in mert-ort, and dropped\nthe overall runtime of my rebase testcase by just under 2%.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 6 ++++++\n strmap.h | 6 ++++++\n 2 files changed, 12 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 829f1bc095..c410c5241a 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -64,6 +64,12 @@ void strmap_clear(struct strmap *map, int free_values)\n \thashmap_clear(&map->map);\n }\n \n+void strmap_partial_clear(struct strmap *map, int free_values)\n+{\n+\tstrmap_free_entries_(map, free_values);\n+\thashmap_partial_clear(&map->map);\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\ndiff --git a/strmap.h b/strmap.h\nindex f74bc582e4..c14fcee148 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -42,6 +42,12 @@ void strmap_init_with_options(struct strmap *map,\n  */\n void strmap_clear(struct strmap *map, int free_values);\n \n+/*\n+ * Similar to strmap_clear() but leaves map->map->table allocated and\n+ * pre-sized so that subsequent uses won't need as many rehashings.\n+ */\n+void strmap_partial_clear(struct strmap *map, int free_values);\n+\n /*\n  * Insert \"str\" into the map, pointing to \"data\".\n  *\n-- \ngitgitgadget\n\n"},{"id":"409703","messageId":"3bcceb8cdb1dfd571ac5e0b45f20b3aef8bf553b.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 10/15] strmap: split create_entry() out of strmap_put()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:16Z","receivedAt":"2020-11-11T20:02:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis will facilitate adding entries to a strmap subtype in ways that\ndiffer slightly from that of strmap_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 37 +++++++++++++++++++++++--------------\n 1 file changed, 23 insertions(+), 14 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 0d10a884b5..dc84c57c07 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -70,27 +70,36 @@ void strmap_partial_clear(struct strmap *map, int free_values)\n \thashmap_partial_clear(&map->map);\n }\n \n+static struct strmap_entry *create_entry(struct strmap *map,\n+\t\t\t\t\t const char *str,\n+\t\t\t\t\t void *data)\n+{\n+\tstruct strmap_entry *entry;\n+\tconst char *key = str;\n+\n+\tentry = xmalloc(sizeof(*entry));\n+\thashmap_entry_init(&entry->ent, strhash(str));\n+\n+\tif (map->strdup_strings)\n+\t\tkey = xstrdup(str);\n+\tentry->key = key;\n+\tentry->value = data;\n+\treturn entry;\n+}\n+\n void *strmap_put(struct strmap *map, const char *str, void *data)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n-\tvoid *old = NULL;\n \n \tif (entry) {\n-\t\told = entry->value;\n+\t\tvoid *old = entry->value;\n \t\tentry->value = data;\n-\t} else {\n-\t\tconst char *key = str;\n-\n-\t\tentry = xmalloc(sizeof(*entry));\n-\t\thashmap_entry_init(&entry->ent, strhash(str));\n-\n-\t\tif (map->strdup_strings)\n-\t\t\tkey = xstrdup(str);\n-\t\tentry->key = key;\n-\t\tentry->value = data;\n-\t\thashmap_add(&map->map, &entry->ent);\n+\t\treturn old;\n \t}\n-\treturn old;\n+\n+\tentry = create_entry(map, str, data);\n+\thashmap_add(&map->map, &entry->ent);\n+\treturn NULL;\n }\n \n struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n-- \ngitgitgadget\n\n"},{"id":"409704","messageId":"e128a71fec415e92cfcb9c63987ee8264235bb6f.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 11/15] strmap: add a strset sub-type","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:17Z","receivedAt":"2020-11-11T20:02:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSimilar to adding strintmap for special-casing a string -> int mapping,\nadd a strset type for cases where we really are only interested in using\nstrmap for storing a set rather than a mapping.  In this case, we'll\nalways just store NULL for the value but the different struct type makes\nit clearer than code comments how a variable is intended to be used.\n\nThe difference in usage also results in some differences in API: a few\nthings that aren't necessary or meaningful are dropped (namely, the\nfree_values argument to *_clear(), and the *_get() function), and\nstrset_add() is chosen as the API instead of strset_put().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 17 +++++++++++++++\n strmap.h | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 80 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex dc84c57c07..3784865745 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -143,3 +143,20 @@ void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\n \telse\n \t\tstrintmap_set(map, str, map->default_value + amt);\n }\n+\n+int strset_add(struct strset *set, const char *str)\n+{\n+\t/*\n+\t * Cannot use strmap_put() because it'll return NULL in both cases:\n+\t *   - cannot find str: NULL means \"not found\"\n+\t *   - does find str: NULL is the value associated with str\n+\t */\n+\tstruct strmap_entry *entry = find_strmap_entry(&set->map, str);\n+\n+\tif (entry)\n+\t\treturn 0;\n+\n+\tentry = create_entry(&set->map, str, NULL);\n+\thashmap_add(&set->map.map, &entry->ent);\n+\treturn 1;\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 56a5cdb864..c8c4d7c932 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -27,6 +27,7 @@ int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t\t.map = STRMAP_INIT,   \\\n \t\t\t.default_value = 0,   \\\n \t\t       }\n+#define STRSET_INIT { .map = STRMAP_INIT }\n \n /*\n  * Initialize the members of the strmap.  Any keys added to the strmap will\n@@ -196,4 +197,66 @@ static inline void strintmap_set(struct strintmap *map, const char *str,\n  */\n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt);\n \n+/*\n+ * strset:\n+ *    A set of strings.\n+ *\n+ * Primary differences with strmap:\n+ *    1) The value is always NULL, and ignored.  As there is no value to free,\n+ *       there is one fewer argument to strset_clear\n+ *    2) No strset_get() because there is no value.\n+ *    3) No strset_put(); use strset_add() instead.\n+ */\n+\n+struct strset {\n+\tstruct strmap map;\n+};\n+\n+#define strset_for_each_entry(mystrset, iter, var)\t\\\n+\tstrmap_for_each_entry(&(mystrset)->map, iter, var)\n+\n+static inline void strset_init(struct strset *set)\n+{\n+\tstrmap_init(&set->map);\n+}\n+\n+static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    int strdup_strings)\n+{\n+\tstrmap_init_with_options(&set->map, strdup_strings);\n+}\n+\n+static inline void strset_clear(struct strset *set)\n+{\n+\tstrmap_clear(&set->map, 0);\n+}\n+\n+static inline void strset_partial_clear(struct strset *set)\n+{\n+\tstrmap_partial_clear(&set->map, 0);\n+}\n+\n+static inline int strset_contains(struct strset *set, const char *str)\n+{\n+\treturn strmap_contains(&set->map, str);\n+}\n+\n+static inline void strset_remove(struct strset *set, const char *str)\n+{\n+\treturn strmap_remove(&set->map, str, 0);\n+}\n+\n+static inline int strset_empty(struct strset *set)\n+{\n+\treturn strmap_empty(&set->map);\n+}\n+\n+static inline unsigned int strset_get_size(struct strset *set)\n+{\n+\treturn strmap_get_size(&set->map);\n+}\n+\n+/* Returns 1 if str is added to the set; returns 0 if str was already in set */\n+int strset_add(struct strset *set, const char *str);\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409705","messageId":"3926c4c97bd08aac93d3f521273db9d76b4d5cd3.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:18Z","receivedAt":"2020-11-11T20:02:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFor heavy users of strmaps, allowing the keys and entries to be\nallocated from a memory pool can provide significant overhead savings.\nAdd an option to strmap_init_with_options() to specify a memory pool.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 31 ++++++++++++++++++++++---------\n strmap.h | 11 ++++++++---\n 2 files changed, 30 insertions(+), 12 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 3784865745..139afb9d4b 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"strmap.h\"\n+#include \"mem-pool.h\"\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n \t\t     const struct hashmap_entry *entry1,\n@@ -24,13 +25,15 @@ static struct strmap_entry *find_strmap_entry(struct strmap *map,\n \n void strmap_init(struct strmap *map)\n {\n-\tstrmap_init_with_options(map, 1);\n+\tstrmap_init_with_options(map, NULL, 1);\n }\n \n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings)\n {\n \thashmap_init(&map->map, cmp_strmap_entry, NULL, 0);\n+\tmap->pool = pool;\n \tmap->strdup_strings = strdup_strings;\n }\n \n@@ -42,6 +45,10 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \tif (!map)\n \t\treturn;\n \n+\tif (!free_values && map->pool)\n+\t\t/* Memory other than util is owned by and freed with the pool */\n+\t\treturn;\n+\n \t/*\n \t * We need to iterate over the hashmap entries and free\n \t * e->key and e->value ourselves; hashmap has no API to\n@@ -52,9 +59,11 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)e->key);\n-\t\tfree(e);\n+\t\tif (!map->pool) {\n+\t\t\tif (map->strdup_strings)\n+\t\t\t\tfree((char*)e->key);\n+\t\t\tfree(e);\n+\t\t}\n \t}\n }\n \n@@ -77,11 +86,13 @@ static struct strmap_entry *create_entry(struct strmap *map,\n \tstruct strmap_entry *entry;\n \tconst char *key = str;\n \n-\tentry = xmalloc(sizeof(*entry));\n+\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n+\t\t\t  : xmalloc(sizeof(*entry));\n \thashmap_entry_init(&entry->ent, strhash(str));\n \n \tif (map->strdup_strings)\n-\t\tkey = xstrdup(str);\n+\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n+\t\t\t\t: xstrdup(str);\n \tentry->key = key;\n \tentry->value = data;\n \treturn entry;\n@@ -128,9 +139,11 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (map->strdup_strings)\n-\t\tfree((char*)ret->key);\n-\tfree(ret);\n+\tif (!map->pool) {\n+\t\tif (map->strdup_strings)\n+\t\t\tfree((char*)ret->key);\n+\t\tfree(ret);\n+\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex c8c4d7c932..8745b7ceb1 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -3,8 +3,10 @@\n \n #include \"hashmap.h\"\n \n+struct mem_pool;\n struct strmap {\n \tstruct hashmap map;\n+\tstruct mem_pool *pool;\n \tunsigned int strdup_strings:1;\n };\n \n@@ -37,9 +39,10 @@ void strmap_init(struct strmap *map);\n \n /*\n  * Same as strmap_init, but for those who want to control the memory management\n- * carefully instead of using the default of strdup_strings=1.\n+ * carefully instead of using the default of strdup_strings=1 and pool=NULL.\n  */\n void strmap_init_with_options(struct strmap *map,\n+\t\t\t      struct mem_pool *pool,\n \t\t\t      int strdup_strings);\n \n /*\n@@ -137,9 +140,10 @@ static inline void strintmap_init(struct strintmap *map, int default_value)\n \n static inline void strintmap_init_with_options(struct strintmap *map,\n \t\t\t\t\t       int default_value,\n+\t\t\t\t\t       struct mem_pool *pool,\n \t\t\t\t\t       int strdup_strings)\n {\n-\tstrmap_init_with_options(&map->map, strdup_strings);\n+\tstrmap_init_with_options(&map->map, pool, strdup_strings);\n \tmap->default_value = default_value;\n }\n \n@@ -221,9 +225,10 @@ static inline void strset_init(struct strset *set)\n }\n \n static inline void strset_init_with_options(struct strset *set,\n+\t\t\t\t\t    struct mem_pool *pool,\n \t\t\t\t\t    int strdup_strings)\n {\n-\tstrmap_init_with_options(&set->map, strdup_strings);\n+\tstrmap_init_with_options(&set->map, pool, strdup_strings);\n }\n \n static inline void strset_clear(struct strset *set)\n-- \ngitgitgadget\n\n"},{"id":"409706","messageId":"61f1da3c51b521035ba728c025cd6a28397f8051.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 04/15] hashmap: introduce a new hashmap_partial_clear()","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:10Z","receivedAt":"2020-11-11T20:02:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nmerge-ort is a heavy user of strmaps, which are built on hashmap.[ch].\nclear_or_reinit_internal_opts() in merge-ort was taking about 12% of\noverall runtime in my testcase involving rebasing 35 patches of\nlinux.git across a big rename.  clear_or_reinit_internal_opts() was\ncalling hashmap_free() followed by hashmap_init(), meaning that not only\nwas it freeing all the memory associated with each of the strmaps just\nto immediately allocate a new array again, it was allocating a new array\nthat was likely smaller than needed (thus resulting in later need to\nrehash things).  The ending size of the map table on the previous commit\nwas likely almost perfectly sized for the next commit we wanted to pick,\nand not dropping and reallocating the table immediately is a win.\n\nAdd some new API to hashmap to clear a hashmap of entries without\nfreeing map->table (and instead only zeroing it out like alloc_table()\nwould do, along with zeroing the count of items in the table and the\nshrink_at field).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n hashmap.c | 39 +++++++++++++++++++++++++++------------\n hashmap.h | 13 ++++++++++++-\n 2 files changed, 39 insertions(+), 13 deletions(-)\n\ndiff --git a/hashmap.c b/hashmap.c\nindex bb7c9979b8..922ed07954 100644\n--- a/hashmap.c\n+++ b/hashmap.c\n@@ -174,22 +174,37 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,\n \tmap->do_count_items = 1;\n }\n \n+static void free_individual_entries(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tstruct hashmap_iter iter;\n+\tstruct hashmap_entry *e;\n+\n+\thashmap_iter_init(map, &iter);\n+\twhile ((e = hashmap_iter_next(&iter)))\n+\t\t/*\n+\t\t * like container_of, but using caller-calculated\n+\t\t * offset (caller being hashmap_free_entries)\n+\t\t */\n+\t\tfree((char *)e - entry_offset);\n+}\n+\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset)\n+{\n+\tif (!map || !map->table)\n+\t\treturn;\n+\tif (entry_offset >= 0)  /* called by hashmap_clear_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n+\tmemset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *));\n+\tmap->shrink_at = 0;\n+\tmap->private_size = 0;\n+}\n+\n void hashmap_free_(struct hashmap *map, ssize_t entry_offset)\n {\n \tif (!map || !map->table)\n \t\treturn;\n-\tif (entry_offset >= 0) { /* called by hashmap_free_entries */\n-\t\tstruct hashmap_iter iter;\n-\t\tstruct hashmap_entry *e;\n-\n-\t\thashmap_iter_init(map, &iter);\n-\t\twhile ((e = hashmap_iter_next(&iter)))\n-\t\t\t/*\n-\t\t\t * like container_of, but using caller-calculated\n-\t\t\t * offset (caller being hashmap_free_entries)\n-\t\t\t */\n-\t\t\tfree((char *)e - entry_offset);\n-\t}\n+\tif (entry_offset >= 0)  /* called by hashmap_free_entries */\n+\t\tfree_individual_entries(map, entry_offset);\n \tfree(map->table);\n \tmemset(map, 0, sizeof(*map));\n }\ndiff --git a/hashmap.h b/hashmap.h\nindex 3b0f2bcade..e9430d582a 100644\n--- a/hashmap.h\n+++ b/hashmap.h\n@@ -235,7 +235,8 @@ void hashmap_init(struct hashmap *map,\n \t\t  const void *equals_function_data,\n \t\t  size_t initial_size);\n \n-/* internal function for freeing hashmap */\n+/* internal functions for clearing or freeing hashmap */\n+void hashmap_partial_clear_(struct hashmap *map, ssize_t offset);\n void hashmap_free_(struct hashmap *map, ssize_t offset);\n \n /*\n@@ -268,6 +269,16 @@ void hashmap_free_(struct hashmap *map, ssize_t offset);\n  */\n #define hashmap_free(map) hashmap_free_(map, -1)\n \n+/*\n+ * Basically the same as calling hashmap_free() followed by hashmap_init(),\n+ * but doesn't incur the overhead of deallocating and reallocating\n+ * map->table; it leaves map->table allocated and the same size but zeroes\n+ * it out so it's ready for use again as an empty map.  As with\n+ * hashmap_free(), you may need to free the entries yourself before calling\n+ * this function.\n+ */\n+#define hashmap_partial_clear(map) hashmap_partial_clear_(map, -1)\n+\n /*\n  * Frees @map and all entries.  @type is the struct type of the entry\n  * where @member is the hashmap_entry struct used to associate with @map.\n-- \ngitgitgadget\n\n"},{"id":"409707","messageId":"058e7a6b76047a6e3196ff13cafc2d6ea3b772df.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 14/15] Use new HASHMAP_INIT macro to simplify hashmap initialization","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:20Z","receivedAt":"2020-11-11T20:02:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNow that hashamp has lazy initialization and a HASHMAP_INIT macro,\nhashmaps allocated on the stack can be initialized without a call to\nhashmap_init() and in some cases makes the code a bit shorter.  Convert\nsome callsites over to take advantage of this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n attr.c                  | 26 ++++++++------------------\n bloom.c                 |  3 +--\n builtin/difftool.c      |  9 ++++-----\n range-diff.c            |  4 +---\n revision.c              |  9 +--------\n t/helper/test-hashmap.c |  3 +--\n 6 files changed, 16 insertions(+), 38 deletions(-)\n\ndiff --git a/attr.c b/attr.c\nindex a826b2ef1f..4ef85d668b 100644\n--- a/attr.c\n+++ b/attr.c\n@@ -52,13 +52,6 @@ static inline void hashmap_unlock(struct attr_hashmap *map)\n \tpthread_mutex_unlock(&map->mutex);\n }\n \n-/*\n- * The global dictionary of all interned attributes.  This\n- * is a singleton object which is shared between threads.\n- * Access to this dictionary must be surrounded with a mutex.\n- */\n-static struct attr_hashmap g_attr_hashmap;\n-\n /* The container for objects stored in \"struct attr_hashmap\" */\n struct attr_hash_entry {\n \tstruct hashmap_entry ent;\n@@ -80,11 +73,14 @@ static int attr_hash_entry_cmp(const void *unused_cmp_data,\n \treturn (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);\n }\n \n-/* Initialize an 'attr_hashmap' object */\n-static void attr_hashmap_init(struct attr_hashmap *map)\n-{\n-\thashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0);\n-}\n+/*\n+ * The global dictionary of all interned attributes.  This\n+ * is a singleton object which is shared between threads.\n+ * Access to this dictionary must be surrounded with a mutex.\n+ */\n+static struct attr_hashmap g_attr_hashmap = {\n+\tHASHMAP_INIT(attr_hash_entry_cmp, NULL)\n+};\n \n /*\n  * Retrieve the 'value' stored in a hashmap given the provided 'key'.\n@@ -96,9 +92,6 @@ static void *attr_hashmap_get(struct attr_hashmap *map,\n \tstruct attr_hash_entry k;\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \thashmap_entry_init(&k.ent, memhash(key, keylen));\n \tk.key = key;\n \tk.keylen = keylen;\n@@ -114,9 +107,6 @@ static void attr_hashmap_add(struct attr_hashmap *map,\n {\n \tstruct attr_hash_entry *e;\n \n-\tif (!map->map.tablesize)\n-\t\tattr_hashmap_init(map);\n-\n \te = xmalloc(sizeof(struct attr_hash_entry));\n \thashmap_entry_init(&e->ent, memhash(key, keylen));\n \te->key = key;\ndiff --git a/bloom.c b/bloom.c\nindex 719c313a1c..b176f28f53 100644\n--- a/bloom.c\n+++ b/bloom.c\n@@ -229,10 +229,9 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r,\n \tdiffcore_std(&diffopt);\n \n \tif (diff_queued_diff.nr <= settings->max_changed_paths) {\n-\t\tstruct hashmap pathmap;\n+\t\tstruct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL);\n \t\tstruct pathmap_hash_entry *e;\n \t\tstruct hashmap_iter iter;\n-\t\thashmap_init(&pathmap, pathmap_cmp, NULL, 0);\n \n \t\tfor (i = 0; i < diff_queued_diff.nr; i++) {\n \t\t\tconst char *path = diff_queued_diff.queue[i]->two->path;\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 7ac432b881..6e18e623fd 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \tconst char *workdir, *tmp;\n \tint ret = 0, i;\n \tFILE *fp;\n-\tstruct hashmap working_tree_dups, submodules, symlinks2;\n+\tstruct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp,\n+\t\t\t\t\t\t\tNULL);\n+\tstruct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL);\n+\tstruct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL);\n \tstruct hashmap_iter iter;\n \tstruct pair_entry *entry;\n \tstruct index_state wtindex;\n@@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \trdir_len = rdir.len;\n \twtdir_len = wtdir.len;\n \n-\thashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0);\n-\thashmap_init(&submodules, pair_cmp, NULL, 0);\n-\thashmap_init(&symlinks2, pair_cmp, NULL, 0);\n-\n \tchild.no_stdin = 1;\n \tchild.git_cmd = 1;\n \tchild.use_shell = 0;\ndiff --git a/range-diff.c b/range-diff.c\nindex befeecae44..b9950f10c8 100644\n--- a/range-diff.c\n+++ b/range-diff.c\n@@ -232,11 +232,9 @@ static int patch_util_cmp(const void *dummy, const struct patch_util *a,\n \n static void find_exact_matches(struct string_list *a, struct string_list *b)\n {\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT((hashmap_cmp_fn)patch_util_cmp, NULL);\n \tint i;\n \n-\thashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);\n-\n \t/* First, add the patches of a to a hash map */\n \tfor (i = 0; i < a->nr; i++) {\n \t\tstruct patch_util *util = a->items[i].util;\ndiff --git a/revision.c b/revision.c\nindex f27649d45d..c6e169e3eb 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -124,11 +124,6 @@ static int path_and_oids_cmp(const void *hashmap_cmp_fn_data,\n \treturn strcmp(e1->path, e2->path);\n }\n \n-static void paths_and_oids_init(struct hashmap *map)\n-{\n-\thashmap_init(map, path_and_oids_cmp, NULL, 0);\n-}\n-\n static void paths_and_oids_clear(struct hashmap *map)\n {\n \tstruct hashmap_iter iter;\n@@ -213,7 +208,7 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \t\t\t\t     struct oidset *trees)\n {\n \tunsigned has_interesting = 0, has_uninteresting = 0;\n-\tstruct hashmap map;\n+\tstruct hashmap map = HASHMAP_INIT(path_and_oids_cmp, NULL);\n \tstruct hashmap_iter map_iter;\n \tstruct path_and_oids_entry *entry;\n \tstruct object_id *oid;\n@@ -237,8 +232,6 @@ void mark_trees_uninteresting_sparse(struct repository *r,\n \tif (!has_uninteresting || !has_interesting)\n \t\treturn;\n \n-\tpaths_and_oids_init(&map);\n-\n \toidset_iter_init(trees, &iter);\n \twhile ((oid = oidset_iter_next(&iter))) {\n \t\tstruct tree *tree = lookup_tree(r, oid);\ndiff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c\nindex 2475663b49..36ff07bd4b 100644\n--- a/t/helper/test-hashmap.c\n+++ b/t/helper/test-hashmap.c\n@@ -151,12 +151,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)\n int cmd__hashmap(int argc, const char **argv)\n {\n \tstruct strbuf line = STRBUF_INIT;\n-\tstruct hashmap map;\n \tint icase;\n+\tstruct hashmap map = HASHMAP_INIT(test_entry_cmp, &icase);\n \n \t/* init hash map */\n \ticase = argc > 1 && !strcmp(\"ignorecase\", argv[1]);\n-\thashmap_init(&map, test_entry_cmp, &icase, 0);\n \n \t/* process commands from stdin */\n \twhile (strbuf_getline(&line, stdin) != EOF) {\n-- \ngitgitgadget\n\n"},{"id":"409708","messageId":"9b494c26c18e5a5b4f2561e30546d3bf3e82ef11.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 15/15] shortlog: use strset from strmap.h","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:21Z","receivedAt":"2020-11-11T20:02:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/shortlog.c | 61 +++-------------------------------------------\n 1 file changed, 4 insertions(+), 57 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex 83f0a739b4..c52e4ccd19 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -10,6 +10,7 @@\n #include \"shortlog.h\"\n #include \"parse-options.h\"\n #include \"trailer.h\"\n+#include \"strmap.h\"\n \n static char const * const shortlog_usage[] = {\n \tN_(\"git shortlog [<options>] [<revision-range>] [[--] <path>...]\"),\n@@ -169,60 +170,6 @@ static void read_from_stdin(struct shortlog *log)\n \tstrbuf_release(&oneline);\n }\n \n-struct strset_item {\n-\tstruct hashmap_entry ent;\n-\tchar value[FLEX_ARRAY];\n-};\n-\n-struct strset {\n-\tstruct hashmap map;\n-};\n-\n-#define STRSET_INIT { { NULL } }\n-\n-static int strset_item_hashcmp(const void *hash_data,\n-\t\t\t       const struct hashmap_entry *entry,\n-\t\t\t       const struct hashmap_entry *entry_or_key,\n-\t\t\t       const void *keydata)\n-{\n-\tconst struct strset_item *a, *b;\n-\n-\ta = container_of(entry, const struct strset_item, ent);\n-\tif (keydata)\n-\t\treturn strcmp(a->value, keydata);\n-\n-\tb = container_of(entry_or_key, const struct strset_item, ent);\n-\treturn strcmp(a->value, b->value);\n-}\n-\n-/*\n- * Adds \"str\" to the set if it was not already present; returns true if it was\n- * already there.\n- */\n-static int strset_check_and_add(struct strset *ss, const char *str)\n-{\n-\tunsigned int hash = strhash(str);\n-\tstruct strset_item *item;\n-\n-\tif (!ss->map.table)\n-\t\thashmap_init(&ss->map, strset_item_hashcmp, NULL, 0);\n-\n-\tif (hashmap_get_from_hash(&ss->map, hash, str))\n-\t\treturn 1;\n-\n-\tFLEX_ALLOC_STR(item, value, str);\n-\thashmap_entry_init(&item->ent, hash);\n-\thashmap_add(&ss->map, &item->ent);\n-\treturn 0;\n-}\n-\n-static void strset_clear(struct strset *ss)\n-{\n-\tif (!ss->map.table)\n-\t\treturn;\n-\thashmap_clear_and_free(&ss->map, struct strset_item, ent);\n-}\n-\n static void insert_records_from_trailers(struct shortlog *log,\n \t\t\t\t\t struct strset *dups,\n \t\t\t\t\t struct commit *commit,\n@@ -253,7 +200,7 @@ static void insert_records_from_trailers(struct shortlog *log,\n \t\tif (!parse_ident(log, &ident, value))\n \t\t\tvalue = ident.buf;\n \n-\t\tif (strset_check_and_add(dups, value))\n+\t\tif (!strset_add(dups, value))\n \t\t\tcontinue;\n \t\tinsert_one_record(log, value, oneline);\n \t}\n@@ -291,7 +238,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%aN <%aE>\" : \"%aN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    strset_add(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_COMMITTER) {\n@@ -300,7 +247,7 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)\n \t\t\t\t      log->email ? \"%cN <%cE>\" : \"%cN\",\n \t\t\t\t      &ident, &ctx);\n \t\tif (!HAS_MULTI_BITS(log->groups) ||\n-\t\t    !strset_check_and_add(&dups, ident.buf))\n+\t\t    strset_add(&dups, ident.buf))\n \t\t\tinsert_one_record(log, ident.buf, oneline_str);\n \t}\n \tif (log->groups & SHORTLOG_GROUP_TRAILER) {\n-- \ngitgitgadget\n"},{"id":"409709","messageId":"562595224b56ff8b3dc3b97baa713e47d18e2126.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 13/15] strmap: take advantage of FLEXPTR_ALLOC_STR when relevant","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:19Z","receivedAt":"2020-11-11T20:02:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nBy default, we do not use a mempool and strdup_strings is true; in this\ncase, we can avoid both an extra allocation and an extra free by just\nover-allocating for the strmap_entry leaving enough space at the end to\ncopy the key.  FLEXPTR_ALLOC_STR exists for exactly this purpose, so\nmake use of it.\n\nAlso, adjust the case when we are using a memory pool and strdup_strings\nis true to just do one allocation from the memory pool instead of two so\nthat the strmap_clear() and strmap_remove() code can just avoid freeing\nthe key in all cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 35 +++++++++++++++++++----------------\n strmap.h |  1 +\n 2 files changed, 20 insertions(+), 16 deletions(-)\n\ndiff --git a/strmap.c b/strmap.c\nindex 139afb9d4b..4fb9f6100e 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -59,11 +59,8 @@ static void strmap_free_entries_(struct strmap *map, int free_values)\n \thashmap_for_each_entry(&map->map, &iter, e, ent) {\n \t\tif (free_values)\n \t\t\tfree(e->value);\n-\t\tif (!map->pool) {\n-\t\t\tif (map->strdup_strings)\n-\t\t\t\tfree((char*)e->key);\n+\t\tif (!map->pool)\n \t\t\tfree(e);\n-\t\t}\n \t}\n }\n \n@@ -84,16 +81,25 @@ static struct strmap_entry *create_entry(struct strmap *map,\n \t\t\t\t\t void *data)\n {\n \tstruct strmap_entry *entry;\n-\tconst char *key = str;\n \n-\tentry = map->pool ? mem_pool_alloc(map->pool, sizeof(*entry))\n-\t\t\t  : xmalloc(sizeof(*entry));\n+\tif (map->strdup_strings) {\n+\t\tif (!map->pool) {\n+\t\t\tFLEXPTR_ALLOC_STR(entry, key, str);\n+\t\t} else {\n+\t\t\tsize_t len = st_add(strlen(str), 1); /* include NUL */\n+\t\t\tentry = mem_pool_alloc(map->pool,\n+\t\t\t\t\t       st_add(sizeof(*entry), len));\n+\t\t\tmemcpy(entry + 1, str, len);\n+\t\t\tentry->key = (void *)(entry + 1);\n+\t\t}\n+\t} else if (!map->pool) {\n+\t\tentry = xmalloc(sizeof(*entry));\n+\t} else {\n+\t\tentry = mem_pool_alloc(map->pool, sizeof(*entry));\n+\t}\n \thashmap_entry_init(&entry->ent, strhash(str));\n-\n-\tif (map->strdup_strings)\n-\t\tkey = map->pool ? mem_pool_strdup(map->pool, str)\n-\t\t\t\t: xstrdup(str);\n-\tentry->key = key;\n+\tif (!map->strdup_strings)\n+\t\tentry->key = str;\n \tentry->value = data;\n \treturn entry;\n }\n@@ -139,11 +145,8 @@ void strmap_remove(struct strmap *map, const char *str, int free_value)\n \t\treturn;\n \tif (free_value)\n \t\tfree(ret->value);\n-\tif (!map->pool) {\n-\t\tif (map->strdup_strings)\n-\t\t\tfree((char*)ret->key);\n+\tif (!map->pool)\n \t\tfree(ret);\n-\t}\n }\n \n void strintmap_incr(struct strintmap *map, const char *str, intptr_t amt)\ndiff --git a/strmap.h b/strmap.h\nindex 8745b7ceb1..c4c104411b 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -14,6 +14,7 @@ struct strmap_entry {\n \tstruct hashmap_entry ent;\n \tconst char *key;\n \tvoid *value;\n+\t/* strmap_entry may be allocated extra space to store the key at end */\n };\n \n int cmp_strmap_entry(const void *hashmap_cmp_fn_data,\n-- \ngitgitgadget\n\n"},{"id":"409710","messageId":"5e8004c728580589cdaf46d755f2c977feaffb34.1605124942.git.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"[PATCH v6 07/15] strmap: add more utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:13Z","receivedAt":"2020-11-11T20:02:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThis adds a number of additional convienence functions I want/need:\n  * strmap_get_size()\n  * strmap_empty()\n  * strmap_remove()\n  * strmap_for_each_entry()\n  * strmap_get_entry()\n\nI suspect the first four are self-explanatory.\n\nstrmap_get_entry() is similar to strmap_get() except that instead of just\nreturning the void* value that the string maps to, it returns the\nstrmap_entry that contains both the string and the void* value (or\nNULL if the string isn't in the map).  This is helpful because it avoids\nmultiple lookups, e.g. in some cases a caller would need to call:\n  * strmap_contains() to check that the map has an entry for the string\n  * strmap_get() to get the void* value\n  * <do some work to update the value>\n  * strmap_put() to update/overwrite the value\nIf the void* pointer returned really is a pointer, then the last step is\nunnecessary, but if the void* pointer is just cast to an integer then\nstrmap_put() will be needed.  In contrast, one can call strmap_get_entry()\nand then:\n  * check if the string was in the map by whether the pointer is NULL\n  * access the value via entry->value\n  * directly update entry->value\nmeaning that we can replace two or three hash table lookups with one.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n strmap.c | 20 ++++++++++++++++++++\n strmap.h | 34 ++++++++++++++++++++++++++++++++++\n 2 files changed, 54 insertions(+)\n\ndiff --git a/strmap.c b/strmap.c\nindex 53f284eb20..829f1bc095 100644\n--- a/strmap.c\n+++ b/strmap.c\n@@ -87,6 +87,11 @@ void *strmap_put(struct strmap *map, const char *str, void *data)\n \treturn old;\n }\n \n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str)\n+{\n+\treturn find_strmap_entry(map, str);\n+}\n+\n void *strmap_get(struct strmap *map, const char *str)\n {\n \tstruct strmap_entry *entry = find_strmap_entry(map, str);\n@@ -97,3 +102,18 @@ int strmap_contains(struct strmap *map, const char *str)\n {\n \treturn find_strmap_entry(map, str) != NULL;\n }\n+\n+void strmap_remove(struct strmap *map, const char *str, int free_value)\n+{\n+\tstruct strmap_entry entry, *ret;\n+\thashmap_entry_init(&entry.ent, strhash(str));\n+\tentry.key = str;\n+\tret = hashmap_remove_entry(&map->map, &entry, ent, NULL);\n+\tif (!ret)\n+\t\treturn;\n+\tif (free_value)\n+\t\tfree(ret->value);\n+\tif (map->strdup_strings)\n+\t\tfree((char*)ret->key);\n+\tfree(ret);\n+}\ndiff --git a/strmap.h b/strmap.h\nindex 96888c23ad..f74bc582e4 100644\n--- a/strmap.h\n+++ b/strmap.h\n@@ -50,6 +50,12 @@ void strmap_clear(struct strmap *map, int free_values);\n  */\n void *strmap_put(struct strmap *map, const char *str, void *data);\n \n+/*\n+ * Return the strmap_entry mapped by \"str\", or NULL if there is not such\n+ * an item in map.\n+ */\n+struct strmap_entry *strmap_get_entry(struct strmap *map, const char *str);\n+\n /*\n  * Return the data pointer mapped by \"str\", or NULL if the entry does not\n  * exist.\n@@ -62,4 +68,32 @@ void *strmap_get(struct strmap *map, const char *str);\n  */\n int strmap_contains(struct strmap *map, const char *str);\n \n+/*\n+ * Remove the given entry from the strmap.  If the string isn't in the\n+ * strmap, the map is not altered.\n+ */\n+void strmap_remove(struct strmap *map, const char *str, int free_value);\n+\n+/*\n+ * Return how many entries the strmap has.\n+ */\n+static inline unsigned int strmap_get_size(struct strmap *map)\n+{\n+\treturn hashmap_get_size(&map->map);\n+}\n+\n+/*\n+ * Return whether the strmap is empty.\n+ */\n+static inline int strmap_empty(struct strmap *map)\n+{\n+\treturn strmap_get_size(map) == 0;\n+}\n+\n+/*\n+ * iterate through @map using @iter, @var is a pointer to a type strmap_entry\n+ */\n+#define strmap_for_each_entry(mystrmap, iter, var)\t\\\n+\thashmap_for_each_entry(&(mystrmap)->map, iter, var, ent)\n+\n #endif /* STRMAP_H */\n-- \ngitgitgadget\n\n"},{"id":"409711","messageId":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","threadId":"54087","inReplyTo":"pull.835.v5.git.git.1604622298.gitgitgadget@gmail.com","subject":"[PATCH v6 00/15] Add struct strmap and associated utility functions","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2020-11-11T20:02:06Z","receivedAt":"2020-11-11T20:02:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Here I introduce new strmap, strintmap, and strset types.\n\nChanges since v5:\n\n * Fixed a typo in forward declaration of struct mem_pool, spotted by\n   Phillip. (Usage via pointers meant gcc & clang wouldn't complain.)\n\n[1] \nhttps://lore.kernel.org/git/20180906191203.GA26184@sigill.intra.peff.net/\n\nElijah Newren (15):\n  hashmap: add usage documentation explaining hashmap_free[_entries]()\n  hashmap: adjust spacing to fix argument alignment\n  hashmap: allow re-use after hashmap_free()\n  hashmap: introduce a new hashmap_partial_clear()\n  hashmap: provide deallocation function names\n  strmap: new utility functions\n  strmap: add more utility functions\n  strmap: enable faster clearing and reusing of strmaps\n  strmap: add functions facilitating use as a string->int map\n  strmap: split create_entry() out of strmap_put()\n  strmap: add a strset sub-type\n  strmap: enable allocations to come from a mem_pool\n  strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n  Use new HASHMAP_INIT macro to simplify hashmap initialization\n  shortlog: use strset from strmap.h\n\n Makefile                |   1 +\n add-interactive.c       |   2 +-\n attr.c                  |  26 ++--\n blame.c                 |   2 +-\n bloom.c                 |   5 +-\n builtin/difftool.c      |   9 +-\n builtin/fetch.c         |   6 +-\n builtin/shortlog.c      |  61 +--------\n config.c                |   2 +-\n diff.c                  |   4 +-\n diffcore-rename.c       |   2 +-\n dir.c                   |   8 +-\n hashmap.c               |  74 +++++++----\n hashmap.h               |  91 +++++++++++---\n merge-recursive.c       |   6 +-\n name-hash.c             |   4 +-\n object.c                |   2 +-\n oidmap.c                |   2 +-\n patch-ids.c             |   2 +-\n range-diff.c            |   6 +-\n ref-filter.c            |   2 +-\n revision.c              |  11 +-\n sequencer.c             |   4 +-\n strmap.c                | 178 ++++++++++++++++++++++++++\n strmap.h                | 268 ++++++++++++++++++++++++++++++++++++++++\n submodule-config.c      |   4 +-\n t/helper/test-hashmap.c |   9 +-\n 27 files changed, 621 insertions(+), 170 deletions(-)\n create mode 100644 strmap.c\n create mode 100644 strmap.h\n\n\nbase-commit: d4a392452e292ff924e79ec8458611c0f679d6d4\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-835%2Fnewren%2Fstrmap-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-835/newren/strmap-v6\nPull-Request: https://github.com/git/git/pull/835\n\nRange-diff vs v5:\n\n  1:  af6b6fcb46 =  1:  af6b6fcb46 hashmap: add usage documentation explaining hashmap_free[_entries]()\n  2:  591161fd78 =  2:  591161fd78 hashmap: adjust spacing to fix argument alignment\n  3:  f2718d036d =  3:  f2718d036d hashmap: allow re-use after hashmap_free()\n  4:  61f1da3c51 =  4:  61f1da3c51 hashmap: introduce a new hashmap_partial_clear()\n  5:  861e8d65ae =  5:  861e8d65ae hashmap: provide deallocation function names\n  6:  448d3b219f =  6:  448d3b219f strmap: new utility functions\n  7:  5e8004c728 =  7:  5e8004c728 strmap: add more utility functions\n  8:  fd96e9fc8d =  8:  fd96e9fc8d strmap: enable faster clearing and reusing of strmaps\n  9:  f499934f54 =  9:  f499934f54 strmap: add functions facilitating use as a string->int map\n 10:  3bcceb8cdb = 10:  3bcceb8cdb strmap: split create_entry() out of strmap_put()\n 11:  e128a71fec = 11:  e128a71fec strmap: add a strset sub-type\n 12:  34f542d9dd ! 12:  3926c4c97b strmap: enable allocations to come from a mem_pool\n     @@ strmap.h\n       \n       #include \"hashmap.h\"\n       \n     -+struct mempool;\n     ++struct mem_pool;\n       struct strmap {\n       \tstruct hashmap map;\n      +\tstruct mem_pool *pool;\n 13:  39ec2fa411 = 13:  562595224b strmap: take advantage of FLEXPTR_ALLOC_STR when relevant\n 14:  d3713d88f2 = 14:  058e7a6b76 Use new HASHMAP_INIT macro to simplify hashmap initialization\n 15:  24e5ce60f5 = 15:  9b494c26c1 shortlog: use strset from strmap.h\n\n-- \ngitgitgadget\n"},{"id":"409712","messageId":"20201111200701.GB39046@coredump.intra.peff.net","threadId":"54087","inReplyTo":"pull.835.v6.git.git.1605124942.gitgitgadget@gmail.com","subject":"Re: [PATCH v6 00/15] Add struct strmap and associated utility functions","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-11T20:07:01Z","receivedAt":"2020-11-11T20:07:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 11, 2020 at 08:02:06PM +0000, Elijah Newren via GitGitGadget wrote:\n\n> Here I introduce new strmap, strintmap, and strset types.\n> \n> Changes since v5:\n> \n>  * Fixed a typo in forward declaration of struct mem_pool, spotted by\n>    Phillip. (Usage via pointers meant gcc & clang wouldn't complain.)\n\nYep, this version looks good to me. I think this is probably ready for\n'next'.\n\n-Peff\n"},{"id":"409717","messageId":"CAPx1GvfXMnhgQDan7N=Y4WWdcS6p6WrekYkDyfx4nwnDtSuMQQ@mail.gmail.com","threadId":"54087","inReplyTo":"20201111190121.GE9902@coredump.intra.peff.net","subject":"Re: [PATCH v5 12/15] strmap: enable allocations to come from a mem_pool","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2020-11-11T20:34:59Z","receivedAt":"2020-11-11T20:35:15Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Wed, Nov 11, 2020 at 11:02 AM Jeff King <peff@peff.net> wrote:\n> Even if we're only using a pointer to it, we still need a valid forward\n> declaration [in a function, but not in a struct definition] ...\n> I'm not sure whether this is a seldom-seen corner of the C standard, or\n> a compiler-specific thing (though both clang and gcc seem to allow it).\n\nIt is standard (and not all *that* seldom-seen, in that many compilers\nhave warnings when you put these in function prototype scope).  The\nforward declaration is implicit, and occurs *in the current scope*.  The\ntrick is figuring out what the scope is.  At file level, the scope is \"level\nzero\" as it were: file scope.  So it's as global as we get (C separates\n\"globality\" into scope and linkage; *variables* have linkagebut *type\nnames* do not).  Function prototypes, however, have function-prototype\nscope, which ends at the end of the function declaration.\n\nOverall, though, I agree that the nicest style is to have an explicit\nforward declaration (in C that is—note that in C++, struct is just a\nclass with everything public, and class definitions have scope!).\n\nChris\n"}]}