mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 16:54:25 +00:00
export fn and finch build --shared, in the compiler written in Finch; FINCH_PATH, FINCH_RT, FINCH_CC
export fn name(...) is a C function of a library; build --shared makes lib<name>.so with no main and a finch_module_on_fatal(handler) the host can take runtime errors over with. Ported from the embed work (the runtime, its test and the docs are that work's; the compiler side is new). The driver reads FINCH_PATH (more module folders), FINCH_RT (the runtime's file) and FINCH_CC (the clang). tests/boot.sh also runs tests/shared.sh with the compiler under test. 41 passed.
This commit is contained in:
parent
94e41484dd
commit
dd9cbe7611
13 changed files with 212 additions and 13 deletions
|
|
@ -102,6 +102,7 @@ struct Node {
|
|||
Type ty // declared type (decl, param, field, fn result)
|
||||
bool flag // decl: has a type; or: the right side is a { block }
|
||||
bool fallible // fn: -> T! or -> !
|
||||
bool exported // export fn: a C function of a library (finch build --shared)
|
||||
[]Node kids
|
||||
[]str names // named arguments: Point(x: 1)
|
||||
int line
|
||||
|
|
|
|||
92
boot/gen.fch
92
boot/gen.fch
|
|
@ -58,6 +58,7 @@ struct FnSig {
|
|||
str recv // a method of this struct ("" for a function)
|
||||
str recvmod // and the module of that struct: "C" for a struct from a header
|
||||
bool recvEnum // the receiver is an enum
|
||||
bool exported // export fn: also a C function of a library
|
||||
[]str tnames // an instance of a generic function: its type parameters and what they stand for
|
||||
[]ast.Type targs
|
||||
str shown // max[int], for messages
|
||||
|
|
@ -136,6 +137,7 @@ struct Gen {
|
|||
[]str mm // names of methods that change self, for the mutation analysis
|
||||
int nvars // variables declared so far in this function
|
||||
cimport.CImports ci
|
||||
bool shared // building a library (finch build --shared): no main, its export fns are the way in
|
||||
[]str cdone // C functions and variables already declared
|
||||
str ftarget // the call an enclosing or / try is waiting on (see nodeKey)
|
||||
str ffailed // set by that call: an i1, true if it failed
|
||||
|
|
@ -1223,7 +1225,7 @@ fn declare(ptr[Gen] g) {
|
|||
fail(g, f, "'" + f.name + "' is a built-in function, pick another name")
|
||||
}
|
||||
}
|
||||
sig := FnSig(module: g.module, name: f.name, recv: f.recv, recvmod: recvmod, recvEnum: recvEnum, fallible: f.fallible, prog: pi, index: fi)
|
||||
sig := FnSig(module: g.module, name: f.name, recv: f.recv, recvmod: recvmod, recvEnum: recvEnum, fallible: f.fallible, exported: f.exported, prog: pi, index: fi)
|
||||
sig.ll = "@finch." + modName(g.module) + "." + f.name
|
||||
if f.recv.len > 0 {
|
||||
sig.ll = "@finch." + modName(g.module) + "." + f.recv + "." + f.name
|
||||
|
|
@ -1236,6 +1238,9 @@ fn declare(ptr[Gen] g) {
|
|||
sig.params.push(resolve(g, f.kids[k].ty, f.kids[k]))
|
||||
sig.pnames.push(f.kids[k].name)
|
||||
}
|
||||
if f.exported {
|
||||
checkExport(g, f, sig)
|
||||
}
|
||||
g.sigs.push(sig)
|
||||
}
|
||||
}
|
||||
|
|
@ -3420,6 +3425,70 @@ fn checkByValue(ptr[Gen] g, ast.Node at, str fname, ast.Type t, str what) {
|
|||
}
|
||||
}
|
||||
|
||||
// export fn: what a C program can be given, and call
|
||||
fn cPlain(ast.Type t) -> bool {
|
||||
return isNumber(t) || t.kind == "bool" || t.kind == "char" || t.kind == "ptr"
|
||||
}
|
||||
|
||||
fn checkExport(ptr[Gen] g, ast.Node f, FnSig sig) {
|
||||
if f.recv.len > 0 {
|
||||
fail(g, f, "methods can't be exported (export a plain fn that calls it)")
|
||||
}
|
||||
if f.fallible {
|
||||
fail(g, f, "an export fn can't fail (C has no errors as values): handle them inside it")
|
||||
}
|
||||
if f.tparams.len > 0 {
|
||||
fail(g, f, "an export fn can't be generic")
|
||||
}
|
||||
for k in 0..sig.params.len {
|
||||
if !cPlain(sig.params[k]) {
|
||||
fail(g, f.kids[k], "an export fn takes numbers, bool, char and pointers only (this is " + ast.show(sig.params[k]) + "; pass text as ptr[char] and make it a str inside)")
|
||||
}
|
||||
}
|
||||
if sig.ret.kind != "void" && !cPlain(sig.ret) {
|
||||
fail(g, f, "an export fn returns nothing, a number, bool, char or a pointer (not " + ast.show(sig.ret) + ")")
|
||||
}
|
||||
for other in g.sigs {
|
||||
if other.exported && other.name == sig.name {
|
||||
fail(g, f, "'" + sig.name + "' is exported twice (from two modules)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// export fn name(...): a C function called `name` that calls the Finch one (a bool is a C byte)
|
||||
fn exportWrapper(ptr[Gen] g, FnSig sig) -> str {
|
||||
[]str params
|
||||
[]str args
|
||||
s := beginHelper(g)
|
||||
for k in 0..sig.params.len {
|
||||
t := sig.params[k]
|
||||
if t.kind == "bool" {
|
||||
params.push("i8 zeroext %e" + str(k))
|
||||
args.push("i1 " + op(g, "icmp ne i8 %e" + str(k) + ", 0"))
|
||||
} else {
|
||||
params.push(cLL(g, t) + cExt(t) + " %e" + str(k))
|
||||
args.push(llty(g, t) + " %e" + str(k))
|
||||
}
|
||||
}
|
||||
text := "call " + llty(g, sig.ret) + " " + sig.ll + "(" + args.join(", ") + ")"
|
||||
retLL := "void"
|
||||
retExt := ""
|
||||
if sig.ret.kind == "void" {
|
||||
emit(g, text)
|
||||
emit(g, "ret void")
|
||||
} else if sig.ret.kind == "bool" {
|
||||
retLL = "i8"
|
||||
retExt = " zeroext"
|
||||
emit(g, "ret i8 " + op(g, "zext i1 " + op(g, text) + " to i8"))
|
||||
} else {
|
||||
retLL = llty(g, sig.ret)
|
||||
retExt = cExt(sig.ret)
|
||||
emit(g, "ret " + retLL + " " + op(g, text))
|
||||
}
|
||||
endHelper(g, s, "define" + retExt + " " + retLL + " @" + sig.name + "(" + params.join(", ") + ")")
|
||||
return sig.name
|
||||
}
|
||||
|
||||
// A C-callable door into a Finch function, for addr(fn): callbacks like qsort's or libclang's.
|
||||
fn cThunk(ptr[Gen] g, int fi, ast.Node at) -> str {
|
||||
sig := g.sigs[fi]
|
||||
|
|
@ -4438,6 +4507,7 @@ fn runtimeDecls() -> str {
|
|||
"declare i32 @printf(ptr, ...)",
|
||||
"declare i32 @fflush(ptr)",
|
||||
"declare void @finch_str_from_c(ptr, ptr)",
|
||||
"declare void @finch_on_fatal(ptr)",
|
||||
"declare void @finch_str_from_bytes(ptr, ptr)",
|
||||
"declare void @exit(i32) noreturn",
|
||||
"declare void @free(ptr)",
|
||||
|
|
@ -4445,12 +4515,16 @@ fn runtimeDecls() -> str {
|
|||
return decls.join("\n") + "\n"
|
||||
}
|
||||
|
||||
fn generate([]ast.Program progs, []str files, cimport.CImports ci) -> str {
|
||||
gen := Gen(progs: progs, files: files, ci: ci)
|
||||
fn generate([]ast.Program progs, []str files, cimport.CImports ci, bool shared) -> str {
|
||||
gen := Gen(progs: progs, files: files, ci: ci, shared: shared)
|
||||
g := addr(gen)
|
||||
declare(g)
|
||||
main := findFn(g, "", "main")
|
||||
if main < 0 {
|
||||
if shared {
|
||||
if main >= 0 {
|
||||
fail(g, g.progs[g.sigs[main].prog].fns[g.sigs[main].index], "a library (--shared) has no main: the program that loads it calls its 'export fn's")
|
||||
}
|
||||
} else if main < 0 {
|
||||
print(files[0] + ":1:1: error: there is no main function (add 'fn main() { ... }')")
|
||||
exit(1)
|
||||
}
|
||||
|
|
@ -4460,6 +4534,16 @@ fn generate([]ast.Program progs, []str files, cimport.CImports ci) -> str {
|
|||
define(g, di)
|
||||
di += 1
|
||||
}
|
||||
for sg in g.sigs {
|
||||
if sg.exported {
|
||||
exportWrapper(g, sg)
|
||||
}
|
||||
}
|
||||
if shared {
|
||||
// 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")
|
||||
return "; generated by the Finch compiler written in Finch\n\n" + g.head.join("\n") + "\n\n" + runtimeDecls() + "\n" + g.out.join("\n")
|
||||
}
|
||||
// the C entry point calls the Finch main
|
||||
m := g.sigs[main]
|
||||
callText := "call " + retTy(g, m) + " " + m.ll + "("
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ fn isHex(char c) -> bool {
|
|||
|
||||
fn isKeyword(str w) -> bool {
|
||||
keywords := ["fn", "return", "if", "else", "while", "for", "in", "break", "continue", "true", "false",
|
||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match"]
|
||||
"null", "import", "link", "struct", "defer", "or", "try", "enum", "match", "export"]
|
||||
return keywords.contains(w)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ast
|
|||
import parser
|
||||
import gen
|
||||
import cimport
|
||||
import "stdlib.h"
|
||||
|
||||
fn dirOf(str path) -> str {
|
||||
i := path.len - 1
|
||||
|
|
@ -39,15 +40,21 @@ struct Loader {
|
|||
[]str files
|
||||
[]str loaded
|
||||
str std // the standard library's folder, or ""
|
||||
[]str paths // FINCH_PATH: more folders to look for modules in
|
||||
}
|
||||
|
||||
// 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.
|
||||
fn findModule(ptr[Loader] l, str from, str name) -> str {
|
||||
local := dirOf(from) + "/" + name + ".fch"
|
||||
if file_exists(local) || l.std.len == 0 {
|
||||
if file_exists(local) {
|
||||
return local
|
||||
}
|
||||
for d in l.paths {
|
||||
if file_exists(d + "/" + name + ".fch") {
|
||||
return d + "/" + name + ".fch"
|
||||
}
|
||||
}
|
||||
shared := l.std + "/" + name + ".fch"
|
||||
if file_exists(shared) {
|
||||
return shared
|
||||
|
|
@ -127,6 +134,8 @@ fn main([]str args) -> int {
|
|||
usage()
|
||||
}
|
||||
out := stem(file)
|
||||
outGiven := false
|
||||
shared := false
|
||||
opt := "-O2"
|
||||
[]str extraLibs
|
||||
[]str extraDirs
|
||||
|
|
@ -138,7 +147,10 @@ fn main([]str args) -> int {
|
|||
progArgs.push(a)
|
||||
} else if a == "-o" && ai + 1 < args.len && cmd == "build" {
|
||||
out = args[ai + 1]
|
||||
outGiven = true
|
||||
ai += 1
|
||||
} else if a == "--shared" && cmd == "build" {
|
||||
shared = true
|
||||
} else if a == "-O0" {
|
||||
opt = "-O0"
|
||||
} else if a == "-l" && ai + 1 < args.len {
|
||||
|
|
@ -162,6 +174,9 @@ fn main([]str args) -> int {
|
|||
if cmd == "run" {
|
||||
out = "/tmp/finch_run_" + stem(file)
|
||||
}
|
||||
if shared && !outGiven {
|
||||
out = "lib" + out + ".so"
|
||||
}
|
||||
|
||||
loader := Loader()
|
||||
for cand in [dirOf(args[0]) + "/../std", dirOf(args[0]) + "/std", "std"] {
|
||||
|
|
@ -169,6 +184,11 @@ fn main([]str args) -> int {
|
|||
loader.std = cand
|
||||
}
|
||||
}
|
||||
for d in getenv("FINCH_PATH").split(":") {
|
||||
if d.len > 0 {
|
||||
loader.paths.push(d)
|
||||
}
|
||||
}
|
||||
load(addr(loader), file, "")
|
||||
|
||||
// C headers, from every file; their folders are searched first for "local.h"
|
||||
|
|
@ -199,14 +219,17 @@ fn main([]str args) -> int {
|
|||
print(err)
|
||||
return 1
|
||||
}
|
||||
ir := gen.generate(loader.progs, loader.files, ci)
|
||||
ir := gen.generate(loader.progs, loader.files, ci, shared)
|
||||
if cmd == "ir" {
|
||||
print(ir)
|
||||
return 0
|
||||
}
|
||||
|
||||
// the runtime is Finch's C file; FINCH_RT can point somewhere else
|
||||
rt := dirOf(args[0]) + "/../runtime/finch_rt.c"
|
||||
rt := getenv("FINCH_RT")
|
||||
if rt.len == 0 {
|
||||
rt = dirOf(args[0]) + "/../runtime/finch_rt.c"
|
||||
}
|
||||
if !file_exists(rt) {
|
||||
rt = dirOf(args[0]) + "/runtime/finch_rt.c"
|
||||
}
|
||||
|
|
@ -219,7 +242,16 @@ fn main([]str args) -> int {
|
|||
}
|
||||
llPath := out + ".ll"
|
||||
write_file(llPath, ir)
|
||||
code := shell("clang " + opt + " -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" "))
|
||||
sharedFlags := ""
|
||||
if shared {
|
||||
sharedFlags = " -shared -fPIC"
|
||||
}
|
||||
// the compiler of the IR is always a clang (CC may be gcc, which can't read it); FINCH_CC picks another clang
|
||||
cc := getenv("FINCH_CC")
|
||||
if cc.len == 0 {
|
||||
cc = "clang"
|
||||
}
|
||||
code := shell(cc + " " + opt + sharedFlags + " -w '" + llPath + "' '" + rt + "' -o '" + out + "' -lm " + libs.join(" "))
|
||||
shell("rm -f '" + llPath + "'")
|
||||
if code != 0 {
|
||||
print("error: clang failed")
|
||||
|
|
|
|||
|
|
@ -376,6 +376,7 @@ fn parseStruct(ptr[Parser] p) -> ast.Node {
|
|||
|
||||
fn parseFn(ptr[Parser] p) -> ast.Node {
|
||||
f := node(p, "fn")
|
||||
f.exported = accept(p, "export")
|
||||
expect(p, "fn", "'fn', 'struct' or 'import'")
|
||||
f.name = expect(p, "ident", "a function name").text
|
||||
if at(p, "[") {
|
||||
|
|
|
|||
Loading…
Reference in a new issue