Currently LLVM is relying on ValueTracking's `isKnownNonZero` to attach `nonnull`, which can return true when the value is poison.
To make the semantics of `nonnull` consistent with the behavior of `isKnownNonZero`, this makes the semantics of `nonnull` to accept poison, and return poison if the input pointer isn't null.
This makes many transformations like below legal:
```
%p = gep inbounds %x, 1 ; % p is non-null pointer or poison
call void @f(%p) ; instcombine converts this to call void @f(nonnull %p)
```
Instead, this semantics makes propagation of `nonnull` to caller illegal.
The reason is that, passing poison to `nonnull` does not immediately raise UB anymore, so such program is still well defined, if the callee does not use the argument.
Having `noundef` attribute there re-allows this.
```
define void @f(i8* %p) { ; functionattr cannot mark %p nonnull here anymore
call void @g(i8* nonnull %p) ; .. because @g never raises UB if it never uses %p.
ret void
}
```
Another attribute that needs to be updated is `align`. This patch updates the semantics of align to accept poison as well.
Reviewed By: jdoerfert
Differential Revision: https://reviews.llvm.org/D90529
.. _attr_align:
``align <n>`` or ``align(<n>)``
- 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 <poisonvalues>` 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.
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 <poisonvalues>` 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(<n>)``
This indicates that the parameter or return pointer is dereferenceable. This
/// 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.
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.
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(),
if (auto *CB = dyn_cast<CallBase>(&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'
; 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.
; 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:%.*]])
; 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:%.*]] ]
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 }
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}
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) {
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
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)
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
+}
+
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