mirror of
https://github.com/Nexoniarz/Finch.git
synced 2026-11-03 08:54:25 +00:00
1505 lines
66 KiB
C++
1505 lines
66 KiB
C++
// Code generation, part 1: the program, statements, scopes and expressions.
|
|
// Types, ownership and printing live in codegen_types.cpp; calls and built-ins in
|
|
// codegen_builtins.cpp; the C calling convention in abi.cpp.
|
|
|
|
#include "codegen_impl.h"
|
|
#include "builtins_doc.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace finch {
|
|
|
|
Codegen::Codegen(LLVMContext &c, const std::vector<Program> &ps, const CImports &ci, TargetMachine &tm, bool dbg)
|
|
: ctx(c), b(c), mod(std::make_unique<Module>("finch", c)), progs(ps), cimports(ci), debug(dbg) {
|
|
mod->setTargetTriple(tm.getTargetTriple());
|
|
mod->setDataLayout(tm.createDataLayout());
|
|
dl = &mod->getDataLayout();
|
|
scopes.reserve(256);
|
|
if (debug) {
|
|
di = std::make_unique<DIBuilder>(*mod);
|
|
for (const SourceFile &f : g_files) {
|
|
std::string path = f.path;
|
|
size_t slash = path.find_last_of('/');
|
|
std::string dir = slash == std::string::npos ? "." : path.substr(0, slash);
|
|
std::string name = slash == std::string::npos ? path : path.substr(slash + 1);
|
|
diFiles.push_back(di->createFile(name, dir));
|
|
}
|
|
diUnit = di->createCompileUnit(dwarf::DW_LANG_C, diFiles[0], "finch " FINCH_VERSION, false, "", 0);
|
|
mod->addModuleFlag(Module::Warning, "Debug Info Version", DEBUG_METADATA_VERSION);
|
|
if (tm.getTargetTriple().isWindowsMSVCEnvironment())
|
|
mod->addModuleFlag(Module::Warning, "CodeView", 1); // what Visual Studio and WinDbg read
|
|
else
|
|
mod->addModuleFlag(Module::Warning, "Dwarf Version", 5);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<Module> Codegen::run() {
|
|
declareStructs();
|
|
declareFns();
|
|
if (!modules[""].fns.count("main")) failAt(0, 1, 1, "there is no main function (add 'fn main() { ... }')");
|
|
for (auto &[name, m] : modules)
|
|
for (auto &[fname, fn] : m.fns) define(fn);
|
|
for (auto &[s, ms] : methods)
|
|
for (auto &[mname, fn] : ms) define(fn);
|
|
defineMainWrapper();
|
|
if (di) di->finalize();
|
|
|
|
std::string err;
|
|
raw_string_ostream os(err);
|
|
if (verifyModule(*mod, &os)) {
|
|
std::fprintf(stderr, "internal compiler error (please report):\n%s\n", err.c_str());
|
|
std::exit(2);
|
|
}
|
|
return std::move(mod);
|
|
}
|
|
|
|
// ---------- declarations ----------
|
|
|
|
void Codegen::declareStructs() {
|
|
for (const Program &p : progs) {
|
|
ModuleScope &m = modules[p.module];
|
|
for (const Import &im : p.imports)
|
|
if (!im.isC) m.imports.insert(im.path);
|
|
for (const StructDecl &d : p.structs) {
|
|
if (m.structs.count(d.name)) failAt(d.pos.file, d.pos.line, d.pos.col, "the struct '" + d.name + "' is defined twice");
|
|
auto s = std::make_unique<StructInfo>();
|
|
s->name = d.name;
|
|
s->module = p.module;
|
|
s->decl = &d;
|
|
s->pos = d.pos;
|
|
s->llvm = StructType::create(ctx, "finch." + (p.module.empty() ? "" : p.module + ".") + d.name);
|
|
m.structs[d.name] = s.get();
|
|
structStore.push_back(std::move(s));
|
|
}
|
|
}
|
|
for (const auto &[name, cs] : cimports.structs) {
|
|
auto s = std::make_unique<StructInfo>();
|
|
s->name = name;
|
|
s->module = "C";
|
|
s->isC = true;
|
|
s->cstruct = &cs;
|
|
s->unsupported = cs.unsupported;
|
|
s->llvm = StructType::create(ctx, "c." + name);
|
|
cStructs[name] = s.get();
|
|
structStore.push_back(std::move(s));
|
|
}
|
|
for (auto &s : structStore) resolveStruct(s.get());
|
|
if (g_index)
|
|
for (auto &s : structStore) {
|
|
StructIndex si{s->module, s->name, {}, -1, 0, 0};
|
|
if (s->decl) {
|
|
si.file = s->decl->namePos.file;
|
|
si.line = s->decl->namePos.line;
|
|
si.col = s->decl->namePos.col;
|
|
note(s->decl->namePos, s->name.size(), structHover(s.get()), s->decl->namePos);
|
|
}
|
|
for (auto &f : s->fields) {
|
|
si.fields.push_back({f.name, f.type.show()});
|
|
if (s->decl) note(f.pos, f.name.size(), "```finch\n" + f.type.show() + " " + f.name + "\n```\nfield of `" + s->name + "`", f.pos);
|
|
}
|
|
g_index->structs.push_back(si);
|
|
}
|
|
}
|
|
|
|
void Codegen::declareFns() {
|
|
for (const Program &p : progs)
|
|
for (const FnDecl &fn : p.fns) declare(p, fn);
|
|
|
|
// Which methods change `self`? Calling one of them on something changes it too, so repeat until stable.
|
|
for (bool changed = true; changed;) {
|
|
changed = false;
|
|
for (auto &[s, ms] : methods)
|
|
for (auto &[name, f] : ms)
|
|
if (!f.mutatesSelf && mutates("self", *f.decl->body)) {
|
|
f.mutatesSelf = true;
|
|
mutatingMethods.insert(name);
|
|
changed = true;
|
|
}
|
|
}
|
|
// Owning parameters the function never changes are lent to it instead of copied.
|
|
auto borrow = [&](Fn &f) {
|
|
for (size_t i = 0; i < f.params.size(); i++)
|
|
f.borrowParam.push_back(owning(f.params[i]) && !mutates(f.decl->params[i].name, *f.decl->body));
|
|
};
|
|
for (auto &[name, m] : modules)
|
|
for (auto &[fname, f] : m.fns) borrow(f);
|
|
for (auto &[s, ms] : methods)
|
|
for (auto &[name, f] : ms) borrow(f);
|
|
}
|
|
|
|
FType Codegen::structType(StructInfo *s) {
|
|
FType t(FType::Struct);
|
|
t.info = s;
|
|
t.name = s->name;
|
|
t.module = s->module == "C" ? "" : s->module;
|
|
return t;
|
|
}
|
|
|
|
llvm::Type *Codegen::retType(const Fn &f) {
|
|
if (!f.fallible) return ty(f.ret);
|
|
llvm::Type *v = f.ret.kind == FType::Void ? (llvm::Type *)b.getInt8Ty() : ty(f.ret);
|
|
return StructType::get(ctx, {b.getInt1Ty(), v, ty(FType::Str)});
|
|
}
|
|
|
|
void Codegen::declare(const Program &p, const FnDecl &fn) {
|
|
ModuleScope &m = modules[p.module];
|
|
Pos pos = fn.pos;
|
|
curModule = p.module;
|
|
StructInfo *recv = nullptr;
|
|
if (!fn.recv.empty()) { // fn Point.move(...)
|
|
Pos rp = fn.recvPos;
|
|
recv = findStruct("", fn.recv, false);
|
|
if (!recv)
|
|
failAt(rp.file, rp.line, rp.col, "there is no struct named '" + fn.recv + "' here (a method goes in the same file as its struct, or on a struct from C)");
|
|
resolveStruct(recv);
|
|
for (auto &f : recv->fields)
|
|
if (f.name == fn.name && !f.hidden)
|
|
failAt(fn.namePos.file, fn.namePos.line, fn.namePos.col, "the struct " + recv->name + " already has a field named '" + fn.name + "'");
|
|
if (methods[recv].count(fn.name)) failAt(pos.file, pos.line, pos.col, "the method " + fn.recv + "." + fn.name + " is defined twice");
|
|
for (const Param &prm : fn.params)
|
|
if (prm.name == "self")
|
|
failAt(prm.namePos.file, prm.namePos.line, prm.namePos.col, "'self' is already the " + recv->name + " the method is called on");
|
|
} else {
|
|
if (m.fns.count(fn.name)) failAt(pos.file, pos.line, pos.col, "function '" + fn.name + "' is defined twice");
|
|
if (isBuiltin(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is a built-in function, pick another name");
|
|
if (m.structs.count(fn.name)) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is already the name of a struct");
|
|
}
|
|
|
|
Fn f;
|
|
f.decl = &fn;
|
|
f.module = p.module;
|
|
f.recv = recv;
|
|
f.fallible = fn.fallible;
|
|
f.ret = resolve(fn.ret, pos);
|
|
for (const Param &prm : fn.params) f.params.push_back(resolve(prm.type, prm.pos));
|
|
|
|
bool isMain = p.module.empty() && fn.name == "main";
|
|
if (isMain) {
|
|
bool argsOk = f.params.empty() || (f.params.size() == 1 && f.params[0] == FType::arrayOf(FType::Str));
|
|
if (!argsOk) failAt(pos.file, pos.line, pos.col, "main takes no parameters, or the program's arguments: fn main([]str args)");
|
|
FType::Kind r = f.ret.kind;
|
|
if (r != FType::Void && r != FType::I64 && r != FType::I32)
|
|
failAt(pos.file, pos.line, pos.col, "main returns nothing (fn main()) or an exit code (fn main() -> int)");
|
|
}
|
|
|
|
std::vector<llvm::Type *> params;
|
|
if (recv) params.push_back(b.getPtrTy()); // self
|
|
for (size_t i = 0; i < f.params.size(); i++) params.push_back(ty(f.params[i]));
|
|
// User functions get a prefix so they never clash with C library names.
|
|
std::string name = "finch." + (p.module.empty() ? "" : p.module + ".") + (recv ? recv->name + "." : "") + fn.name;
|
|
f.llvm = Function::Create(FunctionType::get(retType(f), params, false), Function::InternalLinkage, name, mod.get());
|
|
if (recv) methods[recv][fn.name] = f;
|
|
else m.fns[fn.name] = f;
|
|
}
|
|
|
|
// The real C `main` calls the Finch one and turns its result into an exit code.
|
|
void Codegen::defineMainWrapper() {
|
|
Fn &fm = modules[""].fns["main"];
|
|
Function *w = Function::Create(FunctionType::get(b.getInt32Ty(), {b.getInt32Ty(), b.getPtrTy()}, false),
|
|
Function::ExternalLinkage, "main", mod.get());
|
|
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", w));
|
|
b.SetCurrentDebugLocation(DebugLoc());
|
|
std::vector<Value *> args;
|
|
Value *argsArr = nullptr;
|
|
if (!fm.params.empty()) {
|
|
Value *out = tmp(Constant::getNullValue(ty(fm.params[0])));
|
|
b.CreateCall(rt("finch_args"), {out, w->getArg(0), w->getArg(1)});
|
|
argsArr = b.CreateLoad(ty(fm.params[0]), out);
|
|
args.push_back(argsArr);
|
|
}
|
|
Value *r = b.CreateCall(fm.llvm, args);
|
|
if (argsArr) dropValue(argsArr, fm.params[0]);
|
|
if (fm.fallible) { // fn main() -> !: a failure is printed, and the exit code is 1
|
|
BasicBlock *bad = BasicBlock::Create(ctx, "main.failed", w), *good = BasicBlock::Create(ctx, "main.ok", w);
|
|
b.CreateCondBr(b.CreateExtractValue(r, 0), good, bad);
|
|
b.SetInsertPoint(bad);
|
|
Value *msg = tmp(b.CreateExtractValue(r, 2));
|
|
b.CreateCall(rt("finch_report_error"), {msg});
|
|
b.CreateCall(rt("finch_str_drop"), {msg});
|
|
b.CreateRet(b.getInt32(1));
|
|
b.SetInsertPoint(good);
|
|
if (fm.ret.kind != FType::Void) r = b.CreateExtractValue(r, 1);
|
|
}
|
|
if (fm.ret.kind == FType::Void) b.CreateRet(b.getInt32(0));
|
|
else b.CreateRet(b.CreateIntCast(r, b.getInt32Ty(), true));
|
|
}
|
|
|
|
void Codegen::define(Fn &fn) {
|
|
const FnDecl &d = *fn.decl;
|
|
curFn = &fn;
|
|
curModule = fn.module;
|
|
if (g_index) {
|
|
std::string shown = fn.recv ? fn.recv->name + "." + d.name : d.name;
|
|
FnInfo fi{fn.module, shown, signature(fn), "", {}, d.namePos.file, d.namePos.line, d.namePos.col, d.body->end.line};
|
|
for (size_t i = 0; i < fn.params.size(); i++) fi.params.push_back(fn.params[i].show() + " " + d.params[i].name);
|
|
g_index->fns.push_back(fi);
|
|
note(d.namePos, d.name.size(), "```finch\n" + signature(fn) + "\n```", d.namePos);
|
|
}
|
|
Function *f = fn.llvm;
|
|
b.SetInsertPoint(BasicBlock::Create(ctx, "entry", f));
|
|
if (di) {
|
|
DIFile *file = diFiles[d.pos.file];
|
|
std::vector<Metadata *> types{diType(fn.ret)};
|
|
for (const FType &t : fn.params) types.push_back(diType(t));
|
|
diFn = di->createFunction(file, d.name, f->getName(), file, d.pos.line,
|
|
di->createSubroutineType(di->getOrCreateTypeArray(types)), d.pos.line,
|
|
DINode::FlagPrototyped, DISubprogram::SPFlagDefinition);
|
|
f->setSubprogram(diFn);
|
|
}
|
|
setLoc(d.pos);
|
|
|
|
scopes.clear();
|
|
varOrder = 0;
|
|
pushScope();
|
|
unsigned i = 0;
|
|
auto argIt = f->arg_begin();
|
|
if (fn.recv) { // `self` is the caller's struct itself, reached through its address
|
|
Argument &selfArg = *argIt++;
|
|
selfArg.setName("self");
|
|
Var v;
|
|
v.slot = &selfArg;
|
|
v.type = structType(fn.recv);
|
|
v.order = ++varOrder;
|
|
v.pos = d.recvPos;
|
|
scopes.back().vars["self"] = v;
|
|
if (g_index) g_index->vars.push_back({"self", v.type.show(), d.pos.file, d.pos.line, d.pos.col, d.pos.file, d.pos.line, d.body->end.line});
|
|
if (di) {
|
|
FType pt = FType::ptrTo(v.type);
|
|
AllocaInst *a = slot(pt, "self.addr");
|
|
b.CreateStore(&selfArg, a);
|
|
DILocalVariable *dv = di->createParameterVariable(diFn, "self", 1, diFiles[d.pos.file], d.pos.line, diType(pt));
|
|
di->insertDeclare(a, dv, di->createExpression(), DILocation::get(ctx, d.pos.line, d.pos.col, diFn), b.GetInsertBlock());
|
|
}
|
|
}
|
|
for (; argIt != f->arg_end(); ++argIt) {
|
|
Argument &arg = *argIt;
|
|
const Param &p = d.params[i];
|
|
const FType &t = fn.params[i];
|
|
arg.setName(p.name);
|
|
unsigned argNo = i + 1 + (fn.recv ? 1 : 0);
|
|
if (owning(t) && !fn.borrowParam[i]) addVar(p.namePos, p.name, t, copyValue(&arg, t), true, false, argNo); // changed inside: own a copy
|
|
else addVar(p.namePos, p.name, t, &arg, false, false, argNo);
|
|
i++;
|
|
}
|
|
blockBody(*d.body);
|
|
if (!terminated()) {
|
|
if (fn.ret.kind != FType::Void)
|
|
failAt(d.pos.file, d.pos.line, d.pos.col, "function '" + d.name + "' can reach its end without returning " + fn.ret.show());
|
|
emitCleanups(0);
|
|
retValue(nullptr);
|
|
}
|
|
scopes.clear();
|
|
diFn = nullptr;
|
|
}
|
|
|
|
// ---------- scopes ----------
|
|
|
|
void Codegen::pushScope() { scopes.emplace_back(); }
|
|
|
|
void Codegen::hold(const Value_ &v) {
|
|
if (v.fresh && v.v && !isa<Constant>(v.v) && owning(v.type)) pending.push_back({v.v, v.type, false});
|
|
}
|
|
|
|
void Codegen::holdAddr(Value *addr, const FType &t) {
|
|
if (owning(t)) pending.push_back({addr, t, true});
|
|
}
|
|
|
|
void Codegen::dropPending(size_t downTo) {
|
|
for (size_t i = pending.size(); i-- > downTo;) {
|
|
if (pending[i].isAddr) dropAt(pending[i].v, pending[i].type);
|
|
else dropValue(pending[i].v, pending[i].type);
|
|
}
|
|
}
|
|
|
|
void Codegen::popScope() {
|
|
if (!terminated()) emitScopeCleanups(scopes.size() - 1);
|
|
scopes.pop_back();
|
|
}
|
|
|
|
void Codegen::emitCleanups(size_t downTo) {
|
|
for (size_t i = scopes.size(); i-- > downTo;) emitScopeCleanups(i);
|
|
}
|
|
|
|
// Drop the scope's variables and run its defers, newest first.
|
|
void Codegen::emitScopeCleanups(size_t idx) {
|
|
for (size_t k = scopes[idx].cleanups.size(); k-- > 0;) {
|
|
Cleanup c = scopes[idx].cleanups[k]; // a copy: emitting a defer may grow `scopes`
|
|
if (!c.isDefer) {
|
|
Var v = scopes[idx].vars.at(c.var);
|
|
if (!v.moved) dropAt(v.slot, v.type);
|
|
continue;
|
|
}
|
|
int savedLimit = deferLimit;
|
|
deferLimit = c.order;
|
|
inDefer++;
|
|
pushScope();
|
|
stmt(*c.body);
|
|
popScope();
|
|
inDefer--;
|
|
deferLimit = savedLimit;
|
|
}
|
|
}
|
|
|
|
bool Codegen::terminated() { return b.GetInsertBlock()->getTerminator() != nullptr; }
|
|
|
|
BasicBlock *Codegen::newBlock(const char *name) {
|
|
return BasicBlock::Create(ctx, name, b.GetInsertBlock()->getParent());
|
|
}
|
|
|
|
// Variables live in stack slots created at the top of the function; LLVM turns them into registers.
|
|
AllocaInst *Codegen::slot(const FType &t, const std::string &name) {
|
|
Function *f = b.GetInsertBlock()->getParent();
|
|
IRBuilder<> entry(&f->getEntryBlock(), f->getEntryBlock().begin());
|
|
AllocaInst *a = entry.CreateAlloca(ty(t), nullptr, name);
|
|
if (t.kind == FType::Struct && t.info->isC) a->setAlignment(Align(t.info->cstruct->align));
|
|
return a;
|
|
}
|
|
|
|
Var *Codegen::lookup(const std::string &name) {
|
|
for (size_t i = scopes.size(); i-- > 0;) {
|
|
auto found = scopes[i].vars.find(name);
|
|
if (found == scopes[i].vars.end()) continue;
|
|
if (deferLimit >= 0 && found->second.order > deferLimit) continue; // declared after the defer
|
|
return &found->second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Var &Codegen::addVar(Pos p, const std::string &name, const FType &t, Value *init, bool owned, bool readonly,
|
|
unsigned argNo) {
|
|
// `err` of an inner `or` hides the outer one: f() or g() or err
|
|
if (lookup(name) && name != "err") failAt(p.file, p.line, p.col, "a variable named '" + name + "' already exists here");
|
|
ModuleScope &m = modules[curModule];
|
|
if (m.fns.count(name)) failAt(p.file, p.line, p.col, "'" + name + "' is already the name of a function");
|
|
if (m.imports.count(name)) failAt(p.file, p.line, p.col, "'" + name + "' is already the name of an imported module");
|
|
if (isBuiltin(name)) failAt(p.file, p.line, p.col, "'" + name + "' is a built-in name, pick another one");
|
|
AllocaInst *s = slot(t, name);
|
|
b.CreateStore(init, s);
|
|
Var v;
|
|
v.slot = s;
|
|
v.type = t;
|
|
v.readonly = readonly;
|
|
v.owned = owned && owning(t);
|
|
v.order = ++varOrder;
|
|
v.pos = p;
|
|
if (v.owned) scopes.back().cleanups.push_back({false, name});
|
|
if (g_index && name[0] != '$' && curFn) {
|
|
const FnDecl &d = *curFn->decl;
|
|
g_index->vars.push_back({name, t.show(), p.file, p.line, p.col, d.pos.file, d.pos.line, d.body->end.line});
|
|
note(p, name.size(), "```finch\n" + t.show() + " " + name + "\n```", p);
|
|
}
|
|
if (di && name[0] != '$') {
|
|
DILocalVariable *dv = argNo ? di->createParameterVariable(diFn, name, argNo, diFiles[p.file], p.line, diType(t))
|
|
: di->createAutoVariable(diFn, name, diFiles[p.file], p.line, diType(t));
|
|
di->insertDeclare(s, dv, di->createExpression(), DILocation::get(ctx, p.line, p.col, diFn), b.GetInsertBlock());
|
|
}
|
|
return scopes.back().vars[name] = v;
|
|
}
|
|
|
|
// ---------- statements ----------
|
|
|
|
void Codegen::block(const BlockStmt &blk) {
|
|
pushScope();
|
|
blockBody(blk);
|
|
popScope();
|
|
}
|
|
|
|
void Codegen::blockBody(const BlockStmt &blk) {
|
|
for (const StmtPtr &s : blk.body) {
|
|
if (terminated())
|
|
failAt(s->pos.file, s->pos.line, s->pos.col, "this code can never run (it comes after return, break or continue)");
|
|
stmt(*s);
|
|
}
|
|
}
|
|
|
|
void Codegen::stmt(const Stmt &s) {
|
|
setLoc(s.pos);
|
|
switch (s.kind) {
|
|
case StmtKind::Block: block(static_cast<const BlockStmt &>(s)); break;
|
|
case StmtKind::VarDecl: varDecl(static_cast<const VarDeclStmt &>(s)); break;
|
|
case StmtKind::Assign: assign(static_cast<const AssignStmt &>(s)); break;
|
|
case StmtKind::Expr: {
|
|
const Expr &x = *static_cast<const ExprStmt &>(s).expr;
|
|
release(x.kind == ExprKind::OrElse ? orElse(static_cast<const OrElseExpr &>(x), true) : expr(x));
|
|
break;
|
|
}
|
|
case StmtKind::If: ifStmt(static_cast<const IfStmt &>(s)); break;
|
|
case StmtKind::While: whileStmt(static_cast<const WhileStmt &>(s)); break;
|
|
case StmtKind::For: forStmt(static_cast<const ForStmt &>(s)); break;
|
|
case StmtKind::ForEach: forEachStmt(static_cast<const ForEachStmt &>(s)); break;
|
|
case StmtKind::Return: returnStmt(static_cast<const ReturnStmt &>(s)); break;
|
|
case StmtKind::Break: jump(true, s.pos); break;
|
|
case StmtKind::Continue: jump(false, s.pos); break;
|
|
case StmtKind::Defer: {
|
|
auto &d = static_cast<const DeferStmt &>(s);
|
|
scopes.back().cleanups.push_back({true, "", d.body.get(), varOrder});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Codegen::jump(bool isBreak, Pos p) {
|
|
const char *word = isBreak ? "break" : "continue";
|
|
if (inDefer) failAt(p.file, p.line, p.col, std::string(word) + " can't be used inside defer");
|
|
if (loops.empty()) failAt(p.file, p.line, p.col, std::string(word) + " can only be used inside a loop");
|
|
Loop l = loops.back();
|
|
dropPending(l.pendingDepth);
|
|
emitCleanups(l.scopeDepth);
|
|
b.CreateBr(isBreak ? l.breakTo : l.continueTo);
|
|
}
|
|
|
|
void Codegen::varDecl(const VarDeclStmt &s) {
|
|
Pos p = s.pos;
|
|
if (!s.hasType) {
|
|
Value_ v = expr(*s.init);
|
|
Pos ip = s.init->pos;
|
|
if (v.type.kind == FType::Void) failAt(ip.file, ip.line, ip.col, "this gives back nothing, so it can't be stored");
|
|
if (v.type.kind == FType::Null) failAt(ip.file, ip.line, ip.col, "null needs a type, like: ptr[int] " + s.name + " = null");
|
|
addVar(s.namePos, s.name, v.type, own(v), true);
|
|
return;
|
|
}
|
|
FType t = resolve(s.type, p);
|
|
if (t.kind == FType::Void) failAt(p.file, p.line, p.col, "a variable can't have the type 'nothing'");
|
|
Value_ v = s.init ? coerce(exprWant(*s.init, t), t, s.init->pos, "'" + s.name + "'") : defaultValue(t, p);
|
|
addVar(s.namePos, s.name, t, own(v), true);
|
|
}
|
|
|
|
void Codegen::assign(const AssignStmt &s) {
|
|
std::string root = rootVar(*s.target);
|
|
if (!root.empty()) {
|
|
Var *v = lookup(root);
|
|
if (v && v->readonly) failAt(s.pos.file, s.pos.line, s.pos.col, v->readonlyWhy);
|
|
if (!v && s.target->kind == ExprKind::Var && !cimports.globals.count(root))
|
|
failAt(s.pos.file, s.pos.line, s.pos.col, "there is no variable named '" + root + "' (create it with " + root + " := ...)");
|
|
}
|
|
std::string what = s.target->kind == ExprKind::Var ? "'" + root + "'" : "the value";
|
|
|
|
if (s.op == '=') {
|
|
// The right side first: it may move memory the target lives in (a.push(...) inside it).
|
|
Value_ v;
|
|
bool known = false;
|
|
FType want;
|
|
if (s.target->kind == ExprKind::Var) {
|
|
if (Var *var = lookup(root)) { want = var->type; known = true; }
|
|
}
|
|
v = known ? exprWant(*s.value, want) : expr(*s.value);
|
|
Held held(*this);
|
|
hold(v); // while the target is computed (a[try f()] = ...)
|
|
Place dst = place(*s.target, true);
|
|
v = coerce(v, dst.type, s.value->pos, what);
|
|
Value *nv = own(v);
|
|
if (owning(dst.type)) {
|
|
Value *old = b.CreateLoad(ty(dst.type), dst.addr);
|
|
b.CreateStore(nv, dst.addr);
|
|
dropValue(old, dst.type);
|
|
} else {
|
|
b.CreateStore(nv, dst.addr);
|
|
}
|
|
return;
|
|
}
|
|
BinOp op = s.op == '+' ? BinOp::Add : s.op == '-' ? BinOp::Sub : s.op == '*' ? BinOp::Mul
|
|
: s.op == '/' ? BinOp::Div : BinOp::Mod;
|
|
Value_ rhs = expr(*s.value);
|
|
Held held(*this);
|
|
hold(rhs);
|
|
Place dst = place(*s.target, true);
|
|
Value_ cur{b.CreateLoad(ty(dst.type), dst.addr), dst.type};
|
|
Value_ v = coerce(arith(op, cur, rhs, s.pos), dst.type, s.pos, what);
|
|
Value *nv = own(v);
|
|
if (owning(dst.type)) {
|
|
b.CreateStore(nv, dst.addr);
|
|
dropValue(cur.v, dst.type);
|
|
} else {
|
|
b.CreateStore(nv, dst.addr);
|
|
}
|
|
}
|
|
|
|
Value *Codegen::condition(const Expr &e) { return coerce(expr(e), FType::Bool, e.pos, "a condition").v; }
|
|
|
|
void Codegen::ifStmt(const IfStmt &s) {
|
|
Value *cond = condition(*s.cond);
|
|
BasicBlock *thenBB = newBlock("then");
|
|
BasicBlock *elseBB = s.otherwise ? newBlock("else") : nullptr;
|
|
BasicBlock *end = newBlock("endif");
|
|
b.CreateCondBr(cond, thenBB, elseBB ? elseBB : end);
|
|
|
|
b.SetInsertPoint(thenBB);
|
|
block(*s.then);
|
|
BasicBlock *last = b.GetInsertBlock();
|
|
if (!terminated()) b.CreateBr(end);
|
|
|
|
if (elseBB) {
|
|
b.SetInsertPoint(elseBB);
|
|
stmt(*s.otherwise);
|
|
last = b.GetInsertBlock();
|
|
if (!terminated()) b.CreateBr(end);
|
|
}
|
|
continueAt(end, last);
|
|
}
|
|
|
|
// Continue emitting into `bb`; if nothing jumps there, drop it and stay "after a return".
|
|
void Codegen::continueAt(BasicBlock *bb, BasicBlock *deadEnd) {
|
|
if (pred_empty(bb)) {
|
|
bb->eraseFromParent();
|
|
b.SetInsertPoint(deadEnd);
|
|
} else {
|
|
b.SetInsertPoint(bb);
|
|
}
|
|
}
|
|
|
|
void Codegen::whileStmt(const WhileStmt &s) {
|
|
BasicBlock *condBB = newBlock("while.cond");
|
|
BasicBlock *body = newBlock("while.body");
|
|
BasicBlock *end = newBlock("while.end");
|
|
b.CreateBr(condBB);
|
|
|
|
b.SetInsertPoint(condBB);
|
|
Value *cond = condition(*s.cond);
|
|
if (auto *c = dyn_cast<ConstantInt>(cond); c && c->isOne()) b.CreateBr(body); // while true
|
|
else b.CreateCondBr(cond, body, end);
|
|
|
|
b.SetInsertPoint(body);
|
|
loops.push_back({condBB, end, scopes.size(), pending.size()});
|
|
block(*s.body);
|
|
loops.pop_back();
|
|
if (!terminated()) b.CreateBr(condBB);
|
|
continueAt(end, b.GetInsertBlock());
|
|
}
|
|
|
|
void Codegen::forStmt(const ForStmt &s) {
|
|
Value *from = coerce(expr(*s.from), FType::I64, s.from->pos, "the start of a range").v;
|
|
Value *to = coerce(expr(*s.to), FType::I64, s.to->pos, "the end of a range").v;
|
|
|
|
pushScope();
|
|
Var &iv = addVar(s.varPos, s.var, FType::I64, from, false, true);
|
|
iv.readonlyWhy = "the loop variable '" + s.var + "' can't be changed";
|
|
Value *i = iv.slot;
|
|
|
|
BasicBlock *condBB = newBlock("for.cond");
|
|
BasicBlock *body = newBlock("for.body");
|
|
BasicBlock *step = newBlock("for.step");
|
|
BasicBlock *end = newBlock("for.end");
|
|
b.CreateBr(condBB);
|
|
|
|
b.SetInsertPoint(condBB);
|
|
b.CreateCondBr(b.CreateICmpSLT(b.CreateLoad(b.getInt64Ty(), i), to), body, end);
|
|
|
|
b.SetInsertPoint(body);
|
|
loops.push_back({step, end, scopes.size(), pending.size()});
|
|
block(*s.body);
|
|
loops.pop_back();
|
|
if (!terminated()) b.CreateBr(step);
|
|
|
|
b.SetInsertPoint(step);
|
|
b.CreateStore(b.CreateAdd(b.CreateLoad(b.getInt64Ty(), i), b.getInt64(1)), i);
|
|
b.CreateBr(condBB);
|
|
|
|
b.SetInsertPoint(end);
|
|
popScope();
|
|
}
|
|
|
|
// for x in list: x is each element (or each char of a str); for i, x in list: also its index;
|
|
// for key in map / for key, value in map: in the order the keys were added
|
|
void Codegen::forEachStmt(const ForEachStmt &s) {
|
|
pushScope();
|
|
LRef r = ref(*s.list);
|
|
Value *addr;
|
|
FType lt;
|
|
bool live = r.isPlace; // iterate the variable itself: re-read its length every round
|
|
if (r.isPlace) {
|
|
addr = r.pl.addr;
|
|
lt = r.pl.type;
|
|
} else {
|
|
lt = r.val.type;
|
|
addr = addVar(s.pos, "$list" + std::to_string(varOrder), lt, own(r.val), true).slot;
|
|
}
|
|
if (lt.kind == FType::Map) {
|
|
FType kt = *lt.key, vt = *lt.elem;
|
|
StructType *ET = mapEntryTy(lt);
|
|
llvm::Type *MT = ty(lt);
|
|
// if the loop changes the map, keys and values are copied, so they can't disappear under us
|
|
bool copyKV = live && mutates(rootVar(*s.list), *s.body);
|
|
Value *i = slot(FType::I64, "$i");
|
|
b.CreateStore(b.getInt64(0), i);
|
|
// adding keys inside the loop could reorder the entries: that stops the program (removing is fine)
|
|
Value *adds = live ? b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 6)) : nullptr;
|
|
BasicBlock *condBB = newBlock("map.cond"), *check = newBlock("map.check"), *body = newBlock("map.body"),
|
|
*step = newBlock("map.step"), *end = newBlock("map.end");
|
|
b.CreateBr(condBB);
|
|
b.SetInsertPoint(condBB);
|
|
if (adds && mutates(rootVar(*s.list), *s.body)) {
|
|
BasicBlock *changed = newBlock("map.changed"), *same = newBlock("map.same");
|
|
b.CreateCondBr(b.CreateICmpNE(b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 6)), adds), changed, same);
|
|
b.SetInsertPoint(changed);
|
|
b.CreateCall(rt("finch_map_changed"), {fileName(s.pos), b.getInt64(s.pos.line)});
|
|
b.CreateUnreachable();
|
|
b.SetInsertPoint(same);
|
|
}
|
|
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
|
b.CreateCondBr(b.CreateICmpSLT(iv, b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(MT, addr, 2))), check, end);
|
|
b.SetInsertPoint(check);
|
|
Value *entry = b.CreateInBoundsGEP(ET, b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(MT, addr, 0)), iv);
|
|
Value *hash = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ET, entry, 0));
|
|
b.CreateCondBr(b.CreateICmpEQ(hash, b.getInt64(0)), step, body); // a removed entry
|
|
b.SetInsertPoint(body);
|
|
loops.push_back({step, end, scopes.size(), pending.size()});
|
|
pushScope();
|
|
std::string why = " can't be changed (to change the map, use m[key] = ...)";
|
|
Value *kv = b.CreateLoad(ty(kt), b.CreateStructGEP(ET, entry, 1));
|
|
Var &k = addVar(s.varPos, s.var, kt, copyKV ? copyValue(kv, kt) : kv, copyKV, true);
|
|
k.readonlyWhy = "the loop variable '" + s.var + "'" + why;
|
|
if (!s.var2.empty()) {
|
|
Value *vv = b.CreateLoad(ty(vt), b.CreateStructGEP(ET, entry, 2));
|
|
Var &v = addVar(s.var2Pos, s.var2, vt, copyKV ? copyValue(vv, vt) : vv, copyKV, true);
|
|
v.readonlyWhy = "the loop variable '" + s.var2 + "'" + why;
|
|
}
|
|
block(*s.body);
|
|
popScope();
|
|
loops.pop_back();
|
|
if (!terminated()) b.CreateBr(step);
|
|
b.SetInsertPoint(step);
|
|
b.CreateStore(b.CreateAdd(b.CreateLoad(b.getInt64Ty(), i), b.getInt64(1)), i);
|
|
b.CreateBr(condBB);
|
|
b.SetInsertPoint(end);
|
|
popScope();
|
|
return;
|
|
}
|
|
if (lt.kind != FType::Array && lt.kind != FType::Str && lt.kind != FType::Fixed)
|
|
failAt(s.list->pos.file, s.list->pos.line, s.list->pos.col,
|
|
"for ... in needs an array, a str or a map, but this is " + lt.show() + " (for a range of numbers write 0..n)");
|
|
FType et = lt.kind == FType::Str ? FType(FType::Char) : *lt.elem;
|
|
// If the loop changes the list, each element is copied, so it can't disappear under us.
|
|
bool copyElems = live && owning(et) && mutates(rootVar(*s.list), *s.body);
|
|
|
|
Value *i = slot(FType::I64, "$i");
|
|
b.CreateStore(b.getInt64(0), i);
|
|
BasicBlock *condBB = newBlock("each.cond");
|
|
BasicBlock *body = newBlock("each.body");
|
|
BasicBlock *step = newBlock("each.step");
|
|
BasicBlock *end = newBlock("each.end");
|
|
b.CreateBr(condBB);
|
|
|
|
b.SetInsertPoint(condBB);
|
|
Value *len = lt.kind == FType::Fixed ? (Value *)b.getInt64(lt.count) : b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(lt), addr, 1));
|
|
Value *iv = b.CreateLoad(b.getInt64Ty(), i);
|
|
b.CreateCondBr(b.CreateICmpSLT(iv, len), body, end);
|
|
|
|
b.SetInsertPoint(body);
|
|
Value *elemAddr;
|
|
if (lt.kind == FType::Fixed) {
|
|
elemAddr = b.CreateInBoundsGEP(ty(lt), addr, {b.getInt64(0), iv});
|
|
} else {
|
|
Value *data = b.CreateLoad(b.getPtrTy(), b.CreateStructGEP(ty(lt), addr, 0));
|
|
elemAddr = b.CreateInBoundsGEP(ty(et), data, iv);
|
|
}
|
|
Value *elem = b.CreateLoad(ty(et), elemAddr);
|
|
loops.push_back({step, end, scopes.size(), pending.size()});
|
|
pushScope();
|
|
std::string elemVar = s.var;
|
|
Pos elemPos = s.varPos;
|
|
if (!s.var2.empty()) { // for i, x in list
|
|
Var &index = addVar(s.varPos, s.var, FType::I64, iv, false, true);
|
|
index.readonlyWhy = "the loop variable '" + s.var + "' can't be changed";
|
|
elemVar = s.var2;
|
|
elemPos = s.var2Pos;
|
|
}
|
|
Var &xv = addVar(elemPos, elemVar, et, copyElems ? copyValue(elem, et) : elem, copyElems, true);
|
|
xv.readonlyWhy = "the loop variable '" + elemVar + "' can't be changed (to change the list, use list[i] = ...)";
|
|
block(*s.body);
|
|
popScope();
|
|
loops.pop_back();
|
|
if (!terminated()) b.CreateBr(step);
|
|
|
|
b.SetInsertPoint(step);
|
|
b.CreateStore(b.CreateAdd(b.CreateLoad(b.getInt64Ty(), i), b.getInt64(1)), i);
|
|
b.CreateBr(condBB);
|
|
|
|
b.SetInsertPoint(end);
|
|
popScope();
|
|
}
|
|
|
|
void Codegen::retValue(Value *v) {
|
|
if (!curFn->fallible) {
|
|
if (v) b.CreateRet(v);
|
|
else b.CreateRetVoid();
|
|
return;
|
|
}
|
|
Value *r = Constant::getNullValue(curFn->llvm->getReturnType());
|
|
r = b.CreateInsertValue(r, b.getInt1(true), 0);
|
|
if (v) r = b.CreateInsertValue(r, v, 1);
|
|
b.CreateRet(r);
|
|
}
|
|
|
|
void Codegen::retError(Value *msg) {
|
|
Value *r = Constant::getNullValue(curFn->llvm->getReturnType());
|
|
b.CreateRet(b.CreateInsertValue(r, msg, 2));
|
|
}
|
|
|
|
void Codegen::returnStmt(const ReturnStmt &s) {
|
|
const FnDecl &d = *curFn->decl;
|
|
if (inDefer) failAt(s.pos.file, s.pos.line, s.pos.col, "return can't be used inside defer");
|
|
// return error("...")
|
|
if (s.value && s.value->kind == ExprKind::Call && static_cast<const CallExpr &>(*s.value).callee == "error") {
|
|
auto &c = static_cast<const CallExpr &>(*s.value);
|
|
if (!curFn->fallible)
|
|
failAt(c.pos.file, c.pos.line, c.pos.col, "'" + d.name + "' can't fail, so it can't return an error (make it fallible: -> " +
|
|
(curFn->ret.kind == FType::Void ? std::string("!") : curFn->ret.show() + "!") + ")");
|
|
if (g_index)
|
|
for (const BuiltinDoc &bd : kBuiltins)
|
|
if (std::string(bd.name) == "error") note(c.pos, 5, "```finch\n" + std::string(bd.signature) + "\n```\n" + bd.doc);
|
|
checkArgs(c.args, c.argNames, 1, c.pos, "error");
|
|
Value *msg = own(coerce(expr(*c.args[0]), FType::Str, c.args[0]->pos, "the error message"));
|
|
dropPending(0);
|
|
emitCleanups(0);
|
|
retError(msg);
|
|
return;
|
|
}
|
|
if (curFn->ret.kind == FType::Void) {
|
|
if (s.value)
|
|
failAt(s.value->pos.file, s.value->pos.line, s.value->pos.col, "'" + d.name + "' doesn't return a value (it has no '-> type')");
|
|
dropPending(0);
|
|
emitCleanups(0);
|
|
retValue(nullptr);
|
|
return;
|
|
}
|
|
if (!s.value) failAt(s.pos.file, s.pos.line, s.pos.col, "'" + d.name + "' must return " + curFn->ret.show());
|
|
|
|
// `return local` moves the variable out instead of copying it.
|
|
if (s.value->kind == ExprKind::Var) {
|
|
const std::string &name = static_cast<const VarExpr &>(*s.value).name;
|
|
for (size_t idx = scopes.size(); idx-- > 0;) {
|
|
auto it = scopes[idx].vars.find(name);
|
|
if (it == scopes[idx].vars.end()) continue;
|
|
if (it->second.owned && it->second.type == curFn->ret) {
|
|
Value *v = b.CreateLoad(ty(it->second.type), it->second.slot);
|
|
it->second.moved = true;
|
|
dropPending(0);
|
|
emitCleanups(0);
|
|
scopes[idx].vars[name].moved = false;
|
|
retValue(v);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
Value_ v = coerce(exprWant(*s.value, curFn->ret), curFn->ret, s.value->pos, "the returned value");
|
|
Value *out = own(v);
|
|
dropPending(0);
|
|
emitCleanups(0);
|
|
retValue(out);
|
|
}
|
|
|
|
// ---------- mutation analysis (does a function or loop change a variable?) ----------
|
|
|
|
std::string Codegen::rootVar(const Expr &e) {
|
|
switch (e.kind) {
|
|
case ExprKind::Var: return static_cast<const VarExpr &>(e).name;
|
|
case ExprKind::Member: return rootVar(*static_cast<const MemberExpr &>(e).obj);
|
|
case ExprKind::Index: return rootVar(*static_cast<const IndexExpr &>(e).obj);
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
bool Codegen::mutatesExpr(const std::string &name, const Expr &e) {
|
|
static const std::set<std::string> changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"};
|
|
switch (e.kind) {
|
|
case ExprKind::Try: return mutatesExpr(name, *static_cast<const TryExpr &>(e).call);
|
|
case ExprKind::OrElse: {
|
|
auto &x = static_cast<const OrElseExpr &>(e);
|
|
return mutatesExpr(name, *x.call) || (x.fallback && mutatesExpr(name, *x.fallback)) || (x.block && mutates(name, *x.block));
|
|
}
|
|
case ExprKind::MapLit: {
|
|
auto &x = static_cast<const MapLitExpr &>(e);
|
|
for (size_t k = 0; k < x.keys.size(); k++)
|
|
if (mutatesExpr(name, *x.keys[k]) || mutatesExpr(name, *x.values[k])) return true;
|
|
return false;
|
|
}
|
|
case ExprKind::Unary: return mutatesExpr(name, *static_cast<const UnaryExpr &>(e).operand);
|
|
case ExprKind::Binary: {
|
|
auto &x = static_cast<const BinaryExpr &>(e);
|
|
return mutatesExpr(name, *x.lhs) || mutatesExpr(name, *x.rhs);
|
|
}
|
|
case ExprKind::Call: {
|
|
auto &c = static_cast<const CallExpr &>(e);
|
|
if (c.callee == "addr" && c.args.size() == 1 && rootVar(*c.args[0]) == name) return true;
|
|
for (auto &a : c.args)
|
|
if (mutatesExpr(name, *a)) return true;
|
|
return false;
|
|
}
|
|
case ExprKind::Member: return mutatesExpr(name, *static_cast<const MemberExpr &>(e).obj);
|
|
case ExprKind::Index: {
|
|
auto &x = static_cast<const IndexExpr &>(e);
|
|
return mutatesExpr(name, *x.obj) || mutatesExpr(name, *x.index);
|
|
}
|
|
case ExprKind::ArrayLit:
|
|
for (auto &a : static_cast<const ArrayLitExpr &>(e).elems)
|
|
if (mutatesExpr(name, *a)) return true;
|
|
return false;
|
|
case ExprKind::Method: {
|
|
auto &m = static_cast<const MethodExpr &>(e);
|
|
if ((changing.count(m.name) || mutatingMethods.count(m.name)) && rootVar(*m.obj) == name) return true;
|
|
if (mutatesExpr(name, *m.obj)) return true;
|
|
for (auto &a : m.args)
|
|
if (mutatesExpr(name, *a)) return true;
|
|
return false;
|
|
}
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool Codegen::mutates(const std::string &name, const Stmt &s) {
|
|
auto e = [&](const ExprPtr &x) { return x && mutatesExpr(name, *x); };
|
|
switch (s.kind) {
|
|
case StmtKind::Block:
|
|
for (auto &st : static_cast<const BlockStmt &>(s).body)
|
|
if (mutates(name, *st)) return true;
|
|
return false;
|
|
case StmtKind::VarDecl: return e(static_cast<const VarDeclStmt &>(s).init);
|
|
case StmtKind::Assign: {
|
|
auto &a = static_cast<const AssignStmt &>(s);
|
|
return rootVar(*a.target) == name || e(a.target) || e(a.value);
|
|
}
|
|
case StmtKind::Expr: return e(static_cast<const ExprStmt &>(s).expr);
|
|
case StmtKind::If: {
|
|
auto &x = static_cast<const IfStmt &>(s);
|
|
return e(x.cond) || mutates(name, *x.then) || (x.otherwise && mutates(name, *x.otherwise));
|
|
}
|
|
case StmtKind::While: {
|
|
auto &x = static_cast<const WhileStmt &>(s);
|
|
return e(x.cond) || mutates(name, *x.body);
|
|
}
|
|
case StmtKind::For: {
|
|
auto &x = static_cast<const ForStmt &>(s);
|
|
return e(x.from) || e(x.to) || mutates(name, *x.body);
|
|
}
|
|
case StmtKind::ForEach: {
|
|
auto &x = static_cast<const ForEachStmt &>(s);
|
|
return e(x.list) || mutates(name, *x.body);
|
|
}
|
|
case StmtKind::Return: return e(static_cast<const ReturnStmt &>(s).value);
|
|
case StmtKind::Defer: return mutates(name, *static_cast<const DeferStmt &>(s).body);
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
// ---------- expressions ----------
|
|
|
|
Value_ Codegen::exprWant(const Expr &e, const FType &want) {
|
|
if (e.kind == ExprKind::ArrayLit) return arrayLit(static_cast<const ArrayLitExpr &>(e), &want);
|
|
if (e.kind == ExprKind::MapLit) return mapLit(static_cast<const MapLitExpr &>(e), &want);
|
|
return expr(e);
|
|
}
|
|
|
|
// ---------- errors as values ----------
|
|
|
|
// Emit a call that may fail; `failed` (i1) and `msg` (str) tell how it went.
|
|
Value_ Codegen::fallible(const Expr &call, Value *&failed, Value *&msg) {
|
|
Pos p = call.pos;
|
|
if (call.kind != ExprKind::Call && call.kind != ExprKind::Method)
|
|
failAt(p.file, p.line, p.col, "only a function call can fail; 'or' and 'try' go after one, like int(text) or 0");
|
|
const Expr *savedTarget = fallibleTarget;
|
|
Value *savedFailed = fallibleFailed, *savedMsg = fallibleMsg;
|
|
fallibleTarget = &call;
|
|
fallibleFailed = fallibleMsg = nullptr;
|
|
Value_ v = expr(call);
|
|
failed = fallibleFailed;
|
|
msg = fallibleMsg;
|
|
fallibleTarget = savedTarget;
|
|
fallibleFailed = savedFailed;
|
|
fallibleMsg = savedMsg;
|
|
if (!failed) failAt(p.file, p.line, p.col, "this call can't fail, so it needs no 'or' or 'try'");
|
|
return v;
|
|
}
|
|
|
|
// try f(x): on failure, the current function fails with the same error
|
|
Value_ Codegen::tryExpr(const TryExpr &t) {
|
|
Pos p = t.pos;
|
|
if (inDefer) failAt(p.file, p.line, p.col, "try can't be used inside defer");
|
|
if (!curFn->fallible)
|
|
failAt(p.file, p.line, p.col, "try passes the error on, so '" + curFn->decl->name + "' must be able to fail too (-> " +
|
|
(curFn->ret.kind == FType::Void ? std::string("!") : curFn->ret.show() + "!") +
|
|
"); or handle it here: ... or <value> / ... or { ... }");
|
|
Value *failed, *msg;
|
|
Value_ v = fallible(*t.call, failed, msg);
|
|
BasicBlock *bad = newBlock("try.failed"), *ok = newBlock("try.ok");
|
|
b.CreateCondBr(failed, bad, ok);
|
|
b.SetInsertPoint(bad);
|
|
dropPending(0); // what the expressions around this `try` were holding
|
|
emitCleanups(0);
|
|
retError(msg);
|
|
b.SetInsertPoint(ok);
|
|
return v;
|
|
}
|
|
|
|
// f(x) or fallback / f(x) or { ... err ... }
|
|
Value_ Codegen::orElse(const OrElseExpr &e, bool discard) {
|
|
Value *failed, *msg;
|
|
Value_ v = fallible(*e.call, failed, msg);
|
|
BasicBlock *bad = newBlock("or.failed"), *ok = newBlock("or.ok");
|
|
b.CreateCondBr(failed, bad, ok);
|
|
b.SetInsertPoint(bad);
|
|
if (e.block) {
|
|
pushScope();
|
|
addVar(e.block->pos, "err", FType::Str, msg, true);
|
|
blockBody(*e.block);
|
|
bool left = terminated();
|
|
if (!left && v.type.kind != FType::Void && !discard)
|
|
failAt(e.block->end.file, e.block->end.line, e.block->end.col,
|
|
"this block must leave with return, break, continue or exit(...): after a failure there is no value to go on with (or give one: ... or <value>)");
|
|
popScope();
|
|
if (discard && v.type.kind != FType::Void) { // read_file(p) or { ... } as a statement: drop the unused value
|
|
BasicBlock *join = newBlock("or.end");
|
|
if (!left) b.CreateBr(join);
|
|
b.SetInsertPoint(ok);
|
|
release(v);
|
|
b.CreateBr(join);
|
|
b.SetInsertPoint(join);
|
|
return {nullptr, FType::Void};
|
|
}
|
|
if (!left) b.CreateBr(ok);
|
|
b.SetInsertPoint(ok);
|
|
return v;
|
|
}
|
|
Pos fp = e.fallback->pos;
|
|
if (v.type.kind == FType::Void)
|
|
failAt(fp.file, fp.line, fp.col, "this call gives back no value, so there is nothing to replace; handle the failure with or { ... }");
|
|
pushScope(); // the fallback can use the message too: f(x) or err
|
|
addVar(fp, "err", FType::Str, msg, true);
|
|
Value *fv = own(coerce(exprWant(*e.fallback, v.type), v.type, fp, "the value after 'or'"));
|
|
popScope();
|
|
BasicBlock *badEnd = b.GetInsertBlock();
|
|
BasicBlock *join = newBlock("or.end");
|
|
b.CreateBr(join);
|
|
b.SetInsertPoint(ok);
|
|
b.CreateBr(join);
|
|
b.SetInsertPoint(join);
|
|
PHINode *phi = b.CreatePHI(ty(v.type), 2);
|
|
phi->addIncoming(fv, badEnd);
|
|
phi->addIncoming(v.v, ok);
|
|
return {phi, v.type, false, owning(v.type)};
|
|
}
|
|
|
|
Value_ Codegen::expr(const Expr &e) {
|
|
switch (e.kind) {
|
|
case ExprKind::Int: return {b.getInt64(static_cast<const IntExpr &>(e).value), FType::I64, true};
|
|
case ExprKind::Float: return {ConstantFP::get(b.getDoubleTy(), static_cast<const FloatExpr &>(e).value), FType::F64, true};
|
|
case ExprKind::Bool: return {b.getInt1(static_cast<const BoolExpr &>(e).value), FType::Bool};
|
|
case ExprKind::Char: return {b.getInt8(static_cast<const CharExpr &>(e).value), FType::Char};
|
|
case ExprKind::Str: return {strConst(static_cast<const StrExpr &>(e).value), FType::Str, false, true};
|
|
case ExprKind::Null: return {ConstantPointerNull::get(b.getPtrTy()), FType::Null};
|
|
case ExprKind::Var: {
|
|
auto &v = static_cast<const VarExpr &>(e);
|
|
if (!lookup(v.name)) {
|
|
auto c = cimports.consts.find(v.name);
|
|
if (c != cimports.consts.end()) {
|
|
note(e.pos, v.name.size(), "C constant `" + v.name + " = " +
|
|
(c->second.type.isFloat() ? std::to_string(c->second.f) : std::to_string(c->second.i)) + "`");
|
|
if (c->second.type.isFloat()) return {ConstantFP::get(b.getDoubleTy(), c->second.f), FType::F64, true};
|
|
return {b.getInt64(c->second.i), FType::I64, true};
|
|
}
|
|
if (modules[curModule].imports.count(v.name))
|
|
failAt(e.pos.file, e.pos.line, e.pos.col, "'" + v.name + "' is a module; use something from it, like " + v.name + ".name(...)");
|
|
// C's standard streams are macros on some systems; the runtime knows them everywhere
|
|
if (!cimports.globals.count(v.name) && (v.name == "stdin" || v.name == "stdout" || v.name == "stderr") &&
|
|
cimports.fns.count("fprintf")) {
|
|
int which = v.name == "stdin" ? 0 : v.name == "stdout" ? 1 : 2;
|
|
return {b.CreateCall(rt("finch_std_stream"), {b.getInt32(which)}), FType::Ptr};
|
|
}
|
|
}
|
|
Place pl = place(e);
|
|
return {b.CreateLoad(ty(pl.type), pl.addr, v.name), pl.type};
|
|
}
|
|
case ExprKind::Member:
|
|
case ExprKind::Index: {
|
|
LRef r = ref(e);
|
|
if (!r.isPlace) return r.val;
|
|
return {b.CreateLoad(ty(r.pl.type), r.pl.addr), r.pl.type};
|
|
}
|
|
case ExprKind::Unary: return unary(static_cast<const UnaryExpr &>(e));
|
|
case ExprKind::Binary: return binary(static_cast<const BinaryExpr &>(e));
|
|
case ExprKind::Call: return call(static_cast<const CallExpr &>(e));
|
|
case ExprKind::Method: return method(static_cast<const MethodExpr &>(e));
|
|
case ExprKind::ArrayLit: return arrayLit(static_cast<const ArrayLitExpr &>(e), nullptr);
|
|
case ExprKind::MapLit: return mapLit(static_cast<const MapLitExpr &>(e), nullptr);
|
|
case ExprKind::Try: return tryExpr(static_cast<const TryExpr &>(e));
|
|
case ExprKind::OrElse: return orElse(static_cast<const OrElseExpr &>(e));
|
|
}
|
|
return {nullptr, FType::Void};
|
|
}
|
|
|
|
Value_ Codegen::unary(const UnaryExpr &u) {
|
|
Pos p = u.pos;
|
|
Value_ v = expr(*u.operand);
|
|
if (u.op == '!') return {b.CreateNot(coerce(v, FType::Bool, u.operand->pos, "the value after '!'").v), FType::Bool};
|
|
if (u.op == '~') {
|
|
if (!v.type.isInt()) failAt(p.file, p.line, p.col, "'~' flips the bits of a whole number, not of " + v.type.show());
|
|
return {b.CreateNot(v.v), v.type};
|
|
}
|
|
if (v.type.isSigned()) return {b.CreateNeg(v.v), v.type, v.literal};
|
|
if (v.type.isFloat()) return {b.CreateFNeg(v.v), v.type, v.literal};
|
|
if (v.type.isUnsigned()) failAt(p.file, p.line, p.col, v.type.show() + " can't be negative (it's unsigned)");
|
|
failAt(p.file, p.line, p.col, "can't put '-' in front of " + v.type.show());
|
|
}
|
|
|
|
// An address for `e` if it has one (a variable, field, element, p.value); otherwise its value.
|
|
LRef Codegen::ref(const Expr &e, bool forWrite) {
|
|
switch (e.kind) {
|
|
case ExprKind::Var: {
|
|
auto &v = static_cast<const VarExpr &>(e);
|
|
if (Var *var = lookup(v.name)) {
|
|
note(e.pos, v.name.size(), "```finch\n" + var->type.show() + " " + v.name + "\n```", var->pos);
|
|
return {true, {var->slot, var->type}, {}};
|
|
}
|
|
if (cimports.globals.count(v.name)) return {true, {cGlobal(v.name), resolve(cimports.globals.at(v.name).type, e.pos)}, {}};
|
|
return {false, {}, expr(e)};
|
|
}
|
|
case ExprKind::Member: return member(static_cast<const MemberExpr &>(e), forWrite);
|
|
case ExprKind::Index: return index(static_cast<const IndexExpr &>(e), forWrite);
|
|
default: return {false, {}, expr(e)};
|
|
}
|
|
}
|
|
|
|
Place Codegen::place(const Expr &e, bool forWrite) {
|
|
if (e.kind == ExprKind::Var) {
|
|
auto &v = static_cast<const VarExpr &>(e);
|
|
if (!lookup(v.name) && !cimports.globals.count(v.name))
|
|
failAt(e.pos.file, e.pos.line, e.pos.col, "there is no variable named '" + v.name + "'");
|
|
}
|
|
LRef r = ref(e, forWrite);
|
|
if (!r.isPlace) failAt(e.pos.file, e.pos.line, e.pos.col, "this is a temporary value, it can't be changed or pointed to");
|
|
return r.pl;
|
|
}
|
|
|
|
LRef Codegen::member(const MemberExpr &m, bool forWrite) {
|
|
Pos p = m.pos;
|
|
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))
|
|
failAt(p.file, p.line, p.col, "modules only have functions and structs; call it: " + mn + "." + m.field + "(...)");
|
|
}
|
|
LRef obj = ref(*m.obj, forWrite);
|
|
FType t = obj.isPlace ? obj.pl.type : obj.val.type;
|
|
|
|
// pointers: p.value is the target, p.field reaches through to a struct's field
|
|
if (t.isPtr()) {
|
|
Value *pv = obj.isPlace ? b.CreateLoad(b.getPtrTy(), obj.pl.addr) : obj.val.v;
|
|
if (!t.elem) failAt(p.file, p.line, p.col, "this ptr has no type, so Finch doesn't know what's inside (use ptr[int] etc.)");
|
|
if (!isa<AllocaInst>(pv) && !isa<GlobalValue>(pv))
|
|
panicIf(b.CreateIsNull(pv), "used ." + m.field + " on a null pointer", p);
|
|
// p.value is the target -- unless the target is a struct with its own field called `value`
|
|
bool structHasValue = false;
|
|
if (t.elem->kind == FType::Struct)
|
|
for (auto &f : t.elem->info->fields) structHasValue |= f.name == "value" && !f.hidden;
|
|
if (m.field == "value" && !structHasValue) return {true, {pv, *t.elem}, {}};
|
|
if (t.elem->kind != FType::Struct)
|
|
failAt(p.file, p.line, p.col, "a " + t.show() + " only has '.value' (the thing it points to)");
|
|
obj = {true, {pv, *t.elem}, {}};
|
|
t = *t.elem;
|
|
}
|
|
|
|
if (t.kind == FType::Map) {
|
|
if (m.field != "len") failAt(p.file, p.line, p.col, "a map has .len (the number of keys), not '." + m.field + "'");
|
|
if (forWrite) failAt(p.file, p.line, p.col, ".len can't be changed directly (use m[key] = ..., remove or clear)");
|
|
note(Pos{p.line, p.col + 1, p.file}, 3, "`.len -> int`: the number of keys");
|
|
Value *len;
|
|
if (obj.isPlace) {
|
|
len = b.CreateLoad(b.getInt64Ty(), b.CreateStructGEP(ty(t), obj.pl.addr, 1));
|
|
} else {
|
|
len = b.CreateExtractValue(obj.val.v, 1);
|
|
release(obj.val);
|
|
}
|
|
return {false, {}, {len, FType::I64}};
|
|
}
|
|
if (t.kind == FType::Array || t.kind == FType::Str || t.kind == FType::Fixed) {
|
|
if (m.field == "len" || m.field == "ptr")
|
|
note(Pos{p.line, p.col + 1, p.file}, m.field.size(),
|
|
m.field == "len" ? "`.len -> int`: the number of " + std::string(t.kind == FType::Str ? "bytes" : "elements")
|
|
: "`.ptr`: the address of the first element, for C");
|
|
if (m.field != "len" && m.field != "ptr")
|
|
failAt(p.file, p.line, p.col, (t.kind == FType::Str ? "a str" : "an array") + std::string(" has .len and .ptr, not '.") + m.field + "'");
|
|
if (forWrite) failAt(p.file, p.line, p.col, "." + m.field + " can't be changed directly" + (t.kind == FType::Array ? " (use resize, push or pop)" : ""));
|
|
if (t.kind == FType::Fixed) {
|
|
if (m.field == "len") return {false, {}, {b.getInt64(t.count), FType::I64}};
|
|
if (!obj.isPlace) failAt(p.file, p.line, p.col, ".ptr of a temporary value would point to nothing");
|
|
return {false, {}, {obj.pl.addr, FType::ptrTo(*t.elem)}};
|
|
}
|
|
Value *v = obj.isPlace ? b.CreateLoad(ty(t), obj.pl.addr) : obj.val.v;
|
|
if (m.field == "len") {
|
|
Value *len = b.CreateExtractValue(v, 1);
|
|
if (!obj.isPlace) release(obj.val);
|
|
return {false, {}, {len, FType::I64}};
|
|
}
|
|
// a literal lives as long as the program; other temporaries are freed right after this expression
|
|
if (!obj.isPlace && obj.val.fresh && !isa<Constant>(obj.val.v))
|
|
failAt(p.file, p.line, p.col, ".ptr of a temporary value would point to freed memory");
|
|
FType pt = t.kind == FType::Str ? FType::ptrTo(FType::Char) : FType::ptrTo(*t.elem);
|
|
return {false, {}, {t.kind == FType::Str ? cstr(v) : b.CreateExtractValue(v, 0), pt}};
|
|
}
|
|
|
|
if (t.kind != FType::Struct) failAt(p.file, p.line, p.col, t.show() + " has no fields (no '." + m.field + "')");
|
|
StructInfo *s = t.info;
|
|
int idx = -1;
|
|
for (size_t k = 0; k < s->fields.size(); k++)
|
|
if (s->fields[k].name == m.field && !s->fields[k].hidden) idx = (int)k;
|
|
if (idx < 0) {
|
|
std::string names;
|
|
for (auto &f : s->fields)
|
|
if (!f.hidden) names += (names.empty() ? "" : ", ") + f.name;
|
|
failAt(p.file, p.line, p.col, "the struct " + t.show() + " has no field '" + m.field + "' (it has: " + names + ")");
|
|
}
|
|
const StructInfo::F &f = s->fields[idx];
|
|
note(Pos{p.line, p.col + 1, p.file}, m.field.size(),
|
|
"```finch\n" + f.type.show() + " " + m.field + "\n```\nfield of `" + s->name + "`", f.pos);
|
|
unsigned llvmIdx = f.llvmIndex;
|
|
if (obj.isPlace) return {true, {b.CreateStructGEP(s->llvm, obj.pl.addr, llvmIdx), f.type}, {}};
|
|
// a field of a temporary struct: take it (copying if it owns memory), then drop the rest
|
|
Value *fv = b.CreateExtractValue(obj.val.v, llvmIdx);
|
|
Value_ out{fv, f.type};
|
|
if (owning(f.type) && obj.val.fresh) out = {copyValue(fv, f.type), f.type, false, true};
|
|
release(obj.val);
|
|
return {false, {}, out};
|
|
}
|
|
|
|
LRef Codegen::index(const IndexExpr &e, bool forWrite) {
|
|
Pos p = e.pos;
|
|
LRef obj = ref(*e.obj, forWrite);
|
|
FType t = obj.isPlace ? obj.pl.type : obj.val.type;
|
|
Held held(*this);
|
|
if (!obj.isPlace) hold(obj.val); // while the index is computed
|
|
if (t.kind == FType::Map) return mapIndex(e, obj, t, forWrite);
|
|
Value *i = coerce(expr(*e.index), FType::I64, e.index->pos, "an index").v;
|
|
|
|
if (t.isPtr()) { // C-style: p[i], no bounds known
|
|
if (!t.elem) failAt(p.file, p.line, p.col, "this ptr has no type, so Finch doesn't know what's inside (use ptr[int] etc.)");
|
|
Value *pv = obj.isPlace ? b.CreateLoad(b.getPtrTy(), obj.pl.addr) : obj.val.v;
|
|
panicIf(b.CreateIsNull(pv), "used [] on a null pointer", p);
|
|
return {true, {b.CreateInBoundsGEP(ty(*t.elem), pv, i), *t.elem}, {}};
|
|
}
|
|
if (t.kind == FType::Fixed) {
|
|
boundsCheck(i, b.getInt64(t.count), p);
|
|
if (obj.isPlace) return {true, {b.CreateInBoundsGEP(ty(t), obj.pl.addr, {b.getInt64(0), i}), *t.elem}, {}};
|
|
Value *a = tmp(obj.val.v);
|
|
return {false, {}, {b.CreateLoad(ty(*t.elem), b.CreateInBoundsGEP(ty(t), a, {b.getInt64(0), i})), *t.elem}};
|
|
}
|
|
if (t.kind != FType::Array && t.kind != FType::Str)
|
|
failAt(p.file, p.line, p.col, "[ ] works on arrays, maps, str and pointers, not on " + t.show());
|
|
FType et = t.kind == FType::Str ? FType(FType::Char) : *t.elem;
|
|
if (t.kind == FType::Str && forWrite) {
|
|
if (!obj.isPlace) failAt(p.file, p.line, p.col, "this is a temporary str, it can't be changed");
|
|
b.CreateCall(rt("finch_str_own"), {obj.pl.addr}); // literals live in read-only memory: copy first
|
|
}
|
|
Value *v = obj.isPlace ? b.CreateLoad(ty(t), obj.pl.addr) : obj.val.v;
|
|
Value *len = b.CreateExtractValue(v, 1);
|
|
boundsCheck(i, len, p);
|
|
Value *addr = b.CreateInBoundsGEP(ty(et), b.CreateExtractValue(v, 0), i);
|
|
if (obj.isPlace) return {true, {addr, et}, {}};
|
|
Value *ev = b.CreateLoad(ty(et), addr);
|
|
Value_ out{ev, et};
|
|
if (owning(et) && obj.val.fresh) out = {copyValue(ev, et), et, false, true};
|
|
release(obj.val);
|
|
return {false, {}, out};
|
|
}
|
|
|
|
Value_ Codegen::arrayLit(const ArrayLitExpr &a, const FType *want) {
|
|
Pos p = a.pos;
|
|
FType et;
|
|
if (want && want->kind == FType::Array) {
|
|
et = *want->elem;
|
|
} else if (a.elems.empty()) {
|
|
failAt(p.file, p.line, p.col, "an empty [] needs a type, like: []int nums = []");
|
|
} else {
|
|
// numbers written in the code: if any has a dot, they are all floats
|
|
bool allNum = true, anyFloat = false;
|
|
for (auto &x : a.elems) {
|
|
const Expr *y = x.get();
|
|
if (y->kind == ExprKind::Unary && static_cast<const UnaryExpr *>(y)->op == '-') y = static_cast<const UnaryExpr *>(y)->operand.get();
|
|
if (y->kind == ExprKind::Float) anyFloat = true;
|
|
else if (y->kind != ExprKind::Int) allNum = false;
|
|
}
|
|
et = FType::Void;
|
|
if (allNum && anyFloat) et = FType::F64;
|
|
}
|
|
std::vector<Value *> vals;
|
|
Held held(*this);
|
|
for (size_t k = 0; k < a.elems.size(); k++) {
|
|
Value_ v = et.kind == FType::Void ? exprWant(*a.elems[k], et) : exprWant(*a.elems[k], et);
|
|
if (et.kind == FType::Void) {
|
|
if (v.type.kind == FType::Void || v.type.kind == FType::Null)
|
|
failAt(a.elems[k]->pos.file, a.elems[k]->pos.line, a.elems[k]->pos.col, "an array element needs a value with a type");
|
|
et = v.type;
|
|
}
|
|
v = coerce(v, et, a.elems[k]->pos, "every element of this array");
|
|
vals.push_back(own(v));
|
|
hold({vals.back(), et, false, true});
|
|
}
|
|
FType at = FType::arrayOf(et);
|
|
Value *n = b.getInt64(vals.size());
|
|
Value *data = vals.empty() ? (Value *)ConstantPointerNull::get(b.getPtrTy())
|
|
: b.CreateCall(rt("finch_alloc"), {b.CreateMul(n, elemSize(et))});
|
|
for (size_t k = 0; k < vals.size(); k++) b.CreateStore(vals[k], b.CreateInBoundsGEP(ty(et), data, b.getInt64(k)));
|
|
Value *arr = UndefValue::get(ty(at));
|
|
arr = b.CreateInsertValue(arr, data, 0);
|
|
arr = b.CreateInsertValue(arr, n, 1);
|
|
arr = b.CreateInsertValue(arr, n, 2);
|
|
return {arr, at, false, true};
|
|
}
|
|
|
|
// ---------- operators ----------
|
|
|
|
static const char *opName(BinOp op) {
|
|
switch (op) {
|
|
case BinOp::Add: return "+";
|
|
case BinOp::Sub: return "-";
|
|
case BinOp::Mul: return "*";
|
|
case BinOp::Div: return "/";
|
|
case BinOp::Mod: return "%";
|
|
case BinOp::BitAnd: return "&";
|
|
case BinOp::BitOr: return "|";
|
|
case BinOp::BitXor: return "^";
|
|
case BinOp::Shl: return "<<";
|
|
case BinOp::Shr: return ">>";
|
|
case BinOp::Eq: return "==";
|
|
case BinOp::NotEq: return "!=";
|
|
case BinOp::Less: return "<";
|
|
case BinOp::LessEq: return "<=";
|
|
case BinOp::Greater: return ">";
|
|
case BinOp::GreaterEq: return ">=";
|
|
case BinOp::And: return "&&";
|
|
case BinOp::Or: return "||";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
void Codegen::badOperands(BinOp op, const Value_ &l, const Value_ &r, Pos p) {
|
|
std::string msg = std::string("can't use '") + opName(op) + "' on " + l.type.show() +
|
|
(l.type == r.type ? "" : " and " + r.type.show());
|
|
if (l.type.isInt() && r.type.isInt() && l.type != r.type) msg += " (convert one side, like " + l.type.show() + "(...))";
|
|
if (l.type.kind == FType::Bool && (op == BinOp::BitAnd || op == BinOp::BitOr))
|
|
msg += std::string(" (for true/false use '") + (op == BinOp::BitAnd ? "&&" : "||") + "')";
|
|
else if ((l.type.isFloat() || r.type.isFloat()) && op >= BinOp::BitAnd && op <= BinOp::Shr)
|
|
msg += " (bit operators work on whole numbers only)";
|
|
else if (op == BinOp::Add && (l.type.kind == FType::Str || r.type.kind == FType::Str))
|
|
msg += " (turn the other side into text first: str(...))";
|
|
else if ((l.type.kind == FType::Struct || l.type.kind == FType::Array) && (op == BinOp::Eq || op == BinOp::NotEq))
|
|
msg += " (compare the parts one by one)";
|
|
failAt(p.file, p.line, p.col, msg);
|
|
}
|
|
|
|
// Bring both sides to one type: a number literal adapts to the other side,
|
|
// otherwise the smaller type grows into the bigger one.
|
|
bool Codegen::unify(Value_ &l, Value_ &r) {
|
|
if (l.type == r.type) return true;
|
|
if (l.type.isInt() && r.type.isInt()) {
|
|
auto *lc = l.literal ? dyn_cast<ConstantInt>(l.v) : nullptr;
|
|
auto *rc = r.literal ? dyn_cast<ConstantInt>(r.v) : nullptr;
|
|
if (lc && !rc && fits(lc, l.type, r.type)) { l = {intCast(l.v, l.type, r.type), r.type, true}; return true; }
|
|
if (rc && !lc && fits(rc, r.type, l.type)) { r = {intCast(r.v, r.type, l.type), l.type, true}; return true; }
|
|
if (widens(l.type, r.type)) { l = {intCast(l.v, l.type, r.type), r.type}; return true; }
|
|
if (widens(r.type, l.type)) { r = {intCast(r.v, r.type, l.type), l.type}; return true; }
|
|
return false;
|
|
}
|
|
if (l.type.isInt() && r.type.isFloat()) { l = {intToFloat(l.v, l.type, r.type), r.type, l.literal}; return true; }
|
|
if (l.type.isFloat() && r.type.isInt()) { r = {intToFloat(r.v, r.type, l.type), l.type, r.literal}; return true; }
|
|
if (l.type.isFloat() && r.type.isFloat()) {
|
|
if (l.type.kind == FType::F32) l = {b.CreateFPExt(l.v, b.getDoubleTy()), FType::F64};
|
|
if (r.type.kind == FType::F32) r = {b.CreateFPExt(r.v, b.getDoubleTy()), FType::F64};
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Value_ Codegen::arith(BinOp op, Value_ l, Value_ r, Pos p) {
|
|
Value_ v = arithOp(op, l, r, p);
|
|
v.literal = l.literal && r.literal && isa<Constant>(v.v);
|
|
return v;
|
|
}
|
|
|
|
Value_ Codegen::arithOp(BinOp op, Value_ l, Value_ r, Pos p) {
|
|
if (op == BinOp::Add && l.type.kind == FType::Str && r.type.kind == FType::Str) {
|
|
Value *out = tmp(Constant::getNullValue(ty(FType::Str)));
|
|
b.CreateCall(rt("finch_str_concat"), {out, tmp(l.v), tmp(r.v)});
|
|
release(l);
|
|
release(r);
|
|
return {b.CreateLoad(ty(FType::Str), out), FType::Str, false, true};
|
|
}
|
|
if (!unify(l, r)) badOperands(op, l, r, p);
|
|
if (l.type.isInt()) {
|
|
bool s = l.type.isSigned();
|
|
switch (op) {
|
|
case BinOp::Add: return {b.CreateAdd(l.v, r.v), l.type};
|
|
case BinOp::Sub: return {b.CreateSub(l.v, r.v), l.type};
|
|
case BinOp::Mul: return {b.CreateMul(l.v, r.v), l.type};
|
|
case BinOp::Div:
|
|
checkDivisor(l, r, p);
|
|
return {s ? b.CreateSDiv(l.v, r.v) : b.CreateUDiv(l.v, r.v), l.type};
|
|
case BinOp::Mod:
|
|
checkDivisor(l, r, p);
|
|
return {s ? b.CreateSRem(l.v, r.v) : b.CreateURem(l.v, r.v), l.type};
|
|
case BinOp::BitAnd: return {b.CreateAnd(l.v, r.v), l.type};
|
|
case BinOp::BitOr: return {b.CreateOr(l.v, r.v), l.type};
|
|
case BinOp::BitXor: return {b.CreateXor(l.v, r.v), l.type};
|
|
case BinOp::Shl: return {b.CreateShl(l.v, r.v), l.type};
|
|
case BinOp::Shr: return {s ? b.CreateAShr(l.v, r.v) : b.CreateLShr(l.v, r.v), l.type};
|
|
default: break;
|
|
}
|
|
}
|
|
if (l.type.isFloat()) {
|
|
switch (op) {
|
|
case BinOp::Add: return {b.CreateFAdd(l.v, r.v), l.type};
|
|
case BinOp::Sub: return {b.CreateFSub(l.v, r.v), l.type};
|
|
case BinOp::Mul: return {b.CreateFMul(l.v, r.v), l.type};
|
|
case BinOp::Div: return {b.CreateFDiv(l.v, r.v), l.type};
|
|
case BinOp::Mod: return {b.CreateFRem(l.v, r.v), l.type};
|
|
default: break;
|
|
}
|
|
}
|
|
badOperands(op, l, r, p);
|
|
}
|
|
|
|
Value_ Codegen::compare(BinOp op, Value_ l, Value_ r, Pos p) {
|
|
bool equality = op == BinOp::Eq || op == BinOp::NotEq;
|
|
auto ptrLike = [](const FType &t) { return t.isPtr() || t.kind == FType::Null; };
|
|
|
|
if (ptrLike(l.type) || ptrLike(r.type)) { // pointers compare addresses
|
|
if (!equality || !ptrLike(l.type) || !ptrLike(r.type)) {
|
|
if (l.type.kind == FType::Str || r.type.kind == FType::Str)
|
|
failAt(p.file, p.line, p.col, "a str is never null; to check for empty text use s.len == 0");
|
|
badOperands(op, l, r, p);
|
|
}
|
|
return {op == BinOp::Eq ? b.CreateICmpEQ(l.v, r.v) : b.CreateICmpNE(l.v, r.v), FType::Bool};
|
|
}
|
|
|
|
if (l.type.kind == FType::Str && r.type.kind == FType::Str) {
|
|
Value *res;
|
|
if (equality) {
|
|
Value *eq = b.CreateCall(rt("finch_str_eq"), {tmp(l.v), tmp(r.v)});
|
|
res = op == BinOp::Eq ? b.CreateICmpNE(eq, b.getInt32(0)) : b.CreateICmpEQ(eq, b.getInt32(0));
|
|
} else {
|
|
Value *c = b.CreateCall(rt("finch_str_cmp"), {tmp(l.v), tmp(r.v)});
|
|
CmpInst::Predicate pr = op == BinOp::Less ? CmpInst::ICMP_SLT : op == BinOp::LessEq ? CmpInst::ICMP_SLE
|
|
: op == BinOp::Greater ? CmpInst::ICMP_SGT : CmpInst::ICMP_SGE;
|
|
res = b.CreateICmp(pr, c, b.getInt64(0));
|
|
}
|
|
release(l);
|
|
release(r);
|
|
return {res, FType::Bool};
|
|
}
|
|
|
|
if (!unify(l, r)) badOperands(op, l, r, p);
|
|
if (l.type.isFloat()) {
|
|
CmpInst::Predicate pr;
|
|
switch (op) {
|
|
case BinOp::Eq: pr = CmpInst::FCMP_OEQ; break;
|
|
case BinOp::NotEq: pr = CmpInst::FCMP_UNE; break;
|
|
case BinOp::Less: pr = CmpInst::FCMP_OLT; break;
|
|
case BinOp::LessEq: pr = CmpInst::FCMP_OLE; break;
|
|
case BinOp::Greater: pr = CmpInst::FCMP_OGT; break;
|
|
default: pr = CmpInst::FCMP_OGE; break;
|
|
}
|
|
return {b.CreateFCmp(pr, l.v, r.v), FType::Bool};
|
|
}
|
|
if (!l.type.isInt() && l.type.kind != FType::Char && l.type.kind != FType::Bool) badOperands(op, l, r, p);
|
|
if (l.type.kind == FType::Bool && !equality) badOperands(op, l, r, p);
|
|
bool s = l.type.isSigned();
|
|
CmpInst::Predicate pr;
|
|
switch (op) {
|
|
case BinOp::Eq: pr = CmpInst::ICMP_EQ; break;
|
|
case BinOp::NotEq: pr = CmpInst::ICMP_NE; break;
|
|
case BinOp::Less: pr = s ? CmpInst::ICMP_SLT : CmpInst::ICMP_ULT; break;
|
|
case BinOp::LessEq: pr = s ? CmpInst::ICMP_SLE : CmpInst::ICMP_ULE; break;
|
|
case BinOp::Greater: pr = s ? CmpInst::ICMP_SGT : CmpInst::ICMP_UGT; break;
|
|
default: pr = s ? CmpInst::ICMP_SGE : CmpInst::ICMP_UGE; break;
|
|
}
|
|
return {b.CreateICmp(pr, l.v, r.v), FType::Bool};
|
|
}
|
|
|
|
// && and || only evaluate the right side when needed.
|
|
Value_ Codegen::logic(const BinaryExpr &e) {
|
|
bool isAnd = e.op == BinOp::And;
|
|
Value *l = condition(*e.lhs);
|
|
BasicBlock *from = b.GetInsertBlock();
|
|
BasicBlock *rhsBB = newBlock(isAnd ? "and.rhs" : "or.rhs");
|
|
BasicBlock *end = newBlock(isAnd ? "and.end" : "or.end");
|
|
if (isAnd) b.CreateCondBr(l, rhsBB, end);
|
|
else b.CreateCondBr(l, end, rhsBB);
|
|
|
|
b.SetInsertPoint(rhsBB);
|
|
Value *r = condition(*e.rhs);
|
|
BasicBlock *rhsEnd = b.GetInsertBlock();
|
|
b.CreateBr(end);
|
|
|
|
b.SetInsertPoint(end);
|
|
PHINode *phi = b.CreatePHI(b.getInt1Ty(), 2);
|
|
phi->addIncoming(b.getInt1(!isAnd), from);
|
|
phi->addIncoming(r, rhsEnd);
|
|
return {phi, FType::Bool};
|
|
}
|
|
|
|
Value_ Codegen::binary(const BinaryExpr &e) {
|
|
switch (e.op) {
|
|
case BinOp::And:
|
|
case BinOp::Or: return logic(e);
|
|
case BinOp::Eq:
|
|
case BinOp::NotEq:
|
|
case BinOp::Less:
|
|
case BinOp::LessEq:
|
|
case BinOp::Greater:
|
|
case BinOp::GreaterEq: {
|
|
Value_ l = expr(*e.lhs);
|
|
Held held(*this);
|
|
hold(l);
|
|
Value_ r = expr(*e.rhs);
|
|
return compare(e.op, l, r, e.pos);
|
|
}
|
|
default: {
|
|
Value_ l = expr(*e.lhs);
|
|
Held held(*this);
|
|
hold(l);
|
|
Value_ r = expr(*e.rhs);
|
|
return arith(e.op, l, r, e.pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------- language server index ----------
|
|
|
|
void Codegen::note(Pos at, size_t len, const std::string &hover, Pos def) {
|
|
if (!g_index || at.line <= 0) return;
|
|
SymRef r{at.file, at.line, at.col, (int)len, hover};
|
|
if (def.line > 0) {
|
|
r.defFile = def.file;
|
|
r.defLine = def.line;
|
|
r.defCol = def.col;
|
|
}
|
|
g_index->refs.push_back(r);
|
|
}
|
|
|
|
std::string Codegen::signature(const Fn &fn) {
|
|
std::string owner = fn.recv ? fn.recv->name + "." : fn.module.empty() ? "" : fn.module + ".";
|
|
std::string s = "fn " + owner + fn.decl->name + "(";
|
|
for (size_t i = 0; i < fn.params.size(); i++) s += (i ? ", " : "") + fn.params[i].show() + " " + fn.decl->params[i].name;
|
|
s += ")";
|
|
if (fn.ret.kind != FType::Void) s += " -> " + fn.ret.show() + (fn.fallible ? "!" : "");
|
|
else if (fn.fallible) s += " -> !";
|
|
return s;
|
|
}
|
|
|
|
std::string Codegen::structHover(StructInfo *s) {
|
|
std::string h = "```finch\nstruct " + s->name + " {\n";
|
|
for (auto &f : s->fields) h += " " + f.type.show() + " " + f.name + "\n";
|
|
return h + "}\n```" + (s->isC ? "\nfrom C" : "");
|
|
}
|
|
|
|
// ---------- debug info ----------
|
|
|
|
void Codegen::setLoc(Pos p) {
|
|
if (!di || !diFn) return;
|
|
b.SetCurrentDebugLocation(DILocation::get(ctx, p.line, p.col, diFn));
|
|
}
|
|
|
|
} // namespace finch
|
|
|
|
std::unique_ptr<llvm::Module> generate(const std::vector<Program> &progs, const CImports &c, llvm::LLVMContext &ctx,
|
|
llvm::TargetMachine &tm, bool debug) {
|
|
return finch::Codegen(ctx, progs, c, tm, debug).run();
|
|
}
|