Skip to content

Commit d4aa18a

Browse files
committed
[cxx-interop][index] emit symbolic interface files for C++ modules
1 parent f36e5cf commit d4aa18a

11 files changed

+469
-1
lines changed

include/swift/AST/DiagnosticsFrontend.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ REMARK(remark_indexing_system_module,none,
270270
"indexing system module at %0"
271271
"%select{|; skipping because of a broken swiftinterface}1",
272272
(StringRef, bool))
273+
REMARK(remark_emitting_symbolic_interface_module,none,
274+
"emitting symbolic interface at %0"
275+
"%select{|; skipping because it's up to date}1",
276+
(StringRef, bool))
273277

274278
ERROR(error_wrong_number_of_arguments,none,
275279
"wrong number of '%0' arguments (expected %1, got %2)",

include/swift/AST/PrintOptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,10 @@ struct PrintOptions {
562562
/// If false, we print them as ordinary associated types.
563563
bool PrintPrimaryAssociatedTypes = true;
564564

565+
/// If true, import and print C++ declarations with symbolic import feature
566+
/// enabled.
567+
bool PrintSymbolicCXXDecls = false;
568+
565569
/// If this is not \c nullptr then function bodies (including accessors
566570
/// and constructors) will be printed by this function.
567571
std::function<void(const ValueDecl *, ASTPrinter &)> FunctionBody;

include/swift/ClangImporter/ClangImporter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,9 @@ class ClangImporter final : public ClangModuleLoader {
555555
/// Emit diagnostics for declarations named name that are members
556556
/// of the provided baseType.
557557
void diagnoseMemberValue(const DeclName &name, const Type &baseType) override;
558+
559+
/// Enable/disable the symbolic import experimental feature.
560+
void enableSymbolicImportFeature(bool isEnabled = true);
558561
};
559562

560563
ImportDecl *createImportDecl(ASTContext &Ctx, DeclContext *DC, ClangNode ClangN,

include/swift/IDE/ModuleInterfacePrinting.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include <string>
2020
#include <vector>
2121

22+
namespace clang {
23+
class Module;
24+
}
25+
2226
namespace swift {
2327
class ASTContext;
2428
class ASTPrinter;
@@ -68,6 +72,10 @@ void printHeaderInterface(StringRef Filename, ASTContext &Ctx,
6872
void printSwiftSourceInterface(SourceFile &File, ASTPrinter &Printer,
6973
const PrintOptions &Options);
7074

75+
/// Print the symbolic Swift interface for a given imported clang module.
76+
void printSymbolicSwiftClangModuleInterface(ModuleDecl *M, ASTPrinter &Printer,
77+
const clang::Module *clangModule);
78+
7179
} // namespace ide
7280

7381
} // namespace swift

lib/ClangImporter/ClangImporter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6564,3 +6564,8 @@ CustomRefCountingOperationResult CustomRefCountingOperation::evaluate(
65646564

65656565
return {CustomRefCountingOperationResult::tooManyFound, nullptr, name};
65666566
}
6567+
6568+
void ClangImporter::enableSymbolicImportFeature(bool isEnabled) {
6569+
Impl.importSymbolicCXXDecls = isEnabled;
6570+
Impl.nameImporter->enableSymbolicImportFeature(isEnabled);
6571+
}

lib/ClangImporter/ImportName.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ class NameImporter {
458458
clang::ObjCInterfaceDecl *classDecl,
459459
bool forInstance);
460460

461+
inline void enableSymbolicImportFeature(bool isEnabled) {
462+
importSymbolicCXXDecls = isEnabled;
463+
}
464+
461465
private:
462466
bool enableObjCInterop() const { return swiftCtx.LangOpts.EnableObjCInterop; }
463467

lib/IDE/ModuleInterfacePrinting.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,18 @@ void swift::ide::printModuleInterface(
544544
auto &SwiftContext = TopLevelMod->getASTContext();
545545
auto &Importer =
546546
static_cast<ClangImporter &>(*SwiftContext.getClangModuleLoader());
547+
if (Options.PrintSymbolicCXXDecls)
548+
Importer.enableSymbolicImportFeature(true);
549+
struct SymbolicImportRAII {
550+
bool PrintSymbolicCXXDecls;
551+
ClangImporter &Importer;
552+
SymbolicImportRAII(bool PrintSymbolicCXXDecls, ClangImporter &Importer)
553+
: PrintSymbolicCXXDecls(PrintSymbolicCXXDecls), Importer(Importer) {}
554+
~SymbolicImportRAII() {
555+
if (PrintSymbolicCXXDecls)
556+
Importer.enableSymbolicImportFeature(false);
557+
}
558+
} deferDisableSymbolicImport(Options.PrintSymbolicCXXDecls, Importer);
547559

548560
auto AdjustedOptions = Options;
549561
adjustPrintOptions(AdjustedOptions);
@@ -1134,3 +1146,22 @@ void ClangCommentPrinter::updateLastEntityLine(clang::FileID FID,
11341146
if (LineNo > LastEntiyLine)
11351147
LastEntiyLine = LineNo;
11361148
}
1149+
1150+
void swift::ide::printSymbolicSwiftClangModuleInterface(
1151+
ModuleDecl *M, ASTPrinter &Printer, const clang::Module *clangModule) {
1152+
std::string headerComment;
1153+
llvm::raw_string_ostream(headerComment)
1154+
<< "// Swift interface for " << (clangModule->IsSystem ? "system " : "")
1155+
<< "module '" << clangModule->Name << "'\n";
1156+
Printer.printText(headerComment);
1157+
1158+
ModuleTraversalOptions opts;
1159+
opts |= ModuleTraversal::VisitSubmodules;
1160+
auto popts =
1161+
PrintOptions::printModuleInterface(/*printFullConvention=*/false);
1162+
popts.PrintSymbolicCXXDecls = true;
1163+
popts.PrintDocumentationComments = false;
1164+
popts.PrintRegularClangComments = false;
1165+
printModuleInterface(M, {}, opts, Printer, popts,
1166+
/*SynthesizeExtensions=*/false);
1167+
}

lib/Index/IndexRecord.cpp

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "swift/Index/IndexRecord.h"
1414
#include "swift/AST/ASTContext.h"
15+
#include "swift/AST/ASTPrinter.h"
1516
#include "swift/AST/Decl.h"
1617
#include "swift/AST/DiagnosticsFrontend.h"
1718
#include "swift/AST/Expr.h"
@@ -24,13 +25,15 @@
2425
#include "swift/AST/Types.h"
2526
#include "swift/Basic/PathRemapper.h"
2627
#include "swift/ClangImporter/ClangModule.h"
28+
#include "swift/IDE/ModuleInterfacePrinting.h"
2729
#include "swift/Index/Index.h"
2830
#include "clang/Basic/FileManager.h"
2931
#include "clang/Frontend/CompilerInstance.h"
30-
#include "clang/Index/IndexingAction.h"
3132
#include "clang/Index/IndexRecordWriter.h"
3233
#include "clang/Index/IndexUnitWriter.h"
34+
#include "clang/Index/IndexingAction.h"
3335
#include "clang/Lex/Preprocessor.h"
36+
#include "clang/Serialization/ASTReader.h"
3437
#include "llvm/Support/Path.h"
3538

3639
using namespace swift;
@@ -393,6 +396,176 @@ emitDataForSwiftSerializedModule(ModuleDecl *module,
393396
const PathRemapper &pathRemapper,
394397
SourceFile *initialFile);
395398

399+
// FIXME (Alex): Share code with importer.
400+
inline bool requiresCPlusPlus(const clang::Module *module) {
401+
// The libc++ modulemap doesn't currently declare the requirement.
402+
if (module->getTopLevelModuleName() == "std")
403+
return true;
404+
405+
// Modulemaps often declare the requirement for the top-level module only.
406+
if (auto parent = module->Parent) {
407+
if (requiresCPlusPlus(parent))
408+
return true;
409+
}
410+
411+
return llvm::any_of(module->Requirements, [](clang::Module::Requirement req) {
412+
return req.first == "cplusplus";
413+
});
414+
}
415+
416+
static void
417+
appendSymbolicInterfaceToIndexStorePath(SmallVectorImpl<char> &resultingPath) {
418+
llvm::sys::path::append(resultingPath, "interfaces");
419+
}
420+
421+
static bool initSymbolicInterfaceStorePath(StringRef storePath,
422+
std::string &error) {
423+
using namespace llvm::sys;
424+
SmallString<128> subPath = storePath;
425+
appendSymbolicInterfaceToIndexStorePath(subPath);
426+
std::error_code ec = fs::create_directories(subPath);
427+
if (ec) {
428+
llvm::raw_string_ostream err(error);
429+
err << "failed to create directory '" << subPath << "': " << ec.message();
430+
return true;
431+
}
432+
return false;
433+
}
434+
435+
static void appendSymbolicInterfaceClangModuleFilename(
436+
StringRef filePath, SmallVectorImpl<char> &resultingPath) {
437+
llvm::sys::path::append(resultingPath, llvm::sys::path::filename(filePath));
438+
StringRef extension = ".symbolicswiftinterface";
439+
resultingPath.append(extension.begin(), extension.end());
440+
}
441+
442+
// FIXME (Alex): Share code with IndexUnitWriter in LLVM after refactoring it.
443+
static Optional<bool>
444+
isFileUpToDateForOutputFile(StringRef filePath,
445+
Optional<StringRef> timeCompareFilePath,
446+
std::string &error) {
447+
llvm::sys::fs::file_status unitStat;
448+
if (std::error_code ec = llvm::sys::fs::status(filePath, unitStat)) {
449+
if (ec != std::errc::no_such_file_or_directory) {
450+
llvm::raw_string_ostream err(error);
451+
err << "could not access path '" << filePath << "': " << ec.message();
452+
return {};
453+
}
454+
return false;
455+
}
456+
457+
if (!timeCompareFilePath)
458+
return true;
459+
460+
llvm::sys::fs::file_status compareStat;
461+
if (std::error_code ec =
462+
llvm::sys::fs::status(*timeCompareFilePath, compareStat)) {
463+
if (ec != std::errc::no_such_file_or_directory) {
464+
llvm::raw_string_ostream err(error);
465+
err << "could not access path '" << *timeCompareFilePath
466+
<< "': " << ec.message();
467+
return {};
468+
}
469+
return true;
470+
}
471+
472+
// Return true (unit is up-to-date) if the file to compare is older than the
473+
// unit file.
474+
return compareStat.getLastModificationTime() <=
475+
unitStat.getLastModificationTime();
476+
}
477+
478+
/// Emit the symbolic swift interface file for an imported Clang module into the
479+
/// index store directory.
480+
///
481+
/// The swift interface file is emitted only when it doesn't exist yet, or when
482+
/// the PCM for the Clang module has been updated.
483+
///
484+
/// System modules without the 'cplusplus' requirement are not emitted.
485+
static void emitSymbolicInterfaceForClangModule(
486+
ClangModuleUnit *clangModUnit, ModuleDecl *M,
487+
const clang::Module *clangModule, StringRef indexStorePath,
488+
const clang::CompilerInstance &clangCI, DiagnosticEngine &diags) {
489+
if (!M->getASTContext().LangOpts.EnableCXXInterop)
490+
return;
491+
// Skip system modules without an explicit 'cplusplus' requirement.
492+
bool isSystem = clangModUnit->isSystemModule();
493+
if (isSystem && !requiresCPlusPlus(clangModule))
494+
return;
495+
496+
// Make sure the `interfaces` directory is created.
497+
std::string error;
498+
if (initSymbolicInterfaceStorePath(indexStorePath, error)) {
499+
diags.diagnose(SourceLoc(), diag::error_create_index_dir, error);
500+
return;
501+
}
502+
503+
// Determine the output name for the symbolic interface file.
504+
clang::serialization::ModuleFile *ModFile =
505+
clangCI.getASTReader()->getModuleManager().lookup(
506+
clangModule->getASTFile());
507+
assert(ModFile && "no module file loaded for module ?");
508+
SmallString<128> interfaceOutputPath = indexStorePath;
509+
appendSymbolicInterfaceToIndexStorePath(interfaceOutputPath);
510+
appendSymbolicInterfaceClangModuleFilename(ModFile->FileName,
511+
interfaceOutputPath);
512+
513+
// Check if the symbolic interface file is already up to date.
514+
auto upToDate = isFileUpToDateForOutputFile(
515+
interfaceOutputPath, StringRef(ModFile->FileName), error);
516+
if (!upToDate) {
517+
diags.diagnose(SourceLoc(), diag::error_index_failed_status_check, error);
518+
return;
519+
}
520+
if (M->getASTContext().LangOpts.EnableIndexingSystemModuleRemarks) {
521+
diags.diagnose(SourceLoc(), diag::remark_emitting_symbolic_interface_module,
522+
interfaceOutputPath, *upToDate);
523+
}
524+
if (*upToDate)
525+
return;
526+
527+
// Output the interface to a temporary file first.
528+
SmallString<128> tempOutputPath;
529+
tempOutputPath = llvm::sys::path::parent_path(interfaceOutputPath);
530+
llvm::sys::path::append(tempOutputPath,
531+
llvm::sys::path::filename(interfaceOutputPath));
532+
tempOutputPath += "-%%%%%%%%";
533+
int tempFD;
534+
if (llvm::sys::fs::createUniqueFile(tempOutputPath.str(), tempFD,
535+
tempOutputPath)) {
536+
llvm::raw_string_ostream errOS(error);
537+
errOS << "failed to create temporary file: " << tempOutputPath;
538+
diags.diagnose(SourceLoc(), diag::error_write_index_record, errOS.str());
539+
return;
540+
}
541+
542+
llvm::raw_fd_ostream os(tempFD, /*shouldClose=*/true);
543+
std::unique_ptr<ASTPrinter> printer;
544+
printer.reset(new StreamPrinter(os));
545+
ide::printSymbolicSwiftClangModuleInterface(M, *printer, clangModule);
546+
os.close();
547+
548+
if (os.has_error()) {
549+
llvm::raw_string_ostream errOS(error);
550+
errOS << "failed to write '" << tempOutputPath
551+
<< "': " << os.error().message();
552+
diags.diagnose(SourceLoc(), diag::error_write_index_record, errOS.str());
553+
os.clear_error();
554+
return;
555+
}
556+
557+
// Move the resulting output to the destination symbolic interface file.
558+
std::error_code ec = llvm::sys::fs::rename(
559+
/*from=*/tempOutputPath.c_str(), /*to=*/interfaceOutputPath.c_str());
560+
if (ec) {
561+
llvm::raw_string_ostream errOS(error);
562+
errOS << "failed to rename '" << tempOutputPath << "' to '"
563+
<< interfaceOutputPath << "': " << ec.message();
564+
diags.diagnose(SourceLoc(), diag::error_write_index_record, errOS.str());
565+
return;
566+
}
567+
}
568+
396569
static void addModuleDependencies(ArrayRef<ImportedModule> imports,
397570
StringRef indexStorePath,
398571
bool indexClangModules,
@@ -444,6 +617,9 @@ static void addModuleDependencies(ArrayRef<ImportedModule> imports,
444617
if (shouldIndexModule)
445618
clang::index::emitIndexDataForModuleFile(clangMod,
446619
clangCI, unitWriter);
620+
// Emit the symbolic interface file in addition to index data.
621+
emitSymbolicInterfaceForClangModule(
622+
clangModUnit, mod, clangMod, indexStorePath, clangCI, diags);
447623
}
448624
} else {
449625
// Serialized AST file.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: split-file %s %t
3+
4+
// Verify that symbolic interfaces are emitted.
5+
//
6+
// RUN: %target-swift-frontend %t/test.swift -I %t -c -index-system-modules -index-store-path %t/store -enable-experimental-cxx-interop -Rindexing-system-module 2>&1 | %FileCheck --check-prefix=REMARK_NEW %s
7+
// RUN: ls %t/store/interfaces | %FileCheck --check-prefix=FILES %s
8+
// RUN: cat %t/store/interfaces/std* | %FileCheck --check-prefix=CHECK %s
9+
10+
// Verify that symbolic interfaces are not emitted when PCM doesn't change.
11+
//
12+
// RUN: %target-swift-frontend %t/test.swift -I %t -c -index-system-modules -index-store-path %t/store -enable-experimental-cxx-interop -Rindexing-system-module 2>&1 | %FileCheck --check-prefix=REMARK_NO_UPDATE %s
13+
// RUN: ls %t/store/interfaces | %FileCheck --check-prefix=FILES %s
14+
15+
// REQUIRES: OS=macosx
16+
17+
//--- Inputs/module.modulemap
18+
module CxxModule {
19+
header "headerA.h"
20+
requires cplusplus
21+
}
22+
23+
//--- Inputs/headerA.h
24+
25+
int freeFunction(int x, int y);
26+
27+
//--- test.swift
28+
29+
import CxxStdlib
30+
import CxxModule
31+
import Foundation
32+
33+
// REMARK_NEW: remark: emitting symbolic interface at {{.*}}/interfaces/std-{{.*}}.pcm.symbolicswiftinterface{{$}}
34+
// REMARK_NEW: remark: emitting symbolic interface at {{.*}}/interfaces/CxxModule-{{.*}}.pcm.symbolicswiftinterface{{$}}
35+
36+
// REMARK_NO_UPDATE: remark: emitting symbolic interface at {{.*}}/interfaces/std-{{.*}}.pcm.symbolicswiftinterface; skipping because it's up to date{{$}}
37+
// REMARK_NO_UPDATE: remark: emitting symbolic interface at {{.*}}/interfaces/CxxModule-{{.*}}.pcm.symbolicswiftinterface; skipping because it's up to date{{$}}
38+
39+
// FILES: CxxModule-{{.*}}.pcm.symbolicswiftinterface
40+
// FILES: std-{{.*}}.pcm.symbolicswiftinterface
41+
// FILES-NOT: Foundation*
42+
43+
// CHECK: // Swift interface for system module 'std'

0 commit comments

Comments
 (0)