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