CIRCT 22.0.0git
Loading...
Searching...
No Matches
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
35namespace circt {
36namespace firrtl {
37#define GEN_PASS_DEF_LOWEROPENAGGS
38#include "circt/Dialect/FIRRTL/Passes.h.inc"
39} // namespace firrtl
40} // namespace circt
41
42using namespace circt;
43using namespace firrtl;
44
45namespace {
46
47/// Information on non-hw (ref/prop) elements.
48struct 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.
70struct 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
106void 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}
115void 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
143template <typename Range>
144LogicalResult 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
160namespace {
161class Visitor : public FIRRTLVisitor<Visitor, LogicalResult> {
162public:
163 explicit Visitor(MLIRContext *context) : context(context){};
164
165 /// Entrypoint.
166 LogicalResult visit(FModuleLike mod);
167
168 using FIRRTLVisitor<Visitor, LogicalResult>::visitDecl;
169 using FIRRTLVisitor<Visitor, LogicalResult>::visitExpr;
170 using FIRRTLVisitor<Visitor, LogicalResult>::visitStmt;
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
194private:
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
229LogicalResult 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
392LogicalResult 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 = SubfieldOp::create(builder, 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
460LogicalResult 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 = SubindexOp::create(builder, 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
499LogicalResult 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 portDomain = op.getPortDomain(index);
553 auto loc = op.getLoc();
554
555 // Create new hw-only port, this will generally replace this port.
556 if (pmi.hwType) {
557 PortInfo hwPort(portName, pmi.hwType, portDirection,
558 /*symName=*/StringAttr{}, loc,
559 AnnotationSet(op.getPortAnnotation(index)),
560 portDomain);
561 newPorts.emplace_back(idxOfInsertPoint, hwPort);
562
563 // If want to run this pass later, need to fixup annotations.
564 if (!op.getPortAnnotation(index).empty())
565 return mlir::emitError(op.getLoc())
566 << "annotations on open aggregates not handled yet";
567 } else {
568 if (!op.getPortAnnotation(index).empty())
569 return mlir::emitError(op.getLoc())
570 << "annotations found on aggregate with no HW";
571 }
572
573 // Create ports for each non-hw field.
574 for (const auto &[findex, field] : llvm::enumerate(pmi.fields)) {
575 auto name =
576 StringAttr::get(context, Twine(portName.strref()) + field.suffix);
577 auto orientation =
578 (Direction)((unsigned)portDirection ^ field.isFlip);
579 PortInfo pi(name, field.type, orientation, /*symName=*/StringAttr{},
580 loc, std::nullopt, portDomain);
581 newPorts.emplace_back(idxOfInsertPoint, pi);
582 }
583 return success();
584 });
585 if (failed(result))
586 return failure();
587
588 // If no new ports, we're done.
589 if (newPorts.empty())
590 return success();
591
592 // Changes will be made.
593 recordChanges(true);
594
595 // Create new instance op with desired ports.
596
597 // TODO: add and erase ports without intermediate + various array attributes.
598 auto tempOp = op.cloneAndInsertPorts(newPorts);
599 opsToErase.push_back(tempOp);
600 ImplicitLocOpBuilder builder(op.getLoc(), op);
601 auto newInst = tempOp.erasePorts(builder, portsToErase);
602
603 auto mappingResult = walkMappings(
604 portMappings, /*includeErased=*/false,
605 [&](auto index, MappingInfo &pmi, auto newIndex) {
606 // Identity means index -> newIndex.
607 auto oldResult = op.getResult(index);
608 if (pmi.identity) {
609 // (Just do the RAUW here instead of tracking the mapping for this
610 // too.)
611 assert(oldResult.getType() == newInst.getType(newIndex));
612 oldResult.replaceAllUsesWith(newInst.getResult(newIndex));
613 return success();
614 }
615
616 // Create mappings for updating open aggregate users.
617 auto newPortIndex = newIndex;
618 if (pmi.hwType)
619 hwOnlyAggMap[oldResult] = newInst.getResult(newPortIndex++);
620
621 for (auto &field : pmi.fields) {
622 auto ref = FieldRef(oldResult, field.fieldID);
623 auto newVal = newInst.getResult(newPortIndex++);
624 assert(newVal.getType() == field.type);
625 nonHWValues[ref] = newVal;
626 }
627 for (auto fieldID : pmi.mapToNullInteriors) {
628 auto ref = FieldRef(oldResult, fieldID);
629 assert(!nonHWValues.count(ref));
630 nonHWValues[ref] = {};
631 }
632 return success();
633 });
634 if (failed(mappingResult))
635 return failure();
636
637 opsToErase.push_back(op);
638
639 return success();
640}
641
642LogicalResult Visitor::visitDecl(WireOp op) {
643 auto pmi = mapType(op.getResultTypes()[0], op.getLoc(), op.getInnerSymAttr());
644 if (failed(pmi))
645 return failure();
646 MappingInfo mappings = *pmi;
647
648 LLVM_DEBUG({
649 llvm::dbgs().indent(6) << "- wire:\n";
650 llvm::dbgs().indent(10) << "name: " << op.getNameAttr() << "\n";
651 llvm::dbgs().indent(10) << "type: " << op.getType(0) << "\n";
652 llvm::dbgs().indent(12) << "mapping:\n";
653 mappings.print(llvm::dbgs(), 14);
654 llvm::dbgs() << "\n";
655 });
656
657 if (mappings.identity)
658 return success();
659
660 // Changes will be made.
661 recordChanges(true);
662
663 ImplicitLocOpBuilder builder(op.getLoc(), op);
664
665 if (!op.getAnnotations().empty())
666 return mlir::emitError(op.getLoc())
667 << "annotations on open aggregates not handled yet";
668
669 // Create the new HW wire.
670 if (mappings.hwType)
671 hwOnlyAggMap[op.getResult()] =
672 WireOp::create(builder, mappings.hwType, op.getName(), op.getNameKind(),
673 op.getAnnotations(), mappings.newSym, 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 WireOp::create(builder, type,
680 builder.getStringAttr(Twine(op.getName()) + suffix),
681 NameKindEnum::DroppableName)
682 .getResult();
683
684 for (auto fieldID : mappings.mapToNullInteriors)
685 nonHWValues[FieldRef(op.getResult(), fieldID)] = {};
686
687 opsToErase.push_back(op);
688
689 return success();
690}
691
692//===----------------------------------------------------------------------===//
693// Type Conversion
694//===----------------------------------------------------------------------===//
695
696FailureOr<MappingInfo> Visitor::mapType(Type type, Location errorLoc,
697 hw::InnerSymAttr sym) {
698 MappingInfo pi{false, {}, {}, {}};
699 auto ftype = type_dyn_cast<FIRRTLType>(type);
700 // Anything that isn't an open aggregates is left alone.
701 if (!ftype || !isa<OpenBundleType, OpenVectorType>(ftype)) {
702 pi.identity = true;
703 return pi;
704 }
705
706 SmallVector<hw::InnerSymPropertiesAttr> newProps;
707
708 // NOLINTBEGIN(misc-no-recursion)
709 auto recurse = [&](auto &&f, FIRRTLType type, const Twine &suffix = "",
710 bool flip = false, uint64_t fieldID = 0,
711 uint64_t newFieldID = 0) -> FailureOr<FIRRTLBaseType> {
712 auto newType =
713 TypeSwitch<FIRRTLType, FailureOr<FIRRTLBaseType>>(type)
714 .Case<FIRRTLBaseType>([](auto base) { return base; })
715 .template Case<OpenBundleType>([&](OpenBundleType obTy)
716 -> FailureOr<FIRRTLBaseType> {
717 SmallVector<BundleType::BundleElement> hwElements;
718 uint64_t id = 0;
719 for (const auto &[index, element] :
720 llvm::enumerate(obTy.getElements())) {
721 auto base =
722 f(f, element.type, suffix + "_" + element.name.strref(),
723 flip ^ element.isFlip, fieldID + obTy.getFieldID(index),
724 newFieldID + id + 1);
725 if (failed(base))
726 return failure();
727 if (*base) {
728 hwElements.emplace_back(element.name, element.isFlip, *base);
729 id += hw::FieldIdImpl::getMaxFieldID(*base) + 1;
730 }
731 }
732
733 if (hwElements.empty()) {
734 pi.mapToNullInteriors.push_back(fieldID);
735 return FIRRTLBaseType{};
736 }
737
738 return BundleType::get(context, hwElements, obTy.isConst());
739 })
740 .template Case<OpenVectorType>([&](OpenVectorType ovTy)
741 -> FailureOr<FIRRTLBaseType> {
742 uint64_t id = 0;
744 // Walk for each index to extract each leaf separately, but expect
745 // same hw-only type for all.
746 for (auto idx : llvm::seq<size_t>(0U, ovTy.getNumElements())) {
747 auto hwElementType =
748 f(f, ovTy.getElementType(), suffix + "_" + Twine(idx), flip,
749 fieldID + ovTy.getFieldID(idx), newFieldID + id + 1);
750 if (failed(hwElementType))
751 return failure();
752 assert((!convert || convert == *hwElementType) &&
753 "expected same hw type for all elements");
754 convert = *hwElementType;
755 if (convert)
757 }
758
759 if (!convert) {
760 pi.mapToNullInteriors.push_back(fieldID);
761 return FIRRTLBaseType{};
762 }
763
764 return FVectorType::get(convert, ovTy.getNumElements(),
765 ovTy.isConst());
766 })
767 .template Case<RefType>([&](RefType ref) {
768 auto f = NonHWField{ref, fieldID, flip, {}};
769 suffix.toVector(f.suffix);
770 pi.fields.emplace_back(std::move(f));
771 return FIRRTLBaseType{};
772 })
773 // This is identical to the RefType case above, but copied out
774 // to try to fix a bug when combining + auto w/MSVC.
775 .template Case<PropertyType>([&](PropertyType prop) {
776 auto f = NonHWField{prop, fieldID, flip, {}};
777 suffix.toVector(f.suffix);
778 pi.fields.emplace_back(std::move(f));
779 return FIRRTLBaseType{};
780 })
781 .Default([&](auto _) {
782 pi.mapToNullInteriors.push_back(fieldID);
783 return FIRRTLBaseType{};
784 });
785 if (failed(newType))
786 return failure();
787
788 // If there's a symbol on this, add it with adjusted fieldID.
789 if (sym)
790 if (auto symOnThis = sym.getSymIfExists(fieldID)) {
791 if (!*newType)
792 return mlir::emitError(errorLoc, "inner symbol ")
793 << symOnThis << " mapped to non-HW type";
794 newProps.push_back(hw::InnerSymPropertiesAttr::get(
795 context, symOnThis, newFieldID,
796 StringAttr::get(context, "public")));
797 }
798 return newType;
799 };
800
801 auto hwType = recurse(recurse, ftype);
802 if (failed(hwType))
803 return failure();
804 pi.hwType = *hwType;
805
806 assert(pi.hwType != type);
807 // NOLINTEND(misc-no-recursion)
808
809 if (sym) {
810 assert(sym.size() == newProps.size());
811
812 if (!pi.hwType && !newProps.empty())
813 return mlir::emitError(errorLoc, "inner symbol on non-HW type");
814
815 llvm::sort(newProps, [](auto &p, auto &q) {
816 return p.getFieldID() < q.getFieldID();
817 });
818 pi.newSym = hw::InnerSymAttr::get(context, newProps);
819 }
820
821 return pi;
822}
823
824//===----------------------------------------------------------------------===//
825// Pass Infrastructure
826//===----------------------------------------------------------------------===//
827
828namespace {
829struct LowerOpenAggsPass
830 : public circt::firrtl::impl::LowerOpenAggsBase<LowerOpenAggsPass> {
831 LowerOpenAggsPass() = default;
832 void runOnOperation() override;
833};
834} // end anonymous namespace
835
836// This is the main entrypoint for the lowering pass.
837void LowerOpenAggsPass::runOnOperation() {
838 LLVM_DEBUG(debugPassHeader(this) << "\n");
839 SmallVector<Operation *, 0> ops(getOperation().getOps<FModuleLike>());
840
841 LLVM_DEBUG(llvm::dbgs() << "Visiting modules:\n");
842 std::atomic<bool> madeChanges = false;
843 auto result = failableParallelForEach(&getContext(), ops, [&](Operation *op) {
844 Visitor visitor(&getContext());
845 auto result = visitor.visit(cast<FModuleLike>(op));
846 if (visitor.madeChanges())
847 madeChanges = true;
848 return result;
849 });
850
851 if (result.failed())
852 signalPassFailure();
853 if (!madeChanges)
854 markAllAnalysesPreserved();
855}
assert(baseType &&"element must be base type")
static void dump(DIModule &module, raw_indented_ostream &os)
static void print(TypedAttr val, llvm::raw_ostream &os)
LogicalResult walkMappings(Range &&range, bool includeErased, llvm::function_ref< LogicalResult(size_t, MappingInfo &, size_t)> callback)
static LogicalResult convert(StopBIOp op, StopBIOp::Adaptor adaptor, ConversionPatternRewriter &rewriter)
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.
ResultType visitInvalidOp(Operation *op, ExtraArgs... args)
visitInvalidOp is an override point for non-FIRRTL dialect operations.
ResultType visitUnhandledOp(Operation *op, ExtraArgs... args)
visitUnhandledOp is an override point for FIRRTL dialect ops that the concrete visitor didn't bother ...
Caching version of getFieldRefFromValue.
Direction
This represents the direction of a single port.
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
ModulePort::Direction flip(ModulePort::Direction direction)
Flip a port direction.
Definition HWOps.cpp:36
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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
Definition seq.py:1
This holds the name and type that describes the module's ports.