Skip to content

Commit 8e4f0d8

Browse files
authored
[CIR] Upstream minimal builtin function call support (#142981)
This patch adds all bits required to implement builtin function calls to ClangIR. It doesn't actually implement any of the builtins except those that fold to a constant ahead of CodeGen (`__builtin_is_constant_evaluated()` being one example).
1 parent 78765bb commit 8e4f0d8

File tree

9 files changed

+255
-9
lines changed

9 files changed

+255
-9
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ struct MissingFeatures {
8383
static bool opFuncSetComdat() { return false; }
8484

8585
// CallOp handling
86-
static bool opCallBuiltinFunc() { return false; }
8786
static bool opCallPseudoDtor() { return false; }
8887
static bool opCallAggregateArgs() { return false; }
8988
static bool opCallPaddingArgs() { return false; }
@@ -225,6 +224,8 @@ struct MissingFeatures {
225224
static bool isMemcpyEquivalentSpecialMember() { return false; }
226225
static bool isTrivialCtorOrDtor() { return false; }
227226
static bool implicitConstructorArgs() { return false; }
227+
static bool intrinsics() { return false; }
228+
static bool attributeNoBuiltin() { return false; }
228229

229230
// Missing types
230231
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenBuilder.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,34 @@ mlir::Value CIRGenBuilderTy::getArrayElement(mlir::Location arrayLocBegin,
3939
return create<cir::PtrStrideOp>(arrayLocEnd, flatPtrTy, basePtr, idx);
4040
}
4141

42+
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
43+
llvm::APSInt intVal) {
44+
bool isSigned = intVal.isSigned();
45+
unsigned width = intVal.getBitWidth();
46+
cir::IntType t = isSigned ? getSIntNTy(width) : getUIntNTy(width);
47+
return getConstInt(loc, t,
48+
isSigned ? intVal.getSExtValue() : intVal.getZExtValue());
49+
}
50+
51+
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
52+
llvm::APInt intVal) {
53+
return getConstInt(loc, llvm::APSInt(intVal));
54+
}
55+
56+
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, mlir::Type t,
57+
uint64_t c) {
58+
assert(mlir::isa<cir::IntType>(t) && "expected cir::IntType");
59+
return create<cir::ConstantOp>(loc, cir::IntAttr::get(t, c));
60+
}
61+
62+
cir::ConstantOp
63+
clang::CIRGen::CIRGenBuilderTy::getConstFP(mlir::Location loc, mlir::Type t,
64+
llvm::APFloat fpVal) {
65+
assert(mlir::isa<cir::CIRFPTypeInterface>(t) &&
66+
"expected floating point type");
67+
return create<cir::ConstantOp>(loc, getAttr<cir::FPAttr>(t, fpVal));
68+
}
69+
4270
// This can't be defined in Address.h because that file is included by
4371
// CIRGenBuilder.h
4472
Address Address::withElementType(CIRGenBuilderTy &builder,

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
#include "Address.h"
1313
#include "CIRGenTypeCache.h"
14+
#include "clang/CIR/Interfaces/CIRFPTypeInterface.h"
1415
#include "clang/CIR/MissingFeatures.h"
1516

1617
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
1718
#include "clang/CIR/MissingFeatures.h"
19+
#include "llvm/ADT/APFloat.h"
1820
#include "llvm/ADT/STLExtras.h"
1921

2022
namespace clang::CIRGen {
@@ -229,6 +231,15 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
229231
cir::IntType getUInt32Ty() { return typeCache.UInt32Ty; }
230232
cir::IntType getUInt64Ty() { return typeCache.UInt64Ty; }
231233

234+
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APSInt intVal);
235+
236+
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APInt intVal);
237+
238+
cir::ConstantOp getConstInt(mlir::Location loc, mlir::Type t, uint64_t c);
239+
240+
cir::ConstantOp getConstFP(mlir::Location loc, mlir::Type t,
241+
llvm::APFloat fpVal);
242+
232243
bool isInt8Ty(mlir::Type i) {
233244
return i == typeCache.UInt8Ty || i == typeCache.SInt8Ty;
234245
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This contains code to emit Builtin calls as CIR or a function call to be
10+
// later resolved.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "CIRGenCall.h"
15+
#include "CIRGenFunction.h"
16+
#include "CIRGenModule.h"
17+
#include "CIRGenValue.h"
18+
#include "mlir/IR/BuiltinAttributes.h"
19+
#include "mlir/IR/Value.h"
20+
#include "mlir/Support/LLVM.h"
21+
#include "clang/AST/Expr.h"
22+
#include "clang/AST/GlobalDecl.h"
23+
#include "llvm/Support/ErrorHandling.h"
24+
25+
using namespace clang;
26+
using namespace clang::CIRGen;
27+
28+
RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
29+
const CallExpr *e,
30+
ReturnValueSlot returnValue) {
31+
// See if we can constant fold this builtin. If so, don't emit it at all.
32+
// TODO: Extend this handling to all builtin calls that we can constant-fold.
33+
Expr::EvalResult result;
34+
if (e->isPRValue() && e->EvaluateAsRValue(result, cgm.getASTContext()) &&
35+
!result.hasSideEffects()) {
36+
if (result.Val.isInt()) {
37+
return RValue::get(builder.getConstInt(getLoc(e->getSourceRange()),
38+
result.Val.getInt()));
39+
}
40+
if (result.Val.isFloat()) {
41+
// Note: we are using result type of CallExpr to determine the type of
42+
// the constant. Classic codegen uses the result value to determine the
43+
// type. We feel it should be Ok to use expression type because it is
44+
// hard to imagine a builtin function evaluates to a value that
45+
// over/underflows its own defined type.
46+
mlir::Type type = convertType(e->getType());
47+
return RValue::get(builder.getConstFP(getLoc(e->getExprLoc()), type,
48+
result.Val.getFloat()));
49+
}
50+
}
51+
52+
mlir::Location loc = getLoc(e->getExprLoc());
53+
cgm.errorNYI(loc, "non constant foldable builtin calls");
54+
return getUndefRValue(e->getType());
55+
}

clang/lib/CIR/CodeGen/CIRGenCall.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,25 @@ class CIRGenCalleeInfo {
4444
class CIRGenCallee {
4545
enum class SpecialKind : uintptr_t {
4646
Invalid,
47+
Builtin,
4748

48-
Last = Invalid,
49+
Last = Builtin,
50+
};
51+
52+
struct BuiltinInfoStorage {
53+
const clang::FunctionDecl *decl;
54+
unsigned id;
4955
};
5056

5157
SpecialKind kindOrFunctionPtr;
5258

5359
union {
5460
CIRGenCalleeInfo abstractInfo;
61+
BuiltinInfoStorage builtinInfo;
5562
};
5663

64+
explicit CIRGenCallee(SpecialKind kind) : kindOrFunctionPtr(kind) {}
65+
5766
public:
5867
CIRGenCallee() : kindOrFunctionPtr(SpecialKind::Invalid) {}
5968

@@ -69,6 +78,25 @@ class CIRGenCallee {
6978
return CIRGenCallee(abstractInfo, funcPtr);
7079
}
7180

81+
bool isBuiltin() const { return kindOrFunctionPtr == SpecialKind::Builtin; }
82+
83+
const clang::FunctionDecl *getBuiltinDecl() const {
84+
assert(isBuiltin());
85+
return builtinInfo.decl;
86+
}
87+
unsigned getBuiltinID() const {
88+
assert(isBuiltin());
89+
return builtinInfo.id;
90+
}
91+
92+
static CIRGenCallee forBuiltin(unsigned builtinID,
93+
const clang::FunctionDecl *builtinDecl) {
94+
CIRGenCallee result(SpecialKind::Builtin);
95+
result.builtinInfo.decl = builtinDecl;
96+
result.builtinInfo.id = builtinID;
97+
return result;
98+
}
99+
72100
bool isOrdinary() const {
73101
return uintptr_t(kindOrFunctionPtr) > uintptr_t(SpecialKind::Last);
74102
}

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,8 +1029,48 @@ static cir::FuncOp emitFunctionDeclPointer(CIRGenModule &cgm, GlobalDecl gd) {
10291029
return cgm.getAddrOfFunction(gd);
10301030
}
10311031

1032-
static CIRGenCallee emitDirectCallee(CIRGenModule &cgm, GlobalDecl gd) {
1033-
assert(!cir::MissingFeatures::opCallBuiltinFunc());
1032+
// Detect the unusual situation where an inline version is shadowed by a
1033+
// non-inline version. In that case we should pick the external one
1034+
// everywhere. That's GCC behavior too.
1035+
static bool onlyHasInlineBuiltinDeclaration(const FunctionDecl *fd) {
1036+
for (const FunctionDecl *pd = fd; pd; pd = pd->getPreviousDecl())
1037+
if (!pd->isInlineBuiltinDeclaration())
1038+
return false;
1039+
return true;
1040+
}
1041+
1042+
CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
1043+
const auto *fd = cast<FunctionDecl>(gd.getDecl());
1044+
1045+
if (unsigned builtinID = fd->getBuiltinID()) {
1046+
if (fd->getAttr<AsmLabelAttr>()) {
1047+
cgm.errorNYI("AsmLabelAttr");
1048+
}
1049+
1050+
StringRef ident = fd->getName();
1051+
std::string fdInlineName = (ident + ".inline").str();
1052+
1053+
bool isPredefinedLibFunction =
1054+
cgm.getASTContext().BuiltinInfo.isPredefinedLibFunction(builtinID);
1055+
bool hasAttributeNoBuiltin = false;
1056+
assert(!cir::MissingFeatures::attributeNoBuiltin());
1057+
1058+
// When directing calling an inline builtin, call it through it's mangled
1059+
// name to make it clear it's not the actual builtin.
1060+
auto fn = cast<cir::FuncOp>(curFn);
1061+
if (fn.getName() != fdInlineName && onlyHasInlineBuiltinDeclaration(fd)) {
1062+
cgm.errorNYI("Inline only builtin function calls");
1063+
}
1064+
1065+
// Replaceable builtins provide their own implementation of a builtin. If we
1066+
// are in an inline builtin implementation, avoid trivial infinite
1067+
// recursion. Honor __attribute__((no_builtin("foo"))) or
1068+
// __attribute__((no_builtin)) on the current function unless foo is
1069+
// not a predefined library function which means we must generate the
1070+
// builtin no matter what.
1071+
else if (!isPredefinedLibFunction || !hasAttributeNoBuiltin)
1072+
return CIRGenCallee::forBuiltin(builtinID, fd);
1073+
}
10341074

10351075
cir::FuncOp callee = emitFunctionDeclPointer(cgm, gd);
10361076

@@ -1106,7 +1146,7 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *e) {
11061146
} else if (const auto *declRef = dyn_cast<DeclRefExpr>(e)) {
11071147
// Resolve direct calls.
11081148
const auto *funcDecl = cast<FunctionDecl>(declRef->getDecl());
1109-
return emitDirectCallee(cgm, funcDecl);
1149+
return emitDirectCallee(funcDecl);
11101150
} else if (isa<MemberExpr>(e)) {
11111151
cgm.errorNYI(e->getSourceRange(),
11121152
"emitCallee: call to member function is NYI");
@@ -1162,10 +1202,9 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
11621202

11631203
CIRGenCallee callee = emitCallee(e->getCallee());
11641204

1165-
if (e->getBuiltinCallee()) {
1166-
cgm.errorNYI(e->getSourceRange(), "call to builtin functions");
1167-
}
1168-
assert(!cir::MissingFeatures::opCallBuiltinFunc());
1205+
if (callee.isBuiltin())
1206+
return emitBuiltinExpr(callee.getBuiltinDecl(), callee.getBuiltinID(), e,
1207+
returnValue);
11691208

11701209
if (isa<CXXPseudoDestructorExpr>(e->getCallee())) {
11711210
cgm.errorNYI(e->getSourceRange(), "call to pseudo destructor");

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,8 @@ class CIRGenFunction : public CIRGenTypeCache {
665665
void emitAndUpdateRetAlloca(clang::QualType type, mlir::Location loc,
666666
clang::CharUnits alignment);
667667

668+
CIRGenCallee emitDirectCallee(const GlobalDecl &gd);
669+
668670
public:
669671
Address emitAddrOfFieldStorage(Address base, const FieldDecl *field,
670672
llvm::StringRef fieldName,
@@ -711,6 +713,9 @@ class CIRGenFunction : public CIRGenTypeCache {
711713

712714
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
713715

716+
RValue emitBuiltinExpr(const clang::GlobalDecl &gd, unsigned builtinID,
717+
const clang::CallExpr *e, ReturnValueSlot returnValue);
718+
714719
RValue emitCall(const CIRGenFunctionInfo &funcInfo,
715720
const CIRGenCallee &callee, ReturnValueSlot returnValue,
716721
const CallArgList &args, cir::CIRCallOpInterface *callOp,

clang/lib/CIR/CodeGen/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_clang_library(clangCIR
1313
CIRGenClass.cpp
1414
CIRGenCXXABI.cpp
1515
CIRGenCXXExpr.cpp
16+
CIRGenBuiltin.cpp
1617
CIRGenDecl.cpp
1718
CIRGenDeclOpenACC.cpp
1819
CIRGenExpr.cpp
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
3+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
5+
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
7+
8+
constexpr extern int cx_var = __builtin_is_constant_evaluated();
9+
10+
// CIR: cir.global {{.*}} @cx_var = #cir.int<1> : !s32i
11+
// LLVM: @cx_var = {{.*}} i32 1
12+
// OGCG: @cx_var = {{.*}} i32 1
13+
14+
constexpr extern float cx_var_single = __builtin_huge_valf();
15+
16+
// CIR: cir.global {{.*}} @cx_var_single = #cir.fp<0x7F800000> : !cir.float
17+
// LLVM: @cx_var_single = {{.*}} float 0x7FF0000000000000
18+
// OGCG: @cx_var_single = {{.*}} float 0x7FF0000000000000
19+
20+
constexpr extern long double cx_var_ld = __builtin_huge_vall();
21+
22+
// CIR: cir.global {{.*}} @cx_var_ld = #cir.fp<0x7FFF8000000000000000> : !cir.long_double<!cir.f80>
23+
// LLVM: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
24+
// OGCG: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
25+
26+
int is_constant_evaluated() {
27+
return __builtin_is_constant_evaluated();
28+
}
29+
30+
// CIR: cir.func @_Z21is_constant_evaluatedv() -> !s32i
31+
// CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
32+
33+
// LLVM: define {{.*}}i32 @_Z21is_constant_evaluatedv()
34+
// LLVM: %[[MEM:.+]] = alloca i32
35+
// LLVM: store i32 0, ptr %[[MEM]]
36+
// LLVM: %[[RETVAL:.+]] = load i32, ptr %[[MEM]]
37+
// LLVM: ret i32 %[[RETVAL]]
38+
// LLVM: }
39+
40+
// OGCG: define {{.*}}i32 @_Z21is_constant_evaluatedv()
41+
// OGCG: ret i32 0
42+
// OGCG: }
43+
44+
long double constant_fp_builtin_ld() {
45+
return __builtin_fabsl(-0.1L);
46+
}
47+
48+
// CIR: cir.func @_Z22constant_fp_builtin_ldv() -> !cir.long_double<!cir.f80>
49+
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.long_double<!cir.f80>
50+
51+
// LLVM: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
52+
// LLVM: %[[MEM:.+]] = alloca x86_fp80
53+
// LLVM: store x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD, ptr %[[MEM]]
54+
// LLVM: %[[RETVAL:.+]] = load x86_fp80, ptr %[[MEM]]
55+
// LLVM: ret x86_fp80 %[[RETVAL]]
56+
// LLVM: }
57+
58+
// OGCG: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
59+
// OGCG: ret x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD
60+
// OGCG: }
61+
62+
float constant_fp_builtin_single() {
63+
return __builtin_fabsf(-0.1f);
64+
}
65+
66+
// CIR: cir.func @_Z26constant_fp_builtin_singlev() -> !cir.float
67+
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.float
68+
69+
// LLVM: define {{.*}}float @_Z26constant_fp_builtin_singlev()
70+
// LLVM: %[[MEM:.+]] = alloca float
71+
// LLVM: store float 0x3FB99999A0000000, ptr %[[MEM]]
72+
// LLVM: %[[RETVAL:.+]] = load float, ptr %[[MEM]]
73+
// LLVM: ret float %[[RETVAL]]
74+
// LLVM: }
75+
76+
// OGCG: define {{.*}}float @_Z26constant_fp_builtin_singlev()
77+
// OGCG: ret float 0x3FB99999A0000000
78+
// OGCG: }

0 commit comments

Comments
 (0)