diff --git a/llvm/lib/Transforms/Scalar/JumpThreading.cpp b/llvm/lib/Transforms/Scalar/JumpThreading.cpp index 7f8e10eb201a6..31f3ffc9da745 100644 --- a/llvm/lib/Transforms/Scalar/JumpThreading.cpp +++ b/llvm/lib/Transforms/Scalar/JumpThreading.cpp @@ -1978,14 +1978,19 @@ void JumpThreadingPass::updateSSA(BasicBlock *BB, BasicBlock *NewBB, for (Instruction &I : *BB) { // Scan all uses of this instruction to see if it is used outside of its // block, and if so, record them in UsesToRename. + + SmallVector LifetimeMarkers; for (Use &U : I.uses()) { Instruction *User = cast(U.getUser()); - if (PHINode *UserPN = dyn_cast(User)) { - if (UserPN->getIncomingBlock(U) == BB) + if (User->isLifetimeStartOrEnd()) { + LifetimeMarkers.push_back(User); + } else { + if (PHINode *UserPN = dyn_cast(User)) { + if (UserPN->getIncomingBlock(U) == BB) + continue; + } else if (User->getParent() == BB) continue; - } else if (User->getParent() == BB) - continue; - + } UsesToRename.push_back(&U); } @@ -2014,6 +2019,15 @@ void JumpThreadingPass::updateSSA(BasicBlock *BB, BasicBlock *NewBB, DbgVariableRecords.clear(); } + // Lifetime markers cannot be rewritten through PHIs. If threading leaves + // one of them pointing at a PHI, drop the whole set. + bool HasPhiArg = any_of(LifetimeMarkers, [](Instruction *User) { + return isa(cast(User)->getOperand(0)); + }); + if (HasPhiArg) { + for (Instruction *User : LifetimeMarkers) + User->eraseFromParent(); + } LLVM_DEBUG(dbgs() << "\n"); } } diff --git a/llvm/test/Transforms/JumpThreading/lifetime-alloca.ll b/llvm/test/Transforms/JumpThreading/lifetime-alloca.ll new file mode 100644 index 0000000000000..81e05c70588f6 --- /dev/null +++ b/llvm/test/Transforms/JumpThreading/lifetime-alloca.ll @@ -0,0 +1,80 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -passes=jump-threading -S %s | FileCheck %s + +; Test that JumpThreading does not create a PHI node for an alloca that is +; used by a lifetime intrinsic, which would violate the requirement that +; lifetime markers only operate on allocas. + +define void @widget_dropped(i1 %arg) { +; CHECK-LABEL: @widget_dropped( +; CHECK-NEXT: bb: +; CHECK-NEXT: br i1 [[ARG:%.*]], label [[BB3:%.*]], label [[BB2:%.*]] +; CHECK: bb2: +; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [4 x [4 x i32]], align 8 +; CHECK-NEXT: br label [[BB4:%.*]] +; CHECK: bb3: +; CHECK-NEXT: call void @dummy() +; CHECK-NEXT: [[ALLOCA1:%.*]] = alloca [4 x [4 x i32]], align 8 +; CHECK-NEXT: br label [[BB4]] +; CHECK: bb4: +; CHECK-NEXT: [[ALLOCA2:%.*]] = phi ptr [ [[ALLOCA1]], [[BB3]] ], [ [[ALLOCA]], [[BB2]] ] +; CHECK-NEXT: ret void +; +bb: + br i1 %arg, label %bb1, label %bb2 + +bb1: + call void @dummy() + br label %bb2 + +bb2: + %alloca = alloca [4 x [4 x i32]], align 8 + br i1 %arg, label %bb3, label %bb4 + +bb3: + br label %bb4 + +bb4: + call void @llvm.lifetime.start.p0(ptr %alloca) + call void @llvm.lifetime.end.p0(ptr %alloca) + ret void +} + +; Because the alloca is in the entry block, it dominates all threaded blocks. +; No PHI node is required, and the lifetime markers can be safely preserved. +define void @widget_preserved(i1 %arg) { +; CHECK-LABEL: @widget_preserved( +; CHECK-NEXT: bb: +; CHECK-NEXT: [[ALLOCA:%.*]] = alloca [4 x [4 x i32]], align 8 +; CHECK-NEXT: br i1 [[ARG:%.*]], label [[BB3:%.*]], label [[BB4:%.*]] +; CHECK: bb3: +; CHECK-NEXT: call void @dummy() +; CHECK-NEXT: br label [[BB4]] +; CHECK: bb4: +; CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[ALLOCA]]) +; CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[ALLOCA]]) +; CHECK-NEXT: ret void +; +bb: + %alloca = alloca [4 x [4 x i32]], align 8 + br i1 %arg, label %bb1, label %bb2 + +bb1: + call void @dummy() + br label %bb2 + +bb2: + br i1 %arg, label %bb3, label %bb4 + +bb3: + br label %bb4 + +bb4: + call void @llvm.lifetime.start.p0(ptr %alloca) + call void @llvm.lifetime.end.p0(ptr %alloca) + ret void +} + +declare void @dummy() + +attributes #0 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }