Skip to content

[CIR] Upstream minimal builtin function call support #142981

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 11, 2025
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
3 changes: 2 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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; }
Expand Down
28 changes: 28 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -229,6 +231,15 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we overloading this based on APSInt vs APInt? They are in the same inheritance tree.. We should probably only do APInt.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to handle APSInt to get signed constants represented correctly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we probably only have to handle APSInt. It has an (explicit) ctor from APInt. Wonder if we should just implement one in terms of the other.


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;
}
Expand Down
55 changes: 55 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
@@ -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()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test for one that has a floating point result?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could test float with __builtin_huge_val or builtin_inf (plus [f|l|fp16|fp128] suffixes).

// 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());
}
30 changes: 29 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,25 @@ class CIRGenCalleeInfo {
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) {}

Expand All @@ -69,6 +78,25 @@ class CIRGenCallee {
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);
}
Expand Down
53 changes: 46 additions & 7 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you've removed all instances of this MissingFeature. It should be removed from the header.

// 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);

Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,8 @@ class CIRGenFunction : public CIRGenTypeCache {
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,
Expand Down Expand Up @@ -711,6 +713,9 @@ class CIRGenFunction : public CIRGenTypeCache {

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,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CIR/CodeGen/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_clang_library(clangCIR
CIRGenClass.cpp
CIRGenCXXABI.cpp
CIRGenCXXExpr.cpp
CIRGenBuiltin.cpp
CIRGenDecl.cpp
CIRGenDeclOpenACC.cpp
CIRGenExpr.cpp
Expand Down
78 changes: 78 additions & 0 deletions clang/test/CIR/CodeGen/builtin_call.cpp
Original file line number Diff line number Diff line change
@@ -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();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still seem to have a lot of code not tested in here? I don't see any FP stuff here, for example.


// 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: }