Index: lib/Transforms/InstCombine/InstructionCombining.cpp =================================================================== --- lib/Transforms/InstCombine/InstructionCombining.cpp +++ lib/Transforms/InstCombine/InstructionCombining.cpp @@ -2981,6 +2981,23 @@ return nullptr; } +static bool isProfitableToSink(CallInst &CI) { + // If the function is called with a nonnull argument, we should not sink the + // call. Later passes can be more powerful (e.g. optimize null checks) + Function *F = CI.getFunction(); + if (!F || F->nullPointerIsDefined()) + return true; + + if (Function *CalledFunc = CI.getCalledFunction()) { + for (auto &Arg : CalledFunc->args()) { + auto *FArg = dyn_cast(CI.getArgOperand(Arg.getArgNo())); + if (FArg && FArg->hasNonNullAttr()) + return false; + } + } + return true; +} + /// Try to move the specified instruction from its current block into the /// beginning of DestBlock, which can only happen if it's safe to move the /// instruction past all of the instructions between it and the end of its @@ -3005,7 +3022,7 @@ // Do not sink convergent call instructions. if (auto *CI = dyn_cast(I)) { - if (CI->isConvergent()) + if (CI->isConvergent() || !isProfitableToSink(*CI)) return false; } // We can only sink load instructions if there is nothing between the load and Index: test/Transforms/InstCombine/no-sink-nonnull-args.ll =================================================================== --- test/Transforms/InstCombine/no-sink-nonnull-args.ll +++ test/Transforms/InstCombine/no-sink-nonnull-args.ll @@ -0,0 +1,79 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -instcombine -S | FileCheck %s + +define i32 @no_sink_nonnull(i8* nonnull readonly %s) { +; CHECK-LABEL: @no_sink_nonnull( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[CALL:%.*]] = tail call i32 @foo(i8* nonnull [[S:%.*]]) +; CHECK-NEXT: br i1 false, label [[IF_THEN:%.*]], label [[IF_END:%.*]] +; CHECK: if.then: +; CHECK-NEXT: unreachable +; CHECK: if.end: +; CHECK-NEXT: ret i32 [[CALL]] +; +entry: + %call = tail call i32 @foo(i8* nonnull %s) + %tobool = icmp eq i8* %s, null + br i1 %tobool, label %if.then, label %if.end + +if.then: + tail call void @abort() + unreachable + +if.end: + ret i32 %call +} + +define i32 @sink(i8* readonly %s) { +; CHECK-LABEL: @sink( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp eq i8* [[S:%.*]], null +; CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +; CHECK: if.then: +; CHECK-NEXT: tail call void @abort() +; CHECK-NEXT: unreachable +; CHECK: if.end: +; CHECK-NEXT: [[CALL:%.*]] = tail call i32 @foo(i8* nonnull [[S]]) +; CHECK-NEXT: ret i32 [[CALL]] +; +entry: + %call = tail call i32 @foo(i8* %s) + %tobool = icmp eq i8* %s, null + br i1 %tobool, label %if.then, label %if.end + +if.then: + tail call void @abort() + unreachable + +if.end: + ret i32 %call +} + +define i32 @sink_null_defined(i8* nonnull readonly %s) #1 { +; CHECK-LABEL: @sink_null_defined( +; CHECK-NEXT: entry: +; CHECK-NEXT: br i1 false, label [[IF_THEN:%.*]], label [[IF_END:%.*]] +; CHECK: if.then: +; CHECK-NEXT: unreachable +; CHECK: if.end: +; CHECK-NEXT: [[CALL:%.*]] = tail call i32 @foo(i8* nonnull [[S:%.*]]) +; CHECK-NEXT: ret i32 [[CALL]] +; +entry: + %call = tail call i32 @foo(i8* nonnull %s) + %tobool = icmp eq i8* %s, null + br i1 %tobool, label %if.then, label %if.end + +if.then: + tail call void @abort() + unreachable + +if.end: + ret i32 %call +} + +declare i32 @foo(i8* nocapture) #0 +declare void @abort() + +attributes #0 = { argmemonly nounwind readonly } +attributes #1 = { "null-pointer-is-valid"="true" }