fix LLVM applies libcalls attributes to fn definitions, not just decls
MODIFY
llvm_util/known_fns.cpp
llvm_util/known_fns.cpp
using namespace IR;
using namespace std;
-#define RETURN_VAL(op) \
- return { op, std::move(attrs), std::move(param_attrs), false }
-#define RETURN_EXACT() \
- return { nullptr, std::move(attrs), std::move(param_attrs), false }
-#define RETURN_APPROX() \
- return { nullptr, std::move(attrs), std::move(param_attrs), true }
-
static unsigned align(llvm::CallInst &i) {
auto a = i.getRetAlign();
return a.hasValue() ? a.getValue().value() : 0;
@@ -27,55 +20,15 @@ static unsigned align(llvm::CallInst &i) {
namespace llvm_util {
-tuple<unique_ptr<Instr>, FnAttrs, vector<ParamAttrs>, bool>
-known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
- BasicBlock &BB, const vector<Value*> &args) {
- FnAttrs attrs;
- vector<ParamAttrs> param_attrs;
-
- auto ty = llvm_type2alive(i.getType());
- if (!ty)
- RETURN_EXACT();
-
- // TODO: add support for checking mismatch of C vs C++ alloc fns
- if (llvm::isMallocOrCallocLikeFn(&i, &TLI)) {
- bool isNonNull = i.hasRetAttr(llvm::Attribute::NonNull);
- //TODO: also null for C++'s new throwing operator
-
- // aligned malloc
- if (auto *algn = llvm::getAllocAlignment(&i, &TLI)) {
- if (auto algnint = dyn_cast<llvm::ConstantInt>(algn)) {
- RETURN_VAL(
- make_unique<Malloc>(*ty, value_name(i), *args[1], isNonNull,
- algnint->getZExtValue()));
- } else {
- // TODO: add support for non-const alignments
- RETURN_APPROX();
- }
- }
-
- // calloc
- if (auto *init = llvm::getInitialValueOfAllocation(&i, &TLI, i.getType());
- init && init->isNullValue())
- RETURN_VAL(
- make_unique<Calloc>(*ty, value_name(i), *args[0], *args[1], align(i)));
+#define RETURN_EXACT() \
+ return { std::move(param_attrs), false }
+#define RETURN_APPROX() \
+ return { std::move(param_attrs), true }
- // malloc or new
- RETURN_VAL(
- make_unique<Malloc>(*ty, value_name(i), *args[0], isNonNull, align(i)));
- }
- if (llvm::isReallocLikeFn(&i, &TLI)) {
- bool isNonNull = i.hasRetAttr(llvm::Attribute::NonNull);
- RETURN_VAL(make_unique<Malloc>(*ty, value_name(i), *args[0], *args[1],
- isNonNull, align(i)));
- }
- if (llvm::isFreeCall(&i, &TLI)) {
- if (i.hasFnAttr(llvm::Attribute::NoFree)) {
- auto zero = make_intconst(0, 1);
- RETURN_VAL(make_unique<Assume>(*zero, Assume::AndNonPoison));
- }
- RETURN_VAL(make_unique<Free>(*args[0]));
- }
+static pair<vector<ParamAttrs>, bool>
+implict_attrs_(llvm::LibFunc libfn, FnAttrs &attrs, bool is_void,
+ unsigned num_args) {
+ vector<ParamAttrs> param_attrs;
auto set_param = [&](unsigned i, ParamAttrs::Attribute attr) {
if (param_attrs.size() <= i)
@@ -89,144 +42,26 @@ known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
};
auto ret_and_args_no_undef = [&]() {
- if (!dynamic_cast<VoidType*>(ty))
+ if (!is_void)
attrs.set(FnAttrs::NoUndef);
- for (unsigned i = 0, e = args.size(); i < e; ++i) {
+ for (unsigned i = 0; i < num_args; ++i) {
set_param(i, ParamAttrs::NoUndef);
}
};
- auto fputc_attr = [&]() {
+ switch (libfn) {
+ case llvm::LibFunc_calloc:
+ case llvm::LibFunc_malloc:
+ case llvm::LibFunc_vec_calloc:
ret_and_args_no_undef();
+ attrs.set(FnAttrs::InaccessibleMemOnly);
+ attrs.set(FnAttrs::NoAlias);
attrs.set(FnAttrs::NoThrow);
attrs.set(FnAttrs::NoFree);
- set_param(1, ParamAttrs::NoCapture);
- };
-
- auto decl = i.getCalledFunction();
- llvm::LibFunc libfn;
- if (!decl || !TLI.getLibFunc(*decl, libfn) || !TLI.has(libfn))
+ attrs.set(FnAttrs::WillReturn);
RETURN_EXACT();
- switch (libfn) {
- case llvm::LibFunc_memset: // void* memset(void *ptr, int val, size_t bytes)
- BB.addInstr(make_unique<Memset>(*args[0], *args[1], *args[2], 1));
- RETURN_VAL(make_unique<UnaryOp>(*ty, value_name(i), *args[0],
- UnaryOp::Copy));
- case llvm::LibFunc_strlen:
- RETURN_VAL(make_unique<Strlen>(*ty, value_name(i), *args[0]));
- case llvm::LibFunc_memcmp:
- case llvm::LibFunc_bcmp: {
- RETURN_VAL(
- make_unique<Memcmp>(*ty, value_name(i), *args[0], *args[1], *args[2],
- libfn == llvm::LibFunc_bcmp));
- }
- case llvm::LibFunc_ffs:
- case llvm::LibFunc_ffsl:
- case llvm::LibFunc_ffsll: {
- bool needs_trunc = &args[0]->getType() != ty;
- auto *Op = new UnaryOp(args[0]->getType(),
- value_name(i) + (needs_trunc ? "#beftrunc" : ""),
- *args[0], UnaryOp::FFS);
- if (!needs_trunc)
- RETURN_VAL(unique_ptr<UnaryOp>(Op));
-
- BB.addInstr(unique_ptr<UnaryOp>(Op));
- RETURN_VAL(
- make_unique<ConversionOp>(*ty, value_name(i), *Op, ConversionOp::Trunc));
- }
-
- case llvm::LibFunc_abs:
- case llvm::LibFunc_labs:
- case llvm::LibFunc_llabs:
- RETURN_VAL(make_unique<BinOp>(*ty, value_name(i), *args[0],
- *make_intconst(1, 1), BinOp::Abs));
-
- case llvm::LibFunc_fabs:
- case llvm::LibFunc_fabsf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::FAbs, parse_fmath(i)));
-
- case llvm::LibFunc_ceil:
- case llvm::LibFunc_ceilf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::Ceil, parse_fmath(i)));
-
- case llvm::LibFunc_floor:
- case llvm::LibFunc_floorf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::Floor, parse_fmath(i)));
-
- case llvm::LibFunc_nearbyint:
- case llvm::LibFunc_nearbyintf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::NearbyInt, parse_fmath(i)));
-
- case llvm::LibFunc_rint:
- case llvm::LibFunc_rintf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::RInt, parse_fmath(i)));
-
- case llvm::LibFunc_round:
- case llvm::LibFunc_roundf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::Round, parse_fmath(i)));
-
- case llvm::LibFunc_roundeven:
- case llvm::LibFunc_roundevenf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::RoundEven, parse_fmath(i)));
-
- case llvm::LibFunc_trunc:
- case llvm::LibFunc_truncf:
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::Trunc, parse_fmath(i)));
-
- case llvm::LibFunc_copysign:
- case llvm::LibFunc_copysignf:
- RETURN_VAL(make_unique<FpBinOp>(*ty, value_name(i), *args[0], *args[1],
- FpBinOp::CopySign, parse_fmath(i)));
-
- case llvm::LibFunc_sqrt:
- case llvm::LibFunc_sqrtf:
- BB.addInstr(make_unique<Assume>(*args[0], Assume::WellDefined));
- RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
- FpUnaryOp::Sqrt, parse_fmath(i)));
-
- case llvm::LibFunc_fwrite: {
- auto size = getInt(*args[1]);
- auto count = getInt(*args[2]);
- if (size || count) {
- // size_t fwrite(const void *ptr, 0, 0, FILE *stream) -> 0
- if ((size && *size == 0) || (count && *count == 0))
- RETURN_VAL(
- make_unique<UnaryOp>(*ty, value_name(i),
- *make_intconst(0, ty->bits()), UnaryOp::Copy));
- }
- if (size && count) {
- auto bytes = *size * *count;
- // (void)fwrite(const void *ptr, 1, 1, FILE *stream) ->
- // (void)fputc(int c, FILE *stream))
- if (bytes == 1 && i.use_empty() && TLI.has(llvm::LibFunc_fputc)) {
- fputc_attr();
- auto &byteTy = get_int_type(8); // FIXME
- auto &i32 = get_int_type(32);
- auto call
- = make_unique<FnCall>(i32, value_name(i), "@fputc", std::move(attrs));
- auto load
- = make_unique<Load>(byteTy, value_name(i) + "#load", *args[0], 1);
- auto load_zext
- = make_unique<ConversionOp>(i32, value_name(i) + "#zext", *load,
- ConversionOp::ZExt);
- call->addArg(*load_zext, std::move(param_attrs[0]));
- call->addArg(*args[3], std::move(param_attrs[1]));
- BB.addInstr(std::move(load));
- BB.addInstr(std::move(load_zext));
- RETURN_VAL(std::move(call));
- }
- }
- }
- [[fallthrough]];
+ case llvm::LibFunc_fwrite:
case llvm::LibFunc_fwrite_unlocked:
case llvm::LibFunc_fread:
case llvm::LibFunc_fread_unlocked:
@@ -260,7 +95,10 @@ known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
case llvm::LibFunc_fputc:
case llvm::LibFunc_fputc_unlocked:
case llvm::LibFunc_fstat:
- fputc_attr();
+ ret_and_args_no_undef();
+ attrs.set(FnAttrs::NoThrow);
+ attrs.set(FnAttrs::NoFree);
+ set_param(1, ParamAttrs::NoCapture);
RETURN_EXACT();
case llvm::LibFunc_fputs_unlocked:
@@ -552,4 +390,206 @@ known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
}
}
+pair<vector<ParamAttrs>, bool>
+llvm_implict_attrs(llvm::Function &f, const llvm::TargetLibraryInfo &TLI,
+ FnAttrs &attrs) {
+ llvm::LibFunc libfn;
+ if (!TLI.getLibFunc(f, libfn) || !TLI.has(libfn))
+ return { {}, false };
+ return
+ implict_attrs_(libfn, attrs, f.getReturnType()->isVoidTy(), f.arg_size());
+}
+
+#undef RETURN_EXACT
+#undef RETURN_APPROX
+
+#define RETURN_VAL(op) \
+ return { op, std::move(attrs), std::move(param_attrs), false }
+#define RETURN_EXACT() \
+ return { nullptr, std::move(attrs), std::move(param_attrs), false }
+#define RETURN_APPROX() \
+ return { nullptr, std::move(attrs), std::move(param_attrs), true }
+
+tuple<unique_ptr<Instr>, FnAttrs, vector<ParamAttrs>, bool>
+known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
+ BasicBlock &BB, const vector<Value*> &args) {
+ FnAttrs attrs;
+ vector<ParamAttrs> param_attrs;
+
+ auto ty = llvm_type2alive(i.getType());
+ if (!ty)
+ RETURN_EXACT();
+
+ // TODO: add support for checking mismatch of C vs C++ alloc fns
+ if (llvm::isMallocOrCallocLikeFn(&i, &TLI)) {
+ bool isNonNull = i.hasRetAttr(llvm::Attribute::NonNull);
+ //TODO: also null for C++'s new throwing operator
+
+ // aligned malloc
+ if (auto *algn = llvm::getAllocAlignment(&i, &TLI)) {
+ if (auto algnint = dyn_cast<llvm::ConstantInt>(algn)) {
+ RETURN_VAL(
+ make_unique<Malloc>(*ty, value_name(i), *args[1], isNonNull,
+ algnint->getZExtValue()));
+ } else {
+ // TODO: add support for non-const alignments
+ RETURN_APPROX();
+ }
+ }
+
+ // calloc
+ if (auto *init = llvm::getInitialValueOfAllocation(&i, &TLI, i.getType());
+ init && init->isNullValue())
+ RETURN_VAL(
+ make_unique<Calloc>(*ty, value_name(i), *args[0], *args[1], align(i)));
+
+ // malloc or new
+ RETURN_VAL(
+ make_unique<Malloc>(*ty, value_name(i), *args[0], isNonNull, align(i)));
+ }
+ if (llvm::isReallocLikeFn(&i, &TLI)) {
+ bool isNonNull = i.hasRetAttr(llvm::Attribute::NonNull);
+ RETURN_VAL(make_unique<Malloc>(*ty, value_name(i), *args[0], *args[1],
+ isNonNull, align(i)));
+ }
+ if (llvm::isFreeCall(&i, &TLI)) {
+ if (i.hasFnAttr(llvm::Attribute::NoFree)) {
+ auto zero = make_intconst(0, 1);
+ RETURN_VAL(make_unique<Assume>(*zero, Assume::AndNonPoison));
+ }
+ RETURN_VAL(make_unique<Free>(*args[0]));
+ }
+
+ auto decl = i.getCalledFunction();
+ llvm::LibFunc libfn;
+ if (!decl || !TLI.getLibFunc(*decl, libfn) || !TLI.has(libfn))
+ RETURN_EXACT();
+
+ switch (libfn) {
+ case llvm::LibFunc_memset: // void* memset(void *ptr, int val, size_t bytes)
+ BB.addInstr(make_unique<Memset>(*args[0], *args[1], *args[2], 1));
+ RETURN_VAL(make_unique<UnaryOp>(*ty, value_name(i), *args[0],
+ UnaryOp::Copy));
+ case llvm::LibFunc_strlen:
+ RETURN_VAL(make_unique<Strlen>(*ty, value_name(i), *args[0]));
+ case llvm::LibFunc_memcmp:
+ case llvm::LibFunc_bcmp: {
+ RETURN_VAL(
+ make_unique<Memcmp>(*ty, value_name(i), *args[0], *args[1], *args[2],
+ libfn == llvm::LibFunc_bcmp));
+ }
+ case llvm::LibFunc_ffs:
+ case llvm::LibFunc_ffsl:
+ case llvm::LibFunc_ffsll: {
+ bool needs_trunc = &args[0]->getType() != ty;
+ auto *Op = new UnaryOp(args[0]->getType(),
+ value_name(i) + (needs_trunc ? "#beftrunc" : ""),
+ *args[0], UnaryOp::FFS);
+ if (!needs_trunc)
+ RETURN_VAL(unique_ptr<UnaryOp>(Op));
+
+ BB.addInstr(unique_ptr<UnaryOp>(Op));
+ RETURN_VAL(
+ make_unique<ConversionOp>(*ty, value_name(i), *Op, ConversionOp::Trunc));
+ }
+
+ case llvm::LibFunc_abs:
+ case llvm::LibFunc_labs:
+ case llvm::LibFunc_llabs:
+ RETURN_VAL(make_unique<BinOp>(*ty, value_name(i), *args[0],
+ *make_intconst(1, 1), BinOp::Abs));
+
+ case llvm::LibFunc_fabs:
+ case llvm::LibFunc_fabsf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::FAbs, parse_fmath(i)));
+
+ case llvm::LibFunc_ceil:
+ case llvm::LibFunc_ceilf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::Ceil, parse_fmath(i)));
+
+ case llvm::LibFunc_floor:
+ case llvm::LibFunc_floorf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::Floor, parse_fmath(i)));
+
+ case llvm::LibFunc_nearbyint:
+ case llvm::LibFunc_nearbyintf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::NearbyInt, parse_fmath(i)));
+
+ case llvm::LibFunc_rint:
+ case llvm::LibFunc_rintf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::RInt, parse_fmath(i)));
+
+ case llvm::LibFunc_round:
+ case llvm::LibFunc_roundf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::Round, parse_fmath(i)));
+
+ case llvm::LibFunc_roundeven:
+ case llvm::LibFunc_roundevenf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::RoundEven, parse_fmath(i)));
+
+ case llvm::LibFunc_trunc:
+ case llvm::LibFunc_truncf:
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::Trunc, parse_fmath(i)));
+
+ case llvm::LibFunc_copysign:
+ case llvm::LibFunc_copysignf:
+ RETURN_VAL(make_unique<FpBinOp>(*ty, value_name(i), *args[0], *args[1],
+ FpBinOp::CopySign, parse_fmath(i)));
+
+ case llvm::LibFunc_sqrt:
+ case llvm::LibFunc_sqrtf:
+ BB.addInstr(make_unique<Assume>(*args[0], Assume::WellDefined));
+ RETURN_VAL(make_unique<FpUnaryOp>(*ty, value_name(i), *args[0],
+ FpUnaryOp::Sqrt, parse_fmath(i)));
+ case llvm::LibFunc_fwrite: {
+ auto size = getInt(*args[1]);
+ auto count = getInt(*args[2]);
+ if (size || count) {
+ // size_t fwrite(const void *ptr, 0, 0, FILE *stream) -> 0
+ if ((size && *size == 0) || (count && *count == 0))
+ RETURN_VAL(
+ make_unique<UnaryOp>(*ty, value_name(i),
+ *make_intconst(0, ty->bits()), UnaryOp::Copy));
+ }
+ if (size && count) {
+ auto bytes = *size * *count;
+ // (void)fwrite(const void *ptr, 1, 1, FILE *stream) ->
+ // (void)fputc(int c, FILE *stream))
+ if (bytes == 1 && i.use_empty() && TLI.has(llvm::LibFunc_fputc)) {
+ auto [param_attrs, _]
+ = implict_attrs_(llvm::LibFunc_fputc, attrs, false, 2);
+ auto &byteTy = get_int_type(8); // FIXME
+ auto &i32 = get_int_type(32);
+ auto call
+ = make_unique<FnCall>(i32, value_name(i), "@fputc", std::move(attrs));
+ auto load
+ = make_unique<Load>(byteTy, value_name(i) + "#load", *args[0], 1);
+ auto load_zext
+ = make_unique<ConversionOp>(i32, value_name(i) + "#zext", *load,
+ ConversionOp::ZExt);
+ call->addArg(*load_zext, std::move(param_attrs[0]));
+ call->addArg(*args[3], std::move(param_attrs[1]));
+ BB.addInstr(std::move(load));
+ BB.addInstr(std::move(load_zext));
+ RETURN_VAL(std::move(call));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ auto [params, approx] = llvm_implict_attrs(*decl, TLI, attrs);
+ return { nullptr, std::move(attrs), std::move(params), approx };
+}
+
}

MODIFY
llvm_util/known_fns.h
llvm_util/known_fns.h
namespace llvm {
class CallInst;
+class Function;
class TargetLibraryInfo;
}
@@ -21,6 +22,11 @@ class Value;
namespace llvm_util {
+// returned bool indicates whether it's a known function call
+std::pair<std::vector<IR::ParamAttrs>, bool>
+llvm_implict_attrs(llvm::Function &f, const llvm::TargetLibraryInfo &TLI,
+ IR::FnAttrs &attrs);
+
// returned bool indicates whether it's a known function call
std::tuple<std::unique_ptr<IR::Instr>, IR::FnAttrs, std::vector<IR::ParamAttrs>,
bool>

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -1432,6 +1432,9 @@ public:
f.isVarArg());
reset_state(Fn);
+ auto &attrs = Fn.getFnAttrs();
+ auto [param_attrs, approx] = llvm_implict_attrs(f, TLI, attrs);
+
llvm::AttributeList attrlist = f.getAttributes();
for (unsigned idx = 0; idx < f.arg_size(); ++idx) {
@@ -1440,7 +1443,8 @@ public:
attrlist.getAttributes(llvm::AttributeList::FirstArgIndex + idx);
auto ty = llvm_type2alive(arg.getType());
- ParamAttrs attrs;
+ ParamAttrs attrs = idx < param_attrs.size() ? param_attrs[idx]
+ : ParamAttrs();
if (!ty || !handleParamAttrs(argattr, attrs, false))
return {};
auto val = make_unique<Input>(*ty, value_name(arg), std::move(attrs));
@@ -1454,7 +1458,6 @@ public:
Fn.addInput(std::move(val));
}
- auto &attrs = Fn.getFnAttrs();
const auto &ridx = llvm::AttributeList::ReturnIndex;
const auto &fnidx = llvm::AttributeList::FunctionIndex;
handleRetAttrs(attrlist.getAttributes(ridx), attrs);

MODIFY
llvm_util/llvm_optimizer.cpp
llvm_util/llvm_optimizer.cpp
@@ -9,7 +9,7 @@ using namespace std;
namespace llvm_util {
-string optimize_module(llvm::Module *M, llvm::StringRef optArgs) {
+string optimize_module(llvm::Module *M, string_view optArgs) {
llvm::LoopAnalysisManager LAM;
llvm::FunctionAnalysisManager FAM;
llvm::CGSCCAnalysisManager CGAM;

MODIFY
llvm_util/llvm_optimizer.h
llvm_util/llvm_optimizer.h
// Copyright (c) 2018-present The Alive2 Authors.
// Distributed under the MIT license that can be found in the LICENSE file.
-#include <llvm/ADT/StringRef.h>
#include <string>
+#include <string_view>
namespace llvm {
class Module;
}
namespace llvm_util {
-std::string optimize_module(llvm::Module *M, llvm::StringRef optArgs);
+std::string optimize_module(llvm::Module *M, std::string_view optArgs);
}

ADD
tests/alive-tv/libcalls/defined-fn.src.ll
+define ptr @calloc(i64 %nmeb, i64 %sz) inaccessiblememonly {
+ ret ptr null
+}

ADD
tests/alive-tv/libcalls/defined-fn.tgt.ll
+define noundef ptr @calloc(i64 noundef %a, i64 %b) nofree willreturn inaccessiblememonly {
+ ret ptr null
+}

fix #772: LLVM applies libcalls attributes to fn definitions, not just decls