Implement inaccessiblememonly function attribute
Fixes
MODIFY
ir/attrs.cpp
ir/attrs.cpp
@@ -68,6 +68,8 @@ ostream& operator<<(ostream &os, const FnAttrs &attr) {
os << " willreturn";
if (attr.has(FnAttrs::DereferenceableOrNull))
os << " dereferenceable_or_null(" << attr.derefOrNullBytes << ')';
+ if (attr.has(FnAttrs::InaccessibleMemOnly))
+ os << " inaccessiblememonly";
return os;
}

MODIFY
ir/attrs.h
ir/attrs.h
@@ -56,7 +56,8 @@ public:
Dereferenceable = 1 << 5, NonNull = 1 << 6,
NoFree = 1 << 7, NoUndef = 1 << 8, Align = 1 << 9,
NoThrow = 1 << 10, NoAlias = 1 << 11, WillReturn = 1 << 12,
- DereferenceableOrNull = 1 << 13 };
+ DereferenceableOrNull = 1 << 13,
+ InaccessibleMemOnly = 1 << 14 };
FnAttrs(unsigned bits = None) : bits(bits) {}

MODIFY
ir/globals.cpp
ir/globals.cpp
@@ -15,6 +15,7 @@ unsigned num_locals_tgt;
unsigned num_consts_src;
unsigned num_globals_src;
unsigned num_ptrinputs;
+unsigned num_inaccessiblememonly_fns;
unsigned num_nonlocals;
unsigned num_nonlocals_src;
unsigned bits_poison_per_byte;
@@ -33,6 +34,7 @@ bool has_malloc;
bool has_free;
bool has_alloca;
bool has_fncall;
+bool has_write_fncall;
bool has_nocapture;
bool has_noread;
bool has_nowrite;

MODIFY
ir/globals.h
ir/globals.h
@@ -17,6 +17,8 @@ extern unsigned num_globals_src;
extern unsigned num_ptrinputs;
+extern unsigned num_inaccessiblememonly_fns;
+
/// Number of non-constant globals introduced in tgt
extern unsigned num_extra_nonconst_tgt;
@@ -65,6 +67,9 @@ extern bool has_alloca;
extern bool has_fncall;
+// has a function call that writes to global memory (not-inaccessible only)
+extern bool has_write_fncall;
+
/// Whether any function argument (not function call arg) has the attribute
extern bool has_nocapture;
extern bool has_noread;

MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -1860,7 +1860,12 @@ StateValue FnCall::toSMT(State &s) const {
if (check(FnAttrs::ArgMemOnly) ||
check(FnAttrs::NoFree) ||
check(FnAttrs::NoThrow) ||
- check(FnAttrs::WillReturn))
+ check(FnAttrs::WillReturn) ||
+ check(FnAttrs::InaccessibleMemOnly))
+ s.addUB(expr(false));
+
+ // can't have both!
+ if (attrs.has(FnAttrs::ArgMemOnly) && attrs.has(FnAttrs::InaccessibleMemOnly))
s.addUB(expr(false));
unsigned idx = 0;

MODIFY
ir/memory.cpp
ir/memory.cpp
@@ -17,17 +17,19 @@ using namespace std;
using namespace util;
// Non-local block ids (assuming that no block is optimized out):
- // 1. null block: 0
+ // 1. null block: has_null_block
// 2. global vars in source:
- // 1 ~ num_consts_src (constant globals)
- // num_consts_src + 1 ~ num_globals_src (non-constant globals)
+ // + num_consts_src (constant globals)
+ // + num_globals_src (all globals incl constant)
// 3. pointer argument inputs:
- // num_globals_src + 1 ~ num_globals_src + num_ptrinputs
+ // has_null_block + num_globals_src + num_ptrinputs
// 4. nonlocal blocks returned by loads/calls:
- // num_globals_src + num_ptrinputs + 1 ~ num_nonlocals_src - 2:
+ // has_null_block + num_globals_src + num_ptrinputs + 1 -> ...
// 5. a block reserved for encoding the memory touched by calls:
- // num_nonlocals_src - 1
- // 6. constant global vars in target only:
+ // num_nonlocals_src - num_inaccessiblememonly_fns - has_write_fncall
+ // 6. 1 block per inaccessiblememonly
+ // + num_inaccessiblememonly_fns ~ num_nonlocals_src - 1
+ // 7. constant global vars in target only:
// num_nonlocals_src ~ num_nonlocals - 1
// (constant globals in target only)
@@ -58,12 +60,12 @@ static bool is_constglb(unsigned bid) {
// Return true if bid is the nonlocal block used to encode function calls' side
// effects
static unsigned get_fncallmem_bid() {
- assert(has_fncall);
- return num_nonlocals_src - 1;
+ assert(has_write_fncall);
+ return num_nonlocals_src - num_inaccessiblememonly_fns - 1;
}
static bool is_fncall_mem(unsigned bid) {
- if (!has_fncall)
+ if (!has_write_fncall)
return false;
return bid == get_fncallmem_bid();
}
@@ -1045,9 +1047,14 @@ void Memory::mkNonlocalValAxioms(bool skip_consts) {
// initial memory cannot contain a pointer to fncall mem block.
if (is_fncall_mem(upperbid)) {
upperbid--;
- } else {
+ } else if (!num_inaccessiblememonly_fns) {
assert(!state->isSource()); // target-only glb vars exist
bid_cond = bid != get_fncallmem_bid();
+ } else {
+ bid_cond = bid.ult(num_nonlocals_src - num_inaccessiblememonly_fns
+ - has_write_fncall);
+ if (upperbid > num_nonlocals_src)
+ bid_cond |= bid.ugt(num_nonlocals_src - 1);
}
}
bid_cond &= bid.ule(upperbid);
@@ -1347,7 +1354,7 @@ expr Memory::CallState::operator==(const CallState &rhs) const {
Memory::CallState
Memory::mkCallState(const string &fnname, const vector<PtrInput> *ptr_inputs,
- bool nofree) {
+ bool nofree, bool inaccessiblememonly) {
assert(has_fncall);
CallState st;
st.empty = false;
@@ -1356,12 +1363,21 @@ Memory::mkCallState(const string &fnname, const vector<PtrInput> *ptr_inputs,
auto blk_type = mk_block_val_array(1);
unsigned num_consts = has_null_block + num_consts_src;
- for (unsigned i = num_consts; i < num_nonlocals_src; ++i) {
+ unsigned limit = num_nonlocals_src - num_inaccessiblememonly_fns;
+
+ if (inaccessiblememonly) {
st.non_local_block_val.emplace_back(expr::mkFreshVar("blk_val", blk_type));
+ } else {
+ for (unsigned i = num_consts; i < limit; ++i) {
+ st.non_local_block_val.emplace_back(
+ expr::mkFreshVar("blk_val", blk_type));
+ }
}
+ // TODO: this code should be moved to setState as old_val should be the one
+ // from the corresponding state, not always from source
if (ptr_inputs) {
- for (unsigned bid = num_consts; bid < num_nonlocals_src; ++bid) {
+ for (unsigned bid = num_consts; bid < limit - has_write_fncall; ++bid) {
expr modifies(false);
for (auto &ptr_in : *ptr_inputs) {
if (!ptr_in.byval && bid < next_nonlocal_bid) {
@@ -1375,7 +1391,7 @@ Memory::mkCallState(const string &fnname, const vector<PtrInput> *ptr_inputs,
}
}
- if (num_nonlocals_src && !nofree) {
+ if (num_nonlocals_src && !nofree && !inaccessiblememonly) {
expr one = expr::mkUInt(1, num_nonlocals);
expr zero = expr::mkUInt(0, num_nonlocals);
expr mask = has_null_block ? one : zero;
@@ -1407,15 +1423,26 @@ Memory::mkCallState(const string &fnname, const vector<PtrInput> *ptr_inputs,
return st;
}
-void Memory::setState(const Memory::CallState &st) {
+void Memory::setState(const Memory::CallState &st, unsigned modifies_bid) {
assert(has_fncall);
+ if (modifies_bid != -1u) {
+ assert(st.non_local_block_val.size() == 1);
+ unsigned bid
+ = num_nonlocals_src - num_inaccessiblememonly_fns + modifies_bid;
+ assert(bid < num_nonlocals_src);
+ assert(non_local_block_val[bid].undef.empty());
+ non_local_block_val[bid].val = st.non_local_block_val[0];
+ } else {
auto consts = has_null_block + num_consts_src;
- for (unsigned i = consts; i < num_nonlocals_src; ++i) {
+ unsigned limit = num_nonlocals_src - num_inaccessiblememonly_fns;
+ for (unsigned i = consts; i < limit; ++i) {
non_local_block_val[i].val = st.non_local_block_val[i - consts];
if (isInitialMemBlock(non_local_block_val[i].val, true))
non_local_block_val[i].undef.clear();
}
+ // TODO: this is not ok for functions that can be reordered
non_local_block_liveness = st.non_local_liveness;
+ }
mkNonlocalValAxioms(true);
}
@@ -1939,13 +1966,22 @@ Memory::refined(const Memory &other, bool skip_constants,
expr ret(true);
set<expr> undef_vars;
- auto nonlocal_used = [](const Memory &m, unsigned bid) {
- return bid < m.next_nonlocal_bid;
- };
+ AliasSet block_alias(other);
+ for (auto &[mem, set]
+ : { make_pair(this, set_ptrs), make_pair(&other, set_ptrs2)}) {
+ if (set) {
+ for (auto &it: *set_ptrs) {
+ block_alias.unionWith(
+ computeAliasing(Pointer(*mem, it.val.value), 1, 1, false));
+ }
+ } else if (mem->next_nonlocal_bid > 0) {
+ block_alias.setMayAliasUpTo(false, mem->next_nonlocal_bid-1);
+ }
+ }
unsigned bid = has_null_block + skip_constants * num_consts_src;
for (; bid < num_nonlocals_src; ++bid) {
- if (!nonlocal_used(*this, bid) && !nonlocal_used(other, bid))
+ if (!block_alias.mayAlias(false, bid))
continue;
expr bid_expr = expr::mkUInt(bid, bits_for_bid);
@@ -1958,15 +1994,18 @@ Memory::refined(const Memory &other, bool skip_constants,
// restrict refinement check to set of request blocks
if (set_ptrs) {
- expr c(false);
- for (auto &itm: *set_ptrs) {
- // TODO: deal with the byval arg case (itm.second)
- auto &ptr = itm.val;
- c |= ptr.non_poison && Pointer(*this, ptr.value).getBid() == ptr_bid;
+ OrExpr c;
+ for (auto set : {set_ptrs, set_ptrs2}) {
+ for (auto &it: *set) {
+ auto &ptr = it.val;
+ c.add(ptr.non_poison && Pointer(*this, ptr.value).getBid() == ptr_bid);
+ }
}
- ret = c.implies(ret);
+ ret = c().implies(ret);
}
+ // TODO: missing refinement of escaped local blocks!
+
return { move(ret), move(ptr), move(undef_vars) };
}

MODIFY
ir/memory.h
ir/memory.h
@@ -168,7 +168,7 @@ class Memory {
bool mayalias(bool local, unsigned bid, const smt::expr &offset,
unsigned bytes, uint64_t align, bool write) const;
- AliasSet computeAliasing(const Pointer &ptr, unsigned btyes, uint64_t align,
+ AliasSet computeAliasing(const Pointer &ptr, unsigned bytes, uint64_t align,
bool write) const;
template <typename Fn>
@@ -261,8 +261,9 @@ public:
mkFnRet(const char *name, const std::vector<PtrInput> &ptr_inputs,
bool is_local, const FnRetData *data = nullptr);
CallState mkCallState(const std::string &fnname,
- const std::vector<PtrInput> *ptr_inputs, bool nofree);
- void setState(const CallState &st);
+ const std::vector<PtrInput> *ptr_inputs, bool nofree,
+ bool inaccessiblememonly);
+ void setState(const CallState &st, unsigned modifies_bid);
// Allocates a new memory block and returns (pointer expr, allocated).
// If bid is not specified, it creates a fresh block id by increasing

MODIFY
ir/state.cpp
ir/state.cpp
@@ -35,48 +35,62 @@ void State::ValueAnalysis::meet_with(const State::ValueAnalysis &other) {
non_undef_vals = intersect_set(non_undef_vals, other.non_undef_vals);
unused_vars = intersect_set(unused_vars, other.unused_vars);
- for (auto &[fn, calls] : other.ranges_fn_calls) {
- auto [I, inserted] = ranges_fn_calls.try_emplace(fn, calls);
+ for (auto &[fn, pair] : other.ranges_fn_calls) {
+ auto &[calls, inaccessiblememonly] = pair;
+ auto [I, inserted] = ranges_fn_calls.try_emplace(fn, pair);
if (inserted) {
- I->second.emplace(0);
+ I->second.first.emplace(0);
} else {
- I->second.insert(calls.begin(), calls.end());
+ assert(I->second.second == inaccessiblememonly);
+ I->second.first.insert(calls.begin(), calls.end());
}
}
- for (auto &[fn, calls] : ranges_fn_calls) {
+ for (auto &[fn, pair] : ranges_fn_calls) {
+ auto &[calls, inaccessiblememonly] = pair;
if (!other.ranges_fn_calls.count(fn))
calls.emplace(0);
}
}
-void State::ValueAnalysis::FnCallRanges::inc(const std::string &name) {
+void State::ValueAnalysis::FnCallRanges::inc(const std::string &name,
+ bool inaccessiblememonly) {
auto [I, inserted] = try_emplace(name);
if (inserted) {
- I->second.emplace(1);
+ I->second.first.emplace(1);
+ I->second.second = inaccessiblememonly;
} else {
+ assert(I->second.second == inaccessiblememonly);
set<unsigned> new_set;
- for (unsigned n : I->second) {
+ for (unsigned n : I->second.first) {
new_set.emplace(n+1);
}
- I->second = move(new_set);
+ I->second.first = move(new_set);
}
}
bool
State::ValueAnalysis::FnCallRanges::overlaps(const FnCallRanges &other) const {
- for (auto &[fn, calls] : *this) {
+ for (auto &[fn, pair] : *this) {
+ auto &[calls, inaccessiblememonly] = pair;
+ if (inaccessiblememonly)
+ continue;
+
auto I = other.find(fn);
if (I == other.end()) {
if (calls.count(0))
continue;
return false;
}
- if (intersect_set(calls, I->second).empty())
+ if (intersect_set(calls, I->second.first).empty())
return false;
}
- for (auto &[fn, calls] : other) {
+ for (auto &[fn, pair] : other) {
+ auto &[calls, inaccessiblememonly] = pair;
+ if (inaccessiblememonly)
+ continue;
+
if (!calls.count(0) && !count(fn))
return false;
}
@@ -84,6 +98,16 @@ State::ValueAnalysis::FnCallRanges::overlaps(const FnCallRanges &other) const {
return true;
}
+State::ValueAnalysis::FnCallRanges
+State::ValueAnalysis::FnCallRanges::project(const string &name) const {
+ auto I = find(name);
+ if (I == end())
+ return {};
+ FnCallRanges ranges;
+ ranges.emplace(name, make_pair(I->second.first, false));
+ return ranges;
+}
+
State::VarArgsData
State::VarArgsData::mkIf(const expr &cond, const VarArgsData &then,
const VarArgsData &els) {
@@ -646,6 +670,7 @@ void State::addNoReturn(const expr &cond) {
expr State::FnCallInput::operator==(const FnCallInput &rhs) const {
if (readsmem != rhs.readsmem ||
argmemonly != rhs.argmemonly ||
+ inaccessiblememonly != rhs.inaccessiblememonly ||
noret != rhs.noret || willret != rhs.willret ||
(readsmem && (fncall_ranges != rhs.fncall_ranges || is_neq(m <=> rhs.m))))
return false;
@@ -662,13 +687,14 @@ expr State::FnCallInput::operator==(const FnCallInput &rhs) const {
}
expr State::FnCallInput::refinedBy(
- State &s, const vector<StateValue> &args_nonptr2,
+ State &s, unsigned modifies_bid, const vector<StateValue> &args_nonptr2,
const vector<Memory::PtrInput> &args_ptr2,
const ValueAnalysis::FnCallRanges &fncall_ranges2,
- const Memory &m2, bool readsmem2, bool argmemonly2, bool noret2,
- bool willret2) const {
+ const Memory &m2, bool readsmem2, bool argmemonly2, bool inaccessiblememonly2,
+ bool noret2, bool willret2) const {
if (readsmem != readsmem2 || argmemonly != argmemonly2 ||
+ inaccessiblememonly != inaccessiblememonly2 ||
noret != noret2 || willret != willret2 ||
(readsmem && !fncall_ranges.overlaps(fncall_ranges2)))
return false;
@@ -685,6 +711,7 @@ expr State::FnCallInput::refinedBy(
return false;
set<expr> undef_vars;
+ if (!inaccessiblememonly) {
assert(args_ptr.size() == args_ptr2.size());
for (unsigned i = 0, e = args_ptr.size(); i != e; ++i) {
// TODO: needs to take read/read2 as input to control if mem blocks
@@ -702,13 +729,23 @@ expr State::FnCallInput::refinedBy(
if (!refines)
return false;
}
+ }
for (auto &v : undef_vars)
s.addFnQuantVar(v);
if (readsmem) {
+ vector<Memory::PtrInput> dummy1, dummy2;
auto restrict_ptrs = argmemonly ? &args_ptr : nullptr;
auto restrict_ptrs2 = argmemonly ? &args_ptr2 : nullptr;
+ if (modifies_bid != -1u) {
+ dummy1.emplace_back(
+ StateValue(Pointer(m, modifies_bid, false).release(), true), 0, false);
+ dummy2.emplace_back(
+ StateValue(Pointer(m2, modifies_bid, false).release(), true), 0, false);
+ restrict_ptrs = &dummy1;
+ restrict_ptrs2 = &dummy2;
+ }
auto data = m.refined(m2, true, restrict_ptrs, restrict_ptrs2);
refines.add(get<0>(data));
for (auto &v : get<2>(data))
@@ -752,6 +789,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 inaccessiblememonly = attrs.has(FnAttrs::InaccessibleMemOnly);
bool noret = attrs.has(FnAttrs::NoReturn);
bool willret = attrs.has(FnAttrs::WillReturn);
bool noundef = attrs.has(FnAttrs::NoUndef);
@@ -777,17 +815,26 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
}
vector<StateValue> retval;
+ unsigned modifies_bid = -1u;
+ if (inaccessiblememonly)
+ modifies_bid
+ = inaccessiblemem_bids.try_emplace(name, inaccessiblemem_bids.size())
+ .first->second;
+
+ State::ValueAnalysis::FnCallRanges call_ranges;
+ if (reads_memory)
+ call_ranges = inaccessiblememonly
+ ? analysis.ranges_fn_calls.project(name)
+ : analysis.ranges_fn_calls;
// source may create new fn symbols, target just references src symbols
if (isSource()) {
auto &calls_fn = fn_call_data[name];
auto call_data_pair
= calls_fn.try_emplace(
- { move(inputs), move(ptr_inputs),
- reads_memory ? analysis.ranges_fn_calls
- : State::ValueAnalysis::FnCallRanges(),
+ { move(inputs), move(ptr_inputs), move(call_ranges),
reads_memory ? memory : Memory(*this),
- reads_memory, argmemonly, noret, willret });
+ reads_memory, argmemonly, inaccessiblememonly, noret, willret });
auto &I = call_data_pair.first;
bool inserted = call_data_pair.second;
@@ -821,7 +868,8 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
writes_memory
? memory.mkCallState(name,
argmemonly ? &I->first.args_ptr : nullptr,
- attrs.has(FnAttrs::NoFree))
+ attrs.has(FnAttrs::NoFree),
+ inaccessiblememonly)
: Memory::CallState(), move(ret_data) };
// add equality constraints between source's function calls
@@ -838,16 +886,16 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
addNoReturn(I->second.noreturns);
retval = I->second.retvals;
if (writes_memory)
- memory.setState(I->second.callstate);
+ memory.setState(I->second.callstate, modifies_bid);
}
else {
// target: this fn call must match one from the source, otherwise it's UB
ChoiceExpr<FnCallOutput> data;
for (auto &[in, out] : fn_call_data[name]) {
- auto refined = in.refinedBy(*this, inputs, ptr_inputs,
- analysis.ranges_fn_calls, memory,
- reads_memory, argmemonly, noret, willret);
+ auto refined = in.refinedBy(*this, modifies_bid, inputs, ptr_inputs,
+ call_ranges, memory, reads_memory, argmemonly,
+ inaccessiblememonly, noret, willret);
data.add(out, move(refined));
}
@@ -874,7 +922,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
retval = move(d.retvals);
if (writes_memory)
- memory.setState(d.callstate);
+ memory.setState(d.callstate, modifies_bid);
fn_call_pre &= pre;
if (qvar.isValid())
@@ -888,7 +936,7 @@ State::addFnCall(const string &name, vector<StateValue> &&inputs,
}
if (writes_memory)
- analysis.ranges_fn_calls.inc(name);
+ analysis.ranges_fn_calls.inc(name, inaccessiblememonly);
return retval;
}
@@ -988,6 +1036,7 @@ void State::syncSEdataWithSrc(const State &src) {
itm.second.second = false;
fn_call_data = src.fn_call_data;
+ inaccessiblemem_bids = src.inaccessiblemem_bids;
memory.syncWithSrc(src.returnMemory());
}

MODIFY
ir/state.h
ir/state.h
@@ -49,11 +49,15 @@ private:
// vars that have never been used
std::set<const Value *> unused_vars;
- // Possible number of calls per functio name that occurred so far
+ // Possible number of calls per function name that occurred so far
// This is an over-approximation, union over all predecessors
- struct FnCallRanges : public std::map<std::string, std::set<unsigned>> {
- void inc(const std::string &name);
+ struct FnCallRanges
+ // bool records whether fn only accesses inaccessible memory only
+ : public std::map<std::string, std::pair<std::set<unsigned>, bool>> {
+ void inc(const std::string &name, bool inaccessiblememonly);
bool overlaps(const FnCallRanges &other) const;
+ // remove all ranges but name
+ FnCallRanges project(const std::string &name) const;
};
FnCallRanges ranges_fn_calls;
@@ -141,14 +145,16 @@ private:
std::vector<Memory::PtrInput> args_ptr;
ValueAnalysis::FnCallRanges fncall_ranges;
Memory m;
- bool readsmem, argmemonly, noret, willret;
+ bool readsmem, argmemonly, inaccessiblememonly, noret, willret;
smt::expr operator==(const FnCallInput &rhs) const;
- smt::expr refinedBy(State &s, const std::vector<StateValue> &args_nonptr,
+ smt::expr refinedBy(State &s, unsigned modifies_bid,
+ 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,
- bool noret, bool willret) const;
+ bool inaccessiblememonly, bool noret,
+ bool willret) const;
auto operator<=>(const FnCallInput &rhs) const = default;
};
@@ -168,6 +174,7 @@ private:
std::map<std::string, std::map<FnCallInput, FnCallOutput>> fn_call_data;
smt::expr fn_call_pre = true;
std::set<smt::expr> fn_call_qvars;
+ std::unordered_map<std::string, unsigned> inaccessiblemem_bids;
VarArgsData var_args_data;

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -925,13 +925,17 @@ public:
return error(i);
}
}
- case llvm::Intrinsic::trap:
- {
+ case llvm::Intrinsic::sideeffect: {
+ FnAttrs attrs;
+ attrs.set(FnAttrs::InaccessibleMemOnly);
+ attrs.set(FnAttrs::NoThrow);
+ return make_unique<FnCall>(Type::voidTy, "", "#sideeffect", move(attrs));
+ }
+ case llvm::Intrinsic::trap: {
FnAttrs attrs;
attrs.set(FnAttrs::NoReturn);
- attrs.set(FnAttrs::NoWrite);
- return make_unique<FnCall>(*llvm_type2alive(i.getType()),
- "", "#trap", move(attrs));
+ attrs.set(FnAttrs::NoThrow);
+ return make_unique<FnCall>(Type::voidTy, "", "#trap", move(attrs));
}
case llvm::Intrinsic::vastart: {
PARSE_UNOP();
@@ -1225,6 +1229,10 @@ public:
attrs.set(FnAttrs::ArgMemOnly);
break;
+ case llvm::Attribute::InaccessibleMemOnly:
+ attrs.set(FnAttrs::InaccessibleMemOnly);
+ break;
+
case llvm::Attribute::NoFree:
attrs.set(FnAttrs::NoFree);
break;

ADD
tests/alive-tv/calls/llvm.sideeffect.srctgt.ll
+; ERROR: Source and target don't have the same return domain
+
+define i4 @src(i4* %p) {
+ call void @llvm.sideeffect()
+ ret i4 6
+}
+
+define i4 @tgt(i4* %p) {
+ ret i4 6
+}
+
+declare void @llvm.sideeffect()

ADD
tests/alive-tv/memory/inaccessiblememonly.srctgt.ll
+define i4 @src() inaccessiblememonly {
+ call void @fn()
+ ret i4 0
+}
+
+define i4 @tgt() inaccessiblememonly {
+ unreachable
+}
+
+declare void @fn()

ADD
tests/alive-tv/memory/inaccessiblememonly2.srctgt.ll
+define i4 @src(i4* %p) {
+ %v = load i4, i4* %p
+ call void @fn()
+ ret i4 %v
+}
+
+define i4 @tgt(i4* %p) {
+ call void @fn()
+ %v = load i4, i4* %p
+ ret i4 %v
+}
+
+declare void @fn() inaccessiblememonly

ADD
tests/alive-tv/memory/inaccessiblememonly3.srctgt.ll
+define i4 @src(i4* %p) {
+ call void @f()
+ call void @g()
+ call void @g()
+ %v = load i4, i4* %p
+ ret i4 %v
+}
+
+define i4 @tgt(i4* %p) {
+ call void @g()
+ call void @g()
+ call void @f()
+ %v = load i4, i4* %p
+ ret i4 %v
+}
+
+declare void @f() inaccessiblememonly willreturn
+declare void @g() willreturn

ADD
tests/alive-tv/memory/inaccessiblememonly4.srctgt.ll
+; ERROR: Source and target don't have the same return domain
+
+define i4 @src(i4* %p) {
+ %v = load i4, i4* %p
+ call void @fn()
+ ret i4 %v
+}
+
+define i4 @tgt(i4* %p) {
+ %v = load i4, i4* %p
+ ret i4 %v
+}
+
+declare void @fn() inaccessiblememonly

MODIFY
tools/transform.cpp
tools/transform.cpp
@@ -814,6 +814,9 @@ static void calculateAndInitConstants(Transform &t) {
observes_addresses = false;
bool does_any_byte_access = false;
+ set<string> inaccessiblememonly_fns;
+ num_inaccessiblememonly_fns = 0;
+
// Mininum access size (in bytes)
uint64_t min_access_size = 8;
unsigned min_vect_elem_sz = 0;
@@ -873,8 +876,15 @@ static void calculateAndInitConstants(Transform &t) {
update_min_vect_sz(i.getType());
- if (dynamic_cast<const FnCall*>(&i))
+ if (auto fn = dynamic_cast<const FnCall*>(&i)) {
has_fncall |= true;
+ if (fn->hasAttribute(FnAttrs::InaccessibleMemOnly)) {
+ if (inaccessiblememonly_fns.emplace(fn->getName()).second)
+ ++num_inaccessiblememonly_fns;
+ } else {
+ has_write_fncall |= !fn->hasAttribute(FnAttrs::NoWrite);
+ }
+ }
if (auto *mi = dynamic_cast<const MemInstr *>(&i)) {
auto [alloc, align] = mi->getMaxAllocSize();
@@ -950,10 +960,10 @@ static void calculateAndInitConstants(Transform &t) {
has_ptr_load || has_fncall || has_int2ptr;
num_nonlocals_src = num_globals_src + num_ptrinputs + num_nonlocals_inst_src +
- has_null_block;
+ num_inaccessiblememonly_fns + has_null_block;
// Allow at least one non-const global for calls to change
- num_nonlocals_src += has_fncall;
+ num_nonlocals_src += has_write_fncall;
num_nonlocals = num_nonlocals_src + num_globals - num_globals_src;
@@ -1025,6 +1035,8 @@ static void calculateAndInitConstants(Transform &t) {
<< "\nnum_locals_tgt: " << num_locals_tgt
<< "\nnum_nonlocals_src: " << num_nonlocals_src
<< "\nnum_nonlocals: " << num_nonlocals
+ << "\nnum_inaccessiblememonly_fns: "
+ << num_inaccessiblememonly_fns
<< "\nbits_for_bid: " << bits_for_bid
<< "\nbits_for_offset: " << bits_for_offset
<< "\nbits_size_t: " << bits_size_t

Implement inaccessiblememonly function attribute
Fixes #746