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