Fix inline builtin handling in case of redefinition

Basically, inline builtin definition are shadowed by externally visible
redefinition. This matches GCC behavior.

The implementation has to workaround the fact that:

1. inline builtin are renamed at callsite during codegen, but
2. they may be shadowed by a later external definition

As a consequence, during codegen, we need to walk redecls and eventually rewrite
some call sites, which is totally inelegant.

Differential Revision: https://reviews.llvm.org/D112059
This commit is contained in:
serge-sans-paille 2021-10-19 11:23:27 +02:00
parent 48677f58b0
commit 6bfc85c217
3 changed files with 74 additions and 11 deletions

View File

@ -1299,18 +1299,40 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
// When generating code for a builtin with an inline declaration, use a
// mangled name to hold the actual body, while keeping an external definition
// in case the function pointer is referenced somewhere.
if (FD->isInlineBuiltinDeclaration() && Fn) {
std::string FDInlineName = (Fn->getName() + ".inline").str();
llvm::Module *M = Fn->getParent();
llvm::Function *Clone = M->getFunction(FDInlineName);
if (!Clone) {
Clone = llvm::Function::Create(Fn->getFunctionType(),
llvm::GlobalValue::InternalLinkage,
Fn->getAddressSpace(), FDInlineName, M);
Clone->addFnAttr(llvm::Attribute::AlwaysInline);
if (Fn) {
if (FD->isInlineBuiltinDeclaration()) {
std::string FDInlineName = (Fn->getName() + ".inline").str();
llvm::Module *M = Fn->getParent();
llvm::Function *Clone = M->getFunction(FDInlineName);
if (!Clone) {
Clone = llvm::Function::Create(Fn->getFunctionType(),
llvm::GlobalValue::InternalLinkage,
Fn->getAddressSpace(), FDInlineName, M);
Clone->addFnAttr(llvm::Attribute::AlwaysInline);
}
Fn->setLinkage(llvm::GlobalValue::ExternalLinkage);
Fn = Clone;
}
// Detect the unusual situation where an inline version is shadowed by a
// non-inline version. In that case we should pick the external one
// everywhere. That's GCC behavior too. Unfortunately, I cannot find a way
// to detect that situation before we reach codegen, so do some late
// replacement.
else {
for (const FunctionDecl *PD = FD->getPreviousDecl(); PD;
PD = PD->getPreviousDecl()) {
if (LLVM_UNLIKELY(PD->isInlineBuiltinDeclaration())) {
std::string FDInlineName = (Fn->getName() + ".inline").str();
llvm::Module *M = Fn->getParent();
if (llvm::Function *Clone = M->getFunction(FDInlineName)) {
Clone->replaceAllUsesWith(Fn);
Clone->eraseFromParent();
}
break;
}
}
}
Fn->setLinkage(llvm::GlobalValue::ExternalLinkage);
Fn = Clone;
}
// Check if we should generate debug info for this function.

View File

@ -0,0 +1,21 @@
// RUN: %clang_cc1 -triple x86_64 -S -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
//
// Verifies that clang-generated *.inline are removed when shadowed by an external definition
// CHECK-NOT: strlen.inline
unsigned long strnlen(const char *, unsigned long);
void fortify_panic(const char *);
extern inline __attribute__((always_inline)) __attribute__((gnu_inline)) unsigned long strlen(const char *p) {
return 1;
}
unsigned long mystrlen(char const *s) {
return strlen(s);
}
unsigned long strlen(const char *s) {
return 2;
}
unsigned long yourstrlen(char const *s) {
return strlen(s);
}

View File

@ -0,0 +1,20 @@
// RUN: %clang_cc1 -triple x86_64 -S -emit-llvm -O1 -o - %s | FileCheck %s
//
// Verifies that the gnu_inline version is ignored in favor of the redecl
extern inline __attribute__((gnu_inline)) unsigned long some_size(int c) {
return 1;
}
unsigned long mycall(int s) {
// CHECK-LABEL: i64 @mycall
// CHECK: ret i64 2
return some_size(s);
}
unsigned long some_size(int c) {
return 2;
}
unsigned long yourcall(int s) {
// CHECK-LABEL: i64 @yourcall
// CHECK: ret i64 2
return some_size(s);
}