Skip to content

[5.3] IRGen: Fix asserting local extern declarations. #33881

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
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
47 changes: 37 additions & 10 deletions lib/IRGen/GenClangDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,43 @@ 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<clang::FunctionDecl>(decl)) {
const clang::FunctionDecl *definition;
if (fd->hasBody(definition)) {
return const_cast<clang::FunctionDecl *>(definition);
}
} else if (auto vd = dyn_cast<clang::VarDecl>(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<clang::ValueDecl>(decl);
if (!valueDecl || valueDecl->isExternallyVisible()) {
// Ignore this decl if we've seen it before.
if (!GlobalClangDecls.insert(decl->getCanonicalDecl()).second)
return;

// 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<clang::Decl *>(decl)) == nullptr) {
ClangCodeGen->HandleTopLevelDecl(
clang::DeclGroupRef(const_cast<clang::Decl*>(decl)));
return;
}

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

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

while (!stack.empty()) {
auto *next = const_cast<clang::Decl *>(stack.pop_back_val());
if (auto fn = dyn_cast<clang::FunctionDecl>(next)) {
const clang::FunctionDecl *definition;
if (fn->hasBody(definition)) {
refFinder.TraverseDecl(const_cast<clang::FunctionDecl *>(definition));
next = const_cast<clang::FunctionDecl *>(definition);
}
if (clang::Decl *executableDecl = getDeclWithExecutableCode(next)) {
refFinder.TraverseDecl(executableDecl);
next = executableDecl;
}

if (auto var = dyn_cast<clang::VarDecl>(next))
if (!var->isFileVarDecl())
continue;

ClangCodeGen->HandleTopLevelDecl(clang::DeclGroupRef(next));
}
}
Expand Down
21 changes: 21 additions & 0 deletions test/IRGen/Inputs/local_extern.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
static inline int _no_prior_var() {
extern int var;
return var;
}

static inline int _no_prior_func() {
extern int func();
return func();
}

static int prior_var = 1;
static inline int _prior_var() {
extern int prior_var;
return prior_var;
}

static inline int prior_func() { return 1; }
static inline int _prior_func() {
extern int prior_func();
return prior_func();
}
10 changes: 10 additions & 0 deletions test/IRGen/local_extern.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %target-swift-frontend -import-objc-header %S/Inputs/local_extern.h %s -emit-ir | %FileCheck %s
// CHECK: @var = external {{(dso_local )?}}global i32
// CHECK: @prior_var = internal global i32
// CHECK: declare {{(dso_local )?}}i32 @func
// CHECK: define internal i32 @prior_func

print("\(_no_prior_var())")
print("\(_no_prior_func())")
print("\(_prior_var())")
print("\(_prior_func())")
49 changes: 49 additions & 0 deletions test/Interop/C/function/Inputs/emit-called-inline-function.h
Original file line number Diff line number Diff line change
@@ -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
}
4 changes: 4 additions & 0 deletions test/Interop/C/function/Inputs/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module EmitCalledInlineFunction {
header "emit-called-inline-function.h"
export *
}
23 changes: 23 additions & 0 deletions test/Interop/C/function/emit-called-inline-function-irgen.swift
Original file line number Diff line number Diff line change
@@ -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