Skip to content

Commit f2983ea

Browse files
committed
Implement global index store cache
#567 swift and clang write index data based on the status of what resides in `index-store-path`. When using a transient per-swift-library index, it writes O( M imports * N libs) indexer data and slows down compilation significantly: to the tune of 6GB+ index data in my testing. Bazel likes to use per `swift_library` data in order to remote cache them, which needs extra consideration to work with the design of swift and clang and preserve the performance This PR does 2 things to make index while building use the original model of the index-store so we don't have to patch clang and swift for this yet. When the feature, `swift.use_global_index_store`, is set: 1. it writes to a global index cache, `bazel-out/global_index_store` 2. it copies indexstore data (records and units) to where Bazel expected them for caching: e.g. the original location with `swift.index_while_building` I added a detailed description of this feature in the RFC to add it there MobileNativeFoundation/index-import#53. In practice, transitive imports will be indexed by deps and if they aren't cached then the fallback is Xcode wil index transitive files if we set those as deps on the unit files
1 parent 254e3fa commit f2983ea

File tree

6 files changed

+235
-23
lines changed

6 files changed

+235
-23
lines changed

swift/internal/compiling.bzl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ load(
5858
"SWIFT_FEATURE_SUPPORTS_SYSTEM_MODULE_FLAG",
5959
"SWIFT_FEATURE_SYSTEM_MODULE",
6060
"SWIFT_FEATURE_USE_C_MODULES",
61+
"SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE",
6162
"SWIFT_FEATURE_USE_GLOBAL_MODULE_CACHE",
6263
"SWIFT_FEATURE_VFSOVERLAY",
6364
"SWIFT_FEATURE__NUM_THREADS_0_IN_SWIFTCOPTS",
@@ -805,6 +806,14 @@ def compile_action_configs(
805806
],
806807
features = [SWIFT_FEATURE_ENABLE_SKIP_FUNCTION_BODIES],
807808
),
809+
swift_toolchain_config.action_config(
810+
actions = [swift_action_names.COMPILE],
811+
configurators = [_global_index_store_configurator],
812+
features = [
813+
SWIFT_FEATURE_INDEX_WHILE_BUILDING,
814+
SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE,
815+
],
816+
),
808817

809818
# Configure index-while-building.
810819
swift_toolchain_config.action_config(
@@ -1419,6 +1428,12 @@ def _index_while_building_configurator(prerequisites, args):
14191428
if not _index_store_path_overridden(prerequisites.user_compile_flags):
14201429
args.add("-index-store-path", prerequisites.indexstore_directory.path)
14211430

1431+
def _global_index_store_configurator(prerequisites, args):
1432+
"""Adds flags for index-store generation to the command line."""
1433+
out_dir = prerequisites.indexstore_directory.dirname.split("/")[0]
1434+
path = out_dir + "/_global_index_store"
1435+
args.add("-Xwrapped-swift=-global-index-store-import-path=" + path)
1436+
14221437
def _conditional_compilation_flag_configurator(prerequisites, args):
14231438
"""Adds (non-Clang) conditional compilation flags to the command line."""
14241439
all_defines = depset(

swift/internal/feature_names.bzl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,15 @@ SWIFT_FEATURE_ENABLE_TESTING = "swift.enable_testing"
9999
SWIFT_FEATURE_FULL_DEBUG_INFO = "swift.full_debug_info"
100100

101101
# If enabled, the compilation action for a target will produce an index store.
102+
# https://docs.google.com/document/d/1cH2sTpgSnJZCkZtJl1aY-rzy4uGPcrI-6RrUpdATO2Q/
102103
SWIFT_FEATURE_INDEX_WHILE_BUILDING = "swift.index_while_building"
103104

104105
# If enabled the compilation action will not produce indexes for system modules.
105106
SWIFT_FEATURE_DISABLE_SYSTEM_INDEX = "swift.disable_system_index"
106107

108+
# Index while building - using a global index store cache
109+
SWIFT_FEATURE_USE_GLOBAL_INDEX_STORE = "swift.use_global_index_store"
110+
107111
# If enabled, compilation actions and module map generation will assume that the
108112
# header paths in module maps are relative to the current working directory
109113
# (i.e., the workspace root); if disabled, header paths in module maps are

swift/repositories.bzl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ def swift_rules_dependencies():
108108
type = "zip",
109109
)
110110

111+
# It relies on `index-import` to import indexes into Bazel's remote
112+
# cache and allow using a global index internally in workers.
113+
# Note: this is only loaded if swift.index_while_building_v2 is enabled
114+
_maybe(
115+
http_archive,
116+
name = "build_bazel_rules_swift_index_import",
117+
build_file_content = """\
118+
load("@bazel_skylib//rules:native_binary.bzl", "native_binary")
119+
120+
native_binary(
121+
name = "index_import",
122+
src = "index-import",
123+
out = "index-import",
124+
visibility = ["//visibility:public"],
125+
)
126+
""",
127+
canonical_id = "index-import-5.3.2.6",
128+
urls = ["https://github.com/MobileNativeFoundation/index-import/releases/download/5.3.2.6/index-import.zip"],
129+
sha256 = "61a58363f56c5fd84d4ebebe0d9b5dd90c74ae170405a7b9018e8cf698e679de",
130+
)
131+
111132
_maybe(
112133
swift_autoconfiguration,
113134
name = "build_bazel_rules_swift_local_config",

tools/worker/BUILD

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@ config_setting(
1818
},
1919
)
2020

21+
# Internal hinge for index while building V2 feature
22+
config_setting(
23+
name = "use_global_index_store",
24+
values = {
25+
"features": "swift.use_global_index_store",
26+
},
27+
)
28+
2129
cc_library(
2230
name = "compile_with_worker",
2331
srcs = [
2432
"compile_with_worker.cc",
25-
"output_file_map.cc",
26-
"output_file_map.h",
2733
"work_processor.cc",
2834
"work_processor.h",
2935
],
@@ -34,7 +40,6 @@ cc_library(
3440
"//tools/common:file_system",
3541
"//tools/common:path_utils",
3642
"//tools/common:temp_file",
37-
"@com_github_nlohmann_json//:json",
3843
"@com_google_protobuf//:protobuf",
3944
],
4045
)
@@ -50,13 +55,30 @@ cc_library(
5055

5156
cc_library(
5257
name = "swift_runner",
53-
srcs = ["swift_runner.cc"],
58+
srcs = [
59+
"output_file_map.cc",
60+
"output_file_map.h",
61+
"swift_runner.cc",
62+
],
5463
hdrs = ["swift_runner.h"],
64+
copts = select({
65+
":use_global_index_store": [
66+
"-DINDEX_IMPORT_PATH=\\\"$(rootpath @build_bazel_rules_swift_index_import//:index_import)\\\"",
67+
],
68+
"//conditions:default": [],
69+
}),
70+
data = select({
71+
":use_global_index_store": [
72+
"@build_bazel_rules_swift_index_import//:index_import",
73+
],
74+
"//conditions:default": [],
75+
}),
5576
deps = [
5677
"//tools/common:bazel_substitutions",
5778
"//tools/common:file_system",
5879
"//tools/common:process",
5980
"//tools/common:temp_file",
81+
"@com_github_nlohmann_json//:json",
6082
],
6183
)
6284

tools/worker/swift_runner.cc

Lines changed: 150 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "tools/common/file_system.h"
2121
#include "tools/common/process.h"
2222
#include "tools/common/temp_file.h"
23+
#include "tools/worker/output_file_map.h"
2324

2425
namespace {
2526

@@ -129,29 +130,105 @@ int SwiftRunner::Run(std::ostream *stderr_stream, bool stdout_to_stderr) {
129130
exit_code = RunSubProcess(rewriter_args, stderr_stream, stdout_to_stderr);
130131
}
131132

133+
auto enable_global_index_store = global_index_store_import_path_ != "";
134+
if (enable_global_index_store) {
135+
OutputFileMap output_file_map;
136+
output_file_map.ReadFromPath(output_file_map_path_);
137+
138+
auto outputs = output_file_map.incremental_outputs();
139+
std::map<std::string, std::string>::iterator it;
140+
141+
std::vector<std::string> ii_args;
142+
// The index-import runfile path is passed as a define
143+
#if defined(INDEX_IMPORT_PATH)
144+
ii_args.push_back(INDEX_IMPORT_PATH);
145+
#else
146+
// Logical error
147+
std::cerr << "Incorrectly compiled work_processor.cc";
148+
exit_code = EXIT_FAILURE;
149+
return exit_code;
150+
#endif
151+
152+
for (it = outputs.begin(); it != outputs.end(); it++) {
153+
// Need the actual output paths of the compiler - not bazel
154+
auto output_path = it->first;
155+
auto file_type = output_path.substr(output_path.find_last_of(".") + 1);
156+
if (file_type == "o") {
157+
ii_args.push_back("-import-output-file");
158+
ii_args.push_back(output_path);
159+
}
160+
}
161+
162+
auto exec_root = GetCurrentDirectory();
163+
// Copy back from the global index store to bazel's index store
164+
ii_args.push_back(exec_root + "/" + global_index_store_import_path_);
165+
ii_args.push_back(exec_root + "/" + index_store_path_);
166+
exit_code =
167+
RunSubProcess(ii_args, stderr_stream, /*stdout_to_stderr=*/true);
168+
}
132169
return exit_code;
133170
}
134171

172+
// Marker for end of iteration
173+
class StreamIteratorEnd {};
174+
175+
// Basic iterator over an ifstream
176+
class StreamIterator {
177+
public:
178+
StreamIterator(std::ifstream &file) : file_{file} { next(); }
179+
180+
const std::string &operator*() const { return str_; }
181+
182+
StreamIterator &operator++() {
183+
next();
184+
return *this;
185+
}
186+
187+
bool operator!=(StreamIteratorEnd) const { return !!file_; }
188+
189+
private:
190+
void next() { std::getline(file_, str_); }
191+
192+
std::ifstream &file_;
193+
std::string str_;
194+
};
195+
196+
class ArgsFile {
197+
public:
198+
ArgsFile(std::ifstream &file) : file_(file) {}
199+
200+
StreamIterator begin() { return StreamIterator{file_}; }
201+
202+
StreamIteratorEnd end() { return StreamIteratorEnd{}; }
203+
204+
private:
205+
std::ifstream &file_;
206+
};
207+
135208
bool SwiftRunner::ProcessPossibleResponseFile(
136209
const std::string &arg, std::function<void(const std::string &)> consumer) {
137210
auto path = arg.substr(1);
138211
std::ifstream original_file(path);
212+
ArgsFile args_file(original_file);
213+
139214
// If we couldn't open it, maybe it's not a file; maybe it's just some other
140215
// argument that starts with "@" such as "@loader_path/..."
141216
if (!original_file.good()) {
142217
consumer(arg);
143218
return false;
144219
}
145220

221+
// Read the file to a vector to prevent double I/O
222+
auto args = ParseArguments(args_file);
223+
146224
// If we're forcing response files, process and send the arguments from this
147225
// file directly to the consumer; they'll all get written to the same response
148226
// file at the end of processing all the arguments.
149227
if (force_response_file_) {
150-
std::string arg_from_file;
151-
while (std::getline(original_file, arg_from_file)) {
228+
for (auto it = args.begin(); it != args.end(); ++it) {
152229
// Arguments in response files might be quoted/escaped, so we need to
153230
// unescape them ourselves.
154-
ProcessArgument(Unescape(arg_from_file), consumer);
231+
ProcessArgument(it, Unescape(*it), consumer);
155232
}
156233
return true;
157234
}
@@ -161,12 +238,10 @@ bool SwiftRunner::ProcessPossibleResponseFile(
161238
bool changed = false;
162239
std::string arg_from_file;
163240
std::vector<std::string> new_args;
164-
165-
while (std::getline(original_file, arg_from_file)) {
166-
changed |=
167-
ProcessArgument(arg_from_file, [&](const std::string &processed_arg) {
168-
new_args.push_back(processed_arg);
169-
});
241+
for (auto it = args.begin(); it != args.end(); ++it) {
242+
changed |= ProcessArgument(it, *it, [&](const std::string &processed_arg) {
243+
new_args.push_back(processed_arg);
244+
});
170245
}
171246

172247
if (changed) {
@@ -182,10 +257,11 @@ bool SwiftRunner::ProcessPossibleResponseFile(
182257
return changed;
183258
}
184259

260+
template <typename Iterator>
185261
bool SwiftRunner::ProcessArgument(
186-
const std::string &arg, std::function<void(const std::string &)> consumer) {
262+
Iterator &itr, const std::string &arg,
263+
std::function<void(const std::string &)> consumer) {
187264
bool changed = false;
188-
189265
if (arg[0] == '@') {
190266
changed = ProcessPossibleResponseFile(arg, consumer);
191267
} else {
@@ -198,8 +274,8 @@ bool SwiftRunner::ProcessArgument(
198274
consumer(GetCurrentDirectory() + "=.");
199275
changed = true;
200276
} else if (new_arg == "-coverage-prefix-pwd-is-dot") {
201-
// Get the actual current working directory (the workspace root), which we
202-
// didn't know at analysis time.
277+
// Get the actual current working directory (the workspace root), which
278+
// we didn't know at analysis time.
203279
consumer("-coverage-prefix-map");
204280
consumer(GetCurrentDirectory() + "=.");
205281
changed = true;
@@ -213,14 +289,37 @@ bool SwiftRunner::ProcessArgument(
213289
temp_directories_.push_back(std::move(module_cache_dir));
214290
changed = true;
215291
} else if (StripPrefix("-generated-header-rewriter=", new_arg)) {
216-
generated_header_rewriter_path_ = new_arg;
292+
changed = true;
293+
} else if (StripPrefix("-global-index-store-import-path=", new_arg)) {
217294
changed = true;
218295
} else {
219296
// TODO(allevato): Report that an unknown wrapper arg was found and give
220297
// the caller a way to exit gracefully.
221298
changed = true;
222299
}
223300
} else {
301+
// Process default arguments
302+
if (arg == "-index-store-path") {
303+
consumer("-index-store-path");
304+
++itr;
305+
306+
// If there was a global index store set, pass that to swiftc.
307+
// Otherwise, pass the users. We later copy index data onto the users.
308+
if (global_index_store_import_path_ != "") {
309+
new_arg = global_index_store_import_path_;
310+
} else {
311+
new_arg = index_store_path_;
312+
}
313+
changed = true;
314+
} else if (arg == "-output-file-map") {
315+
// Save the output file map to the value proceeding
316+
// `-output-file-map`
317+
consumer("-output-file-map");
318+
++itr;
319+
new_arg = output_file_map_path_;
320+
changed = true;
321+
}
322+
224323
// Apply any other text substitutions needed in the argument (i.e., for
225324
// Apple toolchains).
226325
//
@@ -236,6 +335,36 @@ bool SwiftRunner::ProcessArgument(
236335
return changed;
237336
}
238337

338+
template <typename Iterator>
339+
std::vector<std::string> SwiftRunner::ParseArguments(Iterator itr) {
340+
std::vector<std::string> out_args;
341+
for (auto it = itr.begin(); it != itr.end(); ++it) {
342+
auto arg = *it;
343+
out_args.push_back(arg);
344+
345+
if (StripPrefix("-Xwrapped-swift=", arg)) {
346+
if (StripPrefix("-global-index-store-import-path=", arg)) {
347+
global_index_store_import_path_ = arg;
348+
} else if (StripPrefix("-generated-header-rewriter=", arg)) {
349+
generated_header_rewriter_path_ = arg;
350+
}
351+
} else {
352+
if (arg == "-output-file-map") {
353+
++it;
354+
arg = *it;
355+
output_file_map_path_ = arg;
356+
out_args.push_back(arg);
357+
} else if (arg == "-index-store-path") {
358+
++it;
359+
arg = *it;
360+
index_store_path_ = arg;
361+
out_args.push_back(arg);
362+
}
363+
}
364+
}
365+
return out_args;
366+
}
367+
239368
std::vector<std::string> SwiftRunner::ProcessArguments(
240369
const std::vector<std::string> &args) {
241370
std::vector<std::string> new_args;
@@ -248,16 +377,19 @@ std::vector<std::string> SwiftRunner::ProcessArguments(
248377
#endif
249378

250379
// The tool is assumed to be the first argument. Push it directly.
251-
auto it = args.begin();
380+
auto parsed_args = ParseArguments(args);
381+
382+
auto it = parsed_args.begin();
252383
new_args.push_back(*it++);
253384

254385
// If we're forcing response files, push the remaining processed args onto a
255386
// different vector that we write out below. If not, push them directly onto
256387
// the vector being returned.
257388
auto &args_destination = force_response_file_ ? response_file_args : new_args;
258-
while (it != args.end()) {
259-
ProcessArgument(
260-
*it, [&](const std::string &arg) { args_destination.push_back(arg); });
389+
while (it != parsed_args.end()) {
390+
ProcessArgument(it, *it, [&](const std::string &arg) {
391+
args_destination.push_back(arg);
392+
});
261393
++it;
262394
}
263395

0 commit comments

Comments
 (0)