gollvm: add support for "dummy" C compilation

When the Go command kicks off a build with gccgo/gollvm, it does a
couple of test compiles to see whether the compiler supports specific
command line flags; this is done to enable building with older
versions of gccgo/gollvm that may not support newer flags such as
"-fgo-importcfg" or "-ffile-prefix-map". The test compiles are done
using a sequence of this form:

   <compiler> <flag_to_be_tested> -c -x c - -o /dev/null < /dev/null

Specific example:

  gccgo -fgo-importcfg=/dev/null -c -x c - -o /dev/null < /dev/null

An empty input file is used, and output is redirected to /dev/null,
but also (crucially) the Go command passes "-x c", meaning "the input
language I want you to compile is C, not Go". This mode of compilation
is supported by gccgo, but not gollvm (gollvm doesn't have an embedded
C compiler). As a result, all such checks currently fail with gollvm.

This patch works around this problem by relaxing gollvm's restrictions
on the use of "-x c". Specifically, the option is accepted and a dummy
compilation is kicked off, but the input is required to be zero sized,
and no output is generated. This allows the test compiles used by the
Go command to do the right thing (e.g. check to see if a given command
line flag is supported/accepted).

Updates golang/go#45880.

Change-Id: I5c45681949926b28e9a1df4754dec37da4d2c9f8
Reviewed-on: https://go-review.googlesource.com/c/gollvm/+/455216
Reviewed-by: Cherry Mui <cherryyz@google.com>
diff --git a/driver/CMakeLists.txt b/driver/CMakeLists.txt
index 62b457a..3609bc3 100644
--- a/driver/CMakeLists.txt
+++ b/driver/CMakeLists.txt
@@ -33,6 +33,7 @@
   CompileGo.cpp
   Distro.cpp
   Driver.cpp
+  DummyCompileC.cpp
   GccUtils.cpp
   GnuTools.cpp
   GollvmOptions.cpp
diff --git a/driver/DummyCompileC.cpp b/driver/DummyCompileC.cpp
new file mode 100644
index 0000000..db43136
--- /dev/null
+++ b/driver/DummyCompileC.cpp
@@ -0,0 +1,46 @@
+//===-- DummyCompileC.cpp -------------------------------------------------===//
+//
+// Copyright 2022 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 "DummyCompileC" methods.
+//
+//===----------------------------------------------------------------------===//
+
+#include "DummyCompileC.h"
+
+#include "Action.h"
+#include "ArchCpuSetup.h"
+#include "Artifact.h"
+#include "Driver.h"
+#include "ToolChain.h"
+#include <iostream>
+
+//......................................................................
+
+namespace gollvm {
+namespace driver {
+
+DummyCompileC::DummyCompileC(ToolChain &tc, const std::string &executablePath)
+    : InternalTool("dummyCcompiler", tc, executablePath)
+{
+}
+
+DummyCompileC::~DummyCompileC()
+{
+}
+
+bool DummyCompileC::performAction(Compilation &compilation,
+                                  const Action &jobAction,
+                                  const ArtifactList &inputArtifacts,
+                                  const Artifact &output)
+{
+  std::cout << "no support for C compilation\n";
+  return true;
+}
+
+} // end namespace driver
+} // end namespace gollvm
diff --git a/driver/DummyCompileC.h b/driver/DummyCompileC.h
new file mode 100644
index 0000000..747d4bf
--- /dev/null
+++ b/driver/DummyCompileC.h
@@ -0,0 +1,46 @@
+//===-- DummyCompileC.h ----------------------------------------------------===//
+//
+// Copyright 2022 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 DummyCompileC class (helper for driver functionality).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef GOLLVM_DRIVER_DUMMYCOMPILEC_H
+#define GOLLVM_DRIVER_DUMMYCOMPILEC_H
+
+#include "Tool.h"
+
+namespace gollvm {
+namespace driver {
+
+class ToolChain;
+class Compilation;
+class Action;
+class Artifact;
+
+// Concrete (dummy) C compiler tool. This tool is used by the driver to carry
+// out "compile" actions when "-x c" is passed, e.g. "compile this set of C
+// files into assembly". Since gollvm can't compile C, we require
+// that the input file be empty, and don't produce any output.
+
+class DummyCompileC : public InternalTool {
+ public:
+  DummyCompileC(ToolChain &tc, const std::string &executablePath);
+  ~DummyCompileC();
+
+  // Perform compilation.
+  bool performAction(Compilation &compilation,
+                     const Action &jobAction,
+                     const ArtifactList &inputArtifacts,
+                     const Artifact &output) override;
+};
+
+} // end namespace driver
+} // end namespace gollvm
+
+#endif // GOLLVM_DRIVER_DUMMYCOMPILEC_H
diff --git a/driver/LinuxToolChain.cpp b/driver/LinuxToolChain.cpp
index bebad8c..a12b794 100644
--- a/driver/LinuxToolChain.cpp
+++ b/driver/LinuxToolChain.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/raw_ostream.h"
 
 #include "CompileGo.h"
+#include "DummyCompileC.h"
 #include "IntegAssembler.h"
 #include "Driver.h"
 #include "GnuTools.h"
@@ -99,6 +100,11 @@
 
 Tool *Linux::buildCompiler()
 {
+  llvm::opt::Arg *xarg = driver().args().getLastArg(gollvm::options::OPT_x);
+  if (xarg != nullptr && llvm::StringRef(xarg->getValue()).equals("c")) {
+    // This is a dummy C compile.
+    return new DummyCompileC(*this, driver().executablePath());
+  }
   return new CompileGo(*this, driver().executablePath());
 }
 
diff --git a/driver/ReadStdin.cpp b/driver/ReadStdin.cpp
index c5f0baa..b7b9223 100644
--- a/driver/ReadStdin.cpp
+++ b/driver/ReadStdin.cpp
@@ -55,7 +55,7 @@
   if (mustBeEmpty_ && stdinBuf->getBufferSize() != 0) {
     llvm::errs() << compilation.driver().progname()
                  << ": unsupported language for -x, "
-                 << "stdin be empty\n";
+                 << "stdin must be empty\n";
     return false;
   }
 
diff --git a/unittests/Driver/DriverTests.cpp b/unittests/Driver/DriverTests.cpp
index 9eedcb7..80ddcc2 100644
--- a/unittests/Driver/DriverTests.cpp
+++ b/unittests/Driver/DriverTests.cpp
@@ -43,7 +43,7 @@
   // Returns non-zero on error, zero for success.
   unsigned Perform();
 
-  // Test that a dump of driver actions matches the expeced result.
+  // Test that a dump of driver actions matches the expected result.
   bool expectActions(const ExpectedDump &ed);
 
  private:
@@ -244,4 +244,27 @@
   EXPECT_TRUE(isOK && "Actions dump does not have expected contents");
 }
 
+TEST(DriverTests, DummyCompileCAction) {
+  DrvTestHarness h(A("-c", "-x", "c", "-", "-o", "/dev/null", nullptr));
+
+  DECLARE_EXPECTED_OUTPUT(exp, R"RAW_RESULT(
+    Action readstdin
+      inputs:
+      output:
+        Artifact file(/tmp/out.readstdin.0)
+    Action compile+assemble
+      inputs:
+        readstdin
+      output:
+        Artifact arg(/dev/null)
+  )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