Skip to content

[Mem2Reg] Generate non-terminator unreachable for !noundef undef #96639

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 25, 2024

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Jun 25, 2024

When performing a load from uninitialized memory using !noundef, insert a non-terminator unreachable instruction, which will be converted to a proper unreachable by SimplifyCFG. This way we retain the fact that UB occurred on this code path.

When performing a load from uninitialized memory using !noundef,
insert a non-terminator unreachable instruction, which will be
converted to a proper unreachable by SimplifyCFG. This way we
retain the fact that UB occurred on this code path.
@llvmbot
Copy link
Member

llvmbot commented Jun 25, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Nikita Popov (nikic)

Changes

When performing a load from uninitialized memory using !noundef, insert a non-terminator unreachable instruction, which will be converted to a proper unreachable by SimplifyCFG. This way we retain the fact that UB occurred on this code path.


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

2 Files Affected:

  • (modified) llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp (+9)
  • (modified) llvm/test/Transforms/Mem2Reg/preserve-nonnull-load-metadata.ll (+6-6)
diff --git a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
index 40d0f6b75d69b..a1210ac0f88ac 100644
--- a/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
+++ b/llvm/lib/Transforms/Utils/PromoteMemoryToRegister.cpp
@@ -453,6 +453,15 @@ static void addAssumeNonNull(AssumptionCache *AC, LoadInst *LI) {
 static void convertMetadataToAssumes(LoadInst *LI, Value *Val,
                                      const DataLayout &DL, AssumptionCache *AC,
                                      const DominatorTree *DT) {
+  if (isa<UndefValue>(Val) && LI->hasMetadata(LLVMContext::MD_noundef)) {
+    // Insert non-terminator unreachable.
+    LLVMContext &Ctx = LI->getContext();
+    new StoreInst(ConstantInt::getTrue(Ctx),
+                  PoisonValue::get(PointerType::getUnqual(Ctx)),
+                  /*isVolatile=*/false, Align(1), LI);
+    return;
+  }
+
   // If the load was marked as nonnull we don't want to lose that information
   // when we erase this Load. So we preserve it with an assume. As !nonnull
   // returns poison while assume violations are immediate undefined behavior,
diff --git a/llvm/test/Transforms/Mem2Reg/preserve-nonnull-load-metadata.ll b/llvm/test/Transforms/Mem2Reg/preserve-nonnull-load-metadata.ll
index e3c14cc662e2a..edc07864795e8 100644
--- a/llvm/test/Transforms/Mem2Reg/preserve-nonnull-load-metadata.ll
+++ b/llvm/test/Transforms/Mem2Reg/preserve-nonnull-load-metadata.ll
@@ -143,6 +143,7 @@ fin:
 define ptr @no_store_single_load_noundef() {
 ; CHECK-LABEL: @no_store_single_load_noundef(
 ; CHECK-NEXT:  entry:
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ;
 entry:
@@ -156,8 +157,10 @@ define ptr @no_store_multiple_loads_noundef(i1 %c) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
 ; CHECK:       if:
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ; CHECK:       else:
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ;
 entry:
@@ -176,8 +179,7 @@ if:
 define ptr @no_store_single_load_nonnull_noundef() {
 ; CHECK-LABEL: @no_store_single_load_nonnull_noundef(
 ; CHECK-NEXT:  entry:
-; CHECK-NEXT:    [[TMP0:%.*]] = icmp ne ptr undef, null
-; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ;
 entry:
@@ -191,12 +193,10 @@ define ptr @no_store_multiple_loads_nonnull_noundef(i1 %c) {
 ; CHECK-NEXT:  entry:
 ; CHECK-NEXT:    br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]]
 ; CHECK:       if:
-; CHECK-NEXT:    [[TMP0:%.*]] = icmp ne ptr undef, null
-; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP0]])
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ; CHECK:       else:
-; CHECK-NEXT:    [[TMP1:%.*]] = icmp ne ptr undef, null
-; CHECK-NEXT:    call void @llvm.assume(i1 [[TMP1]])
+; CHECK-NEXT:    store i1 true, ptr poison, align 1
 ; CHECK-NEXT:    ret ptr undef
 ;
 entry:

Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

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

LGTM. Can we also do this for SROA?

@antoniofrighetto
Copy link
Contributor

Can we also do this for SROA?

Do we have to, out of curiosity? Thought this was only being done here, exactly to relieve SROA from carrying that out.

@nikic
Copy link
Contributor Author

nikic commented Jun 25, 2024

LGTM. Can we also do this for SROA?

SROA uses mem2reg internally, so this mostly also covers SROA as well (apart from one edge case...)

@dianqk
Copy link
Member

dianqk commented Jun 25, 2024

Why not use call void @llvm.assume(i1 false)? If I understand correctly, this should be the more canonical approach?

@nikic
Copy link
Contributor Author

nikic commented Jun 25, 2024

Why not use call void @llvm.assume(i1 false)? If I understand correctly, this should be the more canonical approach?

Store to poison is the canonical form for non-terminator unreachable. In fact, InstCombine will convert assume(false) to it: https://llvm.godbolt.org/z/rsfd7hzja

Copy link
Member

@dianqk dianqk left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks!

@nikic nikic merged commit c6973ad into llvm:main Jun 25, 2024
7 of 8 checks passed
@nikic nikic deleted the sroa-noundef-load branch June 25, 2024 14:39
nikic added a commit to nikic/llvm-project that referenced this pull request Jun 25, 2024
This extends llvm#96639 for mem2reg to earlycse. If we try to replace
a load with !noundef metadata by undef, insert a non-terminator
unreachable.

I've added a handleLoadOfUndef() helper for this purpose, which is
shared between mem2reg and earlycse (and in the future also gvn
etc). This also provides a place to experiment with replacing
uninit loads with freeze poison.
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…m#96639)

When performing a load from uninitialized memory using !noundef, insert
a non-terminator unreachable instruction, which will be converted to a
proper unreachable by SimplifyCFG. This way we retain the fact that UB
occurred on this code path.
dianqk added a commit that referenced this pull request Jul 15, 2024
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
…chable (#98910)

Summary:
Add the content from
#96639 (comment).

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251667
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.

5 participants