Skip to content

Commit 91c7b7e

Browse files
committed
Fix Issue 22861 - build the compiler with PGO
This introduces a new target to build.d, dmd-pgo, which will build a dmd with PGO instrumentation, then run either the phobos or dmd testsuite (both are present in the source code, only the dmd one is set to run at the moment). This data is then merged, at which point the data is used to build a release + LTO build of dmd. This resulting build is significantly faster - on the order of 45% on some programs. ENABLE_PGO=1 can be used to make the build script use the dmd-pgo by default, however something more subtle may be required to make all dmd builds (i.e. releases) fully pgo-ified
1 parent 4fb02ae commit 91c7b7e

File tree

1 file changed

+152
-11
lines changed

1 file changed

+152
-11
lines changed

src/build.d

Lines changed: 152 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ shared bool verbose; // output verbose logging
2828
shared bool force; // always build everything (ignores timestamp checking)
2929
shared bool dryRun; /// dont execute targets, just print command to be executed
3030
__gshared int jobs; // Number of jobs to run in parallel
31+
__gshared bool usePGO = false;
3132

3233
__gshared string[string] env;
3334
__gshared string[][string] flags;
@@ -37,6 +38,7 @@ __gshared TaskPool taskPool;
3738
/// Array of build rules through which all other build rules can be reached
3839
immutable rootRules = [
3940
&dmdDefault,
41+
&dmdPGO,
4042
&autoTesterBuild,
4143
&runDmdUnittest,
4244
&clean,
@@ -96,6 +98,7 @@ Examples
9698
./build.d unittest # runs internal unittests
9799
./build.d clean # remove all generated files
98100
./build.d generated/linux/release/64/dmd.conf
101+
./build.d dmd-pgo # builds dmd with PGO data, currently only LDC is supported
99102
100103
Important variables:
101104
--------------------
@@ -114,6 +117,7 @@ ENABLE_RELEASE: Optimized release build
114117
ENABLE_DEBUG: Add debug instructions and symbols (set if ENABLE_RELEASE isn't set)
115118
ENABLE_ASSERTS: Don't use -release if ENABLE_RELEASE is set
116119
ENABLE_LTO: Enable link-time optimizations
120+
ENABLE_PGO: Makes the default build dmd-pgo, you do not need to combine it with other optimization flags
117121
ENABLE_UNITTEST: Build dmd with unittests (sets ENABLE_COVERAGE=1)
118122
ENABLE_PROFILE: Build dmd with a profiling recorder (D)
119123
ENABLE_COVERAGE Build dmd with coverage counting
@@ -163,10 +167,13 @@ Command-line parameters
163167
if (!flags["DFLAGS"].canFind("-color=off") &&
164168
[env["HOST_DMD_RUN"], "-color=on", "-h"].tryRun().status == 0)
165169
flags["DFLAGS"] ~= "-color=on";
166-
170+
string defaultRule()
171+
{
172+
return usePGO ? "dmd-pgo" : "dmd";
173+
}
167174
// default target
168175
if (!args.length)
169-
args = ["dmd"];
176+
args = [defaultRule()];
170177

171178
auto targets = predefinedTargets(args); // preprocess
172179

@@ -475,17 +482,134 @@ alias dmdDefault = makeRule!((builder, rule) => builder
475482
.description("Build dmd")
476483
.deps([dmdExe(null, null, null), dmdConf])
477484
);
485+
struct PGOState
486+
{
487+
//Does the host compiler actually support PGO, if not print a message
488+
static bool checkPGO(string x)
489+
{
490+
switch (env["HOST_DMD_KIND"])
491+
{
492+
case "dmd":
493+
abortBuild(`DMD does not support PGO!`);
494+
break;
495+
case "ldc":
496+
return true;
497+
break;
498+
case "gdc":
499+
abortBuild(`PGO (or AutoFDO) builds are not yet supported for gdc`);
500+
break;
501+
default:
502+
assert(false, "Unknown host compiler kind: " ~ env["HOST_DMD_KIND"]);
503+
}
504+
assert(0);
505+
}
506+
this(string set)
507+
{
508+
hostKind = set;
509+
profDirPath = buildPath(env["G"], "dmd_profdata");
510+
mkdirRecurse(profDirPath);
511+
}
512+
string profDirPath;
513+
string hostKind;
514+
string[] pgoGenerateFlags() const
515+
{
516+
switch(hostKind)
517+
{
518+
case "ldc":
519+
return ["-fprofile-instr-generate=" ~ pgoDataPath ~ "/data.%p.raw"];
520+
default:
521+
return [""];
522+
}
523+
}
524+
string[] pgoUseFlags() const
525+
{
526+
switch(hostKind)
527+
{
528+
case "ldc":
529+
return ["-fprofile-instr-use=" ~ buildPath(pgoDataPath(), "merged.data")];
530+
default:
531+
return [""];
532+
}
533+
}
534+
string pgoDataPath() const
535+
{
536+
return profDirPath;
537+
}
538+
}
539+
// Compiles the test runner
540+
alias testRunner = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
541+
.msg("(DC) RUN.D")
542+
.sources([ testDir.buildPath( "run.d") ])
543+
.target(env["GENERATED"].buildPath("run".exeName))
544+
.command([ env["HOST_DMD_RUN"], "-of=" ~ rundRule.target, "-i", "-I" ~ testDir] ~ rundRule.sources));
545+
546+
//__gshared PGOState pgoState;
547+
alias dmdPGO = makeRule!((builder, rule) {
548+
const dmdKind = env["HOST_DMD_KIND"];
549+
PGOState pgoState = PGOState(dmdKind);
550+
551+
alias buildInstrumentedDmd = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
552+
.msg("Built dmd with PGO instrumentation")
553+
.deps([dmdExe(null, pgoState.pgoGenerateFlags(), pgoState.pgoGenerateFlags()), dmdConf]));
554+
555+
alias genData = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
556+
.msg("Compiling dmd testsuite to generate PGO data")
557+
.sources([ testDir.buildPath( "run.d") ])
558+
.deps([buildInstrumentedDmd, testRunner])
559+
.commandFunction({
560+
// Run dmd test suite to get data
561+
const scope cmd = [ testRunner.targets[0], "compilable", "-j" ~ jobs.to!string ];
562+
log("%-(%s %)", cmd);
563+
if (spawnProcess(cmd, null, Config.init, testDir).wait())
564+
stderr.writeln("dmd tests failed! This will not end the PGO build because some data may have been gathered");
565+
}));
566+
alias genPhobosData = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
567+
.msg("Compiling phobos testsuite to generate PGO data")
568+
.deps([buildInstrumentedDmd])
569+
.commandFunction({
570+
// Run phobos unittests
571+
//TODO makefiles
572+
//generated/linux/release/64/unittest/test_runner builds the unittests without running them.
573+
const scope cmd = ["make", "-C", "../phobos", "-j" ~ jobs.to!string, "-fposix.mak", "generated/linux/release/64/unittest/test_runner", "DMD_DIR="~dmdRepo];
574+
log("%-(%s %)", cmd);
575+
if (spawnProcess(cmd, null, Config.init).wait())
576+
stderr.writeln("Phobos Tests failed! This will not end the PGO build because some data may have been gathered");
577+
}));
578+
alias finalDataMerge = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
579+
.msg("Merging PGO data")
580+
.deps([/* genData */ genData])
581+
.commandFunction({
582+
// Run dmd test suite to get data
583+
scope cmd = ["ldc-profdata", "merge", "--output=merged.data"];
584+
import std.file : dirEntries;
585+
auto files = dirEntries(pgoState.pgoDataPath, "*.raw", SpanMode.shallow).array;
586+
files.each!(f => cmd ~= f);
587+
log("%-(%s %)", cmd);
588+
if (spawnProcess(cmd, null, Config.init, pgoState.pgoDataPath).wait())
589+
abortBuild("Merge failed");
590+
files.each!(f => remove(f));
591+
}));
592+
builder
593+
.name("dmd-pgo")
594+
.description("Build dmd with PGO data collected from the dmd and phobos testsuites")
595+
.msg("Build with collected PGO data")
596+
.condition(() => PGOState.checkPGO(dmdKind))
597+
.deps([finalDataMerge])
598+
.commandFunction({
599+
auto addArgs = pgoState.pgoUseFlags ~ "-wi";
600+
auto cmd = [env["HOST_DMD_RUN"], "-run", "src/build.d", "ENABLE_RELEASE=1",
601+
"ENABLE_LTO=1",
602+
"DFLAGS="~joiner(addArgs, " ").to!string, "--force", "-j"~jobs.to!string];
603+
log("%-(%s %)", cmd);
604+
if (spawnProcess(cmd, null, Config.init).wait())
605+
abortBuild("PGO Compilation failed");
606+
});
607+
}
608+
);
478609

479610
/// Run's the test suite (unittests & `run.d`)
480611
alias runTests = makeRule!((testBuilder, testRule)
481612
{
482-
// Precompiles the test runner
483-
alias runner = methodInit!(BuildRule, (rundBuilder, rundRule) => rundBuilder
484-
.msg("(DC) RUN.D")
485-
.sources([ testDir.buildPath( "run.d") ])
486-
.target(env["GENERATED"].buildPath("run".exeName))
487-
.command([ env["HOST_DMD_RUN"], "-of=" ~ rundRule.target, "-i", "-I" ~ testDir] ~ rundRule.sources));
488-
489613
// Reference header assumes Linux64
490614
auto headerCheck = env["OS"] == "linux" && env["MODEL"] == "64"
491615
? [ runCxxHeadersTest ] : null;
@@ -494,10 +618,10 @@ alias runTests = makeRule!((testBuilder, testRule)
494618
.name("test")
495619
.description("Run the test suite using test/run.d")
496620
.msg("(RUN) TEST")
497-
.deps([dmdDefault, runDmdUnittest, runner] ~ headerCheck)
621+
.deps([dmdDefault, runDmdUnittest, testRunner] ~ headerCheck)
498622
.commandFunction({
499623
// Use spawnProcess to avoid output redirection for `command`s
500-
const scope cmd = [ runner.targets[0], "-j" ~ jobs.to!string ];
624+
const scope cmd = [ testRunner.targets[0], "-j" ~ jobs.to!string ];
501625
log("%-(%s %)", cmd);
502626
if (spawnProcess(cmd, null, Config.init, testDir).wait())
503627
abortBuild("Tests failed!");
@@ -1261,6 +1385,23 @@ void processEnvironment()
12611385
assert(false, "Unknown host compiler kind: " ~ env["HOST_DMD_KIND"]);
12621386
}
12631387
}
1388+
if (env.getNumberedBool("ENABLE_PGO"))
1389+
{
1390+
switch (env["HOST_DMD_KIND"])
1391+
{
1392+
case "dmd":
1393+
stderr.writeln(`DMD does not support PGO! Ignoring ENABLE_PGO flag`);
1394+
break;
1395+
case "ldc":
1396+
usePGO = true;
1397+
break;
1398+
case "gdc":
1399+
stderr.writeln(`PGO (or AutoFDO) builds are not yet supported for gdc. Ignoring ENABLE_PGO flag`);
1400+
break;
1401+
default:
1402+
assert(false, "Unknown host compiler kind: " ~ env["HOST_DMD_KIND"]);
1403+
}
1404+
}
12641405
if (env.getNumberedBool("ENABLE_UNITTEST"))
12651406
{
12661407
dflags ~= ["-unittest"];

0 commit comments

Comments
 (0)