[llvm] [InstCombine] Preserve all load metadata when folding load of select (PR #206663)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 00:10:07 PDT 2026


https://github.com/ayrai-gb created https://github.com/llvm/llvm-project/pull/206663

`InstCombinerImpl::visitLoadInst` folds a load of a select-of-pointers into a select of loads:

```llvm
load (select c, p1, p2)
    =>
select c, (load p1), (load p2)
```

The transform only fires when both pointers are provably safe to load unconditionally (`isSafeToLoadUnconditionally` on each arm).

When creating the two new loads, the transform currently copies only poison-generating metadata via

```cpp
copyMetadata(LI, Metadata::PoisonGeneratingIDs)
```

which preserves only `!range`, `!nonnull`, `!align`, and `!nofpclass`. All other load metadata is silently dropped, including `!tbaa`, `!invariant.group`, `!invariant.load`, `!nontemporal`, `!dereferenceable`, `!noalias`/`!alias.scope`, `!llvm.access.group`, and `!noundef`.

Each new load performs the same access as the original load on one arm of the select and executes only when that arm is chosen, so the original load's metadata remains valid for the new loads. There is therefore no reason to restrict preservation to poison-generating metadata.

Replace the two `copyMetadata(..., Metadata::PoisonGeneratingIDs)` calls with `copyMetadataForLoad`, the existing helper already used by the load-retype path, so that the full set of load metadata is preserved.

This is a missed optimization rather than a correctness issue—the loss of metadata is the conservative direction—but it is observable. For example, losing `!invariant.group` can inhibit devirtualization, while losing `!tbaa` and `!noalias` pessimizes later alias-analysis-driven optimizations.

The existing `preserve_load_metadata_after_select_transform*` tests in `test/Transforms/InstCombine/loadstore-metadata.ll` already exercise this transform; their `CHECK` lines previously reflected the limited (poison-only) copying. Regenerate them to show the full metadata preservation, and extend `transform1` to also cover `!nontemporal` and `!invariant.group`.

Found via @jlebar's X86 LLVM bug-hunt / FuzzX effort:

* https://github.com/SemiAnalysisAI/FuzzX/tree/master/x86/bugs/179-instcombine-load-of-select-drops-invariant-group-tbaa

cc @jlebar


>From e6f76dbedcc8f00b348e6a77821ed8c1d39df76e Mon Sep 17 00:00:00 2001
From: Ayush Rai <your-email at amd.com>
Date: Tue, 30 Jun 2026 10:20:19 +0530
Subject: [PATCH] [InstCombine] Preserve all load metadata when folding load of
 select

---
 .../InstCombine/InstCombineLoadStoreAlloca.cpp        | 11 +++++++----
 .../test/Transforms/InstCombine/loadstore-metadata.ll | 10 +++++-----
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp b/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
index e6481c89c0265..12f3caf3a9a20 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineLoadStoreAlloca.cpp
@@ -1170,10 +1170,13 @@ Instruction *InstCombinerImpl::visitLoadInst(LoadInst &LI) {
         V1->setAtomic(LI.getOrdering(), LI.getSyncScopeID());
         V2->setAlignment(Alignment);
         V2->setAtomic(LI.getOrdering(), LI.getSyncScopeID());
-        // It is safe to copy any metadata that does not trigger UB. Copy any
-        // poison-generating metadata.
-        V1->copyMetadata(LI, Metadata::PoisonGeneratingIDs);
-        V2->copyMetadata(LI, Metadata::PoisonGeneratingIDs);
+        // Each new load performs the same access as the original load in one
+        // arm of the select, and executes only when that arm is selected, so
+        // every fact the original load carried still holds. Clone all load
+        // metadata (not just the poison-generating kinds) to preserve !tbaa,
+        // !invariant.group, !nontemporal, !dereferenceable, etc.
+        copyMetadataForLoad(*V1, LI);
+        copyMetadataForLoad(*V2, LI);
         return SelectInst::Create(SI->getCondition(), V1, V2, "", nullptr,
                                   ProfcheckDisableMetadataFixes ? nullptr : SI);
       }
diff --git a/llvm/test/Transforms/InstCombine/loadstore-metadata.ll b/llvm/test/Transforms/InstCombine/loadstore-metadata.ll
index 02552df831196..add4e8145aa21 100644
--- a/llvm/test/Transforms/InstCombine/loadstore-metadata.ll
+++ b/llvm/test/Transforms/InstCombine/loadstore-metadata.ll
@@ -204,14 +204,14 @@ define ptr @preserve_load_metadata_after_select_transform1(i1 %c, ptr dereferenc
 ; CHECK-LABEL: define ptr @preserve_load_metadata_after_select_transform1(
 ; CHECK-SAME: i1 [[C:%.*]], ptr dereferenceable(8) [[A:%.*]], ptr dereferenceable(8) [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[B_VAL:%.*]] = load ptr, ptr [[B]], align 1, !nonnull [[META6]], !align [[META8]]
-; CHECK-NEXT:    [[A_VAL:%.*]] = load ptr, ptr [[A]], align 1, !nonnull [[META6]], !align [[META8]]
+; CHECK-NEXT:    [[B_VAL:%.*]] = load ptr, ptr [[B]], align 1, !tbaa [[SCALAR_TYPE_TBAA0]], !invariant.load [[META6]], !nontemporal [[META7]], !nonnull [[META6]], !dereferenceable [[META8]], !invariant.group [[META6]], !align [[META8]], !llvm.access.group [[META6]], !noundef [[META6]]
+; CHECK-NEXT:    [[A_VAL:%.*]] = load ptr, ptr [[A]], align 1, !tbaa [[SCALAR_TYPE_TBAA0]], !invariant.load [[META6]], !nontemporal [[META7]], !nonnull [[META6]], !dereferenceable [[META8]], !invariant.group [[META6]], !align [[META8]], !llvm.access.group [[META6]], !noundef [[META6]]
 ; CHECK-NEXT:    [[L_SEL:%.*]] = select i1 [[C]], ptr [[B_VAL]], ptr [[A_VAL]]
 ; CHECK-NEXT:    ret ptr [[L_SEL]]
 ;
 entry:
   %ptr.sel = select i1 %c, ptr %b, ptr %a
-  %l.sel = load ptr, ptr %ptr.sel, align 1, !tbaa !0, !llvm.access.group !7, !dereferenceable !9, !noundef !{}, !invariant.load !7, !align !9, !nonnull !{}
+  %l.sel = load ptr, ptr %ptr.sel, align 1, !tbaa !0, !llvm.access.group !7, !dereferenceable !9, !noundef !{}, !invariant.load !7, !align !9, !nonnull !{}, !nontemporal !8, !invariant.group !7
   ret ptr %l.sel
 }
 
@@ -220,8 +220,8 @@ define i32 @preserve_load_metadata_after_select_transform_range(i1 %c, ptr deref
 ; CHECK-LABEL: define i32 @preserve_load_metadata_after_select_transform_range(
 ; CHECK-SAME: i1 [[C:%.*]], ptr dereferenceable(8) [[A:%.*]], ptr dereferenceable(8) [[B:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[B_VAL:%.*]] = load i32, ptr [[B]], align 1, !range [[RNG11:![0-9]+]]
-; CHECK-NEXT:    [[A_VAL:%.*]] = load i32, ptr [[A]], align 1, !range [[RNG11]]
+; CHECK-NEXT:    [[B_VAL:%.*]] = load i32, ptr [[B]], align 1, !tbaa [[SCALAR_TYPE_TBAA0]], !range [[RNG11:![0-9]+]], !invariant.load [[META6]], !llvm.access.group [[META6]], !noundef [[META6]]
+; CHECK-NEXT:    [[A_VAL:%.*]] = load i32, ptr [[A]], align 1, !tbaa [[SCALAR_TYPE_TBAA0]], !range [[RNG11]], !invariant.load [[META6]], !llvm.access.group [[META6]], !noundef [[META6]]
 ; CHECK-NEXT:    [[L_SEL:%.*]] = select i1 [[C]], i32 [[B_VAL]], i32 [[A_VAL]]
 ; CHECK-NEXT:    ret i32 [[L_SEL]]
 ;



More information about the llvm-commits mailing list