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 (!module.canDiscardOnUseEmpty() || hasOpaqueUse) {
407 info.hasUnflattenedPath =
true;
409 if (
info.hasInline && hasOpaqueUse) {
410 auto diag = mlir::emitWarning(module.getLoc())
411 <<
"module marked inline is also instantiated by an "
412 "operation that cannot be inlined; it is inlined only "
413 "into its 'firrtl.instance' parents and retained";
414 diag.attachNote((*opaqueRecIt)->getInstance()->getLoc())
415 <<
"instantiated here";
423 if (isa<hw::HierPathOp>(op))
426 if (failed(markSymbolUses(op)))
432 auto *mod = node.
getModule().getOperation();
433 assert(isa<FModuleLike>(mod) &&
"instance graph contains non-fmodulelike");
436 if (
auto fmod = dyn_cast<FModuleOp>(mod))
437 schedule.push_back(fmod);
438 auto &modInfo = classification[mod];
441 if (!modInfo.hasUnflattenedPath && !modInfo.underFlatten)
444 for (
auto *edge : node) {
445 auto *childMod = edge->getTarget()->getModule().getOperation();
446 auto &childInfo = classification[childMod];
447 bool isRegularModule = isa<FModuleOp>(childMod);
449 (isRegularModule || !(childInfo.hasInline || childInfo.hasFlatten)) &&
450 "non-fmoduleop with inline/flatten annotation");
451 if (isRegularModule && modInfo.mayBeFlattened())
452 childInfo.underFlatten =
true;
456 if (modInfo.keepsChildrenInstantiated() || !isRegularModule)
457 childInfo.hasUnflattenedPath =
true;
460 if (childInfo.hasUnflattenedPath &&
461 (!isRegularModule || !childInfo.hasInline))
462 childInfo.isLive =
true;
466 return InliningFacts(std::move(classification), std::move(schedule));
501class VirtualNLA final : llvm::TrailingObjects<VirtualNLA, SurvivingHop> {
502 friend TrailingObjects;
506 VirtualNLA(
unsigned id, StringAttr origSym, ArrayRef<SurvivingHop> path)
507 : numHops(path.size()), id(id), origSym(origSym) {
508 llvm::uninitialized_copy(path, getTrailingObjects());
519 StringAttr realizedSym;
522 bool wasUsed =
false;
524 static VirtualNLA *create(llvm::BumpPtrAllocator &alloc,
unsigned id,
525 StringAttr origSym, ArrayRef<SurvivingHop> path) {
529 assert(!path.empty() &&
"a VNLA always keeps its terminal hop (I8)");
530 size_t size = totalSizeToAlloc<SurvivingHop>(path.size());
531 auto *mem = alloc.Allocate(size,
alignof(VirtualNLA));
532 return new (mem) VirtualNLA(
id, origSym, path);
535 bool isLocal()
const {
return numHops <= 1; }
537 ArrayRef<SurvivingHop> getPath()
const {
538 return {getTrailingObjects(), numHops};
541 MutableArrayRef<SurvivingHop> getPathMutable() {
542 return {getTrailingObjects(), numHops};
545#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
546 LLVM_DUMP_METHOD
void dump()
const {
547 llvm::dbgs() << llvm::formatv(
" VirtualNLA {0}: origSym @{1}",
id,
550 llvm::dbgs() <<
" -> local\n";
552 llvm::dbgs() << llvm::formatv(
", hops: {0}\n", numHops);
553 for (
const auto &hop : getPath()) {
554 llvm::dbgs() << llvm::formatv(
555 " - {0}::{1} -> {2}::{3}\n", hop.origMod,
556 (hop.origSym ? hop.origSym.str() :
"*"), hop.finalMod,
557 (hop.finalSym ? hop.finalSym.str() :
"(TBD)"));
564static_assert(std::is_trivially_destructible_v<VirtualNLA>,
565 "VirtualNLA is arena-allocated; destructors never run");
570static bool vnlaIdLess(
const VirtualNLA *a,
const VirtualNLA *b) {
571 return a->id < b->id;
588 return mod == o.mod && inst == o.inst && sym == o.sym;
595struct TrimmedPathRef {
596 ArrayRef<PathHop> path;
597 llvm::hash_code hash;
599 static TrimmedPathRef
get(ArrayRef<PathHop> path) {
601 for (
const PathHop &hop : path)
603 hop.sym.getAsOpaquePointer());
613 NLAPlanner(CircuitOp circuit, SymbolTable &symbolTable,
615 : circuit(circuit), symbolTable(symbolTable),
616 instanceGraph(instanceGraph), facts(facts) {}
619#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
620 LLVM_DUMP_METHOD
void dump();
628 size_t endInnerSym = 0;
629 size_t endModule = 0;
634 VirtualNLA *createVNLA(StringAttr origSym, ArrayRef<SurvivingHop> path);
647 traceUpUntilSurviving(StringAttr rootModName, hw::HierPathOp diagAnchor,
648 SmallVectorImpl<SmallVector<PathHop>> &discoveredPaths);
654 processSinglePathContext(StringAttr origSym,
655 const SmallVectorImpl<PathHop> &absPath,
656 hw::HierPathOp diagAnchor);
673 size_t minimalRootIndex(ArrayRef<PathHop> upperPath, StringAttr rootMod);
685 Operation *resolveInstanceHop(StringAttr module, StringAttr innerSym);
688 SymbolTable &symbolTable;
690 const InliningFacts &facts;
694 DenseMap<StringAttr, DenseMap<StringAttr, Operation *>> instanceHopIndex;
697 llvm::BumpPtrAllocator alloc;
699 using VirtualNLAHandles = SmallVector<VirtualNLA *>;
704 DenseMap<Operation *, VirtualNLAHandles> pathRoutingTable;
707 DenseMap<StringAttr, ArrayRef<VirtualNLA *>> origToVNLAs;
709 SmallVector<VirtualNLA *> allVNLAs;
713 DenseMap<StringAttr, hw::HierPathOp> hierPathOps;
722 return static_cast<unsigned>(key.hash);
724 static bool isEqual(
const TrimmedPathRef &a,
const TrimmedPathRef &b) {
725 return a.hash == b.hash && a.path == b.path;
729LogicalResult NLAPlanner::run() {
737 for (
auto nla : circuit.getOps<
hw::HierPathOp>()) {
738 byRoot[nla.root()].push_back(nla);
739 hierPathOps[nla.getSymNameAttr()] = nla;
742 for (
auto &[origRoot, nlas] : byRoot) {
746 SmallVector<SmallVector<PathHop>> upperPaths;
747 if (failed(traceUpUntilSurviving(origRoot, nlas.front(), upperPaths)))
752 llvm::SmallDenseSet<TrimmedPathRef, 8> seenPaths;
753 for (
auto &upperPath : upperPaths) {
754 assert(minimalRootIndex(upperPath, origRoot) == 0 &&
755 "pruned climb leaked a trimmable path");
756 assert(seenPaths.insert(TrimmedPathRef::get(upperPath)).second &&
757 "pruned climb repeated a path");
762 for (
auto nla : nlas) {
763 auto origSym = nla.getSymNameAttr();
766 SmallVector<PathHop> nlaHops;
767 for (
auto element : nla.getNamepath()) {
768 if (
auto ref = dyn_cast<InnerRefAttr>(element)) {
769 nlaHops.push_back({ref.getModule(),
770 resolveInstanceHop(ref.getModule(), ref.getName()),
772 }
else if (
auto flat = dyn_cast<FlatSymbolRefAttr>(element))
773 nlaHops.push_back({flat.getAttr(),
nullptr, StringAttr()});
775 llvm_unreachable(
"NLA element must be innerref or flat symbol");
779 ++(nlaHops.back().sym ? stats.endInnerSym : stats.endModule);
783 for (
auto &upperPath : upperPaths) {
784 SmallVector<PathHop> absolutePath;
785 llvm::append_range(absolutePath, upperPath);
786 llvm::append_range(absolutePath, nlaHops);
788 if (failed(processSinglePathContext(origSym, absolutePath, nla)))
795 assert(llvm::all_of(pathRoutingTable,
796 [](
const auto &entry) {
797 return llvm::is_sorted(entry.second,
vnlaIdLess);
799 "routing entries must be born id-sorted (I5)");
805 for (
size_t i = 0, e = allVNLAs.size(); i < e;) {
806 StringAttr origSym = allVNLAs[i]->origSym;
807 size_t groupStart = i;
808 while (i < e && allVNLAs[i]->origSym == origSym)
810 origToVNLAs[origSym] =
811 ArrayRef<VirtualNLA *>(&allVNLAs[groupStart], i - groupStart);
817VirtualNLA *NLAPlanner::createVNLA(StringAttr origSym,
818 ArrayRef<SurvivingHop> path) {
821 auto id = allVNLAs.size();
822 allVNLAs.push_back(VirtualNLA::create(alloc,
id, origSym, path));
823 return allVNLAs.back();
826LogicalResult NLAPlanner::traceUpUntilSurviving(
827 StringAttr rootModName, hw::HierPathOp diagAnchor,
828 SmallVectorImpl<SmallVector<PathHop>> &discoveredPaths) {
830 decltype(std::declval<igraph::InstanceGraphNode>().uses().begin());
835 UseIterator currentEdge;
841 SmallVector<Frame, 16> stack;
842 SmallVector<PathHop, 8> currentPath;
845 DenseMap<StringAttr, bool> visited;
850 auto pushState = [&](StringAttr name) -> LogicalResult {
853 return diagAnchor.emitOpError()
854 <<
"names non-existent root module @" << name;
855 auto uses = node->
uses();
856 stack.push_back({name, uses.begin(), uses.end(),
true,
861 return mlir::emitError(node->
getModule().getLoc(),
862 "instance graph contains cycle");
863 visited[name] =
true;
868 auto popState = [&]() {
870 auto name = stack.back().modName;
871 auto it = visited.find(name);
872 assert(it != visited.end() &&
"visited map missing module");
873 assert(it->second &&
"visited not set for module");
879 if (failed(pushState(rootModName)))
882 while (!stack.empty()) {
883 auto &frame = stack.back();
884 if (frame.isFirstVisit) {
885 frame.isFirstVisit =
false;
887 auto *currentModNode = instanceGraph.
lookup(frame.modName);
888 auto *currentModOp = currentModNode->
getModule().getOperation();
889 auto infoIfValid = facts.getModuleInfoIfPresent(currentModOp);
892 return mlir::emitError(
893 currentModOp->getLoc(),
894 "hierarchical path traced up through unknown operation")
895 .attachNote(diagAnchor.getLoc())
896 <<
"encountered tracing up from root of this hierarchical path";
897 auto info = *infoIfValid;
908 stack.size() > 1 ? stack[stack.size() - 2].pinned :
false;
909 frame.pinned = !
info.hasInline || (!
info.hasFlatten && childPinned);
920 bool deeperRootWins = childPinned && !
info.hasFlatten;
921 if (
info.isLive && !deeperRootWins)
922 discoveredPaths.push_back(llvm::to_vector(llvm::reverse(currentPath)));
925 if (!
info.hasInline && !
info.underFlatten) {
928 currentPath.pop_back();
933 if (frame.currentEdge == frame.endEdge) {
936 currentPath.pop_back();
941 auto *edge = *frame.currentEdge;
944 auto *instOp = edge->getInstance().getOperation();
949 if (!getInlinableInstance(instOp))
956 auto *parentOp = edge->getParent()->getModule().getOperation();
957 auto parentInfo = facts.getModuleInfoIfPresent(parentOp);
960 return mlir::emitError(
962 "hierarchical path traced up through unknown operation")
963 .attachNote(diagAnchor.getLoc())
964 <<
"encountered tracing up from root of this hierarchical path";
965 if (!parentInfo->mayBeFlattened())
968 auto parentName = edge->getParent()->getModule().getModuleNameAttr();
970 if (failed(pushState(parentName)))
977size_t NLAPlanner::minimalRootIndex(ArrayRef<PathHop> upperPath,
978 StringAttr rootMod) {
994 bool isTransitiveFlatten =
false;
997 for (
size_t i = 0, e = upperPath.size(); i <= e; ++i) {
999 if (isTransitiveFlatten)
1001 StringAttr mod = i < e ? upperPath[i].mod : rootMod;
1003 facts.getModuleInfo(symbolTable.lookup<FModuleLike>(mod));
1005 if (!
info.hasInline)
1007 isTransitiveFlatten |=
info.hasFlatten;
1012Operation *NLAPlanner::resolveInstanceHop(StringAttr module,
1013 StringAttr innerSym) {
1014 auto [entry, inserted] = instanceHopIndex.try_emplace(module);
1019 for (
auto *record : *node) {
1020 auto *inst = record->getInstance().getOperation();
1022 entry->second.try_emplace(sym, inst);
1025 return entry->second.lookup(innerSym);
1029NLAPlanner::processSinglePathContext(StringAttr origSym,
1030 const SmallVectorImpl<PathHop> &absPath,
1031 hw::HierPathOp diagAnchor) {
1032 SmallVector<SurvivingHop> survivingHops;
1033 assert(!absPath.empty() &&
"empty absolute path -- empty namepath?");
1035 StringAttr currentDest = absPath.front().mod;
1036 auto destMod = symbolTable.lookup<FModuleLike>(currentDest);
1037 const auto &destInfo = facts.getModuleInfo(destMod);
1039 bool isTransitiveFlatten = destInfo.hasFlatten;
1042 StringAttr flattenCause = isTransitiveFlatten ? currentDest : StringAttr{};
1043 for (
auto it = absPath.begin(),
end = absPath.end(); it !=
end; ++it) {
1044 const auto &hop = *it;
1045 bool isTerminal = std::next(it) ==
end;
1049 auto hopInst = getInlinableInstance(hop.inst);
1050 bool isOpaqueInstanceHop = hop.inst && !hopInst;
1054 StringAttr nextModName;
1056 nextModName = std::next(it)->mod;
1058 nextModName = hopInst.getReferencedModuleNameAttr();
1061 bool nextHasInline =
false;
1062 bool nextHasFlatten =
false;
1063 bool nextIsRegular =
false;
1065 assert((isTerminal || !hopInst ||
1066 std::next(it)->mod == hopInst.getReferencedModuleNameAttr()) &&
1067 "recorded next module disagrees with the instance");
1068 auto modOp = symbolTable.lookup<FModuleLike>(nextModName);
1069 assert(modOp &&
"interior namepath module missing -- ran unverified?");
1070 const auto &
info = facts.getModuleInfo(modOp);
1071 nextHasInline =
info.hasInline;
1072 nextHasFlatten =
info.hasFlatten;
1073 nextIsRegular = isa<FModuleOp>(modOp);
1079 bool isEvaporating = nextModName && nextIsRegular && !isOpaqueInstanceHop &&
1080 (isTransitiveFlatten || nextHasInline);
1088 if (isEvaporating && isTerminal) {
1089 assert(hop.inst &&
"expected instance operation");
1090 auto diag = diagAnchor.emitError(
1091 "hierpath points to inlined instance, cannot proceed");
1092 diag.attachNote(hop.inst->getLoc())
1093 <<
"hierpath targets this inlined instance";
1095 if (nextHasInline) {
1096 diag.attachNote(symbolTable.lookup(nextModName)->getLoc())
1097 <<
"target module is marked inline";
1102 assert(flattenCause &&
"flatten-caused absorption without a cause");
1103 diag.attachNote(symbolTable.lookup(flattenCause)->getLoc())
1104 <<
"flattening this module inlines the instance";
1113 if (isOpaqueInstanceHop) {
1114 isTransitiveFlatten = nextHasFlatten;
1115 flattenCause = nextHasFlatten ? nextModName : StringAttr{};
1117 isTransitiveFlatten |= nextHasFlatten;
1119 flattenCause = nextModName;
1122 if (!isEvaporating) {
1127 StringAttr sym = hop.sym;
1128 assert((sym || isTerminal || !hop.inst) &&
1129 "surviving instance hop without an inner symbol");
1130 StringAttr finalSym;
1131 if (currentDest == hop.mod || isTerminal )
1133 survivingHops.push_back({hop.mod, sym,
1136 if (!isTerminal && nextModName)
1137 currentDest = nextModName;
1142 auto *vnla = createVNLA(origSym, survivingHops);
1147 for (
const auto &hop : absPath)
1149 pathRoutingTable[hop.inst].push_back(vnla);
1154#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
1155LLVM_DUMP_METHOD
void NLAPlanner::dump() {
1156 llvm::dbgs() <<
"\nVirtualNLAs (creation order):\n";
1157 for (
auto *vnla : allVNLAs)
1160 llvm::dbgs() <<
"\nPath Routing Table (Instance -> Routed VirtualNLAs):\n";
1164 auto modOf = [](Operation *op) -> StringAttr {
1165 auto mod = op->getParentOfType<FModuleLike>();
1166 return mod ? mod.getModuleNameAttr() : StringAttr();
1168 SmallVector<Operation *> insts;
1169 for (
const auto &[inst, _] : pathRoutingTable)
1170 insts.push_back(inst);
1171 llvm::sort(insts, [&](Operation *a, Operation *b) {
1172 auto am = modOf(a), bm = modOf(b);
1174 return (am ? am.getValue() :
"") < (bm ? bm.getValue() :
"");
1176 return (as ? as.getValue() :
"") < (bs ? bs.getValue() :
"");
1179 for (
auto *inst : insts) {
1180 const auto &vnlas = pathRoutingTable.lookup(inst);
1181 llvm::dbgs() <<
" @" << modOf(inst);
1183 llvm::dbgs() <<
"::" << instSym;
1185 llvm::dbgs() <<
"::<op@" << inst <<
">";
1187 llvm::dbgs() <<
" -> [";
1188 llvm::interleaveComma(vnlas, llvm::dbgs(), [&](VirtualNLA *vnla) {
1189 llvm::dbgs() <<
"#" << vnla->id;
1191 llvm::dbgs() <<
"]\n";
1194 llvm::dbgs() <<
"\n";
1205 InstanceOp instance) {
1206 for (
auto [result, wire] : llvm::zip_equal(instance.getResults(), wires))
1207 mapper.map(result, wire);
1220 StringAttr istName) {
1221 hw::InnerRefAttr foreign;
1222 mlir::AttrTypeReplacer replacer;
1223 replacer.addReplacement([&](hw::InnerRefAttr innerRef) {
1224 auto it = map.find(innerRef);
1225 if (it == map.end()) {
1228 return std::pair{innerRef, WalkResult::skip()};
1230 return std::pair{hw::InnerRefAttr::get(istName, it->second),
1231 WalkResult::skip()};
1233 for (
auto *op : newOps) {
1234 replacer.recursivelyReplaceElementsIn(op);
1236 return op->emitError(
"unsupported inner reference ")
1237 << foreign <<
" found while inlining";
1247 StringAttr istName) {
1248 if (!old || old.empty())
1251 bool anyChanged =
false;
1253 SmallVector<hw::InnerSymPropertiesAttr> newProps;
1254 auto *
context = old.getContext();
1255 for (
auto &prop : old) {
1256 auto newSym = ns.
newName(prop.getName().strref());
1257 if (newSym == prop.getName()) {
1258 newProps.push_back(prop);
1261 auto newSymStrAttr = StringAttr::get(
context, newSym);
1262 auto newProp = hw::InnerSymPropertiesAttr::get(
1263 context, newSymStrAttr, prop.getFieldID(), prop.getSymVisibility());
1265 newProps.push_back(newProp);
1268 auto newSymAttr = anyChanged ? hw::InnerSymAttr::get(
context, newProps) : old;
1270 for (
auto [oldProp, newProp] : llvm::zip(old, newSymAttr)) {
1271 assert(oldProp.getFieldID() == newProp.getFieldID() &&
1272 "uniquing must preserve fieldIDs");
1274 map[hw::InnerRefAttr::get(istName, oldProp.getName())] = newProp.getName();
1298 Inliner(CircuitOp circuit, SymbolTable &symbolTable,
1300 const InliningFacts &inliningFacts, NLAPlanner &nlaPlanner);
1303 LogicalResult
run();
1307 size_t instancesInlined = 0;
1308 size_t instancesFlattened = 0;
1309 size_t deadModules = 0;
1310 size_t hierPathsUpdated = 0;
1311 size_t hierPathsForked = 0;
1312 size_t hierPathsMerged = 0;
1313 size_t hierPathsErased = 0;
1321 struct ModuleInliningContext {
1322 ModuleInliningContext(FModuleOp module)
1323 : module(module), modNamespace(module), b(module.getContext()) {}
1335 struct InliningLevel {
1336 InliningLevel(ModuleInliningContext &mic, FModuleOp childModule)
1337 : mic(mic), childModule(childModule) {}
1339 ModuleInliningContext &mic;
1341 SmallVector<Operation *> newOps;
1342 SmallVector<Value> wires;
1343 FModuleOp childModule;
1346 SmallVector<VirtualNLA *> activeNLAs;
1349 void setActivePaths(ArrayRef<VirtualNLA *> nlas) {
1350 activeNLAs.assign(nlas);
1356 LogicalResult finalize() {
1358 mic.module.getNameAttr());
1366 bool rename(StringRef prefix, Operation *op, InliningLevel &il);
1370 bool renameInstance(StringRef prefix, InliningLevel &il, Operation *oldInst,
1371 Operation *newInst);
1375 void cloneAndRename(StringRef prefix, InliningLevel &il, IRMapping &mapper,
1389 void recordContexts(Operation *newOp,
const InliningLevel &il);
1396 void updateVirtualNLALeafSymbols(Inliner::InliningLevel &il,
1397 hw::InnerSymAttr oldSymAttr,
1398 hw::InnerSymAttr newSymAttr);
1406 void setActiveNLAsForChild(std::optional<ArrayRef<VirtualNLA *>> activeNLAs,
1407 InliningLevel &childIL, Operation *instance);
1412 void mapPortsToWires(StringRef prefix, InliningLevel &il, IRMapping &mapper);
1417 bool shouldFlatten(FModuleLike mod);
1420 bool shouldInline(FModuleLike mod);
1424 LogicalResult checkInstanceParents(InstanceOp instance);
1431 inliningWalk(OpBuilder &builder, Block *block, IRMapping &mapper,
1432 llvm::function_ref<LogicalResult(Operation *op)> process);
1443 LogicalResult processInto(StringRef prefix, InliningLevel &il,
1444 IRMapping &mapper,
bool flatten);
1449 LogicalResult processInstances(FModuleOp module,
bool flatten);
1453 void createDebugScope(InliningLevel &il, InstanceOp instance,
1454 Value parentScope = {});
1458 LogicalResult inlineModules();
1461 void eraseDeadModules();
1472 void appendContextAnno(
Annotation anno, StringAttr origSym,
1473 VirtualNLA *matched, SmallVectorImpl<Attribute> &out);
1477 void canonicalizeContexts();
1481 void rewriteAnnotations();
1484 void writebackHierPaths();
1490 ArrayAttr materializeNamepath(VirtualNLA *vnla);
1503 void canonicalize(VirtualNLA *vnla);
1510 VirtualNLA *canonicalOrSelf(VirtualNLA *vnla)
const {
1511 return canonicalOf.lookup_or(vnla, vnla);
1517 MLIRContext *context;
1520 SymbolTable &symbolTable;
1527 const InliningFacts &inliningFacts;
1528 NLAPlanner &nlaPlanner;
1539 DenseMap<ArrayAttr, VirtualNLA *> canonicalByPath;
1540 DenseMap<VirtualNLA *, VirtualNLA *> canonicalOf;
1544 struct ClaimedSyms {
1546 DenseSet<StringAttr> syms;
1547 void claim(StringAttr sym) { syms.insert(sym); }
1548 bool has(StringAttr sym)
const {
return syms.contains(sym); }
1550 void claim(StringAttr) {}
1551 bool has(StringAttr)
const {
return true; }
1568 DenseMap<Operation *, SmallVector<VirtualNLA *, 2>> clonedAnnoContexts;
1572 SmallVector<debug::ScopeOp> debugScopes;
1578Inliner::Inliner(CircuitOp circuit, SymbolTable &symbolTable,
1580 const InliningFacts &inliningFacts, NLAPlanner &nlaPlanner)
1581 : circuit(circuit),
context(circuit.getContext()), symbolTable(symbolTable),
1582 circuitNamespace(circuitNamespace), inliningFacts(inliningFacts),
1583 nlaPlanner(nlaPlanner) {}
1585LogicalResult Inliner::run() {
1586 if (failed(inlineModules()))
1590 canonicalizeContexts();
1591 rewriteAnnotations();
1592 writebackHierPaths();
1601bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) {
1604 auto updateDebugScope = [&](
auto op) {
1606 op.getScopeMutable().assign(il.debugScope);
1608 if (
auto varOp = dyn_cast<debug::VariableOp>(op))
1609 return updateDebugScope(varOp),
false;
1610 if (
auto scopeOp = dyn_cast<debug::ScopeOp>(op))
1611 return updateDebugScope(scopeOp),
false;
1614 if (
auto nameAttr = op->getAttrOfType<StringAttr>(
"name"))
1615 op->setAttr(
"name", StringAttr::get(op->getContext(),
1616 (prefix + nameAttr.getValue())));
1619 auto symOp = dyn_cast<hw::InnerSymbolOpInterface>(op);
1622 auto oldSymAttr = symOp.getInnerSymAttr();
1625 il.childModule.getNameAttr());
1632 updateVirtualNLALeafSymbols(il, oldSymAttr, newSymAttr);
1633 symOp.setInnerSymbolAttr(newSymAttr);
1635 return newSymAttr != oldSymAttr;
1638bool Inliner::renameInstance(StringRef prefix, InliningLevel &il,
1639 Operation *oldInst, Operation *newInst) {
1644 llvm::dbgs() <<
"Discarding parent debug scope for " << *oldInst <<
"\n";
1648 auto symbolChanged = rename(prefix, newInst, il);
1654 assert(newSymAttr &&
"uniquing dropped an instance sym?");
1655 StringAttr origMod = il.childModule.getModuleNameAttr();
1656 StringAttr destMod = il.mic.module.getModuleNameAttr();
1657 for (
auto *nla : il.activeNLAs) {
1658 for (
auto &hop : nla->getPathMutable()) {
1661 if (hop.origMod == origMod && hop.origSym == oldInstSym &&
1662 hop.finalMod == destMod) {
1663 hop.finalSym = newSymAttr;
1668 return symbolChanged;
1671void Inliner::recordContexts(Operation *newOp,
const InliningLevel &il) {
1672 StringAttr destMod = il.mic.module.getModuleNameAttr();
1681 auto matchContexts = [&](FlatSymbolRefAttr sym, ArrayRef<VirtualNLA *> active,
1682 SmallVectorImpl<VirtualNLA *> &out) {
1683 auto it = nlaPlanner.origToVNLAs.find(sym.getAttr());
1684 if (it == nlaPlanner.origToVNLAs.end())
1686 ArrayRef<VirtualNLA *> group = it->second;
1687 const auto *lo = llvm::lower_bound(active, group.front(),
vnlaIdLess);
1689 std::upper_bound(lo, active.end(), group.back(),
vnlaIdLess);
1690 for (; lo != hi; ++lo) {
1701 auto path = (*lo)->getPath();
1702 assert(!path.empty() &&
"terminal hop is expected to always survive");
1703 if (path.back().finalMod != destMod)
1707 if (!llvm::is_contained(out, *lo))
1716 bool hasNonlocal =
false;
1717 SmallVector<VirtualNLA *, 2> annoContexts;
1719 auto sym = anno.getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
1723 matchContexts(sym, il.activeNLAs, annoContexts);
1725 if (
auto annos = newOp->getAttrOfType<ArrayAttr>(
"annotations"))
1726 for (Attribute attr : annos)
1728 if (
auto portAnnos = newOp->getAttrOfType<ArrayAttr>(
"portAnnotations"))
1729 for (
auto portArray : portAnnos.getAsRange<ArrayAttr>())
1730 for (Attribute attr : portArray)
1733 clonedAnnoContexts[newOp] = std::move(annoContexts);
1736void Inliner::updateVirtualNLALeafSymbols(Inliner::InliningLevel &il,
1737 hw::InnerSymAttr oldSymAttr,
1738 hw::InnerSymAttr newSymAttr) {
1741 if (!oldSymAttr || oldSymAttr == newSymAttr)
1743 assert(newSymAttr &&
"renamed to a null sym?");
1744 StringAttr origMod = il.childModule.getModuleNameAttr();
1745 StringAttr destMod = il.mic.module.getModuleNameAttr();
1746 for (
auto *nla : il.activeNLAs) {
1752 auto &last = nla->getPathMutable().back();
1755 if (last.origMod == origMod && last.finalMod == destMod) {
1756 for (
auto prop : oldSymAttr.getProps()) {
1757 if (last.origSym == prop.getName()) {
1758 last.finalSym = newSymAttr.getSymIfExists(prop.getFieldID());
1766void Inliner::setActiveNLAsForChild(
1767 std::optional<ArrayRef<VirtualNLA *>> activeNLAs, InliningLevel &childIL,
1768 Operation *instance) {
1771 ArrayRef<VirtualNLA *> instNLAs;
1772 if (
auto it = nlaPlanner.pathRoutingTable.find(instance);
1773 it != nlaPlanner.pathRoutingTable.end())
1774 instNLAs = it->second;
1778 childIL.setActivePaths(instNLAs);
1779 }
else if (!activeNLAs->empty() && !instNLAs.empty()) {
1788 ArrayRef<VirtualNLA *> probe = instNLAs, in = *activeNLAs;
1789 if (probe.size() > in.size())
1790 std::swap(probe, in);
1791 SmallVector<VirtualNLA *> childActiveNLAs;
1792 for (
auto *vnla : probe)
1794 childActiveNLAs.push_back(vnla);
1795 childIL.setActivePaths(childActiveNLAs);
1801void Inliner::mapPortsToWires(StringRef prefix, InliningLevel &il,
1802 IRMapping &mapper) {
1803 auto target = il.childModule;
1804 auto portInfo = target.getPorts();
1805 for (
unsigned i = 0, e = target.getNumPorts(); i < e; ++i) {
1806 auto arg = target.getArgument(i);
1807 auto type = type_cast<FIRRTLType>(arg.getType());
1809 auto oldSymAttr = portInfo[i].sym;
1812 il.mic.modNamespace, target.getNameAttr());
1819 updateVirtualNLALeafSymbols(il, oldSymAttr, newSymAttr);
1823 auto wireOp = WireOp::create(
1824 il.mic.b, target.getLoc(), type,
1825 StringAttr::get(
context, (prefix + portInfo[i].getName())),
1826 NameKindEnumAttr::get(
context, NameKindEnum::DroppableName),
1829 recordContexts(wireOp, il);
1830 Value wire = wireOp.getResult();
1831 il.wires.push_back(wire);
1832 mapper.map(arg, wire);
1838void Inliner::cloneAndRename(StringRef prefix, InliningLevel &il,
1839 IRMapping &mapper, Operation &op) {
1845 assert(op.getNumRegions() == 0 &&
1846 "operation with regions should not reach cloneAndRename");
1847 auto *newOp = il.mic.b.cloneWithoutRegions(op, mapper);
1850 if (isa<FInstanceLike>(&op))
1851 renameInstance(prefix, il, &op, newOp);
1853 rename(prefix, newOp, il);
1855 recordContexts(newOp, il);
1857 il.newOps.push_back(newOp);
1862bool Inliner::shouldFlatten(FModuleLike mod) {
1863 return inliningFacts.hasFlatten(mod);
1866bool Inliner::shouldInline(FModuleLike mod) {
1867 return inliningFacts.hasInline(mod);
1870LogicalResult Inliner::inliningWalk(
1871 OpBuilder &builder, Block *block, IRMapping &mapper,
1872 llvm::function_ref<LogicalResult(Operation *op)> process) {
1875 OpBuilder::InsertPoint target;
1876 Block::iterator source;
1879 SmallVector<IPs> inliningStack;
1883 inliningStack.push_back(IPs{builder.saveInsertionPoint(), block->begin()});
1884 OpBuilder::InsertionGuard guard(builder);
1886 while (!inliningStack.empty()) {
1887 auto target = inliningStack.back().target;
1888 builder.restoreInsertionPoint(target);
1892 auto &ips = inliningStack.back();
1893 source = &*ips.source;
1894 auto end = source->getBlock()->end();
1895 if (++ips.source == end)
1896 inliningStack.pop_back();
1899 if (source->getNumRegions() == 0) {
1901 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
1902 if (failed(process(source)))
1904 assert(builder.saveInsertionPoint().getPoint() == target.getPoint());
1910 if (!isa<LayerBlockOp, WhenOp, MatchOp>(source))
1911 return source->emitError(
"unsupported operation '")
1912 << source->getName() <<
"' cannot be inlined";
1916 auto *newOp = builder.cloneWithoutRegions(*source, mapper);
1917 for (
auto [newRegion, oldRegion] :
llvm::reverse(
1918 llvm::zip_equal(newOp->getRegions(), source->getRegions()))) {
1919 if (oldRegion.empty()) {
1920 assert(newRegion.empty());
1924 assert(oldRegion.hasOneBlock());
1926 auto &oldBlock = oldRegion.getBlocks().front();
1927 auto &newBlock = newRegion.emplaceBlock();
1928 mapper.map(&oldBlock, &newBlock);
1930 for (
auto arg : oldBlock.getArguments())
1931 mapper.map(arg, newBlock.addArgument(arg.getType(), arg.
getLoc()));
1933 if (oldBlock.empty())
1936 inliningStack.push_back(
1937 IPs{OpBuilder::InsertPoint(&newBlock, newBlock.begin()),
1944LogicalResult Inliner::checkInstanceParents(InstanceOp instance) {
1945 auto *parent = instance->getParentOp();
1946 while (!isa<FModuleLike>(parent)) {
1947 if (!isa<LayerBlockOp>(parent))
1948 return instance->emitError(
"cannot inline instance")
1949 .attachNote(parent->getLoc())
1950 <<
"containing operation '" << parent->getName()
1951 <<
"' not safe to inline into";
1952 parent = parent->getParentOp();
1958LogicalResult Inliner::processInto(StringRef prefix, InliningLevel &il,
1959 IRMapping &mapper,
bool flatten) {
1960 auto target = il.childModule;
1962 LLVM_DEBUG(llvm::dbgs() << (flatten ?
"flattening " :
"inlining ")
1963 << target.getModuleName() <<
" into "
1964 << il.mic.module.getModuleName() <<
"\n");
1966 auto visit = [&](Operation *op) {
1968 auto instance = getInlinableInstance(op);
1970 cloneAndRename(prefix, il, mapper, *op);
1975 auto *moduleOp = symbolTable.lookup(instance.getModuleName());
1976 auto childModule = dyn_cast<FModuleOp>(moduleOp);
1978 assert(inliningFacts.isKnownLive(moduleOp) &&
1979 "a kept non-module instance must target a live module");
1980 cloneAndRename(prefix, il, mapper, *op);
1986 if (!flatten && !shouldInline(childModule)) {
1987 assert(inliningFacts.isLive(childModule) &&
1988 "a kept child module must be live");
1989 cloneAndRename(prefix, il, mapper, *op);
1993 if (failed(checkInstanceParents(instance)))
1996 ++(flatten ? stats.instancesFlattened : stats.instancesInlined);
1998 InliningLevel childIL(il.mic, childModule);
1999 setActiveNLAsForChild(il.activeNLAs, childIL, instance);
2000 createDebugScope(childIL, instance, il.debugScope);
2003 auto nestedPrefix = (prefix + instance.getName() +
"_").str();
2004 mapPortsToWires(nestedPrefix, childIL, mapper);
2008 if (failed(processInto(nestedPrefix, childIL, mapper,
2009 flatten || shouldFlatten(childModule))))
2011 return childIL.finalize();
2014 return inliningWalk(il.mic.b, target.getBodyBlock(), mapper, visit);
2017LogicalResult Inliner::processInstances(FModuleOp module,
bool flatten) {
2018 auto moduleName =
module.getNameAttr();
2019 ModuleInliningContext mic(module);
2021 LLVM_DEBUG(llvm::dbgs() <<
"inlining instances within " << moduleName
2023 auto visit = [&](FInstanceLike instanceLike) {
2024 auto instance = getInlinableInstance(instanceLike.getOperation());
2026 return WalkResult::advance();
2028 auto moduleOp = symbolTable.lookup<FModuleLike>(instance.getModuleName());
2029 assert(moduleOp &&
"instance target missing -- ran unverified?");
2030 auto target = dyn_cast<FModuleOp>(*moduleOp);
2032 assert(inliningFacts.isLive(moduleOp) &&
2033 "a kept non-module instance must target a live module");
2034 return WalkResult::advance();
2038 if (!flatten && !shouldInline(target))
2039 return WalkResult::advance();
2041 if (failed(checkInstanceParents(instance)))
2042 return WalkResult::interrupt();
2044 ++(flatten ? stats.instancesFlattened : stats.instancesInlined);
2049 mic.b.setInsertionPoint(instance);
2051 InliningLevel childIL(mic, target);
2052 setActiveNLAsForChild( std::nullopt,
2054 createDebugScope(childIL, instance);
2056 auto nestedPrefix = (instance.getName() +
"_").str();
2057 mapPortsToWires(nestedPrefix, childIL, mapper);
2058 for (
unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
2059 instance.getResult(i).replaceAllUsesWith(childIL.wires[i]);
2062 if (failed(processInto(nestedPrefix, childIL, mapper,
2063 flatten || shouldFlatten(target))) ||
2064 failed(childIL.finalize()))
2065 return WalkResult::interrupt();
2068 return WalkResult::skip();
2071 return failure(module.getBodyBlock()
2072 ->walk<mlir::WalkOrder::PreOrder>(visit)
2076void Inliner::createDebugScope(InliningLevel &il, InstanceOp instance,
2077 Value parentScope) {
2078 auto op = debug::ScopeOp::create(
2079 il.mic.b, instance.getLoc(), instance.getInstanceNameAttr(),
2080 instance.getModuleNameAttr().getAttr(), parentScope);
2081 debugScopes.push_back(op);
2085LogicalResult Inliner::inlineModules() {
2091 for (
auto moduleOp : inliningFacts.getSchedule()) {
2092 const auto &
info = inliningFacts.getModuleInfo(moduleOp);
2101 if (
info.hasFlatten ||
info.hasInline)
2103 return anno.
isClass(flattenAnnoClass, inlineAnnoClass);
2105 if (failed(processInstances(moduleOp,
info.hasFlatten)))
2111 for (
auto scopeOp :
llvm::reverse(debugScopes))
2112 if (scopeOp.use_empty())
2114 debugScopes.clear();
2119void Inliner::eraseDeadModules() {
2120 for (
auto mod :
llvm::make_early_inc_range(circuit.getOps<FModuleLike>())) {
2121 if (inliningFacts.isKnownLive(mod))
2124 ++stats.deadModules;
2130ArrayAttr Inliner::materializeNamepath(VirtualNLA *vnla) {
2131 SmallVector<Attribute> pathAttrs;
2132 for (
auto &hop : vnla->getPath()) {
2139 assert((hop.finalSym || !hop.origSym) &&
2140 "materializing a hop whose final symbol was never filled");
2142 pathAttrs.push_back(InnerRefAttr::get(hop.finalMod, hop.finalSym));
2144 pathAttrs.push_back(FlatSymbolRefAttr::get(hop.finalMod));
2146 return ArrayAttr::get(
context, pathAttrs);
2149void Inliner::canonicalize(VirtualNLA *vnla) {
2153 assert(!vnla->isLocal() &&
"local VNLAs have no hierpath to canonicalize");
2154 assert(!vnla->realizedSym &&
"context canonicalized twice");
2156 canonicalByPath.try_emplace(materializeNamepath(vnla), vnla)
2158 canonicalOf[vnla] = canon;
2159 if (canon == vnla) {
2162 assert(claimed.has(vnla->origSym) &&
2163 "primary claims origSym before any fork canonicalizes (I15)");
2164 vnla->realizedSym = StringAttr::get(
2169void Inliner::appendContextAnno(
Annotation anno, StringAttr origSym,
2170 VirtualNLA *matched,
2171 SmallVectorImpl<Attribute> &out) {
2172 if (matched->isLocal()) {
2174 out.push_back(anno.
getAttr());
2177 matched->wasUsed =
true;
2178 StringAttr canonSym = canonicalOrSelf(matched)->realizedSym;
2180 if (canonSym == origSym) {
2181 out.push_back(anno.
getAttr());
2184 anno.
setMember(
"circt.nonlocal", FlatSymbolRefAttr::get(canonSym));
2185 out.push_back(anno.
getAttr());
2188void Inliner::canonicalizeContexts() {
2203 for (
size_t i = 0, e = nlaPlanner.allVNLAs.size(); i < e;) {
2204 StringAttr origSym = nlaPlanner.allVNLAs[i]->origSym;
2205 size_t groupStart = i;
2206 while (i < e && nlaPlanner.allVNLAs[i]->origSym == origSym)
2208 ArrayRef<VirtualNLA *> group(&nlaPlanner.allVNLAs[groupStart],
2214 VirtualNLA *primary =
nullptr;
2215 for (
auto *v : group)
2216 if (!v->isLocal()) {
2221 primary = group.front();
2228 primary->realizedSym = origSym;
2229 claimed.claim(origSym);
2230 canonicalOf[primary] = primary;
2232 if (!primary->isLocal())
2233 canonicalByPath.try_emplace(materializeNamepath(primary), primary);
2239 for (
auto *v : group) {
2240 if (v == primary || v->isLocal())
2247void Inliner::rewriteAnnotations() {
2250 auto rewriteAnnos = [&](ArrayAttr annos, StringAttr modName,
2251 const SmallVectorImpl<VirtualNLA *> *recorded,
2252 SmallVectorImpl<Attribute> &newAnnos) {
2253 for (Attribute attr : annos) {
2255 auto sym = anno.
getMember<FlatSymbolRefAttr>(
"circt.nonlocal");
2257 newAnnos.push_back(anno.
getAttr());
2264 for (
auto *matched : *recorded)
2265 if (matched->origSym == sym.getAttr())
2266 appendContextAnno(anno, sym.getAttr(), matched, newAnnos);
2272 auto it = nlaPlanner.origToVNLAs.find(sym.getAttr());
2273 if (it == nlaPlanner.origToVNLAs.end())
2275 for (
auto *matched : it->second) {
2279 if (matched->getPath().back().finalMod != modName)
2281 appendContextAnno(anno, sym.getAttr(), matched, newAnnos);
2286 auto rewriteOpAnnos = [&](Operation *op, StringAttr modName) {
2287 const SmallVectorImpl<VirtualNLA *> *recorded =
nullptr;
2288 if (
auto it = clonedAnnoContexts.find(op); it != clonedAnnoContexts.end())
2289 recorded = &it->second;
2294 SmallVector<Attribute> newAnnotations;
2295 rewriteAnnos(annos, modName, recorded, newAnnotations);
2301 if (
auto portAnnos = op->getAttrOfType<ArrayAttr>(
"portAnnotations")) {
2302 SmallVector<Attribute> newPortAnnotations;
2303 SmallVector<Attribute> newAnnotations;
2304 for (
auto portArray : portAnnos.getAsRange<ArrayAttr>()) {
2305 newAnnotations.clear();
2306 rewriteAnnos(portArray, modName, recorded, newAnnotations);
2307 newPortAnnotations.push_back(ArrayAttr::get(
context, newAnnotations));
2309 op->setAttr(
"portAnnotations",
2310 ArrayAttr::get(
context, newPortAnnotations));
2313 auto rewriteModuleAnnos = [&](FModuleLike fmodule) {
2314 StringAttr modName = fmodule.getModuleNameAttr();
2315 fmodule.walk([&](Operation *op) { rewriteOpAnnos(op, modName); });
2323 SmallVector<FModuleOp> bodyModules;
2324 for (
auto fmodule : circuit.getOps<FModuleLike>()) {
2325 if (
auto regular = dyn_cast<FModuleOp>(*fmodule))
2326 bodyModules.push_back(regular);
2328 rewriteModuleAnnos(fmodule);
2330 mlir::parallelForEach(
context, bodyModules, [&](FModuleOp fmodule) {
2331 rewriteModuleAnnos(fmodule);
2335void Inliner::writebackHierPaths() {
2340 for (
auto &[dup, canon] : canonicalOf) {
2343 ++stats.hierPathsMerged;
2345 canon->wasUsed =
true;
2353 for (
size_t i = 0, e = nlaPlanner.allVNLAs.size(); i < e;) {
2354 StringAttr origSym = nlaPlanner.allVNLAs[i]->origSym;
2355 unsigned claimants = 0;
2356 for (; i < e && nlaPlanner.allVNLAs[i]->origSym == origSym; ++i) {
2357 auto *v = nlaPlanner.allVNLAs[i];
2358 if (v->realizedSym == origSym && canonicalOrSelf(v) == v)
2362 "retention: each origSym must have exactly one primary claimant");
2369 auto &existingPaths = nlaPlanner.hierPathOps;
2380 StringAttr curGroup;
2385 DenseSet<StringAttr> retainedPaths;
2386 for (
auto *vnla : nlaPlanner.allVNLAs) {
2387 if (vnla->origSym != curGroup) {
2388 curGroup = vnla->origSym;
2389 if (
auto it = existingPaths.find(curGroup); it != existingPaths.end())
2390 b.setInsertionPointAfter(it->second);
2394 if (canonicalOrSelf(vnla) != vnla)
2399 bool isPrimary = vnla->realizedSym == vnla->origSym;
2400 if (!isPrimary && (vnla->isLocal() || !vnla->wasUsed))
2403 auto arrayAttr = materializeNamepath(vnla);
2405 auto origIt = existingPaths.find(vnla->origSym);
2406 assert(origIt != existingPaths.end() &&
2407 "origSym has no source hw.hierpath");
2410 if (vnla->realizedSym == vnla->origSym) {
2412 if (arrayAttr != origIt->second.getNamepathAttr()) {
2413 origIt->second.setNamepathAttr(arrayAttr);
2414 ++stats.hierPathsUpdated;
2416 retainedPaths.insert(vnla->origSym);
2422 auto hp = hw::HierPathOp::create(b, origIt->second.getLoc(),
2423 vnla->realizedSym, arrayAttr);
2425 ++stats.hierPathsForked;
2427 for (
auto &[sym, deadPath] : existingPaths) {
2428 if (retainedPaths.contains(sym))
2432 ++stats.hierPathsErased;
2444class InlinerPass :
public circt::firrtl::impl::InlinerBase<InlinerPass> {
2447 void runOnOperation()
override {
2449 auto circuit = getOperation();
2450 auto &symbolTable = getAnalysis<SymbolTable>();
2451 auto &instanceGraph = getAnalysis<InstanceGraph>();
2454 auto facts = InliningFacts::compute(circuit, instanceGraph, symbolTable);
2456 return signalPassFailure();
2458 llvm::dbgs() <<
"\n";
2464 NLAPlanner nlaPlanner(circuit, symbolTable, instanceGraph, *facts);
2465 if (failed(nlaPlanner.run()))
2466 return signalPassFailure();
2468 llvm::dbgs() <<
"\n";
2472 numHierPathsEndInnerSym += nlaPlanner.stats.endInnerSym;
2473 numHierPathsEndModule += nlaPlanner.stats.endModule;
2477 Inliner inliner(circuit, symbolTable, circuitNamespace, *facts, nlaPlanner);
2478 if (failed(inliner.run()))
2479 signalPassFailure();
2481 numInstancesInlined += inliner.stats.instancesInlined;
2482 numInstancesFlattened += inliner.stats.instancesFlattened;
2483 numDeadModules += inliner.stats.deadModules;
2484 numHierPathsUpdated += inliner.stats.hierPathsUpdated;
2485 numHierPathsForked += inliner.stats.hierPathsForked;
2486 numHierPathsMerged += inliner.stats.hierPathsMerged;
2487 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)