Skip to content

Optimize Condition Resolution #6279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,13 @@ class ASTContext {
/// Does nothing in non-asserts (NDEBUG) builds.
void verifyAllLoadedModules() const;

/// \brief Check whether the module with a given name can be imported without
/// importing it.
///
/// Note that even if this check succeeds, errors may still occur if the
/// module is loaded in full.
bool canImportModule(std::pair<Identifier, SourceLoc> ModulePath);

/// \returns a module with a given name that was already loaded. If the
/// module was not loaded, returns nullptr.
ModuleDecl *getLoadedModule(
Expand Down
31 changes: 24 additions & 7 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,20 @@ class alignas(1 << DeclAlignInBits) Decl {
enum { NumExtensionDeclBits = NumDeclBits + 6 };
static_assert(NumExtensionDeclBits <= 32, "fits in an unsigned");

class IfConfigDeclBitfields {
friend class IfConfigDecl;
unsigned : NumDeclBits;

/// Whether this decl is missing its closing '#endif'.
unsigned HadMissingEnd : 1;

/// Whether this condition has been resolved either statically by Parse or
/// later by Condition Resolution.
unsigned HasBeenResolved : 1;
};
enum { NumIfConfigDeclBits = NumDeclBits + 2 };
static_assert(NumIfConfigDeclBits <= 32, "fits in an unsigned");

protected:
union {
DeclBitfields DeclBits;
Expand All @@ -601,6 +615,7 @@ class alignas(1 << DeclAlignInBits) Decl {
PrecedenceGroupDeclBitfields PrecedenceGroupDeclBits;
ImportDeclBitfields ImportDeclBits;
ExtensionDeclBitfields ExtensionDeclBits;
IfConfigDeclBitfields IfConfigDeclBits;
uint32_t OpaqueBits;
};

Expand Down Expand Up @@ -1931,14 +1946,16 @@ class IfConfigDecl : public Decl {
/// The array is ASTContext allocated.
ArrayRef<IfConfigDeclClause> Clauses;
SourceLoc EndLoc;
bool HadMissingEnd;
bool HasBeenResolved = false;

public:

IfConfigDecl(DeclContext *Parent, ArrayRef<IfConfigDeclClause> Clauses,
SourceLoc EndLoc, bool HadMissingEnd)
: Decl(DeclKind::IfConfig, Parent), Clauses(Clauses),
EndLoc(EndLoc), HadMissingEnd(HadMissingEnd) {}
: Decl(DeclKind::IfConfig, Parent), Clauses(Clauses), EndLoc(EndLoc)
{
IfConfigDeclBits.HadMissingEnd = HadMissingEnd;
IfConfigDeclBits.HasBeenResolved = false;
}

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

Expand All @@ -1955,13 +1972,13 @@ class IfConfigDecl : public Decl {
return {};
}

bool isResolved() const { return HasBeenResolved; }
void setResolved() { HasBeenResolved = true; }
bool isResolved() const { return IfConfigDeclBits.HasBeenResolved; }
void setResolved() { IfConfigDeclBits.HasBeenResolved = true; }

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

bool hadMissingEnd() const { return HadMissingEnd; }
bool hadMissingEnd() const { return IfConfigDeclBits.HadMissingEnd; }

SourceRange getSourceRange() const;

Expand Down
7 changes: 7 additions & 0 deletions include/swift/AST/ModuleLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ class ModuleLoader {
public:
virtual ~ModuleLoader() = default;

/// \brief Check whether the module with a given name can be imported without
/// importing it.
///
/// Note that even if this check succeeds, errors may still occur if the
/// module is loaded in full.
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) = 0;

/// \brief Import a module with the given module path.
///
/// \param importLoc The location of the 'import' keyword.
Expand Down
7 changes: 7 additions & 0 deletions include/swift/ClangImporter/ClangImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ class ClangImporter final : public ClangModuleLoader {

~ClangImporter();

/// \brief Check whether the module with a given name can be imported without
/// importing it.
///
/// Note that even if this check succeeds, errors may still occur if the
/// module is loaded in full.
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;

/// \brief Import a module with the given module path.
///
/// Clang modules will be imported using the Objective-C ARC dialect,
Expand Down
7 changes: 7 additions & 0 deletions include/swift/Sema/SourceLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ class SourceLoader : public ModuleLoader {
SourceLoader &operator=(const SourceLoader &) = delete;
SourceLoader &operator=(SourceLoader &&) = delete;

/// \brief Check whether the module with a given name can be imported without
/// importing it.
///
/// Note that even if this check succeeds, errors may still occur if the
/// module is loaded in full.
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;

/// \brief Import a module with the given module path.
///
/// \param importLoc The location of the 'import' keyword.
Expand Down
7 changes: 7 additions & 0 deletions include/swift/Serialization/SerializedModuleLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ class SerializedModuleLoader : public ModuleLoader {
SerializedModuleLoader &operator=(const SerializedModuleLoader &) = delete;
SerializedModuleLoader &operator=(SerializedModuleLoader &&) = delete;

/// \brief Check whether the module with a given name can be imported without
/// importing it.
///
/// Note that even if this check succeeds, errors may still occur if the
/// module is loaded in full.
virtual bool canImportModule(std::pair<Identifier, SourceLoc> named) override;

/// \brief Import a module with the given module path.
///
/// \param importLoc The location of the 'import' keyword.
Expand Down
14 changes: 14 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,20 @@ GenericEnvironment *ASTContext::getOrCreateCanonicalGenericEnvironment(
return env;
}

bool ASTContext::canImportModule(std::pair<Identifier, SourceLoc> ModulePath) {
// If this module has already been successfully imported, it is importable.
if (getLoadedModule(ModulePath) != nullptr)
return true;

// Otherwise, ask the module loaders.
for (auto &importer : Impl.ModuleLoaders) {
if (importer->canImportModule(ModulePath)) {
return true;
}
}
return false;
}

Module *
ASTContext::getModule(ArrayRef<std::pair<Identifier, SourceLoc>> ModulePath) {
assert(!ModulePath.empty());
Expand Down
17 changes: 17 additions & 0 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,23 @@ bool ClangImporter::isModuleImported(const clang::Module *M) {
return M->NameVisibility == clang::Module::NameVisibilityKind::AllVisible;
}

bool ClangImporter::canImportModule(std::pair<Identifier, SourceLoc> moduleID) {
// Look up the top-level module to see if it exists.
// FIXME: This only works with top-level modules.
auto &clangHeaderSearch = Impl.getClangPreprocessor().getHeaderSearchInfo();
clang::Module *clangModule =
clangHeaderSearch.lookupModule(moduleID.first.str());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late comment, but this will only tell you that the module map was found and parsed. The module may still be unavailable for a variety of reasons (e.g. it has an unfulfilled "requires" declaration). You should also call the following method from clang to check that the module makes sense to import:

  /// \brief Determine whether this module is available for use within the
  /// current translation unit.
  ///
  /// \param LangOpts The language options used for the current
  /// translation unit.
  ///
  /// \param Target The target options used for the current translation unit.
  ///
  /// \param Req If this module is unavailable, this parameter
  /// will be set to one of the requirements that is not met for use of
  /// this module.
  bool isAvailable(const LangOptions &LangOpts, 
                   const TargetInfo &Target,
                   Requirement &Req,
                   UnresolvedHeaderDirective &MissingHeader) const;

I think you can just ignore the out parameters (Req and MissingHeader), since you're not trying to diagnose why the module isn't available.

Copy link
Contributor

@benlangmuir benlangmuir Jan 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, you can test this by adding a clang module whose module map contains e.g.:

module Foo {
  requires missing
}

which should cause canImport(Foo) to fail. If you want a case where it succeeds, "requires swift" should do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

if (!clangModule) {
return false;
}

clang::Module::Requirement r;
clang::Module::UnresolvedHeaderDirective mh;
clang::Module *m;
auto &ctx = Impl.getClangASTContext();
return clangModule->isAvailable(ctx.getLangOpts(), getTargetInfo(), r, mh, m);
}

Module *ClangImporter::loadModule(
SourceLoc importLoc,
ArrayRef<std::pair<Identifier, SourceLoc>> path) {
Expand Down
9 changes: 4 additions & 5 deletions lib/Parse/ConditionResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ namespace {

ConditionClauseResolver(SmallVectorImpl<Decl *> &ExtraTLCDs,
SourceFile &SF)
: NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF),
Context(SF.getASTContext()) {}
: NDF(SF), ExtraTLCDs(ExtraTLCDs), SF(SF), Context(SF.getASTContext()) {}

void resolveClausesAndInsertMembers(IterableDeclContext *Nom,
IfConfigDecl *Cond) {
Expand All @@ -69,9 +68,9 @@ namespace {
// Evaluate conditions until we find the active clause.
DiagnosticTransaction DT(Context.Diags);
auto classification =
Parser::classifyConditionalCompilationExpr(clause.Cond, Context,
Context.Diags,
/*fullCheck*/ true);
Parser::classifyConditionalCompilationExpr(clause.Cond, Context,
Context.Diags,
/*fullCheck*/ true);
DT.abort();
if (clause.Cond && !classification.getValueOr(false)) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1789,8 +1789,8 @@ Parser::classifyConditionalCompilationExpr(Expr *condition,
if (!fullCheck) {
return None;
}
return
Context.getModule({ { argumentIdent, UDRE->getLoc() } }) != nullptr;

return Context.canImportModule({ argumentIdent, UDRE->getLoc() });
}

if (!fullCheck) {
Expand Down
16 changes: 16 additions & 0 deletions lib/Sema/SourceLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ class SkipNonTransparentFunctions : public DelayedParsingCallbacks {

} // unnamed namespace

bool SourceLoader::canImportModule(std::pair<Identifier, SourceLoc> ID) {
// Search the memory buffers to see if we can find this file on disk.
FileOrError inputFileOrError = findModule(Ctx, ID.first.str(),
ID.second);
if (!inputFileOrError) {
auto err = inputFileOrError.getError();
if (err != std::errc::no_such_file_or_directory) {
Ctx.Diags.diagnose(ID.second, diag::sema_opening_import,
ID.first, err.message());
}

return false;
}
return true;
}

Module *SourceLoader::loadModule(SourceLoc importLoc,
ArrayRef<std::pair<Identifier, SourceLoc>> path) {
// FIXME: Swift submodules?
Expand Down
47 changes: 37 additions & 10 deletions lib/Serialization/SerializedModuleLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "swift/Basic/SourceManager.h"
#include "swift/Basic/Version.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Debug.h"
Expand All @@ -39,15 +40,25 @@ SerializedModuleLoader::~SerializedModuleLoader() = default;
static std::error_code
openModuleFiles(StringRef DirName, StringRef ModuleFilename,
StringRef ModuleDocFilename,
std::unique_ptr<llvm::MemoryBuffer> &ModuleBuffer,
std::unique_ptr<llvm::MemoryBuffer> &ModuleDocBuffer,
std::unique_ptr<llvm::MemoryBuffer> *ModuleBuffer,
std::unique_ptr<llvm::MemoryBuffer> *ModuleDocBuffer,
llvm::SmallVectorImpl<char> &Scratch) {
assert(((ModuleBuffer && ModuleDocBuffer)
|| (!ModuleBuffer && !ModuleDocBuffer))
&& "Module and Module Doc buffer must both be initialized or NULL");
// Try to open the module file first. If we fail, don't even look for the
// module documentation file.
Scratch.clear();
llvm::sys::path::append(Scratch, DirName, ModuleFilename);
// If there are no buffers to load into, simply check for the existence of
// the module file.
if (!(ModuleBuffer || ModuleDocBuffer)) {
return llvm::sys::fs::access(StringRef(Scratch.data(), Scratch.size()),
llvm::sys::fs::AccessMode::Exist);
}

llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleOrErr =
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
if (!ModuleOrErr)
return ModuleOrErr.getError();

Expand All @@ -56,21 +67,23 @@ openModuleFiles(StringRef DirName, StringRef ModuleFilename,
Scratch.clear();
llvm::sys::path::append(Scratch, DirName, ModuleDocFilename);
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> ModuleDocOrErr =
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
llvm::MemoryBuffer::getFile(StringRef(Scratch.data(), Scratch.size()));
if (!ModuleDocOrErr &&
ModuleDocOrErr.getError() != std::errc::no_such_file_or_directory) {
return ModuleDocOrErr.getError();
}
ModuleBuffer = std::move(ModuleOrErr.get());

*ModuleBuffer = std::move(ModuleOrErr.get());
if (ModuleDocOrErr)
ModuleDocBuffer = std::move(ModuleDocOrErr.get());
*ModuleDocBuffer = std::move(ModuleDocOrErr.get());

return std::error_code();
}

static bool
findModule(ASTContext &ctx, AccessPathElem moduleID,
std::unique_ptr<llvm::MemoryBuffer> &moduleBuffer,
std::unique_ptr<llvm::MemoryBuffer> &moduleDocBuffer,
std::unique_ptr<llvm::MemoryBuffer> *moduleBuffer,
std::unique_ptr<llvm::MemoryBuffer> *moduleDocBuffer,
bool &isFramework) {
llvm::SmallString<64> moduleFilename(moduleID.first.str());
moduleFilename += '.';
Expand All @@ -95,7 +108,6 @@ findModule(ASTContext &ctx, AccessPathElem moduleID,

llvm::SmallString<128> scratch;
llvm::SmallString<128> currPath;

isFramework = false;
for (auto path : ctx.SearchPathOpts.ImportSearchPaths) {
auto err = openModuleFiles(path,
Expand Down Expand Up @@ -353,6 +365,21 @@ FileUnit *SerializedModuleLoader::loadAST(
return nullptr;
}

bool
SerializedModuleLoader::canImportModule(std::pair<Identifier, SourceLoc> mID) {
// First see if we find it in the registered memory buffers.
if (!MemoryBuffers.empty()) {
auto bufIter = MemoryBuffers.find(mID.first.str());
if (bufIter != MemoryBuffers.end()) {
return true;
}
}

// Otherwise look on disk.
bool isFramework = false;
return findModule(Ctx, mID, nullptr, nullptr, isFramework);
}

Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,
Module::AccessPathTy path) {
// FIXME: Swift submodules?
Expand All @@ -378,7 +405,7 @@ Module *SerializedModuleLoader::loadModule(SourceLoc importLoc,

// Otherwise look on disk.
if (!moduleInputBuffer) {
if (!findModule(Ctx, moduleID, moduleInputBuffer, moduleDocInputBuffer,
if (!findModule(Ctx, moduleID, &moduleInputBuffer, &moduleDocInputBuffer,
isFramework)) {
return nullptr;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module MissingRequirement {
requires missing
}
40 changes: 40 additions & 0 deletions test/ClangImporter/MixedSource/can_import_objc_idempotent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: cp -R %S/Inputs/mixed-target %t

// FIXME: BEGIN -enable-source-import hackaround
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/CoreGraphics.swift
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -o %t %clang-importer-sdk-path/swift-modules/Foundation.swift
// FIXME: END -enable-source-import hackaround

// 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
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify

// RUN: rm -rf %t/mixed-target/
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk-nosource -I %t) -I %S/../Inputs/custom-modules -typecheck %s -verify

// REQUIRES: objc_interop

// Test that 'canImport(Foo)' directives do not open symbols from 'Foo' into the
// current module. Only an 'import Foo' statement should do this.

#if canImport(AppKit)
class AppKitView : NSView {} // expected-error {{use of undeclared type 'NSView'}}
#endif

#if canImport(UIKit)
class UIKitView : UIView {} // expected-error {{use of undeclared type 'UIView'}}
#endif

#if canImport(CoreGraphics)
let square = CGRect(x: 100, y: 100, width: 100, height: 100) // expected-error {{use of unresolved identifier 'CGRect'}}
// expected-error@-1 {{'square' used within its own type}}
// expected-error@-2 {{could not infer type for 'square'}}
let (r, s) = square.divided(atDistance: 50, from: .minXEdge)
#endif

#if canImport(MixedWithHeader)
let object = NSObject() // expected-error {{use of unresolved identifier 'NSObject'}}
// expected-error@-1 {{'object' used within its own type}}
// expected-error@-2 {{could not infer type for 'object'}}
let someAPI = Derived() // expected-error {{use of unresolved identifier 'Derived'}}
#endif
9 changes: 9 additions & 0 deletions test/ClangImporter/can_import_missing_requirement.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -I %S/Inputs/missing-requirement %s -verify

// REQUIRES: objc_interop

class Unique {}

#if canImport(MissingRequirement)
class Unique {} // No diagnostic
#endif
Loading