// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #include "DynamicBackendUtils.hpp" #include #include #include #include namespace armnn { void* DynamicBackendUtils::OpenHandle(const std::string& sharedObjectPath) { if (sharedObjectPath.empty()) { throw RuntimeException("OpenHandle error: shared object path must not be empty"); } void* sharedObjectHandle = dlopen(sharedObjectPath.c_str(), RTLD_LAZY); if (!sharedObjectHandle) { throw RuntimeException(boost::str(boost::format("OpenHandle error: %1%") % GetDlError())); } return sharedObjectHandle; } void DynamicBackendUtils::CloseHandle(const void* sharedObjectHandle) { if (!sharedObjectHandle) { return; } dlclose(const_cast(sharedObjectHandle)); } bool DynamicBackendUtils::IsBackendCompatible(const BackendVersion &backendVersion) { BackendVersion backendApiVersion = IBackendInternal::GetApiVersion(); return IsBackendCompatibleImpl(backendApiVersion, backendVersion); } bool DynamicBackendUtils::IsBackendCompatibleImpl(const BackendVersion &backendApiVersion, const BackendVersion &backendVersion) { return backendVersion.m_Major == backendApiVersion.m_Major && backendVersion.m_Minor <= backendApiVersion.m_Minor; } std::string DynamicBackendUtils::GetDlError() { const char* errorMessage = dlerror(); if (!errorMessage) { return ""; } return std::string(errorMessage); } std::vector DynamicBackendUtils::GetBackendPaths(const std::string& overrideBackendPath) { // Check if a path where to dynamically load the backends from is given if (!overrideBackendPath.empty()) { if (!IsPathValid(overrideBackendPath)) { BOOST_LOG_TRIVIAL(warning) << "WARNING: The given override path for dynamic backends \"" << overrideBackendPath << "\" is not valid"; return {}; } return std::vector{ overrideBackendPath }; } // Expects a colon-separated list: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2:...:PATH_N" const std::string backendPaths = DYNAMIC_BACKEND_PATHS; return GetBackendPathsImpl(backendPaths); } std::vector DynamicBackendUtils::GetBackendPathsImpl(const std::string& backendPaths) { // Check if there's any path to process at all if (backendPaths.empty()) { // Silently return without issuing a warning as no paths have been passed, so // the whole dynamic backend loading feature can be considered as disabled return {}; } std::unordered_set uniqueBackendPaths; std::vector tempBackendPaths; std::vector validBackendPaths; // Split the given list of paths boost::split(tempBackendPaths, backendPaths, boost::is_any_of(":")); for (const std::string& path : tempBackendPaths) { // Check whether the path is valid if (!IsPathValid(path)) { continue; } // Check whether the path is a duplicate auto it = uniqueBackendPaths.find(path); if (it != uniqueBackendPaths.end()) { // The path is a duplicate continue; } // Add the path to the set of unique paths uniqueBackendPaths.insert(path); // Add the path to the list of valid paths validBackendPaths.push_back(path); } return validBackendPaths; } bool DynamicBackendUtils::IsPathValid(const std::string& path) { if (path.empty()) { BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path is empty"; return false; } boost::filesystem::path boostPath(path); if (!boost::filesystem::exists(boostPath)) { BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" does not exist"; return false; } if (!boost::filesystem::is_directory(boostPath)) { BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory"; return false; } if (!boostPath.is_absolute()) { BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute"; return false; } return true; } std::vector DynamicBackendUtils::GetSharedObjects(const std::vector& backendPaths) { std::unordered_set uniqueSharedObjects; std::vector sharedObjects; for (const std::string& backendPath : backendPaths) { using namespace boost::filesystem; // Check if the path is valid. In case of error, IsValidPath will log an error message if (!IsPathValid(backendPath)) { continue; } // Get all the files in the current path in alphabetical order std::vector backendPathFiles; std::copy(directory_iterator(backendPath), directory_iterator(), std::back_inserter(backendPathFiles)); std::sort(backendPathFiles.begin(), backendPathFiles.end()); // Go through all the files in the current backend path for (const path& backendPathFile : backendPathFiles) { // Get only the name of the file (without the full path) std::string filename = backendPathFile.filename().string(); if (filename.empty()) { // Empty filename continue; } path canonicalPath; try { // Get the canonical path for the current file, it will throw if for example the file is a // symlink that cannot be resolved canonicalPath = canonical(backendPathFile); } catch (const filesystem_error& e) { BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what(); } if (canonicalPath.empty()) { // No such file or perhaps a symlink that couldn't be resolved continue; } // Check if the current filename matches the expected naming convention // The expected format is: __backend.so[] // e.g. "Arm_GpuAcc_backend.so" or "Arm_GpuAcc_backend.so.1.2" const std::regex dynamicBackendRegex("^[a-zA-Z0-9]+_[a-zA-Z0-9]+_backend.so(\\.[0-9]+)*$"); bool filenameMatch = false; try { // Match the filename to the expected naming scheme filenameMatch = std::regex_match(filename, dynamicBackendRegex); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what(); } if (!filenameMatch) { // Filename does not match the expected naming scheme (or an error has occurred) continue; } // Append the valid canonical path to the output list only if it's not a duplicate std::string validCanonicalPath = canonicalPath.string(); auto it = uniqueSharedObjects.find(validCanonicalPath); if (it == uniqueSharedObjects.end()) { // Not a duplicate, append the canonical path to the output list sharedObjects.push_back(validCanonicalPath); // Add the canonical path to the collection of unique shared objects uniqueSharedObjects.insert(validCanonicalPath); } } } return sharedObjects; } std::vector DynamicBackendUtils::CreateDynamicBackends(const std::vector& sharedObjects) { // Create a list of dynamic backends std::vector dynamicBackends; for (const std::string& sharedObject : sharedObjects) { // Create a handle to the shared object void* sharedObjectHandle = nullptr; try { sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObject); } catch (const RuntimeException& e) { BOOST_LOG_TRIVIAL(warning) << "Cannot create a handle to the shared object file \"" << sharedObject << "\": " << e.what(); continue; } if (!sharedObjectHandle) { BOOST_LOG_TRIVIAL(warning) << "Invalid handle to the shared object file \"" << sharedObject << "\""; continue; } // Create a dynamic backend object DynamicBackendPtr dynamicBackend; try { dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)); } catch (const Exception& e) { BOOST_LOG_TRIVIAL(warning) << "Cannot create a valid dynamic backend from the shared object file \"" << sharedObject << "\": " << e.what(); continue; } if (!dynamicBackend) { BOOST_LOG_TRIVIAL(warning) << "Invalid dynamic backend object for the shared object file \"" << sharedObject << "\""; continue; } // Append the newly created dynamic backend to the list dynamicBackends.push_back(std::move(dynamicBackend)); } return dynamicBackends; } BackendIdSet DynamicBackendUtils::RegisterDynamicBackends(const std::vector& dynamicBackends) { // Get a reference of the backend registry BackendRegistry& backendRegistry = BackendRegistryInstance(); // Register the dynamic backends in the backend registry, and return a list of registered backend ids return RegisterDynamicBackendsImpl(backendRegistry, dynamicBackends); } BackendIdSet DynamicBackendUtils::RegisterDynamicBackendsImpl(BackendRegistry& backendRegistry, const std::vector& dynamicBackends) { // Initialize the list of registered backend ids BackendIdSet registeredBackendIds; // Register the dynamic backends in the backend registry for (const DynamicBackendPtr& dynamicBackend : dynamicBackends) { // Get the id of the dynamic backend BackendId dynamicBackendId; try { dynamicBackendId = dynamicBackend->GetBackendId(); } catch (const RuntimeException& e) { BOOST_LOG_TRIVIAL(warning) << "Cannot register dynamic backend, " << "an error has occurred when getting the backend id: " << e.what(); continue; } if (dynamicBackendId.IsEmpty() || dynamicBackendId.IsUndefined()) { BOOST_LOG_TRIVIAL(warning) << "Cannot register dynamic backend, invalid backend id: " << dynamicBackendId; continue; } // Check whether the dynamic backend is already registered bool backendAlreadyRegistered = backendRegistry.IsBackendRegistered(dynamicBackendId); if (backendAlreadyRegistered) { BOOST_LOG_TRIVIAL(warning) << "Cannot register dynamic backend \"" << dynamicBackendId << "\": backend already registered"; continue; } // Get the dynamic backend factory function BackendRegistry::FactoryFunction dynamicBackendFactoryFunction = nullptr; try { dynamicBackendFactoryFunction = dynamicBackend->GetFactoryFunction(); } catch (const RuntimeException& e) { BOOST_LOG_TRIVIAL(warning) << "Cannot register dynamic backend \"" << dynamicBackendId << "\": an error has occurred when getting the backend factory function: " << e.what(); continue; } if (dynamicBackendFactoryFunction == nullptr) { BOOST_LOG_TRIVIAL(warning) << "Cannot register dynamic backend \"" << dynamicBackendId << "\": invalid backend factory function"; continue; } // Register the dynamic backend try { backendRegistry.Register(dynamicBackendId, dynamicBackendFactoryFunction); } catch (const InvalidArgumentException& e) { BOOST_LOG_TRIVIAL(warning) << "An error has occurred when registering the dynamic backend \"" << dynamicBackendId << "\": " << e.what(); continue; } // Add the id of the dynamic backend just registered to the list of registered backend ids registeredBackendIds.insert(dynamicBackendId); } return registeredBackendIds; } } // namespace armnn