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"
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))
87 EffectInfo(CircuitOp circuit, InstanceGraph &instanceGraph) {
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)
decltype(auto) walkPostOrder(Fn &&fn)
Perform a post-order walk across the modules.
connect(destination, source)
static Direction get(bool isOutput)
Return an output direction if isOutput is true, otherwise return an input direction.
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, List[str] cmdline_args=sys.argv)