TCResult ast::IfStmt::typecheck(sst::TypecheckState* fs, fir::Type* infer) { fs->pushLoc(this); defer(fs->popLoc()); using Case = sst::IfStmt::Case; auto ret = util::pool<sst::IfStmt>(this->loc); fs->pushAnonymousTree(); defer(fs->popTree()); for(auto c : this->cases) { auto inits = util::map(c.inits, [fs](Stmt* s) -> auto { return s->typecheck(fs).stmt(); }); auto cs = Case(c.cond->typecheck(fs).expr(), dcast(sst::Block, c.body->typecheck(fs).stmt()), inits); if(!cs.cond->type->isBoolType() && !cs.cond->type->isPointerType()) error(cs.cond, "non-boolean expression with type '%s' cannot be used as a conditional", cs.cond->type); ret->cases.push_back(cs); iceAssert(ret->cases.back().body); } if(this->elseCase) { ret->elseCase = dcast(sst::Block, this->elseCase->typecheck(fs).stmt()); iceAssert(ret->elseCase); } return TCResult(ret); }
static void checkTuple(cgn::CodegenState* cs, const DecompMapping& bind, CGResult rhs) { iceAssert(!bind.array); auto rt = rhs.value->getType(); iceAssert(rt->isTupleType()); auto tty = rt->toTupleType(); iceAssert(bind.inner.size() == tty->getElementCount()); for(size_t i = 0; i < tty->getElementCount(); i++) { CGResult v; if(rhs->islorclvalue()) { auto gep = cs->irb.StructGEP(rhs.value, i); v = CGResult(gep); } else { v = CGResult(cs->irb.ExtractValue(rhs.value, { i })); } cs->generateDecompositionBindings(bind.inner[i], v, true); } }
TCResult ast::EnumDefn::typecheck(sst::TypecheckState* fs, fir::Type* infer, const TypeParamMap_t& gmaps) { fs->pushLoc(this); defer(fs->popLoc()); auto tcr = this->generateDeclaration(fs, infer, gmaps); if(tcr.isParametric()) return tcr; else if(tcr.isError()) error(this, "failed to generate declaration for enum '%s'", this->name); auto defn = dcast(sst::EnumDefn, tcr.defn()); iceAssert(defn); auto oldscope = fs->getCurrentScope(); fs->teleportToScope(defn->id.scope); fs->pushTree(defn->id.name); if(this->memberType) defn->memberType = fs->convertParserTypeToFIR(this->memberType); else defn->memberType = fir::Type::getInt64(); auto ety = fir::EnumType::get(defn->id, defn->memberType); size_t index = 0; for(auto cs : this->cases) { sst::Expr* val = 0; if(cs.value) { iceAssert(defn->memberType); val = cs.value->typecheck(fs, defn->memberType).expr(); if(val->type != defn->memberType) error(cs.value, "mismatched type in enum case value; expected type '%s', but found type '%s'", defn->memberType, val->type); } auto ecd = util::pool<sst::EnumCaseDefn>(cs.loc); ecd->id = Identifier(cs.name, IdKind::Name); ecd->id.scope = fs->getCurrentScope(); ecd->type = ety; ecd->parentEnum = defn; ecd->val = val; ecd->index = index++; defn->cases[cs.name] = ecd; fs->stree->addDefinition(cs.name, ecd); } defn->type = ety; fs->popTree(); fs->teleportToScope(oldscope); return TCResult(defn); }
void OverloadError::post() { // first, post the original error. this->top->wordsBeforeContext = "(call site)"; this->top->post(); // sort the candidates by line number // (idk maybe it's a windows thing but it feels like the order changes every so often) auto cds = std::vector<std::pair<Locatable*, ErrorMsg*>>(this->cands.begin(), this->cands.end()); std::sort(cds.begin(), cds.end(), [](auto a, auto b) -> bool { return a.first->loc.line < b.first->loc.line; }); // go through each candidate. int cand_counter = 1; for(auto [ loc, emg ] : cds) { if(emg->kind != ErrKind::Span) { emg->post(); } else { auto spe = dcast(SpanError, emg); iceAssert(spe); spe->top = SimpleError::make(MsgType::Note, loc->loc, "candidate %d was defined here:", cand_counter++); spe->highlightActual = false; spe->post(); } } for(auto sub : this->subs) sub->post(); }
void CodegenInstance::addGlobalConstructor(std::string name, llvm::Function* constructor) { llvm::GlobalVariable* gv = this->module->getGlobalVariable(name); iceAssert(gv); this->globalConstructors[gv] = constructor; }
void IRBlock::eraseFromParentFunction() { iceAssert(this->parentFunction && "no function"); std::vector<IRBlock*>& blist = this->parentFunction->getBlockList(); for(auto it = blist.begin(); it != blist.end(); it++) { if(*it == this) { blist.erase(it); return; } } iceAssert(0 && "not in function"); }
sst::DefinitionTree* typecheckFiles(CollectorState* state) { // typecheck for(const auto& file : state->allFiles) { // note that we're guaranteed (because that's the whole point) // that any module we encounter here will have had all of its dependencies checked already std::vector<std::pair<ImportThing, sst::StateTree*>> imports; for(auto d : state->graph->getDependenciesOf(file)) { auto imported = d->to; auto stree = state->dtrees[imported->name]->base; iceAssert(stree); ImportThing ithing { imported->name, d->ithing.importAs, d->ithing.pubImport, d->ithing.loc }; if(auto it = std::find_if(imports.begin(), imports.end(), [&ithing](auto x) -> bool { return x.first.name == ithing.name; }); it != imports.end()) { SimpleError::make(ithing.loc, "importing previously imported module '%s'", ithing.name) ->append(SimpleError::make(MsgType::Note, it->first.loc, "previous import was here:")) ->postAndQuit(); } imports.push_back({ ithing, stree }); // debuglog("%s depends on %s\n", frontend::getFilenameFromPath(file).c_str(), frontend::getFilenameFromPath(d->name).c_str()); } state->dtrees[file] = sst::typecheck(state, state->parsed[file], imports); } return state->dtrees[state->fullMainFile]; }
static void runProgramWithJit(llvm::Module* mod) { // all linked already. // dump here, before the output. llvm::verifyModule(*mod, &llvm::errs()); if(mod->getFunction("main") != 0) { std::string err; llvm::ExecutionEngine* ee = llvm::EngineBuilder(std::unique_ptr<llvm::Module>(mod)) .setErrorStr(&err) .setMCJITMemoryManager(llvm::make_unique<llvm::SectionMemoryManager>()) .create(); void* func = ee->getPointerToFunction(mod->getFunction("main")); iceAssert(func); auto mainfunc = (int (*)(int, const char**)) func; const char* m[] = { ("__llvmJIT_" + mod->getModuleIdentifier()).c_str() }; // finalise the object, which causes the memory to be executable // f*****g NX bit ee->finalizeObject(); mainfunc(1, m); } else { error("no main() function, cannot JIT"); } }
void GlobalVariable::setInitialValue(ConstantValue* constVal) { if(constVal && constVal->getType() != this->getType()->getPointerElementType()) error("storing value of '%s' in global var of type '%s'", constVal->getType(), this->getType()); iceAssert((!constVal || constVal->getType() == this->getType()->getPointerElementType()) && "invalid type"); this->initValue = constVal; }
fir::Module* generateFIRModule(CollectorState* state, sst::DefinitionTree* maintree) { iceAssert(maintree && maintree->topLevel); for(const auto& dt : state->dtrees) { for(const auto& def : dt.second->typeDefnMap) { if(auto it = maintree->typeDefnMap.find(def.first); it != maintree->typeDefnMap.end()) iceAssert(it->second == def.second); maintree->typeDefnMap[def.first] = def.second; } } return cgn::codegen(maintree); }
Result_t enumerationAccessCodegen(CodegenInstance* cgi, Expr* lhs, Expr* rhs) { VarRef* enumName = dynamic_cast<VarRef*>(lhs); VarRef* caseName = dynamic_cast<VarRef*>(rhs); iceAssert(enumName); if(!caseName) error(rhs, "Expected identifier after enumeration access"); TypePair_t* tp = cgi->getType(enumName->name); iceAssert(tp); iceAssert(tp->second.second == TypeKind::Enum); Enumeration* enr = dynamic_cast<Enumeration*>(tp->second.first); iceAssert(enr); Result_t res(0, 0); bool found = false; for(auto p : enr->cases) { if(p.first == caseName->name) { res = p.second->codegen(cgi); found = true; break; } } if(!found) error(rhs, "Enum '%s' has no such case '%s'", enumName->name.c_str(), caseName->name.c_str()); if(!enr->isStrong) return res; // strong enum. // create a temp alloca, then use GEP to set the value, then return. llvm::Value* alloca = cgi->allocateInstanceInBlock(tp->first); llvm::Value* gep = cgi->builder.CreateStructGEP(alloca, 0); cgi->builder.CreateStore(res.result.first, gep); return Result_t(cgi->builder.CreateLoad(alloca), alloca); }
std::string getSpannedContext(const Location& loc, const std::vector<util::ESpan>& spans, size_t* adjust, size_t* num_width, size_t* margin, bool underline, bool bottompad, std::string underlineColour) { std::string ret; iceAssert(adjust && margin && num_width); iceAssert((underline == bottompad || bottompad) && "cannot underline without bottom pad"); if(!std::is_sorted(spans.begin(), spans.end(), [](auto a, auto b) -> bool { return a.loc.col < b.loc.col; })) _error_and_exit("spans must be sorted!"); *num_width = std::to_string(loc.line + 1).length(); // one spacing line *margin = *num_width + 2; ret += strprintf("%s |\n", spaces(*num_width)); ret += strprintf("%d |%s\n", loc.line + 1, fetchContextLine(loc, adjust)); if(bottompad) ret += strprintf("%s |", spaces(*num_width)); // ok, now loop through each err, and draw the underline. if(underline) { //* cursor represents the 'virtual' position -- excluding the left margin size_t cursor = 0; // columns actually start at 1 for some reason. ret += spaces(LEFT_PADDING - 1); for(auto span : spans) { // pad out. auto tmp = strprintf("%s", spaces(1 + span.loc.col - *adjust - cursor)); cursor += tmp.length(); ret += tmp + strprintf("%s", span.colour.empty() ? underlineColour : span.colour); tmp = strprintf("%s", repeat(UNDERLINE_CHARACTER, span.loc.len)); cursor += tmp.length(); ret += tmp + strprintf("%s", COLOUR_RESET); } } return ret; }
static std::string typestr(MsgType t) { switch(t) { case MsgType::Note: return "note"; case MsgType::Error: return "error"; case MsgType::Warning: return "warning"; default: iceAssert(0); return ""; } }
static void checkArray(sst::TypecheckState* fs, DecompMapping* bind, fir::Type* rhs, bool immut) { iceAssert(bind->array); if(!rhs->isArrayType() && !rhs->isDynamicArrayType() && !rhs->isArraySliceType() && !rhs->isStringType()) error(bind->loc, "expected array type in destructuring declaration; found type '%s' instead", rhs); if(rhs->isStringType()) { //* note: special-case this, because 1. we want to return chars, but 2. strings are supposed to be immutable. for(auto& b : bind->inner) checkAndAddBinding(fs, &b, fir::Type::getInt8(), immut, false); if(!bind->restName.empty()) { auto fake = util::pool<sst::VarDefn>(bind->loc); fake->id = Identifier(bind->restName, IdKind::Name); fake->immutable = immut; //* note: see typecheck/slices.cpp for mutability rules. if(bind->restRef) fake->type = fir::Type::getCharSlice(sst::getMutabilityOfSliceOfType(rhs)); else fake->type = fir::Type::getString(); fs->stree->addDefinition(bind->restName, fake); bind->restDefn = fake; } } else { for(auto& b : bind->inner) checkAndAddBinding(fs, &b, rhs->getArrayElementType(), immut, true); if(!bind->restName.empty()) { auto fake = util::pool<sst::VarDefn>(bind->loc); fake->id = Identifier(bind->restName, IdKind::Name); fake->immutable = immut; //* note: see typecheck/slices.cpp for mutability rules. if(bind->restRef || rhs->isArraySliceType()) fake->type = fir::ArraySliceType::get(rhs->getArrayElementType(), sst::getMutabilityOfSliceOfType(rhs)); else fake->type = fir::DynamicArrayType::get(rhs->getArrayElementType()); fs->stree->addDefinition(bind->restName, fake); bind->restDefn = fake; } } }
std::string typeParamMapToString(const std::string& name, const TypeParamMap_t& map) { if(map.empty()) return name; std::string ret; for(auto m : map) ret += (m.first + ":" + m.second->encodedStr()) + ","; // shouldn't be empty. iceAssert(ret.size() > 0); return strprintf("%s<%s>", name, ret.substr(0, ret.length() - 1)); }
static void checkTuple(sst::TypecheckState* fs, DecompMapping* bind, fir::Type* rhs, bool immut) { iceAssert(!bind->array); if(!rhs->isTupleType()) error(bind->loc, "expected tuple type in destructuring declaration; found type '%s' instead", rhs); auto tty = rhs->toTupleType(); if(bind->inner.size() != tty->getElementCount()) { error(bind->loc, "too %s bindings in destructuring declaration; expected %d, found %d instead", (bind->inner.size() < tty->getElementCount() ? "few" : "many"), tty->getElementCount(), bind->inner.size()); } for(size_t i = 0; i < tty->getElementCount(); i++) checkAndAddBinding(fs, &bind->inner[i], tty->getElementN(i), immut, true); }
static bool checkBlockPathsReturn(sst::TypecheckState* fs, sst::Block* block, fir::Type* retty, std::vector<sst::Block*>* faulty) { // return value is whether or not the block had a return value; // true if all paths explicitly returned, false if not // this return value is used to determine whether we need to insert a // 'return void' thing. bool ret = false; for(size_t i = 0; i < block->statements.size(); i++) { auto& s = block->statements[i]; if(auto retstmt = dcast(sst::ReturnStmt, s)) { // ok... ret = true; auto t = retstmt->expectedType; iceAssert(t); if(fir::getCastDistance(t, retty) < 0) { if(retstmt->expectedType->isVoidType()) { error(retstmt, "expected value after 'return'; function return type is '%s'", retty); } else { std::string msg; if(block->isSingleExpr) msg = "invalid single-expression with type '%s' in function returning '%s'"; else msg = "mismatched type in return statement; function returns '%s', value has type '%s'"; SpanError::make(SimpleError::make(retstmt->loc, msg.c_str(), retty, retstmt->expectedType)) ->add(util::ESpan(retstmt->value->loc, strprintf("type '%s'", retstmt->expectedType))) ->append(SimpleError::make(MsgType::Note, fs->getCurrentFunction()->loc, "function definition is here:")) ->postAndQuit(); } } if(i != block->statements.size() - 1) { SimpleError::make(block->statements[i + 1]->loc, "unreachable code after return statement") ->append(SimpleError::make(MsgType::Note, retstmt->loc, "return statement was here:")) ->postAndQuit();; doTheExit(); } } else /* if(i == block->statements.size() - 1) */ { //* it's our duty to check the internals of these things regardless of their exhaustiveness //* so that we can check for the elision of the merge block. //? eg: if 's' itself does not have an else case, but say one of its branches is exhaustive (all arms return), //? then we can elide the merge block for that branch, even though we can't for 's' itself. //* this isn't strictly necessary (the program is still correct without it), but we generate nicer IR this way. bool exhausted = false; if(auto ifstmt = dcast(sst::IfStmt, s); ifstmt) { bool all = true; for(const auto& c: ifstmt->cases) all = all && checkBlockPathsReturn(fs, c.body, retty, faulty); exhausted = all && ifstmt->elseCase && checkBlockPathsReturn(fs, ifstmt->elseCase, retty, faulty); ifstmt->elideMergeBlock = exhausted; } else if(auto whileloop = dcast(sst::WhileLoop, s); whileloop) { exhausted = checkBlockPathsReturn(fs, whileloop->body, retty, faulty) && whileloop->isDoVariant; whileloop->elideMergeBlock = exhausted; } else if(auto feloop = dcast(sst::ForeachLoop, s); feloop) { // we don't set 'exhausted' here beacuse for loops cannot be guaranteed to be exhaustive. // but we still want to check the block inside for elision. feloop->elideMergeBlock = checkBlockPathsReturn(fs, feloop->body, retty, faulty); } ret = exhausted; }
static void checkArray(cgn::CodegenState* cs, const DecompMapping& bind, CGResult rhs) { iceAssert(bind.array); auto rt = rhs.value->getType(); bool shouldSliceBeMutable = sst::getMutabilityOfSliceOfType(rt); if(!rt->isArrayType() && !rt->isDynamicArrayType() && !rt->isArraySliceType() && !rt->isStringType()) error(bind.loc, "Expected array type in destructuring declaration; found type '%s' instead", rt); if(rt->isStringType()) { // do a bounds check. auto numbinds = fir::ConstantInt::getInt64(bind.inner.size()); { auto checkf = cgn::glue::string::getBoundsCheckFunction(cs, true); iceAssert(checkf); auto strloc = fir::ConstantString::get(bind.loc.toString()); cs->irb.Call(checkf, cs->irb.GetSAALength(rhs.value), numbinds, strloc); } //* note: special-case this, because 1. we want to return chars auto strdat = cs->irb.PointerTypeCast(cs->irb.GetSAAData(rhs.value), fir::Type::getMutInt8Ptr()); { size_t idx = 0; for(auto& b : bind.inner) { auto v = CGResult(cs->irb.ReadPtr(cs->irb.PointerAdd(strdat, fir::ConstantInt::getInt64(idx)))); cs->generateDecompositionBindings(b, v, false); idx++; } } if(!bind.restName.empty()) { if(bind.restRef) { // make a slice of char. auto remaining = cs->irb.Subtract(cs->irb.GetSAALength(rhs.value), numbinds); auto slice = cs->irb.CreateValue(fir::Type::getCharSlice(shouldSliceBeMutable)); slice = cs->irb.SetArraySliceData(slice, cs->irb.PointerAdd(strdat, numbinds)); slice = cs->irb.SetArraySliceLength(slice, remaining); handleDefn(cs, bind.restDefn, CGResult(slice)); } else { // make string. // auto remaining = cs->irb.Subtract(cs->irb.GetSAALength(rhs.value), numbinds); auto clonef = cgn::glue::string::getCloneFunction(cs); iceAssert(clonef); auto string = cs->irb.Call(clonef, rhs.value, numbinds); handleDefn(cs, bind.restDefn, CGResult(string)); } } } else { auto array = rhs.value; fir::Value* arrlen = 0; auto numbinds = fir::ConstantInt::getInt64(bind.inner.size()); { //* note: 'true' means we're performing a decomposition, so print a more appropriate error message on bounds failure. auto checkf = cgn::glue::array::getBoundsCheckFunction(cs, true); iceAssert(checkf); if(rt->isArrayType()) arrlen = fir::ConstantInt::getInt64(rt->toArrayType()->getArraySize()); else if(rt->isArraySliceType()) arrlen = cs->irb.GetArraySliceLength(array); else if(rt->isDynamicArrayType()) arrlen = cs->irb.GetSAALength(array); else iceAssert(0); auto strloc = fir::ConstantString::get(bind.loc.toString()); cs->irb.Call(checkf, arrlen, numbinds, strloc); } // # if 0 if(!rhs->islorclvalue() && rt->isArrayType()) { //* because of the way LLVM is designed, and hence by extension how we are designed, //* fixed-sized arrays are kinda dumb. If we don't have a pointer to the array (for whatever reason???), //* then we can't do a GEP access, and hence can't get a pointer to use for the 'rest' binding. So, //* we error on that case but allow binding the rest. //* theoretically if the compiler is well designed we should never hit this case, but who knows? size_t idx = 0; for(auto& b : bind.inner) { auto v = CGResult(cs->irb.ExtractValue(array, { idx })); cs->generateDecompositionBindings(b, v, false); idx++; } warn(bind.loc, "Destructure of array without pointer (shouldn't happen!)"); if(!bind.restName.empty()) error(bind.loc, "Could not get pointer to array (of type '%s') to create binding for '...'", rt); } else // #endif { fir::Value* data = 0; if(rt->isArrayType()) data = cs->irb.ConstGEP2(rhs.value, 0, 0); else if(rt->isArraySliceType()) data = cs->irb.GetArraySliceData(array); else if(rt->isDynamicArrayType()) data = cs->irb.GetSAAData(array); else iceAssert(0); size_t idx = 0; for(auto& b : bind.inner) { auto ptr = cs->irb.PointerAdd(data, fir::ConstantInt::getInt64(idx)); auto v = CGResult(cs->irb.Dereference(ptr)); cs->generateDecompositionBindings(b, v, true); idx++; } if(!bind.restName.empty()) { if(bind.restRef) { auto sty = fir::ArraySliceType::get(rt->getArrayElementType(), shouldSliceBeMutable); auto remaining = cs->irb.Subtract(arrlen, numbinds); auto slice = cs->irb.CreateValue(sty); slice = cs->irb.SetArraySliceData(slice, cs->irb.PointerAdd(data, numbinds)); slice = cs->irb.SetArraySliceLength(slice, remaining); handleDefn(cs, bind.restDefn, CGResult(slice)); } else { // always return a dynamic array here. //* note: in order to make our lives somewhat easier, for fixed arrays, we create a fake slice pointing to its data, then we //* call clone on that instead. fir::Value* clonee = 0; if(rt->isArrayType()) { clonee = cs->irb.CreateValue(fir::ArraySliceType::get(rt->getArrayElementType(), shouldSliceBeMutable)); clonee = cs->irb.SetArraySliceData(clonee, data); clonee = cs->irb.SetArraySliceLength(clonee, fir::ConstantInt::getInt64(rt->toArrayType()->getArraySize())); } else { clonee = array; } auto clonef = cgn::glue::array::getCloneFunction(cs, clonee->getType()); iceAssert(clonef); auto ret = cs->irb.Call(clonef, clonee, numbinds); handleDefn(cs, bind.restDefn, CGResult(ret)); } } } } }
static void doGlobalConstructors(std::string filename, CompiledData& data, Ast::Root* root, llvm::Module* mod) { auto& rootmap = std::get<2>(data); bool needGlobalConstructor = false; if(root->globalConstructorTrampoline != 0) needGlobalConstructor = true; for(auto pair : std::get<2>(data)) { if(pair.second->globalConstructorTrampoline != 0) { needGlobalConstructor = true; break; } } if(needGlobalConstructor) { std::vector<llvm::Function*> constructors; rootmap[filename] = root; for(auto pair : rootmap) { if(pair.second->globalConstructorTrampoline != 0) { llvm::Function* constr = mod->getFunction(pair.second->globalConstructorTrampoline->getName()); if(!constr) { error("required global constructor %s was not found in the module!", pair.second->globalConstructorTrampoline->getName().c_str()); } else { constructors.push_back(constr); } } } rootmap.erase(filename); llvm::FunctionType* ft = llvm::FunctionType::get(llvm::Type::getVoidTy(llvm::getGlobalContext()), false); llvm::Function* gconstr = llvm::Function::Create(ft, llvm::GlobalValue::ExternalLinkage, "__global_constructor_top_level__", mod); llvm::IRBuilder<> builder(llvm::getGlobalContext()); llvm::BasicBlock* iblock = llvm::BasicBlock::Create(llvm::getGlobalContext(), "initialiser", gconstr); builder.SetInsertPoint(iblock); for(auto f : constructors) { iceAssert(f); builder.CreateCall(f); } builder.CreateRetVoid(); if(!Compiler::getNoAutoGlobalConstructor()) { // insert a call at the beginning of main(). llvm::Function* mainfunc = mod->getFunction("main"); iceAssert(mainfunc); llvm::BasicBlock* entry = &mainfunc->getEntryBlock(); llvm::BasicBlock* f = llvm::BasicBlock::Create(llvm::getGlobalContext(), "__main_entry", mainfunc); f->moveBefore(entry); builder.SetInsertPoint(f); builder.CreateCall(gconstr); builder.CreateBr(entry); } } }