gollvm: add support for the LLVM integrated assembler

Add preliminary support for the integrated assembler (now enabled by
default, but selectable via -fintegrated-as/-fno-integrated-as).
Includes a set of new unit tests designed to verify that the driver is
doing the right thing in various scenarios.

Assemblers have many options (target-related options, options to
control DWARF/debug, etc); to support these options in the integrated
assembler the gollvm driver code has to look for them on the command
line (e.g. "-Wa,--fatal-warnings") and then set the appropriate flag
when it configures the LLVM assembly back end. The code to do this is
still largely unwritten at the moment, however.

The integrated assemble is on by default, but can be disable
using '-fno-integrated-as". The driver will also flip the default
(e.g. use the external assembler) if an unknown "-Wa,..."  is encountered
on the command line.

Change-Id: Id82d999187550d58b651dd51e03a7e9139aa50a5
Reviewed-on: https://go-review.googlesource.com/c/gollvm/+/255050
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Trust: Than McIntosh <thanm@google.com>
diff --git a/driver/Action.cpp b/driver/Action.cpp
index 3105d2b..ebfab98 100644
--- a/driver/Action.cpp
+++ b/driver/Action.cpp
@@ -15,6 +15,8 @@
 
 #include "llvm/Support/raw_ostream.h"
 
+#include <sstream>
+
 namespace gollvm {
 namespace driver {
 
@@ -23,6 +25,7 @@
   switch (type_) {
     case A_ReadStdin: return "readstdin";
     case A_InputFile: return "inputfile";
+    case A_CompileAndAssemble: return "compile+assemble";
     case A_Compile: return "compile";
     case A_Assemble: return "assemble";
     case A_Link: return "link";
@@ -51,16 +54,23 @@
   return nullptr;
 }
 
-void Action::dump()
+std::string Action::toString()
 {
-  llvm::errs() << "Action " << getName() << " inputs:\n";
+  std::stringstream s;
+  s << "Action " << getName() << std::endl << "  inputs:\n";
   for (auto inp : inputs()) {
-    llvm::errs() << "  " << ((void*) inp) << " " << inp->getName() << " ";
+    s << "    " << inp->getName() << " ";
     InputAction *ia = inp->castToInputAction();
     if (ia)
-      llvm::errs() << ia->input()->toString();
-    llvm::errs() << "\n";
+      s << ia->input()->toString();
+    s << "\n";
   }
+  return s.str();
+}
+
+void Action::dump()
+{
+  llvm::errs() << toString();
 }
 
 } // end namespace driver
diff --git a/driver/Action.h b/driver/Action.h
index b7c1959..39662fd 100644
--- a/driver/Action.h
+++ b/driver/Action.h
@@ -40,6 +40,7 @@
   enum Type {
     A_InputFile,
     A_ReadStdin,
+    A_CompileAndAssemble,
     A_Compile,
     A_Assemble,
     A_Link
@@ -65,6 +66,9 @@
   // debugging
   void dump();
 
+  // unit testing
+  std::string toString();
+
  private:
   Type type_;
   ActionList inputs_;
diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt
index eac9ed6..c6429df 100644
--- a/driver/CMakeLists.txt
+++ b/driver/CMakeLists.txt
@@ -35,6 +35,7 @@
   GccUtils.cpp
   GnuTools.cpp
   GollvmOptions.cpp
+  IntegAssembler.cpp
   LinuxToolChain.cpp
   ReadStdin.cpp
   Tool.cpp
diff --git a/driver/Compilation.cpp b/driver/Compilation.cpp
index ac0291e..4123e40 100644
--- a/driver/Compilation.cpp
+++ b/driver/Compilation.cpp
@@ -20,6 +20,8 @@
 #include "Command.h"
 #include "Driver.h"
 
+#include <sstream>
+
 namespace gollvm {
 namespace driver {
 
@@ -71,6 +73,17 @@
   return ownedArtifacts_.back().get();
 }
 
+Artifact *Compilation::createFakeFileArtifact(Action *act)
+{
+  std::stringstream s;
+  s << "/tmp/out." << act->getName() << "." << paths_.size();
+  std::string path(s.str());
+  paths_.push_back(std::string(path));
+  const char *fn = paths_.back().c_str();
+  ownedArtifacts_.push_back(std::unique_ptr<Artifact>(new Artifact(fn)));
+  return ownedArtifacts_.back().get();
+}
+
 Artifact *Compilation::newFileArtifact(const char *path, bool isTempFile)
 {
   paths_.push_back(std::string(path));
diff --git a/driver/Compilation.h b/driver/Compilation.h
index de82da4..ec96bc8 100644
--- a/driver/Compilation.h
+++ b/driver/Compilation.h
@@ -55,9 +55,9 @@
   // on action plus command line flags).
   Artifact* createOutputFileArtifact(Action *act);
 
-  // Temporary (for this patch only): create a dummy artifact for the
-  // specified file.
-  Artifact* createDummyAsmOutArtifact(const std::string &fileName);
+  // Create a dummy artifact to hold the output of the specified
+  // action. For unit testing.
+  Artifact* createFakeFileArtifact(Action *act);
 
   // Toolchain, driver
   ToolChain &toolchain() { return toolchain_; }
diff --git a/driver/CompileGo.cpp b/driver/CompileGo.cpp
index a064858..a270d83 100644
--- a/driver/CompileGo.cpp
+++ b/driver/CompileGo.cpp
@@ -132,11 +132,11 @@
   void setCConv();
 
   // The routines below return TRUE for success, FALSE for failure/error/
-  bool setup();
+  bool setup(const Action &jobAction);
   bool initBridge();
   bool invokeFrontEnd();
   bool invokeBridge();
-  bool invokeBackEnd();
+  bool invokeBackEnd(const Action &jobAction);
   bool resolveInputOutput(const Action &jobAction,
                           const ArtifactList &inputArtifacts,
                           const Artifact &output);
@@ -180,7 +180,7 @@
     return false;
 
   // Setup
-  if (!setup())
+  if (!setup(jobAction))
     return false;
 
   // Set up the bridge
@@ -192,7 +192,7 @@
     return false;
 
   // Invoke back end
-  if (!invokeBackEnd())
+  if (!invokeBackEnd(jobAction))
     return false;
 
   return true;
@@ -280,7 +280,7 @@
   return pattern;
 }
 
-bool CompileGoImpl::setup()
+bool CompileGoImpl::setup(const Action &jobAction)
 {
   // Set triple.
   triple_ = driver_.triple();
@@ -410,8 +410,10 @@
 
   TargetOptions Options;
 
-  // FIXME: turn off integrated assembler for now.
-  Options.DisableIntegratedAS = true;
+  auto jat = jobAction.type();
+  assert(jat == Action::A_CompileAndAssemble ||
+         jat == Action::A_Compile);
+  Options.DisableIntegratedAS = !(jat == Action::A_CompileAndAssemble);
 
   // FIXME: this hard-wires on the equivalent of -ffunction-sections
   // and -fdata-sections, since there doesn't seem to be a high-level
@@ -872,7 +874,7 @@
   pmb.populateModulePassManager(MPM);
 }
 
-bool CompileGoImpl::invokeBackEnd()
+bool CompileGoImpl::invokeBackEnd(const Action &jobAction)
 {
   tlii_.reset(new TargetLibraryInfoImpl(triple_));
 
@@ -899,7 +901,8 @@
 
   legacy::PassManager codeGenPasses;
   bool noverify = args_.hasArg(gollvm::options::OPT_noverify);
-  CodeGenFileType ft = CGFT_AssemblyFile;
+  CodeGenFileType ft = (jobAction.type() == Action::A_CompileAndAssemble ?
+                        CGFT_ObjectFile : CGFT_AssemblyFile);
 
   // Add passes to emit bitcode or LLVM IR as appropriate. Here we mimic
   // clang behavior, which is to emit bitcode when "-emit-llvm" is specified
diff --git a/driver/Driver.cpp b/driver/Driver.cpp
index c69f3c9..13e7cf7 100644
--- a/driver/Driver.cpp
+++ b/driver/Driver.cpp
@@ -15,6 +15,8 @@
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Program.h"
 
+#include <sstream>
+
 #include "Action.h"
 #include "Compilation.h"
 #include "Driver.h"
@@ -34,7 +36,8 @@
     : args_(args),
       opts_(optTable),
       progname_(argv0),
-      usingSplitStack_(using_splitstack)
+      usingSplitStack_(using_splitstack),
+      unitTesting_(false)
 {
   if (const opt::Arg *arg = args.getLastArg(gollvm::options::OPT_sysroot_EQ))
     sysroot_ = arg->getValue();
@@ -150,6 +153,27 @@
   return name.str();
 }
 
+// Returns TRUE if we should use the integrated assembler.
+bool Driver::useIntegratedAssembler()
+{
+  opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_fintegrated_as,
+                                   gollvm::options::OPT_fno_integrated_as);
+
+  // If option is specified, then go with that.
+  if (arg != nullptr)
+    return arg->getOption().matches(options::OPT_fintegrated_as);
+
+  // If -Xassembler or -Wa,... used, then don't use the integrated
+  // assembler, since the driver doesn't support the full complement
+  // of assembler options (this can be removed if/when we do).
+  auto waComArg = args_.getLastArg(gollvm::options::OPT_Wa_COMMA);
+  auto xAsmArg = args_.getLastArg(gollvm::options::OPT_Xassembler);
+  if (waComArg != nullptr || xAsmArg != nullptr)
+    return false;
+
+  return true;
+}
+
 // FIXME: some  platforms have PIE enabled by default; we don't
 // yet support auto-detection of such platforms.
 
@@ -349,10 +373,12 @@
         std::string fn(arg->getValue());
 
         // Check for existence of input file.
-        if (!sys::fs::exists(fn)) {
-          errs() << progname_ << ": error: input file '"
-                 << fn << "' does not exist\n";
-          return nullptr;
+        if (!unitTesting()) {
+          if (!sys::fs::exists(fn)) {
+            errs() << progname_ << ": error: input file '"
+                   << fn << "' does not exist\n";
+            return nullptr;
+          }
         }
       }
       inputseen = true;
@@ -442,6 +468,7 @@
 
   bool OPT_c = args_.hasArg(gollvm::options::OPT_c);
   bool OPT_S = args_.hasArg(gollvm::options::OPT_S);
+  bool integAs = useIntegratedAssembler();
 
   // For -c/-S compiles, a mix of Go and assembly currently not allowed.
   if ((OPT_c || OPT_S) && !gofiles.empty() && !asmfiles.empty()) {
@@ -457,14 +484,20 @@
     ActionList inacts;
     appendInputActions(gofiles, inacts, compilation);
 
+    bool needAsm = (!OPT_S && !args_.hasArg(gollvm::options::OPT_emit_llvm));
+    Action::Type atyp = (needAsm && integAs ?
+                         Action::A_CompileAndAssemble :
+                         Action::A_Compile);
+
     // Create action
-    Action *gocompact =
-        new Action(Action::A_Compile, inacts);
+    Action *gocompact = new Action(atyp, inacts);
     compilation.recordAction(gocompact);
     compilation.addAction(gocompact);
+    if (atyp == Action::A_CompileAndAssemble && !OPT_c)
+      linkerInputActions.push_back(gocompact);
 
-    // Schedule assemble action now if no -S.
-    if (!OPT_S && !args_.hasArg(gollvm::options::OPT_emit_llvm)) {
+    // Schedule assemble action now if needed.
+    if (needAsm && atyp == Action::A_Compile) {
       // Create action
       Action *asmact =
           new Action(Action::A_Assemble, gocompact);
@@ -515,6 +548,20 @@
   return true;
 }
 
+std::string Driver::dumpActions(Compilation &compilation)
+{
+  std::stringstream s;
+  for (Action *action : compilation.actions()) {
+    s << action->toString();
+    auto it = artmap_.find(action);
+    if (it != artmap_.end()) {
+      Artifact *oa = it->second;
+      s << "  output:\n" << "    " << oa->toString() << "\n";
+    }
+  }
+  return s.str();
+}
+
 ArtifactList Driver::collectInputArtifacts(Action *act, InternalTool *it)
 {
   ArtifactList result;
@@ -540,14 +587,18 @@
   // Select the result file for this action.
   Artifact *result = nullptr;
   if (!lastAct) {
-    auto tfa = compilation.createTemporaryFileArtifact(act);
-    if (!tfa)
-      return false;
-    result = *tfa;
-    artmap_[act] = result;
+    if (unitTesting()) {
+      result = compilation.createFakeFileArtifact(act);
+    } else {
+      auto tfa = compilation.createTemporaryFileArtifact(act);
+      if (!tfa)
+        return false;
+      result = *tfa;
+    }
   } else {
     result = compilation.createOutputFileArtifact(act);
   }
+  artmap_[act] = result;
 
   // Select tool to process the action.
   Tool *tool = compilation.toolchain().getTool(act);
@@ -559,8 +610,10 @@
 
   // If internal tool, perform now.
   if (it != nullptr) {
-    if (! it->performAction(compilation, *act, actionInputs, *result))
-      return false;
+    if (!unitTesting()) {
+      if (! it->performAction(compilation, *act, actionInputs, *result))
+        return false;
+    }
     return true;
   }
 
diff --git a/driver/Driver.h b/driver/Driver.h
index 173f53a..34ce9d4 100644
--- a/driver/Driver.h
+++ b/driver/Driver.h
@@ -58,6 +58,9 @@
   // Build actions for compilation. Returns false if error.
   bool buildActions(Compilation &compilation);
 
+  // Emit actions as string, for unit testing.
+  std::string dumpActions(Compilation &compilation);
+
   // Process the action list. This means:
   // - execute any non-dependent actions that don't require the
   //   invocation of an external tool, and
@@ -105,6 +108,7 @@
   llvm::PIELevel::Level getPieLevel();
   bool picIsPIE();
   bool isPIE();
+  bool useIntegratedAssembler();
   bool usingSplitStack() const { return usingSplitStack_; }
   template<typename IT>
   llvm::Optional<IT> getLastArgAsInteger(gollvm::options::ID id,
@@ -120,6 +124,11 @@
                           Compilation &compilation);
   static void emitVersion();
 
+  // Get/set unit testing mode. In unit testing mode we don't
+  // check for the existence of input files.
+  void setUnitTesting() { unitTesting_ = true; }
+  bool unitTesting() const { return unitTesting_; }
+
  private:
   llvm::Triple triple_;
   llvm::opt::InputArgList &args_;
@@ -135,6 +144,7 @@
   std::unordered_map<Action *, Artifact*> artmap_;
   std::vector<std::string> prefixes_;
   bool usingSplitStack_;
+  bool unitTesting_;
 
   bool processAction(Action *act, Compilation &compilation, bool lastAct);
   ArtifactList collectInputArtifacts(Action *act, InternalTool *it);
diff --git a/driver/GollvmOptions.td b/driver/GollvmOptions.td
index bef655d..6db0a65 100644
--- a/driver/GollvmOptions.td
+++ b/driver/GollvmOptions.td
@@ -145,6 +145,13 @@
 def S : Flag<["-"], "S">, Flags<[DriverOption]>, Group<Action_Group>,
   HelpText<"Only run compilation step">;
 
+def fintegrated_as : Flag<["-"], "fintegrated-as">,
+                     Group<Action_Group>, HelpText<"Enable the integrated assembler">;
+
+def fno_integrated_as : Flag<["-"], "fno-integrated-as">,
+                        Group<Action_Group>,
+                        HelpText<"Disable the integrated assembler">;
+
 def c : Flag<["-"], "c">, Flags<[DriverOption]>, Group<Action_Group>,
   HelpText<"Only run compile and assemble steps">;
 
diff --git a/driver/IntegAssembler.cpp b/driver/IntegAssembler.cpp
new file mode 100644
index 0000000..5ba554b
--- /dev/null
+++ b/driver/IntegAssembler.cpp
@@ -0,0 +1,370 @@
+//===-- IntegAssembler.cpp ------------------------------------------------===//
+//
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+//===----------------------------------------------------------------------===//
+//
+// Gollvm driver helper class "IntegAssembler" methods.
+//
+//===----------------------------------------------------------------------===//
+
+#include "IntegAssembler.h"
+
+#include "go-llvm-linemap.h"
+#include "go-llvm-diagnostics.h"
+#include "go-llvm.h"
+#include "go-c.h"
+#include "mpfr.h"
+#include "GollvmOptions.h"
+#include "GollvmConfig.h"
+#include "GollvmPasses.h"
+
+#include "Action.h"
+#include "Artifact.h"
+#include "Driver.h"
+#include "ToolChain.h"
+
+#include "llvm/ADT/Triple.h"
+#include "llvm/Config/llvm-config.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/MC/MCAsmBackend.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCCodeEmitter.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCInstrInfo.h"
+#include "llvm/MC/MCObjectFileInfo.h"
+#include "llvm/MC/MCObjectWriter.h"
+#include "llvm/MC/MCParser/MCAsmParser.h"
+#include "llvm/MC/MCParser/MCTargetAsmParser.h"
+#include "llvm/MC/MCRegisterInfo.h"
+#include "llvm/MC/MCSectionMachO.h"
+#include "llvm/MC/MCStreamer.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCTargetOptions.h"
+#include "llvm/Option/Arg.h"
+#include "llvm/Option/ArgList.h"
+#include "llvm/Option/OptTable.h"
+#include "llvm/Option/Option.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Host.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/Process.h"
+#include "llvm/Support/Program.h"
+#include "llvm/Support/Regex.h"
+#include "llvm/Support/Signals.h"
+#include "llvm/Support/TargetRegistry.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Target/TargetMachine.h"
+
+#include <sstream>
+
+using namespace llvm;
+
+namespace gollvm {
+namespace driver {
+
+class IntegAssemblerImpl {
+ public:
+  IntegAssemblerImpl(IntegAssembler &ia, ToolChain &tc, const std::string &executablePath);
+
+  // Perform compilation.
+  bool performAction(Compilation &compilation,
+                     const Action &jobAction,
+                     const ArtifactList &inputArtifacts,
+                     const Artifact &output);
+
+ private:
+  IntegAssembler &ia_;
+  Triple triple_;
+  const ToolChain &toolchain_;
+  Driver &driver_;
+  LLVMContext context_;
+  const char *progname_;
+  std::string executablePath_;
+  opt::InputArgList &args_;
+  std::string inputFileName_;
+  std::string objOutFileName_;
+  std::unique_ptr<raw_fd_ostream> objout_;
+
+  bool resolveInputOutput(const Action &jobAction,
+                          const ArtifactList &inputArtifacts,
+                          const Artifact &output);
+  bool invokeAssembler();
+};
+
+IntegAssemblerImpl::IntegAssemblerImpl(IntegAssembler &ia, ToolChain &tc, const std::string &executablePath)
+    : ia_(ia),
+      triple_(tc.driver().triple()),
+      toolchain_(tc),
+      driver_(tc.driver()),
+      progname_(tc.driver().progname()),
+      executablePath_(executablePath),
+      args_(tc.driver().args())
+{
+  InitializeAllTargets();
+  InitializeAllTargetMCs();
+  InitializeAllAsmPrinters();
+  InitializeAllAsmParsers();
+}
+
+bool IntegAssemblerImpl::resolveInputOutput(const Action &jobAction,
+                                            const ArtifactList &inputArtifacts,
+                                            const Artifact &output)
+{
+  // Collect input file. Should be only one.
+  if (inputArtifacts.size() != 1) {
+    errs() << progname_ << ": expected exactly one input file, "
+           << inputArtifacts.size() << " provided.\n";
+    return false;
+  }
+  inputFileName_ = inputArtifacts[0]->file();
+  objOutFileName_ = output.file();
+
+  // Remove output on signal.
+  if (objOutFileName_ != "-")
+    sys::RemoveFileOnSignal(objOutFileName_);
+
+  // Open output file.
+  std::error_code EC;
+  sys::fs::OpenFlags OpenFlags = sys::fs::OF_None;
+  objout_ = std::make_unique<raw_fd_ostream>(
+      objOutFileName_, EC, OpenFlags);
+  if (EC) {
+    errs() << progname_ << ": error opening " << objOutFileName_ << ": "
+           << EC.message() << '\n';
+    return false;
+  }
+  return true;
+}
+
+bool IntegAssemblerImpl::invokeAssembler()
+{
+  // Get the target specific parser.
+  std::string Error;
+  const Target *TheTarget = TargetRegistry::lookupTarget("", triple_, Error);
+  if (!TheTarget) {
+    errs() << progname_ << ": unknown/unsupported target "
+           << triple_.str() << "\n";
+    return false;
+  }
+
+  ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
+      MemoryBuffer::getFileOrSTDIN(inputFileName_);
+  if (std::error_code EC = Buffer.getError()) {
+    Error = EC.message();
+    errs() << progname_ << ": opening/reading " << inputFileName_ << ": "
+           << EC.message() << "\n";
+    return false;
+  }
+
+  auto Trip = triple_.str();
+  SourceMgr SrcMgr;
+
+  // Tell SrcMgr about this buffer, which is what the parser will pick up.
+  SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
+
+  // Record the location of the include directories so that the lexer can find
+  // it later.
+  SrcMgr.setIncludeDirs(driver_.args().getAllArgValues(gollvm::options::OPT_I));
+
+  std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Trip));
+  assert(MRI && "Unable to create target register info!");
+
+  MCTargetOptions MCOptions;
+  std::unique_ptr<MCAsmInfo> MAI(
+      TheTarget->createMCAsmInfo(*MRI, Trip, MCOptions));
+  assert(MAI && "Unable to create target asm info!");
+
+  // FIXME: at this point what we need to do is collect up any assembler
+  // arguments specified with -Wa,XXX and turn them into the correct
+  // back end setup options. For now, just assert if we see -Wa.
+  auto waComArg = args_.getLastArg(gollvm::options::OPT_Wa_COMMA);
+  auto xAsmArg = args_.getLastArg(gollvm::options::OPT_Xassembler);
+  if (waComArg != nullptr || xAsmArg != nullptr) {
+    errs() << progname_ << ": internal error: option '"
+           <<  (waComArg != nullptr ? waComArg->getAsString(args_) :
+                xAsmArg->getAsString(args_))
+           << "' not yet implemented in integrated assembler\n";
+    assert(false);
+    return false;
+  }
+
+  // FIXME: no support yet for -march (bring over from CompileGo.cpp)
+  opt::Arg *cpuarg = args_.getLastArg(gollvm::options::OPT_march_EQ);
+  if (cpuarg != nullptr) {
+    errs() << progname_ << ": internal error: option '"
+           <<  cpuarg->getAsString(args_)
+           << "' not yet implemented in integrated assembler\n";
+    assert(false);
+    return false;
+  }
+
+  // Support for compressed debug.
+  llvm::DebugCompressionType CompressDebugSections =
+      llvm::DebugCompressionType::None;
+  llvm::opt::Arg *gzarg = args_.getLastArg(gollvm::options::OPT_gz,
+                                           gollvm::options::OPT_gz_EQ);
+  if (gzarg != nullptr) {
+    if (gzarg->getOption().matches(gollvm::options::OPT_gz)) {
+      CompressDebugSections = llvm::DebugCompressionType::GNU;
+    } else {
+      std::string ga(gzarg->getValue());
+      if (ga == "zlib") {
+        CompressDebugSections = llvm::DebugCompressionType::Z;
+      } else if (ga == "zlib-gnu") {
+        CompressDebugSections = llvm::DebugCompressionType::GNU;
+      } else if (ga != "none") {
+        errs() << progname_ << ": error: Invalid -Wa,--compress-debug-sections"
+               << " argument '" << ga << "'\n";
+      }
+    }
+  }
+
+  // Ensure MCAsmInfo initialization occurs before any use, otherwise sections
+  // may be created with a combination of default and explicit settings.
+  MAI->setCompressDebugSections(CompressDebugSections);
+
+  // FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
+  // MCObjectFileInfo needs a MCContext reference in order to initialize itself.
+  std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
+
+  MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
+
+  bool PIC = (driver_.getPicLevel() != PICLevel::NotPIC);
+  MOFI->InitMCObjectFileInfo(triple_, PIC, Ctx);
+  Ctx.setGenDwarfForAssembly(true);
+
+  // Use current dir (llvm-goc does not yet support -fdebug-compilation-dir)
+  SmallString<128> CWD;
+  if (!sys::fs::current_path(CWD))
+    Ctx.setCompilationDir(CWD);
+
+  // Honor -fdebug-prefix=... option.
+  for (const auto &arg : driver_.args().getAllArgValues(gollvm::options::OPT_fdebug_prefix_map_EQ)) {
+    std::pair<StringRef, StringRef> p = StringRef(arg).split('=');
+    Ctx.addDebugPrefixMapEntry(std::string(p.first), std::string(p.second));
+  }
+
+
+  StringRef BaseName = llvm::sys::path::filename(inputFileName_);
+  Ctx.setMainFileName(BaseName);
+  // FIXME: incorporate version here?
+  Ctx.setDwarfDebugProducer("llvm-goc");
+
+  // Build up the feature string from the target feature list.
+  std::string FS;
+  std::string CPU;
+  std::unique_ptr<MCStreamer> Str;
+  std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
+  std::unique_ptr<MCSubtargetInfo> STI(
+      TheTarget->createMCSubtargetInfo(Trip, CPU, FS));
+
+  raw_pwrite_stream *Out = objout_.get();
+  std::unique_ptr<buffer_ostream> BOS;
+
+  if (!objout_->supportsSeeking()) {
+    BOS = std::make_unique<buffer_ostream>(*objout_);
+    Out = BOS.get();
+  }
+
+  std::unique_ptr<MCCodeEmitter> CE(
+      TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
+  std::unique_ptr<MCAsmBackend> MAB(
+      TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
+  std::unique_ptr<MCObjectWriter> OW = MAB->createObjectWriter(*Out);
+
+  Triple T(driver_.triple());
+  unsigned RelaxAll = 0;
+  unsigned IncrementalLinkerCompatible = 0;
+  Str.reset(TheTarget->createMCObjectStreamer(
+      T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
+      RelaxAll, IncrementalLinkerCompatible,
+        /*DWARFMustBeAtTheEnd*/ true));
+
+  bool NoExecStack = true;
+  Str.get()->InitSections(NoExecStack);
+
+  // Assembly to object compilation should leverage assembly info.
+  Str->setUseAssemblerInfoForParsing(true);
+
+  std::unique_ptr<MCAsmParser> Parser(
+      createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
+
+  std::unique_ptr<MCTargetAsmParser> TAP(
+      TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
+  if (!TAP) {
+    errs() << progname_ << ": error: unknown triple"
+           << triple_.str() << "\n";
+    return false;
+  }
+
+  // FIXME: add support for -Wa,-defsym here?
+
+  Parser->setTargetParser(*TAP.get());
+  bool NoInitialTextSection = false;
+  auto Failed = Parser->Run(NoInitialTextSection);
+
+  // Close Streamer first.
+  // It might have a reference to the output stream.
+  Str.reset();
+  // Close the output stream early.
+  BOS.reset();
+  objout_.reset();
+
+  // Delete output file if there were errors.
+  if (Failed) {
+    if (objOutFileName_ != "-")
+      sys::fs::remove(objOutFileName_);
+  }
+
+  return !Failed;
+}
+
+bool IntegAssemblerImpl::performAction(Compilation &compilation,
+                                  const Action &jobAction,
+                                  const ArtifactList &inputArtifacts,
+                                  const Artifact &output)
+{
+  if (ia_.emitMinusVOrHashHashHash(triple_, output, jobAction))
+    return true;
+
+  // Resolve input/output files.
+  if (!resolveInputOutput(jobAction, inputArtifacts, output))
+    return false;
+
+  // Invoke back end
+  if (!invokeAssembler())
+    return false;
+
+  return true;
+}
+
+//........................................................................
+
+IntegAssembler::IntegAssembler(ToolChain &tc, const std::string &executablePath)
+    : InternalTool("integassembler", tc, executablePath),
+      impl_(new IntegAssemblerImpl(*this, tc, executablePath))
+{
+}
+
+IntegAssembler::~IntegAssembler()
+{
+}
+
+bool IntegAssembler::performAction(Compilation &compilation,
+                              const Action &jobAction,
+                              const ArtifactList &inputArtifacts,
+                              const Artifact &output)
+{
+  return impl_->performAction(compilation, jobAction, inputArtifacts, output);
+}
+
+
+} // end namespace driver
+} // end namespace gollvm
diff --git a/driver/IntegAssembler.h b/driver/IntegAssembler.h
new file mode 100644
index 0000000..7c27778
--- /dev/null
+++ b/driver/IntegAssembler.h
@@ -0,0 +1,49 @@
+//===-- IntegAssembler.h --------------------------------------------------===//
+//
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines the IntegAssembler class (helper for driver functionality).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GOLLVM_DRIVER_INTEGASSEMBLER_H
+#define GOLLVM_DRIVER_INTEGASSEMBLER_H
+
+#include "Tool.h"
+
+namespace gollvm {
+namespace driver {
+
+class ToolChain;
+class Compilation;
+class Action;
+class Artifact;
+class IntegAssemblerImpl;
+
+// Integrated assembler tool. This tool is used by the driver to carry
+// out "assemble" actions when -fintegrated-as is in effect, e.g. "compile
+// this assembly file down to an object".
+
+class IntegAssembler : public InternalTool {
+ public:
+  IntegAssembler(ToolChain &tc, const std::string &executablePath);
+  ~IntegAssembler();
+
+  // Perform compilation.
+  bool performAction(Compilation &compilation,
+                     const Action &jobAction,
+                     const ArtifactList &inputArtifacts,
+                     const Artifact &output) override;
+
+ private:
+  std::unique_ptr<IntegAssemblerImpl> impl_;
+};
+
+} // end namespace driver
+} // end namespace gollvm
+
+#endif // GOLLVM_DRIVER_INTEGASSEMBLER_H
diff --git a/driver/LinuxToolChain.cpp b/driver/LinuxToolChain.cpp
index 5d3a6cc..22a6120 100644
--- a/driver/LinuxToolChain.cpp
+++ b/driver/LinuxToolChain.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/raw_ostream.h"
 
 #include "CompileGo.h"
+#include "IntegAssembler.h"
 #include "Driver.h"
 #include "GnuTools.h"
 #include "Tool.h"
@@ -97,7 +98,10 @@
 
 Tool *Linux::buildAssembler()
 {
-  return new gnutools::Assembler(*this);
+  if (driver().useIntegratedAssembler())
+    return new IntegAssembler(*this, driver().executablePath());
+  else
+    return new gnutools::Assembler(*this);
 }
 
 Tool *Linux::buildLinker()
diff --git a/driver/ToolChain.cpp b/driver/ToolChain.cpp
index 6f72baa..7a6624d 100644
--- a/driver/ToolChain.cpp
+++ b/driver/ToolChain.cpp
@@ -65,6 +65,7 @@
   assert(act != nullptr);
   switch(act->type()) {
     case Action::A_Compile:
+    case Action::A_CompileAndAssemble:
       return getCompiler();
     case Action::A_Assemble:
       return getAssembler();
diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt
index fb9c3ab..cac798a 100644
--- a/unittests/CMakeLists.txt
+++ b/unittests/CMakeLists.txt
@@ -26,4 +26,5 @@
 include_directories(${GOFRONTEND_SOURCE_DIR})
 
 add_subdirectory(DriverUtils)
+add_subdirectory(Driver)
 add_subdirectory(BackendCore)
diff --git a/unittests/Driver/CMakeLists.txt b/unittests/Driver/CMakeLists.txt
new file mode 100644
index 0000000..0fc07e4
--- /dev/null
+++ b/unittests/Driver/CMakeLists.txt
@@ -0,0 +1,37 @@
+
+set(LLVM_LINK_COMPONENTS
+  DriverUtils
+  CppGoFrontEnd
+  CppGoPasses
+  ${LLVM_TARGETS_TO_BUILD}
+  CodeGen
+  Core
+  IRReader
+  MC
+  Support
+  Target
+  Object
+  Option
+  Passes
+  Support)
+
+set(DriverTestSources
+  DriverTests.cpp)
+
+add_gobackend_unittest(DriverTests
+  ${DriverTestSources})
+
+set(driver_src_dir "${GOLLVM_SOURCE_DIR}/driver")
+
+include_directories(${unittest_testutils_src})
+include_directories(${driver_src_dir})
+include_directories("${gollvm_binroot}/driver")
+
+# Record the fact that this unit test depends on these libs
+add_dependencies(DriverTests libmpfr libmpc libgmp)
+
+target_link_libraries(DriverTests
+  PRIVATE
+  GoUnitTestUtils
+  "-L${EXTLIBDIR}" "-Wl,--push-state" "-Wl,-Bstatic" "-lmpc" "-lmpfr" "-lgmp" "-Wl,--pop-state"
+  )
diff --git a/unittests/Driver/DriverTests.cpp b/unittests/Driver/DriverTests.cpp
new file mode 100644
index 0000000..9eedcb7
--- /dev/null
+++ b/unittests/Driver/DriverTests.cpp
@@ -0,0 +1,247 @@
+//===---- DriverTests.cpp -------------------------------------------------===//
+//
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+//
+//===----------------------------------------------------------------------===//
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <stdarg.h>
+
+#include "Driver.h"
+#include "Compilation.h"
+
+#include "gtest/gtest.h"
+
+#include "DiffUtils.h"
+
+using namespace llvm;
+using namespace gollvm::driver;
+using namespace goBackendUnitTests;
+
+namespace {
+
+inline std::vector<const char *> A(const char *arg, ...) {
+  va_list ap;
+  va_start(ap, arg);
+  std::vector<const char *> result;
+  while(arg != nullptr) {
+    result.push_back(arg);
+    arg = va_arg(ap, const char *);
+  }
+  return result;
+}
+
+class DrvTestHarness {
+ public:
+  explicit DrvTestHarness(const std::vector<const char *> args)
+      : args_(args) { }
+
+  // Returns non-zero on error, zero for success.
+  unsigned Perform();
+
+  // Test that a dump of driver actions matches the expeced result.
+  bool expectActions(const ExpectedDump &ed);
+
+ private:
+  const std::vector<const char *> args_;
+  std::string actionsDump_;
+};
+
+unsigned DrvTestHarness::Perform()
+{
+  std::unique_ptr<opt::OptTable> opts =
+      gollvm::options::createGollvmDriverOptTable();
+  unsigned missingArgIndex, missingArgCount;
+  ArrayRef<const char *> argvv = makeArrayRef(args_);
+  opt::InputArgList args =
+      opts->ParseArgs(argvv, missingArgIndex, missingArgCount);
+
+  // Complain about missing arguments.
+  if (missingArgIndex != 0) {
+    errs() << "error: argument to '"
+           << args.getArgString(missingArgIndex)
+           << "' option missing, expected "
+           << missingArgCount << " value(s)\n";
+    return 1;
+  }
+
+  // Look for unknown options.
+  for (const opt::Arg *arg : args.filtered(gollvm::options::OPT_UNKNOWN)) {
+    errs() << "error: unrecognized command line option '"
+             << arg->getAsString(args) << "'\n";
+    return 2;
+  }
+
+  // Create driver.
+  Driver driver(args, opts.get(), "llvm-goc", true);
+  driver.setUnitTesting();
+
+  // Set up driver, select target and toolchain.
+  ToolChain *toolchain = driver.setup();
+  if (toolchain == nullptr)
+    return 1;
+
+  // Build compilation; construct actions for this compile.
+  std::unique_ptr<Compilation> compilation =
+      driver.buildCompilation(*toolchain);
+  if (!driver.buildActions(*compilation))
+    return 2;
+  if (!driver.processActions(*compilation))
+    return 3;
+  actionsDump_ = driver.dumpActions(*compilation);
+  return 0;
+}
+
+bool DrvTestHarness::expectActions(const ExpectedDump &ed)
+{
+  const std::string &expected = ed.content;
+  std::string reason;
+  auto actual = actionsDump_;
+  bool equal = difftokens(expected, actual, reason);
+  if (! equal)
+    complainOnNequal(reason, ed, actual);
+  return equal;
+}
+
+TEST(DriverTests, SimpleCompile) {
+  DrvTestHarness h(A("-c", "foo.go", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile+assemble
+      inputs:
+        inputfile Artifact arg(foo.go)
+      output:
+        Artifact file(foo.o)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, NoIntegAsmCompile) {
+  DrvTestHarness h(A("-c", "-fno-integrated-as", "foo.go", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile
+      inputs:
+        inputfile Artifact arg(foo.go)
+      output:
+        Artifact file(/tmp/out.compile.0)
+    Action assemble
+      inputs:
+        compile
+      output:
+        Artifact file(foo.o)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, CompileToAsm) {
+  DrvTestHarness h(A("-S", "foo.go", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile
+      inputs:
+        inputfile Artifact arg(foo.go)
+      output:
+        Artifact file(foo.s)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, CompileToLllvmBitcode) {
+  DrvTestHarness h(A("-emit-llvm", "-c", "foo.go", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile
+      inputs:
+        inputfile Artifact arg(foo.go)
+      output:
+        Artifact file(foo.bc)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, CompileToLllvmIRAsm) {
+  DrvTestHarness h(A("-emit-llvm", "-S", "foo.go", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile
+      inputs:
+        inputfile Artifact arg(foo.go)
+      output:
+        Artifact file(foo.ll)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, AssembleAction) {
+  DrvTestHarness h(A("-c", "foo.s", "-o", "blah.o", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action assemble
+      inputs:
+        inputfile Artifact arg(foo.s)
+      output:
+        Artifact arg(blah.o)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+TEST(DriverTests, CompileAndLinkAction) {
+  DrvTestHarness h(A("foo1.go", "foo2.go", "-o", "foo.exe", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action compile+assemble
+      inputs:
+        inputfile Artifact arg(foo1.go)
+        inputfile Artifact arg(foo2.go)
+      output:
+        Artifact file(/tmp/out.compile+assemble.0)
+    Action link
+      inputs:
+        compile+assemble
+      output:
+        Artifact arg(foo.exe)
+  )RAW_RESULT");
+
+  unsigned res = h.Perform();
+  ASSERT_TRUE(res == 0 && "Setup failed");
+
+  bool isOK = h.expectActions(exp);
+  EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
+}
+
+} // namespace