Enzyme main
Loading...
Searching...
No Matches
ActivityAnnotations.cpp
Go to the documentation of this file.
3#include "DataFlowLattice.h"
4#include "Dialect/Dialect.h"
5#include "Dialect/Ops.h"
7
8#include "mlir/Analysis/DataFlow/DenseAnalysis.h"
9#include "mlir/Analysis/DataFlow/SparseAnalysis.h"
10#include "mlir/Analysis/DataFlow/Utils.h"
11#include "mlir/Analysis/DataFlowFramework.h"
12#include "mlir/Interfaces/FunctionInterfaces.h"
13#include "llvm/ADT/TypeSwitch.h"
14#include "llvm/Support/raw_ostream.h"
15
16// TODO: Remove dependency on dialects in favour of differential dependency
17// interface
18#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
19
20using namespace mlir;
21
22static bool isPossiblyActive(Type type) {
23 if (auto tensorType = dyn_cast<TensorType>(type)) {
24 return isPossiblyActive(tensorType.getElementType());
25 }
26 return isa<FloatType, ComplexType>(type);
27}
28
29void enzyme::ForwardOriginsLattice::print(raw_ostream &os) const {
30 os << serialize(getAnchor().getContext());
31}
32
33void enzyme::BackwardOriginsLattice::print(raw_ostream &os) const {
34 os << serialize(getAnchor().getContext());
35}
36
37ChangeResult
39 const auto *otherValueOrigins =
40 static_cast<const ForwardOriginsLattice *>(&other);
41 return elements.join(otherValueOrigins->elements);
42}
43
45 ForwardOriginsLattice *lattice) {
46 auto arg = dyn_cast<BlockArgument>(lattice->getAnchor());
47 if (!arg) {
48 assert(lattice->isUndefined());
49 return;
50 }
51 if (!isPossiblyActive(arg.getType())) {
52 return;
53 }
54
55 if (auto funcOp =
56 dyn_cast<FunctionOpInterface>(arg.getOwner()->getParentOp())) {
57 auto origin = ArgumentOriginAttr::get(FlatSymbolRefAttr::get(funcOp),
58 arg.getArgNumber());
59 return propagateIfChanged(lattice,
61 lattice->getAnchor(), origin)));
62 }
63}
64
65void enzyme::ForwardActivityAnnotationAnalysis::markResultsUnknown(
66 ArrayRef<ForwardOriginsLattice *> results) {
67 for (ForwardOriginsLattice *result : results) {
68 propagateIfChanged(result, result->markUnknown());
69 }
70}
71
73 Operation *op, ArrayRef<const ForwardOriginsLattice *> operands,
74 ArrayRef<ForwardOriginsLattice *> results) {
75 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
76 if (iface && iface.isInactive())
77 return success();
78
79 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
80 if (!memory) {
81 if (!isMemoryEffectFree(op))
82 markResultsUnknown(results);
83 return success();
84 }
85
86 SmallVector<MemoryEffects::EffectInstance> effects;
87 memory.getEffects(effects);
88 for (const auto &effect : effects) {
89 if (!isa<MemoryEffects::Read>(effect.getEffect()))
90 continue;
91
92 Value value = effect.getValue();
93 if (!value) {
94 markResultsUnknown(results);
95 continue;
96 }
97 processMemoryRead(op, value, results);
98 }
99
100 // Propagate sparse differential dependencies, excluding any inactive
101 // arguments.
102 for (auto &&[i, operand] : llvm::enumerate(operands)) {
103 if (iface && iface.isArgInactive(i))
104 continue;
105
106 for (ForwardOriginsLattice *result : results) {
107 join(result, *operand);
108 }
109 }
110 return success();
111}
112
113void enzyme::ForwardActivityAnnotationAnalysis::processMemoryRead(
114 Operation *op, Value address, ArrayRef<ForwardOriginsLattice *> results) {
115 ProgramPoint *point = getProgramPointAfter(op);
116 auto *srcClasses = getOrCreateFor<AliasClassLattice>(point, address);
117 auto *originsMap = getOrCreateFor<ForwardOriginsMap>(point, point);
118 if (srcClasses->isUndefined())
119 return;
120 if (srcClasses->isUnknown())
121 return markResultsUnknown(results);
122
123 // Look up the alias class and see what its origins are, then propagate
124 // those origins to the read results.
125 for (DistinctAttr srcClass : srcClasses->getAliasClasses()) {
126 for (ForwardOriginsLattice *result : results) {
127 if (isPossiblyActive(result->getAnchor().getType())) {
128 propagateIfChanged(result,
129 result->merge(originsMap->getOrigins(srcClass)));
130 }
131 }
132 }
133}
134
135void deserializeReturnOrigins(ArrayAttr returnOrigins,
136 SmallVectorImpl<enzyme::ValueOriginSet> &out) {
137 for (auto &&[resultIdx, argOrigins] : llvm::enumerate(returnOrigins)) {
139 if (auto strAttr = dyn_cast<StringAttr>(argOrigins)) {
140 if (strAttr.getValue() == "<unknown>") {
141 (void)origins.markUnknown();
142 } else {
143 // Leave origins undefined
144 }
145 } else {
146 for (enzyme::ArgumentOriginAttr originAttr :
147 cast<ArrayAttr>(argOrigins)
148 .getAsRange<enzyme::ArgumentOriginAttr>()) {
149 (void)origins.insert({originAttr});
150 }
151 }
152
153 out.push_back(origins);
154 }
155}
156
158 CallOpInterface call, ArrayRef<const ForwardOriginsLattice *> operands,
159 ArrayRef<ForwardOriginsLattice *> results) {
160 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
161 auto markAllResultsUnknown = [&]() {
162 for (ForwardOriginsLattice *result : results) {
163 propagateIfChanged(result, result->markUnknown());
164 }
165 };
166 if (!symbol)
167 return markAllResultsUnknown();
168
169 if (auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
170 call, symbol.getLeafReference())) {
171 if (auto returnOriginsAttr = callee->getAttrOfType<ArrayAttr>(
172 EnzymeDialect::getSparseActivityAnnotationAttrName())) {
173 SmallVector<ValueOriginSet> returnOrigins;
174 deserializeReturnOrigins(returnOriginsAttr, returnOrigins);
175 return processCallToSummarizedFunc(call, returnOrigins, operands,
176 results);
177 }
178 }
179
180 // In the absence of a summary attribute, assume all results differentially
181 // depend on all operands
182 for (ForwardOriginsLattice *result : results)
183 for (const ForwardOriginsLattice *operand : operands)
184 join(result, *operand);
185}
186
187/// Visit everything transitively pointed-to by any pointer in start.
189 const enzyme::PointsToSets &pointsToSets,
190 function_ref<void(DistinctAttr)> visit) {
192 // The points-to graph is a graph, not a tree: without remembering what has
193 // been visited, a cycle spins this loop forever and converging chains
194 // revisit their shared tails once per path.
195 DenseSet<DistinctAttr> visited;
196 AliasClassSet current(start);
197 while (!current.isUndefined()) {
198 AliasClassSet next;
199
200 assert(!current.isUnknown() && "Unhandled traversal of unknown");
201 for (DistinctAttr currentClass : current.getElements()) {
202 if (!visited.insert(currentClass).second)
203 continue;
204 visit(currentClass);
205 (void)next.join(pointsToSets.getPointsTo(currentClass));
206 }
207 if (next.isUndefined())
208 break;
209 std::swap(current, next);
210 }
211}
212
213void enzyme::ForwardActivityAnnotationAnalysis::processCallToSummarizedFunc(
214 CallOpInterface call, ArrayRef<ValueOriginSet> summary,
215 ArrayRef<const ForwardOriginsLattice *> operands,
216 ArrayRef<ForwardOriginsLattice *> results) {
217 for (const auto &[result, returnOrigin] : llvm::zip(results, summary)) {
218 // Convert the origins relative to the callee to relative to the caller
219 ValueOriginSet callerOrigins;
220 if (returnOrigin.isUndefined())
221 continue;
222
223 if (returnOrigin.isUnknown()) {
224 (void)callerOrigins.markUnknown();
225 } else {
226 ProgramPoint *point = getProgramPointAfter(call);
227 auto *denseOrigins = getOrCreateFor<ForwardOriginsMap>(point, point);
228 auto *pointsTo = getOrCreateFor<PointsToSets>(point, point);
229 (void)returnOrigin.foreachElement(
230 [&](OriginAttr calleeOrigin, ValueOriginSet::State state) {
231 assert(state == ValueOriginSet::State::Defined &&
232 "undefined and unknown must have been handled above");
233 auto calleeArgOrigin = cast<ArgumentOriginAttr>(calleeOrigin);
234 // If the caller is a pointer, need to join what it points to
235 const ForwardOriginsLattice *operandOrigins =
236 operands[calleeArgOrigin.getArgNumber()];
237 auto *callerAliasClass = getOrCreateFor<AliasClassLattice>(
238 getProgramPointAfter(call), operandOrigins->getAnchor());
239 traversePointsToSets(callerAliasClass->getAliasClassesObject(),
240 *pointsTo, [&](DistinctAttr aliasClass) {
241 (void)callerOrigins.join(
242 denseOrigins->getOrigins(aliasClass));
243 });
244 return callerOrigins.join(operandOrigins->getOriginsObject());
245 });
246 }
247 propagateIfChanged(result, result->merge(callerOrigins));
248 }
249}
250
253
254void enzyme::BackwardActivityAnnotationAnalysis::markOperandsUnknown(
255 ArrayRef<BackwardOriginsLattice *> operands) {
256 for (BackwardOriginsLattice *operand : operands) {
257 propagateIfChanged(operand, operand->markUnknown());
258 }
259}
260
262 Operation *op, ArrayRef<BackwardOriginsLattice *> operands,
263 ArrayRef<const BackwardOriginsLattice *> results) {
264 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
265 if (iface && iface.isInactive())
266 return success();
267
268 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
269 if (!memory) {
270 if (!isMemoryEffectFree(op))
271 markOperandsUnknown(operands);
272 return success();
273 }
274
275 SmallVector<MemoryEffects::EffectInstance> effects;
276 memory.getEffects(effects);
277 for (const auto &effect : effects) {
278 if (!isa<MemoryEffects::Read>(effect.getEffect()))
279 continue;
280
281 Value value = effect.getValue();
282 if (!value) {
283 markOperandsUnknown(operands);
284 continue;
285 }
286
287 auto *srcClasses =
288 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), value);
289 auto *originsMap =
290 getOrCreate<BackwardOriginsMap>(getProgramPointBefore(op));
291
292 ChangeResult changed = ChangeResult::NoChange;
293 for (const BackwardOriginsLattice *result : results)
294 changed |= originsMap->insert(srcClasses->getAliasClassesObject(),
295 result->getOriginsObject());
296 propagateIfChanged(originsMap, changed);
297 }
298
299 // Propagate sparse differential dependencies, excluding any inactive
300 // arguments.
301 for (auto &&[i, operand] : llvm::enumerate(operands)) {
302 if (iface && iface.isArgInactive(i))
303 continue;
304
305 for (const BackwardOriginsLattice *result : results)
306 meet(operand, *result);
307 }
308 return success();
309}
310
312 CallOpInterface call, ArrayRef<BackwardOriginsLattice *> operands,
313 ArrayRef<const BackwardOriginsLattice *> results) {
314 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
315 if (!symbol)
316 return markOperandsUnknown(operands);
317
318 if (auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
319 call, symbol.getLeafReference())) {
320 if (auto returnOriginsAttr = callee->getAttrOfType<ArrayAttr>(
321 EnzymeDialect::getSparseActivityAnnotationAttrName())) {
322 SmallVector<ValueOriginSet> returnOrigins;
323 deserializeReturnOrigins(returnOriginsAttr, returnOrigins);
324 return processCallToSummarizedFunc(call, returnOrigins, operands,
325 results);
326 }
327 }
328
329 // In the absence of a summary attribute, assume all results differentially
330 // depend on all operands
331 for (BackwardOriginsLattice *operand : operands)
332 for (const BackwardOriginsLattice *result : results)
333 meet(operand, *result);
334}
335
336void enzyme::BackwardActivityAnnotationAnalysis::processCallToSummarizedFunc(
337 CallOpInterface call, ArrayRef<ValueOriginSet> summary,
338 ArrayRef<BackwardOriginsLattice *> operands,
339 ArrayRef<const BackwardOriginsLattice *> results) {
340 // collect the result origins, propagate them to the operands.
341 for (const auto &[result, calleeOrigins] : llvm::zip(results, summary)) {
342 ValueOriginSet resultOrigins = result->getOriginsObject();
343 if (calleeOrigins.isUndefined())
344 continue;
345 if (calleeOrigins.isUnknown())
346 (void)resultOrigins.markUnknown();
347 else {
348 (void)calleeOrigins.foreachElement(
349 [&](OriginAttr calleeOrigin, ValueOriginSet::State state) {
350 auto calleeArgOrigin = cast<ArgumentOriginAttr>(calleeOrigin);
351 BackwardOriginsLattice *operand =
352 operands[calleeArgOrigin.getArgNumber()];
353 propagateIfChanged(operand, operand->merge(resultOrigins));
354 return ChangeResult::NoChange;
355 });
356 }
357 }
358}
359
360template <typename KeyT, typename ElementT>
362 const DenseMap<KeyT, enzyme::SetLattice<ElementT>> map, raw_ostream &os) {
363 if (map.empty()) {
364 os << "<empty>\n";
365 return;
366 }
367 for (const auto &[aliasClass, origins] : map) {
368 os << " " << aliasClass << " originates from {";
369 if (origins.isUnknown()) {
370 os << "<unknown>";
371 } else if (origins.isUndefined()) {
372 os << "<undefined>";
373 } else {
374 llvm::interleaveComma(origins.getElements(), os);
375 }
376 os << "}\n";
377 }
378}
379
380void enzyme::ForwardOriginsMap::print(raw_ostream &os) const {
381 printMapOfSetsLattice(this->map, os);
382}
383
384void enzyme::BackwardOriginsMap::print(raw_ostream &os) const {
385 printMapOfSetsLattice(this->map, os);
386}
387
389 ForwardOriginsMap *lattice) {
390 auto point = dyn_cast<ProgramPoint *>(lattice->getAnchor());
391 auto *block = point->getBlock();
392 if (!(block && isa<FunctionOpInterface>(block->getParentOp())))
393 return;
394
395 auto funcOp = cast<FunctionOpInterface>(block->getParentOp());
396 ChangeResult changed = ChangeResult::NoChange;
397 for (BlockArgument arg : funcOp.getArguments()) {
398 auto *argClass = getOrCreateFor<AliasClassLattice>(point, arg);
399 auto origin = ArgumentOriginAttr::get(FlatSymbolRefAttr::get(funcOp),
400 arg.getArgNumber());
401 changed |= lattice->insert(argClass->getAliasClassesObject(),
402 ValueOriginSet(origin));
403 }
404 propagateIfChanged(lattice, changed);
405}
406
407std::optional<Value> getStored(Operation *op);
408std::optional<Value> getCopySource(Operation *op);
409
411 Operation *op, const ForwardOriginsMap &before, ForwardOriginsMap *after) {
412 join(after, before);
413
414 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
415 if (iface && iface.isInactive())
416 return success();
417
418 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
419 if (!memory) {
420 if (!isMemoryEffectFree(op))
421 propagateIfChanged(after, after->markAllOriginsUnknown());
422 return success();
423 }
424
425 SmallVector<MemoryEffects::EffectInstance> effects;
426 memory.getEffects(effects);
427 for (const auto &effect : effects) {
428 Value value = effect.getValue();
429 if (!value) {
430 propagateIfChanged(after, after->markAllOriginsUnknown());
431 return success();
432 }
433
434 if (isa<MemoryEffects::Read>(effect.getEffect())) {
435 // TODO: Really need that memory interface
436 if (op->getNumResults() != 1)
437 continue;
438 Value readDest = op->getResult(0);
439
440 auto *destClasses =
441 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), readDest);
442 if (destClasses->isUndefined())
443 // Not a pointer, so the sparse analysis will handle this.
444 continue;
445
446 auto *srcClasses =
447 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), value);
448 if (srcClasses->isUnknown()) {
449 propagateIfChanged(after,
450 after->insert(destClasses->getAliasClassesObject(),
452 continue;
453 }
454
455 ChangeResult changed = ChangeResult::NoChange;
456 for (DistinctAttr srcClass : srcClasses->getAliasClasses()) {
457 changed |= after->insert(destClasses->getAliasClassesObject(),
458 before.getOrigins(srcClass));
459 }
460 propagateIfChanged(after, changed);
461 } else if (isa<MemoryEffects::Write>(effect.getEffect())) {
462 if (std::optional<Value> stored = getStored(op)) {
463 if (!isPossiblyActive(stored->getType())) {
464 continue;
465 }
466 auto *origins = getOrCreateFor<ForwardOriginsLattice>(
467 getProgramPointAfter(op), *stored);
468 auto *dest =
469 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), value);
470 propagateIfChanged(after, after->insert(dest->getAliasClassesObject(),
471 origins->getOriginsObject()));
472 } else if (std::optional<Value> copySource = getCopySource(op)) {
473 processCopy(op, *copySource, value, before, after);
474 } else {
475 propagateIfChanged(after, after->markAllOriginsUnknown());
476 }
477 }
478 }
479 return success();
480}
481
482void enzyme::DenseActivityAnnotationAnalysis::processCopy(
483 Operation *op, Value copySource, Value copyDest,
484 const ForwardOriginsMap &before, ForwardOriginsMap *after) {
485 auto *src =
486 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), copySource);
487 ValueOriginSet srcOrigins;
488 if (src->isUndefined())
489 return;
490 if (src->isUnknown())
491 (void)srcOrigins.markUnknown();
492
493 for (DistinctAttr srcClass : src->getAliasClasses())
494 (void)srcOrigins.join(before.getOrigins(srcClass));
495
496 auto *dest =
497 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), copyDest);
498 propagateIfChanged(after,
499 after->insert(dest->getAliasClassesObject(), srcOrigins));
500}
501
502// TODO: rename from pointsto
504 ArrayAttr summaryAttr,
505 DenseMap<DistinctAttr, enzyme::ValueOriginSet> &summaryMap) {
506 // TODO: investigate better encodings for the value origin summary
507 for (auto pair : summaryAttr.getAsRange<ArrayAttr>()) {
508 assert(pair.size() == 2 &&
509 "Expected summary to be in [[key, value]] format");
510 auto pointer = cast<DistinctAttr>(pair[0]);
511 auto pointsToSet = enzyme::ValueOriginSet::getUndefined();
512 if (auto strAttr = dyn_cast<StringAttr>(pair[1])) {
513 if (strAttr.getValue() == enzyme::unknownSetString) {
514 (void)pointsToSet.markUnknown();
515 } else {
516 assert(strAttr.getValue() == enzyme::undefinedSetString &&
517 "unrecognized points-to destination");
518 }
519 } else {
520 auto pointsTo = cast<ArrayAttr>(pair[1]).getAsRange<enzyme::OriginAttr>();
521 (void)pointsToSet.insert(
522 DenseSet<enzyme::OriginAttr>(pointsTo.begin(), pointsTo.end()));
523 }
524
525 summaryMap.insert({pointer, pointsToSet});
526 }
527}
528
530 CallOpInterface call, dataflow::CallControlFlowAction action,
531 const ForwardOriginsMap &before, ForwardOriginsMap *after) {
532 join(after, before);
533 if (action == dataflow::CallControlFlowAction::ExternalCallee) {
534 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
535 if (!symbol)
536 return propagateIfChanged(after, after->markAllOriginsUnknown());
537
538 if (auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
539 call, symbol.getLeafReference())) {
540 if (auto summaryAttr = callee->getAttrOfType<ArrayAttr>(
541 EnzymeDialect::getDenseActivityAnnotationAttrName())) {
542 DenseMap<DistinctAttr, ValueOriginSet> summary;
543 deserializePointsTo(summaryAttr, summary);
544 return processCallToSummarizedFunc(call, summary, before, after);
545 }
546 }
547 }
548}
549
550void enzyme::DenseActivityAnnotationAnalysis::processCallToSummarizedFunc(
551 CallOpInterface call, const DenseMap<DistinctAttr, ValueOriginSet> &summary,
552 const ForwardOriginsMap &before, ForwardOriginsMap *after) {
553 ChangeResult changed = ChangeResult::NoChange;
554 ProgramPoint *point = getProgramPointAfter(call);
555 // Unify the value origin summary with the actual lattices of function
556 // arguments
557 // Collect the origins of the function arguments, then collect the alias
558 // classes of the destinations
559 auto *p2sets = getOrCreateFor<PointsToSets>(point, point);
560 SmallVector<ValueOriginSet> argumentOrigins;
561 SmallVector<AliasClassSet> argumentClasses;
562 for (auto &&[i, argOperand] : llvm::enumerate(call.getArgOperands())) {
563 // Value origin might be sparse, might be dense
564 ValueOriginSet argOrigins;
565 auto *argClasses = getOrCreateFor<AliasClassLattice>(point, argOperand);
566 if (argClasses->isUndefined()) {
567 // Not a pointer, use the sparse lattice state
568 auto *sparseOrigins =
569 getOrCreateFor<ForwardOriginsLattice>(point, argOperand);
570 (void)argOrigins.join(sparseOrigins->getOriginsObject());
571 } else {
572 // Unify all the origins
573 // Since we're not keeping track of argument depth, we need to union the
574 // arg origins with everything it points to.
575 traversePointsToSets(argClasses->getAliasClassesObject(), *p2sets,
576 [&](DistinctAttr aliasClass) {
577 (void)argOrigins.join(
578 before.getOrigins(aliasClass));
579 });
580 }
581 argumentClasses.push_back(argClasses->getAliasClassesObject());
582 argumentOrigins.push_back(argOrigins);
583 }
584
585 for (const auto &[destClass, sourceOrigins] : summary) {
586 ValueOriginSet callerOrigins;
587 // A callee that could not say where this memory came from says nothing
588 // narrower to the caller either; only a set that names its origins has
589 // arguments to translate.
590 if (sourceOrigins.isUndefined())
591 continue;
592 if (sourceOrigins.isUnknown()) {
593 (void)callerOrigins.markUnknown();
594 } else {
595 for (Attribute sourceOrigin : sourceOrigins.getElements()) {
596 unsigned argNumber =
597 cast<ArgumentOriginAttr>(sourceOrigin).getArgNumber();
598 (void)callerOrigins.join(argumentOrigins[argNumber]);
599 }
600 }
601
602 AliasClassSet callerDestClasses;
603 if (auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
604 destClass.getReferencedAttr())) {
605 // Traverse the points-to sets.
606 AliasClassSet current = argumentClasses[pseudoClass.getArgNumber()];
607 unsigned depth = pseudoClass.getDepth();
608 while (depth > 0) {
609 AliasClassSet next;
610 if (current.isUndefined()) {
611 // Activity annotations requires converged pointer info. If the alias
612 // class is undefined, this could be because (1) the points-to info
613 // hasn't _yet_ been computed (in which case we bail out here
614 // expecting to be called again with more complete info), or (2) if
615 // the points-to info has converged, this signifies reading from
616 // uninitialized memory. This is UB, so we assume it never happens.
617 return;
618 }
619 for (DistinctAttr currentClass : current.getElements())
620 (void)next.join(p2sets->getPointsTo(currentClass));
621 std::swap(current, next);
622 depth--;
623 }
624
625 (void)callerDestClasses.join(current);
626 } else {
627 (void)callerDestClasses.insert({destClass});
628 }
629 changed |= after->insert(callerDestClasses, callerOrigins);
630 }
631 propagateIfChanged(after, changed);
632}
633
635 visitCallControlFlowTransfer(CallOpInterface call,
636 dataflow::CallControlFlowAction action,
637 const BackwardOriginsMap &after,
638 BackwardOriginsMap *before) {
639 meet(before, after);
640 if (action == dataflow::CallControlFlowAction::ExternalCallee) {
641 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
642 if (!symbol)
643 return propagateIfChanged(before, before->markAllOriginsUnknown());
644
645 if (auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
646 call, symbol.getLeafReference())) {
647 if (auto summaryAttr = callee->getAttrOfType<ArrayAttr>(
648 EnzymeDialect::getDenseActivityAnnotationAttrName())) {
649 DenseMap<DistinctAttr, ValueOriginSet> summary;
650 deserializePointsTo(summaryAttr, summary);
651 return processCallToSummarizedFunc(call, summary, after, before);
652 }
653 }
654 }
655}
656
658 BackwardOriginsMap *lattice) {
659 auto point = dyn_cast<ProgramPoint *>(lattice->getAnchor());
660 auto *block = point->getBlock();
661 if (!block)
662 return;
663
664 auto funcOp = cast<FunctionOpInterface>(block->getParentOp());
665 ChangeResult changed = ChangeResult::NoChange;
666 for (BlockArgument arg : funcOp.getArguments()) {
667 auto *pointsToSets = getOrCreateFor<PointsToSets>(
668 point, getProgramPointAfter(block->getTerminator()));
669 auto *argClass = getOrCreateFor<AliasClassLattice>(point, arg);
670 auto origin = ArgumentOriginAttr::get(FlatSymbolRefAttr::get(funcOp),
671 arg.getArgNumber());
672
673 // Everything that a pointer argument may point to originates from that
674 // pointer argument.
675 traversePointsToSets(argClass->getAliasClassesObject(), *pointsToSets,
676 [&](DistinctAttr currentClass) {
677 changed |=
678 lattice->insert(AliasClassSet(currentClass),
679 ValueOriginSet(origin));
680 });
681 }
682 propagateIfChanged(lattice, changed);
683}
684
686 Operation *op, const BackwardOriginsMap &after,
687 BackwardOriginsMap *before) {
688 meet(before, after);
689
690 auto activityIface = dyn_cast<enzyme::ActivityOpInterface>(op);
691 if (activityIface && activityIface.isInactive())
692 return success();
693
694 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
695 if (!memory) {
696 if (!isMemoryEffectFree(op))
697 propagateIfChanged(before, before->markAllOriginsUnknown());
698 return success();
699 }
700
701 SmallVector<MemoryEffects::EffectInstance> effects;
702 memory.getEffects(effects);
703 for (const auto &effect : effects) {
704 if (!isa<MemoryEffects::Write>(effect.getEffect()))
705 continue;
706
707 Value value = effect.getValue();
708 if (!value) {
709 propagateIfChanged(before, before->markAllOriginsUnknown());
710 return success();
711 }
712
713 if (std::optional<Value> stored = getStored(op)) {
714 ProgramPoint *point = getProgramPointBefore(op);
715 auto *addressClasses = getOrCreateFor<AliasClassLattice>(point, value);
716 auto *storedClasses = getOrCreateFor<AliasClassLattice>(point, *stored);
717
718 if (storedClasses->isUndefined()) {
719 // Not a pointer being stored, do a sparse update
720 auto *storedOrigins = getOrCreate<BackwardOriginsLattice>(*stored);
721 propagateIfChanged(
722 storedOrigins,
723 addressClasses->getAliasClassesObject().foreachElement(
724 [&](DistinctAttr alloc, AliasClassSet::State state) {
725 if (state == AliasClassSet::State::Undefined) {
726 return ChangeResult::NoChange;
727 }
728 if (state == AliasClassSet::State::Unknown) {
729 return storedOrigins->markUnknown();
730 }
731 return storedOrigins->merge(after.getOrigins(alloc));
732 }));
733 } else if (storedClasses->isUnknown()) {
734 propagateIfChanged(before, before->markAllOriginsUnknown());
735 } else {
736 // Capturing stores are handled via the points-to relationship in
737 // setToExitState.
738 }
739 } else if (std::optional<Value> copySource = getCopySource(op)) {
740 processCopy(op, *copySource, value, after, before);
741 }
742 }
743 return success();
744}
745
746void enzyme::DenseBackwardActivityAnnotationAnalysis::
747 processCallToSummarizedFunc(
748 CallOpInterface call,
749 const DenseMap<DistinctAttr, ValueOriginSet> &summary,
750 const BackwardOriginsMap &after, BackwardOriginsMap *before) {
751 ChangeResult changed = ChangeResult::NoChange;
752 ProgramPoint *pointBefore = getProgramPointBefore(call);
753 ProgramPoint *pointAfter = getProgramPointAfter(call);
754 // Unify the value origin summary with the actual lattices of function
755 // arguments
756 auto *p2sets = getOrCreateFor<PointsToSets>(pointBefore, pointAfter);
757 SmallVector<AliasClassSet> argumentClasses;
758 for (Value argOperand : call.getArgOperands()) {
759 auto *argClasses =
760 getOrCreateFor<AliasClassLattice>(pointBefore, argOperand);
761 argumentClasses.push_back(argClasses->getAliasClassesObject());
762 }
763
764 for (const auto &[destClass, sourceOrigins] : summary) {
765 // Get the destination origins
766 ValueOriginSet destOrigins;
767 if (auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
768 destClass.getReferencedAttr())) {
769 traversePointsToSets(argumentClasses[pseudoClass.getArgNumber()], *p2sets,
770 [&](DistinctAttr aliasClass) {
771 (void)destOrigins.join(
772 after.getOrigins(aliasClass));
773 });
774 }
775
776 if (destOrigins.isUndefined())
777 continue;
778
779 // A callee that could not say which of its arguments this memory flows back
780 // to leaves every one of the caller's classes a candidate.
781 if (sourceOrigins.isUndefined())
782 continue;
783 if (sourceOrigins.isUnknown()) {
784 changed |= before->markAllOriginsUnknown();
785 continue;
786 }
787
788 // Get the source alias classes
789 AliasClassSet callerSourceClasses;
790 for (Attribute sourceOrigin : sourceOrigins.getElements()) {
791 unsigned argNumber =
792 cast<ArgumentOriginAttr>(sourceOrigin).getArgNumber();
793
794 if (argumentClasses[argNumber].isUndefined()) {
795 // Not a pointer, do a sparse update
796 auto *backwardLattice = getOrCreate<BackwardOriginsLattice>(
797 call.getArgOperands()[argNumber]);
798 if (destOrigins.isUnknown()) {
799 propagateIfChanged(backwardLattice, backwardLattice->markUnknown());
800 continue;
801 }
802 propagateIfChanged(backwardLattice,
803 backwardLattice->insert(destOrigins.getElements()));
804 } else {
805 traversePointsToSets(argumentClasses[argNumber], *p2sets,
806 [&](DistinctAttr aliasClass) {
807 (void)callerSourceClasses.insert({aliasClass});
808 });
809 }
810 }
811 changed |= before->insert(callerSourceClasses, destOrigins);
812 }
813 propagateIfChanged(before, changed);
814}
815
816void enzyme::DenseBackwardActivityAnnotationAnalysis::processCopy(
817 Operation *op, Value copySource, Value copyDest,
818 const BackwardOriginsMap &after, BackwardOriginsMap *before) {
819 ProgramPoint *point = getProgramPointBefore(op);
820 auto *dest = getOrCreateFor<AliasClassLattice>(point, copyDest);
821 ValueOriginSet destOrigins;
822 if (dest->isUndefined())
823 return;
824 if (dest->isUnknown())
825 (void)destOrigins.markUnknown();
826
827 for (DistinctAttr destClass : dest->getAliasClasses())
828 (void)destOrigins.join(after.getOrigins(destClass));
829
830 auto *src = getOrCreateFor<AliasClassLattice>(point, copySource);
831 propagateIfChanged(before,
832 before->insert(src->getAliasClassesObject(), destOrigins));
833}
834
835namespace {
836
837// TODO: the alias summary attribute is sufficent to get the correct behaviour
838// here, but it would be nice if these were not hardcoded.
839void annotateHardcoded(FunctionOpInterface func) {
840 if (func.getName() == "lgamma" || func.getName() == "tanh") {
841 MLIRContext *ctx = func.getContext();
842 SmallVector<Attribute> arr = {StringAttr::get(ctx, "<undefined>")};
843 func->setAttr(enzyme::EnzymeDialect::getAliasSummaryAttrName(),
844 ArrayAttr::get(ctx, arr));
845 }
846}
847
848void initializeSparseBackwardActivityAnnotations(FunctionOpInterface func,
849 DataFlowSolver &solver) {
850 using namespace mlir::enzyme;
851
852 for (Operation &op : func.getCallableRegion()->getOps()) {
854 continue;
855
856 for (OpOperand &returnOperand : op.getOpOperands()) {
857 auto *lattice =
858 solver.getOrCreateState<BackwardOriginsLattice>(returnOperand.get());
859 auto origin = ReturnOriginAttr::get(FlatSymbolRefAttr::get(func),
860 returnOperand.getOperandNumber());
861 (void)lattice->insert({origin});
862 }
863 }
864}
865
866using OriginsPair =
867 std::pair<enzyme::ForwardOriginsLattice, enzyme::BackwardOriginsLattice>;
868
869/// Once having reached a top-level entry point, go top-down and convert the
870/// relative sources/sinks into concrete active/constant results.
871///
872/// This would ideally be done after lowering to LLVM and during differentiation
873/// because it loses context sensitivity, but this is faster to prototype with.
874void topDownActivityAnalysis(
875 FunctionOpInterface callee, ArrayRef<enzyme::Activity> argActivities,
876 ArrayRef<enzyme::Activity> retActivities,
877 DenseMap<BlockArgument, OriginsPair> &blockArgOrigins) {
878 using namespace mlir::enzyme;
879 MLIRContext *ctx = callee.getContext();
880 callee->setAttr("enzyme.visited", UnitAttr::get(ctx));
881 auto trueAttr = BoolAttr::get(ctx, true);
882 auto falseAttr = BoolAttr::get(ctx, false);
883
884 auto isOriginActive = [&](OriginAttr origin) {
885 if (auto argOriginAttr = dyn_cast<ArgumentOriginAttr>(origin)) {
886 return llvm::is_contained({Activity::enzyme_dup,
887 Activity::enzyme_dupnoneed,
888 Activity::enzyme_active},
889 argActivities[argOriginAttr.getArgNumber()]);
890 }
891 auto retOriginAttr = cast<ReturnOriginAttr>(origin);
892 return llvm::is_contained({Activity::enzyme_dup, Activity::enzyme_dupnoneed,
893 Activity::enzyme_active},
894 retActivities[retOriginAttr.getReturnNumber()]);
895 };
896 callee.getFunctionBody().walk([&](Operation *op) {
897 if (op->getNumResults() == 0) {
898 // Operations that don't return values are definitionally "constant"
899 op->setAttr("enzyme.icv", trueAttr);
900 } else {
901 // Value activity
902 if (op->hasAttr("enzyme.constantval")) {
903 op->setAttr("enzyme.icv", trueAttr);
904 } else if (op->hasAttr("enzyme.activeval")) {
905 op->setAttr("enzyme.icv", falseAttr);
906 } else {
907 auto valueSource = op->getAttrOfType<ArrayAttr>("enzyme.valsrc");
908 auto valueSink = op->getAttrOfType<ArrayAttr>("enzyme.valsink");
909 if (!(valueSource && valueSink)) {
910 llvm::errs() << "[activity] missing attributes for op: " << *op
911 << "\n";
912 }
913 assert(valueSource && valueSink && "missing attributes for op");
914 bool activeSource =
915 llvm::any_of(valueSource.getAsRange<OriginAttr>(), isOriginActive);
916 bool activeSink =
917 llvm::any_of(valueSink.getAsRange<OriginAttr>(), isOriginActive);
918 bool activeVal = activeSource && activeSink;
919 op->setAttr("enzyme.icv", BoolAttr::get(ctx, !activeVal));
920 }
921 }
922 op->removeAttr("enzyme.constantval");
923 op->removeAttr("enzyme.activeval");
924 op->removeAttr("enzyme.valsrc");
925 op->removeAttr("enzyme.valsink");
926
927 // Instruction activity
928 if (op->hasAttr("enzyme.constantop")) {
929 op->setAttr("enzyme.ici", trueAttr);
930 } else if (op->hasAttr("enzyme.activeop")) {
931 op->setAttr("enzyme.ici", falseAttr);
932 } else {
933 bool activeSource = llvm::any_of(
934 op->getAttrOfType<ArrayAttr>("enzyme.opsrc").getAsRange<OriginAttr>(),
935 isOriginActive);
936 bool activeSink =
937 llvm::any_of(op->getAttrOfType<ArrayAttr>("enzyme.opsink")
938 .getAsRange<OriginAttr>(),
939 isOriginActive);
940 bool activeOp = activeSource && activeSink;
941 op->setAttr("enzyme.ici", BoolAttr::get(ctx, !activeOp));
942 }
943
944 op->removeAttr("enzyme.constantop");
945 op->removeAttr("enzyme.activeop");
946 op->removeAttr("enzyme.opsrc");
947 op->removeAttr("enzyme.opsink");
948
949 if (auto callOp = dyn_cast<CallOpInterface>(op)) {
950 auto funcOp = cast<FunctionOpInterface>(callOp.resolveCallable());
951 if (!funcOp->hasAttr("enzyme.visited")) {
952 SmallVector<Activity> callArgActivities, callResActivities;
953 for (Value operand : callOp.getArgOperands()) {
954 if (auto *definingOp = operand.getDefiningOp()) {
955 bool icv =
956 definingOp->getAttrOfType<BoolAttr>("enzyme.icv").getValue();
957 callArgActivities.push_back(icv ? Activity::enzyme_const
958 : Activity::enzyme_active);
959 } else {
960 BlockArgument blockArg = cast<BlockArgument>(operand);
961 const OriginsPair &originsPair = blockArgOrigins.at(blockArg);
962 const ForwardOriginsLattice &sources = originsPair.first;
963 const BackwardOriginsLattice &sinks = originsPair.second;
964 bool argActive = false;
965 if (sources.isUnknown() || sinks.isUnknown()) {
966 argActive = true;
967 } else if (sources.isUndefined() || sinks.isUndefined()) {
968 argActive = false;
969 } else {
970 argActive = llvm::any_of(sources.getOrigins(), isOriginActive) &&
971 llvm::any_of(sinks.getOrigins(), isOriginActive);
972 }
973 callArgActivities.push_back(argActive ? Activity::enzyme_active
974 : Activity::enzyme_const);
975 }
976 }
977 if (op->getNumResults() != 0) {
978 bool icv = op->getAttrOfType<BoolAttr>("enzyme.icv").getValue();
979 callResActivities.push_back(icv ? Activity::enzyme_const
980 : Activity::enzyme_active);
981 }
982
983 topDownActivityAnalysis(funcOp, callArgActivities, callResActivities,
984 blockArgOrigins);
985 }
986 }
987 });
988}
989
990// Convenience for making debugging less verbose
991std::string format(Attribute attribute) {
992 return llvm::TypeSwitch<Attribute, std::string>(attribute)
993 .Case([](DistinctAttr attr) { return format(attr.getReferencedAttr()); })
994 .Case([](ArrayAttr attr) {
995 std::string res;
996 llvm::raw_string_ostream ss(res);
997 ss << "[";
998 for (unsigned i = 0; i < attr.size(); ++i) {
999 ss << format(attr[i]);
1000 if (i < attr.size() - 1)
1001 ss << ", ";
1002 }
1003 ss << "]";
1004 return res;
1005 })
1006 .Case([](enzyme::PseudoAliasClassAttr attr) {
1007 return ("pclass<(" + Twine(attr.getArgNumber()) + ", " +
1008 Twine(attr.getDepth()) + ")>")
1009 .str();
1010 })
1011 .Case([](enzyme::ArgumentOriginAttr attr) {
1012 return ("arg" + Twine(attr.getArgNumber())).str();
1013 })
1014 .Case([](enzyme::ReturnOriginAttr attr) {
1015 return ("ret" + Twine(attr.getReturnNumber())).str();
1016 })
1017 .Default([](Attribute attr) {
1018 std::string res;
1019 llvm::raw_string_ostream ss(res);
1020 ss << attr;
1021 return res;
1022 });
1023}
1024} // namespace
1025
1026/// Starting from callee, compute a reverse (bottom-up) topological sorting of
1027/// all functions transitively called from callee.
1028LogicalResult
1029enzyme::reverseToposortCallgraph(CallableOpInterface callee,
1030 SymbolTableCollection *symbolTable,
1031 SmallVectorImpl<CallableOpInterface> &sorted) {
1032 DenseSet<CallableOpInterface> permanent;
1033 DenseSet<CallableOpInterface> temporary;
1034 std::function<LogicalResult(CallableOpInterface)> visit =
1035 [&](CallableOpInterface node) {
1036 if (permanent.contains(node))
1037 return success();
1038 if (temporary.contains(node))
1039 // Cycle in call graph, toposort not possible
1040 return failure();
1041
1042 temporary.insert(node);
1043 auto walkResult = node.walk([&](CallOpInterface call) {
1044 auto neighbour = cast<CallableOpInterface>(
1045 call.resolveCallableInTable(symbolTable));
1046 if (failed(visit(neighbour)))
1047 return WalkResult::interrupt();
1048 return WalkResult::advance();
1049 });
1050 if (walkResult.wasInterrupted())
1051 return failure();
1052
1053 temporary.erase(node);
1054 permanent.insert(node);
1055 sorted.push_back(node);
1056 return success();
1057 };
1058
1059 return visit(callee);
1060}
1061
1063 FunctionOpInterface funcOp, DataFlowSolver &solver,
1064 enzyme::PointsToSets &p2sets, enzyme::ForwardOriginsMap &forwardOriginsMap,
1065 enzyme::BackwardOriginsMap &backwardOriginsMap,
1066 SmallVectorImpl<enzyme::ForwardOriginsLattice> &returnOperandOrigins,
1067 SmallVectorImpl<enzyme::AliasClassLattice> &returnAliasClasses) {
1068 assert(returnOperandOrigins.size() == funcOp.getNumResults());
1069 assert(returnAliasClasses.size() == funcOp.getNumResults());
1070
1071 // Create the overall summary by joining sets at all return sites.
1072 for (Operation &op : funcOp.getCallableRegion()->getOps()) {
1074 ProgramPoint *point = solver.getProgramPointAfter(&op);
1075 (void)p2sets.join(*solver.lookupState<enzyme::PointsToSets>(point));
1076 auto *returnOrigins =
1077 solver.lookupState<enzyme::ForwardOriginsMap>(point);
1078 if (returnOrigins)
1079 (void)forwardOriginsMap.join(*returnOrigins);
1080
1081 for (OpOperand &operand : op.getOpOperands()) {
1082 (void)returnAliasClasses[operand.getOperandNumber()].join(
1083 *solver.lookupState<enzyme::AliasClassLattice>(operand.get()));
1084 (void)returnOperandOrigins[operand.getOperandNumber()].join(
1085 *solver.lookupState<enzyme::ForwardOriginsLattice>(operand.get()));
1086 }
1087 }
1088 }
1089
1090 (void)backwardOriginsMap.meet(
1091 *solver.getOrCreateState<enzyme::BackwardOriginsMap>(
1092 solver.getProgramPointBefore(
1093 &funcOp.getCallableRegion()->front().front())));
1094}
1095
1097 Operation *op, const enzyme::PointsToSets &p2sets,
1098 const enzyme::ForwardOriginsMap &forwardOriginsMap,
1099 ArrayRef<enzyme::ForwardOriginsLattice> returnOperandOrigins,
1100 ArrayRef<enzyme::AliasClassLattice> returnAliasClasses) {
1101 using namespace mlir::enzyme;
1102 MLIRContext *ctx = op->getContext();
1103 // Sparse alias annotations
1104 SmallVector<Attribute> aliasAttributes(returnAliasClasses.size());
1105 llvm::transform(returnAliasClasses, aliasAttributes.begin(),
1106 [&](enzyme::AliasClassLattice lattice) {
1107 return lattice.serialize(ctx);
1108 });
1109 op->setAttr(EnzymeDialect::getAliasSummaryAttrName(),
1110 ArrayAttr::get(ctx, aliasAttributes));
1111
1112 // Points-to-pointer annotations
1113 StringRef pointerSummaryName = EnzymeDialect::getPointerSummaryAttrName();
1114 op->setAttr(pointerSummaryName, p2sets.serialize(ctx));
1115
1116 op->setAttr(EnzymeDialect::getDenseActivityAnnotationAttrName(),
1117 forwardOriginsMap.serialize(ctx));
1118
1119 // Serialize return origins
1120 SmallVector<Attribute> serializedReturnOperandOrigins(
1121 returnOperandOrigins.size());
1122 llvm::transform(returnOperandOrigins, serializedReturnOperandOrigins.begin(),
1123 [ctx](enzyme::ForwardOriginsLattice lattice) -> Attribute {
1124 return lattice.serialize(ctx);
1125 });
1126 op->setAttr(EnzymeDialect::getSparseActivityAnnotationAttrName(),
1127 ArrayAttr::get(ctx, serializedReturnOperandOrigins));
1128}
1129
1130void enzyme::removeSummaries(Operation *op) {
1131 op->removeAttr(EnzymeDialect::getAliasSummaryAttrName());
1132 op->removeAttr(EnzymeDialect::getPointerSummaryAttrName());
1133 op->removeAttr(EnzymeDialect::getDenseActivityAnnotationAttrName());
1134 op->removeAttr(EnzymeDialect::getSparseActivityAnnotationAttrName());
1135}
1136
1137LogicalResult
1139 DataFlowSolver &solver) {
1140 using namespace mlir::enzyme;
1141
1142 SymbolTableCollection symbolTable;
1143 MLIRContext *ctx = funcOp.getContext();
1144 dataflow::loadBaselineAnalyses(solver);
1145 solver.load<enzyme::AliasAnalysis>(ctx,
1146 /*relative=*/true);
1147 solver.load<enzyme::PointsToPointerAnalysis>();
1150 solver.load<enzyme::BackwardActivityAnnotationAnalysis>(symbolTable);
1151 solver.load<enzyme::DenseBackwardActivityAnnotationAnalysis>(symbolTable);
1152
1153 initializeSparseBackwardActivityAnnotations(funcOp, solver);
1154 return solver.initializeAndRun(funcOp);
1155}
1156
1158 FunctionOpInterface callee, ArrayRef<enzyme::Activity> argActivities,
1159 const ActivityPrinterConfig &activityConfig) {
1160 SymbolTableCollection symbolTable;
1161 SmallVector<CallableOpInterface> sorted;
1162 (void)reverseToposortCallgraph(callee, &symbolTable, sorted);
1163 raw_ostream &os = llvm::outs();
1164
1165 // TODO: is there any way of serializing information in a block argument?
1166 DenseMap<BlockArgument, OriginsPair> blockArgOrigins;
1167
1168 StringRef pointerSummaryName = EnzymeDialect::getPointerSummaryAttrName();
1169 for (CallableOpInterface node : sorted) {
1170 annotateHardcoded(cast<FunctionOpInterface>(node.getOperation()));
1171
1172 if (!node.getCallableRegion() || node->hasAttr(pointerSummaryName))
1173 continue;
1174 auto funcOp = cast<FunctionOpInterface>(node.getOperation());
1175 if (activityConfig.verbose) {
1176 os << "[ata] processing function @" << funcOp.getName() << "\n";
1177 }
1178 DataFlowConfig dataFlowConfig;
1179 dataFlowConfig.setInterprocedural(false);
1180 DataFlowSolver solver(dataFlowConfig);
1181 if (failed(runActivityAnnotationsForFunction(funcOp, solver))) {
1182 assert(false && "dataflow solver failed");
1183 }
1184
1185 // Create the overall summary by joining sets at all return sites.
1186 enzyme::PointsToSets p2sets(nullptr);
1187 enzyme::ForwardOriginsMap forwardOriginsMap(nullptr);
1188 enzyme::BackwardOriginsMap backwardOriginsMap(nullptr);
1189 size_t numResults = funcOp.getResultTypes().size();
1190 SmallVector<enzyme::ForwardOriginsLattice> returnOperandOrigins(
1191 numResults, ForwardOriginsLattice(nullptr));
1192 SmallVector<enzyme::AliasClassLattice> returnAliasClasses(
1193 numResults, AliasClassLattice(nullptr));
1194 computeSummaries(funcOp, solver, p2sets, forwardOriginsMap,
1195 backwardOriginsMap, returnOperandOrigins,
1196 returnAliasClasses);
1197 serializeSummaries(funcOp, p2sets, forwardOriginsMap, returnOperandOrigins,
1198 returnAliasClasses);
1199 Attribute backwardOrigins = backwardOriginsMap.serialize(node.getContext());
1200
1201 if (activityConfig.verbose) {
1202 os << "[ata] p2p summary:\n";
1203 if (node->getAttrOfType<ArrayAttr>(pointerSummaryName).size() == 0) {
1204 os << " <empty>\n";
1205 }
1206 for (ArrayAttr pair : node->getAttrOfType<ArrayAttr>(pointerSummaryName)
1207 .getAsRange<ArrayAttr>()) {
1208 os << " " << format(pair[0]) << " -> " << format(pair[1]) << "\n";
1209 }
1210
1211 os << "[ata] forward value origins:\n";
1212 for (ArrayAttr pair :
1213 node->getAttrOfType<ArrayAttr>(
1214 EnzymeDialect::getDenseActivityAnnotationAttrName())
1215 .getAsRange<ArrayAttr>()) {
1216 os << " " << format(pair[0]) << " originates from "
1217 << format(pair[1]) << "\n";
1218 }
1219
1220 os << "[ata] backward value origins:\n";
1221 for (ArrayAttr pair :
1222 cast<ArrayAttr>(backwardOrigins).getAsRange<ArrayAttr>()) {
1223 os << " " << format(pair[0]) << " goes to " << format(pair[1])
1224 << "\n";
1225 }
1226
1227 os << "[ata] return origins: "
1228 << format(node->getAttr(
1229 EnzymeDialect::getSparseActivityAnnotationAttrName()))
1230 << "\n";
1231 }
1232
1233 auto joinActiveDataState =
1234 [&](Value value,
1235 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1236 auto *sources = solver.getOrCreateState<ForwardOriginsLattice>(value);
1237 auto *sinks = solver.getOrCreateState<BackwardOriginsLattice>(value);
1238 (void)out.first.join(*sources);
1239 (void)out.second.meet(*sinks);
1240 };
1241
1242 auto joinActivePointerState =
1243 [&](const AliasClassSet &aliasClasses,
1244 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1246 aliasClasses, p2sets, [&](DistinctAttr aliasClass) {
1247 (void)out.first.merge(forwardOriginsMap.getOrigins(aliasClass));
1248 (void)out.second.merge(
1249 backwardOriginsMap.getOrigins(aliasClass));
1250 });
1251 };
1252
1253 auto joinActiveValueState =
1254 [&](Value value,
1255 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1256 if (isa<LLVM::LLVMPointerType, MemRefType>(value.getType())) {
1257 auto *aliasClasses =
1258 solver.getOrCreateState<AliasClassLattice>(value);
1259 joinActivePointerState(aliasClasses->getAliasClassesObject(), out);
1260 } else {
1261 joinActiveDataState(value, out);
1262 }
1263 };
1264
1265 MLIRContext *ctx = node.getContext();
1266 auto annotateActivity = [&](Operation *op) {
1267 assert(op->getNumResults() < 2 && op->getNumRegions() == 0 &&
1268 "annotation only supports the LLVM dialect");
1269 auto unitAttr = UnitAttr::get(ctx);
1270 // Check activity of values
1271 for (OpResult result : op->getResults()) {
1272 std::pair<ForwardOriginsLattice, BackwardOriginsLattice>
1273 activityAttributes({result, ValueOriginSet()},
1274 {result, ValueOriginSet()});
1275 joinActiveValueState(result, activityAttributes);
1276 const auto &sources = activityAttributes.first;
1277 const auto &sinks = activityAttributes.second;
1278 // Possible states: if either source or sink is undefined or empty, the
1279 // value is always constant.
1280 if (sources.isUnknown() || sinks.isUnknown()) {
1281 // Always active
1282 op->setAttr("enzyme.activeval", unitAttr);
1283 } else if (sources.isUndefined() || sinks.isUndefined()) {
1284 // Always constant
1285 op->setAttr("enzyme.constantval", unitAttr);
1286 } else {
1287 // Conditionally active depending on the activity of sources and sinks
1288 op->setAttr("enzyme.valsrc", sources.serialize(ctx));
1289 op->setAttr("enzyme.valsink", sinks.serialize(ctx));
1290 }
1291 }
1292 // Check activity of operation
1293 StringRef opSourceAttrName = "enzyme.opsrc";
1294 StringRef opSinkAttrName = "enzyme.opsink";
1295 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> opAttributes(
1296 {nullptr, ValueOriginSet()}, {nullptr, ValueOriginSet()});
1297 if (isPure(op)) {
1298 // A pure operation can only propagate data via its results
1299 for (OpResult result : op->getResults()) {
1300 joinActiveDataState(result, opAttributes);
1301 }
1302 } else {
1303 // We need a special case because stores of active pointers don't fit
1304 // the definition but are active instructions
1305 if (auto storeOp = dyn_cast<LLVM::StoreOp>(op)) {
1306 auto *storedClass =
1307 solver.getOrCreateState<AliasClassLattice>(storeOp.getValue());
1308 joinActivePointerState(storedClass->getAliasClassesObject(),
1309 opAttributes);
1310 } else if (auto callOp = dyn_cast<CallOpInterface>(op)) {
1311 // TODO: tricky, requires some thought
1312 auto callable = cast<CallableOpInterface>(callOp.resolveCallable());
1313 if (callable->hasAttr(
1314 EnzymeDialect::getDenseActivityAnnotationAttrName())) {
1315 for (Value operand : callOp.getArgOperands())
1316 joinActiveValueState(operand, opAttributes);
1317 }
1318 // We need to
1319 // determine if the body of the function contains active instructions
1320 }
1321
1322 // Default: the op is active iff any of its operands or results are
1323 // active data.
1324 for (Value operand : op->getOperands())
1325 joinActiveDataState(operand, opAttributes);
1326 for (OpResult result : op->getResults())
1327 joinActiveDataState(result, opAttributes);
1328 }
1329
1330 const auto &opSources = opAttributes.first;
1331 const auto &opSinks = opAttributes.second;
1332 if (opSources.isUnknown() || opSinks.isUnknown()) {
1333 op->setAttr("enzyme.activeop", unitAttr);
1334 } else if (opSources.isUndefined() || opSinks.isUndefined()) {
1335 op->setAttr("enzyme.constantop", unitAttr);
1336 } else {
1337 op->setAttr(opSourceAttrName, opAttributes.first.serialize(ctx));
1338 op->setAttr(opSinkAttrName, opAttributes.second.serialize(ctx));
1339 }
1340 };
1341
1342 // We lose the solver state when going top down and I don't know a better
1343 // way to serialize block argument information.
1344 node.getCallableRegion()->walk([&](Block *block) {
1345 for (BlockArgument blockArg : block->getArguments()) {
1346 OriginsPair blockArgAttributes({blockArg, ValueOriginSet()},
1347 {blockArg, ValueOriginSet()});
1348 joinActiveValueState(blockArg, blockArgAttributes);
1349 blockArgOrigins.try_emplace(blockArg, blockArgAttributes);
1350 }
1351 });
1352
1353 node.getCallableRegion()->walk([&](Operation *op) {
1354 if (activityConfig.annotate)
1355 annotateActivity(op);
1356 if (activityConfig.verbose) {
1357 if (op->hasAttr("tag")) {
1358 for (OpResult result : op->getResults()) {
1359 std::pair<ForwardOriginsLattice, BackwardOriginsLattice>
1360 activityAttributes({result, ValueOriginSet()},
1361 {result, ValueOriginSet()});
1362 joinActiveValueState(result, activityAttributes);
1363 os << op->getAttr("tag") << "(#" << result.getResultNumber()
1364 << ")\n"
1365 << " sources: "
1366 << format(activityAttributes.first.serialize(ctx)) << "\n"
1367 << " sinks: "
1368 << format(activityAttributes.second.serialize(ctx)) << "\n";
1369 }
1370 }
1371 }
1372 });
1373 }
1374
1375 if (!argActivities.empty() && activityConfig.annotate) {
1376 SmallVector<enzyme::Activity> resActivities;
1377 for (Type resultType : callee.getResultTypes()) {
1378 resActivities.push_back(isPossiblyActive(resultType)
1379 ? Activity::enzyme_active
1380 : Activity::enzyme_const);
1381 }
1382
1383 topDownActivityAnalysis(callee, argActivities, resActivities,
1384 blockArgOrigins);
1385 }
1386}
static void deserializePointsTo(ArrayAttr summaryAttr, DenseMap< DistinctAttr, enzyme::ValueOriginSet > &summaryMap)
static bool isPossiblyActive(Type type)
void deserializeReturnOrigins(ArrayAttr returnOrigins, SmallVectorImpl< enzyme::ValueOriginSet > &out)
std::optional< Value > getCopySource(Operation *op)
void printMapOfSetsLattice(const DenseMap< KeyT, enzyme::SetLattice< ElementT > > map, raw_ostream &os)
std::optional< Value > getStored(Operation *op)
bool annotate
Annotate the IR with activity information for every operation.
bool verbose
Output extra information for debugging.
This analysis implements interprocedural alias analysis.
void visitExternalCall(CallOpInterface call, ArrayRef< BackwardOriginsLattice * > operands, ArrayRef< const BackwardOriginsLattice * > results) override
void setToExitState(BackwardOriginsLattice *lattice) override
LogicalResult visitOperation(Operation *op, ArrayRef< BackwardOriginsLattice * > operands, ArrayRef< const BackwardOriginsLattice * > results) override
void print(raw_ostream &os) const override
ChangeResult meet(const AbstractSparseLattice &other) override
const DenseSet< OriginAttr > & getOrigins() const
ChangeResult meet(const AbstractDenseLattice &other) override
void print(raw_ostream &os) const override
const ValueOriginSet & getOrigins(DistinctAttr id) const
LogicalResult visitOperation(Operation *op, const ForwardOriginsMap &before, ForwardOriginsMap *after) override
void visitCallControlFlowTransfer(CallOpInterface call, dataflow::CallControlFlowAction action, const ForwardOriginsMap &before, ForwardOriginsMap *after) override
void setToEntryState(ForwardOriginsMap *lattice) override
void setToExitState(BackwardOriginsMap *lattice) override
LogicalResult visitOperation(Operation *op, const BackwardOriginsMap &after, BackwardOriginsMap *before) override
void visitCallControlFlowTransfer(CallOpInterface call, dataflow::CallControlFlowAction action, const BackwardOriginsMap &after, BackwardOriginsMap *before) override
void setToEntryState(ForwardOriginsLattice *lattice) override
void visitExternalCall(CallOpInterface call, ArrayRef< const ForwardOriginsLattice * > operands, ArrayRef< ForwardOriginsLattice * > results) override
LogicalResult visitOperation(Operation *op, ArrayRef< const ForwardOriginsLattice * > operands, ArrayRef< ForwardOriginsLattice * > results) override
ChangeResult join(const AbstractSparseLattice &other) override
const DenseSet< OriginAttr > & getOrigins() const
void print(raw_ostream &os) const override
static ForwardOriginsLattice single(Value point, OriginAttr value)
const ValueOriginSet & getOrigins(DistinctAttr id) const
void print(raw_ostream &os) const override
ChangeResult join(const AbstractDenseLattice &other)
Attribute serialize(MLIRContext *ctx) const
ChangeResult insert(const SetLattice< KeyT > &keysToUpdate, const SetLattice< ElementT > &values)
Map all keys to all values.
const AliasClassSet & getPointsTo(DistinctAttr id) const
DenseSet< ValueT > & getElements()
ChangeResult insert(const DenseSet< ValueT > &newElements)
ChangeResult join(const SetLattice< ValueT > &other)
static const SetLattice< OriginAttr > & getUndefined()
static const SetLattice< OriginAttr > & getUnknown()
Attribute serialize(MLIRContext *ctx) const
bool isFunctionReturnLike(Operation *op)
void serializeSummaries(Operation *op, const enzyme::PointsToSets &p2sets, const enzyme::ForwardOriginsMap &forwardOriginsMap, ArrayRef< enzyme::ForwardOriginsLattice > returnOperandOrigins, ArrayRef< enzyme::AliasClassLattice > returnAliasClasses)
void computeSummaries(FunctionOpInterface funcOp, DataFlowSolver &solver, enzyme::PointsToSets &p2sets, enzyme::ForwardOriginsMap &forwardOriginsMap, enzyme::BackwardOriginsMap &backwardOriginsMap, SmallVectorImpl< enzyme::ForwardOriginsLattice > &returnOperandOrigins, SmallVectorImpl< enzyme::AliasClassLattice > &returnAliasClasses)
SetLattice< OriginAttr > ValueOriginSet
LogicalResult reverseToposortCallgraph(CallableOpInterface callee, SymbolTableCollection *symbolTable, SmallVectorImpl< CallableOpInterface > &sorted)
Starting from callee, compute a reverse (bottom-up) topological sorting of all functions transitively...
constexpr llvm::StringLiteral unknownSetString
void removeSummaries(Operation *op)
SetLattice< DistinctAttr > AliasClassSet
A set of alias class identifiers to be treated as a single union.
void runActivityAnnotations(FunctionOpInterface callee, ArrayRef< enzyme::Activity > argActivities={}, const ActivityPrinterConfig &config=ActivityPrinterConfig())
constexpr llvm::StringLiteral undefinedSetString
void traversePointsToSets(const enzyme::AliasClassSet &start, const enzyme::PointsToSets &pointsToSets, function_ref< void(DistinctAttr)> visit)
Visit everything transitively pointed-to by any pointer in start.
LogicalResult runActivityAnnotationsForFunction(FunctionOpInterface funcOp, DataFlowSolver &solver)