Skip to content

Commit

Permalink
[ESI][Runtime] Wire up services and ports into the design tree (#6406)
Browse files Browse the repository at this point in the history
Create ports in the design hierarchy and wire them up to the correct service. Patch is a bit larger than it probably should be since it includes some not-unrelated NFC refactoring and `TraceAccelerator` support for writing to trace files.
  • Loading branch information
teqdruid authored Nov 10, 2023
1 parent 97c770c commit 106b3a2
Show file tree
Hide file tree
Showing 21 changed files with 813 additions and 92 deletions.
11 changes: 10 additions & 1 deletion integration_test/Dialect/ESI/runtime/loopback.mlir.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import esi
import sys

acc = esi.Accelerator(sys.argv[1], sys.argv[2])
platform = sys.argv[1]
acc = esi.Accelerator(platform, sys.argv[2])

assert acc.sysinfo().esi_version() == 1
m = acc.manifest()
Expand All @@ -14,3 +15,11 @@
print(appid)
assert appid.name == "loopback_inst"
assert appid.idx == 0

# Services are only hooked up for the trace backend currently.
if platform == "trace":
d.children[0].ports[0].getWrite("recv").write([45] * 128)
d.children[0].ports[0].getWrite("recv").write([24])
d.children[0].ports[0].getWrite("recv").write([24, 45, 138])

d.children[0].ports[1].getRead("send").read(8)
1 change: 1 addition & 0 deletions lib/Dialect/ESI/ESIOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,7 @@ void ServiceRequestRecordOp::getDetails(
getDirectionAttrName(),
StringAttr::get(ctxt, stringifyBundleDirection(getDirection())));
results.emplace_back(getBundleTypeAttrName(), getBundleTypeAttr());
results.emplace_back(getServicePortAttrName(), getServicePortAttr());
}

StringRef SymbolMetadataOp::getManifestClass() { return "sym_info"; }
Expand Down
2 changes: 2 additions & 0 deletions lib/Dialect/ESI/runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ include_directories(cpp/include)

set(ESIRuntimeSources
cpp/lib/Accelerator.cpp
cpp/lib/Design.cpp
cpp/lib/Manifest.cpp
cpp/lib/StdServices.cpp
cpp/lib/Utils.cpp

cpp/lib/backends/Trace.cpp
)
Expand Down
77 changes: 69 additions & 8 deletions lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
#ifndef ESI_ACCELERATOR_H
#define ESI_ACCELERATOR_H

#include "esi/Design.h"
#include "esi/Manifest.h"

#include <any>
#include <cstdint>
#include <functional>
#include <map>
Expand All @@ -36,13 +40,63 @@ constexpr uint32_t MagicNumberHi = 0x207D98E5;
constexpr uint32_t VersionNumberOffset = MagicNumOffset + 8;
constexpr uint32_t ExpectedVersionNumber = 0;

/// Unidirectional channels are the basic communication primitive between the
/// host and accelerator. A 'ChannelPort' is the host side of a channel. It can
/// be either read or write but not both. At this level, channels are untyped --
/// just streams of bytes. They are not intended to be used directly by users
/// but used by higher level APIs which add types.
class ChannelPort {
public:
virtual ~ChannelPort() = default;
};

/// A ChannelPort which sends data to the accelerator.
class WriteChannelPort : public ChannelPort {
public:
/// A very basic write API. Will likely change for performance reasons.
virtual void write(const void *data, size_t size) = 0;
};

/// A ChannelPort which reads data from the accelerator.
class ReadChannelPort : public ChannelPort {
public:
/// Specify a buffer to read into and a maximum size to read. Returns the
/// number of bytes read, or -1 on error. Basic API, will likely change for
/// performance reasons.
virtual ssize_t read(void *data, size_t maxSize) = 0;
};

namespace services {
/// Parent class of all APIs modeled as 'services'. May or may not map to a
/// hardware side 'service'.
class Service {
public:
using Type = const std::type_info &;
virtual ~Service() = default;

virtual std::string getServiceSymbol() const = 0;
};

/// A service for which there are no standard services registered. Requires
/// ports be added to the design hierarchy instead of high level interfaces like
/// the ones in StdServices.h.
class CustomService : public Service {
public:
CustomService(AppIDPath idPath, const ServiceImplDetails &details,
const HWClientDetails &clients);
virtual ~CustomService() = default;

virtual std::string getServiceSymbol() const { return _serviceSymbol; }

/// Request the host side channel ports for a particular instance (identified
/// by the AppID path). For convenience, provide the bundle type and direction
/// of the bundle port.
virtual std::map<std::string, ChannelPort &>
requestChannelsFor(AppIDPath, const BundleType &,
BundlePort::Direction portDir) = 0;

private:
std::string _serviceSymbol;
};
} // namespace services

Expand All @@ -51,28 +105,35 @@ class Accelerator {
public:
virtual ~Accelerator() = default;

using Service = services::Service;
/// Get a typed reference to a particular service type. Caller does *not* take
/// ownership of the returned pointer -- the Accelerator object owns it.
/// Pointer lifetime ends with the Accelerator lifetime.
template <typename ServiceClass>
ServiceClass *getService() {
return dynamic_cast<ServiceClass *>(getServiceImpl(typeid(ServiceClass)));
ServiceClass *getService(AppIDPath id = {}, ServiceImplDetails details = {},
HWClientDetails clients = {}) {
return dynamic_cast<ServiceClass *>(
getService(typeid(ServiceClass), id, details, clients));
}
/// Calls `createService` and caches the result. Subclasses can override if
/// they want to use their own caching mechanism.
virtual Service *getService(Service::Type service, AppIDPath id = {},
ServiceImplDetails details = {},
HWClientDetails clients = {});

protected:
using Service = services::Service;
/// Called by `getServiceImpl` exclusively. It wraps the pointer returned by
/// this in a unique_ptr and caches it. Separate this from the
/// wrapping/caching since wrapping/caching is an implementation detail.
virtual Service *createService(Service::Type service) = 0;
/// Calls `createService` and caches the result. Subclasses can override if
/// they want to use their own caching mechanism.
virtual Service *getServiceImpl(Service::Type service);
virtual Service *createService(Service::Type service, AppIDPath idPath,
const ServiceImplDetails &details,
const HWClientDetails &clients) = 0;

private:
/// Cache services via a unique_ptr so they get free'd automatically when
/// Accelerator objects get deconstructed.
std::map<const std::type_info *, std::unique_ptr<Service>> serviceCache;
using ServiceCacheKey = std::tuple<const std::type_info *, AppIDPath>;
std::map<ServiceCacheKey, std::unique_ptr<Service>> serviceCache;
};

namespace registry {
Expand Down
55 changes: 50 additions & 5 deletions lib/Dialect/ESI/runtime/cpp/include/esi/Design.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
#ifndef ESI_DESIGN_H
#define ESI_DESIGN_H

#include "esi/Accelerator.h"
#include "esi/Manifest.h"

#include <any>
Expand All @@ -35,21 +34,65 @@
namespace esi {
// Forward declarations.
class Instance;
class ChannelPort;
class WriteChannelPort;
class ReadChannelPort;
namespace services {
class Service;
}

/// Services provide connections to 'bundles' -- collections of named,
/// unidirectional communication channels. This class provides access to those
/// ChannelPorts.
class BundlePort {
public:
/// Direction of a bundle. This -- combined with the channel direction in the
/// bundle -- can be used to determine if a channel should be writing to or
/// reading from the accelerator.
enum Direction { ToServer, ToClient };

/// Compute the direction of a channel given the bundle direction and the
/// bundle port's direction.
static bool isWrite(BundleType::Direction bundleDir, Direction svcDir) {
if (svcDir == Direction::ToClient)
return bundleDir == BundleType::Direction::To;
return bundleDir == BundleType::Direction::From;
}

/// Construct a port.
BundlePort(AppID id, std::map<std::string, ChannelPort &> channels);

/// Get access to the raw byte streams of a channel. Intended for internal
/// usage and binding to other languages (e.g. Python) which have their own
/// message serialization code.
WriteChannelPort &getRawWrite(const std::string &name) const;
ReadChannelPort &getRawRead(const std::string &name) const;

private:
AppID _id;
std::map<std::string, ChannelPort &> _channels;
};

class Design {
public:
Design(std::optional<ModuleInfo> info,
std::vector<std::unique_ptr<Instance>> children)
: _info(info), _children(std::move(children)) {}
std::vector<std::unique_ptr<Instance>> children,
std::vector<services::Service *> services,
std::vector<BundlePort> ports)
: _info(info), _children(std::move(children)), _services(services),
_ports(ports) {}

std::optional<ModuleInfo> info() const { return _info; }
const std::vector<std::unique_ptr<Instance>> &children() const {
return _children;
}
const std::vector<BundlePort> &getPorts() const { return _ports; }

protected:
const std::optional<ModuleInfo> _info;
const std::vector<std::unique_ptr<Instance>> _children;
const std::vector<services::Service *> _services;
const std::vector<BundlePort> _ports;
};

class Instance : public Design {
Expand All @@ -58,8 +101,10 @@ class Instance : public Design {
Instance(const Instance &) = delete;
~Instance() = default;
Instance(AppID id, std::optional<ModuleInfo> info,
std::vector<std::unique_ptr<Instance>> children)
: Design(info, std::move(children)), id_(id) {}
std::vector<std::unique_ptr<Instance>> children,
std::vector<services::Service *> services,
std::vector<BundlePort> ports)
: Design(info, std::move(children), services, ports), id_(id) {}

const AppID id() const { return id_; }

Expand Down
22 changes: 21 additions & 1 deletion lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <map>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>

Expand All @@ -37,8 +38,15 @@ namespace esi {
struct AppID {
const std::string name;
const std::optional<uint32_t> idx;

bool operator==(const AppID &other) const {
return name == other.name && idx == other.idx;
}
bool operator!=(const AppID &other) const { return !(*this == other); }
};
using AppIDPath = std::vector<AppID>;
bool operator<(const AppIDPath &a, const AppIDPath &b);
std::ostream &operator<<(std::ostream &, const AppIDPath &);

struct ModuleInfo {
const std::optional<std::string> name;
Expand All @@ -49,11 +57,23 @@ struct ModuleInfo {
const std::map<std::string, std::any> extra;
};

struct ServicePort {
/// A description of a service port. Used pretty exclusively in setting up the
/// design.
struct ServicePortDesc {
std::string name;
std::string portName;
};

/// A description of a hardware client. Used pretty exclusively in setting up
/// the design.
struct HWClientDetail {
AppIDPath path;
ServicePortDesc port;
std::map<std::string, std::any> implOptions;
};
using HWClientDetails = std::vector<HWClientDetail>;
using ServiceImplDetails = std::map<std::string, std::any>;

//===----------------------------------------------------------------------===//
// Manifest parsing and API creation.
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 3 additions & 0 deletions lib/Dialect/ESI/runtime/cpp/include/esi/StdServices.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class SysInfo : public Service {
public:
virtual ~SysInfo() = default;

virtual std::string getServiceSymbol() const;

/// Get the ESI version number to check version compatibility.
virtual uint32_t esiVersion() const = 0;

Expand All @@ -47,6 +49,7 @@ class MMIO : public Service {
virtual ~MMIO() = default;
virtual uint64_t read(uint32_t addr) const = 0;
virtual void write(uint32_t addr, uint64_t data) = 0;
virtual std::string getServiceSymbol() const;
};

/// Implement the SysInfo API for a standard MMIO protocol.
Expand Down
3 changes: 2 additions & 1 deletion lib/Dialect/ESI/runtime/cpp/include/esi/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Type {
public:
using ID = std::string;
Type(const ID &id) : id(id) {}
virtual ~Type() = default;

ID getID() { return id; }

Expand All @@ -54,7 +55,7 @@ class BundleType : public Type {
BundleType(const ID &id, const ChannelVector &channels)
: Type(id), channels(channels) {}

const ChannelVector &getChannels() { return channels; }
const ChannelVector &getChannels() const { return channels; }

protected:
ChannelVector channels;
Expand Down
29 changes: 29 additions & 0 deletions lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===- Utils.h - ESI runtime utility code -----------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// DO NOT EDIT!
// This file is distributed as part of an ESI package. The source for this file
// should always be modified within CIRCT.
//
//===----------------------------------------------------------------------===//

// NOLINTNEXTLINE(llvm-header-guard)
#ifndef ESI_UTILS_H
#define ESI_UTILS_H

#include <cstdint>
#include <string>

namespace esi {
namespace utils {
// Very basic base64 encoding.
void encodeBase64(const void *data, size_t size, std::string &out);
} // namespace utils
} // namespace esi

#endif // ESI_UTILS_H
6 changes: 4 additions & 2 deletions lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
// DO NOT EDIT!
// This file is distributed as part of an ESI package. The source for this file
// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/esi.h).
// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp).
//
//===----------------------------------------------------------------------===//

Expand All @@ -35,7 +35,9 @@ class CosimAccelerator : public esi::Accelerator {
static std::unique_ptr<Accelerator> connect(std::string connectionString);

protected:
virtual Service *createService(Service::Type service) override;
virtual Service *createService(Service::Type service, AppIDPath path,
const ServiceImplDetails &details,
const HWClientDetails &clients) override;

private:
struct Impl;
Expand Down
8 changes: 6 additions & 2 deletions lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,15 @@ class TraceAccelerator : public esi::Accelerator {
/// "<mode>:<manifest path>[:<traceFile>]".
static std::unique_ptr<Accelerator> connect(std::string connectionString);

/// Internal implementation.
struct Impl;

protected:
virtual Service *createService(Service::Type service) override;
virtual Service *createService(Service::Type service, AppIDPath idPath,
const ServiceImplDetails &details,
const HWClientDetails &clients) override;

private:
struct Impl;
std::unique_ptr<Impl> impl;
};

Expand Down
Loading

0 comments on commit 106b3a2

Please sign in to comment.