Enzyme main
Loading...
Searching...
No Matches
EnzymeLogicReverse.cpp
Go to the documentation of this file.
2#include "Dialect/Ops.h"
5#include "mlir/IR/Matchers.h"
6#include "mlir/IR/SymbolTable.h"
7#include "mlir/Interfaces/FunctionInterfaces.h"
8
9// TODO: this shouldn't depend on specific dialects except Enzyme.
10#include "mlir/Dialect/Arith/IR/Arith.h"
11#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
12
13#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
14#include "mlir/Dialect/Func/IR/FuncOps.h"
15#include "mlir/Pass/PassManager.h"
16#include "mlir/Pass/PassRegistry.h"
17
18#include "EnzymeLogic.h"
21#include "llvm/ADT/ScopeExit.h"
22
23using namespace mlir;
24using namespace mlir::enzyme;
25
26void handleReturns(Block *oBB, Block *newBB, Block *reverseBB,
27 MGradientUtilsReverse *gutils) {
28 if (oBB->getNumSuccessors() == 0) {
29 Operation *returnStatement = newBB->getTerminator();
30 gutils->erase(returnStatement);
31
32 OpBuilder forwardToBackwardBuilder(newBB, newBB->end());
33
34 Operation *newBranchOp = cf::BranchOp::create(
35 forwardToBackwardBuilder, oBB->getTerminator()->getLoc(), reverseBB);
36
37 gutils->originalToNewFnOps[oBB->getTerminator()] = newBranchOp;
38 }
39}
40
41// Returns true iff the operation:
42// 1. Produces no active data nor active pointers
43// 2. Does not propagate active data nor pointers (via side effects)
44static bool isFullyInactive(Operation *op, MGradientUtils *gutils) {
45 return llvm::all_of(
46 op->getResults(),
47 [gutils](Value v) { return gutils->isConstantValue(v); }) &&
48 gutils->isConstantInstruction(op);
49}
50
51/*
52Create reverse mode adjoint for an operation.
53*/
54LogicalResult MEnzymeLogic::visitChild(Operation *op, OpBuilder &builder,
55 MGradientUtilsReverse *gutils) {
56 if ((op->getBlock()->getTerminator() != op) && isFullyInactive(op, gutils)) {
57 return success();
58 }
59 if (auto ifaceOp = dyn_cast<ReverseAutoDiffOpInterface>(op)) {
60 SmallVector<Value> caches = ifaceOp.cacheValues(gutils);
61 OpBuilder augmentBuilder(gutils->getNewFromOriginal(op));
62 if (failed(ifaceOp.createShadowValues(augmentBuilder, gutils)))
63 return failure();
64 return ifaceOp.createReverseModeAdjoint(builder, gutils, caches);
65 }
66 op->emitError() << "could not compute the adjoint for this operation " << *op;
67 return failure();
68}
69
70LogicalResult MEnzymeLogic::visitChildren(Block *oBB, Block *reverseBB,
71 MGradientUtilsReverse *gutils) {
72 OpBuilder revBuilder(reverseBB, reverseBB->end());
73 bool valid = true;
74 if (!oBB->empty()) {
75 auto first = oBB->rbegin();
76 auto last = oBB->rend();
77 for (auto it = first; it != last; ++it) {
78 Operation *op = &*it;
79 valid &= visitChild(op, revBuilder, gutils).succeeded();
80 }
81 }
82 return success(valid);
83}
84
86 Block *oBB, Block *newBB, Block *reverseBB, MGradientUtilsReverse *gutils,
87 llvm::function_ref<buildReturnFunction> buildReturnOp) {
88 OpBuilder revBuilder(reverseBB, reverseBB->end());
89 if (oBB->hasNoPredecessors()) {
90 buildReturnOp(revBuilder, oBB);
91 } else {
92 Location loc = oBB->rbegin()->getLoc();
93 // TODO remove dependency on CF dialect
94
95 Value cache = gutils->insertInit(gutils->getIndexCacheType());
96
97 Value flag =
98 enzyme::PopOp::create(revBuilder, loc, gutils->getIndexType(), cache);
99
100 Block *defaultBlock = nullptr;
101
102 SmallVector<Block *> blocks;
103 SmallVector<APInt> indices;
104
105 OpBuilder newBuilder(newBB, newBB->begin());
106
107 SmallVector<Value, 1> diffes;
108 for (auto arg : oBB->getArguments()) {
109 if (!gutils->isConstantValue(arg) &&
110 !cast<AutoDiffTypeInterface>(arg.getType()).isMutable()) {
111 diffes.push_back(gutils->diffe(arg, revBuilder));
112 gutils->zeroDiffe(arg, revBuilder);
113 continue;
114 }
115 diffes.push_back(nullptr);
116 }
117
118 for (auto [idx, pred] : llvm::enumerate(oBB->getPredecessors())) {
119 auto reversePred = gutils->mapReverseModeBlocks.lookupOrNull(pred);
120
121 Block *newPred = gutils->getNewFromOriginal(pred);
122
123 OpBuilder predecessorBuilder(newPred->getTerminator());
124
125 Value pred_idx_c =
126 arith::ConstantIntOp::create(predecessorBuilder, loc, idx - 1, 32);
127 enzyme::PushOp::create(predecessorBuilder, loc, cache, pred_idx_c);
128
129 if (idx == 0) {
130 defaultBlock = reversePred;
131
132 } else {
133 indices.push_back(APInt(32, idx - 1));
134 blocks.push_back(reversePred);
135 }
136
137 auto term = pred->getTerminator();
138 if (auto iface = dyn_cast<BranchOpInterface>(term)) {
139 for (auto &op : term->getOpOperands())
140 if (auto blk_idx =
141 iface.getSuccessorBlockArgument(op.getOperandNumber()))
142 if (!gutils->isConstantValue(op.get()) &&
143 (*blk_idx).getOwner() == oBB) {
144 auto idx = (*blk_idx).getArgNumber();
145 if (diffes[idx]) {
146
147 Value rev_idx_c =
148 arith::ConstantIntOp::create(revBuilder, loc, idx - 1, 32);
149
150 auto to_prop = arith::SelectOp::create(
151 revBuilder, loc,
152 arith::CmpIOp::create(revBuilder, loc,
153 arith::CmpIPredicate::eq, flag,
154 rev_idx_c),
155 diffes[idx],
156 cast<AutoDiffTypeInterface>(diffes[idx].getType())
157 .createNullValue(revBuilder, loc));
158
159 gutils->addToDiffe(op.get(), to_prop, revBuilder);
160 }
161 }
162 } else {
163 assert(0 && "predecessor did not implement branch op interface");
164 }
165 }
166
167 cf::SwitchOp::create(revBuilder, loc, flag, defaultBlock, ArrayRef<Value>(),
168 ArrayRef<APInt>(indices), ArrayRef<Block *>(blocks),
169 SmallVector<ValueRange>(indices.size(), ValueRange()));
170 }
171}
172
174 MGradientUtilsReverse *gutils, Region &oldRegion, Region &newRegion,
175 llvm::function_ref<buildReturnFunction> buildFuncReturnOp,
176 std::function<std::pair<Value, Value>(Type)> cacheCreator) {
177 gutils->registerCacheCreatorHook(cacheCreator);
178
179 gutils->createReverseModeBlocks(oldRegion, newRegion);
180
181 bool valid = true;
182 for (auto &oBB : oldRegion) {
183 Block *newBB = gutils->getNewFromOriginal(&oBB);
184 Block *reverseBB = gutils->mapReverseModeBlocks.lookupOrNull(&oBB);
185 handleReturns(&oBB, newBB, reverseBB, gutils);
186 valid &= visitChildren(&oBB, reverseBB, gutils).succeeded();
187 handlePredecessors(&oBB, newBB, reverseBB, gutils, buildFuncReturnOp);
188 }
189
190 gutils->deregisterCacheCreatorHook(cacheCreator);
191 return success(valid);
192}
193
195 FunctionOpInterface fn, std::vector<DIFFE_TYPE> retType,
196 std::vector<DIFFE_TYPE> constants, MTypeAnalysis &TA,
197 std::vector<bool> returnPrimals, std::vector<bool> returnShadows,
198 DerivativeMode mode, bool freeMemory, bool atomicAdd, size_t width,
199 mlir::Type addedType, MFnTypeInfo type_args,
200 std::vector<bool> overwritten_args, void *augmented, bool omp,
201 llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero,
202 bool markReadonly) {
203
204 if (fn.getFunctionBody().empty()) {
205 fn.emitError() << "cannot differentiate a function without a body: "
206 << fn.getNameAttr() << "\n";
207 return nullptr;
208 }
209
210 MReverseCacheKey tup = {fn,
211 retType,
212 constants,
213 returnPrimals,
214 returnShadows,
215 mode,
216 freeMemory,
217 atomicAdd,
218 static_cast<unsigned>(width),
219 addedType,
220 type_args,
221 overwritten_args,
222 omp};
223
224 {
225 auto cachedFn = ReverseCachedFunctions.find(tup);
226 if (cachedFn != ReverseCachedFunctions.end())
227 return cachedFn->second;
228 }
229
230 SmallVector<bool> returnPrimalsP(returnPrimals.begin(), returnPrimals.end());
231 SmallVector<bool> returnShadowsP(returnShadows.begin(), returnShadows.end());
232
234 *this, mode, width, fn, TA, type_args, returnPrimalsP, returnShadowsP,
235 retType, constants, addedType, omp, postpasses, verifyPostPasses,
236 strongZero);
237 if (markReadonly) {
238 markReadOnlyLoads(gutils->oldFunc, [&](Operation *origOp) {
239 gutils->getNewFromOriginal(origOp)->setAttr(
240 "enzyme.readonly", UnitAttr::get(origOp->getContext()));
241 });
242 }
243 gutils->AtomicAdd = atomicAdd;
244
245 ReverseCachedFunctions[tup] = gutils->newFunc;
246
247 Region &oldRegion = gutils->oldFunc.getFunctionBody();
248 Region &newRegion = gutils->newFunc.getFunctionBody();
249
250 auto buildFuncReturnOp = [&](OpBuilder &builder, Block *oBB) {
251 SmallVector<mlir::Value> retargs;
252 for (auto [arg, returnPrimal] :
253 llvm::zip(oBB->getTerminator()->getOperands(), returnPrimals)) {
254 if (returnPrimal) {
255 retargs.push_back(gutils->getNewFromOriginal(arg));
256 }
257 }
258 for (auto [arg, cv] : llvm::zip(oBB->getArguments(), constants)) {
259 if (cv == DIFFE_TYPE::OUT_DIFF) {
260 retargs.push_back(gutils->diffe(arg, builder));
261 }
262 }
263
264 Location loc = oBB->rbegin()->getLoc();
265 if (auto iface = dyn_cast<enzyme::AutoDiffFunctionInterface>(*fn))
266 iface.createReturn(builder, loc, retargs);
267 else
268 fn->emitError() << "this function operation does not implement "
269 "AutoDiffFunctionInterface";
270 return;
271 };
272
273 gutils->forceAugmentedReturns();
274
275 auto res =
276 differentiate(gutils, oldRegion, newRegion, buildFuncReturnOp, nullptr);
277
278 auto nf = gutils->newFunc;
279
280 delete gutils;
281
282 if (!res.succeeded())
283 return nullptr;
284
285 if (postpasses != "") {
286 mlir::PassManager pm(nf->getContext());
287 pm.enableVerifier(verifyPostPasses);
288 std::string error_message;
289 // llvm::raw_string_ostream error_stream(error_message);
290 mlir::LogicalResult result = mlir::parsePassPipeline(postpasses, pm);
291 if (mlir::failed(result)) {
292 return nullptr;
293 }
294
295 if (!mlir::succeeded(pm.run(nf))) {
296 return nullptr;
297 }
298 }
299
300 return nf;
301}
static bool isFullyInactive(Operation *op, MGradientUtils *gutils)
void handleReturns(Block *oBB, Block *newBB, Block *reverseBB, MGradientUtilsReverse *gutils)
DerivativeMode
Definition Utils.h:524
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
void zeroDiffe(mlir::Value origv, mlir::OpBuilder &builder)
LogicalResult visitChildren(Block *oBB, Block *reverseBB, MGradientUtilsReverse *gutils)
LogicalResult visitChild(Operation *op, OpBuilder &builder, MGradientUtilsReverse *gutils)
std::map< MReverseCacheKey, FunctionOpInterface > ReverseCachedFunctions
FunctionOpInterface CreateReverseDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, std::vector< bool > returnShadows, DerivativeMode mode, bool freeMemory, bool atomicAdd, size_t width, mlir::Type addedType, MFnTypeInfo type_args, std::vector< bool > overwritten_args, void *augmented, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero, bool markReadonly)
void handlePredecessors(Block *oBB, Block *newBB, Block *reverseBB, MGradientUtilsReverse *gutils, llvm::function_ref< buildReturnFunction > buildReturnOp)
LogicalResult differentiate(MGradientUtilsReverse *gutils, Region &oldRegion, Region &newRegion, llvm::function_ref< buildReturnFunction > buildFuncRetrunOp, std::function< std::pair< Value, Value >(Type)> cacheCreator)
void createReverseModeBlocks(Region &oldFunc, Region &newFunc)
void deregisterCacheCreatorHook(std::function< std::pair< Value, Value >(Type)> hook)
void registerCacheCreatorHook(std::function< std::pair< Value, Value >(Type)> hook)
void addToDiffe(mlir::Value oldGradient, mlir::Value addedGradient, OpBuilder &builder)
static MGradientUtilsReverse * CreateFromClone(MEnzymeLogic &Logic, DerivativeMode mode_, unsigned width, FunctionOpInterface todiff, MTypeAnalysis &TA, MFnTypeInfo &oldTypeInfo, const ArrayRef< bool > returnPrimals, const ArrayRef< bool > returnShadows, llvm::ArrayRef< DIFFE_TYPE > retType, llvm::ArrayRef< DIFFE_TYPE > constant_args, mlir::Type additionalArg, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero)
FunctionOpInterface oldFunc
std::map< Operation *, Operation * > originalToNewFnOps
void erase(Operation *op)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
FunctionOpInterface newFunc
bool isConstantInstruction(mlir::Operation *v) const
bool isConstantValue(mlir::Value v) const
void markReadOnlyLoads(CallableOpInterface funcOp, function_ref< void(Operation *)> annotate)