Skip to content

Conversation

@antoniofrighetto
Copy link
Contributor

@antoniofrighetto antoniofrighetto commented Feb 27, 2025

Deduce errnomem for a given memory location: accesses to __errno_location et alia do access errnomem memory.

@llvmbot
Copy link
Member

llvmbot commented Feb 27, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Antonio Frighetto (antoniofrighetto)

Changes

Refine errnomem for a given memory location. In particular, accesses to __errno_location et alia do alias errno, whereas locally-invariant memory should never alias errno. Likewise, memory accesses larger than sizeof(int) should not alias errno.


Full diff: https://github.com/llvm/llvm-project/pull/129067.diff

7 Files Affected:

  • (modified) llvm/lib/Transforms/IPO/FunctionAttrs.cpp (+32-2)
  • (modified) llvm/test/Transforms/FunctionAttrs/argmemonly.ll (+7-7)
  • (added) llvm/test/Transforms/FunctionAttrs/errnomemnone.ll (+30)
  • (modified) llvm/test/Transforms/FunctionAttrs/initializes.ll (+12-9)
  • (added) llvm/test/Transforms/FunctionAttrs/read-write-errnomem.ll (+71)
  • (modified) llvm/test/Transforms/FunctionAttrs/readattrs.ll (+6-4)
  • (modified) llvm/test/Transforms/PhaseOrdering/pr95152.ll (+1-1)
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
index a63e38a7d98ad..bebcb4057acba 100644
--- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
+++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp
@@ -118,16 +118,31 @@ static void addLocAccess(MemoryEffects &ME, const MemoryLocation &Loc,
                          ModRefInfo MR, AAResults &AAR) {
   // Ignore accesses to known-invariant or local memory.
   MR &= AAR.getModRefInfoMask(Loc, /*IgnoreLocal=*/true);
-  if (isNoModRef(MR))
+  if (isNoModRef(MR)) {
+    // Mask out errno, can never alias this known-invariant memory location.
+    ME -= ME.errnoMemOnly();
     return;
+  }
 
   const Value *UO = getUnderlyingObjectAggressive(Loc.Ptr);
-  if (isa<AllocaInst>(UO))
+  if (isa<AllocaInst>(UO)) {
+    ME -= ME.errnoMemOnly();
     return;
+  }
   if (isa<Argument>(UO)) {
     ME |= MemoryEffects::argMemOnly(MR);
     return;
   }
+  if (auto *CI = dyn_cast<CallInst>(UO)) {
+    if (auto *Callee = CI->getCalledFunction()) {
+      static constexpr auto ErrnoFnNames = {"__errno_location", "_errno",
+                                            "__errno", "___errno"};
+      if (is_contained(ErrnoFnNames, Callee->getName())) {
+        ME |= MemoryEffects::errnoMemOnly(MR);
+        return;
+      }
+    }
+  }
 
   // If it's not an identified object, it might be an argument.
   if (!isIdentifiedObject(UO))
@@ -250,7 +265,22 @@ checkFunctionMemoryAccess(Function &F, bool ThisBody, AAResults &AAR,
     if (I.isVolatile())
       ME |= MemoryEffects::inaccessibleMemOnly(MR);
 
+    // Refine memory effects for the given location.
     addLocAccess(ME, *Loc, MR, AAR);
+
+    // Last attempt if errnomem has not been inferred yet: accesses larger than
+    // integers cannot alias errno.
+    if (ME.getModRef(IRMemLocation::ErrnoMem) != ModRefInfo::NoModRef) {
+      if (isa<LoadInst>(I) || isa<StoreInst>(I)) {
+        auto *Ty = isa<LoadInst>(I)
+                       ? I.getType()
+                       : cast<StoreInst>(I).getValueOperand()->getType();
+        if (!Ty->isPtrOrPtrVectorTy() &&
+            Loc->Size != MemoryLocation::UnknownSize &&
+            Loc->Size.getValue() > sizeof(int))
+          ME = ME.getWithoutLoc(IRMemLocation::ErrnoMem);
+      }
+    }
   }
 
   return {OrigME & ME, RecursiveArgME};
diff --git a/llvm/test/Transforms/FunctionAttrs/argmemonly.ll b/llvm/test/Transforms/FunctionAttrs/argmemonly.ll
index 42e0e94c1cee3..fe096fcfc43b3 100644
--- a/llvm/test/Transforms/FunctionAttrs/argmemonly.ll
+++ b/llvm/test/Transforms/FunctionAttrs/argmemonly.ll
@@ -334,7 +334,7 @@ define void @test_inaccessiblememonly_readonly() {
 ; FNATTRS: Function Attrs: nofree memory(inaccessiblemem: read)
 ; FNATTRS-LABEL: define void @test_inaccessiblememonly_readonly
 ; FNATTRS-SAME: () #[[ATTR13:[0-9]+]] {
-; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR19:[0-9]+]]
+; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR20:[0-9]+]]
 ; FNATTRS-NEXT:    ret void
 ;
 ; ATTRIBUTOR: Function Attrs: nosync memory(inaccessiblemem: read)
@@ -352,7 +352,7 @@ define void @test_inaccessibleorargmemonly_readonly(ptr %arg) {
 ; FNATTRS-LABEL: define void @test_inaccessibleorargmemonly_readonly
 ; FNATTRS-SAME: (ptr readonly captures(none) [[ARG:%.*]]) #[[ATTR14:[0-9]+]] {
 ; FNATTRS-NEXT:    [[TMP1:%.*]] = load i32, ptr [[ARG]], align 4
-; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR19]]
+; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR20]]
 ; FNATTRS-NEXT:    ret void
 ;
 ; ATTRIBUTOR: Function Attrs: nosync memory(argmem: read, inaccessiblemem: read)
@@ -372,7 +372,7 @@ define void @test_inaccessibleorargmemonly_readwrite(ptr %arg) {
 ; FNATTRS-LABEL: define void @test_inaccessibleorargmemonly_readwrite
 ; FNATTRS-SAME: (ptr writeonly captures(none) initializes((0, 4)) [[ARG:%.*]]) #[[ATTR15:[0-9]+]] {
 ; FNATTRS-NEXT:    store i32 0, ptr [[ARG]], align 4
-; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR19]]
+; FNATTRS-NEXT:    call void @fn_inaccessiblememonly() #[[ATTR20]]
 ; FNATTRS-NEXT:    ret void
 ;
 ; ATTRIBUTOR: Function Attrs: nosync memory(argmem: readwrite, inaccessiblemem: readwrite)
@@ -518,9 +518,9 @@ entry:
 
 ; FIXME: This could be `memory(argmem: read)`.
 define i64 @select_different_obj(i1 %c, ptr %p, ptr %p2) {
-; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read, inaccessiblemem: none)
+; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read, inaccessiblemem: none, errnomem: none)
 ; FNATTRS-LABEL: define i64 @select_different_obj
-; FNATTRS-SAME: (i1 [[C:%.*]], ptr readonly captures(none) [[P:%.*]], ptr readonly captures(none) [[P2:%.*]]) #[[ATTR3]] {
+; FNATTRS-SAME: (i1 [[C:%.*]], ptr readonly captures(none) [[P:%.*]], ptr readonly captures(none) [[P2:%.*]]) #[[ATTR19:[0-9]+]] {
 ; FNATTRS-NEXT:  entry:
 ; FNATTRS-NEXT:    [[P3:%.*]] = select i1 [[C]], ptr [[P]], ptr [[P2]]
 ; FNATTRS-NEXT:    [[R:%.*]] = load i64, ptr [[P3]], align 4
@@ -580,9 +580,9 @@ join:
 
 ; FIXME: This could be `memory(argmem: read)`.
 define i64 @phi_different_obj(i1 %c, ptr %p, ptr %p2) {
-; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read, inaccessiblemem: none)
+; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(read, inaccessiblemem: none, errnomem: none)
 ; FNATTRS-LABEL: define i64 @phi_different_obj
-; FNATTRS-SAME: (i1 [[C:%.*]], ptr readonly captures(none) [[P:%.*]], ptr readonly captures(none) [[P2:%.*]]) #[[ATTR3]] {
+; FNATTRS-SAME: (i1 [[C:%.*]], ptr readonly captures(none) [[P:%.*]], ptr readonly captures(none) [[P2:%.*]]) #[[ATTR19]] {
 ; FNATTRS-NEXT:  entry:
 ; FNATTRS-NEXT:    br i1 [[C]], label [[IF:%.*]], label [[JOIN:%.*]]
 ; FNATTRS:       if:
diff --git a/llvm/test/Transforms/FunctionAttrs/errnomemnone.ll b/llvm/test/Transforms/FunctionAttrs/errnomemnone.ll
new file mode 100644
index 0000000000000..be965e5f55340
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/errnomemnone.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals --version 2
+; RUN: opt -passes=function-attrs -S < %s | FileCheck --check-prefixes=FNATTRS %s
+
+; Stack-allocated memory cannot alias errno.
+define float @cannot_alias_errno(float %f) {
+; FNATTRS: Function Attrs: memory(readwrite, errnomem: none)
+; FNATTRS-LABEL: define float @cannot_alias_errno
+; FNATTRS-SAME: (float [[F:%.*]]) #[[ATTR0:[0-9]+]] {
+  %p = alloca float
+  call void @escape(ptr %p)
+  store float 0.0, ptr %p
+  call float @sinf(float %f)
+  %v = load float, ptr %p
+  ret float %v
+}
+
+; Accesses to memory larger than integer-size cannot alias errno.
+define double @cannot_alias_errno_2(ptr %p, float %f) {
+; FNATTRS: Function Attrs: memory(readwrite, errnomem: none)
+; FNATTRS-LABEL: define double @cannot_alias_errno_2
+; FNATTRS-SAME: (ptr [[P:%.*]], float [[F:%.*]]) #[[ATTR0]] {
+  call void @escape(ptr %p)
+  store double 0.0, ptr %p
+  call float @sinf(float %f)
+  %v = load double, ptr %p
+  ret double %v
+}
+
+declare void @escape(ptr %p)
+declare float @sinf(float) memory(errnomem: write)
diff --git a/llvm/test/Transforms/FunctionAttrs/initializes.ll b/llvm/test/Transforms/FunctionAttrs/initializes.ll
index 861c61d683ae0..b3362589d3f8b 100644
--- a/llvm/test/Transforms/FunctionAttrs/initializes.ll
+++ b/llvm/test/Transforms/FunctionAttrs/initializes.ll
@@ -91,8 +91,9 @@ define void @partial_load_before_store(ptr %p) {
 declare void @use(ptr)
 
 define void @call_clobber(ptr %p) {
+; CHECK: Function Attrs: memory(readwrite, errnomem: none)
 ; CHECK-LABEL: define void @call_clobber(
-; CHECK-SAME: ptr [[P:%.*]]) {
+; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] {
 ; CHECK-NEXT:    call void @use(ptr [[P]])
 ; CHECK-NEXT:    store i64 123, ptr [[P]], align 4
 ; CHECK-NEXT:    ret void
@@ -130,7 +131,7 @@ define void @store_offset(ptr %p) {
 define void @store_volatile(ptr %p) {
 ; CHECK: Function Attrs: nofree norecurse nounwind memory(argmem: readwrite, inaccessiblemem: readwrite)
 ; CHECK-LABEL: define void @store_volatile(
-; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR3:[0-9]+]] {
 ; CHECK-NEXT:    [[G:%.*]] = getelementptr i8, ptr [[P]], i64 8
 ; CHECK-NEXT:    store volatile i32 123, ptr [[G]], align 4
 ; CHECK-NEXT:    ret void
@@ -241,8 +242,9 @@ end:
 }
 
 define void @call_clobber_on_one_branch(ptr %p, i1 %i) {
+; CHECK: Function Attrs: memory(readwrite, errnomem: none)
 ; CHECK-LABEL: define void @call_clobber_on_one_branch(
-; CHECK-SAME: ptr [[P:%.*]], i1 [[I:%.*]]) {
+; CHECK-SAME: ptr [[P:%.*]], i1 [[I:%.*]]) #[[ATTR2]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    br i1 [[I]], label [[BB1:%.*]], label [[BB2:%.*]]
 ; CHECK:       bb1:
@@ -306,8 +308,9 @@ define void @non_const_gep(ptr %p, i64 %i) {
 }
 
 define void @call_clobber_in_entry_block(ptr %p, i1 %i) {
+; CHECK: Function Attrs: memory(readwrite, errnomem: none)
 ; CHECK-LABEL: define void @call_clobber_in_entry_block(
-; CHECK-SAME: ptr [[P:%.*]], i1 [[I:%.*]]) {
+; CHECK-SAME: ptr [[P:%.*]], i1 [[I:%.*]]) #[[ATTR2]] {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    call void @use(ptr [[P]])
 ; CHECK-NEXT:    br i1 [[I]], label [[BB1:%.*]], label [[BB2:%.*]]
@@ -384,7 +387,7 @@ define void @call_initializes_escape_bundle(ptr %p) {
 define void @access_bundle() {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
 ; CHECK-LABEL: define void @access_bundle(
-; CHECK-SAME: ) #[[ATTR3:[0-9]+]] {
+; CHECK-SAME: ) #[[ATTR4:[0-9]+]] {
 ; CHECK-NEXT:    [[SINK:%.*]] = alloca i64, align 8
 ; CHECK-NEXT:    store i64 123, ptr [[SINK]], align 4
 ; CHECK-NEXT:    ret void
@@ -397,7 +400,7 @@ define void @access_bundle() {
 define void @call_operand_bundle(ptr %p) {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn
 ; CHECK-LABEL: define void @call_operand_bundle(
-; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR4:[0-9]+]] {
+; CHECK-SAME: ptr [[P:%.*]]) #[[ATTR5:[0-9]+]] {
 ; CHECK-NEXT:    call void @access_bundle() [ "unknown"(ptr [[P]]) ]
 ; CHECK-NEXT:    ret void
 ;
@@ -445,7 +448,7 @@ define void @memset_neg(ptr %p) {
 define void @memset_volatile(ptr %p) {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn memory(argmem: write)
 ; CHECK-LABEL: define void @memset_volatile(
-; CHECK-SAME: ptr writeonly [[P:%.*]]) #[[ATTR5:[0-9]+]] {
+; CHECK-SAME: ptr writeonly [[P:%.*]]) #[[ATTR6:[0-9]+]] {
 ; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr [[P]], i8 2, i64 9, i1 true)
 ; CHECK-NEXT:    ret void
 ;
@@ -480,7 +483,7 @@ define void @memcpy(ptr %p, ptr %p2) {
 define void @memcpy_volatile(ptr %p, ptr %p2) {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn memory(argmem: readwrite)
 ; CHECK-LABEL: define void @memcpy_volatile(
-; CHECK-SAME: ptr writeonly [[P:%.*]], ptr readonly [[P2:%.*]]) #[[ATTR6:[0-9]+]] {
+; CHECK-SAME: ptr writeonly [[P:%.*]], ptr readonly [[P2:%.*]]) #[[ATTR7:[0-9]+]] {
 ; CHECK-NEXT:    call void @llvm.memcpy.p0.p0.i64(ptr [[P]], ptr [[P2]], i64 9, i1 true)
 ; CHECK-NEXT:    ret void
 ;
@@ -543,7 +546,7 @@ define void @memmove(ptr %p, ptr %p2) {
 define void @memmove_volatile(ptr %p, ptr %p2) {
 ; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn memory(argmem: readwrite)
 ; CHECK-LABEL: define void @memmove_volatile(
-; CHECK-SAME: ptr writeonly [[P:%.*]], ptr readonly [[P2:%.*]]) #[[ATTR6]] {
+; CHECK-SAME: ptr writeonly [[P:%.*]], ptr readonly [[P2:%.*]]) #[[ATTR7]] {
 ; CHECK-NEXT:    call void @llvm.memmove.p0.p0.i64(ptr [[P]], ptr [[P2]], i64 9, i1 true)
 ; CHECK-NEXT:    ret void
 ;
diff --git a/llvm/test/Transforms/FunctionAttrs/read-write-errnomem.ll b/llvm/test/Transforms/FunctionAttrs/read-write-errnomem.ll
new file mode 100644
index 0000000000000..db8effc45442f
--- /dev/null
+++ b/llvm/test/Transforms/FunctionAttrs/read-write-errnomem.ll
@@ -0,0 +1,71 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes --check-globals --version 2
+; RUN: opt -passes=function-attrs -S < %s | FileCheck --check-prefixes=FNATTRS %s
+; RUN: opt -passes=attributor-light -S < %s | FileCheck --check-prefixes=ATTRIBUTOR %s
+
+define i32 @test_read_errno() {
+; FNATTRS: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(errnomem: read)
+; FNATTRS-LABEL: define i32 @test_read_errno
+; FNATTRS-SAME: () #[[ATTR0:[0-9]+]] {
+; FNATTRS-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4:[0-9]+]]
+; FNATTRS-NEXT:    [[ERRNO:%.*]] = load i32, ptr [[CALL]], align 4
+; FNATTRS-NEXT:    ret i32 [[ERRNO]]
+;
+; ATTRIBUTOR: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(read)
+; ATTRIBUTOR-LABEL: define i32 @test_read_errno
+; ATTRIBUTOR-SAME: () #[[ATTR0:[0-9]+]] {
+; ATTRIBUTOR-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4:[0-9]+]]
+; ATTRIBUTOR-NEXT:    [[ERRNO:%.*]] = load i32, ptr [[CALL]], align 4
+; ATTRIBUTOR-NEXT:    ret i32 [[ERRNO]]
+;
+  %call = call ptr @__errno_location() #2
+  %errno = load i32, ptr %call
+  ret i32 %errno
+}
+
+define ptr @test_write_errno() {
+; FNATTRS: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(errnomem: write)
+; FNATTRS-LABEL: define noundef ptr @test_write_errno
+; FNATTRS-SAME: () #[[ATTR1:[0-9]+]] {
+; FNATTRS-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4]]
+; FNATTRS-NEXT:    store i32 0, ptr [[CALL]], align 4
+; FNATTRS-NEXT:    ret ptr [[CALL]]
+;
+; ATTRIBUTOR: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(write)
+; ATTRIBUTOR-LABEL: define ptr @test_write_errno
+; ATTRIBUTOR-SAME: () #[[ATTR1:[0-9]+]] {
+; ATTRIBUTOR-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4]]
+; ATTRIBUTOR-NEXT:    store i32 0, ptr [[CALL]], align 4
+; ATTRIBUTOR-NEXT:    ret ptr [[CALL]]
+;
+  %call = call ptr @__errno_location() #2
+  store i32 0, ptr %call
+  ret ptr %call
+}
+
+define i32 @test_readwrite_errno() {
+; FNATTRS: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(errnomem: readwrite)
+; FNATTRS-LABEL: define i32 @test_readwrite_errno
+; FNATTRS-SAME: () #[[ATTR2:[0-9]+]] {
+; FNATTRS-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4]]
+; FNATTRS-NEXT:    store i32 0, ptr [[CALL]], align 4
+; FNATTRS-NEXT:    [[ERRNO:%.*]] = load i32, ptr [[CALL]], align 4
+; FNATTRS-NEXT:    ret i32 [[ERRNO]]
+;
+; ATTRIBUTOR: Function Attrs: mustprogress nofree nosync nounwind willreturn
+; ATTRIBUTOR-LABEL: define i32 @test_readwrite_errno
+; ATTRIBUTOR-SAME: () #[[ATTR2:[0-9]+]] {
+; ATTRIBUTOR-NEXT:    [[CALL:%.*]] = call ptr @__errno_location() #[[ATTR4]]
+; ATTRIBUTOR-NEXT:    store i32 0, ptr [[CALL]], align 4
+; ATTRIBUTOR-NEXT:    [[ERRNO:%.*]] = load i32, ptr [[CALL]], align 4
+; ATTRIBUTOR-NEXT:    ret i32 [[ERRNO]]
+;
+  %call = call ptr @__errno_location() #2
+  store i32 0, ptr %call
+  %errno = load i32, ptr %call
+  ret i32 %errno
+}
+
+declare ptr @__errno_location() #1
+
+attributes #1 = { mustprogress nofree nosync nounwind willreturn memory(none) }
+attributes #2 = { nounwind willreturn memory(none) }
diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll
index b24c097ad54d0..ec6e116837478 100644
--- a/llvm/test/Transforms/FunctionAttrs/readattrs.ll
+++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll
@@ -376,8 +376,9 @@ declare void @escape_readonly_ptr(ptr %addr, ptr readonly %ptr)
 ; is marked as readnone/only. However, the functions can write the pointer into
 ; %addr, causing the store to write to %escaped_then_written.
 define void @unsound_readnone(ptr %ignored, ptr %escaped_then_written) {
+; FNATTRS: Function Attrs: memory(readwrite, errnomem: write)
 ; FNATTRS-LABEL: define {{[^@]+}}@unsound_readnone
-; FNATTRS-SAME: (ptr readnone captures(none) [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) {
+; FNATTRS-SAME: (ptr readnone captures(none) [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) #[[ATTR14:[0-9]+]] {
 ; FNATTRS-NEXT:    [[ADDR:%.*]] = alloca ptr, align 8
 ; FNATTRS-NEXT:    call void @escape_readnone_ptr(ptr [[ADDR]], ptr [[ESCAPED_THEN_WRITTEN]])
 ; FNATTRS-NEXT:    [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8
@@ -408,8 +409,9 @@ define void @unsound_readnone(ptr %ignored, ptr %escaped_then_written) {
 }
 
 define void @unsound_readonly(ptr %ignored, ptr %escaped_then_written) {
+; FNATTRS: Function Attrs: memory(readwrite, errnomem: write)
 ; FNATTRS-LABEL: define {{[^@]+}}@unsound_readonly
-; FNATTRS-SAME: (ptr readnone captures(none) [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) {
+; FNATTRS-SAME: (ptr readnone captures(none) [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) #[[ATTR14]] {
 ; FNATTRS-NEXT:    [[ADDR:%.*]] = alloca ptr, align 8
 ; FNATTRS-NEXT:    call void @escape_readonly_ptr(ptr [[ADDR]], ptr [[ESCAPED_THEN_WRITTEN]])
 ; FNATTRS-NEXT:    [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8
@@ -570,7 +572,7 @@ define void @fptr_test2c(ptr %p, ptr %f) {
 define void @alloca_recphi() {
 ; FNATTRS: Function Attrs: nofree norecurse nosync nounwind memory(none)
 ; FNATTRS-LABEL: define {{[^@]+}}@alloca_recphi
-; FNATTRS-SAME: () #[[ATTR14:[0-9]+]] {
+; FNATTRS-SAME: () #[[ATTR15:[0-9]+]] {
 ; FNATTRS-NEXT:  entry:
 ; FNATTRS-NEXT:    [[A:%.*]] = alloca [8 x i32], align 4
 ; FNATTRS-NEXT:    [[A_END:%.*]] = getelementptr i32, ptr [[A]], i64 8
@@ -723,7 +725,7 @@ define void @op_bundle_readonly_unknown(ptr %p) {
 define i32 @writable_readonly(ptr writable dereferenceable(4) %p) {
 ; FNATTRS: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
 ; FNATTRS-LABEL: define {{[^@]+}}@writable_readonly
-; FNATTRS-SAME: (ptr readonly captures(none) dereferenceable(4) [[P:%.*]]) #[[ATTR15:[0-9]+]] {
+; FNATTRS-SAME: (ptr readonly captures(none) dereferenceable(4) [[P:%.*]]) #[[ATTR16:[0-9]+]] {
 ; FNATTRS-NEXT:    [[V:%.*]] = load i32, ptr [[P]], align 4
 ; FNATTRS-NEXT:    ret i32 [[V]]
 ;
diff --git a/llvm/test/Transforms/PhaseOrdering/pr95152.ll b/llvm/test/Transforms/PhaseOrdering/pr95152.ll
index 1e28856f32bd4..9c9ebd2dff3f5 100644
--- a/llvm/test/Transforms/PhaseOrdering/pr95152.ll
+++ b/llvm/test/Transforms/PhaseOrdering/pr95152.ll
@@ -45,7 +45,7 @@ define void @g(ptr dead_on_unwind noalias writable dereferenceable(8) align 8 %p
 
 define void @f(ptr dead_on_unwind noalias %p) {
 ; CHECK-LABEL: define void @f(
-; CHECK-SAME: ptr dead_on_unwind noalias initializes((0, 8)) [[P:%.*]]) local_unnamed_addr {
+; CHECK-SAME: ptr dead_on_unwind noalias initializes((0, 8)) [[P:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] {
 ; CHECK-NEXT:    store i64 3, ptr [[P]], align 4
 ; CHECK-NEXT:    tail call void @j(ptr nonnull align 8 dereferenceable(8) [[P]])
 ; CHECK-NEXT:    store i64 43, ptr [[P]], align 4

const Value *UO = getUnderlyingObjectAggressive(Loc.Ptr);
if (isa<AllocaInst>(UO))
if (isa<AllocaInst>(UO)) {
ME -= ME.errnoMemOnly();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is going on here. You're saying that any function that accesses an alloca doesn't accesses errno?

Generally, it doesn't make sense to mask out something from ME after the fact.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the original intent was to try to remove errno, if, say, an argument ptr is an alloca for that memory location; which is however not what is happening here. Looking at this better again, I think there's not much we can reliably infer here, and, once again, it should be up to getModRefInfo to determine whether the underlying ptr is alloca memory? Updated this, meanwhile.

@antoniofrighetto antoniofrighetto requested a review from nikic April 9, 2025 07:41
Deduce `errnomem` for a given memory location: accesses to
`__errno_location` et alia do access `errnomem` memory.
@antoniofrighetto antoniofrighetto force-pushed the feature/infer-errno-logic branch from 872a7e9 to 228a06c Compare October 21, 2025 09:56
@antoniofrighetto antoniofrighetto changed the title [FunctionAttrs] Add errno inference logic [FunctionAttrs] Infer errnomem location when accessing errno Oct 21, 2025
@antoniofrighetto
Copy link
Contributor Author

Kind ping.

Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether this is really useful for anything...

if (auto *CI = dyn_cast<CallInst>(UO)) {
if (auto *Callee = CI->getCalledFunction(); Callee && Callee->hasName()) {
static constexpr auto ErrnoFnNames = {"__errno_location", "_errno",
"__errno", "___errno"};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to go through TLI.

; RUN: opt -passes=attributor-light -S < %s | FileCheck --check-prefixes=ATTRIBUTOR %s

define i32 @test_read_errno() {
; FNATTRS-LABEL: define i32 @test_read_errno() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this isn't actually testing the inferred attributes. You need to pass --function-attrs or something like that to UTC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants