Skip to content

Commit fd70196

Browse files
Introduce a runtimes caching and management layer (#6002)
This layer allows runtimes to be built on-demand but cached in a consistent and re-usable location on the system. It handles careful filesystem operations to ensure consistency even in the face of multiple versions and build configurations. This addresses a number of TODOs from the initial runtimes building on-demand, and sets the stage to scale up to more runtimes. This doesn't switch on-demand runtimes to be on by default, I wanted to wait and make that change as a separate step. --------- Co-authored-by: Geoff Romer <[email protected]>
1 parent 42d03ac commit fd70196

24 files changed

+2125
-119
lines changed

common/filesystem.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ auto PathError::Print(llvm::raw_ostream& out) const -> void {
6262
// The `format_` member is a `StringLiteral` that is null terminated, so
6363
// `.data()` is safe here.
6464
// NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage)
65-
out << llvm::formatv(format_.data(), path_, dir_fd_) << " failed: ";
65+
out << llvm::formatv(format_.data(), path_,
66+
dir_fd_ == AT_FDCWD ? std::string("AT_FDCWD")
67+
: std::to_string(dir_fd_))
68+
<< " failed: ";
6669
PrintErrorNumber(out, unix_errnum());
6770
}
6871

toolchain/diagnostics/diagnostic_kind.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
// ============================================================================
2323

2424
CARBON_DIAGNOSTIC_KIND(DriverInstallInvalid)
25+
CARBON_DIAGNOSTIC_KIND(DriverRuntimesCacheInvalid)
26+
CARBON_DIAGNOSTIC_KIND(DriverPrebuiltRuntimesInvalid)
2527
CARBON_DIAGNOSTIC_KIND(DriverCommandLineParseFailed)
2628
CARBON_DIAGNOSTIC_KIND(CompilePhaseFlagConflict)
2729
CARBON_DIAGNOSTIC_KIND(CompilePreludeManifestError)

toolchain/driver/BUILD

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ cc_library(
2323
hdrs = ["clang_runner.h"],
2424
deps = [
2525
":llvm_runner",
26+
":runtimes_cache",
2627
":tool_runner_base",
2728
"//common:error",
2829
"//common:filesystem",
@@ -130,6 +131,7 @@ cc_library(
130131
":clang_runner",
131132
":lld_runner",
132133
":llvm_runner",
134+
":runtimes_cache",
133135
"//common:command_line",
134136
"//common:error",
135137
"//common:ostream",
@@ -270,6 +272,44 @@ cc_test(
270272
],
271273
)
272274

275+
cc_library(
276+
name = "runtimes_cache",
277+
srcs = ["runtimes_cache.cpp"],
278+
hdrs = ["runtimes_cache.h"],
279+
deps = [
280+
"//common:check",
281+
"//common:error",
282+
"//common:filesystem",
283+
"//common:ostream",
284+
"//common:version",
285+
"//common:vlog",
286+
"//toolchain/install:install_paths",
287+
"@llvm-project//llvm:Support",
288+
],
289+
)
290+
291+
cc_test(
292+
name = "runtimes_cache_test",
293+
size = "small",
294+
srcs = ["runtimes_cache_test.cpp"],
295+
data = ["//toolchain/install:install_data"],
296+
deps = [
297+
":runtimes_cache",
298+
"//common:check",
299+
"//common:error_test_helpers",
300+
"//common:filesystem",
301+
"//common:ostream",
302+
"//common:raw_string_ostream",
303+
"//common:version",
304+
"//testing/base:capture_std_streams",
305+
"//testing/base:file_helpers",
306+
"//testing/base:global_exe_path",
307+
"//testing/base:gtest_main",
308+
"@googletest//:gtest",
309+
"@llvm-project//llvm:Support",
310+
],
311+
)
312+
273313
cc_library(
274314
name = "tool_runner_base",
275315
srcs = ["tool_runner_base.cpp"],

toolchain/driver/build_runtimes_subcommand.cpp

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "toolchain/driver/build_runtimes_subcommand.h"
66

7+
#include <variant>
8+
79
#include "llvm/TargetParser/Triple.h"
810
#include "toolchain/driver/clang_runner.h"
911

@@ -43,9 +45,6 @@ BuildRuntimesSubcommand::BuildRuntimesSubcommand()
4345
: DriverSubcommand(SubcommandInfo) {}
4446

4547
auto BuildRuntimesSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
46-
ClangRunner runner(driver_env.installation, driver_env.fs,
47-
driver_env.vlog_stream);
48-
4948
// Don't run Clang when fuzzing, it is known to not be reliable under fuzzing
5049
// due to many unfixed issues.
5150
if (TestAndDiagnoseIfFuzzingExternalLibraries(driver_env, "clang")) {
@@ -56,39 +55,44 @@ auto BuildRuntimesSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
5655
CARBON_DIAGNOSTIC(FailureBuildingRuntimes, Error,
5756
"failure building runtimes: {0}", std::string);
5857

59-
auto tmp_result = Filesystem::MakeTmpDir();
60-
if (!tmp_result.ok()) {
58+
auto run_result = RunInternal(driver_env);
59+
if (!run_result.ok()) {
6160
driver_env.emitter.Emit(FailureBuildingRuntimes,
62-
tmp_result.error().message());
61+
run_result.error().message());
6362
return {.success = false};
6463
}
65-
Filesystem::RemovingDir tmp_dir = *std::move(tmp_result);
66-
67-
// TODO: Currently, the default location is just a subdirectory of the
68-
// temporary directory used for the build. This allows the subcommand to be
69-
// used to test and debug runtime building, but not for the results to be
70-
// reused. Eventually, this should be connected to the same runtimes cache
71-
// used by link commands.
72-
std::filesystem::path output_path =
73-
options_.directory.empty()
74-
? tmp_dir.abs_path() / "runtimes"
75-
: std::filesystem::path(options_.directory.str());
76-
77-
// Hard code a subdirectory of the runtimes output for the Clang resource
78-
// directory runtimes.
79-
//
80-
// TODO: This should be replaced with an abstraction that manages the layout
81-
// of the generated runtimes rather than hardcoding it.
82-
std::filesystem::path resource_dir_path = output_path / "clang_resource_dir";
83-
84-
auto build_result = runner.BuildTargetResourceDir(
85-
options_.codegen_options.target, resource_dir_path, tmp_dir.abs_path());
86-
if (!build_result.ok()) {
87-
driver_env.emitter.Emit(FailureBuildingRuntimes,
88-
build_result.error().message());
64+
65+
llvm::outs() << "Built runtimes: " << *run_result << "\n";
66+
return {.success = true};
67+
}
68+
69+
auto BuildRuntimesSubcommand::RunInternal(DriverEnv& driver_env)
70+
-> ErrorOr<std::filesystem::path> {
71+
ClangRunner runner(driver_env.installation, &driver_env.runtimes_cache,
72+
driver_env.fs, driver_env.vlog_stream);
73+
74+
Runtimes::Cache::Features features = {
75+
.target = options_.codegen_options.target.str()};
76+
77+
bool is_cache = options_.directory.empty();
78+
std::filesystem::path explicit_output_path = options_.directory.str();
79+
if (!is_cache) {
80+
auto access_result = Filesystem::Cwd().Access(explicit_output_path);
81+
if (access_result.ok()) {
82+
return Error("output directory already exists");
83+
}
84+
if (!access_result.error().no_entity()) {
85+
return std::move(access_result).error();
86+
}
8987
}
9088

91-
return {.success = build_result.ok()};
89+
CARBON_ASSIGN_OR_RETURN(
90+
auto runtimes,
91+
is_cache ? driver_env.runtimes_cache.Lookup(features)
92+
: Runtimes::Make(explicit_output_path, driver_env.vlog_stream));
93+
CARBON_ASSIGN_OR_RETURN(auto tmp_dir, Filesystem::MakeTmpDir());
94+
95+
return runner.BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path());
9296
}
9397

9498
} // namespace Carbon

toolchain/driver/build_runtimes_subcommand.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ class BuildRuntimesSubcommand : public DriverSubcommand {
3636
auto Run(DriverEnv& driver_env) -> DriverResult override;
3737

3838
private:
39+
auto RunInternal(DriverEnv& driver_env) -> ErrorOr<std::filesystem::path>;
40+
3941
BuildRuntimesOptions options_;
4042
};
4143

toolchain/driver/clang_runner.cpp

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <string>
1515
#include <system_error>
1616
#include <utility>
17+
#include <variant>
1718

1819
#include "clang/Basic/Diagnostic.h"
1920
#include "clang/Basic/DiagnosticOptions.h"
@@ -51,10 +52,12 @@ auto clang_main(int Argc, char** Argv, const llvm::ToolContext& ToolContext)
5152
namespace Carbon {
5253

5354
ClangRunner::ClangRunner(const InstallPaths* install_paths,
55+
Runtimes::Cache* runtimes_cache,
5456
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs,
5557
llvm::raw_ostream* vlog_stream,
5658
bool build_runtimes_on_demand)
5759
: ToolRunnerBase(install_paths, vlog_stream),
60+
runtimes_cache_(runtimes_cache),
5861
fs_(std::move(fs)),
5962
diagnostic_ids_(new clang::DiagnosticIDs()),
6063
build_runtimes_on_demand_(build_runtimes_on_demand) {}
@@ -130,47 +133,44 @@ static auto IsNonLinkCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
130133
});
131134
}
132135

133-
auto ClangRunner::Run(
134-
llvm::ArrayRef<llvm::StringRef> args,
135-
std::optional<std::filesystem::path> prebuilt_resource_dir_path)
136-
-> ErrorOr<bool> {
136+
auto ClangRunner::Run(llvm::ArrayRef<llvm::StringRef> args,
137+
Runtimes* prebuilt_runtimes) -> ErrorOr<bool> {
137138
// Check the args to see if we have a known target-independent command. If so,
138139
// directly dispatch it to avoid the cost of building the target resource
139140
// directory.
140141
// TODO: Maybe handle response file expansion similar to the Clang CLI?
141142
if (args.empty() || args[0].starts_with("-cc1") || IsNonLinkCommand(args) ||
142-
(!build_runtimes_on_demand_ && !prebuilt_resource_dir_path)) {
143+
(!build_runtimes_on_demand_ && !prebuilt_runtimes)) {
143144
return RunTargetIndependentCommand(args);
144145
}
145146

146147
std::string target = ComputeClangTarget(args);
147148

148149
// If we have pre-built runtimes, use them rather than building on demand.
149-
if (prebuilt_resource_dir_path) {
150-
return RunInternal(args, target, prebuilt_resource_dir_path->native());
150+
if (prebuilt_runtimes) {
151+
CARBON_ASSIGN_OR_RETURN(std::filesystem::path prebuilt_resource_dir_path,
152+
prebuilt_runtimes->Get(Runtimes::ClangResourceDir));
153+
return RunInternal(args, target, prebuilt_resource_dir_path.native());
151154
}
152155
CARBON_CHECK(build_runtimes_on_demand_);
153156

154157
// Otherwise, we need to build a target resource directory.
155-
//
156-
// TODO: Currently, this builds the runtimes in a temporary directory that is
157-
// removed after the Clang invocation. That requires building them on each
158-
// execution which is expensive and slow. Eventually, we want to replace this
159-
// with using an on-disk cache so that only the first execution has to build
160-
// the runtimes and subsequently the cached build can be used.
161158
CARBON_VLOG("Building target resource dir...\n");
162-
CARBON_ASSIGN_OR_RETURN(Filesystem::RemovingDir tmp_dir,
163-
Filesystem::MakeTmpDir());
164-
165-
// Hard code the subdirectory for the resource-dir runtimes.
166-
//
167-
// TODO: This should be replaced with an abstraction that manages the layout
168-
// of a built runtimes tree.
169-
std::filesystem::path resource_dir_path =
170-
tmp_dir.abs_path() / "clang_resource_dir";
171-
172-
CARBON_RETURN_IF_ERROR(
173-
BuildTargetResourceDir(target, resource_dir_path, tmp_dir.abs_path()));
159+
Runtimes::Cache::Features features = {.target = target};
160+
CARBON_ASSIGN_OR_RETURN(Runtimes runtimes, runtimes_cache_->Lookup(features));
161+
162+
// We need to build the Clang resource directory for these runtimes. This
163+
// requires a temporary directory as well as the destination directory for
164+
// the build. The temporary directory should only be used during the build,
165+
// not once we are running Clang with the built runtime.
166+
std::filesystem::path resource_dir_path;
167+
{
168+
CARBON_ASSIGN_OR_RETURN(Filesystem::RemovingDir tmp_dir,
169+
Filesystem::MakeTmpDir());
170+
CARBON_ASSIGN_OR_RETURN(
171+
resource_dir_path,
172+
BuildTargetResourceDir(features, runtimes, tmp_dir.abs_path()));
173+
}
174174

175175
// Note that this function always successfully runs `clang` and returns a bool
176176
// to indicate whether `clang` itself succeeded, not whether the runner was
@@ -186,32 +186,37 @@ auto ClangRunner::RunTargetIndependentCommand(
186186
}
187187

188188
auto ClangRunner::BuildTargetResourceDir(
189-
llvm::StringRef target, const std::filesystem::path& resource_dir_path,
190-
const std::filesystem::path& tmp_path) -> ErrorOr<Success> {
189+
const Runtimes::Cache::Features& features, Runtimes& runtimes,
190+
const std::filesystem::path& tmp_path) -> ErrorOr<std::filesystem::path> {
191191
// Disable any leaking of memory while building the target resource dir, and
192192
// restore the previous setting at the end.
193193
auto restore_leak_flag = llvm::make_scope_exit(
194194
[&, orig_flag = enable_leaking_] { enable_leaking_ = orig_flag; });
195195
enable_leaking_ = false;
196196

197-
// Create the destination directory if needed.
198-
CARBON_ASSIGN_OR_RETURN(
199-
Filesystem::Dir resource_dir,
200-
Filesystem::Cwd().CreateDirectories(resource_dir_path));
197+
CARBON_ASSIGN_OR_RETURN(auto build_dir,
198+
runtimes.Build(Runtimes::ClangResourceDir));
199+
if (std::holds_alternative<std::filesystem::path>(build_dir)) {
200+
// Found cached build.
201+
return std::get<std::filesystem::path>(std::move(build_dir));
202+
}
203+
204+
auto builder = std::get<Runtimes::Builder>(std::move(build_dir));
205+
std::string target = features.target;
201206

202207
// Symlink the installation's `include` and `share` directories.
203208
std::filesystem::path install_resource_path =
204209
installation_->clang_resource_path();
205210
CARBON_RETURN_IF_ERROR(
206-
resource_dir.Symlink("include", install_resource_path / "include"));
211+
builder.dir().Symlink("include", install_resource_path / "include"));
207212
CARBON_RETURN_IF_ERROR(
208-
resource_dir.Symlink("share", install_resource_path / "share"));
213+
builder.dir().Symlink("share", install_resource_path / "share"));
209214

210215
// Create the target's `lib` directory.
211216
std::filesystem::path lib_path =
212217
std::filesystem::path("lib") / std::string_view(target);
213218
CARBON_ASSIGN_OR_RETURN(Filesystem::Dir lib_dir,
214-
resource_dir.CreateDirectories(lib_path));
219+
builder.dir().CreateDirectories(lib_path));
215220

216221
llvm::Triple target_triple(target);
217222
if (target_triple.isOSWindows()) {
@@ -222,15 +227,15 @@ auto ClangRunner::BuildTargetResourceDir(
222227
// provide the CRT begin/end files, and so we need to build them.
223228
if (target_triple.isOSLinux()) {
224229
BuildCrtFile(target, RuntimeSources::CrtBegin,
225-
resource_dir_path / lib_path / "clang_rt.crtbegin.o");
230+
builder.path() / lib_path / "clang_rt.crtbegin.o");
226231
BuildCrtFile(target, RuntimeSources::CrtEnd,
227-
resource_dir_path / lib_path / "clang_rt.crtend.o");
232+
builder.path() / lib_path / "clang_rt.crtend.o");
228233
}
229234

230235
CARBON_RETURN_IF_ERROR(
231236
BuildBuiltinsLib(target, target_triple, tmp_path, lib_dir));
232237

233-
return Success();
238+
return std::move(builder).Commit();
234239
}
235240

236241
auto ClangRunner::RunInternal(
@@ -508,11 +513,13 @@ auto ClangRunner::BuildBuiltinsLib(llvm::StringRef target,
508513
objs.push_back(std::move(*obj));
509514
}
510515

511-
// Now build an archive out of the `.o` files for the builtins.
516+
// Now build an archive out of the `.o` files for the builtins. Note that we
517+
// build this directly into the `lib_dir` as this is expected to be a staging
518+
// directory and cleaned up on errors.
512519
std::filesystem::path builtins_a_path = "libclang_rt.builtins.a";
513520
CARBON_ASSIGN_OR_RETURN(
514521
Filesystem::WriteFile builtins_a_file,
515-
tmp_dir.OpenWriteOnly(builtins_a_path, Filesystem::CreateAlways));
522+
lib_dir.OpenWriteOnly(builtins_a_path, Filesystem::CreateAlways));
516523
{
517524
llvm::raw_fd_ostream builtins_a_os = builtins_a_file.WriteStream();
518525

@@ -528,10 +535,6 @@ auto ClangRunner::BuildBuiltinsLib(llvm::StringRef target,
528535
}
529536
CARBON_RETURN_IF_ERROR(std::move(builtins_a_file).Close());
530537

531-
// Move it into the lib directory.
532-
CARBON_RETURN_IF_ERROR(
533-
tmp_dir.Rename(builtins_a_path, lib_dir, builtins_a_path));
534-
535538
return Success();
536539
}
537540

0 commit comments

Comments
 (0)