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"
18#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
23 if (
auto tensorType = dyn_cast<TensorType>(type)) {
26 return isa<FloatType, ComplexType>(type);
30 os <<
serialize(getAnchor().getContext());
34 os << serialize(getAnchor().getContext());
39 const auto *otherValueOrigins =
41 return elements.
join(otherValueOrigins->elements);
46 auto arg = dyn_cast<BlockArgument>(lattice->getAnchor());
56 dyn_cast<FunctionOpInterface>(arg.getOwner()->getParentOp())) {
57 auto origin = ArgumentOriginAttr::get(FlatSymbolRefAttr::get(funcOp),
59 return propagateIfChanged(lattice,
61 lattice->getAnchor(), origin)));
65void enzyme::ForwardActivityAnnotationAnalysis::markResultsUnknown(
66 ArrayRef<ForwardOriginsLattice *> results) {
68 propagateIfChanged(result, result->markUnknown());
73 Operation *op, ArrayRef<const ForwardOriginsLattice *> operands,
74 ArrayRef<ForwardOriginsLattice *> results) {
75 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
76 if (iface && iface.isInactive())
79 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
81 if (!isMemoryEffectFree(op))
82 markResultsUnknown(results);
86 SmallVector<MemoryEffects::EffectInstance> effects;
87 memory.getEffects(effects);
88 for (
const auto &effect : effects) {
89 if (!isa<MemoryEffects::Read>(effect.getEffect()))
92 Value value = effect.getValue();
94 markResultsUnknown(results);
97 processMemoryRead(op, value, results);
102 for (
auto &&[i, operand] : llvm::enumerate(operands)) {
103 if (iface && iface.isArgInactive(i))
107 join(result, *operand);
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())
120 if (srcClasses->isUnknown())
121 return markResultsUnknown(results);
125 for (DistinctAttr srcClass : srcClasses->getAliasClasses()) {
128 propagateIfChanged(result,
129 result->merge(originsMap->getOrigins(srcClass)));
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>") {
146 for (enzyme::ArgumentOriginAttr originAttr :
147 cast<ArrayAttr>(argOrigins)
148 .getAsRange<enzyme::ArgumentOriginAttr>()) {
149 (void)origins.
insert({originAttr});
153 out.push_back(origins);
158 CallOpInterface call, ArrayRef<const ForwardOriginsLattice *> operands,
159 ArrayRef<ForwardOriginsLattice *> results) {
160 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
161 auto markAllResultsUnknown = [&]() {
163 propagateIfChanged(result, result->markUnknown());
167 return markAllResultsUnknown();
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;
175 return processCallToSummarizedFunc(call, returnOrigins, operands,
184 join(result, *operand);
190 function_ref<
void(DistinctAttr)> visit) {
195 DenseSet<DistinctAttr> visited;
200 assert(!current.
isUnknown() &&
"Unhandled traversal of unknown");
201 for (DistinctAttr currentClass : current.
getElements()) {
202 if (!visited.insert(currentClass).second)
209 std::swap(current, next);
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)) {
220 if (returnOrigin.isUndefined())
223 if (returnOrigin.isUnknown()) {
224 (void)callerOrigins.markUnknown();
226 ProgramPoint *point = getProgramPointAfter(call);
227 auto *denseOrigins = getOrCreateFor<ForwardOriginsMap>(point, point);
228 auto *pointsTo = getOrCreateFor<PointsToSets>(point, point);
229 (void)returnOrigin.foreachElement(
231 assert(state == ValueOriginSet::State::Defined &&
232 "undefined and unknown must have been handled above");
233 auto calleeArgOrigin = cast<ArgumentOriginAttr>(calleeOrigin);
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));
244 return callerOrigins.join(operandOrigins->getOriginsObject());
247 propagateIfChanged(result, result->merge(callerOrigins));
254void enzyme::BackwardActivityAnnotationAnalysis::markOperandsUnknown(
255 ArrayRef<BackwardOriginsLattice *> operands) {
257 propagateIfChanged(operand, operand->markUnknown());
262 Operation *op, ArrayRef<BackwardOriginsLattice *> operands,
263 ArrayRef<const BackwardOriginsLattice *> results) {
264 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
265 if (iface && iface.isInactive())
268 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
270 if (!isMemoryEffectFree(op))
271 markOperandsUnknown(operands);
275 SmallVector<MemoryEffects::EffectInstance> effects;
276 memory.getEffects(effects);
277 for (
const auto &effect : effects) {
278 if (!isa<MemoryEffects::Read>(effect.getEffect()))
281 Value value = effect.getValue();
283 markOperandsUnknown(operands);
288 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), value);
290 getOrCreate<BackwardOriginsMap>(getProgramPointBefore(op));
292 ChangeResult changed = ChangeResult::NoChange;
294 changed |= originsMap->insert(srcClasses->getAliasClassesObject(),
295 result->getOriginsObject());
296 propagateIfChanged(originsMap, changed);
301 for (
auto &&[i, operand] : llvm::enumerate(operands)) {
302 if (iface && iface.isArgInactive(i))
306 meet(operand, *result);
312 CallOpInterface call, ArrayRef<BackwardOriginsLattice *> operands,
313 ArrayRef<const BackwardOriginsLattice *> results) {
314 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
316 return markOperandsUnknown(operands);
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;
324 return processCallToSummarizedFunc(call, returnOrigins, operands,
333 meet(operand, *result);
336void enzyme::BackwardActivityAnnotationAnalysis::processCallToSummarizedFunc(
337 CallOpInterface call, ArrayRef<ValueOriginSet> summary,
338 ArrayRef<BackwardOriginsLattice *> operands,
339 ArrayRef<const BackwardOriginsLattice *> results) {
341 for (
const auto &[result, calleeOrigins] : llvm::zip(results, summary)) {
343 if (calleeOrigins.isUndefined())
345 if (calleeOrigins.isUnknown())
348 (void)calleeOrigins.foreachElement(
350 auto calleeArgOrigin = cast<ArgumentOriginAttr>(calleeOrigin);
351 BackwardOriginsLattice *operand =
352 operands[calleeArgOrigin.getArgNumber()];
353 propagateIfChanged(operand, operand->merge(resultOrigins));
354 return ChangeResult::NoChange;
360template <
typename KeyT,
typename ElementT>
367 for (
const auto &[aliasClass, origins] : map) {
368 os <<
" " << aliasClass <<
" originates from {";
369 if (origins.isUnknown()) {
371 }
else if (origins.isUndefined()) {
374 llvm::interleaveComma(origins.getElements(), os);
390 auto point = dyn_cast<ProgramPoint *>(lattice->getAnchor());
391 auto *block = point->getBlock();
392 if (!(block && isa<FunctionOpInterface>(block->getParentOp())))
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),
401 changed |= lattice->
insert(argClass->getAliasClassesObject(),
404 propagateIfChanged(lattice, changed);
407std::optional<Value>
getStored(Operation *op);
414 auto iface = dyn_cast<enzyme::ActivityOpInterface>(op);
415 if (iface && iface.isInactive())
418 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
420 if (!isMemoryEffectFree(op))
425 SmallVector<MemoryEffects::EffectInstance> effects;
426 memory.getEffects(effects);
427 for (
const auto &effect : effects) {
428 Value value = effect.getValue();
434 if (isa<MemoryEffects::Read>(effect.getEffect())) {
436 if (op->getNumResults() != 1)
438 Value readDest = op->getResult(0);
441 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), readDest);
442 if (destClasses->isUndefined())
447 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), value);
448 if (srcClasses->isUnknown()) {
449 propagateIfChanged(after,
450 after->
insert(destClasses->getAliasClassesObject(),
455 ChangeResult changed = ChangeResult::NoChange;
456 for (DistinctAttr srcClass : srcClasses->getAliasClasses()) {
457 changed |= after->
insert(destClasses->getAliasClassesObject(),
460 propagateIfChanged(after, changed);
461 }
else if (isa<MemoryEffects::Write>(effect.getEffect())) {
462 if (std::optional<Value> stored =
getStored(op)) {
466 auto *origins = getOrCreateFor<ForwardOriginsLattice>(
467 getProgramPointAfter(op), *stored);
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);
482void enzyme::DenseActivityAnnotationAnalysis::processCopy(
483 Operation *op, Value copySource, Value copyDest,
486 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), copySource);
488 if (src->isUndefined())
490 if (src->isUnknown())
493 for (DistinctAttr srcClass : src->getAliasClasses())
497 getOrCreateFor<AliasClassLattice>(getProgramPointAfter(op), copyDest);
498 propagateIfChanged(after,
499 after->
insert(dest->getAliasClassesObject(), srcOrigins));
504 ArrayAttr summaryAttr,
505 DenseMap<DistinctAttr, enzyme::ValueOriginSet> &summaryMap) {
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]);
512 if (
auto strAttr = dyn_cast<StringAttr>(pair[1])) {
514 (void)pointsToSet.markUnknown();
517 "unrecognized points-to destination");
520 auto pointsTo = cast<ArrayAttr>(pair[1]).getAsRange<enzyme::OriginAttr>();
521 (void)pointsToSet.insert(
522 DenseSet<enzyme::OriginAttr>(pointsTo.begin(), pointsTo.end()));
525 summaryMap.insert({pointer, pointsToSet});
530 CallOpInterface call, dataflow::CallControlFlowAction action,
533 if (action == dataflow::CallControlFlowAction::ExternalCallee) {
534 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
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;
544 return processCallToSummarizedFunc(call, summary, before, after);
550void enzyme::DenseActivityAnnotationAnalysis::processCallToSummarizedFunc(
551 CallOpInterface call,
const DenseMap<DistinctAttr, ValueOriginSet> &summary,
553 ChangeResult changed = ChangeResult::NoChange;
554 ProgramPoint *point = getProgramPointAfter(call);
559 auto *p2sets = getOrCreateFor<PointsToSets>(point, point);
560 SmallVector<ValueOriginSet> argumentOrigins;
561 SmallVector<AliasClassSet> argumentClasses;
562 for (
auto &&[i, argOperand] : llvm::enumerate(call.getArgOperands())) {
565 auto *argClasses = getOrCreateFor<AliasClassLattice>(point, argOperand);
566 if (argClasses->isUndefined()) {
568 auto *sparseOrigins =
569 getOrCreateFor<ForwardOriginsLattice>(point, argOperand);
570 (void)argOrigins.
join(sparseOrigins->getOriginsObject());
576 [&](DistinctAttr aliasClass) {
577 (void)argOrigins.join(
578 before.getOrigins(aliasClass));
581 argumentClasses.push_back(argClasses->getAliasClassesObject());
582 argumentOrigins.push_back(argOrigins);
585 for (
const auto &[destClass, sourceOrigins] : summary) {
590 if (sourceOrigins.isUndefined())
592 if (sourceOrigins.isUnknown()) {
593 (void)callerOrigins.markUnknown();
595 for (Attribute sourceOrigin : sourceOrigins.getElements()) {
597 cast<ArgumentOriginAttr>(sourceOrigin).getArgNumber();
598 (void)callerOrigins.join(argumentOrigins[argNumber]);
603 if (
auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
604 destClass.getReferencedAttr())) {
606 AliasClassSet current = argumentClasses[pseudoClass.getArgNumber()];
607 unsigned depth = pseudoClass.getDepth();
610 if (current.isUndefined()) {
619 for (DistinctAttr currentClass : current.getElements())
620 (void)next.
join(p2sets->getPointsTo(currentClass));
621 std::swap(current, next);
625 (void)callerDestClasses.
join(current);
627 (void)callerDestClasses.insert({destClass});
629 changed |= after->
insert(callerDestClasses, callerOrigins);
631 propagateIfChanged(after, changed);
636 dataflow::CallControlFlowAction action,
640 if (action == dataflow::CallControlFlowAction::ExternalCallee) {
641 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
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;
651 return processCallToSummarizedFunc(call, summary, after, before);
659 auto point = dyn_cast<ProgramPoint *>(lattice->getAnchor());
660 auto *block = point->getBlock();
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),
676 [&](DistinctAttr currentClass) {
678 lattice->insert(AliasClassSet(currentClass),
679 ValueOriginSet(origin));
682 propagateIfChanged(lattice, changed);
690 auto activityIface = dyn_cast<enzyme::ActivityOpInterface>(op);
691 if (activityIface && activityIface.isInactive())
694 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
696 if (!isMemoryEffectFree(op))
701 SmallVector<MemoryEffects::EffectInstance> effects;
702 memory.getEffects(effects);
703 for (
const auto &effect : effects) {
704 if (!isa<MemoryEffects::Write>(effect.getEffect()))
707 Value value = effect.getValue();
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);
718 if (storedClasses->isUndefined()) {
720 auto *storedOrigins = getOrCreate<BackwardOriginsLattice>(*stored);
723 addressClasses->getAliasClassesObject().foreachElement(
725 if (state == AliasClassSet::State::Undefined) {
726 return ChangeResult::NoChange;
728 if (state == AliasClassSet::State::Unknown) {
729 return storedOrigins->markUnknown();
731 return storedOrigins->merge(after.
getOrigins(alloc));
733 }
else if (storedClasses->isUnknown()) {
739 }
else if (std::optional<Value> copySource =
getCopySource(op)) {
740 processCopy(op, *copySource, value, after, before);
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);
756 auto *p2sets = getOrCreateFor<PointsToSets>(pointBefore, pointAfter);
757 SmallVector<AliasClassSet> argumentClasses;
758 for (Value argOperand : call.getArgOperands()) {
760 getOrCreateFor<AliasClassLattice>(pointBefore, argOperand);
761 argumentClasses.push_back(argClasses->getAliasClassesObject());
764 for (
const auto &[destClass, sourceOrigins] : summary) {
767 if (
auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
768 destClass.getReferencedAttr())) {
770 [&](DistinctAttr aliasClass) {
771 (void)destOrigins.join(
772 after.getOrigins(aliasClass));
776 if (destOrigins.isUndefined())
781 if (sourceOrigins.isUndefined())
783 if (sourceOrigins.isUnknown()) {
784 changed |= before->markAllOriginsUnknown();
790 for (Attribute sourceOrigin : sourceOrigins.getElements()) {
792 cast<ArgumentOriginAttr>(sourceOrigin).getArgNumber();
794 if (argumentClasses[argNumber].isUndefined()) {
796 auto *backwardLattice = getOrCreate<BackwardOriginsLattice>(
797 call.getArgOperands()[argNumber]);
798 if (destOrigins.isUnknown()) {
799 propagateIfChanged(backwardLattice, backwardLattice->markUnknown());
802 propagateIfChanged(backwardLattice,
803 backwardLattice->insert(destOrigins.getElements()));
806 [&](DistinctAttr aliasClass) {
807 (void)callerSourceClasses.insert({aliasClass});
811 changed |= before->
insert(callerSourceClasses, destOrigins);
813 propagateIfChanged(before, changed);
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);
822 if (dest->isUndefined())
824 if (dest->isUnknown())
827 for (DistinctAttr destClass : dest->getAliasClasses())
828 (void)destOrigins.join(after.getOrigins(destClass));
830 auto *src = getOrCreateFor<AliasClassLattice>(point, copySource);
831 propagateIfChanged(before,
832 before->insert(src->getAliasClassesObject(), destOrigins));
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));
848void initializeSparseBackwardActivityAnnotations(FunctionOpInterface func,
849 DataFlowSolver &solver) {
852 for (Operation &op : func.getCallableRegion()->getOps()) {
856 for (OpOperand &returnOperand : op.getOpOperands()) {
859 auto origin = ReturnOriginAttr::get(FlatSymbolRefAttr::get(func),
860 returnOperand.getOperandNumber());
861 (void)lattice->insert({origin});
867 std::pair<enzyme::ForwardOriginsLattice, enzyme::BackwardOriginsLattice>;
874void topDownActivityAnalysis(
875 FunctionOpInterface callee, ArrayRef<enzyme::Activity> argActivities,
876 ArrayRef<enzyme::Activity> retActivities,
877 DenseMap<BlockArgument, OriginsPair> &blockArgOrigins) {
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);
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()]);
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()]);
896 callee.getFunctionBody().walk([&](Operation *op) {
897 if (op->getNumResults() == 0) {
899 op->setAttr(
"enzyme.icv", trueAttr);
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);
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
913 assert(valueSource && valueSink &&
"missing attributes for op");
915 llvm::any_of(valueSource.getAsRange<OriginAttr>(), isOriginActive);
917 llvm::any_of(valueSink.getAsRange<OriginAttr>(), isOriginActive);
918 bool activeVal = activeSource && activeSink;
919 op->setAttr(
"enzyme.icv", BoolAttr::get(ctx, !activeVal));
922 op->removeAttr(
"enzyme.constantval");
923 op->removeAttr(
"enzyme.activeval");
924 op->removeAttr(
"enzyme.valsrc");
925 op->removeAttr(
"enzyme.valsink");
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);
933 bool activeSource = llvm::any_of(
934 op->getAttrOfType<ArrayAttr>(
"enzyme.opsrc").getAsRange<OriginAttr>(),
937 llvm::any_of(op->getAttrOfType<ArrayAttr>(
"enzyme.opsink")
938 .getAsRange<OriginAttr>(),
940 bool activeOp = activeSource && activeSink;
941 op->setAttr(
"enzyme.ici", BoolAttr::get(ctx, !activeOp));
944 op->removeAttr(
"enzyme.constantop");
945 op->removeAttr(
"enzyme.activeop");
946 op->removeAttr(
"enzyme.opsrc");
947 op->removeAttr(
"enzyme.opsink");
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()) {
956 definingOp->getAttrOfType<BoolAttr>(
"enzyme.icv").getValue();
957 callArgActivities.push_back(icv ? Activity::enzyme_const
958 : Activity::enzyme_active);
960 BlockArgument blockArg = cast<BlockArgument>(operand);
961 const OriginsPair &originsPair = blockArgOrigins.at(blockArg);
964 bool argActive =
false;
970 argActive = llvm::any_of(sources.
getOrigins(), isOriginActive) &&
971 llvm::any_of(sinks.
getOrigins(), isOriginActive);
973 callArgActivities.push_back(argActive ? Activity::enzyme_active
974 : Activity::enzyme_const);
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);
983 topDownActivityAnalysis(funcOp, callArgActivities, callResActivities,
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) {
996 llvm::raw_string_ostream ss(res);
998 for (
unsigned i = 0; i < attr.size(); ++i) {
999 ss << format(attr[i]);
1000 if (i < attr.size() - 1)
1006 .Case([](enzyme::PseudoAliasClassAttr attr) {
1007 return (
"pclass<(" + Twine(attr.getArgNumber()) +
", " +
1008 Twine(attr.getDepth()) +
")>")
1011 .Case([](enzyme::ArgumentOriginAttr attr) {
1012 return (
"arg" + Twine(attr.getArgNumber())).str();
1014 .Case([](enzyme::ReturnOriginAttr attr) {
1015 return (
"ret" + Twine(attr.getReturnNumber())).str();
1017 .Default([](Attribute attr) {
1019 llvm::raw_string_ostream ss(res);
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))
1038 if (temporary.contains(node))
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();
1050 if (walkResult.wasInterrupted())
1053 temporary.erase(node);
1054 permanent.insert(node);
1055 sorted.push_back(node);
1059 return visit(callee);
1063 FunctionOpInterface funcOp, DataFlowSolver &solver,
1066 SmallVectorImpl<enzyme::ForwardOriginsLattice> &returnOperandOrigins,
1067 SmallVectorImpl<enzyme::AliasClassLattice> &returnAliasClasses) {
1068 assert(returnOperandOrigins.size() == funcOp.getNumResults());
1069 assert(returnAliasClasses.size() == funcOp.getNumResults());
1072 for (Operation &op : funcOp.getCallableRegion()->getOps()) {
1074 ProgramPoint *point = solver.getProgramPointAfter(&op);
1076 auto *returnOrigins =
1079 (void)forwardOriginsMap.
join(*returnOrigins);
1081 for (OpOperand &operand : op.getOpOperands()) {
1082 (void)returnAliasClasses[operand.getOperandNumber()].join(
1084 (void)returnOperandOrigins[operand.getOperandNumber()].join(
1090 (void)backwardOriginsMap.
meet(
1092 solver.getProgramPointBefore(
1093 &funcOp.getCallableRegion()->front().front())));
1099 ArrayRef<enzyme::ForwardOriginsLattice> returnOperandOrigins,
1100 ArrayRef<enzyme::AliasClassLattice> returnAliasClasses) {
1102 MLIRContext *ctx = op->getContext();
1104 SmallVector<Attribute> aliasAttributes(returnAliasClasses.size());
1105 llvm::transform(returnAliasClasses, aliasAttributes.begin(),
1107 return lattice.serialize(ctx);
1109 op->setAttr(EnzymeDialect::getAliasSummaryAttrName(),
1110 ArrayAttr::get(ctx, aliasAttributes));
1113 StringRef pointerSummaryName = EnzymeDialect::getPointerSummaryAttrName();
1114 op->setAttr(pointerSummaryName, p2sets.
serialize(ctx));
1116 op->setAttr(EnzymeDialect::getDenseActivityAnnotationAttrName(),
1120 SmallVector<Attribute> serializedReturnOperandOrigins(
1121 returnOperandOrigins.size());
1122 llvm::transform(returnOperandOrigins, serializedReturnOperandOrigins.begin(),
1124 return lattice.serialize(ctx);
1126 op->setAttr(EnzymeDialect::getSparseActivityAnnotationAttrName(),
1127 ArrayAttr::get(ctx, serializedReturnOperandOrigins));
1131 op->removeAttr(EnzymeDialect::getAliasSummaryAttrName());
1132 op->removeAttr(EnzymeDialect::getPointerSummaryAttrName());
1133 op->removeAttr(EnzymeDialect::getDenseActivityAnnotationAttrName());
1134 op->removeAttr(EnzymeDialect::getSparseActivityAnnotationAttrName());
1139 DataFlowSolver &solver) {
1142 SymbolTableCollection symbolTable;
1143 MLIRContext *ctx = funcOp.getContext();
1144 dataflow::loadBaselineAnalyses(solver);
1153 initializeSparseBackwardActivityAnnotations(funcOp, solver);
1154 return solver.initializeAndRun(funcOp);
1158 FunctionOpInterface callee, ArrayRef<enzyme::Activity> argActivities,
1160 SymbolTableCollection symbolTable;
1161 SmallVector<CallableOpInterface> sorted;
1163 raw_ostream &os = llvm::outs();
1166 DenseMap<BlockArgument, OriginsPair> blockArgOrigins;
1168 StringRef pointerSummaryName = EnzymeDialect::getPointerSummaryAttrName();
1169 for (CallableOpInterface node : sorted) {
1170 annotateHardcoded(cast<FunctionOpInterface>(node.getOperation()));
1172 if (!node.getCallableRegion() || node->hasAttr(pointerSummaryName))
1174 auto funcOp = cast<FunctionOpInterface>(node.getOperation());
1176 os <<
"[ata] processing function @" << funcOp.getName() <<
"\n";
1178 DataFlowConfig dataFlowConfig;
1179 dataFlowConfig.setInterprocedural(
false);
1180 DataFlowSolver solver(dataFlowConfig);
1182 assert(
false &&
"dataflow solver failed");
1189 size_t numResults = funcOp.getResultTypes().size();
1190 SmallVector<enzyme::ForwardOriginsLattice> returnOperandOrigins(
1192 SmallVector<enzyme::AliasClassLattice> returnAliasClasses(
1195 backwardOriginsMap, returnOperandOrigins,
1196 returnAliasClasses);
1198 returnAliasClasses);
1199 Attribute backwardOrigins = backwardOriginsMap.
serialize(node.getContext());
1202 os <<
"[ata] p2p summary:\n";
1203 if (node->getAttrOfType<ArrayAttr>(pointerSummaryName).size() == 0) {
1206 for (ArrayAttr pair : node->getAttrOfType<ArrayAttr>(pointerSummaryName)
1207 .getAsRange<ArrayAttr>()) {
1208 os <<
" " << format(pair[0]) <<
" -> " << format(pair[1]) <<
"\n";
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";
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])
1227 os <<
"[ata] return origins: "
1228 << format(node->getAttr(
1229 EnzymeDialect::getSparseActivityAnnotationAttrName()))
1233 auto joinActiveDataState =
1235 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1238 (void)out.first.join(*sources);
1239 (void)out.second.
meet(*sinks);
1242 auto joinActivePointerState =
1244 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1246 aliasClasses, p2sets, [&](DistinctAttr aliasClass) {
1247 (void)out.first.merge(forwardOriginsMap.
getOrigins(aliasClass));
1248 (void)out.second.merge(
1253 auto joinActiveValueState =
1255 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> &out) {
1256 if (isa<LLVM::LLVMPointerType, MemRefType>(value.getType())) {
1257 auto *aliasClasses =
1259 joinActivePointerState(aliasClasses->getAliasClassesObject(), out);
1261 joinActiveDataState(value, out);
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);
1271 for (OpResult result : op->getResults()) {
1272 std::pair<ForwardOriginsLattice, BackwardOriginsLattice>
1275 joinActiveValueState(result, activityAttributes);
1276 const auto &sources = activityAttributes.first;
1277 const auto &sinks = activityAttributes.second;
1280 if (sources.isUnknown() || sinks.isUnknown()) {
1282 op->setAttr(
"enzyme.activeval", unitAttr);
1283 }
else if (sources.isUndefined() || sinks.isUndefined()) {
1285 op->setAttr(
"enzyme.constantval", unitAttr);
1288 op->setAttr(
"enzyme.valsrc", sources.serialize(ctx));
1289 op->setAttr(
"enzyme.valsink", sinks.serialize(ctx));
1293 StringRef opSourceAttrName =
"enzyme.opsrc";
1294 StringRef opSinkAttrName =
"enzyme.opsink";
1295 std::pair<ForwardOriginsLattice, BackwardOriginsLattice> opAttributes(
1299 for (OpResult result : op->getResults()) {
1300 joinActiveDataState(result, opAttributes);
1305 if (
auto storeOp = dyn_cast<LLVM::StoreOp>(op)) {
1308 joinActivePointerState(storedClass->getAliasClassesObject(),
1310 }
else if (
auto callOp = dyn_cast<CallOpInterface>(op)) {
1312 auto callable = cast<CallableOpInterface>(callOp.resolveCallable());
1313 if (callable->hasAttr(
1314 EnzymeDialect::getDenseActivityAnnotationAttrName())) {
1315 for (Value operand : callOp.getArgOperands())
1316 joinActiveValueState(operand, opAttributes);
1324 for (Value operand : op->getOperands())
1325 joinActiveDataState(operand, opAttributes);
1326 for (OpResult result : op->getResults())
1327 joinActiveDataState(result, opAttributes);
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);
1337 op->setAttr(opSourceAttrName, opAttributes.first.serialize(ctx));
1338 op->setAttr(opSinkAttrName, opAttributes.second.serialize(ctx));
1344 node.getCallableRegion()->walk([&](Block *block) {
1345 for (BlockArgument blockArg : block->getArguments()) {
1348 joinActiveValueState(blockArg, blockArgAttributes);
1349 blockArgOrigins.try_emplace(blockArg, blockArgAttributes);
1353 node.getCallableRegion()->walk([&](Operation *op) {
1355 annotateActivity(op);
1357 if (op->hasAttr(
"tag")) {
1358 for (OpResult result : op->getResults()) {
1359 std::pair<ForwardOriginsLattice, BackwardOriginsLattice>
1362 joinActiveValueState(result, activityAttributes);
1363 os << op->getAttr(
"tag") <<
"(#" << result.getResultNumber()
1366 << format(activityAttributes.first.serialize(ctx)) <<
"\n"
1368 << format(activityAttributes.second.serialize(ctx)) <<
"\n";
1375 if (!argActivities.empty() && activityConfig.
annotate) {
1376 SmallVector<enzyme::Activity> resActivities;
1377 for (Type resultType : callee.getResultTypes()) {
1379 ? Activity::enzyme_active
1380 : Activity::enzyme_const);
1383 topDownActivityAnalysis(callee, argActivities, resActivities,
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 markAllOriginsUnknown()
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
ChangeResult markAllOriginsUnknown()
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
ChangeResult markUnknown()
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)