Skip to content

Commit 7a7657b

Browse files
authored
Merge pull request swiftlang#19935 from graydon/text-interface-to-module
2 parents b50d904 + b4a96bb commit 7a7657b

File tree

11 files changed

+203
-77
lines changed

11 files changed

+203
-77
lines changed

include/swift/AST/FileSystem.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//===--- FileSystem.h - File helpers that interact with Diags ---*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_AST_FILESYSTEM_H
14+
#define SWIFT_AST_FILESYSTEM_H
15+
16+
#include "swift/Basic/FileSystem.h"
17+
#include "swift/AST/DiagnosticEngine.h"
18+
19+
namespace swift {
20+
21+
/// A wrapper around swift::atomicallyWritingToFile that handles diagnosing any
22+
/// filesystem errors and asserts the output path is nonempty.
23+
///
24+
/// \returns true if there were any errors, either from the filesystem
25+
/// operations or from \p action returning true.
26+
inline bool
27+
withOutputFile(DiagnosticEngine &diags, StringRef outputPath,
28+
llvm::function_ref<bool(llvm::raw_pwrite_stream &)> action) {
29+
assert(!outputPath.empty());
30+
31+
bool actionFailed = false;
32+
std::error_code EC = swift::atomicallyWritingToFile(
33+
outputPath,
34+
[&](llvm::raw_pwrite_stream &out) { actionFailed = action(out); });
35+
if (EC) {
36+
diags.diagnose(SourceLoc(), diag::error_opening_output, outputPath,
37+
EC.message());
38+
return true;
39+
}
40+
return actionFailed;
41+
}
42+
} // end namespace swift
43+
44+
#endif // SWIFT_AST_FILESYSTEM_H

include/swift/Basic/FileTypes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ TYPE("swiftmodule", SwiftModuleFile, "swiftmodule", "")
5050
TYPE("swiftdoc", SwiftModuleDocFile, "swiftdoc", "")
5151
TYPE("swiftinterface", SwiftParseableInterfaceFile, \
5252
"swiftinterface", "")
53+
TYPE("swiftinterfacedeps", SwiftParseableInterfaceDeps, \
54+
"sid", "")
5355
TYPE("assembly", Assembly, "s", "")
5456
TYPE("raw-sil", RawSIL, "sil", "")
5557
TYPE("raw-sib", RawSIB, "sib", "")

include/swift/Frontend/ParseableInterfaceSupport.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,10 @@ class ParseableInterfaceModuleLoader : public SerializedModuleLoaderBase {
6464
std::string CacheDir;
6565

6666
void
67-
configureSubInvocationAndOutputPath(CompilerInvocation &SubInvocation,
68-
StringRef InPath,
69-
llvm::SmallString<128> &OutPath);
67+
configureSubInvocationAndOutputPaths(CompilerInvocation &SubInvocation,
68+
StringRef InPath,
69+
llvm::SmallString<128> &OutPath,
70+
llvm::SmallString<128> &DepPath);
7071

7172
std::error_code
7273
openModuleFiles(StringRef DirName, StringRef ModuleFilename,

lib/Basic/FileTypes.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ bool file_types::isTextual(ID Id) {
7979
case file_types::TY_ModuleTrace:
8080
case file_types::TY_OptRecord:
8181
case file_types::TY_SwiftParseableInterfaceFile:
82+
case file_types::TY_SwiftParseableInterfaceDeps:
8283
return true;
8384
case file_types::TY_Image:
8485
case file_types::TY_Object:
@@ -135,6 +136,7 @@ bool file_types::isAfterLLVM(ID Id) {
135136
case file_types::TY_ModuleTrace:
136137
case file_types::TY_OptRecord:
137138
case file_types::TY_SwiftParseableInterfaceFile:
139+
case file_types::TY_SwiftParseableInterfaceDeps:
138140
return false;
139141
case file_types::TY_INVALID:
140142
llvm_unreachable("Invalid type ID.");
@@ -167,6 +169,7 @@ bool file_types::isPartOfSwiftCompilation(ID Id) {
167169
case file_types::TY_SwiftModuleFile:
168170
case file_types::TY_SwiftModuleDocFile:
169171
case file_types::TY_SwiftParseableInterfaceFile:
172+
case file_types::TY_SwiftParseableInterfaceDeps:
170173
case file_types::TY_SerializedDiagnostics:
171174
case file_types::TY_ClangModuleFile:
172175
case file_types::TY_SwiftDeps:

lib/Driver/Driver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,7 @@ void Driver::buildActions(SmallVectorImpl<const Action *> &TopLevelActions,
17801780
case file_types::TY_ModuleTrace:
17811781
case file_types::TY_OptRecord:
17821782
case file_types::TY_SwiftParseableInterfaceFile:
1783+
case file_types::TY_SwiftParseableInterfaceDeps:
17831784
// We could in theory handle assembly or LLVM input, but let's not.
17841785
// FIXME: What about LTO?
17851786
Diags.diagnose(SourceLoc(), diag::error_unexpected_input_file,

lib/Driver/ToolChains.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ const char *ToolChain::JobContext::computeFrontendModeForCompile() const {
438438
case file_types::TY_TBD:
439439
case file_types::TY_OptRecord:
440440
case file_types::TY_SwiftParseableInterfaceFile:
441+
case file_types::TY_SwiftParseableInterfaceDeps:
441442
llvm_unreachable("Output type can never be primary output.");
442443
case file_types::TY_INVALID:
443444
llvm_unreachable("Invalid type ID");
@@ -670,6 +671,7 @@ ToolChain::constructInvocation(const BackendJobAction &job,
670671
case file_types::TY_ModuleTrace:
671672
case file_types::TY_OptRecord:
672673
case file_types::TY_SwiftParseableInterfaceFile:
674+
case file_types::TY_SwiftParseableInterfaceDeps:
673675
llvm_unreachable("Output type can never be primary output.");
674676
case file_types::TY_INVALID:
675677
llvm_unreachable("Invalid type ID");

lib/Frontend/ParseableInterfaceSupport.cpp

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
#include "swift/AST/ASTContext.h"
1515
#include "swift/AST/Decl.h"
1616
#include "swift/AST/DiagnosticsFrontend.h"
17+
#include "swift/AST/FileSystem.h"
1718
#include "swift/AST/Module.h"
1819
#include "swift/Frontend/Frontend.h"
1920
#include "swift/Frontend/ParseableInterfaceSupport.h"
2021
#include "swift/SILOptimizer/PassManager/Passes.h"
2122
#include "swift/Serialization/SerializationOptions.h"
2223
#include "clang/Basic/Module.h"
24+
#include "llvm/ADT/Hashing.h"
2325
#include "llvm/Support/Debug.h"
2426
#include "llvm/Support/CommandLine.h"
2527
#include "llvm/Support/CrashRecoveryContext.h"
@@ -31,6 +33,7 @@ using namespace swift;
3133

3234
#define SWIFT_TOOLS_VERSION_KEY "swift-tools-version"
3335
#define SWIFT_MODULE_FLAGS_KEY "swift-module-flags"
36+
#define SWIFT_INTERFACE_DEPS_VERSION "swift-interface-deps-version-1"
3437

3538
static bool
3639
extractSwiftInterfaceVersionAndArgs(DiagnosticEngine &Diags,
@@ -66,11 +69,44 @@ extractSwiftInterfaceVersionAndArgs(DiagnosticEngine &Diags,
6669
return false;
6770
}
6871

72+
/// Construct a cache key for the .swiftmodule being generated. There is a
73+
/// balance to be struck here between things that go in the cache key and
74+
/// things that go in the "up to date" check of the cache entry. We want to
75+
/// avoid fighting over a single cache entry too much when (say) running
76+
/// different compiler versions on the same machine or different inputs
77+
/// that happen to have the same short module name, so we will disambiguate
78+
/// those in the key. But we want to invalidate and rebuild a cache entry
79+
/// -- rather than making a new one and potentially filling up the cache
80+
/// with dead entries -- when other factors change, such as the contents of
81+
/// the .swiftinterface input or its dependencies.
82+
std::string getCacheHash(ASTContext &Ctx,
83+
CompilerInvocation &SubInvocation,
84+
StringRef InPath) {
85+
// Start with the compiler version (which will be either tag names or revs).
86+
std::string vers = swift::version::getSwiftFullVersion(
87+
Ctx.LangOpts.EffectiveLanguageVersion);
88+
llvm::hash_code H = llvm::hash_value(vers);
89+
90+
// Simplest representation of input "identity" (not content) is just a
91+
// pathname, and probably all we can get from the VFS in this regard anyways.
92+
H = llvm::hash_combine(H, InPath);
93+
94+
// ClangImporterOpts does include the target CPU, which is redundant: we
95+
// already have separate .swiftinterface files per target due to expanding
96+
// preprocessing directives, but further specializing the cache key to that
97+
// target is harmless and will not make any extra cache entries, so allow it.
98+
H = llvm::hash_combine(
99+
H, SubInvocation.getClangImporterOptions().getPCHHashComponents());
100+
101+
return llvm::APInt(64, H).toString(36, /*Signed=*/false);
102+
}
103+
69104
void
70-
ParseableInterfaceModuleLoader::configureSubInvocationAndOutputPath(
105+
ParseableInterfaceModuleLoader::configureSubInvocationAndOutputPaths(
71106
CompilerInvocation &SubInvocation,
72107
StringRef InPath,
73-
llvm::SmallString<128> &OutPath) {
108+
llvm::SmallString<128> &OutPath,
109+
llvm::SmallString<128> &DepPath) {
74110

75111
auto &SearchPathOpts = Ctx.SearchPathOpts;
76112
auto &LangOpts = Ctx.LangOpts;
@@ -84,16 +120,19 @@ ParseableInterfaceModuleLoader::configureSubInvocationAndOutputPath(
84120
SubInvocation.setRuntimeResourcePath(SearchPathOpts.RuntimeResourcePath);
85121
SubInvocation.setTargetTriple(LangOpts.Target);
86122

87-
// Calculate an output filename based on the SubInvocation hash, and
88-
// wire up the SubInvocation's InputsAndOutputs to contain both
89-
// input and output filenames.
123+
// Calculate an output filename that includes a hash of relevant key data, and
124+
// wire up the SubInvocation's InputsAndOutputs to contain both input and
125+
// output filenames.
90126
OutPath = CacheDir;
91127
llvm::sys::path::append(OutPath, llvm::sys::path::stem(InPath));
92128
OutPath.append("-");
93-
OutPath.append(SubInvocation.getPCHHash());
129+
OutPath.append(getCacheHash(Ctx, SubInvocation, InPath));
94130
OutPath.append(".");
95-
auto Ext = file_types::getExtension(file_types::TY_SwiftModuleFile);
96-
OutPath.append(Ext);
131+
DepPath = OutPath;
132+
auto OutExt = file_types::getExtension(file_types::TY_SwiftModuleFile);
133+
OutPath.append(OutExt);
134+
auto DepExt = file_types::getExtension(file_types::TY_SwiftParseableInterfaceDeps);
135+
DepPath.append(DepExt);
97136

98137
auto &FEOpts = SubInvocation.getFrontendOptions();
99138
FEOpts.RequestedAction = FrontendOptions::ActionType::EmitModuleOnly;
@@ -104,24 +143,67 @@ ParseableInterfaceModuleLoader::configureSubInvocationAndOutputPath(
104143
FEOpts.InputsAndOutputs.setMainAndSupplementaryOutputs({MainOut}, {SOPs});
105144
}
106145

107-
// FIXME: this needs to be a more extensive up-to-date check.
146+
// Write the world's simplest dependencies file: a version identifier on
147+
// a line followed by a list of files, one per line.
148+
static bool writeSwiftInterfaceDeps(DiagnosticEngine &Diags,
149+
ArrayRef<std::string> Deps,
150+
StringRef DepPath) {
151+
return withOutputFile(Diags, DepPath, [&](llvm::raw_pwrite_stream &out) {
152+
out << SWIFT_INTERFACE_DEPS_VERSION << '\n';
153+
for (auto const &D : Deps) {
154+
out << D << '\n';
155+
}
156+
return false;
157+
});
158+
}
159+
160+
// Check that the output .swiftmodule file is at least as new as all the
161+
// dependencies it read when it was built last time.
108162
static bool
109163
swiftModuleIsUpToDate(clang::vfs::FileSystem &FS,
110-
StringRef InPath, StringRef OutPath) {
111-
if (FS.exists(OutPath)) {
112-
auto InStatus = FS.status(InPath);
113-
auto OutStatus = FS.status(OutPath);
114-
if (InStatus && OutStatus) {
115-
return InStatus.get().getLastModificationTime() <=
116-
OutStatus.get().getLastModificationTime();
164+
StringRef InPath, StringRef OutPath, StringRef DepPath) {
165+
166+
if (!FS.exists(OutPath) || !FS.exists(DepPath))
167+
return false;
168+
169+
auto OutStatus = FS.status(OutPath);
170+
if (!OutStatus)
171+
return false;
172+
173+
auto DepBuf = FS.getBufferForFile(DepPath);
174+
if (!DepBuf)
175+
return false;
176+
177+
// Split the deps file into a vector of lines.
178+
StringRef Deps = DepBuf.get()->getBuffer();
179+
SmallVector<StringRef, 16> AllDeps;
180+
Deps.split(AllDeps, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false);
181+
182+
// First line in vector is a version-string; check it is the expected value.
183+
if (AllDeps.size() < 1 ||
184+
AllDeps[0] != SWIFT_INTERFACE_DEPS_VERSION) {
185+
return false;
186+
}
187+
188+
// Overwrite the version-string entry in the vector with the .swiftinterface
189+
// input file we're reading, then stat() every entry in the vector and check
190+
// none are newer than the .swiftmodule (OutStatus).
191+
AllDeps[0] = InPath;
192+
for (auto In : AllDeps) {
193+
auto InStatus = FS.status(In);
194+
if (!InStatus ||
195+
(InStatus.get().getLastModificationTime() >
196+
OutStatus.get().getLastModificationTime())) {
197+
return false;
117198
}
118199
}
119-
return false;
200+
return true;
120201
}
121202

122203
static bool buildSwiftModuleFromSwiftInterface(
123204
clang::vfs::FileSystem &FS, DiagnosticEngine &Diags,
124-
CompilerInvocation &SubInvocation, StringRef InPath, StringRef OutPath) {
205+
CompilerInvocation &SubInvocation, StringRef InPath, StringRef OutPath,
206+
StringRef DepPath) {
125207
bool SubError = false;
126208
bool RunSuccess = llvm::CrashRecoveryContext().RunSafelyOnThread([&] {
127209

@@ -145,6 +227,7 @@ static bool buildSwiftModuleFromSwiftInterface(
145227
// module-serialization task we're trying to do here.
146228
LLVM_DEBUG(llvm::dbgs() << "Setting up instance\n");
147229
CompilerInstance SubInstance;
230+
SubInstance.createDependencyTracker(/*TrackSystemDeps=*/false);
148231
if (SubInstance.setup(SubInvocation)) {
149232
SubError = true;
150233
return;
@@ -179,6 +262,12 @@ static bool buildSwiftModuleFromSwiftInterface(
179262
});
180263
SILMod->serialize();
181264
SubError = Diags.hadAnyError();
265+
266+
if (!SubError) {
267+
SubError |= writeSwiftInterfaceDeps(
268+
Diags, SubInstance.getDependencyTracker()->getDependencies(),
269+
DepPath);
270+
}
182271
});
183272
return !RunSuccess || SubError;
184273
}
@@ -194,7 +283,7 @@ std::error_code ParseableInterfaceModuleLoader::openModuleFiles(
194283

195284
auto &FS = *Ctx.SourceMgr.getFileSystem();
196285
auto &Diags = Ctx.Diags;
197-
llvm::SmallString<128> InPath, OutPath;
286+
llvm::SmallString<128> InPath, OutPath, DepPath;
198287

199288
// First check to see if the .swiftinterface exists at all. Bail if not.
200289
InPath = DirName;
@@ -207,12 +296,12 @@ std::error_code ParseableInterfaceModuleLoader::openModuleFiles(
207296
// Set up a _potential_ sub-invocation to consume the .swiftinterface and emit
208297
// the .swiftmodule.
209298
CompilerInvocation SubInvocation;
210-
configureSubInvocationAndOutputPath(SubInvocation, InPath, OutPath);
299+
configureSubInvocationAndOutputPaths(SubInvocation, InPath, OutPath, DepPath);
211300

212301
// Evaluate if we need to run this sub-invocation, and if so run it.
213-
if (!swiftModuleIsUpToDate(FS, InPath, OutPath)) {
302+
if (!swiftModuleIsUpToDate(FS, InPath, OutPath, DepPath)) {
214303
if (buildSwiftModuleFromSwiftInterface(FS, Diags, SubInvocation, InPath,
215-
OutPath))
304+
OutPath, DepPath))
216305
return std::make_error_code(std::errc::invalid_argument);
217306
}
218307

lib/FrontendTool/FrontendTool.cpp

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/AST/ASTScope.h"
3030
#include "swift/AST/DiagnosticsFrontend.h"
3131
#include "swift/AST/DiagnosticsSema.h"
32+
#include "swift/AST/FileSystem.h"
3233
#include "swift/AST/GenericSignatureBuilder.h"
3334
#include "swift/AST/IRGenOptions.h"
3435
#include "swift/AST/ASTMangler.h"
@@ -312,30 +313,6 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
312313
PSPs.OutputFilename, opts.EmitSortedSIL);
313314
}
314315

315-
/// A wrapper around swift::atomicallyWritingToFile that handles diagnosing any
316-
/// filesystem errors and ignores empty output paths.
317-
///
318-
/// \returns true if there were any errors, either from the filesystem
319-
/// operations or from \p action returning true.
320-
static bool atomicallyWritingToTextFile(
321-
StringRef outputPath, DiagnosticEngine &diags,
322-
llvm::function_ref<bool(llvm::raw_pwrite_stream &)> action) {
323-
assert(!outputPath.empty());
324-
325-
bool actionFailed = false;
326-
std::error_code EC =
327-
swift::atomicallyWritingToFile(outputPath,
328-
[&](llvm::raw_pwrite_stream &out) {
329-
actionFailed = action(out);
330-
});
331-
if (EC) {
332-
diags.diagnose(SourceLoc(), diag::error_opening_output,
333-
outputPath, EC.message());
334-
return true;
335-
}
336-
return actionFailed;
337-
}
338-
339316
/// Prints the Objective-C "generated header" interface for \p M to \p
340317
/// outputPath.
341318
///
@@ -348,8 +325,8 @@ static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M,
348325
StringRef bridgingHeader, bool moduleIsPublic) {
349326
if (outputPath.empty())
350327
return false;
351-
return atomicallyWritingToTextFile(outputPath, M->getDiags(),
352-
[&](raw_ostream &out) -> bool {
328+
return withOutputFile(M->getDiags(), outputPath,
329+
[&](raw_ostream &out) -> bool {
353330
auto requiredAccess = moduleIsPublic ? AccessLevel::Public
354331
: AccessLevel::Internal;
355332
return printAsObjC(out, M, bridgingHeader, requiredAccess);
@@ -368,8 +345,8 @@ static bool printParseableInterfaceIfNeeded(StringRef outputPath,
368345
ModuleDecl *M) {
369346
if (outputPath.empty())
370347
return false;
371-
return atomicallyWritingToTextFile(outputPath, M->getDiags(),
372-
[M, Opts](raw_ostream &out) -> bool {
348+
return withOutputFile(M->getDiags(), outputPath,
349+
[M, Opts](raw_ostream &out) -> bool {
373350
return swift::emitParseableInterface(out, Opts, M);
374351
});
375352
}

0 commit comments

Comments
 (0)