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