circt/lib/Dialect/FIRRTL/Transforms/ModuleInliner.cpp

1437 lines
56 KiB
C++

//===- ModuleInliner.cpp - FIRRTL module inlining ---------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements FIRRTL module instance inlining.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
#include "circt/Dialect/FIRRTL/CHIRRTLDialect.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/FIRRTLUtils.h"
#include "circt/Dialect/FIRRTL/FIRRTLVisitors.h"
#include "circt/Dialect/FIRRTL/Namespace.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/InnerSymbolNamespace.h"
#include "circt/Support/Debug.h"
#include "circt/Support/LLVM.h"
#include "mlir/IR/IRMapping.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/SetOperations.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/TypeSwitch.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/FormatVariadic.h"
#define DEBUG_TYPE "firrtl-inliner"
using namespace circt;
using namespace firrtl;
using namespace chirrtl;
using hw::InnerRefAttr;
using llvm::BitVector;
using InnerRefToNewNameMap = DenseMap<hw::InnerRefAttr, StringAttr>;
//===----------------------------------------------------------------------===//
// Module Inlining Support
//===----------------------------------------------------------------------===//
namespace {
/// A representation of an NLA that can be mutated. This is intended to be used
/// in situations where you want to make a series of modifications to an NLA
/// while also being able to query information about it. Finally, the NLA is
/// written back to the IR to replace the original NLA.
class MutableNLA {
// Storage of the NLA this represents.
hw::HierPathOp nla;
// A namespace that can be used to generate new symbol names if needed.
CircuitNamespace *circuitNamespace;
/// A mapping of symbol to index in the NLA.
DenseMap<Attribute, unsigned> symIdx;
/// Records which elements of the path are inlined.
BitVector inlinedSymbols;
/// The point after which the NLA is flattened. A value of "-1" indicates
/// that this was never set.
signed flattenPoint = -1;
/// Indicates if the _original_ NLA is dead and should be deleted. Updates
/// may still need to be written if the newTops vector below is non-empty.
bool dead = false;
/// Indicates if the NLA is only used to target a module
/// (i.e., no ports or operations use this HierPathOp).
/// This is needed to help determine when the HierPathOp is dead:
/// if we inline/flatten a module, NLA's targeting (only) that module
/// are now dead.
bool moduleOnly = false;
/// Stores new roots for the NLA. If this is non-empty, then it indicates
/// that the NLA should be copied and re-topped using the roots stored here.
/// This is non-empty when the NLA's root is inlined and the original NLA
/// migrates to each instantiator of the original NLA.
SmallVector<InnerRefAttr> newTops;
/// Cache of roots that this module participates in. This is only valid when
/// newTops is non-empty.
DenseSet<StringAttr> rootSet;
/// Stores the size of the NLA path.
unsigned int size;
/// A mapping of module name to _new_ inner symbol name. For convenience of
/// how this pass works (operations are inlined *into* a new module), the key
/// is the NEW module, after inlining/flattening as opposed to on the old
/// module.
DenseMap<Attribute, StringAttr> renames;
/// Lookup a reference and apply any renames to it. This requires both the
/// module where the NEW reference lives (to lookup the rename) and the
/// original ID of the reference (to fallback to if the reference was not
/// renamed).
StringAttr lookupRename(Attribute lastMod, unsigned idx = 0) {
if (renames.count(lastMod))
return renames[lastMod];
return nla.refPart(idx);
}
public:
MutableNLA(hw::HierPathOp nla, CircuitNamespace *circuitNamespace)
: nla(nla), circuitNamespace(circuitNamespace),
inlinedSymbols(BitVector(nla.getNamepath().size(), true)),
size(nla.getNamepath().size()) {
for (size_t i = 0, e = size; i != e; ++i)
symIdx.insert({nla.modPart(i), i});
}
/// This default, erroring constructor exists because the pass uses
/// `DenseMap<Attribute, MutableNLA>`. `DenseMap` requires a default
/// constructor for the value type because its `[]` operator (which returns a
/// reference) must default construct the value type for a non-existent key.
/// This default constructor is never supposed to be used because the pass
/// prepopulates a `DenseMap<Attribute, MutableNLA>` before it runs and
/// thereby guarantees that `[]` will always hit and never need to use the
/// default constructor.
MutableNLA() {
llvm_unreachable(
"the default constructor for MutableNLA should never be used");
}
/// Set the state of the mutable NLA to indicate that the _original_ NLA
/// should be removed when updates are applied.
void markDead() { dead = true; }
/// Set the state of the mutable NLA to indicate the only target is a module.
void markModuleOnly() { moduleOnly = true; }
/// Return the original NLA that this was pointing at.
hw::HierPathOp getNLA() { return nla; }
/// Writeback updates accumulated in this MutableNLA to the IR. This method
/// should only ever be called once and, if a writeback occurrs, the
/// MutableNLA is NOT updated for further use. Interacting with the
/// MutableNLA in any way after calling this method may result in crashes.
/// (This is done to save unnecessary state cleanup of a pass-private
/// utility.)
hw::HierPathOp applyUpdates() {
// Delete an NLA which is either dead or has been made local.
if (isLocal() || isDead()) {
nla.erase();
return nullptr;
}
// The NLA was never updated, just return the NLA and do not writeback
// anything.
if (inlinedSymbols.all() && newTops.empty() && flattenPoint == -1 &&
renames.empty())
return nla;
// The NLA has updates. Generate a new NLA with the same symbol and delete
// the original NLA.
OpBuilder b(nla);
auto writeBack = [&](StringAttr root, StringAttr sym) -> hw::HierPathOp {
SmallVector<Attribute> namepath;
StringAttr lastMod;
// Root of the namepath.
if (!inlinedSymbols.test(1))
lastMod = root;
else
namepath.push_back(InnerRefAttr::get(root, lookupRename(root)));
// Everything in the middle of the namepath (excluding the root and leaf).
for (signed i = 1, e = inlinedSymbols.size() - 1; i != e; ++i) {
if (i == flattenPoint) {
lastMod = nla.modPart(i);
break;
}
if (!inlinedSymbols.test(i + 1)) {
if (!lastMod)
lastMod = nla.modPart(i);
continue;
}
// Update the inner symbol if it has been renamed.
auto modPart = lastMod ? lastMod : nla.modPart(i);
auto refPart = lookupRename(modPart, i);
namepath.push_back(InnerRefAttr::get(modPart, refPart));
lastMod = {};
}
// Leaf of the namepath.
auto modPart = lastMod ? lastMod : nla.modPart(size - 1);
auto refPart = lookupRename(modPart, size - 1);
if (refPart)
namepath.push_back(InnerRefAttr::get(modPart, refPart));
else
namepath.push_back(FlatSymbolRefAttr::get(modPart));
auto hp = b.create<hw::HierPathOp>(b.getUnknownLoc(), sym,
b.getArrayAttr(namepath));
hp.setVisibility(nla.getVisibility());
return hp;
};
hw::HierPathOp last;
assert(!dead || !newTops.empty());
if (!dead)
last = writeBack(nla.root(), nla.getNameAttr());
for (auto root : newTops)
last = writeBack(root.getModule(), root.getName());
nla.erase();
return last;
}
void dump() {
llvm::errs() << " - orig: " << nla << "\n"
<< " new: " << *this << "\n"
<< " dead: " << dead << "\n"
<< " isDead: " << isDead() << "\n"
<< " isModuleOnly: " << isModuleOnly() << "\n"
<< " isLocal: " << isLocal() << "\n"
<< " inlinedSymbols: [";
llvm::interleaveComma(inlinedSymbols.getData(), llvm::errs(), [](auto a) {
llvm::errs() << llvm::formatv("{0:x-}", a);
});
llvm::errs() << "]\n"
<< " flattenPoint: " << flattenPoint << "\n"
<< " renames:\n";
for (auto rename : renames)
llvm::errs() << " - " << rename.first << " -> " << rename.second
<< "\n";
}
/// Write the current state of this MutableNLA to a string using a format that
/// looks like the NLA serialization. This is intended to be used for
/// debugging purposes.
friend llvm::raw_ostream &operator<<(llvm::raw_ostream &os, MutableNLA &x) {
auto writePathSegment = [&](StringAttr mod, StringAttr sym = {}) {
if (sym)
os << "#hw.innerNameRef<";
os << "@" << mod.getValue();
if (sym)
os << "::@" << sym.getValue() << ">";
};
auto writeOne = [&](StringAttr root, StringAttr sym) {
os << "firrtl.nla @" << sym.getValue() << " [";
StringAttr lastMod;
// Root of the namepath.
if (!x.inlinedSymbols.test(1))
lastMod = root;
else
writePathSegment(root, x.lookupRename(root));
// Everything in the middle of the namepath (excluding the root and leaf).
bool needsComma = false;
for (signed i = 1, e = x.inlinedSymbols.size() - 1; i != e; ++i) {
if (i == x.flattenPoint) {
lastMod = x.nla.modPart(i);
break;
}
if (!x.inlinedSymbols.test(i + 1)) {
if (!lastMod)
lastMod = x.nla.modPart(i);
continue;
}
if (needsComma)
os << ", ";
auto modPart = lastMod ? lastMod : x.nla.modPart(i);
auto refPart = x.nla.refPart(i);
if (x.renames.count(modPart))
refPart = x.renames[modPart];
writePathSegment(modPart, refPart);
needsComma = true;
lastMod = {};
}
// Leaf of the namepath.
os << ", ";
auto modPart = lastMod ? lastMod : x.nla.modPart(x.size - 1);
auto refPart = x.nla.refPart(x.size - 1);
if (x.renames.count(modPart))
refPart = x.renames[modPart];
writePathSegment(modPart, refPart);
os << "]";
};
SmallVector<InnerRefAttr> tops;
if (!x.dead)
tops.push_back(InnerRefAttr::get(x.nla.root(), x.nla.getNameAttr()));
tops.append(x.newTops.begin(), x.newTops.end());
bool multiary = !x.newTops.empty();
if (multiary)
os << "[";
llvm::interleaveComma(tops, os, [&](InnerRefAttr a) {
writeOne(a.getModule(), a.getName());
});
if (multiary)
os << "]";
return os;
}
/// Returns true if this NLA is dead. There are several reasons why this
/// could be dead:
/// 1. This NLA has no uses and was not re-topped.
/// 2. This NLA was flattened and its leaf reference is a Module.
bool isDead() { return dead && newTops.empty(); }
/// Returns true if this NLA targets only a module.
bool isModuleOnly() { return moduleOnly; }
/// Returns true if this NLA is local. For this to be local, every module
/// after the root (up to the flatten point or the end) must be inlined. The
/// root is never truly inlined as inlining the root just sets a new root.
bool isLocal() {
unsigned end = flattenPoint > -1 ? flattenPoint + 1 : inlinedSymbols.size();
return inlinedSymbols.find_first_in(1, end) == -1;
}
/// Return true if this NLA has a root that originates from a specific module.
bool hasRoot(FModuleLike mod) {
return (isDead() && nla.root() == mod.getModuleNameAttr()) ||
rootSet.contains(mod.getModuleNameAttr());
}
/// Return true if either this NLA is rooted at modName, or is retoped to it.
bool hasRoot(StringAttr modName) {
return (nla.root() == modName) || rootSet.contains(modName);
}
/// Mark a module as inlined. This will remove it from the NLA.
void inlineModule(FModuleOp module) {
auto sym = module.getNameAttr();
assert(sym != nla.root() && "unable to inline the root module");
assert(symIdx.count(sym) && "module is not in the symIdx map");
auto idx = symIdx[sym];
inlinedSymbols.reset(idx);
// If we inlined the last module in the path and the NLA targets only that
// module, then this NLA is dead.
if (idx == size - 1 && moduleOnly)
markDead();
}
/// Mark a module as flattened. This has the effect of inlining all of its
/// children. Also mark the NLA as dead if the leaf reference of this NLA is
/// a module and the only target is a module.
void flattenModule(FModuleOp module) {
auto sym = module.getNameAttr();
assert(symIdx.count(sym) && "module is not in the symIdx map");
auto idx = symIdx[sym] - 1;
flattenPoint = idx;
// If the NLA only targets a module and we're flattening the NLA,
// then the NLA must be dead. Mark it as such.
if (moduleOnly)
markDead();
}
StringAttr reTop(FModuleOp module) {
StringAttr sym = nla.getSymNameAttr();
if (!newTops.empty())
sym = StringAttr::get(nla.getContext(),
circuitNamespace->newName(sym.getValue()));
newTops.push_back(InnerRefAttr::get(module.getNameAttr(), sym));
rootSet.insert(module.getNameAttr());
symIdx.insert({module.getNameAttr(), 0});
markDead();
return sym;
}
ArrayRef<InnerRefAttr> getAdditionalSymbols() { return ArrayRef(newTops); }
void setInnerSym(Attribute module, StringAttr innerSym) {
assert(symIdx.count(module) && "Mutable NLA did not contain symbol");
assert(!renames.count(module) && "Module already renamed");
renames.insert({module, innerSym});
}
};
} // namespace
/// This function is used after inlining a module, to handle the conversion
/// between module ports and instance results. This maps each wire to the
/// result of the instance operation. When future operations are cloned from
/// the current block, they will use the value of the wire instead of the
/// instance results.
static void mapResultsToWires(IRMapping &mapper, SmallVectorImpl<Value> &wires,
InstanceOp instance) {
for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i) {
auto result = instance.getResult(i);
auto wire = wires[i];
mapper.map(result, wire);
}
}
/// Process each operation, updating InnerRefAttr's using the specified map
/// and the given name as the containing IST of the mapped-to sym names.
static void replaceInnerRefUsers(ArrayRef<Operation *> newOps,
const InnerRefToNewNameMap &map,
StringAttr istName) {
mlir::AttrTypeReplacer replacer;
replacer.addReplacement([&](hw::InnerRefAttr innerRef) {
auto it = map.find(innerRef);
// TODO: what to do with users that aren't local (or not mapped?).
assert(it != map.end());
return std::pair{hw::InnerRefAttr::get(istName, it->second),
WalkResult::skip()};
});
llvm::for_each(newOps,
[&](auto *op) { replacer.recursivelyReplaceElementsIn(op); });
}
/// Generate and creating map entries for new inner symbol based on old one
/// and an appropriate namespace for creating unique names for each.
static hw::InnerSymAttr uniqueInNamespace(hw::InnerSymAttr old,
InnerRefToNewNameMap &map,
hw::InnerSymbolNamespace &ns,
StringAttr istName) {
if (!old || old.empty())
return old;
bool anyChanged = false;
SmallVector<hw::InnerSymPropertiesAttr> newProps;
auto *context = old.getContext();
for (auto &prop : old) {
auto newSym = ns.newName(prop.getName().strref());
if (newSym == prop.getName()) {
newProps.push_back(prop);
continue;
}
auto newSymStrAttr = StringAttr::get(context, newSym);
auto newProp = hw::InnerSymPropertiesAttr::get(
context, newSymStrAttr, prop.getFieldID(), prop.getSymVisibility());
anyChanged = true;
newProps.push_back(newProp);
}
auto newSymAttr = anyChanged ? hw::InnerSymAttr::get(context, newProps) : old;
for (auto [oldProp, newProp] : llvm::zip(old, newSymAttr)) {
assert(oldProp.getFieldID() == newProp.getFieldID());
// Map InnerRef to this inner sym -> new inner sym.
map[hw::InnerRefAttr::get(istName, oldProp.getName())] = newProp.getName();
}
return newSymAttr;
}
//===----------------------------------------------------------------------===//
// Inliner
//===----------------------------------------------------------------------===//
/// Inlines, flattens, and removes dead modules in a circuit.
///
/// The inliner works in a top down fashion, starting from the top level module,
/// and inlines every possible instance. With this method of recursive top-down
/// inlining, each operation will be cloned directly to its final location.
///
/// The inliner uses a worklist to track which modules need to be processed.
/// When an instance op is not inlined, the referenced module is added to the
/// worklist. When the inliner is complete, it deletes every un-processed
/// module: either all instances of the module were inlined, or it was not
/// reachable from the top level module.
///
/// During the inlining process, every cloned operation with a name must be
/// prefixed with the instance's name. The top-down process means that we know
/// the entire desired prefix when we clone an operation, and can set the name
/// attribute once. This means that we will not create any intermediate name
/// attributes (which will be interned by the compiler), and helps keep down the
/// total memory usage.
namespace {
class Inliner {
public:
/// Initialize the inliner to run on this circuit.
Inliner(CircuitOp circuit, SymbolTable &symbolTable);
/// Run the inliner.
void run();
private:
/// Inlining context, one per module being inlined into.
/// Cleans up backedges on destruction.
struct ModuleInliningContext {
ModuleInliningContext(FModuleOp module)
: module(module), modNamespace(module), b(module.getContext()) {}
/// Top-level module for current inlining task.
FModuleOp module;
/// Namespace for generating new names in `module`.
hw::InnerSymbolNamespace modNamespace;
/// Builder, insertion point into module.
OpBuilder b;
};
/// One inlining level, created for each instance inlined or flattened.
/// All inner symbols renamed are recorded in relocatedInnerSyms,
/// and new operations in newOps. On destruction newOps are fixed up.
struct InliningLevel {
InliningLevel(ModuleInliningContext &mic, FModuleOp childModule)
: mic(mic), childModule(childModule) {}
/// Top-level inlining context.
ModuleInliningContext &mic;
/// Map of inner-refs to the new inner sym.
InnerRefToNewNameMap relocatedInnerSyms;
/// All operations cloned are tracked here.
SmallVector<Operation *> newOps;
/// Wires and other values introduced for ports.
SmallVector<Value> wires;
/// Ths module being inlined (this "level").
FModuleOp childModule;
~InliningLevel() {
replaceInnerRefUsers(newOps, relocatedInnerSyms,
mic.module.getNameAttr());
}
};
/// Returns true if the NLA matches the current path. This will only return
/// false if there is a mismatch indicating that the NLA definitely is
/// referring to some other path.
bool doesNLAMatchCurrentPath(hw::HierPathOp nla);
/// Rename an operation and unique any symbols it has.
/// Returns true iff symbol was changed.
bool rename(StringRef prefix, Operation *op, InliningLevel &il);
/// Rename an InstanceOp and unique any symbols it has.
/// Requires old and new operations to appropriately update the `HierPathOp`'s
/// that it participates in.
bool renameInstance(StringRef prefix, InliningLevel &il, InstanceOp oldInst,
InstanceOp newInst,
const DenseMap<Attribute, Attribute> &symbolRenames);
/// Clone and rename an operation. Insert the operation into the inlining
/// level.
void cloneAndRename(StringRef prefix, InliningLevel &il, IRMapping &mapper,
Operation &op,
const DenseMap<Attribute, Attribute> &symbolRenames,
const DenseSet<Attribute> &localSymbols);
/// Rewrite the ports of a module as wires. This is similar to
/// cloneAndRename, but operating on ports.
/// Wires are added to il.wires.
void mapPortsToWires(StringRef prefix, InliningLevel &il, IRMapping &mapper,
const DenseSet<Attribute> &localSymbols);
/// Returns true if the operation is annotated to be flattened.
bool shouldFlatten(Operation *op);
/// Returns true if the operation is annotated to be inlined.
bool shouldInline(Operation *op);
/// Flattens a target module into the insertion point of the builder,
/// renaming all operations using the prefix. This clones all operations from
/// the target, and does not trigger inlining on the target itself.
void flattenInto(StringRef prefix, InliningLevel &il, IRMapping &mapper,
DenseSet<Attribute> localSymbols);
/// Inlines a target module into the insertion point of the builder,
/// prefixing all operations with prefix. This clones all operations from
/// the target, and does not trigger inlining on the target itself.
void inlineInto(StringRef prefix, InliningLevel &il, IRMapping &mapper,
DenseMap<Attribute, Attribute> &symbolRenames);
/// Recursively flatten all instances in a module.
void flattenInstances(FModuleOp module);
/// Inline any instances in the module which were marked for inlining.
void inlineInstances(FModuleOp module);
/// Identify all module-only NLA's, marking their MutableNLA's accordingly.
void identifyNLAsTargetingOnlyModules();
/// Populate the activeHierpaths with the HierPaths that are active given the
/// current hierarchy. This is the set of HierPaths that were active in the
/// parent, and on the current instance. Also HierPaths that are rooted at
/// this module are also added to the active set.
void setActiveHierPaths(StringAttr moduleName, StringAttr instInnerSym) {
auto &instPaths =
instOpHierPaths[InnerRefAttr::get(moduleName, instInnerSym)];
if (currentPath.empty()) {
activeHierpaths.insert(instPaths.begin(), instPaths.end());
return;
}
DenseSet<StringAttr> hPaths(instPaths.begin(), instPaths.end());
// Only the hierPaths that this instance participates in, and is active in
// the current path must be kept active for the child modules.
llvm::set_intersect(activeHierpaths, hPaths);
// Also, the nlas, that have current instance as the top must be added to
// the active set.
for (auto hPath : instPaths)
if (nlaMap[hPath].hasRoot(moduleName))
activeHierpaths.insert(hPath);
}
CircuitOp circuit;
MLIRContext *context;
// A symbol table with references to each module in a circuit.
SymbolTable &symbolTable;
/// The set of live modules. Anything not recorded in this set will be
/// removed by dead code elimination.
DenseSet<Operation *> liveModules;
/// Worklist of modules to process for inlining or flattening.
SmallVector<FModuleOp, 16> worklist;
/// A mapping of NLA symbol name to mutable NLA.
DenseMap<Attribute, MutableNLA> nlaMap;
/// A mapping of module names to NLA symbols that originate from that module.
DenseMap<Attribute, SmallVector<Attribute>> rootMap;
/// The current instance path. This is a pair<ModuleName, InstanceName>.
/// This is used to distinguish if a non-local annotation applies to the
/// current instance or not.
SmallVector<std::pair<Attribute, Attribute>> currentPath;
DenseSet<StringAttr> activeHierpaths;
/// Record the HierPathOps that each InstanceOp participates in. This is a map
/// from the InnerRefAttr to the list of HierPathOp names. The InnerRefAttr
/// corresponds to the InstanceOp.
DenseMap<InnerRefAttr, SmallVector<StringAttr>> instOpHierPaths;
};
} // namespace
/// Check if the NLA applies to our instance path. This works by verifying the
/// instance paths backwards starting from the current module. We drop the back
/// element from the NLA because it obviously matches the current operation.
bool Inliner::doesNLAMatchCurrentPath(hw::HierPathOp nla) {
return (activeHierpaths.find(nla.getSymNameAttr()) != activeHierpaths.end());
}
/// If this operation or any child operation has a name, add the prefix to that
/// operation's name. If the operation has any inner symbols, make sure that
/// these are unique in the namespace. Record renamed inner symbols
/// in relocatedInnerSyms map for renaming local users.
bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) {
// Add a prefix to things that has a "name" attribute. We don't prefix
// memories since it will affect the name of the generated module.
// TODO: We should find a way to prefix the instance of a memory module.
if (!isa<MemOp, SeqMemOp, CombMemOp, MemoryPortOp>(op)) {
if (auto nameAttr = op->getAttrOfType<StringAttr>("name"))
op->setAttr("name", StringAttr::get(op->getContext(),
(prefix + nameAttr.getValue())));
}
// If the operation has an inner symbol, ensure that it is unique. Record
// renames for any NLAs that this participates in if the symbol was renamed.
auto symOp = dyn_cast<hw::InnerSymbolOpInterface>(op);
if (!symOp)
return false;
auto oldSymAttr = symOp.getInnerSymAttr();
auto newSymAttr =
uniqueInNamespace(oldSymAttr, il.relocatedInnerSyms, il.mic.modNamespace,
il.childModule.getNameAttr());
if (!newSymAttr)
return false;
// If there's a symbol on the root and it changed, do NLA work.
if (auto newSymStrAttr = newSymAttr.getSymName();
newSymStrAttr && newSymStrAttr != oldSymAttr.getSymName()) {
for (Annotation anno : AnnotationSet(op)) {
auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
if (!sym)
continue;
// If this is a breadcrumb, we update the annotation path
// unconditionally. If this is the leaf of the NLA, we need to make
// sure we only update the annotation if the current path matches the
// NLA. This matters when the same module is inlined twice and the NLA
// only applies to one of them.
auto &mnla = nlaMap[sym.getAttr()];
if (!doesNLAMatchCurrentPath(mnla.getNLA()))
continue;
mnla.setInnerSym(il.mic.module.getModuleNameAttr(), newSymStrAttr);
}
}
symOp.setInnerSymbolAttr(newSymAttr);
return newSymAttr != oldSymAttr;
}
bool Inliner::renameInstance(
StringRef prefix, InliningLevel &il, InstanceOp oldInst, InstanceOp newInst,
const DenseMap<Attribute, Attribute> &symbolRenames) {
// Add this instance to the activeHierpaths. This ensures that NLAs that this
// instance participates in will be updated correctly.
auto parentActivePaths = activeHierpaths;
assert(oldInst->getParentOfType<FModuleOp>() == il.childModule);
if (auto instSym = getInnerSymName(oldInst))
setActiveHierPaths(oldInst->getParentOfType<FModuleOp>().getNameAttr(),
instSym);
// List of HierPathOps that are valid based on the InstanceOp being inlined
// and the InstanceOp which is being replaced after inlining. That is the set
// of HierPathOps that is common between these two.
SmallVector<StringAttr> validHierPaths;
auto oldParent = oldInst->getParentOfType<FModuleOp>().getNameAttr();
auto oldInstSym = getInnerSymName(oldInst);
if (oldInstSym) {
// Get the innerRef to the original InstanceOp that is being inlined here.
// For all the HierPathOps that the instance being inlined participates
// in.
auto oldInnerRef = InnerRefAttr::get(oldParent, oldInstSym);
for (auto old : instOpHierPaths[oldInnerRef]) {
// If this HierPathOp is valid at the inlining context, where the
// instance is being inlined at. That is, if it exists in the
// activeHierpaths.
if (activeHierpaths.find(old) != activeHierpaths.end())
validHierPaths.push_back(old);
else
// The HierPathOp could have been renamed, check for the other retoped
// names, if they are active at the inlining context.
for (auto additionalSym : nlaMap[old].getAdditionalSymbols())
if (activeHierpaths.find(additionalSym.getName()) !=
activeHierpaths.end()) {
validHierPaths.push_back(old);
break;
}
}
}
assert(getInnerSymName(newInst) == oldInstSym);
// Do the renaming, creating new symbol as needed.
auto symbolChanged = rename(prefix, newInst, il);
// If the symbol changed, update instOpHierPaths accordingly.
auto newSymAttr = getInnerSymName(newInst);
if (symbolChanged) {
assert(newSymAttr);
// The InstanceOp is renamed, so move the HierPathOps to the new
// InnerRefAttr.
auto newInnerRef = InnerRefAttr::get(
newInst->getParentOfType<FModuleOp>().getNameAttr(), newSymAttr);
instOpHierPaths[newInnerRef] = validHierPaths;
// Update the innerSym for all the affected HierPathOps.
for (auto nla : instOpHierPaths[newInnerRef]) {
if (!nlaMap.count(nla))
continue;
auto &mnla = nlaMap[nla];
mnla.setInnerSym(newInnerRef.getModule(), newSymAttr);
}
}
if (newSymAttr) {
auto innerRef = InnerRefAttr::get(
newInst->getParentOfType<FModuleOp>().getNameAttr(), newSymAttr);
SmallVector<StringAttr> &nlaList = instOpHierPaths[innerRef];
// Now rename the Updated HierPathOps that this InstanceOp participates in.
for (const auto &en : llvm::enumerate(nlaList)) {
auto oldNLA = en.value();
if (auto newSym = symbolRenames.lookup(oldNLA))
nlaList[en.index()] = cast<StringAttr>(newSym);
}
}
activeHierpaths = std::move(parentActivePaths);
return symbolChanged;
}
/// This function is used before inlining a module, to handle the conversion
/// between module ports and instance results. For every port in the target
/// module, create a wire, and assign a mapping from each module port to the
/// wire. When the body of the module is cloned, the value of the wire will be
/// used instead of the module's ports.
void Inliner::mapPortsToWires(StringRef prefix, InliningLevel &il,
IRMapping &mapper,
const DenseSet<Attribute> &localSymbols) {
auto target = il.childModule;
auto portInfo = target.getPorts();
for (unsigned i = 0, e = target.getNumPorts(); i < e; ++i) {
auto arg = target.getArgument(i);
// Get the type of the wire.
auto type = type_cast<FIRRTLType>(arg.getType());
// Compute new symbols if needed.
auto oldSymAttr = portInfo[i].sym;
auto newSymAttr =
uniqueInNamespace(oldSymAttr, il.relocatedInnerSyms,
il.mic.modNamespace, target.getNameAttr());
StringAttr newRootSymName, oldRootSymName;
if (oldSymAttr)
oldRootSymName = oldSymAttr.getSymName();
if (newSymAttr)
newRootSymName = newSymAttr.getSymName();
SmallVector<Attribute> newAnnotations;
for (auto anno : AnnotationSet::forPort(target, i)) {
// If the annotation is not non-local, copy it to the clone.
if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
auto &mnla = nlaMap[sym.getAttr()];
// If the NLA does not match the path, we don't want to copy it over.
if (!doesNLAMatchCurrentPath(mnla.getNLA()))
continue;
// Update any NLAs with the new symbol name.
// This does not handle per-field symbols used in NLA's.
if (oldRootSymName != newRootSymName)
mnla.setInnerSym(il.mic.module.getModuleNameAttr(), newRootSymName);
// If all paths of the NLA have been inlined, make it local.
if (mnla.isLocal() || localSymbols.count(sym.getAttr()))
anno.removeMember("circt.nonlocal");
}
newAnnotations.push_back(anno.getAttr());
}
Value wire =
il.mic.b
.create<WireOp>(
target.getLoc(), type,
StringAttr::get(context, (prefix + portInfo[i].getName())),
NameKindEnumAttr::get(context, NameKindEnum::DroppableName),
ArrayAttr::get(context, newAnnotations), newSymAttr,
/*forceable=*/UnitAttr{})
.getResult();
il.wires.push_back(wire);
mapper.map(arg, wire);
}
}
/// Clone an operation, mapping used values and results with the mapper, and
/// apply the prefix to the name of the operation. This will clone to the
/// insert point of the builder. Insert the operation into the level.
void Inliner::cloneAndRename(
StringRef prefix, InliningLevel &il, IRMapping &mapper, Operation &op,
const DenseMap<Attribute, Attribute> &symbolRenames,
const DenseSet<Attribute> &localSymbols) {
// Strip any non-local annotations which are local.
AnnotationSet oldAnnotations(&op);
SmallVector<Annotation> newAnnotations;
for (auto anno : oldAnnotations) {
// If the annotation is not non-local, it will apply to all inlined
// instances of this op. Add it to the cloned op.
if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
// Retrieve the corresponding NLA.
auto &mnla = nlaMap[sym.getAttr()];
// If the NLA does not match the path we don't want to copy it over.
if (!doesNLAMatchCurrentPath(mnla.getNLA()))
continue;
// The NLA has become local, rewrite the annotation to be local.
if (mnla.isLocal() || localSymbols.count(sym.getAttr()))
anno.removeMember("circt.nonlocal");
}
// Attach this annotation to the cloned operation.
newAnnotations.push_back(anno);
}
// Clone and rename.
auto *newOp = il.mic.b.clone(op, mapper);
// Rename the new operation and any contained operations.
// (add prefix to it, if named, and unique-ify symbol, updating NLA's).
op.walk<mlir::WalkOrder::PreOrder>([&](Operation *origOp) {
auto *newOpToRename = mapper.lookup(origOp);
assert(newOpToRename);
// TODO: If want to work before ExpandWhen's, more work needed!
// Handle what we can for now.
assert((origOp == &op || !isa<InstanceOp>(origOp)) &&
"Cannot handle instances not at top-level");
// Instances require extra handling to update HierPathOp's if their symbols
// change.
if (auto oldInst = dyn_cast<InstanceOp>(origOp))
renameInstance(prefix, il, oldInst, cast<InstanceOp>(newOpToRename),
symbolRenames);
else
rename(prefix, newOpToRename, il);
});
// We want to avoid attaching an empty annotation array on to an op that
// never had an annotation array in the first place.
if (!newAnnotations.empty() || !oldAnnotations.empty())
AnnotationSet(newAnnotations, context).applyToOperation(newOp);
il.newOps.push_back(newOp);
}
bool Inliner::shouldFlatten(Operation *op) {
return AnnotationSet(op).hasAnnotation(flattenAnnoClass);
}
bool Inliner::shouldInline(Operation *op) {
return AnnotationSet(op).hasAnnotation(inlineAnnoClass);
}
// NOLINTNEXTLINE(misc-no-recursion)
void Inliner::flattenInto(StringRef prefix, InliningLevel &il,
IRMapping &mapper, DenseSet<Attribute> localSymbols) {
auto target = il.childModule;
auto moduleName = target.getNameAttr();
DenseMap<Attribute, Attribute> symbolRenames;
for (auto &op : *target.getBodyBlock()) {
// If it's not an instance op, clone it and continue.
auto instance = dyn_cast<InstanceOp>(op);
if (!instance) {
cloneAndRename(prefix, il, mapper, op, symbolRenames, localSymbols);
continue;
}
// If it's not a regular module we can't inline it. Mark it as live.
auto *module = symbolTable.lookup(instance.getModuleName());
auto childModule = dyn_cast<FModuleOp>(module);
if (!childModule) {
liveModules.insert(module);
cloneAndRename(prefix, il, mapper, op, symbolRenames, localSymbols);
continue;
}
// Add any NLAs which start at this instance to the localSymbols set.
// Anything in this set will be made local during the recursive flattenInto
// walk.
llvm::set_union(localSymbols, rootMap[childModule.getNameAttr()]);
auto instInnerSym = getInnerSymName(instance);
auto parentActivePaths = activeHierpaths;
setActiveHierPaths(moduleName, instInnerSym);
currentPath.emplace_back(moduleName, instInnerSym);
InliningLevel childIL(il.mic, childModule);
// Create the wire mapping for results + ports.
auto nestedPrefix = (prefix + instance.getName() + "_").str();
mapPortsToWires(nestedPrefix, childIL, mapper, localSymbols);
mapResultsToWires(mapper, childIL.wires, instance);
// Unconditionally flatten all instance operations.
flattenInto(nestedPrefix, childIL, mapper, localSymbols);
currentPath.pop_back();
activeHierpaths = parentActivePaths;
}
}
void Inliner::flattenInstances(FModuleOp module) {
auto moduleName = module.getNameAttr();
ModuleInliningContext mic(module);
for (auto &op : llvm::make_early_inc_range(*module.getBodyBlock())) {
// If it's not an instance op, skip it.
auto instance = dyn_cast<InstanceOp>(op);
if (!instance)
continue;
// If it's not a regular module we can't inline it. Mark it as live.
auto *targetModule = symbolTable.lookup(instance.getModuleName());
auto target = dyn_cast<FModuleOp>(targetModule);
if (!target) {
liveModules.insert(targetModule);
continue;
}
if (auto instSym = getInnerSymName(instance)) {
auto innerRef = InnerRefAttr::get(moduleName, instSym);
// Preorder update of any non-local annotations this instance participates
// in. This needs to happen _before_ visiting modules so that internal
// non-local annotations can be deleted if they are now local.
for (auto targetNLA : instOpHierPaths[innerRef]) {
nlaMap[targetNLA].flattenModule(target);
}
}
// Add any NLAs which start at this instance to the localSymbols set.
// Anything in this set will be made local during the recursive flattenInto
// walk.
DenseSet<Attribute> localSymbols;
llvm::set_union(localSymbols, rootMap[target.getNameAttr()]);
auto instInnerSym = getInnerSymName(instance);
auto parentActivePaths = activeHierpaths;
setActiveHierPaths(moduleName, instInnerSym);
currentPath.emplace_back(moduleName, instInnerSym);
// Create the wire mapping for results + ports. We RAUW the results instead
// of mapping them.
IRMapping mapper;
mic.b.setInsertionPoint(instance);
InliningLevel il(mic, target);
auto nestedPrefix = (instance.getName() + "_").str();
mapPortsToWires(nestedPrefix, il, mapper, localSymbols);
for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
instance.getResult(i).replaceAllUsesWith(il.wires[i]);
// Recursively flatten the target module.
flattenInto(nestedPrefix, il, mapper, localSymbols);
currentPath.pop_back();
activeHierpaths = parentActivePaths;
// Erase the replaced instance.
instance.erase();
}
}
// NOLINTNEXTLINE(misc-no-recursion)
void Inliner::inlineInto(StringRef prefix, InliningLevel &il, IRMapping &mapper,
DenseMap<Attribute, Attribute> &symbolRenames) {
auto target = il.childModule;
auto inlineToParent = il.mic.module;
auto moduleName = target.getNameAttr();
// Inline everything in the module's body.
for (auto &op : *target.getBodyBlock()) {
// If it's not an instance op, clone it and continue.
auto instance = dyn_cast<InstanceOp>(op);
if (!instance) {
cloneAndRename(prefix, il, mapper, op, symbolRenames, {});
continue;
}
// If it's not a regular module we can't inline it. Mark it as live.
auto *module = symbolTable.lookup(instance.getModuleName());
auto childModule = dyn_cast<FModuleOp>(module);
if (!childModule) {
liveModules.insert(module);
cloneAndRename(prefix, il, mapper, op, symbolRenames, {});
continue;
}
// If we aren't inlining the target, add it to the work list.
if (!shouldInline(childModule)) {
if (liveModules.insert(childModule).second) {
worklist.push_back(childModule);
}
cloneAndRename(prefix, il, mapper, op, symbolRenames, {});
continue;
}
auto toBeFlattened = shouldFlatten(childModule);
if (auto instSym = getInnerSymName(instance)) {
auto innerRef = InnerRefAttr::get(moduleName, instSym);
// Preorder update of any non-local annotations this instance participates
// in. This needs to happen _before_ visiting modules so that internal
// non-local annotations can be deleted if they are now local.
for (auto sym : instOpHierPaths[innerRef]) {
if (toBeFlattened)
nlaMap[sym].flattenModule(childModule);
else
nlaMap[sym].inlineModule(childModule);
}
}
// The InstanceOp `instance` might not have a symbol, if it does not
// participate in any HierPathOp. But the reTop might add a symbol to it, if
// a HierPathOp is added to this Op. If we're about to inline a module that
// contains a non-local annotation that starts at that module, then we need
// to both update the mutable NLA to indicate that this has a new top and
// add an annotation on the instance saying that this now participates in
// this new NLA.
DenseMap<Attribute, Attribute> symbolRenames;
if (!rootMap[childModule.getNameAttr()].empty()) {
for (auto sym : rootMap[childModule.getNameAttr()]) {
auto &mnla = nlaMap[sym];
// Retop to the new parent, which is the topmost module (and not
// immediate parent) in case of recursive inlining.
sym = mnla.reTop(inlineToParent);
StringAttr instSym = getInnerSymName(instance);
if (!instSym) {
instSym = StringAttr::get(
context, il.mic.modNamespace.newName(instance.getName()));
instance.setInnerSymAttr(hw::InnerSymAttr::get(instSym));
}
instOpHierPaths[InnerRefAttr::get(moduleName, instSym)].push_back(
cast<StringAttr>(sym));
// TODO: Update any symbol renames which need to be used by the next
// call of inlineInto. This will then check each instance and rename
// any symbols appropriately for that instance.
symbolRenames.insert({mnla.getNLA().getNameAttr(), sym});
}
}
auto instInnerSym = getInnerSymName(instance);
auto parentActivePaths = activeHierpaths;
setActiveHierPaths(moduleName, instInnerSym);
// This must be done after the reTop, since it might introduce an innerSym.
currentPath.emplace_back(moduleName, instInnerSym);
InliningLevel childIL(il.mic, childModule);
// Create the wire mapping for results + ports.
auto nestedPrefix = (prefix + instance.getName() + "_").str();
mapPortsToWires(nestedPrefix, childIL, mapper, {});
mapResultsToWires(mapper, childIL.wires, instance);
// Inline the module, it can be marked as flatten and inline.
if (toBeFlattened) {
flattenInto(nestedPrefix, childIL, mapper, {});
} else {
inlineInto(nestedPrefix, childIL, mapper, symbolRenames);
}
currentPath.pop_back();
activeHierpaths = parentActivePaths;
}
}
void Inliner::inlineInstances(FModuleOp module) {
// Generate a namespace for this module so that we can safely inline symbols.
auto moduleName = module.getNameAttr();
SmallVector<Value> wires;
ModuleInliningContext mic(module);
for (auto &op : llvm::make_early_inc_range(*module.getBodyBlock())) {
// If it's not an instance op, skip it.
auto instance = dyn_cast<InstanceOp>(op);
if (!instance)
continue;
// If it's not a regular module we can't inline it. Mark it as live.
auto *childModule = symbolTable.lookup(instance.getModuleName());
auto target = dyn_cast<FModuleOp>(childModule);
if (!target) {
liveModules.insert(childModule);
continue;
}
// If we aren't inlining the target, add it to the work list.
if (!shouldInline(target)) {
if (liveModules.insert(target).second) {
worklist.push_back(target);
}
continue;
}
auto toBeFlattened = shouldFlatten(target);
if (auto instSym = getInnerSymName(instance)) {
auto innerRef = InnerRefAttr::get(moduleName, instSym);
// Preorder update of any non-local annotations this instance participates
// in. This needs to happen _before_ visiting modules so that internal
// non-local annotations can be deleted if they are now local.
for (auto sym : instOpHierPaths[innerRef]) {
if (toBeFlattened)
nlaMap[sym].flattenModule(target);
else
nlaMap[sym].inlineModule(target);
}
}
// The InstanceOp `instance` might not have a symbol, if it does not
// participate in any HierPathOp. But the reTop might add a symbol to it, if
// a HierPathOp is added to this Op.
DenseMap<Attribute, Attribute> symbolRenames;
if (!rootMap[target.getNameAttr()].empty() && !toBeFlattened) {
for (auto sym : rootMap[target.getNameAttr()]) {
auto &mnla = nlaMap[sym];
sym = mnla.reTop(module);
StringAttr instSym = getOrAddInnerSym(
instance, [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
return mic.modNamespace;
});
instOpHierPaths[InnerRefAttr::get(moduleName, instSym)].push_back(
cast<StringAttr>(sym));
// TODO: Update any symbol renames which need to be used by the next
// call of inlineInto. This will then check each instance and rename
// any symbols appropriately for that instance.
symbolRenames.insert({mnla.getNLA().getNameAttr(), sym});
}
}
auto instInnerSym = getInnerSymName(instance);
auto parentActivePaths = activeHierpaths;
setActiveHierPaths(moduleName, instInnerSym);
// This must be done after the reTop, since it might introduce an innerSym.
currentPath.emplace_back(moduleName, instInnerSym);
// Create the wire mapping for results + ports. We RAUW the results instead
// of mapping them.
IRMapping mapper;
mic.b.setInsertionPoint(instance);
auto nestedPrefix = (instance.getName() + "_").str();
InliningLevel childIL(mic, target);
mapPortsToWires(nestedPrefix, childIL, mapper, {});
for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i)
instance.getResult(i).replaceAllUsesWith(childIL.wires[i]);
// Inline the module, it can be marked as flatten and inline.
if (toBeFlattened) {
flattenInto(nestedPrefix, childIL, mapper, {});
} else {
// Recursively inline all the child modules under `parent`, that are
// marked to be inlined.
inlineInto(nestedPrefix, childIL, mapper, symbolRenames);
}
currentPath.pop_back();
activeHierpaths = parentActivePaths;
// Erase the replaced instance.
instance.erase();
}
}
void Inliner::identifyNLAsTargetingOnlyModules() {
DenseSet<Operation *> nlaTargetedModules;
// Identify candidate NLA's: those that end in a module
for (auto &[sym, mnla] : nlaMap) {
auto nla = mnla.getNLA();
if (nla.isModule()) {
auto mod = symbolTable.lookup<FModuleLike>(nla.leafMod());
assert(mod &&
"NLA ends in module reference but does not target FModuleLike?");
nlaTargetedModules.insert(mod);
}
}
// Helper to scan leaf modules for users of NLAs, gathering by symbol names
auto scanForNLARefs = [&](FModuleLike mod) {
DenseSet<StringAttr> referencedNLASyms;
auto scanAnnos = [&](const AnnotationSet &annos) {
for (auto anno : annos)
if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal"))
referencedNLASyms.insert(sym.getAttr());
};
// Scan ports
for (unsigned i = 0, e = mod.getNumPorts(); i != e; ++i)
scanAnnos(AnnotationSet::forPort(mod, i));
// Scan operations (and not the module itself):
// (Walk includes module for lack of simple/generic way to walk body only)
mod.walk([&](Operation *op) {
if (op == mod.getOperation())
return;
scanAnnos(AnnotationSet(op));
// Check MemOp and InstanceOp port annotations, special case
TypeSwitch<Operation *>(op).Case<MemOp, InstanceOp>([&](auto op) {
for (auto portAnnoAttr : op.getPortAnnotations())
scanAnnos(AnnotationSet(portAnnoAttr.template cast<ArrayAttr>()));
});
});
return referencedNLASyms;
};
// Reduction operator
auto mergeSets = [](auto &&a, auto &&b) {
a.insert(b.begin(), b.end());
return std::move(a);
};
// Walk modules in parallel, scanning for references to NLA's
// Gather set of NLA's referenced by each module's ports/operations.
SmallVector<FModuleLike, 0> mods(nlaTargetedModules.begin(),
nlaTargetedModules.end());
auto nonModOnlyNLAs =
transformReduce(circuit->getContext(), mods, DenseSet<StringAttr>{},
mergeSets, scanForNLARefs);
// Mark NLA's that were not referenced as module-only
for (auto &[_, mnla] : nlaMap) {
auto nla = mnla.getNLA();
if (nla.isModule() && !nonModOnlyNLAs.count(nla.getSymNameAttr()))
mnla.markModuleOnly();
}
}
Inliner::Inliner(CircuitOp circuit, SymbolTable &symbolTable)
: circuit(circuit), context(circuit.getContext()),
symbolTable(symbolTable) {}
void Inliner::run() {
CircuitNamespace circuitNamespace(circuit);
// Gather all NLA's, build information about the instance ops used:
for (auto nla : circuit.getBodyBlock()->getOps<hw::HierPathOp>()) {
auto mnla = MutableNLA(nla, &circuitNamespace);
nlaMap.insert({nla.getSymNameAttr(), mnla});
rootMap[mnla.getNLA().root()].push_back(nla.getSymNameAttr());
for (auto p : nla.getNamepath())
if (auto ref = dyn_cast<InnerRefAttr>(p))
instOpHierPaths[ref].push_back(nla.getSymNameAttr());
}
// Mark 'module-only' the NLA's that only target modules.
// These may be deleted when their module is inlined/flattened.
identifyNLAsTargetingOnlyModules();
// Mark the top module as live, so it doesn't get deleted.
for (auto module : circuit.getOps<FModuleLike>()) {
if (module.canDiscardOnUseEmpty())
continue;
liveModules.insert(module);
if (isa<FModuleOp>(module))
worklist.push_back(cast<FModuleOp>(module));
}
// If the module is marked for flattening, flatten it. Otherwise, inline
// every instance marked to be inlined.
while (!worklist.empty()) {
auto module = worklist.pop_back_val();
if (shouldFlatten(module)) {
flattenInstances(module);
// Delete the flatten annotation, the transform was performed.
// Even if visited again in our walk (for inlining),
// we've just flattened it and so the annotation is no longer needed.
AnnotationSet::removeAnnotations(module, flattenAnnoClass);
} else {
inlineInstances(module);
}
}
// Delete all unreferenced modules. Mark any NLAs that originate from dead
// modules as also dead.
for (auto mod : llvm::make_early_inc_range(
circuit.getBodyBlock()->getOps<FModuleLike>())) {
if (liveModules.count(mod))
continue;
for (auto nla : rootMap[mod.getModuleNameAttr()])
nlaMap[nla].markDead();
mod.erase();
}
// Remove leftover inline annotations, and check no flatten annotations
// remain as they should have been processed and removed.
for (auto mod : circuit.getBodyBlock()->getOps<FModuleLike>()) {
if (shouldInline(mod)) {
assert(mod.isPublic() &&
"non-public module with inline annotation still present");
AnnotationSet::removeAnnotations(mod, inlineAnnoClass);
}
assert(!shouldFlatten(mod) && "flatten annotation found on live module");
}
LLVM_DEBUG({
llvm::dbgs() << "NLA modifications:\n";
for (auto nla : circuit.getBodyBlock()->getOps<hw::HierPathOp>()) {
auto &mnla = nlaMap[nla.getNameAttr()];
mnla.dump();
}
});
// Writeback all NLAs to MLIR.
for (auto &nla : nlaMap)
nla.getSecond().applyUpdates();
// Garbage collect any annotations which are now dead. Duplicate annotations
// which are now split.
for (auto fmodule : circuit.getBodyBlock()->getOps<FModuleOp>()) {
SmallVector<Attribute> newAnnotations;
auto processNLAs = [&](Annotation anno) -> bool {
if (auto sym = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
// If the symbol isn't in the NLA map, just skip it. This avoids
// problems where the nlaMap "[]" will try to construct a default
// MutableNLA map (which it should never do).
if (!nlaMap.count(sym.getAttr()))
return false;
auto mnla = nlaMap[sym.getAttr()];
// Garbage collect dead NLA references. This cleans up NLAs that go
// through modules which we never visited.
if (mnla.isDead())
return true;
// Do nothing if there are no additional NLAs to add or if we're
// dealing with a root module. Root modules have already been updated
// earlier in the pass. We only need to update NLA paths which are
// not the root.
auto newTops = mnla.getAdditionalSymbols();
if (newTops.empty() || mnla.hasRoot(fmodule))
return false;
// Add NLAs to the non-root portion of the NLA. This only needs to
// add symbols for NLAs which are after the first one. We reused the
// old symbol name for the first NLA.
NamedAttrList newAnnotation;
for (auto rootAndSym : newTops.drop_front()) {
for (auto pair : anno.getDict()) {
if (pair.getName().getValue() != "circt.nonlocal") {
newAnnotation.push_back(pair);
continue;
}
newAnnotation.push_back(
{pair.getName(), FlatSymbolRefAttr::get(rootAndSym.getName())});
}
newAnnotations.push_back(DictionaryAttr::get(context, newAnnotation));
}
}
return false;
};
fmodule.walk([&](Operation *op) {
AnnotationSet annotations(op);
// Early exit to avoid adding an empty annotations attribute to operations
// which did not previously have annotations.
if (annotations.empty())
return;
// Update annotations on the op.
newAnnotations.clear();
annotations.removeAnnotations(processNLAs);
annotations.addAnnotations(newAnnotations);
annotations.applyToOperation(op);
});
// Update annotations on the ports.
SmallVector<Attribute> newPortAnnotations;
for (auto port : fmodule.getPorts()) {
newAnnotations.clear();
port.annotations.removeAnnotations(processNLAs);
port.annotations.addAnnotations(newAnnotations);
newPortAnnotations.push_back(
ArrayAttr::get(context, port.annotations.getArray()));
}
fmodule->setAttr("portAnnotations",
ArrayAttr::get(context, newPortAnnotations));
}
}
//===----------------------------------------------------------------------===//
// Pass Infrastructure
//===----------------------------------------------------------------------===//
namespace {
class InlinerPass : public InlinerBase<InlinerPass> {
void runOnOperation() override {
LLVM_DEBUG(debugPassHeader(this) << "\n");
Inliner inliner(getOperation(), getAnalysis<SymbolTable>());
inliner.run();
LLVM_DEBUG(debugFooter() << "\n");
}
};
} // namespace
std::unique_ptr<mlir::Pass> circt::firrtl::createInlinerPass() {
return std::make_unique<InlinerPass>();
}