[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).
This commit is contained in:
Morris Hafner 2025-06-11 18:24:46 +02:00 committed by GitHub
parent 78765bb856
commit 8e4f0d8614
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 255 additions and 9 deletions

View File

@ -83,7 +83,6 @@ struct MissingFeatures {
static bool opFuncSetComdat() { return false; }
// CallOp handling
static bool opCallBuiltinFunc() { return false; }
static bool opCallPseudoDtor() { return false; }
static bool opCallAggregateArgs() { return false; }
static bool opCallPaddingArgs() { return false; }
@ -225,6 +224,8 @@ struct MissingFeatures {
static bool isMemcpyEquivalentSpecialMember() { return false; }
static bool isTrivialCtorOrDtor() { return false; }
static bool implicitConstructorArgs() { return false; }
static bool intrinsics() { return false; }
static bool attributeNoBuiltin() { return false; }
// Missing types
static bool dataMemberType() { return false; }

View File

@ -39,6 +39,34 @@ mlir::Value CIRGenBuilderTy::getArrayElement(mlir::Location arrayLocBegin,
return create<cir::PtrStrideOp>(arrayLocEnd, flatPtrTy, basePtr, idx);
}
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
llvm::APSInt intVal) {
bool isSigned = intVal.isSigned();
unsigned width = intVal.getBitWidth();
cir::IntType t = isSigned ? getSIntNTy(width) : getUIntNTy(width);
return getConstInt(loc, t,
isSigned ? intVal.getSExtValue() : intVal.getZExtValue());
}
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
llvm::APInt intVal) {
return getConstInt(loc, llvm::APSInt(intVal));
}
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, mlir::Type t,
uint64_t c) {
assert(mlir::isa<cir::IntType>(t) && "expected cir::IntType");
return create<cir::ConstantOp>(loc, cir::IntAttr::get(t, c));
}
cir::ConstantOp
clang::CIRGen::CIRGenBuilderTy::getConstFP(mlir::Location loc, mlir::Type t,
llvm::APFloat fpVal) {
assert(mlir::isa<cir::CIRFPTypeInterface>(t) &&
"expected floating point type");
return create<cir::ConstantOp>(loc, getAttr<cir::FPAttr>(t, fpVal));
}
// This can't be defined in Address.h because that file is included by
// CIRGenBuilder.h
Address Address::withElementType(CIRGenBuilderTy &builder,

View File

@ -11,10 +11,12 @@
#include "Address.h"
#include "CIRGenTypeCache.h"
#include "clang/CIR/Interfaces/CIRFPTypeInterface.h"
#include "clang/CIR/MissingFeatures.h"
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/STLExtras.h"
namespace clang::CIRGen {
@ -229,6 +231,15 @@ public:
cir::IntType getUInt32Ty() { return typeCache.UInt32Ty; }
cir::IntType getUInt64Ty() { return typeCache.UInt64Ty; }
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APSInt intVal);
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APInt intVal);
cir::ConstantOp getConstInt(mlir::Location loc, mlir::Type t, uint64_t c);
cir::ConstantOp getConstFP(mlir::Location loc, mlir::Type t,
llvm::APFloat fpVal);
bool isInt8Ty(mlir::Type i) {
return i == typeCache.UInt8Ty || i == typeCache.SInt8Ty;
}

View File

@ -0,0 +1,55 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This contains code to emit Builtin calls as CIR or a function call to be
// later resolved.
//
//===----------------------------------------------------------------------===//
#include "CIRGenCall.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "CIRGenValue.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Value.h"
#include "mlir/Support/LLVM.h"
#include "clang/AST/Expr.h"
#include "clang/AST/GlobalDecl.h"
#include "llvm/Support/ErrorHandling.h"
using namespace clang;
using namespace clang::CIRGen;
RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
const CallExpr *e,
ReturnValueSlot returnValue) {
// See if we can constant fold this builtin. If so, don't emit it at all.
// TODO: Extend this handling to all builtin calls that we can constant-fold.
Expr::EvalResult result;
if (e->isPRValue() && e->EvaluateAsRValue(result, cgm.getASTContext()) &&
!result.hasSideEffects()) {
if (result.Val.isInt()) {
return RValue::get(builder.getConstInt(getLoc(e->getSourceRange()),
result.Val.getInt()));
}
if (result.Val.isFloat()) {
// Note: we are using result type of CallExpr to determine the type of
// the constant. Classic codegen uses the result value to determine the
// type. We feel it should be Ok to use expression type because it is
// hard to imagine a builtin function evaluates to a value that
// over/underflows its own defined type.
mlir::Type type = convertType(e->getType());
return RValue::get(builder.getConstFP(getLoc(e->getExprLoc()), type,
result.Val.getFloat()));
}
}
mlir::Location loc = getLoc(e->getExprLoc());
cgm.errorNYI(loc, "non constant foldable builtin calls");
return getUndefRValue(e->getType());
}

View File

@ -44,16 +44,25 @@ public:
class CIRGenCallee {
enum class SpecialKind : uintptr_t {
Invalid,
Builtin,
Last = Invalid,
Last = Builtin,
};
struct BuiltinInfoStorage {
const clang::FunctionDecl *decl;
unsigned id;
};
SpecialKind kindOrFunctionPtr;
union {
CIRGenCalleeInfo abstractInfo;
BuiltinInfoStorage builtinInfo;
};
explicit CIRGenCallee(SpecialKind kind) : kindOrFunctionPtr(kind) {}
public:
CIRGenCallee() : kindOrFunctionPtr(SpecialKind::Invalid) {}
@ -69,6 +78,25 @@ public:
return CIRGenCallee(abstractInfo, funcPtr);
}
bool isBuiltin() const { return kindOrFunctionPtr == SpecialKind::Builtin; }
const clang::FunctionDecl *getBuiltinDecl() const {
assert(isBuiltin());
return builtinInfo.decl;
}
unsigned getBuiltinID() const {
assert(isBuiltin());
return builtinInfo.id;
}
static CIRGenCallee forBuiltin(unsigned builtinID,
const clang::FunctionDecl *builtinDecl) {
CIRGenCallee result(SpecialKind::Builtin);
result.builtinInfo.decl = builtinDecl;
result.builtinInfo.id = builtinID;
return result;
}
bool isOrdinary() const {
return uintptr_t(kindOrFunctionPtr) > uintptr_t(SpecialKind::Last);
}

View File

@ -1029,8 +1029,48 @@ static cir::FuncOp emitFunctionDeclPointer(CIRGenModule &cgm, GlobalDecl gd) {
return cgm.getAddrOfFunction(gd);
}
static CIRGenCallee emitDirectCallee(CIRGenModule &cgm, GlobalDecl gd) {
assert(!cir::MissingFeatures::opCallBuiltinFunc());
// 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.
static bool onlyHasInlineBuiltinDeclaration(const FunctionDecl *fd) {
for (const FunctionDecl *pd = fd; pd; pd = pd->getPreviousDecl())
if (!pd->isInlineBuiltinDeclaration())
return false;
return true;
}
CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
const auto *fd = cast<FunctionDecl>(gd.getDecl());
if (unsigned builtinID = fd->getBuiltinID()) {
if (fd->getAttr<AsmLabelAttr>()) {
cgm.errorNYI("AsmLabelAttr");
}
StringRef ident = fd->getName();
std::string fdInlineName = (ident + ".inline").str();
bool isPredefinedLibFunction =
cgm.getASTContext().BuiltinInfo.isPredefinedLibFunction(builtinID);
bool hasAttributeNoBuiltin = false;
assert(!cir::MissingFeatures::attributeNoBuiltin());
// When directing calling an inline builtin, call it through it's mangled
// 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");
}
// Replaceable builtins provide their own implementation of a builtin. If we
// are in an inline builtin implementation, avoid trivial infinite
// recursion. Honor __attribute__((no_builtin("foo"))) or
// __attribute__((no_builtin)) on the current function unless foo is
// not a predefined library function which means we must generate the
// builtin no matter what.
else if (!isPredefinedLibFunction || !hasAttributeNoBuiltin)
return CIRGenCallee::forBuiltin(builtinID, fd);
}
cir::FuncOp callee = emitFunctionDeclPointer(cgm, gd);
@ -1106,7 +1146,7 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *e) {
} else if (const auto *declRef = dyn_cast<DeclRefExpr>(e)) {
// Resolve direct calls.
const auto *funcDecl = cast<FunctionDecl>(declRef->getDecl());
return emitDirectCallee(cgm, funcDecl);
return emitDirectCallee(funcDecl);
} else if (isa<MemberExpr>(e)) {
cgm.errorNYI(e->getSourceRange(),
"emitCallee: call to member function is NYI");
@ -1162,10 +1202,9 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
CIRGenCallee callee = emitCallee(e->getCallee());
if (e->getBuiltinCallee()) {
cgm.errorNYI(e->getSourceRange(), "call to builtin functions");
}
assert(!cir::MissingFeatures::opCallBuiltinFunc());
if (callee.isBuiltin())
return emitBuiltinExpr(callee.getBuiltinDecl(), callee.getBuiltinID(), e,
returnValue);
if (isa<CXXPseudoDestructorExpr>(e->getCallee())) {
cgm.errorNYI(e->getSourceRange(), "call to pseudo destructor");

View File

@ -665,6 +665,8 @@ private:
void emitAndUpdateRetAlloca(clang::QualType type, mlir::Location loc,
clang::CharUnits alignment);
CIRGenCallee emitDirectCallee(const GlobalDecl &gd);
public:
Address emitAddrOfFieldStorage(Address base, const FieldDecl *field,
llvm::StringRef fieldName,
@ -711,6 +713,9 @@ public:
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
RValue emitBuiltinExpr(const clang::GlobalDecl &gd, unsigned builtinID,
const clang::CallExpr *e, ReturnValueSlot returnValue);
RValue emitCall(const CIRGenFunctionInfo &funcInfo,
const CIRGenCallee &callee, ReturnValueSlot returnValue,
const CallArgList &args, cir::CIRCallOpInterface *callOp,

View File

@ -13,6 +13,7 @@ add_clang_library(clangCIR
CIRGenClass.cpp
CIRGenCXXABI.cpp
CIRGenCXXExpr.cpp
CIRGenBuiltin.cpp
CIRGenDecl.cpp
CIRGenDeclOpenACC.cpp
CIRGenExpr.cpp

View File

@ -0,0 +1,78 @@
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
constexpr extern int cx_var = __builtin_is_constant_evaluated();
// CIR: cir.global {{.*}} @cx_var = #cir.int<1> : !s32i
// LLVM: @cx_var = {{.*}} i32 1
// OGCG: @cx_var = {{.*}} i32 1
constexpr extern float cx_var_single = __builtin_huge_valf();
// CIR: cir.global {{.*}} @cx_var_single = #cir.fp<0x7F800000> : !cir.float
// LLVM: @cx_var_single = {{.*}} float 0x7FF0000000000000
// OGCG: @cx_var_single = {{.*}} float 0x7FF0000000000000
constexpr extern long double cx_var_ld = __builtin_huge_vall();
// CIR: cir.global {{.*}} @cx_var_ld = #cir.fp<0x7FFF8000000000000000> : !cir.long_double<!cir.f80>
// LLVM: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
// OGCG: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
int is_constant_evaluated() {
return __builtin_is_constant_evaluated();
}
// CIR: cir.func @_Z21is_constant_evaluatedv() -> !s32i
// CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
// LLVM: define {{.*}}i32 @_Z21is_constant_evaluatedv()
// LLVM: %[[MEM:.+]] = alloca i32
// LLVM: store i32 0, ptr %[[MEM]]
// LLVM: %[[RETVAL:.+]] = load i32, ptr %[[MEM]]
// LLVM: ret i32 %[[RETVAL]]
// LLVM: }
// OGCG: define {{.*}}i32 @_Z21is_constant_evaluatedv()
// OGCG: ret i32 0
// OGCG: }
long double constant_fp_builtin_ld() {
return __builtin_fabsl(-0.1L);
}
// CIR: cir.func @_Z22constant_fp_builtin_ldv() -> !cir.long_double<!cir.f80>
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.long_double<!cir.f80>
// LLVM: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
// LLVM: %[[MEM:.+]] = alloca x86_fp80
// LLVM: store x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD, ptr %[[MEM]]
// LLVM: %[[RETVAL:.+]] = load x86_fp80, ptr %[[MEM]]
// LLVM: ret x86_fp80 %[[RETVAL]]
// LLVM: }
// OGCG: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
// OGCG: ret x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD
// OGCG: }
float constant_fp_builtin_single() {
return __builtin_fabsf(-0.1f);
}
// CIR: cir.func @_Z26constant_fp_builtin_singlev() -> !cir.float
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.float
// LLVM: define {{.*}}float @_Z26constant_fp_builtin_singlev()
// LLVM: %[[MEM:.+]] = alloca float
// LLVM: store float 0x3FB99999A0000000, ptr %[[MEM]]
// LLVM: %[[RETVAL:.+]] = load float, ptr %[[MEM]]
// LLVM: ret float %[[RETVAL]]
// LLVM: }
// OGCG: define {{.*}}float @_Z26constant_fp_builtin_singlev()
// OGCG: ret float 0x3FB99999A0000000
// OGCG: }