21#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/BuiltinOps.h"
24#include "mlir/Interfaces/CallInterfaces.h"
25#include "mlir/Interfaces/FunctionInterfaces.h"
27#include "llvm/ADT/TypeSwitch.h"
29#include "llvm/Demangle/Demangle.h"
35#define GEN_PASS_DEF_PRINTACTIVITYANALYSISPASS
36#include "Passes/Passes.h.inc"
42enzyme::Activity getDefaultActivity(Type argType) {
43 if (argType.isIntOrIndex())
44 return enzyme::Activity::enzyme_const;
46 if (isa<FloatType, ComplexType>(argType))
47 return enzyme::Activity::enzyme_active;
49 if (
auto T = dyn_cast<TensorType>(argType))
50 return getDefaultActivity(T.getElementType());
52 if (isa<LLVM::LLVMPointerType, MemRefType>(argType))
53 return enzyme::Activity::enzyme_dup;
55 return enzyme::Activity::enzyme_const;
58struct PrintActivityAnalysisPass
59 :
public enzyme::impl::PrintActivityAnalysisPassBase<
60 PrintActivityAnalysisPass> {
61 using PrintActivityAnalysisPassBase::PrintActivityAnalysisPassBase;
65 void initializeArgAndResActivities(
66 FunctionOpInterface callee,
67 MutableArrayRef<enzyme::Activity> argActivities,
68 MutableArrayRef<enzyme::Activity> resActivities)
const {
69 for (
const auto &[idx, argType] :
70 llvm::enumerate(callee.getArgumentTypes())) {
71 if (callee.getArgAttr(idx,
"enzyme.const") || inactiveArgs)
72 argActivities[idx] = enzyme::Activity::enzyme_const;
74 argActivities[idx] = getDefaultActivity(argType);
77 for (
const auto &[idx, resType] :
78 llvm::enumerate(callee.getResultTypes())) {
80 resActivities[idx] = (enzyme::Activity::enzyme_dup);
82 resActivities[idx] = getDefaultActivity(resType);
86 void inferArgActivitiesFromEnzymeAutodiff(
87 FunctionOpInterface callee, CallOpInterface autodiff_call,
88 MutableArrayRef<enzyme::Activity> argActivities,
89 MutableArrayRef<enzyme::Activity> resultActivities) {
91 for (
const auto &[paramIdx, paramType] :
92 llvm::enumerate(callee.getArgumentTypes())) {
93 Value arg = autodiff_call.getArgOperands()[argIdx];
95 dyn_cast_if_present<LLVM::LoadOp>(arg.getDefiningOp())) {
96 if (
auto addressOf = dyn_cast_if_present<LLVM::AddressOfOp>(
97 loadOp.getAddr().getDefiningOp())) {
98 if (addressOf.getGlobalName() ==
"enzyme_const") {
99 argActivities[paramIdx] = enzyme::Activity::enzyme_const;
100 }
else if (addressOf.getGlobalName() ==
"enzyme_dup") {
101 argActivities[paramIdx] = enzyme::Activity::enzyme_dup;
104 }
else if (addressOf.getGlobalName() ==
"enzyme_dupnoneed") {
105 argActivities[paramIdx] = enzyme::Activity::enzyme_dupnoneed;
113 argActivities[paramIdx] =
114 llvm::TypeSwitch<Type, enzyme::Activity>(paramType)
115 .Case<FloatType, ComplexType>(
116 [](
auto type) {
return enzyme::Activity::enzyme_active; })
117 .Case<LLVM::LLVMPointerType, MemRefType>([&](
auto type) {
120 return enzyme::Activity::enzyme_dup;
123 [](Type type) {
return enzyme::Activity::enzyme_const; });
128 for (
const auto &[resIdx, resType] :
129 llvm::enumerate(callee.getResultTypes())) {
130 resultActivities[resIdx] =
131 llvm::TypeSwitch<Type, enzyme::Activity>(resType)
132 .Case<FloatType, ComplexType>(
133 [](
auto type) {
return enzyme::Activity::enzyme_active; })
135 [](Type type) {
return enzyme::Activity::enzyme_const; });
140 FunctionOpInterface callee,
141 ArrayRef<enzyme::Activity> argActivities,
142 ArrayRef<enzyme::Activity> resultActivities,
149 true, verbose, annotate);
152 SmallPtrSet<Block *, 4> blocksNotForAnalysis;
155 SmallPtrSet<mlir::Value, 1> constant_values;
156 SmallPtrSet<mlir::Value, 1> activevals_;
157 for (
auto &&[arg, act] :
158 llvm::zip(callee.getFunctionBody().getArguments(), argActivities)) {
159 if (act == enzyme::Activity::enzyme_const)
160 constant_values.insert(arg);
162 activevals_.insert(arg);
164 SmallVector<DIFFE_TYPE> ReturnActivity;
165 for (
auto act : resultActivities) {
166 if (act != enzyme::Activity::enzyme_const)
172 DenseMap<Operation *, bool> readOnlyCache;
174 readOnlyCache, constant_values,
175 activevals_, ReturnActivity);
177 callee.walk([&](Operation *op) {
180 MLIRContext *ctx = callee.getContext();
181 callee.walk([&](Operation *op) {
183 llvm::outs() <<
" Operation: " << *op <<
"\n";
184 for (
auto ® : op->getRegions()) {
185 for (
auto &blk : reg.getBlocks()) {
186 for (
auto &arg : blk.getArguments()) {
187 bool icv = activityAnalyzer.isConstantValue(TR, arg);
189 op->setAttr(
"enzyme.arg_icv" +
190 std::to_string(arg.getArgNumber()),
191 BoolAttr::get(ctx, icv));
193 llvm::outs() <<
" arg: " << arg <<
" icv=" << icv <<
"\n";
198 bool ici = activityAnalyzer.isConstantOperation(TR, op);
200 op->setAttr(
"enzyme.ici", BoolAttr::get(ctx, ici));
202 llvm::outs() <<
" op ici=" << ici <<
"\n";
204 for (
auto res : op->getResults()) {
205 bool icv = activityAnalyzer.isConstantValue(TR, res);
207 op->setAttr(
"enzyme.res_icv" +
208 std::to_string(res.getResultNumber()),
209 BoolAttr::get(ctx, icv));
211 llvm::outs() <<
" res: " << res <<
" icv=" << icv <<
"\n";
217 void runOnOperation()
override {
225 auto moduleOp = cast<ModuleOp>(getOperation());
227 if (inferFromAutodiff) {
229 Operation *autodiff_decl = moduleOp.lookupSymbol(
"__enzyme_autodiff");
230 if (!autodiff_decl) {
231 for (
auto &subOp : *moduleOp.getBody()) {
232 if (
auto func = dyn_cast<FunctionOpInterface>(&subOp)) {
233 if (func.getName().contains(
"__enzyme_autodiff")) {
234 autodiff_decl = &subOp;
240 if (!autodiff_decl) {
241 moduleOp.emitError(
"Failed to find __enzyme_autodiff symbol");
242 return signalPassFailure();
244 auto uses = SymbolTable::getSymbolUses(autodiff_decl, moduleOp);
245 assert(uses &&
"failed to find symbol uses of autodiff decl");
247 for (SymbolTable::SymbolUse use : *uses) {
248 auto autodiff_call = cast<CallOpInterface>(use.getUser());
249 FlatSymbolRefAttr calleeAttr =
250 cast<LLVM::AddressOfOp>(
251 autodiff_call.getArgOperands().front().getDefiningOp())
252 .getGlobalNameAttr();
254 cast<FunctionOpInterface>(moduleOp.lookupSymbol(calleeAttr));
256 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
257 resultActivities{callee.getNumResults()};
260 inferArgActivitiesFromEnzymeAutodiff(callee, autodiff_call,
261 argActivities, resultActivities);
262 runActivityAnalysis(config, callee, argActivities, resultActivities);
267 if (funcsToAnalyze.empty()) {
268 moduleOp.walk([
this, config](FunctionOpInterface callee) {
269 if (callee.isExternal() || callee.isPrivate())
272 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
273 resultActivities{callee.getNumResults()};
274 initializeArgAndResActivities(callee, argActivities, resultActivities);
276 runActivityAnalysis(config, callee, argActivities, resultActivities);
281 for (std::string funcName : funcsToAnalyze) {
282 Operation *op = moduleOp.lookupSymbol(funcName);
287 if (!isa<FunctionOpInterface>(op)) {
289 <<
"Operation " << funcName <<
" was not a FunctionOpInterface";
290 return signalPassFailure();
293 auto callee = cast<FunctionOpInterface>(op);
294 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
295 resultActivities{callee.getNumResults()};
296 initializeArgAndResActivities(callee, argActivities, resultActivities);
298 runActivityAnalysis(config, callee, argActivities, resultActivities);
Helper class to analyze the differential activity.
bool annotate
Annotate the IR with activity information for every operation.
bool inferFromAutodiff
Infer the starting argument state from an __enzyme_autodiff call.
bool verbose
Output extra information for debugging.
bool dataflow
Whether to use the data-flow based algorithm or the classic activity analysis.
bool relative
Use function summaries.
void runDataFlowActivityAnalysis(FunctionOpInterface callee, ArrayRef< enzyme::Activity > argumentActivity, ArrayRef< enzyme::Activity > returnActivity, bool print=false, bool verbose=false, bool annotate=false)
void runActivityAnnotations(FunctionOpInterface callee, ArrayRef< enzyme::Activity > argActivities={}, const ActivityPrinterConfig &config=ActivityPrinterConfig())