Skip to content

Commit 5334dd5

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. Making the release scripts use the PGO build may require further changes.
1 parent 3206fb2 commit 5334dd5

File tree

1 file changed

+151
-11
lines changed

1 file changed

+151
-11
lines changed

src/build.d

Lines changed: 151 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
--------------------
@@ -163,10 +166,13 @@ Command-line parameters
163166
if (!flags["DFLAGS"].canFind("-color=off") &&
164167
[env["HOST_DMD_RUN"], "-color=on", "-h"].tryRun().status == 0)
165168
flags["DFLAGS"] ~= "-color=on";
166-
169+
string defaultRule()
170+
{
171+
return usePGO ? "dmd-pgo" : "dmd";
172+
}
167173
// default target
168174
if (!args.length)
169-
args = ["dmd"];
175+
args = [defaultRule()];
170176

171177
auto targets = predefinedTargets(args); // preprocess
172178

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

479609
/// Run's the test suite (unittests & `run.d`)
480610
alias runTests = makeRule!((testBuilder, testRule)
481611
{
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-
489612
// Reference header assumes Linux64
490613
auto headerCheck = env["OS"] == "linux" && env["MODEL"] == "64"
491614
? [ runCxxHeadersTest ] : null;
@@ -494,10 +617,10 @@ alias runTests = makeRule!((testBuilder, testRule)
494617
.name("test")
495618
.description("Run the test suite using test/run.d")
496619
.msg("(RUN) TEST")
497-
.deps([dmdDefault, runDmdUnittest, runner] ~ headerCheck)
620+
.deps([dmdDefault, runDmdUnittest, testRunner] ~ headerCheck)
498621
.commandFunction({
499622
// Use spawnProcess to avoid output redirection for `command`s
500-
const scope cmd = [ runner.targets[0], "-j" ~ jobs.to!string ];
623+
const scope cmd = [ testRunner.targets[0], "-j" ~ jobs.to!string ];
501624
log("%-(%s %)", cmd);
502625
if (spawnProcess(cmd, null, Config.init, testDir).wait())
503626
abortBuild("Tests failed!");
@@ -1261,6 +1384,23 @@ void processEnvironment()
12611384
assert(false, "Unknown host compiler kind: " ~ env["HOST_DMD_KIND"]);
12621385
}
12631386
}
1387+
if (env.getNumberedBool("ENABLE_PGO"))
1388+
{
1389+
switch (env["HOST_DMD_KIND"])
1390+
{
1391+
case "dmd":
1392+
stderr.writeln(`DMD does not support PGO! Ignoring ENABLE_PGO flag`);
1393+
break;
1394+
case "ldc":
1395+
usePGO = true;
1396+
break;
1397+
case "gdc":
1398+
stderr.writeln(`PGO (or AutoFDO) builds are not yet supported for gdc. Ignoring ENABLE_PGO flag`);
1399+
break;
1400+
default:
1401+
assert(false, "Unknown host compiler kind: " ~ env["HOST_DMD_KIND"]);
1402+
}
1403+
}
12641404
if (env.getNumberedBool("ENABLE_UNITTEST"))
12651405
{
12661406
dflags ~= ["-unittest"];

0 commit comments

Comments
 (0)