283 PatternRewriter &rewriter)
const {
284 auto forOp = cast<OpName>(op);
287 auto loopLike = dyn_cast<LoopLikeOpInterface>(op);
290 mlir::moveLoopInvariantCode(loopLike);
294 OpName otherForOp =
nullptr;
302 llvm::SetVector<Value> updatedGradients;
304 llvm::MapVector<Value, CacheInfo> cachesMap;
305 SmallVector<CacheInfo> toDelete;
307 Block *body = forOp.getBody();
309 body->walk([&](Operation *op) {
310 if (
auto setOp = dyn_cast<enzyme::SetOp>(op)) {
311 if (!body->getParent()->isAncestor(
312 setOp.getGradient().getDefiningOp()->getParentRegion()))
313 updatedGradients.insert(setOp.getGradient());
316 if (
auto pushOp = dyn_cast<enzyme::PushOp>(op)) {
320 if (cachesMap.contains(pushedValue)) {
321 info = info.
merge(cachesMap.lookup(pushedValue), rewriter);
324 if (info.
pushOp->getBlock() == body && info.
popOp->getBlock() == body &&
326 toDelete.push_back(info);
330 cachesMap[pushedValue] = info;
332 if (isa<OpName>(info.
popOp->getParentOp())) {
333 otherForOp = cast<OpName>(info.
popOp->getParentOp());
338 while (!toDelete.empty()) {
339 CacheInfo info = toDelete.pop_back_val();
340 rewriter.replaceAllUsesWith(info.
popOp.getResult(),
342 rewriter.eraseOp(info.
pushOp);
343 rewriter.eraseOp(info.
popOp);
344 rewriter.eraseOp(info.
initOp);
347 SmallVector<CacheInfo> caches0 =
348 llvm::map_to_vector(cachesMap, [](
auto p) {
return std::get<1>(p); });
350 SmallVector<CacheInfo> caches = caches0;
353 if (updatedGradients.empty() && caches.empty())
362 if (!caches.empty() && !otherForOp) {
363 auto diag = forOp->emitError()
364 <<
"cannot remove " << caches.size()
365 <<
" cache(s) pushed in this loop: no " << forOp->getName()
366 <<
" holding their pops to pair it with";
368 diag.attachNote(info.popOp->getLoc())
369 <<
"pop is in " << info.popOp->getParentOp()->getName()
370 <<
", pushed from " << info.pushOp->getParentOp()->getName();
374 DenseMap<Value, llvm::SmallVector<Operation *>> updatedGradientUsers;
376 for (
auto &it : llvm::make_early_inc_range(*body)) {
379 auto getOp = dyn_cast<enzyme::GetOp>(op);
381 if (
getOp && updatedGradients.contains(
getOp.getGradient())) {
382 updatedGradientUsers[
getOp.getGradient()].push_back(
getOp);
383 }
else if (
auto setOp = dyn_cast<enzyme::SetOp>(op)) {
384 updatedGradientUsers[setOp.getGradient()].push_back(setOp);
387 if (!
getOp || updatedGradients.contains(
getOp.getGradient()))
390 auto outerGet = enzyme::GetOp::create(rewriter,
getOp->getLoc(),
391 getOp.getResult().getType(),
392 getOp.getGradient());
394 rewriter.replaceAllUsesWith(
getOp.getResult(), outerGet.getResult());
395 rewriter.eraseOp(
getOp);
400 bool postAdd = FinalClass::mustPostAdd(forOp);
402 auto term = body->getTerminator();
404 SmallVector<Value> newOperands(FinalClass::getInits(forOp));
405 for (
auto grad : updatedGradients) {
406 auto Ty = cast<enzyme::GradientType>(grad.getType()).getBasetype();
411 newInit = enzyme::GetOp::create(rewriter, grad.getLoc(), Ty, grad);
413 newInit = cast<AutoDiffTypeInterface>(Ty).createNullValue(
414 rewriter, grad.getLoc());
417 newOperands.push_back(newInit);
422 Value val = FinalClass::initialValueInBlock(rewriter, body, grad);
423 for (
auto user : updatedGradientUsers[grad]) {
424 if (
auto getOp = dyn_cast<enzyme::GetOp>(user)) {
425 rewriter.replaceOp(
getOp, val);
427 auto setOp = cast<enzyme::SetOp>(user);
428 val = setOp.getValue();
429 rewriter.eraseOp(setOp);
433 term->insertOperands(term->getNumOperands(), ValueRange(val));
440 SmallVector<Value> inductionVariable;
441 SmallVector<Value> otherInductionVariable;
442 SmallVector<Value> reversedIndex;
444 SmallVector<IntOrValue> revNumIters;
445 SmallVector<IntOrValue> fwdNumIters;
447 if (!fwdNumIters.size()) {
448 OpBuilder::InsertionGuard guard(rewriter);
449 rewriter.setInsertionPoint(forOp);
450 fwdNumIters = FinalClass::getDimensionBounds(rewriter, forOp);
453 Operation *lastFwd =
nullptr;
455 rewriter.setInsertionPointToStart(forOp.getBody());
456 inductionVariable = llvm::map_to_vector(
457 FinalClass::getCanonicalLoopIVs(rewriter, forOp),
459 if (rewriter.getInsertionPoint() != forOp.getBody()->begin()) {
460 lastFwd = rewriter.getInsertionPoint()->getPrevNode();
463 rewriter.setInsertionPointToStart(otherForOp.getBody());
464 otherInductionVariable =
465 FinalClass::getCanonicalLoopIVs(rewriter, otherForOp);
469 if (!revNumIters.size()) {
470 OpBuilder::InsertionGuard guard(rewriter);
471 rewriter.setInsertionPoint(otherForOp);
472 revNumIters = FinalClass::getDimensionBounds(rewriter, otherForOp);
473 for (
auto &&[rev, fwd] : llvm::zip_equal(revNumIters, fwdNumIters)) {
474 if (!fwd.vval && rev.vval) {
481 reversedIndex = FinalClass::computeReversedIndices(
482 rewriter, otherForOp, otherInductionVariable, revNumIters);
483 fwdrevmap = FinalClass::createArgumentMap(
484 rewriter, forOp, inductionVariable, otherForOp, reversedIndex);
485 for (
auto v : inductionVariable) {
486 if (
auto op = v.getDefiningOp()) {
487 op->setAttr(
"enzyme.no_erase", rewriter.getUnitAttr());
490 for (
auto v : otherInductionVariable) {
491 if (
auto op = v.getDefiningOp()) {
492 op->setAttr(
"enzyme.no_erase", rewriter.getUnitAttr());
500 rewriter, fwdrevmap, lastFwd);
503 for (
auto v : inductionVariable) {
504 if (
auto op = v.getDefiningOp()) {
505 op->removeAttr(
"enzyme.no_erase");
508 for (
auto v : otherInductionVariable) {
509 if (
auto op = v.getDefiningOp()) {
510 op->removeAttr(
"enzyme.no_erase");
513 auto revIP = rewriter.saveInsertionPoint();
515 SmallVector<Value> newPushValues;
517 unsigned numNewValuePushes = 0;
520 rewriter.setInsertionPointAfter(lastFwd);
522 rewriter.setInsertionPointToStart(forOp.getBody());
523 for (
auto &info : caches) {
525 Value pushedValue = info.pushedValue();
527 assert(forOp.getRegion().isAncestor(pushedValue.getParentRegion()));
530 if (!inductionVariable.size()) {
531 Value zero = arith::ConstantOp::create(rewriter, forOp->getLoc(),
532 rewriter.getIndexAttr(0));
533 newOperands.push_back(zero);
535 inductionVariable = {
536 body->addArgument(zero.getType(), forOp->getLoc())};
538 OpBuilder::InsertionGuard guard(rewriter);
539 rewriter.setInsertionPoint(term);
541 auto one = arith::ConstantOp::create(rewriter, forOp->getLoc(),
542 rewriter.getIndexAttr(1));
543 auto newInductionVar = arith::AddIOp::create(
544 rewriter, forOp->getLoc(), inductionVariable[0], one);
545 term->insertOperands(term->getNumOperands(),
546 ValueRange(newInductionVar));
550 SmallVector<int64_t> newShape;
551 SmallVector<Value> dynamicDims;
552 for (
const auto &dim : fwdNumIters) {
554 newShape.push_back(mlir::ShapedType::kDynamic);
557 newShape.push_back(dim.ival);
565 size_t numLoopDynamicDims = dynamicDims.size();
567 auto ET = info.cachedType();
570 bool multiDim =
false;
571 Attribute memorySpace =
nullptr;
572 MultidimensionalAllocInterface allocOp;
576 ShapedType cachedShapedTy;
577 if (
auto ST = dyn_cast<ShapedType>(ET)) {
581 bool fullyStatic = llvm::all_of(ST.getShape(), [](int64_t dim) {
582 return dim != ShapedType::kDynamic;
587 if (
auto MT = dyn_cast<MemRefType>(pushedValue.getType()))
588 memorySpace = MT.getMemorySpace();
589 allocOp.appendDynamicDims(dynamicDims);
593 newShape.append(ST.getShape().begin(), ST.getShape().end());
594 ET = ST.getElementType();
599 if (
auto innerMT = dyn_cast<MemRefType>(ET))
600 memorySpace = innerMT.getMemorySpace();
604 ? cast<ShapedType>(RankedTensorType::get(newShape, ET))
605 : cast<ShapedType>(MemRefType::get(newShape, ET));
607 newType = cast<ShapedType>(cast<MemRefType>(newType)
608 .clonePtrWith(memorySpace, std::nullopt)
614 OpBuilder::InsertionGuard guard(rewriter);
615 rewriter.setInsertionPoint(forOp);
616 Value initValue = tensor::EmptyOp::create(
617 rewriter, info.initOp->getLoc(), newType, dynamicDims);
619 newOperands.push_back(initValue);
622 auto cacheValue = body->addArgument(newType, info.pushOp->getLoc());
625 OpBuilder::InsertionGuard guard(rewriter);
626 rewriter.setInsertionPoint(info.pushOp);
629 if (
auto TT = dyn_cast<TensorType>(info.cachedType())) {
630 auto shape = TT.getShape();
632 SmallVector<int64_t> offsets(shape.size() + 1, 0);
633 offsets[0] = ShapedType::kDynamic;
635 SmallVector<int64_t> sizes;
636 sizes.reserve(shape.size() + 1);
638 sizes.append(shape.begin(), shape.end());
640 SmallVector<int64_t> strides(shape.size() + 1, 1);
642 newCacheValue = tensor::InsertSliceOp::create(
643 rewriter, info.pushOp->getLoc(), info.pushOp.getValue(),
644 cacheValue, inductionVariable, ValueRange(), ValueRange(),
645 rewriter.getDenseI64ArrayAttr(offsets),
646 rewriter.getDenseI64ArrayAttr(sizes),
647 rewriter.getDenseI64ArrayAttr(strides));
649 newCacheValue = tensor::InsertOp::create(
650 rewriter, info.pushOp->getLoc(), info.pushOp.getValue(),
651 cacheValue, inductionVariable);
654 term->insertOperands(term->getNumOperands(),
655 ValueRange(newCacheValue));
662 OpBuilder::InsertionGuard guard(rewriter);
663 rewriter.setInsertionPoint(forOp);
665 initValue = allocOp.allocate(rewriter, info.initOp->getLoc(),
666 newType, dynamicDims);
669 memref::AllocOp::create(rewriter, info.initOp->getLoc(),
670 cast<MemRefType>(newType), dynamicDims);
672 newPushValues.push_back(initValue);
676 OpBuilder::InsertionGuard guard(rewriter);
677 rewriter.setInsertionPoint(info.pushOp);
679 auto MT = dyn_cast_or_null<MemRefType>(cachedShapedTy);
680 if (multiDim && MT) {
681 auto memref = info.pushOp.getValue();
682 auto shape = MT.getShape();
684 SmallVector<int64_t> offsets(newShape.size(), 0);
685 SmallVector<int64_t> sizes;
686 for (
auto [i, _] : llvm::enumerate(inductionVariable)) {
687 offsets[i] = ShapedType::kDynamic;
691 SmallVector<Value> dynSizes;
692 for (
size_t i = numLoopDynamicDims; i < dynamicDims.size(); ++i) {
693 dynSizes.push_back(dynamicDims[i]);
696 sizes.append(shape.begin(), shape.end());
698 SmallVector<int64_t> strides(newShape.size(), 1);
700 auto RT = memref::SubViewOp::inferRankReducedResultType(
701 MT.getShape(), cast<MemRefType>(initValue.getType()), offsets,
704 rewriter.setInsertionPointAfterValue(memref);
705 rewriter.replaceAllUsesWith(
707 memref::SubViewOp::create(
708 rewriter, memref.getLoc(), RT, initValue,
712 rewriter.getDenseI64ArrayAttr(offsets),
713 rewriter.getDenseI64ArrayAttr(sizes),
714 rewriter.getDenseI64ArrayAttr(strides))
717 if (
auto defOp = memref.getDefiningOp())
718 rewriter.eraseOp(defOp);
720 if (dynamicDims.empty()) {
721 memref::StoreOp::create(rewriter, info.pushOp->getLoc(),
722 info.pushOp.getValue(), initValue,
725 auto memrefType = cast<MemRefType>(initValue.getType());
726 enzyme::StoreOp::create(rewriter, info.pushOp.getLoc(),
727 info.pushOp.getValue(), initValue,
728 inductionVariable, dynamicDims,
729 memrefType.getShape());
737 auto numInitArgs = FinalClass::getInits(forOp).size();
738 rewriter.setInsertionPoint(forOp);
740 forOp = FinalClass::replaceWithNewOperands(rewriter, forOp, newOperands);
742 for (
size_t i = 0; i < numNewValuePushes; ++i)
743 newPushValues.push_back(
744 forOp->getResult(forOp->getNumResults() - numNewValuePushes + i));
747 rewriter.setInsertionPointAfter(forOp);
749 unsigned resultIdx = numInitArgs;
750 for (
auto grad : updatedGradients) {
753 Value incoming = forOp->getResult(resultIdx);
758 auto T = cast<AutoDiffTypeInterface>(incoming.getType());
759 Value current = enzyme::GetOp::create(rewriter, grad.getLoc(), T, grad);
760 outgoing = T.createAddOp(rewriter, grad.getLoc(), incoming, current);
762 enzyme::SetOp::create(rewriter, grad.getLoc(), grad, outgoing);
766 int pushedValueIdx = 0;
769 if (otherInductionVariable.size()) {
770 rewriter.restoreInsertionPoint(revIP);
772 rewriter.setInsertionPointToStart(otherForOp.getBody());
774 for (
auto &info : caches) {
777 forOp.getRegion().isAncestor(info.pushedValue().getParentRegion()));
779 Value cache = info.initOp.getResult();
783 if (!revNumIters.size()) {
784 OpBuilder::InsertionGuard guard(rewriter);
785 rewriter.setInsertionPoint(otherForOp);
786 revNumIters = FinalClass::getDimensionBounds(rewriter, otherForOp);
787 for (
auto &&[rev, fwd] : llvm::zip_equal(revNumIters, fwdNumIters)) {
788 if (!fwd.vval && rev.vval) {
796 if (otherInductionVariable.size() && !reversedIndex.size()) {
797 reversedIndex = FinalClass::computeReversedIndices(
798 rewriter, otherForOp, otherInductionVariable, revNumIters);
802 if (!otherInductionVariable.size()) {
803 Value zero = arith::ConstantOp::create(rewriter, otherForOp->getLoc(),
804 rewriter.getIndexAttr(0));
805 SmallVector<Value> newOperands =
806 llvm::to_vector(FinalClass::getInits(otherForOp));
807 newOperands.push_back(zero);
809 otherInductionVariable = {
810 body->addArgument(zero.getType(), otherForOp->getLoc())};
812 OpBuilder::InsertionGuard guard(rewriter);
813 rewriter.setInsertionPoint(term);
815 auto one = arith::ConstantOp::create(rewriter, forOp->getLoc(),
816 rewriter.getIndexAttr(1));
817 auto newInductionVar = arith::AddIOp::create(
818 rewriter, forOp->getLoc(), otherInductionVariable[0], one);
819 term->insertOperands(term->getNumOperands(),
820 ValueRange(newInductionVar));
824 OpBuilder::InsertionGuard guard(rewriter);
825 rewriter.setInsertionPoint(otherForOp);
826 otherForOp = FinalClass::replaceWithNewOperands(rewriter, otherForOp,
830 reversedIndex = FinalClass::computeReversedIndices(
831 rewriter, otherForOp, otherInductionVariable, revNumIters);
833 reversedIndex = llvm::map_to_vector(reversedIndex, [&rewriter](Value iv) {
837 SmallVector<int64_t> newShape;
838 SmallVector<Value> dynamicDims;
839 for (
const auto &dim : revNumIters) {
841 newShape.push_back(mlir::ShapedType::kDynamic);
844 newShape.push_back(dim.ival);
848 auto ET = info.cachedType();
851 bool multiDim =
false;
852 Attribute memorySpace =
nullptr;
853 MultidimensionalAllocInterface allocOp;
856 ShapedType cachedShapedTy;
857 if (
auto ST = dyn_cast<ShapedType>(ET)) {
858 if (
auto MT = dyn_cast<MemRefType>(ST))
859 memorySpace = MT.getMemorySpace();
869 if (
auto MT = dyn_cast<MemRefType>(info.pushedValue().getType()))
870 memorySpace = MT.getMemorySpace();
873 bool fullyStatic = llvm::all_of(ST.getShape(), [](int64_t dim) {
874 return dim != ShapedType::kDynamic;
880 newShape.append(ST.getShape().begin(), ST.getShape().end());
881 ET = ST.getElementType();
886 if (
auto innerMT = dyn_cast<MemRefType>(ET))
887 memorySpace = innerMT.getMemorySpace();
891 ? cast<ShapedType>(RankedTensorType::get(newShape, ET))
892 : cast<ShapedType>(MemRefType::get(newShape, ET));
894 newType = cast<ShapedType>(cast<MemRefType>(newType)
895 .clonePtrWith(memorySpace, std::nullopt)
897 enzyme::InitOp newInit = ({
898 OpBuilder::InsertionGuard guard(rewriter);
899 rewriter.setInsertionPoint(info.initOp);
901 enzyme::InitOp::create(
902 rewriter, info.initOp->getLoc(),
903 enzyme::CacheType::get(cache.getContext(), newType));
906 OpBuilder::InsertionGuard guard(rewriter);
907 rewriter.setInsertionPointAfter(forOp);
908 auto newPush = enzyme::PushOp::create(rewriter, cache.getLoc(),
910 newPushValues[pushedValueIdx]);
911 rewriter.eraseOp(info.pushOp);
917 OpBuilder::InsertionGuard guard(rewriter);
919 rewriter.setInsertionPoint(otherForOp);
921 auto popNewValue = enzyme::PopOp::create(rewriter, info.popOp->getLoc(),
922 newType, newInit.getResult());
924 rewriter.setInsertionPoint(info.popOp);
928 if (
auto TT = dyn_cast<TensorType>(info.cachedType())) {
929 auto shape = TT.getShape();
930 SmallVector<int64_t> offsets(shape.size() + 1, 0);
931 offsets[0] = ShapedType::kDynamic;
933 SmallVector<int64_t> sizes;
934 sizes.reserve(shape.size() + 1);
936 sizes.append(shape.begin(), shape.end());
938 SmallVector<int64_t> strides(shape.size() + 1, 1);
940 popValue = tensor::ExtractSliceOp::create(
941 rewriter, info.popOp->getLoc(), TT, popNewValue,
942 reversedIndex, ValueRange(), ValueRange(),
943 rewriter.getDenseI64ArrayAttr(offsets),
944 rewriter.getDenseI64ArrayAttr(sizes),
945 rewriter.getDenseI64ArrayAttr(strides))
948 popValue = tensor::ExtractOp::create(rewriter, info.popOp->getLoc(),
949 popNewValue, reversedIndex)
954 auto MT = dyn_cast_or_null<MemRefType>(cachedShapedTy);
955 if (multiDim && MT) {
956 auto shape = MT.getShape();
958 SmallVector<int64_t> offsets(newShape.size(), 0);
959 SmallVector<int64_t> sizes;
960 for (
auto [i, _] : llvm::enumerate(reversedIndex)) {
961 offsets[i] = ShapedType::kDynamic;
965 sizes.append(shape.begin(), shape.end());
967 SmallVector<Value> dynSizes;
968 for (
size_t i = reversedIndex.size(); i < newShape.size(); ++i) {
972 if (newShape[i] == ShapedType::kDynamic)
973 dynSizes.push_back(memref::DimOp::create(
974 rewriter, popNewValue.getLoc(), popNewValue,
975 arith::ConstantIndexOp::create(rewriter, popNewValue.getLoc(),
979 SmallVector<int64_t> strides(newShape.size(), 1);
981 auto RT = memref::SubViewOp::inferRankReducedResultType(
982 MT.getShape(), cast<MemRefType>(popNewValue.getType()), offsets,
985 popValue = memref::SubViewOp::create(
986 rewriter, info.popOp->getLoc(), RT, popNewValue,
990 rewriter.getDenseI64ArrayAttr(offsets),
991 rewriter.getDenseI64ArrayAttr(sizes),
992 rewriter.getDenseI64ArrayAttr(strides));
995 llvm::make_early_inc_range(info.popOp.getResult().getUsers())) {
996 if (allocOp ? allocOp.isDeallocation(user)
997 : isa<memref::DeallocOp>(user))
998 rewriter.eraseOp(user);
1001 if (dynamicDims.empty()) {
1002 popValue = memref::LoadOp::create(rewriter, info.popOp->getLoc(),
1003 popNewValue, reversedIndex);
1005 auto memrefType = cast<MemRefType>(popNewValue.getType());
1006 popValue = enzyme::LoadOp::create(
1007 rewriter, info.popOp.getLoc(),
1008 cast<MemRefType>(popNewValue.getType()).getElementType(),
1009 popNewValue.getResult(), reversedIndex, dynamicDims,
1010 memrefType.getShape());
1015 rewriter.setInsertionPointAfter(otherForOp);
1017 allocOp.deallocate(rewriter, info.initOp->getLoc(), popNewValue);
1019 memref::DeallocOp::create(rewriter, info.initOp->getLoc(),
1024 rewriter.replaceAllUsesWith(info.popOp.getResult(), popValue);
1025 rewriter.eraseOp(info.popOp);