Skip to content

[interop] add an option to emit C++ header interface for a module #40923

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 1 commit into from
Jan 20, 2022
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
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsFrontend.def
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ ERROR(error_mode_cannot_emit_reference_dependencies,none,
"this mode does not support emitting reference dependency files", ())
ERROR(error_mode_cannot_emit_header,none,
"this mode does not support emitting Objective-C headers", ())
ERROR(error_mode_cannot_emit_cxx_header,none,
"this mode does not support emitting C++ headers", ())
ERROR(error_mode_cannot_emit_loaded_module_trace,none,
"this mode does not support emitting the loaded module trace", ())
ERROR(error_mode_cannot_emit_module,none,
Expand Down
27 changes: 21 additions & 6 deletions include/swift/Basic/SupplementaryOutputPaths.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ struct SupplementaryOutputPaths {
/// \sa swift::printAsObjC
std::string ObjCHeaderOutputPath;

/// The path to which we should emit a C++ header for the module.
///
/// Currently only makes sense when the compiler has whole module knowledge.
/// The modes for which it makes sense include both WMO and the "merge
/// modules" job that happens after the normal compilation jobs. That's where
/// the header is emitted in single-file mode, since it needs whole-module
/// information.
///
/// \sa swift::printAsCxx
std::string CxxHeaderOutputPath;

/// The path to which we should emit a serialized module.
/// It is valid whenever there are any inputs.
///
Expand Down Expand Up @@ -160,7 +171,9 @@ struct SupplementaryOutputPaths {
/// Apply a given function for each existing (non-empty string) supplementary output
void forEachSetOutput(llvm::function_ref<void(const std::string&)> fn) const {
if (!ObjCHeaderOutputPath.empty())
fn(ObjCHeaderOutputPath);
fn(ObjCHeaderOutputPath);
if (!CxxHeaderOutputPath.empty())
fn(CxxHeaderOutputPath);
if (!ModuleOutputPath.empty())
fn(ModuleOutputPath);
if (!ModuleSourceInfoOutputPath.empty())
Expand Down Expand Up @@ -196,14 +209,16 @@ struct SupplementaryOutputPaths {
}

bool empty() const {
return ObjCHeaderOutputPath.empty() && ModuleOutputPath.empty() &&
ModuleDocOutputPath.empty() && DependenciesFilePath.empty() &&
return ObjCHeaderOutputPath.empty() && CxxHeaderOutputPath.empty() &&
ModuleOutputPath.empty() && ModuleDocOutputPath.empty() &&
DependenciesFilePath.empty() &&
ReferenceDependenciesFilePath.empty() &&
SerializedDiagnosticsPath.empty() && LoadedModuleTracePath.empty() &&
TBDPath.empty() && ModuleInterfaceOutputPath.empty() &&
ModuleSourceInfoOutputPath.empty() && ABIDescriptorOutputPath.empty() &&
ModuleSemanticInfoOutputPath.empty() &&
YAMLOptRecordPath.empty() && BitstreamOptRecordPath.empty();
ModuleSourceInfoOutputPath.empty() &&
ABIDescriptorOutputPath.empty() &&
ModuleSemanticInfoOutputPath.empty() && YAMLOptRecordPath.empty() &&
BitstreamOptRecordPath.empty();
}
};
} // namespace swift
Expand Down
1 change: 1 addition & 0 deletions include/swift/Frontend/Frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ class CompilerInvocation {
std::string getOutputFilenameForAtMostOnePrimary() const;
std::string getMainInputFilenameForDebugInfoForAtMostOnePrimary() const;
std::string getObjCHeaderOutputPathForAtMostOnePrimary() const;
std::string getCxxHeaderOutputPathForAtMostOnePrimary() const;
std::string getModuleOutputPathForAtMostOnePrimary() const;
std::string
getReferenceDependenciesFilePathForPrimary(StringRef filename) const;
Expand Down
1 change: 1 addition & 0 deletions include/swift/Frontend/FrontendInputsAndOutputs.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ class FrontendInputsAndOutputs {
bool hasDependenciesPath() const;
bool hasReferenceDependenciesPath() const;
bool hasObjCHeaderOutputPath() const;
bool hasCxxHeaderOutputPath() const;
bool hasLoadedModuleTracePath() const;
bool hasModuleOutputPath() const;
bool hasModuleDocOutputPath() const;
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Frontend/FrontendOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ class FrontendOptions {
private:
static bool canActionEmitDependencies(ActionType);
static bool canActionEmitReferenceDependencies(ActionType);
static bool canActionEmitObjCHeader(ActionType);
static bool canActionEmitClangHeader(ActionType);
static bool canActionEmitLoadedModuleTrace(ActionType);
static bool canActionEmitModule(ActionType);
static bool canActionEmitModuleDoc(ActionType);
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Option/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,14 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">,
SupplementaryOutput]>,
MetaVarName<"<path>">, HelpText<"Emit an Objective-C header file to <path>">;

def emit_cxx_header : Flag<["-"], "emit-cxx-header">,
Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>,
HelpText<"Emit a C++ header file">;
def emit_cxx_header_path : Separate<["-"], "emit-cxx-header-path">,
Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath,
SupplementaryOutput]>,
MetaVarName<"<path>">, HelpText<"Emit a C++ header file to <path>">;

def static : Flag<["-"], "static">,
Flags<[FrontendOption, ModuleInterfaceOption, NoInteractiveOption]>,
HelpText<"Make this module statically linkable and make the output of -emit-library a static library.">;
Expand Down
5 changes: 5 additions & 0 deletions include/swift/PrintAsObjC/PrintAsObjC.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ namespace swift {
///
/// Returns true on error.
bool printAsObjC(raw_ostream &out, ModuleDecl *M, StringRef bridgingHeader);

/// Print the C++-compatible declarations in a module as a Clang header.
///
/// Returns true on error.
bool printAsCXX(raw_ostream &os, ModuleDecl *M);
}

#endif
7 changes: 6 additions & 1 deletion lib/Frontend/ArgsToFrontendOptionsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,11 +623,16 @@ bool ArgsToFrontendOptionsConverter::checkUnusedSupplementaryOutputPaths()
diag::error_mode_cannot_emit_reference_dependencies);
return true;
}
if (!FrontendOptions::canActionEmitObjCHeader(Opts.RequestedAction) &&
if (!FrontendOptions::canActionEmitClangHeader(Opts.RequestedAction) &&
Opts.InputsAndOutputs.hasObjCHeaderOutputPath()) {
Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_header);
return true;
}
if (!FrontendOptions::canActionEmitClangHeader(Opts.RequestedAction) &&
Opts.InputsAndOutputs.hasCxxHeaderOutputPath()) {
Diags.diagnose(SourceLoc(), diag::error_mode_cannot_emit_cxx_header);
return true;
}
if (!FrontendOptions::canActionEmitLoadedModuleTrace(Opts.RequestedAction) &&
Opts.InputsAndOutputs.hasLoadedModuleTracePath()) {
Diags.diagnose(SourceLoc(),
Expand Down
35 changes: 22 additions & 13 deletions lib/Frontend/ArgsToFrontendOutputsConverter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()

auto objCHeaderOutput = getSupplementaryFilenamesFromArguments(
options::OPT_emit_objc_header_path);
auto cxxHeaderOutput =
getSupplementaryFilenamesFromArguments(options::OPT_emit_cxx_header_path);
auto moduleOutput =
getSupplementaryFilenamesFromArguments(options::OPT_emit_module_path);
auto moduleDocOutput =
Expand Down Expand Up @@ -339,8 +341,8 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
options::OPT_emit_module_semantic_info_path);
auto optRecordOutput = getSupplementaryFilenamesFromArguments(
options::OPT_save_optimization_record_path);
if (!objCHeaderOutput || !moduleOutput || !moduleDocOutput ||
!dependenciesFile || !referenceDependenciesFile ||
if (!objCHeaderOutput || !cxxHeaderOutput || !moduleOutput ||
!moduleDocOutput || !dependenciesFile || !referenceDependenciesFile ||
!serializedDiagnostics || !fixItsOutput || !loadedModuleTrace || !TBD ||
!moduleInterfaceOutput || !privateModuleInterfaceOutput ||
!moduleSourceInfoOutput || !moduleSummaryOutput || !abiDescriptorOutput ||
Expand All @@ -354,6 +356,7 @@ SupplementaryOutputPathsComputer::getSupplementaryOutputPathsFromArguments()
for (unsigned i = 0; i < N; ++i) {
SupplementaryOutputPaths sop;
sop.ObjCHeaderOutputPath = (*objCHeaderOutput)[i];
sop.CxxHeaderOutputPath = (*cxxHeaderOutput)[i];
sop.ModuleOutputPath = (*moduleOutput)[i];
sop.ModuleDocOutputPath = (*moduleDocOutput)[i];
sop.DependenciesFilePath = (*dependenciesFile)[i];
Expand Down Expand Up @@ -439,6 +442,11 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(
file_types::TY_ObjCHeader, "",
defaultSupplementaryOutputPathExcludingExtension);

auto cxxHeaderOutputPath = determineSupplementaryOutputFilename(
OPT_emit_cxx_header, pathsFromArguments.CxxHeaderOutputPath,
file_types::TY_ObjCHeader, "",
defaultSupplementaryOutputPathExcludingExtension);

auto loadedModuleTracePath = determineSupplementaryOutputFilename(
OPT_emit_loaded_module_trace, pathsFromArguments.LoadedModuleTracePath,
file_types::TY_ModuleTrace, "",
Expand Down Expand Up @@ -493,6 +501,7 @@ SupplementaryOutputPathsComputer::computeOutputPathsForOneInput(

SupplementaryOutputPaths sop;
sop.ObjCHeaderOutputPath = objcHeaderOutputPath;
sop.CxxHeaderOutputPath = cxxHeaderOutputPath;
sop.ModuleOutputPath = moduleOutputPath;
sop.ModuleDocOutputPath = moduleDocOutputPath;
sop.DependenciesFilePath = dependenciesFilePath;
Expand Down Expand Up @@ -578,6 +587,7 @@ createFromTypeToPathMap(const TypeToPathMap *map) {
return paths;
const std::pair<file_types::ID, std::string &> typesAndStrings[] = {
{file_types::TY_ObjCHeader, paths.ObjCHeaderOutputPath},
{file_types::TY_ObjCHeader, paths.CxxHeaderOutputPath},
Copy link
Member

Choose a reason for hiding this comment

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

This is a problem, because we have two different outputs associated with the same entry in the output file map, so we'll end up writing both the ObjC and C++ header to the same place. I've fixed the issue via #41015, but I think we should reconsider having separate ObjC vs. C++ generated headers. It's a mess for the build system, and what would we do about Objective-C++? Instead, I think we should generate a single header that uses #ifdefs to appropriately compile as C/ObjC/C++/ObjC++.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Doug, thanks for fixing this, and sorry about the breakage. I'm not sure if we would like to generate a single header, instead we actually would prefer to generate multiple C++ headers as they will be target-specific, one per target, and then have the build system create a unified C++ header one from them. Do you think that would be a reasonable idea, and should we even support path map in that case? Also, I don't believe we want to emit Objective-C++ headers, as that would be ambiguous - we would either want to emit Objective-C or C++, and then the programmer can import their preferred interface into Objective-C++ as desired.

{file_types::TY_SwiftModuleFile, paths.ModuleOutputPath},
{file_types::TY_SwiftModuleDocFile, paths.ModuleDocOutputPath},
{file_types::TY_SwiftSourceInfoFile, paths.ModuleSourceInfoOutputPath},
Expand Down Expand Up @@ -606,17 +616,16 @@ createFromTypeToPathMap(const TypeToPathMap *map) {
Optional<std::vector<SupplementaryOutputPaths>>
SupplementaryOutputPathsComputer::readSupplementaryOutputFileMap() const {
if (Arg *A = Args.getLastArg(
options::OPT_emit_objc_header_path,
options::OPT_emit_module_path,
options::OPT_emit_module_doc_path,
options::OPT_emit_dependencies_path,
options::OPT_emit_reference_dependencies_path,
options::OPT_serialize_diagnostics_path,
options::OPT_emit_loaded_module_trace_path,
options::OPT_emit_module_interface_path,
options::OPT_emit_private_module_interface_path,
options::OPT_emit_module_source_info_path,
options::OPT_emit_tbd_path)) {
options::OPT_emit_objc_header_path, options::OPT_emit_cxx_header_path,
options::OPT_emit_module_path, options::OPT_emit_module_doc_path,
options::OPT_emit_dependencies_path,
options::OPT_emit_reference_dependencies_path,
options::OPT_serialize_diagnostics_path,
options::OPT_emit_loaded_module_trace_path,
options::OPT_emit_module_interface_path,
options::OPT_emit_private_module_interface_path,
options::OPT_emit_module_source_info_path,
options::OPT_emit_tbd_path)) {
Diags.diagnose(SourceLoc(),
diag::error_cannot_have_supplementary_outputs,
A->getSpelling(), "-supplementary-output-file-map");
Expand Down
5 changes: 5 additions & 0 deletions lib/Frontend/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ CompilerInvocation::getObjCHeaderOutputPathForAtMostOnePrimary() const {
return getPrimarySpecificPathsForAtMostOnePrimary()
.SupplementaryOutputs.ObjCHeaderOutputPath;
}
std::string
CompilerInvocation::getCxxHeaderOutputPathForAtMostOnePrimary() const {
return getPrimarySpecificPathsForAtMostOnePrimary()
.SupplementaryOutputs.CxxHeaderOutputPath;
}
std::string CompilerInvocation::getModuleOutputPathForAtMostOnePrimary() const {
return getPrimarySpecificPathsForAtMostOnePrimary()
.SupplementaryOutputs.ModuleOutputPath;
Expand Down
6 changes: 6 additions & 0 deletions lib/Frontend/FrontendInputsAndOutputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,12 @@ bool FrontendInputsAndOutputs::hasObjCHeaderOutputPath() const {
return outs.ObjCHeaderOutputPath;
});
}
bool FrontendInputsAndOutputs::hasCxxHeaderOutputPath() const {
return hasSupplementaryOutputPath(
[](const SupplementaryOutputPaths &outs) -> const std::string & {
return outs.CxxHeaderOutputPath;
});
}
bool FrontendInputsAndOutputs::hasLoadedModuleTracePath() const {
return hasSupplementaryOutputPath(
[](const SupplementaryOutputPaths &outs) -> const std::string & {
Expand Down
13 changes: 6 additions & 7 deletions lib/Frontend/FrontendOptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,11 @@ void FrontendOptions::forAllOutputPaths(
}
const SupplementaryOutputPaths &outs =
input.getPrimarySpecificPaths().SupplementaryOutputs;
const std::string *outputs[] = {&outs.ModuleOutputPath,
&outs.ModuleDocOutputPath,
&outs.ModuleInterfaceOutputPath,
&outs.PrivateModuleInterfaceOutputPath,
&outs.ObjCHeaderOutputPath,
&outs.ModuleSourceInfoOutputPath};
const std::string *outputs[] = {
&outs.ModuleOutputPath, &outs.ModuleDocOutputPath,
&outs.ModuleInterfaceOutputPath, &outs.PrivateModuleInterfaceOutputPath,
&outs.ObjCHeaderOutputPath, &outs.CxxHeaderOutputPath,
&outs.ModuleSourceInfoOutputPath};
for (const std::string *next : outputs) {
if (!next->empty())
fn(*next);
Expand Down Expand Up @@ -449,7 +448,7 @@ bool FrontendOptions::canActionEmitModuleSummary(ActionType action) {
llvm_unreachable("unhandled action");
}

bool FrontendOptions::canActionEmitObjCHeader(ActionType action) {
bool FrontendOptions::canActionEmitClangHeader(ActionType action) {
switch (action) {
case ActionType::NoneAction:
case ActionType::Parse:
Expand Down
22 changes: 22 additions & 0 deletions lib/FrontendTool/FrontendTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M,
});
}

/// Prints the C++ "generated header" interface for \p M to \p
/// outputPath.
///
/// ...unless \p outputPath is empty, in which case it does nothing.
///
/// \returns true if there were any errors
///
/// \see swift::printAsCxx
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: that function is spelled swift::printAsCXX with capital XX :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I'll fix it up in the next patch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

static bool printAsCxxIfNeeded(StringRef outputPath, ModuleDecl *M) {
if (outputPath.empty())
return false;
return withOutputFile(
M->getDiags(), outputPath,
[&](raw_ostream &os) -> bool { return printAsCXX(os, M); });
}

/// Prints the stable module interface for \p M to \p outputPath.
///
/// ...unless \p outputPath is empty, in which case it does nothing.
Expand Down Expand Up @@ -826,6 +842,12 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs(
Invocation.getObjCHeaderOutputPathForAtMostOnePrimary(),
Instance.getMainModule(), BridgingHeaderPathForPrint);
}
if ((!Context.hadError() || opts.AllowModuleWithCompilerErrors) &&
opts.InputsAndOutputs.hasCxxHeaderOutputPath()) {
hadAnyError |= printAsCxxIfNeeded(
Invocation.getCxxHeaderOutputPathForAtMostOnePrimary(),
Instance.getMainModule());
}

// Only want the header if there's been any errors, ie. there's not much
// point outputting a swiftinterface for an invalid module
Expand Down
18 changes: 16 additions & 2 deletions lib/PrintAsObjC/PrintAsObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ static void writeEpilogue(raw_ostream &os) {
"#endif\n";
}

static std::string computeMacroGuard(const ModuleDecl *M) {
return (llvm::Twine(M->getNameStr().upper()) + "_SWIFT_H").str();
}

bool swift::printAsObjC(raw_ostream &os, ModuleDecl *M,
StringRef bridgingHeader) {
llvm::PrettyStackTraceString trace("While generating Objective-C header");
Expand All @@ -397,12 +401,22 @@ bool swift::printAsObjC(raw_ostream &os, ModuleDecl *M,
std::string moduleContentsBuf;
llvm::raw_string_ostream moduleContents{moduleContentsBuf};
printModuleContentsAsObjC(moduleContents, imports, *M);
std::string macroGuard = (llvm::Twine(M->getNameStr().upper()) + "_SWIFT_H").str();
writePrologue(os, M->getASTContext(), macroGuard);
writePrologue(os, M->getASTContext(), computeMacroGuard(M));
writeImports(os, imports, *M, bridgingHeader);
writePostImportPrologue(os, *M);
os << moduleContents.str();
writeEpilogue(os);

return false;
}

bool swift::printAsCXX(raw_ostream &os, ModuleDecl *M) {
llvm::PrettyStackTraceString trace("While generating C++ header");

writePrologue(os, M->getASTContext(), computeMacroGuard(M));
writePostImportPrologue(os, *M);
// TODO (Alex): emit module contents.
writeEpilogue(os);

return false;
}
7 changes: 7 additions & 0 deletions test/Frontend/supplementary-output-support.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
// RUN: not %target-swift-frontend -resolve-imports -emit-objc-header %s 2>&1 | %FileCheck -check-prefix=RESOLVE_IMPORTS_NO_OBJC_HEADER %s
// RESOLVE_IMPORTS_NO_OBJC_HEADER: error: this mode does not support emitting Objective-C headers{{$}}

// RUN: not %target-swift-frontend -parse -emit-cxx-header %s 2>&1 | %FileCheck -check-prefix=PARSE_NO_CXX_HEADER %s
// PARSE_NO_CXX_HEADER: error: this mode does not support emitting C++ headers{{$}}
// RUN: not %target-swift-frontend -dump-ast -emit-cxx-header %s 2>&1 | %FileCheck -check-prefix=DUMP_NO_CXX_HEADER %s
// DUMP_NO_CXX_HEADER: error: this mode does not support emitting C++ headers{{$}}
// RUN: not %target-swift-frontend -resolve-imports -emit-cxx-header %s 2>&1 | %FileCheck -check-prefix=RESOLVE_IMPORTS_NO_CXX_HEADER %s
// RESOLVE_IMPORTS_NO_CXX_HEADER: error: this mode does not support emitting C++ headers{{$}}

// RUN: not %target-swift-frontend -parse -emit-module-interface-path %t %s 2>&1 | %FileCheck -check-prefix=PARSE_NO_INTERFACE %s
// PARSE_NO_INTERFACE: error: this mode does not support emitting module interface files{{$}}
// RUN: not %target-swift-frontend -emit-silgen -emit-module-interface-path %t %s 2>&1 | %FileCheck -check-prefix=SILGEN_NO_INTERFACE %s
Expand Down
Loading