From fa101ba70183572c485429c8feb2270724e8537a Mon Sep 17 00:00:00 2001 From: Roberto Raggi Date: Sun, 10 Aug 2025 14:57:16 +0200 Subject: [PATCH] Add -emit-llvm to CLI and emit LLVM IR --- src/frontend/cxx/frontend.cc | 210 ++++++++++++++----- src/frontend/cxx/frontend.h | 48 +---- src/mlir/cxx/mlir/CMakeLists.txt | 3 + src/mlir/cxx/mlir/cxx_dialect_conversions.cc | 15 ++ src/mlir/cxx/mlir/cxx_dialect_conversions.h | 5 + src/parser/cxx/cli.cc | 3 + src/parser/cxx/cli.h | 1 + 7 files changed, 188 insertions(+), 97 deletions(-) diff --git a/src/frontend/cxx/frontend.cc b/src/frontend/cxx/frontend.cc index 368f64b6..0c258139 100644 --- a/src/frontend/cxx/frontend.cc +++ b/src/frontend/cxx/frontend.cc @@ -42,6 +42,8 @@ #include #include #include +#include +#include #endif #include @@ -55,54 +57,123 @@ namespace cxx { -Frontend::Frontend(const CLI& cli, std::string fileName) - : cli(cli), fileName_(std::move(fileName)) { - diagnosticsClient_ = std::make_unique(); - unit_ = std::make_unique(diagnosticsClient_.get()); +struct Frontend::Private { + Frontend& frontend; + const CLI& cli; + std::string fileName_; + std::unique_ptr unit_; + std::unique_ptr diagnosticsClient_; + std::unique_ptr toolchain_; + std::vector> actions_; +#ifdef CXX_WITH_MLIR + std::unique_ptr context_; + mlir::ModuleOp module_; + std::unique_ptr llvmContext_; + std::unique_ptr llvmModule_; +#endif + bool shouldExit_ = false; + int exitStatus_ = 0; - actions_.emplace_back([this]() { showSearchPaths(std::cerr); }); - actions_.emplace_back([this]() { preprocess(); }); - actions_.emplace_back([this]() { printPreprocessedText(); }); - actions_.emplace_back([this]() { dumpMacros(std::cout); }); - actions_.emplace_back([this]() { dumpTokens(std::cout); }); - actions_.emplace_back([this]() { unit_->preprocessor()->squeeze(); }); - actions_.emplace_back([this]() { parse(); }); - actions_.emplace_back([this]() { dumpSymbols(std::cout); }); - actions_.emplace_back([this]() { dumpAst(); }); - actions_.emplace_back([this]() { printAstIfNeeded(); }); - actions_.emplace_back([this]() { serializeAst(); }); - actions_.emplace_back([this]() { emitIR(); }); + Private(Frontend& frontend, const CLI& cli, std::string fileName); + ~Private(); + + [[nodiscard]] auto needsIR() const -> bool { + return cli.opt_emit_ir || cli.opt_emit_llvm || cli.opt_S || cli.opt_c; + } + + void prepare(); + void preparePreprocessor(); + void preprocess(); + void parse(); + void showSearchPaths(std::ostream& out); + void dumpTokens(std::ostream& out); + void dumpSymbols(std::ostream& out); + void serializeAst(); + void dumpAst(); + void printAstIfNeeded(); + void generateIR(); + void emitIR(); + void emitLLVMIR(); + void printPreprocessedText(); + void dumpMacros(std::ostream& out); + + [[nodiscard]] auto readAll(const std::string& fileName, std::istream& in) + -> std::optional; + + [[nodiscard]] auto readAll(const std::string& fileName) + -> std::optional; + + void withOutputStream(const std::optional& extension, + const std::function& action); + +#ifdef CXX_WITH_MLIR + void withRawOutputStream( + const std::optional& extension, + const std::function& action); +#endif +}; + +Frontend::Frontend(const CLI& cli, std::string fileName) { + priv = std::make_unique(*this, cli, std::move(fileName)); } Frontend::~Frontend() {} auto Frontend::translationUnit() const -> TranslationUnit* { - return unit_.get(); + return priv->unit_.get(); } -auto Frontend::toolchain() const -> Toolchain* { return toolchain_.get(); } +auto Frontend::toolchain() const -> Toolchain* { + return priv->toolchain_.get(); +} -auto Frontend::fileName() const -> const std::string& { return fileName_; } +auto Frontend::fileName() const -> const std::string& { + return priv->fileName_; +} void Frontend::addAction(std::function action) { - actions_.emplace_back(std::move(action)); + priv->actions_.emplace_back(std::move(action)); } auto Frontend::operator()() -> bool { - prepare(); - preparePreprocessor(); + priv->prepare(); + priv->preparePreprocessor(); - for (const auto& action : actions_) { - if (shouldExit_) break; + for (const auto& action : priv->actions_) { + if (priv->shouldExit_) break; action(); } - diagnosticsClient_->verifyExpectedDiagnostics(); + priv->diagnosticsClient_->verifyExpectedDiagnostics(); - return !diagnosticsClient_->hasErrors(); + return !priv->diagnosticsClient_->hasErrors(); } -void Frontend::withOutputStream( +Frontend::Private::Private(Frontend& frontend, const CLI& cli, + std::string fileName) + : frontend(frontend), cli(cli), fileName_(std::move(fileName)) { + diagnosticsClient_ = std::make_unique(); + unit_ = std::make_unique(diagnosticsClient_.get()); + + actions_.emplace_back([this]() { showSearchPaths(std::cerr); }); + actions_.emplace_back([this]() { preprocess(); }); + actions_.emplace_back([this]() { printPreprocessedText(); }); + actions_.emplace_back([this]() { dumpMacros(std::cout); }); + actions_.emplace_back([this]() { dumpTokens(std::cout); }); + actions_.emplace_back([this]() { unit_->preprocessor()->squeeze(); }); + actions_.emplace_back([this]() { parse(); }); + actions_.emplace_back([this]() { dumpSymbols(std::cout); }); + actions_.emplace_back([this]() { dumpAst(); }); + actions_.emplace_back([this]() { printAstIfNeeded(); }); + actions_.emplace_back([this]() { serializeAst(); }); + actions_.emplace_back([this]() { generateIR(); }); + actions_.emplace_back([this]() { emitIR(); }); + actions_.emplace_back([this]() { emitLLVMIR(); }); +} + +Frontend::Private::~Private() {} + +void Frontend::Private::withOutputStream( const std::optional& extension, const std::function& action) { auto explicitOutput = cli.getSingle("-o"); @@ -123,7 +194,7 @@ void Frontend::withOutputStream( } #ifdef CXX_WITH_MLIR -void Frontend::withRawOutputStream( +void Frontend::Private::withRawOutputStream( const std::optional& extension, const std::function& action) { auto explicitOutput = cli.getSingle("-o"); @@ -145,7 +216,7 @@ void Frontend::withRawOutputStream( } #endif -void Frontend::printPreprocessedText() { +void Frontend::Private::printPreprocessedText() { if (!cli.opt_E && !cli.opt_Eonly) { return; } @@ -168,7 +239,7 @@ void Frontend::printPreprocessedText() { }); } -void Frontend::preprocess() { +void Frontend::Private::preprocess() { auto source = readAll(fileName_); if (!source.has_value()) { @@ -182,7 +253,7 @@ void Frontend::preprocess() { unit_->setSource(std::move(*source), fileName_); } -void Frontend::dumpMacros(std::ostream& out) { +void Frontend::Private::dumpMacros(std::ostream& out) { if (!cli.opt_E && !cli.opt_dM) return; unit_->preprocessor()->printMacros(out); @@ -190,7 +261,7 @@ void Frontend::dumpMacros(std::ostream& out) { shouldExit_ = true; } -void Frontend::prepare() { +void Frontend::Private::prepare() { auto preprocessor = unit_->preprocessor(); const auto lang = cli.getSingle("-x"); @@ -287,7 +358,7 @@ void Frontend::prepare() { unit_->control()->setMemoryLayout(toolchain_->memoryLayout()); } -void Frontend::preparePreprocessor() { +void Frontend::Private::preparePreprocessor() { auto preprocessor = unit_->preprocessor(); if (cli.opt_P) { @@ -331,7 +402,7 @@ void Frontend::preparePreprocessor() { } } -void Frontend::parse() { +void Frontend::Private::parse() { unit_->parse(ParserConfiguration{ .checkTypes = cli.opt_fcheck || unit_->language() == LanguageKind::kC, .fuzzyTemplateResolution = true, @@ -343,7 +414,7 @@ void Frontend::parse() { } } -void Frontend::dumpTokens(std::ostream& out) { +void Frontend::Private::dumpTokens(std::ostream& out) { if (!cli.opt_dump_tokens) return; auto dumpTokens = DumpTokens{cli}; @@ -352,33 +423,33 @@ void Frontend::dumpTokens(std::ostream& out) { shouldExit_ = true; } -void Frontend::dumpSymbols(std::ostream& out) { +void Frontend::Private::dumpSymbols(std::ostream& out) { if (!cli.opt_dump_symbols) return; auto globalScope = unit_->globalScope(); auto globalNamespace = globalScope->owner(); cxx::dump(out, globalNamespace); } -void Frontend::dumpAst() { +void Frontend::Private::dumpAst() { if (!cli.opt_ast_dump) return; auto printAST = ASTPrinter{unit_.get(), std::cout}; printAST(unit_->ast()); } -void Frontend::printAstIfNeeded() { +void Frontend::Private::printAstIfNeeded() { if (!cli.opt_ast_print) return; auto prettyPrinter = ASTPrettyPrinter{unit_.get(), std::cout}; prettyPrinter(unit_->ast()); } -void Frontend::serializeAst() { +void Frontend::Private::serializeAst() { if (!cli.opt_emit_ast) return; auto outputFile = fs::path{fileName_}.filename().replace_extension(".ast"); std::ofstream out(outputFile.string(), std::ios::binary); (void)unit_->serialize(out); } -void Frontend::showSearchPaths(std::ostream& out) { +void Frontend::Private::showSearchPaths(std::ostream& out) { if (!cli.opt_v) return; out << std::format("#include <...> search starts here:\n"); @@ -392,37 +463,72 @@ void Frontend::showSearchPaths(std::ostream& out) { out << std::format("End of search list.\n"); } -void Frontend::emitIR() { - if (!cli.opt_emit_ir) return; +void Frontend::Private::generateIR() { + if (cli.opt_fsyntax_only) return; + if (!needsIR()) return; #ifdef CXX_WITH_MLIR - mlir::MLIRContext context; - context.loadDialect(); + context_ = std::make_unique(); + context_->loadDialect(); - auto codegen = cxx::Codegen{context, unit_.get()}; + auto codegen = cxx::Codegen{*context_, unit_.get()}; auto ir = codegen(unit_->ast()); - if (failed(lowerToMLIR(ir.module))) { - std::cerr << "cxx: failed to lower C++ AST to MLIR" << std::endl; - shouldExit_ = true; - exitStatus_ = EXIT_FAILURE; + if (succeeded(lowerToMLIR(ir.module))) { + module_ = ir.module; return; } + std::cerr << "cxx: failed to lower C++ AST to MLIR" << std::endl; + shouldExit_ = true; + exitStatus_ = EXIT_FAILURE; +#endif +} + +void Frontend::Private::emitIR() { + if (!cli.opt_emit_ir) return; + +#ifdef CXX_WITH_MLIR + if (!module_) return; + + shouldExit_ = true; + mlir::OpPrintingFlags flags; if (cli.opt_g) { flags.enableDebugInfo(true, false); } withRawOutputStream(std::nullopt, [&](llvm::raw_ostream& out) { - ir.module->print(out, flags); + module_->print(out, flags); }); #endif } -auto Frontend::readAll(const std::string& fileName, std::istream& in) +void Frontend::Private::emitLLVMIR() { + if (!cli.opt_emit_llvm) return; + +#ifdef CXX_WITH_MLIR + if (!module_) return; + + llvmContext_ = std::make_unique(); + llvmModule_ = exportToLLVMIR(module_, *llvmContext_); + + if (!llvmModule_) { + std::cerr << "cxx: failed to lower MLIR module to LLVM IR" << std::endl; + shouldExit_ = true; + exitStatus_ = EXIT_FAILURE; + return; + } + + shouldExit_ = true; + withRawOutputStream( + ".ll", [&](llvm::raw_ostream& out) { llvmModule_->print(out, nullptr); }); +#endif +} + +auto Frontend::Private::readAll(const std::string& fileName, std::istream& in) -> std::optional { std::string code; char buffer[4 * 1024]; @@ -433,7 +539,7 @@ auto Frontend::readAll(const std::string& fileName, std::istream& in) return code; } -auto Frontend::readAll(const std::string& fileName) +auto Frontend::Private::readAll(const std::string& fileName) -> std::optional { if (fileName == "-" || fileName.empty()) return readAll("", std::cin); if (std::ifstream stream(fileName); stream) return readAll(fileName, stream); diff --git a/src/frontend/cxx/frontend.h b/src/frontend/cxx/frontend.h index 55b3fb84..fb33bba7 100644 --- a/src/frontend/cxx/frontend.h +++ b/src/frontend/cxx/frontend.h @@ -27,13 +27,8 @@ #include #include -#ifdef CXX_WITH_MLIR -#include -#endif - namespace cxx { -class VerifyDiagnosticsClient; class Toolchain; class Frontend { @@ -41,53 +36,16 @@ class Frontend { Frontend(const CLI& cli, std::string fileName); ~Frontend(); - [[nodiscard]] auto operator()() -> bool; - [[nodiscard]] auto translationUnit() const -> TranslationUnit*; [[nodiscard]] auto toolchain() const -> Toolchain*; [[nodiscard]] auto fileName() const -> const std::string&; - void addAction(std::function action); - private: - void prepare(); - void preparePreprocessor(); - void preprocess(); - void parse(); - void showSearchPaths(std::ostream& out); - void dumpTokens(std::ostream& out); - void dumpSymbols(std::ostream& out); - void serializeAst(); - void dumpAst(); - void printAstIfNeeded(); - void emitIR(); - void printPreprocessedText(); - void dumpMacros(std::ostream& out); - - void withOutputStream(const std::optional& extension, - const std::function& action); - -#ifdef CXX_WITH_MLIR - void withRawOutputStream( - const std::optional& extension, - const std::function& action); -#endif - - [[nodiscard]] auto readAll(const std::string& fileName, std::istream& in) - -> std::optional; - - [[nodiscard]] auto readAll(const std::string& fileName) - -> std::optional; + [[nodiscard]] auto operator()() -> bool; private: - const CLI& cli; - std::string fileName_; - std::unique_ptr unit_; - std::unique_ptr diagnosticsClient_; - std::unique_ptr toolchain_; - std::vector> actions_; - bool shouldExit_ = false; - int exitStatus_ = 0; + struct Private; + std::unique_ptr priv; }; } // namespace cxx \ No newline at end of file diff --git a/src/mlir/cxx/mlir/CMakeLists.txt b/src/mlir/cxx/mlir/CMakeLists.txt index f9b19b69..37a59f12 100644 --- a/src/mlir/cxx/mlir/CMakeLists.txt +++ b/src/mlir/cxx/mlir/CMakeLists.txt @@ -28,12 +28,15 @@ target_include_directories(cxx-mlir target_link_libraries(cxx-mlir PUBLIC cxx-parser + MLIRBuiltinToLLVMIRTranslation MLIRControlFlowDialect MLIRControlFlowToLLVM MLIRIR MLIRLLVMCommonConversion MLIRLLVMDialect + MLIRLLVMToLLVMIRTranslation MLIRPass + MLIRTargetLLVMIRExport MLIRTransforms ) diff --git a/src/mlir/cxx/mlir/cxx_dialect_conversions.cc b/src/mlir/cxx/mlir/cxx_dialect_conversions.cc index 3e9fa0b8..0c9b8a52 100644 --- a/src/mlir/cxx/mlir/cxx_dialect_conversions.cc +++ b/src/mlir/cxx/mlir/cxx_dialect_conversions.cc @@ -24,12 +24,16 @@ #include // mlir +#include #include #include #include #include #include #include +#include +#include +#include #include #include @@ -1260,3 +1264,14 @@ auto cxx::lowerToMLIR(mlir::ModuleOp module) -> mlir::LogicalResult { return mlir::success(); } + +auto cxx::exportToLLVMIR(mlir::ModuleOp module, llvm::LLVMContext &context) + -> std::unique_ptr { + mlir::registerBuiltinDialectTranslation(*module->getContext()); + mlir::registerLLVMDialectTranslation(*module->getContext()); + + auto llvmModule = mlir::translateModuleToLLVMIR(module, context); + module->getContext()->loadDialect(); + + return llvmModule; +} diff --git a/src/mlir/cxx/mlir/cxx_dialect_conversions.h b/src/mlir/cxx/mlir/cxx_dialect_conversions.h index 8631b252..dc9c662b 100644 --- a/src/mlir/cxx/mlir/cxx_dialect_conversions.h +++ b/src/mlir/cxx/mlir/cxx_dialect_conversions.h @@ -20,6 +20,7 @@ #pragma once +#include #include #include @@ -29,4 +30,8 @@ namespace cxx { [[nodiscard]] auto lowerToMLIR(mlir::ModuleOp module) -> mlir::LogicalResult; +[[nodiscard]] auto exportToLLVMIR(mlir::ModuleOp module, + llvm::LLVMContext& context) + -> std::unique_ptr; + } // namespace cxx diff --git a/src/parser/cxx/cli.cc b/src/parser/cxx/cli.cc index 639485ae..bc03b6a3 100644 --- a/src/parser/cxx/cli.cc +++ b/src/parser/cxx/cli.cc @@ -158,6 +158,9 @@ std::vector options{ {"-emit-ir", "Emit the IR for the source inputs", &CLI::opt_emit_ir}, + {"-emit-llvm", "Emit the LLVM IR for the source inputs", + &CLI::opt_emit_llvm}, + {"-ast-dump", "Build ASTs and then debug dump them", &CLI::opt_ast_dump}, {"-ast-print", "Print the AST", &CLI::opt_ast_print}, diff --git a/src/parser/cxx/cli.h b/src/parser/cxx/cli.h index c20fe83e..b833f434 100644 --- a/src/parser/cxx/cli.h +++ b/src/parser/cxx/cli.h @@ -54,6 +54,7 @@ class CLI { bool opt_ast_dump = false; bool opt_ast_print = false; bool opt_emit_ir = false; + bool opt_emit_llvm = false; bool opt_dM = false; bool opt_dump_symbols = false; bool opt_dump_tokens = false;