Enzyme main
Loading...
Searching...
No Matches
PreserveNVVM.cpp
Go to the documentation of this file.
1//===- PreserveNVVM.cpp - Mark NVVM attributes for preservation. -------===//
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 contains createPreserveNVVM, a transformation pass that marks
22// calls to __nv_* functions, marking them as noinline as implementing the llvm
23// intrinsic.
24//
25//===----------------------------------------------------------------------===//
26#include <llvm/Config/llvm-config.h>
27
28#include "llvm/ADT/ArrayRef.h"
29#include "llvm/ADT/SetVector.h"
30#include "llvm/ADT/SmallVector.h"
31#include "llvm/ADT/StringMap.h"
32
33#include "llvm/ADT/SmallPtrSet.h"
34#include "llvm/ADT/SmallSet.h"
35#include "llvm/IR/Constants.h"
36#include "llvm/IR/Function.h"
37#include "llvm/IR/GlobalVariable.h"
38#include "llvm/IR/Instructions.h"
39#include "llvm/IR/Module.h"
40#include "llvm/Support/raw_ostream.h"
41
42#include "llvm/Support/TimeProfiler.h"
43
44#include "llvm/Pass.h"
45
46#include "llvm/Transforms/Utils.h"
47#include "llvm/Transforms/Utils/ModuleUtils.h"
48
49#include <map>
50
51#include "PreserveNVVM.h"
52#include "Utils.h"
53
54using namespace llvm;
55#ifdef DEBUG_TYPE
56#undef DEBUG_TYPE
57#endif
58#define DEBUG_TYPE "preserve-nvvm"
59
60#if LLVM_VERSION_MAJOR >= 14
61#define addAttribute addAttributeAtIndex
62#endif
63
64#ifndef ENZYME_ENABLE_NVVM_ATTRIBUTION
65#define ENZYME_ENABLE_NVVM_ATTRIBUTION 1
66#endif
67
68/// Mark F itself as inactive, and additionally mark everything in its body, so
69/// that differentiating through the body is a no-op rather than merely calls to
70/// F being inactive.
71void markFunctionInactive(Function &F) {
72 F.addAttribute(AttributeList::FunctionIndex,
73 Attribute::get(F.getContext(), "enzyme_inactive"));
74 auto MD = MDNode::get(F.getContext(), {});
75 for (auto &BB : F) {
76 for (auto &I : BB) {
77 if (auto CB = dyn_cast<CallBase>(&I)) {
78 CB->addFnAttr(llvm::Attribute::get(F.getContext(), "enzyme_inactive"));
79 } else {
80 I.setMetadata("enzyme_inactive", MD);
81 }
82 }
83 }
84}
85
86/// If F is a trivial forwarding wrapper -- one basic block holding a single
87/// call whose operands are F's own arguments in order, followed by a return of
88/// that call's result -- copy the callee's function attributes onto F, except
89/// those that only steer inlining or code placement.
90///
91/// preserveLinkage marks libdevice wrappers such as __nv_sqrt (whose body is
92/// just `call @llvm.nvvm.sqrt.rn.d; ret`) noinline so Enzyme can recognize
93/// them by name. Inlining is how the wrapper would normally pick up the
94/// intrinsic's attributes, and FunctionAttrs never infers `speculatable`, so
95/// without this the wrapper stays an opaque call that LICM cannot hoist and
96/// SimplifyCFG cannot if-convert. In a loop, that forces Enzyme to cache
97/// per-iteration values that would otherwise be loop invariant.
98///
99/// Returns whether F changed.
100bool copyForwardedCalleeAttrs(Function &F) {
101 if (F.isDeclaration() || F.isVarArg() || F.size() != 1)
102 return false;
103 BasicBlock &BB = F.getEntryBlock();
104 if (BB.size() != 2)
105 return false;
106 auto *CI = dyn_cast<CallInst>(&BB.front());
107 auto *RI = dyn_cast<ReturnInst>(BB.getTerminator());
108 if (!CI || !RI)
109 return false;
110 Function *Callee = CI->getCalledFunction();
111 if (!Callee || Callee == &F || CI->arg_size() != F.arg_size())
112 return false;
113 for (unsigned i = 0, e = F.arg_size(); i < e; ++i)
114 if (CI->getArgOperand(i) != F.getArg(i))
115 return false;
116 if (F.getReturnType()->isVoidTy()) {
117 if (RI->getReturnValue())
118 return false;
119 } else if (RI->getReturnValue() != CI) {
120 return false;
121 }
122
123 bool changed = false;
124#if LLVM_VERSION_MAJOR >= 16
125 // F only runs the callee, so its effects are at most the callee's.
126 auto ME = F.getMemoryEffects() & Callee->getMemoryEffects();
127 if (ME != F.getMemoryEffects()) {
128 F.setMemoryEffects(ME);
129 changed = true;
130 }
131#endif
132 // Only semantic guarantees that hold for a call forwarded verbatim. Anything
133 // describing how the callee's own body was compiled (inlining hints, code
134 // placement, sanitizers, stack protection, coroutine state, ...) stays put.
135 static const Attribute::AttrKind Forwardable[] = {
136 Attribute::Speculatable,
137 Attribute::NoUnwind,
138 Attribute::WillReturn,
139 Attribute::MustProgress,
140 Attribute::NoSync,
141 Attribute::NoFree,
142 Attribute::NoRecurse,
143 Attribute::NoCallback,
144 Attribute::NoReturn,
145 Attribute::Convergent,
146#if LLVM_VERSION_MAJOR >= 23
147 Attribute::NoCreateUndefOrPoison,
148#endif
149#if LLVM_VERSION_MAJOR < 16
150 Attribute::ReadNone,
151 Attribute::ReadOnly,
152 Attribute::WriteOnly,
153 Attribute::ArgMemOnly,
154 Attribute::InaccessibleMemOnly,
155 Attribute::InaccessibleMemOrArgMemOnly,
156#endif
157 };
158 for (auto K : Forwardable) {
159 if (!Callee->hasFnAttribute(K) || F.hasFnAttribute(K))
160 continue;
161 F.addFnAttr(K);
162 changed = true;
163 }
164 // llvm.nvvm.sqrt.* (rn/rz/rm/rp, ftz, approx) are declared IntrNoMem but not
165 // IntrSpeculatable upstream, unlike llvm.nvvm.fabs or llvm.sqrt itself, even
166 // though InstCombine later rewrites the rn forms to llvm.sqrt. They have no
167 // side effects or UB (a negative input just yields NaN), so __nv_sqrt and
168 // __nv_sqrtf may be hoisted and if-converted like any other math call.
169 if (startsWith(Callee->getName(), "llvm.nvvm.sqrt.") &&
170 !F.hasFnAttribute(Attribute::Speculatable)) {
171 F.addFnAttr(Attribute::Speculatable);
172 changed = true;
173 }
174 return changed;
175}
176
177//! Returns whether changed.
178bool preserveLinkage(bool Begin, Function &F, bool Inlining = true) {
179 if (Begin && !F.hasFnAttribute("prev_fixup")) {
180 F.addFnAttr("prev_fixup");
181 if (F.hasFnAttribute(Attribute::AlwaysInline))
182 F.addFnAttr("prev_always_inline");
183 if (F.hasFnAttribute(Attribute::NoInline))
184 F.addFnAttr("prev_no_inline");
185 if (Inlining) {
186 F.removeFnAttr(Attribute::AlwaysInline);
187 F.addFnAttr(Attribute::NoInline);
189 }
190 F.addFnAttr("prev_linkage", std::to_string(F.getLinkage()));
191 F.setLinkage(Function::LinkageTypes::ExternalLinkage);
192 return true;
193 }
194 return false;
195}
196
197static void handleFunctionLike(bool Begin, Value *Target,
198 StringRef FunctionName) {
199 while (auto *CE = dyn_cast<ConstantExpr>(Target))
200 Target = CE->getOperand(0);
201
202 if (FunctionName.empty()) {
203 errs() << "Use of enzyme_function_like requires a non-empty function "
204 "name\n";
205 llvm_unreachable("enzyme_function_like");
206 }
207
208 auto *F = dyn_cast<Function>(Target);
209 if (!F) {
210 errs() << "First argument of enzyme_function_like must be a constant "
211 "function\n"
212 << *Target << "\n";
213 llvm_unreachable("enzyme_function_like");
214 }
215
216 // Warn on conflicting registrations while preserving the existing
217 // last-registration-wins behavior.
218 Attribute Existing = F->getFnAttribute("enzyme_math");
219 if (Existing.isValid() && Existing.getValueAsString() != FunctionName) {
220 errs() << "warning: conflicting enzyme_function_like registrations for "
221 "function '"
222 << F->getName() << "': replacing '" << Existing.getValueAsString()
223 << "' with '" << FunctionName << "'\n";
224 }
225
226 F->addAttribute(AttributeList::FunctionIndex,
227 Attribute::get(F->getContext(), "enzyme_math", FunctionName));
228 preserveLinkage(Begin, *F);
229}
230
231// Return true if the module has a triple indicating an nvptx target, false
232// otherwise.
233bool isTargetNVPTX(llvm::Module &M) {
234#if LLVM_VERSION_MAJOR > 20
235 return M.getTargetTriple().getArch() == Triple::ArchType::nvptx ||
236 M.getTargetTriple().getArch() == Triple::ArchType::nvptx64;
237#else
238 return M.getTargetTriple().find("nvptx") != std::string::npos;
239#endif
240}
241
242template <const char *handlername, DerivativeMode Mode, int numargs>
243static void
244handleCustomDerivative(llvm::Module &M, llvm::GlobalVariable &g,
245 SmallVectorImpl<GlobalVariable *> &globalsToErase,
246 bool PreserveCustomRuleLinkage) {
247 if (g.hasInitializer()) {
248 if (auto CA = dyn_cast<ConstantAggregate>(g.getInitializer())) {
249 if (CA->getNumOperands() < numargs) {
250 llvm::errs() << M << "\n";
251 llvm::errs() << "Use of " << handlername
252 << " must be a "
253 "constant of size at least "
254 << numargs << " " << g << "\n";
255 llvm_unreachable(handlername);
256 } else {
257 Function *Fs[numargs];
258 for (size_t i = 0; i < numargs; i++) {
259 Value *V = CA->getOperand(i);
260 while (auto CE = dyn_cast<ConstantExpr>(V)) {
261 V = CE->getOperand(0);
262 }
263 if (auto CA = dyn_cast<ConstantAggregate>(V))
264 V = CA->getOperand(0);
265 while (auto CE = dyn_cast<ConstantExpr>(V)) {
266 V = CE->getOperand(0);
267 }
268 if (auto F = dyn_cast<Function>(V)) {
269 Fs[i] = F;
270 } else {
271 llvm::errs() << M << "\n";
272 llvm::errs() << "Param of " << handlername
273 << " must be a "
274 "function"
275 << g << "\n"
276 << *V << "\n";
277 llvm_unreachable(handlername);
278 }
279 }
280
281 SmallSet<size_t, 1> byref;
282
283 if constexpr (Mode == DerivativeMode::ReverseModeGradient) {
284 assert(numargs >= 3);
285 for (size_t i = numargs; i < CA->getNumOperands(); i++) {
286 Value *V = CA->getOperand(i);
287 while (auto CE = dyn_cast<ConstantExpr>(V)) {
288 V = CE->getOperand(0);
289 }
290 if (auto CA = dyn_cast<ConstantAggregate>(V))
291 V = CA->getOperand(0);
292 while (auto CE = dyn_cast<ConstantExpr>(V)) {
293 V = CE->getOperand(0);
294 }
295 if (auto GV = dyn_cast<GlobalVariable>(V)) {
296 if (GV->isConstant())
297 if (auto C = GV->getInitializer())
298 if (auto CA = dyn_cast<ConstantDataArray>(C))
299 if (CA->getType()->getElementType()->isIntegerTy(8) &&
300 CA->isCString()) {
301
302 auto str = CA->getAsCString();
303 bool legal = startsWith(str, "byref_");
304 size_t argnum = 0;
305 if (legal) {
306 for (size_t i = str.size() - 1, len = strlen("byref_");
307 i >= len; i--) {
308 char c = str[i];
309 if (c < '0' || c > '9') {
310 legal = false;
311 break;
312 }
313 argnum *= 10;
314 argnum += c - '0';
315 }
316 }
317 if (legal) {
318 byref.insert(argnum);
319 continue;
320 }
321 }
322 }
323 llvm::errs() << M << "\n";
324 llvm::errs() << "Use of " << handlername
325 << " possible post args include 'byref_ret'"
326 << "\n";
327 llvm_unreachable(handlername);
328 }
329
330 if (byref.size())
331 for (size_t fn = 1; fn <= 2; fn++) {
332 Function *F = Fs[fn];
333 bool need = false;
334 size_t nonSRetSize = 0;
335 for (size_t i = 0; i < F->arg_size(); i++)
336 if (!F->hasParamAttribute(i, Attribute::StructRet))
337 nonSRetSize++;
338 for (auto r : byref)
339 if (r < nonSRetSize)
340 need = true;
341 if (!need)
342 continue;
343
344 SmallVector<Type *, 3> args;
345 Type *sretTy = nullptr;
346 size_t realidx = 0;
347 size_t i = 0;
348 for (auto &arg : F->args()) {
349 if (!F->hasParamAttribute(i, Attribute::StructRet)) {
350 if (!byref.count(realidx))
351 args.push_back(arg.getType());
352 else {
353 // TODO in opaque pointers
354 Type *subTy = nullptr;
355#if LLVM_VERSION_MAJOR < 17
356 subTy = arg.getType()->getPointerElementType();
357#endif
358 assert(subTy);
359 args.push_back(subTy);
360 }
361 realidx++;
362 } else {
363 llvm::Type *T = nullptr;
364#if LLVM_VERSION_MAJOR > 12
365 T = F->getParamAttribute(i, Attribute::StructRet)
366 .getValueAsType();
367#else
368 T = arg.getType()->getPointerElementType();
369#endif
370 sretTy = T;
371 }
372 i++;
373 }
374 Type *RT = F->getReturnType();
375 if (sretTy) {
376 assert(RT->isVoidTy());
377 RT = sretTy;
378 }
379 FunctionType *FTy =
380 FunctionType::get(RT, args, F->getFunctionType()->isVarArg());
381 Function *NewF =
382 Function::Create(FTy, Function::LinkageTypes::InternalLinkage,
383 "fixbyval_" + F->getName(), F->getParent());
384
385 AllocaInst *AI = nullptr;
386 BasicBlock *BB =
387 BasicBlock::Create(NewF->getContext(), "entry", NewF);
388 IRBuilder<> bb(BB);
389 if (sretTy)
390 AI = bb.CreateAlloca(sretTy);
391 SmallVector<Value *, 3> argVs;
392 auto arg = NewF->arg_begin();
393 realidx = 0;
394 for (size_t i = 0; i < F->arg_size(); i++) {
395 if (!F->hasParamAttribute(i, Attribute::StructRet)) {
396 arg->setName("arg" + Twine(realidx));
397 if (!byref.count(realidx))
398 argVs.push_back(arg);
399 else {
400 auto A = bb.CreateAlloca(arg->getType());
401 bb.CreateStore(arg, A);
402 argVs.push_back(A);
403 }
404 realidx++;
405 ++arg;
406 } else {
407 argVs.push_back(AI);
408 }
409 }
410 auto cal = bb.CreateCall(F, argVs);
411 cal->setCallingConv(F->getCallingConv());
412
413 if (sretTy) {
414 Value *res = bb.CreateLoad(sretTy, AI);
415 bb.CreateRet(res);
416 } else if (!RT->isVoidTy()) {
417 bb.CreateRet(cal);
418 } else
419 bb.CreateRetVoid();
420
421 Fs[fn] = NewF;
422 }
423
424 if (PreserveCustomRuleLinkage)
425 preserveLinkage(true, *Fs[1], false);
426 Fs[0]->setMetadata(
427 "enzyme_augment",
428 llvm::MDTuple::get(Fs[0]->getContext(),
429 {llvm::ValueAsMetadata::get(Fs[1])}));
430 if (PreserveCustomRuleLinkage)
431 preserveLinkage(true, *Fs[2], false);
432 Fs[0]->setMetadata(
433 "enzyme_gradient",
434 llvm::MDTuple::get(Fs[0]->getContext(),
435 {llvm::ValueAsMetadata::get(Fs[2])}));
436 } else if constexpr (Mode == DerivativeMode::ForwardMode) {
437 assert(numargs == 2);
438 if (PreserveCustomRuleLinkage)
439 preserveLinkage(true, *Fs[1], false);
440 Fs[0]->setMetadata(
441 "enzyme_derivative",
442 llvm::MDTuple::get(Fs[0]->getContext(),
443 {llvm::ValueAsMetadata::get(Fs[1])}));
444 } else if constexpr (Mode == DerivativeMode::ForwardModeSplit) {
445 assert(numargs == 3);
446 if (PreserveCustomRuleLinkage)
447 preserveLinkage(true, *Fs[1], false);
448 Fs[0]->setMetadata(
449 "enzyme_augment",
450 llvm::MDTuple::get(Fs[0]->getContext(),
451 {llvm::ValueAsMetadata::get(Fs[1])}));
452 if (PreserveCustomRuleLinkage)
453 preserveLinkage(true, *Fs[2], false);
454 Fs[0]->setMetadata(
455 "enzyme_splitderivative",
456 llvm::MDTuple::get(Fs[0]->getContext(),
457 {llvm::ValueAsMetadata::get(Fs[2])}));
458 } else
459 assert("Unknown mode");
460 }
461 } else if (isTargetNVPTX(M)) {
462 llvm::errs() << M << "\n";
463 llvm::errs() << "Use of " << handlername
464 << " must be a "
465 "constant aggregate "
466 << g << "\n";
467 llvm_unreachable(handlername);
468 }
469 } else {
470 llvm::errs() << M << "\n";
471 llvm::errs() << "Use of " << handlername
472 << " must be a "
473 "constant array of size "
474 << numargs << " " << g << "\n";
475 llvm_unreachable(handlername);
476 }
477 globalsToErase.push_back(&g);
478}
479
480bool preserveNVVM(bool Begin, Module &M,
481 bool PreserveCustomRuleLinkage = true) {
482 bool changed = false;
483 constexpr static const char gradient_handler_name[] =
484 "__enzyme_register_gradient";
485 constexpr static const char derivative_handler_name[] =
486 "__enzyme_register_derivative";
487 constexpr static const char splitderivative_handler_name[] =
488 "__enzyme_register_splitderivative";
489
490 // Flang cannot construct the constant function/string aggregate used by
491 // __enzyme_function_like. The Fortran binding instead passes a function and
492 // a BIND(C) global whose name is enzyme_math_<function>.
493 if (Begin) {
494 SmallVector<CallInst *, 4> functionLikeCalls;
495 for (Function &Caller : M) {
496 for (BasicBlock &BB : Caller) {
497 for (Instruction &I : BB) {
498 auto *Call = dyn_cast<CallInst>(&I);
499 if (!Call)
500 continue;
501
502 auto *Hook =
503 dyn_cast<Function>(Call->getCalledOperand()->stripPointerCasts());
504 if (!Hook || !startsWith(Hook->getName(), "f__enzyme_function_like"))
505 continue;
506
507 if (Call->arg_size() != 2) {
508 errs() << "Fortran enzyme_function_like requires exactly a "
509 "function and a function name\n"
510 << *Call << "\n";
511 llvm_unreachable("invalid Fortran enzyme_function_like call");
512 }
513
514 auto *NameGlobal = dyn_cast<GlobalVariable>(
515 Call->getArgOperand(1)->stripPointerCasts());
516 if (!NameGlobal) {
517 errs() << "Second argument of Fortran enzyme_function_like must "
518 "be an enzyme_math_* function name\n"
519 << *Call->getArgOperand(1) << "\n";
520 llvm_unreachable(
521 "invalid Fortran enzyme_function_like function name");
522 }
523
524 StringRef FunctionName = NameGlobal->getName();
525 if (!FunctionName.consume_front("enzyme_math_")) {
526 errs() << "Fortran enzyme_function_like function name must use "
527 "the enzyme_math_* BIND(C) naming convention\n"
528 << *NameGlobal << "\n";
529 llvm_unreachable(
530 "invalid Fortran enzyme_function_like function name");
531 }
532
533 handleFunctionLike(Begin, Call->getArgOperand(0), FunctionName);
534 functionLikeCalls.push_back(Call);
535 changed = true;
536 }
537 }
538 }
539 for (CallInst *Call : functionLikeCalls)
540 Call->eraseFromParent();
541 }
542
543 if (Begin)
544 if (GlobalVariable *GA = M.getGlobalVariable("llvm.global.annotations")) {
545 if (GA->hasInitializer()) {
546 auto AOp = GA->getInitializer();
547 // all metadata are stored in an array of struct of metadata
548 if (ConstantArray *CA = dyn_cast<ConstantArray>(AOp)) {
549 // so iterate over the operands
550 SmallVector<Constant *, 1> replacements;
551 for (Value *CAOp : CA->operands()) {
552 // get the struct, which holds a pointer to the annotated function
553 // as first field, and the annotation as second field
554 ConstantStruct *CS = dyn_cast<ConstantStruct>(CAOp);
555 if (!CS)
556 continue;
557
558 if (CS->getNumOperands() < 2)
559 continue;
560
561 // the second field is a pointer to a global constant Array that
562 // holds the string
563 GlobalVariable *GAnn =
564 dyn_cast<GlobalVariable>(CS->getOperand(1)->getOperand(0));
565
566 ConstantDataArray *A = nullptr;
567
568 if (GAnn)
569 A = dyn_cast<ConstantDataArray>(GAnn->getOperand(0));
570 else
571 A = dyn_cast<ConstantDataArray>(CS->getOperand(1)->getOperand(0));
572
573 if (!A)
574 continue;
575
576 // we have the annotation! Check it's an epona annotation
577 // and process
578 StringRef AS = A->getAsCString();
579
580 Constant *Val = cast<Constant>(CS->getOperand(0));
581 while (auto CE = dyn_cast<ConstantExpr>(Val))
582 Val = CE->getOperand(0);
583
584 Function *Func = dyn_cast<Function>(Val);
585 GlobalVariable *Glob = dyn_cast<GlobalVariable>(Val);
586
587 if (AS == "enzyme_inactive" && Func) {
588 Func->addAttribute(
589 AttributeList::FunctionIndex,
590 Attribute::get(Func->getContext(), "enzyme_inactive"));
591 changed = true;
592 preserveLinkage(Begin, *Func);
593 replacements.push_back(Constant::getNullValue(CAOp->getType()));
594 continue;
595 }
596
597 // Counterparts of the __enzyme_inactivefn and
598 // __enzyme_inactivenoblockfn registration globals below, emitted by
599 // the clang plugin for __attribute__((enzyme_inactive)) and
600 // __attribute__((enzyme_inactive_noblock)). Unlike the bare
601 // enzyme_inactive annotation above these also mark the body of the
602 // function.
603 if ((AS == "enzyme_inactivefn" ||
604 AS == "enzyme_inactivenoblockfn") &&
605 Func) {
607 changed = true;
608 preserveLinkage(Begin, *Func);
609 replacements.push_back(Constant::getNullValue(CAOp->getType()));
610 continue;
611 }
612
613 if (AS == "enzyme_elementwise_read" && Func) {
614 Func->addAttribute(AttributeList::FunctionIndex,
615 Attribute::get(Func->getContext(),
616 "enzyme_elementwise_read"));
617 changed = true;
618 replacements.push_back(Constant::getNullValue(CAOp->getType()));
619 continue;
620 }
621
622 if (AS == "enzyme_shouldrecompute" && Func) {
623 Func->addAttribute(
624 AttributeList::FunctionIndex,
625 Attribute::get(Func->getContext(), "enzyme_shouldrecompute"));
626 changed = true;
627 replacements.push_back(Constant::getNullValue(CAOp->getType()));
628 continue;
629 }
630
631 if (AS == "enzyme_inactive" && Glob) {
632 Glob->setMetadata("enzyme_inactive",
633 MDNode::get(Glob->getContext(), {}));
634 changed = true;
635 replacements.push_back(Constant::getNullValue(CAOp->getType()));
636 continue;
637 }
638
639 if (AS == "enzyme_nofree" && Func) {
640 Func->addAttribute(
641 AttributeList::FunctionIndex,
642 Attribute::get(Func->getContext(), Attribute::NoFree));
643 changed = true;
644 preserveLinkage(Begin, *Func);
645 replacements.push_back(Constant::getNullValue(CAOp->getType()));
646 continue;
647 }
648
649 if (startsWith(AS, "enzyme_function_like") && Func) {
650 auto val = AS.substr(1 + AS.find('='));
651 handleFunctionLike(Begin, Func, val);
652 changed = true;
653 replacements.push_back(Constant::getNullValue(CAOp->getType()));
654 continue;
655 }
656
657 if (AS == "enzyme_sparse_accumulate" && Func) {
658 Func->addAttribute(AttributeList::FunctionIndex,
659 Attribute::get(Func->getContext(),
660 "enzyme_sparse_accumulate"));
661 changed = true;
662 preserveLinkage(Begin, *Func);
663 replacements.push_back(Constant::getNullValue(CAOp->getType()));
664 continue;
665 }
666
667 if (AS == "enzyme_ta_norecur" && (Glob || Func)) {
668 if (Glob) {
669 Glob->setMetadata("enzyme_ta_norecur",
670 MDNode::get(Glob->getContext(), {}));
671 } else if (Func) {
672 Func->addAttribute(
673 AttributeList::FunctionIndex,
674 Attribute::get(Func->getContext(), "enzyme_ta_norecur"));
675 }
676 changed = true;
677 replacements.push_back(Constant::getNullValue(CAOp->getType()));
678 continue;
679 }
680
681 replacements.push_back(cast<Constant>(CAOp));
682 }
683 GA->setInitializer(ConstantArray::get(CA->getType(), replacements));
684 }
685 }
686 }
687
688 for (GlobalVariable &g : M.globals()) {
689 bool customRule = g.getName().contains(gradient_handler_name) ||
690 g.getName().contains(derivative_handler_name) ||
691 g.getName().contains(splitderivative_handler_name);
692 if (customRule && !PreserveCustomRuleLinkage)
693 continue;
694 if (customRule || g.getName().contains("__enzyme_nofree") ||
695 g.getName().contains("__enzyme_inactivefn") ||
696 g.getName().contains("__enzyme_sparse_accumulate") ||
697 g.getName().contains("__enzyme_function_like") ||
698 g.getName().contains("__enzyme_allocation_like")) {
699 if (g.hasInitializer()) {
700 Value *V = g.getInitializer();
701 while (1) {
702 if (auto CE = dyn_cast<ConstantExpr>(V)) {
703 V = CE->getOperand(0);
704 continue;
705 }
706 if (auto CA = dyn_cast<ConstantAggregate>(V)) {
707 V = CA->getOperand(0);
708 continue;
709 }
710 break;
711 }
712 if (auto F = dyn_cast<Function>(V))
713 changed |= preserveLinkage(Begin, *F);
714 }
715 }
716 }
717 SmallVector<GlobalVariable *, 1> toErase;
718 for (GlobalVariable &g : M.globals()) {
719 if (g.getName().contains(gradient_handler_name)) {
720 handleCustomDerivative<gradient_handler_name,
722 M, g, toErase, PreserveCustomRuleLinkage);
723 changed = true;
724 } else if (g.getName().contains(derivative_handler_name)) {
725 handleCustomDerivative<derivative_handler_name,
727 M, g, toErase, PreserveCustomRuleLinkage);
728 changed = true;
729 } else if (g.getName().contains(splitderivative_handler_name)) {
730 handleCustomDerivative<splitderivative_handler_name,
732 M, g, toErase, PreserveCustomRuleLinkage);
733 changed = true;
734 }
735 if (g.getName().contains("__enzyme_inactive_global")) {
736 if (g.hasInitializer()) {
737 Value *V = g.getInitializer();
738 while (1) {
739 if (auto CE = dyn_cast<ConstantExpr>(V)) {
740 V = CE->getOperand(0);
741 continue;
742 }
743 if (auto CA = dyn_cast<ConstantAggregate>(V)) {
744 V = CA->getOperand(0);
745 continue;
746 }
747 break;
748 }
749 if (auto GV = cast<GlobalVariable>(V)) {
750 GV->setMetadata("enzyme_inactive", MDNode::get(g.getContext(), {}));
751 toErase.push_back(&g);
752 changed = true;
753 } else {
754 llvm::errs() << "Param of __enzyme_inactive_global must be a "
755 "global variable"
756 << g << "\n"
757 << *V << "\n";
758 llvm_unreachable("__enzyme_inactive_global");
759 }
760 }
761 }
762 if (g.getName().contains("__enzyme_inactivefn") ||
763 g.getName().contains("__enzyme_inactivenoblockfn")) {
764 if (g.hasInitializer()) {
765 Value *V = g.getInitializer();
766 while (1) {
767 if (auto CE = dyn_cast<ConstantExpr>(V)) {
768 V = CE->getOperand(0);
769 continue;
770 }
771 if (auto CA = dyn_cast<ConstantAggregate>(V)) {
772 V = CA->getOperand(0);
773 continue;
774 }
775 break;
776 }
777 if (auto F = cast<Function>(V)) {
779 toErase.push_back(&g);
780 changed = true;
781 } else {
782 llvm::errs() << "Param of __enzyme_inactivefn must be a "
783 "constant function"
784 << g << "\n"
785 << *V << "\n";
786 llvm_unreachable("__enzyme_inactivefn");
787 }
788 }
789 }
790 if (g.getName().contains("__enzyme_sparse_accumulate")) {
791 if (g.hasInitializer()) {
792 Value *V = g.getInitializer();
793 while (1) {
794 if (auto CE = dyn_cast<ConstantExpr>(V)) {
795 V = CE->getOperand(0);
796 continue;
797 }
798 if (auto CA = dyn_cast<ConstantAggregate>(V)) {
799 V = CA->getOperand(0);
800 continue;
801 }
802 break;
803 }
804 if (auto F = cast<Function>(V)) {
805 F->addAttribute(
806 AttributeList::FunctionIndex,
807 Attribute::get(g.getContext(), "enzyme_sparse_accumulate"));
808 toErase.push_back(&g);
809 changed = true;
810 } else {
811 llvm::errs() << "Param of __enzyme_sparse_accumulate must be a "
812 "constant function"
813 << g << "\n"
814 << *V << "\n";
815 llvm_unreachable("__enzyme_sparse_accumulate");
816 }
817 }
818 }
819 if (g.getName().contains("__enzyme_nofree")) {
820 if (g.hasInitializer()) {
821 Value *V = g.getInitializer();
822 while (1) {
823 if (auto CE = dyn_cast<ConstantExpr>(V)) {
824 V = CE->getOperand(0);
825 continue;
826 }
827 if (auto CA = dyn_cast<ConstantAggregate>(V)) {
828 V = CA->getOperand(0);
829 continue;
830 }
831 break;
832 }
833 if (auto F = cast<Function>(V)) {
834 F->addAttribute(AttributeList::FunctionIndex,
835 Attribute::get(g.getContext(), Attribute::NoFree));
836 toErase.push_back(&g);
837 changed = true;
838 } else {
839 llvm::errs() << "Param of __enzyme_nofree must be a "
840 "constant function"
841 << g << "\n"
842 << *V << "\n";
843 llvm_unreachable("__enzyme_nofree");
844 }
845 }
846 }
847 if (g.getName().contains("__enzyme_function_like")) {
848 if (g.hasInitializer()) {
849 auto CA = dyn_cast<ConstantAggregate>(g.getInitializer());
850 if (!CA) {
851 constexpr StringLiteral Marker = "__enzyme_function_like__";
852 auto MarkerPos = g.getName().rfind(Marker);
853 Value *Target = g.getInitializer()->stripPointerCasts();
854
855 // Ignore globals that are not Fortran function-like registrations.
856 if (MarkerPos == StringRef::npos || !isa<Function>(Target))
857 continue;
858
859 handleFunctionLike(Begin, Target,
860 g.getName().substr(MarkerPos + Marker.size()));
861 toErase.push_back(&g);
862 changed = true;
863 continue;
864 }
865 if (CA->getNumOperands() < 2) {
866 llvm::errs() << "Use of "
867 << "enzyme_function_like"
868 << " must be a "
869 "constant of size at least "
870 << 2 << " " << g << "\n";
871 llvm_unreachable("enzyme_function_like");
872 }
873 Value *V = CA->getOperand(0);
874 Value *name = CA->getOperand(1);
875 while (auto CE = dyn_cast<ConstantExpr>(name)) {
876 name = CE->getOperand(0);
877 }
878 StringRef nameVal;
879 if (auto GV = dyn_cast<GlobalVariable>(name))
880 if (GV->isConstant())
881 if (auto C = GV->getInitializer())
882 if (auto CA = dyn_cast<ConstantDataArray>(C))
883 if (CA->getType()->getElementType()->isIntegerTy(8) &&
884 CA->isCString())
885 nameVal = CA->getAsCString();
886
887 handleFunctionLike(Begin, V, nameVal);
888 toErase.push_back(&g);
889 changed = true;
890 }
891 }
892 if (g.getName().contains("__enzyme_allocation_like")) {
893 if (g.hasInitializer()) {
894 auto CA = dyn_cast<ConstantAggregate>(g.getInitializer());
895 if (!CA || CA->getNumOperands() != 4) {
896 llvm::errs() << "Use of "
897 << "enzyme_allocation_like"
898 << " must be a "
899 "constant of size at least "
900 << 4 << " " << g << "\n";
901 llvm_unreachable("enzyme_allocation_like");
902 }
903 Value *V = CA->getOperand(0);
904 Value *name = CA->getOperand(1);
905 while (auto CE = dyn_cast<ConstantExpr>(V)) {
906 V = CE->getOperand(0);
907 }
908 while (auto CE = dyn_cast<ConstantExpr>(name)) {
909 name = CE->getOperand(0);
910 }
911 Value *deallocind = CA->getOperand(2);
912 while (auto CE = dyn_cast<ConstantExpr>(deallocind)) {
913 deallocind = CE->getOperand(0);
914 }
915 Value *deallocfn = CA->getOperand(3);
916 while (auto CE = dyn_cast<ConstantExpr>(deallocfn)) {
917 deallocfn = CE->getOperand(0);
918 }
919 size_t index = 0;
920 if (isa<ConstantPointerNull>(name)) {
921 // An integer 0 may have been implicitly converted to a null pointer
922 index = 0;
923 } else if (auto CI = dyn_cast<ConstantInt>(name)) {
924 index = CI->getZExtValue();
925 } else {
926 llvm::errs() << *name << "\n";
927 llvm::errs() << "Use of "
928 << "enzyme_allocation_like"
929 << "requires an integer index"
930 << "\n";
931 llvm_unreachable("enzyme_allocation_like");
932 }
933
934 StringRef deallocIndStr;
935 bool foundInd = false;
936 if (auto GV = dyn_cast<GlobalVariable>(deallocind))
937 if (GV->isConstant())
938 if (auto C = GV->getInitializer())
939 if (auto CA = dyn_cast<ConstantDataArray>(C))
940 if (CA->getType()->getElementType()->isIntegerTy(8) &&
941 CA->isCString()) {
942 deallocIndStr = CA->getAsCString();
943 foundInd = true;
944 }
945
946 if (!foundInd) {
947 llvm::errs() << *deallocind << "\n";
948 llvm::errs() << "Use of "
949 << "enzyme_allocation_like"
950 << "requires a deallocation index string"
951 << "\n";
952 llvm_unreachable("enzyme_allocation_like");
953 }
954 if (auto F = dyn_cast<Function>(V)) {
955 F->addAttribute(AttributeList::FunctionIndex,
956 Attribute::get(g.getContext(), "enzyme_allocator",
957 std::to_string(index)));
958 } else {
959 llvm::errs() << "Param of __enzyme_allocation_like must be a "
960 "function"
961 << g << "\n"
962 << *V << "\n";
963 llvm_unreachable("__enzyme_allocation_like");
964 }
965 cast<Function>(V)->addAttribute(AttributeList::FunctionIndex,
966 Attribute::get(g.getContext(),
967 "enzyme_deallocator",
968 deallocIndStr));
969
970 if (auto F = dyn_cast<Function>(deallocfn)) {
971 cast<Function>(V)->setMetadata(
972 "enzyme_deallocator_fn",
973 llvm::MDTuple::get(F->getContext(),
974 {llvm::ValueAsMetadata::get(F)}));
975 changed |= preserveLinkage(Begin, *F);
976 } else {
977 llvm::errs() << "Free fn of __enzyme_allocation_like must be a "
978 "function"
979 << g << "\n"
980 << *deallocfn << "\n";
981 llvm_unreachable("__enzyme_allocation_like");
982 }
983 toErase.push_back(&g);
984 changed = true;
985 }
986 }
987 }
988
989 for (auto G : toErase) {
990 for (auto name : {"llvm.used", "llvm.compiler.used"}) {
991 if (auto V = M.getGlobalVariable(name)) {
992 auto C = cast<ConstantArray>(V->getInitializer());
993 SmallVector<Constant *, 1> toKeep;
994 bool found = false;
995 for (unsigned i = 0; i < C->getNumOperands(); i++) {
996 Value *Op = C->getOperand(i)->stripPointerCasts();
997 if (Op == G)
998 found = true;
999 else
1000 toKeep.push_back(C->getOperand(i));
1001 }
1002 if (found) {
1003 if (toKeep.size()) {
1004 auto CA = ConstantArray::get(
1005 ArrayType::get(C->getType()->getElementType(), toKeep.size()),
1006 toKeep);
1007 GlobalVariable *NGV = new GlobalVariable(
1008 CA->getType(), V->isConstant(), V->getLinkage(), CA, "",
1009 V->getThreadLocalMode());
1010#if LLVM_VERSION_MAJOR > 16
1011 V->getParent()->insertGlobalVariable(V->getIterator(), NGV);
1012#else
1013 V->getParent()->getGlobalList().insert(V->getIterator(), NGV);
1014#endif
1015 NGV->takeName(V);
1016
1017 // Nuke the old list, replacing any uses with the new one.
1018 if (!V->use_empty()) {
1019 Constant *VV = NGV;
1020 if (VV->getType() != V->getType())
1021 VV = ConstantExpr::getBitCast(VV, V->getType());
1022 V->replaceAllUsesWith(VV);
1023 }
1024 }
1025 V->eraseFromParent();
1026 }
1027 }
1028 }
1029 changed = true;
1030 G->replaceAllUsesWith(ConstantPointerNull::get(G->getType()));
1031 G->eraseFromParent();
1032 }
1033
1034 StringMap<std::pair<std::string, std::string>> Implements;
1035 for (std::string T : {"", "f"}) {
1036 // CUDA
1037 // sincos, sinpi, cospi, sincospi, cyl_bessel_i1
1038 for (std::string name :
1039 {"sin", "cos", "tan", "log2", "exp", "exp2",
1040 "exp10", "cosh", "sinh", "tanh", "atan2", "atan",
1041 "asin", "acos", "log", "log10", "log1p", "acosh",
1042 "asinh", "atanh", "expm1", "hypot", "rhypot", "norm3d",
1043 "rnorm3d", "norm4d", "rnorm4d", "norm", "rnorm", "cbrt",
1044 "rcbrt", "j0", "j1", "y0", "y1", "yn",
1045 "jn", "erf", "erfinv", "erfc", "erfcx", "erfcinv",
1046 "normcdfinv", "normcdf", "lgamma", "ldexp", "scalbn", "frexp",
1047 "modf", "fmod", "remainder", "remquo", "powi", "tgamma",
1048 "round", "fdim", "ilogb", "logb", "isinf", "pow",
1049 "sqrt", "finite", "fabs", "fmax"}) {
1050 std::string nvname = "__nv_" + name;
1051 std::string llname = "llvm." + name + ".";
1052 std::string mathname = name;
1053
1054 if (T == "f") {
1055 mathname += "f";
1056 nvname += "f";
1057 llname += "f32";
1058 } else {
1059 llname += "f64";
1060 }
1061
1062 Implements[nvname] = std::make_pair(mathname, llname);
1063 }
1064 // ROCM
1065 // sincos, sinpi, cospi, sincospi, cyl_bessel_i1
1066 for (std::string name : {"acos", "acosh", "asin",
1067 "asinh", "atan2", "atan",
1068 "atanh", "cbrt", "ceil",
1069 "copysign", "cos", "native_cos",
1070 "cosh", "cospi", "i0",
1071 "i1", "erfc", "erfcinv",
1072 "erfcx", "erf", "erfinv",
1073 "exp10", "native_exp10", "exp2",
1074 "exp", "native_exp", "expm1",
1075 "fabs", "fdim", "floor",
1076 "fma", "fmax", "fmin",
1077 "fmod", "frexp", "hypot",
1078 "ilogb", "isfinite", "isinf",
1079 "isnan", "j0", "j1",
1080 "ldexp", "lgamma", "log10",
1081 "native_log10", "log1p", "log2",
1082 "log2", "logb", "log",
1083 "native_log", "modf", "nearbyint",
1084 "nextafter", "len3", "len4",
1085 "ncdf", "ncdfinv", "pow",
1086 "pown", "rcbrt", "remainder",
1087 "remquo", "rhypot", "rint",
1088 "rlen3", "rlen4", "round",
1089 "rsqrt", "scalb", "scalbn",
1090 "signbit", "sincos", "sincospi",
1091 "sin", "native_sin", "sinh",
1092 "sinpi", "sqrt", "native_sqrt",
1093 "tan", "tanh", "tgamma",
1094 "trunc", "y0", "y1"}) {
1095 std::string nvname = "__ocml_" + name + "_";
1096 std::string llname = "llvm." + name + ".";
1097 std::string mathname = name;
1098
1099 if (T == "f") {
1100 mathname += "f";
1101 nvname += "f32";
1102 llname += "f32";
1103 } else {
1104 nvname += "f64";
1105 llname += "f64";
1106 }
1107
1108 Implements[nvname] = std::make_pair(mathname, llname);
1109 }
1110 // Metal AIR (air.<name>.f32 / air.<name>.f64 -- Metal has no long
1111 // double, so there is no third T variant here).
1112 // tanh/cosh/sinh (and their fast_ forms) are deliberately excluded here:
1113 // CallPattern matching (see InstructionDerivatives.td) runs against this
1114 // same enzyme_math-substituted name, so tagging air.tanh.f32 here would
1115 // make it dispatch through the plain "tanhf" CallPattern -- whose
1116 // companion is hardcoded to the libm name "coshf", not "air.cosh.f32" --
1117 // rather than through the dedicated air.tanh.f32 CallPattern that exists
1118 // specifically to keep the companion AIR-native.
1119 for (std::string name : {"sin", "cos", "tan", "asin", "acos", "atan",
1120 "atan2", "exp", "exp2", "log", "log2", "log10",
1121 "log1p", "expm1", "sqrt", "cbrt", "pow", "fma"}) {
1122 std::string airname = "air." + name + (T == "f" ? ".f32" : ".f64");
1123 std::string llname = "llvm." + name + "." + (T == "f" ? "f32" : "f64");
1124 std::string mathname = name + T;
1125
1126 Implements[airname] = std::make_pair(mathname, llname);
1127 }
1128 // Metal AIR fast-math variants (air.fast_<name>.f32 -- Metal only
1129 // exposes fast math for float). These map to the same mathname/llname
1130 // as the precise version above: enzyme_math dispatch and the
1131 // ReplaceFunctionImplementation companion-rewrite don't distinguish
1132 // fast vs precise, they just need a valid libm/llvm target name.
1133 // fast_tanh/fast_cosh/fast_sinh are excluded for the same reason as
1134 // their non-fast forms above.
1135 if (T == "f") {
1136 for (std::string name :
1137 {"log", "exp", "sin", "cos", "tan", "sqrt", "asin", "acos", "atan",
1138 "atan2", "acosh", "asinh"}) {
1139 std::string airname = "air.fast_" + name + ".f32";
1140 std::string llname = "llvm." + name + ".f32";
1141 std::string mathname = name + T;
1142
1143 Implements[airname] = std::make_pair(mathname, llname);
1144 }
1145 }
1146 }
1147#if ENZYME_ENABLE_NVVM_ATTRIBUTION
1148 for (auto &F : llvm::make_early_inc_range(M)) {
1149 if (Begin) {
1150 changed |= attributeKnownFunctions(F);
1151 }
1152 }
1153#endif
1154 SmallPtrSet<Constant *, 4> compilerUsed;
1155 for (auto &F : M) {
1156 auto found = Implements.find(F.getName());
1157 if (found != Implements.end()) {
1158 changed = true;
1159 if (Begin) {
1160 // As a side effect, enforces arguments
1161 // cannot be erased.
1162 F.addFnAttr("implements", found->second.second);
1163 F.addFnAttr("implements2", found->second.first);
1164 F.addFnAttr("enzyme_math", found->second.first);
1165 changed |= preserveLinkage(Begin, F);
1166 }
1167 } else if (F.getName() == "_ZL21__internal_float2halffRjS_" ||
1168 F.getName() == "_ZL4hlog6__half" ||
1169 F.getName() == "_ZL6__hdiv6__halfS_" ||
1170 F.getName() == "_ZL12__half2float6__half" ||
1171 F.getName() == "_ZL6__habs6__half" ||
1172 F.getName() == "_ZL5__hlt6__halfS_" ||
1173 F.getName() == "_ZL6__hmul6__halfS_" ||
1174 F.getName() == "_ZL6__hadd6__halfS_" ||
1175 F.getName() == "_ZL5hsqrt6__half" ||
1176 F.getName() == "_ZL6__hsub6__halfS_" ||
1177 F.getName() == "_ZL4hexp6__half" ||
1178 F.getName() == "_ZL6__hneg6__half" ||
1179 F.getName() == "_ZL22__internal_device_hdiv13__nv_bfloat16S_" ||
1180 F.getName() ==
1181 "_ZL27__internal_sm80_device_hmul13__nv_bfloat16S_" ||
1182 F.getName() == "_ZL22__internal_device_hadd13__nv_bfloat16S_" ||
1183 F.getName() ==
1184 "_ZL27__internal_sm80_device_hsub13__nv_bfloat16S_" ||
1185 F.getName() == "_ZL22__internal_device_hneg13__nv_bfloat16" ||
1186 F.getName() == "_ZL16__float2bfloat16f" ||
1187 F.getName() == "_ZL25__internal_bfloat162floatt" ||
1188 F.getName() == "_ZL32__internal_device_bfloat162floatt") {
1189 changed = true;
1190 if (Begin) {
1191 changed |= preserveLinkage(Begin, F);
1192 }
1193 }
1194 if (!Begin && F.hasFnAttribute("prev_fixup")) {
1195 changed = true;
1196 if (F.hasFnAttribute("prev_compiler_used")) {
1197 F.removeFnAttr("prev_compiler_used");
1198 compilerUsed.insert(&F);
1199 }
1200 F.removeFnAttr("prev_fixup");
1201 if (F.hasFnAttribute("prev_always_inline")) {
1202 F.addFnAttr(Attribute::AlwaysInline);
1203 F.removeFnAttr("prev_always_inline");
1204 }
1205 if (F.hasFnAttribute("prev_no_inline")) {
1206 F.removeFnAttr("prev_no_inline");
1207 } else {
1208 F.removeFnAttr(Attribute::NoInline);
1209 }
1210 int64_t val;
1211 F.getFnAttribute("prev_linkage").getValueAsString().getAsInteger(10, val);
1212 F.setLinkage((Function::LinkageTypes)val);
1213 }
1214 }
1215#if LLVM_VERSION_MAJOR >= 20
1216 // Only the LTO pre-link end pass, used from LLVM 20, adds these entries.
1217 if (!compilerUsed.empty())
1218 removeFromUsedLists(M, [&](Constant *C) { return compilerUsed.count(C); });
1219#endif
1220 return changed;
1221}
1222
1223// The end of an LTO pre-link pipeline that deferred differentiation. Every
1224// function Begin made external goes back to its original linkage: otherwise
1225// each object file would define a strong copy of the static and inline
1226// functions a rule names, and linking two of them fails on duplicate symbols.
1227// Those that could now be dropped as unused are kept alive through
1228// llvm.compiler.used until the post-link end pass, which also restores the
1229// inlining attributes left in place here.
1230static bool preserveNVVMLTOPreLinkEnd(Module &M) {
1231 bool changed = false;
1232 SmallVector<GlobalValue *, 4> keep;
1233 for (auto &F : M) {
1234 if (!F.hasFnAttribute("prev_fixup") ||
1235 F.hasFnAttribute("prev_compiler_used"))
1236 continue;
1237 int64_t val;
1238 F.getFnAttribute("prev_linkage").getValueAsString().getAsInteger(10, val);
1239 auto L = (Function::LinkageTypes)val;
1240 changed |= F.getLinkage() != L;
1241 F.setLinkage(L);
1242 if (GlobalValue::isDiscardableIfUnused(L) &&
1243 !GlobalValue::isAvailableExternallyLinkage(L)) {
1244 F.addFnAttr("prev_compiler_used");
1245 keep.push_back(&F);
1246 }
1247 }
1248 if (!keep.empty()) {
1249 appendToCompilerUsed(M, keep);
1250 changed = true;
1251 }
1252 return changed;
1253}
1254
1255namespace {
1256
1257class PreserveNVVM final : public ModulePass {
1258public:
1259 static char ID;
1260 bool Begin;
1261 PreserveNVVM(bool Begin = true) : ModulePass(ID), Begin(Begin) {}
1262
1263 void getAnalysisUsage(AnalysisUsage &AU) const override {}
1264 bool runOnModule(Module &M) override { return preserveNVVM(Begin, M); }
1265};
1266
1267class PreserveNVVMFn final : public FunctionPass {
1268public:
1269 static char ID;
1270 bool Begin;
1271 PreserveNVVMFn(bool Begin = true) : FunctionPass(ID), Begin(Begin) {}
1272
1273 void getAnalysisUsage(AnalysisUsage &AU) const override {}
1274 bool runOnFunction(Function &F) override {
1275 return preserveNVVM(Begin, *F.getParent());
1276 }
1277};
1278
1279} // namespace
1280
1281char PreserveNVVM::ID = 0;
1282
1283char PreserveNVVMFn::ID = 0;
1284
1285static RegisterPass<PreserveNVVM> X("preserve-nvvm", "Preserve NVVM Pass");
1286
1287static RegisterPass<PreserveNVVMFn> XFn("preserve-nvvm-fn",
1288 "Preserve NVVM Pass");
1289
1291 return new PreserveNVVM(Begin);
1292}
1293
1294FunctionPass *createPreserveNVVMFnPass(bool Begin) {
1295 return new PreserveNVVMFn(Begin);
1296}
1297
1298#include <llvm-c/Core.h>
1299#include <llvm-c/Types.h>
1300
1301#include "llvm/IR/LegacyPassManager.h"
1302
1303extern "C" void AddPreserveNVVMPass(LLVMPassManagerRef PM, uint8_t Begin) {
1304 unwrap(PM)->add(createPreserveNVVMPass((bool)Begin));
1305}
1306
1308PreserveNVVMNewPM::run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM) {
1309 bool changed = (!Begin && LTOPreLink)
1311 : preserveNVVM(Begin, M, PreserveCustomRuleLinkage);
1312 return changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
1313}
1314llvm::AnalysisKey PreserveNVVMNewPM::Key;
static std::string str(AugmentedStruct c)
Definition EnzymeLogic.h:62
static bool preserveNVVMLTOPreLinkEnd(Module &M)
bool isTargetNVPTX(llvm::Module &M)
static RegisterPass< PreserveNVVM > X("preserve-nvvm", "Preserve NVVM Pass")
bool preserveLinkage(bool Begin, Function &F, bool Inlining=true)
Returns whether changed.
static void handleCustomDerivative(llvm::Module &M, llvm::GlobalVariable &g, SmallVectorImpl< GlobalVariable * > &globalsToErase, bool PreserveCustomRuleLinkage)
ModulePass * createPreserveNVVMPass(bool Begin)
void markFunctionInactive(Function &F)
Mark F itself as inactive, and additionally mark everything in its body, so that differentiating thro...
static void handleFunctionLike(bool Begin, Value *Target, StringRef FunctionName)
static RegisterPass< PreserveNVVMFn > XFn("preserve-nvvm-fn", "Preserve NVVM Pass")
void AddPreserveNVVMPass(LLVMPassManagerRef PM, uint8_t Begin)
bool preserveNVVM(bool Begin, Module &M, bool PreserveCustomRuleLinkage=true)
bool copyForwardedCalleeAttrs(Function &F)
If F is a trivial forwarding wrapper – one basic block holding a single call whose operands are F's o...
FunctionPass * createPreserveNVVMFnPass(bool Begin)
bool attributeKnownFunctions(llvm::Function &F)
Definition Utils.cpp:139
static bool startsWith(llvm::StringRef string, llvm::StringRef prefix)
Definition Utils.h:847
llvm::PreservedAnalyses Result
Result run(llvm::Module &M, llvm::ModuleAnalysisManager &MAM)