From 11db6683a334c5b5f2d4ca80f0d241c18c1e7d8a Mon Sep 17 00:00:00 2001 From: Udit Kumar Agarwal Date: Tue, 30 Sep 2025 03:13:58 -0700 Subject: [PATCH] [SYCL] Lookup versioned OpenCL adapter library as fallback (#20229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Problem** SYCL RT loads `libur_adapter_opencl.so` (https://github.com/intel/llvm/blob/0ff1a5c2b4e4bc56799ec2dd17a89c3c57608890/sycl/include/sycl/detail/os_util.hpp#L129) while UR loads `libur_adapter_opencl.so.0` (https://github.com/intel/llvm/blob/0031df16e41bd0665e85af635b7bfd4e187ce7cd/unified-runtime/source/loader/ur_manifests.hpp#L35). Note that SYCL RT calls `dlopen()` with `RTLD_NOLOAD` flag, which causes `dlopen()` to fail if this library wasn’t loaded before. Now, in our Linux compiler packages, `libur_adapter_opencl.so` and `libur_adapter_opencl.so.0` are symlinked so they are the same file, that’s why call to `dlopen()` in SYCL RT succeeds. However, the problem happens with DPCPP PyPi package, which doesn’t support symlinked files, so call to dlopen() fails because these are two different files. **Proposed solution** Lookup `libur_adapter_opencl.so.0` as fallback. **Other potential solutions** 1. Why not just load `libur_adapter_opencl.so.0` always? Because that causes SYCL unit tests, which rely on mocked OpenCL adapter to fail. In unit tests, we actually want SYCL RT to load `libur_adapter_opencl.so` (mocked) and UR to load `libur_adapter_opencl.so.0`, both of which are different files. 2. Why not remove `RTLD_NOLOAD` flag? When using PyPi package, that can cause SYCL RT and UR to load two OpenCL adapters libraries. I'm not an expert on loaders, but that might lead to more bugs if, for example, OpenCL adapter functions that SYCL RT calls have side effects. --- sycl/include/sycl/detail/os_util.hpp | 36 ++++++++++++------- sycl/source/detail/os_util.cpp | 52 ++++++++++++++++------------ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/sycl/include/sycl/detail/os_util.hpp b/sycl/include/sycl/detail/os_util.hpp index 936d5be56cc46..6dc084573cc5f 100644 --- a/sycl/include/sycl/detail/os_util.hpp +++ b/sycl/include/sycl/detail/os_util.hpp @@ -12,6 +12,7 @@ #include // for __SYCL_EXPORT +#include #include // for size_t #include #include // for string @@ -106,27 +107,38 @@ void fileTreeWalk(const std::string Path, std::function Func, bool ignoreErrors = false); -void *dynLookup(const char *WinName, const char *LinName, const char *FunName); - // Look up a function name that was dynamically linked -// This is used by the runtime where it needs to manipulate native handles (e.g. -// retaining OpenCL handles). On Windows, the symbol name is looked up in -// `WinName`. In Linux, it uses `LinName`. +// This is used by the runtime where it needs to manipulate native handles +// (e.g. retaining OpenCL handles). // // The library must already have been loaded (perhaps by UR), otherwise this // function throws a SYCL runtime exception. +void *dynLookup(const char *const *LibNames, size_t LibNameSizes, + const char *FunName); + template -fn *dynLookupFunction(const char *WinName, const char *LinName, +fn *dynLookupFunction(const char *const *LibNames, size_t LibNameSize, const char *FunName) { - return reinterpret_cast(dynLookup(WinName, LinName, FunName)); + return reinterpret_cast(dynLookup(LibNames, LibNameSize, FunName)); } -// On Linux, the name of OpenCL that was used to link against may be either -// `OpenCL.so`, `OpenCL.so.1` or possibly anything else. -// `libur_adapter_opencl.so` is a more stable name, since it is hardcoded into -// the loader. + +// On Linux, first try to load from libur_adapter_opencl.so, then +// libur_adapter_opencl.so.0 if the first is not found. libur_adapter_opencl.so +// and libur_adapter_opencl.so.0 might be different libraries if they are not +// symlinked, which is the case with PyPi compiler distribution package. +// We can't load libur_adapter_opencl.so.0 always as the first choice because +// that would break SYCL unittests, which rely on mocking libur_adapter_opencl. +#ifdef __SYCL_RT_OS_WINDOWS +constexpr std::array OCLLibNames = {"OpenCL"}; +#else +constexpr std::array OCLLibNames = { + "libur_adapter_opencl.so", "libur_adapter_opencl.so.0"}; +#endif + #define __SYCL_OCL_CALL(FN, ...) \ (sycl::_V1::detail::dynLookupFunction( \ - "OpenCL", "libur_adapter_opencl.so", #FN)(__VA_ARGS__)) + sycl::detail::OCLLibNames.data(), sycl::detail::OCLLibNames.size(), \ + #FN)(__VA_ARGS__)) } // namespace detail } // namespace _V1 diff --git a/sycl/source/detail/os_util.cpp b/sycl/source/detail/os_util.cpp index 9069e1ca25ef0..ba27c507d79a6 100644 --- a/sycl/source/detail/os_util.cpp +++ b/sycl/source/detail/os_util.cpp @@ -291,36 +291,44 @@ size_t getDirectorySize(const std::string &Path, bool ignoreErrors) { return DirSizeVar; } -// Look up a function name that was dynamically linked -// This is used by the runtime where it needs to manipulate native handles (e.g. -// retaining OpenCL handles). On Windows, the symbol name is looked up in -// `WinName`. In Linux, it uses `LinName`. +// Look up a function name from the given list of shared libraries. // -// The library must already have been loaded (perhaps by UR), otherwise this +// These library must already have been loaded (perhaps by UR), otherwise this // function throws a SYCL runtime exception. -void *dynLookup([[maybe_unused]] const char *WinName, - [[maybe_unused]] const char *LinName, const char *FunName) { +void *dynLookup(const char *const *LibNames, size_t LibNameSizes, + const char *FunName) { #ifdef __SYCL_RT_OS_WINDOWS - auto handle = GetModuleHandleA(WinName); - if (!handle) { - throw sycl::exception(make_error_code(errc::runtime), - std::string(WinName) + " library is not loaded"); - } - auto *retVal = GetProcAddress(handle, FunName); + HMODULE handle = nullptr; + auto GetHandleF = [](const char *LibName) { + return GetModuleHandleA(LibName); + }; + auto GetProcF = [&]() { return GetProcAddress(handle, FunName); }; #else - auto handle = dlopen(LinName, RTLD_LAZY | RTLD_NOLOAD); - if (!handle) { - throw sycl::exception(make_error_code(errc::runtime), - std::string(LinName) + " library is not loaded"); - } - auto *retVal = dlsym(handle, FunName); - dlclose(handle); + void *handle = nullptr; + auto GetHandleF = [](const char *LibName) { + return dlopen(LibName, RTLD_LAZY | RTLD_NOLOAD); + }; + auto GetProcF = [&]() { + auto *retVal = dlsym(handle, FunName); + dlclose(handle); + return retVal; + }; #endif - if (!retVal) { + + // Iterate over the list of libraries and try to find one that is loaded. + size_t LibNameIterator = 0; + while (!handle && LibNameIterator < LibNameSizes) + handle = GetHandleF(LibNames[LibNameIterator++]); + if (!handle) + throw sycl::exception(make_error_code(errc::runtime), + "Libraries could not be loaded"); + + // Look up the function in the loaded library. + auto *retVal = GetProcF(); + if (!retVal) throw sycl::exception(make_error_code(errc::runtime), "Symbol " + std::string(FunName) + " could not be found"); - } return reinterpret_cast(retVal); }