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