Enzyme main
Loading...
Searching...
No Matches
DataFlowAliasAnalysis.cpp
Go to the documentation of this file.
1//===- DataFlowAliasAnalysis.cpp - Implementation of Alias Analysis ------===//
2//
3// Enzyme Project
4//
5// Part of the Enzyme Project, under the Apache License v2.0 with LLVM
6// Exceptions. See https://llvm.org/LICENSE.txt for license information.
7// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8//
9// If using this code in an academic setting, please cite the following:
10// @incollection{enzymeNeurips,
11// title = {Instead of Rewriting Foreign Code for Machine Learning,
12// Automatically Synthesize Fast Gradients},
13// author = {Moses, William S. and Churavy, Valentin},
14// booktitle = {Advances in Neural Information Processing Systems 33},
15// year = {2020},
16// note = {To appear in},
17// }
18//
19//===----------------------------------------------------------------------===//
20//
21// This file contains the implementation of Alias (and Points-To) Analysis, a
22// general analysis that determines the possible static memory locations
23// that the pointers in a program may point to.
24//
25//===----------------------------------------------------------------------===//
27#include "Dialect/Dialect.h"
28#include "Dialect/Ops.h"
29
30#include "mlir/Analysis/AliasAnalysis.h"
31#include "mlir/Analysis/DataFlow/DenseAnalysis.h"
32#include "mlir/Analysis/DataFlow/SparseAnalysis.h"
33#include "mlir/Analysis/DataFlow/Utils.h"
34#include "mlir/Analysis/DataFlowFramework.h"
35#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
36#include "mlir/Interfaces/FunctionInterfaces.h"
37#include "mlir/Interfaces/SideEffectInterfaces.h"
38#include "mlir/Interfaces/ViewLikeInterface.h"
39
40// The LLVM dialect is only used for attribute names.
41#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
42
43// TODO: remove this once aliasing interface is factored out.
44#include "mlir/Dialect/Affine/IR/AffineOps.h"
45#include "mlir/Dialect/MemRef/IR/MemRef.h"
46#include "llvm/ADT/SetOperations.h"
47
48using namespace mlir;
49using namespace mlir::dataflow;
50
51static bool isPointerLike(Type type) {
52 return isa<MemRefType, LLVM::LLVMPointerType, enzyme::CacheType>(type);
53}
54
55//===----------------------------------------------------------------------===//
56// PointsToAnalysis
57//===----------------------------------------------------------------------===//
58
59template <typename T>
60static ChangeResult mergeSets(DenseSet<T> &dest, const DenseSet<T> &src) {
61 size_t oldSize = dest.size();
62 dest.insert(src.begin(), src.end());
63 return dest.size() == oldSize ? ChangeResult::NoChange : ChangeResult::Change;
64}
65
66static void
67deserializePointsTo(ArrayAttr summaryAttr,
68 DenseMap<DistinctAttr, enzyme::AliasClassSet> &summaryMap) {
69 for (auto pair : summaryAttr.getAsRange<ArrayAttr>()) {
70 assert(pair.size() == 2 &&
71 "Expected summary to be in [[key, value]] format");
72 auto pointer = cast<DistinctAttr>(pair[0]);
73 auto pointsToSet = enzyme::AliasClassSet::getUndefined();
74
75 if (auto strAttr = dyn_cast<StringAttr>(pair[1])) {
76 if (strAttr.getValue() == enzyme::unknownSetString) {
77 (void)pointsToSet.markUnknown();
78 } else {
79 assert(strAttr.getValue() == enzyme::undefinedSetString &&
80 "unrecognized points-to destination");
81 }
82 } else {
83 auto pointsTo = cast<ArrayAttr>(pair[1]).getAsRange<DistinctAttr>();
84 // TODO: see if there's a nice way to convert the AliasClassSet::insert
85 // method to accept this interator rather than constructing a DenseSet
86 (void)pointsToSet.insert(
87 DenseSet<DistinctAttr>(pointsTo.begin(), pointsTo.end()));
88 }
89 summaryMap.insert({pointer, pointsToSet});
90 }
91}
92
93void enzyme::PointsToSets::print(raw_ostream &os) const {
94 if (map.empty()) {
95 os << "<empty>\n";
96 return;
97 }
98 for (const auto &[srcClass, destClasses] : map) {
99 os << " " << srcClass << " points to {";
100 if (destClasses.isUnknown()) {
101 os << unknownSetString;
102 } else if (destClasses.isUndefined()) {
103 os << undefinedSetString;
104 } else {
105 llvm::interleaveComma(destClasses.getElements(), os);
106 }
107 os << "}\n";
108 }
109}
110
111ChangeResult enzyme::PointsToSets::update(const AliasClassSet &keysToUpdate,
112 const AliasClassSet &values,
113 bool replace) {
114 if (keysToUpdate.isUnknown())
115 return markAllPointToUnknown();
116
117 // Don't yet know what to update.
118 if (keysToUpdate.isUndefined())
119 return ChangeResult::NoChange;
120
121 return keysToUpdate.foreachElement(
122 [&](DistinctAttr dest, AliasClassSet::State state) {
123 assert(state == AliasClassSet::State::Defined &&
124 "unknown must have been handled above");
125 return joinPotentiallyMissing(dest, values);
126 });
127}
128
129ChangeResult
131 return update(destClasses, AliasClassSet::getEmpty(), /*replace=*/true);
132}
133
134ChangeResult
136 const AliasClassSet &srcClasses) {
137 if (destClasses.isUnknown())
138 return markAllPointToUnknown();
139 if (destClasses.isUndefined())
140 return ChangeResult::NoChange;
141
142 return destClasses.foreachElement(
143 [&](DistinctAttr dest, AliasClassSet::State destState) {
144 assert(destState == AliasClassSet::State::Defined);
145 return srcClasses.foreachElement(
146 [&](DistinctAttr src, AliasClassSet::State srcState) {
147 const AliasClassSet *srcClasses = &AliasClassSet::getUndefined();
148 if (srcState == AliasClassSet::State::Unknown)
149 srcClasses = &AliasClassSet::getUnknown();
150 else if (srcState == AliasClassSet::State::Defined) {
151 auto it = map.find(src);
152 if (it != map.end())
153 srcClasses = &it->getSecond();
154 }
155 return joinPotentiallyMissing(dest, *srcClasses);
156 });
157 });
158}
159
160ChangeResult
162 if (destClasses.isUnknown())
163 return markAllPointToUnknown();
164 if (destClasses.isUndefined())
165 return ChangeResult::NoChange;
166
167 return destClasses.foreachElement(
168 [&](DistinctAttr dest, AliasClassSet::State) {
169 return joinPotentiallyMissing(dest, AliasClassSet::getUnknown());
170 });
171}
172
174 const AliasClassSet &destClasses) {
175 if (destClasses.isUndefined())
176 return ChangeResult::NoChange;
177
178 ChangeResult result = ChangeResult::NoChange;
179 for (auto &[key, value] : map) {
180 if (destClasses.isUnknown() || !destClasses.getElements().contains(key)) {
181 result |= value.markUnknown();
182 }
183 }
184
185#ifndef NDEBUG
186 (void)destClasses.foreachElement(
187 [&](DistinctAttr dest, AliasClassSet::State state) {
188 if (state == AliasClassSet::State::Defined)
189 assert(map.contains(dest) && "unknown dest cannot be preserved");
190 return ChangeResult::NoChange;
191 });
192#endif // NDEBUG
193
194 return result;
195}
196
197// TODO: Reduce code duplication with activity analysis
198std::optional<Value> getStored(Operation *op);
199std::optional<Value> getCopySource(Operation *op);
200
202 ProgramPoint *dependent, PointsToSets *after, Value capturedValue,
203 Value destinationAddress, bool isMustStore) {
204 auto *srcClasses =
205 getOrCreateFor<AliasClassLattice>(dependent, capturedValue);
206 auto *destClasses =
207 getOrCreateFor<AliasClassLattice>(dependent, destinationAddress);
208
209 // If the destination class is unknown, i.e. all possible pointers, then we
210 // have reached the pessimistic fixpoint and don't know anything. Bail.
211 if (destClasses->isUnknown()) {
212 propagateIfChanged(after, after->markAllPointToUnknown());
213 return;
214 }
215
216 // If the source class is unknown, record that any destination class may
217 // point to any pointer.
218 if (srcClasses->isUnknown()) {
219 propagateIfChanged(
220 after, after->markPointToUnknown(destClasses->getAliasClassesObject()));
221 } else {
222 // Treat all stores as may-store because we don't know better.
223 if (isMustStore) {
224 propagateIfChanged(after, after->setPointingToClasses(
225 destClasses->getAliasClassesObject(),
226 srcClasses->getAliasClassesObject()));
227 } else {
228 propagateIfChanged(after,
229 after->insert(destClasses->getAliasClassesObject(),
230 srcClasses->getAliasClassesObject()));
231 }
232 }
233}
234
235// TODO: this should become an interface or be integrated into side effects so
236// it doesn't depend on the dialect.
237// TODO: currently, we should treat all stores is "may store" because of how the
238// analysis is used: the points-to of the function exit point is considered as
239// the overall state of the function, which may be incorrect for any operation
240// post-dominated by a must-store.
241static bool isMustStore(Operation *op, Value pointer) {
242 return false; // isa<LLVM::StoreOp>(op);
243}
244
245// TODO: This should be integrated into an interface somewhere
246static bool isNoOp(Operation *op) {
247 return isa<LLVM::NoAliasScopeDeclOp, LLVM::LifetimeStartOp,
248 LLVM::LifetimeEndOp, LLVM::AssumeOp, LLVM::UnreachableOp>(op);
249}
250
252 Operation *op, const PointsToSets &before, PointsToSets *after) {
253 join(after, before);
254
255 // If we know nothing about memory effects, record reaching the pessimistic
256 // fixpoint and bail.
257 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
258 if (!memory) {
259 if (isNoOp(op) || isMemoryEffectFree(op))
260 return success();
261 propagateIfChanged(after, after->markAllPointToUnknown());
262 return success();
263 }
264
265 SmallVector<MemoryEffects::EffectInstance> effects;
266 memory.getEffects(effects);
267
268 // If the operation allocates fresh memory and doesn't write into it, that
269 // memory is known not to point to any known alias class.
270 if (effects.size() == 1 &&
271 isa<MemoryEffects::Allocate>(effects.front().getEffect()) &&
272 effects.front().getValue()) {
273 const auto *destClasses = getOrCreateFor<AliasClassLattice>(
274 getProgramPointAfter(op), effects.front().getValue());
275 propagateIfChanged(
276 after, after->setPointingToEmpty(destClasses->getAliasClassesObject()));
277 return success();
278 }
279
280 for (const auto &effect : effects) {
281 if (!isa<MemoryEffects::Write>(effect.getEffect()))
282 continue;
283
284 SmallVector<Value> targetValues;
285 Value value = effect.getValue();
286 auto pointerLikeOperands =
287 llvm::make_filter_range(op->getOperands(), [](Value operand) {
288 return isPointerLike(operand.getType());
289 });
290
291 // If we don't know the value on which the effect happens, it can happen on
292 // any value.
293 if (value) {
294 targetValues.push_back(value);
295 } else {
296 llvm::append_range(targetValues, pointerLikeOperands);
297 }
298
299 // If we don't know which value is stored, it can be any value. For the
300 // purpose of this analysis, we only care about pointer-like values being
301 // stored.
302 SmallVector<Value> storedValues;
303 if (std::optional<Value> stored = getStored(op)) {
304 storedValues.push_back(*stored);
305 } else if (std::optional<Value> stored = getCopySource(op)) {
306 // TODO: implement capturing via copy ops
307 } else {
308 llvm::append_range(storedValues, pointerLikeOperands);
309 }
310
311 for (Value address : targetValues) {
312 for (Value stored : storedValues) {
313 processCapturingStore(getProgramPointAfter(op), after, stored, address,
314 isMustStore(op, address));
315 }
316 }
317 }
318 return success();
319}
320
321constexpr static llvm::StringLiteral kLLVMMemoryAttrName = "memory_effects";
322
323static bool modRefMayMod(std::optional<LLVM::ModRefInfo> modRef) {
324 return modRef ? (*modRef == LLVM::ModRefInfo::Mod ||
325 *modRef == LLVM::ModRefInfo::ModRef)
326 : true;
327}
328
329static bool modRefMayRef(std::optional<LLVM::ModRefInfo> modRef) {
330 return modRef ? (*modRef == LLVM::ModRefInfo::Ref ||
331 *modRef == LLVM::ModRefInfo::ModRef)
332 : true;
333}
334
335static bool mayReadArg(FunctionOpInterface callee, unsigned argNo,
336 std::optional<LLVM::ModRefInfo> argMemMRI) {
337 // Function-wide annotation.
338 bool funcMayRead = modRefMayRef(argMemMRI);
339
340 // Vararg behavior can only be specified by the function.
341 unsigned numArguments = callee.getNumArguments();
342 if (argNo >= numArguments)
343 return funcMayRead;
344
345 bool hasWriteOnlyAttr =
346 !!callee.getArgAttr(argNo, LLVM::LLVMDialect::getWriteOnlyAttrName());
347 bool hasReadNoneAttr =
348 !!callee.getArgAttr(argNo, LLVM::LLVMDialect::getReadnoneAttrName());
349 return !hasWriteOnlyAttr && !hasReadNoneAttr && funcMayRead;
350}
351
352static bool mayWriteArg(FunctionOpInterface callee, unsigned argNo,
353 std::optional<LLVM::ModRefInfo> argMemMRI) {
354 // Function-wide annotation.
355 bool funcMayWrite = modRefMayMod(argMemMRI);
356
357 // Vararg behavior can only be specified by the function.
358 unsigned numArguments = callee.getNumArguments();
359 if (argNo >= numArguments)
360 return funcMayWrite;
361
362 // Individual attributes can further restrict argument writability. Note that
363 // `readnone` means "no read or write" for LLVM.
364 bool hasReadOnlyAttr =
365 !!callee.getArgAttr(argNo, LLVM::LLVMDialect::getReadonlyAttrName());
366 bool hasReadNoneAttr =
367 !!callee.getArgAttr(argNo, LLVM::LLVMDialect::getReadnoneAttrName());
368 return !hasReadOnlyAttr && !hasReadNoneAttr && funcMayWrite;
369}
370
371/// Returns information indicating whether the function may read or write into
372/// the memory pointed to by its arguments. When unknown, returns `nullopt`.
373static std::optional<LLVM::ModRefInfo>
374getFunctionArgModRef(FunctionOpInterface func) {
375 // First, handle some library functions with statically known behavior.
376 StringRef name = cast<SymbolOpInterface>(func.getOperation()).getName();
377 auto hardcoded = llvm::StringSwitch<std::optional<LLVM::ModRefInfo>>(name)
378 // printf: only reads from arguments.
379 .Case("printf", LLVM::ModRefInfo::Ref)
380 .Case("puts", LLVM::ModRefInfo::Ref)
381 .Case("memset_pattern16", LLVM::ModRefInfo::ModRef)
382 .Case("free", LLVM::ModRefInfo::NoModRef)
383 // operator delete(void *) doesn't read from arguments.
384 .Case("_ZdlPv", LLVM::ModRefInfo::NoModRef)
385 .Default(std::nullopt);
386 if (hardcoded)
387 return hardcoded;
388
389 if (auto memoryAttr =
390 func->getAttrOfType<LLVM::MemoryEffectsAttr>(kLLVMMemoryAttrName))
391 return memoryAttr.getArgMem();
392 return std::nullopt;
393}
394
395/// Returns information indicating whether the function may read or write into
396/// the memory other than that pointed to by its arguments, though still
397/// accessible from (any) calling context. When unknown, returns `nullopt`.
398static std::optional<LLVM::ModRefInfo>
399getFunctionOtherModRef(FunctionOpInterface func) {
400 // First, handle some library functions with statically known behavior.
401 StringRef name = cast<SymbolOpInterface>(func.getOperation()).getName();
402 auto hardcoded =
403 llvm::StringSwitch<std::optional<LLVM::ModRefInfo>>(name)
404 // printf: doesn't access other (technically, stdout is pointer-like,
405 // but we cannot flow information through it since it is write-only.
406 .Case("printf", LLVM::ModRefInfo::NoModRef)
407 .Case("puts", LLVM::ModRefInfo::NoModRef)
408 .Case("gettimeofday", LLVM::ModRefInfo::Ref)
409 // operator delete(void *) doesn't access other.
410 .Case("_ZdlPv", LLVM::ModRefInfo::NoModRef)
411 .Case("free", LLVM::ModRefInfo::NoModRef)
412 .Case("memset_pattern16", LLVM::ModRefInfo::NoModRef)
413 .Default(std::nullopt);
414 if (hardcoded)
415 return hardcoded;
416
417 if (auto memoryAttr =
418 func->getAttrOfType<LLVM::MemoryEffectsAttr>(kLLVMMemoryAttrName))
419 return memoryAttr.getOther();
420 return std::nullopt;
421}
422
424 CallOpInterface call,
425 const DenseMap<DistinctAttr, enzyme::AliasClassSet> &summary,
426 PointsToSets *after) {
427 StringRef calleeName = cast<SymbolRefAttr>(call.getCallableForCallee())
428 .getLeafReference()
429 .getValue();
430 auto lookup = [&](unsigned argNumber,
431 unsigned depth) -> std::optional<AliasClassSet> {
432 for (const auto &[attr, aliasClassSet] : summary) {
433 if (auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
434 attr.getReferencedAttr())) {
435 if (pseudoClass.getFunction().getValue() == calleeName &&
436 pseudoClass.getArgNumber() == argNumber &&
437 pseudoClass.getDepth() == depth) {
438 return aliasClassSet;
439 }
440 }
441 }
442 return std::nullopt;
443 };
444
445 ChangeResult changed = ChangeResult::NoChange;
446 // Unify the points-to summary with the actual lattices of function arguments
447 for (auto &&[i, argOperand] : llvm::enumerate(call.getArgOperands())) {
448 auto *arg = getOrCreateFor<AliasClassLattice>(getProgramPointAfter(call),
449 argOperand);
450
451 std::optional<AliasClassSet> calleePointsTo = lookup(i, /*depth=*/0);
452 // If the argument class isn't in the summary, it hasn't changed what
453 // it points to during the function.
454 if (!calleePointsTo)
455 continue;
456
457 // A callee that could not say what this argument ends up pointing to says
458 // nothing narrower to the caller either.
459 if (calleePointsTo->isUnknown()) {
460 changed |= after->insert(arg->getAliasClassesObject(),
462 continue;
463 }
464 if (calleePointsTo->isUndefined())
465 continue;
466
467 for (DistinctAttr ac : calleePointsTo->getElements()) {
468 if (!isa<PseudoAliasClassAttr>(ac.getReferencedAttr())) {
469 // Fresh classes go in directly
470 changed |=
471 after->insert(arg->getAliasClassesObject(), AliasClassSet(ac));
472 } else {
473 // auto pseudoClass =
474 // cast<PseudoAliasClassAttr>(ac.getReferencedAttr());
475 // TODO: need to handle unifying implicitly de-referenced classes
476 }
477 }
478 }
479
480 propagateIfChanged(after, changed);
481}
482
483/// Returns information indicating whether the function may read or write into
484/// memory previously inaccessible in the calling context. When unknown, returns
485/// `nullopt`.
486static std::optional<LLVM::ModRefInfo>
487getFunctionInaccessibleModRef(FunctionOpInterface func) {
488 if (auto memoryAttr =
489 func->getAttrOfType<LLVM::MemoryEffectsAttr>(kLLVMMemoryAttrName))
490 return memoryAttr.getInaccessibleMem();
491 return std::nullopt;
492}
493
495 CallOpInterface call, CallControlFlowAction action,
496 const PointsToSets &before, PointsToSets *after) {
497 join(after, before);
498
499 if (action == CallControlFlowAction::ExternalCallee) {
500 // When we don't know the callee, be conservative.
501 // TODO: eventually consider an additional "points-to" analysis for indirect
502 // calls.
503 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
504 if (!symbol)
505 return propagateIfChanged(after, after->markAllPointToUnknown());
506
507 // Functions with known behavior.
508 if (symbol.getLeafReference().getValue() == "posix_memalign") {
509 // memalign deals with nested pointers and thus must be handled here
510 // memalign points to a value
511 OperandRange arguments = call.getArgOperands();
512 auto *memPtr = getOrCreateFor<AliasClassLattice>(
513 getProgramPointAfter(call), arguments[0]);
514
515 // Note that this is a "must write" kind of situation, so we can
516 // directly set the classes pointed to, rather than inserting them.
517 auto single = AliasClassLattice::single(
518 arguments[0],
519 originalClasses.getOriginalClass(arguments[0], "memalign"));
520 return propagateIfChanged(
521 after, after->setPointingToClasses(memPtr->getAliasClassesObject(),
522 single.getAliasClassesObject()));
523 }
524
525 // Analyze the callee generically.
526 // A function call may be capturing (storing) any pointers it takes into
527 // any existing pointer, including those that are not directly passed as
528 // arguments. The presence of specific attributes restricts this behavior:
529 // - a pointer argument marked nocapture is not stored anywhere;
530 // - a pointer argument marked readonly is not stored into;
531 // - a function marked memory(arg: readonly) doesn't store anything
532 // into any argument pointer;
533 // - a function marked memory(other: readonly) doesn't store anything
534 // into pointers that are non-arguments.
535 if (auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
536 call, symbol.getLeafReference())) {
537 if (auto summaryAttr = callee->getAttrOfType<ArrayAttr>(
538 EnzymeDialect::getPointerSummaryAttrName())) {
539 DenseMap<DistinctAttr, AliasClassSet> summary;
540 deserializePointsTo(summaryAttr, summary);
541 return processCallToSummarizedFunc(call, summary, after);
542 }
543
544 std::optional<LLVM::ModRefInfo> argModRef = getFunctionArgModRef(callee);
545 std::optional<LLVM::ModRefInfo> otherModRef =
547
548 SmallVector<unsigned> pointerLikeOperands;
549 for (auto &&[i, operand] : llvm::enumerate(call.getArgOperands())) {
550 if (isPointerLike(operand.getType()))
551 pointerLikeOperands.push_back(i);
552 }
553
554 // Precompute the set of alias classes the function may capture.
555 // TODO: consider a more advanced lattice that can encode "may point to
556 // any class _except_ the given classes"; this is mathematically possible
557 // but needs careful programmatic encoding.
558 AliasClassSet functionMayCapture = AliasClassSet::getUndefined();
559 bool funcMayReadOther = modRefMayRef(otherModRef);
560 unsigned numArguments = callee.getNumArguments();
561 if (funcMayReadOther) {
562 // If a function may read from other, it may be storing pointers from
563 // unknown alias sets into any writable pointer.
564 (void)functionMayCapture.markUnknown();
565 } else {
566 for (unsigned pointerAsData : pointerLikeOperands) {
567 // If not captured, it cannot be stored in anything.
568 if ((pointerAsData < numArguments &&
569 !!callee.getArgAttr(pointerAsData,
570 LLVM::LLVMDialect::getNoCaptureAttrName())))
571 continue;
572
573 const auto *srcClasses = getOrCreateFor<AliasClassLattice>(
574 getProgramPointAfter(call), call.getArgOperands()[pointerAsData]);
575 (void)functionMayCapture.join(srcClasses->getAliasClassesObject());
576 }
577 }
578
579 // For each alias class the function may write to, indicate potentially
580 // stored classes. Keep the set of writable alias classes for future.
581 AliasClassSet writableClasses = AliasClassSet::getUndefined();
582 AliasClassSet nonWritableOperandClasses = AliasClassSet::getUndefined();
583 ChangeResult changed = ChangeResult::NoChange;
584 for (unsigned pointerOperand : pointerLikeOperands) {
585 auto *destClasses = getOrCreateFor<AliasClassLattice>(
586 getProgramPointAfter(call), call.getArgOperands()[pointerOperand]);
587
588 // If the argument cannot be stored into, just preserve it as is.
589 if (!mayWriteArg(callee, pointerOperand, argModRef)) {
590 (void)nonWritableOperandClasses.join(
591 destClasses->getAliasClassesObject());
592 continue;
593 }
594 (void)writableClasses.join(destClasses->getAliasClassesObject());
595
596 // If the destination class is unknown, mark all known classes
597 // pessimistic (alias classes that have not beed analyzed and thus are
598 // absent from pointsTo are treated as "undefined" at this point).
599 if (destClasses->isUnknown()) {
600 (void)writableClasses.markUnknown();
601 changed |= after->markAllPointToUnknown();
602 break;
603 }
604
605 if (destClasses->isUndefined())
606 continue;
607
608 // Otherwise, indicate that a pointer that belongs to any of the
609 // classes captured by this function may be stored into the
610 // destination class.
611 changed |= destClasses->getAliasClassesObject().foreachElement(
612 [&](DistinctAttr dest, AliasClassSet::State) {
613 return after->insert(dest, functionMayCapture);
614 });
615 }
616
617 // If the function may write to "other", that is any potential other
618 // pointer, record that.
619 if (modRefMayMod(otherModRef)) {
620 // Classes that have been analyzed, and therefore present in the `after`
621 // lattice after joining it with `before` are marked as pointing to
622 // "unknown", except the classes that are associated with operands for
623 // which we have more specific information. Classes that haven't been
624 // analyzed, and therefore absent from the `after` lattice, are left
625 // unmodified and thus assumed to be "undefined". This makes this
626 // transfer function monotonic as opposed to marking the latter classes
627 // as "unknown" eagerly, which would require rolling that marking back.
628 changed |= after->markAllExceptPointToUnknown(writableClasses);
629 }
630
631 // Pointer-typed results may be pointing to any other pointer. The
632 // presence of attributes restricts this behavior:
633 // - If the function is marked memory(...) so that it doesn't read from
634 // "other" memory, the return values may be pointing only to
635 // same alias classes as arguments + arguments themselves + a new
636 // alias class for a potential allocation.
637 // - Additionally, if any of the arguments are annotated as writeonly or
638 // readnone, the results should not point to alias classes those
639 // arguments are pointing to.
640 // - Additionally, if any of the arguments are annotated as nocapture,
641 // the results should not point to those arguments themselves.
642 // - If the function is marked as not reading from arguments, the
643 // results should not point to any alias classes pointed to by the
644 // arguments.
645 for (OpResult result : call->getResults()) {
646 if (!isPointerLike(result.getType()))
647 continue;
648
649 // Result alias classes may contain operand alias classes because
650 // results may alias with those operands. However, if the operands are
651 // not writable, they cannot be updated to point to other classes
652 // even though results can be. To handle this, only update the alias
653 // classes associated with the results that are not also associated
654 // with non-writable operands.
655 //
656 // This logic is a bit more conservative than the theoretical optimum to
657 // ensure monotonicity of the transfer function: if additional alias
658 // classes are discovered for non-writable operands at a later stage
659 // after these classes have already been associated with the result and
660 // marked as potentially pointing to some other classes, this marking
661 // is *not* rolled back. Since points-to-pointer analysis is a may-
662 // analysis, this is not problematic.
663 const auto *destClasses = getOrCreateFor<AliasClassLattice>(
664 getProgramPointAfter(call), result);
665 AliasClassSet resultWithoutNonWritableOperands =
667 if (destClasses->isUnknown() || nonWritableOperandClasses.isUnknown()) {
668 (void)resultWithoutNonWritableOperands.markUnknown();
669 } else if (!destClasses->isUndefined() &&
670 !nonWritableOperandClasses.isUndefined()) {
671 DenseSet<DistinctAttr> nonOperandClasses =
672 llvm::set_difference(destClasses->getAliasClasses(),
673 nonWritableOperandClasses.getElements());
674 (void)resultWithoutNonWritableOperands.insert(nonOperandClasses);
675 } else {
676 (void)resultWithoutNonWritableOperands.join(
677 destClasses->getAliasClassesObject());
678 }
679
680 // If reading from other memory, the results may point to anything.
681 if (funcMayReadOther) {
682 propagateIfChanged(after, after->markPointToUnknown(
683 resultWithoutNonWritableOperands));
684 continue;
685 }
686
687 for (unsigned operandNo : pointerLikeOperands) {
688 const auto *srcClasses = getOrCreateFor<AliasClassLattice>(
689 getProgramPointAfter(call), call.getArgOperands()[operandNo]);
690 if (mayReadArg(callee, operandNo, argModRef)) {
691 changed |= after->addSetsFrom(resultWithoutNonWritableOperands,
692 srcClasses->getAliasClassesObject());
693 }
694
695 bool isNoCapture =
696 (operandNo < numArguments &&
697 !!callee.getArgAttr(operandNo,
698 LLVM::LLVMDialect::getNoCaptureAttrName()));
699 if (isNoCapture)
700 continue;
701 changed |= after->insert(resultWithoutNonWritableOperands,
702 srcClasses->getAliasClassesObject());
703 }
704 }
705 return propagateIfChanged(after, changed);
706 }
707
708 // Don't know how to handle, record pessimistic fixpoint.
709 return propagateIfChanged(after, after->markAllPointToUnknown());
710 }
711}
712
713// The default initialization (empty map of empty sets) is correct.
715
716//===----------------------------------------------------------------------===//
717// AliasClassLattice
718//===----------------------------------------------------------------------===//
719
720void enzyme::AliasClassLattice::print(raw_ostream &os) const {
721 if (elements.isUnknown()) {
722 os << "Unknown AC";
723 } else if (elements.isUndefined()) {
724 os << "Undefined AC";
725 } else {
726 os << "size: " << elements.getElements().size() << ":\n";
727 for (auto aliasClass : elements.getElements()) {
728 os << " " << aliasClass << "\n";
729 }
730 }
731}
732
733AliasResult
735 const auto *rhs = reinterpret_cast<const AliasClassLattice *>(&other);
736
737 assert(!isUndefined() && !rhs->isUndefined() && "incomplete alias analysis");
738
739 if (getAnchor() == rhs->getAnchor())
740 return AliasResult::MustAlias;
741
742 if (elements.isUnknown() || rhs->elements.isUnknown())
743 return AliasResult::MayAlias;
744
745 size_t overlap =
746 llvm::count_if(elements.getElements(), [rhs](DistinctAttr aliasClass) {
747 return rhs->elements.getElements().contains(aliasClass);
748 });
749
750 if (overlap == 0)
751 return AliasResult::NoAlias;
752
753 // Due to the conservative nature of propagation from all operands to all
754 // results, we can't actually assume that exactly identical alias classes will
755 // lead to a "must alias" result.
756
757 // if (overlap == aliasClasses.size() &&
758 // overlap == rhs->aliasClasses.size())
759 // return AliasResult::MustAlias;
760
761 return AliasResult::MayAlias;
762}
763
764ChangeResult
766 // Set union of the alias classes
767 const auto *otherAliasClass = static_cast<const AliasClassLattice *>(&other);
768 return elements.join(otherAliasClass->elements);
769}
770
771//===----------------------------------------------------------------------===//
772// AliasAnalysis
773//===----------------------------------------------------------------------===//
774
776 if (auto arg = dyn_cast<BlockArgument>(lattice->getAnchor())) {
777 if (auto funcOp =
778 dyn_cast<FunctionOpInterface>(arg.getOwner()->getParentOp())) {
779 // TODO: Not safe in general, integers can be a result of ptrtoint. We
780 // need a type analysis here I guess?
781 if (isPointerLike(arg.getType())) {
782 if (relative ||
783 funcOp.getArgAttr(arg.getArgNumber(),
784 LLVM::LLVMDialect::getNoAliasAttrName())) {
785 // Create a distinct attribute for each function argument. This does
786 // _not_ mean assuming arguments do not alias, merely that we defer
787 // reasoning about arguments aliasing each other until analyzing
788 // callers. These distinct attributes may be unified (copied over?)
789 // depending on the calling contexts of this function.
790
791 Attribute debugLabel = funcOp.getArgAttrOfType<StringAttr>(
792 arg.getArgNumber(), "enzyme.tag");
793 if (relative) {
794 debugLabel =
795 PseudoAliasClassAttr::get(FlatSymbolRefAttr::get(funcOp),
796 arg.getArgNumber(), /*depth=*/0);
797 }
798 DistinctAttr argClass = originalClasses.getOriginalClass(
799 lattice->getAnchor(), debugLabel);
800 return propagateIfChanged(lattice,
802 lattice->getAnchor(), argClass)));
803 } else {
804 return propagateIfChanged(lattice,
806 lattice->getAnchor(), entryClass)));
807 }
808 }
809 }
810 }
811 assert(lattice->isUndefined());
812 // The default state is "undefined", no need to explicitly (re)set it.
813}
814
815/// Returns `true` if the alias transfer function of the operation is fully
816/// described by its memory effects.
817//
818// We could have an operation that has side effects and loads a pointer from
819// another pointer, but also has another result that aliases the operand, which
820// would need additional processing.
821//
822// TODO: turn this into an interface.
824 if (auto call = dyn_cast<CallOpInterface>(op)) {
825 if (auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee())) {
826 if (symbol.getLeafReference().getValue() == "malloc") {
827 return true;
828 }
829 }
830 }
831 return isa<memref::LoadOp, memref::StoreOp, affine::AffineLoadOp,
832 affine::AffineStoreOp, LLVM::LoadOp, LLVM::StoreOp, enzyme::PushOp,
833 enzyme::PopOp>(op);
834}
835
836void enzyme::AliasAnalysis::transfer(
837 Operation *op, ArrayRef<MemoryEffects::EffectInstance> effects,
838 ArrayRef<const AliasClassLattice *> operands,
839 ArrayRef<AliasClassLattice *> results) {
840 bool globalRead = false;
841 for (const auto &effect : effects) {
842 // If the effect is global read, record that.
843 Value value = effect.getValue();
844 if (!value) {
845 globalRead |= isa<MemoryEffects::Read>(effect.getEffect());
846 continue;
847 }
848
849 if (isa<MemoryEffects::Allocate>(effect.getEffect())) {
850 // Mark the result of the allocation as a fresh memory location.
851 for (AliasClassLattice *result : results) {
852 if (result->getAnchor() == value) {
853 std::string debugLabel;
854 llvm::raw_string_ostream sstream(debugLabel);
855 if (relative)
856 sstream << "fresh-";
857
858 if (op->hasAttr("tag")) {
859 if (auto stringTag = dyn_cast<StringAttr>(op->getAttr("tag"))) {
860 sstream << stringTag.getValue();
861 } else {
862 op->getAttr("tag").print(sstream);
863 }
864 }
865 auto fresh = AliasClassLattice::single(
866 result->getAnchor(), originalClasses.getOriginalClass(
867 result->getAnchor(), debugLabel));
868 propagateIfChanged(result, result->join(fresh));
869 }
870 }
871 } else if (isa<MemoryEffects::Read>(effect.getEffect())) {
872 auto *pointsToSets = getOrCreateFor<PointsToSets>(
873 getProgramPointAfter(op), getProgramPointAfter(op));
874 AliasClassLattice *latticeElement = getLatticeElement(value);
875 if (latticeElement->isUnknown()) {
876 for (AliasClassLattice *result : results) {
877 propagateIfChanged(result, result->markUnknown());
878 }
879 } else if (latticeElement->isUndefined()) {
880 // Do nothing unless we know something about the value.
881 } else {
882 for (auto srcClass : latticeElement->getAliasClasses()) {
883 const auto &srcPointsTo = pointsToSets->getPointsTo(srcClass);
884 for (AliasClassLattice *result : results) {
885 if (!isPointerLike(result->getAnchor().getType()))
886 continue;
887
888 // TODO: consider some sort of "point join" or better insert that
889 // doesn't require a conditional here.
890 if (srcPointsTo.isUnknown()) {
891 propagateIfChanged(result, result->markUnknown());
892 } else if (srcPointsTo.isUndefined()) {
893 if (relative)
894 createImplicitArgDereference(op, latticeElement, srcClass,
895 result);
896 } else {
897 propagateIfChanged(result,
898 result->insert(srcPointsTo.getElements()));
899 }
900 }
901 }
902 }
903 }
904 }
905
906 // If there was a global read effect, the operation may be reading from any
907 // pointer so we cannot say what the results are pointing to. Can safely exit
908 // here because all results are now in the fixpoint state.
909 if (globalRead) {
910 for (auto *resultLattice : results)
911 propagateIfChanged(resultLattice, resultLattice->markUnknown());
912 return;
913 }
914
915 // If it was enough to reason about effects, exit here.
916 if (!effects.empty() && isAliasTransferFullyDescribedByMemoryEffects(op))
917 return;
918
919 // Conservatively assume all results alias all operands.
920 for (AliasClassLattice *resultLattice : results) {
921 // TODO: Setting this flag to true will assume non-pointers don't alias,
922 // which is not true in general but can result in improved analysis speed.
923 // We need a type analysis for full correctness.
924 constexpr bool pruneNonPointers = false;
925 if (pruneNonPointers &&
926 !isPointerLike(resultLattice->getAnchor().getType()))
927 continue;
928
929 ChangeResult r = ChangeResult::NoChange;
930 for (const AliasClassLattice *operandLattice : operands)
931 r |= resultLattice->join(*operandLattice);
932 propagateIfChanged(resultLattice, r);
933 }
934}
935
936void enzyme::AliasAnalysis::createImplicitArgDereference(
937 Operation *op, AliasClassLattice *source, DistinctAttr srcClass,
938 AliasClassLattice *result) {
939 assert(relative && "only valid to create implicit argument dereferences when "
940 "operating in relative mode");
941
942 Value readResult = result->getAnchor();
943 auto parent = op->getParentOfType<FunctionOpInterface>();
944 assert(parent && "failed to find function parent");
945 auto *entryPointsToSets = getOrCreateFor<PointsToSets>(
946 getProgramPointAfter(op),
947 getProgramPointAfter(&parent.getCallableRegion()->front()));
948 if (!entryPointsToSets->getPointsTo(srcClass).isUndefined()) {
949 // Only create the pseudo class if another load hasn't already created the
950 // implicitly dereferenced pseudo class.
951 return;
952 }
953 if (auto pseudoClass = dyn_cast_if_present<PseudoAliasClassAttr>(
954 srcClass.getReferencedAttr())) {
955 auto pseudoDeref = PseudoAliasClassAttr::get(pseudoClass.getFunction(),
956 pseudoClass.getArgNumber(),
957 pseudoClass.getDepth() + 1);
958 DistinctAttr derefClass =
959 originalClasses.getOriginalClass(readResult, pseudoDeref);
960 propagateIfChanged(result, result->join(AliasClassLattice::single(
961 readResult, derefClass)));
962 // The read source points to the dereferenced class
963 auto *pointsToState = getOrCreate<PointsToSets>(
964 getProgramPointBefore(&parent.getCallableRegion()->front()));
965 propagateIfChanged(pointsToState,
966 pointsToState->insert(source->getAliasClassesObject(),
967 AliasClassSet(derefClass)));
968 }
969}
970
972 CallOpInterface call,
973 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
974 auto rng = call.getArgOperands();
975 for (auto it = rng.begin(); it != rng.end(); it++) {
976 auto argument = *it;
977 auto argOp = it.getBase();
978 assert(argOp->get() == argument);
979 if (!isPointerLike(argument.getType()))
980 continue;
981
982 effects.emplace_back(MemoryEffects::Read::get(), argOp);
983 effects.emplace_back(MemoryEffects::Write::get(), argOp);
984 // TODO: consider having a may-free effect.
985 }
986}
987
988// TODO: Move this somewhere shared
990 CallOpInterface call,
991 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
992 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
993 if (!symbol)
994 return failure();
995
996 // Functions with known specific behavior.
997 StringRef callableName = symbol.getLeafReference().getValue();
998 if (callableName == "malloc" || callableName == "calloc" ||
999 callableName == "_Znwm") {
1000 assert(call->getNumResults() == 1);
1001 effects.push_back(MemoryEffects::EffectInstance(
1002 MemoryEffects::Allocate::get(), call->getResult(0)));
1003 return success();
1004 }
1005
1006 return failure();
1007}
1008
1010 Operation *op, ArrayRef<const AliasClassLattice *> operands,
1011 ArrayRef<AliasClassLattice *> results) {
1012
1013 // If we don't have memory effect information, don't assume anything about
1014 // values.
1015 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
1016 if (!memory) {
1017 for (OpResult result : op->getResults()) {
1018 if (!isPointerLike(result.getType()))
1019 continue;
1020
1021 (void)results[result.getResultNumber()]->markUnknown();
1022 }
1023 return success();
1024 }
1025
1026 SmallVector<MemoryEffects::EffectInstance> effects;
1027 memory.getEffects(effects);
1028 transfer(op, effects, operands, results);
1029 if (auto view = dyn_cast<OffsetSizeAndStrideOpInterface>(op)) {
1030 // TODO: special handling for offset size and stride op interface to prove
1031 // that non-overlapping subviews of the same buffer don't alias could be a
1032 // promising extension.
1033 }
1034 return success();
1035}
1036
1037static void
1038deserializeAliasSummary(ArrayAttr summary,
1039 SmallVectorImpl<enzyme::AliasClassSet> &out) {
1040 for (Attribute element : summary) {
1041 if (auto strAttr = dyn_cast<StringAttr>(element)) {
1042 if (strAttr.getValue() == enzyme::unknownSetString) {
1043 out.push_back(enzyme::AliasClassSet::getUnknown());
1044 } else {
1045 assert(strAttr.getValue() == enzyme::undefinedSetString);
1046 out.push_back(enzyme::AliasClassSet::getUndefined());
1047 }
1048 } else {
1049 enzyme::AliasClassSet aliasClasses;
1050 for (DistinctAttr aliasClass :
1051 cast<ArrayAttr>(element).getAsRange<DistinctAttr>()) {
1052 (void)aliasClasses.insert({aliasClass});
1053 }
1054 out.push_back(aliasClasses);
1055 }
1056 }
1057}
1058
1060 CallOpInterface call, ArrayRef<const AliasClassLattice *> operands,
1061 ArrayRef<AliasClassLattice *> results) {
1062 // First, try effect-based reasoning for known functions.
1063 SmallVector<MemoryEffects::EffectInstance> effects;
1064 if (succeeded(getEffectsForExternalCall(call, effects)))
1065 return transfer(call, effects, operands, results);
1066
1067 auto markResultsUnknown = [&] {
1068 for (AliasClassLattice *resultLattice : results)
1069 propagateIfChanged(resultLattice, resultLattice->markUnknown());
1070 };
1071
1072 // If failed, try using function attributes. If results are marked noalias,
1073 // they correspond to fresh allocations. Otherwise, they may alias anything.
1074 // Even if a function is marked as not reading from memory or arguments, it
1075 // may still create pointers "out of the thin air", e.g., by "ptrtoint" from a
1076 // constant or an argument.
1077 // TODO: consider "ptrtoint" here, for now assuming it is covered by
1078 // inaccessible and other mem.
1079 auto symbol = dyn_cast<SymbolRefAttr>(call.getCallableForCallee());
1080 if (!symbol)
1081 return markResultsUnknown();
1082 auto callee = SymbolTable::lookupNearestSymbolFrom<FunctionOpInterface>(
1083 call, symbol.getLeafReference());
1084 if (!callee)
1085 return markResultsUnknown();
1086
1087 if (auto aliasSummaryAttr = callee->getAttrOfType<ArrayAttr>(
1088 EnzymeDialect::getAliasSummaryAttrName())) {
1089 // The summary tells us what operands may alias with what results
1090 SmallVector<AliasClassSet> aliasSummary;
1091 deserializeAliasSummary(aliasSummaryAttr, aliasSummary);
1092
1093 assert(results.size() == aliasSummary.size());
1094 for (auto &&[i, resultSummary] : llvm::enumerate(aliasSummary)) {
1095 // Would be nice to zip over results and aliasSummary, but requires
1096 // capture of structured binding (may require a newer clang version)
1097 AliasClassLattice *result = results[i];
1098 ChangeResult changed = ChangeResult::NoChange;
1099 if (resultSummary.isUndefined())
1100 continue;
1101 if (resultSummary.isUnknown())
1102 changed |= result->markUnknown();
1103 else {
1104 changed |= resultSummary.foreachElement(
1105 [&](DistinctAttr aliasClass, AliasClassSet::State state) {
1106 assert(state == AliasClassSet::State::Defined);
1107 if (auto pseudoClass = dyn_cast<PseudoAliasClassAttr>(
1108 aliasClass.getReferencedAttr())) {
1109 assert(
1110 pseudoClass.getDepth() == 0 &&
1111 "sparse alias summaries for depth > 0 not yet implemented");
1112 return result->join(*operands[pseudoClass.getArgNumber()]);
1113 } else {
1114 return result->insert({aliasClass});
1115 }
1116 });
1117 }
1118 propagateIfChanged(result, changed);
1119 }
1120 return;
1121 }
1122
1123 // Collect alias classes that can be read through the arguments.
1124 std::optional<LLVM::ModRefInfo> argModRef = getFunctionArgModRef(callee);
1125 std::optional<LLVM::ModRefInfo> otherModRef = getFunctionOtherModRef(callee);
1126 std::optional<LLVM::ModRefInfo> inaccessibleModRef =
1128 auto operandAliasClasses = AliasClassSet::getEmpty();
1129 for (auto [operandNo, operand] : llvm::enumerate(call.getArgOperands())) {
1130 if (!isPointerLike(operand.getType()))
1131 continue;
1132
1133 const AliasClassLattice *srcClasses = operands[operandNo];
1134 (void)operandAliasClasses.join(srcClasses->getAliasClassesObject());
1135
1136 if (!mayReadArg(callee, operandNo, argModRef))
1137 continue;
1138
1139 // If can read from argument, collect the alias classes that can this
1140 // argument may be pointing to.
1141 const auto *pointsToLattice = getOrCreateFor<PointsToSets>(
1142 getProgramPointAfter(call), getProgramPointAfter(call));
1143 (void)srcClasses->getAliasClassesObject().foreachElement(
1144 [&](DistinctAttr srcClass, AliasClassSet::State state) {
1145 // Nothing to do in top/bottom case. In the top case, we have already
1146 // set `operandAliasClasses` to top above.
1147 if (srcClass == nullptr)
1148 return ChangeResult::NoChange;
1149 (void)operandAliasClasses.join(
1150 pointsToLattice->getPointsTo(srcClass));
1151 return ChangeResult::NoChange;
1152 });
1153 }
1154
1155 auto debugLabel = call->getAttrOfType<StringAttr>("tag");
1156 DistinctAttr commonResultAttr = nullptr;
1157
1158 // Collect all results that are not marked noalias so we can put them in a
1159 // common alias group.
1160 SmallVector<Value> aliasGroupResults;
1161 for (OpResult result : call->getResults()) {
1162 if (!callee.getResultAttr(result.getResultNumber(),
1163 LLVM::LLVMDialect::getNoAliasAttrName()))
1164 aliasGroupResults.push_back(result);
1165 }
1166
1167 for (OpResult result : call->getResults()) {
1168 AliasClassLattice *resultLattice = results[result.getResultNumber()];
1169 if (!llvm::is_contained(aliasGroupResults, result)) {
1170 Attribute individualDebugLabel =
1171 debugLabel
1172 ? StringAttr::get(debugLabel.getContext(),
1173 debugLabel.getValue().str() +
1174 std::to_string(result.getResultNumber()))
1175 : nullptr;
1176 auto individualAlloc = AliasClassLattice::single(
1177 resultLattice->getAnchor(),
1178 originalClasses.getOriginalClass(resultLattice->getAnchor(),
1179 individualDebugLabel));
1180 propagateIfChanged(resultLattice, resultLattice->join(individualAlloc));
1181 } else if (!modRefMayRef(otherModRef) &&
1182 !modRefMayRef(inaccessibleModRef)) {
1183 // Put results that are not marked as noalias into one common group.
1184 if (!commonResultAttr) {
1185 std::string label = !debugLabel
1186 ? "func-result-common"
1187 : debugLabel.getValue().str() + "-common";
1188 commonResultAttr =
1189 originalClasses.getSameOriginalClass(aliasGroupResults, label);
1190 }
1191 AliasClassSet commonClass(commonResultAttr);
1192 ChangeResult changed = resultLattice->join(AliasClassLattice(
1193 resultLattice->getAnchor(), std::move(commonClass)));
1194
1195 // If the function is known not to read other (or inaccessible mem), its
1196 // results may only alias what we know it can read, e.g. other arguments
1197 // or anything stored in those arguments.
1198 // FIXME: note the explicit copy, we need to simplify the relation between
1199 // AliasClassSet and AliasClassLattice.
1200 changed |= resultLattice->join(AliasClassLattice(
1201 resultLattice->getAnchor(), AliasClassSet(operandAliasClasses)));
1202 propagateIfChanged(resultLattice, changed);
1203 } else {
1204 propagateIfChanged(resultLattice, resultLattice->markUnknown());
1205 }
1206 }
1207}
1208
1210 CallableOpInterface funcOp, function_ref<void(Operation *op)> annotate) {
1211 DataFlowSolver solver(DataFlowConfig().setInterprocedural(false));
1212 dataflow::loadBaselineAnalyses(solver);
1213 solver.load<enzyme::AliasAnalysis>(funcOp.getContext(), /*relative=*/false);
1214 solver.load<enzyme::PointsToPointerAnalysis>();
1215 if (failed(solver.initializeAndRun(funcOp))) {
1216 assert(false && "dataflow analysis failed");
1217 return;
1218 }
1219
1220 // Determine all alias classes that were modified
1221 AliasClassLattice modified(
1222 nullptr, DistinctAttr::create(UnitAttr::get(funcOp.getContext())));
1223 funcOp.walk([&](Operation *op) {
1224 auto memory = dyn_cast<MemoryEffectOpInterface>(op);
1225 // If we can't reason about memory effects, conservatively assume that any
1226 // pointer could be modified.
1227 if (!memory) {
1228 if (isMemoryEffectFree(op) ||
1229 isa<FunctionOpInterface, RegionBranchOpInterface>(op) || isNoOp(op)) {
1230 return WalkResult::advance();
1231 }
1232 (void)modified.markUnknown();
1233 return WalkResult::interrupt();
1234 }
1235
1236 SmallVector<MemoryEffects::EffectInstance> effects;
1237 memory.getEffects(effects);
1238 for (const auto &effect : effects) {
1239 if (isa<MemoryEffects::Write>(effect.getEffect())) {
1240 Value val = effect.getValue();
1241 if (val) {
1242 (void)modified.join(*solver.lookupState<AliasClassLattice>(val));
1243 } else {
1244 (void)modified.markUnknown();
1245 return WalkResult::interrupt();
1246 }
1247 }
1248 }
1249 return WalkResult::advance();
1250 });
1251
1252 funcOp.walk([&](MemoryEffectOpInterface memory) {
1253 if (!hasSingleEffect<MemoryEffects::Read>(memory)) {
1254 return;
1255 }
1256 SmallVector<MemoryEffects::EffectInstance> effects;
1257 memory.getEffects(effects);
1258 assert(effects.size() == 1 &&
1259 isa<MemoryEffects::Read>(effects.front().getEffect()));
1260 Value ptr = effects.front().getValue();
1261 auto *ptrClass = solver.lookupState<AliasClassLattice>(ptr);
1262 // TODO: in split mode, this check will no longer be sufficient because the
1263 // caller may modify memory between function invocations even if the memory
1264 // is not modified within the function body.
1265 if (ptrClass->alias(modified).isNo()) {
1266 annotate(memory);
1267 }
1268 });
1269}
static void deserializePointsTo(ArrayAttr summaryAttr, DenseMap< DistinctAttr, enzyme::ValueOriginSet > &summaryMap)
std::optional< Value > getCopySource(Operation *op)
static bool isPointerLike(Type type)
static constexpr llvm::StringLiteral kLLVMMemoryAttrName
static std::optional< LLVM::ModRefInfo > getFunctionInaccessibleModRef(FunctionOpInterface func)
Returns information indicating whether the function may read or write into memory previously inaccess...
static void deserializePointsTo(ArrayAttr summaryAttr, DenseMap< DistinctAttr, enzyme::AliasClassSet > &summaryMap)
static bool mayReadArg(FunctionOpInterface callee, unsigned argNo, std::optional< LLVM::ModRefInfo > argMemMRI)
static std::optional< LLVM::ModRefInfo > getFunctionArgModRef(FunctionOpInterface func)
Returns information indicating whether the function may read or write into the memory pointed to by i...
LogicalResult getEffectsForExternalCall(CallOpInterface call, SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
static bool mayWriteArg(FunctionOpInterface callee, unsigned argNo, std::optional< LLVM::ModRefInfo > argMemMRI)
static void deserializeAliasSummary(ArrayAttr summary, SmallVectorImpl< enzyme::AliasClassSet > &out)
static bool isAliasTransferFullyDescribedByMemoryEffects(Operation *op)
Returns true if the alias transfer function of the operation is fully described by its memory effects...
static bool isNoOp(Operation *op)
static bool isMustStore(Operation *op, Value pointer)
static bool modRefMayMod(std::optional< LLVM::ModRefInfo > modRef)
static ChangeResult mergeSets(DenseSet< T > &dest, const DenseSet< T > &src)
static std::optional< LLVM::ModRefInfo > getFunctionOtherModRef(FunctionOpInterface func)
Returns information indicating whether the function may read or write into the memory other than that...
void populateConservativeCallEffects(CallOpInterface call, SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
static bool modRefMayRef(std::optional< LLVM::ModRefInfo > modRef)
std::optional< Value > getStored(Operation *op)
static bool isNoCapture(const llvm::CallBase *call, size_t idx)
Definition Utils.h:2109
This analysis implements interprocedural alias analysis.
LogicalResult visitOperation(Operation *op, ArrayRef< const AliasClassLattice * > operands, ArrayRef< AliasClassLattice * > results) override
void visitExternalCall(CallOpInterface call, ArrayRef< const AliasClassLattice * > operands, ArrayRef< AliasClassLattice * > results) override
void setToEntryState(AliasClassLattice *lattice) override
const AliasClassSet & getAliasClassesObject() const
void print(raw_ostream &os) const override
ChangeResult join(const AbstractSparseLattice &other) override
static AliasClassLattice single(Value point, DistinctAttr value)
::mlir::AliasResult alias(const AbstractSparseLattice &other) const
DenseMap< DistinctAttr, SetLattice< DistinctAttr > > map
void processCallToSummarizedFunc(CallOpInterface call, const DenseMap< DistinctAttr, AliasClassSet > &summary, PointsToSets *after)
void setToEntryState(PointsToSets *lattice) override
void processCapturingStore(ProgramPoint *dependent, PointsToSets *after, Value capturedValue, Value destinationAddress, bool isMustStore=false)
LogicalResult visitOperation(Operation *op, const PointsToSets &before, PointsToSets *after) override
void visitCallControlFlowTransfer(CallOpInterface call, dataflow::CallControlFlowAction action, const PointsToSets &before, PointsToSets *after) override
ChangeResult insert(const AliasClassSet &destClasses, const AliasClassSet &values)
Mark the pointer stored in dest as possibly pointing to any of values, in addition to the values it m...
ChangeResult markAllExceptPointToUnknown(const AliasClassSet &destClasses)
Mark all alias classes except the given ones to point to the "unknown" alias set.
ChangeResult setPointingToClasses(const AliasClassSet &destClasses, const AliasClassSet &values)
Mark the pointer stored in dest as possibly pointing to any of values, instead of the values it may b...
ChangeResult markAllPointToUnknown()
Mark the entire data structure as "unknown", that is, any pointer may be containing any other pointer...
ChangeResult markPointToUnknown(const AliasClassSet &destClasses)
Mark dest as pointing to "unknown" alias set, that is, any possible other pointer.
ChangeResult setPointingToEmpty(const AliasClassSet &destClasses)
void print(raw_ostream &os) const override
ChangeResult addSetsFrom(const AliasClassSet &destClasses, const AliasClassSet &srcClasses)
For every alias class in dest, record that it may additionally be pointing to the same as the classes...
DenseSet< ValueT > & getElements()
ChangeResult insert(const DenseSet< ValueT > &newElements)
ChangeResult foreachElement(function_ref< ChangeResult(ValueT, State)> callback) const
ChangeResult join(const SetLattice< ValueT > &other)
static const SetLattice< DistinctAttr > & getEmpty()
static const SetLattice< DistinctAttr > & getUndefined()
static const SetLattice< DistinctAttr > & getUnknown()
ChangeResult insert(const DenseSet< ValueT > &newElements)
constexpr llvm::StringLiteral unknownSetString
SetLattice< DistinctAttr > AliasClassSet
A set of alias class identifiers to be treated as a single union.
void markReadOnlyLoads(CallableOpInterface funcOp, function_ref< void(Operation *)> annotate)
constexpr llvm::StringLiteral undefinedSetString