CIRCT  20.0.0git
ESIBuildManifest.cpp
Go to the documentation of this file.
1 //===- ESIBuildManifest.cpp - Build ESI system manifest ---------*- 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 #include "../PassDetails.h"
10 
14 
16 
17 #include "llvm/ADT/StringExtras.h"
18 #include "llvm/ADT/TypeSwitch.h"
19 #include "llvm/Support/Compression.h"
20 #include "llvm/Support/JSON.h"
21 
22 namespace circt {
23 namespace esi {
24 #define GEN_PASS_DEF_ESIBUILDMANIFEST
25 #include "circt/Dialect/ESI/ESIPasses.h.inc"
26 } // namespace esi
27 } // namespace circt
28 
29 using namespace circt;
30 using namespace esi;
31 
32 // TODO: The code herein is a bit ugly, but it works. Consider cleaning it up.
33 
34 namespace {
35 struct ESIBuildManifestPass
36  : public circt::esi::impl::ESIBuildManifestBase<ESIBuildManifestPass> {
37  void runOnOperation() override;
38 
39 private:
40  /// Get a JSON representation of a type. 'useTable' indicates whether to use
41  /// the type table to determine if the type should be emitted as a reference
42  /// if it already exists in the type table.
43  llvm::json::Value json(Operation *errorOp, Type, bool useTable = true);
44  /// Get a JSON representation of a type. 'elideType' indicates to not print
45  /// the type if it would have been printed.
46  llvm::json::Value json(Operation *errorOp, Attribute, bool elideType = false);
47 
48  // Output a node in the appid hierarchy.
49  void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp);
50  // Output the manifest data of a node in the appid hierarchy.
51  void emitBlock(llvm::json::OStream &, Block &block, StringRef manifestClass);
52 
53  AppIDHierRootOp appidRoot;
54 
55  /// Get a JSON representation of the manifest.
56  std::string json();
57 
58  // Type table.
59  std::string useType(Type type) {
60  std::string typeID;
61  llvm::raw_string_ostream(typeID) << type;
62 
63  if (typeLookup.count(type))
64  return typeID;
65  typeLookup[type] = types.size();
66  types.push_back(type);
67  return typeID;
68  }
69  SmallVector<Type, 8> types;
70  DenseMap<Type, size_t> typeLookup;
71 
72  // Symbols / modules which are referenced.
73  DenseSet<SymbolRefAttr> symbols;
74  DenseSet<SymbolRefAttr> modules;
75 
76  hw::HWSymbolCache symCache;
77 };
78 } // anonymous namespace
79 
80 void ESIBuildManifestPass::runOnOperation() {
81  MLIRContext *ctxt = &getContext();
82  Operation *mod = getOperation();
83  symCache.addDefinitions(mod);
84  symCache.freeze();
85 
86  // Find the top level appid hierarchy root.
87  for (auto root : mod->getRegion(0).front().getOps<AppIDHierRootOp>())
88  if (root.getTopModuleRef() == top)
89  appidRoot = root;
90  if (!appidRoot)
91  return;
92 
93  // JSONify the manifest.
94  std::string jsonManifest = json();
95 
96  std::error_code ec;
97  llvm::raw_fd_ostream os("esi_system_manifest.json", ec);
98  if (ec) {
99  mod->emitError() << "Failed to open file for writing: " << ec.message();
100  signalPassFailure();
101  } else {
102  os << jsonManifest << "\n";
103  }
104 
105  // If zlib is available, compress the manifest and append it to the module.
106  SmallVector<uint8_t, 10 * 1024> compressedManifest;
107  if (llvm::compression::zlib::isAvailable()) {
108  // Compress the manifest.
109  llvm::compression::zlib::compress(
110  ArrayRef((uint8_t *)jsonManifest.data(), jsonManifest.length()),
111  compressedManifest, llvm::compression::zlib::BestSizeCompression);
112 
113  llvm::raw_fd_ostream bos("esi_system_manifest.json.zlib", ec);
114  if (ec) {
115  mod->emitError() << "Failed to open compressed file for writing: "
116  << ec.message();
117  signalPassFailure();
118  } else {
119  bos.write((char *)compressedManifest.data(), compressedManifest.size());
120  }
121 
122  OpBuilder b(symCache.getDefinition(appidRoot.getTopModuleRefAttr())
123  ->getRegion(0)
124  .front()
125  .getTerminator());
126  b.create<CompressedManifestOp>(b.getUnknownLoc(),
127  BlobAttr::get(ctxt, compressedManifest));
128  } else {
129  mod->emitWarning()
130  << "zlib not available but required for manifest support";
131  }
132 }
133 
134 void ESIBuildManifestPass::emitNode(llvm::json::OStream &j,
135  AppIDHierNodeOp nodeOp) {
136  std::set<StringRef> classesToEmit;
137  for (auto manifestData : nodeOp.getOps<IsManifestData>())
138  classesToEmit.insert(manifestData.getManifestClass());
139  j.object([&] {
140  j.attribute("appID", json(nodeOp, nodeOp.getAppIDAttr()));
141  j.attribute("instanceOf", json(nodeOp, nodeOp.getModuleRefAttr()));
142  for (StringRef manifestClass : classesToEmit)
143  j.attributeArray(manifestClass.str() + "s", [&]() {
144  emitBlock(j, nodeOp.getChildren().front(), manifestClass);
145  });
146  j.attributeArray("children", [&]() {
147  for (auto nodeOp : nodeOp.getChildren().front().getOps<AppIDHierNodeOp>())
148  emitNode(j, nodeOp);
149  });
150  });
151 }
152 
153 void ESIBuildManifestPass::emitBlock(llvm::json::OStream &j, Block &block,
154  StringRef manifestClass) {
155  for (auto manifestData : block.getOps<IsManifestData>()) {
156  if (manifestData.getManifestClass() != manifestClass)
157  continue;
158  j.object([&] {
159  SmallVector<NamedAttribute, 4> attrs;
160  manifestData.getDetails(attrs);
161  for (auto attr : attrs)
162  j.attribute(attr.getName().getValue(),
163  json(manifestData, attr.getValue()));
164  });
165  }
166 }
167 
168 std::string ESIBuildManifestPass::json() {
169  auto mod = getOperation();
170  std::string jsonStrBuffer;
171  llvm::raw_string_ostream os(jsonStrBuffer);
172  llvm::json::OStream j(os, 2);
173 
174  j.objectBegin(); // Top level object.
175  j.attribute("apiVersion", esiApiVersion);
176 
177  std::set<StringRef> classesToEmit;
178  for (auto manifestData : appidRoot.getOps<IsManifestData>())
179  classesToEmit.insert(manifestData.getManifestClass());
180 
181  j.attributeObject("design", [&]() {
182  j.attribute("instanceOf", json(appidRoot, appidRoot.getTopModuleRefAttr()));
183  modules.insert(appidRoot.getTopModuleRefAttr());
184  for (StringRef manifestClass : classesToEmit)
185  j.attributeArray(manifestClass.str() + "s", [&]() {
186  emitBlock(j, appidRoot.getChildren().front(), manifestClass);
187  });
188  j.attributeArray("children", [&]() {
189  for (auto nodeOp :
190  appidRoot.getChildren().front().getOps<AppIDHierNodeOp>())
191  emitNode(j, nodeOp);
192  });
193  });
194 
195  j.attributeArray("serviceDeclarations", [&]() {
196  for (auto svcDecl : mod.getBody()->getOps<ServiceDeclOpInterface>()) {
197  auto sym = FlatSymbolRefAttr::get(svcDecl);
198  if (!symbols.contains(sym))
199  continue;
200  j.object([&] {
201  j.attribute("symbol", json(svcDecl, sym, /*elideType=*/true));
202  std::optional<StringRef> typeName = svcDecl.getTypeName();
203  if (typeName)
204  j.attribute("serviceName", *typeName);
205  llvm::SmallVector<ServicePortInfo, 8> ports;
206  svcDecl.getPortList(ports);
207  j.attributeArray("ports", [&]() {
208  for (auto port : ports) {
209  j.object([&] {
210  j.attribute("name", port.port.getName().getValue());
211  j.attribute("typeID", useType(port.type));
212  });
213  }
214  });
215  });
216  }
217  });
218 
219  j.attributeArray("modules", [&]() {
220  // Map from symbol to all manifest data ops related to said symbol.
221  llvm::MapVector<SymbolRefAttr, SmallVector<IsManifestData>>
222  symbolInfoLookup;
223  // Ensure that all symbols are present in the lookup even if they have no
224  // manifest metadata.
225  for (auto modSymbol : modules)
226  symbolInfoLookup[modSymbol] = {};
227  // Gather all manifest data for each symbol.
228  for (auto symInfo : mod.getBody()->getOps<IsManifestData>()) {
229  FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr();
230  if (!sym || !symbols.contains(sym))
231  continue;
232  symbolInfoLookup[sym].push_back(symInfo);
233  }
234 
235  // Now, emit a JSON object for each symbol.
236  for (const auto &symNameInfo : symbolInfoLookup) {
237  j.object([&] {
238  std::string symbolStr;
239  llvm::raw_string_ostream(symbolStr) << symNameInfo.first;
240  j.attribute("symbol", symbolStr);
241  for (auto symInfo : symNameInfo.second) {
242  j.attributeBegin(symInfo.getManifestClass());
243  j.object([&] {
244  SmallVector<NamedAttribute, 4> attrs;
245  symInfo.getDetails(attrs);
246  for (auto attr : attrs) {
247  if (attr.getName().getValue() == "symbolRef")
248  continue;
249  j.attribute(attr.getName().getValue(),
250  json(symInfo, attr.getValue()));
251  }
252  });
253  j.attributeEnd();
254  }
255  });
256  }
257  });
258 
259  j.attributeArray("types", [&]() {
260  for (size_t i = 0; i < types.size(); i++)
261  j.value(json(mod, types[i], /*useTable=*/false));
262  });
263 
264  j.objectEnd(); // Top level object.
265 
266  return jsonStrBuffer;
267 }
268 
269 /// Get a JSON representation of a type.
270 // NOLINTNEXTLINE(misc-no-recursion)
271 llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type,
272  bool useTable) {
273  using llvm::json::Array;
274  using llvm::json::Object;
275  using llvm::json::Value;
276 
277  if (useTable && typeLookup.contains(type)) {
278  // If the type is in the type table, it'll be present in the types
279  // section. Just give the circt type name, which is guaranteed to
280  // uniquely identify the type.
281  std::string typeName;
282  llvm::raw_string_ostream(typeName) << type;
283  return typeName;
284  }
285 
286  std::string m;
287  Object o =
288  // This is not complete. Build out as necessary.
289  TypeSwitch<Type, Object>(type)
290  .Case([&](ChannelType t) {
291  m = "channel";
292  return Object({{"inner", json(errorOp, t.getInner(), false)}});
293  })
294  .Case([&](ChannelBundleType t) {
295  m = "bundle";
296  Array channels;
297  for (auto field : t.getChannels())
298  channels.push_back(Object(
299  {{"name", field.name.getValue()},
300  {"direction", stringifyChannelDirection(field.direction)},
301  {"type", json(errorOp, field.type, useTable)}}));
302  return Object({{"channels", Value(std::move(channels))}});
303  })
304  .Case([&](AnyType t) {
305  m = "any";
306  return Object();
307  })
308  .Case([&](ListType t) {
309  m = "list";
310  return Object(
311  {{"element", json(errorOp, t.getElementType(), useTable)}});
312  })
313  .Case([&](hw::ArrayType t) {
314  m = "array";
315  return Object(
316  {{"size", t.getNumElements()},
317  {"element", json(errorOp, t.getElementType(), useTable)}});
318  })
319  .Case([&](hw::StructType t) {
320  m = "struct";
321  Array fields;
322  for (auto field : t.getElements())
323  fields.push_back(
324  Object({{"name", field.name.getValue()},
325  {"type", json(errorOp, field.type, useTable)}}));
326  return Object({{"fields", Value(std::move(fields))}});
327  })
328  .Case([&](hw::TypeAliasType t) {
329  m = "alias";
330  return Object(
331  {{"name", t.getTypeDecl(symCache).getPreferredName()},
332  {"inner", json(errorOp, t.getInnerType(), useTable)}});
333  })
334  .Case([&](IntegerType t) {
335  m = "int";
336  StringRef signedness =
337  t.isSigned() ? "signed"
338  : (t.isUnsigned() ? "unsigned" : "signless");
339  return Object({{"signedness", signedness}});
340  })
341  .Default([&](Type t) {
342  errorOp->emitWarning()
343  << "ESI system manifest: unknown type: " << t;
344  return Object();
345  });
346 
347  // Common metadata.
348  std::string typeID;
349  llvm::raw_string_ostream(typeID) << type;
350  o["id"] = typeID;
351 
352  int64_t width = hw::getBitWidth(type);
353  if (auto chanType = dyn_cast<ChannelType>(type))
354  width = hw::getBitWidth(chanType.getInner());
355  if (width >= 0)
356  o["hwBitwidth"] = width;
357 
358  o["dialect"] = type.getDialect().getNamespace();
359  if (m.length())
360  o["mnemonic"] = m;
361  return o;
362 }
363 
364 // Serialize an attribute to a JSON value.
365 // NOLINTNEXTLINE(misc-no-recursion)
366 llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Attribute attr,
367  bool elideType) {
368 
369  // This is far from complete. Build out as necessary.
370  using llvm::json::Object;
371  using llvm::json::Value;
372  Value value =
373  TypeSwitch<Attribute, Value>(attr)
374  .Case([&](FlatSymbolRefAttr ref) {
375  symbols.insert(ref);
376  std::string value;
377  llvm::raw_string_ostream(value) << ref;
378  return value;
379  })
380  .Case([&](StringAttr a) { return a.getValue(); })
381  .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); })
382  .Case([&](TypeAttr a) { return useType(a.getValue()); })
383  .Case([&](ArrayAttr a) {
384  return llvm::json::Array(llvm::map_range(
385  a, [&](Attribute a) { return json(errorOp, a); }));
386  })
387  .Case([&](DictionaryAttr a) {
388  llvm::json::Object dict;
389  for (const auto &entry : a.getValue())
390  dict[entry.getName().getValue()] =
391  json(errorOp, entry.getValue());
392  return dict;
393  })
394  .Case([&](AppIDAttr appid) {
395  llvm::json::Object dict;
396  dict["name"] = appid.getName().getValue();
397  auto idx = appid.getIndex();
398  if (idx)
399  dict["index"] = *idx;
400  return dict;
401  })
402  .Default([&](Attribute a) {
403  std::string value;
404  llvm::raw_string_ostream(value) << a;
405  return value;
406  });
407 
408  // Don't print the type if it's None or we're eliding it.
409  auto typedAttr = llvm::dyn_cast<TypedAttr>(attr);
410  if (elideType || !typedAttr || isa<NoneType>(typedAttr.getType()))
411  return value;
412 
413  // Otherwise, return an object with the value and type.
414  Object dict;
415  dict["value"] = value;
416  dict["type"] = useType(typedAttr.getType());
417  return dict;
418 }
419 
420 std::unique_ptr<OperationPass<ModuleOp>>
422  return std::make_unique<ESIBuildManifestPass>();
423 }
int32_t width
Definition: FIRRTL.cpp:36
The "any" type is a special type which can be used to represent any type, as identified by the type i...
Definition: Types.h:85
Channels are the basic communication primitives.
Definition: Types.h:63
const Type * getInner() const
Definition: Types.h:66
Integers are bit vectors which may be signed or unsigned and are interpreted as numbers.
Definition: Types.h:112
Root class of the ESI type system.
Definition: Types.h:27
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
constexpr uint64_t esiApiVersion
Manifest format version number.
Definition: ESIDialect.h:33
std::unique_ptr< OperationPass< ModuleOp > > createESIBuildManifestPass()
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition: HWTypes.cpp:110
evaluator::ObjectValue Object
Definition: Evaluator.h:411
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: esi.py:1