mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 16:54:25 +00:00
Windows support (wip): target layer, Microsoft x64 ABI, portable runtime, CI
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8497251dc5
commit
84cbbb3e16
18 changed files with 481 additions and 94 deletions
79
.github/workflows/ci.yml
vendored
Normal file
79
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, "ci/**"]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
LLVM_VERSION: 21.1.8
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install LLVM 21, libclang, tools
|
||||
run: |
|
||||
wget -q https://apt.llvm.org/llvm.sh
|
||||
sudo bash llvm.sh 21
|
||||
sudo apt-get install -y llvm-21-dev libclang-21-dev ninja-build valgrind
|
||||
echo "/usr/lib/llvm-21/bin" >> $GITHUB_PATH
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DLLVM_DIR=/usr/lib/llvm-21/lib/cmake/llvm -DCMAKE_BUILD_TYPE=Release
|
||||
ninja -C build
|
||||
build/finch version
|
||||
- name: Tests
|
||||
run: tests/run.sh
|
||||
- name: Tests under valgrind
|
||||
run: MEMCHECK=1 tests/run.sh
|
||||
- name: Self-hosting
|
||||
run: tests/boot.sh
|
||||
|
||||
windows:
|
||||
runs-on: windows-2022
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Cache LLVM
|
||||
id: cache-llvm
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:/llvm
|
||||
key: llvm-${{ env.LLVM_VERSION }}-x86_64-pc-windows-msvc
|
||||
- name: Download LLVM ${{ env.LLVM_VERSION }}
|
||||
if: steps.cache-llvm.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
curl -sSL -o llvm.tar.xz "https://github.com/llvm/llvm-project/releases/download/llvmorg-$LLVM_VERSION/clang+llvm-$LLVM_VERSION-x86_64-pc-windows-msvc.tar.xz"
|
||||
mkdir -p /c/llvm
|
||||
tar -xf llvm.tar.xz -C /c/llvm --strip-components=1
|
||||
rm llvm.tar.xz
|
||||
- name: Build finch.exe
|
||||
run: |
|
||||
echo "C:/llvm/bin" >> $GITHUB_PATH
|
||||
export PATH="/c/llvm/bin:$PATH"
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_DIR=C:/llvm/lib/cmake/llvm \
|
||||
-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl
|
||||
ninja -C build
|
||||
cp /c/llvm/bin/libclang.dll build/
|
||||
build/finch.exe version
|
||||
- name: Tests
|
||||
run: |
|
||||
export PATH="/c/llvm/bin:$PATH"
|
||||
tests/run.sh
|
||||
- name: Package
|
||||
run: |
|
||||
mkdir -p dist/finch
|
||||
cp build/finch.exe build/libclang.dll dist/finch/
|
||||
cp README.md LICENSE dist/finch/
|
||||
cp -r examples dist/finch/
|
||||
cd dist && 7z a -tzip finch-windows-x64.zip finch > /dev/null
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: finch-windows-x64
|
||||
path: dist/finch-windows-x64.zip
|
||||
|
|
@ -17,13 +17,24 @@ add_executable(finch
|
|||
src/codegen_builtins.cpp
|
||||
src/abi.cpp
|
||||
src/cimport.cpp
|
||||
src/target.cpp
|
||||
)
|
||||
|
||||
# The runtime (runtime/finch_rt.c) is embedded into the compiler as a string;
|
||||
# finch compiles it once into ~/.cache/finch and links it into every program.
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/runtime/finch_rt.c)
|
||||
# (written as several raw string pieces: MSVC limits one literal to 16 KB)
|
||||
file(READ ${CMAKE_SOURCE_DIR}/runtime/finch_rt.c FINCH_RT_SOURCE)
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/rt_source.inc "static const char *kRuntimeSource = R\"FINCHRT(${FINCH_RT_SOURCE})FINCHRT\";\n")
|
||||
string(LENGTH "${FINCH_RT_SOURCE}" FINCH_RT_LENGTH)
|
||||
set(FINCH_RT_INC "static const char *kRuntimeSource =\n")
|
||||
set(FINCH_RT_POS 0)
|
||||
while(FINCH_RT_POS LESS FINCH_RT_LENGTH)
|
||||
string(SUBSTRING "${FINCH_RT_SOURCE}" ${FINCH_RT_POS} 8000 FINCH_RT_PIECE)
|
||||
string(APPEND FINCH_RT_INC "R\"FINCHRT(${FINCH_RT_PIECE})FINCHRT\"\n")
|
||||
math(EXPR FINCH_RT_POS "${FINCH_RT_POS} + 8000")
|
||||
endwhile()
|
||||
string(APPEND FINCH_RT_INC ";\n")
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/rt_source.inc "${FINCH_RT_INC}")
|
||||
target_include_directories(finch PRIVATE ${CMAKE_BINARY_DIR})
|
||||
|
||||
# libclang reads C headers for `import "stdio.h"`
|
||||
|
|
@ -35,7 +46,11 @@ endif()
|
|||
|
||||
target_include_directories(finch SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS} ${LIBCLANG_INCLUDE})
|
||||
target_compile_definitions(finch PRIVATE ${LLVM_DEFINITIONS} FINCH_VERSION="${PROJECT_VERSION}")
|
||||
target_compile_options(finch PRIVATE -Wall -Wextra)
|
||||
if(MSVC)
|
||||
target_compile_options(finch PRIVATE /W3 /utf-8 /EHsc /wd4244 /wd4267 /wd4146 /wd4624)
|
||||
else()
|
||||
target_compile_options(finch PRIVATE -Wall -Wextra)
|
||||
endif()
|
||||
|
||||
# Link the one shared libLLVM when available: libclang uses it too, and two copies
|
||||
# of LLVM in one process crash on exit.
|
||||
|
|
|
|||
24
boot/gen.fch
24
boot/gen.fch
|
|
@ -506,7 +506,18 @@ fn emitPrint(ptr[Gen] g, str v, ast.Type t, bool quoted) {
|
|||
printf(g, "%.*s", "i32 " + n + ", ptr " + p)
|
||||
}
|
||||
} else if k == "ptr" {
|
||||
printf(g, "%p", "ptr " + v)
|
||||
isNull := op(g, "icmp eq ptr " + v + ", null")
|
||||
nul := newLabel(g, "ptr.null")
|
||||
at := newLabel(g, "ptr.addr")
|
||||
done := newLabel(g, "ptr.done")
|
||||
condBr(g, isNull, nul, at)
|
||||
label(g, nul)
|
||||
printf(g, "null", "")
|
||||
br(g, done)
|
||||
label(g, at)
|
||||
printf(g, "0x%llx", "i64 " + op(g, "ptrtoint ptr " + v + " to i64"))
|
||||
br(g, done)
|
||||
label(g, done)
|
||||
} else if k == "null" {
|
||||
printf(g, "null", "")
|
||||
} else {
|
||||
|
|
@ -618,7 +629,7 @@ fn findFn(ptr[Gen] g, str module, str name) -> int {
|
|||
|
||||
fn isBuiltin(str n) -> bool {
|
||||
names := ["print", "addr", "str", "int", "char", "float", "bool", "read_file", "write_file", "file_exists",
|
||||
"shell", "exit", "input", "new", "free", "ptr"]
|
||||
"shell", "exit", "input", "new", "free", "ptr", "delete_file"]
|
||||
return names.contains(n)
|
||||
}
|
||||
|
||||
|
|
@ -1646,6 +1657,14 @@ fn call(ptr[Gen] g, ast.Node c) -> Val {
|
|||
r := op(g, "call i32 @finch_write_file(ptr " + path + ", ptr " + text + ")")
|
||||
return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool"))
|
||||
}
|
||||
if n == "delete_file" {
|
||||
checkArgs(g, c, 0, 1, n)
|
||||
p := strArg(g, c.kids[0], "the file name")
|
||||
heap := strOut(g)
|
||||
emit(g, "call void @finch_str_copy(ptr " + heap + ", ptr " + spill(g, p) + ")")
|
||||
r := op(g, "call i32 @finch_delete_file(ptr " + heap + ")")
|
||||
return Val(v: op(g, "icmp ne i32 " + r + ", 0"), ty: T("bool"))
|
||||
}
|
||||
if n == "shell" {
|
||||
checkArgs(g, c, 0, 1, n)
|
||||
cmd := strArg(g, c.kids[0], "the command")
|
||||
|
|
@ -2122,6 +2141,7 @@ fn runtimeDecls() -> str {
|
|||
"declare void @finch_read_file(ptr, ptr, ptr, i64)",
|
||||
"declare i32 @finch_write_file(ptr, ptr)",
|
||||
"declare i64 @finch_shell(ptr)",
|
||||
"declare i32 @finch_delete_file(ptr)",
|
||||
"declare i32 @printf(ptr, ...)",
|
||||
"declare i32 @fflush(ptr)",
|
||||
"declare void @exit(i32) noreturn",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
|
|
@ -379,17 +381,25 @@ void finch_args(FArr *out, int argc, char **argv) {
|
|||
void finch_input(FStr *out, const FStr *prompt) {
|
||||
if (prompt->len) fwrite(prompt->ptr, 1, (size_t)prompt->len, stdout);
|
||||
fflush(stdout);
|
||||
char *line = NULL;
|
||||
size_t size = 0;
|
||||
ssize_t n = getline(&line, &size, stdin);
|
||||
if (n < 0) {
|
||||
// read one line of any length (getline isn't available everywhere)
|
||||
size_t size = 128, n = 0;
|
||||
char *line = finch_alloc((int64_t)size);
|
||||
int c, got = 0;
|
||||
while ((c = fgetc(stdin)) != EOF) {
|
||||
got = 1;
|
||||
if (c == '\n') break;
|
||||
if (n + 1 >= size) line = must(realloc(line, size *= 2));
|
||||
line[n++] = (char)c;
|
||||
}
|
||||
if (!got) {
|
||||
free(line);
|
||||
str_static(out, empty);
|
||||
return;
|
||||
}
|
||||
while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) line[--n] = 0;
|
||||
while (n > 0 && line[n - 1] == '\r') n--;
|
||||
line[n] = 0;
|
||||
out->ptr = line;
|
||||
out->len = n;
|
||||
out->len = (int64_t)n;
|
||||
out->cap = (int64_t)size;
|
||||
}
|
||||
|
||||
|
|
@ -423,11 +433,20 @@ void finch_read_file(FStr *out, const FStr *path, const char *file, int64_t line
|
|||
int64_t finch_shell(const FStr *cmd) {
|
||||
fflush(stdout);
|
||||
int status = system(cmd->ptr ? cmd->ptr : "");
|
||||
#ifdef _WIN32
|
||||
return status; // Windows gives the exit code directly
|
||||
#else
|
||||
if (status == -1) return -1;
|
||||
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
||||
return 128 + (WIFSIGNALED(status) ? WTERMSIG(status) : 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
int finch_delete_file(const FStr *path) { return remove(path->ptr ? path->ptr : "") == 0; }
|
||||
|
||||
// stdin/stdout/stderr are macros on some systems (Windows), so Finch asks for them here
|
||||
void *finch_std_stream(int i) { return i == 0 ? (void *)stdin : i == 1 ? (void *)stdout : (void *)stderr; }
|
||||
|
||||
int finch_write_file(const FStr *path, const FStr *text) {
|
||||
FILE *f = fopen(path->ptr, "wb");
|
||||
if (!f) return 0;
|
||||
|
|
|
|||
26
src/abi.cpp
26
src/abi.cpp
|
|
@ -3,6 +3,7 @@
|
|||
// either INTEGER (general registers) or SSE (xmm registers); bigger ones go through memory.
|
||||
|
||||
#include "codegen_impl.h"
|
||||
#include "target.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
|
|
@ -44,6 +45,18 @@ 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.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;
|
||||
|
|
@ -96,6 +109,7 @@ static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> &pa
|
|||
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();
|
||||
|
|
@ -108,7 +122,7 @@ 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 (ni <= intRegs && ns <= sseRegs) {
|
||||
if (g_target.windows || (ni <= intRegs && ns <= sseRegs)) {
|
||||
intRegs -= ni;
|
||||
sseRegs -= ns;
|
||||
for (llvm::Type *p : a.parts) types.push_back(p);
|
||||
|
|
@ -116,7 +130,7 @@ static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> &pa
|
|||
a.kind = AbiArg::Memory; // out of registers: the whole struct goes on the stack
|
||||
}
|
||||
}
|
||||
if (a.kind == AbiArg::Memory) types.push_back(cg.b.getPtrTy());
|
||||
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--;
|
||||
|
|
@ -152,6 +166,8 @@ static AttributeList attrsFor(Codegen &cg, const Plan &pl, const FType &ret, con
|
|||
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) {
|
||||
idx += a.parts.size();
|
||||
} else {
|
||||
|
|
@ -221,8 +237,8 @@ Value_ Codegen::emitCCall(const CFunc &f, Function *fn, std::vector<Value_> args
|
|||
const AbiArg &a = pl.params[i];
|
||||
if (ps[i].kind == FType::Str) {
|
||||
ll.push_back(cstr(v.v));
|
||||
} else if (a.kind == AbiArg::Memory) {
|
||||
Value *s = tmpOf(ty(ps[i]));
|
||||
} else if (a.kind == AbiArg::Memory || a.kind == AbiArg::Indirect) {
|
||||
Value *s = tmpOf(ty(ps[i])); // a copy the callee may change
|
||||
cast<AllocaInst>(s)->setAlignment(Align(16));
|
||||
b.CreateStore(v.v, s);
|
||||
ll.push_back(s);
|
||||
|
|
@ -278,7 +294,7 @@ Function *Codegen::cThunk(Fn &fn, Pos p) {
|
|||
std::vector<Value *> args;
|
||||
for (size_t i = 0; i < fn.params.size(); i++) {
|
||||
const AbiArg &a = pl.params[i];
|
||||
if (a.kind == AbiArg::Memory) {
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "cimport.h"
|
||||
#include "error.h"
|
||||
#include "target.h"
|
||||
|
||||
#include <clang-c/Index.h>
|
||||
|
||||
|
|
@ -7,20 +8,20 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
|
||||
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() {
|
||||
const char *cc = std::getenv("CC");
|
||||
std::string cmd = std::string(cc ? cc : "cc") + " -E -v -x c /dev/null 2>&1";
|
||||
std::string output;
|
||||
capture(g_target.cc + " -E -v -x c " + nullDevice(), output);
|
||||
std::vector<std::string> dirs;
|
||||
FILE *p = popen(cmd.c_str(), "r");
|
||||
if (!p) return dirs;
|
||||
char line[4096];
|
||||
std::stringstream lines(output);
|
||||
bool inList = false;
|
||||
while (std::fgets(line, sizeof line, p)) {
|
||||
std::string l = line;
|
||||
for (std::string l; std::getline(lines, l);) {
|
||||
if (!l.empty() && l.back() == '\r') l.pop_back();
|
||||
l += "\n";
|
||||
if (l.rfind("#include <...> search starts here:", 0) == 0) { inList = true; continue; }
|
||||
if (l.rfind("End of search list.", 0) == 0) break;
|
||||
if (!inList) continue;
|
||||
|
|
@ -31,7 +32,6 @@ std::vector<std::string> systemIncludeDirs() {
|
|||
if (d.size() > 20 && d.compare(d.size() - 20, 20, " (framework directory)") == 0) continue;
|
||||
dirs.push_back(d);
|
||||
}
|
||||
pclose(p);
|
||||
return dirs;
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +318,9 @@ CImports importHeaders(const std::vector<Import> &imports, const std::vector<std
|
|||
for (const Import &im : imports) any |= im.isC;
|
||||
if (!any) return out;
|
||||
|
||||
std::vector<std::string> args = {"-x", "c", "-std=gnu11"};
|
||||
// parse for the target system: sizes (long is 32 bits on Windows) and headers differ
|
||||
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);
|
||||
std::vector<const char *> argv;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ Codegen::Codegen(LLVMContext &c, const std::vector<Program> &ps, const CImports
|
|||
}
|
||||
diUnit = di->createCompileUnit(dwarf::DW_LANG_C, diFiles[0], "finch " FINCH_VERSION, false, "", 0);
|
||||
mod->addModuleFlag(Module::Warning, "Debug Info Version", DEBUG_METADATA_VERSION);
|
||||
mod->addModuleFlag(Module::Warning, "Dwarf Version", 5);
|
||||
if (tm.getTargetTriple().isWindowsMSVCEnvironment())
|
||||
mod->addModuleFlag(Module::Warning, "CodeView", 1); // what Visual Studio and WinDbg read
|
||||
else
|
||||
mod->addModuleFlag(Module::Warning, "Dwarf Version", 5);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -657,6 +660,12 @@ Value_ Codegen::expr(const Expr &e) {
|
|||
}
|
||||
if (modules[curModule].imports.count(v.name))
|
||||
failAt(e.pos.file, e.pos.line, e.pos.col, "'" + v.name + "' is a module; use something from it, like " + v.name + ".name(...)");
|
||||
// C's standard streams are macros on some systems; the runtime knows them everywhere
|
||||
if (!cimports.globals.count(v.name) && (v.name == "stdin" || v.name == "stdout" || v.name == "stderr") &&
|
||||
cimports.fns.count("fprintf")) {
|
||||
int which = v.name == "stdin" ? 0 : v.name == "stdout" ? 1 : 2;
|
||||
return {b.CreateCall(rt("finch_std_stream"), {b.getInt32(which)}), FType::Ptr};
|
||||
}
|
||||
}
|
||||
Place pl = place(e);
|
||||
return {b.CreateLoad(ty(pl.type), pl.addr, v.name), pl.type};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace finch {
|
|||
|
||||
bool Codegen::isBuiltin(const std::string &n) {
|
||||
static const std::set<std::string> names = {"print", "addr", "input", "new", "free", "read_file", "write_file",
|
||||
"file_exists", "exit", "shell"};
|
||||
"file_exists", "delete_file", "exit", "shell"};
|
||||
FType t;
|
||||
return names.count(n) || typeFromName(n, t);
|
||||
}
|
||||
|
|
@ -92,6 +92,8 @@ Function *Codegen::rt(const std::string &name) {
|
|||
{"finch_read_file", {V, {P, P, P, I}}},
|
||||
{"finch_write_file", {I32, {P, P}}},
|
||||
{"finch_shell", {I, {P}}},
|
||||
{"finch_delete_file", {I32, {P}}},
|
||||
{"finch_std_stream", {P, {I32}}},
|
||||
};
|
||||
auto it = sigs.find(name);
|
||||
if (it == sigs.end()) {
|
||||
|
|
@ -216,6 +218,15 @@ Value_ Codegen::call(const CallExpr &c) {
|
|||
b.CreateCall(libc("exit", b.getVoidTy(), {b.getInt32Ty()}), {code});
|
||||
return {nullptr, FType::Void};
|
||||
}
|
||||
if (n == "delete_file") {
|
||||
Value_ path = coerce(expr(one("path")), FType::Str, c.args[0]->pos, "the file name");
|
||||
Value *ss = tmpOf(ty(FType::Str));
|
||||
b.CreateCall(rt("finch_str_copy"), {ss, tmp(path.v)}); // NUL-terminated copy
|
||||
Value *r = b.CreateCall(rt("finch_delete_file"), {ss});
|
||||
dropAt(ss, FType::Str);
|
||||
release(path);
|
||||
return {b.CreateICmpNE(r, b.getInt32(0)), FType::Bool};
|
||||
}
|
||||
if (n == "read_file" || n == "file_exists") {
|
||||
Value_ path = coerce(expr(one("path")), FType::Str, c.args[0]->pos, "the file name");
|
||||
Value *pp = tmp(path.v);
|
||||
|
|
|
|||
|
|
@ -101,9 +101,11 @@ struct ModuleScope {
|
|||
std::set<std::string> imports; // Finch modules visible here
|
||||
};
|
||||
|
||||
// How a C struct travels through registers (System V x86-64).
|
||||
// How a C struct travels: System V x86-64 (Linux) or Microsoft x64 (Windows).
|
||||
struct AbiArg {
|
||||
enum Kind { Direct, Expand, Memory } kind = Direct;
|
||||
// Direct: as is. Expand: split into register-sized parts. Memory: on the stack (byval), or
|
||||
// returned through a hidden pointer (sret). Indirect (Windows): a pointer to a copy the caller made.
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -387,7 +387,18 @@ void Codegen::emitPrint(Value *v, const FType &t, bool quoted) {
|
|||
else if (t.kind == FType::Bool) printf_("%s", {b.CreateSelect(v, b.CreateGlobalString("true", "true"), b.CreateGlobalString("false", "false"))});
|
||||
else if (t.kind == FType::Str)
|
||||
printf_(quoted ? "\"%.*s\"" : "%.*s", {b.CreateTrunc(b.CreateExtractValue(v, 1), b.getInt32Ty()), cstr(v)});
|
||||
else if (t.isPtr()) printf_("%p", {v});
|
||||
else if (t.isPtr()) { // the same on every system: null or 0x...
|
||||
Value *isNull = b.CreateIsNull(v);
|
||||
BasicBlock *nul = newBlock("ptr.null"), *addr = newBlock("ptr.addr"), *done = newBlock("ptr.done");
|
||||
b.CreateCondBr(isNull, nul, addr);
|
||||
b.SetInsertPoint(nul);
|
||||
printf_("null", {});
|
||||
b.CreateBr(done);
|
||||
b.SetInsertPoint(addr);
|
||||
printf_("0x%llx", {b.CreatePtrToInt(v, b.getInt64Ty())});
|
||||
b.CreateBr(done);
|
||||
b.SetInsertPoint(done);
|
||||
}
|
||||
else if (t.kind == FType::Null) printf_("null", {});
|
||||
else b.CreateCall(printFn(t), {tmp(v)});
|
||||
}
|
||||
|
|
|
|||
118
src/main.cpp
118
src/main.cpp
|
|
@ -3,6 +3,7 @@
|
|||
#include "error.h"
|
||||
#include "lexer.h"
|
||||
#include "parser.h"
|
||||
#include "target.h"
|
||||
|
||||
#include <llvm/Config/llvm-config.h>
|
||||
#include <llvm/IR/LegacyPassManager.h>
|
||||
|
|
@ -23,7 +24,6 @@
|
|||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/wait.h>
|
||||
#include <vector>
|
||||
|
||||
#include "rt_source.inc" // kRuntimeSource: runtime/finch_rt.c, embedded at build time
|
||||
|
|
@ -39,9 +39,10 @@ static void usage() {
|
|||
" finch ir <file.fch> show the generated LLVM IR\n"
|
||||
" finch version show the version\n"
|
||||
"\n"
|
||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||
" -g add debug info (for gdb / lldb)\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n");
|
||||
" -l <lib> link a C library, same as link \"lib\" in the file\n"
|
||||
" --target <name> build for another system: windows, linux, or an LLVM triple\n"
|
||||
" -g add debug info (gdb / lldb / Visual Studio)\n"
|
||||
" -O0 skip optimizations (to read the raw IR)\n");
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
|
|
@ -54,11 +55,6 @@ static bool readFile(const std::string &path, std::string &out) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static std::string dirOf(const std::string &path) {
|
||||
size_t slash = path.find_last_of('/');
|
||||
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||
}
|
||||
|
||||
// ---------- loading the program and its modules ----------
|
||||
|
||||
struct Loader {
|
||||
|
|
@ -95,7 +91,7 @@ struct Loader {
|
|||
if (sys::fs::exists(here)) return here;
|
||||
if (const char *fp = std::getenv("FINCH_PATH")) {
|
||||
std::stringstream ss(fp);
|
||||
for (std::string d; std::getline(ss, d, ':');)
|
||||
for (std::string d; std::getline(ss, d, pathListSeparator());)
|
||||
if (!d.empty() && sys::fs::exists(d + "/" + name + ".fch")) return d + "/" + name + ".fch";
|
||||
}
|
||||
return here;
|
||||
|
|
@ -118,11 +114,16 @@ static void optimize(Module &mod, TargetMachine *tm) {
|
|||
pb.buildPerModuleDefaultPipeline(OptimizationLevel::O2).run(mod, mam);
|
||||
}
|
||||
|
||||
static TargetMachine *hostMachine() {
|
||||
static TargetMachine *targetMachine() {
|
||||
InitializeNativeTarget();
|
||||
InitializeNativeTargetAsmPrinter();
|
||||
|
||||
Triple triple(sys::getDefaultTargetTriple());
|
||||
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());
|
||||
std::exit(1);
|
||||
}
|
||||
std::string err;
|
||||
const Target *target = TargetRegistry::lookupTarget(triple, err);
|
||||
if (!target) {
|
||||
|
|
@ -149,30 +150,11 @@ static void emitObject(Module &mod, TargetMachine *tm, const std::string &path)
|
|||
|
||||
// ---------- linking ----------
|
||||
|
||||
// Run a shell command; returns its exit status, everything it printed goes to `output`.
|
||||
static int capture(const std::string &cmd, std::string &output) {
|
||||
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!p) return -1;
|
||||
char buf[4096];
|
||||
size_t n;
|
||||
while ((n = std::fread(buf, 1, sizeof buf, p)) > 0) output.append(buf, n);
|
||||
int status = pclose(p);
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
||||
}
|
||||
|
||||
static std::string ccName() {
|
||||
const char *cc = std::getenv("CC");
|
||||
return cc ? cc : "cc";
|
||||
}
|
||||
|
||||
// The runtime is compiled once per version and kept in ~/.cache/finch.
|
||||
static std::string runtimeObject() {
|
||||
std::string dir;
|
||||
if (const char *x = std::getenv("XDG_CACHE_HOME")) dir = std::string(x) + "/finch";
|
||||
else if (const char *h = std::getenv("HOME")) dir = std::string(h) + "/.cache/finch";
|
||||
else dir = "/tmp/finch-cache";
|
||||
size_t hash = std::hash<std::string>()(std::string(kRuntimeSource) + FINCH_VERSION + ccName());
|
||||
std::string obj = dir + "/rt-" + std::to_string(hash) + ".o";
|
||||
std::string dir = cacheDir();
|
||||
size_t hash = std::hash<std::string>()(std::string(kRuntimeSource) + FINCH_VERSION + g_target.cc + g_target.triple.str());
|
||||
std::string obj = dir + (g_target.windows && !g_target.cross ? "\\" : "/") + "rt-" + std::to_string(hash) + ".o";
|
||||
if (sys::fs::exists(obj)) return obj;
|
||||
|
||||
sys::fs::create_directories(dir);
|
||||
|
|
@ -182,10 +164,12 @@ static std::string runtimeObject() {
|
|||
out << kRuntimeSource;
|
||||
}
|
||||
std::string output;
|
||||
int status = capture(ccName() + " -O2 -fPIC -c '" + src + "' -o '" + part + "'", output);
|
||||
std::string pic = g_target.windows ? "" : " -fPIC";
|
||||
int status = capture(g_target.cc + " -O2" + pic + " -c " + shellQuote(src) + " -o " + shellQuote(part), output);
|
||||
std::remove(src.c_str());
|
||||
if (status != 0) {
|
||||
std::fprintf(stderr, "%serror: couldn't build the Finch runtime (is a C compiler installed?)\n", output.c_str());
|
||||
std::fprintf(stderr, "%serror: couldn't build the Finch runtime with '%s' (is a C compiler installed?)\n", output.c_str(),
|
||||
g_target.cc.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
sys::fs::rename(part, obj);
|
||||
|
|
@ -195,11 +179,11 @@ 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;
|
||||
if (capture("pkg-config --libs '" + lib + "'", out) == 0) {
|
||||
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;
|
||||
}
|
||||
return " '-l" + lib + "'";
|
||||
return " " + shellQuote("-l" + lib);
|
||||
}
|
||||
|
||||
// "GLFW/glfw3.h" -> "glfw": a decent first guess for the library's name.
|
||||
|
|
@ -226,6 +210,15 @@ static std::string guessLib(const std::string &header) {
|
|||
} else if ((a = l.find("undefined symbol: ")) != std::string::npos) {
|
||||
a += 18;
|
||||
addOnce(missing, l.substr(a, l.find_first_of(" \n", a) - a));
|
||||
} else if ((a = l.find("unresolved external symbol ")) != std::string::npos) { // MSVC link.exe
|
||||
a += 27;
|
||||
addOnce(missing, l.substr(a, l.find_first_of(" \n\r", a) - a));
|
||||
} else if ((a = l.find("cannot open file '")) != std::string::npos || // MSVC: LNK1104 cannot open file 'x.lib'
|
||||
(a = l.find("could not open '")) != std::string::npos) { // lld-link
|
||||
a = l.find('\'', a) + 1;
|
||||
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));
|
||||
|
|
@ -272,19 +265,21 @@ static bool endsWith(const std::string &s, const char *x) {
|
|||
|
||||
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
|
||||
const CImports &c) {
|
||||
std::string cmd = ccName() + " '" + obj + "' '" + runtimeObject() + "' -o '" + exe + "' -lm";
|
||||
std::string cmd = g_target.cc + " " + shellQuote(obj) + " " + shellQuote(runtimeObject()) + " -o " + shellQuote(exe);
|
||||
if (!g_target.msvc) cmd += " -lm";
|
||||
std::vector<std::string> temps;
|
||||
for (const std::string &l : libs) {
|
||||
if (endsWith(l, ".c")) { // compile the user's C file
|
||||
std::string o = obj + "." + std::to_string(temps.size()) + ".o", output;
|
||||
if (capture(ccName() + " -O2 -fPIC -c '" + l + "' -o '" + o + "'", output) != 0) {
|
||||
std::string pic = g_target.windows ? "" : " -fPIC";
|
||||
if (capture(g_target.cc + " -O2" + pic + " -c " + shellQuote(l) + " -o " + shellQuote(o), output) != 0) {
|
||||
std::fprintf(stderr, "%serror: couldn't compile %s\n", output.c_str(), l.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
temps.push_back(o);
|
||||
cmd += " '" + o + "'";
|
||||
cmd += " " + shellQuote(o);
|
||||
} else if (endsWith(l, ".o") || endsWith(l, ".a")) {
|
||||
cmd += " '" + l + "'";
|
||||
cmd += " " + shellQuote(l);
|
||||
} else {
|
||||
cmd += libFlags(l);
|
||||
}
|
||||
|
|
@ -294,7 +289,7 @@ static void link(const std::string &obj, const std::string &exe, const std::vect
|
|||
std::remove(obj.c_str());
|
||||
for (const std::string &t : temps) std::remove(t.c_str());
|
||||
if (status == 127) {
|
||||
std::fprintf(stderr, "error: no C compiler found to link with (install gcc or clang, or set CC)\n");
|
||||
std::fprintf(stderr, "error: no C compiler found to link with ('%s'; install one, or set FINCH_CC)\n", g_target.cc.c_str());
|
||||
std::exit(1);
|
||||
}
|
||||
if (status != 0) linkFailed(output, c);
|
||||
|
|
@ -302,18 +297,12 @@ static void link(const std::string &obj, const std::string &exe, const std::vect
|
|||
}
|
||||
|
||||
static std::string stem(const std::string &path) {
|
||||
size_t slash = path.find_last_of('/');
|
||||
size_t slash = path.find_last_of("/\\");
|
||||
std::string name = slash == std::string::npos ? path : path.substr(slash + 1);
|
||||
size_t dot = name.rfind('.');
|
||||
return dot == std::string::npos ? name : name.substr(0, dot);
|
||||
}
|
||||
|
||||
static std::string shellQuote(const std::string &s) {
|
||||
std::string out = "'";
|
||||
for (char ch : s) out += ch == '\'' ? std::string("'\\''") : std::string(1, ch);
|
||||
return out + "'";
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc == 2 && (std::string(argv[1]) == "version" || std::string(argv[1]) == "--version")) {
|
||||
std::printf("finch %s (LLVM %s)\n", FINCH_VERSION, LLVM_VERSION_STRING);
|
||||
|
|
@ -324,11 +313,13 @@ int main(int argc, char **argv) {
|
|||
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
|
||||
std::string out = stem(file);
|
||||
std::vector<std::string> libs, programArgs;
|
||||
bool optimizeCode = true, debug = false;
|
||||
std::string targetName;
|
||||
bool optimizeCode = true, debug = false, outGiven = false;
|
||||
for (int i = 3; i < argc; i++) {
|
||||
std::string a = argv[i];
|
||||
if (cmd == "run" && !programArgs.empty()) programArgs.push_back(a);
|
||||
else if (a == "-o" && i + 1 < argc && cmd == "build") out = argv[++i];
|
||||
else if (a == "-o" && i + 1 < argc && cmd == "build") out = argv[++i], outGiven = true;
|
||||
else if (a == "--target" && i + 1 < argc) targetName = argv[++i];
|
||||
else if (a == "-O0") optimizeCode = false;
|
||||
else if (a == "-g") debug = true;
|
||||
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
|
||||
|
|
@ -337,6 +328,9 @@ int main(int argc, char **argv) {
|
|||
else usage();
|
||||
}
|
||||
|
||||
setTarget(targetName);
|
||||
if (!outGiven) out += g_target.exe;
|
||||
|
||||
Loader loader;
|
||||
loader.load(file, "", Pos{});
|
||||
std::vector<Import> allImports;
|
||||
|
|
@ -345,7 +339,7 @@ int main(int argc, char **argv) {
|
|||
allImports.insert(allImports.end(), p.imports.begin(), p.imports.end());
|
||||
for (const Link &l : p.links) {
|
||||
bool file = endsWith(l.lib, ".c") || endsWith(l.lib, ".o") || endsWith(l.lib, ".a");
|
||||
std::string path = file && l.lib[0] != '/' ? dirOf(p.path) + "/" + l.lib : l.lib;
|
||||
std::string path = file && !sys::path::is_absolute(l.lib) ? dirOf(p.path) + "/" + l.lib : l.lib;
|
||||
if (file && !sys::fs::exists(path)) failAt(l.pos.file, l.pos.line, l.pos.col, "can't find the file '" + path + "'");
|
||||
libs.push_back(path);
|
||||
}
|
||||
|
|
@ -353,7 +347,7 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
CImports c = importHeaders(allImports, dirs);
|
||||
LLVMContext ctx;
|
||||
TargetMachine *tm = hostMachine();
|
||||
TargetMachine *tm = targetMachine();
|
||||
std::unique_ptr<Module> mod = generate(loader.progs, c, ctx, *tm, debug);
|
||||
if (optimizeCode) optimize(*mod, tm);
|
||||
|
||||
|
|
@ -365,7 +359,7 @@ int main(int argc, char **argv) {
|
|||
std::string obj = out + ".o";
|
||||
if (cmd == "run") {
|
||||
SmallString<128> tmp;
|
||||
sys::fs::createTemporaryFile("finch", "", tmp);
|
||||
sys::fs::createTemporaryFile("finch", g_target.windows ? "exe" : "", tmp);
|
||||
out = tmp.str().str();
|
||||
obj = out + ".o";
|
||||
}
|
||||
|
|
@ -375,13 +369,15 @@ int main(int argc, char **argv) {
|
|||
if (cmd == "run") {
|
||||
std::string line = shellQuote(out);
|
||||
for (const std::string &a : programArgs) line += " " + shellQuote(a);
|
||||
int status = std::system(line.c_str());
|
||||
if (g_target.cross && g_target.windows) line = "wine " + line; // a Windows program on Linux
|
||||
#ifdef _WIN32
|
||||
line = "\"" + line + "\""; // cmd /c strips one pair of outer quotes
|
||||
#endif
|
||||
std::string crash;
|
||||
int status = runCommand(line, &crash);
|
||||
std::remove(out.c_str());
|
||||
if (WIFSIGNALED(status)) {
|
||||
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));
|
||||
return 128 + WTERMSIG(status);
|
||||
}
|
||||
return WIFEXITED(status) ? WEXITSTATUS(status) : 1;
|
||||
if (!crash.empty()) std::fprintf(stderr, "\nthe program crashed: %s\n", crash.c_str());
|
||||
return status;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
112
src/target.cpp
Normal file
112
src/target.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#include "target.h"
|
||||
|
||||
#include <llvm/TargetParser/Host.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define popen _popen
|
||||
#define pclose _pclose
|
||||
#else
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
|
||||
static bool hostIsWindows() {
|
||||
#ifdef _WIN32
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void setTarget(const std::string &name) {
|
||||
std::string host = llvm::sys::getDefaultTargetTriple();
|
||||
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";
|
||||
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.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.cc = "clang --target=" + g_target.triple.str();
|
||||
else if (hostIsWindows()) g_target.cc = "clang";
|
||||
else g_target.cc = "cc";
|
||||
}
|
||||
|
||||
std::string shellQuote(const std::string &s) {
|
||||
#ifdef _WIN32
|
||||
// cmd.exe: double quotes; a " inside a file name is impossible on Windows
|
||||
return "\"" + s + "\"";
|
||||
#else
|
||||
std::string out = "'";
|
||||
for (char ch : s) out += ch == '\'' ? std::string("'\\''") : std::string(1, ch);
|
||||
return out + "'";
|
||||
#endif
|
||||
}
|
||||
|
||||
static int exitStatus(int status) {
|
||||
#ifdef _WIN32
|
||||
return status;
|
||||
#else
|
||||
if (status == -1) return -1;
|
||||
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
int capture(const std::string &cmd, std::string &output) {
|
||||
FILE *p = popen((cmd + " 2>&1").c_str(), "r");
|
||||
if (!p) return -1;
|
||||
char buf[4096];
|
||||
size_t n;
|
||||
while ((n = std::fread(buf, 1, sizeof buf, p)) > 0) output.append(buf, n);
|
||||
return exitStatus(pclose(p));
|
||||
}
|
||||
|
||||
int runCommand(const std::string &cmd, std::string *crash) {
|
||||
std::fflush(stdout);
|
||||
int status = std::system(cmd.c_str());
|
||||
#ifndef _WIN32
|
||||
if (crash && status != -1 && WIFSIGNALED(status)) *crash = strsignal(WTERMSIG(status));
|
||||
#else
|
||||
// a crashed Windows program exits with an NTSTATUS code like 0xC0000005
|
||||
if (crash && (unsigned)status >= 0xC0000000u) {
|
||||
char buf[64];
|
||||
std::snprintf(buf, sizeof buf, "exception 0x%08X%s", (unsigned)status,
|
||||
(unsigned)status == 0xC0000005u ? " (access violation)" : (unsigned)status == 0xC00000FDu ? " (stack overflow)" : "");
|
||||
*crash = buf;
|
||||
}
|
||||
#endif
|
||||
return exitStatus(status);
|
||||
}
|
||||
|
||||
const char *nullDevice() { return hostIsWindows() ? "NUL" : "/dev/null"; }
|
||||
|
||||
std::string cacheDir() {
|
||||
#ifdef _WIN32
|
||||
if (const char *l = std::getenv("LOCALAPPDATA")) return std::string(l) + "\\finch";
|
||||
if (const char *t = std::getenv("TEMP")) return std::string(t) + "\\finch";
|
||||
return "finch-cache";
|
||||
#else
|
||||
if (const char *x = std::getenv("XDG_CACHE_HOME")) return std::string(x) + "/finch";
|
||||
if (const char *h = std::getenv("HOME")) return std::string(h) + "/.cache/finch";
|
||||
return "/tmp/finch-cache";
|
||||
#endif
|
||||
}
|
||||
|
||||
char pathListSeparator() { return hostIsWindows() ? ';' : ':'; }
|
||||
|
||||
std::string dirOf(const std::string &path) {
|
||||
size_t slash = hostIsWindows() ? path.find_last_of("/\\") : path.find_last_of('/');
|
||||
return slash == std::string::npos ? "." : path.substr(0, slash);
|
||||
}
|
||||
42
src/target.h
Normal file
42
src/target.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
// The system Finch compiles for, and everything that depends on it:
|
||||
// the C compiler used to link, how the shell quotes, file names, the C ABI.
|
||||
|
||||
#include <llvm/TargetParser/Triple.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
struct TargetInfo {
|
||||
llvm::Triple triple;
|
||||
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
|
||||
std::string cc; // the C compiler that links programs and builds the runtime
|
||||
std::string exe; // ".exe" or ""
|
||||
};
|
||||
|
||||
inline TargetInfo g_target;
|
||||
|
||||
// "windows" / "linux" / a full triple; "" = the system finch runs on
|
||||
void setTarget(const std::string &name);
|
||||
|
||||
// Quote one argument for the shell that runs our commands (sh on Unix, cmd.exe on Windows).
|
||||
std::string shellQuote(const std::string &s);
|
||||
|
||||
// Run a command; its exit status, and everything it printed in `output`.
|
||||
int capture(const std::string &cmd, std::string &output);
|
||||
|
||||
// Run a command with its output going to the terminal; its exit status (128+n for a signal).
|
||||
int runCommand(const std::string &cmd, std::string *crash = nullptr);
|
||||
|
||||
// The null device: /dev/null or NUL.
|
||||
const char *nullDevice();
|
||||
|
||||
// The folder for cached files (~/.cache/finch, %LOCALAPPDATA%\finch).
|
||||
std::string cacheDir();
|
||||
|
||||
// Separator of FINCH_PATH entries (':' or ';').
|
||||
char pathListSeparator();
|
||||
|
||||
// The folder part of a path (both / and \ on Windows).
|
||||
std::string dirOf(const std::string &path);
|
||||
|
|
@ -17,7 +17,7 @@ for f in run/*.fch; do
|
|||
want="${f%.fch}.out"
|
||||
input="${f%.fch}.in"
|
||||
[ -f "$input" ] || input=/dev/null
|
||||
got=$("$FINCH" run "$f" < "$input" 2>&1)
|
||||
got=$("$FINCH" run "$f" < "$input" 2>&1 | tr -d '\r') # Windows ends lines with \r\n
|
||||
if [ "$got" != "$(cat "$want")" ]; then
|
||||
failed=$((failed + 1))
|
||||
echo "FAIL $f"
|
||||
|
|
@ -41,6 +41,7 @@ for f in fail/*.fch; do
|
|||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||
got=$("$FINCH" run "$f" < /dev/null 2>&1)
|
||||
status=$?
|
||||
got=$(echo "$got" | tr -d '\r')
|
||||
if [ $status -ne 0 ] && [[ "$got" == *"$expect"* ]]; then
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
fn main() {
|
||||
path := "/tmp/finch_test_file.txt"
|
||||
path := "finch_test_file.txt"
|
||||
ok := write_file(path, "line one\nline two\n")
|
||||
print(ok, file_exists(path))
|
||||
text := read_file(path)
|
||||
lines := text.trim().split("\n")
|
||||
print(lines, lines.len)
|
||||
print(file_exists("/definitely/not/here"))
|
||||
print(delete_file(path), file_exists(path))
|
||||
print(file_exists("definitely/not/here"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
true true
|
||||
["line one", "line two"] 2
|
||||
true false
|
||||
false
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
42
|
||||
2 1
|
||||
99
|
||||
true true (nil)
|
||||
true true null
|
||||
|
|
|
|||
50
tests/windows.sh
Executable file
50
tests/windows.sh
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
#!/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