Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@ 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 }}
cmake-windows-deinit-tests:
runs-on: windows-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
- name: Install ninja
run: choco install ninja
- name: Configure and build with deinit enabled
run: scripts/local-build.sh -DCPUINFO_ENABLE_DEINIT=ON
shell: bash
working-directory: ${{ github.workspace }}
- name: Run init-test
run: ctest --output-on-failure -R init-test
working-directory: ${{ github.workspace }}/build/local
cmake-uwp:
runs-on: windows-latest
timeout-minutes: 40
Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ OPTION(CPUINFO_BUILD_UNIT_TESTS "Build cpuinfo unit tests" ON)
OPTION(CPUINFO_BUILD_MOCK_TESTS "Build cpuinfo mock tests" ON)
OPTION(CPUINFO_BUILD_BENCHMARKS "Build cpuinfo micro-benchmarks" ON)
OPTION(CPUINFO_BUILD_PKG_CONFIG "Build pkg-config manifest" ON)
OPTION(CPUINFO_ENABLE_DEINIT "Release cpuinfo global state on cpuinfo_deinitialize()" OFF)
OPTION(USE_SYSTEM_LIBS "Use system libraries instead of downloading and building them" OFF)
OPTION(USE_SYSTEM_GOOGLEBENCHMARK "Use system Google Benchmark library instead of downloading and building it" ${USE_SYSTEM_LIBS})
OPTION(USE_SYSTEM_GOOGLETEST "Use system Google Test library instead of downloading and building it" ${USE_SYSTEM_LIBS})
Expand Down Expand Up @@ -281,6 +282,8 @@ TARGET_INCLUDE_DIRECTORIES(cpuinfo BEFORE PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRE
TARGET_INCLUDE_DIRECTORIES(cpuinfo BEFORE PRIVATE src)
TARGET_INCLUDE_DIRECTORIES(cpuinfo_internals BEFORE PUBLIC include src)
TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE "CPUINFO_LOG_TO_STDIO=$<BOOL:${CPUINFO_LOG_TO_STDIO}>")
TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE "CPUINFO_ENABLE_DEINIT=$<BOOL:${CPUINFO_ENABLE_DEINIT}>")
TARGET_COMPILE_DEFINITIONS(cpuinfo_internals PRIVATE "CPUINFO_ENABLE_DEINIT=$<BOOL:${CPUINFO_ENABLE_DEINIT}>")
IF(CPUINFO_LOG_LEVEL STREQUAL "default")
# default logging level: error (subject to change)
TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE "CPUINFO_LOG_LEVEL=2")
Expand Down Expand Up @@ -812,6 +815,7 @@ IF(CPUINFO_SUPPORTED_PLATFORM AND CPUINFO_BUILD_UNIT_TESTS)
ADD_EXECUTABLE(init-test test/init.cc)
CPUINFO_TARGET_ENABLE_CXX11(init-test)
CPUINFO_TARGET_RUNTIME_LIBRARY(init-test)
TARGET_COMPILE_DEFINITIONS(init-test PRIVATE "CPUINFO_ENABLE_DEINIT=$<BOOL:${CPUINFO_ENABLE_DEINIT}>")
TARGET_LINK_LIBRARIES(init-test PRIVATE cpuinfo gtest gtest_main)
ADD_TEST(NAME init-test COMMAND init-test)

Expand Down
118 changes: 87 additions & 31 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
#include "TargetConditionals.h"
#endif

#ifndef CPUINFO_ENABLE_DEINIT
#define CPUINFO_ENABLE_DEINIT 0
#endif
Comment thread
crvineeth97 marked this conversation as resolved.

#if !CPUINFO_ENABLE_DEINIT

#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) {
Expand Down Expand Up @@ -67,52 +70,105 @@ bool CPUINFO_ABI cpuinfo_initialize(void) {
return cpuinfo_is_initialized;
}

void CPUINFO_ABI cpuinfo_deinitialize(void) {
if (!cpuinfo_is_initialized) {
return;
}
cpuinfo_is_initialized = false;
void CPUINFO_ABI cpuinfo_deinitialize(void) {}

#else /* CPUINFO_ENABLE_DEINIT */

#if defined(_WIN32) || defined(__CYGWIN__)
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_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
/* 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(&deinit_guard, &cpuinfo_x86_mach_deinit);
cpuinfo_x86_mach_init();
#elif defined(__FreeBSD__)
pthread_once(&deinit_guard, &cpuinfo_x86_freebsd_deinit);
cpuinfo_x86_freebsd_init();
#elif defined(__linux__)
pthread_once(&deinit_guard, &cpuinfo_x86_linux_deinit);
cpuinfo_x86_linux_init();
#elif defined(_WIN32) || defined(__CYGWIN__)
InitOnceExecuteOnce(&deinit_guard, &cpuinfo_x86_windows_deinit, NULL, NULL);
cpuinfo_x86_windows_init(NULL, NULL, NULL);
#else
cpuinfo_log_error("operating system is not supported in cpuinfo");
#endif
#elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
#if defined(__linux__)
pthread_once(&deinit_guard, &cpuinfo_arm_linux_deinit);
cpuinfo_arm_linux_init();
#elif defined(__MACH__) && defined(__APPLE__)
pthread_once(&deinit_guard, &cpuinfo_arm_mach_deinit);
cpuinfo_arm_mach_init();
#elif defined(_WIN32)
InitOnceExecuteOnce(&deinit_guard, &cpuinfo_arm_windows_deinit, NULL, NULL);
cpuinfo_arm_windows_init(NULL, NULL, NULL);
#else
cpuinfo_log_error("operating system is not supported in cpuinfo");
#endif
#elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64
#if defined(__linux__)
pthread_once(&deinit_guard, &cpuinfo_riscv_linux_deinit);
cpuinfo_riscv_linux_init();
#else
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(&deinit_guard, &cpuinfo_emscripten_deinit);
cpuinfo_emscripten_init();
#else
if (!deinit_guard) {
cpuinfo_emscripten_deinit();
cpuinfo_log_error("processor architecture is not supported in cpuinfo");
#endif
}
deinit_guard = true;
if (cpuinfo_is_initialized) {
cpuinfo_init_refcount++;
}
const bool initialized = cpuinfo_is_initialized;
CPUINFO_LIFECYCLE_UNLOCK();
return initialized;
}

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
/* 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;
#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();
}

#endif /* CPUINFO_ENABLE_DEINIT */
65 changes: 65 additions & 0 deletions test/init.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

#include <cpuinfo.h>

#ifndef CPUINFO_ENABLE_DEINIT
#define CPUINFO_ENABLE_DEINIT 0
#endif

#if CPUINFO_ENABLE_DEINIT
#include <atomic>
#include <thread>
#include <vector>
#endif

TEST(PROCESSORS_COUNT, non_zero) {
ASSERT_TRUE(cpuinfo_initialize());
EXPECT_NE(0, cpuinfo_get_processors_count());
Expand Down Expand Up @@ -1519,3 +1529,58 @@ TEST(L4_CACHE, consistent_processors) {
}
cpuinfo_deinitialize();
}

#if CPUINFO_ENABLE_DEINIT
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_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) {
constexpr int kChurnThreads = 10;
constexpr int kChurnIterations = 5000;

std::atomic<bool> stop_holder{false};

// Models a long lived consumer that keeps using cpuinfo for its whole lifetime
// If a concurrent deinitialize from another consumer (churn) frees the shared state
// then cpuinfo aborts the process, which fails the test
const auto holder = [&stop_holder]() {
cpuinfo_initialize();
while (!stop_holder.load(std::memory_order_relaxed)) {
(void)cpuinfo_get_processors();
(void)cpuinfo_get_processor(0);
}
cpuinfo_deinitialize();
};
const auto churn = [kChurnIterations]() {
for (int iteration = 0; iteration < kChurnIterations; iteration++) {
cpuinfo_initialize();
cpuinfo_deinitialize();
}
};

std::thread holder_thread(holder);
std::vector<std::thread> churn_threads;
churn_threads.reserve(kChurnThreads);
for (int t = 0; t < kChurnThreads; t++) {
churn_threads.emplace_back(churn);
}
for (std::thread& thread : churn_threads) {
thread.join();
}
stop_holder.store(true, std::memory_order_relaxed);
holder_thread.join();

// Reaching here without the process aborting means that lifecycle handling is correct
}
#endif // CPUINFO_ENABLE_DEINIT
Loading