// 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" #include "target.h" using namespace llvm; namespace finch { Codegen::Codegen(LLVMContext &c, const std::vector &ps, const CImports &ci, TargetMachine &tm, bool dbg) : ctx(c), b(c), mod(std::make_unique("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(*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 Codegen::run() { declareStructs(); declareFns(); if (g_target.shared) { if (modules[""].fns.count("main")) { const Pos &pos = modules[""].fns["main"].decl->namePos; failAt(pos.file, pos.line, pos.col, "a library (--shared) has no main: the program that loads it calls its 'export fn's"); } } else 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); if (!g_target.shared) defineMainWrapper(); for (const auto &[module, name] : exportNames) defineExport(modules[module].fns[name]); if (g_target.shared) { // finch_module_on_fatal(handler): the host's say on runtime errors (see finch_rt.c) Function *w = Function::Create(FunctionType::get(b.getVoidTy(), {b.getPtrTy()}, false), Function::ExternalLinkage, "finch_module_on_fatal", mod.get()); if (g_target.windows) w->setDLLStorageClass(GlobalValue::DLLExportStorageClass); b.SetInsertPoint(BasicBlock::Create(ctx, "entry", w)); b.SetCurrentDebugLocation(DebugLoc()); b.CreateCall(rt("finch_on_fatal"), {w->getArg(0)}); b.CreateRetVoid(); } 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(); 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(); 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 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 (fn.exported) { if (recv) failAt(pos.file, pos.line, pos.col, "methods can't be exported (export a plain fn that calls it)"); if (f.fallible) failAt(pos.file, pos.line, pos.col, "an export fn can't fail (C has no errors as values): handle them inside it"); auto cType = [](const FType &t) { return t.isNumber() || t.kind == FType::Bool || t.kind == FType::Char || t.kind == FType::Ptr; }; for (size_t i = 0; i < f.params.size(); i++) if (!cType(f.params[i])) failAt(fn.params[i].pos.file, fn.params[i].pos.line, fn.params[i].pos.col, "an export fn takes numbers, bool, char and pointers only (this is " + f.params[i].show() + "; pass text as ptr[char] and make it a str inside)"); if (f.ret.kind != FType::Void && !cType(f.ret)) failAt(pos.file, pos.line, pos.col, "an export fn returns nothing, a number, bool, char or a pointer (not " + f.ret.show() + ")"); for (const auto &[mname, other] : modules) if (const auto it = other.fns.find(fn.name); it != other.fns.end() && it->second.decl->exported) failAt(pos.file, pos.line, pos.col, "'" + fn.name + "' is exported twice (from two modules)"); } if (recv) methods[recv][fn.name] = f; else m.fns[fn.name] = f; if (fn.exported) exportNames.push_back({p.module, fn.name}); } // export fn name(...): a C function called `name` that calls the Finch one (bool is a C byte). void Codegen::defineExport(Fn &fn) { auto cTy = [&](const FType &t) -> llvm::Type * { return t.kind == FType::Bool ? b.getInt8Ty() : ty(t); }; std::vector params; for (const FType &t : fn.params) params.push_back(cTy(t)); llvm::Type *ret = fn.ret.kind == FType::Void ? b.getVoidTy() : cTy(fn.ret); Function *w = Function::Create(FunctionType::get(ret, params, false), Function::ExternalLinkage, fn.decl->name, mod.get()); if (g_target.windows) w->setDLLStorageClass(GlobalValue::DLLExportStorageClass); b.SetInsertPoint(BasicBlock::Create(ctx, "entry", w)); b.SetCurrentDebugLocation(DebugLoc()); std::vector args; for (size_t i = 0; i < fn.params.size(); i++) { Value *a = w->getArg(unsigned(i)); args.push_back(fn.params[i].kind == FType::Bool ? b.CreateICmpNE(a, b.getInt8(0)) : a); } Value *r = b.CreateCall(fn.llvm, args); if (fn.ret.kind == FType::Void) b.CreateRetVoid(); else b.CreateRet(fn.ret.kind == FType::Bool ? b.CreateZExt(r, b.getInt8Ty()) : r); } // 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 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 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(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(s)); break; case StmtKind::VarDecl: varDecl(static_cast(s)); break; case StmtKind::Assign: assign(static_cast(s)); break; case StmtKind::Expr: { const Expr &x = *static_cast(s).expr; release(x.kind == ExprKind::OrElse ? orElse(static_cast(x), true) : expr(x)); break; } case StmtKind::If: ifStmt(static_cast(s)); break; case StmtKind::While: whileStmt(static_cast(s)); break; case StmtKind::For: forStmt(static_cast(s)); break; case StmtKind::ForEach: forEachStmt(static_cast(s)); break; case StmtKind::Return: returnStmt(static_cast(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(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(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(*s.value).callee == "error") { auto &c = static_cast(*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(*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(e).name; case ExprKind::Member: return rootVar(*static_cast(e).obj); case ExprKind::Index: return rootVar(*static_cast(e).obj); default: return ""; } } bool Codegen::mutatesExpr(const std::string &name, const Expr &e) { static const std::set changing = {"push", "pop", "insert", "remove", "clear", "resize", "sort", "reverse"}; switch (e.kind) { case ExprKind::Try: return mutatesExpr(name, *static_cast(e).call); case ExprKind::OrElse: { auto &x = static_cast(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(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(e).operand); case ExprKind::Binary: { auto &x = static_cast(e); return mutatesExpr(name, *x.lhs) || mutatesExpr(name, *x.rhs); } case ExprKind::Call: { auto &c = static_cast(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(e).obj); case ExprKind::Index: { auto &x = static_cast(e); return mutatesExpr(name, *x.obj) || mutatesExpr(name, *x.index); } case ExprKind::ArrayLit: for (auto &a : static_cast(e).elems) if (mutatesExpr(name, *a)) return true; return false; case ExprKind::Method: { auto &m = static_cast(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(s).body) if (mutates(name, *st)) return true; return false; case StmtKind::VarDecl: return e(static_cast(s).init); case StmtKind::Assign: { auto &a = static_cast(s); return rootVar(*a.target) == name || e(a.target) || e(a.value); } case StmtKind::Expr: return e(static_cast(s).expr); case StmtKind::If: { auto &x = static_cast(s); return e(x.cond) || mutates(name, *x.then) || (x.otherwise && mutates(name, *x.otherwise)); } case StmtKind::While: { auto &x = static_cast(s); return e(x.cond) || mutates(name, *x.body); } case StmtKind::For: { auto &x = static_cast(s); return e(x.from) || e(x.to) || mutates(name, *x.body); } case StmtKind::ForEach: { auto &x = static_cast(s); return e(x.list) || mutates(name, *x.body); } case StmtKind::Return: return e(static_cast(s).value); case StmtKind::Defer: return mutates(name, *static_cast(s).body); default: return false; } } // ---------- expressions ---------- Value_ Codegen::exprWant(const Expr &e, const FType &want) { if (e.kind == ExprKind::ArrayLit) return arrayLit(static_cast(e), &want); if (e.kind == ExprKind::MapLit) return mapLit(static_cast(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 / ... 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 )"); 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(e).value), FType::I64, true}; case ExprKind::Float: return {ConstantFP::get(b.getDoubleTy(), static_cast(e).value), FType::F64, true}; case ExprKind::Bool: return {b.getInt1(static_cast(e).value), FType::Bool}; case ExprKind::Char: return {b.getInt8(static_cast(e).value), FType::Char}; case ExprKind::Str: return {strConst(static_cast(e).value), FType::Str, false, true}; case ExprKind::Null: return {ConstantPointerNull::get(b.getPtrTy()), FType::Null}; case ExprKind::Var: { auto &v = static_cast(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(e)); case ExprKind::Binary: return binary(static_cast(e)); case ExprKind::Call: return call(static_cast(e)); case ExprKind::Method: return method(static_cast(e)); case ExprKind::ArrayLit: return arrayLit(static_cast(e), nullptr); case ExprKind::MapLit: return mapLit(static_cast(e), nullptr); case ExprKind::Try: return tryExpr(static_cast(e)); case ExprKind::OrElse: return orElse(static_cast(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(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(e), forWrite); case ExprKind::Index: return index(static_cast(e), forWrite); default: return {false, {}, expr(e)}; } } Place Codegen::place(const Expr &e, bool forWrite) { if (e.kind == ExprKind::Var) { auto &v = static_cast(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(*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(pv) && !isa(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(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(y)->op == '-') y = static_cast(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 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(l.v) : nullptr; auto *rc = r.literal ? dyn_cast(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(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 generate(const std::vector &progs, const CImports &c, llvm::LLVMContext &ctx, llvm::TargetMachine &tm, bool debug) { return finch::Codegen(ctx, progs, c, tm, debug).run(); }