[llvm] bf1b598 - [InferAlignment] Do not use intrinsic align attributes as base pointer alignment (#221018)

via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 06:23:10 PDT 2026


Author: Stanislav Bardyuk
Date: 2026-09-11T13:23:05Z
New Revision: bf1b598fff82413353b3ad45c4df06b243c1e6a1

URL: https://github.com/llvm/llvm-project/commit/bf1b598fff82413353b3ad45c4df06b243c1e6a1
DIFF: https://github.com/llvm/llvm-project/commit/bf1b598fff82413353b3ad45c4df06b243c1e6a1.diff

LOG: [InferAlignment] Do not use intrinsic align attributes as base pointer alignment (#221018)

InferAlignment propagates alignment between accesses of the same base
pointer: each load/store records how aligned the base must be, and later
accesses of that base are upgraded. Since #156057 (masked.load/store)
and
#217172 (memcpy/memmove/memset), the align parameter attribute of these
intrinsics is fed into the same table.

That is not sound. A memset with a zero length (or a masked store with
an
all-false mask) does not access memory, and its pointer argument is
allowed
to be poison, so an align attribute on it proves nothing about the
pointer.
For

```llvm
define void @test(ptr %p, i64 %n) {
  call void @llvm.memset(ptr align 16 %p, i8 0, i64 %n, i1 false)
  store i32 0, ptr %p
  ret void
}
```

the store was upgraded to align 16, which is UB when %p is only 4-byte
aligned and %n is 0.

Stop presenting the attribute alignment to the base pointer logic: the
intrinsic branch now passes align 1, the same convention the
and(ptrtoint)/trunc(ptrtoint) patterns use for "this instruction proves
nothing". The attribute can still be improved from a dominating real
access or from known bits. A constant non-zero length would be a
provable access, but it is deliberately not special-cased, as suggested
in the issue.

Fixes #220520

Added: 
    

Modified: 
    llvm/lib/Transforms/Scalar/InferAlignment.cpp
    llvm/test/Transforms/InferAlignment/masked.ll
    llvm/test/Transforms/InferAlignment/memintrinsics.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/InferAlignment.cpp b/llvm/lib/Transforms/Scalar/InferAlignment.cpp
index 9addfbd725389..57a1119c2bfa9 100644
--- a/llvm/lib/Transforms/Scalar/InferAlignment.cpp
+++ b/llvm/lib/Transforms/Scalar/InferAlignment.cpp
@@ -80,8 +80,12 @@ static bool tryToImproveAlign(
     if (!Arg->getType()->isPointerTy())
       continue;
 
+    // The align attribute only proves the alignment if passing poison is UB
+    // (e.g. noundef): otherwise a zero-length memset may pass an unaligned,
+    // poison pointer, so it must not seed the base pointer alignment.
     Align OldAlign = II->getParamAlign(ArgNo).valueOrOne();
-    Align NewAlign = Fn(Arg, OldAlign, Align(1));
+    Align KnownAlign = II->isPassingUndefUB(ArgNo) ? OldAlign : Align(1);
+    Align NewAlign = Fn(Arg, KnownAlign, Align(1));
     if (NewAlign <= OldAlign)
       continue;
 

diff  --git a/llvm/test/Transforms/InferAlignment/masked.ll b/llvm/test/Transforms/InferAlignment/masked.ll
index 12b5c2c262d9b..19449ea5ebc23 100644
--- a/llvm/test/Transforms/InferAlignment/masked.ll
+++ b/llvm/test/Transforms/InferAlignment/masked.ll
@@ -41,6 +41,33 @@ entry:
   ret <2 x i32> %masked_load
 }
 
+; The align attribute on a masked store is not proof of an access (the mask
+; may be all-false), so it must not be used to infer the alignment of other
+; accesses to the same pointer.
+define void @store_attr_does_not_seed_store(<2 x i1> %mask, <2 x i32> %val, ptr %ptr) {
+; CHECK-LABEL: define void @store_attr_does_not_seed_store(
+; CHECK-SAME: <2 x i1> [[MASK:%.*]], <2 x i32> [[VAL:%.*]], ptr [[PTR:%.*]]) {
+; CHECK-NEXT:    call void @llvm.masked.store.v2i32.p0(<2 x i32> [[VAL]], ptr align 64 [[PTR]], <2 x i1> [[MASK]])
+; CHECK-NEXT:    store i32 0, ptr [[PTR]], align 4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.masked.store.v2i32.p0(<2 x i32> %val, ptr %ptr, i32 64, <2 x i1> %mask)
+  store i32 0, ptr %ptr
+  ret void
+}
+
+define <2 x i32> @load_attr_does_not_seed_load(<2 x i1> %mask, ptr %ptr) {
+; CHECK-LABEL: define <2 x i32> @load_attr_does_not_seed_load(
+; CHECK-SAME: <2 x i1> [[MASK:%.*]], ptr [[PTR:%.*]]) {
+; CHECK-NEXT:    [[MASKED_LOAD:%.*]] = call <2 x i32> @llvm.masked.load.v2i32.p0(ptr align 64 [[PTR]], <2 x i1> [[MASK]], <2 x i32> poison)
+; CHECK-NEXT:    [[V:%.*]] = load <2 x i32>, ptr [[PTR]], align 8
+; CHECK-NEXT:    ret <2 x i32> [[V]]
+;
+  %masked_load = call <2 x i32> @llvm.masked.load.v2i32.p0(ptr %ptr, i32 64, <2 x i1> %mask, <2 x i32> poison)
+  %v = load <2 x i32>, ptr %ptr
+  ret <2 x i32> %v
+}
+
 declare void @llvm.assume(i1)
 declare <2 x i32> @llvm.masked.load.v2i32.p0(ptr, i32, <2 x i1>, <2 x i32>)
 declare void @llvm.masked.store.v2i32.p0(<2 x i32>, ptr, i32, <2 x i1>)

diff  --git a/llvm/test/Transforms/InferAlignment/memintrinsics.ll b/llvm/test/Transforms/InferAlignment/memintrinsics.ll
index ec9817a698078..66ec1824c72cf 100644
--- a/llvm/test/Transforms/InferAlignment/memintrinsics.ll
+++ b/llvm/test/Transforms/InferAlignment/memintrinsics.ll
@@ -44,3 +44,99 @@ define void @memset(i64 %len) {
   call void @llvm.memset.p0.i64(ptr %dst, i8 0, i64 %len, i1 false)
   ret void
 }
+
+; The align attribute on a memory intrinsic is not proof of an access (the
+; length may be zero and the pointer poison), so it must not be used to
+; infer the alignment of other accesses to the same pointer.
+define void @memset_attr_does_not_seed_store(ptr %p, i64 %len) {
+; CHECK-LABEL: define void @memset_attr_does_not_seed_store(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) {
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 16 [[P]], i8 0, i64 [[LEN]], i1 false)
+; CHECK-NEXT:    store i32 0, ptr [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.memset.p0.i64(ptr align 16 %p, i8 0, i64 %len, i1 false)
+  store i32 0, ptr %p
+  ret void
+}
+
+define i32 @memcpy_attr_does_not_seed_load_store(ptr %dst, ptr %src, i64 %len) {
+; CHECK-LABEL: define i32 @memcpy_attr_does_not_seed_load_store(
+; CHECK-SAME: ptr [[DST:%.*]], ptr [[SRC:%.*]], i64 [[LEN:%.*]]) {
+; CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr align 16 [[DST]], ptr align 32 [[SRC]], i64 [[LEN]], i1 false)
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[SRC]], align 4
+; CHECK-NEXT:    store i32 [[V]], ptr [[DST]], align 4
+; CHECK-NEXT:    ret i32 [[V]]
+;
+  call void @llvm.memcpy.p0.p0.i64(ptr align 16 %dst, ptr align 32 %src, i64 %len, i1 false)
+  %v = load i32, ptr %src
+  store i32 %v, ptr %dst
+  ret i32 %v
+}
+
+define void @memset_attr_does_not_seed_dominated_store(ptr %p, i64 %len, i1 %c) {
+; CHECK-LABEL: define void @memset_attr_does_not_seed_dominated_store(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]], i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 16 [[P]], i8 0, i64 [[LEN]], i1 false)
+; CHECK-NEXT:    br i1 [[C]], label %[[THEN:.*]], label %[[EXIT:.*]]
+; CHECK:       [[THEN]]:
+; CHECK-NEXT:    [[Q:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 8
+; CHECK-NEXT:    store i32 0, ptr [[Q]], align 4
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  call void @llvm.memset.p0.i64(ptr align 16 %p, i8 0, i64 %len, i1 false)
+  br i1 %c, label %then, label %exit
+
+then:
+  %q = getelementptr inbounds i8, ptr %p, i64 8
+  store i32 0, ptr %q
+  br label %exit
+
+exit:
+  ret void
+}
+
+; With noundef (or dereferenceable) the pointer cannot be poison, so the
+; align attribute is a real guarantee and may be used.
+define void @memset_noundef_attr_seeds_store(ptr %p, i64 %len) {
+; CHECK-LABEL: define void @memset_noundef_attr_seeds_store(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) {
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr noundef align 16 [[P]], i8 0, i64 [[LEN]], i1 false)
+; CHECK-NEXT:    store i32 0, ptr [[P]], align 16
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.memset.p0.i64(ptr noundef align 16 %p, i8 0, i64 %len, i1 false)
+  store i32 0, ptr %p
+  ret void
+}
+
+define i32 @memcpy_dereferenceable_attr_seeds_load_store(ptr %dst, ptr %src, i64 %len) {
+; CHECK-LABEL: define i32 @memcpy_dereferenceable_attr_seeds_load_store(
+; CHECK-SAME: ptr [[DST:%.*]], ptr [[SRC:%.*]], i64 [[LEN:%.*]]) {
+; CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr align 16 dereferenceable(16) [[DST]], ptr noundef align 32 [[SRC]], i64 [[LEN]], i1 false)
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[SRC]], align 32
+; CHECK-NEXT:    store i32 [[V]], ptr [[DST]], align 16
+; CHECK-NEXT:    ret i32 [[V]]
+;
+  call void @llvm.memcpy.p0.p0.i64(ptr align 16 dereferenceable(16) %dst, ptr noundef align 32 %src, i64 %len, i1 false)
+  %v = load i32, ptr %src
+  store i32 %v, ptr %dst
+  ret i32 %v
+}
+
+; A real store does prove the alignment, so the memset may still use it.
+define void @store_seeds_memset(ptr %p, i64 %len) {
+; CHECK-LABEL: define void @store_seeds_memset(
+; CHECK-SAME: ptr [[P:%.*]], i64 [[LEN:%.*]]) {
+; CHECK-NEXT:    store i32 0, ptr [[P]], align 16
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr align 16 [[P]], i8 0, i64 [[LEN]], i1 false)
+; CHECK-NEXT:    ret void
+;
+  store i32 0, ptr %p, align 16
+  call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 %len, i1 false)
+  ret void
+}


        


More information about the llvm-commits mailing list