clang 24.0.0git
CIRBasicAliasAnalysis.cpp
Go to the documentation of this file.
1//===- CIRBasicAliasAnalysis.cpp - Basic CIR Alias Analysis ---------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "mlir/Interfaces/SideEffectInterfaces.h"
13#include "llvm/Support/DebugLog.h"
14#include "llvm/Support/MathExtras.h"
15
16#include <limits>
17
18#define DEBUG_TYPE "cir-basic-alias-analysis"
19
20using namespace llvm;
21using namespace cir;
22
23//===----------------------------------------------------------------------===//
24// Helpers
25//===----------------------------------------------------------------------===//
26
27static constexpr unsigned MaxLookupDepth = 6;
28
29/// Return the size in bytes of \p type, or std::nullopt when that size isn't
30/// statically known (void, function types, incomplete records, ...).
31static std::optional<int64_t>
32getTypeSizeInBytes(mlir::Type type, const mlir::DataLayout &dataLayout) {
33 if (!cir::isSized(type))
34 return std::nullopt;
35
36 llvm::TypeSize size = dataLayout.getTypeSize(type);
37 if (size.isScalable())
38 return std::nullopt;
39 return size.getFixedValue();
40}
41
42/// If \p val is a constant integer that fits in an int64_t, return its value.
43/// The constant is interpreted according to the signedness of its type.
44static std::optional<int64_t> getConstantIndex(mlir::Value val) {
45 auto constOp =
46 mlir::dyn_cast_if_present<cir::ConstantOp>(val.getDefiningOp());
47 if (!constOp)
48 return std::nullopt;
49
50 auto intAttr = mlir::dyn_cast<cir::IntAttr>(constOp.getValue());
51 if (!intAttr)
52 return std::nullopt;
53
54 const APInt &value = intAttr.getValue();
55 if (intAttr.isSigned())
56 return value.trySExtValue();
57 return value.tryZExtValue();
58}
59
60/// Return `count * size`, or std::nullopt if either input is unknown or the
61/// product overflows.
62static std::optional<int64_t> scaleOffset(std::optional<int64_t> count,
63 std::optional<int64_t> size) {
64 if (!count || !size)
65 return std::nullopt;
66 auto [product, overflow] = MulOverflow(*count, *size);
67 if (overflow)
68 return std::nullopt;
69 return product;
70}
71
72/// Add \p delta bytes to \p offset, making the offset unknown if \p delta is
73/// unknown or if the sum overflows.
74static void addToOffset(std::optional<int64_t> &offset,
75 std::optional<int64_t> delta) {
76 if (!offset)
77 return;
78 if (!delta) {
79 offset.reset();
80 return;
81 }
82 auto [sum, overflow] = AddOverflow(*offset, *delta);
83 if (overflow)
84 offset.reset();
85 else
86 offset = sum;
87}
88
89namespace {
90/// A pointer expressed as a byte offset into the object it points into.
91struct PointerOffset {
92 /// The value the pointer was traced back to. This is an allocation, a block
93 /// argument, or the result of an operation this analysis cannot look through.
94 mlir::Value base;
95
96 /// Byte offset of the pointer from the start of `base`. The offset can be
97 /// negative. If this is std::nullopt, the offset is not a compile-time
98 /// constant.
99 std::optional<int64_t> offset;
100};
101} // namespace
102
103/// Trace \p val back to the object it points into, accumulating the byte offset
104/// of \p val from the start of that object.
105///
106/// The walk stops at a block argument, at an allocation, or at any operation
107/// whose result cannot be described as an offset from one of its operands. The
108/// returned base and offset always describe \p val, even when the walk stops
109/// early because the depth limit was reached.
110///
111/// Operations contributing an offset that isn't a compile-time constant (a
112/// dynamic cir.ptr_stride index, for example) are still traced through, leaving
113/// the offset unknown. Knowing which object a pointer points into is useful
114/// even when the offset within that object is not known.
115static PointerOffset decomposePointer(mlir::Value val,
116 const mlir::DataLayout &dataLayout) {
117 LDBG() << "Decomposing pointer: " << val;
118
119 std::optional<int64_t> offset = 0;
120
121 for (unsigned depth = 0; depth < MaxLookupDepth; ++depth) {
122 mlir::Operation *defOp = val.getDefiningOp();
123 if (!defOp) {
124 LDBG() << "No defining operation, stopping";
125 break; // Block argument (e.g. function parameter) — stop here.
126 }
127
128 // Bitcasts and address-space casts don't change the address, and
129 // array_to_ptrdecay produces a pointer to the first element of the array.
130 if (auto castOp = mlir::dyn_cast<cir::CastOp>(defOp)) {
131 if (castOp.isAllocaPreservingCast() ||
132 castOp.getKind() == cir::CastKind::array_to_ptrdecay) {
133 LDBG() << "Walking past cast operation";
134 val = castOp.getSrc();
135 continue;
136 }
137 LDBG() << "Opaque cast operation, stopping";
138 break;
139 }
140
141 // A stride moves the pointer by `stride * sizeof(pointee)` bytes.
142 if (auto strideOp = mlir::dyn_cast<cir::PtrStrideOp>(defOp)) {
143 LDBG() << "Walking past PtrStrideOp";
144 addToOffset(offset,
145 scaleOffset(getConstantIndex(strideOp.getStride()),
146 getTypeSizeInBytes(strideOp.getElementType(),
147 dataLayout)));
148 val = strideOp.getBase();
149 continue;
150 }
151
152 // A record member sits at a fixed offset given by the record layout.
153 if (auto memberOp = mlir::dyn_cast<cir::GetMemberOp>(defOp)) {
154 LDBG() << "Walking past GetMemberOp";
155 auto recordTy =
156 mlir::cast<cir::RecordType>(memberOp.getAddrTy().getPointee());
157 std::optional<int64_t> memberOffset;
158 if (!recordTy.isIncomplete())
159 memberOffset =
160 recordTy.getElementOffset(dataLayout, memberOp.getIndex());
161 addToOffset(offset, memberOffset);
162 val = memberOp.getAddr();
163 continue;
164 }
165
166 // An array element sits at `index * sizeof(element)` bytes into the array.
167 if (auto elementOp = mlir::dyn_cast<cir::GetElementOp>(defOp)) {
168 LDBG() << "Walking past GetElementOp";
169 addToOffset(offset,
170 scaleOffset(getConstantIndex(elementOp.getIndex()),
171 getTypeSizeInBytes(elementOp.getElementType(),
172 dataLayout)));
173 val = elementOp.getBase();
174 continue;
175 }
176
177 // A base class subobject starts the given number of bytes into the derived
178 // object. This may return null if the input is null, but accessing memory
179 // based on that null pointer would be UB, so we always assume non-null
180 // here.
181 if (auto baseOp = mlir::dyn_cast<cir::BaseClassAddrOp>(defOp)) {
182 LDBG() << "Walking past BaseClassAddrOp";
183 addToOffset(offset, baseOp.getOffset().tryZExtValue());
184 val = baseOp.getDerivedAddr();
185 continue;
186 }
187
188 // Conversely, the derived object starts that many bytes before the base
189 // subobject, so the offset is applied as a negative adjustment. This may
190 // return null if the input is null, but accessing memory based on that null
191 // pointer would be UB, so we always assume non-null here.
192 if (auto derivedOp = mlir::dyn_cast<cir::DerivedClassAddrOp>(defOp)) {
193 LDBG() << "Walking past DerivedClassAddrOp";
194 std::optional<int64_t> baseOffset = derivedOp.getOffset().tryZExtValue();
195 if (baseOffset)
196 baseOffset = -*baseOffset;
197 addToOffset(offset, baseOffset);
198 val = derivedOp.getBaseAddr();
199 continue;
200 }
201
202 // The real part of a complex value is at offset zero, the imaginary part
203 // right behind it.
204 if (auto realOp = mlir::dyn_cast<cir::ComplexRealPtrOp>(defOp)) {
205 LDBG() << "Walking past ComplexRealPtrOp";
206 val = realOp.getOperand();
207 continue;
208 }
209 if (auto imagOp = mlir::dyn_cast<cir::ComplexImagPtrOp>(defOp)) {
210 LDBG() << "Walking past ComplexImagPtrOp";
211 auto ptrTy = mlir::cast<cir::PointerType>(imagOp.getOperand().getType());
212 auto complexTy = mlir::cast<cir::ComplexType>(ptrTy.getPointee());
213 addToOffset(offset,
214 getTypeSizeInBytes(complexTy.getElementType(), dataLayout));
215 val = imagOp.getOperand();
216 continue;
217 }
218
219 LDBG() << "Unhandled operation, stopping";
220 break; // Not expressible as an offset from another pointer.
221 }
222
223 return {val, offset};
224}
225
226/// Return true if \p lhs and \p rhs are provably different objects.
227///
228/// TODO: Extend to cover global addresses, function arguments with noalias, and
229/// heap allocations.
230static bool areDistinctObjects(mlir::Value lhs, mlir::Value rhs) {
231 // Distinct cir.alloca ops allocate distinct storage.
232 return lhs != rhs &&
233 mlir::isa_and_nonnull<cir::AllocaOp>(lhs.getDefiningOp()) &&
234 mlir::isa_and_nonnull<cir::AllocaOp>(rhs.getDefiningOp());
235}
236
237//===----------------------------------------------------------------------===//
238// CIRBasicAliasAnalysis
239//===----------------------------------------------------------------------===//
240
241mlir::AliasResult CIRBasicAliasAnalysis::alias(mlir::Value lhs,
242 mlir::Value rhs) {
243 LDBG() << "Checking alias between: " << lhs << " and " << rhs;
244
245 if (lhs == rhs) {
246 LDBG() << "Trivial alias between identical values";
247 return mlir::AliasResult::MustAlias;
248 }
249
250 PointerOffset lhsPtr = decomposePointer(lhs, dataLayout);
251 PointerOffset rhsPtr = decomposePointer(rhs, dataLayout);
252
253 if (lhsPtr.base != rhsPtr.base) {
254 if (areDistinctObjects(lhsPtr.base, rhsPtr.base)) {
255 LDBG() << "No alias between pointers into distinct objects";
256 return mlir::AliasResult::NoAlias;
257 }
258 LDBG() << "Unrelated base objects, may alias";
259 return mlir::AliasResult::MayAlias;
260 }
261
262 // Both pointers point into the same object, so their offsets can be compared
263 // directly.
264 if (!lhsPtr.offset || !rhsPtr.offset) {
265 LDBG() << "Same object at an unknown offset, may alias";
266 return mlir::AliasResult::MayAlias;
267 }
268
269 // Equal offsets means both pointers start at exactly the same address, which
270 // is all MustAlias claims. How many bytes each access touches doesn't matter.
271 if (*lhsPtr.offset == *rhsPtr.offset) {
272 LDBG() << "Must alias at the same address within the same object";
273 return mlir::AliasResult::MustAlias;
274 }
275
276 // TODO: Two pointers at different offsets into the same object only overlap
277 // if the accesses are large enough to reach one another. Comparing the byte
278 // ranges the accesses cover would prove NoAlias or PartialAlias here.
279 LDBG() << "Same object at different offsets, may alias";
280 return mlir::AliasResult::MayAlias;
281}
282
283mlir::ModRefResult CIRBasicAliasAnalysis::getModRef(mlir::Operation *op,
284 mlir::Value location) {
285 LDBG() << "getModRef: "
286 << mlir::OpWithFlags(op, mlir::OpPrintingFlags().skipRegions())
287 << " on location " << location;
288
289 auto effects = mlir::dyn_cast<mlir::MemoryEffectOpInterface>(op);
290 if (!effects) {
291 LDBG() << "No memory effect interface, returning ModAndRef";
292 return mlir::ModRefResult::getModAndRef();
293 }
294
296 effects.getEffects(effectList);
297
298 auto classifyEffect = [location, this](
299 const mlir::MemoryEffects::EffectInstance &effect) {
300 if (mlir::isa<mlir::MemoryEffects::Allocate>(effect.getEffect())) {
301 LDBG() << "Skipping allocate effect";
302 return mlir::ModRefResult::getNoModRef();
303 }
304
305 mlir::AliasResult aliasResult = mlir::AliasResult::MayAlias;
306 if (mlir::Value affectedLocation = effect.getValue()) {
307 LDBG() << " Checking alias between affected location "
308 << affectedLocation << " and query location " << location;
309 aliasResult = alias(affectedLocation, location);
310 LDBG() << " Alias result: " << aliasResult;
311 } else {
312 // An effect on a non-addressable resource cannot affect a
313 // pointer-based location.
314 if (!effect.getResource()->isAddressable()) {
315 LDBG() << " Effect on non-addressable resource '"
316 << effect.getResource()->getName() << "', skipping (NoAlias)";
317 aliasResult = mlir::AliasResult::NoAlias;
318 } else {
319 LDBG() << " No effect value, assuming MayAlias";
320 }
321 }
322
323 // If the affected location doesn't alias with the query location,
324 // ignore this effect.
325 if (aliasResult.isNo()) {
326 LDBG() << "No alias with affected location";
327 return mlir::ModRefResult::getNoModRef();
328 }
329
330 // TODO: Consider whether Free should be NoModRef.
331 if (mlir::isa<mlir::MemoryEffects::Free>(effect.getEffect())) {
332 LDBG() << "Skipping free effect";
333 return mlir::ModRefResult::getModAndRef();
334 }
335
336 if (mlir::isa<mlir::MemoryEffects::Write>(effect.getEffect())) {
337 LDBG() << "Write effect, adding Mod";
338 return mlir::ModRefResult::getMod();
339 }
340
341 if (mlir::isa<mlir::MemoryEffects::Read>(effect.getEffect())) {
342 LDBG() << "Read effect, adding Ref";
343 return mlir::ModRefResult::getRef();
344 }
345
346 LDBG() << "Unexpected memory effect: " << effect.getEffect();
347 return mlir::ModRefResult::getNoModRef();
348 };
349
350 return llvm::accumulate(llvm::map_range(effectList, classifyEffect),
351 mlir::ModRefResult::getNoModRef(),
352 [](mlir::ModRefResult lhs, mlir::ModRefResult rhs) {
353 return lhs.merge(rhs);
354 });
355}
static std::optional< int64_t > getConstantIndex(mlir::Value val)
If val is a constant integer that fits in an int64_t, return its value.
static bool areDistinctObjects(mlir::Value lhs, mlir::Value rhs)
Return true if lhs and rhs are provably different objects.
static void addToOffset(std::optional< int64_t > &offset, std::optional< int64_t > delta)
Add delta bytes to offset, making the offset unknown if delta is unknown or if the sum overflows.
static std::optional< int64_t > getTypeSizeInBytes(mlir::Type type, const mlir::DataLayout &dataLayout)
Return the size in bytes of type, or std::nullopt when that size isn't statically known (void,...
static constexpr unsigned MaxLookupDepth
static std::optional< int64_t > scaleOffset(std::optional< int64_t > count, std::optional< int64_t > size)
Return count * size, or std::nullopt if either input is unknown or the product overflows.
static PointerOffset decomposePointer(mlir::Value val, const mlir::DataLayout &dataLayout)
Trace val back to the object it points into, accumulating the byte offset of val from the start of th...
mlir::ModRefResult getModRef(mlir::Operation *op, mlir::Value location)
Return the modify-reference behavior of op on location.
mlir::AliasResult alias(mlir::Value lhs, mlir::Value rhs)
Return the aliasing behavior between two values.
bool isSized(mlir::Type ty)
Returns true if the type is a CIR sized type.
Definition CIRTypes.cpp:35
Diagnostic wrappers for TextAPI types for error reporting.
Definition Dominators.h:30