diff --git a/lib/IRGen/GenClangDecl.cpp b/lib/IRGen/GenClangDecl.cpp index 00b5a0e697352..2ecf5c39a724a 100644 --- a/lib/IRGen/GenClangDecl.cpp +++ b/lib/IRGen/GenClangDecl.cpp @@ -34,11 +34,34 @@ class ClangDeclRefFinder return true; } }; + +// If any (re)declaration of `decl` contains executable code, returns that +// redeclaration; otherwise, returns nullptr. +// In the case of a function, executable code is contained in the function +// definition. In the case of a variable, executable code can be contained in +// the initializer of the variable. +clang::Decl *getDeclWithExecutableCode(clang::Decl *decl) { + if (auto fd = dyn_cast(decl)) { + const clang::FunctionDecl *definition; + if (fd->hasBody(definition)) { + return const_cast(definition); + } + } else if (auto vd = dyn_cast(decl)) { + clang::VarDecl *initializingDecl = vd->getInitializingDeclaration(); + if (initializingDecl) { + return initializingDecl; + } + } + + return nullptr; +} + } // end anonymous namespace void IRGenModule::emitClangDecl(const clang::Decl *decl) { - auto valueDecl = dyn_cast(decl); - if (!valueDecl || valueDecl->isExternallyVisible()) { + // Fast path for the case where `decl` doesn't contain executable code, so it + // can't reference any other declarations that we would need to emit. + if (getDeclWithExecutableCode(const_cast(decl)) == nullptr) { ClangCodeGen->HandleTopLevelDecl( clang::DeclGroupRef(const_cast(decl))); return; @@ -69,12 +92,9 @@ void IRGenModule::emitClangDecl(const clang::Decl *decl) { while (!stack.empty()) { auto *next = const_cast(stack.pop_back_val()); - if (auto fn = dyn_cast(next)) { - const clang::FunctionDecl *definition; - if (fn->hasBody(definition)) { - refFinder.TraverseDecl(const_cast(definition)); - next = const_cast(definition); - } + if (clang::Decl *executableDecl = getDeclWithExecutableCode(next)) { + refFinder.TraverseDecl(executableDecl); + next = executableDecl; } ClangCodeGen->HandleTopLevelDecl(clang::DeclGroupRef(next)); } diff --git a/test/Interop/C/function/Inputs/emit-called-inline-function.h b/test/Interop/C/function/Inputs/emit-called-inline-function.h new file mode 100644 index 0000000000000..437d3b8bac6c1 --- /dev/null +++ b/test/Interop/C/function/Inputs/emit-called-inline-function.h @@ -0,0 +1,49 @@ +#ifdef __cplusplus +#define INLINE inline +#else +// When compiling as C, make the functions `static inline`. This is the flavor +// of inline functions for which we require the behavior checked by this test. +// Non-static C inline functions don't require Swift to emit LLVM IR for them +// because the idea is that there will we one `.c` file that declares them +// `extern inline`, causing an out-of-line definition to be emitted to the +// corresponding .o file. +#define INLINE static inline +#endif + +INLINE int notCalled() { + return 42; +} + +INLINE int calledTransitively() { + return 42; +} + +#ifdef __cplusplus +class C { + public: + int memberFunctionCalledTransitively() { + return 42; + } +}; + +inline int calledTransitivelyFromVarInit() { + return 42; +} + +inline int varUsedFromSwift = calledTransitivelyFromVarInit(); +#else +// C only allows constant initializers for variables with static storage +// duration, so there's no way to initialize this with the result of a call to +// an inline method. Just provide _some_ definition of `varImportedToSwift` so +// we can import it in the test. +static int varUsedFromSwift = 42; +#endif + +INLINE int calledFromSwift() { +#ifdef __cplusplus + C c; + return calledTransitively() + c.memberFunctionCalledTransitively(); +#else + return calledTransitively(); +#endif +} diff --git a/test/Interop/C/function/Inputs/module.modulemap b/test/Interop/C/function/Inputs/module.modulemap new file mode 100644 index 0000000000000..77c866f587b72 --- /dev/null +++ b/test/Interop/C/function/Inputs/module.modulemap @@ -0,0 +1,4 @@ +module EmitCalledInlineFunction { + header "emit-called-inline-function.h" + export * +} diff --git a/test/Interop/C/function/emit-called-inline-function-irgen.swift b/test/Interop/C/function/emit-called-inline-function-irgen.swift new file mode 100644 index 0000000000000..851d855640d96 --- /dev/null +++ b/test/Interop/C/function/emit-called-inline-function-irgen.swift @@ -0,0 +1,23 @@ +// Test that we emit LLVM IR for inline functions that are called directly or +// transitively from Swift. +// +// Test that we don't emit LLVM IR for inline functions that are not called from +// Swift. + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -I %S/Inputs -Xcc -std=c99 -emit-ir -o - | %FileCheck %s -check-prefix C99 --implicit-check-not notCalled +// RUN: %target-swift-frontend %s -I %S/Inputs -enable-cxx-interop -emit-ir -o - | %FileCheck %s -check-prefix CXX --implicit-check-not notCalled + +import EmitCalledInlineFunction + +// C99-DAG: define internal i32 @calledFromSwift() +// C99-DAG: define internal i32 @calledTransitively() + +// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z15calledFromSwiftv|"\?calledFromSwift@@YAHXZ"}}() +// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z18calledTransitivelyv|"\?calledTransitively@@YAHXZ"}}() +// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_ZN1C32memberFunctionCalledTransitivelyEv|"\?memberFunctionCalledTransitively@C@@QEAAHXZ"}}(%class.C* %this) +// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z29calledTransitivelyFromVarInitv|"\?calledTransitivelyFromVarInit@@YAHXZ"}}() + +calledFromSwift() + +let _ = varUsedFromSwift