// The C calling convention for structs passed and returned by value, the way clang does it: // System V x86-64 (Linux, macOS): small structs split into eightbytes, INTEGER or SSE registers // Microsoft x64 (Windows): 1/2/4/8 bytes in one register, everything else by address // AAPCS64 (ARM64 Linux, Apple): float groups in vector registers, else up to 16 bytes in x registers #include "codegen_impl.h" #include "target.h" using namespace llvm; namespace finch { namespace { enum Class { None, Integer, Sse }; struct Leaf { uint64_t offset; uint64_t size; bool isFloat; bool isDouble; }; } // namespace // Flatten a value into its scalar pieces with byte offsets. static void leaves(Codegen &cg, const FType &t, uint64_t base, std::vector &out) { switch (t.kind) { case FType::Struct: { const StructLayout *lay = cg.dl->getStructLayout(t.info->llvm); for (auto &f : t.info->fields) leaves(cg, f.type, base + lay->getElementOffset(f.llvmIndex), out); break; } case FType::Fixed: { uint64_t es = cg.dl->getTypeAllocSize(cg.ty(*t.elem)); for (long long i = 0; i < t.count; i++) leaves(cg, *t.elem, base + i * es, out); break; } default: out.push_back({base, cg.dl->getTypeAllocSize(cg.ty(t)), t.kind == FType::F32 || t.kind == FType::F64, t.kind == FType::F64}); } } AbiArg Codegen::classify(const FType &t) { AbiArg a; if (t.kind != FType::Struct) return a; // Direct uint64_t size = dl->getTypeAllocSize(ty(t)); if (g_target.triple.isAArch64()) { // A homogeneous float aggregate (1-4 floats or 1-4 doubles) travels in vector registers. std::vector ls; leaves(*this, t, 0, ls); bool hfa = !ls.empty() && ls.size() <= 4; for (const Leaf &l : ls) hfa &= l.isFloat && l.isDouble == ls[0].isDouble; if (hfa) { llvm::Type *ft = ls[0].isDouble ? (llvm::Type *)b.getDoubleTy() : b.getFloatTy(); a.kind = AbiArg::Expand; a.parts.push_back(ArrayType::get(ft, ls.size())); a.coerced = StructType::get(ctx, std::vector(ls.size(), ft)); a.stackAlign8 = !g_target.triple.isOSDarwin(); return a; } if (size > 16) { a.kind = AbiArg::Indirect; // a copy, passed by address; returned through sret return a; } // up to 16 bytes in general registers: i64 or [2 x i64]; returned as an exact-size integer uint64_t align = dl->getABITypeAlign(ty(t)).value(); a.kind = AbiArg::Expand; if (align >= 16) { a.parts.push_back(b.getInt128Ty()); a.coerced = b.getInt128Ty(); } else if (size <= 8) { a.parts.push_back(b.getInt64Ty()); a.coerced = b.getIntNTy(size * 8); } else { a.parts.push_back(ArrayType::get(b.getInt64Ty(), 2)); a.coerced = a.parts[0]; } return a; } if (g_target.windows) { // Microsoft x64: 1, 2, 4 or 8 bytes travel as one integer (even if they hold floats); // anything else is copied by the caller and passed by address if (size == 1 || size == 2 || size == 4 || size == 8) { a.kind = AbiArg::Expand; a.parts.push_back(b.getIntNTy(size * 8)); a.coerced = a.parts[0]; } else { a.kind = AbiArg::Indirect; } return a; } if (size > 16 || size == 0) { a.kind = AbiArg::Memory; return a; } std::vector ls; leaves(*this, t, 0, ls); unsigned n = (size + 7) / 8; Class cls[2] = {None, None}; bool hasDouble[2] = {false, false}; for (const Leaf &l : ls) { if (l.offset % std::min(l.size, 8) != 0) { // misaligned field (packed struct): memory a.kind = AbiArg::Memory; return a; } unsigned k = l.offset / 8; cls[k] = (cls[k] == Integer || !l.isFloat) ? Integer : Sse; hasDouble[k] |= l.isDouble; } a.kind = AbiArg::Expand; for (unsigned k = 0; k < n; k++) { uint64_t bytes = std::min(8, size - 8 * k); if (cls[k] == Sse) { if (hasDouble[k]) a.parts.push_back(b.getDoubleTy()); else if (bytes > 4) a.parts.push_back(FixedVectorType::get(b.getFloatTy(), 2)); else a.parts.push_back(b.getFloatTy()); } else { a.parts.push_back(b.getIntNTy(bytes * 8)); } } a.coerced = a.parts.size() == 1 ? a.parts[0] : StructType::get(ctx, a.parts); return a; } namespace { struct Plan { AbiArg ret; std::vector params; llvm::FunctionType *fty = nullptr; }; } // namespace // Decide how every parameter travels, counting the 6 integer and 8 SSE argument registers. static Plan makePlan(Codegen &cg, const FType &ret, const std::vector ¶ms, bool variadic, std::vector *extraTypes = nullptr) { Plan pl; int intRegs = 6, sseRegs = 8; std::vector types; llvm::Type *retTy = cg.ty(ret); if (ret.kind == FType::Str) retTy = cg.b.getPtrTy(); pl.ret = cg.classify(ret); if (pl.ret.kind == AbiArg::Indirect) pl.ret.kind = AbiArg::Memory; // returned through a hidden pointer if (pl.ret.kind == AbiArg::Memory) { types.push_back(cg.b.getPtrTy()); // sret retTy = cg.b.getVoidTy(); intRegs--; } else if (pl.ret.kind == AbiArg::Expand) { retTy = pl.ret.coerced; } for (const FType &t : params) { AbiArg a = cg.classify(t); if (a.kind == AbiArg::Expand) { int ni = 0, ns = 0; for (llvm::Type *p : a.parts) (p->isIntegerTy() ? ni : ns)++; // only System V counts registers here; elsewhere the backend places each part if (g_target.windows || g_target.triple.isAArch64() || (ni <= intRegs && ns <= sseRegs)) { intRegs -= ni; sseRegs -= ns; for (llvm::Type *p : a.parts) types.push_back(p); } else { a.kind = AbiArg::Memory; // out of registers: the whole struct goes on the stack } } if (a.kind == AbiArg::Memory || a.kind == AbiArg::Indirect) types.push_back(cg.b.getPtrTy()); if (a.kind == AbiArg::Direct) { if (t.isFloat()) sseRegs--; else intRegs--; types.push_back(t.kind == FType::Str ? cg.b.getPtrTy() : cg.ty(t)); } pl.params.push_back(a); } (void)extraTypes; pl.fty = FunctionType::get(retTy, types, variadic); return pl; } // Is C's plain `char` signed? Yes on x86 and Apple; no on ARM64 Linux. static bool charIsSigned() { return !g_target.triple.isAArch64() || g_target.triple.isOSDarwin() || g_target.triple.isOSWindows(); } static Attribute::AttrKind extAttr(const FType &t) { if (t.bits() >= 32 || !(t.isInt() || t.kind == FType::Bool || t.kind == FType::Char)) return Attribute::None; if (t.kind == FType::Char) return charIsSigned() ? Attribute::SExt : Attribute::ZExt; return t.isSigned() ? Attribute::SExt : Attribute::ZExt; } // Attributes that tell LLVM how C passes each piece: sret, byval, signext/zeroext. static AttributeList attrsFor(Codegen &cg, const Plan &pl, const FType &ret, const std::vector ¶ms) { LLVMContext &ctx = cg.ctx; AttributeList al; unsigned idx = 0; if (pl.ret.kind == AbiArg::Memory) { al = al.addParamAttribute(ctx, idx, Attribute::getWithStructRetType(ctx, cg.ty(ret))); idx++; } else if (Attribute::AttrKind k = extAttr(ret); k != Attribute::None) { al = al.addRetAttribute(ctx, k); } for (size_t i = 0; i < params.size(); i++) { const AbiArg &a = pl.params[i]; if (a.kind == AbiArg::Memory) { uint64_t align = std::max(8, params[i].info->isC ? params[i].info->cstruct->align : 8); al = al.addParamAttribute(ctx, idx, Attribute::getWithByValType(ctx, cg.ty(params[i]))); al = al.addParamAttribute(ctx, idx, Attribute::getWithAlignment(ctx, Align(align))); idx++; } else if (a.kind == AbiArg::Indirect) { idx++; } else if (a.kind == AbiArg::Expand) { if (a.stackAlign8) al = al.addParamAttribute(ctx, idx, Attribute::getWithStackAlignment(ctx, Align(8))); idx += a.parts.size(); } else { if (Attribute::AttrKind k = extAttr(params[i]); k != Attribute::None) al = al.addParamAttribute(ctx, idx, k); idx++; } } return al; } static std::vector resolvedParams(Codegen &cg, const CFunc &f) { std::vector ps; for (const FType &t : f.params) ps.push_back(cg.resolve(t, Pos{})); return ps; } Function *Codegen::declareC(const CFunc &f) { if (Function *fn = mod->getFunction(f.name)) return fn; FType ret = resolve(f.ret, Pos{}); std::vector ps = resolvedParams(*this, f); for (const FType &t : ps) if (t.kind == FType::Struct && !t.info->unsupported.empty()) return nullptr; Plan pl = makePlan(*this, ret, ps, f.variadic); Function *fn = Function::Create(pl.fty, Function::ExternalLinkage, f.name, mod.get()); fn->setAttributes(attrsFor(*this, pl, ret, ps)); return fn; } // A stack slot big enough for both the struct and its register form. static Value *abiSlot(Codegen &cg, const FType &t, const AbiArg &a) { llvm::Type *st = cg.ty(t); llvm::Type *big = a.coerced && cg.dl->getTypeAllocSize(a.coerced) > cg.dl->getTypeAllocSize(st) ? a.coerced : st; return cg.tmpOf(big); } Value_ Codegen::emitCCall(const CFunc &f, Function *fn, std::vector args, Pos p) { FType ret = resolve(f.ret, p); std::vector ps = resolvedParams(*this, f); for (const FType &t : ps) if (t.kind == FType::Struct && !t.info->unsupported.empty()) failAt(p.file, p.line, p.col, "the C function '" + f.name + "' takes the struct " + t.info->name + " by value, which Finch can't pass yet: it " + t.info->unsupported); if (ret.kind == FType::Struct && !ret.info->unsupported.empty()) failAt(p.file, p.line, p.col, "the C function '" + f.name + "' returns the struct " + ret.info->name + " by value, which Finch can't receive yet: it " + ret.info->unsupported); Plan pl = makePlan(*this, ret, ps, f.variadic); std::vector ll; Value *sret = nullptr; if (pl.ret.kind == AbiArg::Memory) { sret = tmpOf(ty(ret)); cast(sret)->setAlignment(Align(16)); ll.push_back(sret); } for (size_t i = 0; i < args.size(); i++) { const Value_ &v = args[i]; if (i >= ps.size()) { // the '...' part: C's default promotions const FType &t = v.type; Value *x = v.v; if (t.kind == FType::F32) x = b.CreateFPExt(x, b.getDoubleTy()); else if (t.kind == FType::Str) x = cstr(x); else if (t.kind == FType::Bool || ((t.isInt() || t.kind == FType::Char) && t.bits() < 32)) x = b.CreateIntCast(x, b.getInt32Ty(), t.isSigned() || (t.kind == FType::Char && charIsSigned())); ll.push_back(x); continue; } const AbiArg &a = pl.params[i]; if (ps[i].kind == FType::Str) { ll.push_back(cstr(v.v)); } else if (a.kind == AbiArg::Memory || a.kind == AbiArg::Indirect) { Value *s = tmpOf(ty(ps[i])); // a copy the callee may change cast(s)->setAlignment(Align(16)); b.CreateStore(v.v, s); ll.push_back(s); } else if (a.kind == AbiArg::Expand) { Value *s = abiSlot(*this, ps[i], a); b.CreateStore(v.v, s); for (size_t k = 0; k < a.parts.size(); k++) ll.push_back(b.CreateLoad(a.parts[k], b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k))); } else { ll.push_back(v.v); } } CallInst *call = b.CreateCall(pl.fty, fn, ll); AttributeList al = attrsFor(*this, pl, ret, ps); call->setAttributes(al); if (ret.kind == FType::Void) return {nullptr, FType::Void}; if (pl.ret.kind == AbiArg::Memory) return {b.CreateLoad(ty(ret), sret), ret}; if (pl.ret.kind == AbiArg::Expand) { Value *s = abiSlot(*this, ret, pl.ret); b.CreateStore(call, s); return {b.CreateLoad(ty(ret), s), ret}; } if (ret.kind == FType::Str) return strFromC(call); // borrowed: copied if it is kept return {call, ret}; } // A C-callable door into a Finch function, for addr(fn) (callbacks like qsort's or libclang's). Function *Codegen::cThunk(Fn &fn, Pos p) { if (fn.cThunk) return fn.cThunk; auto cOk = [](const FType &t) { return t.isNumber() || t.kind == FType::Bool || t.kind == FType::Char || t.isPtr() || t.kind == FType::Void || (t.kind == FType::Struct && t.info->isC); }; for (const FType &t : fn.params) if (!cOk(t)) failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + ") gives C a function pointer, so its parameters must be C types (numbers, ptr, C structs), not " + t.show()); if (!cOk(fn.ret)) failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + "): a function given to C can't return " + fn.ret.show()); Plan pl = makePlan(*this, fn.ret, fn.params, false); Function *th = Function::Create(pl.fty, Function::InternalLinkage, "finch.c." + fn.decl->name, mod.get()); th->setAttributes(attrsFor(*this, pl, fn.ret, fn.params)); fn.cThunk = th; IRBuilderBase::InsertPointGuard guard(b); DISubprogram *savedFn = diFn; diFn = nullptr; b.SetInsertPoint(BasicBlock::Create(ctx, "entry", th)); b.SetCurrentDebugLocation(DebugLoc()); unsigned ai = 0; Value *sret = nullptr; if (pl.ret.kind == AbiArg::Memory) sret = th->getArg(ai++); std::vector args; for (size_t i = 0; i < fn.params.size(); i++) { const AbiArg &a = pl.params[i]; if (a.kind == AbiArg::Memory || a.kind == AbiArg::Indirect) { args.push_back(b.CreateLoad(ty(fn.params[i]), th->getArg(ai++))); } else if (a.kind == AbiArg::Expand) { Value *s = abiSlot(*this, fn.params[i], a); for (size_t k = 0; k < a.parts.size(); k++) b.CreateStore(th->getArg(ai++), b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k)); args.push_back(b.CreateLoad(ty(fn.params[i]), s)); } else { args.push_back(th->getArg(ai++)); } } Value *r = b.CreateCall(fn.llvm, args); if (fn.ret.kind == FType::Void) { b.CreateRetVoid(); } else if (pl.ret.kind == AbiArg::Memory) { b.CreateStore(r, sret); b.CreateRetVoid(); } else if (pl.ret.kind == AbiArg::Expand) { Value *s = abiSlot(*this, fn.ret, pl.ret); b.CreateStore(r, s); b.CreateRet(b.CreateLoad(pl.ret.coerced, s)); } else { b.CreateRet(r); } diFn = savedFn; return th; } } // namespace finch