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"
38#define DEBUG_TYPE "firrtl-dedup"
42#define GEN_PASS_DEF_DEDUP
43#include "circt/Dialect/FIRRTL/Passes.h.inc"
49using hw::InnerRefAttr;
58 if (!symbol.isPrivate())
63 if (!symbol.canDiscardOnUseEmpty())
144 for (
auto [index, innerSym] : llvm::enumerate(module.getPortSymbols())) {
145 for (
auto prop : cast<hw::InnerSymAttr>(innerSym))
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());
169 auto it =
idTable.find(
object);
172 auto id = it->second;
182 auto value = operand.get();
183 if (
auto result = dyn_cast<OpResult>(value)) {
184 auto *op = result.getOwner();
186 update(result.getResultNumber());
189 if (
auto argument = dyn_cast<BlockArgument>(value)) {
190 auto *block = argument.getOwner();
192 update(argument.getArgNumber());
195 llvm_unreachable(
"Unknown value type");
199 auto *
addr =
reinterpret_cast<const uint8_t *
>(&pointer);
200 sha.update(ArrayRef<uint8_t>(
addr,
sizeof pointer));
204 auto *
addr =
reinterpret_cast<const uint8_t *
>(&value);
205 sha.update(ArrayRef<uint8_t>(
addr,
sizeof value));
208 template <
typename T,
typename U>
209 void update(
const std::pair<T, U> &pair) {
219 update(type.getNumElements());
220 for (
auto &element : type.getElements()) {
230 update(type.getAsOpaquePointer());
237 void update(Operation *op, DictionaryAttr dict) {
238 for (
auto namedAttr : dict) {
239 auto name = namedAttr.getName();
240 auto value = namedAttr.getValue();
243 if (
constants.nonessentialAttributes.contains(name))
247 update(name.getAsOpaquePointer());
251 auto portTypes = cast<ArrayAttr>(value).getAsValueRange<TypeAttr>();
252 for (
auto type : portTypes)
262 if (isa<InstanceOp>(op) && name ==
constants.moduleNameAttr) {
267 if (isa<InstanceChoiceOp>(op) && name ==
constants.moduleNamesAttr) {
268 for (
auto module : cast<ArrayAttr>(value))
270 cast<FlatSymbolRefAttr>(module).getAttr());
276 if (isa<DistinctAttr>(value))
280 if (
auto innerRef = dyn_cast<hw::InnerRefAttr>(value)) {
287 update(value.getAsOpaquePointer());
293 update(name.getAsOpaquePointer());
298 for (
auto &op : llvm::reverse(*block))
300 for (
auto type : block->getArgumentTypes())
309 for (
auto &block : llvm::reverse(region->getBlocks()))
319 update(op->getNumRegions());
320 for (
auto ®ion : reverse(op->getRegions()))
326 for (
auto &operand : op->getOpOperands())
331 update(op, op->getAttrDictionary());
334 for (
auto result : op->getResults())
383 return llvm::hash_combine(
427 SmallString<64> buffer;
428 llvm::raw_svector_ostream os(buffer);
429 if (
auto integerAttr = dyn_cast<IntegerAttr>(attr)) {
431 if (integerAttr.getType().isSignlessInteger())
432 integerAttr.getValue().toStringUnsigned(buffer, 16);
434 integerAttr.getAPSInt().toString(buffer, 16);
438 return std::string(buffer);
442 LogicalResult
check(InFlightDiagnostic &diag,
const Twine &message,
443 Operation *a, BundleType aType, Operation *b,
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";
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";
463 if (failed(
check(diag,
464 message +
" -> bundle element \'" +
465 aElement.name.getValue() +
"'",
466 a, aElement.type, b, bElement.type)))
472 LogicalResult
check(InFlightDiagnostic &diag,
const Twine &message,
473 Operation *a, Type aType, Operation *b, Type bType) {
478 return check(diag, message, a, aBundleType, b, bBundleType);
481 diag.attachNote(a->getLoc())
482 << message <<
", has a RefType with a different base type "
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 "
492 diag.attachNote(a->getLoc())
493 << message <<
" types don't match, first type is " << aType;
494 diag.attachNote(b->getLoc()) <<
"second type is " << bType;
499 Block &aBlock, Operation *b, Block &bBlock) {
502 auto portNames = a->getAttrOfType<ArrayAttr>(
"portNames");
504 auto emitMissingPort = [&](Value existsVal, Operation *opExists,
505 Operation *opDoesNotExist) {
507 auto portNames = opExists->getAttrOfType<ArrayAttr>(
"portNames");
509 if (
auto portNameAttr = dyn_cast<StringAttr>(portNames[portNo]))
510 portName = portNameAttr.getValue();
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 "
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";
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()) {
538 if (
auto portNameAttr = dyn_cast<StringAttr>(portNames[portNo]))
539 portName = portNameAttr.getValue();
542 if (failed(
check(diag,
"module port '" + portName +
"'", a,
543 aArg->getType(), b, bArg->getType())))
545 data.map.map(aArg.value(), bArg.value());
549 if (!aArg.has_value())
551 return emitMissingPort(aArg.has_value() ? aArg.value() : bArg.value(), a,
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++)))
564 diag.attachNote(aIt->getLoc()) <<
"first block has more operations";
565 diag.attachNote(b->getLoc()) <<
"second block here";
569 diag.attachNote(bIt->getLoc()) <<
"second block has more operations";
570 diag.attachNote(a->getLoc()) <<
"first block here";
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();
584 while (aIt != aEnd && bIt != bEnd)
585 if (failed(
check(diag,
data, a, *aIt++, b, *bIt++)))
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";
596 LogicalResult
check(InFlightDiagnostic &diag, Operation *a,
597 mlir::DenseBoolArrayAttr aAttr, Operation *b,
598 mlir::DenseBoolArrayAttr bAttr) {
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 ¬e = diag.attachNote(a->getLoc()) <<
"module port ";
608 note <<
"'" << cast<StringAttr>(portNames[i]).getValue() <<
"'";
611 note <<
" directions don't match, first direction is '"
613 diag.attachNote(b->getLoc()) <<
"second direction is '"
622 DictionaryAttr aDict, Operation *b,
623 DictionaryAttr bDict) {
628 DenseSet<Attribute> seenAttrs;
629 for (
auto namedAttr : aDict) {
630 auto attrName = namedAttr.getName();
634 auto aAttr = namedAttr.getValue();
635 auto bAttr = bDict.get(attrName);
637 diag.attachNote(a->getLoc())
638 <<
"second operation is missing attribute " << attrName;
639 diag.attachNote(b->getLoc()) <<
"second operation here";
643 if (isa<hw::InnerRefAttr>(aAttr) && isa<hw::InnerRefAttr>(bAttr)) {
644 auto bRef = cast<hw::InnerRefAttr>(bAttr);
645 auto aRef = cast<hw::InnerRefAttr>(aAttr);
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";
653 diag.attachNote(a->getLoc())
654 <<
"operations have different targets, first operation has "
656 diag.attachNote(b->getLoc()) <<
"second operation has " << bTarget;
659 if (aTarget.isPort()) {
661 if (!bTarget.isPort() || aTarget.getPort() != bTarget.getPort())
665 if (!bTarget.isOpOnly() ||
666 data.map.lookupOrNull(aTarget.getOp()) != bTarget.getOp())
669 if (aTarget.getField() != bTarget.getField())
674 if (failed(
check(diag, a, cast<mlir::DenseBoolArrayAttr>(aAttr), b,
675 cast<mlir::DenseBoolArrayAttr>(bAttr))))
677 }
else if (isa<DistinctAttr>(aAttr) && isa<DistinctAttr>(bAttr)) {
680 }
else if (aAttr != bAttr) {
681 diag.attachNote(a->getLoc())
682 <<
"first operation has attribute '" << attrName.getValue()
684 diag.attachNote(b->getLoc())
685 <<
"second operation has value " <<
prettyPrint(bAttr);
688 seenAttrs.insert(attrName);
690 if (aDict.getValue().size() != bDict.getValue().size()) {
691 for (
auto namedAttr : bDict) {
692 auto attrName = namedAttr.getName();
696 seenAttrs.contains(attrName))
699 diag.attachNote(a->getLoc())
700 <<
"first operation is missing attribute " << attrName;
701 diag.attachNote(b->getLoc()) <<
"second operation here";
709 LogicalResult
check(InFlightDiagnostic &diag, igraph::InstanceOpInterface a,
710 igraph::InstanceOpInterface b) {
713 auto aNames = a.getReferencedModuleNamesAttr();
714 auto bNames = b.getReferencedModuleNamesAttr();
715 if (aNames == bNames)
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";
728 for (
auto [aName, bName] : llvm::zip(aNames.getAsRange<StringAttr>(),
729 bNames.getAsRange<StringAttr>())) {
738 diag.attachNote(std::nullopt)
739 <<
"in instance " << a.getInstanceNameAttr() <<
" of " << aName
740 <<
", and instance " << b.getInstanceNameAttr() <<
" of " << bName;
741 check(diag, aModule, bModule);
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();
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)))
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";
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,
779 data.map.map(aValue, bValue);
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";
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 '"
799 diag.attachNote(b->getLoc())
800 <<
"second operand is '"
804 <<
"', but should have been '"
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";
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)))
829 if (failed(
check(diag,
data, a, a->getAttrDictionary(), b,
830 b->getAttrDictionary())))
836 void check(InFlightDiagnostic &diag, Operation *a, Operation *b) {
841 diag.attachNote(a->getLoc()) <<
"module marked NoDedup";
845 diag.attachNote(b->getLoc()) <<
"module marked NoDedup";
848 auto aSymbol = cast<mlir::SymbolOpInterface>(a);
849 auto bSymbol = cast<mlir::SymbolOpInterface>(b);
851 diag.attachNote(a->getLoc())
853 << (aSymbol.isPrivate() ?
"private but not discardable" :
"public");
854 diag.attachNote(b->getLoc())
856 << (bSymbol.isPrivate() ?
"private but not discardable" :
"public");
861 auto bGroup = dyn_cast_or_null<StringAttr>(
863 if (aGroup != bGroup) {
865 diag.attachNote(b->getLoc())
866 <<
"module is in dedup group '" << bGroup.str() <<
"'";
868 diag.attachNote(b->getLoc()) <<
"module is not part of a dedup group";
871 diag.attachNote(a->getLoc())
872 <<
"module is in dedup group '" << aGroup.str() <<
"'";
874 diag.attachNote(a->getLoc()) <<
"module is not part of a dedup group";
880 diag.attachNote(a->getLoc()) <<
"first module here";
881 diag.attachNote(b->getLoc()) <<
"second module here";
909 unsigned seenFIR = 0;
910 for (
auto loc : {to, from}) {
913 if (
auto fusedLoc = dyn_cast<FusedLoc>(loc)) {
916 for (
auto loc : fusedLoc.getLocations()) {
917 if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
918 if (fileLoc.getFilename().strref().ends_with(
".fir")) {
924 decomposedLocs.insert(loc);
930 if (FileLineColLoc fileLoc = dyn_cast<FileLineColLoc>(loc)) {
931 if (fileLoc.getFilename().strref().ends_with(
".fir")) {
938 if (!isa<UnknownLoc>(loc))
939 decomposedLocs.insert(loc);
942 auto locs = decomposedLocs.getArrayRef();
947 return UnknownLoc::get(
context);
948 if (locs.size() == 1)
951 return FusedLoc::get(
context, locs);
966 for (
auto nla : circuit.getOps<hw::HierPathOp>())
967 nlaCache[nla.getNamepathAttr()] = nla.getNameAttr();
974 void dedup(FModuleLike toModule, FModuleLike fromModule) {
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);
987 cast<LocationAttr>(fromLoc)));
989 toModule->setAttr(
"portLocations", ArrayAttr::get(
context, newLocs));
992 mergeOps(renameMap, toModule, toModule, fromModule, fromModule);
998 if (
auto to = dyn_cast<FModuleOp>(*toModule))
1002 fromModule.getModuleNameAttr());
1013 for (
unsigned i = 0, e =
getNumPorts(module); i < e; ++i)
1016 module->walk([&](Operation *op) { recordAnnotations(op); });
1030 if (
auto nlaRef = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal"))
1031 targetMap[nlaRef.getAttr()].insert(target);
1040 auto mem = dyn_cast<MemOp>(op);
1045 for (
unsigned i = 0, e = mem->getNumResults(); i < e; ++i)
1054 instanceGraph[::cast<igraph::ModuleOpInterface>(fromModule)];
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);
1070 newModules.push_back(FlatSymbolRefAttr::get(moduleName));
1072 instanceChoiceOp.setModuleNamesAttr(
1073 ArrayAttr::get(
context, newModules));
1074 instanceChoiceOp.setPortNamesAttr(toModule.getPortNamesAttr());
1076 oldInstRec->getParent()->addInstance(inst, toNode);
1077 oldInstRec->erase();
1080 fromModule->erase();
1088 SmallVector<FlatSymbolRefAttr>
1089 createNLAs(Operation *fromModule, ArrayRef<Attribute> baseNamepath,
1090 SymbolTable::Visibility vis = SymbolTable::Visibility::Private) {
1093 SmallVector<Attribute> namepath = {
nullptr};
1094 namepath.append(baseNamepath.begin(), baseNamepath.end());
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();
1103 auto arrayAttr = ArrayAttr::get(
context, namepath);
1105 auto &cacheEntry =
nlaCache[arrayAttr];
1107 auto builder = OpBuilder::atBlockBegin(
nlaBlock);
1108 auto nla = hw::HierPathOp::create(builder, loc,
"nla",
1113 cacheEntry = nla.getNameAttr();
1114 nla.setVisibility(vis);
1117 auto nlaRef = FlatSymbolRefAttr::get(cast<StringAttr>(cacheEntry));
1118 nlas.push_back(nlaRef);
1126 SmallVector<FlatSymbolRefAttr>
1128 SymbolTable::Visibility vis = SymbolTable::Visibility::Private) {
1129 return createNLAs(fromModule, FlatSymbolRefAttr::get(toModuleName), vis);
1136 Annotation anno, ArrayRef<NamedAttribute> attributes,
1137 unsigned nonLocalIndex,
1138 SmallVectorImpl<Annotation> &newAnnotations) {
1139 SmallVector<NamedAttribute> mutableAttributes(attributes.begin(),
1141 for (
auto &nla : nlas) {
1143 mutableAttributes[nonLocalIndex].setValue(nla);
1144 auto dict = DictionaryAttr::getWithSorted(
context, mutableAttributes);
1147 newAnnotations.push_back(anno);
1158 nlaCache.erase(nla.getNamepathAttr());
1165 FModuleOp fromModule) {
1166 auto toName = toModule.getNameAttr();
1167 auto fromName = fromModule.getNameAttr();
1170 auto moduleNLAs =
nlaTable->lookup(fromModule.getNameAttr()).vec();
1172 nlaTable->renameModuleAndInnerRef(toName, fromName, renameMap);
1178 std::optional<std::pair<Attribute, StringAttr>> singlePrefixAndName;
1181 for (
auto nla : moduleNLAs) {
1182 auto elements = nla.getNamepath().getValue();
1184 if (nla.root() != toName)
1188 if (fromNode->hasOneUse()) {
1189 if (!singlePrefixAndName) {
1190 auto *instanceRecord = *fromNode->uses().begin();
1192 cast<FModuleOp>(*instanceRecord->getParent()->getModule());
1193 singlePrefixAndName = {
OpAnnoTarget(instanceRecord->getInstance())
1195 parent.getNameAttr()};
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);
1206 nlaCache.erase(nla.getNamepathAttr());
1207 nla.setNamepathAttr(newPath);
1208 nlaCache[newPath] = nla.getNameAttr();
1209 nlaTable->addNLAtoModule(nla, singleParentName);
1215 SmallVector<Attribute> namepath(elements.begin(), elements.end());
1216 auto nlaRefs =
createNLAs(fromModule, namepath, nla.getVisibility());
1218 auto &set =
targetMap[nla.getNameAttr()];
1219 SmallVector<AnnoTarget> targets(set.begin(), set.end());
1221 for (
auto target : targets) {
1224 SmallVector<Annotation> newAnnotations;
1225 for (
auto anno : target.getAnnotations()) {
1227 auto [it,
found] = mlir::impl::findAttrSorted(
1231 if (!
found || cast<FlatSymbolRefAttr>(it->getValue()).getAttr() !=
1232 nla.getNameAttr()) {
1233 newAnnotations.push_back(anno);
1236 auto nonLocalIndex = std::distance(anno.begin(), it);
1239 ArrayRef<NamedAttribute>(anno.begin(), anno.end()),
1240 nonLocalIndex, newAnnotations);
1245 target.setAnnotations(annotations);
1247 for (
auto nla : nlaRefs)
1248 targetMap[nla.getAttr()].insert(target);
1260 FModuleOp fromModule) {
1268 StringAttr fromName) {
1269 nlaTable->renameModuleAndInnerRef(toName, fromName, renameMap);
1277 SmallVectorImpl<Annotation> &newAnnotations) {
1280 SmallVector<NamedAttribute> attributes;
1281 int nonLocalIndex = -1;
1282 for (
const auto &val : llvm::enumerate(anno)) {
1283 auto attr = val.value();
1286 assert(compare != 0 &&
"should not pass non-local annotations here");
1290 nonLocalIndex = val.index();
1296 attributes.push_back(attr);
1298 if (nonLocalIndex == -1) {
1300 nonLocalIndex = attributes.size();
1304 attributes.append(anno.
begin() + nonLocalIndex, anno.
end());
1308 auto nlaRefs =
createNLAs(toModuleName, fromModule);
1309 for (
auto nla : nlaRefs)
1313 cloneAnnotation(nlaRefs, anno, attributes, nonLocalIndex, newAnnotations);
1319 SmallVectorImpl<Annotation> &newAnnotations,
1320 SmallPtrSetImpl<Attribute> &dontTouches) {
1321 for (
auto anno : annos) {
1322 if (anno.isClass(dontTouchAnnoClass)) {
1325 anno.removeMember(
"circt.nonlocal");
1326 auto [it, inserted] = dontTouches.insert(anno.getAttr());
1328 newAnnotations.push_back(anno);
1333 if (
auto nla = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal")) {
1334 newAnnotations.push_back(anno);
1350 SmallVector<Annotation> newAnnotations;
1354 llvm::SmallPtrSet<Attribute, 4> dontTouches;
1364 if (!newAnnotations.empty())
1370 FModuleLike fromModule, Operation *from) {
1376 if (toModule == to) {
1378 for (
unsigned i = 0, e =
getNumPorts(toModule); i < e; ++i)
1383 }
else if (
auto toMem = dyn_cast<MemOp>(to)) {
1385 auto fromMem = cast<MemOp>(from);
1386 for (
unsigned i = 0, e = toMem.getNumResults(); i < e; ++i)
1395 hw::InnerSymAttr toSym,
1396 hw::InnerSymAttr fromSym) {
1397 if (fromSym && !fromSym.getProps().empty()) {
1400 SmallVector<hw::InnerSymPropertiesAttr> newProps;
1403 llvm::append_range(newProps, 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();
1410 if (it != newProps.end()) {
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());
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);
1429 renameMap[fromProp.getName()] = newProp.getName();
1432 llvm::sort(newProps, [](
auto &p,
auto &q) {
1433 return p.getFieldID() < q.getFieldID();
1436 return hw::InnerSymAttr::get(
context, newProps);
1438 return hw::InnerSymAttr();
1445 Operation *to, FModuleLike fromModule,
1449 if (
auto fromInnerSym = dyn_cast<hw::InnerSymbolOpInterface>(from)) {
1450 auto toInnerSym = cast<hw::InnerSymbolOpInterface>(to);
1452 toInnerSym.getInnerSymAttr(),
1453 fromInnerSym.getInnerSymAttr()))
1454 toInnerSym.setInnerSymbolAttr(newSymAttr);
1458 auto fromPortSyms = from->getAttrOfType<ArrayAttr>(
"portSymbols");
1459 if (!fromPortSyms || fromPortSyms.empty())
1462 auto portCount = fromPortSyms.size();
1463 auto toPortSyms = to->getAttrOfType<ArrayAttr>(
"portSymbols");
1467 SmallVector<Attribute> newPortSyms;
1468 if (toPortSyms.empty())
1469 newPortSyms.assign(portCount, hw::InnerSymAttr());
1471 newPortSyms.assign(toPortSyms.begin(), toPortSyms.end());
1473 for (
unsigned portNo = 0; portNo < portCount; ++portNo) {
1475 renameMap, toModule,
1476 llvm::cast_if_present<hw::InnerSymAttr>(newPortSyms[portNo]),
1477 cast<hw::InnerSymAttr>(fromPortSyms[portNo]))) {
1478 newPortSyms[portNo] = newPortSym;
1483 FModuleLike::fixupPortSymsArray(newPortSyms, toModule.getContext());
1484 cast<FModuleLike>(to).setPortSymbols(newPortSyms);
1490 FModuleLike fromModule, Operation *from) {
1492 if (to->getLoc() != from->getLoc())
1496 for (
auto regions : llvm::zip(to->getRegions(), from->getRegions()))
1497 mergeRegions(renameMap, toModule, std::get<0>(regions), fromModule,
1498 std::get<1>(regions));
1509 FModuleLike fromModule, Block &fromBlock) {
1511 for (
auto [toArg, fromArg] :
1512 llvm::zip(toBlock.getArguments(), fromBlock.getArguments()))
1513 if (toArg.getLoc() != fromArg.getLoc())
1516 for (
auto ops : llvm::zip(toBlock, fromBlock))
1517 mergeOps(renameMap, toModule, &std::get<0>(ops), fromModule,
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));
1541 DenseMap<Attribute, llvm::SmallDenseSet<AnnoTarget>>
targetMap;
1562static void fixupConnect(ImplicitLocOpBuilder &builder, Value dst, Value src) {
1564 auto dstType = dst.getType();
1565 auto srcType = src.getType();
1566 if (dstType == 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);
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());
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();
1602 if (newType == oldType)
1604 LLVM_DEBUG(llvm::dbgs()
1605 <<
"- Updating instance port \"" << instOp.getInstanceName()
1606 <<
"." << instOp.getPortName(index) <<
"\" from " << oldType
1607 <<
" to " << newType <<
"\n");
1611 auto wire = WireOp::create(builder, oldType, instOp.getPortName(index))
1613 result.replaceAllUsesWith(wire);
1614 result.setType(newType);
1615 if (instOp.getPortDirection(index) == Direction::Out)
1629class DedupPass :
public circt::firrtl::impl::DedupBase<DedupPass> {
1630 using DedupBase::DedupBase;
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;
1642 llvm::dbgs() <<
"\n";
1643 debugHeader(Twine(
"Dedup circuit \"") + circuit.getName() +
"\"")
1648 auto noDedupClass = StringAttr::get(
context, noDedupAnnoClass);
1651 auto dedupGroupClass = StringAttr::get(
context, dedupGroupAnnoClass);
1654 DenseMap<ModuleInfoRef, Operation *> moduleInfoToModule;
1659 DenseMap<Attribute, StringAttr> dedupMap;
1664 SmallVector<FModuleLike, 0> modules;
1666 if (
auto mod = dyn_cast<FModuleLike>(*node.getModule()))
1667 modules.push_back(mod);
1669 LLVM_DEBUG(llvm::dbgs() <<
"Found " << modules.size() <<
" modules\n");
1671 SmallVector<std::optional<ModuleInfo>> moduleInfos(modules.size());
1672 StructuralHasherSharedConstants hasherConstants(&getContext());
1675 auto dedupGroupAttrName = StringAttr::get(
context,
"firrtl.dedup_group");
1681 for (
auto module : modules) {
1682 llvm::SmallSetVector<StringAttr, 1> groups;
1684 module, [&groups, dedupGroupClass](Annotation annotation) {
1687 groups.insert(annotation.
getMember<StringAttr>(
"group"));
1690 if (groups.size() > 1) {
1691 module.emitError("module belongs to multiple dedup groups: ") << groups;
1692 return signalPassFailure();
1694 assert(!module->hasAttr(dedupGroupAttrName) &&
1695 "unexpected existing use of temporary dedup group attribute");
1696 if (!groups.empty())
1697 module->setDiscardableAttr(dedupGroupAttrName, groups.front());
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];
1706 if (AnnotationSet::hasAnnotation(module, noDedupClass))
1710 if (auto ext = dyn_cast<FExtModuleOp>(*module);
1711 ext && !ext.getDefname().has_value())
1715 if (isa<ClassOp>(*module))
1718 StructuralHasher hasher(hasherConstants);
1720 moduleInfos[idx] = hasher.getModuleInfo(module);
1726 auto &os = llvm::dbgs();
1727 for (
auto [module, info] : llvm::zip(modules, moduleInfos)) {
1730 os << llvm::format_bytes(
info->structuralHash, std::nullopt, 32, 32);
1732 os <<
"--------------------------------";
1733 os <<
"--------------------------------";
1735 os <<
" for " << module.getModuleNameAttr() <<
"\n";
1739 if (result.failed())
1740 return signalPassFailure();
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];
1747 if (!maybeModuleInfo) {
1752 dedupMap[moduleName] = moduleName;
1756 auto &moduleInfo = maybeModuleInfo.value();
1759 for (
auto &referredModule : moduleInfo.referredModuleNames)
1760 referredModule = dedupMap[referredModule];
1763 auto it = moduleInfoToModule.find(&moduleInfo);
1764 if (it != moduleInfoToModule.end()) {
1765 auto original = cast<FModuleLike>(it->second);
1766 auto originalName = original.getModuleNameAttr();
1771 cast<mlir::SymbolOpInterface>(module.getOperation()))) {
1773 dedupMap[moduleName] = moduleName;
1776 cast<mlir::SymbolOpInterface>(original.getOperation())))
1779 for (
auto &[_, dedupedName] : dedupMap)
1780 if (dedupedName == originalName)
1781 dedupedName = moduleName;
1784 it->second =
module;
1786 std::swap(originalName, moduleName);
1787 std::swap(original, module);
1791 LLVM_DEBUG(llvm::dbgs() <<
"- Replace " << moduleName <<
" with "
1792 << originalName <<
"\n");
1793 dedupMap[moduleName] = originalName;
1794 deduper.dedup(original, module);
1796 anythingChanged =
true;
1800 deduper.record(module);
1802 dedupMap[moduleName] = moduleName;
1804 moduleInfoToModule[&moduleInfo] =
module;
1812 auto failed =
false;
1814 auto parseModule = [&](Attribute path) -> StringAttr {
1817 auto [_, rhs] = cast<StringAttr>(path).getValue().split(
'|');
1818 return StringAttr::get(
context, rhs);
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";
1835 LLVM_DEBUG(llvm::dbgs() <<
"Update annotations\n");
1837 if (!annotation.
isClass(mustDeduplicateAnnoClass))
1839 auto modules = annotation.
getMember<ArrayAttr>(
"modules");
1841 emitError(circuit.getLoc(),
1842 "MustDeduplicateAnnotation missing \"modules\" member");
1847 if (modules.empty())
1850 auto firstModule = parseModule(modules[0]);
1851 auto firstLead = getLead(firstModule);
1855 for (
auto attr : modules.getValue().drop_front()) {
1856 auto nextModule = parseModule(attr);
1857 auto nextLead = getLead(nextModule);
1860 if (firstLead != nextLead) {
1861 auto diag = emitError(circuit.getLoc(),
"module ")
1862 << nextModule <<
" not deduplicated with " << firstModule;
1865 equiv.check(diag, a, b);
1873 return signalPassFailure();
1876 for (
auto module : circuit.getOps<FModuleLike>())
1877 module->removeDiscardableAttr(dedupGroupAttrName);
1881 markAnalysesPreserved<NLATable>();
1882 if (!anythingChanged)
1883 markAllAnalysesPreserved();
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void mergeRegions(Region *region1, Region *region2)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
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.
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)
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)
static bool operator==(const ModulePort &a, const ModulePort &b)
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.
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...
Block * nlaBlock
We insert all NLAs to the beginning of this block.
void recordAnnotations(Operation *op)
Record all targets which use an NLA.
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...
void mergeAnnotations(FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
Merge all annotations and port annotations on two operations.
void replaceInstances(FModuleLike toModule, Operation *fromModule)
This deletes and replaces all instances of the "fromModule" with instances of the "toModule".
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...
void rewriteExtModuleNLAs(RenameMap &renameMap, StringAttr toName, StringAttr fromName)
void mergeRegions(RenameMap &renameMap, FModuleLike toModule, Region &toRegion, FModuleLike fromModule, Region &fromRegion)
void dedup(FModuleLike toModule, FModuleLike fromModule)
Remove the "fromModule", and replace all references to it with the "toModule".
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...
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.
void recordAnnotations(AnnoTarget target)
For a specific annotation target, record all the unique NLAs which target it in the targetMap.
NLATable * nlaTable
Cached nla table analysis.
hw::InnerSymAttr mergeInnerSymbols(RenameMap &renameMap, FModuleLike toModule, hw::InnerSymAttr toSym, hw::InnerSymAttr fromSym)
void cloneAnnotation(SmallVectorImpl< FlatSymbolRefAttr > &nlas, Annotation anno, ArrayRef< NamedAttribute > attributes, unsigned nonLocalIndex, SmallVectorImpl< Annotation > &newAnnotations)
Clone the annotation for each NLA in a list.
void recordSymRenames(RenameMap &renameMap, FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
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.
StringAttr nonLocalString
hw::InnerSymbolNamespace & getNamespace(Operation *module)
Get a cached namespace for a module.
SymbolTable & symbolTable
void mergeOps(RenameMap &renameMap, FModuleLike toModule, Operation *to, FModuleLike fromModule, Operation *from)
Recursively merge two operations.
DenseMap< Operation *, hw::InnerSymbolNamespace > moduleNamespaces
A module namespace cache.
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.
InstanceGraph & instanceGraph
void mergeBlocks(RenameMap &renameMap, FModuleLike toModule, Block &toBlock, FModuleLike fromModule, Block &fromBlock)
Recursively merge two blocks.
DenseMap< Attribute, llvm::SmallDenseSet< AnnoTarget > > targetMap
void copyAnnotations(FModuleLike toModule, AnnoTarget to, FModuleLike fromModule, AnnotationSet annos, SmallVectorImpl< Annotation > &newAnnotations, SmallPtrSetImpl< Attribute > &dontTouches)
Deduper(InstanceGraph &instanceGraph, SymbolTable &symbolTable, NLATable *nlaTable, CircuitOp circuit)
void addAnnotationContext(RenameMap &renameMap, FModuleOp toModule, FModuleOp fromModule)
Process all NLAs referencing the "from" module to point to the "to" module.
DenseMap< StringAttr, StringAttr > RenameMap
DenseMap< Attribute, Attribute > nlaCache
const hw::InnerSymbolTable & a
ModuleData(const hw::InnerSymbolTable &a, const hw::InnerSymbolTable &b)
const hw::InnerSymbolTable & b
LogicalResult check(InFlightDiagnostic &diag, igraph::InstanceOpInterface a, igraph::InstanceOpInterface b)
DenseSet< Attribute > nonessentialAttributes
std::string prettyPrint(Attribute attr)
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Block &aBlock, Operation *b, Block &bBlock)
LogicalResult check(InFlightDiagnostic &diag, const Twine &message, Operation *a, Type aType, Operation *b, Type bType)
StringAttr dedupGroupAttrName
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, DictionaryAttr aDict, Operation *b, DictionaryAttr bDict)
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Region &aRegion, Operation *b, Region &bRegion)
StringAttr portDirectionsAttr
LogicalResult check(InFlightDiagnostic &diag, const Twine &message, Operation *a, BundleType aType, Operation *b, BundleType bType)
LogicalResult check(InFlightDiagnostic &diag, ModuleData &data, Operation *a, Operation *b)
Equivalence(MLIRContext *context, InstanceGraph &instanceGraph)
LogicalResult check(InFlightDiagnostic &diag, Operation *a, mlir::DenseBoolArrayAttr aAttr, Operation *b, mlir::DenseBoolArrayAttr bAttr)
InstanceGraph & instanceGraph
void check(InFlightDiagnostic &diag, Operation *a, Operation *b)
A reference to a ModuleInfo that compares and hashes like it.
ModuleInfoRef(ModuleInfo *info)
std::vector< StringAttr > referredModuleNames
std::array< uint8_t, 32 > structuralHash
This struct contains constant string attributes shared across different threads.
StringAttr moduleNamesAttr
StringAttr moduleNameAttr
DenseSet< Attribute > nonessentialAttributes
StructuralHasherSharedConstants(MLIRContext *context)
void populateInnerSymIDTable(FModuleLike module)
Find all the ports and operations which may define an inner symbol operations and give each a unique ...
void update(Operation *op, DictionaryAttr dict)
Hash the top level attribute dictionary of the operation.
void update(const void *pointer)
DenseMap< void *, unsigned > idTable
void update(const std::pair< T, U > &pair)
void update(Operation *op)
DenseMap< StringAttr, std::pair< size_t, size_t > > innerSymIDTable
ModuleInfo getModuleInfo(FModuleLike module)
void update(size_t value)
void update(BundleType type)
unsigned getID(void *object)
void update(OpResult result)
void update(OpOperand &operand)
StructuralHasher(const StructuralHasherSharedConstants &constants)
void update(Region *region)
void update(Block *block)
std::vector< StringAttr > referredModuleNames
void update(TypeID typeID)
const StructuralHasherSharedConstants & constants
std::pair< size_t, size_t > getInnerSymID(StringAttr name)
unsigned finalizeID(void *object)
void update(mlir::OperationName name)
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)
static unsigned getHashValue(const ModuleInfoRef &ref)