Add support for byval fn call arguments
MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -1112,17 +1112,20 @@ unique_ptr<Instr> ExtractValue::dup(const string &suffix) const {
}
-void FnCall::addArg(Value &arg) {
- args.emplace_back(&arg);
+void FnCall::addArg(Value &arg, unsigned flags) {
+ args.emplace_back(&arg, flags);
}
vector<Value*> FnCall::operands() const {
- return args;
+ vector<Value*> output;
+ transform(args.begin(), args.end(), back_inserter(output),
+ [](auto &p){ return p.first; });
+ return output;
}
void FnCall::rauw(const Value &what, Value &with) {
for (auto &arg : args) {
- RAUW(arg);
+ RAUW(arg.first);
}
}
@@ -1133,9 +1136,12 @@ void FnCall::print(ostream &os) const {
os << "call " << print_type(getType()) << fnName << '(';
bool first = true;
- for (auto arg : args) {
+ for (auto &[arg, flags] : args) {
if (!first)
os << ", ";
+
+ if (flags & ArgByVal)
+ os << "byval ";
os << *arg;
first = false;
}
@@ -1154,18 +1160,19 @@ void FnCall::print(ostream &os) const {
os << "\t; WARNING: unknown known function";
}
-static void unpack_inputs(State&s, Type &ty, const StateValue &value,
- vector<StateValue> &inputs,
- vector<StateValue> &ptr_inputs) {
+static void unpack_inputs(State&s, Type &ty, unsigned argflag,
+ const StateValue &value, vector<StateValue> &inputs,
+ vector<pair<StateValue, bool>> &ptr_inputs) {
if (auto agg = ty.getAsAggregateType()) {
for (unsigned i = 0, e = agg->numElementsConst(); i != e; ++i) {
- unpack_inputs(s, agg->getChild(i), agg->extract(value, i), inputs,
- ptr_inputs);
+ unpack_inputs(s, agg->getChild(i), argflag, agg->extract(value, i),
+ inputs, ptr_inputs);
}
} else if (ty.isPtrType()) {
Pointer p(s.getMemory(), value.value);
p.strip_attrs();
- ptr_inputs.emplace_back(p.release(), expr(value.non_poison));
+ ptr_inputs.emplace_back(StateValue(p.release(), expr(value.non_poison)),
+ argflag & FnCall::ArgByVal);
} else {
inputs.emplace_back(value);
}
@@ -1203,13 +1210,14 @@ StateValue FnCall::toSMT(State &s) const {
return {};
}
- vector<StateValue> inputs, ptr_inputs;
+ vector<StateValue> inputs;
+ vector<pair<StateValue, bool>> ptr_inputs;
vector<Type*> out_types;
ostringstream fnName_mangled;
fnName_mangled << fnName;
- for (auto arg : args) {
- unpack_inputs(s, arg->getType(), s[*arg], inputs, ptr_inputs);
+ for (auto &[arg, flags] : args) {
+ unpack_inputs(s, arg->getType(), flags, s[*arg], inputs, ptr_inputs);
fnName_mangled << "#" << arg->getType().toString();
}
fnName_mangled << '!' << getType();

MODIFY
ir/instr.h
ir/instr.h
@@ -174,9 +174,10 @@ class FnCall final : public Instr {
public:
enum Flags { None = 0, NoRead = 1 << 0, NoWrite = 1 << 1, ArgMemOnly = 1 << 2,
NNaN = 1 << 3 };
+ enum ArgFlags { ArgNone = 0, ArgByVal = 1 << 0 };
private:
std::string fnName;
- std::vector<Value*> args;
+ std::vector<std::pair<Value*, unsigned>> args;
unsigned flags;
bool valid;
public:
@@ -184,7 +185,7 @@ public:
unsigned flags = None, bool valid = true)
: Instr(type, std::move(name)), fnName(std::move(fnName)), flags(flags),
valid(valid) {}
- void addArg(Value &arg);
+ void addArg(Value &arg, unsigned flags);
std::vector<Value*> operands() const override;
void rauw(const Value &what, Value &with) override;

MODIFY
ir/memory.cpp
ir/memory.cpp
@@ -700,7 +700,7 @@ expr Pointer::refined(const Pointer &other) const {
*this == other));
}
-expr Pointer::fninput_refined(const Pointer &other) const {
+expr Pointer::fninput_refined(const Pointer &other, bool is_byval_arg) const {
expr size = block_size();
expr off = get_offset_sizet();
expr size2 = other.block_size();
@@ -719,7 +719,7 @@ expr Pointer::fninput_refined(const Pointer &other) const {
(size2 - off2).uge(size - off),
off2.sgt(size2) && off == off2 &&
size2.uge(size))));
- local = (other.is_local() || other.is_byval()) && local;
+ local = (other.is_local() || other.is_byval() || is_byval_arg) && local;
// TODO: this induces an infinite loop
// block_refined(other);
@@ -1142,16 +1142,18 @@ pair<expr, expr> Memory::mkUndefInput(unsigned attributes) const {
}
pair<expr,expr>
-Memory::mkFnRet(const char *name, const vector<StateValue> &ptr_inputs) const {
+Memory::mkFnRet(const char *name,
+ const vector<pair<StateValue, bool>> &ptr_inputs) const {
expr var
= expr::mkFreshVar(name, expr::mkUInt(0, bits_for_bid + bits_for_offset));
Pointer p(*this, var.concat_zeros(bits_for_ptrattrs));
set<expr> local;
for (auto &in : ptr_inputs) {
- Pointer inp(*this, in.value);
+ // TODO: callee cannot observe the bid if this is byval.
+ Pointer inp(*this, in.first.value);
if (!inp.is_local().isFalse())
- local.emplace(in.non_poison && p.get_bid() == inp.get_bid());
+ local.emplace(in.first.non_poison && p.get_bid() == inp.get_bid());
}
for (unsigned i = 0; i < num_locals; ++i) {
if (escaped_local_blks[i])
@@ -1178,7 +1180,7 @@ expr Memory::CallState::implies(const CallState &st) const {
}
Memory::CallState
-Memory::mkCallState(const vector<StateValue> *ptr_inputs) const {
+Memory::mkCallState(const vector<pair<StateValue, bool>> *ptr_inputs) const {
Memory::CallState st;
st.empty = false;
@@ -1192,7 +1194,9 @@ Memory::mkCallState(const vector<StateValue> *ptr_inputs) const {
if (ptr_inputs) {
modifies = false;
- for (auto &arg : *ptr_inputs) {
+ for (auto &[arg, is_byval_arg] : *ptr_inputs) {
+ // TODO: byval's value cannot be modified.
+ (void)is_byval_arg;
Pointer argp(*this, arg.value);
modifies |= arg.non_poison && argp.get_bid() == p.get_bid();
}
@@ -1218,7 +1222,9 @@ Memory::mkCallState(const vector<StateValue> *ptr_inputs) const {
expr modifies = p.is_heap_allocated();
if (ptr_inputs) {
expr c(false);
- for (auto &arg : *ptr_inputs) {
+ for (auto &[arg, is_byval_arg] : *ptr_inputs) {
+ // TODO: liveness of a pointer given as byval doesn't change
+ (void)is_byval_arg;
c |= arg.non_poison &&
Pointer(*this, arg.value).get_bid() == p.get_bid();
}
@@ -1545,8 +1551,9 @@ expr Memory::int2ptr(const expr &val) {
return {};
}
-pair<expr,Pointer> Memory::refined(const Memory &other,
- const vector<StateValue> *set_ptrs) const {
+pair<expr,Pointer>
+Memory::refined(const Memory &other,
+ const vector<pair<StateValue, bool>> *set_ptrs) const {
if (IR::num_nonlocals <= 1)
return { true, Pointer(*this, expr()) };
@@ -1580,7 +1587,9 @@ pair<expr,Pointer> Memory::refined(const Memory &other,
// restrict refinement check to set of request blocks
if (set_ptrs) {
expr c(false);
- for (auto &ptr : *set_ptrs) {
+ for (auto &itm: *set_ptrs) {
+ // TODO: deal with the byval arg case (itm.second)
+ auto &ptr = itm.first;
c |= ptr.non_poison && Pointer(*this, ptr.value).get_bid() == ptr_bid;
}
ret = c.implies(ret);

MODIFY
ir/memory.h
ir/memory.h
@@ -169,7 +169,7 @@ public:
void strip_attrs();
smt::expr refined(const Pointer &other) const;
- smt::expr fninput_refined(const Pointer &other) const;
+ smt::expr fninput_refined(const Pointer &other, bool is_byval_arg) const;
smt::expr block_val_refined(const Pointer &other) const;
smt::expr block_refined(const Pointer &other) const;
@@ -239,8 +239,11 @@ public:
std::pair<smt::expr, smt::expr> mkUndefInput(unsigned attributes) const;
std::pair<smt::expr, smt::expr>
- mkFnRet(const char *name, const std::vector<StateValue> &ptr_inputs) const;
- CallState mkCallState(const std::vector<StateValue> *ptr_inputs) const;
+ mkFnRet(const char *name,
+ const std::vector<std::pair<StateValue, bool>> &ptr_inputs) const;
+ CallState
+ mkCallState(const std::vector<std::pair<StateValue, bool>> *ptr_inputs)
+ const;
void setState(const CallState &st);
// Allocates a new memory block and returns (pointer expr, allocated).
@@ -281,7 +284,8 @@ public:
std::pair<smt::expr,Pointer>
refined(const Memory &other,
- const std::vector<StateValue> *set_ptrs = nullptr) const;
+ const std::vector<std::pair<StateValue, bool>> *set_ptrs = nullptr)
+ const;
// Returns true if a nocapture pointer byte is not in the memory.
smt::expr check_nocapture() const;

MODIFY
ir/state.cpp
ir/state.cpp
@@ -199,7 +199,7 @@ void State::addUB(const expr &ub) {
const vector<StateValue>
State::addFnCall(const string &name, vector<StateValue> &&inputs,
- vector<StateValue> &&ptr_inputs,
+ vector<pair<StateValue, bool>> &&ptr_inputs,
const vector<Type*> &out_types, bool reads_memory,
bool writes_memory, bool argmemonly) {
// TODO: handle changes to memory due to fn call
@@ -212,8 +212,8 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
all_valid &= v.isValid();
}
for (auto &v : ptr_inputs) {
- all_args_np &= v.non_poison;
- all_valid &= v.isValid();
+ all_args_np &= v.first.non_poison;
+ all_valid &= v.first.isValid();
}
if (!all_valid) {
@@ -392,11 +392,18 @@ void State::mkAxioms(State &tgt) {
for (unsigned i = 0, e = ptr_ins.size(); i != e; ++i) {
// TODO: needs to take read/read2 as input to control if mem blocks
// need to be compared
- expr eq_val = Pointer(mem, ptr_ins[i].value)
- .fninput_refined(Pointer(mem2, ptr_ins2[i].value));
+ auto &[ptr_in, is_byval] = ptr_ins[i];
+ auto &[ptr_in2, is_byval2] = ptr_ins2[i];
+ if (!is_byval && is_byval2) {
+ // byval is added at target; this is not supported yet.
+ refines = false;
+ break;
+ }
+ expr eq_val = Pointer(mem, ptr_in.value)
+ .fninput_refined(Pointer(mem2, ptr_in2.value), is_byval2);
is_val_eq &= eq_val;
- refines &= ptr_ins[i].non_poison
- .implies(eq_val && ptr_ins2[i].non_poison);
+ refines &= ptr_in.non_poison
+ .implies(eq_val && ptr_in2.non_poison);
}
if (reads2) {

MODIFY
ir/state.h
ir/state.h
@@ -88,10 +88,12 @@ private:
std::set<smt::expr> return_undef_vars;
// store data for function calls:
- // inputs: non-ptr arguments, ptr arguments, memory, reads memory?, argmemonly
+ // inputs: non-ptr arguments, (ptr arguments, is by_val arg?), memory,
+ // reads memory?, argmemonly
// outputs: values, UB, memory state
std::map<std::string,
- std::map<std::tuple<std::vector<StateValue>, std::vector<StateValue>,
+ std::map<std::tuple<std::vector<StateValue>,
+ std::vector<std::pair<StateValue, bool>>,
Memory, bool, bool>,
std::tuple<std::vector<StateValue>, smt::expr,
Memory::CallState>>>
@@ -125,7 +127,7 @@ public:
const std::vector<StateValue>
addFnCall(const std::string &name, std::vector<StateValue> &&inputs,
- std::vector<StateValue> &&ptr_inputs,
+ std::vector<std::pair<StateValue, bool>> &&ptr_inputs,
const std::vector<Type*> &out_types, bool reads_memory,
bool writes_memory, bool argmemonly);

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -260,11 +260,20 @@ public:
string fn_name = '@' + fn->getName().str();
auto call = make_unique<FnCall>(*ty, value_name(i), move(fn_name), flags,
!known);
+ auto argI = fn->arg_begin(), argE = fn->arg_end();
for (auto &arg : i.args()) {
auto a = get_operand(arg);
if (!a)
return error(i);
- call->addArg(*a);
+
+ unsigned attr = FnCall::ArgNone;
+ if (argI != argE) {
+ // Check whether arg itr finished early because it was var arg
+ if (argI->hasByValAttr())
+ attr |= FnCall::ArgByVal;
+ argI++;
+ }
+ call->addArg(*a, attr);
}
RETURN_IDENTIFIER(move(call));
}

MODIFY
tests/alive-tv/call.src.ll
tests/alive-tv/call.src.ll
@@ -41,6 +41,20 @@ define i8 @f6(i8* byval %p) {
ret i8 %b
}
+define i8 @f6_2(i8* %p) {
+ %a = alloca i8
+ call void @llvm.memcpy.p0i8.p0i8.i64(i8* %a, i8* %p, i64 1, i1 false)
+ %b = call i8 @g2(i8* byval %a)
+ ret i8 %b
+}
+
+define i8 @f6_3() {
+ %a = alloca i8
+ call void @llvm.memcpy.p0i8.p0i8.i64(i8* %a, i8* @glb, i64 1, i1 false)
+ %b = call i8 @g2(i8* byval %a)
+ ret i8 %b
+}
+
define void @f7() {
%ptr = call i8* @k()
%a = load i8, i8* %ptr, align 4
@@ -55,6 +69,7 @@ define void @f8() {
}
declare i8 @g(i8*)
+declare i8 @g2(i8* byval)
declare i8 @h(i8*) readnone
declare i8* @k()

MODIFY
tests/alive-tv/call.tgt.ll
tests/alive-tv/call.tgt.ll
@@ -39,6 +39,16 @@ define i8 @f6(i8* byval %p) {
ret i8 %b
}
+define i8 @f6_2(i8* %p) {
+ %b = call i8 @g2(i8* byval %p)
+ ret i8 %b
+}
+
+define i8 @f6_3() {
+ %b = call i8 @g2(i8* byval @glb)
+ ret i8 %b
+}
+
define void @f7() {
%ptr = call i8* @k()
%a = load i8, i8* %ptr, align 4
@@ -52,6 +62,7 @@ define void @f8() {
}
declare i8 @g(i8*)
+declare i8 @g2(i8* byval)
declare i8 @h(i8*) readnone
declare i8* @k()

MODIFY
tools/alive_parser.cpp
tools/alive_parser.cpp
@@ -922,7 +922,7 @@ static unique_ptr<Instr> parse_call(string_view name) {
tokenizer.ensure(COMMA);
first = false;
auto &ty = parse_type();
- call->addArg(parse_operand(ty));
+ call->addArg(parse_operand(ty), FnCall::ArgNone);
}
tokenizer.ensure(RPAREN);
return call;

Add support for byval fn call arguments (#276)