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
@@ -750,7 +750,7 @@
            "name matches the given POSIX regular expression">;
 def R_Joined : Joined<["-"], "R">, Group<R_Group>, Flags<[CC1Option, CoreOption]>,
   MetaVarName<"<remark>">, HelpText<"Enable the specified remark">;
-def S : Flag<["-"], "S">, Flags<[NoXarchOption,CC1Option]>, Group<Action_Group>,
+def S : Flag<["-"], "S">, Flags<[NoXarchOption,CC1Option,FlangOption,FC1Option]>, Group<Action_Group>,
   HelpText<"Only run preprocess and compilation steps">;
 def Tbss : JoinedOrSeparate<["-"], "Tbss">, Group<T_Group>,
   MetaVarName<"<addr>">, HelpText<"Set starting address of BSS to <addr>">;
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
@@ -192,8 +192,19 @@
   void ExecuteAction() override;
 };
 
-class EmitObjAction : public CodeGenAction {
+class BackendAction : public CodeGenAction {
+public:
+  enum class BackendActionTy {
+    Backend_EmitAssembly, ///< Emit native assembly files
+    Backend_EmitObj ///< Emit native object files
+  };
+
+  BackendAction(BackendActionTy act) : action{act} {};
+
+private:
   void ExecuteAction() override;
+
+  BackendActionTy action;
 };
 
 } // namespace Fortran::frontend
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
@@ -40,6 +40,9 @@
   /// Emit a .o file.
   EmitObj,
 
+  /// Emit a .s file.
+  EmitAssembly,
+
   /// Parse, unparse the parse-tree and output a Fortran source file
   DebugUnparse,
 
diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt
--- a/flang/lib/Frontend/CMakeLists.txt
+++ b/flang/lib/Frontend/CMakeLists.txt
@@ -27,6 +27,8 @@
   FortranLower
   clangBasic
   clangDriver
+  LLVMAnalysis
+  LLVMTarget
   FIRDialect
   FIRSupport
   FIRBuilder
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
@@ -153,6 +153,9 @@
     case clang::driver::options::OPT_emit_obj:
       opts.programAction = EmitObj;
       break;
+    case clang::driver::options::OPT_S:
+      opts.programAction = EmitAssembly;
+      break;
     case clang::driver::options::OPT_fdebug_unparse:
       opts.programAction = DebugUnparse;
       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
@@ -31,7 +31,13 @@
 #include "mlir/Pass/PassManager.h"
 #include "mlir/Target/LLVMIR/ModuleTranslation.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Passes/PassBuilder.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Target/TargetMachine.h"
 #include <clang/Basic/Diagnostic.h>
 #include <memory>
 
@@ -417,7 +423,6 @@
 
   pm.addPass(std::make_unique<Fortran::lower::VerifierPass>());
   pm.enableVerifier(/*verifyPasses=*/true);
-  mlir::PassPipelineCLParser passPipeline("", "Compiler passes to run");
 
   // Create the pass pipeline
   fir::createMLIRToLLVMPassPipeline(pm);
@@ -490,11 +495,90 @@
   mlirModule->print(*os);
 }
 
-void EmitObjAction::ExecuteAction() {
+void BackendAction::ExecuteAction() {
   CompilerInstance &ci = this->instance();
-  unsigned DiagID = ci.diagnostics().getCustomDiagID(
-      clang::DiagnosticsEngine::Error, "code-generation is not available yet");
-  ci.diagnostics().Report(DiagID);
+  // Generate an LLVM module if it's not already present (it will already be
+  // present if the input file is an LLVM IR/BC file).
+  if (!llvmModule)
+    GenerateLLVMIR();
+
+  // Create `Target`
+  std::string error;
+  const std::string &theTriple = llvmModule->getTargetTriple();
+  const llvm::Target *theTarget =
+      llvm::TargetRegistry::lookupTarget(theTriple, error);
+  // TODO: Make this a diagnostic once `flang-new` can consume LLVM IR files
+  // (in which users could use unsupported triples)
+  assert(theTarget && "Failed to create Target");
+
+  // Create `TargetMachine`
+  std::unique_ptr<llvm::TargetMachine> TM;
+  TM.reset(theTarget->createTargetMachine(theTriple, /*CPU=*/"",
+      /*Features=*/"", llvm::TargetOptions(), llvm::None));
+  assert(TM && "Failed to create TargetMachine");
+  llvmModule->setDataLayout(TM->createDataLayout());
+
+  // If the output stream is a file, generate it and define the corresponding
+  // output stream. If a pre-defined output stream is available, we will use
+  // that instead.
+  //
+  // NOTE: `os` is a smart pointer that will be destroyed at the end of this
+  // method. However, it won't be written to until `CodeGenPasses` is
+  // destroyed. By defining `os` before `CodeGenPasses`, we make sure that the
+  // output stream won't be destroyed before it is written to. This only
+  // applies when an output file is used (i.e. there is no pre-defined output
+  // stream).
+  // TODO: Revisit once the new PM is ready (i.e. when `CodeGenPasses` is
+  // updated to use it).
+  std::unique_ptr<llvm::raw_pwrite_stream> os;
+  if (ci.IsOutputStreamNull()) {
+    // Get the output buffer/file
+    switch (action) {
+    case BackendActionTy::Backend_EmitAssembly:
+      os = ci.CreateDefaultOutputFile(
+          /*Binary=*/false, /*InFile=*/GetCurrentFileOrBufferName(), "s");
+      break;
+    case BackendActionTy::Backend_EmitObj:
+      os = ci.CreateDefaultOutputFile(
+          /*Binary=*/true, /*InFile=*/GetCurrentFileOrBufferName(), "o");
+      break;
+    }
+    if (!os) {
+      unsigned diagID = ci.diagnostics().getCustomDiagID(
+          clang::DiagnosticsEngine::Error, "failed to create the output file");
+      ci.diagnostics().Report(diagID);
+      return;
+    }
+  }
+
+  // Create an LLVM code-gen pass pipeline. Currently only the legacy pass
+  // manager is supported.
+  // TODO: Switch to the new PM once it's available in the backend.
+  llvm::legacy::PassManager CodeGenPasses;
+  CodeGenPasses.add(
+      createTargetTransformInfoWrapperPass(TM->getTargetIRAnalysis()));
+  llvm::Triple triple(theTriple);
+
+  std::unique_ptr<llvm::TargetLibraryInfoImpl> TLII =
+      std::make_unique<llvm::TargetLibraryInfoImpl>(triple);
+  assert(TLII && "Failed to create TargetLibraryInfo");
+  CodeGenPasses.add(new llvm::TargetLibraryInfoWrapperPass(*TLII));
+
+  llvm::CodeGenFileType cgft = (action == BackendActionTy::Backend_EmitAssembly)
+      ? llvm::CodeGenFileType::CGFT_AssemblyFile
+      : llvm::CodeGenFileType::CGFT_ObjectFile;
+  if (TM->addPassesToEmitFile(CodeGenPasses,
+          ci.IsOutputStreamNull() ? *os : ci.GetOutputStream(), nullptr,
+          cgft)) {
+    unsigned diagID =
+        ci.diagnostics().getCustomDiagID(clang::DiagnosticsEngine::Error,
+            "emission of this file type is not supported");
+    ci.diagnostics().Report(diagID);
+    return;
+  }
+
+  // Run the code-gen passes
+  CodeGenPasses.run(*llvmModule);
 }
 
 void InitOnlyAction::ExecuteAction() {
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
@@ -38,7 +38,11 @@
   case EmitLLVM:
     return std::make_unique<EmitLLVMAction>();
   case EmitObj:
-    return std::make_unique<EmitObjAction>();
+    return std::make_unique<BackendAction>(
+        BackendAction::BackendActionTy::Backend_EmitObj);
+  case EmitAssembly:
+    return std::make_unique<BackendAction>(
+        BackendAction::BackendActionTy::Backend_EmitAssembly);
   case DebugUnparse:
     return std::make_unique<DebugUnparseAction>();
   case DebugUnparseNoSema:
diff --git a/flang/lib/Optimizer/Support/FIRContext.cpp b/flang/lib/Optimizer/Support/FIRContext.cpp
--- a/flang/lib/Optimizer/Support/FIRContext.cpp
+++ b/flang/lib/Optimizer/Support/FIRContext.cpp
@@ -12,19 +12,20 @@
 
 #include "flang/Optimizer/Support/FIRContext.h"
 #include "flang/Optimizer/Support/KindMapping.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/IR/BuiltinAttributes.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "llvm/Support/Host.h"
 
-static constexpr const char *tripleName = "fir.triple";
-
 void fir::setTargetTriple(mlir::ModuleOp mod, llvm::StringRef triple) {
   auto target = fir::determineTargetTriple(triple);
-  mod->setAttr(tripleName, mlir::StringAttr::get(mod.getContext(), target));
+  mod->setAttr(mlir::LLVM::LLVMDialect::getTargetTripleAttrName(),
+               mlir::StringAttr::get(mod.getContext(), target));
 }
 
 llvm::Triple fir::getTargetTriple(mlir::ModuleOp mod) {
-  if (auto target = mod->getAttrOfType<mlir::StringAttr>(tripleName))
+  if (auto target = mod->getAttrOfType<mlir::StringAttr>(
+          mlir::LLVM::LLVMDialect::getTargetTripleAttrName()))
     return llvm::Triple(target.getValue());
   return llvm::Triple(llvm::sys::getDefaultTargetTriple());
 }
diff --git a/flang/test/CMakeLists.txt b/flang/test/CMakeLists.txt
--- a/flang/test/CMakeLists.txt
+++ b/flang/test/CMakeLists.txt
@@ -46,7 +46,7 @@
   flang_site_config=${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py)
 
 set(FLANG_TEST_DEPENDS
-  flang-new llvm-config FileCheck count not module_files fir-opt tco bbc
+  flang-new llvm-config FileCheck count not module_files fir-opt tco bbc llvm-objdump
 )
 
 if (FLANG_INCLUDE_TESTS)
diff --git a/flang/test/Driver/code-gen-aarch64.f90 b/flang/test/Driver/code-gen-aarch64.f90
new file mode 100644
--- /dev/null
+++ b/flang/test/Driver/code-gen-aarch64.f90
@@ -0,0 +1,31 @@
+! Test -emit-obj (X86)
+
+! REQUIRES: aarch64-registered-target, x86-registered-target
+
+!-------------
+! RUN COMMANDS
+!-------------
+! RUN: rm -f %t.o
+! RUN: %flang_fc1 -emit-obj -triple aarch64-unknown-linux-gnu %s -o %t.o
+! RUN: llvm-objdump --triple aarch64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=CORRECT_TRIPLE
+! RUN: rm -f %t.o
+! RUN: %flang -c --target=aarch64-unknown-linux-gnu %s -o %t.o
+! RUN: llvm-objdump --triple aarch64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=CORRECT_TRIPLE
+
+! RUN: %flang -c --target=aarch64-unknown-linux-gnu %s -o %t.o
+! RUN: llvm-objdump --triple x86_64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=INCORRECT_TRIPLE
+
+!----------------
+! EXPECTED OUTPUT
+!----------------
+! CORRECT_TRIPLE-LABEL: <_QQmain>:
+! CORRECT_TRIPLE-NEXT:  	ret
+
+! When incorrect triple is used to disassemble, there won't be a ret instruction at all.
+! INCORRECT_TRIPLE-LABEL: <_QQmain>:
+! INCORRECT_TRIPLE-NOT:  	ret
+
+!------
+! INPUT
+!------
+end program
diff --git a/flang/test/Driver/code-gen-x86.f90 b/flang/test/Driver/code-gen-x86.f90
new file mode 100644
--- /dev/null
+++ b/flang/test/Driver/code-gen-x86.f90
@@ -0,0 +1,32 @@
+! Test -emit-obj (X86)
+
+! REQUIRES: x86-registered-target, aarch64-registered-target
+! UNSUPPORTED: darwin, macos
+
+!-------------
+! RUN COMMANDS
+!-------------
+! RUN: rm -f %t.o
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-obj %s -o %t.o
+! RUN: llvm-objdump --triple x86_64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=CORRECT_TRIPLE
+! RUN: rm -f %t.o
+! RUN: %flang --target=x86_64-unknown-linux-gnu -c %s -o %t.o
+! RUN: llvm-objdump --triple x86_64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=CORRECT_TRIPLE
+
+! RUN: %flang -c --target=x86_64-unknown-linux-gnu %s -o %t.o
+! RUN: llvm-objdump --triple aarch64-unknown-linux-gnu --disassemble-all %t.o | FileCheck %s --check-prefix=INCORRECT_TRIPLE
+
+!----------------
+! EXPECTED OUTPUT
+!----------------
+! CORRECT_TRIPLE-LABEL: <_QQmain>:
+! CORRECT_TRIPLE-NEXT:  	retq
+
+! When incorrect triple is used to disassemble, there won't be a ret(q) instruction at all.
+! INCORRECT_TRIPLE-LABEL: <_QQmain>:
+! INCORRECT_TRIPLE-NOT:  	ret
+
+!------
+! INPUT
+!------
+end program
diff --git a/flang/test/Driver/code-gen.f90 b/flang/test/Driver/code-gen.f90
deleted file mode 100644
--- a/flang/test/Driver/code-gen.f90
+++ /dev/null
@@ -1,19 +0,0 @@
-! Although code-generation is not yet available, we do have frontend actions
-! that correspond to `-c` and `-emit-obj`. For now these actions are just a
-! placeholder and running them leads to a driver error. This test makes sure
-! that these actions are indeed run (rather than `-c` or `-emit-obj` being
-! rejected earlier).
-! TODO: Replace this file with a proper test once code-generation is available.
-
-!-----------
-! RUN LINES
-!-----------
-! RUN: not %flang %s 2>&1 | FileCheck %s --check-prefix=ERROR
-! RUN: not %flang -c %s 2>&1 | FileCheck %s --check-prefix=ERROR
-! RUN: not %flang -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR
-! RUN: not %flang -fc1 -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR
-
-!-----------------------
-! EXPECTED OUTPUT
-!-----------------------
-! ERROR: code-generation is not available yet
diff --git a/flang/test/Driver/driver-help-hidden.f90 b/flang/test/Driver/driver-help-hidden.f90
--- a/flang/test/Driver/driver-help-hidden.f90
+++ b/flang/test/Driver/driver-help-hidden.f90
@@ -55,6 +55,7 @@
 ! CHECK-NEXT: -print-target-triple    Print the normalized target triple
 ! CHECK-NEXT: -P                     Disable linemarker output in -E mode
 ! CHECK-NEXT: -std=<value>           Language standard to compile for
+! CHECK-NEXT: -S                     Only run preprocess and compilation steps
 ! CHECK-NEXT: --target=<value>        Generate code for the given target
 ! CHECK-NEXT: -U <macro>             Undefine macro <macro>
 ! CHECK-NEXT: --version Print version information
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
@@ -55,6 +55,7 @@
 ! HELP-NEXT: -print-target-triple    Print the normalized target triple
 ! HELP-NEXT: -P                     Disable linemarker output in -E mode
 ! HELP-NEXT: -std=<value>           Language standard to compile for
+! HELP-NEXT: -S                     Only run preprocess and compilation steps
 ! HELP-NEXT: --target=<value>       Generate code for the given target
 ! HELP-NEXT: -U <macro>             Undefine macro <macro>
 ! HELP-NEXT: --version              Print version information
@@ -128,6 +129,7 @@
 ! HELP-FC1-NEXT: -plugin <name>         Use the named plugin action instead of the default action (use "help" to list available options)
 ! HELP-FC1-NEXT: -P                     Disable linemarker output in -E mode
 ! HELP-FC1-NEXT: -std=<value>           Language standard to compile for
+! HELP-FC1-NEXT: -S                     Only run preprocess and compilation steps
 ! HELP-FC1-NEXT: -test-io               Run the InputOuputTest action. Use for development and testing only.
 ! HELP-FC1-NEXT: -triple <value>        Specify target triple (e.g. i686-apple-darwin9)
 ! HELP-FC1-NEXT: -U <macro>             Undefine macro <macro>
diff --git a/flang/test/Driver/emit-asm-aarch64.f90 b/flang/test/Driver/emit-asm-aarch64.f90
new file mode 100644
--- /dev/null
+++ b/flang/test/Driver/emit-asm-aarch64.f90
@@ -0,0 +1,21 @@
+! Test -S (AArch64)
+
+! REQUIRES: aarch64-registered-target
+
+!-------------
+! RUN COMMANDS
+!-------------
+! RUN: %flang_fc1 -S -triple aarch64-unknown-linux-gnu %s -o - | FileCheck %s
+! RUN: %flang -S -target aarch64-unknown-linux-gnu %s -o - | FileCheck %s
+
+!----------------
+! EXPECTED OUTPUT
+!----------------
+! CHECK-LABEL: _QQmain:
+! CHECK-NEXT: .Lfunc_begin0:
+! CHECK: ret
+
+!------
+! INPUT
+!------
+end program
diff --git a/flang/test/Driver/emit-asm-x86.f90 b/flang/test/Driver/emit-asm-x86.f90
new file mode 100644
--- /dev/null
+++ b/flang/test/Driver/emit-asm-x86.f90
@@ -0,0 +1,21 @@
+! Test -S (X86)
+
+! REQUIRES: x86-registered-target
+
+!-------------
+! RUN COMMANDS
+!-------------
+! RUN: %flang_fc1 -S -triple x86_64-unknown-linux-gnu %s -o - | FileCheck %s
+! RUN: %flang -S -target x86_64-unknown-linux-gnu %s -o - | FileCheck %s
+
+!----------------
+! EXPECTED OUTPUT
+!----------------
+! CHECK-LABEL: _QQmain:
+! CHECK-NEXT: .Lfunc_begin0:
+! CHECK: ret
+
+!------
+! INPUT
+!------
+end program
diff --git a/flang/test/Driver/syntax-only.f90 b/flang/test/Driver/syntax-only.f90
--- a/flang/test/Driver/syntax-only.f90
+++ b/flang/test/Driver/syntax-only.f90
@@ -13,14 +13,15 @@
 ! RUN: %flang -fsyntax-only %s 2>&1 | FileCheck %s --allow-empty
 ! RUN: %flang_fc1 %s 2>&1 | FileCheck %s --allow-empty
 
-! RUN: not %flang  %s 2>&1 | FileCheck %s --check-prefix=NO_FSYNTAX_ONLY
-! RUN: not %flang_fc1 -emit-obj %s 2>&1 | FileCheck %s --check-prefix=NO_FSYNTAX_ONLY
+! RUN: rm -rf %t/non-existent-dir/
+! RUN: not %flang -c %s -o %t/non-existent-dir/syntax-only.o 2>&1 | FileCheck %s --check-prefix=NO_FSYNTAX_ONLY
+! RUN: not %flang_fc1 -emit-obj %s -o %t/non-existent-dir/syntax-only.o 2>&1 | FileCheck %s --check-prefix=NO_FSYNTAX_ONLY
 
 !-----------------
 ! EXPECTED OUTPUT
 !-----------------
 ! CHECK-NOT: error
-! NO_FSYNTAX_ONLY: error: code-generation is not available yet
+! NO_FSYNTAX_ONLY: error: failed to create the output file
 
 !-------
 ! INPUT
diff --git a/flang/test/Fir/target-rewrite-triple.fir b/flang/test/Fir/target-rewrite-triple.fir
--- a/flang/test/Fir/target-rewrite-triple.fir
+++ b/flang/test/Fir/target-rewrite-triple.fir
@@ -1,10 +1,10 @@
 // RUN: fir-opt --target-rewrite %s | FileCheck %s --check-prefix=UNCHANGED
 // RUN: fir-opt --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefix=CHANGED
 
-// UNCHANGED: fir.triple = "aarch64-unknown-linux-gnu"
-// CHANGED: fir.triple = "x86_64-unknown-linux-gnu"
-// CHANGED-NOT: fir.triple = "aarch64-unknown-linux-gnu"
-module attributes {fir.triple = "aarch64-unknown-linux-gnu"}  {
+// UNCHANGED: llvm.target_triple = "aarch64-unknown-linux-gnu"
+// CHANGED: llvm.target_triple = "x86_64-unknown-linux-gnu"
+// CHANGED-NOT: llvm.target_triple = "aarch64-unknown-linux-gnu"
+module attributes {llvm.target_triple = "aarch64-unknown-linux-gnu"}  {
   func @dummyfunc() -> () {
     return
   }
diff --git a/flang/test/lit.cfg.py b/flang/test/lit.cfg.py
--- a/flang/test/lit.cfg.py
+++ b/flang/test/lit.cfg.py
@@ -39,6 +39,11 @@
 llvm_config.feature_config(
     [('--assertion-mode', {'ON': 'asserts'})])
 
+# Targets
+config.targets = frozenset(config.targets_to_build.split())
+for arch in config.targets_to_build.split():
+    config.available_features.add(arch.lower() + '-registered-target')
+
 # excludes: A list of directories to exclude from the testsuite. The 'Inputs'
 # subdirectories contain auxiliary inputs for various tests in their parent
 # directories.
diff --git a/flang/test/lit.site.cfg.py.in b/flang/test/lit.site.cfg.py.in
--- a/flang/test/lit.site.cfg.py.in
+++ b/flang/test/lit.site.cfg.py.in
@@ -18,6 +18,7 @@
 config.flang_standalone_build = @FLANG_STANDALONE_BUILD@
 config.has_plugins = @LLVM_ENABLE_PLUGINS@
 config.cc = "@CMAKE_C_COMPILER@"
+config.targets_to_build = "@TARGETS_TO_BUILD@"
 
 # Support substitution of the tools_dir with user parameters. This is
 # used when we can't determine the tool dir at configuration time.
diff --git a/flang/tools/flang-driver/fc1_main.cpp b/flang/tools/flang-driver/fc1_main.cpp
--- a/flang/tools/flang-driver/fc1_main.cpp
+++ b/flang/tools/flang-driver/fc1_main.cpp
@@ -20,6 +20,7 @@
 #include "llvm/Option/Arg.h"
 #include "llvm/Option/ArgList.h"
 #include "llvm/Option/OptTable.h"
+#include "llvm/Support/TargetSelect.h"
 
 #include <cstdio>
 
@@ -48,6 +49,11 @@
   bool success =
       CompilerInvocation::CreateFromArgs(flang->invocation(), argv, diags);
 
+  // Initialize targets first, so that --version shows registered targets.
+  llvm::InitializeAllTargets();
+  llvm::InitializeAllTargetMCs();
+  llvm::InitializeAllAsmPrinters();
+
   diagsBuffer->FlushDiagnostics(flang->diagnostics());
 
   if (!success)
diff --git a/flang/unittests/Frontend/CMakeLists.txt b/flang/unittests/Frontend/CMakeLists.txt
--- a/flang/unittests/Frontend/CMakeLists.txt
+++ b/flang/unittests/Frontend/CMakeLists.txt
@@ -1,3 +1,7 @@
+set(LLVM_LINK_COMPONENTS
+  ${LLVM_TARGETS_TO_BUILD}
+)
+
 add_flang_unittest(FlangFrontendTests
   CompilerInstanceTest.cpp
   FrontendActionTest.cpp
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
@@ -11,6 +11,7 @@
 #include "flang/Frontend/FrontendOptions.h"
 #include "flang/FrontendTool/Utils.h"
 #include "llvm/Support/FileSystem.h"
+#include "llvm/Support/TargetSelect.h"
 #include "llvm/Support/raw_ostream.h"
 
 #include "gtest/gtest.h"
@@ -188,4 +189,35 @@
   EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data())
                   .contains("define void @_QQmain()"));
 }
+
+TEST_F(FrontendActionTest, EmitAsm) {
+  // 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 = EmitAssembly;
+  compInst_.invocation().preprocessorOpts().noReformat = true;
+
+  // Initialise LLVM backend
+  llvm::InitializeAllTargets();
+  llvm::InitializeAllTargetMCs();
+  llvm::InitializeAllAsmPrinters();
+
+  // 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<char, 256> outputFileBuffer;
+  std::unique_ptr<llvm::raw_pwrite_stream> 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("_QQmain"));
+}
 } // namespace