From 3081d5c83d4012086000b68b5dd8a23d2be135bb Mon Sep 17 00:00:00 2001 From: Maciej Plewka Date: Mon, 20 Jul 2026 12:03:23 +0200 Subject: [PATCH] Add Windows registry fallback for loader environment settings Signed-off-by: Maciej Plewka --- README.md | 42 +++++++++++++ source/inc/ze_util.h | 140 +++++++++++++++++++++++++++++-------------- test/CMakeLists.txt | 10 ++++ 3 files changed, 148 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 1d98f871..bdaaa48b 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,48 @@ cmake -G "NMake Makefiles" CMAKE_CXX_FLAGS="/EHsc" .. nmake ``` +# Configuring loader settings via the Windows registry + +On Windows, every loader environment variable (for example `ZEL_ENABLE_LOADER_LOGGING`, +`ZEL_LOADER_LOG_CONSOLE`, `ZEL_LIBRARY_PATH`, `ZE_ENABLE_VALIDATION_LAYER`, ...) can also be +supplied through the registry. This is useful when the L0 application is launched as a child +process of a GUI application that cannot conveniently pass environment variables to its +subprocesses. + +Values are read from the `Environment` subkey of the loader key: + +``` +HKEY_CURRENT_USER\Software\Intel\oneAPI\LevelZero\Environment +HKEY_LOCAL_MACHINE\Software\Intel\oneAPI\LevelZero\Environment +``` + +Each setting is a registry value whose **name matches the environment variable name** and whose +data is the value the variable would otherwise hold. Both `REG_SZ` (string) and `REG_DWORD` +(numeric) value types are supported — a `REG_DWORD` of `1` is equivalent to a `REG_SZ` of `"1"`. + +Lookup order (first match wins): + +1. The process environment variable (if set) — always takes precedence, so existing scripts and + CI setups are unaffected. +2. `HKEY_CURRENT_USER` (per-user, writable without administrator rights). +3. `HKEY_LOCAL_MACHINE` (machine-wide). + +Example — enable console logging at `trace` level for the current user with a `.reg` file: + +``` +Windows Registry Editor Version 5.00 + +[HKEY_CURRENT_USER\Software\Intel\oneAPI\LevelZero\Environment] +"ZEL_LOADER_LOG_CONSOLE"="1" +"ZEL_LOADER_LOGGING_LEVEL"="trace" +``` + +Or from the command line: + +```sh +reg add "HKCU\Software\Intel\oneAPI\LevelZero\Environment" /v ZEL_LOADER_LOG_CONSOLE /t REG_SZ /d 1 /f +``` + # Contributing diff --git a/source/inc/ze_util.h b/source/inc/ze_util.h index a51115b8..b7173026 100644 --- a/source/inc/ze_util.h +++ b/source/inc/ze_util.h @@ -111,7 +111,93 @@ inline std::string readLevelZeroLoaderLibraryPath() { return LoaderRegKeyPath; } + +// Reads a single Level Zero loader setting from the given registry root under +// the "Environment" subkey of the loader key. Value names match the +// corresponding environment variable names (e.g. "ZEL_LOADER_LOG_CONSOLE"). +// Supports REG_SZ / REG_EXPAND_SZ (used verbatim) and REG_DWORD (rendered as a +// decimal string) so that numeric flags can be stored either way. Returns true +// and populates 'value' when the named value exists and is of a supported type. +inline bool readLoaderSettingFromRegistryRoot(HKEY root, const char* name, std::string& value) { + static constexpr char settingsKeyPath[] = "Software\\Intel\\oneAPI\\LevelZero\\Environment"; + + HKEY regKey = {}; + if (ERROR_SUCCESS != RegOpenKeyExA(root, settingsKeyPath, 0, KEY_QUERY_VALUE, ®Key)) { + return false; + } + + DWORD regValueType = {}; + DWORD dataSize = {}; + bool found = false; + LSTATUS regOpStatus = RegQueryValueExA(regKey, name, NULL, ®ValueType, NULL, &dataSize); + + if (ERROR_SUCCESS == regOpStatus) { + if ((REG_SZ == regValueType || REG_EXPAND_SZ == regValueType) && dataSize > 0) { + std::string buffer(dataSize, '\0'); + regOpStatus = RegQueryValueExA(regKey, name, NULL, ®ValueType, + (LPBYTE) & *buffer.begin(), &dataSize); + if (ERROR_SUCCESS == regOpStatus) { + // dataSize includes the terminating null for string types; trim + // at the first null so 'value' holds a clean C-string. + value.assign(buffer.c_str()); + found = true; + } + } else if (REG_DWORD == regValueType && dataSize == sizeof(DWORD)) { + DWORD dwordValue = 0; + regOpStatus = RegQueryValueExA(regKey, name, NULL, ®ValueType, + (LPBYTE)&dwordValue, &dataSize); + if (ERROR_SUCCESS == regOpStatus) { + value.assign(std::to_string(dwordValue)); + found = true; + } + } + } + + RegCloseKey(regKey); + return found; +} + +// Fallback lookup for loader settings on Windows: checks the current user hive +// first (HKEY_CURRENT_USER, writable without administrator rights) and then the +// machine-wide hive (HKEY_LOCAL_MACHINE). This lets loader environment settings +// be configured persistently in the registry for processes (e.g. GUI apps and +// their child processes) that cannot conveniently inherit them from the shell. +inline bool readLoaderSettingFromRegistry(const char* name, std::string& value) { + if (readLoaderSettingFromRegistryRoot(HKEY_CURRENT_USER, name, value)) { + return true; + } + return readLoaderSettingFromRegistryRoot(HKEY_LOCAL_MACHINE, name, value); +} +#endif + +// Looks up a loader setting by name. The process environment always takes +// precedence; on Windows, if the variable is not present in the environment the +// registry fallback (see readLoaderSettingFromRegistry) is consulted. Returns +// true and populates 'value' when a value is found from either source. +inline bool getenv_raw(const char* name, std::string& value) { +#if defined(_WIN32) + // Query the required buffer size (includes the terminating null); a return + // of 0 means the variable is not set in the environment. + DWORD required = GetEnvironmentVariableA(name, nullptr, 0); + if (0 != required) { + std::string buffer(required, '\0'); + DWORD written = GetEnvironmentVariableA(name, &buffer[0], required); + if (0 != written && written < required) { + buffer.resize(written); + value.assign(std::move(buffer)); + return true; + } + } + return readLoaderSettingFromRegistry(name, value); +#else + const char* env = getenv(name); + if (nullptr == env) { + return false; + } + value.assign(env); + return true; #endif +} ////////////////////////////////////////////////////////////////////////// #if !defined(_WIN32) && (__GNUC__ >= 4) @@ -131,21 +217,10 @@ typedef struct _cl_program* cl_program; /////////////////////////////////////////////////////////////////////////////// inline bool getenv_tobool( const char* name ) { - const char* env = nullptr; - -#if defined(_WIN32) - char buffer[8]; - auto rc = GetEnvironmentVariable(name, buffer, 8); - if (0 != rc && rc <= 8) { - env = buffer; - } -#else - env = getenv(name); -#endif - - if( ( nullptr == env ) || ( 0 == strcmp( "0", env ) ) ) + std::string value; + if( !getenv_raw( name, value ) || ( "0" == value ) ) return false; - return ( 0 == strcmp( "1", env ) ); + return ( "1" == value ); } // Returns the numeric mode (0, 1, or 2) for env vars that support a two-level @@ -153,43 +228,20 @@ inline bool getenv_tobool( const char* name ) // Any value other than "1" or "2" is treated as 0. inline uint32_t getenv_tomode( const char* name ) { - const char* env = nullptr; - -#if defined(_WIN32) - char buffer[8]; - auto rc = GetEnvironmentVariable(name, buffer, 8); - if (0 != rc && rc <= 8) { - env = buffer; - } -#else - env = getenv(name); -#endif - - if (env == nullptr || strcmp("0", env) == 0) + std::string value; + if (!getenv_raw(name, value) || value == "0") return 0; - if (strcmp("2", env) == 0) + if (value == "2") return 2; - if (strcmp("1", env) == 0) + if (value == "1") return 1; return 0; } inline std::string getenv_string ( const char* name){ - - const char* env = nullptr; - -#if defined(_WIN32) - char buffer[1024]; - auto rc = GetEnvironmentVariable(name, buffer, 1024); - if (0 != rc && rc <= 1024) { - env = buffer; - } -#else - env = getenv(name); -#endif - - if ((nullptr == env)) + std::string value; + if (!getenv_raw(name, value)) return ""; - return std::string(env); + return value; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a1fa8dd6..d5a1784a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,11 @@ add_executable( loader_tracing_layer.cpp ) +# The registry fallback for loader environment settings is a Windows-only feature. +if(WIN32) + target_sources(tests PRIVATE getenv_registry_unit_tests.cpp) +endif() + # Only include driver_ordering_unit_tests and driver_teardown_unit_tests for static builds or non-Windows platforms # as they require internal loader symbols that are not exported in Windows DLLs if(BUILD_STATIC OR NOT WIN32) @@ -119,6 +124,11 @@ endif() add_test(NAME tests_api COMMAND tests --gtest_filter=*GivenLevelZeroLoaderPresentWhenCallingzeGetLoaderVersionsAPIThenValidVersionIsReturned*) set_property(TEST tests_api PROPERTY ENVIRONMENT "ZE_ENABLE_LOADER_DEBUG_TRACE=1;ZE_ENABLE_NULL_DRIVER=1") +# Windows-only: registry fallback for loader environment settings. +if(WIN32) + add_test(NAME tests_getenv_registry_fallback COMMAND tests --gtest_filter=*GetenvRegistryFallbackTest*) +endif() + # LoaderVersionAPI Tests add_test(NAME tests_loader_version_after_init COMMAND tests --gtest_filter=*LoaderVersionAPI*GivenLoaderWhenCallingzelGetLoaderVersionAfterInitThenValidVersionIsReturned*) set_property(TEST tests_loader_version_after_init PROPERTY ENVIRONMENT "ZE_ENABLE_LOADER_DEBUG_TRACE=1;ZE_ENABLE_NULL_DRIVER=1")