| //===- GradientUtils.h - Helper class and utilities for AD ---------===// | |
| // | |
| // Enzyme Project | |
| // | |
| // Part of the Enzyme Project, under the Apache License v2.0 with LLVM | |
| // Exceptions. See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| // If using this code in an academic setting, please cite the following: | |
| // @incollection{enzymeNeurips, | |
| // title = {Instead of Rewriting Foreign Code for Machine Learning, | |
| // Automatically Synthesize Fast Gradients}, | |
| // author = {Moses, William S. and Churavy, Valentin}, | |
| // booktitle = {Advances in Neural Information Processing Systems 33}, | |
| // year = {2020}, | |
| // note = {To appear in}, | |
| // } | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file declares two helper classes GradientUtils and subclass | |
| // DiffeGradientUtils. These classes contain utilities for managing the cache, | |
| // recomputing statements, and in the case of DiffeGradientUtils, managing | |
| // adjoint values and shadow pointers. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #ifndef ENZYME_GUTILS_H_ | |
| #define ENZYME_GUTILS_H_ | |
| #include <functional> | |
| #include <map> | |
| #include <string> | |
| #include <llvm/Config/llvm-config.h> | |
| #if LLVM_VERSION_MAJOR >= 16 | |
| #define private public | |
| #include "llvm/Analysis/ScalarEvolution.h" | |
| #include "llvm/Transforms/Utils/ScalarEvolutionExpander.h" | |
| #undef private | |
| #else | |
| #include "SCEV/ScalarEvolution.h" | |
| #include "SCEV/ScalarEvolutionExpander.h" | |
| #endif | |
| #include "llvm/ADT/ArrayRef.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/IR/BasicBlock.h" | |
| #include "llvm/IR/DebugInfoMetadata.h" | |
| #include "llvm/IR/Dominators.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/Instructions.h" | |
| #include "llvm/IR/Type.h" | |
| #include "llvm/IR/Value.h" | |
| #include "llvm/Analysis/AliasAnalysis.h" | |
| #include "llvm/Analysis/LoopInfo.h" | |
| #include "llvm/Analysis/PostDominators.h" | |
| #include "llvm/Analysis/ValueTracking.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/ErrorHandling.h" | |
| #include "ActivityAnalysis.h" | |
| #include "CacheUtility.h" | |
| #include "EnzymeLogic.h" | |
| #include "Utils.h" | |
| #include "llvm-c/Core.h" | |
| class GradientUtils; | |
| extern llvm::StringMap<std::function<llvm::Value *( | |
| llvm::IRBuilder<> &, llvm::CallInst *, llvm::ArrayRef<llvm::Value *>, | |
| GradientUtils *)>> | |
| shadowHandlers; | |
| class DiffeGradientUtils; | |
| extern llvm::StringMap<std::pair< | |
| std::function<bool(llvm::IRBuilder<> &, llvm::CallInst *, GradientUtils &, | |
| llvm::Value *&, llvm::Value *&, llvm::Value *&)>, | |
| std::function<void(llvm::IRBuilder<> &, llvm::CallInst *, | |
| DiffeGradientUtils &, llvm::Value *)>>> | |
| customCallHandlers; | |
| extern llvm::StringMap< | |
| std::function<bool(llvm::IRBuilder<> &, llvm::CallInst *, GradientUtils &, | |
| llvm::Value *&, llvm::Value *&)>> | |
| customFwdCallHandlers; | |
| constexpr int IndexMappingError = 0x0000fffd; | |
| /// Classification of what type of use is requested | |
| enum class QueryType { | |
| // The original value is needed for the derivative | |
| Primal = 0, | |
| // The shadow value is needed for the derivative | |
| Shadow = 1, | |
| // The primal value is needed to stand in for the shadow | |
| // value and compute the derivative of an instruction | |
| ShadowByConstPrimal = 2 | |
| }; | |
| typedef std::pair<const llvm::Value *, QueryType> UsageKey; | |
| extern "C" { | |
| extern llvm::cl::opt<bool> EnzymeInactiveDynamic; | |
| extern llvm::cl::opt<bool> EnzymeFreeInternalAllocations; | |
| extern llvm::cl::opt<bool> EnzymeRematerialize; | |
| } | |
| extern llvm::SmallVector<unsigned int, 9> MD_ToCopy; | |
| struct InvertedPointerConfig : llvm::ValueMapConfig<const llvm::Value *> { | |
| typedef GradientUtils *ExtraData; | |
| static void onDelete(ExtraData gutils, const llvm::Value *old); | |
| }; | |
| class InvertedPointerVH final : public llvm::CallbackVH { | |
| public: | |
| GradientUtils *gutils; | |
| InvertedPointerVH(GradientUtils *gutils) : gutils(gutils) {} | |
| InvertedPointerVH(GradientUtils *gutils, llvm::Value *V) | |
| : InvertedPointerVH(gutils) { | |
| setValPtr(V); | |
| } | |
| void deleted() override final; | |
| void allUsesReplacedWith(llvm::Value *new_value) override final { | |
| setValPtr(new_value); | |
| } | |
| virtual ~InvertedPointerVH() {} | |
| }; | |
| enum class AugmentedStruct; | |
| class GradientUtils : public CacheUtility { | |
| public: | |
| EnzymeLogic &Logic; | |
| bool AtomicAdd; | |
| DerivativeMode mode; | |
| llvm::Function *oldFunc; | |
| llvm::ValueMap<const llvm::Value *, InvertedPointerVH> invertedPointers; | |
| llvm::DominatorTree *OrigDT; | |
| llvm::PostDominatorTree *OrigPDT; | |
| llvm::LoopInfo *OrigLI; | |
| llvm::ScalarEvolution *OrigSE; | |
| /// (Original) Blocks which dominate all returns | |
| llvm::SmallPtrSet<llvm::BasicBlock *, 4> BlocksDominatingAllReturns; | |
| llvm::SmallPtrSet<llvm::BasicBlock *, 4> notForAnalysis; | |
| std::shared_ptr<ActivityAnalyzer> ATA; | |
| llvm::SmallVector<llvm::BasicBlock *, 12> originalBlocks; | |
| /// Allocations which are known to always be freed before the | |
| /// reverse, to the list of frees that must apply to this allocation. | |
| llvm::ValueMap<const llvm::CallInst *, | |
| llvm::SmallPtrSet<const llvm::CallInst *, 1>> | |
| allocationsWithGuaranteedFree; | |
| /// Frees which can always be eliminated as the post dominate | |
| /// an allocation (which will itself be freed). | |
| llvm::SmallPtrSet<const llvm::CallInst *, 1> postDominatingFrees; | |
| /// Deallocations that should be kept in the forward pass because | |
| /// they deallocation memory which isn't necessary for the reverse | |
| /// pass | |
| llvm::SmallPtrSet<const llvm::CallInst *, 1> forwardDeallocations; | |
| /// Map of primal block to corresponding block(s) in reverse | |
| std::map<llvm::BasicBlock *, llvm::SmallVector<llvm::BasicBlock *, 4>> | |
| reverseBlocks; | |
| /// Map of block in reverse to corresponding primal block | |
| std::map<llvm::BasicBlock *, llvm::BasicBlock *> reverseBlockToPrimal; | |
| /// A set of tape extractions to enforce a cache of | |
| /// rather than attempting to recompute. | |
| llvm::SmallPtrSet<llvm::Instruction *, 4> TapesToPreventRecomputation; | |
| llvm::ValueMap<llvm::PHINode *, llvm::WeakTrackingVH> fictiousPHIs; | |
| llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> originalToNewFn; | |
| llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> newToOriginalFn; | |
| llvm::SmallVector<llvm::CallInst *, 4> originalCalls; | |
| llvm::SmallPtrSet<llvm::Instruction *, 4> unnecessaryIntermediates; | |
| /// Loop header PHIs (of the original function) that are cached for the | |
| /// reverse pass, but whose value is only required in the reverse pass by | |
| /// users outside of their loop. Such users can only observe the value of | |
| /// the final iteration, so a single cache slot per iteration of the | |
| /// enclosing loops (overwritten on every iteration of the PHI's own loop) | |
| /// suffices, instead of a cache with one entry per iteration. Computed when | |
| /// creating the augmented forward pass and copied from its AugmentedReturn | |
| /// for the passes that consume its tape. | |
| llvm::SmallPtrSet<const llvm::Instruction *, 4> cacheOnlyLastIteration; | |
| const std::map<llvm::Instruction *, bool> *can_modref_map; | |
| const std::map<llvm::CallInst *, std::pair<bool, const std::vector<bool>>> | |
| *overwritten_args_map_ptr; | |
| const llvm::SmallPtrSetImpl<const llvm::Value *> *unnecessaryValuesP; | |
| llvm::SmallVector<llvm::OperandBundleDef, 2> getInvertedBundles( | |
| llvm::CallInst *orig, llvm::ArrayRef<ValueType> types, | |
| llvm::IRBuilder<> &Builder2, bool lookup, | |
| const llvm::ValueToValueMapTy &available = llvm::ValueToValueMapTy()); | |
| bool usedInRooting(const llvm::CallBase *orig, | |
| llvm::ArrayRef<ValueType> types, const llvm::Value *val, | |
| bool shadow) const; | |
| llvm::Value *getNewIfOriginal(llvm::Value *originst) const; | |
| llvm::Value *tid; | |
| llvm::Value *ompThreadId(); | |
| llvm::Value *numThreads; | |
| llvm::Value *ompNumThreads(); | |
| llvm::Value *getOrInsertTotalMultiplicativeProduct(llvm::Value *val, | |
| LoopContext &lc); | |
| llvm::Value *getOrInsertConditionalIndex(llvm::Value *val, LoopContext &lc, | |
| bool pickTrue); | |
| bool assumeDynamicLoopOfSizeOne(llvm::Loop *L) const override; | |
| llvm::DebugLoc getNewFromOriginal(const llvm::DebugLoc L) const; | |
| llvm::Value *getNewFromOriginal(const llvm::Value *originst) const; | |
| llvm::Instruction *getNewFromOriginal(const llvm::Instruction *newinst) const; | |
| llvm::BasicBlock *getNewFromOriginal(const llvm::BasicBlock *newinst) const; | |
| llvm::Value *hasUninverted(const llvm::Value *inverted) const; | |
| llvm::BasicBlock *getOriginalFromNew(const llvm::BasicBlock *newinst) const; | |
| llvm::Value *isOriginal(const llvm::Value *newinst) const; | |
| llvm::Instruction *isOriginal(const llvm::Instruction *newinst) const; | |
| llvm::BasicBlock *isOriginal(const llvm::BasicBlock *newinst) const; | |
| struct LoadLikeCall { | |
| llvm::CallInst *loadCall; | |
| llvm::Value *operand; | |
| LoadLikeCall() = default; | |
| LoadLikeCall(llvm::CallInst *a, llvm::Value *b) : loadCall(a), operand(b) {} | |
| }; | |
| struct Rematerializer { | |
| // Loads which may need to be rematerialized. | |
| llvm::SmallVector<llvm::LoadInst *, 1> loads; | |
| // Loads-like calls which need the memory initialized for the reverse. | |
| llvm::SmallVector<LoadLikeCall, 1> loadLikeCalls; | |
| // Operations which must be rerun to rematerialize | |
| // the value. | |
| llvm::SmallPtrSet<llvm::Instruction *, 1> stores; | |
| // Operations which deallocate the value. | |
| llvm::SmallPtrSet<llvm::Instruction *, 1> frees; | |
| // Loop scope (null if not loop scoped). | |
| llvm::Loop *LI; | |
| // If non-null, a call which writes to the value which cannot be reproduced | |
| // in the reverse pass. If any values of this allocation are needed in the | |
| // reverse pass and this is non-null, this allocation cannot be | |
| // rematerialized. | |
| llvm::CallInst *nonRepeatableWritingCall; | |
| Rematerializer() : loads(), stores(), frees(), LI(nullptr) {} | |
| Rematerializer(llvm::ArrayRef<llvm::LoadInst *> loads, | |
| llvm::ArrayRef<LoadLikeCall> loadLikeCalls, | |
| const llvm::SmallPtrSetImpl<llvm::Instruction *> &stores, | |
| const llvm::SmallPtrSetImpl<llvm::Instruction *> &frees, | |
| llvm::Loop *LI, llvm::CallInst *nonRepeatableWritingCall) | |
| : loads(loads.begin(), loads.end()), | |
| loadLikeCalls(loadLikeCalls.begin(), loadLikeCalls.end()), | |
| stores(stores.begin(), stores.end()), | |
| frees(frees.begin(), frees.end()), LI(LI), | |
| nonRepeatableWritingCall(nonRepeatableWritingCall) {} | |
| }; | |
| struct ShadowRematerializer { | |
| /// Operations which must be rerun to rematerialize | |
| /// the original value. | |
| llvm::SmallPtrSet<llvm::Instruction *, 1> stores; | |
| /// Operations which deallocate the value. | |
| llvm::SmallPtrSet<llvm::Instruction *, 1> frees; | |
| /// Whether the shadow must be initialized in the primal. | |
| bool primalInitialize; | |
| /// Loop scope (null if not loop scoped). | |
| llvm::Loop *LI; | |
| ShadowRematerializer() | |
| : stores(), frees(), primalInitialize(), LI(nullptr) {} | |
| ShadowRematerializer( | |
| const llvm::SmallPtrSetImpl<llvm::Instruction *> &stores, | |
| const llvm::SmallPtrSetImpl<llvm::Instruction *> &frees, | |
| bool primalInitialize, llvm::Loop *LI) | |
| : stores(stores.begin(), stores.end()), | |
| frees(frees.begin(), frees.end()), primalInitialize(primalInitialize), | |
| LI(LI) {} | |
| }; | |
| llvm::ValueMap<llvm::Value *, Rematerializer> rematerializableAllocations; | |
| llvm::SmallPtrSet<llvm::Value *, 4> allocationsToBeRematerialized; | |
| /// Only loaded from and stored to (not captured), mapped to the stores (and | |
| /// memset). Boolean denotes whether the primal initializes the shadow as well | |
| /// (for use) as a structure which carries data. | |
| llvm::ValueMap<llvm::Value *, ShadowRematerializer> backwardsOnlyShadows; | |
| std::vector<bool> nowrite_shadows; | |
| void computeForwardingProperties(llvm::Instruction *V); | |
| void computeGuaranteedFrees(); | |
| private: | |
| llvm::SmallVector<llvm::WeakTrackingVH, 4> addedTapeVals; | |
| unsigned tapeidx; | |
| llvm::Value *tape; | |
| std::map<llvm::BasicBlock *, | |
| llvm::ValueMap<llvm::Value *, | |
| std::map<llvm::BasicBlock *, llvm::WeakTrackingVH>>> | |
| unwrap_cache; | |
| std::map<llvm::BasicBlock *, | |
| llvm::ValueMap<llvm::Value *, llvm::WeakTrackingVH>> | |
| lookup_cache; | |
| public: | |
| void replaceAndRemoveUnwrapCacheFor(llvm::Value *A, llvm::Value *B); | |
| llvm::BasicBlock *addReverseBlock(llvm::BasicBlock *currentBlock, | |
| llvm::Twine const &name, | |
| bool forkCache = true, bool push = true); | |
| std::map<UsageKey, bool> populateSeenFromKnownRecompute() const { | |
| std::map<UsageKey, bool> Seen; | |
| for (auto pair : knownRecomputeHeuristic) { | |
| if (!pair.second || unnecessaryIntermediates.count( | |
| llvm::cast<llvm::Instruction>(pair.first))) { | |
| Seen[UsageKey(pair.first, QueryType::Primal)] = false; | |
| } | |
| } | |
| return Seen; | |
| } | |
| bool legalRecompute(const llvm::Value *val, | |
| const llvm::ValueToValueMapTy &available, | |
| llvm::IRBuilder<> *BuilderM, bool reverse = false, | |
| bool legalRecomputeCache = true) const; | |
| std::map<const llvm::Value *, bool> knownRecomputeHeuristic; | |
| bool shouldRecompute(const llvm::Value *val, | |
| const llvm::ValueToValueMapTy &available, | |
| llvm::IRBuilder<> *BuilderM); | |
| llvm::ValueMap<const llvm::Instruction *, AssertingReplacingVH> | |
| unwrappedLoads; | |
| void replaceAWithB(llvm::Value *A, llvm::Value *B, | |
| bool storeInCache = false) override; | |
| void erase(llvm::Instruction *I) override; | |
| void eraseWithPlaceholder(llvm::Instruction *I, llvm::Instruction *orig, | |
| const llvm::Twine &suffix = "_replacementA", | |
| bool erase = true); | |
| // TODO consider invariant group and/or valueInvariant group | |
| void setTape(llvm::Value *newtape); | |
| void dumpPointers(); | |
| int getIndex( | |
| std::pair<llvm::Instruction *, CacheType> idx, | |
| const std::map<std::pair<llvm::Instruction *, CacheType>, int> &mapping, | |
| llvm::IRBuilder<> &); | |
| int getIndex( | |
| std::pair<llvm::Instruction *, CacheType> idx, | |
| std::map<std::pair<llvm::Instruction *, CacheType>, int> &mapping, | |
| llvm::IRBuilder<> &); | |
| llvm::BasicBlock *getCacheScopeForReverse(llvm::Value *malloc); | |
| llvm::Value *cacheForReverse(llvm::IRBuilder<> &BuilderQ, llvm::Value *malloc, | |
| int idx, bool replace = true); | |
| llvm::ArrayRef<llvm::WeakTrackingVH> getTapeValues() const { | |
| return addedTapeVals; | |
| } | |
| public: | |
| llvm::AAResults *OrigAA; | |
| TypeAnalysis &TA; | |
| TypeResults TR; | |
| bool omp; | |
| bool runtimeActivity; | |
| // Whether to use additional checks to ensure correct behavior when handling | |
| // functions with inf | |
| bool strongZero; | |
| private: | |
| unsigned width; | |
| public: | |
| unsigned getWidth() { return width; } | |
| bool shadowReturnUsed; | |
| llvm::ArrayRef<DIFFE_TYPE> ArgDiffeTypes; | |
| public: | |
| GradientUtils(EnzymeLogic &Logic, llvm::Function *newFunc_, | |
| llvm::Function *oldFunc_, llvm::TargetLibraryInfo &TLI_, | |
| TypeAnalysis &TA_, TypeResults TR_, | |
| llvm::ValueToValueMapTy &invertedPointers_, | |
| const llvm::SmallPtrSetImpl<llvm::Value *> &constantvalues_, | |
| const llvm::SmallPtrSetImpl<llvm::Value *> &activevals_, | |
| DIFFE_TYPE ReturnActivity, bool shadowReturnUsed, | |
| llvm::ArrayRef<DIFFE_TYPE> ArgDiffeTypes_, | |
| llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> | |
| &originalToNewFn_, | |
| DerivativeMode mode, bool runtimeActivity, bool strongZero, | |
| unsigned width, bool omp); | |
| public: | |
| DIFFE_TYPE getDiffeType(llvm::Value *v, bool foreignFunction) const; | |
| DIFFE_TYPE getReturnDiffeType(llvm::Value *orig, bool *primalReturnUsedP, | |
| bool *shadowReturnUsedP, | |
| DerivativeMode cmode) const; | |
| DIFFE_TYPE getReturnDiffeType(llvm::Value *orig, bool *primalReturnUsedP, | |
| bool *shadowReturnUsedP) const; | |
| static GradientUtils * | |
| CreateFromClone(EnzymeLogic &Logic, bool runtimeActivity, bool strongZero, | |
| unsigned width, llvm::Function *todiff, | |
| llvm::TargetLibraryInfo &TLI, TypeAnalysis &TA, | |
| FnTypeInfo &oldTypeInfo, DIFFE_TYPE retType, | |
| llvm::ArrayRef<DIFFE_TYPE> constant_args, bool returnUsed, | |
| bool shadowReturnUsed, | |
| std::map<AugmentedStruct, int> &returnMapping, bool omp); | |
| llvm::ValueMap<const llvm::Value *, llvm::MDNode *> | |
| differentialAliasScopeDomains; | |
| llvm::ValueMap<const llvm::Value *, llvm::DenseMap<ssize_t, llvm::MDNode *>> | |
| differentialAliasScope; | |
| llvm::MDNode *getDerivativeAliasScope(const llvm::Value *origptr, | |
| ssize_t newptr); | |
| void setPtrDiffe(llvm::Instruction *orig, llvm::Value *ptr, | |
| llvm::Value *newval, llvm::IRBuilder<> &BuilderM, | |
| llvm::MaybeAlign align, unsigned start, unsigned size, | |
| bool isVolatile, llvm::AtomicOrdering ordering, | |
| llvm::SyncScope::ID syncScope, llvm::Value *mask, | |
| llvm::ArrayRef<llvm::Metadata *> noAlias, | |
| llvm::ArrayRef<llvm::Metadata *> scopes, | |
| bool needs_post_cache = false); | |
| private: | |
| llvm::BasicBlock *originalForReverseBlock(llvm::BasicBlock &BB2) const; | |
| public: | |
| //! This cache stores blocks we may insert as part of getReverseOrLatchMerge | |
| //! to handle inverse iv iteration | |
| // As we don't want to create redundant blocks, we use this convenient cache | |
| std::map<std::tuple<llvm::BasicBlock *, llvm::BasicBlock *>, | |
| llvm::BasicBlock *> | |
| newBlocksForLoop_cache; | |
| //! This cache stores a rematerialized forward pass in the loop | |
| //! specified. The key is the loop header. | |
| std::map<const llvm::BasicBlock *, llvm::BasicBlock *> | |
| rematerializedLoops_cache; | |
| llvm::BasicBlock *getReverseOrLatchMerge(llvm::BasicBlock *BB, | |
| llvm::BasicBlock *branchingBlock); | |
| private: | |
| //! Given a loop `lc`, create the rematerialization blocks for the reverse | |
| //! pass, if required, caching if already created. This function will return | |
| //! the new block for the rematerialized loop entry to branch to, if created. | |
| //! Otherwise it will return nullptr. | |
| llvm::BasicBlock *prepRematerializedLoopEntry(LoopContext &lc); | |
| public: | |
| void forceContexts(); | |
| void computeMinCache(); | |
| bool isOriginalBlock(const llvm::BasicBlock &BB) const; | |
| llvm::SmallVector<llvm::Instruction *, 1> | |
| rematerializedPrimalOrShadowAllocations; | |
| void eraseFictiousPHIs(); | |
| void forceActiveDetection(); | |
| bool isConstantValue(llvm::Value *val) const; | |
| bool isConstantInstruction(const llvm::Instruction *inst) const; | |
| bool getContext(llvm::BasicBlock *BB, LoopContext &lc); | |
| void forceAugmentedReturns(); | |
| private: | |
| // For a given value, a list of basic blocks where an unwrap to has already | |
| // produced a warning. | |
| std::map<llvm::Instruction *, std::set<llvm::BasicBlock *>> UnwrappedWarnings; | |
| public: | |
| /// if full unwrap, don't just unwrap this instruction, but also its operands, | |
| /// etc | |
| llvm::Value *unwrapM(llvm::Value *const val, llvm::IRBuilder<> &BuilderM, | |
| const llvm::ValueToValueMapTy &available, | |
| UnwrapMode unwrapMode, llvm::BasicBlock *scope = nullptr, | |
| bool permitCache = true) override final; | |
| void ensureLookupCached(llvm::Instruction *inst, bool shouldFree = true, | |
| llvm::BasicBlock *scope = nullptr, | |
| llvm::MDNode *TBAA = nullptr); | |
| std::map<llvm::Instruction *, | |
| llvm::ValueMap<llvm::BasicBlock *, llvm::WeakTrackingVH>> | |
| lcssaFixes; | |
| std::map<llvm::PHINode *, llvm::WeakTrackingVH> lcssaPHIToOrig; | |
| llvm::Value *fixLCSSA(llvm::Instruction *inst, llvm::BasicBlock *forwardBlock, | |
| bool legalInBlock = false); | |
| llvm::Value *lookupM(llvm::Value *val, llvm::IRBuilder<> &BuilderM, | |
| const llvm::ValueToValueMapTy &incoming_availalble = | |
| llvm::ValueToValueMapTy(), | |
| bool tryLegalRecomputeCheck = true, | |
| llvm::BasicBlock *scope = nullptr) override; | |
| llvm::Value *invertPointerM(llvm::Value *val, llvm::IRBuilder<> &BuilderM); | |
| llvm::Value *invertPointerM(llvm::Value *val, llvm::IRBuilder<> &BuilderM, | |
| TypeTree look); | |
| bool isAtomic(llvm::Value *origptr) const; | |
| static llvm::Constant *GetOrCreateShadowConstant( | |
| RequestContext context, EnzymeLogic &Logic, llvm::TargetLibraryInfo &TLI, | |
| TypeAnalysis &TA, llvm::Constant *F, DerivativeMode mode, | |
| bool runtimeActivity, bool strongZero, unsigned width, bool AtomicAdd); | |
| static llvm::Constant *GetOrCreateShadowFunction( | |
| RequestContext context, EnzymeLogic &Logic, llvm::TargetLibraryInfo &TLI, | |
| TypeAnalysis &TA, llvm::Function *F, DerivativeMode mode, | |
| bool runtimeActivity, bool strongZero, unsigned width, bool AtomicAdd); | |
| void branchToCorrespondingTarget( | |
| llvm::BasicBlock *ctx, llvm::IRBuilder<> &BuilderM, | |
| const std::map<llvm::BasicBlock *, | |
| std::vector<std::pair</*pred*/ llvm::BasicBlock *, | |
| /*successor*/ llvm::BasicBlock *>>> | |
| &targetToPreds, | |
| const std::map<llvm::BasicBlock *, llvm::PHINode *> *replacePHIs = | |
| nullptr); | |
| void getReverseBuilder(llvm::IRBuilder<> &Builder2, bool original = true); | |
| void getForwardBuilder(llvm::IRBuilder<> &Builder2); | |
| static llvm::Type *getShadowType(llvm::Type *ty, unsigned width); | |
| llvm::Type *getShadowType(llvm::Type *ty); | |
| //! Helper routine to extract a nested element from a struct/array. This is | |
| // a one dimensional special case of the multi-dim extractMeta below. | |
| static llvm::Value *extractMeta(llvm::IRBuilder<> &Builder, llvm::Value *Agg, | |
| unsigned off, const llvm::Twine &name = ""); | |
| //! Helper routine to extract a nested element from a struct/array. Unlike the | |
| // LLVM instruction, this will attempt to re-use the inserted value, if it | |
| // exists, rather than always creating a new instruction. If fallback is | |
| // true (the default), it will create an instruction if it fails to find an | |
| // appropriate existing value, otherwise it returns nullptr. | |
| static llvm::Value *extractMeta(llvm::IRBuilder<> &Builder, llvm::Value *Agg, | |
| llvm::ArrayRef<unsigned> off, | |
| const llvm::Twine &name = "", | |
| bool fallback = true); | |
| //! Helper routine to get the type of an extraction | |
| static llvm::Type *extractMeta(llvm::Type *T, llvm::ArrayRef<unsigned> off); | |
| static llvm::Value *recursiveFAdd(llvm::IRBuilder<> &B, llvm::Value *lhs, | |
| llvm::Value *rhs, | |
| llvm::ArrayRef<unsigned> lhs_off = {}, | |
| llvm::ArrayRef<unsigned> rhs_off = {}, | |
| llvm::Value *prev = nullptr, | |
| bool vectorLayer = false); | |
| /// Unwraps a vector derivative from its internal representation and applies a | |
| /// function f to each element. Return values of f are collected and wrapped. | |
| template <typename Func, typename... Args> | |
| llvm::Value *applyChainRule(llvm::Type *diffType, llvm::IRBuilder<> &Builder, | |
| Func rule, Args... args) { | |
| if (width > 1) { | |
| const int size = sizeof...(args); | |
| llvm::Value *vals[size] = {args...}; | |
| for (size_t i = 0; i < size; ++i) | |
| if (vals[i]) | |
| assert(llvm::cast<llvm::ArrayType>(vals[i]->getType()) | |
| ->getNumElements() == width); | |
| llvm::Type *wrappedType = diffType->isVoidTy() | |
| ? nullptr | |
| : llvm::ArrayType::get(diffType, width); | |
| llvm::Value *res = | |
| diffType->isVoidTy() ? nullptr : llvm::UndefValue::get(wrappedType); | |
| for (unsigned int i = 0; i < getWidth(); ++i) { | |
| auto tup = std::tuple<Args...>{ | |
| (args ? extractMeta(Builder, args, i) : nullptr)...}; | |
| auto diff = std::apply(rule, std::move(tup)); | |
| if (!diffType->isVoidTy()) | |
| res = Builder.CreateInsertValue(res, diff, {i}); | |
| } | |
| return res; | |
| } else { | |
| return rule(args...); | |
| } | |
| } | |
| /// Unwraps a vector derivative from its internal representation and applies a | |
| /// function f to each element. Return values of f are collected and wrapped. | |
| template <typename Func, typename... Args> | |
| void applyChainRule(llvm::IRBuilder<> &Builder, Func rule, Args... args) { | |
| if (width > 1) { | |
| const int size = sizeof...(args); | |
| llvm::Value *vals[size] = {args...}; | |
| for (size_t i = 0; i < size; ++i) | |
| if (vals[i]) | |
| assert(llvm::cast<llvm::ArrayType>(vals[i]->getType()) | |
| ->getNumElements() == width); | |
| for (unsigned int i = 0; i < getWidth(); ++i) { | |
| auto tup = std::tuple<Args...>{ | |
| (args ? extractMeta(Builder, args, i) : nullptr)...}; | |
| std::apply(rule, std::move(tup)); | |
| } | |
| } else { | |
| rule(args...); | |
| } | |
| } | |
| /// Unwraps an collection of constant vector derivatives from their internal | |
| /// representations and applies a function f to each element. | |
| template <typename Func> | |
| llvm::Value *applyChainRule(llvm::Type *diffType, | |
| llvm::ArrayRef<llvm::Constant *> diffs, | |
| llvm::IRBuilder<> &Builder, Func rule) { | |
| if (width > 1) { | |
| #ifndef NDEBUG | |
| for (auto diff : diffs) { | |
| assert(diff); | |
| assert(llvm::cast<llvm::ArrayType>(diff->getType())->getNumElements() == | |
| width); | |
| } | |
| #endif | |
| llvm::Type *wrappedType = llvm::ArrayType::get(diffType, width); | |
| llvm::Value *res = llvm::UndefValue::get(wrappedType); | |
| for (unsigned int i = 0; i < getWidth(); ++i) { | |
| llvm::SmallVector<llvm::Constant *, 3> extracted_diffs; | |
| for (auto diff : diffs) { | |
| extracted_diffs.push_back( | |
| llvm::cast<llvm::Constant>(extractMeta(Builder, diff, i))); | |
| } | |
| auto diff = rule(extracted_diffs); | |
| res = Builder.CreateInsertValue(res, diff, {i}); | |
| } | |
| return res; | |
| } else { | |
| return rule(diffs); | |
| } | |
| } | |
| bool needsCacheWholeAllocation(const llvm::Value *V) const; | |
| }; | |
| void SubTransferHelper(GradientUtils *gutils, DerivativeMode Mode, | |
| llvm::Type *secretty, llvm::Intrinsic::ID intrinsic, | |
| unsigned dstalign, unsigned srcalign, unsigned offset, | |
| bool dstConstant, llvm::Value *shadow_dst, | |
| llvm::Value *primal_dst, bool srcConstant, | |
| llvm::Value *shadow_src, llvm::Value *primal_src, | |
| llvm::Value *length, llvm::Value *isVolatile, | |
| llvm::CallInst *MTI, bool allowForward = true, | |
| bool shadowsLookedUp = false, | |
| bool backwardsShadow = false); | |
| #endif |