diff --git a/compiler/cmake/version.cmake b/compiler/cmake/version.cmake new file mode 100644 index 00000000..dd0faac8 --- /dev/null +++ b/compiler/cmake/version.cmake @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.22) + +if(NOT DEFINED VERSION_MAJOR) + set(VERSION_MAJOR 1) +endif() +if(NOT DEFINED VERSION_MINOR) + set(VERSION_MINOR 0) +endif() +if(NOT DEFINED VERSION_SUFFIX) + find_package(Git QUIET) + if(GIT_FOUND) + execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short=7 HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE REVPARSE_RESULT + OUTPUT_VARIABLE REVPARSE_OUTPUT + ERROR_QUIET + ) + if(REVPARSE_RESULT EQUAL 0) + string(STRIP "${REVPARSE_OUTPUT}" REVPARSE_OUTPUT) + set(VERSION_SUFFIX "-${REVPARSE_OUTPUT}") + endif() + else() + set(VERSION_SUFFIX "") + message(WARNING "Unable to detect version suffix from Git") + endif() +endif() diff --git a/compiler/include/compiler/cli/compiler.hpp b/compiler/include/compiler/cli/compiler.hpp index 11ca3716..943a35e3 100644 --- a/compiler/include/compiler/cli/compiler.hpp +++ b/compiler/include/compiler/cli/compiler.hpp @@ -1,11 +1,54 @@ #pragma once +#include +#include + +#include "compiler/ast/syntax_tree.hpp" +#include "compiler/frontend/lexer/token.hpp" +#include "compiler/optree/program.hpp" +#include "compiler/utils/source_files.hpp" + +#include "compiler/cli/options.hpp" + +namespace cli { + class Compiler { + const Options &opt; + std::unordered_map measuredTimes; + + utils::SourceFile source; + lexer::TokenList tokens; + ast::SyntaxTree tree; + optree::Program program; + + int readFiles(); + int runPreprocessor(); + int runLexer(); + int runParser(); + int runConverter(); + + int runAstSemantizer(); + int runAstOptimizer(); +#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR + int runAstLLVMIRGenerator(); +#endif + + int runOptreeOptimizer(); +#ifdef ENABLE_CODEGEN_OPTREE_TO_LLVMIR + int runOptreeLLVMIRGenerator(); +#endif + public: - Compiler() = delete; + Compiler(const Options &options); Compiler(const Compiler &) = delete; Compiler(Compiler &&) = delete; - ~Compiler() = delete; + ~Compiler() = default; - static int exec(int argc, char *argv[]); + const auto &measurements() const { + return measuredTimes; + } + + int run(); }; + +} // namespace cli diff --git a/compiler/include/compiler/cli/helpers.hpp b/compiler/include/compiler/cli/helpers.hpp new file mode 100644 index 00000000..fde33ece --- /dev/null +++ b/compiler/include/compiler/cli/helpers.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +namespace cli { + +class TemporaryDirectory { + std::filesystem::path dir; + + public: + TemporaryDirectory(); + TemporaryDirectory(const TemporaryDirectory &) = delete; + TemporaryDirectory(TemporaryDirectory &&) = delete; + ~TemporaryDirectory(); + + const std::filesystem::path &path() const; +}; + +std::string wrapQuotes(const std::string &str); + +std::string makeCommand(const std::vector &args); + +} // namespace cli diff --git a/compiler/include/compiler/cli/options.hpp b/compiler/include/compiler/cli/options.hpp new file mode 100644 index 00000000..de70f888 --- /dev/null +++ b/compiler/include/compiler/cli/options.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include +#include + +#if defined(ENABLE_CODEGEN_AST_TO_LLVMIR) || defined(ENABLE_CODEGEN_OPTREE_TO_LLVMIR) +#define LLVMIR_CODEGEN_ENABLED +#endif + +namespace cli { + +namespace arg { + +constexpr std::string_view help = "--help"; +constexpr std::string_view debug = "--debug"; +constexpr std::string_view optimize = "--optimize"; +constexpr std::string_view time = "--time"; +constexpr std::string_view stopAfter = "--stop-after"; +constexpr std::string_view path = "--path"; +constexpr std::string_view files = "FILES"; + +#ifdef LLVMIR_CODEGEN_ENABLED +constexpr std::string_view compile = "--compile"; +constexpr std::string_view clang = "--clang"; +constexpr std::string_view llc = "--llc"; +constexpr std::string_view output = "--output"; +#endif + +} // namespace arg + +namespace stage { + +constexpr std::string_view preprocessor = "preprocessor"; +constexpr std::string_view lexer = "lexer"; +constexpr std::string_view parser = "parser"; +constexpr std::string_view converter = "converter"; +constexpr std::string_view semantizer = "semantizer"; +constexpr std::string_view optimizer = "optimizer"; + +#ifdef LLVMIR_CODEGEN_ENABLED +constexpr std::string_view codegen = "codegen"; +#endif + +} // namespace stage + +namespace compilation_path { + +constexpr std::string_view ast = "ast"; +constexpr std::string_view optree = "optree"; + +} // namespace compilation_path + +struct Options { + bool help; + bool debug; + std::string path; + bool time; + std::optional stopAfter; + std::optional optimize; +#ifdef LLVMIR_CODEGEN_ENABLED + bool compile; + std::string clang; + std::string llc; + std::string output; +#endif + std::vector files; + std::string helpMessage; +}; + +class OptionsError : public std::exception { + std::string message; + + public: + OptionsError(const std::string &message) : message(message){}; + ~OptionsError() = default; + + OptionsError &operator=(const OptionsError &) noexcept = default; + + const char *what() const noexcept override { + return message.c_str(); + } +}; + +Options parseArguments(int argc, const char *const argv[]); + +} // namespace cli diff --git a/compiler/include/compiler/cli/version.hpp.in b/compiler/include/compiler/cli/version.hpp.in new file mode 100644 index 00000000..6c11daac --- /dev/null +++ b/compiler/include/compiler/cli/version.hpp.in @@ -0,0 +1,10 @@ +#pragma once + +namespace cli { + +constexpr const char *version = "@VERSION_MAJOR@.@VERSION_MINOR@@VERSION_SUFFIX@"; +constexpr const char *versionMajor = "@VERSION_MAJOR@"; +constexpr const char *versionMinor = "@VERSION_MINOR@"; +constexpr const char *versionSuffix = "@VERSION_SUFFIX@"; + +} // namespace cli diff --git a/compiler/lib/cli/CMakeLists.txt b/compiler/lib/cli/CMakeLists.txt index 5d48e031..f7c8f9a9 100644 --- a/compiler/lib/cli/CMakeLists.txt +++ b/compiler/lib/cli/CMakeLists.txt @@ -6,17 +6,21 @@ set_target_include_dir(${TARGET_NAME}) file(GLOB_RECURSE TARGET_HEADERS ${TARGET_INCLUDE_DIR}/*.hpp) file(GLOB_RECURSE TARGET_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +include(${COMPILER_CMAKE_DIR}/version.cmake) +configure_file(${TARGET_INCLUDE_DIR}/version.hpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.hpp @ONLY) + add_executable(${TARGET_NAME} ${TARGET_SRC} ${TARGET_HEADERS}) set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME compiler) target_include_directories(${TARGET_NAME} PUBLIC ${COMPILER_INCLUDE_DIR} - PRIVATE ${TARGET_INCLUDE_DIR} + PRIVATE ${TARGET_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ) target_link_libraries(${TARGET_NAME} PUBLIC argparse backend_ast + backend_optree frontend utils ) diff --git a/compiler/lib/cli/compiler.cpp b/compiler/lib/cli/compiler.cpp index fe917b00..c4b01ae3 100644 --- a/compiler/lib/cli/compiler.cpp +++ b/compiler/lib/cli/compiler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #ifdef ENABLE_CODEGEN_AST_TO_LLVMIR @@ -15,10 +16,11 @@ #include #endif -#include - #include "compiler/backend/ast/optimizer/optimizer.hpp" #include "compiler/backend/ast/semantizer/semantizer.hpp" +#include "compiler/backend/optree/optimizer/optimizer.hpp" +#include "compiler/backend/optree/optimizer/transform_factories.hpp" +#include "compiler/frontend/converter/converter.hpp" #include "compiler/frontend/lexer/lexer.hpp" #include "compiler/frontend/parser/parser.hpp" #include "compiler/frontend/preprocessor/preprocessor.hpp" @@ -28,292 +30,325 @@ #ifdef ENABLE_CODEGEN_AST_TO_LLVMIR #include "compiler/codegen/ast_to_llvmir/ir_generator.hpp" #endif - -#include "dumping.hpp" -#include "logger.hpp" - -using lexer::Lexer; -using optimizer::Optimizer; -using parser::Parser; -using preprocessor::Preprocessor; -using semantizer::Semantizer; -using utils::SourceFile; -using utils::Timer; - -#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR -using ir_generator::IRGenerator; +#ifdef ENABLE_CODEGEN_OPTREE_TO_LLVMIR +#include "compiler/codegen/optree_to_llvmir/llvmir_generator.hpp" #endif -namespace arg { - -constexpr std::string_view help = "--help"; -constexpr std::string_view verbose = "--verbose"; -constexpr std::string_view log = "--log"; -constexpr std::string_view optimize = "--optimize"; -constexpr std::string_view time = "--time"; -constexpr std::string_view stopAfter = "--stop-after"; -constexpr std::string_view files = "FILES"; - -#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR -constexpr std::string_view compile = "--compile"; -constexpr std::string_view clang = "--clang"; -constexpr std::string_view llc = "--llc"; -constexpr std::string_view output = "--output"; -#endif - -} // namespace arg +#include "dumping.hpp" +#include "helpers.hpp" +#include "options.hpp" -namespace stage { +#define RETURN_IF_NONZERO(EXPR) \ + do { \ + if (auto ret = (EXPR)) \ + return ret; \ + } while (0) -constexpr std::string_view preprocessor = "preprocessor"; -constexpr std::string_view lexer = "lexer"; -constexpr std::string_view parser = "parser"; -constexpr std::string_view semantizer = "semantizer"; -constexpr std::string_view optimizer = "optimizer"; +#define RETURN_IF_STOPAFTER(OPTIONS, STAGE) \ + do { \ + if ((OPTIONS).stopAfter == (STAGE)) \ + return 0; \ + } while (0) -#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR -constexpr std::string_view codegen = "codegen"; -#endif +using utils::SourceFile; +using utils::Timer; -} // namespace stage +using namespace cli; namespace { -#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR -std::filesystem::path createTemporaryDirectory() { - auto tempDir = std::filesystem::temp_directory_path(); - std::random_device dev; - std::mt19937 gen(dev()); - std::uniform_int_distribution dist(0); - std::filesystem::path path; - for (int i = 0; i < 1000; i++) { - std::stringstream dirName; - dirName << std::hex << dist(gen); - path = tempDir / dirName.str(); - if (std::filesystem::create_directory(path)) - return path; - } - throw std::runtime_error("could not find non-existing directory"); -} - -std::string wrapQuotes(const std::string &str) { - if (str.front() == '"' && str.back() == '"') - return str; - return "\"" + str + "\""; -} - -std::string generateLlcCommand(const std::string &llcBin, const std::filesystem::path &llFile, - const std::filesystem::path &objFile) { - std::stringstream cmd; - cmd << wrapQuotes(llcBin) << " -filetype=obj " << wrapQuotes(llFile.string()) << " -o " - << wrapQuotes(objFile.string()); - return cmd.str(); +#ifdef LLVMIR_CODEGEN_ENABLED +std::string llToObj(const std::string &llcBin, const std::filesystem::path &llFile, + const std::filesystem::path &objFile) { + std::vector cmd = {llcBin, "-filetype=obj", llFile.string(), "-o", objFile.string()}; + return makeCommand(cmd); } -std::string generateClangCommand(const std::string &clangBin, const std::filesystem::path &objFile, - const std::filesystem::path &exeFile) { - std::stringstream cmd; - cmd << wrapQuotes(clangBin) << ' ' << wrapQuotes(objFile.string()) << " -o " << wrapQuotes(exeFile.string()); - return cmd.str(); +std::string objToExe(const std::string &clangBin, const std::filesystem::path &objFile, + const std::filesystem::path &exeFile) { + std::vector cmd = {clangBin, objFile.string(), "-o", exeFile.string()}; + return makeCommand(cmd); } #endif } // namespace -int Compiler::exec(int argc, char *argv[]) { - argparse::ArgumentParser program("compiler", "1.0", argparse::default_arguments::none); - program.add_argument("-h", arg::help).help("show help message and exit").default_value(false).implicit_value(true); - program.add_argument("-v", arg::verbose).help("print info messages").default_value(false).implicit_value(true); - program.add_argument("-l", arg::log).help("log file (stages output will be saved if provided)"); - program.add_argument("-O", arg::optimize).help("run optimization pass").default_value(false).implicit_value(true); -#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR - program.add_argument("-c", arg::compile) - .help("produce an executable instead of LLVM IR code") - .default_value(false) - .implicit_value(true); - program.add_argument(arg::clang) - .help("path to clang executable (required if --compile argument is set)") - .default_value("clang"); - program.add_argument(arg::llc) - .help("path to llc executable (required if --compile argument is set)") - .default_value("llc"); - program.add_argument("-o", arg::output).help("output file"); -#endif - program.add_argument(arg::time) - .help("print execution times of each stage") - .default_value(false) - .implicit_value(true); - program.add_argument(arg::stopAfter) - .help("stop processing after specific stage") - .choices(stage::preprocessor, stage::lexer, stage::parser, stage::semantizer, stage::optimizer); - program.add_argument(arg::files) - .help("source files (separated by spaces)") - .required() - .nargs(argparse::nargs_pattern::at_least_one); - - try { - program.parse_args(argc, argv); - } catch (const std::runtime_error &err) { - std::cerr << err.what() << "\n"; - std::cerr << program; - return 1; - } - - if (program.get(arg::help)) { - std::cout << program; - return 0; - } - - Logger logger; - bool verbose = program.get(arg::verbose); - if (verbose) - logger.setStdoutEnabled(true); - if (program.is_used(arg::log)) - logger.setOutputFile(program.get(arg::log)); +Compiler::Compiler(const Options &options) : opt(options) { +} - std::vector files; - try { - files = program.get>(arg::files); - logger << files.size() << " file(s) provided:\n"; - for (auto &file : files) - logger << " " << file << "\n"; - } catch (std::logic_error &e) { +int Compiler::readFiles() { + if (opt.files.empty()) { std::cerr << "No files provided\n"; return 2; } - - SourceFile source; + if (opt.debug) { + std::cerr << opt.files.size() << " file(s) provided:\n"; + for (const auto &file : opt.files) + std::cerr << " " << file << "\n"; + } try { - for (const std::string &path : files) { + for (const std::string &path : opt.files) { SourceFile other = utils::readFile(path); source.insert(source.end(), std::make_move_iterator(other.begin()), std::make_move_iterator(other.end())); - logger << "Read file " << path << "\n"; + if (opt.debug) + std::cerr << "Read file " << path << "\n"; } } catch (std::exception &e) { - logger << e.what(); + std::cerr << e.what(); return 3; } + return 0; +} - std::string stopAfter; - if (program.is_used(arg::stopAfter)) - stopAfter = program.get(arg::stopAfter); - bool times = program.get(arg::time); +int Compiler::runPreprocessor() { Timer timer; - std::vector measuredTimes; - - ast::SyntaxTree tree; - try { - logger << "\nPREPROCESSOR:\n"; timer.start(); - source = Preprocessor::process(source); + source = preprocessor::Preprocessor::process(source); timer.stop(); - measuredTimes.push_back(timer.elapsed()); - if (times) - logger << "PREPROCESSOR Elapsed time: " << measuredTimes.back() << "\n"; - logger << dumping::dump(source) << "\n"; - if (stopAfter == stage::preprocessor) - return 0; + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; + } + if (opt.debug) { + std::cerr << "PREPROCESSOR:\n"; + dumping::dump(source); + } + measuredTimes[stage::preprocessor] = timer.elapsed(); + return 0; +} - logger << "\nLEXER:\n"; +int Compiler::runLexer() { + Timer timer; + try { timer.start(); - auto tokens = Lexer::process(source); + tokens = lexer::Lexer::process(source); timer.stop(); - measuredTimes.push_back(timer.elapsed()); - if (times) - logger << "LEXER Elapsed time: " << measuredTimes.back() << "\n"; - logger << dumping::dump(tokens) << "\n"; - if (stopAfter == stage::lexer) - return 0; + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; + } + if (opt.debug) { + std::cerr << "LEXER:\n"; + dumping::dump(tokens); + } + measuredTimes[stage::lexer] = timer.elapsed(); + return 0; +} - logger << "PARSER:\n"; +int Compiler::runParser() { + Timer timer; + try { timer.start(); - tree = Parser::process(tokens); + tree = parser::Parser::process(tokens); timer.stop(); - measuredTimes.push_back(timer.elapsed()); - if (times) - logger << "PARSER Elapsed time: " << measuredTimes.back() << "\n"; - logger << tree.dump(); - if (stopAfter == stage::parser) - return 0; + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; + } + if (opt.debug) { + std::cerr << "PARSER:\n"; + tree.dump(std::cerr); + } + tokens.clear(); + measuredTimes[stage::parser] = timer.elapsed(); + return 0; +} - logger << "SEMANTIZER:\n"; +int Compiler::runConverter() { + Timer timer; + try { timer.start(); - Semantizer::process(tree); + program.root = converter::Converter::process(tree).root; timer.stop(); - measuredTimes.push_back(timer.elapsed()); - if (times) - logger << "SEMANTIZER Elapsed time: " << measuredTimes.back() << "\n"; - logger << tree.dump(); - if (stopAfter == stage::semantizer) - return 0; + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; + } + if (opt.debug) { + std::cerr << "CONVERTER:\n"; + program.root->dump(std::cerr); + } + measuredTimes[stage::converter] = timer.elapsed(); + return 0; +} - if (program.get(arg::optimize)) { - logger << "OPTIMIZER:\n"; - timer.start(); - Optimizer::process(tree); - timer.stop(); - measuredTimes.push_back(timer.elapsed()); - if (times) - logger << "OPTIMIZER Elapsed time: " << measuredTimes.back() << "\n"; - logger << tree.dump(); - } +int Compiler::runAstSemantizer() { + Timer timer; + try { + timer.start(); + semantizer::Semantizer::process(tree); + timer.stop(); + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; + } + if (opt.debug) { + std::cerr << "SEMANTIZER:\n"; + tree.dump(std::cerr); + } + measuredTimes[stage::semantizer] = timer.elapsed(); + return 0; +} - if (stopAfter == stage::optimizer) - return 0; - if (times) - logger << "Compile time: " << std::accumulate(measuredTimes.begin(), measuredTimes.end(), 0LL) << "\n"; - } catch (ErrorBuffer &errors) { +int Compiler::runAstOptimizer() { + Timer timer; + try { + timer.start(); + optimizer::Optimizer::process(tree); + timer.stop(); + } catch (const ErrorBuffer &errors) { std::cerr << errors.message(); return 3; } + if (opt.debug) { + std::cerr << "OPTIMIZER:\n"; + tree.dump(std::cerr); + } + measuredTimes[stage::optimizer] = timer.elapsed(); + return 0; +} #ifdef ENABLE_CODEGEN_AST_TO_LLVMIR - logger << "\nIR GENERATOR:\n"; - IRGenerator generator("module"); +int Compiler::runAstLLVMIRGenerator() { + Timer timer; + ir_generator::IRGenerator generator(opt.files.front()); + timer.start(); generator.process(tree); - if (verbose) { - generator.dump(); - } + timer.stop(); - if (!program.is_used(arg::output)) + if (opt.debug) { + std::cerr << "LLVMIR GENERATOR:\n"; + std::cerr << generator.dump(); + } + if (!opt.compile) { + generator.writeToFile(opt.output); return 0; + } - bool compile = program.get(arg::compile); - std::string output = program.get(arg::output); + TemporaryDirectory tempDir; + try { + auto llFile = tempDir.path() / "out.ll"; + generator.writeToFile(llFile.string()); + auto objFile = tempDir.path() / "out.obj"; + auto exeFile = tempDir.path() / "out.exe"; + auto llcCmd = llToObj(opt.llc, llFile, objFile); + auto clangCmd = objToExe(opt.clang, objFile, exeFile); + if (opt.debug) { + std::cerr << "Executing commands:\n" + << " " << llcCmd << "\n" + << " " << clangCmd << "\n"; + } + bool cmdFailed = (std::system(llcCmd.c_str()) || std::system(clangCmd.c_str())); + if (cmdFailed) + return 3; + else + std::filesystem::copy_file(exeFile, opt.output); + } catch (std::exception &e) { + std::cerr << e.what(); + return 3; + } + measuredTimes[stage::codegen] = timer.elapsed(); + return 0; +} +#endif - if (!compile) { - generator.writeToFile(output); - return 0; +int Compiler::runOptreeOptimizer() { + using namespace optree::optimizer; + Timer timer; + try { + Optimizer optimizer; + optimizer.add(createEraseUnusedOps()); + optimizer.add(createFoldConstants()); + optimizer.add(createEraseUnusedFunctions()); + timer.start(); + optimizer.process(program); + timer.stop(); + } catch (const ErrorBuffer &errors) { + std::cerr << errors.message(); + return 3; } + if (opt.debug) { + std::cerr << "OPTIMIZER:\n"; + program.root->dump(std::cerr); + } + measuredTimes[stage::optimizer] = timer.elapsed(); + return 0; +} - std::string llcBin = program.is_used(arg::llc) ? program.get(arg::llc) : "llc"; - std::string clangBin = program.is_used(arg::clang) ? program.get(arg::clang) : "clang"; +#ifdef ENABLE_CODEGEN_OPTREE_TO_LLVMIR +int Compiler::runOptreeLLVMIRGenerator() { + Timer timer; + optree::llvmir_generator::LLVMIRGenerator generator(opt.files.front()); + timer.start(); + generator.process(program); + timer.stop(); + + if (opt.debug) { + std::cerr << "LLVMIR GENERATOR:\n"; + std::cerr << generator.dump(); + } + if (!opt.compile) { + generator.dumpToFile(opt.output); + return 0; + } + TemporaryDirectory tempDir; try { - std::filesystem::path tempDir = createTemporaryDirectory(); - const auto llFile = tempDir / "out.ll"; - generator.writeToFile(llFile.string()); - const auto objFile = tempDir / "out.obj"; - const auto exeFile = tempDir / "out.exe"; - std::string llcCmd = generateLlcCommand(llcBin, llFile, objFile); - std::string clangCmd = generateClangCommand(clangBin, objFile, exeFile); - logger << "Executing commands:\n" - << " " << llcCmd << "\n" - << " " << clangCmd << "\n"; + auto llFile = tempDir.path() / "out.ll"; + generator.dumpToFile(llFile.string()); + auto objFile = tempDir.path() / "out.obj"; + auto exeFile = tempDir.path() / "out.exe"; + auto llcCmd = llToObj(opt.llc, llFile, objFile); + auto clangCmd = objToExe(opt.clang, objFile, exeFile); + if (opt.debug) { + std::cerr << "Executing commands:\n" + << " " << llcCmd << "\n" + << " " << clangCmd << "\n"; + } bool cmdFailed = (std::system(llcCmd.c_str()) || std::system(clangCmd.c_str())); - if (!cmdFailed) - std::filesystem::copy_file(exeFile, output); - std::filesystem::remove_all(tempDir); if (cmdFailed) return 3; + else + std::filesystem::copy_file(exeFile, opt.output); } catch (std::exception &e) { std::cerr << e.what(); return 3; } - if (stopAfter == stage::codegen) - return 0; + measuredTimes[stage::codegen] = timer.elapsed(); + return 0; +} #endif +int Compiler::run() { + RETURN_IF_NONZERO(readFiles()); + RETURN_IF_NONZERO(runPreprocessor()); + RETURN_IF_STOPAFTER(opt, stage::preprocessor); + RETURN_IF_NONZERO(runLexer()); + RETURN_IF_STOPAFTER(opt, stage::lexer); + RETURN_IF_NONZERO(runParser()); + RETURN_IF_STOPAFTER(opt, stage::parser); + if (opt.path == compilation_path::ast) { + RETURN_IF_NONZERO(runAstSemantizer()); + RETURN_IF_STOPAFTER(opt, stage::semantizer); + RETURN_IF_NONZERO(runAstOptimizer()); + RETURN_IF_STOPAFTER(opt, stage::optimizer); +#ifdef ENABLE_CODEGEN_AST_TO_LLVMIR + RETURN_IF_NONZERO(runAstLLVMIRGenerator()); + RETURN_IF_STOPAFTER(opt, stage::codegen); +#endif + } else if (opt.path == compilation_path::optree) { + RETURN_IF_NONZERO(runConverter()); + RETURN_IF_STOPAFTER(opt, stage::converter); + RETURN_IF_NONZERO(runOptreeOptimizer()); + RETURN_IF_STOPAFTER(opt, stage::optimizer); +#ifdef ENABLE_CODEGEN_OPTREE_TO_LLVMIR + RETURN_IF_NONZERO(runOptreeLLVMIRGenerator()); + RETURN_IF_STOPAFTER(opt, stage::codegen); +#endif + } else { + std::cerr << "Unknown compilation path: " << opt.path << '\n'; + return 127; + } return 0; } diff --git a/compiler/lib/cli/helpers.cpp b/compiler/lib/cli/helpers.cpp new file mode 100644 index 00000000..cc1e27f6 --- /dev/null +++ b/compiler/lib/cli/helpers.cpp @@ -0,0 +1,37 @@ +#include "helpers.hpp" + +#include +#include +#include + +namespace cli { + +TemporaryDirectory::TemporaryDirectory() { + char tmpnamResult[L_tmpnam] = {0}; + tmpnam_s(tmpnamResult); + dir = tmpnamResult; + std::filesystem::create_directory(dir); +} + +TemporaryDirectory::~TemporaryDirectory() { + std::filesystem::remove_all(dir); +} + +const std::filesystem::path &TemporaryDirectory::path() const { + return dir; +} + +std::string wrapQuotes(const std::string &str) { + if (str.front() == '"' && str.back() == '"') + return str; + return "\"" + str + "\""; +} + +std::string makeCommand(const std::vector &args) { + std::stringstream cmd; + for (const auto &arg : args) + cmd << wrapQuotes(arg) << ' '; + return cmd.str().c_str(); +} + +} // namespace cli diff --git a/compiler/lib/cli/main.cpp b/compiler/lib/cli/main.cpp index 6e8f58c3..95168634 100644 --- a/compiler/lib/cli/main.cpp +++ b/compiler/lib/cli/main.cpp @@ -1,5 +1,31 @@ #include "compiler.hpp" +#include "options.hpp" + +using namespace cli; int main(int argc, char *argv[]) { - return Compiler::exec(argc, argv); + Options opt; + + try { + opt = std::move(parseArguments(argc, argv)); + } catch (const OptionsError &err) { + std::cerr << err.what() << "\n"; + return 1; + } + if (opt.help) { + std::cerr << opt.helpMessage; + return 0; + } + + Compiler compiler(opt); + int ret = compiler.run(); + if (ret) + return ret; + + if (opt.time) { + for (const auto &[stage, elapsed] : compiler.measurements()) + std::cerr << stage << " Elapsed time: " << elapsed << " ms\n"; + } + + return 0; } diff --git a/compiler/lib/cli/options.cpp b/compiler/lib/cli/options.cpp new file mode 100644 index 00000000..9dc91c04 --- /dev/null +++ b/compiler/lib/cli/options.cpp @@ -0,0 +1,64 @@ +#include "options.hpp" + +#include + +#include "version.hpp" + +namespace cli { + +Options parseArguments(int argc, const char *const argv[]) { + argparse::ArgumentParser program("compiler", version, argparse::default_arguments::none); + program.add_argument("-h", arg::help).help("show help message and exit").flag(); + program.add_argument(arg::debug).help("print debug info").flag(); + program.add_argument(arg::path) + .help("compilation path") + .choices(compilation_path::ast, compilation_path::optree) + .default_value(compilation_path::optree); + program.add_argument(arg::time).help("print execution times of each stage").flag(); + program.add_argument(arg::stopAfter) + .help("stop processing after specific stage") + .choices(stage::preprocessor, stage::lexer, stage::parser, stage::converter, stage::semantizer, stage::optimizer +#ifdef LLVMIR_CODEGEN_ENABLED + , + stage::codegen +#endif + ); + program.add_argument("-O", arg::optimize).help("perform optimizations").nargs(argparse::nargs_pattern::optional); +#ifdef LLVMIR_CODEGEN_ENABLED + program.add_argument("-c", arg::compile).help("produce an executable instead of LLVM IR code").flag(); + program.add_argument(arg::clang).help("path to clang executable").default_value("clang"); + program.add_argument(arg::llc).help("path to llc executable").default_value("llc"); + program.add_argument("-o", arg::output).help("output file"); +#endif + program.add_argument(arg::files) + .help("source files (separated by spaces)") + .required() + .nargs(argparse::nargs_pattern::at_least_one); + + try { + program.parse_args(argc, argv); + } catch (const std::runtime_error &err) { + throw OptionsError(err.what()); + } + + Options options; + options.help = program.get(arg::help); + options.debug = program.get(arg::debug); + options.path = program.get(arg::path); + options.time = program.get(arg::time); + if (program.is_used(arg::stopAfter)) + options.stopAfter = program.get(arg::stopAfter); + if (program.is_used(arg::optimize)) + options.optimize = program.get(arg::optimize); +#ifdef LLVMIR_CODEGEN_ENABLED + options.compile = program.get(arg::compile); + options.clang = program.get(arg::clang); + options.llc = program.get(arg::llc); + options.output = program.get(arg::output); +#endif + options.files = program.get>(arg::files); + options.helpMessage = program.help().str(); + return options; +} + +} // namespace cli