mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
--target: windows, linux, arm64, macos or a triple, in the compiler written in Finch
Targets, the target's C compiler (FINCH_CC) for linking, C import for the target (--target to libclang, its headers), the AAPCS64 and Microsoft x64 ways of passing structs by value (and the System V one), dllexport for libraries on Windows, .exe names, and running the program through wine or qemu-aarch64. std/ gets time_windows.fch and proc_windows.fch: a module has a version per system (name_windows.fch, name_macos.fch, name_linux.fch) that wins over name.fch. stdin, stdout and stderr work where C makes them macros. Run for real: all 27 programs under tests/run and tests/new pass on ARM64 Linux under qemu and on Windows under Wine (MinGW), structs by value and callbacks included. macOS is only checked as far as clang accepts the IR; there is no emulator for it here.
This commit is contained in:
parent
8c3b5b15b9
commit
750eca3e85
10 changed files with 426 additions and 52 deletions
|
|
@ -99,10 +99,10 @@ fn fixed(ast.Type elem, int count) -> ast.Type {
|
||||||
// ---------- the system's header folders ----------
|
// ---------- the system's header folders ----------
|
||||||
|
|
||||||
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
// Ask the system C compiler where it looks for headers, so we find the same stdio.h it would.
|
||||||
fn systemIncludeArgs() -> []str {
|
fn systemIncludeArgs(str cc) -> []str {
|
||||||
[]str dirs
|
[]str dirs
|
||||||
tmp := "/tmp/finch_cc_dirs.txt"
|
tmp := "/tmp/finch_cc_dirs.txt"
|
||||||
shell("cc -E -v -x c /dev/null 2> '" + tmp + "' > /dev/null")
|
shell(cc + " -E -v -x c /dev/null 2> '" + tmp + "' > /dev/null")
|
||||||
if !file_exists(tmp) {
|
if !file_exists(tmp) {
|
||||||
return dirs
|
return dirs
|
||||||
}
|
}
|
||||||
|
|
@ -504,16 +504,23 @@ fn visit(ptr[Importer] imp, CXCursor c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read every header in `headers`; `dirs` are searched first for "local.h" headers.
|
// Read every header in `headers`; `dirs` are searched first for "local.h" headers.
|
||||||
fn importHeaders([]Header headers, []str dirs) -> CImports! {
|
// `target` is the LLVM triple the program is built for ("" = this system): sizes and headers depend on it.
|
||||||
|
fn importHeaders([]Header headers, []str dirs, str target, bool windows, str ccFor) -> CImports! {
|
||||||
imp := Importer()
|
imp := Importer()
|
||||||
if headers.len == 0 {
|
if headers.len == 0 {
|
||||||
return imp.out
|
return imp.out
|
||||||
}
|
}
|
||||||
[]str args = ["-x", "c", "-std=gnu11"]
|
[]str args = ["-x", "c", "-std=gnu11"]
|
||||||
|
if target.len > 0 {
|
||||||
|
args.push("--target=" + target)
|
||||||
|
}
|
||||||
|
if windows {
|
||||||
|
args.push("-D_USE_MATH_DEFINES") // M_PI and friends, as on Linux
|
||||||
|
}
|
||||||
for d in dirs {
|
for d in dirs {
|
||||||
args.push("-I" + d)
|
args.push("-I" + d)
|
||||||
}
|
}
|
||||||
for d in systemIncludeArgs() {
|
for d in systemIncludeArgs(ccFor) {
|
||||||
args.push(d)
|
args.push(d)
|
||||||
}
|
}
|
||||||
[]ptr[char] argv
|
[]ptr[char] argv
|
||||||
|
|
|
||||||
136
boot/gen.fch
136
boot/gen.fch
|
|
@ -5,6 +5,7 @@
|
||||||
// (so programs behave the same) but never frees them: it is the bootstrap compiler.
|
// (so programs behave the same) but never frees them: it is the bootstrap compiler.
|
||||||
import ast
|
import ast
|
||||||
import cimport
|
import cimport
|
||||||
|
import platform
|
||||||
|
|
||||||
struct Var {
|
struct Var {
|
||||||
str name
|
str name
|
||||||
|
|
@ -21,6 +22,7 @@ struct Options {
|
||||||
bool shared // finch build --shared
|
bool shared // finch build --shared
|
||||||
bool debug // -g
|
bool debug // -g
|
||||||
str cwd // the folder the compiler was started in (for the debug info)
|
str cwd // the folder the compiler was started in (for the debug info)
|
||||||
|
platform.Target target
|
||||||
}
|
}
|
||||||
|
|
||||||
// A generic struct or function, kept as written until something asks for a version of it.
|
// A generic struct or function, kept as written until something asks for a version of it.
|
||||||
|
|
@ -146,6 +148,7 @@ struct Gen {
|
||||||
cimport.CImports ci
|
cimport.CImports ci
|
||||||
bool shared // building a library (finch build --shared): no main, its export fns are the way in
|
bool shared // building a library (finch build --shared): no main, its export fns are the way in
|
||||||
bool debug // -g: debug info (DWARF, as LLVM metadata)
|
bool debug // -g: debug info (DWARF, as LLVM metadata)
|
||||||
|
platform.Target tg // the system the program is for
|
||||||
str cwd
|
str cwd
|
||||||
[]str md // the metadata, numbered by position: !0 = md[0]
|
[]str md // the metadata, numbered by position: !0 = md[0]
|
||||||
map[str]int mdcache // metadata made already: types by key, locations by line:column
|
map[str]int mdcache // metadata made already: types by key, locations by line:column
|
||||||
|
|
@ -3164,6 +3167,16 @@ fn ref(ptr[Gen] g, ast.Node e, bool write) -> Ref {
|
||||||
}
|
}
|
||||||
return Ref(isPlace: false, val: Val(v: str(c.i), ty: T("int"), lit: true))
|
return Ref(isPlace: false, val: Val(v: str(c.i), ty: T("int"), lit: true))
|
||||||
}
|
}
|
||||||
|
// C's standard streams are macros on some systems; the runtime knows them everywhere
|
||||||
|
if id < 0 && !g.ci.globals.has(e.name) && g.ci.fns.has("fprintf") && (e.name == "stdin" || e.name == "stdout" || e.name == "stderr") {
|
||||||
|
which := "2"
|
||||||
|
if e.name == "stdin" {
|
||||||
|
which = "0"
|
||||||
|
} else if e.name == "stdout" {
|
||||||
|
which = "1"
|
||||||
|
}
|
||||||
|
return Ref(isPlace: false, val: Val(v: op(g, "call ptr @finch_std_stream(i32 " + which + ")"), ty: T("ptr")))
|
||||||
|
}
|
||||||
if id < 0 && g.ci.globals.has(e.name) {
|
if id < 0 && g.ci.globals.has(e.name) {
|
||||||
gt := resolve(g, g.ci.globals[e.name], e)
|
gt := resolve(g, g.ci.globals[e.name], e)
|
||||||
if !g.cdone.contains("g:" + e.name) {
|
if !g.cdone.contains("g:" + e.name) {
|
||||||
|
|
@ -3589,6 +3602,49 @@ fn leavesOf(ptr[Gen] g, ast.Type t, int base) -> []Leaf {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AAPCS64 (ARM64 Linux, Apple): a group of one to four floats or doubles travels in vector registers;
|
||||||
|
// anything else up to 16 bytes in general registers; bigger is copied by the caller and passed by address
|
||||||
|
fn classifyArm64(ptr[Gen] g, ast.Type t, Abi first, int size, int align) -> Abi {
|
||||||
|
a := first
|
||||||
|
ls := leavesOf(g, t, 0)
|
||||||
|
hfa := ls.len > 0 && ls.len <= 4
|
||||||
|
for l in ls {
|
||||||
|
if !l.isFloat || l.isDouble != ls[0].isDouble {
|
||||||
|
hfa = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hfa {
|
||||||
|
ft := "float"
|
||||||
|
if ls[0].isDouble {
|
||||||
|
ft = "double"
|
||||||
|
}
|
||||||
|
a.kind = "expand"
|
||||||
|
a.parts.push("[" + str(ls.len) + " x " + ft + "]")
|
||||||
|
[]str elems
|
||||||
|
for l in ls {
|
||||||
|
elems.push(ft)
|
||||||
|
}
|
||||||
|
a.coerced = "{ " + elems.join(", ") + " }"
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
if size > 16 {
|
||||||
|
a.kind = "indirect"
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
a.kind = "expand"
|
||||||
|
if align >= 16 {
|
||||||
|
a.parts.push("i128")
|
||||||
|
a.coerced = "i128"
|
||||||
|
} else if size <= 8 {
|
||||||
|
a.parts.push("i64")
|
||||||
|
a.coerced = "i" + str(size * 8)
|
||||||
|
} else {
|
||||||
|
a.parts.push("[2 x i64]")
|
||||||
|
a.coerced = "[2 x i64]"
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
fn classify(ptr[Gen] g, ast.Type t) -> Abi {
|
fn classify(ptr[Gen] g, ast.Type t) -> Abi {
|
||||||
a := Abi(kind: "direct")
|
a := Abi(kind: "direct")
|
||||||
if t.kind != "struct" {
|
if t.kind != "struct" {
|
||||||
|
|
@ -3600,6 +3656,21 @@ fn classify(ptr[Gen] g, ast.Type t) -> Abi {
|
||||||
if sd.calign > 8 {
|
if sd.calign > 8 {
|
||||||
a.align = sd.calign
|
a.align = sd.calign
|
||||||
}
|
}
|
||||||
|
if g.tg.arm64 {
|
||||||
|
return classifyArm64(g, t, a, size, sd.calign)
|
||||||
|
}
|
||||||
|
if g.tg.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 = "expand"
|
||||||
|
a.parts.push("i" + str(size * 8))
|
||||||
|
a.coerced = a.parts[0]
|
||||||
|
} else {
|
||||||
|
a.kind = "indirect"
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
if size > 16 || size == 0 {
|
if size > 16 || size == 0 {
|
||||||
a.kind = "memory"
|
a.kind = "memory"
|
||||||
return a
|
return a
|
||||||
|
|
@ -3663,6 +3734,9 @@ fn makePlan(ptr[Gen] g, ast.Type ret, []ast.Type params) -> Plan {
|
||||||
if ret.kind != "void" {
|
if ret.kind != "void" {
|
||||||
pl.retLL = cLL(g, ret)
|
pl.retLL = cLL(g, ret)
|
||||||
}
|
}
|
||||||
|
if pl.ret.kind == "indirect" {
|
||||||
|
pl.ret.kind = "memory" // returned through a hidden pointer
|
||||||
|
}
|
||||||
if pl.ret.kind == "memory" {
|
if pl.ret.kind == "memory" {
|
||||||
pl.sret = true
|
pl.sret = true
|
||||||
pl.retLL = "void"
|
pl.retLL = "void"
|
||||||
|
|
@ -3682,7 +3756,8 @@ fn makePlan(ptr[Gen] g, ast.Type ret, []ast.Type params) -> Plan {
|
||||||
ns += 1
|
ns += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ni <= intRegs && ns <= sseRegs {
|
// only System V counts registers here; elsewhere the backend places each part
|
||||||
|
if g.tg.windows || g.tg.arm64 || (ni <= intRegs && ns <= sseRegs) {
|
||||||
intRegs -= ni
|
intRegs -= ni
|
||||||
sseRegs -= ns
|
sseRegs -= ns
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3754,7 +3829,7 @@ fn exportWrapper(ptr[Gen] g, FnSig sig) -> str {
|
||||||
params.push("i8 zeroext %e" + str(k))
|
params.push("i8 zeroext %e" + str(k))
|
||||||
args.push("i1 " + op(g, "icmp ne i8 %e" + str(k) + ", 0"))
|
args.push("i1 " + op(g, "icmp ne i8 %e" + str(k) + ", 0"))
|
||||||
} else {
|
} else {
|
||||||
params.push(cLL(g, t) + cExt(t) + " %e" + str(k))
|
params.push(cLL(g, t) + cExt(g, t) + " %e" + str(k))
|
||||||
args.push(llty(g, t) + " %e" + str(k))
|
args.push(llty(g, t) + " %e" + str(k))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3770,10 +3845,14 @@ fn exportWrapper(ptr[Gen] g, FnSig sig) -> str {
|
||||||
emit(g, "ret i8 " + op(g, "zext i1 " + op(g, text) + " to i8"))
|
emit(g, "ret i8 " + op(g, "zext i1 " + op(g, text) + " to i8"))
|
||||||
} else {
|
} else {
|
||||||
retLL = llty(g, sig.ret)
|
retLL = llty(g, sig.ret)
|
||||||
retExt = cExt(sig.ret)
|
retExt = cExt(g, sig.ret)
|
||||||
emit(g, "ret " + retLL + " " + op(g, text))
|
emit(g, "ret " + retLL + " " + op(g, text))
|
||||||
}
|
}
|
||||||
endHelper(g, s, "define" + retExt + " " + retLL + " @" + sig.name + "(" + params.join(", ") + ")")
|
dll := ""
|
||||||
|
if g.tg.windows {
|
||||||
|
dll = " dllexport"
|
||||||
|
}
|
||||||
|
endHelper(g, s, "define" + dll + retExt + " " + retLL + " @" + sig.name + "(" + params.join(", ") + ")")
|
||||||
return sig.name
|
return sig.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3814,8 +3893,12 @@ fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
||||||
hdr.push("ptr byval(" + llty(g, t) + ") align " + str(a.align) + " %c" + str(ai))
|
hdr.push("ptr byval(" + llty(g, t) + ") align " + str(a.align) + " %c" + str(ai))
|
||||||
args.push(llty(g, t) + " " + load(g, llty(g, t), "%c" + str(ai)))
|
args.push(llty(g, t) + " " + load(g, llty(g, t), "%c" + str(ai)))
|
||||||
ai += 1
|
ai += 1
|
||||||
|
} else if a.kind == "indirect" {
|
||||||
|
hdr.push("ptr %c" + str(ai))
|
||||||
|
args.push(llty(g, t) + " " + load(g, llty(g, t), "%c" + str(ai)))
|
||||||
|
ai += 1
|
||||||
} else if a.kind == "expand" {
|
} else if a.kind == "expand" {
|
||||||
slot := alloca(g, "[16 x i8]")
|
slot := alloca(g, "[32 x i8]")
|
||||||
for k in 0..a.parts.len {
|
for k in 0..a.parts.len {
|
||||||
hdr.push(a.parts[k] + " %c" + str(ai))
|
hdr.push(a.parts[k] + " %c" + str(ai))
|
||||||
store(g, a.parts[k], "%c" + str(ai), op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
store(g, a.parts[k], "%c" + str(ai), op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
||||||
|
|
@ -3823,7 +3906,7 @@ fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
||||||
}
|
}
|
||||||
args.push(llty(g, t) + " " + load(g, llty(g, t), slot))
|
args.push(llty(g, t) + " " + load(g, llty(g, t), slot))
|
||||||
} else {
|
} else {
|
||||||
hdr.push(cLL(g, t) + cExt(t) + " %c" + str(ai))
|
hdr.push(cLL(g, t) + cExt(g, t) + " %c" + str(ai))
|
||||||
args.push(llty(g, t) + " %c" + str(ai))
|
args.push(llty(g, t) + " %c" + str(ai))
|
||||||
ai += 1
|
ai += 1
|
||||||
}
|
}
|
||||||
|
|
@ -3837,11 +3920,11 @@ fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
||||||
store(g, llty(g, sig.ret), op(g, text), sretName)
|
store(g, llty(g, sig.ret), op(g, text), sretName)
|
||||||
emit(g, "ret void")
|
emit(g, "ret void")
|
||||||
} else if pl.ret.kind == "expand" {
|
} else if pl.ret.kind == "expand" {
|
||||||
slot := alloca(g, "[16 x i8]")
|
slot := alloca(g, "[32 x i8]")
|
||||||
store(g, llty(g, sig.ret), op(g, text), slot)
|
store(g, llty(g, sig.ret), op(g, text), slot)
|
||||||
emit(g, "ret " + pl.ret.coerced + " " + load(g, pl.ret.coerced, slot))
|
emit(g, "ret " + pl.ret.coerced + " " + load(g, pl.ret.coerced, slot))
|
||||||
} else {
|
} else {
|
||||||
retExt = cExt(sig.ret)
|
retExt = cExt(g, sig.ret)
|
||||||
emit(g, "ret " + cLL(g, sig.ret) + " " + op(g, text))
|
emit(g, "ret " + cLL(g, sig.ret) + " " + op(g, text))
|
||||||
}
|
}
|
||||||
endHelper(g, s, "define internal" + retExt + " " + pl.retLL + " " + name + "(" + hdr.join(", ") + ")")
|
endHelper(g, s, "define internal" + retExt + " " + pl.retLL + " " + name + "(" + hdr.join(", ") + ")")
|
||||||
|
|
@ -3880,15 +3963,18 @@ fn enumFrom(ptr[Gen] g, ast.Node c, int ei) -> Val {
|
||||||
// ---------- calling C ----------
|
// ---------- calling C ----------
|
||||||
|
|
||||||
// How C extends a number narrower than 32 bits: plain `char` is signed on x86.
|
// How C extends a number narrower than 32 bits: plain `char` is signed on x86.
|
||||||
fn cExt(ast.Type t) -> str {
|
fn cExt(ptr[Gen] g, ast.Type t) -> str {
|
||||||
if t.kind == "bool" || isUnsigned(t) {
|
if t.kind == "bool" || isUnsigned(t) {
|
||||||
if bitsOf(t) < 32 {
|
if bitsOf(t) < 32 {
|
||||||
return " zeroext"
|
return " zeroext"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isSigned(t) || t.kind == "char") && bitsOf(t) < 32 {
|
if (isSigned(t) || (t.kind == "char" && platform.charIsSigned(g.tg))) && bitsOf(t) < 32 {
|
||||||
return " signext"
|
return " signext"
|
||||||
}
|
}
|
||||||
|
if t.kind == "char" && bitsOf(t) < 32 {
|
||||||
|
return " zeroext"
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3939,8 +4025,13 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
||||||
spec := "ptr byval(" + llty(g, pt) + ") align " + str(a.align)
|
spec := "ptr byval(" + llty(g, pt) + ") align " + str(a.align)
|
||||||
ptys.push(spec)
|
ptys.push(spec)
|
||||||
args.push(spec + " " + slot)
|
args.push(spec + " " + slot)
|
||||||
|
} else if a.kind == "indirect" {
|
||||||
|
slot := alloca(g, llty(g, pt))
|
||||||
|
store(g, llty(g, pt), v.v, slot)
|
||||||
|
ptys.push("ptr")
|
||||||
|
args.push("ptr " + slot)
|
||||||
} else if a.kind == "expand" {
|
} else if a.kind == "expand" {
|
||||||
slot := alloca(g, "[16 x i8]")
|
slot := alloca(g, "[32 x i8]")
|
||||||
store(g, llty(g, pt), v.v, slot)
|
store(g, llty(g, pt), v.v, slot)
|
||||||
for k in 0..a.parts.len {
|
for k in 0..a.parts.len {
|
||||||
x := load(g, a.parts[k], op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
x := load(g, a.parts[k], op(g, "getelementptr inbounds i8, ptr " + slot + ", i64 " + str(8 * k)))
|
||||||
|
|
@ -3948,11 +4039,11 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
||||||
args.push(a.parts[k] + " " + x)
|
args.push(a.parts[k] + " " + x)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ptys.push(cLL(g, pt) + cExt(pt))
|
ptys.push(cLL(g, pt) + cExt(g, pt))
|
||||||
if pt.kind == "str" {
|
if pt.kind == "str" {
|
||||||
args.push("ptr " + cstr(g, v.v))
|
args.push("ptr " + cstr(g, v.v))
|
||||||
} else {
|
} else {
|
||||||
args.push(llty(g, pt) + cExt(pt) + " " + v.v)
|
args.push(llty(g, pt) + cExt(g, pt) + " " + v.v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
@ -3986,7 +4077,7 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
||||||
retLL := pl.retLL
|
retLL := pl.retLL
|
||||||
retExt := ""
|
retExt := ""
|
||||||
if pl.ret.kind == "direct" {
|
if pl.ret.kind == "direct" {
|
||||||
retExt = cExt(ret)
|
retExt = cExt(g, ret)
|
||||||
}
|
}
|
||||||
tail := ""
|
tail := ""
|
||||||
if f.variadic {
|
if f.variadic {
|
||||||
|
|
@ -4016,7 +4107,7 @@ fn callC(ptr[Gen] g, ast.Node c, cimport.CFunc f) -> Val {
|
||||||
}
|
}
|
||||||
r := op(g, text)
|
r := op(g, text)
|
||||||
if pl.ret.kind == "expand" {
|
if pl.ret.kind == "expand" {
|
||||||
slot := alloca(g, "[16 x i8]")
|
slot := alloca(g, "[32 x i8]")
|
||||||
store(g, pl.ret.coerced, r, slot)
|
store(g, pl.ret.coerced, r, slot)
|
||||||
return Val(v: load(g, llty(g, ret), slot), ty: ret)
|
return Val(v: load(g, llty(g, ret), slot), ty: ret)
|
||||||
}
|
}
|
||||||
|
|
@ -4796,6 +4887,7 @@ fn runtimeDecls() -> str {
|
||||||
"declare i32 @fflush(ptr)",
|
"declare i32 @fflush(ptr)",
|
||||||
"declare void @finch_str_from_c(ptr, ptr)",
|
"declare void @finch_str_from_c(ptr, ptr)",
|
||||||
"declare void @finch_on_fatal(ptr)",
|
"declare void @finch_on_fatal(ptr)",
|
||||||
|
"declare ptr @finch_std_stream(i32)",
|
||||||
"declare void @finch_str_from_bytes(ptr, ptr)",
|
"declare void @finch_str_from_bytes(ptr, ptr)",
|
||||||
"declare void @exit(i32) noreturn",
|
"declare void @exit(i32) noreturn",
|
||||||
"declare void @free(ptr)",
|
"declare void @free(ptr)",
|
||||||
|
|
@ -4804,7 +4896,11 @@ fn runtimeDecls() -> str {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assemble(ptr[Gen] g) -> str {
|
fn assemble(ptr[Gen] g) -> str {
|
||||||
text := "; generated by the Finch compiler written in Finch\n\n" + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n"
|
text := "; generated by the Finch compiler written in Finch\n\n"
|
||||||
|
if g.tg.cross {
|
||||||
|
text = text + "target triple = \"" + g.tg.triple + "\"\n\n"
|
||||||
|
}
|
||||||
|
text = text + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n"
|
||||||
if g.debug {
|
if g.debug {
|
||||||
text = text + "declare void @llvm.dbg.declare(metadata, metadata, metadata)\n\n"
|
text = text + "declare void @llvm.dbg.declare(metadata, metadata, metadata)\n\n"
|
||||||
}
|
}
|
||||||
|
|
@ -4817,7 +4913,7 @@ fn assemble(ptr[Gen] g) -> str {
|
||||||
|
|
||||||
fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt) -> str {
|
fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt) -> str {
|
||||||
shared := opt.shared
|
shared := opt.shared
|
||||||
gen := Gen(progs: progs, files: files, ci: ci, shared: opt.shared, debug: opt.debug, cwd: opt.cwd, dbgScope: -1)
|
gen := Gen(progs: progs, files: files, ci: ci, shared: opt.shared, debug: opt.debug, cwd: opt.cwd, tg: opt.target, dbgScope: -1)
|
||||||
g := addr(gen)
|
g := addr(gen)
|
||||||
if g.debug {
|
if g.debug {
|
||||||
// !0 the compile unit, !1 and !2 the module flags, then the source files
|
// !0 the compile unit, !1 and !2 the module flags, then the source files
|
||||||
|
|
@ -4852,7 +4948,11 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci, Options opt)
|
||||||
}
|
}
|
||||||
if shared {
|
if shared {
|
||||||
// finch_module_on_fatal(handler): the host's say on runtime errors (see runtime/finch_rt.c)
|
// finch_module_on_fatal(handler): the host's say on runtime errors (see runtime/finch_rt.c)
|
||||||
g.out.push("define void @finch_module_on_fatal(ptr %handler) {\nentry:\n call void @finch_on_fatal(ptr %handler)\n ret void\n}\n")
|
dll := ""
|
||||||
|
if g.tg.windows {
|
||||||
|
dll = " dllexport"
|
||||||
|
}
|
||||||
|
g.out.push("define" + dll + " void @finch_module_on_fatal(ptr %handler) {\nentry:\n call void @finch_on_fatal(ptr %handler)\n ret void\n}\n")
|
||||||
return assemble(g)
|
return assemble(g)
|
||||||
}
|
}
|
||||||
// the C entry point calls the Finch main
|
// the C entry point calls the Finch main
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import ast
|
||||||
import parser
|
import parser
|
||||||
import gen
|
import gen
|
||||||
import cimport
|
import cimport
|
||||||
|
import platform
|
||||||
import "stdlib.h"
|
import "stdlib.h"
|
||||||
|
|
||||||
fn dirOf(str path) -> str {
|
fn dirOf(str path) -> str {
|
||||||
|
|
@ -39,6 +40,7 @@ struct Loader {
|
||||||
[]ast.Program progs
|
[]ast.Program progs
|
||||||
[]str files
|
[]str files
|
||||||
[]str loaded
|
[]str loaded
|
||||||
|
str os // "windows", "macos" or "linux": a module may have a version for each (time_windows.fch)
|
||||||
str std // the standard library's folder, or ""
|
str std // the standard library's folder, or ""
|
||||||
[]str paths // FINCH_PATH: more folders to look for modules in
|
[]str paths // FINCH_PATH: more folders to look for modules in
|
||||||
}
|
}
|
||||||
|
|
@ -46,18 +48,29 @@ struct Loader {
|
||||||
// import json: the file next to the one that imports it, else the standard library's.
|
// import json: the file next to the one that imports it, else the standard library's.
|
||||||
// A program's own json.fch wins over the library's.
|
// A program's own json.fch wins over the library's.
|
||||||
fn findModule(ptr[Loader] l, str from, str name) -> str {
|
fn findModule(ptr[Loader] l, str from, str name) -> str {
|
||||||
|
// in each folder, the version for this system first: time_windows.fch, then time.fch
|
||||||
local := dirOf(from) + "/" + name + ".fch"
|
local := dirOf(from) + "/" + name + ".fch"
|
||||||
|
if file_exists(dirOf(from) + "/" + name + "_" + l.os + ".fch") {
|
||||||
|
return dirOf(from) + "/" + name + "_" + l.os + ".fch"
|
||||||
|
}
|
||||||
if file_exists(local) {
|
if file_exists(local) {
|
||||||
return local
|
return local
|
||||||
}
|
}
|
||||||
for d in l.paths {
|
for d in l.paths {
|
||||||
|
if file_exists(d + "/" + name + "_" + l.os + ".fch") {
|
||||||
|
return d + "/" + name + "_" + l.os + ".fch"
|
||||||
|
}
|
||||||
if file_exists(d + "/" + name + ".fch") {
|
if file_exists(d + "/" + name + ".fch") {
|
||||||
return d + "/" + name + ".fch"
|
return d + "/" + name + ".fch"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shared := l.std + "/" + name + ".fch"
|
if l.std.len > 0 {
|
||||||
if file_exists(shared) {
|
if file_exists(l.std + "/" + name + "_" + l.os + ".fch") {
|
||||||
return shared
|
return l.std + "/" + name + "_" + l.os + ".fch"
|
||||||
|
}
|
||||||
|
if file_exists(l.std + "/" + name + ".fch") {
|
||||||
|
return l.std + "/" + name + ".fch"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return local
|
return local
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +125,9 @@ fn usage() {
|
||||||
print(" -O0 no optimization (faster to build)")
|
print(" -O0 no optimization (faster to build)")
|
||||||
print(" -l <lib> link a library (the same as link \"lib\" in the program)")
|
print(" -l <lib> link a library (the same as link \"lib\" in the program)")
|
||||||
print(" -I <dir> look for imported C headers there too")
|
print(" -I <dir> look for imported C headers there too")
|
||||||
print("not in this compiler yet: -g, --target, lsp. They are in the C++ one (branch before-bootstrap).")
|
print(" -g debug info (for gdb and lldb; use -O0 with it)")
|
||||||
|
print(" --target <name> build for another system: windows, linux, arm64 (Linux), macos, or an LLVM triple")
|
||||||
|
print("not in this compiler yet: lsp. It is in the C++ one (branch before-bootstrap).")
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +152,7 @@ fn main([]str args) -> int {
|
||||||
outGiven := false
|
outGiven := false
|
||||||
shared := false
|
shared := false
|
||||||
debug := false
|
debug := false
|
||||||
|
targetName := ""
|
||||||
opt := "-O2"
|
opt := "-O2"
|
||||||
[]str extraLibs
|
[]str extraLibs
|
||||||
[]str extraDirs
|
[]str extraDirs
|
||||||
|
|
@ -164,9 +180,9 @@ fn main([]str args) -> int {
|
||||||
ai += 1
|
ai += 1
|
||||||
} else if a == "-g" {
|
} else if a == "-g" {
|
||||||
debug = true
|
debug = true
|
||||||
} else if a == "--target" {
|
} else if a == "--target" && ai + 1 < args.len {
|
||||||
print("error: '--target' is not in the compiler written in Finch yet; the C++ one has it (branch before-bootstrap)")
|
targetName = args[ai + 1]
|
||||||
return 1
|
ai += 1
|
||||||
} else if cmd == "run" {
|
} else if cmd == "run" {
|
||||||
progArgs.push(a)
|
progArgs.push(a)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -174,14 +190,28 @@ fn main([]str args) -> int {
|
||||||
}
|
}
|
||||||
ai += 1
|
ai += 1
|
||||||
}
|
}
|
||||||
|
tg := platform.select(targetName)
|
||||||
if cmd == "run" {
|
if cmd == "run" {
|
||||||
out = "/tmp/finch_run_" + stem(file)
|
out = "/tmp/finch_run_" + stem(file) + tg.exe
|
||||||
}
|
} else if shared && !outGiven {
|
||||||
if shared && !outGiven {
|
if tg.windows {
|
||||||
|
out = out + ".dll"
|
||||||
|
} else if tg.darwin {
|
||||||
|
out = "lib" + out + ".dylib"
|
||||||
|
} else {
|
||||||
out = "lib" + out + ".so"
|
out = "lib" + out + ".so"
|
||||||
}
|
}
|
||||||
|
} else if !outGiven {
|
||||||
|
out = out + tg.exe
|
||||||
|
}
|
||||||
|
|
||||||
loader := Loader()
|
loader := Loader()
|
||||||
|
loader.os = "linux"
|
||||||
|
if tg.windows {
|
||||||
|
loader.os = "windows"
|
||||||
|
} else if tg.darwin {
|
||||||
|
loader.os = "macos"
|
||||||
|
}
|
||||||
for cand in [dirOf(args[0]) + "/../std", dirOf(args[0]) + "/std", "std"] {
|
for cand in [dirOf(args[0]) + "/../std", dirOf(args[0]) + "/std", "std"] {
|
||||||
if loader.std.len == 0 && file_exists(cand + "/path.fch") {
|
if loader.std.len == 0 && file_exists(cand + "/path.fch") {
|
||||||
loader.std = cand
|
loader.std = cand
|
||||||
|
|
@ -218,11 +248,15 @@ fn main([]str args) -> int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ci := cimport.importHeaders(headers, dirs) or {
|
triple := ""
|
||||||
|
if tg.cross {
|
||||||
|
triple = tg.triple
|
||||||
|
}
|
||||||
|
ci := cimport.importHeaders(headers, dirs, triple, tg.windows, tg.cc) or {
|
||||||
print(err)
|
print(err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, cwd: getenv("PWD")))
|
ir := gen.generate(loader.progs, loader.files, ci, gen.Options(shared: shared, debug: debug, cwd: getenv("PWD"), target: tg))
|
||||||
if cmd == "ir" {
|
if cmd == "ir" {
|
||||||
print(ir)
|
print(ir)
|
||||||
return 0
|
return 0
|
||||||
|
|
@ -245,19 +279,37 @@ fn main([]str args) -> int {
|
||||||
}
|
}
|
||||||
llPath := out + ".ll"
|
llPath := out + ".ll"
|
||||||
write_file(llPath, ir)
|
write_file(llPath, ir)
|
||||||
sharedFlags := ""
|
// the IR is always compiled by clang (CC may be gcc, which can't read it); FINCH_CC picks the linker for another system
|
||||||
|
clang := getenv("FINCH_CC")
|
||||||
|
if clang.len == 0 || tg.cross {
|
||||||
|
clang = "clang"
|
||||||
|
}
|
||||||
|
extra := ""
|
||||||
if shared {
|
if shared {
|
||||||
sharedFlags = " -shared -fPIC"
|
extra = " -fPIC"
|
||||||
}
|
}
|
||||||
if debug {
|
if debug {
|
||||||
sharedFlags = sharedFlags + " -g" // or clang drops the debug info that is in the IR
|
extra = extra + " -g" // or clang drops the debug info that is in the IR
|
||||||
}
|
}
|
||||||
// the compiler of the IR is always a clang (CC may be gcc, which can't read it); FINCH_CC picks another clang
|
code := 0
|
||||||
cc := getenv("FINCH_CC")
|
if tg.cross {
|
||||||
if cc.len == 0 {
|
// an object for the target, then the target's own C compiler links it with the runtime
|
||||||
cc = "clang"
|
obj := out + ".o"
|
||||||
|
code = shell("clang --target=" + tg.triple + " " + opt + extra + " -w -c '" + llPath + "' -o '" + obj + "'")
|
||||||
|
if code == 0 {
|
||||||
|
linkShared := ""
|
||||||
|
if shared {
|
||||||
|
linkShared = " -shared"
|
||||||
|
}
|
||||||
|
code = shell(tg.cc + " '" + obj + "' '" + rt + "' -o '" + out + "' -lm" + linkShared + " " + libs.join(" "))
|
||||||
|
}
|
||||||
|
shell("rm -f '" + obj + "'")
|
||||||
|
} else {
|
||||||
|
if shared {
|
||||||
|
extra = extra + " -shared"
|
||||||
|
}
|
||||||
|
code = shell(clang + " " + opt + extra + " -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" "))
|
||||||
}
|
}
|
||||||
code := shell(cc + " " + opt + sharedFlags + " -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" "))
|
|
||||||
shell("rm -f '" + llPath + "'")
|
shell("rm -f '" + llPath + "'")
|
||||||
if code != 0 {
|
if code != 0 {
|
||||||
print("error: clang failed")
|
print("error: clang failed")
|
||||||
|
|
@ -268,7 +320,7 @@ fn main([]str args) -> int {
|
||||||
for a in progArgs {
|
for a in progArgs {
|
||||||
quoted.push(quote(a))
|
quoted.push(quote(a))
|
||||||
}
|
}
|
||||||
status := shell(quote(out) + " " + quoted.join(" "))
|
status := shell(tg.runner + quote(out) + " " + quoted.join(" "))
|
||||||
shell("rm -f " + quote(out))
|
shell("rm -f " + quote(out))
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
|
||||||
90
boot/platform.fch
Normal file
90
boot/platform.fch
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
// The system a program is built for, and what depends on it: the C compiler that links, the file
|
||||||
|
// name of the program, how it is run here, and (through the flags) the C calling convention.
|
||||||
|
// The same as src/target.cpp.
|
||||||
|
import "stdlib.h"
|
||||||
|
|
||||||
|
struct Target {
|
||||||
|
str triple // "" = the system the compiler runs on
|
||||||
|
bool windows // Microsoft x64 calling convention, .exe
|
||||||
|
bool msvc
|
||||||
|
bool darwin // macOS
|
||||||
|
bool arm64 // AAPCS64
|
||||||
|
bool cross // another system than the one the compiler runs on
|
||||||
|
str exe // ".exe" or ""
|
||||||
|
str cc // the C compiler that links (the IR is always compiled by clang)
|
||||||
|
str runner // how to run a program for it here: "", "wine ", "qemu-aarch64 "
|
||||||
|
}
|
||||||
|
|
||||||
|
fn firstLine(str text) -> str {
|
||||||
|
parts := text.trim().split("\n")
|
||||||
|
return parts[0].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
// what clang calls the system it runs on, like x86_64-pc-linux-gnu
|
||||||
|
fn hostTriple() -> str {
|
||||||
|
tmp := "/tmp/finch_triple.txt"
|
||||||
|
shell("clang -dumpmachine > '" + tmp + "' 2> /dev/null")
|
||||||
|
out := read_file(tmp) or ""
|
||||||
|
shell("rm -f '" + tmp + "'")
|
||||||
|
if out.trim().len == 0 {
|
||||||
|
return "x86_64-unknown-linux-gnu"
|
||||||
|
}
|
||||||
|
return firstLine(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "windows", "linux", "macos", "arm64" (ARM64 Linux), or a full LLVM triple; "" = this system
|
||||||
|
fn select(str name) -> Target {
|
||||||
|
host := hostTriple()
|
||||||
|
t := name
|
||||||
|
if name.len == 0 {
|
||||||
|
t = host
|
||||||
|
} else if name == "windows" {
|
||||||
|
t = "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" {
|
||||||
|
if host.starts_with("aarch64") || host.starts_with("arm64") {
|
||||||
|
t = "arm64-apple-macosx14.0.0"
|
||||||
|
} else {
|
||||||
|
t = "x86_64-apple-macosx14.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tg := Target(triple: t)
|
||||||
|
tg.windows = t.contains("windows") || t.contains("mingw") || t.contains("msvc") || t.contains("win32")
|
||||||
|
tg.msvc = t.contains("msvc")
|
||||||
|
tg.darwin = t.contains("apple") || t.contains("darwin") || t.contains("macos")
|
||||||
|
tg.arm64 = t.starts_with("aarch64") || t.starts_with("arm64")
|
||||||
|
hostArm := host.starts_with("aarch64") || host.starts_with("arm64")
|
||||||
|
hostDarwin := host.contains("apple") || host.contains("darwin")
|
||||||
|
tg.cross = tg.windows || tg.darwin != hostDarwin || tg.arm64 != hostArm
|
||||||
|
tg.exe = ""
|
||||||
|
if tg.windows {
|
||||||
|
tg.exe = ".exe"
|
||||||
|
}
|
||||||
|
// FINCH_CC chooses the C compiler for any target
|
||||||
|
fc := getenv("FINCH_CC")
|
||||||
|
if fc.len > 0 {
|
||||||
|
tg.cc = fc
|
||||||
|
} else if tg.cross && tg.windows {
|
||||||
|
tg.cc = "x86_64-w64-mingw32-gcc"
|
||||||
|
} else if tg.cross && tg.arm64 && !tg.darwin {
|
||||||
|
tg.cc = "aarch64-linux-gnu-gcc"
|
||||||
|
} else if tg.cross {
|
||||||
|
tg.cc = "clang --target=" + t
|
||||||
|
} else {
|
||||||
|
tg.cc = "cc"
|
||||||
|
}
|
||||||
|
if tg.cross && tg.windows {
|
||||||
|
tg.runner = "wine "
|
||||||
|
} else if tg.cross && tg.arm64 && !tg.darwin {
|
||||||
|
tg.runner = "qemu-aarch64 "
|
||||||
|
}
|
||||||
|
return tg
|
||||||
|
}
|
||||||
|
|
||||||
|
// C's plain `char`: signed on x86 and Apple, not on ARM64 Linux
|
||||||
|
fn charIsSigned(Target t) -> bool {
|
||||||
|
return !t.arm64 || t.darwin || t.windows
|
||||||
|
}
|
||||||
6
std/finch_win.h
Normal file
6
std/finch_win.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/* The Windows functions std/time_windows.fch needs. Declared here, not by including <windows.h>:
|
||||||
|
libclang can't parse that header with MinGW's intrinsics. Both are in kernel32. */
|
||||||
|
unsigned long long GetTickCount64(void);
|
||||||
|
void Sleep(unsigned long milliseconds);
|
||||||
|
int QueryPerformanceCounter(long long *count);
|
||||||
|
int QueryPerformanceFrequency(long long *frequency);
|
||||||
45
std/proc_windows.fch
Normal file
45
std/proc_windows.fch
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
// Programs, for Windows. The same functions as proc.fch (that file is for Linux and macOS).
|
||||||
|
import "stdlib.h"
|
||||||
|
import "process.h"
|
||||||
|
import "direct.h"
|
||||||
|
import "time.h"
|
||||||
|
|
||||||
|
fn run(str command) -> int {
|
||||||
|
return shell(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(str command) -> str! {
|
||||||
|
tmp := proc_env("TEMP", ".") + "\\finch_out_" + str(getpid()) + "_" + str(int(time(null)))
|
||||||
|
code := shell(command + " > \"" + tmp + "\" 2>&1")
|
||||||
|
text := read_file(tmp) or ""
|
||||||
|
delete_file(tmp)
|
||||||
|
if code != 0 {
|
||||||
|
return error("'" + command + "' exited with code " + str(code) + ": " + text.trim())
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proc_env(str name, str fallback) -> str {
|
||||||
|
v := getenv(name)
|
||||||
|
if v.len == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn env(str name, str fallback) -> str {
|
||||||
|
return proc_env(name, fallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pid() -> int {
|
||||||
|
return int(getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cwd() -> str {
|
||||||
|
[]char buf
|
||||||
|
for i in 0..4096 {
|
||||||
|
buf.push(char(0))
|
||||||
|
}
|
||||||
|
getcwd(buf.ptr, 4096)
|
||||||
|
return str(buf.ptr)
|
||||||
|
}
|
||||||
75
std/time_windows.fch
Normal file
75
std/time_windows.fch
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Time, for Windows. The same functions as time.fch (that file is for Linux and macOS).
|
||||||
|
import "time.h"
|
||||||
|
import "finch_win.h"
|
||||||
|
|
||||||
|
struct Date {
|
||||||
|
int year
|
||||||
|
int month // 1 to 12
|
||||||
|
int day // 1 to 31
|
||||||
|
int hour
|
||||||
|
int minute
|
||||||
|
int second
|
||||||
|
int weekday // 0 is Sunday
|
||||||
|
int yearday // 1 to 366
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now() -> int {
|
||||||
|
return int(time(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
// milliseconds on a clock that only goes forward (the performance counter: GetTickCount64 ticks every 15 ms)
|
||||||
|
fn millis() -> int {
|
||||||
|
i64 count = 0
|
||||||
|
i64 freq = 1
|
||||||
|
QueryPerformanceFrequency(addr(freq))
|
||||||
|
QueryPerformanceCounter(addr(count))
|
||||||
|
return int(count / freq) * 1000 + int(count % freq) * 1000 / int(freq)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep(int ms) {
|
||||||
|
Sleep(u32(ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parts(tm t) -> Date {
|
||||||
|
return Date(year: int(t.tm_year) + 1900, month: int(t.tm_mon) + 1, day: int(t.tm_mday), hour: int(t.tm_hour),
|
||||||
|
minute: int(t.tm_min), second: int(t.tm_sec), weekday: int(t.tm_wday), yearday: int(t.tm_yday) + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn date(int unix) -> Date {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
_gmtime64_s(addr(t), addr(stamp))
|
||||||
|
return parts(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local(int unix) -> Date {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
_localtime64_s(addr(t), addr(stamp))
|
||||||
|
return parts(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format(int unix, str pattern) -> str {
|
||||||
|
tm t
|
||||||
|
i64 stamp = unix
|
||||||
|
_localtime64_s(addr(t), addr(stamp))
|
||||||
|
[]char buf
|
||||||
|
for i in 0..256 {
|
||||||
|
buf.push(char(0))
|
||||||
|
}
|
||||||
|
strftime(buf.ptr, 256, pattern, addr(t))
|
||||||
|
return str(buf.ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn days_in_month(int year, int month) -> int {
|
||||||
|
if month == 2 {
|
||||||
|
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
|
||||||
|
return 29
|
||||||
|
}
|
||||||
|
return 28
|
||||||
|
}
|
||||||
|
if month == 4 || month == 6 || month == 9 || month == 11 {
|
||||||
|
return 30
|
||||||
|
}
|
||||||
|
return 31
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
# tests/cross.sh arm64 aarch64 Linux, run with qemu-aarch64
|
# 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'
|
# e.g. nix-shell -p pkgsCross.mingwW64.buildPackages.gcc wine64 --run 'tests/cross.sh windows'
|
||||||
# nix-shell -p pkgsCross.aarch64-multiplatform.buildPackages.gcc qemu \
|
# nix-shell -p pkgsCross.aarch64-multiplatform.buildPackages.gcc qemu \
|
||||||
# --run 'FINCH_CC=aarch64-unknown-linux-gnu-gcc tests/cross.sh arm64'
|
# --run 'FINCH_CC=aarch64-unknown-linux-gnu-gcc QEMU_LD_PREFIX=<the aarch64 glibc> tests/cross.sh arm64'
|
||||||
# (On a real Windows/ARM64/macOS machine, use tests/run.sh with that system's finch.)
|
# (On a real Windows/ARM64/macOS machine, use tests/run.sh with that system's finch.)
|
||||||
set -u
|
set -u
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
@ -24,7 +24,7 @@ run_exe() { # program, stdin -> output without Windows' \r
|
||||||
$runner "$1" < "$2" 2>&1 | tr -d '\r'
|
$runner "$1" < "$2" 2>&1 | tr -d '\r'
|
||||||
}
|
}
|
||||||
|
|
||||||
for f in run/*.fch; do
|
for f in run/*.fch new/*.fch; do
|
||||||
grep -q "^fn main" "$f" || continue
|
grep -q "^fn main" "$f" || continue
|
||||||
input="${f%.fch}.in"
|
input="${f%.fch}.in"
|
||||||
[ -f "$input" ] || input=/dev/null
|
[ -f "$input" ] || input=/dev/null
|
||||||
|
|
@ -40,7 +40,7 @@ for f in run/*.fch; do
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
for f in fail/*.fch; do
|
for f in fail/*.fch new/fail/*.fch; do
|
||||||
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
expect=$(head -1 "$f" | sed -n 's|^// expect: ||p')
|
||||||
exe="$tmp/$(basename "${f%.fch}")$ext"
|
exe="$tmp/$(basename "${f%.fch}")$ext"
|
||||||
if "$FINCH" build "$f" --target "$target" -o "$exe" > "$tmp/out" 2>&1; then
|
if "$FINCH" build "$f" --target "$target" -o "$exe" > "$tmp/out" 2>&1; then
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,12 @@ fn main() -> ! {
|
||||||
a := time.millis()
|
a := time.millis()
|
||||||
time.sleep(30)
|
time.sleep(30)
|
||||||
waited := time.millis() - a
|
waited := time.millis() - a
|
||||||
print(waited >= 25 && waited < 500)
|
print(waited >= 20 && waited < 500)
|
||||||
print(time.now() > 1700000000, time.format(0, "%Y") == "1970" || time.format(0, "%Y") == "1969")
|
print(time.now() > 1700000000, time.format(0, "%Y") == "1970" || time.format(0, "%Y") == "1969")
|
||||||
|
|
||||||
print((try proc.output("echo hello; echo world")).trim())
|
print((try proc.output("echo hello")).trim())
|
||||||
print(proc.run("exit 3"))
|
print(proc.run("exit 3"))
|
||||||
bad := proc.output("echo oops; exit 2") or err
|
bad := proc.output("exit 2") or err
|
||||||
print(bad)
|
print(bad)
|
||||||
print(proc.env("FINCH_SURELY_NOT_SET", "fallback"), proc.pid() > 0, proc.cwd().len > 0)
|
print(proc.env("FINCH_SURELY_NOT_SET", "fallback"), proc.pid() > 0, proc.cwd().len > 0)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
true
|
true
|
||||||
true true
|
true true
|
||||||
hello
|
hello
|
||||||
world
|
|
||||||
3
|
3
|
||||||
'echo oops; exit 2' exited with code 2: oops
|
'exit 2' exited with code 2:
|
||||||
fallback true true
|
fallback true true
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue