Enzyme main
Loading...
Searching...
No Matches
FunctionUtils.h
Go to the documentation of this file.
1//===- FunctionUtils.h - Declaration of function utilities ---------------===//
2//
3// Enzyme Project
4//
5// Part of the Enzyme Project, under the Apache License v2.0 with LLVM
6// Exceptions. See https://llvm.org/LICENSE.txt for license information.
7// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8//
9// If using this code in an academic setting, please cite the following:
10// @incollection{enzymeNeurips,
11// title = {Instead of Rewriting Foreign Code for Machine Learning,
12// Automatically Synthesize Fast Gradients},
13// author = {Moses, William S. and Churavy, Valentin},
14// booktitle = {Advances in Neural Information Processing Systems 33},
15// year = {2020},
16// note = {To appear in},
17// }
18//
19//===----------------------------------------------------------------------===//
20//
21// This file declares utilities on LLVM Functions that are used as part of the
22// AD process.
23//
24//===----------------------------------------------------------------------===//
25#ifndef ENZYME_FUNCTION_UTILS_H
26#define ENZYME_FUNCTION_UTILS_H
27
28#include <deque>
29#include <set>
30
31#include <llvm/Config/llvm-config.h>
32
33#if LLVM_VERSION_MAJOR >= 16
34#define private public
35#include "llvm/Analysis/ScalarEvolution.h"
36#include "llvm/Transforms/Utils/ScalarEvolutionExpander.h"
37#undef private
38#else
39#include "SCEV/ScalarEvolution.h"
40#include "SCEV/ScalarEvolutionExpander.h"
41#endif
42
43#include "Utils.h"
44
45#include "llvm/Analysis/AliasAnalysis.h"
46#include "llvm/Analysis/LoopAnalysisManager.h"
47#include "llvm/Analysis/TargetLibraryInfo.h"
48
49#include "llvm/IR/Function.h"
50#include "llvm/IR/Module.h"
51#include "llvm/IR/Type.h"
52
53#include "llvm/IR/Instructions.h"
54#include "llvm/Transforms/Utils/ValueMapper.h"
55
56#include "llvm/ADT/STLExtras.h"
57
58//;
59
60extern "C" {
61extern llvm::cl::opt<bool> EnzymeAlwaysInlineDiff;
62}
63
64// Perform an analysis to detect functions which only write to visible memory
65// outside the function if an error is not throw. Such a function can touch
66// inaccessible memory [e.g. the insides of malloc/etc], and the only violation
67// is whether existing memory before the call is written to.
68// If non-local, returning memory written to is a violation (since it writes to
69// externally visible memory).
70// If local, returning memory written to is fine (since existing memory before
71// the call remains unchanged).
72// In other words, malloc [local and non-local], calloc [local and non-local],
73// copy_array [local only], and friends, are all considered
74// readonly_or_throw, as they only either read externally visible state, throw
75// an error, or write to inaccesible memory.
76bool DetectReadonlyOrThrow(llvm::Module &M);
77
79public:
82 // Using the default move constructor will botch the FAM/MAM proxy passes
83 // since now the new location of FAM/MAM will not be used. Therefore, use a
84 // custom move constructor and default initialize these, and move the
85 // cache/origin maps.
87 cache = std::move(prev.cache);
88 CloneOrigin = std::move(prev.CloneOrigin);
89 };
90
91 llvm::LoopAnalysisManager LAM;
92 llvm::FunctionAnalysisManager FAM;
93 llvm::ModuleAnalysisManager MAM;
94
95 std::map<std::pair<llvm::Function *, DerivativeMode>, llvm::Function *> cache;
96 std::map<llvm::Function *, llvm::Function *> CloneOrigin;
97
98 llvm::Function *preprocessForClone(llvm::Function *F, DerivativeMode mode);
99
100 llvm::AAResults &getAAResultsFromFunction(llvm::Function *NewF);
101
102 llvm::Function *CloneFunctionWithReturns(
103 DerivativeMode mode, unsigned width, llvm::Function *&F,
104 llvm::ValueToValueMapTy &ptrInputs,
105 llvm::ArrayRef<DIFFE_TYPE> constant_args,
106 llvm::SmallPtrSetImpl<llvm::Value *> &constants,
107 llvm::SmallPtrSetImpl<llvm::Value *> &nonconstant,
108 llvm::SmallPtrSetImpl<llvm::Value *> &returnvals, bool returnTape,
109 bool returnPrimal, bool returnShadow, const llvm::Twine &name,
110 llvm::ValueMap<const llvm::Value *, AssertingReplacingVH> *VMapO,
111 bool diffeReturnArg, llvm::Type *additionalArg = nullptr);
112
113 void ReplaceReallocs(llvm::Function *NewF, bool mem2reg = false);
114 void LowerAllocAddr(llvm::Function *NewF);
115 void AlwaysInline(llvm::Function *NewF);
116 void optimizeIntermediate(llvm::Function *F);
117
118 void clear();
119};
120
121class GradientUtils;
122
123static inline void
124getExitBlocks(const llvm::Loop *L,
125 llvm::SmallPtrSetImpl<llvm::BasicBlock *> &ExitBlocks) {
126 llvm::SmallVector<llvm::BasicBlock *, 8> PotentialExitBlocks;
127 L->getExitBlocks(PotentialExitBlocks);
128 for (auto a : PotentialExitBlocks) {
129
130 llvm::SmallVector<llvm::BasicBlock *, 4> tocheck;
131 llvm::SmallPtrSet<llvm::BasicBlock *, 4> checked;
132 tocheck.push_back(a);
133
134 bool isExit = false;
135
136 while (tocheck.size()) {
137 auto foo = tocheck.back();
138 tocheck.pop_back();
139 if (checked.count(foo)) {
140 isExit = true;
141 goto exitblockcheck;
142 }
143 checked.insert(foo);
144 if (isAnyBranch(foo->getTerminator())) {
145 auto *bTerm = foo->getTerminator();
146 // Instruction::successors() postdates some supported majors; index.
147 for (unsigned i = 0, e = bTerm->getNumSuccessors(); i < e; ++i) {
148 auto *nb = bTerm->getSuccessor(i);
149 if (L->contains(nb))
150 continue;
151 tocheck.push_back(nb);
152 }
153 } else if (llvm::isa<llvm::UnreachableInst>(foo->getTerminator())) {
154 continue;
155 } else {
156 isExit = true;
157 goto exitblockcheck;
158 }
159 }
160
161 exitblockcheck:
162 if (isExit) {
163 ExitBlocks.insert(a);
164 }
165 }
166}
167
168static inline llvm::SmallVector<llvm::BasicBlock *, 3>
169getLatches(const llvm::Loop *L,
170 const llvm::SmallPtrSetImpl<llvm::BasicBlock *> &ExitBlocks) {
171 llvm::BasicBlock *Preheader = L->getLoopPreheader();
172 if (!Preheader) {
173 llvm::errs() << *L->getHeader()->getParent() << "\n";
174 llvm::errs() << *L->getHeader() << "\n";
175 llvm::errs() << *L << "\n";
176 }
177 assert(Preheader && "requires preheader");
178
179 // Find latch, defined as a (perhaps unique) block in loop that branches to
180 // exit block
181 llvm::SmallVector<llvm::BasicBlock *, 3> Latches;
182 for (llvm::BasicBlock *ExitBlock : ExitBlocks) {
183 for (llvm::BasicBlock *pred : llvm::predecessors(ExitBlock)) {
184 if (L->contains(pred)) {
185 if (std::find(Latches.begin(), Latches.end(), pred) != Latches.end())
186 continue;
187 Latches.push_back(pred);
188 }
189 }
190 }
191 return Latches;
192}
193
194// TODO note this doesn't go through [loop, unreachable], and we could get more
195// performance by doing this can consider doing some domtree magic potentially
196static inline llvm::SmallPtrSet<llvm::BasicBlock *, 4>
197getGuaranteedUnreachable(llvm::Function *F) {
198 llvm::SmallPtrSet<llvm::BasicBlock *, 4> knownUnreachables;
199 if (F->empty())
200 return knownUnreachables;
201 std::deque<llvm::BasicBlock *> todo;
202 for (auto &BB : *F) {
203 todo.push_back(&BB);
204 }
205
206 while (!todo.empty()) {
207 llvm::BasicBlock *next = todo.front();
208 todo.pop_front();
209
210 if (knownUnreachables.find(next) != knownUnreachables.end())
211 continue;
212
213 if (llvm::isa<llvm::ReturnInst>(next->getTerminator()))
214 continue;
215
216 if (llvm::isa<llvm::UnreachableInst>(next->getTerminator())) {
217 knownUnreachables.insert(next);
218 for (llvm::BasicBlock *Pred : predecessors(next)) {
219 todo.push_back(Pred);
220 }
221 continue;
222 }
223
224 // Assume resumes don't happen
225 // TODO consider EH
226 if (llvm::isa<llvm::ResumeInst>(next->getTerminator())) {
227 knownUnreachables.insert(next);
228 for (llvm::BasicBlock *Pred : predecessors(next)) {
229 todo.push_back(Pred);
230 }
231 continue;
232 }
233
234 bool unreachable = true;
235 for (llvm::BasicBlock *Succ : llvm::successors(next)) {
236 if (knownUnreachables.find(Succ) == knownUnreachables.end()) {
237 unreachable = false;
238 break;
239 }
240 }
241
242 if (!unreachable)
243 continue;
244 knownUnreachables.insert(next);
245 for (llvm::BasicBlock *Pred : llvm::predecessors(next)) {
246 todo.push_back(Pred);
247 }
248 continue;
249 }
250
251 return knownUnreachables;
252}
253
254enum class UseReq {
255 Need,
256 Recur,
257 Cached,
258};
259static inline void calculateUnusedValues(
260 const llvm::Function &oldFunc,
261 llvm::SmallPtrSetImpl<const llvm::Value *> &unnecessaryValues,
262 llvm::SmallPtrSetImpl<const llvm::Instruction *> &unnecessaryInstructions,
263 bool returnValue, llvm::function_ref<bool(const llvm::Value *)> valneeded,
264 llvm::function_ref<UseReq(const llvm::Instruction *)> instneeded,
265 llvm::function_ref<bool(const llvm::Instruction *, const llvm::Value *)>
266 useneeded) {
267
268 std::deque<const llvm::Instruction *> todo;
269
270 for (const llvm::BasicBlock &BB : oldFunc) {
271 if (auto ri = llvm::dyn_cast<llvm::ReturnInst>(BB.getTerminator())) {
272 if (!returnValue) {
273 unnecessaryInstructions.insert(ri);
274 }
275 unnecessaryValues.insert(ri);
276 }
277 for (auto &inst : BB) {
278 if (&inst == BB.getTerminator())
279 continue;
280 todo.push_back(&inst);
281 }
282 }
283
284 while (!todo.empty()) {
285 auto inst = todo.front();
286 todo.pop_front();
287
288 if (unnecessaryInstructions.count(inst)) {
289 assert(unnecessaryValues.count(inst));
290 continue;
291 }
292
293 if (!unnecessaryValues.count(inst)) {
294
295 if (valneeded(inst)) {
296 continue;
297 }
298
299 bool necessaryUse = false;
300
301 llvm::SmallPtrSet<const llvm::Instruction *, 4> seen;
302 std::deque<const llvm::Instruction *> users;
303
304 for (auto user_dtx : inst->users()) {
305 if (auto cst = llvm::dyn_cast<llvm::Instruction>(user_dtx)) {
306 if (useneeded(cst, inst))
307 users.push_back(cst);
308 }
309 }
310
311 while (users.size()) {
312 auto val = users.front();
313 users.pop_front();
314
315 if (seen.count(val))
316 continue;
317 seen.insert(val);
318
319 if (unnecessaryInstructions.count(val))
320 continue;
321
322 switch (instneeded(val)) {
323 case UseReq::Need:
324 necessaryUse = true;
325 break;
326 case UseReq::Recur:
327 for (auto user_dtx : val->users()) {
328 if (auto cst = llvm::dyn_cast<llvm::Instruction>(user_dtx)) {
329 if (useneeded(cst, val))
330 users.push_back(cst);
331 }
332 }
333 break;
334 case UseReq::Cached:
335 break;
336 }
337 if (necessaryUse)
338 break;
339 }
340
341 if (necessaryUse)
342 continue;
343
344 unnecessaryValues.insert(inst);
345
346 for (auto user : inst->users()) {
347 if (auto usedinst = llvm::dyn_cast<llvm::Instruction>(user))
348 todo.push_back(usedinst);
349 }
350 }
351
352 if (instneeded(inst) == UseReq::Need)
353 continue;
354
355 unnecessaryInstructions.insert(inst);
356
357 for (auto &operand : inst->operands()) {
358 if (auto usedinst = llvm::dyn_cast<llvm::Instruction>(operand.get())) {
359 todo.push_back(usedinst);
360 }
361 }
362 }
363
364 if (false && endsWith(oldFunc.getName(), "subfn")) {
365 llvm::errs() << "Prepping values for: " << oldFunc.getName()
366 << " returnValue: " << returnValue << "\n";
367 for (auto v : unnecessaryInstructions) {
368 llvm::errs() << "+ unnecessaryInstructions: " << *v << "\n";
369 }
370 for (auto v : unnecessaryValues) {
371 llvm::errs() << "+ unnecessaryValues: " << *v << "\n";
372 }
373 llvm::errs() << "</end>\n";
374 }
375}
376
377static inline void calculateUnusedStores(
378 const llvm::Function &oldFunc,
379 llvm::SmallPtrSetImpl<const llvm::Instruction *> &unnecessaryStores,
380 llvm::function_ref<bool(const llvm::Instruction *)> needStore) {
381
382 std::deque<const llvm::Instruction *> todo;
383
384 for (const llvm::BasicBlock &BB : oldFunc) {
385 for (auto &inst : BB) {
386 if (&inst == BB.getTerminator())
387 continue;
388 todo.push_back(&inst);
389 }
390 }
391
392 while (!todo.empty()) {
393 auto inst = todo.front();
394 todo.pop_front();
395
396 if (unnecessaryStores.count(inst)) {
397 continue;
398 }
399
400 if (needStore(inst))
401 continue;
402
403 unnecessaryStores.insert(inst);
404 }
405}
406
407void RecursivelyReplaceAddressSpace(llvm::Value *AI, llvm::Value *rep,
408 bool legal);
409
410void ReplaceFunctionImplementation(llvm::Module &M);
411
412/// Is the use of value val as an argument of call CI potentially captured
413bool couldFunctionArgumentCapture(llvm::CallInst *CI, llvm::Value *val);
414
415llvm::FunctionType *getFunctionTypeForClone(
416 llvm::FunctionType *FTy, DerivativeMode mode, unsigned width,
417 llvm::Type *additionalArg, llvm::ArrayRef<DIFFE_TYPE> constant_args,
418 bool diffeReturnArg, bool returnTape, bool returnPrimal, bool returnShadow);
419
420/// Lower __enzyme_todense, returning if changed.
421bool LowerSparsification(llvm::Function *F, bool replaceAll = true);
422
423#endif
bool couldFunctionArgumentCapture(llvm::CallInst *CI, llvm::Value *val)
Is the use of value val as an argument of call CI potentially captured.
UseReq
llvm::cl::opt< bool > EnzymeAlwaysInlineDiff
void RecursivelyReplaceAddressSpace(llvm::Value *AI, llvm::Value *rep, bool legal)
llvm::FunctionType * getFunctionTypeForClone(llvm::FunctionType *FTy, DerivativeMode mode, unsigned width, llvm::Type *additionalArg, llvm::ArrayRef< DIFFE_TYPE > constant_args, bool diffeReturnArg, bool returnTape, bool returnPrimal, bool returnShadow)
void ReplaceFunctionImplementation(llvm::Module &M)
bool DetectReadonlyOrThrow(llvm::Module &M)
static llvm::SmallPtrSet< llvm::BasicBlock *, 4 > getGuaranteedUnreachable(llvm::Function *F)
static void getExitBlocks(const llvm::Loop *L, llvm::SmallPtrSetImpl< llvm::BasicBlock * > &ExitBlocks)
static void calculateUnusedValues(const llvm::Function &oldFunc, llvm::SmallPtrSetImpl< const llvm::Value * > &unnecessaryValues, llvm::SmallPtrSetImpl< const llvm::Instruction * > &unnecessaryInstructions, bool returnValue, llvm::function_ref< bool(const llvm::Value *)> valneeded, llvm::function_ref< UseReq(const llvm::Instruction *)> instneeded, llvm::function_ref< bool(const llvm::Instruction *, const llvm::Value *)> useneeded)
static void calculateUnusedStores(const llvm::Function &oldFunc, llvm::SmallPtrSetImpl< const llvm::Instruction * > &unnecessaryStores, llvm::function_ref< bool(const llvm::Instruction *)> needStore)
bool LowerSparsification(llvm::Function *F, bool replaceAll=true)
Lower __enzyme_todense, returning if changed.
static llvm::SmallVector< llvm::BasicBlock *, 3 > getLatches(const llvm::Loop *L, const llvm::SmallPtrSetImpl< llvm::BasicBlock * > &ExitBlocks)
static bool isAnyBranch(const llvm::Value *V)
Definition Utils.h:57
static bool endsWith(llvm::StringRef string, llvm::StringRef suffix)
Definition Utils.h:855
DerivativeMode
Definition Utils.h:524
DerivativeMode mode
llvm::Function * oldFunc
llvm::Function * CloneFunctionWithReturns(DerivativeMode mode, unsigned width, llvm::Function *&F, llvm::ValueToValueMapTy &ptrInputs, llvm::ArrayRef< DIFFE_TYPE > constant_args, llvm::SmallPtrSetImpl< llvm::Value * > &constants, llvm::SmallPtrSetImpl< llvm::Value * > &nonconstant, llvm::SmallPtrSetImpl< llvm::Value * > &returnvals, bool returnTape, bool returnPrimal, bool returnShadow, const llvm::Twine &name, llvm::ValueMap< const llvm::Value *, AssertingReplacingVH > *VMapO, bool diffeReturnArg, llvm::Type *additionalArg=nullptr)
std::map< llvm::Function *, llvm::Function * > CloneOrigin
llvm::Function * preprocessForClone(llvm::Function *F, DerivativeMode mode)
llvm::ModuleAnalysisManager MAM
void optimizeIntermediate(llvm::Function *F)
llvm::LoopAnalysisManager LAM
void AlwaysInline(llvm::Function *NewF)
llvm::AAResults & getAAResultsFromFunction(llvm::Function *NewF)
std::map< std::pair< llvm::Function *, DerivativeMode >, llvm::Function * > cache
llvm::FunctionAnalysisManager FAM
void LowerAllocAddr(llvm::Function *NewF)
PreProcessCache(PreProcessCache &)=delete
PreProcessCache(PreProcessCache &&prev)
void ReplaceReallocs(llvm::Function *NewF, bool mem2reg=false)
Calls to realloc with an appropriate implementation.