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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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]

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to