ArmNN
 20.02
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>
8 
9 #include <boost/filesystem.hpp>
10 #include <boost/algorithm/string.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> tempBackendPaths;
114  std::vector<std::string> validBackendPaths;
115 
116  // Split the given list of paths
117  boost::split(tempBackendPaths, backendPaths, boost::is_any_of(":"));
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  boost::filesystem::path boostPath(path);
154 
155  if (!boost::filesystem::exists(boostPath))
156  {
157  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
158  return false;
159  }
160 
161  if (!boost::filesystem::is_directory(boostPath))
162  {
163  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
164  return false;
165  }
166 
167  if (!boostPath.is_absolute())
168  {
169  ARMNN_LOG(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
170  return false;
171  }
172 
173  return true;
174 }
175 
176 std::vector<std::string> DynamicBackendUtils::GetSharedObjects(const std::vector<std::string>& backendPaths)
177 {
178  std::unordered_set<std::string> uniqueSharedObjects;
179  std::vector<std::string> sharedObjects;
180 
181  for (const std::string& backendPath : backendPaths)
182  {
183  using namespace boost::filesystem;
184 
185  // Check if the path is valid. In case of error, IsValidPath will log an error message
186  if (!IsPathValid(backendPath))
187  {
188  continue;
189  }
190 
191  // Get all the files in the current path in alphabetical order
192  std::vector<path> backendPathFiles;
193  std::copy(directory_iterator(backendPath), directory_iterator(), std::back_inserter(backendPathFiles));
194  std::sort(backendPathFiles.begin(), backendPathFiles.end());
195 
196  // Go through all the files in the current backend path
197  for (const path& backendPathFile : backendPathFiles)
198  {
199  // Get only the name of the file (without the full path)
200  std::string filename = backendPathFile.filename().string();
201 
202  if (filename.empty())
203  {
204  // Empty filename
205  continue;
206  }
207 
208  path canonicalPath;
209  try
210  {
211  // Get the canonical path for the current file, it will throw if for example the file is a
212  // symlink that cannot be resolved
213  canonicalPath = canonical(backendPathFile);
214  }
215  catch (const filesystem_error& e)
216  {
217  ARMNN_LOG(warning) << "GetSharedObjects warning: " << e.what();
218  }
219  if (canonicalPath.empty())
220  {
221  // No such file or perhaps a symlink that couldn't be resolved
222  continue;
223  }
224 
225  // Check if the current filename matches the expected naming convention
226  // The expected format is: <vendor>_<name>_backend.so[<version>]
227  // e.g. "Arm_GpuAcc_backend.so" or "Arm_GpuAcc_backend.so.1.2"
228  const std::regex dynamicBackendRegex("^[a-zA-Z0-9]+_[a-zA-Z0-9]+_backend.so(\\.[0-9]+)*$");
229 
230  bool filenameMatch = false;
231  try
232  {
233  // Match the filename to the expected naming scheme
234  filenameMatch = std::regex_match(filename, dynamicBackendRegex);
235  }
236  catch (const std::exception& e)
237  {
238  ARMNN_LOG(warning) << "GetSharedObjects warning: " << e.what();
239  }
240  if (!filenameMatch)
241  {
242  // Filename does not match the expected naming scheme (or an error has occurred)
243  continue;
244  }
245 
246  // Append the valid canonical path to the output list only if it's not a duplicate
247  std::string validCanonicalPath = canonicalPath.string();
248  auto it = uniqueSharedObjects.find(validCanonicalPath);
249  if (it == uniqueSharedObjects.end())
250  {
251  // Not a duplicate, append the canonical path to the output list
252  sharedObjects.push_back(validCanonicalPath);
253 
254  // Add the canonical path to the collection of unique shared objects
255  uniqueSharedObjects.insert(validCanonicalPath);
256  }
257  }
258  }
259 
260  return sharedObjects;
261 }
262 
263 std::vector<DynamicBackendPtr> DynamicBackendUtils::CreateDynamicBackends(const std::vector<std::string>& sharedObjects)
264 {
265  // Create a list of dynamic backends
266  std::vector<DynamicBackendPtr> dynamicBackends;
267  for (const std::string& sharedObject : sharedObjects)
268  {
269  // Create a handle to the shared object
270  void* sharedObjectHandle = nullptr;
271  try
272  {
273  sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObject);
274  }
275  catch (const RuntimeException& e)
276  {
277  ARMNN_LOG(warning) << "Cannot create a handle to the shared object file \""
278  << sharedObject << "\": " << e.what();
279  continue;
280  }
281  if (!sharedObjectHandle)
282  {
283  ARMNN_LOG(warning) << "Invalid handle to the shared object file \"" << sharedObject << "\"";
284 
285  continue;
286  }
287 
288  // Create a dynamic backend object
289  DynamicBackendPtr dynamicBackend;
290  try
291  {
292  dynamicBackend.reset(new DynamicBackend(sharedObjectHandle));
293  }
294  catch (const Exception& e)
295  {
296  ARMNN_LOG(warning) << "Cannot create a valid dynamic backend from the shared object file \""
297  << sharedObject << "\": " << e.what();
298  continue;
299  }
300  if (!dynamicBackend)
301  {
302  ARMNN_LOG(warning) << "Invalid dynamic backend object for the shared object file \""
303  << sharedObject << "\"";
304  continue;
305  }
306 
307  // Append the newly created dynamic backend to the list
308  dynamicBackends.push_back(std::move(dynamicBackend));
309  }
310 
311  return dynamicBackends;
312 }
313 
315 {
316  // Get a reference of the backend registry
317  BackendRegistry& backendRegistry = BackendRegistryInstance();
318 
319  for (const auto& id : dynamicBackends)
320  {
321  backendRegistry.Deregister(id);
322  }
323 
324 }
325 
326 BackendIdSet DynamicBackendUtils::RegisterDynamicBackends(const std::vector<DynamicBackendPtr>& dynamicBackends)
327 {
328  // Get a reference of the backend registry
329  BackendRegistry& backendRegistry = BackendRegistryInstance();
330 
331  // Register the dynamic backends in the backend registry, and return a list of registered backend ids
332  return RegisterDynamicBackendsImpl(backendRegistry, dynamicBackends);
333 }
334 
336  const std::vector<DynamicBackendPtr>& dynamicBackends)
337 {
338  // Initialize the list of registered backend ids
339  BackendIdSet registeredBackendIds;
340 
341  // Register the dynamic backends in the backend registry
342  for (const DynamicBackendPtr& dynamicBackend : dynamicBackends)
343  {
344  // Get the id of the dynamic backend
345  BackendId dynamicBackendId;
346  try
347  {
348  dynamicBackendId = dynamicBackend->GetBackendId();
349  }
350  catch (const RuntimeException& e)
351  {
352  ARMNN_LOG(warning) << "Cannot register dynamic backend, "
353  << "an error has occurred when getting the backend id: " << e.what();
354  continue;
355  }
356  if (dynamicBackendId.IsEmpty() ||
357  dynamicBackendId.IsUndefined())
358  {
359  ARMNN_LOG(warning) << "Cannot register dynamic backend, invalid backend id: " << dynamicBackendId;
360  continue;
361  }
362 
363  // Check whether the dynamic backend is already registered
364  bool backendAlreadyRegistered = backendRegistry.IsBackendRegistered(dynamicBackendId);
365  if (backendAlreadyRegistered)
366  {
367  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
368  << "\": backend already registered";
369  continue;
370  }
371 
372  // Get the dynamic backend factory function
373  BackendRegistry::FactoryFunction dynamicBackendFactoryFunction = nullptr;
374  try
375  {
376  dynamicBackendFactoryFunction = dynamicBackend->GetFactoryFunction();
377  }
378  catch (const RuntimeException& e)
379  {
380  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
381  << "\": an error has occurred when getting the backend factory function: "
382  << e.what();
383  continue;
384  }
385  if (dynamicBackendFactoryFunction == nullptr)
386  {
387  ARMNN_LOG(warning) << "Cannot register dynamic backend \"" << dynamicBackendId
388  << "\": invalid backend factory function";
389  continue;
390  }
391 
392  // Register the dynamic backend
393  try
394  {
395  backendRegistry.Register(dynamicBackendId, dynamicBackendFactoryFunction);
396  }
397  catch (const InvalidArgumentException& e)
398  {
399  ARMNN_LOG(warning) << "An error has occurred when registering the dynamic backend \""
400  << dynamicBackendId << "\": " << e.what();
401  continue;
402  }
403 
404  // Add the id of the dynamic backend just registered to the list of registered backend ids
405  registeredBackendIds.insert(dynamicBackendId);
406  }
407 
408  return registeredBackendIds;
409 }
410 
411 } // namespace armnn
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)