16#include "mlir/IR/Dominance.h"
17#include "mlir/IR/Threading.h"
18#include "mlir/Interfaces/SideEffectInterfaces.h"
19#include "mlir/Transforms/ControlFlowSinkUtils.h"
21#define DEBUG_TYPE "firrtl-layer-sink"
25#define GEN_PASS_DEF_LAYERSINK
26#include "circt/Dialect/FIRRTL/Passes.h.inc"
31using namespace firrtl;
45void walkBwd(
Block *block, T &&f) {
47 llvm::make_early_inc_range(llvm::reverse(block->getOperations()))) {
48 for (
auto ®ion : op.getRegions())
49 for (
auto &block : region.getBlocks())
60 return block->getParent()->isProperAncestor(other->getParent());
69 if (op->hasTrait<OpTrait::ConstantLike>())
73 if (isa<RefSendOp, RefResolveOp, RefCastOp, RefSubOp>(op))
89 [&](
auto &node) { update(node.getModule().getOperation()); });
93 bool effectful(Operation *op)
const {
96 if (
auto name = dyn_cast<FNamableOp>(op))
97 if (!name.hasDroppableName())
99 if (op->getNumRegions() != 0)
101 return TypeSwitch<Operation *, bool>(op)
102 .Case<InstanceOp, InstanceChoiceOp>([&](
auto op) {
103 for (
auto module : op.getReferencedModuleNamesAttr())
104 if (effectfulModules.contains(cast<StringAttr>(module)))
108 .Case<FConnectLike, WireOp, RegResetOp, RegOp, MemOp, NodeOp>(
109 [](
auto) {
return false; })
110 .Default([](
auto op) {
111 return !(mlir::isMemoryEffectFree(op) ||
112 mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op) ||
113 mlir::hasSingleEffect<mlir::MemoryEffects::Read>(op));
119 void update(FModuleOp moduleOp) {
120 moduleOp.getBodyBlock()->walk([&](Operation *op) {
122 markEffectful(moduleOp);
123 return WalkResult::interrupt();
125 return WalkResult::advance();
130 void update(FModuleLike moduleOp) {
135 return markEffectful(moduleOp);
137 for (
auto annos : moduleOp.getPortAnnotations())
138 if (!cast<ArrayAttr>(annos).
empty())
139 return markEffectful(moduleOp);
141 auto *op = moduleOp.getOperation();
143 if (
auto m = dyn_cast<FModuleOp>(op))
146 if (
auto m = dyn_cast<FMemModuleOp>(op))
150 return markEffectful(moduleOp);
153 void update(Operation *op) {
154 if (
auto moduleOp = dyn_cast<FModuleLike>(op))
159 void markEffectful(FModuleLike moduleOp) {
160 effectfulModules.insert(moduleOp.getModuleNameAttr());
163 DenseSet<StringAttr> effectfulModules;
174 constexpr Demand() : Demand(nullptr) {}
175 constexpr Demand(Block *block) : block(block) {}
177 constexpr Demand merge(Demand other)
const {
178 if (block == other.block)
179 return Demand(block);
180 if (other.block ==
nullptr)
181 return Demand(block);
182 if (block ==
nullptr)
183 return Demand(other.block);
187 b = b->getParentOp()->getBlock();
192 bool mergeIn(Demand other) {
194 auto next = merge(other);
199 constexpr bool operator==(Demand rhs)
const {
return block == rhs.block; }
200 constexpr bool operator!=(Demand rhs)
const {
return block != rhs.block; }
201 constexpr operator bool()
const {
return block; }
209 return op && (isa<LayerBlockOp>(op) || isa<FModuleOp>(op));
230static Demand
clamp(Operation *op, Demand demand) {
234 auto *upper = op->getBlock();
235 assert(upper &&
"this should not be called on a top-level operation.");
240 for (
auto *i = demand.block; i != upper; i = i->getParentOp()->getBlock())
242 demand = i->getParentOp()->getBlock();
249 using WorkStack = std::vector<Operation *>;
251 DemandInfo(
const EffectInfo &, FModuleOp);
253 Demand getDemandFor(Operation *op)
const {
return table.lookup(op); }
259 void run(
const EffectInfo &, FModuleOp, WorkStack &);
263 void update(WorkStack &work, Operation *op, Demand demand) {
264 if (table[op].mergeIn(
clamp(op, demand)))
268 void update(WorkStack &work, Value value, Demand demand) {
269 if (
auto result = dyn_cast<OpResult>(value))
270 update(work, cast<OpResult>(value).getOwner(), demand);
273 void updateConnects(WorkStack &, Value, Demand);
274 void updateConnects(WorkStack &, Operation *, Demand);
276 llvm::DenseMap<Operation *, Demand> table;
280DemandInfo::DemandInfo(
const EffectInfo &effectInfo, FModuleOp moduleOp) {
282 Block *body = moduleOp.getBodyBlock();
283 ArrayRef<bool> dirs = moduleOp.getPortDirections();
284 for (
unsigned i = 0, e = moduleOp.getNumPorts(); i < e; ++i)
286 updateConnects(work, body->getArgument(i), moduleOp.getBodyBlock());
287 moduleOp.getBodyBlock()->walk([&](Operation *op) {
288 if (effectInfo.effectful(op))
289 update(work, op, op->getBlock());
291 run(effectInfo, moduleOp, work);
294void DemandInfo::run(
const EffectInfo &effectInfo, FModuleOp, WorkStack &work) {
295 while (!work.empty()) {
296 auto *op = work.back();
298 auto demand = getDemandFor(op);
299 for (
auto operand : op->getOperands())
300 update(work, operand, demand);
301 updateConnects(work, op, demand);
311void DemandInfo::updateConnects(WorkStack &work, Value value, Demand demand) {
312 struct StackElement {
314 Value::user_iterator it;
317 SmallVector<StackElement> stack;
318 stack.push_back({value, value.user_begin()});
319 while (!stack.empty()) {
320 auto &top = stack.back();
321 auto end = top.value.user_end();
327 auto *user = *(top.it++);
328 if (
auto connect = dyn_cast<FConnectLike>(user)) {
329 if (
connect.getDest() == top.value) {
330 update(work, connect, demand);
334 if (isa<SubfieldOp, SubindexOp, SubaccessOp, ObjectSubfieldOp>(user)) {
335 for (
auto result : user->getResults())
336 stack.push_back({result, result.user_begin()});
343void DemandInfo::updateConnects(WorkStack &work, Operation *op, Demand demand) {
344 TypeSwitch<Operation *>(op)
345 .Case<WireOp, RegResetOp, RegOp, MemOp, ObjectOp>([&](
auto op) {
346 for (
auto result : op->getResults())
347 updateConnects(work, result, demand);
349 .Case<InstanceOp, InstanceChoiceOp>([&](
auto op) {
350 for (
auto [i, dir] :
llvm::enumerate(op.getPortDirections()))
352 updateConnects(work, op->getResult(i), demand);
361class ModuleLayerSink {
363 static bool run(FModuleOp moduleOp,
const EffectInfo &effectInfo) {
364 return ModuleLayerSink(moduleOp, effectInfo)();
368 ModuleLayerSink(FModuleOp moduleOp,
const EffectInfo &effectInfo)
369 : moduleOp(moduleOp), effectInfo(effectInfo) {}
372 void moveLayersToBack(Operation *op);
373 void moveLayersToBack(Block *block);
375 void erase(Operation *op);
376 void sinkOpByCloning(Operation *op);
377 void sinkOpByMoving(Operation *op, Block *dest);
380 const EffectInfo &effectInfo;
381 bool changed =
false;
386void ModuleLayerSink::moveLayersToBack(Operation *op) {
387 for (
auto &r : op->getRegions())
388 for (auto &b : r.getBlocks())
389 moveLayersToBack(&b);
393void ModuleLayerSink::moveLayersToBack(Block *block) {
394 auto i = block->rbegin();
395 auto e = block->rend();
400 moveLayersToBack(op);
401 if (!isa<LayerBlockOp>(op))
414 moveLayersToBack(op);
415 if (isa<LayerBlockOp>(op)) {
425void ModuleLayerSink::erase(Operation *op) {
430void ModuleLayerSink::sinkOpByCloning(Operation *op) {
433 auto *src = op->getBlock();
434 DenseMap<Block *, Operation *> cache;
435 for (
unsigned i = 0, e = op->getNumResults(); i < e; ++i) {
436 for (
auto &use :
llvm::make_early_inc_range(op->getResult(i).getUses())) {
437 auto *dst = use.getOwner()->getBlock();
439 auto &clone = cache[dst];
441 clone = OpBuilder::atBlockBegin(dst).clone(*op);
442 use.set(clone->getResult(i));
449 if (isOpTriviallyDead(op))
453void ModuleLayerSink::sinkOpByMoving(Operation *op, Block *dst) {
454 if (dst != op->getBlock()) {
455 op->moveBefore(dst, dst->begin());
460bool ModuleLayerSink::operator()() {
461 moveLayersToBack(moduleOp.getBodyBlock());
462 DemandInfo demandInfo(effectInfo, moduleOp);
463 walkBwd(moduleOp.getBodyBlock(), [&](Operation *op) {
464 auto demand = demandInfo.getDemandFor(op);
468 return sinkOpByCloning(op);
469 sinkOpByMoving(op, demand.block);
481struct LayerSinkPass final
482 :
public circt::firrtl::impl::LayerSinkBase<LayerSinkPass> {
483 void runOnOperation()
override;
487void LayerSinkPass::runOnOperation() {
490 auto circuit = getOperation();
491 LLVM_DEBUG(llvm::dbgs() <<
"Circuit: '" << circuit.getName() <<
"'\n");
493 auto &instanceGraph = getAnalysis<InstanceGraph>();
494 EffectInfo effectInfo(circuit, instanceGraph);
496 std::atomic<bool> changed(
false);
497 parallelForEach(&getContext(), circuit.getOps<FModuleOp>(),
498 [&](FModuleOp moduleOp) {
499 if (ModuleLayerSink::run(moduleOp, effectInfo))
504 markAllAnalysesPreserved();
506 markAnalysesPreserved<InstanceGraph>();
assert(baseType &&"element must be base type")
static bool isBarrier(Operation *op)
True if we are prevented from sinking operations into the regions of the op.
static Demand clamp(Operation *op, Demand demand)
Adjust the demand based on the location of the op being demanded.
static bool isAncestor(Block *block, Block *other)
static bool isValidDest(Operation *op)
True if this operation is a good site to sink operations.
static bool cloneable(Operation *op)
static InstancePath empty
#define CIRCT_DEBUG_SCOPED_PASS_LOGGER(PASS)
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
This graph tracks modules and where they are instantiated.
decltype(auto) walkPostOrder(Fn &&fn)
Perform a post-order walk across the modules.
connect(destination, source)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
static Direction get(bool isOutput)
Return an output direction if isOutput is true, otherwise return an input direction.
Direction
This represents the direction of a single port.
bool hasDontTouch(Value value)
Check whether a block argument ("port") or the operation defining a value has a DontTouch annotation,...
ArrayAttr getAnnotationsIfPresent(Operation *op)
static bool operator==(const ModulePort &a, const ModulePort &b)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
bool operator!=(uint64_t a, const FVInt &b)
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)