IVGCVSW-3595 Implement the LoadDynamicBackends function in the Runtime class
[platform/upstream/armnn.git] / src / backends / backendsCommon / DynamicBackendUtils.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5
6 #include "DynamicBackendUtils.hpp"
7
8 #include <boost/filesystem.hpp>
9 #include <boost/algorithm/string.hpp>
10 #include <boost/log/trivial.hpp>
11
12 #include <regex>
13
14 namespace armnn
15 {
16
17 void* DynamicBackendUtils::OpenHandle(const std::string& sharedObjectPath)
18 {
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(boost::str(boost::format("OpenHandle error: %1%") % GetDlError()));
28     }
29
30     return sharedObjectHandle;
31 }
32
33 void DynamicBackendUtils::CloseHandle(const void* sharedObjectHandle)
34 {
35     if (!sharedObjectHandle)
36     {
37         return;
38     }
39
40     dlclose(const_cast<void*>(sharedObjectHandle));
41 }
42
43 bool DynamicBackendUtils::IsBackendCompatible(const BackendVersion &backendVersion)
44 {
45     BackendVersion backendApiVersion = IBackendInternal::GetApiVersion();
46
47     return IsBackendCompatibleImpl(backendApiVersion, backendVersion);
48 }
49
50 bool DynamicBackendUtils::IsBackendCompatibleImpl(const BackendVersion &backendApiVersion,
51                                                   const BackendVersion &backendVersion)
52 {
53     return backendVersion.m_Major == backendApiVersion.m_Major &&
54            backendVersion.m_Minor <= backendApiVersion.m_Minor;
55 }
56
57 std::string DynamicBackendUtils::GetDlError()
58 {
59     const char* errorMessage = dlerror();
60     if (!errorMessage)
61     {
62         return "";
63     }
64
65     return std::string(errorMessage);
66 }
67
68 std::vector<std::string> DynamicBackendUtils::GetBackendPaths(const std::string& overrideBackendPath)
69 {
70     // Check if a path where to dynamically load the backends from is given
71     if (!overrideBackendPath.empty())
72     {
73         if (!IsPathValid(overrideBackendPath))
74         {
75             BOOST_LOG_TRIVIAL(warning) << "WARNING: The given override path for dynamic backends \""
76                                        << overrideBackendPath << "\" is not valid";
77
78             return {};
79         }
80
81         return std::vector<std::string>{ overrideBackendPath };
82     }
83
84     // Expects a colon-separated list: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2:...:PATH_N"
85     const std::string backendPaths = DYNAMIC_BACKEND_PATHS;
86
87     return GetBackendPathsImpl(backendPaths);
88 }
89
90 std::vector<std::string> DynamicBackendUtils::GetBackendPathsImpl(const std::string& backendPaths)
91 {
92     std::unordered_set<std::string> uniqueBackendPaths;
93     std::vector<std::string> tempBackendPaths;
94     std::vector<std::string> validBackendPaths;
95
96     // Split the given list of paths
97     boost::split(tempBackendPaths, backendPaths, boost::is_any_of(":"));
98
99     for (const std::string& path : tempBackendPaths)
100     {
101         // Check whether the path is valid
102         if (!IsPathValid(path))
103         {
104             continue;
105         }
106
107         // Check whether the path is a duplicate
108         auto it = uniqueBackendPaths.find(path);
109         if (it != uniqueBackendPaths.end())
110         {
111             // The path is a duplicate
112             continue;
113         }
114
115         // Add the path to the set of unique paths
116         uniqueBackendPaths.insert(path);
117
118         // Add the path to the list of valid paths
119         validBackendPaths.push_back(path);
120     }
121
122     return validBackendPaths;
123 }
124
125 bool DynamicBackendUtils::IsPathValid(const std::string& path)
126 {
127     if (path.empty())
128     {
129         BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path is empty";
130         return false;
131     }
132
133     boost::filesystem::path boostPath(path);
134
135     if (!boost::filesystem::exists(boostPath))
136     {
137         BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
138         return false;
139     }
140
141     if (!boost::filesystem::is_directory(boostPath))
142     {
143         BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
144         return false;
145     }
146
147     if (!boostPath.is_absolute())
148     {
149         BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
150         return false;
151     }
152
153     return true;
154 }
155
156 std::vector<std::string> DynamicBackendUtils::GetSharedObjects(const std::vector<std::string>& backendPaths)
157 {
158     std::unordered_set<std::string> uniqueSharedObjects;
159     std::vector<std::string> sharedObjects;
160
161     for (const std::string& backendPath : backendPaths)
162     {
163         using namespace boost::filesystem;
164
165         // Check if the path is valid. In case of error, IsValidPath will log an error message
166         if (!IsPathValid(backendPath))
167         {
168             continue;
169         }
170
171         // Get all the files in the current path in alphabetical order
172         std::vector<path> backendPathFiles;
173         std::copy(directory_iterator(backendPath), directory_iterator(), std::back_inserter(backendPathFiles));
174         std::sort(backendPathFiles.begin(), backendPathFiles.end());
175
176         // Go through all the files in the current backend path
177         for (const path& backendPathFile : backendPathFiles)
178         {
179             // Get only the name of the file (without the full path)
180             std::string filename = backendPathFile.filename().string();
181
182             if (filename.empty())
183             {
184                 // Empty filename
185                 continue;
186             }
187
188             path canonicalPath;
189             try
190             {
191                 // Get the canonical path for the current file, it will throw if for example the file is a
192                 // symlink that cannot be resolved
193                 canonicalPath = canonical(backendPathFile);
194             }
195             catch (const filesystem_error& e)
196             {
197                 BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what();
198             }
199             if (canonicalPath.empty())
200             {
201                 // No such file or perhaps a symlink that couldn't be resolved
202                 continue;
203             }
204
205             // Check if the current filename matches the expected naming convention
206             // The expected format is: <vendor>_<name>_backend.so[<version>]
207             // e.g. "Arm_GpuAcc_backend.so" or "Arm_GpuAcc_backend.so.1.2"
208             const std::regex dynamicBackendRegex("^[a-zA-Z0-9]+_[a-zA-Z0-9]+_backend.so(\\.[0-9]+)*$");
209
210             bool filenameMatch = false;
211             try
212             {
213                 // Match the filename to the expected naming scheme
214                 filenameMatch = std::regex_match(filename, dynamicBackendRegex);
215             }
216             catch (const std::exception& e)
217             {
218                 BOOST_LOG_TRIVIAL(warning) << "GetSharedObjects warning: " << e.what();
219             }
220             if (!filenameMatch)
221             {
222                 // Filename does not match the expected naming scheme (or an error has occurred)
223                 continue;
224             }
225
226             // Append the valid canonical path to the output list only if it's not a duplicate
227             std::string validCanonicalPath = canonicalPath.string();
228             auto it = uniqueSharedObjects.find(validCanonicalPath);
229             if (it == uniqueSharedObjects.end())
230             {
231                 // Not a duplicate, append the canonical path to the output list
232                 sharedObjects.push_back(validCanonicalPath);
233
234                 // Add the canonical path to the collection of unique shared objects
235                 uniqueSharedObjects.insert(validCanonicalPath);
236             }
237         }
238     }
239
240     return sharedObjects;
241 }
242
243 std::vector<DynamicBackendPtr> DynamicBackendUtils::CreateDynamicBackends(const std::vector<std::string>& sharedObjects)
244 {
245     // Create a list of dynamic backends
246     std::vector<DynamicBackendPtr> dynamicBackends;
247     for (const std::string& sharedObject : sharedObjects)
248     {
249         // Create a handle to the shared object
250         void* sharedObjectHandle = nullptr;
251         try
252         {
253             sharedObjectHandle = DynamicBackendUtils::OpenHandle(sharedObject);
254         }
255         catch (const RuntimeException& e)
256         {
257             BOOST_LOG_TRIVIAL(warning) << "Cannot create a handle to the shared object file \""
258                                        << sharedObject << "\": " << e.what();
259             continue;
260         }
261         if (!sharedObjectHandle)
262         {
263             BOOST_LOG_TRIVIAL(warning) << "Invalid handle to the shared object file \"" << sharedObject << "\"";
264
265             continue;
266         }
267
268         // Create a dynamic backend object
269         DynamicBackendPtr dynamicBackend;
270         try
271         {
272             dynamicBackend.reset(new DynamicBackend(sharedObjectHandle));
273         }
274         catch (const Exception& e)
275         {
276             BOOST_LOG_TRIVIAL(warning) << "Cannot create a valid dynamic backend from the shared object file \""
277                                        << sharedObject << "\": " << e.what();
278             continue;
279         }
280         if (!dynamicBackend)
281         {
282             BOOST_LOG_TRIVIAL(warning) << "Invalid dynamic backend object for the shared object file \""
283                                        << sharedObject << "\"";
284
285             continue;
286         }
287
288         // Append the newly created dynamic backend to the list
289         dynamicBackends.push_back(std::move(dynamicBackend));
290     }
291
292     return dynamicBackends;
293 }
294
295 } // namespace armnn