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