gollvm: support reading from stdin (command line flag "-")

Add support for reading Go source code from stdin if the "-" command
line flag is specified. For compatibility with gccgo, support the
limited use case of "-x c -" provided that standard input is actually
empty (the Go tool invokes gccgo in some instances passing "-x c" when
detecting whether a given flag is supported).

Change-Id: I3a78eae62a90a79482d52856551e891e0301e541
Reviewed-on: https://go-review.googlesource.com/112365
Reviewed-by: Ian Lance Taylor <iant@golang.org>
diff --git a/driver-main/llvm-goc.cpp b/driver-main/llvm-goc.cpp
index 7fc6ef2..e788c0f 100644
--- a/driver-main/llvm-goc.cpp
+++ b/driver-main/llvm-goc.cpp
@@ -147,10 +147,17 @@
     llvm::cl::ParseCommandLineOptions(nargs + 1, args.get());
   }
 
-  // Vett the -x option if present. At the moment only "-x go" is accepted
-  // (assembler not allowed, but it could conceivably be added later).
+  // HACK: the go tool likes to invoke the C and Go compiler drivers
+  // at various points to detect whether a given command line flag is
+  // supported (ex: "gcc -x c - -someflag < /dev/null"), and tends to
+  // pass "-x c" even when the driver is gccgo. Gccgo is (mirabile
+  // dictu) a functional C compiler, but gollvm is not. For the time
+  // being we will "fake it" by allowing "-x ... -" but then requiring
+  // that standard input be empty (so as to support the Go tool, but
+  // not act as a general-purposes C compiler).
   opt::Arg *xarg = args_.getLastArg(gollvm::options::OPT_x);
   if (xarg != nullptr &&
+      ! llvm::StringRef(xarg->getValue()).equals("c") &&
       ! llvm::StringRef(xarg->getValue()).equals("go")) {
     errs() << progname << ": invalid argument '"
            << xarg->getValue() << "' to '"
diff --git a/driver/Action.cpp b/driver/Action.cpp
index 2b9bb21..48e8176 100644
--- a/driver/Action.cpp
+++ b/driver/Action.cpp
@@ -22,7 +22,8 @@
 const char *Action::getName() const
 {
   switch (type_) {
-    case A_Input: return "inputfile";
+    case A_ReadStdin: return "readstdin";
+    case A_InputFile: return "inputfile";
     case A_Compile: return "compile";
     case A_Assemble: return "assemble";
     case A_Link: return "link";
@@ -36,7 +37,11 @@
 const char *Action::resultFileSuffix() const
 {
   switch (type_) {
-    case A_Input: return "i";
+    case A_ReadStdin: {
+      const ReadStdinAction *rsia = this->castToReadStdinAction();
+      return rsia->suffix();
+    }
+    case A_InputFile: return "i";
     case A_Compile: return "s";
     case A_Assemble: return "o";
     case A_Link: return "e";
diff --git a/driver/Action.h b/driver/Action.h
index 78f5327..cd92535 100644
--- a/driver/Action.h
+++ b/driver/Action.h
@@ -22,6 +22,7 @@
 class Action;
 class Artifact;
 class InputAction;
+class ReadStdinAction;
 
 // Action lists contain pointers to actions owned by a Compilation.
 typedef llvm::SmallVector<Action*, 3> ActionList;
@@ -38,7 +39,8 @@
 
   // Type of action
   enum Type {
-    A_Input,
+    A_InputFile,
+    A_ReadStdin,
     A_Compile,
     A_Assemble,
     A_Link
@@ -55,6 +57,8 @@
 
   inline InputAction *castToInputAction();
   inline const InputAction *castToInputAction() const;
+  inline ReadStdinAction *castToReadStdinAction();
+  inline const ReadStdinAction *castToReadStdinAction() const;
 
   const char *getName() const;
   const char *resultFileSuffix() const;
@@ -72,18 +76,43 @@
 class InputAction : public Action {
  public:
   explicit InputAction(Artifact *input)
-      : Action(Action::A_Input),
+      : Action(Action::A_InputFile),
         input_(input) { }
   Artifact *input() const { return input_; }
  private:
   Artifact *input_;
 };
 
-inline InputAction *Action::castToInputAction() {
-  return type_ == A_Input ? static_cast<InputAction*>(this) : nullptr;
+// An input action that corresponds to the reading stdin.
+
+class ReadStdinAction : public Action {
+ public:
+  explicit ReadStdinAction(const char *suffix)
+      : Action(Action::A_ReadStdin),
+        suffix_(suffix) { }
+  const char *suffix() const { return suffix_; }
+ private:
+  const char *suffix_;
+};
+
+inline InputAction *Action::castToInputAction()
+{
+  return type_ == A_InputFile ? static_cast<InputAction*>(this) : nullptr;
 }
-inline const InputAction *Action::castToInputAction() const {
-  return type_ == A_Input ? static_cast<const InputAction*>(this) : nullptr;
+
+inline const InputAction *Action::castToInputAction() const
+{
+  return type_ == A_InputFile ? static_cast<const InputAction*>(this) : nullptr;
+}
+
+inline ReadStdinAction *Action::castToReadStdinAction()
+{
+  return type_ == A_ReadStdin ? static_cast<ReadStdinAction*>(this) : nullptr;
+}
+
+inline const ReadStdinAction *Action::castToReadStdinAction() const
+{
+  return type_ == A_ReadStdin ? static_cast<const ReadStdinAction*>(this) : nullptr;
 }
 
 } // end namespace driver
diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt
index d1b13bd..5e5da98 100644
--- a/driver/CMakeLists.txt
+++ b/driver/CMakeLists.txt
@@ -30,6 +30,7 @@
   GnuTools.cpp
   GollvmOptions.cpp
   LinuxToolChain.cpp
+  ReadStdin.cpp
   Tool.cpp
   ToolChain.cpp
   DEPENDS
diff --git a/driver/Driver.cpp b/driver/Driver.cpp
index 9f92274..63d2e43 100644
--- a/driver/Driver.cpp
+++ b/driver/Driver.cpp
@@ -288,9 +288,23 @@
                                 Compilation &compilation)
 {
   for (auto ifarg : ifargs) {
-    InputAction *ia = new InputAction(compilation.newArgArtifact(ifarg));
-    compilation.recordAction(ia);
-    result.push_back(ia);
+    bool schedAction = false;
+    Action *act = nullptr;
+    if (!strcmp(ifarg->getValue(), "-")) {
+      opt::Arg *xarg = args_.getLastArg(gollvm::options::OPT_x);
+      assert(xarg);
+      const char *suf =
+          (llvm::StringRef(xarg->getValue()).equals("c") ? "c" :
+           (llvm::StringRef(xarg->getValue()).equals("go") ? "go" : "?"));
+      act = new ReadStdinAction(suf);
+      schedAction = true;
+    } else {
+      act = new InputAction(compilation.newArgArtifact(ifarg));
+    }
+    compilation.recordAction(act);
+    if (schedAction)
+      compilation.addAction(act);
+    result.push_back(act);
   }
 }
 
@@ -422,7 +436,7 @@
     // 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);
+    assert(it == nullptr || input->castToReadStdinAction() != nullptr);
     auto it = artmap_.find(input);
     assert(it != artmap_.end());
     result.push_back(it->second);
diff --git a/driver/ReadStdin.cpp b/driver/ReadStdin.cpp
new file mode 100644
index 0000000..7be44cb
--- /dev/null
+++ b/driver/ReadStdin.cpp
@@ -0,0 +1,78 @@
+//===-- ReadStdin.cpp -----------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Gollvm driver helper class "ReadStdin" methods.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ReadStdin.h"
+
+#include "Compilation.h"
+#include "Driver.h"
+
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace gollvm {
+namespace driver {
+
+
+ReadStdin::ReadStdin(ToolChain &tc, bool mustBeEmpty)
+    : InternalTool("stdinreader", tc),
+      mustBeEmpty_(mustBeEmpty)
+{
+}
+
+ReadStdin::~ReadStdin()
+{
+}
+
+bool ReadStdin::performAction(Compilation &compilation,
+                              const Action &jobAction,
+                              const ArtifactList &inputArtifacts,
+                              const Artifact &output)
+{
+  assert(inputArtifacts.empty());
+
+  // Read in standard input, the LLVM way...
+  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> stdinBufferOrErr =
+      llvm::MemoryBuffer::getSTDIN();
+  if (std::error_code errc = stdinBufferOrErr.getError()) {
+    llvm::errs() << compilation.driver().progname()
+                 << ": error reading stdin: " << errc.message();
+    return false;
+  }
+  std::unique_ptr<llvm::MemoryBuffer> stdinBuf =
+      std::move(stdinBufferOrErr.get());
+
+  // Enforce the "must be empty" requirement if appropriate.
+  if (mustBeEmpty_ && stdinBuf->getBufferSize() != 0) {
+    llvm::errs() << compilation.driver().progname()
+                 << ": unsupported language for -x, "
+                 << "stdin be empty\n";
+    return false;
+  }
+
+  // Emit to the output artifact.
+  std::error_code errc;
+  llvm::raw_fd_ostream ostr(output.file(), errc,
+                            llvm::sys::fs::OpenFlags::F_None);
+  if (errc) {
+    llvm::errs() << compilation.driver().progname()
+                 << ": cannot open " << output.file() << " for writing: "
+                 << errc.message();
+    return false;
+  }
+
+  ostr.write(stdinBuf->getBufferStart(), stdinBuf->getBufferSize());
+  return true;
+}
+
+} // end namespace driver
+} // end namespace gollvm
diff --git a/driver/ReadStdin.h b/driver/ReadStdin.h
new file mode 100644
index 0000000..c31cbe0
--- /dev/null
+++ b/driver/ReadStdin.h
@@ -0,0 +1,54 @@
+//===-- ReadStdin.h -------------------------------------------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Defines the ReadStdin class (helper for driver functionality).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GOLLVM_DRIVER_READSTDIN_H
+#define GOLLVM_DRIVER_READSTDIN_H
+
+#include "Tool.h"
+
+namespace gollvm {
+namespace driver {
+
+class ToolChain;
+class Compilation;
+class Action;
+class Artifact;
+
+// This class encapsulates the reading of standard input during a compilation
+// that includes the pseudo-input flag "-". The driver handles this case
+// by creating a tool (ReadStdin) that consumes standard input and emits
+// a temporary file, which is then piped through the remainder of the compiler
+// as usual.  See also the notes in the command parsing code relating to
+// handling of and "-x c".
+
+class ReadStdin : public InternalTool {
+ public:
+
+  // If mustBeEmpty is set, then issue an error if stdin has any contents.
+  ReadStdin(ToolChain &tc, bool mustBeEmpty);
+  ~ReadStdin();
+
+  // Perform action (reading stdin).
+  bool performAction(Compilation &compilation,
+                     const Action &jobAction,
+                     const ArtifactList &inputArtifacts,
+                     const Artifact &output) override;
+
+ private:
+  bool mustBeEmpty_;
+};
+
+} // end namespace driver
+} // end namespace gollvm
+
+#endif // GOLLVM_DRIVER_READSTDIN_H
diff --git a/driver/ToolChain.cpp b/driver/ToolChain.cpp
index ba8e805..48ebdd8 100644
--- a/driver/ToolChain.cpp
+++ b/driver/ToolChain.cpp
@@ -17,6 +17,7 @@
 #include "Driver.h"
 #include "Tool.h"
 #include "ToolChain.h"
+#include "ReadStdin.h"
 
 namespace gollvm {
 namespace driver {
@@ -53,6 +54,13 @@
   return linker_.get();
 }
 
+Tool *ToolChain::getStdinReader(bool mustBeEmpty)
+{
+  if (stdinReader_.get() == nullptr)
+    stdinReader_.reset(new ReadStdin(*this, mustBeEmpty));
+  return stdinReader_.get();
+}
+
 Tool *ToolChain::getTool(Action *act)
 {
   assert(act != nullptr);
@@ -63,6 +71,10 @@
       return getAssembler();
     case Action::A_Link:
       return getLinker();
+    case Action::A_ReadStdin: {
+      ReadStdinAction *rsia = act->castToReadStdinAction();
+      return getStdinReader(strcmp(rsia->suffix(), "go"));
+    }
     default:
       assert(false);
       return nullptr;
diff --git a/driver/ToolChain.h b/driver/ToolChain.h
index ffd83e7..689796c 100644
--- a/driver/ToolChain.h
+++ b/driver/ToolChain.h
@@ -79,6 +79,7 @@
   std::unique_ptr<Tool> compiler_;
   std::unique_ptr<Tool> assembler_;
   std::unique_ptr<Tool> linker_;
+  std::unique_ptr<Tool> stdinReader_;
 
   // List of places to look for tools (ld, as, etc)
   pathlist programPaths_;
@@ -86,6 +87,8 @@
   // List of places to look for object files (ex: crt0.o)
   pathlist filePaths_;
 
+  Tool *getStdinReader(bool mustBeEmpty);
+
   ToolChain &thisToolChain() {
     ToolChain *tc = const_cast<ToolChain*>(this);
     return *tc;