Skip to content

Commit dfa41d6

Browse files
authored
Optimize Condition Resolution (#6279)
* Pack the bits for IfConfigDecls into Decl * Don't open symbols into a module when evaluating canImport statements The module loaders now have API to check whether a given module can be imported without importing the referenced module. This provides a significant speed boost to condition resolution and no longer introduces symbols from the referenced module into the current context without the user explicitly requesting it. The definition of ‘canImport’ does not necessarily mean that a full import without error is possible, merely that the path to the import is visible to the compiler and the module is loadable in some form or another. Note that this means this check is insufficient to guarantee that you are on one platform or another. For those kinds of checks, use ‘os(OSNAME)’.
1 parent d5a55c4 commit dfa41d6

File tree

16 files changed

+211
-24
lines changed

16 files changed

+211
-24
lines changed

include/swift/AST/ASTContext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@ class ASTContext {
580580
/// Does nothing in non-asserts (NDEBUG) builds.
581581
void verifyAllLoadedModules() const;
582582

583+
/// \brief Check whether the module with a given name can be imported without
584+
/// importing it.
585+
///
586+
/// Note that even if this check succeeds, errors may still occur if the
587+
/// module is loaded in full.
588+
bool canImportModule(std::pair<Identifier, SourceLoc> ModulePath);
589+
583590
/// \returns a module with a given name that was already loaded. If the
584591
/// module was not loaded, returns nullptr.
585592
ModuleDecl *getLoadedModule(

include/swift/AST/Decl.h

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,20 @@ class alignas(1 << DeclAlignInBits) Decl {
578578
enum { NumExtensionDeclBits = NumDeclBits + 6 };
579579
static_assert(NumExtensionDeclBits <= 32, "fits in an unsigned");
580580

581+
class IfConfigDeclBitfields {
582+
friend class IfConfigDecl;
583+
unsigned : NumDeclBits;
584+
585+
/// Whether this decl is missing its closing '#endif'.
586+
unsigned HadMissingEnd : 1;
587+
588+
/// Whether this condition has been resolved either statically by Parse or
589+
/// later by Condition Resolution.
590+
unsigned HasBeenResolved : 1;
591+
};
592+
enum { NumIfConfigDeclBits = NumDeclBits + 2 };
593+
static_assert(NumIfConfigDeclBits <= 32, "fits in an unsigned");
594+
581595
protected:
582596
union {
583597
DeclBitfields DeclBits;
@@ -601,6 +615,7 @@ class alignas(1 << DeclAlignInBits) Decl {
601615
PrecedenceGroupDeclBitfields PrecedenceGroupDeclBits;
602616
ImportDeclBitfields ImportDeclBits;
603617
ExtensionDeclBitfields ExtensionDeclBits;
618+
IfConfigDeclBitfields IfConfigDeclBits;
604619
uint32_t OpaqueBits;
605620
};
606621

@@ -1931,14 +1946,16 @@ class IfConfigDecl : public Decl {
19311946
/// The array is ASTContext allocated.
19321947
ArrayRef<IfConfigDeclClause> Clauses;
19331948
SourceLoc EndLoc;
1934-
bool HadMissingEnd;
1935-
bool HasBeenResolved = false;
1949+
19361950
public:
19371951

19381952
IfConfigDecl(DeclContext *Parent, ArrayRef<IfConfigDeclClause> Clauses,
19391953
SourceLoc EndLoc, bool HadMissingEnd)
1940-
: Decl(DeclKind::IfConfig, Parent), Clauses(Clauses),
1941-
EndLoc(EndLoc), HadMissingEnd(HadMissingEnd) {}
1954+
: Decl(DeclKind::IfConfig, Parent), Clauses(Clauses), EndLoc(EndLoc)
1955+
{
1956+
IfConfigDeclBits.HadMissingEnd = HadMissingEnd;
1957+
IfConfigDeclBits.HasBeenResolved = false;
1958+
}
19421959

19431960
ArrayRef<IfConfigDeclClause> getClauses() const { return Clauses; }
19441961

@@ -1955,13 +1972,13 @@ class IfConfigDecl : public Decl {
19551972
return {};
19561973
}
19571974

1958-
bool isResolved() const { return HasBeenResolved; }
1959-
void setResolved() { HasBeenResolved = true; }
1975+
bool isResolved() const { return IfConfigDeclBits.HasBeenResolved; }
1976+
void setResolved() { IfConfigDeclBits.HasBeenResolved = true; }
19601977

19611978
SourceLoc getEndLoc() const { return EndLoc; }
19621979
SourceLoc getLoc() const { return Clauses[0].Loc; }
19631980

1964-
bool hadMissingEnd() const { return HadMissingEnd; }
1981+
bool hadMissingEnd() const { return IfConfigDeclBits.HadMissingEnd; }
19651982

19661983
SourceRange getSourceRange() const;
19671984

include/swift/AST/ModuleLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ class ModuleLoader {
7474
public:
7575
virtual ~ModuleLoader() = default;
7676

77+
/// \brief Check whether the module with a given name can be imported without
78+
/// importing it.
79+
///
80+
/// Note that even if this check succeeds, errors may still occur if the
81+
/// module is loaded in full.
82+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) = 0;
83+
7784
/// \brief Import a module with the given module path.
7885
///
7986
/// \param importLoc The location of the 'import' keyword.

include/swift/ClangImporter/ClangImporter.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ class ClangImporter final : public ClangModuleLoader {
9494

9595
~ClangImporter();
9696

97+
/// \brief Check whether the module with a given name can be imported without
98+
/// importing it.
99+
///
100+
/// Note that even if this check succeeds, errors may still occur if the
101+
/// module is loaded in full.
102+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
103+
97104
/// \brief Import a module with the given module path.
98105
///
99106
/// Clang modules will be imported using the Objective-C ARC dialect,

include/swift/Sema/SourceLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ class SourceLoader : public ModuleLoader {
4848
SourceLoader &operator=(const SourceLoader &) = delete;
4949
SourceLoader &operator=(SourceLoader &&) = delete;
5050

51+
/// \brief Check whether the module with a given name can be imported without
52+
/// importing it.
53+
///
54+
/// Note that even if this check succeeds, errors may still occur if the
55+
/// module is loaded in full.
56+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
57+
5158
/// \brief Import a module with the given module path.
5259
///
5360
/// \param importLoc The location of the 'import' keyword.

include/swift/Serialization/SerializedModuleLoader.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ class SerializedModuleLoader : public ModuleLoader {
4848
SerializedModuleLoader &operator=(const SerializedModuleLoader &) = delete;
4949
SerializedModuleLoader &operator=(SerializedModuleLoader &&) = delete;
5050

51+
/// \brief Check whether the module with a given name can be imported without
52+
/// importing it.
53+
///
54+
/// Note that even if this check succeeds, errors may still occur if the
55+
/// module is loaded in full.
56+
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;
57+
5158
/// \brief Import a module with the given module path.
5259
///
5360
/// \param importLoc The location of the 'import' keyword.

lib/AST/ASTContext.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,20 @@ GenericEnvironment *ASTContext::getOrCreateCanonicalGenericEnvironment(
12631263
return env;
12641264
}
12651265

1266+
bool ASTContext::canImportModule(std::pair<Identifier, SourceLoc> ModulePath) {
1267+
// If this module has already been successfully imported, it is importable.
1268+
if (getLoadedModule(ModulePath) != nullptr)
1269+
return true;
1270+
1271+
// Otherwise, ask the module loaders.
1272+
for (auto &importer : Impl.ModuleLoaders) {
1273+
if (importer->canImportModule(ModulePath)) {
1274+
return true;
1275+
}
1276+
}
1277+
return false;
1278+
}
1279+
12661280
Module *
12671281
ASTContext::getModule(ArrayRef<std::pair<Identifier, SourceLoc>> ModulePath) {
12681282
assert(!ModulePath.empty());

lib/ClangImporter/ClangImporter.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,23 @@ bool ClangImporter::isModuleImported(const clang::Module *M) {
10221022
return M->NameVisibility == clang::Module::NameVisibilityKind::AllVisible;
10231023
}
10241024

1025+
bool ClangImporter::canImportModule(std::pair<Identifier, SourceLoc> moduleID) {
1026+
// Look up the top-level module to see if it exists.
1027+
// FIXME: This only works with top-level modules.
1028+
auto &clangHeaderSearch = Impl.getClangPreprocessor().getHeaderSearchInfo();
1029+
clang::Module *clangModule =
1030+
clangHeaderSearch.lookupModule(moduleID.first.str());
1031+
if (!clangModule) {
1032+
return false;
1033+
}
1034+
1035+
clang::Module::Requirement r;
1036+
clang::Module::UnresolvedHeaderDirective mh;
1037+
clang::Module *m;
1038+
auto &ctx = Impl.getClangASTContext();
1039+
return clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(), r, mh, m);
1040+
}
1041+
10251042
Module *ClangImporter::loadModule(
10261043
SourceLoc importLoc,
10271044
ArrayRef<std::pair<Identifier, SourceLoc>> path) {

lib/Parse/ConditionResolver.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ namespace {
5959

6060
ConditionClauseResolver(SmallVectorImpl<Decl *> &ExtraTLCDs,
6161
SourceFile &SF)
62-
: NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF),
63-
Context(SF.getASTContext()) {}
62+
: NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF), Context(SF.getASTContext()) {}
6463

6564
void resolveClausesAndInsertMembers(IterableDeclContext *Nom,
6665
IfConfigDecl *Cond) {
@@ -69,9 +68,9 @@ namespace {
6968
// Evaluate conditions until we find the active clause.
7069
DiagnosticTransaction DT(Context.Diags);
7170
auto classification =
72-
Parser::classifyConditionalCompilationExpr(clause.Cond, Context,
73-
Context.Diags,
74-
/*fullCheck*/ true);
71+
Parser::classifyConditionalCompilationExpr(clause.Cond, Context,
72+
Context.Diags,
73+
/*fullCheck*/ true);
7574
DT.abort();
7675
if (clause.Cond && !classification.getValueOr(false)) {
7776
continue;

lib/Parse/ParseStmt.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,8 +1789,8 @@ Parser::classifyConditionalCompilationExpr(Expr *condition,
17891789
if (!fullCheck) {
17901790
return None;
17911791
}
1792-
return
1793-
Context.getModule({ { argumentIdent, UDRE->getLoc() } }) != nullptr;
1792+
1793+
return Context.canImportModule({ argumentIdent, UDRE->getLoc() });
17941794
}
17951795

17961796
if (!fullCheck) {

lib/Sema/SourceLoader.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ class SkipNonTransparentFunctions : public DelayedParsingCallbacks {
7070

7171
} // unnamed namespace
7272

73+
bool SourceLoader::canImportModule(std::pair<Identifier, SourceLoc> ID) {
74+
// Search the memory buffers to see if we can find this file on disk.
75+
FileOrError inputFileOrError = findModule(Ctx, ID.first.str(),
76+
ID.second);
77+
if (!inputFileOrError) {
78+
auto err = inputFileOrError.getError();
79+
if (err != std::errc::no_such_file_or_directory) {
80+
Ctx.Diags.diagnose(ID.second, diag::sema_opening_import,
81+
ID.first, err.message());
82+
}
83+
84+
return false;
85+
}
86+
return true;
87+
}
88+
7389
Module *SourceLoader::loadModule(SourceLoc importLoc,
7490
ArrayRef<std::pair<Identifier, SourceLoc>> path) {
7591
// FIXME: Swift submodules?

lib/Serialization/SerializedModuleLoader.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "swift/Basic/SourceManager.h"
2020
#include "swift/Basic/Version.h"
2121
#include "llvm/ADT/SmallString.h"
22+
#include "llvm/Support/FileSystem.h"
2223
#include "llvm/Support/MemoryBuffer.h"
2324
#include "llvm/Support/Path.h"
2425
#include "llvm/Support/Debug.h"
@@ -39,15 +40,25 @@ SerializedModuleLoader::~SerializedModuleLoader() = default;
3940
static std::error_code
4041
openModuleFiles(StringRef DirName, StringRef ModuleFilename,
4142
StringRef ModuleDocFilename,
42-
std::unique_ptr<llvm::MemoryBuffer> &ModuleBuffer,
43-
std::unique_ptr<llvm::MemoryBuffer> &ModuleDocBuffer,
43+
std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
44+
std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
4445
llvm::SmallVectorImpl<char> &Scratch) {
46+
assert(((ModuleBuffer && ModuleDocBuffer)
47+
|| (!ModuleBuffer && !ModuleDocBuffer))
48+
&& "Module and Module Doc buffer must both be initialized or NULL");
4549
// Try to open the module file first. If we fail, don't even look for the
4650
// module documentation file.
4751
Scratch.clear();
4852
llvm::sys::path::append(Scratch, DirName, ModuleFilename);
53+
// If there are no buffers to load into, simply check for the existence of
54+
// the module file.
55+
if (!(ModuleBuffer || ModuleDocBuffer)) {
56+
return llvm::sys::fs::access(StringRef(Scratch.data(), Scratch.size()),
57+
llvm::sys::fs::AccessMode::Exist);
58+
}
59+
4960
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleOrErr =
50-
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
61+
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
5162
if (!ModuleOrErr)
5263
return ModuleOrErr.getError();
5364

@@ -56,21 +67,23 @@ openModuleFiles(StringRef DirName, StringRef ModuleFilename,
5667
Scratch.clear();
5768
llvm::sys::path::append(Scratch, DirName, ModuleDocFilename);
5869
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleDocOrErr =
59-
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
70+
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
6071
if (!ModuleDocOrErr &&
6172
ModuleDocOrErr.getError() != std::errc::no_such_file_or_directory) {
6273
return ModuleDocOrErr.getError();
6374
}
64-
ModuleBuffer = std::move(ModuleOrErr.get());
75+
76+
*ModuleBuffer = std::move(ModuleOrErr.get());
6577
if (ModuleDocOrErr)
66-
ModuleDocBuffer = std::move(ModuleDocOrErr.get());
78+
*ModuleDocBuffer = std::move(ModuleDocOrErr.get());
79+
6780
return std::error_code();
6881
}
6982

7083
static bool
7184
findModule(ASTContext &ctx, AccessPathElem moduleID,
72-
std::unique_ptr<llvm::MemoryBuffer> &moduleBuffer,
73-
std::unique_ptr<llvm::MemoryBuffer> &moduleDocBuffer,
85+
std::unique_ptr<llvm::MemoryBuffer> *moduleBuffer,
86+
std::unique_ptr<llvm::MemoryBuffer> *moduleDocBuffer,
7487
bool &isFramework) {
7588
llvm::SmallString<64> moduleFilename(moduleID.first.str());
7689
moduleFilename += '.';
@@ -95,7 +108,6 @@ findModule(ASTContext &ctx, AccessPathElem moduleID,
95108

96109
llvm::SmallString<128> scratch;
97110
llvm::SmallString<128> currPath;
98-
99111
isFramework = false;
100112
for (auto path : ctx.SearchPathOpts.ImportSearchPaths) {
101113
auto err = openModuleFiles(path,
@@ -353,6 +365,21 @@ FileUnit *SerializedModuleLoader::loadAST(
353365
return nullptr;
354366
}
355367

368+
bool
369+
SerializedModuleLoader::canImportModule(std::pair<Identifier, SourceLoc> mID) {
370+
// First see if we find it in the registered memory buffers.
371+
if (!MemoryBuffers.empty()) {
372+
auto bufIter = MemoryBuffers.find(mID.first.str());
373+
if (bufIter != MemoryBuffers.end()) {
374+
return true;
375+
}
376+
}
377+
378+
// Otherwise look on disk.
379+
bool isFramework = false;
380+
return findModule(Ctx, mID, nullptr, nullptr, isFramework);
381+
}
382+
356383
Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,
357384
Module::AccessPathTy path) {
358385
// FIXME: Swift submodules?
@@ -378,7 +405,7 @@ Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,
378405

379406
// Otherwise look on disk.
380407
if (!moduleInputBuffer) {
381-
if (!findModule(Ctx, moduleID, moduleInputBuffer, moduleDocInputBuffer,
408+
if (!findModule(Ctx, moduleID, &moduleInputBuffer, &moduleDocInputBuffer,
382409
isFramework)) {
383410
return nullptr;
384411
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module MissingRequirement {
2+
requires missing
3+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// RUN: rm -rf %t && mkdir -p %t
2+
// RUN: cp -R %S/Inputs/mixed-target %t
3+
4+
// FIXME: BEGIN -enable-source-import hackaround
5+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/CoreGraphics.swift
6+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/Foundation.swift
7+
// FIXME: END -enable-source-import hackaround
8+
9+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -import-objc-header %t/mixed-target/header.h -emit-module-path %t/MixedWithHeader.swiftmodule %S/Inputs/mixed-with-header.swift %S/../../Inputs/empty.swift -module-name MixedWithHeader -disable-objc-attr-requires-foundation-module
10+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify
11+
12+
// RUN: rm -rf %t/mixed-target/
13+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify
14+
15+
// REQUIRES: objc_interop
16+
17+
// Test that 'canImport(Foo)' directives do not open symbols from 'Foo' into the
18+
// current module. Only an 'import Foo' statement should do this.
19+
20+
#if canImport(AppKit)
21+
class AppKitView : NSView {} // expected-error {{use of undeclared type 'NSView'}}
22+
#endif
23+
24+
#if canImport(UIKit)
25+
class UIKitView : UIView {} // expected-error {{use of undeclared type 'UIView'}}
26+
#endif
27+
28+
#if canImport(CoreGraphics)
29+
let square = CGRect(x: 100, y: 100, width: 100, height: 100) // expected-error {{use of unresolved identifier 'CGRect'}}
30+
// expected-error@-1 {{'square' used within its own type}}
31+
// expected-error@-2 {{could not infer type for 'square'}}
32+
let (r, s) = square.divided(atDistance: 50, from: .minXEdge)
33+
#endif
34+
35+
#if canImport(MixedWithHeader)
36+
let object = NSObject() // expected-error {{use of unresolved identifier 'NSObject'}}
37+
// expected-error@-1 {{'object' used within its own type}}
38+
// expected-error@-2 {{could not infer type for 'object'}}
39+
let someAPI = Derived() // expected-error {{use of unresolved identifier 'Derived'}}
40+
#endif
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/missing-requirement %s -verify
2+
3+
// REQUIRES: objc_interop
4+
5+
class Unique {}
6+
7+
#if canImport(MissingRequirement)
8+
class Unique {} // No diagnostic
9+
#endif

0 commit comments

Comments
 (0)