CIRCT  19.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 namespace {
33 struct ESIBuildManifestPass
34  : public circt::esi::impl::ESIBuildManifestBase<ESIBuildManifestPass> {
35  void runOnOperation() override;
36 
37 private:
38  /// Get the types of an operations, but only if the operation is relevant.
39  void gatherFilters(Operation *);
40  void gatherFilters(Attribute);
41 
42  /// Get a JSON representation of a type.
43  llvm::json::Value json(Operation *errorOp, Type);
44  /// Get a JSON representation of a type.
45  llvm::json::Value json(Operation *errorOp, Attribute);
46 
47  // Output a node in the appid hierarchy.
48  void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp);
49  // Output the manifest data of a node in the appid hierarchy.
50  void emitBlock(llvm::json::OStream &, Block &block);
51 
52  AppIDHierRootOp appidRoot;
53 
54  /// Get a JSON representation of the manifest.
55  std::string json();
56 
57  // Type table.
58  void addType(Type type) {
59  if (typeLookup.count(type))
60  return;
61  typeLookup[type] = types.size();
62  types.push_back(type);
63  }
64  SmallVector<Type, 8> types;
65  DenseMap<Type, size_t> typeLookup;
66 
67  // Symbols which are referenced.
68  DenseSet<SymbolRefAttr> symbols;
69 
70  hw::HWSymbolCache symCache;
71 };
72 } // anonymous namespace
73 
74 void ESIBuildManifestPass::runOnOperation() {
75  MLIRContext *ctxt = &getContext();
76  Operation *mod = getOperation();
77  symCache.addDefinitions(mod);
78  symCache.freeze();
79 
80  // Find the top level appid hierarchy root.
81  for (auto root : mod->getRegion(0).front().getOps<AppIDHierRootOp>())
82  if (root.getTopModuleRef() == top)
83  appidRoot = root;
84  if (!appidRoot)
85  return;
86 
87  // Gather the relevant types under the appid hierarchy root only. This avoids
88  // scraping unnecessary types.
89  appidRoot->walk([&](Operation *op) { gatherFilters(op); });
90 
91  // JSONify the manifest.
92  std::string jsonManifest = json();
93 
94  std::error_code ec;
95  llvm::raw_fd_ostream os("esi_system_manifest.json", ec);
96  if (ec) {
97  mod->emitError() << "Failed to open file for writing: " << ec.message();
98  signalPassFailure();
99  } else {
100  os << jsonManifest << "\n";
101  }
102 
103  // If zlib is available, compress the manifest and append it to the module.
104  SmallVector<uint8_t, 10 * 1024> compressedManifest;
105  if (llvm::compression::zlib::isAvailable()) {
106  // Compress the manifest.
107  llvm::compression::zlib::compress(
108  ArrayRef((uint8_t *)jsonManifest.data(), jsonManifest.length()),
109  compressedManifest, llvm::compression::zlib::BestSizeCompression);
110 
111  llvm::raw_fd_ostream bos("esi_system_manifest.json.zlib", ec);
112  if (ec) {
113  mod->emitError() << "Failed to open compressed file for writing: "
114  << ec.message();
115  signalPassFailure();
116  } else {
117  bos.write((char *)compressedManifest.data(), compressedManifest.size());
118  }
119 
120  OpBuilder b(symCache.getDefinition(appidRoot.getTopModuleRefAttr())
121  ->getRegion(0)
122  .front()
123  .getTerminator());
124  b.create<CompressedManifestOp>(b.getUnknownLoc(),
125  BlobAttr::get(ctxt, compressedManifest));
126  } else {
127  mod->emitWarning()
128  << "zlib not available but required for manifest support";
129  }
130 }
131 
132 void ESIBuildManifestPass::emitNode(llvm::json::OStream &j,
133  AppIDHierNodeOp nodeOp) {
134  j.object([&] {
135  j.attribute("app_id", json(nodeOp, nodeOp.getAppIDAttr()));
136  j.attribute("inst_of", json(nodeOp, nodeOp.getModuleRefAttr()));
137  j.attributeArray("contents",
138  [&]() { emitBlock(j, nodeOp.getChildren().front()); });
139  j.attributeArray("children", [&]() {
140  for (auto nodeOp : nodeOp.getChildren().front().getOps<AppIDHierNodeOp>())
141  emitNode(j, nodeOp);
142  });
143  });
144 }
145 
146 void ESIBuildManifestPass::emitBlock(llvm::json::OStream &j, Block &block) {
147  for (auto manifestData : block.getOps<IsManifestData>())
148  j.object([&] {
149  j.attribute("class", manifestData.getManifestClass());
150  SmallVector<NamedAttribute, 4> attrs;
151  manifestData.getDetails(attrs);
152  for (auto attr : attrs)
153  j.attribute(attr.getName().getValue(),
154  json(manifestData, attr.getValue()));
155  });
156 }
157 
158 std::string ESIBuildManifestPass::json() {
159  auto mod = getOperation();
160  std::string jsonStrBuffer;
161  llvm::raw_string_ostream os(jsonStrBuffer);
162  llvm::json::OStream j(os, 2);
163 
164  j.objectBegin();
165  j.attribute("api_version", esiApiVersion);
166 
167  j.attributeArray("symbols", [&]() {
168  for (auto symInfo : mod.getBody()->getOps<SymbolMetadataOp>()) {
169  if (!symbols.contains(symInfo.getSymbolRefAttr()))
170  continue;
171  j.object([&] {
172  SmallVector<NamedAttribute, 4> attrs;
173  symInfo.getDetails(attrs);
174  for (auto attr : attrs)
175  j.attribute(attr.getName().getValue(),
176  json(symInfo, attr.getValue()));
177  });
178  }
179  });
180 
181  j.attributeObject("design", [&]() {
182  j.attribute("inst_of", json(appidRoot, appidRoot.getTopModuleRefAttr()));
183  j.attributeArray("contents",
184  [&]() { emitBlock(j, appidRoot.getChildren().front()); });
185  j.attributeArray("children", [&]() {
186  for (auto nodeOp :
187  appidRoot.getChildren().front().getOps<AppIDHierNodeOp>())
188  emitNode(j, nodeOp);
189  });
190  });
191 
192  j.attributeArray("service_decls", [&]() {
193  for (auto svcDecl : mod.getBody()->getOps<ServiceDeclOpInterface>()) {
194  auto sym = FlatSymbolRefAttr::get(svcDecl);
195  if (!symbols.contains(sym))
196  continue;
197  j.object([&] {
198  j.attribute("symbol", sym.getValue());
199  std::optional<StringRef> typeName = svcDecl.getTypeName();
200  if (typeName)
201  j.attribute("type_name", *typeName);
202  llvm::SmallVector<ServicePortInfo, 8> ports;
203  svcDecl.getPortList(ports);
204  j.attributeArray("ports", [&]() {
205  for (auto port : ports) {
206  j.object([&] {
207  j.attribute("name", port.port.getName().getValue());
208  j.attribute("type", json(svcDecl, TypeAttr::get(port.type)));
209  });
210  }
211  });
212  });
213  }
214  });
215 
216  j.attributeArray("types", [&]() {
217  for (auto type : types) {
218  j.value(json(mod, type));
219  }
220  });
221  j.objectEnd();
222 
223  return jsonStrBuffer;
224 }
225 
226 void ESIBuildManifestPass::gatherFilters(Operation *op) {
227  for (auto oper : op->getOperands())
228  addType(oper.getType());
229  for (auto res : op->getResults())
230  addType(res.getType());
231 
232  // If op is a manifest data op, we only need to include types found in the
233  // details it reports.
234  SmallVector<NamedAttribute> attrs;
235  if (auto manifestData = dyn_cast<IsManifestData>(op))
236  manifestData.getDetails(attrs);
237  else
238  llvm::append_range(attrs, op->getAttrs());
239  for (auto attr : attrs)
240  gatherFilters(attr.getValue());
241 }
242 
243 // NOLINTNEXTLINE(misc-no-recursion)
244 void ESIBuildManifestPass::gatherFilters(Attribute attr) {
245  // This is far from complete. Build out as necessary.
246  TypeSwitch<Attribute>(attr)
247  .Case([&](TypeAttr a) { addType(a.getValue()); })
248  .Case([&](FlatSymbolRefAttr a) { symbols.insert(a); })
249  .Case([&](hw::InnerRefAttr a) { symbols.insert(a.getModuleRef()); })
250  .Case([&](ArrayAttr a) {
251  for (auto attr : a)
252  gatherFilters(attr);
253  })
254  .Case([&](DictionaryAttr a) {
255  for (const auto &entry : a.getValue())
256  gatherFilters(entry.getValue());
257  });
258 }
259 
260 /// Get a JSON representation of a type.
261 // NOLINTNEXTLINE(misc-no-recursion)
262 llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) {
263  using llvm::json::Array;
264  using llvm::json::Object;
265  using llvm::json::Value;
266 
267  std::string m;
268  Object o =
269  // This is not complete. Build out as necessary.
270  TypeSwitch<Type, Object>(type)
271  .Case([&](ChannelType t) {
272  m = "channel";
273  return Object({{"inner", json(errorOp, t.getInner())}});
274  })
275  .Case([&](ChannelBundleType t) {
276  m = "bundle";
277  Array channels;
278  for (auto field : t.getChannels())
279  channels.push_back(Object(
280  {{"name", field.name.getValue()},
281  {"direction", stringifyChannelDirection(field.direction)},
282  {"type", json(errorOp, field.type)}}));
283  return Object({{"channels", Value(std::move(channels))}});
284  })
285  .Case([&](AnyType t) {
286  m = "any";
287  return Object();
288  })
289  .Case([&](ListType t) {
290  m = "list";
291  return Object({{"element", json(errorOp, t.getElementType())}});
292  })
293  .Case([&](hw::ArrayType t) {
294  m = "array";
295  return Object({{"size", t.getNumElements()},
296  {"element", json(errorOp, t.getElementType())}});
297  })
298  .Case([&](hw::StructType t) {
299  m = "struct";
300  Array fields;
301  for (auto field : t.getElements())
302  fields.push_back(Object({{"name", field.name.getValue()},
303  {"type", json(errorOp, field.type)}}));
304  return Object({{"fields", Value(std::move(fields))}});
305  })
306  .Case([&](hw::TypeAliasType t) {
307  m = "alias";
308  return Object({{"name", t.getTypeDecl(symCache).getPreferredName()},
309  {"inner", json(errorOp, t.getInnerType())}});
310  })
311  .Case([&](IntegerType t) {
312  m = "int";
313  StringRef signedness =
314  t.isSigned() ? "signed"
315  : (t.isUnsigned() ? "unsigned" : "signless");
316  return Object({{"signedness", signedness}});
317  })
318  .Default([&](Type t) {
319  errorOp->emitWarning()
320  << "ESI system manifest: unknown type: " << t;
321  return Object();
322  });
323 
324  // Common metadata.
325  std::string circtName;
326  llvm::raw_string_ostream(circtName) << type;
327  o["circt_name"] = circtName;
328 
329  int64_t width = hw::getBitWidth(type);
330  if (auto chanType = dyn_cast<ChannelType>(type))
331  width = hw::getBitWidth(chanType.getInner());
332  if (width >= 0)
333  o["hw_bitwidth"] = width;
334 
335  o["dialect"] = type.getDialect().getNamespace();
336  if (m.length())
337  o["mnemonic"] = m;
338  return o;
339 }
340 
341 // Serialize an attribute to a JSON value.
342 // NOLINTNEXTLINE(misc-no-recursion)
343 llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp,
344  Attribute attr) {
345  // This is far from complete. Build out as necessary.
346  using llvm::json::Value;
347  return TypeSwitch<Attribute, Value>(attr)
348  .Case([&](StringAttr a) { return a.getValue(); })
349  .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); })
350  .Case([&](TypeAttr a) {
351  Type t = a.getValue();
352 
353  llvm::json::Object typeMD;
354  if (typeLookup.contains(t)) {
355  // If the type is in the type table, it'll be present in the types
356  // section. Just give the circt type name, which is guaranteed to
357  // uniquely identify the type.
358  std::string buff;
359  llvm::raw_string_ostream(buff) << a;
360  typeMD["circt_name"] = buff;
361  return typeMD;
362  }
363 
364  typeMD["type"] = json(errorOp, t);
365  return typeMD;
366  })
367  .Case([&](ArrayAttr a) {
368  return llvm::json::Array(
369  llvm::map_range(a, [&](Attribute a) { return json(errorOp, a); }));
370  })
371  .Case([&](DictionaryAttr a) {
372  llvm::json::Object dict;
373  for (const auto &entry : a.getValue())
374  dict[entry.getName().getValue()] = json(errorOp, entry.getValue());
375  return dict;
376  })
377  .Case([&](hw::InnerRefAttr ref) {
378  llvm::json::Object dict;
379  dict["outer_sym"] = ref.getModule().getValue();
380  dict["inner"] = ref.getName().getValue();
381  return dict;
382  })
383  .Case([&](AppIDAttr appid) {
384  llvm::json::Object dict;
385  dict["name"] = appid.getName().getValue();
386  auto idx = appid.getIndex();
387  if (idx)
388  dict["index"] = *idx;
389  return dict;
390  })
391  .Default([&](Attribute a) {
392  std::string buff;
393  llvm::raw_string_ostream(buff) << a;
394  return buff;
395  });
396 }
397 
398 std::unique_ptr<OperationPass<ModuleOp>>
400  return std::make_unique<ESIBuildManifestPass>();
401 }
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:54
constexpr uint64_t esiApiVersion
Manifest format version number.
Definition: ESIDialect.h:33
std::unique_ptr< OperationPass< ModuleOp > > createESIBuildManifestPass()
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
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