Skip to content

Commit 35fb000

Browse files
chandlercdanakj
andauthored
Use a thread pool when building runtimes (carbon-language#6133)
This parallelizes the compilations and dramatically reduces the time to build runtimes. As part of this, teach the driver infrastructure to have an option to control the use of threads and to build the relevant thread pool and thread it into the various APIs. However, it requires our `ClangRunner` to become thread-safe and to invoke Clang in a way that is thread-safe. This is somewhat challenging as the code in `clang_main` is distinctly _not_ thread-safe. To address this, the relevant logic of `clang_main`, especially the CC1 execution, is extracted into our runner and cleaned up to be much more appropriate in a multithreaded context. Much of this code should eventually be factored back into Clang, but that will be a follow-up patch to upstream. Last but not least, this rearranges the `ClangRunner` API to make a bit more sense out of the different options for building runtimes, and have a clean model for which things need to be passed in at which points. --------- Co-authored-by: Dana Jansens <[email protected]>
1 parent a6bb11f commit 35fb000

File tree

10 files changed

+367
-151
lines changed

10 files changed

+367
-151
lines changed

toolchain/driver/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@ cc_library(
3333
"//toolchain/install:install_paths",
3434
"@llvm-project//clang:basic",
3535
"@llvm-project//clang:clang-driver",
36+
"@llvm-project//clang:codegen",
3637
"@llvm-project//clang:driver",
3738
"@llvm-project//clang:frontend",
39+
"@llvm-project//clang:frontend_tool",
40+
"@llvm-project//clang:serialization",
3841
"@llvm-project//llvm:Core",
3942
"@llvm-project//llvm:Object",
4043
"@llvm-project//llvm:Support",

toolchain/driver/build_runtimes_subcommand.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ auto BuildRuntimesSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
6868

6969
auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env)
7070
-> ErrorOr<std::filesystem::path> {
71-
ClangRunner runner(driver_env.installation, &driver_env.runtimes_cache,
72-
driver_env.fs, driver_env.vlog_stream);
71+
ClangRunner runner(driver_env.installation, driver_env.fs,
72+
driver_env.vlog_stream);
7373

7474
Runtimes::Cache::Features features = {
7575
.target = options_.codegen_options.target.str()};
@@ -92,7 +92,8 @@ auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env)
9292
: Runtimes::Make(explicit_output_path, driver_env.vlog_stream));
9393
CARBON_ASSIGN_OR_RETURN(auto tmp_dir, Filesystem::MakeTmpDir());
9494

95-
return runner.BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path());
95+
return runner.BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path(),
96+
*driver_env.thread_pool);
9697
}
9798

9899
} // namespace Carbon

toolchain/driver/clang_runner.cpp

Lines changed: 239 additions & 79 deletions
Large diffs are not rendered by default.

toolchain/driver/clang_runner.h

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "common/ostream.h"
1313
#include "llvm/ADT/ArrayRef.h"
1414
#include "llvm/ADT/StringRef.h"
15+
#include "llvm/Support/ThreadPool.h"
1516
#include "llvm/Support/VirtualFileSystem.h"
1617
#include "llvm/TargetParser/Triple.h"
1718
#include "toolchain/driver/runtimes_cache.h"
@@ -25,11 +26,15 @@ namespace Carbon {
2526
// incorporating custom command line flags from user invocations that we don't
2627
// parse, but will pass transparently along to Clang itself.
2728
//
29+
// This class is thread safe, allowing multiple threads to share a single runner
30+
// and concurrently invoke Clang.
31+
//
2832
// This doesn't literally use a subprocess to invoke Clang; it instead tries to
2933
// directly use the Clang command line driver library. We also work to simplify
3034
// how that driver operates and invoke it in an opinionated way to get the best
3135
// behavior for our expected use cases in the Carbon driver:
3236
//
37+
// - Ensure thread-safe invocation of Clang to enable concurrent usage.
3338
// - Minimize canonicalization of file names to try to preserve the paths as
3439
// users type them.
3540
// - Minimize the use of subprocess invocations which are expensive on some
@@ -42,43 +47,57 @@ namespace Carbon {
4247
// standard output and standard error, and otherwise can only read and write
4348
// files based on their names described in the arguments. It doesn't provide any
4449
// higher-level abstraction such as streams for inputs or outputs.
50+
//
51+
// TODO: Switch the diagnostic machinery to buffer and do locked output so that
52+
// concurrent invocations of Clang don't intermingle their diagnostic output.
53+
//
54+
// TODO: If support for thread-local overrides of `llvm::errs` and `llvm::outs`
55+
// becomes available upstream, also buffer and synchronize those streams to
56+
// further improve the behavior of concurrent invocations.
4557
class ClangRunner : ToolRunnerBase {
4658
public:
47-
// Build a Clang runner that uses the provided `exe_name` and `err_stream`.
59+
// Build a Clang runner that uses the provided installation and filesystem.
4860
//
49-
// If `verbose` is passed as true, will enable verbose logging to the
50-
// `err_stream` both from the runner and Clang itself.
61+
// Optionally accepts a `vlog_stream` to enable verbose logging from Carbon to
62+
// that stream. The verbose output from Clang goes to stderr regardless.
5163
ClangRunner(const InstallPaths* install_paths,
52-
Runtimes::Cache* on_demand_runtimes_cache,
5364
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
54-
llvm::raw_ostream* vlog_stream = nullptr,
55-
bool build_runtimes_on_demand = false);
65+
llvm::raw_ostream* vlog_stream = nullptr);
5666

57-
// Run Clang with the provided arguments.
67+
// Run Clang with the provided arguments and a runtime cache for on-demand
68+
// runtime building.
5869
//
5970
// This works to support all of the Clang commandline, including commands that
6071
// use target-dependent resources like linking. When it detects such commands,
61-
// it will either use the provided target resource-dir path, or if building
62-
// runtimes on demand is enabled it will build the needed resource-dir.
72+
// it will use runtimes from the provided cache. If not available in the
73+
// cache, it will build the necessary runtimes using the provided thread pool
74+
// both to use and incorporate into the cache.
6375
//
6476
// Returns an error only if unable to successfully run Clang with the
6577
// arguments. If able to run Clang, no error is returned a bool indicating
6678
// whether than Clang invocation succeeded is returned.
67-
//
68-
// TODO: Eventually, this will need to accept an abstraction that can
69-
// represent multiple different pre-built runtimes.
7079
auto Run(llvm::ArrayRef<llvm::StringRef> args,
71-
Runtimes* prebuilt_runtimes = nullptr) -> ErrorOr<bool>;
80+
Runtimes::Cache& runtimes_cache,
81+
llvm::ThreadPoolInterface& runtimes_build_thread_pool)
82+
-> ErrorOr<bool>;
83+
84+
// Run Clang with the provided arguments and prebuilt runtimes.
85+
//
86+
// Similar to `Run`, but requires and uses pre-built runtimes rather than a
87+
// cache or building them on demand.
88+
auto RunWithPrebuiltRuntimes(llvm::ArrayRef<llvm::StringRef> args,
89+
Runtimes& prebuilt_runtimes) -> ErrorOr<bool>;
7290

73-
// Run Clang with the provided arguments and without any target-dependent
74-
// resources.
91+
// Run Clang with the provided arguments and without any target runtimes.
7592
//
7693
// This method can be used to avoid building target-dependent resources when
7794
// unnecessary, but not all Clang command lines will work correctly.
7895
// Specifically, compile-only commands will typically work, while linking will
7996
// not.
80-
auto RunTargetIndependentCommand(llvm::ArrayRef<llvm::StringRef> args)
81-
-> bool;
97+
//
98+
// This function simply returns true or false depending on whether Clang runs
99+
// successfully, as it should display any needed error messages.
100+
auto RunWithNoRuntimes(llvm::ArrayRef<llvm::StringRef> args) -> bool;
82101

83102
// Builds the target-specific resource directory for Clang.
84103
//
@@ -88,7 +107,8 @@ class ClangRunner : ToolRunnerBase {
88107
// return the path.
89108
auto BuildTargetResourceDir(const Runtimes::Cache::Features& features,
90109
Runtimes& runtimes,
91-
const std::filesystem::path& tmp_path)
110+
const std::filesystem::path& tmp_path,
111+
llvm::ThreadPoolInterface& threads)
92112
-> ErrorOr<std::filesystem::path>;
93113

94114
// Enable leaking memory.
@@ -103,6 +123,14 @@ class ClangRunner : ToolRunnerBase {
103123
auto EnableLeakingMemory() -> void { enable_leaking_ = true; }
104124

105125
private:
126+
// Emulates `cc1_main` but in a way that doesn't assume it is running in the
127+
// main thread and can more easily fit into library calls to do compiles.
128+
//
129+
// TODO: Much of the logic here should be factored out of the CC1
130+
// implementation in Clang's driver and into a reusable part of its libraries.
131+
// That should allow reducing the code here to a minimal amount.
132+
auto RunCC1(llvm::SmallVectorImpl<const char*>& cc1_args) -> int;
133+
106134
// Handles building the Clang driver and passing the arguments down to it.
107135
auto RunInternal(llvm::ArrayRef<llvm::StringRef> args, llvm::StringRef target,
108136
std::optional<llvm::StringRef> target_resource_dir_path)
@@ -125,16 +153,11 @@ class ClangRunner : ToolRunnerBase {
125153
auto BuildBuiltinsLib(llvm::StringRef target,
126154
const llvm::Triple& target_triple,
127155
const std::filesystem::path& tmp_path,
128-
Filesystem::DirRef lib_dir) -> ErrorOr<Success>;
129-
130-
Runtimes::Cache* runtimes_cache_;
156+
Filesystem::DirRef lib_dir,
157+
llvm::ThreadPoolInterface& threads) -> ErrorOr<Success>;
131158

132159
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs_;
133-
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> diagnostic_ids_;
134-
135-
std::optional<std::filesystem::path> prebuilt_runtimes_path_;
136160

137-
bool build_runtimes_on_demand_;
138161
bool enable_leaking_ = false;
139162
};
140163

toolchain/driver/clang_runner_test.cpp

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,12 @@ class ClangRunnerTest : public ::testing::Test {
7070

7171
TEST_F(ClangRunnerTest, Version) {
7272
RawStringOstream test_os;
73-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &test_os);
73+
ClangRunner runner(&install_paths_, vfs_, &test_os);
7474

7575
std::string out;
7676
std::string err;
77-
EXPECT_TRUE(Testing::CallWithCapturedOutput(out, err, [&] {
78-
return runner.RunTargetIndependentCommand({"--version"});
79-
}));
77+
EXPECT_TRUE(Testing::CallWithCapturedOutput(
78+
out, err, [&] { return runner.RunWithNoRuntimes({"--version"}); }));
8079
// The arguments to Clang should be part of the verbose log.
8180
EXPECT_THAT(test_os.TakeStr(), HasSubstr("--version"));
8281

@@ -101,13 +100,13 @@ TEST_F(ClangRunnerTest, DashC) {
101100
std::filesystem::path test_output = *Testing::WriteTestFile("test.o", "");
102101

103102
RawStringOstream verbose_out;
104-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &verbose_out);
103+
ClangRunner runner(&install_paths_, vfs_, &verbose_out);
105104
std::string out;
106105
std::string err;
107106
EXPECT_TRUE(Testing::CallWithCapturedOutput(
108107
out, err,
109108
[&] {
110-
return runner.RunTargetIndependentCommand(
109+
return runner.RunWithNoRuntimes(
111110
{"-c", test_file.string(), "-o", test_output.string()});
112111
}))
113112
<< "Verbose output from runner:\n"
@@ -130,13 +129,13 @@ TEST_F(ClangRunnerTest, BuitinHeaders) {
130129
std::filesystem::path test_output = *Testing::WriteTestFile("test.o", "");
131130

132131
RawStringOstream verbose_out;
133-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &verbose_out);
132+
ClangRunner runner(&install_paths_, vfs_, &verbose_out);
134133
std::string out;
135134
std::string err;
136135
EXPECT_TRUE(Testing::CallWithCapturedOutput(
137136
out, err,
138137
[&] {
139-
return runner.RunTargetIndependentCommand(
138+
return runner.RunWithNoRuntimes(
140139
{"-c", test_file.string(), "-o", test_output.string()});
141140
}))
142141
<< "Verbose output from runner:\n"
@@ -157,13 +156,13 @@ TEST_F(ClangRunnerTest, CompileMultipleFiles) {
157156
std::filesystem::path output = *Testing::WriteTestFile(output_file, "");
158157

159158
RawStringOstream verbose_out;
160-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &verbose_out);
159+
ClangRunner runner(&install_paths_, vfs_, &verbose_out);
161160
std::string out;
162161
std::string err;
163162
EXPECT_TRUE(Testing::CallWithCapturedOutput(
164163
out, err,
165164
[&] {
166-
return runner.RunTargetIndependentCommand(
165+
return runner.RunWithNoRuntimes(
167166
{"-c", file.string(), "-o", output.string()});
168167
}))
169168
<< "Verbose output from runner:\n"
@@ -180,8 +179,7 @@ TEST_F(ClangRunnerTest, CompileMultipleFiles) {
180179
}
181180

182181
TEST_F(ClangRunnerTest, BuildResourceDir) {
183-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &llvm::errs(),
184-
/*build_runtimes_on_demand=*/true);
182+
ClangRunner runner(&install_paths_, vfs_, &llvm::errs());
185183

186184
// Note that we can't test arbitrary targets here as we need to be able to
187185
// compile the builtin functions for the target. We use the default target as
@@ -191,8 +189,9 @@ TEST_F(ClangRunnerTest, BuildResourceDir) {
191189
Runtimes::Cache::Features features = {.target = target};
192190
auto runtimes = *runtimes_cache_.Lookup(features);
193191
auto tmp_dir = *Filesystem::MakeTmpDir();
194-
auto build_result =
195-
runner.BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path());
192+
llvm::DefaultThreadPool threads(llvm::optimal_concurrency());
193+
auto build_result = runner.BuildTargetResourceDir(
194+
features, runtimes, tmp_dir.abs_path(), threads);
196195
ASSERT_TRUE(build_result.ok()) << build_result.error();
197196
std::filesystem::path resource_dir_path = std::move(*build_result);
198197

@@ -270,7 +269,7 @@ TEST_F(ClangRunnerTest, LinkCommandEcho) {
270269
std::filesystem::path bar_file = *Testing::WriteTestFile("bar.o", "");
271270

272271
RawStringOstream verbose_out;
273-
ClangRunner runner(&install_paths_, &runtimes_cache_, vfs_, &verbose_out);
272+
ClangRunner runner(&install_paths_, vfs_, &verbose_out);
274273
std::string out;
275274
std::string err;
276275
EXPECT_TRUE(Testing::CallWithCapturedOutput(
@@ -280,7 +279,7 @@ TEST_F(ClangRunnerTest, LinkCommandEcho) {
280279
// we're just getting the echo-ed output back. For this to actually
281280
// link, we'd need to have the target-dependent resources, but those are
282281
// expensive to build so we only want to test them once (above).
283-
return runner.RunTargetIndependentCommand(
282+
return runner.RunWithNoRuntimes(
284283
{"-###", "-o", "binary", foo_file.string(), bar_file.string()});
285284
}))
286285
<< "Verbose output from runner:\n"

toolchain/driver/clang_subcommand.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,8 @@ ClangSubcommand::ClangSubcommand() : DriverSubcommand(SubcommandInfo) {}
6969
// add more.
7070
// https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/driver.cpp
7171
auto ClangSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
72-
ClangRunner runner(
73-
driver_env.installation, &driver_env.runtimes_cache, driver_env.fs,
74-
driver_env.vlog_stream,
75-
/*build_runtimes_on_demand=*/options_.build_runtimes_on_demand);
72+
ClangRunner runner(driver_env.installation, driver_env.fs,
73+
driver_env.vlog_stream);
7674

7775
// Don't run Clang when fuzzing, it is known to not be reliable under fuzzing
7876
// due to many unfixed issues.
@@ -85,9 +83,16 @@ auto ClangSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
8583
runner.EnableLeakingMemory();
8684
}
8785

88-
ErrorOr<bool> run_result = runner.Run(
89-
options_.args,
90-
driver_env.prebuilt_runtimes ? &*driver_env.prebuilt_runtimes : nullptr);
86+
ErrorOr<bool> run_result = false;
87+
if (driver_env.prebuilt_runtimes) {
88+
run_result = runner.RunWithPrebuiltRuntimes(options_.args,
89+
*driver_env.prebuilt_runtimes);
90+
} else if (options_.build_runtimes_on_demand) {
91+
run_result = runner.Run(options_.args, driver_env.runtimes_cache,
92+
*driver_env.thread_pool);
93+
} else {
94+
run_result = runner.RunWithNoRuntimes(options_.args);
95+
}
9196
if (!run_result.ok()) {
9297
// This is not a Clang failure, but a failure to even run Clang, so we need
9398
// to diagnose it here.

toolchain/driver/driver.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct Options {
3232
bool verbose = false;
3333
bool fuzzing = false;
3434
bool include_diagnostic_kind = false;
35+
bool threads = true;
3536

3637
llvm::StringRef runtimes_cache_path;
3738
llvm::StringRef prebuilt_runtimes_path;
@@ -124,6 +125,25 @@ applies to each message that forms a diagnostic, not just the primary message.
124125
},
125126
[&](auto& arg_b) { arg_b.Set(&include_diagnostic_kind); });
126127

128+
b.AddFlag(
129+
{
130+
.name = "threads",
131+
.help = R"""(
132+
Controls whether threads are used to build runtimes.
133+
134+
When enabled (the default), Carbon will try to build runtime libraries using
135+
threads to parallelize the operation. How many threads is controlled
136+
automatically by the system.
137+
138+
Disabling threads ensures a single threaded build of the runtimes which can help
139+
when there are errors or other output.
140+
)""",
141+
},
142+
[&](auto& arg_b) {
143+
arg_b.Default(true);
144+
arg_b.Set(&threads);
145+
});
146+
127147
runtimes.AddTo(b, &selected_subcommand);
128148
clang.AddTo(b, &selected_subcommand);
129149
compile.AddTo(b, &selected_subcommand);
@@ -208,6 +228,14 @@ auto Driver::RunCommand(llvm::ArrayRef<llvm::StringRef> args) -> DriverResult {
208228
driver_env_.fuzzing = true;
209229
}
210230

231+
llvm::SingleThreadExecutor single_thread({.ThreadsRequested = 1});
232+
std::optional<llvm::DefaultThreadPool> threads;
233+
driver_env_.thread_pool = &single_thread;
234+
if (options.threads) {
235+
threads.emplace(llvm::optimal_concurrency());
236+
driver_env_.thread_pool = &*threads;
237+
}
238+
211239
CARBON_CHECK(options.selected_subcommand != nullptr);
212240
return options.selected_subcommand->Run(driver_env_);
213241
}

toolchain/driver/driver_env.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <utility>
1010

1111
#include "common/ostream.h"
12+
#include "llvm/Support/ThreadPool.h"
13+
#include "llvm/Support/Threading.h"
1214
#include "llvm/Support/VirtualFileSystem.h"
1315
#include "toolchain/diagnostics/diagnostic_emitter.h"
1416
#include "toolchain/driver/runtimes_cache.h"
@@ -62,6 +64,9 @@ struct DriverEnv {
6264
// A diagnostic emitter that has no locations.
6365
Diagnostics::NoLocEmitter emitter;
6466

67+
// Thread pool available for use when concurrency is needed.
68+
llvm::ThreadPoolInterface* thread_pool;
69+
6570
// For CARBON_VLOG.
6671
llvm::raw_pwrite_stream* vlog_stream = nullptr;
6772

toolchain/driver/link_subcommand.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ auto LinkSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
118118
clang_args.append(options_.object_filenames.begin(),
119119
options_.object_filenames.end());
120120

121-
ClangRunner runner(driver_env.installation, &driver_env.runtimes_cache,
122-
driver_env.fs, driver_env.vlog_stream);
123-
ErrorOr<bool> run_result = runner.Run(clang_args);
121+
ClangRunner runner(driver_env.installation, driver_env.fs,
122+
driver_env.vlog_stream);
123+
ErrorOr<bool> run_result = runner.Run(clang_args, driver_env.runtimes_cache,
124+
*driver_env.thread_pool);
124125
if (!run_result.ok()) {
125126
// This is not a Clang failure, but a failure to even run Clang, so we need
126127
// to diagnose it here.

0 commit comments

Comments
 (0)