From 31801a78220872a1dcee9b261328ce7239eaa7e9 Mon Sep 17 00:00:00 2001 From: Zachary Turner Date: Fri, 17 Mar 2017 16:24:34 +0000 Subject: [PATCH] [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 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@298098 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Driver/Job.h | 10 +++++ lib/Driver/Job.cpp | 26 ++++++++++--- lib/Driver/ToolChains/MSVC.cpp | 69 +++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/include/clang/Driver/Job.h b/include/clang/Driver/Job.h index 54bed09e0a..ff88256b8c 100644 --- a/include/clang/Driver/Job.h +++ b/include/clang/Driver/Job.h @@ -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 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 NewEnvironment); + const char *getExecutable() const { return Executable; } const llvm::opt::ArgStringList &getArguments() const { return Arguments; } diff --git a/lib/Driver/Job.cpp b/lib/Driver/Job.cpp index 9fd8808af3..7a4d055159 100644 --- a/lib/Driver/Job.cpp +++ b/lib/Driver/Job.cpp @@ -301,19 +301,33 @@ void Command::setResponseFile(const char *FileName) { ResponseFileFlag += FileName; } +void Command::setEnvironment(llvm::ArrayRef 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 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(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 **Redirects, std::string *ErrMsg, 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); } diff --git a/lib/Driver/ToolChains/MSVC.cpp b/lib/Driver/ToolChains/MSVC.cpp index 3d7c1d76ae..a09304814c 100644 --- a/lib/Driver/ToolChains/MSVC.cpp +++ b/lib/Driver/ToolChains/MSVC.cpp @@ -446,6 +446,8 @@ void visualstudio::Linker::ConstructJob(Compilation &C, const JobAction &JA, TC.addProfileRTLibs(Args, CmdArgs); + std::vector 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(Compilation &C, const JobAction &JA, // 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( + 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(reinterpret_cast(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(JA, *this, Exec, CmdArgs, Inputs)); + auto LinkCmd = llvm::make_unique( + 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, -- 2.40.0