Enzyme main
Loading...
Searching...
No Matches
CoreDialectsAutoDiffImplementations.cpp
Go to the documentation of this file.
1//===- CoreDialectsAutoDiffImplementations.cpp ----------------------------===//
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 contains common utilities for the external model implementation of
10// the automatic differentiation op interfaces for upstream MLIR dialects.
11//
12//===----------------------------------------------------------------------===//
13
19#include "Interfaces/Utils.h"
20#include "Passes/Utils.h"
21
22#include "mlir/Dialect/Arith/IR/Arith.h"
23#include "mlir/Dialect/Complex/IR/Complex.h"
24#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
25#include "mlir/IR/Matchers.h"
26
27#include "llvm/Support/CommandLine.h"
28
29using namespace mlir;
30using namespace mlir::enzyme;
31
32static llvm::cl::opt<bool> EnzymeMLIRFastMath(
33 "enzyme-mlir-fast-math", llvm::cl::init(true), llvm::cl::Hidden,
34 llvm::cl::desc("Build derivative expressions with fast-math enabled"));
35
37 if (!op || !EnzymeMLIRFastMath)
38 return;
39 if (auto iface = dyn_cast<arith::ArithFastMathInterface>(op))
40 op->setAttr(iface.getFastMathAttrName(),
41 arith::FastMathFlagsAttr::get(op->getContext(),
42 arith::FastMathFlags::fast));
43 else if (auto iface = dyn_cast<LLVM::FastmathFlagsInterface>(op))
44 op->setAttr(iface.getFastmathAttrName(),
45 LLVM::FastmathFlagsAttr::get(op->getContext(),
46 LLVM::FastmathFlags::fast));
47}
48
49mlir::TypedAttr mlir::enzyme::getConstantAttr(mlir::Type type,
50 llvm::StringRef value) {
51 using namespace mlir;
52 if (value == "0") {
53 auto ATI = cast<AutoDiffTypeInterface>(type);
54 return cast<TypedAttr>(ATI.createNullAttr());
55 }
56 if (auto T = dyn_cast<TensorType>(type)) {
57 if (auto ET = dyn_cast<FloatType>(T.getElementType())) {
58 APFloat values[] = {APFloat(ET.getFloatSemantics(), value)};
59 return DenseElementsAttr::get(cast<ShapedType>(type),
60 ArrayRef<APFloat>(values));
61 } else if (auto CET = dyn_cast<ComplexType>(T.getElementType())) {
62 auto ET = cast<FloatType>(CET.getElementType());
63 mlir::Complex<APFloat> values[] = {
64 mlir::Complex<APFloat>(APFloat(ET.getFloatSemantics(), value),
65 APFloat(ET.getFloatSemantics(), "0"))};
66 return DenseElementsAttr::get(cast<ShapedType>(type),
67 ArrayRef<mlir::Complex<APFloat>>(values));
68 } else {
69 llvm::errs() << " unsupported eltype: " << T.getElementType()
70 << " of type " << type << "\n";
71 llvm_unreachable("unsupported eltype");
72 }
73 } else if (auto T = cast<FloatType>(type)) {
74 APFloat apvalue(T.getFloatSemantics(), value);
75 return FloatAttr::get(T, apvalue);
76 // NOTE `complex::ConstantOp` doesn't accept `TypedAttr`, only `ArrayAttr`
77 // } else if (auto T = cast<ComplexType>(type)) {
78 // auto F = cast<FloatType>(T.getElementType());
79 // return mlir::ArrayAttr::get({
80 // FloatAttr::get(F, APFloat(F.getFloatSemantics(), value)),
81 // FloatAttr::get(F, APFloat(F.getFloatSemantics(), "0"));
82 // });
83 } else {
84 llvm::errs() << " unsupported type: " << type << "\n";
85 llvm_unreachable("unsupported eltype");
86 }
87}
88
90 const MGradientUtils *gutils) {
91 for (Value result : op->getResults())
92 if (gutils->isConstantValue(result))
93 return false;
94 return true;
95}
97 OpBuilder &builder,
98 MGradientUtils *gutils) {
99 auto newInst = gutils->getNewFromOriginal(inst);
100
101 auto binst = cast<BranchOpInterface>(inst);
102
103 // TODO generalize to cloneWithNewBlockArgs interface
104 SmallVector<Value> newVals;
105
106 SmallVector<int32_t> segSizes;
107 // Keep non-differentiated, non-forwarded operands. These are the ones ahead
108 // of the first operand any successor forwards -- a condition, a switch value.
109 // When no successor takes an operand there is no such boundary to find, and
110 // every operand the op has is one of these.
111 size_t non_forwarded = binst->getNumOperands();
112 for (size_t i = 0; i < newInst->getNumSuccessors(); i++) {
113 auto ops = binst.getSuccessorOperands(i).getForwardedOperands();
114 if (ops.empty())
115 continue;
116 non_forwarded = ops.getBeginOperandIndex();
117 break;
118 }
119
120 for (size_t i = 0; i < non_forwarded; i++)
121 newVals.push_back(gutils->getNewFromOriginal(binst->getOperand(i)));
122
123 segSizes.push_back(newVals.size());
124 for (size_t i = 0; i < newInst->getNumSuccessors(); i++) {
125 size_t cur = newVals.size();
126 auto ops = binst.getSuccessorOperands(i).getForwardedOperands();
127 for (auto &&[idx, op] : llvm::enumerate(ops)) {
128 auto arg =
129 *binst.getSuccessorBlockArgument(ops.getBeginOperandIndex() + idx);
130 newVals.push_back(gutils->getNewFromOriginal(op));
131 if (!gutils->isConstantValue(arg)) {
132 if (!gutils->isConstantValue(op)) {
133 newVals.push_back(gutils->invertPointerM(op, builder));
134 } else {
135 Type retTy = cast<AutoDiffTypeInterface>(arg.getType())
136 .getShadowType(gutils->width);
137 auto toret = cast<AutoDiffTypeInterface>(retTy).createNullValue(
138 builder, op.getLoc());
139 newVals.push_back(toret);
140 }
141 }
142 }
143 cur = newVals.size() - cur;
144 segSizes.push_back(cur);
145 }
146
147 SmallVector<NamedAttribute> attrs(newInst->getAttrs());
148 bool has_cases = false;
149 for (auto &attr : attrs) {
150 if (attr.getName() == "case_operand_segments") {
151 has_cases = true;
152 }
153 }
154 for (auto &attr : attrs) {
155 if (attr.getName() == "operandSegmentSizes") {
156 if (!has_cases) {
157 attr.setValue(builder.getDenseI32ArrayAttr(segSizes));
158 } else {
159 SmallVector<int32_t> segSlices2(segSizes.begin(), segSizes.begin() + 2);
160 segSlices2.push_back(0);
161 for (size_t i = 2; i < segSizes.size(); i++)
162 segSlices2[2] += segSizes[i];
163 attr.setValue(builder.getDenseI32ArrayAttr(segSlices2));
164 }
165 }
166 if (attr.getName() == "case_operand_segments") {
167 SmallVector<int32_t> segSlices2(segSizes.begin() + 2, segSizes.end());
168 attr.setValue(builder.getDenseI32ArrayAttr(segSlices2));
169 }
170 }
171
172 gutils->getNewFromOriginal(inst->getBlock())
173 ->push_back(newInst->create(newInst->getLoc(), newInst->getName(),
174 TypeRange(), newVals, attrs,
175 mlir::PropertyRef(), newInst->getSuccessors(),
176 newInst->getNumRegions()));
177 gutils->erase(newInst);
178 return;
179}
180
181static bool contains(ArrayRef<int> ar, int v) {
182 for (auto a : ar) {
183 if (a == v) {
184 return true;
185 }
186 }
187 return false;
188}
189
191 Operation *orig, OpBuilder &builder, MGradientUtils *gutils,
192 ArrayRef<int> storedVals) {
193 auto iface = cast<ActivityOpInterface>(orig);
194
195 SmallVector<Value> newOperands;
196 newOperands.reserve(orig->getNumOperands());
197 SmallVector<bool> inverted(orig->getNumOperands(), false);
198 for (OpOperand &operand : orig->getOpOperands()) {
199 if (iface.isArgInactive(operand.getOperandNumber())) {
200 newOperands.push_back(gutils->getNewFromOriginal(operand.get()));
201 } else {
202 if (gutils->isConstantValue(operand.get())) {
203
204 if (contains(storedVals, operand.getOperandNumber()) ||
205 contains(storedVals, -1)) {
206 if (isa<AutoDiffTypeInterface>(operand.get().getType())) {
207 // Zero for an immutable value; for a mutable value -- an
208 // inactive pointer -- the primal is its own shadow.
209 newOperands.push_back(oputils::inactiveStoredValueShadow(
210 orig, *gutils, operand.get(), builder));
211 continue;
212 }
213 }
214 orig->emitError()
215 << "Unsupported constant arg to memory identity forward "
216 "handler(opidx="
217 << operand.getOperandNumber() << ", op=" << operand.get() << ")\n";
218 return failure();
219 }
220 inverted[newOperands.size()] = true;
221 newOperands.push_back(gutils->invertPointerM(operand.get(), builder));
222 }
223 }
224
225 // Assuming shadows following the originals are fine.
226 // TODO: consider extending to have a ShadowableTerminatorOpInterface
227 Operation *primal = gutils->getNewFromOriginal(orig);
228 SmallVector<Operation *, 1> shadows;
229 if (gutils->width == 1) {
230 Operation *shadow = builder.clone(*primal);
231 shadow->setOperands(newOperands);
232 shadows.push_back(shadow);
233 } else {
234 for (size_t w = 0; w < gutils->width; w++) {
235 SmallVector<Value> newOperands2(newOperands);
236 for (size_t i = 0; i < newOperands.size(); i++) {
237 if (!inverted[i])
238 continue;
239 newOperands2[i] = enzyme::getExtractValue(
240 builder, orig->getLoc(), orig->getOperands()[i].getType(),
241 newOperands2[i], w);
242 }
243 Operation *shadow = builder.clone(*primal);
244 shadow->setOperands(newOperands2);
245 shadows.push_back(shadow);
246 }
247 }
248 for (auto &&[i, oval] : llvm::enumerate(orig->getResults())) {
249 if (gutils->isConstantValue(oval))
250 continue;
251 Value sval;
252 if (gutils->width == 1) {
253 sval = shadows[0]->getResult(i);
254 } else {
255 SmallVector<Value> shadowRes;
256 for (auto s : shadows) {
257 shadowRes.push_back(s->getResult(i));
258 }
259 sval = enzyme::getConcatValue(builder, orig->getLoc(), shadowRes);
260 }
261 gutils->setDiffe(oval, sval, builder);
262 }
263
264 // A store into memory whose primal contents the caller declared unneeded
265 // (enzyme_dupnoneed) need not happen: the shadow store above is the whole
266 // of the derivative.
267 if (auto store = dyn_cast<enzyme::StoreLikeInterface>(orig))
268 if (gutils->primalStoreElidable(store.getStoredPointer()))
269 gutils->erase(primal);
270
271 return success();
272}
273
275 Operation *orig, OpBuilder &builder, MGradientUtils *gutils, bool zero) {
276
277 Operation *primal = gutils->getNewFromOriginal(orig);
278 Operation *shadow = builder.clone(*primal);
279
280 Value shadowRes = shadow->getResult(0);
281
282 gutils->setInvertedPointer(orig->getResult(0), shadowRes);
283 gutils->eraseIfUnused(orig);
284
285 if (zero) {
286 // Fill with zeros
287 if (auto iface = dyn_cast<AutoDiffTypeInterface>(shadowRes.getType())) {
288 return iface.zeroInPlace(builder, orig->getLoc(), shadowRes);
289 } else {
290 orig->emitError() << "Type " << shadowRes.getType()
291 << " does not implement "
292 "AutoDiffTypeInterface";
293 return failure();
294 }
295 }
296 return success();
297}
298
300 OpBuilder &builder,
301 MGradientUtilsReverse *gutils) {
302 size_t num_out = 0;
303 for (auto act : gutils->RetDiffeTypes) {
304 if (act == DIFFE_TYPE::OUT_DIFF)
305 num_out++;
306 }
307
308 size_t idx = 0;
309 auto args = gutils->newFunc->getRegions().begin()->begin()->getArguments();
310
311 for (auto &&[op, act] : llvm::zip(op->getOperands(), gutils->RetDiffeTypes)) {
312 if (act == DIFFE_TYPE::OUT_DIFF) {
313 if (!gutils->isConstantValue(op)) {
314 auto d_out = args[args.size() - num_out + idx];
315 gutils->addToDiffe(op, d_out, builder);
316 }
317 idx++;
318 }
319 }
320}
321
323 Operation *origTerminator, OpBuilder &builder, MGradientUtils *gutils) {
324 auto parentOp = origTerminator->getParentOp();
325
326 llvm::SmallDenseSet<unsigned> operandsToShadow;
327 auto termIface = dyn_cast<RegionBranchTerminatorOpInterface>(origTerminator);
328 auto regionBranchOp =
329 dyn_cast<RegionBranchOpInterface>(origTerminator->getParentOp());
330 if (termIface && regionBranchOp) {
331
332 SmallVector<RegionSuccessor> successors;
333 termIface.getSuccessorRegions(
334 SmallVector<Attribute>(origTerminator->getNumOperands(), Attribute()),
335 successors);
336
337 for (auto &successor : successors) {
338 OperandRange operandRange = termIface.getSuccessorOperands(successor);
339 ValueRange targetValues =
340 successor.isOperation()
341 ? parentOp->getResults()
342 : regionBranchOp.getSuccessorInputs(successor);
343 // The parent may carry more results than the terminator forwards to it
344 // (a gpu wrapper's token-like result has no yield operand); only the
345 // paired prefix can be shadowed.
346 size_t numPaired = std::min(operandRange.size(), targetValues.size());
347 for (auto &&[i, target] :
348 llvm::enumerate(targetValues.take_front(numPaired))) {
349 if (!gutils->isConstantValue(target))
350 operandsToShadow.insert(operandRange.getBeginOperandIndex() + i);
351 }
352 }
353 } else {
354 // The parent may carry more results than its terminator forwards (a gpu
355 // wrapper's token-like result has no yield operand); only the paired
356 // results can be shadowed.
357 size_t numPaired = std::min<size_t>(parentOp->getNumResults(),
358 origTerminator->getNumOperands());
359 for (auto res : parentOp->getResults().take_front(numPaired)) {
360 if (!gutils->isConstantValue(res))
361 operandsToShadow.insert(res.getResultNumber());
362 }
363 }
364
365 SmallVector<Value> newOperands;
366 newOperands.reserve(origTerminator->getNumOperands() +
367 operandsToShadow.size());
368 for (OpOperand &operand : origTerminator->getOpOperands()) {
369 newOperands.push_back(gutils->getNewFromOriginal(operand.get()));
370 if (operandsToShadow.contains(operand.getOperandNumber()))
371 newOperands.push_back(gutils->invertPointerM(operand.get(), builder));
372 }
373
374 // Assuming shadows following the originals are fine.
375 // TODO: consider extending to have a ShadowableTerminatorOpInterface
376 Operation *replTerminator = gutils->getNewFromOriginal(origTerminator);
377 replTerminator->setOperands(newOperands);
378}
379
381 Operation *op, OpBuilder &builder, MGradientUtils *gutils) {
382
383 // For all operands that are forwarded to the body, if they are active, also
384 // add the shadow as operand.
385 auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op);
386 if (!regionBranchOp) {
387 op->emitError() << " RegionBranchOpInterface not implemented for " << *op
388 << "\n";
389 return failure();
390 }
391 auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
392 if (!iface) {
393 op->emitError() << " ControlFlowAutoDiffOpInterface not implemented for "
394 << *op << "\n";
395 return failure();
396 }
397
398 // TODO: we may need to record, for every successor, which of its inputs
399 // need a shadow to recreate the body correctly.
400 llvm::SmallDenseSet<unsigned> operandPositionsToShadow;
401 llvm::SmallDenseSet<unsigned> resultPositionsToShadow;
402
403 SmallVector<RegionSuccessor> entrySuccessors;
404 regionBranchOp.getEntrySuccessorRegions(
405 SmallVector<Attribute>(op->getNumOperands(), Attribute()),
406 entrySuccessors);
407
408 for (const RegionSuccessor &successor : entrySuccessors) {
409
410 OperandRange operandRange =
411 iface.getSuccessorOperands(regionBranchOp, successor);
412
413 ValueRange targetValues =
414 successor.isOperation() ? op->getResults()
415 : regionBranchOp.getSuccessorInputs(successor);
416
417 // Need to know which of the arguments are being forwarded to from
418 // operands.
419 for (auto &&[i, regionValue, operand] :
420 llvm::enumerate(targetValues, operandRange)) {
421 if (gutils->isConstantValue(regionValue))
422 continue;
423 operandPositionsToShadow.insert(operandRange.getBeginOperandIndex() + i);
424 if (successor.isOperation())
425 resultPositionsToShadow.insert(i);
426 }
427 }
428
429 for (auto res : op->getResults())
430 if (!gutils->isConstantValue(res))
431 resultPositionsToShadow.insert(res.getResultNumber());
432
434 op, builder, gutils, operandPositionsToShadow, resultPositionsToShadow);
435}
436
438 Operation *op, OpBuilder &builder, MGradientUtils *gutils,
439 const llvm::SmallDenseSet<unsigned> &operandPositionsToShadow,
440 const llvm::SmallDenseSet<unsigned> &resultPositionsToShadow) {
441 // For all active results, add shadow types.
442 // For now, assuming all results are relevant.
443 Operation *newOp = gutils->getNewFromOriginal(op);
444 SmallVector<Type> newOpResultTypes;
445 newOpResultTypes.reserve(op->getNumResults() * 2);
446 for (auto result : op->getResults()) {
447 // TODO only if used (can we DCE the primal after having done the
448 // derivative).
449 newOpResultTypes.push_back(result.getType());
450 if (!gutils->isConstantValue(result)) {
451 assert(resultPositionsToShadow.count(result.getResultNumber()));
452 }
453 if (!resultPositionsToShadow.count(result.getResultNumber()))
454 continue;
455 auto typeIface = dyn_cast<AutoDiffTypeInterface>(result.getType());
456 if (!typeIface) {
457 op->emitError() << " AutoDiffTypeInterface not implemented for "
458 << result.getType() << "\n";
459 return failure();
460 }
461 newOpResultTypes.push_back(typeIface.getShadowType(gutils->width));
462 }
463
464 SmallVector<Value> newOperands;
465 newOperands.reserve(op->getNumOperands() + operandPositionsToShadow.size());
466 for (OpOperand &operand : op->getOpOperands()) {
467 newOperands.push_back(gutils->getNewFromOriginal(operand.get()));
468 if (operandPositionsToShadow.contains(operand.getOperandNumber()))
469 newOperands.push_back(gutils->invertPointerM(operand.get(), builder));
470 }
471 // We are assuming the op can forward additional operands, listed
472 // immediately after the original operands, to the same regions.
473 // ^^
474 // Our interface guarantees this.
475 // We also assume that the region-holding op returns all of the values
476 // yielded by terminators, and only those values.
477
478 auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
479 if (!iface) {
480 op->emitError() << " ControlFlowAutoDiffOpInterface not implemented for "
481 << *op << "\n";
482 return failure();
483 }
484 Operation *replacement = iface.createWithShadows(
485 builder, gutils, op, newOperands, newOpResultTypes);
486 assert(replacement->getNumResults() == newOpResultTypes.size());
487 for (auto &&[region, replacementRegion] :
488 llvm::zip(newOp->getRegions(), replacement->getRegions())) {
489 replacementRegion.takeBody(region);
490 }
491
492 // Inject the mapping for the new results into GradientUtil's shadow
493 // table.
494 SmallVector<Value> reps;
495 size_t idx = 0;
496 for (OpResult r : op->getResults()) {
497 // TODO only if used
498 reps.push_back(replacement->getResult(idx));
499 idx++;
500 if (!gutils->isConstantValue(r)) {
501 assert(resultPositionsToShadow.count(r.getResultNumber()));
502 auto inverted = gutils->invertedPointers.lookupOrNull(r);
503 assert(inverted);
504 gutils->invertedPointers.map(r, replacement->getResult(idx));
505 inverted.replaceAllUsesWith(replacement->getResult(idx));
506 gutils->erase(inverted.getDefiningOp());
507 idx++;
508 } else if (resultPositionsToShadow.count(r.getResultNumber())) {
509 idx++;
510 }
511 }
512
513 // Differentiate body.
514 for (auto &origRegion : op->getRegions()) {
515 for (auto &origBlock : origRegion) {
516 for (Operation &o : origBlock) {
517 if (failed(gutils->visitChild(&o))) {
518 return failure();
519 }
520 }
521 }
522 }
523
524 // Replace all uses of original results
525 gutils->replaceOrigOpWith(op, reps);
526 gutils->erase(newOp);
527 gutils->originalToNewFnOps[op] = replacement;
528
529 return success();
530}
531
532namespace edetail = mlir::enzyme::detail;
533
534// The callee of `op`, or null where it is not a direct call to something this
535// can see the body of.
536static FunctionOpInterface getDirectCallee(Operation *op) {
537 auto callOp = dyn_cast<CallOpInterface>(op);
538 if (!callOp)
539 return nullptr;
540 auto sym = dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
541 if (!sym)
542 return nullptr;
543 return dyn_cast_or_null<FunctionOpInterface>(
544 SymbolTable::lookupNearestSymbolFrom(op, sym));
545}
546
547LogicalResult edetail::callForwardHandler(Operation *orig, OpBuilder &builder,
548 MGradientUtils *gutils) {
550
551 auto fn = getDirectCallee(orig);
552 if (!fn) {
553 return orig->emitError()
554 << "could not find the callee of: " << *orig << "\n";
555 }
556 if (fn.getFunctionBody().empty()) {
557 return orig->emitError()
558 << "cannot differentiate a call to a function without a body and "
559 "without a registered derivative: "
560 << fn.getNameAttr() << "\n";
561 }
562
563 auto narg = orig->getNumOperands();
564 auto nret = orig->getNumResults();
565
566 std::vector<DIFFE_TYPE> RetActivity;
567 RetActivity.reserve(nret);
568 for (auto res : orig->getResults()) {
569 RetActivity.push_back(gutils->isConstantValue(res) ? DIFFE_TYPE::CONSTANT
571 }
572
573 std::vector<DIFFE_TYPE> ArgActivity;
574 ArgActivity.reserve(narg);
575 for (auto arg : orig->getOperands()) {
576 if (gutils->isConstantValue(arg)) {
577 ArgActivity.push_back(DIFFE_TYPE::CONSTANT);
578 continue;
579 }
580 // A pointer whose base the caller declared enzyme_dupnoneed keeps that
581 // declaration through the call: the callee is where the stores live,
582 // and it can only skip their primal halves if it is told.
583 ArgActivity.push_back(gutils->getDiffeTypeOfBase(arg) ==
587 }
588
589 std::vector<bool> returnPrimal(nret, true);
590 std::vector<bool> returnShadow(nret, false);
591
592 auto type_args = gutils->TA.getAnalyzedTypeInfo(fn);
593
594 bool freeMemory = true;
595 size_t width = gutils->width;
596
597 std::vector<bool> overwritten_args(narg, false);
598
599 auto forwardFn = gutils->Logic.CreateForwardDiff(
600 fn, RetActivity, ArgActivity, gutils->TA, returnPrimal, mode, freeMemory,
601 width,
602 /* addedType */ nullptr, type_args, overwritten_args,
603 /* augmented */ nullptr, gutils->omp, gutils->postpasses,
604 gutils->verifyPostPasses, gutils->strongZero);
605
606 if (!forwardFn)
607 return orig->emitError()
608 << "failed to create forward-mode derivative for callee "
609 << fn.getNameAttr() << "\n";
610
611 SmallVector<Value> fwdArguments;
612
613 for (auto &&[arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
614 fwdArguments.push_back(gutils->getNewFromOriginal(arg));
615 if (act == DIFFE_TYPE::DUP_ARG || act == DIFFE_TYPE::DUP_NONEED)
616 fwdArguments.push_back(gutils->invertPointerM(arg, builder));
617 }
618
619 auto *fwdCallOp = cast<AutoDiffFunctionInterface>(forwardFn.getOperation())
620 .createCall(builder, orig->getLoc(), fwdArguments);
621
622 SmallVector<Value> primals;
623 primals.reserve(nret);
624
625 int fwdIndex = 0;
626 for (auto &&[ret, act] : llvm::zip_equal(orig->getResults(), RetActivity)) {
627 auto fwdRet = fwdCallOp->getResult(fwdIndex);
628 primals.push_back(fwdRet);
629
630 fwdIndex++;
631
632 if (act == DIFFE_TYPE::DUP_ARG) {
633 gutils->setDiffe(ret, fwdCallOp->getResult(fwdIndex), builder);
634 fwdIndex++;
635 }
636 }
637
638 auto newOp = gutils->getNewFromOriginal(orig);
639 gutils->replaceOrigOpWith(orig, primals);
640 gutils->erase(newOp);
641
642 return success();
643}
644
645// The reverse call runs long after the forward one, against whatever memory
646// looks like by then -- even argument memory may have been overwritten in
647// between, and deciding which of it was is the overwritten-args analysis
648// Enzyme's LLVM side has and this side does not yet. Until it does, only a
649// callee that touches no memory at all is differentiable here: readnone, or
650// every op in its body free of memory effects.
651// Whether a memory-effects attribute -- later LLVM's spelling of
652// readnone -- rules out every kind of access.
653static bool memoryEffectsNone(LLVM::MemoryEffectsAttr me) {
654 return me && me.getArgMem() == LLVM::ModRefInfo::NoModRef &&
655 me.getInaccessibleMem() == LLVM::ModRefInfo::NoModRef &&
656 me.getOther() == LLVM::ModRefInfo::NoModRef &&
657 me.getErrnoMem() == LLVM::ModRefInfo::NoModRef &&
658 me.getTargetMem0() == LLVM::ModRefInfo::NoModRef &&
659 me.getTargetMem1() == LLVM::ModRefInfo::NoModRef;
660}
661
662static bool splitReverseMemoryOkImpl(Operation *orig, FunctionOpInterface fn,
663 SmallPtrSetImpl<Operation *> &visited) {
664 if (auto call = dyn_cast<LLVM::CallOp>(orig))
665 if (memoryEffectsNone(call.getMemoryEffectsAttr()))
666 return true;
667 if (auto llvmFn = dyn_cast<LLVM::LLVMFuncOp>(fn.getOperation())) {
668 if (memoryEffectsNone(llvmFn.getMemoryEffectsAttr()))
669 return true;
670 if (auto pass = llvmFn->getAttrOfType<ArrayAttr>("passthrough"))
671 for (Attribute a : pass)
672 if (auto s = dyn_cast<StringAttr>(a))
673 if (s.getValue() == "readnone")
674 return true;
675 }
676 if (fn.getFunctionBody().empty())
677 return false;
678 // A cycle contributes no effects beyond those already under check.
679 if (!visited.insert(fn.getOperation()).second)
680 return true;
681 WalkResult res = fn.getFunctionBody().walk([&](Operation *op) {
682 if (isMemoryEffectFree(op))
683 return WalkResult::advance();
684 // A call op answers conservatively for itself -- its effects are its
685 // callee's, so ask the callee.
686 if (auto callee = getDirectCallee(op))
687 if (splitReverseMemoryOkImpl(op, callee, visited))
688 return WalkResult::advance();
689 return WalkResult::interrupt();
690 });
691 return !res.wasInterrupted();
692}
693
694static bool splitReverseMemoryOk(Operation *orig, FunctionOpInterface fn) {
695 SmallPtrSet<Operation *, 8> visited;
696 return splitReverseMemoryOkImpl(orig, fn, visited);
697}
698
699static LogicalResult checkSplitReverseMemory(Operation *orig,
700 FunctionOpInterface fn) {
701 if (splitReverseMemoryOk(orig, fn))
702 return success();
703 return orig->emitError()
704 << "cannot differentiate a call in reverse mode whose callee "
705 "touches memory; caching of overwritten arguments is not yet "
706 "implemented here: "
707 << fn.getNameAttr() << "\n";
708}
709
710LogicalResult edetail::callReverseHandler(Operation *orig, OpBuilder &builder,
711 MGradientUtilsReverse *gutils,
712 SmallVector<Value> caches) {
714
715 auto fn = getDirectCallee(orig);
716 if (!fn) {
717 return orig->emitError()
718 << "could not find the callee of: " << *orig << "\n";
719 }
720 if (fn.getFunctionBody().empty()) {
721 return orig->emitError()
722 << "cannot differentiate a call to a function without a body and "
723 "without a registered derivative: "
724 << fn.getNameAttr() << "\n";
725 }
726 if (failed(checkSplitReverseMemory(orig, fn)))
727 return failure();
728
729 auto narg = orig->getNumOperands();
730 auto nret = orig->getNumResults();
731
732 std::vector<DIFFE_TYPE> RetActivity;
733 for (auto res : orig->getResults()) {
734 RetActivity.push_back(
736 : cast<AutoDiffTypeInterface>(res.getType()).isMutable()
739 }
740
741 std::vector<DIFFE_TYPE> ArgActivity;
742 for (auto arg : orig->getOperands()) {
743 if (gutils->isConstantValue(arg)) {
744 ArgActivity.push_back(DIFFE_TYPE::CONSTANT);
745 continue;
746 }
747 if (cast<AutoDiffTypeInterface>(arg.getType()).isMutable()) {
748 // A pointer whose base the caller declared enzyme_dupnoneed keeps
749 // that declaration through the call: the callee is where the stores
750 // live, and it can only skip their primal halves if it is told.
751 ArgActivity.push_back(gutils->getDiffeTypeOfBase(arg) ==
755 continue;
756 }
757 ArgActivity.push_back(DIFFE_TYPE::OUT_DIFF);
758 }
759
760 if (llvm::any_of(RetActivity,
761 [&](auto act) { return act == DIFFE_TYPE::DUP_ARG; })) {
762 return orig->emitError()
763 << "could not emit adjoint with mutable return types in: " << *orig
764 << "\n";
765 }
766
767 std::vector<bool> overwritten_args(narg, true);
768 std::vector<bool> returnShadow(nret, false);
769 std::vector<bool> returnPrimal(nret, false);
770
771 auto type_args = gutils->TA.getAnalyzedTypeInfo(fn);
772
773 bool freeMemory = true;
774 size_t width = gutils->width;
775
776 auto revFn = gutils->Logic.CreateReverseDiff(
777 fn, RetActivity, ArgActivity, gutils->TA, returnPrimal, returnShadow,
778 mode, freeMemory, gutils->AtomicAdd, width, /*addedType*/ nullptr,
779 type_args, overwritten_args, /*augmented*/ nullptr, gutils->omp,
780 gutils->postpasses, gutils->verifyPostPasses, gutils->strongZero,
781 /*markReadonly=*/false);
782
783 if (!revFn)
784 return orig->emitError()
785 << "failed to create reverse-mode adjoint for callee "
786 << fn.getNameAttr() << "\n";
787
788 SmallVector<Value> revArguments;
789
790 size_t cacheIdx = 0;
791 for (auto [arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
792 revArguments.push_back(gutils->popCache(caches[cacheIdx++], builder));
793 if (act == DIFFE_TYPE::DUP_ARG || act == DIFFE_TYPE::DUP_NONEED)
794 revArguments.push_back(gutils->popCache(caches[cacheIdx++], builder));
795 }
796 assert(cacheIdx == caches.size());
797
798 for (auto result : orig->getResults()) {
799 if (gutils->isConstantValue(result))
800 continue;
801 revArguments.push_back(gutils->diffe(result, builder));
802 }
803
804 auto *revCallOp = cast<AutoDiffFunctionInterface>(revFn.getOperation())
805 .createCall(builder, orig->getLoc(), revArguments);
806
807 int revIndex = 0, fwdIndex = 0;
808 for (auto [arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
809 fwdIndex++;
810
811 if (gutils->isConstantValue(arg))
812 continue;
813
814 if (act == DIFFE_TYPE::DUP_ARG || act == DIFFE_TYPE::DUP_NONEED) {
815 cast<ClonableTypeInterface>(arg.getType())
816 .freeClonedValue(builder, revArguments[fwdIndex - 1]);
817 fwdIndex++;
818 } else {
819 auto diffe = revCallOp->getResult(revIndex);
820 gutils->addToDiffe(arg, diffe, builder);
821 revIndex++;
822 }
823 }
824
825 return success();
826}
827
828SmallVector<Value> edetail::callCacheValues(Operation *orig,
829 MGradientUtilsReverse *gutils) {
830 SmallVector<Value> cachedArguments;
831
832 // A callee the adjoint will refuse gets nothing cached: the caching of
833 // pointer arguments would already need the sizes and copies that the
834 // refusal is about.
835 auto fn = getDirectCallee(orig);
836 if (!fn || fn.getFunctionBody().empty() || !splitReverseMemoryOk(orig, fn))
837 return cachedArguments;
838
839 Operation *newOp = gutils->getNewFromOriginal(orig);
840 OpBuilder cacheBuilder(newOp);
841
842 for (auto arg : orig->getOperands()) {
843 Value toCache = gutils->getNewFromOriginal(arg);
844 if (auto iface = dyn_cast<ClonableTypeInterface>(arg.getType())) {
845 toCache = iface.cloneValue(cacheBuilder, toCache);
846 }
847 Value cache = gutils->initAndPushCache(toCache, cacheBuilder);
848 cachedArguments.push_back(cache);
849 // A mutable shadow is a value of the forward pass -- a shadow of a
850 // pointer derived inside a loop body, say -- and the reverse pass
851 // cannot always rebuild it. Cache it beside its primal; the shadow
852 // buffer itself is shared, so it is the pointer that is put by, not a
853 // copy. (Groundwork: a memory-touching callee is refused until
854 // overwritten-args support lands, so this does not fire yet.)
855 if (!gutils->isConstantValue(arg) &&
856 cast<AutoDiffTypeInterface>(arg.getType()).isMutable()) {
857 Value shadow = gutils->invertPointerM(arg, cacheBuilder);
858 cachedArguments.push_back(gutils->initAndPushCache(shadow, cacheBuilder));
859 }
860 }
861
862 return cachedArguments;
863}
static bool splitReverseMemoryOk(Operation *orig, FunctionOpInterface fn)
static LogicalResult checkSplitReverseMemory(Operation *orig, FunctionOpInterface fn)
static bool contains(ArrayRef< int > ar, int v)
static FunctionOpInterface getDirectCallee(Operation *op)
static llvm::cl::opt< bool > EnzymeMLIRFastMath("enzyme-mlir-fast-math", llvm::cl::init(true), llvm::cl::Hidden, llvm::cl::desc("Build derivative expressions with fast-math enabled"))
static bool splitReverseMemoryOkImpl(Operation *orig, FunctionOpInterface fn, SmallPtrSetImpl< Operation * > &visited)
static bool memoryEffectsNone(LLVM::MemoryEffectsAttr me)
DerivativeMode
Definition Utils.h:524
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
FunctionOpInterface CreateForwardDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, DerivativeMode mode, bool freeMemory, 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)
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)
Value popCache(Value cache, OpBuilder &builder)
Value initAndPushCache(Value v, OpBuilder &builder)
void addToDiffe(mlir::Value oldGradient, mlir::Value addedGradient, OpBuilder &builder)
bool primalStoreElidable(mlir::Value ptr)
True when a primal store through this pointer may be omitted: declaring the base enzyme_dupnoneed is ...
ArrayRef< DIFFE_TYPE > RetDiffeTypes
void replaceOrigOpWith(Operation *op, ValueRange vals)
std::map< Operation *, Operation * > originalToNewFnOps
LogicalResult visitChild(Operation *op)
void erase(Operation *op)
DIFFE_TYPE getDiffeTypeOfBase(mlir::Value ptr)
The activity the caller declared for the memory this pointer views: the base's entry in ArgDiffeTypes...
void setDiffe(mlir::Value origv, mlir::Value newv, mlir::OpBuilder &builder)
mlir::Value invertPointerM(mlir::Value v, OpBuilder &Builder2)
void eraseIfUnused(Operation *op, bool erase=true, bool check=true)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
FunctionOpInterface newFunc
void setInvertedPointer(mlir::Value origv, mlir::Value newv)
bool isConstantValue(mlir::Value v) const
MFnTypeInfo getAnalyzedTypeInfo(FunctionOpInterface op) const
Definition EnzymeLogic.h:25
LogicalResult controlFlowForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
LogicalResult memoryIdentityForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils, ArrayRef< int > storedVals)
void returnReverseHandler(Operation *op, OpBuilder &builder, MGradientUtilsReverse *gutils)
void regionTerminatorForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
SmallVector< Value > callCacheValues(Operation *orig, MGradientUtilsReverse *gutils)
void branchingForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
LogicalResult callReverseHandler(Operation *orig, OpBuilder &builder, MGradientUtilsReverse *gutils, SmallVector< Value > caches)
LogicalResult allocationForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils, bool zero)
LogicalResult callForwardHandler(Operation *orig, OpBuilder &builder, MGradientUtils *gutils)
Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils, Value stored, OpBuilder &builder)
Definition Utils.cpp:104
Value getConcatValue(OpBuilder &builder, Location loc, ArrayRef< Value > argList)
Definition Utils.cpp:155
bool allResultsActive(Operation *op, const MGradientUtils *gutils)
Value getExtractValue(OpBuilder &builder, Location loc, Type argTy, Value val, int64_t index)
Definition Utils.cpp:163
void setDerivativeFastMath(Operation *op)
Give an operation built for a derivative the fast-math flags the LLVM path gives everything it builds...
mlir::TypedAttr getConstantAttr(mlir::Type type, llvm::StringRef value)