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;
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];
388 AnnotationSet anno(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)
604 h = llvm::hash_combine(h, hop.mod.getAsOpaquePointer(), hop.inst,
605 hop.sym.getAsOpaquePointer());
615 NLAPlanner(CircuitOp circuit, SymbolTable &symbolTable,
616 InstanceGraph &instanceGraph,
const InliningFacts &facts)
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,
1301 CircuitNamespace &circuitNamespace,
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()) {}
1330 hw::InnerSymbolNamespace modNamespace;
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);
1522 SymbolTable &symbolTable;
1526 CircuitNamespace &circuitNamespace;
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,
1581 CircuitNamespace &circuitNamespace,
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;
1720 auto visitAnno = [&](Annotation anno) {
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)
1729 visitAnno(Annotation(attr));
1730 if (
auto portAnnos = newOp->getAttrOfType<ArrayAttr>(
"portAnnotations"))
1731 for (
auto portArray : portAnnos.getAsRange<ArrayAttr>())
1732 for (Attribute attr : portArray)
1733 visitAnno(Annotation(attr));
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)
1795 if (llvm::binary_search(in, vnla,
vnlaIdLess))
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);
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) {
2255 Annotation anno(attr);
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);
2297 AnnotationSet(newAnnotations,
context).applyToOperation(op);
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;
2478 CircuitNamespace circuitNamespace(circuit);
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)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
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.
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.
static AnnotationSet forPort(FModuleLike op, size_t portNo)
Get an annotation set for the specified port.
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.
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.
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.
BaseTy type_cast(Type type)
igraph::InstanceRecord InstanceRecord
ArrayAttr getAnnotationsIfPresent(Operation *op)
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
std::map< std::string, std::set< std::string > > InstanceGraph
Iterates over the handshake::FuncOp's in the program to build an instance graph.
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.
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
llvm::hash_code hash_value(const DenseSet< T > &set)
static unsigned getHashValue(const TrimmedPathRef &key)
static bool isEqual(const TrimmedPathRef &a, const TrimmedPathRef &b)