diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -2038,6 +2038,37 @@ return true; }; + auto InspectReturnInstForUB = + [&](Value &V, const SmallSetVector RetInsts) { + // Check if a return instruction always cause UB or not + // Note: It is guaranteed that the returned position of the anchor + // scope has noundef attribute when this is called. + + // When the returned position has noundef attriubte, UB occur in the + // following cases. + // (1) Returned value is known to be undef. + // (2) The value is known to be a null pointer and the returned + // position has nonnull attribute (because the returned value is + // poison). + // Note: This callback is not called for a dead returned value because + // such values are ignored in + // checkForAllReturnedValuesAndReturnedInsts. + bool FoundUB = false; + if (isa(V)) { + FoundUB = true; + } else { + auto &NonNullAA = A.getAAFor( + *this, IRPosition::returned(*getAnchorScope())); + if (NonNullAA.isKnownNonNull() && isa(V)) + FoundUB = true; + } + + if (FoundUB) + for (ReturnInst *RI : RetInsts) + KnownUBInsts.insert(RI); + return true; + }; + A.checkForAllInstructions(InspectMemAccessInstForUB, *this, {Instruction::Load, Instruction::Store, Instruction::AtomicCmpXchg, @@ -2046,6 +2077,13 @@ A.checkForAllInstructions(InspectBrInstForUB, *this, {Instruction::Br}, /* CheckBBLivenessOnly */ true); A.checkForAllCallLikeInstructions(InspectCallSiteForUB, *this); + + // If the returned position of the anchor scope has noundef attriubte, check + // all returned instructions. + // TODO: If AANoUndef is implemented, ask it here. + if (IRPosition::returned(*getAnchorScope()).hasAttr({Attribute::NoUndef})) + A.checkForAllReturnedValuesAndReturnInsts(InspectReturnInstForUB, *this); + if (NoUBPrevSize != AssumedNoUBInsts.size() || UBPrevSize != KnownUBInsts.size()) return ChangeStatus::CHANGED; diff --git a/llvm/test/Transforms/Attributor/undefined_behavior.ll b/llvm/test/Transforms/Attributor/undefined_behavior.ll --- a/llvm/test/Transforms/Attributor/undefined_behavior.ll +++ b/llvm/test/Transforms/Attributor/undefined_behavior.ll @@ -580,7 +580,9 @@ ret i32 %X } -; Tests for nonnull attribute violation. +; Tests for nonnull noundef attribute violation. +; +; Tests for argument position define void @arg_nonnull_1(i32* nonnull %a) { ; IS__TUNIT____: Function Attrs: argmemonly nofree nosync nounwind willreturn writeonly @@ -874,3 +876,131 @@ ret: ret void } + +; Tests for returned position + +define nonnull i32* @returned_nonnnull(i32 %c) { +; IS__TUNIT____: Function Attrs: nofree nosync nounwind readnone willreturn +; IS__TUNIT____-LABEL: define {{[^@]+}}@returned_nonnnull +; IS__TUNIT____-SAME: (i32 [[C:%.*]]) +; IS__TUNIT____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__TUNIT____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__TUNIT____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__TUNIT____-NEXT: ] +; IS__TUNIT____: onzero: +; IS__TUNIT____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__TUNIT____-NEXT: ret i32* [[PTR]] +; IS__TUNIT____: onone: +; IS__TUNIT____-NEXT: ret i32* null +; IS__TUNIT____: ondefault: +; IS__TUNIT____-NEXT: ret i32* undef +; +; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readnone willreturn +; IS__CGSCC____-LABEL: define {{[^@]+}}@returned_nonnnull +; IS__CGSCC____-SAME: (i32 [[C:%.*]]) +; IS__CGSCC____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__CGSCC____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__CGSCC____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__CGSCC____-NEXT: ] +; IS__CGSCC____: onzero: +; IS__CGSCC____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__CGSCC____-NEXT: ret i32* [[PTR]] +; IS__CGSCC____: onone: +; IS__CGSCC____-NEXT: ret i32* null +; IS__CGSCC____: ondefault: +; IS__CGSCC____-NEXT: ret i32* undef +; + switch i32 %c, label %ondefault [ i32 0, label %onzero + i32 1, label %onone ] +onzero: + %ptr = alloca i32 + ret i32* %ptr +onone: + ret i32* null +ondefault: + ret i32* undef +} + +define noundef i32* @returned_noundef(i32 %c) { +; IS__TUNIT____: Function Attrs: nofree nosync nounwind readnone willreturn +; IS__TUNIT____-LABEL: define {{[^@]+}}@returned_noundef +; IS__TUNIT____-SAME: (i32 [[C:%.*]]) +; IS__TUNIT____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__TUNIT____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__TUNIT____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__TUNIT____-NEXT: ] +; IS__TUNIT____: onzero: +; IS__TUNIT____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__TUNIT____-NEXT: ret i32* [[PTR]] +; IS__TUNIT____: onone: +; IS__TUNIT____-NEXT: ret i32* null +; IS__TUNIT____: ondefault: +; IS__TUNIT____-NEXT: unreachable +; +; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readnone willreturn +; IS__CGSCC____-LABEL: define {{[^@]+}}@returned_noundef +; IS__CGSCC____-SAME: (i32 [[C:%.*]]) +; IS__CGSCC____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__CGSCC____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__CGSCC____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__CGSCC____-NEXT: ] +; IS__CGSCC____: onzero: +; IS__CGSCC____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__CGSCC____-NEXT: ret i32* [[PTR]] +; IS__CGSCC____: onone: +; IS__CGSCC____-NEXT: ret i32* null +; IS__CGSCC____: ondefault: +; IS__CGSCC____-NEXT: unreachable +; + switch i32 %c, label %ondefault [ i32 0, label %onzero + i32 1, label %onone ] +onzero: + %ptr = alloca i32 + ret i32* %ptr +onone: + ret i32* null +ondefault: + ret i32* undef +} + +define nonnull noundef i32* @returned_nonnnull_noundef(i32 %c) { +; IS__TUNIT____: Function Attrs: nofree nosync nounwind readnone willreturn +; IS__TUNIT____-LABEL: define {{[^@]+}}@returned_nonnnull_noundef +; IS__TUNIT____-SAME: (i32 [[C:%.*]]) +; IS__TUNIT____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__TUNIT____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__TUNIT____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__TUNIT____-NEXT: ] +; IS__TUNIT____: onzero: +; IS__TUNIT____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__TUNIT____-NEXT: ret i32* [[PTR]] +; IS__TUNIT____: onone: +; IS__TUNIT____-NEXT: unreachable +; IS__TUNIT____: ondefault: +; IS__TUNIT____-NEXT: unreachable +; +; IS__CGSCC____: Function Attrs: nofree norecurse nosync nounwind readnone willreturn +; IS__CGSCC____-LABEL: define {{[^@]+}}@returned_nonnnull_noundef +; IS__CGSCC____-SAME: (i32 [[C:%.*]]) +; IS__CGSCC____-NEXT: switch i32 [[C]], label [[ONDEFAULT:%.*]] [ +; IS__CGSCC____-NEXT: i32 0, label [[ONZERO:%.*]] +; IS__CGSCC____-NEXT: i32 1, label [[ONONE:%.*]] +; IS__CGSCC____-NEXT: ] +; IS__CGSCC____: onzero: +; IS__CGSCC____-NEXT: [[PTR:%.*]] = alloca i32, align 4 +; IS__CGSCC____-NEXT: ret i32* [[PTR]] +; IS__CGSCC____: onone: +; IS__CGSCC____-NEXT: unreachable +; IS__CGSCC____: ondefault: +; IS__CGSCC____-NEXT: unreachable +; + switch i32 %c, label %ondefault [ i32 0, label %onzero + i32 1, label %onone ] +onzero: + %ptr = alloca i32 + ret i32* %ptr +onone: + ret i32* null +ondefault: + ret i32* undef +}