Enzyme main
Loading...
Searching...
No Matches
PrintActivityAnalysis.cpp
Go to the documentation of this file.
1//===- PrintActivityAnalysis.cpp - Pass to print activity analysis --------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements a pass to print the results of running activity
10// analysis.
11//
12//===----------------------------------------------------------------------===//
16#include "Dialect/Ops.h"
18#include "Passes/PassDetails.h"
19#include "Passes/Passes.h"
20
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"
26
27#include "llvm/ADT/TypeSwitch.h"
28
29#include "llvm/Demangle/Demangle.h"
30
31using namespace mlir;
32
33namespace mlir {
34namespace enzyme {
35#define GEN_PASS_DEF_PRINTACTIVITYANALYSISPASS
36#include "Passes/Passes.h.inc"
37} // namespace enzyme
38} // namespace mlir
39
40namespace {
41
42enzyme::Activity getDefaultActivity(Type argType) {
43 if (argType.isIntOrIndex())
44 return enzyme::Activity::enzyme_const;
45
46 if (isa<FloatType, ComplexType>(argType))
47 return enzyme::Activity::enzyme_active;
48
49 if (auto T = dyn_cast<TensorType>(argType))
50 return getDefaultActivity(T.getElementType());
51
52 if (isa<LLVM::LLVMPointerType, MemRefType>(argType))
53 return enzyme::Activity::enzyme_dup;
54
55 return enzyme::Activity::enzyme_const;
56}
57
58struct PrintActivityAnalysisPass
59 : public enzyme::impl::PrintActivityAnalysisPassBase<
60 PrintActivityAnalysisPass> {
61 using PrintActivityAnalysisPassBase::PrintActivityAnalysisPassBase;
62
63 /// Do the simplest possible inference of argument and result activities, or
64 /// take the user's explicit override if provided
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;
73 else
74 argActivities[idx] = getDefaultActivity(argType);
75 }
76
77 for (const auto &[idx, resType] :
78 llvm::enumerate(callee.getResultTypes())) {
79 if (duplicatedRet)
80 resActivities[idx] = (enzyme::Activity::enzyme_dup);
81 else
82 resActivities[idx] = getDefaultActivity(resType);
83 }
84 }
85
86 void inferArgActivitiesFromEnzymeAutodiff(
87 FunctionOpInterface callee, CallOpInterface autodiff_call,
88 MutableArrayRef<enzyme::Activity> argActivities,
89 MutableArrayRef<enzyme::Activity> resultActivities) {
90 unsigned argIdx = 1;
91 for (const auto &[paramIdx, paramType] :
92 llvm::enumerate(callee.getArgumentTypes())) {
93 Value arg = autodiff_call.getArgOperands()[argIdx];
94 if (auto loadOp =
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;
102 // Skip the shadow
103 argIdx++;
104 } else if (addressOf.getGlobalName() == "enzyme_dupnoneed") {
105 argActivities[paramIdx] = enzyme::Activity::enzyme_dupnoneed;
106 // Skip the shadow
107 argIdx++;
108 }
109 }
110 // Skip the enzyme_* annotation
111 argIdx++;
112 } else {
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) {
118 // Skip the shadow
119 argIdx++;
120 return enzyme::Activity::enzyme_dup;
121 })
122 .Default(
123 [](Type type) { return enzyme::Activity::enzyme_const; });
124 }
125 argIdx++;
126 }
127
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; })
134 .Default(
135 [](Type type) { return enzyme::Activity::enzyme_const; });
136 }
137 }
138
139 void runActivityAnalysis(const enzyme::ActivityPrinterConfig &config,
140 FunctionOpInterface callee,
141 ArrayRef<enzyme::Activity> argActivities,
142 ArrayRef<enzyme::Activity> resultActivities,
143 bool print = true) {
144 if (config.relative) {
145 enzyme::runActivityAnnotations(callee, argActivities, config);
146 } else if (config.dataflow) {
147 enzyme::runDataFlowActivityAnalysis(callee, argActivities,
148 resultActivities,
149 /*print=*/true, verbose, annotate);
150 } else {
151
152 SmallPtrSet<Block *, 4> blocksNotForAnalysis;
153
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);
161 else
162 activevals_.insert(arg);
163 }
164 SmallVector<DIFFE_TYPE> ReturnActivity;
165 for (auto act : resultActivities) {
166 if (act != enzyme::Activity::enzyme_const)
167 ReturnActivity.push_back(DIFFE_TYPE::DUP_ARG);
168 else
169 ReturnActivity.push_back(DIFFE_TYPE::CONSTANT);
170 }
171
172 DenseMap<Operation *, bool> readOnlyCache;
173 enzyme::ActivityAnalyzer activityAnalyzer(blocksNotForAnalysis,
174 readOnlyCache, constant_values,
175 activevals_, ReturnActivity);
176
177 callee.walk([&](Operation *op) {
178
179 });
180 MLIRContext *ctx = callee.getContext();
181 callee.walk([&](Operation *op) {
182 if (print)
183 llvm::outs() << " Operation: " << *op << "\n";
184 for (auto &reg : op->getRegions()) {
185 for (auto &blk : reg.getBlocks()) {
186 for (auto &arg : blk.getArguments()) {
187 bool icv = activityAnalyzer.isConstantValue(TR, arg);
188 if (annotate)
189 op->setAttr("enzyme.arg_icv" +
190 std::to_string(arg.getArgNumber()),
191 BoolAttr::get(ctx, icv));
192 if (print)
193 llvm::outs() << " arg: " << arg << " icv=" << icv << "\n";
194 }
195 }
196 }
197
198 bool ici = activityAnalyzer.isConstantOperation(TR, op);
199 if (annotate)
200 op->setAttr("enzyme.ici", BoolAttr::get(ctx, ici));
201 if (print)
202 llvm::outs() << " op ici=" << ici << "\n";
203
204 for (auto res : op->getResults()) {
205 bool icv = activityAnalyzer.isConstantValue(TR, res);
206 if (annotate)
207 op->setAttr("enzyme.res_icv" +
208 std::to_string(res.getResultNumber()),
209 BoolAttr::get(ctx, icv));
210 if (print)
211 llvm::outs() << " res: " << res << " icv=" << icv << "\n";
212 }
213 });
214 }
215 }
216
217 void runOnOperation() override {
219 config.dataflow = dataflow;
220 config.relative = relative;
221 config.annotate = annotate;
222 config.inferFromAutodiff = inferFromAutodiff;
223 config.verbose = verbose;
224
225 auto moduleOp = cast<ModuleOp>(getOperation());
226
227 if (inferFromAutodiff) {
228 // Infer the activity attributes from the __enzyme_autodiff call
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;
235 break;
236 }
237 }
238 }
239 }
240 if (!autodiff_decl) {
241 moduleOp.emitError("Failed to find __enzyme_autodiff symbol");
242 return signalPassFailure();
243 }
244 auto uses = SymbolTable::getSymbolUses(autodiff_decl, moduleOp);
245 assert(uses && "failed to find symbol uses of autodiff decl");
246
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();
253 auto callee =
254 cast<FunctionOpInterface>(moduleOp.lookupSymbol(calleeAttr));
255
256 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
257 resultActivities{callee.getNumResults()};
258 // Populate the argument activities based on either the type or the
259 // supplied annotation. First argument is the callee
260 inferArgActivitiesFromEnzymeAutodiff(callee, autodiff_call,
261 argActivities, resultActivities);
262 runActivityAnalysis(config, callee, argActivities, resultActivities);
263 }
264 return;
265 }
266
267 if (funcsToAnalyze.empty()) {
268 moduleOp.walk([this, config](FunctionOpInterface callee) {
269 if (callee.isExternal() || callee.isPrivate())
270 return;
271
272 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
273 resultActivities{callee.getNumResults()};
274 initializeArgAndResActivities(callee, argActivities, resultActivities);
275
276 runActivityAnalysis(config, callee, argActivities, resultActivities);
277 });
278 return;
279 }
280
281 for (std::string funcName : funcsToAnalyze) {
282 Operation *op = moduleOp.lookupSymbol(funcName);
283 if (!op) {
284 continue;
285 }
286
287 if (!isa<FunctionOpInterface>(op)) {
288 moduleOp.emitError()
289 << "Operation " << funcName << " was not a FunctionOpInterface";
290 return signalPassFailure();
291 }
292
293 auto callee = cast<FunctionOpInterface>(op);
294 SmallVector<enzyme::Activity> argActivities{callee.getNumArguments()},
295 resultActivities{callee.getNumResults()};
296 initializeArgAndResActivities(callee, argActivities, resultActivities);
297
298 runActivityAnalysis(config, callee, argActivities, resultActivities);
299 }
300 }
301};
302} // namespace
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())