CIRCT  20.0.0git
esiCppAccel.cpp
Go to the documentation of this file.
1 //===- esiaccel.cpp - ESI runtime python bindings ---------------*- C++ -*-===//
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 // Simply wrap the C++ API into a Python module called 'esiaccel'.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "esi/Accelerator.h"
14 #include "esi/Services.h"
15 
16 #include "esi/backends/Cosim.h"
17 
18 #include <sstream>
19 
20 // pybind11 includes
21 #include <pybind11/pybind11.h>
22 namespace py = pybind11;
23 
24 #include <pybind11/stl.h>
25 
26 using namespace esi;
27 using namespace esi::services;
28 
29 namespace pybind11 {
30 /// Pybind11 needs a little help downcasting with non-bound instances.
31 template <>
32 struct polymorphic_type_hook<ChannelPort> {
33  static const void *get(const ChannelPort *port, const std::type_info *&type) {
34  if (auto p = dynamic_cast<const WriteChannelPort *>(port)) {
35  type = &typeid(WriteChannelPort);
36  return p;
37  }
38  if (auto p = dynamic_cast<const ReadChannelPort *>(port)) {
39  type = &typeid(ReadChannelPort);
40  return p;
41  }
42  return port;
43  }
44 };
45 template <>
46 struct polymorphic_type_hook<Service> {
47  static const void *get(const Service *svc, const std::type_info *&type) {
48  if (auto p = dynamic_cast<const MMIO *>(svc)) {
49  type = &typeid(MMIO);
50  return p;
51  }
52  if (auto p = dynamic_cast<const SysInfo *>(svc)) {
53  type = &typeid(SysInfo);
54  return p;
55  }
56  if (auto p = dynamic_cast<const HostMem *>(svc)) {
57  type = &typeid(HostMem);
58  return p;
59  }
60  return svc;
61  }
62 };
63 
64 namespace detail {
65 /// Pybind11 doesn't have a built-in type caster for std::any
66 /// (https://github.com/pybind/pybind11/issues/1590). We must provide one which
67 /// knows about all of the potential types which the any might be.
68 template <>
69 struct type_caster<std::any> {
70 public:
71  PYBIND11_TYPE_CASTER(std::any, const_name("object"));
72 
73  static handle cast(std::any src, return_value_policy /* policy */,
74  handle /* parent */) {
75  const std::type_info &t = src.type();
76  if (t == typeid(std::string))
77  return py::str(std::any_cast<std::string>(src));
78  else if (t == typeid(int64_t))
79  return py::int_(std::any_cast<int64_t>(src));
80  else if (t == typeid(uint64_t))
81  return py::int_(std::any_cast<uint64_t>(src));
82  else if (t == typeid(double))
83  return py::float_(std::any_cast<double>(src));
84  else if (t == typeid(bool))
85  return py::bool_(std::any_cast<bool>(src));
86  else if (t == typeid(std::nullptr_t))
87  return py::none();
88  return py::none();
89  }
90 };
91 } // namespace detail
92 } // namespace pybind11
93 
94 /// Resolve a Type to the Python wrapper object.
95 py::object getPyType(std::optional<const Type *> t) {
96  py::object typesModule = py::module_::import("esiaccel.types");
97  if (!t)
98  return py::none();
99  return typesModule.attr("_get_esi_type")(*t);
100 }
101 
102 // NOLINTNEXTLINE(readability-identifier-naming)
103 PYBIND11_MODULE(esiCppAccel, m) {
104  py::class_<Type>(m, "Type")
105  .def_property_readonly("id", &Type::getID)
106  .def("__repr__", [](Type &t) { return "<" + t.getID() + ">"; });
107  py::class_<ChannelType, Type>(m, "ChannelType")
108  .def_property_readonly("inner", &ChannelType::getInner,
109  py::return_value_policy::reference);
110  py::enum_<BundleType::Direction>(m, "Direction")
111  .value("To", BundleType::Direction::To)
112  .value("From", BundleType::Direction::From)
113  .export_values();
114  py::class_<BundleType, Type>(m, "BundleType")
115  .def_property_readonly("channels", &BundleType::getChannels,
116  py::return_value_policy::reference);
117  py::class_<VoidType, Type>(m, "VoidType");
118  py::class_<AnyType, Type>(m, "AnyType");
119  py::class_<BitVectorType, Type>(m, "BitVectorType")
120  .def_property_readonly("width", &BitVectorType::getWidth);
121  py::class_<BitsType, BitVectorType>(m, "BitsType");
122  py::class_<IntegerType, BitVectorType>(m, "IntegerType");
123  py::class_<SIntType, IntegerType>(m, "SIntType");
124  py::class_<UIntType, IntegerType>(m, "UIntType");
125  py::class_<StructType, Type>(m, "StructType")
126  .def_property_readonly("fields", &StructType::getFields,
127  py::return_value_policy::reference);
128  py::class_<ArrayType, Type>(m, "ArrayType")
129  .def_property_readonly("element", &ArrayType::getElementType,
130  py::return_value_policy::reference)
131  .def_property_readonly("size", &ArrayType::getSize);
132 
133  py::class_<Constant>(m, "Constant")
134  .def_property_readonly("value", [](Constant &c) { return c.value; })
135  .def_property_readonly(
136  "type", [](Constant &c) { return getPyType(*c.type); },
137  py::return_value_policy::reference);
138 
139  py::class_<AppID>(m, "AppID")
140  .def(py::init<std::string, std::optional<uint32_t>>(), py::arg("name"),
141  py::arg("idx") = std::nullopt)
142  .def_property_readonly("name", [](AppID &id) { return id.name; })
143  .def_property_readonly("idx",
144  [](AppID &id) -> py::object {
145  if (id.idx)
146  return py::cast(id.idx);
147  return py::none();
148  })
149  .def("__repr__",
150  [](AppID &id) {
151  std::string ret = "<" + id.name;
152  if (id.idx)
153  ret = ret + "[" + std::to_string(*id.idx) + "]";
154  ret = ret + ">";
155  return ret;
156  })
157  .def("__eq__", [](AppID &a, AppID &b) { return a == b; })
158  .def("__hash__", [](AppID &id) {
159  return utils::hash_combine(std::hash<std::string>{}(id.name),
160  std::hash<uint32_t>{}(id.idx.value_or(-1)));
161  });
162  py::class_<AppIDPath>(m, "AppIDPath").def("__repr__", &AppIDPath::toStr);
163 
164  py::class_<ModuleInfo>(m, "ModuleInfo")
165  .def_property_readonly("name", [](ModuleInfo &info) { return info.name; })
166  .def_property_readonly("summary",
167  [](ModuleInfo &info) { return info.summary; })
168  .def_property_readonly("version",
169  [](ModuleInfo &info) { return info.version; })
170  .def_property_readonly("repo", [](ModuleInfo &info) { return info.repo; })
171  .def_property_readonly("commit_hash",
172  [](ModuleInfo &info) { return info.commitHash; })
173  .def_property_readonly("constants",
174  [](ModuleInfo &info) { return info.constants; })
175  // TODO: "extra" field.
176  .def("__repr__", [](ModuleInfo &info) {
177  std::string ret;
178  std::stringstream os(ret);
179  os << info;
180  return os.str();
181  });
182 
183  py::enum_<Logger::Level>(m, "LogLevel")
184  .value("Debug", Logger::Level::Debug)
185  .value("Info", Logger::Level::Info)
186  .value("Warning", Logger::Level::Warning)
187  .value("Error", Logger::Level::Error)
188  .export_values();
189  py::class_<Logger>(m, "Logger");
190 
191  py::class_<services::Service>(m, "Service");
192 
193  py::class_<SysInfo, services::Service>(m, "SysInfo")
194  .def("esi_version", &SysInfo::getEsiVersion)
195  .def("json_manifest", &SysInfo::getJsonManifest);
196 
197  py::class_<MMIO::RegionDescriptor>(m, "MMIORegionDescriptor")
198  .def_property_readonly("base",
199  [](MMIO::RegionDescriptor &r) { return r.base; })
200  .def_property_readonly("size",
201  [](MMIO::RegionDescriptor &r) { return r.size; });
202  py::class_<services::MMIO, services::Service>(m, "MMIO")
203  .def("read", &services::MMIO::read)
204  .def("write", &services::MMIO::write)
205  .def_property_readonly("regions", &services::MMIO::getRegions,
206  py::return_value_policy::reference);
207 
208  py::class_<services::HostMem::HostMemRegion>(m, "HostMemRegion")
209  .def_property_readonly("ptr",
211  return reinterpret_cast<uintptr_t>(mem.getPtr());
212  })
213  .def_property_readonly("size",
215 
216  py::class_<services::HostMem::Options>(m, "HostMemOptions")
217  .def(py::init<>())
218  .def_readwrite("writeable", &services::HostMem::Options::writeable)
219  .def_readwrite("use_large_pages",
221  .def("__repr__", [](services::HostMem::Options &opts) {
222  std::string ret = "HostMemOptions(";
223  if (opts.writeable)
224  ret += "writeable ";
225  if (opts.useLargePages)
226  ret += "use_large_pages";
227  ret += ")";
228  return ret;
229  });
230 
231  py::class_<services::HostMem, services::Service>(m, "HostMem")
232  .def("allocate", &services::HostMem::allocate, py::arg("size"),
233  py::arg("options") = services::HostMem::Options(),
234  py::return_value_policy::take_ownership)
235  .def(
236  "map_memory",
237  [](HostMem &self, uintptr_t ptr, size_t size, HostMem::Options opts) {
238  return self.mapMemory(reinterpret_cast<void *>(ptr), size, opts);
239  },
240  py::arg("ptr"), py::arg("size"),
241  py::arg("options") = services::HostMem::Options())
242  .def(
243  "unmap_memory",
244  [](HostMem &self, uintptr_t ptr) {
245  return self.unmapMemory(reinterpret_cast<void *>(ptr));
246  },
247  py::arg("ptr"));
248 
249  // py::class_<std::__basic_future<MessageData>>(m, "MessageDataFuture");
250  py::class_<std::future<MessageData>>(m, "MessageDataFuture")
251  .def("valid",
252  [](std::future<MessageData> &f) {
253  // For some reason, if we just pass the function pointer, pybind11
254  // sees `std::__basic_future` as the type and pybind11_stubgen
255  // emits an error.
256  return f.valid();
257  })
258  .def("wait", &std::future<MessageData>::wait)
259  .def("get", [](std::future<MessageData> &f) {
260  MessageData data = f.get();
261  return py::bytearray((const char *)data.getBytes(), data.getSize());
262  });
263 
264  py::class_<ChannelPort>(m, "ChannelPort")
265  .def("connect", &ChannelPort::connect,
266  py::arg("buffer_size") = std::nullopt)
267  .def("disconnect", &ChannelPort::disconnect)
268  .def_property_readonly("type", &ChannelPort::getType,
269  py::return_value_policy::reference);
270 
271  py::class_<WriteChannelPort, ChannelPort>(m, "WriteChannelPort")
272  .def("write",
273  [](WriteChannelPort &p, py::bytearray &data) {
274  py::buffer_info info(py::buffer(data).request());
275  std::vector<uint8_t> dataVec((uint8_t *)info.ptr,
276  (uint8_t *)info.ptr + info.size);
277  p.write(dataVec);
278  })
279  .def("tryWrite", [](WriteChannelPort &p, py::bytearray &data) {
280  py::buffer_info info(py::buffer(data).request());
281  std::vector<uint8_t> dataVec((uint8_t *)info.ptr,
282  (uint8_t *)info.ptr + info.size);
283  return p.tryWrite(dataVec);
284  });
285  py::class_<ReadChannelPort, ChannelPort>(m, "ReadChannelPort")
286  .def(
287  "read",
288  [](ReadChannelPort &p) -> py::bytearray {
289  MessageData data;
290  p.read(data);
291  return py::bytearray((const char *)data.getBytes(), data.getSize());
292  },
293  "Read data from the channel. Blocking.")
294  .def("read_async", &ReadChannelPort::readAsync);
295 
296  py::class_<BundlePort>(m, "BundlePort")
297  .def_property_readonly("id", &BundlePort::getID)
298  .def_property_readonly("channels", &BundlePort::getChannels,
299  py::return_value_policy::reference)
300  .def("getWrite", &BundlePort::getRawWrite,
301  py::return_value_policy::reference)
302  .def("getRead", &BundlePort::getRawRead,
303  py::return_value_policy::reference);
304 
305  py::class_<ServicePort, BundlePort>(m, "ServicePort");
306 
307  py::class_<MMIO::MMIORegion, ServicePort>(m, "MMIORegion")
308  .def_property_readonly("descriptor", &MMIO::MMIORegion::getDescriptor)
309  .def("read", &MMIO::MMIORegion::read)
310  .def("write", &MMIO::MMIORegion::write);
311 
312  py::class_<FuncService::Function, ServicePort>(m, "Function")
313  .def(
314  "call",
315  [](FuncService::Function &self,
316  py::bytearray msg) -> std::future<MessageData> {
317  py::buffer_info info(py::buffer(msg).request());
318  std::vector<uint8_t> dataVec((uint8_t *)info.ptr,
319  (uint8_t *)info.ptr + info.size);
320  MessageData data(dataVec);
321  return self.call(data);
322  },
323  py::return_value_policy::take_ownership)
324  .def("connect", &FuncService::Function::connect);
325 
326  // Store this variable (not commonly done) as the "children" method needs for
327  // "Instance" to be defined first.
328  auto hwmodule =
329  py::class_<HWModule>(m, "HWModule")
330  .def_property_readonly("info", &HWModule::getInfo)
331  .def_property_readonly("ports", &HWModule::getPorts,
332  py::return_value_policy::reference)
333  .def_property_readonly("services", &HWModule::getServices,
334  py::return_value_policy::reference);
335 
336  // In order to inherit methods from "HWModule", it needs to be defined first.
337  py::class_<Instance, HWModule>(m, "Instance")
338  .def_property_readonly("id", &Instance::getID);
339 
340  py::class_<Accelerator, HWModule>(m, "Accelerator");
341 
342  // Since this returns a vector of Instance*, we need to define Instance first
343  // or else pybind11-stubgen complains.
344  hwmodule.def_property_readonly("children", &HWModule::getChildren,
345  py::return_value_policy::reference);
346 
347  auto accConn = py::class_<AcceleratorConnection>(m, "AcceleratorConnection");
348 
349  py::class_<Context>(m, "Context")
350  .def(py::init<>())
351  .def("connect", &Context::connect)
352  .def("set_stdio_logger", [](Context &ctxt, Logger::Level level) {
353  ctxt.setLogger(std::make_unique<StreamLogger>(level));
354  });
355 
356  accConn.def(py::init(&registry::connect))
357  .def(
358  "sysinfo",
359  [](AcceleratorConnection &acc) {
360  return acc.getService<services::SysInfo>({});
361  },
362  py::return_value_policy::reference)
363  .def(
364  "get_service_mmio",
365  [](AcceleratorConnection &acc) {
366  return acc.getService<services::MMIO>({});
367  },
368  py::return_value_policy::reference)
369  .def(
370  "get_service_hostmem",
371  [](AcceleratorConnection &acc) {
372  return acc.getService<services::HostMem>({});
373  },
374  py::return_value_policy::reference);
375 
376  py::class_<Manifest>(m, "Manifest")
377  .def(py::init<Context &, std::string>())
378  .def_property_readonly("api_version", &Manifest::getApiVersion)
379  .def(
380  "build_accelerator",
381  [&](Manifest &m, AcceleratorConnection &conn) {
382  auto acc = m.buildAccelerator(conn);
383  conn.getServiceThread()->addPoll(*acc);
384  return acc;
385  },
386  py::return_value_policy::reference)
387  .def_property_readonly("type_table",
388  [](Manifest &m) {
389  std::vector<py::object> ret;
390  std::ranges::transform(m.getTypeTable(),
391  std::back_inserter(ret),
392  getPyType);
393  return ret;
394  })
395  .def_property_readonly("module_infos", &Manifest::getModuleInfos);
396 }
Abstract class representing a connection to an accelerator.
Definition: Accelerator.h:78
ServiceClass * getService(AppIDPath id={}, std::string implName={}, ServiceImplDetails details={}, HWClientDetails clients={})
Get a typed reference to a particular service type.
Definition: Accelerator.h:112
AcceleratorServiceThread * getServiceThread()
Return a pointer to the accelerator 'service' thread (or threads).
Definition: Accelerator.cpp:40
std::string toStr() const
Definition: Manifest.cpp:708
const Type * getElementType() const
Definition: Types.h:159
uint64_t getSize() const
Definition: Types.h:160
uint64_t getWidth() const
Definition: Types.h:96
ReadChannelPort & getRawRead(const std::string &name) const
Definition: Ports.cpp:35
WriteChannelPort & getRawWrite(const std::string &name) const
Get access to the raw byte streams of a channel.
Definition: Ports.cpp:25
const std::map< std::string, ChannelPort & > & getChannels() const
Definition: Ports.h:205
AppID getID() const
Get the ID of the port.
Definition: Ports.h:197
const ChannelVector & getChannels() const
Definition: Types.h:54
Unidirectional channels are the basic communication primitive between the host and accelerator.
Definition: Ports.h:33
virtual void disconnect()=0
virtual void connect(std::optional< unsigned > bufferSize=std::nullopt)=0
Set up a connection to the accelerator.
const Type * getType() const
Definition: Ports.h:59
const Type * getInner() const
Definition: Types.h:66
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition: Context.h:31
std::unique_ptr< AcceleratorConnection > connect(std::string backend, std::string connection)
Connect to an accelerator backend.
Definition: Context.cpp:27
const std::map< AppID, Instance * > & getChildren() const
Access the module's children by ID.
Definition: Design.h:67
const std::vector< services::Service * > & getServices() const
Access the services provided by this module.
Definition: Design.h:80
std::optional< ModuleInfo > getInfo() const
Access the module's metadata, if any.
Definition: Design.h:58
const std::map< AppID, const BundlePort & > & getPorts() const
Access the module's ports by ID.
Definition: Design.h:76
const AppID getID() const
Get the instance's ID, which it will always have.
Definition: Design.h:112
Class to parse a manifest.
Definition: Manifest.h:39
uint32_t getApiVersion() const
std::vector< ModuleInfo > getModuleInfos() const
A logical chunk of data representing serialized data.
Definition: Common.h:103
A ChannelPort which reads data from the accelerator.
Definition: Ports.h:103
virtual std::future< MessageData > readAsync()
Asynchronous read.
Definition: Ports.cpp:77
const FieldVector & getFields() const
Definition: Types.h:137
Root class of the ESI type system.
Definition: Types.h:27
ID getID() const
Definition: Types.h:33
A ChannelPort which sends data to the accelerator.
Definition: Ports.h:74
virtual void write(const MessageData &)=0
A very basic blocking write API.
virtual bool tryWrite(const MessageData &data)=0
A basic non-blocking write API.
A function call which gets attached to a service port.
Definition: Services.h:247
virtual std::unique_ptr< HostMemRegion > allocate(std::size_t size, Options opts) const =0
Allocate a region of host memory in accelerator accessible address space.
virtual uint64_t read(uint32_t addr) const
Read a 64-bit value from this region, not the global address space.
Definition: Services.cpp:126
virtual void write(uint32_t addr, uint64_t data)
Write a 64-bit value to this region, not the global address space.
Definition: Services.cpp:131
virtual RegionDescriptor getDescriptor() const
Get the offset (and size) of the region in the parent (usually global) MMIO address space.
Definition: Services.h:162
virtual uint64_t read(uint32_t addr) const =0
Read a 64-bit value from the global MMIO space.
virtual void write(uint32_t addr, uint64_t data)=0
Write a 64-bit value to the global MMIO space.
const std::map< AppIDPath, RegionDescriptor > & getRegions() const
Get the regions of MMIO space that this service manages.
Definition: Services.h:130
Parent class of all APIs modeled as 'services'.
Definition: Services.h:45
Information about the Accelerator system.
Definition: Services.h:93
virtual std::string getJsonManifest() const
Return the JSON-formatted system manifest.
Definition: Services.cpp:40
virtual uint32_t getEsiVersion() const =0
Get the ESI version number to check version compatibility.
PYBIND11_MODULE(esiCppAccel, m)
py::object getPyType(std::optional< const Type * > t)
Resolve a Type to the Python wrapper object.
Definition: esiCppAccel.cpp:95
std::unique_ptr< AcceleratorConnection > connect(Context &ctxt, const std::string &backend, const std::string &connection)
size_t hash_combine(size_t h1, size_t h2)
C++'s stdlib doesn't have a hash_combine function. This is a simple one.
Definition: Utils.h:32
Definition: esi.py:1
std::any value
Definition: Common.h:58
std::optional< const Type * > type
Definition: Common.h:59
std::optional< std::string > version
Definition: Common.h:65
std::optional< std::string > name
Definition: Common.h:63
std::map< std::string, Constant > constants
Definition: Common.h:68
std::optional< std::string > commitHash
Definition: Common.h:67
std::optional< std::string > repo
Definition: Common.h:66
std::optional< std::string > summary
Definition: Common.h:64
RAII memory region for host memory.
Definition: Services.h:201
virtual void * getPtr() const =0
virtual std::size_t getSize() const =0
Options for allocating host memory.
Definition: Services.h:209
Describe a region (slice) of MMIO space.
Definition: Services.h:114
static handle cast(std::any src, return_value_policy, handle)
Definition: esiCppAccel.cpp:73
PYBIND11_TYPE_CASTER(std::any, const_name("object"))
static const void * get(const ChannelPort *port, const std::type_info *&type)
Definition: esiCppAccel.cpp:33
static const void * get(const Service *svc, const std::type_info *&type)
Definition: esiCppAccel.cpp:47