23#include "mlir/IR/Dominance.h"
24#include "mlir/IR/ImplicitLocOpBuilder.h"
25#include "mlir/IR/Threading.h"
26#include "mlir/Pass/Pass.h"
27#include "llvm/ADT/EquivalenceClasses.h"
28#include "llvm/ADT/SetVector.h"
29#include "llvm/ADT/TypeSwitch.h"
30#include "llvm/Support/Debug.h"
32#define DEBUG_TYPE "infer-resets"
36#define GEN_PASS_DEF_INFERRESETS
37#include "circt/Dialect/FIRRTL/Passes.h.inc"
41using circt::igraph::InstanceOpInterface;
44using llvm::BumpPtrAllocator;
46using llvm::SmallDenseSet;
47using llvm::SmallSetVector;
49using mlir::InferTypeOpInterface;
52using namespace firrtl;
61 if (
auto arg = dyn_cast<BlockArgument>(reset)) {
62 auto module = cast<FModuleOp>(arg.getParentRegion()->getParentOp());
63 return {
module.getPortNameAttr(arg.getArgNumber()), module};
65 auto *op = reset.getDefiningOp();
66 return {op->getAttrOfType<StringAttr>(
"name"),
67 op->getParentOfType<FModuleOp>()};
96 std::optional<unsigned> existingPort;
99 ResetDomain() =
default;
102 ResetDomain(Value rootReset)
103 : rootReset(rootReset), resetName(
getResetName(rootReset)),
104 resetType(rootReset.getType()) {}
107 explicit operator bool()
const {
return static_cast<bool>(rootReset); }
111inline bool operator==(
const ResetDomain &a,
const ResetDomain &b) {
112 return (a.isTop == b.isTop && a.resetName == b.resetName &&
113 a.resetType == b.resetType);
115inline bool operator!=(
const ResetDomain &a,
const ResetDomain &b) {
124 auto it = cache.find(type);
125 if (it != cache.end())
127 auto nullBit = [&]() {
129 builder, UIntType::get(builder.getContext(), 1,
true),
134 .
Case<ClockType>([&](
auto type) {
135 return AsClockPrimOp::create(builder, nullBit());
137 .Case<AsyncResetType>([&](
auto type) {
138 return AsAsyncResetPrimOp::create(builder, nullBit());
140 .Case<SIntType, UIntType>([&](
auto type) {
141 return ConstantOp::create(
142 builder, type, APInt::getZero(type.getWidth().value_or(1)));
144 .Case<FEnumType>([&](
auto type) -> Value {
147 if (type.getNumElements() != 0 &&
148 type.getElement(0).value.getValue().isZero()) {
149 const auto &element = type.getElement(0);
151 return FEnumCreateOp::create(builder, type, element.name, value);
153 auto value = ConstantOp::create(builder,
154 UIntType::get(builder.getContext(),
157 APInt::getZero(type.getBitWidth()));
158 return BitCastOp::create(builder, type, value);
160 .Case<BundleType>([&](
auto type) {
161 auto wireOp = WireOp::create(builder, type);
162 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
163 auto fieldType = type.getElementTypePreservingConst(i);
166 SubfieldOp::create(builder, fieldType, wireOp.getResult(), i);
169 return wireOp.getResult();
171 .Case<FVectorType>([&](
auto type) {
172 auto wireOp = WireOp::create(builder, type);
174 builder, type.getElementTypePreservingConst(), cache);
175 for (
unsigned i = 0, e = type.getNumElements(); i < e; ++i) {
176 auto acc = SubindexOp::create(builder, zero.getType(),
177 wireOp.getResult(), i);
180 return wireOp.getResult();
182 .Case<ResetType, AnalogType>(
183 [&](
auto type) {
return InvalidValueOp::create(builder, type); })
185 llvm_unreachable(
"switch handles all types");
188 cache.insert({type, value});
205 Value reset, Value resetValue) {
209 bool resetValueUsed =
false;
211 for (
auto &use : target.getUses()) {
212 Operation *useOp = use.getOwner();
213 builder.setInsertionPoint(useOp);
214 TypeSwitch<Operation *>(useOp)
217 .Case<ConnectOp, MatchingConnectOp>([&](
auto op) {
218 if (op.getDest() != target)
220 LLVM_DEBUG(llvm::dbgs() <<
" - Insert mux into " << op <<
"\n");
222 MuxPrimOp::create(builder, reset, resetValue, op.getSrc());
223 op.getSrcMutable().assign(muxOp);
224 resetValueUsed =
true;
227 .Case<SubfieldOp>([&](
auto op) {
229 SubfieldOp::create(builder, resetValue, op.getFieldIndexAttr());
231 resetValueUsed =
true;
233 resetSubValue.erase();
236 .Case<SubindexOp>([&](
auto op) {
238 SubindexOp::create(builder, resetValue, op.getIndexAttr());
240 resetValueUsed =
true;
242 resetSubValue.erase();
245 .Case<SubaccessOp>([&](
auto op) {
246 if (op.getInput() != target)
249 SubaccessOp::create(builder, resetValue, op.getIndex());
251 resetValueUsed =
true;
253 resetSubValue.erase();
256 return resetValueUsed;
271 bool operator<(
const ResetSignal &other)
const {
return field < other.field; }
272 bool operator==(
const ResetSignal &other)
const {
273 return field == other.field;
275 bool operator!=(
const ResetSignal &other)
const {
return !(*
this == other); }
295using ResetDrives = SmallVector<ResetDrive, 1>;
298using ResetNetwork = llvm::iterator_range<
299 llvm::EquivalenceClasses<ResetSignal>::member_iterator>;
302enum class ResetKind { Async, Sync };
304static StringRef resetKindToStringRef(
const ResetKind &kind) {
306 case ResetKind::Async:
308 case ResetKind::Sync:
311 llvm_unreachable(
"unhandled reset kind");
327 static bool isEqual(
const ResetSignal &lhs,
const ResetSignal &rhs) {
336 case ResetKind::Async:
337 return os <<
"async";
338 case ResetKind::Sync:
448struct InferResetsPass
449 :
public circt::firrtl::impl::InferResetsBase<InferResetsPass> {
450 void runOnOperation()
override;
451 void runOnOperationInner();
454 using InferResetsBase::InferResetsBase;
455 InferResetsPass(
const InferResetsPass &other) : InferResetsBase(other) {}
460 void traceResets(CircuitOp circuit);
461 void traceResets(InstanceOp inst);
462 void traceResets(Value dst, Value src, Location loc);
463 void traceResets(Value value);
464 void traceResets(Type dstType, Value dst,
unsigned dstID, Type srcType,
465 Value src,
unsigned srcID, Location loc);
467 LogicalResult inferAndUpdateResets();
468 FailureOr<ResetKind> inferReset(ResetNetwork net);
469 LogicalResult updateReset(ResetNetwork net, ResetKind kind);
475 LogicalResult collectAnnos(CircuitOp circuit);
481 FailureOr<std::optional<Value>> collectAnnos(FModuleOp module);
483 LogicalResult buildDomains(CircuitOp circuit);
484 void buildDomains(FModuleOp module,
const InstancePath &instPath,
486 unsigned indent = 0);
488 LogicalResult determineImpl();
489 LogicalResult determineImpl(FModuleOp module, ResetDomain &domain);
491 LogicalResult implementFullReset();
492 LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain);
493 void implementFullReset(Operation *op, FModuleOp module, Value actualReset);
495 LogicalResult verifyNoAbstractReset();
501 ResetNetwork getResetNetwork(ResetSignal signal) {
502 return llvm::make_range(resetClasses.findLeader(signal),
503 resetClasses.member_end());
507 ResetDrives &getResetDrives(ResetNetwork net) {
508 return resetDrives[*net.begin()];
513 ResetSignal guessRoot(ResetNetwork net);
514 ResetSignal guessRoot(ResetSignal signal) {
515 return guessRoot(getResetNetwork(signal));
522 llvm::EquivalenceClasses<ResetSignal> resetClasses;
525 DenseMap<ResetSignal, ResetDrives> resetDrives;
530 DenseMap<Operation *, Value> annotatedResets;
534 MapVector<FModuleOp, SmallVector<std::pair<ResetDomain, InstancePath>, 1>>
541 std::unique_ptr<InstancePathCache> instancePathCache;
545void InferResetsPass::runOnOperation() {
546 runOnOperationInner();
547 resetClasses = llvm::EquivalenceClasses<ResetSignal>();
549 annotatedResets.clear();
551 instancePathCache.reset(
nullptr);
552 markAnalysesPreserved<InstanceGraph>();
555void InferResetsPass::runOnOperationInner() {
556 instanceGraph = &getAnalysis<InstanceGraph>();
557 instancePathCache = std::make_unique<InstancePathCache>(*instanceGraph);
560 traceResets(getOperation());
563 if (failed(inferAndUpdateResets()))
564 return signalPassFailure();
567 if (failed(collectAnnos(getOperation())))
568 return signalPassFailure();
571 if (failed(buildDomains(getOperation())))
572 return signalPassFailure();
575 if (failed(determineImpl()))
576 return signalPassFailure();
579 if (failed(implementFullReset()))
580 return signalPassFailure();
583 if (failed(verifyNoAbstractReset()))
584 return signalPassFailure();
587ResetSignal InferResetsPass::guessRoot(ResetNetwork net) {
588 ResetDrives &drives = getResetDrives(net);
589 ResetSignal bestSignal = *net.begin();
590 unsigned bestNumDrives = -1;
592 for (
auto signal : net) {
594 if (isa_and_nonnull<InvalidValueOp>(
595 signal.field.getValue().getDefiningOp()))
600 unsigned numDrives = 0;
601 for (
auto &drive : drives)
602 if (drive.dst == signal)
608 if (numDrives < bestNumDrives) {
609 bestNumDrives = numDrives;
628 .
Case<BundleType>([](
auto type) {
630 for (
auto e : type.getElements())
635 [](
auto type) {
return getMaxFieldID(type.getElementType()) + 1; })
636 .Default([](
auto) {
return 0; });
640 assert(index < type.getNumElements());
642 for (
unsigned i = 0; i < index; ++i)
650 assert(type.getNumElements() &&
"Bundle must have >0 fields");
652 for (
const auto &e : llvm::enumerate(type.getElements())) {
654 if (fieldID < numSubfields)
656 fieldID -= numSubfields;
658 assert(
false &&
"field id outside bundle");
664 if (oldType.isGround()) {
670 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
678 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
679 if (vectorType.getNumElements() == 0)
696 if (
auto arg = dyn_cast<BlockArgument>(value)) {
697 auto module = cast<FModuleOp>(arg.getOwner()->getParentOp());
698 string +=
module.getPortName(arg.getArgNumber());
702 auto *op = value.getDefiningOp();
703 return TypeSwitch<Operation *, bool>(op)
704 .Case<InstanceOp, MemOp>([&](
auto op) {
705 string += op.getName();
707 string += op.getPortName(cast<OpResult>(value).getResultNumber());
710 .Case<WireOp, NodeOp, RegOp, RegResetOp>([&](
auto op) {
711 string += op.getName();
714 .Default([](
auto) {
return false; });
718 SmallString<64> name;
723 auto type = value.getType();
726 if (
auto bundleType = type_dyn_cast<BundleType>(type)) {
729 auto &element = bundleType.getElements()[index];
732 string += element.name.getValue();
735 localID = localID -
getFieldID(bundleType, index);
736 }
else if (
auto vecType = type_dyn_cast<FVectorType>(type)) {
739 type = vecType.getElementType();
746 llvm_unreachable(
"unsupported type");
758 return TypeSwitch<Type, bool>(type)
760 return type.getRecursiveTypeProperties().hasUninferredReset;
762 .Default([](
auto) {
return false; });
769void InferResetsPass::traceResets(CircuitOp circuit) {
771 llvm::dbgs() <<
"\n";
772 debugHeader(
"Tracing uninferred resets") <<
"\n\n";
775 SmallVector<std::pair<FModuleOp, SmallVector<Operation *>>> moduleToOps;
777 for (
auto module : circuit.getOps<FModuleOp>())
778 moduleToOps.push_back({module, {}});
781 getAnalysis<hw::InnerSymbolTableCollection>()};
783 mlir::parallelForEach(circuit.getContext(), moduleToOps, [](
auto &e) {
784 e.first.walk([&](Operation *op) {
788 op->getResultTypes(),
789 [](mlir::Type type) { return typeContainsReset(type); }) ||
790 llvm::any_of(op->getOperandTypes(), typeContainsReset))
791 e.second.push_back(op);
795 for (
auto &[_, ops] : moduleToOps)
796 for (auto *op : ops) {
797 TypeSwitch<Operation *>(op)
798 .Case<FConnectLike>([&](
auto op) {
799 traceResets(op.getDest(), op.getSrc(), op.getLoc());
801 .Case<InstanceOp>([&](
auto op) { traceResets(op); })
802 .Case<RefSendOp>([&](
auto op) {
804 traceResets(op.getType().getType(), op.getResult(), 0,
805 op.getBase().getType().getPassiveType(), op.getBase(),
808 .Case<RefResolveOp>([&](
auto op) {
810 traceResets(op.getType(), op.getResult(), 0,
811 op.getRef().getType().getType(), op.getRef(), 0,
814 .Case<Forceable>([&](Forceable op) {
815 if (
auto node = dyn_cast<NodeOp>(op.getOperation()))
816 traceResets(node.getResult(), node.getInput(), node.getLoc());
818 if (op.isForceable())
819 traceResets(op.getDataType(), op.getData(), 0, op.getDataType(),
820 op.getDataRef(), 0, op.getLoc());
822 .Case<RWProbeOp>([&](RWProbeOp op) {
823 auto ist = irn.lookup(op.getTarget());
826 auto baseType = op.getType().getType();
827 traceResets(baseType, op.getResult(), 0, baseType.getPassiveType(),
828 ref.getValue(), ref.getFieldID(), op.getLoc());
830 .Case<UninferredResetCastOp, ConstCastOp, RefCastOp>([&](
auto op) {
831 traceResets(op.getResult(), op.getInput(), op.getLoc());
833 .Case<InvalidValueOp>([&](
auto op) {
842 auto type = op.getType();
845 LLVM_DEBUG(llvm::dbgs() <<
"Uniquify " << op <<
"\n");
846 ImplicitLocOpBuilder builder(op->getLoc(), op);
848 llvm::make_early_inc_range(
llvm::drop_begin(op->getUses()))) {
854 auto newOp = InvalidValueOp::create(builder, type);
859 .Case<SubfieldOp>([&](
auto op) {
862 BundleType bundleType = op.getInput().getType();
863 auto index = op.getFieldIndex();
864 traceResets(op.getType(), op.getResult(), 0,
865 bundleType.getElements()[index].type, op.getInput(),
869 .Case<SubindexOp, SubaccessOp>([&](
auto op) {
882 FVectorType vectorType = op.getInput().getType();
883 traceResets(op.getType(), op.getResult(), 0,
884 vectorType.getElementType(), op.getInput(),
888 .Case<RefSubOp>([&](RefSubOp op) {
890 auto aggType = op.getInput().getType().getType();
891 uint64_t fieldID = TypeSwitch<FIRRTLBaseType, uint64_t>(aggType)
892 .Case<FVectorType>([](
auto type) {
895 .Case<BundleType>([&](
auto type) {
898 traceResets(op.getType(), op.getResult(), 0,
899 op.getResult().getType(), op.getInput(), fieldID,
907void InferResetsPass::traceResets(InstanceOp inst) {
909 auto module = inst.getReferencedModule<FModuleOp>(*instanceGraph);
912 LLVM_DEBUG(llvm::dbgs() <<
"Visiting instance " << inst.getName() <<
"\n");
915 for (
const auto &it :
llvm::enumerate(inst.getResults())) {
916 auto dir =
module.getPortDirection(it.index());
917 Value dstPort =
module.getArgument(it.index());
918 Value srcPort = it.value();
919 if (dir == Direction::Out)
920 std::swap(dstPort, srcPort);
921 traceResets(dstPort, srcPort, it.value().getLoc());
927void InferResetsPass::traceResets(Value dst, Value src, Location loc) {
929 traceResets(dst.getType(), dst, 0, src.getType(), src, 0, loc);
934void InferResetsPass::traceResets(Type dstType, Value dst,
unsigned dstID,
935 Type srcType, Value src,
unsigned srcID,
937 if (
auto dstBundle = type_dyn_cast<BundleType>(dstType)) {
938 auto srcBundle = type_cast<BundleType>(srcType);
939 for (
unsigned dstIdx = 0, e = dstBundle.getNumElements(); dstIdx < e;
941 auto dstField = dstBundle.getElements()[dstIdx].name;
942 auto srcIdx = srcBundle.getElementIndex(dstField);
945 auto &dstElt = dstBundle.getElements()[dstIdx];
946 auto &srcElt = srcBundle.getElements()[*srcIdx];
948 traceResets(srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
949 dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
952 traceResets(dstElt.type, dst, dstID +
getFieldID(dstBundle, dstIdx),
953 srcElt.type, src, srcID +
getFieldID(srcBundle, *srcIdx),
960 if (
auto dstVector = type_dyn_cast<FVectorType>(dstType)) {
961 auto srcVector = type_cast<FVectorType>(srcType);
962 auto srcElType = srcVector.getElementType();
963 auto dstElType = dstVector.getElementType();
976 traceResets(dstElType, dst, dstID +
getFieldID(dstVector), srcElType, src,
982 if (
auto dstRef = type_dyn_cast<RefType>(dstType)) {
983 auto srcRef = type_cast<RefType>(srcType);
984 return traceResets(dstRef.getType(), dst, dstID, srcRef.getType(), src,
989 auto dstBase = type_dyn_cast<FIRRTLBaseType>(dstType);
990 auto srcBase = type_dyn_cast<FIRRTLBaseType>(srcType);
991 if (!dstBase || !srcBase)
993 if (!type_isa<ResetType>(dstBase) && !type_isa<ResetType>(srcBase))
998 LLVM_DEBUG(llvm::dbgs() <<
"Visiting driver '" << dstField <<
"' = '"
999 << srcField <<
"' (" << dstType <<
" = " << srcType
1005 ResetSignal dstLeader =
1006 *resetClasses.findLeader(resetClasses.insert({dstField, dstBase}));
1007 ResetSignal srcLeader =
1008 *resetClasses.findLeader(resetClasses.insert({srcField, srcBase}));
1011 ResetSignal unionLeader = *resetClasses.unionSets(dstLeader, srcLeader);
1012 assert(unionLeader == dstLeader || unionLeader == srcLeader);
1017 if (dstLeader != srcLeader) {
1018 auto &unionDrives = resetDrives[unionLeader];
1019 auto mergedDrivesIt =
1020 resetDrives.find(unionLeader == dstLeader ? srcLeader : dstLeader);
1021 if (mergedDrivesIt != resetDrives.end()) {
1022 unionDrives.append(mergedDrivesIt->second);
1023 resetDrives.erase(mergedDrivesIt);
1029 resetDrives[unionLeader].push_back(
1030 {{dstField, dstBase}, {srcField, srcBase}, loc});
1037LogicalResult InferResetsPass::inferAndUpdateResets() {
1039 llvm::dbgs() <<
"\n";
1042 for (
const auto &it : resetClasses) {
1043 if (!it->isLeader())
1045 ResetNetwork net = resetClasses.members(*it);
1048 auto kind = inferReset(net);
1053 if (failed(updateReset(net, *kind)))
1059FailureOr<ResetKind> InferResetsPass::inferReset(ResetNetwork net) {
1060 LLVM_DEBUG(llvm::dbgs() <<
"Inferring reset network with "
1061 << std::distance(net.begin(), net.end())
1065 unsigned asyncDrives = 0;
1066 unsigned syncDrives = 0;
1067 unsigned invalidDrives = 0;
1068 for (ResetSignal signal : net) {
1070 if (type_isa<AsyncResetType>(signal.type))
1072 else if (type_isa<UIntType>(signal.type))
1075 isa_and_nonnull<InvalidValueOp>(
1076 signal.field.getValue().getDefiningOp()))
1079 LLVM_DEBUG(llvm::dbgs() <<
"- Found " << asyncDrives <<
" async, "
1080 << syncDrives <<
" sync, " << invalidDrives
1081 <<
" invalid drives\n");
1084 if (asyncDrives == 0 && syncDrives == 0 && invalidDrives == 0) {
1085 ResetSignal root = guessRoot(net);
1086 auto diag = mlir::emitError(root.field.getValue().getLoc())
1087 <<
"reset network never driven with concrete type";
1088 for (ResetSignal signal : net)
1089 diag.attachNote(signal.field.
getLoc()) <<
"here: ";
1094 if (asyncDrives > 0 && syncDrives > 0) {
1095 ResetSignal root = guessRoot(net);
1096 bool majorityAsync = asyncDrives >= syncDrives;
1097 auto diag = mlir::emitError(root.field.getValue().getLoc())
1099 SmallString<32> fieldName;
1101 diag <<
" \"" << fieldName <<
"\"";
1102 diag <<
" simultaneously connected to async and sync resets";
1103 diag.attachNote(root.field.getValue().getLoc())
1104 <<
"majority of connections to this reset are "
1105 << (majorityAsync ?
"async" :
"sync");
1106 for (
auto &drive : getResetDrives(net)) {
1107 if ((type_isa<AsyncResetType>(drive.dst.type) && !majorityAsync) ||
1108 (type_isa<AsyncResetType>(drive.src.type) && !majorityAsync) ||
1109 (type_isa<UIntType>(drive.dst.type) && majorityAsync) ||
1110 (type_isa<UIntType>(drive.src.type) && majorityAsync))
1111 diag.attachNote(drive.loc)
1112 << (type_isa<AsyncResetType>(drive.src.type) ?
"async" :
"sync")
1121 auto kind = (asyncDrives ? ResetKind::Async : ResetKind::Sync);
1122 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred as " << kind <<
"\n");
1130LogicalResult InferResetsPass::updateReset(ResetNetwork net, ResetKind kind) {
1131 LLVM_DEBUG(llvm::dbgs() <<
"Updating reset network with "
1132 << std::distance(net.begin(), net.end())
1133 <<
" nodes to " << kind <<
"\n");
1137 if (kind == ResetKind::Async)
1138 resetType = AsyncResetType::get(&getContext());
1140 resetType = UIntType::get(&getContext(), 1);
1145 SmallSetVector<Operation *, 16> worklist;
1146 SmallDenseSet<Operation *> moduleWorklist;
1147 SmallDenseSet<std::pair<Operation *, Operation *>> extmoduleWorklist;
1148 for (
auto signal : net) {
1149 Value value = signal.field.getValue();
1150 if (!isa<BlockArgument>(value) &&
1151 !isa_and_nonnull<WireOp, RegOp, RegResetOp, InstanceOp, InvalidValueOp,
1152 ConstCastOp, RefCastOp, UninferredResetCastOp,
1153 RWProbeOp>(value.getDefiningOp()))
1155 if (updateReset(signal.field, resetType)) {
1156 for (
auto user : value.getUsers())
1157 worklist.insert(user);
1158 if (
auto blockArg = dyn_cast<BlockArgument>(value))
1159 moduleWorklist.insert(blockArg.getOwner()->getParentOp());
1160 else if (
auto instOp = value.getDefiningOp<InstanceOp>()) {
1161 if (
auto extmodule =
1162 instOp.getReferencedModule<FExtModuleOp>(*instanceGraph))
1163 extmoduleWorklist.insert({extmodule, instOp});
1164 }
else if (
auto uncast = value.getDefiningOp<UninferredResetCastOp>()) {
1165 uncast.replaceAllUsesWith(uncast.getInput());
1175 while (!worklist.empty()) {
1176 auto *wop = worklist.pop_back_val();
1177 SmallVector<Type, 2> types;
1178 if (
auto op = dyn_cast<InferTypeOpInterface>(wop)) {
1180 SmallVector<Type, 2> types;
1181 if (failed(op.inferReturnTypes(op->getContext(), op->getLoc(),
1182 op->getOperands(), op->getAttrDictionary(),
1183 op->getPropertiesStorage(),
1184 op->getRegions(), types)))
1189 for (
auto it :
llvm::zip(op->getResults(), types)) {
1190 auto newType = std::get<1>(it);
1191 if (std::get<0>(it).getType() == newType)
1193 std::get<0>(it).setType(newType);
1194 for (
auto *user : std::
get<0>(it).getUsers())
1195 worklist.insert(user);
1197 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << *op <<
"\n");
1198 }
else if (
auto uop = dyn_cast<UninferredResetCastOp>(wop)) {
1199 for (
auto *user : uop.getResult().getUsers())
1200 worklist.insert(user);
1201 uop.replaceAllUsesWith(uop.getInput());
1202 LLVM_DEBUG(llvm::dbgs() <<
"- Inferred " << uop <<
"\n");
1208 for (
auto *op : moduleWorklist) {
1209 auto module = dyn_cast<FModuleOp>(op);
1213 SmallVector<Attribute> argTypes;
1214 argTypes.reserve(module.getNumPorts());
1215 for (
auto arg : module.getArguments())
1216 argTypes.push_back(TypeAttr::
get(arg.getType()));
1218 module.setPortTypesAttr(ArrayAttr::get(op->getContext(), argTypes));
1219 LLVM_DEBUG(llvm::dbgs()
1220 <<
"- Updated type of module '" << module.getName() <<
"'\n");
1224 for (
auto pair : extmoduleWorklist) {
1225 auto module = cast<FExtModuleOp>(pair.first);
1226 auto instOp = cast<InstanceOp>(pair.second);
1228 SmallVector<Attribute> types;
1229 for (
auto type : instOp.getResultTypes())
1230 types.push_back(TypeAttr::
get(type));
1232 module.setPortTypesAttr(ArrayAttr::get(module->getContext(), types));
1233 LLVM_DEBUG(llvm::dbgs()
1234 <<
"- Updated type of extmodule '" << module.getName() <<
"'\n");
1244 if (oldType.isGround()) {
1250 if (
auto bundleType = type_dyn_cast<BundleType>(oldType)) {
1252 SmallVector<BundleType::BundleElement> fields(bundleType.begin(),
1255 fields[index].type, fieldID -
getFieldID(bundleType, index), fieldType);
1256 return BundleType::get(oldType.getContext(), fields, bundleType.
isConst());
1260 if (
auto vectorType = type_dyn_cast<FVectorType>(oldType)) {
1261 auto newType =
updateType(vectorType.getElementType(),
1262 fieldID -
getFieldID(vectorType), fieldType);
1263 return FVectorType::get(newType, vectorType.getNumElements(),
1264 vectorType.isConst());
1267 llvm_unreachable(
"unknown aggregate type");
1274 auto oldType = type_cast<FIRRTLType>(field.
getValue().getType());
1280 if (oldType == newType)
1282 LLVM_DEBUG(llvm::dbgs() <<
"- Updating '" << field <<
"' from " << oldType
1283 <<
" to " << newType <<
"\n");
1292LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) {
1294 llvm::dbgs() <<
"\n";
1295 debugHeader(
"Gather reset annotations") <<
"\n\n";
1297 SmallVector<std::pair<FModuleOp, std::optional<Value>>> results;
1298 for (
auto module : circuit.getOps<FModuleOp>())
1299 results.push_back({module, {}});
1301 if (failed(mlir::failableParallelForEach(
1302 circuit.getContext(), results, [&](
auto &moduleAndResult) {
1303 auto result = collectAnnos(moduleAndResult.first);
1306 moduleAndResult.second = *result;
1311 for (
auto [module, reset] : results)
1312 if (reset.has_value())
1313 annotatedResets.insert({module, *reset});
1317FailureOr<std::optional<Value>>
1318InferResetsPass::collectAnnos(FModuleOp module) {
1319 bool anyFailed =
false;
1320 SmallSetVector<std::pair<Annotation, Location>, 4> conflictingAnnos;
1324 bool ignore =
false;
1328 conflictingAnnos.insert({anno, module.getLoc()});
1333 module.emitError("''FullResetAnnotation' cannot target module; must
"
1334 "target port or wire/node instead
");
1342 // Consume any reset annotations on module ports.
1344 // Helper for checking annotations and determining the reset
1345 auto checkAnnotations = [&](Annotation anno, Value arg) {
1346 if (anno.isClass(fullResetAnnoClass)) {
1347 ResetKind expectedResetKind;
1348 if (auto rt = anno.getMember<StringAttr>("resetType
")) {
1350 expectedResetKind = ResetKind::Sync;
1351 } else if (rt == "async
") {
1352 expectedResetKind = ResetKind::Async;
1354 mlir::emitError(arg.getLoc(),
1355 "'FullResetAnnotation' requires resetType ==
'sync' "
1356 "|
'async', but got resetType ==
")
1362 mlir::emitError(arg.getLoc(),
1363 "'FullResetAnnotation' requires resetType ==
"
1364 "'sync' |
'async', but got no resetType
");
1368 // Check that the type is well-formed
1369 bool isAsync = expectedResetKind == ResetKind::Async;
1370 bool validUint = false;
1371 if (auto uintT = dyn_cast<UIntType>(arg.getType()))
1372 validUint = uintT.getWidth() == 1;
1373 if ((isAsync && !isa<AsyncResetType>(arg.getType())) ||
1374 (!isAsync && !validUint)) {
1375 auto kind = resetKindToStringRef(expectedResetKind);
1376 mlir::emitError(arg.getLoc(),
1377 "'FullResetAnnotation' with resetType ==
'")
1378 << kind << "' must target
" << kind << " reset, but targets
"
1385 conflictingAnnos.insert({anno, reset.getLoc()});
1389 if (anno.isClass(excludeFromFullResetAnnoClass)) {
1391 mlir::emitError(arg.getLoc(),
1392 "'ExcludeFromFullResetAnnotation' cannot
"
1393 "target port/wire/node; must target
module instead");
1401 Value arg =
module.getArgument(argNum);
1402 return checkAnnotations(anno, arg);
1408 module.getBody().walk([&](Operation *op) {
1410 if (!isa<WireOp, NodeOp>(op)) {
1411 if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass,
1412 excludeFromFullResetAnnoClass)) {
1415 "reset annotations must target module, port, or wire/node");
1423 auto arg = op->getResult(0);
1424 return checkAnnotations(anno, arg);
1433 if (!ignore && !reset) {
1434 LLVM_DEBUG(llvm::dbgs()
1435 <<
"No reset annotation for " << module.getName() <<
"\n");
1436 return std::optional<Value>();
1440 if (conflictingAnnos.size() > 1) {
1441 auto diag =
module.emitError("multiple reset annotations on module '")
1442 << module.getName() << "'";
1443 for (
auto &annoAndLoc : conflictingAnnos)
1444 diag.attachNote(annoAndLoc.second)
1445 <<
"conflicting " << annoAndLoc.first.getClassAttr() <<
":";
1451 llvm::dbgs() <<
"Annotated reset for " <<
module.getName() << ": ";
1453 llvm::dbgs() <<
"no domain\n";
1454 else if (
auto arg = dyn_cast<BlockArgument>(reset))
1455 llvm::dbgs() <<
"port " <<
module.getPortName(arg.getArgNumber()) << "\n";
1457 llvm::dbgs() <<
"wire "
1458 << reset.getDefiningOp()->getAttrOfType<StringAttr>(
"name")
1464 return std::optional<Value>(reset);
1476LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) {
1478 llvm::dbgs() <<
"\n";
1479 debugHeader(
"Build full reset domains") <<
"\n\n";
1483 auto &instGraph = getAnalysis<InstanceGraph>();
1484 auto module = dyn_cast<FModuleOp>(*instGraph.getTopLevelNode()->getModule());
1486 LLVM_DEBUG(llvm::dbgs()
1487 <<
"Skipping circuit because main module is no `firrtl.module`");
1490 buildDomains(module,
InstancePath{}, Value{}, instGraph);
1493 bool anyFailed =
false;
1494 for (
auto &it : domains) {
1495 auto module = cast<FModuleOp>(it.first);
1496 auto &domainConflicts = it.second;
1497 if (domainConflicts.size() <= 1)
1501 SmallDenseSet<Value> printedDomainResets;
1502 auto diag =
module.emitError("module '")
1504 << "' instantiated in different reset domains";
1505 for (
auto &it : domainConflicts) {
1506 ResetDomain &domain = it.first;
1507 const auto &path = it.second;
1508 auto inst = path.leaf();
1509 auto loc = path.empty() ?
module.getLoc() : inst.getLoc();
1510 auto ¬e = diag.attachNote(loc);
1514 note <<
"root instance";
1516 note <<
"instance '";
1519 [&](InstanceOpInterface inst) { note << inst.getInstanceName(); },
1520 [&]() { note <<
"/"; });
1526 if (domain.rootReset) {
1528 note <<
" reset domain rooted at '" << nameAndModule.first.getValue()
1529 <<
"' of module '" << nameAndModule.second.getName() <<
"'";
1532 if (printedDomainResets.insert(domain.rootReset).second) {
1533 diag.attachNote(domain.rootReset.getLoc())
1534 <<
"reset domain '" << nameAndModule.first.getValue()
1535 <<
"' of module '" << nameAndModule.second.getName()
1536 <<
"' declared here:";
1539 note <<
" no reset domain";
1542 return failure(anyFailed);
1545void InferResetsPass::buildDomains(FModuleOp module,
1550 llvm::dbgs().indent(indent * 2) <<
"Visiting ";
1551 if (instPath.
empty())
1552 llvm::dbgs() <<
"$root";
1554 llvm::dbgs() << instPath.
leaf().getInstanceName();
1555 llvm::dbgs() <<
" (" <<
module.getName() << ")\n";
1560 auto it = annotatedResets.find(module);
1561 if (it != annotatedResets.end()) {
1564 if (
auto localReset = it->second)
1565 domain = ResetDomain(localReset);
1566 domain.isTop =
true;
1567 }
else if (parentReset) {
1569 domain = ResetDomain(parentReset);
1575 auto &entries = domains[module];
1576 if (llvm::all_of(entries,
1577 [&](
const auto &entry) {
return entry.first != domain; }))
1578 entries.push_back({domain, instPath});
1581 for (
auto *record : *instGraph[module]) {
1582 auto submodule = dyn_cast<FModuleOp>(*record->getTarget()->getModule());
1586 instancePathCache->appendInstance(instPath, record->getInstance());
1587 buildDomains(submodule, childPath, domain.rootReset, instGraph, indent + 1);
1592LogicalResult InferResetsPass::determineImpl() {
1593 auto anyFailed =
false;
1595 llvm::dbgs() <<
"\n";
1596 debugHeader(
"Determine implementation") <<
"\n\n";
1598 for (
auto &it : domains) {
1599 auto module = cast<FModuleOp>(it.first);
1600 auto &domain = it.second.back().first;
1601 if (failed(determineImpl(module, domain)))
1604 return failure(anyFailed);
1622LogicalResult InferResetsPass::determineImpl(FModuleOp module,
1623 ResetDomain &domain) {
1627 LLVM_DEBUG(llvm::dbgs() <<
"Planning reset for " << module.getName() <<
"\n");
1632 LLVM_DEBUG(llvm::dbgs()
1633 <<
"- Rooting at local value " << domain.resetName <<
"\n");
1634 domain.localReset = domain.rootReset;
1635 if (
auto blockArg = dyn_cast<BlockArgument>(domain.rootReset))
1636 domain.existingPort = blockArg.getArgNumber();
1642 auto neededName = domain.resetName;
1643 auto neededType = domain.resetType;
1644 LLVM_DEBUG(llvm::dbgs() <<
"- Looking for existing port " << neededName
1646 auto portNames =
module.getPortNames();
1647 auto *portIt = llvm::find(portNames, neededName);
1650 if (portIt == portNames.end()) {
1651 LLVM_DEBUG(llvm::dbgs() <<
"- Creating new port " << neededName <<
"\n");
1652 domain.resetName = neededName;
1656 LLVM_DEBUG(llvm::dbgs() <<
"- Reusing existing port " << neededName <<
"\n");
1659 auto portNo = std::distance(portNames.begin(), portIt);
1660 auto portType =
module.getPortType(portNo);
1661 if (portType != neededType) {
1662 auto diag = emitError(module.getPortLocation(portNo),
"module '")
1663 <<
module.getName() << "' is in reset domain requiring port '"
1664 << domain.resetName.getValue() << "' to have type "
1665 << domain.resetType << ", but has type " << portType;
1666 diag.attachNote(domain.rootReset.getLoc()) <<
"reset domain rooted here";
1671 domain.existingPort = portNo;
1672 domain.localReset =
module.getArgument(portNo);
1681LogicalResult InferResetsPass::implementFullReset() {
1683 llvm::dbgs() <<
"\n";
1686 for (
auto &it : domains)
1687 if (failed(implementFullReset(cast<FModuleOp>(it.first),
1688 it.second.back().first)))
1698LogicalResult InferResetsPass::implementFullReset(FModuleOp module,
1699 ResetDomain &domain) {
1700 LLVM_DEBUG(llvm::dbgs() <<
"Implementing full reset for " << module.getName()
1705 LLVM_DEBUG(llvm::dbgs()
1706 <<
"- Skipping because module explicitly has no domain\n");
1711 auto *context =
module.getContext();
1713 annotations.addAnnotations(DictionaryAttr::get(
1714 context, NamedAttribute(StringAttr::get(context,
"class"),
1716 annotations.applyToOperation(module);
1719 auto actualReset = domain.localReset;
1720 if (!domain.localReset) {
1721 PortInfo portInfo{domain.resetName,
1725 domain.rootReset.getLoc()};
1726 module.insertPorts({{0, portInfo}});
1727 actualReset =
module.getArgument(0);
1728 LLVM_DEBUG(llvm::dbgs() <<
"- Inserted port " << domain.resetName <<
"\n");
1732 llvm::dbgs() <<
"- Using ";
1733 if (
auto blockArg = dyn_cast<BlockArgument>(actualReset))
1734 llvm::dbgs() <<
"port #" << blockArg.getArgNumber() <<
" ";
1736 llvm::dbgs() <<
"wire/node ";
1742 SmallVector<Operation *> opsToUpdate;
1743 module.walk([&](Operation *op) {
1744 if (isa<InstanceOp, RegOp, RegResetOp>(op))
1745 opsToUpdate.push_back(op);
1752 if (!isa<BlockArgument>(actualReset)) {
1753 mlir::DominanceInfo dom(module);
1758 auto *resetOp = actualReset.getDefiningOp();
1759 if (!opsToUpdate.empty() && !dom.dominates(resetOp, opsToUpdate[0])) {
1760 LLVM_DEBUG(llvm::dbgs()
1761 <<
"- Reset doesn't dominate all uses, needs to be moved\n");
1765 auto nodeOp = dyn_cast<NodeOp>(resetOp);
1766 if (nodeOp && !dom.dominates(nodeOp.getInput(), opsToUpdate[0])) {
1767 LLVM_DEBUG(llvm::dbgs()
1768 <<
"- Promoting node to wire for move: " << nodeOp <<
"\n");
1769 auto builder = ImplicitLocOpBuilder::atBlockBegin(nodeOp.getLoc(),
1770 nodeOp->getBlock());
1771 auto wireOp = WireOp::create(
1772 builder, nodeOp.getResult().getType(), nodeOp.getNameAttr(),
1773 nodeOp.getNameKindAttr(), nodeOp.getAnnotationsAttr(),
1774 nodeOp.getInnerSymAttr(), nodeOp.getForceableAttr());
1776 nodeOp->replaceAllUsesWith(wireOp);
1777 nodeOp->removeAttr(nodeOp.getInnerSymAttrName());
1781 nodeOp.setNameKind(NameKindEnum::DroppableName);
1782 nodeOp.setAnnotationsAttr(ArrayAttr::get(builder.getContext(), {}));
1783 builder.setInsertionPointAfter(nodeOp);
1784 emitConnect(builder, wireOp.getResult(), nodeOp.getResult());
1786 actualReset = wireOp.getResult();
1787 domain.localReset = wireOp.getResult();
1792 Block *targetBlock = dom.findNearestCommonDominator(
1793 resetOp->getBlock(), opsToUpdate[0]->getBlock());
1795 if (targetBlock != resetOp->getBlock())
1796 llvm::dbgs() <<
"- Needs to be moved to different block\n";
1805 auto getParentInBlock = [](Operation *op,
Block *block) {
1806 while (op && op->getBlock() != block)
1807 op = op->getParentOp();
1810 auto *resetOpInTarget = getParentInBlock(resetOp, targetBlock);
1811 auto *firstOpInTarget = getParentInBlock(opsToUpdate[0], targetBlock);
1817 if (resetOpInTarget->isBeforeInBlock(firstOpInTarget))
1818 resetOp->moveBefore(resetOpInTarget);
1820 resetOp->moveBefore(firstOpInTarget);
1825 for (
auto *op : opsToUpdate)
1826 implementFullReset(op, module, actualReset);
1833void InferResetsPass::implementFullReset(Operation *op, FModuleOp module,
1834 Value actualReset) {
1835 ImplicitLocOpBuilder builder(op->getLoc(), op);
1838 if (
auto instOp = dyn_cast<InstanceOp>(op)) {
1842 auto refModule = instOp.getReferencedModule<FModuleOp>(*instanceGraph);
1845 auto domainIt = domains.find(refModule);
1846 if (domainIt == domains.end())
1848 auto &domain = domainIt->second.back().first;
1851 LLVM_DEBUG(llvm::dbgs()
1852 <<
"- Update instance '" << instOp.getName() <<
"'\n");
1856 if (!domain.localReset) {
1857 LLVM_DEBUG(llvm::dbgs() <<
" - Adding new result as reset\n");
1858 auto newInstOp = instOp.cloneWithInsertedPortsAndReplaceUses(
1860 {domain.resetName, type_cast<FIRRTLBaseType>(actualReset.getType()),
1862 instReset = newInstOp.getResult(0);
1863 instanceGraph->replaceInstance(instOp, newInstOp);
1866 }
else if (domain.existingPort.has_value()) {
1867 auto idx = *domain.existingPort;
1868 instReset = instOp.getResult(idx);
1869 LLVM_DEBUG(llvm::dbgs() <<
" - Using result #" << idx <<
" as reset\n");
1879 assert(instReset && actualReset);
1880 builder.setInsertionPointAfter(instOp);
1886 if (
auto regOp = dyn_cast<RegOp>(op)) {
1887 LLVM_DEBUG(llvm::dbgs() <<
"- Adding full reset to " << regOp <<
"\n");
1889 auto newRegOp = RegResetOp::create(
1890 builder, regOp.getResult().getType(), regOp.getClockVal(), actualReset,
1891 zero, regOp.getNameAttr(), regOp.getNameKindAttr(),
1892 regOp.getAnnotations(), regOp.getInnerSymAttr(),
1893 regOp.getForceableAttr());
1894 regOp.getResult().replaceAllUsesWith(newRegOp.getResult());
1895 if (regOp.getForceable())
1896 regOp.getRef().replaceAllUsesWith(newRegOp.getRef());
1902 if (
auto regOp = dyn_cast<RegResetOp>(op)) {
1905 if (type_isa<AsyncResetType>(regOp.getResetSignal().getType()) ||
1906 type_isa<UIntType>(actualReset.getType())) {
1907 LLVM_DEBUG(llvm::dbgs() <<
"- Skipping (has reset) " << regOp <<
"\n");
1910 if (failed(regOp.verifyInvariants()))
1911 signalPassFailure();
1914 LLVM_DEBUG(llvm::dbgs() <<
"- Updating reset of " << regOp <<
"\n");
1916 auto reset = regOp.getResetSignal();
1917 auto value = regOp.getResetValue();
1923 builder.setInsertionPointAfterValue(regOp.getResult());
1924 auto mux = MuxPrimOp::create(builder, reset, value, regOp.getResult());
1928 builder.setInsertionPoint(regOp);
1930 regOp.getResetSignalMutable().assign(actualReset);
1931 regOp.getResetValueMutable().assign(zero);
1935LogicalResult InferResetsPass::verifyNoAbstractReset() {
1936 bool hasAbstractResetPorts =
false;
1937 for (FModuleLike module :
1938 getOperation().
getBodyBlock()->getOps<FModuleLike>()) {
1939 for (
PortInfo port : module.getPorts()) {
1940 if (getBaseOfType<ResetType>(port.type)) {
1941 auto diag = emitError(port.loc)
1942 <<
"a port \"" << port.getName()
1943 <<
"\" with abstract reset type was unable to be "
1944 "inferred by InferResets (is this a top-level port?)";
1945 diag.attachNote(module->getLoc())
1946 <<
"the module with this uninferred reset port was defined here";
1947 hasAbstractResetPorts =
true;
1952 if (hasAbstractResetPorts)
assert(baseType &&"element must be base type")
static Value createZeroValue(ImplicitLocOpBuilder &builder, FIRRTLBaseType type, SmallDenseMap< FIRRTLBaseType, Value > &cache)
Construct a zero value of the given type using the given builder.
static unsigned getFieldID(BundleType type, unsigned index)
static unsigned getIndexForFieldID(BundleType type, unsigned fieldID)
static FIRRTLBaseType updateType(FIRRTLBaseType oldType, unsigned fieldID, FIRRTLBaseType fieldType)
Update the type of a single field within a type.
static bool isUselessVec(FIRRTLBaseType oldType, unsigned fieldID)
static StringAttr getResetName(Value reset)
Return the name of a reset.
static bool insertResetMux(ImplicitLocOpBuilder &builder, Value target, Value reset, Value resetValue)
Helper function that inserts reset multiplexer into all ConnectOps with the given target.
static bool typeContainsReset(Type type)
Check whether a type contains a ResetType.
static bool getDeclName(Value value, SmallString< 32 > &string)
static unsigned getMaxFieldID(FIRRTLBaseType type)
static std::pair< StringAttr, FModuleOp > getResetNameAndModule(Value reset)
Return the name and parent module of a reset.
static Location getLoc(DefSlot slot)
static Block * getBodyBlock(FModuleLike mod)
static InstancePath empty
This class represents a reference to a specific field or element of an aggregate value.
unsigned getFieldID() const
Get the field ID of this FieldRef, which is a unique identifier mapped to a specific field in a bundl...
Value getValue() const
Get the Value which created this location.
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.
static bool removePortAnnotations(Operation *module, llvm::function_ref< bool(unsigned, Annotation)> predicate)
Remove all port annotations from a module or extmodule for which predicate returns true.
This class provides a read-only projection of an annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
FIRRTLBaseType getConstType(bool isConst) const
Return a 'const' or non-'const' version of this type.
bool isConst() const
Returns true if this is a 'const' type that can only hold compile-time constant values.
This class implements the same functionality as TypeSwitch except that it uses firrtl::type_dyn_cast ...
FIRRTLTypeSwitch< T, ResultT > & Case(CallableT &&caseFn)
Add a case on the given type.
This graph tracks modules and where they are instantiated.
An instance path composed of a series of instances.
InstanceOpInterface leaf() const
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
constexpr const char * excludeFromFullResetAnnoClass
Annotation that marks a module as not belonging to any reset domain.
FieldRef getFieldRefForTarget(const hw::InnerSymTarget &ist)
Get FieldRef pointing to the specified inner symbol target, which must be valid.
FIRRTLBaseType getBaseType(Type type)
If it is a base type, return it as is.
FIRRTLType mapBaseType(FIRRTLType type, function_ref< FIRRTLBaseType(FIRRTLBaseType)> fn)
Return a FIRRTLType with its base type component mutated by the given function.
constexpr const char * fullResetAnnoClass
Annotation that marks a reset (port or wire) and domain.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs)
Emit a connect between two values.
static bool operator==(const ModulePort &a, const ModulePort &b)
static llvm::hash_code hash_value(const ModulePort &port)
bool operator<(const DictEntry &entry, const DictEntry &other)
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.
bool operator!=(uint64_t a, const FVInt &b)
This holds the name and type that describes the module's ports.
This class represents the namespace in which InnerRef's can be resolved.
A data structure that caches and provides paths to module instances in the IR.
static ResetSignal getEmptyKey()
static ResetSignal getTombstoneKey()
static bool isEqual(const ResetSignal &lhs, const ResetSignal &rhs)
static unsigned getHashValue(const ResetSignal &x)