diff --git a/llvm/include/llvm/IR/InstrTypes.h b/llvm/include/llvm/IR/InstrTypes.h index 4852f64d0977f..86d88da3d9460 100644 --- a/llvm/include/llvm/IR/InstrTypes.h +++ b/llvm/include/llvm/IR/InstrTypes.h @@ -1461,6 +1461,22 @@ class CallBase : public Instruction { /// void setAttributes(AttributeList A) { Attrs = A; } + /// Try to intersect the attributes from 'this' CallBase and the + /// 'Other' CallBase. Sets the intersected attributes to 'this' and + /// return true if successful. Doesn't modify 'this' and returns + /// false if unsuccessful. + bool tryIntersectAttributes(const CallBase *Other) { + if (this == Other) + return true; + AttributeList AL = getAttributes(); + AttributeList ALOther = Other->getAttributes(); + auto Intersected = AL.intersectWith(getContext(), ALOther); + if (!Intersected) + return false; + setAttributes(*Intersected); + return true; + } + /// Determine whether this call has the given attribute. If it does not /// then determine if the called function has the attribute, but only if /// the attribute is allowed for the call. diff --git a/llvm/include/llvm/IR/Instruction.h b/llvm/include/llvm/IR/Instruction.h index 4a1e66a90831c..61dba265dc948 100644 --- a/llvm/include/llvm/IR/Instruction.h +++ b/llvm/include/llvm/IR/Instruction.h @@ -881,16 +881,20 @@ class Instruction : public User, /// This is like isIdenticalTo, except that it ignores the /// SubclassOptionalData flags, which may specify conditions under which the /// instruction's result is undefined. - bool isIdenticalToWhenDefined(const Instruction *I) const LLVM_READONLY; + bool + isIdenticalToWhenDefined(const Instruction *I, + bool IntersectAttrs = false) const LLVM_READONLY; /// When checking for operation equivalence (using isSameOperationAs) it is /// sometimes useful to ignore certain attributes. enum OperationEquivalenceFlags { /// Check for equivalence ignoring load/store alignment. - CompareIgnoringAlignment = 1<<0, + CompareIgnoringAlignment = 1 << 0, /// Check for equivalence treating a type and a vector of that type /// as equivalent. - CompareUsingScalarTypes = 1<<1 + CompareUsingScalarTypes = 1 << 1, + /// Check for equivalence with intersected callbase attrs. + CompareUsingIntersectedAttrs = 1 << 2, }; /// This function determines if the specified instruction executes the same @@ -911,8 +915,8 @@ class Instruction : public User, /// @returns true if the specific instruction has the same opcde specific /// characteristics as the current one. Determine if one instruction has the /// same state as another. - bool hasSameSpecialState(const Instruction *I2, - bool IgnoreAlignment = false) const LLVM_READONLY; + bool hasSameSpecialState(const Instruction *I2, bool IgnoreAlignment = false, + bool IntersectAttrs = false) const LLVM_READONLY; /// Return true if there are any uses of this instruction in blocks other than /// the specified block. Note that PHI nodes are considered to evaluate their diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp index b1c2b0200c826..b84e62d2e43b7 100644 --- a/llvm/lib/IR/Instruction.cpp +++ b/llvm/lib/IR/Instruction.cpp @@ -785,11 +785,21 @@ const char *Instruction::getOpcodeName(unsigned OpCode) { /// This must be kept in sync with FunctionComparator::cmpOperations in /// lib/Transforms/IPO/MergeFunctions.cpp. bool Instruction::hasSameSpecialState(const Instruction *I2, - bool IgnoreAlignment) const { + bool IgnoreAlignment, + bool IntersectAttrs) const { auto I1 = this; assert(I1->getOpcode() == I2->getOpcode() && "Can not compare special state of different instructions"); + auto CheckAttrsSame = [IntersectAttrs](const CallBase *CB0, + const CallBase *CB1) { + return IntersectAttrs + ? CB0->getAttributes() + .intersectWith(CB0->getContext(), CB1->getAttributes()) + .has_value() + : CB0->getAttributes() == CB1->getAttributes(); + }; + if (const AllocaInst *AI = dyn_cast(I1)) return AI->getAllocatedType() == cast(I2)->getAllocatedType() && (AI->getAlign() == cast(I2)->getAlign() || @@ -811,15 +821,15 @@ bool Instruction::hasSameSpecialState(const Instruction *I2, if (const CallInst *CI = dyn_cast(I1)) return CI->isTailCall() == cast(I2)->isTailCall() && CI->getCallingConv() == cast(I2)->getCallingConv() && - CI->getAttributes() == cast(I2)->getAttributes() && + CheckAttrsSame(CI, cast(I2)) && CI->hasIdenticalOperandBundleSchema(*cast(I2)); if (const InvokeInst *CI = dyn_cast(I1)) return CI->getCallingConv() == cast(I2)->getCallingConv() && - CI->getAttributes() == cast(I2)->getAttributes() && + CheckAttrsSame(CI, cast(I2)) && CI->hasIdenticalOperandBundleSchema(*cast(I2)); if (const CallBrInst *CI = dyn_cast(I1)) return CI->getCallingConv() == cast(I2)->getCallingConv() && - CI->getAttributes() == cast(I2)->getAttributes() && + CheckAttrsSame(CI, cast(I2)) && CI->hasIdenticalOperandBundleSchema(*cast(I2)); if (const InsertValueInst *IVI = dyn_cast(I1)) return IVI->getIndices() == cast(I2)->getIndices(); @@ -857,10 +867,10 @@ bool Instruction::isIdenticalTo(const Instruction *I) const { SubclassOptionalData == I->SubclassOptionalData; } -bool Instruction::isIdenticalToWhenDefined(const Instruction *I) const { +bool Instruction::isIdenticalToWhenDefined(const Instruction *I, + bool IntersectAttrs) const { if (getOpcode() != I->getOpcode() || - getNumOperands() != I->getNumOperands() || - getType() != I->getType()) + getNumOperands() != I->getNumOperands() || getType() != I->getType()) return false; // If both instructions have no operands, they are identical. @@ -879,7 +889,8 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I) const { otherPHI->block_begin()); } - return this->hasSameSpecialState(I); + return this->hasSameSpecialState(I, /*IgnoreAlignment=*/false, + IntersectAttrs); } // Keep this in sync with FunctionComparator::cmpOperations in @@ -887,7 +898,8 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I) const { bool Instruction::isSameOperationAs(const Instruction *I, unsigned flags) const { bool IgnoreAlignment = flags & CompareIgnoringAlignment; - bool UseScalarTypes = flags & CompareUsingScalarTypes; + bool UseScalarTypes = flags & CompareUsingScalarTypes; + bool IntersectAttrs = flags & CompareUsingIntersectedAttrs; if (getOpcode() != I->getOpcode() || getNumOperands() != I->getNumOperands() || @@ -905,7 +917,7 @@ bool Instruction::isSameOperationAs(const Instruction *I, getOperand(i)->getType() != I->getOperand(i)->getType()) return false; - return this->hasSameSpecialState(I, IgnoreAlignment); + return this->hasSameSpecialState(I, IgnoreAlignment, IntersectAttrs); } bool Instruction::isUsedOutsideOfBlock(const BasicBlock *BB) const { diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp index ffd4037f23ffa..18d26aaf46066 100644 --- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -1594,7 +1594,7 @@ static void hoistLockstepIdenticalDbgVariableRecords( static bool areIdenticalUpToCommutativity(const Instruction *I1, const Instruction *I2) { - if (I1->isIdenticalToWhenDefined(I2)) + if (I1->isIdenticalToWhenDefined(I2, /*IntersectAttrs=*/true)) return true; if (auto *Cmp1 = dyn_cast(I1)) @@ -1910,6 +1910,14 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI, if (!I2->use_empty()) I2->replaceAllUsesWith(I1); I1->andIRFlags(I2); + if (auto *CB = dyn_cast(I1)) { + bool Success = CB->tryIntersectAttributes(cast(I2)); + assert(Success && "We should not be trying to hoist callbases " + "with non-intersectable attributes"); + // For NDEBUG Compile. + (void)Success; + } + combineMetadataForCSE(I1, I2, true); // I1 and I2 are being combined into a single instruction. Its debug // location is the merged locations of the original instructions. @@ -2130,7 +2138,7 @@ static bool canSinkInstructions( const Instruction *I0 = Insts.front(); const auto I0MMRA = MMRAMetadata(*I0); for (auto *I : Insts) { - if (!I->isSameOperationAs(I0)) + if (!I->isSameOperationAs(I0, Instruction::CompareUsingIntersectedAttrs)) return false; // swifterror pointers can only be used by a load or store; sinking a load @@ -2287,6 +2295,13 @@ static void sinkLastInstruction(ArrayRef Blocks) { I0->applyMergedLocation(I0->getDebugLoc(), I->getDebugLoc()); combineMetadataForCSE(I0, I, true); I0->andIRFlags(I); + if (auto *CB = dyn_cast(I0)) { + bool Success = CB->tryIntersectAttributes(cast(I)); + assert(Success && "We should not be trying to sink callbases " + "with non-intersectable attributes"); + // For NDEBUG Compile. + (void)Success; + } } for (User *U : make_early_inc_range(I0->users())) { diff --git a/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll b/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll new file mode 100644 index 0000000000000..3a4c385727924 --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/hoist-cb-diff-attrs.ll @@ -0,0 +1,202 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals +; RUN: opt < %s -passes='simplifycfg' -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s + +declare ptr @foo(ptr %p, i64 %x) +declare void @side.effect() + +define ptr @test_hoist_int_attrs(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_int_attrs +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: [[R:%.*]] = call ptr @foo(ptr align 32 dereferenceable(50) dereferenceable_or_null(100) [[P]], i64 range(i64 10, 100000) [[X]]) #[[ATTR0:[0-9]+]] +; CHECK-NEXT: br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret ptr [[R]] +; CHECK: else: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call ptr @foo(ptr dereferenceable(50) align 64 dereferenceable_or_null(100) %p, i64 range(i64 10, 1000) %x) memory(read) + ret ptr %r + +else: + %r2 = call ptr @foo(ptr dereferenceable(100) align 32 dereferenceable_or_null(200) %p, i64 range(i64 10000, 100000) %x) memory(write) + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_int_attrs2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_int_attrs2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: [[R:%.*]] = call ptr @foo(ptr dereferenceable(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR1:[0-9]+]] +; CHECK-NEXT: br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret ptr [[R]] +; CHECK: else: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call ptr @foo(ptr dereferenceable(50) %p, i64 range(i64 10, 1000) %x) memory(read) + ret ptr %r + +else: + %r2 = call ptr @foo(ptr dereferenceable(100) align 32 dereferenceable_or_null(200) %p, i64 range(i64 11, 100) %x) memory(none) + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: [[R:%.*]] = call noundef ptr @foo(ptr nonnull [[P]], i64 noundef [[X]]) #[[ATTR2:[0-9]+]] +; CHECK-NEXT: br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret ptr [[R]] +; CHECK: else: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call noundef ptr @foo(ptr readnone nonnull noundef %p, i64 noundef %x) cold mustprogress nocallback nofree nosync willreturn + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr readonly nonnull %p, i64 noundef %x) mustprogress nocallback nofree willreturn + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs3(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs3 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: [[R:%.*]] = call nonnull ptr @foo(ptr [[P]], i64 noundef [[X]]) #[[ATTR3:[0-9]+]] +; CHECK-NEXT: br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret ptr [[R]] +; CHECK: else: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn alwaysinline + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr writeonly nonnull %p, i64 noundef %x) nosync willreturn alwaysinline + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs_fail_non_droppable(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs_fail_non_droppable +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi ptr [ [[R:%.*]], [[IF]] ], [ [[R2:%.*]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[COMMON_RET_OP]] +; CHECK: if: +; CHECK-NEXT: [[R]] = call nonnull ptr @foo(ptr noundef readonly [[P]], i64 noundef [[X]]) #[[ATTR4:[0-9]+]] +; CHECK-NEXT: br label [[COMMON_RET:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2]] = call noundef nonnull ptr @foo(ptr nonnull writeonly [[P]], i64 noundef [[X]]) #[[ATTR5:[0-9]+]] +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn alwaysinline + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr writeonly nonnull %p, i64 noundef %x) nosync willreturn + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs_fail_non_droppable2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs_fail_non_droppable2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi ptr [ [[R:%.*]], [[IF]] ], [ [[R2:%.*]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[COMMON_RET_OP]] +; CHECK: if: +; CHECK-NEXT: [[R]] = call nonnull ptr @foo(ptr noundef readonly [[P]], i64 noundef [[X]]) #[[ATTR6:[0-9]+]] +; CHECK-NEXT: br label [[COMMON_RET:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2]] = call noundef nonnull ptr @foo(ptr nonnull writeonly byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR5]] +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync willreturn + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs_fail_non_droppable3(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs_fail_non_droppable3 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi ptr [ [[R:%.*]], [[IF]] ], [ [[R2:%.*]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[COMMON_RET_OP]] +; CHECK: if: +; CHECK-NEXT: [[R]] = call nonnull ptr @foo(ptr noundef readonly byval(i32) [[P]], i64 noundef [[X]]) #[[ATTR6]] +; CHECK-NEXT: br label [[COMMON_RET:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2]] = call noundef nonnull ptr @foo(ptr nonnull writeonly byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR5]] +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call nonnull ptr @foo(ptr byval(i32) readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync willreturn + call void @side.effect() + ret ptr %r2 +} + +define ptr @test_hoist_bool_attrs4(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_hoist_bool_attrs4 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: [[R:%.*]] = call nonnull ptr @foo(ptr byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR5]] +; CHECK-NEXT: br i1 [[C]], label [[COMMON_RET:%.*]], label [[ELSE:%.*]] +; CHECK: common.ret: +; CHECK-NEXT: ret ptr [[R]] +; CHECK: else: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[COMMON_RET]] +; + br i1 %c, label %if, label %else +if: + %r = call nonnull ptr @foo(ptr byval(i64) readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + ret ptr %r + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync willreturn + call void @side.effect() + ret ptr %r2 +} +;. +; CHECK: attributes #[[ATTR0]] = { memory(readwrite) } +; CHECK: attributes #[[ATTR1]] = { memory(read) } +; CHECK: attributes #[[ATTR2]] = { mustprogress nocallback nofree willreturn } +; CHECK: attributes #[[ATTR3]] = { alwaysinline nosync willreturn } +; CHECK: attributes #[[ATTR4]] = { alwaysinline cold nocallback nofree nosync willreturn } +; CHECK: attributes #[[ATTR5]] = { nosync willreturn } +; CHECK: attributes #[[ATTR6]] = { cold nocallback nofree nosync willreturn } +;. diff --git a/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll b/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll new file mode 100644 index 0000000000000..47150f3167f9f --- /dev/null +++ b/llvm/test/Transforms/SimplifyCFG/sink-cb-diff-attrs.ll @@ -0,0 +1,227 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals +; RUN: opt < %s -passes='simplifycfg' -simplifycfg-require-and-preserve-domtree=1 -S | FileCheck %s + +declare ptr @foo(ptr %p, i64 %x) +declare void @side.effect() + +define ptr @test_sink_int_attrs(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_int_attrs +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[END:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[R2:%.*]] = call ptr @foo(ptr align 32 dereferenceable(50) dereferenceable_or_null(100) [[P]], i64 range(i64 10, 100000) [[X]]) #[[ATTR0:[0-9]+]] +; CHECK-NEXT: ret ptr [[R2]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call ptr @foo(ptr dereferenceable(50) align 64 dereferenceable_or_null(100) %p, i64 range(i64 10, 1000) %x) memory(read) + br label %end + +else: + %r2 = call ptr @foo(ptr dereferenceable(100) align 32 dereferenceable_or_null(200) %p, i64 range(i64 10000, 100000) %x) memory(write) + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_int_attrs2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_int_attrs2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[END:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[R2:%.*]] = call ptr @foo(ptr dereferenceable(50) [[P]], i64 range(i64 10, 1000) [[X]]) #[[ATTR1:[0-9]+]] +; CHECK-NEXT: ret ptr [[R2]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call ptr @foo(ptr dereferenceable(50) %p, i64 range(i64 10, 1000) %x) memory(read) + br label %end + +else: + %r2 = call ptr @foo(ptr dereferenceable(100) align 32 dereferenceable_or_null(200) %p, i64 range(i64 11, 100) %x) memory(none) + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[END:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[R2:%.*]] = call noundef ptr @foo(ptr nonnull [[P]], i64 noundef [[X]]) #[[ATTR2:[0-9]+]] +; CHECK-NEXT: ret ptr [[R2]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call noundef ptr @foo(ptr readnone nonnull noundef %p, i64 noundef %x) cold mustprogress nocallback nofree nosync willreturn + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr readonly nonnull %p, i64 noundef %x) mustprogress nocallback nofree willreturn + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs3(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs3 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[END:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[R2:%.*]] = call nonnull ptr @foo(ptr [[P]], i64 noundef [[X]]) #[[ATTR3:[0-9]+]] +; CHECK-NEXT: ret ptr [[R2]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn alwaysinline + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr writeonly nonnull %p, i64 noundef %x) nosync willreturn alwaysinline + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs_fail_non_droppable(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs_fail_non_droppable +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: [[R:%.*]] = call nonnull ptr @foo(ptr noundef readonly [[P]], i64 noundef [[X]]) #[[ATTR4:[0-9]+]] +; CHECK-NEXT: br label [[END:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2:%.*]] = call noundef nonnull ptr @foo(ptr nonnull writeonly [[P]], i64 noundef [[X]]) #[[ATTR5:[0-9]+]] +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[PR:%.*]] = phi ptr [ [[R]], [[IF]] ], [ [[R2]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[PR]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn alwaysinline + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr writeonly nonnull %p, i64 noundef %x) nosync willreturn + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs_fail_non_droppable2(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs_fail_non_droppable2 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: [[R:%.*]] = call nonnull ptr @foo(ptr noundef readonly [[P]], i64 noundef [[X]]) #[[ATTR6:[0-9]+]] +; CHECK-NEXT: br label [[END:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2:%.*]] = call noundef nonnull ptr @foo(ptr nonnull writeonly byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR5]] +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[PR:%.*]] = phi ptr [ [[R]], [[IF]] ], [ [[R2]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[PR]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call nonnull ptr @foo(ptr readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync willreturn + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs_fail_non_droppable3(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs_fail_non_droppable3 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[ELSE:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: [[R:%.*]] = call nonnull ptr @foo(ptr noundef readonly byval(i32) [[P]], i64 noundef [[X]]) #[[ATTR6]] +; CHECK-NEXT: br label [[END:%.*]] +; CHECK: else: +; CHECK-NEXT: [[R2:%.*]] = call noundef nonnull ptr @foo(ptr nonnull writeonly byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR7:[0-9]+]] +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[PR:%.*]] = phi ptr [ [[R]], [[IF]] ], [ [[R2]], [[ELSE]] ] +; CHECK-NEXT: ret ptr [[PR]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call nonnull ptr @foo(ptr byval(i32) readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} + +define ptr @test_sink_bool_attrs4(i1 %c, ptr %p, i64 %x) { +; CHECK-LABEL: define {{[^@]+}}@test_sink_bool_attrs4 +; CHECK-SAME: (i1 [[C:%.*]], ptr [[P:%.*]], i64 [[X:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[IF:%.*]], label [[END:%.*]] +; CHECK: if: +; CHECK-NEXT: call void @side.effect() +; CHECK-NEXT: br label [[END]] +; CHECK: end: +; CHECK-NEXT: [[R2:%.*]] = call nonnull ptr @foo(ptr byval(i64) [[P]], i64 noundef [[X]]) #[[ATTR7]] +; CHECK-NEXT: ret ptr [[R2]] +; + br i1 %c, label %if, label %else +if: + call void @side.effect() + %r = call nonnull ptr @foo(ptr byval(i64) readonly noundef %p, i64 noundef %x) cold nocallback nofree nosync willreturn + br label %end + +else: + %r2 = call noundef nonnull ptr @foo(ptr byval(i64) writeonly nonnull %p, i64 noundef %x) nosync + br label %end +end: + %pr = phi ptr [ %r, %if], [%r2, %else] + ret ptr %pr +} +;. +; CHECK: attributes #[[ATTR0]] = { memory(readwrite) } +; CHECK: attributes #[[ATTR1]] = { memory(read) } +; CHECK: attributes #[[ATTR2]] = { mustprogress nocallback nofree willreturn } +; CHECK: attributes #[[ATTR3]] = { alwaysinline nosync willreturn } +; CHECK: attributes #[[ATTR4]] = { alwaysinline cold nocallback nofree nosync willreturn } +; CHECK: attributes #[[ATTR5]] = { nosync willreturn } +; CHECK: attributes #[[ATTR6]] = { cold nocallback nofree nosync willreturn } +; CHECK: attributes #[[ATTR7]] = { nosync } +;.