Enzyme main
Loading...
Searching...
No Matches
LLVMAutoDiffOpInterfaceImpl.cpp
Go to the documentation of this file.
1//===- LLVMAutoDiffOpInterfaceImpl.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 LLVM dialect.
11//
12//===----------------------------------------------------------------------===//
13
21#include "Interfaces/Utils.h"
22#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
23#include "mlir/IR/DialectRegistry.h"
24#include "mlir/IR/Matchers.h"
25#include "mlir/Interfaces/DataLayoutInterfaces.h"
26#include "mlir/Support/LogicalResult.h"
27
28using namespace mlir;
29using namespace mlir::enzyme;
30
31namespace {
32
33static mlir::Value allocaExtent(mlir::OpBuilder &builder,
34 mlir::LLVM::AllocaOp alloca);
35#include "Implementations/LLVMDerivatives.inc"
36
37struct BitcastOpViewInterface
38 : public ViewLikeOpInterface::ExternalModel<BitcastOpViewInterface,
39 LLVM::BitcastOp> {
40 Value getViewSource(Operation *op) const {
41 return cast<LLVM::BitcastOp>(op).getArg();
42 }
43};
44
45struct GEPOpOffsetInterface
46 : public OffsetViewInterface::ExternalModel<GEPOpOffsetInterface,
47 LLVM::GEPOp> {
48 bool isZeroOffset(Operation *op) const {
49 for (auto index : cast<LLVM::GEPOp>(op).getIndices()) {
50 if (auto attr = dyn_cast<IntegerAttr>(index)) {
51 if (!attr.getValue().isZero())
52 return false;
53 } else if (!matchPattern(cast<Value>(index), m_Zero())) {
54 return false;
55 }
56 }
57 return true;
58 }
59};
60
61// Lets activity analysis treat llvm.store generically via StoreLikeInterface.
62struct LLVMStoreLike
63 : public StoreLikeInterface::ExternalModel<LLVMStoreLike, LLVM::StoreOp> {
64 Value getStoredValue(Operation *op) const {
65 return cast<LLVM::StoreOp>(op).getValue();
66 }
67 Value getStoredPointer(Operation *op) const {
68 return cast<LLVM::StoreOp>(op).getAddr();
69 }
70};
71
72struct InlineAsmActivityInterface
73 : public ActivityOpInterface::ExternalModel<InlineAsmActivityInterface,
74 LLVM::InlineAsmOp> {
75 bool isInactive(Operation *op) const {
76 auto asmOp = cast<LLVM::InlineAsmOp>(op);
77 auto str = asmOp.getAsmString();
78 return str.contains("cpuid") || str.contains("exit");
79 }
80 bool isArgInactive(Operation *op, size_t) const { return isInactive(op); }
81};
82
83struct SelectActivityInterface
84 : public ActivityOpInterface::ExternalModel<SelectActivityInterface,
85 LLVM::SelectOp> {
86 bool isInactive(Operation *op) const { return false; }
87 bool isArgInactive(Operation *op, size_t idx) const {
88 // llvm.select is not inactive in general, but the condition is always
89 // inactive.
90 return idx == 0;
91 }
92};
93
94class ArrayTypeInterface
95 : public AutoDiffTypeInterface::ExternalModel<ArrayTypeInterface,
96 LLVM::LLVMArrayType> {
97public:
98 mlir::Attribute createNullAttr(mlir::Type self) const {
99 llvm::errs() << " unsupported: createNullAttribute of LLVMArrayType\n";
100 return nullptr;
101 }
102
103 mlir::Value createNullValue(mlir::Type self, OpBuilder &builder,
104 Location loc) const {
105 auto arrayTy = cast<LLVM::LLVMArrayType>(self);
106 Type elemTy = arrayTy.getElementType();
107 auto elemIface = dyn_cast<AutoDiffTypeInterface>(elemTy);
108 if (!elemIface)
109 return LLVM::ZeroOp::create(builder, loc, arrayTy);
110
111 Value nullElem = elemIface.createNullValue(builder, loc);
112 Value result = LLVM::PoisonOp::create(builder, loc, arrayTy);
113 for (uint64_t i = 0; i < arrayTy.getNumElements(); ++i) {
114 result = LLVM::InsertValueOp::create(builder, loc, result, nullElem, i);
115 }
116 return result;
117 }
118
119 Value createAddOp(Type self, OpBuilder &builder, Location loc, Value a,
120 Value b) const {
121 auto arrayTy = cast<LLVM::LLVMArrayType>(self);
122 Type elemTy = arrayTy.getElementType();
123 Value result = LLVM::PoisonOp::create(builder, loc, arrayTy);
124 auto elemIface = dyn_cast<AutoDiffTypeInterface>(elemTy);
125 for (uint64_t i = 0; i < arrayTy.getNumElements(); ++i) {
126 Value aElem = LLVM::ExtractValueOp::create(builder, loc, a, i);
127 Value bElem = LLVM::ExtractValueOp::create(builder, loc, b, i);
128 Value sum;
129 if (elemIface) {
130 sum = elemIface.createAddOp(builder, loc, aElem, bElem);
131 } else {
132 sum = aElem;
133 }
134 result = LLVM::InsertValueOp::create(builder, loc, result, sum, i);
135 }
136 return result;
137 }
138
139 Value createConjOp(Type self, OpBuilder &builder, Location loc,
140 Value a) const {
141 llvm_unreachable("TODO");
142 }
143
144 Type getShadowType(Type self, unsigned width) const {
145 assert(width == 1 && "unsupported width != 1");
146 return self;
147 }
148
149 bool isMutable(Type self) const { return false; }
150
151 LogicalResult zeroInPlace(Type self, OpBuilder &builder, Location loc,
152 Value val) const {
153 return failure();
154 }
155
156 bool isZero(Type self, Value val) const { return false; }
157 bool isZeroAttr(Type self, Attribute attr) const { return false; }
158
159 int64_t getApproxSize(Type self) const {
160 auto arrayType = cast<LLVM::LLVMArrayType>(self);
161 if (auto elemType =
162 dyn_cast<AutoDiffTypeInterface>(arrayType.getElementType())) {
163 int64_t elSize = elemType.getApproxSize();
164 if (elSize != INT64_MAX)
165 return arrayType.getNumElements() * elSize;
166 }
167 return INT64_MAX;
168 }
169};
170
171class PointerTypeInterface
172 : public AutoDiffTypeInterface::ExternalModel<PointerTypeInterface,
173 LLVM::LLVMPointerType> {
174public:
175 mlir::Attribute createNullAttr(mlir::Type self) const {
176 llvm::errs() << " unsupported: createNullAttribute of pointertype\n";
177 return nullptr;
178 }
179
180 mlir::Value createNullValue(mlir::Type self, OpBuilder &builder,
181 Location loc) const {
182 return LLVM::ZeroOp::create(builder, loc, self);
183 }
184
185 Value createAddOp(Type self, OpBuilder &builder, Location loc, Value a,
186 Value b) const {
187 llvm_unreachable("TODO");
188 }
189
190 Value createConjOp(Type self, OpBuilder &builder, Location loc,
191 Value a) const {
192 llvm_unreachable("TODO");
193 }
194
195 Type getShadowType(Type self, unsigned width) const {
196 assert(width == 1 && "unsupported width != 1");
197 return self;
198 }
199
200 bool isMutable(Type self) const { return true; }
201
202 LogicalResult zeroInPlace(Type self, OpBuilder &builder, Location loc,
203 Value val) const {
204 if (auto allocaOp = val.getDefiningOp<LLVM::AllocaOp>()) {
205 Value size = allocaExtent(builder, allocaOp);
206 if (!size)
207 return failure();
208 Value zero =
209 LLVM::ConstantOp::create(builder, loc, builder.getI8IntegerAttr(0));
210 LLVM::MemsetOp::create(builder, loc, val, zero, size,
211 /*isVolatile=*/false);
212 return success();
213 }
214 return failure();
215 }
216
217 bool isZero(Type self, Value val) const { return false; }
218 bool isZeroAttr(Type self, Attribute attr) const { return false; }
219};
220
221// A lifetime marker says when the memory it names is live. The shadow is
222// memory too and lives exactly as long, so the tangent is the same marker said
223// again of the shadow. The op is declared inactive, which is about what it
224// makes active and not about whether the derivative needs it said: a barrier is
225// inactive too and still has to be there.
226template <typename OpTy>
227struct LifetimeForwardInterface
228 : public AutoDiffOpInterface::ExternalModel<LifetimeForwardInterface<OpTy>,
229 OpTy> {
230 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
231 MGradientUtils *gutils) const {
232 auto ltOp = cast<OpTy>(op);
233 // Memory nothing differentiates has no shadow whose lifetime this could be.
234 if (gutils->isConstantValue(ltOp.getPtr()))
235 return success();
236
237 Value shadow = gutils->invertPointerM(ltOp.getPtr(), builder);
238 auto newOp = cast<OpTy>(gutils->getNewFromOriginal(op));
239 auto shadowOp = cast<OpTy>(builder.clone(*newOp));
240 shadowOp.getPtrMutable().assign(shadow);
241 return success();
242 }
243};
244
245// A debug intrinsic narrates the primal: it names which source variable a
246// value stands for, and computes nothing. The primal copy in the generated
247// function keeps saying it; the derivative has nothing to add -- there is no
248// variable metadata under which a shadow could honestly be described. Like the
249// lifetime markers, the op is declared inactive, and this interface exists
250// because inactivity alone answers "what does it make active", not "what is
251// its tangent": an op whose operand is active is still asked for one.
252template <typename OpTy>
253struct NoTangentForwardInterface
254 : public AutoDiffOpInterface::ExternalModel<NoTangentForwardInterface<OpTy>,
255 OpTy> {
256 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
257 MGradientUtils *gutils) const {
258 return success();
259 }
260};
261
262// The reverse-mode side of the same statement. The activity tables say these
263// ops are inactive, but not every analyzer consults that when deciding what
264// to skip; owning the answer here keeps -g working regardless of which one
265// ran.
266template <typename OpTy>
267struct NoAdjointReverseInterface
268 : public ReverseAutoDiffOpInterface::ExternalModel<
269 NoAdjointReverseInterface<OpTy>, OpTy> {
270 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
271 MGradientUtilsReverse *gutils,
272 SmallVector<Value> caches) const {
273 return success();
274 }
275
276 SmallVector<Value> cacheValues(Operation *op,
277 MGradientUtilsReverse *gutils) const {
278 return SmallVector<Value>();
279 }
280
281 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
282 MGradientUtilsReverse *gutils) const {
283 return success();
284 }
285};
286
287// After a memset the memory holds a fixed byte pattern, which depends on no
288// input, so its tangent is zero everywhere the memset reached. Forward mode
289// says that by clearing the shadow over the same range -- whatever derivative
290// the memory carried before is gone along with the value it belonged to.
291//
292// llvm.intr.memset is declared inactive, which is about what it makes active
293// and not about whether the derivative needs it said.
294struct MemsetForwardInterface
295 : public AutoDiffOpInterface::ExternalModel<MemsetForwardInterface,
296 LLVM::MemsetOp> {
297 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
298 MGradientUtils *gutils) const {
299 auto memset = cast<LLVM::MemsetOp>(op);
300 // Memory nothing differentiates has no shadow to clear.
301 if (gutils->isConstantValue(memset.getDst()))
302 return success();
303
304 // A byte something differentiates is splatted across the memory, and its
305 // tangent would have to be splatted across the shadow. Nothing produces
306 // that today, and guessing at it would quietly zero a derivative.
307 if (!gutils->isConstantValue(memset.getVal()))
308 return op->emitError()
309 << "could not compute the tangent of a memset whose value is "
310 "differentiated "
311 << *op;
312
313 Value shadow = gutils->invertPointerM(memset.getDst(), builder);
314 auto newOp = cast<LLVM::MemsetOp>(gutils->getNewFromOriginal(op));
315 Value zero = LLVM::ConstantOp::create(builder, op->getLoc(),
316 newOp.getVal().getType(),
317 builder.getI8IntegerAttr(0));
318 auto shadowOp = cast<LLVM::MemsetOp>(builder.clone(*newOp));
319 shadowOp.getDstMutable().assign(shadow);
320 shadowOp.getValMutable().assign(zero);
321
322 // A memset into memory whose primal contents the caller declared
323 // unneeded (enzyme_dupnoneed) need not write the primal half at all.
324 if (gutils->primalStoreElidable(memset.getDst()))
325 gutils->erase(newOp);
326 return success();
327 }
328};
329
330struct GEPOpInterfaceReverse
331 : public ReverseAutoDiffOpInterface::ExternalModel<GEPOpInterfaceReverse,
332 LLVM::GEPOp> {
333 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
334 MGradientUtilsReverse *gutils,
335 SmallVector<Value> caches) const {
336 return success();
337 }
338
339 SmallVector<Value> cacheValues(Operation *op,
340 MGradientUtilsReverse *gutils) const {
341 return {};
342 }
343
344 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
345 MGradientUtilsReverse *gutils) const {
346 auto gep = cast<LLVM::GEPOp>(op);
347 auto newGep = cast<LLVM::GEPOp>(gutils->getNewFromOriginal(op));
348 auto base = gep.getBase();
349 if (!gutils->isConstantValue(base)) {
350 auto baseShadow = gutils->invertPointerM(base, builder);
351 auto shadowGep = cast<LLVM::GEPOp>(builder.clone(*newGep));
352 shadowGep.getBaseMutable().assign(baseShadow);
353 gutils->setInvertedPointer(gep.getRes(), shadowGep->getResult(0));
354 }
355 return success();
356 }
357};
358
359// Renaming a pointer into another address space moves nothing, so there is no
360// adjoint to accumulate; the shadow is the same rename of the shadow pointer,
361// as for llvm.getelementptr above. Needed because these sit in front of active
362// memory: raise-llvm-ext emits one for a size hint that annotates the memory
363// space of a pointer whose type cannot name it.
364struct AddrSpaceCastOpInterfaceReverse
365 : public ReverseAutoDiffOpInterface::ExternalModel<
366 AddrSpaceCastOpInterfaceReverse, LLVM::AddrSpaceCastOp> {
367 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
368 MGradientUtilsReverse *gutils,
369 SmallVector<Value> caches) const {
370 return success();
371 }
372
373 SmallVector<Value> cacheValues(Operation *op,
374 MGradientUtilsReverse *gutils) const {
375 return {};
376 }
377
378 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
379 MGradientUtilsReverse *gutils) const {
380 auto asCast = cast<LLVM::AddrSpaceCastOp>(op);
381 auto arg = asCast.getArg();
382 if (gutils->isConstantValue(arg))
383 return success();
384
385 auto newCast = cast<LLVM::AddrSpaceCastOp>(gutils->getNewFromOriginal(op));
386 auto shadowCast = cast<LLVM::AddrSpaceCastOp>(builder.clone(*newCast));
387 shadowCast.getArgMutable().assign(gutils->invertPointerM(arg, builder));
388 gutils->setInvertedPointer(asCast.getRes(), shadowCast.getRes());
389 return success();
390 }
391};
392
393struct LoadOpInterfaceReverse
394 : public ReverseAutoDiffOpInterface::ExternalModel<LoadOpInterfaceReverse,
395 LLVM::LoadOp> {
396 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
397 MGradientUtilsReverse *gutils,
398 SmallVector<Value> caches) const {
399 auto loadOp = cast<LLVM::LoadOp>(op);
400 Value addr = loadOp.getAddr();
401
402 if (auto iface = dyn_cast<AutoDiffTypeInterface>(loadOp.getType())) {
403 // A mutable type's derivative is a shadow, not a value to add into: there
404 // is nothing to accumulate here, the same way the store adjoint below has
405 // nothing to take back out. Loading a pointer is the case in hand.
406 if (!gutils->isConstantValue(loadOp) && !gutils->isConstantValue(addr) &&
407 !iface.isMutable()) {
408 Value gradient = gutils->diffe(loadOp, builder);
409 Value addrGradient = gutils->popCache(caches.front(), builder);
410
411 if (!gutils->AtomicAdd) {
412 Value loadedGradient = LLVM::LoadOp::create(
413 builder, loadOp.getLoc(), iface, addrGradient,
414 loadOp.getAlignment().value_or(0));
415 Value addedGradient = iface.createAddOp(builder, loadOp.getLoc(),
416 loadedGradient, gradient);
417
418 LLVM::StoreOp::create(builder, loadOp.getLoc(), addedGradient,
419 addrGradient,
420 loadOp.getAlignment().value_or(0));
421 } else {
422 LLVM::AtomicRMWOp::create(builder, loadOp.getLoc(),
423 LLVM::AtomicBinOp::fadd, addrGradient,
424 gradient, LLVM::AtomicOrdering::monotonic);
425 }
426 }
427 }
428
429 return success();
430 }
431
432 SmallVector<Value> cacheValues(Operation *op,
433 MGradientUtilsReverse *gutils) const {
434 auto loadOp = cast<LLVM::LoadOp>(op);
435 auto addr = loadOp.getAddr();
436 if (!(isa<AutoDiffTypeInterface>(loadOp.getType()) &&
437 (!gutils->isConstantValue(loadOp) && !gutils->isConstantValue(addr))))
438 return {};
439 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
440 return {gutils->initAndPushCache(gutils->invertPointerM(addr, cacheBuilder),
441 cacheBuilder)};
442 }
443
444 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
445 MGradientUtilsReverse *gutils) const {
446 auto loadOp = cast<LLVM::LoadOp>(op);
447 Value addr = loadOp.getAddr();
448 auto iface = dyn_cast<AutoDiffTypeInterface>(loadOp.getType());
449 // What a load of a mutable type reads is itself a handle on active memory,
450 // so what stands for it is the handle held at the same place in the shadow:
451 // the same load, off the shadow address. Reading it out is the whole of the
452 // derivative -- see the adjoint above, which leaves it alone.
453 if (!iface)
454 return op->emitError()
455 << "could not compute the shadow of a load of a type without "
456 "autodiff semantics "
457 << *op;
458 // An immutable load's derivative is the adjoint's to accumulate.
459 if (!iface.isMutable())
460 return success();
461 if (gutils->isConstantValue(loadOp))
462 return success();
463 // Cannot load a non-constant value out of a constant address: there is no
464 // shadow to read the handle from, so the claimed activity cannot be
465 // honored.
466 if (gutils->isConstantValue(addr))
467 return op->emitError()
468 << "cannot load a non-constant value out of a constant address "
469 << *op;
470 Value addrShadow = gutils->invertPointerM(addr, builder);
471 auto newLoad = cast<LLVM::LoadOp>(gutils->getNewFromOriginal(op));
472 auto shadowLoad = cast<LLVM::LoadOp>(builder.clone(*newLoad));
473 shadowLoad.getAddrMutable().assign(addrShadow);
474 gutils->setInvertedPointer(loadOp.getResult(), shadowLoad.getResult());
475 return success();
476 }
477};
478
479// A memcpy moves whatever bytes sit at the source, so its adjoint depends on
480// what those bytes are. When the destination provably holds no floating point
481// data -- its underlying object is an alloca of a float-free type, as for the
482// capture structs a kernel launch packs its arguments into -- the copy only
483// relocates pointers and integers. Their derivative story is entirely
484// structural: the shadow object must hold the shadow pointers at the same
485// offsets, which one copy of the shadow bytes in the forward sweep provides,
486// and the reverse sweep has nothing to accumulate. Bytes that do hold floats
487// need the classic adjoint (dsrc += ddst; ddst = 0) instead, which needs to
488// know where the floats are; without that knowledge we still refuse.
489static Type getUnderlyingAllocaType(Value ptr) {
490 if (auto alloca = oputils::getBaseObject(ptr).getDefiningOp<LLVM::AllocaOp>())
491 return alloca.getElemType();
492 return nullptr;
493}
494
495static bool typeContainsFloat(Type type) {
496 if (isa<FloatType>(type))
497 return true;
498 if (auto structType = dyn_cast<LLVM::LLVMStructType>(type))
499 return llvm::any_of(structType.getBody(), typeContainsFloat);
500 if (auto arrayType = dyn_cast<LLVM::LLVMArrayType>(type))
501 return typeContainsFloat(arrayType.getElementType());
502 if (auto vecType = dyn_cast<VectorType>(type))
503 return typeContainsFloat(vecType.getElementType());
504 return false;
505}
506
507// In forward mode the tangent of a memcpy is a memcpy of the shadows: float
508// bytes carry their tangents, pointer bytes carry their shadow pointers, and
509// one copy serves both. A source nothing differentiates has no shadow to copy
510// from: its float bytes have a zero tangent, while a float-free copy (see
511// above) wants the primal bytes themselves so structural fields stay usable
512// through the shadow object.
513struct MemcpyForwardInterface
514 : public AutoDiffOpInterface::ExternalModel<MemcpyForwardInterface,
515 LLVM::MemcpyOp> {
516 LogicalResult createForwardModeTangent(Operation *op, OpBuilder &builder,
517 MGradientUtils *gutils) const {
518 auto memcpy = cast<LLVM::MemcpyOp>(op);
519 if (gutils->isConstantValue(memcpy.getDst()))
520 return success();
521 Value dstShadow = gutils->invertPointerM(memcpy.getDst(), builder);
522 auto newOp = cast<LLVM::MemcpyOp>(gutils->getNewFromOriginal(op));
523 if (gutils->isConstantValue(memcpy.getSrc())) {
524 // A source nothing differentiates has no shadow to copy from; what
525 // the destination's tangent should hold depends on what the bytes
526 // are, which only the destination's type can say.
527 Type dstType = getUnderlyingAllocaType(memcpy.getDst());
528 if (!dstType)
529 return op->emitError()
530 << "could not compute the tangent of a memcpy from an "
531 "undifferentiated source without knowing the copied type "
532 << *op;
533 if (typeContainsFloat(dstType)) {
534 // TODO: without type analysis saying where the floats sit, zeroing
535 // is right for them but also nulls any structural fields (sizes,
536 // inactive pointers) the copy carried.
537 op->emitWarning()
538 << "assuming the bytes copied from an undifferentiated source "
539 "are floating point and zeroing their tangent "
540 << *op;
541 Value zero =
542 LLVM::ConstantOp::create(builder, op->getLoc(), builder.getI8Type(),
543 builder.getI8IntegerAttr(0));
544 LLVM::MemsetOp::create(builder, op->getLoc(), dstShadow, zero,
545 newOp.getLen(), newOp.getIsVolatile());
546 return success();
547 }
548 // Float-free: the shadow's structural fields (sizes, inactive
549 // pointers) must read as the primal's.
550 }
551 Value srcShadow = gutils->isConstantValue(memcpy.getSrc())
553 op, *gutils, memcpy.getSrc(), builder)
554 : gutils->invertPointerM(memcpy.getSrc(), builder);
555 auto shadowOp = cast<LLVM::MemcpyOp>(builder.clone(*newOp));
556 shadowOp.getDstMutable().assign(dstShadow);
557 shadowOp.getSrcMutable().assign(srcShadow);
558
559 // A copy into memory whose primal contents the caller declared unneeded
560 // (enzyme_dupnoneed) need not write the primal half at all.
561 if (gutils->primalStoreElidable(memcpy.getDst()))
562 gutils->erase(newOp);
563 return success();
564 }
565};
566
567struct StoreOpInterfaceReverse
568 : public ReverseAutoDiffOpInterface::ExternalModel<StoreOpInterfaceReverse,
569 LLVM::StoreOp> {
570 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
571 MGradientUtilsReverse *gutils,
572 SmallVector<Value> caches) const {
573 auto storeOp = cast<LLVM::StoreOp>(op);
574 Value val = storeOp.getValue();
575 Value addr = storeOp.getAddr();
576
577 auto iface = cast<AutoDiffTypeInterface>(val.getType());
578
579 if (!gutils->isConstantValue(addr)) {
580 Value addrGradient = gutils->popCache(caches.front(), builder);
581
582 if (!iface.isMutable()) {
583 if (!gutils->isConstantValue(val)) {
584 Value loadedGradient = LLVM::LoadOp::create(
585 builder, storeOp.getLoc(), val.getType(), addrGradient,
586 storeOp.getAlignment().value_or(0));
587 gutils->addToDiffe(val, loadedGradient, builder);
588 }
589
590 auto zero =
591 cast<AutoDiffTypeInterface>(gutils->getShadowType(val.getType()))
592 .createNullValue(builder, op->getLoc());
593
594 LLVM::StoreOp::create(builder, storeOp.getLoc(), zero, addrGradient,
595 storeOp.getAlignment().value_or(0));
596 }
597 }
598
599 // A store into memory whose primal contents the caller declared
600 // unneeded (enzyme_dupnoneed) need not happen in the augmented
601 // forward pass either.
602 if (gutils->primalStoreElidable(storeOp.getAddr()))
603 gutils->erase(gutils->getNewFromOriginal(op));
604 return success();
605 }
606
607 SmallVector<Value> cacheValues(Operation *op,
608 MGradientUtilsReverse *gutils) const {
609 auto storeOp = cast<LLVM::StoreOp>(op);
610 auto addr = storeOp.getAddr();
611 if (gutils->isConstantValue(addr))
612 return {};
613 OpBuilder cacheBuilder(gutils->getNewFromOriginal(op));
614 return {gutils->initAndPushCache(gutils->invertPointerM(addr, cacheBuilder),
615 cacheBuilder)};
616 }
617
618 // A store of a mutable value -- a pointer -- has no float adjoint to
619 // accumulate; its derivative story is structural, like llvm.getelementptr
620 // above: the shadow memory must hold the shadow pointer at the same spot,
621 // so shadow loads traverse shadow structures. A pointer nothing
622 // differentiates is its own shadow, keeping inactive fields readable
623 // through the shadow object.
624 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
625 MGradientUtilsReverse *gutils) const {
626 auto storeOp = cast<LLVM::StoreOp>(op);
627 Value val = storeOp.getValue();
628 Value addr = storeOp.getAddr();
629 auto iface = dyn_cast<AutoDiffTypeInterface>(val.getType());
630 if (!iface)
631 return op->emitError()
632 << "could not compute the shadow of a store of a type without "
633 "autodiff semantics "
634 << *op;
635 if (gutils->isConstantValue(addr))
636 return success();
637 // Immutable values' shadows live in the reverse sweep's adjoint, which
638 // accumulates and zeroes the slot; only mutable values need the forward
639 // sweep to place their shadow.
640 if (!iface.isMutable())
641 return success();
642 Value addrShadow = gutils->invertPointerM(addr, builder);
643 Value valShadow =
644 gutils->isConstantValue(val)
645 ? oputils::inactiveStoredValueShadow(op, *gutils, val, builder)
646 : gutils->invertPointerM(val, builder);
647 auto newOp = cast<LLVM::StoreOp>(gutils->getNewFromOriginal(op));
648 auto shadowOp = cast<LLVM::StoreOp>(builder.clone(*newOp));
649 shadowOp.getValueMutable().assign(valShadow);
650 shadowOp.getAddrMutable().assign(addrShadow);
651 return success();
652 }
653};
654
655struct ExtractValueOpInterfaceReverse
656 : public ReverseAutoDiffOpInterface::ExternalModel<
657 ExtractValueOpInterfaceReverse, LLVM::ExtractValueOp> {
658 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
659 MGradientUtilsReverse *gutils,
660 SmallVector<Value> caches) const {
661 auto evOp = cast<LLVM::ExtractValueOp>(op);
662 Value container = evOp.getContainer();
663
664 auto containerIface = dyn_cast<AutoDiffTypeInterface>(container.getType());
665 if (!containerIface)
666 return failure();
667
668 if (!gutils->isConstantValue(evOp)) {
669 Value gradient = gutils->diffe(evOp, builder);
670 gutils->zeroDiffe(evOp, builder);
671 // Create a zero aggregate matching the container type, then insert the
672 // gradient at the extracted position.
673 if (!gutils->isConstantValue(container)) {
674 Value zero = containerIface.createNullValue(builder, op->getLoc());
675 Value grad = LLVM::InsertValueOp::create(builder, op->getLoc(), zero,
676 gradient, evOp.getPosition());
677 gutils->addToDiffe(container, grad, builder);
678 }
679 }
680
681 return success();
682 }
683
684 SmallVector<Value> cacheValues(Operation *op,
685 MGradientUtilsReverse *gutils) const {
686 return {};
687 }
688
689 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
690 MGradientUtilsReverse *gutils) const {
691 return success();
692 }
693};
694
695struct InsertValueOpInterfaceReverse
696 : public ReverseAutoDiffOpInterface::ExternalModel<
697 InsertValueOpInterfaceReverse, LLVM::InsertValueOp> {
698 LogicalResult createReverseModeAdjoint(Operation *op, OpBuilder &builder,
699 MGradientUtilsReverse *gutils,
700 SmallVector<Value> caches) const {
701 auto ivOp = cast<LLVM::InsertValueOp>(op);
702 Value container = ivOp.getContainer();
703 Value value = ivOp.getValue();
704
705 auto resultIface = dyn_cast<AutoDiffTypeInterface>(ivOp.getType());
706 if (!resultIface)
707 return failure();
708
709 if (!gutils->isConstantValue(ivOp)) {
710 Value gradient = gutils->diffe(ivOp, builder);
711 gutils->zeroDiffe(ivOp, builder);
712
713 // Propagate gradient to the inserted value: extract from the result
714 // gradient at the insertion position.
715 auto valIface = dyn_cast<AutoDiffTypeInterface>(value.getType());
716 if (valIface && !gutils->isConstantValue(value)) {
717 Value valGrad = LLVM::ExtractValueOp::create(
718 builder, op->getLoc(), gradient, ivOp.getPosition());
719 gutils->addToDiffe(value, valGrad, builder);
720 }
721
722 // Propagate gradient to the container: zero out the inserted position
723 // in the result gradient, then add to the container gradient.
724 if (!gutils->isConstantValue(container)) {
725 Value zeroVal =
726 valIface
727 ? valIface.createNullValue(builder, op->getLoc())
728 : LLVM::ZeroOp::create(builder, op->getLoc(), value.getType());
729 Value containerGrad = LLVM::InsertValueOp::create(
730 builder, op->getLoc(), gradient, zeroVal, ivOp.getPosition());
731 gutils->addToDiffe(container, containerGrad, builder);
732 }
733 }
734
735 return success();
736 }
737
738 SmallVector<Value> cacheValues(Operation *op,
739 MGradientUtilsReverse *gutils) const {
740 return {};
741 }
742
743 LogicalResult createShadowValues(Operation *op, OpBuilder &builder,
744 MGradientUtilsReverse *gutils) const {
745 return success();
746 }
747};
748
749// What is recorded about the allocation a pointer names: how many bytes are
750// reachable from it, and the pointer that says so. The second is not redundant.
751// A clone is allocated and copied in the address space of the pointer it is
752// made from -- which is what decides between host malloc/memcpy and the device
753// runtime -- and the pointer carrying the extent is not always the one asked
754// about: a size hint annotated with a memory space is attached to an
755// llvm.addrspacecast of it (see raise-llvm-ext), which is how memory a frontend
756// could only type as a host pointer still gets cloned on the device.
757struct PtrExtent {
758 Value size; // null when nothing records the extent
759 Value ptr; // pointer the extent was found on; the queried one by default
760};
761
762static Value normalizeToI64(OpBuilder &builder, Location loc, Value size);
763
764// An alloca says how much it is: the size of the element type, that many times.
765// Nothing has to have annotated it, and `builder` is only used to say the
766// product, next to the copy that wants it.
767static Value allocaExtent(OpBuilder &builder, LLVM::AllocaOp alloca) {
768 auto dl = DataLayout::closest(alloca);
769 uint64_t elemBytes = dl.getTypeSize(alloca.getElemType());
770 auto i64Ty = builder.getIntegerType(64);
771 Value bytes = LLVM::ConstantOp::create(builder, alloca.getLoc(), i64Ty,
772 builder.getI64IntegerAttr(elemBytes));
773 Value count = normalizeToI64(builder, alloca.getLoc(), alloca.getArraySize());
774 if (!count)
775 return nullptr;
776 Value size = LLVM::MulOp::create(builder, alloca.getLoc(), bytes, count);
777 return size;
778}
779
780// `builder` may be null where the caller has nowhere to put an op; the extent
781// of an alloca is the one that has to be said rather than found, so that is the
782// case it gives up on.
783static PtrExtent findPtrExtent(Value ptr, OpBuilder *builder = nullptr) {
784 if (auto allocOp = ptr.getDefiningOp<llvm_ext::AllocOp>())
785 return {allocOp.getSize(), ptr};
786
787 for (auto user : ptr.getUsers())
788 if (auto psh = dyn_cast<llvm_ext::PtrSizeHintOp>(user))
789 return {psh.getSize(), ptr};
790
791 // A hint on a cast of `ptr` describes the same bytes, in the space the cast
792 // names. Looked for after the direct hints, so an unannotated one still wins.
793 for (auto user : ptr.getUsers()) {
794 auto cast = dyn_cast<LLVM::AddrSpaceCastOp>(user);
795 if (!cast)
796 continue;
797 for (auto castUser : cast.getRes().getUsers())
798 if (auto psh = dyn_cast<llvm_ext::PtrSizeHintOp>(castUser))
799 return {psh.getSize(), cast.getRes()};
800 }
801
802 // Last, since a hint is what someone meant and this is only what the type
803 // says.
804 if (builder)
805 if (auto alloca = ptr.getDefiningOp<LLVM::AllocaOp>())
806 if (Value size = allocaExtent(*builder, alloca))
807 return {size, ptr};
808
809 return {nullptr, ptr};
810}
811
812// Names `ptr` in the address space `inSpaceOf` is in, so that both ends of a
813// copy agree on the space it is made in. Emits nothing when they already do.
814static Value castLikeSpaceOf(OpBuilder &builder, Value ptr, Value inSpaceOf) {
815 if (ptr.getType() == inSpaceOf.getType())
816 return ptr;
817 return LLVM::AddrSpaceCastOp::create(builder, ptr.getLoc(),
818 inSpaceOf.getType(), ptr);
819}
820
821static Value normalizeToI64(OpBuilder &builder, Location loc, Value val) {
822 auto i64Ty = builder.getI64Type();
823 if (val.getType() == i64Ty)
824 return val;
825
826 if (val.getType().isIndex())
827 return arith::IndexCastOp::create(builder, loc, i64Ty, val);
828
829 auto srcTy = dyn_cast<IntegerType>(val.getType());
830 if (!srcTy) {
831 llvm::errs() << "ptr size hint is not an integer: " << val << "\n";
832 return nullptr;
833 }
834 // Sizes are non-negative, so zero-extend when widening.
835 if (srcTy.getWidth() < 64)
836 return LLVM::ZExtOp::create(builder, loc, i64Ty, val);
837 return LLVM::TruncOp::create(builder, loc, i64Ty, val);
838}
839
840struct PointerClonableTypeInterface
841 : public ClonableTypeInterface::ExternalModel<PointerClonableTypeInterface,
842 LLVM::LLVMPointerType> {
843 mlir::Value cloneValue(Type self, OpBuilder &builder, Value value) const {
844 PtrExtent extent = findPtrExtent(value, &builder);
845 if (!extent.size) {
846 llvm::errs() << "cannot find size of ptr: " << value << "\n";
847 return nullptr;
848 }
849
850 Value size = normalizeToI64(builder, value.getLoc(), extent.size);
851 if (!size)
852 return nullptr;
853
854 // Clone from the pointer that carries the extent, so the allocation and the
855 // copy land in the memory space that pointer names, and picking the
856 // allocator and the copy is left to lower-llvm-ext: emitting host
857 // malloc/memcpy here would silently produce a host read of device memory (a
858 // segfault at runtime) whenever what is being checkpointed is device
859 // memory.
860 Value clone = llvm_ext::AllocOp::create(builder, value.getLoc(),
861 extent.ptr.getType(), size);
862 llvm_ext::MemcpyOp::create(builder, value.getLoc(), clone, extent.ptr,
863 size);
864
865 return clone;
866 }
867
868 void copyValue(Type self, OpBuilder &builder, Value dst, Value src) const {
869 // Prefer `src`: taking a checkpoint copies from the live ref, whose alloc
870 // (or size hint) is visible. Restoring one copies *out of* a clone buffer,
871 // where only `dst` -- a direct clone -- is traceable.
872 PtrExtent extent = findPtrExtent(src);
873 if (!extent.size)
874 extent = findPtrExtent(dst);
875 if (!extent.size) {
876 llvm::errs() << "cannot find size of ptr to copy: " << src << "\n";
877 return;
878 }
879
880 Value size = normalizeToI64(builder, src.getLoc(), extent.size);
881 if (!size)
882 return;
883
884 Value dstInSpace = castLikeSpaceOf(builder, dst, extent.ptr);
885 Value srcInSpace = castLikeSpaceOf(builder, src, extent.ptr);
886 llvm_ext::MemcpyOp::create(builder, src.getLoc(), dstInSpace, srcInSpace,
887 size);
888 }
889
890 void freeClonedValue(Type self, OpBuilder &builder, Value value) const {
891 llvm_ext::FreeOp::create(builder, value.getLoc(), value);
892 }
893
894 bool implementsBatchAllocation(Type self, Value base,
895 OpFoldResult size) const {
896 return true;
897 }
898
899 Value deriveSubElement(Type self, OpBuilder &builder, Location loc,
900 Value base, Value multiel, Value index) const {
901 PtrExtent extent = findPtrExtent(base);
902 Value size = normalizeToI64(builder, loc, extent.size);
903 index = normalizeToI64(builder, loc, index);
904
905 Value offset = arith::MulIOp::create(builder, loc, size, index);
906 Value gep =
907 LLVM::GEPOp::create(builder, loc, multiel.getType(),
908 builder.getI8Type(), multiel, ValueRange{offset});
909
910 return gep;
911 }
912
913 Value batchAllocate(Type self, OpBuilder &builder, Location loc, Value base,
914 OpFoldResult size) const {
915 PtrExtent extent = findPtrExtent(base);
916
917 Value numel = dyn_cast<Value>(size);
918 if (auto attr = dyn_cast<Attribute>(size)) {
919 numel = arith::ConstantOp::materialize(builder, attr,
920 builder.getI64Type(), loc);
921 }
922
923 Value baseSize = normalizeToI64(builder, loc, extent.size);
924 Value totalSize = arith::MulIOp::create(builder, loc, numel, baseSize);
925
926 Value ptr = llvm_ext::AllocOp::create(builder, loc, extent.ptr.getType(),
927 totalSize);
928
929 return ptr;
930 }
931};
932
933class StructTypeInterface
934 : public AutoDiffTypeInterface::ExternalModel<StructTypeInterface,
935 LLVM::LLVMStructType> {
936public:
937 mlir::Attribute createNullAttr(mlir::Type self) const {
938 llvm::errs() << " unsupported: createNullAttribute of LLVMStructType\n";
939 return nullptr;
940 }
941
942 mlir::Value createNullValue(mlir::Type self, OpBuilder &builder,
943 Location loc) const {
944 auto structTy = cast<LLVM::LLVMStructType>(self);
945 Value result = LLVM::PoisonOp::create(builder, loc, structTy);
946 for (auto &&[i, elemTy] : llvm::enumerate(structTy.getBody())) {
947 auto elemIface = dyn_cast<AutoDiffTypeInterface>(elemTy);
948 if (!elemIface) {
949 Value zero = LLVM::ZeroOp::create(builder, loc, elemTy);
950 result = LLVM::InsertValueOp::create(builder, loc, result, zero, i);
951 continue;
952 }
953 Value nullElem = elemIface.createNullValue(builder, loc);
954 result = LLVM::InsertValueOp::create(builder, loc, result, nullElem, i);
955 }
956 return result;
957 }
958
959 Value createAddOp(Type self, OpBuilder &builder, Location loc, Value a,
960 Value b) const {
961 auto structTy = cast<LLVM::LLVMStructType>(self);
962 Value result = LLVM::PoisonOp::create(builder, loc, structTy);
963 for (auto &&[i, elemTy] : llvm::enumerate(structTy.getBody())) {
964 Value aElem = LLVM::ExtractValueOp::create(builder, loc, a, i);
965 Value bElem = LLVM::ExtractValueOp::create(builder, loc, b, i);
966 auto elemIface = dyn_cast<AutoDiffTypeInterface>(elemTy);
967 Value sum;
968 if (elemIface) {
969 sum = elemIface.createAddOp(builder, loc, aElem, bElem);
970 } else {
971 sum = aElem;
972 }
973 result = LLVM::InsertValueOp::create(builder, loc, result, sum, i);
974 }
975 return result;
976 }
977
978 Value createConjOp(Type self, OpBuilder &builder, Location loc,
979 Value a) const {
980 llvm_unreachable("TODO");
981 }
982
983 Type getShadowType(Type self, unsigned width) const {
984 assert(width == 1 && "unsupported width != 1");
985 return self;
986 }
987
988 bool isMutable(Type self) const { return false; }
989
990 LogicalResult zeroInPlace(Type self, OpBuilder &builder, Location loc,
991 Value val) const {
992 return failure();
993 }
994
995 bool isZero(Type self, Value val) const { return false; }
996 bool isZeroAttr(Type self, Attribute attr) const { return false; }
997};
998
999static Value packIntoStruct(ValueRange values, OpBuilder &builder,
1000 Location loc) {
1001 SmallVector<Type> resultTypes =
1002 llvm::map_to_vector(values, [](Value v) { return v.getType(); });
1003 auto structType =
1004 LLVM::LLVMStructType::getLiteral(builder.getContext(), resultTypes);
1005 Value result = LLVM::PoisonOp::create(builder, loc, structType);
1006 for (auto &&[i, v] : llvm::enumerate(values))
1007 result = LLVM::InsertValueOp::create(builder, loc, result, v, i);
1008
1009 return result;
1010}
1011
1012class AutoDiffLLVMFuncOpFunctionInterface
1013 : public AutoDiffFunctionInterface::ExternalModel<
1014 AutoDiffLLVMFuncOpFunctionInterface, LLVM::LLVMFuncOp> {
1015public:
1016 // A comdat says which of the identical copies of a symbol the linker should
1017 // keep, and it keeps or discards the whole group at once. The derivative is
1018 // cloned from the primal and so arrives holding the primal's comdat, which
1019 // puts it in that group -- but only the translation units that differentiate
1020 // the primal put a derivative in it. A unit that merely calls the primal
1021 // offers a group of one under the same key, and if that is the copy the
1022 // linker keeps, the derivative goes with the copy it discarded and every call
1023 // to it is left undefined.
1024 //
1025 // The derivative is its own symbol and wants its own group: keyed on its own
1026 // name, it still dedupes against the other units that built the same
1027 // derivative, and nothing else can take it away.
1028 void detachFromPrimalDefinition(Operation *self) const {
1029 auto fn = cast<LLVM::LLVMFuncOp>(self);
1030 SymbolRefAttr comdat = fn.getComdatAttr();
1031 if (!comdat)
1032 return;
1033
1034 // Follow the primal's selector to the table holding it, and to the kind of
1035 // deduplication it asked for.
1036 auto selector =
1037 SymbolTable::lookupNearestSymbolFrom<LLVM::ComdatSelectorOp>(self,
1038 comdat);
1039 if (!selector)
1040 return;
1041 auto table = dyn_cast<LLVM::ComdatOp>(selector->getParentOp());
1042 if (!table)
1043 return;
1044
1045 StringRef name = fn.getSymName();
1046 SymbolTable selectors(table);
1047 if (!selectors.lookup(name)) {
1048 OpBuilder builder(table.getContext());
1049 builder.setInsertionPointToEnd(&table.getBody().back());
1050 LLVM::ComdatSelectorOp::create(builder, selector.getLoc(), name,
1051 selector.getComdat(),
1052 /*sym_visibility=*/nullptr);
1053 }
1054 fn.setComdatAttr(
1055 SymbolRefAttr::get(table.getSymNameAttr(),
1056 {FlatSymbolRefAttr::get(fn.getContext(), name)}));
1057 }
1058
1059 void transformResultTypes(Operation *self,
1060 SmallVectorImpl<Type> &resultTypes) const {
1061 auto fn = cast<mlir::FunctionOpInterface>(self);
1062 auto FTy = fn.getFunctionType();
1063 if (resultTypes.empty()) {
1064 // llvm.func ops that return no results need to explicitly return
1065 // LLVMVoidType
1066 resultTypes.push_back(LLVM::LLVMVoidType::get(FTy.getContext()));
1067 } else if (resultTypes.size() > 1) {
1068 auto structType =
1069 LLVM::LLVMStructType::getLiteral(FTy.getContext(), resultTypes);
1070 resultTypes.clear();
1071 resultTypes.push_back(structType);
1072 }
1073 }
1074
1075 Operation *createCall(Operation *self, OpBuilder &builder, Location loc,
1076 ValueRange args) const {
1077 return LLVM::CallOp::create(builder, loc, cast<LLVM::LLVMFuncOp>(self),
1078 args);
1079 }
1080
1081 Operation *createReturn(Operation *self, OpBuilder &builder, Location loc,
1082 ValueRange retargs) const {
1083 if (retargs.size() > 1) {
1084 Value packedReturns = packIntoStruct(retargs, builder, loc);
1085 return LLVM::ReturnOp::create(builder, loc, packedReturns);
1086 }
1087
1088 return LLVM::ReturnOp::create(builder, loc, retargs);
1089 }
1090};
1091
1092} // namespace
1093
1095 DialectRegistry &registry) {
1096 registry.addExtension(+[](MLIRContext *context, LLVM::LLVMDialect *) {
1097 registerInterfaces(context);
1098 LLVM::LLVMPointerType::attachInterface<PointerTypeInterface>(*context);
1099 LLVM::LLVMPointerType::attachInterface<PointerClonableTypeInterface>(
1100 *context);
1101 LLVM::LLVMStructType::attachInterface<StructTypeInterface>(*context);
1102 LLVM::LLVMArrayType::attachInterface<ArrayTypeInterface>(*context);
1103
1104 LLVM::LifetimeStartOp::attachInterface<
1105 LifetimeForwardInterface<LLVM::LifetimeStartOp>>(*context);
1106 LLVM::LifetimeEndOp::attachInterface<
1107 LifetimeForwardInterface<LLVM::LifetimeEndOp>>(*context);
1108 LLVM::DbgValueOp::attachInterface<
1109 NoTangentForwardInterface<LLVM::DbgValueOp>>(*context);
1110 LLVM::DbgDeclareOp::attachInterface<
1111 NoTangentForwardInterface<LLVM::DbgDeclareOp>>(*context);
1112 LLVM::DbgLabelOp::attachInterface<
1113 NoTangentForwardInterface<LLVM::DbgLabelOp>>(*context);
1114 LLVM::DbgValueOp::attachInterface<
1115 NoAdjointReverseInterface<LLVM::DbgValueOp>>(*context);
1116 LLVM::DbgDeclareOp::attachInterface<
1117 NoAdjointReverseInterface<LLVM::DbgDeclareOp>>(*context);
1118 LLVM::DbgLabelOp::attachInterface<
1119 NoAdjointReverseInterface<LLVM::DbgLabelOp>>(*context);
1120 LLVM::MemsetOp::attachInterface<MemsetForwardInterface>(*context);
1121 LLVM::MemcpyOp::attachInterface<MemcpyForwardInterface>(*context);
1122 LLVM::SelectOp::attachInterface<SelectActivityInterface>(*context);
1123 LLVM::StoreOp::attachInterface<LLVMStoreLike>(*context);
1124 LLVM::LoadOp::attachInterface<LoadOpInterfaceReverse>(*context);
1125 LLVM::StoreOp::attachInterface<StoreOpInterfaceReverse>(*context);
1126 LLVM::GEPOp::attachInterface<GEPOpInterfaceReverse>(*context);
1127 LLVM::GEPOp::attachInterface<GEPOpOffsetInterface>(*context);
1128 LLVM::BitcastOp::attachInterface<BitcastOpViewInterface>(*context);
1129 LLVM::BitcastOp::attachInterface<
1130 OffsetViewInterface::Model<LLVM::BitcastOp>>(*context);
1131 LLVM::AddrSpaceCastOp::attachInterface<AddrSpaceCastOpInterfaceReverse>(
1132 *context);
1133 LLVM::AddrSpaceCastOp::attachInterface<
1134 OffsetViewInterface::Model<LLVM::AddrSpaceCastOp>>(*context);
1135 LLVM::ExtractValueOp::attachInterface<ExtractValueOpInterfaceReverse>(
1136 *context);
1137 LLVM::InsertValueOp::attachInterface<InsertValueOpInterfaceReverse>(
1138 *context);
1139 LLVM::UnreachableOp::template attachInterface<
1141 LLVM::LLVMFuncOp::attachInterface<AutoDiffLLVMFuncOpFunctionInterface>(
1142 *context);
1143 });
1144}
Type getShadowType(Type type, unsigned width)
Operation * clone(Operation *src, IRMapping &mapper, Operation::CloneOptions options, std::map< Operation *, Operation * > &opMap)
static bool isZero(llvm::Constant *cst)
static std::string str(AugmentedStruct c)
Definition EnzymeLogic.h:62
static bool isMutable(Type type)
Definition Ops.cpp:311
mlir::Value diffe(mlir::Value origv, mlir::OpBuilder &builder)
void zeroDiffe(mlir::Value origv, mlir::OpBuilder &builder)
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 ...
mlir::Type getShadowType(mlir::Type T)
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
Value getBaseObject(Value v, bool offsetAllowed)
Definition Utils.cpp:123
Value inactiveStoredValueShadow(Operation *orig, MGradientUtils &gutils, Value stored, OpBuilder &builder)
Definition Utils.cpp:104
void registerLLVMDialectAutoDiffInterface(DialectRegistry &registry)