CIRCT 22.0.0git
Loading...
Searching...
No Matches
Accelerator.cpp
Go to the documentation of this file.
1//===- Accelerator.cpp - ESI accelerator system API -----------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// DO NOT EDIT!
10// This file is distributed as part of an ESI package. The source for this file
11// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/).
12//
13//===----------------------------------------------------------------------===//
14
15#include "esi/Accelerator.h"
16
17#include <cassert>
18#include <cstdlib>
19#include <filesystem>
20#include <map>
21#include <sstream>
22#include <stdexcept>
23#include <vector>
24
25#include <iostream>
26
27#ifdef __linux__
28#include <dlfcn.h>
29#include <linux/limits.h>
30#include <unistd.h>
31#elif _WIN32
32#include <windows.h>
33#endif
34
35using namespace esi;
36using namespace esi::services;
37
38namespace esi {
40 : ctxt(ctxt), serviceThread(nullptr) {}
42
44 if (!serviceThread)
45 serviceThread = std::make_unique<AcceleratorServiceThread>();
46 return serviceThread.get();
47}
48void AcceleratorConnection::createEngine(const std::string &engineTypeName,
49 AppIDPath idPath,
50 const ServiceImplDetails &details,
51 const HWClientDetails &clients) {
52 std::unique_ptr<Engine> engine = ::esi::registry::createEngine(
53 *this, engineTypeName, idPath, details, clients);
54 registerEngine(idPath, std::move(engine), clients);
55}
56
58 std::unique_ptr<Engine> engine,
59 const HWClientDetails &clients) {
60 assert(engine);
61 auto [engineIter, _] = ownedEngines.emplace(idPath, std::move(engine));
62
63 // Engine is now owned by the accelerator connection, so the std::unique_ptr
64 // is no longer valid. Resolve a new one from the map iter.
65 Engine *enginePtr = engineIter->second.get();
66 // Compute our parents idPath path.
67 AppIDPath prefix = std::move(idPath);
68 if (prefix.size() > 0)
69 prefix.pop_back();
70
71 for (const auto &client : clients) {
72 AppIDPath fullClientPath = prefix + client.relPath;
73 for (const auto &channel : client.channelAssignments)
74 clientEngines[fullClientPath].setEngine(channel.first, enginePtr);
75 }
76}
77
79 AppIDPath id,
80 std::string implName,
81 ServiceImplDetails details,
82 HWClientDetails clients) {
83 std::unique_ptr<Service> &cacheEntry = serviceCache[make_tuple(&svcType, id)];
84 if (cacheEntry == nullptr) {
85 Service *svc = createService(svcType, id, implName, details, clients);
86 if (!svc)
87 svc = ServiceRegistry::createService(this, svcType, id, implName, details,
88 clients);
89 if (!svc)
90 return nullptr;
91 cacheEntry = std::unique_ptr<Service>(svc);
92 }
93 return cacheEntry.get();
94}
95
97AcceleratorConnection::takeOwnership(std::unique_ptr<Accelerator> acc) {
99 throw std::runtime_error(
100 "AcceleratorConnection already owns an accelerator");
101 ownedAccelerator = std::move(acc);
102 return ownedAccelerator.get();
103}
104
105/// Get the path to the currently running executable.
106static std::filesystem::path getExePath() {
107#ifdef __linux__
108 char result[PATH_MAX];
109 ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
110 if (count == -1)
111 throw std::runtime_error("Could not get executable path");
112 return std::filesystem::path(std::string(result, count));
113#elif _WIN32
114 char buffer[MAX_PATH];
115 DWORD length = GetModuleFileNameA(NULL, buffer, MAX_PATH);
116 if (length == 0)
117 throw std::runtime_error("Could not get executable path");
118 return std::filesystem::path(std::string(buffer, length));
119#else
120#eror "Unsupported platform"
121#endif
122}
123
124/// Get the path to the currently running shared library.
125static std::filesystem::path getLibPath() {
126#ifdef __linux__
127 Dl_info dl_info;
128 dladdr((void *)getLibPath, &dl_info);
129 return std::filesystem::path(std::string(dl_info.dli_fname));
130#elif _WIN32
131 HMODULE hModule = NULL;
132 if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
133 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
134 reinterpret_cast<LPCSTR>(&getLibPath), &hModule)) {
135 // Handle error
136 return std::filesystem::path();
137 }
138
139 char buffer[MAX_PATH];
140 DWORD length = GetModuleFileNameA(hModule, buffer, MAX_PATH);
141 if (length == 0)
142 throw std::runtime_error("Could not get library path");
143
144 return std::filesystem::path(std::string(buffer, length));
145#else
146#eror "Unsupported platform"
147#endif
148}
149
150/// Get the list of directories to search for backend plugins.
151static std::vector<std::filesystem::path> getESIBackendDirectories() {
152 std::vector<std::filesystem::path> directories;
153
154 // First, check current directory.
155 directories.push_back(std::filesystem::current_path());
156
157 // Next, parse the ESI_BACKENDS environment variable and add those.
158 const char *esiBackends = std::getenv("ESI_BACKENDS");
159 if (esiBackends) {
160 // Use platform-specific path separator
161#ifdef _WIN32
162 const char separator = ';';
163#else
164 const char separator = ':';
165#endif
166
167 std::string pathsStr(esiBackends);
168 std::stringstream ss(pathsStr);
169 std::string path;
170
171 while (std::getline(ss, path, separator))
172 if (!path.empty())
173 directories.emplace_back(path);
174 }
175
176 // Next, try the directory of the executable.
177 directories.push_back(getExePath().parent_path());
178 // Finally, try the directory of the library.
179 directories.push_back(getLibPath().parent_path());
180
181 return directories;
182}
183
184/// Load a backend plugin dynamically. Plugins are expected to be named
185/// lib<BackendName>Backend.so and located in one of 1) CWD, 2) directories
186/// specified in ESI_BACKENDS environment variable, 3) in the same directory as
187/// the application, or 4) in the same directory as this library.
188static void loadBackend(Context &ctxt, std::string backend) {
189 Logger &logger = ctxt.getLogger();
190 backend[0] = toupper(backend[0]);
191
192 // Get the file name we are looking for.
193#ifdef __linux__
194 std::string backendFileName = "lib" + backend + "Backend.so";
195#elif _WIN32
196 std::string backendFileName = backend + "Backend.dll";
197#else
198#eror "Unsupported platform"
199#endif
200
201 // First, try the current directory.
202 std::filesystem::path backendPath;
203 // Next, try directories specified in ESI_BACKENDS environment variable.
204 std::vector<std::filesystem::path> esiBackendDirs =
206 bool found = false;
207 for (const auto &dir : esiBackendDirs) {
208 backendPath = dir / backendFileName;
209 logger.debug("CONNECT",
210 "trying to find backend plugin: " + backendPath.string());
211 if (std::filesystem::exists(backendPath)) {
212 found = true;
213 break;
214 }
215 }
216
217 // If the path was found, convert it to a string.
218 if (found) {
219 backendPath = std::filesystem::absolute(backendPath);
220 logger.debug("CONNECT", "found backend plugin: " + backendPath.string());
221 } else {
222 // If all else fails, just try the name.
223 backendPath = backendFileName;
224 logger.debug("CONNECT",
225 "trying to find backend plugin: " + backendFileName);
226 }
227
228 // Attempt to load it.
229#ifdef __linux__
230 void *handle = dlopen(backendPath.string().c_str(), RTLD_NOW | RTLD_GLOBAL);
231 if (!handle) {
232 std::string error(dlerror());
233 logger.error("CONNECT",
234 "while attempting to load backend plugin: " + error);
235 throw std::runtime_error("While attempting to load backend plugin: " +
236 error);
237 }
238#elif _WIN32
239 // Set the DLL directory to the same directory as the backend DLL in case it
240 // has transitive dependencies.
241 if (found) {
242 std::filesystem::path backendPathParent = backendPath.parent_path();
243 // If backendPath has no parent directory (e.g., it's a relative path or
244 // a filename without a directory), fallback to the current working
245 // directory. This ensures a valid directory is used for setting the DLL
246 // search path.
247 if (backendPathParent.empty())
248 backendPathParent = std::filesystem::current_path();
249 logger.debug("CONNECT", "setting DLL search directory to: " +
250 backendPathParent.string());
251 if (SetDllDirectoryA(backendPathParent.string().c_str()) == 0)
252 throw std::runtime_error("While setting DLL directory: " +
253 std::to_string(GetLastError()));
254 }
255
256 // Load the backend plugin.
257 HMODULE handle = LoadLibraryA(backendPath.string().c_str());
258 if (!handle) {
259 DWORD error = GetLastError();
260 // Get the error message string
261 LPSTR messageBuffer = nullptr;
262 size_t size = FormatMessageA(
263 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
264 FORMAT_MESSAGE_IGNORE_INSERTS,
265 nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
266 (LPSTR)&messageBuffer, 0, nullptr);
267
268 std::string errorMessage;
269 if (size > 0 && messageBuffer != nullptr) {
270 errorMessage = std::string(messageBuffer, size);
271 LocalFree(messageBuffer);
272 } else {
273 errorMessage = "Unknown error";
274 }
275
276 std::string fullError = "While attempting to load backend plugin '" +
277 backendPath.string() + "': " + errorMessage +
278 " (error code: " + std::to_string(error) + ")";
279
280 logger.error("CONNECT", fullError);
281 throw std::runtime_error(fullError);
282 }
283#else
284#eror "Unsupported platform"
285#endif
286 logger.info("CONNECT", "loaded backend plugin: " + backendPath.string());
287}
288
289namespace registry {
290namespace internal {
291
293public:
294 static std::map<std::string, BackendCreate> &get() {
295 static BackendRegistry instance;
296 return instance.backendRegistry;
297 }
298
299private:
300 std::map<std::string, BackendCreate> backendRegistry;
301};
302
303void registerBackend(const std::string &name, BackendCreate create) {
304 auto &registry = BackendRegistry::get();
305 if (registry.count(name))
306 throw std::runtime_error("Backend already exists in registry");
307 registry[name] = create;
308}
309} // namespace internal
310
311std::unique_ptr<AcceleratorConnection> connect(Context &ctxt,
312 const std::string &backend,
313 const std::string &connection) {
314 auto &registry = internal::BackendRegistry::get();
315 auto f = registry.find(backend);
316 if (f == registry.end()) {
317 // If it's not already found in the registry, try to load it dynamically.
318 loadBackend(ctxt, backend);
319 f = registry.find(backend);
320 if (f == registry.end()) {
322 details["backend"] = backend;
323 std::ostringstream loaded_backends;
324 bool first = true;
325 for (const auto &b : registry) {
326 if (!first)
327 loaded_backends << ", ";
328 loaded_backends << b.first;
329 first = false;
330 }
331 details["loaded_backends"] = loaded_backends.str();
332 ctxt.getLogger().error("CONNECT", "backend '" + backend + "' not found",
333 &details);
334 throw std::runtime_error("Backend '" + backend + "' not found");
335 }
336 }
337 ctxt.getLogger().info("CONNECT", "connecting to backend " + backend +
338 " via '" + connection + "'");
339 return f->second(ctxt, connection);
340}
341
342} // namespace registry
343
345 Impl() {}
346 void start() { me = std::thread(&Impl::loop, this); }
347 void stop() {
348 shutdown = true;
349 me.join();
350 }
351 /// When there's data on any of the listenPorts, call the callback. This
352 /// method can be called from any thread.
353 void
354 addListener(std::initializer_list<ReadChannelPort *> listenPorts,
355 std::function<void(ReadChannelPort *, MessageData)> callback);
356
357 void addTask(std::function<void(void)> task) {
358 std::lock_guard<std::mutex> g(m);
359 taskList.push_back(task);
360 }
361
362private:
363 void loop();
364 volatile bool shutdown = false;
365 std::thread me;
366
367 // Protect the shared data structures.
368 std::mutex m;
369
370 // Map of read ports to callbacks.
371 std::map<ReadChannelPort *,
372 std::pair<std::function<void(ReadChannelPort *, MessageData)>,
373 std::future<MessageData>>>
375
376 /// Tasks which should be called on every loop iteration.
377 std::vector<std::function<void(void)>> taskList;
378};
379
380void AcceleratorServiceThread::Impl::loop() {
381 // These two variables should logically be in the loop, but this avoids
382 // reconstructing them on each iteration.
383 std::vector<std::tuple<ReadChannelPort *,
384 std::function<void(ReadChannelPort *, MessageData)>,
386 portUnlockWorkList;
387 std::vector<std::function<void(void)>> taskListCopy;
388 MessageData data;
389
390 while (!shutdown) {
391 // Ideally we'd have some wake notification here, but this sufficies for
392 // now.
393 // TODO: investigate better ways to do this. For now, just play nice with
394 // the other processes but don't waste time in between polling intervals.
395 std::this_thread::yield();
396
397 // Check and gather data from all the read ports we are monitoring. Put the
398 // callbacks to be called later so we can release the lock.
399 {
400 std::lock_guard<std::mutex> g(m);
401 for (auto &[channel, cbfPair] : listeners) {
402 assert(channel && "Null channel in listener list");
403 std::future<MessageData> &f = cbfPair.second;
404 if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
405 portUnlockWorkList.emplace_back(channel, cbfPair.first, f.get());
406 f = channel->readAsync();
407 }
408 }
409 }
410
411 // Call the callbacks outside the lock.
412 for (auto [channel, cb, data] : portUnlockWorkList)
413 cb(channel, std::move(data));
414
415 // Clear the worklist for the next iteration.
416 portUnlockWorkList.clear();
417
418 // Call any tasks that have been added. Copy it first so we can release the
419 // lock ASAP.
420 {
421 std::lock_guard<std::mutex> g(m);
422 taskListCopy = taskList;
423 }
424 for (auto &task : taskListCopy)
425 task();
426 }
427}
428
429void AcceleratorServiceThread::Impl::addListener(
430 std::initializer_list<ReadChannelPort *> listenPorts,
431 std::function<void(ReadChannelPort *, MessageData)> callback) {
432 std::lock_guard<std::mutex> g(m);
433 for (auto port : listenPorts) {
434 if (listeners.count(port))
435 throw std::runtime_error("Port already has a listener");
436 listeners[port] = std::make_pair(callback, port->readAsync());
437 }
438}
439
440} // namespace esi
441
443 : impl(std::make_unique<Impl>()) {
444 impl->start();
445}
447
449 if (impl) {
450 impl->stop();
451 impl.reset();
452 }
453}
454
455// When there's data on any of the listenPorts, call the callback. This is
456// kinda silly now that we have callback port support, especially given the
457// polling loop. Keep the functionality for now.
459 std::initializer_list<ReadChannelPort *> listenPorts,
460 std::function<void(ReadChannelPort *, MessageData)> callback) {
461 assert(impl && "Service thread not running");
462 impl->addListener(listenPorts, callback);
463}
464
466 assert(impl && "Service thread not running");
467 impl->addTask([&module]() { module.poll(); });
468}
469
471 if (serviceThread) {
472 serviceThread->stop();
473 serviceThread.reset();
474 }
475}
assert(baseType &&"element must be base type")
virtual void disconnect()
Disconnect from the accelerator cleanly.
virtual Service * createService(Service::Type service, AppIDPath idPath, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients)=0
Called by getServiceImpl exclusively.
ServiceClass * getService(AppIDPath id={}, std::string implName={}, ServiceImplDetails details={}, HWClientDetails clients={})
Get a typed reference to a particular service type.
std::map< AppIDPath, BundleEngineMap > clientEngines
Mapping of clients to their servicing engines.
void registerEngine(AppIDPath idPath, std::unique_ptr< Engine > engine, const HWClientDetails &clients)
If createEngine is overridden, this method should be called to register the engine and all of the cha...
std::map< ServiceCacheKey, std::unique_ptr< Service > > serviceCache
std::unique_ptr< AcceleratorServiceThread > serviceThread
std::unique_ptr< Accelerator > ownedAccelerator
Accelerator object owned by this connection.
std::map< AppIDPath, std::unique_ptr< Engine > > ownedEngines
Collection of owned engines.
virtual void createEngine(const std::string &engineTypeName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create a new engine for channel communication with the accelerator.
AcceleratorServiceThread * getServiceThread()
Return a pointer to the accelerator 'service' thread (or threads).
AcceleratorConnection(Context &ctxt)
Accelerator * takeOwnership(std::unique_ptr< Accelerator > accel)
Assume ownership of an accelerator object.
Background thread which services various requests.
void stop()
Instruct the service thread to stop running.
void addListener(std::initializer_list< ReadChannelPort * > listenPorts, std::function< void(ReadChannelPort *, MessageData)> callback)
When there's data on any of the listenPorts, call the callback.
std::unique_ptr< Impl > impl
void addPoll(HWModule &module)
Poll this module.
Top level accelerator class.
Definition Accelerator.h:60
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition Context.h:31
Engines implement the actual channel communication between the host and the accelerator.
Definition Engines.h:42
Represents either the top level or an instance of a hardware module.
Definition Design.h:47
virtual void error(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an error.
Definition Logging.h:64
virtual void info(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an informational message.
Definition Logging.h:75
void debug(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report a debug message.
Definition Logging.h:83
A logical chunk of data representing serialized data.
Definition Common.h:107
A ChannelPort which reads data from the accelerator.
Definition Ports.h:124
std::map< std::string, BackendCreate > backendRegistry
static std::map< std::string, BackendCreate > & get()
static Service * createService(AcceleratorConnection *acc, Service::Type svcType, AppIDPath id, std::string implName, ServiceImplDetails details, HWClientDetails clients)
Create a service instance from the given details.
Definition Services.cpp:333
Parent class of all APIs modeled as 'services'.
Definition Services.h:46
const std::type_info & Type
Definition Services.h:48
void registerBackend(const std::string &name, BackendCreate create)
std::function< std::unique_ptr< AcceleratorConnection >(Context &, std::string)> BackendCreate
Backends can register themselves to be connected via a connection string.
std::unique_ptr< AcceleratorConnection > connect(Context &ctxt, const std::string &backend, const std::string &connection)
std::unique_ptr< Engine > createEngine(AcceleratorConnection &conn, const std::string &dmaEngineName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create an engine by name.
Definition Engines.cpp:507
Definition esi.py:1
static std::filesystem::path getExePath()
Get the path to the currently running executable.
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:102
static void loadBackend(Context &ctxt, std::string backend)
Load a backend plugin dynamically.
static std::filesystem::path getLibPath()
Get the path to the currently running shared library.
static std::vector< std::filesystem::path > getESIBackendDirectories()
Get the list of directories to search for backend plugins.
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:101
std::map< ReadChannelPort *, std::pair< std::function< void(ReadChannelPort *, MessageData)>, std::future< MessageData > > > listeners
void addTask(std::function< void(void)> task)
void addListener(std::initializer_list< ReadChannelPort * > listenPorts, std::function< void(ReadChannelPort *, MessageData)> callback)
When there's data on any of the listenPorts, call the callback.
std::vector< std::function< void(void)> > taskList
Tasks which should be called on every loop iteration.