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
46
.github/workflows/ci.yml
vendored
46
.github/workflows/ci.yml
vendored
|
|
@ -11,7 +11,14 @@ env:
|
|||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { os: ubuntu-24.04, arch: x64 }
|
||||
- { os: ubuntu-24.04-arm, arch: arm64 }
|
||||
name: linux-${{ matrix.arch }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install LLVM 21, libclang, tools
|
||||
|
|
@ -28,11 +35,48 @@ jobs:
|
|||
- name: Tests
|
||||
run: tests/run.sh
|
||||
- name: Tests under valgrind
|
||||
if: matrix.arch == 'x64'
|
||||
run: MEMCHECK=1 tests/run.sh
|
||||
- name: Self-hosting
|
||||
run: tests/boot.sh
|
||||
- name: Language server
|
||||
run: python3 tests/lsp_test.py
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r examples dist/finch/
|
||||
tar -czf finch-linux-${{ matrix.arch }}.tar.gz -C dist finch
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finch-linux-${{ matrix.arch }}
|
||||
path: finch-linux-${{ matrix.arch }}.tar.gz
|
||||
|
||||
macos:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install LLVM 21
|
||||
run: |
|
||||
brew install llvm@21 ninja
|
||||
echo "$(brew --prefix llvm@21)/bin" >> $GITHUB_PATH
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR="$(brew --prefix llvm@21)/lib/cmake/llvm"
|
||||
ninja -C build
|
||||
build/finch version
|
||||
- name: Tests
|
||||
run: CC=/usr/bin/clang tests/run.sh
|
||||
- name: Self-hosting
|
||||
run: CC=/usr/bin/clang tests/boot.sh
|
||||
- name: Language server
|
||||
run: python3 tests/lsp_test.py
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir -p dist/finch && cp build/finch README.md LICENSE dist/finch/ && cp -r examples dist/finch/
|
||||
tar -czf finch-macos-arm64.tar.gz -C dist finch
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finch-macos-arm64
|
||||
path: finch-macos-arm64.tar.gz
|
||||
|
||||
vscode:
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
|
|||
|
|
@ -67,9 +67,24 @@ endif()
|
|||
|
||||
# Link the one shared libLLVM when available: libclang uses it too, and two copies
|
||||
# of LLVM in one process crash on exit.
|
||||
# Finch generates code for x86-64 and ARM64; use whichever of them this LLVM was built with.
|
||||
set(FINCH_BACKENDS)
|
||||
foreach(t X86 AArch64)
|
||||
if(t IN_LIST LLVM_TARGETS_TO_BUILD)
|
||||
list(APPEND FINCH_BACKENDS ${t})
|
||||
string(TOUPPER ${t} T)
|
||||
target_compile_definitions(finch PRIVATE FINCH_TARGET_${T})
|
||||
endif()
|
||||
endforeach()
|
||||
message(STATUS "Finch backends: ${FINCH_BACKENDS}")
|
||||
|
||||
if(TARGET LLVM)
|
||||
set(LLVM_LIBS LLVM)
|
||||
else()
|
||||
llvm_map_components_to_libnames(LLVM_LIBS core support passes native)
|
||||
set(FINCH_COMPONENTS core support passes native)
|
||||
foreach(t ${FINCH_BACKENDS})
|
||||
list(APPEND FINCH_COMPONENTS ${t}codegen ${t}asmparser ${t}desc ${t}info)
|
||||
endforeach()
|
||||
llvm_map_components_to_libnames(LLVM_LIBS ${FINCH_COMPONENTS})
|
||||
endif()
|
||||
target_link_libraries(finch PRIVATE ${LLVM_LIBS} ${LIBCLANG})
|
||||
|
|
|
|||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ for f in tests/run/*.fch; do
|
|||
# (checked in the test and in the modules it imports)
|
||||
files="$f"
|
||||
for m in $(sed -n 's/^import \([a-z_]*\)$/\1/p' "$f"); do files="$files tests/run/$m.fch"; done
|
||||
if grep -qE '^import "|^link |\b(u8|u16|u32|u64|i8|i16|i32|f32|f64)\b|defer |0x' $files; then
|
||||
if grep -qE '^import "|^link |defer |0x' $files || grep -qwE 'u8|u16|u32|u64|i8|i16|i32|f32|f64' $files; then
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
|
|
|||
58
tests/cross.sh
Executable file
58
tests/cross.sh
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
#!/usr/bin/env bash
|
||||
# Builds every test for another system and runs it there through an emulator:
|
||||
# tests/cross.sh windows x86_64-w64-mingw32, run with Wine
|
||||
# tests/cross.sh arm64 aarch64 Linux, run with qemu-aarch64
|
||||
# e.g. nix-shell -p pkgsCross.mingwW64.buildPackages.gcc wine64 --run 'tests/cross.sh windows'
|
||||
# nix-shell -p pkgsCross.aarch64-multiplatform.buildPackages.gcc qemu \
|
||||
# --run 'FINCH_CC=aarch64-unknown-linux-gnu-gcc tests/cross.sh arm64'
|
||||
# (On a real Windows/ARM64/macOS machine, use tests/run.sh with that system's finch.)
|
||||
set -u
|
||||
cd "$(dirname "$0")"
|
||||
target=${1:?usage: tests/cross.sh windows|arm64}
|
||||
FINCH=${FINCH:-../build/finch}
|
||||
export WINEDEBUG=-all
|
||||
case $target in
|
||||
windows) runner=wine; ext=.exe ;;
|
||||
arm64) runner=qemu-aarch64; ext= ;;
|
||||
*) echo "unknown target $target"; exit 2 ;;
|
||||
esac
|
||||
pass=0; failed=0
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
run_exe() { # program, stdin -> output without Windows' \r
|
||||
$runner "$1" < "$2" 2>&1 | tr -d '\r'
|
||||
}
|
||||
|
||||
for f in run/*.fch; do
|
||||
grep -q "^fn main" "$f" || continue
|
||||
input="${f%.fch}.in"
|
||||
[ -f "$input" ] || input=/dev/null
|
||||
exe="$tmp/$(basename "${f%.fch}")$ext"
|
||||
if ! "$FINCH" build "$f" --target "$target" -o "$exe" > "$tmp/err" 2>&1; then
|
||||
failed=$((failed + 1)); echo "FAIL (build) $f"; head -5 "$tmp/err"; continue
|
||||
fi
|
||||
got=$(run_exe "$exe" "$input")
|
||||
if [ "$got" == "$(tr -d '\r' < "${f%.fch}.out")" ]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1)); echo "FAIL $f"; diff <(echo "$got") "${f%.fch}.out" | head -10
|
||||
fi
|
||||
done
|
||||
|
||||
for f in fail/*.fch; do
|
||||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||
exe="$tmp/$(basename "${f%.fch}")$ext"
|
||||
if "$FINCH" build "$f" --target "$target" -o "$exe" > "$tmp/out" 2>&1; then
|
||||
got=$(run_exe "$exe" /dev/null)
|
||||
else
|
||||
got=$(cat "$tmp/out")
|
||||
fi
|
||||
if [[ "$got" == *"$expect"* ]]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1)); echo "FAIL $f (wanted: $expect)"; echo "$got" | head -5
|
||||
fi
|
||||
done
|
||||
echo "$target ($runner): $pass passed, $failed failed"
|
||||
[ $failed -eq 0 ]
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cross-compiles every test for Windows (x86_64-w64-mingw32) and runs it under Wine.
|
||||
# Needs x86_64-w64-mingw32-gcc and wine, e.g.:
|
||||
# nix-shell -p pkgsCross.mingwW64.buildPackages.gcc wine64 --run tests/windows.sh
|
||||
# (On a real Windows machine, use tests/run.sh with finch.exe instead.)
|
||||
set -u
|
||||
cd "$(dirname "$0")"
|
||||
FINCH=${FINCH:-../build/finch}
|
||||
export WINEDEBUG=-all
|
||||
pass=0; failed=0
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
run_exe() { # exe, stdin -> output without Windows' \r
|
||||
wine "$1" < "$2" 2>&1 | tr -d '\r'
|
||||
}
|
||||
|
||||
for f in run/*.fch; do
|
||||
grep -q "^fn main" "$f" || continue
|
||||
input="${f%.fch}.in"
|
||||
[ -f "$input" ] || input=/dev/null
|
||||
exe="$tmp/$(basename "${f%.fch}").exe"
|
||||
if ! "$FINCH" build "$f" --target windows -o "$exe" > "$tmp/err" 2>&1; then
|
||||
failed=$((failed + 1)); echo "FAIL (build) $f"; head -5 "$tmp/err"; continue
|
||||
fi
|
||||
got=$(run_exe "$exe" "$input")
|
||||
if [ "$got" == "$(cat "${f%.fch}.out")" ]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1)); echo "FAIL $f"; diff <(echo "$got") "${f%.fch}.out" | head -10
|
||||
fi
|
||||
done
|
||||
|
||||
for f in fail/*.fch; do
|
||||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||
exe="$tmp/$(basename "${f%.fch}").exe"
|
||||
if got=$("$FINCH" build "$f" --target windows -o "$exe" 2>&1); then
|
||||
got=$(run_exe "$exe" /dev/null); status=$?
|
||||
status=$( [ -n "$got" ] && echo 1 || echo 0 )
|
||||
else
|
||||
status=1
|
||||
fi
|
||||
if [[ "$got" == *"$expect"* ]]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
failed=$((failed + 1)); echo "FAIL $f (wanted: $expect)"; echo "$got" | head -5
|
||||
fi
|
||||
done
|
||||
echo "windows (wine): $pass passed, $failed failed"
|
||||
[ $failed -eq 0 ]
|
||||
Loading…
Reference in a new issue