mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
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:
parent
0591cd20f3
commit
1dc976bd8f
59 changed files with 4577 additions and 1047 deletions
265
src/codegen_impl.h
Normal file
265
src/codegen_impl.h
Normal 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
|
||||
Loading…
Reference in a new issue