19#include "mlir/Dialect/Arith/IR/Arith.h"
20#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
21#include "mlir/Dialect/Func/IR/FuncOps.h"
22#include "mlir/Dialect/MemRef/IR/MemRef.h"
23#include "mlir/Dialect/SCF/IR/SCF.h"
24#include "mlir/Dialect/Tensor/IR/Tensor.h"
25#include "mlir/Transforms/DialectConversion.h"
27#include "mlir/Rewrite/PatternApplicator.h"
28#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
30#include "mlir/IR/Dominance.h"
31#include "llvm/Support/raw_ostream.h"
35#define GEN_PASS_DEF_REMOVEUNUSEDENZYMEOPSPASS
36#include "Passes/Passes.h.inc"
41using namespace enzyme;
46bool mayExecuteBefore(Block *blk, Operation *check, Operation *end) {
47 auto reg = blk->getParent();
49 assert(reg->isAncestor(end->getParentRegion()));
51 DenseSet<Block *> visitedBlocks;
53 SmallVector<Block *> blocksToVisit;
54 for (
auto succ : blk->getSuccessors()) {
55 blocksToVisit.push_back(succ);
58 while (!blocksToVisit.empty()) {
59 Block *cur = blocksToVisit.pop_back_val();
61 if (visitedBlocks.contains(cur))
64 visitedBlocks.insert(cur);
67 for (
auto &op : *cur) {
70 if (op.isAncestor(check)) {
87 if (op.isAncestor(end)) {
97 for (
auto succ : cur->getSuccessors()) {
98 blocksToVisit.push_back(succ);
105bool mayExecuteBetween(Operation *start, Operation *check, Operation *end) {
106 Block *blk = start->getBlock();
107 auto checkAnc = check;
108 while (checkAnc && checkAnc->getBlock() != blk)
109 checkAnc = checkAnc->getParentOp();
112 while (endAnc && endAnc->getBlock() != blk)
113 endAnc = endAnc->getParentOp();
117 if (checkAnc && checkAnc == endAnc)
121 if (checkAnc->isBeforeInBlock(start))
125 if (endAnc->isBeforeInBlock(start))
129 if (checkAnc && endAnc) {
130 if (checkAnc->isBeforeInBlock(endAnc))
141 auto reg = blk->getParent();
142 if (reg->isAncestor(end->getParentRegion())) {
143 return mayExecuteBefore(blk, check, end);
148 if (reg->isAncestor(check->getParentRegion())) {
152 return mayExecuteBetween(start->getParentOp(), check, end);
159template <
class T,
class T2 = T>
160T findNearestDominatingOpByUse(Operation *op, Value v) {
162 PostDominanceInfo pdInfo;
164 SmallVector<T, 1> options;
165 SmallVector<Operation *, 1> conflicts;
166 for (Operation *userSet : v.getUsers()) {
167 if (
auto setOp = dyn_cast<T>(userSet)) {
168 options.push_back(setOp);
169 conflicts.push_back(setOp);
172 if (
auto setOp = dyn_cast<T2>(userSet)) {
173 conflicts.push_back(setOp);
178 for (
auto opt : options) {
179 if (!dInfo.dominates(opt, op))
181 bool conflict =
false;
182 for (
auto opt2 : conflicts) {
188 if (!mayExecuteBetween(opt, opt2, op)) {
205 LogicalResult matchAndRewrite(enzyme::PopOp pop,
206 PatternRewriter &rewriter)
const final {
208 auto init = pop.getCache().getDefiningOp<enzyme::InitOp>();
212 SmallVector<enzyme::PopOp, 1> pops;
213 SmallVector<enzyme::PushOp, 1> pushes;
214 for (Operation *userSet : init.getResult().getUsers()) {
215 if (
auto push = dyn_cast<enzyme::PushOp>(userSet)) {
216 pushes.push_back(push);
219 if (
auto pop = dyn_cast<enzyme::PopOp>(userSet)) {
226 if (
auto push = findNearestDominatingOpByUse<enzyme::PushOp, enzyme::PopOp>(
229 if (pop->getBlock() == push->getBlock()) {
230 rewriter.replaceOp(pop, push.getValue());
231 rewriter.eraseOp(push);
252 LogicalResult matchAndRewrite(enzyme::PopOp pop,
253 PatternRewriter &rewriter)
const final {
254 if (!pop.getResult().use_empty())
257 auto init = pop.getCache().getDefiningOp<enzyme::InitOp>();
263 SmallVector<enzyme::PushOp> pushes;
264 for (Operation *user : init.getResult().getUsers()) {
267 if (
auto push = dyn_cast<enzyme::PushOp>(user)) {
268 pushes.push_back(push);
274 rewriter.eraseOp(pop);
275 for (enzyme::PushOp push : pushes)
276 rewriter.eraseOp(push);
277 rewriter.eraseOp(init);
285 LogicalResult matchAndRewrite(enzyme::GetOp get,
286 PatternRewriter &rewriter)
const final {
288 auto init = get.getGradient().getDefiningOp<enzyme::InitOp>();
292 for (Operation *userSet : init.getResult().getUsers()) {
293 if (isa<enzyme::GetOp>(userSet))
295 if (isa<enzyme::SetOp>(userSet))
300 if (
auto set = findNearestDominatingOpByUse<enzyme::SetOp>(get, init)) {
301 rewriter.replaceOp(get, set.getValue());
311 LogicalResult matchAndRewrite(enzyme::SetOp get,
312 PatternRewriter &rewriter)
const final {
314 auto init = get.getGradient().getDefiningOp<enzyme::InitOp>();
318 for (Operation *userSet : init.getResult().getUsers()) {
319 if (isa<enzyme::SetOp>(userSet))
324 rewriter.eraseOp(get);
332 LogicalResult matchAndRewrite(enzyme::PushOp get,
333 PatternRewriter &rewriter)
const final {
335 auto init = get.getCache().getDefiningOp<enzyme::InitOp>();
339 for (Operation *userSet : init.getResult().getUsers()) {
340 if (isa<enzyme::PushOp>(userSet))
345 rewriter.eraseOp(get);
353 LogicalResult matchAndRewrite(enzyme::InitOp init,
354 PatternRewriter &rewriter)
const final {
355 if (init.use_empty()) {
356 rewriter.eraseOp(init);
363struct IgnoreDerivativesSimplifyPattern
367 LogicalResult matchAndRewrite(enzyme::IgnoreDerivativesOp op,
368 PatternRewriter &rewriter)
const override {
369 rewriter.replaceOp(op, op.getOperand());
374static void applyPatterns(Operation *op) {
375 RewritePatternSet patterns(op->getContext());
376 patterns.insert<PopSimplify, DeadPopSimplify, GetSimplify, PushSimplify,
377 SetSimplify, InitSimplify, IgnoreDerivativesSimplifyPattern>(
380 GreedyRewriteConfig config;
381 config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Normal);
382 config.enableFolding();
383 (void)applyPatternsGreedily(op, std::move(patterns), config);
388static void applyPatternsToEnzymeOps(Operation *op,
389 RewriterBase::Listener *listener) {
390 SmallVector<Operation *> ops;
391 op->walk([&](Operation *nested) {
392 if (isa<enzyme::InitOp, enzyme::PushOp, enzyme::PopOp, enzyme::GetOp,
393 enzyme::SetOp>(nested))
394 ops.push_back(nested);
399 RewritePatternSet patterns(op->getContext());
400 patterns.insert<PopSimplify, DeadPopSimplify, GetSimplify, PushSimplify,
401 SetSimplify, InitSimplify>(op->getContext());
403 GreedyRewriteConfig config;
404 config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Normal);
405 config.enableFolding();
407 config.setListener(listener);
408 (void)applyOpPatternsGreedily(ops, std::move(patterns), config);
411static bool hasInitInRegions(Operation *op) {
412 return op->walk([](enzyme::InitOp) {
return WalkResult::interrupt(); })
416static void annotateRegionOpsInLoops(Operation *op) {
428 op->walk([](LoopLikeOpInterface loop) {
429 loop->walk([&](RegionBranchOpInterface regionBranch) {
430 if (regionBranch.hasLoop())
437 bool anyLoopLocal =
false;
438 auto checkCache = [&](Value cache) {
439 Operation *init = cache.getDefiningOp();
440 if (!init || loop->isProperAncestor(init))
443 regionBranch->walk([&](Operation *nested) {
446 if (nested != regionBranch.getOperation() &&
447 isa<LoopLikeOpInterface>(nested))
448 return WalkResult::skip();
450 if (
auto push = dyn_cast<enzyme::PushOp>(nested))
451 checkCache(push.getCache());
452 else if (
auto pop = dyn_cast<enzyme::PopOp>(nested))
453 checkCache(pop.getCache());
454 return WalkResult::advance();
460 UnitAttr::get(regionBranch.getContext()));
470 Worklist() { list.reserve(8); }
473 void push(Operation *op);
474 void remove(Operation *op);
479 std::vector<Operation *> list;
480 llvm::DenseMap<Operation *, unsigned> map;
483bool Worklist::empty() {
485 return !llvm::any_of(list,
486 [](Operation *op) {
return static_cast<bool>(op); });
489void Worklist::push(Operation *op) {
490 assert(op &&
"cannot push nullptr to worklist");
492 if (!map.insert({op, list.size()}).second)
497void Worklist::reverse() {
498 std::reverse(list.begin(), list.end());
499 for (
size_t i = 0, e = list.size(); i < e; ++i)
503Operation *Worklist::pop() {
507 Operation *op = list.back();
511 while (!list.empty() && !list.back())
516void Worklist::remove(Operation *op) {
517 assert(op &&
"cannot remove nullptr from worklist");
518 auto it = map.find(op);
519 if (it != map.end()) {
520 assert(list[it->second] == op &&
"malformed worklist data structure");
521 list[it->second] =
nullptr;
529class PostOrderWalkDriver :
public RewriterBase::Listener {
531 PostOrderWalkDriver(Operation *root_) : root(root_) {}
533 void initializeWorklist();
534 LogicalResult processWorklist();
537 void notifyOperationInserted(Operation *op,
538 OpBuilder::InsertPoint previous)
override;
539 void notifyOperationErased(Operation *op)
override;
542 notifyMatchFailure(Location loc,
543 function_ref<
void(Diagnostic &)> reasonCallback)
override;
546 void addToWorklist(Operation *op);
550 Operation *current =
nullptr;
554void PostOrderWalkDriver::addToWorklist(Operation *op) {
556 if (!isa<EnzymeOpsRemoverOpInterface>(op))
561void PostOrderWalkDriver::notifyOperationInserted(
562 Operation *op, OpBuilder::InsertPoint previous) {
563 if (!isa<EnzymeOpsRemoverOpInterface>(op))
573 bool shouldInsert =
false;
574 (void)root->walk([&](EnzymeOpsRemoverOpInterface iface) {
575 if ((Operation *)iface == current) {
577 return WalkResult::interrupt();
580 if ((Operation *)iface == op) {
581 shouldInsert =
false;
582 return WalkResult::interrupt();
585 return WalkResult::advance();
592void PostOrderWalkDriver::notifyOperationErased(Operation *op) {
596 if (!isa<EnzymeOpsRemoverOpInterface>(op))
601void PostOrderWalkDriver::notifyMatchFailure(
602 Location loc, function_ref<
void(Diagnostic &)> reasonCallback) {
603 auto diag = mlir::emitError(loc);
604 reasonCallback(*diag.getUnderlyingDiagnostic());
607void PostOrderWalkDriver::initializeWorklist() {
608 root->walk<WalkOrder::PreOrder>(
609 [
this](EnzymeOpsRemoverOpInterface iface) { addToWorklist(iface); });
612LogicalResult PostOrderWalkDriver::processWorklist() {
613 PatternRewriter rewriter(root->getContext());
614 rewriter.setListener(
this);
617 while (!worklist.empty()) {
618 auto op = worklist.pop();
619 auto iface = cast<EnzymeOpsRemoverOpInterface>(op);
622 if (hasInitInRegions(op))
623 applyPatternsToEnzymeOps(op,
this);
625 rewriter.setInsertionPoint(current);
626 result &= iface.removeEnzymeOps(rewriter).succeeded();
630 return LogicalResult::success(result);
633struct RemoveUnusedEnzymeOpsPass
634 :
public enzyme::impl::RemoveUnusedEnzymeOpsPassBase<
635 RemoveUnusedEnzymeOpsPass> {
636 void runOnOperation()
override {
637 auto op = getOperation();
641 annotateRegionOpsInLoops(op);
643 op->walk([&](FunctionOpInterface func) {
644 PostOrderWalkDriver driver(func);
645 driver.initializeWorklist();
646 failed |= driver.processWorklist().failed();
static constexpr llvm::StringLiteral kPreserveCacheAttrName