Implement `cpuinfo_deinitialize()` to free heap-allocated globals (#387)

* Add support for freeing cpuinfo memory

* Add deinit tests

* Move the deinits to respective allocation routines

* Use compound literals for guard resets

---------

Co-authored-by: Vineeth Chelur <vchelur@microsoft.com>
diff --git a/src/arm/linux/init.c b/src/arm/linux/init.c
index 1bf0014..53e9a43 100644
--- a/src/arm/linux/init.c
+++ b/src/arm/linux/init.c
@@ -1001,3 +1001,43 @@
 	free(linux_cpu_to_core_map);
 	free(linux_cpu_to_uarch_index_map);
 }
+
+void cpuinfo_arm_linux_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	/* cpuinfo_packages points to static storage (&package) — do not free */
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	free(cpuinfo_uarchs);
+	cpuinfo_uarchs = NULL;
+	cpuinfo_uarchs_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		free(cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	free(cpuinfo_linux_cpu_to_processor_map);
+	cpuinfo_linux_cpu_to_processor_map = NULL;
+
+	free(cpuinfo_linux_cpu_to_core_map);
+	cpuinfo_linux_cpu_to_core_map = NULL;
+
+	free((void*)cpuinfo_linux_cpu_to_uarch_index_map);
+	cpuinfo_linux_cpu_to_uarch_index_map = NULL;
+
+	cpuinfo_linux_cpu_max = 0;
+}
diff --git a/src/arm/mach/init.c b/src/arm/mach/init.c
index 11de4ae..eac431f 100644
--- a/src/arm/mach/init.c
+++ b/src/arm/mach/init.c
@@ -755,3 +755,32 @@
 	free(l2);
 	free(l3);
 }
+
+void cpuinfo_arm_mach_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	free(cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	free(cpuinfo_uarchs);
+	cpuinfo_uarchs = NULL;
+	cpuinfo_uarchs_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		free(cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+}
diff --git a/src/arm/windows/init-by-logical-sys-info.c b/src/arm/windows/init-by-logical-sys-info.c
index 815ecb7..2e44f8a 100644
--- a/src/arm/windows/init-by-logical-sys-info.c
+++ b/src/arm/windows/init-by-logical-sys-info.c
@@ -372,6 +372,40 @@
 	return result;

 }

 

+BOOL CALLBACK cpuinfo_arm_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context) {

+	HANDLE heap = GetProcessHeap();

+

+	HeapFree(heap, 0, cpuinfo_processors);

+	cpuinfo_processors = NULL;

+	cpuinfo_processors_count = 0;

+

+	HeapFree(heap, 0, cpuinfo_packages);

+	cpuinfo_packages = NULL;

+	cpuinfo_packages_count = 0;

+

+	HeapFree(heap, 0, cpuinfo_clusters);

+	cpuinfo_clusters = NULL;

+	cpuinfo_clusters_count = 0;

+

+	HeapFree(heap, 0, cpuinfo_cores);

+	cpuinfo_cores = NULL;

+	cpuinfo_cores_count = 0;

+

+	HeapFree(heap, 0, cpuinfo_uarchs);

+	cpuinfo_uarchs = NULL;

+	cpuinfo_uarchs_count = 0;

+

+	/* Caches are allocated as a single contiguous block starting at l1i */

+	HeapFree(heap, 0, cpuinfo_cache[cpuinfo_cache_level_1i]);

+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {

+		cpuinfo_cache[lvl] = NULL;

+		cpuinfo_cache_count[lvl] = 0;

+	}

+	cpuinfo_max_cache_size = 0;

+

+	return TRUE;

+}

+

 static uint32_t count_logical_processors(const uint32_t max_group_count, uint32_t* global_proc_index_per_group) {

 	uint32_t nr_of_processors = 0;

 

diff --git a/src/cpuinfo/internal-api.h b/src/cpuinfo/internal-api.h
index d84b26a..9c15888 100644
--- a/src/cpuinfo/internal-api.h
+++ b/src/cpuinfo/internal-api.h
@@ -45,6 +45,9 @@
 extern CPUINFO_INTERNAL uint32_t cpuinfo_linux_cpu_max;
 extern CPUINFO_INTERNAL const struct cpuinfo_processor** cpuinfo_linux_cpu_to_processor_map;
 extern CPUINFO_INTERNAL const struct cpuinfo_core** cpuinfo_linux_cpu_to_core_map;
+#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64
+extern CPUINFO_INTERNAL const uint32_t* cpuinfo_linux_cpu_to_uarch_index_map;
+#endif
 #endif
 
 CPUINFO_PRIVATE void cpuinfo_x86_mach_init(void);
@@ -62,6 +65,23 @@
 CPUINFO_PRIVATE void cpuinfo_riscv_linux_init(void);
 CPUINFO_PRIVATE void cpuinfo_emscripten_init(void);
 
+/* Platform-specific deinitialization functions.
+ * Each frees the globals allocated by its corresponding init function. */
+CPUINFO_PRIVATE void cpuinfo_x86_mach_deinit(void);
+CPUINFO_PRIVATE void cpuinfo_x86_linux_deinit(void);
+CPUINFO_PRIVATE void cpuinfo_x86_freebsd_deinit(void);
+#if defined(_WIN32) || defined(__CYGWIN__)
+#if CPUINFO_ARCH_ARM64
+CPUINFO_PRIVATE BOOL CALLBACK cpuinfo_arm_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context);
+#else
+CPUINFO_PRIVATE BOOL CALLBACK cpuinfo_x86_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context);
+#endif
+#endif
+CPUINFO_PRIVATE void cpuinfo_arm_mach_deinit(void);
+CPUINFO_PRIVATE void cpuinfo_arm_linux_deinit(void);
+CPUINFO_PRIVATE void cpuinfo_riscv_linux_deinit(void);
+CPUINFO_PRIVATE void cpuinfo_emscripten_deinit(void);
+
 CPUINFO_PRIVATE uint32_t cpuinfo_compute_max_cache_size(const struct cpuinfo_processor* processor);
 
 typedef void (*cpuinfo_processor_callback)(uint32_t);
diff --git a/src/emscripten/init.c b/src/emscripten/init.c
index c239324..6c8210f 100644
--- a/src/emscripten/init.c
+++ b/src/emscripten/init.c
@@ -286,3 +286,35 @@
 	free(l1d);
 	free(l2);
 }
+
+void cpuinfo_emscripten_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	/* cpuinfo_packages points to static storage (&static_package) — do not free */
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	/* L1i, L1d, L2 are dynamically allocated; L3 may point to static
+	 * storage (&static_x86_l3) so only free levels that were calloc'd */
+	free(cpuinfo_cache[cpuinfo_cache_level_1i]);
+	free(cpuinfo_cache[cpuinfo_cache_level_1d]);
+	free(cpuinfo_cache[cpuinfo_cache_level_2]);
+	/* cpuinfo_cache[cpuinfo_cache_level_3] may be &static_x86_l3 — do not free */
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0};
+}
diff --git a/src/init.c b/src/init.c
index 81d5721..e71b186 100644
--- a/src/init.c
+++ b/src/init.c
@@ -14,10 +14,13 @@
 
 #if defined(_WIN32) || defined(__CYGWIN__)
 static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT;
+static INIT_ONCE deinit_guard = INIT_ONCE_STATIC_INIT;
 #elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)
 static pthread_once_t init_guard = PTHREAD_ONCE_INIT;
+static pthread_once_t deinit_guard = PTHREAD_ONCE_INIT;
 #else
 static bool init_guard = false;
+static bool deinit_guard = false;
 #endif
 
 bool CPUINFO_ABI cpuinfo_initialize(void) {
@@ -64,4 +67,52 @@
 	return cpuinfo_is_initialized;
 }
 
-void CPUINFO_ABI cpuinfo_deinitialize(void) {}
+void CPUINFO_ABI cpuinfo_deinitialize(void) {
+	if (!cpuinfo_is_initialized) {
+		return;
+	}
+	cpuinfo_is_initialized = false;
+#if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64
+#if defined(__MACH__) && defined(__APPLE__)
+	pthread_once(&deinit_guard, &cpuinfo_x86_mach_deinit);
+#elif defined(__FreeBSD__)
+	pthread_once(&deinit_guard, &cpuinfo_x86_freebsd_deinit);
+#elif defined(__linux__)
+	pthread_once(&deinit_guard, &cpuinfo_x86_linux_deinit);
+#elif defined(_WIN32) || defined(__CYGWIN__)
+	InitOnceExecuteOnce(&deinit_guard, &cpuinfo_x86_windows_deinit, NULL, NULL);
+#endif
+#elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
+#if defined(__linux__)
+	pthread_once(&deinit_guard, &cpuinfo_arm_linux_deinit);
+#elif defined(__MACH__) && defined(__APPLE__)
+	pthread_once(&deinit_guard, &cpuinfo_arm_mach_deinit);
+#elif defined(_WIN32)
+	InitOnceExecuteOnce(&deinit_guard, &cpuinfo_arm_windows_deinit, NULL, NULL);
+#endif
+#elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64
+#if defined(__linux__)
+	pthread_once(&deinit_guard, &cpuinfo_riscv_linux_deinit);
+#endif
+#elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD
+#if defined(__EMSCRIPTEN_PTHREADS__)
+	pthread_once(&deinit_guard, &cpuinfo_emscripten_deinit);
+#else
+	if (!deinit_guard) {
+		cpuinfo_emscripten_deinit();
+	}
+	deinit_guard = true;
+#endif
+#endif
+	/* Reset guards to allow re-initialization */
+#if defined(_WIN32) || defined(__CYGWIN__)
+	init_guard = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
+	deinit_guard = (INIT_ONCE)INIT_ONCE_STATIC_INIT;
+#elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__)
+	init_guard = (pthread_once_t)PTHREAD_ONCE_INIT;
+	deinit_guard = (pthread_once_t)PTHREAD_ONCE_INIT;
+#else
+	init_guard = false;
+	deinit_guard = false;
+#endif
+}
diff --git a/src/riscv/linux/init.c b/src/riscv/linux/init.c
index 45168c7..3da8c3f 100644
--- a/src/riscv/linux/init.c
+++ b/src/riscv/linux/init.c
@@ -618,3 +618,36 @@
 	free(linux_cpu_to_core_map);
 	free(linux_cpu_to_uarch_index_map);
 }
+
+void cpuinfo_riscv_linux_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	free(cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	free(cpuinfo_uarchs);
+	cpuinfo_uarchs = NULL;
+	cpuinfo_uarchs_count = 0;
+
+	free(cpuinfo_linux_cpu_to_processor_map);
+	cpuinfo_linux_cpu_to_processor_map = NULL;
+
+	free(cpuinfo_linux_cpu_to_core_map);
+	cpuinfo_linux_cpu_to_core_map = NULL;
+
+	free((void*)cpuinfo_linux_cpu_to_uarch_index_map);
+	cpuinfo_linux_cpu_to_uarch_index_map = NULL;
+
+	cpuinfo_linux_cpu_max = 0;
+}
diff --git a/src/x86/freebsd/init.c b/src/x86/freebsd/init.c
index 797fa24..ba30a5d 100644
--- a/src/x86/freebsd/init.c
+++ b/src/x86/freebsd/init.c
@@ -396,3 +396,30 @@
 	free(l3);
 	free(l4);
 }
+
+void cpuinfo_x86_freebsd_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	free(cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		free(cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0};
+}
diff --git a/src/x86/linux/init.c b/src/x86/linux/init.c
index d2b2d47..62e1187 100644
--- a/src/x86/linux/init.c
+++ b/src/x86/linux/init.c
@@ -676,3 +676,38 @@
 	free(linux_cpu_to_processor_map);
 	free(linux_cpu_to_core_map);
 }
+
+void cpuinfo_x86_linux_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	free(cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		free(cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	free(cpuinfo_linux_cpu_to_processor_map);
+	cpuinfo_linux_cpu_to_processor_map = NULL;
+
+	free(cpuinfo_linux_cpu_to_core_map);
+	cpuinfo_linux_cpu_to_core_map = NULL;
+
+	cpuinfo_linux_cpu_max = 0;
+
+	cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0};
+}
diff --git a/src/x86/mach/init.c b/src/x86/mach/init.c
index b8ea604..9009e83 100644
--- a/src/x86/mach/init.c
+++ b/src/x86/mach/init.c
@@ -378,3 +378,30 @@
 	free(l3);
 	free(l4);
 }
+
+void cpuinfo_x86_mach_deinit(void) {
+	free(cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	free(cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	free(cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	free(cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		free(cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0};
+}
diff --git a/src/x86/windows/init.c b/src/x86/windows/init.c
index d233280..7b97d11 100644
--- a/src/x86/windows/init.c
+++ b/src/x86/windows/init.c
@@ -667,5 +667,39 @@
 	if (l4 != NULL) {
 		HeapFree(heap, 0, l4);
 	}
+	if (processor_infos != NULL) {
+		HeapFree(heap, 0, processor_infos);
+	}
+	return TRUE;
+}
+
+BOOL CALLBACK cpuinfo_x86_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context) {
+	HANDLE heap = GetProcessHeap();
+
+	HeapFree(heap, 0, cpuinfo_processors);
+	cpuinfo_processors = NULL;
+	cpuinfo_processors_count = 0;
+
+	HeapFree(heap, 0, cpuinfo_cores);
+	cpuinfo_cores = NULL;
+	cpuinfo_cores_count = 0;
+
+	HeapFree(heap, 0, cpuinfo_clusters);
+	cpuinfo_clusters = NULL;
+	cpuinfo_clusters_count = 0;
+
+	HeapFree(heap, 0, cpuinfo_packages);
+	cpuinfo_packages = NULL;
+	cpuinfo_packages_count = 0;
+
+	for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) {
+		HeapFree(heap, 0, cpuinfo_cache[lvl]);
+		cpuinfo_cache[lvl] = NULL;
+		cpuinfo_cache_count[lvl] = 0;
+	}
+	cpuinfo_max_cache_size = 0;
+
+	cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0};
+
 	return TRUE;
 }