17#include "mlir/Dialect/Func/IR/FuncOps.h"
18#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
19#include "mlir/Dialect/MemRef/IR/MemRef.h"
20#include "mlir/IR/Matchers.h"
21#include "mlir/Interfaces/FunctionInterfaces.h"
22#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
23#include "mlir/Transforms/RegionUtils.h"
24#include "llvm/ADT/TypeSwitch.h"
30#define GEN_PASS_DEF_INLINEENZYMEINTOREGIONPASS
31#define GEN_PASS_DEF_OUTLINEENZYMEFROMREGIONPASS
32#include "Passes/Passes.h.inc"
37constexpr static llvm::StringLiteral kFnAttrsName =
"fn_attrs";
39static StringRef getFunctionTypeAttrName(Operation *operation) {
40 if (
auto iface = dyn_cast<mlir::enzyme::AutoDiffFunctionInterface>(operation))
41 return iface.getFunctionTypeAttrName();
45static StringRef getArgAttrsAttrName(Operation *operation) {
46 if (
auto iface = dyn_cast<mlir::enzyme::AutoDiffFunctionInterface>(operation))
47 return iface.getArgAttrsAttrName();
51static void serializeFunctionAttributes(Operation *fn, Operation *regionOp) {
52 SmallVector<NamedAttribute> fnAttrs;
53 fnAttrs.reserve(fn->getAttrDictionary().size());
54 for (
auto attr : fn->getAttrs()) {
57 if (attr.getName() == getFunctionTypeAttrName(fn) ||
58 attr.getName() == SymbolTable::getSymbolAttrName())
60 fnAttrs.push_back(attr);
63 regionOp->setAttr(kFnAttrsName,
64 DictionaryAttr::getWithSorted(fn->getContext(), fnAttrs));
67template <
typename DiffRegionOp>
68static void deserializeFunctionAttributes(DiffRegionOp op,
69 Operation *outlinedFunc,
70 unsigned addedArgCount) {
71 if (!op->template hasAttrOfType<DictionaryAttr>(kFnAttrsName))
74 MLIRContext *ctx = op->getContext();
75 SmallVector<NamedAttribute> fnAttrs;
76 for (
auto attr : op->template getAttrOfType<DictionaryAttr>(kFnAttrsName)) {
80 if (attr.getName() == getArgAttrsAttrName(outlinedFunc)) {
81 SmallVector<Attribute> argAttrs(
82 cast<ArrayAttr>(attr.getValue())
83 .template getAsRange<DictionaryAttr>());
84 for (
unsigned i = 0; i < addedArgCount; ++i)
85 argAttrs.push_back(DictionaryAttr::getWithSorted(ctx, {}));
87 NamedAttribute(attr.getName(), ArrayAttr::get(ctx, argAttrs)));
89 fnAttrs.push_back(attr);
91 outlinedFunc->setAttrs(fnAttrs);
96 LogicalResult matchAndRewrite(enzyme::AutoDiffOp op,
97 PatternRewriter &rewriter)
const override {
98 SymbolTableCollection symbolTable;
106struct InlineEnzymeForwardDiff
109 LogicalResult matchAndRewrite(enzyme::ForwardDiffOp op,
110 PatternRewriter &rewriter)
const override {
111 SymbolTableCollection symbolTable;
112 FunctionOpInterface fn = dyn_cast_or_null<FunctionOpInterface>(
113 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
118 Region &targetRegion = fn.getFunctionBody();
120 if (targetRegion.empty())
125 auto fnAttr = StringAttr::get(op.getContext(), op.getFn());
126 auto regionOp = rewriter.replaceOpWithNewOp<enzyme::ForwardDiffRegionOp>(
127 op, op.getResultTypes(), op.getInputs(), op.getActivity(),
128 op.getRetActivity(), op.getWidth(), op.getStrongZero(), fnAttr);
130 serializeFunctionAttributes(fn, regionOp);
131 rewriter.cloneRegionBefore(targetRegion, regionOp.getBody(),
132 regionOp.getBody().begin());
134 SmallVector<Operation *> toErase;
135 for (Operation &bodyOp : regionOp.getBody().getOps()) {
136 if (bodyOp.hasTrait<OpTrait::ReturnLike>()) {
137 PatternRewriter::InsertionGuard insertionGuard(rewriter);
138 rewriter.setInsertionPoint(&bodyOp);
139 enzyme::YieldOp::create(rewriter, bodyOp.getLoc(),
140 bodyOp.getOperands());
141 toErase.push_back(&bodyOp);
145 for (Operation *opToErase : toErase)
146 rewriter.eraseOp(opToErase);
157static bool isLikelyAnIndexComputation(Operation *op) {
158 return matchPattern(op, m_Constant()) ||
159 isa<memref::DimOp, arith::SelectOp, arith::CmpIOp>(op);
173static bool extractBeneficiaryOps(
174 Operation *op,
const SetVector<Value> &existingDependencies,
175 SetVector<Operation *> &beneficiaryOps,
176 llvm::SmallPtrSetImpl<Value> &availableValues,
177 llvm::function_ref<
bool(Operation *)> isSinkingBeneficiary) {
178 if (beneficiaryOps.count(op))
181 if (!isSinkingBeneficiary(op))
184 for (Value operand : op->getOperands()) {
186 if (availableValues.count(operand))
190 Operation *definingOp = operand.getDefiningOp();
191 if ((!definingOp || !extractBeneficiaryOps(definingOp, existingDependencies,
192 beneficiaryOps, availableValues,
193 isSinkingBeneficiary)) &&
194 !existingDependencies.count(operand))
198 beneficiaryOps.insert(op);
199 for (Value result : op->getResults())
200 availableValues.insert(result);
204template <
typename DiffRegionOp>
205static LogicalResult sinkOperationsIntoRegionOp(
206 DiffRegionOp regionOp,
207 function_ref<
bool(Operation *)> isSinkingBeneficiary) {
208 assert(isSinkingBeneficiary);
209 Region &body = regionOp.getBody();
211 SetVector<Value> sinkCandidates;
212 getUsedValuesDefinedAbove(body, sinkCandidates);
214 SetVector<Operation *> toBeSunk;
215 llvm::SmallPtrSet<Value, 4> availableValues;
216 for (Value operand : sinkCandidates) {
217 Operation *operandOp = operand.getDefiningOp();
220 extractBeneficiaryOps(operandOp, sinkCandidates, toBeSunk, availableValues,
221 isSinkingBeneficiary);
226 OpBuilder builder(body);
227 for (Operation *op : toBeSunk) {
228 Operation *clonedOp = builder.clone(*op, map);
230 for (
auto pair : llvm::zip(op->getResults(), clonedOp->getResults()))
231 replaceAllUsesInRegionWith(std::get<0>(pair), std::get<1>(pair), body);
236template <
typename DiffRegionOp>
237static FailureOr<func::FuncOp> outlineAutoDiffFunc(
238 DiffRegionOp op, StringRef funcName, SmallVectorImpl<Value> &inputs,
239 SmallVectorImpl<enzyme::Activity> &argActivities, OpBuilder &builder) {
240 Region &autodiffRegion = op.getBody();
241 if (failed(sinkOperationsIntoRegionOp(op, isLikelyAnIndexComputation))) {
245 SmallVector<Type> argTypes(autodiffRegion.getArgumentTypes()), resultTypes;
246 SmallVector<Location> argLocs(autodiffRegion.getNumArguments(), op.getLoc());
249 autodiffRegion.walk([&](enzyme::YieldOp yieldOp) {
251 llvm::append_range(resultTypes, yieldOp.getOperandTypes());
252 return WalkResult::interrupt();
255 return op.emitError()
256 <<
"enzyme.yield was not found in enzyme.autodiff_region";
258 llvm::SetVector<Value> freeValues;
259 getUsedValuesDefinedAbove(autodiffRegion, freeValues);
261 llvm::SmallVector<Value> primalValuesAbove = op.getPrimalInputs();
262 llvm::SmallVector<Value> blockArgs(autodiffRegion.getArguments());
263 for (Value value : llvm::make_early_inc_range(freeValues)) {
264 bool isPrimal =
false;
265 for (
auto [pval, bval] : llvm::zip(primalValuesAbove, blockArgs)) {
268 for (OpOperand &use : llvm::make_early_inc_range(value.getUses())) {
269 if (op->isProperAncestor(use.getOwner()))
272 freeValues.remove(value);
277 inputs.push_back(value);
278 argTypes.push_back(value.getType());
279 argLocs.push_back(value.getLoc());
280 argActivities.push_back(enzyme::Activity::enzyme_const);
283 auto fnType = builder.getFunctionType(argTypes, resultTypes);
288 Location loc = UnknownLoc::get(op.getContext());
289 auto outlinedFunc = func::FuncOp::create(builder, loc, funcName, fnType);
290 outlinedFunc.setPrivate();
291 Region &outlinedBody = outlinedFunc.getBody();
292 deserializeFunctionAttributes(op, outlinedFunc, freeValues.size());
296 Block *entryBlock = builder.createBlock(&outlinedBody, outlinedBody.begin(),
298 unsigned originalArgCount = autodiffRegion.getNumArguments();
299 for (
const auto &arg : autodiffRegion.getArguments())
300 map.map(arg, entryBlock->getArgument(arg.getArgNumber()));
301 for (
const auto &operand : enumerate(freeValues))
302 map.map(operand.value(),
303 entryBlock->getArgument(originalArgCount + operand.index()));
304 autodiffRegion.cloneInto(&outlinedBody, map);
307 for (Block &block : autodiffRegion) {
308 Block *clonedBlock = map.lookup(&block);
309 auto terminator = dyn_cast<enzyme::YieldOp>(clonedBlock->getTerminator());
312 OpBuilder replacer(terminator);
313 func::ReturnOp::create(replacer, terminator->getLoc(),
314 terminator->getOperands());
321 Block *clonedEntry = map.lookup(&autodiffRegion.front());
322 entryBlock->getOperations().splice(entryBlock->getOperations().end(),
323 clonedEntry->getOperations());
324 clonedEntry->erase();
328LogicalResult outlineEnzymeAutoDiffRegion(enzyme::AutoDiffRegionOp op,
330 OpBuilder &builder) {
331 OpBuilder::InsertionGuard insertionGuard(builder);
332 builder.setInsertionPointAfter(op->getParentOfType<SymbolOpInterface>());
334 SmallVector<enzyme::Activity> argActivities =
335 llvm::map_to_vector(op.getActivity().getAsRange<enzyme::ActivityAttr>(),
336 [](
auto attr) { return attr.getValue(); });
337 size_t numSeeds = llvm::count_if(
338 op.getRetActivity().getAsRange<enzyme::ActivityAttr>(), [](
auto attr) {
339 switch (attr.getValue()) {
340 case enzyme::Activity::enzyme_active:
341 case enzyme::Activity::enzyme_activenoneed:
347 size_t numPrimalsAndShadows = op.getInputs().size() - numSeeds;
348 SmallVector<Value> primalsAndShadows(
349 op.getInputs().begin(), op.getInputs().begin() + numPrimalsAndShadows);
350 SmallVector<Value> seedInputs(op.getInputs().begin() + numPrimalsAndShadows,
351 op.getInputs().end());
358 FailureOr<func::FuncOp> outlinedFunc = outlineAutoDiffFunc(
359 op, funcName, primalsAndShadows, argActivities, builder);
360 if (failed(outlinedFunc))
363 SmallVector<Value> allInputs;
364 allInputs.append(primalsAndShadows);
365 allInputs.append(seedInputs);
367 builder.setInsertionPoint(op);
368 ArrayAttr argActivityAttr = builder.getArrayAttr(llvm::map_to_vector(
369 argActivities, [&op](enzyme::Activity actv) -> Attribute {
370 return enzyme::ActivityAttr::get(op.getContext(), actv);
372 auto newOp = enzyme::AutoDiffOp::create(
373 builder, op.getLoc(), op.getResultTypes(), outlinedFunc->getName(),
374 allInputs, argActivityAttr, op.getRetActivity(), op.getWidth(),
375 op.getStrongZero(), op.getAtomicAdd());
376 op.replaceAllUsesWith(newOp.getResults());
381LogicalResult outlineEnzymeForwardDiffRegion(enzyme::ForwardDiffRegionOp op,
383 OpBuilder &builder) {
384 OpBuilder::InsertionGuard insertionGuard(builder);
385 builder.setInsertionPointAfter(op->getParentOfType<SymbolOpInterface>());
387 SmallVector<enzyme::Activity> argActivities =
388 llvm::map_to_vector(op.getActivity().getAsRange<enzyme::ActivityAttr>(),
389 [](
auto attr) { return attr.getValue(); });
390 SmallVector<Value> primalsAndShadows(op.getInputs());
396 FailureOr<func::FuncOp> outlinedFunc = outlineAutoDiffFunc(
397 op, funcName, primalsAndShadows, argActivities, builder);
398 if (failed(outlinedFunc))
401 SmallVector<Value> allInputs;
402 allInputs.append(primalsAndShadows);
404 builder.setInsertionPoint(op);
405 ArrayAttr argActivityAttr = builder.getArrayAttr(llvm::map_to_vector(
406 argActivities, [&op](enzyme::Activity actv) -> Attribute {
407 return enzyme::ActivityAttr::get(op.getContext(), actv);
409 auto newOp = enzyme::ForwardDiffOp::create(
410 builder, op.getLoc(), op.getResultTypes(), outlinedFunc->getName(),
411 primalsAndShadows, argActivityAttr, op.getRetActivity(), op.getWidth(),
413 op.replaceAllUsesWith(newOp.getResults());
418struct InlineEnzymeIntoRegion
419 :
public enzyme::impl::InlineEnzymeIntoRegionPassBase<
420 InlineEnzymeIntoRegion> {
421 void runOnOperation()
override {
422 RewritePatternSet patterns(&getContext());
423 patterns.insert<InlineEnzymeAutoDiff, InlineEnzymeForwardDiff>(
426 GreedyRewriteConfig config;
427 config.setRegionSimplificationLevel(GreedySimplifyRegionLevel::Normal);
428 (void)applyPatternsGreedily(getOperation(), std::move(patterns), config);
432struct OutlineEnzymeFromRegion
433 :
public enzyme::impl::OutlineEnzymeFromRegionPassBase<
434 OutlineEnzymeFromRegion> {
435 void runOnOperation()
override {
436 SmallVector<enzyme::AutoDiffRegionOp> toOutlineRev;
437 getOperation()->walk(
438 [&](enzyme::AutoDiffRegionOp op) { toOutlineRev.push_back(op); });
440 SmallVector<enzyme::ForwardDiffRegionOp> toOutlineFwd;
441 getOperation()->walk(
442 [&](enzyme::ForwardDiffRegionOp op) { toOutlineFwd.push_back(op); });
444 OpBuilder builder(getOperation());
445 unsigned increment = 0;
446 for (
auto regionOp : toOutlineRev) {
447 auto symbol = regionOp->getParentOfType<SymbolOpInterface>();
448 std::string defaultName =
449 (Twine(symbol.getName(),
"_to_diff") + Twine(increment)).
str();
450 if (failed(outlineEnzymeAutoDiffRegion(regionOp, defaultName, builder)))
451 return signalPassFailure();
456 for (
auto regionOp : toOutlineFwd) {
457 auto symbol = regionOp->getParentOfType<SymbolOpInterface>();
458 std::string defaultName =
459 (Twine(symbol.getName(),
"_to_fwddiff") + Twine(increment)).
str();
461 outlineEnzymeForwardDiffRegion(regionOp, defaultName, builder)))
462 return signalPassFailure();
472 RewriterBase &rewriter,
473 SymbolTableCollection &symbolTable) {
474 FunctionOpInterface fn = dyn_cast_or_null<FunctionOpInterface>(
475 symbolTable.lookupNearestSymbolFrom(op, op.getFnAttr()));
478 Region &targetRegion = fn.getFunctionBody();
479 if (targetRegion.empty())
484 auto fnAttr = StringAttr::get(op.getContext(), op.getFn());
485 auto regionOp = rewriter.replaceOpWithNewOp<enzyme::AutoDiffRegionOp>(
486 op, op.getResultTypes(), op.getInputs(), op.getActivity(),
487 op.getRetActivity(), op.getWidth(), op.getStrongZero(), op.getAtomicAdd(),
489 serializeFunctionAttributes(fn, regionOp);
490 rewriter.cloneRegionBefore(targetRegion, regionOp.getBody(),
491 regionOp.getBody().begin());
492 SmallVector<Operation *> toErase;
493 for (Operation &bodyOp : regionOp.getBody().getOps()) {
494 if (bodyOp.hasTrait<OpTrait::ReturnLike>()) {
495 PatternRewriter::InsertionGuard insertionGuard(rewriter);
496 rewriter.setInsertionPoint(&bodyOp);
497 enzyme::YieldOp::create(rewriter, bodyOp.getLoc(), bodyOp.getOperands());
498 toErase.push_back(&bodyOp);
502 for (Operation *opToErase : toErase)
503 rewriter.eraseOp(opToErase);
static std::string str(AugmentedStruct c)
bool inlineAutodiffOp(enzyme::AutoDiffOp &op, RewriterBase &rewriter, SymbolTableCollection &symbolTable)