Skip to content

Commit

Permalink
Adopt DMD's MSVC toolchain detection
Browse files Browse the repository at this point in the history
This reduces the overhead for auto-detecting and setting up an MSVC
toolchain from a very rough 1 second to about 8 milliseconds on my box.
Enabling the auto-detection by default (and so preferring MSVC over the
'internal' toolchain if there's a Visual C++ installation) is now
possible, fixing issues like ldc-developers#3402.

The MSVC setup now consists of the bare minimum - prepending 3
directories to the LIB env var and 1-2 directories to PATH.
  • Loading branch information
kinke committed Apr 29, 2020
1 parent 734d849 commit ed0629f
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 257 deletions.
5 changes: 0 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -932,11 +932,6 @@ if(${BUILD_SHARED})
endif()
install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.conf)

if(MSVC)
file(COPY vcbuild/ DESTINATION ${PROJECT_BINARY_DIR}/bin FILES_MATCHING PATTERN "*.bat")
install(DIRECTORY vcbuild/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin FILES_MATCHING PATTERN "*.bat")
endif()

if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
if(NOT DEFINED BASH_COMPLETION_COMPLETIONSDIR)
find_package(bash-completion QUIET)
Expand Down
5 changes: 2 additions & 3 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,11 @@ jobs:
displayName: Generate hello.d
- script: |
echo on
%ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b
%ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d
%ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -run hello.d || exit /b
%ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -m32 -run hello.d
displayName: Run 32/64-bit hello-world smoke test with internal toolchain
- script: |
echo on
set LDC_VSDIR_FORCE=1
%ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b
%ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d
displayName: Run 32/64-bit hello-world smoke test with MSVC auto-detection
Expand Down
93 changes: 78 additions & 15 deletions dmd/vsoptions.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ version (Windows):
import core.stdc.ctype;
import core.stdc.stdlib;
import core.stdc.string;
import core.stdc.wchar_;
import core.sys.windows.winbase;
import core.sys.windows.windef;
import core.sys.windows.winreg;
Expand All @@ -25,7 +26,16 @@ import dmd.root.filename;
import dmd.root.outbuffer;
import dmd.root.rmem;

struct VSOptions
version (IN_LLVM)
{
enum supportedPre2017Versions = ["14.0".ptr];
}
else
{
enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"];
}

extern(C++) struct VSOptions
{
// evaluated once at startup, reflecting the result of vcvarsall.bat
// from the current environment or the latest Visual Studio installation
Expand Down Expand Up @@ -238,14 +248,20 @@ private:
if (VSInstallDir is null)
VSInstallDir = getenv("VSINSTALLDIR");

version (IN_LLVM)
{
if (VSInstallDir is null)
VSInstallDir = getenv("LDC_VSDIR");
}

if (VSInstallDir is null)
VSInstallDir = detectVSInstallDirViaCOM();

if (VSInstallDir is null)
VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"); // VS2017

if (VSInstallDir is null)
foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"])
foreach (const(char)* ver; supportedPre2017Versions)
{
VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir");
if (VSInstallDir)
Expand All @@ -267,7 +283,7 @@ private:

// detect from registry (build tools?)
if (VCInstallDir is null)
foreach (const(char)* ver; ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"])
foreach (const(char)* ver; supportedPre2017Versions)
{
auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC");
VCInstallDir = GetRegistryString(regPath, "ProductDir");
Expand Down Expand Up @@ -311,6 +327,7 @@ private:
}
}

public:
/**
* get Visual C bin folder
* Params:
Expand All @@ -325,7 +342,7 @@ private:
* Note: differences for the linker binaries are small, they all
* allow cross compilation
*/
const(char)* getVCBinDir(bool x64, out const(char)* addpath)
const(char)* getVCBinDir(bool x64, out const(char)* addpath) const
{
static const(char)* linkExists(const(char)* p)
{
Expand Down Expand Up @@ -406,7 +423,7 @@ private:
* Returns:
* folder containing the the VC runtime libraries
*/
const(char)* getVCLibDir(bool x64)
const(char)* getVCLibDir(bool x64) const
{
if (VCToolsInstallDir !is null)
return FileName.combine(VCToolsInstallDir, x64 ? r"lib\x64" : r"lib\x86");
Expand All @@ -422,7 +439,7 @@ private:
* Returns:
* folder containing the universal CRT libraries
*/
const(char)* getUCRTLibPath(bool x64)
const(char)* getUCRTLibPath(bool x64) const
{
if (UCRTSdkDir && UCRTVersion)
return FileName.buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86");
Expand All @@ -436,7 +453,7 @@ private:
* Returns:
* folder containing the Windows SDK libraries
*/
const(char)* getSDKLibPath(bool x64)
const(char)* getSDKLibPath(bool x64) const
{
if (WindowsSdkDir)
{
Expand All @@ -455,13 +472,20 @@ private:
return sdk;
}

version (IN_LLVM) {}
else
{
// try mingw fallback relative to phobos library folder that's part of LIB
if (auto p = FileName.searchPath(getenv("LIB"), r"mingw\kernel32.lib"[], false))
return FileName.path(p).ptr;
}

return null;
}

private:
extern(D):

// iterate through subdirectories named by SDK version in baseDir and return the
// one with the largest version that also contains the test file
static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile)
Expand Down Expand Up @@ -500,7 +524,7 @@ private:
* Returns:
* the registry value if it exists and has string type
*/
const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName)
const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName) const
{
enum x64hive = false; // VS registry entries always in 32-bit hive

Expand All @@ -527,6 +551,7 @@ private:
char[260] buf = void;
DWORD cnt = buf.length * char.sizeof;
DWORD type;
// TODO: wide API
int hr = RegQueryValueExA(key, valueName, null, &type, cast(ubyte*) buf.ptr, &cnt);
if (hr == 0 && cnt > 0)
return buf.dup.ptr;
Expand Down Expand Up @@ -579,6 +604,8 @@ import core.sys.windows.oleauto : SysFreeString;
pragma(lib, "ole32.lib");
pragma(lib, "oleaut32.lib");

extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode);

interface ISetupInstance : IUnknown
{
// static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E");
Expand Down Expand Up @@ -635,18 +662,54 @@ const(char)* detectVSInstallDirViaCOM()
return null;
scope(exit) instances.Release();

BSTR versionString;
BSTR installDir;
scope(exit) SysFreeString(versionString);
scope(exit) SysFreeString(installDir);

while (instances.Next(1, &instance, &fetched) == S_OK && fetched)
{
BSTR bstrInstallDir;
if (instance.GetInstallationPath(&bstrInstallDir) != S_OK)
BSTR thisVersionString;
if (instance.GetInstallationVersion(&thisVersionString) != S_OK)
continue;
scope(exit) SysFreeString(thisVersionString);

BSTR thisInstallDir;
if (instance.GetInstallationPath(&thisInstallDir) != S_OK)
continue;
scope(exit) SysFreeString(thisInstallDir);

if (versionString && wcscmp(thisVersionString, versionString) <= 0)
continue; // not a newer version, skip

const installDirLength = wcslen(thisInstallDir);
const vcInstallDirLength = installDirLength + 4;
auto vcInstallDir = (cast(wchar*) mem.xmalloc_noscan(vcInstallDirLength * wchar.sizeof))[0 .. vcInstallDirLength];
scope(exit) mem.xfree(vcInstallDir.ptr);
vcInstallDir[0 .. installDirLength] = thisInstallDir[0 .. installDirLength];
vcInstallDir[installDirLength .. $] = "\\VC\0"w;
if (_waccess(vcInstallDir.ptr, 0) != 0)
continue; // Visual C++ not included, skip

char[260] path;
int len = WideCharToMultiByte(CP_UTF8, 0, bstrInstallDir, -1, path.ptr, 260, null, null);
SysFreeString(bstrInstallDir);
if (versionString)
{
SysFreeString(versionString);
SysFreeString(installDir);
}
versionString = thisVersionString;
installDir = thisInstallDir;
thisVersionString = null;
thisInstallDir = null;
}

if (len > 0)
return path[0..len].idup.ptr;
if (installDir)
{
char[260] path = void;
int len = WideCharToMultiByte(CP_UTF8, 0, installDir, -1, path.ptr, path.length, null, null);
assert(len);

return path[0 .. len].idup.ptr;
}

return null;
}
32 changes: 32 additions & 0 deletions dmd/vsoptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

/* Compiler implementation of the D programming language
* Copyright (C) 2009-2020 by The D Language Foundation, All Rights Reserved
* written by Walter Bright
* http://www.digitalmars.com
* Distributed under the Boost Software License, Version 1.0.
* http://www.boost.org/LICENSE_1_0.txt
* https://github.com/dlang/dmd/blob/master/src/dmd/vsoptions.h
*/

#pragma once

#ifdef _WIN32

struct VSOptions
{
const char *WindowsSdkDir = nullptr;
const char *WindowsSdkVersion = nullptr;
const char *UCRTSdkDir = nullptr;
const char *UCRTVersion = nullptr;
const char *VSInstallDir = nullptr;
const char *VCInstallDir = nullptr;
const char *VCToolsInstallDir = nullptr; // used by VS 2017+

void initialize();
const char *getVCBinDir(bool x64, const char *&addpath) const;
const char *getVCLibDir(bool x64) const;
const char *getUCRTLibPath(bool x64) const;
const char *getSDKLibPath(bool x64) const;
};

#endif // _WIN32
42 changes: 33 additions & 9 deletions driver/linker-msvc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//===----------------------------------------------------------------------===//

#include "dmd/errors.h"
#include "driver/args.h"
#include "driver/cl_options.h"
#include "driver/cl_options_instrumentation.h"
#include "driver/cl_options_sanitizers.h"
Expand All @@ -31,21 +32,27 @@

namespace {

void addMscrtLibs(std::vector<std::string> &args) {
const auto mscrtlibName = getMscrtLibName();
void addMscrtLibs(bool useInternalToolchain, std::vector<std::string> &args) {
const auto mscrtlibName = getMscrtLibName(&useInternalToolchain);

args.push_back(("/DEFAULTLIB:" + mscrtlibName).str());

// We need the vcruntime lib for druntime's exception handling (ldc.eh_msvc).
// Pick one of the 4 variants matching the selected main UCRT lib.

if (useInternalToolchain) {
#if LDC_LLVM_VER >= 400
if (mscrtlibName.contains_lower("vcruntime")) {
assert(mscrtlibName.contains_lower("vcruntime"));
#endif
return;
}
#endif

#if LDC_LLVM_VER >= 400
const bool isStatic = mscrtlibName.contains_lower("libcmt");
#else // LLVM 3.9: no llvm::StringRef::{contains,find}_lower
const bool isStatic = mscrtlibName.startswith_lower("libcmt");
#endif

const bool isDebug =
mscrtlibName.endswith_lower("d") || mscrtlibName.endswith_lower("d.lib");

Expand Down Expand Up @@ -85,12 +92,29 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
fatal();
}

const bool useInternalToolchain = useInternalToolchainForMSVC();

#ifdef _WIN32
windows::MsvcEnvironmentScope msvcEnv;
if (!useInternalToolchain)
msvcEnv.setup();

const auto begin = std::chrono::steady_clock::now();

const bool forceMSVC = env::has(L"LDC_VSDIR_FORCE");
const bool useInternalToolchain =
(!forceMSVC && getExplicitMscrtLibName().contains_lower("vcruntime")) ||
!msvcEnv.setup();

if (!useInternalToolchain && global.params.verbose) {
const auto end = std::chrono::steady_clock::now();
message("MSVC setup took %lld microseconds",
std::chrono::duration_cast<std::chrono::microseconds>(end - begin)
.count());
}

if (forceMSVC && useInternalToolchain) {
warning(Loc(), "no Visual C++ installation found for linking, falling back "
"to MinGW-based libraries");
}
#else
const bool useInternalToolchain = true;
#endif

// build arguments
Expand Down Expand Up @@ -121,7 +145,7 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
}

// add C runtime libs
addMscrtLibs(args);
addMscrtLibs(useInternalToolchain, args);

// specify creation of DLL
if (global.params.dll) {
Expand Down
Loading

0 comments on commit ed0629f

Please sign in to comment.