Index: llvm/lib/Transforms/InstCombine/InstructionCombining.cpp =================================================================== --- llvm/lib/Transforms/InstCombine/InstructionCombining.cpp +++ llvm/lib/Transforms/InstCombine/InstructionCombining.cpp @@ -3777,6 +3777,7 @@ // account for cycles in doing so. SmallVector AllocaUsers; SmallPtrSet Visited; + SmallPtrSet LifetimeBlockers; auto pushUsers = [&](const Instruction &I) { for (const User *U : I.users()) { if (Visited.insert(U).second) @@ -3793,9 +3794,28 @@ } if (UserI == CB) continue; - // TODO: support lifetime.start/end here + // We can't move the call past a lifetime end without introducing UB. + if (UserI->isLifetimeStartOrEnd() && + DestBlock->getUniquePredecessor() == I->getParent()) { + if (UserI->getParent() == I->getParent()) + LifetimeBlockers.insert(UserI); + continue; + } + + // Unhandled case return false; } + if (!LifetimeBlockers.empty()) { + // If we have lifetime intrinsics in the block, make sure they + // preceed I. We could have used comesBefore above, but this avoids + // needing to number the entire, potentially large, source block. + for (BasicBlock::iterator Scan = std::next(I->getIterator()), + E = I->getParent()->end(); + Scan != E; ++Scan) { + if (LifetimeBlockers.contains(&*Scan)) + return false; + } + } } // We can only sink load instructions if there is nothing between the load and Index: llvm/test/Transforms/InstCombine/sink_sideeffecting_instruction.ll =================================================================== --- llvm/test/Transforms/InstCombine/sink_sideeffecting_instruction.ll +++ llvm/test/Transforms/InstCombine/sink_sideeffecting_instruction.ll @@ -336,11 +336,11 @@ ; CHECK-NEXT: [[VAR:%.*]] = alloca i32, align 4 ; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[VAR]] to i8* ; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull [[BITCAST]]) -; CHECK-NEXT: [[VAR3:%.*]] = call i32 @unknown(i32* nonnull [[VAR]]) #[[ATTR1]] ; CHECK-NEXT: br i1 [[C:%.*]], label [[EARLY_RETURN:%.*]], label [[USE_BLOCK:%.*]] ; CHECK: early_return: ; CHECK-NEXT: ret i32 0 ; CHECK: use_block: +; CHECK-NEXT: [[VAR3:%.*]] = call i32 @unknown(i32* nonnull [[VAR]]) #[[ATTR1]] ; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull [[BITCAST]]) ; CHECK-NEXT: ret i32 [[VAR3]] ; @@ -365,13 +365,13 @@ ; CHECK-NEXT: [[VAR:%.*]] = alloca i32, align 4 ; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[VAR]] to i8* ; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull [[BITCAST]]) -; CHECK-NEXT: [[VAR3:%.*]] = call i32 @unknown(i32* nonnull [[VAR]]) #[[ATTR1]] ; CHECK-NEXT: br i1 [[C:%.*]], label [[MERGE:%.*]], label [[USE_BLOCK:%.*]] ; CHECK: merge: -; CHECK-NEXT: [[RET:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[VAR3]], [[USE_BLOCK]] ] +; CHECK-NEXT: [[RET:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[VAR3:%.*]], [[USE_BLOCK]] ] ; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull [[BITCAST]]) ; CHECK-NEXT: ret i32 [[RET]] ; CHECK: use_block: +; CHECK-NEXT: [[VAR3]] = call i32 @unknown(i32* nonnull [[VAR]]) #[[ATTR1]] ; CHECK-NEXT: br label [[MERGE]] ; entry: @@ -417,8 +417,8 @@ ret i32 %var3 } -define i32 @sink_lifetime4(i1 %c) { -; CHECK-LABEL: @sink_lifetime4( +define i32 @sink_lifetime4a(i1 %c) { +; CHECK-LABEL: @sink_lifetime4a( ; CHECK-NEXT: entry: ; CHECK-NEXT: [[VAR:%.*]] = alloca i32, align 4 ; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[VAR]] to i8* @@ -446,6 +446,36 @@ ret i32 %var3 } +; Version which only writes to var, and thus can't rely on may-read scan for +; clobbers to prevent the transform +define i32 @sink_lifetime4b(i1 %c) { +; CHECK-LABEL: @sink_lifetime4b( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[VAR:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[VAR]] to i8* +; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull [[BITCAST]]) +; CHECK-NEXT: [[VAR3:%.*]] = call i32 @unknown(i32* nonnull writeonly [[VAR]]) #[[ATTR1]] +; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull [[BITCAST]]) +; CHECK-NEXT: br i1 [[C:%.*]], label [[EARLY_RETURN:%.*]], label [[USE_BLOCK:%.*]] +; CHECK: early_return: +; CHECK-NEXT: ret i32 0 +; CHECK: use_block: +; CHECK-NEXT: ret i32 [[VAR3]] +; +entry: + %var = alloca i32, align 4 + %bitcast = bitcast i32* %var to i8* + call void @llvm.lifetime.start.p0i8(i64 4, i8* %bitcast) + %var3 = call i32 @unknown(i32* writeonly %var) argmemonly nounwind willreturn + call void @llvm.lifetime.end.p0i8(i64 4, i8* %bitcast) + br i1 %c, label %early_return, label %use_block + +early_return: + ret i32 0 + +use_block: + ret i32 %var3 +} ; Mostly checking that trying to sink a non-call doesn't crash (i.e. prior bug) define i32 @sink_atomicrmw_to_use(i1 %c) { ; CHECK-LABEL: @sink_atomicrmw_to_use(