diff --git a/sycl/cmake/modules/BuildUnifiedRuntime.cmake b/sycl/cmake/modules/BuildUnifiedRuntime.cmake index 5cdf378bbe958..1160d21d716e8 100644 --- a/sycl/cmake/modules/BuildUnifiedRuntime.cmake +++ b/sycl/cmake/modules/BuildUnifiedRuntime.cmake @@ -132,6 +132,15 @@ if(TARGET UnifiedRuntimeLoader) ARCHIVE DESTINATION "lib${LLVM_LIBDIR_SUFFIX}" COMPONENT unified-runtime-loader RUNTIME DESTINATION "bin" COMPONENT unified-runtime-loader ) + + # The loader dlopen()s its shared layers from its own directory, so they have + # to be installed alongside it. + if(TARGET ur_sanitizer_layer) + install(TARGETS ur_sanitizer_layer + LIBRARY DESTINATION "lib${LLVM_LIBDIR_SUFFIX}" COMPONENT unified-runtime-loader + RUNTIME DESTINATION "bin" COMPONENT unified-runtime-loader + ) + endif() endif() add_custom_target(UnifiedRuntimeAdapters) diff --git a/unified-runtime/source/loader/CMakeLists.txt b/unified-runtime/source/loader/CMakeLists.txt index eb59c85686fb6..7bf82ddd7cd87 100644 --- a/unified-runtime/source/loader/CMakeLists.txt +++ b/unified-runtime/source/loader/CMakeLists.txt @@ -163,98 +163,14 @@ if(UR_ENABLE_TRACING) endif() if(UR_ENABLE_SANITIZER) + # The sanitizer layer is a shared library of its own that the loader loads on + # demand through a proxy - see layers/ur_shared_layer.hpp. target_sources(ur_loader PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/../ur/ur.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_quarantine.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_quarantine.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_statistics.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_statistics.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_validator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/asan/asan_validator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_origin.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/msan/msan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_buffer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_buffer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_ddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_ddi.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_interceptor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_interceptor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_report.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_report.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_shadow.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/tsan/tsan_shadow.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/backtrace.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/sanitizer_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_allocator.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_allocator.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_common.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_libdevice.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_options.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_options.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stackdepot.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stackdepot.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stacktrace.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_stacktrace.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/sanitizer_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanddi.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanitizer_layer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/ur_sanitizer_layer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/layers/ur_shared_layer.cpp ) - if(UR_ENABLE_SYMBOLIZER) - set(symbolizer_sources - ${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer/sanitizer_common/linux/symbolizer.cpp - ) - target_sources(ur_loader - PRIVATE ${symbolizer_sources} - ) - target_include_directories(ur_loader PRIVATE ${LLVM_INCLUDE_DIRS}) - target_link_libraries(ur_loader PRIVATE LLVMSupport LLVMSymbolize) - target_compile_definitions(ur_loader PRIVATE UR_HAVE_SYMBOLIZER) - # In in-tree build, if LLVM is built with libc++, we also need to build - # symbolizer.cpp with libc++ abi and link libc++ in. - if(NOT UR_STANDALONE_BUILD AND LLVM_LIBCXX_USED) - set_property(SOURCE - ${symbolizer_sources} - APPEND_STRING PROPERTY COMPILE_FLAGS - " -stdlib=libc++ ") - # Link with gcc_s fisrt to avoid some symbols resolve to libc++/libc++abi/libunwind's one - target_link_libraries(ur_loader PRIVATE gcc_s ${LIBCXX_PATH} ${LIBCXX_ABI_PATH}) - endif() - endif() - - target_include_directories(ur_loader PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/layers/sanitizer" - "${CMAKE_CURRENT_SOURCE_DIR}/../" - ) + add_subdirectory(layers/sanitizer) endif() if(WIN32) diff --git a/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt b/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt new file mode 100644 index 0000000000000..63829b848c09f --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/CMakeLists.txt @@ -0,0 +1,119 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# A shared library rather than a part of the loader, so that all loader +# instances of a process share one instance of it - see ur_shared_layer.hpp. +# Loaded on demand from the same locations an adapter is, and exports nothing +# but urLoaderLayerGetInterface, which keeps the LLVM symbolizer private too. + +add_ur_library(ur_sanitizer_layer SHARED + ${PROJECT_SOURCE_DIR}/source/ur/ur.cpp + asan/asan_allocator.cpp + asan/asan_buffer.cpp + asan/asan_ddi.cpp + asan/asan_interceptor.cpp + asan/asan_quarantine.cpp + asan/asan_report.cpp + asan/asan_shadow.cpp + asan/asan_statistics.cpp + asan/asan_validator.cpp + msan/msan_allocator.cpp + msan/msan_buffer.cpp + msan/msan_ddi.cpp + msan/msan_interceptor.cpp + msan/msan_report.cpp + msan/msan_shadow.cpp + tsan/tsan_buffer.cpp + tsan/tsan_ddi.cpp + tsan/tsan_interceptor.cpp + tsan/tsan_report.cpp + tsan/tsan_shadow.cpp + sanitizer_common/linux/backtrace.cpp + sanitizer_common/linux/sanitizer_utils.cpp + sanitizer_common/sanitizer_allocator.cpp + sanitizer_common/sanitizer_options.cpp + sanitizer_common/sanitizer_stackdepot.cpp + sanitizer_common/sanitizer_stacktrace.cpp + sanitizer_common/sanitizer_utils.cpp + ur_sanddi.cpp + ur_sanitizer_layer.cpp + ur_sanitizer_layer_entry.cpp +) +install_ur_library(ur_sanitizer_layer) + +target_include_directories(ur_sanitizer_layer PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/source + ${PROJECT_SOURCE_DIR}/source/loader + ${PROJECT_SOURCE_DIR}/source/loader/layers +) + +target_link_libraries(ur_sanitizer_layer PRIVATE + ${PROJECT_NAME}::common + ${PROJECT_NAME}::headers +) + +set_target_properties(ur_sanitizer_layer PROPERTIES + VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}" + SOVERSION "${PROJECT_VERSION_MAJOR}" + # BUILD_WITH_INSTALL_RPATH is intentionally not set, see + # source/adapters/CMakeLists.txt. + INSTALL_RPATH "\$ORIGIN" +) + +target_link_options(ur_sanitizer_layer PRIVATE + "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/sanitizer_layer.map" +) + +if(UR_ENABLE_SYMBOLIZER) + target_sources(ur_sanitizer_layer PRIVATE + sanitizer_common/linux/symbolizer.cpp + ) + target_compile_definitions(ur_sanitizer_layer PRIVATE UR_HAVE_SYMBOLIZER) + target_include_directories(ur_sanitizer_layer PRIVATE ${LLVM_INCLUDE_DIRS}) + target_link_libraries(ur_sanitizer_layer PRIVATE LLVMSupport LLVMSymbolize) + + # The symbolizer dependencies may emit calls to + # _intel_fast_memcpy/_intel_fast_memset, normally provided by libirc, which + # this library doesn't link. The stubs forward to plain memcpy/memset. + if(CMAKE_C_COMPILER_ID STREQUAL "IntelLLVM") + if(MSVC) + set(_no_intel_lib_flag -Qno-intel-lib=libirc) + else() + set(_no_intel_lib_flag -no-intel-lib=libirc) + endif() + set_source_files_properties( + ${PROJECT_SOURCE_DIR}/source/loader/intel_fast_mem_stub.c + PROPERTIES COMPILE_OPTIONS "${_no_intel_lib_flag}") + target_sources(ur_sanitizer_layer PRIVATE + ${PROJECT_SOURCE_DIR}/source/loader/intel_fast_mem_stub.c + ) + endif() + + # In an in-tree build, if LLVM is built with libc++, symbolizer.cpp has to + # be built against libc++ as well and libc++ linked in. + if(NOT UR_STANDALONE_BUILD AND LLVM_LIBCXX_USED) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libc++.a + OUTPUT_VARIABLE LIBCXX_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} --print-file-name=libc++abi.a + OUTPUT_VARIABLE LIBCXX_ABI_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT EXISTS ${LIBCXX_PATH} OR NOT EXISTS ${LIBCXX_ABI_PATH}) + message(FATAL_ERROR "libc++ is required but can't find the libraries") + endif() + set_property(SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/sanitizer_common/linux/symbolizer.cpp + APPEND_STRING PROPERTY COMPILE_FLAGS " -stdlib=libc++ ") + # Link with gcc_s first to avoid some symbols resolving to + # libc++/libc++abi/libunwind's one + target_link_libraries(ur_sanitizer_layer PRIVATE + gcc_s ${LIBCXX_PATH} ${LIBCXX_ABI_PATH}) + endif() +endif() + +# Loaded at runtime, so there is no link time dependency to pull it in. +add_dependencies(ur_loader ur_sanitizer_layer) diff --git a/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp index 04f637fabdbca..7b3c282ae8ce7 100644 --- a/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/asan/asan_ddi.cpp @@ -2108,9 +2108,6 @@ ur_result_t urCheckVersion(ur_api_version_t version) { } // namespace asan ur_result_t initAsanDDITable(ur_dditable_t *dditable) { - - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: ASAN"); - ur_result_t result = ur_sanitizer_layer::asan::urCheckVersion(UR_API_VERSION_CURRENT); diff --git a/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp index 5e83d1e6e3476..f5271ac1fe786 100644 --- a/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/msan/msan_ddi.cpp @@ -2085,8 +2085,6 @@ ur_result_t urCheckVersion(ur_api_version_t version) { ur_result_t initMsanDDITable(ur_dditable_t *dditable) { ur_result_t result = UR_RESULT_SUCCESS; - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: MSAN"); - if (UR_RESULT_SUCCESS == result) { result = ur_sanitizer_layer::msan::urCheckVersion(UR_API_VERSION_CURRENT); } diff --git a/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map b/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map new file mode 100644 index 0000000000000..b6a3c258366f2 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/sanitizer_layer.map @@ -0,0 +1,6 @@ +{ + global: + urLoaderLayerGetInterface; + local: + *; +}; diff --git a/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp b/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp index 266639e68fba2..4ecaf80a2a460 100644 --- a/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/tsan/tsan_ddi.cpp @@ -1704,8 +1704,6 @@ ur_result_t urGetIPCExpProcAddrTable( ur_result_t initTsanDDITable(ur_dditable_t *dditable) { ur_result_t result = UR_RESULT_SUCCESS; - UR_LOG_L(getContext()->logger, QUIET, "==== DeviceSanitizer: TSAN"); - if (UR_RESULT_SUCCESS == result) { result = ur_sanitizer_layer::tsan::urCheckVersion(UR_API_VERSION_CURRENT); } diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp index c7ec6b24cd58a..416ca84d38cda 100644 --- a/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanddi.cpp @@ -60,4 +60,19 @@ ur_result_t context_t::init(ur_dditable_t *dditable, return UR_RESULT_SUCCESS; } +ur_result_t context_t::interceptDdiTable(ur_dditable_t *dditable) { + switch (enabledType) { + case SanitizerType::AddressSanitizer: + return initAsanDDITable(dditable); + case SanitizerType::MemorySanitizer: + return initMsanDDITable(dditable); + case SanitizerType::ThreadSanitizer: + return initTsanDDITable(dditable); + default: + break; + } + + return UR_RESULT_SUCCESS; +} + } // namespace ur_sanitizer_layer diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp index 86789d9bbb57b..e10c6df485935 100644 --- a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer.hpp @@ -46,6 +46,9 @@ class __urdlllocal context_t : public proxy_layer_context_t, const std::set &enabledLayerNames, codeloc_data codelocData) override; + /// @brief Hook up one more ddi table to the already initialized sanitizer. + ur_result_t interceptDdiTable(ur_dditable_t *dditable); + ur_result_t tearDown() override; }; diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp new file mode 100644 index 0000000000000..64c7accc20560 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_entry.cpp @@ -0,0 +1,118 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_sanitizer_layer_entry.cpp + * + */ + +#include "logger/ur_logger.hpp" +#include "ur_layer_interface.h" +#include "ur_sanitizer_layer.hpp" + +#include +#include +#include +#include + +namespace { + +std::mutex layerMutex; +// Loader instances that have the layer initialized. +uint32_t initCount = 0; + +const char *getSanitizerName(ur_sanitizer_layer::SanitizerType type) { + switch (type) { + case ur_sanitizer_layer::SanitizerType::AddressSanitizer: + return "ASAN"; + case ur_sanitizer_layer::SanitizerType::MemorySanitizer: + return "MSAN"; + case ur_sanitizer_layer::SanitizerType::ThreadSanitizer: + return "TSAN"; + default: + return nullptr; + } +} + +ur_result_t UR_APICALL layerInit(ur_dditable_t *pDdiTable, + const char *const *ppEnabledLayerNames, + uint32_t numEnabledLayerNames) { + if (!pDdiTable || (numEnabledLayerNames && !ppEnabledLayerNames)) { + return UR_RESULT_ERROR_INVALID_NULL_POINTER; + } + + std::set enabledLayerNames; + for (uint32_t i = 0; i < numEnabledLayerNames; i++) { + enabledLayerNames.insert(ppEnabledLayerNames[i]); + } + + std::lock_guard lock(layerMutex); + + auto *context = ur_sanitizer_layer::getContext(); + + ur_result_t result; + if (initCount == 0) { + result = context->init(pDdiTable, enabledLayerNames, codeloc_data{}); + // Announced once per process, unlike the per-loader ddi table setup. + const char *name = getSanitizerName(context->enabledType); + if (result == UR_RESULT_SUCCESS && name) { + UR_LOG_L(context->logger, QUIET, "==== DeviceSanitizer: {}", name); + } + } else { + // The state is already up, so only hook up this loader's ddi table. + UR_LOG(DEBUG, "sanitizer layer is already initialized, intercepting an " + "additional ddi table"); + result = context->interceptDdiTable(pDdiTable); + } + + if (result == UR_RESULT_SUCCESS) { + initCount++; + } + + return result; +} + +ur_result_t UR_APICALL layerTearDown() { + std::lock_guard lock(layerMutex); + + if (initCount == 0 || --initCount != 0) { + return UR_RESULT_SUCCESS; + } + + ur_result_t result = ur_sanitizer_layer::getContext()->tearDown(); + ur_sanitizer_layer::context_t::forceDelete(); + + return result; +} + +} // namespace + +extern "C" UR_APIEXPORT ur_result_t UR_APICALL +urLoaderLayerGetInterface(uint32_t version, ur_layer_interface_t *pInterface) { + // This library has its own copy of the logger; keep it on UR_LOG_LOADER. + logger::init("loader"); + + if (version != UR_LAYER_INTERFACE_VERSION) { + UR_LOG(ERR, "unsupported layer interface version {}, expected {}", version, + UR_LAYER_INTERFACE_VERSION); + return UR_RESULT_ERROR_UNSUPPORTED_VERSION; + } + + if (!pInterface) { + return UR_RESULT_ERROR_INVALID_NULL_POINTER; + } + + pInterface->version = UR_LAYER_INTERFACE_VERSION; + pInterface->pfnInit = layerInit; + pInterface->pfnTearDown = layerTearDown; + + return UR_RESULT_SUCCESS; +} + +static_assert(std::is_same_v, + "the loader casts the entry point to this type"); diff --git a/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp new file mode 100644 index 0000000000000..2da162573df54 --- /dev/null +++ b/unified-runtime/source/loader/layers/sanitizer/ur_sanitizer_layer_proxy.hpp @@ -0,0 +1,36 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_sanitizer_layer_proxy.hpp + * + */ + +#pragma once + +#include "ur_shared_layer.hpp" +#include "ur_util.hpp" + +namespace ur_sanitizer_layer_proxy { + +/// @brief Loader side of the device sanitizer layer. +class __urdlllocal context_t final : public ur_loader::SharedLayer, + public AtomicSingleton { +public: + context_t() + : SharedLayer(MAKE_LIBRARY_NAME("ur_sanitizer_layer", "0"), getNames()) {} + + /// Known without loading the library. Keep in sync with + /// ur_sanitizer_layer::context_t. + static std::vector getNames() { + return {"UR_LAYER_ASAN", "UR_LAYER_MSAN", "UR_LAYER_TSAN"}; + } +}; + +inline context_t *getContext() { return context_t::get_direct(); } + +} // namespace ur_sanitizer_layer_proxy diff --git a/unified-runtime/source/loader/layers/ur_layer_interface.h b/unified-runtime/source/loader/layers/ur_layer_interface.h new file mode 100644 index 0000000000000..21dce716bf688 --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_layer_interface.h @@ -0,0 +1,56 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_layer_interface.h + * + */ + +#ifndef UR_LAYER_INTERFACE_H +#define UR_LAYER_INTERFACE_H 1 + +#include "unified-runtime/ur_api.h" +#include "unified-runtime/ur_ddi.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/// @brief The only symbol a layer shared library exports. +#define UR_LAYER_GET_INTERFACE_FUNC_NAME "urLoaderLayerGetInterface" + +/// @brief Bump whenever ur_layer_interface_t changes. The loader only accepts a +/// library implementing the exact version it asks for. +#define UR_LAYER_INTERFACE_VERSION 1 + +/// @brief Entry points of a layer implemented in a shared library. +typedef struct ur_layer_interface_t { + /// [out] Set to UR_LAYER_INTERFACE_VERSION by the layer. + uint32_t version; + + /// @brief Initialize the layer and route pDdiTable through it. Called once + /// per loader instance; all of them share one layer state. + ur_result_t(UR_APICALL *pfnInit)(ur_dditable_t *pDdiTable, + const char *const *ppEnabledLayerNames, + uint32_t numEnabledLayerNames); + + /// @brief Tear the layer down. The state is destroyed once every loader + /// instance that initialized the layer has torn it down. + ur_result_t(UR_APICALL *pfnTearDown)(void); +} ur_layer_interface_t; + +/// @brief Type of the UR_LAYER_GET_INTERFACE_FUNC_NAME entry point. Returns +/// UR_RESULT_ERROR_UNSUPPORTED_VERSION and leaves pInterface untouched +/// if the layer doesn't implement the requested version. +typedef ur_result_t(UR_APICALL *ur_pfnLoaderLayerGetInterface_t)( + uint32_t version, ur_layer_interface_t *pInterface); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif /* UR_LAYER_INTERFACE_H */ diff --git a/unified-runtime/source/loader/layers/ur_shared_layer.cpp b/unified-runtime/source/loader/layers/ur_shared_layer.cpp new file mode 100644 index 0000000000000..fd5d6b4339d0c --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_shared_layer.cpp @@ -0,0 +1,116 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_shared_layer.cpp + * + */ + +#include "ur_shared_layer.hpp" +#include "logger/ur_logger.hpp" +#include "ur_adapter_search.hpp" + +#include + +namespace ur_loader { + +namespace { + +/// @brief Candidate paths for a layer library, belonging to this loader first. +std::vector getLayerLoadPaths(const std::string &libraryName) { + std::vector paths; + + if (auto dir = getLoaderLibPath(); dir.has_value()) { + paths.emplace_back(dir.value() / libraryName); + } + if (auto path = getAdapterNameAsPath(libraryName); path.has_value()) { + paths.emplace_back(path.value()); + } + + return paths; +} + +} // namespace + +ur_result_t SharedLayer::load() { + if (library) { + return UR_RESULT_SUCCESS; + } + + for (const auto &path : getLayerLoadPaths(libraryName)) { + auto handle = LibLoader::loadAdapterLibrary(path.string().c_str()); + if (!handle) { + continue; + } + + auto pfnGetInterface = reinterpret_cast( + LibLoader::getFunctionPtr(handle.get(), + UR_LAYER_GET_INTERFACE_FUNC_NAME)); + ur_layer_interface_t candidate = {}; + if (!pfnGetInterface || + pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &candidate) != + UR_RESULT_SUCCESS || + !candidate.pfnInit || !candidate.pfnTearDown) { + UR_LOG(ERR, "{} doesn't implement layer interface version {}", + path.string(), UR_LAYER_INTERFACE_VERSION); + continue; + } + + library = std::move(handle); + layerInterface = candidate; + return UR_RESULT_SUCCESS; + } + + // At QUIET, which always prints: this aborts the loader initialization. + UR_LOG(QUIET, + "the {} layer is enabled but its library couldn't be loaded, make " + "sure it is installed next to the loader", + libraryName); + + return UR_RESULT_ERROR_LAYER_NOT_PRESENT; +} + +ur_result_t SharedLayer::init(ur_dditable_t *dditable, + const std::set &enabledLayerNames, + codeloc_data) { + bool enabled = std::any_of( + layerNames.begin(), layerNames.end(), [&enabledLayerNames](auto &name) { + return enabledLayerNames.find(name) != enabledLayerNames.end(); + }); + if (!enabled) { + return UR_RESULT_SUCCESS; + } + + if (ur_result_t result = load(); result != UR_RESULT_SUCCESS) { + return result; + } + + // The layer picks the names it implements out of all the enabled ones itself. + std::vector names; + names.reserve(enabledLayerNames.size()); + for (const auto &name : enabledLayerNames) { + names.push_back(name.c_str()); + } + + return layerInterface.pfnInit(dditable, names.data(), + static_cast(names.size())); +} + +ur_result_t SharedLayer::tearDown() { + if (!library) { + return UR_RESULT_SUCCESS; + } + + ur_result_t result = layerInterface.pfnTearDown(); + layerInterface = {}; + // Stays mapped as long as another loader instance still references it. + library.reset(); + + return result; +} + +} // namespace ur_loader diff --git a/unified-runtime/source/loader/layers/ur_shared_layer.hpp b/unified-runtime/source/loader/layers/ur_shared_layer.hpp new file mode 100644 index 0000000000000..ee814d70d0f85 --- /dev/null +++ b/unified-runtime/source/loader/layers/ur_shared_layer.hpp @@ -0,0 +1,55 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file ur_shared_layer.hpp + * + */ + +#ifndef UR_SHARED_LAYER_HPP +#define UR_SHARED_LAYER_HPP 1 + +#include "ur_layer_interface.h" +#include "ur_lib_loader.hpp" +#include "ur_proxy_layer.hpp" + +#include +#include + +namespace ur_loader { + +/// @brief A layer implemented in a separate shared library, so that all loader +/// instances of a process share one instance of it. +/// +/// A static loader can be linked into several libraries of one process, each +/// copy with its own hidden state. A layer compiled into the loader would be +/// duplicated along with it, which a layer holding state that has to exist +/// exactly once cannot tolerate. +class __urdlllocal SharedLayer : public proxy_layer_context_t { +public: + SharedLayer(std::string libraryName, std::vector layerNames) + : libraryName(std::move(libraryName)), layerNames(std::move(layerNames)) { + } + + ur_result_t init(ur_dditable_t *dditable, + const std::set &enabledLayerNames, + codeloc_data codelocData) override; + ur_result_t tearDown() override; + +private: + ur_result_t load(); + + const std::string libraryName; + const std::vector layerNames; + + LibLoader::Lib library; + ur_layer_interface_t layerInterface = {}; +}; + +} // namespace ur_loader + +#endif /* UR_SHARED_LAYER_HPP */ diff --git a/unified-runtime/source/loader/ur_lib.cpp b/unified-runtime/source/loader/ur_lib.cpp index 9030e1c7c7574..947f837255d65 100644 --- a/unified-runtime/source/loader/ur_lib.cpp +++ b/unified-runtime/source/loader/ur_lib.cpp @@ -49,10 +49,18 @@ void context_t::parseEnvEnabledLayers() { } } -void context_t::initLayers() { +// An enabled layer that fails to come up (e.g. a missing shared library) fails +// the whole loader; coming up silently without it would be worse. +ur_result_t context_t::initLayers() { for (auto &[layer, _] : layers) { - layer->init(&urDdiTable, enabledLayerNames, codelocData); + ur_result_t result = + layer->init(&urDdiTable, enabledLayerNames, codelocData); + if (result != UR_RESULT_SUCCESS) { + return result; + } } + + return UR_RESULT_SUCCESS; } void context_t::tearDownLayers() const { @@ -88,8 +96,8 @@ __urdlllocal ur_result_t context_t::Init( enabledLayerNames.merge(hLoaderConfig->getEnabledLayerNames()); } - if (!enabledLayerNames.empty()) { - initLayers(); + if (UR_RESULT_SUCCESS == result && !enabledLayerNames.empty()) { + result = initLayers(); } #if defined(UR_STATIC_UMF) diff --git a/unified-runtime/source/loader/ur_lib.hpp b/unified-runtime/source/loader/ur_lib.hpp index 527699e6b879e..0ec380e72d80c 100644 --- a/unified-runtime/source/loader/ur_lib.hpp +++ b/unified-runtime/source/loader/ur_lib.hpp @@ -24,7 +24,7 @@ #include "tracing/ur_tracing_layer.hpp" #endif #if UR_ENABLE_SANITIZER -#include "sanitizer/ur_sanitizer_layer.hpp" +#include "sanitizer/ur_sanitizer_layer_proxy.hpp" #endif #include @@ -76,8 +76,8 @@ class __urdlllocal context_t : public AtomicSingleton { // Initialize tracing layer after sanitizer layer to make sure tracing // layer will properly print all API calls. #if UR_ENABLE_SANITIZER - {ur_sanitizer_layer::getContext(), - ur_sanitizer_layer::context_t::forceDelete}, + {ur_sanitizer_layer_proxy::getContext(), + ur_sanitizer_layer_proxy::context_t::forceDelete}, #endif #if UR_ENABLE_TRACING {ur_tracing_layer::getContext(), @@ -92,7 +92,7 @@ class __urdlllocal context_t : public AtomicSingleton { ur_tracing_layer::context_t::getNames(), #endif #if UR_ENABLE_SANITIZER - ur_sanitizer_layer::context_t::getNames(), + ur_sanitizer_layer_proxy::context_t::getNames(), #endif }; std::string s; @@ -111,7 +111,7 @@ class __urdlllocal context_t : public AtomicSingleton { codeloc_data codelocData; void parseEnvEnabledLayers(); - void initLayers(); + ur_result_t initLayers(); void tearDownLayers() const; }; diff --git a/unified-runtime/test/layers/sanitizer/CMakeLists.txt b/unified-runtime/test/layers/sanitizer/CMakeLists.txt index b739efcf02940..6c03701260bf2 100644 --- a/unified-runtime/test/layers/sanitizer/CMakeLists.txt +++ b/unified-runtime/test/layers/sanitizer/CMakeLists.txt @@ -18,6 +18,12 @@ endfunction() add_sanitizer_test(asan asan.cpp) add_sanitizer_test(sanitizer_options sanitizer_options.cpp) +add_sanitizer_test(shared_layer shared_layer.cpp) +target_include_directories(shared_layer-test PRIVATE + ${PROJECT_SOURCE_DIR}/source/loader/layers +) +target_link_libraries(shared_layer-test PRIVATE ${CMAKE_DL_LIBS}) + # add the source here in order to test for unexported functions add_test_source(sanitizer_options ${PROJECT_SOURCE_DIR}/source/loader/layers/sanitizer/sanitizer_common/sanitizer_options.cpp diff --git a/unified-runtime/test/layers/sanitizer/shared_layer.cpp b/unified-runtime/test/layers/sanitizer/shared_layer.cpp new file mode 100644 index 0000000000000..57f5219ee7374 --- /dev/null +++ b/unified-runtime/test/layers/sanitizer/shared_layer.cpp @@ -0,0 +1,105 @@ +/* + * + * + * Part of the LLVM Project, under the Apache License v2.0 with LLVM + * Exceptions. See https://llvm.org/LICENSE.txt for license information. + * + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + * + * @file shared_layer.cpp + * + */ + +// RUN: UR_LOG_LOADER="level:debug;flush:debug;output:stdout" shared_layer-test +// REQUIRES: sanitizer + +#include "ur_layer_interface.h" +#include "ur_util.hpp" + +#include +#include + +namespace { + +constexpr const char *LibraryName = + MAKE_LIBRARY_NAME("ur_sanitizer_layer", "0"); + +struct SharedSanitizerLayer : public ::testing::Test { + void SetUp() override { + handle = dlopen(LibraryName, RTLD_LAZY | RTLD_LOCAL); + ASSERT_NE(handle, nullptr) << dlerror(); + } + + void TearDown() override { + if (handle) { + dlclose(handle); + } + } + + ur_pfnLoaderLayerGetInterface_t getInterfaceFn() { + return reinterpret_cast( + dlsym(handle, UR_LAYER_GET_INTERFACE_FUNC_NAME)); + } + + void *handle = nullptr; +}; + +TEST_F(SharedSanitizerLayer, GetInterface) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &interface), + UR_RESULT_SUCCESS); + ASSERT_EQ(interface.version, UR_LAYER_INTERFACE_VERSION); + ASSERT_NE(interface.pfnInit, nullptr); + ASSERT_NE(interface.pfnTearDown, nullptr); +} + +TEST_F(SharedSanitizerLayer, GetInterfaceRejectsUnknownVersion) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION + 1, &interface), + UR_RESULT_ERROR_UNSUPPORTED_VERSION); + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, nullptr), + UR_RESULT_ERROR_INVALID_NULL_POINTER); +} + +// Consumers have their own copy of the layer's dependencies, so nothing but the +// entry point may be visible. +TEST_F(SharedSanitizerLayer, EntryPointIsTheOnlyExportedSymbol) { + ASSERT_NE(dlsym(handle, UR_LAYER_GET_INTERFACE_FUNC_NAME), nullptr); + + for (const char *name : {"urLoaderInit", "urContextCreate", + "_ZN20ur_sanitizer_layer10getContextEv"}) { + EXPECT_EQ(dlsym(handle, name), nullptr) << name << " must not be exported"; + } +} + +// The shared state has to outlive all but the last loader instance. +TEST_F(SharedSanitizerLayer, InitIsReferenceCounted) { + auto pfnGetInterface = getInterfaceFn(); + ASSERT_NE(pfnGetInterface, nullptr) << dlerror(); + + ur_layer_interface_t interface = {}; + ASSERT_EQ(pfnGetInterface(UR_LAYER_INTERFACE_VERSION, &interface), + UR_RESULT_SUCCESS); + + // No sanitizer is enabled here, so this only exercises the book-keeping. + ur_dditable_t firstTable = {}; + ur_dditable_t secondTable = {}; + ASSERT_EQ(interface.pfnInit(&firstTable, nullptr, 0), UR_RESULT_SUCCESS); + ASSERT_EQ(interface.pfnInit(&secondTable, nullptr, 0), UR_RESULT_SUCCESS); + + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + // Tearing down more often than initializing must not underflow the count. + ASSERT_EQ(interface.pfnTearDown(), UR_RESULT_SUCCESS); + + ASSERT_EQ(interface.pfnInit(nullptr, nullptr, 0), + UR_RESULT_ERROR_INVALID_NULL_POINTER); +} + +} // namespace