From be9e6893e5063e803a0cfa7da44f902c7f0ba708 Mon Sep 17 00:00:00 2001
From: Bill Currie <bill@taniwha.org>
Date: Sat, 4 Mar 2023 02:07:31 +0900
Subject: [PATCH] [util] Free up hash links

Finally, hash links can be freed when the hash context is no longer
relevant. The context is created automatically when needed, and the
owner can delete the context when its done with the relevant hash
tables.
---
 include/QF/hash.h            |  2 +
 libs/util/hash.c             | 71 +++++++++++++++++++++++++++---------
 ruamoko/qwaq/builtins/main.c |  3 ++
 3 files changed, 58 insertions(+), 18 deletions(-)

diff --git a/include/QF/hash.h b/include/QF/hash.h
index 8faa44aa5..df9773042 100644
--- a/include/QF/hash.h
+++ b/include/QF/hash.h
@@ -93,6 +93,8 @@ void Hash_SetHashCompare (hashtab_t *tab, uintptr_t (*gh)(const void*,void*),
 */
 void Hash_DelTable (hashtab_t *tab);
 
+void Hash_DelContext (hashctx_t *hashctx);
+
 /** clean out all the entries from a hash table, starting over again.
 	\param tab	the table to be cleared
 */
diff --git a/libs/util/hash.c b/libs/util/hash.c
index 14f41623f..d88f71aec 100644
--- a/libs/util/hash.c
+++ b/libs/util/hash.c
@@ -51,6 +51,15 @@ typedef struct hashlink_s {
 	void *data;
 } hashlink_t;
 
+typedef struct hlinkset_s {
+	struct hlinkset_s *next;
+} hlinkset_t;
+
+struct hashctx_s {
+	hlinkset_t *linksets;
+	hashlink_t *free_hashlinks;
+};
+
 struct hashtab_s {
 	size_t tab_size;
 	unsigned int size_bits;
@@ -60,38 +69,41 @@ struct hashtab_s {
 	uintptr_t (*get_hash)(const void*,void*);
 	const char *(*get_key)(const void*,void*);
 	void (*free_ele)(void*,void*);
-	hashlink_t **hashlink_freelist;
+	hashctx_t  *hashctx;
 	hashlink_t *tab[1];             // variable size
 };
 
 static hashlink_t *
-new_hashlink (hashlink_t **free_hashlinks)
+new_hashlink (hashctx_t *hctx)
 {
 	hashlink_t *link;
 
-	if (!*free_hashlinks) {
+	if (!hctx->free_hashlinks) {
 		int		i;
 
-		if (!(*free_hashlinks = calloc (1024, sizeof (hashlink_t))))
-			return 0;
-		for (i = 0, link = *free_hashlinks; i < 1023; i++, link++)
+		hlinkset_t *linkset = malloc (sizeof (hlinkset_t)
+									  + 1024 * sizeof (hashlink_t));
+		linkset->next = hctx->linksets;
+		hctx->linksets = linkset;
+		hctx->free_hashlinks = (hashlink_t *) &linkset[1];
+		for (i = 0, link = hctx->free_hashlinks; i < 1023; i++, link++)
 			link->next = link + 1;
 		link->next = 0;
 	}
-	link = *free_hashlinks;
-	*free_hashlinks = link->next;
+	link = hctx->free_hashlinks;
+	hctx->free_hashlinks = link->next;
 	link->next = 0;
 	return link;
 }
 
 static void
-free_hashlink (hashlink_t *link, hashlink_t **free_hashlinks)
+free_hashlink (hashlink_t *link, hashctx_t *hctx)
 {
-	link->next = *free_hashlinks;
-	*free_hashlinks = link;
+	link->next = hctx->free_hashlinks;
+	hctx->free_hashlinks = link;
 }
 
-static hashlink_t *default_hashlink_freelist;
+static hashctx_t *default_hashctx;
 
 VISIBLE uintptr_t
 Hash_String (const char *str)
@@ -208,6 +220,23 @@ get_index (uintptr_t hash, size_t size, size_t bits)
 #endif
 }
 
+static void
+hash_shutdown (void *data)
+{
+	Hash_DelContext (default_hashctx);
+}
+
+VISIBLE void
+Hash_DelContext (hashctx_t *hashctx)
+{
+	while (hashctx->linksets) {
+		hlinkset_t *l = hashctx->linksets->next;
+		free (hashctx->linksets);
+		hashctx->linksets = l;
+	}
+	free (hashctx);
+}
+
 VISIBLE hashtab_t *
 Hash_NewTable (int tsize, const char *(*gk)(const void*,void*),
 			   void (*f)(void*,void*), void *ud, hashctx_t **hctx)
@@ -220,9 +249,15 @@ Hash_NewTable (int tsize, const char *(*gk)(const void*,void*),
 	tab->get_key = gk;
 	tab->free_ele = f;
 	if (!hctx) {
-		hctx = (hashctx_t **) &default_hashlink_freelist;
+		hctx = &default_hashctx;
+		if (!default_hashctx) {
+			Sys_RegisterShutdown (hash_shutdown, 0);
+		}
 	}
-	tab->hashlink_freelist = (hashlink_t **) hctx;
+	if (!*hctx) {
+		*hctx = calloc (1, sizeof (hashctx_t));
+	}
+	tab->hashctx = *hctx;
 
 	while (tsize) {
 		tab->size_bits++;
@@ -259,7 +294,7 @@ Hash_FlushTable (hashtab_t *tab)
 			hashlink_t *t = tab->tab[i]->next;
 			void *data = tab->tab[i]->data;
 
-			free_hashlink (tab->tab[i], tab->hashlink_freelist);
+			free_hashlink (tab->tab[i], tab->hashctx);
 			tab->tab[i] = t;
 			if (tab->free_ele)
 				tab->free_ele (data, tab->user_data);
@@ -272,7 +307,7 @@ static int
 hash_add_element (hashtab_t *tab, uintptr_t h, void *ele)
 {
 	size_t ind = get_index (h, tab->tab_size, tab->size_bits);
-	hashlink_t *lnk = new_hashlink (tab->hashlink_freelist);
+	hashlink_t *lnk = new_hashlink (tab->hashctx);
 
 	if (!lnk)
 		return -1;
@@ -415,7 +450,7 @@ Hash_Del (hashtab_t *tab, const char *key)
 			if (lnk->next)
 				lnk->next->prev = lnk->prev;
 			*lnk->prev = lnk->next;
-			free_hashlink (lnk, tab->hashlink_freelist);
+			free_hashlink (lnk, tab->hashctx);
 			tab->num_ele--;
 			return data;
 		}
@@ -438,7 +473,7 @@ Hash_DelElement (hashtab_t *tab, void *ele)
 			if (lnk->next)
 				lnk->next->prev = lnk->prev;
 			*lnk->prev = lnk->next;
-			free_hashlink (lnk, tab->hashlink_freelist);
+			free_hashlink (lnk, tab->hashctx);
 			tab->num_ele--;
 			return data;
 		}
diff --git a/ruamoko/qwaq/builtins/main.c b/ruamoko/qwaq/builtins/main.c
index 2a4a98b76..2533ab677 100644
--- a/ruamoko/qwaq/builtins/main.c
+++ b/ruamoko/qwaq/builtins/main.c
@@ -42,6 +42,7 @@
 #include "QF/cmd.h"
 #include "QF/cvar.h"
 #include "QF/gib.h"
+#include "QF/hash.h"
 #include "QF/idparse.h"
 #include "QF/keys.h"
 #include "QF/progs.h"
@@ -320,6 +321,8 @@ run_progs (void *data)
 	PR_Shutdown (thread->pr);
 	free (thread->pr);
 	thread->pr = 0;
+	Hash_DelContext (thread->hashctx);
+	thread->hashctx = 0;
 	return thread;
 }