Skip to content

Commit ab9f2fe

Browse files
committed
[cxx-interop] Fix linker errors when a template is used from different modules
If a templated C++ class is used from two different C++ modules, and those modules are included in Swift, Clang will create different two different redecls for the class & its methods. For each of the methods, only one of the redecls would actually have a body, the other would have empty bodies. That prevents `ClangDeclFinder` from properly finding the symbols referenced from a method body, leading to a linker error in some cases, when the first redecl is missing a body. This prevents `std::string` from being used on Linux: ``` /tmp/use-std-string-2dd593.o:use-std-string-2dd593.o:function void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag): error: undefined reference to 'bool __gnu_cxx::__is_null_pointer<char>(char*)' /tmp/use-std-string-2dd593.o:use-std-string-2dd593.o:function void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag): error: undefined reference to 'std::iterator_traits<char*>::difference_type std::distance<char*>(char*, char*)' clang-13: error: linker command failed with exit code 1 (use -v to see invocation) <unknown>:0: error: link command failed with exit code 1 (use -v to see invocation) ```
1 parent c3c9b1b commit ab9f2fe

File tree

7 files changed

+85
-5
lines changed

7 files changed

+85
-5
lines changed

lib/IRGen/GenClangDecl.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,15 @@ class ClangDeclFinder
8686
// the initializer of the variable.
8787
clang::Decl *getDeclWithExecutableCode(clang::Decl *decl) {
8888
if (auto fd = dyn_cast<clang::FunctionDecl>(decl)) {
89-
// If this is a potentially not-yet-instanciated template, we might
90-
// still have a body.
91-
if (fd->getTemplateInstantiationPattern())
92-
return fd;
93-
9489
const clang::FunctionDecl *definition;
9590
if (fd->hasBody(definition)) {
9691
return const_cast<clang::FunctionDecl *>(definition);
9792
}
93+
94+
// If this is a potentially not-yet-instanciated template, we might
95+
// still have a body.
96+
if (fd->getTemplateInstantiationPattern())
97+
return fd;
9898
} else if (auto vd = dyn_cast<clang::VarDecl>(decl)) {
9999
clang::VarDecl *initializingDecl = vd->getInitializingDeclaration();
100100
if (initializingDecl) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_A_H
2+
#define TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_A_H
3+
4+
#include "wrapper.h"
5+
6+
WrapperInt getWrapperIntA() {
7+
return WrapperInt();
8+
}
9+
10+
#endif // TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_A_H
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_B_H
2+
#define TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_B_H
3+
4+
#include "wrapper.h"
5+
6+
WrapperInt getWrapperIntB() {
7+
return WrapperInt();
8+
}
9+
10+
#endif // TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_B_H
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module Wrapper {
2+
header "wrapper.h"
3+
export *
4+
requires cplusplus
5+
}
6+
7+
module A {
8+
header "a.h"
9+
export *
10+
requires cplusplus
11+
}
12+
13+
module B {
14+
header "b.h"
15+
export *
16+
requires cplusplus
17+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_WRAPPER_H
2+
#define TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_WRAPPER_H
3+
4+
template <typename T>
5+
struct Wrapper {
6+
T t;
7+
int i = 0;
8+
9+
void foo() { i++; }
10+
11+
Wrapper() { foo(); }
12+
};
13+
14+
typedef Wrapper<int> WrapperInt;
15+
16+
#endif // TEST_INTEROP_CXX_TEMPLATES_INPUTS_MULTIPLE_MODULES_WRAPPER_H
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %target-swift-emit-ir %s -I %S/Inputs -enable-cxx-interop | %FileCheck %s
2+
3+
import A
4+
import B
5+
// Both A and B reference WrapperInt, and clang will instantiate two different redecls for WrapperInt.
6+
7+
let w = WrapperInt()
8+
// The constructor of Wrapper calls Wrapper::foo, we need to make sure that Wrapper::foo is emitted,
9+
// otherwise a linker error occurs.
10+
// CHECK: define linkonce_odr void @{{_ZN7WrapperIiE3fooEv|"?foo@?$Wrapper@H@@QEAAXXZ"}}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %target-run-simple-swift(-I %S/Inputs -Xfrontend -enable-cxx-interop)
2+
//
3+
// REQUIRES: executable_test
4+
5+
import A
6+
import B
7+
import StdlibUnittest
8+
9+
var TemplatesTestSuite = TestSuite("TemplatesTestSuite")
10+
11+
TemplatesTestSuite.test("instantiation-in-multiple-modules") {
12+
var w = WrapperInt()
13+
// Make sure that WrapperInt::foo gets called.
14+
expectEqual(w.i, 1)
15+
}
16+
17+
runAllTests()

0 commit comments

Comments
 (0)