18#include "mlir/Analysis/DataFlow/ConstantPropagationAnalysis.h"
19#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
20#include "mlir/Analysis/DataFlowFramework.h"
21#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22#include "mlir/IR/PatternMatch.h"
23#include "mlir/Interfaces/FunctionInterfaces.h"
24#include "mlir/Support/LLVM.h"
25#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
27#include "llvm/Support/raw_ostream.h"
32#define DEBUG_TYPE "enzyme-activity-opt"
33#define ENZYME_DBGS llvm::dbgs() << "[" << DEBUG_TYPE << "]"
37using namespace mlir::dataflow;
41#define GEN_PASS_DEF_ENZYMEACTIVITYOPT
42#include "Passes/Passes.h.inc"
48struct TaggedActivity {
53struct ActivityOptimization {
54 SmallVector<Activity> argumentActivity;
55 SmallVector<Activity> resultActivity;
56 SmallVector<TaggedActivity> taggedActivities;
60printTaggedActivityAnalysis(FunctionOpInterface callee,
61 ArrayRef<TaggedActivity> taggedActivities) {
62 llvm::errs() << FlatSymbolRefAttr::get(callee) <<
":\n";
63 for (
const TaggedActivity &activity : taggedActivities) {
64 llvm::errs() <<
" " << activity.tag <<
": "
65 << (activity.isConstant ?
"Constant" :
"Active") <<
"\n";
72static FailureOr<ActivityOptimization> computeClassicalActivityOptimization(
73 Operation *diffOp, FunctionOpInterface callee,
74 ArrayRef<Activity> argumentActivity, ArrayRef<Activity> resultActivity,
75 SymbolTableCollection &symbolTable) {
77 SmallPtrSet<Block *, 4> blocksNotForAnalysis;
78 SmallPtrSet<mlir::Value, 1> constargvals;
79 SmallPtrSet<mlir::Value, 1> activeargvals;
82 for (
auto &&[arg, act] :
83 llvm::zip(callee.getFunctionBody().getArguments(), argumentActivity)) {
84 if (act == Activity::enzyme_const)
85 constargvals.insert(arg);
87 activeargvals.insert(arg);
90 SmallVector<DIFFE_TYPE> ReturnActivity;
91 for (
auto act : resultActivity) {
92 if (act != Activity::enzyme_const)
98 DenseMap<Operation *, bool> readOnlyCache;
100 constargvals, activeargvals,
104 SmallVector<bool> isRetValueConstant(resultActivity.size(),
true);
105 callee.walk([&](Operation *op) {
107 op->getParentOp() != callee.getOperation())
110 for (
const auto &[idx, opret] : llvm::enumerate(op->getOperands())) {
111 bool icv = activityAnalyzer.isConstantValue(TR, opret);
112 isRetValueConstant[idx] &= icv;
117 ActivityOptimization optimized;
118 optimized.argumentActivity.assign(argumentActivity.begin(),
119 argumentActivity.end());
120 optimized.resultActivity.assign(resultActivity.begin(), resultActivity.end());
122 for (
auto &&[idx, ract] : llvm::enumerate(optimized.resultActivity)) {
123 if (isRetValueConstant[idx] && ract != Activity::enzyme_const &&
124 ract != Activity::enzyme_constnoneed) {
125 if (ract == Activity::enzyme_dup || ract == Activity::enzyme_active) {
126 ract = Activity::enzyme_const;
127 }
else if (ract == Activity::enzyme_activenoneed ||
128 ract == Activity::enzyme_dupnoneed) {
129 ract = Activity::enzyme_constnoneed;
138static FailureOr<ActivityOptimization> computeDataFlowActivityOptimization(
139 Operation *diffOp, FunctionOpInterface callee,
140 ArrayRef<Activity> argumentActivity, ArrayRef<Activity> resultActivity,
141 SymbolTableCollection &symbolTable,
bool collectActivityAnalysis =
false) {
143 Region &body = callee.getFunctionBody();
144 if (body.empty() || body.front().empty())
146 if (callee.getArguments().size() != argumentActivity.size()) {
148 <<
"callee argument count does not match activity count";
152 DataFlowSolver solver;
160 symbolTable, callee, argumentActivity, resultActivity);
161 solver.load<DeadCodeAnalysis>();
162 solver.load<SparseConstantPropagation>();
164 for (
const auto &[arg, activity] :
165 llvm::zip(callee.getArguments(), argumentActivity)) {
167 if (activity != Activity::enzyme_active &&
168 activity != Activity::enzyme_dup &&
169 activity != Activity::enzyme_dupnoneed)
176 SmallVector<Operation *> returnOps;
177 bool invalidReturn =
false;
181 op->getParentOp() != callee.getOperation())
184 if (op->getNumOperands() != resultActivity.size()) {
185 invalidReturn =
true;
187 <<
"return operand count does not match ret_activity count";
191 returnOps.push_back(op);
192 for (
const auto &[operand, activity] :
193 llvm::zip(op->getOperands(), resultActivity)) {
196 auto *returnLattice =
198 if (activity == Activity::enzyme_active ||
199 activity == Activity::enzyme_activenoneed ||
200 activity == Activity::enzyme_dup ||
201 activity == Activity::enzyme_dupnoneed) {
210 if (returnOps.empty())
213 Operation *analysisRoot = callee->getParentOfType<ModuleOp>();
215 analysisRoot = callee.getOperation();
216 if (failed(solver.initializeAndRun(analysisRoot))) {
217 diffOp->emitError() <<
"dataflow activity analysis failed";
221 auto isActiveData = [&](Value value) {
225 bool backwardActive = backward && backward->getValue().isActiveVal();
226 return forwardActive && backwardActive;
229 auto isConstantPointer = [&](Value value) {
231 solver.getProgramPointBefore(&body.front().front()));
232 auto *aliasClassLattice =
235 if (!backwardMemory || !aliasClassLattice ||
236 aliasClassLattice->isUndefined() || aliasClassLattice->isUnknown())
239 auto scheduleVisit = [](std::deque<DistinctAttr> &frontier,
240 DenseSet<DistinctAttr> &visited,
243 (void)aliasClasses.foreachElement(
245 if (state != enzyme::AliasClassSet::State::Defined || !neighbor) {
247 return ChangeResult::NoChange;
249 if (!visited.contains(neighbor)) {
250 visited.insert(neighbor);
251 frontier.push_back(neighbor);
253 return ChangeResult::NoChange;
258 for (Operation *returnOp : returnOps) {
260 solver.getProgramPointAfter(returnOp));
263 solver.getProgramPointAfter(returnOp));
264 if (!forwardMemory || !pointsToSets)
267 std::deque<DistinctAttr> frontier;
268 DenseSet<DistinctAttr> visited;
269 if (!scheduleVisit(frontier, visited,
270 aliasClassLattice->getAliasClassesObject()))
273 while (!frontier.empty()) {
274 DistinctAttr aliasClass = frontier.front();
275 frontier.pop_front();
277 if (forwardMemory->hasActiveData(aliasClass) &&
278 backwardMemory->activeDataFlowsOut(aliasClass))
285 if (!scheduleVisit(frontier, visited, pointsTo))
293 auto isConstantValue = [&](Value value) {
294 if (isa<LLVM::LLVMPointerType, MemRefType, UnrankedMemRefType>(
296 return isConstantPointer(value);
297 return !isActiveData(value);
301 ActivityOptimization optimized;
302 optimized.argumentActivity.assign(argumentActivity.begin(),
303 argumentActivity.end());
304 optimized.resultActivity.assign(resultActivity.begin(), resultActivity.end());
306 for (
const auto &[index, arg, activity] :
307 llvm::enumerate(callee.getArguments(), argumentActivity)) {
308 if (isConstantValue(arg)) {
309 if (activity == Activity::enzyme_active ||
310 activity == Activity::enzyme_dup ||
311 activity == Activity::enzyme_dupnoneed)
312 optimized.argumentActivity[index] = Activity::enzyme_const;
318 SmallVector<bool> allReturnValuesConstant(resultActivity.size(),
true);
319 for (Operation *returnOp : returnOps) {
320 for (
const auto &[index, operand] :
321 llvm::enumerate(returnOp->getOperands())) {
322 allReturnValuesConstant[index] =
323 allReturnValuesConstant[index] && isConstantValue(operand);
327 for (
const auto &[index, activity] : llvm::enumerate(resultActivity)) {
328 if (allReturnValuesConstant[index]) {
329 if (activity == Activity::enzyme_active ||
330 activity == Activity::enzyme_dup)
331 optimized.resultActivity[index] = Activity::enzyme_const;
332 else if (activity == Activity::enzyme_activenoneed ||
333 activity == Activity::enzyme_dupnoneed)
334 optimized.resultActivity[index] = Activity::enzyme_constnoneed;
338 if (collectActivityAnalysis) {
339 for (BlockArgument arg : callee.getArguments()) {
340 if (Attribute tag = callee.getArgAttr(arg.getArgNumber(),
"enzyme.tag"))
341 optimized.taggedActivities.push_back({tag, isConstantValue(arg)});
344 callee.walk([&](Operation *op) {
345 Attribute tag = op->getAttr(
"tag");
348 for (OpResult result : op->getResults())
349 optimized.taggedActivities.push_back({tag, isConstantValue(result)});
358 MinimizeForwardActivity(MLIRContext *context,
bool dataflow,
359 bool printActivityAnalysis,
360 PatternBenefit benefit = 1)
362 printActivityAnalysis(printActivityAnalysis) {}
364 LogicalResult matchAndRewrite(ForwardDiffOp op,
365 PatternRewriter &rewriter)
const override {
366 auto *ctx = rewriter.getContext();
367 SymbolTableCollection symbolTable;
368 FunctionOpInterface callee = dyn_cast_or_null<FunctionOpInterface>(
369 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
373 SmallVector<Activity> argumentActivity =
374 llvm::to_vector(op.getActivity().getAsValueRange<ActivityAttr>());
375 SmallVector<Activity> resultActivity =
376 llvm::to_vector(op.getRetActivity().getAsValueRange<ActivityAttr>());
378 FailureOr<ActivityOptimization> optimized =
380 ? computeDataFlowActivityOptimization(op, callee, argumentActivity,
381 resultActivity, symbolTable,
382 printActivityAnalysis)
383 : computeClassicalActivityOptimization(op, callee, argumentActivity,
384 resultActivity, symbolTable);
386 if (failed(optimized))
390 if (printActivityAnalysis && dataflow)
391 printTaggedActivityAnalysis(callee, optimized->taggedActivities);
393 if (llvm::equal(argumentActivity, optimized->argumentActivity) &&
394 llvm::equal(resultActivity, optimized->resultActivity))
398 SmallVector<Value> in_args;
400 for (
const auto &[idx, act, nact] :
401 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
402 Value inp = op.getInputs()[in_idx];
404 in_args.push_back(inp);
406 if (act == Activity::enzyme_dup || act == Activity::enzyme_dupnoneed) {
407 inp = op.getInputs()[in_idx];
408 in_args.push_back(inp);
413 in_args.push_back(inp);
416 if ((act == Activity::enzyme_dup ||
417 act == Activity::enzyme_dupnoneed) &&
418 nact == Activity::enzyme_const)
423 SmallVector<Value> out_args;
425 for (
const auto &[idx, act, nact] :
426 llvm::enumerate(resultActivity, optimized->resultActivity)) {
427 if (act == Activity::enzyme_constnoneed)
430 Value out = op.getOutputs()[out_idx];
433 case Activity::enzyme_active:
434 case Activity::enzyme_activenoneed:
435 case Activity::enzyme_const:
436 out_args.push_back(out);
440 case Activity::enzyme_dup:
441 case Activity::enzyme_dupnoneed:
442 out_args.push_back(out);
446 out = op.getOutputs()[out_idx];
447 out_args.push_back(out);
452 llvm_unreachable(
"unexpected return activity arg");
461 case Activity::enzyme_active:
462 case Activity::enzyme_dup:
463 out_args.push_back(out);
468 case Activity::enzyme_dupnoneed:
469 case Activity::enzyme_activenoneed:
475 llvm_unreachable(
"unexpected demotion for return activity");
480 TypeRange out_ty = ValueRange(out_args).getTypes();
481 ArrayAttr newInActivity = ArrayAttr::get(
482 ctx, llvm::map_to_vector(optimized->argumentActivity,
483 [&](enzyme::Activity act) -> Attribute {
484 return ActivityAttr::get(ctx, act);
487 ArrayAttr newOutActivity = ArrayAttr::get(
488 ctx, llvm::map_to_vector(optimized->resultActivity,
489 [&](enzyme::Activity act) -> Attribute {
490 return ActivityAttr::get(ctx, act);
493 ForwardDiffOp newOp = ForwardDiffOp::create(
494 rewriter, op->getLoc(), out_ty, op.getFnAttr(), in_args, newInActivity,
495 newOutActivity, op.getWidthAttr(), op.getStrongZeroAttr());
500 for (
auto [idx, old_val, new_val] :
501 llvm::enumerate(resultActivity, optimized->resultActivity)) {
503 if (old_val == new_val) {
505 if (old_val == Activity::enzyme_constnoneed) {
509 op.getOutputs()[oldIdx++].replaceAllUsesWith(
510 newOp.getOutputs()[newIdx++]);
511 if (old_val == Activity::enzyme_dup) {
513 op.getOutputs()[oldIdx++].replaceAllUsesWith(
514 newOp.getOutputs()[newIdx++]);
518 if (new_val == Activity::enzyme_constnoneed &&
519 old_val == Activity::enzyme_dupnoneed) {
521 }
else if (new_val == Activity::enzyme_const &&
522 old_val == Activity::enzyme_dup) {
523 op.getOutputs()[oldIdx++].replaceAllUsesWith(
524 newOp.getOutputs()[newIdx++]);
527 llvm_unreachable(
"unexpected demotion for return activity");
532 rewriter.eraseOp(op);
538 bool printActivityAnalysis;
543 MinimizeReverseActivity(MLIRContext *context,
bool dataflow,
544 bool printActivityAnalysis,
545 PatternBenefit benefit = 1)
547 printActivityAnalysis(printActivityAnalysis) {}
549 LogicalResult matchAndRewrite(AutoDiffOp op,
550 PatternRewriter &rewriter)
const override {
551 auto *ctx = rewriter.getContext();
552 SymbolTableCollection symbolTable;
553 FunctionOpInterface callee = dyn_cast_or_null<FunctionOpInterface>(
554 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
558 SmallVector<Activity> argumentActivity =
559 llvm::to_vector(op.getActivity().getAsValueRange<ActivityAttr>());
560 SmallVector<Activity> resultActivity =
561 llvm::to_vector(op.getRetActivity().getAsValueRange<ActivityAttr>());
563 FailureOr<ActivityOptimization> optimized =
565 ? computeDataFlowActivityOptimization(op, callee, argumentActivity,
566 resultActivity, symbolTable,
567 printActivityAnalysis)
568 : computeClassicalActivityOptimization(op, callee, argumentActivity,
569 resultActivity, symbolTable);
571 if (failed(optimized))
575 if (printActivityAnalysis && dataflow)
576 printTaggedActivityAnalysis(callee, optimized->taggedActivities);
578 if (llvm::equal(argumentActivity, optimized->argumentActivity) &&
579 llvm::equal(resultActivity, optimized->resultActivity))
583 SmallVector<Value> in_args;
585 for (
const auto &[idx, act, nact] :
586 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
587 mlir::Value res = op.getInputs()[in_idx];
590 in_args.push_back(res);
593 if (act == Activity::enzyme_dup || act == Activity::enzyme_dupnoneed) {
594 mlir::Value dres = op.getInputs()[in_idx];
595 in_args.push_back(dres);
601 if (nact != Activity::enzyme_const)
604 case Activity::enzyme_active:
605 in_args.push_back(res);
608 case Activity::enzyme_dup:
609 case Activity::enzyme_dupnoneed:
610 in_args.push_back(res);
616 "unexpected demotion for input activity in reverse mode");
623 if (in_idx == op.getInputs().size())
626 SmallVector<Value> out_args;
627 SmallVector<Type> out_ty;
629 for (
const auto &[idx, act, nact] :
630 llvm::enumerate(resultActivity, optimized->resultActivity)) {
632 if (act == Activity::enzyme_constnoneed) {
634 return op->emitError() <<
"unknown activity demotion";
641 if (act == Activity::enzyme_active ||
642 act == Activity::enzyme_activenoneed) {
643 mlir::Value diffret = op.getInputs()[in_idx];
644 in_args.push_back(diffret);
650 if (act == Activity::enzyme_activenoneed) {
658 case Activity::enzyme_active:
659 case Activity::enzyme_dup:
660 case Activity::enzyme_const: {
661 mlir::Value res = op.getOutputs()[out_idx];
662 out_args.push_back(res);
663 out_ty.push_back(res.getType());
668 case Activity::enzyme_dupnoneed: {
673 llvm_unreachable(
"unknown reverse mode activity");
687 (act == Activity::enzyme_active &&
688 nact == Activity::enzyme_const) ||
689 (act == Activity::enzyme_activenoneed &&
690 nact == Activity::enzyme_constnoneed) ||
691 (act == Activity::enzyme_dup && nact == Activity::enzyme_const) ||
692 (act == Activity::enzyme_dupnoneed &&
693 nact == Activity::enzyme_constnoneed);
697 if (act == Activity::enzyme_activenoneed &&
698 nact == Activity::enzyme_constnoneed) {
704 case Activity::enzyme_active: {
706 mlir::Value res = op.getOutputs()[out_idx];
707 out_args.push_back(res);
708 out_ty.push_back(res.getType());
712 case Activity::enzyme_dup: {
713 mlir::Value res = op.getOutputs()[out_idx];
714 out_args.push_back(res);
715 out_ty.push_back(res.getType());
719 case Activity::enzyme_dupnoneed: {
724 llvm_unreachable(
"unknown return activity demotion");
730 for (
const auto &[idx, act, nact] :
731 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
733 if (act == Activity::enzyme_active) {
734 mlir::Value res = op.getOutputs()[out_idx];
735 out_args.push_back(res);
736 out_ty.push_back(res.getType());
741 if (act == Activity::enzyme_active && nact == Activity::enzyme_const) {
747 ArrayAttr newInActivity = ArrayAttr::get(
748 ctx, llvm::map_to_vector(optimized->argumentActivity,
749 [&](enzyme::Activity act) -> Attribute {
750 return ActivityAttr::get(ctx, act);
753 ArrayAttr newOutActivity = ArrayAttr::get(
754 ctx, llvm::map_to_vector(optimized->resultActivity,
755 [&](enzyme::Activity act) -> Attribute {
756 return ActivityAttr::get(ctx, act);
759 AutoDiffOp newOp = AutoDiffOp::create(
760 rewriter, op->getLoc(), out_ty, op.getFnAttr(), in_args, newInActivity,
761 newOutActivity, op.getWidthAttr(), op.getStrongZeroAttr(),
762 op.getAtomicAddAttr());
767 for (
auto [idx, old_val, new_val] :
768 llvm::enumerate(resultActivity, optimized->resultActivity)) {
770 if (old_val == new_val) {
772 if (old_val == Activity::enzyme_constnoneed ||
773 old_val == Activity::enzyme_activenoneed ||
774 old_val == Activity::enzyme_dupnoneed) {
778 op.getOutputs()[oldIdx++].replaceAllUsesWith(
779 newOp.getOutputs()[newIdx++]);
782 if (new_val == Activity::enzyme_constnoneed &&
783 old_val == Activity::enzyme_dupnoneed) {
785 }
else if (new_val == Activity::enzyme_const &&
786 old_val == Activity::enzyme_dup) {
787 op.getOutputs()[oldIdx++].replaceAllUsesWith(
788 newOp.getOutputs()[newIdx++]);
789 }
else if (old_val == Activity::enzyme_active &&
790 new_val == Activity::enzyme_const) {
791 op.getOutputs()[oldIdx++].replaceAllUsesWith(
792 newOp.getOutputs()[newIdx++]);
793 }
else if (old_val == Activity::enzyme_activenoneed &&
794 new_val == Activity::enzyme_constnoneed) {
797 llvm_unreachable(
"unexpected demotion for return activity");
803 for (
auto [idx, old_val, new_val] :
804 llvm::enumerate(argumentActivity, optimized->argumentActivity)) {
805 if (old_val == new_val) {
806 if (old_val == Activity::enzyme_active) {
807 op.getOutputs()[oldIdx++].replaceAllUsesWith(
808 newOp.getOutputs()[newIdx++]);
811 if (old_val == Activity::enzyme_active &&
812 new_val == Activity::enzyme_const) {
818 rewriter.eraseOp(op);
824 bool printActivityAnalysis;
827struct EnzymeActivityOptPass
828 :
public enzyme::impl::EnzymeActivityOptBase<EnzymeActivityOptPass> {
829 using EnzymeActivityOptBase::EnzymeActivityOptBase;
831 void runOnOperation()
override {
832 RewritePatternSet patterns(&getContext());
833 patterns.add<MinimizeForwardActivity, MinimizeReverseActivity>(
834 &getContext(), dataflow, printActivityAnalysis);
836 GreedyRewriteConfig config;
837 config.enableFolding();
838 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns),
Helper class to analyze the differential activity.
This analysis implements interprocedural alias analysis.
enzyme::ValueActivity getValue() const
const AliasClassSet & getPointsTo(DistinctAttr id) const
Sparse activity analysis reasons about activity by traversing forward down the def-use chains startin...
static ValueActivity getActiveVal()
bool isFunctionReturnLike(Operation *op)