761 SmallVector<CacheInfo> &caches0,
762 PatternRewriter &rewriter,
763 const IRMapping &fwdrevmap, Operation *lastFwd) {
764 assert(rewriter.getInsertionBlock() == reverse);
765 assert(rewriter.getInsertionPoint()->getBlock() == reverse);
769 LLVM_DEBUG(
if (
DebugGraphviz) annotate_ops(forward, reverse));
772 Operation *entry = caches0[0].initOp;
774 IRMapping mapping = fwdrevmap;
775 SmallVector<CacheInfo> caches;
777 for (
auto &info : caches0) {
778 auto todo = info.pushedValue();
779 bool isDefinedOutside =
780 !forward->getParent()->isAncestor(todo.getParentRegion());
781 if (isDefinedOutside) {
782 rewriter.modifyOpInPlace(info.pushOp, [&]() {
783 if (&*rewriter.getInsertionPoint() == info.pushOp)
784 rewriter.setInsertionPoint(info.pushOp->getNextNode());
786 info.pushOp->moveBefore(forward->getParentOp());
788 rewriter.modifyOpInPlace(info.popOp, [&]() {
789 if (&*rewriter.getInsertionPoint() == info.popOp)
790 rewriter.setInsertionPoint(info.popOp->getNextNode());
791 info.popOp->moveBefore(reverse->getParentOp());
793 mapping.map(info.pushedValue(), info.popOp);
796 caches.push_back(info);
798 assert(rewriter.getInsertionPoint()->getBlock() == reverse);
800 if (caches.empty()) {
807 DenseMap<Block *, OpBuilder::InsertPoint> insertionPointMap;
808 for (
const auto &info : caches) {
809 Block *fwdBlock = info.pushOp->getBlock();
810 Block *revBlock = info.popOp->getBlock();
814 if (revBlock == reverse) {
815 insertionPointMap[fwdBlock] = rewriter.saveInsertionPoint();
817 insertionPointMap[fwdBlock] =
818 OpBuilder::InsertPoint(revBlock, revBlock->begin());
824 LLVM_DEBUG(llvm::dbgs() <<
"trying min/cut\n");
826 findCommonAncestor({forward->getParentOp(), reverse->getParentOp()})
829 LLVM_DEBUG(llvm::dbgs() <<
"forward: " << *forward <<
"\n";);
830 LLVM_DEBUG(llvm::dbgs() <<
"reverse: " << *reverse <<
"\n";);
832 SmallVector<Value> worklist;
833 for (
auto &cache : caches) {
834 worklist.push_back(cache.pushedValue());
838 SetVector<Value> roots;
844 while (!worklist.empty()) {
845 Value todo = worklist.pop_back_val();
847 bool isDefinedOutside =
848 !forward->getParent()->isAncestor(todo.getParentRegion());
849 if (isDefinedOutside || fwdrevmap.contains(todo)) {
853 Operation *owner = todo.getDefiningOp();
854 if (!owner || !isMovable(owner)) {
859 bool inserted = G[Node(owner)].insert(Node(todo));
861 for (Value operand : owner->getOperands()) {
862 G[Node(operand)].insert(Node(owner));
863 worklist.push_back(operand);
866 if (
auto regionBranch = dyn_cast<RegionBranchOpInterface>(owner)) {
867 SetVector<Value> valuesDefinedAbove;
868 mlir::getUsedValuesDefinedAbove(regionBranch->getRegions(),
870 for (Value val : valuesDefinedAbove) {
871 G[Node(val)].insert(Node(regionBranch));
872 worklist.push_back(val);
881 SetVector<Operation *> Required;
884 for (
auto &info : caches) {
885 Value poped = info.popOp.getResult();
887 bool isRequired =
false;
888 for (
auto user : poped.getUsers()) {
889 if (user->getBlock() != reverse || !isMovable(user)) {
890 G[info.pushedValue()].insert(Node(user));
891 Required.insert(user);
897 for (
auto user : poped.getUsers()) {
898 G[Node(info.pushedValue())].insert(user);
899 for (Value res : user->getResults()) {
900 G[Node(user)].insert(res);
901 worklist.push_back(res);
907 while (!worklist.empty()) {
908 Value todo = worklist.pop_back_val();
910 bool isRequired =
false;
911 for (
auto user : todo.getUsers()) {
912 if (user->getBlock() != reverse || !isMovable(user)) {
913 G[todo].insert(Node(user));
914 Required.insert(user);
922 for (
auto user : todo.getUsers()) {
924 bool inserted = G[Node(todo)].insert(N);
926 for (Value res : user->getResults()) {
927 G[N].insert(Node(res));
928 worklist.push_back(res);
935 if (!N.first.isOperation())
937 auto op = N.first.getOperation();
938 if (op->getBlock() != reverse)
940 for (
auto v : op->getOperands()) {
941 if (v.getParentBlock() != reverse) {
944 if (G.contains(Node(v))) {
951 assert(rewriter.getInsertionPoint()->getBlock() == reverse);
953 LLVM_DEBUG(llvm::dbgs() <<
"Required: \n";);
954 LLVM_DEBUG(
for (
auto R : Required) llvm::dbgs() <<
" + " << *R <<
"\n";);
956 LLVM_DEBUG(llvm::dbgs() <<
"Roots: \n";);
957 LLVM_DEBUG(
for (
auto R : roots) llvm::dbgs() <<
" + " << R <<
"\n";);
960 LLVM_DEBUG(llvm::dbgs() <<
"pre filter graph: \n";);
962 G = filterGraph(G, roots, Required);
963 LLVM_DEBUG(llvm::dbgs() <<
"post filter graph: \n";);
976 SetVector<Value> newCaches = minCutValues(Orig, roots, Required);
981 pushCachesDownstream(Orig, Required, newCaches);
983 assert(rewriter.getInsertionPoint()->getBlock() == reverse);
986 DenseMap<Node, Node> parent;
987 bfs(Orig, newCaches, parent);
990 llvm::dbgs() <<
"initial new caches: \n";
991 for (Value v : newCaches) {
998 Graph cacheGraph = filterGraph(Orig, newCaches, Required);
1000 LLVM_DEBUG(llvm::dbgs() <<
"cacheGraph:\n");
1001 LLVM_DEBUG(
dump(cacheGraph));
1003 SmallVector<CacheInfo> newCacheInfos;
1006 Operation *firstClone =
nullptr;
1009 if (newCaches.size()) {
1015 SmallVector<Value> todo(newCaches.begin(), newCaches.end());
1016 while (todo.size()) {
1017 auto cur = todo.pop_back_val();
1019 auto &next = cacheGraph.at(Node(cur));
1021 if (next.size() > 1)
1024 auto nextF = *next.begin();
1025 assert(nextF.isOperation());
1026 auto opNext = nextF.getOperation();
1028 if (Required.count(opNext))
1031 if (opNext->getNumResults() != 1)
1034 Value candidate = opNext->getResult(0);
1036 int64_t curSize = computeSizeOfType(cur),
1037 curRank = computeRankOfType(cur);
1039 int64_t newSize = computeSizeOfType(candidate),
1040 newRank = computeRankOfType(candidate);
1042 if (newRank < curRank || (newRank == curRank && newSize < curSize)) {
1043 newCaches.remove(cur);
1044 newCaches.insert(candidate);
1045 todo.push_back(candidate);
1046 cacheGraph.erase(cur);
1047 cacheGraph.erase(opNext);
1051 LLVM_DEBUG(llvm::dbgs() <<
"refined cacheGraph:\n");
1052 LLVM_DEBUG(
dump(cacheGraph));
1054 llvm::dbgs() <<
"refined new caches: \n";
1055 for (Value v : newCaches) {
1060 SetVector<Value> reverseCaches;
1061 for (Value newCache : newCaches) {
1062 if (!forward->getParent()->isAncestor(newCache.getParentRegion())) {
1063 reverseCaches.insert(newCache);
1066 assert(rewriter.getInsertionBlock() == reverse);
1068 enzyme::InitOp initOp = ({
1069 OpBuilder::InsertionGuard guard(rewriter);
1070 rewriter.setInsertionPoint(entry);
1071 enzyme::InitOp::create(
1072 rewriter, newCache.getLoc(),
1073 enzyme::CacheType::get(newCache.getContext(), newCache.getType()));
1076 enzyme::PushOp pushOp = ({
1077 OpBuilder::InsertionGuard guard(rewriter);
1078 if (lastFwd && isa<BlockArgument>(newCache)) {
1079 rewriter.setInsertionPointAfter(lastFwd);
1081 rewriter.setInsertionPointAfterValue(newCache);
1083 enzyme::PushOp::create(rewriter, newCache.getLoc(), initOp.getResult(),
1087 OpBuilder::InsertionGuard guard(rewriter);
1088 rewriter.restoreInsertionPoint(
1089 insertionPointMap.lookup(newCache.getParentBlock()));
1090 enzyme::PopOp popOp = enzyme::PopOp::create(
1091 rewriter, newCache.getLoc(), newCache.getType(), initOp.getResult());
1092 insertionPointMap[newCache.getParentBlock()] =
1093 rewriter.saveInsertionPoint();
1096 mapping.map(newCache, popOp.getResult());
1102 newCacheInfos.push_back(info);
1105 if (reverseCaches.size()) {
1106 Graph fwdGraph = filterGraph(Orig, roots, newCaches);
1109 for (
auto &info : caches) {
1110 fwdmap.map(info.popOp->getResult(0), info.pushedValue());
1113 SmallVector<Operation *> toErase;
1114 for (
auto &op : llvm::make_early_inc_range(*reverse)) {
1115 if (!fwdGraph.contains(Node(&op)))
1118 Operation *newO = ({
1119 OpBuilder::InsertionGuard guard(rewriter);
1120 rewriter.setInsertionPoint(forward->getTerminator());
1121 rewriter.clone(op, fwdmap);
1124 bool hasUse =
false;
1125 for (
auto &&[res, newRes] :
1126 llvm::zip_equal(op.getResults(), newO->getResults())) {
1127 if (newCaches.contains(res)) {
1128 enzyme::InitOp initOp = ({
1129 OpBuilder::InsertionGuard guard(rewriter);
1130 rewriter.setInsertionPoint(entry);
1131 enzyme::InitOp::create(rewriter, newRes.getLoc(),
1132 enzyme::CacheType::get(newRes.getContext(),
1136 enzyme::PushOp pushOp = ({
1137 OpBuilder::InsertionGuard guard(rewriter);
1138 rewriter.setInsertionPoint(forward->getTerminator());
1139 enzyme::PushOp::create(rewriter, newRes.getLoc(),
1140 initOp.getResult(), newRes);
1143 enzyme::PopOp popOp = ({
1144 OpBuilder::InsertionGuard guard(rewriter);
1145 rewriter.setInsertionPoint(&op);
1146 enzyme::PopOp::create(rewriter, newRes.getLoc(), newRes.getType(),
1147 initOp.getResult());
1150 rewriter.replaceAllUsesWith(res, popOp->getResult(0));
1156 newCacheInfos.push_back(info);
1159 for (
auto user : res.getUsers()) {
1160 if (!fwdGraph.contains(Node(user))) {
1168 if (!hasUse && !op.hasAttr(
"enzyme.no_erase")) {
1169 toErase.push_back(&op);
1172 for (
auto op : llvm::reverse(toErase)) {
1173 rewriter.eraseOp(op);
1178 forward->walk([&](Operation *op) {
1179 if (!cacheGraph.contains(Node(op)))
1181 bool hasUse =
false;
1182 for (
auto res : op->getResults()) {
1183 if (newCaches.contains(res)) {
1190 for (
auto v : op->getOperands()) {
1191 if (mapping.contains(v))
1193 if (forward->getParent()->isAncestor(v.getParentRegion()))
1196 enzyme::InitOp initOp = ({
1197 OpBuilder::InsertionGuard guard(rewriter);
1198 rewriter.setInsertionPoint(entry);
1199 enzyme::InitOp::create(
1200 rewriter, v.getLoc(),
1201 enzyme::CacheType::get(v.getContext(), v.getType()));
1205 OpBuilder::InsertionGuard guard(rewriter);
1206 rewriter.setInsertionPoint(forward->getParentOp());
1207 enzyme::PushOp::create(rewriter, v.getLoc(), initOp.getResult(), v);
1210 enzyme::PopOp popOp = ({
1211 OpBuilder::InsertionGuard guard(rewriter);
1212 rewriter.setInsertionPoint(reverse->getParentOp());
1213 enzyme::PopOp::create(rewriter, v.getLoc(), v.getType(),
1214 initOp.getResult());
1216 mapping.map(v, popOp->getResult(0));
1218 OpBuilder::InsertionGuard guard(rewriter);
1219 rewriter.restoreInsertionPoint(insertionPointMap.lookup(op->getBlock()));
1220 auto cop = rewriter.clone(*op, mapping);
1221 insertionPointMap[op->getBlock()] = rewriter.saveInsertionPoint();
1227 rewriter.setInsertionPoint(firstClone);
1230 for (
auto &info : caches) {
1231 if (mapping.contains(info.pushedValue())) {
1232 rewriter.replaceOp(info.popOp, mapping.lookup(info.pushedValue()));
1234 rewriter.eraseOp(info.popOp);
1236 rewriter.eraseOp(info.pushOp);
1237 rewriter.eraseOp(info.initOp);
1240 LLVM_DEBUG(llvm::dbgs() <<
"post min/cut\n");
1242 findCommonAncestor({forward->getParentOp(), reverse->getParentOp()})
1245 caches0 = std::move(newCacheInfos);
1249 LoopLikeOpInterface fwdLoop,
1250 LoopLikeOpInterface revLoop,
1252 int64_t threshold) {
1253 Operation *definingOp = info.
pushedValue().getDefiningOp();
1256 auto loadOp = dyn_cast<memref::LoadOp>(definingOp);
1259 auto alloc = loadOp.getMemRef();
1260 auto allocaOp = dyn_cast_if_present<memref::AllocaOp>(alloc.getDefiningOp());
1265 if (!(allocaOp.getType().hasStaticShape() &&
1266 allocaOp.getType().getNumElements() <= threshold)) {
1272 auto walkResult = fwdLoop->walk([&alloc](Operation *op) {
1273 if (hasEffect<MemoryEffects::Write>(op, alloc) || hasUnknownEffects(op))
1274 return WalkResult::interrupt();
1275 return WalkResult::advance();
1277 if (walkResult.wasInterrupted())
1280 ImplicitLocOpBuilder builder(alloc.getLoc(), info.
initOp);
1282 SmallVector<Value> pushedValues;
1283 SmallVector<Operation *> toCopy;
1284 std::queue<Value> worklist;
1285 DenseSet<Value> visited;
1286 for (Value idx : loadOp.getIndices()) {
1288 visited.insert(idx);
1290 while (!worklist.empty()) {
1291 Value curr = worklist.front();
1294 if (mapping.contains(curr))
1298 if (dom.dominates(curr, revLoop))
1301 if (Operation *definingOp = curr.getDefiningOp()) {
1304 if (fwdLoop->isProperAncestor(definingOp)) {
1305 toCopy.push_back(definingOp);
1306 for (Value operand : definingOp->getOperands()) {
1307 if (!visited.contains(operand)) {
1308 visited.insert(operand);
1309 worklist.push(operand);
1313 pushedValues.push_back(curr);
1321 builder.setInsertionPoint(info.
initOp);
1322 SmallVector<Value> newInits =
1323 llvm::map_to_vector(pushedValues, [&](Value val) -> Value {
1324 return enzyme::InitOp::create(
1325 builder, CacheType::get(val.getContext(), val.getType()));
1327 builder.setInsertionPoint(fwdLoop);
1328 for (
auto &&[cache, val] : llvm::zip(newInits, pushedValues)) {
1329 enzyme::PushOp::create(builder, cache, val);
1332 builder.setInsertionPoint(revLoop);
1333 for (
auto &&[cache, val] : llvm::zip(newInits, pushedValues)) {
1334 Value popped = enzyme::PopOp::create(builder, val.getType(), cache);
1335 mapping.map(val, popped);
1339 builder.setInsertionPoint(info.
popOp);
1340 for (Operation *op : llvm::reverse(toCopy)) {
1341 builder.clone(*op, mapping);
1345 if (mapping.contains(alloc)) {
1346 cachedMemRef = mapping.lookup(alloc);
1348 builder.setInsertionPoint(info.
initOp);
1349 Value newCache = enzyme::InitOp::create(
1350 builder, enzyme::CacheType::get(builder.getContext(), alloc.getType()));
1352 builder.setInsertionPoint(fwdLoop);
1353 Value copyAlloc = memref::AllocOp::create(
1354 builder, allocaOp.getType(), allocaOp.getDynamicSizes(),
1355 allocaOp.getSymbolOperands(), allocaOp.getAlignmentAttr());
1358 Value zero = arith::ConstantIndexOp::create(builder, 0);
1359 Value one = arith::ConstantIndexOp::create(builder, 1);
1361 int64_t rank = loadOp.getMemRefType().getRank();
1362 SmallVector<Value> lbs(rank, zero);
1363 SmallVector<Value> steps(rank, one);
1364 SmallVector<Value> ubs(rank);
1365 for (int64_t i = 0; i < rank; i++)
1366 ubs[i] = memref::DimOp::create(builder, alloc, i);
1369 builder, builder.getLoc(), lbs, ubs, steps,
1370 [&](OpBuilder &bodyBuilder, Location loc, ValueRange ivs) {
1371 Value loaded = memref::LoadOp::create(bodyBuilder, loc, alloc, ivs);
1372 memref::StoreOp::create(bodyBuilder, loc, loaded, copyAlloc, ivs);
1375 enzyme::PushOp::create(builder, newCache, copyAlloc);
1377 builder.setInsertionPoint(revLoop);
1378 cachedMemRef = enzyme::PopOp::create(builder, alloc.getType(), newCache);
1379 mapping.map(alloc, cachedMemRef);
1382 builder.setInsertionPoint(info.
popOp);
1383 SmallVector<Value> revIndices =
1384 llvm::map_to_vector(loadOp.getIndices(), [&](Value idx) -> Value {
1385 return mapping.lookupOrDefault(idx);
1388 Value newLoad = memref::LoadOp::create(builder, cachedMemRef, revIndices);
1389 info.
popOp.replaceAllUsesWith(newLoad);