Bug type: miscompilation
Component: JumpThreading
AliasAnalysis
Bug location: llvm/include/llvm/Analysis/AliasAnalysis.h: AAQueryInfo
enableCrossIterationMode
llvm/include/llvm/Analysis/BasicAliasAnalysis.h: BasicAAResult
llvm/lib/Analysis/BasicAliasAnalysis.cpp: BasicAAResult::aliasCheck
BasicAAResult::aliasGEP
BasicAAResult::invalidate
BasicAAResult::isValueEqualInPotentialCycles
BasicAAResult::subtractDecomposedGEPs
llvm/lib/Transforms/Scalar/JumpThreading.cpp: JumpThreadingPass::simplifyPartiallyRedundantLoad
Issue title: WRONG code, likely JumpThreadingPass
Labels: miscompilation
llvm:analysis
llvm:transforms
Reported by: JonPsson1

[wrong0.tar.gz](https://github.com/llvm/llvm-project/files/14027750/wrong0.tar.gz)
(reduced c test case, should print 0)

runline:
clang -O3 -march=arch13 wrong0.i -o a.out -w -mllvm -available-load-scan-limit=12

The function n() below is called two times in the reduced test case. The first time f[0] has a value of 0 at the start of the function, and a value of 1 at the end. The second time n() is called, f[0] has a value of 1 at the top, while it is then set to 0 by k().
```
long k(long l, int m); // returns 0 

const int *n() {
  for (; C <= 0;) {
    A = (f[0] == 0 ? 1 : A % f[0]);
    f[C] = k(A, 0);
    g = &f[0];
    f[C] = 1 > *g;
    if (f[C])
      return &e;
    break;
  }
  return 0;
}
```
This is the transformation of jump threading:

```
                                                                          >     *** IR Dump After JumpThreadingPass on n ***
                                                                          >     ; Function Attrs: nounwind
define dso_local ptr @n() local_unnamed_addr #1 {                               define dso_local ptr @n() local_unnamed_addr #1 {
entry:                                                                          entry:
  %0 = load i64, ptr @C, align 8, !tbaa !4                                        %0 = load i64, ptr @C, align 8, !tbaa !4
  %cmp = icmp slt i64 %0, 1                                                       %cmp = icmp slt i64 %0, 1
  br i1 %cmp, label %for.body, label %for.end                                     br i1 %cmp, label %for.body, label %for.end

for.body:                                         ; preds = %entry              for.body:                                         ; preds = %entry
  %1 = load i32, ptr @f, align 4, !tbaa !8                                        %1 = load i32, ptr @f, align 4, !tbaa !8
  %cmp1 = icmp eq i32 %1, 0                                                       %cmp1 = icmp eq i32 %1, 0
  br i1 %cmp1, label %cond.end, label %cond.false                         |       br i1 %cmp1, label %cond.end.thread, label %cond.end

cond.false:                                       ; preds = %for.body     |     cond.end.thread:                                  ; preds = %for.body
                                                                          >       store i64 1, ptr @A, align 8, !tbaa !4
                                                                          >       br label %3
                                                                          >
                                                                          >     cond.end:                                         ; preds = %for.body
  %2 = load i64, ptr @A, align 8, !tbaa !4                                        %2 = load i64, ptr @A, align 8, !tbaa !4
  %conv = sext i32 %1 to i64                                                      %conv = sext i32 %1 to i64
  %rem = srem i64 %2, %conv                                                       %rem = srem i64 %2, %conv
  br label %cond.end                                                      |       store i64 %rem, ptr @A, align 8, !tbaa !4
                                                                          >       %cmp.i = icmp sgt i64 %rem, 0
                                                                          >       %cond.fr = freeze i1 %cmp.i
                                                                          >       br i1 %cond.fr, label %3, label %4
                                                                          >
                                                                          >     3:                                                ; preds = %cond.end.
                                                                          >       %.pr = load i32, ptr @f, align 4, !tbaa !8
                                                                          >       br label %4

cond.end:                                         ; preds = %for.body,    |     4:                                                ; preds = %cond.end,
  %cond = phi i64 [ %rem, %cond.false ], [ 1, %for.body ]                 |       %5 = phi i32 [ %1, %cond.end ], [ %.pr, %3 ]
  store i64 %cond, ptr @A, align 8, !tbaa !4                              |       %6 = phi i64 [ 0, %3 ], [ %rem, %cond.end ]
  %cmp.i = icmp sgt i64 %cond, 0                                          |       %conv2 = trunc i64 %6 to i32
  %cond.i = select i1 %cmp.i, i64 0, i64 %cond                            <
  %conv2 = trunc i64 %cond.i to i32                                       <
  %arrayidx = getelementptr inbounds [1 x i32], ptr @f, i64 0, i64 %0             %arrayidx = getelementptr inbounds [1 x i32], ptr @f, i64 0, i64 %0
  store i32 %conv2, ptr %arrayidx, align 4, !tbaa !8                              store i32 %conv2, ptr %arrayidx, align 4, !tbaa !8
  store ptr @f, ptr @g, align 8, !tbaa !10                                        store ptr @f, ptr @g, align 8, !tbaa !10
  %3 = load i32, ptr @f, align 4, !tbaa !8                                |       %cmp3 = icmp slt i32 %5, 1
  %cmp3 = icmp slt i32 %3, 1                                              <
  %conv4 = zext i1 %cmp3 to i32                                                   %conv4 = zext i1 %cmp3 to i32
  store i32 %conv4, ptr %arrayidx, align 4, !tbaa !8                              store i32 %conv4, ptr %arrayidx, align 4, !tbaa !8
  br i1 %cmp3, label %return, label %for.end                                      br i1 %cmp3, label %return, label %for.end

for.end:                                          ; preds = %cond.end,    |     for.end:                                          ; preds = %4, %entry
  br label %return                                                                br label %return

return:                                           ; preds = %cond.end,    |     return:                                           ; preds = %4, %for.e
  %retval.0 = phi ptr [ null, %for.end ], [ @e, %cond.end ]               |       %retval.0 = phi ptr [ null, %for.end ], [ @e, %4 ]
  ret ptr %retval.0                                                               ret ptr %retval.0
}                                                                               }
~

```

Before (left), `@f `is reloaded for the final icmp in cond.end (%3), which is necessary as the store to %conv2 just above goes to the same address. However, to the right JumpThreading has changed the final icmp to use the %5 value, which does not reflect the stored value of %conv2. This seems wrong and maybe JT has missed the fact that %arrayidx aliases `@f`?

@jmorse @MatzeB @nikic 


Comments: author: jmorse
body: Here's a godbolt link seemingly showing the same thing: https://godbolt.org/z/dMPaP7161 . I can't replicate this at 6a7abea47 with just `opt --passes=jump-threading`, but can if it's part of clang and inspected with `print-after-all`.
author: jmorse
body: Ah -- because it's sensitive to the `-available-load-scan-limit=12`, which defaults to six and needs to be at least ten for this problem to appear. Stepping through bits of opt, the AA trace believes something that's wrong:

```
Start   %arrayidx = getelementptr inbounds [1 x i32], ptr @f, i64 0, i64 %0 @ LocationSize::precise(4), @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::precise(4)
  Start @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::beforeOrAfterPointer, @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::beforeOrAfterPointer
  End @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::beforeOrAfterPointer, @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::beforeOrAfterPointer = MustAlias
End   %arrayidx = getelementptr inbounds [1 x i32], ptr @f, i64 0, i64 %0 @ LocationSize::precise(4), @f = dso_local global [1 x i32] zeroinitializer, align 4 @ LocationSize::precise(4) = NoAlias
```

Which I think is saying that the GEP is believed to not-alias the "f" variable, wheras it might alias depending on the value of %0. Stepping through other things during the alias query, I see `isKnownNonNullFromDominatingCondition` returning true for %0, i.e. the load from "C". I would imagine this means there's something wrong with the dominator tree that makes this code think it's only executing on a path where %0 is always-zero. (EDIT: actually it means always-non-zero).

(However this isn't my normal wheelhouse, so that might not be true!).
author: nikic
body: That sounds about right to me. JumpThreading performs lazy DT updates, so it's not legal to use DT during the transform.

BasicAA in principle already supports working without DT, but it may be a bit tricky to avoid the DT use just in JumpThreading, given how this is all wired up in the pass manager.
author: nikic
body: Somewhat reduced test case:
```llvm
; RUN: opt -S -passes=jump-threading < %s
@f = external global i32

define void @test(i64 %idx, i32 %val) {
entry:
  %cmp = icmp slt i64 %idx, 1
  br i1 %cmp, label %for.body, label %return

for.body:
  %f = load i32, ptr @f, align 4
  %cmp1 = icmp eq i32 %f, 0
  br i1 %cmp1, label %cond.end, label %cond.false

cond.false:
  br label %cond.end

cond.end:
  %phi = phi i32 [ %val, %cond.false ], [ 1, %for.body ]
  %cmp.i = icmp sgt i32 %phi, 0
  %sel = select i1 %cmp.i, i32 0, i32 %phi
  %f.idx = getelementptr inbounds i32, ptr @f, i64 %idx
  store i32 %sel, ptr %f.idx, align 4
  %f.reload = load i32, ptr @f, align 4
  %cmp3 = icmp slt i32 %f.reload, 1
  br i1 %cmp3, label %return, label %return

return:
  ret void
}
```
author: EugeneZelenko
body: Not merged yet.
author: tstellar
body: PR has been created, we will track the status there.

Fix patch:
commit 4f32f5d5720fbef06672714a62376f236a36aef5
Author: Nikita Popov <npopov@redhat.com>
Date:   Wed Jan 31 15:23:53 2024 +0100

    [AA][JumpThreading] Don't use DomTree for AA in JumpThreading (#79294)
    
    JumpThreading may perform AA queries while the dominator tree is not up
    to date, which may result in miscompilations.
    
    Fix this by adding a new AAQI option to disable the use of the dominator
    tree in BasicAA.
    
    Fixes https://github.com/llvm/llvm-project/issues/79175.

diff --git a/llvm/include/llvm/Analysis/AliasAnalysis.h b/llvm/include/llvm/Analysis/AliasAnalysis.h
index d6f732d35fd4..e8e4f491be5a 100644
--- a/llvm/include/llvm/Analysis/AliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/AliasAnalysis.h
@@ -287,6 +287,10 @@ public:
   ///   store %l, ...
   bool MayBeCrossIteration = false;
 
+  /// Whether alias analysis is allowed to use the dominator tree, for use by
+  /// passes that lazily update the DT while performing AA queries.
+  bool UseDominatorTree = true;
+
   AAQueryInfo(AAResults &AAR, CaptureInfo *CI) : AAR(AAR), CI(CI) {}
 };
 
@@ -668,6 +672,9 @@ public:
   void enableCrossIterationMode() {
     AAQI.MayBeCrossIteration = true;
   }
+
+  /// Disable the use of the dominator tree during alias analysis queries.
+  void disableDominatorTree() { AAQI.UseDominatorTree = false; }
 };
 
 /// Temporary typedef for legacy code that uses a generic \c AliasAnalysis
diff --git a/llvm/include/llvm/Analysis/BasicAliasAnalysis.h b/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
index afc1811239f2..7eca82729430 100644
--- a/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
+++ b/llvm/include/llvm/Analysis/BasicAliasAnalysis.h
@@ -43,20 +43,26 @@ class BasicAAResult : public AAResultBase {
   const Function &F;
   const TargetLibraryInfo &TLI;
   AssumptionCache &AC;
-  DominatorTree *DT;
+  /// Use getDT() instead of accessing this member directly, in order to
+  /// respect the AAQI.UseDominatorTree option.
+  DominatorTree *DT_;
+
+  DominatorTree *getDT(const AAQueryInfo &AAQI) const {
+    return AAQI.UseDominatorTree ? DT_ : nullptr;
+  }
 
 public:
   BasicAAResult(const DataLayout &DL, const Function &F,
                 const TargetLibraryInfo &TLI, AssumptionCache &AC,
                 DominatorTree *DT = nullptr)
-      : DL(DL), F(F), TLI(TLI), AC(AC), DT(DT) {}
+      : DL(DL), F(F), TLI(TLI), AC(AC), DT_(DT) {}
 
   BasicAAResult(const BasicAAResult &Arg)
       : AAResultBase(Arg), DL(Arg.DL), F(Arg.F), TLI(Arg.TLI), AC(Arg.AC),
-        DT(Arg.DT) {}
+        DT_(Arg.DT_) {}
   BasicAAResult(BasicAAResult &&Arg)
       : AAResultBase(std::move(Arg)), DL(Arg.DL), F(Arg.F), TLI(Arg.TLI),
-        AC(Arg.AC), DT(Arg.DT) {}
+        AC(Arg.AC), DT_(Arg.DT_) {}
 
   /// Handle invalidation events in the new pass manager.
   bool invalidate(Function &Fn, const PreservedAnalyses &PA,
diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
index 3178e2d27816..1028b52a7912 100644
--- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp
+++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp
@@ -89,7 +89,7 @@ bool BasicAAResult::invalidate(Function &Fn, const PreservedAnalyses &PA,
   // may be created without handles to some analyses and in that case don't
   // depend on them.
   if (Inv.invalidate<AssumptionAnalysis>(Fn, PA) ||
-      (DT && Inv.invalidate<DominatorTreeAnalysis>(Fn, PA)))
+      (DT_ && Inv.invalidate<DominatorTreeAnalysis>(Fn, PA)))
     return true;
 
   // Otherwise this analysis result remains valid.
@@ -1063,6 +1063,7 @@ AliasResult BasicAAResult::aliasGEP(
                                              : AliasResult::MayAlias;
   }
 
+  DominatorTree *DT = getDT(AAQI);
   DecomposedGEP DecompGEP1 = DecomposeGEPExpression(GEP1, DL, &AC, DT);
   DecomposedGEP DecompGEP2 = DecomposeGEPExpression(V2, DL, &AC, DT);
 
@@ -1556,6 +1557,7 @@ AliasResult BasicAAResult::aliasCheck(const Value *V1, LocationSize V1Size,
         const Value *HintO1 = getUnderlyingObject(Hint1);
         const Value *HintO2 = getUnderlyingObject(Hint2);
 
+        DominatorTree *DT = getDT(AAQI);
         auto ValidAssumeForPtrContext = [&](const Value *Ptr) {
           if (const Instruction *PtrI = dyn_cast<Instruction>(Ptr)) {
             return isValidAssumeForContext(Assume, PtrI, DT,
@@ -1735,7 +1737,7 @@ bool BasicAAResult::isValueEqualInPotentialCycles(const Value *V,
   if (!Inst || Inst->getParent()->isEntryBlock())
     return true;
 
-  return isNotInCycle(Inst, DT, /*LI*/ nullptr);
+  return isNotInCycle(Inst, getDT(AAQI), /*LI*/ nullptr);
 }
 
 /// Computes the symbolic difference between two de-composed GEPs.
diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
index b7cf02489631..bb33a5da288c 100644
--- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp
+++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp
@@ -1261,6 +1261,8 @@ bool JumpThreadingPass::simplifyPartiallyRedundantLoad(LoadInst *LoadI) {
   BasicBlock::iterator BBIt(LoadI);
   bool IsLoadCSE;
   BatchAAResults BatchAA(*AA);
+  // The dominator tree is updated lazily and may not be valid at this point.
+  BatchAA.disableDominatorTree();
   if (Value *AvailableVal = FindAvailableLoadedValue(
           LoadI, LoadBB, BBIt, DefMaxInstsToScan, &BatchAA, &IsLoadCSE)) {
     // If the value of the load is locally available within the block, just use


Regression tests:
file: llvm/test/Transforms/JumpThreading/pr79175.ll
commands: opt -S -passes=jump-threading < %s
tests: test_name: test
test_body: @f = external global i32

define i32 @test(i64 %idx, i32 %val) {
entry:
  %cmp = icmp slt i64 %idx, 1
  br i1 %cmp, label %for.body, label %return

for.body:                                         ; preds = %entry
  %f = load i32, ptr @f, align 4
  %cmp1 = icmp eq i32 %f, 0
  br i1 %cmp1, label %cond.end, label %cond.false

cond.false:                                       ; preds = %for.body
  br label %cond.end

cond.end:                                         ; preds = %cond.false, %for.body
  %phi = phi i32 [ %val, %cond.false ], [ 1, %for.body ]
  %cmp.i = icmp sgt i32 %phi, 0
  %sel = select i1 %cmp.i, i32 0, i32 %phi
  %f.idx = getelementptr inbounds i32, ptr @f, i64 %idx
  store i32 %sel, ptr %f.idx, align 4
  %f.reload = load i32, ptr @f, align 4
  %cmp3 = icmp slt i32 %f.reload, 1
  br i1 %cmp3, label %return2, label %return

return:                                           ; preds = %cond.end, %entry
  ret i32 0

return2:                                          ; preds = %cond.end
  ret i32 1
}