Skip to content

Commit ac2b7c2

Browse files
Merge pull request #33881 from aschwaighofer/irgen_fix_asserting_local_extern_5.3
[5.3] IRGen: Fix asserting local extern declarations.
2 parents 45e46eb + 4a4796a commit ac2b7c2

File tree

6 files changed

+144
-10
lines changed

6 files changed

+144
-10
lines changed

lib/IRGen/GenClangDecl.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,43 @@ class ClangDeclRefFinder
3434
return true;
3535
}
3636
};
37+
38+
// If any (re)declaration of `decl` contains executable code, returns that
39+
// redeclaration; otherwise, returns nullptr.
40+
// In the case of a function, executable code is contained in the function
41+
// definition. In the case of a variable, executable code can be contained in
42+
// the initializer of the variable.
43+
clang::Decl *getDeclWithExecutableCode(clang::Decl *decl) {
44+
if (auto fd = dyn_cast<clang::FunctionDecl>(decl)) {
45+
const clang::FunctionDecl *definition;
46+
if (fd->hasBody(definition)) {
47+
return const_cast<clang::FunctionDecl *>(definition);
48+
}
49+
} else if (auto vd = dyn_cast<clang::VarDecl>(decl)) {
50+
clang::VarDecl *initializingDecl = vd->getInitializingDeclaration();
51+
if (initializingDecl) {
52+
return initializingDecl;
53+
}
54+
}
55+
56+
return nullptr;
57+
}
58+
3759
} // end anonymous namespace
3860

3961
void IRGenModule::emitClangDecl(const clang::Decl *decl) {
40-
auto valueDecl = dyn_cast<clang::ValueDecl>(decl);
41-
if (!valueDecl || valueDecl->isExternallyVisible()) {
62+
// Ignore this decl if we've seen it before.
63+
if (!GlobalClangDecls.insert(decl->getCanonicalDecl()).second)
64+
return;
65+
66+
// Fast path for the case where `decl` doesn't contain executable code, so it
67+
// can't reference any other declarations that we would need to emit.
68+
if (getDeclWithExecutableCode(const_cast<clang::Decl *>(decl)) == nullptr) {
4269
ClangCodeGen->HandleTopLevelDecl(
4370
clang::DeclGroupRef(const_cast<clang::Decl*>(decl)));
4471
return;
4572
}
4673

47-
if (!GlobalClangDecls.insert(decl->getCanonicalDecl()).second)
48-
return;
4974
SmallVector<const clang::Decl *, 8> stack;
5075
stack.push_back(decl);
5176

@@ -69,13 +94,15 @@ void IRGenModule::emitClangDecl(const clang::Decl *decl) {
6994

7095
while (!stack.empty()) {
7196
auto *next = const_cast<clang::Decl *>(stack.pop_back_val());
72-
if (auto fn = dyn_cast<clang::FunctionDecl>(next)) {
73-
const clang::FunctionDecl *definition;
74-
if (fn->hasBody(definition)) {
75-
refFinder.TraverseDecl(const_cast<clang::FunctionDecl *>(definition));
76-
next = const_cast<clang::FunctionDecl *>(definition);
77-
}
97+
if (clang::Decl *executableDecl = getDeclWithExecutableCode(next)) {
98+
refFinder.TraverseDecl(executableDecl);
99+
next = executableDecl;
78100
}
101+
102+
if (auto var = dyn_cast<clang::VarDecl>(next))
103+
if (!var->isFileVarDecl())
104+
continue;
105+
79106
ClangCodeGen->HandleTopLevelDecl(clang::DeclGroupRef(next));
80107
}
81108
}

test/IRGen/Inputs/local_extern.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
static inline int _no_prior_var() {
2+
extern int var;
3+
return var;
4+
}
5+
6+
static inline int _no_prior_func() {
7+
extern int func();
8+
return func();
9+
}
10+
11+
static int prior_var = 1;
12+
static inline int _prior_var() {
13+
extern int prior_var;
14+
return prior_var;
15+
}
16+
17+
static inline int prior_func() { return 1; }
18+
static inline int _prior_func() {
19+
extern int prior_func();
20+
return prior_func();
21+
}

test/IRGen/local_extern.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// RUN: %target-swift-frontend -import-objc-header %S/Inputs/local_extern.h %s -emit-ir | %FileCheck %s
2+
// CHECK: @var = external {{(dso_local )?}}global i32
3+
// CHECK: @prior_var = internal global i32
4+
// CHECK: declare {{(dso_local )?}}i32 @func
5+
// CHECK: define internal i32 @prior_func
6+
7+
print("\(_no_prior_var())")
8+
print("\(_no_prior_func())")
9+
print("\(_prior_var())")
10+
print("\(_prior_func())")
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#ifdef __cplusplus
2+
#define INLINE inline
3+
#else
4+
// When compiling as C, make the functions `static inline`. This is the flavor
5+
// of inline functions for which we require the behavior checked by this test.
6+
// Non-static C inline functions don't require Swift to emit LLVM IR for them
7+
// because the idea is that there will we one `.c` file that declares them
8+
// `extern inline`, causing an out-of-line definition to be emitted to the
9+
// corresponding .o file.
10+
#define INLINE static inline
11+
#endif
12+
13+
INLINE int notCalled() {
14+
return 42;
15+
}
16+
17+
INLINE int calledTransitively() {
18+
return 42;
19+
}
20+
21+
#ifdef __cplusplus
22+
class C {
23+
public:
24+
int memberFunctionCalledTransitively() {
25+
return 42;
26+
}
27+
};
28+
29+
inline int calledTransitivelyFromVarInit() {
30+
return 42;
31+
}
32+
33+
inline int varUsedFromSwift = calledTransitivelyFromVarInit();
34+
#else
35+
// C only allows constant initializers for variables with static storage
36+
// duration, so there's no way to initialize this with the result of a call to
37+
// an inline method. Just provide _some_ definition of `varImportedToSwift` so
38+
// we can import it in the test.
39+
static int varUsedFromSwift = 42;
40+
#endif
41+
42+
INLINE int calledFromSwift() {
43+
#ifdef __cplusplus
44+
C c;
45+
return calledTransitively() + c.memberFunctionCalledTransitively();
46+
#else
47+
return calledTransitively();
48+
#endif
49+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module EmitCalledInlineFunction {
2+
header "emit-called-inline-function.h"
3+
export *
4+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Test that we emit LLVM IR for inline functions that are called directly or
2+
// transitively from Swift.
3+
//
4+
// Test that we don't emit LLVM IR for inline functions that are not called from
5+
// Swift.
6+
7+
// RUN: %empty-directory(%t)
8+
// RUN: %target-swift-frontend %s -I %S/Inputs -Xcc -std=c99 -emit-ir -o - | %FileCheck %s -check-prefix C99 --implicit-check-not notCalled
9+
// RUN: %target-swift-frontend %s -I %S/Inputs -enable-cxx-interop -emit-ir -o - | %FileCheck %s -check-prefix CXX --implicit-check-not notCalled
10+
11+
import EmitCalledInlineFunction
12+
13+
// C99-DAG: define internal i32 @calledFromSwift()
14+
// C99-DAG: define internal i32 @calledTransitively()
15+
16+
// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z15calledFromSwiftv|"\?calledFromSwift@@YAHXZ"}}()
17+
// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z18calledTransitivelyv|"\?calledTransitively@@YAHXZ"}}()
18+
// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_ZN1C32memberFunctionCalledTransitivelyEv|"\?memberFunctionCalledTransitively@C@@QEAAHXZ"}}(%class.C* %this)
19+
// CXX-DAG: define linkonce_odr{{( dso_local)?}} i32 @{{_Z29calledTransitivelyFromVarInitv|"\?calledTransitivelyFromVarInit@@YAHXZ"}}()
20+
21+
calledFromSwift()
22+
23+
let _ = varUsedFromSwift

0 commit comments

Comments
 (0)