mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 00:44:27 +00:00
ARM64 and macOS: AAPCS64 struct passing, aarch64/macos targets, Apple frameworks and ld messages, tests/cross.sh, CI on ARM64 Linux and macOS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
013b95d584
commit
484c14fe77
12 changed files with 256 additions and 78 deletions
54
src/abi.cpp
54
src/abi.cpp
|
|
@ -1,6 +1,7 @@
|
|||
// The C calling convention for structs passed and returned by value (System V x86-64),
|
||||
// the way clang does it: small structs travel in registers, split into eightbytes that are
|
||||
// either INTEGER (general registers) or SSE (xmm registers); bigger ones go through memory.
|
||||
// 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"
|
||||
|
|
@ -45,6 +46,39 @@ 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<Leaf> 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<llvm::Type *>(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
|
||||
|
|
@ -122,7 +156,8 @@ static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> &pa
|
|||
if (a.kind == AbiArg::Expand) {
|
||||
int ni = 0, ns = 0;
|
||||
for (llvm::Type *p : a.parts) (p->isIntegerTy() ? ni : ns)++;
|
||||
if (g_target.windows || (ni <= intRegs && ns <= sseRegs)) {
|
||||
// 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);
|
||||
|
|
@ -143,9 +178,15 @@ static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> &pa
|
|||
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;
|
||||
return t.isSigned() || t.kind == FType::Char ? Attribute::SExt : Attribute::ZExt;
|
||||
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.
|
||||
|
|
@ -169,6 +210,7 @@ static AttributeList attrsFor(Codegen &cg, const Plan &pl, const FType &ret, con
|
|||
} 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);
|
||||
|
|
@ -230,7 +272,7 @@ Value_ Codegen::emitCCall(const CFunc &f, Function *fn, std::vector<Value_> args
|
|||
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);
|
||||
x = b.CreateIntCast(x, b.getInt32Ty(), t.isSigned() || (t.kind == FType::Char && charIsSigned()));
|
||||
ll.push_back(x);
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@
|
|||
|
||||
namespace {
|
||||
|
||||
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
||||
std::vector<std::string> systemIncludeDirs() {
|
||||
// Ask the system C compiler where it looks for headers (and, on macOS, frameworks), so we find
|
||||
// the same stdio.h it would. Returns ready -isystem / -iframework arguments.
|
||||
std::vector<std::string> systemIncludeArgs() {
|
||||
std::string output;
|
||||
capture(g_target.cc + " -E -v -x c " + nullDevice(), output);
|
||||
std::vector<std::string> dirs;
|
||||
|
|
@ -29,8 +30,11 @@ std::vector<std::string> systemIncludeDirs() {
|
|||
size_t z = l.find_last_not_of(" \n");
|
||||
if (a == std::string::npos) continue;
|
||||
std::string d = l.substr(a, z - a + 1);
|
||||
if (d.size() > 20 && d.compare(d.size() - 20, 20, " (framework directory)") == 0) continue;
|
||||
dirs.push_back(d);
|
||||
// macOS: "/…/System/Library/Frameworks (framework directory)" holds <OpenGL/gl.h> and friends
|
||||
if (d.size() > 22 && d.compare(d.size() - 22, 22, " (framework directory)") == 0)
|
||||
dirs.push_back("-iframework" + d.substr(0, d.size() - 22));
|
||||
else
|
||||
dirs.push_back("-isystem" + d);
|
||||
}
|
||||
return dirs;
|
||||
}
|
||||
|
|
@ -322,7 +326,7 @@ CImports importHeaders(const std::vector<Import> &imports, const std::vector<std
|
|||
std::vector<std::string> args = {"-x", "c", "-std=gnu11", "--target=" + g_target.triple.str()};
|
||||
if (g_target.windows) args.push_back("-D_USE_MATH_DEFINES"); // M_PI and friends, as on Linux
|
||||
for (const std::string &d : dirs) args.push_back("-I" + d);
|
||||
for (const std::string &d : systemIncludeDirs()) args.push_back("-isystem" + d);
|
||||
for (const std::string &d : systemIncludeArgs()) args.push_back(d);
|
||||
std::vector<const char *> argv;
|
||||
for (const std::string &a : args) argv.push_back(a.c_str());
|
||||
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ struct AbiArg {
|
|||
enum Kind { Direct, Expand, Memory, Indirect } kind = Direct;
|
||||
std::vector<llvm::Type *> parts; // Expand: one value per eightbyte
|
||||
llvm::Type *coerced = nullptr; // Expand: { parts... } for returns
|
||||
bool stackAlign8 = false; // AArch64 Linux: HFA arguments that end up on the stack
|
||||
};
|
||||
|
||||
class Codegen {
|
||||
|
|
|
|||
|
|
@ -165,8 +165,7 @@ int Server::run() {
|
|||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
InitializeNativeTarget();
|
||||
InitializeNativeTargetAsmPrinter();
|
||||
initTargets();
|
||||
setTarget("");
|
||||
std::string err;
|
||||
const Target *target = TargetRegistry::lookupTarget(g_target.triple, err);
|
||||
|
|
|
|||
32
src/main.cpp
32
src/main.cpp
|
|
@ -65,13 +65,10 @@ static void optimize(Module &mod, TargetMachine *tm) {
|
|||
}
|
||||
|
||||
static TargetMachine *targetMachine() {
|
||||
InitializeNativeTarget();
|
||||
InitializeNativeTargetAsmPrinter();
|
||||
|
||||
initTargets();
|
||||
const Triple &triple = g_target.triple;
|
||||
if (triple.getArch() != Triple(sys::getDefaultTargetTriple()).getArch()) {
|
||||
std::fprintf(stderr, "error: Finch can build for other systems on the same processor (like Windows from Linux), but not for %s yet\n",
|
||||
triple.getArchName().str().c_str());
|
||||
if (!triple.isX86() && !triple.isAArch64()) {
|
||||
std::fprintf(stderr, "error: Finch generates code for x86-64 and ARM64, not for %s\n", triple.getArchName().str().c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
std::string err;
|
||||
|
|
@ -129,6 +126,9 @@ static std::string runtimeObject() {
|
|||
// pkg-config knows the right paths for a library if it has one; otherwise plain -l<name>.
|
||||
static std::string libFlags(const std::string &lib) {
|
||||
std::string out;
|
||||
// macOS: link "Cocoa.framework" -> -framework Cocoa
|
||||
if (lib.size() > 10 && lib.compare(lib.size() - 10, 10, ".framework") == 0)
|
||||
return " -framework " + shellQuote(lib.substr(0, lib.size() - 10));
|
||||
if (!g_target.cross && capture("pkg-config --libs " + shellQuote(lib), out) == 0) {
|
||||
while (!out.empty() && std::isspace((unsigned char)out.back())) out.pop_back();
|
||||
return " " + out;
|
||||
|
|
@ -170,9 +170,21 @@ static std::string guessLib(const std::string &header) {
|
|||
std::string lib = l.substr(a, l.find('\'', a) - a);
|
||||
if (lib.size() > 4 && lib.compare(lib.size() - 4, 4, ".lib") == 0) lib = lib.substr(0, lib.size() - 4);
|
||||
addOnce(missingLibs, lib);
|
||||
} else if ((a = l.find("cannot find -l")) != std::string::npos) {
|
||||
a += 14;
|
||||
addOnce(missingLibs, l.substr(a, l.find_first_of(": \n", a) - a));
|
||||
} else if ((a = l.find("cannot find -l")) != std::string::npos ||
|
||||
(a = l.find("library not found for -l")) != std::string::npos) { // older Apple ld
|
||||
a = l.find("-l", a) + 2;
|
||||
addOnce(missingLibs, l.substr(a, l.find_first_of(": \n\r", a) - a));
|
||||
} else if ((a = l.find("ld: library '")) != std::string::npos) { // Apple ld: library 'x' not found
|
||||
a += 13;
|
||||
addOnce(missingLibs, l.substr(a, l.find('\'', a) - a));
|
||||
} else if ((a = l.find("framework '")) != std::string::npos && l.find("not found") != std::string::npos) {
|
||||
a += 11;
|
||||
addOnce(missingLibs, l.substr(a, l.find('\'', a) - a) + ".framework");
|
||||
} else if (l.rfind(" \"", 0) == 0 && (a = l.find("\", referenced from:")) != std::string::npos) {
|
||||
// Apple ld: Undefined symbols for architecture arm64:\n "_name", referenced from:
|
||||
std::string name = l.substr(3, a - 3);
|
||||
if (g_target.darwin && !name.empty() && name[0] == '_') name.erase(0, 1);
|
||||
addOnce(missing, name);
|
||||
}
|
||||
}
|
||||
if (missing.empty() && missingLibs.empty()) {
|
||||
|
|
@ -323,7 +335,7 @@ int main(int argc, char **argv) {
|
|||
if (cmd == "run") {
|
||||
std::string line = shellQuote(out);
|
||||
for (const std::string &a : programArgs) line += " " + shellQuote(a);
|
||||
if (g_target.cross && g_target.windows) line = "wine " + line; // a Windows program on Linux
|
||||
line = runPrefix() + line; // wine / qemu for programs built for another system
|
||||
#ifdef _WIN32
|
||||
line = "\"" + line + "\""; // cmd /c strips one pair of outer quotes
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "target.h"
|
||||
|
||||
#include <llvm/Support/TargetSelect.h>
|
||||
#include <llvm/TargetParser/Host.h>
|
||||
|
||||
#include <cstdio>
|
||||
|
|
@ -21,27 +22,72 @@ static bool hostIsWindows() {
|
|||
#endif
|
||||
}
|
||||
|
||||
// The Finch-generated code needs these LLVM backends (the same C functions LLVM's own tools call).
|
||||
extern "C" {
|
||||
#ifdef FINCH_TARGET_X86
|
||||
void LLVMInitializeX86TargetInfo();
|
||||
void LLVMInitializeX86Target();
|
||||
void LLVMInitializeX86TargetMC();
|
||||
void LLVMInitializeX86AsmPrinter();
|
||||
#endif
|
||||
#ifdef FINCH_TARGET_AARCH64
|
||||
void LLVMInitializeAArch64TargetInfo();
|
||||
void LLVMInitializeAArch64Target();
|
||||
void LLVMInitializeAArch64TargetMC();
|
||||
void LLVMInitializeAArch64AsmPrinter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void initTargets() {
|
||||
#ifdef FINCH_TARGET_X86
|
||||
LLVMInitializeX86TargetInfo();
|
||||
LLVMInitializeX86Target();
|
||||
LLVMInitializeX86TargetMC();
|
||||
LLVMInitializeX86AsmPrinter();
|
||||
#endif
|
||||
#ifdef FINCH_TARGET_AARCH64
|
||||
LLVMInitializeAArch64TargetInfo();
|
||||
LLVMInitializeAArch64Target();
|
||||
LLVMInitializeAArch64TargetMC();
|
||||
LLVMInitializeAArch64AsmPrinter();
|
||||
#endif
|
||||
llvm::InitializeNativeTarget();
|
||||
llvm::InitializeNativeTargetAsmPrinter();
|
||||
}
|
||||
|
||||
void setTarget(const std::string &name) {
|
||||
std::string host = llvm::sys::getDefaultTargetTriple();
|
||||
llvm::Triple hostTriple(llvm::Triple::normalize(host));
|
||||
std::string t = name;
|
||||
if (name.empty()) t = host;
|
||||
else if (name == "windows") t = hostIsWindows() ? host : "x86_64-w64-mingw32";
|
||||
else if (name == "linux") t = "x86_64-unknown-linux-gnu";
|
||||
else if (name == "arm64" || name == "aarch64" || name == "linux-arm64") t = "aarch64-unknown-linux-gnu";
|
||||
else if (name == "macos") t = hostTriple.isAArch64() ? "arm64-apple-macosx14.0.0" : "x86_64-apple-macosx14.0.0";
|
||||
g_target.triple = llvm::Triple(llvm::Triple::normalize(t));
|
||||
g_target.windows = g_target.triple.isOSWindows();
|
||||
g_target.msvc = g_target.triple.isWindowsMSVCEnvironment();
|
||||
g_target.cross = llvm::Triple(llvm::Triple::normalize(host)).getOS() != g_target.triple.getOS();
|
||||
g_target.darwin = g_target.triple.isOSDarwin();
|
||||
g_target.cross = hostTriple.getOS() != g_target.triple.getOS() || hostTriple.getArch() != g_target.triple.getArch();
|
||||
g_target.exe = g_target.windows ? ".exe" : "";
|
||||
|
||||
// FINCH_CC chooses the C compiler for any target; CC only when not cross-compiling.
|
||||
if (const char *fc = std::getenv("FINCH_CC")) g_target.cc = fc;
|
||||
else if (!g_target.cross && std::getenv("CC")) g_target.cc = std::getenv("CC");
|
||||
else if (g_target.cross && g_target.windows) g_target.cc = "x86_64-w64-mingw32-gcc";
|
||||
else if (g_target.cross && g_target.triple.isAArch64() && g_target.triple.isOSLinux()) g_target.cc = "aarch64-linux-gnu-gcc";
|
||||
else if (g_target.cross) g_target.cc = "clang --target=" + g_target.triple.str();
|
||||
else if (hostIsWindows()) g_target.cc = "clang";
|
||||
else g_target.cc = "cc";
|
||||
}
|
||||
|
||||
std::string runPrefix() {
|
||||
if (!g_target.cross) return "";
|
||||
if (g_target.windows && !hostIsWindows()) return "wine ";
|
||||
if (g_target.triple.isAArch64() && g_target.triple.isOSLinux()) return "qemu-aarch64 ";
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string shellQuote(const std::string &s) {
|
||||
#ifdef _WIN32
|
||||
// cmd.exe: double quotes; a " inside a file name is impossible on Windows
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
struct TargetInfo {
|
||||
llvm::Triple triple;
|
||||
bool darwin = false; // macOS
|
||||
bool windows = false; // Windows (MSVC or MinGW): Microsoft x64 calling convention, .exe
|
||||
bool msvc = false; // the MSVC environment (link with clang + the Windows SDK)
|
||||
bool cross = false; // compiling for another system than the one we run on
|
||||
|
|
@ -17,9 +18,15 @@ struct TargetInfo {
|
|||
|
||||
inline TargetInfo g_target;
|
||||
|
||||
// "windows" / "linux" / a full triple; "" = the system finch runs on
|
||||
// "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = the system finch runs on
|
||||
void setTarget(const std::string &name);
|
||||
|
||||
// Register the LLVM backends Finch can generate code for (x86-64, ARM64).
|
||||
void initTargets();
|
||||
|
||||
// How to run a program built for g_target on this machine ("" = directly, "wine", "qemu-aarch64").
|
||||
std::string runPrefix();
|
||||
|
||||
// Quote one argument for the shell that runs our commands (sh on Unix, cmd.exe on Windows).
|
||||
std::string shellQuote(const std::string &s);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue