add support for the allocsize fn attribute
MODIFY
ir/attrs.cpp
ir/attrs.cpp
// Distributed under the MIT license that can be found in the LICENSE file.
#include "ir/attrs.h"
+#include "ir/globals.h"
#include "ir/memory.h"
#include "ir/state.h"
#include "ir/state_value.h"
@@ -88,6 +89,12 @@ ostream& operator<<(ostream &os, const FnAttrs &attr) {
os << " inaccessiblememonly";
if (attr.has(FnAttrs::NullPointerIsValid))
os << " null_pointer_is_valid";
+ if (attr.has(FnAttrs::AllocSize)) {
+ os << " allocsize(" << attr.allocsize_0;
+ if (attr.allocsize_1 != -1u)
+ os << ", " << attr.allocsize_1;
+ os << ')';
+ }
attr.fp_denormal.print(os);
if (attr.fp_denormal32)
@@ -142,7 +149,7 @@ static void
encodePtrAttrs(const State &s, const expr &ptrvalue,
AndExpr &UB, expr &non_poison,
uint64_t derefBytes, uint64_t derefOrNullBytes, uint64_t align,
- bool nonnull, bool nocapture) {
+ bool nonnull, bool nocapture, const expr &deref_expr) {
Pointer p(s.getMemory(), ptrvalue);
if (nonnull)
@@ -150,12 +157,14 @@ encodePtrAttrs(const State &s, const expr &ptrvalue,
non_poison &= p.isNocapture().implies(nocapture);
- if (derefBytes || derefOrNullBytes) {
+ if (derefBytes || derefOrNullBytes || deref_expr.isValid()) {
// dereferenceable, byval (ParamAttrs), dereferenceable_or_null
if (derefBytes)
UB.add(p.isDereferenceable(derefBytes, align));
if (derefOrNullBytes)
UB.add(p.isDereferenceable(derefOrNullBytes, align)() || p.isNull());
+ if (deref_expr.isValid())
+ UB.add(p.isDereferenceable(deref_expr, align, false)() || p.isNull());
} else if (align > 1)
non_poison &= p.isAligned(align);
}
@@ -167,7 +176,7 @@ ParamAttrs::encode(const State &s, const StateValue &val, const Type &ty) const
if (ty.isPtrType())
encodePtrAttrs(s, val.value, UB, new_non_poison, getDerefBytes(),
- derefOrNullBytes, align, has(NonNull), has(NoCapture));
+ derefOrNullBytes, align, has(NonNull), has(NoCapture), {});
if (poisonImpliesUB()) {
UB.add(std::move(new_non_poison));
@@ -185,11 +194,12 @@ bool FnAttrs::isNonNull() const {
bool FnAttrs::poisonImpliesUB() const {
return has(Dereferenceable) || has(NoUndef) || has(NNaN) ||
- has(DereferenceableOrNull);
+ has(DereferenceableOrNull) || has(AllocSize);
}
bool FnAttrs::undefImpliesUB() const {
- bool ub = has(NoUndef) || has(Dereferenceable) || has(DereferenceableOrNull);
+ bool ub = has(NoUndef) || has(Dereferenceable) || has(DereferenceableOrNull)||
+ has(AllocSize);
assert(!ub || poisonImpliesUB());
return ub;
}
@@ -223,7 +233,8 @@ bool FnAttrs::refinedBy(const FnAttrs &other) const {
}
pair<AndExpr, expr>
-FnAttrs::encode(const State &s, const StateValue &val, const Type &ty) const {
+FnAttrs::encode(State &s, const StateValue &val, const Type &ty,
+ const vector<pair<Value*, ParamAttrs>> &args) const {
AndExpr UB;
expr new_non_poison = val.non_poison;
@@ -232,9 +243,22 @@ FnAttrs::encode(const State &s, const StateValue &val, const Type &ty) const {
new_non_poison &= !val.value.isNaN();
}
- if (ty.isPtrType())
+ if (ty.isPtrType()) {
+ expr deref;
+ if (has(AllocSize)) {
+ auto &arg0 = s[*args[allocsize_0].first];
+ UB.add(arg0.non_poison);
+ deref = arg0.value.zextOrTrunc(bits_size_t);
+
+ if (allocsize_1 != -1u) {
+ auto &arg1 = s[*args[allocsize_1].first];
+ UB.add(arg1.non_poison);
+ deref = deref * arg1.value.zextOrTrunc(bits_size_t);
+ }
+ }
encodePtrAttrs(s, val.value, UB, new_non_poison, derefBytes,
- derefOrNullBytes, align, has(NonNull), false);
+ derefOrNullBytes, align, has(NonNull), false, deref);
+ }
if (poisonImpliesUB()) {
UB.add(std::move(new_non_poison));

MODIFY
ir/attrs.h
ir/attrs.h
#include "smt/exprs.h"
#include <optional>
#include <ostream>
+#include <vector>
namespace IR {
+class ParamAttrs;
class State;
struct StateValue;
class Type;
+class Value;
class ParamAttrs final {
unsigned bits;
@@ -70,7 +73,8 @@ public:
NoThrow = 1 << 10, NoAlias = 1 << 11, WillReturn = 1 << 12,
DereferenceableOrNull = 1 << 13,
InaccessibleMemOnly = 1 << 14,
- NullPointerIsValid = 1 << 15 };
+ NullPointerIsValid = 1 << 15,
+ AllocSize = 1 << 16 };
FnAttrs(unsigned bits = None) : bits(bits) {}
@@ -81,6 +85,9 @@ public:
uint64_t derefOrNullBytes = 0; // DereferenceableOrNull
unsigned align = 0;
+ unsigned allocsize_0;
+ unsigned allocsize_1 = -1u;
+
bool isNonNull() const;
// Returns true if returning poison or an aggregate having a poison is UB
@@ -96,7 +103,8 @@ public:
// Encodes the semantics of attributes using UB and poison.
std::pair<smt::AndExpr, smt::expr>
- encode(const State &s, const StateValue &val, const Type &ty) const;
+ encode(State &s, const StateValue &val, const Type &ty,
+ const std::vector<std::pair<Value*, ParamAttrs>> &args) const;
friend std::ostream& operator<<(std::ostream &os, const FnAttrs &attr);
};

MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -2157,31 +2157,27 @@ static void unpack_ret_ty (vector<Type*> &out_types, Type &ty) {
static StateValue
check_return_value(State &s, StateValue &&val, const Type &ty,
- const FnAttrs &attrs, bool is_ret_instr) {
- if (ty.isPtrType() && is_ret_instr) {
- Pointer p(s.getMemory(), val.value);
- val.non_poison &= !p.isStackAllocated();
- val.non_poison &= !p.isNocapture();
- }
-
- auto [UB, new_non_poison] = attrs.encode(s, val, ty);
+ const FnAttrs &attrs,
+ const vector<pair<Value*, ParamAttrs>> &args) {
+ auto [UB, new_non_poison] = attrs.encode(s, val, ty, args);
s.addUB(std::move(UB));
return { std::move(val.value), std::move(new_non_poison) };
}
static StateValue
pack_return(State &s, Type &ty, vector<StateValue> &vals, const FnAttrs &attrs,
- unsigned &idx) {
+ unsigned &idx, const vector<pair<Value*, ParamAttrs>> &args) {
if (auto agg = ty.getAsAggregateType()) {
vector<StateValue> vs;
for (unsigned i = 0, e = agg->numElementsConst(); i != e; ++i) {
if (!agg->isPadding(i))
- vs.emplace_back(pack_return(s, agg->getChild(i), vals, attrs, idx));
+ vs.emplace_back(
+ pack_return(s, agg->getChild(i), vals, attrs, idx, args));
}
return agg->aggregateVals(vs);
}
- return check_return_value(s, std::move(vals[idx++]), ty, attrs, false);
+ return check_return_value(s, std::move(vals[idx++]), ty, attrs, args);
}
StateValue FnCall::toSMT(State &s) const {
@@ -2256,10 +2252,11 @@ StateValue FnCall::toSMT(State &s) const {
s.addUB(expr(false));
unsigned idx = 0;
- auto ret = s.addFnCall(fnName_mangled.str(), std::move(inputs), std::move(ptr_inputs),
- out_types, attrs);
+ auto ret = s.addFnCall(fnName_mangled.str(), std::move(inputs),
+ std::move(ptr_inputs), out_types, attrs);
- return isVoid() ? StateValue() : pack_return(s, getType(), ret, attrs, idx);
+ return isVoid() ? StateValue()
+ : pack_return(s, getType(), ret, attrs, idx, args);
}
expr FnCall::getTypeConstraints(const Function &f) const {
@@ -2830,7 +2827,14 @@ check_ret_attributes(State &s, StateValue &&sv, const Type &t,
}
return agg->aggregateVals(vals);
}
- return check_return_value(s, std::move(sv), t, attrs, true);
+
+ if (t.isPtrType()) {
+ Pointer p(s.getMemory(), sv.value);
+ sv.non_poison &= !p.isStackAllocated();
+ sv.non_poison &= !p.isNocapture();
+ }
+
+ return check_return_value(s, std::move(sv), t, attrs, {});
}
static void eq_val_rec(State &s, const Type &t, const StateValue &a,

MODIFY
llvm_util/known_fns.cpp
llvm_util/known_fns.cpp
@@ -412,6 +412,7 @@ known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
if (auto *algn = llvm::getAllocAlignment(&i, &TLI)) {
if (auto algnint = dyn_cast<llvm::ConstantInt>(algn)) {
attrs.align = algnint->getZExtValue();
+ attrs.allocsize_0 = 0;
RETURN_VAL(
make_unique<Malloc>(*ty, value_name(i), *args[1], std::move(attrs)));
} else {

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -1248,7 +1248,7 @@ public:
case llvm::Attribute::ZExt:
// TODO: not important for IR verification, but we should check that
// they don't change
- continue;
+ break;
case llvm::Attribute::ByVal: {
attrs.set(ParamAttrs::ByVal);
@@ -1258,55 +1258,55 @@ public:
attrs.set(ParamAttrs::Align);
attrs.align = max(attrs.align, DL().getABITypeAlignment(ty));
- continue;
+ break;
}
case llvm::Attribute::NonNull:
attrs.set(ParamAttrs::NonNull);
- continue;
+ break;
case llvm::Attribute::NoCapture:
attrs.set(ParamAttrs::NoCapture);
- continue;
+ break;
case llvm::Attribute::ReadOnly:
attrs.set(ParamAttrs::NoWrite);
- continue;
+ break;
case llvm::Attribute::WriteOnly:
attrs.set(ParamAttrs::NoRead);
- continue;
+ break;
case llvm::Attribute::ReadNone:
// TODO: can this pointer be freed?
attrs.set(ParamAttrs::NoRead);
attrs.set(ParamAttrs::NoWrite);
- continue;
+ break;
case llvm::Attribute::Dereferenceable:
attrs.set(ParamAttrs::Dereferenceable);
attrs.derefBytes = max(attrs.derefBytes,
llvmattr.getDereferenceableBytes());
- continue;
+ break;
case llvm::Attribute::DereferenceableOrNull:
attrs.set(ParamAttrs::DereferenceableOrNull);
attrs.derefOrNullBytes = max(attrs.derefOrNullBytes,
llvmattr.getDereferenceableOrNullBytes());
- continue;
+ break;
case llvm::Attribute::Alignment:
attrs.set(ParamAttrs::Align);
attrs.align = max(attrs.align, llvmattr.getAlignment()->value());
- continue;
+ break;
case llvm::Attribute::NoUndef:
attrs.set(ParamAttrs::NoUndef);
- continue;
+ break;
case llvm::Attribute::Returned:
attrs.set(ParamAttrs::Returned);
- continue;
+ break;
default:
// If it is call site, it should be added at approximation list
@@ -1414,6 +1414,15 @@ public:
attrs.set(FnAttrs::NoFree);
break;
+
+ case llvm::Attribute::AllocSize: {
+ attrs.set(FnAttrs::AllocSize);
+ auto args = llvmattr.getAllocSizeArgs();
+ attrs.allocsize_0 = args.first;
+ if (args.second)
+ attrs.allocsize_1 = *args.second;
+ break;
+ }
case llvm::Attribute::NoReturn:
attrs.set(FnAttrs::NoReturn);
break;

ADD
tests/alive-tv/attrs/allocsize.srctgt.ll
+define ptr @src() {
+ %p = call ptr @my_malloc(i32 23)
+ ret ptr %p
+}
+
+define ptr @tgt() {
+ %p = call dereferenceable_or_null(23) ptr @my_malloc(i32 23)
+ ret ptr %p
+}
+
+declare ptr @my_malloc(i32) allocsize(0)

add support for the allocsize fn attribute