Skip to content

Commit ec1db28

Browse files
[Caching][Macro] Make macro plugin options cacheable
Currently, the macro plugin options are included as cache key and the absolute path of the plugin executable and library will affect cache hit, even the plugin itself is identical. Using the new option `-resolved-plugin-validation` flag, the macro plugin paths are remapped just like the other paths during dependency scanning. `swift-frontend` will unmap to its original path during the compilation, make sure the content hasn't changed, and load the plugin. It also hands few other corner cases for macro plugins: * Make sure the plugin options in the swift module is prefix mapped. * Make sure the remarks of the macro loading is not cached, as the mesasge includes the absolute path of the plugin, and is not cacheable. rdar://148465899
1 parent 562d7dc commit ec1db28

12 files changed

+168
-7
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1262,6 +1262,10 @@ REMARK(macro_loaded,none,
12621262
"compiler plugin server '%2' with shared library '%3'}1",
12631263
(Identifier, unsigned, StringRef, StringRef))
12641264

1265+
ERROR(resolved_macro_changed,none,
1266+
"resolved macro library '%0' failed verification: %1",
1267+
(StringRef, StringRef))
1268+
12651269
REMARK(transitive_dependency_behavior,none,
12661270
"%1 has %select{a required|an optional|an ignored}2 "
12671271
"transitive dependency on '%0'",

include/swift/AST/PluginLoader.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,17 @@ class PluginLoader {
5252
/// Get or lazily create and populate 'PluginMap'.
5353
llvm::DenseMap<swift::Identifier, PluginEntry> &getPluginMap();
5454

55+
/// Resolved plugin path remappings.
56+
std::vector<std::string> PathRemap;
57+
5558
public:
5659
PluginLoader(ASTContext &Ctx, DependencyTracker *DepTracker,
60+
std::optional<std::vector<std::string>> Remap = std::nullopt,
5761
bool disableSandbox = false)
58-
: Ctx(Ctx), DepTracker(DepTracker), disableSandbox(disableSandbox) {}
62+
: Ctx(Ctx), DepTracker(DepTracker), disableSandbox(disableSandbox) {
63+
if (Remap)
64+
PathRemap = std::move(*Remap);
65+
}
5966

6067
void setRegistry(PluginRegistry *newValue);
6168
PluginRegistry *getRegistry();

include/swift/AST/SearchPathOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,9 @@ class SearchPathOptions {
510510
/// Scanner Prefix Mapper.
511511
std::vector<std::string> ScannerPrefixMapper;
512512

513+
/// Verify resolved plugin is not changed.
514+
bool ResolvedPluginVerification = false;
515+
513516
/// When set, don't validate module system dependencies.
514517
///
515518
/// If a system header is modified and this is not set, the compiler will

include/swift/Option/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2263,6 +2263,10 @@ def load_resolved_plugin:
22632263
"and exectuable path can be empty if not used">,
22642264
MetaVarName<"<library-path>#<executable-path>#<module-names>">;
22652265

2266+
def resolved_plugin_verification : Flag<["-"], "resolved-plugin-verification">,
2267+
Flags<[FrontendOption, NoDriverOption]>,
2268+
HelpText<"verify resolved plugins">;
2269+
22662270
def in_process_plugin_server_path : Separate<["-"], "in-process-plugin-server-path">,
22672271
Flags<[FrontendOption, ArgumentIsPath]>,
22682272
HelpText<"Path to dynamic library plugin server">;

lib/AST/PluginLoader.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
#include "swift/AST/PluginLoader.h"
1414
#include "swift/AST/ASTContext.h"
1515
#include "swift/AST/DiagnosticEngine.h"
16-
#include "swift/AST/DiagnosticsFrontend.h"
16+
#include "swift/AST/DiagnosticsSema.h"
1717
#include "swift/Basic/Assertions.h"
1818
#include "swift/Basic/SourceManager.h"
1919
#include "swift/Parse/Lexer.h"
2020
#include "llvm/Config/config.h"
21+
#include "llvm/Support/PrefixMapper.h"
2122
#include "llvm/Support/VirtualFileSystem.h"
2223

2324
using namespace swift;
@@ -96,9 +97,44 @@ PluginLoader::getPluginMap() {
9697
map[moduleNameIdentifier] = {libPath, execPath};
9798
};
9899

100+
std::optional<llvm::PrefixMapper> mapper;
101+
if (!PathRemap.empty()) {
102+
SmallVector<llvm::MappedPrefix, 4> prefixes;
103+
llvm::MappedPrefix::transformJoinedIfValid(PathRemap, prefixes);
104+
mapper.emplace();
105+
mapper->addRange(prefixes);
106+
mapper->sort();
107+
}
108+
auto remapPath = [&mapper](StringRef path) {
109+
if (!mapper)
110+
return path.str();
111+
return mapper->mapToString(path);
112+
};
113+
99114
auto fs = getPluginLoadingFS(Ctx);
100115
std::error_code ec;
101116

117+
auto validateLibrary = [&](StringRef path) -> llvm::Expected<std::string> {
118+
auto remappedPath = remapPath(path);
119+
if (!Ctx.SearchPathOpts.ResolvedPluginVerification || path.empty())
120+
return remappedPath;
121+
122+
auto currentFile = fs->getBufferForFile(remappedPath);
123+
if (!currentFile)
124+
return llvm::createFileError(remappedPath, currentFile.getError());
125+
126+
auto goldFile = Ctx.SourceMgr.getFileSystem()->getBufferForFile(path);
127+
if (!goldFile)
128+
return llvm::createStringError(
129+
"cannot open gold reference library to compare");
130+
131+
if ((*currentFile)->getBuffer() != (*goldFile)->getBuffer())
132+
return llvm::createStringError(
133+
"plugin has changed since dependency scanning");
134+
135+
return remappedPath;
136+
};
137+
102138
for (auto &entry : Ctx.SearchPathOpts.PluginSearchOpts) {
103139
switch (entry.getKind()) {
104140

@@ -156,9 +192,17 @@ PluginLoader::getPluginMap() {
156192
// Respect resolved plugin config above other search path, and it can
157193
// overwrite plugins found by other options or previous resolved
158194
// configuration.
159-
for (auto &moduleName : val.ModuleNames)
160-
try_emplace(moduleName, val.LibraryPath, val.ExecutablePath,
195+
for (auto &moduleName : val.ModuleNames) {
196+
auto libPath = validateLibrary(val.LibraryPath);
197+
if (!libPath) {
198+
Ctx.Diags.diagnose(SourceLoc(), diag::resolved_macro_changed,
199+
remapPath(val.LibraryPath),
200+
toString(libPath.takeError()));
201+
continue;
202+
}
203+
try_emplace(moduleName, *libPath, remapPath(val.ExecutablePath),
161204
/*overwrite*/ true);
205+
}
162206
continue;
163207
}
164208
}

lib/DependencyScan/ModuleDependencyScanner.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ ModuleDependencyScanningWorker::ModuleDependencyScanningWorker(
201201
ScanASTContext.SourceMgr, Diagnostics));
202202
auto loader = std::make_unique<PluginLoader>(
203203
*workerASTContext, /*DepTracker=*/nullptr,
204+
workerCompilerInvocation->getFrontendOptions().CacheReplayPrefixMap,
204205
workerCompilerInvocation->getFrontendOptions().DisableSandbox);
205206
workerASTContext->setPluginLoader(std::move(loader));
206207

lib/DependencyScan/ScanDependencies.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,23 @@ class ExplicitModuleDependencyResolver {
214214
depInfo.addMacroDependency(macro.first(), macro.second.LibraryPath,
215215
macro.second.ExecutablePath);
216216

217+
bool needPathRemapping = instance.getInvocation()
218+
.getSearchPathOptions()
219+
.ResolvedPluginVerification &&
220+
cache.getScanService().hasPathMapping();
221+
auto mapPath = [&](StringRef path) {
222+
if (!needPathRemapping)
223+
return path.str();
224+
225+
return cache.getScanService().remapPath(path);
226+
};
227+
if (needPathRemapping)
228+
commandline.push_back("-resolved-plugin-verification");
229+
217230
for (auto &macro : depInfo.getMacroDependencies()) {
218-
std::string arg = macro.second.LibraryPath + "#" +
219-
macro.second.ExecutablePath + "#" + macro.first;
231+
std::string arg = mapPath(macro.second.LibraryPath) + "#" +
232+
mapPath(macro.second.ExecutablePath) + "#" +
233+
macro.first;
220234
commandline.push_back("-load-resolved-plugin");
221235
commandline.push_back(arg);
222236
}

lib/Frontend/CachedDiagnostics.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
#include "swift/AST/DiagnosticBridge.h"
2020
#include "swift/AST/DiagnosticConsumer.h"
21+
#include "swift/AST/DiagnosticsCommon.h"
2122
#include "swift/AST/DiagnosticsFrontend.h"
23+
#include "swift/AST/DiagnosticsSema.h"
2224
#include "swift/Basic/Assertions.h"
2325
#include "swift/Basic/SourceManager.h"
2426
#include "swift/Frontend/Frontend.h"
@@ -755,6 +757,13 @@ class CachingDiagnosticsProcessor::Implementation
755757
auto &Serializer = getSerializer();
756758
assert(SM.getFileSystem() == Serializer.getSourceMgr().getFileSystem() &&
757759
"Caching for a different file system");
760+
761+
// Bypass the caching.
762+
if (BypassDiagIDs.count(Info.ID)) {
763+
for (auto *Diag : OrigConsumers)
764+
Diag->handleDiagnostic(Serializer.getSourceMgr(), Info);
765+
return;
766+
}
758767
Serializer.handleDiagnostic(SM, Info, [&](const DiagnosticInfo &Info) {
759768
for (auto *Diag : OrigConsumers)
760769
Diag->handleDiagnostic(Serializer.getSourceMgr(), Info);
@@ -809,6 +818,8 @@ class CachingDiagnosticsProcessor::Implementation
809818
// Processor/Serializer alive until then.
810819
std::unique_ptr<DiagnosticSerializer> Serializer;
811820

821+
const llvm::SmallDenseSet<DiagID> BypassDiagIDs = {diag::macro_loaded.ID};
822+
812823
SourceManager &InstanceSourceMgr;
813824
const FrontendInputsAndOutputs &InAndOut;
814825
DiagnosticEngine &Diags;

lib/Frontend/CompilerInvocation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2417,6 +2417,9 @@ static bool ParseSearchPathArgs(SearchPathOptions &Opts, ArgList &Args,
24172417
Opts.ScannerPrefixMapper.push_back(Opt.str());
24182418
}
24192419

2420+
Opts.ResolvedPluginVerification |=
2421+
Args.hasArg(OPT_resolved_plugin_verification);
2422+
24202423
// rdar://132340493 disable scanner-side validation for non-caching builds
24212424
Opts.ScannerModuleValidation |= Args.hasFlag(OPT_scanner_module_validation,
24222425
OPT_no_scanner_module_validation,

lib/Frontend/Frontend.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,7 @@ bool CompilerInstance::setUpPluginLoader() {
878878
/// FIXME: If Invocation has 'PluginRegistry', we can set it. But should we?
879879
auto loader = std::make_unique<PluginLoader>(
880880
*Context, getDependencyTracker(),
881+
Invocation.getFrontendOptions().CacheReplayPrefixMap,
881882
Invocation.getFrontendOptions().DisableSandbox);
882883
Context->setPluginLoader(std::move(loader));
883884
return false;

lib/Frontend/ModuleInterfaceLoader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,8 @@ InterfaceSubContextDelegateImpl::InterfaceSubContextDelegateImpl(
19321932
// Load plugin libraries for macro expression as default arguments
19331933
genericSubInvocation.getSearchPathOptions().PluginSearchOpts =
19341934
searchPathOpts.PluginSearchOpts;
1935+
genericSubInvocation.getSearchPathOptions().ResolvedPluginVerification =
1936+
searchPathOpts.ResolvedPluginVerification;
19351937

19361938
// Get module loading behavior options.
19371939
genericSubInvocation.getSearchPathOptions().ScannerModuleValidation = searchPathOpts.ScannerModuleValidation;

test/CAS/macro_plugin_external.swift

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,78 @@
4343
// RUN: %target-swift-frontend \
4444
// RUN: -typecheck -verify -cache-compile-job -cas-path %t/cas \
4545
// RUN: -swift-version 5 -disable-implicit-swift-modules \
46-
// RUN: -external-plugin-path %t/plugins/#%swift-plugin-server \
4746
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
4847
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map.casid \
4948
// RUN: %t/macro.swift @%t/MyApp.cmd
5049

50+
// RUN: %target-swift-frontend -scan-dependencies -module-load-mode prefer-serialized -module-name MyApp -module-cache-path %t/clang-module-cache -O \
51+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
52+
// RUN: %t/macro.swift -o %t/deps2.json -swift-version 5 -cache-compile-job -cas-path %t/cas -external-plugin-path %t/plugins#%swift-plugin-server \
53+
// RUN: -scanner-prefix-map %t=/^test -scanner-prefix-map %swift-bin-dir=/^bin -resolved-plugin-verification
54+
55+
// RUN: %S/Inputs/SwiftDepsExtractor.py %t/deps2.json MyApp casFSRootID > %t/fs.casid
56+
// RUN: %cache-tool -cas-path %t/cas -cache-tool-action print-include-tree-list @%t/fs.casid | %FileCheck %s --check-prefix=FS-REMAP -DLIB=%target-library-name(MacroDefinition)
57+
58+
/// CASFS is remapped.
59+
// FS-REMAP: /^test/plugins/[[LIB]]
60+
61+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps2.json clang:SwiftShims > %t/SwiftShims2.cmd
62+
// RUN: %swift_frontend_plain @%t/SwiftShims2.cmd
63+
64+
// RUN: %S/Inputs/BuildCommandExtractor.py %t/deps2.json MyApp > %t/MyApp2.cmd
65+
// RUN: %{python} %S/Inputs/GenerateExplicitModuleMap.py %t/deps2.json > %t/map2.json
66+
// RUN: llvm-cas --cas %t/cas --make-blob --data %t/map2.json > %t/map2.casid
67+
68+
/// Command-line is remapped.
69+
// RUN: %FileCheck %s --check-prefix=CMD-REMAP --input-file=%t/MyApp2.cmd -DLIB=%target-library-name(MacroDefinition)
70+
71+
// CMD-REMAP: -resolved-plugin-verification
72+
// CMD-REMAP-NEXT: -load-resolved-plugin
73+
// CMD-REMAP-NEXT: /^test/plugins/[[LIB]]#/^bin/swift-plugin-server#MacroDefinition
74+
75+
// RUN: %target-swift-frontend \
76+
// RUN: -emit-module -o %t/Macro.swiftmodule -cache-compile-job -cas-path %t/cas \
77+
// RUN: -swift-version 5 -disable-implicit-swift-modules -O \
78+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
79+
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map2.casid -Rmacro-loading -Rcache-compile-job \
80+
// RUN: /^test/macro.swift @%t/MyApp2.cmd -cache-replay-prefix-map /^test=%t -cache-replay-prefix-map /^bin=%swift-bin-dir 2>&1 | %FileCheck %s --check-prefix=REMARK
81+
// REMAKR: remark: cache miss
82+
// REMARK: remark: loaded macro implementation module 'MacroDefinition' from compiler plugin server
83+
84+
/// Encoded PLUGIN_SEARCH_OPTION is remapped.
85+
// RUN: llvm-bcanalyzer -dump %t/Macro.swiftmodule | %FileCheck %s --check-prefix=MOD -DLIB=%target-library-name(MacroDefinition)
86+
87+
// MOD: <PLUGIN_SEARCH_OPTION abbrevid=7 op0=4/> blob data = '/^test/plugins/[[LIB]]#/^bin/swift-plugin-server#MacroDefinition'
88+
89+
/// Cache hit has no macro-loading remarks because no macro is actually loaded and the path might not be correct due to different mapping.
90+
// RUN: %target-swift-frontend \
91+
// RUN: -emit-module -o %t/Macro.swiftmodule -cache-compile-job -cas-path %t/cas \
92+
// RUN: -swift-version 5 -disable-implicit-swift-modules -O \
93+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
94+
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map2.casid -Rmacro-loading -Rcache-compile-job \
95+
// RUN: /^test/macro.swift @%t/MyApp2.cmd -cache-replay-prefix-map /^test=%t -cache-replay-prefix-map /^bin=%swift-bin-dir 2>&1 | %FileCheck %s --check-prefix=NO-REMARK
96+
// NO-REMARK: remark: replay output file
97+
// NO-REMARK-NOT: remark: loaded macro implementation module 'MacroDefinition' from compiler plugin server
98+
99+
/// Update timestamp, the build should still work.
100+
// RUN: touch %t/plugins/%target-library-name(MacroDefinition)
101+
// RUN: %target-swift-frontend \
102+
// RUN: -emit-module -o %t/Macro.swiftmodule -cache-compile-job -cas-path %t/cas \
103+
// RUN: -swift-version 5 -disable-implicit-swift-modules -O \
104+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
105+
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map2.casid -Rmacro-loading -Rcache-compile-job -cache-disable-replay \
106+
// RUN: /^test/macro.swift @%t/MyApp2.cmd -cache-replay-prefix-map /^test=%t -cache-replay-prefix-map /^bin=%swift-bin-dir
107+
108+
/// Change the dylib content, this will fail the build.
109+
// RUN: echo " " >> %t/plugins/%target-library-name(MacroDefinition)
110+
// RUN: not %target-swift-frontend \
111+
// RUN: -emit-module -o %t/Macro.swiftmodule -cache-compile-job -cas-path %t/cas \
112+
// RUN: -swift-version 5 -disable-implicit-swift-modules -O \
113+
// RUN: -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import \
114+
// RUN: -module-name MyApp -explicit-swift-module-map-file @%t/map2.casid -Rmacro-loading -Rcache-compile-job -cache-disable-replay \
115+
// RUN: /^test/macro.swift @%t/MyApp2.cmd -cache-replay-prefix-map /^test=%t -cache-replay-prefix-map /^bin=%swift-bin-dir 2>&1 | %FileCheck %s --check-prefix=FAILED
116+
// FAILED: plugin has changed since dependency scanning
117+
51118
//--- nomacro.swift
52119
func test() {}
53120

0 commit comments

Comments
 (0)