Skip to content

Commit 1489279

Browse files
mmhaaokblast
authored andcommitted
[CIR] Implement inline builtin functions (llvm#163911)
This patch implements the handling of inline builtin functions in CIR. There is a known limitation in CIR where direct calls to shadowed inline builtin functions are generated instead of the intrinsic. This is expected to be fixed by the introduction of the nobuiltin attribute in a future patch.
1 parent 2706fc0 commit 1489279

File tree

4 files changed

+164
-1
lines changed

4 files changed

+164
-1
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1675,7 +1675,25 @@ CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
16751675
// name to make it clear it's not the actual builtin.
16761676
auto fn = cast<cir::FuncOp>(curFn);
16771677
if (fn.getName() != fdInlineName && onlyHasInlineBuiltinDeclaration(fd)) {
1678-
cgm.errorNYI("Inline only builtin function calls");
1678+
cir::FuncOp clone =
1679+
mlir::cast_or_null<cir::FuncOp>(cgm.getGlobalValue(fdInlineName));
1680+
1681+
if (!clone) {
1682+
// Create a forward declaration - the body will be generated in
1683+
// generateCode when the function definition is processed
1684+
cir::FuncOp calleeFunc = emitFunctionDeclPointer(cgm, gd);
1685+
mlir::OpBuilder::InsertionGuard guard(builder);
1686+
builder.setInsertionPointToStart(cgm.getModule().getBody());
1687+
1688+
clone = builder.create<cir::FuncOp>(calleeFunc.getLoc(), fdInlineName,
1689+
calleeFunc.getFunctionType());
1690+
clone.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
1691+
&cgm.getMLIRContext(), cir::GlobalLinkageKind::InternalLinkage));
1692+
clone.setSymVisibility("private");
1693+
clone.setInlineKindAttr(cir::InlineAttr::get(
1694+
&cgm.getMLIRContext(), cir::InlineKind::AlwaysInline));
1695+
}
1696+
return CIRGenCallee::forDirect(clone, gd);
16791697
}
16801698

16811699
// Replaceable builtins provide their own implementation of a builtin. If we

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,49 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
551551
const auto funcDecl = cast<FunctionDecl>(gd.getDecl());
552552
curGD = gd;
553553

554+
if (funcDecl->isInlineBuiltinDeclaration()) {
555+
// When generating code for a builtin with an inline declaration, use a
556+
// mangled name to hold the actual body, while keeping an external
557+
// declaration in case the function pointer is referenced somewhere.
558+
std::string fdInlineName = (cgm.getMangledName(funcDecl) + ".inline").str();
559+
cir::FuncOp clone =
560+
mlir::cast_or_null<cir::FuncOp>(cgm.getGlobalValue(fdInlineName));
561+
if (!clone) {
562+
mlir::OpBuilder::InsertionGuard guard(builder);
563+
builder.setInsertionPoint(fn);
564+
clone = builder.create<cir::FuncOp>(fn.getLoc(), fdInlineName,
565+
fn.getFunctionType());
566+
clone.setLinkage(cir::GlobalLinkageKind::InternalLinkage);
567+
clone.setSymVisibility("private");
568+
clone.setInlineKind(cir::InlineKind::AlwaysInline);
569+
}
570+
fn.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
571+
fn.setSymVisibility("private");
572+
fn = clone;
573+
} else {
574+
// Detect the unusual situation where an inline version is shadowed by a
575+
// non-inline version. In that case we should pick the external one
576+
// everywhere. That's GCC behavior too.
577+
for (const FunctionDecl *pd = funcDecl->getPreviousDecl(); pd;
578+
pd = pd->getPreviousDecl()) {
579+
if (LLVM_UNLIKELY(pd->isInlineBuiltinDeclaration())) {
580+
std::string inlineName = funcDecl->getName().str() + ".inline";
581+
if (auto inlineFn = mlir::cast_or_null<cir::FuncOp>(
582+
cgm.getGlobalValue(inlineName))) {
583+
// Replace all uses of the .inline function with the regular function
584+
// FIXME: This performs a linear walk over the module. Introduce some
585+
// caching here.
586+
if (inlineFn
587+
.replaceAllSymbolUses(fn.getSymNameAttr(), cgm.getModule())
588+
.failed())
589+
llvm_unreachable("Failed to replace inline builtin symbol uses");
590+
inlineFn.erase();
591+
}
592+
break;
593+
}
594+
}
595+
}
596+
554597
SourceLocation loc = funcDecl->getLocation();
555598
Stmt *body = funcDecl->getBody();
556599
SourceRange bodyRange =

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,17 @@ void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
19171917
const Decl *decl = globalDecl.getDecl();
19181918
func.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(decl));
19191919
}
1920+
1921+
// If we plan on emitting this inline builtin, we can't treat it as a builtin.
1922+
const auto *fd = cast<FunctionDecl>(globalDecl.getDecl());
1923+
if (fd->isInlineBuiltinDeclaration()) {
1924+
const FunctionDecl *fdBody;
1925+
bool hasBody = fd->hasBody(fdBody);
1926+
(void)hasBody;
1927+
assert(hasBody && "Inline builtin declarations should always have an "
1928+
"available body!");
1929+
assert(!cir::MissingFeatures::attributeNoBuiltin());
1930+
}
19201931
}
19211932

19221933
void CIRGenModule::setCIRFunctionAttributesForDefinition(
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -disable-llvm-passes %s -o %t-cir.ll
4+
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -disable-llvm-passes %s -o %t.ll
6+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
7+
8+
typedef unsigned long size_t;
9+
10+
// Normal inline builtin declaration
11+
// When a builtin is redefined with extern inline + always_inline attributes,
12+
// the compiler creates a .inline version to avoid conflicts with the builtin
13+
14+
extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
15+
void *memcpy(void *a, const void *b, size_t c) {
16+
return __builtin_memcpy(a, b, c);
17+
}
18+
19+
void *test_inline_builtin_memcpy(void *a, const void *b, size_t c) {
20+
return memcpy(a, b, c);
21+
}
22+
23+
// CIR: cir.func internal private{{.*}}@memcpy.inline({{.*}}) -> !cir.ptr<!void> inline(always)
24+
25+
// CIR-LABEL: @test_inline_builtin_memcpy(
26+
// CIR: cir.call @memcpy.inline(
27+
// CIR: }
28+
29+
// LLVM: define internal ptr @memcpy.inline(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}
30+
31+
// LLVM-LABEL: @test_inline_builtin_memcpy(
32+
// LLVM: call ptr @memcpy.inline(
33+
34+
// OGCG-LABEL: @test_inline_builtin_memcpy(
35+
// OGCG: call ptr @memcpy.inline(
36+
37+
// OGCG: define internal ptr @memcpy.inline(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}
38+
39+
// Shadowing case
40+
// When a non-inline function definition shadows an inline builtin declaration,
41+
// the .inline version should be replaced with the regular function and removed.
42+
43+
extern inline __attribute__((always_inline)) __attribute__((gnu_inline))
44+
void *memmove(void *a, const void *b, size_t c) {
45+
return __builtin_memmove(a, b, c);
46+
}
47+
48+
void *memmove(void *a, const void *b, size_t c) {
49+
char *dst = (char *)a;
50+
const char *src = (const char *)b;
51+
if (dst < src) {
52+
for (size_t i = 0; i < c; i++) {
53+
dst[i] = src[i];
54+
}
55+
} else {
56+
for (size_t i = c; i > 0; i--) {
57+
dst[i-1] = src[i-1];
58+
}
59+
}
60+
return a;
61+
}
62+
63+
void *test_shadowed_memmove(void *a, const void *b, size_t c) {
64+
return memmove(a, b, c);
65+
}
66+
67+
// CIR: cir.func{{.*}}@memmove({{.*}}) -> !cir.ptr<!void>{{.*}}{
68+
// CIR-NOT: @memmove.inline
69+
70+
// CIR-LABEL: @test_shadowed_memmove(
71+
// CIR: cir.call @memmove(
72+
// CIR-NOT: @memmove.inline
73+
// CIR: }
74+
75+
// LLVM: define dso_local ptr @memmove(ptr{{.*}}, ptr{{.*}}, i64{{.*}}) #{{[0-9]+}}
76+
// LLVM-NOT: @memmove.inline
77+
78+
// LLVM-LABEL: @test_shadowed_memmove(
79+
// TODO - this deviation from OGCG is expected until we implement the nobuiltin
80+
// attribute. See CIRGenFunction::emitDirectCallee
81+
// LLVM: call ptr @memmove(
82+
// LLVM-NOT: @memmove.inline
83+
// LLVM: }
84+
85+
// OGCG: define dso_local ptr @memmove(ptr{{.*}} %a, ptr{{.*}} %b, i64{{.*}} %c) #{{[0-9]+}}
86+
// OGCG-NOT: @memmove.inline
87+
88+
// OGCG-LABEL: @test_shadowed_memmove(
89+
// OGCG: call void @llvm.memmove.p0.p0.i64(
90+
// OGCG-NOT: @memmove.inline
91+
// OGCG: }

0 commit comments

Comments
 (0)