CIRCT 24.0.0git
Loading...
Searching...
No Matches
Dedup.cpp
Go to the documentation of this file.
1//===- Dedup.cpp - FIRRTL module deduping -----------------------*- 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 implements FIRRTL module deduplication.
10//
11//===----------------------------------------------------------------------===//
12
24#include "circt/Support/Debug.h"
25#include "circt/Support/LLVM.h"
26#include "mlir/IR/IRMapping.h"
27#include "mlir/IR/Threading.h"
28#include "mlir/Pass/Pass.h"
29#include "llvm/ADT/DenseMap.h"
30#include "llvm/ADT/DenseMapInfo.h"
31#include "llvm/ADT/Hashing.h"
32#include "llvm/ADT/PostOrderIterator.h"
33#include "llvm/ADT/SmallPtrSet.h"
34#include "llvm/Support/Debug.h"
35#include "llvm/Support/Format.h"
36#include "llvm/Support/SHA256.h"
37
38#define DEBUG_TYPE "firrtl-dedup"
39
40namespace circt {
41namespace firrtl {
42#define GEN_PASS_DEF_DEDUP
43#include "circt/Dialect/FIRRTL/Passes.h.inc"
44} // namespace firrtl
45} // namespace circt
46
47using namespace circt;
48using namespace firrtl;
49using hw::InnerRefAttr;
50
51//===----------------------------------------------------------------------===//
52// Utility function for classifying a Symbol's dedup-ability.
53//===----------------------------------------------------------------------===//
54
55/// Returns true if the module can be removed.
56static bool canRemoveModule(mlir::SymbolOpInterface symbol) {
57 // If the symbol is not private, it cannot be removed.
58 if (!symbol.isPrivate())
59 return false;
60 // If module can not be removed even if no uses can be found, we can not
61 // delete it. The implication is that there are hidden symbol uses that dedup
62 // will not properly update.
63 if (!symbol.canDiscardOnUseEmpty())
64 return false;
65 // The module can be deleted.
66 return true;
67}
68
69//===----------------------------------------------------------------------===//
70// Hashing
71//===----------------------------------------------------------------------===//
72
73// This struct contains information to determine module module uniqueness. A
74// first element is a structural hash of the module, and the second element is
75// an array which tracks module names encountered in the walk. Since module
76// names could be replaced during dedup, it's necessary to keep names up-to-date
77// before actually combining them into structural hashes.
78struct ModuleInfo {
79 // SHA256 hash.
80 std::array<uint8_t, 32> structuralHash;
81 // Module names referred by instance op in the module.
82 std::vector<StringAttr> referredModuleNames;
83};
84
85static bool operator==(const ModuleInfo &lhs, const ModuleInfo &rhs) {
86 return lhs.structuralHash == rhs.structuralHash &&
88}
89
90/// This struct contains constant string attributes shared across different
91/// threads.
94 portTypesAttr = StringAttr::get(context, "portTypes");
95 moduleNameAttr = StringAttr::get(context, "moduleName");
96 portNamesAttr = StringAttr::get(context, "portNames");
97 nonessentialAttributes.insert(StringAttr::get(context, "annotations"));
98 nonessentialAttributes.insert(StringAttr::get(context, "convention"));
99 nonessentialAttributes.insert(StringAttr::get(context, "inner_sym"));
100 nonessentialAttributes.insert(StringAttr::get(context, "name"));
101 nonessentialAttributes.insert(StringAttr::get(context, "portAnnotations"));
102 nonessentialAttributes.insert(StringAttr::get(context, "portLocations"));
103 nonessentialAttributes.insert(StringAttr::get(context, "portNames"));
104 nonessentialAttributes.insert(StringAttr::get(context, "portSymbols"));
105 nonessentialAttributes.insert(StringAttr::get(context, "sym_name"));
106 nonessentialAttributes.insert(StringAttr::get(context, "sym_visibility"));
107 };
108
109 // This is a cached "portTypes" string attr.
110 StringAttr portTypesAttr;
111
112 // This is a cached "moduleName" string attr.
113 StringAttr moduleNameAttr;
114
115 // This is a cached "moduleNames" string attr.
116 StringAttr moduleNamesAttr;
117
118 // This is a cached "portNames" string attr.
119 StringAttr portNamesAttr;
120
121 // This is a set of every attribute we should ignore.
122 DenseSet<Attribute> nonessentialAttributes;
123};
124
128
129 ModuleInfo getModuleInfo(FModuleLike module) {
131 update(&(*module));
132 return {sha.final(), std::move(referredModuleNames)};
133 }
134
135private:
136 /// Find all the ports and operations which may define an inner symbol
137 /// operations and give each a unique id. If the port/operation does define
138 /// an inner symbol, map the symbol name to a pair of the id and the symbol's
139 /// field id. When we hash (local) references to this inner symbol, we will
140 /// hash in the id and the the field id.
141 void populateInnerSymIDTable(FModuleLike module) {
142 // Add port symbols. If no port has a symbol defined, the port symbol array
143 // will be totally empty.
144 for (auto [index, innerSym] : llvm::enumerate(module.getPortSymbols())) {
145 for (auto prop : cast<hw::InnerSymAttr>(innerSym))
146 innerSymIDTable[prop.getName()] = std::pair(index, prop.getFieldID());
147 }
148 // Add operation symbols.
149 size_t index = module.getNumPorts();
150 module.walk([&](hw::InnerSymbolOpInterface innerSymOp) {
151 if (auto innerSym = innerSymOp.getInnerSymAttr()) {
152 for (auto prop : innerSym)
153 innerSymIDTable[prop.getName()] = std::pair(index, prop.getFieldID());
154 }
155 ++index;
156 });
157 }
158
159 // Get the identifier for an object. The identifier is assigned on first use.
160 unsigned getID(void *object) {
161 auto [it, inserted] = idTable.try_emplace(object, nextID);
162 if (inserted)
163 ++nextID;
164 return it->second;
165 }
166
167 // Get the identifier for an IR object. Free the ID, too.
168 unsigned finalizeID(void *object) {
169 auto it = idTable.find(object);
170 if (it == idTable.end())
171 return nextID++;
172 auto id = it->second;
173 idTable.erase(it);
174 return id;
175 }
176
177 std::pair<size_t, size_t> getInnerSymID(StringAttr name) {
178 return innerSymIDTable.at(name);
179 }
180
181 void update(OpOperand &operand) {
182 auto value = operand.get();
183 if (auto result = dyn_cast<OpResult>(value)) {
184 auto *op = result.getOwner();
185 update(getID(op));
186 update(result.getResultNumber());
187 return;
188 }
189 if (auto argument = dyn_cast<BlockArgument>(value)) {
190 auto *block = argument.getOwner();
191 update(getID(block));
192 update(argument.getArgNumber());
193 return;
194 }
195 llvm_unreachable("Unknown value type");
196 }
197
198 void update(const void *pointer) {
199 auto *addr = reinterpret_cast<const uint8_t *>(&pointer);
200 sha.update(ArrayRef<uint8_t>(addr, sizeof pointer));
201 }
202
203 void update(size_t value) {
204 auto *addr = reinterpret_cast<const uint8_t *>(&value);
205 sha.update(ArrayRef<uint8_t>(addr, sizeof value));
206 }
207
208 template <typename T, typename U>
209 void update(const std::pair<T, U> &pair) {
210 update(pair.first);
211 update(pair.second);
212 }
213
214 void update(TypeID typeID) { update(typeID.getAsOpaquePointer()); }
215
216 // NOLINTNEXTLINE(misc-no-recursion)
217 void update(BundleType type) {
218 update(type.getTypeID());
219 update(type.getNumElements());
220 for (auto &element : type.getElements()) {
221 update(element.isFlip);
222 update(element.type);
223 }
224 }
225
226 // NOLINTNEXTLINE(misc-no-recursion)
227 void update(Type type) {
228 if (auto bundle = type_dyn_cast<BundleType>(type))
229 return update(bundle);
230 update(type.getAsOpaquePointer());
231 }
232
233 void update(OpResult result) { update(result.getType()); }
234
235 /// Hash the top level attribute dictionary of the operation. This function
236 /// has special handling for inner symbols, ports, and referenced modules.
237 void update(Operation *op, DictionaryAttr dict) {
238 for (auto namedAttr : dict) {
239 auto name = namedAttr.getName();
240 auto value = namedAttr.getValue();
241
242 // Skip names and annotations.
243 if (constants.nonessentialAttributes.contains(name))
244 continue;
245
246 // Hash the attribute name (an interned pointer).
247 update(name.getAsOpaquePointer());
248
249 // Hash the port types.
250 if (name == constants.portTypesAttr) {
251 auto portTypes = cast<ArrayAttr>(value).getAsValueRange<TypeAttr>();
252 for (auto type : portTypes)
253 update(type);
254 continue;
255 }
256
257 // For instance op, don't use `moduleName` attributes since they might be
258 // replaced by dedup. Record the names and lazily combine their hashes.
259 // It is assumed that module names are hashed only through instance ops;
260 // it could cause suboptimal results if there was other operation that
261 // refers to module names through essential attributes.
262 if (isa<InstanceOp>(op) && name == constants.moduleNameAttr) {
263 referredModuleNames.push_back(cast<FlatSymbolRefAttr>(value).getAttr());
264 continue;
265 }
266
267 if (isa<InstanceChoiceOp>(op) && name == constants.moduleNamesAttr) {
268 for (auto module : cast<ArrayAttr>(value))
269 referredModuleNames.push_back(
270 cast<FlatSymbolRefAttr>(module).getAttr());
271 continue;
272 }
273
274 // TODO: properly handle DistinctAttr, including its use in paths.
275 // See https://github.com/llvm/circt/issues/6583.
276 if (isa<DistinctAttr>(value))
277 continue;
278
279 // If this is an symbol reference, we need to perform name erasure.
280 if (auto innerRef = dyn_cast<hw::InnerRefAttr>(value)) {
281 update(getInnerSymID(innerRef.getName()));
282 continue;
283 }
284
285 // We don't need to handle this attribute specially, so hash its unique
286 // address.
287 update(value.getAsOpaquePointer());
288 }
289 }
290
291 void update(mlir::OperationName name) {
292 // Operation names are interned.
293 update(name.getAsOpaquePointer());
294 }
295
296 // NOLINTNEXTLINE(misc-no-recursion)
297 void update(Block *block) {
298 for (auto &op : llvm::reverse(*block))
299 update(&op);
300 for (auto type : block->getArgumentTypes())
301 update(type);
302 update(finalizeID(block));
304 ++position;
305 }
306
307 // NOLINTNEXTLINE(misc-no-recursion)
308 void update(Region *region) {
309 for (auto &block : llvm::reverse(region->getBlocks()))
310 update(&block);
312 ++position;
313 }
314
315 // NOLINTNEXTLINE(misc-no-recursion)
316 void update(Operation *op) {
317 // Hash the regions. We need to make sure an empty region doesn't hash the
318 // same as no region, so we include the number of regions.
319 update(op->getNumRegions());
320 for (auto &region : reverse(op->getRegions()))
321 update(&region);
322
323 update(op->getName());
324
325 // Record the uses for later hashing.
326 for (auto &operand : op->getOpOperands())
327 update(operand);
328
329 // This happens after the numbering above, as it uses blockarg numbering
330 // for inner symbols.
331 update(op, op->getAttrDictionary());
332
333 // Record any op results (types).
334 for (auto result : op->getResults())
335 update(result);
336
337 // Incorporate the hash of uses we have already built.
338 update(finalizeID(op));
340 ++position;
341 }
342
343 // A map from an operation/block, to its identifier.
344 DenseMap<void *, unsigned> idTable;
345 unsigned nextID = 0;
346
347 // A map from an inner symbol, to its identifier.
348 DenseMap<StringAttr, std::pair<size_t, size_t>> innerSymIDTable;
349
350 // This keeps track of module names in the order of the appearance.
351 std::vector<StringAttr> referredModuleNames;
352
353 // String constants.
355
356 // This is the actual running hash calculation. This is a stateful element
357 // that should be reinitialized after each hash is produced.
358 llvm::SHA256 sha;
359
360 // The index of the current op. Increment after handling each op.
361 size_t position = 0;
362};
363
364/// A reference to a `ModuleInfo` that compares and hashes like it. This is used
365/// to keep the potentially heavy module infos in a vector, and then populate
366/// maps with references to them.
371
372/// Allow `ModuleInfoRef` to be used as dense map keys. Hashes and compares the
373/// `ModuleInfo` the ref points to.
374template <>
376 static unsigned getHashValue(const ModuleInfoRef &ref) {
377 // We assume SHA256 is already a good hash and just truncate down to the
378 // number of bytes we need for DenseMap.
379 unsigned hash;
380 std::memcpy(&hash, ref.info->structuralHash.data(), sizeof(unsigned));
381
382 // Combine module names.
383 return llvm::hash_combine(
384 hash, llvm::hash_combine_range(ref.info->referredModuleNames.begin(),
385 ref.info->referredModuleNames.end()));
386 }
387
388 static bool isEqual(const ModuleInfoRef &lhs, const ModuleInfoRef &rhs) {
389 if (!lhs.info || !rhs.info)
390 return lhs.info == rhs.info;
391 return *lhs.info == *rhs.info;
392 }
393};
394
395//===----------------------------------------------------------------------===//
396// Equivalence
397//===----------------------------------------------------------------------===//
398
399/// This class is for reporting differences between two modules which should
400/// have been deduplicated.
404 noDedupClass = StringAttr::get(context, noDedupAnnoClass);
405 dedupGroupAttrName = StringAttr::get(context, "firrtl.dedup_group");
406 portDirectionsAttr = StringAttr::get(context, "portDirections");
407 nonessentialAttributes.insert(StringAttr::get(context, "annotations"));
408 nonessentialAttributes.insert(StringAttr::get(context, "name"));
409 nonessentialAttributes.insert(StringAttr::get(context, "portAnnotations"));
410 nonessentialAttributes.insert(StringAttr::get(context, "portNames"));
411 nonessentialAttributes.insert(StringAttr::get(context, "portTypes"));
412 nonessentialAttributes.insert(StringAttr::get(context, "portSymbols"));
413 nonessentialAttributes.insert(StringAttr::get(context, "portLocations"));
414 nonessentialAttributes.insert(StringAttr::get(context, "sym_name"));
415 nonessentialAttributes.insert(StringAttr::get(context, "inner_sym"));
416 }
417
425
426 std::string prettyPrint(Attribute attr) {
427 SmallString<64> buffer;
428 llvm::raw_svector_ostream os(buffer);
429 if (auto integerAttr = dyn_cast<IntegerAttr>(attr)) {
430 os << "0x";
431 if (integerAttr.getType().isSignlessInteger())
432 integerAttr.getValue().toStringUnsigned(buffer, /*radix=*/16);
433 else
434 integerAttr.getAPSInt().toString(buffer, /*radix=*/16);
435
436 } else
437 os << attr;
438 return std::string(buffer);
439 }
440
441 // NOLINTNEXTLINE(misc-no-recursion)
442 LogicalResult check(InFlightDiagnostic &diag, const Twine &message,
443 Operation *a, BundleType aType, Operation *b,
444 BundleType bType) {
445 if (aType.getNumElements() != bType.getNumElements()) {
446 diag.attachNote(a->getLoc())
447 << message << " bundle type has different number of elements";
448 diag.attachNote(b->getLoc()) << "second operation here";
449 return failure();
450 }
451
452 for (auto elementPair :
453 llvm::zip(aType.getElements(), bType.getElements())) {
454 auto aElement = std::get<0>(elementPair);
455 auto bElement = std::get<1>(elementPair);
456 if (aElement.isFlip != bElement.isFlip) {
457 diag.attachNote(a->getLoc()) << message << " bundle element "
458 << aElement.name << " flip does not match";
459 diag.attachNote(b->getLoc()) << "second operation here";
460 return failure();
461 }
462
463 if (failed(check(diag,
464 message + " -> bundle element \'" +
465 aElement.name.getValue() + "'",
466 a, aElement.type, b, bElement.type)))
467 return failure();
468 }
469 return success();
470 }
471
472 LogicalResult check(InFlightDiagnostic &diag, const Twine &message,
473 Operation *a, Type aType, Operation *b, Type bType) {
474 if (aType == bType)
475 return success();
476 if (auto aBundleType = type_dyn_cast<BundleType>(aType))
477 if (auto bBundleType = type_dyn_cast<BundleType>(bType))
478 return check(diag, message, a, aBundleType, b, bBundleType);
479 if (type_isa<RefType>(aType) && type_isa<RefType>(bType) &&
480 aType != bType) {
481 diag.attachNote(a->getLoc())
482 << message << ", has a RefType with a different base type "
483 << type_cast<RefType>(aType).getType()
484 << " in the same position of the two modules marked as 'must dedup'. "
485 "(This may be due to Grand Central Taps or Views being different "
486 "between the two modules.)";
487 diag.attachNote(b->getLoc())
488 << "the second module has a different base type "
489 << type_cast<RefType>(bType).getType();
490 return failure();
491 }
492 diag.attachNote(a->getLoc())
493 << message << " types don't match, first type is " << aType;
494 diag.attachNote(b->getLoc()) << "second type is " << bType;
495 return failure();
496 }
497
498 LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a,
499 Block &aBlock, Operation *b, Block &bBlock) {
500
501 // Block argument types.
502 auto portNames = a->getAttrOfType<ArrayAttr>("portNames");
503 auto portNo = 0;
504 auto emitMissingPort = [&](Value existsVal, Operation *opExists,
505 Operation *opDoesNotExist) {
506 StringRef portName;
507 auto portNames = opExists->getAttrOfType<ArrayAttr>("portNames");
508 if (portNames)
509 if (auto portNameAttr = dyn_cast<StringAttr>(portNames[portNo]))
510 portName = portNameAttr.getValue();
511 if (type_isa<RefType>(existsVal.getType())) {
512 diag.attachNote(opExists->getLoc())
513 << " contains a RefType port named '" + portName +
514 "' that only exists in one of the modules (can be due to "
515 "difference in Grand Central Tap or View of two modules "
516 "marked with must dedup)";
517 diag.attachNote(opDoesNotExist->getLoc())
518 << "second module to be deduped that does not have the RefType "
519 "port";
520 } else {
521 diag.attachNote(opExists->getLoc())
522 << "port '" + portName + "' only exists in one of the modules";
523 diag.attachNote(opDoesNotExist->getLoc())
524 << "second module to be deduped that does not have the port";
525 }
526 return failure();
527 };
528
529 for (auto argPair :
530 llvm::zip_longest(aBlock.getArguments(), bBlock.getArguments())) {
531 auto &aArg = std::get<0>(argPair);
532 auto &bArg = std::get<1>(argPair);
533 if (aArg.has_value() && bArg.has_value()) {
534 // TODO: we should print the port number if there are no port names, but
535 // there are always port names ;).
536 StringRef portName;
537 if (portNames) {
538 if (auto portNameAttr = dyn_cast<StringAttr>(portNames[portNo]))
539 portName = portNameAttr.getValue();
540 }
541 // Assumption here that block arguments correspond to ports.
542 if (failed(check(diag, "module port '" + portName + "'", a,
543 aArg->getType(), b, bArg->getType())))
544 return failure();
545 data.map.map(aArg.value(), bArg.value());
546 portNo++;
547 continue;
548 }
549 if (!aArg.has_value())
550 std::swap(a, b);
551 return emitMissingPort(aArg.has_value() ? aArg.value() : bArg.value(), a,
552 b);
553 }
554
555 // Blocks operations.
556 auto aIt = aBlock.begin();
557 auto aEnd = aBlock.end();
558 auto bIt = bBlock.begin();
559 auto bEnd = bBlock.end();
560 while (aIt != aEnd && bIt != bEnd)
561 if (failed(check(diag, data, &*aIt++, &*bIt++)))
562 return failure();
563 if (aIt != aEnd) {
564 diag.attachNote(aIt->getLoc()) << "first block has more operations";
565 diag.attachNote(b->getLoc()) << "second block here";
566 return failure();
567 }
568 if (bIt != bEnd) {
569 diag.attachNote(bIt->getLoc()) << "second block has more operations";
570 diag.attachNote(a->getLoc()) << "first block here";
571 return failure();
572 }
573 return success();
574 }
575
576 LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a,
577 Region &aRegion, Operation *b, Region &bRegion) {
578 auto aIt = aRegion.begin();
579 auto aEnd = aRegion.end();
580 auto bIt = bRegion.begin();
581 auto bEnd = bRegion.end();
582
583 // Region blocks.
584 while (aIt != aEnd && bIt != bEnd)
585 if (failed(check(diag, data, a, *aIt++, b, *bIt++)))
586 return failure();
587 if (aIt != aEnd || bIt != bEnd) {
588 diag.attachNote(a->getLoc())
589 << "operation regions have different number of blocks";
590 diag.attachNote(b->getLoc()) << "second operation here";
591 return failure();
592 }
593 return success();
594 }
595
596 LogicalResult check(InFlightDiagnostic &diag, Operation *a,
597 mlir::DenseBoolArrayAttr aAttr, Operation *b,
598 mlir::DenseBoolArrayAttr bAttr) {
599 if (aAttr == bAttr)
600 return success();
601 auto portNames = a->getAttrOfType<ArrayAttr>("portNames");
602 for (unsigned i = 0, e = aAttr.size(); i < e; ++i) {
603 auto aDirection = aAttr[i];
604 auto bDirection = bAttr[i];
605 if (aDirection != bDirection) {
606 auto &note = diag.attachNote(a->getLoc()) << "module port ";
607 if (portNames)
608 note << "'" << cast<StringAttr>(portNames[i]).getValue() << "'";
609 else
610 note << i;
611 note << " directions don't match, first direction is '"
612 << direction::toString(aDirection) << "'";
613 diag.attachNote(b->getLoc()) << "second direction is '"
614 << direction::toString(bDirection) << "'";
615 return failure();
616 }
617 }
618 return success();
619 }
620
621 LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a,
622 DictionaryAttr aDict, Operation *b,
623 DictionaryAttr bDict) {
624 // Fast path.
625 if (aDict == bDict)
626 return success();
627
628 DenseSet<Attribute> seenAttrs;
629 for (auto namedAttr : aDict) {
630 auto attrName = namedAttr.getName();
631 if (nonessentialAttributes.contains(attrName))
632 continue;
633
634 auto aAttr = namedAttr.getValue();
635 auto bAttr = bDict.get(attrName);
636 if (!bAttr) {
637 diag.attachNote(a->getLoc())
638 << "second operation is missing attribute " << attrName;
639 diag.attachNote(b->getLoc()) << "second operation here";
640 return diag;
641 }
642
643 if (isa<hw::InnerRefAttr>(aAttr) && isa<hw::InnerRefAttr>(bAttr)) {
644 auto bRef = cast<hw::InnerRefAttr>(bAttr);
645 auto aRef = cast<hw::InnerRefAttr>(aAttr);
646 // See if they are pointing at the same operation or port.
647 auto aTarget = data.a.lookup(aRef.getName());
648 auto bTarget = data.b.lookup(bRef.getName());
649 if (!aTarget || !bTarget)
650 diag.attachNote(a->getLoc())
651 << "malformed ir, possibly violating use-before-def";
652 auto error = [&]() {
653 diag.attachNote(a->getLoc())
654 << "operations have different targets, first operation has "
655 << aTarget;
656 diag.attachNote(b->getLoc()) << "second operation has " << bTarget;
657 return failure();
658 };
659 if (aTarget.isPort()) {
660 // If they are targeting ports, make sure its the same port number.
661 if (!bTarget.isPort() || aTarget.getPort() != bTarget.getPort())
662 return error();
663 } else {
664 // Otherwise make sure that they are targeting the same operation.
665 if (!bTarget.isOpOnly() ||
666 data.map.lookupOrNull(aTarget.getOp()) != bTarget.getOp())
667 return error();
668 }
669 if (aTarget.getField() != bTarget.getField())
670 return error();
671 } else if (attrName == portDirectionsAttr) {
672 // Special handling for the port directions attribute for better
673 // error messages.
674 if (failed(check(diag, a, cast<mlir::DenseBoolArrayAttr>(aAttr), b,
675 cast<mlir::DenseBoolArrayAttr>(bAttr))))
676 return failure();
677 } else if (isa<DistinctAttr>(aAttr) && isa<DistinctAttr>(bAttr)) {
678 // TODO: properly handle DistinctAttr, including its use in paths.
679 // See https://github.com/llvm/circt/issues/6583
680 } else if (aAttr != bAttr) {
681 diag.attachNote(a->getLoc())
682 << "first operation has attribute '" << attrName.getValue()
683 << "' with value " << prettyPrint(aAttr);
684 diag.attachNote(b->getLoc())
685 << "second operation has value " << prettyPrint(bAttr);
686 return failure();
687 }
688 seenAttrs.insert(attrName);
689 }
690 if (aDict.getValue().size() != bDict.getValue().size()) {
691 for (auto namedAttr : bDict) {
692 auto attrName = namedAttr.getName();
693 // Skip the attribute if we don't care about this particular one or it
694 // is one that is known to be in both dictionaries.
695 if (nonessentialAttributes.contains(attrName) ||
696 seenAttrs.contains(attrName))
697 continue;
698 // We have found an attribute that is only in the second operation.
699 diag.attachNote(a->getLoc())
700 << "first operation is missing attribute " << attrName;
701 diag.attachNote(b->getLoc()) << "second operation here";
702 return failure();
703 }
704 }
705 return success();
706 }
707
708 // NOLINTNEXTLINE(misc-no-recursion)
709 LogicalResult check(InFlightDiagnostic &diag, igraph::InstanceOpInterface a,
710 igraph::InstanceOpInterface b) {
711 // Get the list of module names from the list (for InstanceOp/ObjectOp,
712 // there's only one)
713 auto aNames = a.getReferencedModuleNamesAttr();
714 auto bNames = b.getReferencedModuleNamesAttr();
715 if (aNames == bNames)
716 return success();
717
718 if (aNames.size() != bNames.size()) {
719 diag.attachNote(a->getLoc())
720 << "an instance has a different number of referenced "
721 "modules: first instance has "
722 << aNames.size() << " modules";
723 diag.attachNote(b->getLoc())
724 << "second instance has " << bNames.size() << " modules";
725 return failure();
726 }
727
728 for (auto [aName, bName] : llvm::zip(aNames.getAsRange<StringAttr>(),
729 bNames.getAsRange<StringAttr>())) {
730 if (aName == bName)
731 continue;
732 // If the modules instantiate are different we will want to know why the
733 // sub module did not dedupliate. This code recursively checks the child
734 // module.
735 auto aModule = instanceGraph.lookup(aName)->getModule();
736 auto bModule = instanceGraph.lookup(bName)->getModule();
737 // Create a new error for the submodule.
738 diag.attachNote(std::nullopt)
739 << "in instance " << a.getInstanceNameAttr() << " of " << aName
740 << ", and instance " << b.getInstanceNameAttr() << " of " << bName;
741 check(diag, aModule, bModule);
742 }
743 return failure();
744 }
745
746 // NOLINTNEXTLINE(misc-no-recursion)
747 LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a,
748 Operation *b) {
749 // Operation name.
750 if (a->getName() != b->getName()) {
751 diag.attachNote(a->getLoc()) << "first operation is a " << a->getName();
752 diag.attachNote(b->getLoc()) << "second operation is a " << b->getName();
753 return failure();
754 }
755
756 // If it's a firrtl operation that implements InstanceOpInterface
757 // (InstanceOp/InstanceChoiceOp/ObjectOp) perform some checking and possibly
758 // recurse.
759 if (auto aInst = dyn_cast<igraph::InstanceOpInterface>(a))
760 if (auto bInst = dyn_cast<igraph::InstanceOpInterface>(b))
761 if (isa_and_nonnull<firrtl::FIRRTLDialect>(a->getDialect()) &&
762 isa_and_nonnull<firrtl::FIRRTLDialect>(b->getDialect()) &&
763 failed(check(diag, aInst, bInst)))
764 return failure();
765
766 // Operation results.
767 if (a->getNumResults() != b->getNumResults()) {
768 diag.attachNote(a->getLoc())
769 << "operations have different number of results";
770 diag.attachNote(b->getLoc()) << "second operation here";
771 return failure();
772 }
773 for (auto resultPair : llvm::zip(a->getResults(), b->getResults())) {
774 auto &aValue = std::get<0>(resultPair);
775 auto &bValue = std::get<1>(resultPair);
776 if (failed(check(diag, "operation result", a, aValue.getType(), b,
777 bValue.getType())))
778 return failure();
779 data.map.map(aValue, bValue);
780 }
781
782 // Operations operands.
783 if (a->getNumOperands() != b->getNumOperands()) {
784 diag.attachNote(a->getLoc())
785 << "operations have different number of operands";
786 diag.attachNote(b->getLoc()) << "second operation here";
787 return failure();
788 }
789 for (auto operandPair : llvm::zip(a->getOperands(), b->getOperands())) {
790 auto &aValue = std::get<0>(operandPair);
791 auto &bValue = std::get<1>(operandPair);
792 if (bValue != data.map.lookup(aValue)) {
793 diag.attachNote(a->getLoc())
794 << "operations use different operands, first operand is '"
795 << getFieldName(
796 getFieldRefFromValue(aValue, /*lookThroughCasts=*/true))
797 .first
798 << "'";
799 diag.attachNote(b->getLoc())
800 << "second operand is '"
801 << getFieldName(
802 getFieldRefFromValue(bValue, /*lookThroughCasts=*/true))
803 .first
804 << "', but should have been '"
805 << getFieldName(getFieldRefFromValue(data.map.lookup(aValue),
806 /*lookThroughCasts=*/true))
807 .first
808 << "'";
809 return failure();
810 }
811 }
812 data.map.map(a, b);
813
814 // Operation regions.
815 if (a->getNumRegions() != b->getNumRegions()) {
816 diag.attachNote(a->getLoc())
817 << "operations have different number of regions";
818 diag.attachNote(b->getLoc()) << "second operation here";
819 return failure();
820 }
821 for (auto regionPair : llvm::zip(a->getRegions(), b->getRegions())) {
822 auto &aRegion = std::get<0>(regionPair);
823 auto &bRegion = std::get<1>(regionPair);
824 if (failed(check(diag, data, a, aRegion, b, bRegion)))
825 return failure();
826 }
827
828 // Operation attributes.
829 if (failed(check(diag, data, a, a->getAttrDictionary(), b,
830 b->getAttrDictionary())))
831 return failure();
832 return success();
833 }
834
835 // NOLINTNEXTLINE(misc-no-recursion)
836 void check(InFlightDiagnostic &diag, Operation *a, Operation *b) {
837 hw::InnerSymbolTable aTable(a);
838 hw::InnerSymbolTable bTable(b);
839 ModuleData data(aTable, bTable);
841 diag.attachNote(a->getLoc()) << "module marked NoDedup";
842 return;
843 }
845 diag.attachNote(b->getLoc()) << "module marked NoDedup";
846 return;
847 }
848 auto aSymbol = cast<mlir::SymbolOpInterface>(a);
849 auto bSymbol = cast<mlir::SymbolOpInterface>(b);
850 if (!canRemoveModule(aSymbol) && !canRemoveModule(bSymbol)) {
851 diag.attachNote(a->getLoc())
852 << "module is "
853 << (aSymbol.isPrivate() ? "private but not discardable" : "public");
854 diag.attachNote(b->getLoc())
855 << "module is "
856 << (bSymbol.isPrivate() ? "private but not discardable" : "public");
857 return;
858 }
859 auto aGroup =
860 dyn_cast_or_null<StringAttr>(a->getDiscardableAttr(dedupGroupAttrName));
861 auto bGroup = dyn_cast_or_null<StringAttr>(
862 b->getAttrOfType<StringAttr>(dedupGroupAttrName));
863 if (aGroup != bGroup) {
864 if (bGroup) {
865 diag.attachNote(b->getLoc())
866 << "module is in dedup group '" << bGroup.str() << "'";
867 } else {
868 diag.attachNote(b->getLoc()) << "module is not part of a dedup group";
869 }
870 if (aGroup) {
871 diag.attachNote(a->getLoc())
872 << "module is in dedup group '" << aGroup.str() << "'";
873 } else {
874 diag.attachNote(a->getLoc()) << "module is not part of a dedup group";
875 }
876 return;
877 }
878 if (failed(check(diag, data, a, b)))
879 return;
880 diag.attachNote(a->getLoc()) << "first module here";
881 diag.attachNote(b->getLoc()) << "second module here";
882 }
883
884 // This is a cached "portDirections" string attr.
886 // This is a cached "NoDedup" annotation class string attr.
887 StringAttr noDedupClass;
888 // This is a cached string attr for the dedup group attribute.
890
891 // This is a set of every attribute we should ignore.
892 DenseSet<Attribute> nonessentialAttributes;
894};
895
896//===----------------------------------------------------------------------===//
897// Deduplication
898//===----------------------------------------------------------------------===//
899
900// Custom location merging. This only keeps track of 8 annotations from ".fir"
901// files, and however many annotations come from "real" sources. When
902// deduplicating, modules tend not to have scala source locators, so we wind
903// up fusing source locators for a module from every copy being deduped. There
904// is little value in this (all the modules are identical by definition).
905static Location mergeLoc(MLIRContext *context, Location to, Location from) {
906 // Unique the set of locations to be fused.
908 // only track 8 "fir" locations
909 unsigned seenFIR = 0;
910 for (auto loc : {to, from}) {
911 // If the location is a fused location we decompose it if it has no
912 // metadata or the metadata is the same as the top level metadata.
913 if (auto fusedLoc = dyn_cast<FusedLoc>(loc)) {
914 // UnknownLoc's have already been removed from FusedLocs so we can
915 // simply add all of the internal locations.
916 for (auto loc : fusedLoc.getLocations()) {
917 if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
918 if (fileLoc.getFilename().strref().ends_with(".fir")) {
919 ++seenFIR;
920 if (seenFIR > 8)
921 continue;
922 }
923 }
924 decomposedLocs.insert(loc);
925 }
926 continue;
927 }
928
929 // Might need to skip this fir.
930 if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
931 if (fileLoc.getFilename().strref().ends_with(".fir")) {
932 ++seenFIR;
933 if (seenFIR > 8)
934 continue;
935 }
936 }
937 // Otherwise, only add known locations to the set.
938 if (!isa<UnknownLoc>(loc))
939 decomposedLocs.insert(loc);
940 }
941
942 auto locs = decomposedLocs.getArrayRef();
943
944 // Handle the simple cases of less than two locations. Ensure the metadata (if
945 // provided) is not dropped.
946 if (locs.empty())
947 return UnknownLoc::get(context);
948 if (locs.size() == 1)
949 return locs.front();
950
951 return FusedLoc::get(context, locs);
952}
953
954struct Deduper {
955
956 using RenameMap = DenseMap<StringAttr, StringAttr>;
957
959 NLATable *nlaTable, CircuitOp circuit)
960 : context(circuit->getContext()), instanceGraph(instanceGraph),
962 nlaBlock(circuit.getBodyBlock()),
963 nonLocalString(StringAttr::get(context, "circt.nonlocal")),
964 classString(StringAttr::get(context, "class")) {
965 // Populate the NLA cache.
966 for (auto nla : circuit.getOps<hw::HierPathOp>())
967 nlaCache[nla.getNamepathAttr()] = nla.getNameAttr();
968 }
969
970 /// Remove the "fromModule", and replace all references to it with the
971 /// "toModule". Modules should be deduplicated in a bottom-up order. Any
972 /// module which is not deduplicated needs to be recorded with the `record`
973 /// call.
974 void dedup(FModuleLike toModule, FModuleLike fromModule) {
975 // A map of operation (e.g. wires, nodes) names which are changed, which is
976 // used to update NLAs that reference the "fromModule".
977 RenameMap renameMap;
978
979 // Merge the port locations.
980 SmallVector<Attribute> newLocs;
981 for (auto [toLoc, fromLoc] : llvm::zip(toModule.getPortLocations(),
982 fromModule.getPortLocations())) {
983 if (toLoc == fromLoc)
984 newLocs.push_back(toLoc);
985 else
986 newLocs.push_back(mergeLoc(context, cast<LocationAttr>(toLoc),
987 cast<LocationAttr>(fromLoc)));
988 }
989 toModule->setAttr("portLocations", ArrayAttr::get(context, newLocs));
990
991 // Merge the two modules.
992 mergeOps(renameMap, toModule, toModule, fromModule, fromModule);
993
994 // Rewrite NLAs pathing through these modules to refer to the to module. It
995 // is safe to do this at this point because NLAs cannot be one element long.
996 // This means that all NLAs which require more context cannot be targetting
997 // something in the module it self.
998 if (auto to = dyn_cast<FModuleOp>(*toModule))
999 rewriteModuleNLAs(renameMap, to, cast<FModuleOp>(*fromModule));
1000 else
1001 rewriteExtModuleNLAs(renameMap, toModule.getModuleNameAttr(),
1002 fromModule.getModuleNameAttr());
1003
1004 replaceInstances(toModule, fromModule);
1005 }
1006
1007 /// Record the usages of any NLA's in this module, so that we may update the
1008 /// annotation if the parent module is deduped with another module.
1009 void record(FModuleLike module) {
1010 // Record any annotations on the module.
1011 recordAnnotations(module);
1012 // Record port annotations.
1013 for (unsigned i = 0, e = getNumPorts(module); i < e; ++i)
1015 // Record any annotations in the module body.
1016 module->walk([&](Operation *op) { recordAnnotations(op); });
1017 }
1018
1019private:
1020 /// Get a cached namespace for a module.
1022 return moduleNamespaces.try_emplace(module, cast<FModuleLike>(module))
1023 .first->second;
1024 }
1025
1026 /// For a specific annotation target, record all the unique NLAs which
1027 /// target it in the `targetMap`.
1029 for (auto anno : target.getAnnotations())
1030 if (auto nlaRef = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal"))
1031 targetMap[nlaRef.getAttr()].insert(target);
1032 }
1033
1034 /// Record all targets which use an NLA.
1035 void recordAnnotations(Operation *op) {
1036 // Record annotations.
1038
1039 // Record port annotations only if this is a mem operation.
1040 auto mem = dyn_cast<MemOp>(op);
1041 if (!mem)
1042 return;
1043
1044 // Record port annotations.
1045 for (unsigned i = 0, e = mem->getNumResults(); i < e; ++i)
1047 }
1048
1049 /// This deletes and replaces all instances of the "fromModule" with instances
1050 /// of the "toModule".
1051 void replaceInstances(FModuleLike toModule, Operation *fromModule) {
1052 // Replace all instances of the other module.
1053 auto *fromNode =
1054 instanceGraph[::cast<igraph::ModuleOpInterface>(fromModule)];
1055 auto *toNode = instanceGraph[toModule];
1056 auto toModuleRef = FlatSymbolRefAttr::get(toModule.getModuleNameAttr());
1057 for (auto *oldInstRec : llvm::make_early_inc_range(fromNode->uses())) {
1058 auto inst = oldInstRec->getInstance();
1059 if (auto instOp = dyn_cast<InstanceOp>(*inst)) {
1060 instOp.setModuleNameAttr(toModuleRef);
1061 instOp.setPortNamesAttr(toModule.getPortNamesAttr());
1062 } else if (auto instanceChoiceOp = dyn_cast<InstanceChoiceOp>(*inst)) {
1063 auto fromModuleName = fromNode->getModule().getModuleNameAttr();
1064 SmallVector<Attribute> newModules;
1065 for (auto module : instanceChoiceOp.getReferencedModuleNamesAttr()) {
1066 auto moduleName = cast<StringAttr>(module);
1067 if (moduleName == fromModuleName)
1068 newModules.push_back(toModuleRef);
1069 else
1070 newModules.push_back(FlatSymbolRefAttr::get(moduleName));
1071 }
1072 instanceChoiceOp.setModuleNamesAttr(
1073 ArrayAttr::get(context, newModules));
1074 instanceChoiceOp.setPortNamesAttr(toModule.getPortNamesAttr());
1075 }
1076 oldInstRec->getParent()->addInstance(inst, toNode);
1077 oldInstRec->erase();
1078 }
1079 instanceGraph.erase(fromNode);
1080 fromModule->erase();
1081 }
1082
1083 /// Look up the instantiations of the `from` module and create an NLA for each
1084 /// one, appending the baseNamepath to each NLA. This is used to add more
1085 /// context to an already existing NLA. The `fromModule` is used to indicate
1086 /// which module the annotation is coming from before the merge, and will be
1087 /// used to create the namepaths.
1088 SmallVector<FlatSymbolRefAttr>
1089 createNLAs(Operation *fromModule, ArrayRef<Attribute> baseNamepath,
1090 SymbolTable::Visibility vis = SymbolTable::Visibility::Private) {
1091 // Create an attribute array with a placeholder in the first element, where
1092 // the root refence of the NLA will be inserted.
1093 SmallVector<Attribute> namepath = {nullptr};
1094 namepath.append(baseNamepath.begin(), baseNamepath.end());
1095
1096 auto loc = fromModule->getLoc();
1097 auto *fromNode = instanceGraph[cast<igraph::ModuleOpInterface>(fromModule)];
1098 SmallVector<FlatSymbolRefAttr> nlas;
1099 for (auto *instanceRecord : fromNode->uses()) {
1100 auto parent = cast<FModuleOp>(*instanceRecord->getParent()->getModule());
1101 auto inst = instanceRecord->getInstance();
1102 namepath[0] = OpAnnoTarget(inst).getNLAReference(getNamespace(parent));
1103 auto arrayAttr = ArrayAttr::get(context, namepath);
1104 // Check the NLA cache to see if we already have this NLA.
1105 auto &cacheEntry = nlaCache[arrayAttr];
1106 if (!cacheEntry) {
1107 auto builder = OpBuilder::atBlockBegin(nlaBlock);
1108 auto nla = hw::HierPathOp::create(builder, loc, "nla",
1109 /*sym_visibility=*/{}, arrayAttr);
1110 // Insert it into the symbol table to get a unique name.
1111 symbolTable.insert(nla);
1112 // Store it in the cache.
1113 cacheEntry = nla.getNameAttr();
1114 nla.setVisibility(vis);
1115 nlaTable->addNLA(nla);
1116 }
1117 auto nlaRef = FlatSymbolRefAttr::get(cast<StringAttr>(cacheEntry));
1118 nlas.push_back(nlaRef);
1119 }
1120 return nlas;
1121 }
1122
1123 /// Look up the instantiations of this module and create an NLA for each one.
1124 /// This returns an array of symbol references which can be used to reference
1125 /// the NLAs.
1126 SmallVector<FlatSymbolRefAttr>
1127 createNLAs(StringAttr toModuleName, FModuleLike fromModule,
1128 SymbolTable::Visibility vis = SymbolTable::Visibility::Private) {
1129 return createNLAs(fromModule, FlatSymbolRefAttr::get(toModuleName), vis);
1130 }
1131
1132 /// Clone the annotation for each NLA in a list. The attribute list should
1133 /// have a placeholder for the "circt.nonlocal" field, and `nonLocalIndex`
1134 /// should be the index of this field.
1135 void cloneAnnotation(SmallVectorImpl<FlatSymbolRefAttr> &nlas,
1136 Annotation anno, ArrayRef<NamedAttribute> attributes,
1137 unsigned nonLocalIndex,
1138 SmallVectorImpl<Annotation> &newAnnotations) {
1139 SmallVector<NamedAttribute> mutableAttributes(attributes.begin(),
1140 attributes.end());
1141 for (auto &nla : nlas) {
1142 // Add the new annotation.
1143 mutableAttributes[nonLocalIndex].setValue(nla);
1144 auto dict = DictionaryAttr::getWithSorted(context, mutableAttributes);
1145 // The original annotation records if its a subannotation.
1146 anno.setDict(dict);
1147 newAnnotations.push_back(anno);
1148 }
1149 }
1150
1151 /// This erases the NLA op, and removes the NLA from every module's NLA map,
1152 /// but it does not delete the NLA reference from the target operation's
1153 /// annotations.
1154 void eraseNLA(hw::HierPathOp nla) {
1155 // Erase the NLA from the leaf module's nlaMap.
1156 targetMap.erase(nla.getNameAttr());
1157 nlaTable->erase(nla);
1158 nlaCache.erase(nla.getNamepathAttr());
1159 symbolTable.erase(nla);
1160 }
1161
1162 /// Process all NLAs referencing the "from" module to point to the "to"
1163 /// module. This is used after merging two modules together.
1164 void addAnnotationContext(RenameMap &renameMap, FModuleOp toModule,
1165 FModuleOp fromModule) {
1166 auto toName = toModule.getNameAttr();
1167 auto fromName = fromModule.getNameAttr();
1168 // Create a copy of the current NLAs. We will be pushing and removing
1169 // NLAs from this op as we go.
1170 auto moduleNLAs = nlaTable->lookup(fromModule.getNameAttr()).vec();
1171 // Change the NLA to target the toModule.
1172 nlaTable->renameModuleAndInnerRef(toName, fromName, renameMap);
1173
1174 // Fast path: if `fromModule` has exactly one instantiation, prepend that
1175 // single instance reference to the namepath in place, keeping the NLA's
1176 // symbol name so all annotations and targetMap entries stay valid.
1177 auto *fromNode = instanceGraph.lookup(fromName);
1178 std::optional<std::pair<Attribute, StringAttr>> singlePrefixAndName;
1179 // Now we walk the NLA searching for ones that require more context to be
1180 // added.
1181 for (auto nla : moduleNLAs) {
1182 auto elements = nla.getNamepath().getValue();
1183 // If we don't need to add more context, we're done here.
1184 if (nla.root() != toName)
1185 continue;
1186
1187 // Fast path: See the comment above.
1188 if (fromNode->hasOneUse()) {
1189 if (!singlePrefixAndName) {
1190 auto *instanceRecord = *fromNode->uses().begin();
1191 auto parent =
1192 cast<FModuleOp>(*instanceRecord->getParent()->getModule());
1193 singlePrefixAndName = {OpAnnoTarget(instanceRecord->getInstance())
1194 .getNLAReference(getNamespace(parent)),
1195 parent.getNameAttr()};
1196 }
1197 SmallVector<Attribute> newNamepath;
1198 newNamepath.reserve(elements.size() + 1);
1199 auto [singlePrefix, singleParentName] = *singlePrefixAndName;
1200 newNamepath.push_back(singlePrefix);
1201 newNamepath.append(elements.begin(), elements.end());
1202 auto newPath = ArrayAttr::get(context, newNamepath);
1203 // Skip if this path already exists; fall back to the general path
1204 // below which merges duplicates via the cache.
1205 if (!nlaCache.count(newPath)) {
1206 nlaCache.erase(nla.getNamepathAttr());
1207 nla.setNamepathAttr(newPath);
1208 nlaCache[newPath] = nla.getNameAttr();
1209 nlaTable->addNLAtoModule(nla, singleParentName);
1210 continue;
1211 }
1212 }
1213
1214 // Create the replacement NLAs.
1215 SmallVector<Attribute> namepath(elements.begin(), elements.end());
1216 auto nlaRefs = createNLAs(fromModule, namepath, nla.getVisibility());
1217 // Copy out the targets, because we will be updating the map.
1218 auto &set = targetMap[nla.getNameAttr()];
1219 SmallVector<AnnoTarget> targets(set.begin(), set.end());
1220 // Replace the uses of the old NLA with the new NLAs.
1221 for (auto target : targets) {
1222 // We have to clone any annotation which uses the old NLA for each new
1223 // NLA. This array collects the new set of annotations.
1224 SmallVector<Annotation> newAnnotations;
1225 for (auto anno : target.getAnnotations()) {
1226 // Find the non-local field of the annotation.
1227 auto [it, found] = mlir::impl::findAttrSorted(
1228 anno.begin(), anno.end(), nonLocalString);
1229 // If this annotation doesn't use the target NLA, copy it with no
1230 // changes.
1231 if (!found || cast<FlatSymbolRefAttr>(it->getValue()).getAttr() !=
1232 nla.getNameAttr()) {
1233 newAnnotations.push_back(anno);
1234 continue;
1235 }
1236 auto nonLocalIndex = std::distance(anno.begin(), it);
1237 // Clone the annotation and add it to the list of new annotations.
1238 cloneAnnotation(nlaRefs, anno,
1239 ArrayRef<NamedAttribute>(anno.begin(), anno.end()),
1240 nonLocalIndex, newAnnotations);
1241 }
1242
1243 // Apply the new annotations to the operation.
1244 AnnotationSet annotations(newAnnotations, context);
1245 target.setAnnotations(annotations);
1246 // Record that target uses the NLA.
1247 for (auto nla : nlaRefs)
1248 targetMap[nla.getAttr()].insert(target);
1249 }
1250
1251 // Erase the old NLA and remove it from all breadcrumbs.
1252 eraseNLA(nla);
1253 }
1254 }
1255
1256 /// Process all the NLAs that the two modules participate in, replacing
1257 /// references to the "from" module with references to the "to" module, and
1258 /// adding more context if necessary.
1259 void rewriteModuleNLAs(RenameMap &renameMap, FModuleOp toModule,
1260 FModuleOp fromModule) {
1261 addAnnotationContext(renameMap, toModule, toModule);
1262 addAnnotationContext(renameMap, toModule, fromModule);
1263 }
1264
1265 // Update all NLAs which the "from" external module participates in to the
1266 // "toName".
1267 void rewriteExtModuleNLAs(RenameMap &renameMap, StringAttr toName,
1268 StringAttr fromName) {
1269 nlaTable->renameModuleAndInnerRef(toName, fromName, renameMap);
1270 }
1271
1272 /// Take an annotation, and update it to be a non-local annotation. If the
1273 /// annotation is already non-local and has enough context, it will be skipped
1274 /// for now. Return true if the annotation was made non-local.
1275 bool makeAnnotationNonLocal(StringAttr toModuleName, AnnoTarget to,
1276 FModuleLike fromModule, Annotation anno,
1277 SmallVectorImpl<Annotation> &newAnnotations) {
1278 // Start constructing a new annotation, pushing a "circt.nonLocal" field
1279 // into the correct spot if its not already a non-local annotation.
1280 SmallVector<NamedAttribute> attributes;
1281 int nonLocalIndex = -1;
1282 for (const auto &val : llvm::enumerate(anno)) {
1283 auto attr = val.value();
1284 // Is this field "circt.nonlocal"?
1285 auto compare = attr.getName().compare(nonLocalString);
1286 assert(compare != 0 && "should not pass non-local annotations here");
1287 if (compare == 1) {
1288 // This annotation definitely does not have "circt.nonlocal" field. Push
1289 // an empty place holder for the non-local annotation.
1290 nonLocalIndex = val.index();
1291 attributes.push_back(NamedAttribute(nonLocalString, nonLocalString));
1292 break;
1293 }
1294 // Otherwise push the current attribute and keep searching for the
1295 // "circt.nonlocal" field.
1296 attributes.push_back(attr);
1297 }
1298 if (nonLocalIndex == -1) {
1299 // Push an empty "circt.nonlocal" field to the last slot.
1300 nonLocalIndex = attributes.size();
1301 attributes.push_back(NamedAttribute(nonLocalString, nonLocalString));
1302 } else {
1303 // Copy the remaining annotation fields in.
1304 attributes.append(anno.begin() + nonLocalIndex, anno.end());
1305 }
1306
1307 // Construct the NLAs if we don't have any yet.
1308 auto nlaRefs = createNLAs(toModuleName, fromModule);
1309 for (auto nla : nlaRefs)
1310 targetMap[nla.getAttr()].insert(to);
1311
1312 // Clone the annotation for each new NLA.
1313 cloneAnnotation(nlaRefs, anno, attributes, nonLocalIndex, newAnnotations);
1314 return true;
1315 }
1316
1317 void copyAnnotations(FModuleLike toModule, AnnoTarget to,
1318 FModuleLike fromModule, AnnotationSet annos,
1319 SmallVectorImpl<Annotation> &newAnnotations,
1320 SmallPtrSetImpl<Attribute> &dontTouches) {
1321 for (auto anno : annos) {
1322 if (anno.isClass(dontTouchAnnoClass)) {
1323 // Remove the nonlocal field of the annotation if it has one, since this
1324 // is a sticky annotation.
1325 anno.removeMember("circt.nonlocal");
1326 auto [it, inserted] = dontTouches.insert(anno.getAttr());
1327 if (inserted)
1328 newAnnotations.push_back(anno);
1329 continue;
1330 }
1331 // If the annotation is already non-local, we add it as is. It is already
1332 // added to the target map.
1333 if (auto nla = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
1334 newAnnotations.push_back(anno);
1335 targetMap[nla.getAttr()].insert(to);
1336 continue;
1337 }
1338 // Otherwise make the annotation non-local and add it to the set.
1339 makeAnnotationNonLocal(toModule.getModuleNameAttr(), to, fromModule, anno,
1340 newAnnotations);
1341 }
1342 }
1343
1344 /// Merge the annotations of a specific target, either a operation or a port
1345 /// on an operation.
1346 void mergeAnnotations(FModuleLike toModule, AnnoTarget to,
1347 AnnotationSet toAnnos, FModuleLike fromModule,
1348 AnnoTarget from, AnnotationSet fromAnnos) {
1349 // This is a list of all the annotations which will be added to `to`.
1350 SmallVector<Annotation> newAnnotations;
1351
1352 // We have special case handling of DontTouch to prevent it from being
1353 // turned into a non-local annotation, and to remove duplicates.
1354 llvm::SmallPtrSet<Attribute, 4> dontTouches;
1355
1356 // Iterate the annotations, transforming most annotations into non-local
1357 // ones.
1358 copyAnnotations(toModule, to, toModule, toAnnos, newAnnotations,
1359 dontTouches);
1360 copyAnnotations(toModule, to, fromModule, fromAnnos, newAnnotations,
1361 dontTouches);
1362
1363 // Copy over all the new annotations.
1364 if (!newAnnotations.empty())
1365 to.setAnnotations(AnnotationSet(newAnnotations, context));
1366 }
1367
1368 /// Merge all annotations and port annotations on two operations.
1369 void mergeAnnotations(FModuleLike toModule, Operation *to,
1370 FModuleLike fromModule, Operation *from) {
1371 // Merge op annotations.
1372 mergeAnnotations(toModule, OpAnnoTarget(to), AnnotationSet(to), fromModule,
1373 OpAnnoTarget(from), AnnotationSet(from));
1374
1375 // Merge port annotations.
1376 if (toModule == to) {
1377 // Merge module port annotations.
1378 for (unsigned i = 0, e = getNumPorts(toModule); i < e; ++i)
1379 mergeAnnotations(toModule, PortAnnoTarget(toModule, i),
1380 AnnotationSet::forPort(toModule, i), fromModule,
1381 PortAnnoTarget(fromModule, i),
1382 AnnotationSet::forPort(fromModule, i));
1383 } else if (auto toMem = dyn_cast<MemOp>(to)) {
1384 // Merge memory port annotations.
1385 auto fromMem = cast<MemOp>(from);
1386 for (unsigned i = 0, e = toMem.getNumResults(); i < e; ++i)
1387 mergeAnnotations(toModule, PortAnnoTarget(toMem, i),
1388 AnnotationSet::forPort(toMem, i), fromModule,
1389 PortAnnoTarget(fromMem, i),
1390 AnnotationSet::forPort(fromMem, i));
1391 }
1392 }
1393
1394 hw::InnerSymAttr mergeInnerSymbols(RenameMap &renameMap, FModuleLike toModule,
1395 hw::InnerSymAttr toSym,
1396 hw::InnerSymAttr fromSym) {
1397 if (fromSym && !fromSym.getProps().empty()) {
1398 auto &isn = getNamespace(toModule);
1399 // The properties for the new inner symbol..
1400 SmallVector<hw::InnerSymPropertiesAttr> newProps;
1401 // If the "to" op already has an inner symbol, copy all its properties.
1402 if (toSym)
1403 llvm::append_range(newProps, toSym);
1404 // Add each property from the fromSym to the toSym.
1405 for (auto fromProp : fromSym) {
1406 hw::InnerSymPropertiesAttr newProp;
1407 auto *it = llvm::find_if(newProps, [&](auto p) {
1408 return p.getFieldID() == fromProp.getFieldID();
1409 });
1410 if (it != newProps.end()) {
1411 // If we already have an inner sym with the same field id, use
1412 // that.
1413 newProp = *it;
1414 // If the old symbol is public, we need to make the new one public.
1415 if (fromProp.getSymVisibility().getValue() == "public" &&
1416 newProp.getSymVisibility().getValue() != "public") {
1417 *it = hw::InnerSymPropertiesAttr::get(context, newProp.getName(),
1418 newProp.getFieldID(),
1419 fromProp.getSymVisibility());
1420 }
1421 } else {
1422 // We need to add a new property to the inner symbol for this field.
1423 auto newName = isn.newName(fromProp.getName().getValue());
1424 newProp = hw::InnerSymPropertiesAttr::get(
1425 context, StringAttr::get(context, newName), fromProp.getFieldID(),
1426 fromProp.getSymVisibility());
1427 newProps.push_back(newProp);
1428 }
1429 renameMap[fromProp.getName()] = newProp.getName();
1430 }
1431 // Sort the fields by field id.
1432 llvm::sort(newProps, [](auto &p, auto &q) {
1433 return p.getFieldID() < q.getFieldID();
1434 });
1435 // Return the merged inner symbol.
1436 return hw::InnerSymAttr::get(context, newProps);
1437 }
1438 return hw::InnerSymAttr();
1439 }
1440
1441 // Record the symbol name change of the operation or any of its ports when
1442 // merging two operations. The renamed symbols are used to update the
1443 // target of any NLAs. This will add symbols to the "to" operation if needed.
1444 void recordSymRenames(RenameMap &renameMap, FModuleLike toModule,
1445 Operation *to, FModuleLike fromModule,
1446 Operation *from) {
1447 // If the "from" operation has an inner_sym, we need to make sure the
1448 // "to" operation also has an `inner_sym` and then record the renaming.
1449 if (auto fromInnerSym = dyn_cast<hw::InnerSymbolOpInterface>(from)) {
1450 auto toInnerSym = cast<hw::InnerSymbolOpInterface>(to);
1451 if (auto newSymAttr = mergeInnerSymbols(renameMap, toModule,
1452 toInnerSym.getInnerSymAttr(),
1453 fromInnerSym.getInnerSymAttr()))
1454 toInnerSym.setInnerSymbolAttr(newSymAttr);
1455 }
1456
1457 // If there are no port symbols on the "from" operation, we are done here.
1458 auto fromPortSyms = from->getAttrOfType<ArrayAttr>("portSymbols");
1459 if (!fromPortSyms || fromPortSyms.empty())
1460 return;
1461 // We have to map each "fromPort" to each "toPort".
1462 auto portCount = fromPortSyms.size();
1463 auto toPortSyms = to->getAttrOfType<ArrayAttr>("portSymbols");
1464
1465 // Create an array of new port symbols for the "to" operation, copy in the
1466 // old symbols if it has any, create an empty symbol array if it doesn't.
1467 SmallVector<Attribute> newPortSyms;
1468 if (toPortSyms.empty())
1469 newPortSyms.assign(portCount, hw::InnerSymAttr());
1470 else
1471 newPortSyms.assign(toPortSyms.begin(), toPortSyms.end());
1472
1473 for (unsigned portNo = 0; portNo < portCount; ++portNo) {
1474 if (auto newPortSym = mergeInnerSymbols(
1475 renameMap, toModule,
1476 llvm::cast_if_present<hw::InnerSymAttr>(newPortSyms[portNo]),
1477 cast<hw::InnerSymAttr>(fromPortSyms[portNo]))) {
1478 newPortSyms[portNo] = newPortSym;
1479 }
1480 }
1481
1482 // Commit the new symbol attribute.
1483 FModuleLike::fixupPortSymsArray(newPortSyms, toModule.getContext());
1484 cast<FModuleLike>(to).setPortSymbols(newPortSyms);
1485 }
1486
1487 /// Recursively merge two operations.
1488 // NOLINTNEXTLINE(misc-no-recursion)
1489 void mergeOps(RenameMap &renameMap, FModuleLike toModule, Operation *to,
1490 FModuleLike fromModule, Operation *from) {
1491 // Merge the operation locations.
1492 if (to->getLoc() != from->getLoc())
1493 to->setLoc(mergeLoc(context, to->getLoc(), from->getLoc()));
1494
1495 // Recurse into any regions.
1496 for (auto regions : llvm::zip(to->getRegions(), from->getRegions()))
1497 mergeRegions(renameMap, toModule, std::get<0>(regions), fromModule,
1498 std::get<1>(regions));
1499
1500 // Record any inner_sym renamings that happened.
1501 recordSymRenames(renameMap, toModule, to, fromModule, from);
1502
1503 // Merge the annotations.
1504 mergeAnnotations(toModule, to, fromModule, from);
1505 }
1506
1507 /// Recursively merge two blocks.
1508 void mergeBlocks(RenameMap &renameMap, FModuleLike toModule, Block &toBlock,
1509 FModuleLike fromModule, Block &fromBlock) {
1510 // Merge the block locations.
1511 for (auto [toArg, fromArg] :
1512 llvm::zip(toBlock.getArguments(), fromBlock.getArguments()))
1513 if (toArg.getLoc() != fromArg.getLoc())
1514 toArg.setLoc(mergeLoc(context, toArg.getLoc(), fromArg.getLoc()));
1515
1516 for (auto ops : llvm::zip(toBlock, fromBlock))
1517 mergeOps(renameMap, toModule, &std::get<0>(ops), fromModule,
1518 &std::get<1>(ops));
1519 }
1520
1521 // Recursively merge two regions.
1522 void mergeRegions(RenameMap &renameMap, FModuleLike toModule,
1523 Region &toRegion, FModuleLike fromModule,
1524 Region &fromRegion) {
1525 for (auto blocks : llvm::zip(toRegion, fromRegion))
1526 mergeBlocks(renameMap, toModule, std::get<0>(blocks), fromModule,
1527 std::get<1>(blocks));
1528 }
1529
1530 MLIRContext *context;
1532 SymbolTable &symbolTable;
1533
1534 /// Cached nla table analysis.
1535 NLATable *nlaTable = nullptr;
1536
1537 /// We insert all NLAs to the beginning of this block.
1538 Block *nlaBlock;
1539
1540 // This maps an NLA to the operations and ports that uses it.
1541 DenseMap<Attribute, llvm::SmallDenseSet<AnnoTarget>> targetMap;
1542
1543 // This is a cache to avoid creating duplicate NLAs. This maps the ArrayAtr
1544 // of the NLA's path to the name of the NLA which contains it.
1545 DenseMap<Attribute, Attribute> nlaCache;
1546
1547 // Cached attributes for faster comparisons and attribute building.
1548 StringAttr nonLocalString;
1549 StringAttr classString;
1550
1551 /// A module namespace cache.
1552 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
1553};
1554
1555//===----------------------------------------------------------------------===//
1556// Fixup
1557//===----------------------------------------------------------------------===//
1558
1559/// This fixes up connects when the field names of a bundle type changes. It
1560/// finds all fields which were previously bulk connected and legalizes it
1561/// into a connect for each field.
1562static void fixupConnect(ImplicitLocOpBuilder &builder, Value dst, Value src) {
1563 // If the types already match we can emit a connect.
1564 auto dstType = dst.getType();
1565 auto srcType = src.getType();
1566 if (dstType == srcType) {
1567 emitConnect(builder, dst, src);
1568 return;
1569 }
1570 // It must be a bundle type and the field name has changed. We have to
1571 // manually decompose the bulk connect into a connect for each field.
1572 auto dstBundle = type_cast<BundleType>(dstType);
1573 auto srcBundle = type_cast<BundleType>(srcType);
1574 for (unsigned i = 0; i < dstBundle.getNumElements(); ++i) {
1575 auto dstField = SubfieldOp::create(builder, dst, i);
1576 auto srcField = SubfieldOp::create(builder, src, i);
1577 if (dstBundle.getElement(i).isFlip) {
1578 std::swap(srcBundle, dstBundle);
1579 std::swap(srcField, dstField);
1580 }
1581 fixupConnect(builder, dstField, srcField);
1582 }
1583}
1584
1585/// Adjust instance result types to match the deduplicated module's port types.
1586/// Bundle field names may have changed, so preserve the old types for users by
1587/// going through intermediate wires. Delay this until after deduplication to
1588/// avoid introducing differences between otherwise equivalent parent modules.
1589static void fixupInstanceTypes(InstanceGraph &instanceGraph) {
1590 for (auto *node : instanceGraph) {
1591 auto module = node->getModule<FModuleLike>();
1592 for (auto *use : node->uses()) {
1593 auto instOp = dyn_cast<InstanceOp>(*use->getInstance());
1594 if (!instOp)
1595 continue;
1596 ImplicitLocOpBuilder builder(instOp.getLoc(), instOp->getContext());
1597 builder.setInsertionPointAfter(instOp);
1598 for (auto [index, result] : llvm::enumerate(instOp.getResults())) {
1599 auto newType = module.getPortType(index);
1600 auto oldType = result.getType();
1601 // If the type has not changed, we don't have to fix up anything.
1602 if (newType == oldType)
1603 continue;
1604 LLVM_DEBUG(llvm::dbgs()
1605 << "- Updating instance port \"" << instOp.getInstanceName()
1606 << "." << instOp.getPortName(index) << "\" from " << oldType
1607 << " to " << newType << "\n");
1608
1609 // If the type changed we transform it back to the old type with an
1610 // intermediate wire.
1611 auto wire = WireOp::create(builder, oldType, instOp.getPortName(index))
1612 .getResult();
1613 result.replaceAllUsesWith(wire);
1614 result.setType(newType);
1615 if (instOp.getPortDirection(index) == Direction::Out)
1616 fixupConnect(builder, wire, result);
1617 else
1618 fixupConnect(builder, result, wire);
1619 }
1620 }
1621 }
1622}
1623
1624//===----------------------------------------------------------------------===//
1625// DedupPass
1626//===----------------------------------------------------------------------===//
1627
1628namespace {
1629class DedupPass : public circt::firrtl::impl::DedupBase<DedupPass> {
1630 using DedupBase::DedupBase;
1631
1632 void runOnOperation() override {
1633 auto *context = &getContext();
1634 auto circuit = getOperation();
1635 auto &instanceGraph = getAnalysis<InstanceGraph>();
1636 auto *nlaTable = &getAnalysis<NLATable>();
1637 auto &symbolTable = getAnalysis<SymbolTable>();
1638 Deduper deduper(instanceGraph, symbolTable, nlaTable, circuit);
1639 Equivalence equiv(context, instanceGraph);
1640 auto anythingChanged = false;
1641 LLVM_DEBUG({
1642 llvm::dbgs() << "\n";
1643 debugHeader(Twine("Dedup circuit \"") + circuit.getName() + "\"")
1644 << "\n\n";
1645 });
1646
1647 // Modules annotated with this should not be considered for deduplication.
1648 auto noDedupClass = StringAttr::get(context, noDedupAnnoClass);
1649
1650 // Only modules within the same group may be deduplicated.
1651 auto dedupGroupClass = StringAttr::get(context, dedupGroupAnnoClass);
1652
1653 // A map of all the module moduleInfo that we have calculated so far.
1654 DenseMap<ModuleInfoRef, Operation *> moduleInfoToModule;
1655
1656 // We track the name of the module that each module is deduped into, so that
1657 // we can make sure all modules which are marked "must dedup" with each
1658 // other were all deduped to the same module.
1659 DenseMap<Attribute, StringAttr> dedupMap;
1660
1661 // We must iterate the modules from the bottom up so that we can properly
1662 // deduplicate the modules. We copy the list of modules into a vector first
1663 // to avoid iterator invalidation while we mutate the instance graph.
1664 SmallVector<FModuleLike, 0> modules;
1665 instanceGraph.walkPostOrder([&](auto &node) {
1666 if (auto mod = dyn_cast<FModuleLike>(*node.getModule()))
1667 modules.push_back(mod);
1668 });
1669 LLVM_DEBUG(llvm::dbgs() << "Found " << modules.size() << " modules\n");
1670
1671 SmallVector<std::optional<ModuleInfo>> moduleInfos(modules.size());
1672 StructuralHasherSharedConstants hasherConstants(&getContext());
1673
1674 // Attribute name used to store dedup_group for this pass.
1675 auto dedupGroupAttrName = StringAttr::get(context, "firrtl.dedup_group");
1676
1677 // Move dedup group annotations to attributes on the module.
1678 // This results in the desired behavior (included in hash),
1679 // and avoids unnecessary processing of these as annotations
1680 // that need to be tracked, made non-local, so on.
1681 for (auto module : modules) {
1682 llvm::SmallSetVector<StringAttr, 1> groups;
1684 module, [&groups, dedupGroupClass](Annotation annotation) {
1685 if (annotation.getClassAttr() != dedupGroupClass)
1686 return false;
1687 groups.insert(annotation.getMember<StringAttr>("group"));
1688 return true;
1689 });
1690 if (groups.size() > 1) {
1691 module.emitError("module belongs to multiple dedup groups: ") << groups;
1692 return signalPassFailure();
1693 }
1694 assert(!module->hasAttr(dedupGroupAttrName) &&
1695 "unexpected existing use of temporary dedup group attribute");
1696 if (!groups.empty())
1697 module->setDiscardableAttr(dedupGroupAttrName, groups.front());
1698 }
1699
1700 // Calculate module information parallelly.
1701 LLVM_DEBUG(llvm::dbgs() << "Computing module information\n");
1702 auto result = mlir::failableParallelForEach(
1703 context, llvm::seq(modules.size()), [&](unsigned idx) {
1704 auto module = modules[idx];
1705 // If the module is marked with NoDedup, just skip it.
1706 if (AnnotationSet::hasAnnotation(module, noDedupClass))
1707 return success();
1708
1709 // Only dedup extmodule's with defname.
1710 if (auto ext = dyn_cast<FExtModuleOp>(*module);
1711 ext && !ext.getDefname().has_value())
1712 return success();
1713
1714 // Classes are nominally typed and must never be deduplicated.
1715 if (isa<ClassOp>(*module))
1716 return success();
1717
1718 StructuralHasher hasher(hasherConstants);
1719 // Calculate the hash of the module and referred module names.
1720 moduleInfos[idx] = hasher.getModuleInfo(module);
1721 return success();
1722 });
1723
1724 // Dump out the module hashes for debugging.
1725 LLVM_DEBUG({
1726 auto &os = llvm::dbgs();
1727 for (auto [module, info] : llvm::zip(modules, moduleInfos)) {
1728 os << "- Hash ";
1729 if (info) {
1730 os << llvm::format_bytes(info->structuralHash, std::nullopt, 32, 32);
1731 } else {
1732 os << "--------------------------------";
1733 os << "--------------------------------";
1734 }
1735 os << " for " << module.getModuleNameAttr() << "\n";
1736 }
1737 });
1738
1739 if (result.failed())
1740 return signalPassFailure();
1741
1742 LLVM_DEBUG(llvm::dbgs() << "Update modules\n");
1743 for (auto [i, module] : llvm::enumerate(modules)) {
1744 auto moduleName = module.getModuleNameAttr();
1745 auto &maybeModuleInfo = moduleInfos[i];
1746 // If the hash was not calculated, we need to skip it.
1747 if (!maybeModuleInfo) {
1748 // We record it in the dedup map to help detect errors when the user
1749 // marks the module as both NoDedup and MustDedup. We do not record this
1750 // module in the hasher to make sure no other module dedups "into" this
1751 // one.
1752 dedupMap[moduleName] = moduleName;
1753 continue;
1754 }
1755
1756 auto &moduleInfo = maybeModuleInfo.value();
1757
1758 // Replace module names referred in the module with new names.
1759 for (auto &referredModule : moduleInfo.referredModuleNames)
1760 referredModule = dedupMap[referredModule];
1761
1762 // Check if there is a module with the same hash.
1763 auto it = moduleInfoToModule.find(&moduleInfo);
1764 if (it != moduleInfoToModule.end()) {
1765 auto original = cast<FModuleLike>(it->second);
1766 auto originalName = original.getModuleNameAttr();
1767
1768 // If the current module is public, and the original is private, we
1769 // want to dedup the private module into the public one.
1770 if (!canRemoveModule(
1771 cast<mlir::SymbolOpInterface>(module.getOperation()))) {
1772 // Record that this module's name is staying the same.
1773 dedupMap[moduleName] = moduleName;
1774 // If both modules are public, then we can't dedup anything.
1775 if (!canRemoveModule(
1776 cast<mlir::SymbolOpInterface>(original.getOperation())))
1777 continue;
1778 // Swap the canonical module in the dedup map.
1779 for (auto &[_, dedupedName] : dedupMap)
1780 if (dedupedName == originalName)
1781 dedupedName = moduleName;
1782 // Update the module hash table to point to the new original, so all
1783 // future modules dedup with the new canonical module.
1784 it->second = module;
1785 // Swap the locals.
1786 std::swap(originalName, moduleName);
1787 std::swap(original, module);
1788 }
1789
1790 // Record the group ID of the other module.
1791 LLVM_DEBUG(llvm::dbgs() << "- Replace " << moduleName << " with "
1792 << originalName << "\n");
1793 dedupMap[moduleName] = originalName;
1794 deduper.dedup(original, module);
1795 ++erasedModules;
1796 anythingChanged = true;
1797 continue;
1798 }
1799 // Any module not deduplicated must be recorded.
1800 deduper.record(module);
1801 // Add the module to a new dedup group.
1802 dedupMap[moduleName] = moduleName;
1803 // Record the module info.
1804 moduleInfoToModule[&moduleInfo] = module;
1805 }
1806
1807 // This part verifies that all modules marked by "MustDedup" have been
1808 // properly deduped with each other. For this check to succeed, all modules
1809 // have to been deduped to the same module. It is possible that a module was
1810 // deduped with the wrong thing.
1811
1812 auto failed = false;
1813 // This parses the module name out of a target string.
1814 auto parseModule = [&](Attribute path) -> StringAttr {
1815 // Each module is listed as a target "~Circuit|Module" which we have to
1816 // parse.
1817 auto [_, rhs] = cast<StringAttr>(path).getValue().split('|');
1818 return StringAttr::get(context, rhs);
1819 };
1820 // This gets the name of the module which the current module was deduped
1821 // with. If the named module isn't in the map, then we didn't encounter it
1822 // in the circuit.
1823 auto getLead = [&](StringAttr module) -> StringAttr {
1824 auto it = dedupMap.find(module);
1825 if (it == dedupMap.end()) {
1826 auto diag = emitError(circuit.getLoc(),
1827 "MustDeduplicateAnnotation references module ")
1828 << module << " which does not exist";
1829 failed = true;
1830 return nullptr;
1831 }
1832 return it->second;
1833 };
1834
1835 LLVM_DEBUG(llvm::dbgs() << "Update annotations\n");
1836 AnnotationSet::removeAnnotations(circuit, [&](Annotation annotation) {
1837 if (!annotation.isClass(mustDeduplicateAnnoClass))
1838 return false;
1839 auto modules = annotation.getMember<ArrayAttr>("modules");
1840 if (!modules) {
1841 emitError(circuit.getLoc(),
1842 "MustDeduplicateAnnotation missing \"modules\" member");
1843 failed = true;
1844 return false;
1845 }
1846 // Empty module list has nothing to process.
1847 if (modules.empty())
1848 return true;
1849 // Get the first element.
1850 auto firstModule = parseModule(modules[0]);
1851 auto firstLead = getLead(firstModule);
1852 if (!firstLead)
1853 return false;
1854 // Verify that the remaining elements are all the same as the first.
1855 for (auto attr : modules.getValue().drop_front()) {
1856 auto nextModule = parseModule(attr);
1857 auto nextLead = getLead(nextModule);
1858 if (!nextLead)
1859 return false;
1860 if (firstLead != nextLead) {
1861 auto diag = emitError(circuit.getLoc(), "module ")
1862 << nextModule << " not deduplicated with " << firstModule;
1863 auto a = instanceGraph.lookup(firstLead)->getModule();
1864 auto b = instanceGraph.lookup(nextLead)->getModule();
1865 equiv.check(diag, a, b);
1866 failed = true;
1867 return false;
1868 }
1869 }
1870 return true;
1871 });
1872 if (failed)
1873 return signalPassFailure();
1874
1875 // Remove all dedup group attributes, they only exist during this pass.
1876 for (auto module : circuit.getOps<FModuleLike>())
1877 module->removeDiscardableAttr(dedupGroupAttrName);
1878
1879 fixupInstanceTypes(instanceGraph);
1880
1881 markAnalysesPreserved<NLATable>();
1882 if (!anythingChanged)
1883 markAllAnalysesPreserved();
1884 }
1885};
1886} // end anonymous namespace
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void fixupInstanceTypes(InstanceGraph &instanceGraph)
Adjust instance result types to match the deduplicated module's port types.
Definition Dedup.cpp:1589
static Location mergeLoc(MLIRContext *context, Location to, Location from)
Definition Dedup.cpp:905
static void fixupConnect(ImplicitLocOpBuilder &builder, Value dst, Value src)
This fixes up connects when the field names of a bundle type changes.
Definition Dedup.cpp:1562
static bool canRemoveModule(mlir::SymbolOpInterface symbol)
Returns true if the module can be removed.
Definition Dedup.cpp:56
static void mergeRegions(Region *region1, Region *region2)
Definition HWCleanup.cpp:77
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
static Block * getBodyBlock(FModuleLike mod)
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
bool hasAnnotation(StringRef className) const
Return true if we have an annotation with the specified class name.
static AnnotationSet forPort(FModuleLike op, size_t portNo)
Get an annotation set for the specified port.
This class provides a read-only projection of an annotation.
void setDict(DictionaryAttr dict)
Set the data dictionary of this attribute.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
StringAttr getClassAttr() const
Return the 'class' that this annotation is representing.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
This table tracks nlas and what modules participate in them.
Definition NLATable.h:29
A table of inner symbols and their resolutions.
auto getModule()
Get the module that this node is tracking.
decltype(auto) walkPostOrder(Fn &&fn)
Perform a post-order walk across the modules.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
static StringRef toString(Direction direction)
Definition FIRRTLEnums.h:44
BaseTy type_cast(Type type)
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
size_t getNumPorts(Operation *op)
Return the number of ports in a module-like thing (modules, memories, etc).
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs, bool warnOnTruncation=false)
Emit a connect between two values.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
BaseTy type_dyn_cast(Type type)
bool type_isa(Type type)
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:64
void info(Twine message)
Definition LSPUtils.cpp:20
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::raw_ostream & debugHeader(const llvm::Twine &str, unsigned width=80)
Write a "header"-like string to the debug stream with a certain width.
Definition Debug.cpp:17
SmallVector< FlatSymbolRefAttr > createNLAs(Operation *fromModule, ArrayRef< Attribute > baseNamepath, SymbolTable::Visibility vis=SymbolTable::Visibility::Private)
Look up the instantiations of the from module and create an NLA for each one, appending the baseNamep...
Definition Dedup.cpp:1089
MLIRContext * context
Definition Dedup.cpp:1530
Block * nlaBlock
We insert all NLAs to the beginning of this block.
Definition Dedup.cpp:1538
void recordAnnotations(Operation *op)
Record all targets which use an NLA.
Definition Dedup.cpp:1035
void eraseNLA(hw::HierPathOp nla)
This erases the NLA op, and removes the NLA from every module's NLA map, but it does not delete the N...
Definition Dedup.cpp:1154
void mergeAnnotations(FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
Merge all annotations and port annotations on two operations.
Definition Dedup.cpp:1369
void replaceInstances(FModuleLike toModule, Operation *fromModule)
This deletes and replaces all instances of the "fromModule" with instances of the "toModule".
Definition Dedup.cpp:1051
void record(FModuleLike module)
Record the usages of any NLA's in this module, so that we may update the annotation if the parent mod...
Definition Dedup.cpp:1009
void rewriteExtModuleNLAs(RenameMap &renameMap, StringAttr toName, StringAttr fromName)
Definition Dedup.cpp:1267
void mergeRegions(RenameMap &renameMap, FModuleLike toModule, Region &toRegion, FModuleLike fromModule, Region &fromRegion)
Definition Dedup.cpp:1522
void dedup(FModuleLike toModule, FModuleLike fromModule)
Remove the "fromModule", and replace all references to it with the "toModule".
Definition Dedup.cpp:974
void rewriteModuleNLAs(RenameMap &renameMap, FModuleOp toModule, FModuleOp fromModule)
Process all the NLAs that the two modules participate in, replacing references to the "from" module w...
Definition Dedup.cpp:1259
SmallVector< FlatSymbolRefAttr > createNLAs(StringAttr toModuleName, FModuleLike fromModule, SymbolTable::Visibility vis=SymbolTable::Visibility::Private)
Look up the instantiations of this module and create an NLA for each one.
Definition Dedup.cpp:1127
void recordAnnotations(AnnoTarget target)
For a specific annotation target, record all the unique NLAs which target it in the targetMap.
Definition Dedup.cpp:1028
NLATable * nlaTable
Cached nla table analysis.
Definition Dedup.cpp:1535
hw::InnerSymAttr mergeInnerSymbols(RenameMap &renameMap, FModuleLike toModule, hw::InnerSymAttr toSym, hw::InnerSymAttr fromSym)
Definition Dedup.cpp:1394
void cloneAnnotation(SmallVectorImpl< FlatSymbolRefAttr > &nlas, Annotation anno, ArrayRef< NamedAttribute > attributes, unsigned nonLocalIndex, SmallVectorImpl< Annotation > &newAnnotations)
Clone the annotation for each NLA in a list.
Definition Dedup.cpp:1135
void recordSymRenames(RenameMap &renameMap, FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
Definition Dedup.cpp:1444
void mergeAnnotations(FModuleLike toModule, AnnoTarget to, AnnotationSet toAnnos, FModuleLike fromModule, AnnoTarget from, AnnotationSet fromAnnos)
Merge the annotations of a specific target, either a operation or a port on an operation.
Definition Dedup.cpp:1346
StringAttr nonLocalString
Definition Dedup.cpp:1548
hw::InnerSymbolNamespace & getNamespace(Operation *module)
Get a cached namespace for a module.
Definition Dedup.cpp:1021
SymbolTable & symbolTable
Definition Dedup.cpp:1532
void mergeOps(RenameMap &renameMap, FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
Recursively merge two operations.
Definition Dedup.cpp:1489
DenseMap< Operation *, hw::InnerSymbolNamespace > moduleNamespaces
A module namespace cache.
Definition Dedup.cpp:1552
bool makeAnnotationNonLocal(StringAttr toModuleName, AnnoTarget to, FModuleLike fromModule, Annotation anno, SmallVectorImpl< Annotation > &newAnnotations)
Take an annotation, and update it to be a non-local annotation.
Definition Dedup.cpp:1275
InstanceGraph & instanceGraph
Definition Dedup.cpp:1531
void mergeBlocks(RenameMap &renameMap, FModuleLike toModule, Block &toBlock, FModuleLike fromModule, Block &fromBlock)
Recursively merge two blocks.
Definition Dedup.cpp:1508
DenseMap< Attribute, llvm::SmallDenseSet< AnnoTarget > > targetMap
Definition Dedup.cpp:1541
StringAttr classString
Definition Dedup.cpp:1549
void copyAnnotations(FModuleLike toModule, AnnoTarget to, FModuleLike fromModule, AnnotationSet annos, SmallVectorImpl< Annotation > &newAnnotations, SmallPtrSetImpl< Attribute > &dontTouches)
Definition Dedup.cpp:1317
Deduper(InstanceGraph &instanceGraph, SymbolTable &symbolTable, NLATable *nlaTable, CircuitOp circuit)
Definition Dedup.cpp:958
void addAnnotationContext(RenameMap &renameMap, FModuleOp toModule, FModuleOp fromModule)
Process all NLAs referencing the "from" module to point to the "to" module.
Definition Dedup.cpp:1164
DenseMap< StringAttr, StringAttr > RenameMap
Definition Dedup.cpp:956
DenseMap< Attribute, Attribute > nlaCache
Definition Dedup.cpp:1545
const hw::InnerSymbolTable & a
Definition Dedup.cpp:422
ModuleData(const hw::InnerSymbolTable &a, const hw::InnerSymbolTable &b)
Definition Dedup.cpp:419
const hw::InnerSymbolTable & b
Definition Dedup.cpp:423
LogicalResult check(InFlightDiagnostic &diag, igraph::InstanceOpInterface a, igraph::InstanceOpInterface b)
Definition Dedup.cpp:709
DenseSet< Attribute > nonessentialAttributes
Definition Dedup.cpp:892
std::string prettyPrint(Attribute attr)
Definition Dedup.cpp:426
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Block &aBlock, Operation *b, Block &bBlock)
Definition Dedup.cpp:498
LogicalResult check(InFlightDiagnostic &diag, const Twine &message, Operation *a, Type aType, Operation *b, Type bType)
Definition Dedup.cpp:472
StringAttr noDedupClass
Definition Dedup.cpp:887
StringAttr dedupGroupAttrName
Definition Dedup.cpp:889
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, DictionaryAttr aDict, Operation *b, DictionaryAttr bDict)
Definition Dedup.cpp:621
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Region &aRegion, Operation *b, Region &bRegion)
Definition Dedup.cpp:576
StringAttr portDirectionsAttr
Definition Dedup.cpp:885
LogicalResult check(InFlightDiagnostic &diag, const Twine &message, Operation *a, BundleType aType, Operation *b, BundleType bType)
Definition Dedup.cpp:442
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Operation *b)
Definition Dedup.cpp:747
Equivalence(MLIRContext *context, InstanceGraph &instanceGraph)
Definition Dedup.cpp:402
LogicalResult check(InFlightDiagnostic &diag, Operation *a, mlir::DenseBoolArrayAttr aAttr, Operation *b, mlir::DenseBoolArrayAttr bAttr)
Definition Dedup.cpp:596
InstanceGraph & instanceGraph
Definition Dedup.cpp:893
void check(InFlightDiagnostic &diag, Operation *a, Operation *b)
Definition Dedup.cpp:836
A reference to a ModuleInfo that compares and hashes like it.
Definition Dedup.cpp:367
ModuleInfo * info
Definition Dedup.cpp:369
ModuleInfoRef(ModuleInfo *info)
Definition Dedup.cpp:368
std::vector< StringAttr > referredModuleNames
Definition Dedup.cpp:82
std::array< uint8_t, 32 > structuralHash
Definition Dedup.cpp:80
This struct contains constant string attributes shared across different threads.
Definition Dedup.cpp:92
DenseSet< Attribute > nonessentialAttributes
Definition Dedup.cpp:122
StructuralHasherSharedConstants(MLIRContext *context)
Definition Dedup.cpp:93
void populateInnerSymIDTable(FModuleLike module)
Find all the ports and operations which may define an inner symbol operations and give each a unique ...
Definition Dedup.cpp:141
void update(Operation *op, DictionaryAttr dict)
Hash the top level attribute dictionary of the operation.
Definition Dedup.cpp:237
void update(Type type)
Definition Dedup.cpp:227
void update(const void *pointer)
Definition Dedup.cpp:198
DenseMap< void *, unsigned > idTable
Definition Dedup.cpp:344
void update(const std::pair< T, U > &pair)
Definition Dedup.cpp:209
void update(Operation *op)
Definition Dedup.cpp:316
size_t position
Definition Dedup.cpp:361
llvm::SHA256 sha
Definition Dedup.cpp:358
DenseMap< StringAttr, std::pair< size_t, size_t > > innerSymIDTable
Definition Dedup.cpp:348
ModuleInfo getModuleInfo(FModuleLike module)
Definition Dedup.cpp:129
void update(size_t value)
Definition Dedup.cpp:203
void update(BundleType type)
Definition Dedup.cpp:217
unsigned getID(void *object)
Definition Dedup.cpp:160
void update(OpResult result)
Definition Dedup.cpp:233
void update(OpOperand &operand)
Definition Dedup.cpp:181
StructuralHasher(const StructuralHasherSharedConstants &constants)
Definition Dedup.cpp:126
void update(Region *region)
Definition Dedup.cpp:308
void update(Block *block)
Definition Dedup.cpp:297
std::vector< StringAttr > referredModuleNames
Definition Dedup.cpp:351
void update(TypeID typeID)
Definition Dedup.cpp:214
const StructuralHasherSharedConstants & constants
Definition Dedup.cpp:354
std::pair< size_t, size_t > getInnerSymID(StringAttr name)
Definition Dedup.cpp:177
unsigned nextID
Definition Dedup.cpp:345
unsigned finalizeID(void *object)
Definition Dedup.cpp:168
void update(mlir::OperationName name)
Definition Dedup.cpp:291
An annotation target is used to keep track of something that is targeted by an Annotation.
AnnotationSet getAnnotations() const
Get the annotations associated with the target.
void setAnnotations(AnnotationSet annotations) const
Set the annotations associated with the target.
This represents an annotation targeting a specific operation.
Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const
This represents an annotation targeting a specific port of a module, memory, or instance.
static bool isEqual(const ModuleInfoRef &lhs, const ModuleInfoRef &rhs)
Definition Dedup.cpp:388
static unsigned getHashValue(const ModuleInfoRef &ref)
Definition Dedup.cpp:376