ArmNN
 24.02
DynamicBackendUtils.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <armnn/Logging.hpp>
10 
11 #include <regex>
12 
13 namespace armnn
14 {
15 
16 void* DynamicBackendUtils::OpenHandle(const std::string& sharedObjectPath)
17 {
18 #if defined(__unix__) || defined(__APPLE__)
19  if (sharedObjectPath.empty())
20  {
21  throw RuntimeException("OpenHandle error: shared object path must not be empty");
22  }
23 
24  void* sharedObjectHandle = dlopen(sharedObjectPath.c_str(), RTLD_LAZY);
25  if (!sharedObjectHandle)
26  {
27  throw RuntimeException(fmt::format("OpenHandle error: {}", GetDlError()));
28  }
29 
30  return sharedObjectHandle;
31 #else
32  armnn::IgnoreUnused(sharedObjectPath);
33  throw RuntimeException("Dynamic backends not supported on this platform");
34 #endif
35 }
36 
37 void DynamicBackendUtils::CloseHandle(const void* sharedObjectHandle)
38 {
39 #if defined(__unix__) || defined(__APPLE__)
40  if (!sharedObjectHandle)
41  {
42  return;
43  }
44 
45  dlclose(const_cast<void*>(sharedObjectHandle));
46 #else
47  armnn::IgnoreUnused(sharedObjectHandle);
48  throw RuntimeException("Dynamic backends not supported on this platform");
49 #endif
50 }
51 
53 {
54  BackendVersion backendApiVersion = IBackendInternal::GetApiVersion();
55 
56  return IsBackendCompatibleImpl(backendApiVersion, backendVersion);
57 }
58 
60  const BackendVersion &backendVersion)
61 {
62  return backendVersion.m_Major == backendApiVersion.m_Major &&
63  backendVersion.m_Minor <= backendApiVersion.m_Minor;
64 }
65 
66 std::string DynamicBackendUtils::GetDlError()
67 {
68 #if defined(__unix__) || defined(__APPLE__)
69  const char* errorMessage = dlerror();
70  if (!errorMessage)
71  {
72  return "";
73  }
74 
75  return std::string(errorMessage);
76 #else
77  throw RuntimeException("Dynamic backends not supported on this platform");
78 #endif
79 }
80 
81 std::vector<std::string> DynamicBackendUtils::GetBackendPaths(const std::string& overrideBackendPath)
82 {
83  // Check if a path where to dynamically load the backends from is given
84  if (!overrideBackendPath.empty())
85  {
86  if (!IsPathValid(overrideBackendPath))
87  {
88  ARMNN_LOG(warning) << "WARNING: The given override path for dynamic backends \""
89  << overrideBackendPath << "\" is not valid";
90 
91  return {};
92  }
93 
94  return std::vector<std::string>{ overrideBackendPath };
95  }
96 
97  // Expects a colon-separated list: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2:...:PATH_N"
98  const std::string backendPaths = DYNAMIC_BACKEND_PATHS;
99 
100  return GetBackendPathsImpl(backendPaths);
101 }
102 
103 std::vector<std::string> DynamicBackendUtils::GetBackendPathsImpl(const std::string& backendPaths)
104 {
105  // Check if there's any path to process at all
106  if (backendPaths.empty())
107  {
108  // Silently return without issuing a warning as no paths have been passed, so
109  // the whole dynamic backend loading feature can be considered as disabled
110  return {};
111  }
112 
113  std::unordered_set<std::string> uniqueBackendPaths;
114  std::vector<std::string> validBackendPaths;
115 
116  // Split the given list of paths
117  std::vector<std::string> tempBackendPaths = armnn::stringUtils::StringTokenizer(backendPaths, ":");
118 
119  for (const std::string& path : tempBackendPaths)
120  {
121  // Check whether the path is valid
122  if (!IsPathValid(path))
123  {
124  continue;
125  }
126 
127  // Check whether the path is a duplicate
128  auto it = uniqueBackendPaths.find(path);
129  if (it != uniqueBackendPaths.end())
130  {
131  // The path is a duplicate
132  continue;
133  }
134 
135  // Add the path to the set of unique paths
136  uniqueBackendPaths.insert(path);
137 
138  // Add the path to the list of valid paths
139  validBackendPaths.push_back(path);
140  }
141 
142  return validBackendPaths;
143 }
144 
145 bool DynamicBackendUtils::IsPathValid(const std::string& path)
146 {
147  if (path.empty())
148  {
149  ARMNN_LOG(warning) << "WARNING: The given backend path is empty";
150  return false;
151  }
152 
153 #if !defined(ARMNN_DISABLE_FILESYSTEM)
154  fs::path fsPath(path);
155 
156  if (!fs::exists(fsPath))
157  {
158  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
159  return false;
160  }
161 
162  if (!fs::is_directory(fsPath))
163  {
164  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
165  return false;
166  }
167 
168  if (!fsPath.is_absolute())
169  {
170  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
171  return false;
172  }
173 #endif // !defined(ARMNN_DISABLE_FILESYSTEM)
174 
175  return true;
176 }
177 
178 std::vector<std::string> DynamicBackendUtils::GetSharedObjects(const std::vector<std::string>& backendPaths)
179 {
180  std::unordered_set<std::string> uniqueSharedObjects;
181  std::vector<std::string> sharedObjects;
182 
183 #if !defined(ARMNN_DISABLE_FILESYSTEM)
184  for (const std::string& backendPath : backendPaths)
185  {
186  using namespace fs;
187 
188  // Check if the path is valid. In case of error, IsValidPath will log an error message
189  if (!IsPathValid(backendPath))
190  {
191  continue;
192  }
193 
194  // Get all the files in the current path in alphabetical order
195  std::vector<path> backendPathFiles;
196  std::copy(directory_iterator(backendPath), directory_iterator(), std::back_inserter(backendPathFiles));
197  std::sort(backendPathFiles.begin(), backendPathFiles.end());
198 
199  // Go through all the files in the current backend path
200  for (const path& backendPathFile : backendPathFiles)
201  {
202  // Get only the name of the file (without the full path)
203  std::string filename = backendPathFile.filename().string();
204 
205  if (filename.empty())
206  {
207  // Empty filename
208  continue;
209  }
210 
211  path canonicalPath;
212  try
213  {
214  // Get the canonical path for the current file, it will throw if for example the file is a
215  // symlink that cannot be resolved
216  canonicalPath = canonical(backendPathFile);
217  }
218  catch (const filesystem_error& e)
219  {
220  ARMNN_LOG(warning) << "GetSharedObjects warning: " << e.what();
221  }
222  if (canonicalPath.empty())
223  {
224  // No such file or perhaps a symlink that couldn't be resolved
225  continue;
226  }
227 
228  // Check if the current filename matches the expected naming convention
229  // The expected format is: <vendor>_<name>_backend.so[<version>]
230  // e.g. "Arm_GpuAcc_backend.so" or "Arm_GpuAcc_backend.so.1.2"
231  const std::regex dynamicBackendRegex("^[a-zA-Z0-9]+_[a-zA-Z0-9]+_backend.so(\\.[0-9]+)*$");
232 
233  bool filenameMatch = false;
234  try
235  {
236  // Match the filename to the expected naming scheme
237  filenameMatch = std::regex_match(filename, dynamicBackendRegex);
238  }
239  catch (const std::exception& e)
240  {
241  ARMNN_LOG(warning) << "GetSharedObjects warning: " << e.what();
242  }
243  if (!filenameMatch)
244  {
245  // Filename does not match the expected naming scheme (or an error has occurred)
246  continue;
247  }
248 
249  // Append the valid canonical path to the output list only if it's not a duplicate
250  std::string validCanonicalPath = canonicalPath.string();
251  auto it = uniqueSharedObjects.find(validCanonicalPath);
252  if (it == uniqueSharedObjects.end())
253  {
254  // Not a duplicate, append the canonical path to the output list
255  sharedObjects.push_back(validCanonicalPath);
256 
257  // Add the canonical path to the collection of unique shared objects
258  uniqueSharedObjects.insert(validCanonicalPath);
259  }
260  }
261  }
262 #else
263  armnn::IgnoreUnused(backendPaths);
264 #endif // !defined(ARMNN_DISABLE_FILESYSTEM)
265 
266  return sharedObjects;
267 }
268 
269 std::vector<DynamicBackendPtr> DynamicBackendUtils::CreateDynamicBackends(const std::vector<std::string>& sharedObjects)
270 {
271  // Create a list of dynamic backends
272  std::vector<DynamicBackendPtr> dynamicBackends;
273  for (const std::string& sharedObject : sharedObjects)
274  {
275  // Create a handle to the shared object
276  void* sharedObjectHandle = nullptr;
277  try
278  {
279  sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObject);
280  }
281  catch (const RuntimeException& e)
282  {
283  ARMNN_LOG(warning) << "Cannot create a handle to the shared object file \""
284  << sharedObject << "\": " << e.what();
285  continue;
286  }
287  if (!sharedObjectHandle)
288  {
289  ARMNN_LOG(warning) << "Invalid handle to the shared object file \"" << sharedObject << "\"";
290 
291  continue;
292  }
293 
294  // Create a dynamic backend object
295  DynamicBackendPtr dynamicBackend;
296  try
297  {
298  dynamicBackend.reset(new DynamicBackend(sharedObjectHandle));
299  }
300  catch (const Exception& e)
301  {
302  ARMNN_LOG(warning) << "Cannot create a valid dynamic backend from the shared object file \""
303  << sharedObject << "\": " << e.what();
304  continue;
305  }
306  if (!dynamicBackend)
307  {
308  ARMNN_LOG(warning) << "Invalid dynamic backend object for the shared object file \""
309  << sharedObject << "\"";
310  continue;
311  }
312 
313  // Append the newly created dynamic backend to the list
314  dynamicBackends.push_back(std::move(dynamicBackend));
315  }
316 
317  return dynamicBackends;
318 }
319 
321 {
322  // Get a reference of the backend registry
323  BackendRegistry& backendRegistry = BackendRegistryInstance();
324 
325  for (const auto& id : dynamicBackends)
326  {
327  backendRegistry.Deregister(id);
328  }
329 
330 }
331 
332 BackendIdSet DynamicBackendUtils::RegisterDynamicBackends(const std::vector<DynamicBackendPtr>& dynamicBackends)
333 {
334  // Get a reference of the backend registry
335  BackendRegistry& backendRegistry = BackendRegistryInstance();
336 
337  // Register the dynamic backends in the backend registry, and return a list of registered backend ids
338  return RegisterDynamicBackendsImpl(backendRegistry, dynamicBackends);
339 }
340 
342  const std::vector<DynamicBackendPtr>& dynamicBackends)
343 {
344  // Initialize the list of registered backend ids
345  BackendIdSet registeredBackendIds;
346 
347  // Register the dynamic backends in the backend registry
348  for (const DynamicBackendPtr& dynamicBackend : dynamicBackends)
349  {
350  // Get the id of the dynamic backend
351  BackendId dynamicBackendId;
352  try
353  {
354  dynamicBackendId = dynamicBackend->GetBackendId();
355  }
356  catch (const RuntimeException& e)
357  {
358  ARMNN_LOG(warning) << "Cannot register dynamic backend, "
359  << "an error has occurred when getting the backend id: " << e.what();
360  continue;
361  }
362  if (dynamicBackendId.IsEmpty() ||
363  dynamicBackendId.IsUndefined())
364  {
365  ARMNN_LOG(warning) << "Cannot register dynamic backend, invalid backend id: " << dynamicBackendId;
366  continue;
367  }
368 
369  // Check whether the dynamic backend is already registered
370  bool backendAlreadyRegistered = backendRegistry.IsBackendRegistered(dynamicBackendId);
371  if (backendAlreadyRegistered)
372  {
373  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
374  << "\": backend already registered";
375  continue;
376  }
377 
378  // Get the dynamic backend factory function
379  BackendRegistry::FactoryFunction dynamicBackendFactoryFunction = nullptr;
380  try
381  {
382  dynamicBackendFactoryFunction = dynamicBackend->GetFactoryFunction();
383  }
384  catch (const RuntimeException& e)
385  {
386  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
387  << "\": an error has occurred when getting the backend factory function: "
388  << e.what();
389  continue;
390  }
391  if (dynamicBackendFactoryFunction == nullptr)
392  {
393  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
394  << "\": invalid backend factory function";
395  continue;
396  }
397 
398  // Register the dynamic backend
399  try
400  {
401  backendRegistry.Register(dynamicBackendId, dynamicBackendFactoryFunction);
402  }
403  catch (const InvalidArgumentException& e)
404  {
405  ARMNN_LOG(warning) << "An error has occurred when registering the dynamic backend \""
406  << dynamicBackendId << "\": " << e.what();
407  continue;
408  }
409 
410  // Add the id of the dynamic backend just registered to the list of registered backend ids
411  registeredBackendIds.insert(dynamicBackendId);
412  }
413 
414  return registeredBackendIds;
415 }
416 
417 } // namespace armnn
armnn::BackendVersion::m_Minor
uint32_t m_Minor
Definition: IBackendInternal.hpp:37
armnn::DynamicBackendUtils::CreateDynamicBackends
static std::vector< DynamicBackendPtr > CreateDynamicBackends(const std::vector< std::string > &sharedObjects)
Definition: DynamicBackendUtils.cpp:269
armnn::BackendId::IsUndefined
bool IsUndefined() const
Definition: BackendId.hpp:141
armnn::DynamicBackendPtr
std::unique_ptr< DynamicBackend > DynamicBackendPtr
Definition: DynamicBackend.hpp:54
DYNAMIC_BACKEND_PATHS
#define DYNAMIC_BACKEND_PATHS
Definition: DynamicBackendUtils.hpp:22
armnn::BackendRegistry::Deregister
void Deregister(const BackendId &id)
Definition: BackendRegistry.cpp:41
armnn::BackendIdSet
std::unordered_set< BackendId > BackendIdSet
Definition: BackendId.hpp:193
armnn::BackendId::IsEmpty
bool IsEmpty() const
Definition: BackendId.hpp:140
armnn::DynamicBackendUtils::IsBackendCompatibleImpl
static bool IsBackendCompatibleImpl(const BackendVersion &backendApiVersion, const BackendVersion &backendVersion)
Protected methods for testing purposes.
Definition: DynamicBackendUtils.cpp:59
armnn::DynamicBackendUtils::OpenHandle
static void * OpenHandle(const std::string &sharedObjectPath)
Definition: DynamicBackendUtils.cpp:16
armnn::BackendRegistry
Definition: BackendRegistry.hpp:35
armnn::BackendRegistry::FactoryFunction
std::function< PointerType()> FactoryFunction
Definition: BackendRegistry.hpp:39
armnn::DynamicBackend
Definition: DynamicBackend.hpp:20
armnn::DynamicBackendUtils::RegisterDynamicBackendsImpl
static BackendIdSet RegisterDynamicBackendsImpl(BackendRegistry &backendRegistry, const std::vector< DynamicBackendPtr > &dynamicBackends)
Definition: DynamicBackendUtils.cpp:341
StringUtils.hpp
armnn::Exception::what
virtual const char * what() const noexcept override
Definition: Exceptions.cpp:32
ARMNN_LOG
#define ARMNN_LOG(severity)
Definition: Logging.hpp:212
Logging.hpp
armnn::DynamicBackendUtils::GetSharedObjects
static std::vector< std::string > GetSharedObjects(const std::vector< std::string > &backendPaths)
Definition: DynamicBackendUtils.cpp:178
armnn::BackendRegistryInstance
BackendRegistry & BackendRegistryInstance()
Definition: BackendRegistry.cpp:15
armnn::InvalidArgumentException
Definition: Exceptions.hpp:80
armnn::DynamicBackendUtils::IsPathValid
static bool IsPathValid(const std::string &path)
Definition: DynamicBackendUtils.cpp:145
Filesystem.hpp
armnn::DynamicBackendUtils::CloseHandle
static void CloseHandle(const void *sharedObjectHandle)
Definition: DynamicBackendUtils.cpp:37
armnn::Exception
Base class for all ArmNN exceptions so that users can filter to just those.
Definition: Exceptions.hpp:46
armnn::RuntimeException
Definition: Exceptions.hpp:120
armnn::BackendRegistry::IsBackendRegistered
bool IsBackendRegistered(const BackendId &id) const
Definition: BackendRegistry.cpp:52
armnn::IBackendInternal::GetApiVersion
static constexpr BackendVersion GetApiVersion()
Returns the version of the Backend API.
Definition: IBackendInternal.hpp:167
armnn::DynamicBackendUtils::RegisterDynamicBackends
static BackendIdSet RegisterDynamicBackends(const std::vector< DynamicBackendPtr > &dynamicBackends)
Definition: DynamicBackendUtils.cpp:332
armnn::DynamicBackendUtils::DeregisterDynamicBackends
static void DeregisterDynamicBackends(const BackendIdSet &dynamicBackends)
Definition: DynamicBackendUtils.cpp:320
armnn::BackendRegistry::Register
void Register(const BackendId &id, FactoryFunction factory)
Definition: BackendRegistry.cpp:21
armnn::IgnoreUnused
void IgnoreUnused(Ts &&...)
Definition: IgnoreUnused.hpp:14
armnn::BackendVersion
Definition: IBackendInternal.hpp:34
armnn::BackendId
Definition: BackendId.hpp:75
armnn::DynamicBackendUtils::GetBackendPaths
static std::vector< std::string > GetBackendPaths(const std::string &overrideBackendPath="")
Definition: DynamicBackendUtils.cpp:81
armnn
Copyright (c) 2021 ARM Limited and Contributors.
Definition: 01_00_quick_start.dox:6
armnn::BoostLogSeverityMapping::warning
@ warning
armnn::DynamicBackendUtils::GetBackendPathsImpl
static std::vector< std::string > GetBackendPathsImpl(const std::string &backendPaths)
Definition: DynamicBackendUtils.cpp:103
armnn::DynamicBackendUtils::IsBackendCompatible
static bool IsBackendCompatible(const BackendVersion &backendVersion)
Definition: DynamicBackendUtils.cpp:52
armnn::stringUtils::StringTokenizer
std::vector< std::string > StringTokenizer(const std::string &str, const char *delimiters, bool tokenCompression=true)
Function to take a string and a list of delimiters and split the string into tokens based on those de...
Definition: StringUtils.hpp:23
armnn::BackendVersion::m_Major
uint32_t m_Major
Definition: IBackendInternal.hpp:36
DynamicBackendUtils.hpp