| //===- GradientUtils.cpp - 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 define 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. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include <algorithm> |
| #include <functional> |
| #include <map> |
| #include <string> |
| |
| #include "GradientUtils.h" |
| #include "MustExitScalarEvolution.h" |
| #include "Utils.h" |
| |
| #include "DifferentialUseAnalysis.h" |
| #include "LibraryFuncs.h" |
| #include "TypeAnalysis/TBAA.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/MDBuilder.h" |
| #include "llvm/IR/Type.h" |
| #include "llvm/IR/Value.h" |
| |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/SetVector.h" |
| #include "llvm/ADT/SmallPtrSet.h" |
| #include "llvm/ADT/SmallSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringMap.h" |
| |
| #include "llvm/Support/AMDGPUMetadata.h" |
| #include "llvm/Support/Casting.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/TimeProfiler.h" |
| |
| #if LLVM_VERSION_MAJOR >= 14 |
| #define addAttribute addAttributeAtIndex |
| #define hasAttribute hasAttributeAtIndex |
| #endif |
| |
| using namespace llvm; |
| |
| StringMap<std::function<Value *(IRBuilder<> &, CallInst *, ArrayRef<Value *>, |
| GradientUtils *)>> |
| shadowHandlers; |
| StringMap<std::function<CallInst *(IRBuilder<> &, Value *)>> shadowErasers; |
| |
| StringMap< |
| std::pair<std::function<bool(IRBuilder<> &, CallInst *, GradientUtils &, |
| Value *&, Value *&, Value *&)>, |
| std::function<void(IRBuilder<> &, CallInst *, |
| DiffeGradientUtils &, Value *)>>> |
| customCallHandlers; |
| |
| StringMap<std::function<bool(IRBuilder<> &, CallInst *, GradientUtils &, |
| Value *&, Value *&)>> |
| customFwdCallHandlers; |
| |
| extern "C" { |
| llvm::cl::opt<bool> |
| EnzymeNewCache("enzyme-new-cache", cl::init(true), cl::Hidden, |
| cl::desc("Use new cache decision algorithm")); |
| |
| llvm::cl::opt<bool> EnzymeMinCutCache("enzyme-mincut-cache", cl::init(true), |
| cl::Hidden, |
| cl::desc("Use Enzyme Mincut algorithm")); |
| |
| llvm::cl::opt<bool> EnzymeLoopInvariantCache( |
| "enzyme-loop-invariant-cache", cl::init(true), cl::Hidden, |
| cl::desc("Attempt to hoist cache outside of loop")); |
| |
| llvm::cl::opt<bool> EnzymeInactiveDynamic( |
| "enzyme-inactive-dynamic", cl::init(true), cl::Hidden, |
| cl::desc("Force wholy inactive dynamic loops to have 0 iter reverse pass")); |
| |
| llvm::cl::opt<bool> |
| EnzymeSharedForward("enzyme-shared-forward", cl::init(false), cl::Hidden, |
| cl::desc("Forward Shared Memory from definitions")); |
| |
| llvm::cl::opt<bool> |
| EnzymeRegisterReduce("enzyme-register-reduce", cl::init(false), cl::Hidden, |
| cl::desc("Reduce the amount of register reduce")); |
| llvm::cl::opt<bool> |
| EnzymeSpeculatePHIs("enzyme-speculate-phis", cl::init(false), cl::Hidden, |
| cl::desc("Speculatively execute phi computations")); |
| llvm::cl::opt<bool> EnzymeFreeInternalAllocations( |
| "enzyme-free-internal-allocations", cl::init(true), cl::Hidden, |
| cl::desc("Always free internal allocations (disable if allocation needs " |
| "access outside)")); |
| |
| llvm::cl::opt<bool> |
| EnzymeRematerialize("enzyme-rematerialize", cl::init(true), cl::Hidden, |
| cl::desc("Rematerialize allocations/shadows in the " |
| "reverse rather than caching")); |
| |
| llvm::cl::opt<bool> |
| EnzymeVectorSplitPhi("enzyme-vector-split-phi", cl::init(true), cl::Hidden, |
| cl::desc("Split phis according to vector size")); |
| |
| llvm::cl::opt<bool> |
| EnzymePrintDiffUse("enzyme-print-diffuse", cl::init(false), cl::Hidden, |
| cl::desc("Print differential use analysis")); |
| } |
| |
| SmallVector<unsigned int, 9> MD_ToCopy = { |
| LLVMContext::MD_dbg, |
| LLVMContext::MD_tbaa, |
| LLVMContext::MD_tbaa_struct, |
| LLVMContext::MD_range, |
| LLVMContext::MD_nonnull, |
| LLVMContext::MD_dereferenceable, |
| LLVMContext::MD_dereferenceable_or_null}; |
| |
| static bool isPotentialLastLoopValue(llvm::Value *val, |
| const llvm::BasicBlock *loc, |
| const llvm::LoopInfo &LI) { |
| if (llvm::Instruction *inst = llvm::dyn_cast<llvm::Instruction>(val)) { |
| const llvm::Loop *InstLoop = LI.getLoopFor(inst->getParent()); |
| if (InstLoop == nullptr) { |
| return false; |
| } |
| for (const llvm::Loop *L = LI.getLoopFor(loc); L; L = L->getParentLoop()) { |
| if (L == InstLoop) |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| GradientUtils::GradientUtils( |
| EnzymeLogic &Logic, Function *newFunc_, Function *oldFunc_, |
| TargetLibraryInfo &TLI_, TypeAnalysis &TA_, TypeResults TR_, |
| ValueToValueMapTy &invertedPointers_, |
| const SmallPtrSetImpl<Value *> &constantvalues_, |
| const SmallPtrSetImpl<Value *> &activevals_, DIFFE_TYPE ReturnActivity, |
| bool shadowReturnUsed_, ArrayRef<DIFFE_TYPE> ArgDiffeTypes_, |
| llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> &originalToNewFn_, |
| DerivativeMode mode, bool runtimeActivity, bool strongZero, unsigned width, |
| bool omp) |
| : CacheUtility(TLI_, newFunc_), Logic(Logic), mode(mode), oldFunc(oldFunc_), |
| invertedPointers(), |
| OrigDT(oldFunc_->empty() |
| ? ((DominatorTree *)nullptr) |
| : &Logic.PPC.FAM.getResult<llvm::DominatorTreeAnalysis>( |
| *oldFunc_)), |
| OrigPDT(oldFunc_->empty() |
| ? ((PostDominatorTree *)nullptr) |
| : &Logic.PPC.FAM.getResult<llvm::PostDominatorTreeAnalysis>( |
| *oldFunc_)), |
| OrigLI(oldFunc_->empty() |
| ? ((LoopInfo *)nullptr) |
| : &Logic.PPC.FAM.getResult<llvm::LoopAnalysis>(*oldFunc_)), |
| OrigSE(oldFunc_->empty() |
| ? ((ScalarEvolution *)nullptr) |
| : &Logic.PPC.FAM.getResult<llvm::ScalarEvolutionAnalysis>( |
| *oldFunc_)), |
| notForAnalysis(getGuaranteedUnreachable(oldFunc_)), |
| ATA(oldFunc_->empty() |
| ? nullptr |
| : new ActivityAnalyzer( |
| Logic.PPC, Logic.PPC.getAAResultsFromFunction(oldFunc_), |
| notForAnalysis, TLI_, constantvalues_, activevals_, |
| ReturnActivity)), |
| overwritten_args_map_ptr(nullptr), unnecessaryValuesP(nullptr), |
| tid(nullptr), numThreads(nullptr), |
| OrigAA(oldFunc_->empty() ? ((AAResults *)nullptr) |
| : &Logic.PPC.getAAResultsFromFunction(oldFunc_)), |
| TA(TA_), TR(TR_), omp(omp), runtimeActivity(runtimeActivity), |
| strongZero(strongZero), width(width), shadowReturnUsed(shadowReturnUsed_), |
| ArgDiffeTypes(ArgDiffeTypes_) { |
| if (oldFunc_->empty()) |
| return; |
| if (oldFunc_->getSubprogram()) { |
| assert(originalToNewFn_.hasMD()); |
| } |
| |
| for (BasicBlock &BB : *oldFunc) { |
| for (Instruction &I : BB) { |
| if (auto CI = dyn_cast<CallInst>(&I)) { |
| originalCalls.push_back(CI); |
| } |
| } |
| } |
| |
| originalToNewFn.getMDMap() = originalToNewFn_.getMDMap(); |
| |
| if (oldFunc_->getSubprogram()) { |
| assert(originalToNewFn.hasMD()); |
| } |
| for (auto pair : invertedPointers_) { |
| invertedPointers.insert(std::make_pair( |
| (const Value *)pair.first, InvertedPointerVH(this, pair.second))); |
| } |
| originalToNewFn.insert(originalToNewFn_.begin(), originalToNewFn_.end()); |
| for (BasicBlock &oBB : *oldFunc) { |
| for (Instruction &oI : oBB) { |
| newToOriginalFn[originalToNewFn[&oI]] = &oI; |
| } |
| newToOriginalFn[originalToNewFn[&oBB]] = &oBB; |
| } |
| for (Argument &oArg : oldFunc->args()) { |
| newToOriginalFn[originalToNewFn[&oArg]] = &oArg; |
| } |
| for (BasicBlock &BB : *newFunc) { |
| originalBlocks.push_back(&BB); |
| } |
| tape = nullptr; |
| tapeidx = 0; |
| assert(originalBlocks.size() > 0); |
| |
| SmallVector<BasicBlock *, 4> ReturningBlocks; |
| for (BasicBlock &BB : *oldFunc) { |
| if (isa<ReturnInst>(BB.getTerminator())) |
| ReturningBlocks.push_back(&BB); |
| } |
| for (BasicBlock &BB : *oldFunc) { |
| bool legal = true; |
| for (auto BRet : ReturningBlocks) { |
| if (!(BRet == &BB || OrigDT->dominates(&BB, BRet))) { |
| legal = false; |
| break; |
| } |
| } |
| if (legal) |
| BlocksDominatingAllReturns.insert(&BB); |
| } |
| } |
| |
| // Whether a particular value is neded in rooting the reverse pass |
| bool GradientUtils::usedInRooting(const llvm::CallBase *orig, |
| ArrayRef<ValueType> types, |
| const llvm::Value *val, bool shadow) const { |
| SmallVector<OperandBundleDef, 2> OrigDefs; |
| orig->getOperandBundlesAsDefs(OrigDefs); |
| SmallVector<OperandBundleDef, 2> Defs; |
| for (auto bund : OrigDefs) { |
| // Only handle jl_roots & gc-transition tags (for now). |
| StringRef tag = bund.getTag(); |
| if (tag == "jl_roots") { |
| bool anyPrimal = false; |
| bool anyShadow = false; |
| for (auto ty : types) { |
| if (ty == ValueType::Primal || ty == ValueType::Both) |
| anyPrimal = true; |
| if (ty == ValueType::Shadow || ty == ValueType::Both) |
| anyShadow = true; |
| } |
| |
| // In the future we can reduce the number of roots |
| // we preserve by identifying which operands they |
| // correspond to. For now, fall back and preserve all |
| // primals and shadows |
| // assert(bund.inputs().size() == types.size()); |
| for (auto inp : bund.inputs()) { |
| if (inp != val) |
| continue; |
| |
| if (anyPrimal && !shadow) |
| return true; |
| if (anyShadow && shadow) |
| return true; |
| } |
| } else if (tag == "gc-transition") { |
| if (shadow) |
| continue; |
| for (auto inp : bund.inputs()) { |
| if (inp != val) |
| continue; |
| return true; |
| } |
| } else { |
| errs() << "unsupported tag " << bund.getTag() << " for " << *orig << "\n"; |
| llvm_unreachable("unsupported tag"); |
| } |
| } |
| return false; |
| } |
| |
| SmallVector<OperandBundleDef, 2> |
| GradientUtils::getInvertedBundles(CallInst *orig, ArrayRef<ValueType> types, |
| IRBuilder<> &Builder2, bool lookup, |
| const ValueToValueMapTy &available) { |
| assert(!(lookup && (mode == DerivativeMode::ForwardMode || |
| mode == DerivativeMode::ForwardModeError))); |
| |
| SmallVector<OperandBundleDef, 2> OrigDefs; |
| orig->getOperandBundlesAsDefs(OrigDefs); |
| SmallVector<OperandBundleDef, 2> Defs; |
| for (auto bund : OrigDefs) { |
| // Only handle jl_roots & gc-transition tags (for now). |
| StringRef tag = bund.getTag(); |
| if (tag == "jl_roots") { |
| // In the future we can reduce the number of roots |
| // we preserve by identifying which operands they |
| // correspond to. For now, fall back and preserve all |
| // primals and shadows |
| // assert(bund.inputs().size() == types.size()); |
| bool anyPrimal = false; |
| bool anyShadow = false; |
| for (auto ty : types) { |
| if (ty == ValueType::Primal || ty == ValueType::Both) |
| anyPrimal = true; |
| if (ty == ValueType::Shadow || ty == ValueType::Both) |
| anyShadow = true; |
| } |
| |
| SmallVector<Value *, 2> bunds; |
| for (auto inp : bund.inputs()) { |
| if (anyPrimal) { |
| Value *newv = getNewFromOriginal(inp); |
| if (lookup) |
| newv = lookupM(newv, Builder2, available); |
| bunds.push_back(newv); |
| } |
| if (anyShadow && !isConstantValue(inp)) { |
| Value *shadow = invertPointerM(inp, Builder2); |
| if (lookup) |
| shadow = lookupM(shadow, Builder2); |
| bunds.push_back(shadow); |
| } |
| } |
| Defs.push_back(OperandBundleDef(tag.str(), bunds)); |
| } else if (tag == "gc-transition") { |
| SmallVector<Value *, 2> bunds; |
| for (auto inp : bund.inputs()) { |
| Value *newv = getNewFromOriginal(inp); |
| if (lookup) |
| newv = lookupM(newv, Builder2, available); |
| bunds.push_back(newv); |
| } |
| Defs.push_back(OperandBundleDef(tag.str(), bunds)); |
| } else { |
| errs() << "unsupported tag " << tag << " for " << *orig << "\n"; |
| llvm_unreachable("unsupported tag"); |
| } |
| } |
| return Defs; |
| } |
| |
| Value *GradientUtils::getNewIfOriginal(Value *originst) const { |
| assert(originst); |
| auto f = originalToNewFn.find(originst); |
| if (f == originalToNewFn.end()) { |
| return originst; |
| } |
| assert(f != originalToNewFn.end()); |
| if (f->second == nullptr) { |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *originst << "\n"; |
| } |
| assert(f->second); |
| return f->second; |
| } |
| |
| Value *GradientUtils::ompThreadId() { |
| if (tid) |
| return tid; |
| IRBuilder<> B(inversionAllocs); |
| |
| auto FT = FunctionType::get(Type::getInt64Ty(B.getContext()), |
| ArrayRef<Type *>(), false); |
| auto FN = newFunc->getParent()->getOrInsertFunction("omp_get_thread_num", FT); |
| auto CI = B.CreateCall(FN); |
| if (auto F = getFunctionFromCall(CI)) { |
| #if LLVM_VERSION_MAJOR >= 16 |
| F->setOnlyAccessesInaccessibleMemory(); |
| F->setOnlyReadsMemory(); |
| #else |
| F->addFnAttr(Attribute::InaccessibleMemOnly); |
| F->addFnAttr(Attribute::ReadOnly); |
| #endif |
| } |
| #if LLVM_VERSION_MAJOR >= 16 |
| CI->setOnlyAccessesInaccessibleMemory(); |
| CI->setOnlyReadsMemory(); |
| #else |
| CI->addAttribute(AttributeList::FunctionIndex, |
| Attribute::InaccessibleMemOnly); |
| CI->addAttribute(AttributeList::FunctionIndex, Attribute::ReadOnly); |
| #endif |
| return tid = CI; |
| } |
| |
| Value *GradientUtils::ompNumThreads() { |
| if (numThreads) |
| return numThreads; |
| IRBuilder<> B(inversionAllocs); |
| |
| auto FT = FunctionType::get(Type::getInt64Ty(B.getContext()), |
| ArrayRef<Type *>(), false); |
| auto FN = |
| newFunc->getParent()->getOrInsertFunction("omp_get_max_threads", FT); |
| auto CI = B.CreateCall(FN); |
| if (auto F = getFunctionFromCall(CI)) { |
| #if LLVM_VERSION_MAJOR >= 16 |
| F->setOnlyAccessesInaccessibleMemory(); |
| F->setOnlyReadsMemory(); |
| #else |
| F->addFnAttr(Attribute::InaccessibleMemOnly); |
| F->addFnAttr(Attribute::ReadOnly); |
| #endif |
| } |
| #if LLVM_VERSION_MAJOR >= 16 |
| CI->setOnlyAccessesInaccessibleMemory(); |
| CI->setOnlyReadsMemory(); |
| #else |
| CI->addAttribute(AttributeList::FunctionIndex, |
| Attribute::InaccessibleMemOnly); |
| CI->addAttribute(AttributeList::FunctionIndex, Attribute::ReadOnly); |
| #endif |
| return numThreads = CI; |
| } |
| |
| Value *GradientUtils::getOrInsertTotalMultiplicativeProduct(Value *val, |
| LoopContext &lc) { |
| // TODO optimize if val is invariant to loopContext |
| assert(val->getType()->isFPOrFPVectorTy()); |
| for (auto &I : *lc.header) { |
| if (auto PN = dyn_cast<PHINode>(&I)) { |
| if (PN->getType() != val->getType()) |
| continue; |
| if (fictiousPHIs.find(PN) != fictiousPHIs.end()) |
| continue; |
| |
| int Idx = PN->getBasicBlockIndex(lc.preheader); |
| if (Idx < 0) { |
| |
| std::string str; |
| raw_string_ostream ss(str); |
| |
| ss << " Could not find block for index, PN: " << *PN << "\n"; |
| ss << " preheader: " << *lc.preheader << "\n"; |
| ss << " header: " << *lc.header << "\n"; |
| ss << " fn: " << *lc.header->getParent() << "\n"; |
| |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap(PN), ErrorType::InternalError, |
| nullptr, nullptr, nullptr); |
| } else { |
| EmitFailure("GetIndexError", PN->getDebugLoc(), PN, ss.str()); |
| } |
| } |
| Value *ival = PN->getIncomingValue(Idx); |
| if (auto CDV = dyn_cast<ConstantDataVector>(ival)) { |
| if (CDV->isSplat()) |
| ival = CDV->getSplatValue(); |
| } |
| if (auto C = dyn_cast<ConstantFP>(ival)) { |
| if (!C->isExactlyValue(APFloat(C->getType()->getFltSemantics(), "1"))) { |
| continue; |
| } |
| } else |
| continue; |
| for (auto IB : PN->blocks()) { |
| if (IB == lc.preheader) |
| continue; |
| |
| if (auto BO = |
| dyn_cast<BinaryOperator>(PN->getIncomingValueForBlock(IB))) { |
| if (BO->getOpcode() != BinaryOperator::FMul) |
| goto continueOutermost; |
| if (BO->getOperand(0) == PN && BO->getOperand(1) == val) |
| return BO; |
| if (BO->getOperand(1) == PN && BO->getOperand(0) == val) |
| return BO; |
| } else |
| goto continueOutermost; |
| } |
| } else |
| break; |
| continueOutermost:; |
| } |
| |
| IRBuilder<> lbuilder(lc.header, lc.header->begin()); |
| auto PN = lbuilder.CreatePHI(val->getType(), 2); |
| Constant *One = ConstantFP::get(val->getType()->getScalarType(), "1"); |
| if (VectorType *VTy = dyn_cast<VectorType>(val->getType())) { |
| One = ConstantVector::getSplat(VTy->getElementCount(), One); |
| } |
| PN->addIncoming(One, lc.preheader); |
| lbuilder.SetInsertPoint(lc.header->getFirstNonPHI()); |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (DT.dominates(PN, inst)) |
| lbuilder.SetInsertPoint(inst->getNextNode()); |
| } |
| Value *red = lbuilder.CreateFMul(PN, val); |
| for (auto pred : predecessors(lc.header)) { |
| if (pred == lc.preheader) |
| continue; |
| PN->addIncoming(red, pred); |
| } |
| return red; |
| } |
| |
| Value *GradientUtils::getOrInsertConditionalIndex(Value *val, LoopContext &lc, |
| bool pickTrue) { |
| assert(val->getType()->isIntOrIntVectorTy(1)); |
| // TODO optimize if val is invariant to loopContext |
| for (auto &I : *lc.header) { |
| if (auto PN = dyn_cast<PHINode>(&I)) { |
| if (PN->getNumIncomingValues() == 0) |
| continue; |
| if (PN->getType() != lc.incvar->getType()) |
| continue; |
| if (fictiousPHIs.find(PN) != fictiousPHIs.end()) |
| continue; |
| Value *ival = PN->getIncomingValueForBlock(lc.preheader); |
| if (auto C = dyn_cast<Constant>(ival)) { |
| if (!C->isNullValue()) { |
| continue; |
| } |
| } else |
| continue; |
| for (auto IB : PN->blocks()) { |
| if (IB == lc.preheader) |
| continue; |
| |
| if (auto SI = dyn_cast<SelectInst>(PN->getIncomingValueForBlock(IB))) { |
| if (SI->getCondition() != val) |
| goto continueOutermost; |
| if (pickTrue && SI->getFalseValue() == PN) { |
| // TODO handle vector of |
| if (SI->getTrueValue() == lc.incvar) |
| return SI; |
| } |
| if (!pickTrue && SI->getTrueValue() == PN) { |
| // TODO handle vector of |
| if (SI->getFalseValue() == lc.incvar) |
| return SI; |
| } |
| } else |
| goto continueOutermost; |
| } |
| } else |
| break; |
| continueOutermost:; |
| } |
| |
| IRBuilder<> lbuilder(lc.header, lc.header->begin()); |
| auto PN = lbuilder.CreatePHI(lc.incvar->getType(), 2); |
| Constant *Zero = |
| Constant::getNullValue(lc.incvar->getType()->getScalarType()); |
| PN->addIncoming(Zero, lc.preheader); |
| lbuilder.SetInsertPoint(lc.incvar->getNextNode()); |
| Value *red = lc.incvar; |
| if (VectorType *VTy = dyn_cast<VectorType>(val->getType())) { |
| #if LLVM_VERSION_MAJOR >= 12 |
| red = lbuilder.CreateVectorSplat(VTy->getElementCount(), red); |
| #else |
| red = lbuilder.CreateVectorSplat(VTy->getNumElements(), red); |
| #endif |
| } |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (DT.dominates(PN, inst)) |
| lbuilder.SetInsertPoint(inst->getNextNode()); |
| } |
| assert(red->getType() == PN->getType()); |
| red = lbuilder.CreateSelect(val, pickTrue ? red : PN, pickTrue ? PN : red); |
| for (auto pred : predecessors(lc.header)) { |
| if (pred == lc.preheader) |
| continue; |
| PN->addIncoming(red, pred); |
| } |
| return red; |
| } |
| |
| bool GradientUtils::assumeDynamicLoopOfSizeOne(Loop *L) const { |
| if (!EnzymeInactiveDynamic) |
| return false; |
| auto OL = OrigLI->getLoopFor(isOriginal(L->getHeader())); |
| assert(OL); |
| for (auto OB : OL->getBlocks()) { |
| for (auto &OI : *OB) { |
| if (!isConstantInstruction(&OI)) |
| return false; |
| if (auto SI = dyn_cast<StoreInst>(&OI)) { |
| if (!isConstantValue(SI->getPointerOperand())) |
| return false; |
| } |
| if (auto MTI = dyn_cast<MemTransferInst>(&OI)) { |
| if (!isConstantValue(MTI->getArgOperand(0))) |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| DebugLoc GradientUtils::getNewFromOriginal(const DebugLoc L) const { |
| if (L.get() == nullptr) |
| return nullptr; |
| if (!oldFunc->getSubprogram()) |
| return L; |
| assert(originalToNewFn.hasMD()); |
| auto opt = originalToNewFn.getMappedMD(L.getAsMDNode()); |
| if (!opt) |
| return L; |
| assert(opt); |
| #if LLVM_VERSION_MAJOR >= 23 |
| return DebugLoc(cast<DILocation>(*opt)); |
| #else |
| return DebugLoc(cast<MDNode>(*opt)); |
| #endif |
| } |
| |
| Value *GradientUtils::getNewFromOriginal(const Value *originst) const { |
| assert(originst); |
| if (isa<ConstantData>(originst)) |
| return const_cast<Value *>(originst); |
| auto f = originalToNewFn.find(originst); |
| if (f == originalToNewFn.end()) { |
| errs() << *oldFunc << "\n"; |
| errs() << *newFunc << "\n"; |
| dumpMap(originalToNewFn, [&](const Value *const &v) -> bool { |
| if (isa<Instruction>(originst)) |
| return isa<Instruction>(v); |
| if (isa<BasicBlock>(originst)) |
| return isa<BasicBlock>(v); |
| if (isa<Function>(originst)) |
| return isa<Function>(v); |
| if (isa<Argument>(originst)) |
| return isa<Argument>(v); |
| if (isa<Constant>(originst)) |
| return isa<Constant>(v); |
| return true; |
| }); |
| llvm::errs() << *originst << "\n"; |
| } |
| assert(f != originalToNewFn.end()); |
| if (f->second == nullptr) { |
| errs() << *oldFunc << "\n"; |
| errs() << *newFunc << "\n"; |
| errs() << *originst << "\n"; |
| } |
| assert(f->second); |
| return f->second; |
| } |
| |
| Instruction * |
| GradientUtils::getNewFromOriginal(const Instruction *newinst) const { |
| auto ninst = getNewFromOriginal((Value *)newinst); |
| if (!isa<Instruction>(ninst)) { |
| errs() << *oldFunc << "\n"; |
| errs() << *newFunc << "\n"; |
| errs() << *ninst << " - " << *newinst << "\n"; |
| } |
| return cast<Instruction>(ninst); |
| } |
| |
| BasicBlock *GradientUtils::getNewFromOriginal(const BasicBlock *newinst) const { |
| return cast<BasicBlock>(getNewFromOriginal((Value *)newinst)); |
| } |
| |
| Value *GradientUtils::hasUninverted(const Value *inverted) const { |
| for (auto v : invertedPointers) { |
| if (v.second == inverted) |
| return const_cast<Value *>(v.first); |
| } |
| return nullptr; |
| } |
| |
| BasicBlock *GradientUtils::getOriginalFromNew(const BasicBlock *newinst) const { |
| assert(newinst->getParent() == newFunc); |
| auto found = newToOriginalFn.find(newinst); |
| assert(found != newToOriginalFn.end()); |
| Value *res = found->second; |
| return cast<BasicBlock>(res); |
| } |
| |
| Value *GradientUtils::isOriginal(const Value *newinst) const { |
| if (isa<Constant>(newinst) || isa<UndefValue>(newinst)) |
| return const_cast<Value *>(newinst); |
| #ifndef NDEBUG |
| if (auto arg = dyn_cast<Argument>(newinst)) { |
| assert(arg->getParent() == newFunc); |
| } |
| if (auto inst = dyn_cast<Instruction>(newinst)) { |
| assert(inst->getParent()->getParent() == newFunc); |
| } |
| #endif |
| auto found = newToOriginalFn.find(newinst); |
| if (found == newToOriginalFn.end()) |
| return nullptr; |
| return found->second; |
| } |
| |
| Instruction *GradientUtils::isOriginal(const Instruction *newinst) const { |
| return cast_or_null<Instruction>(isOriginal((const Value *)newinst)); |
| } |
| |
| BasicBlock *GradientUtils::isOriginal(const BasicBlock *newinst) const { |
| return cast_or_null<BasicBlock>(isOriginal((const Value *)newinst)); |
| } |
| |
| Value *GradientUtils::unwrapM(Value *const val, IRBuilder<> &BuilderM, |
| const ValueToValueMapTy &available, |
| UnwrapMode unwrapMode, BasicBlock *scope, |
| bool permitCache) { |
| assert(val); |
| assert(val->getName() != "<badref>"); |
| assert(val->getType()); |
| |
| for (auto pair : available) { |
| assert(pair.first); |
| assert(pair.first->getType()); |
| if (pair.second) { |
| assert(pair.second->getType()); |
| assert(pair.first->getType() == pair.second->getType()); |
| } |
| } |
| |
| if (isa<LoadInst>(val) && |
| cast<LoadInst>(val)->getMetadata("enzyme_mustcache")) { |
| return val; |
| } |
| |
| if (available.count(val)) { |
| auto avail = available.lookup(val); |
| assert(avail->getType()); |
| if (avail->getType() != val->getType()) { |
| llvm::errs() << "val: " << *val << "\n"; |
| llvm::errs() << "available[val]: " << *available.lookup(val) << "\n"; |
| } |
| assert(available.lookup(val)->getType() == val->getType()); |
| return available.lookup(val); |
| } |
| |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (inversionAllocs && inst->getParent() == inversionAllocs) { |
| return val; |
| } |
| // if (inst->getParent() == &newFunc->getEntryBlock()) { |
| // return inst; |
| //} |
| if (inst->getParent()->getParent() == newFunc && |
| isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| if (BuilderM.GetInsertBlock()->size() && |
| BuilderM.GetInsertPoint() != BuilderM.GetInsertBlock()->end()) { |
| if (DT.dominates(inst, &*BuilderM.GetInsertPoint())) { |
| // llvm::errs() << "allowed " << *inst << "from domination\n"; |
| assert(inst->getType() == val->getType()); |
| return inst; |
| } |
| } else { |
| if (DT.dominates(inst, &*BuilderM.GetInsertPoint())) { |
| // llvm::errs() << "allowed " << *inst << "from block domination\n"; |
| assert(inst->getType() == val->getType()); |
| return inst; |
| } |
| } |
| } |
| assert(!TapesToPreventRecomputation.count(inst)); |
| } |
| |
| std::pair<Value *, BasicBlock *> idx = std::make_pair(val, scope); |
| // assert(!startsWith(val->getName(), "$tapeload")); |
| if (permitCache) { |
| auto found0 = unwrap_cache.find(BuilderM.GetInsertBlock()); |
| if (found0 != unwrap_cache.end()) { |
| auto found1 = found0->second.find(idx.first); |
| if (found1 != found0->second.end()) { |
| auto found2 = found1->second.find(idx.second); |
| if (found2 != found1->second.end()) { |
| |
| auto cachedValue = found2->second; |
| if (cachedValue == nullptr) { |
| found1->second.erase(idx.second); |
| if (found1->second.size() == 0) { |
| found0->second.erase(idx.first); |
| } |
| } else { |
| if (cachedValue->getType() != val->getType()) { |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "val: " << *val << "\n"; |
| llvm::errs() << "unwrap_cache[cidx]: " << *cachedValue << "\n"; |
| } |
| assert(cachedValue->getType() == val->getType()); |
| return cachedValue; |
| } |
| } |
| } |
| } |
| } |
| |
| if (this->mode == DerivativeMode::ReverseModeGradient || |
| this->mode == DerivativeMode::ForwardModeSplit || |
| this->mode == DerivativeMode::ReverseModeCombined) |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (inst->getParent()->getParent() == newFunc) { |
| if (unwrapMode == UnwrapMode::LegalFullUnwrap && |
| this->mode != DerivativeMode::ReverseModeCombined) { |
| // TODO this isOriginal is a bottleneck, the new mapping of |
| // knownRecompute should be precomputed and maintained to lookup |
| // instead |
| Instruction *orig = isOriginal(inst); |
| // If a given value has been chosen to be cached, do not compute the |
| // operands to unwrap it, instead simply emit a placeholder to be |
| // replaced by the cache load later. This placeholder should only be |
| // returned when the original value would be recomputed (e.g. this |
| // function would not return null). Since this case assumes everything |
| // can be recomputed, simply return the placeholder. |
| if (orig && knownRecomputeHeuristic.find(orig) != |
| knownRecomputeHeuristic.end()) { |
| if (!knownRecomputeHeuristic[orig]) { |
| assert(inst->getParent()->getParent() == newFunc); |
| auto placeholder = BuilderM.CreatePHI( |
| val->getType(), 0, val->getName() + "_krcLFUreplacement"); |
| unwrappedLoads[placeholder] = inst; |
| SmallVector<Metadata *, 1> avail; |
| for (auto pair : available) |
| if (pair.second) |
| avail.push_back(MDNode::get( |
| placeholder->getContext(), |
| {ValueAsMetadata::get(const_cast<Value *>(pair.first)), |
| ValueAsMetadata::get(pair.second)})); |
| placeholder->setMetadata( |
| "enzyme_available", |
| MDNode::get(placeholder->getContext(), avail)); |
| if (!permitCache) |
| return placeholder; |
| return unwrap_cache[BuilderM.GetInsertBlock()][idx.first] |
| [idx.second] = placeholder; |
| } |
| } |
| } else if (unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { |
| // TODO this isOriginal is a bottleneck, the new mapping of |
| // knownRecompute should be precomputed and maintained to lookup |
| // instead |
| Instruction *orig = isOriginal(inst); |
| // If a given value has been chosen to be cached, do not compute the |
| // operands to unwrap it, instead simply emit a placeholder to be |
| // replaced by the cache load later. This placeholder should only be |
| // returned when the original value would be recomputed (e.g. this |
| // function would not return null). See note below about the condition |
| // as applied to this case. |
| if (orig) { |
| auto found = knownRecomputeHeuristic.find(orig); |
| if (found != knownRecomputeHeuristic.end()) { |
| if (!found->second) { |
| if (mode == DerivativeMode::ReverseModeCombined) { |
| // Don't unnecessarily cache a value if the caching |
| // heuristic says we should preserve this precise (and not |
| // an lcssa wrapped) value |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| Value *nval = inst; |
| if (scope) |
| nval = fixLCSSA(inst, scope); |
| if (nval == inst) |
| goto endCheck; |
| } |
| } else { |
| // Note that this logic (original load must dominate or |
| // alternatively be in the reverse block) is only valid iff |
| // when applicable (here if in split mode), an overwritten |
| // load cannot be hoisted outside of a loop to be used as a |
| // loop limit. This optimization is currently done in the |
| // combined mode (e.g. if a load isn't modified between a |
| // prior insertion point and the actual load, it is legal to |
| // recompute). |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock()) || |
| DT.dominates(inst, &*BuilderM.GetInsertPoint())) { |
| assert(inst->getParent()->getParent() == newFunc); |
| auto placeholder = BuilderM.CreatePHI( |
| val->getType(), 0, |
| val->getName() + "_krcAFUWLreplacement"); |
| unwrappedLoads[placeholder] = inst; |
| SmallVector<Metadata *, 1> avail; |
| for (auto pair : available) |
| if (pair.second) |
| avail.push_back( |
| MDNode::get(placeholder->getContext(), |
| {ValueAsMetadata::get( |
| const_cast<Value *>(pair.first)), |
| ValueAsMetadata::get(pair.second)})); |
| placeholder->setMetadata( |
| "enzyme_available", |
| MDNode::get(placeholder->getContext(), avail)); |
| if (!permitCache) |
| return placeholder; |
| return unwrap_cache[BuilderM.GetInsertBlock()][idx.first] |
| [idx.second] = placeholder; |
| } |
| } |
| } |
| } |
| } |
| } else if (unwrapMode != UnwrapMode::LegalFullUnwrapNoTapeReplace && |
| mode != DerivativeMode::ReverseModeCombined) { |
| // TODO this isOriginal is a bottleneck, the new mapping of |
| // knownRecompute should be precomputed and maintained to lookup |
| // instead |
| |
| // If a given value has been chosen to be cached, do not compute the |
| // operands to unwrap it if it is not legal to do so. This prevents |
| // the creation of unused versions of the instruction's operand, which |
| // may be assumed to never be used and thus cause an error when they |
| // are inadvertantly cached. |
| Value *orig = isOriginal(val); |
| if (orig && knownRecomputeHeuristic.find(orig) != |
| knownRecomputeHeuristic.end()) { |
| if (!knownRecomputeHeuristic[orig]) { |
| return nullptr; |
| } |
| } |
| } |
| } |
| } |
| |
| #define getOpFullest(Builder, vtmp, frominst, lookupInst, check) \ |
| ({ \ |
| Value *v = vtmp; \ |
| Type *vty = v->getType(); \ |
| BasicBlock *origParent = frominst; \ |
| Value *___res; \ |
| if (unwrapMode == UnwrapMode::LegalFullUnwrap || \ |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace || \ |
| unwrapMode == UnwrapMode::AttemptFullUnwrap || \ |
| unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { \ |
| if (v == val) \ |
| ___res = nullptr; \ |
| else \ |
| ___res = unwrapM(v, Builder, available, unwrapMode, origParent, \ |
| permitCache); \ |
| if (!___res && unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { \ |
| bool noLookup = false; \ |
| auto found = available.find(v); \ |
| if (found != available.end() && !found->second) \ |
| noLookup = true; \ |
| if (auto opinst = dyn_cast<Instruction>(v)) \ |
| if (isOriginalBlock(*Builder.GetInsertBlock())) { \ |
| if (!DT.dominates(opinst, &*Builder.GetInsertPoint())) \ |
| noLookup = true; \ |
| } \ |
| origParent = lookupInst; \ |
| if (!noLookup) \ |
| ___res = lookupM(v, Builder, available, v != val, origParent); \ |
| } \ |
| if (___res) \ |
| assert(___res->getType() == vty && "uw"); \ |
| } else { \ |
| origParent = lookupInst; \ |
| assert(unwrapMode == UnwrapMode::AttemptSingleUnwrap); \ |
| auto found = available.find(v); \ |
| if (found != available.end() && !found->second) \ |
| ___res = nullptr; \ |
| else { \ |
| ___res = lookupM(v, Builder, available, v != val, origParent); \ |
| if (___res && ___res->getType() != vty) { \ |
| llvm::errs() << *newFunc << "\n"; \ |
| llvm::errs() << " v = " << *v << " res = " << *___res << "\n"; \ |
| } \ |
| if (___res) \ |
| assert(___res->getType() == vty && "lu"); \ |
| } \ |
| } \ |
| ___res; \ |
| }) |
| #define getOpFull(Builder, vtmp, frominst) \ |
| ({ \ |
| BasicBlock *parent = scope; \ |
| if (parent == nullptr) \ |
| if (auto originst = dyn_cast<Instruction>(val)) \ |
| parent = originst->getParent(); \ |
| getOpFullest(Builder, vtmp, frominst, parent, true); \ |
| }) |
| #define getOpUnchecked(vtmp) \ |
| ({ \ |
| BasicBlock *parent = scope; \ |
| getOpFullest(BuilderM, vtmp, parent, parent, false); \ |
| }) |
| #define getOp(vtmp) \ |
| ({ \ |
| BasicBlock *parent = scope; \ |
| if (parent == nullptr) \ |
| if (auto originst = dyn_cast<Instruction>(val)) \ |
| parent = originst->getParent(); \ |
| getOpFullest(BuilderM, vtmp, parent, parent, true); \ |
| }) |
| |
| if (isa<Argument>(val) || isa<Constant>(val)) { |
| return val; |
| } else if (auto op = dyn_cast<FreezeInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateFreeze(op0, op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<CastInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateCast(op->getOpcode(), op0, op->getDestTy(), |
| op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<ExtractValueInst>(val)) { |
| auto op0 = getOp(op->getAggregateOperand()); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateExtractValue(op0, op->getIndices(), |
| op->getName() + "_unwrap"); |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<InsertValueInst>(val)) { |
| // Unwrapped Aggregate, Indices, parent |
| SmallVector<std::tuple<Value *, ArrayRef<unsigned>, InsertValueInst *>, 1> |
| insertElements; |
| |
| Value *agg = op; |
| while (auto op1 = dyn_cast<InsertValueInst>(agg)) { |
| if (Value *orig = isOriginal(op1)) { |
| if (knownRecomputeHeuristic.count(orig)) { |
| if (!knownRecomputeHeuristic[orig]) { |
| break; |
| } |
| } |
| } |
| Value *valOp = op1->getInsertedValueOperand(); |
| valOp = getOp(valOp); |
| if (valOp == nullptr) |
| goto endCheck; |
| insertElements.push_back({valOp, op1->getIndices(), op1}); |
| agg = op1->getAggregateOperand(); |
| } |
| |
| Value *toreturn = getOp(agg); |
| if (toreturn == nullptr) |
| goto endCheck; |
| for (auto &&[valOp, idcs, parent] : reverse(insertElements)) { |
| toreturn = BuilderM.CreateInsertValue(toreturn, valOp, idcs, |
| parent->getName() + "_unwrap"); |
| |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][parent][idx.second] = toreturn; |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(parent); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != parent->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| } |
| |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<ExtractElementInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto toreturn = |
| BuilderM.CreateExtractElement(op0, op1, op->getName() + "_unwrap"); |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<InsertElementInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto op2 = getOp(op->getOperand(2)); |
| if (op2 == nullptr) |
| goto endCheck; |
| auto toreturn = |
| BuilderM.CreateInsertElement(op0, op1, op2, op->getName() + "_unwrap"); |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<ShuffleVectorInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateShuffleVector( |
| op0, op1, op->getShuffleMaskForBitcode(), op->getName() + "'_unwrap"); |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<BinaryOperator>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| if (op0->getType() != op1->getType()) { |
| llvm::errs() << " op: " << *op << " op0: " << *op0 << " op1: " << *op1 |
| << " p0: " << *op->getOperand(0) |
| << " p1: " << *op->getOperand(1) << "\n"; |
| } |
| assert(op0->getType() == op1->getType()); |
| auto toreturn = BuilderM.CreateBinOp(op->getOpcode(), op0, op1, |
| op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<ICmpInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateICmp(op->getPredicate(), op0, op1, |
| op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<FCmpInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateFCmp(op->getPredicate(), op0, op1, |
| op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (isa<FPMathOperator>(val) && |
| cast<FPMathOperator>(val)->getOpcode() == Instruction::FNeg) { |
| auto op = cast<FPMathOperator>(val); |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto toreturn = BuilderM.CreateFNeg(op0, op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != |
| cast<Instruction>(val)->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<SelectInst>(val)) { |
| auto op0 = getOp(op->getOperand(0)); |
| if (op0 == nullptr) |
| goto endCheck; |
| auto op1 = getOp(op->getOperand(1)); |
| if (op1 == nullptr) |
| goto endCheck; |
| auto op2 = getOp(op->getOperand(2)); |
| if (op2 == nullptr) |
| goto endCheck; |
| auto toreturn = |
| BuilderM.CreateSelect(op0, op1, op2, op->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(op); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != op->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto inst = dyn_cast<GetElementPtrInst>(val)) { |
| auto ptr = getOp(inst->getPointerOperand()); |
| if (ptr == nullptr) |
| goto endCheck; |
| SmallVector<Value *, 4> ind; |
| // llvm::errs() << "inst: " << *inst << "\n"; |
| for (unsigned i = 0; i < inst->getNumIndices(); ++i) { |
| Value *a = inst->getOperand(1 + i); |
| auto op = getOp(a); |
| if (op == nullptr) |
| goto endCheck; |
| ind.push_back(op); |
| } |
| auto toreturn = BuilderM.CreateGEP(inst->getSourceElementType(), ptr, ind, |
| inst->getName() + "_unwrap"); |
| if (isa<GetElementPtrInst>(toreturn)) |
| cast<GetElementPtrInst>(toreturn)->setIsInBounds(inst->isInBounds()); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(inst); |
| unwrappedLoads[newi] = val; |
| if (newi->getParent()->getParent() != inst->getParent()->getParent()) |
| newi->setDebugLoc(nullptr); |
| } |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto load = dyn_cast<LoadInst>(val)) { |
| if (load->getMetadata("enzyme_noneedunwrap")) |
| return load; |
| |
| bool legalMove = unwrapMode == UnwrapMode::LegalFullUnwrap || |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace; |
| if (!legalMove) { |
| BasicBlock *parent = nullptr; |
| if (isOriginalBlock(*BuilderM.GetInsertBlock())) |
| parent = BuilderM.GetInsertBlock(); |
| if (!parent || |
| LI.getLoopFor(parent) == LI.getLoopFor(load->getParent()) || |
| DT.dominates(load, parent)) { |
| legalMove = legalRecompute(load, available, &BuilderM); |
| } else { |
| legalMove = |
| legalRecompute(load, available, &BuilderM, /*reverse*/ false, |
| /*legalRecomputeCache*/ false); |
| } |
| } |
| if (!legalMove) { |
| auto &warnMap = UnwrappedWarnings[load]; |
| if (!warnMap.count(BuilderM.GetInsertBlock())) { |
| EmitWarning("UncacheableUnwrap", *load, "Load cannot be unwrapped ", |
| *load, " in ", BuilderM.GetInsertBlock()->getName(), " - ", |
| BuilderM.GetInsertBlock()->getParent()->getName(), " mode ", |
| unwrapMode); |
| warnMap.insert(BuilderM.GetInsertBlock()); |
| } |
| goto endCheck; |
| } |
| |
| Value *pidx = getOp(load->getOperand(0)); |
| |
| if (pidx == nullptr) { |
| goto endCheck; |
| } |
| |
| if (pidx->getType() != load->getOperand(0)->getType()) { |
| llvm::errs() << "load: " << *load << "\n"; |
| llvm::errs() << "load->getOperand(0): " << *load->getOperand(0) << "\n"; |
| llvm::errs() << "idx: " << *pidx << " unwrapping: " << *val |
| << " mode=" << unwrapMode << "\n"; |
| } |
| assert(pidx->getType() == load->getOperand(0)->getType()); |
| |
| auto toreturn = |
| BuilderM.CreateLoad(load->getType(), pidx, load->getName() + "_unwrap"); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| ToCopy2.push_back(LLVMContext::MD_alias_scope); |
| toreturn->copyMetadata(*load, ToCopy2); |
| toreturn->copyIRFlags(load); |
| if (load->getParent()->getParent() == newFunc) |
| if (auto orig = isOriginal(load)) { |
| SmallVector<Metadata *, 1> scopeMD = { |
| getDerivativeAliasScope(orig->getOperand(0), -1)}; |
| if (auto prev = orig->getMetadata(LLVMContext::MD_alias_scope)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| scopeMD.push_back(M); |
| } |
| } |
| auto scope = MDNode::get(orig->getContext(), scopeMD); |
| toreturn->setMetadata(LLVMContext::MD_alias_scope, scope); |
| |
| SmallVector<Metadata *, 1> MDs; |
| for (size_t j = 0; j < getWidth(); j++) { |
| MDs.push_back(getDerivativeAliasScope(orig->getOperand(0), j)); |
| } |
| if (auto prev = orig->getMetadata(LLVMContext::MD_noalias)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| MDs.push_back(M); |
| } |
| } |
| auto noscope = MDNode::get(orig->getContext(), MDs); |
| toreturn->setMetadata(LLVMContext::MD_noalias, noscope); |
| } |
| unwrappedLoads[toreturn] = load; |
| if (toreturn->getParent()->getParent() != load->getParent()->getParent()) |
| toreturn->setDebugLoc(nullptr); |
| else |
| toreturn->setDebugLoc(getNewFromOriginal(load->getDebugLoc())); |
| toreturn->setAlignment(load->getAlign()); |
| toreturn->setVolatile(load->isVolatile()); |
| toreturn->setOrdering(load->getOrdering()); |
| toreturn->setSyncScopeID(load->getSyncScopeID()); |
| if (toreturn->getParent()->getParent() != load->getParent()->getParent()) |
| toreturn->setDebugLoc(nullptr); |
| else |
| toreturn->setDebugLoc(getNewFromOriginal(load->getDebugLoc())); |
| toreturn->setMetadata(LLVMContext::MD_tbaa, |
| load->getMetadata(LLVMContext::MD_tbaa)); |
| auto invar_group = load->getMetadata(LLVMContext::MD_invariant_group); |
| if (!invar_group) { |
| bool legal = true; |
| if (load->getParent()->getParent() != newFunc) |
| legal = false; |
| else if (auto norig = isOriginal(load)) |
| for (const auto &pair : rematerializableAllocations) { |
| for (auto V : pair.second.loads) |
| if (V == norig) { |
| legal = false; |
| break; |
| } |
| if (!legal) |
| break; |
| } |
| if (legal) { |
| invar_group = MDNode::getDistinct(load->getContext(), {}); |
| load->setMetadata(LLVMContext::MD_invariant_group, invar_group); |
| } |
| } |
| toreturn->setMetadata(LLVMContext::MD_invariant_group, invar_group); |
| // TODO adding to cache only legal if no alias of any future writes |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } else if (auto op = dyn_cast<CallInst>(val)) { |
| |
| bool legalMove = unwrapMode == UnwrapMode::LegalFullUnwrap || |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace; |
| if (!legalMove) { |
| legalMove = legalRecompute(op, available, &BuilderM); |
| } |
| if (!legalMove) |
| goto endCheck; |
| |
| SmallVector<Value *, 4> args; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (unsigned i = 0; i < op->arg_size(); ++i) |
| #else |
| for (unsigned i = 0; i < op->getNumArgOperands(); ++i) |
| #endif |
| { |
| args.push_back(getOp(op->getArgOperand(i))); |
| if (args[i] == nullptr) |
| goto endCheck; |
| } |
| |
| Value *fn = getOp(op->getCalledOperand()); |
| if (fn == nullptr) |
| goto endCheck; |
| |
| auto toreturn = |
| cast<CallInst>(BuilderM.CreateCall(op->getFunctionType(), fn, args)); |
| toreturn->copyIRFlags(op); |
| toreturn->setAttributes(op->getAttributes()); |
| toreturn->setCallingConv(op->getCallingConv()); |
| toreturn->setTailCallKind(op->getTailCallKind()); |
| if (toreturn->getParent()->getParent() == op->getParent()->getParent()) |
| toreturn->setDebugLoc(getNewFromOriginal(op->getDebugLoc())); |
| else |
| toreturn->setDebugLoc(nullptr); |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = toreturn; |
| unwrappedLoads[toreturn] = val; |
| return toreturn; |
| } else if (auto phi = dyn_cast<PHINode>(val)) { |
| if (phi->getNumIncomingValues() == 0) { |
| auto uninverted = hasUninverted(phi); |
| if (uninverted) { |
| |
| // This is a placeholder shadow for a load, rather than falling |
| // back to the uncached variant, use the proper procedure for |
| // an inverted load |
| if (auto dli = dyn_cast<LoadInst>(uninverted)) { |
| // Almost identical code to unwrap load (replacing use of shadow |
| // where appropriate) |
| if (dli->getMetadata("enzyme_noneedunwrap")) |
| return dli; |
| |
| bool legalMove = |
| unwrapMode == UnwrapMode::LegalFullUnwrap || |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace; |
| if (!legalMove) { |
| // TODO actually consider whether this is legal to move to the new |
| // location, rather than recomputable anywhere |
| legalMove = legalRecompute(dli, available, &BuilderM); |
| } |
| if (!legalMove) { |
| auto &warnMap = UnwrappedWarnings[phi]; |
| if (!warnMap.count(BuilderM.GetInsertBlock())) { |
| EmitWarning("UncacheableUnwrap", *dli, |
| "Differential Load cannot be unwrapped ", *dli, |
| " in ", BuilderM.GetInsertBlock()->getName(), |
| " mode ", unwrapMode); |
| warnMap.insert(BuilderM.GetInsertBlock()); |
| } |
| return nullptr; |
| } |
| |
| Value *pidx = nullptr; |
| |
| if (isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| pidx = invertPointerM(dli->getOperand(0), BuilderM); |
| } else { |
| pidx = lookupM(invertPointerM(dli->getOperand(0), BuilderM), |
| BuilderM, available); |
| } |
| |
| if (pidx == nullptr) |
| goto endCheck; |
| |
| if (pidx->getType() != getShadowType(dli->getOperand(0)->getType())) { |
| llvm::errs() << "dli: " << *dli << "\n"; |
| llvm::errs() << "dli->getOperand(0): " << *dli->getOperand(0) |
| << "\n"; |
| llvm::errs() << "pidx: " << *pidx << "\n"; |
| } |
| assert(pidx->getType() == |
| getShadowType(dli->getOperand(0)->getType())); |
| |
| size_t s_idx = 0; |
| Value *toreturn = applyChainRule( |
| dli->getType(), BuilderM, |
| [&](Value *pidx) { |
| auto toreturn = BuilderM.CreateLoad(dli->getType(), pidx, |
| phi->getName() + "_unwrap"); |
| if (auto newi = dyn_cast<Instruction>(toreturn)) { |
| newi->copyIRFlags(dli); |
| unwrappedLoads[toreturn] = dli; |
| } |
| toreturn->setAlignment(dli->getAlign()); |
| toreturn->setVolatile(dli->isVolatile()); |
| toreturn->setOrdering(dli->getOrdering()); |
| toreturn->setSyncScopeID(dli->getSyncScopeID()); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| toreturn->copyMetadata(*dli, ToCopy2); |
| SmallVector<Metadata *, 1> scopeMD = { |
| getDerivativeAliasScope(dli->getOperand(0), s_idx)}; |
| if (auto prev = dli->getMetadata(LLVMContext::MD_alias_scope)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| scopeMD.push_back(M); |
| } |
| } |
| auto scope = MDNode::get(dli->getContext(), scopeMD); |
| toreturn->setMetadata(LLVMContext::MD_alias_scope, scope); |
| |
| SmallVector<Metadata *, 1> MDs; |
| for (ssize_t j = -1; j < getWidth(); j++) { |
| if (j != (ssize_t)s_idx) |
| MDs.push_back( |
| getDerivativeAliasScope(dli->getOperand(0), j)); |
| } |
| if (auto prev = dli->getMetadata(LLVMContext::MD_noalias)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| MDs.push_back(M); |
| } |
| } |
| if (MDs.size()) { |
| auto noscope = MDNode::get(dli->getContext(), MDs); |
| toreturn->setMetadata(LLVMContext::MD_noalias, noscope); |
| } |
| toreturn->setDebugLoc(getNewFromOriginal(dli->getDebugLoc())); |
| s_idx++; |
| return toreturn; |
| }, |
| pidx); |
| |
| // TODO adding to cache only legal if no alias of any future writes |
| if (permitCache) |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = |
| toreturn; |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } |
| |
| if (auto dli = dyn_cast<Instruction>(uninverted)) { |
| if (hasNoCache(dli)) { |
| auto found = invertedPointers.find(uninverted); |
| assert(found != invertedPointers.end()); |
| assert(found->second == phi); |
| invertedPointers.erase(found); |
| auto ip = invertPointerM(dli, BuilderM); |
| replaceAWithB(phi, ip); |
| erase(phi); |
| return unwrapM(ip, BuilderM, available, unwrapMode, scope, |
| permitCache); |
| } |
| } |
| } |
| |
| goto endCheck; |
| } |
| assert(phi->getNumIncomingValues() != 0); |
| |
| // If requesting loop bound and are requesting the total size. |
| // Rather than generating a new lcssa variable, use the existing loop exact |
| // bound var |
| BasicBlock *ivctx = scope; |
| if (!ivctx) |
| ivctx = BuilderM.GetInsertBlock(); |
| if (newFunc == ivctx->getParent() && !isOriginalBlock(*ivctx)) { |
| ivctx = originalForReverseBlock(*ivctx); |
| } |
| if ((ivctx == phi->getParent() || DT.dominates(phi, ivctx)) && |
| (!isOriginalBlock(*BuilderM.GetInsertBlock()) || |
| DT.dominates(phi, &*BuilderM.GetInsertPoint()))) { |
| LoopContext lc; |
| bool loopVar = false; |
| if (getContext(phi->getParent(), lc) && lc.var == phi) { |
| loopVar = true; |
| } else { |
| Value *V = nullptr; |
| bool legal = true; |
| for (auto &val : phi->incoming_values()) { |
| if (isa<UndefValue>(val)) |
| continue; |
| if (V == nullptr) |
| V = val; |
| else if (V != val) { |
| legal = false; |
| break; |
| } |
| } |
| if (legal) { |
| if (auto I = dyn_cast_or_null<PHINode>(V)) { |
| if (getContext(I->getParent(), lc) && lc.var == I) { |
| loopVar = true; |
| } |
| } |
| } |
| } |
| if (loopVar) { |
| if (!lc.dynamic) { |
| Value *lim = getOp(lc.trueLimit); |
| if (lim) { |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = |
| lim; |
| return lim; |
| } |
| } else if (unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup && |
| reverseBlocks.size() > 0) { |
| // Must be in a reverse pass fashion for a lookup to index bound to be |
| // legal |
| assert(/*ReverseLimit*/ reverseBlocks.size() > 0); |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| lc.preheader); |
| Value *lim = lookupValueFromCache( |
| lc.var->getType(), |
| /*forwardPass*/ false, BuilderM, lctx, |
| getDynamicLoopLimit(LI.getLoopFor(lc.header)), |
| /*isi1*/ false, available); |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = lim; |
| return lim; |
| } |
| } |
| } |
| |
| auto parent = phi->getParent(); |
| |
| // Don't attempt to unroll a loop induction variable in other |
| // circumstances |
| std::set<BasicBlock *> prevIteration; |
| BasicBlock *origParent = isOriginal(parent); |
| assert(origParent); |
| if (OrigLI->isLoopHeader(origParent)) { |
| if (phi->getNumIncomingValues() != 2) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| auto OrigL = OrigLI->getLoopFor(origParent); |
| for (auto PH : predecessors(parent)) { |
| BasicBlock *origPH = isOriginal(PH); |
| assert(origPH); |
| if (OrigL->contains(origPH)) |
| prevIteration.insert(PH); |
| } |
| if (prevIteration.size() && !legalRecompute(phi, available, &BuilderM)) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| } |
| for (auto &val : phi->incoming_values()) { |
| auto inst = dyn_cast<Instruction>(val); |
| if (!inst) |
| continue; |
| auto origInstParent = isOriginal(inst->getParent()); |
| assert(origInstParent); |
| const llvm::Loop *InstLoop = OrigLI->getLoopFor(origInstParent); |
| if (!InstLoop) { |
| continue; |
| } |
| bool isParentLoop = false; |
| for (const llvm::Loop *L = OrigLI->getLoopFor(origParent); L; |
| L = L->getParentLoop()) { |
| if (L == InstLoop) { |
| isParentLoop = true; |
| break; |
| } |
| } |
| if (isParentLoop) |
| continue; |
| if (unwrapMode == UnwrapMode::LegalFullUnwrap) { |
| llvm::errs() << " module: " << *newFunc->getParent() << "\n"; |
| llvm::errs() << " newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " parent: " << *parent << "\n"; |
| llvm::errs() << " val: " << *val << "\n"; |
| } |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| if (phi->getNumIncomingValues() == 1) { |
| assert(phi->getIncomingValue(0) != phi); |
| auto toreturn = getOpUnchecked(phi->getIncomingValue(0)); |
| if (toreturn == nullptr || toreturn == phi) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } |
| |
| std::set<BasicBlock *> targetToPreds; |
| // Map of function edges to list of values possible |
| std::map<std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| std::set<BasicBlock *>> |
| done; |
| { |
| std::deque<std::tuple< |
| std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| BasicBlock *>> |
| Q; // newblock, target |
| |
| for (unsigned i = 0; i < phi->getNumIncomingValues(); ++i) { |
| Q.push_back( |
| std::make_pair(std::make_pair(phi->getIncomingBlock(i), parent), |
| phi->getIncomingBlock(i))); |
| targetToPreds.insert(phi->getIncomingBlock(i)); |
| } |
| |
| for (std::tuple< |
| std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| BasicBlock *> |
| trace; |
| Q.size() > 0;) { |
| trace = Q.front(); |
| Q.pop_front(); |
| auto edge = std::get<0>(trace); |
| auto block = edge.first; |
| auto target = std::get<1>(trace); |
| |
| if (done[edge].count(target)) |
| continue; |
| done[edge].insert(target); |
| |
| if (DT.dominates(block, phi->getParent())) |
| continue; |
| |
| Loop *blockLoop = LI.getLoopFor(block); |
| |
| for (BasicBlock *Pred : predecessors(block)) { |
| // Don't go up the backedge as we can use the last value if desired |
| // via lcssa |
| if (blockLoop && blockLoop->getHeader() == block && |
| blockLoop == LI.getLoopFor(Pred)) |
| continue; |
| |
| Q.push_back( |
| std::tuple<std::pair<BasicBlock *, BasicBlock *>, BasicBlock *>( |
| std::make_pair(Pred, block), target)); |
| } |
| } |
| } |
| |
| std::set<BasicBlock *> blocks; |
| for (auto pair : done) { |
| const auto &edge = pair.first; |
| blocks.insert(edge.first); |
| } |
| |
| BasicBlock *oldB = BuilderM.GetInsertBlock(); |
| if (BuilderM.GetInsertPoint() != oldB->end()) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| BasicBlock *fwd = oldB; |
| bool inReverseBlocks = false; |
| if (!isOriginalBlock(*fwd)) { |
| auto found = reverseBlockToPrimal.find(oldB); |
| if (found == reverseBlockToPrimal.end()) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| fwd = found->second; |
| inReverseBlocks = |
| std::find(reverseBlocks[fwd].begin(), reverseBlocks[fwd].end(), |
| oldB) != reverseBlocks[fwd].end(); |
| } |
| |
| auto eraseBlocks = [&](ArrayRef<BasicBlock *> blocks, BasicBlock *bret) { |
| SmallVector<BasicBlock *, 2> revtopo; |
| { |
| SmallPtrSet<BasicBlock *, 2> seen; |
| std::function<void(BasicBlock *)> dfs = [&](BasicBlock *B) { |
| if (seen.count(B)) |
| return; |
| seen.insert(B); |
| if (hasTerminator(B)) |
| for (auto S : successors(B)) |
| if (!seen.count(S)) |
| dfs(S); |
| revtopo.push_back(B); |
| }; |
| for (auto B : blocks) |
| dfs(B); |
| if (!seen.count(bret)) |
| revtopo.insert(revtopo.begin(), bret); |
| } |
| |
| SmallVector<Instruction *, 4> toErase; |
| for (auto B : revtopo) { |
| if (B == bret) |
| continue; |
| for (auto &I : llvm::reverse(*B)) { |
| toErase.push_back(&I); |
| } |
| unwrap_cache.erase(B); |
| lookup_cache.erase(B); |
| if (reverseBlocks.size() > 0) { |
| auto tfwd = reverseBlockToPrimal[B]; |
| assert(tfwd); |
| auto rfound = reverseBlocks.find(tfwd); |
| assert(rfound != reverseBlocks.end()); |
| auto &tlst = rfound->second; |
| auto found = std::find(tlst.begin(), tlst.end(), B); |
| if (found != tlst.end()) |
| tlst.erase(found); |
| reverseBlockToPrimal.erase(B); |
| } |
| } |
| for (auto I : toErase) { |
| erase(I); |
| } |
| for (auto B : revtopo) |
| B->eraseFromParent(); |
| }; |
| |
| if (targetToPreds.size() == 3) { |
| for (auto block : blocks) { |
| if (!DT.dominates(block, phi->getParent())) |
| continue; |
| std::set<BasicBlock *> foundtargets; |
| std::set<BasicBlock *> uniqueTargets; |
| for (BasicBlock *succ : successors(block)) { |
| auto edge = std::make_pair(block, succ); |
| for (BasicBlock *target : done[edge]) { |
| if (foundtargets.find(target) != foundtargets.end()) { |
| goto rnextpair; |
| } |
| foundtargets.insert(target); |
| if (done[edge].size() == 1) |
| uniqueTargets.insert(target); |
| } |
| } |
| if (foundtargets.size() != 3) |
| goto rnextpair; |
| if (uniqueTargets.size() != 1) |
| goto rnextpair; |
| |
| { |
| BasicBlock *subblock = nullptr; |
| for (auto block2 : blocks) { |
| { |
| // The second split block must not have a parent with an edge |
| // to a block other than to itself, which can reach any of its |
| // two targets. |
| // TODO verify this |
| for (auto P : predecessors(block2)) { |
| for (auto S : successors(P)) { |
| if (S == block2) |
| continue; |
| auto edge = std::make_pair(P, S); |
| if (done.find(edge) != done.end()) { |
| for (auto target : done[edge]) { |
| if (foundtargets.find(target) != foundtargets.end() && |
| uniqueTargets.find(target) == uniqueTargets.end()) |
| goto nextblock; |
| } |
| } |
| } |
| } |
| std::set<BasicBlock *> seen2; |
| for (BasicBlock *succ : successors(block2)) { |
| auto edge = std::make_pair(block2, succ); |
| if (done[edge].size() != 1) { |
| // llvm::errs() << " -- failed from noonesize\n"; |
| goto nextblock; |
| } |
| for (BasicBlock *target : done[edge]) { |
| if (seen2.find(target) != seen2.end()) { |
| // llvm::errs() << " -- failed from not uniqueTargets\n"; |
| goto nextblock; |
| } |
| seen2.insert(target); |
| if (foundtargets.find(target) == foundtargets.end()) { |
| // llvm::errs() << " -- failed from not unknown target\n"; |
| goto nextblock; |
| } |
| if (uniqueTargets.find(target) != uniqueTargets.end()) { |
| // llvm::errs() << " -- failed from not same target\n"; |
| goto nextblock; |
| } |
| } |
| } |
| if (seen2.size() != 2) { |
| // llvm::errs() << " -- failed from not 2 seen\n"; |
| goto nextblock; |
| } |
| subblock = block2; |
| break; |
| } |
| nextblock:; |
| } |
| |
| if (subblock == nullptr) |
| goto rnextpair; |
| |
| { |
| auto bi1 = dyn_cast<BranchInst>(block->getTerminator()); |
| if (!bi1) { |
| goto endCheck; |
| } |
| |
| auto cond1 = getOp(bi1->getCondition()); |
| if (cond1 == nullptr) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| auto bi2 = cast<BranchInst>(subblock->getTerminator()); |
| auto cond2 = getOp(bi2->getCondition()); |
| if (cond2 == nullptr) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| SmallVector<BasicBlock *, 3> predBlocks = {bi2->getSuccessor(0), |
| bi2->getSuccessor(1)}; |
| for (int i = 0; i < 2; i++) { |
| auto edge = std::make_pair(block, bi1->getSuccessor(i)); |
| if (done[edge].size() == 1) { |
| predBlocks.push_back(bi1->getSuccessor(i)); |
| } |
| } |
| |
| SmallVector<Value *, 2> vals; |
| |
| SmallVector<BasicBlock *, 2> blocks; |
| SmallVector<BasicBlock *, 2> endingBlocks; |
| |
| BasicBlock *last = oldB; |
| |
| BasicBlock *bret = BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phimerge", newFunc); |
| |
| for (size_t i = 0; i < predBlocks.size(); i++) { |
| BasicBlock *valparent = (i < 2) ? subblock : block; |
| assert(done.find(std::make_pair(valparent, predBlocks[i])) != |
| done.end()); |
| assert(done[std::make_pair(valparent, predBlocks[i])].size() == |
| 1); |
| blocks.push_back(BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phirc", newFunc)); |
| blocks[i]->moveAfter(last); |
| last = blocks[i]; |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(blocks[i]); |
| reverseBlockToPrimal[blocks[i]] = fwd; |
| IRBuilder<> B(blocks[i]); |
| |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[blocks[i]].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[blocks[i]].insert(pair); |
| auto PB = *done[std::make_pair(valparent, predBlocks[i])].begin(); |
| |
| if (auto inst = dyn_cast<Instruction>( |
| phi->getIncomingValueForBlock(PB))) { |
| // Recompute the phi computation with the conditional if: |
| // 1) the instruction may read from memory AND does not |
| // dominate the current insertion point (thereby |
| // potentially making such recomputation without the |
| // condition illegal) |
| // 2) the value is a call or load and option is set to not |
| // speculatively recompute values within a phi |
| // OR |
| // 3) the value comes from a previous iteration. |
| BasicBlock *nextScope = PB; |
| // if (inst->getParent() == nextScope) nextScope = |
| // phi->getParent(); |
| if (prevIteration.count(PB)) { |
| assert(0 && "tri block prev iteration unhandled"); |
| } else if (!DT.dominates(inst->getParent(), phi->getParent()) || |
| (!EnzymeSpeculatePHIs && |
| (isa<CallInst>(inst) || isa<LoadInst>(inst)))) |
| vals.push_back(getOpFull(B, inst, nextScope)); |
| else |
| vals.push_back(getOpFull(BuilderM, inst, nextScope)); |
| } else |
| vals.push_back( |
| getOpFull(BuilderM, phi->getIncomingValueForBlock(PB), PB)); |
| |
| if (!vals[i]) { |
| eraseBlocks(blocks, bret); |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| assert(val->getType() == vals[i]->getType()); |
| B.CreateBr(bret); |
| endingBlocks.push_back(B.GetInsertBlock()); |
| } |
| |
| bret->moveAfter(last); |
| |
| BasicBlock *bsplit = BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phisplt", newFunc); |
| bsplit->moveAfter(oldB); |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(bsplit); |
| reverseBlockToPrimal[bsplit] = fwd; |
| BuilderM.CreateCondBr( |
| cond1, |
| (done[std::make_pair(block, bi1->getSuccessor(0))].size() == 1) |
| ? blocks[2] |
| : bsplit, |
| (done[std::make_pair(block, bi1->getSuccessor(1))].size() == 1) |
| ? blocks[2] |
| : bsplit); |
| |
| BuilderM.SetInsertPoint(bsplit); |
| BuilderM.CreateCondBr(cond2, blocks[0], blocks[1]); |
| |
| BuilderM.SetInsertPoint(bret); |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(bret); |
| reverseBlockToPrimal[bret] = fwd; |
| auto toret = BuilderM.CreatePHI(val->getType(), vals.size()); |
| for (size_t i = 0; i < vals.size(); i++) |
| toret->addIncoming(vals[i], endingBlocks[i]); |
| assert(val->getType() == toret->getType()); |
| if (permitCache) { |
| unwrap_cache[bret][idx.first][idx.second] = toret; |
| } |
| unwrappedLoads[toret] = val; |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[bret].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[bret].insert(pair); |
| return toret; |
| } |
| } |
| rnextpair:; |
| } |
| } |
| |
| Instruction *equivalentTerminator = nullptr; |
| |
| if (prevIteration.size() == 1) { |
| if (phi->getNumIncomingValues() == 2) { |
| |
| ValueToValueMapTy prevAvailable; |
| for (const auto &pair : available) |
| prevAvailable.insert(pair); |
| LoopContext ctx; |
| getContext(parent, ctx); |
| Value *prevIdx; |
| if (prevAvailable.count(ctx.var)) |
| prevIdx = prevAvailable[ctx.var]; |
| else { |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| // If we are using the phi in the reverse pass of a block inside the |
| // loop itself the previous index variable (aka the previous inc) is |
| // equivalent to the current load of antivaralloc |
| if (LI.getLoopFor(ctx.header)->contains(fwd)) { |
| prevIdx = |
| BuilderM.CreateLoad(ctx.var->getType(), ctx.antivaralloc); |
| } else { |
| // However, if we are using the phi of the reverse pass of a block |
| // outside the loop we must be in the reverse pass of a block |
| // after the loop. In which case, the previous index variable (aka |
| // previous inc) is the total loop iteration count-1, aka the |
| // trueLimit. |
| Value *lim = nullptr; |
| if (ctx.dynamic) { |
| // Must be in a reverse pass fashion for a lookup to index bound |
| // to be legal |
| assert(/*ReverseLimit*/ reverseBlocks.size() > 0); |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| ctx.preheader); |
| lim = lookupValueFromCache( |
| ctx.var->getType(), |
| /*forwardPass*/ false, BuilderM, lctx, |
| getDynamicLoopLimit(LI.getLoopFor(ctx.header)), |
| /*isi1*/ false, /*available*/ prevAvailable); |
| } else { |
| lim = lookupM(ctx.trueLimit, BuilderM, prevAvailable); |
| } |
| prevIdx = lim; |
| } |
| } else { |
| prevIdx = ctx.var; |
| } |
| } |
| // Prevent recursive unroll. |
| prevAvailable[phi] = nullptr; |
| SmallVector<Value *, 2> vals; |
| |
| SmallVector<BasicBlock *, 2> blocks; |
| SmallVector<BasicBlock *, 2> endingBlocks; |
| BasicBlock *last = oldB; |
| |
| BasicBlock *bret = BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phimerge", newFunc); |
| |
| SmallVector<BasicBlock *, 2> preds(predecessors(phi->getParent())); |
| |
| for (auto tup : llvm::enumerate(preds)) { |
| auto i = tup.index(); |
| BasicBlock *PB = tup.value(); |
| blocks.push_back(BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phirc", newFunc)); |
| blocks[i]->moveAfter(last); |
| last = blocks[i]; |
| if (reverseBlocks.size() > 0) { |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(blocks[i]); |
| reverseBlockToPrimal[blocks[i]] = fwd; |
| } |
| IRBuilder<> B(blocks[i]); |
| |
| if (!prevIteration.count(PB)) { |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[blocks[i]].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[blocks[i]].insert(pair); |
| } |
| |
| if (auto inst = |
| dyn_cast<Instruction>(phi->getIncomingValueForBlock(PB))) { |
| // Recompute the phi computation with the conditional if: |
| // 1) the instruction may read from memory AND does not dominate |
| // the current insertion point (thereby potentially making such |
| // recomputation without the condition illegal) |
| // 2) the value is a call or load and option is set to not |
| // speculatively recompute values within a phi |
| // OR |
| // 3) the value comes from a previous iteration. |
| BasicBlock *nextScope = PB; |
| // if (inst->getParent() == nextScope) nextScope = phi->getParent(); |
| if (prevIteration.count(PB)) { |
| prevAvailable[ctx.incvar] = prevIdx; |
| prevAvailable[ctx.var] = |
| B.CreateSub(prevIdx, ConstantInt::get(prevIdx->getType(), 1), |
| "", /*NUW*/ true, /*NSW*/ false); |
| Value *___res; |
| if (unwrapMode == UnwrapMode::LegalFullUnwrap || |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace || |
| unwrapMode == UnwrapMode::AttemptFullUnwrap || |
| unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { |
| ___res = unwrapM(inst, B, prevAvailable, unwrapMode, nextScope, |
| /*permitCache*/ false); |
| if (!___res && |
| unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { |
| bool noLookup = false; |
| if (isOriginalBlock(*B.GetInsertBlock())) { |
| if (!DT.dominates(inst, &*B.GetInsertPoint())) |
| noLookup = true; |
| } |
| if (!noLookup) { |
| BasicBlock *nS2 = nextScope; |
| Value *v = inst; |
| ___res = lookupM(v, B, prevAvailable, v != val, nS2); |
| } |
| } |
| if (___res) |
| assert(___res->getType() == inst->getType() && "uw"); |
| } else { |
| BasicBlock *nS2 = nextScope; |
| Value *v = inst; |
| ___res = lookupM(v, B, prevAvailable, v != val, nS2); |
| if (___res && ___res->getType() != v->getType()) { |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << " v = " << *v << " res = " << *___res << "\n"; |
| } |
| if (___res) |
| assert(___res->getType() == inst->getType() && "lu"); |
| } |
| vals.push_back(___res); |
| } else if (!DT.dominates(inst->getParent(), phi->getParent()) || |
| (!EnzymeSpeculatePHIs && |
| (isa<CallInst>(inst) || isa<LoadInst>(inst)))) |
| vals.push_back(getOpFull(B, inst, nextScope)); |
| else |
| vals.push_back(getOpFull(BuilderM, inst, nextScope)); |
| } else |
| vals.push_back(phi->getIncomingValueForBlock(PB)); |
| |
| if (!vals[i]) { |
| eraseBlocks(blocks, bret); |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| assert(val->getType() == vals[i]->getType()); |
| B.CreateBr(bret); |
| endingBlocks.push_back(B.GetInsertBlock()); |
| } |
| |
| // Coming from a previous iteration is equivalent to the current |
| // iteration at zero. |
| Value *cond; |
| if (prevIteration.count(preds[0])) |
| cond = BuilderM.CreateICmpNE(prevIdx, |
| ConstantInt::get(prevIdx->getType(), 0)); |
| else |
| cond = BuilderM.CreateICmpEQ(prevIdx, |
| ConstantInt::get(prevIdx->getType(), 0)); |
| |
| if (blocks[0]->size() == 1 && blocks[1]->size() == 1) { |
| if (auto B1 = dyn_cast<BranchInst>(blocks[0]->getTerminator())) |
| if (auto B2 = dyn_cast<BranchInst>(blocks[1]->getTerminator())) |
| if (B1->isUnconditional() && B2->isUnconditional() && |
| B1->getSuccessor(0) == bret && B2->getSuccessor(0) == bret) { |
| eraseBlocks(blocks, bret); |
| Value *toret = BuilderM.CreateSelect( |
| cond, vals[0], vals[1], phi->getName() + "_unwrap"); |
| if (permitCache) { |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first] |
| [idx.second] = toret; |
| } |
| if (auto instRet = dyn_cast<Instruction>(toret)) { |
| unwrappedLoads[instRet] = val; |
| } |
| return toret; |
| } |
| } |
| |
| bret->moveAfter(last); |
| BuilderM.CreateCondBr(cond, blocks[0], blocks[1]); |
| |
| BuilderM.SetInsertPoint(bret); |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(bret); |
| reverseBlockToPrimal[bret] = fwd; |
| auto toret = BuilderM.CreatePHI(val->getType(), vals.size()); |
| for (size_t i = 0; i < vals.size(); i++) |
| toret->addIncoming(vals[i], endingBlocks[i]); |
| assert(val->getType() == toret->getType()); |
| if (permitCache) { |
| unwrap_cache[bret][idx.first][idx.second] = toret; |
| } |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[bret].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[bret].insert(pair); |
| unwrappedLoads[toret] = val; |
| return toret; |
| } |
| } |
| if (prevIteration.size() != 0) { |
| llvm::errs() << "prev iteration: " << *phi << "\n"; |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| for (auto block : blocks) { |
| if (!DT.dominates(block, phi->getParent())) |
| continue; |
| std::set<BasicBlock *> foundtargets; |
| for (BasicBlock *succ : successors(block)) { |
| auto edge = std::make_pair(block, succ); |
| if (done[edge].size() != 1) { |
| goto nextpair; |
| } |
| BasicBlock *target = *done[edge].begin(); |
| if (foundtargets.find(target) != foundtargets.end()) { |
| goto nextpair; |
| } |
| foundtargets.insert(target); |
| } |
| if (foundtargets.size() != targetToPreds.size()) { |
| goto nextpair; |
| } |
| |
| if (DT.dominates(block, parent)) { |
| equivalentTerminator = block->getTerminator(); |
| goto fast; |
| } |
| nextpair:; |
| } |
| goto endCheck; |
| |
| fast:; |
| assert(equivalentTerminator); |
| |
| if (isa<BranchInst>(equivalentTerminator) || |
| isa<SwitchInst>(equivalentTerminator)) { |
| BasicBlock *oldB = BuilderM.GetInsertBlock(); |
| |
| SmallVector<BasicBlock *, 2> predBlocks; |
| Value *cond = nullptr; |
| if (auto branch = dyn_cast<BranchInst>(equivalentTerminator)) { |
| cond = branch->getCondition(); |
| predBlocks.push_back(branch->getSuccessor(0)); |
| predBlocks.push_back(branch->getSuccessor(1)); |
| } else { |
| auto SI = cast<SwitchInst>(equivalentTerminator); |
| cond = SI->getCondition(); |
| predBlocks.push_back(SI->getDefaultDest()); |
| for (auto scase : SI->cases()) { |
| predBlocks.push_back(scase.getCaseSuccessor()); |
| } |
| } |
| cond = getOp(cond); |
| if (!cond) { |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| SmallVector<Value *, 2> vals; |
| |
| SmallVector<BasicBlock *, 2> blocks; |
| SmallVector<BasicBlock *, 2> endingBlocks; |
| |
| BasicBlock *last = oldB; |
| |
| assert(prevIteration.size() == 0); |
| |
| BasicBlock *bret = BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phimerge", newFunc); |
| |
| for (size_t i = 0; i < predBlocks.size(); i++) { |
| assert(done.find(std::make_pair(equivalentTerminator->getParent(), |
| predBlocks[i])) != done.end()); |
| assert(done[std::make_pair(equivalentTerminator->getParent(), |
| predBlocks[i])] |
| .size() == 1); |
| BasicBlock *PB = *done[std::make_pair(equivalentTerminator->getParent(), |
| predBlocks[i])] |
| .begin(); |
| blocks.push_back(BasicBlock::Create( |
| val->getContext(), oldB->getName() + "_phirc", newFunc)); |
| blocks[i]->moveAfter(last); |
| last = blocks[i]; |
| if (reverseBlocks.size() > 0) { |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(blocks[i]); |
| reverseBlockToPrimal[blocks[i]] = fwd; |
| } |
| IRBuilder<> B(blocks[i]); |
| |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[blocks[i]].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[blocks[i]].insert(pair); |
| |
| if (auto inst = |
| dyn_cast<Instruction>(phi->getIncomingValueForBlock(PB))) { |
| // Recompute the phi computation with the conditional if: |
| // 1) the instruction may reat from memory AND does not dominate |
| // the current insertion point (thereby potentially making such |
| // recomputation without the condition illegal) |
| // 2) the value is a call or load and option is set to not |
| // speculatively recompute values within a phi |
| // OR |
| // 3) the value comes from a previous iteration. |
| BasicBlock *nextScope = PB; |
| // if (inst->getParent() == nextScope) nextScope = phi->getParent(); |
| if (!DT.dominates(inst->getParent(), phi->getParent()) || |
| (!EnzymeSpeculatePHIs && |
| (isa<CallInst>(inst) || isa<LoadInst>(inst)))) |
| vals.push_back(getOpFull(B, inst, nextScope)); |
| else |
| vals.push_back(getOpFull(BuilderM, inst, nextScope)); |
| } else |
| vals.push_back(phi->getIncomingValueForBlock(PB)); |
| |
| if (!vals[i]) { |
| eraseBlocks(blocks, bret); |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| assert(val->getType() == vals[i]->getType()); |
| B.CreateBr(bret); |
| endingBlocks.push_back(B.GetInsertBlock()); |
| } |
| |
| // Fast path to not make a split block if no additional instructions |
| // were made in the two blocks |
| if (isa<BranchInst>(equivalentTerminator) && blocks[0]->size() == 1 && |
| blocks[1]->size() == 1) { |
| if (auto B1 = dyn_cast<BranchInst>(blocks[0]->getTerminator())) |
| if (auto B2 = dyn_cast<BranchInst>(blocks[1]->getTerminator())) |
| if (B1->isUnconditional() && B2->isUnconditional() && |
| B1->getSuccessor(0) == bret && B2->getSuccessor(0) == bret) { |
| eraseBlocks(blocks, bret); |
| Value *toret = BuilderM.CreateSelect(cond, vals[0], vals[1], |
| phi->getName() + "_unwrap"); |
| if (permitCache) { |
| unwrap_cache[BuilderM.GetInsertBlock()][idx.first][idx.second] = |
| toret; |
| } |
| if (auto instRet = dyn_cast<Instruction>(toret)) { |
| unwrappedLoads[instRet] = val; |
| } |
| return toret; |
| } |
| } |
| |
| if (BuilderM.GetInsertPoint() != oldB->end()) { |
| eraseBlocks(blocks, bret); |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| bret->moveAfter(last); |
| if (isa<BranchInst>(equivalentTerminator)) { |
| BuilderM.CreateCondBr(cond, blocks[0], blocks[1]); |
| } else { |
| auto SI = cast<SwitchInst>(equivalentTerminator); |
| auto NSI = BuilderM.CreateSwitch(cond, blocks[0], SI->getNumCases()); |
| size_t idx = 1; |
| for (auto scase : SI->cases()) { |
| NSI->addCase(scase.getCaseValue(), blocks[idx]); |
| idx++; |
| } |
| } |
| BuilderM.SetInsertPoint(bret); |
| if (inReverseBlocks) |
| reverseBlocks[fwd].push_back(bret); |
| reverseBlockToPrimal[bret] = fwd; |
| auto toret = BuilderM.CreatePHI(val->getType(), vals.size()); |
| for (size_t i = 0; i < vals.size(); i++) |
| toret->addIncoming(vals[i], endingBlocks[i]); |
| assert(val->getType() == toret->getType()); |
| if (permitCache) { |
| unwrap_cache[bret][idx.first][idx.second] = toret; |
| } |
| for (auto pair : unwrap_cache[oldB]) |
| unwrap_cache[bret].insert(pair); |
| for (auto pair : lookup_cache[oldB]) |
| lookup_cache[bret].insert(pair); |
| unwrappedLoads[toret] = val; |
| return toret; |
| } |
| assert(unwrapMode != UnwrapMode::LegalFullUnwrap); |
| goto endCheck; |
| } |
| |
| endCheck: |
| assert(val); |
| if (unwrapMode == UnwrapMode::LegalFullUnwrap || |
| unwrapMode == UnwrapMode::LegalFullUnwrapNoTapeReplace || |
| unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup) { |
| assert(val->getName() != "<badref>"); |
| Value *nval = val; |
| if (auto opinst = dyn_cast<Instruction>(nval)) |
| if (isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| if (!DT.dominates(opinst, &*BuilderM.GetInsertPoint())) { |
| if (unwrapMode != UnwrapMode::AttemptFullUnwrapWithLookup) { |
| llvm::errs() << " oldF: " << *oldFunc << "\n"; |
| llvm::errs() << " opParen: " << *opinst->getParent()->getParent() |
| << "\n"; |
| llvm::errs() << " newF: " << *newFunc << "\n"; |
| llvm::errs() << " - blk: " << *BuilderM.GetInsertBlock(); |
| llvm::errs() << " opInst: " << *opinst << " mode=" << unwrapMode |
| << "\n"; |
| } |
| assert(unwrapMode == UnwrapMode::AttemptFullUnwrapWithLookup); |
| return nullptr; |
| } |
| } |
| auto toreturn = lookupM(nval, BuilderM, available, |
| /*tryLegalRecomputeCheck*/ false, scope); |
| assert(val->getType() == toreturn->getType()); |
| return toreturn; |
| } |
| |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| if (BuilderM.GetInsertBlock()->size() && |
| BuilderM.GetInsertPoint() != BuilderM.GetInsertBlock()->end()) { |
| if (DT.dominates(inst, &*BuilderM.GetInsertPoint())) { |
| assert(inst->getType() == val->getType()); |
| return inst; |
| } |
| } else { |
| if (DT.dominates(inst, BuilderM.GetInsertBlock())) { |
| assert(inst->getType() == val->getType()); |
| return inst; |
| } |
| } |
| } |
| assert(val->getName() != "<badref>"); |
| auto &warnMap = UnwrappedWarnings[inst]; |
| if (!warnMap.count(BuilderM.GetInsertBlock())) { |
| EmitWarning("NoUnwrap", *inst, "Cannot unwrap ", *val, " in ", |
| BuilderM.GetInsertBlock()->getName()); |
| warnMap.insert(BuilderM.GetInsertBlock()); |
| } |
| } |
| return nullptr; |
| } |
| |
| void GradientUtils::ensureLookupCached(Instruction *inst, bool shouldFree, |
| BasicBlock *scope, MDNode *TBAA) { |
| assert(inst); |
| if (scopeMap.find(inst) != scopeMap.end()) |
| return; |
| if (shouldFree) |
| assert(reverseBlocks.size()); |
| |
| if (scope == nullptr) |
| scope = inst->getParent(); |
| |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, scope); |
| |
| AllocaInst *cache = |
| createCacheForScope(lctx, inst->getType(), inst->getName(), shouldFree); |
| assert(cache); |
| Value *Val = inst; |
| insert_or_assign( |
| scopeMap, Val, |
| std::pair<AssertingVH<AllocaInst>, LimitContext>(cache, lctx)); |
| storeInstructionInCache(lctx, inst, cache, TBAA); |
| } |
| |
| Value *GradientUtils::fixLCSSA(Instruction *inst, BasicBlock *forwardBlock, |
| bool legalInBlock) { |
| assert(inst->getName() != "<badref>"); |
| |
| if (auto lcssaPHI = dyn_cast<PHINode>(inst)) { |
| auto found = lcssaPHIToOrig.find(lcssaPHI); |
| if (found != lcssaPHIToOrig.end()) |
| inst = cast<Instruction>(found->second); |
| } |
| |
| if (inst->getParent() == inversionAllocs) |
| return inst; |
| |
| if (!isOriginalBlock(*forwardBlock)) { |
| forwardBlock = originalForReverseBlock(*forwardBlock); |
| } |
| |
| bool containsLastLoopValue = isPotentialLastLoopValue(inst, forwardBlock, LI); |
| |
| // If the instruction cannot represent a loop value, return the original |
| // instruction if it either is guaranteed to be available within the block, |
| // or it is not needed to guaranteed availability. |
| if (!containsLastLoopValue) { |
| if (!legalInBlock) |
| return inst; |
| if (forwardBlock == inst->getParent() || DT.dominates(inst, forwardBlock)) |
| return inst; |
| } |
| |
| // llvm::errs() << " inst: " << *inst << "\n"; |
| // llvm::errs() << " seen: " << *inst->getParent() << "\n"; |
| assert(inst->getParent() != inversionAllocs); |
| assert(isOriginalBlock(*inst->getParent())); |
| |
| if (lcssaFixes.find(inst) == lcssaFixes.end()) { |
| lcssaFixes[inst][inst->getParent()] = inst; |
| SmallPtrSet<BasicBlock *, 4> seen; |
| std::deque<BasicBlock *> todo = {inst->getParent()}; |
| while (todo.size()) { |
| BasicBlock *cur = todo.front(); |
| todo.pop_front(); |
| if (seen.count(cur)) |
| continue; |
| seen.insert(cur); |
| if (!hasTerminator(cur)) |
| continue; |
| auto terminator = cur->getTerminator(); |
| for (auto Succ : successors(terminator)) { |
| todo.push_back(Succ); |
| } |
| } |
| for (auto &BB : *inst->getParent()->getParent()) { |
| if (!seen.count(&BB) || |
| (inst->getParent() != &BB && DT.dominates(&BB, inst->getParent()))) { |
| // OrigPDT.dominates(isOriginal(inst->getParent()), |
| // isOriginal(&BB)))) { |
| lcssaFixes[inst][&BB] = UndefValue::get(inst->getType()); |
| } |
| } |
| } |
| |
| if (lcssaFixes[inst].find(forwardBlock) != lcssaFixes[inst].end()) { |
| return lcssaFixes[inst][forwardBlock]; |
| } |
| |
| // TODO replace forwardBlock with the first block dominated by inst, |
| // that dominates (or is) forwardBlock to ensuring maximum reuse |
| auto inspos = forwardBlock->front().getIterator(); |
| #if LLVM_VERSION_MAJOR >= 18 |
| #if LLVM_VERSION_MAJOR >= 21 |
| #else |
| if (forwardBlock->IsNewDbgInfoFormat) |
| #endif |
| { |
| if (!inspos.getHeadBit()) { |
| auto srcmarker = forwardBlock->getMarker(inspos); |
| if (srcmarker && !srcmarker->empty()) { |
| inspos.setHeadBit(true); |
| } |
| } |
| } |
| #endif |
| IRBuilder<> lcssa(forwardBlock, inspos); |
| auto lcssaPHI = |
| lcssa.CreatePHI(inst->getType(), 1, inst->getName() + "!manual_lcssa"); |
| lcssaFixes[inst][forwardBlock] = lcssaPHI; |
| lcssaPHIToOrig[lcssaPHI] = inst; |
| for (auto pred : predecessors(forwardBlock)) { |
| Value *val = nullptr; |
| if (inst->getParent() == pred || DT.dominates(inst, pred)) { |
| val = inst; |
| } |
| if (val == nullptr) { |
| val = fixLCSSA(inst, pred, /*legalInBlock*/ true); |
| assert(val->getType() == inst->getType()); |
| } |
| assert(val->getType() == inst->getType()); |
| lcssaPHI->addIncoming(val, pred); |
| } |
| |
| SmallPtrSet<Value *, 2> vals; |
| SmallVector<Value *, 2> todo(lcssaPHI->incoming_values().begin(), |
| lcssaPHI->incoming_values().end()); |
| while (todo.size()) { |
| Value *v = todo.back(); |
| todo.pop_back(); |
| if (v == lcssaPHI) |
| continue; |
| vals.insert(v); |
| } |
| assert(vals.size() > 0); |
| |
| if (vals.size() > 1) { |
| todo.append(vals.begin(), vals.end()); |
| vals.clear(); |
| while (todo.size()) { |
| Value *v = todo.back(); |
| todo.pop_back(); |
| |
| if (auto PN = dyn_cast<PHINode>(v)) |
| if (lcssaPHIToOrig.find(PN) != lcssaPHIToOrig.end()) { |
| v = lcssaPHIToOrig[PN]; |
| } |
| vals.insert(v); |
| } |
| } |
| assert(vals.size() > 0); |
| Value *val = nullptr; |
| if (vals.size() == 1) |
| val = *vals.begin(); |
| |
| if (val && (!legalInBlock || !isa<Instruction>(val) || |
| DT.dominates(cast<Instruction>(val), lcssaPHI))) { |
| |
| if (!isPotentialLastLoopValue(val, forwardBlock, LI)) { |
| bool nonSelfUse = false; |
| for (auto u : lcssaPHI->users()) { |
| if (u != lcssaPHI) { |
| nonSelfUse = true; |
| break; |
| } |
| } |
| if (!nonSelfUse) { |
| lcssaFixes[inst].erase(forwardBlock); |
| while (lcssaPHI->getNumOperands()) |
| lcssaPHI->removeIncomingValue(lcssaPHI->getNumOperands() - 1, false); |
| lcssaPHIToOrig.erase(lcssaPHI); |
| lcssaPHI->eraseFromParent(); |
| } |
| return val; |
| } |
| } |
| return lcssaPHI; |
| } |
| |
| Value *GradientUtils::cacheForReverse(IRBuilder<> &BuilderQ, Value *malloc, |
| int idx, bool replace) { |
| assert(malloc); |
| assert(BuilderQ.GetInsertBlock()->getParent() == newFunc); |
| assert(isOriginalBlock(*BuilderQ.GetInsertBlock())); |
| assert(!hasNoCache(malloc)); |
| if (mode == DerivativeMode::ReverseModeCombined) { |
| assert(!tape); |
| return malloc; |
| } |
| |
| #ifndef NDEBUG |
| if (auto CI = dyn_cast<CallInst>(malloc)) { |
| if (auto F = CI->getCalledFunction()) { |
| assert(F->getName() != "omp_get_thread_num"); |
| } |
| } |
| #endif |
| |
| if (malloc->getType()->isTokenTy()) { |
| llvm::errs() << " oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << " newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " malloc: " << *malloc << "\n"; |
| } |
| assert(!malloc->getType()->isTokenTy()); |
| { |
| CountTrackedPointers T(malloc->getType()); |
| if (T.derived) { |
| llvm::errs() << " oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << " newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " malloc: " << *malloc << "\n"; |
| } |
| assert(!T.derived); |
| } |
| |
| if (tape) { |
| if (idx == IndexMappingError) { |
| assert(malloc); |
| return UndefValue::get(malloc->getType()); |
| } |
| if (idx >= 0 && !tape->getType()->isStructTy()) { |
| llvm::errs() << "cacheForReverse incorrect tape type: " << *tape |
| << " idx: " << idx << "\n"; |
| } |
| assert(idx < 0 || tape->getType()->isStructTy()); |
| if (idx >= 0 && |
| (unsigned)idx >= cast<StructType>(tape->getType())->getNumElements()) { |
| llvm::errs() << "oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| if (malloc) |
| llvm::errs() << "malloc: " << *malloc << "\n"; |
| llvm::errs() << "tape: " << *tape << "\n"; |
| llvm::errs() << "idx: " << idx << "\n"; |
| } |
| assert(idx < 0 || |
| (unsigned)idx < cast<StructType>(tape->getType())->getNumElements()); |
| Value *ret = |
| (idx < 0) ? tape : BuilderQ.CreateExtractValue(tape, {(unsigned)idx}); |
| |
| if (ret->getType()->isEmptyTy()) { |
| if (auto inst = dyn_cast_or_null<Instruction>(malloc)) { |
| if (inst->getType() != ret->getType()) { |
| llvm::errs() << "oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "inst==malloc: " << *inst << "\n"; |
| llvm::errs() << "ret: " << *ret << "\n"; |
| } |
| assert(inst->getType() == ret->getType()); |
| if (replace) { |
| inst->replaceAllUsesWith(UndefValue::get(ret->getType())); |
| erase(inst); |
| } |
| } |
| Type *retType = ret->getType(); |
| if (replace) |
| if (auto ri = dyn_cast<Instruction>(ret)) |
| erase(ri); |
| return UndefValue::get(retType); |
| } |
| |
| LimitContext ctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| BuilderQ.GetInsertBlock()); |
| if (auto inst = dyn_cast<Instruction>(malloc)) |
| ctx = LimitContext(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| inst->getParent()); |
| if (auto found = findInMap(scopeMap, malloc)) { |
| ctx = found->second; |
| } |
| assert(isOriginalBlock(*ctx.Block)); |
| |
| bool inLoop; |
| if (ctx.ForceSingleIteration) { |
| inLoop = true; |
| ctx.ForceSingleIteration = false; |
| } else { |
| LoopContext lc; |
| inLoop = getContext(ctx.Block, lc); |
| } |
| |
| if (!inLoop) { |
| ret->setName(malloc->getName() + "_fromtape"); |
| if (omp) { |
| Value *tid = ompThreadId(); |
| Value *tPtr = BuilderQ.CreateInBoundsGEP(malloc->getType(), ret, |
| ArrayRef<Value *>(tid)); |
| ret = BuilderQ.CreateLoad(malloc->getType(), tPtr); |
| } |
| } else { |
| if (idx >= 0) |
| erase(cast<Instruction>(ret)); |
| IRBuilder<> entryBuilder(inversionAllocs); |
| entryBuilder.setFastMathFlags(getFast()); |
| ret = (idx < 0) ? tape |
| : entryBuilder.CreateExtractValue(tape, {(unsigned)idx}); |
| |
| assert(malloc); |
| |
| Type *innerType = nullptr; |
| |
| #if LLVM_VERSION_MAJOR < 17 |
| #if LLVM_VERSION_MAJOR >= 15 |
| if (ret->getContext().supportsTypedPointers()) { |
| #endif |
| innerType = ret->getType(); |
| for (size_t i = 0, |
| limit = getSubLimits( |
| /*inForwardPass*/ true, nullptr, |
| LimitContext( |
| /*ReverseLimit*/ reverseBlocks.size() > 0, |
| BuilderQ.GetInsertBlock())) |
| .size(); |
| i < limit; ++i) { |
| if (!isa<PointerType>(innerType)) { |
| llvm::errs() << "mod: " |
| << *BuilderQ.GetInsertBlock()->getParent()->getParent() |
| << "\n"; |
| llvm::errs() << "fn: " << *BuilderQ.GetInsertBlock()->getParent() |
| << "\n"; |
| llvm::errs() << "bq insertblock: " << *BuilderQ.GetInsertBlock() |
| << "\n"; |
| llvm::errs() << "ret: " << *ret << " type: " << *ret->getType() |
| << "\n"; |
| llvm::errs() << "innerType: " << *innerType << "\n"; |
| if (malloc) |
| llvm::errs() << " malloc: " << *malloc << " i=" << i |
| << " / lim = " << limit << "\n"; |
| } |
| assert(isa<PointerType>(innerType)); |
| innerType = innerType->getPointerElementType(); |
| } |
| #if LLVM_VERSION_MAJOR >= 15 |
| } else { |
| if (EfficientBoolCache && malloc->getType()->isIntegerTy() && |
| cast<IntegerType>(malloc->getType())->getBitWidth() == 1) |
| innerType = Type::getInt8Ty(malloc->getContext()); |
| else |
| innerType = malloc->getType(); |
| } |
| #endif |
| #else |
| if (EfficientBoolCache && malloc->getType()->isIntegerTy() && |
| cast<IntegerType>(malloc->getType())->getBitWidth() == 1) |
| innerType = Type::getInt8Ty(malloc->getContext()); |
| else |
| innerType = malloc->getType(); |
| #endif |
| |
| if (EfficientBoolCache && malloc->getType()->isIntegerTy() && |
| cast<IntegerType>(malloc->getType())->getBitWidth() == 1 && |
| innerType != ret->getType()) { |
| assert(innerType == Type::getInt8Ty(malloc->getContext())); |
| } else { |
| if (innerType != malloc->getType()) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Illegal loop cache type:\n"; |
| ss << *oldFunc << "\n"; |
| ss << *newFunc << "\n"; |
| ss << "innerType: " << *innerType << "\n"; |
| ss << "malloc->getType(): " << *malloc->getType() << "\n"; |
| ss << "ret: " << *ret << " - " << *ret->getType() << "\n"; |
| ss << "malloc: " << *malloc << "\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap(malloc), |
| ErrorType::InternalError, nullptr, nullptr, |
| nullptr); |
| } else { |
| DebugLoc loc; |
| if (auto I = dyn_cast<Instruction>(malloc)) |
| EmitFailure("LoopCache", I->getDebugLoc(), I, ss.str()); |
| else |
| EmitFailure("LoopCache", DebugLoc(), newFunc, ss.str()); |
| } |
| return UndefValue::get(malloc->getType()); |
| } |
| } |
| |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| BuilderQ.GetInsertBlock()); |
| AllocaInst *cache = |
| createCacheForScope(lctx, innerType, "mdyncache_fromtape", |
| ((DiffeGradientUtils *)this)->FreeMemory, false); |
| assert(malloc); |
| bool isi1 = malloc->getType()->isIntegerTy() && |
| cast<IntegerType>(malloc->getType())->getBitWidth() == 1; |
| assert(isa<PointerType>(cache->getType())); |
| #if LLVM_VERSION_MAJOR < 17 |
| #if LLVM_VERSION_MAJOR >= 15 |
| if (cache->getContext().supportsTypedPointers()) { |
| #endif |
| assert(cache->getType()->getPointerElementType() == ret->getType()); |
| #if LLVM_VERSION_MAJOR >= 15 |
| } |
| #endif |
| #endif |
| entryBuilder.CreateStore(ret, cache); |
| |
| auto v = |
| lookupValueFromCache(innerType, /*forwardPass*/ true, BuilderQ, lctx, |
| cache, isi1, /*available*/ ValueToValueMapTy()); |
| if (malloc) { |
| assert(v->getType() == malloc->getType()); |
| } |
| insert_or_assign(scopeMap, v, |
| std::make_pair(AssertingVH<AllocaInst>(cache), ctx)); |
| ret = cast<Instruction>(v); |
| } |
| |
| if (malloc && !isa<UndefValue>(malloc)) { |
| if (malloc->getType() != ret->getType()) { |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *malloc << "\n"; |
| llvm::errs() << *ret << "\n"; |
| } |
| assert(malloc->getType() == ret->getType()); |
| |
| if (replace) { |
| auto found = newToOriginalFn.find(malloc); |
| if (found != newToOriginalFn.end()) { |
| Value *orig = found->second; |
| originalToNewFn[orig] = ret; |
| newToOriginalFn.erase(malloc); |
| newToOriginalFn[ret] = orig; |
| } |
| } |
| |
| if (auto found = findInMap(scopeMap, malloc)) { |
| // There already exists an alloaction for this, we should fully remove |
| // it |
| if (!inLoop) { |
| |
| // Remove stores into |
| SmallVector<Instruction *, 3> stores( |
| scopeInstructions[found->first].begin(), |
| scopeInstructions[found->first].end()); |
| scopeInstructions.erase(found->first); |
| for (int i = stores.size() - 1; i >= 0; i--) { |
| erase(stores[i]); |
| } |
| |
| SmallVector<User *, 4> users; |
| for (auto u : found->first->users()) { |
| users.push_back(u); |
| } |
| for (auto u : users) { |
| if (auto li = dyn_cast<LoadInst>(u)) { |
| IRBuilder<> lb(li); |
| if (replace) { |
| |
| Value *replacewith = |
| (idx < 0) ? tape |
| : lb.CreateExtractValue(tape, {(unsigned)idx}); |
| if (!inLoop && omp) { |
| Value *tid = ompThreadId(); |
| Value *tPtr = lb.CreateInBoundsGEP(li->getType(), replacewith, |
| ArrayRef<Value *>(tid)); |
| replacewith = lb.CreateLoad(li->getType(), tPtr); |
| } |
| if (li->getType() != replacewith->getType()) { |
| llvm::errs() << " oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << " newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " malloc: " << *malloc << "\n"; |
| llvm::errs() << " li: " << *li << "\n"; |
| llvm::errs() << " u: " << *u << "\n"; |
| llvm::errs() << " replacewith: " << *replacewith |
| << " idx=" << idx << " - tape=" << *tape << "\n"; |
| } |
| assert(li->getType() == replacewith->getType()); |
| li->replaceAllUsesWith(replacewith); |
| } else { |
| auto phi = |
| lb.CreatePHI(li->getType(), 0, li->getName() + "_cfrphi"); |
| unwrappedLoads[phi] = malloc; |
| li->replaceAllUsesWith(phi); |
| } |
| erase(li); |
| } else { |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "malloc: " << *malloc << "\n"; |
| llvm::errs() << "scopeMap[malloc]: " << *found->first << "\n"; |
| llvm::errs() << "u: " << *u << "\n"; |
| assert(0 && "illegal use for out of loop scopeMap1"); |
| } |
| } |
| |
| { |
| AllocaInst *preerase = found->first; |
| scopeMap.erase(malloc); |
| erase(preerase); |
| } |
| } else { |
| // Remove allocations for scopealloc since it is already allocated |
| // by the augmented forward pass |
| // Remove stores into |
| SmallVector<Instruction *, 3> stores( |
| scopeInstructions[found->first].begin(), |
| scopeInstructions[found->first].end()); |
| scopeInstructions.erase(found->first); |
| scopeAllocs.erase(found->first); |
| for (int i = stores.size() - 1; i >= 0; i--) { |
| erase(stores[i]); |
| } |
| |
| // Remove frees |
| SmallVector<CallInst *, 3> tofree(scopeFrees[found->first].begin(), |
| scopeFrees[found->first].end()); |
| scopeFrees.erase(found->first); |
| for (auto freeinst : tofree) { |
| // This deque contains a list of operations |
| // we can erasing upon erasing the free (and so on). |
| // Since multiple operations can have the same operand, |
| // this deque can contain the same value multiple times. |
| // To remedy this we use a tracking value handle which will |
| // be set to null when erased. |
| std::deque<WeakTrackingVH> ops = {freeinst->getArgOperand(0)}; |
| erase(freeinst); |
| |
| while (ops.size()) { |
| auto z = dyn_cast_or_null<Instruction>(ops[0]); |
| ops.pop_front(); |
| if (z && z->getNumUses() == 0 && !z->isUsedByMetadata()) { |
| for (unsigned i = 0; i < z->getNumOperands(); ++i) { |
| ops.push_back(z->getOperand(i)); |
| } |
| erase(z); |
| } |
| } |
| } |
| |
| // uses of the alloc |
| SmallVector<User *, 4> users; |
| for (auto u : found->first->users()) { |
| users.push_back(u); |
| } |
| for (auto u : users) { |
| if (auto li = dyn_cast<LoadInst>(u)) { |
| // even with replace off, this can be replaced |
| // as since we're in a loop this load is a load of cache |
| // not of the final value (thereby overwriting the new |
| // inst |
| IRBuilder<> lb(li); |
| auto replacewith = |
| (idx < 0) ? tape |
| : lb.CreateExtractValue(tape, {(unsigned)idx}); |
| li->replaceAllUsesWith(replacewith); |
| erase(li); |
| } else { |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "malloc: " << *malloc << "\n"; |
| llvm::errs() << "scopeMap[malloc]: " << *found->first << "\n"; |
| llvm::errs() << "u: " << *u << "\n"; |
| assert(0 && "illegal use for out of loop scopeMap2"); |
| } |
| } |
| |
| AllocaInst *preerase = found->first; |
| scopeMap.erase(malloc); |
| if (replace) |
| erase(preerase); |
| } |
| } |
| if (replace) |
| cast<Instruction>(malloc)->replaceAllUsesWith(ret); |
| ret->takeName(malloc); |
| if (replace) { |
| auto malloci = cast<Instruction>(malloc); |
| if (malloci == &*BuilderQ.GetInsertPoint()) { |
| BuilderQ.SetInsertPoint(malloci->getNextNode()); |
| } |
| erase(malloci); |
| } |
| } |
| return ret; |
| } else { |
| assert(malloc); |
| |
| assert(idx >= 0 && (unsigned)idx == addedTapeVals.size()); |
| |
| if (isa<UndefValue>(malloc)) { |
| addedTapeVals.push_back(malloc); |
| return malloc; |
| } |
| |
| LimitContext ctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| BuilderQ.GetInsertBlock()); |
| if (auto inst = dyn_cast<Instruction>(malloc)) |
| ctx = LimitContext(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| inst->getParent()); |
| if (auto found = findInMap(scopeMap, malloc)) { |
| ctx = found->second; |
| } |
| |
| bool inLoop; |
| |
| if (ctx.ForceSingleIteration) { |
| inLoop = true; |
| ctx.ForceSingleIteration = false; |
| } else { |
| LoopContext lc; |
| inLoop = getContext(ctx.Block, lc); |
| } |
| |
| if (!inLoop) { |
| Value *toStoreInTape = malloc; |
| if (omp) { |
| Value *numThreads = ompNumThreads(); |
| Value *tid = ompThreadId(); |
| IRBuilder<> entryBuilder(inversionAllocs); |
| |
| auto firstallocation = |
| CreateAllocation(entryBuilder, malloc->getType(), numThreads, |
| malloc->getName() + "_malloccache"); |
| Value *tPtr = entryBuilder.CreateInBoundsGEP( |
| malloc->getType(), firstallocation, ArrayRef<Value *>(tid)); |
| if (auto inst = dyn_cast<Instruction>(malloc)) { |
| entryBuilder.SetInsertPoint(inst->getNextNode()); |
| } |
| entryBuilder.CreateStore(malloc, tPtr); |
| toStoreInTape = firstallocation; |
| } |
| addedTapeVals.push_back(toStoreInTape); |
| return malloc; |
| } |
| |
| ensureLookupCached( |
| cast<Instruction>(malloc), |
| /*shouldFree=*/reverseBlocks.size() > 0, |
| /*scope*/ nullptr, |
| cast<Instruction>(malloc)->getMetadata(LLVMContext::MD_tbaa)); |
| auto found2 = scopeMap.find(malloc); |
| assert(found2 != scopeMap.end()); |
| assert(found2->second.first); |
| |
| Value *toadd; |
| toadd = scopeAllocs[found2->second.first][0]; |
| for (auto u : toadd->users()) { |
| if (auto ci = dyn_cast<CastInst>(u)) { |
| toadd = ci; |
| break; |
| } |
| } |
| |
| // llvm::errs() << " malloc: " << *malloc << "\n"; |
| // llvm::errs() << " toadd: " << *toadd << "\n"; |
| #if LLVM_VERSION_MAJOR < 17 |
| #if LLVM_VERSION_MAJOR >= 15 |
| if (toadd->getContext().supportsTypedPointers()) { |
| #endif |
| Type *innerType = toadd->getType(); |
| for (size_t i = 0, |
| limit = getSubLimits( |
| /*inForwardPass*/ true, nullptr, |
| LimitContext( |
| /*ReverseLimit*/ reverseBlocks.size() > 0, |
| BuilderQ.GetInsertBlock())) |
| .size(); |
| i < limit; ++i) { |
| innerType = innerType->getPointerElementType(); |
| } |
| if (EfficientBoolCache && malloc->getType()->isIntegerTy() && |
| toadd->getType() != innerType && |
| cast<IntegerType>(malloc->getType())->getBitWidth() == 1) { |
| assert(innerType == Type::getInt8Ty(toadd->getContext())); |
| } else { |
| if (innerType != malloc->getType()) { |
| llvm::errs() << "oldFunc:" << *oldFunc << "\n"; |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " toadd: " << *toadd << "\n"; |
| llvm::errs() << "innerType: " << *innerType << "\n"; |
| llvm::errs() << "malloc: " << *malloc << "\n"; |
| } |
| assert(innerType == malloc->getType()); |
| } |
| #if LLVM_VERSION_MAJOR >= 15 |
| } |
| #endif |
| #endif |
| addedTapeVals.push_back(toadd); |
| return malloc; |
| } |
| llvm::errs() |
| << "Fell through on cacheForReverse. This should never happen.\n"; |
| assert(false); |
| } |
| |
| BasicBlock *GradientUtils::prepRematerializedLoopEntry(LoopContext &lc) { |
| auto header = lc.header; |
| SmallPtrSet<Instruction *, 1> loopRematerializations; |
| SmallPtrSet<Instruction *, 1> loopReallocations; |
| SmallPtrSet<Instruction *, 1> loopShadowReallocations; |
| SmallSetVector<Instruction *, 1> loopShadowZeroInits; |
| SmallPtrSet<Instruction *, 1> loopShadowRematerializations; |
| Loop *origLI = nullptr; |
| for (auto pair : rematerializableAllocations) { |
| if (pair.second.LI && |
| getNewFromOriginal(pair.second.LI->getHeader()) == header) { |
| if (allocationsToBeRematerialized.count(pair.first)) { |
| if (auto inst = dyn_cast<Instruction>(pair.first)) |
| if (pair.second.LI->contains(inst->getParent())) { |
| loopReallocations.insert(inst); |
| } |
| for (auto I : pair.second.stores) |
| loopRematerializations.insert(I); |
| origLI = pair.second.LI; |
| } |
| } |
| } |
| for (auto pair : backwardsOnlyShadows) { |
| if (pair.second.LI && |
| getNewFromOriginal(pair.second.LI->getHeader()) == header) { |
| if (auto inst = dyn_cast<Instruction>(pair.first)) { |
| bool restoreStores = false; |
| if (pair.second.LI->contains(inst->getParent())) { |
| // TODO later make it so primalInitialize can be restored |
| // rather than cached from primal |
| if (!pair.second.primalInitialize) { |
| loopShadowReallocations.insert(inst); |
| restoreStores = true; |
| } |
| } else { |
| // if (pair.second.primalInitialize) { |
| // loopShadowZeroInits.insert(inst); |
| //} |
| restoreStores = true; |
| } |
| if (restoreStores) { |
| for (auto I : pair.second.stores) { |
| loopShadowRematerializations.insert(I); |
| } |
| } |
| origLI = pair.second.LI; |
| } |
| } |
| } |
| if (loopRematerializations.size() != 0 || loopReallocations.size() != 0 || |
| loopShadowRematerializations.size() != 0 || |
| loopShadowReallocations.size() != 0 || loopShadowZeroInits.size() != 0) { |
| auto found = rematerializedLoops_cache.find(header); |
| if (found != rematerializedLoops_cache.end()) { |
| return found->second; |
| } |
| |
| BasicBlock *enterB = |
| BasicBlock::Create(header->getContext(), "remat_enter", newFunc); |
| rematerializedLoops_cache[header] = enterB; |
| std::map<BasicBlock *, BasicBlock *> origToNewForward; |
| for (auto B : origLI->getBlocks()) { |
| BasicBlock *newB = BasicBlock::Create( |
| B->getContext(), "remat_" + header->getName() + "_" + B->getName(), |
| newFunc); |
| origToNewForward[B] = newB; |
| reverseBlockToPrimal[newB] = getNewFromOriginal(B); |
| if (B == origLI->getHeader()) { |
| IRBuilder<> NB(newB); |
| for (auto inst : loopShadowZeroInits) { |
| auto anti = lookupM(invertPointerM(inst, NB), NB); |
| StringRef funcName; |
| SmallVector<Value *, 8> args; |
| if (auto orig = dyn_cast<CallInst>(inst)) { |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : orig->args()) |
| #else |
| for (auto &arg : orig->arg_operands()) |
| #endif |
| { |
| args.push_back(lookupM(getNewFromOriginal(arg), NB)); |
| } |
| funcName = getFuncNameFromCall(orig); |
| } else if (auto AI = dyn_cast<AllocaInst>(inst)) { |
| funcName = "malloc"; |
| Value *sz = lookupM(getNewFromOriginal(AI->getArraySize()), NB); |
| |
| auto ci = ConstantInt::get( |
| sz->getType(), |
| B->getParent() |
| ->getParent() |
| ->getDataLayout() |
| .getTypeAllocSizeInBits(AI->getAllocatedType()) / |
| 8); |
| sz = NB.CreateMul(sz, ci); |
| args.push_back(sz); |
| } |
| assert(funcName.size()); |
| |
| applyChainRule( |
| NB, |
| [&](Value *anti) { |
| zeroKnownAllocation(NB, anti, args, funcName, TLI, |
| dyn_cast<CallInst>(inst)); |
| }, |
| anti); |
| } |
| } |
| } |
| |
| ValueToValueMapTy available; |
| |
| { |
| IRBuilder<> NB(enterB); |
| NB.CreateBr(origToNewForward[origLI->getHeader()]); |
| } |
| |
| std::function<void(Loop *, bool)> handleLoop = [&](Loop *OL, bool subLoop) { |
| if (subLoop) { |
| auto Header = OL->getHeader(); |
| IRBuilder<> NB(origToNewForward[Header]); |
| LoopContext flc; |
| getContext(getNewFromOriginal(Header), flc); |
| |
| auto iv = NB.CreatePHI(flc.var->getType(), 2, "fiv"); |
| auto inc = NB.CreateAdd(iv, ConstantInt::get(iv->getType(), 1)); |
| |
| for (auto PH : predecessors(Header)) { |
| if (notForAnalysis.count(PH)) |
| continue; |
| |
| if (OL->contains(PH)) |
| iv->addIncoming(inc, origToNewForward[PH]); |
| else |
| iv->addIncoming(ConstantInt::get(iv->getType(), 0), |
| origToNewForward[PH]); |
| } |
| available[flc.var] = iv; |
| available[flc.incvar] = inc; |
| } |
| for (auto SL : OL->getSubLoops()) |
| handleLoop(SL, /*subLoop*/ true); |
| }; |
| handleLoop(origLI, /*subLoop*/ false); |
| |
| for (auto B : origLI->getBlocks()) { |
| auto newB = origToNewForward[B]; |
| IRBuilder<> NB(newB); |
| |
| // TODO fill available with relevant IV's surrounding and |
| // IV's of inner loop phi's |
| |
| for (auto &I : *B) { |
| // Only handle store, memset, and julia.write_barrier |
| if (loopRematerializations.count(&I)) { |
| if (auto SI = dyn_cast<StoreInst>(&I)) { |
| auto ts = NB.CreateStore( |
| lookupM(getNewFromOriginal(SI->getValueOperand()), NB, |
| available), |
| lookupM(getNewFromOriginal(SI->getPointerOperand()), NB, |
| available)); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| ToCopy2.push_back(LLVMContext::MD_alias_scope); |
| ts->copyMetadata(*SI, ToCopy2); |
| ts->setAlignment(SI->getAlign()); |
| ts->setVolatile(SI->isVolatile()); |
| ts->setOrdering(SI->getOrdering()); |
| ts->setSyncScopeID(SI->getSyncScopeID()); |
| ts->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| } else if (auto CI = dyn_cast<CallInst>(&I)) { |
| StringRef funcName = getFuncNameFromCall(CI); |
| if (funcName == "enzyme_zerotype") |
| continue; |
| if (funcName == "julia.write_barrier" || |
| funcName == "julia.write_barrier_binding" || |
| isa<MemSetInst>(&I) || isa<MemTransferInst>(&I)) { |
| |
| // TODO |
| SmallVector<Value *, 2> args; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : CI->args()) |
| #else |
| for (auto &arg : CI->arg_operands()) |
| #endif |
| args.push_back(lookupM(getNewFromOriginal(arg), NB, available)); |
| |
| SmallVector<ValueType, 2> BundleTypes(args.size(), |
| ValueType::Primal); |
| |
| auto Defs = getInvertedBundles(CI, BundleTypes, NB, |
| /*lookup*/ true, available); |
| auto cal = NB.CreateCall(CI->getFunctionType(), |
| CI->getCalledOperand(), args, Defs); |
| cal->setAttributes(CI->getAttributes()); |
| cal->setCallingConv(CI->getCallingConv()); |
| cal->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| } else { |
| assert(isDeallocationFunction(funcName, TLI)); |
| continue; |
| } |
| } else { |
| assert(0 && "unhandlable loop rematerialization instruction"); |
| } |
| } else if (loopReallocations.count(&I)) { |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| &newFunc->getEntryBlock()); |
| |
| auto inst = getNewFromOriginal((Value *)&I); |
| |
| auto found = scopeMap.find(inst); |
| if (found == scopeMap.end()) { |
| AllocaInst *cache = createCacheForScope( |
| lctx, inst->getType(), inst->getName(), /*shouldFree*/ true); |
| assert(cache); |
| found = insert_or_assign( |
| scopeMap, inst, |
| std::pair<AssertingVH<AllocaInst>, LimitContext>(cache, lctx)); |
| } |
| auto cache = found->second.first; |
| if (auto MD = hasMetadata(&I, "enzyme_fromstack")) { |
| auto replacement = NB.CreateAlloca( |
| Type::getInt8Ty(I.getContext()), |
| lookupM(getNewFromOriginal(I.getOperand(0)), NB, available)); |
| for (auto MD : {"enzyme_active", "enzyme_inactive", "enzyme_type", |
| "enzymejl_allocart", "enzymejl_allocart_name", |
| "enzymejl_gc_alloc_rt"}) |
| if (auto M = I.getMetadata(MD)) |
| replacement->setMetadata(MD, M); |
| auto Alignment = |
| cast<ConstantInt>( |
| cast<ConstantAsMetadata>(MD->getOperand(0))->getValue()) |
| ->getLimitedValue(); |
| replacement->setAlignment(Align(Alignment)); |
| replacement->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| storeInstructionInCache(lctx, NB, replacement, cache); |
| } else if (auto CI = dyn_cast<CallInst>(&I)) { |
| SmallVector<Value *, 2> args; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : CI->args()) |
| #else |
| for (auto &arg : CI->arg_operands()) |
| #endif |
| args.push_back(lookupM(getNewFromOriginal(arg), NB, available)); |
| |
| SmallVector<ValueType, 2> BundleTypes(args.size(), |
| ValueType::Primal); |
| |
| auto Defs = getInvertedBundles(CI, BundleTypes, NB, |
| /*lookup*/ true, available); |
| auto cal = NB.CreateCall(CI->getCalledFunction(), args, Defs); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| ToCopy2.push_back(LLVMContext::MD_alias_scope); |
| cal->copyMetadata(*CI, ToCopy2); |
| cal->setName("remat_" + CI->getName()); |
| cal->setAttributes(CI->getAttributes()); |
| cal->setCallingConv(CI->getCallingConv()); |
| cal->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| storeInstructionInCache(lctx, NB, cal, cache); |
| } else { |
| llvm::errs() << " realloc: " << I << "\n"; |
| llvm_unreachable("Unknown loop reallocation"); |
| } |
| } |
| if (loopShadowRematerializations.count(&I)) { |
| if (auto SI = dyn_cast<StoreInst>(&I)) { |
| Value *orig_ptr = SI->getPointerOperand(); |
| Value *orig_val = SI->getValueOperand(); |
| Type *valType = orig_val->getType(); |
| assert(!isConstantValue(orig_ptr)); |
| |
| auto &DL = newFunc->getParent()->getDataLayout(); |
| |
| bool constantval = isConstantValue(orig_val) || |
| parseTBAA(I, DL, nullptr)[{-1}].isIntegral(); |
| |
| // TODO allow recognition of other types that could contain |
| // pointers [e.g. {void*, void*} or <2 x i64> ] |
| auto storeSize = DL.getTypeSizeInBits(valType) / 8; |
| |
| //! Storing a floating point value |
| Type *FT = nullptr; |
| if (valType->isFPOrFPVectorTy()) { |
| FT = valType->getScalarType(); |
| } else if (!valType->isPointerTy()) { |
| if (looseTypeAnalysis) { |
| auto fp = TR.firstPointer(storeSize, orig_ptr, &I, this, |
| /*errifnotfound*/ nullptr, |
| /*pointerIntSame*/ true); |
| if (fp.isKnown()) { |
| FT = fp.isFloat(); |
| llvm::errs() << "assuming type as " << fp.str() |
| << " for store: " << I << "\n"; |
| } else if (isa<ConstantInt>(orig_val) || |
| valType->isIntOrIntVectorTy()) { |
| llvm::errs() |
| << "assuming type as integral for store: " << I << "\n"; |
| FT = nullptr; |
| } else { |
| TR.firstPointer(storeSize, orig_ptr, &I, this, |
| /*errifnotfound*/ &NB, |
| /*pointerIntSame*/ true); |
| } |
| } else { |
| FT = TR.firstPointer(storeSize, orig_ptr, &I, this, |
| /*errifnotfound*/ &NB, |
| /*pointerIntSame*/ true) |
| .isFloat(); |
| } |
| } |
| if (!FT) { |
| Value *valueop = nullptr; |
| if (constantval) { |
| Value *val = |
| lookupM(getNewFromOriginal(orig_val), NB, available); |
| valueop = val; |
| if (getWidth() > 1) { |
| Value *array = UndefValue::get(getShadowType(val->getType())); |
| for (unsigned i = 0; i < getWidth(); ++i) { |
| array = NB.CreateInsertValue(array, val, {i}); |
| } |
| valueop = array; |
| } |
| } else { |
| valueop = lookupM(invertPointerM(orig_val, NB), NB, available); |
| } |
| SmallVector<Metadata *, 1> prevScopes; |
| if (auto prev = SI->getMetadata(LLVMContext::MD_alias_scope)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| prevScopes.push_back(M); |
| } |
| } |
| SmallVector<Metadata *, 1> prevNoAlias; |
| if (auto prev = SI->getMetadata(LLVMContext::MD_noalias)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| prevNoAlias.push_back(M); |
| } |
| } |
| auto align = SI->getAlign(); |
| setPtrDiffe(SI, orig_ptr, valueop, NB, align, 0, storeSize, |
| SI->isVolatile(), SI->getOrdering(), |
| SI->getSyncScopeID(), |
| /*mask*/ nullptr, prevNoAlias, prevScopes); |
| } |
| // TODO shadow memtransfer |
| } else if (auto MS = dyn_cast<MemSetInst>(&I)) { |
| if (!isConstantValue(MS->getArgOperand(0))) { |
| Value *args[4] = { |
| lookupM(invertPointerM(MS->getArgOperand(0), NB), NB, |
| available), |
| lookupM(getNewFromOriginal(MS->getArgOperand(1)), NB, |
| available), |
| lookupM(getNewFromOriginal(MS->getArgOperand(2)), NB, |
| available), |
| lookupM(getNewFromOriginal(MS->getArgOperand(3)), NB, |
| available)}; |
| |
| ValueType BundleTypes[4] = {ValueType::Shadow, ValueType::Primal, |
| ValueType::Primal, ValueType::Primal}; |
| auto Defs = getInvertedBundles(MS, BundleTypes, NB, |
| /*lookup*/ true, available); |
| auto cal = NB.CreateCall(MS->getCalledFunction(), args, Defs); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| ToCopy2.push_back(LLVMContext::MD_alias_scope); |
| cal->copyMetadata(*MS, ToCopy2); |
| cal->setAttributes(MS->getAttributes()); |
| cal->setCallingConv(MS->getCallingConv()); |
| cal->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| } |
| } else if (auto CI = dyn_cast<CallInst>(&I)) { |
| StringRef funcName = getFuncNameFromCall(CI); |
| if (funcName == "julia.write_barrier" || |
| funcName == "julia.write_barrier_binding") { |
| |
| // TODO |
| SmallVector<Value *, 2> args; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : CI->args()) |
| #else |
| for (auto &arg : CI->arg_operands()) |
| #endif |
| if (!isConstantValue(arg)) |
| args.push_back( |
| lookupM(invertPointerM(arg, NB), NB, available)); |
| |
| if (args.size()) { |
| SmallVector<ValueType, 2> BundleTypes(args.size(), |
| ValueType::Primal); |
| |
| auto Defs = getInvertedBundles(CI, BundleTypes, NB, |
| /*lookup*/ true, available); |
| auto cal = NB.CreateCall(CI->getFunctionType(), |
| CI->getCalledOperand(), args, Defs); |
| cal->setAttributes(CI->getAttributes()); |
| cal->setCallingConv(CI->getCallingConv()); |
| cal->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| } |
| } else { |
| assert(isDeallocationFunction(funcName, TLI)); |
| continue; |
| } |
| } else { |
| assert(0 && |
| "unhandlable loop shadow rematerialization instruction"); |
| } |
| } else if (loopShadowReallocations.count(&I)) { |
| |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| &newFunc->getEntryBlock()); |
| auto ipfound = invertedPointers.find(&I); |
| PHINode *placeholder = cast<PHINode>(&*ipfound->second); |
| |
| auto found = scopeMap.find(placeholder); |
| if (found == scopeMap.end()) { |
| AllocaInst *cache = createCacheForScope( |
| lctx, placeholder->getType(), placeholder->getName(), |
| /*shouldFree*/ true); |
| assert(cache); |
| Value *placeholder_tmp = placeholder; |
| found = insert_or_assign( |
| scopeMap, placeholder_tmp, |
| std::pair<AssertingVH<AllocaInst>, LimitContext>(cache, lctx)); |
| } |
| auto cache = found->second.first; |
| Value *anti = nullptr; |
| |
| if (auto orig = dyn_cast<CallInst>(&I)) { |
| StringRef funcName = getFuncNameFromCall(orig); |
| assert(funcName.size()); |
| |
| SmallVector<Value *, 8> args; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : orig->args()) |
| #else |
| for (auto &arg : orig->arg_operands()) |
| #endif |
| { |
| args.push_back(lookupM(getNewFromOriginal(arg), NB)); |
| } |
| |
| placeholder->setName(""); |
| if (shadowHandlers.find(funcName) != shadowHandlers.end()) { |
| |
| anti = shadowHandlers[funcName](NB, orig, args, this); |
| } else { |
| auto rule = [&]() { |
| Value *anti = NB.CreateCall(orig->getFunctionType(), |
| orig->getCalledOperand(), args, |
| orig->getName() + "'mi"); |
| cast<CallInst>(anti)->setAttributes(orig->getAttributes()); |
| cast<CallInst>(anti)->setCallingConv(orig->getCallingConv()); |
| cast<CallInst>(anti)->setDebugLoc( |
| getNewFromOriginal(I.getDebugLoc())); |
| |
| cast<CallInst>(anti)->addAttribute(AttributeList::ReturnIndex, |
| Attribute::NoAlias); |
| cast<CallInst>(anti)->addAttribute(AttributeList::ReturnIndex, |
| Attribute::NonNull); |
| return anti; |
| }; |
| |
| anti = applyChainRule(orig->getType(), NB, rule); |
| |
| if (auto MD = hasMetadata(orig, "enzyme_fromstack")) { |
| auto rule = [&](Value *anti) { |
| AllocaInst *replacement = NB.CreateAlloca( |
| Type::getInt8Ty(orig->getContext()), args[0]); |
| for (auto MD : |
| {"enzyme_active", "enzyme_inactive", "enzyme_type", |
| "enzymejl_allocart", "enzymejl_allocart_name"}) |
| if (auto M = I.getMetadata(MD)) |
| replacement->setMetadata(MD, M); |
| replacement->takeName(anti); |
| auto Alignment = cast<ConstantInt>(cast<ConstantAsMetadata>( |
| MD->getOperand(0)) |
| ->getValue()) |
| ->getLimitedValue(); |
| replacement->setAlignment(Align(Alignment)); |
| replacement->setDebugLoc(getNewFromOriginal(I.getDebugLoc())); |
| return replacement; |
| }; |
| |
| Value *replacement = applyChainRule( |
| Type::getInt8Ty(orig->getContext()), NB, rule, anti); |
| |
| replaceAWithB(cast<Instruction>(anti), replacement); |
| erase(cast<Instruction>(anti)); |
| anti = replacement; |
| } |
| |
| applyChainRule( |
| NB, |
| [&](Value *anti) { |
| zeroKnownAllocation(NB, anti, args, funcName, TLI, orig); |
| }, |
| anti); |
| } |
| } else { |
| llvm_unreachable("Unknown shadow rematerialization value"); |
| } |
| assert(anti); |
| storeInstructionInCache(lctx, NB, anti, cache); |
| } |
| } |
| |
| llvm::SmallPtrSet<llvm::BasicBlock *, 8> origExitBlocks; |
| getExitBlocks(origLI, origExitBlocks); |
| // Remap a branch to the header to enter the incremented |
| // reverse of that block. |
| auto remap = [&](BasicBlock *rB) { |
| // Remap of an exit branch is to go to the reverse |
| // exiting block. |
| if (origExitBlocks.count(rB)) { |
| return reverseBlocks[getNewFromOriginal(B)].front(); |
| } |
| // Reverse of an incrementing branch is go to the |
| // reverse of the branching block. |
| if (rB == origLI->getHeader()) |
| return reverseBlocks[getNewFromOriginal(B)].front(); |
| auto found = origToNewForward.find(rB); |
| if (found == origToNewForward.end()) { |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *origLI << "\n"; |
| llvm::errs() << *rB << "\n"; |
| } |
| assert(found != origToNewForward.end()); |
| return found->second; |
| }; |
| |
| // TODO clone terminator |
| auto TI = B->getTerminator(); |
| assert(TI); |
| if (notForAnalysis.count(B)) { |
| NB.CreateUnreachable(); |
| } else if (auto BI = dyn_cast<BranchInst>(TI)) { |
| if (BI->isUnconditional()) { |
| if (notForAnalysis.count(BI->getSuccessor(0))) |
| NB.CreateUnreachable(); |
| else |
| NB.CreateBr(remap(BI->getSuccessor(0))); |
| } else { |
| if (notForAnalysis.count(BI->getSuccessor(0))) { |
| if (notForAnalysis.count(BI->getSuccessor(1))) { |
| NB.CreateUnreachable(); |
| } else { |
| NB.CreateBr(remap(BI->getSuccessor(1))); |
| } |
| } else if (notForAnalysis.count(BI->getSuccessor(1))) { |
| NB.CreateBr(remap(BI->getSuccessor(0))); |
| } else { |
| NB.CreateCondBr( |
| lookupM(getNewFromOriginal(BI->getCondition()), NB, available), |
| remap(BI->getSuccessor(0)), remap(BI->getSuccessor(1))); |
| } |
| } |
| } else if (auto SI = dyn_cast<SwitchInst>(TI)) { |
| BasicBlock *newDest = nullptr; |
| if (!notForAnalysis.count(SI->getDefaultDest())) |
| newDest = remap(SI->getDefaultDest()); |
| else { |
| for (auto cas : SI->cases()) { |
| if (!notForAnalysis.count(cas.getCaseSuccessor())) |
| newDest = remap(cas.getCaseSuccessor()); |
| break; |
| } |
| } |
| if (!newDest) { |
| NB.CreateUnreachable(); |
| } else { |
| auto NSI = NB.CreateSwitch( |
| lookupM(getNewFromOriginal(SI->getCondition()), NB, available), |
| newDest); |
| for (auto cas : SI->cases()) { |
| if (!notForAnalysis.count(cas.getCaseSuccessor())) |
| NSI->addCase(cas.getCaseValue(), remap(cas.getCaseSuccessor())); |
| } |
| } |
| } else { |
| assert(isa<UnreachableInst>(TI)); |
| NB.CreateUnreachable(); |
| } |
| // Fixup phi nodes that may have their predecessors now changed by |
| // the phi unwrapping |
| if (!notForAnalysis.count(B) && |
| NB.GetInsertBlock() != origToNewForward[B]) { |
| for (auto S0 : successors(B)) { |
| if (!origToNewForward.count(S0)) |
| continue; |
| auto S = origToNewForward[S0]; |
| assert(S); |
| for (auto I = S->begin(), E = S->end(); I != E; ++I) { |
| PHINode *orig = dyn_cast<PHINode>(&*I); |
| if (orig == nullptr) |
| break; |
| for (unsigned Op = 0, NumOps = orig->getNumOperands(); Op != NumOps; |
| ++Op) |
| if (orig->getIncomingBlock(Op) == origToNewForward[B]) |
| orig->setIncomingBlock(Op, NB.GetInsertBlock()); |
| } |
| } |
| } |
| } |
| return enterB; |
| } |
| return nullptr; |
| } |
| |
| /// Given an edge from BB to branchingBlock get the corresponding block to |
| /// branch to in the reverse pass |
| BasicBlock *GradientUtils::getReverseOrLatchMerge(BasicBlock *BB, |
| BasicBlock *branchingBlock) { |
| assert(BB); |
| // BB should be a forward pass block, assert that |
| if (reverseBlocks.find(BB) == reverseBlocks.end()) { |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << "BB: " << *BB << "\n"; |
| llvm::errs() << "branchingBlock: " << *branchingBlock << "\n"; |
| } |
| assert(reverseBlocks.find(BB) != reverseBlocks.end()); |
| assert(reverseBlocks.find(branchingBlock) != reverseBlocks.end()); |
| LoopContext lc; |
| bool inLoop = getContext(BB, lc); |
| |
| LoopContext branchingContext; |
| bool inLoopContext = getContext(branchingBlock, branchingContext); |
| |
| if (!inLoop) |
| return reverseBlocks[BB].front(); |
| |
| auto tup = std::make_tuple(BB, branchingBlock); |
| if (newBlocksForLoop_cache.find(tup) != newBlocksForLoop_cache.end()) |
| return newBlocksForLoop_cache[tup]; |
| |
| // If we're reversing a latch edge. |
| bool incEntering = inLoopContext && branchingBlock == lc.header && |
| lc.header == branchingContext.header; |
| |
| auto L = LI.getLoopFor(BB); |
| auto latches = getLatches(L, lc.exitBlocks); |
| // If we're reverseing a loop exit. |
| bool exitEntering = |
| std::find(latches.begin(), latches.end(), BB) != latches.end() && |
| std::find(lc.exitBlocks.begin(), lc.exitBlocks.end(), branchingBlock) != |
| lc.exitBlocks.end(); |
| |
| // It is illegal to be both an increment into a loop, and exiting the loop. |
| assert(!(incEntering && exitEntering)); |
| |
| // If we're re-entering a loop, prepare a loop-level forward pass to |
| // rematerialize any loop-scope rematerialization. |
| |
| if (incEntering) { |
| BasicBlock *resumeblock = reverseBlocks[BB].front(); |
| auto tmp_resumeblock = prepRematerializedLoopEntry(lc); |
| if (tmp_resumeblock) |
| resumeblock = tmp_resumeblock; |
| BasicBlock *incB = BasicBlock::Create( |
| BB->getContext(), "inc" + reverseBlocks[lc.header].front()->getName(), |
| BB->getParent()); |
| incB->moveAfter(reverseBlocks[lc.header].back()); |
| |
| IRBuilder<> tbuild(incB); |
| |
| Value *av = tbuild.CreateLoad(lc.var->getType(), lc.antivaralloc); |
| Value *sub = tbuild.CreateAdd(av, ConstantInt::get(av->getType(), -1), "", |
| /*NUW*/ false, /*NSW*/ true); |
| tbuild.CreateStore(sub, lc.antivaralloc); |
| tbuild.CreateBr(resumeblock); |
| return newBlocksForLoop_cache[tup] = incB; |
| } |
| |
| if (exitEntering) { |
| SmallVector<LoopContext, 1> exitingContexts = {lc}; |
| |
| auto L2 = L; |
| while ((L2 = L2->getParentLoop())) { |
| LoopContext lc2; |
| bool inLoop = getContext(L2->getHeader(), lc2); |
| if (!inLoop) |
| break; |
| |
| auto latches2 = getLatches(L2, lc2.exitBlocks); |
| |
| // If we're reverseing a loop exit. |
| bool exitEntering2 = |
| std::find(latches2.begin(), latches2.end(), BB) != latches2.end() && |
| std::find(lc2.exitBlocks.begin(), lc2.exitBlocks.end(), |
| branchingBlock) != lc2.exitBlocks.end(); |
| if (exitEntering2) { |
| exitingContexts.push_back(lc2); |
| } else |
| break; |
| } |
| |
| BasicBlock *resumeblock = reverseBlocks[BB].front(); |
| BasicBlock *prevBlock = reverseBlocks[branchingBlock].back(); |
| |
| BasicBlock *outerMerge = nullptr; |
| |
| BasicBlock *incB = BasicBlock::Create( |
| BB->getContext(), |
| "merge" + reverseBlocks[lc.header].front()->getName() + "_" + |
| branchingBlock->getName(), |
| BB->getParent()); |
| if (!outerMerge) |
| outerMerge = incB; |
| incB->moveAfter(prevBlock); |
| |
| IRBuilder<> tbuild(prevBlock); |
| |
| SmallVector<std::pair<Value *, Value *>, 1> lims; |
| ValueToValueMapTy available; |
| for (auto I = exitingContexts.rbegin(), E = exitingContexts.rend(); I != E; |
| I++) { |
| auto &lc = *I; |
| auto L = LI.getLoopFor(lc.header); |
| Value *lim = nullptr; |
| if (lc.dynamic && assumeDynamicLoopOfSizeOne(L)) { |
| lim = ConstantInt::get(lc.var->getType(), 0); |
| } else if (lc.dynamic) { |
| // Must be in a reverse pass fashion for a lookup to index bound to be |
| // legal |
| assert(/*ReverseLimit*/ reverseBlocks.size() > 0); |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| lc.preheader); |
| lim = lookupValueFromCache(lc.var->getType(), |
| /*forwardPass*/ false, tbuild, lctx, |
| getDynamicLoopLimit(L), |
| /*isi1*/ false, available); |
| } else { |
| lim = lookupM(lc.trueLimit, tbuild, available); |
| } |
| available[lc.var] = lim; |
| lims.push_back(std::make_pair(lim, (Value *)lc.antivaralloc)); |
| } |
| |
| tbuild.SetInsertPoint(incB); |
| for (auto &pair : lims) { |
| tbuild.CreateStore(pair.first, pair.second); |
| } |
| |
| auto tmp_resumeblock = prepRematerializedLoopEntry(exitingContexts.back()); |
| if (tmp_resumeblock) |
| resumeblock = tmp_resumeblock; |
| |
| tbuild.CreateBr(resumeblock); |
| |
| return newBlocksForLoop_cache[tup] = incB; |
| } |
| |
| return newBlocksForLoop_cache[tup] = reverseBlocks[BB].front(); |
| } |
| |
| void GradientUtils::forceContexts() { |
| for (auto BB : originalBlocks) { |
| LoopContext lc; |
| getContext(BB, lc); |
| } |
| } |
| |
| bool GradientUtils::legalRecompute(const Value *val, |
| const ValueToValueMapTy &available, |
| IRBuilder<> *BuilderM, bool reverse, |
| bool legalRecomputeCache) const { |
| { |
| auto found = available.find(val); |
| if (found != available.end()) { |
| if (found->second) |
| return true; |
| else { |
| return false; |
| } |
| } |
| } |
| |
| if (isa<AtomicRMWInst>(val)) |
| return false; |
| |
| if (auto phi = dyn_cast<PHINode>(val)) { |
| if (auto uiv = hasUninverted(val)) { |
| if (auto dli = dyn_cast_or_null<LoadInst>(uiv)) { |
| return legalRecompute( |
| dli, available, BuilderM, |
| reverse); // TODO ADD && !TR.intType(getOriginal(dli), |
| // /*mustfind*/false).isPossibleFloat(); |
| } |
| if (auto ci = dyn_cast<CallInst>(uiv)) { |
| auto called = ci->getCalledFunction(); |
| if (ci->hasFnAttr("enzyme_shouldrecompute") || |
| (called && called->hasFnAttribute("enzyme_shouldrecompute"))) |
| return true; |
| } |
| if (phi->getNumIncomingValues() == 0) { |
| return false; |
| } |
| } |
| |
| auto found = fictiousPHIs.find(const_cast<llvm::PHINode *>(phi)); |
| if (found != fictiousPHIs.end()) { |
| auto orig = found->second; |
| if (isa<AtomicRMWInst>(orig)) |
| return false; |
| } |
| |
| if (phi->getNumIncomingValues() == 0) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "oldFunc: " << *oldFunc << "\n"; |
| ss << "newFunc: " << *newFunc << "\n"; |
| ss << "phi: " << *phi << "\n"; |
| ss << "Invalid legalRecompute query on ficticious phi\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap(phi), ErrorType::InternalError, |
| nullptr, nullptr, nullptr); |
| } else { |
| EmitFailure("InvalidLegalRecompute", phi->getDebugLoc(), phi, ss.str()); |
| } |
| } |
| auto parent = phi->getParent(); |
| struct { |
| Function *func; |
| const LoopInfo &FLI; |
| } options[2] = {{newFunc, LI}, {oldFunc, *OrigLI}}; |
| for (const auto &tup : options) { |
| if (parent->getParent() == tup.func) { |
| for (auto &val : phi->incoming_values()) { |
| if (isPotentialLastLoopValue(val, parent, tup.FLI)) { |
| return false; |
| } |
| } |
| if (tup.FLI.isLoopHeader(parent)) { |
| // Currently can only recompute header |
| // with two incoming values |
| if (phi->getNumIncomingValues() != 2) |
| return false; |
| auto L = tup.FLI.getLoopFor(parent); |
| |
| // Only recomputable if non recursive. |
| SmallPtrSet<Instruction *, 2> seen; |
| SmallVector<Instruction *, 1> todo; |
| for (auto PH : predecessors(parent)) { |
| // Prior iterations must be recomputable without |
| // this value. |
| if (L->contains(PH)) { |
| if (auto I = |
| dyn_cast<Instruction>(phi->getIncomingValueForBlock(PH))) |
| if (L->contains(I->getParent())) |
| todo.push_back(I); |
| } |
| } |
| |
| while (todo.size()) { |
| auto cur = todo.back(); |
| todo.pop_back(); |
| if (seen.count(cur)) |
| continue; |
| seen.insert(cur); |
| if (cur == phi) |
| return false; |
| for (auto &op : cur->operands()) { |
| if (auto I = dyn_cast<Instruction>(op)) { |
| if (L->contains(I->getParent())) |
| todo.push_back(I); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| if (isa<Instruction>(val) && |
| cast<Instruction>(val)->getMetadata("enzyme_mustcache")) { |
| return false; |
| } |
| |
| // If this is a load from cache already, dont force a cache of this |
| if (legalRecomputeCache && isa<LoadInst>(val) && |
| CacheLookups.count(cast<LoadInst>(val))) { |
| return true; |
| } |
| |
| // TODO consider callinst here |
| |
| if (auto li = dyn_cast<Instruction>(val)) { |
| |
| const IntrinsicInst *II; |
| if (isa<LoadInst>(li) || isNVLoad(li) || |
| ((II = dyn_cast<IntrinsicInst>(li)) && |
| (II->getIntrinsicID() == Intrinsic::masked_load))) { |
| // If this is an already unwrapped value, legal to recompute again. |
| if (unwrappedLoads.find(li) != unwrappedLoads.end()) |
| return legalRecompute(unwrappedLoads.find(li)->second, available, |
| BuilderM, reverse); |
| |
| const Instruction *orig = nullptr; |
| if (li->getParent()->getParent() == oldFunc) { |
| orig = li; |
| } else if (li->getParent()->getParent() == newFunc) { |
| orig = isOriginal(li); |
| // todo consider when we pass non original queries |
| if (orig && !isa<LoadInst>(orig)) { |
| return legalRecompute(orig, available, BuilderM, reverse, |
| legalRecomputeCache); |
| } |
| } else { |
| llvm::errs() << " newFunc: " << *newFunc << "\n"; |
| llvm::errs() << " parent: " << *li->getParent()->getParent() << "\n"; |
| llvm::errs() << " li: " << *li << "\n"; |
| assert(0 && "illegal load legalRecopmute query"); |
| } |
| |
| if (orig) { |
| assert(can_modref_map); |
| auto found = can_modref_map->find(const_cast<Instruction *>(orig)); |
| if (found == can_modref_map->end()) { |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << "can_modref_map:\n"; |
| for (auto &pair : *can_modref_map) { |
| llvm::errs() << " + " << *pair.first << ": " << pair.second |
| << " of func " |
| << pair.first->getParent()->getParent()->getName() |
| << "\n"; |
| } |
| llvm::errs() << "couldn't find in can_modref_map: " << *li << " - " |
| << *orig << " in fn: " |
| << orig->getParent()->getParent()->getName(); |
| } |
| assert(found != can_modref_map->end()); |
| if (!found->second) |
| return true; |
| // if insertion block of this function: |
| BasicBlock *fwdBlockIfReverse = nullptr; |
| if (BuilderM) { |
| fwdBlockIfReverse = BuilderM->GetInsertBlock(); |
| if (!reverse) { |
| auto found = reverseBlockToPrimal.find(BuilderM->GetInsertBlock()); |
| if (found != reverseBlockToPrimal.end()) { |
| fwdBlockIfReverse = found->second; |
| reverse = true; |
| } |
| } |
| if (fwdBlockIfReverse->getParent() != oldFunc) |
| fwdBlockIfReverse = |
| cast_or_null<BasicBlock>(isOriginal(fwdBlockIfReverse)); |
| } |
| if (mode == DerivativeMode::ReverseModeCombined && fwdBlockIfReverse) { |
| if (reverse) { |
| bool failed = false; |
| allFollowersOf( |
| const_cast<Instruction *>(orig), [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy( |
| &TR, *OrigAA, TLI, |
| /*maybeReader*/ const_cast<Instruction *>(orig), |
| /*maybeWriter*/ I)) { |
| failed = true; |
| EmitWarning( |
| "UncacheableLoad", *orig, "Load must be recomputed ", |
| *orig, " in reverse_", |
| BuilderM->GetInsertBlock()->getName(), " due to ", *I); |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (!failed) |
| return true; |
| } else { |
| Instruction *origStart = &*BuilderM->GetInsertPoint(); |
| do { |
| if (Instruction *og = isOriginal(origStart)) { |
| origStart = og; |
| break; |
| } |
| origStart = origStart->getNextNode(); |
| } while (true); |
| if (OrigDT->dominates(origStart, const_cast<Instruction *>(orig))) { |
| bool failed = false; |
| |
| allInstructionsBetween( |
| const_cast<GradientUtils *>(this)->LI, origStart, |
| const_cast<Instruction *>(orig), [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy( |
| &TR, *OrigAA, TLI, |
| /*maybeReader*/ const_cast<Instruction *>(orig), |
| /*maybeWriter*/ I)) { |
| failed = true; |
| EmitWarning("UncacheableLoad", *orig, |
| "Load must be recomputed ", *orig, " in ", |
| BuilderM->GetInsertBlock()->getName(), |
| " due to ", *I); |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (!failed) |
| return true; |
| } |
| } |
| } |
| return false; |
| } else { |
| if (auto dli = dyn_cast_or_null<LoadInst>(hasUninverted(li))) { |
| return legalRecompute(dli, available, BuilderM, reverse); |
| } |
| |
| // TODO mark all the explicitly legal nodes (caches, etc) |
| return true; |
| llvm::errs() << *li << " orig: " << orig |
| << " parent: " << li->getParent()->getParent()->getName() |
| << "\n"; |
| llvm_unreachable("unknown load to redo!"); |
| } |
| } |
| } |
| |
| if (auto ci = dyn_cast<CallInst>(val)) { |
| auto n = getFuncNameFromCall(const_cast<CallInst *>(ci)); |
| auto called = ci->getCalledFunction(); |
| Intrinsic::ID ID = Intrinsic::not_intrinsic; |
| |
| if (ci->hasFnAttr("enzyme_shouldrecompute") || |
| (called && called->hasFnAttribute("enzyme_shouldrecompute")) || |
| isMemFreeLibMFunction(n, &ID) || n == "lgamma_r" || n == "lgammaf_r" || |
| n == "lgammal_r" || n == "__lgamma_r_finite" || |
| n == "__lgammaf_r_finite" || n == "__lgammal_r_finite" || n == "tanh" || |
| n == "tanhf" || n == "__pow_finite" || |
| n == "julia.pointer_from_objref" || startsWith(n, "enzyme_wrapmpi$$") || |
| n == "omp_get_thread_num" || n == "omp_get_max_threads" || |
| n.contains("__enzyme_ignore_derivatives")) { |
| return true; |
| } |
| #if LLVM_VERSION_MAJOR >= 14 |
| if (ci->doesNotAccessMemory()) |
| #else |
| if (ci->hasFnAttr(Attribute::ReadNone) || |
| (called && called->hasFnAttribute(Attribute::ReadNone))) |
| #endif |
| return true; |
| if (isPointerArithmeticInst(ci)) |
| return true; |
| } |
| |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| if (inst->mayReadOrWriteMemory()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| //! Given the option to recompute a value or re-use an old one, return true if |
| //! it is faster to recompute this value from scratch |
| bool GradientUtils::shouldRecompute(const Value *val, |
| const ValueToValueMapTy &available, |
| IRBuilder<> *BuilderM) { |
| if (available.count(val)) |
| return true; |
| // TODO: remake such that this returns whether a load to a cache is more |
| // expensive than redoing the computation. |
| |
| // If this is a load from cache already, just reload this |
| if (isa<LoadInst>(val) && |
| cast<LoadInst>(val)->getMetadata("enzyme_fromcache")) |
| return true; |
| |
| if (!isa<Instruction>(val)) |
| return true; |
| |
| const Instruction *inst = cast<Instruction>(val); |
| |
| if (TapesToPreventRecomputation.count(inst)) |
| return false; |
| |
| if (knownRecomputeHeuristic.find(inst) != knownRecomputeHeuristic.end()) { |
| return knownRecomputeHeuristic[inst]; |
| } |
| if (auto OrigInst = isOriginal(inst)) { |
| if (knownRecomputeHeuristic.find(OrigInst) != |
| knownRecomputeHeuristic.end()) { |
| return knownRecomputeHeuristic[OrigInst]; |
| } |
| } |
| |
| if (isa<CastInst>(val) || isa<GetElementPtrInst>(val)) |
| return true; |
| |
| if (EnzymeNewCache && !EnzymeMinCutCache) { |
| // if this has operands that need to be loaded and haven't already been |
| // loaded |
| // TODO, just cache this |
| for (auto &op : inst->operands()) { |
| if (!legalRecompute(op, available, BuilderM)) { |
| |
| // If this is a load from cache already, dont force a cache of this |
| if (isa<LoadInst>(op) && CacheLookups.count(cast<LoadInst>(op))) |
| continue; |
| |
| // If a previously cached this operand, don't let it trigger the |
| // heuristic for caching this value instead. |
| if (scopeMap.find(op) != scopeMap.end()) |
| continue; |
| |
| // If the actually overwritten operand is in a different loop scope |
| // don't cache this value instead as it may require more memory |
| LoopContext lc1; |
| LoopContext lc2; |
| bool inLoop1 = |
| getContext(const_cast<Instruction *>(inst)->getParent(), lc1); |
| bool inLoop2 = getContext(cast<Instruction>(op)->getParent(), lc2); |
| if (inLoop1 != inLoop2 || (inLoop1 && (lc1.header != lc2.header))) { |
| continue; |
| } |
| |
| // If a placeholder phi for inversion (and we know from above not |
| // recomputable) |
| if (!isa<PHINode>(op) && |
| dyn_cast_or_null<LoadInst>(hasUninverted(op))) { |
| goto forceCache; |
| } |
| |
| // Even if cannot recompute (say a phi node), don't force a reload if it |
| // is possible to just use this instruction from forward pass without |
| // issue |
| if (auto i2 = dyn_cast<Instruction>(op)) { |
| if (!i2->mayReadOrWriteMemory()) { |
| LoopContext lc; |
| bool inLoop = const_cast<GradientUtils *>(this)->getContext( |
| i2->getParent(), lc); |
| if (!inLoop) { |
| // TODO upgrade this to be all returns that this could enter from |
| BasicBlock *orig = isOriginal(i2->getParent()); |
| assert(orig); |
| bool legal = BlocksDominatingAllReturns.count(orig); |
| if (legal) { |
| continue; |
| } |
| } |
| } |
| } |
| forceCache:; |
| EmitWarning("ChosenCache", *inst, "Choosing to cache use ", *inst, |
| " due to ", *op); |
| return false; |
| } |
| } |
| } |
| |
| if (auto op = dyn_cast<IntrinsicInst>(val)) { |
| if (!op->mayReadOrWriteMemory() || isReadNone(op) || isNVLoad(op)) |
| return true; |
| switch (op->getIntrinsicID()) { |
| case Intrinsic::sin: |
| case Intrinsic::cos: |
| case Intrinsic::exp: |
| #if LLVM_VERSION_MAJOR >= 19 |
| case Intrinsic::tanh: |
| case Intrinsic::cosh: |
| case Intrinsic::sinh: |
| #endif |
| case Intrinsic::log: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| if (auto ci = dyn_cast<CallInst>(val)) { |
| auto called = ci->getCalledFunction(); |
| auto n = getFuncNameFromCall(const_cast<CallInst *>(ci)); |
| Intrinsic::ID ID = Intrinsic::not_intrinsic; |
| if ((called && called->hasFnAttribute("enzyme_shouldrecompute")) || |
| isMemFreeLibMFunction(n, &ID) || n == "lgamma_r" || n == "lgammaf_r" || |
| n == "lgammal_r" || n == "__lgamma_r_finite" || |
| n == "__lgammaf_r_finite" || n == "__lgammal_r_finite" || n == "tanh" || |
| n == "tanhf" || n == "__pow_finite" || |
| n == "julia.pointer_from_objref" || startsWith(n, "enzyme_wrapmpi$$") || |
| n == "omp_get_thread_num" || n == "omp_get_max_threads" || |
| startsWith(n, "_ZN4libm4math3log") || |
| n.contains("__enzyme_ignore_derivatives")) { |
| return true; |
| } |
| if (isPointerArithmeticInst(ci)) |
| return true; |
| } |
| |
| // cache a call, assuming its longer to run that |
| if (isa<CallInst>(val)) { |
| llvm::errs() << " caching call: " << *val << "\n"; |
| // cast<CallInst>(val)->getCalledFunction()->dump(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| MDNode *GradientUtils::getDerivativeAliasScope(const Value *origptr, |
| ssize_t newptr) { |
| origptr = getBaseObject(origptr); |
| |
| auto found = differentialAliasScopeDomains.find(origptr); |
| if (found == differentialAliasScopeDomains.end()) { |
| MDBuilder MDB(oldFunc->getContext()); |
| MDNode *scope = MDB.createAnonymousAliasScopeDomain( |
| (" diff: %" + origptr->getName()).str()); |
| // vec.first = scope; |
| // found = differentialAliasScope.find(origptr); |
| found = differentialAliasScopeDomains.insert(std::make_pair(origptr, scope)) |
| .first; |
| } |
| auto &mp = differentialAliasScope[origptr]; |
| auto found2 = mp.find(newptr); |
| if (found2 == mp.end()) { |
| MDBuilder MDB(oldFunc->getContext()); |
| std::string name; |
| if (newptr == -1) |
| name = "primal"; |
| else |
| name = "shadow_" + std::to_string(newptr); |
| found2 = mp.insert(std::make_pair(newptr, MDB.createAnonymousAliasScope( |
| found->second, name))) |
| .first; |
| } |
| return found2->second; |
| } |
| |
| GradientUtils *GradientUtils::CreateFromClone( |
| EnzymeLogic &Logic, bool runtimeActivity, bool strongZero, unsigned width, |
| Function *todiff, TargetLibraryInfo &TLI, TypeAnalysis &TA, |
| FnTypeInfo &oldTypeInfo, DIFFE_TYPE retType, |
| ArrayRef<DIFFE_TYPE> constant_args, bool returnUsed, bool shadowReturnUsed, |
| std::map<AugmentedStruct, int> &returnMapping, bool omp) { |
| Function *oldFunc = todiff; |
| |
| // Since this is forward pass this should always return the tape (at index 0) |
| returnMapping[AugmentedStruct::Tape] = 0; |
| |
| int returnCount = 0; |
| |
| if (returnUsed) { |
| assert(!todiff->getReturnType()->isEmptyTy()); |
| assert(!todiff->getReturnType()->isVoidTy()); |
| returnMapping[AugmentedStruct::Return] = returnCount + 1; |
| ++returnCount; |
| } |
| |
| // We don't need to differentially return something that we know is not a |
| // pointer (or somehow needed for shadow analysis) |
| if (shadowReturnUsed) { |
| assert(!todiff->getReturnType()->isEmptyTy()); |
| assert(!todiff->getReturnType()->isVoidTy()); |
| returnMapping[AugmentedStruct::DifferentialReturn] = returnCount + 1; |
| ++returnCount; |
| } |
| |
| ValueToValueMapTy invertedPointers; |
| SmallPtrSet<Instruction *, 4> constants; |
| SmallPtrSet<Instruction *, 20> nonconstant; |
| SmallPtrSet<Value *, 2> returnvals; |
| llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> originalToNew; |
| |
| SmallPtrSet<Value *, 4> constant_values; |
| SmallPtrSet<Value *, 4> nonconstant_values; |
| |
| std::string prefix = "fakeaugmented"; |
| if (width > 1) |
| prefix += std::to_string(width); |
| prefix += "_"; |
| prefix += todiff->getName().str(); |
| |
| auto newFunc = Logic.PPC.CloneFunctionWithReturns( |
| DerivativeMode::ReverseModePrimal, width, oldFunc, invertedPointers, |
| constant_args, constant_values, nonconstant_values, returnvals, |
| /*returnTape*/ true, /*returnPrimal*/ returnUsed, |
| /*returnShadow*/ shadowReturnUsed, prefix, &originalToNew, |
| /*diffeReturnArg*/ false, /*additionalArg*/ nullptr); |
| |
| // Convert overwritten args from the input function to the preprocessed |
| // function |
| |
| FnTypeInfo typeInfo(oldFunc); |
| { |
| auto toarg = todiff->arg_begin(); |
| auto olarg = oldFunc->arg_begin(); |
| for (; toarg != todiff->arg_end(); ++toarg, ++olarg) { |
| |
| { |
| auto fd = oldTypeInfo.Arguments.find(toarg); |
| assert(fd != oldTypeInfo.Arguments.end()); |
| typeInfo.Arguments.insert( |
| std::pair<Argument *, TypeTree>(olarg, fd->second)); |
| } |
| |
| { |
| auto cfd = oldTypeInfo.KnownValues.find(toarg); |
| assert(cfd != oldTypeInfo.KnownValues.end()); |
| typeInfo.KnownValues.insert( |
| std::pair<Argument *, std::set<int64_t>>(olarg, cfd->second)); |
| } |
| } |
| typeInfo.Return = oldTypeInfo.Return; |
| } |
| |
| TypeResults TR = TA.analyzeFunction(typeInfo); |
| if (!oldFunc->empty()) |
| assert(TR.getFunction() == oldFunc); |
| |
| auto res = new GradientUtils( |
| Logic, newFunc, oldFunc, TLI, TA, TR, invertedPointers, constant_values, |
| nonconstant_values, retType, shadowReturnUsed, constant_args, |
| originalToNew, DerivativeMode::ReverseModePrimal, runtimeActivity, |
| strongZero, width, omp); |
| return res; |
| } |
| |
| DIFFE_TYPE GradientUtils::getReturnDiffeType(llvm::Value *orig, |
| bool *primalReturnUsedP, |
| bool *shadowReturnUsedP) const { |
| return getReturnDiffeType(orig, primalReturnUsedP, shadowReturnUsedP, mode); |
| } |
| |
| DIFFE_TYPE GradientUtils::getReturnDiffeType(llvm::Value *orig, |
| bool *primalReturnUsedP, |
| bool *shadowReturnUsedP, |
| DerivativeMode cmode) const { |
| bool shadowReturnUsed = false; |
| |
| DIFFE_TYPE subretType; |
| if (isConstantValue(orig)) { |
| subretType = DIFFE_TYPE::CONSTANT; |
| } else { |
| if (cmode == DerivativeMode::ForwardMode || |
| cmode == DerivativeMode::ForwardModeError || |
| cmode == DerivativeMode::ForwardModeSplit) { |
| if (DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Shadow>(this, orig, cmode, notForAnalysis)) { |
| subretType = DIFFE_TYPE::DUP_ARG; |
| shadowReturnUsed = true; |
| } else { |
| subretType = DIFFE_TYPE::CONSTANT; |
| } |
| } else { |
| if (!orig->getType()->isFPOrFPVectorTy() && TR.anyPointer(orig)) { |
| if (DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Shadow>(this, orig, cmode, notForAnalysis)) { |
| subretType = DIFFE_TYPE::DUP_ARG; |
| shadowReturnUsed = true; |
| } else |
| subretType = DIFFE_TYPE::CONSTANT; |
| } else { |
| subretType = DIFFE_TYPE::OUT_DIFF; |
| } |
| } |
| } |
| |
| if (primalReturnUsedP) { |
| bool subretused = !unnecessaryValuesP || unnecessaryValuesP->find(orig) == |
| unnecessaryValuesP->end(); |
| auto found = knownRecomputeHeuristic.find(orig); |
| if (found != knownRecomputeHeuristic.end()) { |
| if (!found->second) { |
| subretused = true; |
| } |
| } |
| *primalReturnUsedP = subretused; |
| } |
| |
| if (shadowReturnUsedP) |
| *shadowReturnUsedP = shadowReturnUsed; |
| return subretType; |
| } |
| |
| DIFFE_TYPE GradientUtils::getDiffeType(Value *v, bool foreignFunction) const { |
| if (isConstantValue(v) && !foreignFunction) { |
| return DIFFE_TYPE::CONSTANT; |
| } |
| |
| auto argType = v->getType(); |
| |
| if (!argType->isFPOrFPVectorTy() && (TR.anyPointer(v) || foreignFunction)) { |
| if (argType->isPointerTy()) { |
| auto at = getBaseObject(v); |
| if (auto arg = dyn_cast<Argument>(at)) { |
| if (ArgDiffeTypes[arg->getArgNo()] == DIFFE_TYPE::DUP_NONEED) { |
| return DIFFE_TYPE::DUP_NONEED; |
| } |
| } else if ((isa<AllocaInst>(at) || isAllocationCall(at, TLI)) && |
| unnecessaryValuesP) { |
| if (unnecessaryValuesP->count(at)) { |
| // Just because we chose to cahce the variable (and thus the value is |
| // unnecessary) for saving, does not mean we are no need. |
| auto found = knownRecomputeHeuristic.find(at); |
| if (found == knownRecomputeHeuristic.end() || found->second) |
| return DIFFE_TYPE::DUP_NONEED; |
| } |
| } |
| } |
| return DIFFE_TYPE::DUP_ARG; |
| } else { |
| if (foreignFunction) |
| assert(!argType->isIntOrIntVectorTy()); |
| if (mode == DerivativeMode::ForwardMode || |
| mode == DerivativeMode::ForwardModeError || |
| mode == DerivativeMode::ForwardModeSplit) |
| return DIFFE_TYPE::DUP_ARG; |
| else |
| return DIFFE_TYPE::OUT_DIFF; |
| } |
| } |
| |
| Constant *GradientUtils::GetOrCreateShadowConstant( |
| RequestContext context, EnzymeLogic &Logic, TargetLibraryInfo &TLI, |
| TypeAnalysis &TA, Constant *oval, DerivativeMode mode, bool runtimeActivity, |
| bool strongZero, unsigned width, bool AtomicAdd) { |
| if (isa<ConstantPointerNull>(oval)) { |
| return oval; |
| } else if (isa<UndefValue>(oval)) { |
| return oval; |
| } else if (isa<ConstantInt>(oval)) { |
| return oval; |
| } else if (auto CD = dyn_cast<ConstantDataArray>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| for (size_t i = 0, len = CD->getNumElements(); i < len; i++) { |
| Vals.push_back(GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, CD->getElementAsConstant(i), mode, |
| runtimeActivity, strongZero, width, AtomicAdd)); |
| } |
| return ConstantArray::get(CD->getType(), Vals); |
| } else if (auto CD = dyn_cast<ConstantArray>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| Vals.push_back(GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, CD->getOperand(i), mode, runtimeActivity, |
| strongZero, width, AtomicAdd)); |
| } |
| return ConstantArray::get(CD->getType(), Vals); |
| } else if (auto CD = dyn_cast<ConstantStruct>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| Vals.push_back(GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, CD->getOperand(i), mode, runtimeActivity, |
| strongZero, width, AtomicAdd)); |
| } |
| return ConstantStruct::get(CD->getType(), Vals); |
| } else if (auto CD = dyn_cast<ConstantVector>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| Vals.push_back(GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, CD->getOperand(i), mode, runtimeActivity, |
| strongZero, width, AtomicAdd)); |
| } |
| return ConstantVector::get(Vals); |
| } else if (auto F = dyn_cast<Function>(oval)) { |
| return GetOrCreateShadowFunction(context, Logic, TLI, TA, F, mode, |
| runtimeActivity, strongZero, width, |
| AtomicAdd); |
| } else if (auto arg = dyn_cast<ConstantExpr>(oval)) { |
| auto C = GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, arg->getOperand(0), mode, runtimeActivity, |
| strongZero, width, AtomicAdd); |
| if (arg->isCast() || arg->getOpcode() == Instruction::GetElementPtr || |
| arg->getOpcode() == Instruction::Add) { |
| SmallVector<Constant *, 8> NewOps; |
| for (unsigned i = 0, e = arg->getNumOperands(); i != e; ++i) |
| NewOps.push_back(i == 0 ? C : arg->getOperand(i)); |
| return arg->getWithOperands(NewOps); |
| } |
| } else if (auto arg = dyn_cast<GlobalAlias>(oval)) { |
| return GetOrCreateShadowConstant(context, Logic, TLI, TA, arg->getAliasee(), |
| mode, runtimeActivity, strongZero, width, |
| AtomicAdd); |
| } else if (auto arg = dyn_cast<GlobalVariable>(oval)) { |
| if (arg->getName() == "_ZTVN10__cxxabiv120__si_class_type_infoE" || |
| arg->getName() == "_ZTVN10__cxxabiv117__class_type_infoE" || |
| arg->getName() == "_ZTVN10__cxxabiv121__vmi_class_type_infoE" || |
| startsWith(arg->getName(), "??_R")) // any of the MS RTTI manglings |
| return arg; |
| |
| if (hasMetadata(arg, "enzyme_shadow")) { |
| auto md = arg->getMetadata("enzyme_shadow"); |
| if (!isa<MDTuple>(md)) { |
| llvm::errs() << *arg << "\n"; |
| llvm::errs() << *md << "\n"; |
| assert(0 && "cannot compute with global variable that doesn't have " |
| "marked shadow global"); |
| report_fatal_error( |
| "cannot compute with global variable that doesn't " |
| "have marked shadow global (metadata incorrect type)"); |
| } |
| auto md2 = cast<MDTuple>(md); |
| assert(md2->getNumOperands() == 1); |
| auto gvemd = cast<ConstantAsMetadata>(md2->getOperand(0)); |
| return gvemd->getValue(); |
| } |
| |
| auto TT = llvm::Triple(arg->getParent()->getTargetTriple()); |
| int SharedAddrSpace = getGPUSharedAddrSpace(TT); |
| int AddrSpace = cast<PointerType>(arg->getType())->getAddressSpace(); |
| if (isGPUArch(TT) && AddrSpace == SharedAddrSpace) { |
| assert(0 && "shared memory not handled in meta global"); |
| } |
| |
| // Create global variable locally if not externally visible |
| if (arg->isConstant() || arg->hasInternalLinkage() || |
| arg->hasPrivateLinkage() || |
| (arg->hasExternalLinkage() && arg->hasInitializer())) { |
| Type *type = arg->getValueType(); |
| auto shadow = new GlobalVariable( |
| *arg->getParent(), type, arg->isConstant(), arg->getLinkage(), |
| Constant::getNullValue(type), arg->getName() + "_shadow", arg, |
| arg->getThreadLocalMode(), arg->getType()->getAddressSpace(), |
| arg->isExternallyInitialized()); |
| arg->setMetadata("enzyme_shadow", |
| MDTuple::get(shadow->getContext(), |
| {ConstantAsMetadata::get(shadow)})); |
| shadow->setAlignment(arg->getAlign()); |
| shadow->setUnnamedAddr(arg->getUnnamedAddr()); |
| if (arg->hasInitializer()) |
| shadow->setInitializer(GetOrCreateShadowConstant( |
| context, Logic, TLI, TA, cast<Constant>(arg->getOperand(0)), mode, |
| runtimeActivity, strongZero, width, AtomicAdd)); |
| return shadow; |
| } |
| } |
| llvm::errs() << " unknown constant to create shadow of: " << *oval << "\n"; |
| llvm_unreachable("unknown constant to create shadow of"); |
| } |
| |
| Constant *GradientUtils::GetOrCreateShadowFunction( |
| RequestContext context, EnzymeLogic &Logic, TargetLibraryInfo &TLI, |
| TypeAnalysis &TA, Function *fn, DerivativeMode mode, bool runtimeActivity, |
| bool strongZero, unsigned width, bool AtomicAdd) { |
| //! Todo allow tape propagation |
| // Note that specifically this should _not_ be called with topLevel=true |
| // (since it may not be valid to always assume we can recompute the |
| // augmented primal) However, in the absence of a way to pass tape data |
| // from an indirect augmented (and also since we dont presently allow |
| // indirect augmented calls), topLevel MUST be true otherwise subcalls will |
| // not be able to lookup the augmenteddata/subdata (triggering an assertion |
| // failure, among much worse) |
| bool isRealloc = false; |
| if (fn->empty()) { |
| if (hasMetadata(fn, "enzyme_callwrapper")) { |
| auto md = fn->getMetadata("enzyme_callwrapper"); |
| if (!isa<MDTuple>(md)) { |
| llvm::errs() << *fn << "\n"; |
| llvm::errs() << *md << "\n"; |
| assert(0 && "callwrapper of incorrect type"); |
| report_fatal_error("callwrapper of incorrect type"); |
| } |
| auto md2 = cast<MDTuple>(md); |
| assert(md2->getNumOperands() == 1); |
| auto gvemd = cast<ConstantAsMetadata>(md2->getOperand(0)); |
| fn = cast<Function>(gvemd->getValue()); |
| } else { |
| auto oldfn = fn; |
| fn = Function::Create(oldfn->getFunctionType(), Function::InternalLinkage, |
| "callwrap_" + oldfn->getName(), oldfn->getParent()); |
| BasicBlock *entry = BasicBlock::Create(fn->getContext(), "entry", fn); |
| IRBuilder<> B(entry); |
| SmallVector<Value *, 4> args; |
| for (auto &a : fn->args()) |
| args.push_back(&a); |
| auto res = B.CreateCall(oldfn, args); |
| if (fn->getReturnType()->isVoidTy()) |
| B.CreateRetVoid(); |
| else |
| B.CreateRet(res); |
| oldfn->setMetadata( |
| "enzyme_callwrapper", |
| MDTuple::get(oldfn->getContext(), {ConstantAsMetadata::get(fn)})); |
| if (oldfn->getName() == "realloc") |
| isRealloc = true; |
| } |
| } |
| |
| bool subsequent_calls_may_write = mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError; |
| std::vector<bool> overwritten_args; |
| FnTypeInfo type_args(fn); |
| if (isRealloc) { |
| llvm::errs() << "warning: assuming realloc only creates pointers\n"; |
| type_args.Return.insert({-1, -1}, BaseType::Pointer); |
| } |
| |
| // conservatively assume that we can only cache existing floating types |
| // (i.e. that all args are overwritten) |
| std::vector<DIFFE_TYPE> types; |
| for (auto &a : fn->args()) { |
| overwritten_args.push_back(!a.getType()->isFPOrFPVectorTy()); |
| TypeTree TT; |
| if (a.getType()->isFPOrFPVectorTy()) |
| TT.insert({-1}, ConcreteType(a.getType()->getScalarType())); |
| type_args.Arguments.insert(std::pair<Argument *, TypeTree>(&a, TT)); |
| type_args.KnownValues.insert( |
| std::pair<Argument *, std::set<int64_t>>(&a, {})); |
| DIFFE_TYPE typ; |
| if (a.getType()->isFPOrFPVectorTy()) { |
| typ = (mode == DerivativeMode::ForwardMode || |
| mode == DerivativeMode::ForwardModeError) |
| ? DIFFE_TYPE::DUP_ARG |
| : DIFFE_TYPE::OUT_DIFF; |
| } else if (a.getType()->isIntegerTy() && |
| cast<IntegerType>(a.getType())->getBitWidth() < 16) { |
| typ = DIFFE_TYPE::CONSTANT; |
| } else if (a.getType()->isVoidTy() || a.getType()->isEmptyTy()) { |
| typ = DIFFE_TYPE::CONSTANT; |
| } else { |
| typ = DIFFE_TYPE::DUP_ARG; |
| } |
| types.push_back(typ); |
| } |
| |
| DIFFE_TYPE retType = fn->getReturnType()->isFPOrFPVectorTy() && |
| mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError |
| ? DIFFE_TYPE::OUT_DIFF |
| : DIFFE_TYPE::DUP_ARG; |
| |
| if (fn->getReturnType()->isVoidTy() || fn->getReturnType()->isEmptyTy() || |
| (fn->getReturnType()->isIntegerTy() && |
| cast<IntegerType>(fn->getReturnType())->getBitWidth() < 16)) |
| retType = DIFFE_TYPE::CONSTANT; |
| |
| if (mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError && |
| retType == DIFFE_TYPE::DUP_ARG) { |
| if (auto ST = dyn_cast<StructType>(fn->getReturnType())) { |
| size_t numflt = 0; |
| |
| for (unsigned i = 0; i < ST->getNumElements(); ++i) { |
| auto midTy = ST->getElementType(i); |
| if (midTy->isFPOrFPVectorTy()) |
| numflt++; |
| } |
| if (numflt == ST->getNumElements()) |
| retType = DIFFE_TYPE::OUT_DIFF; |
| } |
| } |
| |
| std::vector<bool> nowrite_shadows(fn->arg_size(), false); |
| |
| switch (mode) { |
| case DerivativeMode::ForwardModeError: |
| case DerivativeMode::ForwardMode: { |
| Constant *newf = Logic.CreateForwardDiff( |
| context, fn, retType, types, TA, false, mode, /*freeMemory*/ true, |
| runtimeActivity, strongZero, width, nullptr, type_args, |
| subsequent_calls_may_write, overwritten_args, |
| /*augmented*/ nullptr); |
| |
| assert(newf); |
| |
| std::string prefix = (mode == DerivativeMode::ForwardMode) |
| ? "_enzyme_forward" |
| : "_enzyme_forwarderror"; |
| |
| if (width > 1) { |
| prefix += std::to_string(width); |
| } |
| |
| std::string globalname = (prefix + "_" + fn->getName() + "'").str(); |
| auto GV = fn->getParent()->getNamedValue(globalname); |
| |
| if (GV == nullptr) { |
| GV = new GlobalVariable(*fn->getParent(), newf->getType(), true, |
| GlobalValue::LinkageTypes::InternalLinkage, newf, |
| globalname); |
| } |
| |
| return ConstantExpr::getPointerCast(GV, fn->getType()); |
| } |
| case DerivativeMode::ForwardModeSplit: { |
| auto &augdata = Logic.CreateAugmentedPrimal( |
| context, fn, retType, /*constant_args*/ types, TA, |
| /*returnUsed*/ !fn->getReturnType()->isEmptyTy() && |
| !fn->getReturnType()->isVoidTy(), |
| /*shadowReturnUsed*/ false, type_args, subsequent_calls_may_write, |
| overwritten_args, nowrite_shadows, |
| /*forceAnonymousTape*/ true, runtimeActivity, strongZero, width, |
| AtomicAdd); |
| Constant *newf = Logic.CreateForwardDiff( |
| context, fn, retType, types, TA, false, mode, /*freeMemory*/ true, |
| runtimeActivity, strongZero, width, nullptr, type_args, |
| subsequent_calls_may_write, overwritten_args, |
| /*augmented*/ &augdata); |
| |
| assert(newf); |
| |
| std::string prefix = "_enzyme_forwardsplit"; |
| |
| if (width > 1) { |
| prefix += std::to_string(width); |
| } |
| |
| auto cdata = ConstantStruct::get( |
| StructType::get(newf->getContext(), |
| {augdata.fn->getType(), newf->getType()}), |
| {augdata.fn, newf}); |
| |
| std::string globalname = (prefix + "_" + fn->getName() + "'").str(); |
| auto GV = fn->getParent()->getNamedValue(globalname); |
| |
| if (GV == nullptr) { |
| GV = new GlobalVariable(*fn->getParent(), cdata->getType(), true, |
| GlobalValue::LinkageTypes::InternalLinkage, cdata, |
| globalname); |
| } |
| |
| return ConstantExpr::getPointerCast(GV, fn->getType()); |
| } |
| case DerivativeMode::ReverseModeCombined: |
| case DerivativeMode::ReverseModeGradient: |
| case DerivativeMode::ReverseModePrimal: { |
| // TODO re atomic add consider forcing it to be atomic always as fallback if |
| // used in a parallel context |
| bool returnUsed = |
| !fn->getReturnType()->isEmptyTy() && !fn->getReturnType()->isVoidTy(); |
| bool shadowReturnUsed = returnUsed && (retType == DIFFE_TYPE::DUP_ARG || |
| retType == DIFFE_TYPE::DUP_NONEED); |
| auto &augdata = Logic.CreateAugmentedPrimal( |
| context, fn, retType, /*constant_args*/ types, TA, returnUsed, |
| shadowReturnUsed, type_args, subsequent_calls_may_write, |
| overwritten_args, nowrite_shadows, |
| /*forceAnonymousTape*/ true, runtimeActivity, strongZero, width, |
| AtomicAdd); |
| Constant *newf = Logic.CreatePrimalAndGradient( |
| context, |
| (ReverseCacheKey){.todiff = fn, |
| .retType = retType, |
| .constant_args = types, |
| .subsequent_calls_may_write = |
| subsequent_calls_may_write, |
| .overwritten_args = overwritten_args, |
| .returnUsed = false, |
| .shadowReturnUsed = false, |
| .mode = DerivativeMode::ReverseModeGradient, |
| .width = width, |
| .freeMemory = true, |
| .AtomicAdd = AtomicAdd, |
| .additionalType = getInt8PtrTy(fn->getContext()), |
| .forceAnonymousTape = true, |
| .typeInfo = type_args, |
| .runtimeActivity = runtimeActivity, |
| .strongZero = strongZero}, |
| TA, |
| /*map*/ &augdata); |
| assert(newf); |
| auto cdata = ConstantStruct::get( |
| StructType::get(newf->getContext(), |
| {augdata.fn->getType(), newf->getType()}), |
| {augdata.fn, newf}); |
| std::string globalname = ("_enzyme_reverse_" + fn->getName() + "'").str(); |
| auto GV = fn->getParent()->getNamedValue(globalname); |
| |
| if (GV == nullptr) { |
| GV = new GlobalVariable(*fn->getParent(), cdata->getType(), true, |
| GlobalValue::LinkageTypes::InternalLinkage, cdata, |
| globalname); |
| } |
| return ConstantExpr::getPointerCast(GV, fn->getType()); |
| } |
| } |
| llvm_unreachable("Illegal state: unknown mode for GetOrCreateShadowFunction"); |
| } |
| |
| void GradientUtils::getReverseBuilder(IRBuilder<> &Builder2, bool original) { |
| assert(reverseBlocks.size()); |
| BasicBlock *BB = Builder2.GetInsertBlock(); |
| if (original) |
| BB = getNewFromOriginal(BB); |
| assert(reverseBlocks.find(BB) != reverseBlocks.end()); |
| BasicBlock *BB2 = reverseBlocks[BB].back(); |
| if (!BB2) { |
| llvm::errs() << "oldFunc: " << oldFunc << "\n"; |
| llvm::errs() << "newFunc: " << newFunc << "\n"; |
| llvm::errs() << "could not invert " << *BB; |
| } |
| assert(BB2); |
| |
| if (hasTerminator(BB2)) |
| Builder2.SetInsertPoint(BB2->getTerminator()); |
| else |
| Builder2.SetInsertPoint(BB2); |
| Builder2.SetCurrentDebugLocation( |
| getNewFromOriginal(Builder2.getCurrentDebugLocation())); |
| Builder2.setFastMathFlags(getFast()); |
| } |
| |
| void GradientUtils::getForwardBuilder(IRBuilder<> &Builder2) { |
| Instruction *insert = &*Builder2.GetInsertPoint(); |
| Instruction *nInsert = getNewFromOriginal(insert); |
| |
| assert(nInsert); |
| |
| Builder2.SetInsertPoint(getNextNonDebugInstruction(nInsert)); |
| Builder2.SetCurrentDebugLocation( |
| getNewFromOriginal(Builder2.getCurrentDebugLocation())); |
| Builder2.setFastMathFlags(getFast()); |
| } |
| |
| void GradientUtils::setPtrDiffe(Instruction *orig, Value *ptr, Value *newval, |
| IRBuilder<> &BuilderM, MaybeAlign align, |
| unsigned start, unsigned size, bool isVolatile, |
| AtomicOrdering ordering, |
| SyncScope::ID syncScope, Value *mask, |
| ArrayRef<Metadata *> noAlias, |
| ArrayRef<Metadata *> scopes, |
| bool needs_post_cache) { |
| #ifndef NDEBUG |
| if (auto inst = dyn_cast<Instruction>(ptr)) { |
| assert(inst->getParent()->getParent() == oldFunc); |
| } |
| if (auto arg = dyn_cast<Argument>(ptr)) { |
| assert(arg->getParent() == oldFunc); |
| } |
| #endif |
| |
| Value *origptr = ptr; |
| |
| ptr = invertPointerM(ptr, BuilderM); |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock()) && |
| mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError) |
| ptr = lookupM(ptr, BuilderM); |
| |
| if (mask && !isOriginalBlock(*BuilderM.GetInsertBlock()) && |
| mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError) |
| mask = lookupM(mask, BuilderM); |
| |
| size_t idx = 0; |
| |
| auto &DL = oldFunc->getParent()->getDataLayout(); |
| |
| Value *invertedBarrier = nullptr; |
| if (needs_post_cache && EnzymeJuliaAddrLoad && |
| anyJuliaObjects(newval->getType())) { |
| auto obj = origptr; |
| while (true) { |
| if (auto CI = dyn_cast<CastInst>(obj)) { |
| obj = CI->getOperand(0); |
| continue; |
| } |
| if (auto GO = dyn_cast<GetElementPtrInst>(obj)) { |
| obj = GO->getOperand(0); |
| continue; |
| } |
| if (auto CI = dyn_cast<CallInst>(obj)) { |
| if (getFuncNameFromCall(CI) == "julia.gc_loaded") { |
| obj = CI->getArgOperand(0); |
| continue; |
| } |
| } |
| if (auto PT = dyn_cast<PointerType>(obj->getType())) { |
| if (PT->getAddressSpace() == 13) { |
| if (auto LI = dyn_cast<LoadInst>(obj)) { |
| obj = LI->getOperand(0); |
| continue; |
| } |
| } |
| } |
| break; |
| } |
| auto PT = cast<PointerType>(obj->getType()); |
| assert(PT->getAddressSpace() != 11 && PT->getAddressSpace() != 13); |
| if (PT->getAddressSpace() == 10) { |
| obj = invertPointerM(obj, BuilderM); |
| |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock()) && |
| mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError) |
| obj = lookupM(obj, BuilderM); |
| |
| invertedBarrier = obj; |
| } |
| } |
| |
| auto rule = [&](Value *ptr, Value *newval, Value *invertedBarrier) { |
| auto storeSize = (DL.getTypeSizeInBits(newval->getType()) + 7) / 8; |
| if (!mask) { |
| |
| if (size != storeSize) { |
| IRBuilder<> A(inversionAllocs); |
| Value *valptr = A.CreateAlloca(newval->getType()); |
| BuilderM.CreateStore(newval, valptr); |
| |
| auto i8 = Type::getInt8Ty(ptr->getContext()); |
| |
| if (start != 0) { |
| ptr = BuilderM.CreatePointerCast( |
| ptr, |
| getPointerType( |
| i8, cast<PointerType>(ptr->getType())->getAddressSpace())); |
| auto off = |
| ConstantInt::get(Type::getInt64Ty(ptr->getContext()), start); |
| ptr = BuilderM.CreateInBoundsGEP(i8, ptr, off); |
| |
| valptr = BuilderM.CreatePointerCast( |
| valptr, |
| getPointerType( |
| i8, cast<PointerType>(valptr->getType())->getAddressSpace())); |
| valptr = BuilderM.CreateInBoundsGEP(i8, valptr, off); |
| } |
| |
| Type *ty = nullptr; |
| |
| if (size == 8) |
| ty = BuilderM.getInt64Ty(); |
| else if (size % 8 == 0) |
| ty = ArrayType::get(BuilderM.getInt64Ty(), size / 8); |
| else if (size == 4) |
| ty = BuilderM.getInt32Ty(); |
| else if (size % 4 == 0) |
| ty = ArrayType::get(BuilderM.getInt32Ty(), size / 4); |
| else |
| ty = ArrayType::get(i8, size); |
| |
| ptr = BuilderM.CreatePointerCast( |
| ptr, getPointerType( |
| ty, cast<PointerType>(ptr->getType())->getAddressSpace())); |
| valptr = BuilderM.CreatePointerCast( |
| valptr, |
| getPointerType( |
| ty, cast<PointerType>(valptr->getType())->getAddressSpace())); |
| newval = BuilderM.CreateLoad(ty, valptr); |
| } |
| |
| auto ts = BuilderM.CreateStore(newval, ptr); |
| if (align) |
| ts->setAlignment(*align); |
| |
| if (invertedBarrier) { |
| auto T_jlvalue = StructType::get(ptr->getContext(), {}); |
| auto T_prjlvalue = getPointerType(T_jlvalue, 10); |
| |
| if (invertedBarrier->getType() != T_prjlvalue) { |
| invertedBarrier = |
| BuilderM.CreateBitCast(invertedBarrier, T_prjlvalue); |
| } |
| |
| auto FT = FunctionType::get(Type::getVoidTy(newval->getContext()), |
| {T_prjlvalue}, true); |
| auto wb = BuilderM.GetInsertBlock() |
| ->getParent() |
| ->getParent() |
| ->getOrInsertFunction("julia.write_barrier", FT); |
| |
| auto subvals = getJuliaObjects(newval, BuilderM); |
| |
| subvals.insert(subvals.begin(), invertedBarrier); |
| BuilderM.CreateCall(wb, subvals); |
| } |
| |
| ts->setVolatile(isVolatile); |
| ts->setOrdering(ordering); |
| ts->setSyncScopeID(syncScope); |
| SmallVector<Metadata *, 1> scopeMD = { |
| getDerivativeAliasScope(origptr, idx)}; |
| for (auto M : scopes) |
| scopeMD.push_back(M); |
| auto scope = MDNode::get(ts->getContext(), scopeMD); |
| ts->setMetadata(LLVMContext::MD_alias_scope, scope); |
| |
| if (start == 0 && size == storeSize) { |
| ts->setMetadata(LLVMContext::MD_tbaa, |
| orig->getMetadata(LLVMContext::MD_tbaa)); |
| ts->setMetadata(LLVMContext::MD_tbaa_struct, |
| orig->getMetadata(LLVMContext::MD_tbaa_struct)); |
| } |
| ts->setDebugLoc(getNewFromOriginal(orig->getDebugLoc())); |
| |
| SmallVector<Metadata *, 1> MDs; |
| for (ssize_t j = -1; j < getWidth(); j++) { |
| if (j != (ssize_t)idx) |
| MDs.push_back(getDerivativeAliasScope(origptr, j)); |
| } |
| for (auto M : noAlias) |
| MDs.push_back(M); |
| if (MDs.size()) { |
| auto noscope = MDNode::get(ptr->getContext(), MDs); |
| ts->setMetadata(LLVMContext::MD_noalias, noscope); |
| } |
| } else { |
| assert(start == 0 && size == storeSize); |
| Type *tys[] = {newval->getType(), ptr->getType()}; |
| auto F = getIntrinsicDeclaration(oldFunc->getParent(), |
| Intrinsic::masked_store, tys); |
| assert(align); |
| assert(!needs_post_cache); |
| Value *alignv = |
| ConstantInt::get(Type::getInt32Ty(ptr->getContext()), align->value()); |
| Value *args[] = {newval, ptr, alignv, mask}; |
| auto ts = BuilderM.CreateCall(F, args); |
| ts->setCallingConv(F->getCallingConv()); |
| ts->setMetadata(LLVMContext::MD_tbaa, |
| orig->getMetadata(LLVMContext::MD_tbaa)); |
| ts->setMetadata(LLVMContext::MD_tbaa_struct, |
| orig->getMetadata(LLVMContext::MD_tbaa_struct)); |
| ts->setDebugLoc(getNewFromOriginal(orig->getDebugLoc())); |
| } |
| idx++; |
| }; |
| |
| applyChainRule(BuilderM, rule, ptr, newval, invertedBarrier); |
| } |
| |
| Type *GradientUtils::getShadowType(Type *ty, unsigned width) { |
| if (width > 1) { |
| if (ty->isVoidTy()) |
| return ty; |
| return ArrayType::get(ty, width); |
| } else { |
| return ty; |
| } |
| } |
| |
| Type *GradientUtils::getShadowType(Type *ty) { |
| return getShadowType(ty, width); |
| } |
| |
| Type *GradientUtils::extractMeta(Type *T, ArrayRef<unsigned> off) { |
| for (auto idx : off) { |
| if (auto AT = dyn_cast<ArrayType>(T)) { |
| T = AT->getElementType(); |
| continue; |
| } |
| if (auto ST = dyn_cast<StructType>(T)) { |
| T = ST->getElementType(idx); |
| continue; |
| } |
| assert(false && "could not sub index into type"); |
| } |
| return T; |
| } |
| |
| Value *GradientUtils::extractMeta(IRBuilder<> &Builder, Value *Agg, |
| unsigned off, const Twine &name) { |
| return extractMeta(Builder, Agg, ArrayRef<unsigned>({off}), name); |
| } |
| |
| Value *GradientUtils::extractMeta(IRBuilder<> &Builder, Value *Agg, |
| ArrayRef<unsigned> off_init, |
| const Twine &name, bool fallback) { |
| std::vector<unsigned> off(off_init.begin(), off_init.end()); |
| while (off.size() != 0) { |
| if (auto Ins = dyn_cast<InsertValueInst>(Agg)) { |
| size_t until = Ins->getNumIndices(); |
| if (off.size() < until) |
| until = off.size(); |
| bool subset = true; |
| for (size_t i = 0; i < until; i++) { |
| if (Ins->getIndices()[i] != off[i]) { |
| subset = false; |
| break; |
| } |
| } |
| if (!subset) { |
| Agg = Ins->getAggregateOperand(); |
| continue; |
| } else if (until < Ins->getNumIndices()) { |
| break; |
| } else { |
| off.erase(off.begin(), off.begin() + until); |
| Agg = Ins->getInsertedValueOperand(); |
| continue; |
| } |
| } |
| if (auto ext = dyn_cast<ExtractValueInst>(Agg)) { |
| off.insert(off.begin(), ext->getIndices().begin(), |
| ext->getIndices().end()); |
| Agg = ext->getAggregateOperand(); |
| continue; |
| } |
| if (auto CA = dyn_cast<ConstantAggregateZero>(Agg)) { |
| Agg = CA->getElementValue(off[0]); |
| off.erase(off.begin(), off.begin() + 1); |
| } |
| break; |
| } |
| if (off.size() == 0) |
| return Agg; |
| |
| if (!fallback) |
| return nullptr; |
| |
| if (Agg->getType()->isVectorTy() && off.size() == 1) |
| return Builder.CreateExtractElement(Agg, off[0], name); |
| |
| return Builder.CreateExtractValue(Agg, off, name); |
| } |
| |
| llvm::Value *GradientUtils::recursiveFAdd(llvm::IRBuilder<> &B, |
| llvm::Value *lhs, llvm::Value *rhs, |
| llvm::ArrayRef<unsigned> lhs_off, |
| llvm::ArrayRef<unsigned> rhs_off, |
| llvm::Value *prev, bool vectorLayer) { |
| llvm::Type *lhs_ty = lhs->getType(); |
| if (!vectorLayer) { |
| for (auto idx : lhs_off) |
| lhs_ty = getSubType(lhs_ty, idx); |
| llvm::Type *rhs_ty = rhs->getType(); |
| for (auto idx : rhs_off) |
| rhs_ty = getSubType(rhs_ty, idx); |
| assert(lhs_ty == rhs_ty); |
| } |
| if (lhs_ty->isFPOrFPVectorTy()) { |
| if (lhs_off.size()) |
| lhs = extractMeta(B, lhs, lhs_off); |
| if (rhs_off.size()) |
| rhs = extractMeta(B, rhs, rhs_off); |
| llvm::Value *res = nullptr; |
| if (auto fp = llvm::dyn_cast<llvm::ConstantFP>(lhs)) { |
| if (fp->isZero()) |
| res = rhs; |
| } |
| if (auto fp = llvm::dyn_cast<llvm::ConstantFP>(rhs)) { |
| if (fp->isZero()) |
| res = lhs; |
| } |
| if (!res) { |
| if (auto *FPMO = dyn_cast<FPMathOperator>(rhs)) |
| if (FPMO->getOpcode() == Instruction::FNeg) { |
| res = B.CreateFSub(lhs, FPMO->getOperand(0)); |
| } |
| } |
| if (!res) { |
| if (auto *S = dyn_cast<BinaryOperator>(rhs)) { |
| if (S->getOpcode() == Instruction::FSub) { |
| if (auto C = dyn_cast<ConstantFP>(S->getOperand(0))) |
| if (C->isZero()) |
| res = B.CreateFSub(lhs, S->getOperand(1)); |
| } |
| } |
| } |
| if (!res) { |
| res = B.CreateFAdd(lhs, rhs); |
| } |
| if (lhs_off.size()) { |
| assert(prev); |
| res = B.CreateInsertValue(prev, res, lhs_off); |
| } |
| return res; |
| } else if (isa<ArrayType>(lhs_ty) || isa<StructType>(lhs_ty)) { |
| if (prev == nullptr) |
| prev = llvm::UndefValue::get(lhs_ty); |
| |
| size_t size; |
| if (auto AT = dyn_cast<ArrayType>(lhs_ty)) |
| size = AT->getNumElements(); |
| else |
| size = cast<StructType>(lhs_ty)->getNumElements(); |
| |
| for (size_t i = 0; i < size; ++i) { |
| llvm::SmallVector<unsigned, 1> nlhs_off(lhs_off.begin(), lhs_off.end()); |
| if (vectorLayer) |
| nlhs_off.insert(nlhs_off.begin(), i); |
| else |
| nlhs_off.push_back(i); |
| llvm::SmallVector<unsigned, 1> nrhs_off(rhs_off.begin(), rhs_off.end()); |
| if (vectorLayer) |
| nrhs_off.insert(nrhs_off.begin(), i); |
| else |
| nrhs_off.push_back(i); |
| prev = recursiveFAdd(B, lhs, rhs, nlhs_off, nrhs_off, prev); |
| } |
| return prev; |
| } |
| llvm_unreachable("Unknown type to recursively accumulate"); |
| } |
| |
| static bool allNullOrUndef(Value *C, const DataLayout &dl, TypeTree TT) { |
| if (!TT.anyPointer(C, dl)) { |
| return true; |
| } |
| if (isa<UndefValue>(C) || isa<ConstantPointerNull>(C) || |
| isa<ConstantAggregateZero>(C)) { |
| return true; |
| } |
| if (auto CF = dyn_cast<ConstantFP>(C)) { |
| if (CF->isZero()) |
| return true; |
| } |
| if (auto CInt = dyn_cast<ConstantInt>(C)) { |
| if (CInt->isZero()) |
| return true; |
| } |
| if (auto CS = dyn_cast<ConstantStruct>(C)) { |
| const StructLayout *Layout = dl.getStructLayout(CS->getType()); |
| for (unsigned i = 0; i < CS->getNumOperands(); ++i) { |
| auto el = CS->getOperand(i); |
| auto Off = Layout->getElementOffset(i); |
| auto ObjSize = (dl.getTypeSizeInBits(el->getType()) + 7) / 8; |
| TypeTree subTT = TT.ShiftIndices(dl, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, dl); |
| if (!allNullOrUndef(el, dl, subTT)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (auto CA = dyn_cast<ConstantArray>(C)) { |
| auto ElTy = CA->getType()->getElementType(); |
| auto ObjSize = (dl.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (unsigned i = 0; i < CA->getNumOperands(); ++i) { |
| auto el = CA->getOperand(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(dl, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, dl); |
| if (!allNullOrUndef(el, dl, subTT)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (auto CV = dyn_cast<ConstantVector>(C)) { |
| auto ElTy = CV->getType()->getElementType(); |
| auto ObjSize = (dl.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (unsigned i = 0; i < CV->getNumOperands(); ++i) { |
| auto el = CV->getOperand(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(dl, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, dl); |
| if (!allNullOrUndef(el, dl, subTT)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (auto CDS = dyn_cast<ConstantDataSequential>(C)) { |
| auto ElTy = CDS->getElementType(); |
| auto ObjSize = (dl.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (unsigned i = 0; i < CDS->getNumElements(); ++i) { |
| auto el = CDS->getElementAsConstant(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(dl, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, dl); |
| if (!allNullOrUndef(el, dl, subTT)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| Value *GradientUtils::invertPointerM(Value *const oval, IRBuilder<> &BuilderM) { |
| return invertPointerM(oval, BuilderM, TR.query(oval)); |
| } |
| |
| bool GradientUtils::isAtomic(Value *origptr) const { |
| return ::isAtomic(origptr, AtomicAdd, newFunc); |
| } |
| |
| Value *GradientUtils::invertPointerM(Value *const oval, IRBuilder<> &BuilderM, |
| TypeTree TT) { |
| assert(oval); |
| #ifndef NDEBUG |
| if (auto inst = dyn_cast<Instruction>(oval)) { |
| assert(inst->getParent()->getParent() == oldFunc); |
| } |
| if (auto arg = dyn_cast<Argument>(oval)) { |
| assert(arg->getParent() == oldFunc); |
| } |
| #endif |
| |
| auto &DL = oldFunc->getParent()->getDataLayout(); |
| if (isa<ConstantPointerNull>(oval) || isa<UndefValue>(oval) || |
| isa<ConstantInt>(oval) || isa<ConstantAggregateZero>(oval) || |
| isa<PoisonValue>(oval)) { |
| if (isa<ConstantPointerNull>(oval) || isa<UndefValue>(oval) || |
| isa<PoisonValue>(oval) || isa<ConstantAggregateZero>(oval) || |
| (isa<ConstantInt>(oval) && cast<ConstantInt>(oval)->isZero()) || |
| TT.allFloat(oval, DL)) |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| else if (!TT.anyFloat(oval, DL)) |
| return applyChainRule(oval->getType(), BuilderM, [&]() { return oval; }); |
| } |
| if (auto CD = dyn_cast<ConstantDataArray>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| auto ElTy = CD->getType()->getElementType(); |
| auto ObjSize = (DL.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (size_t i = 0, len = CD->getNumElements(); i < len; i++) { |
| auto el = CD->getElementAsConstant(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, DL); |
| Value *val = invertPointerM(el, BuilderM, subTT); |
| Vals.push_back(cast<Constant>(val)); |
| } |
| auto rule = [&CD](ArrayRef<Constant *> Vals) { |
| return ConstantArray::get(CD->getType(), Vals); |
| }; |
| return applyChainRule(CD->getType(), Vals, BuilderM, rule); |
| } else if (auto CD = dyn_cast<ConstantArray>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| auto ElTy = CD->getType()->getElementType(); |
| auto ObjSize = (DL.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| auto el = CD->getOperand(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, DL); |
| Value *val = invertPointerM(el, BuilderM, subTT); |
| Vals.push_back(cast<Constant>(val)); |
| } |
| |
| auto rule = [&CD](ArrayRef<Constant *> Vals) { |
| return ConstantArray::get(CD->getType(), Vals); |
| }; |
| |
| return applyChainRule(CD->getType(), Vals, BuilderM, rule); |
| } else if (auto CD = dyn_cast<ConstantStruct>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| auto StructTy = CD->getType(); |
| const StructLayout *Layout = DL.getStructLayout(StructTy); |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| auto el = CD->getOperand(i); |
| auto Off = Layout->getElementOffset(i); |
| auto ObjSize = (DL.getTypeSizeInBits(el->getType()) + 7) / 8; |
| TypeTree subTT = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, DL); |
| Vals.push_back(cast<Constant>(invertPointerM(el, BuilderM, subTT))); |
| } |
| |
| auto rule = [&CD](ArrayRef<Constant *> Vals) { |
| return ConstantStruct::get(CD->getType(), Vals); |
| }; |
| return applyChainRule(CD->getType(), Vals, BuilderM, rule); |
| } else if (auto CD = dyn_cast<ConstantVector>(oval)) { |
| SmallVector<Constant *, 1> Vals; |
| auto ElTy = CD->getType()->getElementType(); |
| auto ObjSize = (DL.getTypeSizeInBits(ElTy) + 7) / 8; |
| for (size_t i = 0, len = CD->getNumOperands(); i < len; i++) { |
| auto el = CD->getOperand(i); |
| auto Off = i * ObjSize; |
| TypeTree subTT = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| subTT.CanonicalizeInPlace(ObjSize, DL); |
| Vals.push_back(cast<Constant>(invertPointerM(el, BuilderM, subTT))); |
| } |
| |
| auto rule = [](ArrayRef<Constant *> Vals) { |
| return ConstantVector::get(Vals); |
| }; |
| |
| return applyChainRule(CD->getType(), Vals, BuilderM, rule); |
| } else if (isa<ConstantData>(oval) && |
| TT.allFloat(oval, DL, /*anythingIsFloat*/ true)) { |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| } |
| |
| bool shouldNullShadow = isConstantValue(oval); |
| if (shouldNullShadow) { |
| if (isa<InsertValueInst>(oval) || isa<ExtractValueInst>(oval) || |
| isa<InsertElementInst>(oval) || isa<ExtractElementInst>(oval)) { |
| shouldNullShadow = false; |
| auto orig = cast<Instruction>(oval); |
| if (knownRecomputeHeuristic.count(orig)) { |
| if (!knownRecomputeHeuristic[orig]) { |
| shouldNullShadow = true; |
| } |
| } |
| } |
| } |
| |
| // NOTE, this is legal and the correct resolution, however, our activity |
| // analysis honeypot no longer exists |
| |
| // Nulling the shadow for a constant is only necessary if any of the data |
| // could contain a float (e.g. should not be applied to pointers). |
| if (shouldNullShadow) { |
| size_t size = (DL.getTypeSizeInBits(oval->getType()) + 7) / 8; |
| if (TT.anyFloat(oval, DL)) { |
| if (TT.allFloat(oval, DL, /*anythingIsFloat*/ true)) |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| else { |
| IRBuilder<> bb(inversionAllocs); |
| if (auto arg = dyn_cast<Instruction>(oval)) { |
| arg = getNewFromOriginal(arg); |
| // Go one after since otherwise we won't be able |
| // to use in the store. |
| arg = arg->getNextNode(); |
| while (auto PN = dyn_cast<PHINode>(arg)) { |
| if (PN->getNumIncomingValues() == 0) |
| break; |
| arg = PN->getNextNode(); |
| } |
| bb.SetInsertPoint(arg); |
| } |
| auto alloc = bb.CreateAlloca(oval->getType()); |
| auto AT = ArrayType::get(bb.getInt8Ty(), size); |
| bb.CreateStore(getNewFromOriginal(oval), alloc); |
| Value *cur = bb.CreatePointerCast(alloc, getUnqual(AT)); |
| size_t i = 0; |
| assert(size > 0); |
| for (; i < size;) { |
| auto CT2 = TT[{(int)i}]; |
| if (CT2 == BaseType::Pointer) { |
| i += DL.getPointerSize(0); |
| continue; |
| } else if (auto flt = CT2.isFloat()) { |
| auto ptr = bb.CreateConstInBoundsGEP2_32(AT, cur, 0, i); |
| ptr = bb.CreatePointerCast(ptr, getUnqual(flt)); |
| bb.CreateStore(Constant::getNullValue(flt), ptr); |
| size_t chunk = DL.getTypeSizeInBits(flt) / 8; |
| i += chunk; |
| } else if (CT2 != BaseType::Integer) { |
| auto ptr = bb.CreateConstInBoundsGEP2_32(AT, cur, 0, i); |
| bb.CreateStore(Constant::getNullValue(bb.getInt8Ty()), ptr); |
| i++; |
| } else { |
| i++; |
| } |
| } |
| auto res = bb.CreateLoad(oval->getType(), alloc); |
| auto rule = [&res]() { return res; }; |
| auto res2 = applyChainRule(oval->getType(), BuilderM, rule); |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, res2))); |
| return res2; |
| } |
| } |
| |
| if (isa<ConstantExpr>(oval) || isa<GlobalValue>(oval)) { |
| auto rule = [&oval]() { return oval; }; |
| return applyChainRule(oval->getType(), BuilderM, rule); |
| } |
| |
| Value *newval = getNewFromOriginal(oval); |
| |
| auto rule = [&]() { return newval; }; |
| |
| return applyChainRule(oval->getType(), BuilderM, rule); |
| } |
| |
| auto M = oldFunc->getParent(); |
| assert(oval); |
| |
| { |
| auto ifound = invertedPointers.find(oval); |
| if (ifound != invertedPointers.end()) { |
| return &*ifound->second; |
| } |
| } |
| |
| if (mode != DerivativeMode::ForwardMode && |
| mode != DerivativeMode::ForwardModeError && |
| mode != DerivativeMode::ForwardModeSplit) { |
| if (TT.allFloat(oval, DL)) { |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| } |
| } |
| |
| if (isa<Argument>(oval) && !TT.anyPointer(oval, DL)) { |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| } else if (isa<Argument>(oval) && cast<Argument>(oval)->hasByValAttr()) { |
| IRBuilder<> bb(inversionAllocs); |
| |
| Type *subType = nullptr; |
| auto attr = cast<Argument>(oval)->getAttribute(Attribute::ByVal); |
| subType = attr.getValueAsType(); |
| |
| auto rule1 = [&]() { |
| AllocaInst *antialloca = bb.CreateAlloca( |
| subType, cast<PointerType>(oval->getType())->getPointerAddressSpace(), |
| nullptr, oval->getName() + "'ipa"); |
| |
| auto dst_arg = |
| bb.CreateBitCast(antialloca, getInt8PtrTy(oval->getContext())); |
| auto val_arg = ConstantInt::get(Type::getInt8Ty(oval->getContext()), 0); |
| auto len_arg = ConstantInt::get( |
| Type::getInt64Ty(oval->getContext()), |
| M->getDataLayout().getTypeAllocSizeInBits(subType) / 8); |
| auto volatile_arg = ConstantInt::getFalse(oval->getContext()); |
| |
| Value *args[] = {dst_arg, val_arg, len_arg, volatile_arg}; |
| Type *tys[] = {dst_arg->getType(), len_arg->getType()}; |
| bb.CreateCall(getIntrinsicDeclaration(M, Intrinsic::memset, tys), args); |
| |
| return antialloca; |
| }; |
| |
| Value *antialloca = applyChainRule(oval->getType(), bb, rule1); |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, antialloca))); |
| |
| return antialloca; |
| } else if (auto arg = dyn_cast<GlobalAlias>(oval)) { |
| Value *aliasTarget = arg->getAliasee(); |
| return invertPointerM(aliasTarget, BuilderM, TT); |
| } else if (auto arg = dyn_cast<GlobalVariable>(oval)) { |
| if (!hasMetadata(arg, "enzyme_shadow")) { |
| |
| if ((mode == DerivativeMode::ReverseModeCombined || |
| mode == DerivativeMode::ForwardMode || |
| mode == DerivativeMode::ForwardModeError) && |
| arg->getType()->getPointerAddressSpace() == 0) { |
| auto CT = TR.query(arg)[{-1, -1}]; |
| // Can only localy replace a global variable if it is |
| // known not to contain a pointer, which may be initialized |
| // outside of this function to contain other memory which |
| // will not have a shadow within the current function. |
| if (CT.isKnown() && CT != BaseType::Pointer) { |
| bool seen = false; |
| MemoryLocation |
| #if LLVM_VERSION_MAJOR >= 12 |
| Loc = MemoryLocation(oval, LocationSize::beforeOrAfterPointer()); |
| #else |
| Loc = MemoryLocation(oval, LocationSize::unknown()); |
| #endif |
| for (CallInst *CI : originalCalls) { |
| if (isa<IntrinsicInst>(CI)) |
| continue; |
| if (!isConstantInstruction(CI)) { |
| auto F = getFunctionFromCall(CI); |
| if (F && isMemFreeLibMFunction(F->getName())) { |
| continue; |
| } |
| if (llvm::isModOrRefSet(OrigAA->getModRefInfo(CI, Loc))) { |
| seen = true; |
| llvm::errs() << " cannot shadow-inline global " << *oval |
| << " due to " << *CI << "\n"; |
| goto endCheck; |
| } |
| } |
| } |
| endCheck:; |
| if (!seen) { |
| IRBuilder<> bb(inversionAllocs); |
| Type *allocaTy = arg->getValueType(); |
| |
| auto rule1 = [&]() { |
| AllocaInst *antialloca = bb.CreateAlloca( |
| allocaTy, arg->getType()->getPointerAddressSpace(), nullptr, |
| arg->getName() + "'ipa"); |
| if (arg->getAlignment()) { |
| antialloca->setAlignment(Align(arg->getAlignment())); |
| } |
| return antialloca; |
| }; |
| |
| Value *antialloca = applyChainRule(arg->getType(), bb, rule1); |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, antialloca))); |
| |
| auto rule2 = [&](Value *antialloca) { |
| auto dst_arg = |
| bb.CreateBitCast(antialloca, getInt8PtrTy(arg->getContext())); |
| auto val_arg = |
| ConstantInt::get(Type::getInt8Ty(arg->getContext()), 0); |
| auto len_arg = |
| ConstantInt::get(Type::getInt64Ty(arg->getContext()), |
| M->getDataLayout().getTypeAllocSizeInBits( |
| arg->getValueType()) / |
| 8); |
| auto volatile_arg = ConstantInt::getFalse(oval->getContext()); |
| |
| Value *args[] = {dst_arg, val_arg, len_arg, volatile_arg}; |
| Type *tys[] = {dst_arg->getType(), len_arg->getType()}; |
| auto memset = cast<CallInst>(bb.CreateCall( |
| getIntrinsicDeclaration(M, Intrinsic::memset, tys), args)); |
| if (arg->getAlignment()) { |
| memset->addParamAttr( |
| 0, Attribute::getWithAlignment(arg->getContext(), |
| Align(arg->getAlignment()))); |
| } |
| memset->addParamAttr(0, Attribute::NonNull); |
| assert((width > 1 && antialloca->getType() == |
| ArrayType::get(arg->getType(), width)) || |
| antialloca->getType() == arg->getType()); |
| return antialloca; |
| }; |
| |
| return applyChainRule(arg->getType(), bb, rule2, antialloca); |
| } |
| } |
| } |
| |
| auto TT = llvm::Triple(newFunc->getParent()->getTargetTriple()); |
| int SharedAddrSpace = getGPUSharedAddrSpace(TT); |
| int AddrSpace = cast<PointerType>(arg->getType())->getAddressSpace(); |
| if (isGPUArch(TT) && AddrSpace == SharedAddrSpace) { |
| llvm::errs() << "warning found shared memory\n"; |
| Type *type = arg->getValueType(); |
| // TODO this needs initialization by entry |
| auto shadow = new GlobalVariable( |
| *arg->getParent(), type, arg->isConstant(), arg->getLinkage(), |
| UndefValue::get(type), arg->getName() + "_shadow", arg, |
| arg->getThreadLocalMode(), arg->getType()->getAddressSpace(), |
| arg->isExternallyInitialized()); |
| arg->setMetadata("enzyme_shadow", |
| MDTuple::get(shadow->getContext(), |
| {ConstantAsMetadata::get(shadow)})); |
| shadow->setMetadata("enzyme_internalshadowglobal", |
| MDTuple::get(shadow->getContext(), {})); |
| shadow->setAlignment(arg->getAlign()); |
| shadow->setUnnamedAddr(arg->getUnnamedAddr()); |
| return shadow; |
| } |
| |
| // Create global variable locally if not externally visible |
| // If a variable is constant, for forward mode it will also |
| // only be read, so invert initializing is fine. |
| // For reverse mode, any floats will be +='d into, but never |
| // read, and any pointers will be used as expected. The never |
| // read means even if two globals for floats, that's fine. |
| // As long as the pointers point to equivalent places (which |
| // they should from the same initialization), it is also ok. |
| if (arg->hasInternalLinkage() || arg->hasPrivateLinkage() || |
| (arg->hasExternalLinkage() && arg->hasInitializer()) || |
| arg->isConstant()) { |
| Type *elemTy = arg->getValueType(); |
| IRBuilder<> B(inversionAllocs); |
| |
| auto rule = [&]() { |
| auto shadow = new GlobalVariable( |
| *arg->getParent(), elemTy, arg->isConstant(), arg->getLinkage(), |
| Constant::getNullValue(elemTy), arg->getName() + "_shadow", arg, |
| arg->getThreadLocalMode(), arg->getType()->getAddressSpace(), |
| arg->isExternallyInitialized()); |
| shadow->setAlignment(arg->getAlign()); |
| shadow->setUnnamedAddr(arg->getUnnamedAddr()); |
| |
| return shadow; |
| }; |
| |
| Value *shadow = applyChainRule(oval->getType(), BuilderM, rule); |
| arg->setMetadata( |
| "enzyme_shadow", |
| MDTuple::get(shadow->getContext(), |
| {ConstantAsMetadata::get(cast<Constant>(shadow))})); |
| if (getWidth() != 1) { |
| BuilderM.Insert(InsertValueInst::Create(shadow, arg, {0}), "tmp"); |
| } |
| |
| if (arg->hasInitializer()) { |
| size_t tsize = |
| (DL.getTypeSizeInBits(arg->getInitializer()->getType()) + 7) / 8; |
| applyChainRule( |
| BuilderM, |
| [&](Value *shadow, Value *ip) { |
| cast<GlobalVariable>(shadow)->setInitializer( |
| cast<Constant>(ip)); |
| }, |
| shadow, |
| invertPointerM(arg->getInitializer(), B, |
| TR.query(oval).Lookup(tsize, DL))); |
| } |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } |
| |
| std::string s; |
| llvm::raw_string_ostream ss(s); |
| ss << "cannot compute with global variable that doesn't have marked " |
| "shadow global\n"; |
| ss << *arg << "\n"; |
| if (CustomErrorHandler) { |
| return unwrap(CustomErrorHandler(ss.str().c_str(), wrap(arg), |
| ErrorType::NoShadow, this, nullptr, |
| wrap(&BuilderM))); |
| } else { |
| EmitFailure("InvertGlobal", BuilderM.getCurrentDebugLocation(), oldFunc, |
| ss.str()); |
| } |
| return UndefValue::get(getShadowType(arg->getType())); |
| } |
| auto md = arg->getMetadata("enzyme_shadow"); |
| if (!isa<MDTuple>(md)) { |
| llvm::errs() << *arg << "\n"; |
| llvm::errs() << *md << "\n"; |
| std::string s; |
| llvm::raw_string_ostream ss(s); |
| ss << "cannot compute with global variable that doesn't have marked " |
| "shadow global as mdtuple\n"; |
| ss << *arg << "\n"; |
| ss << " md: " << *md << "\n"; |
| if (CustomErrorHandler) { |
| return unwrap(CustomErrorHandler(ss.str().c_str(), wrap(arg), |
| ErrorType::NoShadow, this, nullptr, |
| wrap(&BuilderM))); |
| } else { |
| EmitFailure("InvertGlobal", BuilderM.getCurrentDebugLocation(), oldFunc, |
| ss.str()); |
| } |
| return UndefValue::get(getShadowType(arg->getType())); |
| } |
| auto md2 = cast<MDTuple>(md); |
| assert(md2->getNumOperands() == 1); |
| auto gvemd = cast<ConstantAsMetadata>(md2->getOperand(0)); |
| auto cs = cast<Constant>(gvemd->getValue()); |
| |
| if (width > 1) { |
| SmallVector<Constant *, 2> Vals; |
| for (unsigned i = 0; i < width; ++i) { |
| |
| Constant *idxs[] = { |
| ConstantInt::get(Type::getInt32Ty(cs->getContext()), 0), |
| ConstantInt::get(Type::getInt32Ty(cs->getContext()), i)}; |
| Constant *elem = ConstantExpr::getInBoundsGetElementPtr( |
| getShadowType(arg->getValueType()), cs, idxs); |
| Vals.push_back(elem); |
| } |
| |
| auto agg = ConstantArray::get( |
| cast<ArrayType>(getShadowType(arg->getType())), Vals); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, agg))); |
| return agg; |
| } else { |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, cs))); |
| return cs; |
| } |
| } else if (auto fn = dyn_cast<Function>(oval)) { |
| Constant *shadow = GetOrCreateShadowFunction( |
| RequestContext(nullptr, &BuilderM), Logic, TLI, TA, fn, mode, |
| runtimeActivity, strongZero, width, AtomicAdd); |
| if (width > 1) { |
| SmallVector<Constant *, 3> arr; |
| for (unsigned i = 0; i < width; ++i) { |
| arr.push_back(shadow); |
| } |
| ArrayType *arrTy = ArrayType::get(shadow->getType(), width); |
| shadow = ConstantArray::get(arrTy, arr); |
| } |
| return shadow; |
| } else if (auto arg = dyn_cast<CastInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *invertOp = invertPointerM(arg->getOperand(0), bb, TT); |
| Type *shadowTy = arg->getDestTy(); |
| |
| auto rule = [&](Value *invertOp) { |
| return bb.CreateCast(arg->getOpcode(), invertOp, shadowTy, |
| arg->getName() + "'ipc"); |
| }; |
| |
| Value *shadow = applyChainRule(shadowTy, bb, rule, invertOp); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<FreezeInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *invertOp = invertPointerM(arg->getOperand(0), bb, TT); |
| Type *shadowTy = arg->getType(); |
| |
| if (mode == DerivativeMode::ReverseModeCombined || |
| mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeGradient) { |
| if (TT.allFloat(oval, DL)) { |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| } |
| } |
| assert(!arg->getType()->isDoubleTy()); |
| |
| auto rule = [&](Value *invertOp) { |
| return bb.CreateFreeze(invertOp, arg->getName() + "'ipf"); |
| }; |
| |
| Value *shadow = applyChainRule(shadowTy, bb, rule, invertOp); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<ConstantExpr>(oval)) { |
| IRBuilder<> bb(inversionAllocs); |
| if (arg->getOpcode() == Instruction::Add) { |
| if (isa<ConstantInt>(arg->getOperand(0))) { |
| auto rule = [&arg](Value *ip) { |
| Constant *invops[2] = {arg->getOperand(0), cast<Constant>(ip)}; |
| return arg->getWithOperands(invops); |
| }; |
| auto ip = invertPointerM(arg->getOperand(1), bb, TT); |
| return applyChainRule(arg->getType(), bb, rule, ip); |
| } |
| if (isa<ConstantInt>(arg->getOperand(1))) { |
| auto rule = [&arg](Value *ip) { |
| Constant *invops[2] = {cast<Constant>(ip), arg->getOperand(1)}; |
| return arg->getWithOperands(invops); |
| }; |
| auto ip = invertPointerM(arg->getOperand(0), bb, TT); |
| return applyChainRule(arg->getType(), bb, rule, ip); |
| } |
| } |
| auto ip = invertPointerM(arg->getOperand(0), bb, TT); |
| |
| if (arg->isCast()) { |
| #if LLVM_VERSION_MAJOR < 17 |
| if (auto PT = dyn_cast<PointerType>(arg->getType())) { |
| if (isConstantValue(arg->getOperand(0)) && |
| PT->getPointerElementType()->isFunctionTy()) { |
| goto end; |
| } |
| } |
| #endif |
| if (isa<Constant>(ip)) { |
| auto rule = [&arg](Value *ip) { |
| return ConstantExpr::getCast(arg->getOpcode(), cast<Constant>(ip), |
| arg->getType()); |
| }; |
| |
| return applyChainRule(arg->getType(), bb, rule, ip); |
| |
| } else { |
| auto rule = [&](Value *ip) { |
| return bb.CreateCast((Instruction::CastOps)arg->getOpcode(), ip, |
| arg->getType(), arg->getName() + "'ipc"); |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, shadow))); |
| |
| return shadow; |
| } |
| } else if (arg->getOpcode() == Instruction::GetElementPtr) { |
| if (auto C = dyn_cast<Constant>(ip)) { |
| auto rule = [&arg, &C]() { |
| SmallVector<Constant *, 8> NewOps; |
| for (unsigned i = 0, e = arg->getNumOperands(); i != e; ++i) |
| NewOps.push_back(i == 0 ? C : arg->getOperand(i)); |
| return cast<Value>(arg->getWithOperands(NewOps)); |
| }; |
| |
| return applyChainRule(arg->getType(), bb, rule); |
| } else { |
| SmallVector<Value *, 4> invertargs; |
| for (unsigned i = 0; i < arg->getNumOperands() - 1; ++i) { |
| Value *b = getNewFromOriginal(arg->getOperand(1 + i)); |
| invertargs.push_back(b); |
| } |
| |
| auto rule = [&bb, &arg, &invertargs](Value *ip) { |
| // TODO mark this the same inbounds as the original |
| return bb.CreateGEP(cast<GEPOperator>(ip)->getSourceElementType(), ip, |
| invertargs, arg->getName() + "'ipg"); |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } |
| } else { |
| llvm::errs() << *arg << "\n"; |
| assert(0 && "unhandled"); |
| } |
| goto end; |
| } else if (auto arg = dyn_cast<ExtractValueInst>(oval)) { |
| auto newi = getNewFromOriginal(arg); |
| IRBuilder<> bb(newi->getNextNode()); |
| |
| auto AggTy = arg->getAggregateOperand()->getType(); |
| SmallVector<Value *, 4> vec; |
| vec.push_back(ConstantInt::get(Type::getInt64Ty(arg->getContext()), 0)); |
| for (auto ind : arg->getIndices()) { |
| vec.push_back(ConstantInt::get(Type::getInt32Ty(arg->getContext()), ind)); |
| } |
| auto ud = UndefValue::get(getUnqual(AggTy)); |
| auto g2 = GetElementPtrInst::Create(AggTy, ud, vec); |
| APInt ai(DL.getIndexSizeInBits(g2->getPointerAddressSpace()), 0); |
| g2->accumulateConstantOffset(DL, ai); |
| delete g2; |
| |
| unsigned Off = (unsigned)ai.getLimitedValue(); |
| auto ObjSize = (DL.getTypeSizeInBits(arg->getType()) + 7) / 8; |
| auto AggSize = (DL.getTypeSizeInBits(AggTy) + 7) / 8; |
| |
| TypeTree agg_look = TR.query(arg->getAggregateOperand()); |
| if (TT.isKnown()) { |
| agg_look = agg_look.Clear(Off, Off + ObjSize, AggSize); |
| } |
| agg_look |= TT.ShiftIndices(DL, 0, ObjSize, Off); |
| agg_look.CanonicalizeInPlace(AggSize, DL); |
| |
| auto ip = invertPointerM(arg->getOperand(0), bb, agg_look); |
| |
| auto rule = [&bb, &arg, &newi, this](Value *ip) -> llvm::Value * { |
| if (ip == getNewFromOriginal(arg->getOperand(0))) |
| return newi; |
| return bb.CreateExtractValue(ip, arg->getIndices(), |
| arg->getName() + "'ipev"); |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<InsertValueInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *ivops[2] = {nullptr, nullptr}; |
| |
| auto AggTy = arg->getAggregateOperand()->getType(); |
| auto InsertedTy = arg->getInsertedValueOperand()->getType(); |
| SmallVector<Value *, 4> vec; |
| vec.push_back(ConstantInt::get(Type::getInt64Ty(arg->getContext()), 0)); |
| for (auto ind : arg->getIndices()) { |
| vec.push_back(ConstantInt::get(Type::getInt32Ty(arg->getContext()), ind)); |
| } |
| auto ud = UndefValue::get(getUnqual(AggTy)); |
| auto g2 = GetElementPtrInst::Create(AggTy, ud, vec); |
| APInt ai(DL.getIndexSizeInBits(g2->getPointerAddressSpace()), 0); |
| g2->accumulateConstantOffset(DL, ai); |
| delete g2; |
| |
| unsigned Off = (unsigned)ai.getLimitedValue(); |
| auto ObjSize = (DL.getTypeSizeInBits(InsertedTy) + 7) / 8; |
| |
| for (int i = 0; i < 2; i++) { |
| auto op = arg->getOperand(i); |
| TypeTree subTT; |
| if (i == 0) { |
| subTT = TT; |
| } else { |
| subTT = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| if (auto MD = hasMetadata(arg, "enzyme_truetype")) { |
| for (size_t i = 0; i < MD->getNumOperands(); i += 2) { |
| ConcreteType base( |
| llvm::cast<llvm::MDString>(MD->getOperand(i))->getString(), |
| MD->getContext()); |
| auto offset = |
| llvm::cast<llvm::ConstantInt>( |
| llvm::cast<llvm::ConstantAsMetadata>(MD->getOperand(i + 1)) |
| ->getValue()) |
| ->getSExtValue(); |
| if (offset < Off || offset >= Off + ObjSize) { |
| continue; |
| } |
| subTT.insert({(int)(offset - Off)}, base); |
| } |
| } |
| subTT.CanonicalizeInPlace(ObjSize, DL); |
| } |
| |
| if (!runtimeActivity && !isa<InsertValueInst>(op)) { |
| if (isConstantValue(op)) { |
| if (subTT.anyPointer(op, DL) && |
| subTT[{-1, -1}] != BaseType::Integer) { |
| if (!allNullOrUndef(op, DL, subTT)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Mismatched activity for: " << *arg |
| << " const val: " << *op; |
| if (CustomErrorHandler) |
| ivops[i] = unwrap(CustomErrorHandler( |
| str.c_str(), wrap(arg), ErrorType::MixedActivityError, this, |
| wrap(op), wrap(&bb))); |
| else |
| EmitWarning("MixedActivityError", *arg, ss.str()); |
| } |
| } |
| } |
| } |
| if (!ivops[i]) { |
| ivops[i] = invertPointerM(op, bb, subTT); |
| } |
| } |
| |
| auto rule = [&bb, &arg](Value *ip0, Value *ip1) { |
| return bb.CreateInsertValue(ip0, ip1, arg->getIndices(), |
| arg->getName() + "'ipiv"); |
| }; |
| |
| Value *shadow = |
| applyChainRule(arg->getType(), bb, rule, ivops[0], ivops[1]); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<ExtractElementInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| auto VecTy = arg->getVectorOperand()->getType(); |
| auto ElTy = cast<VectorType>(VecTy)->getElementType(); |
| auto ObjSize = (DL.getTypeSizeInBits(ElTy) + 7) / 8; |
| auto VecSize = (DL.getTypeSizeInBits(VecTy) + 7) / 8; |
| |
| TypeTree vec_look = TR.query(arg->getVectorOperand()); |
| if (auto CI = dyn_cast<ConstantInt>(arg->getIndexOperand())) { |
| unsigned Off = (CI->getZExtValue() * DL.getTypeSizeInBits(ElTy)) / 8; |
| if (TT.isKnown()) { |
| vec_look = vec_look.Clear(Off, Off + ObjSize, VecSize); |
| vec_look |= TT.ShiftIndices(DL, 0, ObjSize, Off); |
| vec_look.CanonicalizeInPlace(VecSize, DL); |
| } |
| } |
| |
| auto ip = invertPointerM(arg->getVectorOperand(), bb, vec_look); |
| |
| auto rule = [&](Value *ip) { |
| return bb.CreateExtractElement(ip, |
| getNewFromOriginal(arg->getIndexOperand()), |
| arg->getName() + "'ipee"); |
| ; |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<InsertElementInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *op0 = arg->getOperand(0); |
| Value *op1 = arg->getOperand(1); |
| Value *op2 = arg->getOperand(2); |
| auto VecTy = op0->getType(); |
| auto ElTy = cast<VectorType>(VecTy)->getElementType(); |
| auto ObjSize = (DL.getTypeSizeInBits(ElTy) + 7) / 8; |
| TypeTree subTT1 = TT; |
| if (auto CI = dyn_cast<ConstantInt>(op2)) { |
| unsigned Off = (CI->getZExtValue() * DL.getTypeSizeInBits(ElTy)) / 8; |
| subTT1 = TT.ShiftIndices(DL, Off, ObjSize, 0); |
| subTT1.CanonicalizeInPlace(ObjSize, DL); |
| } |
| auto ip0 = invertPointerM(op0, bb, TT); |
| auto ip1 = invertPointerM(op1, bb, subTT1); |
| |
| auto rule = [&](Value *ip0, Value *ip1) { |
| return bb.CreateInsertElement(ip0, ip1, getNewFromOriginal(op2), |
| arg->getName() + "'ipie"); |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip0, ip1); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<ShuffleVectorInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *op0 = arg->getOperand(0); |
| Value *op1 = arg->getOperand(1); |
| auto ip0 = invertPointerM(op0, bb, TT); |
| auto ip1 = invertPointerM(op1, bb, TT); |
| |
| auto rule = [&bb, &arg](Value *ip0, Value *ip1) { |
| return bb.CreateShuffleVector(ip0, ip1, arg->getShuffleMaskForBitcode(), |
| arg->getName() + "'ipsv"); |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip0, ip1); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<SelectInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| bb.setFastMathFlags(getFast()); |
| |
| Value *itval = nullptr; |
| { |
| auto tval = arg->getTrueValue(); |
| if (!runtimeActivity && TT.anyPointer(tval, DL) && |
| !isa<UndefValue>(tval) && !isa<ConstantPointerNull>(tval) && |
| !isa<ConstantAggregateZero>(tval) && isConstantValue(tval)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Mismatched activity for: " << *arg << " const val: " << *tval; |
| if (CustomErrorHandler) |
| itval = unwrap(CustomErrorHandler(str.c_str(), wrap(arg), |
| ErrorType::MixedActivityError, this, |
| wrap(tval), wrap(&bb))); |
| else |
| EmitWarning("MixedActivityError", *arg, ss.str()); |
| } |
| if (!itval) { |
| itval = invertPointerM(tval, bb, TT); |
| } |
| } |
| Value *ifval = nullptr; |
| { |
| auto fval = arg->getFalseValue(); |
| if (!runtimeActivity && TT[{-1}].isPossiblePointer() && |
| !isa<UndefValue>(fval) && !isa<ConstantPointerNull>(fval) && |
| !isa<ConstantAggregateZero>(fval) && isConstantValue(fval)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Mismatched activity for: " << *arg << " const val: " << *fval; |
| if (CustomErrorHandler) |
| ifval = unwrap(CustomErrorHandler(str.c_str(), wrap(arg), |
| ErrorType::MixedActivityError, this, |
| wrap(fval), wrap(&bb))); |
| else |
| EmitWarning("MixedActivityError", *arg, ss.str()); |
| } |
| if (!ifval) { |
| ifval = invertPointerM(fval, bb, TT); |
| } |
| } |
| |
| Value *shadow = applyChainRule( |
| arg->getType(), bb, |
| [&](Value *tv, Value *fv) { |
| return bb.CreateSelect(getNewFromOriginal(arg->getCondition()), tv, |
| fv, arg->getName() + "'ipse"); |
| }, |
| itval, ifval); |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto arg = dyn_cast<LoadInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *op0 = arg->getOperand(0); |
| Value *ip = invertPointerM(op0, bb); |
| |
| SmallVector<Metadata *, 1> prevScopes; |
| if (auto prev = arg->getMetadata(LLVMContext::MD_alias_scope)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| prevScopes.push_back(M); |
| } |
| } |
| SmallVector<Metadata *, 1> prevNoAlias; |
| if (auto prev = arg->getMetadata(LLVMContext::MD_noalias)) { |
| for (auto &M : cast<MDNode>(prev)->operands()) { |
| prevNoAlias.push_back(M); |
| } |
| } |
| size_t idx = 0; |
| auto rule = [&](Value *ip) { |
| auto li = bb.CreateLoad(arg->getType(), ip, arg->getName() + "'ipl"); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| li->copyMetadata(*arg, ToCopy2); |
| li->copyIRFlags(arg); |
| |
| SmallVector<Metadata *, 1> scopeMD = {getDerivativeAliasScope(op0, idx)}; |
| for (auto M : prevScopes) |
| scopeMD.push_back(M); |
| auto scope = MDNode::get(li->getContext(), scopeMD); |
| li->setMetadata(LLVMContext::MD_alias_scope, scope); |
| |
| SmallVector<Metadata *, 1> MDs; |
| for (ssize_t j = -1; j < getWidth(); j++) { |
| if (j != (ssize_t)idx) |
| MDs.push_back(getDerivativeAliasScope(op0, j)); |
| } |
| for (auto M : prevNoAlias) |
| MDs.push_back(M); |
| if (MDs.size()) { |
| auto noscope = MDNode::get(li->getContext(), MDs); |
| li->setMetadata(LLVMContext::MD_noalias, noscope); |
| } |
| |
| li->setAlignment(arg->getAlign()); |
| li->setDebugLoc(getNewFromOriginal(arg->getDebugLoc())); |
| li->setVolatile(arg->isVolatile()); |
| li->setOrdering(arg->getOrdering()); |
| li->setSyncScopeID(arg->getSyncScopeID()); |
| idx++; |
| return li; |
| }; |
| |
| Value *li = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, li))); |
| return li; |
| |
| } else if (auto arg = dyn_cast<BinaryOperator>(oval)) { |
| if (mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeCombined || |
| mode == DerivativeMode::ReverseModeGradient) { |
| if (TT.allFloat(arg, DL)) { |
| return Constant::getNullValue(getShadowType(arg->getType())); |
| } |
| } |
| |
| if (!arg->getType()->isIntOrIntVectorTy()) { |
| llvm::errs() << *oval << "\n"; |
| } |
| assert(arg->getType()->isIntOrIntVectorTy()); |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| Value *val0 = nullptr; |
| Value *val1 = nullptr; |
| |
| val0 = invertPointerM(arg->getOperand(0), bb, TT); |
| val1 = invertPointerM(arg->getOperand(1), bb, TT); |
| assert(val0->getType() == val1->getType()); |
| |
| auto rule = [&bb, &arg](Value *val0, Value *val1) { |
| auto li = bb.CreateBinOp(arg->getOpcode(), val0, val1, arg->getName()); |
| if (auto BI = dyn_cast<BinaryOperator>(li)) |
| BI->copyIRFlags(arg); |
| return li; |
| }; |
| |
| Value *li = applyChainRule(arg->getType(), bb, rule, val0, val1); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, li))); |
| return li; |
| } else if (auto arg = dyn_cast<GetElementPtrInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(arg)); |
| SmallVector<Value *, 4> invertargs; |
| for (unsigned i = 0; i < arg->getNumIndices(); ++i) { |
| Value *b = getNewFromOriginal(arg->getOperand(1 + i)); |
| invertargs.push_back(b); |
| } |
| Value *ip = invertPointerM(arg->getPointerOperand(), bb); |
| |
| auto rule = [&](Value *ip) { |
| auto shadow = bb.CreateGEP(arg->getSourceElementType(), ip, invertargs, |
| arg->getName() + "'ipg"); |
| |
| if (auto gep = dyn_cast<GetElementPtrInst>(shadow)) |
| gep->setIsInBounds(arg->isInBounds()); |
| |
| return shadow; |
| }; |
| |
| Value *shadow = applyChainRule(arg->getType(), bb, rule, ip); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } else if (auto inst = dyn_cast<AllocaInst>(oval)) { |
| IRBuilder<> bb(getNewFromOriginal(inst)); |
| Value *asize = getNewFromOriginal(inst->getArraySize()); |
| |
| auto rule1 = [&]() { |
| AllocaInst *antialloca = bb.CreateAlloca( |
| inst->getAllocatedType(), inst->getType()->getPointerAddressSpace(), |
| asize, inst->getName() + "'ipa"); |
| antialloca->setAlignment(inst->getAlign()); |
| return antialloca; |
| }; |
| |
| Value *antialloca = applyChainRule(oval->getType(), bb, rule1); |
| |
| invertedPointers.insert(std::make_pair( |
| (const Value *)oval, InvertedPointerVH(this, antialloca))); |
| |
| if (auto ci = dyn_cast<ConstantInt>(asize)) { |
| if (ci->isOne()) { |
| |
| auto rule = [&](Value *antialloca) { |
| StoreInst *st = bb.CreateStore( |
| Constant::getNullValue(inst->getAllocatedType()), antialloca); |
| cast<StoreInst>(st)->setAlignment(inst->getAlign()); |
| }; |
| |
| applyChainRule(bb, rule, antialloca); |
| |
| return antialloca; |
| } else { |
| // TODO handle alloca of size > 1 |
| } |
| } |
| |
| auto rule2 = [&](Value *antialloca) { |
| auto dst_arg = |
| bb.CreateBitCast(antialloca, getInt8PtrTy(oval->getContext())); |
| auto val_arg = ConstantInt::get(Type::getInt8Ty(oval->getContext()), 0); |
| auto len_arg = bb.CreateMul( |
| bb.CreateZExtOrTrunc(asize, Type::getInt64Ty(oval->getContext())), |
| ConstantInt::get(Type::getInt64Ty(oval->getContext()), |
| M->getDataLayout().getTypeAllocSizeInBits( |
| inst->getAllocatedType()) / |
| 8), |
| "", true, true); |
| auto volatile_arg = ConstantInt::getFalse(oval->getContext()); |
| |
| Value *args[] = {dst_arg, val_arg, len_arg, volatile_arg}; |
| Type *tys[] = {dst_arg->getType(), len_arg->getType()}; |
| auto memset = cast<CallInst>(bb.CreateCall( |
| getIntrinsicDeclaration(M, Intrinsic::memset, tys), args)); |
| memset->addParamAttr( |
| 0, Attribute::getWithAlignment(inst->getContext(), inst->getAlign())); |
| memset->addParamAttr(0, Attribute::NonNull); |
| }; |
| |
| applyChainRule(bb, rule2, antialloca); |
| |
| return antialloca; |
| } else if (auto II = dyn_cast<IntrinsicInst>(oval)) { |
| if (isIntelSubscriptIntrinsic(*II)) { |
| IRBuilder<> bb(getNewFromOriginal(II)); |
| |
| const std::array<size_t, 4> idxArgsIndices{{0, 1, 2, 4}}; |
| const size_t ptrArgIndex = 3; |
| |
| SmallVector<Value *, 5> invertArgs(5); |
| for (auto i : idxArgsIndices) { |
| Value *idx = getNewFromOriginal(II->getOperand(i)); |
| invertArgs[i] = idx; |
| } |
| Value *invertPtrArg = invertPointerM(II->getOperand(ptrArgIndex), bb); |
| invertArgs[ptrArgIndex] = invertPtrArg; |
| |
| auto rule = [&](Value *ip) { |
| auto shadow = bb.CreateCall(II->getCalledFunction(), invertArgs); |
| assert(isa<CallInst>(shadow)); |
| #if LLVM_VERSION_MAJOR >= 13 |
| auto CI = cast<CallInst>(shadow); |
| // Must copy the elementtype attribute as it is needed by the intrinsic |
| CI->addParamAttr( |
| ptrArgIndex, |
| II->getParamAttr(ptrArgIndex, Attribute::AttrKind::ElementType)); |
| #endif |
| return shadow; |
| }; |
| |
| Value *shadow = applyChainRule(II->getType(), bb, rule, invertPtrArg); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, shadow))); |
| return shadow; |
| } |
| |
| IRBuilder<> bb(getNewFromOriginal(II)); |
| bb.setFastMathFlags(getFast()); |
| switch (II->getIntrinsicID()) { |
| default: |
| goto end; |
| #if LLVM_VERSION_MAJOR < 20 |
| case Intrinsic::nvvm_ldg_global_i: |
| case Intrinsic::nvvm_ldg_global_p: |
| case Intrinsic::nvvm_ldg_global_f: |
| #endif |
| case Intrinsic::nvvm_ldu_global_i: |
| case Intrinsic::nvvm_ldu_global_p: |
| case Intrinsic::nvvm_ldu_global_f: { |
| return applyChainRule( |
| II->getType(), bb, |
| [&](Value *ptr) { |
| Value *args[] = {ptr, getNewFromOriginal(II->getArgOperand(1))}; |
| auto li = bb.CreateCall(II->getCalledFunction(), args); |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| li->copyMetadata(*II, ToCopy2); |
| li->setDebugLoc(getNewFromOriginal(II->getDebugLoc())); |
| return li; |
| }, |
| invertPointerM(II->getArgOperand(0), bb)); |
| case Intrinsic::masked_load: { |
| auto invDefault = invertPointerM(II->getArgOperand(3), bb, TT); |
| auto invPtr = invertPointerM(II->getArgOperand(0), bb); |
| return applyChainRule( |
| II->getType(), bb, |
| [&](Value *ptr, Value *defaultV) { |
| Value *args[] = {ptr, getNewFromOriginal(II->getArgOperand(1)), |
| getNewFromOriginal(II->getArgOperand(2)), |
| defaultV}; |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| auto li = bb.CreateCall(II->getCalledFunction(), args); |
| li->copyMetadata(*II, ToCopy2); |
| li->setDebugLoc(getNewFromOriginal(II->getDebugLoc())); |
| return li; |
| }, |
| invPtr, invDefault); |
| } |
| } |
| } |
| } else if (auto phi = dyn_cast<PHINode>(oval)) { |
| |
| if (phi->getNumIncomingValues() == 0) { |
| dumpMap(invertedPointers); |
| assert(0 && "illegal iv of phi"); |
| } |
| std::map<Value *, std::set<BasicBlock *>> mapped; |
| for (unsigned int i = 0; i < phi->getNumIncomingValues(); ++i) { |
| mapped[phi->getIncomingValue(i)].insert(phi->getIncomingBlock(i)); |
| } |
| |
| if (false && mapped.size() == 1) { |
| return invertPointerM(phi->getIncomingValue(0), BuilderM, TT); |
| } |
| #if 0 |
| else if (false && mapped.size() == 2) { |
| IRBuilder <> bb(phi); |
| auto which = bb.CreatePHI(Type::getInt1Ty(phi->getContext()), phi->getNumIncomingValues()); |
| //TODO this is not recursive |
| |
| int cnt = 0; |
| Value* vals[2]; |
| for(auto v : mapped) { |
| assert( cnt <= 1 ); |
| vals[cnt] = v.first; |
| for (auto b : v.second) { |
| which->addIncoming(ConstantInt::get(which->getType(), cnt), b); |
| } |
| ++cnt; |
| } |
| auto inv0 = invertPointerM(vals[0], BuilderM, TT); |
| auto inv1 = invertPointerM(vals[1], BuilderM, TT); |
| auto result = BuilderM.CreateSelect(which, inv1, inv0); |
| return result; |
| } |
| #endif |
| |
| else { |
| auto NewV = getNewFromOriginal(phi); |
| IRBuilder<> bb(NewV); |
| bb.setFastMathFlags(getFast()); |
| // Note if the original phi node get's scev'd in NewF, it may |
| // no longer be a phi and we need a new place to insert this phi |
| // Note that if scev'd this can still be a phi with 0 incoming indicating |
| // an unnecessary value to be replaced |
| // TODO consider allowing the inverted pointer to become a scev |
| if (!isa<PHINode>(NewV) || |
| cast<PHINode>(NewV)->getNumIncomingValues() == 0) { |
| bb.SetInsertPoint(bb.GetInsertBlock(), bb.GetInsertBlock()->begin()); |
| } |
| |
| if (EnzymeVectorSplitPhi && width > 1) { |
| IRBuilder<> postPhi(NewV->getParent()->getFirstNonPHI()); |
| Type *shadowTy = getShadowType(phi->getType()); |
| PHINode *tmp = bb.CreatePHI(shadowTy, phi->getNumIncomingValues()); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, tmp))); |
| |
| Type *wrappedType = ArrayType::get(phi->getType(), width); |
| Value *res = UndefValue::get(wrappedType); |
| |
| SmallVector<Value *, 1> invertedVals; |
| for (unsigned int j = 0; j < phi->getNumIncomingValues(); ++j) { |
| IRBuilder<> pre( |
| cast<BasicBlock>(getNewFromOriginal(phi->getIncomingBlock(j))) |
| ->getTerminator()); |
| Value *preval = phi->getIncomingValue(j); |
| |
| Value *val = nullptr; |
| if (!runtimeActivity && TT[{-1}].isPossiblePointer() && |
| !isa<UndefValue>(preval) && !isa<ConstantPointerNull>(preval) && |
| isConstantValue(preval)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Mismatched activity for: " << *phi |
| << " const val: " << *preval; |
| if (CustomErrorHandler) |
| val = unwrap(CustomErrorHandler(str.c_str(), wrap(phi), |
| ErrorType::MixedActivityError, |
| this, wrap(preval), wrap(&pre))); |
| else |
| EmitWarning("MixedActivityError", *phi, ss.str()); |
| } |
| if (!val) { |
| val = invertPointerM(preval, pre, TT); |
| } |
| invertedVals.push_back(val); |
| } |
| |
| for (unsigned int i = 0; i < getWidth(); ++i) { |
| PHINode *which = |
| bb.CreatePHI(phi->getType(), phi->getNumIncomingValues()); |
| which->setDebugLoc(getNewFromOriginal(phi->getDebugLoc())); |
| |
| // Avoid re-extracting from the same value, since multiple |
| // entries to the same phi from the same block must have the |
| // same value; |
| DenseMap<BasicBlock *, Value *> samePHI; |
| for (unsigned int j = 0; j < phi->getNumIncomingValues(); ++j) { |
| IRBuilder<> pre( |
| cast<BasicBlock>(getNewFromOriginal(phi->getIncomingBlock(j))) |
| ->getTerminator()); |
| Value *val = invertedVals[j]; |
| Value *extracted_diff; |
| auto found = samePHI.find(phi->getIncomingBlock(j)); |
| if (found == samePHI.end()) { |
| extracted_diff = extractMeta(pre, val, i); |
| samePHI[phi->getIncomingBlock(j)] = extracted_diff; |
| } else { |
| extracted_diff = found->second; |
| } |
| which->addIncoming( |
| extracted_diff, |
| cast<BasicBlock>(getNewFromOriginal(phi->getIncomingBlock(j)))); |
| } |
| |
| res = postPhi.CreateInsertValue(res, which, {i}); |
| } |
| invertedPointers.erase((const Value *)oval); |
| replaceAWithB(tmp, res); |
| erase(tmp); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, res))); |
| |
| return res; |
| } else { |
| Type *shadowTy = getShadowType(phi->getType()); |
| PHINode *which = bb.CreatePHI(shadowTy, phi->getNumIncomingValues()); |
| which->setDebugLoc(getNewFromOriginal(phi->getDebugLoc())); |
| |
| invertedPointers.insert(std::make_pair((const Value *)oval, |
| InvertedPointerVH(this, which))); |
| |
| for (unsigned int i = 0; i < phi->getNumIncomingValues(); ++i) { |
| IRBuilder<> pre( |
| cast<BasicBlock>(getNewFromOriginal(phi->getIncomingBlock(i))) |
| ->getTerminator()); |
| |
| Value *preval = phi->getIncomingValue(i); |
| |
| Value *val = nullptr; |
| if (!runtimeActivity && TT[{-1}].isPossiblePointer() && |
| !isa<UndefValue>(preval) && !isa<ConstantPointerNull>(preval) && |
| isConstantValue(preval)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Mismatched activity for: " << *phi |
| << " const val: " << *preval; |
| if (CustomErrorHandler) |
| val = unwrap(CustomErrorHandler(str.c_str(), wrap(phi), |
| ErrorType::MixedActivityError, |
| this, wrap(preval), wrap(&pre))); |
| else |
| EmitWarning("MixedActivityError", *phi, ss.str()); |
| } |
| if (!val) { |
| val = invertPointerM(preval, pre, TT); |
| } |
| |
| which->addIncoming(val, cast<BasicBlock>(getNewFromOriginal( |
| phi->getIncomingBlock(i)))); |
| } |
| return which; |
| } |
| } |
| } else if (auto CB = dyn_cast<CallBase>(oval)) { |
| auto funcName = getFuncNameFromCall(CB); |
| if (funcName == "julia.gc_loaded") { |
| IRBuilder<> bb(getNewFromOriginal(CB)); |
| bb.setFastMathFlags(getFast()); |
| Value *ptr0shadow = invertPointerM(CB->getArgOperand(0), bb); |
| Value *ptr1shadow = invertPointerM(CB->getArgOperand(1), bb); |
| Value *res = applyChainRule( |
| CB->getType(), bb, |
| [&](Value *v1, Value *v2) -> Value * { |
| Value *args[2] = {v1, v2}; |
| return bb.CreateCall(CB->getCalledFunction(), args); |
| }, |
| ptr0shadow, ptr1shadow); |
| |
| assert(invertedPointers.find(oval) == invertedPointers.end()); |
| |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, res))); |
| |
| return res; |
| } |
| } else if (auto FPMO = dyn_cast<FPMathOperator>(oval)) { |
| if (FPMO->getOpcode() == Instruction::FNeg) { |
| if (mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeCombined || |
| mode == DerivativeMode::ReverseModeGradient) { |
| if (TT.allFloat(FPMO, DL)) { |
| return Constant::getNullValue(getShadowType(FPMO->getType())); |
| } |
| } |
| } |
| } |
| |
| end:; |
| assert(BuilderM.GetInsertBlock()); |
| assert(BuilderM.GetInsertBlock()->getParent()); |
| assert(oval); |
| |
| if (isa<CallBase>(oval) && TT.allFloat(oval, DL)) { |
| return Constant::getNullValue(getShadowType(oval->getType())); |
| } |
| |
| if (CustomErrorHandler) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "cannot find shadow for " << *oval; |
| auto iv = |
| unwrap(CustomErrorHandler(str.c_str(), wrap(oval), ErrorType::NoShadow, |
| this, nullptr, wrap(&BuilderM))); |
| if (iv) { |
| invertedPointers.insert( |
| std::make_pair((const Value *)oval, InvertedPointerVH(this, iv))); |
| return iv; |
| } |
| } |
| |
| llvm::errs() << *newFunc->getParent() << "\n"; |
| llvm::errs() << "fn:" << *newFunc << "\noval=" << *oval |
| << " icv=" << isConstantValue(oval) << "\n"; |
| for (auto z : invertedPointers) { |
| llvm::errs() << "available inversion for " << *z.first << " of " |
| << *z.second << "\n"; |
| } |
| assert(0 && "cannot find deal with ptr that isnt arg"); |
| report_fatal_error("cannot find deal with ptr that isnt arg"); |
| } |
| |
| Value *GradientUtils::lookupM(Value *val, IRBuilder<> &BuilderM, |
| const ValueToValueMapTy &incoming_available, |
| bool tryLegalRecomputeCheck, BasicBlock *scope) { |
| |
| assert(mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeGradient || |
| mode == DerivativeMode::ReverseModeCombined); |
| |
| assert(val->getName() != "<badref>"); |
| { |
| auto found = incoming_available.find(val); |
| if (found != incoming_available.end()) |
| return found->second; |
| } |
| if (isa<Constant>(val)) { |
| return val; |
| } |
| if (isa<BasicBlock>(val)) { |
| return val; |
| } |
| if (isa<Function>(val)) { |
| return val; |
| } |
| if (isa<UndefValue>(val)) { |
| return val; |
| } |
| if (isa<Argument>(val)) { |
| return val; |
| } |
| if (isa<MetadataAsValue>(val)) { |
| return val; |
| } |
| if (isa<InlineAsm>(val)) { |
| return val; |
| } |
| |
| if (!isa<Instruction>(val)) { |
| llvm::errs() << *val << "\n"; |
| } |
| |
| auto inst = cast<Instruction>(val); |
| if (inversionAllocs && inst->getParent() == inversionAllocs) { |
| return val; |
| } |
| assert(inst->getParent()->getParent() == newFunc); |
| assert(BuilderM.GetInsertBlock()->getParent() == newFunc); |
| if (scope == nullptr) |
| scope = BuilderM.GetInsertBlock(); |
| assert(scope->getParent() == newFunc); |
| |
| bool reduceRegister = false; |
| |
| if (EnzymeRegisterReduce) { |
| if (isNVLoad(inst)) { |
| reduceRegister = true; |
| } |
| if (auto LI = dyn_cast<LoadInst>(inst)) { |
| auto TT = llvm::Triple(newFunc->getParent()->getTargetTriple()); |
| unsigned int SharedAddrSpace = getGPUSharedAddrSpace(TT); |
| if (cast<PointerType>(LI->getPointerOperand()->getType()) |
| ->getAddressSpace() == SharedAddrSpace) { |
| reduceRegister |= tryLegalRecomputeCheck && |
| legalRecompute(LI, incoming_available, &BuilderM) && |
| shouldRecompute(LI, incoming_available, &BuilderM); |
| } |
| } |
| if (!inst->mayReadOrWriteMemory()) { |
| reduceRegister |= tryLegalRecomputeCheck && |
| legalRecompute(inst, incoming_available, &BuilderM) && |
| shouldRecompute(inst, incoming_available, &BuilderM); |
| } |
| if (this->isOriginalBlock(*BuilderM.GetInsertBlock())) |
| reduceRegister = false; |
| } |
| |
| if (!reduceRegister) { |
| if (isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| if (BuilderM.GetInsertBlock()->size() && |
| BuilderM.GetInsertPoint() != BuilderM.GetInsertBlock()->end()) { |
| Instruction *use = &*BuilderM.GetInsertPoint(); |
| while (isa<PHINode>(use)) |
| use = use->getNextNode(); |
| if (DT.dominates(inst, use)) { |
| return inst; |
| } else { |
| llvm::errs() << *BuilderM.GetInsertBlock()->getParent() << "\n"; |
| llvm::errs() << "didn't dominate inst: " << *inst |
| << " point: " << *BuilderM.GetInsertPoint() |
| << "\nbb: " << *BuilderM.GetInsertBlock() << "\n"; |
| } |
| } else { |
| if (inst->getParent() == BuilderM.GetInsertBlock() || |
| DT.dominates(inst, BuilderM.GetInsertBlock())) { |
| // allowed from block domination |
| return inst; |
| } else { |
| llvm::errs() << *BuilderM.GetInsertBlock()->getParent() << "\n"; |
| llvm::errs() << "didn't dominate inst: " << *inst |
| << "\nbb: " << *BuilderM.GetInsertBlock() << "\n"; |
| } |
| } |
| // This is a reverse block |
| } else if (BuilderM.GetInsertBlock() != inversionAllocs) { |
| // Something in the entry (or anything that dominates all returns, doesn't |
| // need caching) |
| BasicBlock *orig = isOriginal(inst->getParent()); |
| if (!orig) { |
| llvm::errs() << "oldFunc: " << *oldFunc << "\n"; |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "insertBlock: " << *BuilderM.GetInsertBlock() << "\n"; |
| llvm::errs() << "instP: " << *inst->getParent() << "\n"; |
| llvm::errs() << "inst: " << *inst << "\n"; |
| } |
| assert(orig); |
| |
| // TODO upgrade this to be all returns that this could enter from |
| bool legal = BlocksDominatingAllReturns.count(orig); |
| if (legal) { |
| |
| BasicBlock *forwardBlock = |
| isOriginal(originalForReverseBlock(*BuilderM.GetInsertBlock())); |
| assert(forwardBlock); |
| |
| // Don't allow this if we're not definitely using the last iteration of |
| // this value |
| // + either because the value isn't in a loop |
| // + or because the forward of the block usage location isn't in a |
| // loop (thus last iteration) |
| // + or because the loop nests share no ancestry |
| |
| bool loopLegal = true; |
| for (Loop *idx = OrigLI->getLoopFor(orig); idx != nullptr; |
| idx = idx->getParentLoop()) { |
| for (Loop *fdx = OrigLI->getLoopFor(forwardBlock); fdx != nullptr; |
| fdx = fdx->getParentLoop()) { |
| if (idx == fdx) { |
| loopLegal = false; |
| break; |
| } |
| } |
| } |
| |
| if (loopLegal) { |
| return inst; |
| } |
| } |
| } |
| } |
| |
| if (lookup_cache[BuilderM.GetInsertBlock()].find(val) != |
| lookup_cache[BuilderM.GetInsertBlock()].end()) { |
| auto result = lookup_cache[BuilderM.GetInsertBlock()][val]; |
| if (result == nullptr) { |
| lookup_cache[BuilderM.GetInsertBlock()].erase(val); |
| } else { |
| assert(result); |
| assert(result->getType()); |
| result = BuilderM.CreateBitCast(result, val->getType()); |
| assert(result->getType() == inst->getType()); |
| return result; |
| } |
| } |
| |
| ValueToValueMapTy available; |
| for (auto pair : incoming_available) { |
| if (pair.second) |
| assert(pair.first->getType() == pair.second->getType()); |
| available[pair.first] = pair.second; |
| } |
| |
| { |
| BasicBlock *forwardPass = BuilderM.GetInsertBlock(); |
| if (forwardPass != inversionAllocs && !isOriginalBlock(*forwardPass)) { |
| forwardPass = originalForReverseBlock(*forwardPass); |
| } |
| LoopContext lc; |
| bool inLoop = getContext(forwardPass, lc); |
| |
| if (inLoop) { |
| bool first = true; |
| for (LoopContext idx = lc;; getContext(idx.parent->getHeader(), idx)) { |
| if (available.count(idx.var) == 0) { |
| if (!isOriginalBlock(*BuilderM.GetInsertBlock())) { |
| available[idx.var] = |
| BuilderM.CreateLoad(idx.var->getType(), idx.antivaralloc); |
| } else { |
| available[idx.var] = idx.var; |
| } |
| } |
| if (!first && idx.var == inst) |
| return available[idx.var]; |
| if (first) { |
| first = false; |
| } |
| if (idx.parent == nullptr) |
| break; |
| } |
| } |
| } |
| |
| if (available.count(inst)) { |
| assert(available[inst]->getType() == inst->getType()); |
| return available[inst]; |
| } |
| |
| // If requesting loop bound and not available from index per above |
| // we must be requesting the total size. Rather than generating |
| // a new lcssa variable, use the existing loop exact bound var |
| { |
| LoopContext lc; |
| bool loopVar = false; |
| if (getContext(inst->getParent(), lc) && lc.var == inst) { |
| loopVar = true; |
| } else if (auto phi = dyn_cast<PHINode>(inst)) { |
| Value *V = nullptr; |
| bool legal = true; |
| for (auto &val : phi->incoming_values()) { |
| if (isa<UndefValue>(val)) |
| continue; |
| if (V == nullptr) |
| V = val; |
| else if (V != val) { |
| legal = false; |
| break; |
| } |
| } |
| if (legal) { |
| if (auto I = dyn_cast_or_null<PHINode>(V)) { |
| if (getContext(I->getParent(), lc) && lc.var == I) { |
| loopVar = true; |
| } |
| } |
| } |
| } |
| if (loopVar) { |
| Value *lim = nullptr; |
| if (lc.dynamic) { |
| // Must be in a reverse pass fashion for a lookup to index bound to be |
| // legal |
| assert(/*ReverseLimit*/ reverseBlocks.size() > 0); |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| lc.preheader); |
| lim = lookupValueFromCache( |
| lc.var->getType(), /*forwardPass*/ false, BuilderM, lctx, |
| getDynamicLoopLimit(LI.getLoopFor(lc.header)), |
| /*isi1*/ false, available); |
| } else { |
| lim = lookupM(lc.trueLimit, BuilderM); |
| } |
| lookup_cache[BuilderM.GetInsertBlock()][val] = lim; |
| return lim; |
| } |
| } |
| |
| Instruction *prelcssaInst = inst; |
| |
| assert(inst->getName() != "<badref>"); |
| val = fixLCSSA(inst, scope); |
| if (isa<UndefValue>(val)) { |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *BuilderM.GetInsertBlock() << "\n"; |
| llvm::errs() << *scope << "\n"; |
| llvm::errs() << *val << " inst " << *inst << "\n"; |
| assert(0 && "undef value upon lcssa"); |
| } |
| inst = cast<Instruction>(val); |
| assert(prelcssaInst->getType() == inst->getType()); |
| assert(!this->isOriginalBlock(*BuilderM.GetInsertBlock())); |
| |
| // Update index and caching per lcssa |
| if (lookup_cache[BuilderM.GetInsertBlock()].find(val) != |
| lookup_cache[BuilderM.GetInsertBlock()].end()) { |
| auto result = lookup_cache[BuilderM.GetInsertBlock()][val]; |
| if (result == nullptr) { |
| lookup_cache[BuilderM.GetInsertBlock()].erase(val); |
| } else { |
| assert(result); |
| assert(result->getType()); |
| result = BuilderM.CreateBitCast(result, val->getType()); |
| assert(result->getType() == inst->getType()); |
| return result; |
| } |
| } |
| |
| // TODO consider call as part of |
| bool lrc = false, src = false; |
| if (tryLegalRecomputeCheck && |
| (lrc = legalRecompute(prelcssaInst, available, &BuilderM))) { |
| if ((src = shouldRecompute(prelcssaInst, available, &BuilderM))) { |
| auto ity = inst->getType(); |
| // unwrap of a placeholder shadow may replace the placeholder with a |
| // real value, in which case the original value may have deleted. |
| // Store the information to correctly store the cache. |
| Value *uninverted = nullptr; |
| if (auto phi = dyn_cast<PHINode>(val)) { |
| if (phi->getNumIncomingValues() == 0) { |
| uninverted = hasUninverted(phi); |
| } |
| } |
| auto op = unwrapM(prelcssaInst, BuilderM, available, |
| UnwrapMode::AttemptSingleUnwrap, scope); |
| if (op) { |
| assert(op); |
| assert(op->getType()); |
| if (op->getType() != ity) { |
| llvm::errs() << " op: " << *op << " inst: " << *inst << "\n"; |
| } |
| assert(op->getType() == ity); |
| if (!reduceRegister) { |
| auto cache_key = val; |
| if (uninverted) { |
| auto found = invertedPointers.find(uninverted); |
| if (found != invertedPointers.end()) { |
| cache_key = found->second; |
| } |
| } |
| lookup_cache[BuilderM.GetInsertBlock()][cache_key] = op; |
| } |
| return op; |
| } |
| } else { |
| if (isa<LoadInst>(prelcssaInst)) { |
| } |
| } |
| } |
| |
| if (auto li = dyn_cast<LoadInst>(inst)) |
| if (auto origInst = dyn_cast_or_null<LoadInst>(isOriginal(inst))) { |
| auto liobj = getBaseObject(li->getPointerOperand()); |
| |
| auto orig_liobj = getBaseObject(origInst->getPointerOperand()); |
| |
| if (scopeMap.find(inst) == scopeMap.end()) { |
| for (auto pair : scopeMap) { |
| if (auto li2 = dyn_cast<LoadInst>(const_cast<Value *>(pair.first))) { |
| |
| auto li2obj = getBaseObject(li2->getPointerOperand()); |
| |
| if (liobj == li2obj && DT.dominates(li2, li)) { |
| auto orig2 = dyn_cast_or_null<LoadInst>(isOriginal(li2)); |
| if (!orig2) |
| continue; |
| |
| bool failed = false; |
| |
| // llvm::errs() << "found potential candidate loads: oli:" |
| // << *origInst << " oli2: " << *orig2 << "\n"; |
| |
| auto scev1 = OrigSE->getSCEV(origInst->getPointerOperand()); |
| auto scev2 = OrigSE->getSCEV(orig2->getPointerOperand()); |
| // llvm::errs() << " scev1: " << *scev1 << " scev2: " << *scev2 |
| // << "\n"; |
| |
| allInstructionsBetween( |
| *OrigLI, orig2, origInst, [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| /*maybeReader*/ origInst, |
| /*maybeWriter*/ I)) { |
| failed = true; |
| // llvm::errs() << "FAILED: " << *I << "\n"; |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (failed) |
| continue; |
| |
| if (auto ar1 = dyn_cast<SCEVAddRecExpr>(scev1)) { |
| if (auto ar2 = dyn_cast<SCEVAddRecExpr>(scev2)) { |
| if (ar1->getStart() != OrigSE->getCouldNotCompute() && |
| ar1->getStart() == ar2->getStart() && |
| ar1->getStepRecurrence(*OrigSE) != |
| OrigSE->getCouldNotCompute() && |
| ar1->getStepRecurrence(*OrigSE) == |
| ar2->getStepRecurrence(*OrigSE)) { |
| |
| LoopContext l1; |
| getContext(getNewFromOriginal(ar1->getLoop()->getHeader()), |
| l1); |
| LoopContext l2; |
| getContext(getNewFromOriginal(ar2->getLoop()->getHeader()), |
| l2); |
| if (l1.dynamic || l2.dynamic) |
| continue; |
| |
| // TODO IF len(ar2) >= len(ar1) then we can replace li with |
| // li2 |
| if (SE.getSCEV(l1.trueLimit) != SE.getCouldNotCompute() && |
| SE.getSCEV(l1.trueLimit) == SE.getSCEV(l2.trueLimit)) { |
| // llvm::errs() |
| // << " step1: " << *ar1->getStepRecurrence(SE) |
| // << " step2: " << *ar2->getStepRecurrence(SE) << |
| // "\n"; |
| |
| inst = li2; |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| auto scev1 = OrigSE->getSCEV(origInst->getPointerOperand()); |
| |
| auto TT = llvm::Triple(newFunc->getParent()->getTargetTriple()); |
| unsigned int SharedAddrSpace = getGPUSharedAddrSpace(TT); |
| if (EnzymeSharedForward && scev1 != OrigSE->getCouldNotCompute() && |
| cast<PointerType>(orig_liobj->getType())->getAddressSpace() == |
| SharedAddrSpace) { |
| Value *resultValue = nullptr; |
| ValueToValueMapTy newavail; |
| for (const auto &pair : available) { |
| assert(pair.first->getType() == pair.second->getType()); |
| newavail[pair.first] = pair.second; |
| } |
| allDomPredecessorsOf(origInst, *OrigDT, [&](Instruction *pred) { |
| if (auto SI = dyn_cast<StoreInst>(pred)) { |
| // auto NewSI = cast<StoreInst>(getNewFromOriginal(SI)); |
| auto si2obj = getBaseObject(SI->getPointerOperand()); |
| |
| if (si2obj != orig_liobj) |
| return false; |
| |
| bool lastStore = true; |
| bool interveningSync = false; |
| allInstructionsBetween( |
| *OrigLI, SI, origInst, [&](Instruction *potentialAlias) { |
| if (!potentialAlias->mayWriteToMemory()) |
| return false; |
| if (!writesToMemoryReadBy(&TR, *OrigAA, TLI, origInst, |
| potentialAlias)) |
| return false; |
| |
| if (auto II = dyn_cast<IntrinsicInst>(potentialAlias)) { |
| #if LLVM_VERSION_MAJOR > 20 |
| if (II->getIntrinsicID() == |
| Intrinsic::nvvm_barrier_cta_sync_aligned_all || |
| II->getIntrinsicID() == Intrinsic::amdgcn_s_barrier) { |
| #else |
| if (II->getIntrinsicID() == Intrinsic::nvvm_barrier0 || |
| II->getIntrinsicID() == Intrinsic::amdgcn_s_barrier) { |
| #endif |
| interveningSync = OrigDT->dominates(SI, II) && |
| OrigDT->dominates(II, origInst); |
| allUnsyncdPredecessorsOf( |
| II, |
| [&](Instruction *mid) { |
| if (!mid->mayWriteToMemory()) |
| return false; |
| |
| if (mid == SI) |
| return false; |
| |
| if (!writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| origInst, mid)) { |
| return false; |
| } |
| lastStore = false; |
| return true; |
| }, |
| [&]() { |
| // if gone past entry |
| if (mode != DerivativeMode::ReverseModeCombined) { |
| lastStore = false; |
| } |
| }); |
| if (!lastStore) |
| return true; |
| else |
| return false; |
| } |
| } |
| |
| lastStore = false; |
| return true; |
| }); |
| |
| if (!lastStore) |
| return false; |
| |
| auto scev2 = OrigSE->getSCEV(SI->getPointerOperand()); |
| bool legal = scev1 == scev2; |
| if (auto ar2 = dyn_cast<SCEVAddRecExpr>(scev2)) { |
| if (auto ar1 = dyn_cast<SCEVAddRecExpr>(scev1)) { |
| if (ar2->getStart() != OrigSE->getCouldNotCompute() && |
| ar1->getStart() == ar2->getStart() && |
| ar2->getStepRecurrence(*OrigSE) != |
| OrigSE->getCouldNotCompute() && |
| ar1->getStepRecurrence(*OrigSE) == |
| ar2->getStepRecurrence(*OrigSE)) { |
| |
| LoopContext l1; |
| getContext(getNewFromOriginal(ar1->getLoop()->getHeader()), |
| l1); |
| LoopContext l2; |
| getContext(getNewFromOriginal(ar2->getLoop()->getHeader()), |
| l2); |
| if (!l1.dynamic && !l2.dynamic) { |
| // TODO IF len(ar2) >= len(ar1) then we can replace li |
| // with li2 |
| if (l1.trueLimit == l2.trueLimit) { |
| const Loop *L1 = ar1->getLoop(); |
| while (L1) { |
| if (L1 == ar2->getLoop()) |
| return false; |
| L1 = L1->getParentLoop(); |
| } |
| newavail[l2.var] = available[l1.var]; |
| legal = true; |
| } |
| } |
| } |
| } |
| } |
| if (!legal) { |
| Value *sval = SI->getPointerOperand(); |
| Value *lval = origInst->getPointerOperand(); |
| while (auto CI = dyn_cast<CastInst>(sval)) |
| sval = CI->getOperand(0); |
| while (auto CI = dyn_cast<CastInst>(lval)) |
| lval = CI->getOperand(0); |
| if (auto sgep = dyn_cast<GetElementPtrInst>(sval)) { |
| if (auto lgep = dyn_cast<GetElementPtrInst>(lval)) { |
| if (sgep->getPointerOperand() == |
| lgep->getPointerOperand()) { |
| SmallVector<Value *, 3> svals; |
| for (auto &v : sgep->indices()) { |
| Value *q = v; |
| while (auto CI = dyn_cast<CastInst>(q)) |
| q = CI->getOperand(0); |
| svals.push_back(q); |
| } |
| SmallVector<Value *, 3> lvals; |
| for (auto &v : lgep->indices()) { |
| Value *q = v; |
| while (auto CI = dyn_cast<CastInst>(q)) |
| q = CI->getOperand(0); |
| lvals.push_back(q); |
| } |
| ValueToValueMapTy ThreadLookup; |
| bool legal = true; |
| for (size_t i = 0; i < svals.size(); i++) { |
| auto ss = OrigSE->getSCEV(svals[i]); |
| auto ls = OrigSE->getSCEV(lvals[i]); |
| if (cast<IntegerType>(ss->getType())->getBitWidth() > |
| cast<IntegerType>(ls->getType())->getBitWidth()) { |
| ls = OrigSE->getZeroExtendExpr(ls, ss->getType()); |
| } |
| if (cast<IntegerType>(ss->getType())->getBitWidth() < |
| cast<IntegerType>(ls->getType())->getBitWidth()) { |
| ls = OrigSE->getTruncateExpr(ls, ss->getType()); |
| } |
| if (ls != ss) { |
| if (auto II = dyn_cast<IntrinsicInst>(svals[i])) { |
| switch (II->getIntrinsicID()) { |
| case Intrinsic::nvvm_read_ptx_sreg_tid_x: |
| case Intrinsic::nvvm_read_ptx_sreg_tid_y: |
| case Intrinsic::nvvm_read_ptx_sreg_tid_z: |
| case Intrinsic::amdgcn_workitem_id_x: |
| case Intrinsic::amdgcn_workitem_id_y: |
| case Intrinsic::amdgcn_workitem_id_z: |
| ThreadLookup[getNewFromOriginal(II)] = |
| BuilderM.CreateZExtOrTrunc( |
| lookupM(getNewFromOriginal(lvals[i]), |
| BuilderM, available), |
| II->getType()); |
| break; |
| default: |
| legal = false; |
| break; |
| } |
| } else { |
| legal = false; |
| break; |
| } |
| } |
| } |
| if (legal) { |
| for (auto pair : newavail) { |
| assert(pair.first->getType() == |
| pair.second->getType()); |
| ThreadLookup[pair.first] = pair.second; |
| } |
| Value *recomp = unwrapM( |
| getNewFromOriginal(SI->getValueOperand()), BuilderM, |
| ThreadLookup, UnwrapMode::AttemptFullUnwrap, scope, |
| /*permitCache*/ false); |
| if (recomp) { |
| resultValue = recomp; |
| return true; |
| ; |
| } |
| } |
| } |
| } |
| } |
| } |
| if (!legal) |
| return false; |
| return true; |
| } |
| return false; |
| }); |
| |
| if (resultValue) { |
| if (resultValue->getType() != val->getType()) |
| resultValue = BuilderM.CreateBitCast(resultValue, val->getType()); |
| return resultValue; |
| } |
| } |
| } |
| |
| auto loadSize = (li->getParent() |
| ->getParent() |
| ->getParent() |
| ->getDataLayout() |
| .getTypeAllocSizeInBits(li->getType()) + |
| 7) / |
| 8; |
| |
| // this is guarded because havent told cacheForReverse how to move |
| if (mode == DerivativeMode::ReverseModeCombined) |
| if (!li->isVolatile() && EnzymeLoopInvariantCache) { |
| if (auto AI = dyn_cast<AllocaInst>(liobj)) { |
| assert(isa<AllocaInst>(orig_liobj)); |
| if (auto AT = dyn_cast<ArrayType>(AI->getAllocatedType())) |
| if (auto GEP = |
| dyn_cast<GetElementPtrInst>(li->getPointerOperand())) { |
| if (GEP->getPointerOperand() == AI) { |
| LoopContext l1; |
| if (!getContext(li->getParent(), l1)) |
| goto noSpeedCache; |
| |
| BasicBlock *ctx = l1.preheader; |
| |
| auto origPH = cast_or_null<BasicBlock>(isOriginal(ctx)); |
| assert(origPH); |
| if (OrigPDT->dominates(origPH, origInst->getParent())) { |
| goto noSpeedCache; |
| } |
| |
| Instruction *origTerm = origPH->getTerminator(); |
| if (!origTerm) |
| llvm::errs() << *origPH << "\n"; |
| assert(origTerm); |
| IRBuilder<> OB(origTerm); |
| LoadInst *tmpload = OB.CreateLoad(AT, orig_liobj, "'tmpload"); |
| |
| bool failed = false; |
| allInstructionsBetween( |
| *OrigLI, &*origTerm, origInst, |
| [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| /*maybeReader*/ tmpload, |
| /*maybeWriter*/ I)) { |
| failed = true; |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (failed) { |
| tmpload->eraseFromParent(); |
| goto noSpeedCache; |
| } |
| while (Loop *L = LI.getLoopFor(ctx)) { |
| BasicBlock *nctx = L->getLoopPreheader(); |
| assert(nctx); |
| bool failed = false; |
| auto origPH = cast_or_null<BasicBlock>(isOriginal(nctx)); |
| assert(origPH); |
| if (OrigPDT->dominates(origPH, origInst->getParent())) { |
| break; |
| } |
| Instruction *origTerm = origPH->getTerminator(); |
| allInstructionsBetween( |
| *OrigLI, &*origTerm, origInst, |
| [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| /*maybeReader*/ tmpload, |
| /*maybeWriter*/ I)) { |
| failed = true; |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (failed) |
| break; |
| ctx = nctx; |
| } |
| |
| tmpload->eraseFromParent(); |
| |
| IRBuilder<> v(ctx->getTerminator()); |
| |
| AllocaInst *cache = nullptr; |
| |
| LoopContext tmp; |
| bool forceSingleIter = false; |
| if (!getContext(ctx, tmp)) { |
| forceSingleIter = true; |
| } |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| ctx, forceSingleIter); |
| |
| if (auto found = findInMap(scopeMap, (Value *)liobj)) { |
| cache = found->first; |
| } else { |
| // if freeing reverseblocks must exist |
| assert(reverseBlocks.size()); |
| cache = createCacheForScope(lctx, AT, li->getName(), |
| /*shouldFree*/ true, |
| /*allocate*/ true); |
| assert(cache); |
| scopeMap.insert( |
| std::make_pair(AI, std::make_pair(cache, lctx))); |
| |
| v.setFastMathFlags(getFast()); |
| assert(isOriginalBlock(*v.GetInsertBlock())); |
| Value *outer = |
| getCachePointer(AT, |
| /*inForwardPass*/ true, v, lctx, cache, |
| /*storeinstorecache*/ true, |
| /*available*/ ValueToValueMapTy(), |
| /*extraSize*/ nullptr); |
| |
| auto ld = v.CreateLoad(AT, AI); |
| ld->setAlignment(AI->getAlign()); |
| scopeInstructions[cache].push_back(ld); |
| auto st = v.CreateStore(ld, outer); |
| auto bsize = newFunc->getParent() |
| ->getDataLayout() |
| .getTypeAllocSizeInBits(AT) / |
| 8; |
| if ((bsize & (bsize - 1)) == 0) { |
| st->setAlignment(Align(bsize)); |
| } |
| scopeInstructions[cache].push_back(st); |
| for (auto post : PostCacheStore(st, v)) { |
| scopeInstructions[cache].push_back(post); |
| } |
| } |
| |
| assert(!isOriginalBlock(*BuilderM.GetInsertBlock())); |
| Value *outer = getCachePointer( |
| AT, |
| /*inForwardPass*/ false, BuilderM, lctx, cache, |
| /*storeinstorecache*/ true, available, |
| /*extraSize*/ nullptr); |
| SmallVector<Value *, 2> idxs; |
| for (auto &idx : GEP->indices()) { |
| idxs.push_back(lookupM(idx, BuilderM, available, |
| tryLegalRecomputeCheck)); |
| } |
| |
| auto cptr = BuilderM.CreateGEP(GEP->getSourceElementType(), |
| outer, idxs); |
| cast<GetElementPtrInst>(cptr)->setIsInBounds(true); |
| |
| // Retrieve the actual result |
| auto result = loadFromCachePointer(val->getType(), BuilderM, |
| cptr, cache); |
| |
| assert(result->getType() == inst->getType()); |
| lookup_cache[BuilderM.GetInsertBlock()][val] = result; |
| return result; |
| } |
| } |
| } |
| |
| auto scev1 = OrigSE->getSCEV(origInst->getPointerOperand()); |
| // Store in memcpy opt |
| Value *lim = nullptr; |
| BasicBlock *ctx = nullptr; |
| Value *start = nullptr; |
| Value *offset = nullptr; |
| if (auto ar1 = dyn_cast<SCEVAddRecExpr>(scev1)) { |
| if (auto step = |
| dyn_cast<SCEVConstant>(ar1->getStepRecurrence(*OrigSE))) { |
| if (step->getAPInt() != loadSize) |
| goto noSpeedCache; |
| |
| LoopContext l1; |
| getContext(getNewFromOriginal(ar1->getLoop()->getHeader()), l1); |
| |
| if (l1.dynamic) |
| goto noSpeedCache; |
| |
| offset = available[l1.var]; |
| ctx = l1.preheader; |
| |
| IRBuilder<> v(ctx->getTerminator()); |
| |
| auto origPH = cast_or_null<BasicBlock>(isOriginal(ctx)); |
| assert(origPH); |
| if (OrigPDT->dominates(origPH, origInst->getParent())) { |
| goto noSpeedCache; |
| } |
| |
| lim = unwrapM(l1.trueLimit, v, |
| /*available*/ ValueToValueMapTy(), |
| UnwrapMode::AttemptFullUnwrapWithLookup); |
| if (!lim) { |
| goto noSpeedCache; |
| } |
| lim = v.CreateAdd(lim, ConstantInt::get(lim->getType(), 1), "", |
| true, true); |
| |
| { |
| #if LLVM_VERSION_MAJOR >= 12 |
| Value *start0; |
| SmallVector<Instruction *, 32> InsertedInstructions; |
| { |
| #if LLVM_VERSION_MAJOR >= 22 |
| SCEVExpander OrigExp(*OrigSE, "enzyme", |
| /*PreserveLCSSA = */ false); |
| #else |
| SCEVExpander OrigExp( |
| *OrigSE, ctx->getParent()->getParent()->getDataLayout(), |
| "enzyme", /*PreserveLCSSA = */ false); |
| #endif |
| |
| OrigExp.setInsertPoint( |
| isOriginal(l1.header)->getTerminator()); |
| |
| start0 = OrigExp.expandCodeFor( |
| ar1->getStart(), li->getPointerOperand()->getType()); |
| InsertedInstructions = OrigExp.getAllInsertedInstructions(); |
| } |
| |
| ValueToValueMapTy available; |
| for (const auto &pair : originalToNewFn) { |
| if (pair.first->getType() == pair.second->getType()) |
| available[pair.first] = pair.second; |
| } |
| |
| // Sort so that later instructions do not dominate earlier |
| // instructions. |
| llvm::stable_sort(InsertedInstructions, |
| [this](Instruction *A, Instruction *B) { |
| return OrigDT->dominates(A, B); |
| }); |
| for (auto a : InsertedInstructions) { |
| if (isa<PHINode>(a)) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "oldFunc: " << *oldFunc << "\n"; |
| ss << "newFunc: " << *newFunc << "\n"; |
| ss << "li: " << *li << "\n"; |
| ss << "start0: " << *start0 << "\n"; |
| ss << "Inserted a phi node (" << *a |
| << ") during unwrap of SCEV: " << *ar1->getStart() |
| << "\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap(li), |
| ErrorType::InternalError, nullptr, |
| nullptr, nullptr); |
| } else { |
| EmitFailure("InsertedPHISCEV", li->getDebugLoc(), li, |
| ss.str()); |
| } |
| } |
| auto uwV = |
| unwrapM(a, v, available, UnwrapMode::AttemptSingleUnwrap, |
| /*scope*/ nullptr, /*cache*/ false); |
| auto uw = dyn_cast<Instruction>(uwV); |
| assert(uwV->getType() == a->getType()); |
| #ifndef NDEBUG |
| if (uw) { |
| for (size_t i = 0; i < uw->getNumOperands(); i++) { |
| auto op = uw->getOperand(i); |
| if (auto arg = dyn_cast<Argument>(op)) |
| assert(arg->getParent() == newFunc); |
| else if (auto inst = dyn_cast<Instruction>(op)) |
| assert(inst->getParent()->getParent() == newFunc); |
| } |
| assert(uw->getParent()->getParent() == newFunc); |
| } |
| #endif |
| available[a] = uwV; |
| if (uw) |
| unwrappedLoads.erase(uw); |
| } |
| |
| start = |
| isa<Constant>(start0) ? start0 : (Value *)available[start0]; |
| if (!start) { |
| llvm::errs() << "old: " << *oldFunc << "\n"; |
| llvm::errs() << "new: " << *newFunc << "\n"; |
| llvm::errs() << "start0: " << *start0 << "\n"; |
| } |
| assert(start); |
| |
| available.clear(); |
| for (auto I : llvm::reverse(InsertedInstructions)) { |
| assert(I->getNumUses() == 0); |
| OrigSE->forgetValue(I); |
| I->eraseFromParent(); |
| } |
| #endif |
| } |
| |
| if (!start) |
| goto noSpeedCache; |
| |
| Instruction *origTerm = origPH->getTerminator(); |
| |
| bool failed = false; |
| allInstructionsBetween( |
| *OrigLI, &*origTerm, origInst, [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| /*maybeReader*/ origInst, |
| /*maybeWriter*/ I)) { |
| failed = true; |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (failed) |
| goto noSpeedCache; |
| } |
| } |
| |
| if (ctx && lim && start && offset) { |
| Value *firstLim = lim; |
| Value *firstStart = start; |
| while (Loop *L = LI.getLoopFor(ctx)) { |
| BasicBlock *nctx = L->getLoopPreheader(); |
| assert(nctx); |
| bool failed = false; |
| auto origPH = cast_or_null<BasicBlock>(isOriginal(nctx)); |
| assert(origPH); |
| if (OrigPDT->dominates(origPH, origInst->getParent())) { |
| break; |
| } |
| Instruction *origTerm = origPH->getTerminator(); |
| allInstructionsBetween( |
| *OrigLI, &*origTerm, origInst, [&](Instruction *I) -> bool { |
| if (I->mayWriteToMemory() && |
| writesToMemoryReadBy(&TR, *OrigAA, TLI, |
| /*maybeReader*/ origInst, |
| /*maybeWriter*/ I)) { |
| failed = true; |
| return /*earlyBreak*/ true; |
| } |
| return /*earlyBreak*/ false; |
| }); |
| if (failed) |
| break; |
| IRBuilder<> nv(nctx->getTerminator()); |
| Value *nlim = unwrapM(firstLim, nv, |
| /*available*/ ValueToValueMapTy(), |
| UnwrapMode::AttemptFullUnwrapWithLookup); |
| if (!nlim) |
| break; |
| Value *nstart = unwrapM(firstStart, nv, |
| /*available*/ ValueToValueMapTy(), |
| UnwrapMode::AttemptFullUnwrapWithLookup); |
| if (!nstart) |
| break; |
| lim = nlim; |
| start = nstart; |
| ctx = nctx; |
| } |
| IRBuilder<> v(ctx->getTerminator()); |
| bool isi1 = val->getType()->isIntegerTy() && |
| cast<IntegerType>(li->getType())->getBitWidth() == 1; |
| |
| AllocaInst *cache = nullptr; |
| |
| LoopContext tmp; |
| bool forceSingleIter = false; |
| if (!getContext(ctx, tmp)) { |
| forceSingleIter = true; |
| } else if (auto inst = dyn_cast<Instruction>(lim)) { |
| if (inst->getParent() == ctx || |
| !DT.dominates(inst->getParent(), ctx)) { |
| forceSingleIter = true; |
| } |
| } |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, ctx, |
| forceSingleIter); |
| |
| if (auto found = findInMap(scopeMap, (Value *)inst)) { |
| cache = found->first; |
| } else { |
| // if freeing reverseblocks must exist |
| assert(reverseBlocks.size()); |
| cache = createCacheForScope(lctx, li->getType(), li->getName(), |
| /*shouldFree*/ true, |
| /*allocate*/ true, /*extraSize*/ lim); |
| assert(cache); |
| scopeMap.insert( |
| std::make_pair(inst, std::make_pair(cache, lctx))); |
| |
| v.setFastMathFlags(getFast()); |
| assert(isOriginalBlock(*v.GetInsertBlock())); |
| Value *outer = |
| getCachePointer(li->getType(), |
| /*inForwardPass*/ true, v, lctx, cache, |
| /*storeinstorecache*/ true, |
| /*available*/ ValueToValueMapTy(), |
| /*extraSize*/ lim); |
| |
| auto dst_arg = v.CreateBitCast( |
| outer, |
| getInt8PtrTy( |
| inst->getContext(), |
| cast<PointerType>(outer->getType())->getAddressSpace())); |
| scopeInstructions[cache].push_back(cast<Instruction>(dst_arg)); |
| auto src_arg = v.CreateBitCast( |
| start, |
| getInt8PtrTy( |
| inst->getContext(), |
| cast<PointerType>(start->getType())->getAddressSpace())); |
| auto len_arg = |
| v.CreateMul(ConstantInt::get(lim->getType(), loadSize), lim, |
| "", true, true); |
| if (Instruction *I = dyn_cast<Instruction>(len_arg)) |
| scopeInstructions[cache].push_back(I); |
| auto volatile_arg = ConstantInt::getFalse(inst->getContext()); |
| |
| Value *nargs[] = {dst_arg, src_arg, len_arg, volatile_arg}; |
| |
| Type *tys[] = {dst_arg->getType(), src_arg->getType(), |
| len_arg->getType()}; |
| |
| auto memcpyF = getIntrinsicDeclaration(newFunc->getParent(), |
| Intrinsic::memcpy, tys); |
| auto mem = cast<CallInst>(v.CreateCall(memcpyF, nargs)); |
| |
| mem->addParamAttr(0, Attribute::NonNull); |
| mem->addParamAttr(1, Attribute::NonNull); |
| |
| auto bsize = |
| newFunc->getParent()->getDataLayout().getTypeAllocSizeInBits( |
| li->getType()) / |
| 8; |
| if ((bsize & (bsize - 1)) == 0) { |
| mem->addParamAttr(0, Attribute::getWithAlignment( |
| memcpyF->getContext(), Align(bsize))); |
| } |
| |
| mem->addParamAttr(1, Attribute::getWithAlignment( |
| memcpyF->getContext(), li->getAlign())); |
| scopeInstructions[cache].push_back(mem); |
| } |
| |
| assert(!isOriginalBlock(*BuilderM.GetInsertBlock())); |
| Value *result = lookupValueFromCache( |
| inst->getType(), |
| /*isForwardPass*/ false, BuilderM, lctx, cache, isi1, available, |
| /*extraSize*/ lim, offset); |
| assert(result->getType() == inst->getType()); |
| lookup_cache[BuilderM.GetInsertBlock()][val] = result; |
| |
| EmitWarning("Uncacheable", *inst, "Caching instruction ", *inst, |
| " legalRecompute: ", lrc, " shouldRecompute: ", src, |
| " tryLegalRecomputeCheck: ", tryLegalRecomputeCheck); |
| return result; |
| } |
| } |
| noSpeedCache:; |
| } |
| |
| if (scopeMap.find(inst) == scopeMap.end()) { |
| EmitWarning("Uncacheable", *inst, "Caching instruction ", *inst, |
| " legalRecompute: ", lrc, " shouldRecompute: ", src, |
| " tryLegalRecomputeCheck: ", tryLegalRecomputeCheck); |
| } |
| |
| BasicBlock *scopeI = inst->getParent(); |
| if (auto origInst = isOriginal(inst)) { |
| auto found = rematerializableAllocations.find(origInst); |
| if (found != rematerializableAllocations.end()) |
| if (found->second.LI && found->second.LI->contains(origInst)) { |
| // If not caching whole allocation and rematerializing the allocation |
| // within the loop, force an entry-level scope so there is no need |
| // to cache. |
| if (!needsCacheWholeAllocation(origInst)) |
| scopeI = &newFunc->getEntryBlock(); |
| } |
| } else { |
| for (auto pair : backwardsOnlyShadows) { |
| if (auto pinst = dyn_cast<Instruction>(pair.first)) |
| if (!pair.second.primalInitialize && pair.second.LI && |
| pair.second.LI->contains(pinst->getParent())) { |
| auto found = invertedPointers.find(pair.first); |
| if (found != invertedPointers.end() && found->second == inst) { |
| scopeI = &newFunc->getEntryBlock(); |
| |
| // Prevent the phi node from being stored into the cache by creating |
| // it before the ensureLookupCached. |
| if (scopeMap.find(inst) == scopeMap.end()) { |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, |
| scopeI); |
| |
| AllocaInst *cache = createCacheForScope( |
| lctx, inst->getType(), inst->getName(), /*shouldFree*/ true); |
| assert(cache); |
| Value *inst_tmp = inst; |
| insert_or_assign(scopeMap, inst_tmp, |
| std::pair<AssertingVH<AllocaInst>, LimitContext>( |
| cache, lctx)); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| ensureLookupCached(inst, /*shouldFree*/ true, scopeI, |
| inst->getMetadata(LLVMContext::MD_tbaa)); |
| bool isi1 = inst->getType()->isIntegerTy() && |
| cast<IntegerType>(inst->getType())->getBitWidth() == 1; |
| assert(!isOriginalBlock(*BuilderM.GetInsertBlock())); |
| auto found = findInMap(scopeMap, (Value *)inst); |
| Value *result = |
| lookupValueFromCache(inst->getType(), /*isForwardPass*/ false, BuilderM, |
| found->second, found->first, isi1, available); |
| if (auto LI2 = dyn_cast<LoadInst>(result)) |
| if (auto LI1 = dyn_cast<LoadInst>(inst)) { |
| llvm::SmallVector<unsigned int, 9> ToCopy2(MD_ToCopy); |
| ToCopy2.push_back(LLVMContext::MD_noalias); |
| ToCopy2.push_back(LLVMContext::MD_alias_scope); |
| LI2->copyMetadata(*LI1, ToCopy2); |
| } |
| if (result->getType() != inst->getType()) { |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "result: " << *result << "\n"; |
| llvm::errs() << "inst: " << *inst << "\n"; |
| llvm::errs() << "val: " << *val << "\n"; |
| } |
| assert(result->getType() == inst->getType()); |
| lookup_cache[BuilderM.GetInsertBlock()][val] = result; |
| assert(result); |
| if (result->getType() != val->getType()) { |
| result = BuilderM.CreateBitCast(result, val->getType()); |
| } |
| assert(result->getType() == val->getType()); |
| assert(result->getType()); |
| return result; |
| } |
| |
| BasicBlock *GradientUtils::originalForReverseBlock(BasicBlock &BB2) const { |
| auto found = reverseBlockToPrimal.find(&BB2); |
| if (found == reverseBlockToPrimal.end()) { |
| errs() << "newFunc: " << *newFunc << "\n"; |
| errs() << BB2 << "\n"; |
| } |
| assert(found != reverseBlockToPrimal.end()); |
| return found->second; |
| } |
| |
| //! Given a map of edges we could have taken to desired target, compute a value |
| //! that determines which target should be branched to |
| // This function attempts to determine an equivalent condition from earlier in |
| // the code and use that if possible, falling back to creating a phi node of |
| // which edge was taken if necessary This function can be used in two ways: |
| // * If replacePHIs is null (usual case), this function does the branch |
| // * If replacePHIs isn't null, do not perform the branch and instead replace |
| // the PHI's with the derived condition as to whether we should branch to a |
| // particular target |
| void GradientUtils::branchToCorrespondingTarget( |
| BasicBlock *ctx, IRBuilder<> &BuilderM, |
| const std::map<BasicBlock *, |
| std::vector<std::pair</*pred*/ BasicBlock *, |
| /*successor*/ BasicBlock *>>> |
| &targetToPreds, |
| const std::map<BasicBlock *, PHINode *> *replacePHIs) { |
| assert(targetToPreds.size() > 0); |
| if (replacePHIs) { |
| if (replacePHIs->size() == 0) |
| return; |
| |
| #ifndef NDEBUG |
| for (auto x : *replacePHIs) { |
| assert(targetToPreds.find(x.first) != targetToPreds.end()); |
| } |
| #endif |
| } |
| |
| if (targetToPreds.size() == 1) { |
| if (replacePHIs == nullptr) { |
| if (!(BuilderM.GetInsertBlock()->size() == 0 || |
| !isa<BranchInst>(BuilderM.GetInsertBlock()->back()))) { |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *BuilderM.GetInsertBlock() << "\n"; |
| } |
| assert(BuilderM.GetInsertBlock()->size() == 0 || |
| !isa<BranchInst>(BuilderM.GetInsertBlock()->back())); |
| BuilderM.CreateBr(targetToPreds.begin()->first); |
| } else { |
| for (auto pair : *replacePHIs) { |
| pair.second->replaceAllUsesWith( |
| ConstantInt::getTrue(pair.second->getContext())); |
| pair.second->eraseFromParent(); |
| } |
| } |
| return; |
| } |
| |
| // Map of function edges to list of targets this can branch to we have |
| std::map<std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| std::set<BasicBlock *>> |
| done; |
| { |
| std::deque< |
| std::tuple<std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| BasicBlock *>> |
| Q; // newblock, target |
| |
| for (auto pair : targetToPreds) { |
| for (auto pred_edge : pair.second) { |
| Q.push_back(std::make_pair(pred_edge, pair.first)); |
| } |
| } |
| |
| for (std::tuple< |
| std::pair</*pred*/ BasicBlock *, /*successor*/ BasicBlock *>, |
| BasicBlock *> |
| trace; |
| Q.size() > 0;) { |
| trace = Q.front(); |
| Q.pop_front(); |
| auto edge = std::get<0>(trace); |
| auto block = edge.first; |
| auto target = std::get<1>(trace); |
| |
| if (done[edge].count(target)) |
| continue; |
| done[edge].insert(target); |
| |
| // If this block dominates the context, don't go back up as any |
| // predecessors won't contain the conditions. |
| if (DT.dominates(block, ctx)) |
| continue; |
| |
| Loop *blockLoop = LI.getLoopFor(block); |
| |
| for (BasicBlock *Pred : predecessors(block)) { |
| // Don't go up the backedge as we can use the last value if desired via |
| // lcssa |
| if (blockLoop && blockLoop->getHeader() == block && |
| blockLoop == LI.getLoopFor(Pred)) |
| continue; |
| |
| Q.push_back( |
| std::tuple<std::pair<BasicBlock *, BasicBlock *>, BasicBlock *>( |
| std::make_pair(Pred, block), target)); |
| } |
| } |
| } |
| |
| IntegerType *T; |
| if (targetToPreds.size() == 2) |
| T = Type::getInt1Ty(BuilderM.getContext()); |
| else if (targetToPreds.size() < 256) |
| T = Type::getInt8Ty(BuilderM.getContext()); |
| else |
| T = Type::getInt32Ty(BuilderM.getContext()); |
| |
| Instruction *equivalentTerminator = nullptr; |
| |
| std::set<BasicBlock *> blocks; |
| |
| // llvm::errs() << "\n\n<DONE = " << ctx->getName() << ">\n"; |
| for (auto pair : done) { |
| const auto &edge = pair.first; |
| blocks.insert(edge.first); |
| // llvm::errs() << " edge (" << edge.first->getName() << ", " |
| // << edge.second->getName() << ") : ["; |
| // for (auto s : pair.second) |
| // llvm::errs() << s->getName() << ","; |
| // llvm::errs() << "]\n"; |
| } |
| // llvm::errs() << "</DONE>\n"; |
| |
| if (targetToPreds.size() == 3) { |
| // Try `block` as a potential first split point. |
| for (auto block : blocks) { |
| { |
| // The original split block must not have a parent with an edge |
| // to a block other than to itself, which can reach any targets. |
| if (!DT.dominates(block, ctx)) |
| continue; |
| |
| // For all successors and thus edges (block, succ): |
| // 1) Ensure that no successors have overlapping potential |
| // destinations (a list of destinations previously seen is in |
| // foundtargets). |
| // 2) The block branches to all 3 destinations (foundTargets==3) |
| std::set<BasicBlock *> foundtargets; |
| // 3) The unique target split off from the others is stored in |
| // uniqueTarget. |
| std::set<BasicBlock *> uniqueTargets; |
| for (BasicBlock *succ : successors(block)) { |
| auto edge = std::make_pair(block, succ); |
| for (BasicBlock *target : done[edge]) { |
| if (foundtargets.find(target) != foundtargets.end()) { |
| goto rnextpair; |
| } |
| foundtargets.insert(target); |
| if (done[edge].size() == 1) |
| uniqueTargets.insert(target); |
| } |
| } |
| if (foundtargets.size() != 3) |
| goto rnextpair; |
| if (uniqueTargets.size() != 1) |
| goto rnextpair; |
| |
| // Only handle cases where the split was due to a conditional |
| // branch. This branch, `bi`, splits off uniqueTargets[0] from |
| // the remainder of foundTargets. |
| auto bi1 = dyn_cast<BranchInst>(block->getTerminator()); |
| if (!bi1) |
| goto rnextpair; |
| |
| { |
| // Find a second block `subblock` which splits the two merged |
| // targets from each other. |
| BasicBlock *subblock = nullptr; |
| for (auto block2 : blocks) { |
| { |
| // The second split block must not have a parent with an edge |
| // to a block other than to itself, which can reach any of its two |
| // targets. |
| // TODO verify this |
| for (auto P : predecessors(block2)) { |
| for (auto S : successors(P)) { |
| if (S == block2) |
| continue; |
| auto edge = std::make_pair(P, S); |
| if (done.find(edge) != done.end()) { |
| for (auto target : done[edge]) { |
| if (foundtargets.find(target) != foundtargets.end() && |
| uniqueTargets.find(target) == uniqueTargets.end()) { |
| goto nextblock; |
| } |
| } |
| } |
| } |
| } |
| |
| // Again, a successful split must have unique targets. |
| std::set<BasicBlock *> seen2; |
| for (BasicBlock *succ : successors(block2)) { |
| auto edge = std::make_pair(block2, succ); |
| // Since there are only two targets, a successful split |
| // condition has only 1 target per successor of block2. |
| if (done[edge].size() != 1) { |
| goto nextblock; |
| } |
| for (BasicBlock *target : done[edge]) { |
| // block2 has non-unique targets. |
| if (seen2.find(target) != seen2.end()) { |
| goto nextblock; |
| } |
| seen2.insert(target); |
| // block2 has a target which is not part of the two needing |
| // to be split. The two needing to be split is equal to |
| // foundtargets-uniqueTargets. |
| if (foundtargets.find(target) == foundtargets.end()) { |
| goto nextblock; |
| } |
| if (uniqueTargets.find(target) != uniqueTargets.end()) { |
| goto nextblock; |
| } |
| } |
| } |
| // If we didn't find two valid successors, continue. |
| if (seen2.size() != 2) { |
| // llvm::errs() << " -- failed from not 2 seen\n"; |
| goto nextblock; |
| } |
| subblock = block2; |
| break; |
| } |
| nextblock:; |
| } |
| |
| // If no split block was found, try again. |
| if (subblock == nullptr) |
| goto rnextpair; |
| |
| // This branch, `bi2`, splits off the two blocks in |
| // (foundTargets-uniqueTargets) from each other. |
| auto bi2 = dyn_cast<BranchInst>(subblock->getTerminator()); |
| if (!bi2) |
| goto rnextpair; |
| |
| // Condition cond1 splits off uniqueTargets[0] from |
| // the remainder of foundTargets. |
| auto cond1 = lookupM(bi1->getCondition(), BuilderM); |
| |
| // Condition cond2 splits off the two blocks in |
| // (foundTargets-uniqueTargets) from each other. |
| auto cond2 = lookupM(bi2->getCondition(), BuilderM); |
| |
| if (replacePHIs == nullptr) { |
| BasicBlock *staging = |
| BasicBlock::Create(oldFunc->getContext(), "staging", newFunc); |
| auto stagingIfNeeded = [&](BasicBlock *B) { |
| auto edge = std::make_pair(block, B); |
| if (done[edge].size() == 1) { |
| return *done[edge].begin(); |
| } else { |
| assert(done[edge].size() == 2); |
| return staging; |
| } |
| }; |
| BuilderM.CreateCondBr(cond1, stagingIfNeeded(bi1->getSuccessor(0)), |
| stagingIfNeeded(bi1->getSuccessor(1))); |
| BuilderM.SetInsertPoint(staging); |
| BuilderM.CreateCondBr( |
| cond2, |
| *done[std::make_pair(subblock, bi2->getSuccessor(0))].begin(), |
| *done[std::make_pair(subblock, bi2->getSuccessor(1))].begin()); |
| } else { |
| Value *otherBranch = nullptr; |
| for (unsigned i = 0; i < 2; ++i) { |
| Value *val = cond1; |
| if (i == 1) |
| val = BuilderM.CreateNot(val, "anot1_"); |
| auto edge = std::make_pair(block, bi1->getSuccessor(i)); |
| if (done[edge].size() == 1) { |
| auto found = replacePHIs->find(*done[edge].begin()); |
| if (found == replacePHIs->end()) |
| continue; |
| if (&*BuilderM.GetInsertPoint() == found->second) { |
| if (found->second->getNextNode()) |
| BuilderM.SetInsertPoint(found->second->getNextNode()); |
| else |
| BuilderM.SetInsertPoint(found->second->getParent()); |
| } |
| found->second->replaceAllUsesWith(val); |
| found->second->eraseFromParent(); |
| } else { |
| otherBranch = val; |
| } |
| } |
| |
| for (unsigned i = 0; i < 2; ++i) { |
| auto edge = std::make_pair(subblock, bi2->getSuccessor(i)); |
| auto found = replacePHIs->find(*done[edge].begin()); |
| if (found == replacePHIs->end()) |
| continue; |
| |
| Value *val = cond2; |
| if (i == 1) |
| val = BuilderM.CreateNot(val, "bnot1_"); |
| val = BuilderM.CreateAnd(val, otherBranch, "andVal" + Twine(i)); |
| if (&*BuilderM.GetInsertPoint() == found->second) { |
| if (found->second->getNextNode()) |
| BuilderM.SetInsertPoint(found->second->getNextNode()); |
| else |
| BuilderM.SetInsertPoint(found->second->getParent()); |
| } |
| found->second->replaceAllUsesWith(val); |
| found->second->eraseFromParent(); |
| } |
| } |
| |
| return; |
| } |
| } |
| rnextpair:; |
| } |
| } |
| |
| BasicBlock *forwardBlock = BuilderM.GetInsertBlock(); |
| |
| if (!isOriginalBlock(*forwardBlock)) { |
| forwardBlock = originalForReverseBlock(*forwardBlock); |
| } |
| |
| for (auto block : blocks) { |
| { |
| // The original split block must not have a parent with an edge |
| // to a block other than to itself, which can reach any targets. |
| if (!DT.dominates(block, ctx)) |
| for (auto P : predecessors(block)) { |
| for (auto S : successors(P)) { |
| if (S == block) |
| continue; |
| auto edge = std::make_pair(P, S); |
| if (done.find(edge) != done.end() && done[edge].size()) |
| goto nextpair; |
| } |
| } |
| |
| std::set<BasicBlock *> foundtargets; |
| for (BasicBlock *succ : successors(block)) { |
| auto edge = std::make_pair(block, succ); |
| if (done[edge].size() != 1) { |
| goto nextpair; |
| } |
| BasicBlock *target = *done[edge].begin(); |
| if (foundtargets.find(target) != foundtargets.end()) { |
| goto nextpair; |
| } |
| foundtargets.insert(target); |
| } |
| if (foundtargets.size() != targetToPreds.size()) { |
| goto nextpair; |
| } |
| |
| if (forwardBlock == block || DT.dominates(block, forwardBlock)) { |
| equivalentTerminator = block->getTerminator(); |
| goto fast; |
| } |
| } |
| nextpair:; |
| } |
| goto nofast; |
| |
| fast:; |
| assert(equivalentTerminator); |
| |
| if (auto branch = dyn_cast<BranchInst>(equivalentTerminator)) { |
| BasicBlock *block = equivalentTerminator->getParent(); |
| assert(branch->getCondition()); |
| |
| assert(branch->getCondition()->getType() == T); |
| |
| if (replacePHIs == nullptr) { |
| if (!(BuilderM.GetInsertBlock()->size() == 0 || |
| !isa<BranchInst>(BuilderM.GetInsertBlock()->back()))) { |
| llvm::errs() << "newFunc : " << *newFunc << "\n"; |
| llvm::errs() << "blk : " << *BuilderM.GetInsertBlock() << "\n"; |
| } |
| assert(BuilderM.GetInsertBlock()->size() == 0 || |
| !isa<BranchInst>(BuilderM.GetInsertBlock()->back())); |
| BuilderM.CreateCondBr( |
| lookupM(branch->getCondition(), BuilderM), |
| *done[std::make_pair(block, branch->getSuccessor(0))].begin(), |
| *done[std::make_pair(block, branch->getSuccessor(1))].begin()); |
| } else { |
| for (auto pair : *replacePHIs) { |
| Value *phi = lookupM(branch->getCondition(), BuilderM); |
| Value *val = nullptr; |
| if (pair.first == |
| *done[std::make_pair(block, branch->getSuccessor(0))].begin()) { |
| val = phi; |
| } else if (pair.first == |
| *done[std::make_pair(block, branch->getSuccessor(1))] |
| .begin()) { |
| val = BuilderM.CreateNot(phi); |
| } else { |
| llvm::errs() << *pair.first->getParent() << "\n"; |
| llvm::errs() << *pair.first << "\n"; |
| llvm::errs() << *branch << "\n"; |
| llvm_unreachable("unknown successor for replacephi"); |
| } |
| if (&*BuilderM.GetInsertPoint() == pair.second) { |
| if (pair.second->getNextNode()) |
| BuilderM.SetInsertPoint(pair.second->getNextNode()); |
| else |
| BuilderM.SetInsertPoint(pair.second->getParent()); |
| } |
| pair.second->replaceAllUsesWith(val); |
| pair.second->eraseFromParent(); |
| } |
| } |
| } else if (auto si = dyn_cast<SwitchInst>(equivalentTerminator)) { |
| BasicBlock *block = equivalentTerminator->getParent(); |
| |
| IRBuilder<> pbuilder(equivalentTerminator); |
| pbuilder.setFastMathFlags(getFast()); |
| |
| if (replacePHIs == nullptr) { |
| SwitchInst *swtch = BuilderM.CreateSwitch( |
| lookupM(si->getCondition(), BuilderM), |
| *done[std::make_pair(block, si->getDefaultDest())].begin()); |
| for (auto switchcase : si->cases()) { |
| swtch->addCase( |
| switchcase.getCaseValue(), |
| *done[std::make_pair(block, switchcase.getCaseSuccessor())] |
| .begin()); |
| } |
| } else { |
| for (auto pair : *replacePHIs) { |
| Value *cas = nullptr; |
| for (auto c : si->cases()) { |
| if (pair.first == |
| *done[std::make_pair(block, c.getCaseSuccessor())].begin()) { |
| cas = c.getCaseValue(); |
| break; |
| } |
| } |
| if (cas == nullptr) { |
| assert(pair.first == |
| *done[std::make_pair(block, si->getDefaultDest())].begin()); |
| } |
| Value *val = nullptr; |
| Value *phi = lookupM(si->getCondition(), BuilderM); |
| |
| if (cas) { |
| val = BuilderM.CreateICmpEQ(cas, phi); |
| } else { |
| // default case |
| val = ConstantInt::getFalse(pair.second->getContext()); |
| for (auto switchcase : si->cases()) { |
| val = BuilderM.CreateOr( |
| val, BuilderM.CreateICmpEQ(switchcase.getCaseValue(), phi)); |
| } |
| val = BuilderM.CreateNot(val); |
| } |
| if (&*BuilderM.GetInsertPoint() == pair.second) { |
| if (pair.second->getNextNode()) |
| BuilderM.SetInsertPoint(pair.second->getNextNode()); |
| else |
| BuilderM.SetInsertPoint(pair.second->getParent()); |
| } |
| pair.second->replaceAllUsesWith(val); |
| pair.second->eraseFromParent(); |
| } |
| } |
| } else { |
| llvm::errs() << "unknown equivalent terminator\n"; |
| llvm::errs() << *equivalentTerminator << "\n"; |
| llvm_unreachable("unknown equivalent terminator"); |
| } |
| return; |
| |
| nofast:; |
| |
| // if freeing reverseblocks must exist |
| assert(reverseBlocks.size()); |
| LimitContext lctx(/*ReverseLimit*/ reverseBlocks.size() > 0, ctx); |
| AllocaInst *cache = createCacheForScope(lctx, T, "", /*shouldFree*/ true); |
| SmallVector<BasicBlock *, 4> targets; |
| { |
| size_t idx = 0; |
| std::map<BasicBlock * /*storingblock*/, |
| std::map<ConstantInt * /*target*/, |
| std::vector<BasicBlock *> /*predecessors*/>> |
| storing; |
| for (const auto &pair : targetToPreds) { |
| for (auto pred : pair.second) { |
| storing[pred.first][ConstantInt::get(T, idx)].push_back(pred.second); |
| } |
| targets.push_back(pair.first); |
| ++idx; |
| } |
| assert(targets.size() > 0); |
| |
| for (const auto &pair : storing) { |
| IRBuilder<> pbuilder(pair.first); |
| |
| if (hasTerminator(pair.first)) |
| pbuilder.SetInsertPoint(pair.first->getTerminator()); |
| |
| pbuilder.setFastMathFlags(getFast()); |
| |
| Value *tostore = ConstantInt::get(T, 0); |
| |
| if (pair.second.size() == 1) { |
| tostore = pair.second.begin()->first; |
| } else { |
| assert(0 && "multi exit edges not supported"); |
| exit(1); |
| // for(auto targpair : pair.second) { |
| // tostore = pbuilder.CreateOr(tostore, pred); |
| //} |
| } |
| storeInstructionInCache(lctx, pbuilder, tostore, cache); |
| } |
| } |
| |
| bool isi1 = T->isIntegerTy() && cast<IntegerType>(T)->getBitWidth() == 1; |
| Value *which = lookupValueFromCache( |
| T, |
| /*forwardPass*/ isOriginalBlock(*BuilderM.GetInsertBlock()), BuilderM, |
| LimitContext(/*reversePass*/ reverseBlocks.size() > 0, ctx), cache, isi1, |
| /*available*/ ValueToValueMapTy()); |
| assert(which); |
| assert(which->getType() == T); |
| |
| if (replacePHIs == nullptr) { |
| if (targetToPreds.size() == 2) { |
| assert(BuilderM.GetInsertBlock()->size() == 0 || |
| !isa<BranchInst>(BuilderM.GetInsertBlock()->back())); |
| BuilderM.CreateCondBr(which, /*true*/ targets[1], /*false*/ targets[0]); |
| } else { |
| assert(targets.size() > 0); |
| auto swit = |
| BuilderM.CreateSwitch(which, targets.back(), targets.size() - 1); |
| for (unsigned i = 0; i < targets.size() - 1; ++i) { |
| swit->addCase(ConstantInt::get(T, i), targets[i]); |
| } |
| } |
| } else { |
| for (unsigned i = 0; i < targets.size(); ++i) { |
| auto found = replacePHIs->find(targets[i]); |
| if (found == replacePHIs->end()) |
| continue; |
| |
| Value *val = nullptr; |
| if (targets.size() == 2 && i == 0) { |
| val = BuilderM.CreateNot(which); |
| } else if (targets.size() == 2 && i == 1) { |
| val = which; |
| } else { |
| val = BuilderM.CreateICmpEQ(ConstantInt::get(T, i), which); |
| } |
| if (&*BuilderM.GetInsertPoint() == found->second) { |
| if (found->second->getNextNode()) |
| BuilderM.SetInsertPoint(found->second->getNextNode()); |
| else |
| BuilderM.SetInsertPoint(found->second->getParent()); |
| } |
| found->second->replaceAllUsesWith(val); |
| found->second->eraseFromParent(); |
| } |
| } |
| return; |
| } |
| |
| void GradientUtils::computeMinCache() { |
| if (EnzymeMinCutCache) { |
| SetVector<Value *> Recomputes; |
| |
| std::map<UsageKey, bool> FullSeen; |
| std::map<UsageKey, bool> OneLevelSeen; |
| |
| ValueToValueMapTy Available; |
| |
| std::map<Loop *, std::set<Instruction *>> LoopAvail; |
| |
| for (BasicBlock &BB : *oldFunc) { |
| if (notForAnalysis.count(&BB)) |
| continue; |
| auto L = OrigLI->getLoopFor(&BB); |
| |
| auto invariant = [&](Value *V) { |
| if (isa<Constant>(V)) |
| return true; |
| if (isa<Argument>(V)) |
| return true; |
| if (auto I = dyn_cast<Instruction>(V)) { |
| if (!L->contains(OrigLI->getLoopFor(I->getParent()))) |
| return true; |
| } |
| return false; |
| }; |
| for (Instruction &I : BB) { |
| if (auto PN = dyn_cast<PHINode>(&I)) { |
| if (!OrigLI->isLoopHeader(&BB)) |
| continue; |
| if (PN->getType()->isIntegerTy()) { |
| bool legal = true; |
| SmallPtrSet<Instruction *, 4> Increment; |
| for (auto B : PN->blocks()) { |
| if (OrigLI->getLoopFor(B) == L) { |
| if (auto BO = dyn_cast<BinaryOperator>( |
| PN->getIncomingValueForBlock(B))) { |
| if (BO->getOpcode() == BinaryOperator::Add) { |
| if ((BO->getOperand(0) == PN && |
| invariant(BO->getOperand(1))) || |
| (BO->getOperand(1) == PN && |
| invariant(BO->getOperand(0)))) { |
| Increment.insert(BO); |
| } else { |
| legal = false; |
| } |
| } else if (BO->getOpcode() == BinaryOperator::Sub) { |
| if (BO->getOperand(0) == PN && |
| invariant(BO->getOperand(1))) { |
| Increment.insert(BO); |
| } else { |
| legal = false; |
| } |
| } else { |
| legal = false; |
| } |
| } else { |
| legal = false; |
| } |
| } |
| } |
| if (legal) { |
| LoopAvail[L].insert(PN); |
| for (auto I : Increment) |
| LoopAvail[L].insert(I); |
| } |
| } |
| } else if (auto CI = dyn_cast<CallInst>(&I)) { |
| StringRef funcName = getFuncNameFromCall(CI); |
| if (isAllocationFunction(funcName, TLI)) { |
| bool legal = true; |
| auto found = rematerializableAllocations.find(CI); |
| if (found != rematerializableAllocations.end()) { |
| if (found->second.nonRepeatableWritingCall) |
| legal = false; |
| } |
| if (legal) |
| Available[CI] = CI; |
| } |
| } |
| } |
| } |
| |
| SmallPtrSet<Instruction *, 3> NewLoopBoundReq; |
| { |
| std::deque<Instruction *> LoopBoundRequirements; |
| |
| for (auto &context : loopContexts) { |
| for (auto val : {context.second.maxLimit, context.second.trueLimit}) { |
| if (val) |
| if (auto inst = dyn_cast<Instruction>(&*val)) { |
| LoopBoundRequirements.push_back(inst); |
| } |
| } |
| } |
| SmallPtrSet<Instruction *, 3> Seen; |
| while (LoopBoundRequirements.size()) { |
| Instruction *val = LoopBoundRequirements.front(); |
| LoopBoundRequirements.pop_front(); |
| if (NewLoopBoundReq.count(val)) |
| continue; |
| if (Seen.count(val)) |
| continue; |
| Seen.insert(val); |
| if (auto orig = isOriginal(val)) { |
| NewLoopBoundReq.insert(orig); |
| } else { |
| for (auto &op : val->operands()) { |
| if (auto inst = dyn_cast<Instruction>(op)) { |
| LoopBoundRequirements.push_back(inst); |
| } |
| } |
| } |
| } |
| for (auto inst : NewLoopBoundReq) { |
| OneLevelSeen[UsageKey(inst, QueryType::Primal)] = true; |
| FullSeen[UsageKey(inst, QueryType::Primal)] = true; |
| } |
| } |
| |
| auto minCutMode = (mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeCombined) |
| ? DerivativeMode::ReverseModeGradient |
| : mode; |
| |
| for (BasicBlock &BB : *oldFunc) { |
| if (notForAnalysis.count(&BB)) |
| continue; |
| ValueToValueMapTy Available2; |
| for (auto a : Available) |
| Available2[a.first] = a.second; |
| for (Loop *L = OrigLI->getLoopFor(&BB); L != nullptr; |
| L = L->getParentLoop()) { |
| for (auto v : LoopAvail[L]) { |
| Available2[v] = v; |
| } |
| } |
| for (Instruction &I : BB) { |
| if (!legalRecompute(&I, Available2, nullptr)) { |
| if (DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Primal>(this, &I, minCutMode, FullSeen, |
| notForAnalysis)) { |
| bool oneneed = DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Primal, |
| /*OneLevel*/ true>(this, &I, minCutMode, OneLevelSeen, |
| notForAnalysis); |
| |
| bool shadowOneNeed = false; |
| // even if the primal is not needed directly by its users, if the |
| // primal is constant and used to create a shadow insertvalue which |
| // is used, we need to save the shadow since shadow cache and primal |
| // cache are the same, we force a save of cache here. |
| // TODO(wsmoses): extend this to separate caching decisions for |
| // primal and shadow |
| if (!oneneed && isConstantValue(&I) && !TR.allFloat(&I)) { |
| SmallVector<Instruction *, 1> todo; |
| todo.push_back(&I); |
| while (todo.size()) { |
| auto cur = todo.pop_back_val(); |
| for (auto u : cur->users()) { |
| if (isa<InsertValueInst>(u) || isa<InsertElementInst>(u) || |
| isa<ExtractValueInst>(u) || isa<ExtractElementInst>(u)) { |
| auto I2 = cast<Instruction>(u); |
| if (!isConstantValue(I2)) { |
| if (DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Shadow>(this, I2, minCutMode, FullSeen, |
| notForAnalysis)) { |
| shadowOneNeed = true; |
| goto endOneNeed; |
| } |
| } else { |
| todo.push_back(I2); |
| } |
| } |
| } |
| } |
| endOneNeed:; |
| } |
| |
| if (oneneed || shadowOneNeed) { |
| knownRecomputeHeuristic[&I] = false; |
| |
| CountTrackedPointers T(I.getType()); |
| assert(!T.derived); |
| } else { |
| Recomputes.insert(&I); |
| } |
| } |
| } |
| } |
| } |
| |
| SetVector<Value *> Intermediates; |
| SetVector<Value *> Required; |
| std::deque<Value *> todo(Recomputes.begin(), Recomputes.end()); |
| |
| while (todo.size()) { |
| Value *V = todo.front(); |
| todo.pop_front(); |
| if (Intermediates.count(V)) |
| continue; |
| bool multiLevel = DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Primal>(this, V, minCutMode, FullSeen, notForAnalysis); |
| if (!multiLevel) { |
| continue; |
| } |
| if (!Recomputes.count(V)) { |
| ValueToValueMapTy Available2; |
| for (auto a : Available) |
| Available2[a.first] = a.second; |
| for (Loop *L = OrigLI->getLoopFor(cast<Instruction>(V)->getParent()); |
| L != nullptr; L = L->getParentLoop()) { |
| for (auto v : LoopAvail[L]) { |
| Available2[v] = v; |
| } |
| } |
| if (!legalRecompute(V, Available2, nullptr)) { |
| // if not legal to recompute, we would've already explicitly marked |
| // this for caching if it was needed in reverse pass |
| continue; |
| } |
| } |
| Intermediates.insert(V); |
| bool singleLevel = DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Primal, /*OneLevel*/ true>(this, V, minCutMode, |
| OneLevelSeen, notForAnalysis); |
| if (singleLevel) { |
| Required.insert(V); |
| } else { |
| DifferentialUseAnalysis::forEachDifferentialUser( |
| [&](Value *V2) { todo.push_back(V2); }, this, V); |
| } |
| } |
| |
| SetVector<Value *> MinReq; |
| DifferentialUseAnalysis::minCut(oldFunc->getParent()->getDataLayout(), |
| *OrigLI, Recomputes, Intermediates, |
| Required, MinReq, this, TLI); |
| SmallPtrSet<Value *, 5> NeedGraph; |
| |
| // The start value of a loopy reduction is needed in the reverse pass, but |
| // is only discovered now that the min cut is known. Since the cut has |
| // already been computed, such a value can no longer be selected for |
| // caching -- everything placed into Intermediates from here on will be |
| // marked as recomputed below. Only add it if that is legal; if it is not, |
| // it has already been given an explicit cache decision by the scan above, |
| // which must not be overwritten. Either way push it onto the worklist so |
| // the need graph stays complete. |
| auto pushLoopyPHIPreheader = [&](Value *V) { |
| SmallVector<Value *, 2> preheaderVals; |
| DifferentialUseAnalysis::pushLoopyPHIPreheader(this, V, preheaderVals); |
| for (Value *PV : preheaderVals) { |
| ValueToValueMapTy Available2; |
| for (auto a : Available) |
| Available2[a.first] = a.second; |
| for (Loop *L = OrigLI->getLoopFor(cast<Instruction>(PV)->getParent()); |
| L != nullptr; L = L->getParentLoop()) { |
| for (auto v : LoopAvail[L]) { |
| Available2[v] = v; |
| } |
| } |
| if (legalRecompute(PV, Available2, nullptr)) |
| Intermediates.insert(PV); |
| todo.push_back(PV); |
| } |
| }; |
| |
| for (Value *V : MinReq) { |
| NeedGraph.insert(V); |
| pushLoopyPHIPreheader(V); |
| } |
| for (Value *V : Required) { |
| todo.push_back(V); |
| } |
| while (todo.size()) { |
| Value *V = todo.front(); |
| todo.pop_front(); |
| if (NeedGraph.count(V)) |
| continue; |
| NeedGraph.insert(V); |
| pushLoopyPHIPreheader(V); |
| auto I = dyn_cast<Instruction>(V); |
| if (!I) |
| continue; |
| for (auto &V2 : I->operands()) { |
| if (Intermediates.count(V2)) { |
| todo.push_back(V2); |
| } |
| } |
| auto found = rematerializableAllocations.find(I); |
| if (found != rematerializableAllocations.end()) { |
| for (auto store : found->second.stores) { |
| for (auto &operand : store->operands()) { |
| if (Intermediates.count(operand)) { |
| todo.push_back(operand); |
| } |
| } |
| } |
| } |
| } |
| |
| for (auto V : Intermediates) { |
| knownRecomputeHeuristic[V] = !MinReq.count(V); |
| if (!MinReq.count(V) && NeedGraph.count(V)) { |
| if (auto CI = dyn_cast<CallInst>(V)) |
| if (getFuncNameFromCall(CI) == "julia.call") |
| assert(0); |
| |
| ValueToValueMapTy Available2; |
| for (auto a : Available) |
| Available2[a.first] = a.second; |
| for (Loop *L = OrigLI->getLoopFor(cast<Instruction>(V)->getParent()); |
| L != nullptr; L = L->getParentLoop()) { |
| for (auto v : LoopAvail[L]) { |
| Available2[v] = v; |
| } |
| } |
| assert(legalRecompute(V, Available2, nullptr)); |
| } |
| if (!NeedGraph.count(V)) { |
| assert(!MinReq.count(V)); |
| unnecessaryIntermediates.insert(cast<Instruction>(V)); |
| } |
| |
| if (NeedGraph.count(V) && MinReq.count(V)) { |
| CountTrackedPointers T(V->getType()); |
| if (T.derived) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Illegal cached pointer: " << *V << "\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap((Value *)V), |
| ErrorType::InternalError, nullptr, nullptr, |
| nullptr); |
| } else { |
| EmitFailure( |
| "CachedPointerError", cast<Instruction>(V)->getDebugLoc(), |
| cast<Instruction>(V)->getParent()->getParent(), ss.str()); |
| } |
| } |
| } |
| } |
| } |
| if (rematerializableAllocations.size()) { |
| |
| // We iterate through the instructions here to ensure a consistent order for |
| // the analysis results. |
| for (auto &BB : *oldFunc) { |
| for (auto &I : BB) { |
| auto found = rematerializableAllocations.find(&I); |
| if (found == rematerializableAllocations.end()) |
| continue; |
| std::map<UsageKey, bool> Seen = populateSeenFromKnownRecompute(); |
| bool primalNeededInReverse = |
| DifferentialUseAnalysis::is_value_needed_in_reverse< |
| QueryType::Primal>(this, &I, mode, Seen, notForAnalysis); |
| |
| { |
| auto found = knownRecomputeHeuristic.find(&I); |
| if (found != knownRecomputeHeuristic.end() && !found->second) { |
| primalNeededInReverse = true; |
| } |
| } |
| |
| if (primalNeededInReverse && !needsCacheWholeAllocation(&I)) { |
| allocationsToBeRematerialized.insert(&I); |
| } |
| } |
| } |
| } |
| } |
| |
| bool GradientUtils::isOriginalBlock(const BasicBlock &BB) const { |
| for (auto A : originalBlocks) { |
| if (A == &BB) |
| return true; |
| } |
| return false; |
| } |
| |
| void GradientUtils::eraseFictiousPHIs() { |
| { |
| for (auto P : rematerializedPrimalOrShadowAllocations) { |
| Value *replacement = |
| getUndefinedValueForType(*oldFunc->getParent(), P->getType()); |
| P->replaceAllUsesWith(replacement); |
| erase(P); |
| } |
| } |
| SmallVector<std::pair<PHINode *, Value *>, 4> phis; |
| for (auto pair : fictiousPHIs) |
| phis.emplace_back(pair.first, pair.second); |
| fictiousPHIs.clear(); |
| |
| for (auto pair : phis) { |
| auto pp = pair.first; |
| if (pp->getNumUses() != 0) { |
| bool skip = false; |
| assert(isa<Instruction>(pair.second)); |
| auto *I = dyn_cast<Instruction>(pair.second); |
| if (!OrigDT->isReachableFromEntry(I->getParent())) { |
| skip = true; |
| } |
| |
| if (!skip) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Illegal replace ficticious phi for: " << *pp << " of " |
| << *pair.second << "\n"; |
| for (auto U : pp->users()) { |
| ss << " user: " << *U << "\n"; |
| } |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap(pp), ErrorType::InternalError, |
| nullptr, nullptr, nullptr); |
| } else { |
| ss << " newFunc:\n" << *newFunc << "\n"; |
| EmitFailure("IllegalReplacePHI", I->getDebugLoc(), I, str); |
| } |
| } |
| Value *replacement = |
| getUndefinedValueForType(*oldFunc->getParent(), pp->getType()); |
| pp->replaceAllUsesWith(replacement); |
| } |
| erase(pp); |
| } |
| } |
| |
| void GradientUtils::forceActiveDetection() { |
| |
| TimeTraceScope timeScope("Activity Analysis", oldFunc->getName()); |
| |
| for (auto &Arg : oldFunc->args()) { |
| ATA->isConstantValue(TR, &Arg); |
| } |
| |
| for (BasicBlock &BB : *oldFunc) { |
| for (Instruction &I : BB) { |
| bool const_inst = ATA->isConstantInstruction(TR, &I); |
| bool const_value = ATA->isConstantValue(TR, &I); |
| if (EnzymePrintActivity) |
| llvm::errs() << I << " cv=" << const_value << " ci=" << const_inst |
| << "\n"; |
| } |
| } |
| } |
| |
| bool GradientUtils::isConstantValue(Value *val) const { |
| if (auto inst = dyn_cast<Instruction>(val)) { |
| (void)inst; |
| assert(inst->getParent()->getParent() == oldFunc); |
| return ATA->isConstantValue(TR, val); |
| } |
| |
| if (auto arg = dyn_cast<Argument>(val)) { |
| (void)arg; |
| assert(arg->getParent() == oldFunc); |
| return ATA->isConstantValue(TR, val); |
| } |
| |
| //! Functions must be false so we can replace function with augmentation, |
| //! fallback to analysis |
| if (isa<Function>(val) || isa<InlineAsm>(val) || isa<Constant>(val) || |
| isa<UndefValue>(val) || isa<MetadataAsValue>(val)) { |
| // llvm::errs() << "calling icv on: " << *val << "\n"; |
| return ATA->isConstantValue(TR, val); |
| } |
| |
| if (auto gv = dyn_cast<GlobalVariable>(val)) { |
| if (hasMetadata(gv, "enzyme_shadow")) |
| return false; |
| if (auto md = gv->getMetadata("enzyme_activity_value")) { |
| auto res = cast<MDString>(md->getOperand(0))->getString(); |
| if (res == "const") |
| return true; |
| if (res == "active") |
| return false; |
| } |
| if (EnzymeNonmarkedGlobalsInactive) |
| return true; |
| goto err; |
| } |
| if (isa<GlobalValue>(val)) { |
| if (EnzymeNonmarkedGlobalsInactive) |
| return true; |
| goto err; |
| } |
| |
| err:; |
| llvm::errs() << *oldFunc << "\n"; |
| llvm::errs() << *newFunc << "\n"; |
| llvm::errs() << *val << "\n"; |
| llvm::errs() << " unknown did status attribute\n"; |
| assert(0 && "bad"); |
| exit(1); |
| } |
| |
| bool GradientUtils::isConstantInstruction(const Instruction *inst) const { |
| assert(inst->getParent()->getParent() == oldFunc); |
| return ATA->isConstantInstruction(TR, const_cast<Instruction *>(inst)); |
| } |
| |
| bool GradientUtils::getContext(llvm::BasicBlock *BB, LoopContext &lc) { |
| return CacheUtility::getContext(BB, lc, |
| /*ReverseLimit*/ reverseBlocks.size() > 0); |
| } |
| |
| void GradientUtils::forceAugmentedReturns() { |
| assert(TR.getFunction() == oldFunc); |
| |
| // Pass 1: create BB-level contexts for the whole loop/function |
| for (BasicBlock &oBB : *oldFunc) { |
| if (notForAnalysis.find(&oBB) != notForAnalysis.end()) |
| continue; |
| LoopContext LC; |
| getContext(cast<BasicBlock>(getNewFromOriginal(&oBB)), LC); |
| } |
| |
| // Pass 2: instruction processing |
| for (BasicBlock &oBB : *oldFunc) { |
| if (notForAnalysis.find(&oBB) != notForAnalysis.end()) |
| continue; |
| |
| for (Instruction &I : oBB) { |
| Instruction *inst = &I; |
| |
| if (inst->getType()->isEmptyTy() || inst->getType()->isVoidTy()) |
| continue; |
| |
| if (mode == DerivativeMode::ForwardMode || |
| mode == DerivativeMode::ForwardModeSplit || |
| mode == DerivativeMode::ForwardModeError) { |
| if (!isConstantValue(inst)) { |
| IRBuilder<> BuilderZ(inst); |
| getForwardBuilder(BuilderZ); |
| #if LLVM_VERSION_MAJOR >= 18 |
| auto It = BuilderZ.GetInsertPoint(); |
| It.setHeadBit(true); |
| BuilderZ.SetInsertPoint(It); |
| #endif |
| Type *antiTy = getShadowType(inst->getType()); |
| PHINode *anti = |
| BuilderZ.CreatePHI(antiTy, 1, inst->getName() + "'dual_phi"); |
| invertedPointers.insert(std::make_pair( |
| (const Value *)inst, InvertedPointerVH(this, anti))); |
| } |
| continue; |
| } |
| |
| if (inst->getType()->isFPOrFPVectorTy()) |
| continue; //! op->getType()->isPointerTy() && |
| //! !op->getType()->isIntegerTy()) { |
| |
| if (!TR.query(inst)[{-1}].isPossiblePointer()) |
| continue; |
| |
| if (isa<LoadInst>(inst)) { |
| IRBuilder<> BuilderZ(inst); |
| getForwardBuilder(BuilderZ); |
| #if LLVM_VERSION_MAJOR >= 18 |
| auto It = BuilderZ.GetInsertPoint(); |
| It.setHeadBit(true); |
| BuilderZ.SetInsertPoint(It); |
| #endif |
| Type *antiTy = getShadowType(inst->getType()); |
| PHINode *anti = |
| BuilderZ.CreatePHI(antiTy, 1, inst->getName() + "'il_phi"); |
| invertedPointers.insert( |
| std::make_pair((const Value *)inst, InvertedPointerVH(this, anti))); |
| continue; |
| } |
| |
| if (!isa<CallInst>(inst)) { |
| continue; |
| } |
| |
| CallInst *op = cast<CallInst>(inst); |
| Function *called = op->getCalledFunction(); |
| |
| if ((mode == DerivativeMode::ReverseModeGradient || |
| mode == DerivativeMode::ReverseModeCombined) && |
| called && called->getName() == "llvm.julia.gc_preserve_begin") { |
| IRBuilder<> BuilderZ(inst); |
| getForwardBuilder(BuilderZ); |
| auto anti = BuilderZ.CreateCall(called, ArrayRef<Value *>(), |
| op->getName() + "'ip"); |
| anti->setDebugLoc(getNewFromOriginal(op->getDebugLoc())); |
| invertedPointers.insert( |
| std::make_pair((const Value *)inst, InvertedPointerVH(this, anti))); |
| continue; |
| } |
| |
| if (isa<IntrinsicInst>(inst)) { |
| continue; |
| } |
| |
| if (isConstantValue(inst)) { |
| continue; |
| } |
| |
| IRBuilder<> BuilderZ(inst); |
| getForwardBuilder(BuilderZ); |
| #if LLVM_VERSION_MAJOR >= 18 |
| auto It = BuilderZ.GetInsertPoint(); |
| It.setHeadBit(true); |
| BuilderZ.SetInsertPoint(It); |
| #endif |
| |
| // Shadow allocations must strictly preceede the primal, lest Julia have |
| // GC issues. Consider the following: %r = gc_alloc() init %r |
| // ... |
| // if the shadow did not preceed |
| // %r = gc_alloc() |
| // %dr = gc_alloc() |
| // zero %dr |
| // init %r, %dr |
| // ... |
| // After %r, before %dr the %r memory would be uninit, so the allocator |
| // inside %dr would hit garbage and segfault. However, by having the %dr |
| // first, then it will be zero'd before the %r allocation, preventing the |
| // issue. |
| if (isAllocationCall(inst, TLI)) { |
| BuilderZ.SetInsertPoint(getNewFromOriginal(inst)); |
| #if LLVM_VERSION_MAJOR >= 18 |
| auto It = BuilderZ.GetInsertPoint(); |
| It.setHeadBit(true); |
| BuilderZ.SetInsertPoint(It); |
| #endif |
| } |
| Type *antiTy = getShadowType(inst->getType()); |
| |
| PHINode *anti = BuilderZ.CreatePHI(antiTy, 1, op->getName() + "'ip_phi"); |
| if (hasNoCache(inst)) { |
| anti->setMetadata("enzyme_nocache", |
| MDNode::get(inst->getContext(), {})); |
| } |
| anti->setDebugLoc(getNewFromOriginal(op->getDebugLoc())); |
| invertedPointers.insert( |
| std::make_pair((const Value *)inst, InvertedPointerVH(this, anti))); |
| |
| if (isAllocationCall(inst, TLI)) { |
| anti->setName(op->getName() + "'mi"); |
| } |
| } |
| } |
| } |
| |
| void InvertedPointerVH::deleted() { |
| llvm::errs() << *gutils->oldFunc << "\n"; |
| llvm::errs() << *gutils->newFunc << "\n"; |
| gutils->dumpPointers(); |
| llvm::errs() << **this << "\n"; |
| assert(0 && "erasing something in invertedPointers map"); |
| } |
| |
| void SubTransferHelper(GradientUtils *gutils, DerivativeMode mode, |
| Type *secretty, Intrinsic::ID intrinsic, |
| unsigned dstalign, unsigned srcalign, unsigned offset, |
| bool dstConstant, Value *shadow_dst, Value *primal_dst, |
| bool srcConstant, Value *shadow_src, Value *primal_src, |
| Value *length, Value *isVolatile, llvm::CallInst *MTI, |
| bool allowForward, bool shadowsLookedUp, |
| bool backwardsShadow) { |
| // TODO offset |
| if (secretty) { |
| // no change to forward pass if represents floats |
| if (mode == DerivativeMode::ReverseModeGradient || |
| mode == DerivativeMode::ReverseModeCombined || |
| mode == DerivativeMode::ForwardModeSplit) { |
| IRBuilder<> Builder2(MTI); |
| if (mode == DerivativeMode::ForwardModeSplit) |
| gutils->getForwardBuilder(Builder2); |
| else |
| gutils->getReverseBuilder(Builder2); |
| |
| // If the src is constant simply zero d_dst and don't propagate to d_src |
| // (which thus == src and may be illegal) |
| if (srcConstant) { |
| // Don't zero in forward mode. |
| if (mode != DerivativeMode::ForwardModeSplit) { |
| |
| Value *args[] = { |
| shadowsLookedUp ? shadow_dst |
| : gutils->lookupM(shadow_dst, Builder2), |
| ConstantInt::get(Type::getInt8Ty(MTI->getContext()), 0), |
| gutils->lookupM(length, Builder2), |
| ConstantInt::getFalse(MTI->getContext())}; |
| |
| if (args[0]->getType()->isIntegerTy()) |
| args[0] = Builder2.CreateIntToPtr(args[0], |
| getInt8PtrTy(MTI->getContext())); |
| |
| Type *tys[] = {args[0]->getType(), args[2]->getType()}; |
| auto memsetIntr = getIntrinsicDeclaration( |
| MTI->getParent()->getParent()->getParent(), Intrinsic::memset, |
| tys); |
| auto cal = Builder2.CreateCall(memsetIntr, args); |
| cal->setCallingConv(memsetIntr->getCallingConv()); |
| if (dstalign != 0) { |
| cal->addParamAttr(0, Attribute::getWithAlignment(MTI->getContext(), |
| Align(dstalign))); |
| } |
| } |
| |
| } else { |
| auto dsto = |
| (shadowsLookedUp || mode == DerivativeMode::ForwardModeSplit) |
| ? shadow_dst |
| : gutils->lookupM(shadow_dst, Builder2); |
| Value *dst_inactive = nullptr; |
| if (gutils->runtimeActivity) { |
| if (primal_dst) { |
| Value *primal_dsto = |
| (shadowsLookedUp || mode == DerivativeMode::ForwardModeSplit) |
| ? primal_dst |
| : gutils->lookupM(primal_dst, Builder2); |
| dst_inactive = Builder2.CreateICmpEQ(dsto, primal_dsto); |
| } else { |
| dst_inactive = ConstantInt::getFalse(Builder2.getContext()); |
| } |
| } |
| if (dsto->getType()->isIntegerTy()) |
| dsto = |
| Builder2.CreateIntToPtr(dsto, getInt8PtrTy(dsto->getContext())); |
| unsigned dstaddr = |
| cast<PointerType>(dsto->getType())->getAddressSpace(); |
| if (offset != 0) { |
| dsto = Builder2.CreateConstInBoundsGEP1_64( |
| Type::getInt8Ty(dsto->getContext()), dsto, offset); |
| } |
| auto srco = |
| (shadowsLookedUp || mode == DerivativeMode::ForwardModeSplit) |
| ? shadow_src |
| : gutils->lookupM(shadow_src, Builder2); |
| Value *src_inactive = nullptr; |
| if (gutils->runtimeActivity) { |
| if (srcConstant) { |
| src_inactive = ConstantInt::getTrue(Builder2.getContext()); |
| } else { |
| Value *primal_srco = |
| (shadowsLookedUp || mode == DerivativeMode::ForwardModeSplit) |
| ? primal_src |
| : gutils->lookupM(primal_src, Builder2); |
| src_inactive = Builder2.CreateICmpEQ(srco, primal_srco); |
| } |
| } |
| if (mode != DerivativeMode::ForwardModeSplit) |
| dsto = Builder2.CreatePointerCast(dsto, |
| getPointerType(secretty, dstaddr)); |
| if (srco->getType()->isIntegerTy()) |
| srco = |
| Builder2.CreateIntToPtr(srco, getInt8PtrTy(srco->getContext())); |
| unsigned srcaddr = |
| cast<PointerType>(srco->getType())->getAddressSpace(); |
| if (offset != 0) { |
| srco = Builder2.CreateConstInBoundsGEP1_64( |
| Type::getInt8Ty(srco->getContext()), srco, offset); |
| } |
| if (mode != DerivativeMode::ForwardModeSplit) |
| srco = Builder2.CreatePointerCast(srco, |
| getPointerType(secretty, srcaddr)); |
| |
| if (mode == DerivativeMode::ForwardModeSplit) { |
| MaybeAlign dalign; |
| if (dstalign) |
| dalign = MaybeAlign(dstalign); |
| MaybeAlign salign; |
| if (srcalign) |
| salign = MaybeAlign(srcalign); |
| |
| if (intrinsic == Intrinsic::memmove) { |
| Builder2.CreateMemMove(dsto, dalign, srco, salign, length); |
| } else { |
| Builder2.CreateMemCpy(dsto, dalign, srco, salign, length); |
| } |
| } else { |
| SmallVector<Value *, 5> args = { |
| Builder2.CreatePointerCast(dsto, |
| getPointerType(secretty, dstaddr)), |
| Builder2.CreatePointerCast(srco, |
| getPointerType(secretty, srcaddr)), |
| Builder2.CreateUDiv( |
| gutils->lookupM(length, Builder2), |
| ConstantInt::get(length->getType(), |
| Builder2.GetInsertBlock() |
| ->getParent() |
| ->getParent() |
| ->getDataLayout() |
| .getTypeAllocSizeInBits(secretty) / |
| 8))}; |
| |
| if (gutils->runtimeActivity) { |
| args.push_back(dst_inactive); |
| args.push_back(src_inactive); |
| } |
| |
| auto dmemcpy = ((intrinsic == Intrinsic::memcpy) |
| ? getOrInsertDifferentialFloatMemcpy |
| : getOrInsertDifferentialFloatMemmove)( |
| *MTI->getParent()->getParent()->getParent(), secretty, dstalign, |
| srcalign, dstaddr, srcaddr, |
| cast<IntegerType>(length->getType())->getBitWidth(), |
| gutils->runtimeActivity, gutils->isAtomic(primal_src)); |
| Builder2.CreateCall(dmemcpy, args); |
| } |
| } |
| } |
| } else { |
| |
| // if represents pointer or integer type then only need to modify forward |
| // pass with the copy |
| if ((allowForward && (mode == DerivativeMode::ReverseModePrimal || |
| mode == DerivativeMode::ReverseModeCombined)) || |
| (backwardsShadow && (mode == DerivativeMode::ReverseModeGradient || |
| mode == DerivativeMode::ForwardModeSplit))) { |
| assert(!shadowsLookedUp); |
| |
| // It is questionable how the following case would even occur, but if |
| // the dst is constant, we shouldn't do anything extra |
| if (dstConstant) { |
| return; |
| } |
| |
| IRBuilder<> BuilderZ(gutils->getNewFromOriginal(MTI)); |
| |
| // If src is inactive, then we should copy from the regular pointer |
| // (i.e. suppose we are copying constant memory representing dimensions |
| // into a tensor) |
| // to ensure that the differential tensor is well formed for use |
| // OUTSIDE the derivative generation (as enzyme doesn't need this), we |
| // should also perform the copy onto the differential. Future |
| // Optimization (not implemented): If dst can never escape Enzyme code, |
| // we may omit this copy. |
| // no need to update pointers, even if dst is active |
| auto dsto = shadow_dst; |
| if (dsto->getType()->isIntegerTy()) |
| dsto = BuilderZ.CreateIntToPtr(dsto, getInt8PtrTy(MTI->getContext())); |
| if (offset != 0) { |
| dsto = BuilderZ.CreateConstInBoundsGEP1_64( |
| Type::getInt8Ty(dsto->getContext()), dsto, offset); |
| } |
| auto srco = shadow_src; |
| if (srco->getType()->isIntegerTy()) |
| srco = BuilderZ.CreateIntToPtr(srco, getInt8PtrTy(MTI->getContext())); |
| if (offset != 0) { |
| srco = BuilderZ.CreateConstInBoundsGEP1_64( |
| Type::getInt8Ty(srco->getContext()), srco, offset); |
| } |
| Value *args[] = {dsto, srco, length, isVolatile}; |
| |
| Type *tys[] = {args[0]->getType(), args[1]->getType(), |
| args[2]->getType()}; |
| |
| auto memtransIntr = |
| getIntrinsicDeclaration(gutils->newFunc->getParent(), intrinsic, tys); |
| auto cal = BuilderZ.CreateCall(memtransIntr, args); |
| cal->setAttributes(MTI->getAttributes()); |
| cal->setCallingConv(memtransIntr->getCallingConv()); |
| cal->setTailCallKind(MTI->getTailCallKind()); |
| |
| if (dstalign != 0) { |
| cal->addParamAttr( |
| 0, Attribute::getWithAlignment(MTI->getContext(), Align(dstalign))); |
| } |
| if (srcalign != 0) { |
| cal->addParamAttr( |
| 1, Attribute::getWithAlignment(MTI->getContext(), Align(srcalign))); |
| } |
| } |
| } |
| } |
| |
| void GradientUtils::computeForwardingProperties(Instruction *V) { |
| if (!EnzymeRematerialize) |
| return; |
| |
| // For the piece of memory V allocated within this scope, it will be |
| // initialized in some way by the (augmented) forward pass. Loads and other |
| // load-like operations will either require the allocation V itself to be |
| // preserved for the reverse pass, or alternatively the tape for those |
| // operations. |
| // |
| // Instead, we ask here whether or not we can restore the memory state of V in |
| // the reverse pass by recreating all of the stores and store-like operations |
| // into the V prior to their load-like uses. |
| // |
| // Notably, we only need to preserve the ability to reload any values actually |
| // used in the reverse pass. |
| |
| std::map<UsageKey, bool> Seen; |
| bool primalNeededInReverse = |
| DifferentialUseAnalysis::is_value_needed_in_reverse<QueryType::Primal>( |
| this, V, DerivativeMode::ReverseModeGradient, Seen, notForAnalysis); |
| |
| SmallVector<LoadInst *, 1> loads; |
| SmallVector<LoadLikeCall, 1> loadLikeCalls; |
| SmallPtrSet<Instruction *, 1> stores; |
| SmallPtrSet<Instruction *, 1> storingOps; |
| SmallPtrSet<Instruction *, 1> frees; |
| SmallPtrSet<IntrinsicInst *, 1> LifetimeStarts; |
| bool promotable = true; |
| bool shadowpromotable = true; |
| |
| CallInst *nonRepeatableWritingCall = nullptr; |
| SmallVector<Instruction *, 1> shadowPointerLoads; |
| |
| std::set<std::pair<Instruction *, Value *>> seen; |
| SmallVector<std::pair<Instruction *, Value *>, 1> todo; |
| for (auto U : V->users()) |
| if (auto I = dyn_cast<Instruction>(U)) |
| todo.push_back(std::make_pair(I, V)); |
| while (todo.size()) { |
| auto tup = todo.back(); |
| Instruction *cur = tup.first; |
| Value *prev = tup.second; |
| todo.pop_back(); |
| if (seen.count(tup)) |
| continue; |
| seen.insert(tup); |
| if (notForAnalysis.count(cur->getParent())) |
| continue; |
| if (isPointerArithmeticInst(cur)) { |
| for (auto u : cur->users()) { |
| if (auto I = dyn_cast<Instruction>(u)) |
| todo.push_back(std::make_pair(I, (Value *)cur)); |
| } |
| } else if (auto load = dyn_cast<LoadInst>(cur)) { |
| |
| // If loaded value is an int or pointer, may need |
| // to preserve initialization within the primal. |
| auto TT = TR.query(load)[{-1}]; |
| if (!TT.isFloat()) { |
| shadowPointerLoads.push_back(cur); |
| } |
| loads.push_back(load); |
| } else if (auto store = dyn_cast<StoreInst>(cur)) { |
| // TODO only add store to shadow iff non float type |
| if (store->getValueOperand() == prev) { |
| EmitWarning("NotPromotable", *cur, " Could not promote allocation ", *V, |
| " due to capturing store ", *cur); |
| promotable = false; |
| shadowpromotable = false; |
| break; |
| } else { |
| stores.insert(store); |
| storingOps.insert(store); |
| } |
| } else if (auto II = dyn_cast<IntrinsicInst>(cur)) { |
| if (II->getCalledFunction()->getName() == "llvm.enzyme.lifetime_start") { |
| LifetimeStarts.insert(II); |
| } else if (II->getCalledFunction()->getName() == |
| "llvm.enzyme.lifetime_end") { |
| } else { |
| switch (II->getIntrinsicID()) { |
| case Intrinsic::lifetime_start: |
| LifetimeStarts.insert(II); |
| break; |
| case Intrinsic::dbg_declare: |
| case Intrinsic::dbg_value: |
| case Intrinsic::dbg_label: |
| #if LLVM_VERSION_MAJOR <= 16 |
| case llvm::Intrinsic::dbg_addr: |
| #endif |
| case Intrinsic::lifetime_end: |
| break; |
| case Intrinsic::memset: { |
| stores.insert(II); |
| storingOps.insert(II); |
| break; |
| } |
| // TODO memtransfer(cpy/move) |
| case Intrinsic::memcpy: |
| case Intrinsic::memmove: |
| default: |
| promotable = false; |
| shadowpromotable = false; |
| EmitWarning("NotPromotable", *cur, " Could not promote allocation ", |
| *V, " due to unknown intrinsic ", *cur); |
| break; |
| } |
| } |
| } else if (auto CI = dyn_cast<CallInst>(cur)) { |
| StringRef funcName = getFuncNameFromCall(CI); |
| if (isDeallocationFunction(funcName, TLI)) { |
| frees.insert(CI); |
| continue; |
| } |
| // The allocation arg is the first arg of the write barrier. |
| // The capturing store in subsequent args should be handled by forbidding |
| // capturing stores |
| if (funcName == "julia.write_barrier" || |
| funcName == "julia.write_barrier_binding") { |
| if (CI->getArgOperand(0) == prev) { |
| stores.insert(CI); |
| } |
| continue; |
| } |
| if (funcName == "enzyme_zerotype") { |
| stores.insert(CI); |
| continue; |
| } |
| |
| size_t idx = 0; |
| bool seenLoadLikeCall = false; |
| #if LLVM_VERSION_MAJOR >= 14 |
| for (auto &arg : CI->args()) |
| #else |
| for (auto &arg : CI->arg_operands()) |
| #endif |
| { |
| if (arg != prev) { |
| idx++; |
| continue; |
| } |
| auto TT = TR.query(prev)[{-1, -1}]; |
| |
| bool NoCapture = isNoCapture(CI, idx); |
| |
| bool ReadOnly = isReadOnly(CI, idx); |
| |
| bool WriteOnly = isWriteOnly(CI, idx); |
| |
| // If the pointer is captured, conservatively assume it is used in |
| // nontrivial ways that make both the primal and shadow not promotable. |
| if (!NoCapture) { |
| shadowpromotable = false; |
| promotable = false; |
| EmitWarning("NotPromotable", *cur, " Could not promote allocation ", |
| *V, " due to unknown capturing call ", *cur, |
| " at idx=", idx, " prev=", *prev); |
| idx++; |
| continue; |
| } |
| |
| // From here on out we can assume the pointer is not captured, and only |
| // written to or read from. |
| |
| // If we may read from the memory, consider this a load-like call |
| // that must have all writes done in preparation for any reverse-pass |
| // users. |
| if (!WriteOnly) { |
| if (!seenLoadLikeCall) { |
| loadLikeCalls.push_back(LoadLikeCall(CI, prev)); |
| seenLoadLikeCall = true; |
| } |
| } |
| |
| // If we may write to memory, we cannot promote if any values |
| // need the allocation or any descendants for the reverse pass. |
| if (!ReadOnly) { |
| // There is an exception for Julia returnRoots which will be |
| // separately handled in a GC postprocessing pass. Moreover these |
| // values are never `needed` in the reverse pass (just we need to mark |
| // those values as being GC'd by the function). |
| bool returnRoots = false; |
| // CI->getAttributes().hasParamAttr(idx, "enzymejl_returnRoots") || |
| // CI->getAttributes().hasParamAttr(idx, "enzymejl_returnRoots_v"); |
| if (primalNeededInReverse && !returnRoots) { |
| promotable = false; |
| EmitWarning("NotPromotable", *cur, " Could not promote allocation ", |
| *V, " due to unknown writing call ", *cur); |
| } |
| if (!nonRepeatableWritingCall) |
| nonRepeatableWritingCall = CI; |
| storingOps.insert(cur); |
| } |
| |
| // Consider shadow memory now. |
| // |
| // If the memory is all floats, there's no issue, since besides zero |
| // initialization nothing should occur for them in the forward pass |
| if (TT.isFloat()) { |
| } else if (WriteOnly) { |
| // Don't need in the case of int/pointer stores, (should be done by |
| // fwd pass), and as isFloat above described does not prevent the |
| // shadow |
| } else { |
| shadowPointerLoads.push_back(cur); |
| } |
| |
| idx++; |
| } |
| |
| } else { |
| promotable = false; |
| shadowpromotable = false; |
| EmitWarning("NotPromotable", *cur, " Could not promote allocation ", *V, |
| " due to unknown instruction ", *cur); |
| } |
| } |
| |
| // Find the outermost loop of all stores, and the allocation/lifetime |
| Loop *outer = OrigLI->getLoopFor(V->getParent()); |
| if (LifetimeStarts.size() == 1) { |
| outer = OrigLI->getLoopFor((*LifetimeStarts.begin())->getParent()); |
| } |
| |
| for (auto S : stores) { |
| outer = getAncestor(outer, OrigLI->getLoopFor(S->getParent())); |
| } |
| |
| // May now read pointers for storing into other pointers. Therefore we |
| // need to pre initialize the shadow. |
| bool primalInitializationOfShadow = shadowPointerLoads.size() > 0; |
| |
| if (shadowpromotable && !isConstantValue(V)) { |
| for (auto LI : shadowPointerLoads) { |
| // Is there a store which could occur after the load. |
| // This subsequent store would invalidate any loads being re-performed. |
| SmallVector<Instruction *, 2> results; |
| mayExecuteAfter(results, LI, storingOps, outer); |
| for (auto res : results) { |
| if (overwritesToMemoryReadBy(&TR, *OrigAA, TLI, *OrigSE, *OrigLI, |
| *OrigDT, LI, res, outer)) { |
| EmitWarning("NotPromotable", *LI, |
| " Could not promote shadow allocation ", *V, |
| " due to pointer load ", *LI, |
| " which does not postdominates store ", *res); |
| shadowpromotable = false; |
| goto exitL; |
| } |
| } |
| } |
| // If there is a store not reproduced in the reverse pass (e.g. as part |
| // of a write in a call), and this store is necessary to a pointer load of |
| // the shadow, this is not materializable since the load will not return |
| // the same value. |
| { |
| SmallVector<Instruction *, 2> nonReproducedStores; |
| for (auto S : storingOps) |
| if (!stores.count(S)) { |
| SmallVector<Instruction *, 2> results; |
| SmallPtrSet<Instruction *, 2> shadowPtrLoadSet( |
| shadowPointerLoads.begin(), shadowPointerLoads.end()); |
| mayExecuteAfter(results, S, shadowPtrLoadSet, outer); |
| if (results.size()) { |
| EmitWarning("NotPromotable", *results[0], |
| " Could not promote shadow allocation ", *V, |
| " due to non-reproduced store ", *S, |
| " which may impact pointer load ", *results[0]); |
| shadowpromotable = false; |
| goto exitL; |
| } |
| } |
| } |
| exitL:; |
| if (shadowpromotable) { |
| backwardsOnlyShadows[V] = ShadowRematerializer( |
| stores, frees, primalInitializationOfShadow, outer); |
| } |
| } |
| |
| if (!promotable) |
| return; |
| |
| SmallPtrSet<LoadInst *, 1> rematerializable; |
| |
| // We currently require a rematerializable allocation to have |
| // all of its loads be able to be performed again. Thus if |
| // there is an overwriting store after a load in context, |
| // it may no longer be rematerializable. |
| for (auto LI : loads) { |
| // Is there a store which could occur after the load. |
| // In other words |
| SmallVector<Instruction *, 2> results; |
| mayExecuteAfter(results, LI, storingOps, outer); |
| for (auto res : results) { |
| if (overwritesToMemoryReadBy(&TR, *OrigAA, TLI, *OrigSE, *OrigLI, *OrigDT, |
| LI, res, outer)) { |
| EmitWarning("NotPromotable", *LI, " Could not promote allocation ", *V, |
| " due to load ", *LI, |
| " which does not postdominates store ", *res); |
| return; |
| } |
| } |
| rematerializable.insert(LI); |
| } |
| for (auto LI : loadLikeCalls) { |
| // Is there a store which could occur after the load. |
| // In other words |
| SmallVector<Instruction *, 2> results; |
| mayExecuteAfter(results, LI.loadCall, storingOps, outer); |
| for (auto res : results) { |
| if (overwritesToMemoryReadBy(&TR, *OrigAA, TLI, *OrigSE, *OrigLI, *OrigDT, |
| LI.loadCall, res, outer)) { |
| EmitWarning("NotPromotable", *LI.loadCall, |
| " Could not promote allocation ", *V, |
| " due to load-like call ", *LI.loadCall, |
| " which does not postdominates store ", *res); |
| return; |
| } |
| } |
| } |
| rematerializableAllocations[V] = Rematerializer( |
| loads, loadLikeCalls, stores, frees, outer, nonRepeatableWritingCall); |
| } |
| |
| BasicBlock *GradientUtils::addReverseBlock(BasicBlock *currentBlock, |
| Twine const &name, bool forkCache, |
| bool push) { |
| assert(reverseBlocks.size()); |
| auto found = reverseBlockToPrimal.find(currentBlock); |
| assert(found != reverseBlockToPrimal.end()); |
| |
| SmallVector<BasicBlock *, 4> &vec = reverseBlocks[found->second]; |
| assert(vec.size()); |
| if (vec.back() != currentBlock) { |
| std::string str; |
| raw_string_ostream ss(str); |
| ss << "Error adding reverse block:\n"; |
| ss << "fwdBlock: " << *found->second << "\n"; |
| ss << "currentBlock: " << *currentBlock << "\n"; |
| ss << "vec.back(): " << *vec.back() << "\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(str.c_str(), wrap((Value *)currentBlock), |
| ErrorType::InternalError, nullptr, nullptr, nullptr); |
| } else { |
| DebugLoc loc; |
| if (hasTerminator(found->second)) { |
| loc = found->second->getTerminator()->getDebugLoc(); |
| } |
| EmitFailure("AddReverseBlockError", loc, found->second->getParent(), |
| ss.str()); |
| } |
| } |
| assert(vec.back() == currentBlock); |
| |
| BasicBlock *rev = |
| BasicBlock::Create(currentBlock->getContext(), name, newFunc); |
| rev->moveAfter(currentBlock); |
| if (push) |
| vec.push_back(rev); |
| reverseBlockToPrimal[rev] = found->second; |
| if (forkCache) { |
| for (auto pair : unwrap_cache[currentBlock]) |
| unwrap_cache[rev].insert(pair); |
| for (auto pair : lookup_cache[currentBlock]) |
| lookup_cache[rev].insert(pair); |
| } |
| return rev; |
| } |
| |
| void GradientUtils::replaceAWithB(Value *A, Value *B, bool storeInCache) { |
| if (A == B) |
| return; |
| assert(A->getType() == B->getType()); |
| |
| if (auto iA = dyn_cast<Instruction>(A)) { |
| if (unwrappedLoads.find(iA) != unwrappedLoads.end()) { |
| auto iB = cast<Instruction>(B); |
| unwrappedLoads[iB] = unwrappedLoads[iA]; |
| unwrappedLoads.erase(iA); |
| } |
| } |
| |
| // Check that the replacement doesn't already exist in the mapping |
| // thereby resulting in a conflict. |
| #ifndef NDEBUG |
| if (!isa<UndefValue>(B)) { |
| auto found = newToOriginalFn.find(A); |
| if (found != newToOriginalFn.end()) { |
| auto foundB = newToOriginalFn.find(B); |
| assert(foundB == newToOriginalFn.end()); |
| } |
| } |
| #endif |
| |
| CacheUtility::replaceAWithB(A, B, storeInCache); |
| } |
| |
| void GradientUtils::erase(Instruction *I) { |
| assert(I); |
| if (I->getParent()->getParent() != newFunc) { |
| llvm::errs() << "newFunc: " << *newFunc << "\n"; |
| llvm::errs() << "paren: " << *I->getParent()->getParent() << "\n"; |
| llvm::errs() << "I: " << *I << "\n"; |
| } |
| assert(I->getParent()->getParent() == newFunc); |
| |
| // not original, should not contain |
| assert(!invertedPointers.count(I)); |
| // not original, should not contain |
| assert(!originalToNewFn.count(I)); |
| |
| originalToNewFn.erase(I); |
| { |
| auto found = newToOriginalFn.find(I); |
| if (found != newToOriginalFn.end()) { |
| Value *orig = found->second; |
| newToOriginalFn.erase(found); |
| originalToNewFn.erase(orig); |
| } |
| } |
| { |
| auto found = UnwrappedWarnings.find(I); |
| if (found != UnwrappedWarnings.end()) { |
| UnwrappedWarnings.erase(found); |
| } |
| } |
| unwrappedLoads.erase(I); |
| |
| for (auto &pair : unwrap_cache) { |
| if (pair.second.find(I) != pair.second.end()) |
| pair.second.erase(I); |
| } |
| |
| for (auto &pair : lookup_cache) { |
| if (pair.second.find(I) != pair.second.end()) |
| pair.second.erase(I); |
| } |
| CacheUtility::erase(I); |
| } |
| |
| void GradientUtils::eraseWithPlaceholder(Instruction *I, Instruction *orig, |
| const Twine &suffix, bool erase) { |
| if (I->getType()->isTokenTy()) { |
| replaceAWithB(I, UndefValue::get(I->getType())); |
| } else if (!I->getType()->isVoidTy() && !I->getType()->isTokenTy()) { |
| auto inspos = I->getIterator(); |
| #if LLVM_VERSION_MAJOR >= 18 |
| #if LLVM_VERSION_MAJOR >= 21 |
| #else |
| if (I->getParent()->IsNewDbgInfoFormat) |
| #endif |
| { |
| if (!inspos.getHeadBit()) { |
| auto srcmarker = I->getParent()->getMarker(inspos); |
| if (srcmarker && !srcmarker->empty()) { |
| inspos.setHeadBit(true); |
| } |
| } |
| } |
| #endif |
| IRBuilder<> BuilderZ(I->getParent(), inspos); |
| auto pn = BuilderZ.CreatePHI(I->getType(), 1, I->getName() + suffix); |
| fictiousPHIs[pn] = orig; |
| replaceAWithB(I, pn); |
| } |
| |
| if (erase) { |
| this->erase(I); |
| } |
| } |
| |
| void GradientUtils::setTape(Value *newtape) { |
| assert(tape == nullptr); |
| assert(newtape != nullptr); |
| assert(tapeidx == 0); |
| assert(addedTapeVals.size() == 0); |
| tape = newtape; |
| } |
| |
| void GradientUtils::dumpPointers() { |
| errs() << "invertedPointers:\n"; |
| for (auto a : invertedPointers) { |
| errs() << " invertedPointers[" << *a.first << "] = " << *a.second << "\n"; |
| } |
| errs() << "end invertedPointers\n"; |
| } |
| |
| int GradientUtils::getIndex( |
| std::pair<Instruction *, CacheType> idx, |
| const std::map<std::pair<Instruction *, CacheType>, int> &mapping, |
| IRBuilder<> &B) { |
| assert(tape); |
| auto found = mapping.find(idx); |
| if (found == mapping.end()) { |
| std::string s; |
| llvm::raw_string_ostream ss(s); |
| ss << *oldFunc << "\n"; |
| ss << *newFunc << "\n"; |
| ss << " <mapping>\n"; |
| for (auto &p : mapping) { |
| ss << " idx: " << *p.first.first << ", " << p.first.second |
| << " pos=" << p.second << "\n"; |
| } |
| ss << " </mapping>\n"; |
| ss << "idx: " << *idx.first << ", " << idx.second << "\n"; |
| ss << " could not find index in mapping\n"; |
| if (CustomErrorHandler) { |
| CustomErrorHandler(ss.str().c_str(), wrap(idx.first), |
| ErrorType::GetIndexError, this, nullptr, wrap(&B)); |
| } else { |
| EmitFailure("GetIndexError", idx.first->getDebugLoc(), idx.first, |
| ss.str()); |
| } |
| return IndexMappingError; |
| } |
| return found->second; |
| } |
| |
| int GradientUtils::getIndex( |
| std::pair<Instruction *, CacheType> idx, |
| std::map<std::pair<Instruction *, CacheType>, int> &mapping, |
| IRBuilder<> &B) { |
| if (tape) { |
| return getIndex( |
| idx, |
| (const std::map<std::pair<Instruction *, CacheType>, int> &)mapping, B); |
| } else { |
| if (mapping.find(idx) != mapping.end()) { |
| return mapping[idx]; |
| } |
| mapping[idx] = tapeidx; |
| ++tapeidx; |
| return mapping[idx]; |
| } |
| } |
| |
| void GradientUtils::computeGuaranteedFrees() { |
| SmallPtrSet<CallInst *, 2> allocsToPromote; |
| |
| DenseMap<Metadata *, SmallVector<CallInst *>> cache_frees; |
| for (auto &BB : *oldFunc) { |
| for (auto &I : BB) { |
| auto CI = dyn_cast<CallInst>(&I); |
| if (!CI) |
| continue; |
| if (auto MD = CI->getMetadata("enzyme_cache_free")) { |
| if (MD->getNumOperands() > 0) { |
| Metadata *id = MD->getOperand(0); |
| cache_frees[id].push_back(CI); |
| } |
| } |
| } |
| } |
| |
| for (auto &BB : *oldFunc) { |
| if (notForAnalysis.count(&BB)) |
| continue; |
| for (auto &I : BB) { |
| if (auto AI = dyn_cast<AllocaInst>(&I)) |
| computeForwardingProperties(AI); |
| |
| auto CI = dyn_cast<CallInst>(&I); |
| if (!CI) |
| continue; |
| |
| StringRef funcName = getFuncNameFromCall(CI); |
| |
| if (isDeallocationFunction(funcName, TLI)) { |
| llvm::Value *val = getBaseObject(CI->getArgOperand(0)); |
| |
| if (auto dc = dyn_cast<CallInst>(val)) { |
| StringRef sfuncName = getFuncNameFromCall(dc); |
| if (isAllocationFunction(sfuncName, TLI)) { |
| |
| bool hasPDFree = false; |
| if (dc->getParent() == CI->getParent() || |
| OrigPDT->dominates(CI->getParent(), dc->getParent())) { |
| hasPDFree = true; |
| } |
| |
| if (hasPDFree) { |
| allocationsWithGuaranteedFree[dc].insert(CI); |
| } |
| } |
| } |
| } |
| if (auto MD = CI->getMetadata("enzyme_cache_alloc")) { |
| if (MD->getNumOperands() > 0) { |
| Metadata *id = MD->getOperand(0); |
| if (cast<ConstantInt>( |
| cast<ConstantAsMetadata>(cast<MDNode>(id)->getOperand(0)) |
| ->getValue()) |
| ->isOne()) { |
| for (auto otherCI : cache_frees[id]) { |
| allocationsWithGuaranteedFree[CI].insert(otherCI); |
| } |
| } |
| } |
| } |
| if (isAllocationFunction(funcName, TLI)) { |
| allocsToPromote.insert(CI); |
| if (hasMetadata(CI, "enzyme_fromstack")) { |
| allocationsWithGuaranteedFree[CI].insert(CI); |
| } |
| // TODO: special case object managed by the GC as it is automatically |
| // freed. |
| if (EnzymeJuliaAddrLoad && isa<PointerType>(CI->getType()) && |
| cast<PointerType>(CI->getType())->getAddressSpace() == 10) { |
| } |
| } |
| } |
| } |
| for (CallInst *V : allocsToPromote) { |
| // TODO compute if an only load/store (non capture) |
| // allocaion by traversing its users. If so, mark |
| // all of its load/stores, as now the loads can |
| // potentially be rematerialized without a cache |
| // of the allocation, but the operands of all stores. |
| // This info needs to be provided to minCutCache |
| // the derivative of store needs to redo the store, |
| // isValueNeededInReverse needs to know to preserve the |
| // store operands in this case, etc |
| computeForwardingProperties(V); |
| } |
| } |
| |
| /// Perform the corresponding deallocation of tofree, given it was allocated by |
| /// allocationfn |
| // For updating below one should read MemoryBuiltins.cpp, TargetLibraryInfo.cpp |
| llvm::CallInst *freeKnownAllocation(llvm::IRBuilder<> &builder, |
| llvm::Value *tofree, |
| llvm::StringRef allocationfn, |
| const llvm::DebugLoc &debuglocation, |
| const llvm::TargetLibraryInfo &TLI, |
| llvm::CallInst *orig, |
| GradientUtils *gutils) { |
| assert(isAllocationFunction(allocationfn, TLI)); |
| |
| if (allocationfn == "__rust_alloc" || allocationfn == "__rust_alloc_zeroed") { |
| Type *VoidTy = Type::getVoidTy(tofree->getContext()); |
| Type *IntPtrTy = orig->getType(); |
| Type *RustSz = orig->getArgOperand(0)->getType(); |
| Type *inTys[3] = {IntPtrTy, RustSz, RustSz}; |
| |
| auto FT = FunctionType::get(VoidTy, inTys, false); |
| Value *freevalue = builder.GetInsertBlock() |
| ->getParent() |
| ->getParent() |
| ->getOrInsertFunction("__rust_dealloc", FT) |
| .getCallee(); |
| Value *vals[3]; |
| vals[0] = builder.CreatePointerCast(tofree, IntPtrTy); |
| // size |
| vals[1] = gutils->lookupM( |
| gutils->getNewFromOriginal(orig->getArgOperand(0)), builder); |
| // alignment |
| vals[2] = gutils->lookupM( |
| gutils->getNewFromOriginal(orig->getArgOperand(1)), builder); |
| CallInst *freecall = cast<CallInst>( |
| CallInst::Create(FT, freevalue, vals, "", builder.GetInsertBlock())); |
| freecall->setDebugLoc(debuglocation); |
| if (isa<CallInst>(tofree) && |
| cast<CallInst>(tofree)->getAttributes().hasAttribute( |
| AttributeList::ReturnIndex, Attribute::NonNull)) { |
| freecall->addAttribute(AttributeList::FirstArgIndex, Attribute::NonNull); |
| } |
| if (Function *F = dyn_cast<Function>(freevalue)) |
| freecall->setCallingConv(F->getCallingConv()); |
| if (freecall->getParent() == nullptr) |
| builder.Insert(freecall); |
| return freecall; |
| } |
| if (allocationfn == "julia.gc_alloc_obj" || |
| allocationfn == "jl_gc_alloc_typed" || |
| allocationfn == "ijl_gc_alloc_typed" || |
| allocationfn == "jl_alloc_array_1d" || |
| allocationfn == "ijl_alloc_array_1d" || |
| allocationfn == "jl_alloc_array_2d" || |
| allocationfn == "ijl_alloc_array_2d" || |
| allocationfn == "jl_alloc_array_3d" || |
| allocationfn == "ijl_alloc_array_3d" || allocationfn == "jl_new_array" || |
| allocationfn == "ijl_new_array" || |
| allocationfn == "jl_alloc_genericmemory" || |
| allocationfn == "ijl_alloc_genericmemory" || |
| allocationfn == "jl_alloc_genericmemory_unchecked" || |
| allocationfn == "ijl_alloc_genericmemory_unchecked") |
| return nullptr; |
| |
| if (allocationfn == "enzyme_allocator") { |
| auto inds = getDeallocationIndicesFromCall(orig); |
| SmallVector<Value *, 2> vals; |
| for (auto ind : inds) { |
| if (ind == -1) |
| vals.push_back(tofree); |
| else |
| vals.push_back(gutils->lookupM( |
| gutils->getNewFromOriginal(orig->getArgOperand(ind)), builder)); |
| } |
| auto tocall = getDeallocatorFnFromCall(orig); |
| auto freecall = builder.CreateCall(tocall, vals); |
| freecall->setDebugLoc(debuglocation); |
| return freecall; |
| } |
| |
| if (allocationfn == "swift_allocObject") { |
| Type *VoidTy = Type::getVoidTy(tofree->getContext()); |
| Type *IntPtrTy = getInt8PtrTy(tofree->getContext()); |
| |
| auto FT = FunctionType::get(VoidTy, ArrayRef<Type *>(IntPtrTy), false); |
| Value *freevalue = builder.GetInsertBlock() |
| ->getParent() |
| ->getParent() |
| ->getOrInsertFunction("swift_release", FT) |
| .getCallee(); |
| CallInst *freecall = cast<CallInst>(CallInst::Create( |
| FT, freevalue, |
| ArrayRef<Value *>(builder.CreatePointerCast(tofree, IntPtrTy)), "", |
| builder.GetInsertBlock())); |
| freecall->setDebugLoc(debuglocation); |
| if (isa<CallInst>(tofree) && |
| cast<CallInst>(tofree)->getAttributes().hasAttribute( |
| AttributeList::ReturnIndex, Attribute::NonNull)) { |
| freecall->addAttribute(AttributeList::FirstArgIndex, Attribute::NonNull); |
| } |
| if (Function *F = dyn_cast<Function>(freevalue)) |
| freecall->setCallingConv(F->getCallingConv()); |
| if (freecall->getParent() == nullptr) |
| builder.Insert(freecall); |
| return freecall; |
| } |
| |
| if (shadowErasers.find(allocationfn) != shadowErasers.end()) { |
| return shadowErasers[allocationfn](builder, tofree); |
| } |
| |
| if (allocationfn == "__size_returning_new_experiment") { |
| allocationfn = "malloc"; |
| tofree = builder.CreateExtractValue(tofree, 0); |
| } |
| |
| if (tofree->getType()->isIntegerTy()) |
| tofree = builder.CreateIntToPtr(tofree, getInt8PtrTy(tofree->getContext())); |
| |
| llvm::LibFunc libfunc; |
| if (allocationfn == "calloc" || allocationfn == "malloc" || |
| allocationfn == "_mlir_memref_to_llvm_alloc") { |
| libfunc = LibFunc_malloc; |
| } else { |
| bool res = TLI.getLibFunc(allocationfn, libfunc); |
| (void)res; |
| assert(res && "ought find known allocation fn"); |
| } |
| |
| llvm::LibFunc freefunc; |
| |
| switch (libfunc) { |
| case LibFunc_malloc: // malloc(unsigned int); |
| case LibFunc_valloc: // valloc(unsigned int); |
| freefunc = LibFunc_free; |
| break; |
| |
| case LibFunc_Znwj: // new(unsigned int); |
| case LibFunc_ZnwjRKSt9nothrow_t: // new(unsigned int, nothrow); |
| case LibFunc_ZnwjSt11align_val_t: // new(unsigned int, align_val_t) |
| case LibFunc_ZnwjSt11align_val_tRKSt9nothrow_t: // new(unsigned int, |
| // align_val_t, nothrow) |
| |
| case LibFunc_Znwm: // new(unsigned long); |
| case LibFunc_ZnwmRKSt9nothrow_t: // new(unsigned long, nothrow); |
| case LibFunc_ZnwmSt11align_val_t: // new(unsigned long, align_val_t) |
| case LibFunc_ZnwmSt11align_val_tRKSt9nothrow_t: // new(unsigned long, |
| // align_val_t, nothrow) |
| freefunc = LibFunc_ZdlPv; |
| break; |
| |
| case LibFunc_Znaj: // new[](unsigned int); |
| case LibFunc_ZnajRKSt9nothrow_t: // new[](unsigned int, nothrow); |
| case LibFunc_ZnajSt11align_val_t: // new[](unsigned int, align_val_t) |
| case LibFunc_ZnajSt11align_val_tRKSt9nothrow_t: // new[](unsigned int, |
| // align_val_t, nothrow |
| |
| case LibFunc_Znam: // new[](unsigned long); |
| case LibFunc_ZnamRKSt9nothrow_t: // new[](unsigned long, nothrow); |
| case LibFunc_ZnamSt11align_val_t: // new[](unsigned long, align_val_t) |
| case LibFunc_ZnamSt11align_val_tRKSt9nothrow_t: // new[](unsigned long, |
| // align_val_t, nothrow) |
| freefunc = LibFunc_ZdaPv; |
| break; |
| |
| case LibFunc_msvc_new_longlong: // new(unsigned long long); |
| case LibFunc_msvc_new_longlong_nothrow: // new(unsigned long long, nothrow); |
| freefunc = LibFunc_msvc_delete_ptr64_longlong; |
| break; |
| |
| case LibFunc_msvc_new_array_longlong: // new[](unsigned long long); |
| case LibFunc_msvc_new_array_longlong_nothrow: // new[](unsigned long long, |
| // nothrow); |
| freefunc = LibFunc_msvc_delete_array_ptr64_longlong; |
| break; |
| |
| case LibFunc_msvc_new_int: // new(unsigned int); |
| case LibFunc_msvc_new_int_nothrow: // new(unsigned int, nothrow); |
| case LibFunc_msvc_new_array_int: // new[](unsigned int); |
| case LibFunc_msvc_new_array_int_nothrow: // new[](unsigned int, nothrow); |
| llvm_unreachable("msvc deletion not handled"); |
| |
| default: |
| llvm_unreachable("unknown allocation function"); |
| } |
| llvm::StringRef freename = TLI.getName(freefunc); |
| if (freefunc == LibFunc_free) { |
| freename = "free"; |
| assert(freename == "free"); |
| if (freename != "free") |
| llvm_unreachable("illegal free"); |
| } |
| if (allocationfn == "_mlir_memref_to_llvm_alloc") |
| freename = "_mlir_memref_to_llvm_free"; |
| |
| Type *VoidTy = Type::getVoidTy(tofree->getContext()); |
| Type *IntPtrTy = getInt8PtrTy(tofree->getContext()); |
| |
| auto FT = FunctionType::get(VoidTy, {IntPtrTy}, false); |
| Value *freevalue = builder.GetInsertBlock() |
| ->getParent() |
| ->getParent() |
| ->getOrInsertFunction(freename, FT) |
| .getCallee(); |
| CallInst *freecall = cast<CallInst>(CallInst::Create( |
| FT, freevalue, {builder.CreatePointerCast(tofree, IntPtrTy)}, "", |
| builder.GetInsertBlock())); |
| freecall->setDebugLoc(debuglocation); |
| if (isa<CallInst>(tofree) && |
| cast<CallInst>(tofree)->getAttributes().hasAttribute( |
| AttributeList::ReturnIndex, Attribute::NonNull)) { |
| freecall->addAttribute(AttributeList::FirstArgIndex, Attribute::NonNull); |
| } |
| if (Function *F = dyn_cast<Function>(freevalue)) |
| freecall->setCallingConv(F->getCallingConv()); |
| if (freecall->getParent() == nullptr) |
| builder.Insert(freecall); |
| return freecall; |
| } |
| |
| bool GradientUtils::needsCacheWholeAllocation( |
| const llvm::Value *origInst) const { |
| auto found = knownRecomputeHeuristic.find(origInst); |
| if (found == knownRecomputeHeuristic.end()) |
| return false; |
| if (!found->second) |
| return true; |
| // User, operand of input, whehter the input is the original allocation |
| SmallVector<std::tuple<const Instruction *, size_t, bool>, 1> todo; |
| for (auto &use : origInst->uses()) |
| todo.push_back(std::make_tuple(cast<Instruction>(use.getUser()), |
| use.getOperandNo(), true)); |
| SmallSet<std::tuple<const Instruction *, size_t, bool>, 1> seen; |
| while (todo.size()) { |
| auto pair = todo.back(); |
| auto [cur, idx, orig] = pair; |
| todo.pop_back(); |
| if (seen.count(pair)) |
| continue; |
| seen.insert(pair); |
| // Loads are always fine |
| if (isa<LoadInst>(cur) || isNVLoad(cur)) |
| continue; |
| |
| if (auto II = dyn_cast<IntrinsicInst>(cur)) |
| if (II->getIntrinsicID() == Intrinsic::masked_load) |
| continue; |
| |
| bool returnedSameValue = false; |
| |
| if (auto CI = dyn_cast<CallInst>(cur)) { |
| #if LLVM_VERSION_MAJOR >= 14 |
| if (idx < CI->arg_size()) |
| #else |
| if (idx < CI->getNumArgOperands()) |
| #endif |
| { |
| |
| // Calling a non-empty function with a julia base object, this is fine. |
| // as GC will deal with any issues with. |
| if (auto PT = dyn_cast<PointerType>(CI->getArgOperand(idx)->getType())) |
| if (PT->getAddressSpace() == 10) |
| if (EnzymeJuliaAddrLoad) |
| if (auto F = getFunctionFromCall(CI)) |
| if (!F->empty()) |
| continue; |
| |
| if (isNoCapture(CI, idx)) |
| continue; |
| |
| if (auto F = CI->getCalledFunction()) |
| if (F->getCallingConv() == CI->getCallingConv() && !F->empty()) { |
| bool onlyReturnUses = true; |
| bool hasReturnUse = true; |
| |
| if (CI->getFunctionType() != F->getFunctionType() || |
| idx >= F->getFunctionType()->getNumParams()) { |
| onlyReturnUses = false; |
| } else { |
| for (auto u : F->getArg(idx)->users()) { |
| if (isa<ReturnInst>(u)) { |
| hasReturnUse = true; |
| continue; |
| } |
| onlyReturnUses = false; |
| continue; |
| } |
| } |
| // The arg itself has no use in the function |
| if (onlyReturnUses && !hasReturnUse) |
| continue; |
| |
| // If this is the original allocation, we return it guaranteed, and |
| // cache the return, that's still fine |
| if (onlyReturnUses && orig) { |
| found = knownRecomputeHeuristic.find(cur); |
| if (found == knownRecomputeHeuristic.end()) |
| continue; |
| |
| if (!found->second) |
| continue; |
| returnedSameValue = true; |
| } |
| } |
| } else { |
| // Either this is the called function, or this is a jlrooted, either way |
| // continue. |
| continue; |
| } |
| } |
| |
| found = knownRecomputeHeuristic.find(cur); |
| if (found == knownRecomputeHeuristic.end()) |
| continue; |
| |
| // If caching a julia base object, this is fine as |
| // GC will deal with any issues with. |
| if (auto PT = dyn_cast<PointerType>(cur->getType())) |
| if (PT->getAddressSpace() == 10) |
| if (EnzymeJuliaAddrLoad) |
| continue; |
| |
| // If caching this user, it cannot be a gep/cast of original |
| if (!found->second) { |
| llvm::errs() << " mod: " << *oldFunc->getParent() << "\n"; |
| llvm::errs() << " oldFunc: " << *oldFunc << "\n"; |
| for (auto &pair : knownRecomputeHeuristic) |
| llvm::errs() << " krc[" << *pair.first << "] = " << pair.second << "\n"; |
| llvm::errs() << " cur: " << *cur << "\n"; |
| llvm::errs() << " origInst: " << *origInst << "\n"; |
| assert(false && "caching potentially capturing/offset of allocation"); |
| } else { |
| // if not caching this user, it is legal to recompute, consider its users |
| for (auto &use : cur->uses()) { |
| todo.push_back(std::make_tuple(cast<Instruction>(use.getUser()), |
| use.getOperandNo(), |
| returnedSameValue && orig)); |
| } |
| } |
| } |
| return false; |
| } |
| |
| void GradientUtils::replaceAndRemoveUnwrapCacheFor(llvm::Value *A, |
| llvm::Value *B) { |
| SmallVector<Instruction *, 1> toErase; |
| for (auto &pair : unwrap_cache) { |
| auto found = pair.second.find(A); |
| if (found != pair.second.end()) { |
| for (auto &p : found->second) { |
| Value *pre = p.second; |
| replaceAWithB(pre, B); |
| if (auto I = dyn_cast<Instruction>(pre)) { |
| toErase.push_back(I); |
| } |
| } |
| pair.second.erase(A); |
| } |
| } |
| for (auto I : toErase) { |
| erase(I); |
| } |
| } |