call sema: dont assume fn calls return
issue
now it wont allow any call removal. We may need to iron that out (issue
MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -1819,20 +1819,15 @@ StateValue FnCall::toSMT(State &s) const {
check_access();
}
- unsigned idx = 0;
- auto ret = s.addFnCall(fnName_mangled.str(), move(inputs), move(ptr_inputs),
- out_types, attrs);
-
// Caller has nofree attribute, so callee must have it as well
if (s.getFn().getFnAttrs().has(FnAttrs::NoFree) &&
!attrs.has(FnAttrs::NoFree))
s.addUB(expr(false));
- if (attrs.has(FnAttrs::NoReturn)) {
- // TODO: Even if a function call doesn't have noreturn, it can possibly
- // exit. Relevant bug: https://bugs.llvm.org/show_bug.cgi?id=27953
- s.addNoReturn();
- }
+ unsigned idx = 0;
+ auto ret = s.addFnCall(fnName_mangled.str(), move(inputs), move(ptr_inputs),
+ out_types, attrs);
+
return isVoid() ? StateValue() : pack_return(s, getType(), ret, attrs, idx);
}

MODIFY
ir/state.cpp
ir/state.cpp
@@ -622,18 +622,20 @@ void State::addUB(AndExpr &&ubs) {
domain.undef_vars.insert(undef_vars.begin(), undef_vars.end());
}
-void State::addNoReturn() {
- return_memory.add(memory, domain.path);
- function_domain.add(domain());
+void State::addNoReturn(const expr &cond) {
+ return_memory.add(memory, domain.path && cond);
+ function_domain.add(domain() && cond);
return_undef_vars.insert(undef_vars.begin(), undef_vars.end());
return_undef_vars.insert(domain.undef_vars.begin(), domain.undef_vars.end());
+ if (cond.isTrue())
undef_vars.clear();
- addUB(expr(false));
+ addUB(!cond);
}
expr State::FnCallInput::operator==(const FnCallInput &rhs) const {
if (readsmem != rhs.readsmem ||
argmemonly != rhs.argmemonly ||
+ noret != rhs.noret ||
(readsmem && (fncall_ranges != rhs.fncall_ranges || is_neq(m <=> rhs.m))))
return false;
@@ -652,9 +654,9 @@ expr State::FnCallInput::refinedBy(
State &s, const vector<StateValue> &args_nonptr2,
const vector<Memory::PtrInput> &args_ptr2,
const ValueAnalysis::FnCallRanges &fncall_ranges2,
- const Memory &m2, bool readsmem2, bool argmemonly2) const {
+ const Memory &m2, bool readsmem2, bool argmemonly2, bool noret2) const {
- if (readsmem != readsmem2 || argmemonly != argmemonly2 ||
+ if (readsmem != readsmem2 || argmemonly != argmemonly2 || noret != noret2 ||
(readsmem && !fncall_ranges.overlaps(fncall_ranges2)))
return false;
@@ -708,6 +710,7 @@ State::FnCallOutput State::FnCallOutput::mkIf(const expr &cond,
const FnCallOutput &b) {
FnCallOutput ret;
ret.ub = expr::mkIf(cond, a.ub, b.ub);
+ ret.noreturns = expr::mkIf(cond, a.noreturns, b.noreturns);
ret.callstate = Memory::CallState::mkIf(cond, a.callstate, b.callstate);
assert(a.retvals.size() == b.retvals.size());
for (unsigned i = 0, e = a.retvals.size(); i != e; ++i) {
@@ -719,6 +722,7 @@ State::FnCallOutput State::FnCallOutput::mkIf(const expr &cond,
expr State::FnCallOutput::operator==(const FnCallOutput &rhs) const {
expr ret = ub == rhs.ub;
+ ret &= noreturns == rhs.noreturns;
for (unsigned i = 0, e = retvals.size(); i != e; ++i) {
ret &= retvals[i] == rhs.retvals[i];
}
@@ -735,6 +739,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
bool reads_memory = !attrs.has(FnAttrs::NoRead);
bool writes_memory = !attrs.has(FnAttrs::NoWrite);
bool argmemonly = attrs.has(FnAttrs::ArgMemOnly);
+ bool noret = attrs.has(FnAttrs::NoReturn);
bool noundef = attrs.has(FnAttrs::NoUndef);
bool all_valid = std::all_of(inputs.begin(), inputs.end(),
@@ -765,7 +770,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
reads_memory ? analysis.ranges_fn_calls
: State::ValueAnalysis::FnCallRanges(),
reads_memory ? memory : Memory(*this),
- reads_memory, argmemonly });
+ reads_memory, argmemonly, noret });
auto &I = call_data_pair.first;
bool inserted = call_data_pair.second;
@@ -786,8 +791,10 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
}
string ub_name = name + "#ub";
+ string noret_name = name + "#noreturn";
I->second
= { move(values), expr::mkFreshVar(ub_name.c_str(), false),
+ noret ? expr(true) : expr::mkFreshVar(noret_name.c_str(), false),
writes_memory
? memory.mkCallState(name,
argmemonly ? &I->first.args_ptr : nullptr,
@@ -805,6 +812,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
}
addUB(I->second.ub);
+ addNoReturn(I->second.noreturns);
retval = I->second.retvals;
if (writes_memory)
memory.setState(I->second.callstate);
@@ -816,7 +824,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
for (auto &[in, out] : fn_call_data[name]) {
auto refined = in.refinedBy(*this, inputs, ptr_inputs,
analysis.ranges_fn_calls, memory,
- reads_memory, argmemonly);
+ reads_memory, argmemonly, noret);
data.add(out, move(refined));
}
@@ -824,6 +832,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
auto [d, domain, qvar, pre] = data();
addUB(move(domain));
addUB(move(d.ub));
+ addNoReturn(move(d.noreturns));
retval = move(d.retvals);
if (writes_memory)
memory.setState(d.callstate);

MODIFY
ir/state.h
ir/state.h
@@ -135,13 +135,14 @@ private:
std::vector<Memory::PtrInput> args_ptr;
ValueAnalysis::FnCallRanges fncall_ranges;
Memory m;
- bool readsmem, argmemonly;
+ bool readsmem, argmemonly, noret;
smt::expr operator==(const FnCallInput &rhs) const;
smt::expr refinedBy(State &s, const std::vector<StateValue> &args_nonptr,
const std::vector<Memory::PtrInput> &args_ptr,
const ValueAnalysis::FnCallRanges &fncall_ranges,
- const Memory &m, bool readsmem, bool argmemonly) const;
+ const Memory &m, bool readsmem, bool argmemonly,
+ bool noret) const;
auto operator<=>(const FnCallInput &rhs) const = default;
};
@@ -149,6 +150,7 @@ private:
struct FnCallOutput {
std::vector<StateValue> retvals;
smt::expr ub;
+ smt::expr noreturns;
Memory::CallState callstate;
static FnCallOutput mkIf(const smt::expr &cond, const FnCallOutput &then,
@@ -195,7 +197,7 @@ public:
void addUB(smt::expr &&ub);
void addUB(const smt::expr &ub);
void addUB(smt::AndExpr &&ubs);
- void addNoReturn();
+ void addNoReturn(const smt::expr &cond);
std::vector<StateValue>
addFnCall(const std::string &name, std::vector<StateValue> &&inputs,

MODIFY
tests/alive-tv/attrs/noreturn-fail-2.srctgt.ll
tests/alive-tv/attrs/noreturn-fail-2.srctgt.ll
@@ -10,4 +10,4 @@ define i8 @tgt() {
ret i8 0
}
-; ERROR: Source and target don't have the same return domain
+; ERROR: Source is more defined than target

MODIFY
tests/alive-tv/attrs/noreturn-fail-4.srctgt.ll
tests/alive-tv/attrs/noreturn-fail-4.srctgt.ll
@@ -10,4 +10,4 @@ define i8 @tgt() {
ret i8 0
}
-; ERROR: Source and target don't have the same return domain
+; ERROR: Source is more defined than target

MODIFY
tests/alive-tv/bugs/pr33673.srctgt.ll
tests/alive-tv/bugs/pr33673.srctgt.ll
; https://bugs.llvm.org/show_bug.cgi?id=33673
-; To find counter-example from this, function should be able to throw or exit
define void @src() {
%p = alloca i32, align 4
%a = load i32, i32* %p, align 4
@@ -16,3 +15,5 @@ define void @tgt() {
}
declare void @foo(i32)
+
+; ERROR: Source is more defined than target

MODIFY
tests/alive-tv/memory/nonlocalbid.srctgt.ll
tests/alive-tv/memory/nonlocalbid.srctgt.ll
@@ -24,4 +24,4 @@ define i8 @tgt() {
ret i8 1
}
-; ERROR: Value mismatch
+; ERROR: Source and target don't have the same return domain

MODIFY
tests/unit/call-error4.opt
tests/unit/call-error4.opt
; TEST-ARGS: -root-only
-; ERROR: Value mismatch
+; ERROR: Source and target don't have the same return domain
%c = call i32 @f(i32 3)
%d = call i32 @f(i32 3)

MODIFY
tests/unit/call-error5.opt
tests/unit/call-error5.opt
@@ -5,4 +5,4 @@ ret i8 %d
=>
ret i8 1
-; ERROR: Value mismatch
+; ERROR: Source and target don't have the same return domain

MODIFY
tests/unit/call-error8.opt
tests/unit/call-error8.opt
@@ -7,4 +7,4 @@ ret true
=>
ret false
-; FIXME: this test should fail
+; ERROR: Source and target don't have the same return domain

call sema: dont assume fn calls return
issue #375
now it wont allow any call removal. We may need to iron that out (issue #477)