diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13942231..50099857 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ jobs: - name: Configure and build run: scripts/local-build.sh working-directory: ${{ github.workspace }} + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-darwin: runs-on: macos-latest timeout-minutes: 40 @@ -34,6 +36,8 @@ jobs: - name: Configure and build run: scripts/local-build.sh working-directory: ${{ github.workspace }} + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-windows: runs-on: windows-latest timeout-minutes: 40 @@ -45,6 +49,8 @@ jobs: run: scripts/local-build.sh shell: bash # Specify bash so we can reuse the build script on Windows (runs on Git bash) working-directory: ${{ github.workspace }} + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-uwp: runs-on: windows-latest timeout-minutes: 40 diff --git a/src/arm/linux/init.c b/src/arm/linux/init.c index 48c9f4a0..cb3170f2 100644 --- a/src/arm/linux/init.c +++ b/src/arm/linux/init.c @@ -1001,3 +1001,43 @@ void cpuinfo_arm_linux_init(void) { 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 76224b9d..9a9fb427 100644 --- a/src/arm/mach/init.c +++ b/src/arm/mach/init.c @@ -873,3 +873,32 @@ void cpuinfo_arm_mach_init(void) { 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 815ecb77..2e44f8aa 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 @@ bool cpu_info_init_by_logical_sys_info(const struct woa_chip_info* chip_info, co 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/arm/windows/init.c b/src/arm/windows/init.c index 528ae327..fcf219df 100644 --- a/src/arm/windows/init.c +++ b/src/arm/windows/init.c @@ -25,12 +25,18 @@ BOOL CALLBACK cpuinfo_arm_windows_init(PINIT_ONCE init_once, PVOID parameter, PV set_cpuinfo_isa_fields(); chip_info = get_system_info_from_registry(); + const bool chip_info_from_registry = chip_info != NULL; if (chip_info == NULL) { chip_info = &woa_chip_unknown; } cpuinfo_is_initialized = cpu_info_init_by_logical_sys_info(chip_info, chip_info->uarchs[0].vendor); + if (chip_info_from_registry) { + HeapFree(GetProcessHeap(), 0, chip_info->chip_name_string); + HeapFree(GetProcessHeap(), 0, chip_info); + } + return true; } @@ -177,6 +183,7 @@ static struct woa_chip_info* get_system_info_from_registry(void) { uint64_t midr_qword = (uint32_t)read_registry_qword(cpu0_subkey, chip_midr_value); if (midr_qword == 0) { cpuinfo_log_error("Registry read error for MIDR value"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // MIDR is only 32 bits, so we need to cast it to uint32_t @@ -187,6 +194,7 @@ static struct woa_chip_info* get_system_info_from_registry(void) { uint64_t frequency_mhz = read_registry_dword(cpu0_subkey, chip_mhz_value); if (frequency_mhz == 0) { cpuinfo_log_error("Registry read error for frequency value"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // Convert MHz to Hz @@ -196,11 +204,13 @@ static struct woa_chip_info* get_system_info_from_registry(void) { chip_info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct woa_chip_info)); if (chip_info == NULL) { cpuinfo_log_error("Heap allocation error for chip_info"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // set chip_info fields chip_info->chip_name_string = wcsndup(text_buffer, CPUINFO_PACKAGE_NAME_MAX - 1); + HeapFree(GetProcessHeap(), 0, text_buffer); chip_info->uarchs[0] = get_core_info_from_midr(midr_value, frequency_hz); cpuinfo_log_debug("detected chip model name: %ls", chip_info->chip_name_string); diff --git a/src/cpuinfo/internal-api.h b/src/cpuinfo/internal-api.h index d84b26a8..9c158887 100644 --- a/src/cpuinfo/internal-api.h +++ b/src/cpuinfo/internal-api.h @@ -45,6 +45,9 @@ extern CPUINFO_INTERNAL struct cpuinfo_uarch_info cpuinfo_global_uarch; 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_arm_linux_init(void); 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 c2393243..6c8210f2 100644 --- a/src/emscripten/init.c +++ b/src/emscripten/init.c @@ -286,3 +286,35 @@ void cpuinfo_emscripten_init(void) { 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 81d5721c..e3cd9217 100644 --- a/src/init.c +++ b/src/init.c @@ -13,55 +13,98 @@ #endif #if defined(_WIN32) || defined(__CYGWIN__) -static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; +static SRWLOCK cpuinfo_lifecycle_lock = SRWLOCK_INIT; +#define CPUINFO_LIFECYCLE_LOCK() AcquireSRWLockExclusive(&cpuinfo_lifecycle_lock) +#define CPUINFO_LIFECYCLE_UNLOCK() ReleaseSRWLockExclusive(&cpuinfo_lifecycle_lock) #elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) -static pthread_once_t init_guard = PTHREAD_ONCE_INIT; +static pthread_mutex_t cpuinfo_lifecycle_lock = PTHREAD_MUTEX_INITIALIZER; +#define CPUINFO_LIFECYCLE_LOCK() pthread_mutex_lock(&cpuinfo_lifecycle_lock) +#define CPUINFO_LIFECYCLE_UNLOCK() pthread_mutex_unlock(&cpuinfo_lifecycle_lock) #else -static bool init_guard = false; +/* Single-threaded build (e.g. Emscripten without pthreads): no lock needed. */ +#define CPUINFO_LIFECYCLE_LOCK() \ + do { \ + } while (0) +#define CPUINFO_LIFECYCLE_UNLOCK() \ + do { \ + } while (0) #endif +static unsigned int cpuinfo_init_refcount = 0; bool CPUINFO_ABI cpuinfo_initialize(void) { + CPUINFO_LIFECYCLE_LOCK(); + if (cpuinfo_init_refcount == 0) { #if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64 #if defined(__MACH__) && defined(__APPLE__) - pthread_once(&init_guard, &cpuinfo_x86_mach_init); + cpuinfo_x86_mach_init(); #elif defined(__FreeBSD__) - pthread_once(&init_guard, &cpuinfo_x86_freebsd_init); + cpuinfo_x86_freebsd_init(); #elif defined(__linux__) - pthread_once(&init_guard, &cpuinfo_x86_linux_init); + cpuinfo_x86_linux_init(); #elif defined(_WIN32) || defined(__CYGWIN__) - InitOnceExecuteOnce(&init_guard, &cpuinfo_x86_windows_init, NULL, NULL); + cpuinfo_x86_windows_init(NULL, NULL, NULL); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 #if defined(__linux__) - pthread_once(&init_guard, &cpuinfo_arm_linux_init); + cpuinfo_arm_linux_init(); #elif defined(__MACH__) && defined(__APPLE__) - pthread_once(&init_guard, &cpuinfo_arm_mach_init); + cpuinfo_arm_mach_init(); #elif defined(_WIN32) - InitOnceExecuteOnce(&init_guard, &cpuinfo_arm_windows_init, NULL, NULL); + cpuinfo_arm_windows_init(NULL, NULL, NULL); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 #if defined(__linux__) - pthread_once(&init_guard, &cpuinfo_riscv_linux_init); + cpuinfo_riscv_linux_init(); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD -#if defined(__EMSCRIPTEN_PTHREADS__) - pthread_once(&init_guard, &cpuinfo_emscripten_init); -#else - if (!init_guard) { cpuinfo_emscripten_init(); - } - init_guard = true; -#endif #else - cpuinfo_log_error("processor architecture is not supported in cpuinfo"); + cpuinfo_log_error("processor architecture is not supported in cpuinfo"); #endif - return cpuinfo_is_initialized; + } + if (cpuinfo_is_initialized) { + cpuinfo_init_refcount++; + } + const bool initialized = cpuinfo_is_initialized; + CPUINFO_LIFECYCLE_UNLOCK(); + return initialized; } -void CPUINFO_ABI cpuinfo_deinitialize(void) {} +void CPUINFO_ABI cpuinfo_deinitialize(void) { + CPUINFO_LIFECYCLE_LOCK(); + if (cpuinfo_init_refcount != 0 && --cpuinfo_init_refcount == 0) { + cpuinfo_is_initialized = false; +#if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64 +#if defined(__MACH__) && defined(__APPLE__) + cpuinfo_x86_mach_deinit(); +#elif defined(__FreeBSD__) + cpuinfo_x86_freebsd_deinit(); +#elif defined(__linux__) + cpuinfo_x86_linux_deinit(); +#elif defined(_WIN32) || defined(__CYGWIN__) + cpuinfo_x86_windows_deinit(NULL, NULL, NULL); +#endif +#elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 +#if defined(__linux__) + cpuinfo_arm_linux_deinit(); +#elif defined(__MACH__) && defined(__APPLE__) + cpuinfo_arm_mach_deinit(); +#elif defined(_WIN32) + cpuinfo_arm_windows_deinit(NULL, NULL, NULL); +#endif +#elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if defined(__linux__) + cpuinfo_riscv_linux_deinit(); +#endif +#elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD + cpuinfo_emscripten_deinit(); +#endif + } + CPUINFO_LIFECYCLE_UNLOCK(); +} diff --git a/src/riscv/linux/init.c b/src/riscv/linux/init.c index 45168c7e..3da8c3ff 100644 --- a/src/riscv/linux/init.c +++ b/src/riscv/linux/init.c @@ -618,3 +618,36 @@ void cpuinfo_riscv_linux_init(void) { 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 797fa24b..ba30a5d9 100644 --- a/src/x86/freebsd/init.c +++ b/src/x86/freebsd/init.c @@ -396,3 +396,30 @@ void cpuinfo_x86_freebsd_init(void) { 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 d2b2d475..62e1187e 100644 --- a/src/x86/linux/init.c +++ b/src/x86/linux/init.c @@ -676,3 +676,38 @@ void cpuinfo_x86_linux_init(void) { 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 b8ea6047..9009e830 100644 --- a/src/x86/mach/init.c +++ b/src/x86/mach/init.c @@ -378,3 +378,30 @@ void cpuinfo_x86_mach_init(void) { 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 7380bb46..f1401beb 100644 --- a/src/x86/windows/init.c +++ b/src/x86/windows/init.c @@ -665,5 +665,39 @@ BOOL CALLBACK cpuinfo_x86_windows_init(PINIT_ONCE init_once, PVOID parameter, PV 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; } diff --git a/test/init.cc b/test/init.cc index a6128e35..899563bb 100644 --- a/test/init.cc +++ b/test/init.cc @@ -2,6 +2,48 @@ #include +#include +#include +#include +#include +#include +#include + +namespace { + +class ThreadBarrier { + public: + explicit ThreadBarrier(size_t participant_count) + : participant_count_(participant_count), remaining_(participant_count), generation_(0) {} + + void Wait() { + std::unique_lock lock(mutex_); + const size_t generation = generation_; + if (--remaining_ == 0) { + generation_++; + remaining_ = participant_count_; + condition_.notify_all(); + return; + } + + condition_.wait(lock, [this, generation]() { return generation_ != generation; }); + } + + private: + const size_t participant_count_; + size_t remaining_; + size_t generation_; + std::mutex mutex_; + std::condition_variable condition_; +}; + +bool HasValidCpuinfoState() { + return cpuinfo_get_processors_count() != 0 && cpuinfo_get_processors() != nullptr && + cpuinfo_get_processor(0) != nullptr; +} + +} // namespace + TEST(PROCESSORS_COUNT, non_zero) { ASSERT_TRUE(cpuinfo_initialize()); EXPECT_NE(0, cpuinfo_get_processors_count()); @@ -1519,3 +1561,166 @@ TEST(L4_CACHE, consistent_processors) { } cpuinfo_deinitialize(); } + +TEST(INIT_REFCOUNT, deinitialize_balances_initialize) { + ASSERT_TRUE(cpuinfo_initialize()); + ASSERT_TRUE(cpuinfo_initialize()); + + const uint32_t processors_count = cpuinfo_get_processors_count(); + EXPECT_NE(0, processors_count); + + cpuinfo_deinitialize(); + EXPECT_EQ(processors_count, cpuinfo_get_processors_count()); + EXPECT_TRUE(cpuinfo_get_processors()); + + cpuinfo_deinitialize(); +} + +TEST(INIT_REFCOUNT, extra_deinitialize_does_not_underflow) { + cpuinfo_deinitialize(); + cpuinfo_deinitialize(); + + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); + cpuinfo_deinitialize(); + + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); +} + +TEST(INIT_REINITIALIZE, repeated_cycles_restore_valid_state) { + constexpr size_t kCycles = 25; + + for (size_t cycle = 0; cycle < kCycles; cycle++) { + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); + } +} + +TEST(INIT_CONCURRENCY, simultaneous_consumers_share_lifecycle) { + constexpr size_t kThreadCount = 12; + constexpr size_t kRounds = 25; + + ThreadBarrier initialized_barrier(kThreadCount); + ThreadBarrier deinitialized_barrier(kThreadCount); + std::atomic failures{0}; + + const auto consumer = [&]() { + for (size_t round = 0; round < kRounds; round++) { + const bool initialized = cpuinfo_initialize(); + if (!initialized) { + failures.fetch_add(1, std::memory_order_relaxed); + } + + initialized_barrier.Wait(); + if (initialized && !HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } + if (initialized) { + cpuinfo_deinitialize(); + } + deinitialized_barrier.Wait(); + } + }; + + std::vector consumers; + consumers.reserve(kThreadCount); + for (size_t thread = 0; thread < kThreadCount; thread++) { + consumers.emplace_back(consumer); + } + for (std::thread& consumer_thread : consumers) { + consumer_thread.join(); + } + + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); +} + +TEST(INIT_CONCURRENCY, long_lived_consumers_survive_concurrent_churn) { + constexpr size_t kHolderThreadCount = 4; + constexpr size_t kChurnThreadCount = 8; + constexpr size_t kChurnIterations = 2000; + + std::atomic stop_holders{false}; + std::atomic holders_ready{0}; + std::atomic holder_references{0}; + std::atomic failures{0}; + + const auto holder = [&]() { + const bool initialized = cpuinfo_initialize(); + if (initialized) { + holder_references.fetch_add(1, std::memory_order_relaxed); + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } + } else { + failures.fetch_add(1, std::memory_order_relaxed); + } + holders_ready.fetch_add(1, std::memory_order_release); + + if (!initialized) { + return; + } + + while (!stop_holders.load(std::memory_order_acquire)) { + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + break; + } + std::this_thread::yield(); + } + + cpuinfo_deinitialize(); + }; + const auto churn = [&]() { + for (size_t iteration = 0; iteration < kChurnIterations; iteration++) { + if (!cpuinfo_initialize()) { + failures.fetch_add(1, std::memory_order_relaxed); + continue; + } + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } + cpuinfo_deinitialize(); + } + }; + + std::vector holder_threads; + holder_threads.reserve(kHolderThreadCount); + for (size_t thread = 0; thread < kHolderThreadCount; thread++) { + holder_threads.emplace_back(holder); + } + while (holders_ready.load(std::memory_order_acquire) != kHolderThreadCount) { + std::this_thread::yield(); + } + + std::vector churn_threads; + churn_threads.reserve(kChurnThreadCount); + for (size_t thread = 0; thread < kChurnThreadCount; thread++) { + churn_threads.emplace_back(churn); + } + for (std::thread& thread : churn_threads) { + thread.join(); + } + + EXPECT_EQ(kHolderThreadCount, holder_references.load(std::memory_order_relaxed)); + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); + if (holder_references.load(std::memory_order_relaxed) != 0) { + EXPECT_TRUE(HasValidCpuinfoState()); + } + + stop_holders.store(true, std::memory_order_release); + for (std::thread& thread : holder_threads) { + thread.join(); + } + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); + + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); +}