import "cffc.hff"; import "common.hff"; import "vec.hff"; import "map.hff"; static outfp *FILE = {}; struct Value { ty *const Type, u enum union { IImm i64, FImm f64, BImm bool, Null, ZeroIni, StrLit [#]const u8, StrConstRef int, Tmp int, LocalRef *Var, GlobalRef *Var, Fn *Fn, } } static ty_i1 *Type = as(*void)&outfp; // dummy fn gen(fmt *const u8, ...) void; fn gen(fmt *const u8, ...) void { let ap va_list #?; ap->start(fmt); fn pritype(ty *const Type) void { if ty == ty_i1 { gen("i1"); return; } switch ty.u { case Void; gen("void"); case Bool; gen("i%z", ty.size*8); case Int; gen("i%z", ty.size*8); case Flo; gen(ty.size == 4 ? "float" : "double"); case Ptr p; gen("%t*", p->is(:Void) ? ty_i8 : p); case Slice p; gen("{ %t*, %t }", p, ty_usize); case Arr arr; gen("[%z x %t]", arr.length, arr.child); case Agg agg; gen("%%%s.%d", agg.name ?? as(*const u8)"_", agg.id); case Enum enu; pritype(enu.intty); case Fn f; gen("%t(", f.ret); foreach(ty, i, f.params) { gen("%t", ty); if i < f.params.#len - 1 { gen(", "); } else if f.variadic { gen(", ..."); } } gen(") "); case VaList; gen("%s", g_targ.valistllvmty); } } for let c = *fmt; c != 0; c = *++fmt { if c != '%' { fputc(c, outfp); continue; } switch c = *++fmt { case '%'; fprintf(outfp, "%%"); case 'c'; fprintf(outfp, "%c", ap->arg(int)); case 's'; fprintf(outfp, "%s", ap->arg(*const u8)); case 'S'; let str = ap->arg([#]const u8); fputc('"', outfp); foreach(c, _, str) { extern fn isprint(int) int; if isprint(c) == 0 { fprintf(outfp, "\\%.2X", c); } else { fputc(c, outfp); } } fprintf(outfp, "\\00\""); case 'd'; fprintf(outfp, "%d", ap->arg(int)); case 'I'; fprintf(outfp, "%lld", as(c_llong)ap->arg(i64)); case 'f'; fprintf(outfp, "%.17f", ap->arg(f64)); case 'z'; fprintf(outfp, "%zu", ap->arg(usize)); case 'v'; switch ap->arg(Value).u { case IImm i; gen("%I", i); case FImm f; gen("%f", f); case BImm b; gen("%d", as(int)b); case Null; gen("null"); case ZeroIni; gen("zeroinitializer"); case Tmp t; fprintf(outfp, "%%t%d", t); case LocalRef var; gen("%%%s.%d", container_of(var, Decl, u.Let).name, var.id); case Fn f; let decl = container_of(f, Decl, u.Fn); if !decl.externp { gen("@%s.%d", decl.name, f.id); } else { gen("@%s", decl.name); } case GlobalRef v; let decl = container_of(v, Decl, u.Static); if !decl.externp { gen("@%s.%d", decl.name, v.id); } else { gen("@%s", decl.name); } case StrConstRef id; gen("@.str.%d", id); } case 't'; pritype(ap->arg(*Type)); case else assert(#f, "bad fmt '%c'", c); } } ap->end(); } static tmpid int = 1; fn mktmp(ty *const Type) Value { return Value{ty, :Tmp(tmpid++)}; } fn genexpr(f *Fn, ex *Expr) Value; static arena Arena = {}; static alloc Allocator = {}; static strs Vec<[#]const u8> = {}; fn genaddr(f *Fn, ex *Expr) Value { switch ex.u { case StrLit s; let p *u8 = alloc->alloc(s.#len + 1, 1); memcpy(p, s.#ptr, s.#len + 1); let s = p[0::s.#len]; strs->push(s); return {mkptrtype(ex.ty), :StrConstRef(strs.len - 1)}; case Symbol decl; switch decl.u { case Let *var; return {mkptrtype(var.ty), :LocalRef(var)}; case Static *var; return {mkptrtype(var.ty), :GlobalRef(var)}; } case UnOp un; switch un.op { case :deref; return genexpr(f, un.ex); } case Index idx; let lhs Value #?; if idx.lhs.ty->is(:Arr) { let arr = genaddr(f, idx.lhs); lhs = mktmp(ex.ty); gen("\t%v = getelementptr %t, %t %v, i32 0, i32 0\n", lhs, arr.ty.u.Arr.child, arr.ty, arr); } else { lhs = genexpr(f, idx.lhs); } let rhs = genexpr(f, idx.rhs), addr = mktmp(ex.ty), val = mktmp(ex.ty); gen("\t%v = getelementptr %t, %t* %v, %t %v\n", addr, ex.ty, ex.ty, lhs, rhs.ty, rhs); return addr; } assert(#f, "genaddr"); } fn convert(f *Fn, to *const Type, ex *Expr) Value { let from = ex.ty; defmacro cvt(inst) [ (do let t = mktmp(to), val = genexpr(f, ex); gen("\t%v = %s %t %v to %t\n", t, inst, from, val, to); t; ) ] switch { case from == to; return genexpr(f, ex); case to->is(:Int) and from->is(:Int) and to.size == from.size and to.u.Int.sgn != from.u.Int.sgn; return genexpr(f, ex); case to->is(:Int) and from->is(:Int) and to.size == from.size; return cvt("bitcast"); case to->is(:Int) and from->is(:Int) and to.size < from.size; return cvt("trunc"); case to->is(:Int) and from->is(:Int) and to.size > from.size; return cvt(from.u.Int.sgn ? "sext" : "zext"); case to->is(:Int) and from->is(:Int) and to.size < from.size; return cvt("trunc"); case to->is(:Int) and from->is(:Int) and to.size > from.size; return cvt(from.u.Int.sgn ? "sext" : "zext"); case to->is(:Flo) and from->is(:Flo) and to.size == from.size; return genexpr(f, ex); case to->is(:Flo) and from->is(:Flo) and to.size > from.size; return cvt("fpext"); case to->is(:Flo) and from->is(:Flo) and to.size < from.size; return cvt("fptrunc"); case to->is(:Ptr) and from->is(:Ptr); let t = mktmp(to), val = genexpr(f, ex); gen("\t%v = bitcast %t %v to %t\n", t, from, val, to); return t; case to->is(:Ptr) and from->is(:Arr); let addr = genaddr(f, ex); let t = mktmp(ex.ty); gen("\t%v = getelementptr %t, %t %v, %t 0, %t 0\n", t, addr.ty.u.Ptr, addr.ty, addr, ty_usize, ty_usize); return t; case to->is(:Arr) and from->is(:Arr); return genexpr(f, ex); case else assert(#f, "convert"); } } fn genblock(f *Fn, block Block) Option; fn genexpr(f *Fn, ex *Expr) Value { fold(ex); switch ex.u { case IntLit i; return {ex.ty, :IImm(i.i)}; case FloLit f; return {ex.ty, :FImm(f)}; case BoolLit b; return {ex.ty, :BImm(b)}; case NullLit; return {ex.ty, :Null}; case ZeroIni; return {ex.ty, :ZeroIni}; case StrLit s; return {ex.ty, :StrLit(s)}; case BinOp b; defmacro genbinop(a, b, inst) [ (do let lhs = convert(f, ex.ty, a), rhs = convert(f, ex.ty, b), t = mktmp(ex.ty); gen("\t%v = %s %t %v, %v\n", t, inst, ex.ty, lhs, rhs); t; ) ] let ty2 = typeof2(b.lhs.ty, b.rhs.ty); defmacro gencmp(op) [ { let lhs = convert(f, ty2, b.lhs), rhs = convert(f, ty2, b.rhs), t0 = mktmp(ex.ty), t1 = mktmp(ex.ty); gen("\t%v = %s %s %t %v, %v\n", t0, ty2->is(:Flo) ? "fcmp" : "icmp", op, ty2, lhs, rhs); gen("\t%v = zext i1 %v to %t\n", t1, t0, ex.ty); return t1; } ] defmacro genadd(type, lhs, rhs) [ (do let res Value #?; if isnumtype(type) { let $t = mktmp(type); gen("\t%v = %s %t %v, %v\n", $t, ex.ty->is(:Flo) ? "fadd" : "add", ex.ty, lhs, rhs); res = $t; } else if lhs.ty->is(:Ptr) { let $t = mktmp(type); gen("\t%v = getelementptr %t, %t %v, %t %v\n", $t, lhs.ty.u.Ptr, lhs.ty, lhs, rhs.ty, rhs); res = $t; } else if rhs.ty->is(:Ptr) { } else { assert(#f, "bad sub"); } res; ) ] defmacro gensub(type, lhs, rhs) [ (do let res Value #?; if isnumtype(type) { let $t = mktmp(type); gen("\t%v = %s %t %v, %v\n", $t, ex.ty->is(:Flo) ? "fsub" : "sub", ex.ty, lhs, rhs); res = $t; } else if lhs.ty->is(:Ptr) and rhs.ty->is(:Int) { let $off = mktmp(rhs.ty); gen("\t%v = sub %t 0, %v\n", $off, rhs.ty, rhs); let $t = mktmp(type); gen("\t%v = getelementptr %t, %t %v, %t %v\n", $t, lhs.ty.u.Ptr, lhs.ty, lhs, $off.ty, $off); res = $t; } else { assert(#f, "bad add"); } res; ) ] defmacro genassignop(inst, type, a, b) [ (do let addr = genaddr(f, a); let rhs = convert(f, a.ty, b); let lhs0 = mktmp(type); gen("\t%v = load %t, %t %v\n", lhs0, type, addr.ty, addr); let tmp = (do let t = mktmp(ex.ty); gen("\t%v = %s %t %v, %v\n", t, inst, ex.ty, lhs0, rhs); t; ); gen("\tstore %t %v, %t* %v\n", tmp.ty, tmp, ex.ty, addr); rhs; ) ] switch b.op { case '+'; let ty2 = typeof2(b.lhs.ty, b.rhs.ty); let lhs = b.lhs.ty->is(:Ptr) ? genexpr(f, b.lhs) : convert(f, ty2 ?? b.lhs.ty, b.lhs), rhs = b.rhs.ty->is(:Ptr) ? genexpr(f, b.rhs) : convert(f, ty2 ?? b.rhs.ty, b.rhs); return genadd(ex.ty, lhs, rhs); case '-'; let ty2 = typeof2(b.lhs.ty, b.rhs.ty); let lhs = b.lhs.ty->is(:Ptr) ? genexpr(f, b.lhs) : convert(f, ty2 ?? b.lhs.ty, b.lhs), rhs = b.rhs.ty->is(:Ptr) ? genexpr(f, b.rhs) : convert(f, ty2 ?? b.rhs.ty, b.rhs); return gensub(ex.ty, lhs, rhs); case '*'; return genbinop(b.lhs, b.rhs, !ex.ty->is(:Flo) ? "mul" : "fmul"); case '/'; return genbinop(b.lhs, b.rhs, ex.ty->is(:Flo) ? "fdiv" : ex.ty.u.Int.sgn ? "sdiv" : "udiv"); case '%'; return genbinop(b.lhs, b.rhs, ex.ty.u.Int.sgn ? "srem" : "urem"); case '&'; return genbinop(b.lhs, b.rhs, "and"); case '|'; return genbinop(b.lhs, b.rhs, "or"); case '^'; return genbinop(b.lhs, b.rhs, "xor"); case '<<'; return genbinop(b.lhs, b.rhs, "shl"); case '>>'; return genbinop(b.lhs, b.rhs, ex.ty.u.Int.sgn ? "ashr" : "lshr"); case '=='; gencmp("eq"); case '!='; gencmp("ne"); case '<'; gencmp(ty2->is(:Flo) ? "olt" : ty2.u.Int.sgn ? "slt" : "ult"); case '<='; gencmp(ty2->is(:Flo) ? "ole" : ty2.u.Int.sgn ? "sle" : "ule"); case '>'; gencmp(ty2->is(:Flo) ? "ogt" : ty2.u.Int.sgn ? "sgt" : "ugt"); case '>='; gencmp(ty2->is(:Flo) ? "oge" : ty2.u.Int.sgn ? "sge" : "uge"); case '='; let addr = genaddr(f, b.lhs); let rhs = convert(f, b.lhs.ty, b.rhs); gen("\tstore %t %v, %t* %v\n", ex.ty, rhs, ex.ty, addr); return rhs; case '+='; let addr = genaddr(f, b.lhs); let rhs = convert(f, b.lhs.ty, b.rhs); let lhs0 = mktmp(ex.ty); gen("\t%v = load %t, %t %v\n", lhs0, ex.ty, addr.ty, addr); let tmp = genadd(ex.ty, lhs0, rhs); gen("\tstore %t %v, %t* %v\n", tmp.ty, tmp, ex.ty, addr); return rhs; case '-='; let addr = genaddr(f, b.lhs); let rhs = convert(f, b.lhs.ty, b.rhs); let lhs0 = mktmp(ex.ty); gen("\t%v = load %t, %t %v\n", lhs0, ex.ty, addr.ty, addr); let tmp = gensub(ex.ty, lhs0, rhs); gen("\tstore %t %v, %t* %v\n", tmp.ty, tmp, ex.ty, addr); return rhs; case '*='; return genassignop(ex.ty->is(:Flo) ? "fdiv" : "mul", ex.ty, b.lhs, b.rhs); case '/='; return genassignop(ex.ty->is(:Flo) ? "fdiv" : ex.ty.u.Int.sgn ? "sdiv" : "udiv", ex.ty, b.lhs, b.rhs); case '%='; return genassignop(ex.ty.u.Int.sgn ? "srem" : "urem", ex.ty, b.lhs, b.rhs); case '&='; return genassignop("and", ex.ty, b.lhs, b.rhs); case '|='; return genassignop("or", ex.ty, b.lhs, b.rhs); case '^='; return genassignop("xor", ex.ty, b.lhs, b.rhs); case'<<='; return genassignop("shl", ex.ty, b.lhs, b.rhs); case'>>='; return genassignop(ex.ty.u.Int.sgn ? "ashr" : "lshr", ex.ty, b.lhs, b.rhs); case else assert(#f, "binop? %c", b.op); } case UnOp un; switch un.op { case :preinc, :predec; let one = Value{ex.ty, :IImm(un.op == :preinc ? 1 : -1)}; let addr = genaddr(f, un.ex); let var = mktmp(ex.ty); let val = mktmp(ex.ty); gen("\t%v = load %t, %t* %v\n", var, ex.ty, ex.ty, addr); if ex.ty->is(:Ptr) { gen("\t%v = getelementptr %t, %t %v, i32 %v\n", val, ex.ty.u.Ptr, ex.ty, var, one); } else { gen("\t%v = add %t %v, %v\n", val, ex.ty, var, one); } gen("\tstore %t %v, %t* %v\n", ex.ty, val, ex.ty, addr); return val; case :postinc, :postdec; let one = Value{ex.ty, :IImm(un.op == :postinc ? 1 : -1)}; let addr = genaddr(f, un.ex); let var = mktmp(ex.ty); let val = mktmp(ex.ty); gen("\t%v = load %t, %t* %v\n", var, ex.ty, ex.ty, addr); if ex.ty->is(:Ptr) { gen("\t%v = getelementptr %t, %t %v, i32 %v\n", val, ex.ty.u.Ptr, ex.ty, var, one); } else { gen("\t%v = add %t %v, %v\n", val, ex.ty, var, one); } gen("\tstore %t %v, %t* %v\n", ex.ty, val, ex.ty, addr); return var; case :deref; let rhs = genexpr(f, un.ex); let val = mktmp(ex.ty); gen("\t%v = load %t, %t* %v\n", val, ex.ty, ex.ty, rhs); return val; case else assert(#f, "unop?"); } case Index idx; let lhs Value #?; if idx.lhs.ty->is(:Arr) { let arr = genaddr(f, idx.lhs); lhs = mktmp(ex.ty); gen("\t%v = getelementptr %t, %t %v, i32 0, i32 0\n", lhs, arr.ty.u.Arr.child, arr.ty, arr); } else { lhs = genexpr(f, idx.lhs); } let rhs = genexpr(f, idx.rhs), addr = mktmp(ex.ty), val = mktmp(ex.ty); gen("\t%v = getelementptr %t, %t* %v, %t %v\n", addr, ex.ty, ex.ty, lhs, rhs.ty, rhs); gen("\t%v = load %t, %t* %v\n", val, ex.ty, ex.ty, addr); return val; case Cast it; return convert(f, ex.ty, it); case Symbol decl; switch decl.u { case Let *var; let t = mktmp(ex.ty); gen("\t%v = load %t, %t* %%%s.%d\n", t, ex.ty, ex.ty, decl.name, var.id); return t; case Static *var; let t = mktmp(ex.ty); if decl.externp { gen("\t%v = load %t, %t* @%s\n", t, ex.ty, ex.ty, decl.name); } else { gen("\t%v = load %t, %t* @%s.%d\n", t, ex.ty, ex.ty, decl.name, var.id); } return t; case Fn *f; return {ex.ty, :Fn(f)}; case else assert(#f, "decl"); } case Call call; let lhs = genexpr(f, call.lhs); let fnty = &(lhs.ty->is(:Ptr) ? lhs.ty.u.Ptr : lhs.ty).u.Fn; let args *Value = xcalloc(call.args.#len, sizeof Value); defer free(args); foreach_ptr(arg, i, call.args) { let ty = i < fnty.params.#len ? fnty.params[i] : typeof2(arg.ty, arg.ty); args[i] = convert(f, ty, arg); } let t Value #?; if fnty.ret->is(:Void) { t = {ty_void}; gen("\tcall %t %v(", call.lhs.ty, lhs); } else { t = mktmp(ex.ty); gen("\t%v = call %t %v(", t, call.lhs.ty, lhs); } foreach_ptr(arg, i, call.args) { let ty = i < fnty.params.#len ? fnty.params[i] : typeof2(arg.ty, arg.ty); gen("%t %v", ty, args[i]); if i < call.args.#len - 1 { gen(", "); } } gen(")\n"); return t; case Stmt block; switch genblock(f, block) { case Some val; return val; case None; return {ty_void}; } case else assert(#f, "expr? %d", ex.u.#tag); } } fn llvmbool(f *Fn, ex *Expr) Value { let v = genexpr(f, ex); let t = mktmp(ty_i1); if v.ty->is(:Bool) or v.ty->is(:Int) { gen("\t%v = icmp ne %t %v, 0\n", t, v.ty, v); } else { gen("\t%v = icmp ne %t %v, null\n", t, v.ty, v); } return t; } fn nop() void { gen("\t%%t%d = bitcast i8 0 to i8 ; NOP\n", tmpid++); } fn genstmt(f *Fn, block *Block, st *Stmt) void { switch st.u { case Block block; genblock(f, block); case Expr *e; genexpr(f, e); case Decl decl; switch decl.u { case Let var; let nam = decl.name, ty = var.ty; gen("\t%%%s.%d = alloca %t\n", nam, var.id, ty); if !var.ini->empty() { gen("\tstore %t %v, %t* %%%s.%d\n", ty, convert(f, ty, &var.ini.Some), ty, nam, var.id); } } case If *cnd; static ifid int = {}; let id = ifid++; gen("\tbr i1 %v, label %%IfT%d, label %%IfF%d\n", llvmbool(f, &cnd.test), id, id); gen("IfT%d:", id); nop(); genblock(f, cnd.t); if cnd.f.sts.#ptr { gen("\tbr label %%IfSk%d\n", id); } gen("\tbr label %%IfF%d\n", id); gen("IfF%d:", id); nop(); genblock(f, cnd.f); if cnd.f.sts.#ptr { gen("\tbr label %%IfSk%d\n", id); gen("IfSk%d:", id); nop(); } case While loop; gen("\tbr label %%Cont%d\n", loop.id); gen("Cont%d:", loop.id); nop(); gen("\tbr i1 %v, label %%Next%d, label %%Brk%d\n", llvmbool(f, &loop.test), loop.id, loop.id); gen("Next%d:", loop.id); nop(); genblock(f, loop.body); gen("\tbr label %%Cont%d\n", loop.id); gen("Brk%d:",loop.id); nop(); case For loop; genblock(f, loop.ini); gen("\tbr label %%Cont%d\n", loop.id); gen("Cont%d:", loop.id); nop(); gen("\tbr i1 %v, label %%Next%d, label %%Brk%d\n", llvmbool(f, &loop.test), loop.id, loop.id); gen("Next%d:", loop.id); nop(); genblock(f, loop.body); if !loop.next->empty() { genexpr(f, &loop.next.Some); } gen("\tbr label %%Cont%d\n", loop.id); gen("Brk%d:",loop.id); nop(); case Break loopid; gen("\tbr label %%Brk%d\n", loopid); case Continue loopid; gen("\tbr label %%Cont%d\n", loopid); case Return *ret; for let defers = block.defers; defers; defers = defers.next { if defers.age < ret.deferage { genexpr(f, &defers.ex); } } switch ret.ex { case None; gen("\tret void\n"); case Some *e; let retty = f.ty.u.Fn.ret; gen("\tret %t %v\n", retty, convert(f, retty, e)); } case else assert(#f, "stmt? %d", st.u.#tag); } } fn genblock(f *Fn, block Block) Option { foreach_ptr (st, _, block.sts) { genstmt(f, &block, st); } for let defers = block.defers; defers; defers = defers.next { if defers.blockid == block.id { let ex = genexpr(f, &defers.ex); if defers.next == #null { return :Some(ex); } } } return :None; } struct FuncKey { name *const u8, externp bool, } struct FuncEntry { t enum { Idk, Def, Decl }, ty *const Type, id int, } static funcs Map = {}; extern fn llvm_addfn(decl *Decl) void { assert(decl.toplevel, "llvm-addfn"); let slot = funcs->get_slot({decl.name, decl.externp}); if slot.t != :Def { slot.t = :Decl; slot.ty = decl.u.Fn.ty; slot.id = decl.u.Fn.id; } } extern fn llvm_genfn(externp bool, name *const u8, f *Fn) void { funcs->put({name, externp}, {:Def, f.ty, f.id}); tmpid = 0; gen("define%s %t ", externp ? "" : " internal", f.ty.u.Fn.ret); if !externp { gen("@%s.%d(", name, f.id); } else { gen("@%s(", name); } foreach(ty, i, f.ty.u.Fn.params) { gen("%t", ty); if f.paramnames[i] { gen(" %%%s", f.paramnames[i]); } if i < f.paramnames.#len - 1 { gen(", "); } } gen(") { \n"); let id = 0; foreach(nam, i, f.paramnames) { if nam { let ty = f.ty.u.Fn.params[i]; gen("%%%s.%d = alloca %t ", nam, id, ty); gen("store %t %%%s, %t* %%%s.%d\n", ty, nam, ty, nam, id); ++id; } } genblock(f, f.body.Some); if f.ty.u.Fn.ret->is(:Void) { gen("\tret void\n"); } else { let retty = f.ty.u.Fn.ret; gen("\tret %t undef\n", retty); } gen("}\n"); } extern fn llvm_addtype(ty *const Type) void { if ty.konst { return; } switch ty.u { case Agg agg; gen("%%%s.%d = type ", agg.name ?? as(*const u8)"_", agg.id); if agg.fwd { gen("opaque\n"); } else if agg.flds.#len == 0 { gen("{ i8 }"); } else if agg.kind == :Struct { gen("{ "); foreach (fld, i, agg.flds) { gen("%t", fld.ty); if i < agg.flds.#len - 1 { gen(", "); } } gen(" }\n"); } else if agg.kind == :Union { let size = ty.size; let ty *const Type = #null; foreach (fld, i, agg.flds) { if ty == #null or fld.ty.align > ty.align { ty = fld.ty; } } gen("{ %t ", ty); if ty.size < size { gen(", [%z x i8] ", size - ty.size); } gen("}\n"); } else if agg.kind == :EUnion { gen("{ %t, ", agg.enumty); let size = ty.size; let ty *const Type = #null; foreach (fld, i, agg.flds) { if ty == #null or (fld.ty != #null and fld.ty.align > ty.align) { ty = fld.ty; } } ty = ty ?? ty_void; gen(" %t ", ty); if ty.size < size { gen(", [%z x i8] ", size - ty.size); } gen("}\n"); } } } extern fn llvm_fini() void { vec_each(s, i, strs) { gen("@.str.%z = internal constant [%z x i8] c%S;\n", i, s.#len + 1, s); } map_each(f, k, funcs) { if f.t == :Decl { let ty = f.ty; gen("declare %t ", ty.u.Fn.ret); if !k.externp { gen("@%s.%d(", k.name, f.id); } else { gen("@%s(", k.name); } foreach(tyy, i, ty.u.Fn.params) { gen("%t", tyy); if i < ty.u.Fn.params.#len - 1 { gen(", "); } else if ty.u.Fn.variadic { gen(", ..."); } } gen(");\n"); } } strs->clear(); funcs->clear(); arena->destroy(); } extern fn llvm_init(out *FILE) void { alloc = {&arena, &Arena:allocf, #null}; outfp = out; }