• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <armnn/Logging.hpp>
7 #include <backendsCommon/DynamicBackendUtils.hpp>
8 #include "armnn/utility/StringUtils.hpp"
9 #include <armnnUtils/Filesystem.hpp>
10 
11 #include <regex>
12 
13 namespace armnn
14 {
15 
OpenHandle(const std::string & sharedObjectPath)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 
CloseHandle(const void * sharedObjectHandle)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 
IsBackendCompatible(const BackendVersion & backendVersion)52 bool DynamicBackendUtils::IsBackendCompatible(const BackendVersion &backendVersion)
53 {
54     BackendVersion backendApiVersion = IBackendInternal::GetApiVersion();
55 
56     return IsBackendCompatibleImpl(backendApiVersion, backendVersion);
57 }
58 
IsBackendCompatibleImpl(const BackendVersion & backendApiVersion,const BackendVersion & backendVersion)59 bool DynamicBackendUtils::IsBackendCompatibleImpl(const BackendVersion &backendApiVersion,
60                                                   const BackendVersion &backendVersion)
61 {
62     return backendVersion.m_Major == backendApiVersion.m_Major &&
63            backendVersion.m_Minor <= backendApiVersion.m_Minor;
64 }
65 
GetDlError()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 
GetBackendPaths(const std::string & overrideBackendPath)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 
GetBackendPathsImpl(const std::string & backendPaths)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 
IsPathValid(const std::string & path)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 
GetSharedObjects(const std::vector<std::string> & backendPaths)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 
CreateDynamicBackends(const std::vector<std::string> & sharedObjects)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 
DeregisterDynamicBackends(const BackendIdSet & dynamicBackends)320 void DynamicBackendUtils::DeregisterDynamicBackends(const BackendIdSet& dynamicBackends)
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 
RegisterDynamicBackends(const std::vector<DynamicBackendPtr> & dynamicBackends)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 
RegisterDynamicBackendsImpl(BackendRegistry & backendRegistry,const std::vector<DynamicBackendPtr> & dynamicBackends)341 BackendIdSet DynamicBackendUtils::RegisterDynamicBackendsImpl(BackendRegistry& backendRegistry,
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
418