Skip to content
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
20 changes: 19 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1675,7 +1675,25 @@ CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
// name to make it clear it's not the actual builtin.
auto fn = cast<cir::FuncOp>(curFn);
if (fn.getName() != fdInlineName && onlyHasInlineBuiltinDeclaration(fd)) {
cgm.errorNYI("Inline only builtin function calls");
cir::FuncOp clone =
mlir::cast_or_null<cir::FuncOp>(cgm.getGlobalValue(fdInlineName));

if (!clone) {
// Create a forward declaration - the body will be generated in
// generateCode when the function definition is processed
cir::FuncOp calleeFunc = emitFunctionDeclPointer(cgm, gd);
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(cgm.getModule().getBody());

clone = builder.create<cir::FuncOp>(calleeFunc.getLoc(), fdInlineName,
calleeFunc.getFunctionType());
clone.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
&cgm.getMLIRContext(), cir::GlobalLinkageKind::InternalLinkage));
clone.setSymVisibility("private");
clone.setInlineKindAttr(cir::InlineAttr::get(
&cgm.getMLIRContext(), cir::InlineKind::AlwaysInline));
}
return CIRGenCallee::forDirect(clone, gd);
}

// Replaceable builtins provide their own implementation of a builtin. If we
Expand Down
43 changes: 43 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,49 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
const auto funcDecl = cast<FunctionDecl>(gd.getDecl());
curGD = gd;

if (funcDecl->isInlineBuiltinDeclaration()) {
// When generating code for a builtin with an inline declaration, use a
// mangled name to hold the actual body, while keeping an external
// declaration in case the function pointer is referenced somewhere.
std::string fdInlineName = (cgm.getMangledName(funcDecl) + ".inline").str();
cir::FuncOp clone =
mlir::cast_or_null<cir::FuncOp>(cgm.getGlobalValue(fdInlineName));
if (!clone) {
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPoint(fn);
clone = builder.create<cir::FuncOp>(fn.getLoc(), fdInlineName,
fn.getFunctionType());
clone.setLinkage(cir::GlobalLinkageKind::InternalLinkage);
clone.setSymVisibility("private");
clone.setInlineKind(cir::InlineKind::AlwaysInline);
}
fn.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
fn.setSymVisibility("private");
fn = clone;
} else {
// 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.
for (const FunctionDecl *pd = funcDecl->getPreviousDecl(); pd;
pd = pd->getPreviousDecl()) {
if (LLVM_UNLIKELY(pd->isInlineBuiltinDeclaration())) {
std::string inlineName = funcDecl->getName().str() + ".inline";
if (auto inlineFn = mlir::cast_or_null<cir::FuncOp>(
cgm.getGlobalValue(inlineName))) {
// Replace all uses of the .inline function with the regular function
// FIXME: This performs a linear walk over the module. Introduce some
// caching here.
if (inlineFn
.replaceAllSymbolUses(fn.getSymNameAttr(), cgm.getModule())
.failed())
llvm_unreachable("Failed to replace inline builtin symbol uses");
inlineFn.erase();
}
break;
}
}
}

SourceLocation loc = funcDecl->getLocation();
Stmt *body = funcDecl->getBody();
SourceRange bodyRange =
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,17 @@ void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
const Decl *decl = globalDecl.getDecl();
func.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(decl));
}

// If we plan on emitting this inline builtin, we can't treat it as a builtin.
const auto *fd = cast<FunctionDecl>(globalDecl.getDecl());
if (fd->isInlineBuiltinDeclaration()) {
const FunctionDecl *fdBody;
bool hasBody = fd->hasBody(fdBody);
(void)hasBody;
assert(hasBody && "Inline builtin declarations should always have an "
"available body!");
assert(!cir::MissingFeatures::attributeNoBuiltin());
}
}

void CIRGenModule::setCIRFunctionAttributesForDefinition(
Expand Down
91 changes: 91 additions & 0 deletions clang/test/CIR/CodeGen/builtin_inline.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -disable-llvm-passes %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -disable-llvm-passes %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG

typedef unsigned long size_t;

// Normal inline builtin declaration
// When a builtin is redefined with extern inline + always_inline attributes,
// the compiler creates a .inline version to avoid conflicts with the builtin

extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
void *memcpy(void *a, const void *b, size_t c) {
return __builtin_memcpy(a, b, c);
}

void *test_inline_builtin_memcpy(void *a, const void *b, size_t c) {
return memcpy(a, b, c);
}

// CIR: cir.func internal private{{.*}}@memcpy.inline({{.*}}) -> !cir.ptr<!void> inline(always)

// CIR-LABEL: @test_inline_builtin_memcpy(
// CIR: cir.call @memcpy.inline(
// CIR: }

// LLVM: define internal ptr @memcpy.inline(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}

// LLVM-LABEL: @test_inline_builtin_memcpy(
// LLVM: call ptr @memcpy.inline(

// OGCG-LABEL: @test_inline_builtin_memcpy(
// OGCG: call ptr @memcpy.inline(

// OGCG: define internal ptr @memcpy.inline(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}

// Shadowing case
// When a non-inline function definition shadows an inline builtin declaration,
// the .inline version should be replaced with the regular function and removed.

extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
void *memmove(void *a, const void *b, size_t c) {
return __builtin_memmove(a, b, c);
}

void *memmove(void *a, const void *b, size_t c) {
char *dst = (char *)a;
const char *src = (const char *)b;
if (dst < src) {
for (size_t i = 0; i < c; i++) {
dst[i] = src[i];
}
} else {
for (size_t i = c; i > 0; i--) {
dst[i-1] = src[i-1];
}
}
return a;
}

void *test_shadowed_memmove(void *a, const void *b, size_t c) {
return memmove(a, b, c);
}

// CIR: cir.func{{.*}}@memmove({{.*}}) -> !cir.ptr<!void>{{.*}}{
// CIR-NOT: @memmove.inline

// CIR-LABEL: @test_shadowed_memmove(
// CIR: cir.call @memmove(
// CIR-NOT: @memmove.inline
// CIR: }

// LLVM: define dso_local ptr @memmove(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}
// LLVM-NOT: @memmove.inline

// LLVM-LABEL: @test_shadowed_memmove(
// TODO - this deviation from OGCG is expected until we implement the nobuiltin
// attribute. See CIRGenFunction::emitDirectCallee
// LLVM: call ptr @memmove(
// LLVM-NOT: @memmove.inline
// LLVM: }

// OGCG: define dso_local ptr @memmove(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}
// OGCG-NOT: @memmove.inline

// OGCG-LABEL: @test_shadowed_memmove(
// OGCG: call void @llvm.memmove.p0.p0.i64(
// OGCG-NOT: @memmove.inline
// OGCG: }
Loading