[clang] [CIR] Add object offset calculation for basic AA (PR #219047)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 15 17:47:51 PDT 2026
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/219047
>From 1f398022f78d215a13d773c9be208f03b6a531e3 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 14 Aug 2026 16:56:45 -0700
Subject: [PATCH 1/4] [CIR] Add object offset calculation for basic AA
This adds code to compute the offset of a pointer into a base object
when the base object is computed by CIR's basic alias analysis. The
offset will be used in a future change to determine partial alias, but
at this point the analysis still reports MayAlias for objects with different
offsets.
Assisted-by: Cursor / various models
---
.../CIR/Dialect/Analysis/CIRAliasAnalysis.h | 9 +-
.../Dialect/Analysis/CIRBasicAliasAnalysis.h | 31 +-
.../CIR/Dialect/Analysis/CIRAliasAnalysis.cpp | 5 +-
.../Analysis/CIRBasicAliasAnalysis.cpp | 315 +++++++++++-------
.../alias-analysis-underlying-object.cir | 270 ++++++++++-----
.../CIR/lib/Analysis/TestCIRAliasAnalysis.cpp | 4 +-
6 files changed, 402 insertions(+), 232 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/Analysis/CIRAliasAnalysis.h b/clang/include/clang/CIR/Dialect/Analysis/CIRAliasAnalysis.h
index df074f9393b0c4..e67913738bc535 100644
--- a/clang/include/clang/CIR/Dialect/Analysis/CIRAliasAnalysis.h
+++ b/clang/include/clang/CIR/Dialect/Analysis/CIRAliasAnalysis.h
@@ -19,15 +19,18 @@
namespace cir {
-/// Register all CIR alias analysis implementations with `aa`.
+/// Register all CIR alias analysis implementations with `aa`, which answers
+/// queries about values within `op`. Pass the same operation `aa` was created
+/// for; the implementations use the data layout in effect at `op`, so values
+/// from a different layout scope must not be queried.
///
/// Passes that want full CIR alias information should call this rather than
/// adding individual implementations:
///
/// mlir::AliasAnalysis aa(funcOp);
-/// cir::registerCIRAliasAnalyses(aa);
+/// cir::registerCIRAliasAnalyses(aa, funcOp);
///
-void registerCIRAliasAnalyses(mlir::AliasAnalysis &aa);
+void registerCIRAliasAnalyses(mlir::AliasAnalysis &aa, mlir::Operation *op);
} // namespace cir
diff --git a/clang/include/clang/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.h b/clang/include/clang/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.h
index 014688092c5976..432f9c2e479d18 100644
--- a/clang/include/clang/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.h
+++ b/clang/include/clang/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.h
@@ -20,6 +20,7 @@
#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/Value.h"
+#include "mlir/Interfaces/DataLayoutInterfaces.h"
namespace cir {
@@ -27,24 +28,17 @@ namespace cir {
/// sites. Conservative defaults (MayAlias / ModRef) are returned for cases
/// that are not yet handled.
class CIRBasicAliasAnalysis {
- enum class ObjectRelation {
- /// Provably different underlying allocations.
- Distinct,
- /// Same underlying allocation, no offset.
- Identical,
- /// Cannot determine the relationship.
- Unknown,
- };
-
public:
- CIRBasicAliasAnalysis() = default;
+ explicit CIRBasicAliasAnalysis(mlir::Operation *op)
+ : dataLayout(mlir::DataLayout::closest(op)) {}
CIRBasicAliasAnalysis(CIRBasicAliasAnalysis &&) = default;
/// Return the aliasing behavior between two values.
///
- /// Returns MayAlias conservatively unless a more precise result can be
- /// determined from CIR-specific information (e.g. distinct alloca ops,
- /// pointer provenance, restrict attributes).
+ /// Both values are traced back to the object they point into and to their
+ /// byte offset within it. Pointers into provably different objects don't
+ /// alias, and pointers at the same offset into the same object must alias.
+ /// MayAlias is returned whenever a more precise answer cannot be determined.
mlir::AliasResult alias(mlir::Value lhs, mlir::Value rhs);
/// Return the modify-reference behavior of `op` on `location`.
@@ -54,16 +48,7 @@ class CIRBasicAliasAnalysis {
mlir::ModRefResult getModRef(mlir::Operation *op, mlir::Value location);
private:
- /// Attempt to find the underlying allocation source for `val` by walking
- /// through pointer arithmetic, casts, and other CIR ops. Returns `val` if
- /// no more specific source is found.
- mlir::Value getUnderlyingObject(mlir::Value val);
-
- /// Classify the relationship between \p lhs and \p rhs. Returns one of:
- /// Distinct – provably different allocations
- /// Identical – same allocation, no offset
- /// Unknown – cannot determine
- ObjectRelation classifyObjects(mlir::Value lhs, mlir::Value rhs);
+ mlir::DataLayout dataLayout;
};
} // namespace cir
diff --git a/clang/lib/CIR/Dialect/Analysis/CIRAliasAnalysis.cpp b/clang/lib/CIR/Dialect/Analysis/CIRAliasAnalysis.cpp
index 03daf3ae4c37ce..c9ccd9c8dbfda0 100644
--- a/clang/lib/CIR/Dialect/Analysis/CIRAliasAnalysis.cpp
+++ b/clang/lib/CIR/Dialect/Analysis/CIRAliasAnalysis.cpp
@@ -9,6 +9,7 @@
#include "clang/CIR/Dialect/Analysis/CIRAliasAnalysis.h"
#include "clang/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.h"
-void cir::registerCIRAliasAnalyses(mlir::AliasAnalysis &aa) {
- aa.addAnalysisImplementation(CIRBasicAliasAnalysis());
+void cir::registerCIRAliasAnalyses(mlir::AliasAnalysis &aa,
+ mlir::Operation *op) {
+ aa.addAnalysisImplementation(CIRBasicAliasAnalysis(op));
}
diff --git a/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp b/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
index ceaca4916fdaed..5a7bcf9f34371a 100644
--- a/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
+++ b/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
@@ -11,6 +11,9 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "llvm/Support/DebugLog.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <limits>
#define DEBUG_TYPE "cir-basic-alias-analysis"
@@ -23,8 +26,97 @@ using namespace cir;
static constexpr unsigned MaxLookupDepth = 6;
-mlir::Value CIRBasicAliasAnalysis::getUnderlyingObject(mlir::Value val) {
- LDBG() << "Getting underlying object for: " << val;
+/// Return the size in bytes of \p type, or std::nullopt when that size isn't
+/// statically known (void, function types, incomplete records, ...).
+static std::optional<int64_t>
+getTypeSizeInBytes(mlir::Type type, const mlir::DataLayout &dataLayout) {
+ if (!cir::isSized(type))
+ return std::nullopt;
+
+ llvm::TypeSize size = dataLayout.getTypeSize(type);
+ if (size.isScalable())
+ return std::nullopt;
+ return size.getFixedValue();
+}
+
+/// If \p val is a constant integer that fits in an int64_t, return its value.
+/// The constant is interpreted according to the signedness of its type.
+static std::optional<int64_t> getConstantIndex(mlir::Value val) {
+ auto constOp =
+ mlir::dyn_cast_if_present<cir::ConstantOp>(val.getDefiningOp());
+ if (!constOp)
+ return std::nullopt;
+
+ auto intAttr = mlir::dyn_cast<cir::IntAttr>(constOp.getValue());
+ if (!intAttr)
+ return std::nullopt;
+
+ const APInt &value = intAttr.getValue();
+ if (intAttr.isSigned())
+ return value.trySExtValue();
+ return value.tryZExtValue();
+}
+
+/// Return `count * size`, or std::nullopt if either input is unknown or the
+/// product overflows.
+static std::optional<int64_t> scaleOffset(std::optional<int64_t> count,
+ std::optional<int64_t> size) {
+ if (!count || !size)
+ return std::nullopt;
+ auto [product, overflow] = MulOverflow(*count, *size);
+ if (overflow)
+ return std::nullopt;
+ return product;
+}
+
+/// Add \p delta bytes to \p offset, making the offset unknown if \p delta is
+/// unknown or if the sum overflows.
+static void addToOffset(std::optional<int64_t> &offset,
+ std::optional<int64_t> delta) {
+ if (!offset)
+ return;
+ if (!delta) {
+ offset.reset();
+ return;
+ }
+ auto [sum, overflow] = AddOverflow(*offset, *delta);
+ if (overflow)
+ offset.reset();
+ else
+ offset = sum;
+}
+
+namespace {
+/// A pointer expressed as a byte offset into the object it points into.
+struct PointerOffset {
+ /// The value the pointer was traced back to. This is an allocation, a block
+ /// argument, or the result of an operation this analysis cannot look through.
+ mlir::Value base;
+
+ /// Byte offset of the pointer from the start of `base`. The offset can be
+ /// negative. If this is std::nullopt, the offset is not a compile-time
+ /// constant.
+ std::optional<int64_t> offset;
+};
+} // namespace
+
+/// Trace \p val back to the object it points into, accumulating the byte offset
+/// of \p val from the start of that object.
+///
+/// The walk stops at a block argument, at an allocation, or at any operation
+/// whose result cannot be described as an offset from one of its operands. The
+/// returned base and offset always describe \p val, even when the walk stops
+/// early because the depth limit was reached.
+///
+/// Operations contributing an offset that isn't a compile-time constant (a
+/// dynamic cir.ptr_stride index, for example) are still traced through, leaving
+/// the offset unknown. Knowing which object a pointer points into is useful
+/// even when the offset within that object is not known.
+static PointerOffset decomposePointer(mlir::Value val,
+ const mlir::DataLayout &dataLayout) {
+ LDBG() << "Decomposing pointer: " << val;
+
+ std::optional<int64_t> offset = 0;
for (unsigned depth = 0; depth < MaxLookupDepth; ++depth) {
mlir::Operation *defOp = val.getDefiningOp();
@@ -33,9 +125,8 @@ mlir::Value CIRBasicAliasAnalysis::getUnderlyingObject(mlir::Value val) {
break; // Block argument (e.g. function parameter) — stop here.
}
- // Bitcast and address-space casts don't change the underlying object.
- // array_to_ptrdecay produces an element pointer to the same storage as
- // the array pointer, so strip through it too.
+ // Bitcasts and address-space casts don't change the address, and
+ // array_to_ptrdecay produces a pointer to the first element of the array.
if (auto castOp = mlir::dyn_cast<cir::CastOp>(defOp)) {
if (castOp.isAllocaPreservingCast() ||
castOp.getKind() == cir::CastKind::array_to_ptrdecay) {
@@ -47,114 +138,96 @@ mlir::Value CIRBasicAliasAnalysis::getUnderlyingObject(mlir::Value val) {
break;
}
- // Pointer stride: only strip through when we can prove the access stays
- // within the bounds of the underlying allocation.
+ // A stride moves the pointer by `stride * sizeof(pointee)` bytes.
if (auto strideOp = mlir::dyn_cast<cir::PtrStrideOp>(defOp)) {
- auto constOp = strideOp.getStride().getDefiningOp<cir::ConstantOp>();
- if (constOp) {
- if (auto intAttr = mlir::dyn_cast<cir::IntAttr>(constOp.getValue())) {
- APInt stride = intAttr.getValue();
-
- // Zero stride is trivially in-bounds.
- if (stride.isZero()) {
- LDBG() << "Walking past zero-strided PtrStrideOp";
- val = strideOp.getBase();
- continue;
- }
- }
- }
- // Dynamic stride or unverifiable bounds — stop here conservatively.
- LDBG() << "Non-zero or dynamic PtrStrideOp, stopping";
- break;
+ LDBG() << "Walking past PtrStrideOp";
+ addToOffset(offset,
+ scaleOffset(getConstantIndex(strideOp.getStride()),
+ getTypeSizeInBytes(strideOp.getElementType(),
+ dataLayout)));
+ val = strideOp.getBase();
+ continue;
}
- // Handle special cases for zero-offset sub-object accesses.
- if (auto op = mlir::dyn_cast<cir::GetMemberOp>(defOp)) {
- if (op.getIndex() == 0) {
- LDBG() << "GetMemberOp[0], following to underlying object";
- val = op.getAddr();
- continue;
- } else {
- LDBG() << "GetMemberOp, non-zero index, stopping";
- break;
- }
+ // A record member sits at a fixed offset given by the record layout.
+ if (auto memberOp = mlir::dyn_cast<cir::GetMemberOp>(defOp)) {
+ LDBG() << "Walking past GetMemberOp";
+ auto recordTy =
+ mlir::cast<cir::RecordType>(memberOp.getAddrTy().getPointee());
+ std::optional<int64_t> memberOffset;
+ if (!recordTy.isIncomplete())
+ memberOffset =
+ recordTy.getElementOffset(dataLayout, memberOp.getIndex());
+ addToOffset(offset, memberOffset);
+ val = memberOp.getAddr();
+ continue;
}
- if (auto op = mlir::dyn_cast<cir::GetElementOp>(defOp)) {
- cir::IntAttr index;
- if (auto constOp = op.getIndex().getDefiningOp<cir::ConstantOp>())
- index = mlir::dyn_cast<cir::IntAttr>(constOp.getValue());
- if (index && index.getValue().isZero()) {
- LDBG() << "GetElementOp[0], following to underlying object";
- val = op.getBase();
- continue;
- }
- LDBG() << "GetElementOp, non-zero or dynamic index, stopping";
- break;
+
+ // An array element sits at `index * sizeof(element)` bytes into the array.
+ if (auto elementOp = mlir::dyn_cast<cir::GetElementOp>(defOp)) {
+ LDBG() << "Walking past GetElementOp";
+ addToOffset(offset,
+ scaleOffset(getConstantIndex(elementOp.getIndex()),
+ getTypeSizeInBytes(elementOp.getElementType(),
+ dataLayout)));
+ val = elementOp.getBase();
+ continue;
}
- if (auto op = mlir::dyn_cast<cir::BaseClassAddrOp>(defOp)) {
- // A zero byte offset means the base subobject starts at the same address
- // as the derived object.
- if (op.getOffset().isZero()) {
- LDBG() << "BaseClassAddrOp[0], following to underlying object";
- val = op.getDerivedAddr();
- continue;
- }
- LDBG() << "BaseClassAddrOp, non-zero offset, stopping";
- break;
+
+ // A base class subobject starts the given number of bytes into the derived
+ // object.
+ if (auto baseOp = mlir::dyn_cast<cir::BaseClassAddrOp>(defOp)) {
+ LDBG() << "Walking past BaseClassAddrOp";
+ addToOffset(offset, baseOp.getOffset().tryZExtValue());
+ val = baseOp.getDerivedAddr();
+ continue;
}
- if (auto op = mlir::dyn_cast<cir::DerivedClassAddrOp>(defOp)) {
- // The offset is stored unsigned but applied as a negative adjustment. A
- // zero offset means the derived object starts at the same address as the
- // base subobject.
- if (op.getOffset().isZero()) {
- LDBG() << "DerivedClassAddrOp[0], following to underlying object";
- val = op.getBaseAddr();
- continue;
- }
- LDBG() << "DerivedClassAddrOp, non-zero offset, stopping";
- break;
+
+ // Conversely, the derived object starts that many bytes before the base
+ // subobject, so the offset is applied as a negative adjustment.
+ if (auto derivedOp = mlir::dyn_cast<cir::DerivedClassAddrOp>(defOp)) {
+ LDBG() << "Walking past DerivedClassAddrOp";
+ std::optional<int64_t> baseOffset = derivedOp.getOffset().tryZExtValue();
+ if (baseOffset)
+ baseOffset = -*baseOffset;
+ addToOffset(offset, baseOffset);
+ val = derivedOp.getBaseAddr();
+ continue;
}
- if (auto op = mlir::dyn_cast<cir::ComplexRealPtrOp>(defOp)) {
- LDBG() << "Getting input pointer for ComplexRealPtrOp";
- val = op.getOperand();
+
+ // The real part of a complex value is at offset zero, the imaginary part
+ // right behind it.
+ if (auto realOp = mlir::dyn_cast<cir::ComplexRealPtrOp>(defOp)) {
+ LDBG() << "Walking past ComplexRealPtrOp";
+ val = realOp.getOperand();
continue;
}
- if (auto op = mlir::dyn_cast<cir::ComplexImagPtrOp>(defOp)) {
- LDBG() << "ComplexImagPtrOp, stopping";
- break;
+ if (auto imagOp = mlir::dyn_cast<cir::ComplexImagPtrOp>(defOp)) {
+ LDBG() << "Walking past ComplexImagPtrOp";
+ auto ptrTy = mlir::cast<cir::PointerType>(imagOp.getOperand().getType());
+ auto complexTy = mlir::cast<cir::ComplexType>(ptrTy.getPointee());
+ addToOffset(offset,
+ getTypeSizeInBytes(complexTy.getElementType(), dataLayout));
+ val = imagOp.getOperand();
+ continue;
}
LDBG() << "Unhandled operation, stopping";
- break; // Unknown op — stop here conservatively.
- }
- return val;
-}
-
-CIRBasicAliasAnalysis::ObjectRelation
-CIRBasicAliasAnalysis::classifyObjects(mlir::Value lhs, mlir::Value rhs) {
- LDBG() << "Checking if " << lhs << " and " << rhs << " are distinct objects";
-
- // Two values are distinct allocations if they originate from different
- // cir.alloca operations (or other allocation ops) in the same function.
- // TODO: Extend to cover global addresses, function arguments with noalias,
- // and heap allocations.
- mlir::Value lhsObj = getUnderlyingObject(lhs);
- mlir::Value rhsObj = getUnderlyingObject(rhs);
-
- if (lhsObj == rhsObj) {
- LDBG() << "Identical values, not distinct";
- return ObjectRelation::Identical;
+ break; // Not expressible as an offset from another pointer.
}
- // Different cir.alloca ops in the same function cannot alias.
- if (mlir::isa_and_nonnull<cir::AllocaOp>(lhsObj.getDefiningOp()) &&
- mlir::isa_and_nonnull<cir::AllocaOp>(rhsObj.getDefiningOp())) {
- LDBG() << "Different cir.alloca ops in the same function, distinct";
- return ObjectRelation::Distinct;
- }
+ return {val, offset};
+}
- LDBG() << "Conservative fallback, not distinct";
- return ObjectRelation::Unknown;
+/// Return true if \p lhs and \p rhs are provably different objects.
+///
+/// TODO: Extend to cover global addresses, function arguments with noalias, and
+/// heap allocations.
+static bool areDistinctObjects(mlir::Value lhs, mlir::Value rhs) {
+ // Distinct cir.alloca ops allocate distinct storage.
+ return lhs != rhs &&
+ mlir::isa_and_nonnull<cir::AllocaOp>(lhs.getDefiningOp()) &&
+ mlir::isa_and_nonnull<cir::AllocaOp>(rhs.getDefiningOp());
}
//===----------------------------------------------------------------------===//
@@ -170,20 +243,37 @@ mlir::AliasResult CIRBasicAliasAnalysis::alias(mlir::Value lhs,
return mlir::AliasResult::MustAlias;
}
- ObjectRelation relation = classifyObjects(lhs, rhs);
- switch (relation) {
- case ObjectRelation::Distinct:
- LDBG() << "No alias between distinct objects";
- return mlir::AliasResult::NoAlias;
- case ObjectRelation::Identical:
- LDBG() << "Must alias between identical objects";
- return mlir::AliasResult::MustAlias;
- case ObjectRelation::Unknown:
- // Conservative fallback — the aggregate will try other implementations.
- LDBG() << "Conservative fallback, may alias";
+ PointerOffset lhsPtr = decomposePointer(lhs, dataLayout);
+ PointerOffset rhsPtr = decomposePointer(rhs, dataLayout);
+
+ if (lhsPtr.base != rhsPtr.base) {
+ if (areDistinctObjects(lhsPtr.base, rhsPtr.base)) {
+ LDBG() << "No alias between pointers into distinct objects";
+ return mlir::AliasResult::NoAlias;
+ }
+ LDBG() << "Unrelated base objects, may alias";
return mlir::AliasResult::MayAlias;
}
- llvm_unreachable("Unhandled ObjectRelation");
+
+ // Both pointers point into the same object, so their offsets can be compared
+ // directly.
+ if (!lhsPtr.offset || !rhsPtr.offset) {
+ LDBG() << "Same object at an unknown offset, may alias";
+ return mlir::AliasResult::MayAlias;
+ }
+
+ // Equal offsets means both pointers start at exactly the same address, which
+ // is all MustAlias claims. How many bytes each access touches doesn't matter.
+ if (*lhsPtr.offset == *rhsPtr.offset) {
+ LDBG() << "Must alias at the same address within the same object";
+ return mlir::AliasResult::MustAlias;
+ }
+
+ // TODO: Two pointers at different offsets into the same object only overlap
+ // if the accesses are large enough to reach one another. Comparing the byte
+ // ranges the accesses cover would prove NoAlias or PartialAlias here.
+ LDBG() << "Same object at different offsets, may alias";
+ return mlir::AliasResult::MayAlias;
}
mlir::ModRefResult CIRBasicAliasAnalysis::getModRef(mlir::Operation *op,
@@ -213,10 +303,7 @@ mlir::ModRefResult CIRBasicAliasAnalysis::getModRef(mlir::Operation *op,
LDBG() << " Checking alias between affected location "
<< affectedLocation << " and query location " << location;
aliasResult = alias(affectedLocation, location);
- LDBG() << " Alias result: "
- << (aliasResult.isMust() ? "MustAlias"
- : aliasResult.isNo() ? "NoAlias"
- : "MayAlias");
+ LDBG() << " Alias result: " << aliasResult;
} else {
// An effect on a non-addressable resource cannot affect a
// pointer-based location.
diff --git a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
index 67794d3e6a0141..412021455b9e4f 100644
--- a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
+++ b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
@@ -59,16 +59,20 @@ cir.func @ptr_stride_zero_strips_to_alloca() {
// -----
-// CHECK-LABEL: Testing : "ptr_stride_inbounds_strips_to_alloca"
+// CHECK-LABEL: Testing : "ptr_stride_constant_offsets"
// CHECK-DAG: arr#0 <-> other#0: NoAlias
// CHECK-DAG: arr#0 <-> ptr#0: MustAlias
-// CHECK-DAG: other#0 <-> ptr#0: NoAlias
// CHECK-DAG: arr#0 <-> elem#0: MayAlias
-// CHECK-DAG: other#0 <-> elem#0: MayAlias
+// CHECK-DAG: arr#0 <-> elem2#0: MayAlias
+// CHECK-DAG: other#0 <-> ptr#0: NoAlias
+// CHECK-DAG: other#0 <-> elem#0: NoAlias
+// CHECK-DAG: other#0 <-> elem2#0: NoAlias
// CHECK-DAG: ptr#0 <-> elem#0: MayAlias
+// CHECK-DAG: ptr#0 <-> elem2#0: MayAlias
+// CHECK-DAG: elem#0 <-> elem2#0: MayAlias
!s32i = !cir.int<s, 32>
-cir.func @ptr_stride_inbounds_strips_to_alloca() {
+cir.func @ptr_stride_constant_offsets() {
%arr = cir.alloca "arr" align(4) : !cir.ptr<!cir.array<!s32i x 4>> {test.ptr = "arr"}
%other = cir.alloca "other" align(4) : !cir.ptr<!s32i> {test.ptr = "other"}
%ptr = cir.cast array_to_ptrdecay %arr
@@ -76,21 +80,60 @@ cir.func @ptr_stride_inbounds_strips_to_alloca() {
%two = cir.const #cir.int<2> : !s32i
%elem = cir.ptr_stride %ptr, %two : (!cir.ptr<!s32i>, !s32i) -> !cir.ptr<!s32i>
{test.ptr = "elem"}
+ %one = cir.const #cir.int<1> : !s32i
+ %elem2 = cir.ptr_stride %elem, %one : (!cir.ptr<!s32i>, !s32i) -> !cir.ptr<!s32i>
+ {test.ptr = "elem2"}
cir.return
}
// -----
-// CHECK-LABEL: Testing : "ptr_stride_dynamic_not_stripped"
+// CHECK-LABEL: Testing : "ptr_stride_out_of_bounds"
// CHECK-DAG: arr#0 <-> other#0: NoAlias
// CHECK-DAG: arr#0 <-> ptr#0: MustAlias
+// CHECK-DAG: arr#0 <-> oob#0: MayAlias
+// CHECK-DAG: arr#0 <-> inbounds#0: MayAlias
+// CHECK-DAG: arr#0 <-> oob2#0: MayAlias
// CHECK-DAG: other#0 <-> ptr#0: NoAlias
+// CHECK-DAG: other#0 <-> oob#0: NoAlias
+// CHECK-DAG: other#0 <-> inbounds#0: NoAlias
+// CHECK-DAG: other#0 <-> oob2#0: NoAlias
+// CHECK-DAG: ptr#0 <-> oob#0: MayAlias
+// CHECK-DAG: ptr#0 <-> inbounds#0: MayAlias
+// CHECK-DAG: ptr#0 <-> oob2#0: MayAlias
+// CHECK-DAG: oob#0 <-> inbounds#0: MayAlias
+// CHECK-DAG: oob#0 <-> oob2#0: MustAlias
+// CHECK-DAG: inbounds#0 <-> oob2#0: MayAlias
+
+!s32i = !cir.int<s, 32>
+cir.func @ptr_stride_out_of_bounds() {
+ %arr = cir.alloca "arr" align(4) : !cir.ptr<!cir.array<!s32i x 4>> {test.ptr = "arr"}
+ %other = cir.alloca "other" align(4) : !cir.ptr<!s32i> {test.ptr = "other"}
+ %ptr = cir.cast array_to_ptrdecay %arr
+ : !cir.ptr<!cir.array<!s32i x 4>> -> !cir.ptr<!s32i> {test.ptr = "ptr"}
+ %four = cir.const #cir.int<4> : !s32i
+ %oob = cir.ptr_stride %ptr, %four : (!cir.ptr<!s32i>, !s32i) -> !cir.ptr<!s32i>
+ {test.ptr = "oob"}
+ %two = cir.const #cir.int<2> : !s32i
+ %inbounds = cir.ptr_stride %ptr, %two : (!cir.ptr<!s32i>, !s32i) -> !cir.ptr<!s32i>
+ {test.ptr = "inbounds"}
+ %oob2 = cir.ptr_stride %inbounds, %two : (!cir.ptr<!s32i>, !s32i) -> !cir.ptr<!s32i>
+ {test.ptr = "oob2"}
+ cir.return
+}
+
+// -----
+
+// CHECK-LABEL: Testing : "ptr_stride_dynamic_offset"
+// CHECK-DAG: arr#0 <-> other#0: NoAlias
+// CHECK-DAG: arr#0 <-> ptr#0: MustAlias
// CHECK-DAG: arr#0 <-> dyn#0: MayAlias
-// CHECK-DAG: other#0 <-> dyn#0: MayAlias
+// CHECK-DAG: other#0 <-> ptr#0: NoAlias
+// CHECK-DAG: other#0 <-> dyn#0: NoAlias
// CHECK-DAG: ptr#0 <-> dyn#0: MayAlias
!s32i = !cir.int<s, 32>
-cir.func @ptr_stride_dynamic_not_stripped(%n: !s32i) {
+cir.func @ptr_stride_dynamic_offset(%n: !s32i) {
%arr = cir.alloca "arr" align(4) : !cir.ptr<!cir.array<!s32i x 4>> {test.ptr = "arr"}
%other = cir.alloca "other" align(4) : !cir.ptr<!s32i> {test.ptr = "other"}
%ptr = cir.cast array_to_ptrdecay %arr
@@ -107,16 +150,16 @@ cir.func @ptr_stride_dynamic_not_stripped(%n: !s32i) {
// CHECK-DAG: s1#0 <-> m1_0#0: MustAlias
// CHECK-DAG: s2#0 <-> m1_0#0: NoAlias
// CHECK-DAG: s1#0 <-> m1_1#0: MayAlias
-// CHECK-DAG: s2#0 <-> m1_1#0: MayAlias
+// CHECK-DAG: s2#0 <-> m1_1#0: NoAlias
// CHECK-DAG: m1_0#0 <-> m1_1#0: MayAlias
// CHECK-DAG: s1#0 <-> m2_0#0: NoAlias
// CHECK-DAG: s2#0 <-> m2_0#0: MustAlias
// CHECK-DAG: m1_0#0 <-> m2_0#0: NoAlias
-// CHECK-DAG: m1_1#0 <-> m2_0#0: MayAlias
-// CHECK-DAG: s1#0 <-> m2_1#0: MayAlias
+// CHECK-DAG: m1_1#0 <-> m2_0#0: NoAlias
+// CHECK-DAG: s1#0 <-> m2_1#0: NoAlias
// CHECK-DAG: s2#0 <-> m2_1#0: MayAlias
-// CHECK-DAG: m1_0#0 <-> m2_1#0: MayAlias
-// CHECK-DAG: m1_1#0 <-> m2_1#0: MayAlias
+// CHECK-DAG: m1_0#0 <-> m2_1#0: NoAlias
+// CHECK-DAG: m1_1#0 <-> m2_1#0: NoAlias
// CHECK-DAG: m2_0#0 <-> m2_1#0: MayAlias
!s32i = !cir.int<s, 32>
@@ -137,6 +180,55 @@ cir.func @get_member_distinct_allocas() {
// -----
+// CHECK-LABEL: Testing : "get_member_nested_records"
+// CHECK-DAG: o#0 <-> inner#0: MustAlias
+// CHECK-DAG: o#0 <-> i0#0: MustAlias
+// CHECK-DAG: inner#0 <-> i0#0: MustAlias
+// CHECK-DAG: o#0 <-> i1#0: MayAlias
+// CHECK-DAG: inner#0 <-> i1#0: MayAlias
+// CHECK-DAG: i0#0 <-> i1#0: MayAlias
+// CHECK-DAG: o#0 <-> z#0: MayAlias
+// CHECK-DAG: inner#0 <-> z#0: MayAlias
+// CHECK-DAG: i0#0 <-> z#0: MayAlias
+// CHECK-DAG: i1#0 <-> z#0: MayAlias
+
+!s32i = !cir.int<s, 32>
+!rec_Inner = !cir.struct<"Inner" {data !s32i, data !s32i}>
+!rec_Outer = !cir.struct<"Outer" {data !rec_Inner, data !s32i}>
+cir.func @get_member_nested_records() {
+ %o = cir.alloca "o" align(4) : !cir.ptr<!rec_Outer> {test.ptr = "o"}
+ %inner = cir.get_member %o[0] {name = "inner", test.ptr = "inner"}
+ : !cir.ptr<!rec_Outer> -> !cir.ptr<!rec_Inner>
+ %i0 = cir.get_member %inner[0] {name = "x", test.ptr = "i0"}
+ : !cir.ptr<!rec_Inner> -> !cir.ptr<!s32i>
+ %i1 = cir.get_member %inner[1] {name = "y", test.ptr = "i1"}
+ : !cir.ptr<!rec_Inner> -> !cir.ptr<!s32i>
+ %z = cir.get_member %o[1] {name = "z", test.ptr = "z"}
+ : !cir.ptr<!rec_Outer> -> !cir.ptr<!s32i>
+ cir.return
+}
+
+// -----
+
+// CHECK-LABEL: Testing : "get_member_union"
+// CHECK-DAG: u#0 <-> m_int#0: MustAlias
+// CHECK-DAG: u#0 <-> m_char#0: MustAlias
+// CHECK-DAG: m_int#0 <-> m_char#0: MustAlias
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!rec_U = !cir.union<"U" {data !s32i, data !u8i}>
+cir.func @get_member_union() {
+ %u = cir.alloca "u" align(4) : !cir.ptr<!rec_U> {test.ptr = "u"}
+ %m_int = cir.get_member %u[0] {name = "i", test.ptr = "m_int"}
+ : !cir.ptr<!rec_U> -> !cir.ptr<!s32i>
+ %m_char = cir.get_member %u[1] {name = "c", test.ptr = "m_char"}
+ : !cir.ptr<!rec_U> -> !cir.ptr<!u8i>
+ cir.return
+}
+
+// -----
+
// CHECK-LABEL: Testing : "get_element_distinct_allocas"
// CHECK-DAG: a1#0 <-> a2#0: NoAlias
// CHECK-DAG: a1#0 <-> e1_0#0: MustAlias
@@ -145,14 +237,14 @@ cir.func @get_member_distinct_allocas() {
// CHECK-DAG: a2#0 <-> e2_0#0: MustAlias
// CHECK-DAG: e1_0#0 <-> e2_0#0: NoAlias
// CHECK-DAG: a1#0 <-> e1_1#0: MayAlias
-// CHECK-DAG: a2#0 <-> e1_1#0: MayAlias
+// CHECK-DAG: a2#0 <-> e1_1#0: NoAlias
// CHECK-DAG: e1_0#0 <-> e1_1#0: MayAlias
-// CHECK-DAG: e2_0#0 <-> e1_1#0: MayAlias
-// CHECK-DAG: a1#0 <-> e2_1#0: MayAlias
+// CHECK-DAG: e2_0#0 <-> e1_1#0: NoAlias
+// CHECK-DAG: a1#0 <-> e2_1#0: NoAlias
// CHECK-DAG: a2#0 <-> e2_1#0: MayAlias
-// CHECK-DAG: e1_0#0 <-> e2_1#0: MayAlias
+// CHECK-DAG: e1_0#0 <-> e2_1#0: NoAlias
// CHECK-DAG: e2_0#0 <-> e2_1#0: MayAlias
-// CHECK-DAG: e1_1#0 <-> e2_1#0: MayAlias
+// CHECK-DAG: e1_1#0 <-> e2_1#0: NoAlias
!s32i = !cir.int<s, 32>
cir.func @get_element_distinct_allocas() {
@@ -173,11 +265,42 @@ cir.func @get_element_distinct_allocas() {
// -----
+// CHECK-LABEL: Testing : "get_element_of_records"
+// CHECK-DAG: arr#0 <-> e0#0: MustAlias
+// CHECK-DAG: arr#0 <-> e1#0: MayAlias
+// CHECK-DAG: e0#0 <-> e1#0: MayAlias
+// CHECK-DAG: arr#0 <-> e0_y#0: MayAlias
+// CHECK-DAG: e0#0 <-> e0_y#0: MayAlias
+// CHECK-DAG: e1#0 <-> e0_y#0: MayAlias
+// CHECK-DAG: arr#0 <-> e1_y#0: MayAlias
+// CHECK-DAG: e0#0 <-> e1_y#0: MayAlias
+// CHECK-DAG: e1#0 <-> e1_y#0: MayAlias
+// CHECK-DAG: e0_y#0 <-> e1_y#0: MayAlias
+
+!s32i = !cir.int<s, 32>
+!rec_P = !cir.struct<"P" {data !s32i, data !s32i}>
+cir.func @get_element_of_records() {
+ %arr = cir.alloca "arr" align(4) : !cir.ptr<!cir.array<!rec_P x 2>> {test.ptr = "arr"}
+ %zero = cir.const #cir.int<0> : !s32i
+ %one = cir.const #cir.int<1> : !s32i
+ %e0 = cir.get_element %arr[%zero : !s32i] {test.ptr = "e0"}
+ : !cir.ptr<!cir.array<!rec_P x 2>> -> !cir.ptr<!rec_P>
+ %e1 = cir.get_element %arr[%one : !s32i] {test.ptr = "e1"}
+ : !cir.ptr<!cir.array<!rec_P x 2>> -> !cir.ptr<!rec_P>
+ %e0_y = cir.get_member %e0[1] {name = "y", test.ptr = "e0_y"}
+ : !cir.ptr<!rec_P> -> !cir.ptr<!s32i>
+ %e1_y = cir.get_member %e1[1] {name = "y", test.ptr = "e1_y"}
+ : !cir.ptr<!rec_P> -> !cir.ptr<!s32i>
+ cir.return
+}
+
+// -----
+
// CHECK-LABEL: Testing : "base_class_addr_distinct_allocas"
// CHECK-DAG: d1#0 <-> d2#0: NoAlias
// CHECK-DAG: d1#0 <-> base1#0: MustAlias
-// CHECK-DAG: d2#0 <-> base1#0: NoAlias
// CHECK-DAG: d1#0 <-> base2#0: NoAlias
+// CHECK-DAG: d2#0 <-> base1#0: NoAlias
// CHECK-DAG: d2#0 <-> base2#0: MustAlias
// CHECK-DAG: base1#0 <-> base2#0: NoAlias
@@ -197,73 +320,26 @@ cir.func @base_class_addr_distinct_allocas() {
// -----
-// CHECK-LABEL: Testing : "base_class_addr_nonzero_offset"
-// CHECK-DAG: d1#0 <-> d2#0: NoAlias
-// CHECK-DAG: d1#0 <-> base1#0: MayAlias
-// CHECK-DAG: d2#0 <-> base1#0: MayAlias
-// CHECK-DAG: d1#0 <-> base2#0: MayAlias
-// CHECK-DAG: d2#0 <-> base2#0: MayAlias
-// CHECK-DAG: base1#0 <-> base2#0: MayAlias
-
-!u8i = !cir.int<u, 8>
-!s32i = !cir.int<s, 32>
-!rec_Base = !cir.struct<"Base" {data !u8i}>
-!rec_Derived = !cir.struct<"Derived" {data !s32i, data !rec_Base}>
-cir.func @base_class_addr_nonzero_offset() {
- %d1 = cir.alloca "d1" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d1"}
- %d2 = cir.alloca "d2" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d2"}
- %base1 = cir.base_class_addr %d1 : !cir.ptr<!rec_Derived> nonnull [4]
- -> !cir.ptr<!rec_Base> {test.ptr = "base1"}
- %base2 = cir.base_class_addr %d2 : !cir.ptr<!rec_Derived> nonnull [4]
- -> !cir.ptr<!rec_Base> {test.ptr = "base2"}
- cir.return
-}
-
-// -----
-
-// CHECK-LABEL: Testing : "derived_class_addr_zero_offset"
-// CHECK-DAG: b1#0 <-> b2#0: NoAlias
-// CHECK-DAG: b1#0 <-> d1#0: MustAlias
-// CHECK-DAG: b2#0 <-> d1#0: NoAlias
-// CHECK-DAG: b1#0 <-> d2#0: NoAlias
-// CHECK-DAG: b2#0 <-> d2#0: MustAlias
-// CHECK-DAG: d1#0 <-> d2#0: NoAlias
-
-!u8i = !cir.int<u, 8>
-!s32i = !cir.int<s, 32>
-!rec_Base = !cir.struct<"Base" {data !u8i}>
-!rec_Derived = !cir.struct<"Derived" {data !rec_Base, data !s32i}>
-cir.func @derived_class_addr_zero_offset() {
- %b1 = cir.alloca "d1" align(4) : !cir.ptr<!rec_Base> {test.ptr = "b1"}
- %b2 = cir.alloca "d2" align(4) : !cir.ptr<!rec_Base> {test.ptr = "b2"}
- %d1 = cir.derived_class_addr %b1 : !cir.ptr<!rec_Base> nonnull [0]
- -> !cir.ptr<!rec_Derived> {test.ptr = "d1"}
- %d2 = cir.derived_class_addr %b2 : !cir.ptr<!rec_Base> nonnull [0]
- -> !cir.ptr<!rec_Derived> {test.ptr = "d2"}
- cir.return
-}
-
-// -----
-
-// CHECK-LABEL: Testing : "derived_class_addr_nonzero_offset"
-// CHECK-DAG: b1#0 <-> b2#0: NoAlias
-// CHECK-DAG: b1#0 <-> d1#0: MayAlias
-// CHECK-DAG: b2#0 <-> d1#0: MayAlias
-// CHECK-DAG: b1#0 <-> d2#0: MayAlias
-// CHECK-DAG: b2#0 <-> d2#0: MayAlias
-// CHECK-DAG: d1#0 <-> d2#0: MayAlias
+// CHECK-LABEL: Testing : "base_and_derived_class_addr"
+// CHECK-DAG: d#0 <-> base#0: MayAlias
+// CHECK-DAG: d#0 <-> derived#0: MustAlias
+// CHECK-DAG: base#0 <-> derived#0: MayAlias
+// CHECK-DAG: d#0 <-> first#0: MustAlias
+// CHECK-DAG: base#0 <-> first#0: MayAlias
+// CHECK-DAG: derived#0 <-> first#0: MustAlias
!u8i = !cir.int<u, 8>
!s32i = !cir.int<s, 32>
!rec_Base = !cir.struct<"Base" {data !u8i}>
!rec_Derived = !cir.struct<"Derived" {data !s32i, data !rec_Base}>
-cir.func @derived_class_addr_nonzero_offset() {
- %b1 = cir.alloca "d1" align(4) : !cir.ptr<!rec_Base> {test.ptr = "b1"}
- %b2 = cir.alloca "d2" align(4) : !cir.ptr<!rec_Base> {test.ptr = "b2"}
- %d1 = cir.derived_class_addr %b1 : !cir.ptr<!rec_Base> nonnull [4]
- -> !cir.ptr<!rec_Derived> {test.ptr = "d1"}
- %d2 = cir.derived_class_addr %b2 : !cir.ptr<!rec_Base> nonnull [4]
- -> !cir.ptr<!rec_Derived> {test.ptr = "d2"}
+cir.func @base_and_derived_class_addr() {
+ %d = cir.alloca "d" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d"}
+ %base = cir.base_class_addr %d : !cir.ptr<!rec_Derived> nonnull [4]
+ -> !cir.ptr<!rec_Base> {test.ptr = "base"}
+ %derived = cir.derived_class_addr %base : !cir.ptr<!rec_Base> nonnull [4]
+ -> !cir.ptr<!rec_Derived> {test.ptr = "derived"}
+ %first = cir.get_member %d[0] {name = "x", test.ptr = "first"}
+ : !cir.ptr<!rec_Derived> -> !cir.ptr<!s32i>
cir.return
}
@@ -272,18 +348,18 @@ cir.func @derived_class_addr_nonzero_offset() {
// CHECK-LABEL: Testing : "complex_parts_distinct_allocas"
// CHECK-DAG: c1#0 <-> c2#0: NoAlias
// CHECK-DAG: c1#0 <-> real1#0: MustAlias
-// CHECK-DAG: c2#0 <-> real1#0: NoAlias
// CHECK-DAG: c1#0 <-> imag1#0: MayAlias
-// CHECK-DAG: c2#0 <-> imag1#0: MayAlias
-// CHECK-DAG: real1#0 <-> imag1#0: MayAlias
// CHECK-DAG: c1#0 <-> real2#0: NoAlias
+// CHECK-DAG: c1#0 <-> imag2#0: NoAlias
+// CHECK-DAG: c2#0 <-> real1#0: NoAlias
+// CHECK-DAG: c2#0 <-> imag1#0: NoAlias
// CHECK-DAG: c2#0 <-> real2#0: MustAlias
-// CHECK-DAG: real1#0 <-> real2#0: NoAlias
-// CHECK-DAG: imag1#0 <-> real2#0: MayAlias
-// CHECK-DAG: c1#0 <-> imag2#0: MayAlias
// CHECK-DAG: c2#0 <-> imag2#0: MayAlias
-// CHECK-DAG: real1#0 <-> imag2#0: MayAlias
-// CHECK-DAG: imag1#0 <-> imag2#0: MayAlias
+// CHECK-DAG: real1#0 <-> imag1#0: MayAlias
+// CHECK-DAG: real1#0 <-> real2#0: NoAlias
+// CHECK-DAG: real1#0 <-> imag2#0: NoAlias
+// CHECK-DAG: imag1#0 <-> real2#0: NoAlias
+// CHECK-DAG: imag1#0 <-> imag2#0: NoAlias
// CHECK-DAG: real2#0 <-> imag2#0: MayAlias
cir.func @complex_parts_distinct_allocas() {
@@ -303,3 +379,21 @@ cir.func @complex_parts_distinct_allocas() {
{test.ptr = "imag2"}
cir.return
}
+
+// -----
+
+// CHECK-LABEL: Testing : "offsets_from_pointer_argument"
+// CHECK-DAG: px#0 <-> py#0: MayAlias
+// CHECK-DAG: px#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: py#0 <-> func.region0#0: MayAlias
+
+!s32i = !cir.int<s, 32>
+!rec_S = !cir.struct<"S" {data !s32i, data !s32i}>
+cir.func @offsets_from_pointer_argument(%s: !cir.ptr<!rec_S>)
+ attributes {test.ptr = "func"} {
+ %px = cir.get_member %s[0] {name = "x", test.ptr = "px"}
+ : !cir.ptr<!rec_S> -> !cir.ptr<!s32i>
+ %py = cir.get_member %s[1] {name = "y", test.ptr = "py"}
+ : !cir.ptr<!rec_S> -> !cir.ptr<!s32i>
+ cir.return
+}
diff --git a/clang/test/CIR/lib/Analysis/TestCIRAliasAnalysis.cpp b/clang/test/CIR/lib/Analysis/TestCIRAliasAnalysis.cpp
index 2bee3f522ab914..06669750097373 100644
--- a/clang/test/CIR/lib/Analysis/TestCIRAliasAnalysis.cpp
+++ b/clang/test/CIR/lib/Analysis/TestCIRAliasAnalysis.cpp
@@ -29,7 +29,7 @@ struct TestCIRAliasAnalysisPass
}
void runOnOperation() override {
mlir::AliasAnalysis aliasAnalysis(getOperation());
- cir::registerCIRAliasAnalyses(aliasAnalysis);
+ cir::registerCIRAliasAnalyses(aliasAnalysis, getOperation());
runAliasAnalysisOnOperation(getOperation(), aliasAnalysis);
}
};
@@ -51,7 +51,7 @@ struct TestCIRAliasAnalysisModRefPass
}
void runOnOperation() override {
mlir::AliasAnalysis aliasAnalysis(getOperation());
- cir::registerCIRAliasAnalyses(aliasAnalysis);
+ cir::registerCIRAliasAnalyses(aliasAnalysis, getOperation());
runAliasAnalysisOnOperation(getOperation(), aliasAnalysis);
}
};
>From 2d0179178aefd2ed4e89ed6036cb2e5ce0ee2308 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 31 Aug 2026 15:06:06 -0700
Subject: [PATCH 2/4] Add tests for BitInt(19)
---
.../alias-analysis-underlying-object.cir | 74 +++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
index 412021455b9e4f..eaf9dfd6d7900b 100644
--- a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
+++ b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
@@ -397,3 +397,77 @@ cir.func @offsets_from_pointer_argument(%s: !cir.ptr<!rec_S>)
: !cir.ptr<!rec_S> -> !cir.ptr<!s32i>
cir.return
}
+
+// -----
+
+// CHECK-LABEL: Testing : "bitint_struct_elements"
+// CHECK-DAG: px#0 <-> py#0: MayAlias
+// CHECK-DAG: px#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: px#0 <-> px_plus_one#0: MayAlias
+// CHECK-DAG: px#0 <-> pxb#0: MustAlias
+// CHECK-DAG: px#0 <-> pxb_plus_four#0: MayAlias
+// CHECK-DAG: py#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: py#0 <-> px_plus_one#0: MustAlias
+// CHECK-DAG: py#0 <-> pxb#0: MayAlias
+// CHECK-DAG: py#0 <-> pxb_plus_four#0: MustAlias
+
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+!rec_S = !cir.struct<"S" {data !cir.int<s, 19, bitint>, data !cir.int<s, 19, bitint>}>
+cir.func @bitint_struct_elements(%s: !cir.ptr<!rec_S>)
+ attributes {test.ptr = "func"} {
+ %px = cir.get_member %s[0] {name = "x", test.ptr = "px"}
+ : !cir.ptr<!rec_S> -> !cir.ptr<!cir.int<s, 19, bitint>>
+ %py = cir.get_member %s[1] {name = "y", test.ptr = "py"}
+ : !cir.ptr<!rec_S> -> !cir.ptr<!cir.int<s, 19, bitint>>
+ %one = cir.const #cir.int<1> : !s32i
+ %px_plus_one = cir.ptr_stride %px, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
+ -> !cir.ptr<!cir.int<s, 19, bitint>>
+ {test.ptr = "px_plus_one"}
+ %pxb = cir.cast bitcast %px : !cir.ptr<!cir.int<s, 19, bitint>> -> !cir.ptr<!u8i> {test.ptr = "pxb"}
+ %four = cir.const #cir.int<4> : !s32i
+ %pxb_plus_four = cir.ptr_stride %pxb, %four : (!cir.ptr<!u8i>, !s32i) -> !cir.ptr<!u8i>
+ {test.ptr = "pxb_plus_four"}
+ cir.return
+}
+
+// -----
+
+// CHECK-LABEL: Testing : "bitint_array_elements"
+// CHECK-DAG: decay#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: decay#0 <-> elem0#0: MustAlias
+// CHECK-DAG: decay#0 <-> elem1#0: MayAlias
+// CHECK-DAG: decay#0 <-> decay_plus_one#0: MayAlias
+// CHECK-DAG: decay#0 <-> db#0: MustAlias
+// CHECK-DAG: decay#0 <-> db_plus_four#0: MayAlias
+// CHECK-DAG: elem0#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: elem0#0 <-> elem1#0: MayAlias
+// CHECK-DAG: elem0#0 <-> decay_plus_one#0: MayAlias
+// CHECK-DAG: elem0#0 <-> db#0: MustAlias
+// CHECK-DAG: elem0#0 <-> db_plus_four#0: MayAlias
+// CHECK-DAG: elem1#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: elem1#0 <-> decay_plus_one#0: MustAlias
+// CHECK-DAG: elem1#0 <-> db#0: MayAlias
+// CHECK-DAG: elem1#0 <-> db_plus_four#0: MustAlias
+
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+cir.func @bitint_array_elements(%arr: !cir.ptr<!cir.array<!cir.int<s, 19, bitint> x 4>>)
+ attributes {test.ptr = "func"} {
+ %decay = cir.cast array_to_ptrdecay %arr : !cir.ptr<!cir.array<!cir.int<s, 19, bitint> x 4>>
+ -> !cir.ptr<!cir.int<s, 19, bitint>> {test.ptr = "decay"}
+ %zero = cir.const #cir.int<0> : !s32i
+ %elem0 = cir.get_element %arr[%zero : !s32i] {test.ptr = "elem0"}
+ : !cir.ptr<!cir.array<!cir.int<s, 19, bitint> x 4>> -> !cir.ptr<!cir.int<s, 19, bitint>>
+ %one = cir.const #cir.int<1> : !s32i
+ %elem1 = cir.get_element %arr[%one : !s32i] {test.ptr = "elem1"}
+ : !cir.ptr<!cir.array<!cir.int<s, 19, bitint> x 4>> -> !cir.ptr<!cir.int<s, 19, bitint>>
+ %decay_plus_one = cir.ptr_stride %decay, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
+ -> !cir.ptr<!cir.int<s, 19, bitint>>
+ {test.ptr = "decay_plus_one"}
+ %db = cir.cast bitcast %decay : !cir.ptr<!cir.int<s, 19, bitint>> -> !cir.ptr<!u8i> {test.ptr = "db"}
+ %four = cir.const #cir.int<4> : !s32i
+ %db_plus_four = cir.ptr_stride %db, %four : (!cir.ptr<!u8i>, !s32i) -> !cir.ptr<!u8i>
+ {test.ptr = "db_plus_four"}
+ cir.return
+}
>From 71d484c9194be4f57997edf8b03ca96b6abe0b2c Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 31 Aug 2026 15:12:17 -0700
Subject: [PATCH 3/4] Add tests and comments for ignoring the non-null
base/derived class addrs
---
.../Analysis/CIRBasicAliasAnalysis.cpp | 8 +++--
.../alias-analysis-underlying-object.cir | 29 +++++++++++++++++--
2 files changed, 33 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp b/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
index 5a7bcf9f34371a..de4f8c4f401410 100644
--- a/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
+++ b/clang/lib/CIR/Dialect/Analysis/CIRBasicAliasAnalysis.cpp
@@ -175,7 +175,9 @@ static PointerOffset decomposePointer(mlir::Value val,
}
// A base class subobject starts the given number of bytes into the derived
- // object.
+ // object. This may return null if the input is null, but accessing memory
+ // based on that null pointer would be UB, so we always assume non-null
+ // here.
if (auto baseOp = mlir::dyn_cast<cir::BaseClassAddrOp>(defOp)) {
LDBG() << "Walking past BaseClassAddrOp";
addToOffset(offset, baseOp.getOffset().tryZExtValue());
@@ -184,7 +186,9 @@ static PointerOffset decomposePointer(mlir::Value val,
}
// Conversely, the derived object starts that many bytes before the base
- // subobject, so the offset is applied as a negative adjustment.
+ // subobject, so the offset is applied as a negative adjustment. This may
+ // return null if the input is null, but accessing memory based on that null
+ // pointer would be UB, so we always assume non-null here.
if (auto derivedOp = mlir::dyn_cast<cir::DerivedClassAddrOp>(defOp)) {
LDBG() << "Walking past DerivedClassAddrOp";
std::optional<int64_t> baseOffset = derivedOp.getOffset().tryZExtValue();
diff --git a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
index eaf9dfd6d7900b..5854c6847dd2bd 100644
--- a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
+++ b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
@@ -345,6 +345,31 @@ cir.func @base_and_derived_class_addr() {
// -----
+// CHECK-LABEL: Testing : "base_and_derived_class_maybe_null"
+// CHECK-DAG: d#0 <-> base#0: MayAlias
+// CHECK-DAG: d#0 <-> derived#0: MustAlias
+// CHECK-DAG: base#0 <-> derived#0: MayAlias
+// CHECK-DAG: d#0 <-> first#0: MustAlias
+// CHECK-DAG: base#0 <-> first#0: MayAlias
+// CHECK-DAG: derived#0 <-> first#0: MustAlias
+
+!u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+!rec_Base = !cir.struct<"Base" {data !u8i}>
+!rec_Derived = !cir.struct<"Derived" {data !s32i, data !rec_Base}>
+cir.func @base_and_derived_class_maybe_null() {
+ %d = cir.alloca "d" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d"}
+ %base = cir.base_class_addr %d : !cir.ptr<!rec_Derived> [4]
+ -> !cir.ptr<!rec_Base> {test.ptr = "base"}
+ %derived = cir.derived_class_addr %base : !cir.ptr<!rec_Base> [4]
+ -> !cir.ptr<!rec_Derived> {test.ptr = "derived"}
+ %first = cir.get_member %d[0] {name = "x", test.ptr = "first"}
+ : !cir.ptr<!rec_Derived> -> !cir.ptr<!s32i>
+ cir.return
+}
+
+// -----
+
// CHECK-LABEL: Testing : "complex_parts_distinct_allocas"
// CHECK-DAG: c1#0 <-> c2#0: NoAlias
// CHECK-DAG: c1#0 <-> real1#0: MustAlias
@@ -421,7 +446,7 @@ cir.func @bitint_struct_elements(%s: !cir.ptr<!rec_S>)
%py = cir.get_member %s[1] {name = "y", test.ptr = "py"}
: !cir.ptr<!rec_S> -> !cir.ptr<!cir.int<s, 19, bitint>>
%one = cir.const #cir.int<1> : !s32i
- %px_plus_one = cir.ptr_stride %px, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
+ %px_plus_one = cir.ptr_stride %px, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
-> !cir.ptr<!cir.int<s, 19, bitint>>
{test.ptr = "px_plus_one"}
%pxb = cir.cast bitcast %px : !cir.ptr<!cir.int<s, 19, bitint>> -> !cir.ptr<!u8i> {test.ptr = "pxb"}
@@ -462,7 +487,7 @@ cir.func @bitint_array_elements(%arr: !cir.ptr<!cir.array<!cir.int<s, 19, bitint
%one = cir.const #cir.int<1> : !s32i
%elem1 = cir.get_element %arr[%one : !s32i] {test.ptr = "elem1"}
: !cir.ptr<!cir.array<!cir.int<s, 19, bitint> x 4>> -> !cir.ptr<!cir.int<s, 19, bitint>>
- %decay_plus_one = cir.ptr_stride %decay, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
+ %decay_plus_one = cir.ptr_stride %decay, %one : (!cir.ptr<!cir.int<s, 19, bitint>>, !s32i)
-> !cir.ptr<!cir.int<s, 19, bitint>>
{test.ptr = "decay_plus_one"}
%db = cir.cast bitcast %decay : !cir.ptr<!cir.int<s, 19, bitint>> -> !cir.ptr<!u8i> {test.ptr = "db"}
>From 7ac41e27f036abb29002e26fea3924e5a7afd2f9 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Tue, 15 Sep 2026 17:46:45 -0700
Subject: [PATCH 4/4] Update tests to avoid alloca use
---
.../alias-analysis-underlying-object.cir | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
index 5854c6847dd2bd..f22b7b7b1d45fe 100644
--- a/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
+++ b/clang/test/CIR/Analysis/alias-analysis-underlying-object.cir
@@ -321,19 +321,19 @@ cir.func @base_class_addr_distinct_allocas() {
// -----
// CHECK-LABEL: Testing : "base_and_derived_class_addr"
-// CHECK-DAG: d#0 <-> base#0: MayAlias
-// CHECK-DAG: d#0 <-> derived#0: MustAlias
// CHECK-DAG: base#0 <-> derived#0: MayAlias
-// CHECK-DAG: d#0 <-> first#0: MustAlias
// CHECK-DAG: base#0 <-> first#0: MayAlias
// CHECK-DAG: derived#0 <-> first#0: MustAlias
+// CHECK-DAG: base#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: derived#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: first#0 <-> func.region0#0: MustAlias
!u8i = !cir.int<u, 8>
!s32i = !cir.int<s, 32>
!rec_Base = !cir.struct<"Base" {data !u8i}>
!rec_Derived = !cir.struct<"Derived" {data !s32i, data !rec_Base}>
-cir.func @base_and_derived_class_addr() {
- %d = cir.alloca "d" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d"}
+cir.func @base_and_derived_class_addr(%d: !cir.ptr<!rec_Derived>)
+ attributes {test.ptr = "func"} {
%base = cir.base_class_addr %d : !cir.ptr<!rec_Derived> nonnull [4]
-> !cir.ptr<!rec_Base> {test.ptr = "base"}
%derived = cir.derived_class_addr %base : !cir.ptr<!rec_Base> nonnull [4]
@@ -346,19 +346,19 @@ cir.func @base_and_derived_class_addr() {
// -----
// CHECK-LABEL: Testing : "base_and_derived_class_maybe_null"
-// CHECK-DAG: d#0 <-> base#0: MayAlias
-// CHECK-DAG: d#0 <-> derived#0: MustAlias
// CHECK-DAG: base#0 <-> derived#0: MayAlias
-// CHECK-DAG: d#0 <-> first#0: MustAlias
// CHECK-DAG: base#0 <-> first#0: MayAlias
// CHECK-DAG: derived#0 <-> first#0: MustAlias
+// CHECK-DAG: base#0 <-> func.region0#0: MayAlias
+// CHECK-DAG: derived#0 <-> func.region0#0: MustAlias
+// CHECK-DAG: first#0 <-> func.region0#0: MustAlias
!u8i = !cir.int<u, 8>
!s32i = !cir.int<s, 32>
!rec_Base = !cir.struct<"Base" {data !u8i}>
!rec_Derived = !cir.struct<"Derived" {data !s32i, data !rec_Base}>
-cir.func @base_and_derived_class_maybe_null() {
- %d = cir.alloca "d" align(4) : !cir.ptr<!rec_Derived> {test.ptr = "d"}
+cir.func @base_and_derived_class_maybe_null(%d: !cir.ptr<!rec_Derived>)
+ attributes {test.ptr = "func"} {
%base = cir.base_class_addr %d : !cir.ptr<!rec_Derived> [4]
-> !cir.ptr<!rec_Base> {test.ptr = "base"}
%derived = cir.derived_class_addr %base : !cir.ptr<!rec_Base> [4]
More information about the cfe-commits
mailing list