179#include "mlir/IR/IRMapping.h"
180#include "mlir/IR/Threading.h"
181#include "mlir/Pass/Pass.h"
182#include "llvm/ADT/DenseMap.h"
183#include "llvm/ADT/MapVector.h"
184#include "llvm/ADT/STLExtras.h"
185#include "llvm/Support/Debug.h"
186#include "llvm/Support/FormatVariadic.h"
187#include "llvm/Support/TrailingObjects.h"
189#define DEBUG_TYPE "firrtl-inliner"
193#define GEN_PASS_DEF_INLINER
194#include "circt/Dialect/FIRRTL/Passes.h.inc"
198using namespace circt;
199using namespace firrtl;
201using hw::InnerRefAttr;
222static InstanceOp getInlinableInstance(Operation *op) {
223 return dyn_cast_or_null<InstanceOp>(op);
235 bool underFlatten : 1;
241 bool hasUnflattenedPath : 1;
250 bool keepsChildrenInstantiated()
const {
251 return hasUnflattenedPath && !hasFlatten;
255 bool mayBeFlattened()
const {
return underFlatten || hasFlatten; }
259 : hasInline(
false), hasFlatten(
false), underFlatten(
false),
260 hasUnflattenedPath(
false), isLive(
false) {}
265 using ModuleClassification = DenseMap<Operation *, ModuleInfo>;
268 ModuleClassification classification;
269 SmallVector<FModuleOp, 0> schedule;
271 InliningFacts(ModuleClassification &&classification,
272 SmallVector<FModuleOp, 0> &&schedule)
273 : classification(std::move(classification)),
274 schedule(std::move(schedule)) {}
280 static FailureOr<InliningFacts> compute(CircuitOp circuit,
282 const mlir::SymbolTable &symbolTable);
285 const ModuleInfo &getModuleInfo(FModuleLike fmod)
const {
286 assert(fmod &&
"queried with null fmodulelike");
287 auto it = classification.find(fmod);
288 assert(it != classification.end() &&
"module not found");
293 std::optional<ModuleInfo> getModuleInfoIfPresent(Operation *op)
const {
294 auto it = classification.find(op);
295 if (it == classification.end())
302 bool isLive(FModuleLike mod)
const {
return getModuleInfo(mod).isLive; }
303 bool hasInline(FModuleLike mod)
const {
return getModuleInfo(mod).hasInline; }
304 bool hasFlatten(FModuleLike mod)
const {
305 return getModuleInfo(mod).hasFlatten;
309 bool isKnownLive(Operation *op)
const {
310 auto ret = getModuleInfoIfPresent(op);
311 return ret && ret->isLive;
317 ArrayRef<FModuleOp> getSchedule()
const {
return schedule; }
319#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
321 auto &os = llvm::dbgs();
322 auto printOne = [&os](StringRef name,
ModuleInfo info) {
323 os <<
"@" << name <<
": inline=" << info.hasInline
324 <<
" flatten=" << info.hasFlatten
325 <<
" underFlatten=" << info.underFlatten
326 <<
" unflattenedPath=" << info.hasUnflattenedPath
327 <<
" live=" << info.isLive <<
"\n";
330 for (
auto module : schedule)
331 printOne(module.getModuleName(), classification.lookup(module));
332 SmallVector<std::pair<StringRef, ModuleInfo>> rest;
333 for (
auto &[op, info] : classification)
334 if (!isa<FModuleOp>(op))
335 rest.emplace_back(cast<FModuleLike>(op).getModuleName(), info);
336 llvm::sort(rest, llvm::less_first());
337 for (
auto &[name, info] : rest)
338 printOne(name, info);
345FailureOr<InliningFacts>
346InliningFacts::compute(CircuitOp circuit,
InstanceGraph &instanceGraph,
347 const mlir::SymbolTable &symbolTable) {
348 InliningFacts::ModuleClassification classification;
349 SmallVector<FModuleOp, 0> schedule;
352 auto *ctx = circuit.getContext();
353 auto inlineAnnoClassAttr = StringAttr::get(ctx, inlineAnnoClass);
354 auto flattenAnnoClassAttr = StringAttr::get(ctx, flattenAnnoClass);
359 auto markSymbolUses = [&](Operation &op) -> LogicalResult {
361 auto symbolUses = SymbolTable::getSymbolUses(&op);
363 return op.emitError(
"cannot analyze symbol uses of this operation");
364 for (
const auto &use : *symbolUses) {
365 auto root = use.getSymbolRef().getRootReference();
366 if (
auto moduleLike = symbolTable.lookup<FModuleLike>(root)) {
367 auto &
info = classification[moduleLike];
369 info.hasUnflattenedPath =
true;
381 if (failed(markSymbolUses(*circuit.getOperation())))
384 for (
auto &op : circuit.getOps()) {
386 if (
auto module = dyn_cast<FModuleLike>(op)) {
387 auto &
info = classification[module];
389 info.hasInline = anno.hasAnnotation(inlineAnnoClassAttr);
390 info.hasFlatten = anno.hasAnnotation(flattenAnnoClassAttr);
394 if (!isa<FModuleOp>(module) && (
info.hasInline ||
info.hasFlatten))
395 return emitError(module.getLoc()) <<
"inline/flatten annotations are "
396 "only valid on a 'firrtl.module'";
399 auto instantiators = instanceGraph.
lookup(module)->
uses();
400 auto opaqueRecIt = llvm::find_if(instantiators, [](
InstanceRecord *rec) {
403 bool hasOpaqueUse = opaqueRecIt != instantiators.end();
405 if (!cast<mlir::SymbolOpInterface>(module.getOperation())
406 .canDiscardOnUseEmpty() ||
409 info.hasUnflattenedPath =
true;
411 if (
info.hasInline && hasOpaqueUse) {
412 auto diag = mlir::emitWarning(module.getLoc())
413 <<
"module marked inline is also instantiated by an "
414 "operation that cannot be inlined; it is inlined only "
415 "into its 'firrtl.instance' parents and retained";
416 diag.attachNote((*opaqueRecIt)->getInstance()->getLoc())
417 <<
"instantiated here";
425 if (isa<hw::HierPathOp>(op))
428 if (failed(markSymbolUses(op)))
434 auto *mod = node.
getModule().getOperation();
435 assert(isa<FModuleLike>(mod) &&
"instance graph contains non-fmodulelike");
438 if (
auto fmod = dyn_cast<FModuleOp>(mod))
439 schedule.push_back(fmod);
440 auto &modInfo = classification[mod];
443 if (!modInfo.hasUnflattenedPath && !modInfo.underFlatten)
446 for (
auto *edge : node) {
447 auto *childMod = edge->getTarget()->getModule().getOperation();
448 auto &childInfo = classification[childMod];
449 bool isRegularModule = isa<FModuleOp>(childMod);
451 (isRegularModule || !(childInfo.hasInline || childInfo.hasFlatten)) &&
452 "non-fmoduleop with inline/flatten annotation");
453 if (isRegularModule && modInfo.mayBeFlattened())
454 childInfo.underFlatten =
true;
458 if (modInfo.keepsChildrenInstantiated() || !isRegularModule)
459 childInfo.hasUnflattenedPath =
true;
462 if (childInfo.hasUnflattenedPath &&
463 (!isRegularModule || !childInfo.hasInline))
464 childInfo.isLive =
true;
468 return InliningFacts(std::move(classification), std::move(schedule));
503class VirtualNLA final : llvm::TrailingObjects<VirtualNLA, SurvivingHop> {
504 friend TrailingObjects;
508 VirtualNLA(
unsigned id, StringAttr origSym, ArrayRef<SurvivingHop> path)
509 : numHops(path.size()), id(id), origSym(origSym) {
510 llvm::uninitialized_copy(path, getTrailingObjects());
521 StringAttr realizedSym;
524 bool wasUsed =
false;
526 static VirtualNLA *create(llvm::BumpPtrAllocator &alloc,
unsigned id,
527 StringAttr origSym, ArrayRef<SurvivingHop> path) {
531 assert(!path.empty() &&
"a VNLA always keeps its terminal hop (I8)");
532 size_t size = totalSizeToAlloc<SurvivingHop>(path.size());
533 auto *mem = alloc.Allocate(size,
alignof(VirtualNLA));
534 return new (mem) VirtualNLA(
id, origSym, path);
537 bool isLocal()
const {
return numHops <= 1; }
539 ArrayRef<SurvivingHop> getPath()
const {
540 return {getTrailingObjects(), numHops};
543 MutableArrayRef<SurvivingHop> getPathMutable() {
544 return {getTrailingObjects(), numHops};
547#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
548 LLVM_DUMP_METHOD
void dump()
const {
549 llvm::dbgs() << llvm::formatv(
" VirtualNLA {0}: origSym @{1}",
id,
552 llvm::dbgs() <<
" -> local\n";
554 llvm::dbgs() << llvm::formatv(
", hops: {0}\n", numHops);
555 for (
const auto &hop : getPath()) {
556 llvm::dbgs() << llvm::formatv(
557 " - {0}::{1} -> {2}::{3}\n", hop.origMod,
558 (hop.origSym ? hop.origSym.str() :
"*"), hop.finalMod,
559 (hop.finalSym ? hop.finalSym.str() :
"(TBD)"));
566static_assert(std::is_trivially_destructible_v<VirtualNLA>,
567 "VirtualNLA is arena-allocated; destructors never run");
572static bool vnlaIdLess(
const VirtualNLA *a,
const VirtualNLA *b) {
573 return a->id < b->id;
590 return mod == o.mod && inst == o.inst && sym == o.sym;
597struct TrimmedPathRef {
598 ArrayRef<PathHop> path;
599 llvm::hash_code hash;
601 static TrimmedPathRef
get(ArrayRef<PathHop> path) {
603 for (
const PathHop &hop : path)
605 hop.sym.getAsOpaquePointer());
615 NLAPlanner(CircuitOp circuit, SymbolTable &symbolTable,
617 : circuit(circuit), symbolTable(symbolTable),
618 instanceGraph(instanceGraph), facts(facts) {}
621#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
622 LLVM_DUMP_METHOD
void dump();
630 size_t endInnerSym = 0;
631 size_t endModule = 0;
636 VirtualNLA *createVNLA(StringAttr origSym, ArrayRef<SurvivingHop> path);
649 traceUpUntilSurviving(StringAttr rootModName, hw::HierPathOp diagAnchor,
650 SmallVectorImpl<SmallVector<PathHop>> &discoveredPaths);
656 processSinglePathContext(StringAttr origSym,
657 const SmallVectorImpl<PathHop> &absPath,
658 hw::HierPathOp diagAnchor);
675 size_t minimalRootIndex(ArrayRef<PathHop> upperPath, StringAttr rootMod);
687 Operation *resolveInstanceHop(StringAttr module, StringAttr innerSym);
690 SymbolTable &symbolTable;
692 const InliningFacts &facts;
696 DenseMap<StringAttr, DenseMap<StringAttr, Operation *>> instanceHopIndex;
699 llvm::BumpPtrAllocator alloc;
701 using VirtualNLAHandles = SmallVector<VirtualNLA *>;
706 DenseMap<Operation *, VirtualNLAHandles> pathRoutingTable;
709 DenseMap<StringAttr, ArrayRef<VirtualNLA *>> origToVNLAs;
711 SmallVector<VirtualNLA *> allVNLAs;
715 DenseMap<StringAttr, hw::HierPathOp> hierPathOps;
724 return static_cast<unsigned>(key.hash);
726 static bool isEqual(
const TrimmedPathRef &a,
const TrimmedPathRef &b) {
727 return a.hash == b.hash && a.path == b.path;
731LogicalResult NLAPlanner::run() {
739 for (
auto nla : circuit.getOps<
hw::HierPathOp>()) {
740 byRoot[nla.root()].push_back(nla);
741 hierPathOps[nla.getSymNameAttr()] = nla;
744 for (
auto &[origRoot, nlas] : byRoot) {
748 SmallVector<SmallVector<PathHop>> upperPaths;
749 if (failed(traceUpUntilSurviving(origRoot, nlas.front(), upperPaths)))
754 llvm::SmallDenseSet<TrimmedPathRef, 8> seenPaths;
755 for (
auto &upperPath : upperPaths) {
756 assert(minimalRootIndex(upperPath, origRoot) == 0 &&
757 "pruned climb leaked a trimmable path");
758 assert(seenPaths.insert(TrimmedPathRef::get(upperPath)).second &&
759 "pruned climb repeated a path");
764 for (
auto nla : nlas) {
765 auto origSym = nla.getSymNameAttr();
768 SmallVector<PathHop> nlaHops;
769 for (
auto element : nla.getNamepath()) {
770 if (
auto ref = dyn_cast<InnerRefAttr>(element)) {
771 nlaHops.push_back({ref.getModule(),
772 resolveInstanceHop(ref.getModule(), ref.getName()),
774 }
else if (
auto flat = dyn_cast<FlatSymbolRefAttr>(element))
775 nlaHops.push_back({flat.getAttr(),
nullptr, StringAttr()});
777 llvm_unreachable(
"NLA element must be innerref or flat symbol");
781 ++(nlaHops.back().sym ? stats.endInnerSym : stats.endModule);
785 for (
auto &upperPath : upperPaths) {
786 SmallVector<PathHop> absolutePath;
787 llvm::append_range(absolutePath, upperPath);
788 llvm::append_range(absolutePath, nlaHops);
790 if (failed(processSinglePathContext(origSym, absolutePath, nla)))
797 assert(llvm::all_of(pathRoutingTable,
798 [](
const auto &entry) {
799 return llvm::is_sorted(entry.second,
vnlaIdLess);
801 "routing entries must be born id-sorted (I5)");
807 for (
size_t i = 0, e = allVNLAs.size(); i < e;) {
808 StringAttr origSym = allVNLAs[i]->origSym;
809 size_t groupStart = i;
810 while (i < e && allVNLAs[i]->origSym == origSym)
812 origToVNLAs[origSym] =
813 ArrayRef<VirtualNLA *>(&allVNLAs[groupStart], i - groupStart);
819VirtualNLA *NLAPlanner::createVNLA(StringAttr origSym,
820 ArrayRef<SurvivingHop> path) {
823 auto id = allVNLAs.size();
824 allVNLAs.push_back(VirtualNLA::create(alloc,
id, origSym, path));
825 return allVNLAs.back();
828LogicalResult NLAPlanner::traceUpUntilSurviving(
829 StringAttr rootModName, hw::HierPathOp diagAnchor,
830 SmallVectorImpl<SmallVector<PathHop>> &discoveredPaths) {
832 decltype(std::declval<igraph::InstanceGraphNode>().uses().begin());
837 UseIterator currentEdge;
843 SmallVector<Frame, 16> stack;
844 SmallVector<PathHop, 8> currentPath;
847 DenseMap<StringAttr, bool> visited;
852 auto pushState = [&](StringAttr name) -> LogicalResult {
855 return diagAnchor.emitOpError()
856 <<
"names non-existent root module @" << name;
857 auto uses = node->
uses();
858 stack.push_back({name, uses.begin(), uses.end(),
true,
863 return mlir::emitError(node->
getModule().getLoc(),
864 "instance graph contains cycle");
865 visited[name] =
true;
870 auto popState = [&]() {
872 auto name = stack.back().modName;
873 auto it = visited.find(name);
874 assert(it != visited.end() &&
"visited map missing module");
875 assert(it->second &&
"visited not set for module");
881 if (failed(pushState(rootModName)))
884 while (!stack.empty()) {
885 auto &frame = stack.back();
886 if (frame.isFirstVisit) {
887 frame.isFirstVisit =
false;
889 auto *currentModNode = instanceGraph.
lookup(frame.modName);
890 auto *currentModOp = currentModNode->
getModule().getOperation();
891 auto infoIfValid = facts.getModuleInfoIfPresent(currentModOp);
894 return mlir::emitError(
895 currentModOp->getLoc(),
896 "hierarchical path traced up through unknown operation")
897 .attachNote(diagAnchor.getLoc())
898 <<
"encountered tracing up from root of this hierarchical path";
899 auto info = *infoIfValid;
910 stack.size() > 1 ? stack[stack.size() - 2].pinned :
false;
911 frame.pinned = !
info.hasInline || (!
info.hasFlatten && childPinned);
922 bool deeperRootWins = childPinned && !
info.hasFlatten;
923 if (
info.isLive && !deeperRootWins)
924 discoveredPaths.push_back(llvm::to_vector(llvm::reverse(currentPath)));
927 if (!
info.hasInline && !
info.underFlatten) {
930 currentPath.pop_back();
935 if (frame.currentEdge == frame.endEdge) {
938 currentPath.pop_back();
943 auto *edge = *frame.currentEdge;
946 auto *instOp = edge->getInstance().getOperation();
951 if (!getInlinableInstance(instOp))
958 auto *parentOp = edge->getParent()->getModule().getOperation();
959 auto parentInfo = facts.getModuleInfoIfPresent(parentOp);
962 return mlir::emitError(
964 "hierarchical path traced up through unknown operation")
965 .attachNote(diagAnchor.getLoc())
966 <<
"encountered tracing up from root of this hierarchical path";
967 if (!parentInfo->mayBeFlattened())
970 auto parentName = edge->getParent()->getModule().getModuleNameAttr();
972 if (failed(pushState(parentName)))
979size_t NLAPlanner::minimalRootIndex(ArrayRef<PathHop> upperPath,
980 StringAttr rootMod) {
996 bool isTransitiveFlatten =
false;
999 for (
size_t i = 0, e = upperPath.size(); i <= e; ++i) {
1001 if (isTransitiveFlatten)
1003 StringAttr mod = i < e ? upperPath[i].mod : rootMod;
1005 facts.getModuleInfo(symbolTable.lookup<FModuleLike>(mod));
1007 if (!
info.hasInline)
1009 isTransitiveFlatten |=
info.hasFlatten;
1014Operation *NLAPlanner::resolveInstanceHop(StringAttr module,
1015 StringAttr innerSym) {
1016 auto [entry, inserted] = instanceHopIndex.try_emplace(module);
1021 for (
auto *record : *node) {
1022 auto *inst = record->getInstance().getOperation();
1024 entry->second.try_emplace(sym, inst);
1027 return entry->second.lookup(innerSym);
1031NLAPlanner::processSinglePathContext(StringAttr origSym,
1032 const SmallVectorImpl<PathHop> &absPath,
1033 hw::HierPathOp diagAnchor) {
1034 SmallVector<SurvivingHop> survivingHops;
1035 assert(!absPath.empty() &&
"empty absolute path -- empty namepath?");
1037 StringAttr currentDest = absPath.front().mod;
1038 auto destMod = symbolTable.lookup<FModuleLike>(currentDest);
1039 const auto &destInfo = facts.getModuleInfo(destMod);
1041 bool isTransitiveFlatten = destInfo.hasFlatten;
1044 StringAttr flattenCause = isTransitiveFlatten ? currentDest : StringAttr{};
1045 for (
auto it = absPath.begin(),
end = absPath.end(); it !=
end; ++it) {
1046 const auto &hop = *it;
1047 bool isTerminal = std::next(it) ==
end;
1051 auto hopInst = getInlinableInstance(hop.inst);
1052 bool isOpaqueInstanceHop = hop.inst && !hopInst;
1056 StringAttr nextModName;
1058 nextModName = std::next(it)->mod;
1060 nextModName = hopInst.getReferencedModuleNameAttr();
1063 bool nextHasInline =
false;
1064 bool nextHasFlatten =
false;
1065 bool nextIsRegular =
false;
1067 assert((isTerminal || !hopInst ||
1068 std::next(it)->mod == hopInst.getReferencedModuleNameAttr()) &&
1069 "recorded next module disagrees with the instance");
1070 auto modOp = symbolTable.lookup<FModuleLike>(nextModName);
1071 assert(modOp &&
"interior namepath module missing -- ran unverified?");
1072 const auto &
info = facts.getModuleInfo(modOp);
1073 nextHasInline =
info.hasInline;
1074 nextHasFlatten =
info.hasFlatten;
1075 nextIsRegular = isa<FModuleOp>(modOp);
1081 bool isEvaporating = nextModName && nextIsRegular && !isOpaqueInstanceHop &&
1082 (isTransitiveFlatten || nextHasInline);
1090 if (isEvaporating && isTerminal) {
1091 assert(hop.inst &&
"expected instance operation");
1092 auto diag = diagAnchor.emitError(
1093 "hierpath points to inlined instance, cannot proceed");
1094 diag.attachNote(hop.inst->getLoc())
1095 <<
"hierpath targets this inlined instance";
1097 if (nextHasInline) {
1098 diag.attachNote(symbolTable.lookup(nextModName)->getLoc())
1099 <<
"target module is marked inline";
1104 assert(flattenCause &&
"flatten-caused absorption without a cause");
1105 diag.attachNote(symbolTable.lookup(flattenCause)->getLoc())
1106 <<
"flattening this module inlines the instance";
1115 if (isOpaqueInstanceHop) {
1116 isTransitiveFlatten = nextHasFlatten;
1117 flattenCause = nextHasFlatten ? nextModName : StringAttr{};
1119 isTransitiveFlatten |= nextHasFlatten;
1121 flattenCause = nextModName;
1124 if (!isEvaporating) {
1129 StringAttr sym = hop.sym;
1130 assert((sym || isTerminal || !hop.inst) &&
1131 "surviving instance hop without an inner symbol");
1132 StringAttr finalSym;
1133 if (currentDest == hop.mod || isTerminal )
1135 survivingHops.push_back({hop.mod, sym,
1138 if (!isTerminal && nextModName)
1139 currentDest = nextModName;
1144 auto *vnla = createVNLA(origSym, survivingHops);
1149 for (
const auto &hop : absPath)
1151 pathRoutingTable[hop.inst].push_back(vnla);
1156#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
1157LLVM_DUMP_METHOD
void NLAPlanner::dump() {
1158 llvm::dbgs() <<
"\nVirtualNLAs (creation order):\n";
1159 for (
auto *vnla : allVNLAs)
1162 llvm::dbgs() <<
"\nPath Routing Table (Instance -> Routed VirtualNLAs):\n";
1166 auto modOf = [](Operation *op) -> StringAttr {
1167 auto mod = op->getParentOfType<FModuleLike>();
1168 return mod ? mod.getModuleNameAttr() : StringAttr();
1170 SmallVector<Operation *> insts;
1171 for (
const auto &[inst, _] : pathRoutingTable)
1172 insts.push_back(inst);
1173 llvm::sort(insts, [&](Operation *a, Operation *b) {
1174 auto am = modOf(a), bm = modOf(b);
1176 return (am ? am.getValue() :
"") < (bm ? bm.getValue() :
"");
1178 return (as ? as.getValue() :
"") < (bs ? bs.getValue() :
"");
1181 for (
auto *inst : insts) {
1182 const auto &vnlas = pathRoutingTable.lookup(inst);
1183 llvm::dbgs() <<
" @" << modOf(inst);
1185 llvm::dbgs() <<
"::" << instSym;
1187 llvm::dbgs() <<
"::<op@" << inst <<
">";
1189 llvm::dbgs() <<
" -> [";
1190 llvm::interleaveComma(vnlas, llvm::dbgs(), [&](VirtualNLA *vnla) {
1191 llvm::dbgs() <<
"#" << vnla->id;
1193 llvm::dbgs() <<
"]\n";
1196 llvm::dbgs() <<
"\n";
1207 InstanceOp instance) {
1208 for (
auto [result, wire] : llvm::zip_equal(instance.getResults(), wires))
1209 mapper.map(result, wire);
1222 StringAttr istName) {
1223 hw::InnerRefAttr foreign;
1224 mlir::AttrTypeReplacer replacer;
1225 replacer.addReplacement([&](hw::InnerRefAttr innerRef) {
1226 auto it = map.find(innerRef);
1227 if (it == map.end()) {
1230 return std::pair{innerRef, WalkResult::skip()};
1232 return std::pair{hw::InnerRefAttr::get(istName, it->second),
1233 WalkResult::skip()};
1235 for (
auto *op : newOps) {
1236 replacer.recursivelyReplaceElementsIn(op);
1238 return op->emitError(
"unsupported inner reference ")
1239 << foreign <<
" found while inlining";
1249 StringAttr istName) {
1250 if (!old || old.empty())
1253 bool anyChanged =
false;
1255 SmallVector<hw::InnerSymPropertiesAttr> newProps;
1256 auto *
context = old.getContext();
1257 for (
auto &prop : old) {
1258 auto newSym = ns.
newName(prop.getName().strref());
1259 if (newSym == prop.getName()) {
1260 newProps.push_back(prop);
1263 auto newSymStrAttr = StringAttr::get(
context, newSym);
1264 auto newProp = hw::InnerSymPropertiesAttr::get(
1265 context, newSymStrAttr, prop.getFieldID(), prop.getSymVisibility());
1267 newProps.push_back(newProp);
1270 auto newSymAttr = anyChanged ? hw::InnerSymAttr::get(
context, newProps) : old;
1272 for (
auto [oldProp, newProp] : llvm::zip(old, newSymAttr)) {
1273 assert(oldProp.getFieldID() == newProp.getFieldID() &&
1274 "uniquing must preserve fieldIDs");
1276 map[hw::InnerRefAttr::get(istName, oldProp.getName())] = newProp.getName();
1300 Inliner(CircuitOp circuit, SymbolTable &symbolTable,
1302 const InliningFacts &inliningFacts, NLAPlanner &nlaPlanner);
1305 LogicalResult
run();
1309 size_t instancesInlined = 0;
1310 size_t instancesFlattened = 0;
1311 size_t deadModules = 0;
1312 size_t hierPathsUpdated = 0;
1313 size_t hierPathsForked = 0;
1314 size_t hierPathsMerged = 0;
1315 size_t hierPathsErased = 0;
1323 struct ModuleInliningContext {
1324 ModuleInliningContext(FModuleOp module)
1325 : module(module), modNamespace(module), b(module.getContext()) {}
1337 struct InliningLevel {
1338 InliningLevel(ModuleInliningContext &mic, FModuleOp childModule)
1339 : mic(mic), childModule(childModule) {}
1341 ModuleInliningContext &mic;
1343 SmallVector<Operation *> newOps;
1344 SmallVector<Value> wires;
1345 FModuleOp childModule;
1348 SmallVector<VirtualNLA *> activeNLAs;
1351 void setActivePaths(ArrayRef<VirtualNLA *> nlas) {
1352 activeNLAs.assign(nlas);
1358 LogicalResult finalize() {
1360 mic.module.getNameAttr());
1368 bool rename(StringRef prefix, Operation *op, InliningLevel &il);
1372 bool renameInstance(StringRef prefix, InliningLevel &il, Operation *oldInst,
1373 Operation *newInst);
1377 void cloneAndRename(StringRef prefix, InliningLevel &il, IRMapping &mapper,
1391 void recordContexts(Operation *newOp,
const InliningLevel &il);
1398 void updateVirtualNLALeafSymbols(Inliner::InliningLevel &il,
1399 hw::InnerSymAttr oldSymAttr,
1400 hw::InnerSymAttr newSymAttr);
1408 void setActiveNLAsForChild(std::optional<ArrayRef<VirtualNLA *>> activeNLAs,
1409 InliningLevel &childIL, Operation *instance);
1414 void mapPortsToWires(StringRef prefix, InliningLevel &il, IRMapping &mapper);
1419 bool shouldFlatten(FModuleLike mod);
1422 bool shouldInline(FModuleLike mod);
1426 LogicalResult checkInstanceParents(InstanceOp instance);
1433 inliningWalk(OpBuilder &builder, Block *block, IRMapping &mapper,
1434 llvm::function_ref<LogicalResult(Operation *op)> process);
1445 LogicalResult processInto(StringRef prefix, InliningLevel &il,
1446 IRMapping &mapper,
bool flatten);
1451 LogicalResult processInstances(FModuleOp module,
bool flatten);
1455 void createDebugScope(InliningLevel &il, InstanceOp instance,
1456 Value parentScope = {});
1460 LogicalResult inlineModules();
1463 void eraseDeadModules();
1474 void appendContextAnno(
Annotation anno, StringAttr origSym,
1475 VirtualNLA *matched, SmallVectorImpl<Attribute> &out);
1479 void canonicalizeContexts();
1483 void rewriteAnnotations();
1486 void writebackHierPaths();
1492 ArrayAttr materializeNamepath(VirtualNLA *vnla);
1505 void canonicalize(VirtualNLA *vnla);
1512 VirtualNLA *canonicalOrSelf(VirtualNLA *vnla)
const {
1513 return canonicalOf.lookup_or(vnla, vnla);
1519 MLIRContext *context;
1522 SymbolTable &symbolTable;
1529 const InliningFacts &inliningFacts;
1530 NLAPlanner &nlaPlanner;
1541 DenseMap<ArrayAttr, VirtualNLA *> canonicalByPath;
1542 DenseMap<VirtualNLA *, VirtualNLA *> canonicalOf;
1546 struct ClaimedSyms {
1548 DenseSet<StringAttr> syms;
1549 void claim(StringAttr sym) { syms.insert(sym); }
1550 bool has(StringAttr sym)
const {
return syms.contains(sym); }
1552 void claim(StringAttr) {}
1553 bool has(StringAttr)
const {
return true; }
1570 DenseMap<Operation *, SmallVector<VirtualNLA *, 2>> clonedAnnoContexts;
1574 SmallVector<debug::ScopeOp> debugScopes;
1580Inliner::Inliner(CircuitOp circuit, SymbolTable &symbolTable,
1582 const InliningFacts &inliningFacts, NLAPlanner &nlaPlanner)
1583 : circuit(circuit),
context(circuit.getContext()), symbolTable(symbolTable),
1584 circuitNamespace(circuitNamespace), inliningFacts(inliningFacts),
1585 nlaPlanner(nlaPlanner) {}
1587LogicalResult Inliner::run() {
1588 if (failed(inlineModules()))
1592 canonicalizeContexts();
1593 rewriteAnnotations();
1594 writebackHierPaths();
1603bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) {
1606 auto updateDebugScope = [&](
auto op) {
1608 op.getScopeMutable().assign(il.debugScope);
1610 if (
auto varOp = dyn_cast<debug::VariableOp>(op))
1611 return updateDebugScope(varOp),
false;
1612 if (
auto scopeOp = dyn_cast<debug::ScopeOp>(op))
1613 return updateDebugScope(scopeOp),
false;
1616 if (
auto nameAttr = op->getAttrOfType<StringAttr>(
"name"))
1617 op->setAttr(
"name", StringAttr::get(op->getContext(),
1618 (prefix + nameAttr.getValue())));
1621 auto symOp = dyn_cast<hw::InnerSymbolOpInterface>(op);
1624 auto oldSymAttr = symOp.getInnerSymAttr();
1627 il.childModule.getNameAttr());
1634 updateVirtualNLALeafSymbols(il, oldSymAttr, newSymAttr);
1635 symOp.setInnerSymbolAttr(newSymAttr);
1637 return newSymAttr != oldSymAttr;
1640bool Inliner::renameInstance(StringRef prefix, InliningLevel &il,
1641 Operation *oldInst, Operation *newInst) {
1646 llvm::dbgs() <<
"Discarding parent debug scope for " << *oldInst <<
"\n";
1650 auto symbolChanged = rename(prefix, newInst, il);
1656 assert(newSymAttr &&
"uniquing dropped an instance sym?");
1657 StringAttr origMod = il.childModule.getModuleNameAttr();
1658 StringAttr destMod = il.mic.module.getModuleNameAttr();
1659 for (
auto *nla : il.activeNLAs) {
1660 for (
auto &hop : nla->getPathMutable()) {
1663 if (hop.origMod == origMod && hop.origSym == oldInstSym &&
1664 hop.finalMod == destMod) {
1665 hop.finalSym = newSymAttr;
1670 return symbolChanged;
1673void Inliner::recordContexts(Operation *newOp,
const InliningLevel &il) {
1674 StringAttr destMod = il.mic.module.getModuleNameAttr();
1683 auto matchContexts = [&](FlatSymbolRefAttr sym, ArrayRef<VirtualNLA *> active,
1684 SmallVectorImpl<VirtualNLA *> &out) {
1685 auto it = nlaPlanner.origToVNLAs.find(sym.getAttr());
1686 if (it == nlaPlanner.origToVNLAs.end())
1688 ArrayRef<VirtualNLA *> group = it->second;
1689 const auto *lo = llvm::lower_bound(active, group.front(),
vnlaIdLess);
1691 std::upper_bound(lo, active.end(), group.back(),
vnlaIdLess);
1692 for (; lo != hi; ++lo) {
1703 auto path = (*lo)->getPath();
1704 assert(!path.empty() &&
"terminal hop is expected to always survive");
1705 if (path.back().finalMod != destMod)
1709 if (!llvm::is_contained(out, *lo))
1718 bool hasNonlocal =
false;
1719 SmallVector<VirtualNLA *, 2> annoContexts;
1721 auto sym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
1725 matchContexts(sym, il.activeNLAs, annoContexts);
1727 if (
auto annos = newOp->getAttrOfType<ArrayAttr>(
"annotations"))
1728 for (Attribute attr : annos)
1730 if (
auto portAnnos = newOp->getAttrOfType<ArrayAttr>(
"portAnnotations"))
1731 for (
auto portArray : portAnnos.getAsRange<ArrayAttr>())
1732 for (Attribute attr : portArray)
1735 clonedAnnoContexts[newOp] = std::move(annoContexts);
1738void Inliner::updateVirtualNLALeafSymbols(Inliner::InliningLevel &il,
1739 hw::InnerSymAttr oldSymAttr,
1740 hw::InnerSymAttr newSymAttr) {
1743 if (!oldSymAttr || oldSymAttr == newSymAttr)
1745 assert(newSymAttr &&
"renamed to a null sym?");
1746 StringAttr origMod = il.childModule.getModuleNameAttr();
1747 StringAttr destMod = il.mic.module.getModuleNameAttr();
1748 for (
auto *nla : il.activeNLAs) {
1754 auto &last = nla->getPathMutable().back();
1757 if (last.origMod == origMod && last.finalMod == destMod) {
1758 for (
auto prop : oldSymAttr.getProps()) {
1759 if (last.origSym == prop.getName()) {
1760 last.finalSym = newSymAttr.getSymIfExists(prop.getFieldID());
1768void Inliner::setActiveNLAsForChild(
1769 std::optional<ArrayRef<VirtualNLA *>> activeNLAs, InliningLevel &childIL,
1770 Operation *instance) {
1773 ArrayRef<VirtualNLA *> instNLAs;
1774 if (
auto it = nlaPlanner.pathRoutingTable.find(instance);
1775 it != nlaPlanner.pathRoutingTable.end())
1776 instNLAs = it->second;
1780 childIL.setActivePaths(instNLAs);
1781 }
else if (!activeNLAs->empty() && !instNLAs.empty()) {
1790 ArrayRef<VirtualNLA *> probe = instNLAs, in = *activeNLAs;
1791 if (probe.size() > in.size())
1792 std::swap(probe, in);
1793 SmallVector<VirtualNLA *> childActiveNLAs;
1794 for (
auto *vnla : probe)
1796 childActiveNLAs.push_back(vnla);
1797 childIL.setActivePaths(childActiveNLAs);
1803void Inliner::mapPortsToWires(StringRef prefix, InliningLevel &il,
1804 IRMapping &mapper) {
1805 auto target = il.childModule;
1806 auto portInfo = target.getPorts();
1807 for (
unsigned i = 0, e = target.getNumPorts(); i < e; ++i) {
1808 auto arg = target.getArgument(i);
1809 auto type = type_cast<FIRRTLType>(arg.getType());
1811 auto oldSymAttr = portInfo[i].sym;
1814 il.mic.modNamespace, target.getNameAttr());
1821 updateVirtualNLALeafSymbols(il, oldSymAttr, newSymAttr);
1825 auto wireOp = WireOp::create(
1826 il.mic.b, target.getLoc(), type,
1827 StringAttr::get(
context, (prefix + portInfo[i].getName())),
1828 NameKindEnumAttr::get(
context, NameKindEnum::DroppableName),
1831 recordContexts(wireOp, il);
1832 Value wire = wireOp.getResult();
1833 il.wires.push_back(wire);
1834 mapper.map(arg, wire);
1840void Inliner::cloneAndRename(StringRef prefix, InliningLevel &il,
1841 IRMapping &mapper, Operation &op) {
1847 assert(op.getNumRegions() == 0 &&
1848 "operation with regions should not reach cloneAndRename");
1849 auto *newOp = il.mic.b.cloneWithoutRegions(op, mapper);
1852 if (isa<FInstanceLike>(&op))
1853 renameInstance(prefix, il, &op, newOp);
1855 rename(prefix, newOp, il);
1857 recordContexts(newOp, il);
1859 il.newOps.push_back(newOp);
1864bool Inliner::shouldFlatten(FModuleLike mod) {
1865 return inliningFacts.hasFlatten(mod);
1868bool Inliner::shouldInline(FModuleLike mod) {
1869 return inliningFacts.hasInline(mod);
1872LogicalResult Inliner::inliningWalk(
1873 OpBuilder &builder, Block *block, IRMapping &mapper,
1874 llvm::function_ref<LogicalResult(Operation *op)> process) {
1877 OpBuilder::InsertPoint target;
1878 Block::iterator source;
1881 SmallVector<IPs> inliningStack;
1885 inliningStack.push_back(IPs{builder.saveInsertionPoint(), block->begin()});
1886 OpBuilder::InsertionGuard guard(builder);
1888 while (!inliningStack.empty()) {
1889 auto target = inliningStack.back().target;
1890 builder.restoreInsertionPoint(target);
1894 auto &ips = inliningStack.back();
1895 source = &*ips.source;
1896 auto end = source->getBlock()->end();
1897 if (++ips.source == end)
1898 inliningStack.pop_back();
1901 if (source->getNumRegions() == 0) {
1903 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
1904 if (failed(process(source)))
1906 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
1912 if (!isa<LayerBlockOp, WhenOp, MatchOp>(source))
1913 return source->emitError(
"unsupported operation '")
1914 << source->getName() <<
"' cannot be inlined";
1918 auto *newOp = builder.cloneWithoutRegions(*source, mapper);
1919 for (
auto [newRegion, oldRegion] :
llvm::reverse(
1920 llvm::zip_equal(newOp->getRegions(), source->getRegions()))) {
1921 if (oldRegion.empty()) {
1922 assert(newRegion.empty());
1926 assert(oldRegion.hasOneBlock());
1928 auto &oldBlock = oldRegion.getBlocks().front();
1929 auto &newBlock = newRegion.emplaceBlock();
1930 mapper.map(&oldBlock, &newBlock);
1932 for (
auto arg : oldBlock.getArguments())
1933 mapper.map(arg, newBlock.addArgument(arg.getType(), arg.
getLoc()));
1935 if (oldBlock.empty())
1938 inliningStack.push_back(
1939 IPs{OpBuilder::InsertPoint(&newBlock, newBlock.begin()),
1946LogicalResult Inliner::checkInstanceParents(InstanceOp instance) {
1947 auto *parent = instance->getParentOp();
1948 while (!isa<FModuleLike>(parent)) {
1949 if (!isa<LayerBlockOp>(parent))
1950 return instance->emitError(
"cannot inline instance")
1951 .attachNote(parent->getLoc())
1952 <<
"containing operation '" << parent->getName()
1953 <<
"' not safe to inline into";
1954 parent = parent->getParentOp();
1960LogicalResult Inliner::processInto(StringRef prefix, InliningLevel &il,
1961 IRMapping &mapper,
bool flatten) {
1962 auto target = il.childModule;
1964 LLVM_DEBUG(llvm::dbgs() << (flatten ?
"flattening " :
"inlining ")
1965 << target.getModuleName() <<
" into "
1966 << il.mic.module.getModuleName() <<
"\n");
1968 auto visit = [&](Operation *op) {
1970 auto instance = getInlinableInstance(op);
1972 cloneAndRename(prefix, il, mapper, *op);
1977 auto *moduleOp = symbolTable.lookup(instance.getModuleName());
1978 auto childModule = dyn_cast<FModuleOp>(moduleOp);
1980 assert(inliningFacts.isKnownLive(moduleOp) &&
1981 "a kept non-module instance must target a live module");
1982 cloneAndRename(prefix, il, mapper, *op);
1988 if (!flatten && !shouldInline(childModule)) {
1989 assert(inliningFacts.isLive(childModule) &&
1990 "a kept child module must be live");
1991 cloneAndRename(prefix, il, mapper, *op);
1995 if (failed(checkInstanceParents(instance)))
1998 ++(flatten ? stats.instancesFlattened : stats.instancesInlined);
2000 InliningLevel childIL(il.mic, childModule);
2001 setActiveNLAsForChild(il.activeNLAs, childIL, instance);
2002 createDebugScope(childIL, instance, il.debugScope);
2005 auto nestedPrefix = (prefix + instance.getName() +
"_").str();
2006 mapPortsToWires(nestedPrefix, childIL, mapper);
2010 if (failed(processInto(nestedPrefix, childIL, mapper,
2011 flatten || shouldFlatten(childModule))))
2013 return childIL.finalize();
2016 return inliningWalk(il.mic.b, target.getBodyBlock(), mapper, visit);
2019LogicalResult Inliner::processInstances(FModuleOp module,
bool flatten) {
2020 ModuleInliningContext mic(module);
2022 LLVM_DEBUG(llvm::dbgs() <<
"inlining instances within "
2023 << module.getNameAttr() <<
"...\n");
2024 auto visit = [&](FInstanceLike instanceLike) {
2025 auto instance = getInlinableInstance(instanceLike.getOperation());
2027 return WalkResult::advance();
2029 auto moduleOp = symbolTable.lookup<FModuleLike>(instance.getModuleName());
2030 assert(moduleOp &&
"instance target missing -- ran unverified?");
2031 auto target = dyn_cast<FModuleOp>(*moduleOp);
2033 assert(inliningFacts.isLive(moduleOp) &&
2034 "a kept non-module instance must target a live module");
2035 return WalkResult::advance();
2039 if (!flatten && !shouldInline(target))
2040 return WalkResult::advance();
2042 if (failed(checkInstanceParents(instance)))
2043 return WalkResult::interrupt();
2045 ++(flatten ? stats.instancesFlattened : stats.instancesInlined);
2050 mic.b.setInsertionPoint(instance);
2052 InliningLevel childIL(mic, target);
2053 setActiveNLAsForChild( std::nullopt,
2055 createDebugScope(childIL, instance);
2057 auto nestedPrefix = (instance.getName() +
"_").str();
2058 mapPortsToWires(nestedPrefix, childIL, mapper);
2059 for (
unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
2060 instance.getResult(i).replaceAllUsesWith(childIL.wires[i]);
2063 if (failed(processInto(nestedPrefix, childIL, mapper,
2064 flatten || shouldFlatten(target))) ||
2065 failed(childIL.finalize()))
2066 return WalkResult::interrupt();
2069 return WalkResult::skip();
2072 return failure(module.getBodyBlock()
2073 ->walk<mlir::WalkOrder::PreOrder>(visit)
2077void Inliner::createDebugScope(InliningLevel &il, InstanceOp instance,
2078 Value parentScope) {
2079 auto op = debug::ScopeOp::create(
2080 il.mic.b, instance.getLoc(), instance.getInstanceNameAttr(),
2081 instance.getModuleNameAttr().getAttr(), parentScope);
2082 debugScopes.push_back(op);
2086LogicalResult Inliner::inlineModules() {
2092 for (
auto moduleOp : inliningFacts.getSchedule()) {
2093 const auto &
info = inliningFacts.getModuleInfo(moduleOp);
2102 if (
info.hasFlatten ||
info.hasInline)
2104 return anno.
isClass(flattenAnnoClass, inlineAnnoClass);
2106 if (failed(processInstances(moduleOp,
info.hasFlatten)))
2112 for (
auto scopeOp :
llvm::reverse(debugScopes))
2113 if (scopeOp.use_empty())
2115 debugScopes.clear();
2120void Inliner::eraseDeadModules() {
2121 for (
auto mod :
llvm::make_early_inc_range(circuit.getOps<FModuleLike>())) {
2122 if (inliningFacts.isKnownLive(mod))
2125 ++stats.deadModules;
2131ArrayAttr Inliner::materializeNamepath(VirtualNLA *vnla) {
2132 SmallVector<Attribute> pathAttrs;
2133 for (
auto &hop : vnla->getPath()) {
2140 assert((hop.finalSym || !hop.origSym) &&
2141 "materializing a hop whose final symbol was never filled");
2143 pathAttrs.push_back(InnerRefAttr::get(hop.finalMod, hop.finalSym));
2145 pathAttrs.push_back(FlatSymbolRefAttr::get(hop.finalMod));
2147 return ArrayAttr::get(
context, pathAttrs);
2150void Inliner::canonicalize(VirtualNLA *vnla) {
2154 assert(!vnla->isLocal() &&
"local VNLAs have no hierpath to canonicalize");
2155 assert(!vnla->realizedSym &&
"context canonicalized twice");
2157 canonicalByPath.try_emplace(materializeNamepath(vnla), vnla)
2159 canonicalOf[vnla] = canon;
2160 if (canon == vnla) {
2163 assert(claimed.has(vnla->origSym) &&
2164 "primary claims origSym before any fork canonicalizes (I15)");
2165 vnla->realizedSym = StringAttr::get(
2170void Inliner::appendContextAnno(
Annotation anno, StringAttr origSym,
2171 VirtualNLA *matched,
2172 SmallVectorImpl<Attribute> &out) {
2173 if (matched->isLocal()) {
2175 out.push_back(anno.
getAttr());
2178 matched->wasUsed =
true;
2179 StringAttr canonSym = canonicalOrSelf(matched)->realizedSym;
2181 if (canonSym == origSym) {
2182 out.push_back(anno.
getAttr());
2185 anno.
setMember(
"circt.nonlocal", FlatSymbolRefAttr::get(canonSym));
2186 out.push_back(anno.
getAttr());
2189void Inliner::canonicalizeContexts() {
2204 for (
size_t i = 0, e = nlaPlanner.allVNLAs.size(); i < e;) {
2205 StringAttr origSym = nlaPlanner.allVNLAs[i]->origSym;
2206 size_t groupStart = i;
2207 while (i < e && nlaPlanner.allVNLAs[i]->origSym == origSym)
2209 ArrayRef<VirtualNLA *> group(&nlaPlanner.allVNLAs[groupStart],
2215 VirtualNLA *primary =
nullptr;
2216 for (
auto *v : group)
2217 if (!v->isLocal()) {
2222 primary = group.front();
2229 primary->realizedSym = origSym;
2230 claimed.claim(origSym);
2231 canonicalOf[primary] = primary;
2233 if (!primary->isLocal())
2234 canonicalByPath.try_emplace(materializeNamepath(primary), primary);
2240 for (
auto *v : group) {
2241 if (v == primary || v->isLocal())
2248void Inliner::rewriteAnnotations() {
2251 auto rewriteAnnos = [&](ArrayAttr annos, StringAttr modName,
2252 const SmallVectorImpl<VirtualNLA *> *recorded,
2253 SmallVectorImpl<Attribute> &newAnnos) {
2254 for (Attribute attr : annos) {
2256 auto sym = anno.
getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
2258 newAnnos.push_back(anno.
getAttr());
2265 for (
auto *matched : *recorded)
2266 if (matched->origSym == sym.getAttr())
2267 appendContextAnno(anno, sym.getAttr(), matched, newAnnos);
2273 auto it = nlaPlanner.origToVNLAs.find(sym.getAttr());
2274 if (it == nlaPlanner.origToVNLAs.end())
2276 for (
auto *matched : it->second) {
2280 if (matched->getPath().back().finalMod != modName)
2282 appendContextAnno(anno, sym.getAttr(), matched, newAnnos);
2287 auto rewriteOpAnnos = [&](Operation *op, StringAttr modName) {
2288 const SmallVectorImpl<VirtualNLA *> *recorded =
nullptr;
2289 if (
auto it = clonedAnnoContexts.find(op); it != clonedAnnoContexts.end())
2290 recorded = &it->second;
2295 SmallVector<Attribute> newAnnotations;
2296 rewriteAnnos(annos, modName, recorded, newAnnotations);
2302 if (
auto portAnnos = op->getAttrOfType<ArrayAttr>(
"portAnnotations")) {
2303 SmallVector<Attribute> newPortAnnotations;
2304 SmallVector<Attribute> newAnnotations;
2305 for (
auto portArray : portAnnos.getAsRange<ArrayAttr>()) {
2306 newAnnotations.clear();
2307 rewriteAnnos(portArray, modName, recorded, newAnnotations);
2308 newPortAnnotations.push_back(ArrayAttr::get(
context, newAnnotations));
2310 op->setAttr(
"portAnnotations",
2311 ArrayAttr::get(
context, newPortAnnotations));
2314 auto rewriteModuleAnnos = [&](FModuleLike fmodule) {
2315 StringAttr modName = fmodule.getModuleNameAttr();
2316 fmodule.walk([&](Operation *op) { rewriteOpAnnos(op, modName); });
2324 SmallVector<FModuleOp> bodyModules;
2325 for (
auto fmodule : circuit.getOps<FModuleLike>()) {
2326 if (
auto regular = dyn_cast<FModuleOp>(*fmodule))
2327 bodyModules.push_back(regular);
2329 rewriteModuleAnnos(fmodule);
2331 mlir::parallelForEach(
context, bodyModules, [&](FModuleOp fmodule) {
2332 rewriteModuleAnnos(fmodule);
2336void Inliner::writebackHierPaths() {
2341 for (
auto &[dup, canon] : canonicalOf) {
2344 ++stats.hierPathsMerged;
2346 canon->wasUsed =
true;
2354 for (
size_t i = 0, e = nlaPlanner.allVNLAs.size(); i < e;) {
2355 StringAttr origSym = nlaPlanner.allVNLAs[i]->origSym;
2356 unsigned claimants = 0;
2357 for (; i < e && nlaPlanner.allVNLAs[i]->origSym == origSym; ++i) {
2358 auto *v = nlaPlanner.allVNLAs[i];
2359 if (v->realizedSym == origSym && canonicalOrSelf(v) == v)
2363 "retention: each origSym must have exactly one primary claimant");
2370 auto &existingPaths = nlaPlanner.hierPathOps;
2381 StringAttr curGroup;
2386 DenseSet<StringAttr> retainedPaths;
2387 for (
auto *vnla : nlaPlanner.allVNLAs) {
2388 if (vnla->origSym != curGroup) {
2389 curGroup = vnla->origSym;
2390 if (
auto it = existingPaths.find(curGroup); it != existingPaths.end())
2391 b.setInsertionPointAfter(it->second);
2395 if (canonicalOrSelf(vnla) != vnla)
2400 bool isPrimary = vnla->realizedSym == vnla->origSym;
2401 if (!isPrimary && (vnla->isLocal() || !vnla->wasUsed))
2404 auto arrayAttr = materializeNamepath(vnla);
2406 auto origIt = existingPaths.find(vnla->origSym);
2407 assert(origIt != existingPaths.end() &&
2408 "origSym has no source hw.hierpath");
2411 if (vnla->realizedSym == vnla->origSym) {
2413 if (arrayAttr != origIt->second.getNamepathAttr()) {
2414 origIt->second.setNamepathAttr(arrayAttr);
2415 ++stats.hierPathsUpdated;
2417 retainedPaths.insert(vnla->origSym);
2424 hw::HierPathOp::create(b, origIt->second.getLoc(), vnla->realizedSym,
2427 ++stats.hierPathsForked;
2429 for (
auto &[sym, deadPath] : existingPaths) {
2430 if (retainedPaths.contains(sym))
2434 ++stats.hierPathsErased;
2446class InlinerPass :
public circt::firrtl::impl::InlinerBase<InlinerPass> {
2449 void runOnOperation()
override {
2451 auto circuit = getOperation();
2452 auto &symbolTable = getAnalysis<SymbolTable>();
2453 auto &instanceGraph = getAnalysis<InstanceGraph>();
2456 auto facts = InliningFacts::compute(circuit, instanceGraph, symbolTable);
2458 return signalPassFailure();
2460 llvm::dbgs() <<
"\n";
2466 NLAPlanner nlaPlanner(circuit, symbolTable, instanceGraph, *facts);
2467 if (failed(nlaPlanner.run()))
2468 return signalPassFailure();
2470 llvm::dbgs() <<
"\n";
2474 numHierPathsEndInnerSym += nlaPlanner.stats.endInnerSym;
2475 numHierPathsEndModule += nlaPlanner.stats.endModule;
2479 Inliner inliner(circuit, symbolTable, circuitNamespace, *facts, nlaPlanner);
2480 if (failed(inliner.run()))
2481 signalPassFailure();
2483 numInstancesInlined += inliner.stats.instancesInlined;
2484 numInstancesFlattened += inliner.stats.instancesFlattened;
2485 numDeadModules += inliner.stats.deadModules;
2486 numHierPathsUpdated += inliner.stats.hierPathsUpdated;
2487 numHierPathsForked += inliner.stats.hierPathsForked;
2488 numHierPathsMerged += inliner.stats.hierPathsMerged;
2489 numHierPathsErased += inliner.stats.hierPathsErased;
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void dump(DIModule &module, raw_indented_ostream &os)
static Location getLoc(DefSlot slot)
DenseMap< hw::InnerRefAttr, StringAttr > InnerRefToNewNameMap
static hw::InnerSymAttr uniqueInNamespace(hw::InnerSymAttr old, InnerRefToNewNameMap &map, hw::InnerSymbolNamespace &ns, StringAttr istName)
Unique each of old's symbols in ns; record old-ref -> new-name entries in map under istName.
static bool vnlaIdLess(const VirtualNLA *a, const VirtualNLA *b)
Context collections are ordered by creation id throughout (I4/I5/I6).
static void mapResultsToWires(IRMapping &mapper, SmallVectorImpl< Value > &wires, InstanceOp instance)
Map each of the instance's results to its corresponding replacement wire.
static LogicalResult replaceInnerRefUsers(ArrayRef< Operation * > newOps, const InnerRefToNewNameMap &map, StringAttr istName)
Process each operation, updating InnerRefAttr's using the specified map, with the given name as the c...
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
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.
ArrayAttr getArrayAttr() const
Return this annotation set as an ArrayAttr.
bool applyToOperation(Operation *op) const
Store the annotations in this set in an operation's annotations attribute, overwriting any existing a...
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.
Attribute getAttr() const
Get the underlying attribute.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
void setMember(StringAttr name, Attribute value)
Add or set a member of the annotation to a value.
void removeMember(StringAttr name)
Remove a member of the annotation.
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 is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
auto getModule()
Get the module that this node is tracking.
InstanceGraphNode * lookupOrNull(StringAttr name)
Lookup an module by name.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
decltype(auto) walkInversePostOrder(Fn &&fn)
Perform an inverse-post-order walk across the modules.
This is an edge in the InstanceGraph.
auto getInstance()
Get the instance-like op that this is tracking.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
ArrayAttr getAnnotationsIfPresent(Operation *op)
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
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.
size_t hash_combine(size_t h1, size_t h2)
C++'s stdlib doesn't have a hash_combine function. This is a simple one.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
llvm::hash_code hash_value(const DenseSet< T > &set)
The namespace of a CircuitOp, generally inhabited by modules.
static unsigned getHashValue(const TrimmedPathRef &key)
static bool isEqual(const TrimmedPathRef &a, const TrimmedPathRef &b)