CIRCT  20.0.0git
LowerOpenAggs.cpp
Go to the documentation of this file.
1 //===- LowerOpenAggs.cpp - Lower Open Aggregate Types -----------*- 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 // This file defines the LowerOpenAggs pass. This pass replaces the open
10 // aggregate types with hardware aggregates, with non-hardware fields
11 // expanded out as with LowerTypes.
12 //
13 // This pass supports reference and property types.
14 //
15 //===----------------------------------------------------------------------===//
16 
23 #include "circt/Support/Debug.h"
24 #include "mlir/IR/BuiltinAttributes.h"
25 #include "mlir/IR/Threading.h"
26 #include "mlir/IR/Visitors.h"
27 #include "mlir/Pass/Pass.h"
28 #include "llvm/Support/Debug.h"
29 #include "llvm/Support/ErrorHandling.h"
30 #include "llvm/Support/FormatAdapters.h"
31 #include "llvm/Support/FormatVariadic.h"
32 
33 #define DEBUG_TYPE "firrtl-lower-open-aggs"
34 
35 namespace circt {
36 namespace firrtl {
37 #define GEN_PASS_DEF_LOWEROPENAGGS
38 #include "circt/Dialect/FIRRTL/Passes.h.inc"
39 } // namespace firrtl
40 } // namespace circt
41 
42 using namespace circt;
43 using namespace firrtl;
44 
45 namespace {
46 
47 /// Information on non-hw (ref/prop) elements.
48 struct NonHWField {
49  /// Type of the field, not a hardware type.
50  FIRRTLType type;
51  /// FieldID relative to base of converted type.
52  uint64_t fieldID;
53  /// Relative orientation. False means aligned.
54  bool isFlip;
55  /// String suffix naming this field.
56  SmallString<16> suffix;
57 
58  /// Print this structure to the specified stream.
59  void print(raw_ostream &os, unsigned indent = 0) const;
60 
61 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
62  /// Print this structure to llvm::errs().
63  LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
64 #endif
65 };
66 
67 /// Structure that describes how a given operation with a type (which may or may
68 /// not contain non-hw typess) should be lowered to one or more operations with
69 /// other types.
70 struct MappingInfo {
71  /// Preserve this type. Map any uses of old directly to new.
72  bool identity;
73 
74  // When not identity, the type will be split:
75 
76  /// Type of the hardware-only portion. May be null, indicating all non-hw.
77  Type hwType;
78 
79  /// List of the individual non-hw fields to be split out.
80  SmallVector<NonHWField, 0> fields;
81 
82  /// List of fieldID's of interior nodes that map to nothing. HW-only
83  /// projection is empty, and not leaf.
84  SmallVector<uint64_t, 0> mapToNullInteriors;
85 
86  hw::InnerSymAttr newSym = {};
87 
88  /// Determine number of types this argument maps to.
89  size_t count(bool includeErased = false) const {
90  if (identity)
91  return 1;
92  return fields.size() + (hwType ? 1 : 0) + (includeErased ? 1 : 0);
93  }
94 
95  /// Print this structure to the specified stream.
96  void print(raw_ostream &os, unsigned indent = 0) const;
97 
98 #if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
99  /// Print this structure to llvm::errs().
100  LLVM_DUMP_METHOD void dump() const { print(llvm::errs()); }
101 #endif
102 };
103 
104 } // namespace
105 
106 void NonHWField::print(llvm::raw_ostream &os, unsigned indent) const {
107  os << llvm::formatv("{0}- type: {2}\n"
108  "{1}fieldID: {3}\n"
109  "{1}isFlip: {4}\n"
110  "{1}suffix: \"{5}\"\n",
111  llvm::fmt_pad("", indent, 0),
112  llvm::fmt_pad("", indent + 2, 0), type, fieldID, isFlip,
113  suffix);
114 }
115 void MappingInfo::print(llvm::raw_ostream &os, unsigned indent) const {
116  if (identity) {
117  os << "<identity>";
118  return;
119  }
120 
121  os.indent(indent) << "hardware: ";
122  if (hwType)
123  os << hwType;
124  else
125  os << "<none>";
126  os << "\n";
127 
128  os.indent(indent) << "non-hardware:\n";
129  for (auto &field : fields)
130  field.print(os, indent + 2);
131 
132  os.indent(indent) << "mappedToNull:\n";
133  for (auto &null : mapToNullInteriors)
134  os.indent(indent + 2) << "- " << null << "\n";
135 
136  os.indent(indent) << "newSym: ";
137  if (newSym)
138  os << newSym;
139  else
140  os << "<empty>";
141 }
142 
143 template <typename Range>
144 LogicalResult walkMappings(
145  Range &&range, bool includeErased,
146  llvm::function_ref<LogicalResult(size_t, MappingInfo &, size_t)> callback) {
147  size_t count = 0;
148  for (const auto &[index, pmi] : llvm::enumerate(range)) {
149  if (failed(callback(index, pmi, count)))
150  return failure();
151  count += pmi.count(includeErased);
152  }
153  return success();
154 }
155 
156 //===----------------------------------------------------------------------===//
157 // Visitor
158 //===----------------------------------------------------------------------===//
159 
160 namespace {
161 class Visitor : public FIRRTLVisitor<Visitor, LogicalResult> {
162 public:
163  explicit Visitor(MLIRContext *context) : context(context){};
164 
165  /// Entrypoint.
166  LogicalResult visit(FModuleLike mod);
167 
171 
172  LogicalResult visitDecl(InstanceOp op);
173  LogicalResult visitDecl(WireOp op);
174 
175  LogicalResult visitExpr(OpenSubfieldOp op);
176  LogicalResult visitExpr(OpenSubindexOp op);
177 
178  LogicalResult visitUnhandledOp(Operation *op) {
179  auto notOpenAggType = [](auto type) {
180  return !isa<OpenBundleType, OpenVectorType>(type);
181  };
182  if (!llvm::all_of(op->getOperandTypes(), notOpenAggType) ||
183  !llvm::all_of(op->getResultTypes(), notOpenAggType))
184  return op->emitOpError(
185  "unhandled use or producer of types containing non-hw types");
186  return success();
187  }
188 
189  LogicalResult visitInvalidOp(Operation *op) { return visitUnhandledOp(op); }
190 
191  /// Whether any changes were made.
192  bool madeChanges() const { return changesMade; }
193 
194 private:
195  /// Convert a type to its HW-only projection, adjusting symbols. Gather
196  /// non-hw elements encountered and their names / positions. Returns a
197  /// MappingInfo with its findings.
198  FailureOr<MappingInfo> mapType(Type type, Location errorLoc,
199  hw::InnerSymAttr sym = {});
200 
201  /// Helper to record changes that may have been made.
202  void recordChanges(bool changed) {
203  if (changed)
204  changesMade = true;
205  }
206 
207  MLIRContext *context;
208 
209  /// Map non-HW fields to their new Value.
210  /// Null value indicates no equivalent (dead).
211  /// These values are available wherever the root is used.
212  DenseMap<FieldRef, Value> nonHWValues;
213 
214  /// Map from original to its hw-only aggregate equivalent.
215  DenseMap<Value, Value> hwOnlyAggMap;
216 
217  /// List of operations to erase at the end.
218  SmallVector<Operation *> opsToErase;
219 
220  /// FieldRef cache. Be careful to only use this for operations
221  /// in the original IR / not mutated.
222  FieldRefCache refs;
223 
224  /// Whether IR was changed.
225  bool changesMade = false;
226 };
227 } // namespace
228 
229 LogicalResult Visitor::visit(FModuleLike mod) {
230  auto ports = mod.getPorts();
231 
232  SmallVector<MappingInfo, 16> portMappings;
233  for (auto &port : ports) {
234  auto pmi = mapType(port.type, port.loc, port.sym);
235  if (failed(pmi))
236  return failure();
237  portMappings.push_back(*pmi);
238  }
239 
240  /// Total number of types mapped to.
241  /// Include erased ports.
242  size_t countWithErased = 0;
243  for (auto &pmi : portMappings)
244  countWithErased += pmi.count(/*includeErased=*/true);
245 
246  /// Ports to add.
247  SmallVector<std::pair<unsigned, PortInfo>> newPorts;
248 
249  /// Ports to remove.
250  BitVector portsToErase(countWithErased);
251 
252  /// Go through each port mapping, gathering information about all new ports.
253  LLVM_DEBUG({
254  llvm::dbgs().indent(2) << "- name: "
255  << cast<mlir::SymbolOpInterface>(*mod).getNameAttr()
256  << "\n";
257  llvm::dbgs().indent(4) << "ports:\n";
258  });
259  auto result = walkMappings(
260  portMappings, /*includeErased=*/true,
261  [&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
262  LLVM_DEBUG({
263  llvm::dbgs().indent(6) << "- name: " << ports[index].name << "\n";
264  llvm::dbgs().indent(8) << "type: " << ports[index].type << "\n";
265  llvm::dbgs().indent(8) << "mapping:\n";
266  pmi.print(llvm::dbgs(), /*indent=*/10);
267  llvm::dbgs() << "\n";
268  });
269  // Index for inserting new points next to this point.
270  // (Immediately after current port's index).
271  auto idxOfInsertPoint = index + 1;
272 
273  if (pmi.identity)
274  return success();
275 
276  auto &port = ports[index];
277 
278  // If not identity, mark this port for eventual removal.
279  portsToErase.set(newIndex);
280 
281  // Create new hw-only port, this will generally replace this port.
282  if (pmi.hwType) {
283  auto newPort = port;
284  newPort.type = pmi.hwType;
285  newPort.sym = pmi.newSym;
286  newPorts.emplace_back(idxOfInsertPoint, newPort);
287 
288  assert(!port.sym ||
289  (pmi.newSym && port.sym.size() == pmi.newSym.size()));
290 
291  // If want to run this pass later, need to fixup annotations.
292  if (!port.annotations.empty())
293  return mlir::emitError(port.loc)
294  << "annotations on open aggregates not handled yet";
295  } else {
296  assert(!port.sym && !pmi.newSym);
297  if (!port.annotations.empty())
298  return mlir::emitError(port.loc)
299  << "annotations found on aggregate with no HW";
300  }
301 
302  // Create ports for each non-hw field.
303  for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
304  auto name = StringAttr::get(context,
305  Twine(port.name.strref()) + field.suffix);
306  auto orientation =
307  (Direction)((unsigned)port.direction ^ field.isFlip);
308  PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
309  port.loc, std::nullopt);
310  newPorts.emplace_back(idxOfInsertPoint, pi);
311  }
312  return success();
313  });
314  if (failed(result))
315  return failure();
316 
317  // Insert the new ports!
318  mod.insertPorts(newPorts);
319  recordChanges(!newPorts.empty());
320 
321  assert(mod->getNumRegions() == 1);
322 
323  // (helper to determine/get the body block if present)
324  auto getBodyBlock = [](auto mod) {
325  auto &blocks = mod->getRegion(0).getBlocks();
326  return !blocks.empty() ? &blocks.front() : nullptr;
327  };
328 
329  // Process body block.
330  // Create mapping for ports, then visit all operations within.
331  if (auto *block = getBodyBlock(mod)) {
332  // Create mappings for split ports.
333  auto result =
334  walkMappings(portMappings, /*includeErased=*/true,
335  [&](auto index, MappingInfo &pmi, auto newIndex) {
336  // Nothing to do for identity.
337  if (pmi.identity)
338  return success();
339 
340  // newIndex is index of this port after insertion.
341  // This will be removed.
342  assert(portsToErase.test(newIndex));
343  auto oldPort = block->getArgument(newIndex);
344  auto newPortIndex = newIndex;
345 
346  // Create mappings for split ports.
347  if (pmi.hwType)
348  hwOnlyAggMap[oldPort] =
349  block->getArgument(++newPortIndex);
350 
351  for (auto &field : pmi.fields) {
352  auto ref = FieldRef(oldPort, field.fieldID);
353  auto newVal = block->getArgument(++newPortIndex);
354  nonHWValues[ref] = newVal;
355  }
356  for (auto fieldID : pmi.mapToNullInteriors) {
357  auto ref = FieldRef(oldPort, fieldID);
358  assert(!nonHWValues.count(ref));
359  nonHWValues[ref] = {};
360  }
361 
362  return success();
363  });
364  if (failed(result))
365  return failure();
366 
367  // Walk the module.
368  LLVM_DEBUG(llvm::dbgs().indent(4) << "body:\n");
369  if (block
370  ->walk<mlir::WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
371  return dispatchVisitor(op);
372  })
373  .wasInterrupted())
374  return failure();
375 
376  assert(opsToErase.empty() || madeChanges());
377 
378  // Cleanup dead operations.
379  for (auto &op : llvm::reverse(opsToErase))
380  op->erase();
381  }
382 
383  // Drop dead ports.
384  mod.erasePorts(portsToErase);
385  recordChanges(portsToErase.any());
386 
387  LLVM_DEBUG(refs.printStats(llvm::dbgs()));
388 
389  return success();
390 }
391 
392 LogicalResult Visitor::visitExpr(OpenSubfieldOp op) {
393  // Changes will be made.
394  recordChanges(true);
395 
396  // We're indexing into an OpenBundle, which contains some non-hw elements and
397  // may contain hw elements.
398 
399  // By the time this is reached, the "root" storage for the input
400  // has already been handled and mapped to its new location(s),
401  // such that the hardware-only contents are split from non-hw.
402 
403  // If there is a hardware portion selected by this operation,
404  // create a "closed" subfieldop using the hardware-only new storage,
405  // and add an entry mapping our old (soon, dead) result to
406  // this new hw-only result (of the subfieldop).
407 
408  // Downstream indexing operations will expect that they can
409  // still chase up through this operation, and that they will find
410  // the hw-only portion in the map.
411 
412  // If this operation selects a non-hw element (not mixed),
413  // look up where that ref now lives and update all users to use that instead.
414  // (This case falls under "this selects only non-hw", which means
415  // that this operation is now dead).
416 
417  // In all cases, this operation will be dead and should be removed.
418  opsToErase.push_back(op);
419 
420  // Chase this to its original root.
421  // If the FieldRef for this selection has a new home,
422  // RAUW to that value and this op is dead.
423  auto resultRef = refs.getFieldRefFromValue(op.getResult());
424  auto nonHWForResult = nonHWValues.find(resultRef);
425  if (nonHWForResult != nonHWValues.end()) {
426  // If has nonHW portion, RAUW to it.
427  if (auto newResult = nonHWForResult->second) {
428  assert(op.getResult().getType() == newResult.getType());
429  assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
430  op.getResult().replaceAllUsesWith(newResult);
431  }
432  return success();
433  }
434 
435  assert(hwOnlyAggMap.count(op.getInput()));
436 
437  auto newInput = hwOnlyAggMap[op.getInput()];
438  assert(newInput);
439 
440  auto bundleType = type_cast<BundleType>(newInput.getType());
441 
442  // Recompute the "actual" index for this field, it may have changed.
443  auto fieldName = op.getFieldName();
444  auto newFieldIndex = bundleType.getElementIndex(fieldName);
445  assert(newFieldIndex.has_value());
446 
447  ImplicitLocOpBuilder builder(op.getLoc(), op);
448  auto newOp = builder.create<SubfieldOp>(newInput, *newFieldIndex);
449  if (auto name = op->getAttrOfType<StringAttr>("name"))
450  newOp->setAttr("name", name);
451 
452  hwOnlyAggMap[op.getResult()] = newOp;
453 
454  if (type_isa<FIRRTLBaseType>(op.getType()))
455  op.getResult().replaceAllUsesWith(newOp.getResult());
456 
457  return success();
458 }
459 
460 LogicalResult Visitor::visitExpr(OpenSubindexOp op) {
461  // Changes will be made.
462  recordChanges(true);
463 
464  // In all cases, this operation will be dead and should be removed.
465  opsToErase.push_back(op);
466 
467  // Chase this to its original root.
468  // If the FieldRef for this selection has a new home,
469  // RAUW to that value and this op is dead.
470  auto resultRef = refs.getFieldRefFromValue(op.getResult());
471  auto nonHWForResult = nonHWValues.find(resultRef);
472  if (nonHWForResult != nonHWValues.end()) {
473  // If has nonHW portion, RAUW to it.
474  if (auto newResult = nonHWForResult->second) {
475  assert(op.getResult().getType() == newResult.getType());
476  assert(!type_isa<FIRRTLBaseType>(newResult.getType()));
477  op.getResult().replaceAllUsesWith(newResult);
478  }
479  return success();
480  }
481 
482  assert(hwOnlyAggMap.count(op.getInput()));
483 
484  auto newInput = hwOnlyAggMap[op.getInput()];
485  assert(newInput);
486 
487  ImplicitLocOpBuilder builder(op.getLoc(), op);
488  auto newOp = builder.create<SubindexOp>(newInput, op.getIndex());
489  if (auto name = op->getAttrOfType<StringAttr>("name"))
490  newOp->setAttr("name", name);
491 
492  hwOnlyAggMap[op.getResult()] = newOp;
493 
494  if (type_isa<FIRRTLBaseType>(op.getType()))
495  op.getResult().replaceAllUsesWith(newOp.getResult());
496  return success();
497 }
498 
499 LogicalResult Visitor::visitDecl(InstanceOp op) {
500  // Rewrite ports same strategy as for modules.
501 
502  SmallVector<MappingInfo, 16> portMappings;
503 
504  for (auto type : op.getResultTypes()) {
505  auto pmi = mapType(type, op.getLoc());
506  if (failed(pmi))
507  return failure();
508  portMappings.push_back(*pmi);
509  }
510 
511  /// Total number of types mapped to.
512  size_t countWithErased = 0;
513  for (auto &pmi : portMappings)
514  countWithErased += pmi.count(/*includeErased=*/true);
515 
516  /// Ports to add.
517  SmallVector<std::pair<unsigned, PortInfo>> newPorts;
518 
519  /// Ports to remove.
520  BitVector portsToErase(countWithErased);
521 
522  /// Go through each port mapping, gathering information about all new ports.
523  LLVM_DEBUG({
524  llvm::dbgs().indent(6) << "- instance:\n";
525  llvm::dbgs().indent(10) << "name: " << op.getInstanceNameAttr() << "\n";
526  llvm::dbgs().indent(10) << "module: " << op.getModuleNameAttr() << "\n";
527  llvm::dbgs().indent(10) << "ports:\n";
528  });
529  auto result = walkMappings(
530  portMappings, /*includeErased=*/true,
531  [&](auto index, auto &pmi, auto newIndex) -> LogicalResult {
532  LLVM_DEBUG({
533  llvm::dbgs().indent(12)
534  << "- name: " << op.getPortName(index) << "\n";
535  llvm::dbgs().indent(14) << "type: " << op.getType(index) << "\n";
536  llvm::dbgs().indent(14) << "mapping:\n";
537  pmi.print(llvm::dbgs(), /*indent=*/16);
538  llvm::dbgs() << "\n";
539  });
540  // Index for inserting new points next to this point.
541  // (Immediately after current port's index).
542  auto idxOfInsertPoint = index + 1;
543 
544  if (pmi.identity)
545  return success();
546 
547  // If not identity, mark this port for eventual removal.
548  portsToErase.set(newIndex);
549 
550  auto portName = op.getPortName(index);
551  auto portDirection = op.getPortDirection(index);
552  auto loc = op.getLoc();
553 
554  // Create new hw-only port, this will generally replace this port.
555  if (pmi.hwType) {
556  PortInfo hwPort(portName, pmi.hwType, portDirection,
557  /*symName=*/StringAttr{}, loc,
558  AnnotationSet(op.getPortAnnotation(index)));
559  newPorts.emplace_back(idxOfInsertPoint, hwPort);
560 
561  // If want to run this pass later, need to fixup annotations.
562  if (!op.getPortAnnotation(index).empty())
563  return mlir::emitError(op.getLoc())
564  << "annotations on open aggregates not handled yet";
565  } else {
566  if (!op.getPortAnnotation(index).empty())
567  return mlir::emitError(op.getLoc())
568  << "annotations found on aggregate with no HW";
569  }
570 
571  // Create ports for each non-hw field.
572  for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
573  auto name =
574  StringAttr::get(context, Twine(portName.strref()) + field.suffix);
575  auto orientation =
576  (Direction)((unsigned)portDirection ^ field.isFlip);
577  PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
578  loc, std::nullopt);
579  newPorts.emplace_back(idxOfInsertPoint, pi);
580  }
581  return success();
582  });
583  if (failed(result))
584  return failure();
585 
586  // If no new ports, we're done.
587  if (newPorts.empty())
588  return success();
589 
590  // Changes will be made.
591  recordChanges(true);
592 
593  // Create new instance op with desired ports.
594 
595  // TODO: add and erase ports without intermediate + various array attributes.
596  auto tempOp = op.cloneAndInsertPorts(newPorts);
597  opsToErase.push_back(tempOp);
598  ImplicitLocOpBuilder builder(op.getLoc(), op);
599  auto newInst = tempOp.erasePorts(builder, portsToErase);
600 
601  auto mappingResult = walkMappings(
602  portMappings, /*includeErased=*/false,
603  [&](auto index, MappingInfo &pmi, auto newIndex) {
604  // Identity means index -> newIndex.
605  auto oldResult = op.getResult(index);
606  if (pmi.identity) {
607  // (Just do the RAUW here instead of tracking the mapping for this
608  // too.)
609  assert(oldResult.getType() == newInst.getType(newIndex));
610  oldResult.replaceAllUsesWith(newInst.getResult(newIndex));
611  return success();
612  }
613 
614  // Create mappings for updating open aggregate users.
615  auto newPortIndex = newIndex;
616  if (pmi.hwType)
617  hwOnlyAggMap[oldResult] = newInst.getResult(newPortIndex++);
618 
619  for (auto &field : pmi.fields) {
620  auto ref = FieldRef(oldResult, field.fieldID);
621  auto newVal = newInst.getResult(newPortIndex++);
622  assert(newVal.getType() == field.type);
623  nonHWValues[ref] = newVal;
624  }
625  for (auto fieldID : pmi.mapToNullInteriors) {
626  auto ref = FieldRef(oldResult, fieldID);
627  assert(!nonHWValues.count(ref));
628  nonHWValues[ref] = {};
629  }
630  return success();
631  });
632  if (failed(mappingResult))
633  return failure();
634 
635  opsToErase.push_back(op);
636 
637  return success();
638 }
639 
640 LogicalResult Visitor::visitDecl(WireOp op) {
641  auto pmi = mapType(op.getResultTypes()[0], op.getLoc(), op.getInnerSymAttr());
642  if (failed(pmi))
643  return failure();
644  MappingInfo mappings = *pmi;
645 
646  LLVM_DEBUG({
647  llvm::dbgs().indent(6) << "- wire:\n";
648  llvm::dbgs().indent(10) << "name: " << op.getNameAttr() << "\n";
649  llvm::dbgs().indent(10) << "type: " << op.getType(0) << "\n";
650  llvm::dbgs().indent(12) << "mapping:\n";
651  mappings.print(llvm::dbgs(), 14);
652  llvm::dbgs() << "\n";
653  });
654 
655  if (mappings.identity)
656  return success();
657 
658  // Changes will be made.
659  recordChanges(true);
660 
661  ImplicitLocOpBuilder builder(op.getLoc(), op);
662 
663  if (!op.getAnnotations().empty())
664  return mlir::emitError(op.getLoc())
665  << "annotations on open aggregates not handled yet";
666 
667  // Create the new HW wire.
668  if (mappings.hwType)
669  hwOnlyAggMap[op.getResult()] =
670  builder
671  .create<WireOp>(mappings.hwType, op.getName(), op.getNameKind(),
672  op.getAnnotations(), mappings.newSym,
673  op.getForceable())
674  .getResult();
675 
676  // Create the non-HW wires. Non-HW wire names are always droppable.
677  for (auto &[type, fieldID, _, suffix] : mappings.fields)
678  nonHWValues[FieldRef(op.getResult(), fieldID)] =
679  builder
680  .create<WireOp>(type,
681  builder.getStringAttr(Twine(op.getName()) + suffix),
682  NameKindEnum::DroppableName)
683  .getResult();
684 
685  for (auto fieldID : mappings.mapToNullInteriors)
686  nonHWValues[FieldRef(op.getResult(), fieldID)] = {};
687 
688  opsToErase.push_back(op);
689 
690  return success();
691 }
692 
693 //===----------------------------------------------------------------------===//
694 // Type Conversion
695 //===----------------------------------------------------------------------===//
696 
697 FailureOr<MappingInfo> Visitor::mapType(Type type, Location errorLoc,
698  hw::InnerSymAttr sym) {
699  MappingInfo pi{false, {}, {}, {}};
700  auto ftype = type_dyn_cast<FIRRTLType>(type);
701  // Anything that isn't an open aggregates is left alone.
702  if (!ftype || !isa<OpenBundleType, OpenVectorType>(ftype)) {
703  pi.identity = true;
704  return pi;
705  }
706 
707  SmallVector<hw::InnerSymPropertiesAttr> newProps;
708 
709  // NOLINTBEGIN(misc-no-recursion)
710  auto recurse = [&](auto &&f, FIRRTLType type, const Twine &suffix = "",
711  bool flip = false, uint64_t fieldID = 0,
712  uint64_t newFieldID = 0) -> FailureOr<FIRRTLBaseType> {
713  auto newType =
714  TypeSwitch<FIRRTLType, FailureOr<FIRRTLBaseType>>(type)
715  .Case<FIRRTLBaseType>([](auto base) { return base; })
716  .template Case<OpenBundleType>([&](OpenBundleType obTy)
717  -> FailureOr<FIRRTLBaseType> {
718  SmallVector<BundleType::BundleElement> hwElements;
719  uint64_t id = 0;
720  for (const auto &[index, element] :
721  llvm::enumerate(obTy.getElements())) {
722  auto base =
723  f(f, element.type, suffix + "_" + element.name.strref(),
724  flip ^ element.isFlip, fieldID + obTy.getFieldID(index),
725  newFieldID + id + 1);
726  if (failed(base))
727  return failure();
728  if (*base) {
729  hwElements.emplace_back(element.name, element.isFlip, *base);
730  id += hw::FieldIdImpl::getMaxFieldID(*base) + 1;
731  }
732  }
733 
734  if (hwElements.empty()) {
735  pi.mapToNullInteriors.push_back(fieldID);
736  return FIRRTLBaseType{};
737  }
738 
739  return BundleType::get(context, hwElements, obTy.isConst());
740  })
741  .template Case<OpenVectorType>([&](OpenVectorType ovTy)
742  -> FailureOr<FIRRTLBaseType> {
743  uint64_t id = 0;
744  FIRRTLBaseType convert;
745  // Walk for each index to extract each leaf separately, but expect
746  // same hw-only type for all.
747  for (auto idx : llvm::seq<size_t>(0U, ovTy.getNumElements())) {
748  auto hwElementType =
749  f(f, ovTy.getElementType(), suffix + "_" + Twine(idx), flip,
750  fieldID + ovTy.getFieldID(idx), newFieldID + id + 1);
751  if (failed(hwElementType))
752  return failure();
753  assert((!convert || convert == *hwElementType) &&
754  "expected same hw type for all elements");
755  convert = *hwElementType;
756  if (convert)
757  id += hw::FieldIdImpl::getMaxFieldID(convert) + 1;
758  }
759 
760  if (!convert) {
761  pi.mapToNullInteriors.push_back(fieldID);
762  return FIRRTLBaseType{};
763  }
764 
765  return FVectorType::get(convert, ovTy.getNumElements(),
766  ovTy.isConst());
767  })
768  .template Case<RefType>([&](RefType ref) {
769  auto f = NonHWField{ref, fieldID, flip, {}};
770  suffix.toVector(f.suffix);
771  pi.fields.emplace_back(std::move(f));
772  return FIRRTLBaseType{};
773  })
774  // This is identical to the RefType case above, but copied out
775  // to try to fix a bug when combining + auto w/MSVC.
776  .template Case<PropertyType>([&](PropertyType prop) {
777  auto f = NonHWField{prop, fieldID, flip, {}};
778  suffix.toVector(f.suffix);
779  pi.fields.emplace_back(std::move(f));
780  return FIRRTLBaseType{};
781  })
782  .Default([&](auto _) {
783  pi.mapToNullInteriors.push_back(fieldID);
784  return FIRRTLBaseType{};
785  });
786  if (failed(newType))
787  return failure();
788 
789  // If there's a symbol on this, add it with adjusted fieldID.
790  if (sym)
791  if (auto symOnThis = sym.getSymIfExists(fieldID)) {
792  if (!*newType)
793  return mlir::emitError(errorLoc, "inner symbol ")
794  << symOnThis << " mapped to non-HW type";
795  newProps.push_back(hw::InnerSymPropertiesAttr::get(
796  context, symOnThis, newFieldID,
797  StringAttr::get(context, "public")));
798  }
799  return newType;
800  };
801 
802  auto hwType = recurse(recurse, ftype);
803  if (failed(hwType))
804  return failure();
805  pi.hwType = *hwType;
806 
807  assert(pi.hwType != type);
808  // NOLINTEND(misc-no-recursion)
809 
810  if (sym) {
811  assert(sym.size() == newProps.size());
812 
813  if (!pi.hwType && !newProps.empty())
814  return mlir::emitError(errorLoc, "inner symbol on non-HW type");
815 
816  llvm::sort(newProps, [](auto &p, auto &q) {
817  return p.getFieldID() < q.getFieldID();
818  });
819  pi.newSym = hw::InnerSymAttr::get(context, newProps);
820  }
821 
822  return pi;
823 }
824 
825 //===----------------------------------------------------------------------===//
826 // Pass Infrastructure
827 //===----------------------------------------------------------------------===//
828 
829 namespace {
830 struct LowerOpenAggsPass
831  : public circt::firrtl::impl::LowerOpenAggsBase<LowerOpenAggsPass> {
832  LowerOpenAggsPass() = default;
833  void runOnOperation() override;
834 };
835 } // end anonymous namespace
836 
837 // This is the main entrypoint for the lowering pass.
838 void LowerOpenAggsPass::runOnOperation() {
839  LLVM_DEBUG(debugPassHeader(this) << "\n");
840  SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
841 
842  LLVM_DEBUG(llvm::dbgs() << "Visiting modules:\n");
843  std::atomic<bool> madeChanges = false;
844  auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
845  Visitor visitor(&getContext());
846  auto result = visitor.visit(cast<FModuleLike>(op));
847  if (visitor.madeChanges())
848  madeChanges = true;
849  return result;
850  });
851 
852  if (result.failed())
853  signalPassFailure();
854  if (!madeChanges)
855  markAllAnalysesPreserved();
856 }
857 
858 /// This is the pass constructor.
859 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerOpenAggsPass() {
860  return std::make_unique<LowerOpenAggsPass>();
861 }
assert(baseType &&"element must be base type")
static void dump(DIModule &module, raw_indented_ostream &os)
LogicalResult walkMappings(Range &&range, bool includeErased, llvm::function_ref< LogicalResult(size_t, MappingInfo &, size_t)> callback)
static Block * getBodyBlock(FModuleLike mod)
This class represents a reference to a specific field or element of an aggregate value.
Definition: FieldRef.h:28
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
FIRRTLVisitor allows you to visit all of the expr/stmt/decls with one class declaration.
Caching version of getFieldRefFromValue.
Definition: FieldRefCache.h:26
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Direction flip(Direction direction)
Flip a port direction.
Direction
This represents the direction of a single port.
std::unique_ptr< mlir::Pass > createLowerOpenAggsPass()
This is the pass constructor.
uint64_t getMaxFieldID(Type)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
llvm::raw_ostream & debugPassHeader(const mlir::Pass *pass, int width=80)
Write a boilerplate header for a pass to the debug stream.
Definition: Debug.cpp:31
This holds the name and type that describes the module's ports.