clang plugin: encode I/O calls as unknown fncalls except when running InstCombine
MODIFY
ir/function.h
ir/function.h
@@ -62,6 +62,8 @@ class Function final {
unsigned bits_pointers = 64;
unsigned bits_ptr_offset = 64;
bool little_endian = true;
+ // If this is true, FnCalls having DependsOnFlag become valid unknown fn calls
+ bool fncall_valid_flag = true;
// constants used in this function
std::vector<std::unique_ptr<Value>> constants;
@@ -86,6 +88,9 @@ public:
auto& getFnAttrs() { return attrs; }
+ bool getFnCallValidFlag() const { return fncall_valid_flag; }
+ void setFnCallValidFlag(bool f) { fncall_valid_flag = f; }
+
smt::expr getTypeConstraints() const;
void fixupTypes(const smt::Model &m);

MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -1583,7 +1583,9 @@ void FnCall::print(ostream &os) const {
}
os << ')' << attrs;
- if (!valid)
+ // To print this when valid == DependsOnFlag, FnCall::print should know
+ // Function::fncall_valid_flag
+ if (valid == Invalid)
os << "\t; WARNING: unknown known function";
}
@@ -1666,7 +1668,9 @@ pack_return(State &s, Type &ty, vector<StateValue> &vals, const FnAttrs &attrs,
}
StateValue FnCall::toSMT(State &s) const {
- if (!valid) {
+ bool is_valid = valid == Valid ||
+ (valid == DependsOnFlag && s.getFn().getFnCallValidFlag());
+ if (!is_valid) {
s.addUB(expr());
return {};
}

MODIFY
ir/instr.h
ir/instr.h
@@ -769,14 +769,20 @@ public:
class FnCall final : public MemInstr {
+public:
+ enum ValidKind {
+ Invalid, Valid,
+ // This fn call is valid iff Function::fncall_valid_flag is true
+ DependsOnFlag
+ };
private:
std::string fnName;
std::vector<std::pair<Value*, ParamAttrs>> args;
FnAttrs attrs;
- bool valid;
+ ValidKind valid;
public:
FnCall(Type &type, std::string &&name, std::string &&fnName,
- FnAttrs &&attrs = FnAttrs::None, bool valid = true)
+ FnAttrs &&attrs = FnAttrs::None, ValidKind valid = Valid)
: MemInstr(type, std::move(name)), fnName(std::move(fnName)),
attrs(std::move(attrs)), valid(valid) {}
void addArg(Value &arg, ParamAttrs &&attrs);

MODIFY
llvm_util/known_fns.cpp
llvm_util/known_fns.cpp
using namespace IR;
using namespace std;
-#define RETURN_KNOWN(op) return { op, true }
-#define RETURN_FAIL_KNOWN() return { nullptr, true }
-#define RETURN_FAIL_UNKNOWN() return { nullptr, false }
+#define RETURN_KNOWN(op) return { op, FnKnown }
+#define RETURN_FAIL_KNOWN() return { nullptr, FnKnown }
+#define RETURN_FAIL_DEPENDS() return { nullptr, FnDependsOnOpt }
+#define RETURN_FAIL_UNKNOWN() return { nullptr, FnUnknown }
namespace llvm_util {
-pair<unique_ptr<Instr>, bool>
+pair<unique_ptr<Instr>, KnownFnKind>
known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
BasicBlock &BB, const vector<Value*> &args) {
auto ty = llvm_type2alive(i.getType());
@@ -72,7 +73,7 @@ known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
case llvm::LibFunc_perror:
case llvm::LibFunc_read:
case llvm::LibFunc_write:
- RETURN_FAIL_UNKNOWN();
+ RETURN_FAIL_DEPENDS();
default:
break;
}

MODIFY
llvm_util/known_fns.h
llvm_util/known_fns.h
@@ -21,8 +21,14 @@ class Value;
namespace llvm_util {
+enum KnownFnKind {
+ FnUnknown,
+ FnDependsOnOpt, // some optimizations know this fn, some opts don't
+ FnKnown
+};
+
// returned bool indicates whether it's a known function call
-std::pair<std::unique_ptr<IR::Instr>, bool>
+std::pair<std::unique_ptr<IR::Instr>, KnownFnKind>
known_call(llvm::CallInst &i, const llvm::TargetLibraryInfo &TLI,
IR::BasicBlock &BB, const std::vector<IR::Value*> &args);

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -275,7 +275,7 @@ public:
args.emplace_back(a);
}
- auto [call_val, known] = known_call(i, TLI, *BB, args);
+ auto [call_val, known_kind] = known_call(i, TLI, *BB, args);
if (call_val)
RETURN_IDENTIFIER(move(call_val));
@@ -309,13 +309,19 @@ public:
}
string fn_name = '@' + fn->getName().str();
+ FnCall::ValidKind valid = FnCall::Invalid;
+ if (known_kind == FnUnknown)
+ valid = FnCall::Valid;
+ else if (known_kind == FnDependsOnOpt)
+ valid = FnCall::DependsOnFlag;
+
auto call =
make_unique<FnCall>(*ty, value_name(i), move(fn_name), move(attrs),
- !known);
+ valid);
unique_ptr<Instr> ret_val;
// avoid parsing arguments altogether for "unknown known" functions
- if (known)
+ if (known_kind == FnKnown)
goto end;
for (uint64_t argidx = 0, nargs = i.arg_size(); argidx < nargs; ++argidx) {
@@ -368,7 +374,7 @@ public:
if (i.paramHasAttr(argidx, llvm::Attribute::Returned)) {
auto call2
= make_unique<FnCall>(Type::voidTy, "", string(call->getFnName()),
- FnAttrs(call->getAttributes()), !known);
+ FnAttrs(call->getAttributes()), valid);
for (auto &[arg, flags] : call->getArgs()) {
call2->addArg(*arg, ParamAttrs(flags));
}

MODIFY
tv/tv.cpp
tv/tv.cpp
@@ -122,7 +122,8 @@ llvm::cl::opt<unsigned> opt_omit_array_size(
llvm::cl::opt<bool> opt_io_nobuiltin(
"tv-io-nobuiltin",
- llvm::cl::desc("Encode standard I/O functions as an unknown function"),
+ llvm::cl::desc("Encode standard I/O functions as an unknown function "
+ "(unused by clang plugin)"),
llvm::cl::init(false));
struct FnInfo {
@@ -149,6 +150,7 @@ bool is_clangtv = false;
struct TVPass final : public llvm::FunctionPass {
static char ID;
bool skip_verify = false;
+ bool encode_io_fns_as_unknown = false;
TVPass() : FunctionPass(ID) {}
@@ -213,6 +215,9 @@ struct TVPass final : public llvm::FunctionPass {
if (first || skip_verify)
return false;
+ if (is_clangtv)
+ I->second.fn.setFnCallValidFlag(encode_io_fns_as_unknown);
+
smt_init->reset();
Transform t;
t.src = move(old_fn);
@@ -221,6 +226,9 @@ struct TVPass final : public llvm::FunctionPass {
TransformVerify verifier(t, false);
t.print(*out, print_opts);
+ if (is_clangtv)
+ I->second.fn.setFnCallValidFlag(false);
+
{
auto types = verifier.getTypings();
if (!types) {
@@ -303,7 +311,17 @@ struct TVPass final : public llvm::FunctionPass {
smt::set_random_seed(to_string(opt_smt_random_seed));
smt::set_memory_limit(opt_max_mem * 1024 * 1024);
config::skip_smt = opt_smt_skip;
+
+ if (!is_clangtv)
config::io_nobuiltin = opt_io_nobuiltin;
+ else {
+ config::io_nobuiltin = true;
+ if (opt_io_nobuiltin)
+ cerr << "Warning: -tv-io-nobuiltin isn't used by clang plugin. I/O"
+ " function calls will be always regarded as unknown fn calls"
+ " except InstCombine.\n";
+ }
+
config::symexec_print_each_value = opt_se_verbose;
config::disable_undef_input = opt_disable_undef_input;
config::disable_poison_input = opt_disable_poison_input;
@@ -456,6 +474,11 @@ llvmGetPassPluginInfo() {
TVPass tv;
tv.skip_verify = skip_pass;
+ // For I/O known calls like printf, it is fine to regard them as valid
+ // 'unknown calls' except when it is InstCombine.
+ tv.encode_io_fns_as_unknown = P != "InstCombinePass" &&
+ P != "AggressiveInstCombinePass";
+
auto M = const_cast<llvm::Module *>(unwrapModule(IR));
for (auto &F: *M)
// If skip_pass is true, this updates fns map only.

clang plugin: encode I/O calls as unknown fncalls except when running InstCombine (#524)