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
