Add support for dereferenceable_or_null
MODIFY
ir/attrs.cpp
ir/attrs.cpp
@@ -28,6 +28,8 @@ ostream& operator<<(ostream &os, const ParamAttrs &attr) {
os << "returned ";
if (attr.has(ParamAttrs::NoAlias))
os << "noalias ";
+ if (attr.has(ParamAttrs::DereferenceableOrNull))
+ os << "dereferenceable_or_null(" << attr.derefOrNullBytes << ") ";
return os;
}
@@ -59,11 +61,14 @@ ostream& operator<<(ostream &os, const FnAttrs &attr) {
os << " noalias";
if (attr.has(FnAttrs::WillReturn))
os << " willreturn";
+ if (attr.has(FnAttrs::DereferenceableOrNull))
+ os << " dereferenceable_or_null(" << attr.derefOrNullBytes << ')';
return os;
}
bool ParamAttrs::undefImpliesUB() const {
- bool ub = has(NoUndef) || has(Dereferenceable) || has(ByVal);
+ bool ub = has(NoUndef) || has(Dereferenceable) || has(ByVal) ||
+ has(DereferenceableOrNull);
assert(!ub || poisonImpliesUB());
return ub;
}
@@ -79,11 +84,12 @@ uint64_t ParamAttrs::getDerefBytes() const {
}
bool FnAttrs::poisonImpliesUB() const {
- return has(Dereferenceable) || has(NoUndef) || has(NNaN);
+ return has(Dereferenceable) || has(NoUndef) || has(NNaN) ||
+ has(DereferenceableOrNull);
}
bool FnAttrs::undefImpliesUB() const {
- bool ub = has(NoUndef) || has(Dereferenceable);
+ bool ub = has(NoUndef) || has(Dereferenceable) || has(DereferenceableOrNull);
assert(!ub || poisonImpliesUB());
return ub;
}

MODIFY
ir/attrs.h
ir/attrs.h
@@ -14,11 +14,12 @@ public:
enum Attribute { None = 0, NonNull = 1<<0, ByVal = 1<<1, NoCapture = 1<<2,
NoRead = 1<<3, NoWrite = 1<<4, Dereferenceable = 1<<5,
NoUndef = 1<<6, Align = 1<<7, Returned = 1<<8,
- NoAlias = 1<<9 };
+ NoAlias = 1<<9, DereferenceableOrNull = 1<<10 };
ParamAttrs(unsigned bits = None) : bits(bits) {}
uint64_t derefBytes; // Dereferenceable
+ uint64_t derefOrNullBytes; // DereferenceableOrNull
unsigned blockSize; // exact block size for e.g. byval args
unsigned align = 1;
@@ -27,7 +28,8 @@ public:
// Returns true if it's UB for the argument to be poison / have a poison elem.
bool poisonImpliesUB() const
- { return has(Dereferenceable) || has(NoUndef) || has(ByVal); }
+ { return has(Dereferenceable) || has(NoUndef) || has(ByVal) ||
+ has(DereferenceableOrNull); }
// Returns true if it is UB for the argument to be (partially) undef.
bool undefImpliesUB() const;
@@ -46,7 +48,8 @@ public:
ArgMemOnly = 1 << 2, NNaN = 1 << 3, NoReturn = 1 << 4,
Dereferenceable = 1 << 5, NonNull = 1 << 6,
NoFree = 1 << 7, NoUndef = 1 << 8, Align = 1 << 9,
- NoThrow = 1 << 10, NoAlias = 1 << 11, WillReturn = 1 << 12 };
+ NoThrow = 1 << 10, NoAlias = 1 << 11, WillReturn = 1 << 12,
+ DereferenceableOrNull = 1 << 13 };
FnAttrs(unsigned bits = None) : bits(bits) {}
@@ -54,6 +57,7 @@ public:
void set(Attribute a) { bits |= (unsigned)a; }
uint64_t derefBytes; // Dereferenceable
+ uint64_t derefOrNullBytes; // DereferenceableOrNull
unsigned align = 1;
// Returns true if returning poison or an aggregate having a poison is UB

MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -1601,28 +1601,42 @@ bool FnCall::canFree() const {
uint64_t FnCall::getMaxAccessSize() const {
uint64_t sz = attrs.has(FnAttrs::Dereferenceable) ? attrs.derefBytes : 0;
+ if (attrs.has(FnAttrs::DereferenceableOrNull))
+ sz = max(sz, attrs.derefOrNullBytes);
+
for (auto &arg : args) {
if (arg.second.has(ParamAttrs::Dereferenceable))
sz = max(sz, arg.second.derefBytes);
+ if (arg.second.has(ParamAttrs::DereferenceableOrNull))
+ sz = max(sz, arg.second.derefOrNullBytes);
}
return sz;
}
FnCall::ByteAccessInfo FnCall::getByteAccessInfo() const {
+ // If bytesize is zero, this call does not participate in byte encoding.
+ uint64_t bytesize = 0;
+
+#define UPDATE(attr) \
+ do { \
+ uint64_t sz = 0; \
+ if (attr.has(decay<decltype(attr)>::type::Dereferenceable)) \
+ sz = attr.derefBytes; \
+ if (attr.has(decay<decltype(attr)>::type::DereferenceableOrNull)) \
+ sz = gcd(sz, attr.derefOrNullBytes); \
+ /* Without align, nothing is guaranteed about the bytesize */ \
+ sz = gcd(sz, retattr.align); \
+ bytesize = bytesize ? gcd(bytesize, sz) : sz; \
+ } while (0)
+
auto &retattr = getAttributes();
- bool has_deref = retattr.has(FnAttrs::Dereferenceable);
- uint64_t bytesize = has_deref ? gcd(retattr.derefBytes, retattr.align) : 0;
+ UPDATE(retattr);
for (auto &arg : args) {
if (!arg.first->getType().isPtrType())
continue;
- if (arg.second.has(ParamAttrs::Dereferenceable)) {
- has_deref = true;
- // Without align, nothing is guaranteed about the bytesize
- uint64_t b = gcd(arg.second.derefBytes, arg.second.align);
- bytesize = bytesize ? gcd(bytesize, b) : b;
- }
+ UPDATE(arg.second);
// Pointer arguments without dereferenceable attr don't contribute to the
// byte size.
// call f(* dereferenceable(n) align m %p, * %q) is equivalent to a dummy
@@ -1632,12 +1646,13 @@ FnCall::ByteAccessInfo FnCall::getByteAccessInfo() const {
// f(%p, %q) does not contribute to the bytesize. After bytesize is fixed,
// function calls update a memory with the granularity.
}
- if (!has_deref) {
+ if (bytesize == 0) {
// No dereferenceable attribute
- assert(bytesize == 0);
return {};
}
+#undef UPDATE
+
return ByteAccessInfo::anyType(bytesize);
}
@@ -1692,11 +1707,19 @@ static void unpack_inputs(State &s, Value &argv, Type &ty,
if (ty.isPtrType()) {
expr np(true);
Pointer p(s.getMemory(), move(value.value));
- if (argflag.has(ParamAttrs::Dereferenceable) ||
- argflag.has(ParamAttrs::ByVal))
+
+ bool isDeref = argflag.has(ParamAttrs::Dereferenceable) ||
+ argflag.has(ParamAttrs::ByVal);
+ bool isDerefOrNull = argflag.has(ParamAttrs::DereferenceableOrNull);
+
+ if (isDeref || isDerefOrNull) {
+ if (isDeref)
+ s.addUB(p.isDereferenceable(argflag.getDerefBytes(), argflag.align));
+ if (isDerefOrNull)
s.addUB(
- p.isDereferenceable(argflag.getDerefBytes(), argflag.align, false));
- else if (argflag.has(ParamAttrs::Align))
+ p.isDereferenceable(argflag.derefOrNullBytes, argflag.align)() ||
+ !p.isNonZero());
+ } else if (argflag.has(ParamAttrs::Align))
np &= p.isAligned(argflag.align);
if (argflag.has(ParamAttrs::NonNull))
@@ -1752,16 +1775,21 @@ pack_return(State &s, Type &ty, vector<StateValue> &vals, const FnAttrs &attrs,
}
bool isDeref = attrs.has(FnAttrs::Dereferenceable);
+ bool isDerefOrNull = attrs.has(FnAttrs::DereferenceableOrNull);
bool isNonNull = attrs.has(FnAttrs::NonNull);
bool isAlign = attrs.has(FnAttrs::Align);
- if (isDeref || isNonNull || isAlign) {
+ if (isDeref || isDerefOrNull || isNonNull || isAlign) {
assert(ty.isPtrType());
Pointer p(s.getMemory(), ret.value);
+ if (isDeref || isDerefOrNull) {
if (isDeref)
s.addUB(p.isDereferenceable(attrs.derefBytes, attrs.align));
- else if (isAlign)
+ if (isDerefOrNull)
+ s.addUB(p.isDereferenceable(attrs.derefOrNullBytes, attrs.align)() ||
+ !p.isNonZero());
+ } else if (isAlign)
ret.non_poison &= p.isAligned(attrs.align);
if (isNonNull)
@@ -2422,15 +2450,21 @@ StateValue Return::toSMT(State &s) const {
check_ret_attributes(s, retval, val->getType(), attrs);
bool isDeref = attrs.has(FnAttrs::Dereferenceable);
+ bool isDerefOrNull = attrs.has(FnAttrs::DereferenceableOrNull);
bool isNonNull = attrs.has(FnAttrs::NonNull);
bool isAlign = attrs.has(FnAttrs::Align);
- if (isDeref || isNonNull || isAlign) {
+ if (isDeref || isDerefOrNull || isNonNull || isAlign) {
assert(val->getType().isPtrType());
Pointer p(s.getMemory(), retval.value);
- if (isDeref) {
+ if (isDeref || isDerefOrNull) {
+ if (isDeref)
s.addUB(p.isDereferenceable(attrs.derefBytes, attrs.align));
+ if (isDerefOrNull)
+ s.addUB(p.isDereferenceable(attrs.derefOrNullBytes, attrs.align)() ||
+ !p.isNonZero());
+
if (has_alloca)
s.addUB(p.getAllocType() != Pointer::STACK);
} else if (isAlign)

MODIFY
ir/value.cpp
ir/value.cpp
@@ -220,16 +220,24 @@ StateValue Input::mkInput(State &s, const Type &ty, unsigned child) const {
// Some attributes generate poison rather than raise UB
expr np_from_attr(true);
+ if (ty.isPtrType()) {
+ Pointer p(s.getMemory(), val);
if (hasAttribute(ParamAttrs::NonNull))
- np_from_attr = Pointer(s.getMemory(), val).isNonZero();
-
- if (hasAttribute(ParamAttrs::Dereferenceable) ||
- hasAttribute(ParamAttrs::ByVal))
- s.addUB(Pointer(s.getMemory(), val)
- .isDereferenceable(attrs.getDerefBytes(), attrs.align, false));
- else if (hasAttribute(ParamAttrs::Align))
- np_from_attr &= Pointer(s.getMemory(), val).isAligned(attrs.align);
+ np_from_attr = p.isNonZero();
+
+ bool hasDeref = hasAttribute(ParamAttrs::Dereferenceable) ||
+ hasAttribute(ParamAttrs::ByVal);
+ bool hasDerefOrNull = hasAttribute(ParamAttrs::DereferenceableOrNull);
+ if (hasDeref || hasDerefOrNull) {
+ if (hasDeref)
+ s.addUB(p.isDereferenceable(attrs.getDerefBytes(), attrs.align));
+ if (hasDerefOrNull)
+ s.addUB(p.isDereferenceable(attrs.derefOrNullBytes, attrs.align)() ||
+ !p.isNonZero());
+ } else if (hasAttribute(ParamAttrs::Align))
+ np_from_attr &= p.isAligned(attrs.align);
+ }
if (attrs.poisonImpliesUB()) {
s.addUB(move(np_from_attr));

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -293,6 +293,12 @@ public:
attrs.derefBytes = b;
}
+ if (uint64_t b = max(i.getDereferenceableOrNullBytes(ret),
+ fn->getDereferenceableOrNullBytes(ret))) {
+ attrs.set(FnAttrs::DereferenceableOrNull);
+ attrs.derefOrNullBytes = b;
+ }
+
{
uint64_t align = 0;
llvm::MaybeAlign ra = i.getRetAlign();
@@ -337,6 +343,20 @@ public:
attr.derefBytes = derefb;
}
+ if (i.paramHasAttr(argidx, llvm::Attribute::DereferenceableOrNull)) {
+ uint64_t sz = 0;
+ // dereferenceable_or_null at caller
+ if (i.getAttributes()
+ .hasParamAttr(argidx, llvm::Attribute::DereferenceableOrNull))
+ sz = i.getParamAttr(argidx, llvm::Attribute::DereferenceableOrNull)
+ .getDereferenceableOrNullBytes();
+ if (argidx < fn->arg_size())
+ sz = max(sz, fn->getParamDereferenceableOrNullBytes(argidx));
+ assert(sz);
+ attr.set(ParamAttrs::DereferenceableOrNull);
+ attr.derefOrNullBytes = sz;
+ }
+
if (i.paramHasAttr(argidx, llvm::Attribute::Alignment)) {
uint64_t a = 0;
// alignment at caller
@@ -1083,6 +1103,11 @@ public:
attrs.derefBytes = attr.getDereferenceableBytes();
continue;
+ case llvm::Attribute::DereferenceableOrNull:
+ attrs.set(ParamAttrs::DereferenceableOrNull);
+ attrs.derefOrNullBytes = attr.getDereferenceableOrNullBytes();
+ continue;
+
case llvm::Attribute::Alignment:
attrs.set(ParamAttrs::Align);
attrs.align = attr.getAlignment()->value();

ADD
tests/alive-tv/attrs/dereferenceable_or_null-callsite.srctgt.ll
+define void @src(i32* %p) {
+ call void @f(i32* dereferenceable_or_null(4) %p)
+ ret void
+}
+
+define void @tgt(i32* %p) {
+ call void @f(i32* dereferenceable(4) %p)
+ ret void
+}
+
+declare void @f(i32*)
+
+; ERROR: Source is more defined than target

ADD
tests/alive-tv/attrs/dereferenceable_or_null.srctgt.ll
+define i32* @src(i32* dereferenceable_or_null(4) %p) {
+ ret i32* %p
+}
+
+define i32* @tgt(i32* dereferenceable(4) %p) {
+ ret i32* %p
+}
+
+; ERROR: Source is more defined than target

ADD
tests/alive-tv/attrs/dereferenceable_or_null2.srctgt.ll
+define i32* @src(i32* dereferenceable(4) %p) {
+ ret i32* %p
+}
+
+define i32* @tgt(i32* dereferenceable_or_null(4) %p) {
+ ret i32* %p
+}

ADD
tests/alive-tv/attrs/dereferenceable_or_null3.srctgt.ll
+define void @src(i32* dereferenceable_or_null(4) %p) {
+ ret void
+}
+
+define void @tgt(i32* dereferenceable_or_null(4) %p) {
+ %c = icmp eq i32* %p, null
+ br i1 %c, label %NOP, label %DEREFERENCE
+NOP:
+ ret void
+DEREFERENCE:
+ load i32, i32* %p, align 1
+ ret void
+}

ADD
tests/alive-tv/attrs/dereferenceable_or_null4.srctgt.ll
+define void @src(i32* dereferenceable_or_null(4) %p) {
+ ret void
+}
+
+define void @tgt(i32* dereferenceable_or_null(4) %p) {
+ load i32, i32* %p, align 1
+ ret void
+}
+
+; ERROR: Source is more defined than target

ADD
tests/alive-tv/attrs/dereferenceable_or_null5.srctgt.ll
+define void @src(i32* dereferenceable_or_null(4) dereferenceable(2) %p) {
+ ret void
+}
+
+define void @tgt(i32* dereferenceable_or_null(4) dereferenceable(2) %p) {
+ load i32, i32* %p, align 1
+ ret void
+}

ADD
tests/alive-tv/attrs/dereferenceable_or_null6.srctgt.ll
+define void @src(i32* dereferenceable_or_null(4) nonnull %p) {
+ ret void
+}
+
+define void @tgt(i32* dereferenceable_or_null(4) nonnull %p) {
+ load i32, i32* %p, align 1
+ ret void
+}

MODIFY
tools/transform.cpp
tools/transform.cpp
@@ -752,6 +752,16 @@ static void calculateAndInitConstants(Transform &t) {
uint64_t deref_bytes = i->getAttributes().derefBytes;
max_access_size = max(max_access_size, deref_bytes);
}
+ if (i->hasAttribute(ParamAttrs::DereferenceableOrNull)) {
+ // Optimization: unless explicitly compared with a null pointer, don't
+ // set nullptr_is_used to true.
+ // Null constant pointer will set nullptr_is_used to true anyway.
+ // Note that dereferenceable_or_null implies num_ptrinputs > 0,
+ // which may turn has_null_block on.
+ does_mem_access = true;
+ uint64_t deref_bytes = i->getAttributes().derefOrNullBytes;
+ max_access_size = max(max_access_size, deref_bytes);
+ }
if (i->hasAttribute(ParamAttrs::ByVal)) {
does_mem_access = true;
uint64_t sz = i->getAttributes().blockSize;

Add support for dereferenceable_or_null (#679)