gollvm: roll out new driver functionality for assembly phase

Transition over to using the new helper classes (Driver, Compilation,
etc) for the assembly portion of the compilation. The main compile (Go
to assembly) is still being done as before. There are some temporary
hacks needed in the way that output Artifacts are created; these will
go away in a subsequent patch.

Change-Id: I9aae0e7ee1846286029f3181111ed451fe369aeb
Reviewed-on: https://go-review.googlesource.com/110621
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/driver-main/llvm-goc.cpp b/driver-main/llvm-goc.cpp
index 98f559c..2856c8f 100644
--- a/driver-main/llvm-goc.cpp
+++ b/driver-main/llvm-goc.cpp
@@ -68,6 +68,7 @@
 #include <unistd.h>
 
 using namespace llvm;
+using namespace gollvm::driver;
 
 class CommandLineParser {
  public:
@@ -223,6 +224,9 @@
   // Exit code to return if there was an error in one of the steps above.
   int errorReturnCode() const { return errorReturnCode_; }
 
+  // Temporary: return asm output file.
+  const std::string &asmOutFile() const { return asmOutFileName_; }
+
  private:
   Triple triple_;
   llvm::LLVMContext context_;
@@ -1050,9 +1054,29 @@
   if (! orchestrator.invokeBackEnd())
     return orchestrator.errorReturnCode();
 
-  // Invoke assembler if needed.
-  if (! orchestrator.invokeAssembler())
-    return orchestrator.errorReturnCode();
+  // Create driver.
+  Driver driver(clp.args(), opts.get(), argv[0]);
+
+  // 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, orchestrator.asmOutFile()))
+    return 2;
+
+  // Process the action list. This will carry out actions that don't
+  // require use of an external tool, and will generate a list of
+  // commands for invoking external tools.
+  if (!driver.processActions(*compilation))
+    return 3;
+
+  // Execute the external-tool command list created above.
+  if (! compilation->executeCommands())
+    return 4;
 
   // We're done.
   return 0;
diff --git a/driver/Artifact.cpp b/driver/Artifact.cpp
index d9e00f8..27a5695 100644
--- a/driver/Artifact.cpp
+++ b/driver/Artifact.cpp
@@ -28,6 +28,11 @@
           u.arg->getValue() : u.file);
 }
 
+llvm::opt::Arg *Artifact::arg()
+{
+  return (type_ == A_Argument ? u.arg : nullptr);
+}
+
 std::string Artifact::toString()
 {
   std::stringstream ss;
diff --git a/driver/Artifact.h b/driver/Artifact.h
index 094f831..eed4415 100644
--- a/driver/Artifact.h
+++ b/driver/Artifact.h
@@ -55,6 +55,9 @@
   // File for input
   const char *file() const;
 
+  // Return input argument if type is A_Argument, null otherwise.
+  llvm::opt::Arg *arg();
+
   // Debugging
   std::string toString();
   void dump();
diff --git a/driver/Command.cpp b/driver/Command.cpp
index de67211..b59977f 100644
--- a/driver/Command.cpp
+++ b/driver/Command.cpp
@@ -41,12 +41,13 @@
                                    errMsg);
 }
 
-void Command::print(llvm::raw_ostream &os)
+void Command::print(llvm::raw_ostream &os, bool quoteArgs)
 {
-  os << executable_;
+  os << " " << executable_;
+  const char *qu = (quoteArgs ? "\"" : "");
   for (auto arg : arguments_)
     if (arg != nullptr)
-      os << " "  << arg;
+      os << " "  << qu << arg << qu;
   os << "\n";
 }
 
diff --git a/driver/Command.h b/driver/Command.h
index d51b0e9..e1a2864 100644
--- a/driver/Command.h
+++ b/driver/Command.h
@@ -49,7 +49,7 @@
   int execute(std::string *errMsg);
 
   // Print to string
-  void print(llvm::raw_ostream &OS);
+  void print(llvm::raw_ostream &OS, bool quoteArgs);
 
   // Print for debugging
   void dbgPrint();
diff --git a/driver/Compilation.cpp b/driver/Compilation.cpp
index 04c1d29..8731419 100644
--- a/driver/Compilation.cpp
+++ b/driver/Compilation.cpp
@@ -68,26 +68,66 @@
 
 Artifact *Compilation::newArgArtifact(llvm::opt::Arg *arg)
 {
-  // to be implemented in a later patch
-  return nullptr;
+  ownedArtifacts_.push_back(std::unique_ptr<Artifact>(new Artifact(arg)));
+  return ownedArtifacts_.back().get();
 }
 
-Artifact *Compilation::newFileArtifact(const char *tempfilename)
+Artifact *Compilation::newFileArtifact(const char *path, bool isTempFile)
 {
-  // to be implemented in a later patch
-  return nullptr;
+  paths_.push_back(std::string(path));
+  const char *fn = paths_.back().c_str();
+  if (isTempFile)
+    tempFileNames_.push_back(fn);
+  ownedArtifacts_.push_back(std::unique_ptr<Artifact>(new Artifact(fn)));
+  return ownedArtifacts_.back().get();
 }
 
 Artifact *Compilation::createOutputFileArtifact(Action *act)
 {
-  // to be implemented in a later patch
-  return nullptr;
+  llvm::opt::InputArgList &args = driver_.args();
+
+  // Honor -o if present
+  llvm::opt::Arg *outf = args.getLastArg(gollvm::options::OPT_o);
+  if (outf)
+    return newArgArtifact(outf);
+
+  // No -o: construct output file name.
+  std::string ofn;
+  if (act->type() == Action::A_Link) {
+    assert(! args.hasArg(gollvm::options::OPT_emit_llvm));
+    ofn = "a.out";
+  } else {
+    ofn = firstFileBase();
+    if (args.hasArg(gollvm::options::OPT_emit_llvm)) {
+      if (args.hasArg(gollvm::options::OPT_S))
+        ofn += ".ll";
+      else
+        ofn += ".bc";
+    } else {
+      if (args.hasArg(gollvm::options::OPT_S))
+        ofn += ".s";
+      else
+        ofn += ".o";
+    }
+  }
+
+  outFileName_ = ofn;
+  return newFileArtifact(ofn.c_str(), false);
 }
 
 llvm::Optional<Artifact*> Compilation::createTemporaryFileArtifact(Action *act)
 {
-  // to be implemented in a later patch
-  return llvm::None;
+  llvm::SmallString<128> tempFileName;
+  std::error_code tfcEC =
+      llvm::sys::fs::createTemporaryFile(act->getName(),
+                                         act->resultFileSuffix(),
+                                         tempFileName);
+  if (tfcEC) {
+    llvm::errs() << driver_.progname() << ": error: "
+                 << tfcEC.message() << "\n";
+    return llvm::None;
+  }
+  return newFileArtifact(tempFileName.c_str(), true);
 }
 
 void Compilation::addCommand(const Action &srcAction,
@@ -104,8 +144,29 @@
 
 bool Compilation::executeCommands()
 {
-  // to be implemented in a later patch
-  return false;
+  llvm::opt::ArgList &args = driver().args();
+
+  bool hashHashHash = args.hasArg(gollvm::options::OPT__HASH_HASH_HASH);
+  for (auto cmd : commands_) {
+
+    // Support -v and/or -###
+    if (hashHashHash || args.hasArg(gollvm::options::OPT_v))
+      cmd->print(llvm::errs(), hashHashHash);
+
+    // Support -###
+    if (hashHashHash)
+      continue;
+
+    // Execute.
+    std::string errMsg;
+    int rc = cmd->execute(&errMsg);
+    if (rc != 0) {
+      llvm::errs() << errMsg << "\n";
+      return false;
+    }
+  }
+
+  return true;
 }
 
 } // end namespace driver
diff --git a/driver/Compilation.h b/driver/Compilation.h
index aff398e..b7af173 100644
--- a/driver/Compilation.h
+++ b/driver/Compilation.h
@@ -43,13 +43,23 @@
   // Return is true for success, false for error;
   bool executeCommands();
 
-  // Create a temp file and return as artifact.
+  // Generate a new temp file and return an artifact for it. Here
+  // llvm::Optional is used in case the temp file creation fails for
+  // some reason.
   llvm::Optional<Artifact*> createTemporaryFileArtifact(Action *act);
 
+    // Create new artifact based on file name. If 'isTempfile' is set,
+  // the file should be scheduled for deletion after compilation finishes.
+  Artifact *newFileArtifact(const char *path, bool isTempFile);
+
   // Return an artifact corresponding to the proper output file (depends
   // 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);
+
   // Toolchain, driver
   ToolChain &toolchain() { return toolchain_; }
   Driver &driver() { return driver_; }
@@ -98,10 +108,8 @@
   llvm::SmallVector<std::unique_ptr<Action>, 8> ownedActions_;
   llvm::SmallVector<std::unique_ptr<Artifact>, 8> ownedArtifacts_;
   llvm::SmallVector<std::unique_ptr<Command>, 8> ownedCommands_;
-  llvm::SmallVector<std::string, 8> tempFileNames_;
-
-  // Create new artifact based on temp file.
-  Artifact *newFileArtifact(const char *tempfilename);
+  llvm::SmallVector<const char *, 8> tempFileNames_;
+  llvm::SmallVector<std::string, 8> paths_;
 };
 
 } // end namespace driver
diff --git a/driver/Driver.cpp b/driver/Driver.cpp
index 00b0d63..71901af 100644
--- a/driver/Driver.cpp
+++ b/driver/Driver.cpp
@@ -16,8 +16,9 @@
 #include "llvm/Support/Program.h"
 
 #include "Action.h"
-#include "Driver.h"
 #include "Compilation.h"
+#include "Driver.h"
+#include "LinuxToolChain.h"
 #include "ToolChain.h"
 
 using namespace llvm;
@@ -40,6 +41,16 @@
 {
 }
 
+// TODO: create a mechanism for capturing release tag/branch, and/or
+// git/svn revision for LLVM, gollvm, and so on.
+
+void Driver::emitVersion()
+{
+  // NB: the go build tool keys off the presence of the "experimental"
+  // keyword (hashes compiler binary if detected).
+  llvm::errs() << "gollvm version 1 (experimental)\n";
+}
+
 std::string Driver::getFilePath(llvm::StringRef name,
                                 ToolChain &toolchain)
 {
@@ -51,9 +62,32 @@
 std::string Driver::getProgramPath(llvm::StringRef name,
                                    ToolChain &toolchain)
 {
-  // to be implemented in a later patch
-  assert(false);
-  return "";
+  // TODO: add support for -Bprefix option.
+
+  // Include target-prefixed name in search.
+  SmallVector<std::string, 2> candidates;
+  candidates.push_back((triple_.str() + "-" + name).str());
+  candidates.push_back(name);
+
+  // Examine toolchain program paths.
+  for (auto &dir : toolchain.programPaths()) {
+    for (auto &cand : candidates) {
+      llvm::SmallString<256> candidate(dir);
+      llvm::sys::path::append(candidate, cand);
+      if (llvm::sys::fs::can_execute(llvm::Twine(candidate)))
+        return candidate.str();
+    }
+  }
+
+  // Search path.
+  for (auto &cand : candidates) {
+    llvm::ErrorOr<std::string> pcand =
+      llvm::sys::findProgramByName(cand);
+    if (pcand)
+      return *pcand;
+  }
+
+  return name;
 }
 
 // FIXME: some  platforms have PIE enabled by default; we don't
@@ -154,29 +188,275 @@
 
 ToolChain *Driver::setup()
 {
-  // to be implemented in a later patch
-  return nullptr;
+   bool inputseen = false;
+
+  if (args_.hasArg(gollvm::options::OPT_v) ||
+      args_.hasArg(gollvm::options::OPT__HASH_HASH_HASH))
+    emitVersion();
+
+  // Check for existence of input files.
+  for (opt::Arg *arg : args_) {
+    if (arg->getOption().getKind() == opt::Option::InputClass) {
+
+      // Special case for "-" (always assumed to exist)
+      if (strcmp(arg->getValue(), "-")) {
+        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;
+        }
+      }
+      inputseen = true;
+    }
+  }
+
+  // Issue an error if no inputs.
+  if (! inputseen) {
+    errs() << progname_ << ": error: no inputs\n";
+    return nullptr;
+  }
+
+  // Set triple.
+  if (const opt::Arg *arg = args_.getLastArg(gollvm::options::OPT_target_EQ))
+    triple_ = Triple(Triple::normalize(arg->getValue()));
+  else
+    triple_ = Triple(sys::getDefaultTargetTriple());
+
+  // Support -march
+  std::string archStr;
+  opt::Arg *archarg = args_.getLastArg(gollvm::options::OPT_march_EQ);
+  if (archarg != nullptr) {
+    std::string val(archarg->getValue());
+    if (val == "native")
+      archStr = sys::getHostCPUName();
+    else
+      archStr = archarg->getValue();
+    triple_.setArchName(archStr);
+  }
+
+  // Look up toolchain.
+  auto &tc = toolchains_[triple_.str()];
+  if (!tc) {
+    switch (triple_.getOS()) {
+      case Triple::Linux:
+        tc = make_unique<toolchains::Linux>(*this, triple_);
+        break;
+      default:
+        errs() << progname_ << ": error: unsupported target "
+               << triple_.str() << ", unable to create toolchain\n";
+        return nullptr;
+    }
+  }
+
+  // FIXME: add code to weed out unknown architectures (ex:
+  // SomethingWeird-unknown-linux-gnu).
+
+  return tc.get();
 }
 
 ActionList Driver::createInputActions(const inarglist &ifargs,
                                       Compilation &compilation)
 {
-  // to be implemented in a later patch
-  return ActionList();
+  ActionList result;
+  for (auto ifarg : ifargs) {
+    InputAction *ia = new InputAction(compilation.newArgArtifact(ifarg));
+    compilation.recordAction(ia);
+    result.push_back(ia);
+  }
+  return result;
 }
 
-bool Driver::buildActions(Compilation &compilation)
+bool Driver::buildActions(Compilation &compilation,
+                          const std::string &asmOutFile)
 {
-  // to be implemented in a later patch
-  assert(false);
-  return false;
+  inarglist gofiles;
+  inarglist asmfiles;
+  inarglist linkerinputs;
+
+  // Examine inputs to see what sort of actions we need.
+  for (opt::Arg *arg : args_) {
+    if (arg->getOption().getKind() == opt::Option::InputClass) {
+      std::string fn(arg->getValue());
+
+      size_t dotindex = fn.find_last_of(".");
+      if (dotindex != std::string::npos) {
+        std::string extension = fn.substr(dotindex);
+        if (extension.compare(".s") == 0) {
+          asmfiles.push_back(arg);
+          continue;
+        } else if (extension.compare(".go") == 0) {
+          gofiles.push_back(arg);
+          continue;
+        } else if (extension.compare(".S") == 0) {
+          errs() << progname_ << ": warning: " << arg->getValue()
+                 << ": .S files (preprocessed assembler) not supported; "
+                 << "treating as linker input.\n";
+        }
+      }
+
+      // everything else assumed to be a linker input
+      linkerinputs.push_back(arg);
+      continue;
+    }
+  }
+
+  bool OPT_c = args_.hasArg(gollvm::options::OPT_c);
+  bool OPT_S = args_.hasArg(gollvm::options::OPT_S);
+  ActionList linkerInputs;
+
+  // For -c/-S compiles, a mix of Go and assembly currently not allowed.
+  if ((OPT_c || OPT_S) && !gofiles.empty() && !asmfiles.empty()) {
+    errs() << progname_ << ": error: cannot specify mix of "
+           << "Go and assembly inputs with -c/-S\n";
+    return false;
+  }
+
+  // Handle Go compilation action if needed.
+  if (!gofiles.empty()) {
+
+    // Build a list of input actions for the go files.
+    ActionList inacts = createInputActions(gofiles, compilation);
+
+    // Create action
+    Action *gocompact =
+        new Action(Action::A_Compile, inacts);
+    compilation.recordAction(gocompact);
+    compilation.addAction(gocompact);
+
+    // Temporary: at this point compilation is still being done
+    // by CompilationOrchestrator, so create a dummy output artifact
+    // corresponding to the asm output file.
+    artmap_[gocompact] = compilation.newFileArtifact(asmOutFile.c_str(), false);
+
+    // Schedule assemble action now if no -S.
+    if (!OPT_S) {
+      // Create action
+      Action *asmact =
+          new Action(Action::A_Assemble, gocompact);
+      compilation.recordAction(asmact);
+      compilation.addAction(asmact);
+      if (!OPT_c)
+        linkerInputs.push_back(asmact);
+    }
+  }
+
+  // Create actions to assemble any asm files appearing on the cmd line.
+  if (gofiles.empty() && !asmfiles.empty()) {
+
+    // Issue an error if -c in combination with multiple files.
+    if (OPT_c && asmfiles.size() > 1) {
+      errs() << progname_ << ": error: cannot specify multiple inputs "
+             << "in combination with -c\n";
+      return false;
+    }
+
+    for (auto asmf : asmfiles) {
+
+      // Input action.
+      InputAction *ia = new InputAction(compilation.newArgArtifact(asmf));
+      compilation.recordAction(ia);
+
+      // Assemble action.
+      Action *asmact =
+          new Action(Action::A_Assemble, ia);
+      compilation.recordAction(asmact);
+      compilation.addAction(asmact);
+      if (!OPT_c)
+        linkerInputs.push_back(asmact);
+    }
+  }
+
+  // If -S or -c, we are done at this point.
+  if (OPT_c || OPT_S)
+    return true;
+
+  // Create a linker action.
+  Action *linkact =
+          new Action(Action::A_Link, linkerInputs);
+  compilation.recordAction(linkact);
+  compilation.addAction(linkact);
+
+  return true;
+}
+
+ArtifactList Driver::collectInputArtifacts(Action *act, InternalTool *it)
+{
+  ArtifactList result;
+  for (auto &input : act->inputs()) {
+    InputAction *inact = input->castToInputAction();
+    if (inact != nullptr) {
+      result.push_back(inact->input());
+      continue;
+    }
+    // It is an error if an internal-tool action is receiving a result
+    // from an external tool (in the current model all internal-tool actions
+    // have to take place before any external-tool actions).
+    assert(it == nullptr);
+    auto it = artmap_.find(input);
+    assert(it != artmap_.end());
+    result.push_back(it->second);
+  }
+  return result;
+}
+
+bool Driver::processAction(Action *act, Compilation &compilation, bool lastAct)
+{
+  // Temporary: Go compilation has already been performed.
+  if (act->type() == Action::A_Compile)
+    return true;
+
+  // 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;
+  } else {
+    result = compilation.createOutputFileArtifact(act);
+  }
+
+  // Select tool to process the action.
+  Tool *tool = compilation.toolchain().getTool(act);
+  assert(tool != nullptr);
+  InternalTool *it = tool->castToInternalTool();
+
+  // Collect input artifacts for this
+  ArtifactList actionInputs = collectInputArtifacts(act, it);
+
+  // If internal tool, perform now.
+  if (it != nullptr) {
+    if (! it->performAction(compilation, *act, actionInputs, *result))
+      return false;
+    return true;
+  }
+
+  // External tool: build command
+  ExternalTool *et = tool->castToExternalTool();
+  if (! et->constructCommand(compilation, *act, actionInputs, *result))
+    return false;
+
+  return true;
 }
 
 bool Driver::processActions(Compilation &compilation)
 {
-  // to be implemented in a later patch
-  assert(false);
-  return false;
+  SmallVector<Action*, 8> actv;
+  for (Action *action : compilation.actions())
+    actv.push_back(action);
+
+  for (unsigned ai = 0; ai < actv.size(); ++ai) {
+    Action *act = actv[ai];
+    bool lastAction = (ai == actv.size()-1);
+    if (!processAction(act, compilation, lastAction))
+      return false;
+  }
+
+  return true;
 }
 
 } // end namespace driver
diff --git a/driver/Driver.h b/driver/Driver.h
index efa2ad5..938d442 100644
--- a/driver/Driver.h
+++ b/driver/Driver.h
@@ -35,6 +35,7 @@
 namespace driver {
 
 class Compilation;
+class InternalTool;
 class ToolChain;
 
 // Driver class. Drives the process of translating a given command
@@ -55,7 +56,9 @@
   std::unique_ptr<Compilation> buildCompilation(ToolChain &tc);
 
   // Build actions for compilation. Returns false if error.
-  bool buildActions(Compilation &compilation);
+  // TEMPORARY: pass in asm out file from compiled Go.
+  bool buildActions(Compilation &compilation,
+                    const std::string &asmOutFile);
 
   // Process the action list. This means:
   // - execute any non-dependent actions that don't require the
@@ -111,6 +114,10 @@
   llvm::StringMap<std::unique_ptr<ToolChain>> toolchains_;
   // Maps non-input actions to output artifacts.
   std::unordered_map<Action *, Artifact*> artmap_;
+
+  bool processAction(Action *act, Compilation &compilation, bool lastAct);
+  ArtifactList collectInputArtifacts(Action *act, InternalTool *it);
+  void emitVersion();
 };
 
 template<typename IT>
diff --git a/driver/GnuTools.cpp b/driver/GnuTools.cpp
index 19c47ca..ea38bce 100644
--- a/driver/GnuTools.cpp
+++ b/driver/GnuTools.cpp
@@ -20,10 +20,63 @@
 #include "llvm/Option/ArgList.h"
 #include "llvm/Support/Path.h"
 
+#include <set>
+
 using namespace gollvm::driver;
 
 namespace gnutools {
 
+// This helper routine is used for constructing linker and
+// assembler command lines. It combines input arguments with
+// any escape-oriented command line arguments via "-Wl,..." or
+// equivalent. For example, consider the following command line:
+//
+//    llvm-goc -L /somepath foo.go -o qux \
+//        -Wl,--whole-archive mumble.a -Wl,--no-whole-archive blah.a
+//
+// This will result in three linker inputs (foo.o, mumble.a, and blah.a).
+// Here in order to get the semantics we have to properly interleave
+// the inputs with the flags, e.g.
+//
+//    ld -o qux <...> foo.o --whole-archive mumble.a --no-whole-archive blah.a
+//
+// This helper routine walks through the command line arguments and picks
+// out the corresponding "escaped" arguments and mixes them in with
+// any args that appear in the input list.
+
+static void combineInputsWithEscapes(gollvm::options::ID escape1,
+                                     gollvm::options::ID escape2,
+                                     const ArtifactList &inputArtifacts,
+                                     llvm::opt::ArgList &args,
+                                     llvm::opt::ArgStringList &cmdArgs)
+{
+  // Collect the args mentioned in the input artifacts.
+  std::set<llvm::opt::Arg *> argset;
+  for (auto &inart : inputArtifacts) {
+    if (inart->type() == Artifact::A_Argument)
+      argset.insert(inart->arg());
+    else
+      cmdArgs.push_back(inart->file());
+  }
+
+  // Walk the args to sort things out.
+  for (auto arg : args) {
+
+    // If this is an arg that is part of the input set, append it now.
+    if (arg->getOption().getKind() == llvm::opt::Option::InputClass &&
+        argset.find(arg) != argset.end()) {
+      cmdArgs.push_back(arg->getValue());
+      continue;
+    }
+
+    // If this matches one of our escape options, then add its value(s) now.
+    if (arg->getOption().matches(escape1) ||
+        arg->getOption().matches(escape2))
+      for (auto &av : arg->getValues())
+        cmdArgs.push_back(av);
+  }
+}
+
 Assembler::Assembler(gollvm::driver::ToolChain &tc)
     : ExternalTool("gnu-assembler", tc)
 {
@@ -34,8 +87,59 @@
                                  const ArtifactList &inputArtifacts,
                                  const Artifact &output)
 {
-  // implementation to appear in a subsequent patch
-  return false;
+  llvm::opt::ArgList &args = toolchain().driver().args();
+  llvm::opt::ArgStringList cmdArgs;
+
+  // Executable path.
+  const char *executable =
+      args.MakeArgString(toolchain().getProgramPath("as"));
+  if (! executable) {
+    llvm::errs() << "error: unable to locate path for 'as'\n";
+    return false;
+  }
+  cmdArgs.push_back(executable);
+
+  // Add correct 32/64 option.
+  switch (toolchain().driver().triple().getArch()) {
+    case llvm::Triple::x86:
+      cmdArgs.push_back("--32");
+      break;
+    case llvm::Triple::x86_64:
+      // NB: no GNUX32 support yet
+      cmdArgs.push_back("--64");
+      break;
+    default:
+      break;
+  }
+
+  // Output file.
+  cmdArgs.push_back("-o");
+  cmdArgs.push_back(output.file());
+
+  // Incorporate inputs with -Wa,.. and -Xassembler args, in correct order.
+  combineInputsWithEscapes(gollvm::options::OPT_Wa_COMMA,
+                           gollvm::options::OPT_Xassembler,
+                           inputArtifacts, args, cmdArgs);
+
+  // Support for compressed debug.
+  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)) {
+      cmdArgs.push_back("-compress-debug-sections");
+    } else {
+      std::string cds("-compress-debug-sections=");
+      cds += gzarg->getValue();
+      cmdArgs.push_back(args.MakeArgString(cds));
+    }
+  }
+  cmdArgs.push_back(nullptr);
+
+  // Add final command.
+  compilation.addCommand(jobAction, *this,
+                         executable, cmdArgs);
+
+  return true;
 }
 
 Linker::Linker(gollvm::driver::ToolChain &tc)