/* -*- C++ -*- * Serene Programming Language * * Copyright (c) 2019-2021 Sameer Rahmani * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "serene/jit.h" #include "serene/namespace.h" #include "serene/reader/location.h" #include "serene/reader/reader.h" #include "serene/reader/semantics.h" #include "serene/serene.h" #include "serene/slir/generatable.h" #include "serene/slir/slir.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace serene; namespace cl = llvm::cl; namespace { enum Action { None, DumpAST, DumpSLIR, DumpMLIR, DumpSemantic, DumpLIR, DumpIR, CompileToObject, Compile, // TODO: Remove this option and replace it by a subcommand RunJIT, }; } static std::string banner = llvm::formatv("\n\nSerene Compiler Version {0}" "\nCopyright (C) 2019-2021 " "Sameer Rahmani \n" "Serene comes with ABSOLUTELY NO WARRANTY;\n" "This is free software, and you are welcome\n" "to redistribute it under certain conditions; \n" "for details take a look at the LICENSE file.\n", SERENE_VERSION); static cl::opt inputNS(cl::Positional, cl::desc(""), cl::Required); static cl::opt outputFile( "o", cl::desc("The relative path to the output file from the build dir"), cl::init("-"), cl::value_desc("filename")); static cl::opt outputDir("b", cl::desc("The absolute path to the build directory"), cl::value_desc("filename"), cl::Required); static cl::opt emitAction( "emit", cl::desc("Select what to dump."), cl::init(Compile), cl::values(clEnumValN(DumpSemantic, "semantic", "Output the AST after one level of analysis only")), cl::values(clEnumValN(DumpIR, "ir", "Output the lowered IR only")), cl::values(clEnumValN(DumpSLIR, "slir", "Output the SLIR only")), cl::values(clEnumValN(DumpMLIR, "mlir", "Output the MLIR only (Lowered SLIR)")), cl::values(clEnumValN(DumpLIR, "lir", "Output the LIR only (Lowerd to LLVM dialect)")), cl::values(clEnumValN(DumpAST, "ast", "Output the AST only")), cl::values(clEnumValN(CompileToObject, "object", "Compile to object file.")), cl::values(clEnumValN(Compile, "target", "Compile to target code. (Default)")), cl::values(clEnumValN(RunJIT, "jit", "Run the give input file with the JIT.")) ); int dumpAsObject(Namespace &ns) { // TODO: Move the compilation process to the Namespace class auto maybeModule = ns.compileToLLVM(); // TODO: Fix this call to raise the wrapped error instead if (!maybeModule) { // TODO: Rais and error: "Faild to generato LLVM IR for namespace" return -1; } auto module = std::move(maybeModule.getValue()); auto &ctx = ns.getContext(); // TODO: We need to set the triple data layout and everything to that sort in // one place. We want them for the JIT as well and also we're kinda // duplicating what we're doing in `Namespace#compileToLLVM`. module->setTargetTriple(ctx.targetTriple); std::string Error; auto target = llvm::TargetRegistry::lookupTarget(ctx.targetTriple, Error); // Print an error and exit if we couldn't find the requested target. // This generally occurs if we've forgotten to initialise the // TargetRegistry or we have a bogus target triple. if (!target) { llvm::errs() << Error; return 1; } auto cpu = "generic"; auto features = ""; llvm::TargetOptions opt; auto rm = llvm::Optional(); auto targetMachinePtr = target->createTargetMachine(ctx.targetTriple, cpu, features, opt, rm); auto targetMachine = std::unique_ptr(targetMachinePtr); module->setDataLayout(targetMachine->createDataLayout()); auto filename = strcmp(outputFile.c_str(), "-") == 0 ? "output" : outputFile.c_str(); std::error_code ec; llvm::SmallString<256> destFile(outputDir); llvm::sys::path::append(destFile, filename); auto destObjFilePath = llvm::formatv("{0}.o", destFile).str(); llvm::raw_fd_ostream dest(destObjFilePath, ec, llvm::sys::fs::OF_None); if (ec) { llvm::errs() << "Could not open file: " << ec.message(); return 1; } llvm::legacy::PassManager pass; auto fileType = llvm::CGFT_ObjectFile; if (targetMachine->addPassesToEmitFile(pass, dest, nullptr, fileType)) { llvm::errs() << "TheTargetMachine can't emit a file of this type"; return 1; } pass.run(*module); dest.flush(); if (emitAction == Action::Compile) { llvm::IntrusiveRefCntPtr opts = new clang::DiagnosticOptions; clang::DiagnosticsEngine diags( new clang::DiagnosticIDs, opts, new clang::TextDiagnosticPrinter(llvm::errs(), opts.get())); clang::driver::Driver d("clang", ctx.targetTriple, diags, "Serene compiler"); std::vector args = {"serenec"}; args.push_back(destObjFilePath.c_str()); args.push_back("-o"); args.push_back(destFile.c_str()); d.setCheckInputsExist(false); std::unique_ptr compilation; compilation.reset(d.BuildCompilation(args)); if (!compilation) { return 1; } llvm::SmallVector> failCommand; // compilation->ExecuteJobs(compilation->getJobs(), failCommand); d.ExecuteCompilation(*compilation, failCommand); if (failCommand.empty()) { llvm::outs() << "Done!\n"; } else { llvm::errs() << "Linking failed!\n"; } } return 0; }; int main(int argc, char *argv[]) { initCompiler(); registerSereneCLOptions(); cl::ParseCommandLineOptions(argc, argv, banner); auto ctx = makeSereneContext(); auto userNS = makeNamespace(*ctx, "user", llvm::None); applySereneCLOptions(*ctx); // TODO: handle the outputDir by not forcing it. it should be // default to the current working dir if (outputDir == "-") { llvm::errs() << "Error: The build directory is not set. Did you forget to " "use '-build-dir'?\n"; return 1; } switch (emitAction) { case Action::RunJIT: { // TODO: Replace it by a proper jit configuration ctx->setOperationPhase(CompilationPhase::NoOptimization); break; }; // Just print out the raw AST case Action::DumpAST: { ctx->setOperationPhase(CompilationPhase::Parse); break; }; case Action::DumpSemantic: { ctx->setOperationPhase(CompilationPhase::Analysis); break; }; case Action::DumpSLIR: { ctx->setOperationPhase(CompilationPhase::SLIR); break; } case Action::DumpMLIR: { ctx->setOperationPhase(CompilationPhase::MLIR); break; } case Action::DumpLIR: { ctx->setOperationPhase(CompilationPhase::LIR); break; } case Action::DumpIR: { ctx->setOperationPhase(CompilationPhase::IR); break; } case Action::CompileToObject: { ctx->setOperationPhase(CompilationPhase::NoOptimization); break; } case Action::Compile: { ctx->setOperationPhase(CompilationPhase::NoOptimization); break; } default: { llvm::errs() << "No action specified. TODO: Print out help here\n"; return 1; } } auto runLoc = reader::LocationRange::UnknownLocation(inputNS); auto ns = ctx->sourceManager.readNamespace(*ctx, inputNS, runLoc); if (!ns) { return (int)std::errc::no_such_file_or_directory; } ctx->insertNS(ns); switch (emitAction) { case Action::DumpAST: case Action::DumpSemantic: { auto ast = ns->getTree(); llvm::outs() << exprs::astToString(&ast) << "\n"; return 0; } case Action::DumpSLIR: case Action::DumpMLIR: case Action::DumpLIR: { ns->dump(); break; }; case Action::DumpIR: { auto maybeModule = ns->compileToLLVM(); if (!maybeModule) { llvm::errs() << "Failed to generate the IR.\n"; return 1; } maybeModule.getValue()->dump(); break; }; case Action::RunJIT: { auto maybeJIT = JIT::make(*ns.get()); if (!maybeJIT) { // TODO: panic in here: "Couldn't creat the JIT!" return -1; } auto jit = std::move(maybeJIT.getValue()); if (jit->invoke("main")) { llvm::errs() << "Faild to invoke the 'main' function.\n"; return 1; } llvm::outs() << "Done!"; break; }; case Action::Compile: case Action::CompileToObject: { return dumpAsObject(*ns); }; default: { llvm::errs() << "Action is not supported yet!\n"; }; } return 0; }