Skip to content

Commit 371e3c9

Browse files
[CAS] Teach swift-frontend to replay result from cache
1 parent ab0eeb7 commit 371e3c9

File tree

6 files changed

+201
-0
lines changed

6 files changed

+201
-0
lines changed

include/swift/AST/DiagnosticsFrontend.def

+3
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,9 @@ REMARK(matching_output_produced,none,
489489
ERROR(error_caching_no_cas_fs, none,
490490
"caching is enabled without -cas-fs option, input is not immutable", ())
491491

492+
REMARK(replay_output, none, "replay output file '%0': key '%1'", (StringRef, StringRef))
493+
REMARK(output_cache_miss, none, "cache miss output file '%0': key '%1'", (StringRef, StringRef))
494+
492495
// CAS related diagnostics
493496
ERROR(error_create_cas, none, "failed to create CAS '%0' (%1)", (StringRef, StringRef))
494497
ERROR(error_invalid_cas_id, none, "invalid CASID '%0' (%1)", (StringRef, StringRef))

include/swift/Frontend/CachingUtils.h

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef SWIFT_FRONTEND_CACHINGUTILS_H
1414
#define SWIFT_FRONTEND_CACHINGUTILS_H
1515

16+
#include "swift/Frontend/CachedDiagnostics.h"
1617
#include "swift/Frontend/FrontendInputsAndOutputs.h"
1718
#include "llvm/ADT/IntrusiveRefCntPtr.h"
1819
#include "llvm/CAS/ActionCache.h"
@@ -31,6 +32,14 @@ createSwiftCachingOutputBackend(
3132
llvm::cas::ObjectRef BaseKey,
3233
const FrontendInputsAndOutputs &InputsAndOutputs);
3334

35+
/// Replay the output of the compilation from cache.
36+
/// Return true if outputs are replayed, false otherwise.
37+
bool replayCachedCompilerOutputs(
38+
llvm::cas::ObjectStore &CAS, llvm::cas::ActionCache &Cache,
39+
llvm::cas::ObjectRef BaseKey, DiagnosticEngine &Diag,
40+
const FrontendInputsAndOutputs &InputsAndOutputs,
41+
CachingDiagnosticsProcessor &CDP);
42+
3443
/// Load the cached compile result from cache.
3544
std::unique_ptr<llvm::MemoryBuffer> loadCachedCompileResultFromCacheKey(
3645
llvm::cas::ObjectStore &CAS, llvm::cas::ActionCache &Cache,

lib/Frontend/CachingUtils.cpp

+128
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,134 @@ createSwiftCachingOutputBackend(
133133
InputsAndOutputs);
134134
}
135135

136+
bool replayCachedCompilerOutputs(
137+
ObjectStore &CAS, ActionCache &Cache, ObjectRef BaseKey,
138+
DiagnosticEngine &Diag, const FrontendInputsAndOutputs &InputsAndOutputs,
139+
CachingDiagnosticsProcessor &CDP) {
140+
clang::cas::CompileJobResultSchema Schema(CAS);
141+
bool CanReplayAllOutput = true;
142+
struct OutputEntry {
143+
std::string Path;
144+
std::string Key;
145+
llvm::cas::ObjectProxy Proxy;
146+
};
147+
SmallVector<OutputEntry> OutputProxies;
148+
149+
auto replayOutputFile = [&](StringRef InputName, file_types::ID OutputKind,
150+
StringRef OutputPath) -> Optional<OutputEntry> {
151+
LLVM_DEBUG(llvm::dbgs()
152+
<< "DEBUG: lookup output \'" << OutputPath << "\' type \'"
153+
<< file_types::getTypeName(OutputKind) << "\' input \'"
154+
<< InputName << "\n";);
155+
156+
auto OutputKey =
157+
createCompileJobCacheKeyForOutput(CAS, BaseKey, InputName, OutputKind);
158+
if (!OutputKey) {
159+
Diag.diagnose(SourceLoc(), diag::error_cas,
160+
toString(OutputKey.takeError()));
161+
return None;
162+
}
163+
auto OutputKeyID = CAS.getID(*OutputKey);
164+
auto Lookup = Cache.get(OutputKeyID);
165+
if (!Lookup) {
166+
Diag.diagnose(SourceLoc(), diag::error_cas, toString(Lookup.takeError()));
167+
return None;
168+
}
169+
if (!*Lookup) {
170+
Diag.diagnose(SourceLoc(), diag::output_cache_miss, OutputPath,
171+
OutputKeyID.toString());
172+
return None;
173+
}
174+
auto OutputRef = CAS.getReference(**Lookup);
175+
if (!OutputRef) {
176+
return None;
177+
}
178+
auto Result = Schema.load(*OutputRef);
179+
if (!Result) {
180+
Diag.diagnose(SourceLoc(), diag::error_cas, toString(Result.takeError()));
181+
return None;
182+
}
183+
auto MainOutput = Result->getOutput(
184+
clang::cas::CompileJobCacheResult::OutputKind::MainOutput);
185+
if (!MainOutput) {
186+
return None;
187+
}
188+
auto LoadedResult = CAS.getProxy(MainOutput->Object);
189+
if (!LoadedResult) {
190+
Diag.diagnose(SourceLoc(), diag::error_cas,
191+
toString(LoadedResult.takeError()));
192+
return None;
193+
}
194+
195+
return OutputEntry{OutputPath.str(), OutputKeyID.toString(), *LoadedResult};
196+
};
197+
198+
auto replayOutputFromInput = [&](const InputFile &Input) {
199+
auto InputPath = Input.getFileName();
200+
if (!Input.outputFilename().empty()) {
201+
if (auto Result = replayOutputFile(
202+
InputPath, InputsAndOutputs.getPrincipalOutputType(),
203+
Input.outputFilename()))
204+
OutputProxies.emplace_back(*Result);
205+
else
206+
CanReplayAllOutput = false;
207+
}
208+
209+
Input.getPrimarySpecificPaths()
210+
.SupplementaryOutputs.forEachSetOutputAndType(
211+
[&](const std::string &File, file_types::ID ID) {
212+
if (ID == file_types::ID::TY_SerializedDiagnostics)
213+
return;
214+
215+
if (auto Result = replayOutputFile(InputPath, ID, File))
216+
OutputProxies.emplace_back(*Result);
217+
else
218+
CanReplayAllOutput = false;
219+
});
220+
};
221+
222+
llvm::for_each(InputsAndOutputs.getAllInputs(), replayOutputFromInput);
223+
224+
auto DiagnosticsOutput = replayOutputFile(
225+
"<cached-diagnostics>", file_types::ID::TY_CachedDiagnostics,
226+
"<cached-diagnostics>");
227+
if (!DiagnosticsOutput)
228+
CanReplayAllOutput = false;
229+
230+
if (!CanReplayAllOutput)
231+
return false;
232+
233+
// Replay Diagnostics first so the output failures comes after.
234+
// Also if the diagnostics replay failed, proceed to re-compile.
235+
if (auto E = CDP.replayCachedDiagnostics(
236+
DiagnosticsOutput->Proxy.getData())) {
237+
Diag.diagnose(SourceLoc(), diag::error_replay_cached_diag,
238+
toString(std::move(E)));
239+
return false;
240+
}
241+
242+
// Replay the result only when everything is resolved.
243+
// Use on disk output backend directly here to write to disk.
244+
llvm::vfs::OnDiskOutputBackend Backend;
245+
for (auto &Output : OutputProxies) {
246+
auto File = Backend.createFile(Output.Path);
247+
if (!File) {
248+
Diag.diagnose(SourceLoc(), diag::error_opening_output, Output.Path,
249+
toString(File.takeError()));
250+
continue;
251+
}
252+
*File << Output.Proxy.getData();
253+
if (auto E = File->keep()) {
254+
Diag.diagnose(SourceLoc(), diag::error_closing_output, Output.Path,
255+
toString(std::move(E)));
256+
continue;
257+
}
258+
Diag.diagnose(SourceLoc(), diag::replay_output, Output.Path, Output.Key);
259+
}
260+
261+
return true;
262+
}
263+
136264
static Expected<std::unique_ptr<llvm::MemoryBuffer>>
137265
loadCachedCompileResultFromCacheKeyImpl(ObjectStore &CAS, ActionCache &Cache,
138266
StringRef CacheKey,

lib/Frontend/CompileJobCacheKey.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ llvm::Expected<llvm::cas::ObjectRef> swift::createCompileJobBaseCacheKey(
4848
SkipNext = true;
4949
continue;
5050
}
51+
// FIXME: Use a heuristic to remove all the flags that affect output paths.
52+
// Those should not affect compile cache key.
53+
if (Arg.startswith("-emit-")) {
54+
if (Arg.endswith("-path"))
55+
SkipNext = true;
56+
continue;
57+
}
5158
CommandLine.append(Arg);
5259
CommandLine.push_back(0);
5360
}

lib/FrontendTool/FrontendTool.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
#include "swift/ConstExtract/ConstExtract.h"
5252
#include "swift/DependencyScan/ScanDependencies.h"
5353
#include "swift/Frontend/AccumulatingDiagnosticConsumer.h"
54+
#include "swift/Frontend/CachedDiagnostics.h"
55+
#include "swift/Frontend/CachingUtils.h"
56+
#include "swift/Frontend/CompileJobCacheKey.h"
5457
#include "swift/Frontend/Frontend.h"
5558
#include "swift/Frontend/ModuleInterfaceLoader.h"
5659
#include "swift/Frontend/ModuleInterfaceSupport.h"
@@ -1380,6 +1383,35 @@ static bool performAction(CompilerInstance &Instance,
13801383
return Instance.getASTContext().hadError();
13811384
}
13821385

1386+
/// Try replay the compiler result from cache.
1387+
///
1388+
/// Return true if all the outputs are fetched from cache. Otherwise, return
1389+
/// false and will not replay any output.
1390+
static bool tryReplayCompilerResults(CompilerInstance &Instance) {
1391+
if (!Instance.supportCaching())
1392+
return false;
1393+
1394+
assert(Instance.getCompilerBaseKey() &&
1395+
"Instance is not setup correctly for replay");
1396+
1397+
auto *CDP = Instance.getCachingDiagnosticsProcessor();
1398+
assert(CDP && "CachingDiagnosticsProcessor needs to be setup for replay");
1399+
1400+
// Don't capture diagnostics from replay.
1401+
CDP->endDiagnosticCapture();
1402+
1403+
bool replayed = replayCachedCompilerOutputs(
1404+
Instance.getObjectStore(), Instance.getActionCache(),
1405+
*Instance.getCompilerBaseKey(), Instance.getDiags(),
1406+
Instance.getInvocation().getFrontendOptions().InputsAndOutputs, *CDP);
1407+
1408+
// If we didn't replay successfully, re-start capture.
1409+
if (!replayed)
1410+
CDP->startDiagnosticCapture();
1411+
1412+
return replayed;
1413+
}
1414+
13831415
/// Performs the compile requested by the user.
13841416
/// \param Instance Will be reset after performIRGeneration when the verifier
13851417
/// mode is NoVerify and there were no errors.
@@ -1391,6 +1423,9 @@ static bool performCompile(CompilerInstance &Instance,
13911423
const auto &opts = Invocation.getFrontendOptions();
13921424
const FrontendOptions::ActionType Action = opts.RequestedAction;
13931425

1426+
if (tryReplayCompilerResults(Instance))
1427+
return false;
1428+
13941429
// To compile LLVM IR, just pass it off unmodified.
13951430
if (opts.InputsAndOutputs.shouldTreatAsLLVM())
13961431
return compileLLVMIR(Instance);

test/CAS/cache_replay.swift

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %empty-directory(%t)
2+
3+
/// Run the command first time, expect cache miss.
4+
/// FIXME: This command doesn't use `-cas-fs` so it is not a good cache entry. It is currently allowed so it is easier to write tests.
5+
// RUN: %target-swift-frontend -enable-cas %s -emit-module -emit-module-path %t/Test.swiftmodule -c -emit-dependencies \
6+
// RUN: -module-name Test -o %t/test.o -cas-path %t/cas -allow-unstable-cache-key-for-testing 2>&1 | %FileCheck --check-prefix=CACHE-MISS %s
7+
8+
/// Expect cache hit for second time.
9+
// RUN: %target-swift-frontend -enable-cas %s -emit-module -emit-module-path %t/Test.swiftmodule -c -emit-dependencies \
10+
// RUN: -module-name Test -o %t/test.o -cas-path %t/cas -allow-unstable-cache-key-for-testing 2>&1 | %FileCheck --check-prefix=CACHE-HIT %s
11+
12+
/// Expect cache hit a subset of outputs.
13+
// RUN: %target-swift-frontend -enable-cas %s -emit-module -emit-module-path %t/Test.swiftmodule -c \
14+
// RUN: -module-name Test -o %t/test.o -cas-path %t/cas -allow-unstable-cache-key-for-testing 2>&1 | %FileCheck --check-prefix=CACHE-HIT %s
15+
16+
// CACHE-MISS: remark: cache miss output file
17+
// CACHE-HIT: remark: replay output file
18+
19+
func testFunc() {}

0 commit comments

Comments
 (0)