Skip to content

Commit b3d94ff

Browse files
committed
Adopt DMD's MSVC toolchain detection
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.
1 parent 734d849 commit b3d94ff

12 files changed

+244
-256
lines changed

CMakeLists.txt

-5
Original file line numberDiff line numberDiff line change
@@ -932,11 +932,6 @@ if(${BUILD_SHARED})
932932
endif()
933933
install(FILES ${PROJECT_BINARY_DIR}/bin/${LDC_EXE}_install.conf DESTINATION ${CONF_INST_DIR} RENAME ${LDC_EXE}.conf)
934934

935-
if(MSVC)
936-
file(COPY vcbuild/ DESTINATION ${PROJECT_BINARY_DIR}/bin FILES_MATCHING PATTERN "*.bat")
937-
install(DIRECTORY vcbuild/ DESTINATION ${CMAKE_INSTALL_PREFIX}/bin FILES_MATCHING PATTERN "*.bat")
938-
endif()
939-
940935
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
941936
if(NOT DEFINED BASH_COMPLETION_COMPLETIONSDIR)
942937
find_package(bash-completion QUIET)

azure-pipelines.yml

+2-3
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,11 @@ jobs:
9090
displayName: Generate hello.d
9191
- script: |
9292
echo on
93-
%ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b
94-
%ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d
93+
%ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -run hello.d || exit /b
94+
%ARTIFACT_NAME%\bin\ldc2 -v -mscrtlib=vcruntime140 -m32 -run hello.d
9595
displayName: Run 32/64-bit hello-world smoke test with internal toolchain
9696
- script: |
9797
echo on
98-
set LDC_VSDIR_FORCE=1
9998
%ARTIFACT_NAME%\bin\ldc2 -v -run hello.d || exit /b
10099
%ARTIFACT_NAME%\bin\ldc2 -v -m32 -run hello.d
101100
displayName: Run 32/64-bit hello-world smoke test with MSVC auto-detection

dmd/vsoptions.d

+78-15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ version (Windows):
1515
import core.stdc.ctype;
1616
import core.stdc.stdlib;
1717
import core.stdc.string;
18+
import core.stdc.wchar_;
1819
import core.sys.windows.winbase;
1920
import core.sys.windows.windef;
2021
import core.sys.windows.winreg;
@@ -25,7 +26,16 @@ import dmd.root.filename;
2526
import dmd.root.outbuffer;
2627
import dmd.root.rmem;
2728

28-
struct VSOptions
29+
version (IN_LLVM)
30+
{
31+
enum supportedPre2017Versions = ["14.0".ptr];
32+
}
33+
else
34+
{
35+
enum supportedPre2017Versions = ["14.0".ptr, "12.0", "11.0", "10.0", "9.0"];
36+
}
37+
38+
extern(C++) struct VSOptions
2939
{
3040
// evaluated once at startup, reflecting the result of vcvarsall.bat
3141
// from the current environment or the latest Visual Studio installation
@@ -238,14 +248,20 @@ private:
238248
if (VSInstallDir is null)
239249
VSInstallDir = getenv("VSINSTALLDIR");
240250

251+
version (IN_LLVM)
252+
{
253+
if (VSInstallDir is null)
254+
VSInstallDir = getenv("LDC_VSDIR");
255+
}
256+
241257
if (VSInstallDir is null)
242258
VSInstallDir = detectVSInstallDirViaCOM();
243259

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

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

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

330+
public:
314331
/**
315332
* get Visual C bin folder
316333
* Params:
@@ -325,7 +342,7 @@ private:
325342
* Note: differences for the linker binaries are small, they all
326343
* allow cross compilation
327344
*/
328-
const(char)* getVCBinDir(bool x64, out const(char)* addpath)
345+
const(char)* getVCBinDir(bool x64, out const(char)* addpath) const
329346
{
330347
static const(char)* linkExists(const(char)* p)
331348
{
@@ -406,7 +423,7 @@ private:
406423
* Returns:
407424
* folder containing the the VC runtime libraries
408425
*/
409-
const(char)* getVCLibDir(bool x64)
426+
const(char)* getVCLibDir(bool x64) const
410427
{
411428
if (VCToolsInstallDir !is null)
412429
return FileName.combine(VCToolsInstallDir, x64 ? r"lib\x64" : r"lib\x86");
@@ -422,7 +439,7 @@ private:
422439
* Returns:
423440
* folder containing the universal CRT libraries
424441
*/
425-
const(char)* getUCRTLibPath(bool x64)
442+
const(char)* getUCRTLibPath(bool x64) const
426443
{
427444
if (UCRTSdkDir && UCRTVersion)
428445
return FileName.buildPath(UCRTSdkDir, "Lib", UCRTVersion, x64 ? r"ucrt\x64" : r"ucrt\x86");
@@ -436,7 +453,7 @@ private:
436453
* Returns:
437454
* folder containing the Windows SDK libraries
438455
*/
439-
const(char)* getSDKLibPath(bool x64)
456+
const(char)* getSDKLibPath(bool x64) const
440457
{
441458
if (WindowsSdkDir)
442459
{
@@ -455,13 +472,20 @@ private:
455472
return sdk;
456473
}
457474

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

462483
return null;
463484
}
464485

486+
private:
487+
extern(D):
488+
465489
// iterate through subdirectories named by SDK version in baseDir and return the
466490
// one with the largest version that also contains the test file
467491
static const(char)* findLatestSDKDir(const(char)* baseDir, const(char)* testfile)
@@ -500,7 +524,7 @@ private:
500524
* Returns:
501525
* the registry value if it exists and has string type
502526
*/
503-
const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName)
527+
const(char)* GetRegistryString(const(char)* softwareKeyPath, const(char)* valueName) const
504528
{
505529
enum x64hive = false; // VS registry entries always in 32-bit hive
506530

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

607+
extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode);
608+
582609
interface ISetupInstance : IUnknown
583610
{
584611
// static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E");
@@ -635,18 +662,54 @@ const(char)* detectVSInstallDirViaCOM()
635662
return null;
636663
scope(exit) instances.Release();
637664

665+
BSTR versionString;
666+
BSTR installDir;
667+
scope(exit) SysFreeString(versionString);
668+
scope(exit) SysFreeString(installDir);
669+
638670
while (instances.Next(1, &instance, &fetched) == S_OK && fetched)
639671
{
640-
BSTR bstrInstallDir;
641-
if (instance.GetInstallationPath(&bstrInstallDir) != S_OK)
672+
BSTR thisVersionString;
673+
if (instance.GetInstallationVersion(&thisVersionString) != S_OK)
674+
continue;
675+
scope(exit) SysFreeString(thisVersionString);
676+
677+
BSTR thisInstallDir;
678+
if (instance.GetInstallationPath(&thisInstallDir) != S_OK)
642679
continue;
680+
scope(exit) SysFreeString(thisInstallDir);
681+
682+
if (versionString && wcscmp(thisVersionString, versionString) <= 0)
683+
continue; // not a newer version, skip
684+
685+
const installDirLength = wcslen(thisInstallDir);
686+
const vcInstallDirLength = installDirLength + 4;
687+
auto vcInstallDir = (cast(wchar*) mem.xmalloc_noscan(vcInstallDirLength * wchar.sizeof))[0 .. vcInstallDirLength];
688+
scope(exit) mem.xfree(vcInstallDir.ptr);
689+
vcInstallDir[0 .. installDirLength] = thisInstallDir[0 .. installDirLength];
690+
vcInstallDir[installDirLength .. $] = "\\VC\0"w;
691+
if (_waccess(vcInstallDir.ptr, 0) != 0)
692+
continue; // Visual C++ not included, skip
643693

644-
char[260] path;
645-
int len = WideCharToMultiByte(CP_UTF8, 0, bstrInstallDir, -1, path.ptr, 260, null, null);
646-
SysFreeString(bstrInstallDir);
694+
if (versionString)
695+
{
696+
SysFreeString(versionString);
697+
SysFreeString(installDir);
698+
}
699+
versionString = thisVersionString;
700+
installDir = thisInstallDir;
701+
thisVersionString = null;
702+
thisInstallDir = null;
703+
}
647704

648-
if (len > 0)
649-
return path[0..len].idup.ptr;
705+
if (installDir)
706+
{
707+
char[260] path = void;
708+
int len = WideCharToMultiByte(CP_UTF8, 0, installDir, -1, path.ptr, path.length, null, null);
709+
assert(len);
710+
711+
return path[0 .. len].idup.ptr;
650712
}
713+
651714
return null;
652715
}

dmd/vsoptions.h

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
/* Compiler implementation of the D programming language
3+
* Copyright (C) 2009-2020 by The D Language Foundation, All Rights Reserved
4+
* written by Walter Bright
5+
* http://www.digitalmars.com
6+
* Distributed under the Boost Software License, Version 1.0.
7+
* http://www.boost.org/LICENSE_1_0.txt
8+
* https://github.com/dlang/dmd/blob/master/src/dmd/vsoptions.h
9+
*/
10+
11+
#pragma once
12+
13+
#ifdef _WIN32
14+
15+
struct VSOptions
16+
{
17+
const char *WindowsSdkDir = nullptr;
18+
const char *WindowsSdkVersion = nullptr;
19+
const char *UCRTSdkDir = nullptr;
20+
const char *UCRTVersion = nullptr;
21+
const char *VSInstallDir = nullptr;
22+
const char *VCInstallDir = nullptr;
23+
const char *VCToolsInstallDir = nullptr; // used by VS 2017+
24+
25+
void initialize();
26+
const char *getVCBinDir(bool x64, const char *&addpath) const;
27+
const char *getVCLibDir(bool x64) const;
28+
const char *getUCRTLibPath(bool x64) const;
29+
const char *getSDKLibPath(bool x64) const;
30+
};
31+
32+
#endif // _WIN32

driver/linker-msvc.cpp

+34-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//===----------------------------------------------------------------------===//
99

1010
#include "dmd/errors.h"
11+
#include "driver/args.h"
1112
#include "driver/cl_options.h"
1213
#include "driver/cl_options_instrumentation.h"
1314
#include "driver/cl_options_sanitizers.h"
@@ -31,21 +32,28 @@
3132

3233
namespace {
3334

34-
void addMscrtLibs(std::vector<std::string> &args) {
35-
const auto mscrtlibName = getMscrtLibName();
35+
void addMscrtLibs(bool useInternalToolchain, std::vector<std::string> &args) {
36+
const auto mscrtlibName = getMscrtLibName(&useInternalToolchain);
3637

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

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

43+
if (useInternalToolchain) {
4244
#if LDC_LLVM_VER >= 400
43-
if (mscrtlibName.contains_lower("vcruntime")) {
45+
assert(mscrtlibName.contains_lower("vcruntime"));
46+
#endif
4447
return;
4548
}
49+
50+
#if LDC_LLVM_VER >= 400
51+
const bool isStatic = mscrtlibName.contains_lower("libcmt");
52+
#else
53+
const bool isStatic =
54+
mscrtlibName.find_lower("libcmt") != llvm::StringRef::npos;
4655
#endif
4756

48-
const bool isStatic = mscrtlibName.startswith_lower("libcmt");
4957
const bool isDebug =
5058
mscrtlibName.endswith_lower("d") || mscrtlibName.endswith_lower("d.lib");
5159

@@ -85,12 +93,29 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
8593
fatal();
8694
}
8795

88-
const bool useInternalToolchain = useInternalToolchainForMSVC();
89-
9096
#ifdef _WIN32
9197
windows::MsvcEnvironmentScope msvcEnv;
92-
if (!useInternalToolchain)
93-
msvcEnv.setup();
98+
99+
const auto begin = std::chrono::steady_clock::now();
100+
101+
const bool forceMSVC = env::has(L"LDC_VSDIR_FORCE");
102+
const bool useInternalToolchain =
103+
(!forceMSVC && getExplicitMscrtLibName().contains_lower("vcruntime")) ||
104+
!msvcEnv.setup();
105+
106+
if (!useInternalToolchain && global.params.verbose) {
107+
const auto end = std::chrono::steady_clock::now();
108+
message("MSVC setup took %lld microseconds",
109+
std::chrono::duration_cast<std::chrono::microseconds>(end - begin)
110+
.count());
111+
}
112+
113+
if (forceMSVC && useInternalToolchain) {
114+
warning(Loc(), "no Visual C++ installation found for linking, falling back "
115+
"to MinGW-based libraries");
116+
}
117+
#else
118+
const bool useInternalToolchain = true;
94119
#endif
95120

96121
// build arguments
@@ -121,7 +146,7 @@ int linkObjToBinaryMSVC(llvm::StringRef outputPath,
121146
}
122147

123148
// add C runtime libs
124-
addMscrtLibs(args);
149+
addMscrtLibs(useInternalToolchain, args);
125150

126151
// specify creation of DLL
127152
if (global.params.dll) {

0 commit comments

Comments
 (0)