Enzyme main
Loading...
Searching...
No Matches
LibraryFuncs.h
Go to the documentation of this file.
1//===- LibraryFuncs.h - Utilities for handling library functions ---------===//
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}, author = {Moses, William S. and
13// Churavy, Valentin}, booktitle = {Advances in Neural Information Processing
14// Systems 33}, year = {2020}, note = {To appear in},
15// }
16//
17//===----------------------------------------------------------------------===//
18//
19// This file defines miscelaious utilities for handling library functions.
20//
21//===----------------------------------------------------------------------===//
22
23#ifndef LIBRARYFUNCS_H_
24#define LIBRARYFUNCS_H_
25
26#include <llvm/ADT/StringMap.h>
27#include <llvm/Analysis/AliasAnalysis.h>
28#include <llvm/Analysis/TargetLibraryInfo.h>
29#include <llvm/IR/IRBuilder.h>
30#include <llvm/IR/InlineAsm.h>
31#include <llvm/IR/Instructions.h>
32
33#include "Utils.h"
34
35class GradientUtils;
36extern llvm::StringMap<std::function<llvm::Value *(
37 llvm::IRBuilder<> &, llvm::CallInst *, llvm::ArrayRef<llvm::Value *>,
38 GradientUtils *)>>
40extern llvm::StringMap<
41 std::function<llvm::CallInst *(llvm::IRBuilder<> &, llvm::Value *)>>
43
44/// Return whether a given function is a known C/C++ memory allocation function
45/// For updating below one should read MemoryBuiltins.cpp, TargetLibraryInfo.cpp
46static inline bool isAllocationFunction(const llvm::StringRef name,
47 const llvm::TargetLibraryInfo &TLI) {
48 if (name == "enzyme_allocator")
49 return true;
50 if (name == "calloc" || name == "malloc")
51 return true;
52 if (name == "_mlir_memref_to_llvm_alloc")
53 return true;
54 if (name == "swift_allocObject")
55 return true;
56 if (name == "__size_returning_new_experiment")
57 return true;
58 if (name == "__rust_alloc" || name == "__rust_alloc_zeroed")
59 return true;
60 if (name == "julia.gc_alloc_obj" || name == "jl_gc_alloc_typed" ||
61 name == "ijl_gc_alloc_typed")
62 return true;
63 if (shadowHandlers.find(name) != shadowHandlers.end())
64 return true;
65
66 using namespace llvm;
67 llvm::LibFunc libfunc;
68#if LLVM_VERSION_MAJOR >= 24
69 libfunc = TLI.getLibFunc(name);
70 if (libfunc == NotLibFunc)
71 return false;
72#else
73 if (!TLI.getLibFunc(name, libfunc))
74 return false;
75#endif
76
77 switch (libfunc) {
78 case LibFunc_malloc: // malloc(unsigned int);
79 case LibFunc_valloc: // valloc(unsigned int);
80 case LibFunc_aligned_alloc: // aligned_alloc(size_t align, size_t size);
81
82 case LibFunc_Znwj: // new(unsigned int);
83 case LibFunc_ZnwjRKSt9nothrow_t: // new(unsigned int, nothrow);
84 case LibFunc_ZnwjSt11align_val_t: // new(unsigned int, align_val_t)
85 case LibFunc_ZnwjSt11align_val_tRKSt9nothrow_t: // new(unsigned int,
86 // align_val_t, nothrow)
87
88 case LibFunc_Znwm: // new(unsigned long);
89 case LibFunc_ZnwmRKSt9nothrow_t: // new(unsigned long, nothrow);
90 case LibFunc_ZnwmSt11align_val_t: // new(unsigned long, align_val_t)
91 case LibFunc_ZnwmSt11align_val_tRKSt9nothrow_t: // new(unsigned long,
92 // align_val_t, nothrow)
93
94 case LibFunc_Znaj: // new[](unsigned int);
95 case LibFunc_ZnajRKSt9nothrow_t: // new[](unsigned int, nothrow);
96 case LibFunc_ZnajSt11align_val_t: // new[](unsigned int, align_val_t)
97 case LibFunc_ZnajSt11align_val_tRKSt9nothrow_t: // new[](unsigned int,
98 // align_val_t, nothrow)
99
100 case LibFunc_Znam: // new[](unsigned long);
101 case LibFunc_ZnamRKSt9nothrow_t: // new[](unsigned long, nothrow);
102 case LibFunc_ZnamSt11align_val_t: // new[](unsigned long, align_val_t)
103 case LibFunc_ZnamSt11align_val_tRKSt9nothrow_t: // new[](unsigned long,
104 // align_val_t, nothrow)
105
106 case LibFunc_msvc_new_int: // new(unsigned int);
107 case LibFunc_msvc_new_int_nothrow: // new(unsigned int, nothrow);
108 case LibFunc_msvc_new_longlong: // new(unsigned long long);
109 case LibFunc_msvc_new_longlong_nothrow: // new(unsigned long long, nothrow);
110 case LibFunc_msvc_new_array_int: // new[](unsigned int);
111 case LibFunc_msvc_new_array_int_nothrow: // new[](unsigned int, nothrow);
112 case LibFunc_msvc_new_array_longlong: // new[](unsigned long long);
113 case LibFunc_msvc_new_array_longlong_nothrow: // new[](unsigned long long,
114 // nothrow);
115
116 // TODO strdup, strndup
117
118 // TODO call, realloc, reallocf
119
120 // TODO (perhaps) posix_memalign
121 return true;
122 default:
123 return false;
124 }
125}
126
127/// Return whether a given function frees a CUDA allocation. Their first
128/// argument is the allocation being freed, which for the driver API is a
129/// CUdeviceptr -- an integer at the LLVM level rather than a pointer.
130static inline bool isCudaDeallocationFunction(const llvm::StringRef name) {
131 return name == "cuMemFree" || name == "cuMemFree_v2" ||
132 name == "cuMemFreeAsync" || name == "cudaFree" ||
133 name == "cudaFreeAsync" || name == "cudaFreeHost";
134}
135
136/// Return whether a given function is a known C/C++ memory deallocation
137/// function For updating below one should read MemoryBuiltins.cpp,
138/// TargetLibraryInfo.cpp
139static inline bool isDeallocationFunction(const llvm::StringRef name,
140 const llvm::TargetLibraryInfo &TLI) {
141 using namespace llvm;
142 llvm::LibFunc libfunc;
143 if (name == "_ZdlPvmSt11align_val_t")
144 return true;
145#if LLVM_VERSION_MAJOR >= 24
146 libfunc = TLI.getLibFunc(name);
147 if (libfunc == NotLibFunc) {
148#else
149 if (!TLI.getLibFunc(name, libfunc)) {
150#endif
151 if (name == "free")
152 return true;
153 if (name == "_mlir_memref_to_llvm_free")
154 return true;
155 if (name == "__rust_dealloc")
156 return true;
157 if (name == "swift_release")
158 return true;
159 // Counterparts of the CUDA allocations recognized in
160 // AdjointGenerator::handleKnownCallDerivatives.
162 return true;
163 return false;
164 }
165
166 switch (libfunc) {
167 // void free(void*);
168 case LibFunc_free:
169
170 // void operator delete[](void*);
171 case LibFunc_ZdaPv:
172 // void operator delete(void*);
173 case LibFunc_ZdlPv:
174 // void operator delete[](void*);
175 case LibFunc_msvc_delete_array_ptr32:
176 // void operator delete[](void*);
177 case LibFunc_msvc_delete_array_ptr64:
178 // void operator delete(void*);
179 case LibFunc_msvc_delete_ptr32:
180 // void operator delete(void*);
181 case LibFunc_msvc_delete_ptr64:
182
183 // void operator delete[](void*, nothrow);
184 case LibFunc_ZdaPvRKSt9nothrow_t:
185 // void operator delete[](void*, unsigned int);
186 case LibFunc_ZdaPvj:
187 // void operator delete[](void*, unsigned long);
188 case LibFunc_ZdaPvm:
189 // void operator delete(void*, nothrow);
190 case LibFunc_ZdlPvRKSt9nothrow_t:
191 // void operator delete(void*, unsigned int);
192 case LibFunc_ZdlPvj:
193 // void operator delete(void*, unsigned long);
194 case LibFunc_ZdlPvm:
195 // void operator delete(void*, align_val_t)
196 case LibFunc_ZdlPvSt11align_val_t:
197 // void operator delete[](void*, align_val_t)
198 case LibFunc_ZdaPvSt11align_val_t:
199 // void operator delete[](void*, unsigned int);
200 case LibFunc_msvc_delete_array_ptr32_int:
201 // void operator delete[](void*, nothrow);
202 case LibFunc_msvc_delete_array_ptr32_nothrow:
203 // void operator delete[](void*, unsigned long long);
204 case LibFunc_msvc_delete_array_ptr64_longlong:
205 // void operator delete[](void*, nothrow);
206 case LibFunc_msvc_delete_array_ptr64_nothrow:
207 // void operator delete(void*, unsigned int);
208 case LibFunc_msvc_delete_ptr32_int:
209 // void operator delete(void*, nothrow);
210 case LibFunc_msvc_delete_ptr32_nothrow:
211 // void operator delete(void*, unsigned long long);
212 case LibFunc_msvc_delete_ptr64_longlong:
213 // void operator delete(void*, nothrow);
214 case LibFunc_msvc_delete_ptr64_nothrow:
215 // void operator delete(void*, align_val_t, nothrow)
216 case LibFunc_ZdlPvSt11align_val_tRKSt9nothrow_t:
217 // void operator delete[](void*, align_val_t, nothrow)
218 case LibFunc_ZdaPvSt11align_val_tRKSt9nothrow_t:
219 return true;
220 default:
221 return false;
222 }
223}
224
225static inline void zeroKnownAllocation(llvm::IRBuilder<> &bb,
226 llvm::Value *toZero,
227 llvm::ArrayRef<llvm::Value *> argValues,
228 const llvm::StringRef funcName,
229 const llvm::TargetLibraryInfo &TLI,
230 llvm::CallInst *orig) {
231 using namespace llvm;
232 assert(isAllocationFunction(funcName, TLI));
233
234 // Don't re-zero an already-zero buffer
235 if (funcName == "calloc" || funcName == "__rust_alloc_zeroed")
236 return;
237
238 Value *allocSize = argValues[0];
239 if (funcName == "julia.gc_alloc_obj" || funcName == "jl_gc_alloc_typed" ||
240 funcName == "ijl_gc_alloc_typed" || funcName == "aligned_alloc") {
241 allocSize = argValues[1];
242 }
243 if (funcName == "enzyme_allocator") {
244 auto index = getAllocationIndexFromCall(orig);
245 allocSize = argValues[*index];
246 }
247 Value *dst_arg = toZero;
248
249 if (funcName == "__size_returning_new_experiment")
250 dst_arg = bb.CreateExtractValue(dst_arg, 0);
251
252 if (dst_arg->getType()->isIntegerTy())
253 dst_arg = bb.CreateIntToPtr(dst_arg, getInt8PtrTy(toZero->getContext()));
254 else
255 dst_arg = bb.CreateBitCast(
256 dst_arg, getInt8PtrTy(toZero->getContext(),
257 toZero->getType()->getPointerAddressSpace()));
258
259 auto val_arg = ConstantInt::get(Type::getInt8Ty(toZero->getContext()), 0);
260 auto len_arg =
261 bb.CreateZExtOrTrunc(allocSize, Type::getInt64Ty(toZero->getContext()));
262
263 auto memset = bb.CreateMemSet(dst_arg, val_arg, len_arg, MaybeAlign());
264 memset->addParamAttr(0, Attribute::NonNull);
265 if (auto CI = dyn_cast<ConstantInt>(allocSize)) {
266 auto derefBytes = CI->getLimitedValue();
267#if LLVM_VERSION_MAJOR >= 14
268 memset->addDereferenceableParamAttr(0, derefBytes);
269 memset->setAttributes(
270 memset->getAttributes().addDereferenceableOrNullParamAttr(
271 memset->getContext(), 0, derefBytes));
272#else
273 memset->addDereferenceableAttr(llvm::AttributeList::FirstArgIndex,
274 derefBytes);
275 memset->addDereferenceableOrNullAttr(llvm::AttributeList::FirstArgIndex,
276 derefBytes);
277#endif
278 }
279}
280
281/// Perform the corresponding deallocation of tofree, given it was allocated by
282/// allocationfn
283// For updating below one should read MemoryBuiltins.cpp, TargetLibraryInfo.cpp
284llvm::CallInst *freeKnownAllocation(llvm::IRBuilder<> &builder,
285 llvm::Value *tofree,
286 llvm::StringRef allocationfn,
287 const llvm::DebugLoc &debuglocation,
288 const llvm::TargetLibraryInfo &TLI,
289 llvm::CallInst *orig,
290 GradientUtils *gutils);
291
292static inline bool isAllocationCall(const llvm::Value *TmpOrig,
293 llvm::TargetLibraryInfo &TLI) {
294 if (auto *CI = llvm::dyn_cast<llvm::CallBase>(TmpOrig)) {
295 auto AttrList =
296 CI->getAttributes().getAttributes(llvm::AttributeList::FunctionIndex);
297 if (AttrList.hasAttribute("enzyme_allocation"))
298 return true;
299 if (auto Fn = getFunctionFromCall(CI))
300 if (Fn->hasFnAttribute("enzyme_allocation"))
301 return true;
303 }
304 return false;
305}
306
307static inline bool isDeallocationCall(const llvm::Value *TmpOrig,
308 llvm::TargetLibraryInfo &TLI) {
309 if (auto *CI = llvm::dyn_cast<llvm::CallBase>(TmpOrig)) {
311 }
312 return false;
313}
314
315#endif
static bool isDeallocationFunction(const llvm::StringRef name, const llvm::TargetLibraryInfo &TLI)
Return whether a given function is a known C/C++ memory deallocation function For updating below one ...
static bool isDeallocationCall(const llvm::Value *TmpOrig, llvm::TargetLibraryInfo &TLI)
llvm::StringMap< std::function< llvm::CallInst *(llvm::IRBuilder<> &, llvm::Value *)> > shadowErasers
static bool isCudaDeallocationFunction(const llvm::StringRef name)
Return whether a given function frees a CUDA allocation.
llvm::StringMap< std::function< llvm::Value *(llvm::IRBuilder<> &, llvm::CallInst *, llvm::ArrayRef< llvm::Value * >, GradientUtils *)> > shadowHandlers
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)
Perform the corresponding deallocation of tofree, given it was allocated by allocationfn.
static bool isAllocationFunction(const llvm::StringRef name, const llvm::TargetLibraryInfo &TLI)
Return whether a given function is a known C/C++ memory allocation function For updating below one sh...
static void zeroKnownAllocation(llvm::IRBuilder<> &bb, llvm::Value *toZero, llvm::ArrayRef< llvm::Value * > argValues, const llvm::StringRef funcName, const llvm::TargetLibraryInfo &TLI, llvm::CallInst *orig)
static bool isAllocationCall(const llvm::Value *TmpOrig, llvm::TargetLibraryInfo &TLI)
static Operation * getFunctionFromCall(CallOpInterface iface)
static llvm::PointerType * getInt8PtrTy(llvm::LLVMContext &Context, unsigned AddressSpace=0)
Definition Utils.h:1324
static llvm::StringRef getFuncNameFromCall(const llvm::CallBase *op)
Definition Utils.h:1470
static llvm::Optional< size_t > getAllocationIndexFromCall(const llvm::CallBase *op)
Definition Utils.h:1519
llvm::TargetLibraryInfo & TLI
Various analysis results of newFunc.