CIRCT  19.0.0git
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 <filesystem>
19 #include <map>
20 #include <stdexcept>
21 
22 #include <iostream>
23 
24 #ifdef __linux__
25 #include <dlfcn.h>
26 #include <linux/limits.h>
27 #include <unistd.h>
28 #elif _WIN32
29 #include <windows.h>
30 #endif
31 
32 using namespace esi;
33 using namespace esi::services;
34 
35 namespace esi {
37  : ctxt(ctxt), serviceThread(std::make_unique<AcceleratorServiceThread>()) {}
38 
40  AppIDPath id,
41  std::string implName,
42  ServiceImplDetails details,
43  HWClientDetails clients) {
44  std::unique_ptr<Service> &cacheEntry = serviceCache[make_tuple(&svcType, id)];
45  if (cacheEntry == nullptr) {
46  Service *svc = createService(svcType, id, implName, details, clients);
47  if (!svc)
48  svc = ServiceRegistry::createService(this, svcType, id, implName, details,
49  clients);
50  if (!svc)
51  return nullptr;
52  cacheEntry = std::unique_ptr<Service>(svc);
53  }
54  return cacheEntry.get();
55 }
56 
57 /// Get the path to the currently running executable.
58 static std::filesystem::path getExePath() {
59 #ifdef __linux__
60  char result[PATH_MAX];
61  ssize_t count = readlink("/proc/self/exe", result, PATH_MAX);
62  if (count == -1)
63  throw std::runtime_error("Could not get executable path");
64  return std::filesystem::path(std::string(result, count));
65 #elif _WIN32
66  char buffer[MAX_PATH];
67  DWORD length = GetModuleFileNameA(NULL, buffer, MAX_PATH);
68  if (length == 0)
69  throw std::runtime_error("Could not get executable path");
70  return std::filesystem::path(std::string(buffer, length));
71 #else
72 #eror "Unsupported platform"
73 #endif
74 }
75 
76 /// Get the path to the currently running shared library.
77 static std::filesystem::path getLibPath() {
78 #ifdef __linux__
79  Dl_info dl_info;
80  dladdr((void *)getLibPath, &dl_info);
81  return std::filesystem::path(std::string(dl_info.dli_fname));
82 #elif _WIN32
83  HMODULE hModule = NULL;
84  if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
85  GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
86  reinterpret_cast<LPCSTR>(&getLibPath), &hModule)) {
87  // Handle error
88  return std::filesystem::path();
89  }
90 
91  char buffer[MAX_PATH];
92  DWORD length = GetModuleFileNameA(hModule, buffer, MAX_PATH);
93  if (length == 0)
94  throw std::runtime_error("Could not get library path");
95 
96  return std::filesystem::path(std::string(buffer, length));
97 #else
98 #eror "Unsupported platform"
99 #endif
100 }
101 
102 /// Load a backend plugin dynamically. Plugins are expected to be named
103 /// lib<BackendName>Backend.so and located in one of 1) CWD, 2) in the same
104 /// directory as the application, or 3) in the same directory as this library.
105 static void loadBackend(std::string backend) {
106  backend[0] = toupper(backend[0]);
107 
108  // Get the file name we are looking for.
109 #ifdef __linux__
110  std::string backendFileName = "lib" + backend + "Backend.so";
111 #elif _WIN32
112  std::string backendFileName = backend + "Backend.dll";
113 #else
114 #eror "Unsupported platform"
115 #endif
116 
117  // Look for library using the C++ std API.
118  // TODO: once the runtime has a logging framework, log the paths we are
119  // trying.
120 
121  // First, try the current directory.
122  std::filesystem::path backendPath = backendFileName;
123  std::string backendPathStr;
124  if (!std::filesystem::exists(backendPath)) {
125  // Next, try the directory of the executable.
126  backendPath = getExePath().parent_path().append(backendFileName);
127  if (!std::filesystem::exists(backendPath)) {
128  // Finally, try the directory of the library.
129  backendPath = getLibPath().parent_path().append(backendFileName);
130  if (!std::filesystem::exists(backendPath))
131  // If all else fails, just try the name.
132  backendPathStr = backendFileName;
133  }
134  }
135  // If the path was found, convert it to a string.
136  if (backendPathStr.empty())
137  backendPathStr = backendPath.string();
138  else
139  // Otherwise, signal that the path wasn't found by clearing the path and
140  // just use the name. (This is only used on Windows to add the same
141  // directory as the backend DLL to the DLL search path.)
142  backendPath.clear();
143 
144  // Attempt to load it.
145 #ifdef __linux__
146  void *handle = dlopen(backendPathStr.c_str(), RTLD_NOW | RTLD_GLOBAL);
147  if (!handle)
148  throw std::runtime_error("While attempting to load backend plugin: " +
149  std::string(dlerror()));
150 #elif _WIN32
151  // Set the DLL directory to the same directory as the backend DLL in case it
152  // has transitive dependencies.
153  if (backendPath != std::filesystem::path()) {
154  std::filesystem::path backendPathParent = backendPath.parent_path();
155  if (SetDllDirectoryA(backendPathParent.string().c_str()) == 0)
156  throw std::runtime_error("While setting DLL directory: " +
157  std::to_string(GetLastError()));
158  }
159 
160  // Load the backend plugin.
161  HMODULE handle = LoadLibraryA(backendPathStr.c_str());
162  if (!handle) {
163  DWORD error = GetLastError();
164  if (error == ERROR_MOD_NOT_FOUND)
165  throw std::runtime_error("While attempting to load backend plugin: " +
166  backendPathStr + " not found");
167  throw std::runtime_error("While attempting to load backend plugin: " +
168  std::to_string(error));
169  }
170 #else
171 #eror "Unsupported platform"
172 #endif
173 }
174 
175 namespace registry {
176 namespace internal {
177 
178 static std::map<std::string, BackendCreate> backendRegistry;
179 void registerBackend(std::string name, BackendCreate create) {
180  if (backendRegistry.count(name))
181  throw std::runtime_error("Backend already exists in registry");
182  backendRegistry[name] = create;
183 }
184 } // namespace internal
185 
186 std::unique_ptr<AcceleratorConnection>
187 connect(Context &ctxt, std::string backend, std::string connection) {
188  auto f = internal::backendRegistry.find(backend);
189  if (f == internal::backendRegistry.end()) {
190  // If it's not already found in the registry, try to load it dynamically.
191  loadBackend(backend);
192  f = internal::backendRegistry.find(backend);
193  if (f == internal::backendRegistry.end())
194  throw std::runtime_error("Backend '" + backend + "' not found");
195  }
196  return f->second(ctxt, connection);
197 }
198 
199 } // namespace registry
200 
202  Impl() {}
203  void start() { me = std::thread(&Impl::loop, this); }
204  void stop() {
205  shutdown = true;
206  me.join();
207  }
208  /// When there's data on any of the listenPorts, call the callback. This
209  /// method can be called from any thread.
210  void
211  addListener(std::initializer_list<ReadChannelPort *> listenPorts,
212  std::function<void(ReadChannelPort *, MessageData)> callback);
213 
214 private:
215  void loop();
216  volatile bool shutdown = false;
217  std::thread me;
218 
219  // Protect the listeners std::map.
220  std::mutex listenerMutex;
221  // Map of read ports to callbacks.
222  std::map<ReadChannelPort *,
223  std::pair<std::function<void(ReadChannelPort *, MessageData)>,
224  std::future<MessageData>>>
226 };
227 
229  // These two variables should logically be in the loop, but this avoids
230  // reconstructing them on each iteration.
231  std::vector<std::tuple<ReadChannelPort *,
232  std::function<void(ReadChannelPort *, MessageData)>,
233  MessageData>>
234  portUnlockWorkList;
235  MessageData data;
236 
237  while (!shutdown) {
238  // Ideally we'd have some wake notification here, but this sufficies for
239  // now.
240  // TODO: investigate better ways to do this.
241  std::this_thread::sleep_for(std::chrono::microseconds(100));
242 
243  // Check and gather data from all the read ports we are monitoring. Put the
244  // callbacks to be called later so we can release the lock.
245  {
246  std::lock_guard<std::mutex> g(listenerMutex);
247  for (auto &[channel, cbfPair] : listeners) {
248  assert(channel && "Null channel in listener list");
249  std::future<MessageData> &f = cbfPair.second;
250  if (f.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
251  portUnlockWorkList.emplace_back(channel, cbfPair.first, f.get());
252  f = channel->readAsync();
253  }
254  }
255  }
256 
257  // Call the callbacks outside the lock.
258  for (auto [channel, cb, data] : portUnlockWorkList)
259  cb(channel, std::move(data));
260 
261  // Clear the worklist for the next iteration.
262  portUnlockWorkList.clear();
263  }
264 }
265 
267  std::initializer_list<ReadChannelPort *> listenPorts,
268  std::function<void(ReadChannelPort *, MessageData)> callback) {
269  std::lock_guard<std::mutex> g(listenerMutex);
270  for (auto port : listenPorts) {
271  if (listeners.count(port))
272  throw std::runtime_error("Port already has a listener");
273  listeners[port] = std::make_pair(callback, port->readAsync());
274  }
275 }
276 
277 } // namespace esi
278 
280  : impl(std::make_unique<Impl>()) {
281  impl->start();
282 }
284 
286  if (impl) {
287  impl->stop();
288  impl.reset();
289  }
290 }
291 
292 // When there's data on any of the listenPorts, call the callback. This is
293 // kinda silly now that we have callback port support, especially given the
294 // polling loop. Keep the functionality for now.
296  std::initializer_list<ReadChannelPort *> listenPorts,
297  std::function<void(ReadChannelPort *, MessageData)> callback) {
298  assert(impl && "Service thread not running");
299  impl->addListener(listenPorts, callback);
300 }
301 
303  if (serviceThread) {
304  serviceThread->stop();
305  serviceThread.reset();
306  }
307 }
assert(baseType &&"element must be base type")
void disconnect()
Disconnect from the accelerator cleanly.
ServiceClass * getService(AppIDPath id={}, std::string implName={}, ServiceImplDetails details={}, HWClientDetails clients={})
Get a typed reference to a particular service type.
Definition: Accelerator.h:99
std::map< ServiceCacheKey, std::unique_ptr< Service > > serviceCache
Definition: Accelerator.h:128
std::unique_ptr< AcceleratorServiceThread > serviceThread
Definition: Accelerator.h:130
virtual Service * createService(Service::Type service, AppIDPath idPath, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients)=0
Called by getServiceImpl exclusively.
AcceleratorConnection(Context &ctxt)
Definition: Accelerator.cpp:36
Background thread which services various requests.
Definition: Accelerator.h:164
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
Definition: Accelerator.h:179
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition: Context.h:30
A logical chunk of data representing serialized data.
Definition: Common.h:86
A ChannelPort which reads data from the accelerator.
Definition: Ports.h:69
Parent class of all APIs modeled as 'services'.
Definition: Services.h:42
const std::type_info & Type
Definition: Services.h:44
void registerBackend(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.
Definition: Accelerator.h:144
static std::map< std::string, BackendCreate > backendRegistry
std::unique_ptr< AcceleratorConnection > connect(Context &ctxt, std::string backend, std::string connection)
Definition: esi.py:1
static std::filesystem::path getExePath()
Get the path to the currently running executable.
Definition: Accelerator.cpp:58
std::map< std::string, std::any > ServiceImplDetails
Definition: Common.h:81
static std::filesystem::path getLibPath()
Get the path to the currently running shared library.
Definition: Accelerator.cpp:77
std::vector< HWClientDetail > HWClientDetails
Definition: Common.h:80
static void loadBackend(std::string backend)
Load a backend plugin dynamically.
std::map< ReadChannelPort *, std::pair< std::function< void(ReadChannelPort *, MessageData)>, std::future< MessageData > > > listeners
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.