Enzyme main
Loading...
Searching...
No Matches
SCFAutoDiffOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- SCFAutoDiffOpInterfaceImpl.cpp - Interface external model ----------===//
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 the external model implementation of the automatic
10// differentiation op interfaces for the upstream MLIR SCF dialect.
11//
12//===----------------------------------------------------------------------===//
13
20#include "Passes/RemovalUtils.h"
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"
30#include <array>
31#include <functional>
32
33using namespace mlir;
34using namespace mlir::enzyme;
35
36namespace {
37#include "Implementations/SCFDerivatives.inc"
38
39struct ForOpEnzymeOpsRemover
40 : public ForLikeEnzymeOpsRemover<ForOpEnzymeOpsRemover, scf::ForOp> {
41public:
42 // TODO: support non constant number of iteration by using unknown dimensions
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();
48
49 IntegerAttr lbAttr, ubAttr, stepAttr;
50 if (!matchPattern(lb, m_Constant(&lbAttr)))
51 return std::nullopt;
52 if (!matchPattern(ub, m_Constant(&ubAttr)))
53 return std::nullopt;
54 if (!matchPattern(step, m_Constant(&stepAttr)))
55 return std::nullopt;
56
57 int64_t lbI = lbAttr.getInt(), ubI = ubAttr.getInt(),
58 stepI = stepAttr.getInt();
59
60 return (ubI - lbI) / stepI;
61 }
62
63 static SmallVector<IntOrValue, 1> getDimensionBounds(OpBuilder &builder,
64 scf::ForOp forOp) {
65 auto iters = getConstantNumberOfIterations(forOp);
66 if (iters) {
67 return {IntOrValue(*iters)};
68 } else {
69 Value lb = forOp.getLowerBound(), ub = forOp.getUpperBound(),
70 step = forOp.getStep();
71 Value diff = arith::SubIOp::create(builder, forOp->getLoc(), ub, lb);
72 Value nSteps =
73 arith::DivUIOp::create(builder, forOp->getLoc(), diff, step);
74 return {IntOrValue(nSteps)};
75 }
76 }
77
78 static SmallVector<Value> getCanonicalLoopIVs(OpBuilder &builder,
79 scf::ForOp forOp) {
80
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());
85 }
86
87 if (!matchPattern(forOp.getStep(), m_One())) {
88 val = arith::DivUIOp::create(builder, forOp->getLoc(), val,
89 forOp.getStep());
90 }
91 return {val};
92 }
93
94 static IRMapping createArgumentMap(PatternRewriter &rewriter,
95 scf::ForOp forOp, ArrayRef<Value> indFor,
96 scf::ForOp otherForOp,
97 ArrayRef<Value> reversedOther) {
98 IRMapping map;
99 for (auto &&[f, o] : llvm::zip_equal(indFor, reversedOther)) {
100 map.map(f, o);
101 }
102
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()));
107
108 Location loc = forOp.getLoc();
109 // The reverse IV can be computed as (lb + ub - 1 - iv)
110 Value revIV =
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);
119 }
120 return map;
121 }
122
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);
129
130 // The rebuilt loop is the same loop with extra iteration arguments, so it
131 // keeps everything that was set on it. Without this, anything the caller
132 // put there -- enzyme.disable_mincut in particular -- is dropped the moment
133 // the removal pass needs to widen the loop.
134 newOtherForOp->setDiscardableAttrs(
135 otherForOp->getDiscardableAttrDictionary());
136
137 newOtherForOp.getRegion().takeBody(otherForOp.getRegion());
138 rewriter.replaceOp(otherForOp, newOtherForOp->getResults().slice(
139 0, otherForOp->getNumResults()));
140 return newOtherForOp;
141 }
142
143 static ValueRange getInits(scf::ForOp forOp) { return forOp.getInitArgs(); }
144
145 static bool mustPostAdd(scf::ForOp forOp) { return false; }
146
147 static Value initialValueInBlock(OpBuilder &builder, Block *body,
148 Value grad) {
149 auto Ty = cast<enzyme::GradientType>(grad.getType()).getBasetype();
150 return body->addArgument(Ty, grad.getLoc());
151 }
152};
153
154struct ForOpInterfaceReverse
155 : public ReverseAutoDiffOpInterface::ExternalModel<ForOpInterfaceReverse,
156 scf::ForOp>,
157 public LoopCheckpointing<ForOpInterfaceReverse, scf::ForOp> {
158 // ---- hooks required by LoopCheckpointing<ForOpInterfaceReverse, scf::ForOp>
159 // ----
160
161 static std::optional<int64_t>
162 getConstantNumberOfIterations(scf::ForOp forOp) {
163 return ForOpEnzymeOpsRemover::getConstantNumberOfIterations(forOp);
164 }
165
166 static Value materializeLowerBound(OpBuilder &, Location, scf::ForOp forOp,
167 MGradientUtilsReverse *gutils) {
168 return gutils->getNewFromOriginal(forOp.getLowerBound());
169 }
170
171 static Value materializeUpperBound(OpBuilder &, Location, scf::ForOp forOp,
172 MGradientUtilsReverse *gutils) {
173 return gutils->getNewFromOriginal(forOp.getUpperBound());
174 }
175
176 static Value materializeStep(OpBuilder &, Location, scf::ForOp forOp,
177 MGradientUtilsReverse *gutils) {
178 return gutils->getNewFromOriginal(forOp.getStep());
179 }
180
181 static int64_t getConstantStart(scf::ForOp forOp) {
182 llvm::APInt v;
183 (void)matchPattern(forOp.getLowerBound(), m_ConstantInt(&v));
184 return v.getSExtValue();
185 }
186
187 static int64_t getConstantStep(scf::ForOp forOp) {
188 llvm::APInt v;
189 (void)matchPattern(forOp.getStep(), m_ConstantInt(&v));
190 return v.getSExtValue();
191 }
192
193 static LogicalResult requireSingleResultBounds(scf::ForOp) {
194 return success();
195 }
196
197 static void cloneOp(OpBuilder &builder, Operation &op, IRMapping &mapping) {
198 builder.clone(op, mapping);
199 }
200
201 // ---- periodic-scaffold hooks (see LoopCheckpointing.h doc comment) ----
202 // All of these reproduce this file's pre-existing periodic-checkpointing
203 // formulas verbatim (including the known reverse-formula defect noted in
204 // the header) -- this is a pure refactor for scf.for, not a behavior
205 // change.
206
207 static scf::ForOp createConstantScaffoldLoop(OpBuilder &builder, Location loc,
208 int64_t lb, int64_t ub,
209 int64_t step, ValueRange inits) {
210 // Creation order (step, then ub, then lb): canonicalize's constant
211 // placement is sensitive to it, since constants aren't freely reordered,
212 // so keeping one order here is what keeps the two scaffolds' constants
213 // laid out the same way.
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);
218 }
219
220 // Both outer loops count segments: one iteration per checkpoint, so the trip
221 // count is the stated period. It stays a compile-time constant even when the
222 // trip count of the loop being differentiated is not -- that is what reading
223 // the period as a segment count buys -- which is what lets the checkpoint
224 // storage these loops push one entry to per iteration be sized statically.
225 static scf::ForOp createForwardOuterLoop(OpBuilder &builder, Location loc,
226 const PeriodicSchedule &sched,
227 ValueRange inits) {
228 return createConstantScaffoldLoop(builder, loc, 0, sched.numSegments(), 1,
229 inits);
230 }
231
232 static scf::ForOp createReverseOuterLoop(OpBuilder &builder, Location loc,
233 const PeriodicSchedule &sched,
234 ValueRange inits) {
235 return createConstantScaffoldLoop(builder, loc, 0, sched.numSegments(), 1,
236 inits);
237 }
238
239 // {segment base, segment length}, for the segment `outerIV` indexes. Both are
240 // emitted once here, at the top of the outer body, and handed back to the
241 // hooks that need them -- which is also what keeps the base from being
242 // computed twice, once for the segment loop's bound and once for the
243 // induction variable derived from it.
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)};
249 }
250
251 // The reverse replays the segments back to front, so its own counter names
252 // segment numSegments() - 1 - i. Same two values as the forward direction,
253 // from there on.
254 //
255 // numSegments() - 1, not nOuter: the two coincide only when there *is* a
256 // trailing segment. Without one (a period that divides the trip count, or a
257 // perfect-square sqrt split) nOuter - i names a segment one past the end, and
258 // the replayed segment's induction variable comes out shifted by a whole
259 // period -- silently, since the value is dead whenever the loop body does not
260 // read its induction variable, which is why no test caught it.
261 static SmallVector<Value>
262 computeReverseSegmentHint(OpBuilder &builder, Location loc, Value outerIV,
263 const PeriodicSchedule &sched) {
264 Value lastSegment =
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)};
269 }
270
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);
276 }
277
278 static scf::ForOp createForwardSegmentLoop(OpBuilder &builder, Location loc,
279 Value, ArrayRef<Value> fwdHint,
280 const PeriodicSchedule &,
281 ValueRange inits) {
282 return createSegmentLoop(builder, loc, fwdHint, inits);
283 }
284
285 static scf::ForOp createReverseSegmentLoop(OpBuilder &builder, Location loc,
286 Value, ArrayRef<Value> revHint,
287 const PeriodicSchedule &,
288 ValueRange inits) {
289 return createSegmentLoop(builder, loc, revHint, inits);
290 }
291
292 static scf::ForOp createLoopWithSameBounds(OpBuilder &builder, Location loc,
293 scf::ForOp templateLoop,
294 ValueRange inits) {
295 return scf::ForOp::create(builder, loc, templateLoop.getLowerBound(),
296 templateLoop.getUpperBound(),
297 templateLoop.getStep(), inits);
298 }
299
300 // start + step * (segment base + localIV): the original loop's own induction
301 // variable at the iteration this replay step stands for. Both directions
302 // count iterations from the same place, so both ask for it the same way.
303 static Value segmentIV(OpBuilder &builder, Location loc, scf::ForOp forOp,
304 const PeriodicSchedule &sched, ArrayRef<Value> hint,
305 Value localIV) {
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));
312 }
313 Value startCst =
314 arith::ConstantIndexOp::create(builder, loc, getConstantStart(forOp));
315 Value stepCst =
316 arith::ConstantIndexOp::create(builder, loc, getConstantStep(forOp));
317 return arith::AddIOp::create(
318 builder, loc, arith::MulIOp::create(builder, loc, flatIV, stepCst),
319 startCst);
320 }
321
322 static Value computeForwardSegmentIV(OpBuilder &builder, Location loc,
323 scf::ForOp forOp, Value /*outerIV*/,
324 Value localIV,
325 const PeriodicSchedule &sched,
326 ArrayRef<Value> fwdHint) {
327 return segmentIV(builder, loc, forOp, sched, fwdHint, localIV);
328 }
329
330 static Value computeReverseSegmentIV(OpBuilder &builder, Location loc,
331 scf::ForOp forOp, Value /*outerIV*/,
332 Value localIV,
333 const PeriodicSchedule &sched,
334 ArrayRef<Value> revHint) {
335 return segmentIV(builder, loc, forOp, sched, revHint, localIV);
336 }
337
338 static void createScaffoldYield(OpBuilder &builder, Location loc,
339 ValueRange operands) {
340 scf::YieldOp::create(builder, loc, operands);
341 }
342
343 // preserveAttributesButCheckpointing is inherited from LoopCheckpointing.
344
345public:
346 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
347 MGradientUtilsReverse *gutils,
348 SmallVector<Value> caches) const {
349 // SCF ForOp has 3 more operands than results (lb, ub, step).
350 // Its body has 1 more argument than yielded values (the induction
351 // variable).
352
353 auto forOp = cast<scf::ForOp>(op);
354 auto yieldOp = cast<scf::YieldOp>(forOp.getBody()->getTerminator());
355
356 SmallVector<bool> operandsActive(forOp.getNumOperands() - 3, false);
357 for (int i = 0, e = operandsActive.size(); i < e; ++i) {
358 operandsActive[i] = !gutils->isConstantValue(op->getOperand(i + 3)) ||
359 !gutils->isConstantValue(op->getResult(i)) ||
360 !gutils->isConstantValue(yieldOp.getOperand(i));
361 }
362
363 SmallVector<Value> incomingGradients;
364 for (auto &&[active, res] :
365 llvm::zip_equal(operandsActive, op->getResults())) {
366 if (active) {
367 incomingGradients.push_back(gutils->diffe(res, builder));
368 if (!gutils->isConstantValue(res))
369 gutils->zeroDiffe(res, builder);
370 }
371 }
372
373 if (auto r = tryCreateReverseModeAdjoint(forOp, op, builder, gutils, caches,
374 operandsActive, incomingGradients))
375 return *r;
376
377 auto start = gutils->popCache(caches[0], builder);
378 auto end = gutils->popCache(caches[1], builder);
379 auto step = gutils->popCache(caches[2], builder);
380
381 auto repFor = scf::ForOp::create(builder, forOp.getLoc(), start, end, step,
382 incomingGradients);
383 preserveAttributesButCheckpointing(repFor, forOp);
384
385 bool valid = true;
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());
390
391 // Create implicit terminator if not present (when num results > 0)
392 if (revBB.empty()) {
393 scf::YieldOp::create(bodyBuilder, repFor->getLoc());
394 }
395
396 bodyBuilder.setInsertionPointToStart(&revBB);
397 mlir::enzyme::localizeGradients(bodyBuilder, gutils, &oBB);
398
399 bodyBuilder.setInsertionPoint(revBB.getTerminator());
400
401 auto term = oBB.getTerminator();
402
403 for (auto &&[active, operand] :
404 llvm::zip_equal(operandsActive, term->getOperands())) {
405 if (active) {
406 // Zero the diffe at the start of each iteration because it should
407 // not accumulate across iterations. The new gradient is passed as
408 // an iter_arg in the reverse for.
409 gutils->zeroDiffe(operand, bodyBuilder);
410 }
411 }
412
413 unsigned argIdx = 1; // Skip over the reversed IV
414 for (auto &&[active, operand] :
415 llvm::zip_equal(operandsActive, term->getOperands())) {
416 if (active) {
417 // If the same value is yielded multiple times in the original, the
418 // gradients must be accumulated.
419 gutils->addToDiffe(operand, revBB.getArgument(argIdx), bodyBuilder);
420 argIdx++;
421 }
422 }
423
424 auto first = oBB.rbegin();
425 first++; // skip terminator
426
427 auto last = oBB.rend();
428
429 for (auto it = first; it != last; ++it) {
430 Operation *op = &*it;
431 valid &=
432 gutils->Logic.visitChild(op, bodyBuilder, gutils).succeeded();
433 }
434
435 SmallVector<Value> newResults;
436 newResults.reserve(incomingGradients.size());
437
438 for (auto &&[active, arg] :
439 llvm::zip_equal(operandsActive, oBB.getArguments().slice(1))) {
440 if (active) {
441 newResults.push_back(gutils->diffe(arg, bodyBuilder));
442 if (!gutils->isConstantValue(arg))
443 gutils->zeroDiffe(arg, bodyBuilder);
444 }
445 }
446
447 // yield new gradient values
448 revBB.getTerminator()->setOperands(newResults);
449 }
450 }
451
452 unsigned resIdx = 0;
453 for (auto &&[active, arg] :
454 llvm::zip_equal(operandsActive, forOp.getInitArgs())) {
455 if (active) {
456 if (!gutils->isConstantValue(arg)) {
457 gutils->addToDiffe(arg, repFor.getResult(resIdx), builder);
458 resIdx++;
459 }
460 }
461 }
462
463 return success(valid);
464 }
465
466 SmallVector<Value> cacheValues(Operation *op,
467 MGradientUtilsReverse *gutils) const {
468 auto forOp = cast<scf::ForOp>(op);
469
470 if (auto r = tryCacheValues(forOp, op, gutils))
471 return *r;
472
473 Operation *newOp = gutils->getNewFromOriginal(op);
474 OpBuilder cacheBuilder(newOp);
475
476 SmallVector<Value> caches;
477
478 Value cacheLB = gutils->initAndPushCache(
479 gutils->getNewFromOriginal(forOp.getLowerBound()), cacheBuilder);
480 caches.push_back(cacheLB);
481
482 Value cacheUB = gutils->initAndPushCache(
483 gutils->getNewFromOriginal(forOp.getUpperBound()), cacheBuilder);
484 caches.push_back(cacheUB);
485
486 Value cacheStep = gutils->initAndPushCache(
487 gutils->getNewFromOriginal(forOp.getStep()), cacheBuilder);
488 caches.push_back(cacheStep);
489
490 return caches;
491 }
492
493 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
494 MGradientUtilsReverse *gutils) const {
495 // auto forOp = cast<scf::ForOp>(op);
496 return success();
497 }
498};
499
500struct ParallelOpEnzymeOpsRemover
501 : public ForLikeEnzymeOpsRemover<ParallelOpEnzymeOpsRemover,
502 scf::ParallelOp> {
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)))
507 return std::nullopt;
508 if (!matchPattern(ub, m_Constant(&ubAttr)))
509 return std::nullopt;
510 if (!matchPattern(step, m_Constant(&stepAttr)))
511 return std::nullopt;
512
513 int64_t lbI = lbAttr.getInt(), ubI = ubAttr.getInt(),
514 stepI = stepAttr.getInt();
515 return (ubI - lbI) / stepI;
516 }
517
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);
525 if (iters) {
526 bounds.push_back(IntOrValue(*iters));
527 } else {
528 Value diff = arith::SubIOp::create(builder, parOp.getLoc(), ub, lb);
529 Value nSteps =
530 arith::DivUIOp::create(builder, parOp.getLoc(), diff, step);
531 bounds.push_back(IntOrValue(nSteps));
532 }
533 }
534 return bounds;
535 }
536
537 static SmallVector<Value>
538 computeReversedIndices(PatternRewriter &rewriter, scf::ParallelOp parOp,
539 ArrayRef<Value> otherInductionVariable,
540 ArrayRef<IntOrValue> bounds) {
541 return SmallVector<Value>(otherInductionVariable);
542 }
543
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(),
550 parOp.getStep())) {
551 Value val = iv;
552 if (!matchPattern(lb, m_Zero())) {
553 val = arith::SubIOp::create(builder, parOp.getLoc(), val, lb);
554 }
555
556 if (!matchPattern(step, m_One())) {
557 val = arith::DivUIOp::create(builder, parOp.getLoc(), val, step);
558 }
559 canonicalIVs.push_back(val);
560 }
561 return canonicalIVs;
562 }
563
564 static IRMapping createArgumentMap(PatternRewriter &rewriter,
565 scf::ParallelOp parOp,
566 ArrayRef<Value> indPar,
567 scf::ParallelOp otherParOp,
568 ArrayRef<Value> indOther) {
569 IRMapping map;
570 for (auto &&[f, o] : llvm::zip_equal(indPar, indOther))
571 map.map(f, o);
572
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));
580 map.map(iv, oiv);
581 }
582 }
583 return map;
584 }
585
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);
592
593 newOtherParOp->setDiscardableAttrs(
594 otherParallelOp->getDiscardableAttrDictionary());
595
596 newOtherParOp.getRegion().takeBody(otherParallelOp.getRegion());
597 rewriter.replaceOp(
598 otherParallelOp,
599 newOtherParOp.getResults().slice(0, otherParallelOp.getNumResults()));
600
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());
607
608 for (auto [reg, operand] :
609 llvm::zip_equal(term->getRegions(), operands)) {
610 Block *b = &reg.front();
611 rewriter.setInsertionPointToEnd(b);
612
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);
617 }
618
619 oldTerm->erase();
620 }
621
622 return newOtherParOp;
623 }
624
625 static ValueRange getInits(scf::ParallelOp parallelOp) {
626 return parallelOp.getInitVals();
627 }
628
629 static bool mustPostAdd(scf::ParallelOp forOp) { return false; }
630
631 static Value initialValueInBlock(OpBuilder &builder, Block *body,
632 Value grad) {
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());
638 }
639};
640
641struct ParallelOpInterfaceReverse
642 : public ReverseAutoDiffOpInterface::ExternalModel<
643 ParallelOpInterfaceReverse, scf::ParallelOp> {
644 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
645 MGradientUtilsReverse *gutils,
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";
651 }
652
653 unsigned loopCount = parallelOp.getNumLoops();
654 SmallVector<Value> bounds = llvm::map_to_vector(
655 caches, [&](Value cache) { return gutils->popCache(cache, builder); });
656
657 auto revPar = scf::ParallelOp::create(
658 builder, op->getLoc(),
659 /*lowerBounds=*/ValueRange(bounds).slice(0, loopCount),
660 /*upperBounds=*/ValueRange(bounds).slice(loopCount, loopCount),
661 /*steps=*/ValueRange(bounds).slice(loopCount * 2, loopCount));
662
663 bool valid = true;
664 bool wasAtomic = gutils->AtomicAdd;
665 gutils->AtomicAdd = true;
666
667 {
668 Block *oBB = parallelOp.getBody();
669 Block *revBB = revPar.getBody();
670
671 OpBuilder bodyBuilder(revBB, revBB->end());
672
673 bodyBuilder.setInsertionPointToStart(revBB);
674 mlir::enzyme::localizeGradients(bodyBuilder, gutils, oBB);
675
676 bodyBuilder.setInsertionPoint(revBB->getTerminator());
677
678 auto first = oBB->rbegin();
679 first++; // skip terminator
680
681 auto last = oBB->rend();
682
683 for (auto it = first; it != last; ++it) {
684 Operation *op = &*it;
685 valid &= gutils->Logic.visitChild(op, bodyBuilder, gutils).succeeded();
686 }
687 }
688
689 gutils->AtomicAdd = wasAtomic;
690 return success(valid);
691 }
692
693 SmallVector<Value> cacheValues(Operation *op,
694 MGradientUtilsReverse *gutils) const {
695 auto parallelOp = cast<scf::ParallelOp>(op);
696 Operation *newOp = gutils->getNewFromOriginal(op);
697 OpBuilder cacheBuilder(newOp);
698 SmallVector<Value> caches;
699 for (Value lb : parallelOp.getLowerBound())
700 caches.push_back(gutils->initAndPushCache(gutils->getNewFromOriginal(lb),
701 cacheBuilder));
702 for (Value ub : parallelOp.getUpperBound())
703 caches.push_back(gutils->initAndPushCache(gutils->getNewFromOriginal(ub),
704 cacheBuilder));
705 for (Value step : parallelOp.getStep())
706 caches.push_back(gutils->initAndPushCache(
707 gutils->getNewFromOriginal(step), cacheBuilder));
708
709 return caches;
710 }
711
712 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
713 MGradientUtilsReverse *gutils) const {
714 return success();
715 }
716};
717
718struct IfOpEnzymeOpsRemover
719 : public IfLikeEnzymeOpsRemover<IfOpEnzymeOpsRemover, scf::IfOp> {
720 static Block *getThenBlock(scf::IfOp ifOp, OpBuilder &builder) {
721 return ifOp.thenBlock();
722 }
723
724 static Block *getElseBlock(scf::IfOp ifOp, OpBuilder &builder) {
725 // Ensure the if has an else block
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());
731 }
732
733 return ifOp.elseBlock();
734 }
735
736 static Value getDummyValue(OpBuilder &builder, Location loc, Type dummyType) {
737 return cast<AutoDiffTypeInterface>(dummyType).createNullValue(builder, loc);
738 }
739
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());
744
745 newIf.getThenRegion().takeBody(otherIfOp.getThenRegion());
746 newIf.getElseRegion().takeBody(otherIfOp.getElseRegion());
747
748 rewriter.replaceAllUsesWith(
749 otherIfOp->getResults(),
750 newIf->getResults().slice(0, otherIfOp->getNumResults()));
751 rewriter.eraseOp(otherIfOp);
752 return newIf;
753 }
754};
755
756struct IfOpInterfaceReverse
757 : public ReverseAutoDiffOpInterface::ExternalModel<IfOpInterfaceReverse,
758 scf::IfOp> {
759 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
760 MGradientUtilsReverse *gutils,
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);
765
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();
771 resultsActive[i] = needsGrad && !gutils->isConstantValue(result);
772 }
773
774 SmallVector<Value> incomingGradients;
775 for (auto &&[active, res] :
776 llvm::zip_equal(resultsActive, ifOp.getResults())) {
777 if (active) {
778 incomingGradients.push_back(gutils->diffe(res, builder));
779 if (!gutils->isConstantValue(res))
780 gutils->zeroDiffe(res, builder);
781 }
782 }
783
784 auto revIf =
785 scf::IfOp::create(builder, ifOp.getLoc(), TypeRange{}, cond, hasElse);
786 bool valid = true;
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());
792
793 // All values defined in the body should have no use outside this
794 // block therefore we can set their diffe to zero upon entering the
795 // reverse block to simplify the work of the
796 // remove-unnecessary-enzyme-ops pass.
797 for (auto &it : oBB.getOperations()) {
798 for (auto res : it.getResults()) {
799 if (!gutils->isConstantValue(res)) {
800 auto iface = dyn_cast<AutoDiffTypeInterface>(res.getType());
801 if (iface && !iface.isMutable())
802 gutils->zeroDiffe(res, bodyBuilder);
803 }
804 }
805 }
806
807 auto term = oBB.getTerminator();
808 // Align incomingGradients with their corresponding yield operands.
809 SmallVector<Value> activeTermOperands;
810 activeTermOperands.reserve(incomingGradients.size());
811 for (auto &&[resultActive, operand] :
812 llvm::zip_equal(resultsActive, term->getOperands())) {
813 if (resultActive)
814 activeTermOperands.push_back(operand);
815 }
816
817 for (auto &&[arg, operand] :
818 llvm::zip_equal(incomingGradients, activeTermOperands)) {
819 // Check activity of the argument separately from the result. If
820 // some branches yield inactive values while others yield active
821 // values, the result will be active, but this operand may still be
822 // inactive (and we cannot addToDiffe)
823 if (!gutils->isConstantValue(operand)) {
824 gutils->addToDiffe(operand, arg, bodyBuilder);
825 }
826 }
827
828 auto first = oBB.rbegin();
829 first++; // skip terminator
830
831 auto last = oBB.rend();
832
833 for (auto it = first; it != last; ++it) {
834 Operation *op = &*it;
835 valid &=
836 gutils->Logic.visitChild(op, bodyBuilder, gutils).succeeded();
837 }
838 }
839 }
840 return success(valid);
841 }
842
843 SmallVector<Value> cacheValues(Operation *op,
844 MGradientUtilsReverse *gutils) const {
845 auto ifOp = cast<scf::IfOp>(op);
846
847 Operation *newOp = gutils->getNewFromOriginal(op);
848 OpBuilder cacheBuilder(newOp);
849 Value cacheCond = gutils->initAndPushCache(
850 gutils->getNewFromOriginal(ifOp.getCondition()), cacheBuilder);
851 return SmallVector<Value>{cacheCond};
852 }
853
854 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
855 MGradientUtilsReverse *gutils) const {
856 // TODO: consider making this generic for RegionBranchOpInterface
857 auto ifOp = cast<scf::IfOp>(op);
858 if (ifOp.getNumResults() == 0)
859 return success();
860
861 auto newIf = cast<scf::IfOp>(gutils->getNewFromOriginal(ifOp));
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());
867 if (iface && iface.isMutable() && !gutils->isConstantValue(result)) {
868 newResultTypes.push_back(result.getType());
869 needsShadow[result.getResultNumber()] = true;
870 } else {
871 needsShadow[result.getResultNumber()] = false;
872 }
873 }
874
875 // Replace the new op with an augmented op
876 auto augmentedOp =
877 scf::IfOp::create(builder, op->getLoc(), newResultTypes,
878 gutils->getNewFromOriginal(ifOp.getCondition()),
879 /*withElseRegion=*/true);
880
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();
888
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(
897 gutils->invertPointerM(oldOperand.get(), builder));
898 }
899 }
900
901 scf::YieldOp::create(builder, oldYield->getLoc(), newOperands);
902 augYield->erase();
903 }
904 }
905
906 // Determine which returns correspond to the primal
907 SmallVector<Value> augmentedResults;
908 unsigned resIdx = 0;
909 for (auto res : ifOp.getResults()) {
910 augmentedResults.push_back(augmentedOp.getResult(resIdx));
911 resIdx++;
912 if (needsShadow[res.getResultNumber()]) {
913 gutils->setInvertedPointer(res, augmentedOp.getResult(resIdx));
914 resIdx++;
915 }
916 }
917 newIf.replaceAllUsesWith(augmentedResults);
918 newIf.erase();
919 return success();
920 }
921};
922
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);
928 return {
929 forOp->getOperand(res.getResultNumber() + 3),
930 forOp.getBody()->getTerminator()->getOperand(res.getResultNumber())};
931 }
932 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
933 BlockArgument arg) const {
934 auto forOp = cast<scf::ForOp>(op);
935 if (arg.getArgNumber() < forOp.getNumInductionVars())
936 return {};
937 auto idx = arg.getArgNumber() - forOp.getNumInductionVars();
938 return {forOp->getOperand(idx + 3),
939 forOp.getBody()->getTerminator()->getOperand(idx)};
940 }
941 SmallVector<Value> getPotentialTerminatorUsers(Operation *op, Operation *term,
942 Value val) const {
943 auto forOp = cast<scf::ForOp>(op);
944 SmallVector<Value> sv;
945
946 for (auto &&[res, arg, barg] :
947 llvm::zip_equal(forOp->getResults(), term->getOperands(),
948 forOp.getRegionIterArgs())) {
949 if (arg == val) {
950 sv.push_back(res);
951 sv.push_back(barg);
952 }
953 }
954
955 return sv;
956 }
957};
958
959struct ParallelOpADDataFlow
960 : public ADDataFlowOpInterface::ExternalModel<ParallelOpADDataFlow,
961 scf::ParallelOp> {
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),
970 parOp.getBody()
971 ->getTerminator()
972 ->getRegion(res.getResultNumber())
973 .front()
974 .getTerminator()
975 ->getOperand(0)};
976 }
977 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
978 BlockArgument arg) const {
979 // TO DO: do we need this?
980 assert(0);
981 return SmallVector<Value>();
982 }
983 SmallVector<Value> getPotentialTerminatorUsers(Operation *op, Operation *term,
984 Value val) const {
985 SmallVector<Value> sv;
986
987 for (auto [idx, arg] : llvm::enumerate(term->getOperands())) {
988 if (arg == val) {
989 sv.push_back(term->getRegion(idx).front().getArgument(0));
990 }
991 }
992
993 return sv;
994 }
995};
996
997struct ReduceOpADDataFlow
998 : public ADDataFlowOpInterface::ExternalModel<ReduceOpADDataFlow,
999 scf::ReduceOp> {
1000 SmallVector<Value> getPotentialIncomingValuesRes(Operation *op,
1001 OpResult res) const {
1002 // ReduceOp's have no results
1003 return SmallVector<Value>();
1004 }
1005 SmallVector<Value> getPotentialIncomingValuesArg(Operation *op,
1006 BlockArgument arg) const {
1007 // The op here is the parent of the block, which is a ReduceOp
1008 // All but the last block arguments match up with the corresponding operand
1009 // of the reduce op. The last matches up with terminator operand as well as
1010 // the initial value. If this is the ith block, it is the ith initial value
1011
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)};
1024 } else {
1025 return {redOp->getOperand(region_idx)};
1026 }
1027 }
1028 SmallVector<Value> getPotentialTerminatorUsers(Operation *op, Operation *term,
1029 Value val) const {
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();
1034
1035 return {parOp->getResult(region_idx), ownerBlock->getArgument(1)};
1036 }
1037};
1038
1039class SCFReduceAutoDiffOpInterface
1040 : public AutoDiffOpInterface::ExternalModel<SCFReduceAutoDiffOpInterface,
1041 scf::ReduceOp> {
1042public:
1043 LogicalResult createForwardModeTangent(Operation *origTerminator,
1044 OpBuilder &builder,
1045 MGradientUtils *gutils) const {
1046 auto parentOp = origTerminator->getParentOp();
1047 if (!isa<scf::ParallelOp>(parentOp)) {
1048 origTerminator->emitError()
1049 << " createForwardModeTangent called with invalid parent" << *parentOp
1050 << "\n";
1051 return failure();
1052 }
1053
1054 // Note, this works for scf::ReduceOp because it has the same number of
1055 // operands as the parent (scf::ParallelOp) has results
1056 assert(parentOp->getNumResults() == origTerminator->getNumOperands());
1057 llvm::SmallDenseSet<unsigned> operandsToShadow;
1058 for (auto res : parentOp->getResults()) {
1059 if (!gutils->isConstantValue(res))
1060 operandsToShadow.insert(res.getResultNumber());
1061 }
1062
1063 SmallVector<Value> newOperands;
1064 newOperands.reserve(origTerminator->getNumOperands() +
1065 operandsToShadow.size());
1066 for (OpOperand &operand : origTerminator->getOpOperands()) {
1067 newOperands.push_back(gutils->getNewFromOriginal(operand.get()));
1068 if (operandsToShadow.contains(operand.getOperandNumber()))
1069 newOperands.push_back(gutils->invertPointerM(operand.get(), builder));
1070 }
1071
1072 // Assuming shadows following the originals are fine.
1073 // TODO: consider extending to have a ShadowableTerminatorOpInterface
1074 Operation *replTerminator = gutils->getNewFromOriginal(origTerminator);
1075 replTerminator->setOperands(newOperands);
1076
1077 // Differentiate the body of the reducer
1078 for (auto &origRegion : origTerminator->getRegions()) {
1079 for (auto &origBlock : origRegion) {
1080 for (Operation &o : origBlock) {
1081 if (failed(gutils->visitChild(&o))) {
1082 replTerminator->emitError() << " Differentiating reducer block "
1083 << *replTerminator << " failed!\n";
1084 }
1085 }
1086 }
1087 }
1088
1089 // Delete the primal operations in each differentiated reducer block by
1090 // building a map of the operations that are ultimately used by starting
1091 // from the shadow operands of the terminator (scf::ReduceReturnOp). Then
1092 // erase all of the operations that aren't used. Note that from above, all
1093 // operands for the terminator are shadow operands.
1094 for (auto &region : replTerminator->getRegions()) {
1095 for (auto &block : region) {
1096 std::map<Operation *, bool> used;
1097 std::vector<Operation *> op_list;
1098
1099 // Initialize all operations as not used
1100 for (Operation &o : block) {
1101 used[&o] = false;
1102 op_list.push_back(&o);
1103 }
1104
1105 // Recursively mark operations that are used starting from the
1106 // terminator
1107 auto mark_used = [&used](const auto &self, Operation *op) -> void {
1108 if (op != nullptr) {
1109 assert(used.find(op) != used.end());
1110 used[op] = true;
1111 for (auto v : op->getOperands())
1112 self(self, v.getDefiningOp());
1113 }
1114 };
1115 mark_used(mark_used, block.getTerminator());
1116
1117 // Delete the unused operations squentially, starting from the last so
1118 // that all users of an operation are erased before the operation itself
1119 for (auto it = op_list.rbegin(); it != op_list.rend(); ++it) {
1120 if (!used[*it]) {
1121 (*it)->erase();
1122 }
1123 }
1124
1125 // Delete the primal arguments from the block. We have to go backwards
1126 // starting from the second-to-last as the args will shift forward after
1127 // erasing.
1128 for (int i = block.getNumArguments() - 2; i >= 0; i -= 2) {
1129 block.eraseArgument(i);
1130 }
1131 }
1132 }
1133
1134 // Create a new terminator combining the regions of differentiated and
1135 // original terminators. We clone the original region so that it still
1136 // exists for the undifferentiated reducer but we can take the region from
1137 // the originally differentiated one because we delete it later
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));
1149 }
1150 Operation *new_terminator_op = term_builder.create(state);
1151 gutils->erase(replTerminator);
1152 gutils->originalToNewFnOps[origTerminator] = new_terminator_op;
1153
1154 return success();
1155 }
1156};
1157
1158class SCFReduceReturnAutoDiffOpInterface
1159 : public AutoDiffOpInterface::ExternalModel<
1160 SCFReduceReturnAutoDiffOpInterface, scf::ReduceReturnOp> {
1161public:
1162 LogicalResult createForwardModeTangent(Operation *origTerminator,
1163 OpBuilder &builder,
1164 MGradientUtils *gutils) const {
1165 auto parentOp = origTerminator->getParentOp();
1166 if (!isa<scf::ReduceOp>(parentOp)) {
1167 origTerminator->emitError()
1168 << " createForwardModeTangent called with invalid parent" << *parentOp
1169 << "\n";
1170 return failure();
1171 }
1172
1173 // ReduceOp has no direct results, instead the result of the ith reducer
1174 // block within the ReduceOp matches up with the ith result of the parent
1175 // ParallelOp of the ReduceOp. Therefore the terminator must have exactly 1
1176 // operand and we will shadow it
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;
1182 if (!gutils->isConstantValue(
1183 parentOp->getParentOp()->getResult(reducer_index)))
1184 operandsToShadow.insert(0);
1185
1186 // For scf::ReduceReturnOp only add the
1187 // shadows as operands since the primal reducer will be in a different
1188 // region with its own scf::ReduceReturnOp
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));
1194 }
1195
1196 // Special handling for scf::ReduceOp where the assumption that shadows
1197 // follow originals is violated. Here the shadow operations need to be put
1198 // in a shadow region. It isn't clear how to do that directly, so instead
1199 // we will create the shadows as normal and then create a new scf::ReduceOp
1200 // terminator that combines the regions from the original and
1201 // differentiated. We then erase the primal operations from the derivative
1202 // reducer region(s).
1203 Operation *replTerminator = gutils->getNewFromOriginal(origTerminator);
1204 replTerminator->setOperands(newOperands);
1205
1206 return success();
1207 }
1208};
1209
1210} // namespace
1211
1213 DialectRegistry &registry) {
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>(
1224 *context);
1225 scf::ForOp::attachInterface<ForOpInterfaceReverse>(*context);
1226 scf::ForOp::attachInterface<ForOpEnzymeOpsRemover>(*context);
1227 scf::ForOp::attachInterface<ForOpADDataFlow>(*context);
1228 });
1229}
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 &registry)