Structs, arrays, owned strings, memory model, modules, debug info

- struct with default values; constructors Point(1, 2) / Point(x: 1); field access, auto-deref through pointers
- []T arrays with bounds checks, literals, for-each, push/pop/insert/remove/clear/resize/find/contains/join/slice/reverse/sort
- str carries its length: .len, s[i], +, comparison, sub/find/split/trim/upper/lower/replace/repeat/bytes, str(x), int(s), float(s)
- Memory: values own their memory and are freed at the end of their block; assignment copies, temporaries move;
  parameters are lent and only copied when the function changes them; defer; new()/free() for manual heap memory
- input(), read_file/write_file/file_exists, exit(), fn main([]str args)
- Finch modules: import name, module.fn(), module.Struct
- C structs from headers (fields, fixed arrays, nested); passed and returned by value with the System V x86-64 ABI;
  addr(fn) gives C a callback
- link "file.c" compiles and links your own C code
- -g emits DWARF debug info (gdb/lldb)
- Small C runtime (runtime/finch_rt.c) embedded in the compiler, cached in ~/.cache/finch
- Tests: 58, all clean under valgrind (MEMCHECK=1 tests/run.sh)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-08 18:57:37 +02:00
commit 1dc976bd8f
59 changed files with 4577 additions and 1047 deletions

309
src/abi.cpp Normal file
View file

@ -0,0 +1,309 @@
// The C calling convention for structs passed and returned by value (System V x86-64),
// the way clang does it: small structs travel in registers, split into eightbytes that are
// either INTEGER (general registers) or SSE (xmm registers); bigger ones go through memory.
#include "codegen_impl.h"
using namespace llvm;
namespace finch {
namespace {
enum Class { None, Integer, Sse };
struct Leaf {
uint64_t offset;
uint64_t size;
bool isFloat;
bool isDouble;
};
} // namespace
// Flatten a value into its scalar pieces with byte offsets.
static void leaves(Codegen &cg, const FType &t, uint64_t base, std::vector<Leaf> &out) {
switch (t.kind) {
case FType::Struct: {
const StructLayout *lay = cg.dl->getStructLayout(t.info->llvm);
for (auto &f : t.info->fields) leaves(cg, f.type, base + lay->getElementOffset(f.llvmIndex), out);
break;
}
case FType::Fixed: {
uint64_t es = cg.dl->getTypeAllocSize(cg.ty(*t.elem));
for (long long i = 0; i < t.count; i++) leaves(cg, *t.elem, base + i * es, out);
break;
}
default:
out.push_back({base, cg.dl->getTypeAllocSize(cg.ty(t)), t.kind == FType::F32 || t.kind == FType::F64,
t.kind == FType::F64});
}
}
AbiArg Codegen::classify(const FType &t) {
AbiArg a;
if (t.kind != FType::Struct) return a; // Direct
uint64_t size = dl->getTypeAllocSize(ty(t));
if (size > 16 || size == 0) {
a.kind = AbiArg::Memory;
return a;
}
std::vector<Leaf> ls;
leaves(*this, t, 0, ls);
unsigned n = (size + 7) / 8;
Class cls[2] = {None, None};
bool hasDouble[2] = {false, false};
for (const Leaf &l : ls) {
if (l.offset % std::min<uint64_t>(l.size, 8) != 0) { // misaligned field (packed struct): memory
a.kind = AbiArg::Memory;
return a;
}
unsigned k = l.offset / 8;
cls[k] = (cls[k] == Integer || !l.isFloat) ? Integer : Sse;
hasDouble[k] |= l.isDouble;
}
a.kind = AbiArg::Expand;
for (unsigned k = 0; k < n; k++) {
uint64_t bytes = std::min<uint64_t>(8, size - 8 * k);
if (cls[k] == Sse) {
if (hasDouble[k]) a.parts.push_back(b.getDoubleTy());
else if (bytes > 4) a.parts.push_back(FixedVectorType::get(b.getFloatTy(), 2));
else a.parts.push_back(b.getFloatTy());
} else {
a.parts.push_back(b.getIntNTy(bytes * 8));
}
}
a.coerced = a.parts.size() == 1 ? a.parts[0] : StructType::get(ctx, a.parts);
return a;
}
namespace {
struct Plan {
AbiArg ret;
std::vector<AbiArg> params;
llvm::FunctionType *fty = nullptr;
};
} // namespace
// Decide how every parameter travels, counting the 6 integer and 8 SSE argument registers.
static Plan makePlan(Codegen &cg, const FType &ret, const std::vector<FType> &params, bool variadic,
std::vector<llvm::Type *> *extraTypes = nullptr) {
Plan pl;
int intRegs = 6, sseRegs = 8;
std::vector<llvm::Type *> types;
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::Memory) {
types.push_back(cg.b.getPtrTy()); // sret
retTy = cg.b.getVoidTy();
intRegs--;
} else if (pl.ret.kind == AbiArg::Expand) {
retTy = pl.ret.coerced;
}
for (const FType &t : params) {
AbiArg a = cg.classify(t);
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) {
intRegs -= ni;
sseRegs -= ns;
for (llvm::Type *p : a.parts) types.push_back(p);
} else {
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::Direct) {
if (t.isFloat()) sseRegs--;
else intRegs--;
types.push_back(t.kind == FType::Str ? cg.b.getPtrTy() : cg.ty(t));
}
pl.params.push_back(a);
}
(void)extraTypes;
pl.fty = FunctionType::get(retTy, types, variadic);
return pl;
}
static Attribute::AttrKind extAttr(const FType &t) {
if (t.bits() >= 32 || !(t.isInt() || t.kind == FType::Bool || t.kind == FType::Char)) return Attribute::None;
return t.isSigned() || t.kind == FType::Char ? Attribute::SExt : Attribute::ZExt;
}
// Attributes that tell LLVM how C passes each piece: sret, byval, signext/zeroext.
static AttributeList attrsFor(Codegen &cg, const Plan &pl, const FType &ret, const std::vector<FType> &params) {
LLVMContext &ctx = cg.ctx;
AttributeList al;
unsigned idx = 0;
if (pl.ret.kind == AbiArg::Memory) {
al = al.addParamAttribute(ctx, idx, Attribute::getWithStructRetType(ctx, cg.ty(ret)));
idx++;
} else if (Attribute::AttrKind k = extAttr(ret); k != Attribute::None) {
al = al.addRetAttribute(ctx, k);
}
for (size_t i = 0; i < params.size(); i++) {
const AbiArg &a = pl.params[i];
if (a.kind == AbiArg::Memory) {
uint64_t align = std::max<uint64_t>(8, params[i].info->isC ? params[i].info->cdecl->align : 8);
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::Expand) {
idx += a.parts.size();
} else {
if (Attribute::AttrKind k = extAttr(params[i]); k != Attribute::None) al = al.addParamAttribute(ctx, idx, k);
idx++;
}
}
return al;
}
static std::vector<FType> resolvedParams(Codegen &cg, const CFunc &f) {
std::vector<FType> ps;
for (const FType &t : f.params) ps.push_back(cg.resolve(t, Pos{}));
return ps;
}
Function *Codegen::declareC(const CFunc &f) {
if (Function *fn = mod->getFunction(f.name)) return fn;
FType ret = resolve(f.ret, Pos{});
std::vector<FType> ps = resolvedParams(*this, f);
for (const FType &t : ps)
if (t.kind == FType::Struct && !t.info->unsupported.empty()) return nullptr;
Plan pl = makePlan(*this, ret, ps, f.variadic);
Function *fn = Function::Create(pl.fty, Function::ExternalLinkage, f.name, mod.get());
fn->setAttributes(attrsFor(*this, pl, ret, ps));
return fn;
}
// A stack slot big enough for both the struct and its register form.
static Value *abiSlot(Codegen &cg, const FType &t, const AbiArg &a) {
llvm::Type *st = cg.ty(t);
llvm::Type *big = a.coerced && cg.dl->getTypeAllocSize(a.coerced) > cg.dl->getTypeAllocSize(st) ? a.coerced : st;
return cg.tmpOf(big);
}
Value_ Codegen::emitCCall(const CFunc &f, Function *fn, std::vector<Value_> args, Pos p) {
FType ret = resolve(f.ret, p);
std::vector<FType> ps = resolvedParams(*this, f);
for (const FType &t : ps)
if (t.kind == FType::Struct && !t.info->unsupported.empty())
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' takes the struct " + t.info->name +
" by value, which Finch can't pass yet: it " + t.info->unsupported);
if (ret.kind == FType::Struct && !ret.info->unsupported.empty())
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' returns the struct " + ret.info->name +
" by value, which Finch can't receive yet: it " + ret.info->unsupported);
Plan pl = makePlan(*this, ret, ps, f.variadic);
std::vector<Value *> ll;
Value *sret = nullptr;
if (pl.ret.kind == AbiArg::Memory) {
sret = tmpOf(ty(ret));
cast<AllocaInst>(sret)->setAlignment(Align(16));
ll.push_back(sret);
}
for (size_t i = 0; i < args.size(); i++) {
const Value_ &v = args[i];
if (i >= ps.size()) { // the '...' part: C's default promotions
const FType &t = v.type;
Value *x = v.v;
if (t.kind == FType::F32) x = b.CreateFPExt(x, b.getDoubleTy());
else if (t.kind == FType::Str) x = cstr(x);
else if (t.kind == FType::Bool || ((t.isInt() || t.kind == FType::Char) && t.bits() < 32))
x = b.CreateIntCast(x, b.getInt32Ty(), t.isSigned() || t.kind == FType::Char);
ll.push_back(x);
continue;
}
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]));
cast<AllocaInst>(s)->setAlignment(Align(16));
b.CreateStore(v.v, s);
ll.push_back(s);
} else if (a.kind == AbiArg::Expand) {
Value *s = abiSlot(*this, ps[i], a);
b.CreateStore(v.v, s);
for (size_t k = 0; k < a.parts.size(); k++)
ll.push_back(b.CreateLoad(a.parts[k], b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k)));
} else {
ll.push_back(v.v);
}
}
CallInst *call = b.CreateCall(pl.fty, fn, ll);
AttributeList al = attrsFor(*this, pl, ret, ps);
call->setAttributes(al);
if (ret.kind == FType::Void) return {nullptr, FType::Void};
if (pl.ret.kind == AbiArg::Memory) return {b.CreateLoad(ty(ret), sret), ret};
if (pl.ret.kind == AbiArg::Expand) {
Value *s = abiSlot(*this, ret, pl.ret);
b.CreateStore(call, s);
return {b.CreateLoad(ty(ret), s), ret};
}
if (ret.kind == FType::Str) return strFromC(call); // borrowed: copied if it is kept
return {call, ret};
}
// A C-callable door into a Finch function, for addr(fn) (callbacks like qsort's or libclang's).
Function *Codegen::cThunk(Fn &fn, Pos p) {
if (fn.cThunk) return fn.cThunk;
auto cOk = [](const FType &t) {
return t.isNumber() || t.kind == FType::Bool || t.kind == FType::Char || t.isPtr() || t.kind == FType::Void ||
(t.kind == FType::Struct && t.info->isC);
};
for (const FType &t : fn.params)
if (!cOk(t))
failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + ") gives C a function pointer, so its parameters must be C types (numbers, ptr, C structs), not " + t.show());
if (!cOk(fn.ret)) failAt(p.file, p.line, p.col, "addr(" + fn.decl->name + "): a function given to C can't return " + fn.ret.show());
Plan pl = makePlan(*this, fn.ret, fn.params, false);
Function *th = Function::Create(pl.fty, Function::InternalLinkage, "finch.c." + fn.decl->name, mod.get());
th->setAttributes(attrsFor(*this, pl, fn.ret, fn.params));
fn.cThunk = th;
IRBuilderBase::InsertPointGuard guard(b);
DISubprogram *savedFn = diFn;
diFn = nullptr;
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", th));
b.SetCurrentDebugLocation(DebugLoc());
unsigned ai = 0;
Value *sret = nullptr;
if (pl.ret.kind == AbiArg::Memory) sret = th->getArg(ai++);
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) {
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);
for (size_t k = 0; k < a.parts.size(); k++)
b.CreateStore(th->getArg(ai++), b.CreateConstInBoundsGEP1_64(b.getInt8Ty(), s, 8 * k));
args.push_back(b.CreateLoad(ty(fn.params[i]), s));
} else {
args.push_back(th->getArg(ai++));
}
}
Value *r = b.CreateCall(fn.llvm, args);
if (fn.ret.kind == FType::Void) {
b.CreateRetVoid();
} else if (pl.ret.kind == AbiArg::Memory) {
b.CreateStore(r, sret);
b.CreateRetVoid();
} else if (pl.ret.kind == AbiArg::Expand) {
Value *s = abiSlot(*this, fn.ret, pl.ret);
b.CreateStore(r, s);
b.CreateRet(b.CreateLoad(pl.ret.coerced, s));
} else {
b.CreateRet(r);
}
diFn = savedFn;
return th;
}
} // namespace finch

103
src/ast.h
View file

@ -4,10 +4,23 @@
#include <string>
#include <vector>
struct StructInfo; // filled in by codegen: fields, LLVM type, ownership
struct Type {
enum Kind { Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64, Str, Ptr, Null };
enum Kind {
Void, Bool, Char, I8, I16, I32, I64, U8, U16, U32, U64, F32, F64,
Str, Ptr, Null,
Array, // []T: growable, owns its elements
Fixed, // [N]T: only inside C structs
Struct, // a resolved struct
Named, // a struct name as written, before codegen resolves it
};
Kind kind = Void;
std::shared_ptr<Type> elem; // what a Ptr points to; null for the untyped `ptr`
std::shared_ptr<Type> elem; // Ptr (null = untyped `ptr`), Array, Fixed
long long count = 0; // Fixed
std::string name; // Named / Struct
std::string module; // Named: written qualifier ("" = none); Struct: defining module
StructInfo *info = nullptr; // Struct
Type() = default;
Type(Kind k) : kind(k) {}
@ -16,6 +29,11 @@ struct Type {
p.elem = std::make_shared<Type>(t);
return p;
}
static Type arrayOf(const Type &t) {
Type a(Array);
a.elem = std::make_shared<Type>(t);
return a;
}
bool isInt() const { return kind >= I8 && kind <= U64; }
bool isSigned() const { return kind >= I8 && kind <= I64; }
@ -35,13 +53,21 @@ struct Type {
bool operator==(const Type &o) const {
if (kind != o.kind) return false;
if (kind != Ptr) return true;
if (!elem || !o.elem) return !elem && !o.elem;
return *elem == *o.elem;
switch (kind) {
case Ptr:
if (!elem || !o.elem) return !elem && !o.elem;
return *elem == *o.elem;
case Array: return *elem == *o.elem;
case Fixed: return count == o.count && *elem == *o.elem;
case Struct: return info == o.info;
case Named: return name == o.name && module == o.module;
default: return true;
}
}
bool operator!=(const Type &o) const { return !(*this == o); }
std::string name() const {
// How the type is written in Finch, for error messages.
std::string show() const {
switch (kind) {
case Void: return "nothing";
case Bool: return "bool";
@ -57,8 +83,12 @@ struct Type {
case F32: return "f32";
case F64: return "float";
case Str: return "str";
case Ptr: return elem ? "ptr[" + elem->name() + "]" : "ptr";
case Ptr: return elem ? "ptr[" + elem->show() + "]" : "ptr";
case Null: return "null";
case Array: return "[]" + elem->show();
case Fixed: return "[" + std::to_string(count) + "]" + elem->show();
case Struct:
case Named: return module.empty() ? name : module + "." + name;
}
return "?";
}
@ -69,11 +99,12 @@ bool typeFromName(const std::string &name, Type &out);
struct Pos {
int line = 0, col = 0;
int file = 0; // index into the list of source files (see error.h)
};
// ---------- expressions ----------
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member };
enum class ExprKind { Int, Float, Bool, Char, Str, Null, Var, Unary, Binary, Call, Member, Index, ArrayLit, Method };
struct Expr {
ExprKind kind;
@ -127,21 +158,39 @@ struct BinaryExpr : Expr {
BinaryExpr(Pos p, BinOp o, ExprPtr l, ExprPtr r)
: Expr(ExprKind::Binary, p), op(o), lhs(std::move(l)), rhs(std::move(r)) {}
};
// name(args): a function, a built-in, a conversion or a struct constructor.
struct CallExpr : Expr {
std::string callee;
std::vector<ExprPtr> args;
std::vector<std::string> argNames; // "" for positional; Point(x: 1, y: 2)
CallExpr(Pos p, std::string c) : Expr(ExprKind::Call, p), callee(std::move(c)) {}
};
struct MemberExpr : Expr { // obj.field (for now only p.value)
struct MemberExpr : Expr { // obj.field, p.value, a.len
ExprPtr obj;
std::string field;
MemberExpr(Pos p, ExprPtr o, std::string f)
: Expr(ExprKind::Member, p), obj(std::move(o)), field(std::move(f)) {}
};
struct IndexExpr : Expr { // a[i]
ExprPtr obj, index;
IndexExpr(Pos p, ExprPtr o, ExprPtr i) : Expr(ExprKind::Index, p), obj(std::move(o)), index(std::move(i)) {}
};
struct ArrayLitExpr : Expr { // [1, 2, 3]
std::vector<ExprPtr> elems;
explicit ArrayLitExpr(Pos p) : Expr(ExprKind::ArrayLit, p) {}
};
// obj.name(args): a.push(x), s.find("x"), and math.sqrt(2) / math.Vec(1, 2) for modules
struct MethodExpr : Expr {
ExprPtr obj;
std::string name;
std::vector<ExprPtr> args;
std::vector<std::string> argNames;
MethodExpr(Pos p, ExprPtr o, std::string n) : Expr(ExprKind::Method, p), obj(std::move(o)), name(std::move(n)) {}
};
// ---------- statements ----------
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, Return, Break, Continue, Block };
enum class StmtKind { VarDecl, Assign, Expr, If, While, For, ForEach, Return, Break, Continue, Block, Defer };
struct Stmt {
StmtKind kind;
@ -165,7 +214,7 @@ struct VarDeclStmt : Stmt {
VarDeclStmt(Pos p) : Stmt(StmtKind::VarDecl, p) {}
};
struct AssignStmt : Stmt {
ExprPtr target; // a variable or p.value
ExprPtr target; // a variable, field, element or p.value
char op; // '=', '+', '-', '*', '/', '%'
ExprPtr value;
AssignStmt(Pos p) : Stmt(StmtKind::Assign, p) {}
@ -191,6 +240,12 @@ struct ForStmt : Stmt {
BlockPtr body;
ForStmt(Pos p) : Stmt(StmtKind::For, p) {}
};
struct ForEachStmt : Stmt {
std::string var;
ExprPtr list; // for var in list (an array or a str)
BlockPtr body;
ForEachStmt(Pos p) : Stmt(StmtKind::ForEach, p) {}
};
struct ReturnStmt : Stmt {
ExprPtr value; // may be null
ReturnStmt(Pos p) : Stmt(StmtKind::Return, p) {}
@ -201,6 +256,10 @@ struct BreakStmt : Stmt {
struct ContinueStmt : Stmt {
explicit ContinueStmt(Pos p) : Stmt(StmtKind::Continue, p) {}
};
struct DeferStmt : Stmt { // defer <statement>: runs when the block ends
StmtPtr body;
DeferStmt(Pos p, StmtPtr s) : Stmt(StmtKind::Defer, p), body(std::move(s)) {}
};
// ---------- top level ----------
@ -218,18 +277,36 @@ struct FnDecl {
BlockPtr body;
};
struct Field {
Type type;
std::string name;
ExprPtr init; // default value, may be null
Pos pos;
};
struct StructDecl {
Pos pos;
std::string name;
std::vector<Field> fields;
};
struct Import {
Pos pos;
std::string path; // "stdio.h" for C headers
std::string path; // "stdio.h" (C header, isC) or "math" (Finch module)
bool isC = true;
};
struct Link {
Pos pos;
std::string lib; // "glfw" -> libglfw.so
std::string lib; // "glfw" -> libglfw.so; "helpers.c" / ".o" / ".a" -> a file next to the .fn file
};
// One .fn file.
struct Program {
std::string module; // "" for the main file, else the module's name
std::string path;
std::vector<Import> imports;
std::vector<Link> links;
std::vector<StructDecl> structs;
std::vector<FnDecl> fns;
};

View file

@ -41,189 +41,300 @@ std::string str(CXString s) {
return out;
}
// C type -> Finch type. Returns false (with a reason) if Finch can't express it yet.
bool mapType(CXType t, Type &out, std::string &why) {
t = clang_getCanonicalType(t);
long long size = clang_Type_getSizeOf(t);
auto sized = [&](bool isSigned) {
switch (size) {
case 1: out = isSigned ? Type::I8 : Type::U8; return true;
case 2: out = isSigned ? Type::I16 : Type::U16; return true;
case 4: out = isSigned ? Type::I32 : Type::U32; return true;
case 8: out = isSigned ? Type::I64 : Type::U64; return true;
}
why = "uses a " + std::to_string(size * 8) + "-bit number";
return false;
};
// Where a C type is used decides how `char *` looks in Finch: in function signatures it is
// a `str`; inside structs, globals and behind pointers it stays a raw `ptr[char]`.
enum class Ctx { Signature, Data };
switch (t.kind) {
case CXType_Void: out = Type::Void; return true;
case CXType_Bool: out = Type::Bool; return true;
case CXType_Char_S:
case CXType_Char_U: out = Type::Char; return true;
case CXType_SChar:
case CXType_Short:
case CXType_Int:
case CXType_Long:
case CXType_LongLong: return sized(true);
case CXType_UChar:
case CXType_UShort:
case CXType_UInt:
case CXType_ULong:
case CXType_ULongLong: return sized(false);
case CXType_Enum: return sized(true);
case CXType_Float: out = Type::F32; return true;
case CXType_Double: out = Type::F64; return true;
case CXType_Pointer: {
CXType pointee = clang_getCanonicalType(clang_getPointeeType(t));
if (pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) {
out = Type::Str; // char* is a C string
struct Importer {
CImports &out;
CXTranslationUnit tu = nullptr;
std::string header;
std::map<std::string, std::string> recordNames; // record USR -> Finch name (typedef wins)
std::map<std::string, int> defining; // USR -> 1 while its fields are being mapped
int anon = 0;
explicit Importer(CImports &o) : out(o) {}
// The Finch name of a C record: its typedef name, its tag, or a made-up one.
std::string recordName(CXType t) {
CXCursor decl = clang_getTypeDeclaration(t);
std::string usr = str(clang_getCursorUSR(decl));
auto it = recordNames.find(usr);
if (it != recordNames.end()) return it->second;
std::string name = str(clang_getCursorSpelling(decl));
if (name.empty() || name.find(' ') != std::string::npos) name = "anon_struct_" + std::to_string(++anon);
recordNames[usr] = name;
return name;
}
// Fill out.structs[name] for a complete record; returns its name, or "" if it can't be one.
std::string defineRecord(CXType t) {
t = clang_getCanonicalType(t);
CXCursor decl = clang_getTypeDeclaration(t);
std::string usr = str(clang_getCursorUSR(decl));
std::string name = recordName(t);
if (out.structs.count(name) || defining.count(usr)) return name;
long long size = clang_Type_getSizeOf(t);
if (size < 0) return ""; // incomplete (opaque) struct
defining[usr] = 1;
CStruct s;
s.name = name;
s.size = size;
s.align = clang_Type_getAlignOf(t);
if (clang_getCursorKind(decl) == CXCursor_UnionDecl) s.unsupported = "is a C union";
struct Ctx2 { Importer *self; CStruct *s; } c2{this, &s};
clang_Type_visitFields(t, [](CXCursor field, CXClientData data) -> CXVisitorResult {
auto &c = *static_cast<Ctx2 *>(data);
CField f;
f.name = str(clang_getCursorSpelling(field));
CXType ft = clang_getCanonicalType(clang_getCursorType(field));
f.offset = clang_Cursor_getOffsetOfField(field) / 8;
f.size = clang_Type_getSizeOf(ft);
f.align = clang_Type_getAlignOf(ft);
std::string why;
if (clang_Cursor_isBitField(field)) {
c.s->unsupported = "has bit fields";
f.hidden = true;
} else if (ft.kind == CXType_Record &&
clang_getCursorKind(clang_getTypeDeclaration(ft)) == CXCursor_UnionDecl) {
f.hidden = true; // unions are kept as opaque bytes
} else if (!c.self->mapType(ft, f.type, why, Ctx::Data)) {
f.hidden = true;
}
if (f.hidden) c.s->hasHidden = true;
if (f.name.empty()) f.hidden = true;
c.s->fields.push_back(f);
return CXVisit_Continue;
}, &c2);
defining.erase(usr);
out.structs[name] = s;
return name;
}
// C type -> Finch type. Returns false (with a reason) if Finch can't express it.
bool mapType(CXType t, Type &res, std::string &why, Ctx ctx) {
t = clang_getCanonicalType(t);
long long size = clang_Type_getSizeOf(t);
auto sized = [&](bool isSigned) {
switch (size) {
case 1: res = isSigned ? Type::I8 : Type::U8; return true;
case 2: res = isSigned ? Type::I16 : Type::U16; return true;
case 4: res = isSigned ? Type::I32 : Type::U32; return true;
case 8: res = isSigned ? Type::I64 : Type::U64; return true;
}
why = "uses a " + std::to_string(size * 8) + "-bit number";
return false;
};
switch (t.kind) {
case CXType_Void: res = Type::Void; return true;
case CXType_Bool: res = Type::Bool; return true;
case CXType_Char_S:
case CXType_Char_U: res = Type::Char; return true;
case CXType_SChar:
case CXType_Short:
case CXType_Int:
case CXType_Long:
case CXType_LongLong: return sized(true);
case CXType_UChar:
case CXType_UShort:
case CXType_UInt:
case CXType_ULong:
case CXType_ULongLong: return sized(false);
case CXType_Enum: return sized(true);
case CXType_Float: res = Type::F32; return true;
case CXType_Double: res = Type::F64; return true;
case CXType_Pointer: {
CXType pointee = clang_getCanonicalType(clang_getPointeeType(t));
if ((pointee.kind == CXType_Char_S || pointee.kind == CXType_Char_U) && ctx == Ctx::Signature) {
res = Type::Str; // char* in a signature is a C string
return true;
}
Type inner;
std::string ignored;
if (pointee.kind == CXType_Void || pointee.kind == CXType_FunctionProto ||
pointee.kind == CXType_FunctionNoProto || !mapType(pointee, inner, ignored, Ctx::Data) ||
inner.kind == Type::Void || inner.kind == Type::Fixed)
res = Type::Ptr; // an opaque `ptr` you can pass around
else
res = Type::ptrTo(inner);
return true;
}
Type inner;
std::string ignored;
// pointers to structs, functions, void...: an opaque `ptr` you can pass around
if (pointee.kind == CXType_Void || !mapType(pointee, inner, ignored) || inner.kind == Type::Void)
out = Type::Ptr;
else
out = Type::ptrTo(inner);
case CXType_Record: {
if (clang_getCursorKind(clang_getTypeDeclaration(t)) == CXCursor_UnionDecl) {
why = "uses a C union by value";
return false;
}
std::string name = defineRecord(t);
if (name.empty()) {
why = "uses an incomplete C struct by value";
return false;
}
res = Type(Type::Named);
res.name = name;
res.module = "C";
return true;
}
case CXType_ConstantArray: {
Type inner;
if (!mapType(clang_getArrayElementType(t), inner, why, Ctx::Data)) return false;
res = Type(Type::Fixed);
res.elem = std::make_shared<Type>(inner);
res.count = clang_getArraySize(t);
return true;
}
case CXType_LongDouble: why = "uses long double"; return false;
default: why = "uses the C type '" + str(clang_getTypeSpelling(t)) + "'"; return false;
}
}
// Simple #defines: a single number, maybe negative, maybe in parentheses.
bool macroValue(CXCursor c, CConst &res) {
CXToken *toks;
unsigned n;
clang_tokenize(tu, clang_getCursorExtent(c), &toks, &n);
std::vector<std::string> parts;
for (unsigned k = 1; k < n; k++) { // token 0 is the macro's name
if (clang_getTokenKind(toks[k]) == CXToken_Comment) continue;
parts.push_back(str(clang_getTokenSpelling(tu, toks[k])));
}
clang_disposeTokens(tu, toks, n);
while (parts.size() >= 2 && parts.front() == "(" && parts.back() == ")") {
parts.erase(parts.begin());
parts.pop_back();
}
bool neg = false;
if (parts.size() == 2 && parts[0] == "-") {
neg = true;
parts.erase(parts.begin());
}
if (parts.size() != 1) return false;
const std::string &lit = parts[0];
if (lit.empty() || !(std::isdigit((unsigned char)lit[0]) || lit[0] == '.')) return false;
bool isFloat = lit.find_first_of(".eE") != std::string::npos && lit.rfind("0x", 0) != 0;
char *end;
if (isFloat) {
res.type = Type::F64;
res.f = std::strtod(lit.c_str(), &end);
if (neg) res.f = -res.f;
} else {
res.type = Type::I64;
res.i = (long long)std::strtoull(lit.c_str(), &end, 0); // C rules: 0x.., 0755
if (neg) res.i = -res.i;
}
for (; *end; end++) // allow suffixes: 10UL, 1.0f
if (!std::strchr("uUlLfF", *end)) return false;
return true;
}
case CXType_Record: why = "passes a C struct by value (structs are coming)"; return false;
case CXType_LongDouble: why = "uses long double"; return false;
default: why = "uses the C type '" + str(clang_getTypeSpelling(t)) + "'"; return false;
}
}
// Simple #defines: a single number, maybe negative, maybe in parentheses.
bool macroValue(CXTranslationUnit tu, CXCursor c, CConst &out) {
CXToken *toks;
unsigned n;
clang_tokenize(tu, clang_getCursorExtent(c), &toks, &n);
std::vector<std::string> parts;
for (unsigned k = 1; k < n; k++) { // token 0 is the macro's name
if (clang_getTokenKind(toks[k]) == CXToken_Comment) continue;
parts.push_back(str(clang_getTokenSpelling(tu, toks[k])));
void visitTop(CXCursor c) {
std::string name = str(clang_getCursorSpelling(c));
switch (clang_getCursorKind(c)) {
case CXCursor_TypedefDecl: {
// typedef struct {...} Name; -> the struct is called Name in Finch
CXType u = clang_getCanonicalType(clang_getTypedefDeclUnderlyingType(c));
if (u.kind == CXType_Record) {
std::string usr = str(clang_getCursorUSR(clang_getTypeDeclaration(u)));
// prefer the typedef name, unless the struct is already known under its tag
auto it = recordNames.find(usr);
bool used = it != recordNames.end() && out.structs.count(it->second);
if (!used && !out.structs.count(name)) recordNames[usr] = name;
}
break;
}
case CXCursor_FunctionDecl: {
if (out.fns.count(name)) break;
CFunc f;
f.name = name;
f.header = header;
if (clang_Cursor_getStorageClass(c) == CX_SC_Static) {
f.unsupported = "is a static inline C function (it has no code to link against)";
out.fns[name] = f;
break;
}
CXType ft = clang_getCanonicalType(clang_getCursorType(c));
if (ft.kind == CXType_FunctionNoProto) {
f.unsupported = "has no parameter list in its C declaration";
out.fns[name] = f;
break;
}
std::string why;
if (!mapType(clang_getResultType(ft), f.ret, why, Ctx::Signature)) f.unsupported = why;
int n = clang_getNumArgTypes(ft);
for (int k = 0; k < n && f.unsupported.empty(); k++) {
Type t;
if (!mapType(clang_getArgType(ft, k), t, why, Ctx::Signature)) f.unsupported = why;
f.params.push_back(t);
}
f.variadic = clang_isFunctionTypeVariadic(ft);
out.fns[name] = f;
break;
}
case CXCursor_VarDecl: {
if (clang_Cursor_getStorageClass(c) != CX_SC_Extern || out.globals.count(name)) break;
CGlobal g;
std::string why;
if (mapType(clang_getCursorType(c), g.type, why, Ctx::Data)) out.globals[name] = g;
break;
}
case CXCursor_StructDecl:
if (clang_isCursorDefinition(c) && !name.empty() && name.find(' ') == std::string::npos) {
std::string usr = str(clang_getCursorUSR(c));
if (!recordNames.count(usr)) recordNames[usr] = name;
}
break;
case CXCursor_EnumDecl:
clang_visitChildren(c, [](CXCursor k, CXCursor, CXClientData d) {
static_cast<Importer *>(d)->visitTop(k);
return CXChildVisit_Continue;
}, this);
break;
case CXCursor_EnumConstantDecl: {
CConst k;
k.type = Type::I64;
k.i = clang_getEnumConstantDeclValue(c);
out.consts.emplace(name, k);
break;
}
case CXCursor_MacroDefinition: {
if (clang_Cursor_isMacroBuiltin(c) || clang_Cursor_isMacroFunctionLike(c)) break;
CConst k;
if (macroValue(c, k)) out.consts.emplace(name, k);
break;
}
default: break;
}
}
clang_disposeTokens(tu, toks, n);
while (parts.size() >= 2 && parts.front() == "(" && parts.back() == ")") {
parts.erase(parts.begin());
parts.pop_back();
}
bool neg = false;
if (parts.size() == 2 && parts[0] == "-") {
neg = true;
parts.erase(parts.begin());
}
if (parts.size() != 1) return false;
const std::string &lit = parts[0];
if (lit.empty() || !(std::isdigit((unsigned char)lit[0]) || lit[0] == '.')) return false;
bool isFloat = lit.find_first_of(".eE") != std::string::npos && lit.rfind("0x", 0) != 0;
char *end;
if (isFloat) {
out.type = Type::F64;
out.f = std::strtod(lit.c_str(), &end);
if (neg) out.f = -out.f;
} else {
out.type = Type::I64;
out.i = (long long)std::strtoull(lit.c_str(), &end, 0); // C rules: 0x.., 0755
if (neg) out.i = -out.i;
}
for (; *end; end++) // allow suffixes: 10UL, 1.0f
if (!std::strchr("uUlLfF", *end)) return false;
return true;
}
struct Visit {
CImports *out;
CXTranslationUnit tu;
std::string header;
};
CXChildVisitResult visit(CXCursor c, CXCursor, CXClientData data) {
Visit &v = *static_cast<Visit *>(data);
CImports &out = *v.out;
std::string name = str(clang_getCursorSpelling(c));
switch (clang_getCursorKind(c)) {
case CXCursor_FunctionDecl: {
if (out.fns.count(name)) break;
CFunc f;
f.name = name;
f.header = v.header;
if (clang_Cursor_getStorageClass(c) == CX_SC_Static) {
f.unsupported = "is a static inline C function (it has no code to link against)";
out.fns[name] = f;
break;
}
CXType ft = clang_getCanonicalType(clang_getCursorType(c));
if (ft.kind == CXType_FunctionNoProto) {
f.unsupported = "has no parameter list in its C declaration";
out.fns[name] = f;
break;
}
std::string why;
if (!mapType(clang_getResultType(ft), f.ret, why)) f.unsupported = why;
int n = clang_getNumArgTypes(ft);
for (int k = 0; k < n && f.unsupported.empty(); k++) {
Type t;
if (!mapType(clang_getArgType(ft, k), t, why)) f.unsupported = why;
f.params.push_back(t);
}
f.variadic = clang_isFunctionTypeVariadic(ft);
out.fns[name] = f;
break;
}
case CXCursor_VarDecl: {
if (clang_Cursor_getStorageClass(c) != CX_SC_Extern || out.globals.count(name)) break;
CGlobal g;
std::string why;
if (mapType(clang_getCursorType(c), g.type, why)) out.globals[name] = g;
break;
}
case CXCursor_EnumDecl:
clang_visitChildren(c, visit, data);
break;
case CXCursor_EnumConstantDecl: {
CConst k;
k.type = Type::I64;
k.i = clang_getEnumConstantDeclValue(c);
out.consts.emplace(name, k);
break;
}
case CXCursor_MacroDefinition: {
if (clang_Cursor_isMacroBuiltin(c) || clang_Cursor_isMacroFunctionLike(c)) break;
CConst k;
if (macroValue(v.tu, c, k)) out.consts.emplace(name, k);
break;
}
default: break;
}
return CXChildVisit_Continue;
}
} // namespace
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir) {
CImports importHeaders(const std::vector<Import> &imports, const std::vector<std::string> &dirs) {
CImports out;
if (imports.empty()) return out;
bool any = false;
for (const Import &im : imports) any |= im.isC;
if (!any) return out;
std::vector<std::string> args = {"-x", "c", "-std=gnu11", "-I" + dir};
std::vector<std::string> args = {"-x", "c", "-std=gnu11"};
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;
for (const std::string &a : args) argv.push_back(a.c_str());
Importer imp(out);
CXIndex index = clang_createIndex(0, 0);
for (const Import &im : imports) {
if (!im.isC) continue;
std::string src = "#include \"" + im.path + "\"\n";
CXUnsavedFile file = {"finch_import.c", src.c_str(), (unsigned long)src.size()};
CXTranslationUnit tu = nullptr;
CXErrorCode err = clang_parseTranslationUnit2(
index, "finch_import.c", argv.data(), (int)argv.size(), &file, 1,
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies, &tu);
if (err != CXError_Success || !tu) fail(im.pos.line, im.pos.col, "couldn't read C header '" + im.path + "'");
if (err != CXError_Success || !tu) failAt(im.pos.file, im.pos.line, im.pos.col, "couldn't read C header '" + im.path + "'");
for (unsigned k = 0; k < clang_getNumDiagnostics(tu); k++) {
CXDiagnostic d = clang_getDiagnostic(tu, k);
@ -231,14 +342,18 @@ CImports importHeaders(const std::vector<Import> &imports, const std::string &di
std::string msg = str(clang_getDiagnosticSpelling(d));
clang_disposeDiagnostic(d);
if (msg.find("file not found") != std::string::npos)
fail(im.pos.line, im.pos.col, "can't find the C header '" + im.path + "'");
fail(im.pos.line, im.pos.col, "C header '" + im.path + "' has an error: " + msg);
failAt(im.pos.file, im.pos.line, im.pos.col, "can't find the C header '" + im.path + "'");
failAt(im.pos.file, im.pos.line, im.pos.col, "C header '" + im.path + "' has an error: " + msg);
}
clang_disposeDiagnostic(d);
}
Visit v{&out, tu, im.path};
clang_visitChildren(clang_getTranslationUnitCursor(tu), visit, &v);
imp.tu = tu;
imp.header = im.path;
clang_visitChildren(clang_getTranslationUnitCursor(tu), [](CXCursor c, CXCursor, CXClientData d) {
static_cast<Importer *>(d)->visitTop(c);
return CXChildVisit_Continue;
}, &imp);
clang_disposeTranslationUnit(tu);
}
clang_disposeIndex(index);

View file

@ -2,11 +2,13 @@
#include "ast.h"
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
// What Finch learned from `import "something.h"`.
// C structs by value appear in types as Named{name, module "C"}.
struct CFunc {
std::string name;
@ -27,11 +29,28 @@ struct CGlobal { // extern variables like stdout
Type type;
};
struct CField {
std::string name;
Type type;
long long offset = 0; // bytes
long long size = 0, align = 1;
bool hidden = false; // unions, bitfields, unmappable types: kept for layout only
};
struct CStruct {
std::string name;
std::vector<CField> fields;
long long size = 0, align = 1;
std::string unsupported; // why it can't be used by value (pointers to it still work)
bool hasHidden = false;
};
struct CImports {
std::unordered_map<std::string, CFunc> fns;
std::unordered_map<std::string, CConst> consts;
std::unordered_map<std::string, CGlobal> globals;
std::map<std::string, CStruct> structs;
};
// `dir` is the folder of the .fn file, searched first for "local.h" headers.
CImports importHeaders(const std::vector<Import> &imports, const std::string &dir);
// `dirs`: folders searched first for "local.h" headers (the .fn files' folders).
CImports importHeaders(const std::vector<Import> &imports, const std::vector<std::string> &dirs);

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@
#include <memory>
// All files of the program (the main file first, then its modules) become one LLVM module.
// `tm` describes the machine we compile for (sizes, alignment), which shapes the IR.
std::unique_ptr<llvm::Module> generate(const Program &prog, const CImports &c, llvm::LLVMContext &ctx,
llvm::TargetMachine &tm);
std::unique_ptr<llvm::Module> generate(const std::vector<Program> &progs, const CImports &c, llvm::LLVMContext &ctx,
llvm::TargetMachine &tm, bool debug);

703
src/codegen_builtins.cpp Normal file
View file

@ -0,0 +1,703 @@
// Code generation, part 3: calls, built-in functions, conversions and methods.
#include "codegen_impl.h"
using namespace llvm;
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"};
FType t;
return names.count(n) || typeFromName(n, t);
}
// Help people who forgot an import.
std::string Codegen::headerHint(const std::string &name) {
static const std::unordered_map<std::string, const char *> known = {
{"printf", "stdio.h"}, {"puts", "stdio.h"}, {"putchar", "stdio.h"}, {"scanf", "stdio.h"},
{"fopen", "stdio.h"}, {"getchar", "stdio.h"}, {"sqrt", "math.h"}, {"pow", "math.h"},
{"sin", "math.h"}, {"cos", "math.h"}, {"floor", "math.h"}, {"fabs", "math.h"},
{"malloc", "stdlib.h"}, {"rand", "stdlib.h"}, {"srand", "stdlib.h"}, {"abs", "stdlib.h"},
{"atoi", "stdlib.h"}, {"strlen", "string.h"}, {"strcpy", "string.h"}, {"memcpy", "string.h"},
{"time", "time.h"}, {"sleep", "unistd.h"}, {"usleep", "unistd.h"},
};
auto it = known.find(name);
return it == known.end() ? "" : std::string(" (it's from C: add import \"") + it->second + "\" at the top)";
}
void Codegen::checkArgs(const std::vector<ExprPtr> &args, const std::vector<std::string> &names, size_t want, Pos p,
const std::string &name, bool variadic) {
for (size_t i = 0; i < names.size(); i++)
if (!names[i].empty())
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "named arguments (" + names[i] + ": ...) are only for creating structs");
if (args.size() == want || (variadic && args.size() > want)) return;
failAt(p.file, p.line, p.col, "'" + name + "' takes " + (variadic ? "at least " : "") + std::to_string(want) +
" argument(s), but got " + std::to_string(args.size()));
}
Function *Codegen::libc(const char *name, llvm::Type *ret, std::vector<llvm::Type *> params, bool vararg) {
FunctionCallee c = mod->getOrInsertFunction(name, FunctionType::get(ret, params, vararg));
return cast<Function>(c.getCallee());
}
// The runtime (runtime/finch_rt.c), declared on first use.
Function *Codegen::rt(const std::string &name) {
if (Function *f = mod->getFunction(name)) return f;
llvm::Type *P = b.getPtrTy(), *I = b.getInt64Ty(), *I32 = b.getInt32Ty(), *D = b.getDoubleTy(), *V = b.getVoidTy();
struct Sig { llvm::Type *ret; std::vector<llvm::Type *> params; };
std::map<std::string, Sig> sigs = {
{"finch_panic", {V, {P, I, P}}},
{"finch_panic_index", {V, {P, I, I, I}}},
{"finch_alloc", {P, {I}}},
{"finch_zalloc", {P, {I}}},
{"finch_str_copy", {V, {P, P}}},
{"finch_str_own", {V, {P}}},
{"finch_str_drop", {V, {P}}},
{"finch_str_from_c", {V, {P, P}}},
{"finch_str_concat", {V, {P, P, P}}},
{"finch_str_eq", {I32, {P, P}}},
{"finch_str_cmp", {I, {P, P}}},
{"finch_str_from_int", {V, {P, I}}},
{"finch_str_from_uint", {V, {P, I}}},
{"finch_str_from_float", {V, {P, D}}},
{"finch_str_from_char", {V, {P, b.getInt8Ty()}}},
{"finch_str_from_bool", {V, {P, I32}}},
{"finch_str_to_int", {I, {P, P, I}}},
{"finch_str_to_float", {D, {P, P, I}}},
{"finch_str_sub", {V, {P, P, I, I, P, I}}},
{"finch_str_find", {I, {P, P}}},
{"finch_str_starts", {I32, {P, P}}},
{"finch_str_ends", {I32, {P, P}}},
{"finch_str_trim", {V, {P, P}}},
{"finch_str_upper", {V, {P, P}}},
{"finch_str_lower", {V, {P, P}}},
{"finch_str_replace", {V, {P, P, P, P}}},
{"finch_str_split", {V, {P, P, P}}},
{"finch_str_join", {V, {P, P, P}}},
{"finch_str_repeat", {V, {P, P, I}}},
{"finch_str_from_bytes", {V, {P, P}}},
{"finch_arr_reserve", {V, {P, I, I}}},
{"finch_arr_make", {V, {P, I, I}}},
{"finch_arr_resize", {V, {P, I, I}}},
{"finch_arr_clone_raw", {V, {P, P, I}}},
{"finch_arr_free", {V, {P}}},
{"finch_arr_insert_gap", {V, {P, I, I, P, I}}},
{"finch_arr_remove_gap", {V, {P, I, I}}},
{"finch_bytes_from_str", {V, {P, P}}},
{"finch_args", {V, {P, I32, P}}},
{"finch_input", {V, {P, P}}},
{"finch_file_exists", {I32, {P}}},
{"finch_read_file", {V, {P, P, P, I}}},
{"finch_write_file", {I32, {P, P}}},
};
auto it = sigs.find(name);
if (it == sigs.end()) {
std::fprintf(stderr, "internal compiler error: unknown runtime function %s\n", name.c_str());
std::exit(2);
}
Function *f = Function::Create(FunctionType::get(it->second.ret, it->second.params, false), Function::ExternalLinkage,
name, mod.get());
if (name == "finch_panic" || name == "finch_panic_index") {
f->addFnAttr(Attribute::NoReturn);
f->addFnAttr(Attribute::Cold);
}
if (name == "finch_str_from_char") f->addParamAttr(1, Attribute::SExt);
return f;
}
// ---------- runtime errors ----------
Value *Codegen::fileName(Pos p) {
std::string key = "file." + std::to_string(p.file);
if (GlobalVariable *g = mod->getNamedGlobal("finch." + key)) return g;
GlobalVariable *g = b.CreateGlobalString(g_files[p.file].path, "finch." + key);
return g;
}
void Codegen::panicIf(Value *cond, const std::string &msg, Pos p) {
BasicBlock *bad = newBlock("panic");
BasicBlock *ok = newBlock("ok");
b.CreateCondBr(cond, bad, ok);
b.SetInsertPoint(bad);
b.CreateCall(rt("finch_panic"), {fileName(p), b.getInt64(p.line), b.CreateGlobalString(msg, "panic.msg")});
b.CreateUnreachable();
b.SetInsertPoint(ok);
}
void Codegen::boundsCheck(Value *i, Value *len, Pos p) {
BasicBlock *bad = newBlock("out.of.range");
BasicBlock *ok = newBlock("in.range");
b.CreateCondBr(b.CreateICmpUGE(i, len), bad, ok); // unsigned: catches negative indexes too
b.SetInsertPoint(bad);
b.CreateCall(rt("finch_panic_index"), {fileName(p), b.getInt64(p.line), i, len});
b.CreateUnreachable();
b.SetInsertPoint(ok);
}
// Dividing by zero is undefined in C; in Finch it is a clear error.
void Codegen::checkDivisor(const Value_ &l, const Value_ &r, Pos p) {
auto *c = dyn_cast<ConstantInt>(r.v);
if (c && c->isZero()) failAt(p.file, p.line, p.col, "division by zero");
if (!c) panicIf(b.CreateICmpEQ(r.v, ConstantInt::get(r.v->getType(), 0)), "division by zero", p);
// the smallest signed number divided by -1 doesn't fit anywhere
if (l.type.isSigned() && (!c || c->isMinusOne())) {
Value *min = ConstantInt::get(l.v->getType(), APInt::getSignedMinValue(l.type.bits()));
Value *minusOne = ConstantInt::get(r.v->getType(), -1, true);
panicIf(b.CreateAnd(b.CreateICmpEQ(l.v, min), b.CreateICmpEQ(r.v, minusOne)),
"division overflows (the smallest number divided by -1)", p);
}
}
Value *Codegen::cGlobal(const std::string &name) {
if (GlobalVariable *gv = mod->getNamedGlobal(name)) return gv;
FType t = resolve(cimports.globals.at(name).type, Pos{});
return new GlobalVariable(*mod, ty(t), false, GlobalValue::ExternalLinkage, nullptr, name);
}
// ---------- calls ----------
Value_ Codegen::call(const CallExpr &c) {
const std::string &n = c.callee;
Pos p = c.pos;
auto one = [&](const char *what) -> const Expr & {
checkArgs(c.args, c.argNames, 1, p, n);
(void)what;
return *c.args[0];
};
if (n == "print") {
checkArgs(c.args, c.argNames, 0, p, n, true);
return print(c.args, p);
}
if (n == "addr") {
checkArgs(c.args, c.argNames, 1, p, n);
return addrOf(c.args, p);
}
if (n == "input") {
if (c.args.size() > 1) checkArgs(c.args, c.argNames, 1, p, n);
Value_ prompt = c.args.empty() ? Value_{strConst(""), FType::Str, false, true}
: coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the question");
Value *out = tmpOf(ty(FType::Str));
b.CreateCall(rt("finch_input"), {out, tmp(prompt.v)});
release(prompt);
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
}
if (n == "new") {
const Expr &a = one("value");
Value_ v = expr(a);
if (v.type.kind == FType::Void || v.type.kind == FType::Null)
failAt(a.pos.file, a.pos.line, a.pos.col, "new(...) needs a value to put in memory, like new(Point(x: 1, y: 2))");
Value *mem = b.CreateCall(rt("finch_alloc"), {elemSize(v.type)});
b.CreateStore(own(v), mem);
return {mem, FType::ptrTo(v.type)};
}
if (n == "free") {
const Expr &a = one("pointer");
Value_ v = expr(a);
if (!v.type.isPtr())
failAt(a.pos.file, a.pos.line, a.pos.col, "free(...) is only for pointers from new(...) or C; arrays, str and structs are freed by Finch at the end of their block");
if (v.type.elem && owning(*v.type.elem)) {
BasicBlock *drop = newBlock("free.drop"), *done = newBlock("free.done");
b.CreateCondBr(b.CreateIsNull(v.v), done, drop);
b.SetInsertPoint(drop);
dropAt(v.v, *v.type.elem);
b.CreateBr(done);
b.SetInsertPoint(done);
}
b.CreateCall(libc("free", b.getVoidTy(), {b.getPtrTy()}), {v.v});
return {nullptr, FType::Void};
}
if (n == "exit") {
Value *code = coerce(expr(one("code")), FType::I32, c.args[0]->pos, "the exit code").v;
b.CreateCall(libc("fflush", b.getInt32Ty(), {b.getPtrTy()}), {ConstantPointerNull::get(b.getPtrTy())});
b.CreateCall(libc("exit", b.getVoidTy(), {b.getInt32Ty()}), {code});
return {nullptr, FType::Void};
}
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);
Value_ res;
if (n == "file_exists") {
res = {b.CreateICmpNE(b.CreateCall(rt("finch_file_exists"), {pp}), b.getInt32(0)), FType::Bool};
} else {
Value *out = tmpOf(ty(FType::Str));
b.CreateCall(rt("finch_read_file"), {out, pp, fileName(p), b.getInt64(p.line)});
res = {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
}
release(path);
return res;
}
if (n == "write_file") {
checkArgs(c.args, c.argNames, 2, p, n);
Value_ path = coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the file name");
Value_ text = coerce(expr(*c.args[1]), FType::Str, c.args[1]->pos, "the text");
Value *ok = b.CreateCall(rt("finch_write_file"), {tmp(path.v), tmp(text.v)});
release(path);
release(text);
return {b.CreateICmpNE(ok, b.getInt32(0)), FType::Bool};
}
FType target;
if (typeFromName(n, target)) {
checkArgs(c.args, c.argNames, 1, p, n);
return convert(target, c.args, p, n);
}
ModuleScope &m = modules[curModule];
if (auto it = m.fns.find(n); it != m.fns.end()) return callFinch(it->second, c.args, c.argNames, p, n);
if (StructInfo *s = findStruct("", n, false)) return construct(s, c.args, c.argNames, p);
if (auto cf = cimports.fns.find(n); cf != cimports.fns.end()) {
checkArgs(c.args, c.argNames, cf->second.params.size(), p, n, cf->second.variadic);
return callC(cf->second, c.args, p);
}
if (lookup(n)) failAt(p.file, p.line, p.col, "'" + n + "' is a variable, not a function");
for (auto &[mname, ms] : modules)
if (ms.fns.count(n) && mname != curModule && !mname.empty())
failAt(p.file, p.line, p.col, "there is no function named '" + n + "' here; it is in the module " + mname +
" (write " + mname + "." + n + "(...)" + (m.imports.count(mname) ? "" : " and add: import " + mname) + ")");
failAt(p.file, p.line, p.col, "there is no function named '" + n + "'" + headerHint(n));
}
Value_ Codegen::callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
const std::string &shown) {
const FnDecl &d = *fn.decl;
if (fn.module.empty() && d.name == "main") failAt(p.file, p.line, p.col, "main can't be called, it is where the program starts");
checkArgs(args, names, fn.params.size(), p, shown);
std::vector<Value *> vals;
std::vector<Value_> fresh;
for (size_t i = 0; i < args.size(); i++) {
Value_ v = coerce(exprWant(*args[i], fn.params[i]), fn.params[i], args[i]->pos, "argument '" + d.params[i].name + "'");
vals.push_back(v.v); // owning values are lent: the function never drops them
if (v.fresh) fresh.push_back(v);
}
setLoc(p);
Value *r = b.CreateCall(fn.llvm, vals);
for (auto &v : fresh) release(v);
if (fn.ret.kind == FType::Void) return {nullptr, FType::Void};
return {r, fn.ret, false, owning(fn.ret)};
}
Value_ Codegen::construct(StructInfo *s, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p) {
resolveStruct(s);
FType t(FType::Struct);
t.info = s;
t.name = s->name;
t.module = s->module == "C" ? "" : s->module;
std::vector<const StructInfo::F *> fields;
for (auto &f : s->fields) fields.push_back(&f);
bool named = !names.empty() && !names[0].empty();
std::vector<const Expr *> given(fields.size(), nullptr);
for (size_t i = 0; i < args.size(); i++) {
Pos ap = args[i]->pos;
if (names[i].empty() == named)
failAt(ap.file, ap.line, ap.col, "give either all fields by name (x: 1, y: 2) or none (1, 2), not a mix");
if (!named) {
if (args.size() != fields.size())
failAt(p.file, p.line, p.col, s->name + " has " + std::to_string(fields.size()) + " fields, but got " +
std::to_string(args.size()) + " values (or name them: " + s->name + "(field: value))");
given[i] = args[i].get();
continue;
}
size_t k = 0;
while (k < fields.size() && fields[k]->name != names[i]) k++;
if (k == fields.size()) failAt(ap.file, ap.line, ap.col, s->name + " has no field '" + names[i] + "'");
if (given[k]) failAt(ap.file, ap.line, ap.col, "the field '" + names[i] + "' is given twice");
given[k] = args[i].get();
}
Value *v = zero(t);
for (size_t k = 0; k < fields.size(); k++) {
const StructInfo::F &f = *fields[k];
Value_ fv;
if (given[k]) {
fv = coerce(exprWant(*given[k], f.type), f.type, given[k]->pos, "the field '" + f.name + "'");
} else if (f.init) {
// a default value is evaluated where the struct is defined, without the caller's variables
std::vector<Scope> saved;
std::swap(saved, scopes);
scopes.reserve(256);
pushScope();
std::string savedModule = curModule;
curModule = s->module;
fv = coerce(exprWant(*f.init, f.type), f.type, f.init->pos, "the default of '" + f.name + "'");
curModule = savedModule;
scopes.pop_back();
std::swap(saved, scopes);
} else {
continue; // zero
}
v = b.CreateInsertValue(v, own(fv), f.llvmIndex);
}
return {v, t, false, owning(t)};
}
Value_ Codegen::callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p) {
if (!f.unsupported.empty())
failAt(p.file, p.line, p.col, "the C function '" + f.name + "' can't be used from Finch yet: it " + f.unsupported);
std::vector<Value_> vals;
for (size_t i = 0; i < args.size(); i++) {
if (i < f.params.size()) {
FType pt = resolve(f.params[i], p);
vals.push_back(coerce(exprWant(*args[i], pt), pt, args[i]->pos, "argument " + std::to_string(i + 1) + " of " + f.name));
} else {
Value_ v = expr(*args[i]);
if (v.type.kind == FType::Void) failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "this gives back nothing");
if (v.type.kind == FType::Struct || v.type.kind == FType::Array)
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "structs and arrays can't be passed to the '...' part of a C function");
vals.push_back(v);
}
}
setLoc(p);
Value_ r = emitCCall(f, declareC(f), vals, p);
for (auto &v : vals) release(v);
return r;
}
// ---------- built-ins ----------
// print(a, b, c) prints the values separated by spaces, then a newline.
Value_ Codegen::print(const std::vector<ExprPtr> &args, Pos) {
for (size_t i = 0; i < args.size(); i++) {
Value_ v = expr(*args[i]);
if (v.type.kind == FType::Void)
failAt(args[i]->pos.file, args[i]->pos.line, args[i]->pos.col, "this gives back nothing, so there is nothing to print");
if (i) printf_(" ", {});
emitPrint(v.v, v.type, false);
release(v);
}
printf_("\n", {});
return {nullptr, FType::Void};
}
// addr(x) gives a pointer to x; addr(function) gives a C-callable function pointer.
Value_ Codegen::addrOf(const std::vector<ExprPtr> &args, Pos p) {
const Expr &e = *args[0];
if (e.kind == ExprKind::Var) {
const std::string &n = static_cast<const VarExpr &>(e).name;
Var *v = lookup(n);
if (!v) {
auto &fns = modules[curModule].fns;
if (auto it = fns.find(n); it != fns.end()) return {cThunk(it->second, p), FType::Ptr};
}
if (v && v->readonly) failAt(e.pos.file, e.pos.line, e.pos.col, "can't take the address of a loop variable");
}
if (e.kind != ExprKind::Var && e.kind != ExprKind::Member && e.kind != ExprKind::Index)
failAt(e.pos.file, e.pos.line, e.pos.col, "addr(...) needs a variable, a field or an element, like addr(x)");
Place pl = place(e, true);
return {pl.addr, FType::ptrTo(pl.type)};
}
// Explicit conversions: int(x), u8(x), float(x), char(x), bool(x), str(x), ptr(x) ...
Value_ Codegen::convert(const FType &to, const std::vector<ExprPtr> &args, Pos, const std::string &name) {
Value_ v = expr(*args[0]);
const FType &from = v.type;
Pos p = args[0]->pos;
if (to.kind == FType::Str) {
Value *out = tmpOf(ty(FType::Str));
if (from.kind == FType::Str) return {copyValue(v.v, FType::Str), FType::Str, false, true};
if (from.isPtr()) return strFromC(v.v);
if (from.isSigned()) b.CreateCall(rt("finch_str_from_int"), {out, b.CreateSExt(v.v, b.getInt64Ty())});
else if (from.isUnsigned()) b.CreateCall(rt("finch_str_from_uint"), {out, b.CreateZExt(v.v, b.getInt64Ty())});
else if (from.isFloat()) b.CreateCall(rt("finch_str_from_float"), {out, b.CreateFPExt(v.v, b.getDoubleTy())});
else if (from.kind == FType::Char) b.CreateCall(rt("finch_str_from_char"), {out, v.v});
else if (from.kind == FType::Bool) b.CreateCall(rt("finch_str_from_bool"), {out, b.CreateZExt(v.v, b.getInt32Ty())});
else if (from.kind == FType::Array && (from.elem->kind == FType::U8 || from.elem->kind == FType::Char)) {
b.CreateCall(rt("finch_str_from_bytes"), {out, tmp(v.v)});
release(v);
} else {
failAt(p.file, p.line, p.col, "can't turn " + from.show() + " into str");
}
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
}
if (from == to) return v;
if (from.kind == FType::Str && (to.isInt() || to.isFloat())) {
Value *sp = tmp(v.v);
Value_ r;
if (to.isInt()) r = {intCast(b.CreateCall(rt("finch_str_to_int"), {sp, fileName(p), b.getInt64(p.line)}), FType::I64, to), to};
else r = {b.CreateFPCast(b.CreateCall(rt("finch_str_to_float"), {sp, fileName(p), b.getInt64(p.line)}), ty(to)), to};
release(v);
return r;
}
if (to.isInt() || to.kind == FType::Char) {
if (from.isInt() || from.kind == FType::Char) return {b.CreateIntCast(v.v, ty(to), from.isSigned()), to};
if (from.kind == FType::Bool) return {b.CreateZExt(v.v, ty(to)), to};
if (from.isFloat() && to.isInt()) return {to.isSigned() ? b.CreateFPToSI(v.v, ty(to)) : b.CreateFPToUI(v.v, ty(to)), to};
}
if (to.isFloat()) {
if (from.isInt()) return {intToFloat(v.v, from, to), to};
if (from.isFloat()) return {b.CreateFPCast(v.v, ty(to)), to};
}
if (to.kind == FType::Bool && from.isInt()) return {b.CreateICmpNE(v.v, ConstantInt::get(ty(from), 0)), to};
if (to.isPtr() && (from.isPtr() || from.kind == FType::Null)) return {v.v, to};
if (to.isPtr() && from.kind == FType::Str) failAt(p.file, p.line, p.col, "use .ptr to get a str's address (it is only valid while the str lives)");
failAt(p.file, p.line, p.col, "can't turn " + from.show() + " into " + name);
}
// ---------- methods ----------
Value_ Codegen::method(const MethodExpr &m) {
Pos p = m.pos;
// math.sqrt(x), math.Vec(1, 2)
if (m.obj->kind == ExprKind::Var) {
const std::string &mn = static_cast<const VarExpr &>(*m.obj).name;
if (!lookup(mn) && modules[curModule].imports.count(mn)) {
auto mi = modules.find(mn);
if (mi == modules.end()) failAt(p.file, p.line, p.col, "the module '" + mn + "' was not found");
if (auto it = mi->second.fns.find(m.name); it != mi->second.fns.end())
return callFinch(it->second, m.args, m.argNames, p, mn + "." + m.name);
if (auto it = mi->second.structs.find(m.name); it != mi->second.structs.end())
return construct(it->second, m.args, m.argNames, p);
failAt(p.file, p.line, p.col, "the module '" + mn + "' has no function or struct named '" + m.name + "'");
}
if (!lookup(mn) && !cimports.globals.count(mn) && !cimports.consts.count(mn))
failAt(m.obj->pos.file, m.obj->pos.line, m.obj->pos.col,
"there is no variable or module named '" + mn + "' (to use a module, add import " + mn + " at the top)");
}
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
LRef r = ref(*m.obj, changing.count(m.name) > 0);
FType t = r.isPlace ? r.pl.type : r.val.type;
if (t.kind == FType::Array) {
Value_ recv = r.isPlace ? Value_{r.pl.addr, t} : r.val;
if (!r.isPlace) {
if (changing.count(m.name)) failAt(p.file, p.line, p.col, m.name + "() would change a temporary array; store it in a variable first");
recv = {tmp(r.val.v), t, false, r.val.fresh};
}
Value_ out = arrayMethod(m, &recv);
if (!r.isPlace && r.val.fresh) dropAt(recv.v, t);
return out;
}
if (t.kind == FType::Str) {
Value_ sv = r.isPlace ? Value_{b.CreateLoad(ty(t), r.pl.addr), t} : r.val;
Value *addr = tmp(sv.v); // the runtime takes strings by address
auto argStr = [&](size_t i, const char *what) {
return coerce(expr(*m.args[i]), FType::Str, m.args[i]->pos, what);
};
auto argInt = [&](size_t i, const char *what) {
return coerce(expr(*m.args[i]), FType::I64, m.args[i]->pos, what).v;
};
auto need = [&](size_t n) { checkArgs(m.args, m.argNames, n, p, m.name); };
Value *out = tmpOf(ty(FType::Str));
Value_ res;
const std::string &n = m.name;
auto strRes = [&]() { return Value_{b.CreateLoad(ty(FType::Str), out), FType::Str, false, true}; };
if (n == "sub") {
need(2);
Value *a = argInt(0, "the start"), *e = argInt(1, "the end");
b.CreateCall(rt("finch_str_sub"), {out, addr, a, e, fileName(p), b.getInt64(p.line)});
res = strRes();
} else if (n == "find" || n == "contains" || n == "starts_with" || n == "ends_with") {
need(1);
Value_ t2 = argStr(0, "the text to look for");
Value *tp = tmp(t2.v);
if (n == "find") res = {b.CreateCall(rt("finch_str_find"), {addr, tp}), FType::I64};
else if (n == "contains") res = {b.CreateICmpSGE(b.CreateCall(rt("finch_str_find"), {addr, tp}), b.getInt64(0)), FType::Bool};
else res = {b.CreateICmpNE(b.CreateCall(rt(n == "starts_with" ? "finch_str_starts" : "finch_str_ends"), {addr, tp}), b.getInt32(0)), FType::Bool};
release(t2);
} else if (n == "trim" || n == "upper" || n == "lower") {
need(0);
b.CreateCall(rt("finch_str_" + n), {out, addr});
res = strRes();
} else if (n == "replace") {
need(2);
Value_ a = argStr(0, "the text to replace"), c = argStr(1, "the new text");
b.CreateCall(rt("finch_str_replace"), {out, addr, tmp(a.v), tmp(c.v)});
release(a);
release(c);
res = strRes();
} else if (n == "split") {
need(1);
Value_ sep = argStr(0, "the separator");
FType at = FType::arrayOf(FType::Str);
Value *ao = tmpOf(ty(at));
b.CreateCall(rt("finch_str_split"), {ao, addr, tmp(sep.v)});
release(sep);
res = {b.CreateLoad(ty(at), ao), at, false, true};
} else if (n == "repeat") {
need(1);
b.CreateCall(rt("finch_str_repeat"), {out, addr, argInt(0, "the count")});
res = strRes();
} else if (n == "bytes") {
need(0);
FType at = FType::arrayOf(FType::U8);
Value *ao = tmpOf(ty(at));
b.CreateCall(rt("finch_bytes_from_str"), {ao, addr});
res = {b.CreateLoad(ty(at), ao), at, false, true};
} else {
failAt(p.file, p.line, p.col, "a str has no method '" + n + "' (it has: sub, find, contains, starts_with, ends_with, split, trim, upper, lower, replace, repeat, bytes)");
}
if (!r.isPlace) release(sv);
return res;
}
if (t.kind == FType::Struct)
failAt(p.file, p.line, p.col, "structs don't have methods; write a function and call it: " + m.name + "(value, ...)");
failAt(p.file, p.line, p.col, t.show() + " has no methods");
}
// An element compare for find/contains/sort: -1, 0, 1 (only numbers, chars, bools and str)
static bool comparable(const FType &t) {
return t.isNumber() || t.kind == FType::Char || t.kind == FType::Bool || t.kind == FType::Str;
}
Value_ Codegen::arrayMethod(const MethodExpr &m, Value_ *recv) {
Pos p = m.pos;
const std::string &n = m.name;
FType at = recv->type, et = *at.elem;
llvm::Type *AT = ty(at), *ET = ty(et);
Value *a = recv->v; // the array's address
auto need = [&](size_t k) { checkArgs(m.args, m.argNames, k, p, n); };
auto lenAddr = [&]() { return b.CreateStructGEP(AT, a, 1); };
auto len = [&]() { return b.CreateLoad(b.getInt64Ty(), lenAddr()); };
auto data = [&]() { return b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(AT, a, 0)); };
auto elemValue = [&](size_t i) { return own(coerce(exprWant(*m.args[i], et), et, m.args[i]->pos, "the element")); };
if (n == "push") {
need(1);
Value *v = elemValue(0);
Value *l = len();
b.CreateCall(rt("finch_arr_reserve"), {a, b.CreateAdd(l, b.getInt64(1)), elemSize(et)});
b.CreateStore(v, b.CreateInBoundsGEP(ET, data(), l));
b.CreateStore(b.CreateAdd(l, b.getInt64(1)), lenAddr());
return {nullptr, FType::Void};
}
if (n == "pop") {
need(0);
Value *l = len();
panicIf(b.CreateICmpEQ(l, b.getInt64(0)), "pop() on an empty array", p);
Value *last = b.CreateSub(l, b.getInt64(1));
Value *v = b.CreateLoad(ET, b.CreateInBoundsGEP(ET, data(), last));
b.CreateStore(last, lenAddr());
return {v, et, false, owning(et)};
}
if (n == "insert") {
need(2);
Value *i = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the position").v;
Value *v = elemValue(1);
b.CreateCall(rt("finch_arr_insert_gap"), {a, i, elemSize(et), fileName(p), b.getInt64(p.line)});
b.CreateStore(v, b.CreateInBoundsGEP(ET, data(), i));
return {nullptr, FType::Void};
}
if (n == "remove") {
need(1);
Value *i = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the position").v;
boundsCheck(i, len(), p);
Value *v = b.CreateLoad(ET, b.CreateInBoundsGEP(ET, data(), i));
b.CreateCall(rt("finch_arr_remove_gap"), {a, i, elemSize(et)});
return {v, et, false, owning(et)};
}
if (n == "clear" || n == "resize") {
need(n == "clear" ? 0 : 1);
Value *nl = n == "clear" ? (Value *)b.getInt64(0) : coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the new length").v;
if (owning(et)) { // drop the elements that go away
Value *l = len(), *d = data();
Value *from = b.CreateSelect(b.CreateICmpSLT(nl, l), b.CreateSelect(b.CreateICmpSLT(nl, b.getInt64(0)), b.getInt64(0), nl), l);
forN(b.CreateSub(l, from), [&](Value *k) { dropAt(b.CreateInBoundsGEP(ET, d, b.CreateAdd(from, k)), et); });
}
b.CreateCall(rt("finch_arr_resize"), {a, nl, elemSize(et)});
return {nullptr, FType::Void};
}
if (n == "find" || n == "contains") {
need(1);
if (!comparable(et)) failAt(p.file, p.line, p.col, n + "() works on arrays of numbers, chars, bools or str");
Value_ x = coerce(exprWant(*m.args[0], et), et, m.args[0]->pos, "the value to look for");
Value *xv = x.v;
Value *res = tmpOf(b.getInt64Ty());
b.CreateStore(b.getInt64(-1), res);
Value *l = len(), *d = data();
Value *i = tmpOf(b.getInt64Ty());
b.CreateStore(b.getInt64(0), i);
BasicBlock *cond = newBlock("find.cond"), *body = newBlock("find.body"), *hit = newBlock("find.hit"),
*next = newBlock("find.next"), *end = newBlock("find.end");
b.CreateBr(cond);
b.SetInsertPoint(cond);
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
b.CreateCondBr(b.CreateICmpSLT(iv, l), body, end);
b.SetInsertPoint(body);
Value_ ev{b.CreateLoad(ET, b.CreateInBoundsGEP(ET, d, iv)), et};
Value *eq = compare(BinOp::Eq, ev, {xv, et}, p).v;
b.CreateCondBr(eq, hit, next);
b.SetInsertPoint(hit);
b.CreateStore(iv, res);
b.CreateBr(end);
b.SetInsertPoint(next);
b.CreateStore(b.CreateAdd(iv, b.getInt64(1)), i);
b.CreateBr(cond);
b.SetInsertPoint(end);
release(x);
Value *r = b.CreateLoad(b.getInt64Ty(), res);
if (n == "find") return {r, FType::I64};
return {b.CreateICmpSGE(r, b.getInt64(0)), FType::Bool};
}
if (n == "join") {
need(1);
if (et.kind != FType::Str) failAt(p.file, p.line, p.col, "join() works on an array of str");
Value_ sep = coerce(expr(*m.args[0]), FType::Str, m.args[0]->pos, "the separator");
Value *out = tmpOf(ty(FType::Str));
b.CreateCall(rt("finch_str_join"), {out, a, tmp(sep.v)});
release(sep);
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
}
if (n == "slice") {
need(2);
Value *s = coerce(expr(*m.args[0]), FType::I64, m.args[0]->pos, "the start").v;
Value *e = coerce(expr(*m.args[1]), FType::I64, m.args[1]->pos, "the end").v;
Value *l = len();
panicIf(b.CreateOr(b.CreateOr(b.CreateICmpSLT(s, b.getInt64(0)), b.CreateICmpSGT(e, l)), b.CreateICmpSGT(s, e)),
"slice(start, end) is out of range", p);
Value *cnt = b.CreateSub(e, s);
Value *out = tmpOf(AT);
b.CreateCall(rt("finch_arr_make"), {out, cnt, elemSize(et)});
Value *src = data();
Value *dst = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(AT, out, 0));
forN(cnt, [&](Value *k) {
copyAt(b.CreateInBoundsGEP(ET, dst, k), b.CreateInBoundsGEP(ET, src, b.CreateAdd(s, k)), et);
});
return {b.CreateLoad(AT, out), at, false, true};
}
if (n == "reverse") {
need(0);
Value *l = len(), *d = data();
forN(b.CreateSDiv(l, b.getInt64(2)), [&](Value *k) {
Value *x = b.CreateInBoundsGEP(ET, d, k);
Value *y = b.CreateInBoundsGEP(ET, d, b.CreateSub(b.CreateSub(l, b.getInt64(1)), k));
Value *xv = b.CreateLoad(ET, x), *yv = b.CreateLoad(ET, y);
b.CreateStore(yv, x);
b.CreateStore(xv, y);
});
return {nullptr, FType::Void};
}
if (n == "sort") {
need(0);
if (!comparable(et) || et.kind == FType::Bool) failAt(p.file, p.line, p.col, "sort() works on arrays of numbers, chars or str");
std::string key = "cmp." + typeKey(et);
Function *cmp = helpers[key];
if (!cmp) {
cmp = Function::Create(FunctionType::get(b.getInt32Ty(), {b.getPtrTy(), b.getPtrTy()}, false),
Function::InternalLinkage, "finch." + key, mod.get());
helpers[key] = cmp;
IRBuilderBase::InsertPointGuard g(b);
DISubprogram *savedFn = diFn;
diFn = nullptr;
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", cmp));
b.SetCurrentDebugLocation(DebugLoc());
Value *x = cmp->getArg(0), *y = cmp->getArg(1);
Value *r;
if (et.kind == FType::Str) {
r = b.CreateTrunc(b.CreateCall(rt("finch_str_cmp"), {x, y}), b.getInt32Ty());
} else {
Value_ xv{b.CreateLoad(ET, x), et}, yv{b.CreateLoad(ET, y), et};
Value *lt = compare(BinOp::Less, xv, yv, p).v, *gt = compare(BinOp::Greater, xv, yv, p).v;
r = b.CreateSub(b.CreateZExt(gt, b.getInt32Ty()), b.CreateZExt(lt, b.getInt32Ty()));
}
b.CreateRet(r);
diFn = savedFn;
}
b.CreateCall(libc("qsort", b.getVoidTy(), {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty(), b.getPtrTy()}),
{data(), len(), elemSize(et), cmp});
return {nullptr, FType::Void};
}
failAt(p.file, p.line, p.col, "an array has no method '" + n + "' (it has: push, pop, insert, remove, clear, resize, find, contains, join, slice, reverse, sort)");
}
} // namespace finch

265
src/codegen_impl.h Normal file
View file

@ -0,0 +1,265 @@
#pragma once
// Internal to the code generator: the Codegen class, shared by codegen*.cpp and abi.cpp.
#include "codegen.h"
#include "error.h"
#include <llvm/IR/Constants.h>
#include <llvm/IR/DIBuilder.h>
#include <llvm/IR/IRBuilder.h>
#include <llvm/IR/Verifier.h>
#include <llvm/Support/raw_ostream.h>
#include <functional>
#include <map>
#include <set>
#include <unordered_map>
using FType = ::Type; // Finch types (llvm::Type is the LLVM one)
struct StructInfo {
std::string name;
std::string module; // "" main file, a module name, or "C" for imported C structs
bool isC = false;
const StructDecl *decl = nullptr; // Finch structs
const CStruct *cdecl = nullptr; // C structs
struct F {
std::string name;
FType type;
const Expr *init = nullptr; // default value
bool hidden = false; // C union/bitfield padding: has no name in Finch
unsigned llvmIndex = 0; // C structs carry explicit padding between fields
};
std::vector<F> fields;
llvm::StructType *llvm = nullptr;
int state = 0; // 0 = not resolved, 1 = resolving (cycle check), 2 = done
bool owning = false;
std::string unsupported; // C structs Finch can't lay out
Pos pos;
};
namespace finch {
struct Value_ {
llvm::Value *v;
FType type;
bool literal = false; // a number written in the code (or a C constant): adapts to the type around it
bool fresh = false; // an owning value nobody holds yet: it must be stored (moved) or released
};
struct Var {
llvm::Value *slot;
FType type;
bool readonly = false; // loop variables
std::string readonlyWhy;
bool owned = false; // dropped when its scope ends
int order = 0; // declaration order, for defer
bool moved = false; // returned out of the function: not dropped on that path
};
struct Cleanup {
bool isDefer;
std::string var; // Drop: the variable
const Stmt *body = nullptr; // Defer: the statement
int order = 0; // Defer: variables declared after it are not visible
};
struct Scope {
std::unordered_map<std::string, Var> vars;
std::vector<Cleanup> cleanups;
};
struct Fn {
llvm::Function *llvm = nullptr;
const FnDecl *decl = nullptr;
std::string module;
std::vector<bool> borrowParam; // owning params the function never changes: no copy
llvm::Function *cThunk = nullptr;
FType ret; // resolved types
std::vector<FType> params;
};
struct Loop {
llvm::BasicBlock *continueTo, *breakTo;
size_t scopeDepth; // scopes opened inside the loop are cleaned up by break/continue
};
struct Place { // something that can be assigned to
llvm::Value *addr;
FType type;
};
struct LRef {
bool isPlace;
Place pl;
Value_ val;
};
struct ModuleScope {
std::map<std::string, Fn> fns;
std::map<std::string, StructInfo *> structs;
std::set<std::string> imports; // Finch modules visible here
};
// How a C struct travels through registers (System V x86-64).
struct AbiArg {
enum Kind { Direct, Expand, Memory } kind = Direct;
std::vector<llvm::Type *> parts; // Expand: one value per eightbyte
llvm::Type *coerced = nullptr; // Expand: { parts... } for returns
};
class Codegen {
public:
Codegen(llvm::LLVMContext &c, const std::vector<Program> &progs, const CImports &ci, llvm::TargetMachine &tm,
bool debug);
std::unique_ptr<llvm::Module> run();
// ---------- state ----------
llvm::LLVMContext &ctx;
llvm::IRBuilder<> b;
std::unique_ptr<llvm::Module> mod;
const std::vector<Program> &progs;
const CImports &cimports;
const llvm::DataLayout *dl;
std::map<std::string, ModuleScope> modules;
std::map<std::string, StructInfo *> cStructs;
std::vector<std::unique_ptr<StructInfo>> structStore;
std::vector<Scope> scopes;
std::vector<Loop> loops;
const Fn *curFn = nullptr;
std::string curModule;
int varOrder = 0;
int deferLimit = -1; // while emitting a defer: hide variables declared after it
int inDefer = 0;
std::map<std::string, llvm::Function *> helpers; // drop/copy/print per type
// debug info (-g)
bool debug;
std::unique_ptr<llvm::DIBuilder> di;
std::vector<llvm::DIFile *> diFiles;
llvm::DICompileUnit *diUnit = nullptr;
llvm::DISubprogram *diFn = nullptr;
std::map<std::string, llvm::DIType *> diTypes;
// ---------- codegen.cpp: program, statements, expressions ----------
void declareStructs();
void declareFns();
void declare(const Program &p, const FnDecl &fn);
void define(Fn &fn);
void defineMainWrapper();
void block(const BlockStmt &blk);
void blockBody(const BlockStmt &blk);
void stmt(const Stmt &s);
void varDecl(const VarDeclStmt &s);
void assign(const AssignStmt &s);
void ifStmt(const IfStmt &s);
void whileStmt(const WhileStmt &s);
void forStmt(const ForStmt &s);
void forEachStmt(const ForEachStmt &s);
void returnStmt(const ReturnStmt &s);
void jump(bool isBreak, Pos p);
llvm::Value *condition(const Expr &e);
void continueAt(llvm::BasicBlock *bb, llvm::BasicBlock *deadEnd);
bool terminated();
llvm::BasicBlock *newBlock(const char *name);
void pushScope();
void popScope(); // emits the scope's cleanups if reachable
void emitCleanups(size_t downTo); // run cleanups of scopes [downTo, end) without popping
void emitScopeCleanups(size_t idx);
llvm::AllocaInst *slot(const FType &t, const std::string &name);
Var *lookup(const std::string &name);
Var &addVar(Pos p, const std::string &name, const FType &t, llvm::Value *init, bool owned, bool readonly = false,
unsigned argNo = 0);
Value_ expr(const Expr &e);
Value_ exprWant(const Expr &e, const FType &want);
Value_ binary(const BinaryExpr &e);
Value_ unary(const UnaryExpr &e);
Value_ logic(const BinaryExpr &e);
Value_ arith(BinOp op, Value_ l, Value_ r, Pos p);
Value_ arithOp(BinOp op, Value_ l, Value_ r, Pos p);
Value_ compare(BinOp op, Value_ l, Value_ r, Pos p);
bool unify(Value_ &l, Value_ &r);
[[noreturn]] void badOperands(BinOp op, const Value_ &l, const Value_ &r, Pos p);
LRef ref(const Expr &e, bool forWrite = false); // an address if `e` has one, else a value
Place place(const Expr &e, bool forWrite = false);
LRef member(const MemberExpr &m, bool forWrite);
LRef index(const IndexExpr &e, bool forWrite);
Value_ arrayLit(const ArrayLitExpr &a, const FType *want);
std::string rootVar(const Expr &e);
bool mutates(const std::string &name, const Stmt &s);
bool mutatesExpr(const std::string &name, const Expr &e);
// ---------- codegen_types.cpp: types, conversions, ownership, printing ----------
llvm::Type *ty(const FType &t);
FType resolve(const FType &t, Pos p);
FType resolveT(const FType &t, Pos p, bool deep); // deep: structs by value must be complete
void forN(llvm::Value *n, const std::function<void(llvm::Value *)> &body); // for i in 0..n
llvm::Value *tmpOf(llvm::Type *t); // an uninitialised stack slot
void resolveStruct(StructInfo *s);
StructInfo *findStruct(const std::string &module, const std::string &name, bool qualified);
bool owning(const FType &t);
llvm::Constant *zero(const FType &t);
Value_ defaultValue(const FType &t, Pos p);
static bool widens(const FType &from, const FType &to);
static bool fits(llvm::ConstantInt *c, const FType &from, const FType &to);
llvm::Value *intCast(llvm::Value *v, const FType &from, const FType &to);
llvm::Value *intToFloat(llvm::Value *v, const FType &from, const FType &to);
Value_ coerce(Value_ v, const FType &want, Pos p, const std::string &what);
llvm::Value *own(const Value_ &v); // something that can be stored: moved if fresh, else copied
void release(const Value_ &v); // drop a fresh value nobody kept
llvm::Value *copyValue(llvm::Value *v, const FType &t);
void dropValue(llvm::Value *v, const FType &t);
void dropAt(llvm::Value *addr, const FType &t);
void copyAt(llvm::Value *dst, llvm::Value *src, const FType &t);
llvm::Function *dropFn(const FType &t);
llvm::Function *copyFn(const FType &t);
std::string typeKey(const FType &t);
llvm::Value *tmp(llvm::Value *v); // a stack copy of v, to pass by address
void emitPrint(llvm::Value *v, const FType &t, bool quoted);
llvm::Function *printFn(const FType &t);
void printf_(const std::string &fmt, std::vector<llvm::Value *> args);
llvm::Value *strConst(const std::string &s);
llvm::Value *cstr(llvm::Value *strValue); // str -> char* for C
Value_ strFromC(llvm::Value *p); // char* -> borrowed str
llvm::Value *elemSize(const FType &t);
// ---------- codegen_builtins.cpp: calls, built-ins, methods ----------
Value_ call(const CallExpr &c);
Value_ method(const MethodExpr &m);
Value_ callFinch(const Fn &fn, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p,
const std::string &shownName);
Value_ construct(StructInfo *s, const std::vector<ExprPtr> &args, const std::vector<std::string> &names, Pos p);
Value_ callC(const CFunc &f, const std::vector<ExprPtr> &args, Pos p);
Value_ convert(const FType &to, const std::vector<ExprPtr> &args, Pos p, const std::string &name);
Value_ print(const std::vector<ExprPtr> &args, Pos p);
Value_ addrOf(const std::vector<ExprPtr> &args, Pos p);
Value_ arrayMethod(const MethodExpr &m, Value_ *recv);
bool isBuiltin(const std::string &n);
std::string headerHint(const std::string &name);
void checkArgs(const std::vector<ExprPtr> &args, const std::vector<std::string> &names, size_t want, Pos p,
const std::string &name, bool variadic = false);
llvm::Value *cGlobal(const std::string &name);
llvm::Function *rt(const std::string &name); // a runtime function by name
// runtime errors
llvm::Value *fileName(Pos p);
void panicIf(llvm::Value *cond, const std::string &msg, Pos p);
void boundsCheck(llvm::Value *i, llvm::Value *len, Pos p);
void checkDivisor(const Value_ &l, const Value_ &r, Pos p);
llvm::Function *libc(const char *name, llvm::Type *ret, std::vector<llvm::Type *> params, bool vararg = false);
// ---------- abi.cpp: C calling convention for structs by value ----------
AbiArg classify(const FType &t);
llvm::Function *declareC(const CFunc &f);
Value_ emitCCall(const CFunc &f, llvm::Function *fn, std::vector<Value_> args, Pos p);
llvm::Function *cThunk(Fn &fn, Pos p);
// ---------- debug info ----------
void setLoc(Pos p);
llvm::DIType *diType(const FType &t);
};
} // namespace finch

500
src/codegen_types.cpp Normal file
View file

@ -0,0 +1,500 @@
// Code generation, part 2: types, conversions, ownership (copy/drop) and printing.
#include "codegen_impl.h"
using namespace llvm;
namespace finch {
// ---------- types ----------
llvm::Type *Codegen::ty(const FType &t) {
switch (t.kind) {
case FType::Void: return b.getVoidTy();
case FType::Bool: return b.getInt1Ty();
case FType::Char:
case FType::I8:
case FType::U8: return b.getInt8Ty();
case FType::I16:
case FType::U16: return b.getInt16Ty();
case FType::I32:
case FType::U32: return b.getInt32Ty();
case FType::I64:
case FType::U64: return b.getInt64Ty();
case FType::F32: return b.getFloatTy();
case FType::F64: return b.getDoubleTy();
case FType::Str:
case FType::Array: return StructType::get(ctx, {b.getPtrTy(), b.getInt64Ty(), b.getInt64Ty()});
case FType::Ptr:
case FType::Null: return b.getPtrTy();
case FType::Fixed: return ArrayType::get(ty(*t.elem), t.count);
case FType::Struct: return t.info->llvm;
case FType::Named: break;
}
std::fprintf(stderr, "internal compiler error: unresolved type %s\n", t.show().c_str());
std::exit(2);
}
FType Codegen::resolve(const FType &t, Pos p) { return resolveT(t, p, true); }
StructInfo *Codegen::findStruct(const std::string &module, const std::string &name, bool qualified) {
if (qualified) {
if (module == "C") {
auto it = cStructs.find(name);
return it == cStructs.end() ? nullptr : it->second;
}
auto m = modules.find(module);
if (m == modules.end()) return nullptr;
auto it = m->second.structs.find(name);
return it == m->second.structs.end() ? nullptr : it->second;
}
auto &here = modules[curModule].structs;
if (auto it = here.find(name); it != here.end()) return it->second;
if (auto it = cStructs.find(name); it != cStructs.end()) return it->second;
return nullptr;
}
FType Codegen::resolveT(const FType &t, Pos p, bool deep) {
switch (t.kind) {
case FType::Ptr:
if (!t.elem) return t;
return FType::ptrTo(resolveT(*t.elem, p, false));
case FType::Array: return FType::arrayOf(resolveT(*t.elem, p, false));
case FType::Fixed: {
FType f = t;
f.elem = std::make_shared<FType>(resolveT(*t.elem, p, deep));
return f;
}
case FType::Named: {
bool qualified = !t.module.empty();
if (qualified && t.module != "C" && !modules[curModule].imports.count(t.module))
failAt(p.file, p.line, p.col, "'" + t.module + "' is not imported here (add: import " + t.module + ")");
StructInfo *s = findStruct(t.module, t.name, qualified);
if (!s) failAt(p.file, p.line, p.col, "there is no type named '" + t.show() + "'");
FType r(FType::Struct);
r.info = s;
r.name = s->name;
r.module = s->module == "C" ? "" : s->module; // shown as math.Vec; C and main-file structs by name
if (deep) resolveStruct(s);
return r;
}
default: return t;
}
}
void Codegen::resolveStruct(StructInfo *s) {
if (s->state == 2) return;
if (s->state == 1)
failAt(s->pos.file, s->pos.line, s->pos.col,
"the struct " + s->name + " contains itself; use ptr[" + s->name + "] or []" + s->name + " for that field");
s->state = 1;
std::string savedModule = curModule;
curModule = s->isC ? "" : s->module;
std::vector<llvm::Type *> elems;
if (!s->isC) {
for (const Field &f : s->decl->fields) {
StructInfo::F nf;
nf.name = f.name;
nf.type = resolveT(f.type, f.pos, true);
if (nf.type.kind == FType::Void) failAt(f.pos.file, f.pos.line, f.pos.col, "a field can't have the type 'nothing'");
nf.init = f.init.get();
nf.llvmIndex = elems.size();
elems.push_back(ty(nf.type));
s->owning |= owning(nf.type);
s->fields.push_back(nf);
}
s->llvm->setBody(elems);
} else {
// Lay the C struct out byte for byte: explicit padding, packed, exactly C's offsets.
long long off = 0;
Pos none;
for (const CField &cf : s->cdecl->fields) {
if (cf.hidden || cf.offset < off) continue;
FType ft = resolveT(cf.type, none, true);
llvm::Type *lt = ty(ft);
long long size = dl->getTypeAllocSize(lt);
if (size != cf.size) continue; // something Finch can't mirror; the padding covers it
if (cf.offset > off) elems.push_back(ArrayType::get(b.getInt8Ty(), cf.offset - off));
StructInfo::F nf;
nf.name = cf.name;
nf.type = ft;
nf.llvmIndex = elems.size();
elems.push_back(lt);
s->fields.push_back(nf);
off = cf.offset + size;
}
if (s->cdecl->size > off) elems.push_back(ArrayType::get(b.getInt8Ty(), s->cdecl->size - off));
s->llvm->setBody(elems, true);
if (s->cdecl->hasHidden && s->unsupported.empty()) s->unsupported = "has fields Finch can't see (unions or bit fields)";
}
curModule = savedModule;
s->state = 2;
}
bool Codegen::owning(const FType &t) {
switch (t.kind) {
case FType::Str:
case FType::Array: return true;
case FType::Struct:
resolveStruct(t.info);
return t.info->owning;
default: return false;
}
}
Constant *Codegen::zero(const FType &t) { return Constant::getNullValue(ty(t)); }
Value_ Codegen::defaultValue(const FType &t, Pos p) {
if (t.kind == FType::Struct && !t.info->isC) {
for (auto &f : t.info->fields)
if (f.init) return construct(t.info, {}, {}, p);
}
return {zero(t), t, false, owning(t)};
}
// Can every value of `from` be stored in `to`? (i8 -> i32 yes, i32 -> u32 no)
bool Codegen::widens(const FType &from, const FType &to) {
if (!from.isInt() || !to.isInt()) return false;
if (from.isSigned() && to.isUnsigned()) return false;
return to.bits() > from.bits();
}
// Does the constant number `c` (of type `from`) fit into `to`?
bool Codegen::fits(ConstantInt *c, const FType &from, const FType &to) {
if (!to.isInt()) return false;
bool negative = from.isSigned() && c->isNegative();
if (negative && to.isUnsigned()) return false;
if (negative) return c->getSExtValue() >= -(1LL << (to.bits() - 1));
unsigned long long v = c->getZExtValue();
if (to.bits() == 64) return to.isUnsigned() || v <= (unsigned long long)INT64_MAX;
unsigned long long max = to.isUnsigned() ? (1ULL << to.bits()) - 1 : (1ULL << (to.bits() - 1)) - 1;
return v <= max;
}
Value *Codegen::intCast(Value *v, const FType &from, const FType &to) { return b.CreateIntCast(v, ty(to), from.isSigned()); }
Value *Codegen::intToFloat(Value *v, const FType &from, const FType &to) {
return from.isSigned() ? b.CreateSIToFP(v, ty(to)) : b.CreateUIToFP(v, ty(to));
}
// Implicit conversions: only the ones that can't surprise you.
Value_ Codegen::coerce(Value_ v, const FType &want, Pos p, const std::string &what) {
const FType &from = v.type;
if (from == want) return v;
if (from.kind == FType::Null && want.isPtr()) return {ConstantPointerNull::get(b.getPtrTy()), want};
// any pointer fits an untyped `ptr`; an untyped ptr fits any pointer
if (want.isPtr() && from.isPtr() && (!want.elem || !from.elem)) return {v.v, want};
if (from.isInt() && want.isInt()) {
auto *c = v.literal ? dyn_cast<ConstantInt>(v.v) : nullptr;
if ((c && fits(c, from, want)) || widens(from, want)) return {intCast(v.v, from, want), want, (bool)c};
if (c)
failAt(p.file, p.line, p.col, "the number " + std::to_string(from.isSigned() ? c->getSExtValue() : (long long)c->getZExtValue()) +
" doesn't fit in " + want.show());
}
if (from.isInt() && want.isFloat()) return {intToFloat(v.v, from, want), want, v.literal};
if (from.kind == FType::F32 && want.kind == FType::F64) return {b.CreateFPExt(v.v, ty(want)), want};
if (from.kind == FType::F64 && want.kind == FType::F32 && v.literal) return {b.CreateFPTrunc(v.v, ty(want)), want, true};
std::string msg = what + " must be " + want.show() + ", but this is " + from.show();
if (from.isNumber() && want.isNumber())
msg += " (use " + want.show() + "(...) to convert" + (from.isFloat() && want.isInt() ? ", it cuts off the fraction)" : ")");
else if (want.kind == FType::Str && (from.isNumber() || from.kind == FType::Bool || from.kind == FType::Char))
msg += " (turn it into text with str(...))";
else if (want.isPtr() && from.kind == FType::Str)
msg += " (use .ptr to get the text's address)";
if (from.kind == FType::Null) msg = what + " can't be null (only pointers can)";
failAt(p.file, p.line, p.col, msg);
}
// ---------- ownership ----------
Value *Codegen::tmpOf(llvm::Type *t) {
Function *f = b.GetInsertBlock()->getParent();
IRBuilder<> entry(&f->getEntryBlock(), f->getEntryBlock().begin());
AllocaInst *a = entry.CreateAlloca(t);
if (t->isStructTy()) a->setAlignment(Align(16));
return a;
}
Value *Codegen::tmp(Value *v) {
Value *a = tmpOf(v->getType());
b.CreateStore(v, a);
return a;
}
Value *Codegen::own(const Value_ &v) {
if (!owning(v.type) || v.fresh) return v.v;
return copyValue(v.v, v.type);
}
void Codegen::release(const Value_ &v) {
if (v.fresh && v.v && owning(v.type)) dropValue(v.v, v.type);
}
Value *Codegen::copyValue(Value *v, const FType &t) {
if (!owning(t)) return v;
Value *out = tmpOf(ty(t));
copyAt(out, tmp(v), t);
return b.CreateLoad(ty(t), out);
}
void Codegen::dropValue(Value *v, const FType &t) {
if (owning(t)) dropAt(tmp(v), t);
}
void Codegen::dropAt(Value *addr, const FType &t) {
if (!owning(t)) return;
if (t.kind == FType::Str) b.CreateCall(rt("finch_str_drop"), {addr});
else b.CreateCall(dropFn(t), {addr});
}
void Codegen::copyAt(Value *dst, Value *src, const FType &t) {
if (!owning(t)) {
b.CreateStore(b.CreateLoad(ty(t), src), dst);
return;
}
if (t.kind == FType::Str) b.CreateCall(rt("finch_str_copy"), {dst, src});
else b.CreateCall(copyFn(t), {dst, src});
}
void Codegen::forN(Value *n, const std::function<void(Value *)> &body) {
Value *i = tmpOf(b.getInt64Ty());
b.CreateStore(b.getInt64(0), i);
BasicBlock *cond = newBlock("n.cond"), *loop = newBlock("n.body"), *end = newBlock("n.end");
b.CreateBr(cond);
b.SetInsertPoint(cond);
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
b.CreateCondBr(b.CreateICmpSLT(iv, n), loop, end);
b.SetInsertPoint(loop);
body(iv);
b.CreateStore(b.CreateAdd(iv, b.getInt64(1)), i);
b.CreateBr(cond);
b.SetInsertPoint(end);
}
std::string Codegen::typeKey(const FType &t) {
switch (t.kind) {
case FType::Array: return "arr_" + typeKey(*t.elem);
case FType::Fixed: return "fix" + std::to_string(t.count) + "_" + typeKey(*t.elem);
case FType::Ptr: return "ptr";
case FType::Struct: return "S_" + (t.info->module.empty() ? std::string("main") : t.info->module) + "_" + t.info->name;
default: return t.show();
}
}
// Helper functions are generated once per type and shared; they have no source position.
struct HelperScope {
Codegen &cg;
IRBuilderBase::InsertPointGuard guard;
DISubprogram *savedFn;
HelperScope(Codegen &c, Function *f) : cg(c), guard(c.b), savedFn(c.diFn) {
c.diFn = nullptr;
c.b.SetInsertPoint(BasicBlock::Create(c.ctx, "entry", f));
c.b.SetCurrentDebugLocation(DebugLoc());
}
~HelperScope() { cg.diFn = savedFn; }
};
Function *Codegen::dropFn(const FType &t) {
std::string key = "drop." + typeKey(t);
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::InternalLinkage,
"finch." + key, mod.get());
helpers[key] = f;
HelperScope hs(*this, f);
Value *a = f->getArg(0);
if (t.kind == FType::Array) {
FType et = *t.elem;
if (owning(et)) {
Value *data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), a, 0));
Value *len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), a, 1));
forN(len, [&](Value *i) { dropAt(b.CreateInBoundsGEP(ty(et), data, i), et); });
}
b.CreateCall(rt("finch_arr_free"), {a});
} else if (t.kind == FType::Struct) {
for (auto &fl : t.info->fields)
if (owning(fl.type)) dropAt(b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex), fl.type);
}
b.CreateRetVoid();
return f;
}
Function *Codegen::copyFn(const FType &t) {
std::string key = "copy." + typeKey(t);
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy(), b.getPtrTy()}, false),
Function::InternalLinkage, "finch." + key, mod.get());
helpers[key] = f;
HelperScope hs(*this, f);
Value *dst = f->getArg(0), *src = f->getArg(1);
if (t.kind == FType::Array) {
FType et = *t.elem;
b.CreateCall(rt("finch_arr_clone_raw"), {dst, src, elemSize(et)});
if (owning(et)) {
Value *d = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), dst, 0));
Value *s = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), src, 0));
Value *len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), src, 1));
forN(len, [&](Value *i) {
copyAt(b.CreateInBoundsGEP(ty(et), d, i), b.CreateInBoundsGEP(ty(et), s, i), et);
});
}
} else if (t.kind == FType::Struct) {
b.CreateStore(b.CreateLoad(ty(t), src), dst);
for (auto &fl : t.info->fields)
if (owning(fl.type))
copyAt(b.CreateStructGEP(t.info->llvm, dst, fl.llvmIndex), b.CreateStructGEP(t.info->llvm, src, fl.llvmIndex), fl.type);
}
b.CreateRetVoid();
return f;
}
// ---------- strings ----------
Value *Codegen::strConst(const std::string &s) {
Constant *g = b.CreateGlobalString(s, "str");
return ConstantStruct::get(cast<StructType>(ty(FType::Str)), {g, b.getInt64(s.size()), b.getInt64(0)});
}
// str -> char* for C (an empty str may have no memory at all; C gets "" then)
Value *Codegen::cstr(Value *s) {
Value *p = b.CreateExtractValue(s, 0);
return b.CreateSelect(b.CreateIsNull(p), b.CreateGlobalString("", "empty"), p);
}
Value_ Codegen::strFromC(Value *p) {
Value *out = tmpOf(ty(FType::Str));
b.CreateCall(rt("finch_str_from_c"), {out, p});
return {b.CreateLoad(ty(FType::Str), out), FType::Str};
}
Value *Codegen::elemSize(const FType &t) { return b.getInt64(dl->getTypeAllocSize(ty(t))); }
// ---------- printing ----------
void Codegen::printf_(const std::string &fmt, std::vector<Value *> args) {
args.insert(args.begin(), b.CreateGlobalString(fmt, "fmt"));
b.CreateCall(libc("printf", b.getInt32Ty(), {b.getPtrTy()}, true), args);
}
void Codegen::emitPrint(Value *v, const FType &t, bool quoted) {
if (t.isSigned()) printf_("%lld", {b.CreateSExt(v, b.getInt64Ty())});
else if (t.isUnsigned()) printf_("%llu", {b.CreateZExt(v, b.getInt64Ty())});
else if (t.isFloat()) printf_("%g", {b.CreateFPExt(v, b.getDoubleTy())});
else if (t.kind == FType::Char) printf_(quoted ? "'%c'" : "%c", {b.CreateZExt(v, b.getInt32Ty())});
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.kind == FType::Null) printf_("null", {});
else b.CreateCall(printFn(t), {tmp(v)});
}
Function *Codegen::printFn(const FType &t) {
std::string key = "print." + typeKey(t);
if (auto it = helpers.find(key); it != helpers.end()) return it->second;
Function *f = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::InternalLinkage,
"finch." + key, mod.get());
helpers[key] = f;
HelperScope hs(*this, f);
Value *a = f->getArg(0);
if (t.kind == FType::Array || t.kind == FType::Fixed) {
FType et = *t.elem;
Value *data, *len;
if (t.kind == FType::Fixed) {
data = a;
len = b.getInt64(t.count);
} else {
data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(t), a, 0));
len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), a, 1));
}
printf_("[", {});
forN(len, [&](Value *i) {
BasicBlock *sep = newBlock("sep"), *item = newBlock("item");
b.CreateCondBr(b.CreateICmpSGT(i, b.getInt64(0)), sep, item);
b.SetInsertPoint(sep);
printf_(", ", {});
b.CreateBr(item);
b.SetInsertPoint(item);
emitPrint(b.CreateLoad(ty(et), b.CreateInBoundsGEP(ty(et), data, i)), et, true);
});
printf_("]", {});
} else if (t.kind == FType::Struct) {
printf_(t.info->name + "(", {});
bool first = true;
for (auto &fl : t.info->fields) {
printf_((first ? "" : ", ") + fl.name + ": ", {});
first = false;
emitPrint(b.CreateLoad(ty(fl.type), b.CreateStructGEP(t.info->llvm, a, fl.llvmIndex)), fl.type, true);
}
printf_(")", {});
}
b.CreateRetVoid();
return f;
}
// ---------- debug info ----------
DIType *Codegen::diType(const FType &t) {
std::string key = typeKey(t);
if (auto it = diTypes.find(key); it != diTypes.end()) return it->second;
DIType *d = nullptr;
auto basic = [&](const char *n, unsigned bits, unsigned enc) { return di->createBasicType(n, bits, enc); };
switch (t.kind) {
case FType::Void: return nullptr;
case FType::Bool: d = basic("bool", 8, dwarf::DW_ATE_boolean); break;
case FType::Char: d = basic("char", 8, dwarf::DW_ATE_signed_char); break;
case FType::I8: d = basic("i8", 8, dwarf::DW_ATE_signed); break;
case FType::I16: d = basic("i16", 16, dwarf::DW_ATE_signed); break;
case FType::I32: d = basic("i32", 32, dwarf::DW_ATE_signed); break;
case FType::I64: d = basic("int", 64, dwarf::DW_ATE_signed); break;
case FType::U8: d = basic("u8", 8, dwarf::DW_ATE_unsigned); break;
case FType::U16: d = basic("u16", 16, dwarf::DW_ATE_unsigned); break;
case FType::U32: d = basic("u32", 32, dwarf::DW_ATE_unsigned); break;
case FType::U64: d = basic("u64", 64, dwarf::DW_ATE_unsigned); break;
case FType::F32: d = basic("f32", 32, dwarf::DW_ATE_float); break;
case FType::F64: d = basic("float", 64, dwarf::DW_ATE_float); break;
case FType::Ptr:
case FType::Null: d = di->createPointerType(t.elem ? diType(*t.elem) : nullptr, 64, 0, std::nullopt, t.show()); break;
case FType::Fixed:
d = di->createArrayType(t.count * (uint64_t)dl->getTypeAllocSizeInBits(ty(*t.elem)), 0, diType(*t.elem),
di->getOrCreateArray({di->getOrCreateSubrange(0, t.count)}));
break;
case FType::Str:
case FType::Array: {
DIType *et = t.kind == FType::Str ? diType(FType::Char) : diType(*t.elem);
DIType *pt = di->createPointerType(et, 64);
DIType *i64 = diType(FType::I64);
DIFile *f = diFiles[0];
Metadata *els[] = {di->createMemberType(diUnit, "ptr", f, 0, 64, 64, 0, DINode::FlagZero, pt),
di->createMemberType(diUnit, "len", f, 0, 64, 64, 64, DINode::FlagZero, i64),
di->createMemberType(diUnit, "cap", f, 0, 64, 64, 128, DINode::FlagZero, i64)};
d = di->createStructType(diUnit, t.show(), f, 0, 192, 64, DINode::FlagZero, nullptr, di->getOrCreateArray(els));
break;
}
case FType::Struct: {
StructInfo *s = t.info;
DIFile *f = s->isC ? diFiles[0] : diFiles[s->pos.file];
auto *fwd = di->createReplaceableCompositeType(dwarf::DW_TAG_structure_type, s->name, diUnit, f, s->pos.line);
diTypes[key] = fwd;
const StructLayout *lay = dl->getStructLayout(s->llvm);
std::vector<Metadata *> els;
for (auto &fl : s->fields) {
uint64_t bits = dl->getTypeAllocSizeInBits(ty(fl.type));
els.push_back(di->createMemberType(diUnit, fl.name, f, s->pos.line, bits, 8, lay->getElementOffsetInBits(fl.llvmIndex),
DINode::FlagZero, diType(fl.type)));
}
DICompositeType *real = di->createStructType(diUnit, s->name, f, s->pos.line, lay->getSizeInBits(), 8,
DINode::FlagZero, nullptr, di->getOrCreateArray(els));
di->replaceTemporary(TempDIType(fwd), real);
d = real;
break;
}
case FType::Named: return nullptr;
}
diTypes[key] = d;
return d;
}
} // namespace finch

View file

@ -3,21 +3,28 @@
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>
// Every source file the compiler has read; positions point into this list.
struct SourceFile {
std::string path;
std::string text;
};
inline std::vector<SourceFile> g_files;
inline int g_curFile = 0; // the file the lexer/parser is working on
// Finch stops at the first error and shows exactly where it is.
inline std::string g_file;
inline std::string g_source;
[[noreturn]] inline void fail(int line, int col, const std::string &msg) {
std::fprintf(stderr, "%s:%d:%d: error: %s\n", g_file.c_str(), line, col, msg.c_str());
[[noreturn]] inline void failAt(int file, int line, int col, const std::string &msg) {
const SourceFile &f = g_files.at(file);
std::fprintf(stderr, "%s:%d:%d: error: %s\n", f.path.c_str(), line, col, msg.c_str());
size_t start = 0;
for (int l = 1; l < line && start != std::string::npos; l++) {
start = g_source.find('\n', start);
start = f.text.find('\n', start);
if (start != std::string::npos) start++;
}
if (start != std::string::npos && start < g_source.size()) {
std::string text = g_source.substr(start, g_source.find('\n', start) - start);
if (line > 0 && start != std::string::npos && start < f.text.size()) {
std::string text = f.text.substr(start, f.text.find('\n', start) - start);
std::string pad;
int c = 1; // columns count characters; skip UTF-8 continuation bytes
for (size_t k = 0; k < text.size() && c < col; k++) {
@ -29,3 +36,5 @@ inline std::string g_source;
}
std::exit(1);
}
[[noreturn]] inline void fail(int line, int col, const std::string &msg) { failAt(g_curFile, line, col, msg); }

View file

@ -9,12 +9,13 @@ static const std::unordered_map<std::string, Tok> keywords = {
{"else", Tok::Else}, {"while", Tok::While}, {"for", Tok::For},
{"in", Tok::In}, {"break", Tok::Break}, {"continue", Tok::Continue},
{"true", Tok::True}, {"false", Tok::False}, {"null", Tok::Null},
{"import", Tok::Import}, {"link", Tok::Link},
{"import", Tok::Import}, {"link", Tok::Link}, {"struct", Tok::Struct},
{"defer", Tok::Defer},
};
std::vector<Token> lex(const std::string &src, const std::string &file) {
g_file = file;
g_source = src;
std::vector<Token> lex(int file) {
g_curFile = file;
const std::string &src = g_files[file].text;
std::vector<Token> out;
size_t i = 0;
int line = 1, col = 1;
@ -126,10 +127,7 @@ std::vector<Token> lex(const std::string &src, const std::string &file) {
case '!': n == '=' ? two(Tok::NotEq) : one(Tok::Not); break;
case '<': n == '=' ? two(Tok::LessEq) : n == '<' ? two(Tok::Shl) : one(Tok::Less); break;
case '>': n == '=' ? two(Tok::GreaterEq) : n == '>' ? two(Tok::Shr) : one(Tok::Greater); break;
case ':':
if (n != '=') fail(line, col, "unexpected ':' (did you mean ':='?)");
two(Tok::Declare);
break;
case ':': n == '=' ? two(Tok::Declare) : one(Tok::Colon); break;
case '&': n == '&' ? two(Tok::And) : one(Tok::Amp); break;
case '|': n == '|' ? two(Tok::Or) : one(Tok::Pipe); break;
default: {
@ -169,6 +167,9 @@ const char *tokName(Tok t) {
case Tok::Null: return "'null'";
case Tok::Import: return "'import'";
case Tok::Link: return "'link'";
case Tok::Struct: return "'struct'";
case Tok::Defer: return "'defer'";
case Tok::Colon: return "':'";
case Tok::LParen: return "'('";
case Tok::RParen: return "')'";
case Tok::LBrace: return "'{'";

View file

@ -8,10 +8,10 @@ enum class Tok {
Ident, Int, Float, String, Char,
// keywords
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link,
Fn, Return, If, Else, While, For, In, Break, Continue, True, False, Null, Import, Link, Struct, Defer,
// punctuation
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Dot, DotDot, Arrow,
LParen, RParen, LBrace, RBrace, LBracket, RBracket, Comma, Colon, Dot, DotDot, Arrow,
// operators
Plus, Minus, Star, Slash, Percent,
@ -30,5 +30,6 @@ struct Token {
bool newlineBefore; // token starts a new line: ends the previous statement
};
std::vector<Token> lex(const std::string &src, const std::string &file);
// Lexes g_files[file].text.
std::vector<Token> lex(int file);
const char *tokName(Tok t);

View file

@ -1,14 +1,16 @@
#include "cimport.h"
#include "codegen.h"
#include "error.h"
#include "lexer.h"
#include "parser.h"
#include <llvm/Config/llvm-config.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/MC/TargetRegistry.h>
#include <llvm/Passes/PassBuilder.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/Path.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Config/llvm-config.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/TargetParser/Host.h>
@ -18,38 +20,90 @@
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <map>
#include <sstream>
#include <sys/wait.h>
#include <string>
#include <sys/wait.h>
#include <vector>
#include "rt_source.inc" // kRuntimeSource: runtime/finch_rt.c, embedded at build time
using namespace llvm;
static void usage() {
std::fprintf(stderr,
"Finch " FINCH_VERSION " - small, quick, sharp.\n\n"
"usage:\n"
" finch run <file.fn> compile and run\n"
" finch run <file.fn> [args...] compile and run\n"
" finch build <file.fn> [-o name] compile to a program\n"
" finch ir <file.fn> 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"
" -O0 skip optimizations (to read the raw IR)\n"
" finch ir <file.fn> show the generated LLVM IR\n"
" finch version show the version\n");
" -g add debug info (for gdb / lldb)\n"
" -O0 skip optimizations (to read the raw IR)\n");
std::exit(1);
}
static std::string readFile(const std::string &path) {
static bool readFile(const std::string &path, std::string &out) {
std::ifstream in(path);
if (!in) {
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
std::exit(1);
}
if (!in) return false;
std::stringstream ss;
ss << in.rdbuf();
return ss.str();
out = ss.str();
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 {
std::vector<Program> progs;
std::map<std::string, bool> loaded; // module name -> seen
void load(const std::string &path, const std::string &module, Pos from) {
std::string text;
if (!readFile(path, text)) {
if (from.line) failAt(from.file, from.line, from.col, "can't find the module '" + module + "' (looked for " + path + ")");
std::fprintf(stderr, "error: can't open '%s'\n", path.c_str());
std::exit(1);
}
int idx = (int)g_files.size();
g_files.push_back({path, text});
Program p = parse(lex(idx), idx);
p.module = module;
p.path = path;
std::vector<Import> mods;
for (const Import &im : p.imports)
if (!im.isC) mods.push_back(im);
progs.push_back(std::move(p));
for (const Import &im : mods) {
if (im.path == "main") failAt(im.pos.file, im.pos.line, im.pos.col, "'main' can't be imported");
if (loaded[im.path]) continue;
loaded[im.path] = true;
load(findModule(dirOf(path), im.path), im.path, im.pos);
}
}
// next to the importing file, then in $FINCH_PATH folders
static std::string findModule(const std::string &dir, const std::string &name) {
std::string here = dir + "/" + name + ".fn";
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, ':');)
if (!d.empty() && sys::fs::exists(d + "/" + name + ".fn")) return d + "/" + name + ".fn";
}
return here;
}
};
// ---------- LLVM ----------
static void optimize(Module &mod, TargetMachine *tm) {
LoopAnalysisManager lam;
FunctionAnalysisManager fam;
@ -93,6 +147,8 @@ static void emitObject(Module &mod, TargetMachine *tm, const std::string &path)
pm.run(mod);
}
// ---------- 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");
@ -104,6 +160,38 @@ static int capture(const std::string &cmd, std::string &output) {
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";
if (sys::fs::exists(obj)) return obj;
sys::fs::create_directories(dir);
std::string src = obj + ".c", part = obj + ".part";
{
std::ofstream out(src);
out << kRuntimeSource;
}
std::string output;
int status = capture(ccName() + " -O2 -fPIC -c '" + src + "' -o '" + 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::exit(1);
}
sys::fs::rename(part, obj);
return obj;
}
// 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;
@ -151,7 +239,6 @@ static std::string guessLib(const std::string &header) {
for (const std::string &lib : missingLibs)
std::fprintf(stderr, "error: the library '%s' wasn't found. Is it installed? (on Nix: add it to shell.nix)\n", lib.c_str());
// group the missing functions by the header they came from
std::vector<std::pair<std::string, std::vector<std::string>>> byHeader;
for (const std::string &name : missing) {
auto f = c.fns.find(name);
@ -178,14 +265,34 @@ static std::string guessLib(const std::string &header) {
std::exit(1);
}
static bool endsWith(const std::string &s, const char *x) {
size_t n = std::strlen(x);
return s.size() > n && s.compare(s.size() - n, n, x) == 0;
}
static void link(const std::string &obj, const std::string &exe, const std::vector<std::string> &libs,
const CImports &c) {
const char *cc = std::getenv("CC");
std::string cmd = std::string(cc ? cc : "cc") + " '" + obj + "' -o '" + exe + "' -lm";
for (const std::string &l : libs) cmd += libFlags(l);
std::string cmd = ccName() + " '" + obj + "' '" + runtimeObject() + "' -o '" + exe + "' -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::fprintf(stderr, "%serror: couldn't compile %s\n", output.c_str(), l.c_str());
std::exit(1);
}
temps.push_back(o);
cmd += " '" + o + "'";
} else if (endsWith(l, ".o") || endsWith(l, ".a")) {
cmd += " '" + l + "'";
} else {
cmd += libFlags(l);
}
}
std::string output;
int status = capture(cmd, output);
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::exit(1);
@ -201,6 +308,12 @@ static std::string stem(const std::string &path) {
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);
@ -208,26 +321,40 @@ int main(int argc, char **argv) {
}
if (argc < 3) usage();
std::string cmd = argv[1], file = argv[2];
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
std::string out = stem(file);
std::vector<std::string> libs;
bool optimizeCode = true;
std::vector<std::string> libs, programArgs;
bool optimizeCode = true, debug = false;
for (int i = 3; i < argc; i++) {
std::string a = argv[i];
if (a == "-o" && i + 1 < argc) out = 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 == "-O0") optimizeCode = false;
else if (a == "-g") debug = true;
else if (a == "-l" && i + 1 < argc) libs.push_back(argv[++i]);
else if (a.rfind("-l", 0) == 0 && a.size() > 2) libs.push_back(a.substr(2));
else if (cmd == "run") programArgs.push_back(a);
else usage();
}
if (cmd != "run" && cmd != "build" && cmd != "ir") usage();
Program prog = parse(lex(readFile(file), file));
for (const Link &l : prog.links) libs.push_back(l.lib);
size_t slash = file.find_last_of('/');
CImports c = importHeaders(prog.imports, slash == std::string::npos ? "." : file.substr(0, slash));
Loader loader;
loader.load(file, "", Pos{});
std::vector<Import> allImports;
std::vector<std::string> dirs;
for (const Program &p : loader.progs) {
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;
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);
}
if (std::find(dirs.begin(), dirs.end(), dirOf(p.path)) == dirs.end()) dirs.push_back(dirOf(p.path));
}
CImports c = importHeaders(allImports, dirs);
LLVMContext ctx;
TargetMachine *tm = hostMachine();
std::unique_ptr<Module> mod = generate(prog, c, ctx, *tm);
std::unique_ptr<Module> mod = generate(loader.progs, c, ctx, *tm, debug);
if (optimizeCode) optimize(*mod, tm);
if (cmd == "ir") {
@ -246,7 +373,9 @@ int main(int argc, char **argv) {
link(obj, out, libs, c);
if (cmd == "run") {
int status = std::system(("'" + out + "'").c_str());
std::string line = shellQuote(out);
for (const std::string &a : programArgs) line += " " + shellQuote(a);
int status = std::system(line.c_str());
std::remove(out.c_str());
if (WIFSIGNALED(status)) {
std::fprintf(stderr, "\nthe program crashed: %s\n", strsignal(WTERMSIG(status)));

View file

@ -5,6 +5,7 @@
#include <cerrno>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <unordered_map>
bool typeFromName(const std::string &name, Type &out) {
@ -29,28 +30,32 @@ bool isTypeName(const std::string &name) {
class Parser {
public:
explicit Parser(const std::vector<Token> &t) : toks(t) {}
Parser(const std::vector<Token> &t, int f) : toks(t), file(f) {}
Program program() {
Program p;
while (!at(Tok::End)) {
if (at(Tok::Import)) p.imports.push_back(import());
else if (at(Tok::Link)) p.links.push_back(link());
else p.fns.push_back(function());
switch (cur().kind) {
case Tok::Import: p.imports.push_back(import()); break;
case Tok::Link: p.links.push_back(link()); break;
case Tok::Struct: p.structs.push_back(structDecl()); break;
default: p.fns.push_back(function()); break;
}
}
return p;
}
private:
const std::vector<Token> &toks;
int file;
size_t i = 0;
int parenDepth = 0; // inside ( ) newlines don't end anything
int parenDepth = 0; // inside ( ) and [ ] newlines don't end anything
const Token &cur() const { return toks[i]; }
const Token &peekTok(size_t off = 1) const { return toks[std::min(i + off, toks.size() - 1)]; }
const Token &next() { return toks[i++]; }
bool at(Tok k) const { return cur().kind == k; }
Pos pos() const { return {cur().line, cur().col}; }
Pos pos() const { return {cur().line, cur().col, file}; }
std::string describe(const Token &t) const {
return t.kind == Tok::Ident ? "'" + t.text + "'" : tokName(t.kind);
@ -71,27 +76,44 @@ private:
fail(cur().line, cur().col, "unexpected " + describe(cur()) + " " + ctx);
}
// A token can continue an expression only if it is on the same line (or we're inside parens).
// A token can continue an expression only if it is on the same line (or we're inside brackets).
bool sameLine() const { return parenDepth > 0 || !cur().newlineBefore; }
// int, u8, ptr, ptr[int], ptr[ptr[u8]] ...
// int, u8, ptr, ptr[int], []str, Point, math.Vec ...
Type type() {
if (!at(Tok::Ident) || !isTypeName(cur().text))
unexpected("(expected a type like int, float, u8, str or ptr[int])");
if (accept(Tok::LBracket)) {
expect(Tok::RBracket, "']' (an array type looks like []int)");
return Type::arrayOf(type());
}
if (!at(Tok::Ident)) unexpected("(expected a type like int, str, []int, ptr[int] or a struct name)");
std::string name = next().text;
Type t;
typeFromName(next().text, t);
if (t.kind == Type::Ptr && accept(Tok::LBracket)) {
t = Type::ptrTo(type());
expect(Tok::RBracket);
if (typeFromName(name, t)) {
if (t.kind == Type::Ptr && accept(Tok::LBracket)) {
t = Type::ptrTo(type());
expect(Tok::RBracket);
}
return t;
}
t = Type(Type::Named);
t.name = name;
if (at(Tok::Dot) && peekTok().kind == Tok::Ident) { // module.Struct
next();
t.module = name;
t.name = next().text;
}
return t;
}
// Does a declaration like `int x` or `ptr[int] p` start here?
// Does a declaration like `int x`, `ptr[int] p`, `[]int a`, `Point p` or `math.Vec v` start here?
bool atDeclaration() const {
if (!at(Tok::Ident) || !isTypeName(cur().text)) return false;
if (at(Tok::LBracket)) return peekTok().kind == Tok::RBracket;
if (!at(Tok::Ident)) return false;
Tok n = peekTok().kind;
return n == Tok::Ident || (cur().text == "ptr" && n == Tok::LBracket);
if (n == Tok::Ident) return !peekTok().newlineBefore;
if (cur().text == "ptr" && n == Tok::LBracket) return true;
return n == Tok::Dot && peekTok(2).kind == Tok::Ident && peekTok(3).kind == Tok::Ident &&
!peekTok(3).newlineBefore;
}
// ---------- top level ----------
@ -100,9 +122,12 @@ private:
Import im;
im.pos = pos();
expect(Tok::Import);
if (at(Tok::Ident))
fail(im.pos.line, im.pos.col, "Finch modules are not ready yet; for C headers write import \"stdio.h\"");
im.path = expect(Tok::String, "a header name in quotes, like \"stdio.h\"").text;
if (at(Tok::Ident)) {
im.isC = false;
im.path = next().text;
} else {
im.path = expect(Tok::String, "a module name (import math) or a C header in quotes (import \"stdio.h\")").text;
}
return im;
}
@ -111,17 +136,44 @@ private:
l.pos = pos();
expect(Tok::Link);
l.lib = expect(Tok::String, "a library name in quotes, like link \"glfw\"").text;
auto ends = [&](const char *x) { size_t n = std::strlen(x); return l.lib.size() > n && l.lib.compare(l.lib.size() - n, n, x) == 0; };
if (ends(".c") || ends(".o") || ends(".a")) return l; // your own C code, next to the .fn file
if (l.lib.rfind("lib", 0) == 0 || l.lib.find(".so") != std::string::npos || l.lib.find('/') != std::string::npos)
fail(l.pos.line, l.pos.col, "write just the library's name: for libglfw.so that's link \"glfw\"");
return l;
}
StructDecl structDecl() {
StructDecl s;
s.pos = pos();
expect(Tok::Struct);
s.name = expect(Tok::Ident, "a struct name").text;
if (isTypeName(s.name)) fail(s.pos.line, s.pos.col, "'" + s.name + "' is a built-in type name, pick another");
Pos open = pos();
expect(Tok::LBrace);
while (!at(Tok::RBrace)) {
if (at(Tok::End)) fail(open.line, open.col, "this '{' is never closed (missing '}')");
Field f;
f.pos = pos();
if (!atDeclaration()) unexpected("(a field looks like: int x or str name = \"default\")");
f.type = type();
f.name = expect(Tok::Ident, "a field name").text;
for (const Field &other : s.fields)
if (other.name == f.name) fail(f.pos.line, f.pos.col, "the field '" + f.name + "' is listed twice");
if (accept(Tok::Assign)) f.init = expr();
s.fields.push_back(std::move(f));
endOfStatement();
}
next();
return s;
}
FnDecl function() {
FnDecl fn;
fn.pos = pos();
if (at(Tok::Ident) && isTypeName(cur().text))
fail(fn.pos.line, fn.pos.col, "functions start with 'fn', like: fn add(int a, int b) -> int { ... }");
expect(Tok::Fn, "'fn' (every function starts with fn)");
expect(Tok::Fn, "'fn', 'struct', 'import' or 'link' (every function starts with fn)");
fn.name = expect(Tok::Ident, "a function name").text;
expect(Tok::LParen);
@ -177,12 +229,20 @@ private:
}
case Tok::For: {
next();
auto s = std::make_unique<ForStmt>(p);
s->var = expect(Tok::Ident, "a loop variable name").text;
std::string var = expect(Tok::Ident, "a loop variable name").text;
expect(Tok::In);
s->from = expr();
expect(Tok::DotDot, "'..' (like 'for i in 0..10')");
s->to = expr();
ExprPtr first = expr();
if (accept(Tok::DotDot)) { // for i in 0..10
auto s = std::make_unique<ForStmt>(p);
s->var = var;
s->from = std::move(first);
s->to = expr();
s->body = block();
return s;
}
auto s = std::make_unique<ForEachStmt>(p); // for x in list
s->var = var;
s->list = std::move(first);
s->body = block();
return s;
}
@ -194,19 +254,25 @@ private:
}
case Tok::Break: next(); return std::make_unique<BreakStmt>(p);
case Tok::Continue: next(); return std::make_unique<ContinueStmt>(p);
case Tok::Defer: {
next();
if (at(Tok::Defer)) fail(p.line, p.col, "defer inside defer is not allowed");
return std::make_unique<DeferStmt>(p, statement());
}
case Tok::LBrace: return block();
case Tok::Fn: fail(p.line, p.col, "functions can't be inside other functions (move it out)");
case Tok::Struct: fail(p.line, p.col, "structs go outside functions, at the top level of the file");
case Tok::Import:
case Tok::Link: fail(p.line, p.col, "import and link go at the top of the file, outside functions");
default: break;
}
// int x = 5
// int x = 5, Point p, []int a
if (atDeclaration()) {
auto s = std::make_unique<VarDeclStmt>(p);
s->hasType = true;
s->type = type();
s->name = next().text;
s->name = expect(Tok::Ident, "a variable name").text;
if (accept(Tok::Assign)) s->init = expr();
return s;
}
@ -223,7 +289,7 @@ private:
ExprPtr e = expr();
// x = 5, p.value += 1, ...
// x = 5, p.value += 1, a[i] = 2, ...
char op = 0;
switch (cur().kind) {
case Tok::Assign: op = '='; break;
@ -237,8 +303,8 @@ private:
default: break;
}
if (op && sameLine()) {
if (e->kind != ExprKind::Var && e->kind != ExprKind::Member)
fail(p.line, p.col, "only a variable or p.value can be assigned to");
if (e->kind != ExprKind::Var && e->kind != ExprKind::Member && e->kind != ExprKind::Index)
fail(p.line, p.col, "only a variable, a field, an element or p.value can be assigned to");
auto s = std::make_unique<AssignStmt>(p);
s->target = std::move(e);
s->op = op;
@ -247,7 +313,8 @@ private:
return s;
}
if (e->kind != ExprKind::Call) fail(p.line, p.col, "this value is computed but never used");
if (e->kind != ExprKind::Call && e->kind != ExprKind::Method)
fail(p.line, p.col, "this value is computed but never used");
return std::make_unique<ExprStmt>(p, std::move(e));
}
@ -355,29 +422,51 @@ private:
return postfix();
}
// p.value
// p.value, a.len, a[i], a.push(x), math.sqrt(2)
ExprPtr postfix() {
ExprPtr e = primary();
while (sameLine() && at(Tok::Dot)) {
while (sameLine()) {
Pos p = pos();
next();
e = std::make_unique<MemberExpr>(p, std::move(e), expect(Tok::Ident, "a name after '.'").text);
if (accept(Tok::Dot)) {
std::string name = expect(Tok::Ident, "a name after '.'").text;
if (at(Tok::LParen) && !cur().newlineBefore) {
auto m = std::make_unique<MethodExpr>(p, std::move(e), name);
args(m->args, m->argNames);
e = std::move(m);
} else {
e = std::make_unique<MemberExpr>(p, std::move(e), name);
}
} else if (at(Tok::LBracket)) {
next();
parenDepth++;
ExprPtr index = expr();
parenDepth--;
expect(Tok::RBracket);
e = std::make_unique<IndexExpr>(p, std::move(e), std::move(index));
} else {
return e;
}
}
return e;
}
// name(arg, arg, ...) -- we are at the '('
ExprPtr callArgs(Pos p, const std::string &name) {
auto call = std::make_unique<CallExpr>(p, name);
// ( arg, name: arg, ... ) -- we are at the '('
void args(std::vector<ExprPtr> &out, std::vector<std::string> &names) {
expect(Tok::LParen);
parenDepth++;
if (!at(Tok::RParen)) {
do call->args.push_back(expr());
while (accept(Tok::Comma));
do {
std::string name;
if (at(Tok::Ident) && peekTok().kind == Tok::Colon) {
name = next().text;
next();
}
names.push_back(name);
out.push_back(expr());
} while (accept(Tok::Comma));
}
parenDepth--;
expect(Tok::RParen);
return call;
}
ExprPtr primary() {
@ -406,9 +495,25 @@ private:
expect(Tok::RParen);
return e;
}
case Tok::LBracket: { // [1, 2, 3]
next();
auto a = std::make_unique<ArrayLitExpr>(p);
parenDepth++;
while (!at(Tok::RBracket)) {
a->elems.push_back(expr());
if (!accept(Tok::Comma)) break; // a trailing comma is fine
}
parenDepth--;
expect(Tok::RBracket);
return a;
}
case Tok::Ident: {
next();
if (at(Tok::LParen) && !cur().newlineBefore) return callArgs(p, t.text);
if (at(Tok::LParen) && !cur().newlineBefore) {
auto call = std::make_unique<CallExpr>(p, t.text);
args(call->args, call->argNames);
return call;
}
if (isTypeName(t.text))
fail(p.line, p.col, "a type name alone is not a value (did you mean " + t.text + "(...)?)");
return std::make_unique<VarExpr>(p, t.text);
@ -420,6 +525,6 @@ private:
} // namespace
Program parse(const std::vector<Token> &tokens) {
return Parser(tokens).program();
Program parse(const std::vector<Token> &tokens, int file) {
return Parser(tokens, file).program();
}

View file

@ -3,4 +3,5 @@
#include "ast.h"
#include "lexer.h"
Program parse(const std::vector<Token> &tokens);
// Parses one file's tokens; `file` is its index in g_files.
Program parse(const std::vector<Token> &tokens, int file);