add experimental support for varargs
MODIFY
ir/function.cpp
ir/function.cpp
@@ -765,6 +765,8 @@ void Function::print(ostream &os, bool print_header) const {
os << input;
first = false;
}
+ if (isVarArgs())
+ os << (first ? "..." : ", ...");
os << ") {\n";
}

MODIFY
ir/function.h
ir/function.h
@@ -66,6 +66,7 @@ class Function final {
unsigned bits_pointers = 64;
unsigned bits_ptr_offset = 64;
bool little_endian = true;
+ bool is_var_args = false;
// constants used in this function
std::vector<std::unique_ptr<Value>> constants;
@@ -79,12 +80,15 @@ class Function final {
FnAttrs attrs;
+
public:
Function() {}
Function(Type &type, std::string &&name, unsigned bits_pointers = 64,
- unsigned bits_ptr_offset = 64, bool little_endian = true)
+ unsigned bits_ptr_offset = 64, bool little_endian = true,
+ bool is_var_args = false)
: type(&type), name(std::move(name)), bits_pointers(bits_pointers),
- bits_ptr_offset(bits_ptr_offset), little_endian(little_endian) {}
+ bits_ptr_offset(bits_ptr_offset), little_endian(little_endian),
+ is_var_args(is_var_args) {}
const IR::Type& getType() const { return type ? *type : Type::voidTy; }
void setType(IR::Type &t) { type = &t; }
@@ -135,6 +139,7 @@ public:
unsigned bitsPointers() const { return bits_pointers; }
unsigned bitsPtrOffset() const { return bits_ptr_offset; }
bool isLittleEndian() const { return little_endian; }
+ bool isVarArgs() const { return is_var_args; }
void syncDataWithSrc(const Function &src);

MODIFY
ir/instr.cpp
ir/instr.cpp
@@ -27,6 +27,9 @@ using namespace std;
MemInstr::ByteAccessInfo cls::getByteAccessInfo() const \
{ return {}; }
+// log2 of max number of var args per function
+#define VARARG_BITS 8
+
namespace {
struct print_type {
IR::Type &ty;
@@ -3291,6 +3294,232 @@ unique_ptr<Instr> Strlen::dup(const string &suffix) const {
}
+vector<Value*> VaStart::operands() const {
+ return { ptr };
+}
+
+void VaStart::rauw(const Value &what, Value &with) {
+ RAUW(ptr);
+}
+
+void VaStart::print(ostream &os) const {
+ os << "call void @llvm.va_start(" << *ptr << ')';
+}
+
+StateValue VaStart::toSMT(State &s) const {
+ s.addUB(expr(s.getFn().isVarArgs()));
+
+ auto &data = s.getVarArgsData();
+ auto &raw_p = s.getAndAddPoisonUB(*ptr, true).value;
+
+ expr zero = expr::mkUInt(0, VARARG_BITS);
+ expr num_args = expr::mkVar("num_va_args", VARARG_BITS);
+
+ // just in case there's already a pointer there
+ OrExpr matched_one;
+ for (auto &[ptr, entry] : data) {
+ // FIXME. if entry.alive => memory leak (though not UB). detect this
+ expr eq = ptr == raw_p;
+ entry.alive |= eq;
+ entry.next_arg = expr::mkIf(eq, zero, entry.next_arg);
+ entry.num_args = expr::mkIf(eq, num_args, entry.num_args);
+ entry.va_start = expr::mkIf(eq, true, entry.va_start);
+ matched_one.add(move(eq));
+ }
+
+ Pointer ptr(s.getMemory(), raw_p);
+ s.addUB(ptr.isBlockAlive());
+ s.addUB(ptr.blockSize().uge(4)); // FIXME: this is target dependent
+
+ // alive, next_arg, num_args, va_start, active
+ data.try_emplace(raw_p, true, move(zero), move(num_args), true,
+ !matched_one());
+
+ return {};
+}
+
+expr VaStart::getTypeConstraints(const Function &f) const {
+ return ptr->getType().enforcePtrType();
+}
+
+unique_ptr<Instr> VaStart::dup(const string &suffix) const {
+ return make_unique<VaStart>(*ptr);
+}
+
+
+vector<Value*> VaEnd::operands() const {
+ return { ptr };
+}
+
+void VaEnd::rauw(const Value &what, Value &with) {
+ RAUW(ptr);
+}
+
+void VaEnd::print(ostream &os) const {
+ os << "call void @llvm.va_end(" << *ptr << ')';
+}
+
+template <typename D>
+static void ensure_varargs_ptr(D &data, State &s, const expr &arg_ptr) {
+ OrExpr matched_one;
+ for (auto &[ptr, entry] : data) {
+ matched_one.add(ptr == arg_ptr);
+ }
+
+ expr matched = matched_one();
+ if (matched.isTrue())
+ return;
+
+ // Insert a new entry in case there was none before.
+ // This might be a ptr passed as argument (va_start in the callee).
+ s.addUB(matched || !Pointer(s.getMemory(), arg_ptr).isLocal());
+
+ expr zero = expr::mkUInt(0, VARARG_BITS);
+ ENSURE(data.try_emplace(arg_ptr,
+ expr::mkUF("vararg_alive", { arg_ptr }, false),
+ zero, // = next_arg
+ expr::mkUF("vararg_num_args", { arg_ptr }, zero),
+ false, // = va_start
+ !matched).second);
+}
+
+StateValue VaEnd::toSMT(State &s) const {
+ auto &data = s.getVarArgsData();
+ auto &raw_p = s.getAndAddPoisonUB(*ptr, true).value;
+
+ s.addUB(Pointer(s.getMemory(), raw_p).isBlockAlive());
+
+ ensure_varargs_ptr(data, s, raw_p);
+
+ for (auto &[ptr, entry] : data) {
+ expr eq = ptr == raw_p;
+ s.addUB((eq && entry.active).implies(entry.alive));
+ entry.alive &= !eq;
+ }
+ return {};
+}
+
+expr VaEnd::getTypeConstraints(const Function &f) const {
+ return ptr->getType().enforcePtrType();
+}
+
+unique_ptr<Instr> VaEnd::dup(const string &suffix) const {
+ return make_unique<VaEnd>(*ptr);
+}
+
+
+vector<Value*> VaCopy::operands() const {
+ return { dst, src };
+}
+
+void VaCopy::rauw(const Value &what, Value &with) {
+ RAUW(dst);
+ RAUW(src);
+}
+
+void VaCopy::print(ostream &os) const {
+ os << "call void @llvm.va_copy(" << *dst << ", " << *src << ')';
+}
+
+StateValue VaCopy::toSMT(State &s) const {
+ auto &data = s.getVarArgsData();
+ auto &dst_raw = s.getAndAddPoisonUB(*dst, true).value;
+ auto &src_raw = s.getAndAddPoisonUB(*src, true).value;
+ Pointer dst(s.getMemory(), dst_raw);
+ Pointer src(s.getMemory(), src_raw);
+
+ s.addUB(dst.isBlockAlive());
+ s.addUB(src.isBlockAlive());
+ s.addUB(dst.blockSize() == src.blockSize());
+
+ ensure_varargs_ptr(data, s, src_raw);
+
+ DisjointExpr<expr> next_arg, num_args, va_start;
+ for (auto &[ptr, entry] : data) {
+ expr select = entry.active && ptr == src_raw;
+ s.addUB(select.implies(entry.alive));
+
+ next_arg.add(entry.next_arg, select);
+ num_args.add(entry.num_args, select);
+ va_start.add(entry.va_start, move(select));
+ }
+
+ // FIXME: dst should be empty or we have a mem leak
+ // alive, next_arg, num_args, va_start, active
+ data[dst_raw] = { true, *next_arg(), *num_args(), *va_start(), true };
+
+ return {};
+}
+
+expr VaCopy::getTypeConstraints(const Function &f) const {
+ return dst->getType().enforcePtrType() &&
+ src->getType().enforcePtrType();
+}
+
+unique_ptr<Instr> VaCopy::dup(const string &suffix) const {
+ return make_unique<VaCopy>(*dst, *src);
+}
+
+
+vector<Value*> VaArg::operands() const {
+ return { ptr };
+}
+
+void VaArg::rauw(const Value &what, Value &with) {
+ RAUW(ptr);
+}
+
+void VaArg::print(ostream &os) const {
+ os << getName() << " = va_arg " << *ptr << ", " << getType();
+}
+
+StateValue VaArg::toSMT(State &s) const {
+ auto &data = s.getVarArgsData();
+ auto &raw_p = s.getAndAddPoisonUB(*ptr, true).value;
+
+ s.addUB(Pointer(s.getMemory(), raw_p).isBlockAlive());
+
+ ensure_varargs_ptr(data, s, raw_p);
+
+ DisjointExpr<StateValue> ret(StateValue{});
+ expr value_kind = getType().getDummyValue(false).value;
+ expr one = expr::mkUInt(1, VARARG_BITS);
+
+ for (auto &[ptr, entry] : data) {
+ string type = getType().toString();
+ string arg_name = "va_arg_" + type;
+ string arg_in_name = "va_arg_in_" + type;
+ StateValue val = {
+ expr::mkIf(entry.va_start,
+ expr::mkUF(arg_name.c_str(), { entry.next_arg }, value_kind),
+ expr::mkUF(arg_in_name.c_str(), { ptr, entry.next_arg },
+ value_kind)),
+ expr::mkIf(entry.va_start,
+ expr::mkUF("va_arg_np", { entry.next_arg }, true),
+ expr::mkUF("va_arg_np_in", { ptr, entry.next_arg }, true))
+ };
+ expr eq = ptr == raw_p;
+ expr select = entry.active && eq;
+ ret.add(move(val), select);
+
+ expr next_arg = entry.next_arg + one;
+ s.addUB(select.implies(entry.alive && entry.num_args.uge(next_arg)));
+ entry.next_arg = expr::mkIf(eq, next_arg, entry.next_arg);
+ }
+
+ return *ret();
+}
+
+expr VaArg::getTypeConstraints(const Function &f) const {
+ return getType().enforceScalarType() &&
+ ptr->getType().enforcePtrType();
+}
+
+unique_ptr<Instr> VaArg::dup(const string &suffix) const {
+ return make_unique<VaArg>(getType(), getName() + suffix, *ptr);
+}
+
+
vector<Value*> ExtractElement::operands() const {
return { v, idx };
}

MODIFY
ir/instr.h
ir/instr.h
@@ -813,11 +813,65 @@ public:
};
+class VaStart final : public Instr {
+ Value *ptr;
+public:
+ VaStart(Value &ptr) : Instr(Type::voidTy, "va_start"), ptr(&ptr) {}
+ std::vector<Value*> operands() const override;
+ void rauw(const Value &what, Value &with) override;
+ void print(std::ostream &os) const override;
+ StateValue toSMT(State &s) const override;
+ smt::expr getTypeConstraints(const Function &f) const override;
+ std::unique_ptr<Instr> dup(const std::string &suffix) const override;
+};
+
+
+class VaEnd final : public Instr {
+ Value *ptr;
+public:
+ VaEnd(Value &ptr) : Instr(Type::voidTy, "va_end"), ptr(&ptr) {}
+ std::vector<Value*> operands() const override;
+ void rauw(const Value &what, Value &with) override;
+ void print(std::ostream &os) const override;
+ StateValue toSMT(State &s) const override;
+ smt::expr getTypeConstraints(const Function &f) const override;
+ std::unique_ptr<Instr> dup(const std::string &suffix) const override;
+};
+
+
+class VaCopy final : public Instr {
+ Value *dst, *src;
+public:
+ VaCopy(Value &dst, Value &src)
+ : Instr(Type::voidTy, "va_copy"), dst(&dst), src(&src) {}
+ std::vector<Value*> operands() const override;
+ void rauw(const Value &what, Value &with) override;
+ void print(std::ostream &os) const override;
+ StateValue toSMT(State &s) const override;
+ smt::expr getTypeConstraints(const Function &f) const override;
+ std::unique_ptr<Instr> dup(const std::string &suffix) const override;
+};
+
+
+class VaArg final : public Instr {
+ Value *ptr;
+public:
+ VaArg(Type &type, std::string &&name, Value &ptr)
+ : Instr(type, std::move(name)), ptr(&ptr) {}
+ std::vector<Value*> operands() const override;
+ void rauw(const Value &what, Value &with) override;
+ void print(std::ostream &os) const override;
+ StateValue toSMT(State &s) const override;
+ smt::expr getTypeConstraints(const Function &f) const override;
+ std::unique_ptr<Instr> dup(const std::string &suffix) const override;
+};
+
+
class ExtractElement final : public Instr {
Value *v, *idx;
public:
ExtractElement(Type &type, std::string &&name, Value &v, Value &idx)
- : Instr(type, move(name)), v(&v), idx(&idx) {}
+ : Instr(type, std::move(name)), v(&v), idx(&idx) {}
std::vector<Value*> operands() const override;
void rauw(const Value &what, Value &with) override;
void print(std::ostream &os) const override;
@@ -831,7 +885,7 @@ class InsertElement final : public Instr {
Value *v, *e, *idx;
public:
InsertElement(Type &type, std::string &&name, Value &v, Value &e, Value &idx)
- : Instr(type, move(name)), v(&v), e(&e), idx(&idx) {}
+ : Instr(type, std::move(name)), v(&v), e(&e), idx(&idx) {}
std::vector<Value*> operands() const override;
void rauw(const Value &what, Value &with) override;
void print(std::ostream &os) const override;
@@ -847,7 +901,7 @@ class ShuffleVector final : public Instr {
public:
ShuffleVector(Type &type, std::string &&name, Value &v1, Value &v2,
std::vector<unsigned> mask)
- : Instr(type, move(name)), v1(&v1), v2(&v2), mask(std::move(mask)) {}
+ : Instr(type, std::move(name)), v1(&v1), v2(&v2), mask(std::move(mask)) {}
std::vector<Value*> operands() const override;
void rauw(const Value &what, Value &with) override;
void print(std::ostream &os) const override;

MODIFY
ir/state.cpp
ir/state.cpp
@@ -85,6 +85,38 @@ State::ValueAnalysis::FnCallRanges::overlaps(const FnCallRanges &other) const {
return true;
}
+bool State::VarArgsEntry::operator<(const VarArgsEntry &rhs) const {
+ return tie(alive, next_arg, num_args, va_start, active) <
+ tie(rhs.alive, rhs.next_arg, rhs.num_args, rhs.va_start, rhs.active);
+}
+
+State::VarArgsData
+State::VarArgsData::mkIf(const expr &cond, const VarArgsData &then,
+ const VarArgsData &els) {
+ VarArgsData ret;
+ for (auto &[ptr, entry] : then.data) {
+ auto other = els.data.find(ptr);
+ if (other == els.data.end()) {
+ ret.data.try_emplace(ptr, cond && entry.alive, entry.next_arg,
+ entry.num_args, entry.va_start, entry.active);
+ } else {
+#define C(f) expr::mkIf(cond, entry.f, other->second.f)
+ ret.data.try_emplace(ptr, C(alive), C(next_arg), C(num_args), C(va_start),
+ C(active));
+#undef C
+ }
+ }
+
+ for (auto &[ptr, entry] : els.data) {
+ if (then.data.count(ptr))
+ continue;
+ ret.data.try_emplace(ptr, !cond && entry.alive, entry.next_arg,
+ entry.num_args, entry.va_start, entry.active);
+ }
+
+ return ret;
+}
+
State::State(Function &f, bool source)
: f(f), source(source), memory(*this),
return_val(f.getType().getDummyValue(false)), return_memory(memory) {}
@@ -322,7 +354,6 @@ StateValue* State::no_more_tmp_slots() {
const StateValue& State::operator[](const Value &val) {
auto &[var, val_uvars] = values[values_map.at(&val)];
auto &[sval, uvars] = val_uvars;
- (void)var;
auto undef_itr = analysis.non_undef_vals.find(&val);
bool is_non_undef = undef_itr != analysis.non_undef_vals.end();
@@ -500,28 +531,30 @@ bool State::startBB(const BasicBlock &bb) {
DisjointExpr<Memory> in_memory;
DisjointExpr<expr> UB;
+ DisjointExpr<VarArgsData> var_args_in;
OrExpr path;
bool isFirst = true;
for (auto &[src, data] : I->second) {
- (void)src;
- auto &[dom, anlys, mem] = data;
- path.add(dom.path);
- expr p = dom.path();
- UB.add_disj(dom.UB, p);
- in_memory.add_disj(mem, move(p));
- domain.undef_vars.insert(dom.undef_vars.begin(), dom.undef_vars.end());
+ path.add(data.domain.path);
+ expr p = data.domain.path();
+ UB.add_disj(data.domain.UB, p);
+ in_memory.add_disj(data.mem, p);
+ var_args_in.add_disj(data.var_args, move(p));
+ domain.undef_vars.insert(data.domain.undef_vars.begin(),
+ data.domain.undef_vars.end());
if (isFirst)
- analysis = anlys;
+ analysis = data.analysis;
else
- analysis.intersect(anlys);
+ analysis.intersect(data.analysis);
isFirst = false;
}
domain.path = path();
domain.UB.add(*UB());
memory = *in_memory();
+ var_args_data = *var_args_in();
return domain;
}
@@ -893,7 +926,6 @@ expr State::sinkDomain() const {
OrExpr ret;
for (auto &[src, data] : I->second) {
- (void)src;
ret.add(data.domain.path());
}
return ret();

MODIFY
ir/state.h
ir/state.h
@@ -63,10 +63,30 @@ private:
void intersect(const ValueAnalysis &other);
};
+ struct VarArgsEntry {
+ smt::expr alive;
+ smt::expr next_arg;
+ smt::expr num_args;
+ smt::expr va_start;
+ smt::expr active; // false if this entry is repeated
+
+ // FIXME: upgrade with C++20
+ bool operator<(const VarArgsEntry &rhs) const;
+ };
+
+ struct VarArgsData {
+ std::map<smt::expr, VarArgsEntry> data;
+ static VarArgsData mkIf(const smt::expr &cond, const VarArgsData &then,
+ const VarArgsData &els);
+ // FIXME: upgrade with C++20
+ bool operator<(const VarArgsData &rhs) const { return data < rhs.data; }
+ };
+
struct BasicBlockInfo {
DomainPreds domain;
ValueAnalysis analysis;
smt::DisjointExpr<Memory> mem;
+ VarArgsData var_args;
};
// TODO: make this const again
@@ -144,6 +164,8 @@ private:
smt::expr fn_call_pre = true;
std::set<smt::expr> fn_call_qvars;
+ VarArgsData var_args_data;
+
public:
State(Function &f, bool source);
@@ -184,6 +206,8 @@ public:
std::vector<Memory::PtrInput> &&ptr_inputs,
const std::vector<Type*> &out_types, const FnAttrs &attrs);
+ auto& getVarArgsData() { return var_args_data.data; }
+
void useUnsupported(const char *name);
auto& getUnsupported() const { return used_unsupported; }

MODIFY
ir/type.cpp
ir/type.cpp
@@ -135,6 +135,10 @@ expr Type::enforceFloatType() const {
return false;
}
+expr Type::enforceScalarType() const {
+ return enforceIntType() || enforcePtrType() || enforceFloatType();
+}
+
expr Type::enforceVectorType() const {
return enforceVectorType([](auto &ty) { return true; });
}

MODIFY
ir/type.h
ir/type.h
@@ -69,6 +69,7 @@ public:
virtual smt::expr enforceAggregateType(
std::vector<Type*> *element_types = nullptr) const;
virtual smt::expr enforceFloatType() const;
+ smt::expr enforceScalarType() const;
smt::expr enforceVectorType() const;
// enforce same number of elements if other is a vector

MODIFY
llvm_util/llvm2alive.cpp
llvm_util/llvm2alive.cpp
@@ -890,6 +890,18 @@ end:
return make_unique<FnCall>(*llvm_type2alive(i.getType()),
"", "#trap", move(attrs));
}
+ case llvm::Intrinsic::vastart: {
+ PARSE_UNOP();
+ return make_unique<VaStart>(*val);
+ }
+ case llvm::Intrinsic::vaend: {
+ PARSE_UNOP();
+ return make_unique<VaEnd>(*val);
+ }
+ case llvm::Intrinsic::vacopy: {
+ PARSE_BINOP();
+ return make_unique<VaCopy>(*a, *b);
+ }
// do nothing intrinsics
case llvm::Intrinsic::dbg_addr:
@@ -929,6 +941,11 @@ end:
move(mask)));
}
+ RetTy visitVAArg(llvm::VAArgInst &i) {
+ PARSE_UNOP();
+ RETURN_IDENTIFIER(make_unique<VaArg>(*ty, value_name(i), *val));
+ }
+
RetTy visitInstruction(llvm::Instruction &i) { return error(i); }
RetTy error(llvm::Instruction &i) {
@@ -1092,7 +1109,8 @@ end:
return {};
Function Fn(*type, f.getName().str(), 8 * DL().getPointerSize(),
- DL().getIndexSizeInBits(0), DL().isLittleEndian());
+ DL().getIndexSizeInBits(0), DL().isLittleEndian(),
+ f.isVarArg());
reset_state(Fn);
for (auto &arg : f.args()) {

ADD
tests/alive-tv/varargs/arg-fail.srctgt.ll
+; ERROR: Target is more poisonous than source
+
+%struct.va_list = type { i8* }
+
+define i32 @src(%struct.va_list* %p) {
+ %p2 = bitcast %struct.va_list* %p to i8*
+ %a = va_arg i8* %p2, i32
+ ret i32 %a
+}
+
+define i32 @tgt(%struct.va_list* %p) {
+ %p2 = bitcast %struct.va_list* %p to i8*
+ %a = va_arg i8* %p2, i32
+ ret i32 poison
+}

ADD
tests/alive-tv/varargs/arg.srctgt.ll
+%struct.va_list = type { i8* }
+
+define i32 @src(%struct.va_list* %p) {
+ %p2 = bitcast %struct.va_list* %p to i8*
+ %a = va_arg i8* %p2, i32
+ ret i32 %a
+}
+
+define i32 @tgt(%struct.va_list* %p) {
+ %p2 = bitcast %struct.va_list* %p to i8*
+ %a = va_arg i8* %p2, i32
+ ret i32 %a
+}

ADD
tests/alive-tv/varargs/copy-fail.srctgt.ll
+; ERROR: Value mismatch
+
+%struct.va_list = type { i8* }
+
+define i32 @src(...) {
+ %ap = alloca %struct.va_list
+ %aq = alloca i8*
+
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+
+ %a = va_arg i8* %ap2, i32
+
+ %aq2 = bitcast i8** %aq to i8*
+ call void @llvm.va_copy(i8* %aq2, i8* %ap2)
+
+ call void @llvm.va_end(i8* %ap2)
+
+ %b = va_arg i8* %aq2, i32
+ call void @llvm.va_end(i8* %aq2)
+
+ %r = add i32 %a, %b
+ ret i32 %r
+}
+
+define i32 @tgt(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+ %a = va_arg i8* %ap2, i32
+ call void @llvm.va_end(i8* %ap2)
+ %r = add i32 %a, %a
+ ret i32 %r
+}
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_copy(i8*, i8*)
+declare void @llvm.va_end(i8*)

ADD
tests/alive-tv/varargs/copy.srctgt.ll
+%struct.va_list = type { i8* }
+
+define i32 @src(...) {
+ %ap = alloca %struct.va_list
+ %aq = alloca i8*
+
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+
+ %aq2 = bitcast i8** %aq to i8*
+ call void @llvm.va_copy(i8* %aq2, i8* %ap2)
+
+ %a = va_arg i8* %ap2, i32
+ call void @llvm.va_end(i8* %ap2)
+
+ %b = va_arg i8* %aq2, i32
+ call void @llvm.va_end(i8* %aq2)
+
+ %r = add i32 %a, %b
+ ret i32 %r
+}
+
+define i32 @tgt(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+ %a = va_arg i8* %ap2, i32
+ call void @llvm.va_end(i8* %ap2)
+ %r = add i32 %a, %a
+ ret i32 %r
+}
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_copy(i8*, i8*)
+declare void @llvm.va_end(i8*)

ADD
tests/alive-tv/varargs/dead-ub.srctgt.ll
+%struct.va_list = type { i8* }
+
+define i32 @src(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+ call void @llvm.va_end(i8* %ap2)
+ %a = va_arg i8* %ap2, i32
+ ret i32 %a
+}
+
+define i32 @tgt(...) {
+ unreachable
+}
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_end(i8*)

ADD
tests/alive-tv/varargs/extra-arg-fail.srctgt.ll
+; ERROR: Source is more defined than target
+
+%struct.va_list = type { i8* }
+
+define i32 @src(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+ %a = va_arg i8* %ap2, i32
+ call void @llvm.va_end(i8* %ap2)
+ ret i32 %a
+}
+
+define i32 @tgt(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ call void @llvm.va_start(i8* %ap2)
+ %a = va_arg i8* %ap2, i32
+ %b = va_arg i8* %ap2, i32
+ call void @llvm.va_end(i8* %ap2)
+ ret i32 %a
+}
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_end(i8*)

ADD
tests/alive-tv/varargs/local-uninit.srctgt.ll
+%struct.va_list = type { i8* }
+
+define i32 @src(...) {
+ %ap = alloca %struct.va_list
+ %ap2 = bitcast %struct.va_list* %ap to i8*
+ %a = va_arg i8* %ap2, i32
+ ret i32 %a
+}
+
+define i32 @tgt(...) {
+ unreachable
+}
+
+declare void @llvm.va_start(i8*)
+declare void @llvm.va_end(i8*)

add experimental support for varargs