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