Skip to content

Commit 4d01890

Browse files
Merge pull request #72804 from cachemeifyoucan/eng/PR-125719747
[Caching] Embed bridging header in binary module correctly when caching
2 parents e53da29 + e654e37 commit 4d01890

File tree

6 files changed

+177
-19
lines changed

6 files changed

+177
-19
lines changed

include/swift/AST/DiagnosticsClangImporter.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ WARNING(could_not_rewrite_bridging_header,none,
4242
ERROR(bridging_header_pch_error,Fatal,
4343
"failed to emit precompiled header '%0' for bridging header '%1'",
4444
(StringRef, StringRef))
45+
ERROR(err_rewrite_bridging_header,none,
46+
"failed to serialize bridging header: '%0'", (StringRef))
4547

4648
ERROR(emit_pcm_error,Fatal,
4749
"failed to emit precompiled module '%0' for module map '%1'",

include/swift/ClangImporter/ClangImporter.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,8 +402,10 @@ class ClangImporter final : public ClangModuleLoader {
402402
getWrapperForModule(const clang::Module *mod,
403403
bool returnOverlayIfPossible = false) const override;
404404

405-
std::string getBridgingHeaderContents(StringRef headerPath, off_t &fileSize,
406-
time_t &fileModTime);
405+
std::string
406+
getBridgingHeaderContents(StringRef headerPath, off_t &fileSize,
407+
time_t &fileModTime,
408+
StringRef pchIncludeTree);
407409

408410
/// Makes a temporary replica of the ClangImporter's CompilerInstance, reads
409411
/// an Objective-C header file into the replica and emits a PCH file of its

lib/ClangImporter/ClangImporter.cpp

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@
5555
#include "clang/Basic/DiagnosticOptions.h"
5656
#include "clang/Basic/FileEntry.h"
5757
#include "clang/Basic/IdentifierTable.h"
58+
#include "clang/Basic/LangStandard.h"
5859
#include "clang/Basic/Module.h"
5960
#include "clang/Basic/TargetInfo.h"
6061
#include "clang/Basic/Version.h"
6162
#include "clang/CAS/CASOptions.h"
63+
#include "clang/CAS/IncludeTree.h"
6264
#include "clang/CodeGen/ObjectFilePCHContainerOperations.h"
6365
#include "clang/Frontend/CompilerInvocation.h"
6466
#include "clang/Frontend/FrontendActions.h"
67+
#include "clang/Frontend/IncludeTreePPActions.h"
6568
#include "clang/Frontend/TextDiagnosticPrinter.h"
6669
#include "clang/Frontend/Utils.h"
6770
#include "clang/Index/IndexingAction.h"
@@ -80,7 +83,11 @@
8083
#include "llvm/ADT/STLExtras.h"
8184
#include "llvm/ADT/SmallVector.h"
8285
#include "llvm/ADT/StringExtras.h"
86+
#include "llvm/CAS/CASReference.h"
87+
#include "llvm/CAS/ObjectStore.h"
8388
#include "llvm/Support/CrashRecoveryContext.h"
89+
#include "llvm/Support/Error.h"
90+
#include "llvm/Support/ErrorHandling.h"
8491
#include "llvm/Support/FileCollector.h"
8592
#include "llvm/Support/FileSystem.h"
8693
#include "llvm/Support/Memory.h"
@@ -1720,8 +1727,13 @@ bool ClangImporter::importHeader(StringRef header, ModuleDecl *adapter,
17201727
StringRef cachedContents, SourceLoc diagLoc) {
17211728
clang::FileManager &fileManager = Impl.Instance->getFileManager();
17221729
auto headerFile = fileManager.getFile(header, /*OpenFile=*/true);
1730+
// Prefer importing the header directly if the header content matches by
1731+
// checking size and mod time. This allows correct import if some no-modular
1732+
// headers are already imported into clang importer. If mod time is zero, then
1733+
// the module should be built from CAS and there is no mod time to verify.
17231734
if (headerFile && (*headerFile)->getSize() == expectedSize &&
1724-
(*headerFile)->getModificationTime() == expectedModTime) {
1735+
(expectedModTime == 0 ||
1736+
(*headerFile)->getModificationTime() == expectedModTime)) {
17251737
return importBridgingHeader(header, adapter, diagLoc, false, true);
17261738
}
17271739

@@ -1776,16 +1788,46 @@ bool ClangImporter::importBridgingHeader(StringRef header, ModuleDecl *adapter,
17761788
std::move(sourceBuffer), implicitImport);
17771789
}
17781790

1779-
std::string ClangImporter::getBridgingHeaderContents(StringRef headerPath,
1780-
off_t &fileSize,
1781-
time_t &fileModTime) {
1791+
static llvm::Expected<llvm::cas::ObjectRef>
1792+
setupIncludeTreeInput(clang::CompilerInvocation &invocation,
1793+
StringRef headerPath, StringRef pchIncludeTree) {
1794+
auto DB = invocation.getCASOpts().getOrCreateDatabases();
1795+
if (!DB)
1796+
return DB.takeError();
1797+
auto CAS = DB->first;
1798+
auto ID = CAS->parseID(pchIncludeTree);
1799+
if (!ID)
1800+
return ID.takeError();
1801+
auto includeTreeRef = CAS->getReference(*ID);
1802+
if (!includeTreeRef)
1803+
return llvm::cas::ObjectStore::createUnknownObjectError(*ID);
1804+
1805+
invocation.getFrontendOpts().Inputs.push_back(clang::FrontendInputFile(
1806+
*includeTreeRef, headerPath, clang::Language::ObjC));
1807+
1808+
return *includeTreeRef;
1809+
}
1810+
1811+
std::string ClangImporter::getBridgingHeaderContents(
1812+
StringRef headerPath, off_t &fileSize, time_t &fileModTime,
1813+
StringRef pchIncludeTree) {
17821814
auto invocation =
17831815
std::make_shared<clang::CompilerInvocation>(*Impl.Invocation);
17841816

17851817
invocation->getFrontendOpts().DisableFree = false;
17861818
invocation->getFrontendOpts().Inputs.clear();
1787-
invocation->getFrontendOpts().Inputs.push_back(
1788-
clang::FrontendInputFile(headerPath, clang::Language::ObjC));
1819+
1820+
std::optional<llvm::cas::ObjectRef> includeTreeRef;
1821+
if (pchIncludeTree.empty())
1822+
invocation->getFrontendOpts().Inputs.push_back(
1823+
clang::FrontendInputFile(headerPath, clang::Language::ObjC));
1824+
else if (auto err =
1825+
setupIncludeTreeInput(*invocation, headerPath, pchIncludeTree)
1826+
.moveInto(includeTreeRef)) {
1827+
Impl.diagnose({}, diag::err_rewrite_bridging_header,
1828+
toString(std::move(err)));
1829+
return "";
1830+
}
17891831

17901832
invocation->getPreprocessorOpts().resetNonModularOptions();
17911833

@@ -1806,18 +1848,36 @@ std::string ClangImporter::getBridgingHeaderContents(StringRef headerPath,
18061848
// write to an in-memory buffer.
18071849
class RewriteIncludesAction : public clang::PreprocessorFrontendAction {
18081850
raw_ostream &OS;
1851+
std::optional<llvm::cas::ObjectRef> includeTreeRef;
18091852

18101853
void ExecuteAction() override {
18111854
clang::CompilerInstance &compiler = getCompilerInstance();
1855+
// If the input is include tree, setup the IncludeTreePPAction.
1856+
if (includeTreeRef) {
1857+
auto IncludeTreeRoot = clang::cas::IncludeTreeRoot::get(
1858+
compiler.getOrCreateObjectStore(), *includeTreeRef);
1859+
if (!IncludeTreeRoot)
1860+
llvm::report_fatal_error(IncludeTreeRoot.takeError());
1861+
auto PPCachedAct =
1862+
clang::createPPActionsFromIncludeTree(*IncludeTreeRoot);
1863+
if (!PPCachedAct)
1864+
llvm::report_fatal_error(PPCachedAct.takeError());
1865+
compiler.getPreprocessor().setPPCachedActions(
1866+
std::move(*PPCachedAct));
1867+
}
1868+
18121869
clang::RewriteIncludesInInput(compiler.getPreprocessor(), &OS,
18131870
compiler.getPreprocessorOutputOpts());
18141871
}
1872+
18151873
public:
1816-
explicit RewriteIncludesAction(raw_ostream &os) : OS(os) {}
1874+
explicit RewriteIncludesAction(
1875+
raw_ostream &os, std::optional<llvm::cas::ObjectRef> includeTree)
1876+
: OS(os), includeTreeRef(includeTree) {}
18171877
};
18181878

18191879
llvm::raw_string_ostream os(result);
1820-
RewriteIncludesAction action(os);
1880+
RewriteIncludesAction action(os, includeTreeRef);
18211881
rewriteInstance.ExecuteAction(action);
18221882
});
18231883

@@ -2553,6 +2613,7 @@ ClangImporter::Implementation::Implementation(
25532613
!ctx.ClangImporterOpts.BridgingHeader.empty()),
25542614
DisableOverlayModules(ctx.ClangImporterOpts.DisableOverlayModules),
25552615
EnableClangSPI(ctx.ClangImporterOpts.EnableClangSPI),
2616+
UseClangIncludeTree(ctx.ClangImporterOpts.UseClangIncludeTree),
25562617
importSymbolicCXXDecls(
25572618
ctx.LangOpts.hasFeature(Feature::ImportSymbolicCXXDecls)),
25582619
IsReadingBridgingPCH(false),

lib/ClangImporter/ImporterImpl.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,7 @@ class LLVM_LIBRARY_VISIBILITY ClangImporter::Implementation
467467
const bool BridgingHeaderExplicitlyRequested;
468468
const bool DisableOverlayModules;
469469
const bool EnableClangSPI;
470+
const bool UseClangIncludeTree;
470471
bool importSymbolicCXXDecls;
471472

472473
bool IsReadingBridgingPCH;

lib/Serialization/Serialization.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,21 +1321,25 @@ void Serializer::writeInputBlock() {
13211321
time_t importedHeaderModTime = 0;
13221322
std::string contents;
13231323
auto importedHeaderPath = Options.ImportedHeader;
1324+
std::string pchIncludeTree;
13241325
// We do not want to serialize the explicitly-specified .pch path if one was
13251326
// provided. Instead we write out the path to the original header source so
13261327
// that clients can consume it.
13271328
if (Options.ExplicitModuleBuild &&
13281329
llvm::sys::path::extension(importedHeaderPath)
1329-
.ends_with(file_types::getExtension(file_types::TY_PCH)))
1330-
importedHeaderPath = clangImporter->getClangInstance()
1331-
.getASTReader()
1332-
->getModuleManager()
1333-
.lookupByFileName(importedHeaderPath)
1334-
->OriginalSourceFileName;
1330+
.ends_with(file_types::getExtension(file_types::TY_PCH))) {
1331+
auto *pch = clangImporter->getClangInstance()
1332+
.getASTReader()
1333+
->getModuleManager()
1334+
.lookupByFileName(importedHeaderPath);
1335+
pchIncludeTree = pch->IncludeTreeID;
1336+
importedHeaderPath = pch->OriginalSourceFileName;
1337+
}
13351338

13361339
if (!importedHeaderPath.empty()) {
13371340
contents = clangImporter->getBridgingHeaderContents(
1338-
importedHeaderPath, importedHeaderSize, importedHeaderModTime);
1341+
importedHeaderPath, importedHeaderSize, importedHeaderModTime,
1342+
pchIncludeTree);
13391343
}
13401344
assert(publicImportSet.count(bridgingHeaderImport));
13411345
ImportedHeader.emit(ScratchRecord,

test/CAS/bridging-header.swift

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
// REQUIRES: objc_interop
12
// RUN: %empty-directory(%t)
23
// RUN: split-file %s %t
34

5+
// RUN: %target-swift-frontend -emit-module -o %t/temp.swiftmodule -module-name Test -swift-version 5 \
6+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
7+
// RUN: -Xcc -fmodule-map-file=%t/a.modulemap -Xcc -fmodule-map-file=%t/b.modulemap -import-objc-header %t/Bridging.h \
8+
// RUN: %t/test.swift
9+
410
// RUN: %target-swift-frontend -scan-dependencies -module-name Test -module-cache-path %t/clang-module-cache -O \
511
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
612
// RUN: %t/test.swift -o %t/deps.json -swift-version 5 -cache-compile-job -cas-path %t/cas \
@@ -27,21 +33,103 @@
2733
// CHECK-NEXT: "-Xcc",
2834
// CHECK-NEXT: "llvmcas://{{.*}}"
2935

36+
/// Try build then import from a non-caching compilation.
37+
38+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:SwiftShims > %t/shim.cmd
39+
// RUN: %swift_frontend_plain @%t/shim.cmd
40+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:B > %t/B.cmd
41+
// RUN: %swift_frontend_plain @%t/B.cmd
42+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps.json clang:A > %t/A.cmd
43+
// RUN: %swift_frontend_plain @%t/A.cmd
44+
45+
// RUN: %{python} %S/Inputs/GenerateExplicitModuleMap.py %t/deps.json > %t/map.json
46+
// RUN: llvm-cas --cas %t/cas --make-blob --data %t/map.json > %t/map.casid
47+
48+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps.json bridgingHeader | tail -n +2 > %t/header.cmd
49+
// RUN: %target-swift-frontend @%t/header.cmd -disable-implicit-swift-modules %t/Bridging.h -O -o %t/bridging.pch
50+
// RUN: %cache-tool -cas-path %t/cas -cache-tool-action print-output-keys -- \
51+
// RUN: %target-swift-frontend @%t/header.cmd -disable-implicit-swift-modules %t/Bridging.h -O -o %t/bridging.pch > %t/keys.json
52+
// RUN: %{python} %S/Inputs/ExtractOutputKey.py %t/keys.json %t/Bridging.h > %t/key
53+
54+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps.json Test > %t/MyApp.cmd
55+
// RUN: echo "\"-disable-implicit-string-processing-module-import\"" >> %t/MyApp.cmd
56+
// RUN: echo "\"-disable-implicit-concurrency-module-import\"" >> %t/MyApp.cmd
57+
// RUN: echo "\"-disable-implicit-swift-modules\"" >> %t/MyApp.cmd
58+
// RUN: echo "\"-import-objc-header\"" >> %t/MyApp.cmd
59+
// RUN: echo "\"%t/bridging.pch\"" >> %t/MyApp.cmd
60+
// RUN: echo "\"-bridging-header-pch-key\"" >> %t/MyApp.cmd
61+
// RUN: echo "\"@%t/key\"" >> %t/MyApp.cmd
62+
// RUN: echo "\"-explicit-swift-module-map-file\"" >> %t/MyApp.cmd
63+
// RUN: echo "\"@%t/map.casid\"" >> %t/MyApp.cmd
64+
65+
// RUN: %target-swift-frontend -cache-compile-job -module-name Test -O -cas-path %t/cas @%t/MyApp.cmd %t/test.swift \
66+
// RUN: -emit-module -o %t/Test.swiftmodule
67+
68+
/// Importing binary module with bridging header built from CAS from a regluar build.
69+
/// This should succeed even it is also importing a bridging header that shares same header dependencies (with proper header guard).
70+
// RUN: %target-swift-frontend -typecheck -module-name User -swift-version 5 \
71+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
72+
// RUN: -Xcc -fmodule-map-file=%t/a.modulemap -Xcc -fmodule-map-file=%t/b.modulemap \
73+
// RUN: -I %t %t/user.swift -import-objc-header %t/Bridging2.h
74+
75+
/// Importing binary module with bridging header built from CAS from a cached build. This should work without additional bridging header deps.
76+
// RUN: %target-swift-frontend -scan-dependencies -module-name User -module-cache-path %t/clang-module-cache -O \
77+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
78+
// RUN: %t/user.swift -o %t/deps2.json -swift-version 5 -cache-compile-job -cas-path %t/cas \
79+
// RUN: -Xcc -fmodule-map-file=%t/a.modulemap -Xcc -fmodule-map-file=%t/b.modulemap -I %t
80+
81+
// RUN: %{python} %S/Inputs/GenerateExplicitModuleMap.py %t/deps2.json > %t/map2.json
82+
// RUN: llvm-cas --cas %t/cas --make-blob --data %t/map2.json > %t/map2.casid
83+
// RUN: %{python} %S/Inputs/BuildCommandExtractor.py %t/deps2.json User > %t/User.cmd
84+
// RUN: %target-swift-frontend -cache-compile-job -module-name User -O -cas-path %t/cas \
85+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -disable-implicit-swift-modules \
86+
// RUN: -explicit-swift-module-map-file @%t/map2.casid @%t/User.cmd %t/user.swift \
87+
// RUN: -emit-module -o %t/User.swiftmodule
88+
3089
//--- test.swift
31-
import B
32-
public func test() {}
90+
public func test() {
91+
b()
92+
}
93+
public class TestB: B {}
94+
95+
//--- user.swift
96+
import Test
97+
98+
func user() {
99+
var b: TestB
100+
test()
101+
}
102+
103+
extension A {
104+
public func testA() {}
105+
}
106+
33107

34108
//--- Bridging.h
35109
#include "Foo.h"
110+
#include "Foo2.h"
111+
112+
//--- Bridging2.h
113+
#include "Foo.h"
114+
#include "Foo2.h"
36115

37116
//--- Foo.h
38117
#import "a.h"
39118

119+
//--- Foo2.h
120+
#pragma once
121+
int Foo = 0;
122+
40123
//--- a.h
41124
#include "b.h"
125+
struct A {
126+
int a;
127+
};
42128

43129
//--- b.h
44130
void b(void);
131+
@interface B
132+
@end
45133
46134
//--- a.modulemap
47135
module A {

0 commit comments

Comments
 (0)