Enzyme main
Loading...
Searching...
No Matches
FixupJuliaCallingConvention.cpp
Go to the documentation of this file.
1#include "CApi.h"
2#include "FunctionUtils.h"
3#include "GradientUtils.h"
4#include "Utils.h"
5#include "llvm/ADT/SmallSet.h"
6#include "llvm/IR/Function.h"
7#include "llvm/IR/IRBuilder.h"
8#include "llvm/IR/Instructions.h"
9#include "llvm/IR/LegacyPassManager.h"
10#include "llvm/IR/Module.h"
11#include "llvm/Pass.h"
12#include "llvm/Transforms/Utils/BasicBlockUtils.h"
13#include "llvm/Transforms/Utils/Cloning.h"
14
15#define addAttribute addAttributeAtIndex
16#define removeAttribute removeAttributeAtIndex
17#define getAttribute getAttributeAtIndex
18#define hasAttribute hasAttributeAtIndex
19
20#if LLVM_VERSION_MAJOR >= 17
21#include "llvm/TargetParser/Triple.h"
22#endif
23
24using namespace llvm;
25
26extern bool
27DetectPointerArgOfFn(llvm::Function &F,
28 llvm::SmallPtrSetImpl<llvm::Function *> &calls_todo);
29
30// Determine whether the tracked pointers stored into the sret-like argument
31// `arg` need to be given fresh roots. They do not if every one of them is also
32// stored into an existing enzymejl_returnRoots argument; the indices of the
33// returnRoots arguments found that way are added to `rootingArgs`.
34//
35// `assignedRoots` holds the returnRoots arguments which were already assigned
36// to another sret. As those hold the tracked pointers of that sret, not finding
37// the ones of `arg` in them is expected rather than worth a diagnostic.
38bool needsReRooting(llvm::Argument *arg, bool &anyJLStore,
39 llvm::Type *SRetType = nullptr,
40 std::set<size_t> *rootingArgs = nullptr,
41 const std::map<size_t, size_t> *assignedRoots = nullptr) {
42 auto Attrs = arg->getParent()->getAttributes();
43
44 if (!SRetType)
46 Attrs
47 .getAttribute(AttributeList::FirstArgIndex + arg->getArgNo(),
48 "enzyme_sret")
49 .getValueAsString(),
50 &arg->getContext());
51
52 CountTrackedPointers tracked(SRetType);
53 if (tracked.count == 0) {
54 return false;
55 }
56
57 bool hasReturnRootingAfterArg = false;
58 bool hasUnassignedReturnRootingAfterArg = false;
59 for (size_t i = arg->getArgNo() + 1; i < arg->getParent()->arg_size(); i++) {
60 if (Attrs.hasAttribute(AttributeList::FirstArgIndex + i,
61 "enzymejl_returnRoots")) {
62 hasReturnRootingAfterArg = true;
63 if (!assignedRoots || !assignedRoots->count(i)) {
64 hasUnassignedReturnRootingAfterArg = true;
65 break;
66 }
67 }
68 }
69
70 // If there is no returnRoots, we _must_ reroot the arg.
71 if (!hasReturnRootingAfterArg) {
72 return true;
73 }
74
75 SmallVector<Value *> storedValues;
76
77 auto &DL = arg->getParent()->getParent()->getDataLayout();
78 SmallVector<size_t> sret_offsets;
79 {
80 std::deque<std::pair<llvm::Type *, std::vector<unsigned>>> todo = {
81 {SRetType, {}}};
82 while (!todo.empty()) {
83 auto cur = std::move(todo[0]);
84 todo.pop_front();
85 auto path = std::move(cur.second);
86 auto ty = cur.first;
87
88 if (auto PT = dyn_cast<PointerType>(ty)) {
89 if (!isSpecialPtr(PT))
90 continue;
91
92 SmallVector<Constant *, 1> IdxList;
93 IdxList.push_back(
94 ConstantInt::get(Type::getInt64Ty(PT->getContext()), 0));
95
96 for (auto v : path)
97 IdxList.push_back(
98 ConstantInt::get(Type::getInt32Ty(PT->getContext()), v));
99 auto nullp = ConstantPointerNull::get(getUnqual(SRetType));
100 auto gep = ConstantExpr::getGetElementPtr(SRetType, nullp, IdxList);
101
102 if (gep == ConstantPointerNull::get(getUnqual(PT))) {
103 sret_offsets.push_back(0);
104 continue;
105 }
106#if LLVM_VERSION_MAJOR >= 20
107 SmallMapVector<Value *, APInt, 4> VariableOffsets;
108#else
109 MapVector<Value *, APInt> VariableOffsets;
110#endif
111 auto width = DL.getPointerSize() * 8;
112 APInt Offset(width, 0);
113 bool success = collectOffset(cast<GEPOperator>(gep), DL, width,
114 VariableOffsets, Offset);
115 if (!success)
116 llvm_unreachable("Illegal offset collection");
117 sret_offsets.push_back(Offset.getZExtValue());
118 continue;
119 }
120
121 if (auto AT = dyn_cast<ArrayType>(ty)) {
122 for (size_t i = 0; i < AT->getNumElements(); i++) {
123 std::vector<unsigned> path2(path);
124 path2.push_back(i);
125 todo.emplace_back(AT->getElementType(), path2);
126 }
127 continue;
128 }
129
130 if (auto VT = dyn_cast<VectorType>(ty)) {
131 for (size_t i = 0; i < VT->getElementCount().getKnownMinValue(); i++) {
132 std::vector<unsigned> path2(path);
133 path2.push_back(i);
134 todo.emplace_back(VT->getElementType(), path2);
135 }
136 continue;
137 }
138
139 if (auto ST = dyn_cast<StructType>(ty)) {
140 for (size_t i = 0; i < ST->getNumElements(); i++) {
141 std::vector<unsigned> path2(path);
142 path2.push_back(i);
143 todo.emplace_back(ST->getTypeAtIndex(i), path2);
144 }
145 continue;
146 }
147 }
148 }
149
150 bool legal = true;
151 for (auto &&[I, cur, byteOffset] : findAllUsersOf(arg)) {
152 assert(I->getParent()->getParent() == arg->getParent());
153
154 if (isa<ICmpInst>(I)) {
155 continue;
156 }
157 if (isa<LoadInst>(I)) {
158 continue;
159 }
160 if (auto SI = dyn_cast<StoreInst>(I)) {
161 assert(SI->getValueOperand() != cur);
162
163 if (CountTrackedPointers(SI->getValueOperand()->getType()).count == 0)
164 continue;
165
166 storedValues.push_back(SI->getValueOperand());
167 anyJLStore = true;
168 continue;
169 }
170
171 if (isa<MemSetInst>(I))
172 continue;
173
174 if (auto MSI = dyn_cast<MemTransferInst>(I)) {
175 if (auto Len = dyn_cast<ConstantInt>(MSI->getLength())) {
176 bool mlegal = true;
177 for (auto offset : sret_offsets) {
178 if (byteOffset + Len->getSExtValue() <= offset)
179 continue;
180 if (offset + DL.getPointerSize() <= byteOffset)
181 continue;
182 mlegal = false;
183 break;
184 }
185 if (mlegal)
186 break;
187 }
188 }
189
190 std::string s;
191 llvm::raw_string_ostream ss(s);
192 ss << "Unknown user of sret-like argument\n";
193 CustomErrorHandler(ss.str().c_str(), wrap(I), ErrorType::GCRewrite,
194 wrap(cur), wrap(arg), nullptr);
195 legal = false;
196 anyJLStore = true;
197 break;
198 }
199
200 if (legal) {
201 while (!storedValues.empty()) {
202 auto sv = storedValues.pop_back_val();
203 if (auto I = dyn_cast<Instruction>(sv)) {
204 assert(I->getParent()->getParent() == arg->getParent());
205 }
206 bool foundUse = false;
207 for (auto &U : sv->uses()) {
208 // If we had a constant originally, it could have been used in another
209 // function. We can/should ignore those uses.
210 if (auto I = dyn_cast<Instruction>(U.getUser())) {
211 if (I->getParent()->getParent() != arg->getParent()) {
212 continue;
213 }
214 }
215
216 if (auto SI = dyn_cast<StoreInst>(U.getUser())) {
217 if (SI->getValueOperand() == sv) {
218 auto base = getBaseObject(SI->getPointerOperand());
219 if (base == arg) {
220 continue;
221 }
222 if (auto evi = dyn_cast<ExtractValueInst>(base)) {
223 base = evi->getAggregateOperand();
224 }
225 if (auto arg2 = dyn_cast<Argument>(base)) {
226 if (Attrs
227 .getAttribute(AttributeList::FirstArgIndex +
228 arg2->getArgNo(),
229 "enzymejl_returnRoots")
230 .isValid()) {
231 if (rootingArgs)
232 rootingArgs->insert(arg2->getArgNo());
233 foundUse = true;
234 break;
235 }
236 }
237 }
238 }
239 }
240 if (!foundUse) {
241 if (auto IVI = dyn_cast<InsertValueInst>(sv)) {
242 // An undef/poison/zeroinitializer base has no live pointer in any of
243 // its fields, so it needs no root regardless of which field the
244 // insertvalue overwrites.
245 bool trivialAggregate =
246 isa<UndefValue>(IVI->getAggregateOperand()) ||
247 isa<PoisonValue>(IVI->getAggregateOperand()) ||
248 isa<ConstantAggregateZero>(IVI->getAggregateOperand());
249 CountTrackedPointers tracked(
250 IVI->getInsertedValueOperand()->getType());
251 if (tracked.count == 0) {
252 if (trivialAggregate)
253 continue;
254 storedValues.push_back(IVI->getAggregateOperand());
255 continue;
256 }
257 if (trivialAggregate) {
258 storedValues.push_back(IVI->getInsertedValueOperand());
259 continue;
260 }
261 storedValues.push_back(IVI->getAggregateOperand());
262 storedValues.push_back(IVI->getInsertedValueOperand());
263 continue;
264 }
265 if (auto ST = dyn_cast<StructType>(sv->getType())) {
266 bool legal = true;
267 for (size_t i = 0; i < ST->getNumElements(); i++) {
268
269 CountTrackedPointers tracked(ST->getElementType(i));
270 if (tracked.count == 0) {
271 continue;
272 }
273 std::map<std::vector<unsigned>, bool> paths_to_cover;
274 {
275 std::deque<std::pair<llvm::Type *, std::vector<unsigned>>> todo =
276 {{ST->getElementType(i), {}}};
277 while (!todo.empty()) {
278 auto cur = std::move(todo[0]);
279 todo.pop_front();
280 auto path = std::move(cur.second);
281 auto ty = cur.first;
282
283 if (auto PT = dyn_cast<PointerType>(ty)) {
284 if (isSpecialPtr(PT)) {
285 paths_to_cover[path] = false;
286 }
287 continue;
288 }
289
290 if (auto AT = dyn_cast<ArrayType>(ty)) {
291 for (size_t k = 0; k < AT->getNumElements(); k++) {
292 std::vector<unsigned> path2(path);
293 path2.push_back(k);
294 todo.emplace_back(AT->getElementType(), path2);
295 }
296 continue;
297 }
298
299 if (auto VT = dyn_cast<VectorType>(ty)) {
300 for (size_t k = 0;
301 k < VT->getElementCount().getKnownMinValue(); k++) {
302 std::vector<unsigned> path2(path);
303 path2.push_back(k);
304 todo.emplace_back(VT->getElementType(), path2);
305 }
306 continue;
307 }
308
309 if (auto ST2 = dyn_cast<StructType>(ty)) {
310 for (size_t k = 0; k < ST2->getNumElements(); k++) {
311 std::vector<unsigned> path2(path);
312 path2.push_back(k);
313 todo.emplace_back(ST2->getTypeAtIndex(k), path2);
314 }
315 continue;
316 }
317 }
318 }
319
320 for (auto u : sv->users()) {
321 if (auto ev0 = dyn_cast<ExtractValueInst>(u)) {
322 if (ev0->getIndices()[0] == i) {
323 std::vector<unsigned> extract_path;
324 for (size_t k = 1; k < ev0->getNumIndices(); ++k) {
325 extract_path.push_back(ev0->getIndices()[k]);
326 }
327 storedValues.push_back(ev0);
328
329 // Mark paths covered
330 for (auto &pair : paths_to_cover) {
331 const auto &p = pair.first;
332 bool match = true;
333 if (extract_path.size() > p.size()) {
334 match = false;
335 } else {
336 for (size_t idx = 0; idx < extract_path.size(); ++idx) {
337 if (extract_path[idx] != p[idx]) {
338 match = false;
339 break;
340 }
341 }
342 }
343 if (match) {
344 pair.second = true;
345 }
346 }
347 }
348 }
349 }
350
351 bool fullyCovered = true;
352 for (const auto &pair : paths_to_cover) {
353 if (!pair.second) {
354 fullyCovered = false;
355 break;
356 }
357 }
358
359 if (!fullyCovered) {
360 llvm::errs() << " failed to find extracted pointer for " << *sv
361 << " at index " << i << "\n";
362 legal = false;
363 break;
364 }
365 }
366 if (legal) {
367 continue;
368 }
369 }
370 if (!isa<PointerType>(sv->getType()) ||
371 !isSpecialPtr(cast<PointerType>(sv->getType()))) {
372 llvm::errs() << " sf: " << *arg->getParent() << "\n";
373 llvm::errs() << " arg: " << *arg << "\n";
374 llvm::errs() << "Pointer of wrong type: " << *sv << "\n";
375 assert(0);
376 }
377
378 {
379 bool saw_bitcast = false;
380 for (auto u : sv->users()) {
381 if (auto ev0 = dyn_cast<CastInst>(u)) {
382 auto t2 = ev0->getType();
383 if (isa<PointerType>(t2) && isSpecialPtr(cast<PointerType>(t2))) {
384 saw_bitcast = true;
385 storedValues.push_back(ev0);
386 break;
387 }
388 }
389 }
390 if (saw_bitcast)
391 continue;
392 }
393
394 if (hasUnassignedReturnRootingAfterArg) {
395 std::string s;
396 llvm::raw_string_ostream ss(s);
397 ss << "Could not find use of stored value\n";
398 ss << " sv: " << *sv << "\n";
399 CustomErrorHandler(ss.str().c_str(), wrap(sv), ErrorType::GCRewrite,
400 nullptr, wrap(arg), nullptr);
401 }
402 legal = false;
403 break;
404 }
405 }
406 }
407
408 return !legal;
409}
410
411// For a given enzymejl_returnRoots, which we assume is loaded after
412// the call (and therefore is needed to be preserved), check whether
413// there is an existing enzyme_sret for whom the roots could be assigned to,
414// or if an additional sret argument is required.
415// This is because count(sret_type) == returnRoots for the final merged type.
416// As a result, there _must_ be a sret_type corresponding to the return root.
417bool needsReReturning(llvm::Argument *arg, size_t &sret_idx,
418 std::map<size_t, size_t> &srets_without_stores) {
419 auto Attrs = arg->getParent()->getAttributes();
420
421 bool hasSRetBeforeArg = false;
422 for (size_t i = 0; i < arg->getArgNo(); i++) {
423 if (Attrs.hasAttribute(AttributeList::FirstArgIndex + i, "enzyme_sret")) {
424 hasSRetBeforeArg = true;
425 break;
426 }
427 }
428
429 if (!hasSRetBeforeArg) {
430 assert(srets_without_stores.size() == 0);
431 return true;
432 }
433
434 if (srets_without_stores.size() == 0) {
435 return true;
436 }
437
438 size_t subCount = convertRRootCountFromString(
439 Attrs
440 .getAttribute(AttributeList::FirstArgIndex + arg->getArgNo(),
441 "enzymejl_returnRoots")
442 .getValueAsString());
443
444 for (auto &pair : srets_without_stores) {
445 if (pair.second == subCount) {
446 sret_idx = pair.first;
447 srets_without_stores.erase(sret_idx);
448 return false;
449 }
450 }
451
452 llvm_unreachable("Unsupported needsReRooting");
453 return true;
454}
455
456static bool isOpaque(llvm::Type *T) {
457#if LLVM_VERSION_MAJOR >= 20
458 return T->isPointerTy();
459#else
460 return T->isOpaquePointerTy();
461#endif
462}
463
464static void removeRange(std::vector<std::pair<uint64_t, uint64_t>> &ranges,
465 uint64_t start, uint64_t end) {
466 std::vector<std::pair<uint64_t, uint64_t>> nextRanges;
467 for (auto &range : ranges) {
468 if (end <= range.first || start >= range.second) {
469 nextRanges.push_back(range);
470 } else {
471 if (start > range.first) {
472 nextRanges.push_back({range.first, start});
473 }
474 if (end < range.second) {
475 nextRanges.push_back({end, range.second});
476 }
477 }
478 }
479 ranges = std::move(nextRanges);
480}
481// A function that gains an sret (or roots) parameter now writes argument
482// memory, so memory attributes that rule out writes no longer hold for it or
483// for its call sites: LLVM would otherwise forward the caller's initialization
484// of the sret buffer past the call. Reads are unchanged: a shadow carried in
485// through an enzyme_sret was already read as argument memory.
486static AttributeList allowArgumentMemoryWrites(LLVMContext &Ctx,
487 AttributeList Attrs) {
488#if LLVM_VERSION_MAJOR >= 16
489 if (Attrs.hasFnAttr(Attribute::Memory)) {
490 auto ME =
491 Attrs.getMemoryEffects() | MemoryEffects::argMemOnly(ModRefInfo::Mod);
492 Attrs = Attrs.removeFnAttribute(Ctx, Attribute::Memory);
493 Attrs = Attrs.addFnAttribute(Ctx, Attribute::getWithMemoryEffects(Ctx, ME));
494 }
495#else
496 for (auto Kind : {Attribute::ReadNone, Attribute::ReadOnly,
497 Attribute::InaccessibleMemOnly})
498 Attrs = Attrs.removeFnAttribute(Ctx, Kind);
499#endif
500 // enzyme_ReadOnlyOrThrow rules out writes to memory visible to the caller,
501 // which an sret is; its local variant explicitly allows writing an sret or
502 // returnRoots parameter.
503 if (Attrs.hasFnAttr("enzyme_ReadOnlyOrThrow")) {
504 Attrs = Attrs.removeFnAttribute(Ctx, "enzyme_ReadOnlyOrThrow");
505 Attrs = Attrs.addFnAttribute(Ctx, "enzyme_LocalReadOnlyOrThrow");
506 }
507 return Attrs;
508}
509
510static bool isReadOnlyNoCapture(Function *F, unsigned argNo) {
511 return F->hasParamAttribute(argNo, Attribute::ReadOnly) &&
512 F->getArg(argNo)->hasNoCaptureAttr();
513}
514
515static bool isGuaranteedToFullyWrite(Function *F, unsigned argNo, Type *T) {
516 if (F->isDeclaration())
517 return false;
518
519 auto &DL = F->getParent()->getDataLayout();
520 auto size = DL.getTypeAllocSize(T);
521
522 std::vector<std::pair<uint64_t, uint64_t>> ranges = {{0, size}};
523 std::vector<std::pair<Value *, uint64_t>> worklist = {{F->getArg(argNo), 0}};
524 std::set<Value *> seen = {F->getArg(argNo)};
525
526 PostDominatorTree PDT(*F);
527
528 while (!worklist.empty()) {
529 auto item = worklist.back();
530 worklist.pop_back();
531 Value *val = item.first;
532 uint64_t offset = item.second;
533
534 for (auto *U : val->users()) {
535 if (auto *BI = dyn_cast<CastInst>(U)) {
536 if (seen.insert(BI).second)
537 worklist.push_back({BI, offset});
538 continue;
539 }
540
541 if (auto *GEP = dyn_cast<GetElementPtrInst>(U)) {
542 APInt gepOffset(DL.getIndexTypeSizeInBits(GEP->getType()), 0);
543 if (GEP->accumulateConstantOffset(DL, gepOffset)) {
544 if (seen.insert(GEP).second)
545 worklist.push_back({GEP, offset + gepOffset.getZExtValue()});
546 continue;
547 }
548 }
549
550 if (auto *I = dyn_cast<Instruction>(U)) {
551 if (I->getParent() != &F->getEntryBlock() &&
552 !PDT.dominates(I->getParent(), &F->getEntryBlock()))
553 continue;
554
555 if (auto *SI = dyn_cast<StoreInst>(I)) {
556 if (SI->getPointerOperand() == val) {
557 auto storeSize =
558 DL.getTypeAllocSize(SI->getValueOperand()->getType());
559 removeRange(ranges, offset, offset + storeSize);
560 if (ranges.empty())
561 return true;
562 continue;
563 }
564 }
565
566 if (auto *MSI = dyn_cast<MemSetInst>(I)) {
567 if (MSI->getDest() == val) {
568 if (auto *CI = dyn_cast<ConstantInt>(MSI->getLength())) {
569 removeRange(ranges, offset, offset + CI->getZExtValue());
570 if (ranges.empty())
571 return true;
572 continue;
573 }
574 }
575 }
576
577 if (auto *MCI = dyn_cast<MemCpyInst>(I)) {
578 if (MCI->getDest() == val) {
579 if (auto *CI = dyn_cast<ConstantInt>(MCI->getLength())) {
580 removeRange(ranges, offset, offset + CI->getZExtValue());
581 if (ranges.empty())
582 return true;
583 continue;
584 }
585 }
586 }
587 }
588 }
589 }
590
591 return ranges.empty();
592}
593
594// TODO, for sret/sret_v check if it actually stores the jlvalue_t's into the
595// sret If so, confirm that those values are saved elsewhere in a returnroot
596void EnzymeFixupJuliaCallingConvention(Function *F, bool sret_jlvalue) {
597 if (F->empty())
598 return;
599
600 auto RT = F->getReturnType();
601 std::set<size_t> srets;
602 std::set<size_t> enzyme_srets;
603
604 std::set<size_t> reroot_enzyme_srets;
605
606 std::set<size_t> noroot_enzyme_srets;
607
608 std::set<size_t> rroots;
609
610 std::set<size_t> reret_roots;
611
612 auto FT = F->getFunctionType();
613 auto Attrs = F->getAttributes();
614
615 std::map<size_t, size_t> selected_roots;
616
617 // Map from the sret index to the number of stores, as unused
618 std::map<size_t, size_t> srets_without_stores;
619
620 for (size_t i = 0, end = FT->getNumParams(); i < end; i++) {
621 if (Attrs.hasAttribute(AttributeList::FirstArgIndex + i,
622 Attribute::StructRet))
623 srets.insert(i);
624 if (Attrs.hasAttribute(AttributeList::FirstArgIndex + i, "enzyme_sret")) {
625 bool anyJLStore = false;
626 std::set<size_t> rootingArgs;
627 enzyme_srets.insert(i);
628 if (needsReRooting(F->getArg(i), anyJLStore, nullptr, &rootingArgs,
629 &selected_roots)) {
630 // Case 1: jlvalue_t's were stored into the sret, but were not stored
631 // into an existing rooted argument.
632 reroot_enzyme_srets.insert(i);
633 } else if (anyJLStore) {
634 // Case 2: jlvalue_t's were stored into the sret, and the were stored
635 // into an existing rooted argument.
636 //
637 // The sret contributes its tracked pointers to the merged sret type
638 // without contributing roots of its own, so the returnRoots which
639 // holds them must provide exactly those roots: it is assigned to this
640 // sret rather than being returned as a further member of the merged
641 // sret type (which would add tracked pointers that nothing roots).
642 // If there is no single such returnRoots of the right size, give the
643 // sret roots of its own instead.
644 llvm::Type *SRetType = convertSRetTypeFromString(
645 Attrs.getAttribute(AttributeList::FirstArgIndex + i, "enzyme_sret")
646 .getValueAsString(),
647 &F->getContext());
648 CountTrackedPointers tracked(SRetType);
649 bool assigned = false;
650 // An sret made only of tracked pointers may end up acting as its own
651 // root (see below), which leaves no separate roots to place the
652 // returnRoots in. The returnRoots therefore stays a member of the
653 // merged sret type in that case.
654 if (rootingArgs.size() == 1 && !tracked.all) {
655 size_t ridx = *rootingArgs.begin();
656 size_t rcount = convertRRootCountFromString(
657 Attrs
658 .getAttribute(AttributeList::FirstArgIndex + ridx,
659 "enzymejl_returnRoots")
660 .getValueAsString());
661 if (ridx > i && !selected_roots.count(ridx) &&
662 rcount == tracked.count) {
663 selected_roots[ridx] = i;
664 assigned = true;
665 }
666 }
667 if (!assigned)
668 reroot_enzyme_srets.insert(i);
669 } else {
670 // Case 3: No jlvalue_t's were stored into the sret.
671 llvm::Type *SRetType = convertSRetTypeFromString(
672 Attrs.getAttribute(AttributeList::FirstArgIndex + i, "enzyme_sret")
673 .getValueAsString(),
674 &F->getContext());
675 if (auto count = CountTrackedPointers(SRetType).count) {
676 srets_without_stores[i] = count;
677 noroot_enzyme_srets.insert(i);
678 }
679 }
680 }
681 assert(
682 !Attrs.hasAttribute(AttributeList::FirstArgIndex + i, "enzyme_sret_v"));
683
684 if (Attrs.hasAttribute(AttributeList::FirstArgIndex + i,
685 "enzymejl_returnRoots")) {
686 rroots.insert(i);
687 size_t sret_idx;
688 if (selected_roots.count(i)) {
689 // Already assigned to the sret whose jlvalue_t's it holds (Case 2).
690 } else if (needsReReturning(F->getArg(i), sret_idx,
691 srets_without_stores)) {
692 reret_roots.insert(i);
693 } else {
694 selected_roots[i] = sret_idx;
695 }
696 }
697 assert(!Attrs.hasAttribute(AttributeList::FirstArgIndex + i,
698 "enzymejl_returnRoots_v"));
699 }
700
701 // Regular julia function, needing no intervention
702 if (srets.size() == 1) {
703 assert(*srets.begin() == 0);
704 assert(enzyme_srets.size() == 0);
705 llvm::Type *SRetType = F->getParamStructRetType(0);
706 CountTrackedPointers tracked(SRetType);
707
708 // No jlvaluet to rewrite
709 if (!tracked.count) {
710 return;
711 }
712
713 bool anyJLStore = false;
714 bool rerooting = needsReRooting(F->getArg(0), anyJLStore, SRetType);
715
716 // We now assume we have an sret.
717 // If it is properly rooted, we don't have any work to do
718 if (rroots.size()) {
719 assert(rroots.size() == 1);
720 assert(*rroots.begin() == 1);
721 // GVN is only powerful enough at LLVM 16+
722 // (https://godbolt.org/z/ebY3exW9K)
723#if LLVM_VERSION_MAJOR >= 16
724 if (rerooting) {
725 std::string s;
726 llvm::raw_string_ostream ss(s);
727 ss << "Illegal GC setup in which rerooting is required\n";
728 ss << " + F: " << *F << "\n";
730 nullptr, nullptr, nullptr);
731 }
732 assert(!rerooting);
733#endif
734
735 size_t count = convertRRootCountFromString(
736 Attrs
737 .getAttribute(AttributeList::FirstArgIndex + 1,
738 "enzymejl_returnRoots")
739 .getValueAsString());
740
741 assert(count == tracked.count);
742
743 bool allCallersAlloca = true;
744 for (auto U : F->users()) {
745 if (auto CI = dyn_cast<CallInst>(U)) {
746 if (CI->getCalledFunction() == F) {
747 if (!isa<AllocaInst>(getBaseObject(CI->getArgOperand(0), false)) ||
748 !isa<AllocaInst>(getBaseObject(CI->getArgOperand(1), false))) {
749 allCallersAlloca = false;
750 break;
751 }
752 }
753 }
754 }
755 if (allCallersAlloca)
756 return;
757
758 F->addParamAttr(0, Attribute::get(F->getContext(), "enzyme_sret",
759 convertSRetTypeToString(SRetType)));
760 Attrs = F->getAttributes();
761 srets.clear();
762 size_t i = 0;
763 enzyme_srets.insert(i);
764 selected_roots[1] = 0;
765 reret_roots.clear();
766 } else {
767 F->addParamAttr(0, Attribute::get(F->getContext(), "enzyme_sret",
768 convertSRetTypeToString(SRetType)));
769 Attrs = F->getAttributes();
770 srets.clear();
771 size_t i = 0;
772 enzyme_srets.insert(i);
773 if (rerooting) {
774 reroot_enzyme_srets.insert(i);
775 } else if (anyJLStore) {
776 } else {
777 if (auto count = CountTrackedPointers(SRetType).count) {
778 srets_without_stores[i] = count;
779 noroot_enzyme_srets.insert(i);
780 }
781 }
782 }
783 } else if (srets.size() == 0 && enzyme_srets.size() == 0 &&
784 rroots.size() == 0) {
785 // No sret/rooting, no intervention needed.
786 return;
787 }
788
789 // Number of additional roots, which contain actually no data at all.
790 // Consider this additional rerooting of the sret, except this time
791 // just fill it with 0's
792 for (auto &pair : srets_without_stores) {
793 assert(pair.second);
794 reroot_enzyme_srets.insert(pair.first);
795 }
796
797 assert(srets.size() == 0);
798
799 SmallVector<Type *, 1> Types;
800 if (!RT->isVoidTy()) {
801 Types.push_back(RT);
802 }
803
804 auto T_jlvalue = StructType::get(F->getContext(), {});
805 auto T_prjlvalue = getPointerType(T_jlvalue, AddressSpace::Tracked);
806
807 size_t numRooting = RT->isVoidTy() ? 0 : CountTrackedPointers(RT).count;
808
809 for (auto idx : enzyme_srets) {
810 llvm::Type *SRetType = convertSRetTypeFromString(
811 Attrs.getAttribute(AttributeList::FirstArgIndex + idx, "enzyme_sret")
812 .getValueAsString(),
813 &F->getContext());
814#if LLVM_VERSION_MAJOR < 17
815 if (F->getContext().supportsTypedPointers()) {
816 auto T = FT->getParamType(idx)->getPointerElementType();
817 if (T != SRetType) {
818 std::string s;
819 llvm::raw_string_ostream ss(s);
820 ss << "Type mismatch in FixupJuliaCallingConvention:\n";
821 ss << " + T: " << *T << "\n";
822 ss << " + SRetType: " << *SRetType << "\n";
823 EmitFailure("TypeMismatch", F->getSubprogram(), F, ss.str());
824 }
825 }
826#endif
827 Types.push_back(SRetType);
828 if (reroot_enzyme_srets.count(idx)) {
829 numRooting += CountTrackedPointers(SRetType).count;
830 }
831 }
832 for (auto idx : rroots) {
833 size_t count = convertRRootCountFromString(
834 Attrs
835 .getAttribute(AttributeList::FirstArgIndex + idx,
836 "enzymejl_returnRoots")
837 .getValueAsString());
838 auto T = ArrayType::get(T_prjlvalue, count);
839#if LLVM_VERSION_MAJOR < 17
840 if (F->getContext().supportsTypedPointers()) {
841 auto NT = FT->getParamType(idx)->getPointerElementType();
842 assert(NT == T);
843 }
844#endif
845 if (reret_roots.count(idx)) {
846 Types.push_back(T);
847 }
848 numRooting += count;
849 }
850
851 StructType *ST =
852 Types.size() <= 1 ? nullptr : StructType::get(F->getContext(), Types);
853 Type *sretTy = nullptr;
854 if (Types.size())
855 sretTy = Types.size() == 1 ? Types[0] : ST;
856
857 ArrayType *roots_AT =
858 numRooting ? ArrayType::get(T_prjlvalue, numRooting) : nullptr;
859
860 if (sretTy) {
861 CountTrackedPointers countF(sretTy);
862 // If all fields of the sret struct are tracked pointers, the struct itself
863 // acts as a root anchor on the caller's stack frame. In this scenario, we
864 // do not allocate an additional explicit ReturnRoots array argument.
865 if (countF.all) {
866 roots_AT = nullptr;
867 numRooting = 0;
868 reroot_enzyme_srets.clear();
869 } else if (countF.count) {
870 if (!roots_AT) {
871 llvm::errs() << " sretTy: " << *sretTy << "\n";
872 llvm::errs() << " numRooting: " << numRooting << "\n";
873 llvm::errs() << " tracked.count: " << countF.count << "\n";
874 }
875 assert(roots_AT);
876 if (numRooting != countF.count) {
877 std::string s;
878 llvm::raw_string_ostream ss(s);
879 ss << "Illegal GC setup in which numRooting (" << numRooting
880 << ") != tracked.count (" << countF.count << ")\n";
881 ss << " sretTy: " << *sretTy << "\n";
882 ss << " Types.size(): " << Types.size() << "\n";
883 for (size_t i = 0; i < Types.size(); i++) {
884 ss << " + Types[" << i << "] = " << *Types[i] << "\n";
885 }
886 ss << " F: " << *F << "\n";
888 nullptr, nullptr, nullptr);
889 }
890 assert(numRooting == countF.count);
891 }
892 }
893
894 AttributeList NewAttrs;
895 SmallVector<Type *, 1> types;
896 size_t nexti = 0;
897 if (sretTy) {
898 types.push_back(getUnqual(sretTy));
899 NewAttrs = NewAttrs.addAttribute(
900 F->getContext(), AttributeList::FirstArgIndex + nexti,
901 Attribute::get(F->getContext(), Attribute::StructRet, sretTy));
902 NewAttrs = NewAttrs.addAttribute(F->getContext(),
903 AttributeList::FirstArgIndex + nexti,
904 Attribute::NoAlias);
905 nexti++;
906 }
907 if (roots_AT) {
908 NewAttrs = NewAttrs.addAttribute(
909 F->getContext(), AttributeList::FirstArgIndex + nexti,
910 "enzymejl_returnRoots", std::to_string(numRooting));
911 NewAttrs = NewAttrs.addAttribute(F->getContext(),
912 AttributeList::FirstArgIndex + nexti,
913 Attribute::NoAlias);
914 NewAttrs = NewAttrs.addAttribute(F->getContext(),
915 AttributeList::FirstArgIndex + nexti,
916 Attribute::WriteOnly);
917 types.push_back(getUnqual(roots_AT));
918 nexti++;
919 }
920
921 for (size_t i = 0, end = FT->getNumParams(); i < end; i++) {
922 if (enzyme_srets.count(i) || rroots.count(i))
923 continue;
924
925 for (auto attr : Attrs.getAttributes(AttributeList::FirstArgIndex + i)) {
926 NewAttrs = NewAttrs.addAttribute(
927 F->getContext(), AttributeList::FirstArgIndex + nexti, attr);
928 }
929 types.push_back(F->getFunctionType()->getParamType(i));
930 nexti++;
931 }
932
933 for (auto attr : Attrs.getAttributes(AttributeList::FunctionIndex))
934 NewAttrs = NewAttrs.addAttribute(F->getContext(),
935 AttributeList::FunctionIndex, attr);
936 if (sretTy || roots_AT)
937 NewAttrs = allowArgumentMemoryWrites(F->getContext(), NewAttrs);
938
939 FunctionType *FTy = FunctionType::get(Type::getVoidTy(F->getContext()), types,
940 FT->isVarArg());
941
942 // Create the new function
943 auto &M = *F->getParent();
944 Function *NewF = Function::Create(FTy, F->getLinkage(), F->getAddressSpace(),
945 F->getName(), &M);
946
947 ValueToValueMapTy VMap;
948 // Loop over the arguments, copying the names of the mapped arguments over...
949 Function::arg_iterator DestI = NewF->arg_begin();
950 Argument *sret = nullptr;
951 if (sretTy) {
952 sret = &*DestI;
953 DestI++;
954 }
955 Argument *roots = nullptr;
956 if (roots_AT) {
957 roots = &*DestI;
958 DestI++;
959 }
960
961 // To handle the deleted args, it needs to be replaced by a non-arg operand.
962 // This map contains the temporary phi nodes corresponding
963 std::map<size_t, PHINode *> delArgMap;
964 for (Argument &I : F->args()) {
965 auto i = I.getArgNo();
966 if (enzyme_srets.count(i) || rroots.count(i)) {
967 VMap[&I] = delArgMap[i] = PHINode::Create(I.getType(), 0);
968 continue;
969 }
970 assert(DestI != NewF->arg_end());
971 DestI->setName(I.getName()); // Copy the name over...
972 VMap[&I] = &*DestI++; // Add mapping to VMap
973 }
974 // Compute the readonly/nocapture/etc properties for analysis use later.
975 {
976 SmallPtrSet<Function *, 1> calls_todo;
977 (void)DetectPointerArgOfFn(*F, calls_todo);
978 }
979 SmallVector<ReturnInst *, 8> Returns; // Ignore returns cloned.
980 CloneFunctionInto(NewF, F, VMap, CloneFunctionChangeType::LocalChangesOnly,
981 Returns, "", nullptr);
982 resetClonedGUID(NewF);
983
984 SmallVector<CallInst *, 1> callers;
985 for (auto U : F->users()) {
986 auto CI = dyn_cast<CallInst>(U);
987 assert(CI);
988 assert(CI->getCalledFunction() == F);
989 callers.push_back(CI);
990 }
991
992 {
993 size_t curOffset = 0;
994 size_t sretCount = 0;
995 if (!RT->isVoidTy()) {
996 for (auto &RT : Returns) {
997 IRBuilder<> B(RT);
998 Value *gep = ST ? B.CreateConstInBoundsGEP2_32(ST, sret, 0, 0) : sret;
999 Value *rval = RT->getReturnValue();
1000 B.CreateStore(rval, gep);
1001
1002 if (roots) {
1003 moveSRetToFromRoots(B, rval->getType(), rval, roots_AT, roots,
1004 /*rootOffset*/ 0,
1006 }
1007
1008 auto NR = B.CreateRetVoid();
1009 RT->eraseFromParent();
1010 RT = NR;
1011 }
1012 if (roots_AT)
1013 curOffset = CountTrackedPointers(RT).count;
1014 sretCount++;
1015 }
1016
1017 // TODO this must be re-ordered to interleave the sret/roots/etc args as
1018 // required.
1019
1020 for (size_t i = 0, end = FT->getNumParams(); i < end; i++) {
1021
1022 if (enzyme_srets.count(i)) {
1023 auto argFound = delArgMap.find(i);
1024 assert(argFound != delArgMap.end());
1025 auto arg = argFound->second;
1026 assert(arg);
1027 SmallVector<Instruction *, 1> uses;
1028 SmallVector<unsigned, 1> op;
1029 for (auto &U : arg->uses()) {
1030 auto I = cast<Instruction>(U.getUser());
1031 uses.push_back(I);
1032 op.push_back(U.getOperandNo());
1033 }
1034 IRBuilder<> EB(&NewF->getEntryBlock().front());
1035 auto gep =
1036 ST ? EB.CreateConstInBoundsGEP2_32(ST, sret, 0, sretCount) : sret;
1037 for (size_t i = 0; i < uses.size(); i++) {
1038 uses[i]->setOperand(op[i], gep);
1039 }
1040
1041 if (reroot_enzyme_srets.count(i)) {
1042 assert(roots_AT);
1043 auto cnt = CountTrackedPointers(Types[sretCount]).count;
1044 for (auto &RT : Returns) {
1045 IRBuilder<> B(RT);
1046 if (noroot_enzyme_srets.count(i)) {
1047 for (size_t i = 0; i < cnt; i++) {
1048 B.CreateStore(ConstantPointerNull::get(T_prjlvalue),
1049 B.CreateConstInBoundsGEP2_32(roots_AT, roots, 0,
1050 i + curOffset));
1051 }
1052 } else {
1053 moveSRetToFromRoots(B, Types[sretCount], gep, roots_AT, roots,
1054 curOffset,
1056 }
1057 }
1058 curOffset += cnt;
1059 }
1060
1061 delete arg;
1062
1063 sretCount++;
1064 continue;
1065 }
1066
1067 if (rroots.count(i)) {
1068 auto attr = Attrs.getAttribute(AttributeList::FirstArgIndex + i,
1069 "enzymejl_returnRoots");
1070 auto attrv = attr.getValueAsString();
1071 assert(attrv.size());
1072 size_t subCount = convertRRootCountFromString(attrv);
1073
1074 auto argFound = delArgMap.find(i);
1075 assert(argFound != delArgMap.end());
1076 auto arg = argFound->second;
1077 assert(arg);
1078 SmallVector<Instruction *, 1> uses;
1079 SmallVector<unsigned, 1> op;
1080 for (auto &U : arg->uses()) {
1081 auto I = cast<Instruction>(U.getUser());
1082 uses.push_back(I);
1083 op.push_back(U.getOperandNo());
1084 }
1085 IRBuilder<> EB(&NewF->getEntryBlock().front());
1086
1087 Value *gep = nullptr;
1088 if (roots_AT) {
1089 assert(roots);
1090 assert(roots_AT);
1091
1092 gep = roots;
1093 if (curOffset != 0) {
1094 gep = EB.CreateConstInBoundsGEP2_32(roots_AT, roots, 0, curOffset);
1095 }
1096 if (subCount != numRooting) {
1097 gep = EB.CreatePointerCast(
1098 gep, getUnqual(ArrayType::get(T_prjlvalue, subCount)));
1099 }
1100 curOffset += subCount;
1101 if (reret_roots.count(i))
1102 sretCount++;
1103 } else {
1104 assert(sret);
1105 gep =
1106 ST ? EB.CreateConstInBoundsGEP2_32(ST, sret, 0, sretCount) : sret;
1107
1108 if (!reret_roots.count(i)) {
1109
1110 std::string s;
1111 llvm::raw_string_ostream ss(s);
1112 ss << "Illegal GC setup in which there was no roots_AT, but a new "
1113 "sret ("
1114 << *sret << "), but no rereturned roots at index i=" << i
1115 << "\n";
1116 CustomErrorHandler(s.c_str(), wrap(gep), ErrorType::InternalError,
1117 nullptr, nullptr, nullptr);
1118 }
1119
1120 sretCount++;
1121 }
1122
1123 for (size_t i = 0; i < uses.size(); i++) {
1124 uses[i]->setOperand(op[i], gep);
1125 }
1126
1127 delete arg;
1128 continue;
1129 }
1130 }
1131
1132 assert(curOffset == numRooting);
1133 assert(sretCount == Types.size());
1134 }
1135
1136 auto &DL = F->getParent()->getDataLayout();
1137
1138 // TODO fix caller side
1139 for (auto CI : callers) {
1140 auto Attrs = CI->getAttributes();
1141 AttributeList NewAttrs;
1142 IRBuilder<> B(CI);
1143 IRBuilder<> EB(&CI->getParent()->getParent()->getEntryBlock().front());
1144 SmallVector<Value *, 1> vals;
1145 size_t nexti = 0;
1146 Value *sret = nullptr;
1147 if (sretTy) {
1148 sret = EB.CreateAlloca(sretTy, 0, "stack_sret");
1149 vals.push_back(sret);
1150 NewAttrs = NewAttrs.addAttribute(
1151 F->getContext(), AttributeList::FirstArgIndex + nexti,
1152 Attribute::get(F->getContext(), Attribute::StructRet, sretTy));
1153 nexti++;
1154 }
1155 AllocaInst *roots = nullptr;
1156 if (roots_AT) {
1157 roots = EB.CreateAlloca(roots_AT, 0, "stack_roots_AT");
1158 vals.push_back(roots);
1159 NewAttrs = NewAttrs.addAttribute(
1160
1161 F->getContext(), AttributeList::FirstArgIndex + nexti,
1162 "enzymejl_returnRoots", std::to_string(numRooting));
1163 nexti++;
1164 }
1165
1166 for (auto attr : Attrs.getAttributes(AttributeList::FunctionIndex))
1167 NewAttrs = NewAttrs.addAttribute(F->getContext(),
1168 AttributeList::FunctionIndex, attr);
1169 if (sretTy || roots_AT)
1170 NewAttrs = allowArgumentMemoryWrites(F->getContext(), NewAttrs);
1171
1172 SmallVector<std::tuple<Value *, Value *, Type *>> preCallReplacements;
1173 SmallVector<std::tuple<Value *, Value *, Type *, bool>>
1174 postCallReplacements;
1175
1176 {
1177 size_t local_root_count = 0;
1178 size_t sretCount = 0;
1179 if (!RT->isVoidTy()) {
1180 if (roots_AT) {
1181 local_root_count += CountTrackedPointers(RT).count;
1182 }
1183 sretCount++;
1184 }
1185
1186 /// TODO continue from here down for external rewrites
1187 for (size_t i = 0, end = CI->arg_size(); i < end; i++) {
1188
1189 if (enzyme_srets.count(i)) {
1190 auto val = CI->getArgOperand(i);
1191
1192 if (isa<UndefValue>(val) || isa<PoisonValue>(val) ||
1193 isa<ConstantPointerNull>(val)) {
1194 std::string s;
1195 llvm::raw_string_ostream ss(s);
1196 ss << "Unsupported constant argument in "
1197 "FixupJuliaCallingConvention\n";
1198 ss << " + val: " << *val << "\n";
1199 ss << " + Function being rewritten: " << F->getName() << "\n";
1200 ss << " + CI erring: " << *CI << "\n";
1201 ss << " + Function containing CI: "
1202 << CI->getParent()->getParent()->getName() << "\n";
1203 if (CustomErrorHandler) {
1204 CustomErrorHandler(s.c_str(), wrap(CI), ErrorType::InternalError,
1205 nullptr, nullptr, nullptr);
1206 } else {
1207 EmitFailure("UnsupportedArgument", CI->getDebugLoc(), CI,
1208 ss.str());
1209 }
1210 }
1211
1212 Value *gep = sret;
1213 if (ST) {
1214 IRBuilder<> GEPB(cast<Instruction>(sret)->getNextNode());
1215 gep = GEPB.CreateConstInBoundsGEP2_32(ST, sret, 0, sretCount);
1216 }
1217
1218 bool handled = false;
1219 if (auto AI = dyn_cast<AllocaInst>(getBaseObject(val, false))) {
1220 if (AI->getAllocatedType() == Types[sretCount] ||
1221 (isOpaque(AI->getType()) &&
1222 DL.getTypeSizeInBits(AI->getAllocatedType()) ==
1223 DL.getTypeSizeInBits(Types[sretCount]))) {
1224 AI->replaceAllUsesWith(gep);
1225 AI->eraseFromParent();
1226 handled = true;
1227 }
1228 }
1229
1230 if (!handled) {
1231 assert(!isa<UndefValue>(val));
1232 assert(!isa<PoisonValue>(val));
1233 assert(!isa<ConstantPointerNull>(val));
1234
1235 // On Julia 1.12+, the sret does not actually contain the jlvaluet
1236 // (and it should not). However, if the sret does not contain a
1237 // return roots (per tracked pointers), we do still need to perform
1238 // the store.
1239 bool should_sret = sret_jlvalue;
1240 if (!should_sret) {
1241 CountTrackedPointers tracked(Types[sretCount]);
1242 if (tracked.count && tracked.all)
1243 should_sret = true;
1244 }
1245
1246 // Don't bother to copy back in if the original function doesn't
1247 // store anything.
1248 bool copyBack = !isReadOnlyNoCapture(F, i);
1249 if (copyBack) {
1250 postCallReplacements.emplace_back(val, gep, Types[sretCount],
1251 should_sret);
1252 }
1253 // Only copy in the inital value if the function reads, or we are
1254 // going to copy back and the function doesn't store all bytes.
1255 if (!isWriteOnly(CI, i) ||
1256 (copyBack &&
1257 !isGuaranteedToFullyWrite(F, i, Types[sretCount]))) {
1258 preCallReplacements.emplace_back(val, gep, Types[sretCount]);
1259 }
1260 }
1261
1262 if (roots_AT && reroot_enzyme_srets.count(i)) {
1263 local_root_count += CountTrackedPointers(Types[sretCount]).count;
1264 }
1265
1266 sretCount++;
1267 continue;
1268 }
1269
1270 if (rroots.count(i)) {
1271 auto val = CI->getArgOperand(i);
1272 if (isa<UndefValue>(val) || isa<PoisonValue>(val) ||
1273 isa<ConstantPointerNull>(val)) {
1274 std::string s;
1275 llvm::raw_string_ostream ss(s);
1276 ss << "Unsupported constant argument in "
1277 "FixupJuliaCallingConvention\n";
1278 ss << " + val: " << *val << "\n";
1279 ss << " + Function being rewritten: " << F->getName() << "\n";
1280 ss << " + CI erring: " << *CI << "\n";
1281 ss << " + Function containing CI: "
1282 << CI->getParent()->getParent()->getName() << "\n";
1283 if (CustomErrorHandler) {
1284 CustomErrorHandler(s.c_str(), wrap(CI), ErrorType::InternalError,
1285 nullptr, nullptr, nullptr);
1286 } else {
1287 EmitFailure("UnsupportedArgument", CI->getDebugLoc(), CI,
1288 ss.str());
1289 }
1290 }
1291
1292 auto attr = Attrs.getAttribute(AttributeList::FirstArgIndex + i,
1293 "enzymejl_returnRoots");
1294 auto attrv = attr.getValueAsString();
1295 assert(attrv.size());
1296 size_t subCount = convertRRootCountFromString(attrv);
1297
1298 Value *gep = nullptr;
1299
1300 if (roots_AT) {
1301 assert(roots);
1302 IRBuilder<> GEPB(cast<Instruction>(roots)->getNextNode());
1303 gep = roots;
1304 if (local_root_count != 0) {
1305 gep = GEPB.CreateConstInBoundsGEP2_32(roots_AT, roots, 0,
1306 local_root_count);
1307 }
1308
1309 if (subCount != numRooting) {
1310 gep = GEPB.CreatePointerCast(
1311 gep, getUnqual(ArrayType::get(T_prjlvalue, subCount)));
1312 }
1313 local_root_count += subCount;
1314 if (reret_roots.count(i))
1315 sretCount++;
1316 } else {
1317 assert(reret_roots.count(i));
1318 assert(sret);
1319 IRBuilder<> GEPB(cast<Instruction>(sret)->getNextNode());
1320 gep = sret;
1321 if (ST) {
1322 gep = GEPB.CreateConstInBoundsGEP2_32(ST, sret, 0, sretCount);
1323 }
1324 sretCount++;
1325 }
1326
1327 bool handled = false;
1328 if (auto AI = dyn_cast<AllocaInst>(getBaseObject(val, false))) {
1329 if (AI->getAllocatedType() ==
1330 ArrayType::get(T_prjlvalue, subCount)) {
1331 AI->replaceAllUsesWith(gep);
1332 AI->eraseFromParent();
1333 handled = true;
1334 }
1335 }
1336
1337 if (!handled) {
1338 assert(!isa<UndefValue>(val));
1339 assert(!isa<PoisonValue>(val));
1340 assert(!isa<ConstantPointerNull>(val));
1341 // TODO consider doing pre-emptive pre zero of the section?
1342 preCallReplacements.emplace_back(
1343 val, gep, ArrayType::get(T_prjlvalue, subCount));
1344 postCallReplacements.emplace_back(
1345 val, gep, ArrayType::get(T_prjlvalue, subCount), true);
1346 }
1347 continue;
1348 }
1349
1350 for (auto attr : Attrs.getAttributes(AttributeList::FirstArgIndex + i))
1351 NewAttrs = NewAttrs.addAttribute(
1352 F->getContext(), AttributeList::FirstArgIndex + nexti, attr);
1353 vals.push_back(CI->getArgOperand(i));
1354 nexti++;
1355 }
1356
1357 assert(sretCount == Types.size());
1358 assert(local_root_count == numRooting);
1359 }
1360
1361 // Because we will += into the corresponding derivative sret, we need to
1362 // pass in the values that were actually there before the call
1363 // TODO we can optimize this further and avoid the copy in the primal and/or
1364 // forward mode as the copy is _only_ needed for the adjoint.
1365 for (auto &&[val, gep, ty] : preCallReplacements) {
1366 copyNonJLValueInto(B, ty, ty, gep, {}, ty, val, {}, /*shouldZero*/ true);
1367 }
1368
1369 // Actually perform the call, copying over relevant information.
1370 SmallVector<OperandBundleDef, 1> Bundles;
1371 for (unsigned I = 0, E = CI->getNumOperandBundles(); I != E; ++I)
1372 Bundles.emplace_back(CI->getOperandBundleAt(I));
1373
1374 if (!NewF->getFunctionType()->isVarArg() &&
1375 NewF->getFunctionType()->getNumParams() != vals.size()) {
1376 llvm::errs() << "NewF: " << *NewF << "\n";
1377 for (size_t i = 0; i < vals.size(); i++) {
1378 llvm::errs() << " Args[" << i << "] = " << *vals[i] << "\n";
1379 }
1380 }
1381 auto NC = B.CreateCall(NewF, vals, Bundles);
1382 NC->setAttributes(NewAttrs);
1383
1384 SmallVector<std::pair<unsigned, MDNode *>, 4> TheMDs;
1385 CI->getAllMetadataOtherThanDebugLoc(TheMDs);
1386 SmallVector<unsigned, 1> toCopy;
1387 for (auto pair : TheMDs)
1388 if (pair.first != LLVMContext::MD_range) {
1389 toCopy.push_back(pair.first);
1390 }
1391 if (!toCopy.empty())
1392 NC->copyMetadata(*CI, toCopy);
1393 NC->setDebugLoc(CI->getDebugLoc());
1394
1395 if (!RT->isVoidTy()) {
1396 auto gep = ST ? B.CreateConstInBoundsGEP2_32(ST, sret, 0, 0) : sret;
1397 auto ld = B.CreateLoad(RT, gep);
1398 if (auto MD = CI->getMetadata(LLVMContext::MD_range))
1399 ld->setMetadata(LLVMContext::MD_range, MD);
1400 ld->takeName(CI);
1401 Value *replacement = ld;
1402
1403 // We don't need to override the jlvalue_t's with the rooted versions here
1404 // since we already stored the full value into the sret above.
1405 // if (fromRoots) {
1406 // replacement = moveSRetToFromRoots(B, replacement->getType(),
1407 // replacement, root_AT, root, /*rootOffset*/0,
1408 // SRetRootMovement::RootPointerToSRetValue);
1409 //}
1410
1411 CI->replaceAllUsesWith(replacement);
1412 }
1413
1414 for (auto &&[val, gep, ty, jlvalue] : postCallReplacements) {
1415 if (jlvalue) {
1416 auto ld = B.CreateLoad(ty, gep);
1417 auto SI = B.CreateStore(ld, val);
1418 if (val->getType()->getPointerAddressSpace() == 10)
1419 PostCacheStore(SI, B);
1420 } else {
1421 copyNonJLValueInto(B, ty, ty, val, {}, ty, gep, {},
1422 /*shouldZero*/ false);
1423 }
1424 }
1425
1426 NC->setCallingConv(CI->getCallingConv());
1427 CI->eraseFromParent();
1428 }
1429 NewF->setAttributes(NewAttrs);
1430 SmallVector<std::pair<unsigned, MDNode *>, 1> MD;
1431 F->getAllMetadata(MD);
1432 for (auto pair : MD)
1433 if (pair.first != LLVMContext::MD_dbg)
1434 NewF->addMetadata(pair.first, *pair.second);
1435 NewF->takeName(F);
1436 NewF->setCallingConv(F->getCallingConv());
1437 F->eraseFromParent();
1438}
1439
1440#include "llvm/Passes/PassBuilder.h"
1441
1442#include <string>
1443
1444using namespace llvm;
1445
1447 if (F->empty())
1448 return;
1449 auto RT = F->getReturnType();
1450 auto FT = F->getFunctionType();
1451 auto Attrs = F->getAttributes();
1452
1453 AttributeList NewAttrs;
1454 SmallVector<Type *, 1> types;
1455 SmallSet<size_t, 1> changed;
1456 for (auto pair : llvm::enumerate(FT->params())) {
1457 auto T = pair.value();
1458 auto i = pair.index();
1459 bool sretv = false;
1460 StringRef kind;
1461 StringRef value;
1462 for (auto attr : Attrs.getAttributes(AttributeList::FirstArgIndex + i)) {
1463 if (attr.isStringAttribute() &&
1464 attr.getKindAsString() == "enzyme_sret_v") {
1465 sretv = true;
1466 kind = "enzyme_sret";
1467 value = attr.getValueAsString();
1468 } else if (attr.isStringAttribute() &&
1469 attr.getKindAsString() == "enzymejl_rooted_typ_v") {
1470 sretv = true;
1471 kind = "enzymejl_rooted_typ";
1472 value = attr.getValueAsString();
1473 } else if (attr.isStringAttribute() &&
1474 attr.getKindAsString() == "enzymejl_returnRoots_v") {
1475 sretv = true;
1476 kind = "enzymejl_returnRoots";
1477 value = attr.getValueAsString();
1478 } else {
1479 NewAttrs = NewAttrs.addAttribute(
1480 F->getContext(), AttributeList::FirstArgIndex + types.size(), attr);
1481 }
1482 }
1483 if (auto AT = dyn_cast<ArrayType>(T)) {
1484 if (auto PT = dyn_cast<PointerType>(AT->getElementType())) {
1485 auto AS = PT->getAddressSpace();
1486 if (AS == 11 || AS == 12 || AS == 13 || sretv) {
1487 for (unsigned i = 0; i < AT->getNumElements(); i++) {
1488 if (sretv) {
1489 NewAttrs = NewAttrs.addAttribute(
1490 F->getContext(), AttributeList::FirstArgIndex + types.size(),
1491 Attribute::get(F->getContext(), kind, value));
1492 }
1493 types.push_back(PT);
1494 }
1495 changed.insert(i);
1496 continue;
1497 }
1498 }
1499 }
1500 assert(!sretv);
1501 types.push_back(T);
1502 }
1503 if (changed.size() == 0)
1504 return;
1505
1506 for (auto attr : Attrs.getAttributes(AttributeList::FunctionIndex))
1507 NewAttrs = NewAttrs.addAttribute(F->getContext(),
1508 AttributeList::FunctionIndex, attr);
1509
1510 for (auto attr : Attrs.getAttributes(AttributeList::ReturnIndex))
1511 NewAttrs = NewAttrs.addAttribute(F->getContext(),
1512 AttributeList::ReturnIndex, attr);
1513
1514 FunctionType *FTy =
1515 FunctionType::get(FT->getReturnType(), types, FT->isVarArg());
1516
1517 // Create the new function
1518 Function *NewF = Function::Create(FTy, F->getLinkage(), F->getAddressSpace(),
1519 F->getName(), F->getParent());
1520
1521 ValueToValueMapTy VMap;
1522 // Loop over the arguments, copying the names of the mapped arguments over...
1523 Function::arg_iterator DestI = NewF->arg_begin();
1524
1525 // To handle the deleted args, it needs to be replaced by a non-arg operand.
1526 // This map contains the temporary phi nodes corresponding
1527 SmallVector<Instruction *, 1> toInsert;
1528 for (Argument &I : F->args()) {
1529 auto T = I.getType();
1530 if (auto AT = dyn_cast<ArrayType>(T)) {
1531 if (changed.count(I.getArgNo())) {
1532 Value *V = UndefValue::get(T);
1533 for (unsigned i = 0; i < AT->getNumElements(); i++) {
1534 DestI->setName(I.getName() + "." +
1535 std::to_string(i)); // Copy the name over...
1536 unsigned idx[1] = {i};
1537 auto IV = InsertValueInst::Create(V, (llvm::Value *)&*DestI++, idx);
1538 toInsert.push_back(IV);
1539 V = IV;
1540 }
1541 VMap[&I] = V;
1542 continue;
1543 }
1544 }
1545 DestI->setName(I.getName()); // Copy the name over...
1546 VMap[&I] = &*DestI++; // Add mapping to VMap
1547 }
1548
1549 SmallVector<ReturnInst *, 8> Returns; // Ignore returns cloned.
1550 CloneFunctionInto(NewF, F, VMap, CloneFunctionChangeType::LocalChangesOnly,
1551 Returns, "", nullptr);
1552 resetClonedGUID(NewF);
1553
1554 {
1555 IRBuilder<> EB(&*NewF->getEntryBlock().begin());
1556 for (auto I : toInsert)
1557 EB.Insert(I);
1558 }
1559
1560 SmallVector<CallInst *, 1> callers;
1561 for (auto U : F->users()) {
1562 auto CI = dyn_cast<CallInst>(U);
1563 assert(CI);
1564 assert(CI->getCalledFunction() == F);
1565 callers.push_back(CI);
1566 }
1567
1568 for (auto CI : callers) {
1569 auto Attrs = CI->getAttributes();
1570 AttributeList NewAttrs;
1571 IRBuilder<> B(CI);
1572
1573 for (auto attr : Attrs.getAttributes(AttributeList::FunctionIndex))
1574 NewAttrs = NewAttrs.addAttribute(F->getContext(),
1575 AttributeList::FunctionIndex, attr);
1576
1577 for (auto attr : Attrs.getAttributes(AttributeList::ReturnIndex))
1578 NewAttrs = NewAttrs.addAttribute(F->getContext(),
1579 AttributeList::ReturnIndex, attr);
1580
1581 SmallVector<Value *, 1> vals;
1582 for (size_t j = 0, end = CI->arg_size(); j < end; j++) {
1583
1584 auto T = CI->getArgOperand(j)->getType();
1585 if (auto AT = dyn_cast<ArrayType>(T)) {
1586 if (isa<PointerType>(AT->getElementType())) {
1587 if (changed.count(j)) {
1588 bool sretv = false;
1589 std::string kind;
1590 StringRef value;
1591 for (auto attr :
1592 Attrs.getAttributes(AttributeList::FirstArgIndex + j)) {
1593 if (attr.isStringAttribute() &&
1594 attr.getKindAsString() == "enzyme_sret_v") {
1595 sretv = true;
1596 kind = "enzyme_sret";
1597 value = attr.getValueAsString();
1598 } else if (attr.isStringAttribute() &&
1599 attr.getKindAsString() == "enzymejl_returnRoots_v") {
1600 sretv = true;
1601 kind = "enzymejl_returnRoots";
1602 value = attr.getValueAsString();
1603 } else if (attr.isStringAttribute() &&
1604 attr.getKindAsString() == "enzymejl_rooted_typ_v") {
1605 sretv = true;
1606 kind = "enzymejl_rooted_typ_v";
1607 value = attr.getValueAsString();
1608 }
1609 }
1610 for (unsigned i = 0; i < AT->getNumElements(); i++) {
1611 if (sretv)
1612 NewAttrs = NewAttrs.addAttribute(
1613 F->getContext(), AttributeList::FirstArgIndex + vals.size(),
1614 Attribute::get(F->getContext(), kind, value));
1615 vals.push_back(
1616 GradientUtils::extractMeta(B, CI->getArgOperand(j), i));
1617 }
1618 continue;
1619 }
1620 }
1621 }
1622
1623 for (auto attr : Attrs.getAttributes(AttributeList::FirstArgIndex + j)) {
1624 if (attr.isStringAttribute() &&
1625 attr.getKindAsString() == "enzyme_sret_v") {
1626 NewAttrs = NewAttrs.addAttribute(
1627 F->getContext(), AttributeList::FirstArgIndex + vals.size(),
1628 Attribute::get(F->getContext(), "enzyme_sret",
1629 attr.getValueAsString()));
1630 } else if (attr.isStringAttribute() &&
1631 attr.getKindAsString() == "enzymejl_returnRoots_v") {
1632 NewAttrs = NewAttrs.addAttribute(
1633 F->getContext(), AttributeList::FirstArgIndex + vals.size(),
1634 Attribute::get(F->getContext(), "enzymejl_returnRoots",
1635 attr.getValueAsString()));
1636 } else if (attr.isStringAttribute() &&
1637 attr.getKindAsString() == "enzymejl_rooted_typ_v") {
1638 NewAttrs = NewAttrs.addAttribute(
1639 F->getContext(), AttributeList::FirstArgIndex + vals.size(),
1640 Attribute::get(F->getContext(), "enzymejl_rooted_typ",
1641 attr.getValueAsString()));
1642 } else {
1643 NewAttrs = NewAttrs.addAttribute(
1644 F->getContext(), AttributeList::FirstArgIndex + vals.size(),
1645 attr);
1646 }
1647 }
1648
1649 vals.push_back(CI->getArgOperand(j));
1650 }
1651
1652 SmallVector<OperandBundleDef, 1> Bundles;
1653 for (unsigned I = 0, E = CI->getNumOperandBundles(); I != E; ++I)
1654 Bundles.emplace_back(CI->getOperandBundleAt(I));
1655 auto NC = B.CreateCall(NewF, vals, Bundles);
1656 NC->setAttributes(NewAttrs);
1657
1658 SmallVector<std::pair<unsigned, MDNode *>, 4> TheMDs;
1659 CI->getAllMetadataOtherThanDebugLoc(TheMDs);
1660 SmallVector<unsigned, 1> toCopy;
1661 for (auto pair : TheMDs)
1662 toCopy.push_back(pair.first);
1663 if (!toCopy.empty())
1664 NC->copyMetadata(*CI, toCopy);
1665 NC->setDebugLoc(CI->getDebugLoc());
1666
1667 if (!RT->isVoidTy()) {
1668 NC->takeName(CI);
1669 CI->replaceAllUsesWith(NC);
1670 }
1671
1672 NC->setCallingConv(CI->getCallingConv());
1673 CI->eraseFromParent();
1674 }
1675 NewF->setAttributes(NewAttrs);
1676 SmallVector<std::pair<unsigned, MDNode *>, 1> MD;
1677 F->getAllMetadata(MD);
1678 for (auto pair : MD)
1679 if (pair.first != LLVMContext::MD_dbg)
1680 NewF->addMetadata(pair.first, *pair.second);
1681 NewF->takeName(F);
1682 NewF->setCallingConv(F->getCallingConv());
1683 F->eraseFromParent();
1684}
1685
1687#if LLVM_VERSION_MAJOR >= 23
1688 : public OptionalPassInfoMixin<FixupJuliaCallingConventionNewPM> {
1689#else
1691#endif
1692 bool sret_jlvalue;
1693
1694public:
1696 : sret_jlvalue(sret_jlvalue) {}
1697
1698 PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
1699 bool changed = false;
1700 SmallVector<llvm::Function *, 16> Functions;
1701 for (auto &F : M) {
1702 if (F.empty())
1703 continue;
1704 Functions.push_back(&F);
1705 }
1706 for (auto *F : Functions) {
1707 EnzymeFixupJuliaCallingConvention(F, sret_jlvalue);
1708 changed = true;
1709 }
1710 return changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
1711 }
1712};
1713
1715#if LLVM_VERSION_MAJOR >= 23
1716 : public OptionalPassInfoMixin<FixupBatchedJuliaCallingConventionNewPM> {
1717#else
1719#endif
1720public:
1721 PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
1722 bool changed = false;
1723 SmallVector<llvm::Function *, 16> Functions;
1724 for (auto &F : M) {
1725 if (F.empty())
1726 continue;
1727 Functions.push_back(&F);
1728 }
1729 for (auto *F : Functions) {
1731 changed = true;
1732 }
1733 return changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
1734 }
1735};
1736
1737// Expose New PM pass for registration
1738bool registerFixupJuliaPass(StringRef Name, ModulePassManager &MPM) {
1739 if (Name == "enzyme-fixup-julia") {
1740 MPM.addPass(FixupJuliaCallingConventionNewPM(false));
1741 return true;
1742 }
1743 if (Name == "enzyme-fixup-julia-sret") {
1744 MPM.addPass(FixupJuliaCallingConventionNewPM(true));
1745 return true;
1746 }
1747 if (Name == "enzyme-fixup-batched-julia") {
1749 return true;
1750 }
1751 return false;
1752}
static bool isReadOnlyNoCapture(Function *F, unsigned argNo)
static AttributeList allowArgumentMemoryWrites(LLVMContext &Ctx, AttributeList Attrs)
static void removeRange(std::vector< std::pair< uint64_t, uint64_t > > &ranges, uint64_t start, uint64_t end)
void EnzymeFixupBatchedJuliaCallingConvention(Function *F)
bool needsReReturning(llvm::Argument *arg, size_t &sret_idx, std::map< size_t, size_t > &srets_without_stores)
#define getAttribute
bool needsReRooting(llvm::Argument *arg, bool &anyJLStore, llvm::Type *SRetType=nullptr, std::set< size_t > *rootingArgs=nullptr, const std::map< size_t, size_t > *assignedRoots=nullptr)
static bool isOpaque(llvm::Type *T)
bool DetectPointerArgOfFn(llvm::Function &F, llvm::SmallPtrSetImpl< llvm::Function * > &calls_todo)
static bool isGuaranteedToFullyWrite(Function *F, unsigned argNo, Type *T)
bool registerFixupJuliaPass(StringRef Name, ModulePassManager &MPM)
void EnzymeFixupJuliaCallingConvention(Function *F, bool sret_jlvalue)
llvm::SmallVector< llvm::Instruction *, 2 > PostCacheStore(llvm::StoreInst *SI, llvm::IRBuilder<> &B)
Definition Utils.cpp:517
LLVMValueRef(* CustomErrorHandler)(const char *, LLVMValueRef, ErrorType, const void *, LLVMValueRef, LLVMBuilderRef)
Definition Utils.cpp:62
llvm::Value * moveSRetToFromRoots(llvm::IRBuilder<> &B, llvm::Type *jltype, llvm::Value *sret, llvm::Type *root_ty, llvm::Value *rootRet, size_t rootOffset, SRetRootMovement direction)
Definition Utils.cpp:5148
SmallVector< std::tuple< Instruction *, Value *, size_t >, 1 > findAllUsersOf(Value *AI)
Definition Utils.cpp:3488
void copyNonJLValueInto(llvm::IRBuilder<> &B, llvm::Type *curType, llvm::Type *dstType, llvm::Value *dst, llvm::ArrayRef< unsigned > dstPrefix0, llvm::Type *srcType, llvm::Value *src, llvm::ArrayRef< unsigned > srcPrefix0, bool shouldZero)
Definition Utils.cpp:5271
bool collectOffset(GEPOperator *gep, const DataLayout &DL, unsigned BitWidth, MapVector< Value *, APInt > &VariableOffsets, APInt &ConstantOffset)
Definition Utils.cpp:4464
static llvm::PointerType * getPointerType(llvm::Type *T, unsigned AddressSpace=0)
Pointer into the given address space.
Definition Utils.h:1313
static size_t convertRRootCountFromString(llvm::StringRef str)
Definition Utils.h:2910
static llvm::PointerType * getUnqual(llvm::Type *T)
Definition Utils.h:1329
void EmitFailure(llvm::StringRef RemarkName, const llvm::DiagnosticLocation &Loc, const llvm::Instruction *CodeRegion, Args &...args)
Definition Utils.h:347
static llvm::Type * convertSRetTypeFromString(llvm::StringRef str, llvm::LLVMContext *C=nullptr)
Definition Utils.h:2858
static llvm::Value * getBaseObject(llvm::Value *V, bool offsetAllowed=true)
Definition Utils.h:1711
static bool isSpecialPtr(llvm::Type *Ty)
Definition Utils.h:2664
@ Tracked
Definition Utils.h:2651
static void resetClonedGUID(llvm::Function *F)
Definition Utils.h:91
static std::string convertSRetTypeToString(llvm::Type *T)
Definition Utils.h:2853
static bool isWriteOnly(const llvm::Function *F, ssize_t arg=-1)
Definition Utils.h:2033
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM)
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM)
static llvm::Value * extractMeta(llvm::IRBuilder<> &Builder, llvm::Value *Agg, unsigned off, const llvm::Twine &name="")
Helper routine to extract a nested element from a struct/array. This is.