diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1160,10 +1160,12 @@ .. _attr_align: ``align `` or ``align()`` - This indicates that the pointer value may be assumed by the optimizer to - have the specified alignment. If the pointer value does not have the - specified alignment, behavior is undefined. ``align 1`` has no effect on - non-byval, non-preallocated arguments. + This indicates that the pointer value has the specified alignment. + If the pointer value does not have the specified alignment, + :ref:`poison value ` is returned or passed instead. The + ``align`` attribute should be combined with the ``noundef`` attribute to + ensure a pointer is aligned, or otherwise the behavior is undefined. Note + that ``align 1`` has no effect on non-byval, non-preallocated arguments. Note that this attribute has additional semantics when combined with the ``byval`` or ``preallocated`` attribute, which are documented there. @@ -1225,7 +1227,9 @@ This indicates that the parameter or return pointer is not null. This attribute may only be applied to pointer typed parameters. This is not checked or enforced by LLVM; if the parameter or return pointer is null, - the behavior is undefined. + :ref:`poison value ` is returned or passed instead. + The ``nonnull`` attribute should be combined with the ``noundef`` attribute + to ensure a pointer is not null or otherwise the behavior is undefined. ``dereferenceable()`` This indicates that the parameter or return pointer is dereferenceable. This diff --git a/llvm/include/llvm/IR/Argument.h b/llvm/include/llvm/IR/Argument.h --- a/llvm/include/llvm/IR/Argument.h +++ b/llvm/include/llvm/IR/Argument.h @@ -52,7 +52,9 @@ /// Return true if this argument has the nonnull attribute. Also returns true /// if at least one byte is known to be dereferenceable and the pointer is in /// addrspace(0). - bool hasNonNullAttr() const; + /// If AllowUndefOrPoison is true, respect the semantics of nonnull attribute + /// and return true even if the argument can be undef or poison. + bool hasNonNullAttr(bool AllowUndefOrPoison = true) const; /// If this argument has the dereferenceable attribute, return the number of /// bytes known to be dereferenceable. Otherwise, zero is returned. diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp --- a/llvm/lib/Analysis/ValueTracking.cpp +++ b/llvm/lib/Analysis/ValueTracking.cpp @@ -2088,7 +2088,8 @@ if (auto *CalledFunc = CB->getCalledFunction()) for (const Argument &Arg : CalledFunc->args()) if (CB->getArgOperand(Arg.getArgNo()) == V && - Arg.hasNonNullAttr() && DT->dominates(CB, CtxI)) + Arg.hasNonNullAttr(/* AllowUndefOrPoison */ false) && + DT->dominates(CB, CtxI)) return true; // If the value is used as a load/store, then the pointer must be non null. diff --git a/llvm/lib/IR/Function.cpp b/llvm/lib/IR/Function.cpp --- a/llvm/lib/IR/Function.cpp +++ b/llvm/lib/IR/Function.cpp @@ -87,9 +87,11 @@ Parent = parent; } -bool Argument::hasNonNullAttr() const { +bool Argument::hasNonNullAttr(bool AllowUndefOrPoison) const { if (!getType()->isPointerTy()) return false; - if (getParent()->hasParamAttribute(getArgNo(), Attribute::NonNull)) + if (getParent()->hasParamAttribute(getArgNo(), Attribute::NonNull) && + (AllowUndefOrPoison || + getParent()->hasParamAttribute(getArgNo(), Attribute::NoUndef))) return true; else if (getDereferenceableBytes() > 0 && !NullPointerIsDefined(getParent(), diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -642,7 +642,7 @@ if (auto *CB = dyn_cast(&I)) { if (auto *CalledFunc = CB->getCalledFunction()) { for (auto &CSArg : CalledFunc->args()) { - if (!CSArg.hasNonNullAttr()) + if (!CSArg.hasNonNullAttr(/* AllowUndefOrPoison */ false)) continue; // If the non-null callsite argument operand is an argument to 'F' diff --git a/llvm/test/Analysis/ValueTracking/known-nonnull-at.ll b/llvm/test/Analysis/ValueTracking/known-nonnull-at.ll --- a/llvm/test/Analysis/ValueTracking/known-nonnull-at.ll +++ b/llvm/test/Analysis/ValueTracking/known-nonnull-at.ll @@ -1,7 +1,8 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py ; RUN: opt -S -instsimplify < %s | FileCheck %s -declare void @bar(i8* %a, i8* nonnull %b) +declare void @bar(i8* %a, i8* nonnull noundef %b) +declare void @bar_without_noundef(i8* %a, i8* nonnull %b) ; 'y' must be nonnull. @@ -17,6 +18,19 @@ ; Don't know anything about 'y'. +define i1 @caller1_maybepoison(i8* %x, i8* %y) { +; CHECK-LABEL: @caller1_maybepoison( +; CHECK-NEXT: call void @bar_without_noundef(i8* [[X:%.*]], i8* [[Y:%.*]]) +; CHECK-NEXT: [[NULL_CHECK:%.*]] = icmp eq i8* [[Y]], null +; CHECK-NEXT: ret i1 [[NULL_CHECK]] +; + call void @bar_without_noundef(i8* %x, i8* %y) + %null_check = icmp eq i8* %y, null + ret i1 %null_check +} + +; Don't know anything about 'y'. + define i1 @caller2(i8* %x, i8* %y) { ; CHECK-LABEL: @caller2( ; CHECK-NEXT: call void @bar(i8* [[Y:%.*]], i8* [[X:%.*]]) @@ -187,7 +201,7 @@ ; CHECK-NEXT: [[NULL_CHECK:%.*]] = icmp eq i8* [[TMP1]], null ; CHECK-NEXT: br i1 [[NULL_CHECK]], label [[RETURN:%.*]], label [[IF_END:%.*]] ; CHECK: if.end: -; CHECK-NEXT: store i8 7, i8* [[TMP1]] +; CHECK-NEXT: store i8 7, i8* [[TMP1]], align 1 ; CHECK-NEXT: br label [[RETURN]] ; CHECK: return: ; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i8* [ [[TMP1]], [[IF_END]] ], [ null, [[ENTRY:%.*]] ] diff --git a/llvm/test/Transforms/Attributor/align.ll b/llvm/test/Transforms/Attributor/align.ll --- a/llvm/test/Transforms/Attributor/align.ll +++ b/llvm/test/Transforms/Attributor/align.ll @@ -1041,6 +1041,19 @@ ret i32* %retval.0 } +; FIXME: align 4 should not be propagated to the caller's p unless there is noundef +define void @align4_caller(i8* %p) { +; CHECK-LABEL: define {{[^@]+}}@align4_caller +; CHECK-SAME: (i8* align 4 [[P:%.*]]) { +; CHECK-NEXT: call void @align4_callee(i8* align 4 [[P]]) +; CHECK-NEXT: ret void +; + call void @align4_callee(i8* %p) + ret void +} + +declare void @align4_callee(i8* align(4) %p) + attributes #0 = { nounwind uwtable noinline } attributes #1 = { uwtable noinline } diff --git a/llvm/test/Transforms/Attributor/nonnull.ll b/llvm/test/Transforms/Attributor/nonnull.ll --- a/llvm/test/Transforms/Attributor/nonnull.ll +++ b/llvm/test/Transforms/Attributor/nonnull.ll @@ -1654,5 +1654,18 @@ ret i8* %bc } +; FIXME: nonnull should not be propagated to the caller's p unless there is noundef +define void @nonnull_caller(i8* %p) { +; CHECK-LABEL: define {{[^@]+}}@nonnull_caller +; CHECK-SAME: (i8* nonnull [[P:%.*]]) { +; CHECK-NEXT: call void @nonnull_callee(i8* nonnull [[P]]) +; CHECK-NEXT: ret void +; + call void @nonnull_callee(i8* %p) + ret void +} + +declare void @nonnull_callee(i8* nonnull %p) + attributes #0 = { null_pointer_is_valid } attributes #1 = { nounwind willreturn} diff --git a/llvm/test/Transforms/FunctionAttrs/nonnull.ll b/llvm/test/Transforms/FunctionAttrs/nonnull.ll --- a/llvm/test/Transforms/FunctionAttrs/nonnull.ll +++ b/llvm/test/Transforms/FunctionAttrs/nonnull.ll @@ -327,12 +327,21 @@ declare void @use2(i8* %x, i8* %y); declare void @use3(i8* %x, i8* %y, i8* %z); -declare void @use1nonnull(i8* nonnull %x); -declare void @use2nonnull(i8* nonnull %x, i8* nonnull %y); -declare void @use3nonnull(i8* nonnull %x, i8* nonnull %y, i8* nonnull %z); +declare void @use1nonnull(i8* nonnull noundef %x); +declare void @use1nonnull_without_noundef(i8* nonnull %x); +declare void @use2nonnull(i8* nonnull noundef %x, i8* nonnull noundef %y); +declare void @use3nonnull(i8* nonnull noundef %x, i8* nonnull noundef %y, i8* nonnull noundef %z); declare i8 @use1safecall(i8* %x) readonly nounwind ; readonly+nounwind guarantees that execution continues to successor +; Without noundef, nonnull cannot be propagated to the parent + +define void @parent_poison(i8* %a) { +; FNATTR-LABEL: @parent_poison(i8* %a) + call void @use1nonnull_without_noundef(i8* %a) + ret void +} + ; Can't extend non-null to parent for any argument because the 2nd call is not guaranteed to execute. define void @parent1(i8* %a, i8* %b, i8* %c) { diff --git a/llvm/test/Transforms/InstCombine/call_nonnull_arg.ll b/llvm/test/Transforms/InstCombine/call_nonnull_arg.ll --- a/llvm/test/Transforms/InstCombine/call_nonnull_arg.ll +++ b/llvm/test/Transforms/InstCombine/call_nonnull_arg.ll @@ -7,13 +7,13 @@ define void @test(i32* %a, i32 %b) { ; CHECK-LABEL: @test( ; CHECK-NEXT: entry: -; CHECK-NEXT: [[COND1:%.*]] = icmp eq i32* %a, null -; CHECK-NEXT: br i1 [[COND1]], label %dead, label %not_null +; CHECK-NEXT: [[COND1:%.*]] = icmp eq i32* [[A:%.*]], null +; CHECK-NEXT: br i1 [[COND1]], label [[DEAD:%.*]], label [[NOT_NULL:%.*]] ; CHECK: not_null: -; CHECK-NEXT: [[COND2:%.*]] = icmp eq i32 %b, 0 -; CHECK-NEXT: br i1 [[COND2]], label %dead, label %not_zero +; CHECK-NEXT: [[COND2:%.*]] = icmp eq i32 [[B:%.*]], 0 +; CHECK-NEXT: br i1 [[COND2]], label [[DEAD]], label [[NOT_ZERO:%.*]] ; CHECK: not_zero: -; CHECK-NEXT: call void @dummy(i32* nonnull %a, i32 %b) +; CHECK-NEXT: call void @dummy(i32* nonnull [[A]], i32 [[B]]) ; CHECK-NEXT: ret void ; CHECK: dead: ; CHECK-NEXT: unreachable @@ -31,16 +31,17 @@ unreachable } -; The nonnull attribute in the 'bar' declaration is -; propagated to the parameters of the 'baz' callsite. +; The nonnull attribute in the 'bar' declaration is +; propagated to the parameters of the 'baz' callsite. -declare void @bar(i8*, i8* nonnull) +declare void @bar(i8*, i8* nonnull noundef) +declare void @bar_without_noundef(i8*, i8* nonnull) declare void @baz(i8*, i8*) define void @deduce_nonnull_from_another_call(i8* %a, i8* %b) { ; CHECK-LABEL: @deduce_nonnull_from_another_call( -; CHECK-NEXT: call void @bar(i8* %a, i8* %b) -; CHECK-NEXT: call void @baz(i8* nonnull %b, i8* nonnull %b) +; CHECK-NEXT: call void @bar(i8* [[A:%.*]], i8* [[B:%.*]]) +; CHECK-NEXT: call void @baz(i8* nonnull [[B]], i8* nonnull [[B]]) ; CHECK-NEXT: ret void ; call void @bar(i8* %a, i8* %b) @@ -48,3 +49,15 @@ ret void } + +define void @deduce_nonnull_from_another_call2(i8* %a, i8* %b) { +; CHECK-LABEL: @deduce_nonnull_from_another_call2( +; CHECK-NEXT: call void @bar_without_noundef(i8* [[A:%.*]], i8* [[B:%.*]]) +; CHECK-NEXT: call void @baz(i8* [[B]], i8* [[B]]) +; CHECK-NEXT: ret void +; + call void @bar_without_noundef(i8* %a, i8* %b) + call void @baz(i8* %b, i8* %b) + ret void +} + diff --git a/llvm/test/Transforms/InstCombine/unused-nonnull.ll b/llvm/test/Transforms/InstCombine/unused-nonnull.ll --- a/llvm/test/Transforms/InstCombine/unused-nonnull.ll +++ b/llvm/test/Transforms/InstCombine/unused-nonnull.ll @@ -35,9 +35,9 @@ ret i32 %retval } -define i32 @compute(i8* nonnull %ptr, i32 %x) #1 { +define i32 @compute(i8* noundef nonnull %ptr, i32 %x) #1 { ; CHECK-LABEL: define {{[^@]+}}@compute -; CHECK-SAME: (i8* nocapture nonnull readnone [[PTR:%.*]], i32 returned [[X:%.*]]) local_unnamed_addr #1 +; CHECK-SAME: (i8* nocapture noundef nonnull readnone [[PTR:%.*]], i32 returned [[X:%.*]]) local_unnamed_addr #1 ; CHECK-NEXT: ret i32 [[X]] ; ret i32 %x