22#include "mlir/Dialect/Arith/IR/Arith.h"
23#include "mlir/Dialect/Complex/IR/Complex.h"
24#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
25#include "mlir/IR/Matchers.h"
27#include "llvm/Support/CommandLine.h"
33 "enzyme-mlir-fast-math", llvm::cl::init(
true), llvm::cl::Hidden,
34 llvm::cl::desc(
"Build derivative expressions with fast-math enabled"));
39 if (
auto iface = dyn_cast<arith::ArithFastMathInterface>(op))
40 op->setAttr(iface.getFastMathAttrName(),
41 arith::FastMathFlagsAttr::get(op->getContext(),
42 arith::FastMathFlags::fast));
43 else if (
auto iface = dyn_cast<LLVM::FastmathFlagsInterface>(op))
44 op->setAttr(iface.getFastmathAttrName(),
45 LLVM::FastmathFlagsAttr::get(op->getContext(),
46 LLVM::FastmathFlags::fast));
50 llvm::StringRef value) {
53 auto ATI = cast<AutoDiffTypeInterface>(type);
54 return cast<TypedAttr>(ATI.createNullAttr());
56 if (
auto T = dyn_cast<TensorType>(type)) {
57 if (
auto ET = dyn_cast<FloatType>(T.getElementType())) {
58 APFloat values[] = {APFloat(ET.getFloatSemantics(), value)};
59 return DenseElementsAttr::get(cast<ShapedType>(type),
60 ArrayRef<APFloat>(values));
61 }
else if (
auto CET = dyn_cast<ComplexType>(T.getElementType())) {
62 auto ET = cast<FloatType>(CET.getElementType());
63 mlir::Complex<APFloat> values[] = {
64 mlir::Complex<APFloat>(APFloat(ET.getFloatSemantics(), value),
65 APFloat(ET.getFloatSemantics(),
"0"))};
66 return DenseElementsAttr::get(cast<ShapedType>(type),
67 ArrayRef<mlir::Complex<APFloat>>(values));
69 llvm::errs() <<
" unsupported eltype: " << T.getElementType()
70 <<
" of type " << type <<
"\n";
71 llvm_unreachable(
"unsupported eltype");
73 }
else if (
auto T = cast<FloatType>(type)) {
74 APFloat apvalue(T.getFloatSemantics(), value);
75 return FloatAttr::get(T, apvalue);
84 llvm::errs() <<
" unsupported type: " << type <<
"\n";
85 llvm_unreachable(
"unsupported eltype");
91 for (Value result : op->getResults())
101 auto binst = cast<BranchOpInterface>(inst);
104 SmallVector<Value> newVals;
106 SmallVector<int32_t> segSizes;
111 size_t non_forwarded = binst->getNumOperands();
112 for (
size_t i = 0; i < newInst->getNumSuccessors(); i++) {
113 auto ops = binst.getSuccessorOperands(i).getForwardedOperands();
116 non_forwarded = ops.getBeginOperandIndex();
120 for (
size_t i = 0; i < non_forwarded; i++)
123 segSizes.push_back(newVals.size());
124 for (
size_t i = 0; i < newInst->getNumSuccessors(); i++) {
125 size_t cur = newVals.size();
126 auto ops = binst.getSuccessorOperands(i).getForwardedOperands();
127 for (
auto &&[idx, op] : llvm::enumerate(ops)) {
129 *binst.getSuccessorBlockArgument(ops.getBeginOperandIndex() + idx);
135 Type retTy = cast<AutoDiffTypeInterface>(arg.getType())
136 .getShadowType(gutils->
width);
137 auto toret = cast<AutoDiffTypeInterface>(retTy).createNullValue(
138 builder, op.getLoc());
139 newVals.push_back(toret);
143 cur = newVals.size() - cur;
144 segSizes.push_back(cur);
147 SmallVector<NamedAttribute> attrs(newInst->getAttrs());
148 bool has_cases =
false;
149 for (
auto &attr : attrs) {
150 if (attr.getName() ==
"case_operand_segments") {
154 for (
auto &attr : attrs) {
155 if (attr.getName() ==
"operandSegmentSizes") {
157 attr.setValue(builder.getDenseI32ArrayAttr(segSizes));
159 SmallVector<int32_t> segSlices2(segSizes.begin(), segSizes.begin() + 2);
160 segSlices2.push_back(0);
161 for (
size_t i = 2; i < segSizes.size(); i++)
162 segSlices2[2] += segSizes[i];
163 attr.setValue(builder.getDenseI32ArrayAttr(segSlices2));
166 if (attr.getName() ==
"case_operand_segments") {
167 SmallVector<int32_t> segSlices2(segSizes.begin() + 2, segSizes.end());
168 attr.setValue(builder.getDenseI32ArrayAttr(segSlices2));
173 ->push_back(newInst->create(newInst->getLoc(), newInst->getName(),
174 TypeRange(), newVals, attrs,
175 mlir::PropertyRef(), newInst->getSuccessors(),
176 newInst->getNumRegions()));
177 gutils->
erase(newInst);
192 ArrayRef<int> storedVals) {
193 auto iface = cast<ActivityOpInterface>(orig);
195 SmallVector<Value> newOperands;
196 newOperands.reserve(orig->getNumOperands());
197 SmallVector<bool> inverted(orig->getNumOperands(),
false);
198 for (OpOperand &operand : orig->getOpOperands()) {
199 if (iface.isArgInactive(operand.getOperandNumber())) {
204 if (
contains(storedVals, operand.getOperandNumber()) ||
206 if (isa<AutoDiffTypeInterface>(operand.get().getType())) {
210 orig, *gutils, operand.get(), builder));
215 <<
"Unsupported constant arg to memory identity forward "
217 << operand.getOperandNumber() <<
", op=" << operand.get() <<
")\n";
220 inverted[newOperands.size()] =
true;
221 newOperands.push_back(gutils->
invertPointerM(operand.get(), builder));
228 SmallVector<Operation *, 1> shadows;
229 if (gutils->
width == 1) {
230 Operation *shadow = builder.clone(*primal);
231 shadow->setOperands(newOperands);
232 shadows.push_back(shadow);
234 for (
size_t w = 0; w < gutils->
width; w++) {
235 SmallVector<Value> newOperands2(newOperands);
236 for (
size_t i = 0; i < newOperands.size(); i++) {
240 builder, orig->getLoc(), orig->getOperands()[i].getType(),
243 Operation *shadow = builder.clone(*primal);
244 shadow->setOperands(newOperands2);
245 shadows.push_back(shadow);
248 for (
auto &&[i, oval] : llvm::enumerate(orig->getResults())) {
252 if (gutils->
width == 1) {
253 sval = shadows[0]->getResult(i);
255 SmallVector<Value> shadowRes;
256 for (
auto s : shadows) {
257 shadowRes.push_back(s->getResult(i));
261 gutils->
setDiffe(oval, sval, builder);
267 if (
auto store = dyn_cast<enzyme::StoreLikeInterface>(orig))
269 gutils->
erase(primal);
275 Operation *orig, OpBuilder &builder,
MGradientUtils *gutils,
bool zero) {
278 Operation *shadow = builder.clone(*primal);
280 Value shadowRes = shadow->getResult(0);
287 if (
auto iface = dyn_cast<AutoDiffTypeInterface>(shadowRes.getType())) {
288 return iface.zeroInPlace(builder, orig->getLoc(), shadowRes);
290 orig->emitError() <<
"Type " << shadowRes.getType()
291 <<
" does not implement "
292 "AutoDiffTypeInterface";
309 auto args = gutils->
newFunc->getRegions().begin()->begin()->getArguments();
311 for (
auto &&[op, act] : llvm::zip(op->getOperands(), gutils->
RetDiffeTypes)) {
314 auto d_out = args[args.size() - num_out + idx];
323 Operation *origTerminator, OpBuilder &builder,
MGradientUtils *gutils) {
324 auto parentOp = origTerminator->getParentOp();
326 llvm::SmallDenseSet<unsigned> operandsToShadow;
327 auto termIface = dyn_cast<RegionBranchTerminatorOpInterface>(origTerminator);
328 auto regionBranchOp =
329 dyn_cast<RegionBranchOpInterface>(origTerminator->getParentOp());
330 if (termIface && regionBranchOp) {
332 SmallVector<RegionSuccessor> successors;
333 termIface.getSuccessorRegions(
334 SmallVector<Attribute>(origTerminator->getNumOperands(), Attribute()),
337 for (
auto &successor : successors) {
338 OperandRange operandRange = termIface.getSuccessorOperands(successor);
339 ValueRange targetValues =
340 successor.isOperation()
341 ? parentOp->getResults()
342 : regionBranchOp.getSuccessorInputs(successor);
346 size_t numPaired = std::min(operandRange.size(), targetValues.size());
347 for (
auto &&[i, target] :
348 llvm::enumerate(targetValues.take_front(numPaired))) {
350 operandsToShadow.insert(operandRange.getBeginOperandIndex() + i);
357 size_t numPaired = std::min<size_t>(parentOp->getNumResults(),
358 origTerminator->getNumOperands());
359 for (
auto res : parentOp->getResults().take_front(numPaired)) {
361 operandsToShadow.insert(res.getResultNumber());
365 SmallVector<Value> newOperands;
366 newOperands.reserve(origTerminator->getNumOperands() +
367 operandsToShadow.size());
368 for (OpOperand &operand : origTerminator->getOpOperands()) {
370 if (operandsToShadow.contains(operand.getOperandNumber()))
371 newOperands.push_back(gutils->
invertPointerM(operand.get(), builder));
377 replTerminator->setOperands(newOperands);
385 auto regionBranchOp = dyn_cast<RegionBranchOpInterface>(op);
386 if (!regionBranchOp) {
387 op->emitError() <<
" RegionBranchOpInterface not implemented for " << *op
391 auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
393 op->emitError() <<
" ControlFlowAutoDiffOpInterface not implemented for "
400 llvm::SmallDenseSet<unsigned> operandPositionsToShadow;
401 llvm::SmallDenseSet<unsigned> resultPositionsToShadow;
403 SmallVector<RegionSuccessor> entrySuccessors;
404 regionBranchOp.getEntrySuccessorRegions(
405 SmallVector<Attribute>(op->getNumOperands(), Attribute()),
408 for (
const RegionSuccessor &successor : entrySuccessors) {
410 OperandRange operandRange =
411 iface.getSuccessorOperands(regionBranchOp, successor);
413 ValueRange targetValues =
414 successor.isOperation() ? op->getResults()
415 : regionBranchOp.getSuccessorInputs(successor);
419 for (
auto &&[i, regionValue, operand] :
420 llvm::enumerate(targetValues, operandRange)) {
423 operandPositionsToShadow.insert(operandRange.getBeginOperandIndex() + i);
424 if (successor.isOperation())
425 resultPositionsToShadow.insert(i);
429 for (
auto res : op->getResults())
431 resultPositionsToShadow.insert(res.getResultNumber());
434 op, builder, gutils, operandPositionsToShadow, resultPositionsToShadow);
439 const llvm::SmallDenseSet<unsigned> &operandPositionsToShadow,
440 const llvm::SmallDenseSet<unsigned> &resultPositionsToShadow) {
444 SmallVector<Type> newOpResultTypes;
445 newOpResultTypes.reserve(op->getNumResults() * 2);
446 for (
auto result : op->getResults()) {
449 newOpResultTypes.push_back(result.getType());
451 assert(resultPositionsToShadow.count(result.getResultNumber()));
453 if (!resultPositionsToShadow.count(result.getResultNumber()))
455 auto typeIface = dyn_cast<AutoDiffTypeInterface>(result.getType());
457 op->emitError() <<
" AutoDiffTypeInterface not implemented for "
458 << result.getType() <<
"\n";
461 newOpResultTypes.push_back(typeIface.getShadowType(gutils->
width));
464 SmallVector<Value> newOperands;
465 newOperands.reserve(op->getNumOperands() + operandPositionsToShadow.size());
466 for (OpOperand &operand : op->getOpOperands()) {
468 if (operandPositionsToShadow.contains(operand.getOperandNumber()))
469 newOperands.push_back(gutils->
invertPointerM(operand.get(), builder));
478 auto iface = dyn_cast<ControlFlowAutoDiffOpInterface>(op);
480 op->emitError() <<
" ControlFlowAutoDiffOpInterface not implemented for "
484 Operation *replacement = iface.createWithShadows(
485 builder, gutils, op, newOperands, newOpResultTypes);
486 assert(replacement->getNumResults() == newOpResultTypes.size());
487 for (
auto &&[region, replacementRegion] :
488 llvm::zip(newOp->getRegions(), replacement->getRegions())) {
489 replacementRegion.takeBody(region);
494 SmallVector<Value> reps;
496 for (OpResult r : op->getResults()) {
498 reps.push_back(replacement->getResult(idx));
501 assert(resultPositionsToShadow.count(r.getResultNumber()));
505 inverted.replaceAllUsesWith(replacement->getResult(idx));
506 gutils->
erase(inverted.getDefiningOp());
508 }
else if (resultPositionsToShadow.count(r.getResultNumber())) {
514 for (
auto &origRegion : op->getRegions()) {
515 for (
auto &origBlock : origRegion) {
516 for (Operation &o : origBlock) {
526 gutils->
erase(newOp);
537 auto callOp = dyn_cast<CallOpInterface>(op);
540 auto sym = dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
543 return dyn_cast_or_null<FunctionOpInterface>(
544 SymbolTable::lookupNearestSymbolFrom(op, sym));
553 return orig->emitError()
554 <<
"could not find the callee of: " << *orig <<
"\n";
556 if (fn.getFunctionBody().empty()) {
557 return orig->emitError()
558 <<
"cannot differentiate a call to a function without a body and "
559 "without a registered derivative: "
560 << fn.getNameAttr() <<
"\n";
563 auto narg = orig->getNumOperands();
564 auto nret = orig->getNumResults();
566 std::vector<DIFFE_TYPE> RetActivity;
567 RetActivity.reserve(nret);
568 for (
auto res : orig->getResults()) {
573 std::vector<DIFFE_TYPE> ArgActivity;
574 ArgActivity.reserve(narg);
575 for (
auto arg : orig->getOperands()) {
589 std::vector<bool> returnPrimal(nret,
true);
590 std::vector<bool> returnShadow(nret,
false);
594 bool freeMemory =
true;
595 size_t width = gutils->
width;
597 std::vector<bool> overwritten_args(narg,
false);
600 fn, RetActivity, ArgActivity, gutils->
TA, returnPrimal, mode, freeMemory,
602 nullptr, type_args, overwritten_args,
607 return orig->emitError()
608 <<
"failed to create forward-mode derivative for callee "
609 << fn.getNameAttr() <<
"\n";
611 SmallVector<Value> fwdArguments;
613 for (
auto &&[arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
619 auto *fwdCallOp = cast<AutoDiffFunctionInterface>(forwardFn.getOperation())
620 .createCall(builder, orig->getLoc(), fwdArguments);
622 SmallVector<Value> primals;
623 primals.reserve(nret);
626 for (
auto &&[ret, act] : llvm::zip_equal(orig->getResults(), RetActivity)) {
627 auto fwdRet = fwdCallOp->getResult(fwdIndex);
628 primals.push_back(fwdRet);
633 gutils->
setDiffe(ret, fwdCallOp->getResult(fwdIndex), builder);
640 gutils->
erase(newOp);
654 return me && me.getArgMem() == LLVM::ModRefInfo::NoModRef &&
655 me.getInaccessibleMem() == LLVM::ModRefInfo::NoModRef &&
656 me.getOther() == LLVM::ModRefInfo::NoModRef &&
657 me.getErrnoMem() == LLVM::ModRefInfo::NoModRef &&
658 me.getTargetMem0() == LLVM::ModRefInfo::NoModRef &&
659 me.getTargetMem1() == LLVM::ModRefInfo::NoModRef;
663 SmallPtrSetImpl<Operation *> &visited) {
664 if (
auto call = dyn_cast<LLVM::CallOp>(orig))
667 if (
auto llvmFn = dyn_cast<LLVM::LLVMFuncOp>(fn.getOperation())) {
670 if (
auto pass = llvmFn->getAttrOfType<ArrayAttr>(
"passthrough"))
671 for (Attribute a : pass)
672 if (
auto s = dyn_cast<StringAttr>(a))
673 if (s.getValue() ==
"readnone")
676 if (fn.getFunctionBody().empty())
679 if (!visited.insert(fn.getOperation()).second)
681 WalkResult res = fn.getFunctionBody().walk([&](Operation *op) {
682 if (isMemoryEffectFree(op))
683 return WalkResult::advance();
688 return WalkResult::advance();
689 return WalkResult::interrupt();
691 return !res.wasInterrupted();
695 SmallPtrSet<Operation *, 8> visited;
700 FunctionOpInterface fn) {
703 return orig->emitError()
704 <<
"cannot differentiate a call in reverse mode whose callee "
705 "touches memory; caching of overwritten arguments is not yet "
707 << fn.getNameAttr() <<
"\n";
712 SmallVector<Value> caches) {
717 return orig->emitError()
718 <<
"could not find the callee of: " << *orig <<
"\n";
720 if (fn.getFunctionBody().empty()) {
721 return orig->emitError()
722 <<
"cannot differentiate a call to a function without a body and "
723 "without a registered derivative: "
724 << fn.getNameAttr() <<
"\n";
729 auto narg = orig->getNumOperands();
730 auto nret = orig->getNumResults();
732 std::vector<DIFFE_TYPE> RetActivity;
733 for (
auto res : orig->getResults()) {
734 RetActivity.push_back(
736 : cast<AutoDiffTypeInterface>(res.getType()).isMutable()
741 std::vector<DIFFE_TYPE> ArgActivity;
742 for (
auto arg : orig->getOperands()) {
747 if (cast<AutoDiffTypeInterface>(arg.getType()).isMutable()) {
760 if (llvm::any_of(RetActivity,
762 return orig->emitError()
763 <<
"could not emit adjoint with mutable return types in: " << *orig
767 std::vector<bool> overwritten_args(narg,
true);
768 std::vector<bool> returnShadow(nret,
false);
769 std::vector<bool> returnPrimal(nret,
false);
773 bool freeMemory =
true;
774 size_t width = gutils->
width;
777 fn, RetActivity, ArgActivity, gutils->
TA, returnPrimal, returnShadow,
778 mode, freeMemory, gutils->
AtomicAdd, width,
nullptr,
779 type_args, overwritten_args,
nullptr, gutils->
omp,
784 return orig->emitError()
785 <<
"failed to create reverse-mode adjoint for callee "
786 << fn.getNameAttr() <<
"\n";
788 SmallVector<Value> revArguments;
791 for (
auto [arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
792 revArguments.push_back(gutils->
popCache(caches[cacheIdx++], builder));
794 revArguments.push_back(gutils->
popCache(caches[cacheIdx++], builder));
796 assert(cacheIdx == caches.size());
798 for (
auto result : orig->getResults()) {
801 revArguments.push_back(gutils->
diffe(result, builder));
804 auto *revCallOp = cast<AutoDiffFunctionInterface>(revFn.getOperation())
805 .createCall(builder, orig->getLoc(), revArguments);
807 int revIndex = 0, fwdIndex = 0;
808 for (
auto [arg, act] : llvm::zip_equal(orig->getOperands(), ArgActivity)) {
815 cast<ClonableTypeInterface>(arg.getType())
816 .freeClonedValue(builder, revArguments[fwdIndex - 1]);
819 auto diffe = revCallOp->getResult(revIndex);
830 SmallVector<Value> cachedArguments;
837 return cachedArguments;
840 OpBuilder cacheBuilder(newOp);
842 for (
auto arg : orig->getOperands()) {
844 if (
auto iface = dyn_cast<ClonableTypeInterface>(arg.getType())) {
845 toCache = iface.cloneValue(cacheBuilder, toCache);
848 cachedArguments.push_back(cache);
856 cast<AutoDiffTypeInterface>(arg.getType()).isMutable()) {
862 return cachedArguments;
static bool splitReverseMemoryOk(Operation *orig, FunctionOpInterface fn)
static LogicalResult checkSplitReverseMemory(Operation *orig, FunctionOpInterface fn)
static bool contains(ArrayRef< int > ar, int v)
static FunctionOpInterface getDirectCallee(Operation *op)
static llvm::cl::opt< bool > EnzymeMLIRFastMath("enzyme-mlir-fast-math", llvm::cl::init(true), llvm::cl::Hidden, llvm::cl::desc("Build derivative expressions with fast-math enabled"))
static bool splitReverseMemoryOkImpl(Operation *orig, FunctionOpInterface fn, SmallPtrSetImpl< Operation * > &visited)
static bool memoryEffectsNone(LLVM::MemoryEffectsAttr me)
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
FunctionOpInterface CreateForwardDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, DerivativeMode mode, bool freeMemory, size_t width, mlir::Type addedType, MFnTypeInfo type_args, std::vector< bool > overwritten_args, void *augmented, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero)
FunctionOpInterface CreateReverseDiff(FunctionOpInterface fn, std::vector< DIFFE_TYPE > retType, std::vector< DIFFE_TYPE > constants, MTypeAnalysis &TA, std::vector< bool > returnPrimals, std::vector< bool > returnShadows, DerivativeMode mode, bool freeMemory, bool atomicAdd, size_t width, mlir::Type addedType, MFnTypeInfo type_args, std::vector< bool > overwritten_args, void *augmented, bool omp, llvm::StringRef postpasses, bool verifyPostPasses, bool strongZero, bool markReadonly)
Value popCache(Value cache, OpBuilder &builder)
Value initAndPushCache(Value v, OpBuilder &builder)
void addToDiffe(mlir::Value oldGradient, mlir::Value addedGradient, OpBuilder &builder)
bool primalStoreElidable(mlir::Value ptr)
True when a primal store through this pointer may be omitted: declaring the base enzyme_dupnoneed is ...
ArrayRef< DIFFE_TYPE > RetDiffeTypes
llvm::StringRef postpasses
IRMapping invertedPointers
void replaceOrigOpWith(Operation *op, ValueRange vals)
std::map< Operation *, Operation * > originalToNewFnOps
LogicalResult visitChild(Operation *op)
void erase(Operation *op)
DIFFE_TYPE getDiffeTypeOfBase(mlir::Value ptr)
The activity the caller declared for the memory this pointer views: the base's entry in ArgDiffeTypes...
void setDiffe(mlir::Value origv, mlir::Value newv, mlir::OpBuilder &builder)
mlir::Value invertPointerM(mlir::Value v, OpBuilder &Builder2)
void eraseIfUnused(Operation *op, bool erase=true, bool check=true)
SmallVector< mlir::Value, 1 > getNewFromOriginal(ValueRange originst) const
FunctionOpInterface newFunc
void setInvertedPointer(mlir::Value origv, mlir::Value newv)
bool isConstantValue(mlir::Value v) const
MFnTypeInfo getAnalyzedTypeInfo(FunctionOpInterface op) const
LogicalResult controlFlowForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
LogicalResult memoryIdentityForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils, ArrayRef< int > storedVals)
void returnReverseHandler(Operation *op, OpBuilder &builder, MGradientUtilsReverse *gutils)
void regionTerminatorForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
SmallVector< Value > callCacheValues(Operation *orig, MGradientUtilsReverse *gutils)
void branchingForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils)
LogicalResult callReverseHandler(Operation *orig, OpBuilder &builder, MGradientUtilsReverse *gutils, SmallVector< Value > caches)
LogicalResult allocationForwardHandler(Operation *op, OpBuilder &builder, MGradientUtils *gutils, bool zero)
LogicalResult callForwardHandler(Operation *orig, OpBuilder &builder, MGradientUtils *gutils)
Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils, Value stored, OpBuilder &builder)
Value getConcatValue(OpBuilder &builder, Location loc, ArrayRef< Value > argList)
bool allResultsActive(Operation *op, const MGradientUtils *gutils)
Value getExtractValue(OpBuilder &builder, Location loc, Type argTy, Value val, int64_t index)
void setDerivativeFastMath(Operation *op)
Give an operation built for a derivative the fast-math flags the LLVM path gives everything it builds...
mlir::TypedAttr getConstantAttr(mlir::Type type, llvm::StringRef value)