diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1071,7 +1071,7 @@ def d_Joined : Joined<["-"], "d">, Group; def emit_ast : Flag<["-"], "emit-ast">, HelpText<"Emit Clang AST files for source inputs">; -def emit_llvm : Flag<["-"], "emit-llvm">, Flags<[CC1Option]>, Group, +def emit_llvm : Flag<["-"], "emit-llvm">, Flags<[CC1Option, FC1Option]>, Group, HelpText<"Use the LLVM representation for assembler and object files">; def emit_interface_stubs : Flag<["-"], "emit-interface-stubs">, Flags<[CC1Option]>, Group, HelpText<"Generate Interface Stub Files.">; diff --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h --- a/flang/include/flang/Frontend/FrontendActions.h +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -13,6 +13,7 @@ #include "flang/Semantics/semantics.h" #include "mlir/IR/BuiltinOps.h" +#include "llvm/IR/Module.h" #include namespace Fortran::frontend { @@ -163,12 +164,25 @@ std::unique_ptr mlirModule; std::unique_ptr mlirCtx; /// } + + /// @name LLVM IR + std::unique_ptr llvmCtx; + std::unique_ptr llvmModule; + + /// Generates an LLVM IR module from CodeGenAction::mlirModule and saves it + /// in CodeGenAction::llvmModule. + void GenerateLLVMIR(); + /// } }; class EmitMLIRAction : public CodeGenAction { void ExecuteAction() override; }; +class EmitLLVMAction : public CodeGenAction { + void ExecuteAction() override; +}; + class EmitObjAction : public CodeGenAction { void ExecuteAction() override; }; diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h --- a/flang/include/flang/Frontend/FrontendOptions.h +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -34,6 +34,9 @@ /// Emit a .mlir file EmitMLIR, + /// Emit an .ll file + EmitLLVM, + /// Emit a .o file. EmitObj, @@ -84,9 +87,6 @@ /// Run a plugin action PluginAction - - /// TODO: RunPreprocessor, EmitLLVM, EmitLLVMOnly, - /// EmitCodeGenOnly, EmitAssembly, (...) }; /// \param suffix The file extension diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -137,6 +137,9 @@ case clang::driver::options::OPT_emit_mlir: opts.programAction = EmitMLIR; break; + case clang::driver::options::OPT_emit_llvm: + opts.programAction = EmitLLVM; + break; case clang::driver::options::OPT_emit_obj: opts.programAction = EmitObj; break; diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -14,6 +14,7 @@ #include "flang/Lower/Bridge.h" #include "flang/Lower/PFTBuilder.h" #include "flang/Lower/Support/Verifier.h" +#include "flang/Optimizer/Support/FIRContext.h" #include "flang/Optimizer/Support/InitFIR.h" #include "flang/Optimizer/Support/KindMapping.h" #include "flang/Optimizer/Support/Utils.h" @@ -28,6 +29,7 @@ #include "mlir/IR/Dialect.h" #include "mlir/Pass/PassManager.h" +#include "mlir/Target/LLVMIR/ModuleTranslation.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ErrorHandling.h" #include @@ -406,6 +408,76 @@ ci.semantics().DumpSymbolsSources(llvm::outs()); } +#include "flang/Tools/CLOptions.inc" + +// Lower the previously generated MLIR module into an LLVM IR module +void CodeGenAction::GenerateLLVMIR() { + assert(mlirModule && "The MLIR module has not been generated yet."); + + CompilerInstance &ci = this->instance(); + + fir::support::loadDialects(*mlirCtx); + fir::support::registerLLVMTranslation(*mlirCtx); + + // Set-up the MLIR pass manager + mlir::PassManager pm(mlirCtx.get(), mlir::OpPassManager::Nesting::Implicit); + + pm.addPass(std::make_unique()); + pm.enableVerifier(/*verifyPasses=*/true); + mlir::PassPipelineCLParser passPipeline("", "Compiler passes to run"); + + // Create the pass pipeline + fir::createMLIRToLLVMPassPipeline(pm); + + // Run the pass manager + if (!mlir::succeeded(pm.run(*mlirModule))) { + unsigned diagID = ci.diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "Lowering to LLVM IR failed"); + ci.diagnostics().Report(diagID); + } + + // Translate to LLVM IR + auto optName = mlirModule->getName(); + llvmCtx = std::make_unique(); + llvmModule = mlir::translateModuleToLLVMIR( + *mlirModule, *llvmCtx, optName ? *optName : "FIRModule"); + + if (!llvmModule) { + unsigned diagID = ci.diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "failed to create the LLVM module"); + ci.diagnostics().Report(diagID); + return; + } +} + +void EmitLLVMAction::ExecuteAction() { + CompilerInstance &ci = this->instance(); + GenerateLLVMIR(); + + // Print the generated LLVM IR. If there is no pre-defined output stream to + // print to, create an output file. + std::unique_ptr os; + if (ci.IsOutputStreamNull()) { + os = ci.CreateDefaultOutputFile( + /*Binary=*/true, /*InFile=*/GetCurrentFileOrBufferName(), "ll"); + if (!os) { + unsigned diagID = ci.diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "failed to create the output file"); + ci.diagnostics().Report(diagID); + return; + } + } + + if (!ci.IsOutputStreamNull()) { + llvmModule->print( + ci.GetOutputStream(), /*AssemblyAnnotationWriter=*/nullptr); + } else { + llvmModule->print(*os, /*AssemblyAnnotationWriter=*/nullptr); + } + + return; +} + void EmitMLIRAction::ExecuteAction() { CompilerInstance &ci = this->instance(); diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -35,6 +35,8 @@ return std::make_unique(); case EmitMLIR: return std::make_unique(); + case EmitLLVM: + return std::make_unique(); case EmitObj: return std::make_unique(); case DebugUnparse: diff --git a/flang/test/Driver/driver-help.f90 b/flang/test/Driver/driver-help.f90 --- a/flang/test/Driver/driver-help.f90 +++ b/flang/test/Driver/driver-help.f90 @@ -65,6 +65,7 @@ ! HELP-FC1-NEXT:OPTIONS: ! HELP-FC1-NEXT: -cpp Enable predefined and command line preprocessor macros ! HELP-FC1-NEXT: -D = Define to (or 1 if omitted) +! HELP-FC1-NEXT: -emit-llvm Use the LLVM representation for assembler and object files ! HELP-FC1-NEXT: -emit-mlir Build the parse tree, then lower it to MLIR ! HELP-FC1-NEXT: -emit-obj Emit native object files ! HELP-FC1-NEXT: -E Only run the preprocessor diff --git a/flang/test/Driver/emit-llvm.f90 b/flang/test/Driver/emit-llvm.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Driver/emit-llvm.f90 @@ -0,0 +1,19 @@ +! Test the `-emit-llvm` option + +!------------ +! RUN COMMAND +!------------ +! RUN: %flang_fc1 -emit-llvm %s -o - | FileCheck %s + +!---------------- +! EXPECTED OUTPUT +!---------------- +! CHECK: ; ModuleID = 'FIRModule' +! CHECK: define void @_QQmain() +! CHECK-NEXT: ret void +! CHECK-NEXT: } + +!------ +! INPUT +!------ +end program diff --git a/flang/unittests/Frontend/FrontendActionTest.cpp b/flang/unittests/Frontend/FrontendActionTest.cpp --- a/flang/unittests/Frontend/FrontendActionTest.cpp +++ b/flang/unittests/Frontend/FrontendActionTest.cpp @@ -161,4 +161,31 @@ .contains( ":1:14: error: IF statement is not allowed in IF statement\n")); } + +TEST_F(FrontendActionTest, EmitLLVM) { + // Populate the input file with the pre-defined input and flush it. + *(inputFileOs_) << "end program"; + inputFileOs_.reset(); + + // Set-up the action kind. + compInst_.invocation().frontendOpts().programAction = EmitLLVM; + compInst_.invocation().preprocessorOpts().noReformat = true; + + // Set-up the output stream. We are using output buffer wrapped as an output + // stream, as opposed to an actual file (or a file descriptor). + llvm::SmallVector outputFileBuffer; + std::unique_ptr outputFileStream( + new llvm::raw_svector_ostream(outputFileBuffer)); + compInst_.set_outputStream(std::move(outputFileStream)); + + // Execute the action. + bool success = ExecuteCompilerInvocation(&compInst_); + + // Validate the expected output. + EXPECT_TRUE(success); + EXPECT_TRUE(!outputFileBuffer.empty()); + + EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()) + .contains("define void @_QQmain()")); +} } // namespace