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