21#include "mlir/Dialect/Arith/IR/Arith.h"
22#include "mlir/Dialect/MemRef/IR/MemRef.h"
23#include "mlir/Dialect/SCF/IR/SCF.h"
24#include "mlir/IR/DialectRegistry.h"
25#include "mlir/IR/Types.h"
26#include "mlir/Support/LogicalResult.h"
27#include "mlir/Transforms/RegionUtils.h"
28#include "llvm/ADT/STLExtras.h"
29#include "llvm/ADT/ScopeExit.h"
37#include "Implementations/SCFDerivatives.inc"
39struct ForOpEnzymeOpsRemover
43 static std::optional<int64_t>
44 getConstantNumberOfIterations(scf::ForOp forOp) {
45 auto lb = forOp.getLowerBound();
46 auto ub = forOp.getUpperBound();
47 auto step = forOp.getStep();
49 IntegerAttr lbAttr, ubAttr, stepAttr;
50 if (!matchPattern(lb, m_Constant(&lbAttr)))
52 if (!matchPattern(ub, m_Constant(&ubAttr)))
54 if (!matchPattern(step, m_Constant(&stepAttr)))
57 int64_t lbI = lbAttr.getInt(), ubI = ubAttr.getInt(),
58 stepI = stepAttr.getInt();
60 return (ubI - lbI) / stepI;
63 static SmallVector<IntOrValue, 1> getDimensionBounds(OpBuilder &builder,
65 auto iters = getConstantNumberOfIterations(forOp);
67 return {IntOrValue(*iters)};
69 Value lb = forOp.getLowerBound(), ub = forOp.getUpperBound(),
70 step = forOp.getStep();
71 Value diff = arith::SubIOp::create(builder, forOp->getLoc(), ub, lb);
73 arith::DivUIOp::create(builder, forOp->getLoc(), diff, step);
74 return {IntOrValue(nSteps)};
78 static SmallVector<Value> getCanonicalLoopIVs(OpBuilder &builder,
81 Value val = forOp.getBody()->getArgument(0);
82 if (!matchPattern(forOp.getLowerBound(), m_Zero())) {
83 val = arith::SubIOp::create(builder, forOp->getLoc(), val,
84 forOp.getLowerBound());
87 if (!matchPattern(forOp.getStep(), m_One())) {
88 val = arith::DivUIOp::create(builder, forOp->getLoc(), val,
94 static IRMapping createArgumentMap(PatternRewriter &rewriter,
95 scf::ForOp forOp, ArrayRef<Value> indFor,
96 scf::ForOp otherForOp,
97 ArrayRef<Value> reversedOther) {
99 for (
auto &&[f, o] : llvm::zip_equal(indFor, reversedOther)) {
103 Value canIdx = forOp.getBody()->getArgument(0);
104 if (!map.contains(canIdx)) {
105 assert(Equivalent(forOp.getLowerBound(), otherForOp.getLowerBound()));
106 assert(Equivalent(forOp.getStep(), otherForOp.getStep()));
108 Location loc = forOp.getLoc();
111 arith::AddIOp::create(rewriter, loc, otherForOp.getLowerBound(),
112 otherForOp.getUpperBound());
113 Value c1 = arith::ConstantOp::create(
114 rewriter, loc, IntegerAttr::get(revIV.getType(), 1));
115 revIV = arith::SubIOp::create(rewriter, loc, revIV, c1);
116 revIV = arith::SubIOp::create(rewriter, loc, revIV,
117 otherForOp.getBody()->getArgument(0));
118 map.map(forOp.getBody()->getArgument(0), revIV);
123 static scf::ForOp replaceWithNewOperands(PatternRewriter &rewriter,
124 scf::ForOp otherForOp,
125 ArrayRef<Value> operands) {
126 auto newOtherForOp = scf::ForOp::create(
127 rewriter, otherForOp->getLoc(), otherForOp.getLowerBound(),
128 otherForOp.getUpperBound(), otherForOp.getStep(), operands);
134 newOtherForOp->setDiscardableAttrs(
135 otherForOp->getDiscardableAttrDictionary());
137 newOtherForOp.getRegion().takeBody(otherForOp.getRegion());
138 rewriter.replaceOp(otherForOp, newOtherForOp->getResults().slice(
139 0, otherForOp->getNumResults()));
140 return newOtherForOp;
143 static ValueRange getInits(scf::ForOp forOp) {
return forOp.getInitArgs(); }
145 static bool mustPostAdd(scf::ForOp forOp) {
return false; }
147 static Value initialValueInBlock(OpBuilder &builder, Block *body,
149 auto Ty = cast<enzyme::GradientType>(grad.getType()).getBasetype();
150 return body->addArgument(Ty, grad.getLoc());
154struct ForOpInterfaceReverse
155 :
public ReverseAutoDiffOpInterface::ExternalModel<ForOpInterfaceReverse,
161 static std::optional<int64_t>
162 getConstantNumberOfIterations(scf::ForOp forOp) {
163 return ForOpEnzymeOpsRemover::getConstantNumberOfIterations(forOp);
166 static Value materializeLowerBound(OpBuilder &, Location, scf::ForOp forOp,
171 static Value materializeUpperBound(OpBuilder &, Location, scf::ForOp forOp,
176 static Value materializeStep(OpBuilder &, Location, scf::ForOp forOp,
181 static int64_t getConstantStart(scf::ForOp forOp) {
183 (void)matchPattern(forOp.getLowerBound(), m_ConstantInt(&v));
184 return v.getSExtValue();
187 static int64_t getConstantStep(scf::ForOp forOp) {
189 (void)matchPattern(forOp.getStep(), m_ConstantInt(&v));
190 return v.getSExtValue();
193 static LogicalResult requireSingleResultBounds(scf::ForOp) {
197 static void cloneOp(OpBuilder &builder, Operation &op, IRMapping &mapping) {
198 builder.clone(op, mapping);
207 static scf::ForOp createConstantScaffoldLoop(OpBuilder &builder, Location loc,
208 int64_t lb, int64_t ub,
209 int64_t step, ValueRange inits) {
214 Value stepV = arith::ConstantIndexOp::create(builder, loc, step);
215 Value ubV = arith::ConstantIndexOp::create(builder, loc, ub);
216 Value lbV = arith::ConstantIndexOp::create(builder, loc, lb);
217 return scf::ForOp::create(builder, loc, lbV, ubV, stepV, inits);
225 static scf::ForOp createForwardOuterLoop(OpBuilder &builder, Location loc,
226 const PeriodicSchedule &sched,
228 return createConstantScaffoldLoop(builder, loc, 0, sched.numSegments(), 1,
232 static scf::ForOp createReverseOuterLoop(OpBuilder &builder, Location loc,
233 const PeriodicSchedule &sched,
235 return createConstantScaffoldLoop(builder, loc, 0, sched.numSegments(), 1,
244 static SmallVector<Value>
245 computeForwardSegmentHint(OpBuilder &builder, Location loc, Value outerIV,
246 const PeriodicSchedule &sched) {
247 Value base = segmentBase(builder, loc, sched, outerIV);
248 return {base, segmentLength(builder, loc, sched, base)};
261 static SmallVector<Value>
262 computeReverseSegmentHint(OpBuilder &builder, Location loc, Value outerIV,
263 const PeriodicSchedule &sched) {
265 arith::ConstantIndexOp::create(builder, loc, sched.numSegments() - 1);
266 Value index = arith::SubIOp::create(builder, loc, lastSegment, outerIV);
267 Value base = segmentBase(builder, loc, sched, index);
268 return {base, segmentLength(builder, loc, sched, base)};
271 static scf::ForOp createSegmentLoop(OpBuilder &builder, Location loc,
272 ArrayRef<Value> hint, ValueRange inits) {
273 Value one = arith::ConstantIndexOp::create(builder, loc, 1);
274 Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
275 return scf::ForOp::create(builder, loc, zero, hint[1], one, inits);
278 static scf::ForOp createForwardSegmentLoop(OpBuilder &builder, Location loc,
279 Value, ArrayRef<Value> fwdHint,
280 const PeriodicSchedule &,
282 return createSegmentLoop(builder, loc, fwdHint, inits);
285 static scf::ForOp createReverseSegmentLoop(OpBuilder &builder, Location loc,
286 Value, ArrayRef<Value> revHint,
287 const PeriodicSchedule &,
289 return createSegmentLoop(builder, loc, revHint, inits);
292 static scf::ForOp createLoopWithSameBounds(OpBuilder &builder, Location loc,
293 scf::ForOp templateLoop,
295 return scf::ForOp::create(builder, loc, templateLoop.getLowerBound(),
296 templateLoop.getUpperBound(),
297 templateLoop.getStep(), inits);
303 static Value segmentIV(OpBuilder &builder, Location loc, scf::ForOp forOp,
304 const PeriodicSchedule &sched, ArrayRef<Value> hint,
306 Value flatIV = arith::AddIOp::create(builder, loc, hint[0], localIV);
307 if (sched.isDynamic()) {
308 flatIV = castToType(builder, loc, flatIV, sched.stepV.getType());
309 return arith::AddIOp::create(
310 builder, loc, sched.startV,
311 arith::MulIOp::create(builder, loc, sched.stepV, flatIV));
314 arith::ConstantIndexOp::create(builder, loc, getConstantStart(forOp));
316 arith::ConstantIndexOp::create(builder, loc, getConstantStep(forOp));
317 return arith::AddIOp::create(
318 builder, loc, arith::MulIOp::create(builder, loc, flatIV, stepCst),
322 static Value computeForwardSegmentIV(OpBuilder &builder, Location loc,
323 scf::ForOp forOp, Value ,
325 const PeriodicSchedule &sched,
326 ArrayRef<Value> fwdHint) {
327 return segmentIV(builder, loc, forOp, sched, fwdHint, localIV);
330 static Value computeReverseSegmentIV(OpBuilder &builder, Location loc,
331 scf::ForOp forOp, Value ,
333 const PeriodicSchedule &sched,
334 ArrayRef<Value> revHint) {
335 return segmentIV(builder, loc, forOp, sched, revHint, localIV);
338 static void createScaffoldYield(OpBuilder &builder, Location loc,
339 ValueRange operands) {
340 scf::YieldOp::create(builder, loc, operands);
346 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
348 SmallVector<Value> caches)
const {
353 auto forOp = cast<scf::ForOp>(op);
354 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
356 SmallVector<bool> operandsActive(forOp.getNumOperands() - 3,
false);
357 for (
int i = 0, e = operandsActive.size(); i < e; ++i) {
363 SmallVector<Value> incomingGradients;
364 for (
auto &&[active, res] :
365 llvm::zip_equal(operandsActive, op->getResults())) {
367 incomingGradients.push_back(gutils->
diffe(res, builder));
373 if (
auto r = tryCreateReverseModeAdjoint(forOp, op, builder, gutils, caches,
374 operandsActive, incomingGradients))
377 auto start = gutils->
popCache(caches[0], builder);
378 auto end = gutils->
popCache(caches[1], builder);
379 auto step = gutils->
popCache(caches[2], builder);
381 auto repFor = scf::ForOp::create(builder, forOp.getLoc(), start, end, step,
383 preserveAttributesButCheckpointing(repFor, forOp);
386 for (
auto &&[oldReg, newReg] :
387 llvm::zip(op->getRegions(), repFor->getRegions())) {
388 for (
auto &&[oBB, revBB] : llvm::zip(oldReg, newReg)) {
389 OpBuilder bodyBuilder(&revBB, revBB.end());
393 scf::YieldOp::create(bodyBuilder, repFor->getLoc());
396 bodyBuilder.setInsertionPointToStart(&revBB);
399 bodyBuilder.setInsertionPoint(revBB.getTerminator());
401 auto term = oBB.getTerminator();
403 for (
auto &&[active, operand] :
404 llvm::zip_equal(operandsActive, term->getOperands())) {
414 for (
auto &&[active, operand] :
415 llvm::zip_equal(operandsActive, term->getOperands())) {
419 gutils->
addToDiffe(operand, revBB.getArgument(argIdx), bodyBuilder);
424 auto first = oBB.rbegin();
427 auto last = oBB.rend();
429 for (
auto it = first; it != last; ++it) {
430 Operation *op = &*it;
435 SmallVector<Value> newResults;
436 newResults.reserve(incomingGradients.size());
438 for (
auto &&[active, arg] :
439 llvm::zip_equal(operandsActive, oBB.getArguments().slice(1))) {
441 newResults.push_back(gutils->
diffe(arg, bodyBuilder));
448 revBB.getTerminator()->setOperands(newResults);
453 for (
auto &&[active, arg] :
454 llvm::zip_equal(operandsActive, forOp.getInitArgs())) {
457 gutils->
addToDiffe(arg, repFor.getResult(resIdx), builder);
463 return success(valid);
466 SmallVector<Value> cacheValues(Operation *op,
468 auto forOp = cast<scf::ForOp>(op);
470 if (
auto r = tryCacheValues(forOp, op, gutils))
474 OpBuilder cacheBuilder(newOp);
476 SmallVector<Value> caches;
480 caches.push_back(cacheLB);
484 caches.push_back(cacheUB);
488 caches.push_back(cacheStep);
493 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
500struct ParallelOpEnzymeOpsRemover
503 static std::optional<int64_t>
504 getConstantNumberOfIterations(Value lb, Value ub, Value step) {
505 IntegerAttr lbAttr, ubAttr, stepAttr;
506 if (!matchPattern(lb, m_Constant(&lbAttr)))
508 if (!matchPattern(ub, m_Constant(&ubAttr)))
510 if (!matchPattern(step, m_Constant(&stepAttr)))
513 int64_t lbI = lbAttr.getInt(), ubI = ubAttr.getInt(),
514 stepI = stepAttr.getInt();
515 return (ubI - lbI) / stepI;
518 static SmallVector<IntOrValue, 1> getDimensionBounds(OpBuilder &builder,
519 scf::ParallelOp parOp) {
520 SmallVector<IntOrValue, 1> bounds;
521 bounds.reserve(parOp.getNumLoops());
522 for (
auto &&[lb, ub, step] : llvm::zip_equal(
523 parOp.getLowerBound(), parOp.getUpperBound(), parOp.getStep())) {
524 auto iters = getConstantNumberOfIterations(lb, ub, step);
526 bounds.push_back(IntOrValue(*iters));
528 Value diff = arith::SubIOp::create(builder, parOp.getLoc(), ub, lb);
530 arith::DivUIOp::create(builder, parOp.getLoc(), diff, step);
531 bounds.push_back(IntOrValue(nSteps));
537 static SmallVector<Value>
538 computeReversedIndices(PatternRewriter &rewriter, scf::ParallelOp parOp,
539 ArrayRef<Value> otherInductionVariable,
540 ArrayRef<IntOrValue> bounds) {
541 return SmallVector<Value>(otherInductionVariable);
544 static SmallVector<Value> getCanonicalLoopIVs(OpBuilder &builder,
545 scf::ParallelOp parOp) {
546 SmallVector<Value> canonicalIVs;
547 canonicalIVs.reserve(parOp.getNumLoops());
548 for (
auto &&[iv, lb, step] :
549 llvm::zip_equal(parOp.getInductionVars(), parOp.getLowerBound(),
552 if (!matchPattern(lb, m_Zero())) {
553 val = arith::SubIOp::create(builder, parOp.getLoc(), val, lb);
556 if (!matchPattern(step, m_One())) {
557 val = arith::DivUIOp::create(builder, parOp.getLoc(), val, step);
559 canonicalIVs.push_back(val);
564 static IRMapping createArgumentMap(PatternRewriter &rewriter,
565 scf::ParallelOp parOp,
566 ArrayRef<Value> indPar,
567 scf::ParallelOp otherParOp,
568 ArrayRef<Value> indOther) {
570 for (
auto &&[f, o] : llvm::zip_equal(indPar, indOther))
573 for (
auto &&[iv, oiv, lb, olb, step, ostep] : llvm::zip_equal(
574 parOp.getInductionVars(), otherParOp.getInductionVars(),
575 parOp.getLowerBound(), otherParOp.getLowerBound(), parOp.getStep(),
576 otherParOp.getStep())) {
577 if (!map.contains(iv)) {
578 assert(Equivalent(lb, olb));
579 assert(Equivalent(step, ostep));
586 static scf::ParallelOp replaceWithNewOperands(PatternRewriter &rewriter,
587 scf::ParallelOp otherParallelOp,
588 ArrayRef<Value> operands) {
589 auto newOtherParOp = scf::ParallelOp::create(
590 rewriter, otherParallelOp.getLoc(), otherParallelOp.getLowerBound(),
591 otherParallelOp.getUpperBound(), otherParallelOp.getStep(), operands);
593 newOtherParOp->setDiscardableAttrs(
594 otherParallelOp->getDiscardableAttrDictionary());
596 newOtherParOp.getRegion().takeBody(otherParallelOp.getRegion());
599 newOtherParOp.getResults().slice(0, otherParallelOp.getNumResults()));
601 if (operands.size() >= 1) {
602 OpBuilder::InsertionGuard guard(rewriter);
603 Operation *oldTerm = newOtherParOp.getBody()->getTerminator();
604 rewriter.setInsertionPointToEnd(newOtherParOp.getBody());
605 auto term = scf::ReduceOp::create(rewriter, newOtherParOp.getLoc(),
606 oldTerm->getOperands());
608 for (
auto [reg, operand] :
609 llvm::zip_equal(term->getRegions(), operands)) {
610 Block *b = ®.front();
611 rewriter.setInsertionPointToEnd(b);
613 auto Ty = cast<AutoDiffTypeInterface>(operand.getType());
614 Value reduced = Ty.createAddOp(rewriter, operand.getLoc(),
615 b->getArgument(0), b->getArgument(1));
616 scf::ReduceReturnOp::create(rewriter, reduced.getLoc(), reduced);
622 return newOtherParOp;
625 static ValueRange getInits(scf::ParallelOp parallelOp) {
626 return parallelOp.getInitVals();
629 static bool mustPostAdd(scf::ParallelOp forOp) {
return false; }
631 static Value initialValueInBlock(OpBuilder &builder, Block *body,
633 OpBuilder::InsertionGuard guard(builder);
634 builder.setInsertionPointToStart(body);
635 return cast<AutoDiffTypeInterface>(
636 cast<enzyme::GradientType>(grad.getType()).getBasetype())
637 .createNullValue(builder, grad.getLoc());
641struct ParallelOpInterfaceReverse
642 :
public ReverseAutoDiffOpInterface::ExternalModel<
643 ParallelOpInterfaceReverse, scf::ParallelOp> {
644 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
646 SmallVector<Value> caches)
const {
647 auto parallelOp = cast<scf::ParallelOp>(op);
648 if (parallelOp.getNumReductions() != 0) {
649 return parallelOp.emitError()
650 <<
"parallel reductions not yet implemented\n";
653 unsigned loopCount = parallelOp.getNumLoops();
654 SmallVector<Value> bounds = llvm::map_to_vector(
655 caches, [&](Value cache) {
return gutils->
popCache(cache, builder); });
657 auto revPar = scf::ParallelOp::create(
658 builder, op->getLoc(),
659 ValueRange(bounds).slice(0, loopCount),
660 ValueRange(bounds).slice(loopCount, loopCount),
661 ValueRange(bounds).slice(loopCount * 2, loopCount));
668 Block *oBB = parallelOp.getBody();
669 Block *revBB = revPar.getBody();
671 OpBuilder bodyBuilder(revBB, revBB->end());
673 bodyBuilder.setInsertionPointToStart(revBB);
676 bodyBuilder.setInsertionPoint(revBB->getTerminator());
678 auto first = oBB->rbegin();
681 auto last = oBB->rend();
683 for (
auto it = first; it != last; ++it) {
684 Operation *op = &*it;
685 valid &= gutils->
Logic.
visitChild(op, bodyBuilder, gutils).succeeded();
690 return success(valid);
693 SmallVector<Value> cacheValues(Operation *op,
695 auto parallelOp = cast<scf::ParallelOp>(op);
697 OpBuilder cacheBuilder(newOp);
698 SmallVector<Value> caches;
699 for (Value lb : parallelOp.getLowerBound())
702 for (Value ub : parallelOp.getUpperBound())
705 for (Value step : parallelOp.getStep())
712 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
718struct IfOpEnzymeOpsRemover
720 static Block *getThenBlock(scf::IfOp ifOp, OpBuilder &builder) {
721 return ifOp.thenBlock();
724 static Block *getElseBlock(scf::IfOp ifOp, OpBuilder &builder) {
726 if (ifOp.getElseRegion().empty()) {
727 OpBuilder::InsertionGuard guard(builder);
728 Block &newBlock = ifOp.getElseRegion().emplaceBlock();
729 builder.setInsertionPointToStart(&newBlock);
730 scf::YieldOp::create(builder, ifOp.getLoc());
733 return ifOp.elseBlock();
736 static Value getDummyValue(OpBuilder &builder, Location loc, Type dummyType) {
737 return cast<AutoDiffTypeInterface>(dummyType).createNullValue(builder, loc);
740 static scf::IfOp replace(PatternRewriter &rewriter, scf::IfOp otherIfOp,
741 TypeRange resultTypes) {
742 auto newIf = scf::IfOp::create(rewriter, otherIfOp->getLoc(), resultTypes,
743 otherIfOp.getCondition());
745 newIf.getThenRegion().takeBody(otherIfOp.getThenRegion());
746 newIf.getElseRegion().takeBody(otherIfOp.getElseRegion());
748 rewriter.replaceAllUsesWith(
749 otherIfOp->getResults(),
750 newIf->getResults().slice(0, otherIfOp->getNumResults()));
751 rewriter.eraseOp(otherIfOp);
756struct IfOpInterfaceReverse
757 :
public ReverseAutoDiffOpInterface::ExternalModel<IfOpInterfaceReverse,
759 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
761 SmallVector<Value> caches)
const {
762 auto ifOp = cast<scf::IfOp>(op);
763 bool hasElse = ifOp.elseBlock() !=
nullptr;
764 Value cond = gutils->
popCache(caches[0], builder);
766 SmallVector<bool> resultsActive(ifOp.getNumResults(),
false);
767 for (
int i = 0, e = resultsActive.size(); i < e; ++i) {
768 auto result = ifOp.getResult(i);
769 auto iface = dyn_cast<AutoDiffTypeInterface>(result.getType());
770 bool needsGrad = iface && !iface.isMutable();
774 SmallVector<Value> incomingGradients;
775 for (
auto &&[active, res] :
776 llvm::zip_equal(resultsActive, ifOp.getResults())) {
778 incomingGradients.push_back(gutils->
diffe(res, builder));
785 scf::IfOp::create(builder, ifOp.getLoc(), TypeRange{}, cond, hasElse);
787 for (
auto &&[oldReg, newReg] :
788 llvm::zip(op->getRegions(), revIf->getRegions())) {
789 for (
auto &&[oBB, revBB] : llvm::zip(oldReg, newReg)) {
790 OpBuilder bodyBuilder(&revBB, revBB.end());
791 bodyBuilder.setInsertionPoint(revBB.getTerminator());
797 for (
auto &it : oBB.getOperations()) {
798 for (
auto res : it.getResults()) {
800 auto iface = dyn_cast<AutoDiffTypeInterface>(res.getType());
801 if (iface && !iface.isMutable())
807 auto term = oBB.getTerminator();
809 SmallVector<Value> activeTermOperands;
810 activeTermOperands.reserve(incomingGradients.size());
811 for (
auto &&[resultActive, operand] :
812 llvm::zip_equal(resultsActive, term->getOperands())) {
814 activeTermOperands.push_back(operand);
817 for (
auto &&[arg, operand] :
818 llvm::zip_equal(incomingGradients, activeTermOperands)) {
824 gutils->
addToDiffe(operand, arg, bodyBuilder);
828 auto first = oBB.rbegin();
831 auto last = oBB.rend();
833 for (
auto it = first; it != last; ++it) {
834 Operation *op = &*it;
840 return success(valid);
843 SmallVector<Value> cacheValues(Operation *op,
845 auto ifOp = cast<scf::IfOp>(op);
848 OpBuilder cacheBuilder(newOp);
851 return SmallVector<Value>{cacheCond};
854 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
857 auto ifOp = cast<scf::IfOp>(op);
858 if (ifOp.getNumResults() == 0)
862 SmallVector<Type> newResultTypes;
863 SmallVector<bool> needsShadow(op->getNumResults());
864 for (
auto result : op->getResults()) {
865 newResultTypes.push_back(result.getType());
866 auto iface = dyn_cast<AutoDiffTypeInterface>(result.getType());
868 newResultTypes.push_back(result.getType());
869 needsShadow[result.getResultNumber()] =
true;
871 needsShadow[result.getResultNumber()] =
false;
877 scf::IfOp::create(builder, op->getLoc(), newResultTypes,
881 for (
auto &&[oldReg, newReg, augReg] :
882 llvm::zip(op->getRegions(), newIf->getRegions(),
883 augmentedOp->getRegions())) {
884 augReg.takeBody(newReg);
885 for (
auto &&[oldBlk, augBlk] : llvm::zip(oldReg, augReg)) {
886 Operation *oldYield = oldBlk.getTerminator();
887 Operation *augYield = augBlk.getTerminator();
889 OpBuilder::InsertionGuard guard(builder);
890 builder.setInsertionPoint(augYield);
891 SmallVector<Value> newOperands;
892 for (
auto &&[oldOperand, augOperand] :
893 llvm::zip(oldYield->getOpOperands(), augYield->getOpOperands())) {
894 newOperands.push_back(augOperand.get());
895 if (needsShadow[oldOperand.getOperandNumber()]) {
896 newOperands.push_back(
901 scf::YieldOp::create(builder, oldYield->getLoc(), newOperands);
907 SmallVector<Value> augmentedResults;
909 for (
auto res : ifOp.getResults()) {
910 augmentedResults.push_back(augmentedOp.getResult(resIdx));
912 if (needsShadow[res.getResultNumber()]) {
917 newIf.replaceAllUsesWith(augmentedResults);
923struct ForOpADDataFlow
924 :
public ADDataFlowOpInterface::ExternalModel<ForOpADDataFlow, scf::ForOp> {
925 SmallVector<Value> getPotentialIncomingValuesRes(Operation *op,
926 OpResult res)
const {
927 auto forOp = cast<scf::ForOp>(op);
929 forOp->getOperand(res.getResultNumber() + 3),
930 forOp.getBody()->getTerminator()->getOperand(res.getResultNumber())};
932 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
933 BlockArgument arg)
const {
934 auto forOp = cast<scf::ForOp>(op);
935 if (arg.getArgNumber() < forOp.getNumInductionVars())
937 auto idx = arg.getArgNumber() - forOp.getNumInductionVars();
938 return {forOp->getOperand(idx + 3),
939 forOp.getBody()->getTerminator()->getOperand(idx)};
943 auto forOp = cast<scf::ForOp>(op);
944 SmallVector<Value> sv;
946 for (
auto &&[res, arg, barg] :
947 llvm::zip_equal(forOp->getResults(), term->getOperands(),
948 forOp.getRegionIterArgs())) {
959struct ParallelOpADDataFlow
960 :
public ADDataFlowOpInterface::ExternalModel<ParallelOpADDataFlow,
962 SmallVector<Value> getPotentialIncomingValuesRes(Operation *op,
963 OpResult res)
const {
964 auto parOp = cast<scf::ParallelOp>(op);
965 const size_t num_lower = parOp.getLowerBound().size();
966 const size_t num_upper = parOp.getUpperBound().size();
967 const size_t num_step = parOp.getStep().size();
968 const size_t init_vals_offset = num_lower + num_upper + num_step;
969 return {parOp->getOperand(res.getResultNumber() + init_vals_offset),
972 ->getRegion(res.getResultNumber())
977 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
978 BlockArgument arg)
const {
981 return SmallVector<Value>();
985 SmallVector<Value> sv;
987 for (
auto [idx, arg] : llvm::enumerate(term->getOperands())) {
989 sv.push_back(term->getRegion(idx).front().getArgument(0));
997struct ReduceOpADDataFlow
998 :
public ADDataFlowOpInterface::ExternalModel<ReduceOpADDataFlow,
1000 SmallVector<Value> getPotentialIncomingValuesRes(Operation *op,
1001 OpResult res)
const {
1003 return SmallVector<Value>();
1005 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
1006 BlockArgument arg)
const {
1012 auto redOp = cast<scf::ReduceOp>(op);
1013 mlir::Block *ownerBlock = arg.getOwner();
1014 auto num_args = ownerBlock->getNumArguments();
1015 auto arg_idx = arg.getArgNumber();
1016 auto region_idx = ownerBlock->getParent()->getRegionNumber();
1017 if (arg_idx == num_args - 1) {
1018 auto parOp = cast<scf::ParallelOp>(redOp->getParentOp());
1019 auto num_lb = parOp.getLowerBound().size();
1020 auto num_ub = parOp.getUpperBound().size();
1021 auto num_st = parOp.getStep().size();
1022 return {parOp->getOperand(num_lb + num_ub + num_st + region_idx),
1023 ownerBlock->getTerminator()->getOperand(0)};
1025 return {redOp->getOperand(region_idx)};
1030 auto redOp = cast<scf::ReduceOp>(op);
1031 auto parOp = cast<scf::ParallelOp>(redOp->getParentOp());
1032 mlir::Block *ownerBlock = term->getBlock();
1033 auto region_idx = ownerBlock->getParent()->getRegionNumber();
1035 return {parOp->getResult(region_idx), ownerBlock->getArgument(1)};
1039class SCFReduceAutoDiffOpInterface
1040 :
public AutoDiffOpInterface::ExternalModel<SCFReduceAutoDiffOpInterface,
1043 LogicalResult createForwardModeTangent(Operation *origTerminator,
1046 auto parentOp = origTerminator->getParentOp();
1047 if (!isa<scf::ParallelOp>(parentOp)) {
1048 origTerminator->emitError()
1049 <<
" createForwardModeTangent called with invalid parent" << *parentOp
1056 assert(parentOp->getNumResults() == origTerminator->getNumOperands());
1057 llvm::SmallDenseSet<unsigned> operandsToShadow;
1058 for (
auto res : parentOp->getResults()) {
1060 operandsToShadow.insert(res.getResultNumber());
1063 SmallVector<Value> newOperands;
1064 newOperands.reserve(origTerminator->getNumOperands() +
1065 operandsToShadow.size());
1066 for (OpOperand &operand : origTerminator->getOpOperands()) {
1068 if (operandsToShadow.contains(operand.getOperandNumber()))
1069 newOperands.push_back(gutils->
invertPointerM(operand.get(), builder));
1075 replTerminator->setOperands(newOperands);
1078 for (
auto &origRegion : origTerminator->getRegions()) {
1079 for (
auto &origBlock : origRegion) {
1080 for (Operation &o : origBlock) {
1082 replTerminator->emitError() <<
" Differentiating reducer block "
1083 << *replTerminator <<
" failed!\n";
1094 for (
auto ®ion : replTerminator->getRegions()) {
1095 for (
auto &block : region) {
1096 std::map<Operation *, bool> used;
1097 std::vector<Operation *> op_list;
1100 for (Operation &o : block) {
1102 op_list.push_back(&o);
1107 auto mark_used = [&used](
const auto &self, Operation *op) ->
void {
1108 if (op !=
nullptr) {
1109 assert(used.find(op) != used.end());
1111 for (
auto v : op->getOperands())
1112 self(self, v.getDefiningOp());
1115 mark_used(mark_used, block.getTerminator());
1119 for (
auto it = op_list.rbegin(); it != op_list.rend(); ++it) {
1128 for (
int i = block.getNumArguments() - 2; i >= 0; i -= 2) {
1129 block.eraseArgument(i);
1138 mlir::OpBuilder term_builder(replTerminator);
1139 mlir::IRMapping mapper;
1140 OperationState state(replTerminator->getLoc(),
1141 scf::ReduceOp::getOperationName());
1142 state.addOperands(newOperands);
1143 size_t num_regions = origTerminator->getNumRegions();
1144 for (
size_t i = 0; i < num_regions; ++i) {
1145 Region *new_orig_region = state.addRegion();
1146 Region *new_diff_region = state.addRegion();
1147 origTerminator->getRegion(i).cloneInto(new_orig_region, mapper);
1148 new_diff_region->takeBody(replTerminator->getRegion(i));
1150 Operation *new_terminator_op = term_builder.create(state);
1151 gutils->
erase(replTerminator);
1158class SCFReduceReturnAutoDiffOpInterface
1159 :
public AutoDiffOpInterface::ExternalModel<
1160 SCFReduceReturnAutoDiffOpInterface, scf::ReduceReturnOp> {
1162 LogicalResult createForwardModeTangent(Operation *origTerminator,
1165 auto parentOp = origTerminator->getParentOp();
1166 if (!isa<scf::ReduceOp>(parentOp)) {
1167 origTerminator->emitError()
1168 <<
" createForwardModeTangent called with invalid parent" << *parentOp
1177 auto reducer_index =
1178 origTerminator->getBlock()->getParent()->getRegionNumber();
1179 assert(reducer_index < parentOp->getParentOp()->getNumResults());
1180 assert(origTerminator->getNumOperands() == 1);
1181 llvm::SmallDenseSet<unsigned> operandsToShadow;
1183 parentOp->getParentOp()->getResult(reducer_index)))
1184 operandsToShadow.insert(0);
1189 SmallVector<Value> newOperands;
1190 newOperands.reserve(operandsToShadow.size());
1191 for (OpOperand &operand : origTerminator->getOpOperands()) {
1192 if (operandsToShadow.contains(operand.getOperandNumber()))
1193 newOperands.push_back(gutils->
invertPointerM(operand.get(), builder));
1204 replTerminator->setOperands(newOperands);
1213 DialectRegistry ®istry) {
1214 registry.addExtension(+[](MLIRContext *context, scf::SCFDialect *) {
1215 registerInterfaces(context);
1216 scf::IfOp::attachInterface<IfOpInterfaceReverse>(*context);
1217 scf::IfOp::attachInterface<IfOpEnzymeOpsRemover>(*context);
1218 scf::ParallelOp::attachInterface<ParallelOpInterfaceReverse>(*context);
1219 scf::ParallelOp::attachInterface<ParallelOpEnzymeOpsRemover>(*context);
1220 scf::ParallelOp::attachInterface<ParallelOpADDataFlow>(*context);
1221 scf::ReduceOp::attachInterface<ReduceOpADDataFlow>(*context);
1222 scf::ReduceOp::attachInterface<SCFReduceAutoDiffOpInterface>(*context);
1223 scf::ReduceReturnOp::attachInterface<SCFReduceReturnAutoDiffOpInterface>(
1225 scf::ForOp::attachInterface<ForOpInterfaceReverse>(*context);
1226 scf::ForOp::attachInterface<ForOpEnzymeOpsRemover>(*context);
1227 scf::ForOp::attachInterface<ForOpADDataFlow>(*context);
static std::optional< SmallVector< Value > > getPotentialTerminatorUsers(Operation *op, Value parent)
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
void zeroDiffe(mlir::Value origv, mlir::OpBuilder &builder)
LogicalResult visitChild(Operation *op, OpBuilder &builder, MGradientUtilsReverse *gutils)
Value popCache(Value cache, OpBuilder &builder)
Value initAndPushCache(Value v, OpBuilder &builder)
void addToDiffe(mlir::Value oldGradient, mlir::Value addedGradient, OpBuilder &builder)
std::map< Operation *, Operation * > originalToNewFnOps
LogicalResult visitChild(Operation *op)
void erase(Operation *op)
mlir::Value invertPointerM(mlir::Value v, OpBuilder &Builder2)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
void setInvertedPointer(mlir::Value origv, mlir::Value newv)
bool isConstantValue(mlir::Value v) const
void localizeGradients(OpBuilder &builder, MGradientUtilsReverse *gutils, Block *fwd)
void registerSCFDialectAutoDiffInterface(DialectRegistry ®istry)