blob: b31ce60564db29c8b5caa1fe8f731b428d66de2c [file] [log] [blame]
//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//
#include "DynamicBackendUtils.hpp"
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <regex>
namespace armnn
{
void* DynamicBackendUtils::OpenHandle(const std::string& sharedObjectPath)
{
#if defined(__unix__)
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;
#else
throw RuntimeException("Dynamic backends not supported on this platform");
#endif
}
void DynamicBackendUtils::CloseHandle(const void* sharedObjectHandle)
{
#if defined(__unix__)
if (!sharedObjectHandle)
{
return;
}
dlclose(const_cast<void*>(sharedObjectHandle));
#else
throw RuntimeException("Dynamic backends not supported on this platform");
#endif
}
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()
{
#if defined(__unix__)
const char* errorMessage = dlerror();
if (!errorMessage)
{
return "";
}
return std::string(errorMessage);
#else
throw RuntimeException("Dynamic backends not supported on this platform");
#endif
}
std::vector<std::string> 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))
{
ARMNN_LOG(warning) << "WARNING: The given override path for dynamic backends \""
<< overrideBackendPath << "\" is not valid";
return {};
}
return std::vector<std::string>{ 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<std::string> 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<std::string> uniqueBackendPaths;
std::vector<std::string> tempBackendPaths;
std::vector<std::string> 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())
{
ARMNN_LOG(warning) << "WARNING: The given backend path is empty";
return false;
}
boost::filesystem::path boostPath(path);
if (!boost::filesystem::exists(boostPath))
{
ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
return false;
}
if (!boost::filesystem::is_directory(boostPath))
{
ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
return false;
}
if (!boostPath.is_absolute())
{
ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
return false;
}
return true;
}
std::vector<std::string> DynamicBackendUtils::GetSharedObjects(const std::vector<std::string>& backendPaths)
{
std::unordered_set<std::string> uniqueSharedObjects;
std::vector<std::string> 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<path> 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)
{
ARMNN_LOG(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: <vendor>_<name>_backend.so[<version>]
// 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)
{
ARMNN_LOG(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<DynamicBackendPtr> DynamicBackendUtils::CreateDynamicBackends(const std::vector<std::string>& sharedObjects)
{
// Create a list of dynamic backends
std::vector<DynamicBackendPtr> 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)
{
ARMNN_LOG(warning) << "Cannot create a handle to the shared object file \""
<< sharedObject << "\": " << e.what();
continue;
}
if (!sharedObjectHandle)
{
ARMNN_LOG(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)
{
ARMNN_LOG(warning) << "Cannot create a valid dynamic backend from the shared object file \""
<< sharedObject << "\": " << e.what();
continue;
}
if (!dynamicBackend)
{
ARMNN_LOG(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<DynamicBackendPtr>& 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<DynamicBackendPtr>& 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)
{
ARMNN_LOG(warning) << "Cannot register dynamic backend, "
<< "an error has occurred when getting the backend id: " << e.what();
continue;
}
if (dynamicBackendId.IsEmpty() ||
dynamicBackendId.IsUndefined())
{
ARMNN_LOG(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)
{
ARMNN_LOG(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)
{
ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
<< "\": an error has occurred when getting the backend factory function: "
<< e.what();
continue;
}
if (dynamicBackendFactoryFunction == nullptr)
{
ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
<< "\": invalid backend factory function";
continue;
}
// Register the dynamic backend
try
{
backendRegistry.Register(dynamicBackendId, dynamicBackendFactoryFunction);
}
catch (const InvalidArgumentException& e)
{
ARMNN_LOG(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