r298098 - [clang-cl] Fix cross-compilation with MSVC 2017.

Zachary Turner via cfe-commits cfe-commits at lists.llvm.org
Fri Mar 17 09:24:35 PDT 2017


Author: zturner
Date: Fri Mar 17 11:24:34 2017
New Revision: 298098

URL: http://llvm.org/viewvc/llvm-project?rev=298098&view=rev
Log:
[clang-cl] Fix cross-compilation with MSVC 2017.

clang-cl works best when the user runs vcvarsall to set up
an environment before running, but even this is not enough
on VC 2017 when cross compiling (e.g. using an x64 toolchain
to target x86, or vice versa).

The reason is that although clang-cl itself will have a
valid environment, it will shell out to other tools (such
as link.exe) which may not.  Generally we solve this through
adding the appropriate linker flags, but this is not enough
in VC 2017.

The cross-linker and the regular linker both link against
some common DLLs, but these DLLs live in the binary directory
of the native linker.  When setting up a cross-compilation
environment through vcvarsall, it will add *both* directories
to %PATH%, so that when cl shells out to any of the associated
tools, those tools will be able to find all of the dependencies
that it links against.  If you don't do this, link.exe will
fail to run because the loader won't be able to find all of
the required DLLs that it links against.

To solve this we teach the driver how to spawn a process with
an explicitly specified environment.  Then we modify the
PATH before shelling out to subtools and run with the modified
PATH.

Patch by Hamza Sood
Differential Revision: https://reviews.llvm.org/D30991

Modified:
    cfe/trunk/include/clang/Driver/Job.h
    cfe/trunk/lib/Driver/Job.cpp
    cfe/trunk/lib/Driver/ToolChains/MSVC.cpp

Modified: cfe/trunk/include/clang/Driver/Job.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Driver/Job.h?rev=298098&r1=298097&r2=298098&view=diff
==============================================================================
--- cfe/trunk/include/clang/Driver/Job.h (original)
+++ cfe/trunk/include/clang/Driver/Job.h Fri Mar 17 11:24:34 2017
@@ -11,6 +11,7 @@
 #define LLVM_CLANG_DRIVER_JOB_H
 
 #include "clang/Basic/LLVM.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/iterator.h"
 #include "llvm/Option/Option.h"
@@ -69,6 +70,9 @@ class Command {
   /// file
   std::string ResponseFileFlag;
 
+  /// See Command::setEnvironment
+  std::vector<const char *> Environment;
+
   /// When a response file is needed, we try to put most arguments in an
   /// exclusive file, while others remains as regular command line arguments.
   /// This functions fills a vector with the regular command line arguments,
@@ -111,6 +115,12 @@ public:
     InputFileList = std::move(List);
   }
 
+  /// \brief Sets the environment to be used by the new process.
+  /// \param NewEnvironment An array of environment variables.
+  /// \remark If the environment remains unset, then the environment
+  ///         from the parent process will be used.
+  void setEnvironment(llvm::ArrayRef<const char *> NewEnvironment);
+
   const char *getExecutable() const { return Executable; }
 
   const llvm::opt::ArgStringList &getArguments() const { return Arguments; }

Modified: cfe/trunk/lib/Driver/Job.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Driver/Job.cpp?rev=298098&r1=298097&r2=298098&view=diff
==============================================================================
--- cfe/trunk/lib/Driver/Job.cpp (original)
+++ cfe/trunk/lib/Driver/Job.cpp Fri Mar 17 11:24:34 2017
@@ -301,19 +301,33 @@ void Command::setResponseFile(const char
   ResponseFileFlag += FileName;
 }
 
+void Command::setEnvironment(llvm::ArrayRef<const char *> NewEnvironment) {
+  Environment.reserve(NewEnvironment.size() + 1);
+  Environment.assign(NewEnvironment.begin(), NewEnvironment.end());
+  Environment.push_back(nullptr);
+}
+
 int Command::Execute(const StringRef **Redirects, std::string *ErrMsg,
                      bool *ExecutionFailed) const {
   SmallVector<const char*, 128> Argv;
 
+  const char **Envp;
+  if (Environment.empty()) {
+    Envp = nullptr;
+  } else {
+    assert(Environment.back() == nullptr &&
+           "Environment vector should be null-terminated by now");
+    Envp = const_cast<const char **>(Environment.data());
+  }
+
   if (ResponseFile == nullptr) {
     Argv.push_back(Executable);
     Argv.append(Arguments.begin(), Arguments.end());
     Argv.push_back(nullptr);
 
-    return llvm::sys::ExecuteAndWait(Executable, Argv.data(), /*env*/ nullptr,
-                                     Redirects, /*secondsToWait*/ 0,
-                                     /*memoryLimit*/ 0, ErrMsg,
-                                     ExecutionFailed);
+    return llvm::sys::ExecuteAndWait(
+        Executable, Argv.data(), Envp, Redirects, /*secondsToWait*/ 0,
+        /*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
   }
 
   // We need to put arguments in a response file (command is too large)
@@ -337,8 +351,8 @@ int Command::Execute(const StringRef **R
     return -1;
   }
 
-  return llvm::sys::ExecuteAndWait(Executable, Argv.data(), /*env*/ nullptr,
-                                   Redirects, /*secondsToWait*/ 0,
+  return llvm::sys::ExecuteAndWait(Executable, Argv.data(), Envp, Redirects,
+                                   /*secondsToWait*/ 0,
                                    /*memoryLimit*/ 0, ErrMsg, ExecutionFailed);
 }
 

Modified: cfe/trunk/lib/Driver/ToolChains/MSVC.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Driver/ToolChains/MSVC.cpp?rev=298098&r1=298097&r2=298098&view=diff
==============================================================================
--- cfe/trunk/lib/Driver/ToolChains/MSVC.cpp (original)
+++ cfe/trunk/lib/Driver/ToolChains/MSVC.cpp Fri Mar 17 11:24:34 2017
@@ -446,6 +446,8 @@ void visualstudio::Linker::ConstructJob(
 
   TC.addProfileRTLibs(Args, CmdArgs);
 
+  std::vector<const char *> Environment;
+
   // We need to special case some linker paths.  In the case of lld, we need to
   // translate 'lld' into 'lld-link', and in the case of the regular msvc
   // linker, we need to use a special search algorithm.
@@ -459,14 +461,77 @@ void visualstudio::Linker::ConstructJob(
     // from the program PATH, because other environments like GnuWin32 install
     // their own link.exe which may come first.
     linkPath = FindVisualStudioExecutable(TC, "link.exe");
+
+#ifdef USE_WIN32
+    // When cross-compiling with VS2017 or newer, link.exe expects to have
+    // its containing bin directory at the top of PATH, followed by the
+    // native target bin directory.
+    // e.g. when compiling for x86 on an x64 host, PATH should start with:
+    // /bin/HostX64/x86;/bin/HostX64/x64
+    if (TC.getIsVS2017OrNewer() &&
+        llvm::Triple(llvm::sys::getProcessTriple()).getArch() != TC.getArch()) {
+      auto HostArch = llvm::Triple(llvm::sys::getProcessTriple()).getArch();
+
+      auto EnvBlockWide =
+          std::unique_ptr<wchar_t[], decltype(&FreeEnvironmentStringsW)>(
+              GetEnvironmentStringsW(), FreeEnvironmentStringsW);
+      if (!EnvBlockWide)
+        goto SkipSettingEnvironment;
+
+      size_t EnvCount = 0;
+      size_t EnvBlockLen = 0;
+      while (EnvBlockWide[EnvBlockLen] != L'\0') {
+        ++EnvCount;
+        EnvBlockLen += std::wcslen(&EnvBlockWide[EnvBlockLen]) +
+                       1 /*string null-terminator*/;
+      }
+      ++EnvBlockLen; // add the block null-terminator
+
+      std::string EnvBlock;
+      if (!llvm::convertUTF16ToUTF8String(
+              llvm::ArrayRef<char>(reinterpret_cast<char *>(EnvBlockWide.get()),
+                                   EnvBlockLen * sizeof(EnvBlockWide[0])),
+              EnvBlock))
+        goto SkipSettingEnvironment;
+
+      Environment.reserve(EnvCount);
+
+      // Now loop over each string in the block and copy them into the
+      // environment vector, adjusting the PATH variable as needed when we
+      // find it.
+      for (const char *Cursor = EnvBlock.data(); *Cursor != '\0';) {
+        llvm::StringRef EnvVar(Cursor);
+        if (EnvVar.startswith_lower("path=")) {
+          using SubDirectoryType = toolchains::MSVCToolChain::SubDirectoryType;
+          constexpr size_t PrefixLen = 5; // strlen("path=")
+          Environment.push_back(Args.MakeArgString(
+              EnvVar.substr(0, PrefixLen) +
+              TC.getSubDirectoryPath(SubDirectoryType::Bin) +
+              llvm::Twine(llvm::sys::EnvPathSeparator) +
+              TC.getSubDirectoryPath(SubDirectoryType::Bin, HostArch) +
+              (EnvVar.size() > PrefixLen
+                   ? llvm::Twine(llvm::sys::EnvPathSeparator) +
+                         EnvVar.substr(PrefixLen)
+                   : "")));
+        } else {
+          Environment.push_back(Args.MakeArgString(EnvVar));
+        }
+        Cursor += EnvVar.size() + 1 /*null-terminator*/;
+      }
+    }
+  SkipSettingEnvironment:;
+#endif
   } else {
     linkPath = Linker;
     llvm::sys::path::replace_extension(linkPath, "exe");
     linkPath = TC.GetProgramPath(linkPath.c_str());
   }
 
-  const char *Exec = Args.MakeArgString(linkPath);
-  C.addCommand(llvm::make_unique<Command>(JA, *this, Exec, CmdArgs, Inputs));
+  auto LinkCmd = llvm::make_unique<Command>(
+      JA, *this, Args.MakeArgString(linkPath), CmdArgs, Inputs);
+  if (!Environment.empty())
+    LinkCmd->setEnvironment(Environment);
+  C.addCommand(std::move(LinkCmd));
 }
 
 void visualstudio::Compiler::ConstructJob(Compilation &C, const JobAction &JA,




More information about the cfe-commits mailing list