Skip to content
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

[Loads] Check context instruction for context-sensitive derefability #109277

Merged
merged 2 commits into from
Sep 23, 2024

Conversation

nikic
Copy link
Contributor

@nikic nikic commented Sep 19, 2024

If a dereferenceability fact is provided through !dereferenceable (or similar), it may only hold on the given control flow path. When we use isSafeToSpeculativelyExecute() to check multiple instructions, we might make use of !dereferenceable information that does not hold at the speculation target. This doesn't happen when speculating instructions one by one, because !dereferenceable will be dropped while speculating.

Fix this by checking whether the instruction with !dereferenceable dominates the context instruction. If this is not the case, it means we are speculating, and cannot guarantee that it holds at the speculation target.

Fixes #108854.

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 19, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-llvm-analysis

Author: Nikita Popov (nikic)

Changes

If a dereferenceability fact is provided through !dereferenceable (or similar), it may only hold on the given control flow path. When we use isSafeToSpeculativelyExecute() to check multiple instructions, we might make use of !dereferenceable information that does not hold at the speculation target. This doesn't happen when speculating instructions one by one, because !dereferenceable will be dropped while speculating.

Fix this by checking whether the instruction with !dereferenceable dominates the context instruction. If this is not the case, it means we are speculating, and cannot guarantee that it holds at the speculation target.

Fixes #108854.


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

4 Files Affected:

  • (modified) llvm/lib/Analysis/Loads.cpp (+11)
  • (modified) llvm/lib/Analysis/MemDerefPrinter.cpp (+2-2)
  • (modified) llvm/lib/CodeGen/MachineOperand.cpp (+2-1)
  • (modified) llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll (+7-4)
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 957ac883490c45..11f3807ffacf6e 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -104,6 +104,17 @@ static bool isDereferenceableAndAlignedPointer(
     if (CheckForNonNull &&
         !isKnownNonZero(V, SimplifyQuery(DL, DT, AC, CtxI)))
       return false;
+    // When using something like !dereferenceable on a load, the
+    // dereferenceability may only be valid on a specific control-flow path.
+    // If the instruction doesn't dominate the context instruction, we're
+    // asking about dereferenceability under the assumption that the
+    // instruction has been speculated to the point of the context instruction,
+    // in which case we don't know if the dereferenceability info still holds.
+    // We don't bother handling allocas here, as they aren't speculatable
+    // anyway.
+    auto *I = dyn_cast<Instruction>(V);
+    if (I && !isa<AllocaInst>(I))
+      return CtxI && isValidAssumeForContext(I, CtxI, DT);
     return true;
   };
   if (IsKnownDeref()) {
diff --git a/llvm/lib/Analysis/MemDerefPrinter.cpp b/llvm/lib/Analysis/MemDerefPrinter.cpp
index e858d941435441..68cb8859488f70 100644
--- a/llvm/lib/Analysis/MemDerefPrinter.cpp
+++ b/llvm/lib/Analysis/MemDerefPrinter.cpp
@@ -30,10 +30,10 @@ PreservedAnalyses MemDerefPrinterPass::run(Function &F,
   for (auto &I : instructions(F)) {
     if (LoadInst *LI = dyn_cast<LoadInst>(&I)) {
       Value *PO = LI->getPointerOperand();
-      if (isDereferenceablePointer(PO, LI->getType(), DL))
+      if (isDereferenceablePointer(PO, LI->getType(), DL, LI))
         Deref.push_back(PO);
       if (isDereferenceableAndAlignedPointer(PO, LI->getType(), LI->getAlign(),
-                                             DL))
+                                             DL, LI))
         DerefAndAligned.insert(PO);
     }
   }
diff --git a/llvm/lib/CodeGen/MachineOperand.cpp b/llvm/lib/CodeGen/MachineOperand.cpp
index 6ee47624f31c54..89d32c3f005e00 100644
--- a/llvm/lib/CodeGen/MachineOperand.cpp
+++ b/llvm/lib/CodeGen/MachineOperand.cpp
@@ -1047,7 +1047,8 @@ bool MachinePointerInfo::isDereferenceable(unsigned Size, LLVMContext &C,
     return false;
 
   return isDereferenceableAndAlignedPointer(
-      BasePtr, Align(1), APInt(DL.getPointerSizeInBits(), Offset + Size), DL);
+      BasePtr, Align(1), APInt(DL.getPointerSizeInBits(), Offset + Size), DL,
+      dyn_cast<Instruction>(BasePtr));
 }
 
 /// getConstantPool - Return a MachinePointerInfo record that refers to the
diff --git a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
index 8c7afa4598bd4b..0138433312ed84 100644
--- a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
+++ b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
@@ -77,14 +77,17 @@ exit:
   ret i64 %res
 }
 
-; FIXME: This is a miscompile.
 define i64 @deref_no_hoist(i1 %c, ptr align 8 dereferenceable(8) %p1) {
 ; CHECK-LABEL: define i64 @deref_no_hoist(
 ; CHECK-SAME: i1 [[C:%.*]], ptr align 8 dereferenceable(8) [[P1:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[P2:%.*]] = load ptr, ptr [[P1]], align 8, !align [[META0:![0-9]+]]
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF]]:
+; CHECK-NEXT:    [[P2:%.*]] = load ptr, ptr [[P1]], align 8, !dereferenceable [[META0:![0-9]+]], !align [[META0]]
 ; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P2]], align 8
-; CHECK-NEXT:    [[RES:%.*]] = select i1 [[C]], i64 [[V]], i64 0
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    [[RES:%.*]] = phi i64 [ [[V]], %[[IF]] ], [ 0, %[[ENTRY]] ]
 ; CHECK-NEXT:    ret i64 [[RES]]
 ;
 entry:

@llvmbot
Copy link
Collaborator

llvmbot commented Sep 19, 2024

@llvm/pr-subscribers-llvm-transforms

Author: Nikita Popov (nikic)

Changes

If a dereferenceability fact is provided through !dereferenceable (or similar), it may only hold on the given control flow path. When we use isSafeToSpeculativelyExecute() to check multiple instructions, we might make use of !dereferenceable information that does not hold at the speculation target. This doesn't happen when speculating instructions one by one, because !dereferenceable will be dropped while speculating.

Fix this by checking whether the instruction with !dereferenceable dominates the context instruction. If this is not the case, it means we are speculating, and cannot guarantee that it holds at the speculation target.

Fixes #108854.


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

4 Files Affected:

  • (modified) llvm/lib/Analysis/Loads.cpp (+11)
  • (modified) llvm/lib/Analysis/MemDerefPrinter.cpp (+2-2)
  • (modified) llvm/lib/CodeGen/MachineOperand.cpp (+2-1)
  • (modified) llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll (+7-4)
diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index 957ac883490c45..11f3807ffacf6e 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -104,6 +104,17 @@ static bool isDereferenceableAndAlignedPointer(
     if (CheckForNonNull &&
         !isKnownNonZero(V, SimplifyQuery(DL, DT, AC, CtxI)))
       return false;
+    // When using something like !dereferenceable on a load, the
+    // dereferenceability may only be valid on a specific control-flow path.
+    // If the instruction doesn't dominate the context instruction, we're
+    // asking about dereferenceability under the assumption that the
+    // instruction has been speculated to the point of the context instruction,
+    // in which case we don't know if the dereferenceability info still holds.
+    // We don't bother handling allocas here, as they aren't speculatable
+    // anyway.
+    auto *I = dyn_cast<Instruction>(V);
+    if (I && !isa<AllocaInst>(I))
+      return CtxI && isValidAssumeForContext(I, CtxI, DT);
     return true;
   };
   if (IsKnownDeref()) {
diff --git a/llvm/lib/Analysis/MemDerefPrinter.cpp b/llvm/lib/Analysis/MemDerefPrinter.cpp
index e858d941435441..68cb8859488f70 100644
--- a/llvm/lib/Analysis/MemDerefPrinter.cpp
+++ b/llvm/lib/Analysis/MemDerefPrinter.cpp
@@ -30,10 +30,10 @@ PreservedAnalyses MemDerefPrinterPass::run(Function &F,
   for (auto &I : instructions(F)) {
     if (LoadInst *LI = dyn_cast<LoadInst>(&I)) {
       Value *PO = LI->getPointerOperand();
-      if (isDereferenceablePointer(PO, LI->getType(), DL))
+      if (isDereferenceablePointer(PO, LI->getType(), DL, LI))
         Deref.push_back(PO);
       if (isDereferenceableAndAlignedPointer(PO, LI->getType(), LI->getAlign(),
-                                             DL))
+                                             DL, LI))
         DerefAndAligned.insert(PO);
     }
   }
diff --git a/llvm/lib/CodeGen/MachineOperand.cpp b/llvm/lib/CodeGen/MachineOperand.cpp
index 6ee47624f31c54..89d32c3f005e00 100644
--- a/llvm/lib/CodeGen/MachineOperand.cpp
+++ b/llvm/lib/CodeGen/MachineOperand.cpp
@@ -1047,7 +1047,8 @@ bool MachinePointerInfo::isDereferenceable(unsigned Size, LLVMContext &C,
     return false;
 
   return isDereferenceableAndAlignedPointer(
-      BasePtr, Align(1), APInt(DL.getPointerSizeInBits(), Offset + Size), DL);
+      BasePtr, Align(1), APInt(DL.getPointerSizeInBits(), Offset + Size), DL,
+      dyn_cast<Instruction>(BasePtr));
 }
 
 /// getConstantPool - Return a MachinePointerInfo record that refers to the
diff --git a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
index 8c7afa4598bd4b..0138433312ed84 100644
--- a/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
+++ b/llvm/test/Transforms/SimplifyCFG/speculate-derefable-load.ll
@@ -77,14 +77,17 @@ exit:
   ret i64 %res
 }
 
-; FIXME: This is a miscompile.
 define i64 @deref_no_hoist(i1 %c, ptr align 8 dereferenceable(8) %p1) {
 ; CHECK-LABEL: define i64 @deref_no_hoist(
 ; CHECK-SAME: i1 [[C:%.*]], ptr align 8 dereferenceable(8) [[P1:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    [[P2:%.*]] = load ptr, ptr [[P1]], align 8, !align [[META0:![0-9]+]]
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[EXIT:.*]]
+; CHECK:       [[IF]]:
+; CHECK-NEXT:    [[P2:%.*]] = load ptr, ptr [[P1]], align 8, !dereferenceable [[META0:![0-9]+]], !align [[META0]]
 ; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P2]], align 8
-; CHECK-NEXT:    [[RES:%.*]] = select i1 [[C]], i64 [[V]], i64 0
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    [[RES:%.*]] = phi i64 [ [[V]], %[[IF]] ], [ 0, %[[ENTRY]] ]
 ; CHECK-NEXT:    ret i64 [[RES]]
 ;
 entry:

If a dereferenceability fact is provided through `!dereferenceable`,
it may only hold on the given control flow path. When we use
`isSafeToSpeculativelyExecute()` to check multiple instructions, we
might make use of `!dereferenceable` information that does not
hold at the speculation target. This doesn't happen when speculating
instructions one by one, because `!dereferenceable` will be dropped
while speculating.

Fix this by checking whether the instruction with `!dereferenceable`
dominates the context instruction. If this is not the case, it means
we are speculating, and cannot guarantee that it holds at the
speculation target.

Fixes llvm#108854.
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Sep 19, 2024
// in which case we don't know if the dereferenceability info still holds.
// We don't bother handling allocas here, as they aren't speculatable
// anyway.
auto *I = dyn_cast<Instruction>(V);
Copy link
Member

Choose a reason for hiding this comment

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

if (auto *I = dyn_cast<Instruction>(V))
  if (!isa<AllocaInst>(I))
     return CtxI && isValidAssumeForContext(I, CtxI, DT);

Copy link
Member

Choose a reason for hiding this comment

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

if (auto *I = dyn_cast<Instruction>(V))
  if (!isa<AllocaInst>(I))
    if (CtxI == nullptr)
      return false;
    else
     return isValidAssumeForContext(I, CtxI, DT);

@efriedma-quic
Copy link
Collaborator

Please update the documentation for isSafeToSpeculativelyExecute() to specify the semantics in the case where the operands of the instruction don't dominate CtxI.

@nikic
Copy link
Contributor Author

nikic commented Sep 20, 2024

I added some wording to isSafeToSpeculativelyExecute(), but not sure if this is what you had in mind.

@efriedma-quic
Copy link
Collaborator

Yes, that makes sense, thanks.

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.

Nice catch!

@nikic nikic merged commit 5a4c6f9 into llvm:main Sep 23, 2024
8 checks passed
@nikic nikic deleted the spec-deref branch September 23, 2024 07:13
augusto2112 pushed a commit to augusto2112/llvm-project that referenced this pull request Sep 26, 2024
…lvm#109277)

If a dereferenceability fact is provided through `!dereferenceable` (or
similar), it may only hold on the given control flow path. When we use
`isSafeToSpeculativelyExecute()` to check multiple instructions, we
might make use of `!dereferenceable` information that does not hold at
the speculation target. This doesn't happen when speculating
instructions one by one, because `!dereferenceable` will be dropped
while speculating.

Fix this by checking whether the instruction with `!dereferenceable`
dominates the context instruction. If this is not the case, it means we
are speculating, and cannot guarantee that it holds at the speculation
target.

Fixes llvm#108854.
xgupta pushed a commit to xgupta/llvm-project that referenced this pull request Oct 4, 2024
…lvm#109277)

If a dereferenceability fact is provided through `!dereferenceable` (or
similar), it may only hold on the given control flow path. When we use
`isSafeToSpeculativelyExecute()` to check multiple instructions, we
might make use of `!dereferenceable` information that does not hold at
the speculation target. This doesn't happen when speculating
instructions one by one, because `!dereferenceable` will be dropped
while speculating.

Fix this by checking whether the instruction with `!dereferenceable`
dominates the context instruction. If this is not the case, it means we
are speculating, and cannot guarantee that it holds at the speculation
target.

Fixes llvm#108854.
@danilaml
Copy link
Collaborator

danilaml commented Nov 4, 2024

Is it even possible for isSafeToSpeculativelyExecute to return true now with the default (nullptr) CtxI for loads? Can isDereferenceableAndAlignedPointer just short-circuit to false with null CtxI?

@nikic
Copy link
Contributor Author

nikic commented Nov 4, 2024

Is it even possible for isSafeToSpeculativelyExecute to return true now with the default (nullptr) CtxI for loads? Can isDereferenceableAndAlignedPointer just short-circuit to false with null CtxI?

Yes, it's possible for anything where the derefability is not context-sensitive (like globals, dereferenceable arguments, allocas, etc).

@danilaml
Copy link
Collaborator

danilaml commented Nov 4, 2024

@nikic I mean not in theory but currently. I don't see those (except allocas) handled anywhere unless I'm missing something?

@nikic
Copy link
Contributor Author

nikic commented Nov 4, 2024

@nikic I mean not in theory but currently. I don't see those (except allocas) handled anywhere unless I'm missing something?

The I && part handles those. If it's a global or argument (thus not an instruction) we'll fall through to the return true.

@danilaml
Copy link
Collaborator

danilaml commented Nov 4, 2024

@nikic I see. Thank. Probably not gonna help me come up with a reproducer for a weird behavior/bug I was seeing with LoopVectorizer where it produced loads from poison pointers when it thought it didn't need to predicate a load (after this patch it always needs to since loads are never safe to speculate in its check but I think it's just masking whatever is going on).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category llvm:analysis llvm:transforms
Projects
None yet
Development

Successfully merging this pull request may close these issues.

isSafeToSpeculativelyExecute seems not safe to use from SimplifyCFG
6 participants